Forráskód Böngészése

feat(@six/smart-pharmacy): 智慧药事系统第二版-满意度评价静态页面新增

cmj 4 hete
szülő
commit
bf6e789bd6

+ 22 - 0
apps/smart-pharmacy/public/database/menu.json

@@ -124,5 +124,27 @@
         "component": "/prescription-review/indicator-library/list"
       }
     ]
+  },
+  {
+    "id": "2600",
+    "meta": {
+      "icon": "mdi:star-outline",
+      "order": 9995,
+      "title": "患者评价"
+    },
+    "name": "PatientEvaluation",
+    "path": "/patient-evaluation",
+    "children": [
+      {
+        "id": "2601",
+        "path": "/patient-evaluation/satisfaction",
+        "name": "PatientEvaluationSatisfaction",
+        "meta": {
+          "icon": "mdi:emoticon-happy-outline",
+          "title": "满意度评价"
+        },
+        "component": "/patient-evaluation/satisfaction/list"
+      }
+    ]
   }
 ]

+ 1 - 0
apps/smart-pharmacy/src/api/index.ts

@@ -16,6 +16,7 @@ export * from './method/access';
 export * from './method/business';
 export * from './method/common';
 export * from './method/dict';
+export * from './method/patient-evaluation';
 export * from './method/prescription';
 export * from './method/prescription-review';
 export * from './method/system';

+ 485 - 0
apps/smart-pharmacy/src/api/method/patient-evaluation.ts

