企业项目管理、ORK、研发管理与敏捷开发工具平台

网站首页 > 精选文章 正文

医院患者陪诊智能体 - Vue3+TypeScript 前端工程

wudianyun 2025-09-29 13:51:30 精选文章 14 ℃

一、工程结构设计(Feature-Based 领域划分)

plaintext

patient-companion-agent/
├── .vscode/                  # VS Code配置
│   ├── extensions.json       # 推荐插件
│   └── settings.json         # 编辑器配置
├── public/                   # 静态资源
│   ├── favicon.ico
│   └── index.html
├── src/
│   ├── assets/               # 全局资源(样式、图片)
│   │   ├── styles/           # 全局样式
│   │   │   ├── main.scss
│   │   │   └── variables.scss
│   │   └── images/           # 图片资源
│   ├── common/               # 公共能力(工具、hooks、组件)
│   │   ├── components/       # 公共组件(按钮、输入框等)
│   │   │   ├── BaseButton/
│   │   │   └── BaseInput/
│   │   ├── hooks/            # 公共hooks
│   │   │   ├── useTracker.ts # 埋点统计
│   │   │   ├── useError.ts   # 错误处理
│   │   │   └── useStorage.ts # 安全存储
│   │   ├── types/            # 公共类型
│   │   │   └── common.ts
│   │   └── utils/            # 工具函数
│   │       ├── format.ts     # 格式化(日期、脱敏)
│   │       ├── security.ts   # 安全处理(XSS、加密)
│   │       └── request.ts    # API请求封装
│   ├── modules/              # 业务领域模块(DDD划分)
│   │   ├── patient/          # 用户域(患者信息)
│   │   │   ├── components/   # 域内组件
│   │   │   ├── hooks/        # 域内hooks
│   │   │   ├── services/     # 域内服务(业务逻辑+API)
│   │   │   ├── store/        # 域内状态
│   │   │   ├── types/        # 域内类型
│   │   │   └── index.ts      # 域导出入口
│   │   ├── companion/        # 陪诊服务域(核心业务)
│   │   │   ├── components/   # 挂号引导、流程查询等组件
│   │   │   ├── hooks/        # 陪诊相关hooks
│   │   │   ├── services/     # 陪诊服务逻辑
│   │   │   ├── store/        # 陪诊状态
│   │   │   ├── types/        # 陪诊类型
│   │   │   └── index.ts
│   │   └── knowledge/        # 知识库域(常见问题)
│   │       ├── components/   # FAQ列表、搜索组件
│   │       ├── services/     # 知识库服务
│   │       ├── store/        # 知识库状态
│   │       ├── types/        # 知识库类型
│   │       └── index.ts
│   ├── router/               # 路由配置
│   │   ├── index.ts
│   │   └── modules/          # 模块路由拆分
│   ├── store/                # Pinia根配置
│   │   └── index.ts
│   ├── App.vue               # 根组件
│   ├── main.ts               # 入口文件
│   └── env.d.ts              # 环境声明
├── .eslintrc.js              # ESLint配置
├── .prettierrc               # Prettier配置
├── .stylelintrc.js           # Stylelint配置
├── commitlint.config.js      # Commit规范配置
├── package.json              # 依赖配置
├── tsconfig.json             # TypeScript配置
└── vite.config.ts            # Vite构建配置

二、核心配置文件(确保规范落地)

1. package.json(依赖与脚本)

json

{
  "name": "patient-companion-agent",
  "private": true,
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vue-tsc && vite build",
    "preview": "vite preview",
    "lint:eslint": "eslint . --ext .vue,.js,.ts --fix",
    "lint:style": "stylelint \"src/**/*.{vue,scss}\" --fix",
    "prepare": "husky install"
  },
  "dependencies": {
    "axios": "^1.6.8",
    "crypto-js": "^4.2.0",
    "element-plus": "^2.7.0",
    "pinia": "^2.1.7",
    "pinia-plugin-persistedstate": "^3.2.1",
    "vue": "^3.4.21",
    "vue-router": "^4.3.0"
  },
  "devDependencies": {
    "@commitlint/cli": "^19.3.0",
    "@commitlint/config-conventional": "^19.2.2",
    "@vitejs/plugin-legacy": "^5.3.0",
    "@vitejs/plugin-vue": "^5.0.4",
    "@vitejs/plugin-vue-jsx": "^3.1.0",
    "@vue/eslint-config-prettier": "^8.0.0",
    "@vue/eslint-config-typescript": "^13.0.0",
    "eslint": "^8.57.0",
    "eslint-plugin-vue": "^9.23.0",
    "husky": "^9.0.11",
    "prettier": "^3.2.5",
    "sass": "^1.77.0",
    "stylelint": "^16.5.0",
    "stylelint-config-recommended-scss": "^14.0.0",
    "stylelint-config-standard": "^36.0.0",
    "stylelint-scss": "^6.3.0",
    "typescript": "^5.4.5",
    "vite": "^5.2.11",
    "vue-tsc": "^2.0.19"
  }
}

