Commit 0a06b2d19c364a091819054e1e044dbf07848536

Authored by wuxw
1 parent ec9ec2ce

开发完成投诉建议

src/api/oa/addComplaintApi.js 0 → 100644
  1 +import request from '@/utils/request'
  2 +import { getCommunityId } from '@/api/community/communityApi'
  3 +
  4 +// 获取投诉类型列表
  5 +export function listComplaintTypes(params) {
  6 + return new Promise((resolve, reject) => {
  7 + const communityId = getCommunityId()
  8 + request({
  9 + url: '/complaintType.listComplaintType',
  10 + method: 'get',
  11 + params: {
  12 + ...params,
  13 + communityId
  14 + }
  15 + }).then(response => {
  16 + const res = response.data
  17 + resolve(res)
  18 + }).catch(error => {
  19 + reject(error)
  20 + })
  21 + })
  22 +}
  23 +
  24 +// 保存投诉信息
  25 +export function saveComplaint(data) {
  26 + return new Promise((resolve, reject) => {
  27 + const communityId = getCommunityId()
  28 + request({
  29 + url: '/complaint.saveComplaint',
  30 + method: 'post',
  31 + data: {
  32 + ...data,
  33 + communityId
  34 + }
  35 + }).then(response => {
  36 + const res = response.data
  37 + if (res.code === 0) {
  38 + resolve(res)
  39 + } else {
  40 + reject(new Error(res.msg))
  41 + }
  42 + }).catch(error => {
  43 + reject(error)
  44 + })
  45 + })
  46 +}
  47 +
  48 +// 查询房间信息
  49 +export function queryRooms(params) {
  50 + return new Promise((resolve, reject) => {
  51 + const communityId = getCommunityId()
  52 + request({
  53 + url: '/room.queryRooms',
  54 + method: 'get',
  55 + params: {
  56 + ...params,
  57 + communityId
  58 + }
  59 + }).then(response => {
  60 + const res = response.data
  61 + resolve(res)
  62 + }).catch(error => {
  63 + reject(error)
  64 + })
  65 + })
  66 +}
0 67 \ No newline at end of file
... ...
src/api/oa/addComplaintTypeApi.js 0 → 100644
  1 +import request from '@/utils/request'
  2 +
  3 +/**
  4 + * 获取组织树
  5 + * @param {Object} params
  6 + * @returns {Promise}
  7 + */
  8 +export function listOrgTree(params) {
  9 + return new Promise((resolve, reject) => {
  10 + request({
  11 + url: '/org.listOrgTree',
  12 + method: 'get',
  13 + params
  14 + }).then(response => {
  15 + const res = response.data
  16 + resolve(res)
  17 + }).catch(error => {
  18 + reject(error)
  19 + })
  20 + })
  21 +}
  22 +
  23 +/**
  24 + * 查询员工信息
  25 + * @param {Object} params
  26 + * @returns {Promise}
  27 + */
  28 +export function getStaffInfos(params) {
  29 + return new Promise((resolve, reject) => {
  30 + request({
  31 + url: '/query.staff.infos',
  32 + method: 'get',
  33 + params
  34 + }).then(response => {
  35 + const res = response.data
  36 + resolve(res)
  37 + }).catch(error => {
  38 + reject(error)
  39 + })
  40 + })
  41 +}
  42 +
  43 +/**
  44 + * 保存投诉类型
  45 + * @param {Object} data
  46 + * @returns {Promise}
  47 + */
  48 +export function saveComplaintType(data) {
  49 + return new Promise((resolve, reject) => {
  50 + request({
  51 + url: '/complaintType.saveComplaintType',
  52 + method: 'post',
  53 + data
  54 + }).then(response => {
  55 + const res = response.data
  56 + resolve(res)
  57 + }).catch(error => {
  58 + reject(error)
  59 + })
  60 + })
  61 +}
0 62 \ No newline at end of file
... ...
src/api/oa/complaintApi.js 0 → 100644
  1 +import request from '@/utils/request'
  2 +import { getCommunityId } from '@/api/community/communityApi'
  3 +
  4 +// 获取投诉列表
  5 +export function listComplaints(params) {
  6 + return new Promise((resolve, reject) => {
  7 + request({
  8 + url: '/complaint.listComplaints',
  9 + method: 'get',
  10 + params: {
  11 + ...params,
  12 + communityId: getCommunityId()
  13 + }
  14 + }).then(response => {
  15 + const res = response.data
  16 + resolve(res)
  17 + }).catch(error => {
  18 + reject(error)
  19 + })
  20 + })
  21 +}
  22 +
  23 +// 获取投诉类型列表
  24 +export function listComplaintTypes(params) {
  25 + return new Promise((resolve, reject) => {
  26 + request({
  27 + url: '/complaintType.listComplaintType',
  28 + method: 'get',
  29 + params: {
  30 + ...params,
  31 + communityId: getCommunityId()
  32 + }
  33 + }).then(response => {
  34 + const res = response.data
  35 + resolve(res)
  36 + }).catch(error => {
  37 + reject(error)
  38 + })
  39 + })
  40 +}
  41 +
  42 +// 更新投诉信息
  43 +export function updateComplaint(data) {
  44 + return new Promise((resolve, reject) => {
  45 + request({
  46 + url: '/complaint.updateComplaint',
  47 + method: 'post',
  48 + data: {
  49 + ...data,
  50 + communityId: getCommunityId()
  51 + }
  52 + }).then(response => {
  53 + const res = response.data
  54 + resolve(res)
  55 + }).catch(error => {
  56 + reject(error)
  57 + })
  58 + })
  59 +}
  60 +
  61 +// 删除投诉
  62 +export function deleteComplaint(data) {
  63 + return new Promise((resolve, reject) => {
  64 + request({
  65 + url: '/complaint.deleteComplaint',
  66 + method: 'post',
  67 + data: {
  68 + ...data,
  69 + communityId: getCommunityId()
  70 + }
  71 + }).then(response => {
  72 + const res = response.data
  73 + resolve(res)
  74 + }).catch(error => {
  75 + reject(error)
  76 + })
  77 + })
  78 +}
  79 +
  80 +// 获取工作流审核信息
  81 +export function listWorkflowAuditInfo(params) {
  82 + return new Promise((resolve, reject) => {
  83 + request({
  84 + url: '/workflow.listWorkflowAuditInfo',
  85 + method: 'get',
  86 + params: {
  87 + ...params,
  88 + communityId: getCommunityId()
  89 + }
  90 + }).then(response => {
  91 + const res = response.data
  92 + resolve(res)
  93 + }).catch(error => {
  94 + reject(error)
  95 + })
  96 + })
  97 +}
0 98 \ No newline at end of file
... ...
src/api/oa/complaintTypeApi.js 0 → 100644
  1 +import request from '@/utils/request'
  2 +import { getCommunityId } from '@/api/community/communityApi'
  3 +
  4 +// 获取投诉类型列表
  5 +export function listComplaintType(params) {
  6 + return new Promise((resolve, reject) => {
  7 + request({
  8 + url: '/complaintType.listComplaintType',
  9 + method: 'get',
  10 + params: {
  11 + ...params,
  12 + communityId: getCommunityId()
  13 + }
  14 + }).then(response => {
  15 + const res = response.data
  16 + resolve(res)
  17 + }).catch(error => {
  18 + reject(error)
  19 + })
  20 + })
  21 +}
  22 +
  23 +// 删除投诉类型
  24 +export function deleteComplaintType(data) {
  25 + return new Promise((resolve, reject) => {
  26 + request({
  27 + url: '/complaintType.deleteComplaintType',
  28 + method: 'post',
  29 + data: {
  30 + ...data,
  31 + communityId: getCommunityId()
  32 + }
  33 + }).then(response => {
  34 + const res = response.data
  35 + resolve(res)
  36 + }).catch(error => {
  37 + reject(error)
  38 + })
  39 + })
  40 +}
  41 +
  42 +// 添加投诉类型
  43 +export function addComplaintType(data) {
  44 + return new Promise((resolve, reject) => {
  45 + request({
  46 + url: '/complaintType.saveComplaintType',
  47 + method: 'post',
  48 + data: {
  49 + ...data,
  50 + communityId: getCommunityId()
  51 + }
  52 + }).then(response => {
  53 + const res = response.data
  54 + resolve(res)
  55 + }).catch(error => {
  56 + reject(error)
  57 + })
  58 + })
  59 +}
  60 +
  61 +// 更新投诉类型
  62 +export function updateComplaintType(data) {
  63 + return new Promise((resolve, reject) => {
  64 + request({
  65 + url: '/complaintType.updateComplaintType',
  66 + method: 'post',
  67 + data: {
  68 + ...data,
  69 + communityId: getCommunityId()
  70 + }
  71 + }).then(response => {
  72 + const res = response.data
  73 + resolve(res)
  74 + }).catch(error => {
  75 + reject(error)
  76 + })
  77 + })
  78 +}
  79 +
  80 +// 获取单个投诉类型详情
  81 +export function getComplaintTypeDetail(typeCd) {
  82 + return new Promise((resolve, reject) => {
  83 + request({
  84 + url: '/complaintType.getComplaintType',
  85 + method: 'get',
  86 + params: {
  87 + typeCd,
  88 + communityId: getCommunityId()
  89 + }
  90 + }).then(response => {
  91 + const res = response.data
  92 + resolve(res)
  93 + }).catch(error => {
  94 + reject(error)
  95 + })
  96 + })
  97 +}
0 98 \ No newline at end of file
... ...
src/api/oa/editComplaintTypeApi.js 0 → 100644
  1 +import request from '@/utils/request'
  2 +import { getCommunityId } from '@/api/community/communityApi'
  3 +
  4 +// 获取投诉类型详情
  5 +export function getComplaintTypeDetail(params) {
  6 + return new Promise((resolve, reject) => {
  7 + request({
  8 + url: '/complaintType.listComplaintType',
  9 + method: 'get',
  10 + params: {
  11 + ...params,
  12 + communityId: params.communityId || getCommunityId()
  13 + }
  14 + }).then(response => {
  15 + const res = response.data
  16 + resolve(res)
  17 + }).catch(error => {
  18 + reject(error)
  19 + })
  20 + })
  21 +}
  22 +
  23 +// 更新投诉类型
  24 +export function updateComplaintType(data) {
  25 + return new Promise((resolve, reject) => {
  26 + request({
  27 + url: '/complaintType.updateComplaintType',
  28 + method: 'post',
  29 + data: {
  30 + ...data,
  31 + communityId: data.communityId || getCommunityId()
  32 + }
  33 + }).then(response => {
  34 + const res = response.data
  35 + resolve(res)
  36 + }).catch(error => {
  37 + reject(error)
  38 + })
  39 + })
  40 +}
  41 +
  42 +// 获取组织树
  43 +export function listOrgTree(params) {
  44 + return new Promise((resolve, reject) => {
  45 + request({
  46 + url: '/org.listOrgTree',
  47 + method: 'get',
  48 + params: {
  49 + ...params,
  50 + communityId: params.communityId || getCommunityId()
  51 + }
  52 + }).then(response => {
  53 + const res = response.data
  54 + resolve(res)
  55 + }).catch(error => {
  56 + reject(error)
  57 + })
  58 + })
  59 +}
  60 +
  61 +// 获取员工信息
  62 +export function getStaffInfos(params) {
  63 + return new Promise((resolve, reject) => {
  64 + request({
  65 + url: '/query.staff.infos',
  66 + method: 'get',
  67 + params: {
  68 + ...params,
  69 + communityId: params.communityId || getCommunityId()
  70 + }
  71 + }).then(response => {
  72 + const res = response.data
  73 + resolve(res)
  74 + }).catch(error => {
  75 + reject(error)
  76 + })
  77 + })
  78 +}