@@ -0,0 +1,485 @@
+import type { TransformList, TransformRecord } from '#/api';
+
+/** 患者评价(接口就绪后替换为真实请求) */
+export namespace PatientEvaluationModel {
+  export type SatisfactionScore = 1 | 2 | 3 | 4 | 5;
+
+  export interface SatisfactionEvaluation extends TransformRecord {
+    prescriptionDate: string;
+    prescriptionNumber: string;
+    /** 关联处方 id,用于跳转处方详情 */
+    prescriptionId?: string;
+    medicalInstitution?: string;
+    campus?: string;
+    decoctionEnterprise?: string;
+    decoctionCenter?: string;
+    logisticsCompany?: string;
+    logisticsNumber?: string;
+    patientName?: string;
+    gender?: string;
+    age?: number | string;
+    patientPhone?: string;
+    /** 代煎代配 */
+    decoctionDispensing?: string;
+    satisfactionScore: SatisfactionScore;
+    evaluationTime: string;
+    /** 满意度评价详情 */
+    evaluationDetail?: string;
+    /** 改进建议 */
+    improvementSuggestion?: string;
+    remark?: string;
+  }
+
+  export interface SatisfactionListQuery {
+    dateRange?: [string, string];
+  }
+}
+
+export const SATISFACTION_SCORE_LABELS: Record<
+  PatientEvaluationModel.SatisfactionScore,
+  string
+> = {
+  1: '1分:非常不满意',
+  2: '2分:不满意',
+  3: '3分:一般',
+  4: '4分:满意',
+  5: '5分:非常满意',
+};
+
+export function getSatisfactionScoreLabel(
+  score?: PatientEvaluationModel.SatisfactionScore,
+): string {
+  if (!score) return '';
+  return SATISFACTION_SCORE_LABELS[score] ?? '';
+}
+
+const MOCK_DETAIL_TEXT = [
+  '内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容',
+  '内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容',
+  '内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容',
+  '内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容',
+].join('\n');
+
+function withDetailFields(
+  item: PatientEvaluationModel.SatisfactionEvaluation,
+): PatientEvaluationModel.SatisfactionEvaluation {
+  return {
+    ...item,
+    prescriptionId: item.prescriptionId ?? item.id,
+    logisticsNumber: item.logisticsNumber ?? '',
+    patientName: item.patientName ?? '',
+    gender: item.gender ?? '',
+    age: item.age ?? '',
+    patientPhone: item.patientPhone ?? '',
+    decoctionDispensing: item.decoctionDispensing ?? '',
+    evaluationDetail:
+      item.evaluationDetail ?? item.remark ?? MOCK_DETAIL_TEXT,
+    improvementSuggestion: item.improvementSuggestion ?? MOCK_DETAIL_TEXT,
+  };
+}
+
+const MOCK_SATISFACTION_EVALUATIONS: PatientEvaluationModel.SatisfactionEvaluation[] =
+  [
+    {
+      id: '1',
+      prescriptionDate: '2027-3-20',
+      prescriptionNumber: '944895756806594342',
+      prescriptionId: '1',
+      medicalInstitution: '蒋村社区卫生服务中心',
+      campus: '',
+      decoctionEnterprise: '重药(浙江)控股',
+      decoctionCenter: '重药煎药中心华东区',
+      logisticsCompany: '顺丰速运',
+      logisticsNumber: 'SF73648596038958987',
+      patientName: '唐理理',
+      gender: '女',
+      age: 48,
+      patientPhone: '13828394123',
+      decoctionDispensing: '代煎',
+      satisfactionScore: 5,
+      evaluationTime: '2024-01-06 10:30:00',
+      evaluationDetail: MOCK_DETAIL_TEXT,
+      improvementSuggestion: MOCK_DETAIL_TEXT,
+    },
+    {
+      id: '2',
+      prescriptionDate: '2024-01-08',
+      prescriptionNumber: 'CF20240108002',
+      medicalInstitution: '西溪社区卫生服务中心',
+      campus: '',
+      decoctionEnterprise: '杭州中药煎配中心',
+      decoctionCenter: '西溪煎药中心',
+      logisticsCompany: '京东物流',
+      satisfactionScore: 1,
+      evaluationTime: '2024-01-09 14:20:00',
+      remark: '等待时间过长',
+    },
+    {
+      id: '3',
+      prescriptionDate: '2024-01-10',
+      prescriptionNumber: 'CF20240110003',
+      medicalInstitution: '文新社区卫生服务中心',
+      campus: '文新院区',
+      decoctionEnterprise: '',
+      decoctionCenter: '文新煎药中心',
+      logisticsCompany: '',
+      satisfactionScore: 3,
+      evaluationTime: '2024-01-11 09:15:00',
+    },
+    {
+      id: '4',
+      prescriptionDate: '2024-01-12',
+      prescriptionNumber: 'CF20240112004',
+      medicalInstitution: '蒋村社区卫生服务中心',
+      campus: '蒋村院区',
+      decoctionEnterprise: '杭州中药煎配中心',
+      decoctionCenter: '西湖煎药中心',
+      logisticsCompany: '顺丰速运',
+      satisfactionScore: 5,
+      evaluationTime: '2024-01-13 16:45:00',
+    },
+    {
+      id: '5',
+      prescriptionDate: '2024-01-15',
+      prescriptionNumber: 'CF20240115005',
+      medicalInstitution: '西溪社区卫生服务中心',
+      campus: '西溪院区',
+      decoctionEnterprise: '浙江煎药企业',
+      decoctionCenter: '西溪煎药中心',
+      logisticsCompany: '圆通速递',
+      satisfactionScore: 4,
+      evaluationTime: '2024-01-16 11:00:00',
+    },
+    {
+      id: '6',
+      prescriptionDate: '2024-01-18',
+      prescriptionNumber: 'CF20240118006',
+      medicalInstitution: '文新社区卫生服务中心',
+      campus: '',
+      decoctionEnterprise: '杭州中药煎配中心',
+      decoctionCenter: '文新煎药中心',
+      logisticsCompany: '顺丰速运',
+      satisfactionScore: 2,
+      evaluationTime: '2024-01-19 08:30:00',
+      remark: '包装有破损',
+    },
+    {
+      id: '7',
+      prescriptionDate: '2024-01-20',
+      prescriptionNumber: 'CF20240120007',
+      medicalInstitution: '蒋村社区卫生服务中心',
+      campus: '蒋村院区',
+      decoctionEnterprise: '浙江煎药企业',
+      decoctionCenter: '西湖煎药中心',
+      logisticsCompany: '京东物流',
+      satisfactionScore: 5,
+      evaluationTime: '2024-01-21 13:20:00',
+    },
+    {
+      id: '8',
+      prescriptionDate: '2024-01-22',
+      prescriptionNumber: 'CF20240122008',
+      medicalInstitution: '西溪社区卫生服务中心',
+      campus: '西溪院区',
+      decoctionEnterprise: '',
+      decoctionCenter: '西溪煎药中心',
+      logisticsCompany: '顺丰速运',
+      satisfactionScore: 3,
+      evaluationTime: '2024-01-23 15:10:00',
+    },
+    {
+      id: '9',
+      prescriptionDate: '2024-01-25',
+      prescriptionNumber: 'CF20240125009',
+      medicalInstitution: '文新社区卫生服务中心',
+      campus: '文新院区',
+      decoctionEnterprise: '杭州中药煎配中心',
+      decoctionCenter: '文新煎药中心',
+      logisticsCompany: '圆通速递',
+      satisfactionScore: 1,
+      evaluationTime: '2024-01-26 10:00:00',
+    },
+    {
+      id: '10',
+      prescriptionDate: '2024-01-28',
+      prescriptionNumber: 'CF20240128010',
+      medicalInstitution: '蒋村社区卫生服务中心',
+      campus: '蒋村院区',
+      decoctionEnterprise: '杭州中药煎配中心',
+      decoctionCenter: '西湖煎药中心',
+      logisticsCompany: '顺丰速运',
+      satisfactionScore: 4,
+      evaluationTime: '2024-01-29 17:30:00',
+    },
+    {
+      id: '11',
+      prescriptionDate: '2024-02-01',
+      prescriptionNumber: 'CF20240201011',
+      medicalInstitution: '西溪社区卫生服务中心',
+      campus: '',
+      decoctionEnterprise: '浙江煎药企业',
+      decoctionCenter: '西溪煎药中心',
+      logisticsCompany: '京东物流',
+      satisfactionScore: 5,
+      evaluationTime: '2024-02-02 09:45:00',
+    },
+    {
+      id: '12',
+      prescriptionDate: '2024-02-03',
+      prescriptionNumber: 'CF20240203012',
+      medicalInstitution: '文新社区卫生服务中心',
+      campus: '文新院区',
+      decoctionEnterprise: '杭州中药煎配中心',
+      decoctionCenter: '文新煎药中心',
+      logisticsCompany: '',
+      satisfactionScore: 3,
+      evaluationTime: '2024-02-04 14:00:00',
+    },
+    {
+      id: '13',
+      prescriptionDate: '2024-02-05',
+      prescriptionNumber: 'CF20240205013',
+      medicalInstitution: '蒋村社区卫生服务中心',
+      campus: '蒋村院区',
+      decoctionEnterprise: '杭州中药煎配中心',
+      decoctionCenter: '西湖煎药中心',
+      logisticsCompany: '顺丰速运',
+      satisfactionScore: 5,
+      evaluationTime: '2024-02-06 11:30:00',
+    },
+    {
+      id: '14',
+      prescriptionDate: '2024-02-08',
+      prescriptionNumber: 'CF20240208014',
+      medicalInstitution: '西溪社区卫生服务中心',
+      campus: '西溪院区',
+      decoctionEnterprise: '',
+      decoctionCenter: '西溪煎药中心',
+      logisticsCompany: '圆通速递',
+      satisfactionScore: 2,
+      evaluationTime: '2024-02-09 16:20:00',
+    },
+    {
+      id: '15',
+      prescriptionDate: '2024-02-10',
+      prescriptionNumber: 'CF20240210015',
+      medicalInstitution: '文新社区卫生服务中心',
+      campus: '',
+      decoctionEnterprise: '浙江煎药企业',
+      decoctionCenter: '文新煎药中心',
+      logisticsCompany: '顺丰速运',
+      satisfactionScore: 4,
+      evaluationTime: '2024-02-11 08:50:00',
+    },
+    {
+      id: '16',
+      prescriptionDate: '2024-02-12',
+      prescriptionNumber: 'CF20240212016',
+      medicalInstitution: '蒋村社区卫生服务中心',
+      campus: '蒋村院区',
+      decoctionEnterprise: '杭州中药煎配中心',
+      decoctionCenter: '西湖煎药中心',
+      logisticsCompany: '京东物流',
+      satisfactionScore: 1,
+      evaluationTime: '2024-02-13 12:10:00',
+    },
+    {
+      id: '17',
+      prescriptionDate: '2024-02-15',
+      prescriptionNumber: 'CF20240215017',
+      medicalInstitution: '西溪社区卫生服务中心',
+      campus: '西溪院区',
+      decoctionEnterprise: '杭州中药煎配中心',
+      decoctionCenter: '西溪煎药中心',
+      logisticsCompany: '顺丰速运',
+      satisfactionScore: 5,
+      evaluationTime: '2024-02-16 15:40:00',
+    },
+    {
+      id: '18',
+      prescriptionDate: '2024-02-18',
+      prescriptionNumber: 'CF20240218018',
+      medicalInstitution: '文新社区卫生服务中心',
+      campus: '文新院区',
+      decoctionEnterprise: '浙江煎药企业',
+      decoctionCenter: '文新煎药中心',
+      logisticsCompany: '圆通速递',
+      satisfactionScore: 3,
+      evaluationTime: '2024-02-19 10:25:00',
+    },
+    {
+      id: '19',
+      prescriptionDate: '2024-02-20',
+      prescriptionNumber: 'CF20240220019',
+      medicalInstitution: '蒋村社区卫生服务中心',
+      campus: '',
+      decoctionEnterprise: '杭州中药煎配中心',
+      decoctionCenter: '西湖煎药中心',
+      logisticsCompany: '',
+      satisfactionScore: 4,
+      evaluationTime: '2024-02-21 13:55:00',
+    },
+    {
+      id: '20',
+      prescriptionDate: '2024-02-22',
+      prescriptionNumber: 'CF20240222020',
+      medicalInstitution: '西溪社区卫生服务中心',
+      campus: '西溪院区',
+      decoctionEnterprise: '杭州中药煎配中心',
+      decoctionCenter: '西溪煎药中心',
+      logisticsCompany: '顺丰速运',
+      satisfactionScore: 5,
+      evaluationTime: '2024-02-23 09:00:00',
+    },
+    {
+      id: '21',
+      prescriptionDate: '2024-02-25',
+      prescriptionNumber: 'CF20240225021',
+      medicalInstitution: '文新社区卫生服务中心',
+      campus: '文新院区',
+      decoctionEnterprise: '',
+      decoctionCenter: '文新煎药中心',
+      logisticsCompany: '京东物流',
+      satisfactionScore: 2,
+      evaluationTime: '2024-02-26 14:30:00',
+    },
+    {
+      id: '22',
+      prescriptionDate: '2024-02-28',
+      prescriptionNumber: 'CF20240228022',
+      medicalInstitution: '蒋村社区卫生服务中心',
+      campus: '蒋村院区',
+      decoctionEnterprise: '浙江煎药企业',
+      decoctionCenter: '西湖煎药中心',
+      logisticsCompany: '顺丰速运',
+      satisfactionScore: 3,
+      evaluationTime: '2024-03-01 11:15:00',
+    },
+    {
+      id: '23',
+      prescriptionDate: '2024-03-02',
+      prescriptionNumber: 'CF20240302023',
+      medicalInstitution: '西溪社区卫生服务中心',
+      campus: '',
+      decoctionEnterprise: '杭州中药煎配中心',
+      decoctionCenter: '西溪煎药中心',
+      logisticsCompany: '圆通速递',
+      satisfactionScore: 5,
+      evaluationTime: '2024-03-03 16:00:00',
+    },
+    {
+      id: '24',
+      prescriptionDate: '2024-03-05',
+      prescriptionNumber: 'CF20240305024',
+      medicalInstitution: '文新社区卫生服务中心',
+      campus: '文新院区',
+      decoctionEnterprise: '杭州中药煎配中心',
+      decoctionCenter: '文新煎药中心',
+      logisticsCompany: '顺丰速运',
+      satisfactionScore: 1,
+      evaluationTime: '2024-03-06 08:40:00',
+    },
+    {
+      id: '25',
+      prescriptionDate: '2024-03-08',
+      prescriptionNumber: 'CF20240308025',
+      medicalInstitution: '蒋村社区卫生服务中心',
+      campus: '蒋村院区',
+      decoctionEnterprise: '浙江煎药企业',
+      decoctionCenter: '西湖煎药中心',
+      logisticsCompany: '京东物流',
+      satisfactionScore: 4,
+      evaluationTime: '2024-03-09 12:50:00',
+    },
+  ];
+
+function filterSatisfactionEvaluations(
+  items: PatientEvaluationModel.SatisfactionEvaluation[],
+  query?: PatientEvaluationModel.SatisfactionListQuery,
+) {
+  const range = query?.dateRange;
+  if (!range?.length || range.length < 2 || !range[0] || !range[1]) {
+    return items;
+  }
+  const [start, end] = range;
+  return items.filter(
+    (item) => item.prescriptionDate >= start && item.prescriptionDate <= end,
+  );
+}
+
+/** 满意度评价列表(当前为本地 mock,后期对接后端分页接口) */
+export function listSatisfactionEvaluationsMethod(
+  page = 1,
+  size = 10,
+  query?: PatientEvaluationModel.SatisfactionListQuery,
+): Promise<TransformList<PatientEvaluationModel.SatisfactionEvaluation>> {
+  const filtered = filterSatisfactionEvaluations(
+    MOCK_SATISFACTION_EVALUATIONS,
+    query,
+  );
+  const start = (page - 1) * size;
+  const items = filtered.slice(start, start + size);
+  return Promise.resolve({
+    items,
+    total: filtered.length,
+    data: { page, size, total: filtered.length },
+  });
+}
+
+/** 满意度评价详情(当前为本地 mock,后期对接后端接口) */
+export function getSatisfactionEvaluationMethod(id: string) {
+  const item = MOCK_SATISFACTION_EVALUATIONS.find((row) => row.id === id);
+  if (!item) {
+    return Promise.reject(new Error('评价记录不存在'));
+  }
+  return Promise.resolve(withDetailFields(item));
+}
+
+/** 导出满意度评价(当前为本地 mock,后期对接后端导出接口) */
+export function exportSatisfactionEvaluationsMethod(
+  query?: PatientEvaluationModel.SatisfactionListQuery,
+) {
+  const filtered = filterSatisfactionEvaluations(
+    MOCK_SATISFACTION_EVALUATIONS,
+    query,
+  );
+  const headers = [
+    '处方日期',
+    '处方号',
+    '医疗机构',
+    '院区',
+    '煎药企业',
+    '煎药中心',
+    '物流公司',
+    '满意度评分',
+    '评价时间',
+    '评价备注',
+  ];
+  const rows = filtered.map((item) => [
+    item.prescriptionDate,
+    item.prescriptionNumber,
+    item.medicalInstitution ?? '',
+    item.campus ?? '',
+    item.decoctionEnterprise ?? '',
+    item.decoctionCenter ?? '',
+    item.logisticsCompany ?? '',
+    getSatisfactionScoreLabel(item.satisfactionScore),
+    item.evaluationTime,
+    item.remark ?? '',
+  ]);
+  const csvContent = [headers, ...rows]
+    .map((row) =>
+      row.map((cell) => `"${String(cell).replaceAll('"', '""')}"`).join(','),
+    )
+    .join('\n');
+  const blob = new Blob([`\uFEFF${csvContent}`], {
+    type: 'text/csv;charset=utf-8;',
+  });
+  const url = URL.createObjectURL(blob);
+  const link = document.createElement('a');
+  link.href = url;
+  link.download = `满意度评价_${new Date().toISOString().slice(0, 10)}.csv`;
+  link.click();
+  URL.revokeObjectURL(url);
+  return Promise.resolve(true);
+}

