first commit
255
src/views/flow/create/all.vue
Normal file
@@ -0,0 +1,255 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<!-- Header Bar -->
|
||||
<div class="flex justify-between items-center px-5 py-3 mb-2 h-15">
|
||||
<div class="flex-1"></div>
|
||||
|
||||
<!-- Steps Navigation -->
|
||||
<div class="flex-2 text-center max-w-[600px]">
|
||||
<span
|
||||
v-for="(step, index) in steps"
|
||||
:key="index"
|
||||
class="inline-block px-5 py-2.5 cursor-pointer"
|
||||
:class="{ 'border-b-2 border-primary text-primary': activeStep === index }"
|
||||
@click="activeStep = index"
|
||||
>
|
||||
<span
|
||||
class="mr-1.5 inline-block w-6 h-6 text-base font-normal text-center leading-[22px] border rounded-full"
|
||||
:class="[activeStep === index ? 'bg-primary text-white' : 'border-current']"
|
||||
>
|
||||
{{ index + 1 }}
|
||||
</span>
|
||||
<span class="text-lg font-medium">{{ $t(step.title) }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Publish Button -->
|
||||
<div class="flex flex-1 justify-end items-center">
|
||||
<el-button type="primary" @click="publish">{{ $t('flow.publish') }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content Area -->
|
||||
<el-scrollbar class="h-[calc(100vh-20px)]">
|
||||
<step1 v-show="activeStep === 0" :groupId="paramGroupId" ref="step1Ref" />
|
||||
<step2 v-show="activeStep === 1" ref="step2Ref" />
|
||||
<step3 v-show="activeStep === 2" :nodeConfigObj="step3NodeConfig" ref="step3Ref" />
|
||||
</el-scrollbar>
|
||||
|
||||
<!-- Validation Dialog -->
|
||||
<el-dialog v-model="validateDialogShow" :title="$t('flow.processCheck')">
|
||||
<el-steps :active="validateFlowStep" finish-status="success" simple class="mt-5">
|
||||
<el-step :title="$t('flow.basicInformation')" />
|
||||
<el-step :title="$t('flow.formDesign')" />
|
||||
<el-step :title="$t('flow.processDesign')" />
|
||||
</el-steps>
|
||||
|
||||
<div class="text-center">
|
||||
<!-- Success Result -->
|
||||
<el-result v-if="validateFlowStep === 3" icon="success" :title="$t('flow.checkSuccess')" :sub-title="$t('flow.checkSubSuccess')">
|
||||
<template #extra>
|
||||
<el-button type="primary" :loading="isSubmitting" @click="submitFlow">{{ $t('flow.submit') }}</el-button>
|
||||
</template>
|
||||
</el-result>
|
||||
|
||||
<!-- Loading Result -->
|
||||
<el-result
|
||||
v-if="validateErrMsg.length === 0 && validateDialogShow && validatingShow && validateFlowStep < 3"
|
||||
:title="$t('flow.checkIng')"
|
||||
:sub-title="$t('flow.checkSubIng')"
|
||||
>
|
||||
<template #icon>
|
||||
<span class="inline-block w-25 h-25" v-loading="true"></span>
|
||||
</template>
|
||||
</el-result>
|
||||
|
||||
<!-- Error Result -->
|
||||
<el-result v-if="validateErrMsg.length > 0" icon="error" title="检查失败">
|
||||
<template #sub-title>
|
||||
<div v-for="item in validateErrMsg" :key="item" class="text-red-500">
|
||||
<el-icon><WarnTriangleFilled /></el-icon>
|
||||
{{ item }}
|
||||
</div>
|
||||
</template>
|
||||
<template #extra>
|
||||
<el-button type="primary" @click="gotoEdit">{{ $t('common.editBtn') }}</el-button>
|
||||
</template>
|
||||
</el-result>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { WarnTriangleFilled } from '@element-plus/icons-vue';
|
||||
import { addFlow } from '/@/api/flow/flow';
|
||||
import Step1 from './step1.vue';
|
||||
import Step2 from './step2.vue';
|
||||
import Step3 from './step3.vue';
|
||||
import { useFlowStore } from '../workflow/stores/flow';
|
||||
import { LocationQuery, LocationQueryValue, useRouter } from 'vue-router';
|
||||
import FcDesigner from 'form-create-designer';
|
||||
|
||||
let store = useFlowStore();
|
||||
const step1Ref = ref();
|
||||
const step2Ref = ref();
|
||||
const step3Ref = ref();
|
||||
|
||||
const validateErrMsg = ref([]);
|
||||
|
||||
const activeStep = ref(0);
|
||||
const validateFlowStep = ref(0);
|
||||
const validateDialogShow = ref(false);
|
||||
const validatingShow = ref(false);
|
||||
const isSubmitting = ref(false);
|
||||
|
||||
const gotoEdit = () => {
|
||||
activeStep.value = validateFlowStep.value;
|
||||
validateDialogShow.value = false;
|
||||
};
|
||||
const publish = (t) => {
|
||||
validateErrMsg.value = [];
|
||||
|
||||
validateFlowStep.value = 0;
|
||||
validateDialogShow.value = true;
|
||||
validatingShow.value = true;
|
||||
|
||||
setTimeout(function () {
|
||||
checkStep1();
|
||||
}, 500);
|
||||
};
|
||||
import { useRoute } from 'vue-router';
|
||||
import { getFlowDetail } from '/@/api/flow/flow';
|
||||
import { useUserInfo } from '/@/stores/userInfo';
|
||||
import other from '/@/utils/other';
|
||||
|
||||
const route = useRoute();
|
||||
onMounted(() => {
|
||||
const query: LocationQuery = route.query;
|
||||
|
||||
const groupId = (query.groupId as LocationQueryValue) ?? '';
|
||||
const flowId = (query.flowId as LocationQueryValue) ?? '';
|
||||
const cp = (query.cp as LocationQueryValue) ?? '';
|
||||
|
||||
if (groupId) {
|
||||
paramGroupId.value = groupId;
|
||||
}
|
||||
if (flowId) {
|
||||
getFlowDetail(flowId).then((res) => {
|
||||
var { data } = res;
|
||||
|
||||
store.step1.adminList = JSON.parse(data.adminList);
|
||||
store.step1.name = data.name;
|
||||
store.step1.logo = data.logo;
|
||||
if (!cp || !(parseInt(cp) === 1)) {
|
||||
//复制
|
||||
store.step1.flowId = flowId;
|
||||
}
|
||||
store.step1.remark = data.remark;
|
||||
store.step1.groupId = data.groupId;
|
||||
store.setStep2(FcDesigner.formCreate.parseJson(data.formItems));
|
||||
step3NodeConfig = JSON.parse(data.process);
|
||||
});
|
||||
} else {
|
||||
//新增
|
||||
let userId = useUserInfo().userInfos.user.userId;
|
||||
let name = useUserInfo().userInfos.user.username;
|
||||
let avatar = useUserInfo().userInfos.user.avatar;
|
||||
//清空 store 属性
|
||||
store.clearStep1();
|
||||
store.clearStep2();
|
||||
store.step1.adminList = [
|
||||
{
|
||||
id: userId,
|
||||
name: name,
|
||||
avatar: avatar,
|
||||
type: 'user',
|
||||
},
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
let step3NodeConfig = reactive({});
|
||||
|
||||
var paramGroupId = ref();
|
||||
|
||||
const checkStep1 = () => {
|
||||
step1Ref.value.validate(function (valid, arr) {
|
||||
if (valid) {
|
||||
validateFlowStep.value = 1;
|
||||
|
||||
setTimeout(function () {
|
||||
checkStep2();
|
||||
}, 500);
|
||||
} else {
|
||||
validatingShow.value = false;
|
||||
//错误信息
|
||||
validateErrMsg.value = arr;
|
||||
}
|
||||
});
|
||||
};
|
||||
const checkStep2 = () => {
|
||||
step2Ref.value.validate(function (valid, arr) {
|
||||
if (valid) {
|
||||
setTimeout(function () {
|
||||
validateFlowStep.value = 2;
|
||||
checkStep3();
|
||||
});
|
||||
} else {
|
||||
validatingShow.value = false;
|
||||
//错误信息
|
||||
validateErrMsg.value = arr;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const checkStep3 = () => {
|
||||
setTimeout(function () {
|
||||
step3Ref.value.validate(function (valid, arr) {
|
||||
if (valid) {
|
||||
validateFlowStep.value = 3;
|
||||
} else {
|
||||
validatingShow.value = false;
|
||||
//错误信息
|
||||
validateErrMsg.value = arr;
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
const router = useRouter();
|
||||
|
||||
const submitFlow = () => {
|
||||
if (isSubmitting.value) return;
|
||||
isSubmitting.value = true;
|
||||
|
||||
step3Ref.value
|
||||
.getProcessData()
|
||||
.then((processData: any) => {
|
||||
let step1 = store.step1;
|
||||
let step2 = store.step2;
|
||||
|
||||
let flow = other.deepClone(step1);
|
||||
flow.formItems = JSON.stringify(step2);
|
||||
flow.process = JSON.stringify(processData);
|
||||
flow.adminList = JSON.stringify(step1.adminList);
|
||||
|
||||
addFlow(flow)
|
||||
.then(() => {
|
||||
validateDialogShow.value = false;
|
||||
store.$reset();
|
||||
router.push('/flow/list/index');
|
||||
})
|
||||
.catch(() => {
|
||||
isSubmitting.value = false;
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
isSubmitting.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
// Add steps data
|
||||
const steps = [{ title: 'flow.basicInformation' }, { title: 'flow.formDesign' }, { title: 'flow.processDesign' }];
|
||||
</script>
|
||||
388
src/views/flow/create/rules/form-rules.ts
Normal file
@@ -0,0 +1,388 @@
|
||||
import type { DragRule } from 'form-create-designer';
|
||||
import other from '/@/utils/other';
|
||||
import { dayjs } from 'element-plus';
|
||||
// Create a map of rule creators
|
||||
const ruleCreators: Record<string, () => DragRule> = {
|
||||
selectUser: () => ({
|
||||
menu: 'biz',
|
||||
icon: 'icon-table-form2',
|
||||
label: '人员',
|
||||
name: 'SelectUser',
|
||||
mask: true,
|
||||
rule() {
|
||||
return {
|
||||
type: 'OrgSelector',
|
||||
field: 'SelectUser' + other.getNonDuplicateID(),
|
||||
title: '人员',
|
||||
$required: true,
|
||||
props: {
|
||||
type: 'user',
|
||||
},
|
||||
};
|
||||
},
|
||||
props() {
|
||||
return [
|
||||
{
|
||||
type: 'switch',
|
||||
title: '多选',
|
||||
field: 'multiple',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
type: 'switch',
|
||||
title: '禁用',
|
||||
field: 'disabled',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
type: 'switch',
|
||||
title: '选择自己',
|
||||
field: 'selectSelf',
|
||||
value: true,
|
||||
},
|
||||
];
|
||||
},
|
||||
}),
|
||||
selectDept: () => ({
|
||||
menu: 'biz',
|
||||
icon: 'icon-group',
|
||||
label: '部门',
|
||||
name: 'SelectDept',
|
||||
mask: true,
|
||||
rule() {
|
||||
return {
|
||||
type: 'OrgSelector',
|
||||
field: 'SelectDept' + other.getNonDuplicateID(),
|
||||
title: '部门',
|
||||
$required: true,
|
||||
props: {
|
||||
type: 'dept',
|
||||
},
|
||||
};
|
||||
},
|
||||
props() {
|
||||
return [
|
||||
{
|
||||
type: 'switch',
|
||||
title: '多选',
|
||||
field: 'multiple',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
type: 'switch',
|
||||
title: '禁用',
|
||||
field: 'disabled',
|
||||
value: false,
|
||||
},
|
||||
];
|
||||
},
|
||||
}),
|
||||
uploadFile: () => ({
|
||||
menu: 'biz',
|
||||
icon: 'icon-folder',
|
||||
label: '上传',
|
||||
name: 'UploadFile' + other.getNonDuplicateID(),
|
||||
mask: true,
|
||||
rule() {
|
||||
return {
|
||||
type: 'UploadFile',
|
||||
field: 'UploadFile',
|
||||
title: '文件上传',
|
||||
$required: true,
|
||||
props: {},
|
||||
};
|
||||
},
|
||||
props() {
|
||||
return [
|
||||
{
|
||||
type: 'input',
|
||||
title: '上传地址',
|
||||
field: 'uploadFileUrl',
|
||||
value: '/admin/sys-file/upload',
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
title: '数量限制',
|
||||
field: 'limit',
|
||||
value: 5,
|
||||
props: {
|
||||
min: 1,
|
||||
max: 100,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
title: '大小限制(MB)',
|
||||
field: 'fileSize',
|
||||
value: 5,
|
||||
props: {
|
||||
min: 1,
|
||||
max: 500,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
title: '上传类型',
|
||||
field: 'type',
|
||||
value: 'default',
|
||||
options: [
|
||||
{ label: '默认', value: 'default' },
|
||||
{ label: '简单', value: 'simple' },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
title: '上传目录',
|
||||
field: 'dir',
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
type: 'switch',
|
||||
title: '自动上传',
|
||||
field: 'autoUpload',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
type: 'switch',
|
||||
title: '显示提示',
|
||||
field: 'isShowTip',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
type: 'switch',
|
||||
title: '禁用',
|
||||
field: 'disabled',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
title: '文件类型',
|
||||
field: 'fileType',
|
||||
value: ['png', 'jpg', 'jpeg', 'doc', 'xls', 'ppt', 'txt', 'pdf', 'docx', 'xlsx', 'pptx'],
|
||||
props: {
|
||||
multiple: true,
|
||||
},
|
||||
options: [
|
||||
{ label: 'PNG', value: 'png' },
|
||||
{ label: 'JPG', value: 'jpg' },
|
||||
{ label: 'JPEG', value: 'jpeg' },
|
||||
{ label: 'DOC', value: 'doc' },
|
||||
{ label: 'DOCX', value: 'docx' },
|
||||
{ label: 'XLS', value: 'xls' },
|
||||
{ label: 'XLSX', value: 'xlsx' },
|
||||
{ label: 'PPT', value: 'ppt' },
|
||||
{ label: 'PPTX', value: 'pptx' },
|
||||
{ label: 'PDF', value: 'pdf' },
|
||||
{ label: 'TXT', value: 'txt' },
|
||||
],
|
||||
},
|
||||
];
|
||||
},
|
||||
}),
|
||||
chinaArea: () => ({
|
||||
menu: 'biz',
|
||||
icon: 'icon-subform',
|
||||
label: '地区',
|
||||
name: 'ChinaArea',
|
||||
mask: true,
|
||||
rule() {
|
||||
return {
|
||||
type: 'ChinaArea',
|
||||
field: 'ChinaArea' + other.getNonDuplicateID(),
|
||||
title: '地区',
|
||||
$required: true,
|
||||
props: {},
|
||||
};
|
||||
},
|
||||
props() {
|
||||
return [
|
||||
{
|
||||
type: 'number',
|
||||
title: '层级',
|
||||
field: 'type',
|
||||
value: 4,
|
||||
props: {
|
||||
min: 1,
|
||||
max: 4,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'switch',
|
||||
title: '禁用',
|
||||
field: 'disabled',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
type: 'switch',
|
||||
title: '子级必选',
|
||||
field: 'plus',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
type: 'switch',
|
||||
title: '懒加载',
|
||||
field: 'lazy',
|
||||
value: false,
|
||||
},
|
||||
];
|
||||
},
|
||||
}),
|
||||
sign: () => ({
|
||||
menu: 'biz',
|
||||
icon: 'icon-edit',
|
||||
label: '签名',
|
||||
name: 'Sign',
|
||||
mask: true,
|
||||
rule() {
|
||||
return {
|
||||
type: 'Sign',
|
||||
field: 'Sign' + other.getNonDuplicateID(),
|
||||
title: '签名',
|
||||
$required: true,
|
||||
props: {},
|
||||
};
|
||||
},
|
||||
props() {
|
||||
return [
|
||||
{
|
||||
type: 'number',
|
||||
title: '宽度',
|
||||
field: 'width',
|
||||
value: 300,
|
||||
props: {
|
||||
min: 100,
|
||||
max: 1000,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
title: '高度',
|
||||
field: 'height',
|
||||
value: 150,
|
||||
props: {
|
||||
min: 50,
|
||||
max: 500,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
title: '线宽',
|
||||
field: 'lineWidth',
|
||||
value: 4,
|
||||
props: {
|
||||
min: 1,
|
||||
max: 20,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'colorPicker',
|
||||
title: '线条颜色',
|
||||
field: 'lineColor',
|
||||
value: '#000000',
|
||||
},
|
||||
{
|
||||
type: 'colorPicker',
|
||||
title: '背景颜色',
|
||||
field: 'bgColor',
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
type: 'switch',
|
||||
title: '裁剪空白',
|
||||
field: 'isCrop',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
type: 'switch',
|
||||
title: '清除背景',
|
||||
field: 'isClearBgColor',
|
||||
value: true,
|
||||
},
|
||||
];
|
||||
},
|
||||
}),
|
||||
dictSelect: () => ({
|
||||
menu: 'biz',
|
||||
icon: 'icon-select',
|
||||
label: '字典',
|
||||
name: 'DictSelect' + other.getNonDuplicateID(),
|
||||
mask: true,
|
||||
rule() {
|
||||
return {
|
||||
type: 'DictSelect',
|
||||
field: 'DictSelect',
|
||||
title: '字典',
|
||||
$required: true,
|
||||
props: {},
|
||||
};
|
||||
},
|
||||
props() {
|
||||
return [
|
||||
{
|
||||
type: 'input',
|
||||
title: '字典类型',
|
||||
field: 'dictType',
|
||||
value: '',
|
||||
props: {
|
||||
placeholder: '请输入字典类型',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'switch',
|
||||
title: '多选',
|
||||
field: 'multiple',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
type: 'switch',
|
||||
title: '可清除',
|
||||
field: 'clearable',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
type: 'switch',
|
||||
title: '禁用',
|
||||
field: 'disabled',
|
||||
value: false,
|
||||
},
|
||||
];
|
||||
},
|
||||
}),
|
||||
|
||||
sequence: () => ({
|
||||
menu: 'biz',
|
||||
icon: 'icon-number',
|
||||
label: '流水号',
|
||||
name: 'Sequence',
|
||||
mask: true,
|
||||
rule() {
|
||||
const currentDate = dayjs().format('YYYYMMDD');
|
||||
const sequenceNumber = '00001';
|
||||
|
||||
return {
|
||||
type: 'Input',
|
||||
field: 'Sequence' + other.getNonDuplicateID(),
|
||||
title: '流水号',
|
||||
$required: true,
|
||||
props: {
|
||||
disabled: true,
|
||||
type: 'Sequence',
|
||||
placeholder: currentDate + sequenceNumber,
|
||||
},
|
||||
};
|
||||
},
|
||||
props() {
|
||||
return [
|
||||
{
|
||||
type: 'switch',
|
||||
title: '禁用',
|
||||
field: 'disabled',
|
||||
value: true,
|
||||
},
|
||||
];
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
// Export all rules as an array
|
||||
export const rules: any[] = Object.values(ruleCreators).map((creator) => creator());
|
||||
131
src/views/flow/create/step1.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="container-div">
|
||||
<el-card class="box-card" style="padding-right: 10%; padding-left: 10%">
|
||||
<el-form ref="ruleForm" :model="form" :rules="rules" label-position="top" status-icon label-width="120px" @submit.prevent>
|
||||
<el-form-item :label="$t('flow.logo')" prop="logo">
|
||||
<upload-img v-model:imageUrl="form.logo" height="100px" width="100px">
|
||||
<template #empty>
|
||||
<el-icon>
|
||||
<Avatar />
|
||||
</el-icon>
|
||||
<span>{{ $t('flow.logo') }}</span>
|
||||
</template>
|
||||
</upload-img>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('flow.name')" prop="name">
|
||||
<el-input v-model="form.name" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('flow.remark')" prop="remark">
|
||||
<el-input v-model="form.remark" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('flow.group')" prop="groupId">
|
||||
<el-select v-model="form.groupId" :placeholder="$t('flow.groupTips')">
|
||||
<el-option v-for="item in groupList" :key="item.id" :label="item.groupName" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('flow.admin')" prop="adminList">
|
||||
<select-show v-model:orgList="form.adminList" type="user" :multiple="false"></select-show>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { FormRules } from 'element-plus';
|
||||
import selectShow from '/@/components/OrgSelector/index.vue';
|
||||
import { queryGroupList } from '/@/api/flow/group';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useFlowStore } from '../workflow/stores/flow';
|
||||
import { GroupVO } from '/@/api/flow/group/types';
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const validate = (f) => {
|
||||
proxy.$refs.ruleForm.validate((valid, fields) => {
|
||||
var arr = [];
|
||||
if (!valid) {
|
||||
for (var err in fields) {
|
||||
arr.push(fields[err][0].message);
|
||||
}
|
||||
}
|
||||
|
||||
f(valid, arr);
|
||||
});
|
||||
};
|
||||
|
||||
// 暴露方法和属性给父组件
|
||||
defineExpose({ validate });
|
||||
const rules = reactive<FormRules>({
|
||||
name: [
|
||||
{ required: true, message: '请填写名称', trigger: 'blur' },
|
||||
{ min: 2, max: 10, message: '2-10个字符', trigger: 'blur' },
|
||||
],
|
||||
remark: [
|
||||
{ required: false, message: '请填写描述', trigger: 'blur' },
|
||||
{ min: 2, max: 20, message: '2-20个字符', trigger: 'blur' },
|
||||
],
|
||||
groupId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择分组',
|
||||
trigger: 'change',
|
||||
},
|
||||
],
|
||||
logo: [
|
||||
{
|
||||
required: true,
|
||||
message: '请上传图标',
|
||||
trigger: 'change',
|
||||
},
|
||||
],
|
||||
adminList: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择管理员',
|
||||
trigger: 'change',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let props = defineProps({
|
||||
groupId: {
|
||||
type: Number,
|
||||
default: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const groupList = ref<GroupVO[]>([]);
|
||||
|
||||
onMounted(() => {
|
||||
queryGroupList().then(({ data }) => {
|
||||
groupList.value = data;
|
||||
});
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.groupId,
|
||||
(val) => {
|
||||
if (val) {
|
||||
form.value.groupId = parseInt(val);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const flowStore = useFlowStore();
|
||||
|
||||
const form = computed(() => {
|
||||
return flowStore.step1;
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
.container-div {
|
||||
width: 800px;
|
||||
height: 100vh; /* Set the container height to 100% of the viewport height */
|
||||
margin-left: calc(50% - 400px);
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
120
src/views/flow/create/step2.vue
Normal file
@@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<div>
|
||||
<fc-designer ref="designer" height="100vh" @save="handleSave" :config="designerConfig" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import FcDesigner from 'form-create-designer';
|
||||
import type { Config, DragRule } from 'form-create-designer';
|
||||
import { useFlowStore } from '../workflow/stores/flow';
|
||||
import { Session } from '/@/utils/storage';
|
||||
import { CommonHeaderEnum } from '/@/utils/request';
|
||||
import { rules } from './rules/form-rules';
|
||||
|
||||
// 组件引用
|
||||
const designer = ref<InstanceType<typeof FcDesigner> | null>(null);
|
||||
const flowStore = useFlowStore();
|
||||
|
||||
// 设计器配置
|
||||
const designerConfig: Config = {
|
||||
fieldReadonly: false, // 字段是否只读
|
||||
showSaveBtn: true, // 显示保存按钮
|
||||
showDevice: false, // 不显示设备选择
|
||||
// showJsonPreview: false,
|
||||
hiddenItem: ['upload'],
|
||||
};
|
||||
|
||||
// 配置表单请求拦截器
|
||||
FcDesigner.designerForm.fetch = FcDesigner.formCreate.fetch = (options: any) => {
|
||||
const headers: Record<string, string> = {};
|
||||
|
||||
// 添加认证token
|
||||
const token = Session.getToken();
|
||||
if (token) {
|
||||
headers[CommonHeaderEnum.AUTHORIZATION] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
// 添加租户ID
|
||||
const tenantId = Session.getTenant();
|
||||
if (tenantId) {
|
||||
headers[CommonHeaderEnum.TENANT_ID] = tenantId;
|
||||
}
|
||||
|
||||
// 发起请求
|
||||
fetch(options.action, {
|
||||
headers,
|
||||
method: options.method,
|
||||
})
|
||||
.then(async (res) => {
|
||||
const data = await res.json();
|
||||
options.onSuccess(data);
|
||||
})
|
||||
.catch(options.onError);
|
||||
};
|
||||
|
||||
// 表单验证方法
|
||||
const validate = (callback: (valid: boolean) => void): void => {
|
||||
if (designer.value) {
|
||||
// 保存当前设计的表单规则到store
|
||||
flowStore.setStep2(designer.value.getRule());
|
||||
callback(true);
|
||||
} else {
|
||||
callback(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 监听store中的表单规则变化
|
||||
watch(
|
||||
() => flowStore.step2,
|
||||
(newRule) => {
|
||||
// 当规则变化且设计器存在时,更新设计器的规则
|
||||
if (designer.value && newRule) {
|
||||
designer.value.setRule(newRule);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 保存处理函数
|
||||
const handleSave = () => {
|
||||
if (designer.value) {
|
||||
flowStore.setStep2(designer.value.getRule());
|
||||
}
|
||||
};
|
||||
|
||||
// 组件挂载后,如果store中有规则则加载
|
||||
onMounted(() => {
|
||||
if (designer.value && flowStore.step2) {
|
||||
designer.value.addMenu({ name: 'biz', title: '业务组件', list: []});
|
||||
|
||||
// Add all rules at once
|
||||
rules.forEach((rule) => {
|
||||
designer.value?.addComponent(rule as DragRule);
|
||||
});
|
||||
|
||||
designer.value.setRule(flowStore.step2);
|
||||
}
|
||||
});
|
||||
|
||||
// 暴露验证方法给父组件
|
||||
defineExpose({ validate });
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.fc-form-row {
|
||||
// 选中效果
|
||||
._fd-drag-tool.active {
|
||||
outline: 2px solid #2e73ff !important;
|
||||
}
|
||||
// 栅格线条
|
||||
._fd-drag-tool {
|
||||
outline: 1px dashed var(--fc-tool-border-color)!important;
|
||||
}
|
||||
}
|
||||
// 设置事件样式
|
||||
._fd-event-l {
|
||||
.el-menu-item {
|
||||
line-height: 1em!important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
135
src/views/flow/create/step3.vue
Normal file
@@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 流程节点绘制 -->
|
||||
<div class="fd-nav-content">
|
||||
<el-scrollbar style="height: 600px">
|
||||
<section class="dingflow-design">
|
||||
<div class="zoom">
|
||||
<div class="zoom-out" :class="nowVal === 50 && 'disabled'" @click="zoomSize(1)"></div>
|
||||
<span>{{ nowVal }}%</span>
|
||||
<div class="zoom-in" :class="nowVal === 300 && 'disabled'" @click="zoomSize(2)"></div>
|
||||
</div>
|
||||
<div class="box-scale" :style="`transform: scale(${nowVal / 100});`">
|
||||
<nodeWrap v-model:nodeConfig="nodeConfig"/>
|
||||
<div class="end-node">
|
||||
<div class="end-node-circle"></div>
|
||||
<div class="end-node-text">{{ $t('flow.end') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
<!-- 发起人 -->
|
||||
<promoterDrawer/>
|
||||
<!-- 审批人 -->
|
||||
<approverDrawer/>
|
||||
<!-- 抄送人 -->
|
||||
<copyerDrawer/>
|
||||
<!-- 条件分支 -->
|
||||
<conditionDrawer/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import nodeWrap from '../workflow/components/nodeWrap.vue';
|
||||
import promoterDrawer from '../workflow/components/drawer/promoterDrawer.vue';
|
||||
import approverDrawer from '../workflow/components/drawer/approverDrawer.vue';
|
||||
import copyerDrawer from '../workflow/components/drawer/copyerDrawer.vue';
|
||||
import conditionDrawer from '../workflow/components/drawer/conditionDrawer.vue';
|
||||
import {useI18n} from 'vue-i18n';
|
||||
import {useFlowStore} from "../workflow/stores/flow";
|
||||
|
||||
let store = useFlowStore();
|
||||
|
||||
const {t} = useI18n();
|
||||
|
||||
let tipList = ref([]);
|
||||
let nowVal = ref(100);
|
||||
let nodeConfig = ref({
|
||||
nodeName: t('flow.initiator'),
|
||||
type: 0,
|
||||
id: 'root',
|
||||
formPerms: {},
|
||||
eventConfig: '',
|
||||
nodeUserList: [],
|
||||
childNode: {},
|
||||
});
|
||||
|
||||
let props = defineProps({
|
||||
nodeConfigObj: {
|
||||
type: Object,
|
||||
},
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.nodeConfigObj,
|
||||
(val) => {
|
||||
nodeConfig.value = val;
|
||||
}
|
||||
);
|
||||
|
||||
const reErr = ({childNode}) => {
|
||||
if (childNode) {
|
||||
let {type, error, nodeName, conditionNodes} = childNode;
|
||||
if (type == 1 || type == 2) {
|
||||
if (error) {
|
||||
tipList.value.push(nodeName + ' 未设置人员');
|
||||
}
|
||||
reErr(childNode);
|
||||
} else if (type == 3) {
|
||||
reErr(childNode);
|
||||
} else if (type == 4) {
|
||||
reErr(childNode);
|
||||
for (var i = 0; i < conditionNodes.length; i++) {
|
||||
if (conditionNodes[i].error) {
|
||||
tipList.value.push('请设置' + conditionNodes[i].nodeName + '的条件');
|
||||
}
|
||||
reErr(conditionNodes[i]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
childNode = null;
|
||||
}
|
||||
};
|
||||
|
||||
watch(() => nodeConfig.value, (v) => {
|
||||
store.setStep3(v)
|
||||
}, {deep: true})
|
||||
const getProcessData = async () => {
|
||||
return nodeConfig.value;
|
||||
};
|
||||
const zoomSize = (type) => {
|
||||
if (type == 1) {
|
||||
if (nowVal.value == 50) {
|
||||
return;
|
||||
}
|
||||
nowVal.value -= 10;
|
||||
} else {
|
||||
if (nowVal.value == 300) {
|
||||
return;
|
||||
}
|
||||
nowVal.value += 10;
|
||||
}
|
||||
};
|
||||
const validate = (f) => {
|
||||
tipList.value = [];
|
||||
if (nodeConfig.value.childNode == undefined || nodeConfig.value.childNode.id === undefined) {
|
||||
tipList.value = ['请完善流程节点'];
|
||||
}
|
||||
|
||||
reErr(nodeConfig.value);
|
||||
if (tipList.value.length != 0) {
|
||||
f(false, tipList.value);
|
||||
return;
|
||||
}
|
||||
f(true);
|
||||
};
|
||||
defineExpose({validate, getProcessData});
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
@import '../workflow/css/workflow.css';
|
||||
|
||||
.error-modal-list {
|
||||
width: 455px;
|
||||
}
|
||||
</style>
|
||||
47
src/views/flow/form/const/types.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
export interface FormGroupVO {
|
||||
name: string;
|
||||
formList: FormVO[];
|
||||
}
|
||||
export interface FormVO {
|
||||
name: string | '';
|
||||
id: String | '';
|
||||
type: string | '';
|
||||
typeName: string | '';
|
||||
placeholder: string;
|
||||
required: boolean;
|
||||
icon: String;
|
||||
props: FormConfigVO;
|
||||
}
|
||||
export interface FormConfigVO {
|
||||
minLength: Number;
|
||||
maxLength: Number;
|
||||
|
||||
value: any | '';
|
||||
regex: string | '';
|
||||
regexDesc: string | '';
|
||||
min: Number;
|
||||
max: Number;
|
||||
radixNum: Number;
|
||||
showChinese: boolean;
|
||||
self: boolean;
|
||||
multi: boolean;
|
||||
showThousandSymbol: boolean;
|
||||
fileList: any[];
|
||||
unit: string;
|
||||
options: FormConfigOptionVO[];
|
||||
maxSize: Number;
|
||||
suffixArray: String[];
|
||||
prefix: string | '';
|
||||
}
|
||||
|
||||
export interface FormConfigOptionVO {
|
||||
key: String;
|
||||
value: String;
|
||||
}
|
||||
|
||||
export interface FormConfigUserVO {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
avatar: string;
|
||||
}
|
||||
125
src/views/flow/form/tools/FlowNodeFormat.vue
Normal file
@@ -0,0 +1,125 @@
|
||||
<script setup lang="ts">
|
||||
import selectShow from '/@/components/OrgSelector/index.vue';
|
||||
|
||||
interface UserVo {
|
||||
id: string;
|
||||
name: string;
|
||||
showTime: string | null;
|
||||
avatar: string | null;
|
||||
approveDesc: string | null;
|
||||
operType: string | null;
|
||||
status: number | null;
|
||||
}
|
||||
|
||||
interface FlowNode {
|
||||
id: string;
|
||||
userVoList: UserVo[] | null;
|
||||
placeholder: string | null;
|
||||
status: number;
|
||||
name: string;
|
||||
type: number;
|
||||
selectUser: boolean | null;
|
||||
multiple: boolean | null;
|
||||
children: FlowNode[] | null;
|
||||
branch: FlowNode[];
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
nodeUser: Record<string, any>;
|
||||
row: FlowNode[];
|
||||
disableSelect: boolean;
|
||||
}>();
|
||||
|
||||
// Create a map to store active tabs for each node
|
||||
const activeTabsMap = ref<Map<string, string>>(new Map());
|
||||
|
||||
// Function to get/set active tab
|
||||
function getNodeActiveTab(node: FlowNode): string {
|
||||
if (!activeTabsMap.value.has(node.id)) {
|
||||
// Initialize with computed default value
|
||||
const activeIndex = node.branch.findIndex((branch) => branch.children?.some((child) => child.status !== 0));
|
||||
activeTabsMap.value.set(node.id, activeIndex >= 0 ? String(activeIndex) : '0');
|
||||
}
|
||||
return activeTabsMap.value.get(node.id) || '0';
|
||||
}
|
||||
|
||||
// Add function to get branch label
|
||||
function getBranchLabel(node: FlowNode, index: number): string {
|
||||
return node.placeholder || `分支${index + 1}`;
|
||||
}
|
||||
|
||||
import { Check, Plus, Refresh } from '@element-plus/icons-vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-3">
|
||||
<el-timeline :reverse="false">
|
||||
<el-timeline-item
|
||||
v-for="(node, index) in row"
|
||||
:key="index"
|
||||
size="large"
|
||||
:color="node.status != 2 ? (node.status == 1 ? 'var(--el-color-warning)' : 'var(--el-color-success)') : 'var(--el-color-primary)'"
|
||||
:icon="node.status == 2 ? Check : node.status == 1 ? Plus : Refresh"
|
||||
>
|
||||
<!-- Node Title Section -->
|
||||
<div :class="['text-sm font-medium mb-3', { 'text-red-500': node.selectUser && (!nodeUser[node.id] || nodeUser[node.id]?.length == 0) }]">
|
||||
{{ node.name }}
|
||||
<span v-if="node.placeholder" class="ml-1 text-gray-500">[{{ node.placeholder }}]</span>
|
||||
</div>
|
||||
|
||||
<!-- User Avatar List Section -->
|
||||
<div v-if="node.userVoList?.length" class="flex flex-wrap gap-2 mb-3">
|
||||
<div v-for="(item1, index1) in node.userVoList" :key="index1" class="w-10 text-center">
|
||||
<div class="flex flex-col items-center">
|
||||
<upload-img v-model:image-url="item1.avatar" width="30px" height="30px"></upload-img>
|
||||
<div class="mt-1 w-full text-xs truncate">{{ item1.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Comments Section -->
|
||||
<div v-for="(item1, index1) in node.userVoList" :key="'comment-' + index1">
|
||||
<div v-if="item1.approveDesc" class="mb-3">
|
||||
<div class="flex gap-2 items-center mb-2">
|
||||
<div class="w-10 text-center">
|
||||
<upload-img v-model:image-url="item1.avatar" disabled width="30px" height="30px"></upload-img>
|
||||
</div>
|
||||
<div class="text-xs">
|
||||
<span class="font-medium">{{ item1.name }}</span>
|
||||
<span class="ml-1 text-gray-500">(添加了评论) {{ item1.showTime }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-3 text-sm bg-gray-50 rounded">{{ item1.approveDesc }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- User Selection Section -->
|
||||
<div v-if="node.selectUser">
|
||||
<select-show :disabled="disableSelect" v-model:orgList="nodeUser[node.id]" type="user" :multiple="node.multiple"></select-show>
|
||||
</div>
|
||||
|
||||
<!-- Branch Tabs Section -->
|
||||
<el-tabs
|
||||
v-if="node.branch.length > 0"
|
||||
type="border-card"
|
||||
:model-value="getNodeActiveTab(node)"
|
||||
@update:model-value="(val) => activeTabsMap.set(node.id, val)"
|
||||
class="mt-4"
|
||||
>
|
||||
<el-tab-pane v-for="(node1, index1) in node.branch" :label="getBranchLabel(node1, index1)" :name="String(index1)" :key="index1">
|
||||
<div class="p-2">
|
||||
<flow-node-format :node-user="nodeUser" :disableSelect="disableSelect" :row="node1.children"></flow-node-format>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* Only keep styles that can't be achieved with Tailwind */
|
||||
:deep(.el-tabs__header) {
|
||||
@apply mb-3;
|
||||
}
|
||||
</style>
|
||||
92
src/views/flow/form/tools/FlowNodeFormatData.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<script setup lang="ts">
|
||||
import FlowNodeFormat from '/@/views/flow/form/tools/FlowNodeFormat.vue';
|
||||
import { formatStartNodeShow } from '/@/api/flow/task';
|
||||
import { defineExpose, onMounted, ref, watch } from 'vue';
|
||||
|
||||
let props = defineProps({
|
||||
flowId: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
disableSelect: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
taskId: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
processInstanceId: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
formData: {
|
||||
type: Object,
|
||||
dafault: () => {},
|
||||
},
|
||||
selectUserNodeId: {
|
||||
type: Array,
|
||||
dafault: () => [],
|
||||
},
|
||||
});
|
||||
const row = ref([]);
|
||||
|
||||
const queryData = (p) => {
|
||||
var data = {
|
||||
flowId: props.flowId,
|
||||
processInstanceId: props.processInstanceId,
|
||||
paramMap: p,
|
||||
taskId: props.taskId,
|
||||
};
|
||||
formatStartNodeShow(data).then((res) => {
|
||||
row.value = res.data;
|
||||
});
|
||||
};
|
||||
watch(
|
||||
() => props.formData,
|
||||
(val) => {
|
||||
setTimeout(function () {
|
||||
if (new Date().getTime() - formDataChangeTime.value > 500) {
|
||||
formDataChangeTime.value = new Date().getTime();
|
||||
queryData(val);
|
||||
}
|
||||
}, 600);
|
||||
}
|
||||
);
|
||||
const formDataChangeTime = ref();
|
||||
onMounted(() => {
|
||||
formDataChangeTime.value = new Date().getTime();
|
||||
queryData({});
|
||||
});
|
||||
|
||||
const validate = () => {
|
||||
for (var k of props.selectUserNodeId) {
|
||||
var d = nodeUser.value[k];
|
||||
if (d && d.length > 0) {
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const nodeUser = ref({});
|
||||
|
||||
const formatSelectNodeUser = () => {
|
||||
var obj = {};
|
||||
|
||||
for (var k of props.selectUserNodeId) {
|
||||
var d = nodeUser.value[k];
|
||||
obj[k + '_assignee_select'] = d;
|
||||
}
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
defineExpose({ validate, formatSelectNodeUser });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<flow-node-format :row="row" :node-user="nodeUser" :disableSelect="disableSelect" ref="flowNodeFormatRef"></flow-node-format>
|
||||
</template>
|
||||
115
src/views/flow/form/tools/startFlow.vue
Normal file
@@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog v-model="dialogTableVisible" title="发起流程" width="800px" destroy-on-close @closed="handleDialogClosed">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form label-position="top">
|
||||
<FormCreate :rule="rule" v-model="formData" v-model:api="fApi" />
|
||||
</el-form>
|
||||
|
||||
<div class="flex justify-center mt-4">
|
||||
<el-button @click="dialogTableVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitProcess"> 提交</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<flow-node-format :selectUserNodeId="selectUserNodeId" :flow-id="currentOpenFlow.flowId" ref="flowNodeFormatRef"></flow-node-format>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import FlowNodeFormat from '/@/views/flow/form/tools/FlowNodeFormatData.vue';
|
||||
import { getFlowDetail, startFlow } from '/@/api/flow/flow';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import FcDesigner from 'form-create-designer';
|
||||
import { Api, Rule } from '@form-create/element-ui';
|
||||
import FormCreate from '/@/views/flow/workflow/components/FormCreate.vue';
|
||||
import { processFormItemsWithPerms } from '/@/views/flow/workflow/utils/formPermissions';
|
||||
|
||||
// 定义接口
|
||||
interface FlowData {
|
||||
flowId: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface FormDataType {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// Api 类型
|
||||
const fApi = ref<Api>();
|
||||
const formData = ref<FormDataType>({});
|
||||
|
||||
// 类型是 Rule Array
|
||||
const rule = ref<Rule>([]);
|
||||
|
||||
const dialogTableVisible = ref<Boolean>(false);
|
||||
const currentOpenFlow = ref<FlowData>();
|
||||
|
||||
const submitProcess = () => {
|
||||
let validate = flowNodeFormatRef.value.validate();
|
||||
if (!validate) {
|
||||
ElMessage.warning('请选择节点执行人');
|
||||
return;
|
||||
}
|
||||
|
||||
let param: Record<string, any> = flowNodeFormatRef.value.formatSelectNodeUser();
|
||||
|
||||
// 将 formData 这个map 的所有值 push 到 param 里面
|
||||
for (const key in formData.value) {
|
||||
param[key] = formData.value[key];
|
||||
}
|
||||
|
||||
const data = {
|
||||
flowId: currentOpenFlow.value?.flowId,
|
||||
paramMap: param,
|
||||
};
|
||||
|
||||
if (fApi.value) {
|
||||
fApi.value.submit().then(() => {
|
||||
startFlow(data).then(() => {
|
||||
ElMessage.success('提交成功');
|
||||
dialogTableVisible.value = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handle = (row: FlowData) => {
|
||||
currentOpenFlow.value = row;
|
||||
startProcess(row);
|
||||
};
|
||||
|
||||
defineExpose({ handle });
|
||||
|
||||
const selectUserNodeId = ref<String[]>([]);
|
||||
|
||||
const startProcess = (f: FlowData) => {
|
||||
getFlowDetail(f.flowId).then((res) => {
|
||||
const { data } = res;
|
||||
const { formItems, formPerms = {} } = data;
|
||||
|
||||
// 解析表单项
|
||||
const parsedFormItems = FcDesigner.formCreate.parseJson(formItems);
|
||||
// 处理表单权限
|
||||
const itemsWithPerms = processFormItemsWithPerms(parsedFormItems, formPerms);
|
||||
|
||||
rule.value = itemsWithPerms;
|
||||
selectUserNodeId.value = data.selectUserNodeId;
|
||||
dialogTableVisible.value = true;
|
||||
});
|
||||
};
|
||||
|
||||
const flowNodeFormatRef = ref();
|
||||
|
||||
const handleDialogClosed = () => {
|
||||
formData.value = {};
|
||||
if (fApi.value) {
|
||||
fApi.value.resetFields();
|
||||
}
|
||||
// Reset other related data
|
||||
currentOpenFlow.value = undefined;
|
||||
};
|
||||
</script>
|
||||
244
src/views/flow/form/tools/utils/formValidate.ts
Normal file
@@ -0,0 +1,244 @@
|
||||
import other from "/@/utils/other";
|
||||
|
||||
export function inputValidate(configValue: any, proxy: any) {
|
||||
let minLength = configValue.props.minLength;
|
||||
let maxLength = configValue.props.maxLength;
|
||||
let value = configValue.props.value;
|
||||
let regex = configValue.props.regex;
|
||||
let regexDesc = configValue.props.regexDesc;
|
||||
|
||||
if (minLength && maxLength && maxLength < minLength) {
|
||||
return {
|
||||
valid: false,
|
||||
msg: configValue.name + ':长度设置错误',
|
||||
};
|
||||
}
|
||||
if (regex) {
|
||||
if (!regexDesc) {
|
||||
return {
|
||||
valid: false,
|
||||
msg: configValue.name + ':请填写正则表达式提示语',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (value) {
|
||||
if (minLength) {
|
||||
if (value.length < minLength) {
|
||||
return {
|
||||
valid: false,
|
||||
msg: configValue.name + ':默认值长度不能小于' + minLength,
|
||||
};
|
||||
}
|
||||
}
|
||||
if (maxLength) {
|
||||
if (value.length > maxLength) {
|
||||
return {
|
||||
valid: false,
|
||||
msg: configValue.name + ':默认值长度不能大于' + maxLength,
|
||||
};
|
||||
}
|
||||
}
|
||||
if (regex) {
|
||||
var reg = new RegExp(regex, 'g');
|
||||
if (!reg.test(value)) {
|
||||
return {
|
||||
valid: false,
|
||||
msg: configValue.name + ':默认值不符合正则表达式',
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
valid: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function descriptionValidate(configValue: any, proxy: any) {
|
||||
let placeHolder = configValue.placeholder;
|
||||
if (!placeHolder) {
|
||||
return {
|
||||
valid: false,
|
||||
msg: configValue.name + ':请设置提示',
|
||||
};
|
||||
}
|
||||
return {
|
||||
valid: true,
|
||||
};
|
||||
}
|
||||
export function numberValidate(configValue: any, proxy: any) {
|
||||
let min = configValue.props.min;
|
||||
let max = configValue.props.max;
|
||||
let defaultValue = configValue.props.value;
|
||||
let radixNum = configValue.props.radixNum;
|
||||
|
||||
if (min && max && max < min) {
|
||||
return {
|
||||
valid: false,
|
||||
msg: configValue.name + ':值范围设置错误',
|
||||
};
|
||||
}
|
||||
|
||||
if (radixNum) {
|
||||
if (min) {
|
||||
let num = other.getNumberRadixNum(min);
|
||||
if (num > radixNum) {
|
||||
return {
|
||||
valid: false,
|
||||
msg: configValue.name + ':最小值小数点位数不能超过' + radixNum,
|
||||
};
|
||||
}
|
||||
}
|
||||
if (max) {
|
||||
let num = other.getNumberRadixNum(max);
|
||||
if (num > radixNum) {
|
||||
return {
|
||||
valid: false,
|
||||
msg: configValue.name + ':最大值小数点位数不能超过' + radixNum,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (defaultValue) {
|
||||
if (radixNum) {
|
||||
let num = other.getNumberRadixNum(defaultValue);
|
||||
if (num > radixNum) {
|
||||
return {
|
||||
valid: false,
|
||||
msg: configValue.name + ':默认值小数点位数不能超过' + radixNum,
|
||||
};
|
||||
}
|
||||
}
|
||||
if (min) {
|
||||
if (defaultValue < min) {
|
||||
return {
|
||||
valid: false,
|
||||
msg: configValue.name + ':默认值不能小于' + min,
|
||||
};
|
||||
}
|
||||
}
|
||||
if (max) {
|
||||
if (defaultValue > max) {
|
||||
return {
|
||||
valid: false,
|
||||
msg: configValue.name + ':默认值不能大于' + max,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
};
|
||||
}
|
||||
export function layoutValidate(configValue: any, proxy: any) {
|
||||
let min = configValue.props.min;
|
||||
let max = configValue.props.max;
|
||||
|
||||
if (min && max && max < min) {
|
||||
return {
|
||||
valid: false,
|
||||
msg: configValue.name + ':数量范围设置错误',
|
||||
};
|
||||
}
|
||||
let value = configValue.props.value;
|
||||
if (value.length == 0) {
|
||||
return {
|
||||
valid: false,
|
||||
msg: configValue.name + ':内部表单不能为空',
|
||||
};
|
||||
}
|
||||
for (var item of value) {
|
||||
let formValidateDictElement = formValidateDict[item.type];
|
||||
if (formValidateDictElement) {
|
||||
let result = formValidateDictElement(item, proxy);
|
||||
|
||||
if (!result.valid) {
|
||||
return {
|
||||
valid: false,
|
||||
msg: configValue.name + ':' + result.msg,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
};
|
||||
}
|
||||
export function selectValidate(configValue: any, proxy: any) {
|
||||
var options = configValue.props.options;
|
||||
if (!options) {
|
||||
options = [];
|
||||
}
|
||||
|
||||
if (options.length < 1) {
|
||||
return {
|
||||
valid: false,
|
||||
msg: configValue.name + ':请设置选项',
|
||||
};
|
||||
}
|
||||
|
||||
{
|
||||
var keyList = options.map((w) => w.key);
|
||||
let newList = Array.from(new Set(keyList));
|
||||
if (keyList.length > newList.length) {
|
||||
return {
|
||||
valid: false,
|
||||
msg: configValue.name + ':选项值不能重复',
|
||||
};
|
||||
}
|
||||
}
|
||||
{
|
||||
let length = options.filter((res) => !res.key || !res.value).length;
|
||||
if (length > 0) {
|
||||
return {
|
||||
valid: false,
|
||||
msg: configValue.name + ':选项不能为空',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var keyList = options.map((w) => w.value);
|
||||
let newList = Array.from(new Set(keyList));
|
||||
if (keyList.length > newList.length) {
|
||||
return {
|
||||
valid: false,
|
||||
msg: configValue.name + ':选项标签不能重复',
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
valid: true,
|
||||
};
|
||||
}
|
||||
export function fileValidate(configValue: any, proxy: any) {
|
||||
let min = configValue.props.min;
|
||||
let max = configValue.props.max;
|
||||
|
||||
if (min && max && max < min) {
|
||||
return {
|
||||
valid: false,
|
||||
msg: configValue.name + ':数量设置错误',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
};
|
||||
}
|
||||
|
||||
export let formValidateDict = {
|
||||
Input: inputValidate,
|
||||
Textarea: inputValidate,
|
||||
Number: numberValidate,
|
||||
Money: numberValidate,
|
||||
Description: descriptionValidate,
|
||||
SingleSelect: selectValidate,
|
||||
MultiSelect: selectValidate,
|
||||
UploadFile: fileValidate,
|
||||
Layout: layoutValidate,
|
||||
UploadImage: fileValidate,
|
||||
};
|
||||
10
src/views/flow/form/tools/utils/getFormConfigWidget.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
const gets = {} as any;
|
||||
const modules = import.meta.glob('../../config/*.vue', { eager: true });
|
||||
|
||||
for (let each in modules) {
|
||||
const name = modules[each].default.__name;
|
||||
|
||||
gets[name] = (modules[each] as any).default;
|
||||
}
|
||||
|
||||
export default gets;
|
||||
14
src/views/flow/form/tools/utils/getFormWidget.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
const gets = {} as any;
|
||||
const modules = import.meta.glob('../../*.vue', { eager: true });
|
||||
|
||||
async function processModules() {
|
||||
for (let each in modules) {
|
||||
const module = await modules[each];
|
||||
const name = module.default.__name;
|
||||
gets[name] = module.default;
|
||||
}
|
||||
}
|
||||
|
||||
processModules();
|
||||
|
||||
export default gets;
|
||||
319
src/views/flow/group/index.vue
Normal file
@@ -0,0 +1,319 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row shadow="hover" class="mt-4 ml-8">
|
||||
<div class="flex justify-start">
|
||||
<el-button class="button" @click.stop="toCreateFlow(undefined)" type="primary" :icon="Plus">
|
||||
{{ $t('flow.creationProcess') }}
|
||||
</el-button>
|
||||
<popover-input @confirm="addGroupInputBlur" class="ml-4">
|
||||
<template #default>
|
||||
<el-button :icon="Plus">{{ $t('flow.creationGroup') }}</el-button>
|
||||
</template>
|
||||
</popover-input>
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
<el-tabs class="mt-4 ml-8" @tab-click="handleTabClick">
|
||||
<el-tab-pane :label="item.groupName" v-for="item in successGroupList" :key="item.id">
|
||||
<template #label>
|
||||
<div class="card-header">
|
||||
<span>{{ item.groupName }}</span>
|
||||
<span>
|
||||
<el-tooltip effect="dark" content="删除" placement="top">
|
||||
<el-button text :icon="Delete" @click.stop="deleteGroup(item.id)" circle/>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table
|
||||
:data="state.dataList"
|
||||
style="width: 100%"
|
||||
border
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
max-height="530px"
|
||||
>
|
||||
<el-table-column prop="flowId" label="#" width="250"/>
|
||||
<el-table-column prop="logo" :label="$t('flow.logo')" width="80" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<upload-img disabled v-model:imageUrl="row.logo" width="50px" height="50px"></upload-img>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" :label="$t('flow.name')" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ row.name }}
|
||||
<el-tag v-if="row.stop === '1'" type="danger">已停用</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" :label="$t('flow.createTime')" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="rangeShow" :label="$t('flow.scopeOfUse')" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ row.rangeShow && row.rangeShow.length > 0 ? row.rangeShow : $t('flow.allUser') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('common.action')" width="300">
|
||||
<template #default="{ row }">
|
||||
<el-tooltip effect="dark" content="收藏至首页导航" placement="top" v-if="showFavorite(row)">
|
||||
<el-button text @click="toIndex(row)" :icon="Star" circle/>
|
||||
</el-tooltip>
|
||||
<el-tooltip effect="dark" content="编辑" placement="top">
|
||||
<el-button text @click="toEditFlow(row)" :icon="Edit" circle/>
|
||||
</el-tooltip>
|
||||
<el-tooltip effect="dark" content="复制" placement="top">
|
||||
<el-button text @click="toCopyFlow(row)" :icon="DocumentCopy" circle/>
|
||||
</el-tooltip>
|
||||
<!-- <el-tooltip v-if="row.stop === '0'" effect="dark" content="停用" placement="top">
|
||||
<el-button @click="showDisableConfirm(row)" text :icon="Hide" circle/>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip v-else effect="dark" content="启用" placement="top">
|
||||
<el-button @click="showEnableConfirm(row)" text :icon="View" circle/>
|
||||
</el-tooltip> -->
|
||||
|
||||
<!-- <el-tooltip effect="dark" content="删除" placement="top">
|
||||
<el-button text @click="showDeleteConfirm(row)" :icon="Delete" circle/>
|
||||
</el-tooltip> -->
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination"/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="flowGroup">
|
||||
import {Star, Plus, Delete, Edit, DocumentCopy, Hide, View} from '@element-plus/icons-vue';
|
||||
import {addGroup, delGroup, queryGroupList, queryGroupFlowList} from '/@/api/flow/group';
|
||||
import {disableFlow, enableFlow, deleteFlow} from '/@/api/flow/flow';
|
||||
import {GroupVO} from '/@/api/flow/group/types';
|
||||
import {useRouter} from 'vue-router';
|
||||
import {ElMessage, ElMessageBox} from 'element-plus';
|
||||
import {BasicTableProps, useTable} from '/@/hooks/table';
|
||||
import {useTagsViewRoutes} from "/@/stores/tagsViewRoutes";
|
||||
import {useMessage} from "/@/hooks/message";
|
||||
import {storeToRefs} from "pinia";
|
||||
|
||||
const storesTagsViewRoutes = useTagsViewRoutes();
|
||||
|
||||
// 默认查询
|
||||
const handleQueryFlowPage = (params: any) => {
|
||||
if (successGroupList && !state.queryForm.groupId) {
|
||||
params.groupId = successGroupList.value[0].id;
|
||||
}
|
||||
return queryGroupFlowList(params);
|
||||
};
|
||||
|
||||
// 定义基础表格相关的状态及方法
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {
|
||||
groupId: '',
|
||||
},
|
||||
pagination: {
|
||||
size: 5,
|
||||
current: 1,
|
||||
},
|
||||
createdIsNeed: false,
|
||||
pageList: handleQueryFlowPage,
|
||||
descs: ['create_time'],
|
||||
});
|
||||
|
||||
const {getDataList, sizeChangeHandle, currentChangeHandle, tableStyle} = useTable(state);
|
||||
|
||||
// 异步加载PopoverInput组件
|
||||
const PopoverInput = defineAsyncComponent(() => import('/@/components/PopoverInput/index.vue'));
|
||||
|
||||
// 存储成功的分组列表
|
||||
const successGroupList = ref<GroupVO[]>([]);
|
||||
|
||||
// 失去焦点时触发新增分组名称
|
||||
const addGroupInputBlur = (groupName: string) => {
|
||||
if (!groupName) {
|
||||
return;
|
||||
}
|
||||
addGroup({groupName: groupName}).then(() => {
|
||||
ElMessage.success('新增成功');
|
||||
handleQuery();
|
||||
});
|
||||
};
|
||||
|
||||
// 切换查询
|
||||
const handleTabClick = (pane?: any) => {
|
||||
if (successGroupList) {
|
||||
state.queryForm.groupId = successGroupList.value[pane.paneName].id;
|
||||
}
|
||||
getDataList();
|
||||
};
|
||||
|
||||
/**
|
||||
* 判断是否可以加入首页收藏
|
||||
* @param row
|
||||
*/
|
||||
const showFavorite = (row) => {
|
||||
|
||||
if (favoriteRoutes.value.length === 0) {
|
||||
return true
|
||||
}
|
||||
|
||||
return !favoriteRoutes.value.find(o => o.path.includes(row.flowId))
|
||||
}
|
||||
|
||||
// 路由实例
|
||||
const router = useRouter();
|
||||
|
||||
// 跳转到创建流程页
|
||||
const toCreateFlow = (id) => {
|
||||
let to = '/flow/create/all';
|
||||
if (id) {
|
||||
to = to + '?groupId=' + id;
|
||||
}
|
||||
router.push(to);
|
||||
};
|
||||
|
||||
// 跳转到编辑流程页
|
||||
const {favoriteRoutes} = storeToRefs(storesTagsViewRoutes);
|
||||
|
||||
const toIndex = (flow) => {
|
||||
const route: RouteItem = {
|
||||
name: flow.name,
|
||||
path: '/flow/list/index?flowId=' + flow.flowId,
|
||||
meta: {
|
||||
icon: 'fa fa-play'
|
||||
}
|
||||
}
|
||||
|
||||
if (!favoriteRoutes.value.find((o) => o.path === route.path)) {
|
||||
storesTagsViewRoutes.setFavoriteRoutes(route);
|
||||
useMessage().success('收藏成功,可在首页直接发起流程')
|
||||
} else {
|
||||
useMessage().error('已经存在收藏');
|
||||
}
|
||||
};
|
||||
|
||||
// 跳转到编辑流程页
|
||||
const toEditFlow = (flow) => {
|
||||
let to = '/flow/create/all?flowId=' + flow.flowId;
|
||||
router.push(to);
|
||||
};
|
||||
|
||||
// 跳转到复制流程页
|
||||
const toCopyFlow = (flow) => {
|
||||
let to = '/flow/create/all?cp=1&flowId=' + flow.flowId;
|
||||
router.push(to);
|
||||
};
|
||||
|
||||
// 显示停用确认弹窗
|
||||
const showDisableConfirm = (flow) => {
|
||||
ElMessageBox.confirm('确定要停用该流程吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
disableFlow(flow.flowId).then((res) => {
|
||||
handleQuery();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 显示启用确认弹窗
|
||||
const showEnableConfirm = (flow) => {
|
||||
ElMessageBox.confirm('确定要启用该流程吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
enableFlow(flow.flowId).then((res) => {
|
||||
handleQuery();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 显示删除确认弹窗
|
||||
const showDeleteConfirm = (flow) => {
|
||||
ElMessageBox.confirm('确定要删除该流程吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
deleteFlow(flow.flowId).then((res) => {
|
||||
handleQuery();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 组件挂载后执行查询操作
|
||||
onMounted(() => {
|
||||
handleQuery();
|
||||
});
|
||||
|
||||
// 查询分组列表
|
||||
const handleQuery = () => {
|
||||
queryGroupList().then((res) => {
|
||||
const {data} = res;
|
||||
successGroupList.value = data;
|
||||
if (data.length > 0) {
|
||||
getDataList();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 删除分组
|
||||
const deleteGroup = async (id) => {
|
||||
state.queryForm.groupId = id;
|
||||
await getDataList();
|
||||
// 获取当前
|
||||
if (state.pagination?.total > 0) {
|
||||
ElMessage.warning('删除失败,存在可用流程');
|
||||
return;
|
||||
}
|
||||
|
||||
delGroup(id).then(() => {
|
||||
ElMessage.success('删除成功');
|
||||
handleQuery();
|
||||
});
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 60px;
|
||||
padding-top: 5px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
> div:nth-child(2) > div:first-child {
|
||||
font-size: 15px;
|
||||
height: 30px;
|
||||
font-weight: bolder;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
> div:nth-child(2) > div:last-child {
|
||||
font-size: 12px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.box-card {
|
||||
width: 80%;
|
||||
margin-left: 10%;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.menu-right {
|
||||
width: 250px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
47
src/views/flow/i18n/en.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
// 定义通用内容
|
||||
export default {
|
||||
flow: {
|
||||
allUser: 'all user',
|
||||
end: 'end',
|
||||
initiator: 'initiator',
|
||||
approver: 'approver',
|
||||
carbonCopyRecipient: 'carbonCopy',
|
||||
conditionalBranching: 'conditional',
|
||||
parallelBranch: 'parallel',
|
||||
basicInformation: 'basic information',
|
||||
formDesign: 'form design',
|
||||
processDesign: 'process design',
|
||||
processCheck: 'process check',
|
||||
publish: 'publish',
|
||||
submit: 'submit',
|
||||
checkSuccess: 'check success',
|
||||
checkSubSuccess: 'check success,submit?',
|
||||
checkIng: 'check ing',
|
||||
checkSubIng: 'check ing',
|
||||
logo: 'logo',
|
||||
name: 'name',
|
||||
remark: 'remark',
|
||||
group: 'group',
|
||||
admin: 'admin',
|
||||
groupTips: 'please select a group',
|
||||
emptyComponent: 'please click on the left component and drag it here ',
|
||||
createTime: 'createTime',
|
||||
scopeOfUse: 'scopeOfUse',
|
||||
creationProcess: 'creation process',
|
||||
creationGroup: 'creation group',
|
||||
componentLibrary: 'ComponentLibrary',
|
||||
title: 'title',
|
||||
minLength: 'min length',
|
||||
maxLength: 'max length',
|
||||
regularExpression: 'regularExpression',
|
||||
regularExpressionTip: 'regular expression tip',
|
||||
inputErrorRegularExpressionTip: 'input regular expression tip',
|
||||
defaultTip: 'default',
|
||||
enTitle: 'en title',
|
||||
required: 'required',
|
||||
other: 'other',
|
||||
tips: 'tips',
|
||||
servicePrefix: 'service prefix',
|
||||
servicePrefixTips: 'input tips',
|
||||
},
|
||||
};
|
||||
47
src/views/flow/i18n/zh-cn.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
// 定义通用内容
|
||||
export default {
|
||||
flow: {
|
||||
allUser: '所有人',
|
||||
end: '结束',
|
||||
initiator: '发起人',
|
||||
approver: '审批人',
|
||||
carbonCopyRecipient: '抄送人',
|
||||
conditionalBranching: '条件分支',
|
||||
parallelBranch: '并行分支',
|
||||
basicInformation: '基础信息',
|
||||
formDesign: '表单设计',
|
||||
processDesign: '流程设计',
|
||||
processCheck: '流程检查',
|
||||
publish: '发布',
|
||||
submit: '提交',
|
||||
remark: '备注',
|
||||
group: '分组',
|
||||
groupTips: '请选择分组',
|
||||
admin: '管理员',
|
||||
checkSuccess: '检查成功',
|
||||
checkSubSuccess: '流程检查完成,现在提交?',
|
||||
checkIng: '检查中',
|
||||
checkSubIng: '正在检查流程信息',
|
||||
emptyComponent: '请点击左侧组件拖拽到此处',
|
||||
logo: '图标',
|
||||
name: '名称',
|
||||
createTime: '创建时间',
|
||||
scopeOfUse: '使用范围',
|
||||
creationProcess: '创建流程',
|
||||
creationGroup: '创建分组',
|
||||
componentLibrary: '组件库',
|
||||
title: '标题',
|
||||
enTitle: '英文标题',
|
||||
required: '必填',
|
||||
other: '其他',
|
||||
tips: '提示',
|
||||
minLength: '最小长度',
|
||||
maxLength: '最大长度',
|
||||
regularExpression: '正则表达式',
|
||||
regularExpressionTip: '请输入正则表达式',
|
||||
inputErrorRegularExpressionTip: '请输入正确的正则表达式',
|
||||
defaultTip: '默认',
|
||||
servicePrefix: '服务前缀',
|
||||
servicePrefixTips: '请输入服务前缀',
|
||||
},
|
||||
};
|
||||
127
src/views/flow/list/index.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<el-scrollbar>
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row shadow="hover" class="mt-4 ml-8">
|
||||
<el-form :model="queryForm" ref="queryRef" :inline="true" @keyup.enter="handleQuery">
|
||||
<el-form-item label="流程名称" prop="flowName">
|
||||
<el-input placeholder="请输入流程名称" v-model="queryForm.flowName" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button icon="search" type="primary" @click="handleQuery">
|
||||
{{ $t('common.queryBtn') }}
|
||||
</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">{{ $t('common.resetBtn') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-row>
|
||||
|
||||
<el-tabs tab-position="left" class="mt-4 ml-8">
|
||||
<el-tab-pane :label="item.name" v-for="item in successGroupList" :key="item.name">
|
||||
<div class="flex flex-col">
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
<div
|
||||
v-for="(flow, index1) in item.items"
|
||||
:key="index1"
|
||||
class="flex relative items-start p-4 bg-blue-50 rounded-xl shadow-lg dark:bg-slate-800 hover:scale-110 hover:shadow-lg"
|
||||
>
|
||||
<div class="flex justify-center items-center ml-8 w-12 h-12 bg-blue-50 rounded-full border border-blue-100 dark:bg-slate-700 dark:border-slate-600">
|
||||
<upload-img v-model:imageUrl="flow.logo" disabled width="50px" height="50px"></upload-img>
|
||||
</div>
|
||||
<div class="ml-8">
|
||||
<h2 class="font-semibold dark:text-slate-200">{{ flow.name }}</h2>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-slate-400">
|
||||
<el-link @click="startProcess(flow)" type="primary">发起流程</el-link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<start ref="startRef"></start>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="flowList">
|
||||
import { queryMineStartGroupFlowList } from '/@/api/flow/group';
|
||||
import { GroupVO } from '/@/api/flow/group/types';
|
||||
import Start from '/@/views/flow/form/tools/startFlow.vue';
|
||||
import { LocationQuery, LocationQueryValue, useRoute } from 'vue-router';
|
||||
|
||||
// 定义引用
|
||||
const startRef = ref();
|
||||
const queryRef = ref();
|
||||
const successGroupList = ref<GroupVO[]>([]);
|
||||
|
||||
// 定义响应式数据
|
||||
const queryForm = reactive({
|
||||
flowName: '',
|
||||
});
|
||||
|
||||
// 查询流程列表
|
||||
const handleQuery = async () => {
|
||||
const res = await queryMineStartGroupFlowList('0');
|
||||
const { data } = res;
|
||||
|
||||
// 根据用户输入的flowName进行过滤
|
||||
if (queryForm.flowName) {
|
||||
const filteredData = data.map(({ items, ...rest }) => ({
|
||||
...rest,
|
||||
items: items.filter(({ name }) => name.includes(queryForm.flowName)),
|
||||
}));
|
||||
|
||||
successGroupList.value = filteredData;
|
||||
} else {
|
||||
successGroupList.value = data;
|
||||
}
|
||||
};
|
||||
|
||||
// 清空搜索条件并执行查询方法
|
||||
const resetQuery = () => {
|
||||
queryRef.value.resetFields();
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
// 发起流程
|
||||
const startProcess = (flow: any) => {
|
||||
startRef.value.handle(flow);
|
||||
};
|
||||
|
||||
const route = useRoute();
|
||||
// 页面加载完成后执行查询方法
|
||||
onMounted(() => {
|
||||
handleQuery();
|
||||
// 流程参数
|
||||
const query: LocationQuery = route.query;
|
||||
const flowId = (query.flowId as LocationQueryValue) ?? '';
|
||||
if (flowId) {
|
||||
startProcess({ flowId: flowId });
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.item {
|
||||
margin: 5px 20px;
|
||||
padding: 5px;
|
||||
padding-bottom: 0px;
|
||||
width: 260px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-radius: 5px;
|
||||
border: 1px solid var(--el-color-info);
|
||||
|
||||
.f2 {
|
||||
font-weight: bolder;
|
||||
height: 50px;
|
||||
margin-left: 15px;
|
||||
width: 183px;
|
||||
}
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
border: 1px solid var(--el-color-primary);
|
||||
}
|
||||
</style>
|
||||
143
src/views/flow/task/cc.vue
Normal file
@@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row shadow="hover" v-show="showSearch" class="ml10">
|
||||
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
|
||||
<el-form-item label="发起时间" prop="taskTime">
|
||||
<el-date-picker
|
||||
type="datetimerange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
v-model="state.queryForm.taskTime"
|
||||
is-range
|
||||
range-separator="To"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button icon="search" type="primary" @click="getDataList">
|
||||
{{ $t('common.queryBtn') }}
|
||||
</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">{{ $t('common.resetBtn') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<div class="mb8" style="width: 100%">
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
class="ml10"
|
||||
style="float: right; margin-right: 20px"
|
||||
@queryTable="getDataList"
|
||||
></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
<el-table
|
||||
ref="dataTableRef"
|
||||
v-loading="loading"
|
||||
:data="state.dataList"
|
||||
highlight-current-row
|
||||
border
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
>
|
||||
<el-table-column label="分组" prop="groupName" width="100" />
|
||||
<el-table-column label="流程" prop="processName" />
|
||||
<el-table-column label="发起人" prop="startUserName" />
|
||||
<el-table-column label="发起时间" prop="startTime" />
|
||||
<el-table-column label="节点" prop="nodeName" />
|
||||
<el-table-column label="抄送时间" prop="nodeTime" />
|
||||
|
||||
<el-table-column fixed="right" label="操作" width="200">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" size="small" icon="View" link @click="deal(scope.row)"> 查看 </el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination @current-change="currentChangeHandle" @size-change="sizeChangeHandle" v-bind="state.pagination"></pagination>
|
||||
</div>
|
||||
<!-- 右侧抽屉-->
|
||||
<el-drawer v-model="rightDrawerVisible" v-if="rightDrawerVisible" direction="rtl" size="600px">
|
||||
<template #header>
|
||||
<h3>{{ currentData?.processName }}</h3>
|
||||
</template>
|
||||
<template #default>
|
||||
<el-card class="box-card">
|
||||
<FormCreate :rule="rule" v-model="formData" v-model:api="fApi" />
|
||||
</el-card>
|
||||
<flow-node-format
|
||||
:disableSelect="true"
|
||||
:processInstanceId="currentData.processInstanceId"
|
||||
:flow-id="currentData.flowId"
|
||||
ref="flowNodeFormatRef"
|
||||
></flow-node-format>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import FlowNodeFormat from '/@/views/flow/form/tools/FlowNodeFormatData.vue';
|
||||
import FormCreate from '/@/views/flow/workflow/components/FormCreate.vue';
|
||||
|
||||
import { queryMineCC, queryMineCCDetail } from '/@/api/flow/task';
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
|
||||
import FcDesigner from 'form-create-designer';
|
||||
import { processFormItemsWithPerms } from '/@/views/flow/workflow/utils/formPermissions';
|
||||
|
||||
const rule = ref([]);
|
||||
const fApi = ref();
|
||||
const formData = ref({});
|
||||
const currentData = ref();
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
pageList: queryMineCC,
|
||||
queryForm: {
|
||||
taskTime: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const { tableStyle ,getDataList, currentChangeHandle,
|
||||
sortChangeHandle,
|
||||
sizeChangeHandle, } = useTable(state);
|
||||
|
||||
const rightDrawerVisible = ref(false);
|
||||
|
||||
const loading = ref(false);
|
||||
const showSearch = ref(true);
|
||||
const queryRef = ref();
|
||||
|
||||
/**
|
||||
* 点击开始处理
|
||||
* @param row
|
||||
*/
|
||||
const deal = (row) => {
|
||||
currentData.value = row;
|
||||
|
||||
queryMineCCDetail({ id: row.id }).then((res) => {
|
||||
const { formItems, formPerms, formData: responseFormData } = res.data;
|
||||
|
||||
// 解析表单项并处理权限
|
||||
const parsedFormItems = FcDesigner.formCreate.parseJson(formItems);
|
||||
const itemsWithPerms = processFormItemsWithPerms(parsedFormItems, formPerms);
|
||||
|
||||
rule.value = itemsWithPerms;
|
||||
formData.value = FcDesigner.formCreate.parseJson(responseFormData);
|
||||
currentOpenFlowForm.value = formItems;
|
||||
rightDrawerVisible.value = true;
|
||||
});
|
||||
};
|
||||
const currentOpenFlowForm = ref();
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
queryRef.value.resetFields();
|
||||
getDataList();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getDataList();
|
||||
});
|
||||
</script>
|
||||
140
src/views/flow/task/completed.vue
Normal file
@@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row shadow="hover" v-show="showSearch" class="ml10">
|
||||
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
|
||||
<el-form-item label="发起时间" prop="taskTime">
|
||||
<el-date-picker
|
||||
type="datetimerange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
v-model="state.queryForm.taskTime"
|
||||
is-range
|
||||
range-separator="To"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button icon="search" type="primary" @click="getDataList">
|
||||
{{ $t('common.queryBtn') }}
|
||||
</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">{{ $t('common.resetBtn') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<div class="mb8" style="width: 100%">
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
class="ml10"
|
||||
style="float: right; margin-right: 20px"
|
||||
@queryTable="getDataList"
|
||||
></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table
|
||||
ref="dataTableRef"
|
||||
v-loading="loading"
|
||||
:data="state.dataList"
|
||||
highlight-current-row
|
||||
border
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
>
|
||||
<el-table-column label="分组" prop="groupName" width="100" />
|
||||
<el-table-column label="流程" prop="processName" />
|
||||
<el-table-column label="发起人" prop="rootUserName" />
|
||||
<el-table-column label="发起时间" prop="startTime" />
|
||||
<el-table-column label="任务名称" prop="taskName" />
|
||||
<el-table-column label="任务开始时间" prop="taskCreateTime" />
|
||||
<el-table-column label="任务结束时间" prop="taskEndTime" />
|
||||
<el-table-column fixed="right" label="操作" width="200">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" size="small" link icon="View" @click="deal(scope.row)"> 查看 </el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination @current-change="currentChangeHandle" @size-change="sizeChangeHandle" v-bind="state.pagination"></pagination>
|
||||
</div>
|
||||
<!-- 右侧抽屉-->
|
||||
<el-drawer v-model="rightDrawerVisible" v-if="rightDrawerVisible" direction="rtl" size="600px">
|
||||
<template #header>
|
||||
<h3>{{ currentData?.processName }}</h3>
|
||||
</template>
|
||||
<template #default>
|
||||
<el-card class="box-card">
|
||||
<FormCreate
|
||||
:rule="rule"
|
||||
v-model="formData"
|
||||
v-model:api="fApi"
|
||||
/>
|
||||
</el-card>
|
||||
<flow-node-format
|
||||
:disableSelect="true"
|
||||
:processInstanceId="currentData.processInstanceId"
|
||||
:flow-id="currentData.flowId"
|
||||
ref="flowNodeFormatRef"
|
||||
></flow-node-format>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import FlowNodeFormat from '/@/views/flow/form/tools/FlowNodeFormatData.vue';
|
||||
import FormCreate from '/@/views/flow/workflow/components/FormCreate.vue';
|
||||
|
||||
import { queryMineEndTask, queryTask, stopProcessInstance } from '/@/api/flow/task';
|
||||
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import FcDesigner from 'form-create-designer';
|
||||
import { processFormItemsWithPerms } from '/@/views/flow/workflow/utils/formPermissions';
|
||||
|
||||
const rule = ref([]);
|
||||
const fApi = ref();
|
||||
const formData = ref({});
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
pageList: queryMineEndTask,
|
||||
queryForm: {
|
||||
taskTime: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const { tableStyle, getDataList, currentChangeHandle, sortChangeHandle, sizeChangeHandle } = useTable(state);
|
||||
|
||||
const rightDrawerVisible = ref(false);
|
||||
|
||||
const loading = ref(false);
|
||||
const showSearch = ref(true);
|
||||
const queryRef = ref();
|
||||
const currentData = ref();
|
||||
/**
|
||||
* 点击开始处理
|
||||
* @param row
|
||||
*/
|
||||
const deal = (row) => {
|
||||
currentData.value = row;
|
||||
queryTask(row.taskId, true).then((res) => {
|
||||
const { formItems, formPerms, formData: responseFormData } = res.data;
|
||||
|
||||
// 解析表单项并处理权限
|
||||
const parsedFormItems = FcDesigner.formCreate.parseJson(formItems);
|
||||
const itemsWithPerms = processFormItemsWithPerms(parsedFormItems, formPerms);
|
||||
|
||||
rule.value = itemsWithPerms;
|
||||
formData.value = responseFormData;
|
||||
currentOpenFlowForm.value = formItems;
|
||||
rightDrawerVisible.value = true;
|
||||
});
|
||||
};
|
||||
const currentOpenFlowForm = ref();
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
queryRef.value.resetFields();
|
||||
getDataList();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getDataList();
|
||||
});
|
||||
</script>
|
||||
84
src/views/flow/task/handler/agree.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { completeTask } from '/@/api/flow/task'
|
||||
import type { FlowFormItem, TaskData, TaskSubmitParam } from './types'
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const submitDesc = ref('')
|
||||
const currentData = ref<TaskData>()
|
||||
const currentOpenFlowForm = ref<FlowFormItem[]>()
|
||||
|
||||
const handle = (row: TaskData, formData: FlowFormItem[]) => {
|
||||
submitDesc.value = ''
|
||||
currentData.value = row
|
||||
currentOpenFlowForm.value = formData
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
defineExpose({ handle })
|
||||
const emit = defineEmits<{
|
||||
(e: 'taskSubmitEvent'): void
|
||||
}>()
|
||||
|
||||
const submit = async () => {
|
||||
if (!currentData.value || !currentOpenFlowForm.value) return
|
||||
|
||||
const formData: Record<string, any> = {}
|
||||
formData[`${currentData.value.nodeId}_approve_condition`] = true
|
||||
|
||||
const param: TaskSubmitParam = {
|
||||
paramMap: formData,
|
||||
taskId: currentData.value.taskId,
|
||||
taskLocalParamMap: {
|
||||
approveDesc: submitDesc.value,
|
||||
},
|
||||
}
|
||||
|
||||
try {
|
||||
await completeTask(param)
|
||||
dialogVisible.value = false
|
||||
emit('taskSubmitEvent')
|
||||
} catch (error) {
|
||||
console.error('Failed to complete task:', error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
title="提交审核"
|
||||
:width="400"
|
||||
class="rounded-lg shadow-xl"
|
||||
>
|
||||
<div class="p-4 space-y-4">
|
||||
<el-input
|
||||
v-model="submitDesc"
|
||||
type="textarea"
|
||||
maxlength="100"
|
||||
:rows="5"
|
||||
placeholder="请输入审核意见..."
|
||||
show-word-limit
|
||||
class="w-full transition-all duration-300 focus:ring-2 focus:ring-primary"
|
||||
/>
|
||||
|
||||
<div class="flex justify-end pt-4 space-x-3">
|
||||
<el-button
|
||||
@click="dialogVisible = false"
|
||||
class="hover:bg-gray-100 dark:hover:bg-gray-700"
|
||||
>
|
||||
取消
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="submit"
|
||||
class="bg-primary hover:bg-primary-focus"
|
||||
>
|
||||
确定
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
56
src/views/flow/task/handler/refuse.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<script setup lang="ts">
|
||||
import {defineExpose} from 'vue';
|
||||
import {completeTask} from '/@/api/flow/task';
|
||||
|
||||
const dialogVisible = ref(false);
|
||||
|
||||
const submitDesc = ref('');
|
||||
|
||||
const currentData = ref();
|
||||
const currentProcessInstanceId = ref('')
|
||||
const currentOpenFlowForm = ref();
|
||||
|
||||
const handle = (row, formData) => {
|
||||
submitDesc.value = '';
|
||||
currentProcessInstanceId.value = row.processInstanceId;
|
||||
currentData.value = row;
|
||||
currentOpenFlowForm.value = formData;
|
||||
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
|
||||
defineExpose({ handle });
|
||||
const emit = defineEmits(['taskSubmitEvent']);
|
||||
|
||||
const submit = () => {
|
||||
const formData: Record<string, any> = {};
|
||||
formData[`${currentData.value.nodeId}_approve_condition`] = false;
|
||||
|
||||
const param = {
|
||||
paramMap: formData,
|
||||
taskId: currentData.value.taskId,
|
||||
taskLocalParamMap: {
|
||||
approveDesc: '拒绝原因:' + submitDesc.value,
|
||||
},
|
||||
};
|
||||
|
||||
completeTask(param).then((res) => {
|
||||
dialogVisible.value = false;
|
||||
emit('taskSubmitEvent');
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog v-model="dialogVisible" title="拒绝审核" width="400px">
|
||||
<el-input v-model="submitDesc" type="textarea" maxlength="100" :rows="5" placeholder="审核意见" show-word-limit />
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submit"> 确定 </el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
113
src/views/flow/task/handler/transfer.vue
Normal file
@@ -0,0 +1,113 @@
|
||||
<script setup lang="ts">
|
||||
import { defineExpose, ref } from 'vue';
|
||||
import { transferTask } from '/@/api/flow/task';
|
||||
import selectShow from '/@/components/OrgSelector/index.vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
interface FlowFormItem {
|
||||
id: string;
|
||||
type: string;
|
||||
props: {
|
||||
value: any;
|
||||
label?: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface TaskData {
|
||||
taskId: string;
|
||||
nodeId: string;
|
||||
}
|
||||
|
||||
const dialogVisible = ref(false);
|
||||
const submitDesc = ref('');
|
||||
const userList = ref<Array<{ id: string }>>([]);
|
||||
const currentData = ref<TaskData>();
|
||||
const currentOpenFlowForm = ref<FlowFormItem[]>();
|
||||
|
||||
const handle = (row: TaskData, formData: FlowFormItem[]) => {
|
||||
submitDesc.value = '';
|
||||
currentData.value = row;
|
||||
currentOpenFlowForm.value = formData;
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
|
||||
defineExpose({ handle });
|
||||
const emit = defineEmits(['taskSubmitEvent']);
|
||||
|
||||
const validateForm = (): boolean => {
|
||||
if (userList.value.length === 0) {
|
||||
ElMessage.warning('请选择转办人员');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!submitDesc.value.trim()) {
|
||||
ElMessage.warning('请输入审核意见');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (submitDesc.value.trim().length < 2) {
|
||||
ElMessage.warning('审核意见至少需要2个字符');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
if (!currentOpenFlowForm.value || !currentData.value) return;
|
||||
|
||||
// 表单校验
|
||||
if (!validateForm()) return;
|
||||
|
||||
try {
|
||||
const formData: Record<string, any> = {};
|
||||
|
||||
formData[`${currentData.value.nodeId}_approve_condition`] = true;
|
||||
|
||||
const param = {
|
||||
paramMap: formData,
|
||||
taskId: currentData.value.taskId,
|
||||
targetUserIdList: userList.value.map((user) => user.id),
|
||||
taskLocalParamMap: {
|
||||
approveDesc: '转办事由:' + submitDesc.value.trim(),
|
||||
},
|
||||
};
|
||||
|
||||
await transferTask(param);
|
||||
ElMessage.success('转办成功');
|
||||
dialogVisible.value = false;
|
||||
emit('taskSubmitEvent');
|
||||
} catch (error) {
|
||||
ElMessage.error('转办失败,请重试');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog v-model="dialogVisible" width="400px" :close-on-click-modal="false" :close-on-press-escape="false" class="transfer-dialog">
|
||||
<div class="space-y-4">
|
||||
<div class="w-full">
|
||||
<label class="block pb-2">
|
||||
<span class="text-base font-medium after:content-['*'] after:text-red-500 after:ml-1"> 选择转办人员 </span>
|
||||
</label>
|
||||
<select-show v-model:orgList="userList" type="user" :multiple="true"></select-show>
|
||||
</div>
|
||||
|
||||
<div class="w-full">
|
||||
<label class="block pb-2">
|
||||
<span class="text-base font-medium after:content-['*'] after:text-red-500 after:ml-1"> 审核意见 </span>
|
||||
</label>
|
||||
<el-input v-model="submitDesc" type="textarea" maxlength="100" :rows="5" placeholder="请输入审核意见(2-100字)" show-word-limit />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<span class="flex justify-end gap-2">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submit">确定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
22
src/views/flow/task/handler/types.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
// First let's create types for our form data
|
||||
export interface FlowFormItem {
|
||||
id: string
|
||||
type: string
|
||||
props: {
|
||||
value: any
|
||||
[key: string]: any
|
||||
}
|
||||
}
|
||||
|
||||
export interface TaskData {
|
||||
taskId: string
|
||||
nodeId: string
|
||||
}
|
||||
|
||||
export interface TaskSubmitParam {
|
||||
paramMap: Record<string, any>
|
||||
taskId: string
|
||||
taskLocalParamMap: {
|
||||
approveDesc: string
|
||||
}
|
||||
}
|
||||
198
src/views/flow/task/pending.vue
Normal file
@@ -0,0 +1,198 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row shadow="hover" v-show="showSearch" class="ml10">
|
||||
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
|
||||
<el-form-item label="流程" prop="processName">
|
||||
<el-input placeholder="请输入流程名称" v-model="state.queryForm.processName" />
|
||||
</el-form-item>
|
||||
<el-form-item label="发起时间" prop="taskTime">
|
||||
<el-date-picker
|
||||
type="datetimerange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
v-model="state.queryForm.taskTime"
|
||||
is-range
|
||||
range-separator="To"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button icon="search" type="primary" @click="getDataList">
|
||||
{{ $t('common.queryBtn') }}
|
||||
</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">{{ $t('common.resetBtn') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<div class="mb8" style="width: 100%">
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
class="ml10"
|
||||
style="float: right; margin-right: 20px"
|
||||
@queryTable="getDataList"
|
||||
></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
<el-table
|
||||
ref="dataTableRef"
|
||||
v-loading="loading"
|
||||
:data="state.dataList"
|
||||
highlight-current-row
|
||||
border
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
>
|
||||
<el-table-column label="分组" prop="groupName" width="100" />
|
||||
<el-table-column label="流程" prop="processName" />
|
||||
<el-table-column label="发起人" prop="rootUserName" />
|
||||
<el-table-column label="发起时间" prop="startTime" />
|
||||
<el-table-column label="当前节点" prop="taskName" />
|
||||
<el-table-column label="任务时间" prop="taskCreateTime" />
|
||||
<el-table-column fixed="right" label="操作" width="200">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" size="small" link icon="VideoPlay" @click="deal(scope.row)"> 开始处理 </el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination @current-change="currentChangeHandle" @size-change="sizeChangeHandle" v-bind="state.pagination"></pagination>
|
||||
|
||||
<!-- 右侧抽屉-->
|
||||
<el-drawer v-model="rightDrawerVisible" v-if="rightDrawerVisible" direction="rtl" size="600px">
|
||||
<template #header>
|
||||
<h3>{{ currentData?.processName }}</h3>
|
||||
</template>
|
||||
<template #default>
|
||||
<el-card class="box-card">
|
||||
<FormCreate :rule="rule" v-model="formData" v-model:api="fApi" />
|
||||
</el-card>
|
||||
<flow-node-format
|
||||
:disableSelect="true"
|
||||
:task-id="currentData.taskId"
|
||||
:processInstanceId="currentData.processInstanceId"
|
||||
:flow-id="currentData.flowId"
|
||||
ref="flowNodeFormatRef"
|
||||
class="mt-4"
|
||||
/>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div style="flex: auto">
|
||||
<el-button size="large" type="danger" icon="Close" @click="refuseTask">拒绝</el-button>
|
||||
<el-button size="large" type="primary" icon="Check" @click="submitTask">提交</el-button>
|
||||
<el-button size="large" type="info" icon="Share" @click="transferTask">转办</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-drawer>
|
||||
|
||||
<!--同意提交处理-->
|
||||
<agree-handle @taskSubmitEvent="taskSubmitEvent" ref="agreeHandler"></agree-handle>
|
||||
|
||||
<!--拒绝审核处理-->
|
||||
<refuse-handle @taskSubmitEvent="taskSubmitEvent" ref="refuseHandler"></refuse-handle>
|
||||
|
||||
<!--转办处理-->
|
||||
<transfer-handle @taskSubmitEvent="taskSubmitEvent" ref="transferHandler"></transfer-handle>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import AgreeHandle from './handler/agree.vue';
|
||||
import RefuseHandle from './handler/refuse.vue';
|
||||
import TransferHandle from './handler/transfer.vue';
|
||||
import FlowNodeFormat from '/@/views/flow/form/tools/FlowNodeFormatData.vue';
|
||||
import { queryMineTask, queryTask } from '/@/api/flow/task';
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import FcDesigner from 'form-create-designer';
|
||||
import { processFormItemsWithPerms } from '/@/views/flow/workflow/utils/formPermissions';
|
||||
import FormCreate from '/@/views/flow/workflow/components/FormCreate.vue';
|
||||
|
||||
const rightDrawerVisible = ref(false);
|
||||
const showSearch = ref(true);
|
||||
const loading = ref(false);
|
||||
const queryRef = ref();
|
||||
|
||||
const fApi = ref();
|
||||
const formData = ref({});
|
||||
|
||||
// Define the FormItem interface if not already defined
|
||||
interface FormItem {
|
||||
// Define the properties of FormItem based on your data structure
|
||||
}
|
||||
|
||||
const rule = ref<FormItem[]>([]);
|
||||
|
||||
const currentData = ref();
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
pageList: queryMineTask,
|
||||
queryForm: {
|
||||
processName: '',
|
||||
taskTime: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const { tableStyle, getDataList, currentChangeHandle, sizeChangeHandle } = useTable(state);
|
||||
|
||||
/**
|
||||
* 点击开始处理
|
||||
* @param row
|
||||
*/
|
||||
const deal = (row: any) => {
|
||||
currentData.value = row;
|
||||
queryTask(row.taskId, false).then((res) => {
|
||||
const { formItems, formPerms, formData: responseFormData } = res.data;
|
||||
|
||||
// 解析表单项
|
||||
const parsedFormItems = FcDesigner.formCreate.parseJson(formItems);
|
||||
|
||||
// 递归处理所有表单项的权限
|
||||
const itemsWithPerms = processFormItemsWithPerms(parsedFormItems, formPerms);
|
||||
|
||||
rule.value = itemsWithPerms;
|
||||
formData.value = responseFormData;
|
||||
currentOpenFlowForm.value = formItems;
|
||||
rightDrawerVisible.value = true;
|
||||
});
|
||||
};
|
||||
const currentOpenFlowForm = ref();
|
||||
|
||||
const agreeHandler = ref();
|
||||
const refuseHandler = ref();
|
||||
const transferHandler = ref();
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
queryRef.value.resetFields();
|
||||
getDataList();
|
||||
};
|
||||
|
||||
const taskSubmitEvent = () => {
|
||||
rightDrawerVisible.value = false;
|
||||
getDataList();
|
||||
};
|
||||
|
||||
/**
|
||||
* 提交任务
|
||||
*/
|
||||
const submitTask = () => {
|
||||
agreeHandler.value.handle(currentData.value, currentOpenFlowForm.value);
|
||||
};
|
||||
/**
|
||||
* 拒绝任务
|
||||
*/
|
||||
const refuseTask = () => {
|
||||
refuseHandler.value.handle(currentData.value, currentOpenFlowForm.value);
|
||||
};
|
||||
|
||||
/**
|
||||
* 转办任务
|
||||
*/
|
||||
const transferTask = () => {
|
||||
transferHandler.value.handle(currentData.value, currentOpenFlowForm.value);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getDataList();
|
||||
});
|
||||
</script>
|
||||
161
src/views/flow/task/started.vue
Normal file
@@ -0,0 +1,161 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row shadow="hover" v-show="showSearch" class="ml10">
|
||||
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="state.queryForm.status" placeholder="请选择状态">
|
||||
<el-option :key="1" label="进行中" :value="1" />
|
||||
<el-option :key="2" label="已结束" :value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="发起时间" prop="taskTime">
|
||||
<el-date-picker
|
||||
type="datetimerange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
v-model="state.queryForm.taskTime"
|
||||
is-range
|
||||
range-separator="To"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button icon="search" type="primary" @click="getDataList">
|
||||
{{ $t('common.queryBtn') }}
|
||||
</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">{{ $t('common.resetBtn') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<div class="mb8" style="width: 100%">
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
class="ml10"
|
||||
style="float: right; margin-right: 20px"
|
||||
@queryTable="getDataList"
|
||||
></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
<el-table
|
||||
ref="dataTableRef"
|
||||
v-loading="loading"
|
||||
:data="state.dataList"
|
||||
highlight-current-row
|
||||
border
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
>
|
||||
<el-table-column label="分组" prop="groupName" width="100" />
|
||||
<el-table-column label="流程" prop="name" width="150" />
|
||||
<el-table-column label="发起时间" prop="createTime" width="200" />
|
||||
<el-table-column label="结束时间" prop="endTime" width="200" />
|
||||
<el-table-column label="状态" prop="taskCreateTime" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.status == 1" type="success">进行中</el-tag>
|
||||
<el-tag v-else>已结束</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column fixed="right" label="操作">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" size="small" link icon="View" @click="deal(scope.row)"> 查看 </el-button>
|
||||
<el-button :disabled="scope.row.status != 1" type="primary" size="small" link icon="VideoPause" @click="stop(scope.row)">
|
||||
终止流程
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination @current-change="currentChangeHandle" @size-change="sizeChangeHandle" v-bind="state.pagination"></pagination>
|
||||
<!-- 右侧抽屉-->
|
||||
<el-drawer v-model="rightDrawerVisible" v-if="rightDrawerVisible" direction="rtl" size="600px">
|
||||
<template #header>
|
||||
<h3>{{ currentData?.name }}</h3>
|
||||
</template>
|
||||
<template #default>
|
||||
<el-card class="box-card">
|
||||
<FormCreate :rule="rule" v-model="formData" v-model:api="fApi" />
|
||||
</el-card>
|
||||
<flow-node-format
|
||||
:disableSelect="true"
|
||||
:processInstanceId="currentData.processInstanceId"
|
||||
:flow-id="currentData.flowId"
|
||||
ref="flowNodeFormatRef"
|
||||
></flow-node-format>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import FlowNodeFormat from '/@/views/flow/form/tools/FlowNodeFormatData.vue';
|
||||
import { queryMineStarted, stopProcessInstance } from '/@/api/flow/task';
|
||||
import { detail } from '/@/api/flow/processInstance';
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
|
||||
import FcDesigner from 'form-create-designer';
|
||||
import { processFormItemsWithPerms } from '/@/views/flow/workflow/utils/formPermissions';
|
||||
import FormCreate from '/@/views/flow/workflow/components/FormCreate.vue';
|
||||
|
||||
const rule = ref([]);
|
||||
const fApi = ref();
|
||||
const formData = ref({});
|
||||
|
||||
const rightDrawerVisible = ref(false);
|
||||
|
||||
const loading = ref(false);
|
||||
const showSearch = ref(true);
|
||||
const queryRef = ref();
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
pageList: queryMineStarted,
|
||||
queryForm: {
|
||||
taskTime: undefined,
|
||||
status: 1,
|
||||
},
|
||||
});
|
||||
|
||||
const { tableStyle, getDataList, currentChangeHandle, sortChangeHandle, sizeChangeHandle } = useTable(state);
|
||||
function stop(row) {
|
||||
stopProcessInstance({
|
||||
processInstanceId: row.processInstanceId,
|
||||
}).then((res) => {
|
||||
getDataList();
|
||||
});
|
||||
}
|
||||
|
||||
const currentData = ref();
|
||||
/**
|
||||
* 点击开始处理
|
||||
* @param row
|
||||
*/
|
||||
const deal = (row) => {
|
||||
currentData.value = row;
|
||||
detail({
|
||||
processInstanceId: row.processInstanceId,
|
||||
}).then((res) => {
|
||||
const { formItems, formPerms, formData: responseFormData } = res.data;
|
||||
|
||||
// 解析表单项并处理权限
|
||||
const parsedFormItems = FcDesigner.formCreate.parseJson(formItems);
|
||||
const itemsWithPerms = processFormItemsWithPerms(parsedFormItems, formPerms);
|
||||
|
||||
rule.value = itemsWithPerms;
|
||||
formData.value = FcDesigner.formCreate.parseJson(responseFormData);
|
||||
currentOpenFlowForm.value = formItems;
|
||||
rightDrawerVisible.value = true;
|
||||
});
|
||||
};
|
||||
const currentOpenFlowForm = ref();
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
queryRef.value.resetFields();
|
||||
getDataList();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getDataList();
|
||||
});
|
||||
</script>
|
||||
74
src/views/flow/workflow/assets/base.css
Normal file
@@ -0,0 +1,74 @@
|
||||
/* color palette from <https://github.com/vuejs/theme> */
|
||||
:root {
|
||||
--vt-c-white: #ffffff;
|
||||
--vt-c-white-soft: #f8f8f8;
|
||||
--vt-c-white-mute: #f2f2f2;
|
||||
|
||||
--vt-c-black: #181818;
|
||||
--vt-c-black-soft: #222222;
|
||||
--vt-c-black-mute: #282828;
|
||||
|
||||
--vt-c-indigo: #2c3e50;
|
||||
|
||||
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
|
||||
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
|
||||
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
|
||||
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
|
||||
|
||||
--vt-c-text-light-1: var(--vt-c-indigo);
|
||||
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
||||
--vt-c-text-dark-1: var(--vt-c-white);
|
||||
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
||||
}
|
||||
|
||||
/* semantic color variables for this project */
|
||||
:root {
|
||||
--color-background: var(--vt-c-white);
|
||||
--color-background-soft: var(--vt-c-white-soft);
|
||||
--color-background-mute: var(--vt-c-white-mute);
|
||||
|
||||
--color-border: var(--vt-c-divider-light-2);
|
||||
--color-border-hover: var(--vt-c-divider-light-1);
|
||||
|
||||
--color-heading: var(--vt-c-text-light-1);
|
||||
--color-text: var(--vt-c-text-light-1);
|
||||
|
||||
--section-gap: 160px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-background: var(--vt-c-black);
|
||||
--color-background-soft: var(--vt-c-black-soft);
|
||||
--color-background-mute: var(--vt-c-black-mute);
|
||||
|
||||
--color-border: var(--vt-c-divider-dark-2);
|
||||
--color-border-hover: var(--vt-c-divider-dark-1);
|
||||
|
||||
--color-heading: var(--vt-c-text-dark-1);
|
||||
--color-text: var(--vt-c-text-dark-2);
|
||||
}
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
color: var(--color-text);
|
||||
background: var(--color-background);
|
||||
transition: color 0.5s, background-color 0.5s;
|
||||
line-height: 1.6;
|
||||
font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
font-size: 15px;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
BIN
src/views/flow/workflow/assets/images/add-close.png
Normal file
|
After Width: | Height: | Size: 466 B |
BIN
src/views/flow/workflow/assets/images/add-close1.png
Normal file
|
After Width: | Height: | Size: 223 B |
BIN
src/views/flow/workflow/assets/images/cancel.png
Normal file
|
After Width: | Height: | Size: 549 B |
BIN
src/views/flow/workflow/assets/images/check_box.png
Normal file
|
After Width: | Height: | Size: 325 B |
BIN
src/views/flow/workflow/assets/images/icon_file.png
Normal file
|
After Width: | Height: | Size: 209 B |
BIN
src/views/flow/workflow/assets/images/icon_people.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/views/flow/workflow/assets/images/icon_role.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
src/views/flow/workflow/assets/images/jiaojiao.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/views/flow/workflow/assets/images/list_search.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/views/flow/workflow/assets/images/loading.gif
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
src/views/flow/workflow/assets/images/next_level.png
Normal file
|
After Width: | Height: | Size: 245 B |
BIN
src/views/flow/workflow/assets/images/next_level_active.png
Normal file
|
After Width: | Height: | Size: 266 B |
1
src/views/flow/workflow/assets/logo.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69" xmlns:v="https://vecta.io/nano"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
|
||||
|
After Width: | Height: | Size: 308 B |
35
src/views/flow/workflow/assets/main.css
Normal file
@@ -0,0 +1,35 @@
|
||||
@import './base.css';
|
||||
|
||||
#app {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
a,
|
||||
.green {
|
||||
text-decoration: none;
|
||||
color: hsla(160, 100%, 37%, 1);
|
||||
transition: 0.4s;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
a:hover {
|
||||
background-color: hsla(160, 100%, 37%, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
body {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
#app {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
}
|
||||
45
src/views/flow/workflow/components/FormCreate.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<formCreate
|
||||
:rule="rule"
|
||||
:option="options"
|
||||
:modelValue="modelValue"
|
||||
@update:modelValue="emit('update:modelValue', $event)"
|
||||
:api="api"
|
||||
@update:api="emit('update:api', $event)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Api, Rule } from '@form-create/element-ui';
|
||||
|
||||
interface Props {
|
||||
rule: Rule;
|
||||
modelValue: any;
|
||||
api?: Api;
|
||||
submitBtn?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
rule: () => [],
|
||||
modelValue: () => ({}),
|
||||
api: undefined,
|
||||
submitBtn: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'update:api']);
|
||||
|
||||
// Default options
|
||||
const options = computed(() => ({
|
||||
submitBtn: props.submitBtn,
|
||||
}));
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.fc-form-row) {
|
||||
margin-top: 20px !important;
|
||||
}
|
||||
|
||||
:deep(.el-col.el-col-24.fc-form-col) {
|
||||
margin-top: 10px !important;
|
||||
}
|
||||
</style>
|
||||
359
src/views/flow/workflow/components/addNode.vue
Normal file
@@ -0,0 +1,359 @@
|
||||
<template>
|
||||
<div class="add-node-btn-box">
|
||||
<div class="add-node-btn">
|
||||
<el-popover placement="right-start" v-model="visible" width="250px">
|
||||
<div class="add-node-popover-body">
|
||||
<a class="add-node-popover-item approver" @click="addType(1)">
|
||||
<div class="item-wrapper">
|
||||
<span class="iconfont"></span>
|
||||
</div>
|
||||
<p>{{ $t('flow.approver') }}</p>
|
||||
</a>
|
||||
<a class="add-node-popover-item notifier" @click="addType(2)">
|
||||
<div class="item-wrapper">
|
||||
<span class="iconfont"></span>
|
||||
</div>
|
||||
<p>{{ $t('flow.carbonCopyRecipient') }}</p>
|
||||
</a>
|
||||
|
||||
<a class="add-node-popover-item condition" @click="addType(4)">
|
||||
<div class="item-wrapper">
|
||||
<span class="iconfont"></span>
|
||||
</div>
|
||||
<p>{{ $t('flow.conditionalBranching') }}</p>
|
||||
</a>
|
||||
|
||||
<a class="add-node-popover-item ParallelGateway" @click="addType(5)">
|
||||
<div class="item-wrapper">
|
||||
<span class="iconfont"></span>
|
||||
</div>
|
||||
<p>{{ $t('flow.parallelBranch') }}</p>
|
||||
</a>
|
||||
</div>
|
||||
<template #reference>
|
||||
<button class="btn" type="button">
|
||||
<span class="iconfont"></span>
|
||||
</button>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import other from '/@/utils/other';
|
||||
|
||||
let props = defineProps({
|
||||
childNodeP: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
currentNode: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
let emits = defineEmits(['update:childNodeP']);
|
||||
let visible = ref(false);
|
||||
const addType = (type) => {
|
||||
visible.value = false;
|
||||
if (type !== 4 && type !== 5) {
|
||||
var data;
|
||||
if (type === 1) {
|
||||
data = {
|
||||
id: other.getNonDuplicateID(),
|
||||
nodeName: '审批人',
|
||||
error: true,
|
||||
type: 1,
|
||||
parentId: props.currentNode.id,
|
||||
//人员方式
|
||||
assignedType: 1,
|
||||
//单选还是多选
|
||||
multiple: false,
|
||||
//多人审批的选项
|
||||
multipleMode: 1,
|
||||
//第几级部门负责人
|
||||
deptLeaderLevel: 1,
|
||||
//人员选择
|
||||
formUserId: '',
|
||||
formUserName: '',
|
||||
//表单权限
|
||||
formPerms: {},
|
||||
//审批人为空
|
||||
nobody: {
|
||||
handler: 'TO_PASS',
|
||||
assignedUser: [],
|
||||
},
|
||||
//审批人拒绝
|
||||
refuse: {
|
||||
handler: 'TO_END',
|
||||
nodeId: '',
|
||||
},
|
||||
childNode: props.childNodeP,
|
||||
nodeUserList: [],
|
||||
};
|
||||
} else if (type === 2) {
|
||||
data = {
|
||||
id: other.getNonDuplicateID(),
|
||||
parentId: props.currentNode.id,
|
||||
|
||||
nodeName: '抄送人',
|
||||
type: 2,
|
||||
error: true,
|
||||
childNode: props.childNodeP,
|
||||
nodeUserList: [],
|
||||
//表单权限
|
||||
formPerms: {},
|
||||
};
|
||||
}
|
||||
emits('update:childNodeP', data);
|
||||
} else if (type === 4) {
|
||||
let id = other.getNonDuplicateID();
|
||||
emits('update:childNodeP', {
|
||||
nodeName: '条件分支',
|
||||
type: 4,
|
||||
id: id,
|
||||
parentId: props.currentNode.id,
|
||||
|
||||
childNode: props.childNodeP,
|
||||
conditionNodes: [
|
||||
{
|
||||
id: other.getNonDuplicateID(),
|
||||
nodeName: '条件1',
|
||||
groupMode: true,
|
||||
error: true,
|
||||
type: 3,
|
||||
parentId: id,
|
||||
|
||||
priorityLevel: 1,
|
||||
conditionList: [
|
||||
{
|
||||
mode: true,
|
||||
conditionList: [
|
||||
{
|
||||
key: '',
|
||||
expression: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
nodeUserList: [],
|
||||
childNode: null,
|
||||
},
|
||||
{
|
||||
nodeName: '条件2',
|
||||
type: 3,
|
||||
id: other.getNonDuplicateID(),
|
||||
groupMode: true,
|
||||
error: false,
|
||||
parentId: id,
|
||||
priorityLevel: 2,
|
||||
conditionList: [
|
||||
{
|
||||
mode: true,
|
||||
conditionList: [
|
||||
{
|
||||
key: '',
|
||||
expression: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
nodeUserList: [],
|
||||
childNode: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
} else if (type === 5) {
|
||||
let id = other.getNonDuplicateID();
|
||||
emits('update:childNodeP', {
|
||||
nodeName: '并行分支',
|
||||
type: 5,
|
||||
id: id,
|
||||
parentId: props.currentNode.id,
|
||||
|
||||
childNode: props.childNodeP,
|
||||
conditionNodes: [
|
||||
{
|
||||
id: other.getNonDuplicateID(),
|
||||
nodeName: '分支1',
|
||||
placeHolder: '满足条件',
|
||||
parentId: id,
|
||||
|
||||
error: false,
|
||||
type: 3,
|
||||
priorityLevel: 1,
|
||||
conditionList: [],
|
||||
nodeUserList: [],
|
||||
childNode: null,
|
||||
},
|
||||
{
|
||||
nodeName: '分支2',
|
||||
type: 3,
|
||||
id: other.getNonDuplicateID(),
|
||||
parentId: id,
|
||||
|
||||
error: false,
|
||||
placeHolder: '满足条件',
|
||||
|
||||
priorityLevel: 2,
|
||||
conditionList: [],
|
||||
nodeUserList: [],
|
||||
childNode: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="css">
|
||||
.add-node-btn-box {
|
||||
width: 240px;
|
||||
display: inline-flex;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.add-node-btn-box::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
margin: auto;
|
||||
width: 2px;
|
||||
height: 100%;
|
||||
background-color: #cacaca;
|
||||
}
|
||||
|
||||
.add-node-btn {
|
||||
user-select: none;
|
||||
width: 240px;
|
||||
padding: 20px 0 32px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.add-node-btn .btn {
|
||||
outline: none;
|
||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
min-width: 30px;
|
||||
min-height: 30px;
|
||||
background: #3296fa;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
border: none;
|
||||
padding: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
}
|
||||
|
||||
.add-node-btn .btn .iconfont {
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.add-node-btn .btn:hover {
|
||||
transform: scale3d(1.3, 1.3, 1);
|
||||
box-shadow: 0 13px 27px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.add-node-btn .btn:active {
|
||||
transform: none;
|
||||
background: #1e83e9;
|
||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
</style>
|
||||
<style scoped lang="scss">
|
||||
@import '/@/views/flow/workflow/css/workflow.css';
|
||||
|
||||
.add-node-popover-body {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.add-node-popover-item {
|
||||
margin-right: 10px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
|
||||
.item-wrapper {
|
||||
user-select: none;
|
||||
display: inline-block;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
margin-bottom: 5px;
|
||||
background: #fff;
|
||||
border: 1px solid #e2e2e2;
|
||||
border-radius: 50%;
|
||||
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
|
||||
.iconfont {
|
||||
font-size: 35px;
|
||||
line-height: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
&.approver {
|
||||
.item-wrapper {
|
||||
color: #ff943e;
|
||||
}
|
||||
}
|
||||
|
||||
&.notifier {
|
||||
.item-wrapper {
|
||||
//color: #3296fa
|
||||
}
|
||||
}
|
||||
|
||||
&.condition {
|
||||
.item-wrapper {
|
||||
color: #15bc83;
|
||||
}
|
||||
}
|
||||
|
||||
&.ParallelGateway {
|
||||
.item-wrapper {
|
||||
color: rgb(255, 69, 0);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.item-wrapper {
|
||||
background: #3296fa;
|
||||
box-shadow: 0 10px 20px 0 rgba(50, 150, 250, 0.4);
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
.item-wrapper {
|
||||
box-shadow: none;
|
||||
background: #eaeaea;
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
//color: inherit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
312
src/views/flow/workflow/components/drawer/approverDrawer.vue
Normal file
@@ -0,0 +1,312 @@
|
||||
<template>
|
||||
<el-drawer
|
||||
v-model="visible"
|
||||
:append-to-body="true"
|
||||
title="审批人设置"
|
||||
:show-close="false"
|
||||
:size="550"
|
||||
:before-close="saveApprover"
|
||||
@open="openEvent"
|
||||
>
|
||||
<el-tabs type="border-card">
|
||||
<el-tab-pane label="设置审批人">
|
||||
<el-radio-group v-model="approverConfig.assignedType" class="ml-4" @change="assignedTypeChangeEvent">
|
||||
<el-row>
|
||||
<el-col v-for="{ value, label } in setTypes" :key="value" :span="8">
|
||||
<el-radio :label="value">{{ label }}</el-radio>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-radio-group>
|
||||
|
||||
<el-divider />
|
||||
<template v-if="approverConfig.assignedType === 2">
|
||||
<h4>选择部门</h4>
|
||||
<select-show v-model:orgList="approverConfig.nodeUserList" type="dept"></select-show>
|
||||
</template>
|
||||
<template v-if="approverConfig.assignedType === 3">
|
||||
<h4>选择角色</h4>
|
||||
|
||||
<select-show v-model:orgList="approverConfig.nodeUserList" type="role" :multiple="true"></select-show>
|
||||
</template>
|
||||
<template v-if="approverConfig.assignedType === 1">
|
||||
<h4>选择成员</h4>
|
||||
|
||||
<select-show v-model:orgList="approverConfig.nodeUserList" type="user" :multiple="true"></select-show>
|
||||
</template>
|
||||
<template v-if="approverConfig.assignedType === 8">
|
||||
<h4>人员控件</h4>
|
||||
<el-select v-model="approverConfig.formUserId" clearable class="m-2" placeholder="请选择审批表单" size="large">
|
||||
<el-option v-for="item in step2FormUserList" :key="item.field" :label="item.title" :value="item.field" />
|
||||
</el-select>
|
||||
</template>
|
||||
<template v-if="approverConfig.assignedType === 7">
|
||||
<h4>审批终点</h4>
|
||||
<span style="margin-right: 5px; font-size: 14px">到第</span>
|
||||
<el-input-number v-model="approverConfig.deptLeaderLevel" :step="1" :min="1" :max="20" step-strictly size="small" />
|
||||
<span style="margin-left: 5px; font-size: 14px">级部门主管终止</span>
|
||||
</template>
|
||||
<template v-if="approverConfig.assignedType === 4">
|
||||
<h4>选择方式</h4>
|
||||
<el-radio-group v-model="approverConfig.multiple" class="ml-4">
|
||||
<el-radio :label="false" size="large">单选</el-radio>
|
||||
<el-radio :label="true" size="large">多选</el-radio>
|
||||
</el-radio-group>
|
||||
</template>
|
||||
<template
|
||||
v-if="
|
||||
((approverConfig.multiple === true && approverConfig.assignedType === 4) ||
|
||||
(approverConfig.assignedType === 1 && approverConfig.nodeUserList.length > 1) ||
|
||||
approverConfig.assignedType === 2 ||
|
||||
approverConfig.assignedType === 3 ||
|
||||
(approverConfig.assignedType === 7 && approverConfig.deptLeaderLevel > 1) ||
|
||||
(approverConfig.assignedType === 8 && isMultiUserForm(approverConfig.formUserId))) &&
|
||||
approverConfig.assignedType !== 5
|
||||
"
|
||||
>
|
||||
<h4>多人审批时采用的审批方式</h4>
|
||||
<el-radio-group v-model="approverConfig.multipleMode" class="ml-4">
|
||||
<p style="display: block; width: 100%">
|
||||
<el-radio :label="1" size="large">会签(需要所有审批人同意)</el-radio>
|
||||
</p>
|
||||
<p style="display: block; width: 100%">
|
||||
<el-radio :label="2" size="large">或签(一名审批人同意即可)</el-radio>
|
||||
</p>
|
||||
<p style="display: block; width: 100%">
|
||||
<el-radio :label="3" size="large">依次审批(按顺序依次审批)</el-radio>
|
||||
</p>
|
||||
</el-radio-group>
|
||||
</template>
|
||||
<el-divider />
|
||||
|
||||
<h4>审批人为空时</h4>
|
||||
<el-radio-group v-model="approverConfig.nobody.handler" class="ml-4">
|
||||
<el-radio label="TO_PASS" size="large">自动通过</el-radio>
|
||||
<el-radio label="TO_REFUSE" size="large">自动拒绝</el-radio>
|
||||
<el-radio label="TO_END" size="large">自动结束</el-radio>
|
||||
<el-radio label="TO_ADMIN" size="large">转交给管理员</el-radio>
|
||||
<el-radio label="TO_USER" size="large">指定人员</el-radio>
|
||||
</el-radio-group>
|
||||
<select-show
|
||||
v-if="approverConfig?.nobody.handler === 'TO_USER'"
|
||||
v-model:orgList="approverConfig.nobody.assignedUser"
|
||||
type="user"
|
||||
:multiple="false"
|
||||
></select-show>
|
||||
|
||||
<el-divider />
|
||||
|
||||
<template v-if="approverConfig.refuse?.handler">
|
||||
<h4>审批被拒绝</h4>
|
||||
<el-radio-group v-model="approverConfig.refuse.handler" class="ml-4">
|
||||
<el-radio label="TO_END" size="large">直接结束流程</el-radio>
|
||||
<el-radio label="TO_NODE" size="large" v-if="rejectNodeList.length > 0">驳回到指定节点</el-radio>
|
||||
</el-radio-group>
|
||||
<el-select
|
||||
v-if="approverConfig.refuse.handler === 'TO_NODE' && rejectNodeList.length > 0"
|
||||
v-model="approverConfig.refuse.nodeId"
|
||||
placeholder="驳回节点"
|
||||
class="mb-2 w-1/2"
|
||||
>
|
||||
<el-option v-for="item in rejectNodeList" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="表单权限">
|
||||
<form-perm :form-perm="approverConfig.formPerms"></form-perm>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="事件通知">
|
||||
<event-config v-model="approverConfig.eventConfig" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-drawer>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { setTypes } from '../../utils/const';
|
||||
import { useStore } from '../../stores/index';
|
||||
import { useFlowStore } from '../../stores/flow';
|
||||
import FormPerm from './components/formPerm.vue';
|
||||
import EventConfig from './components/eventConfig.vue';
|
||||
import selectShow from '/@/components/OrgSelector/index.vue';
|
||||
import { validateNull } from '/@/utils/validate';
|
||||
import other from '/@/utils/other';
|
||||
import { processFormItemsWithPerms } from '/@/views/flow/workflow/utils/formPermissions';
|
||||
|
||||
let flowStore = useFlowStore();
|
||||
|
||||
const processFormPermissions = () => {
|
||||
const formItems = step2FormList.value;
|
||||
const processedItems = processFormItemsWithPerms(formItems, approverConfig.value.formPerms || {});
|
||||
return processedItems;
|
||||
};
|
||||
|
||||
// 驳回节点列表
|
||||
const rejectNodeList = computed(() => {
|
||||
let values = [];
|
||||
const excType = [1];
|
||||
const arr = {};
|
||||
const obj = {};
|
||||
|
||||
produceSerialNodeList(undefined, flowStore.step3, arr, obj, true);
|
||||
|
||||
const k = arr[approverConfig.value.id];
|
||||
|
||||
if (k == undefined) {
|
||||
return [];
|
||||
}
|
||||
|
||||
for (const item of k) {
|
||||
const type = obj[item].type;
|
||||
if (excType.indexOf(type) > -1 && obj[item].id != approverConfig.value.id) {
|
||||
values.push({ id: obj[item].id, name: obj[item].nodeName });
|
||||
}
|
||||
}
|
||||
return values;
|
||||
});
|
||||
|
||||
function produceSerialNodeList(parentId, process, nodeArr, nodeObj, noBranch) {
|
||||
if (validateNull(process?.id)) {
|
||||
return;
|
||||
}
|
||||
const nodeId = process.id;
|
||||
nodeObj[nodeId] = process;
|
||||
|
||||
if (validateNull(parentId)) {
|
||||
const arr = [];
|
||||
arr.push(nodeId);
|
||||
nodeArr[nodeId] = arr;
|
||||
} else {
|
||||
const p = nodeArr[parentId];
|
||||
let parentType = nodeObj[parentId].type;
|
||||
|
||||
if ((parentType == 5 || parentType == 8) && !noBranch) {
|
||||
nodeArr[nodeId] = [];
|
||||
} else {
|
||||
const arr1 = other.deepClone(p);
|
||||
arr1.push(nodeId);
|
||||
nodeArr[nodeId] = arr1;
|
||||
}
|
||||
}
|
||||
|
||||
const type = process.type;
|
||||
const children = process.childNode;
|
||||
|
||||
if (type === 5 || type === 8 || type === 4) {
|
||||
const branchs = process.conditionNodes;
|
||||
for (const item of branchs) {
|
||||
produceSerialNodeList(nodeId, item, nodeArr, nodeObj, false);
|
||||
}
|
||||
|
||||
if (!validateNull(children?.id)) {
|
||||
produceSerialNodeList(nodeId, children, nodeArr, nodeObj, true);
|
||||
}
|
||||
} else {
|
||||
if (!validateNull(children?.id)) {
|
||||
produceSerialNodeList(nodeId, children, nodeArr, nodeObj, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const step2FormList = computed(() => {
|
||||
return flowStore.step2;
|
||||
});
|
||||
|
||||
const step2FormUserList = computed(() => {
|
||||
return step2FormList.value.filter((res) => res.type === 'OrgSelector' && res.props?.type === 'user');
|
||||
});
|
||||
|
||||
const openEvent = () => {
|
||||
let value = step2FormList.value;
|
||||
let formPerms = approverConfig.value.formPerms || {};
|
||||
|
||||
// 初始化默认权限
|
||||
for (const item of value) {
|
||||
if (!formPerms[item.field]) {
|
||||
formPerms[item.field] = 'R';
|
||||
}
|
||||
}
|
||||
|
||||
approverConfig.value.formPerms = formPerms;
|
||||
|
||||
// 处理表单项权限
|
||||
const processedFormItems = processFormPermissions();
|
||||
// 这里可以进一步使用处理后的表单项
|
||||
};
|
||||
|
||||
let approverConfig = ref({});
|
||||
|
||||
let store = useStore();
|
||||
let { setApproverConfig, setApprover } = store;
|
||||
let approverConfigData = computed(() => store.approverConfigData);
|
||||
let approverDrawer = computed(() => store.approverDrawer);
|
||||
let visible = computed({
|
||||
get() {
|
||||
return approverDrawer.value;
|
||||
},
|
||||
set() {
|
||||
closeDrawer();
|
||||
},
|
||||
});
|
||||
watch(approverConfigData, (val) => {
|
||||
approverConfig.value = val.value;
|
||||
});
|
||||
//用户表单是否是多选
|
||||
const isMultiUserForm = (id) => {
|
||||
if (!id) {
|
||||
return false;
|
||||
}
|
||||
return step2FormUserList.value.filter((res) => res.id === id)[0]?.props.multiple;
|
||||
};
|
||||
//监听用户选择表单值变化
|
||||
watch(
|
||||
() => approverConfig.value.formUserId,
|
||||
(val) => {
|
||||
if (val) {
|
||||
approverConfig.value.formUserName = step2FormUserList.value.filter((res) => res.field === val)[0].field;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
//审批人类型变化
|
||||
const assignedTypeChangeEvent = () => {
|
||||
approverConfig.value.nodeUserList = [];
|
||||
};
|
||||
|
||||
const saveApprover = () => {
|
||||
approverConfig.value.error = !checkApproval(approverConfig.value);
|
||||
setApproverConfig({
|
||||
value: approverConfig.value,
|
||||
flag: true,
|
||||
id: approverConfigData.value.id,
|
||||
});
|
||||
closeDrawer();
|
||||
};
|
||||
const closeDrawer = () => {
|
||||
setApprover(false);
|
||||
};
|
||||
|
||||
const checkApproval = (nodeConfig) => {
|
||||
if (nodeConfig.assignedType == 1 || nodeConfig.assignedType == 3) {
|
||||
//指定成员
|
||||
if (nodeConfig.nodeUserList.length == 0) {
|
||||
return false;
|
||||
}
|
||||
} else if (nodeConfig.assignedType == 8 && nodeConfig.formUserId.length == 0) {
|
||||
//表单
|
||||
return false;
|
||||
}
|
||||
|
||||
//审批人为空
|
||||
if (nodeConfig?.nobody?.handler === 'TO_USER' && nodeConfig.nobody.assignedUser.length === 0) {
|
||||
return false;
|
||||
}
|
||||
//操作权限
|
||||
let operList = nodeConfig.operList;
|
||||
let length = operList?.filter((res) => res.checked).length;
|
||||
if (length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
</script>
|
||||
<style scoped></style>
|
||||
@@ -0,0 +1,238 @@
|
||||
<script setup lang="ts">
|
||||
import { useFlowStore } from '../../../stores/flow';
|
||||
import { getCurrentInstance, computed, ref, watch } from 'vue';
|
||||
import selectShow from '/@/components/OrgSelector/index.vue';
|
||||
import other from '/@/utils/other';
|
||||
import { PropType } from 'vue';
|
||||
|
||||
// 表单项接口定义
|
||||
interface FormItem {
|
||||
id: string;
|
||||
field: string;
|
||||
name: string;
|
||||
type: string;
|
||||
props?: Record<string, any>;
|
||||
value?: any;
|
||||
title?: string;
|
||||
typeName?: string;
|
||||
}
|
||||
|
||||
// 表达式类型接口定义
|
||||
interface ExpressionType {
|
||||
key: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
// 表达式映射接口定义
|
||||
interface ExpressionMap {
|
||||
OrgSelector: ExpressionType[];
|
||||
input: ExpressionType[];
|
||||
textarea: ExpressionType[];
|
||||
textArea: ExpressionType[]; // 添加这个字段以匹配接口要求
|
||||
inputNumber: ExpressionType[];
|
||||
datePicker: ExpressionType[];
|
||||
[key: string]: ExpressionType[];
|
||||
}
|
||||
|
||||
let flowStore = useFlowStore();
|
||||
|
||||
// 获取步骤2的表单列表
|
||||
const step2FormList = computed(() => {
|
||||
return flowStore.step2 as FormItem[];
|
||||
});
|
||||
|
||||
// 定义允许参与计算的表单类型
|
||||
const allowedTypes = ['OrgSelector', 'input', 'textarea', 'inputNumber', 'datePicker'];
|
||||
|
||||
// 过滤并处理表单列表
|
||||
const formList = computed(() => {
|
||||
const value = step2FormList.value;
|
||||
const $deepCopy = other.deepClone(value.filter((res: FormItem) => allowedTypes.includes(res.type)));
|
||||
|
||||
// 添加发起人选项
|
||||
$deepCopy.push({
|
||||
id: 'root',
|
||||
field: 'root',
|
||||
name: '发起人',
|
||||
typeName: '用户',
|
||||
type: 'OrgSelector',
|
||||
props: { type: 'user' },
|
||||
value: [],
|
||||
});
|
||||
|
||||
return $deepCopy as FormItem[];
|
||||
});
|
||||
|
||||
// 条件接口定义
|
||||
interface Condition {
|
||||
key: string;
|
||||
value: any;
|
||||
keyType?: string;
|
||||
expression?: string;
|
||||
}
|
||||
|
||||
// 组件属性定义
|
||||
let props = defineProps({
|
||||
condition: {
|
||||
type: Object as PropType<Condition>,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
// 表单项映射对象
|
||||
const formIdObj = computed(() => {
|
||||
const obj: Record<string, FormItem> = {};
|
||||
formList.value.forEach((item: FormItem) => {
|
||||
obj[item.field] = item;
|
||||
});
|
||||
return obj;
|
||||
});
|
||||
|
||||
// 条件选择值的计算属性
|
||||
const conditionSelectVal = computed({
|
||||
get() {
|
||||
let value = props.condition.value;
|
||||
return value && value.length > 0 ? value.map((res: { key: string }) => res.key) : undefined;
|
||||
},
|
||||
set(t: string[]) {
|
||||
let options = formList.value.find((item) => item.field === props.condition.key)?.props?.options || [];
|
||||
let filterElement = options.filter((res: { key: string }) => t.indexOf(res.key) >= 0);
|
||||
props.condition.value = filterElement;
|
||||
},
|
||||
});
|
||||
|
||||
// 表达式配置
|
||||
let expression = ref<ExpressionMap>({
|
||||
OrgSelector: [
|
||||
{ key: 'in', name: '属于' },
|
||||
{ key: 'notin', name: '不属于' },
|
||||
],
|
||||
input: [
|
||||
{ key: '==', name: '等于' },
|
||||
{ key: '!=', name: '不等于' },
|
||||
{ key: 'contain', name: '包含' },
|
||||
{ key: 'notcontain', name: '不包含' },
|
||||
],
|
||||
textarea: [
|
||||
{ key: '==', name: '等于' },
|
||||
{ key: '!=', name: '不等于' },
|
||||
{ key: 'contain', name: '包含' },
|
||||
{ key: 'notcontain', name: '不包含' },
|
||||
],
|
||||
textArea: [
|
||||
// 添加这个字段以匹配接口要求
|
||||
{ key: '==', name: '等于' },
|
||||
{ key: '!=', name: '不等于' },
|
||||
{ key: 'contain', name: '包含' },
|
||||
{ key: 'notcontain', name: '不包含' },
|
||||
],
|
||||
inputNumber: [
|
||||
{ key: '==', name: '等于' },
|
||||
{ key: '!=', name: '不等于' },
|
||||
{ key: '>', name: '大于' },
|
||||
{ key: '>=', name: '大于等于' },
|
||||
{ key: '<', name: '小于' },
|
||||
{ key: '<=', name: '小于等于' },
|
||||
],
|
||||
datePicker: [
|
||||
{ key: '==', name: '等于' },
|
||||
{ key: '!=', name: '不等于' },
|
||||
{ key: '>', name: '大于' },
|
||||
{ key: '>=', name: '大于等于' },
|
||||
{ key: '<', name: '小于' },
|
||||
{ key: '<=', name: '小于等于' },
|
||||
],
|
||||
});
|
||||
|
||||
// 监听条件键值变化,确保OrgSelector的值始终为数组
|
||||
watch(
|
||||
() => props.condition.key,
|
||||
(newKey) => {
|
||||
if (formIdObj.value[newKey]?.type === 'OrgSelector') {
|
||||
if (!props.condition.value || !Array.isArray(props.condition.value)) {
|
||||
props.condition.value = [];
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<el-select v-model="condition.key" placeholder="选择表单" style="width: 100%">
|
||||
<el-option v-for="f in formList" :key="f.field" :label="f.title || f.name" :value="f.field" />
|
||||
</el-select>
|
||||
|
||||
<el-select v-model="condition.expression" placeholder="选择关系" style="width: 100%; margin-top: 20px">
|
||||
<el-option
|
||||
v-for="f in formIdObj[condition.key]?.type ? expression[formIdObj[condition.key].type] : []"
|
||||
:key="f.key"
|
||||
:label="f.name"
|
||||
:value="f.key"
|
||||
/>
|
||||
</el-select>
|
||||
|
||||
<el-input
|
||||
v-model="condition.value"
|
||||
v-if="formIdObj[condition.key]?.type === 'input' || formIdObj[condition.key]?.type === 'textarea'"
|
||||
style="margin-top: 20px"
|
||||
placeholder="条件值"
|
||||
></el-input>
|
||||
|
||||
<el-input-number
|
||||
v-model="condition.value"
|
||||
v-if="formIdObj[condition.key]?.type === 'Money' || formIdObj[condition.key]?.type === 'inputNumber'"
|
||||
placeholder="条件值"
|
||||
style="width: 100%; margin-top: 20px"
|
||||
controls-position="right"
|
||||
/>
|
||||
|
||||
<el-date-picker
|
||||
value-format="YYYY-MM-DD"
|
||||
type="date"
|
||||
class="formDate"
|
||||
v-model="condition.value"
|
||||
v-if="formIdObj[condition.key]?.type === 'datePicker'"
|
||||
placeholder="条件值"
|
||||
style="width: 100%; margin-top: 20px"
|
||||
/>
|
||||
|
||||
<el-select
|
||||
v-model="conditionSelectVal"
|
||||
v-if="formIdObj[condition.key]?.type === 'SingleSelect'"
|
||||
style="width: 100%; margin-top: 20px"
|
||||
multiple
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
placeholder="请选择值"
|
||||
>
|
||||
<el-option v-for="item in formIdObj[condition.key]?.props?.options || []" :key="item.key" :label="item.value" :value="item.key" />
|
||||
</el-select>
|
||||
<div style="margin-top: 20px">
|
||||
<select-show
|
||||
v-if="formIdObj[condition.key]?.type === 'OrgSelector' && formIdObj[condition.key]?.props?.type === 'user'"
|
||||
v-model:orgList="condition.value"
|
||||
type="user"
|
||||
:multiple="true"
|
||||
></select-show>
|
||||
</div>
|
||||
<div style="margin-top: 20px">
|
||||
<select-show
|
||||
v-if="formIdObj[condition.key]?.type === 'OrgSelector' && formIdObj[condition.key]?.props?.type === 'dept'"
|
||||
v-model:orgList="condition.value"
|
||||
type="dept"
|
||||
:multiple="true"
|
||||
></select-show>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.formDate div.el-input__wrapper) {
|
||||
width: 100% !important;
|
||||
}
|
||||
:deep(.formDate) {
|
||||
width: 100% !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
},
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="mb-4">节点任务完成推送接口:</div>
|
||||
<el-input type="textarea" show-word-limit maxlength="200" rows="5" :model-value="props.modelValue"
|
||||
@input="emit('update:modelValue',$event)"
|
||||
placeholder="此节点任务完成后,会往目标接口推送事件通知"/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,101 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useFlowStore } from '/@/views/flow/workflow/stores/flow';
|
||||
|
||||
const props = defineProps({
|
||||
formPerm: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
hideKey: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
interface FormItem {
|
||||
field: string;
|
||||
title?: string;
|
||||
required?: boolean;
|
||||
children?: FormItem[];
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
let flowStore = useFlowStore();
|
||||
|
||||
function flattenFormItems(items: FormItem[]): FormItem[] {
|
||||
const flattened: FormItem[] = [];
|
||||
|
||||
items.forEach((item) => {
|
||||
if (item.children) {
|
||||
flattened.push(...flattenFormItems(item.children));
|
||||
} else if (item.field && item.title) {
|
||||
flattened.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
return flattened;
|
||||
}
|
||||
|
||||
const step2FormList = computed(() => {
|
||||
const step2 = flowStore.step2;
|
||||
return flattenFormItems(step2);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div style="display: flex; flex-direction: row; background-color: var(--el-fill-color-light)" effect="dark">
|
||||
<div class="f1">表单字段</div>
|
||||
<div class="f2">只读</div>
|
||||
<div class="f3">编辑</div>
|
||||
<div class="f4">隐藏</div>
|
||||
</div>
|
||||
|
||||
<div v-if="step2FormList.length == 0">
|
||||
<el-empty description="暂无表单" />
|
||||
</div>
|
||||
<div v-for="item in step2FormList" :key="item.name">
|
||||
<div style="display: flex; flex-direction: row">
|
||||
<div class="f1">
|
||||
<span>{{ item.title }}123</span>
|
||||
<span v-if="item.required" style="color: #c75450"> * </span>
|
||||
</div>
|
||||
|
||||
<el-radio-group v-model="formPerm[item.field]" size="large">
|
||||
<div class="f2">
|
||||
<el-radio size="large" label="R"><span></span></el-radio>
|
||||
</div>
|
||||
<div class="f3">
|
||||
<el-radio :disabled="!(hideKey.length == 0 || hideKey.indexOf('E') < 0)" size="large" label="E"><span></span> </el-radio>
|
||||
</div>
|
||||
<div class="f4">
|
||||
<el-radio size="large" label="H"><span></span></el-radio>
|
||||
</div>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.f1 {
|
||||
width: calc(100% - 80px - 80px - 80px);
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.f2 {
|
||||
width: 80px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.f3 {
|
||||
width: 80px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.f4 {
|
||||
width: 80px;
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
181
src/views/flow/workflow/components/drawer/conditionDrawer.vue
Normal file
@@ -0,0 +1,181 @@
|
||||
<template>
|
||||
<el-drawer
|
||||
:append-to-body="true"
|
||||
title="条件设置"
|
||||
v-model="visible"
|
||||
:show-close="false"
|
||||
:size="550"
|
||||
@open="openEvent"
|
||||
:before-close="saveCondition"
|
||||
>
|
||||
<template #header="{ titleId, titleClass }">
|
||||
<h3 :id="titleId" :class="titleClass">条件设置</h3>
|
||||
</template>
|
||||
<el-form label-width="120px">
|
||||
<el-form-item label="条件组关系">
|
||||
<el-switch v-model="conditionConfig.groupMode" active-text="且" inactive-text="或" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-card class="box-card" v-for="(item, index) in conditionConfig.conditionList" :key="index" style="margin-bottom: 20px">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>条件组{{ index + 1 }}</span>
|
||||
<el-switch v-model="item.mode" active-text="且" inactive-text="或" />
|
||||
|
||||
<el-button text v-if="conditionConfig.conditionList.length > 1" @click="deleteGroup(index)" icon="Delete"></el-button>
|
||||
</div>
|
||||
</template>
|
||||
<div v-for="(item1, index1) in item.conditionList" :key="index1">
|
||||
<div style="display: flex; flex-direction: row; justify-content: space-between">
|
||||
<div>
|
||||
{{ index1 == 0 ? '当' : item.mode ? '且' : '或' }}
|
||||
</div>
|
||||
<div>
|
||||
<el-button text @click="deleteCondition(index, index1)" v-if="item.conditionList.length > 1" icon="Delete"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
<condition :condition="item1"></condition>
|
||||
</div>
|
||||
<el-button dark type="success" style="margin-top: 20px" @click="addOneCondition(item, index)">添加条件</el-button>
|
||||
</el-card>
|
||||
<el-button dark type="primary" @click="addOneConditionGroup">添加条件组</el-button>
|
||||
</el-drawer>
|
||||
</template>
|
||||
<script setup>
|
||||
import {computed, getCurrentInstance, ref, watch} from 'vue';
|
||||
import $func from '../../utils/index';
|
||||
import {useStore} from '../../stores/index';
|
||||
import Condition from './components/condition.vue';
|
||||
import {useFlowStore} from '../../stores/flow';
|
||||
|
||||
let conditionsConfig = ref({
|
||||
conditionNodes: [],
|
||||
});
|
||||
let conditionConfig = ref({});
|
||||
let PriorityLevel = ref('');
|
||||
|
||||
let store = useStore();
|
||||
let { setCondition, setConditionsConfig } = store;
|
||||
let conditionsConfig1 = computed(() => store.conditionsConfig1);
|
||||
let conditionDrawer = computed(() => store.conditionDrawer);
|
||||
let visible = computed({
|
||||
get() {
|
||||
return conditionDrawer.value;
|
||||
},
|
||||
set() {
|
||||
closeDrawer();
|
||||
},
|
||||
});
|
||||
//删除条件组
|
||||
const deleteGroup = (index) => {
|
||||
conditionConfig.value?.conditionList.splice(index, 1);
|
||||
};
|
||||
//刪除单个条件
|
||||
const deleteCondition = (index, index1) => {
|
||||
conditionConfig.value?.conditionList[index].conditionList.splice(index1, 1);
|
||||
};
|
||||
|
||||
//添加一个条件组
|
||||
const addOneConditionGroup = () => {
|
||||
conditionConfig.value?.conditionList.push({
|
||||
mode: true,
|
||||
conditionList: [{}],
|
||||
});
|
||||
};
|
||||
//添加组内一个条件
|
||||
const addOneCondition = (item, index) => {
|
||||
let conditionList = item.conditionList;
|
||||
if (!conditionList) {
|
||||
conditionList = [];
|
||||
}
|
||||
conditionList.push({});
|
||||
item.conditionList = conditionList;
|
||||
};
|
||||
|
||||
let flowStore = useFlowStore();
|
||||
|
||||
const step2FormList = computed(() => {
|
||||
return flowStore.step2;
|
||||
});
|
||||
|
||||
const openEvent = () => {};
|
||||
|
||||
watch(conditionsConfig1, (val) => {
|
||||
let conditionNodes = val.value.conditionNodes;
|
||||
|
||||
for (var item of conditionNodes) {
|
||||
let groupList = item.conditionList;
|
||||
for (var group of groupList) {
|
||||
let conditionList = group.conditionList;
|
||||
for (var con of conditionList) {
|
||||
let key = con.key;
|
||||
if (key === 'root') {
|
||||
con.keyType = 'SelectUser';
|
||||
} else {
|
||||
let ele = step2FormList.value.filter((res) => res.field === key);
|
||||
if (ele.length > 0 && ele[0]?.props?.type === 'user') {
|
||||
con.keyType = 'SelectUser';
|
||||
}else if (ele.length > 0 && ele[0]?.props?.type === 'dept') {
|
||||
con.keyType = 'SelectDept';
|
||||
} else if (ele.length > 0) {
|
||||
con.keyType = ele[0].type;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
conditionsConfig.value = val.value;
|
||||
PriorityLevel.value = val.priorityLevel;
|
||||
conditionConfig.value = val.priorityLevel ? conditionsConfig.value.conditionNodes[val.priorityLevel - 1] : { nodeUserList: [], conditionList: [] };
|
||||
});
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const saveCondition = () => {
|
||||
closeDrawer();
|
||||
var a = conditionsConfig.value.conditionNodes.splice(PriorityLevel.value - 1, 1); //截取旧下标
|
||||
conditionsConfig.value.conditionNodes.splice(conditionConfig.value.priorityLevel - 1, 0, a[0]); //填充新下标
|
||||
conditionsConfig.value.conditionNodes.map((item, index) => {
|
||||
item.priorityLevel = index + 1;
|
||||
});
|
||||
|
||||
for (var i = 0; i < conditionsConfig.value.conditionNodes.length; i++) {
|
||||
let conditionNode = conditionsConfig.value.conditionNodes[i];
|
||||
|
||||
conditionNode.error = false;
|
||||
let conditionList = conditionNode.conditionList;
|
||||
if (i != conditionsConfig.value.conditionNodes.length - 1) {
|
||||
var error = conditionList.length == 0;
|
||||
|
||||
for (var it of conditionList) {
|
||||
error = it.conditionList.length == 0;
|
||||
for (var ite of it.conditionList) {
|
||||
if (!ite.key || !ite.expression || !ite.value) {
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
conditionNode.error = error;
|
||||
}
|
||||
|
||||
conditionNode.placeHolder = $func.conditionStr(conditionsConfig.value, i);
|
||||
}
|
||||
setConditionsConfig({
|
||||
value: conditionsConfig.value,
|
||||
flag: true,
|
||||
id: conditionsConfig1.value.id,
|
||||
});
|
||||
};
|
||||
|
||||
const closeDrawer = (val) => {
|
||||
setCondition(false);
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
83
src/views/flow/workflow/components/drawer/copyerDrawer.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<el-drawer
|
||||
:append-to-body="true"
|
||||
title="抄送人设置"
|
||||
v-model="visible"
|
||||
@open="openEvent"
|
||||
class="set_copyer"
|
||||
:show-close="false"
|
||||
:size="550"
|
||||
:before-close="saveCopyer"
|
||||
>
|
||||
<el-tabs type="border-card">
|
||||
<el-tab-pane label="设置抄送人">
|
||||
<select-show v-model:orgList="copyerConfig.nodeUserList" type="user" :multiple="true"></select-show>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="表单权限">
|
||||
<form-perm :hide-key="['E']" :form-perm="copyerConfig.formPerms"></form-perm>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-drawer>
|
||||
</template>
|
||||
<script setup>
|
||||
import selectShow from '/@/components/OrgSelector/index.vue';
|
||||
|
||||
import $func from '../../utils/index';
|
||||
import {useStore} from '../../stores/index';
|
||||
import {computed, ref, watch} from 'vue';
|
||||
import {useFlowStore} from '../../stores/flow';
|
||||
import FormPerm from './components/formPerm.vue';
|
||||
|
||||
let copyerConfig = ref({});
|
||||
|
||||
let flowStore = useFlowStore();
|
||||
|
||||
const step2FormList = computed(() => {
|
||||
return flowStore.step2;
|
||||
});
|
||||
|
||||
let store = useStore();
|
||||
let { setCopyerConfig, setCopyer } = store;
|
||||
let copyerDrawer = computed(() => store.copyerDrawer);
|
||||
let copyerConfig1 = computed(() => store.copyerConfig1);
|
||||
let visible = computed({
|
||||
get() {
|
||||
return copyerDrawer.value;
|
||||
},
|
||||
set() {
|
||||
closeDrawer();
|
||||
},
|
||||
});
|
||||
watch(copyerConfig1, (val) => {
|
||||
copyerConfig.value = val.value;
|
||||
});
|
||||
|
||||
const openEvent = () => {
|
||||
let value = step2FormList.value;
|
||||
var arr = {};
|
||||
let formPerms = copyerConfig.value.formPerms;
|
||||
|
||||
for (var item of value) {
|
||||
arr[item.field] = 'R';
|
||||
if (formPerms[item.field]) {
|
||||
arr[item.field] = formPerms[item.field];
|
||||
}
|
||||
}
|
||||
copyerConfig.value.formPerms = arr;
|
||||
};
|
||||
|
||||
const saveCopyer = () => {
|
||||
copyerConfig.value.error = !$func.copyerStr(copyerConfig.value);
|
||||
setCopyerConfig({
|
||||
value: copyerConfig.value,
|
||||
flag: true,
|
||||
id: copyerConfig1.value.id,
|
||||
});
|
||||
closeDrawer();
|
||||
};
|
||||
const closeDrawer = () => {
|
||||
setCopyer(false);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
88
src/views/flow/workflow/components/drawer/promoterDrawer.vue
Normal file
@@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<el-drawer
|
||||
:append-to-body="true"
|
||||
:title="$t('flow.initiator')"
|
||||
v-model="visible"
|
||||
class="set_promoter"
|
||||
:show-close="false"
|
||||
@open="openEvent"
|
||||
:size="550"
|
||||
:before-close="savePromoter"
|
||||
>
|
||||
<div class="demo-drawer__content">
|
||||
<el-tabs type="border-card">
|
||||
<el-tab-pane label="设置发起人">
|
||||
<select-show v-model:orgList="starterConfig.nodeUserList" type="org" :multiple="true"></select-show>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="表单权限">
|
||||
<form-perm :form-perm="starterConfig.formPerms"></form-perm>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
<script setup>
|
||||
import selectShow from '/@/components/OrgSelector/index.vue';
|
||||
import {useFlowStore} from '../../stores/flow';
|
||||
import {useStore} from '../../stores/index';
|
||||
import FormPerm from './components/formPerm.vue';
|
||||
|
||||
let store = useStore();
|
||||
|
||||
let starterConfig = ref({});
|
||||
let flowStore = useFlowStore();
|
||||
|
||||
let starterConfigData = computed(() => store.starterConfigData);
|
||||
watch(starterConfigData, (val) => {
|
||||
starterConfig.value = val.value;
|
||||
});
|
||||
const step2FormList = computed(() => {
|
||||
return flowStore.step2;
|
||||
});
|
||||
|
||||
const openEvent = () => {
|
||||
let value = step2FormList.value;
|
||||
var arr = {};
|
||||
let formPerms = starterConfig.value.formPerms;
|
||||
for (var item of value) {
|
||||
arr[item.field] = 'E';
|
||||
|
||||
if (formPerms[item.field]) {
|
||||
arr[item.field] = formPerms[item.field];
|
||||
}
|
||||
if (item.type === 'Layout') {
|
||||
let value1 = item.props.value;
|
||||
for (var it of value1) {
|
||||
arr[it.field] = 'E';
|
||||
if (formPerms[it.field]) {
|
||||
arr[it.field] = formPerms[it.field];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
starterConfig.value.formPerms = arr;
|
||||
};
|
||||
|
||||
let { setPromoter, setStarterConfig } = store;
|
||||
let promoterDrawer = computed(() => store.promoterDrawer);
|
||||
let visible = computed({
|
||||
get() {
|
||||
return promoterDrawer.value;
|
||||
},
|
||||
set() {
|
||||
closeDrawer();
|
||||
},
|
||||
});
|
||||
|
||||
const savePromoter = () => {
|
||||
setStarterConfig({
|
||||
value: starterConfig.value,
|
||||
flag: true,
|
||||
id: starterConfigData.value.id,
|
||||
});
|
||||
closeDrawer();
|
||||
};
|
||||
const closeDrawer = () => {
|
||||
setPromoter(false);
|
||||
};
|
||||
</script>
|
||||
131
src/views/flow/workflow/components/node/approval.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
let isInput = ref(false);
|
||||
|
||||
let props = defineProps({
|
||||
nodeConfig: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
const blurEvent = (index) => {
|
||||
if (index || index === 0) {
|
||||
isInputList.value[index] = false;
|
||||
props.nodeConfig.conditionNodes[index].nodeName = props.nodeConfig.conditionNodes[index].nodeName || '条件';
|
||||
} else {
|
||||
isInput.value = false;
|
||||
props.nodeConfig.nodeName = props.nodeConfig.nodeName || defaultText;
|
||||
}
|
||||
};
|
||||
import { bgColors, placeholderList } from '/@/views/flow/workflow/utils/const';
|
||||
import { computed } from 'vue';
|
||||
import addNode from '../addNode.vue';
|
||||
|
||||
import $func from '/@/views/flow/workflow/utils';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
let defaultText = computed(() => {
|
||||
return placeholderList[props.nodeConfig.type];
|
||||
});
|
||||
|
||||
const { t } = useI18n;
|
||||
var placeHolder = computed(() => {
|
||||
if (props.nodeConfig.type == 0) {
|
||||
return $func.arrToStr(props.nodeConfig.nodeUserList) || t('flow.allUser');
|
||||
}
|
||||
if (props.nodeConfig.type == 1) {
|
||||
return $func.setApproverStr(props.nodeConfig);
|
||||
}
|
||||
if (props.nodeConfig.type == 2) {
|
||||
return $func.copyerStr(props.nodeConfig);
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
const clickEvent = (index) => {
|
||||
if (index || index === 0) {
|
||||
isInputList.value[index] = true;
|
||||
} else {
|
||||
isInput.value = true;
|
||||
}
|
||||
};
|
||||
let isInputList = ref([]);
|
||||
|
||||
const delNode = () => {
|
||||
emits('update:nodeConfig', props.nodeConfig.childNode);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="node-wrap">
|
||||
<div class="node-wrap-box" :class="(nodeConfig.type == 0 ? 'start-node ' : '') + (nodeConfig.error ? 'active error' : '')">
|
||||
<div class="title" :style="`background: rgb(${bgColors[nodeConfig.type]});`">
|
||||
<span v-if="nodeConfig.type == 0">{{ nodeConfig.nodeName }}</span>
|
||||
<template v-else>
|
||||
<span class="iconfont">{{ '' }}</span>
|
||||
<input
|
||||
v-if="isInput"
|
||||
type="text"
|
||||
class="ant-input editable-title-input"
|
||||
@blur="blurEvent()"
|
||||
@focus="$event.currentTarget.select()"
|
||||
v-focus
|
||||
v-model="nodeConfig.nodeName"
|
||||
:placeholder="defaultText"
|
||||
/>
|
||||
<span v-else class="editable-title" @click="clickEvent()">{{ nodeConfig.nodeName }}</span>
|
||||
<i class="anticon anticon-close close" @click="delNode"></i>
|
||||
</template>
|
||||
</div>
|
||||
<div class="content" @click="openConfigDrawer">
|
||||
<div class="text">
|
||||
{{ placeHolder?.length > 0 ? placeHolder : '请选择' + defaultText }}
|
||||
</div>
|
||||
<i class="anticon anticon-right arrow"></i>
|
||||
</div>
|
||||
|
||||
<div class="error_tip" v-if="nodeConfig.error">
|
||||
<i class="anticon anticon-exclamation-circle"></i>
|
||||
</div>
|
||||
</div>
|
||||
<addNode :current-node="nodeConfig" v-model:childNodeP="nodeConfig.childNode" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@import '/@/views/flow/workflow/css/workflow.css';
|
||||
|
||||
.error_tip {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
transform: translate(150%, 0px);
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.promoter_person .el-dialog__body {
|
||||
padding: 10px 20px 14px 20px;
|
||||
}
|
||||
|
||||
.selected_list {
|
||||
margin-bottom: 20px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.selected_list span {
|
||||
margin-right: 10px;
|
||||
padding: 3px 6px 3px 9px;
|
||||
line-height: 12px;
|
||||
white-space: nowrap;
|
||||
border-radius: 2px;
|
||||
border: 1px solid rgba(220, 220, 220, 1);
|
||||
}
|
||||
|
||||
.selected_list img {
|
||||
margin-left: 5px;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
481
src/views/flow/workflow/components/nodeWrap.vue
Normal file
@@ -0,0 +1,481 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- <approval :node-config="nodeConfig" v-if="nodeConfig.type==1"></approval>-->
|
||||
<div class="node-wrap" v-if="nodeConfig.type < 3">
|
||||
<div class="node-wrap-box" :class="(nodeConfig.type == 0 ? 'start-node ' : '') + (nodeConfig.error ? 'active error' : '')">
|
||||
<div class="title" :style="`background: rgb(${bgColors[nodeConfig.type]});`">
|
||||
<span v-if="nodeConfig.type == 0">{{ nodeConfig.nodeName }}</span>
|
||||
<template v-else>
|
||||
<span class="iconfont">{{ nodeConfig.type == 1 ? '' : '' }}</span>
|
||||
<input
|
||||
v-if="isInput"
|
||||
type="text"
|
||||
class="ant-input editable-title-input"
|
||||
@blur="blurEvent()"
|
||||
@focus="$event.currentTarget.select()"
|
||||
v-focus
|
||||
v-model="nodeConfig.nodeName"
|
||||
:placeholder="defaultText"
|
||||
/>
|
||||
<span v-else class="editable-title" @click="clickEvent()">{{ nodeConfig.nodeName }}</span>
|
||||
<i class="anticon anticon-close close" @click="delNode"></i>
|
||||
</template>
|
||||
</div>
|
||||
<div class="content" @click="openConfigDrawer">
|
||||
<div class="text">
|
||||
{{ placeHolder?.length > 0 ? placeHolder : '请选择' + defaultText }}
|
||||
</div>
|
||||
<i class="anticon anticon-right arrow"></i>
|
||||
</div>
|
||||
|
||||
<div class="error_tip" v-if="nodeConfig.error">
|
||||
<i class="anticon anticon-exclamation-circle"></i>
|
||||
</div>
|
||||
</div>
|
||||
<addNode :current-node="nodeConfig" v-model:childNodeP="nodeConfig.childNode" />
|
||||
</div>
|
||||
<div class="branch-wrap" v-if="nodeConfig.type == 4">
|
||||
<div class="branch-box-wrap">
|
||||
<div class="branch-box">
|
||||
<button class="add-branch" @click="addTerm">添加条件</button>
|
||||
<div class="col-box" v-for="(item, index) in nodeConfig.conditionNodes" :key="index">
|
||||
<div class="condition-node">
|
||||
<div class="condition-node-box">
|
||||
<div class="auto-judge" :class="item.error ? 'error active' : ''">
|
||||
<div class="sort-left" v-if="index != 0" @click="arrTransfer(index, -1)"><</div>
|
||||
<div class="title-wrapper">
|
||||
<input
|
||||
style="width: 50%"
|
||||
v-if="isInputList[index]"
|
||||
type="text"
|
||||
class="ant-input editable-title-input"
|
||||
@blur="blurEvent(index)"
|
||||
@focus="$event.currentTarget.select()"
|
||||
v-focus
|
||||
v-model="item.nodeName"
|
||||
/>
|
||||
<span v-else class="editable-title" @click="clickEvent(index)">{{ item.nodeName }}</span>
|
||||
<span class="priority-title">优先级{{ item.priorityLevel }}</span>
|
||||
<i class="anticon anticon-close close" @click="delTerm(index)"></i>
|
||||
</div>
|
||||
<div class="sort-right" v-if="index != nodeConfig.conditionNodes.length - 1" @click="arrTransfer(index)">></div>
|
||||
<div class="content" v-if="index < nodeConfig.conditionNodes.length - 1" @click="openConfigDrawer(item.priorityLevel)">
|
||||
{{ $func.conditionStr(nodeConfig, index) }}
|
||||
</div>
|
||||
<div class="content" v-else>{{ $func.conditionStr(nodeConfig, index) }}</div>
|
||||
<div class="error_tip" v-if="item.error">
|
||||
<i class="anticon anticon-exclamation-circle"></i>
|
||||
</div>
|
||||
</div>
|
||||
<addNode v-model:childNodeP="item.childNode" :current-node="item" />
|
||||
</div>
|
||||
</div>
|
||||
<nodeWrap v-if="item.childNode" v-model:nodeConfig="item.childNode" />
|
||||
<template v-if="index == 0">
|
||||
<div class="top-left-cover-line"></div>
|
||||
<div class="bottom-left-cover-line"></div>
|
||||
</template>
|
||||
<template v-if="index == nodeConfig.conditionNodes.length - 1">
|
||||
<div class="top-right-cover-line"></div>
|
||||
<div class="bottom-right-cover-line"></div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<addNode v-model:childNodeP="nodeConfig.childNode" :current-node="nodeConfig" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="branch-wrap" v-if="nodeConfig.type == 5">
|
||||
<div class="branch-box-wrap">
|
||||
<div class="branch-box">
|
||||
<button class="add-branch" @click="addTerm">添加分支</button>
|
||||
<div class="col-box" v-for="(item, index) in nodeConfig.conditionNodes" :key="index">
|
||||
<div class="condition-node">
|
||||
<div class="condition-node-box">
|
||||
<div class="auto-judge" :class="item.error ? 'error active' : ''">
|
||||
<div class="sort-left" v-if="index != 0" @click="arrTransfer(index, -1)"><</div>
|
||||
<div class="title-wrapper">
|
||||
<input
|
||||
style="width: 50%"
|
||||
v-if="isInputList[index]"
|
||||
type="text"
|
||||
class="ant-input editable-title-input"
|
||||
@blur="blurEvent(index)"
|
||||
@focus="$event.currentTarget.select()"
|
||||
v-focus
|
||||
v-model="item.nodeName"
|
||||
/>
|
||||
<span v-else class="editable-title" @click="clickEvent(index)">{{ item.nodeName }}</span>
|
||||
|
||||
<!-- <span class="priority-title" @click="openConfigDrawer(item.priorityLevel)">优先级{{-->
|
||||
<!-- item.priorityLevel-->
|
||||
<!-- }}</span>-->
|
||||
<i class="anticon anticon-close close" @click="delTerm(index)"></i>
|
||||
</div>
|
||||
<div class="sort-right" v-if="index != nodeConfig.conditionNodes.length - 1" @click="arrTransfer(index)">></div>
|
||||
|
||||
<div class="content">{{ item.placeHolder }}</div>
|
||||
<div class="error_tip" v-if="item.error">
|
||||
<i class="anticon anticon-exclamation-circle"></i>
|
||||
</div>
|
||||
</div>
|
||||
<addNode v-model:childNodeP="item.childNode" :current-node="item" />
|
||||
</div>
|
||||
</div>
|
||||
<nodeWrap v-if="item.childNode" v-model:nodeConfig="item.childNode" />
|
||||
<template v-if="index == 0">
|
||||
<div class="top-left-cover-line"></div>
|
||||
<div class="bottom-left-cover-line"></div>
|
||||
</template>
|
||||
<template v-if="index == nodeConfig.conditionNodes.length - 1">
|
||||
<div class="top-right-cover-line"></div>
|
||||
<div class="bottom-right-cover-line"></div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<addNode v-model:childNodeP="nodeConfig.childNode" :current-node="nodeConfig" />
|
||||
</div>
|
||||
</div>
|
||||
<nodeWrap v-if="nodeConfig.childNode" v-model:nodeConfig="nodeConfig.childNode" />
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import addNode from './addNode.vue';
|
||||
import Approval from './node/approval.vue';
|
||||
|
||||
import { onMounted, ref, watch, getCurrentInstance, computed } from 'vue';
|
||||
import $func from '../utils/index';
|
||||
import { useStore } from '../stores/index';
|
||||
import { bgColors, placeholderList } from '../utils/const';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
let _uid = getCurrentInstance().uid;
|
||||
|
||||
let props = defineProps({
|
||||
nodeConfig: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
let defaultText = computed(() => {
|
||||
return placeholderList[props.nodeConfig.type];
|
||||
});
|
||||
|
||||
const {t} = useI18n()
|
||||
var placeHolder = computed(() => {
|
||||
if (props.nodeConfig.type == 0) {
|
||||
return $func.arrToStr(props.nodeConfig.nodeUserList) || t('flow.allUser');
|
||||
}
|
||||
if (props.nodeConfig.type == 1) {
|
||||
return $func.setApproverStr(props.nodeConfig);
|
||||
}
|
||||
if (props.nodeConfig.type == 2) {
|
||||
return $func.copyerStr(props.nodeConfig);
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
watch(placeHolder, (value, oldValue, onCleanup) => {
|
||||
props.nodeConfig.placeHolder = value;
|
||||
});
|
||||
|
||||
import { useFlowStore } from '../stores/flow';
|
||||
import other from '/@/utils/other';
|
||||
|
||||
let flowStore = useFlowStore();
|
||||
|
||||
const step2FormList = computed(() => {
|
||||
let step2 = flowStore.step2;
|
||||
|
||||
return step2;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => step2FormList.value,
|
||||
(val) => {
|
||||
let nodeConfig = props.nodeConfig;
|
||||
|
||||
if (nodeConfig.type == 1) {
|
||||
//审批人
|
||||
|
||||
if (nodeConfig.assignedType == 8) {
|
||||
//表单人员
|
||||
let formUserId = nodeConfig.formUserId;
|
||||
let length = val.filter((res) => res.field === formUserId).length;
|
||||
if (length == 0) {
|
||||
nodeConfig.formUserId = '';
|
||||
nodeConfig.formUserName = '';
|
||||
nodeConfig.error = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nodeConfig.type == 4) {
|
||||
//条件分支
|
||||
var index = 0;
|
||||
var len = nodeConfig.conditionNodes.length;
|
||||
for (var node of nodeConfig.conditionNodes) {
|
||||
if (index >= len - 1) {
|
||||
break;
|
||||
}
|
||||
for (var item1 of node.conditionList) {
|
||||
for (var item2 of item1.conditionList) {
|
||||
let length = val.filter((res) => res.field === item2.key).length;
|
||||
if (length == 0) {
|
||||
item2.key = '';
|
||||
item2.expression = '';
|
||||
item2.keyType = '';
|
||||
item2.value = '';
|
||||
node.error = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
let isInputList = ref([]);
|
||||
let isInput = ref(false);
|
||||
const resetConditionNodesErr = () => {
|
||||
if (props.nodeConfig.type == 5) {
|
||||
return;
|
||||
}
|
||||
for (var i = 0; i < props.nodeConfig.conditionNodes.length; i++) {
|
||||
let conditionNode = props.nodeConfig.conditionNodes[i];
|
||||
|
||||
conditionNode.error = false;
|
||||
let conditionList = conditionNode.conditionList;
|
||||
if (i != props.nodeConfig.conditionNodes.length - 1) {
|
||||
var error = conditionList.length == 0;
|
||||
|
||||
for (var it of conditionList) {
|
||||
error = it.conditionList.length == 0;
|
||||
for (var ite of it.conditionList) {
|
||||
if (!ite.key || !ite.expression || !ite.value) {
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
conditionNode.error = error;
|
||||
}
|
||||
}
|
||||
};
|
||||
onMounted(() => {
|
||||
if (props.nodeConfig.type == 1) {
|
||||
props.nodeConfig.error = !$func.checkApproval(props.nodeConfig);
|
||||
} else if (props.nodeConfig.type == 2) {
|
||||
props.nodeConfig.error = !$func.copyerStr(props.nodeConfig);
|
||||
} else if (props.nodeConfig.type == 4) {
|
||||
resetConditionNodesErr();
|
||||
}
|
||||
});
|
||||
let emits = defineEmits(['update:nodeConfig']);
|
||||
let store = useStore();
|
||||
let { setPromoter, setApprover, setCopyer, setCondition, setStarterConfig, setApproverConfig, setCopyerConfig, setConditionsConfig } = store;
|
||||
let starterConfigData = computed(() => store.starterConfigData);
|
||||
let approverConfigData = computed(() => store.approverConfigData);
|
||||
let copyerConfig1 = computed(() => store.copyerConfig1);
|
||||
let conditionsConfig1 = computed(() => store.conditionsConfig1);
|
||||
|
||||
watch(starterConfigData, (approver) => {
|
||||
if (approver.flag && approver.id === _uid) {
|
||||
emits('update:nodeConfig', approver.value);
|
||||
}
|
||||
});
|
||||
watch(approverConfigData, (approver) => {
|
||||
if (approver.flag && approver.id === _uid) {
|
||||
emits('update:nodeConfig', approver.value);
|
||||
}
|
||||
});
|
||||
watch(copyerConfig1, (copyer) => {
|
||||
if (copyer.flag && copyer.id === _uid) {
|
||||
emits('update:nodeConfig', copyer.value);
|
||||
}
|
||||
});
|
||||
watch(conditionsConfig1, (condition) => {
|
||||
if (condition.flag && condition.id === _uid) {
|
||||
emits('update:nodeConfig', condition.value);
|
||||
}
|
||||
});
|
||||
|
||||
const clickEvent = (index) => {
|
||||
if (index || index === 0) {
|
||||
isInputList.value[index] = true;
|
||||
} else {
|
||||
isInput.value = true;
|
||||
}
|
||||
};
|
||||
const blurEvent = (index) => {
|
||||
if (index || index === 0) {
|
||||
isInputList.value[index] = false;
|
||||
props.nodeConfig.conditionNodes[index].nodeName = props.nodeConfig.conditionNodes[index].nodeName || '条件';
|
||||
} else {
|
||||
isInput.value = false;
|
||||
props.nodeConfig.nodeName = props.nodeConfig.nodeName || defaultText;
|
||||
}
|
||||
};
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const delNode = () => {
|
||||
emits('update:nodeConfig', props.nodeConfig.childNode);
|
||||
};
|
||||
const addTerm = () => {
|
||||
if (props.nodeConfig.type == 5) {
|
||||
let len = props.nodeConfig.conditionNodes.length + 1;
|
||||
props.nodeConfig.conditionNodes.push({
|
||||
nodeName: '分支' + len,
|
||||
type: 3,
|
||||
id: other.getNonDuplicateID(),
|
||||
placeHolder: '满足条件',
|
||||
parentId: props.nodeConfig.id,
|
||||
|
||||
priorityLevel: len,
|
||||
conditionList: [
|
||||
{
|
||||
conditionList: [],
|
||||
},
|
||||
],
|
||||
nodeUserList: [],
|
||||
childNode: null,
|
||||
});
|
||||
emits('update:nodeConfig', props.nodeConfig);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let len = props.nodeConfig.conditionNodes.length + 1;
|
||||
props.nodeConfig.conditionNodes.push({
|
||||
nodeName: '条件' + len,
|
||||
type: 3,
|
||||
parentId: props.nodeConfig.id,
|
||||
id: other.getNonDuplicateID(),
|
||||
priorityLevel: len,
|
||||
conditionList: [
|
||||
{
|
||||
mode: true,
|
||||
conditionList: [
|
||||
{
|
||||
key: '',
|
||||
expression: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
nodeUserList: [],
|
||||
childNode: null,
|
||||
});
|
||||
resetConditionNodesErr();
|
||||
emits('update:nodeConfig', props.nodeConfig);
|
||||
};
|
||||
const delTerm = (index) => {
|
||||
props.nodeConfig.conditionNodes.splice(index, 1);
|
||||
props.nodeConfig.conditionNodes.map((item, index) => {
|
||||
item.priorityLevel = index + 1;
|
||||
item.nodeName = `条件${index + 1}`;
|
||||
});
|
||||
resetConditionNodesErr();
|
||||
emits('update:nodeConfig', props.nodeConfig);
|
||||
if (props.nodeConfig.conditionNodes.length == 1) {
|
||||
if (props.nodeConfig.childNode) {
|
||||
if (props.nodeConfig.conditionNodes[0].childNode) {
|
||||
reData(props.nodeConfig.conditionNodes[0].childNode, props.nodeConfig.childNode);
|
||||
} else {
|
||||
props.nodeConfig.conditionNodes[0].childNode = props.nodeConfig.childNode;
|
||||
}
|
||||
}
|
||||
emits('update:nodeConfig', props.nodeConfig.conditionNodes[0].childNode);
|
||||
}
|
||||
};
|
||||
const reData = (data, addData) => {
|
||||
if (!data.childNode) {
|
||||
data.childNode = addData;
|
||||
} else {
|
||||
reData(data.childNode, addData);
|
||||
}
|
||||
};
|
||||
const openConfigDrawer = (priorityLevel) => {
|
||||
var { type } = props.nodeConfig;
|
||||
if (type == 0) {
|
||||
setPromoter(true);
|
||||
setStarterConfig({
|
||||
value: JSON.parse(JSON.stringify(props.nodeConfig)),
|
||||
|
||||
flag: false,
|
||||
id: _uid,
|
||||
});
|
||||
} else if (type == 1) {
|
||||
setApprover(true);
|
||||
setApproverConfig({
|
||||
value: {
|
||||
...JSON.parse(JSON.stringify(props.nodeConfig)),
|
||||
...{ assignedType: props.nodeConfig.assignedType || 1 },
|
||||
...{ multiple: props.nodeConfig.multiple || false },
|
||||
},
|
||||
flag: false,
|
||||
id: _uid,
|
||||
});
|
||||
} else if (type == 2) {
|
||||
setCopyer(true);
|
||||
setCopyerConfig({
|
||||
value: JSON.parse(JSON.stringify(props.nodeConfig)),
|
||||
flag: false,
|
||||
id: _uid,
|
||||
});
|
||||
} else {
|
||||
setCondition(true);
|
||||
setConditionsConfig({
|
||||
value: JSON.parse(JSON.stringify(props.nodeConfig)),
|
||||
priorityLevel,
|
||||
flag: false,
|
||||
id: _uid,
|
||||
});
|
||||
}
|
||||
};
|
||||
const arrTransfer = (index, type = 1) => {
|
||||
//向左-1,向右1
|
||||
props.nodeConfig.conditionNodes[index] = props.nodeConfig.conditionNodes.splice(index + type, 1, props.nodeConfig.conditionNodes[index])[0];
|
||||
props.nodeConfig.conditionNodes.map((item, index) => {
|
||||
item.priorityLevel = index + 1;
|
||||
});
|
||||
resetConditionNodesErr();
|
||||
emits('update:nodeConfig', props.nodeConfig);
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
@import '/@/views/flow/workflow/css/workflow.css';
|
||||
|
||||
.error_tip {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
transform: translate(150%, 0px);
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.promoter_person .el-dialog__body {
|
||||
padding: 10px 20px 14px 20px;
|
||||
}
|
||||
|
||||
.selected_list {
|
||||
margin-bottom: 20px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.selected_list span {
|
||||
margin-right: 10px;
|
||||
padding: 3px 6px 3px 9px;
|
||||
line-height: 12px;
|
||||
white-space: nowrap;
|
||||
border-radius: 2px;
|
||||
border: 1px solid rgba(220, 220, 220, 1);
|
||||
}
|
||||
|
||||
.selected_list img {
|
||||
margin-left: 5px;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
114
src/views/flow/workflow/css/override-element-ui.css.bak
Normal file
@@ -0,0 +1,114 @@
|
||||
.el-drawer__header {
|
||||
margin-bottom: 0 !important;
|
||||
padding: 14px 0 14px 20px !important;
|
||||
/* border-bottom: 1px solid #f2f2f2 !important; */
|
||||
color: #323232 !important;
|
||||
font-size: 16px !important;
|
||||
}
|
||||
.el-drawer__header .el-drawer__title{
|
||||
font-size: 16px !important;
|
||||
}
|
||||
.demo-drawer__content {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
.drawer_content {
|
||||
flex: 1 !important;
|
||||
}
|
||||
|
||||
.demo-drawer__content>div {
|
||||
border-top: 1px solid #F2F2F2 !important;
|
||||
}
|
||||
|
||||
.el-button {
|
||||
min-width: 79px !important;
|
||||
padding: 8px 12px !important;
|
||||
font-size: 12px !important;
|
||||
border-radius: 2px !important;
|
||||
color: #323232 !important;
|
||||
background: #f2f2f2 !important;
|
||||
height: 30px !important;
|
||||
}
|
||||
|
||||
.el-button.el-button--primary {
|
||||
background: #46A6FE !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.demo-drawer__footer {
|
||||
padding: 10px 30px !important;
|
||||
border-top: 1px solid #F2F2F2 !important;
|
||||
}
|
||||
|
||||
.demo-drawer__footer .el-button {
|
||||
float: right !important;
|
||||
margin-right: 10px !important;
|
||||
}
|
||||
|
||||
.el-dialog {
|
||||
width: 520px;
|
||||
border: 1px solid #DDE1E5 !important;
|
||||
border-radius: 3px !important;
|
||||
}
|
||||
|
||||
.el-dialog__header {
|
||||
padding: 0 0 0 20px !important;
|
||||
line-height: 50px !important;
|
||||
height: 50px !important;
|
||||
background: #fff !important;
|
||||
border-bottom: 1px solid #F2F2F2 !important;
|
||||
}
|
||||
|
||||
.el-dialog__header .el-dialog__title {
|
||||
font-size: 16px !important;
|
||||
line-height: 50px !important;
|
||||
color: #333333 !important;
|
||||
}
|
||||
|
||||
.el-dialog__header .el-dialog__headerbtn {
|
||||
height: 12px !important;
|
||||
width: 12px !important;
|
||||
}
|
||||
|
||||
.el-dialog__header .el-icon-close {
|
||||
width: 12px !important;
|
||||
height: 12px !important;
|
||||
float: left !important;
|
||||
}
|
||||
|
||||
.el-dialog__header .el-icon-close::before {
|
||||
display: block !important;
|
||||
width: 12px !important;
|
||||
height: 12px !important;
|
||||
background: url(~@/assets/images/add-close.png) no-repeat center !important;
|
||||
background-size: 100% 100% !important;
|
||||
content: "" !important;
|
||||
}
|
||||
.el-drawer__body {
|
||||
padding: 0 !important;
|
||||
}
|
||||
.el-dialog__footer {
|
||||
border-top: 1px solid #F2F2F2 !important;
|
||||
padding-bottom: 10px !important;
|
||||
}
|
||||
|
||||
.el-checkbox,
|
||||
.el-checkbox__input.is-checked+.el-checkbox__label,
|
||||
.el-radio,
|
||||
.el-radio__input.is-checked+.el-radio__label,
|
||||
.el-dialog__body,
|
||||
.el-tree {
|
||||
color: #333 !important;
|
||||
}
|
||||
|
||||
.el-radio__label, .el-checkbox__label {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
.my-el-custom-spinner {
|
||||
display: inline-block !important;
|
||||
width: 80px !important;
|
||||
height: 80px !important;
|
||||
background: url(~@/assets/images/loading.gif) no-repeat center !important;
|
||||
}
|
||||
1709
src/views/flow/workflow/css/workflow.css
Normal file
48
src/views/flow/workflow/stores/flow.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* @Date: 2022-08-25 14:13:11
|
||||
* @LastEditors: StavinLi 495727881@qq.com
|
||||
* @LastEditTime: 2023-05-24 15:00:32
|
||||
* @FilePath: /Workflow-Vue3/src/store/index.js
|
||||
*/
|
||||
import { defineStore } from 'pinia';
|
||||
import { FormConfigUserVO, FormVO } from '/@/api/flow/form/types';
|
||||
|
||||
var adminList: FormConfigUserVO[] = reactive([]);
|
||||
|
||||
export const useFlowStore = defineStore('flow', {
|
||||
state: () => {
|
||||
return {
|
||||
step1: {
|
||||
logo: '',
|
||||
name: '',
|
||||
flowId: '',
|
||||
groupId: undefined,
|
||||
adminList: adminList,
|
||||
remark: '',
|
||||
},
|
||||
step2: [] as FormVO[],
|
||||
step3:{}
|
||||
};
|
||||
},
|
||||
actions: {
|
||||
setStep2(p: FormVO[]) {
|
||||
this.step2 = p;
|
||||
},
|
||||
clearStep1() {
|
||||
this.step1 = {
|
||||
logo: '',
|
||||
name: '',
|
||||
flowId: '',
|
||||
groupId: undefined,
|
||||
adminList: adminList,
|
||||
remark: '',
|
||||
};
|
||||
},
|
||||
clearStep2() {
|
||||
this.step2 = [];
|
||||
},
|
||||
setStep3(p: any) {
|
||||
this.step3 = p;
|
||||
}
|
||||
},
|
||||
});
|
||||
50
src/views/flow/workflow/stores/index.js
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* @Date: 2022-08-25 14:13:11
|
||||
* @LastEditors: StavinLi 495727881@qq.com
|
||||
* @LastEditTime: 2023-05-24 15:00:32
|
||||
* @FilePath: /Workflow-Vue3/src/store/index.js
|
||||
*/
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
export const useStore = defineStore('store', {
|
||||
state: () => ({
|
||||
promoterDrawer: false,
|
||||
|
||||
approverDrawer: false,
|
||||
approverConfigData: {},
|
||||
starterConfigData: {},
|
||||
copyerDrawer: false,
|
||||
copyerConfig1: {},
|
||||
conditionDrawer: false,
|
||||
conditionsConfig1: {
|
||||
conditionNodes: [],
|
||||
},
|
||||
}),
|
||||
actions: {
|
||||
setPromoter(payload) {
|
||||
this.promoterDrawer = payload;
|
||||
},
|
||||
|
||||
setApprover(payload) {
|
||||
this.approverDrawer = payload;
|
||||
},
|
||||
setApproverConfig(payload) {
|
||||
this.approverConfigData = payload;
|
||||
},
|
||||
setStarterConfig(payload) {
|
||||
this.starterConfigData = payload;
|
||||
},
|
||||
setCopyer(payload) {
|
||||
this.copyerDrawer = payload;
|
||||
},
|
||||
setCopyerConfig(payload) {
|
||||
this.copyerConfig1 = payload;
|
||||
},
|
||||
setCondition(payload) {
|
||||
this.conditionDrawer = payload;
|
||||
},
|
||||
setConditionsConfig(payload) {
|
||||
this.conditionsConfig1 = payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
44
src/views/flow/workflow/utils/const.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* @Date: 2023-03-29 15:25:37
|
||||
* @LastEditors: StavinLi 495727881@qq.com
|
||||
* @LastEditTime: 2023-03-29 15:52:38
|
||||
* @FilePath: /Workflow-Vue3/src/utils/const.js
|
||||
*/
|
||||
|
||||
export let bgColors = ['87, 106, 149', '255, 148, 62', '50, 150, 250'];
|
||||
export let placeholderList = ['发起人', '审批人', '抄送人'];
|
||||
|
||||
export let setTypes = [
|
||||
{ value: 1, label: '指定成员' },
|
||||
{ value: 2, label: '部门主管' },
|
||||
{ value: 3, label: '角色' },
|
||||
{ value: 4, label: '发起人自选' },
|
||||
{ value: 5, label: '发起人自己' },
|
||||
//{ value: 7, label: '连续多级主管' },
|
||||
{ value: 8, label: '表单人员' },
|
||||
];
|
||||
|
||||
export let selectModes = [
|
||||
{ value: 1, label: '选一个人' },
|
||||
{ value: 2, label: '选多个人' },
|
||||
];
|
||||
|
||||
export let selectRanges = [
|
||||
{ value: 1, label: '全公司' },
|
||||
{ value: 2, label: '指定成员' },
|
||||
{ value: 3, label: '指定角色' },
|
||||
];
|
||||
|
||||
export let optTypes = [
|
||||
{ value: '1', label: '小于' },
|
||||
{ value: '2', label: '大于' },
|
||||
{ value: '3', label: '小于等于' },
|
||||
{ value: '4', label: '等于' },
|
||||
{ value: '5', label: '大于等于' },
|
||||
{ value: '6', label: '介于两个数之间' },
|
||||
];
|
||||
|
||||
export let opt1s = [
|
||||
{ value: '<', label: '<' },
|
||||
{ value: '≤', label: '≤' },
|
||||
];
|
||||
50
src/views/flow/workflow/utils/formPermissions.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
type FormItem = {
|
||||
field: string;
|
||||
props?: Record<string, any>;
|
||||
children?: FormItem[];
|
||||
hidden?: boolean;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
export function applyPermissionsToItem(item: FormItem, formPerms: Record<string, string>): FormItem {
|
||||
// 如果权限为空,默认设置为只读 'R'
|
||||
const permission = formPerms?.[item.field] || 'E';
|
||||
|
||||
// 检查字段权限
|
||||
if (permission === 'R') {
|
||||
return {
|
||||
...item,
|
||||
props: {
|
||||
...(item.props || {}),
|
||||
disabled: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (permission === 'H') {
|
||||
return {
|
||||
...item,
|
||||
hidden: true,
|
||||
};
|
||||
}
|
||||
|
||||
// 如果是 'E' 或其他情况,返回原始item
|
||||
return item;
|
||||
}
|
||||
|
||||
export function processFormItemsWithPerms(items: FormItem[], formPerms: Record<string, string>): FormItem[] {
|
||||
return items.map((item) => {
|
||||
// 处理当前项的权限
|
||||
const processedItem = applyPermissionsToItem(item, formPerms);
|
||||
|
||||
// 如果有子项,递归处理子项的权限
|
||||
if (Array.isArray(item.children)) {
|
||||
return {
|
||||
...processedItem,
|
||||
children: processFormItemsWithPerms(item.children, formPerms),
|
||||
};
|
||||
}
|
||||
|
||||
return processedItem;
|
||||
});
|
||||
}
|
||||
220
src/views/flow/workflow/utils/index.js
Normal file
@@ -0,0 +1,220 @@
|
||||
function All() {
|
||||
}
|
||||
|
||||
import {useFlowStore} from '../stores/flow';
|
||||
|
||||
let flowStore = useFlowStore();
|
||||
const step2FormList = computed(() => {
|
||||
return flowStore.step2;
|
||||
});
|
||||
const formIdObj = computed(() => {
|
||||
var obj = {};
|
||||
for (var item of step2FormList.value) {
|
||||
obj[item.field] = item;
|
||||
}
|
||||
obj['root'] = {
|
||||
name: '发起人',
|
||||
type: 'SelectUser',
|
||||
};
|
||||
return obj;
|
||||
});
|
||||
|
||||
All.prototype = {
|
||||
timer: '',
|
||||
debounce(fn, delay = 500) {
|
||||
var _this = this;
|
||||
return function (arg) {
|
||||
//获取函数的作用域和变量
|
||||
let that = this;
|
||||
let args = arg;
|
||||
clearTimeout(_this.timer); // 清除定时器
|
||||
_this.timer = setTimeout(function () {
|
||||
fn.call(that, args);
|
||||
}, delay);
|
||||
};
|
||||
},
|
||||
setCookie(val) {
|
||||
//cookie设置[{key:value}]、获取key、清除['key1','key2']
|
||||
for (var i = 0, len = val.length; i < len; i++) {
|
||||
for (var key in val[i]) {
|
||||
document.cookie = key + '=' + encodeURIComponent(val[i][key]) + '; path=/';
|
||||
}
|
||||
}
|
||||
},
|
||||
getCookie(name) {
|
||||
var strCookie = document.cookie;
|
||||
var arrCookie = strCookie.split('; ');
|
||||
for (var i = 0, len = arrCookie.length; i < len; i++) {
|
||||
var arr = arrCookie[i].split('=');
|
||||
if (name == arr[0]) {
|
||||
return decodeURIComponent(arr[1]);
|
||||
}
|
||||
}
|
||||
},
|
||||
clearCookie(name) {
|
||||
var myDate = new Date();
|
||||
myDate.setTime(-1000); //设置时间
|
||||
for (var i = 0, len = name.length; i < len; i++) {
|
||||
document.cookie = '' + name[i] + "=''; path=/; expires=" + myDate.toGMTString();
|
||||
}
|
||||
},
|
||||
arrToStr(arr) {
|
||||
if (arr) {
|
||||
return arr
|
||||
.map((item) => {
|
||||
return item.name;
|
||||
})
|
||||
.toString();
|
||||
}
|
||||
},
|
||||
toChecked(arr, elem, key = 'id') {
|
||||
var isIncludes = this.toggleClass(arr, elem, key);
|
||||
!isIncludes ? arr.push(elem) : this.removeEle(arr, elem, key);
|
||||
},
|
||||
checkApproval(nodeConfig) {
|
||||
if (nodeConfig.assignedType == 1) {
|
||||
//指定成员
|
||||
if (nodeConfig.nodeUserList.length == 0) {
|
||||
return false;
|
||||
}
|
||||
} else if (nodeConfig.assignedType == 3) {
|
||||
//指定角色
|
||||
if (nodeConfig.nodeUserList.length == 0) {
|
||||
return false;
|
||||
}
|
||||
} else if (nodeConfig.assignedType == 8 && nodeConfig.formUserId.length == 0) {
|
||||
//表单
|
||||
return false;
|
||||
}
|
||||
|
||||
//审批人为空
|
||||
if (nodeConfig.nobody?.handler === 'TO_USER' && nodeConfig.nobody.assignedUser.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
setApproverStr(nodeConfig) {
|
||||
if (nodeConfig.assignedType == 1) {
|
||||
//指定成员
|
||||
if (nodeConfig.nodeUserList.length >= 1) {
|
||||
return nodeConfig.nodeUserList.map((res) => res.name).join(',');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
} else if (nodeConfig.assignedType == 3) {
|
||||
//指定角色
|
||||
if (nodeConfig.nodeUserList.length >= 1) {
|
||||
return nodeConfig.nodeUserList.map((res) => res.name).join(',');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
} else if (nodeConfig.assignedType == 2) {
|
||||
return nodeConfig.deptLeaderLevel == 1 ? '直接部门主管' : '第' + nodeConfig.deptLeaderLevel + '级部门主管';
|
||||
} else if (nodeConfig.assignedType == 4) {
|
||||
//发起人自选
|
||||
return '发起人自选';
|
||||
} else if (nodeConfig.assignedType == 5) {
|
||||
return '发起人自己';
|
||||
} else if (nodeConfig.assignedType == 7) {
|
||||
return '到第' + nodeConfig.deptLeaderLevel + '级部门主管终止';
|
||||
} else if (nodeConfig.assignedType == 8 && nodeConfig.formUserId.length > 0) {
|
||||
//表单
|
||||
return '表单:' + nodeConfig.formUserName;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
dealStr(str, obj) {
|
||||
let arr = [];
|
||||
let list = str.split(',');
|
||||
for (var elem in obj) {
|
||||
list.map((item) => {
|
||||
if (item == elem) {
|
||||
arr.push(obj[elem].value);
|
||||
}
|
||||
});
|
||||
}
|
||||
return arr.join('或');
|
||||
},
|
||||
conditionStr(nodeConfig, index) {
|
||||
var { conditionList, groupMode } = nodeConfig.conditionNodes[index];
|
||||
if (conditionList.length == 0) {
|
||||
return index == nodeConfig.conditionNodes.length - 1 ? '其他条件进入此流程' : '请设置条件';
|
||||
} else {
|
||||
var groupConArr = [];
|
||||
for (var groupCondition of conditionList) {
|
||||
let mode = groupCondition.mode;
|
||||
|
||||
var conArr = [];
|
||||
|
||||
for (var con of groupCondition.conditionList) {
|
||||
const { key, expression, value } = con;
|
||||
if (!key) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let valueElement = formIdObj.value[key];
|
||||
var name = valueElement.title || valueElement.name;
|
||||
|
||||
var valueShow = value;
|
||||
if (valueElement.type === 'SelectUser' || valueElement.type === 'SelectDept' || valueElement.type === 'OrgSelector') {
|
||||
valueShow = value.map((res) => res.name).join(',');
|
||||
} else if (valueElement.type === 'SingleSelect') {
|
||||
valueShow = value.map(res=>res.value).join(",")
|
||||
} else {
|
||||
if (!valueShow) {
|
||||
valueShow = '?';
|
||||
}
|
||||
}
|
||||
|
||||
if (expression === '==') {
|
||||
conArr.push(name + ' 等于 ' + valueShow);
|
||||
} else if (expression === 'in') {
|
||||
conArr.push(name + ' 属于 ' + valueShow);
|
||||
} else if (expression === 'notin') {
|
||||
conArr.push(name + ' 不属于 ' + valueShow);
|
||||
} else if (expression === '!=') {
|
||||
conArr.push(name + ' 不等于 ' + valueShow);
|
||||
} else if (expression === '>') {
|
||||
conArr.push(name + ' 大于 ' + valueShow);
|
||||
} else if (expression === '>=') {
|
||||
conArr.push(name + ' 大于等于 ' + valueShow);
|
||||
} else if (expression === '<') {
|
||||
conArr.push(name + ' 小于 ' + valueShow);
|
||||
} else if (expression === '<=') {
|
||||
conArr.push(name + ' 小于等于 ' + valueShow);
|
||||
} else if (expression === 'contain') {
|
||||
conArr.push(name + " 包含 " + valueShow);
|
||||
} else if (expression === 'notcontain') {
|
||||
conArr.push(name + " 不包含 " + valueShow);
|
||||
}
|
||||
}
|
||||
|
||||
if (mode) {
|
||||
let s = conArr.join(' 且 ');
|
||||
if (conArr.length > 0) {
|
||||
groupConArr.push('(' + s + ')');
|
||||
}
|
||||
} else {
|
||||
let s = conArr.join(' 或 ');
|
||||
if (conArr.length > 0) {
|
||||
groupConArr.push('(' + s + ')');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return groupConArr.length > 0
|
||||
? groupConArr.join(groupMode ? ' 且 ' : ' 或 ')
|
||||
: index == nodeConfig.conditionNodes.length - 1
|
||||
? '默认条件'
|
||||
: '请设置条件';
|
||||
}
|
||||
},
|
||||
copyerStr(nodeConfig) {
|
||||
if (nodeConfig.nodeUserList?.length != 0) {
|
||||
return this.arrToStr(nodeConfig.nodeUserList);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default new All();
|
||||