0 79 \ No newline at end of file
... ...
src/components/oa/complaintDetail.vue 0 → 100644
  1 +<template>
  2 + <el-dialog :title="`${form.typeCdName} ${$t('complaint.detail.title')}`" :visible.sync="visible" width="70%" top="5vh">
  3 + <el-form label-width="120px">
  4 + <el-row :gutter="20">
  5 + <el-col :span="12">
  6 + <el-form-item :label="$t('complaint.detail.room')">
  7 + <el-input v-model="form.roomName" readonly />
  8 + </el-form-item>
  9 + </el-col>
  10 + <el-col :span="12">
  11 + <el-form-item :label="$t('complaint.detail.id')">
  12 + <el-input v-model="form.complaintId" readonly />
  13 + </el-form-item>
  14 + </el-col>
  15 + </el-row>
  16 +
  17 + <el-row :gutter="20">
  18 + <el-col :span="12">
  19 + <el-form-item :label="$t('complaint.detail.type')">
  20 + <el-input v-model="form.typeCdName" readonly />
  21 + </el-form-item>
  22 + </el-col>
  23 + <el-col :span="12">
  24 + <el-form-item :label="$t('complaint.detail.status')">
  25 + <el-input v-model="form.stateName" readonly />
  26 + </el-form-item>
  27 + </el-col>
  28 + </el-row>
  29 +
  30 + <el-row :gutter="20">
  31 + <el-col :span="12">
  32 + <el-form-item :label="$t('complaint.detail.contact')">
  33 + <el-input v-model="form.complaintName" readonly />
  34 + </el-form-item>
  35 + </el-col>
  36 + <el-col :span="12">
  37 + <el-form-item :label="$t('complaint.detail.phone')">
  38 + <el-input v-model="form.tel" readonly />
  39 + </el-form-item>
  40 + </el-col>
  41 + </el-row>
  42 +
  43 + <template v-if="form.showCurrentUser">
  44 + <el-row :gutter="20">
  45 + <el-col :span="12">
  46 + <el-form-item :label="$t('complaint.detail.handler')">
  47 + <el-input v-model="form.currentUserName" readonly />
  48 + </el-form-item>
  49 + </el-col>
  50 + <el-col :span="12">
  51 + <el-form-item :label="$t('complaint.detail.handlerPhone')">
  52 + <el-input v-model="form.currentUserTel" readonly />
  53 + </el-form-item>
  54 + </el-col>
  55 + </el-row>
  56 + <el-form-item :label="$t('complaint.detail.handlerId')">
  57 + <el-input v-model="form.currentUserId" readonly />
  58 + </el-form-item>
  59 + </template>
  60 +
  61 + <el-form-item :label="`${form.typeCdName}${$t('complaint.detail.content')}`">
  62 + <el-input type="textarea" :rows="4" v-model="form.context" readonly />
  63 + </el-form-item>
  64 +
  65 + <el-form-item :label="$t('complaint.detail.photos')" v-if="form.photosShow.length > 0">
  66 + <div style="display:flex;flex-wrap:wrap;gap:10px">
  67 + <el-image v-for="(photo, index) in form.photosShow" :key="index" style="width:100px;height:100px"
  68 + :src="photo.url" :preview-src-list="form.photosShow.map(p => p.url)" fit="cover" />
  69 + </div>
  70 + </el-form-item>
  71 + </el-form>
  72 +
  73 + <el-table :data="form.comments" border style="width:100%;margin-top:20px">
  74 + <el-table-column prop="index" :label="$t('complaint.detail.serial')" width="80" align="center">
  75 + <template slot-scope="scope">
  76 + {{ scope.$index + 1 }}
  77 + </template>
  78 + </el-table-column>
  79 + <el-table-column prop="auditName" :label="$t('complaint.detail.handler')" align="center" />
  80 + <el-table-column prop="stateName" :label="$t('complaint.detail.status')" align="center" />
  81 + <el-table-column prop="auditTime" :label="$t('complaint.detail.handleTime')" align="center" />
  82 + <el-table-column prop="duration" :label="$t('complaint.detail.duration')" align="center" />
  83 + <el-table-column prop="message" :label="$t('complaint.detail.comment')" align="center" />
  84 + </el-table>
  85 + </el-dialog>
  86 +</template>
  87 +
  88 +<script>
  89 +import { getCommunityId } from '@/api/community/communityApi'
  90 +import { listWorkflowAuditInfo } from '@/api/oa/complaintApi'
  91 +
  92 +export default {
  93 + name: 'ComplaintDetail',
  94 + data() {
  95 + return {
  96 + visible: false,
  97 + form: {
  98 + complaintId: '',
  99 + typeCd: '',
  100 + complaintName: '',
  101 + tel: '',
  102 + context: '',
  103 + typeCdName: '',
  104 + stateName: '',
  105 + roomName: '',
  106 + currentUserName: '',
  107 + currentUserTel: '',
  108 + currentUserId: '',
  109 + showCurrentUser: true,
  110 + photos: [],
  111 + photosShow: [],
  112 + comments: []
  113 + }
  114 + }
  115 + },
  116 + methods: {
  117 + open(row) {
  118 + this.visible = true
  119 + this.form = {
  120 + ...row,
  121 + photosShow: row.photos.map(photo => ({
  122 + url: `/callComponent/download/getFile/file?fileId=${photo.url}&communityId=-1&time=${new Date()}`
  123 + })),
  124 + showCurrentUser: row['currentUserName'],
  125 + currentUserName: row.currentUserName || this.$t('common.none'),
  126 + currentUserTel: row.currentUserTel || this.$t('common.none'),
  127 + currentUserId: row.currentUserId || this.$t('common.none')
  128 + }
  129 + this.loadComments()
  130 + },
  131 + async loadComments() {
  132 + try {
  133 + const params = {
  134 + communityId: getCommunityId(),
  135 + businessKey: this.form.complaintId
  136 + }
  137 + const { data } = await listWorkflowAuditInfo(params)
  138 + this.form.comments = data
  139 + } catch (error) {
  140 + console.error('获取处理记录失败:', error)
  141 + }
  142 + }
  143 + }
  144 +}
  145 +</script>
0 146 \ No newline at end of file
... ...
src/components/oa/deleteComplaint.vue 0 → 100644
  1 +<template>
  2 + <el-dialog
  3 + :title="$t('complaint.delete.title')"
  4 + :visible.sync="visible"
  5 + width="30%"
  6 + center
  7 + >
  8 + <div style="text-align:center">
  9 + <p>{{ $t('complaint.delete.confirm') }}</p>
  10 + </div>
  11 + <span slot="footer" class="dialog-footer">
  12 + <el-button @click="visible = false">{{ $t('common.cancel') }}</el-button>
  13 + <el-button type="primary" @click="handleDelete" :loading="loading">{{ $t('common.confirm') }}</el-button>
  14 + </span>
  15 + </el-dialog>
  16 +</template>
  17 +
  18 +<script>
  19 +import { deleteComplaint } from '@/api/oa/complaintApi'
  20 +import { getCommunityId } from '@/api/community/communityApi'
  21 +
  22 +export default {
  23 + name: 'DeleteComplaint',
  24 + data() {
  25 + return {
  26 + visible: false,
  27 + loading: false,
  28 + complaintId: '',
  29 + communityId: ''
  30 + }
  31 + },
  32 + methods: {
  33 + open(row) {
  34 + this.communityId = getCommunityId()
  35 + this.complaintId = row.complaintId
  36 + this.visible = true
  37 + },
  38 + async handleDelete() {
  39 + try {
  40 + this.loading = true
  41 + await deleteComplaint({
  42 + complaintId: this.complaintId,
  43 + communityId: this.communityId
  44 + })
  45 + this.$message.success(this.$t('complaint.delete.success'))
  46 + this.$emit('success')
  47 + this.visible = false
  48 + } catch (error) {
  49 + console.error('删除投诉失败:', error)
  50 + } finally {
  51 + this.loading = false
  52 + }
  53 + }
  54 + }
  55 +}
  56 +</script>
0 57 \ No newline at end of file
... ...
src/components/oa/deleteComplaintType.vue 0 → 100644
  1 +<template>
  2 + <el-dialog
  3 + :title="$t('deleteComplaintType.confirmTitle')"
  4 + :visible.sync="visible"
  5 + width="30%"
  6 + @close="handleClose"
  7 + >
  8 + <div style="text-align: center;">
  9 + <p>{{ $t('deleteComplaintType.confirmText') }}</p>
  10 + </div>
  11 + <span slot="footer" class="dialog-footer">
  12 + <el-button @click="handleClose">{{ $t('deleteComplaintType.cancel') }}</el-button>
  13 + <el-button type="primary" @click="deleteComplaintType">{{ $t('deleteComplaintType.confirm') }}</el-button>
  14 + </span>
  15 + </el-dialog>
  16 +</template>
  17 +
  18 +<script>
  19 +import { deleteComplaintType } from '@/api/oa/complaintTypeApi'
  20 +import { getCommunityId } from '@/api/community/communityApi'
  21 +
  22 +export default {
  23 + name: 'DeleteComplaintType',
  24 + data() {
  25 + return {
  26 + visible: false,
  27 + currentComplaintType: null
  28 + }
  29 + },
  30 + methods: {
  31 + open(complaintType) {
  32 + this.currentComplaintType = complaintType
  33 + this.visible = true
  34 + },
  35 + async deleteComplaintType() {
  36 + try {
  37 + const params = {
  38 + ...this.currentComplaintType,
  39 + communityId: getCommunityId()
  40 + }
  41 + await deleteComplaintType(params)
  42 + this.$emit('success')
  43 + this.visible = false
  44 + this.$message.success(this.$t('deleteComplaintType.success'))
  45 + } catch (error) {
  46 + this.$message.error(this.$t('deleteComplaintType.error'))
  47 + }
  48 + },
  49 + handleClose() {
  50 + this.visible = false
  51 + }
  52 + }
  53 +}
  54 +</script>
