Commit e4877374699083d2fea69045eaad8e8775048d7d

Authored by wuxw
1 parent e0196f8a

开发完成admin 小区投诉功能

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 &#39;../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
... ...