Commit e4877374699083d2fea69045eaad8e8775048d7d
1 parent
e0196f8a
开发完成admin 小区投诉功能
Showing
12 changed files
with
956 additions
and
0 deletions
src/api/complaint/adminComplaintApi.js
0 → 100644
| 1 | +import request from '@/utils/request' | |
| 2 | + | |
| 3 | +// 获取投诉建议列表 | |
| 4 | +export function listAdminComplaints(params) { | |
| 5 | + return new Promise((resolve, reject) => { | |
| 6 | + request({ | |
| 7 | + url: '/complaint.listAdminComplaints', | |
| 8 | + method: 'get', | |
| 9 | + params | |
| 10 | + }).then(response => { | |
| 11 | + const res = response.data | |
| 12 | + if (res.code === 0) { | |
| 13 | + resolve({ | |
| 14 | + data: res.data, | |
| 15 | + total: res.total | |
| 16 | + }) | |
| 17 | + } else { | |
| 18 | + reject(new Error(res.msg || 'Failed to get complaint list')) | |
| 19 | + } | |
| 20 | + }).catch(error => { | |
| 21 | + reject(error) | |
| 22 | + }) | |
| 23 | + }) | |
| 24 | +} | |
| 25 | + | |
| 26 | +// 获取管理员小区列表 | |
| 27 | +export function listAdminCommunitys(params) { | |
| 28 | + return new Promise((resolve, reject) => { | |
| 29 | + request({ | |
| 30 | + url: '/community.listAdminCommunitys', | |
| 31 | + method: 'get', | |
| 32 | + params | |
| 33 | + }).then(response => { | |
| 34 | + const res = response.data | |
| 35 | + if (res.code === 0) { | |
| 36 | + resolve({ | |
| 37 | + data: res.data | |
| 38 | + }) | |
| 39 | + } else { | |
| 40 | + reject(new Error(res.msg || 'Failed to get community list')) | |
| 41 | + } | |
| 42 | + }).catch(error => { | |
| 43 | + reject(error) | |
| 44 | + }) | |
| 45 | + }) | |
| 46 | +} | |
| 0 | 47 | \ No newline at end of file | ... | ... |
src/api/complaint/adminComplaintDetailApi.js
0 → 100644
| 1 | +import request from '@/utils/request' | |
| 2 | + | |
| 3 | +// 获取投诉详情 | |
| 4 | +export function getComplaintDetail(params) { | |
| 5 | + return new Promise((resolve, reject) => { | |
| 6 | + request({ | |
| 7 | + url: '/complaint.listAdminComplaints', | |
| 8 | + method: 'get', | |
| 9 | + params | |
| 10 | + }).then(response => { | |
| 11 | + const res = response.data | |
| 12 | + if (res.code == 0) { | |
| 13 | + resolve(res) | |
| 14 | + } else { | |
| 15 | + reject(new Error(res.msg || '获取投诉详情失败')) | |
| 16 | + } | |
| 17 | + }).catch(error => { | |
| 18 | + reject(error) | |
| 19 | + }) | |
| 20 | + }) | |
| 21 | +} | |
| 22 | + | |
| 23 | +// 获取投诉事件列表 | |
| 24 | +export function listComplaintEvents(params) { | |
| 25 | + return new Promise((resolve, reject) => { | |
| 26 | + request({ | |
| 27 | + url: '/complaint.listAdminComplaintEvent', | |
| 28 | + method: 'get', | |
| 29 | + params | |
| 30 | + }).then(response => { | |
| 31 | + const res = response.data | |
| 32 | + if (res.code == 0) { | |
| 33 | + resolve(res) | |
| 34 | + } else { | |
| 35 | + reject(new Error(res.msg || '获取投诉事件列表失败')) | |
| 36 | + } | |
| 37 | + }).catch(error => { | |
| 38 | + reject(error) | |
| 39 | + }) | |
| 40 | + }) | |
| 41 | +} | |
| 42 | + | |
| 43 | +// 获取投诉评价列表 | |
| 44 | +export function listComplaintAppraises(params) { | |
| 45 | + return new Promise((resolve, reject) => { | |
| 46 | + request({ | |
| 47 | + url: '/complaintAppraise.listAdminComplaintAppraise', | |
| 48 | + method: 'get', | |
| 49 | + params | |
| 50 | + }).then(response => { | |
| 51 | + const res = response.data | |
| 52 | + if (res.code == 0) { | |
| 53 | + resolve(res) | |
| 54 | + } else { | |
| 55 | + reject(new Error(res.msg || '获取投诉评价列表失败')) | |
| 56 | + } | |
| 57 | + }).catch(error => { | |
| 58 | + reject(error) | |
| 59 | + }) | |
| 60 | + }) | |
| 61 | +} | |
| 62 | + | |
| 63 | +// 获取投诉类型列表 | |
| 64 | +export function listComplaintTypes(params) { | |
| 65 | + return new Promise((resolve, reject) => { | |
| 66 | + request({ | |
| 67 | + url: '/complaintType.listAdminComplaintType', | |
| 68 | + method: 'get', | |
| 69 | + params | |
| 70 | + }).then(response => { | |
| 71 | + const res = response.data | |
| 72 | + if (res.code == 0) { | |
| 73 | + resolve(res) | |
| 74 | + } else { | |
| 75 | + reject(new Error(res.msg || '获取投诉类型列表失败')) | |
| 76 | + } | |
| 77 | + }).catch(error => { | |
| 78 | + reject(error) | |
| 79 | + }) | |
| 80 | + }) | |
| 81 | +} | |
| 0 | 82 | \ No newline at end of file | ... | ... |
src/components/community/viewImage.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <div id="view-image" v-show="visible"> | |
| 3 | + <div class="image-container"> | |
| 4 | + <img :src="imageUrl" :width="imageWidth" :height="imageHeight" @error="handleImageError" /> | |
| 5 | + <span class="el-icon-close close-icon" @click="close" /> | |
| 6 | + </div> | |
| 7 | + </div> | |
| 8 | +</template> | |
| 9 | + | |
| 10 | +<script> | |
| 11 | +export default { | |
| 12 | + name: 'ViewImage', | |
| 13 | + data() { | |
| 14 | + return { | |
| 15 | + visible: false, | |
| 16 | + imageUrl: '', | |
| 17 | + imageWidth: 800, | |
| 18 | + imageHeight: 800 | |
| 19 | + } | |
| 20 | + }, | |
| 21 | + methods: { | |
| 22 | + open(url) { | |
| 23 | + this.imageUrl = url | |
| 24 | + this.visible = true | |
| 25 | + this.enterFullscreen() | |
| 26 | + const img = new Image() | |
| 27 | + img.src = url | |
| 28 | + img.onload = () => { | |
| 29 | + const imgScale = img.width / img.height | |
| 30 | + this.imageWidth = 800 | |
| 31 | + this.imageHeight = 800 / imgScale | |
| 32 | + } | |
| 33 | + }, | |
| 34 | + close() { | |
| 35 | + this.exitFullscreen() | |
| 36 | + this.visible = false | |
| 37 | + }, | |
| 38 | + enterFullscreen() { | |
| 39 | + const element = document.getElementById('view-image') | |
| 40 | + if (element.requestFullscreen) { | |
| 41 | + element.requestFullscreen() | |
| 42 | + } else if (element.mozRequestFullScreen) { | |
| 43 | + element.mozRequestFullScreen() | |
| 44 | + } else if (element.webkitRequestFullscreen) { | |
| 45 | + element.webkitRequestFullscreen() | |
| 46 | + } else if (element.msRequestFullscreen) { | |
| 47 | + element.msRequestFullscreen() | |
| 48 | + } | |
| 49 | + }, | |
| 50 | + exitFullscreen() { | |
| 51 | + if (document.exitFullscreen) { | |
| 52 | + document.exitFullscreen() | |
| 53 | + } else if (document.mozCancelFullScreen) { | |
| 54 | + document.mozCancelFullScreen() | |
| 55 | + } else if (document.webkitExitFullscreen) { | |
| 56 | + document.webkitExitFullscreen() | |
| 57 | + } | |
| 58 | + }, | |
| 59 | + handleImageError(e) { | |
| 60 | + e.target.src = '/img/noPhoto.jpg' | |
| 61 | + } | |
| 62 | + } | |
| 63 | +} | |
| 64 | +</script> | |
| 65 | + | |
| 66 | +<style scoped> | |
| 67 | +#view-image { | |
| 68 | + position: fixed; | |
| 69 | + top: 0; | |
| 70 | + left: 0; | |
| 71 | + width: 100%; | |
| 72 | + height: 100%; | |
| 73 | + background-color: rgba(0, 0, 0, 0.5); | |
| 74 | + z-index: 9999; | |
| 75 | + display: flex; | |
| 76 | + justify-content: center; | |
| 77 | + align-items: center; | |
| 78 | +} | |
| 79 | + | |
| 80 | +.image-container { | |
| 81 | + position: relative; | |
| 82 | + background-color: #fff; | |
| 83 | + padding: 20px; | |
| 84 | + border-radius: 4px; | |
| 85 | +} | |
| 86 | + | |
| 87 | +.close-icon { | |
| 88 | + position: absolute; | |
| 89 | + right: 20px; | |
| 90 | + top: 20px; | |
| 91 | + font-size: 20px; | |
| 92 | + color: red; | |
| 93 | + cursor: pointer; | |
| 94 | +} | |
| 95 | +</style> | |
| 0 | 96 | \ No newline at end of file | ... | ... |
src/components/complaint/aComplaintDetailAppraise.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <div class="appraise-container"> | |
| 3 | + <el-table :data="appraises" border style="width: 100%"> | |
| 4 | + <el-table-column prop="createUserName" :label="$t('complaintDetailAppraise.user')" align="center"></el-table-column> | |
| 5 | + <el-table-column prop="context" :label="$t('complaintDetailAppraise.content')" align="center"></el-table-column> | |
| 6 | + <el-table-column prop="score" :label="$t('complaintDetailAppraise.score')" align="center"></el-table-column> | |
| 7 | + <el-table-column prop="state" :label="$t('complaintDetailAppraise.status')" align="center"> | |
| 8 | + <template slot-scope="scope"> | |
| 9 | + {{ scope.row.state === 'W' ? $t('complaintDetailAppraise.pending') : $t('complaintDetailAppraise.replied') }} | |
| 10 | + </template> | |
| 11 | + </el-table-column> | |
| 12 | + <el-table-column prop="createTime" :label="$t('complaintDetailAppraise.time')" align="center"></el-table-column> | |
| 13 | + <el-table-column prop="replyUserName" :label="$t('complaintDetailAppraise.replier')" align="center"></el-table-column> | |
| 14 | + <el-table-column prop="replyContext" :label="$t('complaintDetailAppraise.replyContent')" align="center"></el-table-column> | |
| 15 | + </el-table> | |
| 16 | + </div> | |
| 17 | +</template> | |
| 18 | + | |
| 19 | +<script> | |
| 20 | +import { listComplaintAppraises } from '@/api/complaint/adminComplaintDetailApi' | |
| 21 | + | |
| 22 | +export default { | |
| 23 | + name: 'AComplaintDetailAppraise', | |
| 24 | + props: { | |
| 25 | + complaintId: { | |
| 26 | + type: String, | |
| 27 | + required: true | |
| 28 | + } | |
| 29 | + }, | |
| 30 | + data() { | |
| 31 | + return { | |
| 32 | + appraises: [] | |
| 33 | + } | |
| 34 | + }, | |
| 35 | + methods: { | |
| 36 | + async loadData() { | |
| 37 | + try { | |
| 38 | + const params = { | |
| 39 | + complaintId: this.complaintId, | |
| 40 | + page: 1, | |
| 41 | + row: 100 | |
| 42 | + } | |
| 43 | + const { data } = await listComplaintAppraises(params) | |
| 44 | + this.appraises = data || [] | |
| 45 | + } catch (error) { | |
| 46 | + this.$message.error(this.$t('complaintDetailAppraise.fetchError')) | |
| 47 | + } | |
| 48 | + } | |
| 49 | + } | |
| 50 | +} | |
| 51 | +</script> | |
| 52 | + | |
| 53 | +<style lang="scss" scoped> | |
| 54 | +.appraise-container { | |
| 55 | + padding: 20px 0; | |
| 56 | +} | |
| 57 | +</style> | |
| 0 | 58 | \ No newline at end of file | ... | ... |
src/components/complaint/aComplaintDetailEvent.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <div class="event-container"> | |
| 3 | + <el-table :data="events" border style="width: 100%"> | |
| 4 | + <el-table-column prop="eventType" :label="$t('complaintDetailEvent.type')" align="center"> | |
| 5 | + <template slot-scope="scope"> | |
| 6 | + {{ getEventTypeName(scope.row.eventType) }} | |
| 7 | + </template> | |
| 8 | + </el-table-column> | |
| 9 | + <el-table-column prop="createUserName" :label="$t('complaintDetailEvent.operator')" align="center"></el-table-column> | |
| 10 | + <el-table-column prop="remark" :label="$t('complaintDetailEvent.remark')" align="center"></el-table-column> | |
| 11 | + <el-table-column prop="createTime" :label="$t('complaintDetailEvent.time')" align="center"></el-table-column> | |
| 12 | + </el-table> | |
| 13 | + </div> | |
| 14 | +</template> | |
| 15 | + | |
| 16 | +<script> | |
| 17 | +import { listComplaintEvents } from '@/api/complaint/adminComplaintDetailApi' | |
| 18 | + | |
| 19 | +export default { | |
| 20 | + name: 'AComplaintDetailEvent', | |
| 21 | + props: { | |
| 22 | + complaintId: { | |
| 23 | + type: String, | |
| 24 | + required: true | |
| 25 | + } | |
| 26 | + }, | |
| 27 | + data() { | |
| 28 | + return { | |
| 29 | + events: [] | |
| 30 | + } | |
| 31 | + }, | |
| 32 | + methods: { | |
| 33 | + async loadData() { | |
| 34 | + try { | |
| 35 | + const params = { | |
| 36 | + complaintId: this.complaintId, | |
| 37 | + page: 1, | |
| 38 | + row: 100 | |
| 39 | + } | |
| 40 | + const { data } = await listComplaintEvents(params) | |
| 41 | + this.events = data || [] | |
| 42 | + } catch (error) { | |
| 43 | + this.$message.error(this.$t('complaintDetailEvent.fetchError')) | |
| 44 | + } | |
| 45 | + }, | |
| 46 | + getEventTypeName(eventType) { | |
| 47 | + switch (eventType) { | |
| 48 | + case '1000': return this.$t('complaintDetailEvent.submit') | |
| 49 | + case '1001': return this.$t('complaintDetailEvent.process') | |
| 50 | + case '2002': return this.$t('complaintDetailEvent.evaluate') | |
| 51 | + default: return this.$t('complaintDetailEvent.reply') | |
| 52 | + } | |
| 53 | + } | |
| 54 | + } | |
| 55 | +} | |
| 56 | +</script> | |
| 57 | + | |
| 58 | +<style lang="scss" scoped> | |
| 59 | +.event-container { | |
| 60 | + padding: 20px 0; | |
| 61 | +} | |
| 62 | +</style> | |
| 0 | 63 | \ No newline at end of file | ... | ... |
src/components/complaint/aComplaintDetailType.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <div class="type-container"> | |
| 3 | + <el-table :data="complaintTypes" border style="width: 100%"> | |
| 4 | + <el-table-column prop="typeName" :label="$t('complaintDetailType.name')" align="center"></el-table-column> | |
| 5 | + <el-table-column prop="notifyWay" :label="$t('complaintDetailType.notifyWay')" align="center"> | |
| 6 | + <template slot-scope="scope"> | |
| 7 | + {{ scope.row.notifyWay === 'SMS' ? $t('complaintDetailType.sms') : $t('complaintDetailType.wechat') }} | |
| 8 | + </template> | |
| 9 | + </el-table-column> | |
| 10 | + <el-table-column prop="appraiseReply" :label="$t('complaintDetailType.replyType')" align="center"> | |
| 11 | + <template slot-scope="scope"> | |
| 12 | + {{ scope.row.appraiseReply === 'Y' ? $t('complaintDetailType.autoReply') : $t('complaintDetailType.manualReply') }} | |
| 13 | + </template> | |
| 14 | + </el-table-column> | |
| 15 | + <el-table-column :label="$t('complaintDetailType.handler')" align="center"> | |
| 16 | + <template slot-scope="scope"> | |
| 17 | + <div v-for="(staff, index) in scope.row.staffs" :key="index"> | |
| 18 | + {{ staff.staffName }} | |
| 19 | + </div> | |
| 20 | + </template> | |
| 21 | + </el-table-column> | |
| 22 | + <el-table-column prop="createTime" :label="$t('complaintDetailType.createTime')" align="center"></el-table-column> | |
| 23 | + </el-table> | |
| 24 | + </div> | |
| 25 | +</template> | |
| 26 | + | |
| 27 | +<script> | |
| 28 | +import { listComplaintTypes } from '@/api/complaint/adminComplaintDetailApi' | |
| 29 | + | |
| 30 | +export default { | |
| 31 | + name: 'AComplaintDetailType', | |
| 32 | + props: { | |
| 33 | + complaintId: { | |
| 34 | + type: String, | |
| 35 | + default: '' | |
| 36 | + }, | |
| 37 | + typeCd: { | |
| 38 | + type: String, | |
| 39 | + default: '' | |
| 40 | + } | |
| 41 | + }, | |
| 42 | + data() { | |
| 43 | + return { | |
| 44 | + complaintTypes: [] | |
| 45 | + } | |
| 46 | + }, | |
| 47 | + methods: { | |
| 48 | + async loadData() { | |
| 49 | + try { | |
| 50 | + const params = { | |
| 51 | + typeCd: this.typeCd, | |
| 52 | + page: 1, | |
| 53 | + row: 100 | |
| 54 | + } | |
| 55 | + const { data } = await listComplaintTypes( params ) | |
| 56 | + this.complaintTypes = data || [] | |
| 57 | + } catch (error) { | |
| 58 | + this.$message.error(this.$t('complaintDetailType.fetchError')) | |
| 59 | + } | |
| 60 | + } | |
| 61 | + } | |
| 62 | +} | |
| 63 | +</script> | |
| 64 | + | |
| 65 | +<style lang="scss" scoped> | |
| 66 | +.type-container { | |
| 67 | + padding: 20px 0; | |
| 68 | +} | |
| 69 | +</style> | |
| 0 | 70 | \ No newline at end of file | ... | ... |
src/i18n/index.js
| ... | ... | @@ -72,6 +72,8 @@ import { messages as adminInspectionPlanMessages } from '../views/inspection/adm |
| 72 | 72 | import { messages as aInspectionPlanDetailMessages } from '../views/inspection/aInspectionPlanDetailLang' |
| 73 | 73 | import { messages as adminInspectionTaskMessages } from '../views/inspection/adminInspectionTaskLang' |
| 74 | 74 | import { messages as adminInspectionTaskDetailMessages } from '../views/inspection/adminInspectionTaskDetailLang' |
| 75 | +import { messages as adminComplaintMessages } from '../views/complaint/adminComplaintLang' | |
| 76 | +import { messages as adminComplaintDetailMessages } from '../views/complaint/adminComplaintDetailLang' | |
| 75 | 77 | |
| 76 | 78 | Vue.use(VueI18n) |
| 77 | 79 | |
| ... | ... | @@ -148,6 +150,8 @@ const messages = { |
| 148 | 150 | ...aInspectionPlanDetailMessages.en, |
| 149 | 151 | ...adminInspectionTaskMessages.en, |
| 150 | 152 | ...adminInspectionTaskDetailMessages.en, |
| 153 | + ...adminComplaintMessages.en, | |
| 154 | + ...adminComplaintDetailMessages.en, | |
| 151 | 155 | }, |
| 152 | 156 | zh: { |
| 153 | 157 | ...loginMessages.zh, |
| ... | ... | @@ -220,6 +224,8 @@ const messages = { |
| 220 | 224 | ...aInspectionPlanDetailMessages.zh, |
| 221 | 225 | ...adminInspectionTaskMessages.zh, |
| 222 | 226 | ...adminInspectionTaskDetailMessages.zh, |
| 227 | + ...adminComplaintMessages.zh, | |
| 228 | + ...adminComplaintDetailMessages.zh, | |
| 223 | 229 | } |
| 224 | 230 | } |
| 225 | 231 | ... | ... |
src/router/index.js
| ... | ... | @@ -346,6 +346,16 @@ const routes = [ |
| 346 | 346 | name: '/views/inspection/adminInspectionTaskDetail', |
| 347 | 347 | component: () => import('@/views/inspection/adminInspectionTaskDetailList.vue') |
| 348 | 348 | }, |
| 349 | + { | |
| 350 | + path:'/pages/complaint/adminComplaint', | |
| 351 | + name:'/pages/complaint/adminComplaint', | |
| 352 | + component: () => import('@/views/complaint/adminComplaintList.vue') | |
| 353 | + }, | |
| 354 | + { | |
| 355 | + path:'/views/complaint/adminComplaintDetail', | |
| 356 | + name:'/views/complaint/adminComplaintDetail', | |
| 357 | + component: () => import('@/views/complaint/adminComplaintDetailList.vue') | |
| 358 | + }, | |
| 349 | 359 | // 其他子路由可以在这里添加 |
| 350 | 360 | ] |
| 351 | 361 | }, | ... | ... |
src/views/complaint/adminComplaintDetailLang.js
0 → 100644
| 1 | +export const messages = { | |
| 2 | + en: { | |
| 3 | + complaintDetail: { | |
| 4 | + title: 'Complaint Details', | |
| 5 | + orderId: 'Order ID', | |
| 6 | + type: 'Type', | |
| 7 | + room: 'Room', | |
| 8 | + contact: 'Contact', | |
| 9 | + phone: 'Phone', | |
| 10 | + status: 'Status', | |
| 11 | + createTime: 'Create Time', | |
| 12 | + content: 'Content', | |
| 13 | + workflow: 'Workflow', | |
| 14 | + evaluation: 'Evaluation', | |
| 15 | + fetchError: 'Failed to fetch complaint details' | |
| 16 | + }, | |
| 17 | + complaintDetailEvent: { | |
| 18 | + type: 'Type', | |
| 19 | + operator: 'Operator', | |
| 20 | + remark: 'Remark', | |
| 21 | + time: 'Time', | |
| 22 | + submit: 'Submit', | |
| 23 | + process: 'Process', | |
| 24 | + evaluate: 'Evaluate', | |
| 25 | + reply: 'Reply', | |
| 26 | + fetchError: 'Failed to fetch event list' | |
| 27 | + }, | |
| 28 | + complaintDetailAppraise: { | |
| 29 | + user: 'User Name', | |
| 30 | + content: 'Evaluation Content', | |
| 31 | + score: 'Evaluation Score', | |
| 32 | + status: 'Evaluation Status', | |
| 33 | + time: 'Evaluation Time', | |
| 34 | + replier: 'Replier', | |
| 35 | + replyContent: 'Reply Content', | |
| 36 | + pending: 'Pending Reply', | |
| 37 | + replied: 'Replied', | |
| 38 | + fetchError: 'Failed to fetch evaluation list' | |
| 39 | + }, | |
| 40 | + complaintDetailType: { | |
| 41 | + name: 'Type Name', | |
| 42 | + notifyWay: 'Notification Method', | |
| 43 | + replyType: 'Reply Type', | |
| 44 | + handler: 'Handler', | |
| 45 | + createTime: 'Create Time', | |
| 46 | + sms: 'SMS', | |
| 47 | + wechat: 'WeChat', | |
| 48 | + autoReply: 'Auto Reply', | |
| 49 | + manualReply: 'Manual Reply', | |
| 50 | + fetchError: 'Failed to fetch type list' | |
| 51 | + } | |
| 52 | + }, | |
| 53 | + zh: { | |
| 54 | + complaintDetail: { | |
| 55 | + title: '投诉详情', | |
| 56 | + orderId: '订单编号', | |
| 57 | + type: '类型', | |
| 58 | + room: '房屋', | |
| 59 | + contact: '联系人', | |
| 60 | + phone: '联系电话', | |
| 61 | + status: '状态', | |
| 62 | + createTime: '创建时间', | |
| 63 | + content: '投诉内容', | |
| 64 | + workflow: '工单流转', | |
| 65 | + evaluation: '工单评价', | |
| 66 | + fetchError: '获取投诉详情失败' | |
| 67 | + }, | |
| 68 | + complaintDetailEvent: { | |
| 69 | + type: '类型', | |
| 70 | + operator: '操作人', | |
| 71 | + remark: '说明', | |
| 72 | + time: '时间', | |
| 73 | + submit: '提交', | |
| 74 | + process: '投诉处理', | |
| 75 | + evaluate: '评价', | |
| 76 | + reply: '评价回复', | |
| 77 | + fetchError: '获取事件列表失败' | |
| 78 | + }, | |
| 79 | + complaintDetailAppraise: { | |
| 80 | + user: '用户名称', | |
| 81 | + content: '评价内容', | |
| 82 | + score: '评价得分', | |
| 83 | + status: '评价状态', | |
| 84 | + time: '评价时间', | |
| 85 | + replier: '回复人', | |
| 86 | + replyContent: '回复内容', | |
| 87 | + pending: '待回复', | |
| 88 | + replied: '已回复', | |
| 89 | + fetchError: '获取评价列表失败' | |
| 90 | + }, | |
| 91 | + complaintDetailType: { | |
| 92 | + name: '类型名称', | |
| 93 | + notifyWay: '通知方式', | |
| 94 | + replyType: '评价回复', | |
| 95 | + handler: '处理人', | |
| 96 | + createTime: '创建时间', | |
| 97 | + sms: '短信', | |
| 98 | + wechat: '微信', | |
| 99 | + autoReply: '自动回复', | |
| 100 | + manualReply: '人工回复', | |
| 101 | + fetchError: '获取类型列表失败' | |
| 102 | + } | |
| 103 | + } | |
| 104 | +} | |
| 0 | 105 | \ No newline at end of file | ... | ... |
src/views/complaint/adminComplaintDetailList.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <div class="complaint-detail-container"> | |
| 3 | + <el-card class="box-card"> | |
| 4 | + <div slot="header" class="clearfix flex justify-between"> | |
| 5 | + <span>{{ $t('complaintDetail.title') }}</span> | |
| 6 | + <el-button type="link" size="mini" @click="goBack"> | |
| 7 | + {{ $t('common.back') }} | |
| 8 | + </el-button> | |
| 9 | + </div> | |
| 10 | + <div class="card-content"> | |
| 11 | + <el-row :gutter="20" class="info-section text-left"> | |
| 12 | + <el-col :span="6"> | |
| 13 | + <div class="info-item"> | |
| 14 | + <label>{{ $t('complaintDetail.orderId') }}:</label> | |
| 15 | + <span>{{ complaintDetail.complaintId }}</span> | |
| 16 | + </div> | |
| 17 | + </el-col> | |
| 18 | + <el-col :span="6"> | |
| 19 | + <div class="info-item"> | |
| 20 | + <label>{{ $t('complaintDetail.type') }}:</label> | |
| 21 | + <span>{{ complaintDetail.typeName }}</span> | |
| 22 | + </div> | |
| 23 | + </el-col> | |
| 24 | + <el-col :span="6"> | |
| 25 | + <div class="info-item"> | |
| 26 | + <label>{{ $t('complaintDetail.room') }}:</label> | |
| 27 | + <span>{{ complaintDetail.roomName }}</span> | |
| 28 | + </div> | |
| 29 | + </el-col> | |
| 30 | + <el-col :span="6"> | |
| 31 | + <div class="info-item"> | |
| 32 | + <label>{{ $t('complaintDetail.contact') }}:</label> | |
| 33 | + <span>{{ complaintDetail.complaintName }}</span> | |
| 34 | + </div> | |
| 35 | + </el-col> | |
| 36 | + </el-row> | |
| 37 | + | |
| 38 | + | |
| 39 | + <el-row :gutter="20" class="info-section text-left"> | |
| 40 | + <el-col :span="6"> | |
| 41 | + <div class="info-item"> | |
| 42 | + <label>{{ $t('complaintDetail.phone') }}:</label> | |
| 43 | + <span>{{ complaintDetail.tel }}</span> | |
| 44 | + </div> | |
| 45 | + </el-col> | |
| 46 | + <el-col :span="6"> | |
| 47 | + <div class="info-item"> | |
| 48 | + <label>{{ $t('complaintDetail.status') }}:</label> | |
| 49 | + <span>{{ complaintDetail.stateName }}</span> | |
| 50 | + </div> | |
| 51 | + </el-col> | |
| 52 | + <el-col :span="12"> | |
| 53 | + <div class="info-item"> | |
| 54 | + <label>{{ $t('complaintDetail.createTime') }}:</label> | |
| 55 | + <span>{{ complaintDetail.createTime }}</span> | |
| 56 | + </div> | |
| 57 | + </el-col> | |
| 58 | + </el-row> | |
| 59 | + | |
| 60 | + <el-row :gutter="20" class="info-section text-left"> | |
| 61 | + <el-col :span="24"> | |
| 62 | + <div class="info-item"> | |
| 63 | + <label>{{ $t('complaintDetail.content') }}:</label> | |
| 64 | + <span>{{ complaintDetail.context }}</span> | |
| 65 | + </div> | |
| 66 | + </el-col> | |
| 67 | + </el-row> | |
| 68 | + </div> | |
| 69 | + <divider></divider> | |
| 70 | + | |
| 71 | + <el-tabs v-model="activeTab" @tab-click="handleTabClick(activeTab)"> | |
| 72 | + <el-tab-pane :label="$t('complaintDetail.workflow')" name="aComplaintDetailEvent"> | |
| 73 | + <a-complaint-detail-event ref="aComplaintDetailEvent" | |
| 74 | + :complaint-id="complaintDetail.complaintId"></a-complaint-detail-event> | |
| 75 | + </el-tab-pane> | |
| 76 | + <el-tab-pane :label="$t('complaintDetail.evaluation')" name="aComplaintDetailAppraise"> | |
| 77 | + <a-complaint-detail-appraise ref="aComplaintDetailAppraise" | |
| 78 | + :complaint-id="complaintDetail.complaintId"></a-complaint-detail-appraise> | |
| 79 | + </el-tab-pane> | |
| 80 | + <el-tab-pane :label="$t('complaintDetail.type')" name="aComplaintDetailType"> | |
| 81 | + <a-complaint-detail-type ref="aComplaintDetailType" :complaint-id="complaintDetail.complaintId" | |
| 82 | + :type-cd="complaintDetail.typeCd"></a-complaint-detail-type> | |
| 83 | + </el-tab-pane> | |
| 84 | + </el-tabs> | |
| 85 | + | |
| 86 | + </el-card> | |
| 87 | + </div> | |
| 88 | +</template> | |
| 89 | + | |
| 90 | +<script> | |
| 91 | +import { getComplaintDetail } from '@/api/complaint/adminComplaintDetailApi' | |
| 92 | +import AComplaintDetailEvent from '@/components/complaint/aComplaintDetailEvent' | |
| 93 | +import AComplaintDetailAppraise from '@/components/complaint/aComplaintDetailAppraise' | |
| 94 | +import AComplaintDetailType from '@/components/complaint/aComplaintDetailType' | |
| 95 | +import divider from '@/components/system/divider' | |
| 96 | + | |
| 97 | +export default { | |
| 98 | + name: 'AdminComplaintDetail', | |
| 99 | + components: { | |
| 100 | + AComplaintDetailEvent, | |
| 101 | + AComplaintDetailAppraise, | |
| 102 | + AComplaintDetailType, | |
| 103 | + divider | |
| 104 | + }, | |
| 105 | + data() { | |
| 106 | + return { | |
| 107 | + complaintDetail: { | |
| 108 | + complaintId: '', | |
| 109 | + typeName: '', | |
| 110 | + typeCd: '', | |
| 111 | + roomName: '', | |
| 112 | + complaintName: '', | |
| 113 | + tel: '', | |
| 114 | + stateName: '', | |
| 115 | + createTime: '', | |
| 116 | + context: '' | |
| 117 | + }, | |
| 118 | + activeTab: 'aComplaintDetailEvent' | |
| 119 | + } | |
| 120 | + }, | |
| 121 | + created() { | |
| 122 | + this.complaintDetail.complaintId = this.$route.query.complaintId | |
| 123 | + if (this.$route.query.currentTab) { | |
| 124 | + this.activeTab = this.$route.query.currentTab | |
| 125 | + } | |
| 126 | + this.loadComplaintDetail() | |
| 127 | + }, | |
| 128 | + methods: { | |
| 129 | + async loadComplaintDetail() { | |
| 130 | + try { | |
| 131 | + const params = { | |
| 132 | + complaintId: this.complaintDetail.complaintId, | |
| 133 | + page: 1, | |
| 134 | + row: 1, | |
| 135 | + ownerTypeCd: '1001' | |
| 136 | + } | |
| 137 | + const { data } = await getComplaintDetail(params) | |
| 138 | + if (data && data.length > 0) { | |
| 139 | + Object.assign(this.complaintDetail, data[0]) | |
| 140 | + } | |
| 141 | + } catch (error) { | |
| 142 | + this.$message.error(this.$t('complaintDetail.fetchError')) | |
| 143 | + } | |
| 144 | + }, | |
| 145 | + goBack() { | |
| 146 | + this.$router.go(-1) | |
| 147 | + }, | |
| 148 | + handleTabClick(tab) { | |
| 149 | + this.activeTab = tab | |
| 150 | + setTimeout(() => { | |
| 151 | + this.$refs[tab].loadData() | |
| 152 | + },500) | |
| 153 | + } | |
| 154 | + } | |
| 155 | +} | |
| 156 | +</script> | |
| 157 | + | |
| 158 | +<style lang="scss" scoped> | |
| 159 | +.complaint-detail-container { | |
| 160 | + | |
| 161 | + | |
| 162 | + .header-wrapper { | |
| 163 | + display: flex; | |
| 164 | + justify-content: space-between; | |
| 165 | + align-items: center; | |
| 166 | + margin-bottom: 20px; | |
| 167 | + | |
| 168 | + .title { | |
| 169 | + font-size: 18px; | |
| 170 | + font-weight: bold; | |
| 171 | + } | |
| 172 | + } | |
| 173 | + | |
| 174 | + .info-section { | |
| 175 | + margin-bottom: 15px; | |
| 176 | + color: #666; | |
| 177 | + } | |
| 178 | + | |
| 179 | + .info-item { | |
| 180 | + margin-bottom: 10px; | |
| 181 | + | |
| 182 | + label { | |
| 183 | + font-weight: bold; | |
| 184 | + margin-right: 10px; | |
| 185 | + } | |
| 186 | + } | |
| 187 | +}</style> | |
| 0 | 188 | \ No newline at end of file | ... | ... |
src/views/complaint/adminComplaintLang.js
0 → 100644
| 1 | +export const messages = { | |
| 2 | + en: { | |
| 3 | + adminComplaint: { | |
| 4 | + search: { | |
| 5 | + title: 'Search Conditions', | |
| 6 | + roomName: 'Room', | |
| 7 | + roomNamePlaceholder: 'Building-Unit-Room e.g. 1-1-1', | |
| 8 | + complaintName: 'Contact', | |
| 9 | + complaintNamePlaceholder: 'Please enter contact name', | |
| 10 | + tel: 'Contact Tel', | |
| 11 | + telPlaceholder: 'Please enter contact tel', | |
| 12 | + complaintId: 'Complaint ID', | |
| 13 | + complaintIdPlaceholder: 'Please enter complaint ID', | |
| 14 | + startTime: 'Start Time', | |
| 15 | + startTimePlaceholder: 'Please select start time', | |
| 16 | + endTime: 'End Time', | |
| 17 | + endTimePlaceholder: 'Please select end time' | |
| 18 | + }, | |
| 19 | + list: { | |
| 20 | + title: 'Complaint Suggestions' | |
| 21 | + }, | |
| 22 | + table: { | |
| 23 | + complaintId: 'Order ID', | |
| 24 | + type: 'Type', | |
| 25 | + room: 'Room', | |
| 26 | + contact: 'Contact', | |
| 27 | + contactTel: 'Contact Tel', | |
| 28 | + status: 'Status', | |
| 29 | + handler: 'Handler', | |
| 30 | + createTime: 'Create Time' | |
| 31 | + }, | |
| 32 | + fetchError: 'Failed to fetch complaint list' | |
| 33 | + } | |
| 34 | + }, | |
| 35 | + zh: { | |
| 36 | + adminComplaint: { | |
| 37 | + search: { | |
| 38 | + title: '查询条件', | |
| 39 | + roomName: '房屋', | |
| 40 | + roomNamePlaceholder: '楼栋-单元-房屋 如1-1-1', | |
| 41 | + complaintName: '联系人', | |
| 42 | + complaintNamePlaceholder: '请输入联系人', | |
| 43 | + tel: '联系电话', | |
| 44 | + telPlaceholder: '请输入联系电话', | |
| 45 | + complaintId: '工单编号', | |
| 46 | + complaintIdPlaceholder: '请输入工单编号', | |
| 47 | + startTime: '开始时间', | |
| 48 | + startTimePlaceholder: '请选择开始时间', | |
| 49 | + endTime: '结束时间', | |
| 50 | + endTimePlaceholder: '请选择结束时间' | |
| 51 | + }, | |
| 52 | + list: { | |
| 53 | + title: '投诉建议' | |
| 54 | + }, | |
| 55 | + table: { | |
| 56 | + complaintId: '订单编号', | |
| 57 | + type: '类型', | |
| 58 | + room: '房屋', | |
| 59 | + contact: '联系人', | |
| 60 | + contactTel: '联系电话', | |
| 61 | + status: '状态', | |
| 62 | + handler: '处理人', | |
| 63 | + createTime: '创建时间' | |
| 64 | + }, | |
| 65 | + fetchError: '获取投诉列表失败' | |
| 66 | + } | |
| 67 | + } | |
| 68 | +} | |
| 0 | 69 | \ No newline at end of file | ... | ... |
src/views/complaint/adminComplaintList.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <div class="admin-complaint-container animated fadeInRight"> | |
| 3 | + <el-row :gutter="20"> | |
| 4 | + <el-col :span="3"> | |
| 5 | + <select-admin-community @changeCommunity="handleCommunityChange" /> | |
| 6 | + </el-col> | |
| 7 | + <el-col :span="21"> | |
| 8 | + <el-card class="box-card"> | |
| 9 | + <div slot="header" class="clearfix flex justify-between"> | |
| 10 | + <span>{{ $t('adminComplaint.search.title') }}</span> | |
| 11 | + </div> | |
| 12 | + <el-row :gutter="20"> | |
| 13 | + <el-col :span="4"> | |
| 14 | + <el-input v-model="searchForm.roomName" :placeholder="$t('adminComplaint.search.roomNamePlaceholder')" | |
| 15 | + clearable /> | |
| 16 | + </el-col> | |
| 17 | + <el-col :span="4"> | |
| 18 | + <el-input v-model="searchForm.complaintName" | |
| 19 | + :placeholder="$t('adminComplaint.search.complaintNamePlaceholder')" clearable /> | |
| 20 | + </el-col> | |
| 21 | + <el-col :span="4"> | |
| 22 | + <el-input v-model="searchForm.tel" :placeholder="$t('adminComplaint.search.telPlaceholder')" clearable /> | |
| 23 | + </el-col> | |
| 24 | + <el-col :span="4"> | |
| 25 | + <el-input v-model="searchForm.complaintId" :placeholder="$t('adminComplaint.search.complaintIdPlaceholder')" | |
| 26 | + clearable /> | |
| 27 | + </el-col> | |
| 28 | + <el-col :span="4" class="flex justify-start"> | |
| 29 | + <el-button type="primary" @click="handleSearch">{{ $t('common.search') }}</el-button> | |
| 30 | + </el-col> | |
| 31 | + </el-row> | |
| 32 | + <el-row :gutter="20" class="margin-top"> | |
| 33 | + <el-col :span="4"> | |
| 34 | + <el-date-picker v-model="searchForm.startTime" type="datetime" | |
| 35 | + :placeholder="$t('adminComplaint.search.startTimePlaceholder')" value-format="yyyy-MM-dd HH:mm:ss" /> | |
| 36 | + </el-col> | |
| 37 | + <el-col :span="4"> | |
| 38 | + <el-date-picker v-model="searchForm.endTime" type="datetime" | |
| 39 | + :placeholder="$t('adminComplaint.search.endTimePlaceholder')" value-format="yyyy-MM-dd HH:mm:ss" /> | |
| 40 | + </el-col> | |
| 41 | + | |
| 42 | + | |
| 43 | + </el-row> | |
| 44 | + </el-card> | |
| 45 | + | |
| 46 | + <el-card class="box-card margin-top-20"> | |
| 47 | + <div slot="header" class="clearfix flex justify-between"> | |
| 48 | + <span>{{ $t('adminComplaint.list.title') }}</span> | |
| 49 | + </div> | |
| 50 | + <el-table :data="tableData" border style="width: 100%" v-loading="loading"> | |
| 51 | + <el-table-column prop="complaintId" :label="$t('adminComplaint.table.complaintId')" align="center" /> | |
| 52 | + <el-table-column prop="typeName" :label="$t('adminComplaint.table.type')" align="center" /> | |
| 53 | + <el-table-column prop="roomName" :label="$t('adminComplaint.table.room')" align="center" /> | |
| 54 | + <el-table-column prop="complaintName" :label="$t('adminComplaint.table.contact')" align="center" /> | |
| 55 | + <el-table-column prop="tel" :label="$t('adminComplaint.table.contactTel')" align="center" /> | |
| 56 | + <el-table-column prop="stateName" :label="$t('adminComplaint.table.status')" align="center" /> | |
| 57 | + <el-table-column :label="$t('adminComplaint.table.handler')" align="center"> | |
| 58 | + <template slot-scope="scope"> | |
| 59 | + <span v-for="(item, index) in scope.row.staffs" :key="index">{{ item.staffName }}<br /></span> | |
| 60 | + </template> | |
| 61 | + </el-table-column> | |
| 62 | + <el-table-column prop="createTime" :label="$t('adminComplaint.table.createTime')" align="center" /> | |
| 63 | + <el-table-column :label="$t('common.operation')" align="center" width="120"> | |
| 64 | + <template slot-scope="scope"> | |
| 65 | + <el-button size="mini" type="primary" @click="handleDetail(scope.row)"> | |
| 66 | + {{ $t('common.detail') }} | |
| 67 | + </el-button> | |
| 68 | + </template> | |
| 69 | + </el-table-column> | |
| 70 | + </el-table> | |
| 71 | + <el-pagination class="margin-top-20" :current-page="pagination.current" :page-sizes="[10, 20, 30, 50]" | |
| 72 | + :page-size="pagination.size" :total="pagination.total" layout="total, sizes, prev, pager, next, jumper" | |
| 73 | + @size-change="handleSizeChange" @current-change="handleCurrentChange" /> | |
| 74 | + </el-card> | |
| 75 | + </el-col> | |
| 76 | + </el-row> | |
| 77 | + <view-image ref="viewImage" /> | |
| 78 | + </div> | |
| 79 | +</template> | |
| 80 | + | |
| 81 | +<script> | |
| 82 | +import { listAdminComplaints } from '@/api/complaint/adminComplaintApi' | |
| 83 | +import SelectAdminCommunity from '@/components/community/selectAdminCommunity' | |
| 84 | +import ViewImage from '@/components/community/viewImage' | |
| 85 | + | |
| 86 | +export default { | |
| 87 | + name: 'AdminComplaintList', | |
| 88 | + components: { | |
| 89 | + SelectAdminCommunity, | |
| 90 | + ViewImage | |
| 91 | + }, | |
| 92 | + data() { | |
| 93 | + return { | |
| 94 | + loading: false, | |
| 95 | + searchForm: { | |
| 96 | + communityId: '', | |
| 97 | + complaintId: '', | |
| 98 | + typeCd: '', | |
| 99 | + complaintName: '', | |
| 100 | + tel: '', | |
| 101 | + roomName: '', | |
| 102 | + state: '', | |
| 103 | + startTime: '', | |
| 104 | + endTime: '' | |
| 105 | + }, | |
| 106 | + tableData: [], | |
| 107 | + pagination: { | |
| 108 | + current: 1, | |
| 109 | + size: 10, | |
| 110 | + total: 0 | |
| 111 | + } | |
| 112 | + } | |
| 113 | + }, | |
| 114 | + created() { | |
| 115 | + this.getList() | |
| 116 | + }, | |
| 117 | + methods: { | |
| 118 | + async getList() { | |
| 119 | + try { | |
| 120 | + this.loading = true | |
| 121 | + const params = { | |
| 122 | + ...this.searchForm, | |
| 123 | + page: this.pagination.current, | |
| 124 | + row: this.pagination.size | |
| 125 | + } | |
| 126 | + const { data, total } = await listAdminComplaints(params) | |
| 127 | + this.tableData = data | |
| 128 | + this.pagination.total = total | |
| 129 | + } catch (error) { | |
| 130 | + this.$message.error(this.$t('adminComplaint.fetchError')) | |
| 131 | + } finally { | |
| 132 | + this.loading = false | |
| 133 | + } | |
| 134 | + }, | |
| 135 | + handleSearch() { | |
| 136 | + this.pagination.current = 1 | |
| 137 | + this.getList() | |
| 138 | + }, | |
| 139 | + handleCommunityChange(community) { | |
| 140 | + this.searchForm.communityId = community.communityId | |
| 141 | + this.getList() | |
| 142 | + }, | |
| 143 | + handleDetail(row) { | |
| 144 | + this.$router.push(`/views/complaint/adminComplaintDetail?complaintId=${row.complaintId}`) | |
| 145 | + }, | |
| 146 | + handleSizeChange(val) { | |
| 147 | + this.pagination.size = val | |
| 148 | + this.getList() | |
| 149 | + }, | |
| 150 | + handleCurrentChange(val) { | |
| 151 | + this.pagination.current = val | |
| 152 | + this.getList() | |
| 153 | + } | |
| 154 | + } | |
| 155 | +} | |
| 156 | +</script> | |
| 157 | + | |
| 158 | +<style lang="scss" scoped> | |
| 159 | +.admin-complaint-container { | |
| 160 | + padding: 20px; | |
| 161 | + | |
| 162 | + .search-form { | |
| 163 | + display: flex; | |
| 164 | + flex-wrap: wrap; | |
| 165 | + } | |
| 166 | + | |
| 167 | + .margin-top-20 { | |
| 168 | + margin-top: 20px; | |
| 169 | + } | |
| 170 | +} | |
| 171 | +</style> | |
| 0 | 172 | \ No newline at end of file | ... | ... |