Quality-inspection.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. <template>
  2. <el-dialog
  3. v-model="state.dialogVisible"
  4. :title="dialogTitle"
  5. draggable
  6. ref="dialogRef"
  7. width="50%"
  8. append-to-body
  9. destroy-on-close
  10. @close="close"
  11. @opened="opened"
  12. >
  13. <el-form :model="state.ruleForm" ref="ruleFormRef" label-width="110px">
  14. <el-row :gutter="10">
  15. <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
  16. <el-form-item label="工单编码:"> {{ state.orderDetail.no }} </el-form-item>
  17. </el-col>
  18. <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
  19. <el-form-item label="来电号码:">
  20. {{ state.orderDetail.fromPhone }}
  21. <el-button
  22. plain
  23. title="工单录音文件"
  24. v-if="state.orderDetail.recordingAbsolutePath"
  25. size="small"
  26. type="primary"
  27. class="ml8"
  28. @click="recordFile(state.orderDetail, 'order')"
  29. >工单录音</el-button
  30. >
  31. <el-button
  32. plain
  33. title="回访录音文件"
  34. v-if="state.visit.recordingAbsolutePath"
  35. size="small"
  36. type="primary"
  37. class="ml8"
  38. @click="recordFile(state.visit, 'visit')"
  39. >回访录音</el-button
  40. >
  41. </el-form-item>
  42. </el-col>
  43. <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
  44. <el-form-item label="受理人:"> >{{ state.orderDetail.acceptorName }} </el-form-item>
  45. </el-col>
  46. <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
  47. <el-form-item label="受理时间:"> {{ formatDate(state.orderDetail?.startTime, 'YYYY-mm-dd HH:MM:SS') }} </el-form-item>
  48. </el-col>
  49. </el-row>
  50. <el-divider content-position="left">
  51. <el-text tag="b" size="large"> 质检内容 </el-text>
  52. </el-divider>
  53. <div class="mt20 mb20">
  54. <el-button type="primary" class="mb10" @click="onAddItem">新增扣分项</el-button>
  55. <!-- 表格 -->
  56. <el-table :data="tableData" border row-key="name" ref="multipleTableRef" @selection-change="handleSelectionChange">
  57. <el-table-column type="selection" width="55" align="center" :reserve-selection="true" />
  58. <el-table-column label="开始扣分时间点" show-overflow-tooltip align="center" min-width="120">
  59. <template #default="{ row }">
  60. {{ formatDuration(row.second, false, true) }}
  61. </template>
  62. </el-table-column>
  63. <el-table-column label="结束扣分时间点" show-overflow-tooltip align="center" min-width="120">
  64. <template #default="{ row }">
  65. {{ formatDuration(row.endSecond, false, true) }}
  66. </template>
  67. </el-table-column>
  68. <el-table-column prop="name" label="扣分项" show-overflow-tooltip align="center"></el-table-column>
  69. <el-table-column label="质检类型" show-overflow-tooltip align="center">
  70. <template #default="{ row }">
  71. <span> {{ row.intelligent ? '智能质检' : '人工质检' }}</span>
  72. </template>
  73. </el-table-column>
  74. <el-table-column prop="content" label="扣分内容" show-overflow-tooltip align="center"></el-table-column>
  75. <el-table-column prop="grade" label="分值(分)" show-overflow-tooltip align="center"></el-table-column>
  76. <el-table-column label="操作" width="120" fixed="right" align="center">
  77. <template #default="{ row }">
  78. <!-- 智能质检不能编辑就和删除 -->
  79. <el-button link type="primary" @click="onEditItem(row)" v-if="!row.intelligent"> 编辑 </el-button>
  80. <el-button link type="primary" @click="onDeleteItem(row)" v-if="!row.intelligent"> 删除 </el-button>
  81. </template>
  82. </el-table-column>
  83. </el-table>
  84. </div>
  85. <el-row :gutter="10">
  86. <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
  87. <el-form-item label="总分:"> 100 分 </el-form-item>
  88. </el-col>
  89. <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
  90. <el-form-item label="当前质检得分:"> {{ score }} </el-form-item>
  91. </el-col>
  92. <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
  93. <el-form-item label="质检评价:" prop="content" :rules="[{ required: false, message: '请填写质检评价', trigger: 'blur' }]">
  94. <el-input
  95. v-model="state.ruleForm.content"
  96. placeholder="请填写质检评价"
  97. clearable
  98. type="textarea"
  99. :autosize="{ minRows: 4, maxRows: 8 }"
  100. ></el-input>
  101. </el-form-item>
  102. </el-col>
  103. </el-row>
  104. </el-form>
  105. <template #footer>
  106. <span class="dialog-footer">
  107. <el-button @click="closeDialog" class="default-button">取 消</el-button>
  108. <el-button type="primary" @click="onSubmit(ruleFormRef)" v-waves="'light'" :loading="state.loading">确 定 </el-button>
  109. </span>
  110. </template>
  111. </el-dialog>
  112. <el-dialog
  113. v-model="state.dialogVisibleItem"
  114. :title="dialogTitleItem"
  115. draggable
  116. ref="dialogItemRef"
  117. width="600px"
  118. append-to-body
  119. destroy-on-close
  120. @close="closeItem"
  121. >
  122. <el-form :model="state.ruleItemForm" ref="ruleItemFormRef" label-width="100px">
  123. <el-form-item
  124. label="开始扣分时间点"
  125. prop="value"
  126. :rules="[{ required: true, message: '请选择开始扣分时间点', trigger: 'change' }]"
  127. label-width="140px"
  128. >
  129. <el-time-picker
  130. v-model="state.ruleItemForm.value"
  131. placeholder="请选择开始扣分时间点"
  132. @change="selectSecond"
  133. value-format="mm:ss"
  134. format="mm:ss"
  135. :picker-options="{
  136. selectableRange: '00:00:00 - 00:59:59',
  137. }"
  138. popper-class="noneMinute"
  139. />
  140. </el-form-item>
  141. <el-form-item
  142. label="结束扣分时间点"
  143. prop="endValue"
  144. :rules="[{ required: true, message: '请选择结束扣分时间点', trigger: 'change' }]"
  145. label-width="140px"
  146. >
  147. <el-time-picker
  148. v-model="state.ruleItemForm.endValue"
  149. placeholder="请选择结束扣分时间点"
  150. @change="selectSecondEnd"
  151. value-format="mm:ss"
  152. format="mm:ss"
  153. :picker-options="{
  154. selectableRange: '00:00:00 - 00:59:59',
  155. }"
  156. popper-class="noneMinute"
  157. />
  158. </el-form-item>
  159. <el-form-item label="扣分项" prop="name" :rules="[{ required: true, message: '请选择扣分项', trigger: 'change' }]">
  160. <el-select v-model="state.ruleItemForm.name" placeholder="请选择扣分项" class="w100" @change="changeProject">
  161. <el-option v-for="item in projectArray" :key="item.id" :label="item.name" :value="item.name" :disabled="item.disabled" />
  162. </el-select>
  163. </el-form-item>
  164. <el-form-item label="分值" prop="grade" :rules="[{ required: true, message: '请填写分值', trigger: 'blur' }]">
  165. <el-input-number v-model="state.ruleItemForm.grade" :precision="0" :min="1" :max="100" placeholder="请填写分值" />
  166. <span class="ml10">分</span>
  167. </el-form-item>
  168. <el-form-item label="扣分内容" prop="content" :rules="[{ required: false, message: '请填写扣分内容', trigger: 'blur' }]">
  169. <el-input v-model="state.ruleItemForm.content" placeholder="请填写扣分内容" type="textarea" :autosize="{ minRows: 4, maxRows: 8 }"></el-input>
  170. </el-form-item>
  171. </el-form>
  172. <template #footer>
  173. <span class="dialog-footer">
  174. <el-button @click="state.dialogVisibleItem = false" class="default-button">取 消</el-button>
  175. <el-button type="primary" @click="onSubmitItem(ruleItemFormRef)" v-waves="'light'" :loading="state.loading">确 定 </el-button>
  176. </span>
  177. </template>
  178. </el-dialog>
  179. <!-- 播放录音 -->
  180. <play-record ref="playRecordRef" />
  181. </template>
  182. <script setup lang="ts" name="qualityInspection">
  183. import { computed, defineAsyncComponent, reactive, ref, watch } from 'vue';
  184. import { ElMessage, ElMessageBox, FormInstance } from 'element-plus';
  185. import { formatDate, formatDuration } from '@/utils/formatTime';
  186. import { templateList } from '@/api/quality/template';
  187. import Other from '@/utils/other';
  188. import { qualityUpdate, qualityDetail } from '@/api/quality';
  189. // 引入组件
  190. const PlayRecord = defineAsyncComponent(() => import('@/views/tels/callLog/component/Play-record.vue')); // 播放录音
  191. // 定义子组件向父组件传值/事件
  192. const emit = defineEmits(['updateList']);
  193. const state = reactive<any>({
  194. dialogVisible: false, // 弹窗显示隐藏
  195. ruleForm: {
  196. name: '', // 违禁词
  197. classify: '', // 违禁词分类
  198. type: '', // 违禁词属性
  199. },
  200. loading: false, // 确定按钮loading
  201. orderDetail: {}, // 工单详情
  202. visit: {}, // 回访详情
  203. dialogVisibleItem: false,
  204. ruleItemForm: {
  205. second: 0,
  206. name: '', // 扣分时间点
  207. grade: 1, // 分值
  208. content: '', // 扣分内容
  209. },
  210. source: 1, // 来源
  211. });
  212. const dialogTitle = ref<string>('受理质检');
  213. const dialogTitleItem = ref<string>('新增扣分项');
  214. const projectArray = ref<EmptyArrayType>([]); // 质检项数组
  215. const tableData = ref<EmptyArrayType>([]); // 表格数据
  216. const qualityId = ref<string>(''); // 质检ID
  217. // 打开弹窗
  218. const openDialog = async (row: any, source?: string | number | undefined) => {
  219. qualityId.value = row.id;
  220. score.value = 100;
  221. if (source) {
  222. state.source = source;
  223. const [temRes, qualityRes] = await Promise.all([
  224. templateList({ IsEnable: 1, Grouping: source, PageIndex: 1, PageSize: 999 }),
  225. qualityDetail(row.id),
  226. ]);
  227. projectArray.value = temRes.result?.items[0]?.templateDetails ?? [];
  228. tableData.value = qualityRes.result?.qualityDetails ?? [];
  229. multipleSelection.value = tableData.value.filter((v: any) => v.check === true);
  230. state.orderDetail = qualityRes.result?.order ?? {};
  231. state.visit = qualityRes.result?.visit ?? {};
  232. switch (source) {
  233. case 1:
  234. dialogTitle.value = '受理质检';
  235. break;
  236. case 2:
  237. dialogTitle.value = '派单质检';
  238. break;
  239. case 3:
  240. dialogTitle.value = '回访质检';
  241. break;
  242. }
  243. }
  244. state.dialogVisible = true;
  245. };
  246. const opened = () => {
  247. // 回显选中表格
  248. if (state.dialogVisible) {
  249. multipleSelection.value.forEach((v: any) => {
  250. multipleTableRef.value?.toggleRowSelection(v, true);
  251. });
  252. }
  253. };
  254. // 表格多选
  255. const multipleTableRef = ref<RefType>();
  256. const multipleSelection = ref<EmptyArrayType>([]);
  257. const handleSelectionChange = (val: any[]) => {
  258. multipleSelection.value = val;
  259. multipleSelection.value.forEach((v: any) => {
  260. v.check = true;
  261. });
  262. };
  263. // 计算当前得分
  264. const score = ref(100);
  265. watch(
  266. () => multipleSelection.value,
  267. (newVal) => {
  268. let total = 100;
  269. for (let i of newVal) {
  270. total -= i.grade;
  271. }
  272. score.value = total;
  273. }
  274. );
  275. watch(
  276. () => score.value,
  277. (newVal) => {
  278. if (newVal <= 0) {
  279. ElMessage.warning('质检得分不能小于0分');
  280. }
  281. }
  282. );
  283. // 关闭弹窗
  284. const closeDialog = () => {
  285. state.dialogVisible = false;
  286. };
  287. const ruleFormRef = ref<FormInstance>();
  288. const close = () => {
  289. ruleFormRef.value?.clearValidate();
  290. ruleFormRef.value?.resetFields();
  291. };
  292. const closeItem = () => {
  293. ruleItemFormRef.value?.clearValidate();
  294. ruleItemFormRef.value?.resetFields();
  295. };
  296. // 查看录音文件
  297. const playRecordRef = ref<RefType>();
  298. const recordFile = (obj: any, type: string) => {
  299. switch (type) {
  300. case 'order':
  301. const fileName = `工单编号-${obj.no} 录音文件`;
  302. playRecordRef.value.openDialog(import.meta.env.VITE_RECORD_PREFIX + obj.recordingAbsolutePath, fileName, obj.recordingAbsolutePath);
  303. break;
  304. case 'visit':
  305. const fileNames = `工单编号-${obj.order?.no} 录音文件`;
  306. playRecordRef.value.openDialog(import.meta.env.VITE_RECORD_PREFIX + obj.recordingAbsolutePath, fileNames, obj.recordingAbsolutePath);
  307. break;
  308. }
  309. };
  310. // 新增质检项内容
  311. const onAddItem = () => {
  312. dialogTitleItem.value = '新增扣分项';
  313. state.ruleItemForm = {
  314. second: 0,
  315. name: '', // 扣分时间点
  316. grade: 1, // 分值
  317. content: '', // 扣分内容
  318. };
  319. state.dialogVisibleItem = true;
  320. getEnableAcceptType();
  321. };
  322. // 选择扣分项
  323. const changeProject = (val: any) => {
  324. const item = projectArray.value.filter((v: any) => v.name === val)[0];
  325. state.ruleItemForm.grade = item.grade;
  326. state.ruleItemForm.content = item.content;
  327. getEnableAcceptType();
  328. };
  329. // 编辑质检内容
  330. const onEditItem = (row: any) => {
  331. dialogTitleItem.value = '编辑扣分项';
  332. state.ruleItemForm = Other.deepClone(row);
  333. state.ruleItemForm.value = formatDuration(state.ruleItemForm.second, false, true);
  334. state.ruleItemForm.endValue = formatDuration(state.ruleItemForm.endSecond, false, true);
  335. state.dialogVisibleItem = true;
  336. getEnableAcceptType();
  337. };
  338. // 分秒计算成毫秒
  339. const formatSecond = (val: string) => {
  340. if (!val) return 0;
  341. const minute = Number(val.split(':')[0]);
  342. const second = Number(val.split(':')[1]);
  343. return (minute * 60 + second) * 1000;
  344. };
  345. // 选择开始扣分时间点
  346. const selectSecond = (val: any) => {
  347. state.ruleItemForm.second = formatSecond(val);
  348. };
  349. // 选择结束扣分时间点
  350. const selectSecondEnd = (val: any) => {
  351. state.ruleItemForm.endSecond = formatSecond(val);
  352. };
  353. // 扣分项保存
  354. const ruleItemFormRef = ref<RefType>();
  355. const onSubmitItem = (formEl: FormInstance | undefined) => {
  356. if (!formEl) return;
  357. formEl.validate((valid: boolean) => {
  358. if (!valid) return;
  359. state.loading = true;
  360. if (dialogTitleItem.value === '新增扣分项') {
  361. const data = Other.deepClone(state.ruleItemForm);
  362. tableData.value.push(data);
  363. state.loading = false;
  364. state.dialogVisibleItem = false;
  365. } else {
  366. const newArray = <EmptyArrayType>[];
  367. for (let i of tableData.value) {
  368. if (i.name === state.ruleItemForm.name) {
  369. newArray.push({
  370. ...i,
  371. ...state.ruleItemForm,
  372. });
  373. } else {
  374. newArray.push(i);
  375. }
  376. }
  377. tableData.value = newArray;
  378. state.loading = false;
  379. state.dialogVisibleItem = false;
  380. }
  381. });
  382. };
  383. // 删除质检项内容
  384. const onDeleteItem = (row: any) => {
  385. ElMessageBox.confirm(`您确定要删除:【${row.name}】扣分项,是否继续?`, '提示', {
  386. confirmButtonText: '确认',
  387. cancelButtonText: '取消',
  388. type: 'warning',
  389. draggable: true,
  390. cancelButtonClass: 'default-button',
  391. autofocus: false,
  392. })
  393. .then(() => {
  394. tableData.value = tableData.value.filter((v: any) => v.name !== row.name);
  395. setTimeout(() => {
  396. getEnableAcceptType();
  397. }, 0);
  398. })
  399. .catch(() => {});
  400. };
  401. // 获取可用的数组
  402. const getEnableAcceptType = () => {
  403. if (tableData.value.length) {
  404. const array = tableData.value.map((v: any) => v.name);
  405. projectArray.value.forEach((v: any) => {
  406. v.disabled = array.includes(v.name);
  407. });
  408. } else {
  409. projectArray.value.forEach((v: any) => {
  410. v.disabled = false;
  411. });
  412. }
  413. };
  414. // 保存
  415. const onSubmit = (formEl: FormInstance | undefined) => {
  416. if (!formEl) return;
  417. formEl.validate((valid: boolean) => {
  418. if (!valid) return;
  419. if (score.value <= 0) {
  420. ElMessage.warning('质检得分不能小于0分');
  421. return;
  422. }
  423. const qualityDetails = tableData.value.map((v: any) => {
  424. return {
  425. ...v,
  426. check: multipleSelection.value.includes(v),
  427. };
  428. });
  429. state.loading = true;
  430. const request = {
  431. orderId: state.orderDetail.id, // 工单id
  432. mode: '人工质检', // 质检模式
  433. grade: score.value, // 质检得分
  434. content: state.ruleForm.content, // 质检评价
  435. source: state.source, // 来源
  436. qualityDetails: qualityDetails, // 质检详情
  437. id: qualityId.value, // 质检id
  438. };
  439. qualityUpdate(request)
  440. .then(() => {
  441. ElMessage.success('操作成功');
  442. emit('updateList');
  443. state.loading = false;
  444. state.dialogVisible = false;
  445. })
  446. .catch(() => {
  447. state.loading = false;
  448. state.dialogVisible = false;
  449. emit('updateList');
  450. });
  451. });
  452. };
  453. defineExpose({
  454. openDialog,
  455. closeDialog,
  456. });
  457. </script>