+ 15 - 4
apps/smart-pharmacy/src/api/model/menu.ts

@@ -44,8 +44,15 @@ export const HARDCODED_MENU_TREE_SELECT: TreeSelectMenuNode[] = [
       { id: '2502', label: '点评指标库' },
     ],
   },
+  {
+    id: '2600',
+    label: '患者评价',
+    children: [{ id: '2601', label: '满意度评价' }],
+  },
 ];
 
+const HARDCODED_MENU_ROOT_IDS = ['2500', '2600'];
+
 /** 将本地写死菜单合并进后端 treeselect 结果 */
 export function mergeHardcodedMenuTree(
   nodes?: TreeSelectMenuNode[],
@@ -77,10 +84,14 @@ export function ensureHardcodedMenusVisible(
   filtered: SystemModel.Menu[],
   all: SystemModel.Menu[],
 ): SystemModel.Menu[] {
-  const hardRoot = all.find((menu) => String(menu.id) === '2500');
-  if (!hardRoot) return filtered;
-  if (filtered.some((menu) => String(menu.id) === '2500')) return filtered;
-  return [...filtered, hardRoot].sort(
+  let result = [...filtered];
+  for (const rootId of HARDCODED_MENU_ROOT_IDS) {
+    const hardRoot = all.find((menu) => String(menu.id) === rootId);
+    if (!hardRoot) continue;
+    if (result.some((menu) => String(menu.id) === rootId)) continue;
+    result = [...result, hardRoot];
+  }
+  return result.sort(
     (a, b) => (a.meta.order ?? 999_999) - (b.meta.order ?? 999_999),
   );
 }

+ 95 - 0
apps/smart-pharmacy/src/views/patient-evaluation/satisfaction/data.ts

@@ -0,0 +1,95 @@
+import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
+
+import type { VbenFormSchema } from '#/adapter/form';
+import type { OnActionClickFn } from '#/adapter/vxe-table';
+import type { PatientEvaluationModel } from '#/api/method/patient-evaluation';
+
+import { getSatisfactionScoreLabel } from '#/api/method/patient-evaluation';
+
+export function useSatisfactionSearchFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      component: 'RangePicker',
+      fieldName: 'dateRange',
+      label: '日期范围',
+      componentProps: {
+        valueFormat: 'YYYY-MM-DD',
+        style: { width: '100%' },
+        placeholder: ['开始日期', '结束日期'],
+        inputReadOnly: true,
+      },
+    },
+  ];
+}
+
+export function useSatisfactionTableColumns(
+  onActionClick?: OnActionClickFn<PatientEvaluationModel.SatisfactionEvaluation>,
+): VxeTableGridOptions<PatientEvaluationModel.SatisfactionEvaluation>['columns'] {
+  return [
+    { type: 'seq', title: '序号', width: 60 },
+    {
+      field: 'prescriptionDate',
+      minWidth: 110,
+      title: '处方日期',
+    },
+    {
+      field: 'prescriptionNumber',
+      minWidth: 130,
+      title: '处方号',
+    },
+    {
+      field: 'medicalInstitution',
+      minWidth: 160,
+      title: '医疗机构',
+    },
+    {
+      field: 'campus',
+      minWidth: 100,
+      title: '院区',
+    },
+    {
+      field: 'decoctionEnterprise',
+      minWidth: 140,
+      title: '煎药企业',
+    },
+    {
+      field: 'decoctionCenter',
+      minWidth: 120,
+      title: '煎药中心',
+    },
+    {
+      field: 'logisticsCompany',
+      minWidth: 100,
+      title: '物流公司',
+    },
+    {
+      field: 'satisfactionScore',
+      minWidth: 140,
+      title: '满意度评分',
+      slots: {
+        default: ({ row }) => getSatisfactionScoreLabel(row.satisfactionScore),
+      },
+    },
+    {
+      field: 'evaluationTime',
+      minWidth: 160,
+      title: '评价时间',
+    },
+    {
+      align: 'center',
+      cellRender: {
+        name: 'CellOperation',
+        options: [{ code: 'view', text: '查看' }],
+        attrs: {
+          nameField: 'prescriptionNumber',
+          nameTitle: '满意度评价',
+          onClick: onActionClick,
+        },
+      },
+      field: 'operation',
+      fixed: 'right',
+      title: '操作',
+      width: 80,
+    },
+  ];
+}

