Explorar el Código

增加满意度调查日志

xiongxing hace 3 semanas
padre
commit
adc453d518

+ 19 - 1
src/api/system/foods/index.ts

@@ -195,4 +195,22 @@ export const deleteElderFoodOrder = (id) => {
 // 导出
 export const exportElderFoodOrderExcel = (params) => {
   return request.download({ url: '/elderly/order-food/exportExcel', params })
-}
+}
+
+// ====================膳食满意度调查==================
+
+export const getFoodSatisfactionSurveyPage = (params) => {
+  return request.get({ url: '/food-satisfaction-survey/page', params })
+}
+
+export const getFoodSatisfactionSurvey = (id: number) => {
+  return request.get({ url: `/food-satisfaction-survey/get?id=${id}` })
+}
+
+export const deleteFoodSatisfactionSurvey = (id: number) => {
+  return request.delete({ url: `/food-satisfaction-survey/delete?id=${id}` })
+}
+
+export const exportFoodSatisfactionSurveyExcel = (params) => {
+  return request.download({ url: '/food-satisfaction-survey/exportExcel', params })
+}

+ 448 - 0
src/views/elderly/restaurant/satisfactionSurvey/index.vue

@@ -0,0 +1,448 @@
+<template>
+  <ContentWrap>
+    <el-form ref="queryFormRef" :model="queryParams" label-width="92px" class="query-form">
+      <el-row :gutter="16">
+        <el-col :xs="24" :sm="12" :md="12" :lg="5" :xl="6">
+          <TenantSelect v-model="queryParams.tenantIds" placeholder="请选择院区" prop="tenantIds" />
+        </el-col>
+        <el-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
+          <el-form-item label="填写身份" prop="role">
+            <el-select v-model="queryParams.role" clearable placeholder="请选择身份" class="!w-full">
+              <el-option
+                v-for="item in roleOptions"
+                :key="item.value"
+                :label="item.label"
+                :value="item.value"
+              />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
+          <el-form-item label="填写日期" prop="recordDate">
+            <el-date-picker
+              v-model="queryParams.recordDate"
+              type="daterange"
+              value-format="YYYY-MM-DD"
+              range-separator="至"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+              class="!w-full"
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
+          <el-form-item class="query-actions" label-width="0">
+            <el-button @click="handleQuery">
+              <Icon icon="ep:search" class="mr-5px" />
+              搜索
+            </el-button>
+            <el-button @click="resetQuery">
+              <Icon icon="ep:refresh" class="mr-5px" />
+              重置
+            </el-button>
+            <el-button v-hasPermi="['satisfactionSurvey:export']" type="warning" plain :loading="exportLoading" @click="handleExport">
+              <Icon icon="ep:download" class="mr-5px" />
+              导出
+            </el-button>
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+  </ContentWrap>
+
+  <ContentWrap>
+    <div class="mb-12px flex items-center justify-between">
+      <div class="text-[14px] text-[#606266]">
+        共筛选出 <span class="text-[#409eff]">{{ total }}</span> 条满意度问卷
+      </div>
+    </div>
+
+    <el-table v-loading="loading" :data="pageList" border>
+      <!-- <el-table-column label="序号" type="index" width="60" align="center" /> -->
+      <el-table-column label="总得分" min-width="90" align="center">
+        <template #default="{ row }">
+          {{ getGrandTotal(row) }}
+        </template>
+      </el-table-column>
+      <el-table-column label="填写日期" prop="recordDate" min-width="110" />
+      <el-table-column label="院区/用户" min-width="200" show-overflow-tooltip>
+        <template #default="{ row }">
+          {{ getTenantUserText(row) }}
+        </template>
+      </el-table-column>
+      <el-table-column label="填写身份" prop="role" min-width="90">
+        <template #default="{ row }">
+          {{ row.role }}
+        </template>
+      </el-table-column>
+      <el-table-column label="膳食质量总分" prop="qualityTotal" min-width="120" align="center" />
+      <el-table-column label="膳食服务总分" prop="serviceTotal" min-width="120" align="center" />
+      <el-table-column label="软食/流食满足" min-width="130">
+        <template #default="{ row }">
+          {{ row.specialNeeds?.softFood || '-' }}
+        </template>
+      </el-table-column>
+      <el-table-column label="素食需求满足" min-width="130">
+        <template #default="{ row }">
+          {{ row.specialNeeds?.vegetarian || '-' }}
+        </template>
+      </el-table-column>
+      <el-table-column label="过敏食材标注" min-width="140">
+        <template #default="{ row }">
+          {{ row.specialNeeds?.allergyLabel || '-' }}
+        </template>
+      </el-table-column>
+      <el-table-column label="最需要改进的方面" min-width="220" show-overflow-tooltip>
+        <template #default="{ row }">
+          {{ row.suggestions?.improve || '-' }}
+        </template>
+      </el-table-column>
+      <el-table-column label="希望新增哪些菜品或服务" min-width="240" show-overflow-tooltip>
+        <template #default="{ row }">
+          {{ row.suggestions?.newDishes || '-' }}
+        </template>
+      </el-table-column>
+      <el-table-column label="其他想反馈的内容" min-width="200" show-overflow-tooltip>
+        <template #default="{ row }">
+          {{ row.suggestions?.other || '-' }}
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" width="140" fixed="right" align="center">
+        <template #default="{ row }">
+          <el-button link type="primary" @click="openDetail(row)">查看详情</el-button>
+          <el-button v-hasPermi="['satisfactionSurvey:delete']" link type="danger" @click="handleDelete(row.id)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <el-drawer v-model="detailVisible" title="满意度调查详情" size="760px">
+    <template v-if="currentRecord">
+      <el-descriptions :column="2" border class="mb-16px">
+        <el-descriptions-item label="填写身份">
+          {{ currentRecord.role }}
+        </el-descriptions-item>
+        <el-descriptions-item label="填写日期">
+          {{ currentRecord.recordDate || '-' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="院区名称">
+          {{ currentRecord.tenantName || '-' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="提交用户">
+          {{ currentRecord.submitter || '-' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="膳食质量总分">
+          {{ currentRecord.qualityTotal }} 分
+        </el-descriptions-item>
+        <el-descriptions-item label="膳食服务总分">
+          {{ currentRecord.serviceTotal }} 分
+        </el-descriptions-item>
+        <el-descriptions-item label="综合总分">
+          {{ getGrandTotal(currentRecord) }} 分
+        </el-descriptions-item>
+        <el-descriptions-item label="软食/流食需求">
+          {{ currentRecord.specialNeeds?.softFood || '-' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="素食需求">
+          {{ currentRecord.specialNeeds?.vegetarian || '-' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="过敏食材标注" :span="2">
+          {{ currentRecord.specialNeeds?.allergyLabel || '-' }}
+        </el-descriptions-item>
+      </el-descriptions>
+
+      <div class="detail-section">
+        <div class="detail-title">膳食质量评价</div>
+        <el-table :data="currentRecord.qualityItems" border>
+          <el-table-column label="评价项" prop="label" min-width="260" />
+          <el-table-column label="评分" prop="score" width="100" align="center">
+            <template #default="{ row }">{{ row.score }} 分</template>
+          </el-table-column>
+        </el-table>
+      </div>
+
+      <div class="detail-section">
+        <div class="detail-title">膳食服务评价</div>
+        <el-table :data="currentRecord.serviceItems" border>
+          <el-table-column label="评价项" prop="label" min-width="260" />
+          <el-table-column label="评分" prop="score" width="100" align="center">
+            <template #default="{ row }">{{ row.score }} 分</template>
+          </el-table-column>
+        </el-table>
+      </div>
+
+      <div class="detail-section">
+        <div class="detail-title">建议与意见</div>
+        <el-descriptions :column="1" border>
+          <el-descriptions-item label="最需要改进的方面">
+            {{ currentRecord.suggestions.improve || '无' }}
+          </el-descriptions-item>
+          <el-descriptions-item label="希望新增哪些菜品或服务">
+            {{ currentRecord.suggestions.newDishes || '无' }}
+          </el-descriptions-item>
+          <el-descriptions-item label="其他反馈内容">
+            {{ currentRecord.suggestions.other || '无' }}
+          </el-descriptions-item>
+        </el-descriptions>
+      </div>
+    </template>
+  </el-drawer>
+</template>
+
+<script lang="ts" setup>
+import { reactive, ref } from 'vue'
+import dayjs from 'dayjs'
+import { useUserStore } from '@/store/modules/user'
+import download from '@/utils/download'
+import {
+  deleteFoodSatisfactionSurvey,
+  exportFoodSatisfactionSurveyExcel,
+  getFoodSatisfactionSurvey,
+  getFoodSatisfactionSurveyPage
+} from '@/api/system/foods'
+
+defineOptions({ name: 'SatisfactionSurvey' })
+
+type RoleType = 'RELATIVE' | 'EMPLOYEE'
+
+interface ScoreItem {
+  key: string
+  label: string
+  score: number
+}
+
+interface SuggestionInfo {
+  improve: string
+  newDishes: string
+  other: string
+}
+
+interface SurveyRecord {
+  id: number
+  tenantId: number
+  tenantName?: string
+  submitter?: string
+  role?: RoleType
+  recordDate?: string
+  qualityItems: ScoreItem[]
+  qualityTotal?: number
+  serviceItems: ScoreItem[]
+  serviceTotal?: number
+  specialNeeds: {
+    softFood?: string
+    vegetarian?: string
+    allergyLabel?: string
+  }
+  suggestions: SuggestionInfo
+  createTime?: string
+}
+
+interface QueryParams {
+  pageNo: number
+  pageSize: number
+  tenantIds: number[]
+  role?: RoleType
+  recordDate: string[]
+}
+
+const roleOptions = [
+  { label: '家属', value: '家属' },
+  { label: '员工', value: '员工' }
+]
+
+const userStore = useUserStore()
+const message = useMessage()
+
+const qualityItemTemplates: Omit<ScoreItem, 'score'>[] = [
+  { key: 'fresh', label: '1. 菜品新鲜度' },
+  { key: 'taste', label: '2. 菜品口味(咸淡、口感等)' },
+  { key: 'variety', label: '3. 菜品种类丰富度' },
+  { key: 'staple', label: '4. 主食多样性(米饭、面食等)' },
+  { key: 'soup', label: '5. 汤品 / 饮品口感与温度' },
+  { key: 'nutrition', label: '6. 菜品营养搭配合理性' }
+]
+
+const serviceItemTemplates: Omit<ScoreItem, 'score'>[] = [
+  { key: 'tableware', label: '1. 餐具清洁程度' },
+  { key: 'environment', label: '2. 用餐环境卫生(桌椅、地面等)' },
+  { key: 'attitude', label: '3. 工作人员服务态度' },
+  { key: 'timely', label: '4. 菜品供应及时性(未出现长时间断供)' },
+  { key: 'portion', label: '5. 菜品分量充足度' }
+]
+const loading = ref(false)
+const exportLoading = ref(false)
+const total = ref(0)
+const pageList = ref<SurveyRecord[]>([])
+const currentRecord = ref<SurveyRecord>()
+const detailVisible = ref(false)
+const queryFormRef = ref()
+
+const createDefaultQueryParams = (): QueryParams => ({
+  pageNo: 1,
+  pageSize: 10,
+  tenantIds: userStore.orgTenantId || [],
+  role: undefined,
+  recordDate: []
+})
+
+const queryParams = reactive<QueryParams>(createDefaultQueryParams())
+
+const getRoleLabel = (role?: string) => {
+  if (!role) return '-'
+  const hit = roleOptions.find((item) => item.value === role)
+  return hit?.label || role
+}
+
+const getGrandTotal = (row: SurveyRecord) =>
+  Number(row.qualityTotal || 0) + Number(row.serviceTotal || 0)
+
+const getTenantNameById = (tenantId?: number) => {
+  const options = userStore.orgTenant?.options || []
+  const found = options.find((item) => Number(item.value ?? item.id) === Number(tenantId))
+  return found?.label ?? found?.name ?? '-'
+}
+
+const getTenantUserText = (record: SurveyRecord) =>
+  `${record.tenantName || getTenantNameById(record.tenantId)} / ${record.submitter || '-'}`
+
+const normalizeRecord = (raw: any): SurveyRecord => {
+  const qualityItems = qualityItemTemplates.map((item) => ({
+    ...item,
+    score: Number(raw?.[item.key] ?? 0)
+  }))
+  const serviceItems = serviceItemTemplates.map((item) => ({
+    ...item,
+    score: Number(raw?.[item.key] ?? 0)
+  }))
+  return {
+    id: Number(raw?.id),
+    tenantId: Number(raw?.tenantId),
+    tenantName: raw?.tenantName || getTenantNameById(raw?.tenantId),
+    submitter: raw?.submitter,
+    role: raw?.role,
+    recordDate: raw?.recordDate,
+    qualityItems,
+    qualityTotal: Number(raw?.qualityTotal ?? qualityItems.reduce((sum, x) => sum + x.score, 0)),
+    serviceItems,
+    serviceTotal: Number(raw?.serviceTotal ?? serviceItems.reduce((sum, x) => sum + x.score, 0)),
+    specialNeeds: {
+      softFood: raw?.softFood,
+      vegetarian: raw?.vegetarian,
+      allergyLabel: raw?.allergyLabel
+    },
+    suggestions: {
+      improve: raw?.improve,
+      newDishes: raw?.newDishes,
+      other: raw?.other
+    },
+    createTime: raw?.createTime
+  }
+}
+
+const buildParams = (isExport = false) => {
+  const params: any = { ...queryParams }
+  params.tenantIds = params.tenantIds && params.tenantIds.length ? params.tenantIds : undefined
+  params.recordDate = params.recordDate && params.recordDate.length ? params.recordDate : undefined
+  if (isExport) {
+    params.pageNo = 1
+    params.pageSize = 99999
+  }
+  return params
+}
+
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await getFoodSatisfactionSurveyPage(buildParams())
+    pageList.value = (data?.list || []).map((item) => normalizeRecord(item))
+    total.value = data?.total || 0
+  } finally {
+    loading.value = false
+  }
+}
+
+const handleExport = async () => {
+  if (exportLoading.value) return
+  exportLoading.value = true
+  const fullscreenLoading = ElLoading.service({
+    lock: true,
+    text: '正在导出,请稍候...',
+    background: 'rgba(0, 0, 0, 0.7)'
+  })
+  try {
+    const data = await exportFoodSatisfactionSurveyExcel(buildParams(true))
+    download.excel(data as unknown as Blob, `膳食满意度调查_${dayjs().format('YYYY-MM-DD_HH-mm-ss')}.xls`)
+    message.success('导出成功')
+  } catch (error) {
+    console.error('导出失败:', error)
+    message.error('导出失败!')
+  } finally {
+    fullscreenLoading.close()
+    exportLoading.value = false
+  }
+}
+
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+const resetQuery = () => {
+  queryFormRef.value?.resetFields()
+  Object.assign(queryParams, createDefaultQueryParams())
+  handleQuery()
+}
+
+const openDetail = async (row: SurveyRecord) => {
+  try {
+    const data = await getFoodSatisfactionSurvey(Number(row.id))
+    currentRecord.value = normalizeRecord(data)
+    detailVisible.value = true
+  } catch (error) {
+    console.error('获取详情失败:', error)
+    message.error('获取详情失败!')
+  }
+}
+
+const handleDelete = async (id: number) => {
+  try {
+    await message.delConfirm()
+    await deleteFoodSatisfactionSurvey(Number(id))
+    message.success('删除成功')
+    await getList()
+  } catch {}
+}
+
+onMounted(() => {
+  getList()
+})
+</script>
+
+<style lang="scss" scoped>
+.query-form :deep(.el-form-item) {
+  margin-bottom: 16px;
+}
+
+.query-actions :deep(.el-form-item__content) {
+  justify-content: flex-start;
+}
+
+.detail-section {
+  margin-top: 20px;
+}
+
+.detail-title {
+  margin-bottom: 12px;
+  font-size: 16px;
+  font-weight: 600;
+  color: #303133;
+}
+
+</style>