|
@@ -0,0 +1,496 @@
|
|
|
+<template>
|
|
|
+ <el-dialog
|
|
|
+ :title="editData ? (props.mode === 'view' ? '查看审批流程' : '编辑审批流程') : '新建审批流程'"
|
|
|
+ v-model="visible"
|
|
|
+ width="900px"
|
|
|
+ @close="onCancel"
|
|
|
+ >
|
|
|
+ <el-form :model="form" label-width="110px" :rules="rules" ref="formRef" :disabled="props.mode === 'view'">
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="流程名称" prop="name">
|
|
|
+ <el-input v-model="form.name" placeholder="请输入" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <!-- <el-col :span="12">
|
|
|
+ <el-form-item label="适用场景" prop="scene">
|
|
|
+ <el-input v-model="form.scene" placeholder="请输入" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col> -->
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="流程类型" prop="workflow_type">
|
|
|
+ <el-select v-model="form.workflow_type" placeholder="请选择流程类型" :disabled="props.mode === 'view'">
|
|
|
+ <el-option label="设备借用申请" value="0" />
|
|
|
+ <el-option label="设备维修申请" value="1" />
|
|
|
+ <el-option label="设备采购申请" value="2" />
|
|
|
+ <el-option label="其他申请" value="3" />
|
|
|
+ <el-option label="课堂借用申请" value="5" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-row :gutter="20">
|
|
|
+
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="流程描述" prop="description">
|
|
|
+ <el-input v-model="form.description" type="textarea" :rows="2" placeholder="请输入流程描述" :disabled="props.mode === 'view'" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="是否启用" prop="is_active">
|
|
|
+ <el-select v-model="form.is_active" placeholder="请选择启用还是禁用" :disabled="props.mode === 'view'">
|
|
|
+ <el-option label="启用" :value="true" />
|
|
|
+ <el-option label="禁用" :value="false" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-divider>条件选择</el-divider>
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="8">
|
|
|
+ <el-form-item label="借用天数大于" prop="condition.days">
|
|
|
+ <el-input-number v-model="form.condition.days" :min="1" controls-position="right" style="width: 100px" />
|
|
|
+ <span style="margin-left: 8px">天</span>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="8">
|
|
|
+ <el-form-item label="用户角色" prop="condition.roles">
|
|
|
+ <el-checkbox-group v-model="form.condition.roles">
|
|
|
+ <el-checkbox label="教师" value="teacher" />
|
|
|
+ <el-checkbox label="学生" value="student" />
|
|
|
+ <el-checkbox label="非本院师生" value="external" />
|
|
|
+ </el-checkbox-group>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="8">
|
|
|
+ <el-form-item label="设备标签" prop="condition.equipment">
|
|
|
+ <el-select v-model="form.condition.equipment" placeholder="请选择">
|
|
|
+ <el-option
|
|
|
+ v-for="(item, index) in resdevicelist"
|
|
|
+ :key="index"
|
|
|
+ :label="item.name"
|
|
|
+ :value="item.id"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="8">
|
|
|
+ <el-form-item label="借用类型" prop="condition.borrow_types">
|
|
|
+ <el-checkbox-group v-model="form.condition.borrow_types" :disabled="props.mode === 'view'">
|
|
|
+ <el-checkbox label="常规借用" value="0"></el-checkbox>
|
|
|
+ <el-checkbox label="课堂借用" value="1"></el-checkbox>
|
|
|
+ <el-checkbox label="特殊借用" value="2"></el-checkbox>
|
|
|
+ </el-checkbox-group>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="8">
|
|
|
+ <el-form-item label="借用方式" prop="condition.team_type">
|
|
|
+ <el-checkbox-group v-model="form.condition.team_type" :disabled="props.mode === 'view'">
|
|
|
+ <el-checkbox label="个人借用" value="0"></el-checkbox>
|
|
|
+ <el-checkbox label="团队借用" value="1"></el-checkbox>
|
|
|
+ </el-checkbox-group>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-divider>流程配置</el-divider>
|
|
|
+ <div class="workflow-steps">
|
|
|
+ <!-- 借用人 -->
|
|
|
+ <div class="workflow-step">
|
|
|
+ <el-card shadow="never" class="step-card borrower">
|
|
|
+ <div class="step-title">借用人</div>
|
|
|
+ </el-card>
|
|
|
+ <div class="step-line"></div>
|
|
|
+ </div>
|
|
|
+ <!-- 审批步骤 -->
|
|
|
+ <div class="workflow-step" v-for="(step, idx) in form.steps" :key="idx">
|
|
|
+ <el-card shadow="never" class="step-card">
|
|
|
+ <div class="step-title">
|
|
|
+ 审批步骤{{ idx + 1 }}
|
|
|
+ <el-button v-if="form.steps.length > 1 && idx !== 0" type="danger" size="small" @click="removeStep(idx)" style="margin-left: 10px">
|
|
|
+ <el-icon><Minus /></el-icon> 删除步骤
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ <div class="step-roles">
|
|
|
+ <div v-for="(role, ridx) in step.roles" :key="ridx" class="role-row" style="position: relative; padding: 0 20px">
|
|
|
+ <el-form-item
|
|
|
+ :label="'角色'"
|
|
|
+ :label-position="'left'"
|
|
|
+ label-width="100px"
|
|
|
+ :prop="`steps.${idx}.roles.${ridx}.name`"
|
|
|
+ :rules="[{ required: true, message: '请选择角色', trigger: 'change' }]"
|
|
|
+ >
|
|
|
+ <!-- <el-select v-model="role.name" placeholder="请选择角色" style="width: 300px" @change="onRoleChange(idx, ridx, $event)"> -->
|
|
|
+ <el-select v-model="role.name" placeholder="请选择角色" style="width: 300px">
|
|
|
+ <el-option label="老师" value="老师" />
|
|
|
+ <el-option label="管理员" value="管理员" />
|
|
|
+ <el-option label="学院领导" value="学院领导" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item
|
|
|
+ :label="'通过条件'"
|
|
|
+ :label-position="'left'"
|
|
|
+ :prop="`steps.${idx}.roles.${ridx}.passType`"
|
|
|
+ :rules="[{ required: true, message: '请选择通过条件', trigger: 'change' }]"
|
|
|
+ >
|
|
|
+ <el-radio-group v-model="role.passType">
|
|
|
+ <!-- <el-radio label="单人审核" value="0"></el-radio> -->
|
|
|
+ <el-radio label="1名通过即可" value="1"></el-radio>
|
|
|
+ <el-radio label="所有人员通过" value="2"></el-radio>
|
|
|
+ <el-radio label="部门负责人审核" value="3"></el-radio>
|
|
|
+ <el-radio label="角色审核" value="4"></el-radio>
|
|
|
+ <el-radio label="自动审核" value="5"></el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ <el-button
|
|
|
+
|
|
|
+ v-if="step.roles.length > 1 && ridx !== 0"
|
|
|
+ type="danger"
|
|
|
+ size="small"
|
|
|
+ @click="removeRole(idx, ridx)"
|
|
|
+ style="position: absolute; right: 0; top: 0"
|
|
|
+ >
|
|
|
+ <el-icon><Minus /></el-icon> 删除角色
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ <div class="addRole" style="width: 100%; display: flex; justify-content: center">
|
|
|
+ <el-button type="primary" size="small" @click="addRole(idx)" :disabled="step.roles.length >= 5" v-if="props.mode !== 'view'">
|
|
|
+ <el-icon><Plus /></el-icon> 添加角色
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ <div class="step-line"></div>
|
|
|
+ </div>
|
|
|
+ <!-- 添加步骤按钮 -->
|
|
|
+ <div style="text-align: center; margin: 16px 0">
|
|
|
+ <el-button type="primary" @click="addStep" :disabled="form.steps.length >= 5" v-if="props.mode !== 'view'">
|
|
|
+ <el-icon><Plus /></el-icon> 添加审批步骤
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ <!-- 审批完成 -->
|
|
|
+ <div class="workflow-step">
|
|
|
+ <el-card shadow="never" class="step-card done">
|
|
|
+ <div class="step-title">审批完成</div>
|
|
|
+ </el-card>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="onCancel">取消</el-button>
|
|
|
+ <el-button v-if="props.mode !== 'view'" type="primary" @click="onSave">保存</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, watch, reactive ,nextTick } from 'vue';
|
|
|
+import { ElMessage } from 'element-plus';
|
|
|
+import { Plus, Minus } from '@element-plus/icons-vue';
|
|
|
+import * as api from '../api';
|
|
|
+import * as firstapi from './api';
|
|
|
+
|
|
|
+
|
|
|
+const props = defineProps<{
|
|
|
+ visible: boolean;
|
|
|
+ editData?: any;
|
|
|
+ mode?: 'view' | 'edit' | 'add';
|
|
|
+}>();
|
|
|
+const emit = defineEmits(['update:visible', 'success']);
|
|
|
+
|
|
|
+const visible = ref(props.visible);
|
|
|
+watch(
|
|
|
+ () => props.visible,
|
|
|
+ (v) => (visible.value = v)
|
|
|
+);
|
|
|
+watch(visible, (v) => emit('update:visible', v));
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+const resdevicelist=reactive([]);
|
|
|
+firstapi.GetDeviceClassList()
|
|
|
+ .then(result => {
|
|
|
+ const deviceData = result.data;
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
+ console.log("获取到的data值:", deviceData);
|
|
|
+ // 清空并填充响应式数组
|
|
|
+ resdevicelist.splice(0, resdevicelist.length, ...deviceData);
|
|
|
+ })
|
|
|
+ .catch(error => {
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
+ console.error("请求失败:", error);
|
|
|
+});
|
|
|
+
|
|
|
+const defaultRole = () => ({
|
|
|
+ name: '',
|
|
|
+ passType: '',
|
|
|
+});
|
|
|
+const defaultStep = () => ({
|
|
|
+ roles: [defaultRole()],
|
|
|
+});
|
|
|
+
|
|
|
+const form = reactive({
|
|
|
+ name: '',
|
|
|
+ scene: '',
|
|
|
+ workflow_type: "0",
|
|
|
+ description: '',
|
|
|
+ is_active: true,
|
|
|
+ condition: {
|
|
|
+ days: 1,
|
|
|
+ roles: [],
|
|
|
+ equipment: [],
|
|
|
+ borrow_types: [],
|
|
|
+ team_type:[],
|
|
|
+ },
|
|
|
+ steps: [defaultStep()],
|
|
|
+});
|
|
|
+
|
|
|
+const rules = reactive({
|
|
|
+ name: [{ required: true, message: '请输入流程名称', trigger: 'blur' }],
|
|
|
+ workflow_type: [{ required: true, message: '请选择流程类型', trigger: 'change' }],
|
|
|
+ description: [{ required: true, message: '请输入流程描述', trigger: 'blur' }],
|
|
|
+ is_active:[{ required: true, message: '请输选择是否启用', trigger: 'blur' }],
|
|
|
+ 'condition.days': [{ required: true, message: '请输入借用天数', trigger: 'change' }],
|
|
|
+ 'condition.roles': [{ required: true, message: '请选择用户角色', trigger: 'change' }],
|
|
|
+ 'condition.equipment': [{ required: true, message: '请选择设备标签', trigger: 'change' }],
|
|
|
+ 'condition.borrow_types': [{ required: true, message: '请选择借用类型', trigger: 'change' }],
|
|
|
+ 'condition.team_type': [{ required: true, message: '请选择借用方式', trigger: 'change' }],
|
|
|
+});
|
|
|
+
|
|
|
+const formRef = ref();
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+function generateStepRules() {
|
|
|
+ form.steps.forEach((step, stepIdx) => {
|
|
|
+ step.roles.forEach((role, roleIdx) => {
|
|
|
+ rules[`steps.${stepIdx}.roles.${roleIdx}.name`] = [
|
|
|
+ { required: true, message: '请选择角色', trigger: 'change' },
|
|
|
+ ];
|
|
|
+ rules[`steps.${stepIdx}.roles.${roleIdx}.passType`] = [
|
|
|
+ { required: true, message: '请选择通过条件', trigger: 'change' },
|
|
|
+ ];
|
|
|
+ });
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+const editId=ref('');
|
|
|
+
|
|
|
+watch(
|
|
|
+ () => props.editData,
|
|
|
+ async (val) => {
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
+ console.error("编辑时获取到的值val:", val);
|
|
|
+ if (val) {
|
|
|
+ // Object.assign(form, JSON.parse(JSON.stringify(val)));
|
|
|
+ const cloned = JSON.parse(JSON.stringify(val));
|
|
|
+ form.name = cloned.name || '';
|
|
|
+ form.scene = cloned.scene || '';
|
|
|
+ form.workflow_type = String(cloned.workflow_type ?? '0');
|
|
|
+ form.description = cloned.description || '';
|
|
|
+ form.is_active = cloned.is_active ?? true;
|
|
|
+
|
|
|
+ const tc = cloned.trigger_conditions || {};
|
|
|
+ form.condition = {
|
|
|
+ days: 1,
|
|
|
+ roles: tc.user_types || [],
|
|
|
+ equipment: Array.isArray(tc.equipment_categories) ? tc.equipment_categories[0] : '',
|
|
|
+ borrow_types: tc.borrow_types || [],
|
|
|
+ team_type: tc.team_type || [],
|
|
|
+ };
|
|
|
+
|
|
|
+ // 获取工作流步骤信息
|
|
|
+ const workflowSteps = await firstapi.GetInfo(cloned.id);
|
|
|
+ if (workflowSteps && workflowSteps.data) {
|
|
|
+ editId.value = workflowSteps.data[0].id;
|
|
|
+ form.steps = workflowSteps.data.map((step: any) => ({
|
|
|
+ roles: [{
|
|
|
+ name: step.step_name || '',
|
|
|
+ passType: String(step.step_type ?? '1'),
|
|
|
+ }],
|
|
|
+ }));
|
|
|
+ } else {
|
|
|
+ form.steps = [defaultStep()];
|
|
|
+ }
|
|
|
+
|
|
|
+ generateStepRules();
|
|
|
+ } else {
|
|
|
+ form.name = '';
|
|
|
+ form.scene = '';
|
|
|
+ form.workflow_type = "0";
|
|
|
+ form.description = '';
|
|
|
+ form.is_active = true;
|
|
|
+ form.condition = { days: 1, roles: [], equipment: [], borrow_types: [],team_type:[] };
|
|
|
+ form.steps = [defaultStep()];
|
|
|
+ }
|
|
|
+ generateStepRules();
|
|
|
+
|
|
|
+ await nextTick();
|
|
|
+ },
|
|
|
+ { immediate: true }
|
|
|
+);
|
|
|
+
|
|
|
+function addStep() {
|
|
|
+ if (form.steps.length >= 5) {
|
|
|
+ ElMessage.warning('最多只能添加 5 个审批步骤');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ form.steps.push(defaultStep());
|
|
|
+}
|
|
|
+function removeStep(idx: number) {
|
|
|
+ if (form.steps.length > 1) form.steps.splice(idx, 1);
|
|
|
+}
|
|
|
+function addRole(stepIdx: number) {
|
|
|
+ const step = form.steps[stepIdx];
|
|
|
+ if (step.roles.length >= 5) {
|
|
|
+ ElMessage.warning('每个步骤最多只能添加 5 个角色');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ form.steps[stepIdx].roles.push(defaultRole());
|
|
|
+}
|
|
|
+function removeRole(stepIdx: number, roleIdx: number) {
|
|
|
+ if (form.steps[stepIdx].roles.length > 1) form.steps[stepIdx].roles.splice(roleIdx, 1);
|
|
|
+}
|
|
|
+function onCancel() {
|
|
|
+ visible.value = false;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+const roleNameToId: { [key: string]: number } = {
|
|
|
+ '管理员': 1,
|
|
|
+ '指导老师': 2
|
|
|
+};
|
|
|
+
|
|
|
+
|
|
|
+async function onSave() {
|
|
|
+
|
|
|
+ try {
|
|
|
+ await formRef.value?.validate();
|
|
|
+ } catch (err) {
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
+ console.warn('表单验证失败:', err);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const isEditMode = !!props.editData;
|
|
|
+
|
|
|
+ // 组装接口需要的数据
|
|
|
+ const payload = {
|
|
|
+ name: form.name,
|
|
|
+ workflow_type: form.workflow_type,
|
|
|
+ description: form.description,
|
|
|
+ is_active: typeof form.is_active === 'boolean' ? form.is_active : true,
|
|
|
+ trigger_conditions: {
|
|
|
+ equipment_categories: form.condition?.equipment ? [form.condition.equipment] : ['贵重设备'],
|
|
|
+ borrow_types: form.condition?.borrow_types?.length ? form.condition.borrow_types : ['0', '1', '2'],
|
|
|
+ team_type: form.condition?.team_type?.length ? form.condition.team_type : ['0', '1'],
|
|
|
+ user_types: form.condition?.roles?.length ? form.condition.roles:['student', 'teacher'],
|
|
|
+ },
|
|
|
+ };
|
|
|
+
|
|
|
+ let workflowId = '';
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
+ // console.log(payload);
|
|
|
+ if (isEditMode) {
|
|
|
+ console.log(editId.value);
|
|
|
+ const editRes = await api.UpdateObj(editId.value, payload);
|
|
|
+ workflowId = editId.value;
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
+ console.log(editRes);
|
|
|
+ if (!editRes || editRes.code !== 2000) {
|
|
|
+ ElMessage.error('编辑流程失败');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }else{
|
|
|
+ //创建审批
|
|
|
+ const res = await api.AddObj(payload);
|
|
|
+ workflowId = res?.data?.id;
|
|
|
+ if (!workflowId) {
|
|
|
+ ElMessage.error('新增未能获取流程 ID');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ for (let i = 0; i < form.steps.length; i++) {
|
|
|
+ const step = form.steps[i];
|
|
|
+ const stepOrder = i + 1;
|
|
|
+
|
|
|
+ const roleNames = step.roles.map(role => role.name).filter(Boolean);
|
|
|
+ const passTypes = step.roles.map(role => role.passType).filter(Boolean);
|
|
|
+
|
|
|
+ const stepType = passTypes[0] || '1';
|
|
|
+
|
|
|
+ const approverRoles = roleNames.map(name => roleNameToId[name]).filter(Boolean);
|
|
|
+
|
|
|
+ const stepPayload = {
|
|
|
+ workflow: String(workflowId),
|
|
|
+ step_name: `${roleNames.join(',')}审批`,
|
|
|
+ step_order: stepOrder,
|
|
|
+ step_type: stepType,
|
|
|
+ description: `由${roleNames.join(',')}进行审批`,
|
|
|
+ approvers: [1], // 可选:你可以根据需要填入具体 ID
|
|
|
+ approver_roles: approverRoles,
|
|
|
+ approver_depts: [],
|
|
|
+ approval_conditions: {
|
|
|
+ required_fields: [],
|
|
|
+ value_ranges: {}
|
|
|
+ },
|
|
|
+ timeout_hours: 24,
|
|
|
+ timeout_action: "auto_reject",
|
|
|
+ is_active: true
|
|
|
+ };
|
|
|
+
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
+ console.log(`步骤 ${stepOrder} payload`, stepPayload);
|
|
|
+ if (isEditMode) {
|
|
|
+ await api.UpdateFirstObj(props.editData.id,stepPayload);
|
|
|
+ }else{
|
|
|
+ await api.AddFirstObj(stepPayload);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ ElMessage.success('保存成功');
|
|
|
+ emit('success');
|
|
|
+ visible.value = false;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.workflow-steps {
|
|
|
+ margin: 0 40px;
|
|
|
+}
|
|
|
+.workflow-step {
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+.step-card {
|
|
|
+ margin-bottom: 0;
|
|
|
+}
|
|
|
+.borrower {
|
|
|
+ background: #e3f0ff;
|
|
|
+}
|
|
|
+.done {
|
|
|
+ background: #eee;
|
|
|
+}
|
|
|
+.step-title {
|
|
|
+ font-weight: bold;
|
|
|
+ font-size: 16px;
|
|
|
+ margin-bottom: 8px;
|
|
|
+}
|
|
|
+.step-line {
|
|
|
+ width: 4px;
|
|
|
+ height: 32px;
|
|
|
+ background: #409eff;
|
|
|
+ margin: 0 auto;
|
|
|
+}
|
|
|
+.role-row {
|
|
|
+ margin-bottom: 8px;
|
|
|
+}
|
|
|
+</style>
|