complaintMixin.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687
  1. import {
  2. getList,
  3. add,
  4. update,
  5. remove,
  6. getDetail,
  7. updateStatus,
  8. batchUpdateStatus
  9. } from '@/api/complaint'
  10. import {
  11. getReplyList,
  12. addReply
  13. } from '@/api/complaint/reply'
  14. import { mapGetters } from 'vuex'
  15. import {
  16. COMPLAINANT_TYPE_OPTIONS,
  17. COMPLAINT_TYPE_OPTIONS,
  18. COMPLAINT_STATUS_OPTIONS,
  19. REPLY_STATUS_OPTIONS,
  20. REPLY_TYPE_OPTIONS,
  21. COMPLAINT_STATUS,
  22. REPLY_TYPE,
  23. REPLY_STATUS,
  24. getComplainantTypeLabel,
  25. getComplaintTypeLabel,
  26. getComplainantTypeType,
  27. getComplaintStatusLabel,
  28. getComplaintStatusType,
  29. getReplyStatusLabel,
  30. getReplyStatusType,
  31. getReplyTypeLabel,
  32. getReplyTypeType,
  33. getReplyTypeColor,
  34. isComplaintEditable,
  35. isComplaintProcessable,
  36. isComplaintClosable,
  37. isValidComplaintStatus,
  38. isValidReplyStatus
  39. } from '@/constants/complaint'
  40. /**
  41. * 投诉查询参数类型定义
  42. * @typedef {Object} ComplaintQueryParams
  43. * @property {string} [complaintNo] - 投诉单号
  44. * @property {string} [title] - 投诉标题
  45. * @property {number} [complainantType] - 投诉人类型
  46. * @property {string} [customerCode] - 客户编码
  47. * @property {string} [customerName] - 客户名称
  48. * @property {string} [contactName] - 联系人姓名
  49. * @property {string} [contactPhone] - 联系人电话
  50. * @property {string} [complaintType] - 投诉类型
  51. * @property {number} [status] - 投诉状态
  52. * @property {number} [replyStatus] - 回复状态
  53. * @property {string} [submitTimeStart] - 提交时间开始
  54. * @property {string} [submitTimeEnd] - 提交时间结束
  55. */
  56. /**
  57. * 状态更新表单类型定义
  58. * @typedef {Object} StatusForm
  59. * @property {number} status - 新状态
  60. * @property {string} [closeReason] - 关闭原因
  61. */
  62. export default {
  63. data() {
  64. return {
  65. /** @type {ComplaintRecord[]} */
  66. data: [],
  67. /** @type {ComplaintQueryParams} */
  68. query: {},
  69. loading: true,
  70. /** @type {ComplaintForm} */
  71. form: {},
  72. /** @type {ComplaintRecord[]} */
  73. selectionList: [],
  74. /** @type {Object} */
  75. page: {
  76. pageSize: 10,
  77. currentPage: 1,
  78. total: 0
  79. },
  80. /** @type {ComplaintRecord|null} */
  81. detailData: null,
  82. detailVisible: false,
  83. statusVisible: false,
  84. statusDialogTitle: '',
  85. /** @type {StatusForm} */
  86. statusForm: {
  87. status: COMPLAINT_STATUS.PROCESSING,
  88. closeReason: ''
  89. },
  90. statusLoading: false,
  91. /** @type {string[]} */
  92. currentIds: [],
  93. statusRules: {
  94. status: [
  95. { required: true, message: '请选择状态', trigger: 'change' }
  96. ],
  97. closeReason: [
  98. { required: true, message: '请输入关闭原因', trigger: 'blur' }
  99. ]
  100. },
  101. // 回复列表相关数据
  102. replyListVisible: false,
  103. replyList: [],
  104. replyListLoading: false,
  105. replyPage: {
  106. current: 1,
  107. size: 10
  108. },
  109. replyTotal: 0,
  110. currentComplaintId: '',
  111. currentComplaintNo: '',
  112. /** @type {number} */
  113. currentComplaintReplyStatus: 0,
  114. // 新增回复相关数据
  115. addReplyVisible: false,
  116. replyForm: {
  117. complaintId: '',
  118. replyType: REPLY_TYPE.SYSTEM,
  119. replyContent: '',
  120. replyAttachUrl: ''
  121. },
  122. replyRules: {
  123. replyType: [{ required: true, message: '请选择回复类型', trigger: 'change' }],
  124. replyContent: [{ required: true, message: '请输入回复内容', trigger: 'blur' }]
  125. },
  126. addReplyLoading: false,
  127. option: {
  128. height: 'auto',
  129. calcHeight: 30,
  130. tip: false,
  131. searchShow: true,
  132. searchMenuSpan: 6,
  133. border: true,
  134. index: true,
  135. viewBtn: true,
  136. selection: true,
  137. dialogClickModal: false,
  138. column: [
  139. {
  140. label: '投诉单号',
  141. prop: 'complaintNo',
  142. minWidth: 150,
  143. search: true
  144. },
  145. {
  146. label: '投诉标题',
  147. prop: 'title',
  148. minWidth: 200,
  149. search: true,
  150. overHidden: true
  151. },
  152. {
  153. label: '投诉人类型',
  154. prop: 'complainantType',
  155. minWidth: 100,
  156. slot: true,
  157. search: true,
  158. type: 'select',
  159. dicData: COMPLAINANT_TYPE_OPTIONS
  160. },
  161. {
  162. label: '客户名称',
  163. prop: 'customerName',
  164. minWidth: 150,
  165. search: true,
  166. overHidden: true
  167. },
  168. {
  169. label: '联系人',
  170. prop: 'contactName',
  171. minWidth: 100,
  172. search: true
  173. },
  174. {
  175. label: '联系电话',
  176. prop: 'contactPhone',
  177. minWidth: 120,
  178. search: true
  179. },
  180. {
  181. label: '投诉类型',
  182. prop: 'complaintType',
  183. minWidth: 100,
  184. search: true,
  185. type: 'select',
  186. dicData: COMPLAINT_TYPE_OPTIONS
  187. },
  188. {
  189. label: '投诉状态',
  190. prop: 'status',
  191. minWidth: 100,
  192. slot: true,
  193. search: true,
  194. type: 'select',
  195. dicData: COMPLAINT_STATUS_OPTIONS
  196. },
  197. {
  198. label: '回复状态',
  199. prop: 'replyStatus',
  200. minWidth: 100,
  201. slot: true,
  202. search: true,
  203. type: 'select',
  204. dicData: REPLY_STATUS_OPTIONS
  205. },
  206. {
  207. label: '提交时间',
  208. prop: 'submitTime',
  209. minWidth: 150,
  210. type: 'datetime',
  211. format: 'YYYY-MM-DD HH:mm:ss',
  212. valueFormat: 'YYYY-MM-DD HH:mm:ss'
  213. },
  214. {
  215. label: '更新时间',
  216. prop: 'updateTime',
  217. minWidth: 150,
  218. type: 'datetime',
  219. format: 'YYYY-MM-DD HH:mm:ss',
  220. valueFormat: 'YYYY-MM-DD HH:mm:ss'
  221. }
  222. ]
  223. }
  224. }
  225. },
  226. computed: {
  227. ...mapGetters(['permission']),
  228. permissionList() {
  229. return {
  230. // addBtn: this.vaildData(this.permission.complaint_add, false),
  231. // viewBtn: this.vaildData(this.permission.complaint_view, false),
  232. // delBtn: this.vaildData(this.permission.complaint_delete, false),
  233. // editBtn: this.vaildData(this.permission.complaint_edit, false)
  234. addBtn: false,
  235. viewBtn: false,
  236. delBtn: false,
  237. editBtn: false,
  238. }
  239. },
  240. ids() {
  241. /** @type {string[]} */
  242. const ids = []
  243. this.selectionList.forEach(ele => {
  244. ids.push(ele.id)
  245. })
  246. return ids.join(',')
  247. },
  248. /**
  249. * 回复类型选项
  250. * @returns {Array<{label: string, value: number}>} 回复类型选项数组
  251. */
  252. replyTypeOptions() {
  253. return REPLY_TYPE_OPTIONS
  254. },
  255. /**
  256. * 投诉状态常量
  257. */
  258. COMPLAINT_STATUS() {
  259. return COMPLAINT_STATUS
  260. }
  261. },
  262. methods: {
  263. // 导入工具函数
  264. getComplainantTypeLabel,
  265. getComplainantTypeType,
  266. getComplaintTypeLabel,
  267. getComplaintStatusLabel,
  268. getComplaintStatusType,
  269. getReplyStatusLabel,
  270. getReplyStatusType,
  271. isComplaintEditable,
  272. isComplaintProcessable,
  273. // getComplainantTypeText,
  274. isComplaintClosable,
  275. /**
  276. * 行保存
  277. * @param {ComplaintForm} row - 表单数据
  278. * @param {Function} done - 完成回调
  279. * @param {Function} loading - 加载状态回调
  280. */
  281. async rowSave(row, done, loading) {
  282. try {
  283. loading()
  284. const res = await add(row)
  285. if (res.data.success) {
  286. this.$message.success('操作成功')
  287. done()
  288. this.onLoad(this.page)
  289. } else {
  290. this.$message.error(res.data.msg || '操作失败')
  291. loading()
  292. }
  293. } catch (error) {
  294. console.error('新增投诉失败:', error)
  295. this.$message.error('操作失败')
  296. loading()
  297. }
  298. },
  299. /**
  300. * 行更新
  301. * @param {ComplaintForm} row - 表单数据
  302. * @param {number} index - 行索引
  303. * @param {Function} done - 完成回调
  304. * @param {Function} loading - 加载状态回调
  305. */
  306. async rowUpdate(row, index, done, loading) {
  307. try {
  308. loading()
  309. const res = await update(row)
  310. if (res.data.success) {
  311. this.$message.success('操作成功')
  312. done()
  313. this.onLoad(this.page)
  314. } else {
  315. this.$message.error(res.data.msg || '操作失败')
  316. loading()
  317. }
  318. } catch (error) {
  319. console.error('更新投诉失败:', error)
  320. this.$message.error('操作失败')
  321. loading()
  322. }
  323. },
  324. /**
  325. * 行删除
  326. * @param {ComplaintRecord} row - 行数据
  327. * @param {number} index - 行索引
  328. */
  329. async rowDel(row, index) {
  330. try {
  331. const res = await remove(row.id)
  332. if (res.data.success) {
  333. this.$message.success('操作成功')
  334. this.onLoad(this.page)
  335. } else {
  336. this.$message.error(res.data.msg || '操作失败')
  337. }
  338. } catch (error) {
  339. console.error('删除投诉失败:', error)
  340. this.$message.error('操作失败')
  341. }
  342. },
  343. /**
  344. * 搜索变化
  345. * @param {ComplaintQueryParams} params - 搜索参数
  346. * @param {boolean} done - 完成回调
  347. */
  348. searchChange(params, done) {
  349. this.query = params
  350. this.onLoad(this.page, params)
  351. done()
  352. },
  353. /**
  354. * 搜索重置
  355. */
  356. searchReset() {
  357. this.query = {}
  358. this.onLoad(this.page)
  359. },
  360. /**
  361. * 选择变化
  362. * @param {ComplaintRecord[]} val - 选中的行
  363. */
  364. selectionChange(val) {
  365. this.selectionList = val
  366. },
  367. /**
  368. * 当前页变化
  369. * @param {number} currentPage - 当前页
  370. */
  371. currentChange(currentPage) {
  372. this.page.currentPage = currentPage
  373. },
  374. /**
  375. * 页大小变化
  376. * @param {number} pageSize - 页大小
  377. */
  378. sizeChange(pageSize) {
  379. this.page.pageSize = pageSize
  380. },
  381. /**
  382. * 刷新变化
  383. */
  384. refreshChange() {
  385. this.onLoad(this.page, this.query)
  386. },
  387. /**
  388. * 加载数据
  389. * @param {Object} page - 分页信息
  390. * @param {ComplaintQueryParams} [params={}] - 查询参数
  391. */
  392. async onLoad(page, params = {}) {
  393. this.loading = true
  394. try {
  395. const res = await getList(page.currentPage, page.pageSize, Object.assign(params, this.query))
  396. if (res.data.success) {
  397. const data = res.data.data
  398. this.data = data.records
  399. this.page.total = data.total
  400. } else {
  401. this.$message.error(res.data.msg || '查询失败')
  402. }
  403. } catch (error) {
  404. console.error('查询投诉列表失败:', error)
  405. this.$message.error('查询失败')
  406. } finally {
  407. this.loading = false
  408. }
  409. },
  410. /**
  411. * 新增投诉
  412. */
  413. handleAdd() {
  414. this.$refs.crud.rowAdd()
  415. },
  416. /**
  417. * 编辑投诉
  418. * @param {ComplaintRecord} row - 行数据
  419. */
  420. handleEdit(row) {
  421. this.$refs.crud.rowEdit(row, this.data.indexOf(row))
  422. },
  423. /**
  424. * 查看详情
  425. * @param {ComplaintRecord} row - 行数据
  426. */
  427. async handleDetail(row) {
  428. try {
  429. const res = await getDetail(row.id)
  430. if (res.data.success) {
  431. this.detailData = res.data.data
  432. this.detailVisible = true
  433. } else {
  434. this.$message.error(res.data.msg || '获取详情失败')
  435. }
  436. } catch (error) {
  437. console.error('获取投诉详情失败:', error)
  438. this.$message.error('获取详情失败')
  439. }
  440. },
  441. /**
  442. * 处理投诉
  443. * @param {ComplaintRecord} row - 行数据
  444. */
  445. handleProcess(row) {
  446. this.statusDialogTitle = '处理投诉'
  447. this.statusForm = {
  448. id: row.id,
  449. status: COMPLAINT_STATUS.PROCESSING,
  450. closeReason: ''
  451. }
  452. this.currentIds = null
  453. this.statusVisible = true
  454. },
  455. /**
  456. * 关闭投诉
  457. * @param {ComplaintRecord} row - 行数据
  458. */
  459. handleClose(row) {
  460. this.statusDialogTitle = '关闭投诉'
  461. this.statusForm = {
  462. id: row.id,
  463. status: COMPLAINT_STATUS.CLOSED,
  464. closeReason: ''
  465. }
  466. this.currentIds = null
  467. this.statusVisible = true
  468. },
  469. /**
  470. * 打开回复列表
  471. * @param {Object} row - 投诉数据行
  472. */
  473. async handleReplyList(row) {
  474. this.currentComplaintId = row.id
  475. this.currentComplaintNo = row.complaintNo
  476. this.currentComplaintReplyStatus = row.replyStatus
  477. this.replyListVisible = true
  478. this.replyPage.current = 1
  479. await this.loadReplyList()
  480. },
  481. /**
  482. * 加载回复列表
  483. */
  484. async loadReplyList() {
  485. if (!this.currentComplaintId) return
  486. this.replyListLoading = true
  487. try {
  488. const params = {
  489. current: this.replyPage.current,
  490. size: this.replyPage.size,
  491. complaintId: this.currentComplaintId
  492. }
  493. const response = await getReplyList(params)
  494. const { records, total } = response.data.data
  495. this.replyList = records || []
  496. this.replyTotal = total || 0
  497. } catch (error) {
  498. this.$message.error('获取回复列表失败')
  499. console.error('获取回复列表失败:', error)
  500. } finally {
  501. this.replyListLoading = false
  502. }
  503. },
  504. /**
  505. * 回复列表分页大小变化
  506. * @param {number} size - 新的分页大小
  507. */
  508. async handleReplyPageSizeChange(size) {
  509. this.replyPage.size = size
  510. this.replyPage.current = 1
  511. await this.loadReplyList()
  512. },
  513. /**
  514. * 回复列表页码变化
  515. * @param {number} current - 新的页码
  516. */
  517. async handleReplyPageChange(current) {
  518. this.replyPage.current = current
  519. await this.loadReplyList()
  520. },
  521. /**
  522. * 打开新增回复对话框
  523. */
  524. handleAddReply() {
  525. this.replyForm = {
  526. complaintId: this.currentComplaintId,
  527. complaintNo: this.currentComplaintNo,
  528. replyType: REPLY_TYPE.SYSTEM,
  529. replyContent: '',
  530. replyAttachUrl: ''
  531. }
  532. this.addReplyVisible = true
  533. this.$nextTick(() => {
  534. this.$refs.replyForm && this.$refs.replyForm.clearValidate()
  535. })
  536. },
  537. /**
  538. * 确认新增回复
  539. */
  540. async confirmAddReply() {
  541. try {
  542. const valid = await this.$refs.replyForm.validate()
  543. if (!valid) return
  544. this.addReplyLoading = true
  545. const formData = {
  546. ...this.replyForm
  547. }
  548. const response = await addReply(formData)
  549. if (response.data.success) {
  550. this.$message.success('新增回复成功')
  551. this.addReplyVisible = false
  552. // 如果当前投诉的回复状态是未回复,则更新为已回复
  553. if (this.currentComplaintReplyStatus === REPLY_STATUS.NOT_REPLIED) {
  554. try {
  555. const updateData = {
  556. id: this.currentComplaintId,
  557. replyStatus: REPLY_STATUS.REPLIED
  558. }
  559. await update(updateData)
  560. this.currentComplaintReplyStatus = REPLY_STATUS.REPLIED
  561. } catch (error) {
  562. console.error('更新投诉回复状态失败:', error)
  563. }
  564. }
  565. // 重新加载回复列表
  566. await this.loadReplyList()
  567. // 重新加载投诉列表以更新回复状态
  568. this.onLoad(this.page)
  569. } else {
  570. this.$message.error(response.data.msg || '新增回复失败')
  571. }
  572. } catch (error) {
  573. this.$message.error('新增回复失败')
  574. console.error('新增回复失败:', error)
  575. } finally {
  576. this.addReplyLoading = false
  577. }
  578. },
  579. /**
  580. * 获取回复类型标签
  581. * @param {number} replyType - 回复类型
  582. * @returns {string} 回复类型标签
  583. */
  584. getReplyTypeLabel,
  585. /**
  586. * 获取回复类型Element UI标签类型
  587. * @param {number} replyType - 回复类型
  588. * @returns {string} Element UI标签类型
  589. */
  590. getReplyTypeType,
  591. /**
  592. * 获取回复类型颜色
  593. * @param {number} replyType - 回复类型
  594. * @returns {string} 回复类型颜色
  595. */
  596. getReplyTypeColor,
  597. /**
  598. * 批量状态处理
  599. */
  600. handleBatchStatus() {
  601. if (this.selectionList.length === 0) {
  602. this.$message.warning('请选择要处理的投诉')
  603. return
  604. }
  605. this.statusDialogTitle = '批量处理投诉'
  606. this.statusForm = {
  607. status: COMPLAINT_STATUS.PROCESSING,
  608. closeReason: ''
  609. }
  610. this.currentIds = this.selectionList.map(item => item.id)
  611. this.statusVisible = true
  612. },
  613. /**
  614. * 确认状态更新
  615. */
  616. async confirmStatusUpdate() {
  617. try {
  618. await this.$refs.statusForm.validate()
  619. this.statusLoading = true
  620. let res
  621. if (this.currentIds && this.currentIds.length > 0) {
  622. // 批量处理
  623. res = await batchUpdateStatus(this.currentIds, this.statusForm.status, this.statusForm.closeReason)
  624. } else {
  625. // 单个处理
  626. res = await updateStatus(this.statusForm.id, this.statusForm.status, this.statusForm.closeReason)
  627. }
  628. if (res.data.success) {
  629. this.$message.success('操作成功')
  630. this.statusVisible = false
  631. this.onLoad(this.page, this.query)
  632. } else {
  633. this.$message.error(res.data.msg || '操作失败')
  634. }
  635. } catch (error) {
  636. if (error.message) {
  637. console.error('状态更新失败:', error)
  638. this.$message.error('操作失败')
  639. }
  640. } finally {
  641. this.statusLoading = false
  642. }
  643. }
  644. }
  645. }