169 lines
7.6 KiB
Vue
169 lines
7.6 KiB
Vue
<script setup lang="ts">
|
|
/**
|
|
* F006 意见反馈 - W10 A1 标准化
|
|
*
|
|
* 沿用老 vue api/feedback.vue 137 行:
|
|
* - 搜索: 用户昵称
|
|
* - 表格列: userId/nickName/phone/title/content/orderCode/createDate
|
|
* - 操作: 预览 / 删除
|
|
* - 预览弹窗展示完整反馈内容 (含图片列表)
|
|
* - /api/feedback 后端 404 → FALLBACK mock
|
|
*
|
|
* W10 A1: 套 PageHeader/SearchBar 标准化模板 (无 STATUS_MAP, 无增改弹窗)
|
|
*/
|
|
import { ref, onMounted, reactive } from 'vue'
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
import { httpGet, httpPost } from '@/utils/request'
|
|
import DataTable from '@/components/DataTable/DataTable.vue'
|
|
import PageHeader from '@/components/PageHeader/PageHeader.vue'
|
|
import SearchBar from '@/components/SearchBar/SearchBar.vue'
|
|
|
|
interface Feedback {
|
|
id: string
|
|
userId: string
|
|
nickName: string
|
|
phone: string
|
|
title: string
|
|
content: string
|
|
orderCode: string
|
|
createDate: string
|
|
imgList: string[]
|
|
raw: any
|
|
}
|
|
|
|
// ========== 端点 404 降级 mock ==========
|
|
const FALLBACK: Feedback[] = [
|
|
{ id: 'F001', userId: 'U10086', nickName: '张经理', phone: '13800138001', title: '订单延迟发货', content: '我们于6月28日下单采购一批钢材, 至今未发货, 影响项目进度, 请尽快处理。', orderCode: 'O20260628001', createDate: '2026-07-01 09:32:18', imgList: ['https://placehold.co/120x120/2563EB/fff?text=IMG1', 'https://placehold.co/120x120/2563EB/fff?text=IMG2'], raw: {} },
|
|
{ id: 'F002', userId: 'U10087', nickName: '李工', phone: '13900139002', title: '产品质量问题', content: '收到的商品有破损, 已拍照。附件图1是外包装破损, 图2是商品本身。', orderCode: 'O20260630002', createDate: '2026-07-02 11:15:42', imgList: ['https://placehold.co/120x120/DC2626/fff?text=BAD1'], raw: {} },
|
|
{ id: 'F003', userId: 'U10088', nickName: '王采购', phone: '13700137003', title: '系统使用咨询', content: '请问如何批量导出订单? 需要按月统计采购金额。', orderCode: '', createDate: '2026-07-02 14:08:00', imgList: [], raw: {} },
|
|
{ id: 'F004', userId: 'U10089', nickName: '赵总', phone: '13600136004', title: '希望增加新的支付方式', content: '目前只支持对公转账, 希望增加企业网银在线支付。', orderCode: '', createDate: '2026-07-03 10:22:11', imgList: [], raw: {} },
|
|
{ id: 'F005', userId: 'U10090', nickName: '钱运营', phone: '13500135005', title: '订单退款申请', content: '订单已取消但款项未到账, 请核实。订单号见附件。', orderCode: 'O20260701003', createDate: '2026-07-03 16:45:30', imgList: ['https://placehold.co/120x120/F59E0B/fff?text=REFUND'], raw: {} },
|
|
{ id: 'F006', userId: 'U10091', nickName: '孙技术', phone: '13400134006', title: '功能建议: 自定义审批流', content: '建议在询价单模块增加自定义审批流, 当前固定流程不够灵活。', orderCode: '', createDate: '2026-07-04 09:10:00', imgList: [], raw: {} }
|
|
]
|
|
|
|
// ========== 标准状态 ==========
|
|
const list = ref<Feedback[]>([])
|
|
const total = ref(0)
|
|
const loading = ref(false)
|
|
const error = ref<string | null>(null)
|
|
const page = ref(1)
|
|
const limit = ref(20)
|
|
const filters = reactive<{ nickName: string }>({ nickName: '' })
|
|
|
|
// ========== 弹窗状态 ==========
|
|
const previewVisible = ref(false)
|
|
const previewData = ref<Feedback | null>(null)
|
|
|
|
// ========== 加载逻辑 ==========
|
|
async function load() {
|
|
loading.value = true
|
|
error.value = null
|
|
try {
|
|
const resp: any = await httpGet('/other/feedback/page', { page: page.value, limit: limit.value })
|
|
list.value = resp?.list || FALLBACK
|
|
total.value = Number(resp?.total || 0)
|
|
} catch (e: any) {
|
|
error.value = e?.msg || e?.message || '加载失败'
|
|
list.value = FALLBACK
|
|
total.value = FALLBACK.length
|
|
} finally { loading.value = false }
|
|
}
|
|
|
|
// ========== 事件 ==========
|
|
function onSearch() { page.value = 1; load() }
|
|
function onReset() { filters.nickName = ''; load() }
|
|
function onPageChange(p: number) { page.value = p; load() }
|
|
function onLimitChange(l: number) { limit.value = l; load() }
|
|
function onPreview(row: Feedback) { previewData.value = row; previewVisible.value = true }
|
|
function onDelete(row: Feedback) {
|
|
ElMessageBox.confirm(`确认删除「${row.title}」?`, '提示', { type: 'warning' }).then(async () => {
|
|
try { await httpPost('/other/feedback/delete', { id: row.id }); ElMessage.success('已删除'); load() }
|
|
catch (e: any) { ElMessage.error(e?.msg || '删除失败') }
|
|
}).catch(() => {})
|
|
}
|
|
|
|
onMounted(load)
|
|
</script>
|
|
|
|
<template>
|
|
<div class="rr-page">
|
|
<PageHeader title="意见反馈" :sub="`用户反馈信息管理 (共 ${total} 条)`" />
|
|
|
|
<SearchBar
|
|
v-model:filters="filters"
|
|
:fields="[
|
|
{ key: 'nickName', label: '用户昵称', type: 'text', placeholder: '请输入用户昵称' }
|
|
]"
|
|
@search="onSearch"
|
|
@reset="onReset"
|
|
/>
|
|
|
|
<div class="rr-card">
|
|
<DataTable
|
|
:columns="[
|
|
{ prop: 'userId', label: '用户ID', width: 120 },
|
|
{ prop: 'nickName', label: '用户昵称', width: 140 },
|
|
{ prop: 'phone', label: '电话', width: 140 },
|
|
{ prop: 'title', label: '标题', minWidth: 200 },
|
|
{ prop: 'content', label: '反馈内容', minWidth: 280 },
|
|
{ prop: 'orderCode', label: '订单编号', width: 160 },
|
|
{ prop: 'createDate', label: '创建时间', width: 180 },
|
|
{ label: '操作', width: 160, align: 'center', slot: 'action' }
|
|
]"
|
|
:data="list"
|
|
:loading="loading"
|
|
:total="total"
|
|
:page="page"
|
|
:limit="limit"
|
|
:show-pagination="total > 10" :page-sizes="[10, 20, 50, 100]"
|
|
:error="error"
|
|
empty-text="暂无反馈"
|
|
borderless
|
|
row-key="id"
|
|
@page-change="onPageChange"
|
|
@limit-change="onLimitChange"
|
|
>
|
|
<template #action="{ row }">
|
|
<el-button text type="primary" size="small" @click="onPreview(row)">预览</el-button>
|
|
<el-button text type="danger" size="small" @click="onDelete(row)">删除</el-button>
|
|
</template>
|
|
</DataTable>
|
|
</div>
|
|
|
|
<!-- 预览弹窗 -->
|
|
<el-dialog v-model="previewVisible" title="反馈内容" width="700px">
|
|
<el-form label-width="100px" v-if="previewData">
|
|
<el-form-item label="用户昵称">
|
|
<el-input :model-value="previewData.nickName" readonly />
|
|
</el-form-item>
|
|
<el-form-item label="电话">
|
|
<el-input :model-value="previewData.phone" readonly />
|
|
</el-form-item>
|
|
<el-form-item label="标题">
|
|
<el-input :model-value="previewData.title" type="textarea" :rows="2" readonly />
|
|
</el-form-item>
|
|
<el-form-item label="内容">
|
|
<el-input :model-value="previewData.content" type="textarea" :rows="6" readonly />
|
|
</el-form-item>
|
|
<el-form-item label="订单编号">
|
|
<el-input :model-value="previewData.orderCode || '--'" readonly />
|
|
</el-form-item>
|
|
<el-form-item label="反馈图片" v-if="previewData.imgList && previewData.imgList.length">
|
|
<div class="feedback-imgs">
|
|
<el-image v-for="(img, idx) in previewData.imgList" :key="idx" :src="img" :preview-src-list="previewData.imgList" :initial-index="idx" fit="cover" class="feedback-img-item" preview-teleported />
|
|
</div>
|
|
</el-form-item>
|
|
</el-form>
|
|
<template #footer>
|
|
<el-button @click="previewVisible = false">关闭</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped lang="scss">
|
|
.rr-page { max-width: 1400px; }
|
|
.feedback-imgs { display: flex; gap: 8px; flex-wrap: wrap; }
|
|
.feedback-img-item { width: 80px; height: 80px; border-radius: 6px; border: 1px solid var(--border); }
|
|
</style>
|