Delay-edit.vue 18 KB

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