Discern-edit.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. <template>
  2. <el-dialog
  3. v-model="state.dialogVisible"
  4. draggable
  5. :title="state.dialogTitle"
  6. ref="dialogRef"
  7. @mouseup="mouseup"
  8. :style="'transform: ' + state.transform + ';'"
  9. append-to-body
  10. destroy-on-close
  11. :close-on-click-modal="false"
  12. @close="close"
  13. >
  14. <el-steps :active="activeStep" align-center finish-status="success" class="mb20">
  15. <el-step title="业务表单" />
  16. <el-step title="流程表单" />
  17. </el-steps>
  18. <div v-show="activeStep === 0" v-loading="state.loading">
  19. <el-form :model="state.discernForm" label-width="110px" ref="discernFormRef">
  20. <el-row :gutter="10">
  21. <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
  22. <el-form-item label="工单编码"> {{ state.orderDetail.no }} </el-form-item>
  23. </el-col>
  24. <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
  25. <el-form-item label="工单标题"> {{ state.orderDetail.order?.title }} </el-form-item>
  26. </el-col>
  27. <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
  28. <el-form-item label="申请人"> {{ state.orderDetail.creatorName }} </el-form-item>
  29. </el-col>
  30. <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
  31. <el-form-item label="申请部门"> {{ state.orderDetail.creatorOrgName }} </el-form-item>
  32. </el-col>
  33. <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
  34. <el-form-item label="申请时间"> {{ dayjs(state.orderDetail.creationTime).format('YYYY-MM-DD HH:mm:ss') }} </el-form-item>
  35. </el-col>
  36. <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
  37. <el-form-item label="申请类型" prop="type" :rules="[{ required: true, message: '请选择申请类型', trigger: 'change' }]">
  38. <el-select v-model="state.discernForm.type" placeholder="请选择申请类型" class="w100" value-key="dicDataValue">
  39. <el-option v-for="item in screenTypeOptions" :value="item" :key="item.dicDataValue" :label="item.dicDataName" />
  40. </el-select>
  41. </el-form-item>
  42. </el-col>
  43. <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
  44. <el-form-item label="申请理由" prop="content" :rules="[{ required: true, message: '请填写甄别申请理由', trigger: 'blur' }]">
  45. <common-advice
  46. @chooseAdvice="chooseAdviceDiscern"
  47. v-model="state.discernForm.content"
  48. placeholder="请填写甄别申请理由"
  49. :loading="state.loading"
  50. :commonEnum="commonEnum.Discriminate"
  51. />
  52. </el-form-item>
  53. </el-col>
  54. <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
  55. <el-form-item label="附件">
  56. <annex-list
  57. name="甄别附件"
  58. ref="discernAnnexListRef"
  59. v-model:format="handleFilesDiscern"
  60. :businessId="state.orderDetail.orderId"
  61. v-model="state.ruleForm.files"
  62. classify="甄别上传"
  63. />
  64. </el-form-item>
  65. </el-col>
  66. </el-row>
  67. </el-form>
  68. </div>
  69. <el-form :model="state.ruleForm" label-width="110px" ref="ruleFormRef" v-show="activeStep === 1" v-loading="state.loading">
  70. <slot name="header"></slot>
  71. <el-row :gutter="10">
  72. <!-- 非退回流程都需要选择 -->
  73. <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
  74. <el-form-item label="下一环节" prop="nextStepCode" :rules="[{ required: true, message: '请选择下一环节', trigger: 'change' }]">
  75. <el-select v-model="state.ruleForm.nextStepCode" placeholder="请选择下一环节" class="w100" @change="selectNextStep">
  76. <el-option v-for="item in state.nextStepOptions" :key="item.key" :label="item.value" :value="item.key" />
  77. </el-select>
  78. <p class="flex-center-align color-danger" v-if="showFastSendOrder">
  79. 当前推荐派单办理对象:{{ fastStepName }} <el-button type="primary" link class="ml4" @click="fastSendOrder">快捷派单</el-button>
  80. </p>
  81. </el-form-item>
  82. </el-col>
  83. <!-- 非退回流程都需要选择并且如果选择了结束节点就不需要选择办理对象 -->
  84. <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" v-if="showHandlers">
  85. <el-form-item label="办理对象" prop="nextHandlers" :rules="[{ required: true, message: '请选择办理对象', trigger: 'change' }]">
  86. <el-select-v2
  87. v-model="state.ruleForm.nextHandlers"
  88. :options="state.handlerOptions"
  89. placeholder="请选择办理对象"
  90. class="w100"
  91. multiple
  92. clearable
  93. collapse-tags
  94. collapse-tags-tooltip
  95. filterable
  96. value-key="key"
  97. @change="selectHandlers"
  98. :multiple-limit="multipleLimit"
  99. />
  100. </el-form-item>
  101. </el-col>
  102. <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" v-if="showMainHandler">
  103. <el-form-item label="主办" prop="nextMainHandler" :rules="[{ required: false, message: '请选择主办', trigger: 'change' }]">
  104. <el-select v-model="state.ruleForm.nextMainHandler" placeholder="请选择主办" class="w100" filterable>
  105. <el-option v-for="item in state.handlerMainOptions" :key="item.key" :label="item.value" :value="item.key" />
  106. </el-select>
  107. </el-form-item>
  108. </el-col>
  109. <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" v-if="countersignAble">
  110. <el-form-item label="发起会签" prop="isStartCountersign" :rules="[{ required: false, message: '请选择发起会签', trigger: 'change' }]">
  111. <el-switch
  112. v-model="state.ruleForm.isStartCountersign"
  113. inline-prompt
  114. active-text="是"
  115. inactive-text="否"
  116. @change="changeStartCountersign"
  117. :disabled="countersignDisabled"
  118. />
  119. </el-form-item>
  120. </el-col>
  121. <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
  122. <el-form-item label="" prop="isSms">
  123. <el-checkbox v-model="state.ruleForm.isSms" label="短信通知" />
  124. </el-form-item>
  125. </el-col>
  126. </el-row>
  127. </el-form>
  128. <template #footer>
  129. <span class="dialog-footer">
  130. <el-button @click="closeDialog" class="default-button">取 消</el-button>
  131. <el-button class="default-button" @click="onPrevious" :loading="state.loading" v-if="activeStep === 1">上一步</el-button>
  132. <el-button class="default-button" @click="onNext" :loading="state.loading" v-if="activeStep === 0">下一步</el-button>
  133. <el-button type="primary" @click="onSubmit(ruleFormRef)" :loading="state.loading" v-if="activeStep === 1">办理</el-button>
  134. </span>
  135. </template>
  136. </el-dialog>
  137. </template>
  138. <script setup lang="ts" name="processApproval">
  139. import { computed, defineAsyncComponent, nextTick, reactive, ref, watch } from 'vue';
  140. import { ElMessage, ElMessageBox, FormInstance } from 'element-plus';
  141. import other from '@/utils/other';
  142. import { useUserInfo } from '@/stores/userInfo';
  143. import { storeToRefs } from 'pinia';
  144. import { commonEnum } from '@/utils/constants';
  145. import { discernUpdate, screenBaseData, screenDetail, workflowDiscernParams } from '@/api/business/discern';
  146. import { transformFile } from '@/utils/tools';
  147. import dayjs from 'dayjs';
  148. // 引入组件
  149. const CommonAdvice = defineAsyncComponent(() => import('@/components/CommonAdvice/index.vue')); // 常用意见
  150. const AnnexList = defineAsyncComponent(() => import('@/components/AnnexList/index.vue')); // 附件列表
  151. // 定义子组件向父组件传值/事件
  152. const emit = defineEmits(['updateList', 'orderProcessFailed']);
  153. // 定义变量内容
  154. const state = reactive<any>({
  155. dialogVisible: false, // 弹窗显示隐藏
  156. ruleForm: {
  157. isPass: true, // 审批结果
  158. //流程表单
  159. opinion: '', // 意见
  160. nextStepCode: '', // 下一节点
  161. nextStepName: '', // 下一节点名称
  162. backToCountersignEnd: false, // 是否回到会签结束节点
  163. nextHandlers: [], // 下一节点办理对象
  164. nextMainHandler: '', // 主办人
  165. isSms: false, // 是否短信通知
  166. isStartCountersign: false, // 是否发起会签
  167. },
  168. discernForm: {
  169. // 甄别表单
  170. content: '', // 甄别理由
  171. },
  172. nextStepOptions: [], // 下一节点
  173. handlerOptions: [], // 办理对象
  174. transform: 'translate(0px, 0px)', // 滚动条位置
  175. loading: false, // 提交按钮loading
  176. workflowId: '', // 流程id
  177. handlerClassifies: [], //撤回办理对象
  178. handlerMainOptions: [], // 主办人
  179. handleId: '', // 流程处理ID
  180. dialogTitle: '', // 弹窗标题
  181. annexName: '', // 附件标题
  182. inputPlaceholder: '', // 意见提示
  183. orderDetail: {}, // 工单详情
  184. });
  185. const ruleFormRef = ref<RefType>(); //表单组件
  186. const storesUserInfo = useUserInfo();
  187. const { userInfos } = storeToRefs(storesUserInfo); // 用户信息
  188. const screenTypeOptions = ref<EmptyArrayType>([]); // 甄别类型
  189. // 打开弹窗
  190. const openDialog = async (val: any) => {
  191. console.log(val);
  192. state.loading = true;
  193. try {
  194. state.dialogTitle = '甄别申请'; // 流程标题
  195. activeStep.value = 0;
  196. const [workflowDiscernResponse, responseDiscern] = await Promise.all([workflowDiscernParams(), screenBaseData()]); //获取开启流程参数
  197. screenTypeOptions.value = responseDiscern.result?.screenType ?? []; // 甄别理由
  198. handleResult(workflowDiscernResponse);
  199. const { result } = await screenDetail(val.id);
  200. state.discernForm.content = result.content ?? {};
  201. state.discernForm.type = {
  202. dicDataValue: result.typeDicId,
  203. dicDataName: result.typeDicName,
  204. };
  205. state.orderDetail = result;
  206. state.ruleForm.files = transformFile(result.files);
  207. console.log(result);
  208. await nextTick(() => {
  209. restForm(ruleFormRef.value);
  210. });
  211. state.dialogVisible = true;
  212. } finally {
  213. state.loading = false;
  214. }
  215. };
  216. const canStartCountersign = ref<boolean>(false); // 是否可以发起会签
  217. const isMainHandlerShow = ref<boolean>(false); // 是否展示主办人
  218. const handleResult = (res: any) => {
  219. state.nextStepOptions = res.result.steps; //办理对象选择内容
  220. canStartCountersign.value = res.result.canStartCountersign ?? false; // 是否可以发起会签
  221. isMainHandlerShow.value = res.result.isMainHandlerShow ?? false; // 是否展示主办人
  222. if (state.nextStepOptions.length === 1) {
  223. // 下一节点是否只有一个 默认选中第一个
  224. setTimeout(() => {
  225. state.ruleForm.nextStepCode = state.nextStepOptions[0].key; // 下一节点code
  226. state.ruleForm.nextStepName = state.nextStepOptions[0].value; // 下一节点name
  227. state.ruleForm.backToCountersignEnd = state.nextStepOptions[0].backToCountersignEnd ?? false; // 是否回到会签结束节点
  228. }, 0);
  229. selectNextStep(state.nextStepOptions[0].key); // 查询流程下一节点参数
  230. } else {
  231. state.ruleForm.nextStepCode = '';
  232. state.ruleForm.nextStepName = '';
  233. }
  234. state.loading = false;
  235. };
  236. // 上一部
  237. const onPrevious = () => {
  238. activeStep.value = 0;
  239. };
  240. const discernFormRef = ref<RefType>(); //甄别申请表单组件
  241. // 下一步
  242. const onNext = () => {
  243. discernFormRef.value?.validate((valid: boolean) => {
  244. if (!valid) return;
  245. activeStep.value = 1;
  246. });
  247. };
  248. const activeStep = ref(0); //步骤条
  249. // 流程选择下一环节
  250. const fastStepName = ref(''); // 推荐派单处理对象
  251. const fastStepCode = ref(''); // 推荐派单处理对象code
  252. const selectNextStep = (val: any) => {
  253. ruleFormRef.value?.resetFields('nextHandlers');
  254. ruleFormRef.value?.resetFields('nextMainHandler');
  255. const next = state.nextStepOptions.find((item: any) => item.key === val);
  256. const items = next.items; //获取下一节点
  257. state.ruleForm.nextStepName = next.value; // 下一节点name
  258. state.ruleForm.backToCountersignEnd = next.backToCountersignEnd ?? false; // 是否回到会签结束节点
  259. state.handlerOptions = items ?? [];
  260. state.handlerOptions = state.handlerOptions.map((item: any) => {
  261. return {
  262. value: {
  263. ...item,
  264. },
  265. label: item.value,
  266. };
  267. });
  268. fastStepName.value = next.recommendOrgName; // 推荐派单处理对象
  269. fastStepCode.value = next.recommendOrgId; // 推荐派单处理对象code
  270. };
  271. // 会签是否可用 (多个办理对象,并且配置可以会签)
  272. const countersignAble = computed(() => {
  273. return canStartCountersign.value;
  274. });
  275. // 办理对象是否能够选择多个(可以发起会签可以选择多个,不能发起会签只能选择一个)
  276. const multipleLimit = computed(() => {
  277. return canStartCountersign.value ? 0 : 1;
  278. });
  279. watch(
  280. () => state.ruleForm.nextHandlers, // 监听办理对象 多个办理对象自动发起会签
  281. (val) => {
  282. state.ruleForm.isStartCountersign = val.length > 1;
  283. }
  284. );
  285. const countersignDisabled = computed(() => {
  286. // 是否可以发起会签
  287. return state.ruleForm.nextHandlers.length <= 1;
  288. });
  289. // 是否发起会签
  290. const changeStartCountersign = (val: boolean) => {
  291. if (!val) {
  292. // 如果不能会签清空办理对象
  293. state.ruleForm.nextHandlers = [];
  294. ruleFormRef.value?.resetFields('nextHandlers');
  295. }
  296. };
  297. // 是否展示办理对象 (只有结束节点不展示 next.stepType===2 表示为结束节点)
  298. const showHandlers = computed(() => {
  299. const next = state.nextStepOptions.find((item: any) => item.key === state.ruleForm.nextStepCode);
  300. if (!next) return true;
  301. return next.stepType !== 2;
  302. });
  303. // 是否显示快捷派单
  304. const showFastSendOrder = computed(() => {
  305. const next = state.nextStepOptions.find((item: any) => item.key === state.ruleForm.nextStepCode);
  306. if (!next) return false;
  307. return next?.recommendOrgName && next?.recommendOrgId;
  308. });
  309. // 快速派单
  310. const fastSendOrder = () => {
  311. if (!fastStepCode.value) return;
  312. // 如果办理对象中没有推荐派单的对象就添加
  313. if (!state.ruleForm.nextHandlers.find((item: any) => item.key === fastStepCode.value)) {
  314. state.ruleForm.nextHandlers = [...state.ruleForm.nextHandlers, { key: fastStepCode.value, value: fastStepName.value }];
  315. }
  316. };
  317. // 选择办理对象
  318. const selectHandlers = () => {
  319. ruleFormRef.value?.resetFields('nextMainHandler');
  320. if (state.ruleForm.nextHandlers.length > 1) {
  321. // 多个办理对象 主办
  322. state.ruleForm.nextMainHandler = state.ruleForm.nextHandlers[0].key;
  323. }
  324. if (state.ruleForm.nextHandlers.length <= 1) {
  325. // 如果只有一个办理对象就不需要发起会签
  326. state.ruleForm.isStartCountersign = false;
  327. }
  328. };
  329. // 是否展示主办
  330. const showMainHandler = computed(() => {
  331. return state.ruleForm.nextHandlers.length > 1 && isMainHandlerShow.value;
  332. });
  333. // 主办从办理对象中选择
  334. state.handlerMainOptions = computed(() => {
  335. return state.ruleForm.nextHandlers;
  336. });
  337. // 设置抽屉
  338. const dialogRef = ref<RefType>();
  339. const mouseup = () => {
  340. state.transform = dialogRef.value.dialogContentRef.$el.style.transform;
  341. };
  342. // 关闭弹窗
  343. const closeDialog = () => {
  344. state.dialogVisible = false;
  345. };
  346. // 重置表单方法
  347. const restForm = (formEl: FormInstance | undefined) => {
  348. if (!formEl) return;
  349. state.ruleForm.opinion = '';
  350. formEl.resetFields();
  351. formEl.clearValidate();
  352. };
  353. // 选择常用意见 填入填写框 甄别
  354. const chooseAdviceDiscern = (item: any) => {
  355. state.discernForm.content += item.content;
  356. };
  357. const afterSubmit = (emitType?: 'updateList' | 'orderProcessFailed', showMessage?: boolean) => {
  358. state.loading = false;
  359. closeDialog();
  360. if (showMessage) ElMessage.success('操作成功');
  361. if (emitType) emit(emitType);
  362. };
  363. const close = () => {
  364. restForm(ruleFormRef.value);
  365. restForm(discernFormRef.value);
  366. };
  367. // 提交
  368. const handleFilesDiscern = ref<EmptyArrayType>([]); // 甄别附件
  369. const onSubmit = (formEl: FormInstance | undefined) => {
  370. if (!formEl) return;
  371. formEl.validate((valid: boolean) => {
  372. if (!valid) return;
  373. ElMessageBox.confirm(`确认办理?`, '提示', {
  374. confirmButtonText: '确认',
  375. cancelButtonText: '取消',
  376. type: 'warning',
  377. draggable: true,
  378. cancelButtonClass: 'default-button',
  379. autofocus: false,
  380. })
  381. .then(() => {
  382. state.loading = true;
  383. let submitObj = other.deepClone(state.ruleForm);
  384. if (submitObj.nextHandlers && submitObj.nextHandlers.length) {
  385. if (submitObj.nextHandlers.length === 1) {
  386. submitObj.nextMainHandler = submitObj.nextHandlers[0].key;
  387. }
  388. }
  389. const requestDiscern = {
  390. data: {
  391. id: state.orderDetail.id,
  392. no: state.orderDetail.no,
  393. visitId: state.orderDetail.visitId,
  394. visitDetailId: state.orderDetail.visitDetailId,
  395. orderId: state.orderDetail.orderId,
  396. typeDicId: state.discernForm.type.dicDataValue,
  397. typeDicName: state.discernForm.type.dicDataName,
  398. content: state.discernForm.content,
  399. files: handleFilesDiscern.value,
  400. },
  401. nextWorkflow: { ...submitObj, files: handleFilesDiscern.value, opinion: state.discernForm.content },
  402. };
  403. discernUpdate(requestDiscern)
  404. .then(() => {
  405. afterSubmit('updateList', true);
  406. })
  407. .catch(() => {
  408. afterSubmit('orderProcessFailed');
  409. });
  410. })
  411. .catch(() => {});
  412. });
  413. };
  414. // 暴露变量
  415. defineExpose({
  416. openDialog,
  417. closeDialog,
  418. });
  419. </script>