master
xy 1 year ago
parent d3fae3f6c2
commit 256236fe26

@ -3,6 +3,6 @@ ENV = 'development'
# base api
VUE_APP_BASE_API='https://cz-hjjc.115.langye.net'
VUE_APP_UPLOAD_API='https://cz-hjjc.115.langye.net/api/admin/upload-file'
VUE_APP_UPLOAD_API='https://cz-hjjc.115.langye.net/api/upload-file'
VUE_APP_PREVIEW=//view.langye.net/preview/onlinePreview
VUE_APP_MODULE_NAME=oa

@ -3,6 +3,6 @@ ENV = 'production'
# base api
VUE_APP_BASE_API='https://cz-hjjc.115.langye.net'
VUE_APP_UPLOAD_API='https://cz-hjjc.115.langye.net/api/admin/upload-file'
VUE_APP_UPLOAD_API='https://cz-hjjc.115.langye.net/api/upload-file'
VUE_APP_PREVIEW=//view.langye.net/preview/onlinePreview
VUE_APP_MODULE_NAME=oa

@ -5,6 +5,6 @@ ENV = 'staging'
# base api
VUE_APP_BASE_API='https://cz-hjjc.115.langye.net'
VUE_APP_UPLOAD_API='https://cz-hjjc.115.langye.net/api/admin/upload-file'
VUE_APP_UPLOAD_API='https://cz-hjjc.115.langye.net/api/upload-file'
VUE_APP_PREVIEW=//view.langye.net/preview/onlinePreview
VUE_APP_MODULE_NAME=oa

@ -8,19 +8,18 @@ export function flow(isLoading=false) {
})
}
export function flowList(params, type) {
export function preConfig(custom_model_id,isLoading=false) {
return request({
method: 'get',
url: `/api/oa/flow/list/${type}`,
params
url: `/api/oa/flow/create-pre/${custom_model_id}`,
isLoading
})
}
export function preConfig(custom_model_id,isLoading=false) {
export function fieldConfig(custom_model_id,isLoading=false) {
return request({
method: 'get',
url: `/api/oa/flow/create-pre/${custom_model_id}`,
url: `/api/oa/flow/custom-model-fields/${custom_model_id}`,
isLoading
})
}
@ -29,10 +28,20 @@ export function create(data,custom_model_id) {
return request({
method: 'post',
url: `/api/oa/flow/create/${custom_model_id}`,
data: qs.stringify(data, { arrayFormat: 'brackets' }),
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data
// data: qs.stringify(data, { arrayFormat: 'brackets' }),
// headers: {
// 'Content-Type': 'application/x-www-form-urlencoded'
// },
})
}
export function preDeal(flow_id,params,isLoading=false) {
return request({
method: 'get',
url: `/api/oa/flow/deal-pre/${flow_id}`,
params,
isLoading
})
}
@ -43,3 +52,30 @@ export function deal(data,flow_id) {
data
})
}
export function checkIsThroughNode(params,isLoading=false) {
return request({
method: 'get',
url: '/api/oa/flow/check-is-through-node',
params,
isLoading
})
}
export function getNextNodeUsers(params,isLoading=false) {
return request({
method: 'get',
url: '/api/oa/flow/get-next-node-users',
params,
isLoading
})
}
//查看列表相关
export function flowList(type,params,isLoading = false) {
return request({
method: 'get',
url: `/api/oa/flow/list/${type}`,
isLoading
})
}

