|
|
@@ -0,0 +1,176 @@
|
|
|
+<template>
|
|
|
+ <div class="panel">
|
|
|
+ <div class="toolbar">
|
|
|
+ <el-select v-model="query.status" clearable placeholder="全部状态" style="width: 140px">
|
|
|
+ <el-option label="待处理" value="待处理" />
|
|
|
+ <el-option label="处理中" value="处理中" />
|
|
|
+ <el-option label="已办结" value="已办结" />
|
|
|
+ </el-select>
|
|
|
+ <el-select v-model="query.fbType" clearable placeholder="全部类型" style="width: 140px">
|
|
|
+ <el-option label="投诉" value="投诉" />
|
|
|
+ <el-option label="表扬" value="表扬" />
|
|
|
+ </el-select>
|
|
|
+ <el-input v-model="query.keyword" clearable placeholder="关键词" style="width: 220px" />
|
|
|
+ <el-button type="primary" @click="load">查询</el-button>
|
|
|
+ </div>
|
|
|
+ <el-table :data="list" v-loading="loading" stripe>
|
|
|
+ <el-table-column prop="bizNo" label="编号" min-width="140" />
|
|
|
+ <el-table-column prop="fbType" label="类型" width="90" />
|
|
|
+ <el-table-column prop="targetName" label="被反馈人" width="110" />
|
|
|
+ <el-table-column prop="content" label="内容摘要" min-width="200" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="reporterName" label="举报人" width="100" />
|
|
|
+ <el-table-column prop="createTime" label="提交时间" min-width="160" />
|
|
|
+ <el-table-column prop="status" label="状态" width="100" />
|
|
|
+ <el-table-column label="操作" width="100" fixed="right">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-button link type="primary" @click="openDetail(row)">详情</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ <div class="pager">
|
|
|
+ <el-pagination
|
|
|
+ background
|
|
|
+ layout="total, prev, pager, next"
|
|
|
+ :total="total"
|
|
|
+ v-model:current-page="query.page"
|
|
|
+ :page-size="query.pageSize"
|
|
|
+ @current-change="load"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-drawer v-model="drawer" title="反馈详情" size="480px">
|
|
|
+ <template v-if="detail">
|
|
|
+ <el-descriptions :column="1" border>
|
|
|
+ <el-descriptions-item label="编号">{{ detail.bizNo }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="类型">{{ detail.fbType }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="状态">{{ detail.status }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="被反馈人">
|
|
|
+ {{ detail.targetName || '-' }} / {{ detail.targetDept || '-' }} /
|
|
|
+ {{ detail.targetPost || '-' }}
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="内容">{{ detail.content }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="举报人">
|
|
|
+ {{ detail.reporterName || '匿名' }} {{ detail.reporterPhone || '' }}
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="附件">
|
|
|
+ <div v-if="detail.attachmentUrls?.length">
|
|
|
+ <a
|
|
|
+ v-for="(u, i) in detail.attachmentUrls"
|
|
|
+ :key="i"
|
|
|
+ :href="u"
|
|
|
+ target="_blank"
|
|
|
+ rel="noreferrer"
|
|
|
+ >附件{{ i + 1 }}</a
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ <span v-else>无</span>
|
|
|
+ </el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+ <div v-if="canHandle" class="handle-box">
|
|
|
+ <h4>办理</h4>
|
|
|
+ <el-select v-model="handleForm.status" style="width: 100%; margin-bottom: 10px">
|
|
|
+ <el-option label="待处理" value="待处理" />
|
|
|
+ <el-option label="处理中" value="处理中" />
|
|
|
+ <el-option label="已办结" value="已办结" />
|
|
|
+ </el-select>
|
|
|
+ <el-input
|
|
|
+ v-model="handleForm.handleRemark"
|
|
|
+ type="textarea"
|
|
|
+ :rows="3"
|
|
|
+ placeholder="办理备注"
|
|
|
+ />
|
|
|
+ <el-button type="primary" style="margin-top: 12px" :loading="saving" @click="saveHandle"
|
|
|
+ >保存</el-button
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-drawer>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+ import { computed, onMounted, reactive, ref } from 'vue'
|
|
|
+ import { ElMessage } from 'element-plus'
|
|
|
+ import { fetchFeedback, fetchFeedbacks, updateFeedbackStatus } from '@/api/admin'
|
|
|
+ import { useUserStore } from '@/stores/user'
|
|
|
+
|
|
|
+ const userStore = useUserStore()
|
|
|
+ const canHandle = computed(() => userStore.hasPerm('feedback:handle'))
|
|
|
+ const loading = ref(false)
|
|
|
+ const saving = ref(false)
|
|
|
+ const list = ref<any[]>([])
|
|
|
+ const total = ref(0)
|
|
|
+ const drawer = ref(false)
|
|
|
+ const detail = ref<any>(null)
|
|
|
+ const query = reactive({
|
|
|
+ status: '',
|
|
|
+ fbType: '',
|
|
|
+ keyword: '',
|
|
|
+ page: 1,
|
|
|
+ pageSize: 10
|
|
|
+ })
|
|
|
+ const handleForm = reactive({ status: '', handleRemark: '' })
|
|
|
+
|
|
|
+ async function load() {
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ const res: any = await fetchFeedbacks({ ...query })
|
|
|
+ list.value = res.data.list || []
|
|
|
+ total.value = res.data.total || 0
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async function openDetail(row: any) {
|
|
|
+ const res: any = await fetchFeedback(row.id)
|
|
|
+ detail.value = res.data
|
|
|
+ handleForm.status = res.data.status
|
|
|
+ handleForm.handleRemark = res.data.handleRemark || ''
|
|
|
+ drawer.value = true
|
|
|
+ }
|
|
|
+
|
|
|
+ async function saveHandle() {
|
|
|
+ if (!detail.value) return
|
|
|
+ saving.value = true
|
|
|
+ try {
|
|
|
+ const res: any = await updateFeedbackStatus(detail.value.id, {
|
|
|
+ status: handleForm.status,
|
|
|
+ handleRemark: handleForm.handleRemark
|
|
|
+ })
|
|
|
+ detail.value = res.data
|
|
|
+ ElMessage.success('已保存')
|
|
|
+ load()
|
|
|
+ } finally {
|
|
|
+ saving.value = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ onMounted(load)
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+ .panel {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 10px;
|
|
|
+ padding: 20px;
|
|
|
+ }
|
|
|
+ .toolbar {
|
|
|
+ display: flex;
|
|
|
+ gap: 10px;
|
|
|
+ margin-bottom: 14px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ }
|
|
|
+ .pager {
|
|
|
+ margin-top: 14px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ }
|
|
|
+ .handle-box {
|
|
|
+ margin-top: 20px;
|
|
|
+ }
|
|
|
+ a {
|
|
|
+ display: block;
|
|
|
+ color: #a51f23;
|
|
|
+ }
|
|
|
+</style>
|