0 55 \ No newline at end of file
... ...
src/components/oa/editComplaint.vue 0 → 100644
  1 +<template>
  2 + <el-dialog
  3 + :title="$t('complaint.edit.title')"
  4 + :visible.sync="visible"
  5 + width="50%"
  6 + @close="handleClose"
  7 + >
  8 + <el-form
  9 + ref="form"
  10 + :model="form"
  11 + label-width="120px"
  12 + :rules="rules"
  13 + >
  14 + <el-form-item
  15 + :label="$t('complaint.edit.type')"
  16 + prop="typeCd"
  17 + >
  18 + <el-select
  19 + v-model="form.typeCd"
  20 + style="width:100%"
  21 + :placeholder="$t('complaint.edit.typePlaceholder')"
  22 + >
  23 + <el-option
  24 + v-for="item in complaintTypes"
  25 + :key="item.typeCd"
  26 + :label="item.typeName"
  27 + :value="item.typeCd"
  28 + />
  29 + </el-select>
  30 + </el-form-item>
  31 + <el-form-item
  32 + :label="$t('complaint.edit.contact')"
  33 + prop="complaintName"
  34 + >
  35 + <el-input
  36 + v-model="form.complaintName"
  37 + :placeholder="$t('complaint.edit.contactPlaceholder')"
  38 + />
  39 + </el-form-item>
  40 + <el-form-item
  41 + :label="$t('complaint.edit.phone')"
  42 + prop="tel"
  43 + >
  44 + <el-input
  45 + v-model="form.tel"
  46 + :placeholder="$t('complaint.edit.phonePlaceholder')"
  47 + />
  48 + </el-form-item>
  49 + <el-form-item
  50 + :label="$t('complaint.edit.content')"
  51 + prop="context"
  52 + >
  53 + <el-input
  54 + type="textarea"
  55 + :rows="4"
  56 + v-model="form.context"
  57 + :placeholder="$t('complaint.edit.contentPlaceholder')"
  58 + />
  59 + </el-form-item>
  60 + </el-form>
  61 + <span slot="footer" class="dialog-footer">
  62 + <el-button @click="visible = false">{{ $t('common.cancel') }}</el-button>
  63 + <el-button type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</el-button>
  64 + </span>
  65 + </el-dialog>
  66 +</template>
  67 +
  68 +<script>
  69 +import { updateComplaint } from '@/api/oa/complaintApi'
  70 +import { listComplaintTypes } from '@/api/oa/complaintApi'
  71 +import { getCommunityId } from '@/api/community/communityApi'
  72 +
  73 +export default {
  74 + name: 'EditComplaint',
  75 + data() {
  76 + return {
  77 + visible: false,
  78 + communityId: '',
  79 + complaintTypes: [],
  80 + form: {
  81 + complaintId: '',
  82 + typeCd: '',
  83 + complaintName: '',
  84 + tel: '',
  85 + context: '',
  86 + communityId: ''
  87 + },
  88 + rules: {
  89 + typeCd: [
  90 + { required: true, message: this.$t('complaint.validate.type'), trigger: 'blur' }
  91 + ],
  92 + complaintName: [
  93 + { required: true, message: this.$t('complaint.validate.contact'), trigger: 'blur' },
  94 + { max: 200, message: this.$t('complaint.validate.contactMax'), trigger: 'blur' }
  95 + ],
  96 + tel: [
  97 + { required: true, message: this.$t('complaint.validate.phone'), trigger: 'blur' },
  98 + { pattern: /^1[3-9]\d{9}$/, message: this.$t('complaint.validate.phoneFormat'), trigger: 'blur' }
  99 + ],
  100 + context: [
  101 + { required: true, message: this.$t('complaint.validate.content'), trigger: 'blur' },
  102 + { max: 4000, message: this.$t('complaint.validate.contentMax'), trigger: 'blur' }
  103 + ]
  104 + }
  105 + }
  106 + },
  107 + methods: {
  108 + open(row) {
  109 + this.communityId = getCommunityId()
  110 + this.form = {
  111 + ...row,
  112 + communityId: this.communityId
  113 + }
  114 + this.getComplaintTypes()
  115 + this.visible = true
  116 + },
  117 + async getComplaintTypes() {
  118 + try {
  119 + const params = {
  120 + page: 1,
  121 + row: 100,
  122 + communityId: this.communityId
  123 + }
  124 + const { data } = await listComplaintTypes(params)
  125 + this.complaintTypes = data
  126 + } catch (error) {
  127 + console.error('获取投诉类型失败:', error)
  128 + }
  129 + },
  130 + handleSubmit() {
  131 + this.$refs.form.validate(async valid => {
  132 + if (valid) {
  133 + try {
  134 + await updateComplaint(this.form)
  135 + this.$message.success(this.$t('complaint.edit.success'))
  136 + this.$emit('success')
  137 + this.visible = false
  138 + } catch (error) {
  139 + console.error('更新投诉失败:', error)
  140 + }
  141 + }
  142 + })
  143 + },
  144 + handleClose() {
  145 + this.$refs.form.resetFields()
  146 + }
  147 + }
  148 +}
  149 +</script>
0 150 \ No newline at end of file
... ...
src/components/staff/selectStaffsDiv.vue
1 1 <template>
2   - <el-row :gutter="20">
3   - <el-col :span="6" class="border-right">
4   - <div class="text-center">
5   - <h4>{{ $t('selectStaff.orgInfo') }}</h4>
  2 + <el-row :gutter="20">
  3 + <el-col :span="6" class="border-right">
  4 + <div class="text-center">
  5 + <h4>{{ $t('selectStaff.orgInfo') }}</h4>
  6 + </div>
  7 + <div class="tree-container">
  8 + <org-tree-show @org-selected="handleNodeClick" />
  9 + </div>
  10 + </el-col>
  11 +
  12 + <el-col :span="6" class="border-right">
  13 + <div class="text-center">
  14 + <h4>{{ $t('selectStaff.staffInfo') }}</h4>
  15 + </div>
  16 + <div class="staff-list">
  17 + <div v-for="staff in staffList" :key="staff.staffId" class="staff-item"
  18 + :class="{ selected: currentStaffId === staff.userId }" @click="selectStaff(staff)">
  19 + <i class="el-icon-user"></i>
  20 + <span>{{ staff.name }}</span>
  21 + <div>{{ staff.tel }}</div>
6 22 </div>
7   - <div class="tree-container">
8   - <org-tree-show @org-selected="handleNodeClick" />
  23 + </div>
  24 + </el-col>
  25 +
  26 + <el-col :span="6">
  27 + <div class="text-center">
  28 + <h4>{{ $t('selectStaff.selectedStaff') }}</h4>
  29 + </div>
  30 + <div class="selected-staff">
  31 + <div v-for="staff in selectedStaffs" :key="staff.userId" class="selected-item">
  32 + <span>{{ staff.name }}</span>
  33 + <i class="el-icon-close remove-icon" @click="removeStaff(staff)"></i>
9 34 </div>
10   - </el-col>
11   -
12   - <el-col :span="6" class="border-right">
13   - <div class="text-center">
14   - <h4>{{ $t('selectStaff.staffInfo') }}</h4>
15   - </div>
16   - <div class="staff-list">
17   - <div
18   - v-for="staff in staffList"
19   - :key="staff.staffId"
20   - class="staff-item"
21   - :class="{ selected: currentStaffId === staff.userId }"
22   - @click="selectStaff(staff)"
23   - >
24   - <i class="el-icon-user"></i>
25   - <span>{{ staff.name }}</span>
26   - <div>{{ staff.tel }}</div>
27   - </div>
28   - </div>
29   - </el-col>
30   -
31   - <el-col :span="6">
32   - <div class="text-center">
33   - <h4>{{ $t('selectStaff.selectedStaff') }}</h4>
34   - </div>
35   - <div class="selected-staff">
36   - <div v-for="staff in selectedStaffs" :key="staff.userId" class="selected-item">
37   - <span>{{ staff.name }}</span>
38   - <i class="el-icon-close remove-icon" @click="removeStaff(staff)"></i>
39   - </div>
40   - </div>
41   - </el-col>
42   - </el-row>
43   - </template>
44   -
45   - <script>
46   - import { listStaffsByOrgId } from '@/api/inspection/inspectionPlanApi'
47   - import OrgTreeShow from '@/components/org/OrgTreeShow'
  35 + </div>
  36 + </el-col>
  37 + </el-row>
  38 +</template>
