Files
yuezi-saas/frontend/src/views/meal/reviews/index.vue
T
li ee2b1757d0 feat: 员工自助注册+审核流程
- 新增 registration_packages 表:管理员配置注册套餐(名称/角色)
- AuthController 新增 register() 和 registrationPackages() 公开接口
- UserController 新增 approve() / reject() 审核接口
- StoreController 新增 publicList() 公开门店列表
- 前端 /register 注册页:选门店→选岗位套餐→填信息→提交
- 前端 system/registration-packages:套餐 CRUD
- 用户管理页:待审核状态展示 + 通过/拒绝快捷操作
- 登录页底部加「申请注册」跳转链接
- 路由白名单加 /register
2026-03-14 17:39:50 +08:00

192 lines
7.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { Icon } from '@iconify/vue'
import { mealReviewApi } from '@/api/meal.js'
import { customerApi } from '@/api/crm.js'
const tableData = ref([])
const total = ref(0)
const loading = ref(false)
const detailVisible = ref(false)
const detailLoading = ref(false)
const currentDetail = ref(null)
const customers = ref([])
const searchForm = reactive({ customer_id: '', rating: '', meal_date: '' })
const pagination = reactive({ page: 1, per_page: 20 })
const ratingOptions = [
{ label: '5星', value: 5 },
{ label: '4星', value: 4 },
{ label: '3星', value: 3 },
{ label: '2星', value: 2 },
{ label: '1星', value: 1 }
]
async function fetchList() {
loading.value = true
try {
const res = await mealReviewApi.getList({ ...searchForm, ...pagination })
tableData.value = res.data?.list || res.data?.data || []
total.value = res.data?.total || 0
} catch {
ElMessage.error('加载失败')
} finally {
loading.value = false
}
}
async function fetchCustomers() {
try {
const res = await customerApi.getList({ per_page: 500 })
customers.value = res.data?.list || res.data?.data || []
} catch {}
}
function handleSearch() { pagination.page = 1; fetchList() }
function handleReset() {
Object.assign(searchForm, { customer_id: '', rating: '', meal_date: '' })
handleSearch()
}
function handleView(row) {
currentDetail.value = row
detailVisible.value = true
}
function getRatingColor(rating) {
if (rating >= 4) return 'text-green-500'
if (rating >= 3) return 'text-yellow-500'
return 'text-red-400'
}
function handlePageChange(val) { pagination.page = val; fetchList() }
function handleSizeChange(val) { pagination.per_page = val; pagination.page = 1; fetchList() }
onMounted(() => { fetchList(); fetchCustomers() })
</script>
<template>
<div class="p-6 space-y-4">
<!-- 搜索栏 -->
<div class="bg-white rounded-2xl border border-gray-100 shadow-sm p-4 flex items-center flex-wrap gap-3">
<el-select v-model="searchForm.customer_id" placeholder="选择客户" clearable filterable style="width:180px">
<el-option v-for="c in customers" :key="c.id" :label="c.name" :value="c.id" />
</el-select>
<el-select v-model="searchForm.rating" placeholder="评分筛选" clearable style="width:130px">
<el-option v-for="r in ratingOptions" :key="r.value" :label="r.label" :value="r.value" />
</el-select>
<el-date-picker v-model="searchForm.meal_date" type="date" value-format="YYYY-MM-DD" placeholder="就餐日期" style="width:160px" />
<el-button type="primary" @click="handleSearch">
<Icon icon="solar:magnifer-bold-duotone" class="mr-1" />查询
</el-button>
<el-button @click="handleReset">重置</el-button>
</div>
<!-- 表格区 -->
<div class="bg-white rounded-2xl border border-gray-100 shadow-sm">
<div class="flex items-center justify-between px-5 py-4 border-b border-gray-50">
<div class="flex items-center gap-2">
<span class="w-1 h-5 bg-rose-500 rounded-full inline-block"></span>
<span class="font-semibold text-gray-700">膳食评价列表</span>
<span class="text-xs text-gray-400 ml-1">只读客户评价不可编辑</span>
</div>
</div>
<div class="px-2">
<el-table v-loading="loading" :data="tableData" style="width:100%">
<el-table-column label="客户" min-width="100">
<template #default="{ row }">{{ row.customer_name || '-' }}</template>
</el-table-column>
<el-table-column prop="meal_date" label="就餐日期" width="110" />
<el-table-column label="评分" width="120" align="center">
<template #default="{ row }">
<div class="flex items-center justify-center gap-0.5">
<Icon
v-for="i in 5"
:key="i"
:icon="i <= row.rating ? 'solar:star-bold' : 'solar:star-line-duotone'"
:class="['text-base', i <= row.rating ? 'text-amber-400' : 'text-gray-200']"
/>
<span :class="['ml-1 text-sm font-semibold', getRatingColor(row.rating)]">{{ row.rating }}</span>
</div>
</template>
</el-table-column>
<el-table-column prop="content" label="评价内容" min-width="200" show-overflow-tooltip />
<el-table-column label="照片" width="70" align="center">
<template #default="{ row }">
<span v-if="row.photos && row.photos.length" class="text-rose-500 text-sm">{{ row.photos.length }} </span>
<span v-else class="text-gray-300 text-sm"></span>
</template>
</el-table-column>
<el-table-column prop="created_at" label="评价时间" min-width="160" />
<el-table-column label="操作" width="80" fixed="right" align="center">
<template #default="{ row }">
<el-button size="small" type="primary" text @click="handleView(row)">详情</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="px-5 py-3 border-t border-gray-50">
<el-pagination
v-model:current-page="pagination.page"
v-model:page-size="pagination.per_page"
:total="total"
:page-sizes="[10, 20, 50]"
layout="total,sizes,prev,pager,next,jumper"
background
@current-change="handlePageChange"
@size-change="handleSizeChange"
/>
</div>
</div>
<!-- 详情弹窗 -->
<el-dialog v-model="detailVisible" title="评价详情" width="520px" destroy-on-close>
<div v-loading="detailLoading">
<template v-if="currentDetail">
<el-descriptions :column="2" border>
<el-descriptions-item label="客户">{{ currentDetail.customer_name || '-' }}</el-descriptions-item>
<el-descriptions-item label="就餐日期">{{ currentDetail.meal_date || '-' }}</el-descriptions-item>
<el-descriptions-item label="评分" :span="2">
<div class="flex items-center gap-1">
<Icon
v-for="i in 5"
:key="i"
:icon="i <= currentDetail.rating ? 'solar:star-bold' : 'solar:star-line-duotone'"
:class="['text-lg', i <= currentDetail.rating ? 'text-amber-400' : 'text-gray-200']"
/>
<span :class="['ml-1 font-semibold', getRatingColor(currentDetail.rating)]">
{{ currentDetail.rating }} / 5
</span>
</div>
</el-descriptions-item>
<el-descriptions-item label="评价内容" :span="2">
{{ currentDetail.content || '(无文字评价)' }}
</el-descriptions-item>
<el-descriptions-item label="评价时间" :span="2">{{ currentDetail.created_at || '-' }}</el-descriptions-item>
</el-descriptions>
<!-- 照片 -->
<div v-if="currentDetail.photos && currentDetail.photos.length" class="mt-4">
<p class="text-sm text-gray-500 mb-2">评价照片{{ currentDetail.photos.length }} </p>
<div class="flex flex-wrap gap-2">
<el-image
v-for="(src, idx) in currentDetail.photos"
:key="idx"
:src="src"
:preview-src-list="currentDetail.photos"
:initial-index="idx"
fit="cover"
class="w-20 h-20 rounded-lg object-cover cursor-pointer"
/>
</div>
</div>
</template>
</div>
<template #footer>
<el-button @click="detailVisible = false">关闭</el-button>
</template>
</el-dialog>
</div>
</template>