处置申请-审批流程设置修改
This commit is contained in:
@@ -85,14 +85,6 @@ export function getFlowApi(data:{category:number | null,level:number | null,dura
|
|||||||
params: data,
|
params: data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// 本单审批流程·审批人设置
|
|
||||||
export function setApproverApi(id:number,data:any) {
|
|
||||||
return request({
|
|
||||||
url: `/api/changes/${id}/approval-flow`,
|
|
||||||
method: 'put',
|
|
||||||
data,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取风险矩阵配置
|
// 获取风险矩阵配置
|
||||||
export function matrixConfigApi() {
|
export function matrixConfigApi() {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Icon } from '@iconify/vue'
|
|||||||
import { useThemeStore } from '@/store/modules/theme';
|
import { useThemeStore } from '@/store/modules/theme';
|
||||||
import { useRouterPush } from '@/hooks/common/router';
|
import { useRouterPush } from '@/hooks/common/router';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { getColorWithOpacity, tagTextColor, tagBgColor, tagBorderColor, formatTimestamp, downloadFileFun } from '@/utils/common';
|
import { tagTextColor, tagBgColor, tagBorderColor, formatTimestamp, downloadFileFun } from '@/utils/common';
|
||||||
import { AiError, aiTypeJudge } from "@/service/api/ai";
|
import { AiError, aiTypeJudge } from "@/service/api/ai";
|
||||||
import { systemConfigApi, peopleListApi } from '@/service/api/system';
|
import { systemConfigApi, peopleListApi } from '@/service/api/system';
|
||||||
import { getChangeDetailApi, urgeApi } from '@/service/api/change';
|
import { getChangeDetailApi, urgeApi } from '@/service/api/change';
|
||||||
@@ -45,10 +45,18 @@ const judge = ref<string>('');
|
|||||||
const delayDate = ref<number | null>(null);
|
const delayDate = ref<number | null>(null);
|
||||||
// 审批流程抽屉
|
// 审批流程抽屉
|
||||||
const approvalDrawer = ref<boolean>(false);
|
const approvalDrawer = ref<boolean>(false);
|
||||||
// 存储每个步骤选中的审核人 user_id
|
|
||||||
const approvers = ref<Record<number, number[]>>({})
|
|
||||||
const flowList = ref<any[]>([]);
|
const flowList = ref<any[]>([]);
|
||||||
const steps = ref<any[]>([]);
|
const steps = ref<any[]>([]);
|
||||||
|
// 专业
|
||||||
|
const SPECIALTIES = [
|
||||||
|
{ label: '生产', value: '生产' },
|
||||||
|
{ label: '技术', value: '技术' },
|
||||||
|
{ label: '仪表', value: '仪表' },
|
||||||
|
{ label: '电气', value: '电气' },
|
||||||
|
{ label: '设备', value: '设备' },
|
||||||
|
{ label: '安全', value: '安全' },
|
||||||
|
{ label: '质量', value: '质量' }
|
||||||
|
];
|
||||||
// 审批链
|
// 审批链
|
||||||
const approvalFlowText = computed(() => {
|
const approvalFlowText = computed(() => {
|
||||||
if (!flowList.value || flowList.value.length === 0) return '暂无步骤'
|
if (!flowList.value || flowList.value.length === 0) return '暂无步骤'
|
||||||
@@ -236,28 +244,9 @@ const handleReminder = async (m: any) => {
|
|||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新选中审批人 user_id
|
|
||||||
const updateApprover = (stepId: number, usersId: number[]) => {
|
|
||||||
approvers.value[stepId] = usersId
|
|
||||||
}
|
|
||||||
// 匹配审批人
|
|
||||||
function matchApprovers(steps: any[], mapping: Record<number, number[]>) {
|
|
||||||
const result = [];
|
|
||||||
for (const [stepId, usersId] of Object.entries(mapping)) {
|
|
||||||
const step = steps.find(s => s.id === Number(stepId));
|
|
||||||
if (step) {
|
|
||||||
const memberList = step.members.filter((m: any) => usersId.includes(m.user_id));
|
|
||||||
result.push({
|
|
||||||
members: memberList
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
// 完成设置
|
// 完成设置
|
||||||
const finishApprover = async () => {
|
const finishApprover = async () => {
|
||||||
const matched = matchApprovers(flowList.value, approvers.value);
|
steps.value = flowList.value || [];
|
||||||
steps.value = matched;
|
|
||||||
window.$message?.success("审批流程设置完成");
|
window.$message?.success("审批流程设置完成");
|
||||||
approvalDrawer.value = false;
|
approvalDrawer.value = false;
|
||||||
}
|
}
|
||||||
@@ -349,6 +338,7 @@ const submitDisposal = async (type: string) => {
|
|||||||
const result = steps.value.map(({ members }:any) => ({
|
const result = steps.value.map(({ members }:any) => ({
|
||||||
members: members.map(({ id, step_order, user_name, status, ...rest }:any) => rest)
|
members: members.map(({ id, step_order, user_name, status, ...rest }:any) => rest)
|
||||||
}));
|
}));
|
||||||
|
loading.value = true;
|
||||||
const {error} = await myTasksDisposalSubmitApi(currentId.value,{
|
const {error} = await myTasksDisposalSubmitApi(currentId.value,{
|
||||||
action: type,
|
action: type,
|
||||||
act: actType,
|
act: actType,
|
||||||
@@ -369,6 +359,7 @@ const submitDisposal = async (type: string) => {
|
|||||||
back();
|
back();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
loading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取变更详情
|
// 获取变更详情
|
||||||
@@ -379,20 +370,7 @@ const getDetail = async () => {
|
|||||||
currentInfo.value = data;
|
currentInfo.value = data;
|
||||||
// ******审批流程********
|
// ******审批流程********
|
||||||
flowList.value = data?.approval_flow?.steps || [];
|
flowList.value = data?.approval_flow?.steps || [];
|
||||||
// ******默认审批人********
|
steps.value = data?.approval_flow?.steps || [];
|
||||||
if(Object.keys(approvers.value).length === 0){
|
|
||||||
flowList.value.forEach((item: any) => {
|
|
||||||
if (item.members && item.members.length > 0) {
|
|
||||||
// 优先选 is_sign=1 的成员 ,否则选第一个
|
|
||||||
const defaultMembers = item.members
|
|
||||||
.filter((m: any) => m.is_sign === 1)
|
|
||||||
.map((m: any) => m.user_id);
|
|
||||||
approvers.value[item.id] = defaultMembers.length > 0 ? defaultMembers : [item.members[0].user_id];
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const matched = matchApprovers(flowList.value, approvers.value);
|
|
||||||
steps.value = matched;
|
|
||||||
// ******解析 PSSR 检查内容********
|
// ******解析 PSSR 检查内容********
|
||||||
const map = new Map();
|
const map = new Map();
|
||||||
for (const item of currentInfo.value?.pssr?.items) {
|
for (const item of currentInfo.value?.pssr?.items) {
|
||||||
@@ -473,22 +451,105 @@ const getConfig = async () => {
|
|||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 人员数据转换
|
||||||
|
const groupUsersByOrg = (users:any) => {
|
||||||
|
const orgMap = new Map(); // key: org.id, value: { id, label, children }
|
||||||
|
users.forEach((user:any) => {
|
||||||
|
const { id: userId, realname, user_departments } = user;
|
||||||
|
if (!user_departments || user_departments.length === 0) return;
|
||||||
|
|
||||||
|
user_departments.forEach((dept:any) => {
|
||||||
|
const org = dept.org;
|
||||||
|
if (!org || !org.id) return;
|
||||||
|
|
||||||
|
if (!orgMap.has(org.id)) {
|
||||||
|
orgMap.set(org.id, {
|
||||||
|
value: org.id,
|
||||||
|
label: org.label,
|
||||||
|
children: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const targetOrg = orgMap.get(org.id);
|
||||||
|
// 避免同一个用户重复加入同一个组织(理论上不会发生,但做去重保护)
|
||||||
|
if (!targetOrg.children.some((child:any) => child.id === userId)) {
|
||||||
|
targetOrg.children.push({
|
||||||
|
value: userId.toString(),
|
||||||
|
label: realname,
|
||||||
|
user_id: userId.toString()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return Array.from(orgMap.values());
|
||||||
|
}
|
||||||
|
|
||||||
// 获取所有人员
|
// 获取所有人员
|
||||||
const getAllMembers = async () => {
|
const getAllMembers = async () => {
|
||||||
const {data,error} = await peopleListApi();
|
const {data,error} = await peopleListApi();
|
||||||
if(!error){
|
if(!error){
|
||||||
const transformed = data.map((item:any) => ({
|
memberList.value = groupUsersByOrg(data);
|
||||||
user_id: item.id,
|
|
||||||
user_name: item.realname,
|
|
||||||
full_name: item.realname + (`(${item.user_departments.map((d:any) => d.label).join(';')})`),
|
|
||||||
org_id: item.user_departments.map((d:any) => d.organization_id).join(';'),
|
|
||||||
org_name: item.user_departments.map((d:any) => d.label).join(';')
|
|
||||||
}));
|
|
||||||
memberList.value = transformed;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在组织树中根据 user_id 查找用户信息
|
||||||
|
* @param {Array} tree - 组织树数据
|
||||||
|
* @param {string|number} targetUserId - 要查找的 user_id
|
||||||
|
* @returns {Object|null} - 返回 { userLabel, parentLabel, parentValue } 或 null
|
||||||
|
*/
|
||||||
|
function findUserInTree(tree: any[], targetUserId: string | number) {
|
||||||
|
for (const org of tree) {
|
||||||
|
const children = org.children || [];
|
||||||
|
for (const user of children) {
|
||||||
|
if (String(user.user_id) === String(targetUserId)) {
|
||||||
|
return {
|
||||||
|
user_name: user.label,
|
||||||
|
org_name: org.label,
|
||||||
|
org_id: org.value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null; // 未找到
|
||||||
|
}
|
||||||
|
// 设置专业会签步骤
|
||||||
|
const setStepCountersign = (target: any, v: boolean) => {
|
||||||
|
flowList.value.forEach((s:any) => {
|
||||||
|
s.countersign = s === target ? v : false;
|
||||||
|
if (!s.countersign) s.members.forEach((m:any) => { m.specialty = ""; m.is_sign = 0; });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 选择专业
|
||||||
|
const selectSpecialty = (idx: number, mIdx: number, v: string | null) => {
|
||||||
|
flowList.value[idx].members[mIdx].specialty = v ?? null;
|
||||||
|
}
|
||||||
|
// 选择是否必须参与会签
|
||||||
|
const selectSign = (idx: number, mIdx: number, v: number) => {
|
||||||
|
flowList.value[idx].members[mIdx].is_sign = v;
|
||||||
|
}
|
||||||
|
// 当前打开人员选择器的步骤(步骤行「+」按钮切换)
|
||||||
|
const pickerStepId = ref<number | null>(null);
|
||||||
|
const pick = ref<string | null>(null);
|
||||||
|
const togglePicker = (s: any) => {
|
||||||
|
pickerStepId.value = pickerStepId.value === s.id ? null : s.id;
|
||||||
|
pick.value = null;
|
||||||
|
}
|
||||||
|
// 添加审批步骤人员
|
||||||
|
const addMember = (s: any, v: string | null) => {
|
||||||
|
if (v && !s.members.some((m:any) => m.user_name === v)){
|
||||||
|
let info = findUserInTree(memberList.value, v);
|
||||||
|
s.members.push({ user_id: Number(v), user_name: info?.user_name || '', org_id: info?.org_id || '', org_name: info?.org_name || '', specialty: "", is_sign: 0 });
|
||||||
|
pick.value = null;
|
||||||
|
pickerStepId.value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 删除审批步骤人员
|
||||||
|
const delMember = (s: any, p: number) => {
|
||||||
|
s.members = s.members.filter((m:any) => m.user_id !== p);
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
getAllMembers();
|
||||||
if(route.query && route.query.id){
|
if(route.query && route.query.id){
|
||||||
if(route.query.id){
|
if(route.query.id){
|
||||||
currentId.value = Number(route.query.id);
|
currentId.value = Number(route.query.id);
|
||||||
@@ -1009,8 +1070,8 @@ onMounted(async () => {
|
|||||||
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
>
|
>
|
||||||
<NButton size="small" @click="approvalDrawer = true">审批流程设置</NButton>
|
<NButton size="small" @click="approvalDrawer = true">审批流程设置</NButton>
|
||||||
<NButton size="small" @click="submitDisposal('save')">保存</NButton>
|
<NButton size="small" :disabled="loading" @click="submitDisposal('save')">保存</NButton>
|
||||||
<NButton size="small" type="primary" @click="submitDisposal('submit')">提交</NButton>
|
<NButton size="small" type="primary" :disabled="loading" @click="submitDisposal('submit')">提交</NButton>
|
||||||
</div>
|
</div>
|
||||||
<!-- 变更类型判定说明(右侧抽屉) -->
|
<!-- 变更类型判定说明(右侧抽屉) -->
|
||||||
<NDrawer v-model:show="describeDrawer" :width="576" placement="right">
|
<NDrawer v-model:show="describeDrawer" :width="576" placement="right">
|
||||||
@@ -1082,15 +1143,56 @@ onMounted(async () => {
|
|||||||
</template>
|
</template>
|
||||||
<div class="h-full space-y-4 overflow-y-auto">
|
<div class="h-full space-y-4 overflow-y-auto">
|
||||||
<p class="text-xs leading-relaxed text-slate-400">审批链:{{ approvalFlowText }}。专业会签人由申请表「涉及专业」勾选自动增减,可在此调整人选;重要变更自动追加安全审查与分管领导终批。</p>
|
<p class="text-xs leading-relaxed text-slate-400">审批链:{{ approvalFlowText }}。专业会签人由申请表「涉及专业」勾选自动增减,可在此调整人选;重要变更自动追加安全审查与分管领导终批。</p>
|
||||||
<div class="rounded-lg border p-3" v-for="(item,index) in flowList" :key="item.id">
|
<div v-for="(s, idx) in flowList" :key="s.id" class="rounded-lg border border-slate-100 bg-slate-50/40 p-3">
|
||||||
<div class="mb-2 flex items-center gap-2 text-sm font-semibold text-slate-700">
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
<NTag type="info" size="small">{{Number(index)+1}}</NTag> {{item.name}} {{item.countersign ? '(专业会签)' : ''}}
|
<NTag type="info" size="small">{{Number(idx)+1}}</NTag>
|
||||||
|
<p class="text-sm">{{s.name}}</p>
|
||||||
|
<NCheckbox :checked="s.countersign" size="small" @update:checked="(v) => setStepCountersign(s, v)">
|
||||||
|
<span class="text-xs text-slate-500">专业会签步骤</span>
|
||||||
|
</NCheckbox>
|
||||||
|
<NButton ghost type="primary" size="tiny" @click="togglePicker(s)" title="添加预设人员" class="ml-auto">
|
||||||
|
<Icon icon="ic:round-plus" class="size-16px" />
|
||||||
|
</NButton>
|
||||||
|
</div>
|
||||||
|
<!-- 预设人员:人员必选;仅会签步骤可选择专业(必选),且人员可勾选「必须进行专业会签」 -->
|
||||||
|
<div class="mt-2 flex gap-2 pl-8">
|
||||||
|
<span class="shrink-0 text-xs leading-7 text-slate-400">预设人员:</span>
|
||||||
|
<div class="min-w-0 flex-1 space-y-1.5">
|
||||||
|
<div v-for="(m, mIdx) in s.members" :key="m.id" class="flex min-h-7 flex-wrap items-center gap-2">
|
||||||
|
<span class="shrink-0 truncate text-xs font-medium text-slate-700">{{ m.user_name }}</span>
|
||||||
|
<template v-if="s.countersign">
|
||||||
|
<span class="text-xs text-slate-400"><span class="text-red-500">*</span>专业:</span>
|
||||||
|
<NSelect :value="m.specialty || null" clearable size="small" placeholder="请选择专业" class="w-28"
|
||||||
|
:options="SPECIALTIES"
|
||||||
|
@update:value="(v) => selectSpecialty(Number(idx), Number(mIdx), v)"
|
||||||
|
/>
|
||||||
|
<NCheckbox :checked="m.is_sign" size="small"
|
||||||
|
:checked-value="1"
|
||||||
|
:unchecked-value="0"
|
||||||
|
@update:checked="(v) => selectSign(Number(idx), Number(mIdx), v)"
|
||||||
|
>
|
||||||
|
<span class="text-xs text-slate-500">必须参与会签</span>
|
||||||
|
</NCheckbox>
|
||||||
|
</template>
|
||||||
|
<NButton text type="error" @click="delMember(s, m.user_id)" title="删除该人员">
|
||||||
|
<Icon icon="iconamoon:trash" class="size-16px" />
|
||||||
|
</NButton>
|
||||||
|
</div>
|
||||||
|
<p v-if="s.countersign && s.members.length" class="text-[11px] leading-5 text-slate-400">
|
||||||
|
必须参与会签:勾选的人员必须参与专业会签,未勾选的人员为可选会签(按需参加)
|
||||||
|
</p>
|
||||||
|
<NCascader v-if="pickerStepId === s.id" :value="pick" :options="memberList"
|
||||||
|
placeholder="请选择人员" size="small" check-strategy="child" :show-path="false" expand-trigger="click"
|
||||||
|
class="w-56" :on-update:value="(v) => addMember(s, v)" />
|
||||||
|
|
||||||
|
<span v-if="s.members.length === 0" class="text-xs leading-7 text-slate-300">未预设</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<NSelect :value="approvers[item.id]" multiple :options="memberList" label-field="full_name" value-field="user_id"
|
|
||||||
@update:value="(v) => updateApprover(item.id, v)" />
|
|
||||||
</div>
|
</div>
|
||||||
<NButton v-if="flowList.length" type="primary" class="w-full" @click="finishApprover">完成设置</NButton>
|
|
||||||
</div>
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<NButton v-if="flowList.length" type="primary" class="w-full" @click="finishApprover">完成设置</NButton>
|
||||||
|
</template>
|
||||||
</NDrawerContent>
|
</NDrawerContent>
|
||||||
</NDrawer>
|
</NDrawer>
|
||||||
<!-- 附件在线预览弹窗 -->
|
<!-- 附件在线预览弹窗 -->
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
<!-- 审批流程设置 -->
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { computed, onMounted, reactive, ref } from 'vue';
|
|
||||||
import { setApproverApi } from '@/service/api/system';
|
|
||||||
|
|
||||||
const props = defineProps(['currentId','currentTemplate'])
|
|
||||||
const emit = defineEmits(['finish'])
|
|
||||||
|
|
||||||
const loading = ref<boolean>(false);
|
|
||||||
// 存储每个步骤选中的审核人 user_id
|
|
||||||
const approvers = reactive<Record<number, number>>({})
|
|
||||||
// 审批链
|
|
||||||
const approvalFlowText = computed(() => {
|
|
||||||
if (!list.value || list.value.length === 0) return '暂无步骤'
|
|
||||||
// 按 step_order 排序
|
|
||||||
const sorted = [...list.value].sort((a, b) => a.step_order - b.step_order)
|
|
||||||
return sorted.map(item => item.name).join(' → ')
|
|
||||||
})
|
|
||||||
const list = ref<any[]>(props.currentTemplate?.steps || []);
|
|
||||||
|
|
||||||
// 更新选中值
|
|
||||||
const updateApprover = (stepId: number, userId: number) => {
|
|
||||||
approvers[stepId] = userId
|
|
||||||
}
|
|
||||||
// 匹配审批人
|
|
||||||
function matchApprovers(steps: any[], mapping: Record<number, number>) {
|
|
||||||
const result = [];
|
|
||||||
for (const [stepId, userId] of Object.entries(mapping)) {
|
|
||||||
const step = steps.find(s => s.id === Number(stepId));
|
|
||||||
if (step) {
|
|
||||||
const member = step.members.filter((m: any) => m.user_id === userId);
|
|
||||||
if (member.length > 0) {
|
|
||||||
result.push({
|
|
||||||
step_id: step.id,
|
|
||||||
member: member
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
// 完成设置
|
|
||||||
const finishApprover = async () => {
|
|
||||||
const matched = matchApprovers(list.value, approvers);
|
|
||||||
loading.value = true;
|
|
||||||
const {error} = await setApproverApi(props.currentId,{steps: matched})
|
|
||||||
if(!error){
|
|
||||||
window.$message?.success("审批流程设置已保存");
|
|
||||||
emit('finish', matched, false)
|
|
||||||
}
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
list.value.forEach((item: any) => {
|
|
||||||
if (item.members && item.members.length > 0) {
|
|
||||||
// 优先选 is_sign=1 的成员 ,否则选第一个
|
|
||||||
const defaultMember = item.members.find((m: any) => m.is_sign === 1) || item.members[0]
|
|
||||||
approvers[item.id] = defaultMember.user_id
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="h-full space-y-4 overflow-y-auto">
|
|
||||||
<p class="text-xs leading-relaxed text-slate-400">审批链:{{ approvalFlowText }}。专业会签人由申请表「涉及专业」勾选自动增减,可在此调整人选;重要变更自动追加安全审查与分管领导终批。</p>
|
|
||||||
<div class="rounded-lg border p-3" v-for="(item,index) in list" :key="item.id">
|
|
||||||
<div class="mb-2 flex items-center gap-2 text-sm font-semibold text-slate-700">
|
|
||||||
<NTag type="info" size="small">{{Number(index)+1}}</NTag> {{item.name}} {{item.countersign ? '(专业会签)' : ''}}
|
|
||||||
</div>
|
|
||||||
<NSelect :value="approvers[item.id]" :options="item.members" label-field="user_name" value-field="user_id"
|
|
||||||
@update:value="(v) => updateApprover(item.id, v)" />
|
|
||||||
</div>
|
|
||||||
<NButton type="primary" class="w-full" :loading="loading" @click="finishApprover">完成设置</NButton>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
Reference in New Issue
Block a user