index.vue 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929
  1. <script setup lang="ts">
  2. import { computed, getCurrentInstance, onMounted, ref, unref } from 'vue'
  3. import { ElMessage, ElMessageBox } from 'element-plus'
  4. import { Delete, Edit, Plus, Search, View } from '@element-plus/icons-vue'
  5. import { useLoading } from '/@/utils/loading-util'
  6. import complaints from '/@/api/system/report/complaints'
  7. import feedback_api from '/@/api/system/report/feedback'
  8. import {
  9. Complaint,
  10. ComplaintArea,
  11. ComplaintQueryParams,
  12. ComplaintStatus,
  13. CreateComplaintRequest,
  14. FeedbackCreateParams,
  15. UpdateComplaintRequest,
  16. } from '/@/api/system/report/type'
  17. import system from '/@/api/system'
  18. import { useAsyncState, useLocalStorage } from '@vueuse/core'
  19. import ReportDetailDialog from '/@/views/system/report/componments/report-detail-dialog.vue'
  20. import { MessageProps } from 'element-plus/es/components/message/src/message'
  21. import { FlowDemoTableColumns } from '/@/views/flow/flowDemo/list/component/model'
  22. import CheckFlow from '/@/components/gFlow/checkFlow.vue'
  23. const { proxy } = getCurrentInstance() as any
  24. //投诉等级,投诉来源,投诉类型
  25. const {
  26. report_level,
  27. report_source,
  28. report_type,
  29. }: {
  30. [key: string]: Array<{
  31. label: string
  32. value: string
  33. }>
  34. } = proxy.useDict('report_level', 'report_source', 'report_type')
  35. // eslint-disable-next-line no-unused-vars
  36. const formatReportLevel = computed<(value: string) => string>(() => {
  37. const levels = unref(report_level)
  38. return (value: string) => {
  39. if (value === undefined) {
  40. return '-'
  41. }
  42. if (levels === undefined) {
  43. return '-'
  44. }
  45. return proxy.selectDictLabel(levels, value)
  46. }
  47. })
  48. // eslint-disable-next-line no-unused-vars
  49. const formatReportType = computed<(value: string) => string>(() => {
  50. const types = unref(report_type)
  51. return (value: string) => {
  52. if (value === undefined) {
  53. return '-'
  54. }
  55. if (types === undefined) {
  56. return '-'
  57. }
  58. return proxy.selectDictLabel(types, value)
  59. }
  60. })
  61. const formatReportStatus = (value: Complaint['status']) => {
  62. let a = '-'
  63. switch (value) {
  64. case ComplaintStatus.COMPLETED:
  65. a = '已完成'
  66. break
  67. case ComplaintStatus.PENDING:
  68. a = '待处理'
  69. break
  70. case ComplaintStatus.PROCESSING:
  71. a = '进行中'
  72. break
  73. }
  74. return a
  75. }
  76. const formatReportStatusTagType = (value: Complaint['status']) => {
  77. let type: MessageProps['type'] | undefined = undefined
  78. switch (value) {
  79. case ComplaintStatus.COMPLETED:
  80. type = 'success'
  81. break
  82. case ComplaintStatus.PENDING:
  83. type = 'info'
  84. break
  85. case ComplaintStatus.PROCESSING:
  86. type = undefined
  87. break
  88. }
  89. return type
  90. }
  91. // 响应式数据
  92. const tableData = ref<Complaint[]>([])
  93. const total = ref(0)
  94. const selectedIds = ref<number[]>([])
  95. const queryRef = ref()
  96. // 查询参数
  97. const queryParams = ref<ComplaintQueryParams>({
  98. pageNum: 1,
  99. pageSize: 20,
  100. })
  101. // 获取列表数据
  102. const { loading, doLoading: getComplaintList } = useLoading(async () => {
  103. const data = await complaints.getList(queryParams.value).catch(() => undefined)
  104. if (!data) {
  105. return
  106. }
  107. tableData.value = data.list
  108. total.value = data.total
  109. })
  110. // 事件处理
  111. const handleSearch = () => {
  112. queryParams.value.pageNum = 1
  113. getComplaintList()
  114. }
  115. const handlePageChange = ({ page, limit }: { page: number; limit: number }) => {
  116. queryParams.value.pageNum = page
  117. queryParams.value.pageSize = limit
  118. getComplaintList()
  119. }
  120. const handleSelectionChange = (selection: Complaint[]) => {
  121. selectedIds.value = selection.map((item) => item.id)
  122. }
  123. // 新增投诉相关
  124. const addDialogVisible = ref(false)
  125. const addFormRef = ref()
  126. const addForm = ref<CreateComplaintRequest>({
  127. title: '',
  128. category: '',
  129. source: '',
  130. area: 'A区' as ComplaintArea,
  131. complainantName: '',
  132. contact: '',
  133. level: '',
  134. content: '',
  135. assignee: undefined,
  136. })
  137. //草稿,若有内容则会patch上去
  138. //添加flag的原因是:useLocalStorage不允许初值为undefined
  139. //flag为undefined证明有草稿,否则无草稿。
  140. type CCRWithDemoFlag = Partial<CreateComplaintRequest> & {
  141. flag: boolean | undefined
  142. }
  143. const demo = useLocalStorage<CCRWithDemoFlag>('system-report-summary-index-demo-params', {
  144. flag: false,
  145. })
  146. onMounted(() => {
  147. if (demo.value.flag !== undefined) {
  148. addForm.value = {
  149. ...(demo.value as CreateComplaintRequest),
  150. }
  151. }
  152. })
  153. // 表单验证规则
  154. const addFormRules = {
  155. title: [{ required: true, message: '请输入投诉标题', trigger: 'blur' }],
  156. category: [{ required: true, message: '请选择投诉类型', trigger: 'change' }],
  157. source: [{ required: true, message: '请选择投诉来源', trigger: 'change' }],
  158. area: [{ required: true, message: '请选择投诉区域', trigger: 'change' }],
  159. complainantName: [{ required: true, message: '请输入投诉人姓名', trigger: 'blur' }],
  160. level: [{ required: true, message: '请选择投诉等级', trigger: 'change' }],
  161. content: [{ required: true, message: '请输入投诉内容', trigger: 'blur' }],
  162. }
  163. const handleAdd = () => {
  164. addDialogVisible.value = true
  165. }
  166. const handleAddCancel = () => {
  167. addDialogVisible.value = false
  168. addFormRef.value?.resetFields()
  169. }
  170. const handleDemoAdded = () => {
  171. demo.value = {
  172. ...addForm.value,
  173. flag: true,
  174. }
  175. ElMessage.success('草稿已经保存')
  176. }
  177. const handleAddConfirm = async () => {
  178. const valid = await addFormRef.value?.validate().catch(() => false)
  179. if (!valid) {
  180. return
  181. }
  182. const result = await complaints
  183. .add(addForm.value)
  184. .then(() => true)
  185. .catch(() => false)
  186. if (result) {
  187. ElMessage.success('新增投诉成功')
  188. //删除草稿
  189. demo.value = {
  190. flag: undefined,
  191. }
  192. //关闭新增对话框
  193. addDialogVisible.value = false
  194. addFormRef.value?.resetFields()
  195. await getComplaintList()
  196. } else {
  197. ElMessage.error('新增投诉失败')
  198. }
  199. }
  200. //修改投诉相关
  201. const editDialogVisible = ref(false)
  202. const editFormRef = ref()
  203. const editForm = ref<UpdateComplaintRequest | undefined>(undefined)
  204. const editFormRules = {
  205. title: [{ required: true, message: '请输入投诉标题', trigger: 'blur' }],
  206. category: [{ required: true, message: '请选择投诉类型', trigger: 'change' }],
  207. source: [{ required: true, message: '请选择投诉来源', trigger: 'change' }],
  208. area: [{ required: true, message: '请选择投诉区域', trigger: 'change' }],
  209. complainantName: [{ required: true, message: '请输入投诉人姓名', trigger: 'blur' }],
  210. level: [{ required: true, message: '请选择投诉等级', trigger: 'change' }],
  211. content: [{ required: true, message: '请输入投诉内容', trigger: 'blur' }],
  212. }
  213. const currentLoadingEdit = ref(-1)
  214. const { loading: loadingEdit, doLoading: handleEdit } = useLoading(async (id: number) => {
  215. currentLoadingEdit.value = id
  216. const data = await complaints.detail(id).catch(() => undefined)
  217. if (!data) {
  218. return
  219. }
  220. editForm.value = data
  221. editDialogVisible.value = true
  222. })
  223. const handleEditCancel = () => {
  224. editDialogVisible.value = false
  225. editFormRef.value?.resetFields()
  226. currentLoadingEdit.value = -1
  227. }
  228. const handleEditConfirm = async () => {
  229. const valid = await editFormRef.value?.validate().catch(() => false)
  230. if (!valid) {
  231. return
  232. }
  233. const data = editForm.value
  234. if (!data) {
  235. return
  236. }
  237. const result = await complaints
  238. .edit(data)
  239. .then(() => true)
  240. .catch(() => false)
  241. if (result) {
  242. ElMessage.success('修改投诉成功')
  243. editDialogVisible.value = false
  244. editFormRef.value?.resetFields()
  245. await getComplaintList()
  246. }
  247. }
  248. const handleDeleteSingle = async (row: Complaint) => {
  249. const status = await ElMessageBox.confirm(`确定要删除投诉 "${row.title}" 吗?`, '提示', {
  250. confirmButtonText: '确定',
  251. cancelButtonText: '取消',
  252. type: 'warning',
  253. })
  254. if (status !== 'confirm') {
  255. return
  256. }
  257. const result = await complaints
  258. .del([row.id])
  259. .then(() => true)
  260. .catch(() => false)
  261. if (result) {
  262. ElMessage.success('删除成功')
  263. await getComplaintList()
  264. }
  265. }
  266. const handleDelete = async () => {
  267. if (selectedIds.value.length === 0) {
  268. ElMessage.warning('请选择要删除的数据')
  269. return
  270. }
  271. const status = await ElMessageBox.confirm(`确定要删除选中的 ${selectedIds.value.length} 条投诉吗?`, '提示', {
  272. confirmButtonText: '确定',
  273. cancelButtonText: '取消',
  274. type: 'warning',
  275. })
  276. if (status !== 'confirm') {
  277. return
  278. }
  279. const result = await complaints
  280. .del(selectedIds.value)
  281. .then(() => true)
  282. .catch(() => false)
  283. if (result) {
  284. ElMessage.success('删除成功')
  285. await getComplaintList()
  286. }
  287. }
  288. // 初始化
  289. onMounted(() => {
  290. getComplaintList()
  291. })
  292. type SimpleUser = {
  293. id: number
  294. userNickname: string
  295. }
  296. //用户获取
  297. const {
  298. state: userList,
  299. isLoading: isLoadingUserList,
  300. execute: loadingUserList,
  301. } = useAsyncState<SimpleUser[]>(async (name: string) => {
  302. //为了防止默认情况下的用户列表不存在已经分配的用户,需要提前获取这个用户的bean。
  303. const user_id = editForm.value?.assignee ?? undefined
  304. const [origin_user, data]: [origin_user: SimpleUser | undefined, data: SimpleUser[]] = await Promise.all([
  305. user_id === undefined ? Promise.resolve(undefined) : system.user.detail(user_id).catch(() => undefined),
  306. system.user
  307. .getList({ keyWords: name, status: 1 })
  308. .then((res: { list: SimpleUser[] }) => res.list)
  309. .catch(() => []),
  310. ])
  311. if (data.length === 0) {
  312. return origin_user !== undefined ? [origin_user] : []
  313. }
  314. if (data.filter((item) => item.id === origin_user?.id).length !== 0) {
  315. return data
  316. }
  317. if (origin_user !== undefined) {
  318. return [origin_user, ...data]
  319. } else {
  320. return data
  321. }
  322. }, [])
  323. const feedback = ref(false)
  324. const feedFormRef = ref()
  325. const feedCreateForm = ref<Omit<FeedbackCreateParams, 'surveyCode'>>({
  326. contactInfo: '',
  327. investigatorName: '',
  328. ticketNo: 0,
  329. processingSpeed: '',
  330. staffAttitude: '',
  331. resolutionEffect: '',
  332. otherSuggestions: '',
  333. })
  334. // 反馈表单验证规则
  335. const feedFormRules = {
  336. surveyCode: [{ required: true, message: '请输入问卷编号', trigger: 'blur' }],
  337. investigatorName: [{ required: true, message: '请输入调查者姓名', trigger: 'blur' }],
  338. contactInfo: [
  339. { required: true, message: '请输入联系信息', trigger: 'blur' },
  340. { pattern: /^(1[3-9]\d{9}|[\w.-]+@[\w.-]+\.\w+)$/, message: '请输入正确的手机号或邮箱', trigger: 'blur' },
  341. ],
  342. processingSpeed: [{ required: true, message: '请选择处理速度评价', trigger: 'change' }],
  343. staffAttitude: [{ required: true, message: '请选择工作人员态度评价', trigger: 'change' }],
  344. resolutionEffect: [{ required: true, message: '请选择解决效果评价', trigger: 'change' }],
  345. }
  346. //投诉等级,投诉来源,投诉类型
  347. const {
  348. related_level,
  349. }: {
  350. [key: string]: Array<{
  351. label: string
  352. value: string
  353. }>
  354. } = proxy.useDict('related_level')
  355. const handleFeedback = (row: Complaint) => {
  356. // 重置表单
  357. feedFormRef.value?.resetFields()
  358. // 设置投诉编号(不可修改)
  359. feedCreateForm.value.ticketNo = row.id
  360. // 清空其他字段
  361. feedCreateForm.value = {
  362. ...feedCreateForm.value,
  363. contactInfo: '',
  364. investigatorName: '',
  365. processingSpeed: '',
  366. staffAttitude: '',
  367. resolutionEffect: '',
  368. otherSuggestions: '',
  369. }
  370. feedback.value = true
  371. }
  372. // 取消反馈
  373. const handleFeedbackCancel = () => {
  374. feedback.value = false
  375. feedFormRef.value?.resetFields()
  376. }
  377. const { loading: createFeedbackLoading, doLoading: createFeedback } = useLoading(async () => {
  378. const valid = await feedFormRef.value?.validate().catch(() => false)
  379. if (!valid) {
  380. return
  381. }
  382. const result = await feedback_api
  383. .create({
  384. surveyCode: `${new Date().getFullYear()}-${new Date().getMonth()}-${new Date().getDay()}-${(Math.random() * 100000).toFixed(0)}`,
  385. ...feedCreateForm.value,
  386. })
  387. .then(() => true)
  388. .catch(() => false)
  389. if (result) {
  390. ElMessage.success('反馈成功')
  391. }
  392. handleFeedbackCancel()
  393. })
  394. const complaintDetailId = ref<number | undefined>(undefined)
  395. const showDetail = ref(false)
  396. const handleDetail = (row: Complaint) => {
  397. complaintDetailId.value = row.id
  398. showDetail.value = true
  399. }
  400. //发起审批
  401. const ckFlowRef = ref()
  402. const handleStartFlow = (row: FlowDemoTableColumns | null) => {
  403. ckFlowRef.value.handleStartFlow(row)
  404. }
  405. </script>
  406. <template>
  407. <div class="page">
  408. <el-card shadow="never">
  409. <!-- 搜索和筛选区域 -->
  410. <el-form :model="queryParams" ref="queryRef" inline class="mb-4">
  411. <el-form-item>
  412. <el-input
  413. v-model="queryParams.name"
  414. placeholder="输入名称搜索设备"
  415. clearable
  416. style="width: 300px"
  417. :prefix-icon="Search"
  418. @keyup.enter="handleSearch"
  419. />
  420. </el-form-item>
  421. <el-form-item>
  422. <el-select v-model="queryParams.status" placeholder="状态" style="width: 120px">
  423. <el-option label="全部" value="" />
  424. <el-option label="待处理" value="pending" />
  425. <el-option label="处理中" value="processing" />
  426. <el-option label="已完成" value="completed" />
  427. </el-select>
  428. </el-form-item>
  429. <el-form-item>
  430. <el-select v-model="queryParams.category" placeholder="分类" style="width: 120px">
  431. <el-option label="全部" value="" />
  432. <el-option v-for="i in report_type" :label="i.label" :value="i.value" :key="i.value" />
  433. </el-select>
  434. </el-form-item>
  435. <el-form-item>
  436. <el-select v-model="queryParams.level" placeholder="等级" style="width: 120px">
  437. <el-option label="全部" value="" />
  438. <el-option v-for="i in report_level" :label="i.label" :value="i.value" :key="i.value"></el-option>
  439. </el-select>
  440. </el-form-item>
  441. <el-form-item>
  442. <el-button type="primary" @click="handleSearch">
  443. <el-icon>
  444. <Search />
  445. </el-icon>
  446. 查询
  447. </el-button>
  448. </el-form-item>
  449. </el-form>
  450. <!-- 操作按钮区域 -->
  451. <div class="flex justify-between items-center mb-4">
  452. <div class="flex gap-2">
  453. <el-button type="primary" @click="handleAdd">
  454. <el-icon>
  455. <Plus />
  456. </el-icon>
  457. 新增投诉
  458. </el-button>
  459. <el-button @click="handleDelete" :disabled="selectedIds.length === 0">
  460. <el-icon>
  461. <Delete />
  462. </el-icon>
  463. 删除
  464. </el-button>
  465. </div>
  466. </div>
  467. <!-- 表格 -->
  468. <el-table :data="tableData" v-loading="loading" @selection-change="handleSelectionChange" style="width: 100%">
  469. <el-table-column type="selection" width="55" align="center" />
  470. <el-table-column prop="id" label="标识" width="160" align="center" />
  471. <el-table-column prop="title" label="投诉标题" min-width="200" show-overflow-tooltip />
  472. <el-table-column prop="category" label="投诉类型" width="120" align="center">
  473. <template #default="{ row }: { row: Complaint }">
  474. <span>{{ formatReportType(row.category) }}</span>
  475. </template>
  476. </el-table-column>
  477. <el-table-column prop="area" label="区域" width="80" align="center" />
  478. <el-table-column prop="level" label="等级" width="80" align="center">
  479. <template #default="{ row }: { row: Complaint }">
  480. <span>{{ formatReportLevel(row.level) }}</span>
  481. </template>
  482. </el-table-column>
  483. <el-table-column prop="status" label="状态" width="100" align="center">
  484. <template #default="{ row }: { row: Complaint }">
  485. <el-tag :type="formatReportStatusTagType(row.status)">{{ formatReportStatus(row.status) }}</el-tag>
  486. </template>
  487. </el-table-column>
  488. <el-table-column prop="updatedAt" label="最后更新时间" width="180" align="center" />
  489. <el-table-column prop="assignee" label="分配给" width="120" align="center" />
  490. <el-table-column label="操作" width="300" align="center" fixed="right">
  491. <template #default="{ row }: { row: Complaint }">
  492. <el-button v-if="row.actionBtn && row.actionBtn.type != 'disabled'" type="primary" link @click="handleStartFlow(row)"
  493. ><el-icon><ele-Coordinate /></el-icon>{{ row.actionBtn.title }}</el-button
  494. >
  495. <el-button size="small" type="primary" link @click="handleDetail(row)">
  496. <el-icon>
  497. <View />
  498. </el-icon>
  499. 详情
  500. </el-button>
  501. <el-button size="small" type="warning" link @click="handleEdit(row.id)" :loading="row.id == currentLoadingEdit && loadingEdit">
  502. <el-icon>
  503. <Edit />
  504. </el-icon>
  505. 修改
  506. </el-button>
  507. <el-button size="small" type="danger" link @click="handleDeleteSingle(row)">
  508. <el-icon>
  509. <Delete />
  510. </el-icon>
  511. 删除
  512. </el-button>
  513. <el-button size="small" type="info" link @click="handleFeedback(row)">
  514. <el-icon>
  515. <Search />
  516. </el-icon>
  517. 反馈
  518. </el-button>
  519. </template>
  520. </el-table-column>
  521. </el-table>
  522. <pagination
  523. v-show="total > 0"
  524. :total="total"
  525. v-model:page="queryParams.pageNum"
  526. v-model:limit="queryParams.pageSize"
  527. @pagination="handlePageChange"
  528. />
  529. </el-card>
  530. <!-- 新增投诉对话框 -->
  531. <el-dialog v-model="addDialogVisible" title="新建投诉" width="800px" :close-on-click-modal="false">
  532. <el-form ref="addFormRef" :model="addForm" :rules="addFormRules" label-width="100px" label-position="left">
  533. <el-row :gutter="35">
  534. <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
  535. <el-form-item label="投诉标题" prop="title" required>
  536. <el-input v-model="addForm.title" placeholder="请输入投诉标题" maxlength="100" show-word-limit />
  537. </el-form-item>
  538. </el-col>
  539. <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
  540. <el-form-item label="投诉类型" prop="category" required>
  541. <el-select v-model="addForm.category" placeholder="选择投诉类型" style="width: 100%">
  542. <el-option v-for="item in report_type" :key="item.value" :label="item.label" :value="item.value" />
  543. </el-select>
  544. </el-form-item>
  545. </el-col>
  546. <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
  547. <el-form-item label="投诉来源" prop="source" required>
  548. <el-select v-model="addForm.source" placeholder="选择投诉来源" style="width: 100%">
  549. <el-option v-for="item in report_source" :key="item.value" :label="item.label" :value="item.value" />
  550. </el-select>
  551. </el-form-item>
  552. </el-col>
  553. <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
  554. <el-form-item label="投诉区域" prop="area" required>
  555. <el-select v-model="addForm.area" placeholder="选择投诉区域" style="width: 100%">
  556. <el-option label="A区" value="A区" />
  557. <el-option label="B区" value="B区" />
  558. </el-select>
  559. </el-form-item>
  560. </el-col>
  561. <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
  562. <el-form-item label="投诉人姓名" prop="complainantName" required>
  563. <el-input v-model="addForm.complainantName" placeholder="请输入投诉人姓名" maxlength="50" />
  564. </el-form-item>
  565. </el-col>
  566. <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
  567. <el-form-item label="联系方式" prop="contact">
  568. <el-input v-model="addForm.contact" placeholder="请输入联系电话或邮箱" maxlength="50" />
  569. </el-form-item>
  570. </el-col>
  571. </el-row>
  572. <el-form-item label="投诉等级" prop="level" required>
  573. <el-radio-group v-model="addForm.level">
  574. <el-radio v-for="item in report_level" :label="item.value" :key="item.value">
  575. <span>{{ item.label }}</span>
  576. </el-radio>
  577. </el-radio-group>
  578. </el-form-item>
  579. <el-form-item label="投诉内容" prop="content" required>
  580. <el-input v-model="addForm.content" type="textarea" :rows="4" placeholder="请详细描述投诉内容..." maxlength="500" show-word-limit />
  581. </el-form-item>
  582. <el-form-item label="指派负责人" prop="assignee">
  583. <el-select
  584. v-model="addForm.assignee"
  585. placeholder="选择负责人"
  586. style="width: 100%"
  587. filterable
  588. remote
  589. :remote-method="(data: string) => loadingUserList(100,data)"
  590. :loading="isLoadingUserList"
  591. clearable
  592. >
  593. <el-option v-for="user in userList" :key="user.id" :label="user.userNickname" :value="user.id" />
  594. </el-select>
  595. </el-form-item>
  596. </el-form>
  597. <template #footer>
  598. <div class="dialog-footer">
  599. <el-button @click="handleAddCancel">取消</el-button>
  600. <el-button type="primary" @click="handleAddConfirm"> 提交投诉 </el-button>
  601. <el-button @click="handleDemoAdded">保存草稿</el-button>
  602. </div>
  603. </template>
  604. </el-dialog>
  605. <!-- 编辑投诉对话框 -->
  606. <el-dialog v-model="editDialogVisible" title="编辑投诉" width="700px" :close-on-click-modal="false">
  607. <el-form ref="editFormRef" :model="editForm" :rules="editFormRules" label-width="100px" label-position="left">
  608. <el-row :gutter="35">
  609. <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
  610. <el-form-item label="投诉标题" prop="title" required>
  611. <el-input v-model="editForm.title" placeholder="请输入投诉标题" maxlength="100" show-word-limit />
  612. </el-form-item>
  613. </el-col>
  614. <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
  615. <el-form-item label="投诉类型" prop="category" required>
  616. <el-select v-model="editForm.category" placeholder="选择投诉类型" style="width: 100%">
  617. <el-option v-for="item in report_type" :key="item.value" :label="item.label" :value="item.value" />
  618. </el-select>
  619. </el-form-item>
  620. </el-col>
  621. <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
  622. <el-form-item label="投诉来源" prop="source" required>
  623. <el-select v-model="editForm.source" placeholder="选择投诉来源" style="width: 100%">
  624. <el-option v-for="item in report_source" :key="item.value" :label="item.label" :value="item.value" />
  625. </el-select>
  626. </el-form-item>
  627. </el-col>
  628. <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
  629. <el-form-item label="投诉区域" prop="area" required>
  630. <el-select v-model="editForm.area" placeholder="选择投诉区域" style="width: 100%">
  631. <el-option label="A区" value="A区" />
  632. <el-option label="B区" value="B区" />
  633. </el-select>
  634. </el-form-item>
  635. </el-col>
  636. <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
  637. <el-form-item label="投诉人姓名" prop="complainantName" required>
  638. <el-input v-model="editForm.complainantName" placeholder="请输入投诉人姓名" maxlength="50" />
  639. </el-form-item>
  640. </el-col>
  641. <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
  642. <el-form-item label="联系方式" prop="contact">
  643. <el-input v-model="editForm.contact" placeholder="请输入联系电话或邮箱" maxlength="50" />
  644. </el-form-item>
  645. </el-col>
  646. </el-row>
  647. <el-form-item label="投诉等级" prop="level" required>
  648. <el-radio-group v-model="editForm.level">
  649. <el-radio v-for="item in report_level" :label="item.value" :key="item.value">
  650. <span>{{ item.label }}</span>
  651. </el-radio>
  652. </el-radio-group>
  653. </el-form-item>
  654. <el-form-item label="投诉内容" prop="content" required>
  655. <el-input v-model="editForm.content" type="textarea" :rows="4" placeholder="请详细描述投诉内容..." maxlength="500" show-word-limit />
  656. </el-form-item>
  657. <el-form-item label="指派负责人" prop="assignee">
  658. <el-select
  659. v-model="editForm.assignee"
  660. placeholder="选择负责人"
  661. style="width: 100%"
  662. filterable
  663. remote
  664. :remote-method="(data: string) => loadingUserList(100,data)"
  665. :loading="isLoadingUserList"
  666. clearable
  667. >
  668. <el-option v-for="user in userList" :key="user.id" :label="user.userNickname" :value="user.id" />
  669. </el-select>
  670. </el-form-item>
  671. </el-form>
  672. <template #footer>
  673. <div class="dialog-footer">
  674. <el-button @click="handleEditCancel">取消</el-button>
  675. <el-button type="primary" @click="handleEditConfirm"> 提交修改 </el-button>
  676. <el-button @click="handleEditCancel">保存草稿</el-button>
  677. </div>
  678. </template>
  679. </el-dialog>
  680. <!-- 反馈对话框 -->
  681. <el-dialog v-model="feedback" title="投诉反馈" width="700px" :close-on-click-modal="false">
  682. <el-form ref="feedFormRef" :model="feedCreateForm" :rules="feedFormRules" label-width="120px" label-position="left">
  683. <!-- <el-form-item label="问卷编号" prop="surveyCode" required>-->
  684. <!-- <el-input v-model="feedCreateForm.surveyCode" placeholder="请输入问卷编号" maxlength="50" />-->
  685. <!-- </el-form-item>-->
  686. <!-- <el-form-item label="投诉编号" prop="ticketNo">-->
  687. <!-- <el-input v-model="feedCreateForm.ticketNo" placeholder="投诉编号" disabled />-->
  688. <!-- </el-form-item>-->
  689. <div style="display: flex">
  690. <div style="flex: 1">
  691. <el-form-item label="调查者姓名" prop="investigatorName" required>
  692. <el-input v-model="feedCreateForm.investigatorName" placeholder="请输入调查者姓名" maxlength="50" />
  693. </el-form-item>
  694. </div>
  695. <div style="width: 32px"></div>
  696. <div style="flex: 1">
  697. <el-form-item label="联系信息" prop="contactInfo" required>
  698. <el-input v-model="feedCreateForm.contactInfo" placeholder="请输入联系电话或邮箱" maxlength="100" />
  699. </el-form-item>
  700. </div>
  701. </div>
  702. <el-form-item label="处理速度" prop="processingSpeed" required>
  703. <el-radio-group v-model="feedCreateForm.processingSpeed">
  704. <el-radio v-for="item in related_level" :label="item.value" :key="item.value">
  705. <span>{{ item.label }}</span>
  706. </el-radio>
  707. </el-radio-group>
  708. <!-- <el-select v-model="feedCreateForm.processingSpeed" placeholder="请选择处理速度评价" style="width: 100%">-->
  709. <!-- <el-option v-for="item in related_level" :key="item.value" :label="item.label" :value="item.value" />-->
  710. <!-- </el-select>-->
  711. </el-form-item>
  712. <el-form-item label="工作人员态度" prop="staffAttitude" required>
  713. <el-radio-group v-model="feedCreateForm.staffAttitude">
  714. <el-radio v-for="item in related_level" :label="item.value" :key="item.value">
  715. <span>{{ item.label }}</span>
  716. </el-radio>
  717. </el-radio-group>
  718. <!-- <el-select v-model="feedCreateForm.staffAttitude" placeholder="请选择工作人员态度评价" style="width: 100%">-->
  719. <!-- <el-option v-for="item in related_level" :key="item.value" :label="item.label" :value="item.value" />-->
  720. <!-- </el-select>-->
  721. </el-form-item>
  722. <el-form-item label="解决效果" prop="resolutionEffect" required>
  723. <el-radio-group v-model="feedCreateForm.resolutionEffect">
  724. <el-radio v-for="item in related_level" :label="item.value" :key="item.value">
  725. <span>{{ item.label }}</span>
  726. </el-radio>
  727. </el-radio-group>
  728. <!-- <el-select v-model="feedCreateForm.resolutionEffect" placeholder="请选择解决效果评价" style="width: 100%">-->
  729. <!-- <el-option v-for="item in related_level" :key="item.value" :label="item.label" :value="item.value" />-->
  730. <!-- </el-select>-->
  731. </el-form-item>
  732. <el-form-item label="其他建议" prop="otherSuggestions">
  733. <el-input
  734. v-model="feedCreateForm.otherSuggestions"
  735. type="textarea"
  736. :rows="4"
  737. placeholder="请输入其他建议..."
  738. maxlength="500"
  739. show-word-limit
  740. />
  741. </el-form-item>
  742. </el-form>
  743. <template #footer>
  744. <div class="dialog-footer">
  745. <el-button @click="handleFeedbackCancel">取消</el-button>
  746. <el-button type="primary" @click="createFeedback" :loading="createFeedbackLoading">
  747. <el-icon>
  748. <Plus />
  749. </el-icon>
  750. 提交反馈
  751. </el-button>
  752. </div>
  753. </template>
  754. </el-dialog>
  755. <report-detail-dialog :id="complaintDetailId" v-model:visible="showDetail" />
  756. <checkFlow ref="ckFlowRef" @getList="getComplaintList"></checkFlow>
  757. </div>
  758. </template>
  759. <style scoped lang="scss">
  760. .page {
  761. padding: 20px;
  762. }
  763. .breadcrumb-container {
  764. font-size: 14px;
  765. color: #606266;
  766. }
  767. .flex {
  768. display: flex;
  769. }
  770. .justify-between {
  771. justify-content: space-between;
  772. }
  773. .items-center {
  774. align-items: center;
  775. }
  776. .gap-2 > * + * {
  777. margin-left: 8px;
  778. }
  779. .mb-4 {
  780. margin-bottom: 16px;
  781. }
  782. .mt-4 {
  783. margin-top: 16px;
  784. }
  785. .text-gray-500 {
  786. color: #909399;
  787. }
  788. .text-gray-400 {
  789. color: #c0c4cc;
  790. }
  791. .text-blue-600 {
  792. color: #409eff;
  793. }
  794. .mx-2 {
  795. margin-left: 8px;
  796. margin-right: 8px;
  797. }
  798. .mr-2 {
  799. margin-right: 8px;
  800. }
  801. .w-2 {
  802. width: 8px;
  803. }
  804. .h-2 {
  805. height: 8px;
  806. }
  807. .bg-blue-500 {
  808. background-color: #409eff;
  809. }
  810. .rounded-full {
  811. border-radius: 50%;
  812. }
  813. .text-sm {
  814. font-size: 12px;
  815. }
  816. .dialog-footer {
  817. display: flex;
  818. justify-content: flex-end;
  819. gap: 8px;
  820. }
  821. </style>