2. vite.config.ts(构建与兼容性)

typescript

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import legacy from '@vitejs/plugin-legacy';
import { resolve } from 'path';

// https://vitejs.dev/config/
export default defineConfig({
  // 基础路径(根据部署环境调整)
  base: '/patient-companion/',
  // 路径别名
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src'),
      '@assets': resolve(__dirname, 'src/assets'),
      '@common': resolve(__dirname, 'src/common'),
      '@modules': resolve(__dirname, 'src/modules'),
    },
  },
  // 插件配置
  plugins: [
    vue(),
    // 渐进式增强:兼容低版本浏览器
    legacy({
      targets: ['defaults', 'not IE 11'],
      additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
    }),
  ],
  // 服务配置(开发环境)
  server: {
    port: 3000,
    open: true,
    cors: true,
  },
  // 构建优化
  build: {
    // 资源压缩
    minify: 'esbuild',
    // 分块策略
    rollupOptions: {
      output: {
        manualChunks: {
          // 第三方库拆分
          vendor: ['vue', 'vue-router', 'pinia', 'element-plus'],
          // 业务模块拆分
          patient: ['@modules/patient'],
          companion: ['@modules/companion'],
          knowledge: ['@modules/knowledge'],
        },
      },
    },
  },
});

3. 编码规范配置(.eslintrc.js)

javascript

module.exports = {
  root: true,
  env: {
    browser: true,
    es2021: true,
    node: true,
  },
  extends: [
    'eslint:recommended',
    'plugin:vue/vue3-essential',
    '@vue/eslint-config-typescript',
    '@vue/eslint-config-prettier',
  ],
  parserOptions: {
    ecmaVersion: 'latest',
    parser: '@typescript-eslint/parser',
    sourceType: 'module',
  },
  plugins: ['vue', '@typescript-eslint'],
  rules: {
    // 基础规范
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    // TypeScript规范
    '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
    // Vue规范
    'vue/multi-word-component-names': [
      'warn',
      { ignores: ['App', 'NotFound'] }, // 允许单字组件名
    ],
    'vue/script-setup-uses-vars': 'error', // 检查script-setup中未使用的变量
  },
};

三、核心业务模块实现(DDD 领域建模落地)

1. 公共工具 - 安全处理(src/common/utils/security.ts)

typescript

import CryptoJS from 'crypto-js';

/**
 * 安全工具类:处理XSS防护、敏感信息加密/脱敏
 */
export class SecurityUtil {
  // 加密密钥(生产环境建议从环境变量获取)
  private static readonly SECRET_KEY = import.meta.env.VITE_APP_SECRET_KEY || 'patient-companion-2024';

  /**
   * XSS防护:过滤HTML标签
   * @param html 待过滤的HTML字符串
   * @returns 过滤后的纯文本
   */
  static filterXSS(html: string): string {
    if (!html) return '';
    return html
      .replace(/<script[^>]*?>[\s\S]*?<\/script>/gi, '')
      .replace(/<\/?[^>]*>/gi, '')
      .replace(/\s+/g, ' ');
  }

  /**
   * 敏感信息脱敏:就诊卡号(保留前4位和后4位)
   * @param cardNo 就诊卡号
   * @returns 脱敏后的卡号
   */
  static maskMedicalCard(cardNo: string): string {
    if (!cardNo) return '';
    const reg = /^(\d{4})\d+(\d{4})$/;
    return cardNo.replace(reg, '$1****$2');
  }

