Commit 0bf7e6a57b454586fc1c8153599d30ea63c7f30b

Authored by wuxw
1 parent 2ca25ccd

加入问卷功能代码

Showing 42 changed files with 5405 additions and 6 deletions
src/api/community/communityApi.js
@@ -57,6 +57,18 @@ export function getCommunityId() { @@ -57,6 +57,18 @@ export function getCommunityId() {
57 return getCurrentCommunity().communityId 57 return getCurrentCommunity().communityId
58 } 58 }
59 59
  60 +/************* ✨ Windsurf Command ⭐ *************/
  61 +/**
  62 + * Get the current community
  63 + * @returns {Object} The current community object
  64 + */
  65 +/******* 8c78c7d7-a438-4630-a8c7-b05b9f491159 *******/
  66 +
  67 +
  68 +export function getCommunity() {
  69 + return getCurrentCommunity()
  70 +}
  71 +
60 72
61 73
62 export function getCommunityName() { 74 export function getCommunityName() {
src/api/oa/addQuestionAnswerApi.js 0 → 100644
  1 +import request from '@/utils/request'
  2 +import { getCommunityId } from '@/api/community/communityApi'
  3 +
  4 +// 查询楼栋和单元信息
  5 +export function queryFloorAndUnits(params) {
  6 + return new Promise((resolve, reject) => {
  7 + request({
  8 + url: '/floor.queryFloorAndUnits',
  9 + method: 'get',
  10 + params: {
  11 + ...params,
  12 + communityId: params.communityId || getCommunityId()
  13 + }
  14 + }).then(response => {
  15 + resolve(response.data)
  16 + }).catch(error => {
  17 + reject(error)
  18 + })
  19 + })
  20 +}
  21 +
  22 +// 查询房屋信息
  23 +export function queryRooms(params) {
  24 + return new Promise((resolve, reject) => {
  25 + request({
  26 + url: '/room.queryRooms',
  27 + method: 'get',
  28 + params: {
  29 + ...params,
  30 + communityId: params.communityId || getCommunityId()
  31 + }
  32 + }).then(response => {
  33 + resolve(response.data)
  34 + }).catch(error => {
  35 + reject(error)
  36 + })
  37 + })
  38 +}
  39 +
  40 +// 查询题目列表
  41 +export function listQuestionTitle(params) {
  42 + return new Promise((resolve, reject) => {
  43 + request({
  44 + url: '/question.listQuestionTitle',
  45 + method: 'get',
  46 + params: {
  47 + ...params,
  48 + communityId: params.communityId || getCommunityId()
  49 + }
  50 + }).then(response => {
  51 + resolve(response.data)
  52 + }).catch(error => {
  53 + reject(error)
  54 + })
  55 + })
  56 +}
  57 +
  58 +// 保存问卷
  59 +export function saveQuestionAnswer(data) {
  60 + return new Promise((resolve, reject) => {
  61 + request({
  62 + url: '/question.saveQuestionAnswer',
  63 + method: 'post',
  64 + data: {
  65 + ...data,
  66 + communityId: data.communityId || getCommunityId()
  67 + }
  68 + }).then(response => {
  69 + resolve(response.data)
  70 + }).catch(error => {
  71 + reject(error)
  72 + })
  73 + })
  74 +}
0 \ No newline at end of file 75 \ No newline at end of file
src/api/oa/editQuestionAnswerApi.js 0 → 100644
  1 +import request from '@/utils/request'
  2 +
  3 +// 查询问卷信息
  4 +export function listQuestionAnswer(params) {
  5 + return new Promise((resolve, reject) => {
  6 + request({
  7 + url: '/question.listQuestionAnswer',
  8 + method: 'get',
  9 + params
  10 + }).then(response => {
  11 + const res = response.data
  12 + resolve(res)
  13 + }).catch(error => {
  14 + reject(error)
  15 + })
  16 + })
  17 +}
  18 +
  19 +// 更新问卷信息
  20 +export function updateQuestionAnswer(data) {
  21 + return new Promise((resolve, reject) => {
  22 + request({
  23 + url: '/question.updateQuestionAnswer',
  24 + method: 'post',
  25 + data
  26 + }).then(response => {
  27 + const res = response.data
  28 + resolve(res)
  29 + }).catch(error => {
  30 + reject(error)
  31 + })
  32 + })
  33 +}
  34 +
  35 +// 查询题目列表
  36 +export function listQuestionTitle(params) {
  37 + return new Promise((resolve, reject) => {
  38 + request({
  39 + url: '/question.listQuestionTitle',
  40 + method: 'get',
  41 + params
  42 + }).then(response => {
  43 + const res = response.data
  44 + resolve(res)
  45 + }).catch(error => {
  46 + reject(error)
  47 + })
  48 + })
  49 +}
  50 +
  51 +// 查询楼栋和单元信息
  52 +export function queryFloorAndUnits(params) {
  53 + return new Promise((resolve, reject) => {
  54 + request({
  55 + url: '/floor.queryFloorAndUnits',
  56 + method: 'get',
  57 + params
  58 + }).then(response => {
  59 + const res = response.data
  60 + resolve(res)
  61 + }).catch(error => {
  62 + reject(error)
  63 + })
  64 + })
  65 +}
  66 +
  67 +// 查询房屋信息
  68 +export function queryRooms(params) {
  69 + return new Promise((resolve, reject) => {
  70 + request({
  71 + url: '/room.queryRooms',
  72 + method: 'get',
  73 + params
  74 + }).then(response => {
  75 + const res = response.data
  76 + resolve(res)
  77 + }).catch(error => {
  78 + reject(error)
  79 + })
  80 + })
  81 +}
0 \ No newline at end of file 82 \ No newline at end of file
src/api/oa/notepadManageApi.js 0 → 100644
  1 +import request from '@/utils/request'
  2 +import { getCommunityId } from '@/api/community/communityApi'
  3 +
  4 +// 获取记事本列表
  5 +export function listNotepad(params) {
  6 + return new Promise((resolve, reject) => {
  7 + request({
  8 + url: '/notepad.listNotepad',
  9 + method: 'get',
  10 + params: {
  11 + ...params,
  12 + communityId: getCommunityId()
  13 + }
  14 + }).then(response => {
  15 + const res = response.data
  16 + resolve({
  17 + data: res.data,
  18 + total: res.total
  19 + })
  20 + }).catch(error => {
  21 + reject(error)
  22 + })
  23 + })
  24 +}
  25 +
  26 +// 更新记事本
  27 +export function updateNotepad(data) {
  28 + return new Promise((resolve, reject) => {
  29 + request({
  30 + url: '/notepad.updateNotepad',
  31 + method: 'post',
  32 + data: {
  33 + ...data,
  34 + communityId: getCommunityId()
  35 + }
  36 + }).then(response => {
  37 + const res = response.data
  38 + resolve(res)
  39 + }).catch(error => {
  40 + reject(error)
  41 + })
  42 + })
  43 +}
  44 +
  45 +// 删除记事本
  46 +export function deleteNotepad(data) {
  47 + return new Promise((resolve, reject) => {
  48 + request({
  49 + url: '/notepad.deleteNotepad',
  50 + method: 'post',
  51 + data: {
  52 + ...data,
  53 + communityId: getCommunityId()
  54 + }
  55 + }).then(response => {
  56 + const res = response.data
  57 + resolve(res)
  58 + }).catch(error => {
  59 + reject(error)
  60 + })
  61 + })
  62 +}
  63 +
  64 +// 保存记事本详情
  65 +export function saveNotepadDetail(data) {
  66 + return new Promise((resolve, reject) => {
  67 + request({
  68 + url: '/notepad.saveNotepadDetail',
  69 + method: 'post',
  70 + data: {
  71 + ...data,
  72 + communityId: getCommunityId()
  73 + }
  74 + }).then(response => {
  75 + const res = response.data
  76 + resolve(res)
  77 + }).catch(error => {
  78 + reject(error)
  79 + })
  80 + })
  81 +}
  82 +
  83 +// 获取记事本详情列表
  84 +export function listNotepadDetail(params) {
  85 + return new Promise((resolve, reject) => {
  86 + request({
  87 + url: '/notepad.listNotepadDetail',
  88 + method: 'get',
  89 + params: {
  90 + ...params,
  91 + communityId: getCommunityId()
  92 + }
  93 + }).then(response => {
  94 + const res = response.data
  95 + resolve({
  96 + data: res.data,
  97 + total: res.total
  98 + })
  99 + }).catch(error => {
  100 + reject(error)
  101 + })
  102 + })
  103 +}
  104 +
  105 +// 删除记事本详情
  106 +export function deleteNotepadDetail(data) {
  107 + return new Promise((resolve, reject) => {
  108 + request({
  109 + url: '/notepad.deleteNotepadDetail',
  110 + method: 'post',
  111 + data: {
  112 + ...data,
  113 + communityId: getCommunityId()
  114 + }
  115 + }).then(response => {
  116 + const res = response.data
  117 + resolve(res)
  118 + }).catch(error => {
  119 + reject(error)
  120 + })
  121 + })
  122 +}
  123 +
  124 +// 保存业主报修信息
  125 +export function saveOwnerRepair(data) {
  126 + return new Promise((resolve, reject) => {
  127 + request({
  128 + url: '/ownerRepair.saveOwnerRepair',
  129 + method: 'post',
  130 + data: {
  131 + ...data,
  132 + communityId: getCommunityId()
  133 + }
  134 + }).then(response => {
  135 + const res = response.data
  136 + resolve(res)
  137 + }).catch(error => {
  138 + reject(error)
  139 + })
  140 + })
  141 +}
  142 +
  143 +// 获取报修设置列表
  144 +export function listRepairSettings(params) {
  145 + return new Promise((resolve, reject) => {
  146 + request({
  147 + url: '/repair.listRepairSettings',
  148 + method: 'get',
  149 + params: {
  150 + ...params,
  151 + communityId: getCommunityId()
  152 + }
  153 + }).then(response => {
  154 + const res = response.data
  155 + resolve({
  156 + data: res.data,
  157 + total: res.total
  158 + })
  159 + }).catch(error => {
  160 + reject(error)
  161 + })
  162 + })
  163 +}
0 \ No newline at end of file 164 \ No newline at end of file
src/api/oa/printQuestionAnswerApi.js 0 → 100644
  1 +import request from '@/utils/request'
  2 +
  3 +// 获取问题答案列表
  4 +export function listQuestionAnswer(params) {
  5 + return new Promise((resolve, reject) => {
  6 + request({
  7 + url: '/question.listQuestionAnswer',
  8 + method: 'get',
  9 + params
  10 + }).then(response => {
  11 + const res = response.data
  12 + resolve(res)
  13 + }).catch(error => {
  14 + reject(error)
  15 + })
  16 + })
  17 +}
  18 +
  19 +// 获取问题标题列表
  20 +export function listQuestionTitle(params) {
  21 + return new Promise((resolve, reject) => {
  22 + request({
  23 + url: '/question.listQuestionTitle',
  24 + method: 'get',
  25 + params
  26 + }).then(response => {
  27 + const res = response.data
  28 + resolve(res)
  29 + }).catch(error => {
  30 + reject(error)
  31 + })
  32 + })
  33 +}
0 \ No newline at end of file 34 \ No newline at end of file
src/api/oa/printQuestionAnswerDetailApi.js 0 → 100644
  1 +import request from '@/utils/request'
  2 +
  3 +// 获取问题回答列表
  4 +export function listQuestionAnswer(params) {
  5 + return new Promise((resolve, reject) => {
  6 + request({
  7 + url: '/question.listQuestionAnswer',
  8 + method: 'get',
  9 + params
  10 + }).then(response => {
  11 + const res = response.data
  12 + resolve(res)
  13 + }).catch(error => {
  14 + reject(error)
  15 + })
  16 + })
  17 +}
  18 +
  19 +// 获取问题标题列表
  20 +export function listQuestionTitle(params) {
  21 + return new Promise((resolve, reject) => {
  22 + request({
  23 + url: '/question.listQuestionTitle',
  24 + method: 'get',
  25 + params
  26 + }).then(response => {
  27 + const res = response.data
  28 + resolve(res)
  29 + }).catch(error => {
  30 + reject(error)
  31 + })
  32 + })
  33 +}
  34 +
  35 +// 获取用户问题回答列表
  36 +export function listUserQuestionAnswer(params) {
  37 + return new Promise((resolve, reject) => {
  38 + request({
  39 + url: '/question.listUserQuestionAnswer',
  40 + method: 'get',
  41 + params
  42 + }).then(response => {
  43 + const res = response.data
  44 + resolve(res)
  45 + }).catch(error => {
  46 + reject(error)
  47 + })
  48 + })
  49 +}
0 \ No newline at end of file 50 \ No newline at end of file
src/api/oa/questionAnswerManageApi.js 0 → 100644
  1 +import request from '@/utils/request'
  2 +import { getCommunityId } from '@/api/community/communityApi'
  3 +
  4 +// 获取问卷列表
  5 +export function listQuestionAnswer(params) {
  6 + return new Promise((resolve, reject) => {
  7 + request({
  8 + url: '/question.listQuestionAnswer',
  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 updateQuestionAnswer(data) {
  25 + return new Promise((resolve, reject) => {
  26 + request({
  27 + url: '/questionAnswer.updateQuestionAnswer',
  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 deleteQuestionAnswer(params) {
  44 + return new Promise((resolve, reject) => {
  45 + request({
  46 + url: '/question.deleteQuestionAnswer',
  47 + method: 'post',
  48 + data: {
  49 + ...params,
  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 publishQuestionAnswer(data) {
  63 + return new Promise((resolve, reject) => {
  64 + request({
  65 + url: '/question.publishQuestion',
  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 uploadImage(data) {
  82 + return new Promise((resolve, reject) => {
  83 + request({
  84 + url: '/uploadImage',
  85 + method: 'post',
  86 + headers: {
  87 + 'Content-Type': 'multipart/form-data'
  88 + },
  89 + data
  90 + }).then(response => {
  91 + const res = response.data
  92 + resolve(res)
  93 + }).catch(error => {
  94 + reject(error)
  95 + })
  96 + })
  97 +}
0 \ No newline at end of file 98 \ No newline at end of file
src/api/oa/questionTitleApi.js 0 → 100644
  1 +import request from '@/utils/request'
  2 +import { getCommunityId } from '@/api/community/communityApi'
  3 +
  4 +// 获取题目列表
  5 +export function listQuestionTitle(params) {
  6 + return new Promise((resolve, reject) => {
  7 + const communityId = getCommunityId()
  8 + request({
  9 + url: '/question.listQuestionTitle',
  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 saveQuestionTitle(data) {
  26 + return new Promise((resolve, reject) => {
  27 + const communityId = getCommunityId()
  28 + request({
  29 + url: '/question.saveQuestionTitle',
  30 + method: 'post',
  31 + data: {
  32 + ...data,
  33 + communityId
  34 + }
  35 + }).then(response => {
  36 + const res = response.data
  37 + resolve(res)
  38 + }).catch(error => {
  39 + reject(error)
  40 + })
  41 + })
  42 +}
  43 +
  44 +// 更新题目
  45 +export function updateQuestionTitle(data) {
  46 + return new Promise((resolve, reject) => {
  47 + const communityId = getCommunityId()
  48 + request({
  49 + url: '/question.updateQuestionTitle',
  50 + method: 'post',
  51 + data: {
  52 + ...data,
  53 + communityId
  54 + }
  55 + }).then(response => {
  56 + const res = response.data
  57 + resolve(res)
  58 + }).catch(error => {
  59 + reject(error)
  60 + })
  61 + })
  62 +}
  63 +
  64 +// 删除题目
  65 +export function deleteQuestionTitle(data) {
  66 + return new Promise((resolve, reject) => {
  67 + const communityId = getCommunityId()
  68 + request({
  69 + url: '/question.deleteQuestionTitle',
  70 + method: 'post',
  71 + data: {
  72 + ...data,
  73 + communityId
  74 + }
  75 + }).then(response => {
  76 + const res = response.data
  77 + resolve(res)
  78 + }).catch(error => {
  79 + reject(error)
  80 + })
  81 + })
  82 +}
0 \ No newline at end of file 83 \ No newline at end of file
src/api/oa/visitManageApi.js 0 → 100644
  1 +import request from '@/utils/request'
  2 +
  3 +// 获取访客列表
  4 +export function listVisits(params) {
  5 + return new Promise((resolve, reject) => {
  6 + request({
  7 + url: '/iot.getOpenApi',
  8 + method: 'get',
  9 + params: {
  10 + ...params,
  11 + iotApiCode: 'listVisitBmoImpl'
  12 + }
  13 + }).then(response => {
  14 + const res = response.data
  15 + resolve({
  16 + data: res.data,
  17 + total: res.total
  18 + })
  19 + }).catch(error => {
  20 + reject(error)
  21 + })
  22 + })
  23 +}
  24 +
  25 +// 获取访客类型列表
  26 +export function listVisitTypes(params) {
  27 + return new Promise((resolve, reject) => {
  28 + request({
  29 + url: '/iot.getOpenApi',
  30 + method: 'get',
  31 + params: {
  32 + ...params,
  33 + iotApiCode: 'listVisitTypeBmoImpl'
  34 + }
  35 + }).then(response => {
  36 + const res = response.data
  37 + resolve({
  38 + data: res.data,
  39 + total: res.total
  40 + })
  41 + }).catch(error => {
  42 + reject(error)
  43 + })
  44 + })
  45 +}
0 \ No newline at end of file 46 \ No newline at end of file
src/components/oa/addNotepadDetail.vue 0 → 100644
  1 +<template>
  2 + <el-dialog
  3 + :title="$t('notepadManage.addDetail.title')"
  4 + :visible.sync="visible"
  5 + width="50%"
  6 + @close="handleClose"
  7 + >
  8 + <el-form
  9 + ref="form"
  10 + :model="formData"
  11 + :rules="rules"
  12 + label-width="120px"
  13 + >
  14 + <el-form-item
  15 + :label="$t('notepadManage.addDetail.state')"
  16 + prop="state"
  17 + >
  18 + <el-select
  19 + v-model="formData.state"
  20 + style="width:100%"
  21 + >
  22 + <el-option
  23 + :label="$t('notepadManage.addDetail.selectState')"
  24 + disabled
  25 + value=""
  26 + />
  27 + <el-option
  28 + :label="$t('notepadManage.state.following')"
  29 + value="W"
  30 + />
  31 + <el-option
  32 + :label="$t('notepadManage.state.completed')"
  33 + value="F"
  34 + />
  35 + </el-select>
  36 + </el-form-item>
  37 + <el-form-item
  38 + :label="$t('notepadManage.addDetail.content')"
  39 + prop="content"
  40 + >
  41 + <el-input
  42 + v-model="formData.content"
  43 + type="textarea"
  44 + rows="5"
  45 + :placeholder="$t('notepadManage.addDetail.contentPlaceholder')"
  46 + />
  47 + </el-form-item>
  48 + </el-form>
  49 + <span slot="footer" class="dialog-footer">
  50 + <el-button @click="visible = false">{{ $t('common.cancel') }}</el-button>
  51 + <el-button type="primary" @click="handleSubmit">{{ $t('common.save') }}</el-button>
  52 + </span>
  53 + </el-dialog>
  54 +</template>
  55 +
  56 +<script>
  57 +import { saveNotepadDetail } from '@/api/oa/notepadManageApi'
  58 +import { getCommunityId } from '@/api/community/communityApi'
  59 +
  60 +export default {
  61 + name: 'AddNotepadDetail',
  62 + props: {
  63 + callBackListener: {
  64 + type: String,
  65 + default: ''
  66 + },
  67 + callBackFunction: {
  68 + type: String,
  69 + default: ''
  70 + }
  71 + },
  72 + data() {
  73 + return {
  74 + visible: false,
  75 + formData: {
  76 + noteId: '',
  77 + content: '',
  78 + state: 'W',
  79 + communityId: ''
  80 + },
  81 + rules: {
  82 + state: [
  83 + { required: true, message: this.$t('notepadManage.validate.state'), trigger: 'blur' }
  84 + ],
  85 + content: [
  86 + { required: true, message: this.$t('notepadManage.validate.content'), trigger: 'blur' }
  87 + ]
  88 + }
  89 + }
  90 + },
  91 + methods: {
  92 + open(data) {
  93 + this.visible = true
  94 + this.formData.noteId = data.noteId
  95 + this.formData.communityId = getCommunityId()
  96 + },
  97 + async handleSubmit() {
  98 + this.$refs.form.validate(async valid => {
  99 + if (valid) {
  100 + try {
  101 + await saveNotepadDetail(this.formData)
  102 + this.$message.success(this.$t('common.saveSuccess'))
  103 + this.visible = false
  104 + this.$emit('success')
  105 + } catch (error) {
  106 + console.error('保存记事本详情失败:', error)
  107 + }
  108 + }
  109 + })
  110 + },
  111 + handleClose() {
  112 + this.$refs.form.resetFields()
  113 + this.formData = {
  114 + noteId: '',
  115 + content: '',
  116 + state: 'W',
  117 + communityId: ''
  118 + }
  119 + }
  120 + }
  121 +}
  122 +</script>
0 \ No newline at end of file 123 \ No newline at end of file
src/components/oa/addQuestionTitle.vue 0 → 100644
  1 +<template>
  2 + <el-dialog :title="$t('questionTitle.add.title')" :visible.sync="visible" width="70%" @close="handleClose">
  3 + <el-form ref="form" :model="formData" label-width="120px">
  4 + <el-form-item :label="$t('questionTitle.add.name')" prop="qaTitle" :rules="[
  5 + { required: true, message: $t('questionTitle.add.nameRequired'), trigger: 'blur' },
  6 + { max: 256, message: $t('questionTitle.add.nameTooLong'), trigger: 'blur' }
  7 + ]">
  8 + <el-input v-model="formData.qaTitle" :placeholder="$t('questionTitle.add.namePlaceholder')" />
  9 + </el-form-item>
  10 +
  11 + <el-form-item :label="$t('questionTitle.add.type')" prop="titleType" :rules="[
  12 + { required: true, message: $t('questionTitle.add.typeRequired'), trigger: 'change' }
  13 + ]">
  14 + <el-select v-model="formData.titleType" :placeholder="$t('questionTitle.add.typePlaceholder')" style="width:100%"
  15 + @change="handleTypeChange">
  16 + <el-option :label="$t('questionTitle.add.pleaseSelect')" value="" disabled />
  17 + <el-option :label="$t('questionTitle.types.singleChoice')" value="1001" />
  18 + <el-option :label="$t('questionTitle.types.multipleChoice')" value="2002" />
  19 + <el-option :label="$t('questionTitle.types.shortAnswer')" value="3003" />
  20 + </el-select>
  21 + </el-form-item>
  22 +
  23 + <template v-if="formData.titleType && formData.titleType !== '3003'">
  24 + <el-form-item v-for="(item, index) in formData.titleValues" :key="index"
  25 + :label="`${$t('questionTitle.add.option')}${item.seq}`" :prop="`titleValues.${index}.itemValue`" :rules="[
  26 + { required: true, message: $t('questionTitle.add.optionRequired'), trigger: 'blur' }
  27 + ]">
  28 + <el-row :gutter="20">
  29 + <el-col :span="18">
  30 + <el-input v-model="item.itemValue" :placeholder="$t('questionTitle.add.optionPlaceholder')" />
  31 + </el-col>
  32 + <el-col :span="5">
  33 + <el-button v-if="index === formData.titleValues.length - 1" type="text" @click="addOption">
  34 + {{ $t('questionTitle.add.addOption') }}
  35 + </el-button>
  36 + <el-button v-else type="text" @click="removeOption(index)">
  37 + {{ $t('questionTitle.add.removeOption') }}
  38 + </el-button>
  39 + </el-col>
  40 + </el-row>
  41 + </el-form-item>
  42 + </template>
  43 + </el-form>
  44 +
  45 + <div slot="footer" class="dialog-footer">
  46 + <el-button @click="visible = false">
  47 + {{ $t('common.cancel') }}
  48 + </el-button>
  49 + <el-button type="primary" @click="handleSubmit">
  50 + {{ $t('common.confirm') }}
  51 + </el-button>
  52 + </div>
  53 + </el-dialog>
  54 +</template>
  55 +
  56 +<script>
  57 +import { saveQuestionTitle } from '@/api/oa/questionTitleApi'
  58 +import { getCommunityId } from '@/api/community/communityApi'
  59 +
  60 +export default {
  61 + name: 'AddQuestionTitle',
  62 + data() {
  63 + return {
  64 + visible: false,
  65 + formData: {
  66 + titleType: '',
  67 + qaTitle: '',
  68 + titleValues: [],
  69 + communityId: ''
  70 + }
  71 + }
  72 + },
  73 + methods: {
  74 + open() {
  75 + this.resetForm()
  76 + this.visible = true
  77 + this.$nextTick(() => {
  78 + this.$refs.form.clearValidate()
  79 + })
  80 + },
  81 + handleClose() {
  82 + this.$refs.form.resetFields()
  83 + },
  84 + resetForm() {
  85 + this.formData = {
  86 + titleType: '',
  87 + qaTitle: '',
  88 + titleValues: [],
  89 + communityId: getCommunityId()
  90 + }
  91 + },
  92 + handleTypeChange(value) {
  93 + this.formData.titleValues = []
  94 + if (value === '1001') {
  95 + this.formData.titleValues = [{ itemValue: '', seq: 1 }]
  96 + } else if (value === '2002') {
  97 + this.formData.titleValues = [
  98 + { itemValue: '', seq: 1 },
  99 + { itemValue: '', seq: 2 }
  100 + ]
  101 + }
  102 + },
  103 + addOption() {
  104 + const newSeq = this.formData.titleValues.length + 1
  105 + this.formData.titleValues.push({ itemValue: '', seq: newSeq })
  106 + },
  107 + removeOption(index) {
  108 + this.formData.titleValues.splice(index, 1)
  109 + // 重新排序
  110 + this.formData.titleValues.forEach((item, i) => {
  111 + item.seq = i + 1
  112 + })
  113 + },
  114 + async handleSubmit() {
  115 + try {
  116 + const valid = await this.$refs.form.validate()
  117 + if (!valid) return
  118 +
  119 + // 如果是简答题,清空选项
  120 + if (this.formData.titleType === '3003') {
  121 + this.formData.titleValues = []
  122 + }
  123 +
  124 + await saveQuestionTitle(this.formData)
  125 + this.$message.success(this.$t('questionTitle.add.success'))
  126 + this.$emit('success')
  127 + this.visible = false
  128 + } catch (error) {
  129 + console.error(error)
  130 + }
  131 + }
  132 + }
  133 +}
  134 +</script>
0 \ No newline at end of file 135 \ No newline at end of file
src/components/oa/chooseQuestionTitle.vue 0 → 100644
  1 +<template>
  2 + <el-dialog :title="$t('chooseQuestionTitle.title')" :visible.sync="visible" width="70%" @close="handleClose">
  3 + <el-row :gutter="20">
  4 + <el-col :span="24">
  5 + <el-row :gutter="10">
  6 + <el-col :span="18"></el-col>
  7 + <el-col :span="6">
  8 + <el-input v-model="currentQuestionTitleName" :placeholder="$t('chooseQuestionTitle.searchPlaceholder')"
  9 + clearable @keyup.enter.native="queryQuestionTitles">
  10 + <el-button slot="append" icon="el-icon-search" @click="queryQuestionTitles" />
  11 + </el-input>
  12 + </el-col>
  13 + </el-row>
  14 +
  15 + <el-table :data="questionTitles" border style="width: 100%" class="margin-top">
  16 + <el-table-column prop="qaTitle" :label="$t('chooseQuestionTitle.table.title')" align="center" />
  17 + <el-table-column prop="createTime" :label="$t('chooseQuestionTitle.table.createTime')" align="center" />
  18 + <el-table-column :label="$t('chooseQuestionTitle.table.actions')" align="center">
  19 + <template slot-scope="scope">
  20 + <el-button type="primary" size="mini" @click="handleChoose(scope.row)">
  21 + {{ $t('chooseQuestionTitle.table.choose') }}
  22 + </el-button>
  23 + </template>
  24 + </el-table-column>
  25 + </el-table>
  26 +
  27 + <el-pagination :current-page="pagination.current" :page-sizes="[10, 20, 30, 50]" :page-size="pagination.size"
  28 + :total="pagination.total" layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange"
  29 + @current-change="handleCurrentChange" class="margin-top" />
  30 + </el-col>
  31 + </el-row>
  32 + </el-dialog>
  33 +</template>
  34 +
  35 +<script>
  36 +import { getCommunityId } from '@/api/community/communityApi'
  37 +import { listQuestionTitle } from '@/api/oa/editQuestionAnswerApi'
  38 +
  39 +export default {
  40 + name: 'ChooseQuestionTitle',
  41 +
  42 + data() {
  43 + return {
  44 + visible: false,
  45 + questionTitles: [],
  46 + currentQuestionTitleName: '',
  47 + pagination: {
  48 + current: 1,
  49 + size: 10,
  50 + total: 0
  51 + }
  52 + }
  53 + },
  54 + methods: {
  55 + open() {
  56 + this.visible = true
  57 + this.resetQuestionTitles()
  58 + this.loadAllQuestionTitleInfo()
  59 + },
  60 + async loadAllQuestionTitleInfo() {
  61 + try {
  62 + const params = {
  63 + page: this.pagination.current,
  64 + row: this.pagination.size,
  65 + communityId: getCommunityId(),
  66 + qaTitle: this.currentQuestionTitleName
  67 + }
  68 + const { data, total } = await listQuestionTitle(params)
  69 + this.questionTitles = data
  70 + this.pagination.total = total
  71 + } catch (error) {
  72 + console.error('Failed to load question titles:', error)
  73 + }
  74 + },
  75 + handleChoose(questionTitle) {
  76 + this.$emit('chooseQuestionTitle', questionTitle)
  77 + this.visible = false
  78 + },
  79 + queryQuestionTitles() {
  80 + this.pagination.current = 1
  81 + this.loadAllQuestionTitleInfo()
  82 + },
  83 + resetQuestionTitles() {
  84 + this.currentQuestionTitleName = ''
  85 + this.pagination.current = 1
  86 + },
  87 + handleSizeChange(size) {
  88 + this.pagination.size = size
  89 + this.loadAllQuestionTitleInfo()
  90 + },
  91 + handleCurrentChange(current) {
  92 + this.pagination.current = current
  93 + this.loadAllQuestionTitleInfo()
  94 + },
  95 + handleClose() {
  96 + this.resetQuestionTitles()
  97 + }
  98 + }
  99 +}
  100 +</script>
  101 +
  102 +<style lang="scss" scoped>
  103 +.margin-top {
  104 + margin-top: 15px;
  105 +}
  106 +</style>
0 \ No newline at end of file 107 \ No newline at end of file
src/components/oa/deleteNotepad.vue 0 → 100644
  1 +<template>
  2 + <el-dialog
  3 + :title="$t('notepadManage.delete.title')"
  4 + :visible.sync="visible"
  5 + width="30%"
  6 + >
  7 + <div class="text-center">
  8 + <p>{{ $t('notepadManage.delete.confirm') }}</p>
  9 + </div>
  10 + <span slot="footer" class="dialog-footer">
  11 + <el-button @click="visible = false">{{ $t('common.cancel') }}</el-button>
  12 + <el-button type="primary" @click="handleConfirm">{{ $t('common.confirm') }}</el-button>
  13 + </span>
  14 + </el-dialog>
  15 +</template>
  16 +
  17 +<script>
  18 +import { deleteNotepad } from '@/api/oa/notepadManageApi'
  19 +import { getCommunityId } from '@/api/community/communityApi'
  20 +
  21 +export default {
  22 + name: 'DeleteNotepad',
  23 + data() {
  24 + return {
  25 + visible: false,
  26 + formData: {
  27 + noteId: '',
  28 + communityId: ''
  29 + }
  30 + }
  31 + },
  32 + methods: {
  33 + open(data) {
  34 + this.visible = true
  35 + this.formData.noteId = data.noteId
  36 + this.formData.communityId = getCommunityId()
  37 + },
  38 + async handleConfirm() {
  39 + try {
  40 + await deleteNotepad(this.formData)
  41 + this.$message.success(this.$t('common.deleteSuccess'))
  42 + this.visible = false
  43 + this.$emit('success')
  44 + } catch (error) {
  45 + console.error('删除记事本失败:', error)
  46 + }
  47 + }
  48 + }
  49 +}
  50 +</script>
  51 +
  52 +<style scoped>
  53 +.text-center {
  54 + text-align: center;
  55 +}
  56 +</style>
0 \ No newline at end of file 57 \ No newline at end of file
src/components/oa/deleteQuestionAnswer.vue 0 → 100644
  1 +<template>
  2 + <el-dialog
  3 + :title="$t('questionAnswerManage.delete.title')"
  4 + :visible.sync="visible"
  5 + width="30%"
  6 + @close="handleClose"
  7 + >
  8 + <div class="delete-content">
  9 + <i class="el-icon-warning" style="color: #e6a23c; font-size: 24px;"></i>
  10 + <span style="margin-left: 10px;">{{ $t('questionAnswerManage.delete.confirmText') }}</span>
  11 + </div>
  12 +
  13 + <div slot="footer" class="dialog-footer">
  14 + <el-button @click="visible = false">
  15 + {{ $t('common.cancel') }}
  16 + </el-button>
  17 + <el-button type="primary" @click="handleConfirm" :loading="loading">
  18 + {{ $t('common.confirm') }}
  19 + </el-button>
  20 + </div>
  21 + </el-dialog>
  22 +</template>
  23 +
  24 +<script>
  25 +import { deleteQuestionAnswer } from '@/api/oa/questionAnswerManageApi'
  26 +import { getCommunityId } from '@/api/community/communityApi'
  27 +
  28 +export default {
  29 + name: 'DeleteQuestionAnswer',
  30 + data() {
  31 + return {
  32 + visible: false,
  33 + loading: false,
  34 + currentData: null
  35 + }
  36 + },
  37 + methods: {
  38 + open(data) {
  39 + this.currentData = data
  40 + this.visible = true
  41 + },
  42 + handleClose() {
  43 + this.currentData = null
  44 + this.loading = false
  45 + },
  46 + async handleConfirm() {
  47 + if (!this.currentData) return
  48 +
  49 + try {
  50 + this.loading = true
  51 + const params = {
  52 + qaId: this.currentData.qaId,
  53 + communityId: getCommunityId()
  54 + }
  55 + await deleteQuestionAnswer(params)
  56 + this.$message.success(this.$t('questionAnswerManage.message.deleteSuccess'))
  57 + this.visible = false
  58 + this.$emit('success')
  59 + } catch (error) {
  60 + this.$message.error(error.message || this.$t('questionAnswerManage.message.deleteError'))
  61 + } finally {
  62 + this.loading = false
  63 + }
  64 + }
  65 + }
  66 +}
  67 +</script>
  68 +
  69 +<style lang="scss" scoped>
  70 +.delete-content {
  71 + display: flex;
  72 + align-items: center;
  73 + justify-content: center;
  74 + padding: 20px 0;
  75 + font-size: 16px;
  76 +}
  77 +</style>
0 \ No newline at end of file 78 \ No newline at end of file
src/components/oa/deleteQuestionTitle.vue 0 → 100644
  1 +<template>
  2 + <el-dialog
  3 + :title="$t('questionTitle.delete.title')"
  4 + :visible.sync="visible"
  5 + width="500px"
  6 + @close="handleClose"
  7 + >
  8 + <div class="delete-content">
  9 + <el-alert
  10 + :title="$t('questionTitle.delete.confirmMessage')"
  11 + type="warning"
  12 + :closable="false"
  13 + show-icon
  14 + />
  15 + </div>
  16 +
  17 + <div slot="footer" class="dialog-footer">
  18 + <el-button @click="visible = false">
  19 + {{ $t('common.cancel') }}
  20 + </el-button>
  21 + <el-button type="primary" @click="handleConfirm" :loading="loading">
  22 + {{ $t('common.confirm') }}
  23 + </el-button>
  24 + </div>
  25 + </el-dialog>
  26 +</template>
  27 +
  28 +<script>
  29 +import { deleteQuestionTitle } from '@/api/oa/questionTitleApi'
  30 +import { getCommunityId } from '@/api/community/communityApi'
  31 +
  32 +export default {
  33 + name: 'DeleteQuestionTitle',
  34 + data() {
  35 + return {
  36 + visible: false,
  37 + loading: false,
  38 + formData: {
  39 + titleId: '',
  40 + communityId: ''
  41 + }
  42 + }
  43 + },
  44 + methods: {
  45 + open(data) {
  46 + this.formData = {
  47 + titleId: data.titleId,
  48 + communityId: getCommunityId()
  49 + }
  50 + this.visible = true
  51 + },
  52 + handleClose() {
  53 + this.formData = {
  54 + titleId: '',
  55 + communityId: ''
  56 + }
  57 + this.loading = false
  58 + },
  59 + async handleConfirm() {
  60 + try {
  61 + this.loading = true
  62 + await deleteQuestionTitle(this.formData)
  63 + this.$message.success(this.$t('questionTitle.delete.success'))
  64 + this.$emit('success')
  65 + this.visible = false
  66 + } catch (error) {
  67 + console.error(error)
  68 + } finally {
  69 + this.loading = false
  70 + }
  71 + }
  72 + }
  73 +}
  74 +</script>
  75 +
  76 +<style scoped>
  77 +.delete-content {
  78 + padding: 20px 0;
  79 + text-align: center;
  80 +}
  81 +</style>
0 \ No newline at end of file 82 \ No newline at end of file
src/components/oa/editNotepad.vue 0 → 100644
  1 +<template>
  2 + <el-dialog
  3 + :title="$t('notepadManage.edit.title')"
  4 + :visible.sync="visible"
  5 + width="50%"
  6 + @close="handleClose"
  7 + >
  8 + <el-form
  9 + ref="form"
  10 + :model="formData"
  11 + :rules="rules"
  12 + label-width="120px"
  13 + >
  14 + <el-form-item
  15 + :label="$t('notepadManage.edit.roomName')"
  16 + prop="roomName"
  17 + >
  18 + <el-input
  19 + v-model="formData.roomName"
  20 + disabled
  21 + />
  22 + </el-form-item>
  23 + <el-form-item
  24 + :label="$t('notepadManage.edit.objName')"
  25 + prop="objName"
  26 + >
  27 + <el-input
  28 + v-model="formData.objName"
  29 + disabled
  30 + />
  31 + </el-form-item>
  32 + <el-form-item
  33 + :label="$t('notepadManage.edit.link')"
  34 + prop="link"
  35 + >
  36 + <el-input
  37 + v-model="formData.link"
  38 + disabled
  39 + />
  40 + </el-form-item>
  41 + <el-form-item
  42 + :label="$t('notepadManage.edit.noteType')"
  43 + prop="noteType"
  44 + >
  45 + <el-select
  46 + v-model="formData.noteType"
  47 + style="width:100%"
  48 + >
  49 + <el-option
  50 + :label="$t('notepadManage.edit.selectNoteType')"
  51 + disabled
  52 + value=""
  53 + />
  54 + <el-option
  55 + v-for="(item,index) in noteTypes"
  56 + :key="index"
  57 + :label="item.name"
  58 + :value="item.statusCd"
  59 + />
  60 + </el-select>
  61 + </el-form-item>
  62 + <el-form-item
  63 + :label="$t('notepadManage.edit.title')"
  64 + prop="title"
  65 + >
  66 + <el-input
  67 + v-model="formData.title"
  68 + type="textarea"
  69 + rows="5"
  70 + />
  71 + </el-form-item>
  72 + </el-form>
  73 + <span slot="footer" class="dialog-footer">
  74 + <el-button @click="visible = false">{{ $t('common.cancel') }}</el-button>
  75 + <el-button type="primary" @click="handleSubmit">{{ $t('common.save') }}</el-button>
  76 + </span>
  77 + </el-dialog>
  78 +</template>
  79 +
  80 +<script>
  81 +import { updateNotepad } from '@/api/oa/notepadManageApi'
  82 +import { getDict } from '@/api/community/communityApi'
  83 +import { getCommunityId } from '@/api/community/communityApi'
  84 +
  85 +export default {
  86 + name: 'EditNotepad',
  87 + props: {
  88 + callBackListener: {
  89 + type: String,
  90 + default: ''
  91 + },
  92 + callBackFunction: {
  93 + type: String,
  94 + default: ''
  95 + }
  96 + },
  97 + data() {
  98 + return {
  99 + visible: false,
  100 + formData: {
  101 + noteId: '',
  102 + noteType: '',
  103 + title: '',
  104 + roomName: '',
  105 + roomId: '',
  106 + objId: '',
  107 + objName: '',
  108 + objType: '',
  109 + link: '',
  110 + communityId: ''
  111 + },
  112 + noteTypes: [],
  113 + rules: {
  114 + noteType: [
  115 + { required: true, message: this.$t('notepadManage.validate.noteType'), trigger: 'blur' }
  116 + ],
  117 + title: [
  118 + { required: true, message: this.$t('notepadManage.validate.title'), trigger: 'blur' },
  119 + { max: 256, message: this.$t('notepadManage.validate.titleMaxLength'), trigger: 'blur' }
  120 + ]
  121 + }
  122 + }
  123 + },
  124 + methods: {
  125 + open(data) {
  126 + this.visible = true
  127 + this.formData = { ...data }
  128 + this.formData.communityId = getCommunityId()
  129 + this.getNoteTypes()
  130 + },
  131 + async getNoteTypes() {
  132 + try {
  133 + const data = await getDict('notepad', 'note_type')
  134 + this.noteTypes = data
  135 + } catch (error) {
  136 + console.error('获取字典数据失败:', error)
  137 + }
  138 + },
  139 + handleSubmit() {
  140 + this.$refs.form.validate(async valid => {
  141 + if (valid) {
  142 + try {
  143 + await updateNotepad(this.formData)
  144 + this.$message.success(this.$t('common.updateSuccess'))
  145 + this.visible = false
  146 + this.$emit('success')
  147 + } catch (error) {
  148 + console.error('更新记事本失败:', error)
  149 + }
  150 + }
  151 + })
  152 + },
  153 + handleClose() {
  154 + this.$refs.form.resetFields()
  155 + }
  156 + }
  157 +}
  158 +</script>
0 \ No newline at end of file 159 \ No newline at end of file
src/components/oa/editQuestionAnswer.vue 0 → 100644
  1 +<template>
  2 + <el-dialog
  3 + :title="$t('questionAnswerManage.edit.title')"
  4 + :visible.sync="visible"
  5 + width="50%"
  6 + @close="handleClose"
  7 + >
  8 + <el-form
  9 + ref="form"
  10 + :model="formData"
  11 + :rules="rules"
  12 + label-width="120px"
  13 + label-position="right"
  14 + >
  15 + <el-form-item
  16 + :label="$t('questionAnswerManage.edit.qaType')"
  17 + prop="qaType"
  18 + >
  19 + <el-select
  20 + v-model="formData.qaType"
  21 + :placeholder="$t('questionAnswerManage.edit.qaTypePlaceholder')"
  22 + style="width: 100%"
  23 + >
  24 + <el-option
  25 + value="1001"
  26 + :label="$t('questionAnswerManage.edit.qaType1')"
  27 + />
  28 + <el-option
  29 + value="3003"
  30 + :label="$t('questionAnswerManage.edit.qaType3')"
  31 + />
  32 + <el-option
  33 + value="4004"
  34 + :label="$t('questionAnswerManage.edit.qaType4')"
  35 + />
  36 + </el-select>
  37 + </el-form-item>
  38 +
  39 + <el-form-item
  40 + :label="$t('questionAnswerManage.edit.qaName')"
  41 + prop="qaName"
  42 + >
  43 + <el-input
  44 + v-model="formData.qaName"
  45 + :placeholder="$t('questionAnswerManage.edit.qaNamePlaceholder')"
  46 + />
  47 + </el-form-item>
  48 +
  49 + <el-form-item
  50 + :label="$t('questionAnswerManage.edit.startTime')"
  51 + prop="startTime"
  52 + >
  53 + <el-date-picker
  54 + v-model="formData.startTime"
  55 + type="datetime"
  56 + :placeholder="$t('questionAnswerManage.edit.startTimePlaceholder')"
  57 + style="width: 100%"
  58 + value-format="yyyy-MM-dd HH:mm:ss"
  59 + />
  60 + </el-form-item>
  61 +
  62 + <el-form-item
  63 + :label="$t('questionAnswerManage.edit.endTime')"
  64 + prop="endTime"
  65 + >
  66 + <el-date-picker
  67 + v-model="formData.endTime"
  68 + type="datetime"
  69 + :placeholder="$t('questionAnswerManage.edit.endTimePlaceholder')"
  70 + style="width: 100%"
  71 + value-format="yyyy-MM-dd HH:mm:ss"
  72 + />
  73 + </el-form-item>
  74 +
  75 + <el-form-item :label="$t('questionAnswerManage.edit.content')">
  76 + <el-input
  77 + v-model="formData.content"
  78 + type="textarea"
  79 + :placeholder="$t('questionAnswerManage.edit.contentPlaceholder')"
  80 + :rows="3"
  81 + />
  82 + </el-form-item>
  83 +
  84 + <el-form-item :label="$t('questionAnswerManage.edit.remark')">
  85 + <el-input
  86 + v-model="formData.remark"
  87 + type="textarea"
  88 + :placeholder="$t('questionAnswerManage.edit.remarkPlaceholder')"
  89 + :rows="3"
  90 + />
  91 + </el-form-item>
  92 +
  93 + <el-form-item :label="$t('questionAnswerManage.edit.photos')">
  94 + <upload-image-url
  95 + ref="uploadImage"
  96 + :image-count="99"
  97 + @change="handleImageChange"
  98 + />
  99 + </el-form-item>
  100 + </el-form>
  101 +
  102 + <div slot="footer" class="dialog-footer">
  103 + <el-button @click="visible = false">
  104 + {{ $t('common.cancel') }}
  105 + </el-button>
  106 + <el-button type="primary" @click="handleSubmit">
  107 + {{ $t('common.confirm') }}
  108 + </el-button>
  109 + </div>
  110 + </el-dialog>
  111 +</template>
  112 +
  113 +<script>
  114 +import { updateQuestionAnswer } from '@/api/oa/questionAnswerManageApi'
  115 +import UploadImageUrl from '@/components/oa/UploadImageUrl'
  116 +import { getCommunityId } from '@/api/community/communityApi'
  117 +
  118 +export default {
  119 + name: 'EditQuestionAnswer',
  120 + components: {
  121 + UploadImageUrl
  122 + },
  123 + data() {
  124 + return {
  125 + visible: false,
  126 + formData: {
  127 + qaId: '',
  128 + qaType: '',
  129 + qaName: '',
  130 + startTime: '',
  131 + endTime: '',
  132 + content: '',
  133 + remark: '',
  134 + photos: []
  135 + },
  136 + rules: {
  137 + qaType: [
  138 + { required: true, message: this.$t('questionAnswerManage.validate.qaType'), trigger: 'blur' }
  139 + ],
  140 + qaName: [
  141 + { required: true, message: this.$t('questionAnswerManage.validate.qaName'), trigger: 'blur' },
  142 + { max: 256, message: this.$t('questionAnswerManage.validate.qaNameMax'), trigger: 'blur' }
  143 + ],
  144 + startTime: [
  145 + { required: true, message: this.$t('questionAnswerManage.validate.startTime'), trigger: 'blur' }
  146 + ],
  147 + endTime: [
  148 + { required: true, message: this.$t('questionAnswerManage.validate.endTime'), trigger: 'blur' }
  149 + ]
  150 + }
  151 + }
  152 + },
  153 + methods: {
  154 + open(data) {
  155 + this.visible = true
  156 + this.$nextTick(() => {
  157 + this.resetForm()
  158 + if (data) {
  159 + Object.assign(this.formData, data)
  160 + if (data.fileUrls) {
  161 + this.$refs.uploadImage.setPhotos(data.fileUrls)
  162 + }
  163 + }
  164 + })
  165 + },
  166 + resetForm() {
  167 + this.$refs.form && this.$refs.form.resetFields()
  168 + this.$refs.uploadImage && this.$refs.uploadImage.clear()
  169 + this.formData = {
  170 + qaId: '',
  171 + qaType: '',
  172 + qaName: '',
  173 + startTime: '',
  174 + endTime: '',
  175 + content: '',
  176 + remark: '',
  177 + photos: []
  178 + }
  179 + },
  180 + handleClose() {
  181 + this.resetForm()
  182 + },
  183 + handleImageChange(photos) {
  184 + this.formData.photos = photos.map(item => item.fileId)
  185 + },
  186 + async handleSubmit() {
  187 + this.$refs.form.validate(async valid => {
  188 + if (!valid) return
  189 +
  190 + try {
  191 + this.formData.communityId = getCommunityId()
  192 + await updateQuestionAnswer(this.formData)
  193 + this.$message.success(this.$t('questionAnswerManage.message.updateSuccess'))
  194 + this.visible = false
  195 + this.$emit('success')
  196 + } catch (error) {
  197 + this.$message.error(error.message || this.$t('questionAnswerManage.message.updateError'))
  198 + }
  199 + })
  200 + }
  201 + }
  202 +}
  203 +</script>
0 \ No newline at end of file 204 \ No newline at end of file
src/components/oa/editQuestionTitle.vue 0 → 100644
  1 +<template>
  2 + <el-dialog :title="$t('questionTitle.edit.title')" :visible.sync="visible" width="70%" @close="handleClose">
  3 + <el-form ref="form" :model="formData" label-width="120px">
  4 + <el-form-item :label="$t('questionTitle.edit.type')" prop="titleType" :rules="[
  5 + { required: true, message: $t('questionTitle.edit.typeRequired'), trigger: 'change' }
  6 + ]">
  7 + <el-select v-model="formData.titleType" :placeholder="$t('questionTitle.edit.typePlaceholder')" style="width:100%"
  8 + @change="handleTypeChange">
  9 + <el-option :label="$t('questionTitle.edit.pleaseSelect')" value="" disabled />
  10 + <el-option :label="$t('questionTitle.types.singleChoice')" value="1001" />
  11 + <el-option :label="$t('questionTitle.types.multipleChoice')" value="2002" />
  12 + <el-option :label="$t('questionTitle.types.shortAnswer')" value="3003" />
  13 + </el-select>
  14 + </el-form-item>
  15 +
  16 + <el-form-item :label="$t('questionTitle.edit.name')" prop="qaTitle" :rules="[
  17 + { required: true, message: $t('questionTitle.edit.nameRequired'), trigger: 'blur' },
  18 + { max: 256, message: $t('questionTitle.edit.nameTooLong'), trigger: 'blur' }
  19 + ]">
  20 + <el-input v-model="formData.qaTitle" :placeholder="$t('questionTitle.edit.namePlaceholder')" />
  21 + </el-form-item>
  22 +
  23 + <template v-if="formData.titleType && formData.titleType !== '3003'">
  24 + <el-form-item v-for="(item, index) in formData.titleValues" :key="index"
  25 + :label="`${$t('questionTitle.edit.option')}${item.seq}`" :prop="`titleValues.${index}.qaValue`" :rules="[
  26 + { required: true, message: $t('questionTitle.edit.optionRequired'), trigger: 'blur' }
  27 + ]">
  28 + <el-row :gutter="20">
  29 + <el-col :span="18">
  30 + <el-input v-model="item.qaValue" :placeholder="$t('questionTitle.edit.optionPlaceholder')" />
  31 + </el-col>
  32 + <el-col :span="5">
  33 + <el-button v-if="index === formData.titleValues.length - 1" type="text" @click="addOption">
  34 + {{ $t('questionTitle.edit.addOption') }}
  35 + </el-button>
  36 + <el-button v-else type="text" @click="removeOption(index)">
  37 + {{ $t('questionTitle.edit.removeOption') }}
  38 + </el-button>
  39 + </el-col>
  40 + </el-row>
  41 + </el-form-item>
  42 + </template>
  43 + </el-form>
  44 +
  45 + <div slot="footer" class="dialog-footer">
  46 + <el-button @click="visible = false">
  47 + {{ $t('common.cancel') }}
  48 + </el-button>
  49 + <el-button type="primary" @click="handleSubmit">
  50 + {{ $t('common.confirm') }}
  51 + </el-button>
  52 + </div>
  53 + </el-dialog>
  54 +</template>
  55 +
  56 +<script>
  57 +import { updateQuestionTitle } from '@/api/oa/questionTitleApi'
  58 +import { getCommunityId } from '@/api/community/communityApi'
  59 +
  60 +export default {
  61 + name: 'EditQuestionTitle',
  62 + data() {
  63 + return {
  64 + visible: false,
  65 + formData: {
  66 + titleId: '',
  67 + titleType: '',
  68 + qaTitle: '',
  69 + titleValues: [],
  70 + communityId: ''
  71 + }
  72 + }
  73 + },
  74 + methods: {
  75 + open(data) {
  76 + this.resetForm()
  77 + this.formData = {
  78 + ...data,
  79 + communityId: getCommunityId()
  80 + }
  81 + this.visible = true
  82 + this.$nextTick(() => {
  83 + this.$refs.form.clearValidate()
  84 + })
  85 + },
  86 + handleClose() {
  87 + this.$refs.form.resetFields()
  88 + },
  89 + resetForm() {
  90 + this.formData = {
  91 + titleId: '',
  92 + titleType: '',
  93 + qaTitle: '',
  94 + titleValues: [],
  95 + communityId: getCommunityId()
  96 + }
  97 + },
  98 + handleTypeChange(value) {
  99 + if (value === '3003') {
  100 + this.formData.titleValues = []
  101 + } else if (value === '1001' && this.formData.titleValues.length === 0) {
  102 + this.formData.titleValues = [{ qaValue: '', seq: 1 }]
  103 + } else if (value === '2002' && this.formData.titleValues.length < 2) {
  104 + this.formData.titleValues = [
  105 + { qaValue: '', seq: 1 },
  106 + { qaValue: '', seq: 2 }
  107 + ]
  108 + }
  109 + },
  110 + addOption() {
  111 + const newSeq = this.formData.titleValues.length + 1
  112 + this.formData.titleValues.push({ qaValue: '', seq: newSeq })
  113 + },
  114 + removeOption(index) {
  115 + this.formData.titleValues.splice(index, 1)
  116 + // 重新排序
  117 + this.formData.titleValues.forEach((item, i) => {
  118 + item.seq = i + 1
  119 + })
  120 + },
  121 + async handleSubmit() {
  122 + try {
  123 + const valid = await this.$refs.form.validate()
  124 + if (!valid) return
  125 +
  126 + // 如果是简答题,清空选项
  127 + if (this.formData.titleType === '3003') {
  128 + this.formData.titleValues = []
  129 + }
  130 +
  131 + await updateQuestionTitle(this.formData)
  132 + this.$message.success(this.$t('questionTitle.edit.success'))
  133 + this.$emit('success')
  134 + this.visible = false
  135 + } catch (error) {
  136 + console.error(error)
  137 + }
  138 + }
  139 + }
  140 +}
  141 +</script>
0 \ No newline at end of file 142 \ No newline at end of file
src/components/oa/notepadDetail.vue 0 → 100644
  1 +<template>
  2 + <el-dialog
  3 + :title="$t('notepadManage.detail.title')"
  4 + :visible.sync="visible"
  5 + width="70%"
  6 + >
  7 + <el-table
  8 + :data="tableData"
  9 + border
  10 + style="width: 100%"
  11 + >
  12 + <el-table-column
  13 + type="index"
  14 + :label="$t('notepadManage.detail.index')"
  15 + width="80"
  16 + align="center"
  17 + />
  18 + <el-table-column
  19 + prop="createUserName"
  20 + :label="$t('notepadManage.detail.createUser')"
  21 + align="center"
  22 + />
  23 + <el-table-column
  24 + prop="createTime"
  25 + :label="$t('notepadManage.detail.createTime')"
  26 + align="center"
  27 + />
  28 + <el-table-column
  29 + prop="content"
  30 + :label="$t('notepadManage.detail.content')"
  31 + align="center"
  32 + />
  33 + <el-table-column
  34 + :label="$t('common.operation')"
  35 + align="center"
  36 + width="120"
  37 + >
  38 + <template slot-scope="scope">
  39 + <el-button
  40 + size="mini"
  41 + type="danger"
  42 + @click="handleDelete(scope.row)"
  43 + >
  44 + {{ $t('common.delete') }}
  45 + </el-button>
  46 + </template>
  47 + </el-table-column>
  48 + </el-table>
  49 + </el-dialog>
  50 +</template>
  51 +
  52 +<script>
  53 +import { listNotepadDetail, deleteNotepadDetail } from '@/api/oa/notepadManageApi'
  54 +import { getCommunityId } from '@/api/community/communityApi'
  55 +
  56 +export default {
  57 + name: 'NotepadDetail',
  58 + data() {
  59 + return {
  60 + visible: false,
  61 + tableData: [],
  62 + formData: {
  63 + noteId: '',
  64 + communityId: ''
  65 + }
  66 + }
  67 + },
  68 + methods: {
  69 + open(data) {
  70 + this.visible = true
  71 + this.formData.noteId = data.noteId
  72 + this.formData.communityId = getCommunityId()
  73 + this.loadDetails()
  74 + },
  75 + async loadDetails() {
  76 + try {
  77 + const params = {
  78 + noteId: this.formData.noteId,
  79 + communityId: this.formData.communityId,
  80 + page: 1,
  81 + row: 50
  82 + }
  83 + const { data } = await listNotepadDetail(params)
  84 + this.tableData = data
  85 + } catch (error) {
  86 + console.error('获取记事本详情失败:', error)
  87 + }
  88 + },
  89 + async handleDelete(row) {
  90 + try {
  91 + await deleteNotepadDetail({
  92 + detailId: row.detailId,
  93 + communityId: this.formData.communityId
  94 + })
  95 + this.$message.success(this.$t('common.deleteSuccess'))
  96 + this.loadDetails()
  97 + } catch (error) {
  98 + console.error('删除记事本详情失败:', error)
  99 + }
  100 + }
  101 + }
  102 +}
  103 +</script>
0 \ No newline at end of file 104 \ No newline at end of file
src/components/oa/notepadOwnerRepair.vue 0 → 100644
  1 +<template>
  2 + <el-dialog
  3 + :title="$t('notepadManage.ownerRepair.title')"
  4 + :visible.sync="visible"
  5 + width="50%"
  6 + @close="handleClose"
  7 + >
  8 + <el-form
  9 + ref="form"
  10 + :model="formData"
  11 + :rules="rules"
  12 + label-width="120px"
  13 + >
  14 + <el-form-item
  15 + :label="$t('notepadManage.ownerRepair.repairType')"
  16 + prop="repairType"
  17 + >
  18 + <el-select
  19 + v-model="formData.repairType"
  20 + style="width:100%"
  21 + >
  22 + <el-option
  23 + :label="$t('notepadManage.ownerRepair.selectRepairType')"
  24 + disabled
  25 + value=""
  26 + />
  27 + <el-option
  28 + v-for="(item,index) in repairSettings"
  29 + :key="index"
  30 + :label="item.repairTypeName"
  31 + :value="item.repairType"
  32 + />
  33 + </el-select>
  34 + </el-form-item>
  35 + <el-form-item
  36 + :label="$t('notepadManage.ownerRepair.repairObjName')"
  37 + prop="repairObjName"
  38 + >
  39 + <el-input
  40 + v-model="formData.repairObjName"
  41 + :placeholder="$t('notepadManage.ownerRepair.repairObjNamePlaceholder')"
  42 + />
  43 + </el-form-item>
  44 + <el-form-item
  45 + :label="$t('notepadManage.ownerRepair.repairName')"
  46 + prop="repairName"
  47 + >
  48 + <el-input
  49 + v-model="formData.repairName"
  50 + :placeholder="$t('notepadManage.ownerRepair.repairNamePlaceholder')"
  51 + />
  52 + </el-form-item>
  53 + <el-form-item
  54 + :label="$t('notepadManage.ownerRepair.tel')"
  55 + prop="tel"
  56 + >
  57 + <el-input
  58 + v-model="formData.tel"
  59 + :placeholder="$t('notepadManage.ownerRepair.telPlaceholder')"
  60 + />
  61 + </el-form-item>
  62 + <el-form-item
  63 + :label="$t('notepadManage.ownerRepair.appointmentTime')"
  64 + prop="appointmentTime"
  65 + >
  66 + <el-date-picker
  67 + v-model="formData.appointmentTime"
  68 + type="datetime"
  69 + style="width:100%"
  70 + :placeholder="$t('notepadManage.ownerRepair.selectTime')"
  71 + value-format="yyyy-MM-dd HH:mm:ss"
  72 + />
  73 + </el-form-item>
  74 + <el-form-item
  75 + :label="$t('notepadManage.ownerRepair.context')"
  76 + prop="context"
  77 + >
  78 + <el-input
  79 + v-model="formData.context"
  80 + type="textarea"
  81 + rows="5"
  82 + :placeholder="$t('notepadManage.ownerRepair.contextPlaceholder')"
  83 + />
  84 + </el-form-item>
  85 + </el-form>
  86 + <span slot="footer" class="dialog-footer">
  87 + <el-button @click="visible = false">{{ $t('common.cancel') }}</el-button>
  88 + <el-button type="primary" @click="handleSubmit">{{ $t('common.submit') }}</el-button>
  89 + </span>
  90 + </el-dialog>
  91 +</template>
  92 +
  93 +<script>
  94 +import { saveOwnerRepair, listRepairSettings } from '@/api/oa/notepadManageApi'
  95 +import { getCommunityId } from '@/api/community/communityApi'
  96 +
  97 +export default {
  98 + name: 'NotepadOwnerRepair',
  99 + data() {
  100 + return {
  101 + visible: false,
  102 + formData: {
  103 + repairType: '',
  104 + repairName: '',
  105 + tel: '',
  106 + appointmentTime: '',
  107 + context: '',
  108 + repairObjType: '004',
  109 + repairObjId: '',
  110 + repairObjName: '',
  111 + repairChannel: 'T',
  112 + noteId: '',
  113 + communityId: ''
  114 + },
  115 + repairSettings: [],
  116 + rules: {
  117 + repairType: [
  118 + { required: true, message: this.$t('notepadManage.validate.repairType'), trigger: 'blur' }
  119 + ],
  120 + repairObjName: [
  121 + { required: true, message: this.$t('notepadManage.validate.repairObjName'), trigger: 'blur' }
  122 + ],
  123 + repairName: [
  124 + { required: true, message: this.$t('notepadManage.validate.repairName'), trigger: 'blur' },
  125 + { min: 2, max: 10, message: this.$t('notepadManage.validate.repairNameLength'), trigger: 'blur' }
  126 + ],
  127 + tel: [
  128 + { required: true, message: this.$t('notepadManage.validate.tel'), trigger: 'blur' },
  129 + { pattern: /^1[3-9]\d{9}$/, message: this.$t('notepadManage.validate.telFormat'), trigger: 'blur' }
  130 + ],
  131 + appointmentTime: [
  132 + { required: true, message: this.$t('notepadManage.validate.appointmentTime'), trigger: 'blur' }
  133 + ],
  134 + context: [
  135 + { required: true, message: this.$t('notepadManage.validate.context'), trigger: 'blur' },
  136 + { max: 2000, message: this.$t('notepadManage.validate.contextMaxLength'), trigger: 'blur' }
  137 + ]
  138 + }
  139 + }
  140 + },
  141 + methods: {
  142 + open(data) {
  143 + this.visible = true
  144 + this.formData = {
  145 + ...this.formData,
  146 + repairObjName: data.roomName,
  147 + repairObjId: data.roomId,
  148 + tel: data.link,
  149 + repairName: data.objName,
  150 + context: data.title,
  151 + noteId: data.noteId,
  152 + communityId: getCommunityId()
  153 + }
  154 + this.loadRepairSettings()
  155 + },
  156 + async loadRepairSettings() {
  157 + try {
  158 + const params = {
  159 + page: 1,
  160 + row: 50,
  161 + communityId: this.formData.communityId,
  162 + publicArea: 'F'
  163 + }
  164 + const { data } = await listRepairSettings(params)
  165 + this.repairSettings = data
  166 + } catch (error) {
  167 + console.error('获取报修设置失败:', error)
  168 + }
  169 + },
  170 + async handleSubmit() {
  171 + this.$refs.form.validate(async valid => {
  172 + if (valid) {
  173 + try {
  174 + await saveOwnerRepair(this.formData)
  175 + this.$message.success(this.$t('common.submitSuccess'))
  176 + this.visible = false
  177 + this.$emit('success')
  178 + } catch (error) {
  179 + console.error('提交报修单失败:', error)
  180 + }
  181 + }
  182 + })
  183 + },
  184 + handleClose() {
  185 + this.$refs.form.resetFields()
  186 + this.formData = {
  187 + repairType: '',
  188 + repairName: '',
  189 + tel: '',
  190 + appointmentTime: '',
  191 + context: '',
  192 + repairObjType: '004',
  193 + repairObjId: '',
  194 + repairObjName: '',
  195 + repairChannel: 'T',
  196 + noteId: '',
  197 + communityId: ''
  198 + }
  199 + }
  200 + }
  201 +}
  202 +</script>
0 \ No newline at end of file 203 \ No newline at end of file
src/components/oa/publishQuestionAnswer.vue 0 → 100644
  1 +<template>
  2 + <el-dialog
  3 + :title="$t('questionAnswerManage.publish.title')"
  4 + :visible.sync="visible"
  5 + width="40%"
  6 + @close="handleClose"
  7 + >
  8 + <el-form ref="form" :model="formData" :rules="rules" label-width="120px">
  9 + <el-form-item :label="$t('questionAnswerManage.publish.qaName')">
  10 + <el-input v-model="formData.qaName" disabled />
  11 + </el-form-item>
  12 +
  13 + <el-form-item :label="$t('questionAnswerManage.publish.tip')">
  14 + <el-input
  15 + :value="$t('questionAnswerManage.publish.tipText')"
  16 + disabled
  17 + />
  18 + </el-form-item>
  19 +
  20 + <el-form-item
  21 + :label="$t('questionAnswerManage.publish.notifyWay')"
  22 + prop="notifyWay"
  23 + >
  24 + <el-select
  25 + v-model="formData.notifyWay"
  26 + :placeholder="$t('questionAnswerManage.publish.notifyWayPlaceholder')"
  27 + style="width: 100%"
  28 + >
  29 + <el-option
  30 + value="1001"
  31 + :label="$t('questionAnswerManage.publish.notifyWay1')"
  32 + />
  33 + <el-option
  34 + value="2002"
  35 + :label="$t('questionAnswerManage.publish.notifyWay2')"
  36 + />
  37 + <el-option
  38 + value="3003"
  39 + :label="$t('questionAnswerManage.publish.notifyWay3')"
  40 + />
  41 + </el-select>
  42 + </el-form-item>
  43 + </el-form>
  44 +
  45 + <div slot="footer" class="dialog-footer">
  46 + <el-button @click="visible = false">
  47 + {{ $t('common.cancel') }}
  48 + </el-button>
  49 + <el-button type="primary" @click="handlePublish" :loading="loading">
  50 + {{ $t('questionAnswerManage.publish.button') }}
  51 + </el-button>
  52 + </div>
  53 + </el-dialog>
  54 +</template>
  55 +
  56 +<script>
  57 +import { publishQuestionAnswer } from '@/api/oa/questionAnswerManageApi'
  58 +import { getCommunityId } from '@/api/community/communityApi'
  59 +
  60 +export default {
  61 + name: 'PublishQuestionAnswer',
  62 + data() {
  63 + return {
  64 + visible: false,
  65 + loading: false,
  66 + formData: {
  67 + qaId: '',
  68 + qaName: '',
  69 + notifyWay: ''
  70 + },
  71 + rules: {
  72 + notifyWay: [
  73 + { required: true, message: this.$t('questionAnswerManage.validate.notifyWay'), trigger: 'blur' }
  74 + ]
  75 + }
  76 + }
  77 + },
  78 + methods: {
  79 + open(data) {
  80 + this.formData = {
  81 + qaId: data.qaId,
  82 + qaName: data.qaName,
  83 + notifyWay: ''
  84 + }
  85 + this.visible = true
  86 + this.$nextTick(() => {
  87 + this.$refs.form && this.$refs.form.clearValidate()
  88 + })
  89 + },
  90 + handleClose() {
  91 + this.formData = {
  92 + qaId: '',
  93 + qaName: '',
  94 + notifyWay: ''
  95 + }
  96 + this.loading = false
  97 + },
  98 + async handlePublish() {
  99 + this.$refs.form.validate(async valid => {
  100 + if (!valid) return
  101 +
  102 + try {
  103 + this.loading = true
  104 + const params = {
  105 + ...this.formData,
  106 + communityId: getCommunityId()
  107 + }
  108 + await publishQuestionAnswer(params)
  109 + this.$message.success(this.$t('questionAnswerManage.message.publishSuccess'))
  110 + this.visible = false
  111 + this.$emit('success')
  112 + } catch (error) {
  113 + this.$message.error(error.message || this.$t('questionAnswerManage.message.publishError'))
  114 + } finally {
  115 + this.loading = false
  116 + }
  117 + })
  118 + }
  119 + }
  120 +}
  121 +</script>
0 \ No newline at end of file 122 \ No newline at end of file
src/components/oa/uploadImageUrl.vue 0 → 100644
  1 +<template>
  2 + <div class="upload-image-container">
  3 + <div v-for="(image, index) in photos" :key="index" class="image-item">
  4 + <el-image
  5 + :src="getImageUrl(image)"
  6 + fit="cover"
  7 + :preview-src-list="[getImageUrl(image)]"
  8 + class="image-preview"
  9 + >
  10 + <div slot="error" class="image-slot">
  11 + <i class="el-icon-picture-outline"></i>
  12 + </div>
  13 + </el-image>
  14 + <i class="el-icon-close remove-icon" @click="removeImage(index)"></i>
  15 + </div>
  16 +
  17 + <el-upload
  18 + v-if="photos.length < imageCount"
  19 + action=""
  20 + :show-file-list="false"
  21 + :before-upload="beforeUpload"
  22 + :http-request="handleUpload"
  23 + class="upload-button"
  24 + >
  25 + <i class="el-icon-plus"></i>
  26 + </el-upload>
  27 + </div>
  28 +</template>
  29 +
  30 +<script>
  31 +import { uploadImage } from '@/api/oa/questionAnswerManageApi'
  32 +import { getCommunityId } from '@/api/community/communityApi'
  33 +
  34 +export default {
  35 + name: 'UploadImageUrl',
  36 + props: {
  37 + imageCount: {
  38 + type: Number,
  39 + default: 99
  40 + }
  41 + },
  42 + data() {
  43 + return {
  44 + photos: [],
  45 + photoUrls: []
  46 + }
  47 + },
  48 + methods: {
  49 + getImageUrl(image) {
  50 + if (typeof image === 'string') {
  51 + if (image.startsWith('http') || image.startsWith('https') || image.startsWith('data:')) {
  52 + return image
  53 + }
  54 + return `/callComponent/download/getFile/file?fileId=${image}&communityId=-1&time=${new Date().getTime()}`
  55 + }
  56 + return image.url || ''
  57 + },
  58 + setPhotos(photos) {
  59 + this.photos = photos || []
  60 + this.photoUrls = photos.map(photo => {
  61 + return typeof photo === 'string' ? { fileId: photo } : photo
  62 + })
  63 + this.$emit('change', this.photoUrls)
  64 + },
  65 + clear() {
  66 + this.photos = []
  67 + this.photoUrls = []
  68 + this.$emit('change', [])
  69 + },
  70 + beforeUpload(file) {
  71 + const isImage = file.type.includes('image/')
  72 + const isLt2M = file.size / 1024 / 1024 < 2
  73 +
  74 + if (!isImage) {
  75 + this.$message.error(this.$t('uploadImage.validate.imageType'))
  76 + }
  77 + if (!isLt2M) {
  78 + this.$message.error(this.$t('uploadImage.validate.imageSize'))
  79 + }
  80 + return isImage && isLt2M
  81 + },
  82 + async handleUpload({ file }) {
  83 + try {
  84 + const formData = new FormData()
  85 + formData.append('uploadFile', file)
  86 + formData.append('communityId', getCommunityId())
  87 +
  88 + const response = await uploadImage(formData)
  89 + this.photos.push(response.data)
  90 + this.photoUrls.push(response.data)
  91 + this.$emit('change', this.photoUrls)
  92 + } catch (error) {
  93 + this.$message.error(error.message || this.$t('uploadImage.message.uploadError'))
  94 + }
  95 + },
  96 + removeImage(index) {
  97 + this.photos.splice(index, 1)
  98 + this.photoUrls.splice(index, 1)
  99 + this.$emit('change', this.photoUrls)
  100 + }
  101 + }
  102 +}
  103 +</script>
  104 +
  105 +<style lang="scss" scoped>
  106 +.upload-image-container {
  107 + display: flex;
  108 + flex-wrap: wrap;
  109 + gap: 10px;
  110 +
  111 + .image-item {
  112 + position: relative;
  113 + width: 100px;
  114 + height: 100px;
  115 + border: 1px dashed #d9d9d9;
  116 + border-radius: 6px;
  117 + overflow: hidden;
  118 +
  119 + .image-preview {
  120 + width: 100%;
  121 + height: 100%;
  122 + }
  123 +
  124 + .remove-icon {
  125 + position: absolute;
  126 + top: 0;
  127 + right: 0;
  128 + padding: 5px;
  129 + color: #fff;
  130 + background-color: rgba(0, 0, 0, 0.5);
  131 + cursor: pointer;
  132 + z-index: 10;
  133 + }
  134 + }
  135 +
  136 + .upload-button {
  137 + display: flex;
  138 + justify-content: center;
  139 + align-items: center;
  140 + width: 100px;
  141 + height: 100px;
  142 + border: 1px dashed #d9d9d9;
  143 + border-radius: 6px;
  144 + cursor: pointer;
  145 + font-size: 28px;
  146 + color: #8c939d;
  147 +
  148 + &:hover {
  149 + border-color: #409eff;
  150 + }
  151 + }
  152 +}
  153 +</style>
0 \ No newline at end of file 154 \ No newline at end of file
src/components/room/selectRooms.vue 0 → 100644
  1 +<template>
  2 + <el-row :gutter="20">
  3 + <el-col :span="6" class="border-right">
  4 + <div class="text-center margin-bottom">
  5 + <span>{{ $t('selectRoom.buildingUnit') }}</span>
  6 + </div>
  7 + <div class="tree-container">
  8 + <el-tree ref="floorUnitTree" :data="units" :props="treeProps" node-key="id" show-checkbox @check="handleNodeCheck"
  9 + :default-expanded-keys="expandedKeys" />
  10 + </div>
  11 + </el-col>
  12 + <el-col :span="16">
  13 + <div class="text-center margin-bottom">
  14 + <span>{{ $t('selectRoom.roomInfo') }}</span>
  15 + </div>
  16 + <div class="room-container">
  17 + <el-row :gutter="10">
  18 + <el-col v-for="(item, index) in rooms" :key="index" :span="6" class="margin-bottom">
  19 + <el-checkbox v-model="roomIds" :label="item" @change="handleRoomSelect(item)">
  20 + {{ item.floorNum }}-{{ item.unitNum }}-{{ item.roomNum }}({{ item.stateName }})
  21 + </el-checkbox>
  22 + </el-col>
  23 + </el-row>
  24 + </div>
  25 + </el-col>
  26 + </el-row>
  27 +</template>
  28 +
  29 +<script>
  30 +import { getCommunityId } from '@/api/community/communityApi'
  31 +import { queryFloorAndUnits, queryRooms } from '@/api/oa/addQuestionAnswerApi'
  32 +
  33 +export default {
  34 + name: 'SelectRooms',
  35 + props: {
  36 + emitSelectRooms: {
  37 + type: String,
  38 + default: ''
  39 + }
  40 + },
  41 + data() {
  42 + return {
  43 + units: [],
  44 + rooms: [],
  45 + roomIds: [],
  46 + conditions: {
  47 + floorId: '',
  48 + unitId: '',
  49 + communityId: getCommunityId()
  50 + },
  51 + treeProps: {
  52 + label: 'text',
  53 + children: 'children'
  54 + },
  55 + expandedKeys: []
  56 + }
  57 + },
  58 + created() {
  59 + this.loadFloorAndUnits()
  60 + },
  61 + methods: {
  62 + open() {
  63 + this.loadFloorAndUnits()
  64 + },
  65 + async loadFloorAndUnits() {
  66 + try {
  67 + const res = await queryFloorAndUnits({
  68 + communityId: this.conditions.communityId
  69 + })
  70 + this.units = this.formatTreeData(res)
  71 + } catch (error) {
  72 + console.log(error)
  73 + this.$message.error(this.$t('selectRoom.loadError'))
  74 + }
  75 + },
  76 + formatTreeData(units) {
  77 + const floorMap = {}
  78 +
  79 + units.forEach(unit => {
  80 + if (!floorMap[unit.floorId]) {
  81 + floorMap[unit.floorId] = {
  82 + id: `f_${unit.floorId}`,
  83 + floorId: unit.floorId,
  84 + floorNum: unit.floorNum,
  85 + icon: "/img/floor.png",
  86 + text: `${unit.floorNum}${this.$t('room.floorUnitTree.building')}`,
  87 + children: []
  88 + }
  89 + }
  90 +
  91 + const unitNode = {
  92 + id: `u_${unit.unitId}`,
  93 + unitId: unit.unitId,
  94 + text: `${unit.unitNum}${this.$t('room.floorUnitTree.unit')}`,
  95 + icon: "/img/unit.png",
  96 + children: []
  97 + }
  98 +
  99 + floorMap[unit.floorId].children.push(unitNode)
  100 + })
  101 +
  102 + return Object.values(floorMap)
  103 + },
  104 + async listRooms() {
  105 + try {
  106 + const params = {
  107 + ...this.conditions,
  108 + page: 1,
  109 + row: 500
  110 + }
  111 + const res = await queryRooms(params)
  112 + this.rooms = res.rooms
  113 + } catch (error) {
  114 + this.$message.error(this.$t('selectRoom.loadRoomError'))
  115 + }
  116 + },
  117 + handleNodeCheck(data, { checkedNodes }) {
  118 + if (data.id.startsWith('f_')) {
  119 + this.conditions.floorId = checkedNodes.some(node => node.id === data.id) ? data.floorId : ''
  120 + } else if (data.id.startsWith('u_')) {
  121 + this.conditions.unitId = checkedNodes.some(node => node.id === data.id) ? data.unitId : ''
  122 + }
  123 + this.listRooms()
  124 + },
  125 + handleRoomSelect(room) {
  126 + console.log('选择房间', room)
  127 + this.$emit('notifySelectRooms', this.roomIds)
  128 + },
  129 + refreshTree(params) {
  130 + if (params) {
  131 + this.conditions.floorId = params.floorId || ''
  132 + }
  133 + this.loadFloorAndUnits()
  134 + }
  135 + }
  136 +}
  137 +</script>
  138 +
  139 +<style scoped>
  140 +.border-right {
  141 + border-right: 1px solid #ebeef5;
  142 +}
  143 +
  144 +.tree-container {
  145 + height: 300px;
  146 + overflow-y: auto;
  147 +}
  148 +
  149 +.room-container {
  150 + height: 300px;
  151 + overflow-y: auto;
  152 +}
  153 +
  154 +.margin-bottom {
  155 + margin-bottom: 10px;
  156 +}
  157 +
  158 +.text-center {
  159 + text-align: center;
  160 + padding: 10px 0;
  161 +}
  162 +</style>
0 \ No newline at end of file 163 \ No newline at end of file
src/i18n/oaI18n.js
@@ -9,7 +9,14 @@ import { messages as addComplaintTypeMessages } from &#39;../views/oa/addComplaintTy @@ -9,7 +9,14 @@ import { messages as addComplaintTypeMessages } from &#39;../views/oa/addComplaintTy
9 import { messages as editComplaintTypeMessages } from '../views/oa/editComplaintTypeLang' 9 import { messages as editComplaintTypeMessages } from '../views/oa/editComplaintTypeLang'
10 import { messages as addComplaintMessages } from '../views/oa/addComplaintLang' 10 import { messages as addComplaintMessages } from '../views/oa/addComplaintLang'
11 import { messages as complaintMessages } from '../views/oa/complaintLang' 11 import { messages as complaintMessages } from '../views/oa/complaintLang'
12 - 12 +import { messages as notepadManageMessages } from '../views/oa/notepadManageLang'
  13 +import { messages as visitManageMessages } from '../views/oa/visitManageLang'
  14 +import { messages as questionTitleMessages } from '../views/oa/questionTitleLang'
  15 +import { messages as questionAnswerManageMessages } from '../views/oa/questionAnswerManageLang'
  16 +import { messages as printQuestionAnswerMessages } from '../views/oa/printQuestionAnswerLang'
  17 +import { messages as addQuestionAnswerMessages } from '../views/oa/addQuestionAnswerLang'
  18 +import { messages as editQuestionAnswerMessages } from '../views/oa/editQuestionAnswerLang'
  19 +import { messages as printQuestionAnswerDetailMessages } from '../views/oa/printQuestionAnswerDetailLang'
13 export const messages ={ 20 export const messages ={
14 en:{ 21 en:{
15 ...activitiesTypeManageMessages.en, 22 ...activitiesTypeManageMessages.en,
@@ -23,6 +30,14 @@ export const messages ={ @@ -23,6 +30,14 @@ export const messages ={
23 ...editComplaintTypeMessages.en, 30 ...editComplaintTypeMessages.en,
24 ...addComplaintMessages.en, 31 ...addComplaintMessages.en,
25 ...complaintMessages.en, 32 ...complaintMessages.en,
  33 + ...notepadManageMessages.en,
  34 + ...visitManageMessages.en,
  35 + ...questionTitleMessages.en,
  36 + ...questionAnswerManageMessages.en,
  37 + ...printQuestionAnswerMessages.en,
  38 + ...addQuestionAnswerMessages.en,
  39 + ...editQuestionAnswerMessages.en,
  40 + ...printQuestionAnswerDetailMessages.en,
26 }, 41 },
27 zh:{ 42 zh:{
28 ...activitiesTypeManageMessages.zh, 43 ...activitiesTypeManageMessages.zh,
@@ -36,5 +51,13 @@ export const messages ={ @@ -36,5 +51,13 @@ export const messages ={
36 ...editComplaintTypeMessages.zh, 51 ...editComplaintTypeMessages.zh,
37 ...addComplaintMessages.zh, 52 ...addComplaintMessages.zh,
38 ...complaintMessages.zh, 53 ...complaintMessages.zh,
  54 + ...notepadManageMessages.zh,
  55 + ...visitManageMessages.zh,
  56 + ...questionTitleMessages.zh,
  57 + ...questionAnswerManageMessages.zh,
  58 + ...printQuestionAnswerMessages.zh,
  59 + ...addQuestionAnswerMessages.zh,
  60 + ...editQuestionAnswerMessages.zh,
  61 + ...printQuestionAnswerDetailMessages.zh,
39 } 62 }
40 } 63 }
41 \ No newline at end of file 64 \ No newline at end of file
src/router/index.js
@@ -330,7 +330,7 @@ const routes = [ @@ -330,7 +330,7 @@ const routes = [
330 name: '/views/work/adminRepairDetail', 330 name: '/views/work/adminRepairDetail',
331 component: () => import('@/views/work/adminRepairDetailList.vue') 331 component: () => import('@/views/work/adminRepairDetailList.vue')
332 }, 332 },
333 - 333 +
334 { 334 {
335 path: '/pages/complaint/adminComplaint', 335 path: '/pages/complaint/adminComplaint',
336 name: '/pages/complaint/adminComplaint', 336 name: '/pages/complaint/adminComplaint',
@@ -361,7 +361,7 @@ const routes = [ @@ -361,7 +361,7 @@ const routes = [
361 name: '/pages/iot/adminBarrier', 361 name: '/pages/iot/adminBarrier',
362 component: () => import('@/views/iot/adminBarrierList.vue') 362 component: () => import('@/views/iot/adminBarrierList.vue')
363 }, 363 },
364 - 364 +
365 { 365 {
366 path: '/pages/iot/adminMeter', 366 path: '/pages/iot/adminMeter',
367 name: '/pages/iot/adminMeter', 367 name: '/pages/iot/adminMeter',
@@ -822,7 +822,7 @@ const routes = [ @@ -822,7 +822,7 @@ const routes = [
822 name: '/pages/property/repairForceFinishManage', 822 name: '/pages/property/repairForceFinishManage',
823 component: () => import('@/views/work/repairForceFinishManageList.vue') 823 component: () => import('@/views/work/repairForceFinishManageList.vue')
824 }, 824 },
825 - 825 +
826 { 826 {
827 path: '/pages/resource/resourceAuditFlow', 827 path: '/pages/resource/resourceAuditFlow',
828 name: '/pages/resource/resourceAuditFlow', 828 name: '/pages/resource/resourceAuditFlow',
@@ -843,7 +843,7 @@ const routes = [ @@ -843,7 +843,7 @@ const routes = [
843 name: '/pages/common/resourceStoreManage', 843 name: '/pages/common/resourceStoreManage',
844 component: () => import('@/views/resource/resourceStoreManageList.vue') 844 component: () => import('@/views/resource/resourceStoreManageList.vue')
845 }, 845 },
846 - 846 +
847 { 847 {
848 path: '/pages/property/resourceSupplierManage', 848 path: '/pages/property/resourceSupplierManage',
849 name: '/pages/property/resourceSupplierManage', 849 name: '/pages/property/resourceSupplierManage',
@@ -924,7 +924,7 @@ const routes = [ @@ -924,7 +924,7 @@ const routes = [
924 name: '/pages/property/locationManage', 924 name: '/pages/property/locationManage',
925 component: () => import('@/views/community/locationManageList.vue') 925 component: () => import('@/views/community/locationManageList.vue')
926 }, 926 },
927 - 927 +
928 ...inspectionRouter, 928 ...inspectionRouter,
929 ...machineRouter, 929 ...machineRouter,
930 ...oaRouter, 930 ...oaRouter,
@@ -945,6 +945,16 @@ const routes = [ @@ -945,6 +945,16 @@ const routes = [
945 name: '/views/resource/printEquipmentAccountLabel', 945 name: '/views/resource/printEquipmentAccountLabel',
946 component: printEquipmentAccountLabel 946 component: printEquipmentAccountLabel
947 }, 947 },
  948 + {
  949 + path: '/pages/question/printQuestionAnswer',
  950 + name: '/pages/question/printQuestionAnswer',
  951 + component: () => import('@/views/oa/printQuestionAnswerList.vue')
  952 + },
  953 + {
  954 + path: '/pages/question/printQuestionAnswerDetail',
  955 + name: '/pages/question/printQuestionAnswerDetail',
  956 + component: () => import('@/views/oa/printQuestionAnswerDetailList.vue')
  957 + },
948 ] 958 ]
949 959
950 const router = new VueRouter({ 960 const router = new VueRouter({
src/router/oaRouter.js
@@ -54,4 +54,35 @@ export default [ @@ -54,4 +54,35 @@ export default [
54 name: '/pages/complaint/complaint', 54 name: '/pages/complaint/complaint',
55 component: () => import('@/views/oa/complaintList.vue') 55 component: () => import('@/views/oa/complaintList.vue')
56 }, 56 },
  57 + {
  58 + path: '/pages/property/notepadManage',
  59 + name: '/pages/property/notepadManage',
  60 + component: () => import('@/views/oa/notepadManageList.vue')
  61 + },
  62 + {
  63 + path: '/pages/property/visitManage',
  64 + name: '/pages/property/visitManage',
  65 + component: () => import('@/views/oa/visitManageList.vue')
  66 + },
  67 + {
  68 + path: '/pages/question/questionTitle',
  69 + name: '/pages/question/questionTitle',
  70 + component: () => import('@/views/oa/questionTitleList.vue')
  71 + },
  72 + {
  73 + path: '/pages/property/questionAnswerManage',
  74 + name: '/pages/property/questionAnswerManage',
  75 + component: () => import('@/views/oa/questionAnswerManageList.vue')
  76 + },
  77 + {
  78 + path: '/views/oa/addQuestionAnswer',
  79 + name: '/views/oa/addQuestionAnswer',
  80 + component: () => import('@/views/oa/addQuestionAnswerList.vue')
  81 + },
  82 + {
  83 + path:'/views/oa/editQuestionAnswer',
  84 + name:'/views/oa/editQuestionAnswer',
  85 + component: () => import('@/views/oa/editQuestionAnswerList.vue')
  86 + },
  87 +
57 ] 88 ]
58 \ No newline at end of file 89 \ No newline at end of file
src/views/oa/addQuestionAnswerLang.js 0 → 100644
  1 +export const messages = {
  2 + en: {
  3 + addQuestionAnswer: {
  4 + title: 'Add Questionnaire',
  5 + qaName: 'Questionnaire Name',
  6 + qaNamePlaceholder: 'Required, please fill in the questionnaire name',
  7 + startTime: 'Start Time',
  8 + startTimePlaceholder: 'Required, please select start time',
  9 + endTime: 'End Time',
  10 + endTimePlaceholder: 'Required, please select end time',
  11 + content: 'Questionnaire Description',
  12 + contentPlaceholder: 'Required, please enter questionnaire description',
  13 + questionTitle: 'Questionnaire Questions',
  14 + chooseTitle: 'Select Questions',
  15 + questionRoom: 'Questionnaire Rooms',
  16 + qaNameRequired: 'Questionnaire name is required',
  17 + startTimeRequired: 'Start time is required',
  18 + endTimeRequired: 'End time is required',
  19 + timeInvalid: 'End time must be later than start time',
  20 + contentRequired: 'Questionnaire description is required',
  21 + titleRequired: 'Please select at least one question',
  22 + roomRequired: 'Please select at least one room',
  23 + titleExist: 'This question has already been selected',
  24 + confirmDeleteTitle: 'Are you sure to delete this question?',
  25 + saveSuccess: 'Questionnaire saved successfully',
  26 + saveError: 'Failed to save questionnaire'
  27 + },
  28 + questionTitle: {
  29 + name: 'Name',
  30 + type: 'Type',
  31 + options: 'Options',
  32 + operation: 'Operation',
  33 + delete: 'Delete',
  34 + createTime: 'Create Time',
  35 + singleChoice: 'Single Choice',
  36 + multipleChoice: 'Multiple Choice',
  37 + shortAnswer: 'Short Answer'
  38 + },
  39 + selectRoom: {
  40 + buildingUnit: 'Building Unit',
  41 + roomInfo: 'Room Information',
  42 + unit: 'Unit',
  43 + loadError: 'Failed to load building units',
  44 + loadRoomError: 'Failed to load rooms'
  45 + },
  46 + chooseQuestionTitle: {
  47 + title: 'Select Questions',
  48 + searchPlaceholder: 'Enter question name',
  49 + question: 'Question',
  50 + operation: 'Operation',
  51 + choose: 'Choose',
  52 + queryError: 'Failed to query questions'
  53 + }
  54 + },
  55 + zh: {
  56 + addQuestionAnswer: {
  57 + title: '添加问卷',
  58 + qaName: '问卷名称',
  59 + qaNamePlaceholder: '必填,请填写问卷名称',
  60 + startTime: '开始时间',
  61 + startTimePlaceholder: '必填,请选择开始时间',
  62 + endTime: '结束时间',
  63 + endTimePlaceholder: '必填,请选择结束时间',
  64 + content: '问卷说明',
  65 + contentPlaceholder: '必填,请输入问卷说明',
  66 + questionTitle: '问卷题目',
  67 + chooseTitle: '选择题目',
  68 + questionRoom: '问卷房屋',
  69 + qaNameRequired: '问卷名称不能为空',
  70 + startTimeRequired: '开始时间不能为空',
  71 + endTimeRequired: '结束时间不能为空',
  72 + timeInvalid: '结束时间必须晚于开始时间',
  73 + contentRequired: '问卷说明不能为空',
  74 + titleRequired: '请至少选择一个题目',
  75 + roomRequired: '请至少选择一间房屋',
  76 + titleExist: '该题目已被选择',
  77 + confirmDeleteTitle: '确定删除该题目吗?',
  78 + saveSuccess: '问卷保存成功',
  79 + saveError: '问卷保存失败'
  80 + },
  81 + questionTitle: {
  82 + name: '名称',
  83 + type: '类型',
  84 + options: '选项',
  85 + operation: '操作',
  86 + delete: '删除',
  87 + createTime: '创建时间',
  88 + singleChoice: '单选',
  89 + multipleChoice: '多选',
  90 + shortAnswer: '简答'
  91 + },
  92 + selectRoom: {
  93 + buildingUnit: '楼栋单元',
  94 + roomInfo: '房屋信息',
  95 + unit: '单元',
  96 + loadError: '加载楼栋单元失败',
  97 + loadRoomError: '加载房屋信息失败'
  98 + },
  99 + chooseQuestionTitle: {
  100 + title: '选择题目',
  101 + searchPlaceholder: '输入题目名称',
  102 + question: '题目',
  103 + operation: '操作',
  104 + choose: '选择',
  105 + queryError: '查询题目失败'
  106 + }
  107 + }
  108 +}
0 \ No newline at end of file 109 \ No newline at end of file
src/views/oa/addQuestionAnswerList.vue 0 → 100644
  1 +<template>
  2 + <div class="add-question-answer-container">
  3 + <el-card class="box-card">
  4 + <div slot="header" class="flex justify-between">
  5 + <span>{{ $t('addQuestionAnswer.title') }}</span>
  6 + </div>
  7 + <el-row :gutter="20">
  8 + <el-col :span="24">
  9 + <el-form ref="form" :model="addQuestionAnswerInfo" label-width="120px">
  10 + <el-row>
  11 + <el-col :span="12">
  12 + <el-form-item :label="$t('addQuestionAnswer.qaName')" prop="qaName">
  13 + <el-input v-model="addQuestionAnswerInfo.qaName"
  14 + :placeholder="$t('addQuestionAnswer.qaNamePlaceholder')" />
  15 + </el-form-item>
  16 + </el-col>
  17 + <el-col :span="12">
  18 + <el-form-item :label="$t('addQuestionAnswer.startTime')" prop="startTime">
  19 + <el-date-picker v-model="addQuestionAnswerInfo.startTime" type="datetime"
  20 + :placeholder="$t('addQuestionAnswer.startTimePlaceholder')" value-format="yyyy-MM-dd HH:mm:ss"
  21 + style="width: 100%" />
  22 + </el-form-item>
  23 + </el-col>
  24 + </el-row>
  25 + <el-row>
  26 + <el-col :span="12">
  27 + <el-form-item :label="$t('addQuestionAnswer.endTime')" prop="endTime">
  28 + <el-date-picker v-model="addQuestionAnswerInfo.endTime" type="datetime"
  29 + :placeholder="$t('addQuestionAnswer.endTimePlaceholder')" value-format="yyyy-MM-dd HH:mm:ss"
  30 + style="width: 100%" />
  31 + </el-form-item>
  32 + </el-col>
  33 + </el-row>
  34 + <el-row>
  35 + <el-col :span="24">
  36 + <el-form-item :label="$t('addQuestionAnswer.content')">
  37 + <el-input v-model="addQuestionAnswerInfo.content" type="textarea" :rows="5"
  38 + :placeholder="$t('addQuestionAnswer.contentPlaceholder')" />
  39 + </el-form-item>
  40 + </el-col>
  41 + </el-row>
  42 + </el-form>
  43 + </el-col>
  44 + </el-row>
  45 + </el-card>
  46 +
  47 + <el-card class="box-card margin-top">
  48 + <div slot="header" class="flex justify-between">
  49 + <span>{{ $t('addQuestionAnswer.questionTitle') }}</span>
  50 + <el-button type="primary" class="float-right" @click="chooseTitle">
  51 + <i class="el-icon-search"></i>{{ $t('addQuestionAnswer.chooseTitle') }}
  52 + </el-button>
  53 + </div>
  54 + <el-table :data="addQuestionAnswerInfo.questionTitles" border style="width: 100%">
  55 + <el-table-column prop="qaTitle" :label="$t('questionTitle.name')" align="center" />
  56 + <el-table-column prop="titleType" :label="$t('questionTitle.type')" align="center">
  57 + <template slot-scope="scope">
  58 + {{ getTitleTypeName(scope.row.titleType) }}
  59 + </template>
  60 + </el-table-column>
  61 + <el-table-column :label="$t('questionTitle.options')" align="center">
  62 + <template slot-scope="scope">
  63 + <div v-for="(item, index) in scope.row.titleValues" :key="index">
  64 + {{ item.seq }}、{{ item.qaValue }}
  65 + </div>
  66 + </template>
  67 + </el-table-column>
  68 + <el-table-column :label="$t('questionTitle.operation')" align="center" width="150">
  69 + <template slot-scope="scope">
  70 + <el-button type="danger" size="mini" @click="openDeleteQuestionTitle(scope.row)">
  71 + {{ $t('questionTitle.delete') }}
  72 + </el-button>
  73 + </template>
  74 + </el-table-column>
  75 + </el-table>
  76 + </el-card>
  77 +
  78 + <el-card class="box-card margin-top">
  79 + <div slot="header" class="flex justify-between">
  80 + <span>{{ $t('addQuestionAnswer.questionRoom') }}</span>
  81 + </div>
  82 + <el-row>
  83 + <el-col :span="24">
  84 + <el-form>
  85 + <el-form-item :label="$t('addQuestionAnswer.questionRoom')">
  86 + <select-rooms ref="selectRooms" @notifySelectRooms="handleSelectRooms" />
  87 + </el-form-item>
  88 + </el-form>
  89 + </el-col>
  90 + </el-row>
  91 + </el-card>
  92 +
  93 + <div class="action-buttons margin-top">
  94 + <el-button type="primary" @click="saveQuestionAnswer">
  95 + <i class="el-icon-check"></i>{{ $t('common.save') }}
  96 + </el-button>
  97 + <el-button type="warning" @click="goBack">
  98 + {{ $t('common.cancel') }}
  99 + </el-button>
  100 + </div>
  101 +
  102 + <choose-question-title ref="chooseQuestionTitle" @chooseQuestionTitle="handleChooseQuestionTitle" />
  103 + </div>
  104 +</template>
  105 +
  106 +<script>
  107 +import SelectRooms from '@/components/room/selectRooms'
  108 +import ChooseQuestionTitle from '@/components/oa/chooseQuestionTitle'
  109 +import { saveQuestionAnswer } from '@/api/oa/addQuestionAnswerApi'
  110 +import { getCommunityId } from '@/api/community/communityApi'
  111 +
  112 +export default {
  113 + name: 'AddQuestionAnswer',
  114 + components: {
  115 + SelectRooms,
  116 + ChooseQuestionTitle
  117 + },
  118 + data() {
  119 + return {
  120 + addQuestionAnswerInfo: {
  121 + qaName: '',
  122 + startTime: '',
  123 + endTime: '',
  124 + communityId: '',
  125 + content: '',
  126 + questionTitles: [],
  127 + roomIds: []
  128 + }
  129 + }
  130 + },
  131 + created() {
  132 + this.addQuestionAnswerInfo.communityId = getCommunityId()
  133 + },
  134 + methods: {
  135 + getTitleTypeName(titleType) {
  136 + if (titleType === '1001') {
  137 + return this.$t('questionTitle.singleChoice')
  138 + } else if (titleType === '2002') {
  139 + return this.$t('questionTitle.multipleChoice')
  140 + } else {
  141 + return this.$t('questionTitle.shortAnswer')
  142 + }
  143 + },
  144 + chooseTitle() {
  145 + this.$refs.chooseQuestionTitle.open()
  146 + },
  147 + handleChooseQuestionTitle(questionTitle) {
  148 + const hasTitle = this.addQuestionAnswerInfo.questionTitles.some(
  149 + item => item.titleId === questionTitle.titleId
  150 + )
  151 + if (hasTitle) {
  152 + this.$message.warning(this.$t('addQuestionAnswer.titleExist'))
  153 + return
  154 + }
  155 + this.addQuestionAnswerInfo.questionTitles.push(questionTitle)
  156 + },
  157 + openDeleteQuestionTitle(title) {
  158 + this.$confirm(
  159 + this.$t('addQuestionAnswer.confirmDeleteTitle'),
  160 + this.$t('common.tip'),
  161 + {
  162 + confirmButtonText: this.$t('common.confirm'),
  163 + cancelButtonText: this.$t('common.cancel'),
  164 + type: 'warning'
  165 + }
  166 + ).then(() => {
  167 + this.addQuestionAnswerInfo.questionTitles = this.addQuestionAnswerInfo.questionTitles.filter(
  168 + item => item.titleId !== title.titleId
  169 + )
  170 + })
  171 + },
  172 + handleSelectRooms(rooms) {
  173 + this.addQuestionAnswerInfo.roomIds = rooms.map(item => item.roomId)
  174 + },
  175 + validateForm() {
  176 + if (!this.addQuestionAnswerInfo.qaName) {
  177 + this.$message.warning(this.$t('addQuestionAnswer.qaNameRequired'))
  178 + return false
  179 + }
  180 + if (!this.addQuestionAnswerInfo.startTime) {
  181 + this.$message.warning(this.$t('addQuestionAnswer.startTimeRequired'))
  182 + return false
  183 + }
  184 + if (!this.addQuestionAnswerInfo.endTime) {
  185 + this.$message.warning(this.$t('addQuestionAnswer.endTimeRequired'))
  186 + return false
  187 + }
  188 + if (this.addQuestionAnswerInfo.startTime >= this.addQuestionAnswerInfo.endTime) {
  189 + this.$message.warning(this.$t('addQuestionAnswer.timeInvalid'))
  190 + return false
  191 + }
  192 + if (!this.addQuestionAnswerInfo.content) {
  193 + this.$message.warning(this.$t('addQuestionAnswer.contentRequired'))
  194 + return false
  195 + }
  196 + if (this.addQuestionAnswerInfo.questionTitles.length === 0) {
  197 + this.$message.warning(this.$t('addQuestionAnswer.titleRequired'))
  198 + return false
  199 + }
  200 + if (this.addQuestionAnswerInfo.roomIds.length === 0) {
  201 + this.$message.warning(this.$t('addQuestionAnswer.roomRequired'))
  202 + return false
  203 + }
  204 + return true
  205 + },
  206 + async saveQuestionAnswer() {
  207 + if (!this.validateForm()) return
  208 +
  209 + try {
  210 + const res = await saveQuestionAnswer(this.addQuestionAnswerInfo)
  211 + if (res.code === 0) {
  212 + this.$message.success(this.$t('addQuestionAnswer.saveSuccess'))
  213 + this.goBack()
  214 + } else {
  215 + this.$message.error(res.msg)
  216 + }
  217 + } catch (error) {
  218 + this.$message.error(this.$t('addQuestionAnswer.saveError'))
  219 + }
  220 + },
  221 + goBack() {
  222 + this.$router.go(-1)
  223 + }
  224 + }
  225 +}
  226 +</script>
  227 +
  228 +<style lang="scss" scoped>
  229 +.add-question-answer-container {
  230 + padding: 20px;
  231 +
  232 + .box-card {
  233 + margin-bottom: 20px;
  234 + }
  235 +
  236 + .margin-top {
  237 + margin-top: 20px;
  238 + }
  239 +
  240 + .float-right {
  241 + float: right;
  242 + }
  243 +
  244 + .action-buttons {
  245 + text-align: right;
  246 + margin-top: 20px;
  247 + padding: 20px;
  248 + background: #fff;
  249 + border-radius: 4px;
  250 + }
  251 +}
  252 +</style>
0 \ No newline at end of file 253 \ No newline at end of file
src/views/oa/editQuestionAnswerLang.js 0 → 100644
  1 +export const messages = {
  2 + en: {
  3 + editQuestionAnswer: {
  4 + title: 'Edit Questionnaire',
  5 + qaName: 'Questionnaire Name',
  6 + qaNamePlaceholder: 'Required, please enter questionnaire name',
  7 + startTime: 'Start Time',
  8 + startTimePlaceholder: 'Required, please select start time',
  9 + endTime: 'End Time',
  10 + endTimePlaceholder: 'Required, please select end time',
  11 + content: 'Questionnaire Description',
  12 + contentPlaceholder: 'Required, please enter questionnaire description',
  13 + questionTitle: 'Questionnaire Questions',
  14 + chooseTitle: 'Select Questions',
  15 + questionRooms: 'Questionnaire Rooms',
  16 + household: 'households',
  17 + reselect: 'Reselect',
  18 + save: 'Save',
  19 + cancel: 'Cancel',
  20 + table: {
  21 + name: 'Name',
  22 + type: 'Type',
  23 + options: 'Options',
  24 + actions: 'Actions',
  25 + delete: 'Delete'
  26 + },
  27 + singleChoice: 'Single Choice',
  28 + multipleChoice: 'Multiple Choice',
  29 + shortAnswer: 'Short Answer',
  30 + duplicateTitle: 'Please do not select duplicate questions',
  31 + validate: {
  32 + qaName: 'Questionnaire name cannot be empty',
  33 + startTime: 'Start time cannot be empty',
  34 + endTime: 'End time cannot be empty',
  35 + timeError: 'Start time must be earlier than end time'
  36 + },
  37 + updateSuccess: 'Update successful',
  38 + updateFailed: 'Update failed'
  39 + },
  40 + selectRoom: {
  41 + buildingUnit: 'Building Unit',
  42 + roomInfo: 'Room Information'
  43 + },
  44 + chooseQuestionTitle: {
  45 + title: 'Select Questions',
  46 + searchPlaceholder: 'Enter question name',
  47 + table: {
  48 + title: 'Question',
  49 + createTime: 'Create Time',
  50 + actions: 'Actions',
  51 + choose: 'Select'
  52 + }
  53 + }
  54 + },
  55 + zh: {
  56 + editQuestionAnswer: {
  57 + title: '修改问卷',
  58 + qaName: '问卷名称',
  59 + qaNamePlaceholder: '必填,请填写问卷名称',
  60 + startTime: '开始时间',
  61 + startTimePlaceholder: '必填,请选择开始时间',
  62 + endTime: '结束时间',
  63 + endTimePlaceholder: '必填,请选择结束时间',
  64 + content: '问卷说明',
  65 + contentPlaceholder: '必填,请输入问卷说明',
  66 + questionTitle: '问卷题目',
  67 + chooseTitle: '选择题目',
  68 + questionRooms: '问卷房屋',
  69 + household: '户',
  70 + reselect: '重新选择',
  71 + save: '保存',
  72 + cancel: '取消',
  73 + table: {
  74 + name: '名称',
  75 + type: '类型',
  76 + options: '选项',
  77 + actions: '操作',
  78 + delete: '删除'
  79 + },
  80 + singleChoice: '单选',
  81 + multipleChoice: '多选',
  82 + shortAnswer: '简答',
  83 + duplicateTitle: '请勿重复选择题目',
  84 + validate: {
  85 + qaName: '问卷名称不能为空',
  86 + startTime: '开始时间不能为空',
  87 + endTime: '结束时间不能为空',
  88 + timeError: '开始时间必须小于结束时间'
  89 + },
  90 + updateSuccess: '修改成功',
  91 + updateFailed: '修改失败'
  92 + },
  93 + selectRoom: {
  94 + buildingUnit: '楼栋单元',
  95 + roomInfo: '房屋信息'
  96 + },
  97 + chooseQuestionTitle: {
  98 + title: '选择题目',
  99 + searchPlaceholder: '输入题目名称',
  100 + table: {
  101 + title: '题目',
  102 + createTime: '创建时间',
  103 + actions: '操作',
  104 + choose: '选择'
  105 + }
  106 + }
  107 + }
  108 +}
0 \ No newline at end of file 109 \ No newline at end of file
src/views/oa/editQuestionAnswerList.vue 0 → 100644
  1 +<template>
  2 + <div class="edit-question-answer-container">
  3 + <el-card class="box-card">
  4 + <div slot="header" class="flex justify-between">
  5 + <span>{{ $t('editQuestionAnswer.title') }}</span>
  6 + </div>
  7 + <el-row :gutter="20">
  8 + <el-col :span="24">
  9 + <el-form ref="form" :model="editQuestionAnswerInfo" label-width="120px">
  10 + <el-row>
  11 + <el-col :span="12">
  12 + <el-form-item :label="$t('editQuestionAnswer.qaName')" prop="qaName">
  13 + <el-input v-model="editQuestionAnswerInfo.qaName"
  14 + :placeholder="$t('editQuestionAnswer.qaNamePlaceholder')" />
  15 + </el-form-item>
  16 + </el-col>
  17 + <el-col :span="12">
  18 + <el-form-item :label="$t('editQuestionAnswer.startTime')" prop="startTime">
  19 + <el-date-picker v-model="editQuestionAnswerInfo.startTime" type="datetime"
  20 + :placeholder="$t('editQuestionAnswer.startTimePlaceholder')" style="width: 100%" />
  21 + </el-form-item>
  22 + </el-col>
  23 + </el-row>
  24 + <el-row>
  25 + <el-col :span="12">
  26 + <el-form-item :label="$t('editQuestionAnswer.endTime')" prop="endTime">
  27 + <el-date-picker v-model="editQuestionAnswerInfo.endTime" type="datetime"
  28 + :placeholder="$t('editQuestionAnswer.endTimePlaceholder')" style="width: 100%" />
  29 + </el-form-item>
  30 + </el-col>
  31 + </el-row>
  32 + <el-row>
  33 + <el-col :span="24">
  34 + <el-form-item :label="$t('editQuestionAnswer.content')">
  35 + <el-input v-model="editQuestionAnswerInfo.content" type="textarea" :rows="5"
  36 + :placeholder="$t('editQuestionAnswer.contentPlaceholder')" />
  37 + </el-form-item>
  38 + </el-col>
  39 + </el-row>
  40 + </el-form>
  41 + </el-col>
  42 + </el-row>
  43 + </el-card>
  44 +
  45 + <el-card class="box-card margin-top">
  46 + <div slot="header" class="flex justify-between">
  47 + <span>{{ $t('editQuestionAnswer.questionTitle') }}</span>
  48 + <div class="float-right">
  49 + <el-button type="primary" @click="chooseTitle">
  50 + <i class="el-icon-search"></i>{{ $t('editQuestionAnswer.chooseTitle') }}
  51 + </el-button>
  52 + </div>
  53 + </div>
  54 + <el-table :data="editQuestionAnswerInfo.questionTitles" border style="width: 100%">
  55 + <el-table-column prop="qaTitle" :label="$t('editQuestionAnswer.table.name')" align="center" />
  56 + <el-table-column prop="titleType" :label="$t('editQuestionAnswer.table.type')" align="center">
  57 + <template slot-scope="scope">
  58 + {{ getTitleTypeName(scope.row.titleType) }}
  59 + </template>
  60 + </el-table-column>
  61 + <el-table-column :label="$t('editQuestionAnswer.table.options')" align="center">
  62 + <template slot-scope="scope">
  63 + <div v-for="(item, index) in scope.row.titleValues" :key="index">
  64 + {{ item.seq }}、{{ item.qaValue }}
  65 + </div>
  66 + </template>
  67 + </el-table-column>
  68 + <el-table-column :label="$t('editQuestionAnswer.table.actions')" align="center">
  69 + <template slot-scope="scope">
  70 + <el-button type="danger" size="mini" @click="openDeleteQuestionTitle(scope.row)">
  71 + {{ $t('editQuestionAnswer.table.delete') }}
  72 + </el-button>
  73 + </template>
  74 + </el-table-column>
  75 + </el-table>
  76 + </el-card>
  77 +
  78 + <el-card class="box-card margin-top">
  79 + <div slot="header" class="flex justify-between">
  80 + <span>{{ $t('editQuestionAnswer.questionRooms') }}</span>
  81 + </div>
  82 + <el-row>
  83 + <el-col :span="24">
  84 + <el-form>
  85 + <el-form-item :label="$t('editQuestionAnswer.questionRooms')">
  86 + <div v-if="editQuestionAnswerInfo.updateRoomIds">
  87 + <select-rooms ref="selectRooms" @notifySelectRooms="handleSelectRooms" />
  88 + </div>
  89 + <div v-else>
  90 + <span>{{ editQuestionAnswerInfo.voteCount }}{{ $t('editQuestionAnswer.household') }}</span>
  91 + <el-button type="text" @click="updateRoomIdsMethod">
  92 + {{ $t('editQuestionAnswer.reselect') }}
  93 + </el-button>
  94 + </div>
  95 + </el-form-item>
  96 + </el-form>
  97 + </el-col>
  98 + </el-row>
  99 + </el-card>
  100 +
  101 + <div class="action-buttons margin-top">
  102 + <el-button type="primary" @click="updateQuestionAnswer">
  103 + <i class="el-icon-check"></i>{{ $t('editQuestionAnswer.save') }}
  104 + </el-button>
  105 + <el-button type="warning" @click="goBack">
  106 + {{ $t('editQuestionAnswer.cancel') }}
  107 + </el-button>
  108 + </div>
  109 +
  110 + <choose-question-title ref="chooseQuestionTitle" @chooseQuestionTitle="handleChooseQuestionTitle" />
  111 + </div>
  112 +</template>
  113 +
  114 +<script>
  115 +import { getCommunityId } from '@/api/community/communityApi'
  116 +import SelectRooms from '@/components/room/selectRooms'
  117 +import ChooseQuestionTitle from '@/components/oa/chooseQuestionTitle'
  118 +import { updateQuestionAnswer, listQuestionAnswer, listQuestionTitle } from '@/api/oa/editQuestionAnswerApi'
  119 +
  120 +export default {
  121 + name: 'EditQuestionAnswerList',
  122 + components: {
  123 + SelectRooms,
  124 + ChooseQuestionTitle
  125 + },
  126 + data() {
  127 + return {
  128 + editQuestionAnswerInfo: {
  129 + qaName: '',
  130 + startTime: '',
  131 + endTime: '',
  132 + communityId: '',
  133 + content: '',
  134 + titleType: '',
  135 + questionTitles: [],
  136 + roomIds: [],
  137 + updateRoomIds: false,
  138 + voteCount: 0,
  139 + qaId: ''
  140 + }
  141 + }
  142 + },
  143 + created() {
  144 + this.editQuestionAnswerInfo.communityId = getCommunityId()
  145 + this.editQuestionAnswerInfo.qaId = this.$route.query.qaId
  146 + this.loadQuestionAnswerInfo()
  147 + this.loadQuestionTitles()
  148 + },
  149 + methods: {
  150 + async loadQuestionAnswerInfo() {
  151 + try {
  152 + const params = {
  153 + page: 1,
  154 + row: 1,
  155 + communityId: this.editQuestionAnswerInfo.communityId,
  156 + qaId: this.editQuestionAnswerInfo.qaId
  157 + }
  158 + const { data } = await listQuestionAnswer(params)
  159 + this.editQuestionAnswerInfo = { ...this.editQuestionAnswerInfo, ...data[0] }
  160 + } catch (error) {
  161 + console.error('Failed to load question answer info:', error)
  162 + }
  163 + },
  164 + async loadQuestionTitles() {
  165 + try {
  166 + const params = {
  167 + page: 1,
  168 + row: 100,
  169 + communityId: this.editQuestionAnswerInfo.communityId,
  170 + qaId: this.editQuestionAnswerInfo.qaId
  171 + }
  172 + const { data } = await listQuestionTitle(params)
  173 + this.editQuestionAnswerInfo.questionTitles = data
  174 + } catch (error) {
  175 + console.error('Failed to load question titles:', error)
  176 + }
  177 + },
  178 + getTitleTypeName(titleType) {
  179 + if (titleType === '1001') {
  180 + return this.$t('editQuestionAnswer.singleChoice')
  181 + } else if (titleType === '2002') {
  182 + return this.$t('editQuestionAnswer.multipleChoice')
  183 + } else {
  184 + return this.$t('editQuestionAnswer.shortAnswer')
  185 + }
  186 + },
  187 + chooseTitle() {
  188 + this.$refs.chooseQuestionTitle.open()
  189 + },
  190 + handleChooseQuestionTitle(questionTitle) {
  191 + const exists = this.editQuestionAnswerInfo.questionTitles.some(
  192 + item => item.titleId === questionTitle.titleId
  193 + )
  194 + if (exists) {
  195 + this.$message.warning(this.$t('editQuestionAnswer.duplicateTitle'))
  196 + return
  197 + }
  198 + this.editQuestionAnswerInfo.questionTitles.push(questionTitle)
  199 + },
  200 + openDeleteQuestionTitle(title) {
  201 + this.editQuestionAnswerInfo.questionTitles = this.editQuestionAnswerInfo.questionTitles.filter(
  202 + item => item.titleId !== title.titleId
  203 + )
  204 + },
  205 + updateRoomIdsMethod() {
  206 + this.editQuestionAnswerInfo.updateRoomIds = true
  207 + },
  208 + handleSelectRooms(selectRooms) {
  209 + this.editQuestionAnswerInfo.roomIds = selectRooms.map(item => item.roomId)
  210 + },
  211 + validateForm() {
  212 + if (!this.editQuestionAnswerInfo.qaName) {
  213 + this.$message.error(this.$t('editQuestionAnswer.validate.qaName'))
  214 + return false
  215 + }
  216 + if (!this.editQuestionAnswerInfo.startTime) {
  217 + this.$message.error(this.$t('editQuestionAnswer.validate.startTime'))
  218 + return false
  219 + }
  220 + if (!this.editQuestionAnswerInfo.endTime) {
  221 + this.$message.error(this.$t('editQuestionAnswer.validate.endTime'))
  222 + return false
  223 + }
  224 + if (new Date(this.editQuestionAnswerInfo.startTime) >= new Date(this.editQuestionAnswerInfo.endTime)) {
  225 + this.$message.error(this.$t('editQuestionAnswer.validate.timeError'))
  226 + return false
  227 + }
  228 + return true
  229 + },
  230 + async updateQuestionAnswer() {
  231 + if (!this.validateForm()) return
  232 +
  233 + try {
  234 + await updateQuestionAnswer(this.editQuestionAnswerInfo)
  235 + this.$message.success(this.$t('editQuestionAnswer.updateSuccess'))
  236 + this.goBack()
  237 + } catch (error) {
  238 + console.error('Failed to update question answer:', error)
  239 + this.$message.error(error.message || this.$t('editQuestionAnswer.updateFailed'))
  240 + }
  241 + },
  242 + goBack() {
  243 + this.$router.go(-1)
  244 + }
  245 + }
  246 +}
  247 +</script>
  248 +
  249 +<style lang="scss" scoped>
  250 +.edit-question-answer-container {
  251 + padding: 20px;
  252 +
  253 + .box-card {
  254 + margin-bottom: 20px;
  255 + }
  256 +
  257 + .margin-top {
  258 + margin-top: 20px;
  259 + }
  260 +
  261 + .float-right {
  262 + float: right;
  263 + }
  264 +
  265 + .action-buttons {
  266 + text-align: right;
  267 + margin-top: 20px;
  268 + }
  269 +}
  270 +</style>
0 \ No newline at end of file 271 \ No newline at end of file
src/views/oa/notepadManageLang.js 0 → 100644
  1 +export const messages = {
  2 + en: {
  3 + notepadManage: {
  4 + search: {
  5 + title: 'Search Conditions',
  6 + noteType: 'Please select feedback type',
  7 + roomName: 'Please select room',
  8 + state: 'Please select status',
  9 + createUserName: 'Please select registrant',
  10 + objName: 'Please select owner name'
  11 + },
  12 + list: {
  13 + title: 'Owner Feedback'
  14 + },
  15 + table: {
  16 + noteType: 'Type',
  17 + roomName: 'Room',
  18 + objName: 'Contact',
  19 + link: 'Contact Phone',
  20 + state: 'Status',
  21 + createTime: 'Record Time',
  22 + createUserName: 'Registrant',
  23 + title: 'Content'
  24 + },
  25 + state: {
  26 + following: 'Following',
  27 + completed: 'Completed',
  28 + transferred: 'Transferred'
  29 + },
  30 + operation: {
  31 + follow: 'Follow',
  32 + progress: 'Progress',
  33 + transfer: 'Transfer to Repair',
  34 + repairDetail: 'Repair Detail'
  35 + },
  36 + tip: 'Tip: Please register at the business acceptance page',
  37 + edit: {
  38 + title: 'Edit',
  39 + roomName: 'Room',
  40 + objName: 'Contact',
  41 + link: 'Contact Phone',
  42 + noteType: 'Type',
  43 + Content: 'Content',
  44 + selectNoteType: 'Please select feedback type'
  45 + },
  46 + delete: {
  47 + title: 'Confirm Operation',
  48 + confirm: 'Confirm to delete notepad?'
  49 + },
  50 + addDetail: {
  51 + title: 'Follow Up',
  52 + state: 'Status',
  53 + content: 'Follow Content',
  54 + selectState: 'Please select status',
  55 + contentPlaceholder: 'Please enter follow content'
  56 + },
  57 + detail: {
  58 + title: 'Follow Progress',
  59 + index: 'No.',
  60 + createUser: 'Handler',
  61 + createTime: 'Handle Time',
  62 + content: 'Handle Content'
  63 + },
  64 + ownerRepair: {
  65 + title: 'Transfer to Repair',
  66 + repairType: 'Repair Type',
  67 + repairObjName: 'Room',
  68 + repairName: 'Repairer',
  69 + tel: 'Contact',
  70 + appointmentTime: 'Appointment Time',
  71 + context: 'Repair Content',
  72 + selectRepairType: 'Please select repair type',
  73 + repairObjNamePlaceholder: 'Please enter room',
  74 + repairNamePlaceholder: 'Please enter repairer',
  75 + telPlaceholder: 'Please enter contact',
  76 + selectTime: 'Select appointment time',
  77 + contextPlaceholder: 'Please enter repair content'
  78 + },
  79 + validate: {
  80 + noteType: 'Type cannot be empty',
  81 + title: 'Content cannot be empty',
  82 + titleMaxLength: 'Content cannot exceed 256 characters',
  83 + state: 'Status cannot be empty',
  84 + content: 'Content cannot be empty',
  85 + repairType: 'Repair type cannot be empty',
  86 + repairObjName: 'Room cannot be empty',
  87 + repairName: 'Repairer cannot be empty',
  88 + repairNameLength: 'Repairer name must be between 2-10 characters',
  89 + tel: 'Contact cannot be empty',
  90 + telFormat: 'Invalid contact format',
  91 + appointmentTime: 'Appointment time cannot be empty',
  92 + context: 'Repair content cannot be empty',
  93 + contextMaxLength: 'Repair content cannot exceed 2000 characters'
  94 + }
  95 + }
  96 + },
  97 + zh: {
  98 + notepadManage: {
  99 + search: {
  100 + title: '查询条件',
  101 + noteType: '请选择反馈类型',
  102 + roomName: '请选择房屋',
  103 + state: '请选择状态',
  104 + createUserName: '请选择登记人',
  105 + objName: '请选择业主名称'
  106 + },
  107 + list: {
  108 + title: '业主反馈'
  109 + },
  110 + table: {
  111 + noteType: '类型',
  112 + roomName: '房屋',
  113 + objName: '联系人',
  114 + link: '联系电话',
  115 + state: '状态',
  116 + createTime: '记录时间',
  117 + createUserName: '登记人',
  118 + title: '内容'
  119 + },
  120 + state: {
  121 + following: '跟进中',
  122 + completed: '完成',
  123 + transferred: '已转报修单'
  124 + },
  125 + operation: {
  126 + follow: '跟进',
  127 + progress: '进度',
  128 + transfer: '转报修单',
  129 + repairDetail: '报修详情'
  130 + },
  131 + tip: '温馨提示:请到业务受理页面登记',
  132 + edit: {
  133 + title: '修改',
  134 + roomName: '房屋',
  135 + objName: '联系人',
  136 + link: '联系电话',
  137 + noteType: '类型',
  138 + content: '内容',
  139 + selectNoteType: '请选择反馈类型'
  140 + },
  141 + delete: {
  142 + title: '请确认您的操作',
  143 + confirm: '确定删除记事薄?'
  144 + },
  145 + addDetail: {
  146 + title: '跟进',
  147 + state: '状态',
  148 + content: '跟进内容',
  149 + selectState: '请选择状态',
  150 + contentPlaceholder: '请填写跟进内容'
  151 + },
  152 + detail: {
  153 + title: '跟进进度',
  154 + index: '序号',
  155 + createUser: '处理人',
  156 + createTime: '处理时间',
  157 + content: '处理内容'
  158 + },
  159 + ownerRepair: {
  160 + title: '转报修单',
  161 + repairType: '报修类型',
  162 + repairObjName: '房屋',
  163 + repairName: '报修人',
  164 + tel: '联系方式',
  165 + appointmentTime: '预约时间',
  166 + context: '报修内容',
  167 + selectRepairType: '请选择报修类型',
  168 + repairObjNamePlaceholder: '请填写房屋',
  169 + repairNamePlaceholder: '请填写报修人',
  170 + telPlaceholder: '请填写联系方式',
  171 + selectTime: '选择预约时间',
  172 + contextPlaceholder: '请填写报修内容'
  173 + },
  174 + validate: {
  175 + noteType: '类型不能为空',
  176 + title: '内容不能为空',
  177 + titleMaxLength: '内容不能超过256个字符',
  178 + state: '状态不能为空',
  179 + content: '内容不能为空',
  180 + repairType: '报修类型不能为空',
  181 + repairObjName: '房屋不能为空',
  182 + repairName: '报修人不能为空',
  183 + repairNameLength: '报修人名称必须在2至10字符之间',
  184 + tel: '联系方式不能为空',
  185 + telFormat: '联系方式格式不正确',
  186 + appointmentTime: '预约时间不能为空',
  187 + context: '报修内容不能为空',
  188 + contextMaxLength: '报修内容不能超过2000个字符'
  189 + }
  190 + }
  191 + }
  192 +}
0 \ No newline at end of file 193 \ No newline at end of file
src/views/oa/notepadManageList.vue 0 → 100644
  1 +<template>
  2 + <div class="notepad-manage-container">
  3 + <!-- 查询条件 -->
  4 + <el-card class="search-wrapper">
  5 + <div slot="header" class="flex justify-between">
  6 + <span>{{ $t('notepadManage.search.title') }}</span>
  7 + <el-button type="text" style="float: right; padding: 3px 0" @click="_moreCondition">
  8 + {{ notepadManageInfo.moreCondition ? $t('common.hide') : $t('common.more') }}
  9 + </el-button>
  10 + </div>
  11 + <el-row :gutter="20">
  12 + <el-col :span="6">
  13 + <el-select v-model="notepadManageInfo.conditions.noteType" :placeholder="$t('notepadManage.search.noteType')"
  14 + style="width:100%">
  15 + <el-option :label="$t('common.select')" value="" />
  16 + <el-option v-for="(item, index) in notepadManageInfo.noteTypes" :key="index" :label="item.name"
  17 + :value="item.statusCd" />
  18 + </el-select>
  19 + </el-col>
  20 + <el-col :span="6">
  21 + <el-input v-model="notepadManageInfo.conditions.roomName" :placeholder="$t('notepadManage.search.roomName')" />
  22 + </el-col>
  23 + <el-col :span="6">
  24 + <el-select v-model="notepadManageInfo.conditions.state" :placeholder="$t('notepadManage.search.state')"
  25 + style="width:100%">
  26 + <el-option :label="$t('common.select')" value="" />
  27 + <el-option :label="$t('notepadManage.state.following')" value="W" />
  28 + <el-option :label="$t('notepadManage.state.completed')" value="F" />
  29 + </el-select>
  30 + </el-col>
  31 + <el-col :span="6">
  32 + <el-button type="primary" @click="_queryNotepadMethod">
  33 + <i class="el-icon-search"></i>
  34 + {{ $t('common.search') }}
  35 + </el-button>
  36 + <el-button @click="_resetNotepadMethod">
  37 + <i class="el-icon-refresh"></i>
  38 + {{ $t('common.reset') }}
  39 + </el-button>
  40 + </el-col>
  41 + </el-row>
  42 +
  43 + <el-row v-show="notepadManageInfo.moreCondition" :gutter="20" style="margin-top:20px">
  44 + <el-col :span="6">
  45 + <el-input v-model="notepadManageInfo.conditions.createUserName"
  46 + :placeholder="$t('notepadManage.search.createUserName')" />
  47 + </el-col>
  48 + <el-col :span="6">
  49 + <el-input v-model="notepadManageInfo.conditions.objName" :placeholder="$t('notepadManage.search.objName')" />
  50 + </el-col>
  51 + </el-row>
  52 + </el-card>
  53 +
  54 + <!-- 列表 -->
  55 + <el-card class="list-wrapper">
  56 + <div slot="header" class="flex justify-between">
  57 + <span>{{ $t('notepadManage.list.title') }}</span>
  58 + </div>
  59 +
  60 + <el-table v-loading="loading" :data="notepadManageInfo.notepads" border style="width: 100%">
  61 + <el-table-column prop="noteTypeName" :label="$t('notepadManage.table.noteType')" align="center" />
  62 + <el-table-column prop="roomName" :label="$t('notepadManage.table.roomName')" align="center" />
  63 + <el-table-column prop="objName" :label="$t('notepadManage.table.objName')" align="center" />
  64 + <el-table-column prop="link" :label="$t('notepadManage.table.link')" align="center" />
  65 + <el-table-column :label="$t('notepadManage.table.state')" align="center">
  66 + <template slot-scope="scope">
  67 + {{ scope.row.state === 'F' ? $t('notepadManage.state.completed') : $t('notepadManage.state.following') }}
  68 + <span v-if="scope.row.thridId">({{ $t('notepadManage.state.transferred') }})</span>
  69 + </template>
  70 + </el-table-column>
  71 + <el-table-column prop="createTime" :label="$t('notepadManage.table.createTime')" align="center" />
  72 + <el-table-column prop="createUserName" :label="$t('notepadManage.table.createUserName')" align="center" />
  73 + <el-table-column prop="title" :label="$t('notepadManage.table.title')" align="center" />
  74 + <el-table-column :label="$t('common.operation')" align="center" width="300">
  75 + <template slot-scope="scope">
  76 + <el-button v-if="scope.row.state === 'W'" size="mini" @click="_openAddNotepadDetailModal(scope.row)">
  77 + {{ $t('notepadManage.operation.follow') }}
  78 + </el-button>
  79 + <el-button size="mini" @click="_openListNotepadDetailModal(scope.row)">
  80 + {{ $t('notepadManage.operation.progress') }}
  81 + </el-button>
  82 + <el-button v-if="!scope.row.thridId" size="mini" @click="_openAddRepairModal(scope.row)">
  83 + {{ $t('notepadManage.operation.transfer') }}
  84 + </el-button>
  85 + <el-button v-if="scope.row.thridId" size="mini" @click="_toRepairDetail(scope.row)">
  86 + {{ $t('notepadManage.operation.repairDetail') }}
  87 + </el-button>
  88 + <el-button size="mini" @click="_openEditNotepadModel(scope.row)">
  89 + {{ $t('common.edit') }}
  90 + </el-button>
  91 + <el-button size="mini" type="danger" @click="_openDeleteNotepadModel(scope.row)">
  92 + {{ $t('common.delete') }}
  93 + </el-button>
  94 + </template>
  95 + </el-table-column>
  96 + </el-table>
  97 +
  98 + <div class="margin-top-xs">
  99 + <div class="tip">{{ $t('notepadManage.tip') }}</div>
  100 + <el-pagination :current-page.sync="page.current" :page-sizes="[10, 20, 30, 50]" :page-size="page.size"
  101 + :total="page.total" layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange"
  102 + @current-change="handleCurrentChange" />
  103 + </div>
  104 + </el-card>
  105 +
  106 + <!-- 组件 -->
  107 + <edit-notepad ref="editNotepad" @success="handleSuccess" />
  108 + <delete-notepad ref="deleteNotepad" @success="handleSuccess" />
  109 + <add-notepad-detail ref="addNotepadDetail" @success="handleSuccess" />
  110 + <notepad-detail ref="notepadDetail" />
  111 + <notepad-owner-repair ref="notepadOwnerRepair" @success="handleSuccess" />
  112 + </div>
  113 +</template>
  114 +
  115 +<script>
  116 +import { listNotepad } from '@/api/oa/notepadManageApi'
  117 +import { getDict } from '@/api/community/communityApi'
  118 +import { getCommunityId } from '@/api/community/communityApi'
  119 +import EditNotepad from '@/components/oa/editNotepad'
  120 +import DeleteNotepad from '@/components/oa/deleteNotepad'
  121 +import AddNotepadDetail from '@/components/oa/addNotepadDetail'
  122 +import NotepadDetail from '@/components/oa/notepadDetail'
  123 +import NotepadOwnerRepair from '@/components/oa/notepadOwnerRepair'
  124 +
  125 +export default {
  126 + name: 'NotepadManageList',
  127 + components: {
  128 + EditNotepad,
  129 + DeleteNotepad,
  130 + AddNotepadDetail,
  131 + NotepadDetail,
  132 + NotepadOwnerRepair
  133 + },
  134 + data() {
  135 + return {
  136 + loading: false,
  137 + notepadManageInfo: {
  138 + notepads: [],
  139 + total: 0,
  140 + records: 1,
  141 + moreCondition: false,
  142 + noteTypes: [],
  143 + conditions: {
  144 + noteType: '',
  145 + roomName: '',
  146 + objName: '',
  147 + createUserName: '',
  148 + state: '',
  149 + communityId: ''
  150 + }
  151 + },
  152 + page: {
  153 + current: 1,
  154 + size: 10,
  155 + total: 0
  156 + }
  157 + }
  158 + },
  159 + created() {
  160 + this.notepadManageInfo.conditions.communityId = getCommunityId()
  161 + this._initMethod()
  162 + },
  163 + methods: {
  164 + async _initMethod() {
  165 + try {
  166 + const data = await getDict('notepad', 'note_type')
  167 + this.notepadManageInfo.noteTypes = data
  168 + this._listNotepads(this.page.current, this.page.size)
  169 + } catch (error) {
  170 + console.error('获取字典数据失败:', error)
  171 + }
  172 + },
  173 + async _listNotepads(page, size) {
  174 + this.loading = true
  175 + try {
  176 + const params = {
  177 + page: page,
  178 + row: size,
  179 + ...this.notepadManageInfo.conditions
  180 + }
  181 + const { data, total } = await listNotepad(params)
  182 + this.notepadManageInfo.notepads = data
  183 + this.page.total = total
  184 + } catch (error) {
  185 + console.error('获取记事本列表失败:', error)
  186 + } finally {
  187 + this.loading = false
  188 + }
  189 + },
  190 + _openAddNotepadDetailModal(notepad) {
  191 + this.$refs.addNotepadDetail.open(notepad)
  192 + },
  193 + _openListNotepadDetailModal(notepad) {
  194 + this.$refs.notepadDetail.open(notepad)
  195 + },
  196 + _openEditNotepadModel(notepad) {
  197 + this.$refs.editNotepad.open(notepad)
  198 + },
  199 + _openDeleteNotepadModel(notepad) {
  200 + this.$refs.deleteNotepad.open(notepad)
  201 + },
  202 + _queryNotepadMethod() {
  203 + this.page.current = 1
  204 + this._listNotepads(this.page.current, this.page.size)
  205 + },
  206 + _resetNotepadMethod() {
  207 + this.notepadManageInfo.conditions = {
  208 + noteType: '',
  209 + roomName: '',
  210 + state: '',
  211 + createUserName: '',
  212 + objName: '',
  213 + communityId: this.notepadManageInfo.conditions.communityId
  214 + }
  215 + this._listNotepads(this.page.current, this.page.size)
  216 + },
  217 + _openAddRepairModal(notepad) {
  218 + this.$refs.notepadOwnerRepair.open(notepad)
  219 + },
  220 + _toRepairDetail(notepad) {
  221 + this.$router.push(`/views/work/repairDetail?repairId=${notepad.thridId}`)
  222 + },
  223 + _moreCondition() {
  224 + this.notepadManageInfo.moreCondition = !this.notepadManageInfo.moreCondition
  225 + },
  226 + handleSizeChange(val) {
  227 + this.page.size = val
  228 + this._listNotepads(this.page.current, this.page.size)
  229 + },
  230 + handleCurrentChange(val) {
  231 + this.page.current = val
  232 + this._listNotepads(this.page.current, this.page.size)
  233 + },
  234 + handleSuccess() {
  235 + this._listNotepads(this.page.current, this.page.size)
  236 + }
  237 + }
  238 +}
  239 +</script>
  240 +
  241 +<style lang="scss" scoped>
  242 +.notepad-manage-container {
  243 + padding: 20px;
  244 +
  245 + .search-wrapper {
  246 + margin-bottom: 20px;
  247 + }
  248 +
  249 + .list-wrapper {
  250 + margin-bottom: 20px;
  251 + }
  252 +
  253 + .margin-top-xs {
  254 + margin-top: 20px;
  255 + display: flex;
  256 + justify-content: space-between;
  257 + align-items: center;
  258 +
  259 + .tip {
  260 + color: #999;
  261 + font-size: 14px;
  262 + }
  263 + }
  264 +}
  265 +</style>
0 \ No newline at end of file 266 \ No newline at end of file
src/views/oa/printQuestionAnswerDetailLang.js 0 → 100644
  1 +export const messages = {
  2 + en: {
  3 + printQuestionAnswerDetail: {
  4 + table: {
  5 + title: 'Title',
  6 + type: 'Type',
  7 + options: 'Options',
  8 + choices: 'Choices'
  9 + },
  10 + singleChoice: 'Single Choice',
  11 + multipleChoice: 'Multiple Choice',
  12 + shortAnswer: 'Short Answer',
  13 + total: 'Total',
  14 + submitted: 'Submitted',
  15 + people: 'people',
  16 + print: 'Print',
  17 + cancel: 'Cancel'
  18 + }
  19 + },
  20 + zh: {
  21 + printQuestionAnswerDetail: {
  22 + table: {
  23 + title: '名称',
  24 + type: '类型',
  25 + options: '选项',
  26 + choices: '选择'
  27 + },
  28 + singleChoice: '单选',
  29 + multipleChoice: '多选',
  30 + shortAnswer: '简答',
  31 + total: '总数',
  32 + submitted: '已提交',
  33 + people: '人',
  34 + print: '打印',
  35 + cancel: '取消'
  36 + }
  37 + }
  38 +}
0 \ No newline at end of file 39 \ No newline at end of file
src/views/oa/printQuestionAnswerDetailList.vue 0 → 100644
  1 +<template>
  2 + <div class="print-question-answer-detail-container">
  3 + <el-row :gutter="20">
  4 + <el-col :span="4" id="print-room">
  5 + <el-card class="tree-card">
  6 + <ul class="room-list">
  7 + <li v-for="(item, index) in printQuestionAnswerDetailInfo.userQuestions" :key="index"
  8 + @click="swatchRoom(item)" :class="{ 'selected-room': printQuestionAnswerDetailInfo.roomId == item.roomId }">
  9 + {{ item.roomName }}
  10 + </li>
  11 + </ul>
  12 + </el-card>
  13 + </el-col>
  14 + <el-col :span="20">
  15 + <div class="text-center">
  16 + <h1>{{ printQuestionAnswerDetailInfo.qaName }}</h1>
  17 + </div>
  18 + <div v-html="printQuestionAnswerDetailInfo.content"></div>
  19 +
  20 + <el-table :data="printQuestionAnswerDetailInfo.questionTitles" border style="width: 100%" class="margin-top">
  21 + <el-table-column prop="qaTitle" :label="$t('printQuestionAnswerDetail.table.title')" width="300" align="center">
  22 + <template slot-scope="scope">
  23 + <div style="max-width: 300px;">
  24 + {{ scope.row.qaTitle }}
  25 + </div>
  26 + </template>
  27 + </el-table-column>
  28 + <el-table-column prop="titleType" :label="$t('printQuestionAnswerDetail.table.type')" width="300"
  29 + align="center">
  30 + <template slot-scope="scope">
  31 + {{ _getTitleTypeName(scope.row.titleType) }}
  32 + </template>
  33 + </el-table-column>
  34 + <el-table-column :label="$t('printQuestionAnswerDetail.table.options')" align="center">
  35 + <template slot-scope="scope">
  36 + <div v-for="(item, index) in scope.row.titleValues" :key="index">
  37 + {{ item.seq }}、{{ item.qaValue }}
  38 + </div>
  39 + </template>
  40 + </el-table-column>
  41 + <el-table-column :label="$t('printQuestionAnswerDetail.table.choices')" align="center">
  42 + <template slot-scope="scope">
  43 + <div v-for="(item, index) in scope.row.titleValues" :key="index">
  44 + {{ _getChooseValue(item) }}
  45 + </div>
  46 + </template>
  47 + </el-table-column>
  48 + </el-table>
  49 +
  50 + <div class="margin-top">
  51 + <span>{{ $t('printQuestionAnswerDetail.total') }}:{{ printQuestionAnswerDetailInfo.voteCount }}</span>;
  52 + <span>{{ $t('printQuestionAnswerDetail.submitted') }}:{{ printQuestionAnswerDetailInfo.votedCount }}</span>;
  53 + <span v-for="(item, tIndex) in printQuestionAnswerDetailInfo.titleValues" :key="tIndex">
  54 + {{ item.qaValue }}: {{ item.personCount }}{{ $t('printQuestionAnswerDetail.people') }};
  55 + </span>
  56 + </div>
  57 +
  58 + <div class="text-right margin-top">
  59 + {{ currentCommunity.name }}
  60 + </div>
  61 + <div class="text-right margin-top-sm">
  62 + {{ currentDate }}
  63 + </div>
  64 + </el-col>
  65 + </el-row>
  66 +
  67 + <div id="print-btn" class="margin-top">
  68 + <el-button type="primary" class="float-right" @click="_printPurchaseApplyDiv">
  69 + <i class="el-icon-printer"></i>&nbsp;{{ $t('printQuestionAnswerDetail.print') }}
  70 + </el-button>
  71 + <el-button type="warning" class="float-right margin-right" @click="_closePage">
  72 + <i class="el-icon-close"></i>
  73 + {{ $t('printQuestionAnswerDetail.cancel') }}
  74 + </el-button>
  75 + </div>
  76 + </div>
  77 +</template>
  78 +
  79 +<script>
  80 +import { getCommunityId ,getCommunity} from '@/api/community/communityApi'
  81 +import {
  82 + listQuestionAnswer,
  83 + listQuestionTitle,
  84 + listUserQuestionAnswer
  85 +} from '@/api/oa/printQuestionAnswerDetailApi'
  86 +
  87 +export default {
  88 + name: 'PrintQuestionAnswerDetailList',
  89 + data() {
  90 + return {
  91 + printQuestionAnswerDetailInfo: {
  92 + qaName: '',
  93 + startTime: '',
  94 + endTime: '',
  95 + communityId: '',
  96 + content: '',
  97 + titleType: '',
  98 + questionTitles: [],
  99 + updateRoomIds: false,
  100 + voteCount: 0,
  101 + qaId: '',
  102 + votedCount: 0,
  103 + userQuestions: [],
  104 + roomId: '',
  105 + userQuestion: {}
  106 + },
  107 + currentCommunity: {},
  108 + currentDate: new Date().toLocaleDateString()
  109 + }
  110 + },
  111 + created() {
  112 + this.printQuestionAnswerDetailInfo.communityId = getCommunityId()
  113 + this.currentCommunity = getCommunity()
  114 + this.printQuestionAnswerDetailInfo.qaId = this.$route.query.qaId
  115 + this._listValues()
  116 + },
  117 + methods: {
  118 + async _listQuestionAnswers() {
  119 + try {
  120 + const params = {
  121 + page: 1,
  122 + row: 1,
  123 + communityId: this.printQuestionAnswerDetailInfo.communityId,
  124 + qaId: this.printQuestionAnswerDetailInfo.qaId
  125 + }
  126 + const { data } = await listQuestionAnswer(params)
  127 + Object.assign(this.printQuestionAnswerDetailInfo, data[0])
  128 + } catch (error) {
  129 + console.error('Failed to load question answers:', error)
  130 + }
  131 + },
  132 + async _loadQuestionTitles() {
  133 + try {
  134 + const params = {
  135 + page: 1,
  136 + row: 100,
  137 + communityId: this.printQuestionAnswerDetailInfo.communityId,
  138 + qaId: this.printQuestionAnswerDetailInfo.qaId
  139 + }
  140 + const { data } = await listQuestionTitle(params)
  141 + this.printQuestionAnswerDetailInfo.questionTitles = data
  142 + } catch (error) {
  143 + console.error('Failed to load question titles:', error)
  144 + }
  145 + },
  146 + _printPurchaseApplyDiv() {
  147 + this.printFlag = '1'
  148 + document.getElementById("print-btn").style.display = "none"
  149 + document.getElementById("print-room").style.display = "none"
  150 + window.print()
  151 + window.opener = null
  152 + window.close()
  153 + },
  154 + _closePage() {
  155 + window.opener = null
  156 + window.close()
  157 + },
  158 + _getTitleTypeName(_titleType) {
  159 + if (_titleType === '1001') {
  160 + return this.$t('printQuestionAnswerDetail.singleChoice')
  161 + } else if (_titleType === '2002') {
  162 + return this.$t('printQuestionAnswerDetail.multipleChoice')
  163 + } else {
  164 + return this.$t('printQuestionAnswerDetail.shortAnswer')
  165 + }
  166 + },
  167 + swatchRoom(_room) {
  168 + this.printQuestionAnswerDetailInfo.roomId = _room.roomId
  169 + this.printQuestionAnswerDetailInfo.userQuestion = _room
  170 + },
  171 + async _listValues() {
  172 + try {
  173 + const params = {
  174 + page: 1,
  175 + row: 500,
  176 + communityId: this.printQuestionAnswerDetailInfo.communityId,
  177 + qaId: this.printQuestionAnswerDetailInfo.qaId
  178 + }
  179 + const { data } = await listUserQuestionAnswer(params)
  180 + this.printQuestionAnswerDetailInfo.userQuestions = data
  181 + await this._listQuestionAnswers()
  182 + await this._loadQuestionTitles()
  183 + if (data && data.length > 0) {
  184 + this.swatchRoom(data[0])
  185 + }
  186 + } catch (error) {
  187 + console.error('Failed to load user question answers:', error)
  188 + }
  189 + },
  190 + _getChooseValue(_title) {
  191 + const _userQuestion = this.printQuestionAnswerDetailInfo.userQuestion
  192 + let _chooseValue = 'X'
  193 + if (_userQuestion['values']) {
  194 + _userQuestion.values.forEach(tmpValue => {
  195 + if (tmpValue.qaValue === _title.qaValue) {
  196 + _chooseValue = 'V'
  197 + }
  198 + })
  199 + }
  200 + return _chooseValue
  201 + }
  202 + }
  203 +}
  204 +</script>
  205 +
  206 +<style lang="scss" scoped>
  207 +.print-question-answer-detail-container {
  208 + padding: 20px;
  209 +
  210 + .tree-card {
  211 + height: 100%;
  212 +
  213 + .room-list {
  214 + list-style: none;
  215 + padding: 0;
  216 + margin: 0;
  217 +
  218 + li {
  219 + padding: 10px;
  220 + margin-bottom: 5px;
  221 + cursor: pointer;
  222 + border-radius: 4px;
  223 + text-align: center;
  224 + transition: all 0.3s;
  225 +
  226 + &:hover {
  227 + background-color: #f5f7fa;
  228 + }
  229 +
  230 + &.selected-room {
  231 + background-color: #409eff;
  232 + color: white;
  233 + }
  234 + }
  235 + }
  236 + }
  237 +
  238 + .margin-top {
  239 + margin-top: 20px;
  240 + }
  241 +
  242 + .margin-top-sm {
  243 + margin-top: 10px;
  244 + }
  245 +
  246 + .margin-right {
  247 + margin-right: 20px;
  248 + }
  249 +
  250 + .float-right {
  251 + float: right;
  252 + }
  253 +
  254 + .text-center {
  255 + text-align: center;
  256 + }
  257 +
  258 + .text-right {
  259 + text-align: right;
  260 + }
  261 +}
  262 +
  263 +#print-btn,
  264 +#print-room {
  265 + @media print {
  266 + display: none !important;
  267 + }
  268 +}
  269 +</style>
0 \ No newline at end of file 270 \ No newline at end of file
src/views/oa/printQuestionAnswerLang.js 0 → 100644
  1 +export const messages = {
  2 + en: {
  3 + printQuestionAnswer: {
  4 + table: {
  5 + name: 'Name',
  6 + type: 'Type',
  7 + options: 'Options',
  8 + selectCount: 'Select Count'
  9 + },
  10 + type: {
  11 + single: 'Single Choice',
  12 + multiple: 'Multiple Choice',
  13 + shortAnswer: 'Short Answer'
  14 + },
  15 + label: {
  16 + total: 'Total',
  17 + submitted: 'Submitted'
  18 + },
  19 + unit: {
  20 + person: 'person(s)'
  21 + }
  22 + }
  23 + },
  24 + zh: {
  25 + printQuestionAnswer: {
  26 + table: {
  27 + name: '名称',
  28 + type: '类型',
  29 + options: '选项',
  30 + selectCount: '选择人数'
  31 + },
  32 + type: {
  33 + single: '单选',
  34 + multiple: '多选',
  35 + shortAnswer: '简答'
  36 + },
  37 + label: {
  38 + total: '总数',
  39 + submitted: '已提交'
  40 + },
  41 + unit: {
  42 + person: '人'
  43 + }
  44 + }
  45 + }
  46 +}
0 \ No newline at end of file 47 \ No newline at end of file
src/views/oa/printQuestionAnswerList.vue 0 → 100644
  1 +<template>
  2 + <div class="print-question-answer-container">
  3 + <el-card>
  4 + <div class="text-center">
  5 + <h1>{{ printQuestionAnswerInfo.qaName }}</h1>
  6 + </div>
  7 + <div v-html="printQuestionAnswerInfo.content"></div>
  8 +
  9 + <el-table :data="printQuestionAnswerInfo.questionTitles" border style="width: 100%" class="margin-top">
  10 + <el-table-column prop="qaTitle" :label="$t('printQuestionAnswer.table.name')" width="300" align="center">
  11 + <template slot-scope="{row}">
  12 + <div style="max-width: 300px;">
  13 + {{ row.qaTitle }}
  14 + </div>
  15 + </template>
  16 + </el-table-column>
  17 + <el-table-column prop="titleType" :label="$t('printQuestionAnswer.table.type')" width="300" align="center">
  18 + <template slot-scope="{row}">
  19 + {{ _getTitleTypeName(row.titleType) }}
  20 + </template>
  21 + </el-table-column>
  22 + <el-table-column :label="$t('printQuestionAnswer.table.options')" align="center">
  23 + <template slot-scope="{row}">
  24 + <div v-for="(item, index) in row.titleValues" :key="index">
  25 + {{ item.seq }}、{{ item.qaValue }}
  26 + </div>
  27 + </template>
  28 + </el-table-column>
  29 + <el-table-column :label="$t('printQuestionAnswer.table.selectCount')" align="center">
  30 + <template slot-scope="{row}">
  31 + <div v-for="(item, index) in row.titleValues" :key="index">
  32 + {{ item.personCount }} {{ $t('printQuestionAnswer.unit.person') }}
  33 + </div>
  34 + </template>
  35 + </el-table-column>
  36 + </el-table>
  37 +
  38 + <div class="margin-top">
  39 + <span>{{ $t('printQuestionAnswer.label.total') }}:{{ printQuestionAnswerInfo.voteCount }}</span>;
  40 + <span>{{ $t('printQuestionAnswer.label.submitted') }}:{{ printQuestionAnswerInfo.votedCount }}</span>;
  41 + <span v-for="(item, tIndex) in printQuestionAnswerInfo.titleValues" :key="tIndex">
  42 + {{ item.qaValue }}: {{ item.personCount }}{{ $t('printQuestionAnswer.unit.person') }};
  43 + </span>
  44 + </div>
  45 +
  46 + <div class="text-right margin-top margin-right">
  47 + {{ currentCommunity.name }}
  48 + </div>
  49 + <div class="text-right margin-top-sm margin-right">
  50 + {{ dateFormat(new Date()) }}
  51 + </div>
  52 +
  53 + <div id="print-btn" class="text-right margin-top">
  54 + <el-button type="primary" @click="_printPurchaseApplyDiv">
  55 + <i class="el-icon-printer"></i>&nbsp;{{ $t('common.print') }}
  56 + </el-button>
  57 + <el-button type="warning" style="margin-left: 20px;" @click="_closePage">
  58 + <i class="el-icon-close"></i>
  59 + {{ $t('common.cancel') }}
  60 + </el-button>
  61 + </div>
  62 + </el-card>
  63 + </div>
  64 +</template>
  65 +
  66 +<script>
  67 +import { getCommunityId } from '@/api/community/communityApi'
  68 +import {
  69 + listQuestionAnswer,
  70 + listQuestionTitle
  71 +} from '@/api/oa/printQuestionAnswerApi'
  72 +import { dateFormat } from '@/utils/dateUtil'
  73 +
  74 +export default {
  75 + name: 'PrintQuestionAnswerList',
  76 + data() {
  77 + return {
  78 + printQuestionAnswerInfo: {
  79 + qaName: '',
  80 + startTime: '',
  81 + endTime: '',
  82 + communityId: '',
  83 + content: '',
  84 + titleType: '',
  85 + questionTitles: [],
  86 + updateRoomIds: false,
  87 + voteCount: 0,
  88 + qaId: '',
  89 + votedCount: 0
  90 + },
  91 + currentCommunity: {
  92 + name: '',
  93 + communityId: ''
  94 + }
  95 + }
  96 + },
  97 + created() {
  98 + this.printQuestionAnswerInfo.qaId = this.$route.query.qaId
  99 + this.currentCommunity.communityId = getCommunityId()
  100 + this._listQuestionAnswers()
  101 + this._loadQuestionTitles()
  102 + },
  103 + methods: {
  104 + async _listQuestionAnswers() {
  105 + try {
  106 + const params = {
  107 + page: 1,
  108 + row: 1,
  109 + communityId: this.currentCommunity.communityId,
  110 + qaId: this.printQuestionAnswerInfo.qaId
  111 + }
  112 + const { data } = await listQuestionAnswer(params)
  113 + this.printQuestionAnswerInfo = Object.assign(
  114 + this.printQuestionAnswerInfo,
  115 + data[0]
  116 + )
  117 + } catch (error) {
  118 + console.error('Failed to load question answers:', error)
  119 + }
  120 + },
  121 + async _loadQuestionTitles() {
  122 + try {
  123 + const params = {
  124 + page: 1,
  125 + row: 100,
  126 + communityId: this.currentCommunity.communityId,
  127 + qaId: this.printQuestionAnswerInfo.qaId
  128 + }
  129 + const { data } = await listQuestionTitle(params)
  130 + this.printQuestionAnswerInfo.questionTitles = data
  131 + } catch (error) {
  132 + console.error('Failed to load question titles:', error)
  133 + }
  134 + },
  135 + _printPurchaseApplyDiv() {
  136 + this.printFlag = '1'
  137 + document.getElementById("print-btn").style.display = "none"
  138 + window.print()
  139 + window.opener = null
  140 + window.close()
  141 + },
  142 + _closePage() {
  143 + window.opener = null
  144 + window.close()
  145 + },
  146 + _getTitleTypeName(_titleType) {
  147 + if (_titleType == '1001') {
  148 + return this.$t('printQuestionAnswer.type.single')
  149 + } else if (_titleType == '2002') {
  150 + return this.$t('printQuestionAnswer.type.multiple')
  151 + } else {
  152 + return this.$t('printQuestionAnswer.type.shortAnswer')
  153 + }
  154 + },
  155 + dateFormat
  156 + }
  157 +}
  158 +</script>
  159 +
  160 +<style lang="scss" scoped>
  161 +.print-question-answer-container {
  162 + padding: 20px;
  163 +
  164 + .margin-top {
  165 + margin-top: 20px;
  166 + }
  167 +
  168 + .margin-top-sm {
  169 + margin-top: 10px;
  170 + }
  171 +
  172 + .margin-right {
  173 + margin-right: 20px;
  174 + }
  175 +
  176 + .text-center {
  177 + text-align: center;
  178 + }
  179 +
  180 + .text-right {
  181 + text-align: right;
  182 + }
  183 +}
  184 +</style>
0 \ No newline at end of file 185 \ No newline at end of file
src/views/oa/questionAnswerManageLang.js 0 → 100644
  1 +export const messages = {
  2 + en: {
  3 + questionAnswerManage: {
  4 + search: {
  5 + title: 'Search Conditions',
  6 + qaName: 'Please enter questionnaire name'
  7 + },
  8 + list: {
  9 + title: 'Questionnaire Survey'
  10 + },
  11 + table: {
  12 + qaName: 'Questionnaire Name',
  13 + validity: 'Validity Period',
  14 + voteCount: 'Total/Completed',
  15 + score: 'Score',
  16 + state: 'Status',
  17 + createTime: 'Create Time'
  18 + },
  19 + state: {
  20 + published: 'Published',
  21 + unpublished: 'Unpublished'
  22 + },
  23 + button: {
  24 + result: 'Result',
  25 + detail: 'Detail',
  26 + publish: 'Publish'
  27 + },
  28 + edit: {
  29 + title: 'Edit Questionnaire',
  30 + qaType: 'Questionnaire Type',
  31 + qaTypePlaceholder: 'Required, please select questionnaire type',
  32 + qaType1: 'Owner Questionnaire',
  33 + qaType3: 'Owner Voting',
  34 + qaType4: 'Staff Voting',
  35 + qaName: 'Name',
  36 + qaNamePlaceholder: 'Required, please enter name',
  37 + startTime: 'Start Time',
  38 + startTimePlaceholder: 'Required, please enter start time',
  39 + endTime: 'End Time',
  40 + endTimePlaceholder: 'Required, please enter end time',
  41 + content: 'Content',
  42 + contentPlaceholder: 'Optional, please enter content',
  43 + remark: 'Remark',
  44 + remarkPlaceholder: 'Optional, please enter remark',
  45 + photos: 'Photos'
  46 + },
  47 + delete: {
  48 + title: 'Confirm Operation',
  49 + confirmText: 'Are you sure to delete this questionnaire?'
  50 + },
  51 + publish: {
  52 + title: 'Publish Questionnaire',
  53 + qaName: 'Name',
  54 + tip: 'Reminder',
  55 + tipText: 'After publishing, it cannot be modified again. Please confirm before publishing!',
  56 + notifyWay: 'Notification Method',
  57 + notifyWayPlaceholder: 'Required, please select notification method',
  58 + notifyWay1: 'SMS',
  59 + notifyWay2: 'WeChat Message',
  60 + notifyWay3: 'No Notification',
  61 + button: 'Publish'
  62 + },
  63 + validate: {
  64 + qaType: 'Questionnaire type is required',
  65 + qaName: 'Questionnaire name is required',
  66 + qaNameMax: 'Questionnaire name is too long',
  67 + startTime: 'Start time is required',
  68 + endTime: 'End time is required',
  69 + notifyWay: 'Notification method is required'
  70 + },
  71 + message: {
  72 + updateSuccess: 'Update successfully',
  73 + updateError: 'Update failed',
  74 + deleteSuccess: 'Delete successfully',
  75 + deleteError: 'Delete failed',
  76 + publishSuccess: 'Publish successfully',
  77 + publishError: 'Publish failed',
  78 + fetchError: 'Failed to fetch data'
  79 + }
  80 + },
  81 + uploadImage: {
  82 + validate: {
  83 + imageType: 'Only image files are allowed',
  84 + imageSize: 'Image size cannot exceed 2MB'
  85 + },
  86 + message: {
  87 + uploadError: 'Image upload failed'
  88 + }
  89 + }
  90 + },
  91 + zh: {
  92 + questionAnswerManage: {
  93 + search: {
  94 + title: '查询条件',
  95 + qaName: '请输入问卷名称'
  96 + },
  97 + list: {
  98 + title: '问卷调研'
  99 + },
  100 + table: {
  101 + qaName: '问卷名称',
  102 + validity: '有效期',
  103 + voteCount: '总问卷/已问卷',
  104 + score: '得分',
  105 + state: '状态',
  106 + createTime: '创建时间'
  107 + },
  108 + state: {
  109 + published: '已发布',
  110 + unpublished: '待发布'
  111 + },
  112 + button: {
  113 + result: '结果',
  114 + detail: '明细',
  115 + publish: '发布'
  116 + },
  117 + edit: {
  118 + title: '修改问卷信息',
  119 + qaType: '问卷类型',
  120 + qaTypePlaceholder: '必填,请选择问卷类型',
  121 + qaType1: '业主问卷',
  122 + qaType3: '业主投票',
  123 + qaType4: '员工投票',
  124 + qaName: '名称',
  125 + qaNamePlaceholder: '必填,请填写名称',
  126 + startTime: '开始时间',
  127 + startTimePlaceholder: '必填,请填写开始时间',
  128 + endTime: '结束时间',
  129 + endTimePlaceholder: '必填,请填写结束时间',
  130 + content: '内容',
  131 + contentPlaceholder: '选填,请填写内容',
  132 + remark: '备注',
  133 + remarkPlaceholder: '选填,请填写备注',
  134 + photos: '图片'
  135 + },
  136 + delete: {
  137 + title: '请确认您的操作',
  138 + confirmText: '确定删除问卷信息'
  139 + },
  140 + publish: {
  141 + title: '发布',
  142 + qaName: '名称',
  143 + tip: '温馨提示',
  144 + tipText: '发布后将无法再次修改,请确认无误后再发布!',
  145 + notifyWay: '通知方式',
  146 + notifyWayPlaceholder: '必填,请选择通知方式',
  147 + notifyWay1: '短信',
  148 + notifyWay2: '微信消息',
  149 + notifyWay3: '不通知',
  150 + button: '发布'
  151 + },
  152 + validate: {
  153 + qaType: '问卷类型不能为空',
  154 + qaName: '问卷名称不能为空',
  155 + qaNameMax: '问卷名称太长',
  156 + startTime: '开始时间不能为空',
  157 + endTime: '结束时间不能为空',
  158 + notifyWay: '通知方式不能为空'
  159 + },
  160 + message: {
  161 + updateSuccess: '修改成功',
  162 + updateError: '修改失败',
  163 + deleteSuccess: '删除成功',
  164 + deleteError: '删除失败',
  165 + publishSuccess: '发布成功',
  166 + publishError: '发布失败',
  167 + fetchError: '获取数据失败'
  168 + }
  169 + },
  170 + uploadImage: {
  171 + validate: {
  172 + imageType: '只能上传图片文件',
  173 + imageSize: '图片大小不能超过2MB'
  174 + },
  175 + message: {
  176 + uploadError: '图片上传失败'
  177 + }
  178 + }
  179 + }
  180 +}
0 \ No newline at end of file 181 \ No newline at end of file
src/views/oa/questionAnswerManageList.vue 0 → 100644
  1 +<template>
  2 + <div class="question-answer-manage-container">
  3 + <!-- 查询条件 -->
  4 + <el-card class="search-wrapper">
  5 + <div slot="header" class="flex justify-between">
  6 + <span>{{ $t('questionAnswerManage.search.title') }}</span>
  7 + </div>
  8 + <el-row :gutter="20">
  9 + <el-col :span="8">
  10 + <el-input v-model="searchForm.qaName" :placeholder="$t('questionAnswerManage.search.qaName')" clearable
  11 + @keyup.enter.native="handleSearch" />
  12 + </el-col>
  13 + <el-col :span="8">
  14 + <el-button type="primary" @click="handleSearch">
  15 + <i class="el-icon-search"></i>
  16 + {{ $t('common.search') }}
  17 + </el-button>
  18 + <el-button @click="handleReset">
  19 + <i class="el-icon-refresh"></i>
  20 + {{ $t('common.reset') }}
  21 + </el-button>
  22 + </el-col>
  23 + </el-row>
  24 + </el-card>
  25 +
  26 + <!-- 问卷列表 -->
  27 + <el-card class="list-wrapper">
  28 + <div slot="header" class="flex justify-between">
  29 + <span>{{ $t('questionAnswerManage.list.title') }}</span>
  30 + <el-button type="primary" size="small" style="float: right;" @click="handleAdd">
  31 + <i class="el-icon-plus"></i>
  32 + {{ $t('common.add') }}
  33 + </el-button>
  34 + </div>
  35 +
  36 + <el-table v-loading="loading" :data="tableData" border style="width: 100%">
  37 + <el-table-column prop="qaName" :label="$t('questionAnswerManage.table.qaName')" align="center" />
  38 + <el-table-column :label="$t('questionAnswerManage.table.validity')" align="center">
  39 + <template slot-scope="scope">
  40 + <div>{{ scope.row.startTime }}</div>
  41 + <div>{{ scope.row.endTime }}</div>
  42 + </template>
  43 + </el-table-column>
  44 + <el-table-column :label="$t('questionAnswerManage.table.voteCount')" align="center">
  45 + <template slot-scope="scope">
  46 + {{ scope.row.voteCount }}/{{ scope.row.votedCount }}
  47 + </template>
  48 + </el-table-column>
  49 + <el-table-column prop="score" :label="$t('questionAnswerManage.table.score')" align="center" />
  50 + <el-table-column :label="$t('questionAnswerManage.table.state')" align="center">
  51 + <template slot-scope="scope">
  52 + {{ scope.row.state === 'C' ? $t('questionAnswerManage.state.published') :
  53 + $t('questionAnswerManage.state.unpublished') }}
  54 + </template>
  55 + </el-table-column>
  56 + <el-table-column prop="createTime" :label="$t('questionAnswerManage.table.createTime')" align="center" />
  57 + <el-table-column :label="$t('common.operation')" align="center" width="300">
  58 + <template slot-scope="scope">
  59 + <el-button size="mini" @click="handleResult(scope.row)">
  60 + {{ $t('questionAnswerManage.button.result') }}
  61 + </el-button>
  62 + <el-button size="mini" @click="handleDetail(scope.row)">
  63 + {{ $t('questionAnswerManage.button.detail') }}
  64 + </el-button>
  65 + <el-button v-if="scope.row.state === 'W'" size="mini" type="success" @click="handlePublish(scope.row)">
  66 + {{ $t('questionAnswerManage.button.publish') }}
  67 + </el-button>
  68 + <el-button size="mini" type="primary" @click="handleEdit(scope.row)">
  69 + {{ $t('common.edit') }}
  70 + </el-button>
  71 + <el-button size="mini" type="danger" @click="handleDelete(scope.row)">
  72 + {{ $t('common.delete') }}
  73 + </el-button>
  74 + </template>
  75 + </el-table-column>
  76 + </el-table>
  77 +
  78 + <el-pagination :current-page.sync="page.current" :page-sizes="[10, 20, 30, 50]" :page-size="page.size"
  79 + :total="page.total" layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange"
  80 + @current-change="handleCurrentChange" />
  81 + </el-card>
  82 +
  83 + <!-- 组件 -->
  84 + <delete-question-answer ref="deleteDialog" @success="fetchData" />
  85 + <publish-question-answer ref="publishDialog" @success="fetchData" />
  86 + </div>
  87 +</template>
  88 +
  89 +<script>
  90 +import { listQuestionAnswer } from '@/api/oa/questionAnswerManageApi'
  91 +import DeleteQuestionAnswer from '@/components/oa/deleteQuestionAnswer'
  92 +import PublishQuestionAnswer from '@/components/oa/publishQuestionAnswer'
  93 +
  94 +export default {
  95 + name: 'QuestionAnswerManageList',
  96 + components: {
  97 + DeleteQuestionAnswer,
  98 + PublishQuestionAnswer
  99 + },
  100 + data() {
  101 + return {
  102 + loading: false,
  103 + searchForm: {
  104 + qaName: '',
  105 + qaType: ''
  106 + },
  107 + tableData: [],
  108 + page: {
  109 + current: 1,
  110 + size: 10,
  111 + total: 0
  112 + }
  113 + }
  114 + },
  115 + created() {
  116 + this.fetchData()
  117 + },
  118 + methods: {
  119 + async fetchData() {
  120 + try {
  121 + this.loading = true
  122 + const params = {
  123 + page: this.page.current,
  124 + row: this.page.size,
  125 + qaName: this.searchForm.qaName,
  126 + qaType: this.searchForm.qaType
  127 + }
  128 + const { data, total } = await listQuestionAnswer(params)
  129 + this.tableData = data
  130 + this.page.total = total
  131 + } catch (error) {
  132 + this.$message.error(this.$t('questionAnswerManage.fetchError'))
  133 + } finally {
  134 + this.loading = false
  135 + }
  136 + },
  137 + handleSearch() {
  138 + this.page.current = 1
  139 + this.fetchData()
  140 + },
  141 + handleReset() {
  142 + this.searchForm = {
  143 + qaName: '',
  144 + qaType: ''
  145 + }
  146 + this.handleSearch()
  147 + },
  148 + handleAdd() {
  149 + this.$router.push('/views/oa/addQuestionAnswer')
  150 + },
  151 + handleEdit(row) {
  152 + this.$router.push(`/views/oa/editQuestionAnswer?qaId=${row.qaId}`)
  153 + },
  154 + handleDelete(row) {
  155 + this.$refs.deleteDialog.open(row)
  156 + },
  157 + handlePublish(row) {
  158 + this.$refs.publishDialog.open(row)
  159 + },
  160 + handleResult(row) {
  161 + window.open(`/#/pages/question/printQuestionAnswer?qaId=${row.qaId}`)
  162 + },
  163 + handleDetail(row) {
  164 + window.open(`/#/pages/question/printQuestionAnswerDetail?qaId=${row.qaId}`)
  165 + },
  166 + handleSizeChange(val) {
  167 + this.page.size = val
  168 + this.fetchData()
  169 + },
  170 + handleCurrentChange(val) {
  171 + this.page.current = val
  172 + this.fetchData()
  173 + }
  174 + }
  175 +}
  176 +</script>
  177 +
  178 +<style lang="scss" scoped>
  179 +.question-answer-manage-container {
  180 + padding: 20px;
  181 +
  182 + .search-wrapper {
  183 + margin-bottom: 20px;
  184 +
  185 + .el-row {
  186 + margin-bottom: 20px;
  187 + }
  188 + }
  189 +
  190 + .list-wrapper {
  191 + margin-bottom: 20px;
  192 + }
  193 +
  194 + .el-pagination {
  195 + margin-top: 20px;
  196 + text-align: right;
  197 + }
  198 +}
  199 +</style>
0 \ No newline at end of file 200 \ No newline at end of file
src/views/oa/questionTitleLang.js 0 → 100644
  1 +export const messages = {
  2 + en: {
  3 + questionTitle: {
  4 + search: {
  5 + title: 'Search Conditions',
  6 + itemTitlePlaceholder: 'Please enter question name',
  7 + titleTypePlaceholder: 'Please select question type',
  8 + pleaseSelect: 'Please select',
  9 + singleChoice: 'Single Choice',
  10 + multipleChoice: 'Multiple Choice',
  11 + shortAnswer: 'Short Answer'
  12 + },
  13 + list: {
  14 + title: 'Questionnaire Questions'
  15 + },
  16 + table: {
  17 + name: 'Name',
  18 + type: 'Type',
  19 + options: 'Options',
  20 + createTime: 'Create Time'
  21 + },
  22 + types: {
  23 + singleChoice: 'Single Choice',
  24 + multipleChoice: 'Multiple Choice',
  25 + shortAnswer: 'Short Answer'
  26 + },
  27 + add: {
  28 + title: 'Add Question',
  29 + name: 'Name',
  30 + namePlaceholder: 'Required, please fill in the name',
  31 + nameRequired: 'Name cannot be empty',
  32 + nameTooLong: 'Name is too long',
  33 + type: 'Type',
  34 + typePlaceholder: 'Required, please select type',
  35 + typeRequired: 'Type cannot be empty',
  36 + option: 'Option',
  37 + optionPlaceholder: 'Required, please fill in the option content',
  38 + optionRequired: 'Option content cannot be empty',
  39 + addOption: 'Add Option',
  40 + removeOption: 'Remove Option',
  41 + pleaseSelect: 'Please select',
  42 + success: 'Added successfully'
  43 + },
  44 + edit: {
  45 + title: 'Edit Question',
  46 + name: 'Name',
  47 + namePlaceholder: 'Required, please fill in the name',
  48 + nameRequired: 'Name cannot be empty',
  49 + nameTooLong: 'Name is too long',
  50 + type: 'Type',
  51 + typePlaceholder: 'Required, please select type',
  52 + typeRequired: 'Type cannot be empty',
  53 + option: 'Option',
  54 + optionPlaceholder: 'Required, please fill in the option content',
  55 + optionRequired: 'Option content cannot be empty',
  56 + addOption: 'Add Option',
  57 + removeOption: 'Remove Option',
  58 + pleaseSelect: 'Please select',
  59 + success: 'Updated successfully'
  60 + },
  61 + delete: {
  62 + title: 'Confirm Operation',
  63 + confirmMessage: 'Are you sure to delete this question?',
  64 + success: 'Deleted successfully'
  65 + },
  66 + fetchError: 'Failed to fetch question data'
  67 + }
  68 + },
  69 + zh: {
  70 + questionTitle: {
  71 + search: {
  72 + title: '查询条件',
  73 + itemTitlePlaceholder: '请输入问题名称',
  74 + titleTypePlaceholder: '请选择题目类型',
  75 + pleaseSelect: '请选择',
  76 + singleChoice: '单选',
  77 + multipleChoice: '多选',
  78 + shortAnswer: '简答'
  79 + },
  80 + list: {
  81 + title: '问卷题目'
  82 + },
  83 + table: {
  84 + name: '名称',
  85 + type: '类型',
  86 + options: '选项',
  87 + createTime: '创建时间'
  88 + },
  89 + types: {
  90 + singleChoice: '单选',
  91 + multipleChoice: '多选',
  92 + shortAnswer: '简答'
  93 + },
  94 + add: {
  95 + title: '添加题目',
  96 + name: '名称',
  97 + namePlaceholder: '必填,请填写名称',
  98 + nameRequired: '名称不能为空',
  99 + nameTooLong: '名称太长',
  100 + type: '类型',
  101 + typePlaceholder: '必填,请选择类型',
  102 + typeRequired: '类型不能为空',
  103 + option: '选项',
  104 + optionPlaceholder: '必填,请填写选项内容',
  105 + optionRequired: '选项内容不能为空',
  106 + addOption: '增加选项',
  107 + removeOption: '删除选项',
  108 + pleaseSelect: '请选择',
  109 + success: '添加成功'
  110 + },
  111 + edit: {
  112 + title: '修改题目',
  113 + name: '名称',
  114 + namePlaceholder: '必填,请填写名称',
  115 + nameRequired: '名称不能为空',
  116 + nameTooLong: '名称太长',
  117 + type: '类型',
  118 + typePlaceholder: '必填,请选择类型',
  119 + typeRequired: '类型不能为空',
  120 + option: '选项',
  121 + optionPlaceholder: '必填,请填写选项内容',
  122 + optionRequired: '选项内容不能为空',
  123 + addOption: '增加选项',
  124 + removeOption: '删除选项',
  125 + pleaseSelect: '请选择',
  126 + success: '修改成功'
  127 + },
  128 + delete: {
  129 + title: '确认操作',
  130 + confirmMessage: '确定删除该题目吗?',
  131 + success: '删除成功'
  132 + },
  133 + fetchError: '获取题目数据失败'
  134 + }
  135 + }
  136 +}
0 \ No newline at end of file 137 \ No newline at end of file
src/views/oa/questionTitleList.vue 0 → 100644
  1 +<template>
  2 + <div class="question-title-container">
  3 + <!-- 查询条件 -->
  4 + <el-card class="search-wrapper">
  5 + <div slot="header" class="flex justify-between">
  6 + <span>{{ $t('questionTitle.search.title') }}</span>
  7 + </div>
  8 + <el-row :gutter="20">
  9 + <el-col :span="6">
  10 + <el-input v-model="searchForm.itemTitle" :placeholder="$t('questionTitle.search.itemTitlePlaceholder')"
  11 + clearable />
  12 + </el-col>
  13 + <el-col :span="6">
  14 + <el-select v-model="searchForm.titleType" :placeholder="$t('questionTitle.search.titleTypePlaceholder')"
  15 + style="width:100%">
  16 + <el-option :label="$t('questionTitle.search.pleaseSelect')" value="" />
  17 + <el-option :label="$t('questionTitle.search.singleChoice')" value="1001" />
  18 + <el-option :label="$t('questionTitle.search.multipleChoice')" value="2002" />
  19 + <el-option :label="$t('questionTitle.search.shortAnswer')" value="3003" />
  20 + </el-select>
  21 + </el-col>
  22 + <el-col :span="12">
  23 + <el-button type="primary" @click="handleSearch">
  24 + <i class="el-icon-search"></i>
  25 + {{ $t('common.search') }}
  26 + </el-button>
  27 + <el-button @click="handleReset">
  28 + <i class="el-icon-refresh"></i>
  29 + {{ $t('common.reset') }}
  30 + </el-button>
  31 + </el-col>
  32 + </el-row>
  33 + </el-card>
  34 +
  35 + <!-- 问卷题目列表 -->
  36 + <el-card class="list-wrapper">
  37 + <div slot="header" class="flex justify-between">
  38 + <span>{{ $t('questionTitle.list.title') }}</span>
  39 + <el-button type="primary" size="small" style="float:right" @click="handleAdd">
  40 + <i class="el-icon-plus"></i>
  41 + {{ $t('common.add') }}
  42 + </el-button>
  43 + </div>
  44 +
  45 + <el-table v-loading="loading" :data="tableData" border style="width:100%">
  46 + <el-table-column prop="qaTitle" :label="$t('questionTitle.table.name')" align="center" />
  47 + <el-table-column prop="titleType" :label="$t('questionTitle.table.type')" align="center">
  48 + <template slot-scope="scope">
  49 + {{ getTitleTypeName(scope.row.titleType) }}
  50 + </template>
  51 + </el-table-column>
  52 + <el-table-column :label="$t('questionTitle.table.options')" align="center">
  53 + <template slot-scope="scope">
  54 + <div v-for="(item, index) in scope.row.titleValues" :key="index">
  55 + {{ item.seq }}、{{ item.qaValue }}
  56 + </div>
  57 + </template>
  58 + </el-table-column>
  59 + <el-table-column prop="createTime" :label="$t('questionTitle.table.createTime')" align="center" />
  60 + <el-table-column :label="$t('common.operation')" align="center" width="200">
  61 + <template slot-scope="scope">
  62 + <el-button size="mini" type="primary" @click="handleEdit(scope.row)">
  63 + {{ $t('common.edit') }}
  64 + </el-button>
  65 + <el-button size="mini" type="danger" @click="handleDelete(scope.row)">
  66 + {{ $t('common.delete') }}
  67 + </el-button>
  68 + </template>
  69 + </el-table-column>
  70 + </el-table>
  71 +
  72 + <el-pagination :current-page="pagination.current" :page-sizes="[10, 20, 30, 50]" :page-size="pagination.size"
  73 + :total="pagination.total" layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange"
  74 + @current-change="handleCurrentChange" />
  75 + </el-card>
  76 +
  77 + <!-- 添加题目对话框 -->
  78 + <add-question-title ref="addDialog" @success="handleSuccess" />
  79 +
  80 + <!-- 编辑题目对话框 -->
  81 + <edit-question-title ref="editDialog" @success="handleSuccess" />
  82 +
  83 + <!-- 删除题目对话框 -->
  84 + <delete-question-title ref="deleteDialog" @success="handleSuccess" />
  85 + </div>
  86 +</template>
  87 +
  88 +<script>
  89 +import { listQuestionTitle } from '@/api/oa/questionTitleApi'
  90 +import AddQuestionTitle from '@/components/oa/addQuestionTitle'
  91 +import EditQuestionTitle from '@/components/oa/editQuestionTitle'
  92 +import DeleteQuestionTitle from '@/components/oa/deleteQuestionTitle'
  93 +import { getCommunityId } from '@/api/community/communityApi'
  94 +
  95 +export default {
  96 + name: 'QuestionTitleList',
  97 + components: {
  98 + AddQuestionTitle,
  99 + EditQuestionTitle,
  100 + DeleteQuestionTitle
  101 + },
  102 + data() {
  103 + return {
  104 + loading: false,
  105 + searchForm: {
  106 + itemTitle: '',
  107 + titleType: '',
  108 + communityId: ''
  109 + },
  110 + tableData: [],
  111 + pagination: {
  112 + current: 1,
  113 + size: 10,
  114 + total: 0
  115 + }
  116 + }
  117 + },
  118 + created() {
  119 + this.searchForm.communityId = getCommunityId()
  120 + this.getList()
  121 + },
  122 + methods: {
  123 + async getList() {
  124 + try {
  125 + this.loading = true
  126 + const params = {
  127 + page: this.pagination.current,
  128 + row: this.pagination.size,
  129 + ...this.searchForm
  130 + }
  131 + const { data, total } = await listQuestionTitle(params)
  132 + this.tableData = data
  133 + this.pagination.total = total
  134 + } catch (error) {
  135 + this.$message.error(this.$t('questionTitle.fetchError'))
  136 + } finally {
  137 + this.loading = false
  138 + }
  139 + },
  140 + handleSearch() {
  141 + this.pagination.current = 1
  142 + this.getList()
  143 + },
  144 + handleReset() {
  145 + this.searchForm = {
  146 + itemTitle: '',
  147 + titleType: '',
  148 + communityId: getCommunityId()
  149 + }
  150 + this.pagination.current = 1
  151 + this.getList()
  152 + },
  153 + handleAdd() {
  154 + this.$refs.addDialog.open()
  155 + },
  156 + handleEdit(row) {
  157 + this.$refs.editDialog.open(row)
  158 + },
  159 + handleDelete(row) {
  160 + this.$refs.deleteDialog.open(row)
  161 + },
  162 + handleSuccess() {
  163 + this.getList()
  164 + },
  165 + handleSizeChange(val) {
  166 + this.pagination.size = val
  167 + this.getList()
  168 + },
  169 + handleCurrentChange(val) {
  170 + this.pagination.current = val
  171 + this.getList()
  172 + },
  173 + getTitleTypeName(type) {
  174 + switch (type) {
  175 + case '1001':
  176 + return this.$t('questionTitle.types.singleChoice')
  177 + case '2002':
  178 + return this.$t('questionTitle.types.multipleChoice')
  179 + case '3003':
  180 + return this.$t('questionTitle.types.shortAnswer')
  181 + default:
  182 + return ''
  183 + }
  184 + }
  185 + }
  186 +}
  187 +</script>
  188 +
  189 +<style lang="scss" scoped>
  190 +.question-title-container {
  191 + padding: 20px;
  192 +
  193 + .search-wrapper {
  194 + margin-bottom: 20px;
  195 +
  196 + .el-row {
  197 + margin-bottom: 10px;
  198 + }
  199 + }
  200 +
  201 + .list-wrapper {
  202 + margin-bottom: 20px;
  203 + }
  204 +
  205 + .el-pagination {
  206 + margin-top: 20px;
  207 + text-align: right;
  208 + }
  209 +}
  210 +</style>
0 \ No newline at end of file 211 \ No newline at end of file
src/views/oa/visitManageLang.js 0 → 100644
  1 +export const messages = {
  2 + en: {
  3 + visitManage: {
  4 + search: {
  5 + title: 'Search Conditions',
  6 + state: 'Select Audit Status',
  7 + nameLike: 'Please enter visitor name',
  8 + gender: 'Select Gender',
  9 + phoneNumber: 'Please enter phone number',
  10 + roomName: 'Please enter building-unit-room',
  11 + ownerName: 'Please enter owner name',
  12 + startTime: 'Please enter start time',
  13 + endTime: 'Please enter end time'
  14 + },
  15 + list: {
  16 + title: 'Visitor List'
  17 + },
  18 + table: {
  19 + visitId: 'Visit ID',
  20 + name: 'Name',
  21 + gender: 'Gender',
  22 + phoneNumber: 'Phone Number',
  23 + roomName: 'Visit Room',
  24 + ownerName: 'Visit Owner',
  25 + carNum: 'Visitor Car',
  26 + visitType: 'Visit Type',
  27 + visitCase: 'Visit Reason',
  28 + visitTime: 'Visit Time',
  29 + departureTime: 'Departure Time',
  30 + state: 'Audit Status',
  31 + msg: 'Audit Remark',
  32 + createTime: 'Create Time'
  33 + },
  34 + gender: {
  35 + male: 'Male',
  36 + female: 'Female'
  37 + },
  38 + visitType: 'Visit Type',
  39 + manage: 'Manage'
  40 + },
  41 + },
  42 + zh: {
  43 + visitManage: {
  44 + search: {
  45 + title: '查询条件',
  46 + state: '请选择审核状态',
  47 + nameLike: '请输入访客姓名',
  48 + gender: '请选择性别',
  49 + phoneNumber: '请输入电话号码',
  50 + roomName: '请输入拜访房屋 楼栋-单元-房屋',
  51 + ownerName: '请输入拜访业主',
  52 + startTime: '请输入开始时间',
  53 + endTime: '请输入结束时间'
  54 + },
  55 + list: {
  56 + title: '访客人员'
  57 + },
  58 + table: {
  59 + visitId: '拜访ID',
  60 + name: '姓名',
  61 + gender: '性别',
  62 + phoneNumber: '电话号码',
  63 + roomName: '拜访房屋',
  64 + ownerName: '拜访业主',
  65 + carNum: '访客车辆',
  66 + visitType: '访客类型',
  67 + visitCase: '拜访事由',
  68 + visitTime: '拜访时间',
  69 + departureTime: '离开时间',
  70 + state: '审核状态',
  71 + msg: '审核备注',
  72 + createTime: '创建时间'
  73 + },
  74 + gender: {
  75 + male: '男',
  76 + female: '女'
  77 + },
  78 + visitType: '访客类型',
  79 + manage: '管理'
  80 + },
  81 + }
  82 +}
0 \ No newline at end of file 83 \ No newline at end of file
src/views/oa/visitManageList.vue 0 → 100644
  1 +<template>
  2 + <div class="visit-manage-container">
  3 + <el-row :gutter="20">
  4 + <el-col :span="4">
  5 + <el-card class="tree-card">
  6 + <ul class="visit-type-list">
  7 + <li
  8 + v-for="(item, index) in visitTypeList"
  9 + :key="index"
  10 + @click="switchVisitType(item)"
  11 + :class="{'selected': visitManageInfo.conditions.typeId === item.typeId}"
  12 + >
  13 + {{ item.name }}
  14 + </li>
  15 + </ul>
  16 + </el-card>
  17 + </el-col>
  18 + <el-col :span="20">
  19 + <el-card>
  20 + <div slot="header" class="flex justify-between">
  21 + <span>{{ $t('visitManage.search.title') }}</span>
  22 + </div>
  23 + <el-form :inline="true" :model="visitManageInfo.conditions" class="search-form text-left">
  24 + <el-form-item>
  25 + <el-select
  26 + v-model="visitManageInfo.conditions.state"
  27 + :placeholder="$t('visitManage.search.state')"
  28 + style="width:100%"
  29 + >
  30 + <el-option
  31 + v-for="(item, index) in stateList"
  32 + :key="index"
  33 + :label="item.name"
  34 + :value="item.statusCd"
  35 + ></el-option>
  36 + </el-select>
  37 + </el-form-item>
  38 + <el-form-item>
  39 + <el-input
  40 + v-model="visitManageInfo.conditions.nameLike"
  41 + :placeholder="$t('visitManage.search.nameLike')"
  42 + ></el-input>
  43 + </el-form-item>
  44 + <el-form-item>
  45 + <el-select
  46 + v-model="visitManageInfo.conditions.visitGender"
  47 + :placeholder="$t('visitManage.search.gender')"
  48 + style="width:100%"
  49 + >
  50 + <el-option :label="$t('visitManage.gender.male')" value="0"></el-option>
  51 + <el-option :label="$t('visitManage.gender.female')" value="1"></el-option>
  52 + </el-select>
  53 + </el-form-item>
  54 + <el-form-item>
  55 + <el-input
  56 + v-model="visitManageInfo.conditions.phoneNumberLike"
  57 + :placeholder="$t('visitManage.search.phoneNumber')"
  58 + ></el-input>
  59 + </el-form-item>
  60 + <el-form-item>
  61 + <el-input
  62 + v-model="visitManageInfo.conditions.roomNameLike"
  63 + :placeholder="$t('visitManage.search.roomName')"
  64 + ></el-input>
  65 + </el-form-item>
  66 + <el-form-item>
  67 + <el-button type="primary" @click="_queryVisitMethod">{{ $t('common.search') }}</el-button>
  68 + <el-button @click="_resetVisitMethod">{{ $t('common.reset') }}</el-button>
  69 + </el-form-item>
  70 + </el-form>
  71 + <el-form :inline="true" :model="visitManageInfo.conditions" class="search-form text-left">
  72 + <el-form-item>
  73 + <el-input
  74 + v-model="visitManageInfo.conditions.ownerNameLike"
  75 + :placeholder="$t('visitManage.search.ownerName')"
  76 + ></el-input>
  77 + </el-form-item>
  78 + <el-form-item>
  79 + <el-date-picker
  80 + v-model="visitManageInfo.conditions.queryStartTime"
  81 + type="datetime"
  82 + :placeholder="$t('visitManage.search.startTime')"
  83 + ></el-date-picker>
  84 + </el-form-item>
  85 + <el-form-item>
  86 + <el-date-picker
  87 + v-model="visitManageInfo.conditions.queryEndTime"
  88 + type="datetime"
  89 + :placeholder="$t('visitManage.search.endTime')"
  90 + ></el-date-picker>
  91 + </el-form-item>
  92 + </el-form>
  93 + </el-card>
  94 +
  95 + <el-card class="mt-20">
  96 + <div slot="header" class="flex justify-between">
  97 + <span>{{ $t('visitManage.list.title') }}</span>
  98 + <el-button type="primary" size="small" style="float: right" @click="_toIotVisit">
  99 + {{ $t('visitManage.manage') }}
  100 + </el-button>
  101 + </div>
  102 + <el-table :data="visitManageInfo.visits" border style="width: 100%">
  103 + <el-table-column prop="visitId" :label="$t('visitManage.table.visitId')" align="center"></el-table-column>
  104 + <el-table-column prop="name" :label="$t('visitManage.table.name')" align="center"></el-table-column>
  105 + <el-table-column :label="$t('visitManage.table.gender')" align="center">
  106 + <template slot-scope="scope">
  107 + {{ scope.row.visitGender === '0' ? $t('visitManage.gender.male') : $t('visitManage.gender.female') }}
  108 + </template>
  109 + </el-table-column>
  110 + <el-table-column prop="phoneNumber" :label="$t('visitManage.table.phoneNumber')" align="center"></el-table-column>
  111 + <el-table-column prop="roomName" :label="$t('visitManage.table.roomName')" align="center"></el-table-column>
  112 + <el-table-column prop="ownerName" :label="$t('visitManage.table.ownerName')" align="center">
  113 + <template slot-scope="scope">
  114 + {{ scope.row.ownerName || '-' }}
  115 + </template>
  116 + </el-table-column>
  117 + <el-table-column prop="carNum" :label="$t('visitManage.table.carNum')" align="center">
  118 + <template slot-scope="scope">
  119 + {{ scope.row.carNum || '-' }}
  120 + </template>
  121 + </el-table-column>
  122 + <el-table-column prop="visitTypeName" :label="$t('visitManage.table.visitType')" align="center"></el-table-column>
  123 + <el-table-column prop="visitCase" :label="$t('visitManage.table.visitCase')" align="center">
  124 + <template slot-scope="scope">
  125 + {{ scope.row.visitCase || '-' }}
  126 + </template>
  127 + </el-table-column>
  128 + <el-table-column prop="visitTime" :label="$t('visitManage.table.visitTime')" align="center">
  129 + <template slot-scope="scope">
  130 + {{ scope.row.visitTime || '-' }}
  131 + </template>
  132 + </el-table-column>
  133 + <el-table-column prop="departureTime" :label="$t('visitManage.table.departureTime')" align="center">
  134 + <template slot-scope="scope">
  135 + {{ scope.row.departureTime || '-' }}
  136 + </template>
  137 + </el-table-column>
  138 + <el-table-column prop="stateName" :label="$t('visitManage.table.state')" align="center"></el-table-column>
  139 + <el-table-column prop="msg" :label="$t('visitManage.table.msg')" align="center">
  140 + <template slot-scope="scope">
  141 + {{ scope.row.msg || '-' }}
  142 + </template>
  143 + </el-table-column>
  144 + <el-table-column prop="createTime" :label="$t('visitManage.table.createTime')" align="center"></el-table-column>
  145 + <el-table-column :label="$t('common.operation')" align="center" width="120">
  146 + <template slot-scope="scope">
  147 + <el-button size="mini" @click="_toVisitDetail(scope.row)">{{ $t('common.detail') }}</el-button>
  148 + </template>
  149 + </el-table-column>
  150 + </el-table>
  151 + <el-pagination
  152 + @size-change="handleSizeChange"
  153 + @current-change="handleCurrentChange"
  154 + :current-page="page.current"
  155 + :page-sizes="[10, 20, 30, 50]"
  156 + :page-size="page.size"
  157 + layout="total, sizes, prev, pager, next, jumper"
  158 + :total="visitManageInfo.total"
  159 + class="pagination"
  160 + ></el-pagination>
  161 + </el-card>
  162 + </el-col>
  163 + </el-row>
  164 + </div>
  165 +</template>
  166 +
  167 +<script>
  168 +import { listVisits, listVisitTypes } from '@/api/oa/visitManageApi'
  169 +import { getDict } from '@/api/community/communityApi'
  170 +import { getCommunityId } from '@/api/community/communityApi'
  171 +
  172 +export default {
  173 + name: 'VisitManageList',
  174 + data() {
  175 + return {
  176 + visitManageInfo: {
  177 + visits: [],
  178 + total: 0,
  179 + conditions: {
  180 + visitId: '',
  181 + nameLike: '',
  182 + visitGender: '',
  183 + phoneNumberLike: '',
  184 + roomNameLike: '',
  185 + ownerNameLike: '',
  186 + communityId: '',
  187 + typeId: '',
  188 + state: '',
  189 + queryStartTime: '',
  190 + queryEndTime: '',
  191 + page: 1,
  192 + row: 10
  193 + }
  194 + },
  195 + visitTypeList: [],
  196 + stateList: [],
  197 + page: {
  198 + current: 1,
  199 + size: 10
  200 + }
  201 + }
  202 + },
  203 + created() {
  204 + this.communityId = getCommunityId()
  205 + this.initData()
  206 + },
  207 + methods: {
  208 + async initData() {
  209 + try {
  210 + await this.listVisitTypes()
  211 + await this.getDictData()
  212 + this._listVisits(1, 10)
  213 + } catch (error) {
  214 + console.error('初始化数据失败:', error)
  215 + }
  216 + },
  217 + async getDictData() {
  218 + try {
  219 + const data = await getDict('visit', 'state')
  220 + this.stateList = data
  221 + } catch (error) {
  222 + console.error('获取字典数据失败:', error)
  223 + }
  224 + },
  225 + async _listVisits(page, rows) {
  226 + try {
  227 + this.visitManageInfo.conditions.page = page
  228 + this.visitManageInfo.conditions.row = rows
  229 + this.visitManageInfo.conditions.communityId = this.communityId
  230 + const { data, total } = await listVisits(this.visitManageInfo.conditions)
  231 + this.visitManageInfo.visits = data
  232 + this.visitManageInfo.total = total
  233 + this.page.current = page
  234 + this.page.size = rows
  235 + } catch (error) {
  236 + console.error('获取访客列表失败:', error)
  237 + }
  238 + },
  239 + async listVisitTypes() {
  240 + try {
  241 + const { data } = await listVisitTypes({
  242 + page: -1,
  243 + row: 100,
  244 + communityId: this.communityId
  245 + })
  246 + this.visitTypeList = [{ name: this.$t('visitManage.visitType'), typeId: '' }, ...data]
  247 + } catch (error) {
  248 + console.error('获取访客类型失败:', error)
  249 + }
  250 + },
  251 + _queryVisitMethod() {
  252 + this._listVisits(1, this.page.size)
  253 + },
  254 + _resetVisitMethod() {
  255 + this.visitManageInfo.conditions = {
  256 + visitId: '',
  257 + nameLike: '',
  258 + visitGender: '',
  259 + phoneNumberLike: '',
  260 + roomNameLike: '',
  261 + ownerNameLike: '',
  262 + communityId: this.communityId,
  263 + typeId: '',
  264 + state: '',
  265 + queryStartTime: '',
  266 + queryEndTime: '',
  267 + page: 1,
  268 + row: this.page.size
  269 + }
  270 + this._listVisits(1, this.page.size)
  271 + },
  272 + switchVisitType(item) {
  273 + this.visitManageInfo.conditions.typeId = item.typeId
  274 + this._listVisits(1, this.page.size)
  275 + },
  276 + _toIotVisit() {
  277 + this.$router.push('/pages/accessControl/visitManage')
  278 + },
  279 + _toVisitDetail(visit) {
  280 + this.$router.push(`/pages/accessControl/visitDetail?visitId=${visit.visitId}&phoneNumber=${visit.phoneNumber}`)
  281 + },
  282 + handleSizeChange(val) {
  283 + this._listVisits(1, val)
  284 + },
  285 + handleCurrentChange(val) {
  286 + this._listVisits(val, this.page.size)
  287 + }
  288 + }
  289 +}
  290 +</script>
  291 +
  292 +<style lang="scss" scoped>
  293 +.visit-manage-container {
  294 + padding: 20px;
  295 +
  296 + .tree-card {
  297 + height: 100%;
  298 +
  299 + .visit-type-list {
  300 + list-style: none;
  301 + padding: 0;
  302 + margin: 0;
  303 +
  304 + li {
  305 + padding: 10px;
  306 + cursor: pointer;
  307 + text-align: center;
  308 + border-bottom: 1px solid #eee;
  309 + transition: all 0.3s;
  310 +
  311 + &:hover {
  312 + background-color: #f5f7fa;
  313 + }
  314 +
  315 + &.selected {
  316 + background-color: #409eff;
  317 + color: white;
  318 + }
  319 + }
  320 + }
  321 + }
  322 +
  323 + .search-form {
  324 + margin-bottom: 0;
  325 +
  326 + .el-form-item {
  327 + margin-bottom: 10px;
  328 + margin-right: 10px;
  329 + }
  330 + }
  331 +
  332 + .mt-20 {
  333 + margin-top: 20px;
  334 + }
  335 +
  336 + .pagination {
  337 + margin-top: 20px;
  338 + text-align: right;
  339 + }
  340 +}
  341 +</style>
0 \ No newline at end of file 342 \ No newline at end of file