+ 100 - 0
apps/smart-pharmacy/src/views/patient-evaluation/satisfaction/list.vue

@@ -0,0 +1,100 @@
+<script lang="ts" setup>
+import type {
+  OnActionClickParams,
+  VxeTableGridOptions,
+} from '#/adapter/vxe-table';
+import type { PatientEvaluationModel } from '#/api';
+
+import { Page, useVbenModal } from '@vben/common-ui';
+
+import { Button, message } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  exportSatisfactionEvaluationsMethod,
+  listSatisfactionEvaluationsMethod,
+} from '#/api';
+
+import {
+  useSatisfactionSearchFormSchema,
+  useSatisfactionTableColumns,
+} from './data';
+import Detail from './modules/detail.vue';
+
+const [DetailModal, detailModalApi] = useVbenModal({
+  connectedComponent: Detail,
+  destroyOnClose: true,
+});
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useSatisfactionSearchFormSchema(),
+    submitOnChange: false,
+    wrapperClass: 'satisfaction-search-form',
+  },
+  gridOptions: {
+    columns: useSatisfactionTableColumns(onActionClick),
+    height: 'auto',
+    keepSource: true,
+    pagerConfig: {
+      pageSize: 5,
+      layouts: ['PrevPage', 'Jump', 'NextPage'],
+    },
+    proxyConfig: {
+      ajax: {
+        query({ page }, formValues) {
+          return listSatisfactionEvaluationsMethod(
+            page.currentPage,
+            page.pageSize,
+            formValues,
+          );
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+    },
+    stripe: true,
+  } as VxeTableGridOptions<PatientEvaluationModel.SatisfactionEvaluation>,
+});
+
+function onActionClick(
+  e: OnActionClickParams<PatientEvaluationModel.SatisfactionEvaluation>,
+) {
+  switch (e.code) {
+    case 'view': {
+      onViewHandle(e.row);
+      break;
+    }
+  }
+}
+
+function onViewHandle(row: PatientEvaluationModel.SatisfactionEvaluation) {
+  detailModalApi.setData(row).open();
+}
+
+async function onExport() {
+  const formValues = await gridApi.formApi.getValues();
+  try {
+    await exportSatisfactionEvaluationsMethod(formValues);
+    message.success('导出成功');
+  } catch (error: any) {
+    message.error(error.message || '导出失败');
+  }
+}
+</script>
+<template>
+  <Page auto-content-height class="satisfaction-page">
+    <DetailModal />
+    <Grid>
+      <template #toolbar-tools>
+        <Button type="link" @click="onExport">导出</Button>
+      </template>
+    </Grid>
+  </Page>
+</template>
+<style scoped>
+.satisfaction-page :deep(.satisfaction-search-form) {
+  margin-bottom: 0;
+}
+</style>