  /**
   * 本地存储加密:AES加密
   * @param data 待加密数据
   * @returns 加密后的字符串
   */
  static encryptStorage(data: unknown): string {
    const jsonStr = JSON.stringify(data);
    return CryptoJS.AES.encrypt(jsonStr, this.SECRET_KEY).toString();
  }

  /**
   * 本地存储解密:AES解密
   * @param encryptedStr 加密字符串
   * @returns 解密后的原始数据
   */
  static decryptStorage(encryptedStr: string): unknown {
    if (!encryptedStr) return null;
    const bytes = CryptoJS.AES.decrypt(encryptedStr, this.SECRET_KEY);
    const jsonStr = bytes.toString(CryptoJS.enc.Utf8);
    return JSON.parse(jsonStr);
  }
}

2. 患者域 - 类型定义(src/modules/patient/types/index.ts)

typescript

/**
 * 患者实体(Entity):有唯一标识的业务对象
 */
export interface Patient {
  id: string; // 唯一标识(就诊卡号/身份证号)
  name: string; // 患者姓名
  age: number; // 年龄
  gender: 'male' | 'female' | 'other'; // 性别
  medicalCardNo: string; // 就诊卡号(敏感信息)
  phone: string; // 手机号(敏感信息)
  address?: string; // 地址(值对象)
  emergencyContact?: EmergencyContact; // 紧急联系人(值对象)
}

/**
 * 紧急联系人(Value Object):无唯一标识,不可变
 */
export interface EmergencyContact {
  name: string;
  phone: string;
  relation: string; // 关系(父子、夫妻等)
}

/**
 * 患者状态类型
 */
export interface PatientState {
  currentPatient: Patient | null; // 当前登录患者
  isAuthenticated: boolean; // 是否已认证
  loading: boolean; // 加载状态
  error: string | null; // 错误信息
}

/**
 * 患者登录请求参数
 */
export interface PatientLoginParams {
  medicalCardNo: string;
  phone: string;
  verifyCode: string;
}

3. 患者域 - 状态管理(src/modules/patient/store/index.ts)

typescript

import { defineStore } from 'pinia';
import { persist } from 'pinia-plugin-persistedstate';
import { PatientState, PatientLoginParams, Patient } from '../types';
import { patientService } from '../services';
import { SecurityUtil } from '@common/utils/security';

// 初始状态
const initialState: PatientState = {
  currentPatient: null,
  isAuthenticated: false,
  loading: false,
  error: null,
};

/**
 * 患者域状态管理:封装患者信息的CRUD与状态维护
 */
export const usePatientStore = defineStore('patient', {
  state: (): PatientState => initialState,
  // 持久化配置:加密存储敏感信息
  persist: {
    key: 'patient-companion-patient',
    storage: localStorage,
    // 自定义序列化(加密)
    serialize: (state) => {
      if (state.currentPatient) {
        // 加密敏感字段
        return {
          ...state,
          currentPatient: {
            ...state.currentPatient,
            medicalCardNo: SecurityUtil.encryptStorage(state.currentPatient.medicalCardNo),
            phone: SecurityUtil.encryptStorage(state.currentPatient.phone),
          },
        };
      }
      return state;
    },
    // 自定义反序列化(解密)
    deserialize: (value) => {
      const parsed = JSON.parse(value);
      if (parsed.currentPatient) {
        // 解密敏感字段
        return {
          ...parsed,
          currentPatient: {
            ...parsed.currentPatient,
            medicalCardNo: SecurityUtil.decryptStorage(parsed.currentPatient.medicalCardNo) as string,
            phone: SecurityUtil.decryptStorage(parsed.currentPatient.phone) as string,
          },
        };
      }
      return parsed;
    },
  },
  actions: {
    /**
     * 患者登录
     * @param params 登录参数
     */
    async login(params: PatientLoginParams) {
      try {
        this.loading = true;
        this.error = null;
        // 调用服务层接口
        const patient = await patientService.login(params);
        // 更新状态
        this.currentPatient = patient;
        this.isAuthenticated = true;
        return patient;
      } catch (error) {
        this.error = error instanceof Error ? error.message : '登录失败,请重试';
        throw error;
      } finally {
        this.loading = false;
      }
    },

    /**
     * 患者退出登录
     */
    logout() {
      this.$reset(); // 重置状态
    },

    /**
     * 更新患者信息
     * @param patient 新的患者信息
     */
    async updatePatient(patient: Partial<Patient>) {
      try {
        this.loading = true;
        this.error = null;
        if (!this.currentPatient) throw new Error('请先登录');
        // 调用服务层接口
        const updatedPatient = await patientService.update({
          ...this.currentPatient,
          ...patient,
        });
        // 更新状态
        this.currentPatient = updatedPatient;
        return updatedPatient;
      } catch (error) {
        this.error = error instanceof Error ? error.message : '更新信息失败';
        throw error;
      } finally {
        this.loading = false;
      }
    },
  },
  getters: {
    /**
     * 脱敏后的就诊卡号
     */
    maskedMedicalCard(): string {
      return this.currentPatient
        ? SecurityUtil.maskMedicalCard(this.currentPatient.medicalCardNo)
        : '';
    },

    /**
     * 脱敏后的手机号
     */
    maskedPhone(): string {
      return this.currentPatient
        ? this.currentPatient.phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
        : '';
    },
  },
});