@ -32,16 +32,18 @@ if (process.env.NODE_ENV === 'production') {
//Vue.use(ElementUI, { locale })
// 如果想要中文版 element-ui按如下方式声明
Vue.use(ElementUI)
import { VxeIcon, VxeTable, VxeColumn, VxeColgroup, VxeTableEditModule, VxeTableValidatorModule, VxeModal, VxeToolbar } from "vxe-table";
import { VxeButton, VxeIcon, VxeTable, VxeColumn, VxeColgroup, VxeModal, VxeToolbar, VxeTableEditModule, VxeTableValidatorModule, VxeTableExportModule } from "vxe-table";
import "vxe-table/styles/index.scss"
Vue.use(VxeTableEditModule);
Vue.use(VxeTableValidatorModule);
Vue.use(VxeTableExportModule);
Vue.use(VxeIcon);
Vue.use(VxeTable);
Vue.use(VxeColumn);
Vue.use(VxeColgroup);
Vue.use(VxeModal);
Vue.use(VxeToolbar);
Vue.use(VxeButton);
Vue.config.productionTip = false

@ -65,6 +65,11 @@ export const constantRoutes = [
name: 'flowIndex',
component: () => import('@/views/flow/index.vue'),
meta: { title: '流程创建', icon: 'tree' }
},{
path: '/list',
name: 'flowList',
component: () => import('@/views/flow/list.vue'),
meta: { title: '流程监管', icon: 'table', type: 'created-by-me' }
},{
path: 'create',
name: 'create',

@ -13,6 +13,13 @@ import { deepCopy } from "@/utils/index";
**/
export default function formBuilder (device, info, h, row, pWrite=false) {
let formItem;
//下拉选项
if(info?.stub) {
options = info?.stub?.split('|') || []
} else if (info?.selection_model) {
options = info.selection_model_items
}
let options;
if (device === 'desktop') {
if(info._writeable || (info.type === 'relation' && info._readable) || pWrite) {
switch (info.type) {
@ -201,6 +208,9 @@ export default function formBuilder (device, info, h, row, pWrite=false) {
clearable: true,
placeholder: info.help_text
},
style: {
width: '100%'
},
attrs: {
placeholder: info.help_text
},
@ -209,11 +219,11 @@ export default function formBuilder (device, info, h, row, pWrite=false) {
row ? this.$set(row,info.name,e) : this.$set(this.form,info.name,e)
}
}
},[1,2,3].map(option => (
},options.map(option => (
h('el-option',{
props: {
label: option,
value: option
label: typeof option === 'object' ? option.name : option,
value: typeof option === 'object' ? option.id : option,
}
})
)))
@ -231,12 +241,12 @@ export default function formBuilder (device, info, h, row, pWrite=false) {
row ? this.$set(row,info.name,e) : this.$set(this.form,info.name,e)
}
}
},[1,2,3].map(option => (
},options.map(option => (
h('el-radio',{
props: {
label: option
label: typeof option === 'object' ? option.id : option
}
},option)
},typeof option === 'object' ? option.name : option)
)))
break;
case 'file':
@ -259,6 +269,11 @@ export default function formBuilder (device, info, h, row, pWrite=false) {
}
},
onSuccess: (response, file, fileList) => {
fileList.forEach(file => {
if(file.response?.data && !file.response?.code) {
file.response = file.response.data
}
})
this.file[info.name] = fileList;
},
onRemove: (file, fileList) => {
@ -383,7 +398,7 @@ export default function formBuilder (device, info, h, row, pWrite=false) {
row ? this.$set(row,info.name,e) : this.$set(this.form,info.name,e)
}
}
},[1,2,3].map(option => (
},options.map(option => (
h('el-option',{
props: {
label: option,
@ -408,7 +423,7 @@ export default function formBuilder (device, info, h, row, pWrite=false) {
row ? this.$set(row,info.name,e) : this.$set(this.form,info.name,e)
}
}
},[1,2,3].map(option => (
},options.map(option => (
h('el-option',{
props: {
label: option,
@ -540,108 +555,126 @@ export default function formBuilder (device, info, h, row, pWrite=false) {
}
}
if(device === 'mobile') {
switch (info.type) {
case 'text':
formItem = h(formBuilderMap(device).get(info.type),{
props: {
name: info.name,
label: info.label,
value: this.form[info.name],
clearable: true,
placeholder: info.help_text
},
attrs: {
placeholder: info.help_text
},
on: {
input: e => {
this.$set(this.form,info.name,e)
if(info._writeable || pWrite) {
switch (info.type) {
case 'text':
formItem = h(formBuilderMap(device).get(info.type),{
props: {
name: info.name,
label: info.label,
value: row ? row[info.name] : this.form[info.name],
clearable: true,
placeholder: info.help_text
},
attrs: {
placeholder: info.help_text
},
on: {
input: e => {
this.$set(this.form,info.name,e)
}
}
}
})
break;
case 'textarea':
formItem = h(formBuilderMap(device).get(info.type),{
props: {
name: info.name,
label: info.label,
type: 'textarea',
value: this.form[info.name],
clearable: true,
placeholder: info.help_text
},
attrs: {
placeholder: info.help_text
},
on: {
input: e => {
this.$set(this.form,info.name,e)
})
break;
case 'textarea':
formItem = h(formBuilderMap(device).get(info.type),{
props: {
name: info.name,
label: info.label,
type: 'textarea',
value: row ? row[info.name] : this.form[info.name],
clearable: true,
placeholder: info.help_text
},
attrs: {
placeholder: info.help_text
},
on: {
input: e => {
this.$set(this.form,info.name,e)
}
}
}
})
break;
case 'date':
formItem = h('van-field',{
props: {
readonly: true,
clickable: true,
name: info.name,
label: info.label,
value: this.form[info.name],
clearable: true,
placeholder: info.help_text,
},
attrs: {
placeholder: info.help_text
},
on: {
click: _ => {
this.vanCalendarOption.forFormName = info.name;
this.$set(this.vanCalendarOption,'isShow',true);
})
break;
case 'date':
formItem = h('van-field',{
props: {
readonly: true,
clickable: true,
name: info.name,
label: info.label,
value: row ? row[info.name] : this.form[info.name],
clearable: true,
placeholder: info.help_text,
},
attrs: {
placeholder: info.help_text
},
on: {
click: _ => {
this.vanCalendarOption.forFormName = info.name;
this.$set(this.vanCalendarOption,'isShow',true);
}
}
}
})
break;
case 'datetime':
formItem = h('van-field',{
props: {
readonly: true,
clickable: true,
name: info.name,
label: info.label,
value: this.form[info.name],
clearable: true,
placeholder: info.help_text,
},
on: {
click:_ => {
this.vanTimePickerOption.forFormName = info.name;
this.$set(this.vanTimePickerOption,'isShow',true);
})
break;
case 'datetime':
formItem = h('van-field',{
props: {
readonly: true,
clickable: true,
name: info.name,
label: info.label,
value: row ? row[info.name] : this.form[info.name],
clearable: true,
placeholder: info.help_text,
},
on: {
click:_ => {
this.vanTimePickerOption.forFormName = info.name;
this.$set(this.vanTimePickerOption,'isShow',true);
}
}
}
})
break;
case 'select':
formItem = h('van-field',{
props: {
readonly: true,
clickable: true,
name: info.name,
label: info.label,
value: this.form[info.name],
clearable: true,
placeholder: info.help_text,
},
on: {
click:_ => {
this.vanPopupOption.forFormName = info.name;
this.$set(this.vanPopupOption,'columns',['杭州', '宁波', '温州', '嘉兴', '湖州']);
this.$set(this.vanPopupOption,'isShow',true);
})
break;
case 'select':
formItem = h('van-field',{
props: {
readonly: true,
clickable: true,
name: info.name,
label: info.label,
value: row ? row[info.name] : this.form[info.name],
clearable: true,
placeholder: info.help_text,
},
on: {
click:_ => {
this.vanPopupOption.forFormName = info.name;
this.$set(this.vanPopupOption,'columns',options);
this.$set(this.vanPopupOption,'isShow',true);
}
}
}
})
break;
})
break;
case 'relation':
formItem = h('div',this.form[info.name].map((sForm,sIndex) => (
h('van-cell',{
props: {
'arrow-direction': 'down',
title: info.label
}
},[
h('van-form',{
props: {
'scroll-to-error': true
}
},Array.from(this.subForm).map(i => i[1]?.customModel?.fields)?.flat().map(subField => formBuilder.bind(this)(device, subField, h, sForm, info._writeable)))
])
)))
break;
}
return formItem
}
return formItem
}
}

@ -18,3 +18,12 @@ export function validUsername(str) {
const valid_map = ['admin', 'editor']
return valid_map.indexOf(str.trim()) >= 0
}
export const validation = new Map([
['integer',/^-?\d+$/],//整数
['date',/(^\d{10})|(^\d{13})|(^((\d\d\d\d(.+)|\d\d(.+))?(0?[1-9](.+)|1[012](.+))?((0?[1-9]|[12]\d|3[01])(.+))?\s?)((0?[0-9]|1[0-9]|2[0-3])(.+))?((0?[0-9]|[1-5][0-9])(.+))?((0?[0-9]|[1-5][0-9])(.+))?)$/],
['email',/^[^\s@]+@[^\s@]+\.[^\s@]+$/],
['idcard',/(^([1-6][1-9]|50)\d{4}\d{2}((0[1-9])|10|11|12)(([0-2][1-9])|10|20|30|31)\d{3})|(^([1-6][1-9]|50)\d{4}(18|19|20)\d{2}((0[1-9])|10|11|12)(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx])$/],
['mobile',/(^1[3456789]\d{9})|(^(0\d{2,3}(-)*)?\d{7})$/],
['numeric',/^-?\d+(.\d+)?$/],
])

@ -36,7 +36,8 @@ export default {
type: Object,
default: () => ({}),
required: true
}
},
scriptContent: String
},
data() {
return {
@ -61,6 +62,15 @@ export default {
},
fileList(newVal) {
this.file = deepCopy(newVal)
},
scriptContent(newVal) {
if(newVal) {
try {
new Function(newVal).bind(this)()
} catch (err) {
console.error(err)
}
}
}
},
render(h) {

@ -1,18 +1,45 @@
<script>
import formBuilder from '@/utils/formBuilder'
import moment from "moment/moment";
import {deepCopy} from "@/utils";
export default {
props: {
readable: {
type: Array,
default: () => [],
required: true
},
writeable: {
type: Array,
default: () => [],
required: true
},
originalForm: {
type: Object,
default: () => ({}),
required: true
},
subForm: {
type: Map,
default: () => new Map()
},
device: {
type: String,
default: 'desktop',
required: true
},
info: {
fields: {
type: Array,
default: () => [],
required: true
}
},
fileList: {
type: Object,
default: () => ({}),
required: true
},
scriptContent: String
},
data() {
return {
@ -44,17 +71,37 @@ export default {
keys.forEach(key => {
this.form[key] = ''
})
},
originalForm(newVal) {
this.form = deepCopy(newVal)
},
fileList(newVal) {
this.file = deepCopy(newVal)
},
scriptContent(newVal) {
if(newVal) {
try {
new Function(newVal).bind(this)()
} catch (err) {
console.error(err)
}
}
}
},
render(h) {
const authFields = this.fields.map(field => ({
...field,
_readable: this.readable.indexOf(field.id) !== -1,
_writeable: this.writeable.indexOf(field.id) !== -1
}))
return h('div',[
h('van-form',{
props: {
'scroll-to-error': true
}
},this.info.map(field => formBuilder.bind(this)(this.device, field, h))),
},authFields.map(field => formBuilder.bind(this)(this.device, field, h))),
//calendar
this.info.findIndex(i => i.type === 'date') !== -1 ?
authFields.findIndex(i => i.type === 'date') !== -1 ?
h('van-calendar',{
ref: `vanCalendar`,
props: {
@ -73,7 +120,7 @@ export default {
}
}) : '',
//datetimepicker
this.info.findIndex(i => i.type === 'datetime') !== -1 ?
authFields.findIndex(i => i.type === 'datetime') !== -1 ?
h('van-popup',{
props: {
value: this.vanTimePickerOption.isShow,
@ -101,7 +148,7 @@ export default {
})
]) : '',
//popup
this.info.findIndex(i => i.type === 'datetime') !== -1 ?
authFields.findIndex(i => i.type === 'select') !== -1 ?
h('van-popup',{
props: {
value: this.vanPopupOption.isShow,
@ -120,9 +167,8 @@ export default {
},
on: {
confirm: value => {
console.log(value)
this.$set(this.form,this.vanPopupOption.forFormName,value)
this.$set(this.vanPopupOption,'isShow',false)
},
cancel: _ => {
this.$set(this.vanPopupOption,'isShow',false)

@ -1,6 +1,6 @@
<template>
<div class="container">
<el-card shadow="always" class="card" style="height: 2000px;">
<el-card shadow="always" class="card">
<template #header>
<p>{{ config.customModel ? config.customModel.name : '办理' }}</p>
</template>
@ -28,10 +28,19 @@
:original-form="form"
:readable="readableFields"
:file-list="fileList"
:script-content="scriptContent"
:writeable="writeableFields"></DesktopForm>
</template>
<template v-else>
<MobileForm :device="device" :info="fields"></MobileForm>
<MobileForm :device="device"
ref="mobileForm"
:sub-form="subConfig"
:fields="fields"
:original-form="form"
:readable="readableFields"
:file-list="fileList"
:script-content="scriptContent"
:writeable="writeableFields"></MobileForm>
</template>
</div>
</template>
@ -49,7 +58,7 @@
<script>
import DesktopForm from "./DesktopForm.vue";
import MobileForm from "./MobileForm.vue";
import { preConfig, deal, create } from "@/api/flow"
import { preConfig, preDeal, create, fieldConfig, getNextNodeUsers, checkIsThroughNode } from "@/api/flow"
import { deepCopy } from "@/utils";
export default {
components: {
@ -93,7 +102,7 @@ export default {
const { fields } = res?.customModel;
let subFormRequest = []
const getSubForm = (id) => {
subFormRequest.push(preConfig(id))
subFormRequest.push(fieldConfig(id))
}
fields.forEach(field => {
if(field.sub_custom_model_id) {
@ -117,7 +126,7 @@ export default {
}
},
submit() {
async submit() {
if(this.device === 'desktop') {
console.log(this.$refs['desktopForm'].form)
let copyForm = deepCopy(this.$refs['desktopForm'].form)
@ -127,26 +136,33 @@ export default {
copyForm[key] = value.map(i => i.response?.id)?.toString()
}
}
for (let key in copyForm) {
if(copyForm[key] instanceof Array && copyForm[key][0]) {
let formatObj = {}
let subKeys = Object.keys(copyForm[key][0])
subKeys.forEach(key => {
formatObj[key] = []
})
copyForm[key].forEach(item => {
subKeys.forEach(subKey => {
formatObj[subKey].push(item[subKey])
})
})
delete formatObj['_X_ROW_KEY']
copyForm[key] = formatObj
}
// for (let key in copyForm) {
// if(copyForm[key] instanceof Array && copyForm[key][0]) {
// let formatObj = {}
// let subKeys = Object.keys(copyForm[key][0])
// subKeys.forEach(key => {
// formatObj[key] = []
// })
// copyForm[key].forEach(item => {
// subKeys.forEach(subKey => {
// formatObj[subKey].push(item[subKey])
// })
// })
// delete formatObj['_X_ROW_KEY']
// copyForm[key] = formatObj
//
// }
// }
try {
const res = await create(copyForm,this.$route.query.module_id)
await getNextNodeUsers({
id: res.id,
next_node_id: this.config?.currentNode?.nextNodes[0]?.id
})
} catch(err) {
console.error(err)
}
create(copyForm,this.$route.query.module_id)
console.log(copyForm)
}
}
},
@ -165,6 +181,11 @@ export default {
},
node() {
return this.config?.currentNode || {}
},
scriptContent() {
if(this.config?.customModel?.js) {
return this.config?.customModel?.js
}
}
},
created() {

@ -0,0 +1,247 @@
<template>
<div style="padding: 20px">
<el-card shadow="always">
<template #header>
<p>{{ title }}</p>
<vxe-toolbar ref="toolbar" custom print>
<template #buttons>
<div class="selects">
<el-select
style="width: 180px"
filterable
clearable
size="small"
v-model="select.custom_model_id"
>
<el-option
v-for="item in models"
:key="item.id"
:value="item.id"
:label="item.name"
></el-option>
</el-select>
<el-select
style="width: 100px"
size="small"
v-model="select.date_type"
>
<el-option
v-for="item in dataTypes"
:key="item.value"
:value="item.value"
:label="item.label"
></el-option>
</el-select>
<el-date-picker
style="width: 240px"
value-format="yyyy-MM-dd"
size="small"
:value="select.date_range ? select.date_range.split('~') : ''"
type="daterange"
align="right"
unlink-panels
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
:picker-options="pickerOptions"
@input="
([start, end]) => (select.date_range = `${start}~${end}`)
"
>
</el-date-picker>
<el-input
clearable
v-model="select.keyword"
style="width: 160px"
placeholder="请输入关键词"
size="small"
></el-input>
<el-button
icon="el-icon-search"
type="primary"
size="small"
@click="getList"
>搜索</el-button
>
</div>
</template>
</vxe-toolbar>
</template>
<template #default>
<div>
<vxe-table
ref="table"
stripe
style="margin-top: 10px"
:loading="loading"
keep-source
show-overflow
:column-config="{ resizable: true }"
:print-config="{}"
:custom-config="{ mode: 'popup' }"
:data="list"
>
<vxe-column field="name" title="Name"></vxe-column>
<vxe-column field="role" title="Role"></vxe-column>
<vxe-column field="sex" title="Sex"></vxe-column>
</vxe-table>
<el-pagination
style="margin-top: 10px"
@size-change="
(e) => {
select.page_size = e;
select.page = 1;
getList();
}
"
@current-change="
(e) => {
select.page = e;
getList();
}
"
:current-page="select.page"
:page-sizes="[20, 30, 40, 50]"
:page-size="select.page_size"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
></el-pagination>
</div>
</template>
</el-card>
</div>
</template>
<script>
import { flowList } from "@/api/flow";
import moment from "moment/moment";
export default {
data() {
return {
pickerOptions: {
shortcuts: [
{
text: "一年前",
onClick(picker) {
picker.$emit("pick", [
moment().subtract(1, "years").toDate(),
new Date(),
]);
},
},
{
text: "一月前",
onClick(picker) {
picker.$emit("pick", [
moment().subtract(1, "months").toDate(),
new Date(),
]);
},
},
{
text: "一周前",
onClick(picker) {
picker.$emit("pick", [
moment().subtract(1, "weeks").toDate(),
new Date(),
]);
},
},
{
text: "一周后",
onClick(picker) {
picker.$emit("pick", [
new Date(),
moment().add(1, "weeks").toDate(),
]);
},
},
{
text: "一月后",
onClick(picker) {
picker.$emit("pick", [
new Date(),
moment().add(1, "months").toDate(),
]);
},
},
{
text: "一年后",
onClick(picker) {
picker.$emit("pick", [
new Date(),
moment().add(1, "years").toDate(),
]);
},
},
],
},
select: {
page: 1,
page_size: 20,
sort_name: "",
sort_type: "",
keyword: "",
department_id: "",
is_fav: "",
custom_model_id: "",
date_range: "",
date_type: "create_date",
},
loading: false,
list: [],
total: 0,
title: "",
models: [],
dataTypes: [
{
value: "create_date",
label: "录入时间",
},
{
value: "happened_date",
label: "经办时间",
},
],
};
},
methods: {
async getList() {
this.loading = true;
try {
const res = await flowList(this.$route.meta.type, this.select, false);
console.log(res);
this.list = res?.data?.data || [];
this.total = res?.data?.total ?? 0;
this.models = res.customModels;
this.title = res.pageTitle;
this.loading = false;
} catch (err) {
console.error(err);
this.loading = false;
}
},
},
computed: {},
created() {
this.getList();
},
mounted() {
this.$nextTick(() => {
if(this.$refs['table'] && this.$refs['toolbar']) {
this.$refs['table'].connect(this.$refs['toolbar'])
}
})
}
};
</script>
<style scoped lang="scss">
.selects > * + * {
margin: 2px 0 2px 6px;
}
</style>

@ -24,8 +24,8 @@ module.exports = {
* In most cases please use '/' !!!
* Detail: https://cli.vuejs.org/config/#publicpath
*/
publicPath: '/oa',
outputDir: 'dist',
publicPath: '/oaw',
outputDir: '../cz_hjjc/public/oaw',
assetsDir: 'static',
lintOnSave: process.env.NODE_ENV === 'development',
productionSourceMap: false,

Loading…
Cancel
Save