审批模板管理接口完成
This commit is contained in:
@@ -23,6 +23,14 @@ export function editSystemConfigApi(id:number, data:{group_key:string, label:str
|
|||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// 批量修改ai配置项和变更编号规则
|
||||||
|
export function updateConfigApi(data:{items:any[]}) {
|
||||||
|
return request({
|
||||||
|
url: '/api/system-config/batch',
|
||||||
|
method: 'put',
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
// 删除配置项
|
// 删除配置项
|
||||||
export function deleteSystemConfigApi(id:number) {
|
export function deleteSystemConfigApi(id:number) {
|
||||||
return request({
|
return request({
|
||||||
@@ -45,12 +53,27 @@ export function peopleListApi() {
|
|||||||
method: 'get',
|
method: 'get',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// 新增审批模板
|
||||||
// 批量修改ai配置项和变更编号规则
|
export function addTemplateApi(data:any) {
|
||||||
export function updateConfigApi(data:{items:any[]}) {
|
|
||||||
return request({
|
return request({
|
||||||
url: '/api/system-config/batch',
|
url: '/api/approval-templates',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 编辑审批模板
|
||||||
|
export function editTemplateApi(id:number,data:any) {
|
||||||
|
return request({
|
||||||
|
url: `/api/approval-templates/${id}`,
|
||||||
method: 'put',
|
method: 'put',
|
||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// 删除审批模板
|
||||||
|
export function deleteTemplateApi(id:number) {
|
||||||
|
return request({
|
||||||
|
url: `/api/approval-templates/${id}`,
|
||||||
|
method: 'delete',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import { Icon } from '@iconify/vue'
|
|||||||
import { useThemeStore } from '@/store/modules/theme';
|
import { useThemeStore } from '@/store/modules/theme';
|
||||||
import { NButton, NPopconfirm, NTag } from 'naive-ui';
|
import { NButton, NPopconfirm, NTag } from 'naive-ui';
|
||||||
import { tagTextColor, tagBgColor, tagBorderColor } from '@/utils/common';
|
import { tagTextColor, tagBgColor, tagBorderColor } from '@/utils/common';
|
||||||
import { templateListApi, systemConfigApi, peopleListApi } from '@/service/api/system';
|
import { templateListApi, systemConfigApi, peopleListApi, addTemplateApi, editTemplateApi, deleteTemplateApi } from '@/service/api/system';
|
||||||
|
import { json } from 'node:stream/consumers';
|
||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const themeStore = useThemeStore();
|
const themeStore = useThemeStore();
|
||||||
@@ -24,10 +25,9 @@ const currentTemplate = ref<{ id: number, category: string | null, level: string
|
|||||||
steps: [],
|
steps: [],
|
||||||
});
|
});
|
||||||
const rules = ref({
|
const rules = ref({
|
||||||
category: { required: true, message: '变更类别不能为空', trigger: ['blur'] },
|
category: { required: true, message: '变更类别不能为空', trigger: ['blur', 'change'] },
|
||||||
level: { required: true, message: '变更等级不能为空', trigger: ['blur'] },
|
level: { required: true, message: '变更等级不能为空', trigger: ['blur', 'change'] },
|
||||||
duration: { required: true, message: '变更时效不能为空', trigger: ['blur'] },
|
duration: { type: 'number', required: true, message: '变更时效不能为空', trigger: ['blur', 'change'] },
|
||||||
step: { required: true, message: '审批步骤不能为空', trigger: ['blur'] }
|
|
||||||
})
|
})
|
||||||
const templateModal = ref<boolean>(false);
|
const templateModal = ref<boolean>(false);
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ const SPECIALTIES = [
|
|||||||
{ label: '质量', value: '质量' }
|
{ label: '质量', value: '质量' }
|
||||||
];
|
];
|
||||||
// 审批用户列表
|
// 审批用户列表
|
||||||
const peopleList = ref<{label: string, value: number,children: any[]}[]>([]);
|
const peopleList = ref<{label: string, value: string,children: any[]}[]>([]);
|
||||||
|
|
||||||
// 根据value匹配label和color
|
// 根据value匹配label和color
|
||||||
const getLabel = (type: string, value: number) => {
|
const getLabel = (type: string, value: number) => {
|
||||||
@@ -154,7 +154,7 @@ const columns = ref([
|
|||||||
positiveText: '确定',
|
positiveText: '确定',
|
||||||
negativeText: '取消',
|
negativeText: '取消',
|
||||||
onPositiveClick: () => {
|
onPositiveClick: () => {
|
||||||
handleDelete(row)
|
handleDelete(row.id)
|
||||||
},
|
},
|
||||||
// 可选:自定义确认/取消按钮的样式
|
// 可选:自定义确认/取消按钮的样式
|
||||||
positiveButtonProps: { size: 'tiny', type: 'primary' },
|
positiveButtonProps: { size: 'tiny', type: 'primary' },
|
||||||
@@ -208,64 +208,81 @@ const handleView = (row: any) => {
|
|||||||
// 编辑模板
|
// 编辑模板
|
||||||
const handleEdit = (row: any) => {
|
const handleEdit = (row: any) => {
|
||||||
currentType.value = 'edit';
|
currentType.value = 'edit';
|
||||||
currentTemplate.value = {
|
let obj = {
|
||||||
id: row.id,
|
id: row.id,
|
||||||
category: row.category.toString(),
|
category: row.category.toString(),
|
||||||
level: row.level.toString(),
|
level: row.level.toString(),
|
||||||
duration: row.duration,
|
duration: row.duration,
|
||||||
steps: row.steps,
|
steps: row.steps,
|
||||||
};
|
};
|
||||||
|
currentTemplate.value = JSON.parse(JSON.stringify(obj));
|
||||||
templateModal.value = true;
|
templateModal.value = true;
|
||||||
}
|
}
|
||||||
// 删除模板
|
// 删除模板
|
||||||
const handleDelete = (row: any) => {
|
const handleDelete = async (id: number) => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
setTimeout(() => {
|
const {error} = await deleteTemplateApi(id);
|
||||||
loading.value = false;
|
if(!error){
|
||||||
list.value = list.value.filter((item: any) => item.id !== row.id);
|
list.value = list.value.filter((item: any) => item.id !== id);
|
||||||
window.$message?.success('删除成功')
|
window.$message?.success('删除成功')
|
||||||
}, 1000)
|
}
|
||||||
|
loading.value = false;
|
||||||
}
|
}
|
||||||
// 保存模板
|
// 保存模板
|
||||||
const saveTemplate = async (e: MouseEvent) => {
|
const saveTemplate = async (e: MouseEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
formRef.value?.validate((errors: any) => {
|
formRef.value?.validate(async (errors: any) => {
|
||||||
if (!errors) {
|
if (!errors) {
|
||||||
|
if(currentTemplate.value.steps.length===0){
|
||||||
|
return window.$message?.warning('请添加审批步骤')
|
||||||
|
}
|
||||||
|
const hasEmptyName = currentTemplate.value.steps.some((item:any) => item.name === '');
|
||||||
|
if(hasEmptyName){
|
||||||
|
return window.$message?.warning('请填写审批步骤名称')
|
||||||
|
}
|
||||||
|
const hasEmptyMembers = currentTemplate.value.steps.some((item:any) => item.members.length=== 0);
|
||||||
|
if(hasEmptyMembers){
|
||||||
|
return window.$message?.warning('请添加审批步骤人员')
|
||||||
|
}
|
||||||
|
const hasCountersignTrue = currentTemplate.value.steps.some((item:any) => item.countersign===true);
|
||||||
|
if(hasCountersignTrue){
|
||||||
|
const hasEmptySpecialty = currentTemplate.value.steps
|
||||||
|
.filter((step:any) => step.countersign === true)
|
||||||
|
.some((step:any) => step.members.some((member:any) => member.specialty === ""));
|
||||||
|
if(hasEmptySpecialty){
|
||||||
|
return window.$message?.warning('请选择审批步骤人员专业')
|
||||||
|
}
|
||||||
|
}
|
||||||
btnLoading.value = true;
|
btnLoading.value = true;
|
||||||
setTimeout(() => {
|
|
||||||
// 新增
|
|
||||||
if(currentType.value === 'add'){
|
if(currentType.value === 'add'){
|
||||||
list.value.push({
|
const {error} = await addTemplateApi(currentTemplate.value);
|
||||||
id:list.value.length+1,
|
if(!error){
|
||||||
category:currentTemplate.value?.category as string,
|
window.$message?.success('添加成功')
|
||||||
level:currentTemplate.value?.level as string,
|
|
||||||
duration:currentTemplate.value?.duration as number,
|
|
||||||
steps:currentTemplate.value?.steps as any[]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// 编辑
|
|
||||||
if(currentType.value === 'edit'){
|
|
||||||
const target = list.value.find((item:any) => item.id === currentTemplate.value.id);
|
|
||||||
if (target) {
|
|
||||||
target.category = currentTemplate.value?.category as string;
|
|
||||||
target.level = currentTemplate.value?.level as string;
|
|
||||||
target.duration = currentTemplate.value?.duration as number;
|
|
||||||
target.steps = currentTemplate.value?.steps as any[];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
window.$message?.success('操作成功')
|
|
||||||
btnLoading.value = false;
|
|
||||||
templateModal.value = false;
|
templateModal.value = false;
|
||||||
}, 1000)
|
getList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(currentType.value === 'edit'){
|
||||||
|
let obj = JSON.parse(JSON.stringify(currentTemplate.value));
|
||||||
|
obj.category = Number(obj.category);
|
||||||
|
obj.level = Number(obj.level);
|
||||||
|
const {error} = await editTemplateApi(currentTemplate.value.id,obj);
|
||||||
|
if(!error){
|
||||||
|
window.$message?.success('编辑成功')
|
||||||
|
templateModal.value = false;
|
||||||
|
getList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
btnLoading.value = false;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// 会签步骤:一个模板最多一步;只有会签步骤能选专业。
|
|
||||||
// 勾选/取消后,所有非会签步骤的人员专业与「必须会签」标记一并清空
|
// 专业会签步骤勾选
|
||||||
const setStepCountersign = (target: any, v: boolean) => {
|
const setStepCountersign = (target: any, v: boolean) => {
|
||||||
currentTemplate.value.steps.forEach((s:any) => {
|
currentTemplate.value.steps.forEach((s:any) => {
|
||||||
s.countersign = s === target ? v : false;
|
s.countersign = s === target ? v : false;
|
||||||
if (!s.countersign) s.members.forEach((m:any) => { m.specialty = ""; m.countersign = false; });
|
if (!s.countersign) s.members.forEach((m:any) => { m.specialty = ""; m.is_sign = false; });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// 当前打开人员选择器的步骤(步骤行「+」按钮切换)
|
// 当前打开人员选择器的步骤(步骤行「+」按钮切换)
|
||||||
@@ -275,18 +292,39 @@ const togglePicker = (s: any) => {
|
|||||||
pickerStepId.value = pickerStepId.value === s.unique_id ? null : s.unique_id;
|
pickerStepId.value = pickerStepId.value === s.unique_id ? null : s.unique_id;
|
||||||
pick.value = null;
|
pick.value = null;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 在组织树中根据 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 addMember = (s: any, v: string | null) => {
|
const addMember = (s: any, v: string | null) => {
|
||||||
console.log(s,v)
|
|
||||||
if (v && !s.members.some((m:any) => m.user_name === v)){
|
if (v && !s.members.some((m:any) => m.user_name === v)){
|
||||||
s.members.push({ user_name: v, specialty: "", countersign: false });
|
let info = findUserInTree(peopleList.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;
|
pick.value = null;
|
||||||
pickerStepId.value = null;
|
pickerStepId.value = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 删除审批步骤人员
|
// 删除审批步骤人员
|
||||||
const delMember = (s: any, p: string) => {
|
const delMember = (s: any, p: number) => {
|
||||||
s.members = s.members.filter((m:any) => m.person !== p);
|
s.members = s.members.filter((m:any) => m.user_id !== p);
|
||||||
}
|
}
|
||||||
// 删除审批步骤
|
// 删除审批步骤
|
||||||
const removeStep = (idx: number) => {
|
const removeStep = (idx: number) => {
|
||||||
@@ -334,7 +372,6 @@ const getConfig = async () => {
|
|||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 人员数据转换
|
// 人员数据转换
|
||||||
const groupUsersByOrg = (users:any) => {
|
const groupUsersByOrg = (users:any) => {
|
||||||
const orgMap = new Map(); // key: org.id, value: { id, label, children }
|
const orgMap = new Map(); // key: org.id, value: { id, label, children }
|
||||||
@@ -357,13 +394,13 @@ const groupUsersByOrg = (users:any) => {
|
|||||||
// 避免同一个用户重复加入同一个组织(理论上不会发生,但做去重保护)
|
// 避免同一个用户重复加入同一个组织(理论上不会发生,但做去重保护)
|
||||||
if (!targetOrg.children.some((child:any) => child.id === userId)) {
|
if (!targetOrg.children.some((child:any) => child.id === userId)) {
|
||||||
targetOrg.children.push({
|
targetOrg.children.push({
|
||||||
value: userId,
|
value: userId.toString(),
|
||||||
label: realname
|
label: realname,
|
||||||
|
user_id: userId.toString()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return Array.from(orgMap.values());
|
return Array.from(orgMap.values());
|
||||||
}
|
}
|
||||||
// 获取审批用户列表
|
// 获取审批用户列表
|
||||||
@@ -371,12 +408,9 @@ const getPeopleList = async () => {
|
|||||||
const {data,error} = await peopleListApi();
|
const {data,error} = await peopleListApi();
|
||||||
if(!error){
|
if(!error){
|
||||||
peopleList.value = groupUsersByOrg(data);
|
peopleList.value = groupUsersByOrg(data);
|
||||||
console.log(groupUsersByOrg(data))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getPeopleList();
|
getPeopleList();
|
||||||
getConfig()
|
getConfig()
|
||||||
@@ -385,7 +419,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NSpace vertical :size="16">
|
<NSpace vertical :size="16">
|
||||||
<!-- 角色管理 -->
|
<!-- 审批模板管理 -->
|
||||||
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||||
<div class="border mb-3 py-2 px-3" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
<div class="border mb-3 py-2 px-3" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||||
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
|
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
|
||||||
@@ -443,7 +477,7 @@ onMounted(() => {
|
|||||||
placeholder="请选择"
|
placeholder="请选择"
|
||||||
/>
|
/>
|
||||||
</NFormItemGi>
|
</NFormItemGi>
|
||||||
<NFormItemGi :span="8" label="变更时效" path="timeLimit">
|
<NFormItemGi :span="8" label="变更时效" path="duration">
|
||||||
<NSelect
|
<NSelect
|
||||||
v-model:value="currentTemplate.duration"
|
v-model:value="currentTemplate.duration"
|
||||||
:options="durationOptions"
|
:options="durationOptions"
|
||||||
@@ -478,15 +512,19 @@ onMounted(() => {
|
|||||||
<span class="text-xs text-slate-400"><span class="mr-0.5 text-red-500">*</span>专业:</span>
|
<span class="text-xs text-slate-400"><span class="mr-0.5 text-red-500">*</span>专业:</span>
|
||||||
<NSelect :value="m.specialty || null" clearable size="small" placeholder="请选择专业" class="w-28"
|
<NSelect :value="m.specialty || null" clearable size="small" placeholder="请选择专业" class="w-28"
|
||||||
:options="SPECIALTIES"
|
:options="SPECIALTIES"
|
||||||
|
:disabled="readonly"
|
||||||
@update:value="(v) => m.specialty = v ?? null"
|
@update:value="(v) => m.specialty = v ?? null"
|
||||||
/>
|
/>
|
||||||
<NCheckbox :checked="m.is_sign" size="small"
|
<NCheckbox :checked="m.is_sign" size="small"
|
||||||
|
:disabled="readonly"
|
||||||
|
:checked-value="1"
|
||||||
|
:unchecked-value="0"
|
||||||
@update:checked="(v) => m.is_sign = v"
|
@update:checked="(v) => m.is_sign = v"
|
||||||
>
|
>
|
||||||
<span class="text-xs text-slate-500">必须参与会签</span>
|
<span class="text-xs text-slate-500">必须参与会签</span>
|
||||||
</NCheckbox>
|
</NCheckbox>
|
||||||
</template>
|
</template>
|
||||||
<NButton v-if="!readonly" text type="error" @click="delMember(s, m.user_name)" title="删除该人员">
|
<NButton v-if="!readonly" text type="error" @click="delMember(s, m.user_id)" title="删除该人员">
|
||||||
<Icon icon="iconamoon:trash" class="size-16px" />
|
<Icon icon="iconamoon:trash" class="size-16px" />
|
||||||
</NButton>
|
</NButton>
|
||||||
</div>
|
</div>
|
||||||
@@ -495,7 +533,7 @@ onMounted(() => {
|
|||||||
</p>
|
</p>
|
||||||
<NCascader v-if="!readonly && pickerStepId === s.unique_id" :value="pick" :options="peopleList"
|
<NCascader v-if="!readonly && pickerStepId === s.unique_id" :value="pick" :options="peopleList"
|
||||||
placeholder="请选择人员" size="small" check-strategy="child" :show-path="false" expand-trigger="click"
|
placeholder="请选择人员" size="small" check-strategy="child" :show-path="false" expand-trigger="click"
|
||||||
class="w-56" :on-update:value="(v) => addMember(s, v ? null : String(v))" />
|
class="w-56" :on-update:value="(v) => addMember(s, v)" />
|
||||||
|
|
||||||
<span v-if="readonly && s.members.length === 0" class="text-xs leading-7 text-slate-300">未预设</span>
|
<span v-if="readonly && s.members.length === 0" class="text-xs leading-7 text-slate-300">未预设</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user