4. 陪诊服务域 - 核心组件(src/modules/companion/components/RegistrationGuide.vue)

vue

<template>
  <div class="registration-guide">
    <el-page-header content="挂号引导" @back="handleBack" />

    <!-- 患者信息校验 -->
    <el-card v-if="!patientStore.isAuthenticated" shadow="hover">
      <el-alert
        title="请先完成患者认证"
        type="warning"
        show-icon
        style="margin-bottom: 16px"
      />
      <el-button type="primary" @click="handleGoLogin">前往登录</el-button>
    </el-card>

    <!-- 挂号引导流程 -->
    <el-card v-else shadow="hover" class="mt-4">
      <div class="step-container">
        <!-- 步骤条 -->
        <el-steps :active="currentStep" direction="vertical" class="mb-6">
          <el-step title="选择科室" />
          <el-step title="选择医生" />
          <el-step title="选择就诊时间" />
          <el-step title="确认挂号信息" />
        </el-steps>

        <!-- 步骤内容 -->
        <div class="step-content">
          <!-- 步骤1:选择科室 -->
          <div v-if="currentStep === 0">
            <el-select
              v-model="selectedDept"
              placeholder="请选择就诊科室"
              @change="handleDeptChange"
              class="w-full mb-4"
            >
              <el-option
                v-for="dept in deptList"
                :key="dept.id"
                :label="dept.name"
                :value="dept"
              />
            </el-select>
            <el-button
              type="primary"
              @click="handleNextStep"
              :disabled="!selectedDept"
            >
              下一步
            </el-button>
          </div>

          <!-- 步骤2:选择医生(根据科室筛选) -->
          <div v-else-if="currentStep === 1">
            <el-table :data="filteredDoctors" border class="mb-4">
              <el-table-column label="医生姓名" prop="name" />
              <el-table-column label="职称" prop="title" />
              <el-table-column label="擅长领域" prop="specialty" />
              <el-table-column label="操作">
                <template #default="scope">
                  <el-button
                    type="text"
                    @click="handleSelectDoctor(scope.row)"
                  >
                    选择
                  </el-button>
                </template>
              </el-table-column>
            </el-table>
            <div class="button-group">
              <el-button @click="handlePrevStep">上一步</el-button>
              <el-button
                type="primary"
                @click="handleNextStep"
                :disabled="!selectedDoctor"
              >
                下一步
              </el-button>
            </div>
          </div>

          <!-- 步骤3-4:省略(同理实现时间选择、信息确认) -->
        </div>
      </div>
    </el-card>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { ElSteps, ElStep, ElCard, ElSelect, ElOption, ElTable, ElTableColumn, ElButton, ElAlert, ElPageHeader } from 'element-plus';
import { usePatientStore } from '@modules/patient/store';
import { companionService } from '@modules/companion/services';
import { Department, Doctor } from '@modules/companion/types';
import { useTracker } from '@common/hooks/useTracker';

// 状态管理
const patientStore = usePatientStore();
const router = useRouter();
const { trackEvent } = useTracker(); // 埋点统计

// 步骤状态
const currentStep = ref(0);
const selectedDept = ref<Department | null>(null);
const selectedDoctor = ref<Doctor | null>(null);
const deptList = ref<Department[]>([]);
const doctorList = ref<Doctor[]>([]);

