---初始化后台管理web页面项目

This commit is contained in:
2025-08-20 14:39:30 +08:00
parent ad49711a7e
commit 87545a8baf
2057 changed files with 282864 additions and 213 deletions

View File

@ -0,0 +1,314 @@
<template>
<a-modal :mask-closable="false" :title="dialogTitle" :visible="isOpen" :width="500" centered class="geg" @cancel="onClickCancel">
<template #footer>
<a-button :disabled="loading" @click="onClickCancel">取消</a-button>
<a-button :loading="loading" type="primary" @click="onClickOK">确定</a-button>
</template>
<div class="dialog-wrap">
<a-form :label-col="{ span: 6 }" :model="formState" autocomplete="off">
<a-form-item v-if="_action === 'agree'" label="下一节点" name="nextNodeName">
<span>{{ getNextNodesName() }}</span>
</a-form-item>
<template v-for="node in flowNextNodes" :key="node.activityId">
<div v-if="flowNextNodes.length > 1" class="node-info">
<span class="node-label">{{ node.nodeTypeLabel }}</span>
<span class="node-name">{{node.activityName}}</span>
<a-switch :checked="node.chooseNode" v-if="!node.hiddenNode" style="margin-left: 10px;" @change="agreeNodeChange(node)"></a-switch>
</div>
<a-form-item :required="(flowNextNodes.length === 1 || node.chooseNode)" v-if="_action === 'agree' && !isEnd" :label="'审批人'">
<a-select v-show="node.chooseAssign" v-model:value="node.assignees" :options="node.nextAssignees" :disabled="loading"
:placeholder="'审批人'" max-tag-count="responsive"
:mode="node.isChooseMulti? 'multiple' : ''"
:filterOption="search"
></a-select>
<span v-show="!node.chooseAssign">{{ getAssigneeText(node) }}</span>
</a-form-item>
</template>
<a-form-item v-if="_action === 'reject'" label="退回至" name="rejectNode">
<a-select v-model:value="rejectNodeId" :disabled="loading">
<a-select-option v-for="(item, index) in rejectNodeList" :key="index" :value="item.activityId">{{ item.activityName }}</a-select-option>
</a-select>
</a-form-item>
<template v-for="node in rejectNodeList">
<a-form-item v-if="_action === 'reject'&&rejectNodeId===node.activityId" label="审批人">
<a-select v-show="node.chooseAssign" v-model:value="node.assignees" :options="node.nextAssignees" :disabled="loading"
:placeholder="'请选择' + node.activityName + '的审批人'" max-tag-count="responsive" mode="multiple"
:filterOption="search"
></a-select>
<span v-show="!node.chooseAssign">{{ getAssigneeText(node) }}</span>
</a-form-item>
</template>
<a-form-item label="审批意见" name="opinion">
<a-textarea v-model:value="formState.opinion" :disabled="loading" :maxlength="200" :rows="3" placeholder="请输入审批意见不超过200字" />
</a-form-item>
</a-form>
</div>
</a-modal>
</template>
<script setup>
import { reactive, ref } from 'vue';
import { getRejectNodeList } from '/@/api/workflow/task';
import { message } from 'ant-design-vue';
const dialogTitle = ref('审批');
const isOpen = ref(false);
const rejectNodeList = ref([]);
const rejectNodeId = ref('');
const loading = ref(false);
const isEnd = ref(false);
let _action = ref('agree');
let _processId = '';
let _taskId = '';
let flowNextNodes = ref([]);
let _callback = null;
let _onCancel = null;
const formState = reactive({
opinion: '',
opinionList: ['同意。', '请领导审批。']
});
function getAssigneeText(node) {
// 注意这里用的是下拉框的数据结构 所以字段是value和label
return (node.nextAssignees || [])
.filter((item) => node.assignees.includes(item.value))
.map((item) => item.label)
.join('、');
}
function getNextNodesName() {
return flowNextNodes.value.length > 1 ? '多个节点,请选择流向节点' : flowNextNodes?.value[0]?.activityName;
}
function toggleDialog({ isClose, action, callback, rejectCancel, processId, taskId, nextNodes } = {}) {
if (isClose) {
isOpen.value = false;
loading.value = false;
return;
}
isOpen.value = true;
_action.value = action;
_callback = callback;
_onCancel = rejectCancel;
_processId = processId;
_taskId = taskId;
flowNextNodes.value = nextNodes;
formState.opinion = '';
dialogTitle.value = `审批`;
if (nextNodes && nextNodes.length) {
// 下一个节点唯一时(可能有并行节点)
const nNode = nextNodes[0];
//formState.nextNodeName = nNode.activityName;
isEnd.value = nNode.isEnd;
const typeCount = nextNodes.length <= 1 ? nextNodes.length : (new Set(nextNodes.map(nNode => nNode.nodeType))).size;
nextNodes.forEach((nNode) => {
if (!nNode.userList?.length) {
return;
}
const selected = [];
nNode.nextAssignees = nNode.userList.map((item) => {
if (item.checked || nNode.userList.length === 1) {
// 只有一个人的时候必须选他
selected.push(item['F_UserId']);
}
return {
value: item['F_UserId'],
label: item['F_RealName'],
item:item
};
});
nNode.assignees = selected;
if (!nNode.chooseAssign) {
// 不需要选审批人的时候 所有备选人都要放到下个节点
nNode.assignees = nNode.userList.map((item) => item['F_UserId']);
}
nNode.chooseAssign = nNode.chooseAssign;
if (nNode.nodeType === 'commitToNode') {
nNode.nodeTypeLabel = '提交到退回节点:';
}
if (nextNodes.length>1 && nNode.nodeType === 'common') {
nNode.nodeTypeLabel = '并行节点:';
}
// 如果是必选并行节点 或者 只有一个节点,默认选中
if (nNode.isChooseParallel || nextNodes.length == 1) {
nNode.chooseNode = true;
if (typeCount == 1) {
// 如果只有一个节点类型的,默认隐藏
nNode.hiddenNode = true;
} else {
nNode.hiddenNode = false;
}
}
});
flowNextNodes.value = nextNodes;
}
if (action === 'reject') {
loadRejectNodeList();
}
}
function agreeNodeChange(node) {
if (node.chooseNode) {
// 如果是必选并行节点,取消选择时,警告并返回
if (node.isChooseParallel) {
return message.warning('并行节点必选,不能取消选择');
}
node.chooseNode = false;
// 遍历节点如果不是common类型都是false了需要遍历common类型中必填节点是否都是true
const sumNotCommonNode = flowNextNodes.value.filter((nNode) => nNode.nodeType != 'common' && nNode.chooseNode).length;
if (sumNotCommonNode === 0) {
// 如果没有非common类型的节点被选中则将遍历common类型的节点,必填节点设置为true
flowNextNodes.value.forEach((nNode) => {
if (nNode.nodeType === 'common' && nNode.isChooseParallel) {
nNode.chooseNode = true;
}
});
}
} else if (!node.chooseNode) {
node.chooseNode = true;
// 遍历节点,取消与自己不同类型的节点的选择
flowNextNodes.value.forEach((nNode) => {
if (nNode.nodeType != node.nodeType) {
nNode.chooseNode = false;
}
});
if (node.nodeType === 'common') {
// 如果是common类型的节点遍历所有common类型的节点必填节点设置为true
flowNextNodes.value.forEach((nNode) => {
if (nNode.nodeType === 'common' && nNode.isChooseParallel) {
nNode.chooseNode = true;
}
});
}
}
}
function search(inputValue, option){
return inputValue?(option.item.F_Account.indexOf(inputValue)>-1||option.item.F_RealName.indexOf(inputValue)>-1):true;
}
async function loadRejectNodeList() {
rejectNodeId.value='';
let res = await getRejectNodeList(_processId, _taskId);
if (res && Array.isArray(res) && res.length > 0) {
rejectNodeList.value = res;
dialogTitle.value = `退回`;
if (res?.length) {
res.forEach((nNode) => {
if (!nNode.userList?.length) {
return;
}
const selected = [];
nNode.nextAssignees = nNode.userList.map((item) => {
if (item.checked || nNode.userList.length === 1) {
// 只有一个人的时候必须选他
selected.push(item['F_UserId']);
}
return {
value: item['F_UserId'],
label: item['F_RealName']+(item.remarks?"("+item.remarks+")":""),
item:item
};
});
nNode.assignees = selected;
if (!nNode.chooseAssign) {
// 不需要选审批人的时候 所有备选人都要放到下个节点
nNode.assignees = nNode.userList.map((item) => item['F_UserId']);
}
nNode.chooseAssign = nNode.chooseAssign;
});
}
}
}
function onClickOK() {
const nextTaskUser = {};
if (_action.value === 'agree' && !isEnd.value) {
// 如果是同意,并且没有选择节点 报错
const choose = flowNextNodes.value.find((node) => (node.chooseNode));
if (!choose) {
return message.error('请选择下一节点');
}
// 调整成选中的节点的被审人员
const isEmpty = flowNextNodes.value.find((node) => (!node.assignees?.length && node.chooseNode));
if (isEmpty) {
return message.error('请选择审批人');
}
flowNextNodes.value.filter((node)=>node.chooseNode).forEach((nNode) => {
nextTaskUser[nNode.activityId] = isEnd.value ? '' : (typeof(nNode.assignees) == 'string' ? nNode.assignees : nNode.assignees.join(','));
});
}
if (_action.value === 'reject') {
const isChoose = rejectNodeList.value.find((node) => node.activityId==rejectNodeId.value&&node.assignees?.length);
if (!isChoose) {
return message.error('请选择审批人');
}
rejectNodeList.value.forEach((nNode) => {
if(nNode.activityId==rejectNodeId.value){
nextTaskUser[nNode.activityId] = isEnd.value ? '' : nNode.assignees.join(',');
}
});
}
if (_callback && typeof _callback === 'function') {
loading.value = true;
_callback({
opinion: formState.opinion,
rejectNodeId: rejectNodeId.value,
nextTaskUser,
isEnd
});
} else {
isOpen.value = false;
}
}
function onClickCancel() {
if (_onCancel && typeof _onCancel === 'function') {
_onCancel();
}
isOpen.value = false;
}
function stopLoading() {
loading.value = false;
}
defineExpose({
toggleDialog,
stopLoading
});
</script>
<style lang="less" scoped>
.dialog-wrap {
padding: 10px 15px 0 0;
}
.node-info {
display: block;
margin: 6px 0;
line-height: 2;
padding-left: 0;
}
.node-label {
display: inline-block;
min-width: 25%;
text-align: right;
padding-right: 8px;
color: #000;
vertical-align: top;
}
.node-name {
display: inline-block;
flex: none;
text-align: left;
color: #333;
font-weight: 600;
vertical-align: top;
}
</style>