48 39  
49   - export default {
50   - name: 'SelectStaffs',
51   - components: {
52   - OrgTreeShow
  40 +<script>
  41 +import { listStaffsByOrgId } from '@/api/inspection/inspectionPlanApi'
  42 +import OrgTreeShow from '@/components/org/OrgTreeShow'
  43 +
  44 +export default {
  45 + name: 'SelectStaffs',
  46 + components: {
  47 + OrgTreeShow
  48 + },
  49 + data() {
  50 + return {
  51 + staffList: [],
  52 + selectedStaffs: [],
  53 + currentStaffId: null,
  54 + currentOrgId: null
  55 + }
  56 + },
  57 + methods: {
  58 + setStaffs(staffs) {
  59 + this.selectedStaffs = [...staffs]
  60 + },
  61 +
  62 + getSelectedStaffs() {
  63 + return this.selectedStaffs
53 64 },
54   - data() {
55   - return {
56   - staffList: [],
57   - selectedStaffs: [],
58   - currentStaffId: null,
59   - currentOrgId: null
  65 +
  66 + async handleNodeClick(node) {
  67 + this.currentOrgId = node.id
  68 + try {
  69 + const { staffs } = await listStaffsByOrgId({
  70 + orgId: node.id,
  71 + page: 1,
  72 + row: 100
  73 + })
  74 + this.staffList = staffs
  75 + } catch (error) {
  76 + console.error('Failed to load staffs:', error)
60 77 }
61 78 },
62   - methods: {
63   - setStaffs(staffs) {
64   - this.selectedStaffs = [...staffs]
65   - },
66   -
67   - getSelectedStaffs() {
68   - return this.selectedStaffs
69   - },
70   -
71   - async handleNodeClick(node) {
72   - this.currentOrgId = node.id
73   - try {
74   - const { staffs } = await listStaffsByOrgId({
75   - orgId: node.id,
76   - page: 1,
77   - row: 100
78   - })
79   - this.staffList = staffs
80   - } catch (error) {
81   - console.error('Failed to load staffs:', error)
82   - }
83   - },
84   -
85   - selectStaff(staff) {
86   - this.currentStaffId = staff.userId
87   - // 检查是否已选择
88   - const isSelected = this.selectedStaffs.some(s => s.userId === staff.userId)
89   - if (!isSelected) {
90   - this.selectedStaffs.push({
91   - userId: staff.userId,
92   - name: staff.userName
93   - })
94   - }
95   - this.$emit('selectStaffs', this.selectedStaffs)
96   - },
97   -
98   - removeStaff(staff) {
99   - this.selectedStaffs = this.selectedStaffs.filter(s => s.staffId !== staff.staffId)
  79 +
  80 + selectStaff(staff) {
  81 + this.currentStaffId = staff.userId
  82 + // 检查是否已选择
  83 + const isSelected = this.selectedStaffs.some(s => s.userId === staff.userId)
  84 + if (!isSelected) {
  85 + this.selectedStaffs.push({
  86 + userId: staff.userId,
  87 + name: staff.userName
  88 + })
100 89 }
  90 + this.$emit('selectStaffs', this.selectedStaffs)
  91 + },
  92 +
  93 + removeStaff(staff) {
  94 + this.selectedStaffs = this.selectedStaffs.filter(s => s.staffId !== staff.staffId)
101 95 }
102 96 }
103   - </script>
  97 +}
  98 +</script>
104 99  
105   - <style scoped>
106   - .border-right {
107   - border-right: 1px solid #eee;
108   - }
109   - .tree-container {
110   - height: 300px;
111   - overflow: auto;
112   - padding: 10px;
113   - }
114   - .staff-list {
115   - height: 300px;
116   - overflow: auto;
117   - padding: 10px;
118   - }
119   - .staff-item {
120   - padding: 10px;
121   - cursor: pointer;
122   - border-bottom: 1px solid #eee;
123   - }
124   - .staff-item:hover {
125   - background-color: #f5f7fa;
126   - }
127   - .staff-item.selected {
128   - background-color: #ecf5ff;
129   - }
130   - .selected-staff {
131   - height: 300px;
132   - overflow: auto;
133   - padding: 10px;
134   - }
135   - .selected-item {
136   - display: flex;
137   - justify-content: space-between;
138   - align-items: center;
139   - padding: 8px;
140   - margin-bottom: 5px;
141   - border: 1px solid #ebeef5;
142   - border-radius: 4px;
143   - }
144   - .remove-icon {
145   - cursor: pointer;
146   - color: #f56c6c;
147   - }
148   - .text-center {
149   - text-align: center;
150   - padding: 10px 0;
151   - }
152   - </style>
153 100 \ No newline at end of file
  101 +<style scoped>
  102 +.border-right {
  103 + border-right: 1px solid #eee;
  104 +}
  105 +
  106 +.tree-container {
  107 + height: 300px;
  108 + overflow: auto;
  109 + padding: 10px;
  110 +}
  111 +
  112 +.staff-list {
  113 + height: 300px;
  114 + overflow: auto;
  115 + padding: 10px;
  116 +}
  117 +
  118 +.staff-item {
  119 + padding: 10px;
  120 + cursor: pointer;
  121 + border-bottom: 1px solid #eee;
  122 +}
  123 +
  124 +.staff-item:hover {
  125 + background-color: #f5f7fa;
  126 +}
  127 +
  128 +.staff-item.selected {
  129 + background-color: #ecf5ff;
  130 +}
  131 +
  132 +.selected-staff {
  133 + height: 300px;
  134 + overflow: auto;
  135 + padding: 10px;
  136 +}
  137 +
  138 +.selected-item {
  139 + display: flex;
  140 + justify-content: space-between;
  141 + align-items: center;
  142 + padding: 8px;
  143 + margin-bottom: 5px;
  144 + border: 1px solid #ebeef5;
  145 + border-radius: 4px;
  146 +}
  147 +
  148 +.remove-icon {
  149 + cursor: pointer;
  150 + color: #f56c6c;
  151 +}
  152 +
  153 +.text-center {
  154 + text-align: center;
  155 + padding: 10px 0;
  156 +}</style>
154 157 \ No newline at end of file
... ...
src/i18n/oaI18n.js
... ... @@ -4,6 +4,12 @@ import { messages as noticeManageMessages } from &#39;../views/oa/noticeManageLang&#39;
4 4 import { messages as addNoticeViewMessages } from '../views/oa/addNoticeViewLang'
5 5 import { messages as editNoticeViewMessages } from '../views/oa/editNoticeViewLang'
6 6 import { messages as noticeDetailMessages } from '../views/oa/noticeDetailLang'
  7 +import { messages as complaintTypeMessages } from '../views/oa/complaintTypeLang'
  8 +import { messages as addComplaintTypeMessages } from '../views/oa/addComplaintTypeLang'
  9 +import { messages as editComplaintTypeMessages } from '../views/oa/editComplaintTypeLang'
  10 +import { messages as addComplaintMessages } from '../views/oa/addComplaintLang'
  11 +import { messages as complaintMessages } from '../views/oa/complaintLang'
  12 +
7 13 export const messages ={
8 14 en:{
9 15 ...activitiesTypeManageMessages.en,
... ... @@ -12,6 +18,11 @@ export const messages ={
12 18 ...addNoticeViewMessages.en,
13 19 ...editNoticeViewMessages.en,
14 20 ...noticeDetailMessages.en,
  21 + ...complaintTypeMessages.en,
  22 + ...addComplaintTypeMessages.en,
  23 + ...editComplaintTypeMessages.en,
  24 + ...addComplaintMessages.en,
  25 + ...complaintMessages.en,
15 26 },
16 27 zh:{
17 28 ...activitiesTypeManageMessages.zh,
... ... @@ -20,5 +31,10 @@ export const messages ={
20 31 ...addNoticeViewMessages.zh,
21 32 ...editNoticeViewMessages.zh,
22 33 ...noticeDetailMessages.zh,
  34 + ...complaintTypeMessages.zh,
  35 + ...addComplaintTypeMessages.zh,
  36 + ...editComplaintTypeMessages.zh,
  37 + ...addComplaintMessages.zh,
  38 + ...complaintMessages.zh,
23 39 }
24 40 }
25 41 \ No newline at end of file
... ...
src/router/oaRouter.js
... ... @@ -29,4 +29,29 @@ export default [
29 29 name: '/views/oa/noticeDetail',
30 30 component: () => import('@/views/oa/noticeDetailList.vue')
31 31 },
  32 + {
  33 + path: '/pages/complaint/complaintType',
  34 + name: '/pages/complaint/complaintType',
  35 + component: () => import('@/views/oa/complaintTypeList.vue')
  36 + },
  37 + {
  38 + path: '/views/oa/addComplaintType',
  39 + name: '/views/oa/addComplaintType',
  40 + component: () => import('@/views/oa/addComplaintTypeList.vue')
  41 + },
  42 + {
  43 + path: '/views/oa/editComplaintType',
  44 + name: '/views/oa/editComplaintType',
  45 + component: () => import('@/views/oa/editComplaintTypeList.vue')
  46 + },
  47 + {
  48 + path: '/views/oa/addComplaint',
  49 + name: '/views/oa/addComplaint',
  50 + component: () => import('@/views/oa/addComplaintList.vue')
  51 + },
  52 + {
  53 + path: '/pages/complaint/complaint',
  54 + name: '/pages/complaint/complaint',
  55 + component: () => import('@/views/oa/complaintList.vue')
  56 + },
32 57 ]
33 58 \ No newline at end of file
... ...
src/views/oa/addComplaintLang.js 0 → 100644
  1 +export const messages = {
  2 + en: {
  3 + addComplaint: {
  4 + title: 'Register Complaint',
  5 + type: 'Type',
  6 + typePlaceholder: 'Required, please select type',
  7 + room: 'Room',
  8 + roomPlaceholder: 'Required, please select room',
  9 + contact: 'Contact Person',
  10 + contactPlaceholder: 'Required, please enter contact person',
  11 + phone: 'Phone Number',
  12 + phonePlaceholder: 'Required, please enter phone number',
  13 + content: 'Content',
  14 + contentPlaceholder: 'Required, please enter content',
  15 + register: 'Register',
  16 + typeRequired: 'Complaint type is required',
  17 + contactRequired: 'Contact person is required',
  18 + contactMaxLength: 'Contact person cannot exceed 200 characters',
  19 + phoneRequired: 'Phone number is required',
  20 + phoneFormat: 'Invalid phone number format',
  21 + contentRequired: 'Content is required',
  22 + contentMaxLength: 'Content cannot exceed 4000 characters',
  23 + saveSuccess: 'Complaint registered successfully',
  24 + saveError: 'Failed to register complaint',
  25 + fetchTypesError: 'Failed to fetch complaint types'
  26 + }
  27 + },
  28 + zh: {
  29 + addComplaint: {
  30 + title: '登记投诉',
  31 + type: '类型',
  32 + typePlaceholder: '必填,请选择类型',
  33 + room: '房屋',
  34 + roomPlaceholder: '必填,请选择房屋',
  35 + contact: '联系人',
  36 + contactPlaceholder: '必填,请填写联系人',
  37 + phone: '联系电话',
  38 + phonePlaceholder: '必填,请填写联系电话',
  39 + content: '内容',
  40 + contentPlaceholder: '必填,请填写内容',
  41 + register: '登记',
  42 + typeRequired: '投诉类型不能为空',
  43 + contactRequired: '联系人不能为空',
  44 + contactMaxLength: '联系人不能大于200位',
  45 + phoneRequired: '联系电话不能为空',
  46 + phoneFormat: '联系电话格式错误',
  47 + contentRequired: '内容不能为空',
  48 + contentMaxLength: '内容超过4000位',
  49 + saveSuccess: '添加成功',
  50 + saveError: '添加失败',
  51 + fetchTypesError: '获取投诉类型失败'
  52 + }
  53 + }
  54 +}
0 55 \ No newline at end of file
... ...
src/views/oa/addComplaintList.vue 0 → 100644
  1 +<template>
  2 + <div class="add-complaint-container">
  3 + <el-card class="box-card">
  4 + <div slot="header" class="clearfix">
  5 + <span>{{ $t('addComplaint.title') }}</span>
  6 + </div>
  7 + <el-form ref="form" :model="addComplaintInfo" :rules="rules" label-width="120px">
  8 + <el-row :gutter="20">
  9 + <el-col :span="24">
  10 + <el-form-item :label="$t('addComplaint.type')" prop="typeCd">
  11 + <el-select v-model="addComplaintInfo.typeCd" :placeholder="$t('addComplaint.typePlaceholder')"
  12 + style="width:100%">
  13 + <el-option v-for="item in addComplaintInfo.complaintTypes" :key="item.typeCd" :label="item.typeName"
  14 + :value="item.typeCd" />
  15 + </el-select>
  16 + </el-form-item>
  17 + </el-col>
  18 + </el-row>
  19 +
  20 + <el-row :gutter="20">
  21 + <el-col :span="24">
  22 + <el-form-item :label="$t('addComplaint.room')" prop="roomName">
  23 + <el-input v-model="addComplaintInfo.roomName" :placeholder="$t('addComplaint.roomPlaceholder')" disabled />
  24 + <el-button type="primary" @click="selectComplaintRoom" style="margin-left:10px">
  25 + {{ $t('addComplaint.register') }}
  26 + </el-button>
  27 + </el-form-item>
  28 + </el-col>
  29 + </el-row>
  30 +
  31 + <el-row :gutter="20">
  32 + <el-col :span="12">
  33 + <el-form-item :label="$t('addComplaint.contact')" prop="complaintName">
  34 + <el-input v-model="addComplaintInfo.complaintName" :placeholder="$t('addComplaint.contactPlaceholder')" />
  35 + </el-form-item>
  36 + </el-col>
  37 + <el-col :span="12">
  38 + <el-form-item :label="$t('addComplaint.phone')" prop="tel">
  39 + <el-input v-model="addComplaintInfo.tel" :placeholder="$t('addComplaint.phonePlaceholder')" />
  40 + </el-form-item>
  41 + </el-col>
  42 + </el-row>
  43 +
  44 + <el-row :gutter="20">
  45 + <el-col :span="24">
  46 + <el-form-item :label="$t('addComplaint.content')" prop="context">
  47 + <el-input type="textarea" :rows="4" v-model="addComplaintInfo.context"
  48 + :placeholder="$t('addComplaint.contentPlaceholder')" />
  49 + </el-form-item>
  50 + </el-col>
  51 + </el-row>
  52 +
  53 + <el-row :gutter="20">
  54 + <el-col :span="24" style="text-align:right">
  55 + <el-button type="warning" @click="goBack">{{ $t('common.back') }}</el-button>
  56 + <el-button type="primary" @click="saveComplaint">{{ $t('addComplaint.register') }}</el-button>
  57 + </el-col>
  58 + </el-row>
  59 + </el-form>
  60 + </el-card>
  61 + </div>
  62 +</template>
  63 +
  64 +<script>
  65 +import { listComplaintTypes, saveComplaint } from '@/api/oa/addComplaintApi'
  66 +import { getCommunityId } from '@/api/community/communityApi'
  67 +
  68 +export default {
  69 + name: 'AddComplaintList',
  70 + data() {
  71 + return {
  72 + addComplaintInfo: {
  73 + complaintTypes: [],
  74 + typeCd: '',
  75 + complaintName: '',
  76 + tel: '',
  77 + context: '',
  78 + communityId: '',
  79 + roomId: '',
  80 + roomName: '',
  81 + ownerId: '',
  82 + ownerName: ''
  83 + },
  84 + rules: {
  85 + typeCd: [{ required: true, message: this.$t('addComplaint.typeRequired'), trigger: 'blur' }],
  86 + complaintName: [
  87 + { required: true, message: this.$t('addComplaint.contactRequired'), trigger: 'blur' },
  88 + { max: 200, message: this.$t('addComplaint.contactMaxLength'), trigger: 'blur' }
  89 + ],
  90 + tel: [
  91 + { required: true, message: this.$t('addComplaint.phoneRequired'), trigger: 'blur' },
  92 + { pattern: /^1[3-9]\d{9}$/, message: this.$t('addComplaint.phoneFormat'), trigger: 'blur' }
  93 + ],
  94 + context: [
  95 + { required: true, message: this.$t('addComplaint.contentRequired'), trigger: 'blur' },
  96 + { max: 4000, message: this.$t('addComplaint.contentMaxLength'), trigger: 'blur' }
  97 + ]
  98 + }
  99 + }
  100 + },
  101 + created() {
  102 + this.communityId = getCommunityId()
  103 + this.listComplaintTypes()
  104 + },
  105 + methods: {
  106 + async listComplaintTypes() {
  107 + try {
  108 + const params = {
  109 + page: 1,
  110 + row: 100,
  111 + communityId: this.communityId
  112 + }
  113 + const { data } = await listComplaintTypes(params)
  114 + this.addComplaintInfo.complaintTypes = data
  115 + } catch (error) {
  116 + this.$message.error(this.$t('addComplaint.fetchTypesError'))
  117 + }
  118 + },
  119 + selectComplaintRoom() {
  120 + this.$emit('selectRoom')
  121 + },
  122 + async saveComplaint() {
  123 + await this.$refs.form.validate()
  124 +
  125 + try {
  126 + this.addComplaintInfo.communityId = this.communityId
  127 + await saveComplaint(this.addComplaintInfo)
  128 + this.$message.success(this.$t('addComplaint.saveSuccess'))
  129 + this.goBack()
  130 + } catch (error) {
  131 + this.$message.error(error.message || this.$t('addComplaint.saveError'))
  132 + }
  133 + },
  134 + goBack() {
  135 + this.$router.go(-1)
  136 + }
  137 + }
  138 +}
  139 +</script>
  140 +
  141 +<style scoped>
  142 +.add-complaint-container {
  143 + padding: 20px;
  144 +}
  145 +
  146 +.box-card {
  147 + margin-bottom: 20px;
  148 +}
  149 +</style>
0 150 \ No newline at end of file
... ...
src/views/oa/addComplaintTypeLang.js 0 → 100644
  1 +export const messages = {
  2 + en: {
  3 + addComplaintType: {
  4 + title: 'Add Complaint Type',
  5 + typeName: 'Type Name',
  6 + typeNamePlaceholder: 'Required, please enter type name',
  7 + notifyWay: 'Notification Method',
  8 + notifyWayPlaceholder: 'Required, please select notification method',
  9 + appraiseReply: 'Evaluation Reply',
  10 + appraiseReplyPlaceholder: 'Required, please select evaluation reply',
  11 + remark: 'Remark',
  12 + remarkPlaceholder: 'Optional, please enter remark',
  13 + selectStaff: 'Select Staff',
  14 + sms: 'SMS',
  15 + wechat: 'WeChat',
  16 + workLicense: 'WeChat + Work License',
  17 + autoReply: 'Auto Reply',
  18 + manualReply: 'Manual Reply'
  19 + },
  20 + selectStaff: {
  21 + orgInfo: 'Organization Info',
  22 + staffInfo: 'Staff Info',
  23 + selectedStaff: 'Selected Staff',
  24 + noRepeatSelect: 'Do not select repeatedly'
  25 + }
  26 + },
  27 + zh: {
  28 + addComplaintType: {
  29 + title: '添加投诉类型',
  30 + typeName: '类型名称',
  31 + typeNamePlaceholder: '必填,请填写类型名称',
  32 + notifyWay: '通知方式',
  33 + notifyWayPlaceholder: '必填,请选择通知方式',
  34 + appraiseReply: '评价回复',
  35 + appraiseReplyPlaceholder: '必填,请选择评价回复',
  36 + remark: '备注',
  37 + remarkPlaceholder: '选填,请填写备注',
  38 + selectStaff: '选择员工',
  39 + sms: '短信',
  40 + wechat: '微信',
  41 + workLicense: '微信+员工工牌',
  42 + autoReply: '自动回复',
  43 + manualReply: '人工回复'
  44 + },
  45 + selectStaff: {
  46 + orgInfo: '组织信息',
  47 + staffInfo: '员工信息',
  48 + selectedStaff: '已选员工',
  49 + noRepeatSelect: '请勿重复选择'
  50 + }
  51 + }
  52 +}
0 53 \ No newline at end of file
... ...
src/views/oa/addComplaintTypeList.vue 0 → 100644
  1 +<template>
  2 + <el-card class="complaint-type-container">
  3 + <div class="header-wrapper">
  4 + <h3>{{ $t('addComplaintType.title') }}</h3>
  5 + </div>
  6 +
  7 + <el-form ref="form" :model="addComplaintTypeInfo" label-width="120px">
  8 + <el-row :gutter="20">
  9 + <el-col :span="12">
  10 + <el-form-item :label="$t('addComplaintType.typeName')" prop="typeName">
  11 + <el-input v-model="addComplaintTypeInfo.typeName" :placeholder="$t('addComplaintType.typeNamePlaceholder')"
  12 + clearable />
  13 + </el-form-item>
  14 + </el-col>
  15 + <el-col :span="12">
  16 + <el-form-item :label="$t('addComplaintType.notifyWay')" prop="notifyWay">
  17 + <el-select v-model="addComplaintTypeInfo.notifyWay" :placeholder="$t('addComplaintType.notifyWayPlaceholder')"
  18 + style="width:100%">
  19 + <el-option v-for="item in notifyWayOptions" :key="item.value" :label="item.label" :value="item.value" />
  20 + </el-select>
  21 + </el-form-item>
  22 + </el-col>
  23 + </el-row>
  24 +
  25 + <el-row :gutter="20">
  26 + <el-col :span="12">
  27 + <el-form-item :label="$t('addComplaintType.appraiseReply')" prop="appraiseReply">
  28 + <el-select v-model="addComplaintTypeInfo.appraiseReply"
  29 + :placeholder="$t('addComplaintType.appraiseReplyPlaceholder')" style="width:100%">
  30 + <el-option v-for="item in appraiseReplyOptions" :key="item.value" :label="item.label" :value="item.value" />
  31 + </el-select>
  32 + </el-form-item>
  33 + </el-col>
  34 + <el-col :span="12">
  35 + <el-form-item :label="$t('addComplaintType.remark')" prop="remark">
  36 + <el-input v-model="addComplaintTypeInfo.remark" type="textarea"
  37 + :placeholder="$t('addComplaintType.remarkPlaceholder')" :rows="2" />
  38 + </el-form-item>
  39 + </el-col>
  40 + </el-row>
  41 +
  42 + <el-row>
  43 + <el-col :span="24">
  44 + <el-form-item :label="$t('addComplaintType.selectStaff')">
  45 + <select-staffs ref="selectStaffs" @selectStaffs="handleStaffSelected" />
  46 + </el-form-item>
  47 + </el-col>
  48 + </el-row>
  49 +
  50 + <el-row>
  51 + <el-col :span="24" class="button-group">
  52 + <el-button type="primary" @click="saveComplaintTypeInfo">
  53 + {{ $t('common.save') }}
  54 + </el-button>
  55 + <el-button @click="goBack">
  56 + {{ $t('common.back') }}
  57 + </el-button>
  58 + </el-col>
  59 + </el-row>
  60 + </el-form>
  61 + </el-card>
  62 +</template>
  63 +
  64 +<script>
  65 +import SelectStaffs from '@/components/staff/selectStaffsDiv'
  66 +import { saveComplaintType } from '@/api/oa/addComplaintTypeApi'
  67 +import { getCommunityId } from '@/api/community/communityApi'
  68 +
  69 +export default {
  70 + name: 'AddComplaintTypeList',
  71 + components: {
  72 + SelectStaffs
  73 + },
  74 + data() {
  75 + return {
  76 + addComplaintTypeInfo: {
  77 + typeName: '',
  78 + notifyWay: '',
  79 + appraiseReply: '',
  80 + remark: '',
  81 + staffs: []
  82 + },
  83 + notifyWayOptions: [
  84 + { value: 'SMS', label: this.$t('addComplaintType.sms') },
  85 + { value: 'WECHAT', label: this.$t('addComplaintType.wechat') },
  86 + { value: 'WORK_LICENSE', label: this.$t('addComplaintType.workLicense') }
  87 + ],
  88 + appraiseReplyOptions: [
  89 + { value: 'Y', label: this.$t('addComplaintType.autoReply') },
  90 + { value: 'N', label: this.$t('addComplaintType.manualReply') }
  91 + ]
  92 + }
  93 + },
  94 + created() {
  95 + this.communityId = getCommunityId()
  96 + },
  97 + methods: {
  98 + validateForm() {
  99 + return this.$refs.form.validate()
  100 + },
  101 + handleStaffSelected(staffs) {
  102 + this.addComplaintTypeInfo.staffs = staffs
  103 + },
  104 + async saveComplaintTypeInfo() {
  105 + try {
  106 + const isValid = await this.validateForm()
  107 + if (!isValid) return
  108 +
  109 + // 获取选择的员工
  110 + this.addComplaintTypeInfo.staffs = this.$refs.selectStaffs.getSelectedStaffs()
  111 +
  112 + const params = {
  113 + ...this.addComplaintTypeInfo,
  114 + communityId: this.communityId
  115 + }
  116 +
  117 + const res = await saveComplaintType(params)
  118 + if (res.code === 0) {
  119 + this.$message.success(this.$t('common.saveSuccess'))
  120 + this.goBack()
  121 + } else {
  122 + this.$message.error(res.msg)
  123 + }
  124 + } catch (error) {
  125 + console.error('保存投诉类型失败:', error)
  126 + this.$message.error(this.$t('common.saveFailed'))
  127 + }
  128 + },
  129 + goBack() {
  130 + this.$router.go(-1)
  131 + }
  132 + }
  133 +}
  134 +</script>
  135 +
  136 +<style lang="scss" scoped>
  137 +.complaint-type-container {
  138 + .header-wrapper {
  139 + margin-bottom: 20px;
  140 +
  141 + h3 {
  142 + font-size: 18px;
  143 + font-weight: bold;
  144 + }
  145 + }
  146 +
  147 + .button-group {
  148 + text-align: right;
  149 + margin-top: 20px;
  150 +
  151 + .el-button {
  152 + margin-left: 10px;
  153 + }
  154 + }
  155 +}
  156 +</style>
0 157 \ No newline at end of file
... ...
src/views/oa/complaintLang.js 0 → 100644
  1 +export const messages = {
  2 + en: {
  3 + complaint: {
  4 + search: {
  5 + title: 'Search Conditions',
  6 + type: 'Select Type',
  7 + room: 'Building-Unit-Room e.g. 1-1-1',
  8 + contact: 'Contact Person',
  9 + phone: 'Contact Phone',
  10 + id: 'Complaint ID',
  11 + startTime: 'Start Time',
  12 + endTime: 'End Time'
  13 + },
  14 + list: {
  15 + title: 'Complaints & Suggestions',
  16 + add: 'Add Complaint'
  17 + },
  18 + status: {
  19 + all: 'All',
  20 + processing: 'Processing',
  21 + completed: 'Completed'
  22 + },
  23 + table: {
  24 + id: 'ID',
  25 + type: 'Type',
  26 + room: 'Room',
  27 + contact: 'Contact',
  28 + phone: 'Phone',
  29 + status: 'Status',
  30 + handler: 'Handler',
  31 + createTime: 'Create Time'
  32 + },
  33 + edit: {
  34 + title: 'Edit Information',
  35 + type: 'Type',
  36 + contact: 'Contact',
  37 + phone: 'Phone',
  38 + content: 'Content',
  39 + typePlaceholder: 'Required, please select type',
  40 + contactPlaceholder: 'Required, please enter contact',
  41 + phonePlaceholder: 'Required, please enter phone',
  42 + contentPlaceholder: 'Required, please enter content',
  43 + success: 'Edit successful'
  44 + },
  45 + delete: {
  46 + title: 'Confirm Operation',
  47 + confirm: 'Are you sure to delete this complaint?',
  48 + success: 'Delete successful'
  49 + },
  50 + detail: {
  51 + title: 'Details',
  52 + room: 'Room',
  53 + id: 'ID',
  54 + type: 'Type',
  55 + contact: 'Contact',
  56 + phone: 'Phone',
  57 + status: 'Status',
  58 + handler: 'Handler',
  59 + handlerPhone: 'Handler Phone',
  60 + handlerId: 'Handler ID',
  61 + content: 'Content',
  62 + photos: 'Photos',
  63 + serial: 'No.',
  64 + handleTime: 'Handle Time',
  65 + duration: 'Duration',
  66 + comment: 'Comment'
  67 + },
  68 + validate: {
  69 + type: 'Type is required',
  70 + contact: 'Contact is required',
  71 + contactMax: 'Contact cannot exceed 200 characters',
  72 + phone: 'Phone is required',
  73 + phoneFormat: 'Invalid phone format',
  74 + content: 'Content is required',
  75 + contentMax: 'Content cannot exceed 4000 characters'
  76 + },
  77 + fetchError: 'Failed to fetch complaints data'
  78 + }
  79 + },
  80 + zh: {
  81 + complaint: {
  82 + search: {
  83 + title: '查询条件',
  84 + type: '请选择类型',
  85 + room: '楼栋-单元-房屋 如1-1-1',
  86 + contact: '请输入联系人',
  87 + phone: '请输入联系电话',
  88 + id: '请输入工单编号',
  89 + startTime: '请选择开始时间',
  90 + endTime: '请选择结束时间'
  91 + },
  92 + list: {
  93 + title: '投诉建议',
  94 + add: '投诉'
  95 + },
  96 + status: {
  97 + all: '全部',
  98 + processing: '处理中',
  99 + completed: '处理完成'
  100 + },
  101 + table: {
  102 + id: '订单编号',
  103 + type: '类型',
  104 + room: '房屋',
  105 + contact: '联系人',
  106 + phone: '联系电话',
  107 + status: '状态',
  108 + handler: '处理人',
  109 + createTime: '创建时间'
  110 + },
  111 + edit: {
  112 + title: '修改信息',
  113 + type: '类型',
  114 + contact: '联系人',
  115 + phone: '联系电话',
  116 + content: '内容',
  117 + typePlaceholder: '必填,请选择类型',
  118 + contactPlaceholder: '必填,请填写联系人',
  119 + phonePlaceholder: '必填,请填写联系电话',
  120 + contentPlaceholder: '必填,请填写内容',
  121 + success: '修改成功'
  122 + },
  123 + delete: {
  124 + title: '请确认您的操作',
  125 + confirm: '确定删除投诉建议吗?',
  126 + success: '删除成功'
  127 + },
  128 + detail: {
  129 + title: '详情',
  130 + room: '房屋',
  131 + id: 'ID',
  132 + type: '类型',
  133 + contact: '联系人',
  134 + phone: '联系电话',
  135 + status: '状态',
  136 + handler: '处理人',
  137 + handlerPhone: '处理人电话',
  138 + handlerId: '处理人编号',
  139 + content: '内容',
  140 + photos: '图片',
  141 + serial: '序号',
  142 + handleTime: '处理时间',
  143 + duration: '耗时',
  144 + comment: '意见'
  145 + },
  146 + validate: {
  147 + type: '投诉类型不能为空',
  148 + contact: '投诉人不能为空',
  149 + contactMax: '投诉人不能大于200位',
  150 + phone: '投诉电话不能为空',
  151 + phoneFormat: '投诉电话格式错误',
  152 + content: '投诉内容不能为空',
  153 + contentMax: '投诉状态超过4000位'
  154 + },
  155 + fetchError: '获取投诉数据失败'
  156 + }
  157 + }
  158 +}
0 159 \ No newline at end of file
... ...
src/views/oa/complaintList.vue 0 → 100644
  1 +<template>
  2 + <div class="complaint-container animated fadeInRight">
  3 + <el-row :gutter="20">
  4 + <el-col :span="4">
  5 + <el-card class="border-radius">
  6 + <div class="treeview">
  7 + <ul class="list-group text-center border-radius">
  8 + <li v-for="(item, index) in complaintInfo.states" :key="index" @click="swatchComplaintState(item)"
  9 + :class="{ 'vc-node-selected': complaintInfo.conditions.state == item.statusCd }"
  10 + class="list-group-item node-orgTree">
  11 + {{ item.name }}
  12 + </li>
  13 + </ul>
  14 + </div>
  15 + </el-card>
  16 + </el-col>
  17 + <el-col :span="20">
  18 + <el-card>
  19 + <div slot="header" class="flex justify-between">
  20 + <span>{{ $t('complaint.search.title') }}</span>
  21 + </div>
  22 + <el-row :gutter="20">
  23 + <el-col :span="4">
  24 + <el-select v-model="complaintInfo.conditions.typeCd" :placeholder="$t('complaint.search.type')"
  25 + style="width:100%">
  26 + <el-option v-for="item in complaintInfo.complaintTypes" :key="item.typeCd" :label="item.typeName"
  27 + :value="item.typeCd" />
  28 + </el-select>
  29 + </el-col>
  30 + <el-col :span="4">
  31 + <el-input v-model="complaintInfo.conditions.roomName" :placeholder="$t('complaint.search.room')" />
  32 + </el-col>
  33 + <el-col :span="4">
  34 + <el-input v-model="complaintInfo.conditions.complaintName" :placeholder="$t('complaint.search.contact')" />
  35 + </el-col>
  36 + <el-col :span="4">
  37 + <el-input v-model="complaintInfo.conditions.tel" :placeholder="$t('complaint.search.phone')" />
  38 + </el-col>
  39 + <el-col :span="4">
  40 + <el-input v-model="complaintInfo.conditions.complaintId" :placeholder="$t('complaint.search.id')" />
  41 + </el-col>
  42 + <el-col :span="4">
  43 + <el-button type="primary" @click="_queryComplaintMethod()">
  44 + {{ $t('common.search') }}
  45 + </el-button>
  46 + </el-col>
  47 + </el-row>
  48 + <el-row :gutter="20" style="margin-top:15px">
  49 + <el-col :span="4">
  50 + <el-date-picker v-model="complaintInfo.conditions.startTime" type="datetime"
  51 + :placeholder="$t('complaint.search.startTime')" style="width:100%" />
  52 + </el-col>
  53 + <el-col :span="4">
  54 + <el-date-picker v-model="complaintInfo.conditions.endTime" type="datetime"
  55 + :placeholder="$t('complaint.search.endTime')" style="width:100%" />
  56 + </el-col>
  57 + </el-row>
  58 + </el-card>
  59 +
  60 + <el-card style="margin-top:20px">
  61 + <div slot="header" class="flex justify-between">
  62 + <span>{{ $t('complaint.list.title') }}</span>
  63 + <el-button type="primary" size="small" style="float:right" @click="_openAddComplaintModal()">
  64 + <i class="el-icon-plus"></i>{{ $t('complaint.list.add') }}
  65 + </el-button>
  66 + </div>
  67 + <el-table :data="complaintInfo.complaints" border style="width:100%" v-loading="loading">
  68 + <el-table-column prop="complaintId" :label="$t('complaint.table.id')" align="center" />
  69 + <el-table-column prop="typeName" :label="$t('complaint.table.type')" align="center" />
  70 + <el-table-column prop="roomName" :label="$t('complaint.table.room')" align="center" />
  71 + <el-table-column prop="complaintName" :label="$t('complaint.table.contact')" align="center" />
  72 + <el-table-column prop="tel" :label="$t('complaint.table.phone')" align="center" />
  73 + <el-table-column prop="stateName" :label="$t('complaint.table.status')" align="center" />
  74 + <el-table-column :label="$t('complaint.table.handler')" align="center">
  75 + <template slot-scope="scope">
  76 + <span v-for="(item, index) in scope.row.staffs" :key="index">{{ item.staffName }}<br /></span>
  77 + </template>
  78 + </el-table-column>
  79 + <el-table-column prop="createTime" :label="$t('complaint.table.createTime')" align="center" />
  80 + <el-table-column :label="$t('common.operation')" align="center" width="220">
  81 + <template slot-scope="scope">
  82 + <el-button-group>
  83 + <el-button size="mini" @click="_openComplaintDetailModel(scope.row)">
  84 + {{ $t('common.detail') }}
  85 + </el-button>
  86 + <el-button size="mini" @click="_openEditComplaintModel(scope.row)">
  87 + {{ $t('common.edit') }}
  88 + </el-button>
  89 + <el-button size="mini" type="danger" @click="_openDeleteComplaintModel(scope.row)">
  90 + {{ $t('common.delete') }}
  91 + </el-button>
  92 + </el-button-group>
  93 + </template>
  94 + </el-table-column>
  95 + </el-table>
  96 + <el-pagination style="margin-top:15px" @size-change="handleSizeChange" @current-change="handleCurrentChange"
  97 + :current-page="page.current" :page-sizes="[10, 20, 30, 50]" :page-size="page.size"
  98 + layout="total, sizes, prev, pager, next, jumper" :total="page.total" />
  99 + </el-card>
  100 + </el-col>
  101 + </el-row>
  102 +
  103 + <view-image ref="viewImage" />
  104 + <edit-complaint ref="editComplaint" @success="handleSuccess" />
  105 + <delete-complaint ref="deleteComplaint" @success="handleSuccess" />
  106 + <complaint-detail ref="complaintDetail" />
  107 + </div>
  108 +</template>
  109 +
  110 +<script>
  111 +import { listComplaints, listComplaintTypes } from '@/api/oa/complaintApi'
  112 +import { getCommunityId } from '@/api/community/communityApi'
  113 +import ViewImage from '@/components/system/viewImage'
  114 +import EditComplaint from '@/components/oa/editComplaint'
  115 +import DeleteComplaint from '@/components/oa/deleteComplaint'
  116 +import ComplaintDetail from '@/components/oa/complaintDetail'
  117 +
  118 +export default {
  119 + name: 'ComplaintList',
  120 + components: {
  121 + ViewImage,
  122 + EditComplaint,
  123 + DeleteComplaint,
  124 + ComplaintDetail
  125 + },
  126 + data() {
  127 + return {
  128 + loading: false,
  129 + page: {
  130 + current: 1,
  131 + size: 10,
  132 + total: 0
  133 + },
  134 + complaintInfo: {
  135 + complaints: [],
  136 + states: [
  137 + { statusCd: '', name: this.$t('complaint.status.all') },
  138 + { statusCd: 'W', name: this.$t('complaint.status.processing') },
  139 + { statusCd: 'C', name: this.$t('complaint.status.completed') }
  140 + ],
  141 + complaintTypes: [],
  142 + conditions: {
  143 + complaintId: '',
  144 + typeCd: '',
  145 + complaintName: '',
  146 + tel: '',
  147 + roomName: '',
  148 + state: '',
  149 + startTime: '',
  150 + endTime: '',
  151 + communityId: ''
  152 + }
  153 + }
  154 + }
  155 + },
  156 + created() {
  157 + this.communityId = getCommunityId()
  158 + this.complaintInfo.conditions.communityId = this.communityId
  159 + this._listComplaintTypes()
  160 + this._listComplaints()
  161 + },
  162 + methods: {
  163 + async _listComplaints() {
  164 + try {
  165 + this.loading = true
  166 + const params = {
  167 + ...this.complaintInfo.conditions,
  168 + page: this.page.current,
  169 + row: this.page.size
  170 + }
  171 + const { data, total } = await listComplaints(params)
  172 + this.complaintInfo.complaints = data
  173 + this.page.total = total
  174 + } catch (error) {
  175 + this.$message.error(this.$t('complaint.fetchError'))
  176 + } finally {
  177 + this.loading = false
  178 + }
  179 + },
  180 + async _listComplaintTypes() {
  181 + try {
  182 + const params = {
  183 + page: 1,
  184 + row: 100,
  185 + communityId: this.communityId
  186 + }
  187 + const { data } = await listComplaintTypes(params)
  188 + this.complaintInfo.complaintTypes = data
  189 + } catch (error) {
  190 + console.error('获取投诉类型失败:', error)
  191 + }
  192 + },
  193 + swatchComplaintState(item) {
  194 + this.complaintInfo.conditions.state = item.statusCd
  195 + this._listComplaints()
  196 + },
  197 + _openAddComplaintModal() {
  198 + this.$router.push('/views/oa/addComplaint')
  199 + },
  200 + _openEditComplaintModel(row) {
  201 + this.$refs.editComplaint.open(row)
  202 + },
  203 + _openDeleteComplaintModel(row) {
  204 + this.$refs.deleteComplaint.open(row)
  205 + },
  206 + _openComplaintDetailModel(row) {
  207 + this.$refs.complaintDetail.open(row)
  208 + },
  209 + _queryComplaintMethod() {
  210 + this.page.current = 1
  211 + this._listComplaints()
  212 + },
  213 + handleSizeChange(val) {
  214 + this.page.size = val
  215 + this._listComplaints()
  216 + },
  217 + handleCurrentChange(val) {
  218 + this.page.current = val
  219 + this._listComplaints()
  220 + },
  221 + handleSuccess() {
  222 + this._listComplaints()
  223 + }
  224 + }
  225 +}
  226 +</script>
  227 +
  228 +<style lang="scss" scoped>
  229 +.complaint-container {
  230 + padding: 20px;
  231 +
  232 + .border-radius {
  233 + border-radius: 4px;
  234 + }
  235 +
  236 + .treeview {
  237 + .list-group {
  238 + padding: 0;
  239 + margin: 0;
  240 + list-style: none;
  241 +
  242 + .list-group-item {
  243 + padding: 10px 15px;
  244 + margin-bottom: -1px;
  245 + border: 1px solid #ddd;
  246 + cursor: pointer;
  247 +
  248 + &:hover {
  249 + background-color: #f5f5f5;
  250 + }
  251 +
  252 + &.vc-node-selected {
  253 + background-color: #409EFF;
  254 + color: #fff;
  255 + }
  256 + }
  257 + }
  258 + }
  259 +}
  260 +</style>
0 261 \ No newline at end of file
... ...
src/views/oa/complaintTypeLang.js 0 → 100644
  1 +export const messages = {
  2 + en: {
  3 + complaintType: {
  4 + title: 'Complaint Type',
  5 + add: 'Add',
  6 + typeName: 'Type Name',
  7 + notifyWay: 'Notification Method',
  8 + sms: 'SMS',
  9 + wechat: 'WeChat',
  10 + wechatWorkLicense: 'WeChat + Work License',
  11 + appraiseReply: 'Evaluation Reply',
  12 + autoReply: 'Auto Reply',
  13 + manualReply: 'Manual Reply',
  14 + handler: 'Handler',
  15 + createTime: 'Create Time',
  16 + fetchError: 'Failed to fetch complaint types'
  17 + },
  18 + deleteComplaintType: {
  19 + confirmTitle: 'Confirm Operation',
  20 + confirmText: 'Are you sure to delete this complaint type?',
  21 + confirm: 'Confirm Delete',
  22 + cancel: 'Cancel',
  23 + success: 'Delete successfully',
  24 + error: 'Delete failed'
  25 + }
  26 + },
  27 + zh: {
  28 + complaintType: {
  29 + title: '投诉类型',
  30 + add: '添加',
  31 + typeName: '类型名称',
  32 + notifyWay: '通知方式',
  33 + sms: '短信',
  34 + wechat: '微信',
  35 + wechatWorkLicense: '微信+员工工牌',
  36 + appraiseReply: '评价回复',
  37 + autoReply: '自动回复',
  38 + manualReply: '人工回复',
  39 + handler: '处理人',
  40 + createTime: '创建时间',
  41 + fetchError: '获取投诉类型失败'
  42 + },
  43 + deleteComplaintType: {
  44 + confirmTitle: '请确认您的操作',
  45 + confirmText: '确定删除投诉类型',
  46 + confirm: '确认删除',
  47 + cancel: '点错了',
  48 + success: '删除成功',
  49 + error: '删除失败'
  50 + }
  51 + }
  52 +}
0 53 \ No newline at end of file
... ...
src/views/oa/complaintTypeList.vue 0 → 100644
  1 +<template>
  2 + <div class="complaint-type-container">
  3 + <el-card class="box-card">
  4 + <div slot="header" class="flex justify-between">
  5 + <span>{{ $t('complaintType.title') }}</span>
  6 + <el-button type="primary" size="small" style="float: right;" @click="_openAddComplaintTypeModal">
  7 + {{ $t('complaintType.add') }}
  8 + </el-button>
  9 + </div>
  10 +
  11 + <el-table :data="complaintTypeInfo.complaintTypes" border style="width: 100%" v-loading="loading">
  12 + <el-table-column prop="typeName" :label="$t('complaintType.typeName')" align="center" />
  13 + <el-table-column prop="notifyWay" :label="$t('complaintType.notifyWay')" align="center">
  14 + <template slot-scope="scope">
  15 + <span v-if="scope.row.notifyWay === 'SMS'">
  16 + {{ $t('complaintType.sms') }}
  17 + </span>
  18 + <span v-else-if="scope.row.notifyWay === 'WORK_LICENSE'">
  19 + {{ $t('complaintType.wechatWorkLicense') }}
  20 + </span>
  21 + <span v-else>
  22 + {{ $t('complaintType.wechat') }}
  23 + </span>
  24 + </template>
  25 + </el-table-column>
  26 + <el-table-column prop="appraiseReply" :label="$t('complaintType.appraiseReply')" align="center">
  27 + <template slot-scope="scope">
  28 + {{ scope.row.appraiseReply === 'Y' ? $t('complaintType.autoReply') : $t('complaintType.manualReply') }}
  29 + </template>
  30 + </el-table-column>
  31 + <el-table-column prop="staffs" :label="$t('complaintType.handler')" align="center">
  32 + <template slot-scope="scope">
  33 + <div v-for="(item, index) in scope.row.staffs" :key="index">
  34 + {{ item.staffName }}
  35 + </div>
  36 + </template>
  37 + </el-table-column>
  38 + <el-table-column prop="createTime" :label="$t('complaintType.createTime')" align="center" />
  39 + <el-table-column :label="$t('common.operation')" align="center" width="200">
  40 + <template slot-scope="scope">
  41 + <el-button size="mini" type="primary" @click="_openEditComplaintTypeModel(scope.row)">
  42 + {{ $t('common.edit') }}
  43 + </el-button>
  44 + <el-button size="mini" type="danger" @click="_openDeleteComplaintTypeModel(scope.row)">
  45 + {{ $t('common.delete') }}
  46 + </el-button>
  47 + </template>
  48 + </el-table-column>
  49 + </el-table>
  50 +
  51 + <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="page.current"
  52 + :page-sizes="[10, 20, 30, 50]" :page-size="page.size" layout="total, sizes, prev, pager, next, jumper"
  53 + :total="complaintTypeInfo.total" style="margin-top: 20px;" />
  54 + </el-card>
  55 +
  56 + <delete-complaint-type ref="deleteComplaintType" @success="handleSuccess" />
  57 + </div>
  58 +</template>
  59 +
  60 +<script>
  61 +import { listComplaintType } from '@/api/oa/complaintTypeApi'
  62 +import DeleteComplaintType from '@/components/oa/deleteComplaintType'
  63 +import { getCommunityId } from '@/api/community/communityApi'
  64 +
  65 +export default {
  66 + name: 'ComplaintTypeList',
  67 + components: {
  68 + DeleteComplaintType
  69 + },
  70 + data() {
  71 + return {
  72 + loading: false,
  73 + complaintTypeInfo: {
  74 + complaintTypes: [],
  75 + total: 0,
  76 + conditions: {
  77 + typeCd: '',
  78 + typeName: '',
  79 + notifyWay: ''
  80 + }
  81 + },
  82 + page: {
  83 + current: 1,
  84 + size: 10
  85 + }
  86 + }
  87 + },
  88 + created() {
  89 + this.communityId = getCommunityId()
  90 + this._listComplaintTypes()
  91 + },
  92 + methods: {
  93 + async _listComplaintTypes() {
  94 + try {
  95 + this.loading = true
  96 + const params = {
  97 + page: this.page.current,
  98 + row: this.page.size,
  99 + communityId: this.communityId,
  100 + ...this.complaintTypeInfo.conditions
  101 + }
  102 + const { data, total } = await listComplaintType(params)
  103 + this.complaintTypeInfo.complaintTypes = data
  104 + this.complaintTypeInfo.total = total
  105 + } catch (error) {
  106 + this.$message.error(this.$t('complaintType.fetchError'))
  107 + } finally {
  108 + this.loading = false
  109 + }
  110 + },
  111 + _openAddComplaintTypeModal() {
  112 + this.$router.push('/views/oa/addComplaintType')
  113 + },
  114 + _openEditComplaintTypeModel(complaintType) {
  115 + this.$router.push(`/views/oa/editComplaintType?typeCd=${complaintType.typeCd}`)
  116 + },
  117 + _openDeleteComplaintTypeModel(complaintType) {
  118 + this.$refs.deleteComplaintType.open(complaintType)
  119 + },
  120 + handleSuccess() {
  121 + this._listComplaintTypes()
  122 + },
  123 + handleSizeChange(val) {
  124 + this.page.size = val
  125 + this._listComplaintTypes()
  126 + },
  127 + handleCurrentChange(val) {
  128 + this.page.current = val
  129 + this._listComplaintTypes()
  130 + }
  131 + }
  132 +}
  133 +</script>
  134 +
  135 +<style lang="scss" scoped>
  136 +.complaint-type-container {
  137 + padding: 20px;
  138 +
  139 + .box-card {
  140 + margin-bottom: 20px;
  141 + }
  142 +
  143 + .el-pagination {
  144 + margin-top: 20px;
  145 + text-align: right;
  146 + }
  147 +}
  148 +</style>
0 149 \ No newline at end of file
... ...
src/views/oa/editComplaintTypeLang.js 0 → 100644
  1 +export const messages = {
  2 + en: {
  3 + editComplaintType: {
  4 + title: 'Edit Complaint Type',
  5 + typeName: 'Type Name',
  6 + typeNamePlaceholder: 'Required, please enter type name',
  7 + notifyWay: 'Notification Method',
  8 + notifyWayPlaceholder: 'Required, please select notification method',
  9 + sms: 'SMS',
  10 + wechat: 'WeChat',
  11 + workLicense: 'WeChat + Work License',
  12 + appraiseReply: 'Evaluation Reply',
  13 + appraiseReplyPlaceholder: 'Required, please select evaluation reply',
  14 + autoReply: 'Auto Reply',
  15 + manualReply: 'Manual Reply',
  16 + remark: 'Remark',
  17 + remarkPlaceholder: 'Optional, please enter remark',
  18 + selectStaff: 'Select Staff',
  19 + saveSuccess: 'Save successfully',
  20 + saveError: 'Save failed',
  21 + loadError: 'Failed to load complaint type details'
  22 + },
  23 + selectStaff: {
  24 + orgInfo: 'Organization Information',
  25 + staffInfo: 'Staff Information',
  26 + selectedStaff: 'Selected Staff',
  27 + staffAlreadySelected: 'Staff already selected',
  28 + loadStaffError: 'Failed to load staff information'
  29 + },
  30 + orgTree: {
  31 + loadError: 'Failed to load organization tree'
  32 + }
  33 + },
  34 + zh: {
  35 + editComplaintType: {
  36 + title: '修改投诉类型',
  37 + typeName: '类型名称',
  38 + typeNamePlaceholder: '必填,请填写类型名称',
  39 + notifyWay: '通知方式',
  40 + notifyWayPlaceholder: '必填,请选择通知方式',
  41 + sms: '短信',
  42 + wechat: '微信',
  43 + workLicense: '微信+员工工牌',
  44 + appraiseReply: '评价回复',
  45 + appraiseReplyPlaceholder: '必填,请选择评价回复',
  46 + autoReply: '自动回复',
  47 + manualReply: '人工回复',
  48 + remark: '备注',
  49 + remarkPlaceholder: '选填,请填写备注',
  50 + selectStaff: '选择员工',
  51 + saveSuccess: '保存成功',
  52 + saveError: '保存失败',
  53 + loadError: '加载投诉类型详情失败'
  54 + },
  55 + selectStaff: {
  56 + orgInfo: '组织信息',
  57 + staffInfo: '员工信息',
  58 + selectedStaff: '已选员工',
  59 + staffAlreadySelected: '请勿重复选择',
  60 + loadStaffError: '加载员工信息失败'
  61 + },
  62 + orgTree: {
  63 + loadError: '加载组织树失败'
  64 + }
  65 + }
  66 +}
0 67 \ No newline at end of file
... ...
src/views/oa/editComplaintTypeList.vue 0 → 100644
  1 +<template>
  2 + <el-card class="edit-complaint-type-container">
  3 + <div slot="header" class="clearfix">
  4 + <span>{{ $t('editComplaintType.title') }}</span>
  5 + </div>
  6 +
  7 + <el-form ref="form" :model="editComplaintTypeInfo" label-width="120px">
  8 + <el-row :gutter="20">
  9 + <el-col :span="12">
  10 + <el-form-item :label="$t('editComplaintType.typeName')" prop="typeName">
  11 + <el-input v-model="editComplaintTypeInfo.typeName"
  12 + :placeholder="$t('editComplaintType.typeNamePlaceholder')" />
  13 + </el-form-item>
  14 + </el-col>
  15 + <el-col :span="12">
  16 + <el-form-item :label="$t('editComplaintType.notifyWay')" prop="notifyWay">
  17 + <el-select v-model="editComplaintTypeInfo.notifyWay"
  18 + :placeholder="$t('editComplaintType.notifyWayPlaceholder')" style="width:100%">
  19 + <el-option v-for="item in notifyWayOptions" :key="item.value" :label="item.label" :value="item.value" />
  20 + </el-select>
  21 + </el-form-item>
  22 + </el-col>
  23 + </el-row>
  24 +
  25 + <el-row :gutter="20">
  26 + <el-col :span="12">
  27 + <el-form-item :label="$t('editComplaintType.appraiseReply')" prop="appraiseReply">
  28 + <el-select v-model="editComplaintTypeInfo.appraiseReply"
  29 + :placeholder="$t('editComplaintType.appraiseReplyPlaceholder')" style="width:100%">
  30 + <el-option v-for="item in appraiseReplyOptions" :key="item.value" :label="item.label" :value="item.value" />
  31 + </el-select>
  32 + </el-form-item>
  33 + </el-col>
  34 + <el-col :span="12">
  35 + <el-form-item :label="$t('editComplaintType.remark')" prop="remark">
  36 + <el-input v-model="editComplaintTypeInfo.remark" type="textarea"
  37 + :placeholder="$t('editComplaintType.remarkPlaceholder')" />
  38 + </el-form-item>
  39 + </el-col>
  40 + </el-row>
  41 +
  42 + <el-row>
  43 + <el-col :span="24">
  44 + <el-form-item :label="$t('editComplaintType.selectStaff')">
  45 + <select-staffs ref="selectStaffs" @selectStaffs="handleStaffSelected"/>
  46 + </el-form-item>
  47 + </el-col>
  48 + </el-row>
  49 +
  50 + <el-row>
  51 + <el-col :span="24" class="text-right">
  52 + <el-button type="warning" @click="handleBack">
  53 + <i class="el-icon-close"></i>
  54 + {{ $t('common.back') }}
  55 + </el-button>
  56 + <el-button type="primary" @click="handleSave">
  57 + <i class="el-icon-check"></i>
  58 + {{ $t('common.save') }}
  59 + </el-button>
  60 + </el-col>
  61 + </el-row>
  62 + </el-form>
  63 + </el-card>
  64 +</template>
  65 +
  66 +<script>
  67 +import { updateComplaintType, getComplaintTypeDetail } from '@/api/oa/editComplaintTypeApi'
  68 +import SelectStaffs from '@/components/staff/selectStaffsDiv'
  69 +import { getCommunityId } from '@/api/community/communityApi'
  70 +
  71 +export default {
  72 + name: 'EditComplaintTypeList',
  73 + components: {
  74 + SelectStaffs
  75 + },
  76 + data() {
  77 + return {
  78 + editComplaintTypeInfo: {
  79 + typeCd: '',
  80 + typeName: '',
  81 + notifyWay: '',
  82 + appraiseReply: '',
  83 + remark: '',
  84 + staffs: [],
  85 + communityId: ''
  86 + },
  87 + notifyWayOptions: [
  88 + { value: 'SMS', label: this.$t('editComplaintType.sms') },
  89 + { value: 'WECHAT', label: this.$t('editComplaintType.wechat') },
  90 + { value: 'WORK_LICENSE', label: this.$t('editComplaintType.workLicense') }
  91 + ],
  92 + appraiseReplyOptions: [
  93 + { value: 'Y', label: this.$t('editComplaintType.autoReply') },
  94 + { value: 'N', label: this.$t('editComplaintType.manualReply') }
  95 + ]
  96 + }
  97 + },
  98 + created() {
  99 + this.editComplaintTypeInfo.communityId = getCommunityId()
  100 + this.editComplaintTypeInfo.typeCd = this.$route.query.typeCd
  101 + this.loadComplaintTypeDetail()
  102 + },
  103 + methods: {
  104 + handleStaffSelected(staffs) {
  105 + this.editComplaintTypeInfo.staffs = staffs
  106 + },
  107 + async loadComplaintTypeDetail() {
  108 + try {
  109 + const params = {
  110 + typeCd: this.editComplaintTypeInfo.typeCd,
  111 + communityId: this.editComplaintTypeInfo.communityId,
  112 + page: 1,
  113 + row: 1
  114 + }
  115 + const { data } = await getComplaintTypeDetail(params)
  116 + if (data && data.length > 0) {
  117 + this.editComplaintTypeInfo = { ...data[0] }
  118 + let _staffs = []
  119 + data[0].staffs.forEach(staff => {
  120 + _staffs.push({ userId: staff.staffId, name: staff.staffName })
  121 + });
  122 + this.$refs.selectStaffs.setStaffs(_staffs || [])
  123 + }
  124 + } catch (error) {
  125 + this.$message.error(this.$t('editComplaintType.loadError'))
  126 + }
  127 + },
  128 + validateForm() {
  129 + return this.$refs.form.validate()
  130 + },
  131 + async handleSave() {
  132 + try {
  133 + const isValid = await this.validateForm()
  134 + if (!isValid) return
  135 +
  136 + this.editComplaintTypeInfo.staffs = this.$refs.selectStaffs.getSelectedStaffs()
  137 + await updateComplaintType(this.editComplaintTypeInfo)
  138 + this.$message.success(this.$t('editComplaintType.saveSuccess'))
  139 + this.handleBack()
  140 + } catch (error) {
  141 + this.$message.error(error.message || this.$t('editComplaintType.saveError'))
  142 + }
  143 + },
  144 + handleBack() {
  145 + this.$router.go(-1)
  146 + }
  147 + }
  148 +}
  149 +</script>
  150 +
  151 +<style lang="scss" scoped>
  152 +.edit-complaint-type-container {
  153 + margin: 20px;
  154 +
  155 + .text-right {
  156 + text-align: right;
  157 + }
  158 +
  159 + .el-form-item {
  160 + margin-bottom: 22px;
  161 + }
  162 +}
  163 +</style>
0 164 \ No newline at end of file
... ...