/**
 * 过滤后的医生列表(根据选中的科室)
 */
const filteredDoctors = ref<Doctor[]>([]);

/**
 * 页面挂载时加载科室列表
 */
onMounted(async () => {
  try {
    // 埋点:进入挂号引导页面
    trackEvent('companion', 'enter_registration_guide', {
      patientId: patientStore.currentPatient?.id,
    });

    // 加载科室数据
    const depts = await companionService.getDepartmentList();
    deptList.value = depts;
  } catch (error) {
    console.error('加载科室列表失败:', error);
  }
});

/**
 * 处理科室选择变更
 */
const handleDeptChange = async (dept: Department) => {
  if (!dept) {
    filteredDoctors.value = [];
    return;
  }

  try {
    // 根据科室ID加载医生列表
    const doctors = await companionService.getDoctorListByDept(dept.id);
    doctorList.value = doctors;
    filteredDoctors.value = doctors;
  } catch (error) {
    console.error('加载医生列表失败:', error);
  }
};

/**
 * 选择医生
 */
const handleSelectDoctor = (doctor: Doctor) => {
  selectedDoctor.value = doctor;
  // 埋点:选择医生
  trackEvent('companion', 'select_doctor', {
    doctorId: doctor.id,
    deptId: selectedDept.value?.id,
  });
};

/**
 * 下一步
 */
const handleNextStep = () => {
  currentStep.value += 1;
  // 埋点:步骤推进
  trackEvent('companion', 'next_step', {
    currentStep: currentStep.value,
  });
};

/**
 * 上一步
 */
const handlePrevStep = () => {
  currentStep.value -= 1;
};

/**
 * 返回上一页
 */
const handleBack = () => {
  router.back();
};

/**
 * 前往登录
 */
const handleGoLogin = () => {
  router.push('/patient/login');
};
</script>

<style scoped lang="scss">
.registration-guide {
  padding: 16px;
}

.step-container {
  max-width: 800px;
  margin: 0 auto;
}

.step-content {
  background: #fff;
  padding: 24px;
  border-radius: 4px;
}

.button-group {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 16px;
}
</style>

5. 知识库域 - 服务层(src/modules/knowledge/services/index.ts)

typescript

import { request } from '@common/utils/request';
import { FAQ, FAQSearchParams } from '../types';

/**
 * 知识库服务:处理FAQ查询、搜索等业务逻辑
 */
export class KnowledgeService {
  /**
   * 获取FAQ列表(分页)
   * @param params 分页参数
   * @returns FAQ列表与分页信息
   */
  static async getFAQList(params: { page: number; size: number }): Promise<{
    list: FAQ[];
    total: number;
    page: number;
    size: number;
  }> {
    const response = await request({
      url: '/api/knowledge/faq/list',
      method: 'GET',
      params,
    });
    return response.data;
  }

  /**
   * 搜索FAQ(支持关键词匹配)
   * @param params 搜索参数
   * @returns 匹配的FAQ列表
   */
  static async searchFAQ(params: FAQSearchParams): Promise<FAQ[]> {
    // 安全处理:过滤XSS
    const safeParams = {
      ...params,
      keyword: params.keyword ? SecurityUtil.filterXSS(params.keyword) : '',
    };

    const response = await request({
      url: '/api/knowledge/faq/search',
      method: 'GET',
      params: safeParams,
    });
    return response.data.list;
  }

  /**
   * 获取FAQ详情
   * @param id FAQ唯一ID
   * @returns FAQ详情
   */
  static async getFAQDetail(id: string): Promise<FAQ> {
    const response = await request({
      url: `/api/knowledge/faq/${id}`,
      method: 'GET',
    });
    return response.data;
  }

  /**
   * 记录FAQ点击量(埋点关联)
   * @param id FAQ唯一ID
   */
  static async recordFAQClick(id: string): Promise<void> {
    await request({
      url: `/api/knowledge/faq/${id}/click`,
      method: 'POST',
    });
  }
}

// 导出单例(避免重复创建实例)
export const knowledgeService = KnowledgeService;
import { SecurityUtil } from '@common/utils/security';

四、入口与路由配置

1. 主入口(src/main.ts)

typescript

