初始化版本,引入智慧食堂模块,第三方模块

This commit is contained in:
zjf committed 2024-12-06 18:48:18 +08:00
commit d22f88efd3
677 files changed
+69341

No files matched your search

+74
View 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;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 466 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 549 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 B

+1
View 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
View 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;
}
}
@@ -0,0 +1,350 @@
<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: () => ({}),
},
});
const { proxy } = getCurrentInstance();
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;
background: #3296fa;
border-radius: 50%;
position: relative;
border: none;
line-height: 30px;
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
}
.add-node-btn .btn .iconfont {
color: #fff;
font-size: 16px;
}
.add-node-btn .btn:hover {
transform: scale(1.3);
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>
@@ -0,0 +1,323 @@
<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.id" :label="item.name" :value="item.id"/>
</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="w-1/2 mb-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 type="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";
let flowStore = useFlowStore();
//驳回节点列表
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 === 'SelectUser');
})
const openEvent = () => {
let value = step2FormList.value;
var arr = {};
let formPerms = approverConfig.value.formPerms;
for (var item of value) {
arr[item.id] = "R"
if (item.type === 'Layout') {
arr[item.id] = "E"
}
if (formPerms[item.id]) {
arr[item.id] = formPerms[item.id]
}
if (item.type === 'Layout') {
let value1 = item.props.value;
for (var it of value1) {
arr[it.id] = "R"
if (formPerms[it.id]) {
arr[it.id] = formPerms[it.id]
}
}
}
}
approverConfig.value.formPerms = arr;
}
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
}
let t = step2FormUserList.value.filter(res => res.id === id)[0].props.multi;
return t;
}
//监听用户选择表单值变化
watch(() => approverConfig.value.formUserId, (val) => {
if (val) {
approverConfig.value.formUserName = step2FormUserList.value.filter(res => res.id === val)[0].name
}
})
//审批人类型变化
const assignedTypeChangeEvent = (e) => {
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,365 @@
<script setup lang="ts">
import { useFlowStore } from '../../../stores/flow';
import { getCurrentInstance } from 'vue';
let flowStore = useFlowStore();
const step2FormList = computed(() => {
let step2 = flowStore.step2;
return step2;
})
const {proxy} = getCurrentInstance();
//表单
const formList = computed(() => {
let value = step2FormList.value;
let $deepCopy = other.deepClone(value.filter(res =>
res.type != 'MultiSelect' &&
res.type != 'Description' &&
res.type != 'SelectMultiUser' &&
res.type != 'SelectMultiDept' &&
res.type != 'Layout' &&
res.type != 'UploadImage' &&
res.type != 'UploadFile'
));
$deepCopy.push({
id: 'root',
name: '发起人',
type: "SelectUser",
typeName: "用户"
})
return $deepCopy;
})
let props = defineProps({
condition: {
type: Object,
default: () => {
},
},
});
const formIdObj = computed(() => {
var obj = {}
for (var item of formList.value) {
obj[item.id] = item;
}
return obj
})
const conditionSelectVal = computed(
{
get(){
let value = props.condition.value;
return (value&&value.length>0)?(value.map(res=>res.key)):undefined;
},
set(t){
let options = (formIdObj.value)[props.condition.key].props.options;
let filterElement = options.filter(res=>t.indexOf(res.key)>=0);
props.condition.value = filterElement
}
}
)
//表达式
let expression = ref(
{
"SelectDept": [
{
key: "in",
name: "属于"
}, {
key: "notin",
name: "不属于"
}
],
"SelectUser": [
{
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: "不包含"
}
],
"Number": [
{
key: "==",
name: "等于"
}, {
key: "!=",
name: "不等于"
}, {
key: ">",
name: "大于"
}, {
key: ">=",
name: "大于等于"
}, {
key: "<",
name: "小于"
}, {
key: "<=",
name: "小于等于"
}
],
"Date": [
{
key: "==",
name: "等于"
}, {
key: "!=",
name: "不等于"
}, {
key: ">",
name: "大于"
}, {
key: ">=",
name: "大于等于"
}, {
key: "<",
name: "小于"
}, {
key: "<=",
name: "小于等于"
}
],
"DateTime": [
{
key: "==",
name: "等于"
}, {
key: "!=",
name: "不等于"
}, {
key: ">",
name: "大于"
}, {
key: ">=",
name: "大于等于"
}, {
key: "<",
name: "小于"
}, {
key: "<=",
name: "小于等于"
}
],
"Time": [
{
key: "==",
name: "等于"
}, {
key: "!=",
name: "不等于"
}, {
key: ">",
name: "大于"
}, {
key: ">=",
name: "大于等于"
}, {
key: "<",
name: "小于"
}, {
key: "<=",
name: "小于等于"
}
],
"Money": [
{
key: "==",
name: "等于"
}, {
key: "!=",
name: "不等于"
}, {
key: ">",
name: "大于"
}, {
key: ">=",
name: "大于等于"
}, {
key: "<",
name: "小于"
}, {
key: "<=",
name: "小于等于"
}
],
"SingleSelect": [
{
key: "in",
name: "属于"
}, {
key: "notin",
name: "不属于"
}
]
}
)
import selectShow from '/@/components/OrgSelector/index.vue';
import other from '/@/utils/other';
</script>
<template>
<div>
<el-select v-model="condition.key" placeholder="选择表单" style="width: 100%;">
<el-option
v-for="f in formList"
:key="f.id"
:label="f.name"
:value="f.id"
/>
</el-select>
<el-select v-model="condition.expression" placeholder="选择关系" style="width: 100%;margin-top: 20px">
<el-option
v-for="f in 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==='Number'
"
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==='Date'
"
placeholder="条件值"
style="width: 100%;margin-top: 20px"
/>
<el-time-picker
arrow-control
value-format="HH:mm:ss"
class="formDate"
v-model="condition.value"
v-if="formIdObj[condition.key]?.type==='Time'
"
placeholder="条件值"
style="width: 100%;margin-top: 20px"
/>
<el-date-picker
value-format="YYYY-MM-DD HH:mm:ss"
type="datetime"
class="formDate"
v-model="condition.value"
v-if="formIdObj[condition.key]?.type==='DateTime'
"
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==='SelectUser'
" v-model:orgList="condition.value" type="org" :multiple="true"></select-show>
</div>
<div style="margin-top: 20px">
<select-show v-if="formIdObj[condition.key]?.type==='SelectDept'
" 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';
defineProps({
formPerm: {
type: Object,
default: () => {},
},
hideKey: {
type: Array,
default: () => [],
},
});
let flowStore = useFlowStore();
const step2FormList = computed(() => {
let step2 = flowStore.step2;
return 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.id">
<div style="display: flex; flex-direction: row">
<div class="f1">
<span>{{ item.name }}</span>
<span v-if="item.required" style="color: #c75450"> * </span>
</div>
<el-radio-group v-model="formPerm[item.id]" 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 v-if="item.type === 'Layout'">
<div v-for="item1 in item.props.value" :key="item1.id">
<div style="display: flex; flex-direction: row">
<div class="f1">
<span> {{ '\u00A0\u00A0\u00A0\u00A0-' + item1.name }}</span>
<span v-if="item1.required" style="color: #c75450"> * </span>
</div>
<el-radio-group v-model="formPerm[item1.id]" 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>
</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>
@@ -0,0 +1,182 @@
<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 { ref, watch, computed, getCurrentInstance } from 'vue';
import $func from '../../utils/index';
import { useStore } from '../../stores/index';
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;
};
import Condition from './components/condition.vue';
import { useFlowStore } from '../../stores/flow';
let flowStore = useFlowStore();
const step2FormList = computed(() => {
let step2 = flowStore.step2;
return 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.id === key);
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>
@@ -0,0 +1,98 @@
<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="org" :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 { ref, watch, computed } from 'vue';
let copyerConfig = ref({});
import { useFlowStore } from '../../stores/flow';
import { ElTable } from 'element-plus';
let flowStore = useFlowStore();
import FormPerm from './components/formPerm.vue';
const step2FormList = computed(() => {
let step2 = flowStore.step2;
return 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.id] = 'R';
// if (item.type === 'Layout') {
// arr[item.id] = "R"
// }
if (formPerms[item.id]) {
arr[item.id] = formPerms[item.id];
}
if (item.type === 'Layout') {
let value1 = item.props.value;
for (var it of value1) {
arr[it.id] = 'R';
if (formPerms[it.id]) {
arr[it.id] = formPerms[it.id];
}
}
}
}
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>
@@ -0,0 +1,90 @@
<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(() => {
let step2 = flowStore.step2;
return step2;
});
const openEvent = () => {
let value = step2FormList.value;
var arr = {};
let formPerms = starterConfig.value.formPerms;
for (var item of value) {
arr[item.id] = 'E';
if (formPerms[item.id]) {
arr[item.id] = formPerms[item.id];
}
if (item.type === 'Layout') {
let value1 = item.props.value;
for (var it of value1) {
arr[it.id] = 'E';
if (formPerms[it.id]) {
arr[it.id] = formPerms[it.id];
}
}
}
}
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>
@@ -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>
@@ -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)">&lt;</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)">&gt;</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)">&lt;</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)">&gt;</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.id === 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.id === 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>
@@ -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;
}
File diff suppressed because it is too large. Load diff
+48
View 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
View 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
View 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: '≤' },
];
+221
View File
@@ -0,0 +1,221 @@
function All() {}
import { useFlowStore } from '../stores/flow';
let flowStore = useFlowStore();
const step2FormList = computed(() => {
let step2 = flowStore.step2;
return step2;
});
const formIdObj = computed(() => {
var obj = {};
for (var item of step2FormList.value) {
obj[item.id] = 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.name;
var valueShow = value;
if (valueElement.type === 'SelectUser' || valueElement.type === 'SelectDept') {
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();