+ 202 - 0
apps/smart-pharmacy/src/views/patient-evaluation/satisfaction/modules/detail.vue

@@ -0,0 +1,202 @@
+<script lang="ts" setup>
+import type { PatientEvaluationModel } from '#/api/method/patient-evaluation';
+
+import { computed, ref } from 'vue';
+import { useRouter } from 'vue-router';
+
+import { useVbenModal } from '@vben/common-ui';
+
+import { Spin } from 'ant-design-vue';
+
+import { getSatisfactionEvaluationMethod } from '#/api';
+import { getSatisfactionScoreLabel } from '#/api/method/patient-evaluation';
+import { maskPhone } from '#/utils/mask-phone';
+
+const router = useRouter();
+const loading = ref(false);
+const detail = ref<PatientEvaluationModel.SatisfactionEvaluation>(
+  {} as PatientEvaluationModel.SatisfactionEvaluation,
+);
+
+const overallScoreText = computed(() => {
+  const label = getSatisfactionScoreLabel(detail.value.satisfactionScore);
+  if (!label) return '-';
+  return `药事服务整体满意度评价:${label}`;
+});
+
+const [Modal, modalApi] = useVbenModal({
+  class: 'satisfaction-detail-modal !w-[calc(100%-120px)] !max-w-[720px]',
+  showConfirmButton: false,
+  async onOpenChange(isOpen) {
+    if (!isOpen) {
+      detail.value = {} as PatientEvaluationModel.SatisfactionEvaluation;
+      return;
+    }
+    const data = modalApi.getData<PatientEvaluationModel.SatisfactionEvaluation>();
+    if (!data?.id) return;
+    loading.value = true;
+    try {
+      detail.value = await getSatisfactionEvaluationMethod(data.id);
+    } finally {
+      loading.value = false;
+    }
+  },
+});
+
+function displayValue(value?: number | string) {
+  if (value === undefined || value === null || value === '') return '';
+  return String(value);
+}
+
+function goPrescriptionDetail() {
+  const prescriptionId = detail.value.prescriptionId ?? detail.value.id;
+  if (!prescriptionId) return;
+  modalApi.close();
+  router.push(`/prescription/detail/${prescriptionId}`);
+}
+</script>
+<template>
+  <Modal title="满意度评价详情">
+    <Spin :spinning="loading">
+      <div class="satisfaction-detail">
+        <div class="info-grid">
+          <div class="info-item">
+            <span class="info-label">处方号:</span>
+            <a
+              v-if="detail.prescriptionNumber"
+              class="info-link"
+              @click.prevent="goPrescriptionDetail"
+            >
+              {{ detail.prescriptionNumber }}
+            </a>
+            <span v-else>-</span>
+          </div>
+          <div class="info-item">
+            <span class="info-label">处方日期:</span>
+            <span>{{ displayValue(detail.prescriptionDate) || '-' }}</span>
+          </div>
+          <div class="info-item">
+            <span class="info-label">医疗机构:</span>
+            <span>{{ displayValue(detail.medicalInstitution) || '-' }}</span>
+          </div>
+          <div class="info-item">
+            <span class="info-label">院区:</span>
+            <span>{{ displayValue(detail.campus) || '-' }}</span>
+          </div>
+          <div class="info-item">
+            <span class="info-label">煎药企业:</span>
+            <span>{{ displayValue(detail.decoctionEnterprise) || '-' }}</span>
+          </div>
+          <div class="info-item">
+            <span class="info-label">煎药中心:</span>
+            <span>{{ displayValue(detail.decoctionCenter) || '-' }}</span>
+          </div>
+          <div class="info-item">
+            <span class="info-label">物流公司:</span>
+            <span>{{ displayValue(detail.logisticsCompany) || '-' }}</span>
+          </div>
+          <div class="info-item">
+            <span class="info-label">物流单号:</span>
+            <span>{{ displayValue(detail.logisticsNumber) || '-' }}</span>
+          </div>
+          <div class="info-item">
+            <span class="info-label">患者姓名:</span>
+            <span>{{ displayValue(detail.patientName) || '-' }}</span>
+          </div>
+          <div class="info-item">
+            <span class="info-label">性别:</span>
+            <span>{{ displayValue(detail.gender) || '-' }}</span>
+          </div>
+          <div class="info-item">
+            <span class="info-label">年龄:</span>
+            <span>{{ displayValue(detail.age) || '-' }}</span>
+          </div>
+          <div class="info-item">
+            <span class="info-label">患者电话:</span>
+            <span>{{ maskPhone(detail.patientPhone) || '-' }}</span>
+          </div>
+          <div class="info-item">
+            <span class="info-label">代煎代配:</span>
+            <span>{{ displayValue(detail.decoctionDispensing) || '-' }}</span>
+          </div>
+        </div>
+
+        <div class="overall-score">{{ overallScoreText }}</div>
+
+        <div class="detail-block">
+          <div class="detail-block-title">满意度评价详情:</div>
+          <div class="detail-block-content whitespace-pre-line">
+            {{ detail.evaluationDetail || '-' }}
+          </div>
+        </div>
+
+        <div class="detail-block">
+          <div class="detail-block-title">改进建议:</div>
+          <div class="detail-block-content whitespace-pre-line">
+            {{ detail.improvementSuggestion || '-' }}
+          </div>
+        </div>
+      </div>
+    </Spin>
+  </Modal>
+</template>
+<style scoped>
+.satisfaction-detail {
+  padding: 8px 24px 12px;
+}
+
+.info-grid {
+  display: grid;
+  grid-template-columns: repeat(4, minmax(0, 1fr));
+  gap: 14px 16px;
+}
+
+.info-item {
+  min-width: 0;
+  line-height: 1.6;
+  word-break: break-all;
+}
+
+.info-label {
+  color: rgb(0 0 0 / 85%);
+}
+
+.info-link {
+  color: #1677ff;
+  cursor: pointer;
+}
+
+.info-link:hover {
+  color: #4096ff;
+}
+
+.overall-score {
+  margin: 28px 0 24px;
+  color: #f5a623;
+  font-size: 16px;
+  line-height: 1.6;
+  text-align: center;
+}
+
+.detail-block {
+  margin-bottom: 20px;
+  text-align: center;
+}
+
+.detail-block:last-child {
+  margin-bottom: 0;
+}
+
+.detail-block-title {
+  margin-bottom: 8px;
+  color: rgb(0 0 0 / 85%);
+  line-height: 1.6;
+}
+
+.detail-block-content {
+  color: rgb(0 0 0 / 65%);
+  line-height: 1.8;
+  text-align: center;
+  white-space: pre-line;
+}
+</style>