import { createApp } from 'vue';
import { createPinia } from 'pinia';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
import { createRouter, createWebHistory } from 'vue-router';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import App from './App.vue';
import { routes } from './router';
import { useErrorHandler } from '@common/hooks/useError';
import '@assets/styles/main.scss';

// 1. 创建Pinia实例(状态管理)
const pinia = createPinia();
// 安装持久化插件
pinia.use(piniaPluginPersistedstate);

// 2. 创建路由实例
const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes,
});

// 3. 全局错误处理
useErrorHandler();

// 4. 创建Vue应用
const app = createApp(App);
app.use(pinia);
app.use(router);
app.use(ElementPlus);

// 5. 挂载应用
app.mount('#app');

2. 路由配置(src/router/index.ts)

typescript

import { RouteRecordRaw } from 'vue-router';
// 模块路由拆分
import patientRoutes from './modules/patientRoutes';
import companionRoutes from './modules/companionRoutes';
import knowledgeRoutes from './modules/knowledgeRoutes';

/**
 * 根路由配置
 */
export const routes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'Home',
    component: () => import('@/views/Home.vue'),
    meta: {
      title: '患者陪诊智能体',
      requiresAuth: false, // 是否需要登录
    },
  },
  // 患者域路由
  ...patientRoutes,
  // 陪诊服务域路由
  ...companionRoutes,
  // 知识库域路由
  ...knowledgeRoutes,
  // 404页面
  {
    path: '/:pathMatch(.*)*',
    name: 'NotFound',
    component: () => import('@/views/NotFound.vue'),
    meta: {
      title: '页面不存在',
      requiresAuth: false,
    },
  },
];

// 路由守卫:权限控制
router.beforeEach((to, from, next) => {
  // 设置页面标题
  document.title = to.meta.title as string || '患者陪诊智能体';

  // 权限校验:需要登录但未登录的页面,跳转至登录页
  const { requiresAuth = false } = to.meta;
  const patientStore = usePatientStore();
  if (requiresAuth && !patientStore.isAuthenticated) {
    next({
      path: '/patient/login',
      query: { redirect: to.fullPath }, // 登录后跳转回原页面
    });
    return;
  }

  next();
});

import { usePatientStore } from '@modules/patient/store';
import { createRouter, createWebHistory } from 'vue-router';

五、工程运行与维护

1. 本地运行步骤

bash

# 1. 安装依赖
npm install

# 2. 启动开发服务(默认3000端口)
npm run dev

# 3. 访问地址
http://localhost:3000/patient-companion/

2. 构建部署

bash

# 1. 构建生产包
npm run build

# 2. 预览生产包
npm run preview

# 3. 部署:将dist目录文件上传至服务器(如Nginx、OSS等)

3. 编码规范执行

bash

# 1. ESLint检查与自动修复
npm run lint:eslint

# 2. Stylelint检查与自动修复
npm run lint:style

# 3. Commit规范:提交代码时自动校验(husky)
git add .
git commit -m "feat: 新增挂号引导功能"

六、符合架构设计要求的关键特性

  1. DDD 领域建模落地:按患者域、陪诊服务域、知识库域拆分模块,每个域内包含实体、值对象、服务、状态管理,边界清晰。
  2. 安全要求满足:XSS 防护:SecurityUtil.filterXSS过滤 HTML 标签敏感信息处理:就诊卡号 / 手机号脱敏,本地存储加密API 请求拦截:Axios 拦截器处理请求头与响应错误
  3. 可观测性:埋点统计:useTracker hook 记录用户操作(如挂号步骤、医生选择)错误处理:useErrorHandler全局捕获错误并上报
  4. 性能优化:路由按需加载:defineAsyncComponent拆分代码包资源压缩:Vite 构建时自动压缩 JS/CSS/ 图片缓存策略:Pinia 持久化缓存用户信息,localStorage 缓存 FAQ 数据
  5. 演进性:模块化设计:新增业务域只需在modules目录下添加子模块插件化能力:Pinia 插件、Vue 指令可灵活扩展兼容低版本:@vitejs/plugin-legacy支持 IE11 及以上浏览器


该工程可直接在 VS Code 中打开,安装依赖后即可运行,符合前端架构设计的核心要求,同时满足医院陪诊场景的业务需求。

最近发表
标签列表