index.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. <template>
  2. <el-dialog
  3. :title="editData ? (props.mode === 'view' ? '查看审批流程' : '编辑审批流程') : '新建审批流程'"
  4. v-model="visible"
  5. width="900px"
  6. @close="onCancel"
  7. >
  8. <el-form :model="form" label-width="110px" :rules="rules" ref="formRef" :disabled="props.mode === 'view'">
  9. <el-row :gutter="20">
  10. <el-col :span="12">
  11. <el-form-item label="流程名称" prop="name">
  12. <el-input v-model="form.name" placeholder="请输入" />
  13. </el-form-item>
  14. </el-col>
  15. <!-- <el-col :span="12">
  16. <el-form-item label="适用场景" prop="scene">
  17. <el-input v-model="form.scene" placeholder="请输入" />
  18. </el-form-item>
  19. </el-col> -->
  20. <el-col :span="12">
  21. <el-form-item label="流程类型" prop="workflow_type">
  22. <el-select v-model="form.workflow_type" placeholder="请选择流程类型" :disabled="props.mode === 'view'">
  23. <el-option label="设备借用申请" value="0" />
  24. <el-option label="设备维修申请" value="1" />
  25. <el-option label="设备采购申请" value="2" />
  26. <el-option label="其他申请" value="3" />
  27. <el-option label="课堂借用申请" value="5" />
  28. </el-select>
  29. </el-form-item>
  30. </el-col>
  31. </el-row>
  32. <el-row :gutter="20">
  33. <el-col :span="12">
  34. <el-form-item label="流程描述" prop="description">
  35. <el-input v-model="form.description" type="textarea" :rows="2" placeholder="请输入流程描述" :disabled="props.mode === 'view'" />
  36. </el-form-item>
  37. </el-col>
  38. <el-col :span="12">
  39. <el-form-item label="是否启用" prop="is_active">
  40. <el-select v-model="form.is_active" placeholder="请选择启用还是禁用" :disabled="props.mode === 'view'">
  41. <el-option label="启用" :value="true" />
  42. <el-option label="禁用" :value="false" />
  43. </el-select>
  44. </el-form-item>
  45. </el-col>
  46. </el-row>
  47. <el-divider>条件选择</el-divider>
  48. <el-row :gutter="20">
  49. <el-col :span="8">
  50. <el-form-item label="借用天数大于" prop="condition.days">
  51. <el-input-number v-model="form.condition.days" :min="1" controls-position="right" style="width: 100px" />
  52. <span style="margin-left: 8px">天</span>
  53. </el-form-item>
  54. </el-col>
  55. <el-col :span="8">
  56. <el-form-item label="用户角色" prop="condition.roles">
  57. <el-checkbox-group v-model="form.condition.roles">
  58. <el-checkbox label="教师" value="teacher" />
  59. <el-checkbox label="学生" value="student" />
  60. <el-checkbox label="非本院师生" value="external" />
  61. </el-checkbox-group>
  62. </el-form-item>
  63. </el-col>
  64. <el-col :span="8">
  65. <el-form-item label="设备标签" prop="condition.equipment">
  66. <el-select v-model="form.condition.equipment" placeholder="请选择">
  67. <el-option
  68. v-for="(item, index) in resdevicelist"
  69. :key="index"
  70. :label="item.name"
  71. :value="item.id"
  72. />
  73. </el-select>
  74. </el-form-item>
  75. </el-col>
  76. <el-col :span="8">
  77. <el-form-item label="借用类型" prop="condition.borrow_types">
  78. <el-checkbox-group v-model="form.condition.borrow_types" :disabled="props.mode === 'view'">
  79. <el-checkbox label="常规借用" value="0"></el-checkbox>
  80. <el-checkbox label="课堂借用" value="1"></el-checkbox>
  81. <el-checkbox label="特殊借用" value="2"></el-checkbox>
  82. </el-checkbox-group>
  83. </el-form-item>
  84. </el-col>
  85. <el-col :span="8">
  86. <el-form-item label="借用方式" prop="condition.team_type">
  87. <el-checkbox-group v-model="form.condition.team_type" :disabled="props.mode === 'view'">
  88. <el-checkbox label="个人借用" value="0"></el-checkbox>
  89. <el-checkbox label="团队借用" value="1"></el-checkbox>
  90. </el-checkbox-group>
  91. </el-form-item>
  92. </el-col>
  93. </el-row>
  94. <el-divider>流程配置</el-divider>
  95. <div class="workflow-steps">
  96. <!-- 借用人 -->
  97. <div class="workflow-step">
  98. <el-card shadow="never" class="step-card borrower">
  99. <div class="step-title">借用人</div>
  100. </el-card>
  101. <div class="step-line"></div>
  102. </div>
  103. <!-- 审批步骤 -->
  104. <div class="workflow-step" v-for="(step, idx) in form.steps" :key="idx">
  105. <el-card shadow="never" class="step-card">
  106. <div class="step-title">
  107. 审批步骤{{ idx + 1 }}
  108. <el-button v-if="form.steps.length > 1 && idx !== 0" type="danger" size="small" @click="removeStep(idx)" style="margin-left: 10px">
  109. <el-icon><Minus /></el-icon> 删除步骤
  110. </el-button>
  111. </div>
  112. <div class="step-roles">
  113. <div v-for="(role, ridx) in step.roles" :key="ridx" class="role-row" style="position: relative; padding: 0 20px">
  114. <el-form-item
  115. :label="'角色'"
  116. :label-position="'left'"
  117. label-width="100px"
  118. :prop="`steps.${idx}.roles.${ridx}.name`"
  119. :rules="[{ required: true, message: '请选择角色', trigger: 'change' }]"
  120. >
  121. <!-- <el-select v-model="role.name" placeholder="请选择角色" style="width: 300px" @change="onRoleChange(idx, ridx, $event)"> -->
  122. <el-select v-model="role.name" placeholder="请选择角色" style="width: 300px">
  123. <el-option label="老师" value="老师" />
  124. <el-option label="管理员" value="管理员" />
  125. <el-option label="学院领导" value="学院领导" />
  126. </el-select>
  127. </el-form-item>
  128. <el-form-item
  129. :label="'通过条件'"
  130. :label-position="'left'"
  131. :prop="`steps.${idx}.roles.${ridx}.passType`"
  132. :rules="[{ required: true, message: '请选择通过条件', trigger: 'change' }]"
  133. >
  134. <el-radio-group v-model="role.passType">
  135. <!-- <el-radio label="单人审核" value="0"></el-radio> -->
  136. <el-radio label="1名通过即可" value="1"></el-radio>
  137. <el-radio label="所有人员通过" value="2"></el-radio>
  138. <el-radio label="部门负责人审核" value="3"></el-radio>
  139. <el-radio label="角色审核" value="4"></el-radio>
  140. <el-radio label="自动审核" value="5"></el-radio>
  141. </el-radio-group>
  142. </el-form-item>
  143. <el-button
  144. v-if="step.roles.length > 1 && ridx !== 0"
  145. type="danger"
  146. size="small"
  147. @click="removeRole(idx, ridx)"
  148. style="position: absolute; right: 0; top: 0"
  149. >
  150. <el-icon><Minus /></el-icon> 删除角色
  151. </el-button>
  152. </div>
  153. <div class="addRole" style="width: 100%; display: flex; justify-content: center">
  154. <el-button type="primary" size="small" @click="addRole(idx)" :disabled="step.roles.length >= 5" v-if="props.mode !== 'view'">
  155. <el-icon><Plus /></el-icon> 添加角色
  156. </el-button>
  157. </div>
  158. </div>
  159. </el-card>
  160. <div class="step-line"></div>
  161. </div>
  162. <!-- 添加步骤按钮 -->
  163. <div style="text-align: center; margin: 16px 0">
  164. <el-button type="primary" @click="addStep" :disabled="form.steps.length >= 5" v-if="props.mode !== 'view'">
  165. <el-icon><Plus /></el-icon> 添加审批步骤
  166. </el-button>
  167. </div>
  168. <!-- 审批完成 -->
  169. <div class="workflow-step">
  170. <el-card shadow="never" class="step-card done">
  171. <div class="step-title">审批完成</div>
  172. </el-card>
  173. </div>
  174. </div>
  175. </el-form>
  176. <template #footer>
  177. <el-button @click="onCancel">取消</el-button>
  178. <el-button v-if="props.mode !== 'view'" type="primary" @click="onSave">保存</el-button>
  179. </template>
  180. </el-dialog>
  181. </template>
  182. <script setup lang="ts">
  183. import { ref, watch, reactive ,nextTick } from 'vue';
  184. import { ElMessage } from 'element-plus';
  185. import { Plus, Minus } from '@element-plus/icons-vue';
  186. import * as api from '../api';
  187. import * as firstapi from './api';
  188. const props = defineProps<{
  189. visible: boolean;
  190. editData?: any;
  191. mode?: 'view' | 'edit' | 'add';
  192. }>();
  193. const emit = defineEmits(['update:visible', 'success']);
  194. const visible = ref(props.visible);
  195. watch(
  196. () => props.visible,
  197. (v) => (visible.value = v)
  198. );
  199. watch(visible, (v) => emit('update:visible', v));
  200. const resdevicelist=reactive([]);
  201. firstapi.GetDeviceClassList()
  202. .then(result => {
  203. const deviceData = result.data;
  204. // eslint-disable-next-line no-console
  205. console.log("获取到的data值:", deviceData);
  206. // 清空并填充响应式数组
  207. resdevicelist.splice(0, resdevicelist.length, ...deviceData);
  208. })
  209. .catch(error => {
  210. // eslint-disable-next-line no-console
  211. console.error("请求失败:", error);
  212. });
  213. const defaultRole = () => ({
  214. name: '',
  215. passType: '',
  216. });
  217. const defaultStep = () => ({
  218. roles: [defaultRole()],
  219. });
  220. const form = reactive({
  221. name: '',
  222. scene: '',
  223. workflow_type: "0",
  224. description: '',
  225. is_active: true,
  226. condition: {
  227. days: 1,
  228. roles: [],
  229. equipment: [],
  230. borrow_types: [],
  231. team_type:[],
  232. },
  233. steps: [defaultStep()],
  234. });
  235. const rules = reactive({
  236. name: [{ required: true, message: '请输入流程名称', trigger: 'blur' }],
  237. workflow_type: [{ required: true, message: '请选择流程类型', trigger: 'change' }],
  238. description: [{ required: true, message: '请输入流程描述', trigger: 'blur' }],
  239. is_active:[{ required: true, message: '请输选择是否启用', trigger: 'blur' }],
  240. 'condition.days': [{ required: true, message: '请输入借用天数', trigger: 'change' }],
  241. 'condition.roles': [{ required: true, message: '请选择用户角色', trigger: 'change' }],
  242. 'condition.equipment': [{ required: true, message: '请选择设备标签', trigger: 'change' }],
  243. 'condition.borrow_types': [{ required: true, message: '请选择借用类型', trigger: 'change' }],
  244. 'condition.team_type': [{ required: true, message: '请选择借用方式', trigger: 'change' }],
  245. });
  246. const formRef = ref();
  247. function generateStepRules() {
  248. form.steps.forEach((step, stepIdx) => {
  249. step.roles.forEach((role, roleIdx) => {
  250. rules[`steps.${stepIdx}.roles.${roleIdx}.name`] = [
  251. { required: true, message: '请选择角色', trigger: 'change' },
  252. ];
  253. rules[`steps.${stepIdx}.roles.${roleIdx}.passType`] = [
  254. { required: true, message: '请选择通过条件', trigger: 'change' },
  255. ];
  256. });
  257. });
  258. }
  259. const editId=ref('');
  260. watch(
  261. () => props.editData,
  262. async (val) => {
  263. // eslint-disable-next-line no-console
  264. console.error("编辑时获取到的值val:", val);
  265. if (val) {
  266. // Object.assign(form, JSON.parse(JSON.stringify(val)));
  267. const cloned = JSON.parse(JSON.stringify(val));
  268. form.name = cloned.name || '';
  269. form.scene = cloned.scene || '';
  270. form.workflow_type = String(cloned.workflow_type ?? '0');
  271. form.description = cloned.description || '';
  272. form.is_active = cloned.is_active ?? true;
  273. const tc = cloned.trigger_conditions || {};
  274. form.condition = {
  275. days: 1,
  276. roles: tc.user_types || [],
  277. equipment: Array.isArray(tc.equipment_categories) ? tc.equipment_categories[0] : '',
  278. borrow_types: tc.borrow_types || [],
  279. team_type: tc.team_type || [],
  280. };
  281. // 获取工作流步骤信息
  282. const workflowSteps = await firstapi.GetInfo(cloned.id);
  283. if (workflowSteps && workflowSteps.data) {
  284. editId.value = workflowSteps.data[0].id;
  285. form.steps = workflowSteps.data.map((step: any) => ({
  286. roles: [{
  287. name: step.step_name || '',
  288. passType: String(step.step_type ?? '1'),
  289. }],
  290. }));
  291. } else {
  292. form.steps = [defaultStep()];
  293. }
  294. generateStepRules();
  295. } else {
  296. form.name = '';
  297. form.scene = '';
  298. form.workflow_type = "0";
  299. form.description = '';
  300. form.is_active = true;
  301. form.condition = { days: 1, roles: [], equipment: [], borrow_types: [],team_type:[] };
  302. form.steps = [defaultStep()];
  303. }
  304. generateStepRules();
  305. await nextTick();
  306. },
  307. { immediate: true }
  308. );
  309. function addStep() {
  310. if (form.steps.length >= 5) {
  311. ElMessage.warning('最多只能添加 5 个审批步骤');
  312. return;
  313. }
  314. form.steps.push(defaultStep());
  315. }
  316. function removeStep(idx: number) {
  317. if (form.steps.length > 1) form.steps.splice(idx, 1);
  318. }
  319. function addRole(stepIdx: number) {
  320. const step = form.steps[stepIdx];
  321. if (step.roles.length >= 5) {
  322. ElMessage.warning('每个步骤最多只能添加 5 个角色');
  323. return;
  324. }
  325. form.steps[stepIdx].roles.push(defaultRole());
  326. }
  327. function removeRole(stepIdx: number, roleIdx: number) {
  328. if (form.steps[stepIdx].roles.length > 1) form.steps[stepIdx].roles.splice(roleIdx, 1);
  329. }
  330. function onCancel() {
  331. visible.value = false;
  332. }
  333. const roleNameToId: { [key: string]: number } = {
  334. '管理员': 1,
  335. '指导老师': 2
  336. };
  337. async function onSave() {
  338. try {
  339. await formRef.value?.validate();
  340. } catch (err) {
  341. // eslint-disable-next-line no-console
  342. console.warn('表单验证失败:', err);
  343. return;
  344. }
  345. const isEditMode = !!props.editData;
  346. // 组装接口需要的数据
  347. const payload = {
  348. name: form.name,
  349. workflow_type: form.workflow_type,
  350. description: form.description,
  351. is_active: typeof form.is_active === 'boolean' ? form.is_active : true,
  352. trigger_conditions: {
  353. equipment_categories: form.condition?.equipment ? [form.condition.equipment] : ['贵重设备'],
  354. borrow_types: form.condition?.borrow_types?.length ? form.condition.borrow_types : ['0', '1', '2'],
  355. team_type: form.condition?.team_type?.length ? form.condition.team_type : ['0', '1'],
  356. user_types: form.condition?.roles?.length ? form.condition.roles:['student', 'teacher'],
  357. },
  358. };
  359. let workflowId = '';
  360. // eslint-disable-next-line no-console
  361. // console.log(payload);
  362. if (isEditMode) {
  363. console.log(editId.value);
  364. const editRes = await api.UpdateObj(editId.value, payload);
  365. workflowId = editId.value;
  366. // eslint-disable-next-line no-console
  367. console.log(editRes);
  368. if (!editRes || editRes.code !== 2000) {
  369. ElMessage.error('编辑流程失败');
  370. return;
  371. }
  372. }else{
  373. //创建审批
  374. const res = await api.AddObj(payload);
  375. workflowId = res?.data?.id;
  376. if (!workflowId) {
  377. ElMessage.error('新增未能获取流程 ID');
  378. return;
  379. }
  380. }
  381. for (let i = 0; i < form.steps.length; i++) {
  382. const step = form.steps[i];
  383. const stepOrder = i + 1;
  384. const roleNames = step.roles.map(role => role.name).filter(Boolean);
  385. const passTypes = step.roles.map(role => role.passType).filter(Boolean);
  386. const stepType = passTypes[0] || '1';
  387. const approverRoles = roleNames.map(name => roleNameToId[name]).filter(Boolean);
  388. const stepPayload = {
  389. workflow: String(workflowId),
  390. step_name: `${roleNames.join(',')}审批`,
  391. step_order: stepOrder,
  392. step_type: stepType,
  393. description: `由${roleNames.join(',')}进行审批`,
  394. approvers: [1], // 可选:你可以根据需要填入具体 ID
  395. approver_roles: approverRoles,
  396. approver_depts: [],
  397. approval_conditions: {
  398. required_fields: [],
  399. value_ranges: {}
  400. },
  401. timeout_hours: 24,
  402. timeout_action: "auto_reject",
  403. is_active: true
  404. };
  405. // eslint-disable-next-line no-console
  406. console.log(`步骤 ${stepOrder} payload`, stepPayload);
  407. if (isEditMode) {
  408. await api.UpdateFirstObj(props.editData.id,stepPayload);
  409. }else{
  410. await api.AddFirstObj(stepPayload);
  411. }
  412. }
  413. ElMessage.success('保存成功');
  414. emit('success');
  415. visible.value = false;
  416. }
  417. </script>
  418. <style scoped>
  419. .workflow-steps {
  420. margin: 0 40px;
  421. }
  422. .workflow-step {
  423. position: relative;
  424. }
  425. .step-card {
  426. margin-bottom: 0;
  427. }
  428. .borrower {
  429. background: #e3f0ff;
  430. }
  431. .done {
  432. background: #eee;
  433. }
  434. .step-title {
  435. font-weight: bold;
  436. font-size: 16px;
  437. margin-bottom: 8px;
  438. }
  439. .step-line {
  440. width: 4px;
  441. height: 32px;
  442. background: #409eff;
  443. margin: 0 auto;
  444. }
  445. .role-row {
  446. margin-bottom: 8px;
  447. }
  448. </style>