processSubmitDialog.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. <template>
  2. <ele-modal
  3. custom-class="ele-dialog-form long-dialog-form"
  4. :centered="true"
  5. :visible="processSubmitDialogFlag"
  6. :title="title"
  7. append-to-body
  8. :close-on-click-modal="false"
  9. :width="isRight ? '800px' : '450px'"
  10. :before-close="cancel"
  11. :maxable="true"
  12. :resizable="true"
  13. >
  14. <div style="display: flex; height: 100%; justify-content: space-between">
  15. <div class="form-box">
  16. <fm-generate-form
  17. :data="formSchema"
  18. :value="form.valueJson"
  19. :remote="remoteFuncs"
  20. ref="generateForm"
  21. class="el-form-box"
  22. ></fm-generate-form>
  23. </div>
  24. <div
  25. style="
  26. align-self: center;
  27. display: flex;
  28. color: #1890ff;
  29. cursor: pointer;
  30. width: 15px;
  31. "
  32. @click="() => (isRight = !isRight)"
  33. >
  34. <span
  35. style="align-self: center; transform: scale(1.5)"
  36. :class="isRight ? 'el-icon-caret-left' : 'el-icon-caret-right'"
  37. ></span>
  38. <span style="writing-mode: vertical-rl">选择流程</span>
  39. </div>
  40. <div style="flex: 1" v-if="isRight">
  41. <el-form
  42. ref="processForm"
  43. class="el-form-box"
  44. :model="form"
  45. label-width="80px"
  46. >
  47. <el-row>
  48. <el-col :span="12">
  49. <el-form-item label="流程分类">
  50. <ele-tree-select
  51. @change="changeLCFL"
  52. clearable
  53. ref="processTypeRef"
  54. filterable
  55. :data="LCFLList"
  56. v-model="form.LCFL"
  57. childrenKey="children"
  58. valueKey="id"
  59. labelKey="name"
  60. placeholder="请选择"
  61. default-expand-all
  62. />
  63. </el-form-item>
  64. </el-col>
  65. <el-col :span="12">
  66. <el-form-item label="发起流程">
  67. <el-select
  68. filterable
  69. v-model="form.FQLC"
  70. @change="changeFQLC"
  71. style="width: 100%"
  72. >
  73. <el-option
  74. v-for="item in processList"
  75. :key="item.id"
  76. :value="item.id"
  77. :label="item.name"
  78. ></el-option>
  79. </el-select>
  80. </el-form-item>
  81. </el-col>
  82. </el-row>
  83. <el-row>
  84. <el-col :span="12">
  85. <el-form-item label="流程名称">
  86. <el-input v-model="form.name" clearable></el-input>
  87. </el-form-item>
  88. </el-col>
  89. <el-col :span="12">
  90. <el-form-item label="流程标识">
  91. <el-input v-model="form.key" disabled></el-input>
  92. </el-form-item>
  93. </el-col>
  94. </el-row>
  95. <headerTitle
  96. title="流程执行人/流程图"
  97. style="margin-top: 30px"
  98. ></headerTitle>
  99. <el-table
  100. :data="datasource"
  101. height="150px"
  102. border
  103. style="margin-bottom: 10px"
  104. >
  105. <el-table-column
  106. label="任务节点"
  107. align="center"
  108. prop="taskDefinitionName"
  109. width="140"
  110. fixed
  111. />
  112. <el-table-column
  113. label="执行人"
  114. align="center"
  115. prop="options"
  116. min-width="140px"
  117. >
  118. <template v-slot="scope">
  119. <div
  120. v-if="
  121. scope.row.type !== 60 &&
  122. scope.row.type !== 70 &&
  123. scope.row.type !== 80 &&
  124. scope.row.options.length > 0
  125. "
  126. >
  127. <el-tag
  128. size="medium"
  129. :key="option"
  130. v-for="option in scope.row.options"
  131. >
  132. {{ getAssignRuleOptionName(scope.row, option) }}
  133. </el-tag>
  134. </div>
  135. <el-tag
  136. size="medium"
  137. v-if="
  138. scope.row.type === 60 ||
  139. scope.row.type === 70 ||
  140. scope.row.type === 80
  141. "
  142. >
  143. {{ getAssignRuleOptionName(scope.row) }}
  144. </el-tag>
  145. </template>
  146. </el-table-column>
  147. </el-table>
  148. <!-- 流程图展示 -->
  149. <my-process-viewer
  150. style="min-width: 100%; height: 200px"
  151. key="designer"
  152. v-model="bpmnXML"
  153. />
  154. </el-form>
  155. </div>
  156. </div>
  157. <!-- 底部:仅保留流程提交/关闭按钮 -->
  158. <div slot="footer">
  159. <el-button type="primary" size="small" @click="submit">提交</el-button>
  160. <el-button size="small" @click="cancel">关闭</el-button>
  161. </div>
  162. </ele-modal>
  163. </template>
  164. <script>
  165. // 引入流程核心依赖
  166. import {
  167. getModelPage,
  168. getProcessDefinitionBpmnXML,
  169. getTaskAssignRuleList,
  170. listAllUserBind,
  171. listSimpleUserGroups,
  172. getModel
  173. } from './api';
  174. import { treeClassifyCodeEnum } from '@/enum/dict';
  175. import { listRoles } from '@/api/system/role';
  176. import { listOrganizations } from '@/api/system/organization';
  177. import dictMixins from '@/mixins/dictMixins';
  178. import { getByCode } from '@/api/system/dictionary-data';
  179. import { getProduceTreeByCode } from '@/api/main';
  180. import { topLevel1, topLevel2, topLevel3 } from '@/enum/dict';
  181. import { getToken } from '@/utils/token-util';
  182. import { mapGetters } from 'vuex';
  183. export default {
  184. name: 'processSubmitDialog',
  185. mixins: [dictMixins],
  186. props: {
  187. processSubmitDialogFlag: {
  188. type: Boolean,
  189. default: false
  190. },
  191. formSchema: {
  192. type: Object,
  193. default: () => ({})
  194. },
  195. carByTemplate: {
  196. type: Object,
  197. default: () => ({})
  198. }
  199. },
  200. data() {
  201. return {
  202. title: '用车',
  203. isRight: false,
  204. remoteFuncs: {},
  205. // 流程核心数据:key默认值设为TYCSLC
  206. form: {
  207. LCFL: '', // 流程分类ID
  208. FQLC: '', // 发起流程ID(流程模型ID)
  209. processDefinitionId: '', // 流程定义ID
  210. name: '', // 流程名称
  211. key: 'TYCSLC', // 流程标识默认值
  212. valueJson: {}, // 表单提交数据
  213. processModelId: '' // 流程模型ID
  214. },
  215. // 流程基础选项列表
  216. LCFLList: [],
  217. processList: [],
  218. datasource: [],
  219. bpmnXML: null,
  220. // 执行人解析依赖数据
  221. roleOptions: [],
  222. deptOptions: [],
  223. userOptions: [],
  224. userGroupOptions: [],
  225. dictList: {}
  226. };
  227. },
  228. computed: {
  229. ...mapGetters(['user'])
  230. },
  231. async created() {
  232. // 1. 初始化基础数据(分类、角色、部门等)
  233. await this.initProcessBaseData();
  234. // 2. 通过流程标识(TYCSLC)查询关联的流程模型,自动赋值相关字段
  235. await this.initDefaultProcessByKey('TYCSLC');
  236. },
  237. methods: {
  238. /**
  239. * 1. 初始化流程基础数据(分类、角色、部门、用户组)
  240. */
  241. async initProcessBaseData() {
  242. // 加载流程分类列表
  243. const typeObj = await getProduceTreeByCode(
  244. treeClassifyCodeEnum['PROCESSTYPE']
  245. );
  246. this.LCFLList = typeObj[0]?.children || [];
  247. // 加载角色、部门、用户、用户组(用于执行人解析)
  248. await this.initBaseOptions();
  249. },
  250. /**
  251. * 2. 初始化角色/部门/用户/用户组选项
  252. */
  253. async initBaseOptions() {
  254. // 角色列表
  255. const roleRes = await listRoles({ current: 1, size: 9999 });
  256. this.roleOptions = roleRes.list || [];
  257. // 部门列表
  258. const deptRes = await listOrganizations();
  259. this.deptOptions = deptRes || [];
  260. // 用户列表
  261. const userRes = await listAllUserBind();
  262. this.userOptions = userRes || [];
  263. // 用户组列表
  264. const userGroupRes = await listSimpleUserGroups();
  265. this.userGroupOptions = userGroupRes || [];
  266. // 加载流程相关字典
  267. await this.getDictList(this.dictEnum['工作流任务分配自定义脚本']);
  268. await this.getDictList(this.dictEnum['工种类型']);
  269. },
  270. /**
  271. * 3. 新增:通过流程标识(key)查询关联流程模型,自动赋值分类、名称等字段
  272. * @param {string} targetKey - 目标流程标识(如TYCSLC)
  273. */
  274. async initDefaultProcessByKey(targetKey) {
  275. try {
  276. // 调用接口:按流程标识筛选流程模型(只查1条匹配数据)
  277. const { list } = await getModelPage({
  278. pageNo: 1,
  279. pageSize: 1,
  280. key: targetKey // 核心筛选条件:流程标识
  281. });
  282. if (list.length === 0) {
  283. this.$message.warning(`未找到标识为【${targetKey}】的流程模型,请检查配置`);
  284. return;
  285. }
  286. // 获取匹配的流程模型
  287. const defaultModel = list[0];
  288. // 自动赋值核心字段
  289. this.form.LCFL = defaultModel.category; // 流程分类(模型的分类ID)
  290. this.form.FQLC = defaultModel.id; // 发起流程(模型ID)
  291. this.form.name = defaultModel.name; // 流程名称(模型名称)
  292. this.form.processModelId = defaultModel.id; // 保存模型ID
  293. // 加载该分类下的流程列表(让“发起流程”下拉框显示可选项)
  294. await this.getProcessList(defaultModel.category);
  295. // 加载流程图和执行人列表(让默认流程显示完整信息)
  296. await this.changeFQLC(defaultModel.id);
  297. } catch (error) {
  298. console.error('通过流程标识初始化默认流程失败:', error);
  299. this.$message.error('默认流程加载失败,请手动选择流程');
  300. }
  301. },
  302. /**
  303. * 4. 切换流程分类:加载对应流程列表(修复清空key和name的问题)
  304. */
  305. async changeLCFL(val) {
  306. this.bpmnXML = null;
  307. this.form.processDefinitionId = '';
  308. this.form.FQLC = '';
  309. // 保留流程标识和名称,不清空
  310. // this.form.name = '';
  311. // this.form.key = '';
  312. // 加载当前分类下的流程列表
  313. await this.getProcessList(val);
  314. },
  315. /**
  316. * 5. 获取指定分类下的流程列表
  317. */
  318. async getProcessList(processTypeId) {
  319. const { list } = await getModelPage({
  320. pageNo: 1,
  321. pageSize: 999,
  322. processTypeId
  323. });
  324. // 过滤出有流程定义的有效流程
  325. this.processList = list.filter((item) => item.processDefinition);
  326. },
  327. /**
  328. * 6. 切换发起流程:加载流程图和执行人列表
  329. */
  330. async changeFQLC(val) {
  331. if (!val) return;
  332. const selectedProcess = this.processList.find((item) => item.id === val) || {};
  333. const processDef = selectedProcess.processDefinition || {};
  334. // 更新流程信息(选中流程时覆盖名称和标识)
  335. this.form.name = selectedProcess.name || this.title;
  336. this.form.key = selectedProcess.key || 'TYCSLC'; // 无key时 fallback 到默认值
  337. this.form.processDefinitionId = processDef.id || '';
  338. this.form.processModelId = val;
  339. // 加载流程图
  340. await this.getProcessDefinitionBpmnXMLInfo(val);
  341. // 加载执行人列表
  342. await this.getTaskAssignRuleListInfo({
  343. modelId: val,
  344. processDefinitionId: processDef.id
  345. });
  346. },
  347. /**
  348. * 7. 加载流程图XML数据
  349. */
  350. async getProcessDefinitionBpmnXMLInfo(modelId) {
  351. const res = await getModel(modelId);
  352. this.bpmnXML = res.bpmnXml;
  353. },
  354. /**
  355. * 8. 加载流程执行人列表
  356. */
  357. async getTaskAssignRuleListInfo(params) {
  358. this.datasource = await getTaskAssignRuleList(params);
  359. },
  360. /**
  361. * 9. 解析执行人显示名称
  362. */
  363. getAssignRuleOptionName(row, option) {
  364. if (row.type === 10) {
  365. const role = this.roleOptions.find((item) => item.id === option);
  366. return role?.name || `未知角色(${option})`;
  367. } else if (row.type === 20 || row.type === 21) {
  368. const dept = this.deptOptions.find((item) => item.id === option);
  369. return dept?.name || `未知部门(${option})`;
  370. } else if (row.type === 22) {
  371. return this.getDictV(this.dictEnum['工种类型'], option + '') || `未知工种(${option})`;
  372. } else if (row.type === 30 || row.type === 31 || row.type === 32) {
  373. const user = this.userOptions.find((item) => item.id === option);
  374. return user?.nickname || user?.name || `未知用户(${option})`;
  375. } else if (row.type === 40) {
  376. const group = this.userGroupOptions.find((item) => item.id === option);
  377. return group?.name || `未知用户组(${option})`;
  378. } else if (row.type === 50) {
  379. return this.getDictV(this.dictEnum['工作流任务分配自定义脚本'], option + '') || `未知脚本(${option})`;
  380. } else if (row.type === 60) {
  381. return row.variableName || '变量执行人';
  382. } else if (row.type === 70) {
  383. const data = JSON.parse(row.variableName || '{}');
  384. const levelList = data.direction === 1 ? topLevel2 : topLevel1;
  385. return levelList.find((item) => item.value === data.topLevel)?.label || '未知层级';
  386. } else if (row.type === 80) {
  387. const data = JSON.parse(row.variableName || '{}');
  388. return topLevel3.find((item) => item.value === data.topLevel)?.label || '未知层级';
  389. }
  390. return `未知类型(${option || '无'})`;
  391. },
  392. /**
  393. * 10. 获取字典标签
  394. */
  395. getDictV(dictCode, val) {
  396. if (!this.dictList[dictCode]) return '';
  397. return this.dictList[dictCode].find((item) => item.value === val)?.label || '';
  398. },
  399. /**
  400. * 11. 加载字典列表
  401. */
  402. async getDictList(dictCode) {
  403. const { data: res } = await getByCode(dictCode);
  404. this.dictList[dictCode] = res.map((item) => {
  405. const keys = Object.keys(item);
  406. return { value: keys[0], label: item[keys[0]] };
  407. });
  408. },
  409. /**
  410. * 12. 表单验证
  411. */
  412. generateFormValid(validate = true) {
  413. return this.$refs.generateForm.getData(validate).then((data) => data);
  414. },
  415. /**
  416. * 13. 提交流程
  417. */
  418. async submit() {
  419. try {
  420. this.form.valueJson = await this.generateFormValid();
  421. const submitData = {
  422. ...this.form,
  423. ...this.carByTemplate,
  424. processType: '1',
  425. variables: { ...this.form.valueJson }
  426. };
  427. this.$emit('formSubmit', submitData);
  428. this.cancel();
  429. } catch (error) {
  430. console.error('表单验证失败:', error);
  431. this.$message.warning('表单验证失败,请检查填写内容');
  432. }
  433. },
  434. /**
  435. * 14. 关闭弹窗:重置数据(保留key默认值)
  436. */
  437. cancel() {
  438. this.$emit('update:processSubmitDialogFlag', false);
  439. this.isRight = false;
  440. this.bpmnXML = null;
  441. this.datasource = [];
  442. // 重置时保留流程标识默认值TYCSLC
  443. this.form = {
  444. LCFL: '',
  445. FQLC: '',
  446. processDefinitionId: '',
  447. name: '',
  448. key: 'TYCSLC', // 保留默认值
  449. valueJson: {},
  450. processModelId: ''
  451. };
  452. }
  453. }
  454. };
  455. </script>
  456. <style scoped lang="scss">
  457. .form-box {
  458. min-width: 400px;
  459. overflow: auto;
  460. background: #4298fd0d;
  461. }
  462. ::v-deep .el-dialog {
  463. min-width: 400px;
  464. .el-dialog__header {
  465. background: #1890ffd6;
  466. }
  467. .el-dialog__title,
  468. .el-dialog__close {
  469. color: #ffffff;
  470. }
  471. }
  472. ::v-deep .el-table {
  473. margin-top: 10px;
  474. .el-table__header th {
  475. background: #f5f7fa;
  476. }
  477. }
  478. ::v-deep .bpmn-viewer {
  479. border: 1px solid #e5e7eb;
  480. border-radius: 4px;
  481. }
  482. </style>