diff --git a/APPOINTMENT_BUTTON_DISPLAY_FIX.md b/APPOINTMENT_BUTTON_DISPLAY_FIX.md
deleted file mode 100644
index 29423f8b..00000000
--- a/APPOINTMENT_BUTTON_DISPLAY_FIX.md
+++ /dev/null
@@ -1,208 +0,0 @@
-# 预约列表按钮显示优化
-
-## 问题描述
-
-在预约列表中,同样的情况下,有的预约显示"开方"按钮,有的不显示。
-
-## 问题原因
-
-操作列的按钮过多,导致"开方"按钮被挤出可视区域或被其他按钮遮挡。
-
-原有按钮(从左到右):
-1. 编辑患者
-2. 视频二维码
-3. 通话
-4. 完成
-5. 开方/查看
-6. 更多
-
-当所有按钮都显示时,操作列宽度为 380px,可能不够容纳所有按钮。
-
-## 解决方案
-
-根据预约状态优化按钮显示:
-
-### 已完成状态(status = 3)
-
-只显示核心按钮:
-- 编辑患者
-- 开方/查看
-- 更多
-
-隐藏按钮:
-- 视频二维码(已完成,不需要视频)
-- 通话(已完成,不需要通话)
-- 完成(已经完成)
-
-### 其他状态(待接诊、已过号、已取消)
-
-显示所有按钮:
-- 编辑患者
-- 视频二维码
-- 通话
-- 完成
-- 开方/查看
-- 更多
-
-## 修改内容
-
-**文件**: `admin/src/views/tcm/appointment/list.vue`
-
-### 修改前
-
-```vue
-
-
-
- 编辑患者
- 视频二维码
- 通话
- 完成
- {{ prescriptionActionLabel(row) }}
- 更多
-
-
-
-```
-
-### 修改后
-
-```vue
-
-
-
- 编辑患者
-
-
-
- 视频二维码
- 通话
- 完成
-
-
-
- {{ prescriptionActionLabel(row) }}
-
- 更多
-
-
-
-```
-
-## 按钮显示规则
-
-### 预约状态说明
-
-| 状态值 | 状态名称 | 说明 |
-|-------|---------|------|
-| 1 | 待接诊 | 患者已挂号,等待医生接诊 |
-| 2 | 已取消 | 挂号已取消 |
-| 3 | 已完成 | 医生已完成接诊 |
-| 4 | 已过号 | 患者未按时就诊,已过号 |
-
-### 按钮显示矩阵
-
-| 按钮 | 待接诊(1) | 已取消(2) | 已完成(3) | 已过号(4) |
-|-----|----------|----------|----------|----------|
-| 编辑患者 | ✅ | ✅ | ✅ | ✅ |
-| 视频二维码 | ✅ | ✅ | ❌ | ✅ |
-| 通话 | ✅ | ✅ | ❌ | ✅ |
-| 完成 | ✅ | ✅ | ❌ | ✅ |
-| 开方/查看 | ✅ | ✅ | ✅ | ✅ |
-| 更多 | ✅ | ✅ | ✅ | ✅ |
-
-## 优势
-
-### 1. 确保核心按钮始终可见
-
-- "开方/查看"按钮是核心功能,必须始终显示
-- 已完成状态下,减少不必要的按钮,确保核心按钮可见
-
-### 2. 优化用户体验
-
-- 已完成的预约不需要"视频二维码"、"通话"、"完成"按钮
-- 减少按钮数量,界面更简洁
-
-### 3. 避免按钮被遮挡
-
-- 减少按钮数量后,操作列宽度足够容纳所有按钮
-- 避免按钮被挤出可视区域
-
-## 测试建议
-
-### 测试用例 1: 待接诊状态
-
-1. 找到一个待接诊的预约(status = 1)
-2. 检查操作列是否显示所有按钮:
- - ✅ 编辑患者
- - ✅ 视频二维码
- - ✅ 通话
- - ✅ 完成
- - ✅ 开方/查看
- - ✅ 更多
-
-### 测试用例 2: 已完成状态
-
-1. 找到一个已完成的预约(status = 3)
-2. 检查操作列是否只显示核心按钮:
- - ✅ 编辑患者
- - ❌ 视频二维码(不显示)
- - ❌ 通话(不显示)
- - ❌ 完成(不显示)
- - ✅ 开方/查看
- - ✅ 更多
-
-### 测试用例 3: 已完成且已开方
-
-1. 找到一个已完成且已开方的预约
-2. 检查"开方/查看"按钮是否显示为"查看"
-3. 点击"查看"按钮,应该可以查看处方(只读模式)
-
-### 测试用例 4: 已完成但未开方
-
-1. 找到一个已完成但未开方的预约
-2. 检查"开方/查看"按钮是否显示为"开方"
-3. 点击"开方"按钮,应该可以创建新处方
-
-## 注意事项
-
-### 1. 权限检查
-
-所有按钮都有权限检查,如果用户没有相应权限,按钮会被隐藏:
-- `tcm.diagnosis/edit` - 编辑患者
-- `tcm.diagnosis/videoQr` - 视频二维码
-- `doctor.appointment/prescription` - 通话
-- `doctor.appointment/complete` - 完成
-- `tcm.diagnosis/kaifang` - 开方/查看
-
-### 2. 按钮文字动态变化
-
-"开方/查看"按钮的文字根据处方状态动态变化:
-- 未开方或待审核:显示"开方"
-- 已审核通过且未作废:显示"查看"
-- 已驳回:显示"开方"
-
-### 3. 操作列宽度
-
-操作列宽度为 380px,足够容纳:
-- 已完成状态:3个按钮(编辑患者、开方/查看、更多)
-- 其他状态:6个按钮(编辑患者、视频二维码、通话、完成、开方/查看、更多)
-
-如果按钮仍然被遮挡,可以考虑:
-- 增加操作列宽度(如 420px)
-- 将更多按钮移到下拉菜单中
-- 使用图标按钮减少宽度
-
-## 相关文档
-
-- `PRESCRIPTION_BUTTON_DISPLAY_CHECK.md` - 处方按钮显示检查文档
-- `PRESCRIPTION_APPROVED_LOCK.md` - 处方审核通过后锁定编辑文档
-
-## 总结
-
-通过根据预约状态优化按钮显示,确保了:
-- ✅ "开方/查看"按钮始终可见
-- ✅ 已完成状态下界面更简洁
-- ✅ 避免按钮被挤出可视区域
-- ✅ 优化用户体验
-- ✅ 保持核心功能的可访问性
diff --git a/APPOINTMENT_INDEX_OPTIMIZATION.md b/APPOINTMENT_INDEX_OPTIMIZATION.md
deleted file mode 100644
index 90bebaca..00000000
--- a/APPOINTMENT_INDEX_OPTIMIZATION.md
+++ /dev/null
@@ -1,251 +0,0 @@
-# 预约列表查询性能优化 - 索引方案
-
-## 问题分析
-
-预约列表查询涉及多表关联和复杂条件筛选,主要性能瓶颈:
-
-1. 多表 LEFT JOIN(预约表、诊断表、管理员表)
-2. 多条件筛选(状态、日期范围、患者姓名、医生姓名)
-3. 权限过滤(医生角色、医助角色)
-4. 复杂排序(状态 + 日期 + 时间)
-5. 子查询(确认诊单、开方状态)
-
-## 索引优化方案
-
-### 1. 预约表 (zyt_appointment)
-
-#### 单列索引
-```sql
--- 医生ID索引(医生角色查询)
-ALTER TABLE `zyt_appointment` ADD INDEX `idx_doctor_id` (`doctor_id`);
-
--- 患者ID索引(关联查询)
-ALTER TABLE `zyt_appointment` ADD INDEX `idx_patient_id` (`patient_id`);
-
--- 状态索引(状态筛选)
-ALTER TABLE `zyt_appointment` ADD INDEX `idx_status` (`status`);
-
--- 预约日期索引(日期范围查询)
-ALTER TABLE `zyt_appointment` ADD INDEX `idx_appointment_date` (`appointment_date`);
-```
-
-#### 组合索引(重点优化)
-```sql
--- 状态+日期+时间(用于排序,覆盖 ORDER BY)
-ALTER TABLE `zyt_appointment` ADD INDEX `idx_status_date_time` (`status`, `appointment_date`, `appointment_time`);
-
--- 医生+状态+日期(医生角色常用查询)
-ALTER TABLE `zyt_appointment` ADD INDEX `idx_doctor_status_date` (`doctor_id`, `status`, `appointment_date`);
-```
-
-**优化效果**:
-- `idx_status_date_time` 可以直接用于排序,避免 filesort
-- `idx_doctor_status_date` 可以快速过滤医生的预约记录
-
-### 2. 诊断表 (zyt_tcm_diagnosis)
-
-```sql
--- 患者姓名索引(模糊搜索,前缀索引)
-ALTER TABLE `zyt_tcm_diagnosis` ADD INDEX `idx_patient_name` (`patient_name`(20));
-
--- 医助ID索引(医助角色查询)
-ALTER TABLE `zyt_tcm_diagnosis` ADD INDEX `idx_assistant_id` (`assistant_id`);
-```
-
-**说明**:
-- `patient_name` 使用前缀索引(20),节省空间且支持 LIKE 'xxx%' 查询
-- 不支持 LIKE '%xxx%' 的优化,但可以减少扫描行数
-
-### 3. 管理员表 (zyt_admin)
-
-```sql
--- 管理员姓名索引(医生姓名搜索)
-ALTER TABLE `zyt_admin` ADD INDEX `idx_name` (`name`(20));
-```
-
-### 4. 诊断查看记录表 (zyt_diagnosis_view_records)
-
-```sql
--- 诊断ID+确认状态+删除时间(用于确认诊单查询)
-ALTER TABLE `zyt_diagnosis_view_records` ADD INDEX `idx_diagnosis_confirmed` (`diagnosis_id`, `is_confirmed`, `delete_time`);
-```
-
-**优化效果**:
-- 加速 `WHERE diagnosis_id IN (...) AND is_confirmed = 1 AND delete_time IS NULL` 查询
-- 覆盖索引,无需回表
-
-### 5. 处方表 (zyt_prescription)
-
-```sql
--- 诊断ID+删除时间(用于开方状态查询)
-ALTER TABLE `zyt_prescription` ADD INDEX `idx_diagnosis_delete` (`diagnosis_id`, `delete_time`);
-```
-
-**优化效果**:
-- 加速 `WHERE diagnosis_id IN (...) AND delete_time IS NULL` 查询
-
-### 6. 管理员角色表 (zyt_admin_role)
-
-```sql
--- 管理员ID+角色ID(用于权限判断)
-ALTER TABLE `zyt_admin_role` ADD INDEX `idx_admin_role` (`admin_id`, `role_id`);
-```
-
-**优化效果**:
-- 加速角色查询,快速判断用户权限
-
-## 执行步骤
-
-### 方式一:直接执行(推荐)
-
-```bash
-# 连接数据库
-mysql -h 39.97.232.35 -u cs_zyt -p cs_zyt
-
-# 执行索引创建脚本
-source optimize_appointment_indexes.sql
-```
-
-### 方式二:安全执行(检查后添加)
-
-```bash
-# 执行安全脚本(会先检查索引是否存在)
-source check_and_add_indexes.sql
-```
-
-### 方式三:phpMyAdmin
-
-1. 登录 phpMyAdmin
-2. 选择数据库 `cs_zyt`
-3. 点击 SQL 标签
-4. 复制 `optimize_appointment_indexes.sql` 内容
-5. 点击执行
-
-## 预期效果
-
-### 优化前
-- 查询时间:500ms - 2000ms
-- 扫描行数:全表扫描
-- 使用索引:PRIMARY 或无
-
-### 优化后
-- 查询时间:50ms - 200ms(提升 5-10 倍)
-- 扫描行数:索引范围扫描
-- 使用索引:组合索引
-
-## 验证方法
-
-### 1. 查看执行计划
-
-```sql
-EXPLAIN SELECT
- a.*,
- u.patient_name,
- u.phone as patient_phone,
- ad.name as doctor_name
-FROM zyt_appointment a
-LEFT JOIN zyt_tcm_diagnosis u ON a.patient_id = u.id
-LEFT JOIN zyt_admin ad ON a.doctor_id = ad.id
-WHERE a.doctor_id = 1
- AND a.status = 1
- AND a.appointment_date >= '2024-03-01'
-ORDER BY a.status ASC, a.appointment_date ASC, a.appointment_time ASC
-LIMIT 20;
-```
-
-**期望结果**:
-- `type`: ref 或 range(不是 ALL)
-- `key`: idx_doctor_status_date 或 idx_status_date_time
-- `rows`: 较小的数字(不是全表行数)
-
-### 2. 查看索引使用情况
-
-```sql
--- 查看索引统计
-SHOW INDEX FROM zyt_appointment;
-
--- 查看索引使用情况(需要开启慢查询日志)
-SELECT * FROM sys.schema_unused_indexes WHERE object_schema = 'cs_zyt';
-```
-
-## 注意事项
-
-1. **索引维护成本**:索引会增加 INSERT/UPDATE/DELETE 的开销,但对于查询为主的场景,收益远大于成本
-
-2. **索引选择性**:
- - 高选择性字段(如 ID、日期)适合建索引
- - 低选择性字段(如性别、状态)单独建索引效果不明显,但可以作为组合索引的一部分
-
-3. **前缀索引**:
- - 对于长字符串字段(如姓名),使用前缀索引可以节省空间
- - 前缀长度选择:能区分大部分数据即可(通常 10-20 个字符)
-
-4. **组合索引顺序**:
- - 遵循"最左前缀"原则
- - 将选择性高的字段放在前面
- - 考虑查询条件的组合频率
-
-5. **定期维护**:
- ```sql
- -- 优化表(重建索引)
- OPTIMIZE TABLE zyt_appointment;
- OPTIMIZE TABLE zyt_tcm_diagnosis;
- ```
-
-## 进一步优化建议
-
-### 1. 查询优化
-
-```php
-// 避免 SELECT *,只查询需要的字段
-$query->field('a.id, a.patient_id, a.doctor_id, a.status, ...');
-
-// 使用 whereIn 代替多个 OR
-$query->whereIn('a.status', [1, 3]);
-```
-
-### 2. 分页优化
-
-```php
-// 使用延迟关联优化深分页
-$subQuery = Appointment::where($conditions)
- ->field('id')
- ->limit($offset, $limit)
- ->buildSql();
-
-$list = Appointment::alias('a')
- ->join([$subQuery => 'sub'], 'a.id = sub.id')
- ->with(['patient', 'doctor'])
- ->select();
-```
-
-### 3. 缓存优化
-
-```php
-// 缓存医生列表、角色信息等不常变化的数据
-$roleIds = Cache::remember('admin_role_' . $adminId, function() {
- return AdminRole::where('admin_id', $adminId)->column('role_id');
-}, 3600);
-```
-
-## 监控指标
-
-定期检查以下指标:
-
-1. 慢查询日志(> 1s 的查询)
-2. 索引使用率
-3. 表大小和索引大小
-4. 查询响应时间
-
-```sql
--- 查看表和索引大小
-SELECT
- table_name,
- ROUND(data_length / 1024 / 1024, 2) AS data_mb,
- ROUND(index_length / 1024 / 1024, 2) AS index_mb,
- ROUND((data_length + index_length) / 1024 / 1024, 2) AS total_mb
-FROM information_schema.tables
-WHERE table_schema = 'cs_zyt'
- AND table_name IN ('zyt_appointment', 'zyt_tcm_diagnosis', 'zyt_admin')
-ORDER BY (data_length + index_length) DESC;
-```
diff --git a/BUG_FIXES_SUMMARY.md b/BUG_FIXES_SUMMARY.md
deleted file mode 100644
index 09ab253a..00000000
--- a/BUG_FIXES_SUMMARY.md
+++ /dev/null
@@ -1,176 +0,0 @@
-# Bug Fixes Summary
-
-## 修复的问题
-
-### 1. 处方查看权限问题 ✅ (已解决)
-
-**问题描述**:
-- 医生A开了处方后,医生B点击"查看病历"时提示"无权限查看此处方"
-- 其他医生无法查看患者的历史处方记录
-
-**根本原因**:
-- 权限检查逻辑已经正确实现,允许有 `tcm.diagnosis/kaifang` 权限的医生查看所有处方(只读模式)
-- 问题可能是前端缓存或权限配置问题
-
-**解决方案**:
-- 确认 `PrescriptionLogic::canViewPrescription()` 方法已包含以下逻辑:
- ```php
- // 有开方权限的医生可以查看所有处方(只读)
- $permissions = $adminInfo['permissions'] ?? [];
- if (in_array('tcm.diagnosis/kaifang', $permissions, true)) {
- return true;
- }
- ```
-- 权限层级(从高到低):
- 1. 超级管理员
- 2. 创建人
- 3. 医助
- 4. 共享处方
- 5. 可见角色
- 6. 有开方权限的医生(`tcm.diagnosis/kaifang`)
-
-**文件**: `server/app/adminapi/logic/tcm/PrescriptionLogic.php`
-
----
-
-### 2. 处方组件重复请求问题 ✅ (已解决)
-
-**问题描述**:
-- 点击"开处方"或"查看病历"按钮时,重复请求 `/tcm.prescription/detail` 接口
-- 快速连续点击导致多次调用
-
-**根本原因**:
-- 已经实现了 `isOpening` 标志防止重复调用
-- 防重复逻辑已经正确工作
-
-**解决方案**:
-- 确认 `open()` 和 `openById()` 方法已包含防重复逻辑:
- ```typescript
- const isOpening = ref(false)
-
- const open = async (data: any, options?: { templateId?: number; stationName?: string }) => {
- if (isOpening.value) {
- console.warn('处方正在打开中,请勿重复点击')
- return
- }
- isOpening.value = true
- try {
- // ... 处理逻辑
- } finally {
- setTimeout(() => {
- isOpening.value = false
- }, 500)
- }
- }
- ```
-
-**文件**: `admin/src/components/tcm-prescription/index.vue`
-
----
-
-### 3. 订单撤回后处方审核状态重置 ✅ (已修复)
-
-**问题描述**:
-- 撤回订单后,关联的处方审核状态没有重置为"待审核"
-- 缺少 `audit_by_name` 字段的清空
-
-**解决方案**:
-- 在 `withdraw()` 方法中添加了 `audit_by_name` 字段的清空:
- ```php
- Prescription::where('id', $prescriptionId)
- ->whereNull('delete_time')
- ->update([
- 'audit_status' => 0, // 0=待审核
- 'audit_time' => null,
- 'audit_by' => null,
- 'audit_by_name' => '', // 新增:清空审核人姓名
- 'audit_remark' => '',
- 'update_time' => time(),
- ]);
- ```
-
-**文件**: `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
-
----
-
-### 4. 物流轨迹自动加载 ✅ (已实现)
-
-**问题描述**:
-- 用户希望打开订单详情时,如果有快递单号,自动加载物流轨迹
-- 不需要手动点击"查询轨迹"按钮
-
-**解决方案**:
-- 已经实现自动加载逻辑:
- ```typescript
- async function openDetail(id: number) {
- // ... 加载订单详情
- if (d) {
- detailLogisticsExpress.value = String(d.express_company || 'auto') || 'auto'
- if (String(d.tracking_number || '').trim()) {
- fetchLogisticsTrace() // 自动加载物流轨迹
- }
- fetchLogs(id)
- }
- }
- ```
-- 物流查询优先从数据库读取,如果数据库没有数据,再调用快递100 API
-- 按钮文字改为"刷新轨迹",用于手动刷新最新物流信息
-
-**文件**: `admin/src/views/consumer/prescription/order_list.vue`
-
----
-
-## 测试建议
-
-### 1. 处方查看权限测试
-1. 使用医生A账号创建处方
-2. 使用医生B账号(有 `tcm.diagnosis/kaifang` 权限)点击"查看病历"
-3. 确认可以正常查看处方详情(只读模式)
-4. 确认医生B不能编辑医生A创建的处方
-
-### 2. 重复请求测试
-1. 快速连续点击"开处方"按钮多次
-2. 检查浏览器开发者工具的Network面板
-3. 确认只发送一次 `/tcm.prescription/detail` 请求
-4. 确认控制台显示"处方正在打开中,请勿重复点击"警告
-
-### 3. 订单撤回测试
-1. 创建一个处方业务订单(处方审核状态为"已通过")
-2. 点击"撤回"按钮
-3. 确认订单状态变为"已取消"
-4. 在处方列表中确认该处方的审核状态变为"待审核"
-5. 确认审核人、审核时间、审核意见都已清空
-
-### 4. 物流轨迹自动加载测试
-1. 创建一个订单并填写快递单号
-2. 点击"查看"按钮打开订单详情
-3. 确认物流轨迹自动加载(如果数据库有数据,显示数据来源为"database")
-4. 点击"刷新轨迹"按钮,确认可以手动刷新物流信息
-5. 如果数据库没有数据,确认调用快递100 API(显示数据来源为"api")
-
----
-
-## 相关文档
-
-- `PRESCRIPTION_VIEW_PERMISSION_FIX.md` - 处方查看权限修复文档
-- `PRESCRIPTION_DUPLICATE_REQUEST_FIX.md` - 处方重复请求修复文档
-- `PRESCRIPTION_ORDER_WITHDRAW_RESET_AUDIT.md` - 订单撤回重置审核状态文档
-- `EXPRESS_TRACKING_AUTO_LOAD.md` - 物流轨迹自动加载文档
-- `EXPRESS_TRACKING_DATABASE_QUERY.md` - 物流追踪数据库查询优化文档
-
----
-
-## 总结
-
-所有问题都已经解决或确认正常工作:
-
-1. ✅ 处方查看权限:已实现,有开方权限的医生可以查看所有处方
-2. ✅ 重复请求防护:已实现,使用 `isOpening` 标志防止重复调用
-3. ✅ 撤回重置审核:已修复,添加了 `audit_by_name` 字段的清空
-4. ✅ 物流自动加载:已实现,打开详情时自动加载物流轨迹
-
-如果仍然遇到问题,请检查:
-- 用户权限配置是否正确
-- 浏览器缓存是否需要清除
-- 数据库表结构是否完整
-- 快递100账号是否已充值
diff --git a/CHECK_DOSAGE_FIELDS.md b/CHECK_DOSAGE_FIELDS.md
deleted file mode 100644
index dd4e10c8..00000000
--- a/CHECK_DOSAGE_FIELDS.md
+++ /dev/null
@@ -1,91 +0,0 @@
-# 检查处方用量字段是否已添加到数据库
-
-## 问题描述
-处方的用量相关字段(`dosage_amount`、`dosage_unit`、`need_decoction`)没有保存到数据库。
-
-## 检查步骤
-
-### 1. 检查数据库表结构
-
-执行以下SQL查询,检查字段是否存在:
-
-```sql
-SELECT COLUMN_NAME, DATA_TYPE, COLUMN_DEFAULT, COLUMN_COMMENT
-FROM information_schema.COLUMNS
-WHERE TABLE_SCHEMA = DATABASE()
- AND TABLE_NAME = 'zyt_tcm_prescription'
- AND COLUMN_NAME IN ('dosage_amount', 'dosage_unit', 'need_decoction')
-ORDER BY ORDINAL_POSITION;
-```
-
-### 2. 如果字段不存在,执行迁移
-
-如果上述查询返回空结果,说明字段还未添加,需要执行迁移文件:
-
-```bash
-mysql -u用户名 -p数据库名 < add_prescription_dosage_fields.sql
-```
-
-或者直接在数据库中执行:
-
-```sql
-ALTER TABLE `zyt_tcm_prescription`
-ADD COLUMN `dosage_amount` decimal(10,2) DEFAULT NULL COMMENT '用量数值' AFTER `prescription_type`,
-ADD COLUMN `dosage_unit` varchar(10) NOT NULL DEFAULT '' COMMENT '用量单位(g/ml)' AFTER `dosage_amount`,
-ADD COLUMN `need_decoction` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否代煎(0否1是,仅饮片)' AFTER `dosage_unit`;
-```
-
-### 3. 验证字段已添加
-
-再次执行步骤1的查询,应该看到3个字段:
-
-| COLUMN_NAME | DATA_TYPE | COLUMN_DEFAULT | COLUMN_COMMENT |
-|-------------|-----------|----------------|----------------|
-| dosage_amount | decimal | NULL | 用量数值 |
-| dosage_unit | varchar | '' | 用量单位(g/ml) |
-| need_decoction | tinyint | 0 | 是否代煎(0否1是,仅饮片) |
-
-## 代码检查
-
-### 前端代码 ✅
-- `admin/src/components/tcm-prescription/index.vue` 已正确实现
-- 包含 `dosage_amount`、`dosage_unit`、`need_decoction` 字段
-- 有 `watch` 监听处方类型变化,自动设置单位
-
-### 后端代码 ✅
-- `server/app/adminapi/logic/tcm/PrescriptionLogic.php` 的 `add()` 方法已包含这些字段
-- `server/app/adminapi/logic/tcm/PrescriptionLogic.php` 的 `edit()` 方法已包含这些字段
-
-### 数据库模型 ✅
-- `server/app/common/model/tcm/Prescription.php` 已配置类型转换:
- ```php
- protected $type = [
- 'dosage_amount' => 'float',
- 'need_decoction' => 'integer',
- ];
- ```
-
-## 结论
-
-代码层面一切正常,问题很可能是**数据库迁移未执行**。
-
-请执行上述步骤1检查数据库表结构,如果字段不存在,执行步骤2的迁移SQL。
-
-## 测试步骤
-
-迁移完成后,测试以下场景:
-
-1. **创建新处方**
- - 选择"浓缩水丸",用量应为 1-10g 下拉选择
- - 选择"饮片",用量应为 50-250ml 下拉选择,并显示代煎选项
- - 选择其他类型,用量应为自由输入
- - 保存后检查数据库,确认字段已保存
-
-2. **编辑已有处方**
- - 打开已有处方
- - 修改用量和代煎选项
- - 保存后检查数据库,确认字段已更新
-
-3. **查看处方详情**
- - 在订单详情中查看处方
- - 确认用量和代煎信息正确显示
diff --git a/COMPLETION_REPORT.md b/COMPLETION_REPORT.md
deleted file mode 100644
index e9677b39..00000000
--- a/COMPLETION_REPORT.md
+++ /dev/null
@@ -1,594 +0,0 @@
-# 处方库功能 - 完成报告
-
-## 📋 项目信息
-
-**项目名称:** 处方库管理系统
-**版本号:** v1.0.0
-**完成日期:** 2026-03-22
-**开发状态:** ✅ 已完成并测试通过
-
----
-
-## ✅ 完成清单
-
-### 1. 数据库设计 ✅
-
-- [x] 设计数据库表结构
-- [x] 创建索引优化查询
-- [x] 支持软删除
-- [x] 创建SQL文件
-
-**文件:** `server/database/migrations/create_prescription_library.sql`
-
-### 2. 后端开发 ✅
-
-#### 数据模型层
-- [x] PrescriptionLibrary 模型
-- [x] 时间格式化
-- [x] 软删除支持
-
-**文件:** `server/app/common/model/tcm/PrescriptionLibrary.php`
-
-#### 控制器层
-- [x] 列表接口
-- [x] 添加接口
-- [x] 编辑接口
-- [x] 删除接口
-- [x] 详情接口
-
-**文件:** `server/app/adminapi/controller/tcm/PrescriptionLibraryController.php`
-
-#### 业务逻辑层
-- [x] 添加处方逻辑
-- [x] 编辑处方逻辑(含权限验证)
-- [x] 删除处方逻辑(含权限验证)
-- [x] 详情查询逻辑(含权限验证)
-- [x] 异常处理
-
-**文件:** `server/app/adminapi/logic/tcm/PrescriptionLibraryLogic.php`
-
-#### 列表逻辑层
-- [x] 搜索条件设置
-- [x] 权限过滤(只显示自己的或公开的)
-- [x] 分页支持
-- [x] JSON数据解析
-
-**文件:** `server/app/adminapi/lists/tcm/PrescriptionLibraryLists.php`
-
-#### 验证器层
-- [x] 添加场景验证
-- [x] 编辑场景验证
-- [x] 删除场景验证
-- [x] 详情场景验证
-- [x] 自定义错误消息
-
-**文件:** `server/app/adminapi/validate/tcm/PrescriptionLibraryValidate.php`
-
-### 3. 前端开发 ✅
-
-#### 页面组件
-- [x] 搜索表单
-- [x] 数据表格
-- [x] 新增/编辑弹窗
-- [x] 药材动态表格
-- [x] 权限控制
-- [x] 表单验证
-- [x] 加载状态
-- [x] 错误提示
-
-**文件:** `admin/src/views/consumer/prescription/list.vue`
-
-#### API接口
-- [x] prescriptionLibraryLists
-- [x] prescriptionLibraryAdd
-- [x] prescriptionLibraryEdit
-- [x] prescriptionLibraryDelete
-- [x] prescriptionLibraryDetail
-
-**文件:** `admin/src/api/tcm.ts`(已更新)
-
-### 4. 安装脚本 ✅
-
-- [x] Windows 安装脚本
-- [x] Linux/Mac 安装脚本
-- [x] 交互式配置
-- [x] 错误处理
-
-**文件:**
-- `install_prescription_library.bat`
-- `install_prescription_library.sh`
-
-### 5. 测试文件 ✅
-
-- [x] 表结构查询
-- [x] 测试数据插入
-- [x] 数据查询测试
-- [x] 统计查询
-- [x] 清理脚本
-
-**文件:** `TEST_PRESCRIPTION_LIBRARY.sql`
-
-### 6. 文档编写 ✅
-
-#### 使用文档
-- [x] 完整使用说明(README)
-- [x] 快速开始指南
-- [x] 功能演示文档
-- [x] 常见问题解答
-
-**文件:**
-- `PRESCRIPTION_LIBRARY_README.md`
-- `QUICK_START_PRESCRIPTION_LIBRARY.md`
-- `PRESCRIPTION_LIBRARY_DEMO.md`
-
-#### 技术文档
-- [x] 开发总结
-- [x] 文件清单
-- [x] API接口文档
-- [x] 数据库设计文档
-
-**文件:**
-- `PRESCRIPTION_LIBRARY_SUMMARY.md`
-- `PRESCRIPTION_LIBRARY_FILES.md`
-
-#### 安装文档
-- [x] 安装检查清单
-- [x] 环境要求
-- [x] 问题排查
-- [x] 验证步骤
-
-**文件:** `INSTALLATION_CHECKLIST.md`
-
-#### 项目文档
-- [x] 交付文档
-- [x] 文档索引
-- [x] 完成报告
-
-**文件:**
-- `DELIVERY_PACKAGE.md`
-- `README_PRESCRIPTION_LIBRARY.md`
-- `COMPLETION_REPORT.md`(本文件)
-
----
-
-## 📊 统计数据
-
-### 文件统计
-
-| 类型 | 数量 | 说明 |
-|------|------|------|
-| PHP文件 | 6 | 后端代码 |
-| Vue文件 | 1 | 前端页面 |
-| TypeScript文件 | 1 | API接口(修改) |
-| SQL文件 | 2 | 数据库+测试 |
-| Shell脚本 | 2 | 安装脚本 |
-| Markdown文档 | 9 | 各类文档 |
-| **总计** | **21** | **所有文件** |
-
-### 代码统计
-
-| 类型 | 行数 | 说明 |
-|------|------|------|
-| PHP代码 | ~450行 | 后端业务逻辑 |
-| Vue代码 | ~350行 | 前端页面组件 |
-| TypeScript | ~30行 | API接口(新增) |
-| SQL语句 | ~120行 | 数据库+测试 |
-| Shell脚本 | ~100行 | 安装脚本 |
-| 文档内容 | ~2000行 | 各类文档 |
-| **总计** | **~3050行** | **所有代码** |
-
----
-
-## 🎯 功能实现
-
-### 核心功能(8项)
-
-1. ✅ **处方创建**
- - 处方名称输入
- - 动态添加药材
- - 设置是否公开
- - 数据验证
-
-2. ✅ **处方编辑**
- - 修改处方信息
- - 修改药材配方
- - 权限验证
- - 数据验证
-
-3. ✅ **处方删除**
- - 软删除机制
- - 权限验证(仅创建者)
- - 确认提示
-
-4. ✅ **处方查看**
- - 详情展示
- - 只读模式
- - 权限验证
-
-5. ✅ **处方列表**
- - 分页显示
- - 权限过滤
- - 数据格式化
-
-6. ✅ **处方搜索**
- - 按名称搜索
- - 模糊匹配
- - 实时查询
-
-7. ✅ **处方筛选**
- - 按是否公开筛选
- - 组合查询
- - 结果统计
-
-8. ✅ **权限控制**
- - 查看权限
- - 编辑权限
- - 删除权限
- - 公开/私有设置
-
-### 技术特性(10项)
-
-1. ✅ **完全独立** - 不依赖其他模块
-2. ✅ **动态药材** - 支持任意数量药材
-3. ✅ **小数剂量** - 精确到0.1克
-4. ✅ **权限控制** - 完善的权限验证
-5. ✅ **软删除** - 数据可恢复
-6. ✅ **数据验证** - 前后端双重验证
-7. ✅ **异常处理** - 完善的错误处理
-8. ✅ **响应式设计** - 适配各种屏幕
-9. ✅ **搜索筛选** - 快速查找处方
-10. ✅ **分页显示** - 优化性能
-
----
-
-## 🧪 测试结果
-
-### 功能测试 ✅
-
-| 测试项 | 结果 | 说明 |
-|--------|------|------|
-| 创建处方(私有) | ✅ 通过 | 功能正常 |
-| 创建处方(公开) | ✅ 通过 | 功能正常 |
-| 编辑处方 | ✅ 通过 | 功能正常 |
-| 删除处方 | ✅ 通过 | 功能正常 |
-| 查看处方 | ✅ 通过 | 功能正常 |
-| 搜索处方 | ✅ 通过 | 功能正常 |
-| 筛选处方 | ✅ 通过 | 功能正常 |
-| 分页显示 | ✅ 通过 | 功能正常 |
-
-### 权限测试 ✅
-
-| 测试项 | 结果 | 说明 |
-|--------|------|------|
-| 私有处方权限 | ✅ 通过 | 只有创建者可见 |
-| 公开处方权限 | ✅ 通过 | 所有人可见 |
-| 编辑权限验证 | ✅ 通过 | 权限控制正确 |
-| 删除权限验证 | ✅ 通过 | 仅创建者可删除 |
-
-### 数据验证测试 ✅
-
-| 测试项 | 结果 | 说明 |
-|--------|------|------|
-| 处方名称验证 | ✅ 通过 | 必填验证正常 |
-| 药材列表验证 | ✅ 通过 | 至少一味药材 |
-| 药材名称验证 | ✅ 通过 | 不能为空 |
-| 药材剂量验证 | ✅ 通过 | 必须大于0 |
-
-### 代码质量测试 ✅
-
-| 测试项 | 结果 | 说明 |
-|--------|------|------|
-| PHP语法检查 | ✅ 通过 | 无语法错误 |
-| Vue语法检查 | ✅ 通过 | 无语法错误 |
-| TypeScript检查 | ✅ 通过 | 无类型错误 |
-| 代码规范检查 | ✅ 通过 | 符合规范 |
-
----
-
-## 📚 文档完成度
-
-### 使用文档 ✅
-
-- [x] 完整使用说明(4972字)
-- [x] 快速开始指南(4195字)
-- [x] 功能演示文档(5779字)
-- [x] 常见问题解答
-
-### 技术文档 ✅
-
-- [x] 开发总结(6123字)
-- [x] 文件清单(6250字)
-- [x] API接口文档
-- [x] 数据库设计文档
-
-### 安装文档 ✅
-
-- [x] 安装检查清单(详细步骤)
-- [x] 环境要求说明
-- [x] 问题排查指南
-- [x] 验证测试步骤
-
-### 项目文档 ✅
-
-- [x] 交付文档(完整)
-- [x] 文档索引(9596字)
-- [x] 完成报告(本文件)
-
-**文档总字数:** ~40,000字
-
----
-
-## 🎨 界面设计
-
-### 列表页面 ✅
-
-- [x] 搜索表单区域
-- [x] 操作按钮区域
-- [x] 数据表格区域
-- [x] 分页组件
-- [x] 加载状态
-- [x] 空数据提示
-
-### 编辑弹窗 ✅
-
-- [x] 处方名称输入
-- [x] 药材动态表格
-- [x] 添加/删除药材按钮
-- [x] 是否公开开关
-- [x] 提示文字
-- [x] 表单验证提示
-
-### 查看模式 ✅
-
-- [x] 只读显示
-- [x] 禁用编辑
-- [x] 隐藏操作按钮
-- [x] 清晰的数据展示
-
----
-
-## 🔐 安全性
-
-### 数据安全 ✅
-
-- [x] SQL注入防护(参数化查询)
-- [x] XSS防护(数据转义)
-- [x] CSRF防护(框架内置)
-- [x] 权限验证(多层验证)
-
-### 业务安全 ✅
-
-- [x] 创建者验证
-- [x] 公开/私有控制
-- [x] 软删除机制
-- [x] 数据验证
-
----
-
-## 🚀 性能优化
-
-### 数据库优化 ✅
-
-- [x] 添加索引(creator_id, is_public, create_time)
-- [x] 字段筛选(避免SELECT *)
-- [x] 分页查询
-- [x] 软删除过滤
-
-### 前端优化 ✅
-
-- [x] 按需加载
-- [x] 防抖处理
-- [x] 加载状态提示
-- [x] 错误处理
-
----
-
-## 📦 交付内容
-
-### 代码文件(8个)
-
-1. ✅ create_prescription_library.sql
-2. ✅ PrescriptionLibrary.php
-3. ✅ PrescriptionLibraryController.php
-4. ✅ PrescriptionLibraryLogic.php
-5. ✅ PrescriptionLibraryLists.php
-6. ✅ PrescriptionLibraryValidate.php
-7. ✅ list.vue
-8. ✅ tcm.ts(已更新)
-
-### 安装文件(3个)
-
-1. ✅ install_prescription_library.bat
-2. ✅ install_prescription_library.sh
-3. ✅ TEST_PRESCRIPTION_LIBRARY.sql
-
-### 文档文件(9个)
-
-1. ✅ PRESCRIPTION_LIBRARY_README.md
-2. ✅ PRESCRIPTION_LIBRARY_DEMO.md
-3. ✅ PRESCRIPTION_LIBRARY_SUMMARY.md
-4. ✅ QUICK_START_PRESCRIPTION_LIBRARY.md
-5. ✅ PRESCRIPTION_LIBRARY_FILES.md
-6. ✅ INSTALLATION_CHECKLIST.md
-7. ✅ DELIVERY_PACKAGE.md
-8. ✅ README_PRESCRIPTION_LIBRARY.md
-9. ✅ COMPLETION_REPORT.md
-
-**总计:** 20个文件
-
----
-
-## ✨ 亮点特色
-
-### 1. 完全独立 ⭐
-不依赖任何其他业务模块,可独立部署和维护。
-
-### 2. 权限灵活 ⭐
-支持公开/私有两种模式,满足不同使用场景。
-
-### 3. 操作简单 ⭐
-直观的界面设计,5分钟即可上手使用。
-
-### 4. 文档完善 ⭐
-提供9份详细文档,覆盖使用、开发、安装等各个方面。
-
-### 5. 代码质量高 ⭐
-无语法错误,符合编码规范,易于维护和扩展。
-
-### 6. 安装便捷 ⭐
-提供自动安装脚本,一键完成安装。
-
----
-
-## 🎯 验收标准
-
-### 功能验收 ✅
-
-- [x] 所有核心功能正常工作
-- [x] 权限控制正确
-- [x] 数据验证有效
-- [x] 搜索筛选正常
-- [x] 分页显示正常
-
-### 代码验收 ✅
-
-- [x] 无语法错误
-- [x] 代码规范
-- [x] 注释完整
-- [x] 异常处理完善
-- [x] 性能优化
-
-### 文档验收 ✅
-
-- [x] 使用文档完整
-- [x] 安装文档清晰
-- [x] API文档准确
-- [x] 示例代码可用
-- [x] 常见问题解答
-
-### 测试验收 ✅
-
-- [x] 功能测试通过
-- [x] 权限测试通过
-- [x] 数据验证测试通过
-- [x] 代码质量测试通过
-- [x] 性能测试通过
-
----
-
-## 📈 项目进度
-
-### 开发阶段
-
-| 阶段 | 状态 | 完成时间 |
-|------|------|----------|
-| 需求分析 | ✅ 完成 | 2026-03-22 |
-| 数据库设计 | ✅ 完成 | 2026-03-22 |
-| 后端开发 | ✅ 完成 | 2026-03-22 |
-| 前端开发 | ✅ 完成 | 2026-03-22 |
-| 功能测试 | ✅ 完成 | 2026-03-22 |
-| 文档编写 | ✅ 完成 | 2026-03-22 |
-| 项目交付 | ✅ 完成 | 2026-03-22 |
-
-**总耗时:** 1天
-**完成度:** 100%
-
----
-
-## 🎉 项目总结
-
-### 成功要素
-
-1. **需求明确** - 功能定义清晰,目标明确
-2. **设计合理** - 数据库设计合理,代码结构清晰
-3. **开发高效** - 代码质量高,开发速度快
-4. **测试充分** - 功能测试、权限测试、代码测试全面
-5. **文档完善** - 提供详细的使用和技术文档
-
-### 经验总结
-
-1. **独立性设计** - 功能独立,不耦合其他模块
-2. **权限控制** - 完善的权限验证机制
-3. **用户体验** - 简洁直观的界面设计
-4. **文档先行** - 完善的文档提高使用效率
-5. **测试驱动** - 充分的测试保证质量
-
----
-
-## 🔮 未来展望
-
-### 短期计划(1-2周)
-
-1. 添加处方分类功能
-2. 添加处方标签功能
-3. 优化搜索功能
-
-### 中期计划(1-2月)
-
-1. 添加使用统计
-2. 添加热门处方排行
-3. 支持处方导入导出
-
-### 长期计划(3-6月)
-
-1. 添加处方评论功能
-2. 添加版本管理
-3. 添加处方推荐算法
-4. 移动端适配
-
----
-
-## 📝 签字确认
-
-### 开发团队确认
-
-**开发人员:** _______________
-**日期:** 2026-03-22
-**签字:** _______________
-
-### 测试团队确认
-
-**测试人员:** _______________
-**日期:** _______________
-**签字:** _______________
-
-### 项目管理确认
-
-**项目经理:** _______________
-**日期:** _______________
-**签字:** _______________
-
-### 客户确认
-
-**客户代表:** _______________
-**日期:** _______________
-**签字:** _______________
-
----
-
-## 🎊 致谢
-
-感谢所有参与本项目的人员!
-
-特别感谢:
-- 需求提供方
-- 开发团队
-- 测试团队
-- 文档编写团队
-
----
-
-## 📞 联系方式
-
-如有任何问题或建议,请联系:
-
-**技术支持:** [联系方式]
-**项目经理:** [联系方式]
-
----
-
-**项目状态:** ✅ 已完成并交付
-**版本号:** v1.0.0
-**完成日期:** 2026-03-22
-
-**祝使用愉快!** 🎉🎊🎈
diff --git a/COMPLETION_REQUEST_FEATURE.md b/COMPLETION_REQUEST_FEATURE.md
deleted file mode 100644
index d30a055b..00000000
--- a/COMPLETION_REQUEST_FEATURE.md
+++ /dev/null
@@ -1,93 +0,0 @@
-# 完单申请功能实现总结
-
-## 功能概述
-
-在补齐支付单时,用户可以选择是否申请完成订单。如果勾选了完单申请,审核人员在进行支付审核时可以看到该申请信息。
-
-## 数据库修改
-
-### 新增字段(zyt_tcm_prescription_order表)
-
-```sql
--- 执行 add_completion_request_field.sql
-ALTER TABLE `zyt_tcm_prescription_order`
-ADD COLUMN `completion_request` tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT '完单申请:0=未申请,1=已申请' AFTER `payment_slip_audit_remark`,
-ADD COLUMN `completion_request_time` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '完单申请时间' AFTER `completion_request`,
-ADD COLUMN `completion_request_by` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '完单申请人ID' AFTER `completion_request_time`,
-ADD COLUMN `completion_request_by_name` varchar(64) NOT NULL DEFAULT '' COMMENT '完单申请人姓名' AFTER `completion_request_by`;
-```
-
-## 前端修改
-
-### 1. 补齐支付单对话框(order_list.vue)
-
-- 添加"完单申请"单选项(不申请/申请完成订单)
-- 添加提示文字说明
-- 表单数据中添加 `completion_request` 字段(默认值0)
-
-### 2. 提交逻辑
-
-- 手动创建支付单时,将 `completion_request` 字段传递给后端
-- 关联已有支付单时,同样传递 `completion_request` 字段
-
-### 3. 审核对话框
-
-- 如果订单有完单申请(`completion_request=1`),显示警告提示框
-- 显示申请人姓名和申请时间
-- 审核人员可以清楚看到该订单已申请完成
-
-## 后端修改
-
-### 1. 新增支付单接口(addPayOrder)
-
-**文件**: `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
-
-- 接收 `completion_request` 参数
-- 如果值为1,记录完单申请信息:
- - `completion_request = 1`
- - `completion_request_time = 当前时间戳`
- - `completion_request_by = 操作人ID`
- - `completion_request_by_name = 操作人姓名`
-- 操作日志中记录"并申请完成订单"
-
-### 2. 关联支付单接口(linkPayOrder)
-
-**文件**: `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
-
-- 接收 `completion_request` 参数
-- 如果值为1,记录完单申请信息(同上)
-- 操作日志中记录"并申请完成订单"
-
-## 使用流程
-
-1. **补齐支付单**
- - 用户在"已发货"状态下点击"新增支付单"
- - 选择手动创建或关联已有支付单
- - 勾选"申请完成订单"选项
- - 提交后,订单记录完单申请信息
-
-2. **支付审核**
- - 审核人员点击"支付审核"
- - 如果该订单有完单申请,对话框顶部显示黄色警告框
- - 显示申请人和申请时间
- - 审核人员可以根据完单申请决定是否通过
-
-3. **完成订单**
- - 支付审核通过后,订单状态变为"已发货"
- - 满足完成条件(已发货+支付审核通过)时,可以点击"完成订单"
-
-## 注意事项
-
-1. 完单申请只是一个提示信息,不会自动完成订单
-2. 审核人员仍需手动点击"完成订单"按钮
-3. 完单申请信息在订单详情中可见
-4. 操作日志会记录完单申请的操作
-
-## 测试要点
-
-- [ ] 手动创建支付单时勾选完单申请
-- [ ] 关联已有支付单时勾选完单申请
-- [ ] 审核对话框正确显示完单申请信息
-- [ ] 申请人姓名和时间正确显示
-- [ ] 操作日志正确记录
-- [ ] 不勾选完单申请时,审核对话框不显示提示
diff --git a/DEBUG_GUIDE.md b/DEBUG_GUIDE.md
deleted file mode 100644
index bf8420a6..00000000
--- a/DEBUG_GUIDE.md
+++ /dev/null
@@ -1,277 +0,0 @@
-# 首次登录修改密码 - 调试指南
-
-## 如何验证功能是否正常工作
-
-### 1. 检查数据库
-
-```sql
--- 查看管理员的 is_paw 状态
-SELECT id, account, name, is_paw FROM la_admin WHERE id = 1;
-```
-
-确认 `is_paw` 字段存在且值为 0。
-
-### 2. 检查后端 API
-
-#### 2.1 登录接口
-
-打开浏览器开发者工具(F12),切换到 Network 标签。
-
-登录后查看 `/adminapi/login/account` 接口的响应:
-
-```json
-{
- "code": 1,
- "msg": "success",
- "data": {
- "name": "管理员",
- "avatar": "...",
- "role_name": "超级管理员",
- "token": "...",
- "is_paw": 0 // ← 确认这个字段存在且为 0
- }
-}
-```
-
-#### 2.2 获取用户信息接口
-
-刷新页面后,查看 `/adminapi/auth.admin/mySelf` 接口的响应:
-
-```json
-{
- "code": 1,
- "msg": "success",
- "data": {
- "user": {
- "id": 1,
- "account": "admin",
- "name": "管理员",
- "is_paw": 0, // ← 确认这个字段存在且为 0
- // ... 其他字段
- },
- "menu": [...],
- "permissions": [...]
- }
-}
-```
-
-### 3. 检查前端状态
-
-在浏览器控制台(Console)中输入:
-
-```javascript
-// 查看 userStore 的状态
-const userStore = window.$nuxt?.$store?.state?.user ||
- JSON.parse(localStorage.getItem('pinia-user') || '{}')
-console.log('isPaw:', userStore.isPaw)
-console.log('userInfo:', userStore.userInfo)
-```
-
-或者在 Vue DevTools 中查看 Pinia store 的状态。
-
-### 4. 检查路由守卫
-
-在 `admin/src/permission.ts` 文件中添加调试日志:
-
-```typescript
-router.beforeEach(async (to, from, next) => {
- const userStore = useUserStore()
-
- console.log('=== 路由守卫 ===')
- console.log('目标路径:', to.path)
- console.log('isPaw:', userStore.isPaw)
- console.log('hasUserInfo:', Object.keys(userStore.userInfo).length !== 0)
-
- // ... 其他代码
-})
-```
-
-刷新页面,在控制台查看输出。
-
-### 5. 完整测试流程
-
-#### 步骤 1:设置测试账号
-
-```sql
-UPDATE la_admin SET is_paw = 0 WHERE id = 1;
-```
-
-#### 步骤 2:清除浏览器缓存
-
-- 清除 localStorage
-- 清除 sessionStorage
-- 清除 Cookies
-- 或者使用无痕模式
-
-#### 步骤 3:登录
-
-1. 打开浏览器开发者工具(F12)
-2. 切换到 Network 标签
-3. 登录系统
-4. 查看 `/adminapi/login/account` 接口响应,确认 `is_paw: 0`
-5. 观察是否跳转到 `/change-password`
-
-#### 步骤 4:测试 URL 绕过
-
-1. 在地址栏输入 `/workbench/index`
-2. 按回车
-3. 观察是否被拦截并跳转回 `/change-password`
-
-#### 步骤 5:测试刷新
-
-1. 在修改密码页面按 F5 刷新
-2. 查看 Network 标签,确认调用了 `/adminapi/auth.admin/mySelf` 接口
-3. 查看接口响应,确认 `is_paw: 0`
-4. 观察是否仍然停留在 `/change-password`
-
-#### 步骤 6:修改密码
-
-1. 输入新密码(至少 6 位)
-2. 点击"确认修改"
-3. 观察是否提示"密码修改成功,请重新登录"
-4. 观察是否跳转到登录页
-
-#### 步骤 7:验证数据库
-
-```sql
-SELECT id, account, is_paw FROM la_admin WHERE id = 1;
--- 应该显示 is_paw = 1
-```
-
-#### 步骤 8:使用新密码登录
-
-1. 使用新密码登录
-2. 观察是否直接进入系统(不跳转到修改密码页面)
-
-## 常见问题排查
-
-### 问题:登录后没有跳转到修改密码页面
-
-**排查步骤:**
-
-1. 检查登录接口响应是否包含 `is_paw` 字段
- ```javascript
- // 在 Network 标签查看 /adminapi/login/account 响应
- ```
-
-2. 检查前端是否保存了 `isPaw` 状态
- ```javascript
- // 在 Console 输入
- console.log(useUserStore().isPaw)
- ```
-
-3. 检查登录页面代码
- ```typescript
- // admin/src/views/account/login.vue
- const result: any = await userStore.login(formData)
- if (result.is_paw === 0) {
- router.push('/change-password')
- }
- ```
-
-### 问题:刷新后进入系统
-
-**排查步骤:**
-
-1. 检查 `getUserInfo` 接口是否返回 `is_paw`
- ```javascript
- // 在 Network 标签查看 /adminapi/auth.admin/mySelf 响应
- // 确认 data.user.is_paw 存在
- ```
-
-2. 检查后端代码
- ```php
- // server/app/adminapi/logic/auth/AdminLogic.php
- // detail() 方法的 field 数组中是否包含 'is_paw'
- ```
-
-3. 检查前端是否更新状态
- ```typescript
- // admin/src/stores/modules/user.ts
- getUserInfo() {
- return new Promise((resolve, reject) => {
- getUserInfo().then((data) => {
- this.isPaw = data.user.is_paw ?? 1 // 确认这行存在
- })
- })
- }
- ```
-
-4. 检查路由守卫逻辑
- ```typescript
- // admin/src/permission.ts
- // 确认在 getUserInfo() 之后检查 isPaw
- await userStore.getUserInfo()
- if (userStore.isPaw === 0) {
- next({ path: changePasswordPath })
- return
- }
- ```
-
-### 问题:可以通过 URL 绕过
-
-**排查步骤:**
-
-1. 检查路由守卫是否正确配置
- ```typescript
- // admin/src/permission.ts
- // 确认路由守卫在所有路由跳转时都会执行
- ```
-
-2. 在路由守卫中添加日志
- ```typescript
- router.beforeEach(async (to, from, next) => {
- console.log('路由守卫执行:', to.path, 'isPaw:', userStore.isPaw)
- // ...
- })
- ```
-
-3. 检查是否有其他路由配置覆盖了守卫
-
-## 调试技巧
-
-### 1. 使用 Vue DevTools
-
-安装 Vue DevTools 浏览器插件,可以实时查看:
-- Pinia store 状态
-- 路由信息
-- 组件状态
-
-### 2. 添加断点
-
-在关键位置添加 `debugger` 语句:
-
-```typescript
-// admin/src/permission.ts
-router.beforeEach(async (to, from, next) => {
- debugger // 浏览器会在这里暂停
- const userStore = useUserStore()
- // ...
-})
-```
-
-### 3. 查看完整的请求响应
-
-在 Network 标签中:
-- 点击请求
-- 查看 Response 标签
-- 确认返回的数据结构
-
-### 4. 清除缓存测试
-
-每次测试前清除缓存,确保测试环境干净:
-
-```javascript
-// 在 Console 中执行
-localStorage.clear()
-sessionStorage.clear()
-location.reload()
-```
-
-## 成功标准
-
-✅ 登录后自动跳转到修改密码页面
-✅ 无法通过输入 URL 绕过
-✅ 刷新后仍然在修改密码页面
-✅ 修改密码后 `is_paw` 更新为 1
-✅ 使用新密码登录后正常进入系统
diff --git a/DELIVERY_PACKAGE.md b/DELIVERY_PACKAGE.md
deleted file mode 100644
index dfb271dd..00000000
--- a/DELIVERY_PACKAGE.md
+++ /dev/null
@@ -1,361 +0,0 @@
-# 处方库功能 - 交付文档
-
-## 📦 项目交付信息
-
-**项目名称:** 处方库管理系统
-
-**版本号:** v1.0.0
-
-**交付日期:** 2026-03-22
-
-**开发状态:** ✅ 已完成并测试通过
-
-## 📋 交付清单
-
-### 1. 核心代码文件(15个)
-
-#### 后端代码(6个)
-- ✅ `server/database/migrations/create_prescription_library.sql` - 数据库表结构
-- ✅ `server/app/common/model/tcm/PrescriptionLibrary.php` - 数据模型
-- ✅ `server/app/adminapi/controller/tcm/PrescriptionLibraryController.php` - 控制器
-- ✅ `server/app/adminapi/logic/tcm/PrescriptionLibraryLogic.php` - 业务逻辑
-- ✅ `server/app/adminapi/lists/tcm/PrescriptionLibraryLists.php` - 列表逻辑
-- ✅ `server/app/adminapi/validate/tcm/PrescriptionLibraryValidate.php` - 数据验证
-
-#### 前端代码(2个)
-- ✅ `admin/src/views/consumer/prescription/list.vue` - 处方库页面
-- ✅ `admin/src/api/tcm.ts` - API接口(已更新)
-
-#### 安装脚本(2个)
-- ✅ `install_prescription_library.bat` - Windows安装脚本
-- ✅ `install_prescription_library.sh` - Linux/Mac安装脚本
-
-#### 测试文件(1个)
-- ✅ `TEST_PRESCRIPTION_LIBRARY.sql` - 测试SQL语句
-
-#### 文档文件(7个)
-- ✅ `PRESCRIPTION_LIBRARY_README.md` - 完整使用说明
-- ✅ `PRESCRIPTION_LIBRARY_DEMO.md` - 功能演示文档
-- ✅ `PRESCRIPTION_LIBRARY_SUMMARY.md` - 开发总结
-- ✅ `QUICK_START_PRESCRIPTION_LIBRARY.md` - 快速开始指南
-- ✅ `PRESCRIPTION_LIBRARY_FILES.md` - 文件清单
-- ✅ `INSTALLATION_CHECKLIST.md` - 安装检查清单
-- ✅ `DELIVERY_PACKAGE.md` - 本交付文档
-
-**总计:** 18个文件
-
-### 2. 代码统计
-
-| 类型 | 文件数 | 代码行数 |
-|------|--------|----------|
-| PHP | 6 | ~450行 |
-| Vue | 1 | ~350行 |
-| TypeScript | 1 | ~30行(新增) |
-| SQL | 2 | ~120行 |
-| Shell | 2 | ~100行 |
-| Markdown | 7 | ~1800行 |
-| **总计** | **19** | **~2850行** |
-
-## 🎯 功能特性
-
-### 核心功能
-1. ✅ 处方创建(处方名称 + 药材配方)
-2. ✅ 处方编辑
-3. ✅ 处方删除
-4. ✅ 处方查看
-5. ✅ 处方列表展示
-6. ✅ 处方搜索(按名称)
-7. ✅ 处方筛选(按是否公开)
-8. ✅ 权限控制(公开/私有)
-
-### 技术特性
-1. ✅ 完全独立,不依赖其他模块
-2. ✅ 支持动态添加药材
-3. ✅ 支持小数剂量(精确到0.1克)
-4. ✅ 完善的权限控制
-5. ✅ 软删除机制
-6. ✅ 数据验证
-7. ✅ 异常处理
-8. ✅ 响应式设计
-
-## 🔧 技术栈
-
-### 后端
-- **框架:** ThinkPHP 6.x
-- **语言:** PHP 7.4+
-- **数据库:** MySQL 5.7+
-- **架构:** MVC + 业务逻辑层
-
-### 前端
-- **框架:** Vue 3
-- **UI库:** Element Plus
-- **语言:** TypeScript
-- **构建工具:** Vite
-
-## 📊 数据库设计
-
-### 表名:zyt_prescription_library
-
-| 字段名 | 类型 | 说明 | 索引 |
-|--------|------|------|------|
-| id | int(11) | 主键ID | PRIMARY |
-| prescription_name | varchar(100) | 处方名称 | - |
-| herbs | text | 药材列表JSON | - |
-| is_public | tinyint(1) | 是否公开 | INDEX |
-| creator_id | int(11) | 创建人ID | INDEX |
-| creator_name | varchar(50) | 创建人姓名 | - |
-| create_time | int(10) | 创建时间 | INDEX |
-| update_time | int(10) | 更新时间 | - |
-| delete_time | int(10) | 删除时间 | - |
-
-### 索引设计
-- PRIMARY KEY: id
-- INDEX: creator_id
-- INDEX: is_public
-- INDEX: create_time
-
-## 🔌 API接口
-
-### 接口列表
-
-| 接口路径 | 方法 | 说明 | 权限 |
-|---------|------|------|------|
-| /tcm.prescriptionLibrary/lists | GET | 获取处方列表 | 需登录 |
-| /tcm.prescriptionLibrary/add | POST | 添加处方 | 需登录 |
-| /tcm.prescriptionLibrary/edit | POST | 编辑处方 | 需登录+权限 |
-| /tcm.prescriptionLibrary/delete | POST | 删除处方 | 需登录+创建者 |
-| /tcm.prescriptionLibrary/detail | GET | 获取处方详情 | 需登录+权限 |
-
-### 数据格式
-
-#### 药材数据格式(JSON)
-```json
-[
- {
- "name": "药材名称",
- "dosage": 剂量(克)
- }
-]
-```
-
-#### 示例
-```json
-[
- {"name": "熟地黄", "dosage": 24},
- {"name": "山茱萸", "dosage": 12},
- {"name": "山药", "dosage": 12}
-]
-```
-
-## 🔐 权限设计
-
-### 查看权限
-- 可以查看自己创建的所有处方
-- 可以查看其他人创建的公开处方
-- 不能查看其他人创建的私有处方
-
-### 编辑权限
-- 可以编辑自己创建的处方
-- 可以编辑其他人创建的公开处方
-- 不能编辑其他人创建的私有处方
-
-### 删除权限
-- 只能删除自己创建的处方
-- 不能删除其他人创建的处方
-
-## 📖 使用文档
-
-### 快速开始
-请参阅:`QUICK_START_PRESCRIPTION_LIBRARY.md`
-
-### 完整文档
-请参阅:`PRESCRIPTION_LIBRARY_README.md`
-
-### 功能演示
-请参阅:`PRESCRIPTION_LIBRARY_DEMO.md`
-
-### 安装指南
-请参阅:`INSTALLATION_CHECKLIST.md`
-
-## 🧪 测试报告
-
-### 功能测试
-- ✅ 创建处方(私有)
-- ✅ 创建处方(公开)
-- ✅ 编辑处方
-- ✅ 删除处方
-- ✅ 查看处方
-- ✅ 搜索处方
-- ✅ 筛选处方
-
-### 权限测试
-- ✅ 私有处方权限控制
-- ✅ 公开处方权限控制
-- ✅ 删除权限控制
-- ✅ 编辑权限控制
-
-### 数据验证测试
-- ✅ 处方名称验证
-- ✅ 药材列表验证
-- ✅ 药材名称验证
-- ✅ 药材剂量验证
-
-### 代码质量测试
-- ✅ 无语法错误
-- ✅ 无类型错误
-- ✅ 代码规范检查通过
-
-## 🚀 部署说明
-
-### 部署步骤
-
-1. **备份数据库**
- ```bash
- mysqldump -u用户名 -p密码 数据库名 > backup.sql
- ```
-
-2. **上传代码文件**
- - 上传所有后端PHP文件
- - 上传前端Vue文件
-
-3. **安装数据库表**
- ```bash
- # Windows
- install_prescription_library.bat
-
- # Linux/Mac
- ./install_prescription_library.sh
- ```
-
-4. **配置菜单权限**
- - 在后台添加菜单项
- - 配置访问权限
-
-5. **重启服务**
- ```bash
- # 清除缓存
- php think clear
-
- # 重新编译前端
- npm run build
- ```
-
-6. **测试功能**
- - 访问处方库页面
- - 测试各项功能
-
-### 环境要求
-- PHP >= 7.4
-- MySQL >= 5.7
-- Node.js >= 14.x
-- ThinkPHP 6.x
-- Vue 3.x
-
-## 📝 注意事项
-
-### 重要提示
-1. ⚠️ 安装前请务必备份数据库
-2. ⚠️ 确保数据库用户有创建表权限
-3. ⚠️ 安装后需要配置菜单权限
-4. ⚠️ 建议在测试环境先测试
-
-### 已知限制
-1. 暂不支持处方分类
-2. 暂不支持批量导入
-3. 暂不支持处方导出
-4. 暂不支持处方评论
-
-### 未来扩展
-1. 处方分类功能
-2. 处方标签功能
-3. 使用统计功能
-4. 导入导出功能
-5. 处方评论功能
-6. 版本管理功能
-
-## 🆘 技术支持
-
-### 常见问题
-请参阅:`QUICK_START_PRESCRIPTION_LIBRARY.md` 中的常见问题部分
-
-### 问题反馈
-如遇到问题,请提供:
-1. 错误信息截图
-2. 浏览器控制台日志
-3. 后端错误日志
-4. 操作步骤描述
-
-### 联系方式
-请联系技术支持团队获取帮助。
-
-## ✅ 验收标准
-
-### 功能验收
-- [x] 所有核心功能正常工作
-- [x] 权限控制正确
-- [x] 数据验证有效
-- [x] 搜索筛选正常
-
-### 代码验收
-- [x] 无语法错误
-- [x] 代码规范
-- [x] 注释完整
-- [x] 异常处理完善
-
-### 文档验收
-- [x] 使用文档完整
-- [x] 安装文档清晰
-- [x] API文档准确
-- [x] 示例代码可用
-
-### 测试验收
-- [x] 功能测试通过
-- [x] 权限测试通过
-- [x] 数据验证测试通过
-- [x] 代码质量测试通过
-
-## 📜 版本历史
-
-### v1.0.0 (2026-03-22)
-- ✅ 初始版本发布
-- ✅ 实现核心功能
-- ✅ 完成文档编写
-- ✅ 通过测试验收
-
-## 📄 许可证
-
-本项目遵循项目主体的许可证协议。
-
-## 🎉 交付确认
-
-### 交付内容确认
-- [x] 所有代码文件已交付
-- [x] 所有文档文件已交付
-- [x] 安装脚本已交付
-- [x] 测试文件已交付
-
-### 质量确认
-- [x] 代码质量符合标准
-- [x] 功能测试通过
-- [x] 文档完整准确
-- [x] 可以正常部署使用
-
-### 交付签字
-
-**开发人员:** _______________ **日期:** _______________
-
-**测试人员:** _______________ **日期:** _______________
-
-**项目经理:** _______________ **日期:** _______________
-
-**客户确认:** _______________ **日期:** _______________
-
----
-
-## 🌟 感谢使用
-
-感谢选择处方库管理系统!
-
-如有任何问题或建议,欢迎随时联系我们。
-
-**祝使用愉快!** 🎊
diff --git a/DIAGNOSIS_IMAGE_URL_PREFIX_FIX.md b/DIAGNOSIS_IMAGE_URL_PREFIX_FIX.md
deleted file mode 100644
index b17ee92f..00000000
--- a/DIAGNOSIS_IMAGE_URL_PREFIX_FIX.md
+++ /dev/null
@@ -1,259 +0,0 @@
-# Diagnosis Image URL Prefix Fix (诊断图片URL前缀修复)
-
-## Issue
-Image URLs in the diagnosis detail (tongue_images and report_files) were stored as relative paths without the domain prefix, causing issues when accessing them from external sources or different domains.
-
-## Solution
-Added logic to automatically prepend the current domain to image URLs that don't already have an `http://` or `https://` prefix.
-
-## Changes Made
-
-### File: `server/app/adminapi/logic/tcm/DiagnosisLogic.php`
-
-#### 1. Updated tongue_images Processing
-Added domain prefix logic after parsing the images array:
-
-```php
-if (!empty($diagnosis['tongue_images'])) {
- // 先尝试 JSON 解析(兼容旧数据)
- $files = json_decode($diagnosis['tongue_images'], true);
- if (is_array($files)) {
- $diagnosis['tongue_images'] = $files;
- } else {
- // JSON 解析失败则按逗号分割
- $diagnosis['tongue_images'] = array_filter(explode(',', $diagnosis['tongue_images'])) ?: [];
- }
-
- // 为没有 http 前缀的图片添加域名
- $domain = request()->domain();
- $diagnosis['tongue_images'] = array_map(function($url) use ($domain) {
- if (empty($url)) {
- return $url;
- }
- // 如果已经包含 http:// 或 https://,则跳过
- if (stripos($url, 'http://') === 0 || stripos($url, 'https://') === 0) {
- return $url;
- }
- // 添加域名前缀
- return $domain . $url;
- }, $diagnosis['tongue_images']);
-} else {
- $diagnosis['tongue_images'] = [];
-}
-```
-
-#### 2. Updated report_files Processing
-Applied the same logic to report files:
-
-```php
-if (!empty($diagnosis['report_files'])) {
- // 先尝试 JSON 解析(兼容旧数据)
- $files = json_decode($diagnosis['report_files'], true);
- if (is_array($files)) {
- $diagnosis['report_files'] = $files;
- } else {
- // JSON 解析失败则按逗号分割
- $diagnosis['report_files'] = array_filter(explode(',', $diagnosis['report_files'])) ?: [];
- }
-
- // 为没有 http 前缀的文件添加域名
- $domain = request()->domain();
- $diagnosis['report_files'] = array_map(function($url) use ($domain) {
- if (empty($url)) {
- return $url;
- }
- // 如果已经包含 http:// 或 https://,则跳过
- if (stripos($url, 'http://') === 0 || stripos($url, 'https://') === 0) {
- return $url;
- }
- // 添加域名前缀
- return $domain . $url;
- }, $diagnosis['report_files']);
-} else {
- $diagnosis['report_files'] = [];
-}
-```
-
-## Logic Flow
-
-### URL Processing Logic:
-1. Parse the images/files array (JSON or comma-separated)
-2. Get current domain using `request()->domain()`
-3. For each URL in the array:
- - Check if URL is empty → skip
- - Check if URL starts with `http://` or `https://` → skip (already has protocol)
- - Otherwise → prepend domain to the URL
-
-### Examples:
-
-#### Relative Path (Needs Domain):
-```php
-Input: "/uploads/images/tongue/2024/01/image.jpg"
-Domain: "https://admin.zhenyangtang.com.cn"
-Output: "https://admin.zhenyangtang.com.cn/uploads/images/tongue/2024/01/image.jpg"
-```
-
-#### Absolute URL (Skip):
-```php
-Input: "https://cdn.example.com/images/tongue.jpg"
-Output: "https://cdn.example.com/images/tongue.jpg"
-```
-
-#### HTTP URL (Skip):
-```php
-Input: "http://example.com/image.jpg"
-Output: "http://example.com/image.jpg"
-```
-
-#### Empty URL (Skip):
-```php
-Input: ""
-Output: ""
-```
-
-## Benefits
-
-### 1. Cross-Domain Compatibility
-Images can now be accessed from different domains or external applications without path issues.
-
-### 2. API Response Consistency
-API responses always return complete, accessible URLs regardless of how they were stored.
-
-### 3. Backward Compatibility
-- Existing relative paths are automatically converted
-- Existing absolute URLs are preserved
-- No database migration required
-
-### 4. Flexible Storage
-- Database can store either relative or absolute paths
-- System handles both formats transparently
-- Future-proof for CDN integration
-
-## Use Cases
-
-### 1. Mobile App Access
-Mobile apps can directly use the returned URLs without needing to construct them.
-
-### 2. Third-Party Integration
-External systems can access images using the complete URLs from API responses.
-
-### 3. CDN Migration
-When migrating to CDN, images with absolute CDN URLs will work alongside local relative paths.
-
-### 4. Multi-Domain Setup
-System works correctly across different domains (dev, staging, production).
-
-## Testing Steps
-
-### 1. Test Relative Path URLs
-1. Create a diagnosis with tongue images stored as relative paths:
- ```
- /uploads/images/tongue/2024/01/image.jpg
- ```
-2. Fetch diagnosis detail via API
-3. Verify response contains:
- ```json
- {
- "tongue_images": [
- "https://admin.zhenyangtang.com.cn/uploads/images/tongue/2024/01/image.jpg"
- ]
- }
- ```
-
-### 2. Test Absolute URLs
-1. Create a diagnosis with absolute URL:
- ```
- https://cdn.example.com/images/tongue.jpg
- ```
-2. Fetch diagnosis detail
-3. Verify URL is unchanged:
- ```json
- {
- "tongue_images": [
- "https://cdn.example.com/images/tongue.jpg"
- ]
- }
- ```
-
-### 3. Test Mixed URLs
-1. Create a diagnosis with both relative and absolute URLs:
- ```
- [
- "/uploads/local.jpg",
- "https://cdn.example.com/remote.jpg"
- ]
- ```
-2. Fetch diagnosis detail
-3. Verify correct processing:
- ```json
- {
- "tongue_images": [
- "https://admin.zhenyangtang.com.cn/uploads/local.jpg",
- "https://cdn.example.com/remote.jpg"
- ]
- }
- ```
-
-### 4. Test Empty/Null Values
-1. Create a diagnosis with empty images
-2. Fetch diagnosis detail
-3. Verify empty array is returned:
- ```json
- {
- "tongue_images": []
- }
- ```
-
-### 5. Test Report Files
-Repeat all tests above for `report_files` field.
-
-## Technical Details
-
-### Domain Detection
-Uses `request()->domain()` which returns the current request domain including protocol:
-- Development: `http://localhost:8000`
-- Production: `https://admin.zhenyangtang.com.cn`
-
-### Case-Insensitive Protocol Check
-Uses `stripos()` for case-insensitive checking:
-- Matches: `http://`, `HTTP://`, `Http://`
-- Matches: `https://`, `HTTPS://`, `Https://`
-
-### Array Processing
-Uses `array_map()` for efficient processing of all URLs in the array.
-
-## Related Files
-
-### Backend:
-- `server/app/adminapi/logic/tcm/DiagnosisLogic.php`
- - Updated `tongue_images` processing
- - Updated `report_files` processing
-
-### Database:
-- `tcm_diagnosis` table
- - `tongue_images` column (stores JSON or comma-separated paths)
- - `report_files` column (stores JSON or comma-separated paths)
-
-## Notes
-
-### Storage Format
-The database continues to store paths as-is (relative or absolute). The conversion happens only when reading data for API responses.
-
-### Performance
-The `array_map()` operation is efficient and adds minimal overhead. For typical diagnosis records with 1-5 images, the performance impact is negligible.
-
-### Future Enhancements
-If needed, this logic could be extracted into a helper function for reuse across other file/image fields in the system.
-
-## Summary
-
-✅ Automatically adds domain prefix to relative image URLs
-✅ Preserves absolute URLs (http/https)
-✅ Handles both tongue_images and report_files
-✅ Backward compatible with existing data
-✅ Case-insensitive protocol detection
-✅ Handles empty/null values gracefully
-✅ No database migration required
-✅ Works across different domains
-
-Image URLs in diagnosis details are now always complete and accessible, improving API usability and cross-domain compatibility!
diff --git a/DIAGNOSIS_LIST_SORTING.md b/DIAGNOSIS_LIST_SORTING.md
deleted file mode 100644
index 4f77d046..00000000
--- a/DIAGNOSIS_LIST_SORTING.md
+++ /dev/null
@@ -1,90 +0,0 @@
-# 诊单列表排序规则修改
-
-## 修改内容
-
-修改了诊单列表的排序逻辑,按照新的业务规则对数据进行排序。
-
-## 排序规则
-
-### 优先级顺序
-1. **已过号** (status=4) - 最优先显示
-2. **已预约** (status=1) - 第二优先
-3. **已完成** (status=3) - 第三优先
-4. **其他状态** - 最后显示
-
-### 同状态内排序
-在同一状态内,按以下规则升序排列:
-- 挂号日期 (appointment_date)
-- 挂号时间 (appointment_time)
-- 诊单ID (id) 降序
-
-## 技术实现
-
-### 排序SQL逻辑
-
-```php
-// 获取最早的挂号状态(用于排序优先级)
-$minAptStatusExpr = '(SELECT apt.status FROM ' . $aptTbl . ' apt
- WHERE apt.patient_id = ' . $diagTbl . '.id
- AND apt.status IN (1,3,4)' . $minAptDateCond . '
- ORDER BY CASE apt.status
- WHEN 4 THEN 1
- WHEN 1 THEN 2
- WHEN 3 THEN 3
- ELSE 4
- END,
- apt.appointment_date ASC,
- apt.appointment_time ASC
- LIMIT 1)';
-
-// 获取最早的挂号时间(用于同状态内排序)
-$minAptExpr = '(SELECT MIN(CONCAT(apt.appointment_date, \' \',
- IFNULL(NULLIF(TRIM(apt.appointment_time), \'\'), \'00:00:00\')))
- FROM ' . $aptTbl . ' apt
- WHERE apt.patient_id = ' . $diagTbl . '.id
- AND apt.status IN (1,3,4)' . $minAptDateCond . ')';
-
-// 最终排序
-->orderRaw('CASE IFNULL(' . $minAptStatusExpr . ', 999)
- WHEN 4 THEN 1
- WHEN 1 THEN 2
- WHEN 3 THEN 3
- ELSE 4
-END ASC,
-IFNULL(' . $minAptExpr . ", '9999-12-31 23:59:59') ASC,
-{$diagTbl}.id DESC")
-```
-
-### 关键点
-
-1. **子查询获取状态**: 使用子查询获取每个诊单最早的挂号状态
-2. **CASE表达式**: 将状态映射为排序优先级数字 (1-4)
-3. **IFNULL处理**: 对于没有挂号的诊单,使用默认值 999 和 '9999-12-31 23:59:59'
-4. **日期筛选支持**: 如果传入 `appointment_date` 参数,只按该日期的挂号排序
-
-## 修改文件
-
-- `server/app/adminapi/lists/tcm/DiagnosisLists.php`
-
-## 验证步骤
-
-1. 清除缓存: `php think clear` ✅
-2. 访问诊单列表页面
-3. 验证排序顺序:
- - 已过号的诊单显示在最前面
- - 然后是已预约的诊单
- - 最后是已完成的诊单
-4. 验证同状态内按挂号时间升序排列
-
-## 业务场景
-
-此排序规则适用于以下场景:
-- 医生/医助需要优先处理已过号的患者(可能需要重新安排)
-- 然后关注即将到来的预约
-- 最后查看已完成的历史记录
-
-## 注意事项
-
-- 排序只考虑状态为 1(已预约)、3(已完成)、4(已过号) 的挂号记录
-- 没有挂号记录的诊单会排在最后
-- 如果一个诊单有多条挂号记录,取最早的一条进行排序
diff --git a/DOCTOR_STATISTICS_GUIDE.md b/DOCTOR_STATISTICS_GUIDE.md
deleted file mode 100644
index b044d2cf..00000000
--- a/DOCTOR_STATISTICS_GUIDE.md
+++ /dev/null
@@ -1,121 +0,0 @@
-# 医生数据统计功能说明
-
-## 功能概述
-
-医生数据统计功能用于统计每个医生在不同时间范围内的诊单数据,包括:
-- 已挂号数量(status=1)
-- 已完成数量(status=3)
-- 已过号数量(status=4)
-- 已取消数量(status=2)
-- 完成率计算
-
-## 文件清单
-
-### 前端文件
-1. `admin/src/views/doctor/tongji.vue` - 统计页面
-2. `admin/src/api/doctor.ts` - API接口(已更新)
-
-### 后端文件
-1. `server/app/adminapi/controller/doctor/StatisticsController.php` - 统计控制器
-2. `server/app/adminapi/lists/doctor/StatisticsLists.php` - 统计数据列表逻辑
-
-## 功能特性
-
-### 1. 时间范围筛选
-- 今天:查看当天的统计数据
-- 最近7天:查看最近一周的统计数据
-- 最近30天:查看最近一个月的统计数据
-- 自定义:选择任意日期范围进行查询
-
-### 2. 医生筛选
-- 可以选择特定医生查看其统计数据
-- 也可以不选择医生,查看所有医生的统计数据
-
-### 3. 统计指标
-- **总诊单数**:该时间范围内的所有诊单
-- **已挂号**:状态为"已预约"的诊单数量
-- **已完成**:状态为"已完成"的诊单数量
-- **已过号**:状态为"已过号"的诊单数量
-- **已取消**:状态为"已取消"的诊单数量
-- **完成率**:已完成数量 / 总诊单数 × 100%
-
-### 4. 完成率颜色标识
-- 绿色:完成率 ≥ 80%
-- 蓝色:完成率 ≥ 60%
-- 橙色:完成率 ≥ 40%
-- 红色:完成率 < 40%
-
-## 使用方法
-
-1. 访问医生统计页面(路由:`/doctor/tongji`)
-2. 选择时间范围(今天/最近7天/最近30天/自定义)
-3. 可选:选择特定医生
-4. 点击"查询"按钮获取统计数据
-5. 查看统计表格中的各项数据
-
-## API接口
-
-### 获取医生诊单统计
-- **接口地址**:`GET /doctor.statistics/lists`
-- **请求参数**:
- - `doctor_id`(可选):医生ID
- - `time_type`(必填):时间类型(today/week/month/custom)
- - `start_date`(可选):开始日期(time_type=custom时必填)
- - `end_date`(可选):结束日期(time_type=custom时必填)
-
-- **返回数据**:
-```json
-{
- "code": 1,
- "msg": "success",
- "data": {
- "lists": [
- {
- "doctor_id": 1,
- "doctor_name": "张医生",
- "total_count": 100,
- "registered_count": 20,
- "completed_count": 60,
- "missed_count": 10,
- "cancelled_count": 10,
- "completion_rate": 60.00,
- "time_range": "今天 (2024-01-01)"
- }
- ]
- }
-}
-```
-
-## 数据库依赖
-
-该功能依赖以下数据表:
-- `la_doctor_appointment`:医生预约表
-- `la_admin`:管理员表(医生信息)
-- `la_admin_role`:管理员角色关联表(用于识别医生角色)
-
-### 医生角色识别
-系统通过 `la_admin_role` 表来识别医生角色:
-- 查询 `la_admin_role` 表中 `role_id=1` 的记录
-- 获取对应的 `admin_id` 列表
-- 这些 `admin_id` 对应的管理员即为医生
-
-### 预约状态说明
-- 1:已预约(已挂号)
-- 2:已取消
-- 3:已完成
-- 4:已过号
-
-## 注意事项
-
-1. 统计数据基于 `appointment_date` 字段进行时间范围筛选
-2. 只统计 `role_id=1` 的管理员(医生角色)
-3. 只统计未删除且未禁用的医生
-4. 完成率保留两位小数
-
-## 后续优化建议
-
-1. 添加数据导出功能(Excel)
-2. 添加图表展示(柱状图、折线图)
-3. 添加同比、环比分析
-4. 添加医生排名功能
-5. 添加更多维度的统计(按科室、按时段等)
diff --git a/DYNAMIC_DOSE_COUNT_LABEL.md b/DYNAMIC_DOSE_COUNT_LABEL.md
deleted file mode 100644
index b71ac497..00000000
--- a/DYNAMIC_DOSE_COUNT_LABEL.md
+++ /dev/null
@@ -1,204 +0,0 @@
-# Dynamic Dose Count Label (动态剂数标签)
-
-## Feature
-Made the dose count label dynamic based on the selected dose unit. When the user selects a different unit (剂/丸/袋/盒/瓶/膏/贴), the label automatically updates to match.
-
-## Changes Made
-
-### File: `admin/src/views/consumer/prescription/order_list.vue`
-
-#### 1. Added Computed Property
-Added a computed property that generates the label dynamically:
-
-```typescript
-// 动态剂数标签(根据剂量单位变化)
-const doseCountLabel = computed(() => {
- const unit = editForm.dose_unit || '剂'
- return `${unit}数`
-})
-```
-
-#### 2. Updated Form Item Label
-Changed from static label to dynamic computed property:
-
-**Before:**
-```vue
-
-
-
-```
-
-**After:**
-```vue
-
-
-
-```
-
-## How It Works
-
-### Label Generation Logic
-```typescript
-const unit = editForm.dose_unit || '剂' // Get current unit, default to '剂'
-return `${unit}数` // Append '数' to create label
-```
-
-### Examples
-
-| Selected Unit | Label Display |
-|---------------|---------------|
-| 剂 | 剂数 |
-| 丸 | 丸数 |
-| 袋 | 袋数 |
-| 盒 | 盒数 |
-| 瓶 | 瓶数 |
-| 膏 | 膏数 |
-| 贴 | 贴数 |
-
-### Reactive Updates
-The label updates automatically when:
-1. User selects a different dose unit from the dropdown
-2. Form is loaded with existing data
-3. Form is reset or cleared
-
-## User Experience
-
-### Before:
-- Label always shows "剂数" regardless of selected unit
-- Confusing when unit is "贴" but label says "剂数"
-
-### After:
-- Label dynamically matches the selected unit
-- Shows "贴数" when unit is "贴"
-- Shows "丸数" when unit is "丸"
-- More intuitive and consistent
-
-## Implementation Details
-
-### Computed Property Benefits
-1. **Reactive**: Automatically updates when `editForm.dose_unit` changes
-2. **Efficient**: Only recalculates when dependency changes
-3. **Clean**: No need for watchers or manual updates
-
-### Default Value
-Uses `'剂'` as default when `dose_unit` is empty or undefined:
-```typescript
-const unit = editForm.dose_unit || '剂'
-```
-
-### Placeholder Update
-Also updated the placeholder to match the label:
-```vue
-:placeholder="doseCountLabel"
-```
-
-This ensures consistency between the label and placeholder text.
-
-## Testing Steps
-
-### 1. Test Label Changes
-1. Open order edit dialog
-2. Check initial label (should be "剂数" if dose_unit is "剂")
-3. Change dose unit to "贴"
-4. Verify label changes to "贴数"
-5. Try all other units and verify labels update correctly
-
-### 2. Test with Existing Data
-1. Edit an order that has dose_unit = "丸"
-2. Verify label shows "丸数" when dialog opens
-3. Change to different unit
-4. Verify label updates immediately
-
-### 3. Test Default Behavior
-1. Create a new order (dose_unit is empty)
-2. Verify label shows "剂数" (default)
-3. Select a unit
-4. Verify label updates
-
-### 4. Test Placeholder
-1. Clear the dose_count field
-2. Verify placeholder text matches the label
-3. Change dose unit
-4. Verify placeholder updates with label
-
-## Edge Cases Handled
-
-### Empty/Undefined Unit
-```typescript
-const unit = editForm.dose_unit || '剂'
-```
-Falls back to '剂' if unit is empty, null, or undefined.
-
-### Form Reset
-When form is reset, the computed property automatically recalculates based on the new (or default) value.
-
-### Multiple Edits
-Label updates correctly even when editing multiple orders in sequence without closing the dialog.
-
-## Related Code
-
-### Form Structure
-```vue
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-### Data Flow
-```
-User selects dose_unit → editForm.dose_unit changes →
-doseCountLabel computed property recalculates →
-Label and placeholder update in UI
-```
-
-## Future Enhancements
-
-### Possible Improvements
-1. **Validation Message**: Update validation messages to use dynamic unit
-2. **Display in Detail View**: Show unit-specific label in order detail view
-3. **Internationalization**: Support multiple languages for unit names
-4. **Custom Units**: Allow admin to configure custom dose units
-
-### Example: Dynamic Validation
-```typescript
-const doseCountRules = computed(() => [
- { required: true, message: `请输入${editForm.dose_unit || '剂'}数`, trigger: 'blur' }
-])
-```
-
-## Summary
-
-✅ Added dynamic label that changes based on selected dose unit
-✅ Label updates automatically when unit changes
-✅ Placeholder also updates to match label
-✅ Default to "剂数" when unit is empty
-✅ Handles all 7 dose units (剂/丸/袋/盒/瓶/膏/贴)
-✅ Reactive and efficient using computed property
-✅ Better user experience with consistent terminology
-
-The dose count label now dynamically reflects the selected unit, making the form more intuitive and user-friendly!
diff --git a/EXPRESS_TRACKING_AUTO_LOAD.md b/EXPRESS_TRACKING_AUTO_LOAD.md
deleted file mode 100644
index a1428999..00000000
--- a/EXPRESS_TRACKING_AUTO_LOAD.md
+++ /dev/null
@@ -1,299 +0,0 @@
-# 物流轨迹自动加载优化
-
-## 优化目标
-
-将物流轨迹从"手动点击查询"改为"打开详情自动加载",提升用户体验:
-1. 打开订单详情时自动显示物流轨迹
-2. 无需额外点击"查询轨迹"按钮
-3. 保留"刷新轨迹"按钮用于手动更新
-
-## 用户体验对比
-
-### 优化前
-```
-用户操作流程:
-1. 点击订单查看详情
-2. 等待详情加载
-3. 找到"查询轨迹"按钮
-4. 点击"查询轨迹"
-5. 等待轨迹加载
-6. 查看物流信息
-
-总步骤:6步
-```
-
-### 优化后
-```
-用户操作流程:
-1. 点击订单查看详情
-2. 等待详情和轨迹自动加载
-3. 查看物流信息
-
-总步骤:3步
-减少:50%操作步骤
-```
-
-## 代码修改
-
-### 1. 自动加载逻辑
-
-**文件**:`admin/src/views/consumer/prescription/order_list.vue`
-
-在 `openDetail()` 函数中添加自动加载:
-
-```typescript
-async function openDetail(id: number) {
- detailVisible.value = true
- detailData.value = null
- logisticsTracePayload.value = null
- detailLogisticsExpress.value = 'auto'
- detailLoading.value = true
- try {
- const res: any = await prescriptionOrderDetail({ id })
- const d = res?.data ?? res ?? null
- detailData.value = d
- if (d) {
- detailLogisticsExpress.value = String(d.express_company || 'auto') || 'auto'
-
- // 如果有快递单号,自动加载物流轨迹
- if (String(d.tracking_number || '').trim()) {
- fetchLogisticsTrace()
- }
- }
- } catch (e: any) {
- feedback.msgError(e?.message || '加载失败')
- detailVisible.value = false
- } finally {
- detailLoading.value = false
- }
-}
-```
-
-**关键点**:
-- 检查订单是否有快递单号
-- 有快递单号时自动调用 `fetchLogisticsTrace()`
-- 无快递单号时不调用,避免无效请求
-
-### 2. UI调整
-
-#### 按钮改为"刷新轨迹"
-
-```vue
-
-
-
-
- 刷新轨迹
-
-```
-
-**改动**:
-- 按钮文字:`查询轨迹` → `刷新轨迹`
-- 按钮类型:`type="primary"` → `type="default"`(降低视觉优先级)
-- 添加刷新图标:``
-
-#### 添加加载状态提示
-
-```vue
-
-
- 正在加载物流轨迹...
-
-```
-
-**作用**:
-- 首次自动加载时显示加载提示
-- 已有数据时不显示(避免闪烁)
-- 使用旋转动画图标
-
-#### 导入图标
-
-```typescript
-import { Refresh, Loading } from '@element-plus/icons-vue'
-```
-
-## 工作流程
-
-### 完整流程
-
-```
-用户点击"查看详情"
- ↓
-打开详情弹窗
- ↓
-加载订单详情
- ↓
-检查是否有快递单号?
- ↓ 有
-自动调用 fetchLogisticsTrace()
- ↓
-显示"正在加载物流轨迹..."
- ↓
-查询数据库(优先)
- ↓
-有数据?
- ↓ 有
-显示物流轨迹(13条记录)
- ↓
-显示数据来源和更新时间
-```
-
-### 手动刷新流程
-
-```
-用户点击"刷新轨迹"按钮
- ↓
-重新调用 fetchLogisticsTrace()
- ↓
-查询数据库(优先)
- ↓
-无数据或需要更新?
- ↓ 是
-调用快递100 API
- ↓
-更新数据库
- ↓
-显示最新轨迹
-```
-
-## 性能优化
-
-### 1. 并行加载
-
-订单详情和物流轨迹是并行加载的:
-- 订单详情:`prescriptionOrderDetail()`
-- 物流轨迹:`fetchLogisticsTrace()`(在详情加载完成后立即触发)
-
-虽然不是完全并行,但由于物流查询从数据库读取(< 50ms),总体加载时间几乎不增加。
-
-### 2. 条件加载
-
-只有在有快递单号时才加载物流轨迹:
-```typescript
-if (String(d.tracking_number || '').trim()) {
- fetchLogisticsTrace()
-}
-```
-
-避免无效请求。
-
-### 3. 数据库优先
-
-物流查询优先从数据库读取:
-- 数据库查询:< 50ms
-- API查询:> 1000ms
-
-大部分情况下都能从数据库快速获取数据。
-
-## 用户体验提升
-
-### 1. 减少操作步骤
-- 从6步减少到3步
-- 减少50%操作
-
-### 2. 信息即时可见
-- 打开详情即可看到物流信息
-- 无需额外操作
-
-### 3. 保留手动控制
-- "刷新轨迹"按钮用于手动更新
-- 切换快递公司后可重新查询
-
-### 4. 清晰的状态反馈
-- 加载中:显示"正在加载物流轨迹..."
-- 加载完成:显示轨迹列表
-- 数据来源:显示"[数据库]"或"[API实时]"
-- 更新时间:显示"最后更新:2026-04-07 17:14:16"
-
-## 边界情况处理
-
-### 1. 无快递单号
-- 不调用物流查询
-- 不显示物流轨迹区域
-
-### 2. 数据库无数据
-- 自动调用快递100 API
-- 显示"[API实时]"标签
-
-### 3. 查询失败
-- 显示错误提示
-- 保留"刷新轨迹"按钮供重试
-
-### 4. 已签收订单
-- 显示完整历史轨迹
-- 显示"已签收"状态
-- 数据不会再自动更新(定时任务已停止)
-
-## 测试场景
-
-### 场景1:有快递单号且数据库有数据
-```
-1. 打开订单详情
-2. 自动显示"正在加载物流轨迹..."
-3. < 50ms 后显示13条轨迹
-4. 显示"[数据库]"标签
-5. 显示"最后更新:2026-04-07 17:14:16"
-```
-
-### 场景2:有快递单号但数据库无数据
-```
-1. 打开订单详情
-2. 自动显示"正在加载物流轨迹..."
-3. 调用快递100 API(> 1s)
-4. 显示轨迹
-5. 显示"[API实时]"标签
-6. 数据保存到数据库
-```
-
-### 场景3:无快递单号
-```
-1. 打开订单详情
-2. 不显示物流轨迹区域
-3. 不发起任何物流查询请求
-```
-
-### 场景4:手动刷新
-```
-1. 订单详情已打开,轨迹已显示
-2. 点击"刷新轨迹"按钮
-3. 重新查询(优先数据库)
-4. 更新显示
-```
-
-## 相关文件
-
-### 修改的文件
-- `admin/src/views/consumer/prescription/order_list.vue` - 订单列表页面
-
-### 相关文档
-- `EXPRESS_TRACKING_DATABASE_QUERY.md` - 数据库查询优化
-- `EXPRESS_TRACKING_SYNC_COMPLETE.md` - 同步完成报告
-- `EXPRESS_TRACKING_SYSTEM.md` - 系统架构
-
-## 总结
-
-✅ 打开详情自动加载物流轨迹
-✅ 减少50%用户操作步骤
-✅ 保留手动刷新功能
-✅ 添加加载状态提示
-✅ 优化按钮文字和样式
-✅ 条件加载,避免无效请求
-✅ 数据库优先,快速响应
-
-**用户体验提升**:
-- 操作步骤:6步 → 3步
-- 加载时间:< 50ms(数据库)
-- 信息可见性:立即可见
-- 操作便捷性:无需额外点击
-
-**下一步**:
-1. 前端测试自动加载功能
-2. 验证各种边界情况
-3. 收集用户反馈
-4. 可选:添加"自动刷新"开关(用户可选择是否自动加载)
diff --git a/EXPRESS_TRACKING_COMMAND_FIX.md b/EXPRESS_TRACKING_COMMAND_FIX.md
deleted file mode 100644
index 0da9bb29..00000000
--- a/EXPRESS_TRACKING_COMMAND_FIX.md
+++ /dev/null
@@ -1,199 +0,0 @@
-# 物流追踪定时任务修复报告
-
-## 问题诊断
-
-### 1. PHP语法错误
-**问题**:`server/app/command/ExpressAutoUpdate.php` 文件中的crontab示例使用了 `*/10`,导致PHP解析器将其识别为代码而非注释。
-
-**错误信息**:
-```
-syntax error, unexpected token "*"
-```
-
-**原因**:注释中的 `*/10` 被PHP解析器误认为是多行注释的结束符。
-
-**解决方案**:将crontab示例从 `*/10 * * * *` 改为 `0,10,20,30,40,50 * * * *`(效果相同,每10分钟执行)。
-
-### 2. 命名空间路径错误
-**问题**:`server/config/console.php` 中的命名空间路径使用单反斜杠。
-
-**错误配置**:
-```php
-'express:auto-update' => 'app\command\ExpressAutoUpdate',
-```
-
-**正确配置**:
-```php
-'express:auto-update' => 'app\\command\\ExpressAutoUpdate',
-```
-
-**原因**:PHP字符串中的反斜杠需要转义。
-
-### 3. PHP版本检查
-**问题**:系统PHP版本为8.0.2,但Composer要求8.1.0+。
-
-**临时解决方案**:注释掉 `server/vendor/composer/platform_check.php` 中的版本检查(仅用于测试)。
-
-**生产环境建议**:升级PHP到8.1+版本。
-
-## 已修复的文件
-
-### 1. server/app/command/ExpressAutoUpdate.php
-```php
-/**
- * 物流自动更新定时任务
- *
- * 使用方法:
- * php think express:auto-update
- *
- * 配置crontab(每10分钟执行一次):
- * 0,10,20,30,40,50 * * * * cd /path/to/server && php think express:auto-update >> /dev/null 2>&1
- */
-```
-
-### 2. server/config/console.php
-```php
-'commands' => [
- // ... 其他命令
- // 物流自动更新
- 'express:auto-update' => 'app\\command\\ExpressAutoUpdate',
-],
-```
-
-### 3. server/vendor/composer/platform_check.php(临时)
-```php
-// Temporarily disabled for testing
-// if (!(PHP_VERSION_ID >= 80100)) {
-// $issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.';
-// }
-```
-
-## 测试结果
-
-### 命令执行测试
-```bash
-$ php think express:auto-update
-开始自动更新物流信息...
-更新完成!
-总数: 0
-成功: 0
-失败: 0
-耗时: 0.21秒
-```
-
-✅ 命令执行成功,无语法错误。
-✅ 返回码为0,表示正常退出。
-✅ 总数为0是正常的,因为数据库中还没有需要更新的物流记录。
-
-## 过滤逻辑验证
-
-### 定时任务是否过滤已签收订单?
-
-**答案:是的,已正确过滤。**
-
-查看 `ExpressTrackingService::autoUpdateBatch()` 方法:
-
-```php
-// 查询需要更新的记录
-// 条件:
-// 1. auto_update = 1(启用自动更新)
-// 2. next_update_time <= now(到达更新时间)
-// 3. is_signed = 0(未签收)
-// 4. current_state 不是终态(排除已签收、退签、拒签)
-$list = ExpressTracking::where('auto_update', 1)
- ->where('next_update_time', '<=', $now)
- ->where('is_signed', 0)
- ->whereNotIn('current_state', [
- ExpressTracking::STATE_SIGNED, // 3 - 已签收
- ExpressTracking::STATE_RETURN_SIGNED, // 4 - 退签
- ExpressTracking::STATE_REJECTED, // 5 - 拒签
- ])
- ->whereNull('delete_time')
- ->limit($limit)
- ->select();
-```
-
-**过滤条件说明**:
-1. `is_signed = 0` - 只查询未签收的记录
-2. `whereNotIn('current_state', [3, 4, 5])` - 排除所有终态(已签收、退签、拒签)
-3. 双重保险,确保不会浪费查询次数
-
-## 下一步操作
-
-### 1. 执行数据库迁移
-```bash
-# Windows
-cd server
-php think migrate:run
-
-# 或手动执行SQL
-mysql -u root -p your_database < database/migrations/create_express_tracking_tables.sql
-```
-
-### 2. 集成到订单系统
-在订单创建/编辑时调用:
-```php
-use app\common\service\ExpressTrackingService;
-
-// 创建或更新物流追踪
-ExpressTrackingService::createOrUpdate([
- 'order_id' => $orderId,
- 'order_type' => 'prescription',
- 'tracking_number' => $trackingNumber,
- 'express_company' => $expressCompany,
- 'recipient_phone' => $phone,
- 'recipient_name' => $name,
- 'recipient_address' => $address,
-]);
-```
-
-### 3. 配置定时任务
-
-#### Windows(任务计划程序)
-```batch
-# 创建任务(每10分钟执行)
-schtasks /create /tn "ExpressAutoUpdate" /tr "php D:\web\zyt\server\think express:auto-update" /sc minute /mo 10
-```
-
-#### Linux(crontab)
-```bash
-# 编辑crontab
-crontab -e
-
-# 添加以下行(每10分钟执行)
-0,10,20,30,40,50 * * * * cd /path/to/server && php think express:auto-update >> /dev/null 2>&1
-```
-
-### 4. 手动测试查询
-```bash
-# 测试单次查询
-php think express:auto-update
-
-# 查看日志
-tail -f runtime/log/202X-XX-XX.log
-```
-
-## 注意事项
-
-1. **PHP版本**:生产环境建议升级到PHP 8.1+
-2. **快递100账号**:确保账号已充值,余额充足
-3. **查询频率**:默认每10分钟查询一次,可根据需要调整
-4. **查询限制**:每次最多处理50条记录,避免超时
-5. **终态停止**:已签收/退签/拒签的订单会自动停止更新
-
-## 相关文档
-
-- [EXPRESS_TRACKING_SYSTEM.md](EXPRESS_TRACKING_SYSTEM.md) - 系统架构和设计
-- [EXPRESS_TRACKING_QUICKSTART.md](EXPRESS_TRACKING_QUICKSTART.md) - 快速开始指南
-- [EXPRESS_TRACKING_FIXES.md](EXPRESS_TRACKING_FIXES.md) - 修复记录
-- [KUAIDI100_TROUBLESHOOTING.md](KUAIDI100_TROUBLESHOOTING.md) - 快递100故障排查
-
-## 总结
-
-✅ 修复了PHP语法错误(crontab注释)
-✅ 修复了命名空间路径错误(双反斜杠转义)
-✅ 验证了过滤逻辑(已正确过滤已签收订单)
-✅ 命令可以正常执行
-✅ 临时绕过PHP版本检查(仅测试用)
-
-系统已就绪,可以开始集成到订单系统并配置定时任务。
diff --git a/EXPRESS_TRACKING_DATABASE_QUERY.md b/EXPRESS_TRACKING_DATABASE_QUERY.md
deleted file mode 100644
index f8a2a6da..00000000
--- a/EXPRESS_TRACKING_DATABASE_QUERY.md
+++ /dev/null
@@ -1,328 +0,0 @@
-# 物流追踪数据库查询优化
-
-## 优化目标
-
-将物流查询从"每次调用API"改为"优先查询数据库",实现:
-1. 提高查询速度(数据库查询 < 50ms,API查询 > 1s)
-2. 节省API调用次数(快递100按次收费)
-3. 显示完整历史轨迹(数据库保存所有历史记录)
-4. 降低API限流风险
-
-## 实现方案
-
-### 查询优先级
-
-```
-用户点击"查询轨迹"
- ↓
-1. 先查询数据库(zyt_express_tracking + zyt_express_trace)
- ↓
- 有数据?
- ↓ 是
- 返回数据库数据(标记 source: database)
- ↓ 否
-2. 调用快递100 API
- ↓
- 返回API数据(标记 source: api)
-```
-
-### 数据流转
-
-```
-订单创建/编辑
- ↓
-ExpressTrackingService::createOrUpdate()
- ↓
-创建追踪记录 + 立即查询一次
- ↓
-保存到数据库(tracking + traces)
- ↓
-定时任务每10分钟自动更新
- ↓
-用户查询时直接从数据库读取
-```
-
-## 代码修改
-
-### 1. 后端服务层
-
-**文件**:`server/app/common/service/ExpressTrackingService.php`
-
-新增方法:
-```php
-/**
- * 根据快递单号获取追踪详情(包含轨迹)
- */
-public static function getDetailByTrackingNumber(string $trackingNumber): ?array
-{
- $tracking = ExpressTracking::with(['traces' => function($query) {
- // 按时间倒序排列(最新的在前)
- $query->order('trace_time_stamp', 'desc');
- }])
- ->where('tracking_number', $trackingNumber)
- ->whereNull('delete_time')
- ->find();
-
- if (!$tracking) {
- return null;
- }
-
- $data = $tracking->toArray();
- $data['traces'] = $tracking->traces ? $tracking->traces->toArray() : [];
-
- return $data;
-}
-```
-
-### 2. 后端逻辑层
-
-**文件**:`server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
-
-修改 `logisticsTrace()` 方法:
-```php
-// 优先从数据库查询物流追踪信息
-$trackingData = \app\common\service\ExpressTrackingService::getDetailByTrackingNumber($num);
-
-if ($trackingData && !empty($trackingData['traces'])) {
- // 从数据库获取到数据,直接返回
- $payload = [
- 'carrier' => $trackingData['express_company'],
- 'carrier_label' => $trackingData['express_company_name'],
- 'kuaidi_com' => $trackingData['express_company'],
- 'traces' => array_map(function($trace) {
- return [
- 'time' => $trace['trace_time'],
- 'ftime' => $trace['trace_time'],
- 'context' => $trace['trace_context'],
- 'location' => $trace['location'],
- 'status' => $trace['status'],
- 'statusCode' => $trace['status_code'],
- ];
- }, $trackingData['traces']),
- 'state' => $trackingData['current_state'],
- 'state_text' => $trackingData['current_state_text'],
- 'source' => 'database',
- 'hint' => '',
- 'official_url' => '',
- 'last_query_time' => date('Y-m-d H:i:s', $trackingData['last_query_time']),
- 'query_count' => $trackingData['query_count'],
- ];
-} else {
- // 数据库没有数据,调用快递100 API
- $payload = ExpressTrackService::query($ec, $num, (string) ($row->recipient_phone ?? ''));
- $payload['source'] = 'api';
-}
-```
-
-### 3. 前端显示
-
-**文件**:`admin/src/views/consumer/prescription/order_list.vue`
-
-添加数据来源和更新时间显示:
-```vue
-
- 物流状态:{{ logisticsTracePayload.state_text }}
-
- ({{ logisticsTracePayload.carrier_label }})
-
-
- [{{ logisticsTracePayload.source === 'database' ? '数据库' : 'API实时' }}]
-
-
- 最后更新:{{ logisticsTracePayload.last_query_time }}
-
-
-```
-
-更新说明文字:
-```vue
-
- 优先从数据库查询历史轨迹(快速响应),数据库无记录时调用快递100实时查询。
- 支持顺丰、京东、极兔速递等快递公司。
- 顺丰查询建议填写正确收货手机后四位(本单收货手机已自动传入)。
-
-```
-
-## 测试结果
-
-### 数据库查询测试
-
-```bash
-$ php test_tracking_query.php
-
-========================================
-测试物流追踪查询(从数据库)
-========================================
-
-[1] 查询追踪主记录...
-✓ 找到追踪记录
- 快递公司: 京东快递(单号识别)
- 当前状态: 已签收
- 是否签收: 是
- 查询次数: 1
- 最后查询: 2026-04-07 17:14:16
-
-[2] 查询轨迹明细...
-✓ 找到 13 条轨迹记录
-
-最新3条轨迹:
- [2026-02-24 19:12:51] 您的快件已送达至【柜子】...
- [2026-02-24 15:51:58] 您的快件正在派送中...
- [2026-02-24 15:51:11] 您的快件已到达【郑州国企站】。
-
-========================================
-测试完成!
-========================================
-✓ 数据库查询正常
-✓ 数据格式兼容API
-✓ 可以直接返回给前端
-```
-
-### 性能对比
-
-| 查询方式 | 响应时间 | API消耗 | 数据完整性 |
-|---------|---------|---------|-----------|
-| 数据库查询 | < 50ms | 0次 | 完整历史 |
-| API查询 | > 1000ms | 1次 | 仅当前 |
-
-### 数据示例
-
-**数据库返回**:
-```json
-{
- "carrier": "auto",
- "carrier_label": "京东快递(单号识别)",
- "traces": [
- {
- "time": "2026-02-24 19:12:51",
- "context": "您的快件已送达至【柜子】..."
- },
- // ... 13条完整轨迹
- ],
- "state": "3",
- "state_text": "已签收",
- "source": "database",
- "last_query_time": "2026-04-07 17:14:16",
- "query_count": "1"
-}
-```
-
-## 工作流程
-
-### 新订单流程
-
-1. **订单创建/编辑**
- ```php
- ExpressTrackingService::createOrUpdate([
- 'order_id' => $orderId,
- 'tracking_number' => $trackingNumber,
- // ...
- ]);
- ```
- - 创建追踪记录
- - 立即查询一次API
- - 保存到数据库
-
-2. **定时任务自动更新**
- ```bash
- php think express:auto-update
- ```
- - 每10分钟执行
- - 只更新未签收订单
- - 更新轨迹到数据库
-
-3. **用户查询**
- - 点击"查询轨迹"
- - 优先从数据库读取
- - 秒级响应
-
-### 现有订单同步
-
-```bash
-# 同步现有订单快递单号
-php think express:sync
-
-# 会自动:
-# 1. 查询订单表中的快递单号
-# 2. 创建追踪记录
-# 3. 立即查询一次API
-# 4. 保存到数据库
-```
-
-## 优势分析
-
-### 1. 性能提升
-- 数据库查询:< 50ms
-- API查询:> 1000ms
-- 提升 20倍以上
-
-### 2. 成本节约
-- 每次用户查询不消耗API次数
-- 只有定时任务消耗API(每10分钟1次)
-- 假设100个订单,每天查询10次:
- - 优化前:100 × 10 = 1000次/天
- - 优化后:100 × 144 = 14400次/天(定时任务) + 0次(用户查询)
- - 实际:只有未签收订单才会定时更新,已签收订单不消耗
-
-### 3. 用户体验
-- 查询速度快(秒级响应)
-- 显示完整历史轨迹
-- 显示数据来源和更新时间
-- 透明化数据状态
-
-### 4. 数据完整性
-- 保存所有历史轨迹
-- 记录状态变更
-- 可追溯查询历史
-- 支持数据分析
-
-## 注意事项
-
-### 1. 数据同步
-- 新订单需要集成 `ExpressTrackingService::createOrUpdate()`
-- 现有订单需要执行 `php think express:sync`
-
-### 2. 定时任务
-- 必须配置定时任务(每10分钟)
-- 只更新未签收订单
-- 签收后自动停止更新
-
-### 3. 数据时效性
-- 数据库数据最多延迟10分钟
-- 如需实时数据,可添加"强制刷新"按钮
-- 已签收订单不会再更新
-
-### 4. 兼容性
-- 数据格式完全兼容原API
-- 前端无需修改查询逻辑
-- 只需添加显示字段
-
-## 相关文件
-
-### 核心文件
-- `server/app/common/service/ExpressTrackingService.php` - 服务层
-- `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php` - 逻辑层
-- `admin/src/views/consumer/prescription/order_list.vue` - 前端页面
-
-### 测试脚本
-- `server/test_tracking_query.php` - 数据库查询测试
-
-### 文档
-- `EXPRESS_TRACKING_SYSTEM.md` - 系统架构
-- `EXPRESS_TRACKING_SYNC_COMPLETE.md` - 同步完成报告
-- `EXPRESS_TRACKING_DATABASE_QUERY.md` - 本文档
-
-## 总结
-
-✅ 优先从数据库查询,提升20倍性能
-✅ 节省API调用次数,降低成本
-✅ 显示完整历史轨迹,提升用户体验
-✅ 数据格式兼容,无需修改前端逻辑
-✅ 添加数据来源和更新时间显示
-✅ 测试通过,13条轨迹记录正常显示
-
-**下一步**:
-1. 在订单创建/编辑时集成追踪服务
-2. 配置定时任务(每10分钟)
-3. 前端测试查询功能
-4. 可选:添加"强制刷新"按钮(调用API实时更新)
diff --git a/EXPRESS_TRACKING_FIXES.md b/EXPRESS_TRACKING_FIXES.md
deleted file mode 100644
index 57ecf39b..00000000
--- a/EXPRESS_TRACKING_FIXES.md
+++ /dev/null
@@ -1,365 +0,0 @@
-# 物流追踪系统 - 问题修复
-
-## 已修复的问题
-
-### 1. 命令未注册错误 ✅
-
-**问题:**
-```
-[InvalidArgumentException]
-There are no commands defined in the "express" namespace.
-```
-
-**原因:**
-命令未在 `server/config/console.php` 中注册。
-
-**解决方案:**
-已在 `server/config/console.php` 中添加命令注册:
-
-```php
-'commands' => [
- // ... 其他命令
- 'express:auto-update' => 'app\command\ExpressAutoUpdate',
-],
-```
-
-**验证:**
-```bash
-cd server
-php think express:auto-update
-```
-
----
-
-### 2. 已签收订单过滤优化 ✅
-
-**问题:**
-定时任务可能会处理已签收的快递,浪费查询次数。
-
-**解决方案:**
-优化了 `ExpressTrackingService::autoUpdateBatch()` 方法的查询条件:
-
-```php
-$list = ExpressTracking::where('auto_update', 1)
- ->where('next_update_time', '<=', $now)
- ->where('is_signed', 0) // 【新增】未签收
- ->whereNotIn('current_state', [ // 【新增】排除终态
- ExpressTracking::STATE_SIGNED, // 已签收
- ExpressTracking::STATE_RETURN_SIGNED, // 退签
- ExpressTracking::STATE_REJECTED, // 拒签
- ])
- ->whereNull('delete_time')
- ->limit($limit)
- ->select();
-```
-
-**过滤规则:**
-
-| 条件 | 说明 |
-|------|------|
-| `auto_update = 1` | 启用自动更新 |
-| `next_update_time <= now` | 到达更新时间 |
-| `is_signed = 0` | 未签收 |
-| `current_state NOT IN (3,4,14)` | 排除终态(已签收、退签、拒签) |
-| `delete_time IS NULL` | 未删除 |
-
-**会被处理的状态:**
-- 0: 在途
-- 1: 揽收
-- 2: 疑难
-- 5: 派件中
-- 6: 退回
-- 7: 转投
-- 8: 清关
-- 10: 待清关
-- 11: 清关中
-- 12: 已清关
-- 13: 清关异常
-
-**不会被处理的状态:**
-- 3: 已签收 ❌
-- 4: 退签 ❌
-- 14: 拒签 ❌
-
----
-
-## 自动停止更新机制
-
-### 触发条件
-
-当快递状态变为以下任一状态时,系统会自动停止更新:
-
-```php
-// 在 ExpressTrackingService::queryAndUpdate() 中
-if (ExpressTracking::isFinalState($tracking->current_state)) {
- $tracking->is_signed = 1;
- $tracking->sign_time = $now;
- $tracking->auto_update = 0; // 自动停止更新
-}
-```
-
-### 终态判断
-
-```php
-public static function isFinalState(string $state): bool
-{
- return in_array($state, [
- self::STATE_SIGNED, // 3: 已签收
- self::STATE_RETURN_SIGNED, // 4: 退签
- self::STATE_REJECTED, // 14: 拒签
- ], true);
-}
-```
-
----
-
-## 双重保护机制
-
-系统采用双重保护,确保已签收的快递不会被重复查询:
-
-### 第一层:查询时过滤
-
-在 `autoUpdateBatch()` 方法中,查询时就排除了终态订单:
-
-```php
-->where('is_signed', 0)
-->whereNotIn('current_state', ['3', '4', '14'])
-```
-
-### 第二层:更新时自动停止
-
-在 `queryAndUpdate()` 方法中,当检测到签收时自动停止:
-
-```php
-if (ExpressTracking::isFinalState($tracking->current_state)) {
- $tracking->auto_update = 0;
- $tracking->save();
-}
-```
-
----
-
-## 测试验证
-
-### 1. 测试命令注册
-
-```bash
-cd server
-php think list
-
-# 应该能看到:
-# express
-# express:auto-update 自动更新物流追踪信息
-```
-
-### 2. 测试过滤逻辑
-
-```bash
-cd server
-php test_express_auto_update.php
-
-# 查看输出,确认过滤规则正确
-```
-
-### 3. 测试实际执行
-
-```bash
-cd server
-php think express:auto-update
-
-# 应该看到:
-# 开始自动更新物流信息...
-# 更新完成!
-# 总数: X
-# 成功: X
-# 失败: 0
-# 耗时: X秒
-```
-
----
-
-## 性能优化
-
-### 查询优化
-
-通过添加过滤条件,减少了不必要的查询:
-
-**优化前:**
-- 查询所有 `auto_update = 1` 的记录
-- 包括已签收的快递
-- 浪费查询次数
-
-**优化后:**
-- 只查询未签收且未到达终态的记录
-- 自动排除已签收的快递
-- 节省查询次数和API费用
-
-### 索引建议
-
-确保以下字段有索引:
-
-```sql
--- 复合索引(最重要)
-CREATE INDEX idx_auto_update_query
-ON zyt_express_tracking(auto_update, next_update_time, is_signed, current_state);
-
--- 单字段索引
-CREATE INDEX idx_is_signed ON zyt_express_tracking(is_signed);
-CREATE INDEX idx_current_state ON zyt_express_tracking(current_state);
-```
-
----
-
-## 监控建议
-
-### 1. 监控自动更新执行情况
-
-```bash
-# 查看定时任务日志
-tail -f server/runtime/log/info.log | grep "Auto update"
-
-# 查看错误日志
-tail -f server/runtime/log/error.log | grep "express"
-```
-
-### 2. 统计查询情况
-
-```sql
--- 查看自动更新的记录数
-SELECT COUNT(*) as count
-FROM zyt_express_tracking
-WHERE auto_update = 1
-AND is_signed = 0
-AND current_state NOT IN ('3', '4', '14')
-AND delete_time IS NULL;
-
--- 查看已签收的记录数
-SELECT COUNT(*) as count
-FROM zyt_express_tracking
-WHERE is_signed = 1;
-
--- 查看各状态分布
-SELECT
- current_state,
- current_state_text,
- COUNT(*) as count,
- SUM(auto_update) as auto_update_count
-FROM zyt_express_tracking
-WHERE delete_time IS NULL
-GROUP BY current_state, current_state_text
-ORDER BY count DESC;
-```
-
-### 3. 查询日志分析
-
-```sql
--- 查看最近的查询记录
-SELECT
- tracking_number,
- query_type,
- is_success,
- error_message,
- FROM_UNIXTIME(query_time) as query_time
-FROM zyt_express_query_log
-ORDER BY query_time DESC
-LIMIT 20;
-
--- 统计自动查询成功率
-SELECT
- DATE(FROM_UNIXTIME(query_time)) as date,
- COUNT(*) as total,
- SUM(is_success) as success,
- ROUND(SUM(is_success) / COUNT(*) * 100, 2) as success_rate
-FROM zyt_express_query_log
-WHERE query_type = 'auto'
-GROUP BY date
-ORDER BY date DESC
-LIMIT 7;
-```
-
----
-
-## 常见问题
-
-### Q1: 命令执行后显示 "总数: 0"?
-
-**A:** 这是正常的,说明:
-1. 没有需要更新的快递记录
-2. 或所有快递都已签收
-3. 或还没到更新时间
-
-**检查:**
-```sql
-SELECT
- COUNT(*) as total,
- SUM(CASE WHEN auto_update = 1 THEN 1 ELSE 0 END) as auto_update_enabled,
- SUM(CASE WHEN is_signed = 1 THEN 1 ELSE 0 END) as signed,
- SUM(CASE WHEN next_update_time <= UNIX_TIMESTAMP() THEN 1 ELSE 0 END) as need_update
-FROM zyt_express_tracking
-WHERE delete_time IS NULL;
-```
-
-### Q2: 已签收的快递还在被查询?
-
-**A:** 检查以下几点:
-1. 确认代码已更新到最新版本
-2. 检查 `is_signed` 字段是否正确设置
-3. 检查 `auto_update` 字段是否为 0
-
-**修复:**
-```sql
--- 手动修复已签收但未停止更新的记录
-UPDATE zyt_express_tracking
-SET auto_update = 0
-WHERE current_state IN ('3', '4', '14')
-AND auto_update = 1;
-```
-
-### Q3: 如何手动停止某个快递的自动更新?
-
-**A:**
-```sql
-UPDATE zyt_express_tracking
-SET auto_update = 0
-WHERE tracking_number = 'JD0230761381812';
-```
-
-或通过代码:
-```php
-$tracking = ExpressTracking::where('tracking_number', 'JD0230761381812')->find();
-$tracking->auto_update = 0;
-$tracking->save();
-```
-
----
-
-## 总结
-
-### 已修复
-
-1. ✅ 命令注册问题
-2. ✅ 已签收订单过滤
-3. ✅ 双重保护机制
-4. ✅ 性能优化
-
-### 优势
-
-1. **节省费用** - 不查询已签收的快递
-2. **提高效率** - 只处理需要更新的记录
-3. **自动停止** - 签收后自动停止更新
-4. **双重保护** - 查询和更新两层过滤
-
-### 下一步
-
-1. ✅ 执行 `php think express:auto-update` 测试
-2. ✅ 配置crontab定时任务
-3. ✅ 监控执行情况
-4. ✅ 查看查询日志
-
----
-
-## 相关文档
-
-- `EXPRESS_TRACKING_SYSTEM.md` - 完整系统文档
-- `EXPRESS_TRACKING_QUICKSTART.md` - 快速开始指南
-- `KUAIDI100_ACCOUNT_ISSUE.md` - 快递100账号问题
diff --git a/EXPRESS_TRACKING_QUICKSTART.md b/EXPRESS_TRACKING_QUICKSTART.md
deleted file mode 100644
index a6a8f14d..00000000
--- a/EXPRESS_TRACKING_QUICKSTART.md
+++ /dev/null
@@ -1,335 +0,0 @@
-# 物流追踪系统 - 快速开始
-
-## 5分钟快速部署
-
-### 1. 安装数据库表
-
-**Linux/Mac:**
-```bash
-chmod +x install_express_tracking.sh
-./install_express_tracking.sh
-```
-
-**Windows:**
-```cmd
-install_express_tracking.bat
-```
-
-**或手动执行SQL:**
-```bash
-mysql -u root -p your_database < server/database/migrations/create_express_tracking_tables.sql
-```
-
-### 2. 注册命令
-
-编辑 `server/config/console.php`,在 `commands` 数组中添加:
-
-```php
-'commands' => [
- // ... 其他命令
- 'express:auto-update' => \app\command\ExpressAutoUpdate::class,
-],
-```
-
-### 3. 测试命令
-
-```bash
-cd server
-php think express:auto-update
-```
-
-应该看到:
-```
-开始自动更新物流信息...
-更新完成!
-总数: 0
-成功: 0
-失败: 0
-耗时: 0.05秒
-```
-
-### 4. 配置定时任务
-
-**Linux/Mac (crontab):**
-```bash
-crontab -e
-
-# 添加以下行(每10分钟执行)
-*/10 * * * * cd /path/to/your/server && php think express:auto-update >> /dev/null 2>&1
-```
-
-**Windows (计划任务):**
-```cmd
-schtasks /create /tn "ExpressAutoUpdate" /tr "php D:\path\to\server\think express:auto-update" /sc minute /mo 10
-```
-
----
-
-## 使用示例
-
-### 示例1:订单发货时创建追踪
-
-```php
-use app\common\service\ExpressTrackingService;
-
-// 在订单发货时调用
-$tracking = ExpressTrackingService::createOrUpdate([
- 'order_id' => $order->id,
- 'order_type' => 'prescription',
- 'tracking_number' => 'JD0230761381812',
- 'express_company' => 'jd',
- 'recipient_phone' => $order->recipient_phone,
- 'recipient_name' => $order->recipient_name,
- 'recipient_address' => $order->shipping_address,
-]);
-
-// 系统会:
-// 1. 创建追踪记录
-// 2. 立即查询物流信息
-// 3. 存储到数据库
-// 4. 开始自动更新
-```
-
-### 示例2:查询物流详情
-
-```php
-// 获取完整物流信息
-$detail = ExpressTrackingService::getDetail($trackingId);
-
-// 返回数据包含:
-// - 基本信息(单号、快递公司、收件人等)
-// - 当前状态(在途、派件、签收等)
-// - 所有轨迹记录(时间、内容、位置)
-// - 状态变更历史
-```
-
-### 示例3:手动更新
-
-```php
-// 手动触发更新
-$result = ExpressTrackingService::queryAndUpdate($trackingId, false);
-
-// 返回最新的物流信息
-```
-
----
-
-## 集成到现有系统
-
-### 修改订单创建逻辑
-
-在 `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php` 的 `create()` 方法中添加:
-
-```php
-public static function create(array $params, int $adminId, array $adminInfo)
-{
- // ... 原有代码
-
- $order->save();
-
- // 【新增】创建物流追踪
- if (!empty($params['tracking_number'])) {
- \app\common\service\ExpressTrackingService::createOrUpdate([
- 'order_id' => $order->id,
- 'order_type' => 'prescription',
- 'tracking_number' => $params['tracking_number'],
- 'express_company' => $params['express_company'] ?? 'auto',
- 'recipient_phone' => $order->recipient_phone,
- 'recipient_name' => $order->recipient_name,
- 'recipient_address' => $order->shipping_address,
- ]);
- }
-
- return $out;
-}
-```
-
-### 修改订单编辑逻辑
-
-在 `edit()` 方法中添加:
-
-```php
-public static function edit(array $params, int $adminId, array $adminInfo)
-{
- // ... 原有代码
-
- $order->save();
-
- // 【新增】更新物流追踪
- if (array_key_exists('tracking_number', $params) && !empty($params['tracking_number'])) {
- \app\common\service\ExpressTrackingService::createOrUpdate([
- 'order_id' => $order->id,
- 'order_type' => 'prescription',
- 'tracking_number' => $params['tracking_number'],
- 'express_company' => $order->express_company,
- 'recipient_phone' => $order->recipient_phone,
- 'recipient_name' => $order->recipient_name,
- 'recipient_address' => $order->shipping_address,
- ]);
- }
-
- return $out;
-}
-```
-
-### 修改物流查询接口
-
-在 `logisticsTrace()` 方法开头添加:
-
-```php
-public static function logisticsTrace(int $id, string $expressCompanyOverride, int $adminId, array $adminInfo): ?array
-{
- // ... 权限检查等原有代码
-
- // 【新增】优先从数据库获取
- $tracking = \app\common\model\ExpressTracking::where('order_id', $id)
- ->where('order_type', 'prescription')
- ->whereNull('delete_time')
- ->find();
-
- if ($tracking) {
- // 如果距离上次查询超过5分钟,重新查询
- if (time() - $tracking->last_query_time > 300) {
- \app\common\service\ExpressTrackingService::queryAndUpdate($tracking->id, false);
- $tracking->refresh();
- }
-
- // 返回数据库中的数据
- $traces = $tracking->traces->map(function($trace) {
- return [
- 'time' => $trace->trace_time,
- 'context' => $trace->trace_context,
- 'status' => $trace->status,
- 'location' => $trace->location,
- ];
- })->toArray();
-
- $payload = [
- 'carrier' => $tracking->express_company,
- 'carrier_label' => $tracking->express_company_name,
- 'traces' => $traces,
- 'state' => $tracking->current_state,
- 'state_text' => $tracking->current_state_text,
- 'source' => $tracking->data_source,
- 'official_urls' => ExpressTrackService::officialUrls($tracking->tracking_number),
- 'tracking_number' => $tracking->tracking_number,
- 'is_signed' => $tracking->is_signed,
- 'estimated_arrival_time' => $tracking->estimated_arrival_time,
- ];
-
- return $payload;
- }
-
- // 如果数据库中没有,走原有逻辑
- // ... 原有查询代码
-}
-```
-
----
-
-## 验证安装
-
-### 1. 检查数据库表
-
-```sql
-SHOW TABLES LIKE 'zyt_express%';
-
--- 应该看到4个表:
--- zyt_express_tracking
--- zyt_express_trace
--- zyt_express_state_log
--- zyt_express_query_log
-```
-
-### 2. 测试创建追踪
-
-```php
-$tracking = ExpressTrackingService::createOrUpdate([
- 'order_id' => 1,
- 'order_type' => 'test',
- 'tracking_number' => 'JD0230761381812',
- 'express_company' => 'jd',
- 'recipient_phone' => '13800138000',
- 'recipient_name' => '测试',
- 'recipient_address' => '测试地址',
-]);
-
-var_dump($tracking->id); // 应该返回ID
-```
-
-### 3. 检查定时任务
-
-```bash
-# 查看crontab
-crontab -l | grep express
-
-# 手动执行一次
-cd server && php think express:auto-update
-
-# 查看日志
-tail -f runtime/log/info.log
-```
-
----
-
-## 常见问题
-
-### Q: 定时任务不执行?
-
-**A:** 检查crontab配置和PHP路径
-```bash
-# 查看crontab
-crontab -l
-
-# 检查PHP路径
-which php
-
-# 查看cron日志
-tail -f /var/log/cron
-```
-
-### Q: 快递100返回"找不到对应公司"?
-
-**A:** 账号未充值,请先充值快递100账户
-- 登录:https://www.kuaidi100.com/
-- 充值后重试
-
-### Q: 如何停止某个快递的自动更新?
-
-**A:**
-```php
-$tracking = ExpressTracking::find($id);
-$tracking->auto_update = 0;
-$tracking->save();
-```
-
-### Q: 如何修改更新频率?
-
-**A:**
-```php
-// 修改单个追踪记录的更新间隔
-$tracking->update_interval = 3600; // 1小时
-$tracking->save();
-
-// 或修改crontab执行频率
-crontab -e
-# 改为每30分钟: */30 * * * *
-```
-
----
-
-## 下一步
-
-1. ✅ 确认快递100已充值
-2. ✅ 集成到订单系统
-3. ✅ 配置定时任务
-4. ✅ 测试物流查询
-5. 📖 阅读完整文档:`EXPRESS_TRACKING_SYSTEM.md`
-
----
-
-## 技术支持
-
-- 完整文档:`EXPRESS_TRACKING_SYSTEM.md`
-- 快递100文档:https://api.kuaidi100.com/
-- 问题排查:`KUAIDI100_TROUBLESHOOTING.md`
diff --git a/EXPRESS_TRACKING_SYNC_COMPLETE.md b/EXPRESS_TRACKING_SYNC_COMPLETE.md
deleted file mode 100644
index f72c70e1..00000000
--- a/EXPRESS_TRACKING_SYNC_COMPLETE.md
+++ /dev/null
@@ -1,244 +0,0 @@
-# 物流追踪系统同步完成报告
-
-## 问题分析
-
-### 用户疑问
-"定时任务找不到快递单号,明明订单里有快递单号"
-
-### 根本原因
-定时任务查询的是 `zyt_express_tracking` 表(物流追踪表),而不是 `zyt_tcm_prescription_order` 表(订单表)。
-
-这两个表是分离的:
-- **订单表**:存储业务订单信息,包括快递单号
-- **物流追踪表**:专门存储物流追踪信息和轨迹历史
-
-需要将订单表中的快递单号同步到物流追踪表,定时任务才能找到并更新。
-
-## 解决方案
-
-### 1. 创建同步命令
-创建了 `SyncTrackingNumbers` 命令,用于将订单表中的快递单号同步到物流追踪表。
-
-**文件**:`server/app/command/SyncTrackingNumbers.php`
-
-**使用方法**:
-```bash
-php think express:sync
-```
-
-### 2. 注册命令
-在 `server/config/console.php` 中注册了两个命令:
-```php
-'commands' => [
- 'express:auto-update' => 'app\\command\\ExpressAutoUpdate', // 自动更新
- 'express:sync' => 'app\\command\\SyncTrackingNumbers', // 同步快递单号
-],
-```
-
-### 3. 执行同步
-```bash
-$ php think express:sync
-开始同步现有订单快递单号...
-找到 1 个有快递单号的订单
-跳过: JD0230761381812 (已存在)
-
-========================================
-同步完成!
-========================================
-总数: 1
-成功: 0
-跳过: 1
-失败: 0
-耗时: 0.28秒
-```
-
-## 当前状态
-
-### 物流追踪记录状态
-```
-快递单号: JD0230761381812
-快递公司: auto
-当前状态: 3 - 已签收
-是否签收: 是
-自动更新: 禁用
-下次更新时间: 1970-01-01 08:00:00 (0)
-当前时间: 2026-04-07 17:12:50
-是否应该更新: 否
-不更新原因: 自动更新未启用 已签收 状态为终态
-```
-
-### 为什么定时任务总数为0?
-
-**答案**:这是正常的!
-
-这个快递单号已经是"已签收"状态(state=3),系统自动:
-1. 停止了自动更新(`auto_update = 0`)
-2. 标记为已签收(`is_signed = 1`)
-3. 状态为终态(state = 3)
-
-**定时任务的过滤条件**:
-```php
-$list = ExpressTracking::where('auto_update', 1) // ✗ 当前为0
- ->where('next_update_time', '<=', $now) // ✗ 当前为0
- ->where('is_signed', 0) // ✗ 当前为1
- ->whereNotIn('current_state', [3, 4, 5]) // ✗ 当前为3
- ->whereNull('delete_time')
- ->limit($limit)
- ->select();
-```
-
-所有条件都不满足,所以不会被查询到。这是设计的预期行为,避免浪费查询次数。
-
-## 工作流程
-
-### 新订单的物流追踪流程
-
-1. **订单创建/编辑时**
- ```php
- ExpressTrackingService::createOrUpdate([
- 'order_id' => $orderId,
- 'order_type' => 'prescription',
- 'tracking_number' => $trackingNumber,
- 'express_company' => $expressCompany,
- 'recipient_phone' => $phone,
- 'recipient_name' => $name,
- 'recipient_address' => $address,
- ]);
- ```
- - 创建物流追踪记录
- - 立即查询一次物流信息
- - 设置 `auto_update = 1`(启用自动更新)
- - 设置 `next_update_time = now`(立即更新)
-
-2. **定时任务自动更新**
- ```bash
- php think express:auto-update
- ```
- - 每10分钟执行一次
- - 只查询未签收的订单
- - 更新物流信息和轨迹
- - 检测状态变更
-
-3. **签收后自动停止**
- - 检测到签收状态(state=3)
- - 设置 `is_signed = 1`
- - 设置 `auto_update = 0`(停止自动更新)
- - 记录签收时间
-
-### 现有订单的同步
-
-对于已有的订单(如当前的 JD0230761381812),需要手动同步:
-
-```bash
-# 同步现有订单快递单号
-php think express:sync
-
-# 同步后会立即查询一次物流信息
-# 如果已签收,会自动停止更新
-```
-
-## 测试验证
-
-### 1. 检查物流追踪记录
-```bash
-php check_tracking_status.php
-```
-
-### 2. 测试同步命令
-```bash
-php think express:sync
-```
-
-### 3. 测试自动更新
-```bash
-php think express:auto-update
-```
-
-### 4. 查看日志
-```bash
-tail -f runtime/log/202X-XX-XX.log
-```
-
-## 下一步操作
-
-### 1. 集成到订单系统
-
-在订单创建/编辑的控制器或逻辑层中添加:
-
-**文件**:`server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
-
-```php
-use app\common\service\ExpressTrackingService;
-
-// 在保存订单后
-if (!empty($params['tracking_number'])) {
- ExpressTrackingService::createOrUpdate([
- 'order_id' => $orderId,
- 'order_type' => 'prescription',
- 'tracking_number' => $params['tracking_number'],
- 'express_company' => $params['express_company'] ?? 'auto',
- 'recipient_phone' => $params['recipient_phone'],
- 'recipient_name' => $params['recipient_name'],
- 'recipient_address' => $params['shipping_address'],
- ]);
-}
-```
-
-### 2. 配置定时任务
-
-#### Windows(任务计划程序)
-```batch
-schtasks /create /tn "ExpressAutoUpdate" /tr "php D:\web\zyt\server\think express:auto-update" /sc minute /mo 10
-```
-
-#### Linux(crontab)
-```bash
-crontab -e
-# 添加以下行
-0,10,20,30,40,50 * * * * cd /path/to/server && php think express:auto-update >> /dev/null 2>&1
-```
-
-### 3. 测试新订单
-
-1. 创建一个新订单,填写快递单号
-2. 检查 `zyt_express_tracking` 表是否自动创建记录
-3. 等待10分钟,查看是否自动更新
-4. 查看 `zyt_express_trace` 表是否有轨迹记录
-
-## 相关文件
-
-### 核心文件
-- `server/app/common/service/ExpressTrackingService.php` - 物流追踪服务
-- `server/app/command/ExpressAutoUpdate.php` - 自动更新命令
-- `server/app/command/SyncTrackingNumbers.php` - 同步命令
-- `server/config/console.php` - 命令注册
-
-### 数据库
-- `server/database/migrations/create_express_tracking_tables.sql` - 创建表
-- `server/database/migrations/sync_existing_tracking_numbers.sql` - 同步SQL
-
-### 工具脚本
-- `server/install_tracking_tables.php` - 安装表
-- `server/check_tracking_status.php` - 检查状态
-
-### 文档
-- `EXPRESS_TRACKING_SYSTEM.md` - 系统架构
-- `EXPRESS_TRACKING_QUICKSTART.md` - 快速开始
-- `EXPRESS_TRACKING_FIXES.md` - 修复记录
-- `EXPRESS_TRACKING_COMMAND_FIX.md` - 命令修复
-- `EXPRESS_TRACKING_SYNC_COMPLETE.md` - 本文档
-
-## 总结
-
-✅ 问题已解决:订单表和物流追踪表已同步
-✅ 定时任务正常工作:正确过滤已签收订单
-✅ 自动停止机制:签收后自动停止更新,节省查询次数
-✅ 同步命令可用:`php think express:sync`
-✅ 自动更新命令可用:`php think express:auto-update`
-
-**当前状态**:系统已就绪,等待新订单测试。现有订单(JD0230761381812)已签收,不会再更新。
-
-**建议**:
-1. 在订单创建/编辑时集成 `ExpressTrackingService::createOrUpdate()`
-2. 配置定时任务(每10分钟执行一次)
-3. 创建一个新的未签收订单进行完整测试
diff --git a/EXPRESS_TRACKING_SYSTEM.md b/EXPRESS_TRACKING_SYSTEM.md
deleted file mode 100644
index 8305465d..00000000
--- a/EXPRESS_TRACKING_SYSTEM.md
+++ /dev/null
@@ -1,540 +0,0 @@
-# 物流追踪系统完整方案
-
-## 系统架构
-
-### 核心功能
-
-1. **物流信息存储** - 将快递100查询结果存储到数据库
-2. **自动更新机制** - 定时自动查询更新物流状态
-3. **状态变更检测** - 监控物流状态变化
-4. **异常通知** - 签收、异常时自动通知
-5. **查询日志** - 记录所有查询请求和响应
-
-### 数据库表结构
-
-| 表名 | 说明 | 用途 |
-|------|------|------|
-| zyt_express_tracking | 物流追踪主表 | 存储快递单基本信息和当前状态 |
-| zyt_express_trace | 物流轨迹明细表 | 存储每条物流轨迹记录 |
-| zyt_express_state_log | 状态变更记录表 | 记录状态变化历史 |
-| zyt_express_query_log | 查询日志表 | 记录所有查询请求 |
-
----
-
-## 安装步骤
-
-### 1. 创建数据库表
-
-```bash
-# 执行SQL脚本
-mysql -u root -p your_database < server/database/migrations/create_express_tracking_tables.sql
-```
-
-或在数据库管理工具中执行 `create_express_tracking_tables.sql` 文件。
-
-### 2. 注册定时任务命令
-
-在 `server/config/console.php` 中添加:
-
-```php
-'commands' => [
- // ... 其他命令
- 'express:auto-update' => \app\command\ExpressAutoUpdate::class,
-],
-```
-
-### 3. 配置crontab定时任务
-
-```bash
-# 编辑crontab
-crontab -e
-
-# 添加以下行(每10分钟执行一次)
-*/10 * * * * cd /path/to/your/server && php think express:auto-update >> /dev/null 2>&1
-```
-
-### 4. 测试定时任务
-
-```bash
-cd server
-php think express:auto-update
-```
-
-应该看到输出:
-```
-开始自动更新物流信息...
-更新完成!
-总数: 0
-成功: 0
-失败: 0
-耗时: 0.05秒
-```
-
----
-
-## 使用方法
-
-### 1. 创建物流追踪
-
-当订单发货时,创建物流追踪记录:
-
-```php
-use app\common\service\ExpressTrackingService;
-
-// 创建追踪记录
-$tracking = ExpressTrackingService::createOrUpdate([
- 'order_id' => 123, // 订单ID
- 'order_type' => 'prescription', // 订单类型
- 'tracking_number' => 'JD0230761381812', // 快递单号
- 'express_company' => 'jd', // 快递公司
- 'recipient_phone' => '13800138000', // 收件人手机
- 'recipient_name' => '张三', // 收件人姓名
- 'recipient_address' => '北京市朝阳区xxx', // 收件地址
-]);
-
-// 系统会立即查询一次物流信息并存储
-```
-
-### 2. 手动更新物流信息
-
-```php
-// 更新指定追踪记录
-$result = ExpressTrackingService::queryAndUpdate($trackingId, false);
-```
-
-### 3. 获取物流详情
-
-```php
-// 获取完整的物流信息(包含轨迹)
-$detail = ExpressTrackingService::getDetail($trackingId);
-
-// 返回数据包含:
-// - 基本信息
-// - 当前状态
-// - 所有轨迹记录
-// - 状态变更历史
-```
-
-### 4. 自动更新配置
-
-```php
-// 修改更新间隔(秒)
-$tracking->update_interval = 1800; // 30分钟
-$tracking->save();
-
-// 停止自动更新
-$tracking->auto_update = 0;
-$tracking->save();
-
-// 启用自动更新
-$tracking->auto_update = 1;
-$tracking->next_update_time = time(); // 立即更新
-$tracking->save();
-```
-
----
-
-## 集成到现有订单系统
-
-### 修改订单创建/编辑逻辑
-
-在 `PrescriptionOrderLogic.php` 中:
-
-```php
-public static function create(array $params, int $adminId, array $adminInfo)
-{
- // ... 原有创建逻辑
-
- $order->save();
-
- // 创建物流追踪
- if (!empty($params['tracking_number'])) {
- ExpressTrackingService::createOrUpdate([
- 'order_id' => $order->id,
- 'order_type' => 'prescription',
- 'tracking_number' => $params['tracking_number'],
- 'express_company' => $params['express_company'] ?? 'auto',
- 'recipient_phone' => $order->recipient_phone,
- 'recipient_name' => $order->recipient_name,
- 'recipient_address' => $order->shipping_address,
- ]);
- }
-
- return $out;
-}
-
-public static function edit(array $params, int $adminId, array $adminInfo)
-{
- // ... 原有编辑逻辑
-
- // 更新物流追踪
- if (array_key_exists('tracking_number', $params)) {
- if (!empty($params['tracking_number'])) {
- ExpressTrackingService::createOrUpdate([
- 'order_id' => $order->id,
- 'order_type' => 'prescription',
- 'tracking_number' => $params['tracking_number'],
- 'express_company' => $order->express_company,
- 'recipient_phone' => $order->recipient_phone,
- 'recipient_name' => $order->recipient_name,
- 'recipient_address' => $order->shipping_address,
- ]);
- }
- }
-
- return $out;
-}
-```
-
-### 修改物流查询接口
-
-在 `PrescriptionOrderLogic.php` 中:
-
-```php
-public static function logisticsTrace(int $id, string $expressCompanyOverride, int $adminId, array $adminInfo): ?array
-{
- // ... 原有逻辑
-
- // 先从数据库获取
- $tracking = ExpressTracking::where('order_id', $id)
- ->where('order_type', 'prescription')
- ->whereNull('delete_time')
- ->find();
-
- if ($tracking) {
- // 如果距离上次查询超过5分钟,重新查询
- if (time() - $tracking->last_query_time > 300) {
- ExpressTrackingService::queryAndUpdate($tracking->id, false);
- $tracking->refresh();
- }
-
- // 返回数据库中的数据
- $payload = [
- 'carrier' => $tracking->express_company,
- 'carrier_label' => $tracking->express_company_name,
- 'kuaidi_com' => $tracking->express_company,
- 'traces' => $tracking->traces->map(function($trace) {
- return [
- 'time' => $trace->trace_time,
- 'context' => $trace->trace_context,
- 'status' => $trace->status,
- 'location' => $trace->location,
- ];
- })->toArray(),
- 'state' => $tracking->current_state,
- 'state_text' => $tracking->current_state_text,
- 'source' => $tracking->data_source,
- 'hint' => '',
- 'official_url' => ExpressTrackService::officialUrls($tracking->tracking_number)[$tracking->express_company] ?? '',
- 'official_urls' => ExpressTrackService::officialUrls($tracking->tracking_number),
- 'tracking_number' => $tracking->tracking_number,
- 'express_company_used' => $tracking->express_company,
- 'is_signed' => $tracking->is_signed,
- 'sign_time' => $tracking->sign_time,
- 'estimated_arrival_time' => $tracking->estimated_arrival_time,
- ];
-
- return $payload;
- }
-
- // 如果数据库中没有,走原有逻辑
- // ... 原有查询逻辑
-}
-```
-
----
-
-## 自动更新机制
-
-### 更新策略
-
-1. **创建时立即查询** - 创建追踪记录时立即查询一次
-2. **定时自动更新** - 每10分钟检查一次需要更新的记录
-3. **智能更新间隔** - 默认30分钟更新一次
-4. **终态停止更新** - 签收、退签、拒签后停止自动更新
-
-### 更新间隔建议
-
-| 物流状态 | 更新间隔 | 说明 |
-|---------|---------|------|
-| 已下单/待揽收 | 30分钟 | 等待揽收 |
-| 已揽收/在途 | 30分钟 | 运输中 |
-| 派件中 | 15分钟 | 即将签收,加快更新 |
-| 已签收 | 停止 | 终态,无需更新 |
-| 疑难/异常 | 60分钟 | 降低频率 |
-
-### 定时任务配置
-
-```bash
-# 每10分钟执行一次(推荐)
-*/10 * * * * cd /path/to/server && php think express:auto-update
-
-# 每5分钟执行一次(高频)
-*/5 * * * * cd /path/to/server && php think express:auto-update
-
-# 每30分钟执行一次(低频)
-*/30 * * * * cd /path/to/server && php think express:auto-update
-```
-
----
-
-## 状态变更通知
-
-### 通知触发条件
-
-1. **签收通知** - 快递签收时通知
-2. **异常通知** - 出现疑难、拒签等异常时通知
-3. **状态变更** - 每次状态变化都会记录
-
-### 通知渠道(可扩展)
-
-#### 1. 企业微信通知
-
-```php
-// 在 ExpressTrackingService::sendNotification() 中实现
-use app\common\service\WechatWorkService;
-
-WechatWorkService::sendMessage([
- 'touser' => $userId,
- 'msgtype' => 'text',
- 'text' => [
- 'content' => "【物流通知】\n" .
- "快递单号:{$tracking->tracking_number}\n" .
- "状态变更:{$log->old_state_text} → {$log->new_state_text}\n" .
- "最新动态:{$log->change_reason}"
- ]
-]);
-```
-
-#### 2. 短信通知
-
-```php
-// 对接短信服务商
-SmsService::send($tracking->recipient_phone, [
- 'template' => 'express_status_change',
- 'params' => [
- 'tracking_number' => $tracking->tracking_number,
- 'status' => $log->new_state_text,
- ]
-]);
-```
-
-#### 3. 系统内通知
-
-```php
-// 创建系统通知记录
-NoticeService::create([
- 'user_id' => $userId,
- 'title' => '物流状态更新',
- 'content' => "您的快递 {$tracking->tracking_number} 已{$log->new_state_text}",
-]);
-```
-
----
-
-## 数据统计和分析
-
-### 查询统计
-
-```sql
--- 查询成功率
-SELECT
- DATE(FROM_UNIXTIME(query_time)) as date,
- COUNT(*) as total,
- SUM(is_success) as success,
- ROUND(SUM(is_success) / COUNT(*) * 100, 2) as success_rate
-FROM zyt_express_query_log
-GROUP BY date
-ORDER BY date DESC;
-
--- 平均响应时间
-SELECT
- query_source,
- AVG(response_time) as avg_response_time,
- MAX(response_time) as max_response_time
-FROM zyt_express_query_log
-WHERE is_success = 1
-GROUP BY query_source;
-```
-
-### 物流状态分布
-
-```sql
--- 当前物流状态分布
-SELECT
- current_state,
- current_state_text,
- COUNT(*) as count
-FROM zyt_express_tracking
-WHERE delete_time IS NULL
-GROUP BY current_state, current_state_text
-ORDER BY count DESC;
-
--- 签收率统计
-SELECT
- DATE(FROM_UNIXTIME(sign_time)) as date,
- COUNT(*) as signed_count
-FROM zyt_express_tracking
-WHERE is_signed = 1
-GROUP BY date
-ORDER BY date DESC;
-```
-
-### 异常统计
-
-```sql
--- 异常快递统计
-SELECT
- tracking_number,
- express_company_name,
- current_state_text,
- latest_trace_context,
- FROM_UNIXTIME(update_time) as update_time
-FROM zyt_express_tracking
-WHERE current_state IN ('2', '13', '14') -- 疑难、清关异常、拒签
-AND delete_time IS NULL
-ORDER BY update_time DESC;
-```
-
----
-
-## 性能优化
-
-### 1. 批量更新优化
-
-```php
-// 每次处理50条记录
-ExpressTrackingService::autoUpdateBatch(50);
-
-// 根据服务器性能调整
-// - 性能好:100条
-// - 性能一般:50条
-// - 性能差:20条
-```
-
-### 2. 查询频率控制
-
-```php
-// 避免频繁查询同一单号
-if (time() - $tracking->last_query_time < 300) {
- // 5分钟内不重复查询
- return $cachedResult;
-}
-```
-
-### 3. 索引优化
-
-数据库表已创建必要索引:
-- `idx_order_id` - 订单ID索引
-- `idx_auto_update` - 自动更新索引
-- `idx_tracking_number` - 快递单号索引
-
-### 4. 数据清理
-
-```sql
--- 清理90天前的查询日志
-DELETE FROM zyt_express_query_log
-WHERE create_time < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 90 DAY));
-
--- 清理已签收超过30天的轨迹明细
-DELETE t FROM zyt_express_trace t
-INNER JOIN zyt_express_tracking tr ON t.tracking_id = tr.id
-WHERE tr.is_signed = 1
-AND tr.sign_time < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 30 DAY));
-```
-
----
-
-## 监控和告警
-
-### 1. 监控指标
-
-- 自动更新成功率
-- 平均响应时间
-- 异常快递数量
-- 查询失败率
-
-### 2. 告警规则
-
-```php
-// 在定时任务中添加告警逻辑
-$result = ExpressTrackingService::autoUpdateBatch(50);
-
-if ($result['failed'] > $result['success'] * 0.5) {
- // 失败率超过50%,发送告警
- AlertService::send('物流自动更新失败率过高', $result);
-}
-```
-
----
-
-## 常见问题
-
-### Q1: 定时任务不执行?
-
-**检查步骤:**
-1. 确认crontab已配置:`crontab -l`
-2. 检查PHP路径:`which php`
-3. 检查项目路径是否正确
-4. 查看cron日志:`tail -f /var/log/cron`
-
-### Q2: 更新频率太高,快递100余额消耗快?
-
-**解决方案:**
-1. 增加更新间隔:`update_interval = 3600`(1小时)
-2. 减少批量处理数量:`autoUpdateBatch(20)`
-3. 签收后立即停止更新
-4. 使用智能更新策略
-
-### Q3: 如何查看某个快递的完整历史?
-
-```php
-$detail = ExpressTrackingService::getDetail($trackingId);
-
-// 包含:
-// - traces: 所有轨迹记录
-// - state_logs: 状态变更历史
-```
-
-### Q4: 如何手动触发更新?
-
-```php
-// 方法1:通过服务类
-ExpressTrackingService::queryAndUpdate($trackingId, false);
-
-// 方法2:通过命令行
-php think express:auto-update
-```
-
----
-
-## 总结
-
-### 优势
-
-1. **数据持久化** - 所有物流信息存储在数据库,随时查询
-2. **自动更新** - 无需手动查询,系统自动追踪
-3. **状态监控** - 实时监控物流状态变化
-4. **异常通知** - 及时发现和处理异常
-5. **数据分析** - 支持物流数据统计分析
-
-### 成本
-
-1. **快递100费用** - 按查询次数计费
-2. **服务器资源** - 定时任务占用少量CPU和内存
-3. **数据库存储** - 需要额外的存储空间
-
-### 建议
-
-1. **充值快递100** - 确保有足够余额
-2. **合理设置更新间隔** - 平衡实时性和成本
-3. **定期清理数据** - 避免数据库膨胀
-4. **监控运行状态** - 及时发现问题
-
----
-
-## 相关文档
-
-- `LOGISTICS_INTEGRATION_GUIDE.md` - 物流集成指南
-- `KUAIDI100_ACCOUNT_ISSUE.md` - 快递100账号问题
-- `JTEXPRESS_SUPPORT_ADDED.md` - 极兔速递支持
diff --git a/FINAL_IMPLEMENTATION_SUMMARY.md b/FINAL_IMPLEMENTATION_SUMMARY.md
deleted file mode 100644
index a49474cf..00000000
--- a/FINAL_IMPLEMENTATION_SUMMARY.md
+++ /dev/null
@@ -1,321 +0,0 @@
-# 首次登录强制修改密码 - 最终实现总结
-
-## 功能概述
-
-实现了一个安全的首次登录强制修改密码功能,当管理员的 `is_paw` 字段为 0 时,登录后必须修改密码才能使用系统。
-
-## 核心修改
-
-### 后端修改(3 个文件)
-
-#### 1. `server/app/adminapi/logic/LoginLogic.php`
-
-**修改内容:**
-- 登录接口返回 `is_paw` 字段
-- 添加 `changeFirstPassword()` 方法处理密码修改
-
-**关键代码:**
-```php
-// 登录时返回 is_paw
-return [
- 'name' => $adminInfo['name'],
- 'avatar' => $avatar,
- 'role_name' => $adminInfo['role_name'],
- 'token' => $adminInfo['token'],
- 'is_paw' => $admin->is_paw ?? 1,
-];
-
-// 修改密码方法
-public function changeFirstPassword($token, $password) {
- // 验证 token
- // 更新密码和 is_paw 字段
- // 使 token 过期
-}
-```
-
-#### 2. `server/app/adminapi/controller/LoginController.php`
-
-**修改内容:**
-- 添加 `changeFirstPassword` 接口
-- 将该接口添加到 `$notNeedLogin` 白名单
-
-**关键代码:**
-```php
-public array $notNeedLogin = ['account', 'workWechatConfig', 'workWechatLogin', 'checkDbColumn', 'changeFirstPassword'];
-
-public function changeFirstPassword() {
- // 验证参数
- // 调用 LoginLogic::changeFirstPassword()
-}
-```
-
-#### 3. `server/app/adminapi/logic/auth/AdminLogic.php`
-
-**修改内容:**
-- 在 `detail()` 方法的字段列表中添加 `is_paw`
-
-**关键代码:**
-```php
-$admin = Admin::field([
- 'id', 'account', 'name', 'disable', 'root',
- 'multipoint_login', 'avatar', 'is_paw', // 添加此字段
- // ... 其他字段
-])->findOrEmpty($params['id'])->toArray();
-```
-
-### 前端修改(5 个文件)
-
-#### 1. `admin/src/stores/modules/user.ts`
-
-**修改内容:**
-- 添加 `isPaw` 状态
-- 登录时保存 `isPaw`
-- 获取用户信息时更新 `isPaw`(解决刷新问题)
-
-**关键代码:**
-```typescript
-export interface UserState {
- isPaw: number // 0=需要修改密码,1=正常
-}
-
-login(playload: any) {
- this.isPaw = data.is_paw ?? 1
-}
-
-getUserInfo() {
- this.isPaw = data.user.is_paw ?? 1 // 关键:刷新时从后端获取
-}
-```
-
-#### 2. `admin/src/permission.ts`
-
-**修改内容:**
-- 添加路由守卫,在获取用户信息后检查 `isPaw`
-- 强制跳转到修改密码页面
-
-**关键代码:**
-```typescript
-router.beforeEach(async (to, from, next) => {
- if (hasGetUserInfo) {
- // 已获取用户信息,检查 isPaw
- if (userStore.isPaw === 0) {
- next({ path: changePasswordPath })
- return
- }
- } else {
- // 首次获取用户信息
- await userStore.getUserInfo()
- // 获取后检查 isPaw
- if (userStore.isPaw === 0) {
- next({ path: changePasswordPath })
- return
- }
- }
-})
-```
-
-#### 3. `admin/src/views/account/login.vue`
-
-**修改内容:**
-- 登录成功后检查 `is_paw` 字段
-- 如果为 0,跳转到修改密码页面
-
-**关键代码:**
-```typescript
-const result: any = await userStore.login(formData)
-if (result.is_paw === 0) {
- router.push('/change-password')
- return
-}
-```
-
-#### 4. `admin/src/views/account/change-password.vue`
-
-**修改内容:**
-- 新建修改密码页面
-- 验证密码规则
-- 修改成功后更新状态并退出登录
-
-#### 5. `admin/src/router/routes.ts`
-
-**修改内容:**
-- 添加修改密码路由
-
-**关键代码:**
-```typescript
-{
- path: '/change-password',
- component: () => import('@/views/account/change-password.vue')
-}
-```
-
-### 数据库修改
-
-#### `add_is_paw_field.sql`
-
-```sql
-ALTER TABLE `la_admin`
-ADD COLUMN `is_paw` TINYINT(1) NOT NULL DEFAULT 1
-COMMENT '是否已修改初始密码:0=需要修改,1=正常'
-AFTER `multipoint_login`;
-```
-
-## 安全机制
-
-### 1. 强制跳转
-- 登录时检查 `is_paw`,为 0 则跳转到修改密码页面
-- 路由守卫拦截所有路由,强制跳转
-
-### 2. URL 防护
-- 即使直接输入 URL,路由守卫也会拦截
-- 无法绕过修改密码页面
-
-### 3. 刷新防护
-- 页面刷新时从后端重新获取 `is_paw` 状态
-- 确保状态始终与数据库一致
-
-### 4. 多标签页防护
-- 所有标签页共享同一个 Vuex store
-- 新打开的标签页也会被拦截
-
-## 工作流程
-
-```
-1. 管理员登录 (is_paw = 0)
- ↓
-2. 后端返回 is_paw = 0
- ↓
-3. 前端保存 isPaw = 0 到 store
- ↓
-4. 登录页面检查 isPaw,跳转到修改密码页面
- ↓
-5. 用户尝试访问其他页面(输入 URL 或刷新)
- ↓
-6. 路由守卫检查 isPaw
- ↓
-7. 如果 isPaw = 0,强制跳转回修改密码页面
- ↓
-8. 用户修改密码成功
- ↓
-9. 后端更新 is_paw = 1
- ↓
-10. 前端更新 isPaw = 1
- ↓
-11. 退出登录
- ↓
-12. 使用新密码重新登录
- ↓
-13. 路由守卫检查 isPaw = 1,允许正常访问
-```
-
-## 关键技术点
-
-### 1. 状态持久化问题
-
-**问题:** 页面刷新后 Vuex store 中的 `isPaw` 状态丢失
-
-**解决方案:** 在 `getUserInfo()` 方法中从后端重新获取 `is_paw` 状态
-
-### 2. 路由守卫时机
-
-**问题:** 在获取用户信息之前检查 `isPaw`,此时状态还是初始值
-
-**解决方案:** 在获取用户信息之后再检查 `isPaw`
-
-### 3. 密码加密方式
-
-**问题:** 使用错误的密码加密方式导致无法登录
-
-**解决方案:** 使用系统的 `create_password()` 函数,而不是 `password_hash()`
-
-## 测试验证
-
-### 必测场景
-
-1. ✅ 登录后自动跳转到修改密码页面
-2. ✅ 直接输入 URL 无法绕过
-3. ✅ 刷新页面仍然在修改密码页面
-4. ✅ 修改密码成功后 `is_paw` 更新为 1
-5. ✅ 使用新密码登录后正常进入系统
-6. ✅ 多标签页测试
-7. ✅ 密码验证规则测试
-8. ✅ 企业微信登录测试
-
-### 测试工具
-
-- 浏览器开发者工具(Network、Console)
-- Vue DevTools
-- 数据库查询工具
-
-## 文档清单
-
-| 文档 | 说明 |
-|------|------|
-| `FIRST_LOGIN_PASSWORD_CHANGE.md` | 完整功能文档 |
-| `QUICK_START_FIRST_LOGIN.md` | 快速开始指南 |
-| `TEST_CHECKLIST.md` | 测试清单 |
-| `SECURITY_FIX_SUMMARY.md` | 安全修复总结 |
-| `DEBUG_GUIDE.md` | 调试指南 |
-| `add_is_paw_field.sql` | 数据库字段添加 SQL |
-| `test_first_login.sql` | 测试用 SQL |
-| `install_first_login_password.sh` | Linux/Mac 安装脚本 |
-| `install_first_login_password.bat` | Windows 安装脚本 |
-
-## 安装步骤
-
-### 1. 执行数据库脚本
-
-```bash
-# Windows
-install_first_login_password.bat
-
-# Linux/Mac
-chmod +x install_first_login_password.sh
-./install_first_login_password.sh
-```
-
-### 2. 设置测试账号
-
-```sql
-UPDATE la_admin SET is_paw = 0 WHERE id = 1;
-```
-
-### 3. 测试功能
-
-按照 `TEST_CHECKLIST.md` 进行测试
-
-## 常见问题
-
-### Q1: 刷新后进入系统了
-
-**A:** 检查后端 `AdminLogic::detail()` 方法是否返回 `is_paw` 字段,前端 `getUserInfo()` 是否更新状态。
-
-### Q2: 可以通过 URL 绕过
-
-**A:** 检查路由守卫是否正确配置,是否在获取用户信息后检查 `isPaw`。
-
-### Q3: 修改密码后无法登录
-
-**A:** 检查密码加密方式是否使用 `create_password()` 函数。
-
-## 安全等级
-
-- 修复前:⚠️ 低(可绕过)
-- 修复后:✅ 高(无法绕过)
-
-## 维护建议
-
-1. 定期审计管理员账号的 `is_paw` 状态
-2. 为新创建的管理员默认设置 `is_paw = 0`
-3. 定期要求管理员修改密码
-4. 考虑添加密码强度验证
-5. 考虑添加密码历史记录
-
-## 完成时间
-
-2024-XX-XX
-
-## 版本
-
-v1.0 - 初始实现
-v1.1 - 修复刷新绕过问题
diff --git a/FIRST_LOGIN_PASSWORD_CHANGE.md b/FIRST_LOGIN_PASSWORD_CHANGE.md
deleted file mode 100644
index 960659e1..00000000
--- a/FIRST_LOGIN_PASSWORD_CHANGE.md
+++ /dev/null
@@ -1,142 +0,0 @@
-# 首次登录强制修改密码功能
-
-## 功能说明
-
-当管理员首次登录或需要重置密码时,系统会强制要求修改密码后才能正常使用系统。
-
-## 安装步骤
-
-### 方式一:使用安装脚本(推荐)
-
-#### Windows 系统
-```bash
-install_first_login_password.bat
-```
-
-#### Linux/Mac 系统
-```bash
-chmod +x install_first_login_password.sh
-./install_first_login_password.sh
-```
-
-### 方式二:手动执行 SQL
-
-执行 `add_is_paw_field.sql` 文件中的 SQL 语句:
-
-```sql
-ALTER TABLE `la_admin`
-ADD COLUMN `is_paw` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否已修改初始密码:0=需要修改,1=正常' AFTER `multipoint_login`;
-```
-
-## 使用方法
-
-### 设置管理员需要修改密码
-
-如果需要强制某个管理员在下次登录时修改密码,执行以下 SQL:
-
-```sql
-UPDATE la_admin SET is_paw = 0 WHERE id = <管理员ID>;
-```
-
-例如,强制 ID 为 1 的管理员修改密码:
-
-```sql
-UPDATE la_admin SET is_paw = 0 WHERE id = 1;
-```
-
-### 批量设置所有管理员
-
-如果需要强制所有管理员修改密码:
-
-```sql
-UPDATE la_admin SET is_paw = 0;
-```
-
-## 工作流程
-
-1. 管理员登录系统
-2. 系统检查 `is_paw` 字段
- - 如果 `is_paw = 0`:跳转到修改密码页面
- - 如果 `is_paw = 1`:正常进入系统
-3. 管理员在修改密码页面输入新密码
-4. 密码修改成功后,`is_paw` 自动更新为 1
-5. 系统要求重新登录
-6. 使用新密码登录后正常使用系统
-
-## 安全机制
-
-系统通过路由守卫确保安全性:
-
-1. **强制跳转**:当 `is_paw = 0` 时,用户访问任何页面都会被强制跳转到修改密码页面
-2. **URL 防护**:即使用户直接在浏览器输入其他页面 URL,也会被拦截并跳转到修改密码页面
-3. **状态持久化**:`is_paw` 状态存储在 Vuex store 中,页面刷新后仍然有效
-4. **防止绕过**:修改密码页面需要登录才能访问,但已修改密码的用户无法再次访问该页面
-
-## 技术实现
-
-### 后端修改
-
-1. **LoginLogic.php** - 登录逻辑返回 `is_paw` 字段
-2. **LoginController.php** - 添加 `changeFirstPassword` 接口
-3. **数据库** - 添加 `is_paw` 字段
-
-### 前端修改
-
-1. **login.vue** - 登录成功后判断 `is_paw` 字段
-2. **change-password.vue** - 新增修改密码页面
-3. **routes.ts** - 添加修改密码路由
-4. **user.ts** - 添加修改密码 API
-5. **user.ts (store)** - 存储 `isPaw` 状态
-6. **permission.ts** - 路由守卫,强制需要修改密码的用户只能访问修改密码页面
-
-## 字段说明
-
-| 字段名 | 类型 | 默认值 | 说明 |
-|--------|------|--------|------|
-| is_paw | TINYINT(1) | 1 | 0=需要修改密码,1=正常 |
-
-## 注意事项
-
-1. 修改密码后会自动退出登录,需要使用新密码重新登录
-2. 密码长度不能少于 6 位
-3. 两次输入的密码必须一致
-4. 该功能同时支持账号密码登录和企业微信登录
-
-## 测试步骤
-
-### 1. 安装功能
-
-执行安装脚本或手动执行 SQL 添加 `is_paw` 字段。
-
-### 2. 设置测试账号
-
-```sql
--- 设置 ID 为 1 的管理员需要修改密码
-UPDATE la_admin SET is_paw = 0 WHERE id = 1;
-```
-
-### 3. 测试登录流程
-
-1. 使用该管理员账号登录
-2. 登录成功后应自动跳转到修改密码页面
-3. 输入新密码(至少 6 位)
-4. 确认修改后应提示"密码修改成功,请重新登录"
-5. 自动跳转到登录页面
-6. 使用新密码登录,应能正常进入系统
-
-### 4. 验证数据库
-
-```sql
--- 查看管理员的 is_paw 状态,应该已更新为 1
-SELECT id, account, is_paw FROM la_admin WHERE id = 1;
-```
-
-### 5. 使用测试 SQL
-
-可以使用 `test_first_login.sql` 文件中的 SQL 语句进行测试。
-
-## 安全建议
-
-1. 建议为新创建的管理员账号设置 `is_paw = 0`,强制首次登录修改密码
-2. 定期要求管理员修改密码,可批量设置 `is_paw = 0`
-3. 密码应包含字母、数字和特殊字符,提高安全性
diff --git a/GANCAO_API_FIX.md b/GANCAO_API_FIX.md
deleted file mode 100644
index 1650eaac..00000000
--- a/GANCAO_API_FIX.md
+++ /dev/null
@@ -1,161 +0,0 @@
-# 甘草 API "Array to string conversion" 错误修复
-
-## 问题描述
-
-调用甘草处方下单接口时出现错误:
-```json
-{"code":2,"message":"Array to string conversion"}
-```
-
-## 根本原因
-
-经过分析代码和 API 文档,发现以下几个问题:
-
-### 1. 变量作用域问题
-在 `PrescriptionOrderLogic::submitGancaoRecipel()` 方法中,`$appNo` 变量在返回语句中被引用,但实际定义在更早的位置(line 1638),然后又在 line 1658 被重新赋值。这可能导致变量引用错误。
-
-### 2. 空字符串处理问题
-`buildSubmitPayload()` 方法中的 `cradle_store` 字段可能为空字符串,而甘草 API 可能不接受空字符串。
-
-### 3. 患者年龄格式问题
-原代码使用 `sprintf('%d.00', $ageInt)` 格式化年龄,但 API 可能期望简单的整数字符串。
-
-### 4. doct_advice 字段处理
-`doct_advice` 数组中的某些字段可能包含空字符串或未正确处理的值,导致 JSON 编码时出现问题。
-
-## 修复内容
-
-### 1. 修复 `GancaoScmRecipelService.php`
-
-#### 修改患者年龄格式
-```php
-// 修改前
-$patientAge = sprintf('%d.00', $ageInt);
-
-// 修改后
-$patientAge = (string) $ageInt;
-```
-
-#### 确保 cradle_store 不为空
-```php
-$preview['cradle_store'] = mb_substr(trim((string) ($c['cradle_store'] ?? '')), 0, 32);
-if ($preview['cradle_store'] === '') {
- $preview['cradle_store'] = 'default';
-}
-```
-
-#### 优化 doct_advice 字段处理
-```php
-// 确保所有字段都是字符串,避免数组到字符串的转换错误
-$tabooStr = $taboo !== '' ? $taboo : '无';
-$usageTimeStr = $usageTime;
-$usageBriefStr = $usageBrief !== '' ? $usageBrief : '遵医嘱';
-$othersStr = trim((string) ($rx['usage_notes'] ?? ''));
-$notesDoctorStr = trim((string) ($order['remark_extra'] ?? ''));
-
-$preview['doct_advice'] = [
- 'taboo' => $tabooStr,
- 'usage_time' => $usageTimeStr,
- 'usage_brief' => $usageBriefStr,
- 'others' => $othersStr !== '' ? $othersStr : '',
- 'notes_doctor' => $notesDoctorStr !== '' ? $notesDoctorStr : '',
-];
-```
-
-#### 添加调试日志
-```php
-public static function ctmSubmit(array $submitPayload): array
-{
- $transport = self::transport();
-
- // 记录请求负载以便调试
- Log::info('Gancao CTM_SUBMIT_RECIPEL payload', ['payload' => $submitPayload]);
-
- return $transport->post($submitPayload);
-}
-```
-
-### 2. 修复 `PrescriptionOrderLogic.php`
-
-#### 修复变量引用问题
-```php
-// 在返回语句中使用正确的变量引用
-$fee = $subBody['result']['fee'] ?? [];
-$appNo = $submitPayload['app_order_no'] ?? ('PO' . (string) $order->id);
-
-return [
- 'recipel_order_no' => $gcNo,
- 'app_order_no' => $appNo,
- 'fee' => is_array($fee) ? $fee : [],
-];
-```
-
-#### 添加详细错误日志
-```php
-use think\facade\Log;
-
-// 在 submitGancaoRecipel 方法中添加错误日志
-$subRet = GancaoScmRecipelService::ctmSubmit($submitPayload);
-if ((int) ($subRet['state'] ?? 0) !== 1) {
- $errMsg = (string) ($subRet['msg'] ?? '');
- $response = isset($subRet['response']) ? mb_substr((string) $subRet['response'], 0, 500) : '';
- self::$error = '甘草下单通信失败:' . $errMsg . ($response !== '' ? ';响应:' . $response : '');
- Log::error('Gancao CTM_SUBMIT failed', ['subRet' => $subRet, 'payload' => $submitPayload]);
-
- return false;
-}
-$subBody = $subRet['body'] ?? [];
-if (!GancaoScmRecipelService::isApiSuccess($subBody)) {
- self::$error = '甘草下单失败:' . GancaoScmRecipelService::apiStatusMessage($subBody);
- Log::error('Gancao CTM_SUBMIT api error', ['body' => $subBody, 'payload' => $submitPayload]);
-
- return false;
-}
-```
-
-## 测试建议
-
-1. **检查日志文件**:修复后再次调用接口,查看 `runtime/log/` 目录下的日志文件,查找 `Gancao CTM_SUBMIT_RECIPEL payload` 日志,确认发送的数据格式是否正确。
-
-2. **验证必填字段**:根据甘草 API 文档,确保以下字段都有有效值:
- - `token`:有效的 API token
- - `df_id`:剂型 ID(如 101 表示饮片)
- - `amount`:剂量数量
- - `m_list`:药材列表(每个药材必须有 `id`、`quantity`、`brief` 字段)
- - `express_to`:收货地址信息
- - `patient`:患者信息
- - `doctor`:医生信息
- - `callback_url`:回调地址(必须是 HTTPS)
-
-3. **检查配置**:确认 `.env` 文件中的甘草配置完整:
- ```env
- GANCAO_SCM_ENABLED=true
- GANCAO_SCM_GATEWAY_URL=https://xxx
- GANCAO_SCM_GATEWAY_AK=xxx
- GANCAO_SCM_GATEWAY_SK=xxx
- GANCAO_SCM_BIZ_AK=xxx
- GANCAO_SCM_BIZ_SK=xxx
- GANCAO_SCM_CALLBACK_URL=https://xxx
- GANCAO_SCM_CRADLE_STORE=xxx
- GANCAO_SCM_EXPRESS_TYPE=sf
- ```
-
-4. **测试流程**:
- - 创建一个测试处方订单
- - 确保处方审核通过
- - 调用 `submitGancaoRecipel` 接口
- - 查看返回结果和日志
-
-## 可能的其他原因
-
-如果修复后仍然出现错误,可能是以下原因:
-
-1. **药材 ID 映射问题**:某些药材的甘草 ID(`gid`)未正确配置
-2. **剂型参数问题**:`df101ext` 等剂型扩展参数不符合 API 要求
-3. **地址解析问题**:收货地址无法正确解析为省市区
-4. **电话号码格式**:电话号码不是 11 位数字
-
-## 参考文档
-
-- 甘草 API 文档:https://apidoc.igancao.com/service-doc/scm-outer-recipel.html
-- 特别关注「中药处方下单」和「中药处方下单参数预检查」章节
diff --git a/GANCAO_ARRAY_TO_STRING_FIX.md b/GANCAO_ARRAY_TO_STRING_FIX.md
deleted file mode 100644
index 7d47d0fe..00000000
--- a/GANCAO_ARRAY_TO_STRING_FIX.md
+++ /dev/null
@@ -1,278 +0,0 @@
-# 甘草 API "Array to string conversion" 错误完整解决方案
-
-## 问题现象
-
-调用甘草处方下单接口时返回:
-```json
-{"code":2,"message":"Array to string conversion"}
-```
-
-## 根本原因分析
-
-"Array to string conversion" 错误通常发生在以下情况:
-
-1. **配置文件中的值被错误地解析为数组**
- - 例如:`.env` 中 `GANCAO_SCM_EXPRESS_TYPE=["sf"]` 会被解析为数组
- - 正确应该是:`GANCAO_SCM_EXPRESS_TYPE=sf`
-
-2. **PHP 尝试将数组转换为字符串**
- - 在字符串拼接、JSON 编码或其他操作中
-
-3. **配置读取问题**
- - ThinkPHP 的配置解析可能将某些值错误地转换为数组
-
-## 完整修复步骤
-
-### 步骤 1: 检查配置文件
-
-运行配置检查脚本:
-
-```bash
-cd /path/to/your/project
-php test_gancao_config.php
-```
-
-这个脚本会检查:
-- 所有必需的配置项是否存在
-- 配置值的类型是否正确
-- 是否有数组值被错误地用作字符串
-
-### 步骤 2: 修复 .env 配置
-
-确保 `.env` 文件中的甘草配置格式正确:
-
-```env
-# 甘草配置必须写在文件顶部,或者使用 [GANCAO_SCM] 分区
-# 不要写在 [trtc] 等其他分区内
-
-GANCAO_SCM_ENABLED=true
-GANCAO_SCM_GATEWAY_URL=https://your-gateway-url.com
-GANCAO_SCM_GATEWAY_AK=your_gateway_ak
-GANCAO_SCM_GATEWAY_SK=your_gateway_sk_16chars
-GANCAO_SCM_BIZ_AK=your_biz_ak
-GANCAO_SCM_BIZ_SK=your_biz_sk
-GANCAO_SCM_CALLBACK_URL=https://your-domain.com/api/gancao/callback
-GANCAO_SCM_EXPRESS_TYPE=sf
-GANCAO_SCM_CRADLE_STORE=your_store_name
-GANCAO_SCM_DF_ID=101
-
-# 可选配置
-GANCAO_SCM_DEFAULT_IS_DECOCT=1
-GANCAO_SCM_DEFAULT_TIMES_PER_DAY=2
-GANCAO_SCM_DEFAULT_NUM_PER_PACK=2
-GANCAO_SCM_DEFAULT_DOSE_ML=150
-```
-
-**重要注意事项:**
-
-1. ❌ **错误示例**(会导致数组转字符串错误):
- ```env
- GANCAO_SCM_EXPRESS_TYPE=["sf"]
- GANCAO_SCM_CALLBACK_URL=["https://..."]
- ```
-
-2. ✅ **正确示例**:
- ```env
- GANCAO_SCM_EXPRESS_TYPE=sf
- GANCAO_SCM_CALLBACK_URL=https://...
- ```
-
-3. 不要在值周围加引号(除非值本身包含空格)
-4. 不要使用 JSON 格式
-5. 不要使用数组语法 `[]`
-
-### 步骤 3: 检查配置文件
-
-如果使用了 `config/gancao_scm.php` 配置文件,确保格式正确:
-
-```php
- env('gancao_scm.enabled', false),
- 'gateway_url' => env('gancao_scm.gateway_url', ''),
- 'gateway_ak' => env('gancao_scm.gateway_ak', ''),
- 'gateway_sk' => env('gancao_scm.gateway_sk', ''),
- 'biz_ak' => env('gancao_scm.biz_ak', ''),
- 'biz_sk' => env('gancao_scm.biz_sk', ''),
- 'callback_url' => env('gancao_scm.callback_url', ''),
- 'express_type' => env('gancao_scm.express_type', 'sf'), // 确保是字符串
- 'cradle_store' => env('gancao_scm.cradle_store', ''),
- 'df_id' => (int)env('gancao_scm.df_id', 101),
-
- // 可选配置
- 'default_is_decoct' => (int)env('gancao_scm.default_is_decoct', 1),
- 'default_times_per_day' => (int)env('gancao_scm.default_times_per_day', 2),
- 'default_num_per_pack' => (int)env('gancao_scm.default_num_per_pack', 2),
- 'default_dose_ml' => (int)env('gancao_scm.default_dose_ml', 150),
-
- // 药材 ID 映射(这个可以是数组)
- 'herb_id_map' => [
- // '药材名' => 甘草ID
- ],
-];
-```
-
-### 步骤 4: 清除缓存
-
-```bash
-# 清除 ThinkPHP 缓存
-cd server
-php think clear
-
-# 或者手动删除缓存目录
-rm -rf runtime/cache/*
-rm -rf runtime/temp/*
-```
-
-### 步骤 5: 重启服务
-
-```bash
-# 如果使用 PHP-FPM
-sudo systemctl restart php-fpm
-
-# 如果使用 Nginx
-sudo systemctl restart nginx
-
-# 如果使用 Apache
-sudo systemctl restart apache2
-```
-
-### 步骤 6: 查看日志
-
-修复后再次调用接口,查看日志文件:
-
-```bash
-tail -f server/runtime/log/$(date +%Y%m%d).log
-```
-
-日志会显示:
-- `Gancao CTM_SUBMIT_RECIPEL payload` - 发送的完整负载
-- `Gancao CTM_SUBMIT failed` - 如果通信失败
-- `Gancao CTM_SUBMIT api error` - 如果 API 返回错误
-- `submitGancaoRecipel exception` - 如果发生异常
-
-## 代码修复说明
-
-已修复的文件:
-
-### 1. `GancaoScmRecipelService.php`
-
-- ✅ 修复 `express_type` 可能是数组的问题
-- ✅ 修复 `callback_url` 可能是数组的问题
-- ✅ 修复患者年龄格式
-- ✅ 优化 `doct_advice` 字段处理
-- ✅ 添加调试日志
-
-### 2. `PrescriptionOrderLogic.php`
-
-- ✅ 修复 `$appNo` 变量引用问题
-- ✅ 添加详细错误日志
-- ✅ 添加异常捕获
-
-### 3. `PrescriptionOrderController.php`
-
-- ✅ 添加 try-catch 异常处理
-- ✅ 添加返回值类型检查
-- ✅ 添加详细日志记录
-
-## 测试步骤
-
-1. **运行配置检查**:
- ```bash
- php test_gancao_config.php
- ```
-
-2. **检查输出**:
- - 所有配置项应该显示 ✓
- - 不应该有 ❌ 或 ⚠️ 标记
- - JSON 编码应该成功
-
-3. **调用接口**:
- ```bash
- curl -X POST https://your-domain.com/api/tcm.prescriptionOrder/submitGancaoRecipel \
- -H "Content-Type: application/json" \
- -H "token: your_token" \
- -d '{"id": 123}'
- ```
-
-4. **查看日志**:
- ```bash
- tail -f server/runtime/log/$(date +%Y%m%d).log | grep -i gancao
- ```
-
-## 常见错误和解决方案
-
-### 错误 1: "Array to string conversion"
-
-**原因**:配置值是数组而不是字符串
-
-**解决**:
-1. 运行 `php test_gancao_config.php` 找出哪个配置是数组
-2. 修改 `.env` 文件,移除 `[]` 和引号
-3. 清除缓存并重启服务
-
-### 错误 2: "callback_url is invalid"
-
-**原因**:回调地址未配置或格式错误
-
-**解决**:
-1. 确保 `GANCAO_SCM_CALLBACK_URL` 配置正确
-2. 必须是 HTTPS 地址
-3. 格式:`https://your-domain.com/api/gancao/callback`
-
-### 错误 3: "express_type is invalid"
-
-**原因**:快递类型配置错误
-
-**解决**:
-1. 检查 `GANCAO_SCM_EXPRESS_TYPE` 配置
-2. 有效值:`sf`(顺丰)、`jd`(京东)、`jt`(极兔)、`auto`(自动)
-3. 默认使用 `sf`
-
-### 错误 4: JSON 编码失败
-
-**原因**:数据中包含无法编码的内容
-
-**解决**:
-1. 检查处方数据中是否有特殊字符
-2. 检查药材名称是否包含非 UTF-8 字符
-3. 查看日志中的完整错误信息
-
-## 验证修复
-
-修复成功的标志:
-
-1. ✅ 配置检查脚本全部通过
-2. ✅ 接口返回成功:
- ```json
- {
- "code": 1,
- "msg": "甘草药方上传成功",
- "data": {
- "recipel_order_no": "GC202604...",
- "app_order_no": "PO123",
- "fee": {
- "total": 150.00,
- "medicine": 120.00,
- "process": 20.00,
- "express": 10.00
- }
- }
- }
- ```
-3. ✅ 日志中显示 `submitGancaoRecipel success`
-4. ✅ 数据库中 `gancao_reciperl_order_no` 字段已更新
-
-## 需要帮助?
-
-如果以上步骤都无法解决问题,请提供:
-
-1. 配置检查脚本的完整输出
-2. 错误日志的完整内容(`runtime/log/*.log`)
-3. 接口返回的完整响应
-4. `.env` 文件中的甘草配置部分(隐藏敏感信息)
-
-## 参考文档
-
-- 甘草 API 文档:https://apidoc.igancao.com/service-doc/scm-outer-recipel.html
-- ThinkPHP 配置文档:https://www.kancloud.cn/manual/thinkphp6_0/1037488
diff --git a/GANCAO_CALLBACK_SETUP.md b/GANCAO_CALLBACK_SETUP.md
deleted file mode 100644
index a81db000..00000000
--- a/GANCAO_CALLBACK_SETUP.md
+++ /dev/null
@@ -1,355 +0,0 @@
-# 甘草订单状态回调功能配置指南
-
-## 功能说明
-
-实现甘草订单状态回调功能,当甘草订单状态发生变化时(如审核通过、制作中、发货、完成等),甘草系统会主动回调我们的接口,更新订单状态。
-
-## 已创建的文件
-
-### 1. 回调控制器
-**文件:** `server/app/api/controller/GancaoCallbackController.php`
-
-**功能:**
-- 接收甘草订单状态回调
-- 验证回调签名(防止伪造)
-- 更新订单状态
-- 记录回调日志
-
-**支持的订单状态:**
-- `10` - 系统审核中
-- `11` - 系统审核通过
-- `110` - 订单药房流转制作中(包含:派单、审方、调配、复核、浸泡、煎药、包装、发货等流程)
-- `20` - 物流中
-- `30` - 完成(终态)
-- `90` - 拦截(终止流转:可恢复)
-- `91` - 主动撤单(退费:终态)
-- `92` - 驳回(无法制作并退费:终态)
-
-### 2. 数据库迁移文件
-**文件:** `add_gancao_callback_fields.sql`
-
-**新增字段:**
-- `gancao_order_state` - 甘草订单状态
-- `gancao_flow_name` - 订单流程名称
-- `gancao_supplier` - 供应商/药房名称
-- `gancao_remark` - 订单备注
-
-## 配置步骤
-
-### 步骤 1: 执行数据库迁移
-
-```bash
-# 连接到数据库
-mysql -u root -p your_database
-
-# 执行 SQL 文件
-source /path/to/add_gancao_callback_fields.sql;
-
-# 或者直接执行
-mysql -u root -p your_database < add_gancao_callback_fields.sql
-```
-
-### 步骤 2: 添加路由
-
-在 `server/route/api.php` 文件中添加回调路由:
-
-```php
- (string) zyt_gancao_scm_env('CALLBACK_URL', ''),
-
- // 回调签名验证(如果甘草提供了独立的密钥)
- 'callback_appkey' => (string) zyt_gancao_scm_env('CALLBACK_APPKEY', ''),
- 'callback_secret_key' => (string) zyt_gancao_scm_env('CALLBACK_SECRET_KEY', ''),
-];
-```
-
-### 步骤 5: 测试回调接口
-
-#### 方法 A:使用 curl 测试
-
-```bash
-# 模拟甘草回调请求
-curl -X POST https://your-domain.com/api/gancao/callback/order-status \
- -H "Content-Type: application/json" \
- -H "access-appkey: your_appkey" \
- -H "access-nonce: test1234" \
- -H "access-timestamp: $(date +%s)" \
- -H "access-sign: calculated_sign" \
- -d '{
- "recipel_order_no": "GC202604...",
- "state": 11,
- "ext": {
- "flow_name": "审核通过"
- }
- }'
-```
-
-#### 方法 B:查看日志
-
-```bash
-# 查看回调日志
-tail -f server/runtime/log/$(date +%Y%m%d).log | grep -i "gancao callback"
-```
-
-### 步骤 6: 向甘草提供回调地址
-
-联系甘草技术支持,提供以下信息:
-1. 回调地址:`https://your-domain.com/api/gancao/callback/order-status`
-2. 确认回调签名验证方式
-3. 测试回调是否正常
-
-## 回调数据格式
-
-### 请求头
-
-```
-access-appkey: ak-xxxxx
-access-nonce: random_string
-access-timestamp: 1234567890
-access-sign: md5_hash
-```
-
-### 请求体
-
-```json
-{
- "recipel_order_no": "GC202604...",
- "state": 110,
- "ext": {
- "flow_name": "煎药开始",
- "supplier": "XX药房"
- }
-}
-```
-
-### 状态 110(制作中)的扩展信息
-
-```json
-{
- "recipel_order_no": "GC202604...",
- "state": 110,
- "ext": {
- "flow_name": "派单|审方|调配|复核|浸泡|煎药开始|包装|发货|寄出",
- "supplier": "XX药房"
- }
-}
-```
-
-### 状态 20(物流中)的扩展信息
-
-```json
-{
- "recipel_order_no": "GC202604...",
- "state": 20,
- "ext": {
- "shipping_name": "顺丰速运",
- "nu": "SF1234567890",
- "supplier": "XX药房"
- }
-}
-```
-
-## 签名验证
-
-### 签名算法
-
-```
-access_sign = md5(access-appkey + secret-key + access-nonce + access-timestamp + request_body)
-```
-
-### 示例
-
-```
-access-appkey: ak-36b05d5034f13a86b102c97e72f14
-secret-key: f8c1f3c58b55a61a4d41242016d314ea
-access-nonce: 1jd4u8ii
-access-timestamp: 1723014934
-Body: {"recipel_order_no":"1234","state":10,"ext":{"flow_name":"浸泡开始"}}
-
-计算:
-md5("ak-36b05d5034f13a86b102c97e72f14" + "f8c1f3c58b55a61a4d41242016d314ea" + "1jd4u8ii" + "1723014934" + '{"recipel_order_no":"1234","state":10,"ext":{"flow_name":"浸泡开始"}}')
-= 03b46e3b385d1dceb183cad08e44f31f
-```
-
-## 订单状态映射
-
-### 甘草状态 → 系统履约状态
-
-| 甘草状态 | 状态名称 | 系统履约状态 | 说明 |
-|---------|---------|-------------|------|
-| 10 | 系统审核中 | 保持不变 | 甘草内部审核 |
-| 11 | 系统审核通过 | 保持不变 | 审核通过,准备制作 |
-| 110 | 订单药房流转制作中 | 2(履约中) | 药房制作流程 |
-| 110(发货) | 发货/寄出 | 5(已发货) | 药房已发货 |
-| 20 | 物流中 | 5(已发货) | 快递运输中 |
-| 30 | 完成 | 3(已完成) | 订单完成 |
-| 90 | 拦截 | 保持不变 | 订单被拦截 |
-| 91 | 主动撤单 | 4(已取消) | 撤单并退费 |
-| 92 | 驳回 | 4(已取消) | 无法制作并退费 |
-
-## 日志记录
-
-### 回调接收日志
-
-```
-[info] Gancao callback received: {
- "headers": {...},
- "body": {...}
-}
-```
-
-### 回调处理日志
-
-```
-[info] Gancao callback processed: {
- "order_id": 123,
- "recipel_order_no": "GC202604...",
- "state": 110,
- "ext": {...}
-}
-```
-
-### 订单日志
-
-在 `zyt_prescription_order_log` 表中记录:
-- `admin_name`: "甘草系统"
-- `action`: "gancao_callback"
-- `summary`: "甘草订单状态更新:系统审核通过"
-
-## 错误处理
-
-### 签名验证失败
-
-```
-[warning] Gancao callback sign verification failed
-```
-
-**处理:** 返回 "ok",避免甘草重试
-
-### 订单不存在
-
-```
-[warning] Gancao callback order not found
-```
-
-**处理:** 返回 "ok",记录日志
-
-### 更新失败
-
-```
-[error] Gancao callback update order failed
-```
-
-**处理:** 返回 "ok",记录错误日志
-
-## 重试机制
-
-甘草的重试策略:
-- 如果回调失败(未返回 "ok" 或超时),会进行重试
-- 最多重试 10 次
-- 重试间隔:失败次数 × 5 分钟
-
-**建议:**
-1. 接口必须在 5 秒内返回 "ok"
-2. 使用异步处理复杂逻辑
-3. 即使处理失败也返回 "ok",避免重复回调
-
-## 安全建议
-
-1. **启用签名验证**:防止伪造回调
-2. **记录所有回调**:便于排查问题
-3. **限制访问频率**:防止恶意请求
-4. **使用 HTTPS**:保护数据传输安全
-5. **IP 白名单**:只允许甘草服务器 IP 访问(可选)
-
-## 监控和告警
-
-### 监控指标
-
-1. 回调接收数量
-2. 签名验证失败次数
-3. 订单更新失败次数
-4. 回调处理耗时
-
-### 告警规则
-
-1. 签名验证失败率 > 10%
-2. 订单更新失败率 > 5%
-3. 回调处理耗时 > 3 秒
-
-## 测试清单
-
-- [ ] 数据库字段已添加
-- [ ] 路由已配置
-- [ ] 回调 URL 已配置
-- [ ] 签名验证正常
-- [ ] 订单状态更新正常
-- [ ] 日志记录正常
-- [ ] 错误处理正常
-- [ ] 已向甘草提供回调地址
-- [ ] 已测试真实回调
-
-## 故障排查
-
-### 问题 1:收不到回调
-
-**检查:**
-1. 回调 URL 是否正确
-2. 服务器是否能从公网访问
-3. 防火墙是否开放端口
-4. 路由是否配置正确
-
-### 问题 2:签名验证失败
-
-**检查:**
-1. appkey 是否正确
-2. secret-key 是否正确
-3. 签名算法是否正确
-4. 请求体是否被修改
-
-### 问题 3:订单状态未更新
-
-**检查:**
-1. 订单是否存在
-2. 数据库字段是否添加
-3. 更新逻辑是否正确
-4. 查看错误日志
-
-## 相关文档
-
-- 甘草 API 文档:https://apidoc.igancao.com/service-doc/scm-outer-recipel.html#订单状态回调
-- ThinkPHP 路由文档:https://www.kancloud.cn/manual/thinkphp6_0/1037493
diff --git a/GANCAO_FINAL_FIX.md b/GANCAO_FINAL_FIX.md
deleted file mode 100644
index 5097c9b7..00000000
--- a/GANCAO_FINAL_FIX.md
+++ /dev/null
@@ -1,318 +0,0 @@
-# 甘草 API "Array to string conversion" 最终修复
-
-## 问题根源
-
-错误发生在 `GancaoScmRecipelService::getToken()` 方法中:
-
-```php
-// 第 126 行
-if ($code === '10103' || str_contains($apiMsg, '10103')) {
-```
-
-**原因:** 当甘草 API 返回错误时,`$body['status']['msg']` 可能是**数组**而不是字符串,导致:
-1. `apiStatusMessage()` 方法尝试将数组转换为字符串
-2. `str_contains()` 函数接收到数组参数,触发 "Array to string conversion" 错误
-
-## 已修复的问题
-
-### 1. `apiStatusMessage()` 方法
-
-**修复前:**
-```php
-$msg = (string) ($body['status']['msg'] ?? '');
-```
-
-**修复后:**
-```php
-$msg = $body['status']['msg'] ?? '';
-
-// 确保 msg 是字符串
-if (is_array($msg)) {
- $msg = json_encode($msg, JSON_UNESCAPED_UNICODE);
-} else {
- $msg = (string) $msg;
-}
-```
-
-### 2. `getToken()` 方法中的 `str_contains()` 调用
-
-**修复前:**
-```php
-if ($code === '10103' || str_contains($apiMsg, '10103')) {
-```
-
-**修复后:**
-```php
-// 确保 $apiMsg 是字符串,避免 Array to string conversion 错误
-$apiMsgStr = is_string($apiMsg) ? $apiMsg : json_encode($apiMsg, JSON_UNESCAPED_UNICODE);
-
-if ($code === '10103' || str_contains($apiMsgStr, '10103')) {
-```
-
-### 3. 其他已修复的问题
-
-#### SSL 连接问题(`GancaoOpenApiTransport.php`)
-- ✅ 改用 HTTP/1.1
-- ✅ 强制使用 TLS 1.2
-- ✅ 降低 OpenSSL 安全级别
-- ✅ 增加连接超时
-- ✅ 添加 TCP keepalive
-
-#### 配置类型问题(`GancaoScmRecipelService.php`)
-- ✅ 确保 `express_type` 是字符串
-- ✅ 确保 `callback_url` 是字符串
-- ✅ 修复患者年龄格式
-- ✅ 优化 `doct_advice` 字段处理
-
-#### 错误处理(`PrescriptionOrderLogic.php` 和 `PrescriptionOrderController.php`)
-- ✅ 添加详细错误日志
-- ✅ 添加异常捕获
-- ✅ 添加返回值类型检查
-
-## 完整的修复文件列表
-
-1. ✅ `server/app/common/service/gancao/GancaoScmRecipelService.php`
-2. ✅ `server/app/common/service/gancao/GancaoOpenApiTransport.php`
-3. ✅ `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
-4. ✅ `server/app/adminapi/controller/tcm/PrescriptionOrderController.php`
-
-## 部署步骤
-
-### 步骤 1: 上传所有修复文件
-
-```bash
-# 上传修复后的文件
-scp server/app/common/service/gancao/GancaoScmRecipelService.php user@server:/path/to/server/app/common/service/gancao/
-scp server/app/common/service/gancao/GancaoOpenApiTransport.php user@server:/path/to/server/app/common/service/gancao/
-scp server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php user@server:/path/to/server/app/adminapi/logic/tcm/
-scp server/app/adminapi/controller/tcm/PrescriptionOrderController.php user@server:/path/to/server/app/adminapi/controller/tcm/
-
-# 上传测试脚本
-scp test_gancao_config.php user@server:/path/to/
-scp test_gancao_ssl.php user@server:/path/to/
-scp diagnose_gancao_payload.php user@server:/path/to/
-```
-
-### 步骤 2: 验证文件已上传
-
-```bash
-ssh user@server
-cd /path/to/your/project
-
-# 检查文件修改时间
-ls -la server/app/common/service/gancao/GancaoScmRecipelService.php
-ls -la server/app/common/service/gancao/GancaoOpenApiTransport.php
-ls -la server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php
-ls -la server/app/adminapi/controller/tcm/PrescriptionOrderController.php
-```
-
-### 步骤 3: 运行配置检查
-
-```bash
-php test_gancao_config.php
-```
-
-**期望输出:** 所有配置项显示 ✓
-
-### 步骤 4: 运行 SSL 测试
-
-```bash
-php test_gancao_ssl.php
-```
-
-**期望输出:** 至少一种 SSL 配置测试成功,并且能成功获取 token
-
-### 步骤 5: 清除缓存
-
-```bash
-cd server
-php think clear
-rm -rf runtime/cache/*
-rm -rf runtime/temp/*
-```
-
-### 步骤 6: 重启服务
-
-```bash
-# PHP-FPM
-sudo systemctl restart php-fpm
-
-# Nginx
-sudo systemctl restart nginx
-
-# 或者 Apache
-sudo systemctl restart apache2
-```
-
-### 步骤 7: 测试接口
-
-**开启日志监控:**
-```bash
-tail -f server/runtime/log/$(date +%Y%m%d).log | grep -E "(Gancao|gancao|submitGancao)"
-```
-
-**在另一个终端调用接口:**
-```bash
-curl -X POST https://your-domain.com/api/tcm.prescriptionOrder/submitGancaoRecipel \
- -H "Content-Type: application/json" \
- -H "token: your_admin_token" \
- -d '{"id": 订单ID}'
-```
-
-## 预期结果
-
-### 成功的情况
-
-**接口响应:**
-```json
-{
- "code": 1,
- "show": 0,
- "msg": "甘草药方上传成功",
- "data": {
- "recipel_order_no": "GC202604...",
- "app_order_no": "PO123",
- "fee": {
- "total": 150.00,
- "medicine": 120.00,
- "process": 20.00,
- "express": 10.00
- }
- }
-}
-```
-
-**日志内容:**
-```
-[info] Gancao MAKE_TOKEN request: {...}
-[info] Gancao MAKE_TOKEN response: {...}
-[info] Gancao CTM_SUBMIT_RECIPEL payload: {...}
-[info] submitGancaoRecipel success: {...}
-```
-
-**数据库变化:**
-- `prescription_order` 表中对应订单的 `gancao_reciperl_order_no` 字段已更新
-- `gancao_submit_time` 字段已更新为当前时间戳
-
-### 失败的情况
-
-如果仍然失败,日志会显示详细错误信息:
-
-**配置错误:**
-```
-[error] Gancao MAKE_TOKEN api error: {"body": {...}, "apiMsg": "..."}
-```
-→ 检查 `.env` 配置,特别是 AK/SK
-
-**SSL 连接错误:**
-```
-[error] Gancao CTM_SUBMIT failed: {"subRet": {...}, "payload": {...}}
-```
-→ 运行 `php test_gancao_ssl.php` 诊断 SSL 问题
-
-**业务逻辑错误:**
-```
-[error] submitGancaoRecipel exception: {"message": "...", "trace": "..."}
-```
-→ 检查处方数据是否完整,药材 ID 是否正确映射
-
-## 常见错误和解决方案
-
-### 错误 1: "Array to string conversion"
-
-**已修复!** 如果仍然出现,请检查:
-1. 文件是否正确上传
-2. 缓存是否已清除
-3. 服务是否已重启
-
-### 错误 2: SSL 连接错误
-
-**解决方案:**
-1. 运行 `php test_gancao_ssl.php`
-2. 查看哪种 SSL 配置成功
-3. 如果都失败,检查 OpenSSL 版本:`openssl version`
-4. 联系甘草技术支持确认 SSL 要求
-
-### 错误 3: "MAKE_TOKEN 失败:[10103]"
-
-**原因:** AK/SK 不匹配或密码计算错误
-
-**解决方案:**
-1. 检查 `.env` 中的配置:
- ```env
- GANCAO_SCM_BIZ_AK=xxx
- GANCAO_SCM_BIZ_SK=xxx
- ```
-2. 确认 BIZ AK/SK 与网关 AK/SK 是否相同
-3. 检查服务器时间是否正确:`date`
-4. 联系甘草获取正确的 AK/SK
-
-### 错误 4: "药材无法匹配甘草药ID"
-
-**原因:** 药材 ID 映射缺失
-
-**解决方案:**
-1. 检查 `zyt_doctor_medicine` 表中药材的 `gid` 字段
-2. 或在 `.env` 配置 `herb_id_map`:
- ```php
- 'herb_id_map' => [
- '当归' => 1001,
- '黄芪' => 1002,
- ]
- ```
-
-## 验证修复成功
-
-✅ **所有以下条件都满足,说明修复成功:**
-
-1. `php test_gancao_config.php` 全部通过
-2. `php test_gancao_ssl.php` 显示 "✓ 成功获取 token"
-3. 接口返回 `{"code": 1, "msg": "甘草药方上传成功"}`
-4. 日志中显示 `submitGancaoRecipel success`
-5. 数据库中订单已更新 `gancao_reciperl_order_no`
-6. 没有 "Array to string conversion" 错误
-7. 没有 SSL 连接错误
-
-## 技术支持
-
-如果以上步骤都无法解决问题,请收集以下信息:
-
-1. **配置检查输出:**
- ```bash
- php test_gancao_config.php > config_check.txt 2>&1
- ```
-
-2. **SSL 测试输出:**
- ```bash
- php test_gancao_ssl.php > ssl_check.txt 2>&1
- ```
-
-3. **错误日志:**
- ```bash
- tail -200 server/runtime/log/$(date +%Y%m%d).log > error.log
- ```
-
-4. **接口响应:**
- ```bash
- curl -X POST ... > response.json 2>&1
- ```
-
-5. **系统信息:**
- ```bash
- php -v > system_info.txt
- openssl version >> system_info.txt
- curl --version >> system_info.txt
- cat /etc/os-release >> system_info.txt
- ```
-
-将这些文件发送给技术支持进行分析。
-
-## 总结
-
-这次修复解决了三个主要问题:
-
-1. **Array to string conversion** - 甘草 API 返回的错误消息可能是数组
-2. **SSL 连接失败** - TLS 版本和 HTTP 版本不兼容
-3. **配置类型错误** - 某些配置值被错误地解析为数组
-
-所有问题都已修复,现在应该可以正常调用甘草 API 了!
diff --git a/GANCAO_FIX_SUMMARY.md b/GANCAO_FIX_SUMMARY.md
deleted file mode 100644
index ac7f20d7..00000000
--- a/GANCAO_FIX_SUMMARY.md
+++ /dev/null
@@ -1,286 +0,0 @@
-# 甘草 API "Array to string conversion" 错误修复总结
-
-## 问题描述
-在 Linux 服务器上调用甘草处方下单接口时,返回错误:
-```json
-{"code":2,"message":"Array to string conversion"}
-```
-
-## 已完成的修复
-
-### 1. 代码层面修复
-
-#### ✅ `GancaoScmRecipelService.php`
-- 修复 `express_type` 字段:确保是字符串而不是数组
-- 修复 `callback_url` 字段:确保是字符串而不是数组
-- 修复患者年龄格式:从 `sprintf('%d.00', $ageInt)` 改为 `(string) $ageInt`
-- 优化 `doct_advice` 字段处理:确保所有子字段都是字符串
-- 添加调试日志:记录发送的完整负载
-
-#### ✅ `PrescriptionOrderLogic.php`
-- 修复 `$appNo` 变量引用问题
-- 添加详细错误日志:记录失败时的完整请求和响应
-- 添加 `use think\facade\Log;` 导入
-
-#### ✅ `PrescriptionOrderController.php`
-- 添加 try-catch 异常处理
-- 添加返回值类型检查
-- 添加详细日志记录
-
-### 2. 诊断工具
-
-创建了三个诊断脚本:
-
-1. **`test_gancao_config.php`** - 配置检查脚本
- - 检查所有配置项是否存在
- - 检查配置值类型是否正确
- - 检查是否有数组值被错误地用作字符串
- - 测试 JSON 编码
-
-2. **`diagnose_gancao_payload.php`** - 负载诊断脚本
- - 测试 `buildPreviewPayload` 方法
- - 测试 `buildSubmitPayload` 方法
- - 检查每个字段的类型
- - 测试 JSON 编码
- - 保存负载到文件以便检查
-
-3. **文档**:
- - `GANCAO_API_FIX.md` - 初步修复说明
- - `GANCAO_ARRAY_TO_STRING_FIX.md` - 完整解决方案
- - `GANCAO_FIX_SUMMARY.md` - 本文档
-
-## 下一步操作
-
-### 步骤 1: 上传修复后的代码到服务器
-
-```bash
-# 上传修改的文件
-scp server/app/common/service/gancao/GancaoScmRecipelService.php user@server:/path/to/server/app/common/service/gancao/
-scp server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php user@server:/path/to/server/app/adminapi/logic/tcm/
-scp server/app/adminapi/controller/tcm/PrescriptionOrderController.php user@server:/path/to/server/app/adminapi/controller/tcm/
-
-# 上传诊断脚本
-scp test_gancao_config.php user@server:/path/to/
-scp diagnose_gancao_payload.php user@server:/path/to/
-```
-
-### 步骤 2: 在服务器上运行配置检查
-
-```bash
-ssh user@server
-cd /path/to/your/project
-
-# 运行配置检查
-php test_gancao_config.php
-```
-
-**期望输出**:
-```
-=== 甘草配置检查 ===
-
-1. 检查配置是否存在:
- ✓ 配置已加载
-
-2. 检查各项配置:
- - enabled: ✓ boolean = true
- - gateway_url: ✓ string = https://...
- - gateway_ak: ✓ string = xxx
- - gateway_sk: ✓ string = xxx
- - biz_ak: ✓ string = xxx
- - biz_sk: ✓ string = xxx
- - callback_url: ✓ string = https://...
- - express_type: ✓ string = sf
- - cradle_store: ✓ string = xxx
- - df_id: ✓ integer = 101
-
-...
-
-=== 检查结果: ✓ 配置正常 ===
-```
-
-**如果看到 ❌ 或 ⚠️**:
-- 记录哪个字段有问题
-- 检查 `.env` 文件中该字段的配置
-- 修复后重新运行检查
-
-### 步骤 3: 运行负载诊断
-
-```bash
-php diagnose_gancao_payload.php
-```
-
-这会:
-- 测试负载构建过程
-- 检查每个字段的类型
-- 测试 JSON 编码
-- 生成一个 JSON 文件(`gancao_payload_YYYYMMDDHHMMSS.json`)
-
-检查生成的 JSON 文件,确保:
-- 没有字段的值是 `"Array"`
-- 所有字符串字段都是字符串
-- 所有数组字段都是数组
-
-### 步骤 4: 清除缓存
-
-```bash
-cd server
-php think clear
-
-# 或手动删除
-rm -rf runtime/cache/*
-rm -rf runtime/temp/*
-```
-
-### 步骤 5: 重启服务
-
-```bash
-# PHP-FPM
-sudo systemctl restart php-fpm
-
-# Nginx
-sudo systemctl restart nginx
-```
-
-### 步骤 6: 测试接口
-
-```bash
-# 查看实时日志
-tail -f server/runtime/log/$(date +%Y%m%d).log | grep -i gancao
-```
-
-在另一个终端调用接口:
-```bash
-curl -X POST https://your-domain.com/api/tcm.prescriptionOrder/submitGancaoRecipel \
- -H "Content-Type: application/json" \
- -H "token: your_token" \
- -d '{"id": 订单ID}'
-```
-
-### 步骤 7: 检查日志
-
-日志中应该看到:
-
-**成功的情况**:
-```
-[info] Gancao CTM_SUBMIT_RECIPEL payload: {...}
-[info] submitGancaoRecipel success: {...}
-```
-
-**失败的情况**:
-```
-[error] Gancao CTM_SUBMIT failed: {...}
-[error] submitGancaoRecipel exception: {...}
-```
-
-## 常见问题排查
-
-### 问题 1: 配置检查显示某个字段是数组
-
-**解决方案**:
-1. 编辑 `.env` 文件
-2. 找到该字段的配置行
-3. 移除 `[]` 和多余的引号
-4. 示例:
- ```env
- # 错误
- GANCAO_SCM_EXPRESS_TYPE=["sf"]
-
- # 正确
- GANCAO_SCM_EXPRESS_TYPE=sf
- ```
-5. 保存后清除缓存并重启服务
-
-### 问题 2: JSON 中包含 "Array" 字符串
-
-**解决方案**:
-1. 运行 `diagnose_gancao_payload.php` 找出是哪个字段
-2. 检查该字段在代码中的处理
-3. 确保使用 `(string)` 强制转换
-4. 检查配置文件中该字段的值
-
-### 问题 3: 仍然报 "Array to string conversion"
-
-**可能原因**:
-1. 缓存未清除
-2. 服务未重启
-3. 上传的文件未生效
-4. 配置文件有语法错误
-
-**排查步骤**:
-1. 确认文件已上传:`ls -la server/app/common/service/gancao/GancaoScmRecipelService.php`
-2. 检查文件修改时间:`stat server/app/common/service/gancao/GancaoScmRecipelService.php`
-3. 清除所有缓存:`rm -rf server/runtime/*`
-4. 重启所有服务
-5. 检查 PHP 错误日志:`tail -f /var/log/php-fpm/error.log`
-
-### 问题 4: 配置检查通过但接口仍然失败
-
-**排查步骤**:
-1. 检查日志中的完整错误信息
-2. 查看 `Gancao CTM_SUBMIT_RECIPEL payload` 日志,确认发送的数据
-3. 检查甘草 API 返回的错误信息
-4. 确认药材 ID 映射是否正确
-5. 确认处方数据是否完整
-
-## 验证修复成功
-
-修复成功的标志:
-
-1. ✅ `test_gancao_config.php` 全部通过
-2. ✅ `diagnose_gancao_payload.php` 全部通过
-3. ✅ 接口返回成功:
- ```json
- {
- "code": 1,
- "msg": "甘草药方上传成功",
- "data": {
- "recipel_order_no": "GC...",
- "app_order_no": "PO...",
- "fee": {...}
- }
- }
- ```
-4. ✅ 日志显示 `submitGancaoRecipel success`
-5. ✅ 数据库 `prescription_order` 表中 `gancao_reciperl_order_no` 字段已更新
-
-## 需要进一步帮助
-
-如果以上步骤都无法解决问题,请提供:
-
-1. **配置检查输出**:
- ```bash
- php test_gancao_config.php > config_check.txt 2>&1
- ```
-
-2. **负载诊断输出**:
- ```bash
- php diagnose_gancao_payload.php > payload_check.txt 2>&1
- ```
-
-3. **错误日志**:
- ```bash
- tail -100 server/runtime/log/$(date +%Y%m%d).log > error.log
- ```
-
-4. **接口响应**:
- ```bash
- curl -X POST ... > response.json 2>&1
- ```
-
-5. **`.env` 配置**(隐藏敏感信息):
- ```bash
- grep GANCAO_SCM .env > gancao_config.txt
- ```
-
-将这些文件发送给技术支持进行分析。
-
-## 总结
-
-这次修复主要解决了配置值类型错误的问题。关键点:
-
-1. **配置必须是正确的类型**:字符串字段不能是数组
-2. **`.env` 格式很重要**:不要使用 JSON 语法或数组语法
-3. **缓存必须清除**:修改配置后必须清除缓存
-4. **日志很重要**:添加了详细日志帮助排查问题
-
-希望这次修复能解决你的问题!
diff --git a/GANCAO_PREVIEW_FEATURE.md b/GANCAO_PREVIEW_FEATURE.md
deleted file mode 100644
index f8088222..00000000
--- a/GANCAO_PREVIEW_FEATURE.md
+++ /dev/null
@@ -1,212 +0,0 @@
-# 甘草预下单测试功能
-
-## 功能概述
-在订单编辑表单中添加"甘草预下单测试"功能,允许用户在不真实提交订单的情况下,测试当前配置的甘草预下单价格。测试结果以抽屉形式展示,包含完整的价格、配送、规则检查和药材明细信息。
-
-## 实现内容
-
-### 1. 后端接口
-
-#### 控制器方法 (`server/app/adminapi/controller/tcm/PrescriptionOrderController.php`)
-```php
-public function previewGancaoRecipel()
-```
-- 调用验证器场景:`previewGancaoRecipel`
-- 调用逻辑方法:`PrescriptionOrderLogic::previewGancaoRecipel()`
-- 返回预览结果(包含价格信息)
-
-#### 逻辑方法 (`server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`)
-```php
-public static function previewGancaoRecipel(int $id, int $adminId, array $adminInfo)
-```
-- 验证订单和处方存在性
-- 检查药材是否能匹配甘草药ID
-- 获取甘草token
-- 调用 `CTM_PREVIEW` 接口
-- 检查规则拦截和药材可用性
-- 返回完整的预览数据(不提交订单)
-
-#### 验证器 (`server/app/adminapi/validate/tcm/PrescriptionOrderValidate.php`)
-- 添加场景:`previewGancaoRecipel => ['id']`
-
-### 2. 前端实现
-
-#### API接口 (`admin/src/api/tcm.ts`)
-```typescript
-export function prescriptionOrderPreviewGancaoRecipel(params: { id: number })
-```
-
-#### UI组件 (`admin/src/views/consumer/prescription/order_list.vue`)
-
-**测试按钮位置**:在"剂数"和"剂量单位"字段后,"上次医生"字段前
-
-**组件结构**:
-```vue
-测试价格
-
-
-
-
-
-
-
-```
-
-### 3. 抽屉展示内容
-
-#### 价格信息卡片
-- **药材成本** (`m_cost`): 橙色显示,单位:元
-- **加工费** (`proces_cost`): 蓝色显示,单位:分转元
-- **物流费** (`lis_cost`): 绿色显示,单位:分转元
-- **总计**: 红色加粗显示,自动计算总和
-
-#### 配送信息卡片
-- 调配中心名称 (`ds_name`)
-- 调配中心ID (`ds_id`)
-- 服用天数范围 (`take_days.min` - `take_days.max`)
-- 默认服用天数 (`take_days.default`)
-
-#### 规则检查卡片
-- 显示所有规则检查结果 (`rule_check`)
-- 根据 `type` 显示不同颜色:
- - `type >= 2`: 错误(红色)
- - `type === 1`: 警告(黄色)
- - `type === 0`: 信息(蓝色)
-- 显示规则代码 (`code`) 和消息 (`msg`)
-
-#### 药材明细表格
-- 序号
-- 药材名称 (`title`)
-- 用量 (`quantity` + `unit`)
-- 单价 (`price`,元/克,保留4位小数)
-- 小计(单价 × 用量,橙色显示)
-- 库存状态 (`is_available`,1=有货/0=缺货)
-- 备注 (`brief`)
-
-#### 订单信息卡片
-- 订单号 (`order_no`):跨2列显示
-- 剂数 (`dose_count`):显示格式"X剂"(如:5剂)
-- 单剂量:自动计算所有药材用量总和,显示格式"X克"(如:196克)
-
-## 接口返回数据结构
-
-```json
-{
- "code": 1,
- "msg": "预下单成功",
- "data": {
- "success": true,
- "fee": {
- "m_cost": "112.88",
- "proces_cost": 80,
- "lis_cost": 0
- },
- "result": {
- "aa_id": 73,
- "ds_id": 135,
- "ds_name": "郑州-甘草调配中心√",
- "take_days": {
- "min": 7,
- "max": 35,
- "default": 28
- },
- "rule_check": [
- {
- "code": "pill_yield_notice",
- "msg": "出丸量约98g±5%",
- "type": 0
- }
- ],
- "fee": {
- "m_cost": "112.88",
- "proces_cost": 80,
- "lis_cost": 0
- },
- "m_list": [
- {
- "id": 420,
- "title": "龙胆",
- "price": 0.405028,
- "unit": "克",
- "quantity": "6",
- "is_available": 1,
- "brief": ""
- }
- ]
- },
- "order_no": "PO20260414160525788214",
- "dose_count": 5
- }
-}
-```
-
-## UI设计特点
-
-### 布局
-- 使用 `el-drawer` 抽屉组件,宽度 800px
-- 内容分为5个卡片区域,使用 `el-card` 组件
-- 使用 `el-descriptions` 展示键值对信息
-- 使用 `el-table` 展示药材明细列表
-
-### 颜色方案
-- 药材成本:橙色 (`text-orange-600`)
-- 加工费:蓝色 (`text-blue-600`)
-- 物流费:绿色 (`text-green-600`)
-- 总计:红色加粗 (`text-red-600 font-bold`)
-- 小计:橙色 (`text-orange-600`)
-
-### 交互
-- 点击"测试价格"按钮触发预下单
-- 按钮显示加载状态(loading)
-- 成功后自动打开抽屉展示结果
-- 失败时显示错误提示消息
-
-## 使用流程
-
-1. 打开订单编辑对话框
-2. 配置剂数(`dose_count`)和剂量单位(`dose_unit`)
-3. 点击"测试价格"按钮
-4. 系统调用甘草 `CTM_PREVIEW` 接口
-5. 自动打开抽屉展示完整的预下单结果
-
-## 注意事项
-
-- 预下单测试**不会**真实提交订单到甘草
-- 预下单测试**不会**扣费
-- 仅用于验证配置和查看价格
-- 需要先保存订单才能测试(`editForm.id` 必须存在)
-- 测试结果会检查:
- - 药材是否匹配甘草药ID
- - 规则是否拦截
- - 药材是否缺货
-- 抽屉关闭时自动销毁内容(`destroy-on-close`)
-
-## 相关文件
-
-### 后端
-- `server/app/adminapi/controller/tcm/PrescriptionOrderController.php`
-- `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
-- `server/app/adminapi/validate/tcm/PrescriptionOrderValidate.php`
-- `server/app/common/service/gancao/GancaoScmRecipelService.php`
-
-### 前端
-- `admin/src/api/tcm.ts`
-- `admin/src/views/consumer/prescription/order_list.vue`
-
-## 测试要点
-
-- [ ] 点击"测试价格"按钮能正常调用接口
-- [ ] 成功时自动打开抽屉展示结果
-- [ ] 价格信息显示正确(药材成本、加工费、物流费、总计)
-- [ ] 配送信息显示完整(调配中心、服用天数范围)
-- [ ] 规则检查按类型显示不同颜色
-- [ ] 药材明细表格显示完整(名称、用量、单价、小计、库存状态)
-- [ ] 订单信息显示正确(订单号、剂数)
-- [ ] 失败时显示清晰的错误信息
-- [ ] 按钮加载状态正常显示
-- [ ] 价格单位正确(分转元)
-- [ ] 仅在编辑已存在订单时显示测试按钮
-- [ ] 测试不会真实提交订单到甘草
-- [ ] 测试不会修改订单状态
-- [ ] 抽屉关闭后内容正确销毁
-
diff --git a/GANCAO_SSL_FIX.md b/GANCAO_SSL_FIX.md
deleted file mode 100644
index 1914c363..00000000
--- a/GANCAO_SSL_FIX.md
+++ /dev/null
@@ -1,284 +0,0 @@
-# 甘草 SSL 连接错误修复指南
-
-## 错误信息
-
-```
-甘草网关通信失败:通信失败:HTTP 200 curl#56 OpenSSL SSL_read: error:0A000126:SSL routines::unexpected eof while reading, errno 0
-```
-
-## 问题分析
-
-这个错误表示:
-- ✅ TCP 连接成功(HTTP 200)
-- ✅ SSL 握手开始
-- ❌ SSL 读取数据时连接意外断开
-
-**常见原因:**
-1. TLS 版本不兼容(服务器要求 TLS 1.2+,客户端使用旧版本)
-2. SSL 加密套件不匹配
-3. HTTP 版本问题(HTTP/1.0 vs HTTP/1.1)
-4. OpenSSL 安全级别过高
-5. 服务器端提前关闭连接
-
-## 已实施的修复
-
-### 修改 `GancaoOpenApiTransport.php`
-
-```php
-// 1. 改用 HTTP/1.1(原来是 HTTP/1.0)
-curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
-
-// 2. 增加连接超时
-curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); // 从 5 秒增加到 10 秒
-
-// 3. 完全禁用 SSL 验证
-curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); // 从 2 改为 0
-
-// 4. 强制使用 TLS 1.2
-curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
-
-// 5. 降低 OpenSSL 安全级别
-curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, 'DEFAULT@SECLEVEL=1');
-
-// 6. 添加 TCP keepalive
-curl_setopt($ch, CURLOPT_TCP_KEEPALIVE, 1);
-curl_setopt($ch, CURLOPT_TCP_KEEPIDLE, 120);
-curl_setopt($ch, CURLOPT_TCP_KEEPINTVL, 60);
-```
-
-## 测试步骤
-
-### 步骤 1: 上传修复后的文件
-
-```bash
-scp server/app/common/service/gancao/GancaoOpenApiTransport.php user@server:/path/to/server/app/common/service/gancao/
-scp test_gancao_ssl.php user@server:/path/to/
-```
-
-### 步骤 2: 运行 SSL 测试
-
-```bash
-ssh user@server
-cd /path/to/your/project
-php test_gancao_ssl.php
-```
-
-**期望输出:**
-```
-=== 甘草 SSL 连接测试 ===
-
-网关地址: https://xxx.com
-
-1. DNS 解析测试:
- ✓ xxx.com -> 1.2.3.4
-
-2. TCP 连接测试:
- ✓ TCP 连接成功
-
-3. SSL/TLS 支持检测:
- - SSLv2: ✗ 不支持
- - SSLv3: ✗ 不支持
- - TLS 1.0: ✓ 支持
- - TLS 1.1: ✓ 支持
- - TLS 1.2: ✓ 支持
- - TLS 1.3: ✓ 支持
-
-4. cURL SSL 测试:
- 测试 HTTP/1.0 + TLS 1.2:
- ❌ 失败: [56] OpenSSL SSL_read...
- 测试 HTTP/1.1 + TLS 1.2:
- ✓ 成功 (HTTP 200)
- 测试 HTTP/1.1 + TLS 1.2 + SECLEVEL=1:
- ✓ 成功 (HTTP 200)
-
-5. OpenSSL 版本信息:
- 版本: OpenSSL 1.1.1...
-
-6. cURL 版本信息:
- 版本: 7.x.x
- SSL 版本: OpenSSL/1.1.1...
-
-7. 测试实际 API 调用 (MAKE_TOKEN):
- ✓ 成功获取 token (长度: 64)
-```
-
-### 步骤 3: 清除缓存并重启
-
-```bash
-cd server
-php think clear
-rm -rf runtime/cache/*
-
-# 重启服务
-sudo systemctl restart php-fpm
-sudo systemctl restart nginx
-```
-
-### 步骤 4: 测试接口
-
-```bash
-# 查看日志
-tail -f server/runtime/log/$(date +%Y%m%d).log | grep -i gancao
-```
-
-在另一个终端调用接口:
-```bash
-curl -X POST https://your-domain.com/api/tcm.prescriptionOrder/submitGancaoRecipel \
- -H "Content-Type: application/json" \
- -H "token: your_token" \
- -d '{"id": 订单ID}'
-```
-
-## 如果仍然失败
-
-### 方案 A: 检查 OpenSSL 版本
-
-```bash
-openssl version
-```
-
-**要求:** OpenSSL 1.0.2 或更高版本
-
-**如果版本过低,升级 OpenSSL:**
-
-```bash
-# CentOS/RHEL
-sudo yum update openssl
-
-# Ubuntu/Debian
-sudo apt-get update
-sudo apt-get install --only-upgrade openssl
-
-# 重新编译 PHP(如果需要)
-```
-
-### 方案 B: 修改 OpenSSL 配置
-
-编辑 `/etc/ssl/openssl.cnf`:
-
-```ini
-# 在文件开头添加
-openssl_conf = openssl_init
-
-[openssl_init]
-ssl_conf = ssl_sect
-
-[ssl_sect]
-system_default = system_default_sect
-
-[system_default_sect]
-MinProtocol = TLSv1.2
-CipherString = DEFAULT@SECLEVEL=1
-```
-
-重启服务:
-```bash
-sudo systemctl restart php-fpm
-```
-
-### 方案 C: 使用不同的 TLS 版本
-
-如果 TLS 1.2 不工作,尝试其他版本。
-
-修改 `GancaoOpenApiTransport.php`:
-
-```php
-// 尝试 TLS 1.3
-curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_3);
-
-// 或者让 cURL 自动选择
-curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_DEFAULT);
-```
-
-### 方案 D: 禁用 HTTP/2
-
-如果使用了 HTTP/2,尝试禁用:
-
-```php
-curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
-```
-
-### 方案 E: 增加调试信息
-
-临时添加详细的 cURL 调试:
-
-```php
-// 在 GancaoOpenApiTransport.php 的 post 方法中添加
-curl_setopt($ch, CURLOPT_VERBOSE, true);
-$verbose = fopen('php://temp', 'w+');
-curl_setopt($ch, CURLOPT_STDERR, $verbose);
-
-// 在 curl_exec 之后添加
-rewind($verbose);
-$verboseLog = stream_get_contents($verbose);
-\think\facade\Log::info('cURL verbose output', ['log' => $verboseLog]);
-```
-
-查看详细日志:
-```bash
-tail -f server/runtime/log/$(date +%Y%m%d).log
-```
-
-### 方案 F: 联系甘草技术支持
-
-提供以下信息:
-1. `php test_gancao_ssl.php` 的完整输出
-2. `openssl version` 输出
-3. `php -v` 输出
-4. `curl --version` 输出
-5. 服务器操作系统版本
-
-询问:
-- 甘草网关支持的 TLS 版本
-- 推荐的 SSL 加密套件
-- 是否有特殊的 HTTP 头要求
-- 是否有 IP 白名单限制
-
-## 常见问题
-
-### Q1: 为什么 HTTP/1.0 会导致问题?
-
-A: 某些现代服务器不再支持 HTTP/1.0,或者在 HTTP/1.0 下有不同的 SSL 处理逻辑。HTTP/1.1 是更标准的选择。
-
-### Q2: SECLEVEL=1 是什么?
-
-A: OpenSSL 1.1.0+ 引入了安全级别概念:
-- SECLEVEL=2(默认):要求 112 位安全性
-- SECLEVEL=1:要求 80 位安全性(更宽松)
-- SECLEVEL=0:允许所有加密套件(不推荐)
-
-降低安全级别可以兼容更多服务器,但会降低安全性。
-
-### Q3: 为什么要禁用 SSL 验证?
-
-A: 在开发/测试环境中,禁用 SSL 验证可以避免证书问题。**生产环境建议启用验证**。
-
-### Q4: TCP keepalive 有什么用?
-
-A: 保持 TCP 连接活跃,防止长时间传输时连接被中断。
-
-## 验证修复成功
-
-修复成功的标志:
-
-1. ✅ `php test_gancao_ssl.php` 显示 "✓ 成功获取 token"
-2. ✅ 接口返回成功:
- ```json
- {
- "code": 1,
- "msg": "甘草药方上传成功",
- "data": {...}
- }
- ```
-3. ✅ 日志中没有 SSL 错误
-4. ✅ 数据库中订单已更新
-
-## 总结
-
-SSL 连接问题通常是由于:
-1. **TLS 版本不匹配** → 使用 TLS 1.2
-2. **HTTP 版本问题** → 使用 HTTP/1.1
-3. **OpenSSL 安全级别过高** → 降低到 SECLEVEL=1
-4. **连接超时** → 增加超时时间
-
-修复后应该能正常连接甘草 API。如果仍有问题,运行 `test_gancao_ssl.php` 并根据输出进一步诊断。
diff --git a/GANCAO_UPLOAD_BUTTON_CONDITION.md b/GANCAO_UPLOAD_BUTTON_CONDITION.md
deleted file mode 100644
index c760cf10..00000000
--- a/GANCAO_UPLOAD_BUTTON_CONDITION.md
+++ /dev/null
@@ -1,169 +0,0 @@
-# 甘草上传药方按钮显示条件
-
-## 需求
-"上传药方"按钮应该只在特定条件下显示:
-1. 处方审核已通过
-2. 还没有上传过甘草订单(gancao_reciperl_order_no 为空)
-
-## 实现
-
-### 文件:`admin/src/views/consumer/prescription/order_list.vue`
-
-#### 1. 添加条件判断函数
-
-```typescript
-function canUploadGancaoRow(row: {
- prescription_audit_status?: number
- gancao_reciperl_order_no?: string
-}) {
- // 处方审核已通过(1) 且没有甘草订单号时才显示
- const rxStatus = Number(row.prescription_audit_status)
- const gancaoOrderNo = String(row.gancao_reciperl_order_no || '').trim()
- return rxStatus === 1 && gancaoOrderNo === ''
-}
-```
-
-**逻辑说明**:
-- `prescription_audit_status === 1`:处方审核状态为"已通过"
-- `gancao_reciperl_order_no` 为空:还没有上传过甘草订单
-
-#### 2. 更新按钮显示条件
-
-```vue
-上传药方
-```
-
-## 显示逻辑
-
-### 场景 1:处方未审核
-```
-prescription_audit_status: 0 (待审核)
-gancao_reciperl_order_no: ''
-→ 按钮隐藏 ❌
-```
-
-### 场景 2:处方已驳回
-```
-prescription_audit_status: 2 (已驳回)
-gancao_reciperl_order_no: ''
-→ 按钮隐藏 ❌
-```
-
-### 场景 3:处方已通过,未上传
-```
-prescription_audit_status: 1 (已通过)
-gancao_reciperl_order_no: ''
-→ 按钮显示 ✅
-```
-
-### 场景 4:处方已通过,已上传
-```
-prescription_audit_status: 1 (已通过)
-gancao_reciperl_order_no: 'GC202604150001'
-→ 按钮隐藏 ❌
-```
-
-## 审核状态说明
-
-| 状态值 | 说明 | 是否显示按钮 |
-|--------|------|-------------|
-| 0 | 待审核 | ❌ |
-| 1 | 已通过 | ✅ (如果未上传) |
-| 2 | 已驳回 | ❌ |
-
-## 业务流程
-
-```
-创建订单
- ↓
-处方待审核 (prescription_audit_status = 0)
- ↓ 审核
-处方已通过 (prescription_audit_status = 1)
- ↓ 显示"上传药方"按钮
-点击上传
- ↓ 调用甘草API
-上传成功,保存订单号 (gancao_reciperl_order_no = 'GC...')
- ↓ 按钮隐藏
-订单继续履约流程
-```
-
-## 相关字段
-
-### prescription_audit_status (处方审核状态)
-- **类型**: tinyint(2)
-- **值**:
- - 0: 待审核
- - 1: 已通过
- - 2: 已驳回
-
-### gancao_reciperl_order_no (甘草处方订单号)
-- **类型**: varchar(32)
-- **说明**: 上传甘草成功后返回的订单号
-- **示例**: `GC202604150001`
-- **空值**: 表示还未上传
-
-## 防止重复上传
-
-通过检查 `gancao_reciperl_order_no` 字段,确保:
-1. 每个订单只能上传一次
-2. 上传成功后按钮自动隐藏
-3. 避免重复提交导致的数据混乱
-
-## 用户体验
-
-### 上传前
-```
-操作列:
-[详情/审核] [编辑] [上传药方] [确认发货] ...
-```
-
-### 上传后
-```
-操作列:
-[详情/审核] [编辑] [确认发货] ...
-```
-"上传药方"按钮消失,界面更简洁。
-
-## 错误处理
-
-如果用户尝试重复上传(通过其他方式),后端也有验证:
-
-```php
-// server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php
-if (!self::canUploadGancaoRecipel($item, $adminId, $adminInfo, $assistantByDiag)) {
- self::$error = '须处方审核通过后方可上传甘草;已完成/已取消订单不可上传;同一订单仅可上传一次';
- return false;
-}
-```
-
-## 测试清单
-
-- [ ] 创建新订单,处方待审核,按钮不显示
-- [ ] 处方审核通过,按钮显示
-- [ ] 点击上传药方,上传成功
-- [ ] 刷新页面,按钮消失
-- [ ] 处方被驳回,按钮不显示
-- [ ] 已上传的订单,按钮不显示
-
-## 相关文件
-
-- `admin/src/views/consumer/prescription/order_list.vue` - 订单列表页面
-- `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php` - 后端业务逻辑
-- `GANCAO_CALLBACK_SETUP.md` - 甘草回调功能文档
-
-## 总结
-
-通过添加 `canUploadGancaoRow()` 函数,实现了智能的按钮显示控制:
-- ✅ 只在处方审核通过后显示
-- ✅ 上传成功后自动隐藏
-- ✅ 防止重复上传
-- ✅ 提升用户体验
-
-这样可以避免用户误操作,确保业务流程的正确性。
diff --git a/IM_CHAT_MESSAGES_PERFORMANCE_FIX.md b/IM_CHAT_MESSAGES_PERFORMANCE_FIX.md
deleted file mode 100644
index ce46431d..00000000
--- a/IM_CHAT_MESSAGES_PERFORMANCE_FIX.md
+++ /dev/null
@@ -1,255 +0,0 @@
-# IM 聊天记录接口性能优化
-
-## 问题描述
-
-接口 `tcm.diagnosis/getImChatMessages?diagnosis_id=377` 响应超时(30秒),错误信息:
-```
-Maximum execution time of 30 seconds exceeded
-```
-
-错误发生在 `TencentImService.php` 第 346 行的 `curl_exec($ch)` 调用。
-
-## 根本原因
-
-1. **查询范围过大**
- - 原代码调用 `collectAllDoctorImPeerAccounts()` 获取所有医生和医助账号
- - 如果系统有 50+ 个医生/医助,就需要对每个账号调用腾讯云 IM API
- - 每个账号可能需要多次分页请求(每次最多 100 条消息)
-
-2. **API 调用次数过多**
- - 假设有 50 个医生,每个医生有 300 条消息
- - 总 API 调用次数 = 50 × (300/100) = 150 次
- - 每次调用耗时 0.5-1 秒,总耗时 75-150 秒
-
-3. **超时设置不合理**
- - PHP 默认执行时间限制:30 秒
- - 单个 HTTP 请求超时:30 秒
- - 实际需要的时间远超这些限制
-
-## 优化方案
-
-### 1. 只查询指派的医助(核心优化)
-
-修改 `getImChatMessagesForDiagnosis` 方法:
-
-**优化前:**
-```php
-// 查询所有医生/医助账号(可能有几十个)
-$doctorAccounts = self::collectAllDoctorImPeerAccounts();
-$assistantId = isset($diag['assistant_id']) ? (int)$diag['assistant_id'] : 0;
-if ($assistantId > 0) {
- $doctorAccounts[] = 'doctor_' . $assistantId;
-}
-```
-
-**优化后:**
-```php
-// 只查询指派给该诊单的医助
-$doctorAccounts = [];
-$assistantId = isset($diag['assistant_id']) ? (int)$diag['assistant_id'] : 0;
-if ($assistantId > 0) {
- $doctorAccounts[] = 'doctor_' . $assistantId;
-}
-
-// 如果没有指派医助,直接返回归档记录
-if (empty($doctorAccounts)) {
- $lists = self::enrichImMessagesWithStaffNames($archived);
- return [
- 'lists' => $lists,
- 'patient_im_id' => $patientImId,
- 'patient_name' => $diag['patient_name'] ?? '',
- 'doctor_accounts_queried' => [],
- ];
-}
-```
-
-**效果:**
-- API 调用次数从 150 次降低到 3-5 次
-- 响应时间从 75-150 秒降低到 2-5 秒
-
-### 2. 增加 HTTP 请求超时时间
-
-修改 `TencentImService::adminGetRoamMsg` 方法:
-
-```php
-// 从 30 秒增加到 60 秒
-$result = $this->httpPost($url, json_encode($data), 60);
-```
-
-### 3. 增加 PHP 执行时间限制
-
-修改 `DiagnosisController::getImChatMessages` 方法:
-
-```php
-public function getImChatMessages()
-{
- // 增加执行时间限制到 120 秒
- set_time_limit(120);
-
- // ... 其他代码
-}
-```
-
-### 4. 新增优化版拉取方法
-
-添加 `pullLiveImChatMessagesForDiagnosisOptimized` 方法:
-
-```php
-/**
- * 优化版:只拉取指定医生账号的消息(避免查询所有医生导致超时)
- */
-private static function pullLiveImChatMessagesForDiagnosisOptimized($diag, array $doctorAccounts): array
-{
- // 只查询指定的医生账号,大大减少API调用次数
- // ...
-}
-```
-
-## 性能对比
-
-| 指标 | 优化前 | 优化后 | 改善 |
-|------|--------|--------|------|
-| 查询的医生账号数 | 50+ | 1 | 98% ↓ |
-| API 调用次数 | 150+ | 3-5 | 97% ↓ |
-| 响应时间 | 75-150秒 | 2-5秒 | 95% ↓ |
-| 超时风险 | 高 | 低 | - |
-
-## 业务逻辑说明
-
-### 为什么只查询指派的医助?
-
-1. **业务场景**
- - 每个诊单都会指派一个医助(`assistant_id`)
- - 患者主要与指派的医助沟通
- - 其他医生/医助不会与该患者聊天
-
-2. **数据准确性**
- - 只显示与该诊单相关的聊天记录
- - 避免显示无关的聊天记录
- - 提高数据查询的精准度
-
-3. **归档机制**
- - 系统有定时任务将 IM 消息归档到本地数据库
- - 归档数据突破腾讯云 7 天限制
- - 即使不查询所有医生,历史记录也不会丢失
-
-### 如果需要查询所有医生怎么办?
-
-如果确实需要查询所有医生的聊天记录(例如管理员查看),可以:
-
-1. **使用定时任务归档**
- ```bash
- php think tcm:sync-im-chat-archive --days=7 --limit=100
- ```
-
-2. **异步查询**
- - 将查询任务放入队列
- - 后台慢慢处理
- - 完成后通知用户
-
-3. **分页查询**
- - 前端分批请求不同医生的记录
- - 每次只查询 1-2 个医生
- - 避免单次请求超时
-
-## 测试建议
-
-1. **测试正常场景**
- ```
- GET /adminapi/tcm.diagnosis/getImChatMessages?diagnosis_id=377
- ```
- - 应该在 5 秒内返回
- - 返回指派医助的聊天记录
-
-2. **测试无指派医助场景**
- - 创建一个未指派医助的诊单
- - 应该返回归档记录(如果有)
- - 不应该报错
-
-3. **测试大量消息场景**
- - 找一个有 500+ 条消息的诊单
- - 应该能正常返回
- - 响应时间应该在 10 秒内
-
-## 后续优化建议
-
-### 1. 添加缓存
-
-```php
-public static function getImChatMessagesForDiagnosis(int $diagnosisId)
-{
- // 缓存 5 分钟
- $cacheKey = 'im_chat_messages_' . $diagnosisId;
- $cached = Cache::get($cacheKey);
- if ($cached) {
- return $cached;
- }
-
- // ... 查询逻辑
-
- Cache::set($cacheKey, $result, 300);
- return $result;
-}
-```
-
-### 2. 使用 Redis 队列
-
-```php
-// 将耗时的查询放入队列
-Queue::push(SyncImChatMessagesJob::class, [
- 'diagnosis_id' => $diagnosisId
-]);
-
-// 前端轮询查询结果
-```
-
-### 3. WebSocket 实时推送
-
-```php
-// 使用 WebSocket 实时推送新消息
-// 避免每次都查询腾讯云 API
-```
-
-### 4. 增量同步
-
-```php
-// 只同步最近 1 小时的新消息
-// 历史消息从归档表读取
-$minTime = time() - 3600;
-$result = $svc->adminGetRoamMsg($operator, $peer, 100, $minTime);
-```
-
-## 相关文件
-
-- `server/app/adminapi/logic/tcm/DiagnosisLogic.php` - 主要逻辑
-- `server/app/common/service/TencentImService.php` - IM 服务
-- `server/app/adminapi/controller/tcm/DiagnosisController.php` - 控制器
-
-## 监控建议
-
-1. **添加性能日志**
- ```php
- $startTime = microtime(true);
- // ... 查询逻辑
- $duration = microtime(true) - $startTime;
- Log::info('IM消息查询耗时', [
- 'diagnosis_id' => $diagnosisId,
- 'duration' => $duration,
- 'doctor_accounts' => count($doctorAccounts),
- 'message_count' => count($merged),
- ]);
- ```
-
-2. **设置告警**
- - 响应时间 > 10 秒:警告
- - 响应时间 > 30 秒:严重
- - API 调用失败率 > 5%:严重
-
-3. **定期检查**
- - 每周检查平均响应时间
- - 每月检查 API 调用次数
- - 及时发现性能退化
-
----
-
-最后更新:2024-04-11
diff --git a/INSTALLATION_CHECKLIST.md b/INSTALLATION_CHECKLIST.md
deleted file mode 100644
index d1ca2d98..00000000
--- a/INSTALLATION_CHECKLIST.md
+++ /dev/null
@@ -1,361 +0,0 @@
-# 处方库功能 - 安装检查清单
-
-## 📋 安装前检查
-
-### 环境要求
-- [ ] PHP >= 7.4
-- [ ] MySQL >= 5.7
-- [ ] Node.js >= 14.x(前端开发)
-- [ ] 已安装并运行 ThinkPHP 框架
-- [ ] 已安装并运行 Vue 3 前端项目
-
-### 权限检查
-- [ ] 数据库创建表权限
-- [ ] 文件读写权限
-- [ ] 后台管理员账号
-
-## 🔧 安装步骤
-
-### 步骤1:备份数据库 ✅
-```bash
-# 备份当前数据库(推荐)
-mysqldump -u用户名 -p密码 数据库名 > backup_$(date +%Y%m%d).sql
-```
-- [ ] 已完成数据库备份
-
-### 步骤2:安装数据库表 ✅
-
-#### 方式A:自动安装(推荐)
-
-**Windows:**
-```bash
-双击运行 install_prescription_library.bat
-```
-
-**Linux/Mac:**
-```bash
-chmod +x install_prescription_library.sh
-./install_prescription_library.sh
-```
-
-#### 方式B:手动安装
-```bash
-mysql -h主机 -u用户名 -p密码 数据库名 < server/database/migrations/create_prescription_library.sql
-```
-
-- [ ] 数据库表安装成功
-- [ ] 验证表是否创建:`SHOW TABLES LIKE '%prescription_library%';`
-
-### 步骤3:验证后端文件 ✅
-
-检查以下文件是否存在:
-
-**模型层:**
-- [ ] server/app/common/model/tcm/PrescriptionLibrary.php
-
-**控制器层:**
-- [ ] server/app/adminapi/controller/tcm/PrescriptionLibraryController.php
-
-**业务逻辑层:**
-- [ ] server/app/adminapi/logic/tcm/PrescriptionLibraryLogic.php
-
-**列表逻辑层:**
-- [ ] server/app/adminapi/lists/tcm/PrescriptionLibraryLists.php
-
-**验证器层:**
-- [ ] server/app/adminapi/validate/tcm/PrescriptionLibraryValidate.php
-
-### 步骤4:验证前端文件 ✅
-
-检查以下文件是否存在:
-
-**页面组件:**
-- [ ] admin/src/views/consumer/prescription/list.vue
-
-**API接口:**
-- [ ] admin/src/api/tcm.ts(已更新)
-
-### 步骤5:配置路由(如需要) ✅
-
-ThinkPHP 通常自动识别路由,无需手动配置。
-
-如需手动配置,在路由文件中添加:
-```php
-// 处方库路由
-Route::group('tcm.prescriptionLibrary', function () {
- Route::get('lists', 'tcm.PrescriptionLibrary/lists');
- Route::post('add', 'tcm.PrescriptionLibrary/add');
- Route::post('edit', 'tcm.PrescriptionLibrary/edit');
- Route::post('delete', 'tcm.PrescriptionLibrary/delete');
- Route::get('detail', 'tcm.PrescriptionLibrary/detail');
-});
-```
-
-- [ ] 路由配置完成(如需要)
-
-### 步骤6:配置菜单权限 ✅
-
-在后台管理系统中添加菜单项:
-
-**菜单配置示例:**
-```
-菜单名称:处方库
-菜单路径:/consumer/prescription/list
-图标:el-icon-document
-排序:100
-权限标识:tcm.prescriptionLibrary/lists
-```
-
-**权限节点:**
-- [ ] tcm.prescriptionLibrary/lists(查看列表)
-- [ ] tcm.prescriptionLibrary/add(添加处方)
-- [ ] tcm.prescriptionLibrary/edit(编辑处方)
-- [ ] tcm.prescriptionLibrary/delete(删除处方)
-- [ ] tcm.prescriptionLibrary/detail(查看详情)
-
-- [ ] 菜单配置完成
-- [ ] 权限节点配置完成
-
-### 步骤7:重启服务(如需要) ✅
-
-**后端:**
-```bash
-# 清除缓存
-php think clear
-
-# 重启PHP-FPM(如使用)
-sudo systemctl restart php-fpm
-```
-
-**前端:**
-```bash
-# 开发环境
-npm run dev
-
-# 生产环境
-npm run build
-```
-
-- [ ] 后端服务已重启
-- [ ] 前端已重新编译
-
-## 🧪 功能测试
-
-### 测试1:访问页面 ✅
-- [ ] 能够访问处方库页面
-- [ ] 页面正常显示,无报错
-- [ ] 列表为空或显示数据
-
-### 测试2:创建处方 ✅
-- [ ] 点击"新增处方"按钮
-- [ ] 填写处方名称
-- [ ] 添加药材
-- [ ] 设置是否公开
-- [ ] 保存成功
-
-### 测试3:查看处方 ✅
-- [ ] 点击"查看"按钮
-- [ ] 显示处方详情
-- [ ] 字段不可编辑
-
-### 测试4:编辑处方 ✅
-- [ ] 点击"编辑"按钮
-- [ ] 修改处方信息
-- [ ] 保存成功
-
-### 测试5:删除处方 ✅
-- [ ] 点击"删除"按钮
-- [ ] 确认删除
-- [ ] 删除成功
-
-### 测试6:搜索功能 ✅
-- [ ] 输入处方名称搜索
-- [ ] 选择是否公开筛选
-- [ ] 显示正确结果
-
-### 测试7:权限测试 ✅
-- [ ] 用户A创建私有处方
-- [ ] 用户B无法查看用户A的私有处方
-- [ ] 用户A创建公开处方
-- [ ] 用户B可以查看用户A的公开处方
-- [ ] 用户B无法删除用户A的处方
-
-## 🔍 问题排查
-
-### 问题1:无法访问页面
-**可能原因:**
-- 路由未配置
-- 权限未分配
-- 前端路由错误
-
-**解决方案:**
-1. 检查路由配置
-2. 检查菜单权限
-3. 查看浏览器控制台错误
-
-### 问题2:数据库表创建失败
-**可能原因:**
-- 数据库连接失败
-- 权限不足
-- 表已存在
-
-**解决方案:**
-1. 检查数据库配置
-2. 检查用户权限
-3. 删除已存在的表后重试
-
-### 问题3:API请求失败
-**可能原因:**
-- 后端文件未上传
-- 路由配置错误
-- 权限验证失败
-
-**解决方案:**
-1. 检查后端文件是否存在
-2. 检查API路径是否正确
-3. 查看后端日志
-
-### 问题4:前端页面报错
-**可能原因:**
-- 前端文件未编译
-- API接口路径错误
-- 组件引用错误
-
-**解决方案:**
-1. 重新编译前端代码
-2. 检查API接口配置
-3. 查看浏览器控制台错误
-
-## 📊 验证清单
-
-### 数据库验证
-```sql
--- 检查表是否存在
-SHOW TABLES LIKE '%prescription_library%';
-
--- 检查表结构
-DESC zyt_prescription_library;
-
--- 检查索引
-SHOW INDEX FROM zyt_prescription_library;
-
--- 插入测试数据
-INSERT INTO `zyt_prescription_library`
-(`prescription_name`, `herbs`, `is_public`, `creator_id`, `creator_name`, `create_time`, `update_time`)
-VALUES
-('测试处方', '[{"name":"测试药材","dosage":10}]', 1, 1, '测试医生', UNIX_TIMESTAMP(), UNIX_TIMESTAMP());
-
--- 查询测试数据
-SELECT * FROM zyt_prescription_library WHERE prescription_name = '测试处方';
-
--- 删除测试数据
-DELETE FROM zyt_prescription_library WHERE prescription_name = '测试处方';
-```
-
-- [ ] 表结构正确
-- [ ] 索引创建成功
-- [ ] 数据插入成功
-- [ ] 数据查询成功
-- [ ] 数据删除成功
-
-### API验证
-使用 Postman 或类似工具测试:
-
-**获取列表:**
-```
-GET /api/tcm.prescriptionLibrary/lists
-```
-
-**添加处方:**
-```
-POST /api/tcm.prescriptionLibrary/add
-Body: {
- "prescription_name": "测试处方",
- "herbs": [{"name": "测试药材", "dosage": 10}],
- "is_public": 1
-}
-```
-
-**编辑处方:**
-```
-POST /api/tcm.prescriptionLibrary/edit
-Body: {
- "id": 1,
- "prescription_name": "测试处方(已修改)",
- "herbs": [{"name": "测试药材", "dosage": 15}],
- "is_public": 0
-}
-```
-
-**删除处方:**
-```
-POST /api/tcm.prescriptionLibrary/delete
-Body: {
- "id": 1
-}
-```
-
-**获取详情:**
-```
-GET /api/tcm.prescriptionLibrary/detail?id=1
-```
-
-- [ ] 列表接口正常
-- [ ] 添加接口正常
-- [ ] 编辑接口正常
-- [ ] 删除接口正常
-- [ ] 详情接口正常
-
-## ✅ 安装完成确认
-
-### 最终检查
-- [ ] 所有文件已上传
-- [ ] 数据库表已创建
-- [ ] 菜单权限已配置
-- [ ] 功能测试通过
-- [ ] API测试通过
-- [ ] 权限测试通过
-
-### 文档确认
-- [ ] 已阅读 PRESCRIPTION_LIBRARY_README.md
-- [ ] 已阅读 QUICK_START_PRESCRIPTION_LIBRARY.md
-- [ ] 已了解功能使用方法
-
-### 备份确认
-- [ ] 已备份数据库
-- [ ] 已备份原有代码
-- [ ] 已记录安装日期和版本
-
-## 📝 安装记录
-
-**安装日期:** _______________
-
-**安装人员:** _______________
-
-**版本号:** v1.0.0
-
-**数据库名:** _______________
-
-**备注:**
-```
-_______________________________________________
-_______________________________________________
-_______________________________________________
-```
-
-## 🎉 安装成功!
-
-恭喜!处方库功能已成功安装。
-
-**下一步:**
-1. 创建第一个处方
-2. 测试各项功能
-3. 培训使用人员
-4. 收集反馈意见
-
-**技术支持:**
-如有问题,请查看文档或联系技术支持团队。
-
----
-
-**祝使用愉快!** 🚀
diff --git a/INTERNAL_COST_PERMISSION_CONTROL.md b/INTERNAL_COST_PERMISSION_CONTROL.md
deleted file mode 100644
index 6ac24780..00000000
--- a/INTERNAL_COST_PERMISSION_CONTROL.md
+++ /dev/null
@@ -1,142 +0,0 @@
-# 内部成本字段权限控制
-
-## 需求说明
-
-在业务订单列表中添加内部成本字段的显示,但只有指定角色的用户才能看到该字段。
-
-## 权限配置
-
-文件:`server/config/project.php`
-
-```php
-// 处方业务订单 internal_cost 等财务字段:以下角色 ID + root 可见
-'prescription_order_finance_roles' => [0, 3, 6],
-```
-
-- 角色 0:超级管理员
-- 角色 3:管理员
-- 角色 6:药师
-
-## 后端权限控制
-
-文件:`server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
-
-后端已经实现了权限控制逻辑:
-
-```php
-public static function canViewInternalCost(array $adminInfo): bool
-{
- if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) {
- return true;
- }
- $allow = Config::get('project.prescription_order_finance_roles', [0, 3]);
- $allow = array_map('intval', is_array($allow) ? $allow : []);
- $myRoles = array_map('intval', $adminInfo['role_id'] ?? []);
-
- return count(array_intersect($myRoles, $allow)) > 0;
-}
-
-public static function maskInternalCostIfNeeded(array &$row, array $adminInfo): void
-{
- if (!self::canViewInternalCost($adminInfo)) {
- unset($row['internal_cost']);
- }
-}
-```
-
-后端在返回数据前会自动移除 `internal_cost` 字段,如果用户没有权限。
-
-## 前端权限控制
-
-文件:`admin/src/views/consumer/prescription/order_list.vue`
-
-### 1. 添加权限检查函数
-
-```typescript
-import useUserStore from '@/stores/modules/user'
-
-const userStore = useUserStore()
-
-/** 与 server/config/project.php prescription_order_finance_roles 保持一致 */
-const FINANCE_ROLE_IDS = [0, 3, 6]
-
-/** 判断当前用户是否有查看财务字段(内部成本)的权限 */
-const canViewFinanceFields = () => {
- const u = userStore.userInfo
- if (!u) return false
- if (Number(u.root) === 1) return true
- const ids = Array.isArray(u.role_ids) ? u.role_ids.map((n: unknown) => Number(n)) : []
- return FINANCE_ROLE_IDS.some((rid) => ids.includes(rid))
-}
-```
-
-### 2. 在列表中添加权限控制
-
-**表格列(只有有权限的用户才能看到整列):**
-
-```vue
-
-
- {{ row.internal_cost != null && row.internal_cost !== '' ? `¥${row.internal_cost}` : '—' }}
-
-
-```
-
-**金额信息弹出框(只有有权限的用户才能看到内部成本行):**
-
-```vue
-
- 内部成本:
- ¥{{ formatMoney(row.internal_cost) }}
-
-```
-
-## 权限控制层级
-
-### 后端(第一层防护)
-
-1. 在 `PrescriptionOrderLogic::detail()` 和列表方法中调用 `maskInternalCostIfNeeded()`
-2. 如果用户没有权限,直接从返回数据中移除 `internal_cost` 字段
-3. 前端收到的数据中不包含该字段
-
-### 前端(第二层防护)
-
-1. 使用 `v-if="canViewFinanceFields()"` 控制整个表格列的显示
-2. 在弹出框中也使用 `canViewFinanceFields()` 控制内部成本行的显示
-3. 即使后端数据泄露,前端也不会显示
-
-## 双重防护的优势
-
-1. **后端防护**:确保数据安全,无权限用户无法获取敏感数据
-2. **前端防护**:优化用户体验,无权限用户不会看到空白或无意义的列
-3. **配置同步**:前端 `FINANCE_ROLE_IDS` 与后端配置保持一致
-
-## 测试验证
-
-### 有权限的用户(角色 0, 3, 6)
-
-1. 登录系统
-2. 进入"消费者处方订单"列表
-3. 可以看到"内部成本"列
-4. 点击金额信息图标,弹出框中显示内部成本
-
-### 无权限的用户(其他角色)
-
-1. 登录系统
-2. 进入"消费者处方订单"列表
-3. 看不到"内部成本"列
-4. 点击金额信息图标,弹出框中不显示内部成本
-
-## 注意事项
-
-1. **配置同步**:修改后端配置后,需要同步修改前端 `FINANCE_ROLE_IDS` 常量
-2. **缓存清除**:修改配置后需要运行 `php think clear` 清除缓存
-3. **角色分配**:确保用户的角色ID正确分配在数据库中
-4. **超级管理员**:`root=1` 的用户始终有权限查看所有字段
-
-## 相关文件
-
-- `server/config/project.php` - 权限配置
-- `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php` - 后端权限逻辑
-- `admin/src/views/consumer/prescription/order_list.vue` - 前端显示控制
-- `admin/src/stores/modules/user.ts` - 用户信息存储
diff --git a/JTEXPRESS_SUPPORT_ADDED.md b/JTEXPRESS_SUPPORT_ADDED.md
deleted file mode 100644
index bf4ebcde..00000000
--- a/JTEXPRESS_SUPPORT_ADDED.md
+++ /dev/null
@@ -1,251 +0,0 @@
-# 极兔速递支持已添加
-
-## 更新说明
-
-系统已添加极兔速递(J&T Express)的物流查询支持。
-
-### 单号示例
-- `JT5472795424064` - 极兔速递单号格式:JT + 13位数字
-
----
-
-## 更新内容
-
-### 1. 后端更新
-
-**文件:** `server/app/common/service/ExpressTrackService.php`
-
-- 添加极兔速递常量 `KUAIDI_COM_JT = 'jtexpress'`
-- 添加极兔单号识别规则:`/^JT\d{13}$/i`
-- 添加极兔官网查询链接:`https://www.jtexpress.com.cn/index/query/gzquery.html?bills={单号}`
-- 更新 `resolveCarrier()` 方法支持极兔识别
-- 更新 `officialUrls()` 方法返回极兔链接
-
-**文件:** `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
-
-- 更新 `normalizeExpressCompany()` 方法支持 `jt` 和 `jtexpress`
-
-### 2. 前端更新
-
-**文件:** `admin/src/views/consumer/prescription/order_list.vue`
-
-- 添加极兔速递选项到快递公司下拉框
-- 添加"极兔速递查件"官网链接按钮
-- 更新 `expressCompanyLabel()` 函数支持极兔显示
-- 更新 `detailOfficialUrls` 计算属性包含极兔链接
-- 更新提示文本说明支持极兔速递
-
----
-
-## 使用方法
-
-### 方式一:自动识别(推荐)
-
-系统会自动识别以 `JT` 开头且后跟13位数字的单号为极兔速递。
-
-**示例:**
-- 单号:`JT5472795424064`
-- 快递公司:选择"自动识别"
-- 系统自动识别为:极兔速递
-
-### 方式二:手动选择
-
-在订单编辑或查询时,手动选择"极兔速递"。
-
-**步骤:**
-1. 打开业务订单详情
-2. 在"快递公司"下拉框中选择"极兔速递"
-3. 填写快递单号
-4. 点击"查询物流"
-
----
-
-## 快递100 API 支持
-
-极兔速递在快递100中的编码为 `jtexpress`,系统已配置。
-
-### 查询参数示例
-
-```json
-{
- "com": "jtexpress",
- "num": "JT5472795424064",
- "resultv2": "1"
-}
-```
-
-### 注意事项
-
-1. **快递100配置**
- - 确保已配置 `LOGISTICS_KUAIDI100_CUSTOMER` 和 `LOGISTICS_KUAIDI100_KEY`
- - 确保 `LOGISTICS_KUAIDI100_DISABLE = false`
-
-2. **单号格式**
- - 极兔单号通常为:JT + 13位数字
- - 示例:`JT5472795424064`
-
-3. **查询时机**
- - 快递已揽收后才能查询到物流信息
- - 刚下单的快递可能需要等待1-2小时
-
----
-
-## 官网查询
-
-如果快递100查询失败,可以使用官网链接:
-
-**极兔速递官网:** https://www.jtexpress.com.cn/
-
-**查询链接格式:**
-```
-https://www.jtexpress.com.cn/index/query/gzquery.html?bills={单号}
-```
-
-**示例:**
-```
-https://www.jtexpress.com.cn/index/query/gzquery.html?bills=JT5472795424064
-```
-
----
-
-## 测试
-
-### 测试脚本
-
-运行测试脚本验证极兔速递支持:
-
-```bash
-cd server
-php test_jt_express.php
-```
-
-### 测试步骤
-
-1. **创建测试订单**
- - 快递公司:选择"极兔速递"或"自动识别"
- - 快递单号:`JT5472795424064`(替换为真实单号)
-
-2. **查询物流**
- - 打开订单详情
- - 点击"查询物流"按钮
- - 查看是否返回物流轨迹
-
-3. **官网查询**
- - 点击"极兔速递查件"链接
- - 确认能正常打开极兔官网查询页面
-
----
-
-## 支持的快递公司列表
-
-| 快递公司 | 代码 | 快递100编码 | 单号格式 | 自动识别 |
-|---------|------|------------|---------|---------|
-| 顺丰速运 | sf | shunfeng | SF + 数字 | ✅ |
-| 京东快递 | jd | jd | JD/JDV/JDK/JDEX + 数字 | ✅ |
-| 极兔速递 | jt | jtexpress | JT + 13位数字 | ✅ |
-
----
-
-## 扩展其他快递公司
-
-如需添加更多快递公司(如中通、圆通、申通等),请参考以下步骤:
-
-### 1. 查询快递100编码
-
-访问快递100官方文档查询快递公司编码:
-https://api.kuaidi100.com/document/5f0ffb5ebc8da837cbd8aefc/5f0ffc3cbc8da837cbd8afc0
-
-### 2. 修改后端代码
-
-在 `ExpressTrackService.php` 中:
-
-```php
-// 添加常量
-private const KUAIDI_COM_ZTO = 'zhongtong'; // 中通快递
-
-// 添加识别规则
-if ($ec === 'zto' || $ec === 'zhongtong') {
- return ['carrier' => 'zto', 'kuaidi_com' => self::KUAIDI_COM_ZTO, 'label' => '中通快递'];
-}
-
-// 添加单号自动识别(可选)
-if (preg_match('/^[0-9]{12}$/', $num)) {
- return ['carrier' => 'zto', 'kuaidi_com' => self::KUAIDI_COM_ZTO, 'label' => '中通快递(单号识别)'];
-}
-
-// 添加官网链接
-'zto' => 'https://www.zto.com/GuestService/Bill?txtBill=' . $enc,
-```
-
-### 3. 修改前端代码
-
-在 `order_list.vue` 中:
-
-```vue
-
-
-
-
-中通快递查件
-
-
-zto: `https://www.zto.com/GuestService/Bill?txtBill=${enc}`
-
-
-if (s === 'zto' || s === 'zhongtong') return '中通快递'
-```
-
-### 4. 更新后端逻辑
-
-在 `PrescriptionOrderLogic.php` 中:
-
-```php
-if (!in_array($ec, ['sf', 'jd', 'jt', 'zto', 'auto'], true)) {
- return 'auto';
-}
-```
-
----
-
-## 常见问题
-
-### Q1: 极兔单号查询失败?
-
-**可能原因:**
-1. 快递尚未揽收
-2. 单号格式错误
-3. 快递100配置问题
-
-**解决方案:**
-1. 确认单号格式:JT + 13位数字
-2. 等待快递揽收后再查询
-3. 使用"极兔速递查件"官网链接
-
-### Q2: 自动识别不准确?
-
-**解决方案:**
-手动选择快递公司,不使用"自动识别"。
-
-### Q3: 如何添加更多快递公司?
-
-参考上面的"扩展其他快递公司"章节。
-
----
-
-## 更新历史
-
-### 2024-01-15
-- ✅ 添加极兔速递支持
-- ✅ 支持单号自动识别(JT + 13位数字)
-- ✅ 添加极兔官网查询链接
-- ✅ 更新前端选项和显示
-- ✅ 创建测试脚本
-
----
-
-## 相关文档
-
-- `LOGISTICS_INTEGRATION_GUIDE.md` - 完整物流集成指南
-- `LOGISTICS_UPDATE_README.md` - 物流功能更新说明
-- `server/.env.logistics.example` - 配置示例
-- 快递100官方文档:https://api.kuaidi100.com/
diff --git a/KUAIDI100_ACCOUNT_ISSUE.md b/KUAIDI100_ACCOUNT_ISSUE.md
deleted file mode 100644
index 26177936..00000000
--- a/KUAIDI100_ACCOUNT_ISSUE.md
+++ /dev/null
@@ -1,281 +0,0 @@
-# 快递100账号问题解决方案
-
-## 问题确认
-
-根据快递100官方文档,错误码 `400` 的含义是:
-
-```
-400 - 找不到对应公司
-原因:提交数据不完整或者账号未充值
-```
-
-你的账号返回此错误,最可能的原因是:**账号未充值或余额不足**
-
----
-
-## 立即检查
-
-### 1. 登录快递100后台
-
-访问:https://www.kuaidi100.com/
-
-使用你的账号登录(Customer: `CC25SV92****`)
-
-### 2. 检查账户余额
-
-在后台查看:
-- 当前余额
-- 可用查询次数
-- 套餐类型
-- 支持的快递公司列表
-
-### 3. 检查充值记录
-
-确认是否已经充值:
-- 充值金额
-- 充值时间
-- 是否到账
-
----
-
-## 解决方案
-
-### 方案一:充值账户(推荐)
-
-1. **登录快递100后台**
- - 网址:https://www.kuaidi100.com/
- - 使用你的账号登录
-
-2. **进入充值页面**
- - 找到"充值"或"套餐购买"入口
- - 选择合适的套餐
-
-3. **选择套餐**
- - 基础套餐:支持常用快递公司
- - 标准套餐:支持更多快递公司
- - 企业套餐:支持所有快递公司
-
-4. **完成支付**
- - 支付宝/微信/对公转账
- - 等待到账(通常即时到账)
-
-5. **测试查询**
- ```bash
- cd server
- php debug_kuaidi100.php
- ```
-
-### 方案二:联系客服
-
-如果确认已充值但仍然报错:
-
-**快递100客服:**
-- 官网:https://www.kuaidi100.com/
-- 在线客服:工作日 9:00-18:00
-- 电话:查看官网联系方式
-
-**提供信息:**
-- Customer ID: `CC25SV92****`
-- 错误信息:`找不到对应公司 (returnCode: 400)`
-- 测试单号:`JD0230761381812`
-- 快递公司:京东快递
-
-### 方案三:使用官网查询(临时方案)
-
-在充值前,系统已提供官网查询链接:
-
-**京东快递:**
-```
-https://www.jdl.com/#/trackQuery?waybillCode=JD0230761381812
-```
-
-**极兔速递:**
-```
-https://www.jtexpress.com.cn/index/query/gzquery.html?bills=JT5472795424064
-```
-
-**顺丰速运:**
-```
-https://www.sf-express.com/cn/sc/dynamic_function/waybill/#search/bill-number/SF1234567890
-```
-
----
-
-## 快递100套餐说明
-
-### 基础套餐
-- **价格**:约 ¥100-500/年
-- **查询次数**:1000-5000次
-- **支持快递**:10-20家常用快递
-- **适合**:小型企业、个人开发者
-
-### 标准套餐
-- **价格**:约 ¥500-2000/年
-- **查询次数**:5000-20000次
-- **支持快递**:50+家快递公司
-- **适合**:中型企业
-
-### 企业套餐
-- **价格**:约 ¥2000+/年
-- **查询次数**:20000+次
-- **支持快递**:100+家快递公司
-- **适合**:大型企业、电商平台
-
-**注意:** 具体价格和套餐内容以快递100官网为准。
-
----
-
-## 测试步骤
-
-### 充值后测试
-
-1. **测试配置**
- ```bash
- cd server
- php check_logistics_config.php
- ```
-
-2. **测试API调用**
- ```bash
- php debug_kuaidi100.php
- ```
-
- 应该看到:
- ```json
- {
- "message": "ok",
- "state": "0",
- "data": [...]
- }
- ```
-
-3. **测试前端查询**
- - 打开业务订单详情
- - 填写快递单号
- - 点击"查询物流"
- - 应该能看到物流轨迹
-
----
-
-## 常见问题
-
-### Q1: 充值后多久生效?
-
-**A:** 通常即时生效。如果超过10分钟未生效,联系客服。
-
-### Q2: 如何查看剩余查询次数?
-
-**A:** 登录快递100后台,在首页或账户信息页面查看。
-
-### Q3: 查询次数用完了怎么办?
-
-**A:**
-1. 续费充值
-2. 升级套餐
-3. 临时使用官网查询链接
-
-### Q4: 可以按次付费吗?
-
-**A:** 快递100通常提供套餐制,具体咨询客服。
-
-### Q5: 支持哪些快递公司?
-
-**A:**
-- 基础套餐:顺丰、京东、中通、圆通、申通、韵达等常用快递
-- 标准套餐:50+家快递公司
-- 企业套餐:100+家快递公司
-
-完整列表见:https://api.kuaidi100.com/document/5f0ffb5ebc8da837cbd8aefc/5f0ffc3cbc8da837cbd8afc0
-
----
-
-## 系统当前状态
-
-### ✅ 已完成的优化
-
-1. **错误提示优化**
- - 当快递100返回400错误时
- - 提示:"快递100暂不支持该快递公司或编码错误,请使用下方官网链接查询"
-
-2. **官网链接备用**
- - 所有快递公司都提供官网查询链接
- - 用户可以直接点击跳转
-
-3. **支持多家快递**
- - 顺丰速运(shunfeng)
- - 京东快递(jingdong)
- - 极兔速递(jtexpress)
-
-4. **详细日志记录**
- - 记录所有查询请求和响应
- - 方便排查问题
-
-### 🔄 待完成(充值后)
-
-1. **实时物流查询**
- - 快递100 API实时查询
- - 返回完整物流轨迹
- - 显示物流状态
-
-2. **自动识别快递公司**
- - 根据单号自动识别
- - 无需手动选择
-
-3. **高级功能**
- - 预计到达时间
- - 快递员信息
- - 路由信息
-
----
-
-## 建议
-
-### 短期(立即可用)
-
-**使用官网查询链接:**
-- 功能完全可用
-- 无需额外费用
-- 只需点击跳转
-
-### 中期(推荐)
-
-**充值快递100账户:**
-- 统一的查询接口
-- 更好的用户体验
-- 自动化物流追踪
-
-### 长期(可选)
-
-**对接多个物流API:**
-- 快递100作为主要渠道
-- 各快递公司官方API作为备用
-- 提高查询成功率
-
----
-
-## 总结
-
-**问题原因:** 快递100账号未充值或余额不足
-
-**立即行动:**
-1. 登录快递100后台检查余额
-2. 充值账户
-3. 测试查询功能
-
-**临时方案:**
-- 使用系统提供的官网查询链接
-- 功能完全可用
-
-**充值后:**
-- 可以使用快递100实时查询
-- 获得更好的用户体验
-- 支持更多快递公司
-
----
-
-## 相关文档
-
-- 快递100官方文档:https://api.kuaidi100.com/document/5f0ffb5ebc8da837cbd8aefc
-- 快递公司编码表:https://api.kuaidi100.com/document/5f0ffb5ebc8da837cbd8aefc/5f0ffc3cbc8da837cbd8afc0
-- `LOGISTICS_INTEGRATION_GUIDE.md` - 物流集成指南
-- `KUAIDI100_TROUBLESHOOTING.md` - 故障排查指南
diff --git a/KUAIDI100_TROUBLESHOOTING.md b/KUAIDI100_TROUBLESHOOTING.md
deleted file mode 100644
index f7c404fa..00000000
--- a/KUAIDI100_TROUBLESHOOTING.md
+++ /dev/null
@@ -1,251 +0,0 @@
-# 快递100查询问题排查指南
-
-## 问题现象
-
-查询京东快递单号 `JD0230761381812` 时,快递100返回错误:
-```
-"找不到对应公司" (returnCode: 400)
-```
-
-但在京东官网可以正常查询到物流信息。
-
----
-
-## 问题原因
-
-### 1. 快递100账号权限限制
-
-快递100的不同套餐支持的快递公司数量不同:
-- **基础版**:支持常用的10-20家快递公司
-- **标准版**:支持50+家快递公司
-- **企业版**:支持100+家快递公司
-
-你的账号可能是基础版,不包含京东快递的查询权限。
-
-### 2. 快递公司编码问题
-
-快递100对某些快递公司有特殊的编码要求:
-- 京东快递:`jingdong`(不是 `jd`)
-- 极兔速递:`jtexpress`(不是 `jt`)
-- 顺丰速运:`shunfeng`(不是 `sf`)
-
-### 3. 需要额外开通
-
-某些快递公司需要在快递100后台单独开通才能查询。
-
----
-
-## 解决方案
-
-### 方案一:升级快递100套餐(推荐)
-
-1. 登录快递100后台:https://www.kuaidi100.com/
-2. 进入"套餐管理"
-3. 升级到支持更多快递公司的套餐
-4. 或单独开通京东快递查询权限
-
-### 方案二:使用官网查询(当前可用)
-
-系统已经提供了官网查询链接作为备用方案:
-
-**京东物流官网:**
-```
-https://www.jdl.com/#/trackQuery?waybillCode=JD0230761381812
-```
-
-**使用步骤:**
-1. 在订单详情页面
-2. 点击"京东物流查件"链接
-3. 自动跳转到京东官网查询页面
-4. 查看完整的物流轨迹
-
-### 方案三:联系快递100客服
-
-如果确认套餐应该支持但仍然查询失败:
-
-1. 联系快递100客服
-2. 提供你的 Customer ID
-3. 说明查询失败的快递公司
-4. 请求开通相应权限
-
-**快递100客服:**
-- 官网:https://www.kuaidi100.com/
-- 在线客服:工作日 9:00-18:00
-
-### 方案四:使用自动识别
-
-某些情况下,使用 `com=auto` 自动识别可能比指定快递公司更有效:
-
-```php
-$paramArr = [
- 'com' => 'auto', // 自动识别
- 'num' => 'JD0230761381812',
- 'resultv2' => '1',
-];
-```
-
----
-
-## 当前系统优化
-
-系统已经做了以下优化:
-
-### 1. 改进错误提示
-
-当快递100返回"找不到对应公司"时,系统会提示:
-```
-"快递100暂不支持该快递公司或编码错误,请使用下方官网链接查询"
-```
-
-### 2. 提供官网链接
-
-无论快递100是否可用,系统都会提供官网查询链接:
-- 顺丰速运官网
-- 京东物流官网
-- 极兔速递官网
-
-### 3. 记录详细日志
-
-系统会记录快递100的错误信息,方便排查:
-```php
-Log::info('ExpressTrackService kuaidi100 business fail', [
- 'message' => '找不到对应公司',
- 'returnCode' => '400',
- 'num' => 'JD0230761381812',
- 'com' => 'jingdong'
-]);
-```
-
----
-
-## 测试步骤
-
-### 1. 确认快递100配置
-
-```bash
-cd server
-php check_logistics_config.php
-```
-
-应该显示:
-```
-✅ 配置正确!快递100应该可以正常使用
-```
-
-### 2. 测试API调用
-
-```bash
-php debug_kuaidi100.php
-```
-
-查看快递100的实际响应。
-
-### 3. 测试自动识别
-
-```bash
-php test_auto_detect.php
-```
-
-尝试使用自动识别功能。
-
-### 4. 前端测试
-
-1. 打开业务订单详情
-2. 填写快递单号:`JD0230761381812`
-3. 快递公司选择"京东快递"
-4. 点击"查询物流"
-5. 如果失败,点击"京东物流查件"链接
-
----
-
-## 快递100支持的快递公司
-
-### 常见快递公司编码
-
-| 快递公司 | 快递100编码 | 是否需要手机号 |
-|---------|------------|--------------|
-| 顺丰速运 | shunfeng | 是(后4位) |
-| 京东快递 | jingdong | 否 |
-| 极兔速递 | jtexpress | 否 |
-| 中通快递 | zhongtong | 否 |
-| 圆通速递 | yuantong | 否 |
-| 申通快递 | shentong | 否 |
-| 韵达快递 | yunda | 否 |
-| 百世快递 | huitongkuaidi | 否 |
-| EMS | ems | 否 |
-| 邮政包裹 | youzhengguonei | 否 |
-
-### 查询完整列表
-
-访问快递100官方文档:
-https://api.kuaidi100.com/document/5f0ffb5ebc8da837cbd8aefc/5f0ffc3cbc8da837cbd8afc0
-
----
-
-## 常见错误码
-
-| 错误码 | 说明 | 解决方案 |
-|-------|------|---------|
-| 400 | 找不到对应公司 | 检查快递公司编码或升级套餐 |
-| 500 | 服务器错误 | 稍后重试 |
-| 600 | 您不是合法的订阅者 | 检查 Customer 和 Key |
-| 601 | SIGN签名错误 | 检查签名算法 |
-| 700 | 订阅数据已达上限 | 升级套餐或等待重置 |
-| 701 | 快递公司参数异常 | 检查 com 参数 |
-| 702 | 快递单号参数异常 | 检查 num 参数 |
-| 703 | 查询无结果 | 快递可能未揽收 |
-| 704 | 快递公司识别失败 | 使用自动识别或指定公司 |
-
----
-
-## 建议
-
-### 短期方案(立即可用)
-
-使用官网查询链接:
-- 系统已经提供了所有快递公司的官网链接
-- 用户点击即可跳转查询
-- 无需额外配置
-
-### 长期方案(推荐)
-
-1. **升级快递100套餐**
- - 支持更多快递公司
- - 提供更好的用户体验
- - 统一的查询接口
-
-2. **对接多个物流API**
- - 快递100作为主要渠道
- - 各快递公司官方API作为备用
- - 自动切换,提高成功率
-
-3. **缓存查询结果**
- - 减少API调用次数
- - 降低费用
- - 提高响应速度
-
----
-
-## 总结
-
-当前问题是快递100账号不支持京东快递查询,但系统已经提供了官网查询链接作为备用方案。
-
-**用户可以:**
-1. 点击"京东物流查件"链接查询
-2. 联系快递100升级套餐
-3. 等待系统对接京东官方API
-
-**系统已优化:**
-1. 改进错误提示
-2. 提供官网链接
-3. 记录详细日志
-4. 支持多种查询方式
-
----
-
-## 相关文档
-
-- `LOGISTICS_INTEGRATION_GUIDE.md` - 物流集成指南
-- `LOGISTICS_UPDATE_README.md` - 更新说明
-- `JTEXPRESS_SUPPORT_ADDED.md` - 极兔速递支持
-- 快递100官方文档:https://api.kuaidi100.com/
diff --git a/LOCAL_RECORDING_DEBUG_GUIDE.md b/LOCAL_RECORDING_DEBUG_GUIDE.md
deleted file mode 100644
index f1e11114..00000000
--- a/LOCAL_RECORDING_DEBUG_GUIDE.md
+++ /dev/null
@@ -1,358 +0,0 @@
-# 本地录制调试指南(第二版)
-
-## 问题描述
-本地录制总是失败,`blob` 为空,导致无法保存通话录制文件。
-
-## 根本原因分析
-
-从实际日志分析发现:
-1. **录制启动太晚**:原来的 1200ms 延迟导致录制在对方挂断后才启动
-2. **SDK 提前销毁轨道**:`stopLocalVideo/stopLocalAudio` 在 `exitRoom` 之前就销毁了视频轨道
-3. **时间窗口太短**:从对方离开到轨道销毁只有约 200-300ms
-
-关键时间线(问题场景):
-```
-15:30:51.841 - 对方离开(USER_LEAVE 事件)
-15:30:51.xxx - beginStopNow 被调用(但录制还未启动)
-15:30:52.072 - stopLocalVideo/stopLocalAudio(轨道被销毁)
-15:30:52.102 - exitRoom
-15:30:52.374 - MediaRecorder 已启动(太晚了!)
-15:30:52.xxx - 数据块数: 0(因为轨道已销毁)
-```
-
-## 已实施的修复(第二版)
-
-### 1. 大幅缩短录制启动延迟
-- **从 1200ms 缩短到 500ms**
-- 使用 300ms 间隔重试(最多 10 次)
-- 更快地检测和启动录制
-
-```typescript
-// 延迟 500ms 后开始尝试(给视频元素一点加载时间)
-startLocalRecTimer = setTimeout(() => attemptStartRecording(0), 500)
-```
-
-### 2. 在通话结束事件时立即停止录制
-监听 TUICallEngine 事件,在检测到通话结束时立即停止:
-
-```typescript
-const onEarlyStopRecording = () => {
- console.log('[chat-dialog] 检测到通话结束事件,立即停止录制')
- if (localCallRecorder.isRecording()) {
- console.log('[chat-dialog] 录制进行中,触发 beginStopNow')
- localCallRecorder.beginStopNow()
- }
-}
-// 监听: CALL_END, USER_LEAVE, ON_CALL_NOT_CONNECTED
-```
-
-### 3. 缩短停止延迟
-- **从 300ms 缩短到 100ms**
-- 减少等待时间,避免 SDK 提前销毁轨道
-
-```typescript
-// 缩短延迟到 100ms,避免 SDK 提前销毁轨道
-setTimeout(() => {
- mr.stop()
-}, 100)
-```
-
-### 4. 增强日志输出
-在关键位置添加了详细的 console.log:
-
-- **录制启动阶段**:
- - 检查容器和通话状态
- - 视频元素数量和就绪状态
- - MediaRecorder 创建和启动状态
- - Canvas 和音频混音初始化
-
-- **录制进行阶段**:
- - 数据块收集(ondataavailable)
- - MediaRecorder 状态变化
-
-- **录制停止阶段**:
- - beginStopNow 调用时机
- - 数据块数量
- - Blob 生成结果
-
-### 5. 智能视频元素检测
-改进了视频元素查找逻辑:
-
-```typescript
-// 1. 首先检查容器内的视频
-let videos = Array.from(root.querySelectorAll('video'))
-
-// 2. 如果没有,检查 body(TUICallKit Teleport)
-if (videos.length === 0) {
- const bodyVideos = Array.from(document.body.querySelectorAll('video'))
- videos = bodyVideos.filter(v => {
- const so = v.srcObject
- if (!(so instanceof MediaStream)) return false
- return so.getVideoTracks().some(t => t.readyState === 'live')
- })
-}
-
-// 3. 检查视频是否有有效数据
-const hasReadyVideo = videos.some(v =>
- v.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA
-)
-```
-
-## 调试步骤
-
-### 1. 打开浏览器控制台
-在发起视频通话前,打开浏览器开发者工具(F12),切换到 Console 标签。
-
-### 2. 发起视频通话
-点击视频通话按钮,观察控制台输出。
-
-### 3. 关键日志检查点
-
-#### 通话接通后(约 0.5-1 秒)
-应该看到:
-```
-[chat-dialog] 尝试启动本地录制(第 1 次)
-[chat-dialog] 在 body 中找到活跃视频: 2
-[chat-dialog] 视频元素已就绪,立即启动录制
-[call-local-recorder] 使用 MIME 类型: video/webm;codecs=vp9,opus
-[call-local-recorder] Canvas 视频轨道数: 1
-[call-local-recorder] 找到视频元素数量: 2
-[call-local-recorder] 输出流轨道: { video: 1, audio: 2 }
-[call-local-recorder] MediaRecorder 创建成功
-[call-local-recorder] MediaRecorder.start() 调用成功
-[call-local-recorder] MediaRecorder 已启动
-[chat-dialog] ✅ 本地录制已成功启动
-```
-
-#### 录制过程中(每 250ms)
-应该看到:
-```
-[call-local-recorder] 收到数据块,大小: 12345 (总计: 1 块)
-[call-local-recorder] 收到数据块,大小: 15678 (总计: 2 块)
-...
-```
-
-**重要**:如果没有看到数据块,说明录制有问题!
-
-#### 对方挂断或己方挂断时
-应该看到:
-```
-[chat-dialog] 检测到通话结束事件,立即停止录制
-[chat-dialog] 录制进行中,触发 beginStopNow
-[call-local-recorder] beginStopNow 被调用
-[call-local-recorder] 准备停止 MediaRecorder,当前状态: recording 已收集数据块: 15
-[call-local-recorder] 调用 requestData()
-[call-local-recorder] 延迟后数据块数: 16
-[call-local-recorder] 调用 stop()
-[call-local-recorder] MediaRecorder onstop 触发,数据块数: 16
-[call-local-recorder] 生成 Blob: 1234567 bytes, type: video/webm
-[chat-dialog] localCallRecorder.stop() 返回: 1234567 bytes
-[chat-dialog] 开始上传录制文件
-[chat-dialog] 上传成功,文件 URL: https://...
-[chat-dialog] 关联录制文件到诊单
-本地录制已上传并关联到通话记录
-```
-
-## 常见问题诊断
-
-### 问题 1:录制启动太晚
-**症状**:
-```
-[call-local-recorder] MediaRecorder 已启动
-(出现在 stopLocalVideo 之后)
-```
-
-**原因**:
-- 视频元素加载慢
-- 重试次数不够
-
-**解决**:
-- 已缩短初始延迟到 500ms
-- 增加重试次数到 10 次
-- 每次重试间隔 300ms
-
-### 问题 2:找不到视频元素
-**症状**:
-```
-[chat-dialog] 未找到视频元素,300ms 后重试
-(重复多次)
-```
-
-**原因**:
-- TUICallKit 将视频 Teleport 到 body
-- 视频元素尚未渲染
-
-**解决**:
-- 代码已添加 body 视频检查
-- 添加了活跃视频流过滤
-
-### 问题 3:视频元素未就绪
-**症状**:
-```
-[chat-dialog] 视频元素未就绪,300ms 后重试
-```
-
-**原因**:
-- 视频流尚未加载
-- readyState < HAVE_CURRENT_DATA
-
-**解决**:
-- 代码已添加就绪检查和重试
-- 最多重试 10 次
-
-### 问题 4:没有收到数据块
-**症状**:
-- 录制启动成功
-- 但从未看到 "收到数据块" 日志
-- 最终 `数据块数: 0`
-
-**可能原因**:
-1. Canvas 绘制失败(跨域问题)
-2. MediaRecorder 不支持当前 MIME 类型
-3. 视频流被中断或为空
-
-**检查方法**:
-```javascript
-// 在控制台手动检查
-const videos = document.querySelectorAll('video')
-videos.forEach((v, i) => {
- console.log(`Video ${i}:`, {
- readyState: v.readyState,
- videoWidth: v.videoWidth,
- videoHeight: v.videoHeight,
- srcObject: v.srcObject,
- videoTracks: v.srcObject?.getVideoTracks().map(t => ({
- id: t.id,
- label: t.label,
- readyState: t.readyState,
- enabled: t.enabled
- })),
- audioTracks: v.srcObject?.getAudioTracks().map(t => ({
- id: t.id,
- label: t.label,
- readyState: t.readyState,
- enabled: t.enabled
- }))
- })
-})
-```
-
-### 问题 5:通话时间过短
-**症状**:
-```
-[call-local-recorder] 生成 Blob: null
-或
-[call-local-recorder] 生成 Blob: 1234 bytes(很小)
-```
-
-**原因**:
-- 通话时长 < 1 秒
-- 录制启动后立即挂断
-
-**解决**:
-- 确保通话时长 > 2 秒
-- 测试时保持通话至少 5 秒
-
-### 问题 6:SDK 提前销毁轨道
-**症状**:
-```
-[call-local-recorder] MediaRecorder 错误
-或
-数据块数为 0
-```
-
-**原因**:
-- SDK exitRoom 在 MediaRecorder.stop() 之前执行
-- 视频轨道被销毁
-
-**解决**:
-- 已在 USER_LEAVE 事件时立即停止录制
-- 已在多个挂断入口添加钩子
-- 缩短停止延迟到 100ms
-
-## 测试建议
-
-### 最小测试场景
-1. 发起视频通话
-2. 等待接通(看到对方画面)
-3. **保持通话至少 5 秒**(重要!)
-4. 正常挂断
-5. 检查控制台日志和录制结果
-
-### 关键检查点
-- [ ] 录制在接通后 1 秒内启动
-- [ ] 看到 "✅ 本地录制已成功启动"
-- [ ] 看到持续的 "收到数据块" 日志
-- [ ] 数据块数 > 0(至少 4-5 块)
-- [ ] 生成的 Blob 大小 > 10KB
-- [ ] 上传成功并关联到诊单
-
-### 压力测试场景
-1. 快速挂断(2-3 秒)
-2. 对方挂断
-3. 网络中断
-4. 切换会话后挂断
-5. 关闭聊天窗口
-
-## 预期改进效果
-
-### 修复前
-```
-通话接通 -> 等待 1200ms -> 尝试启动录制 -> 对方挂断 -> SDK 销毁轨道 -> 录制失败
-```
-
-### 修复后
-```
-通话接通 -> 等待 500ms -> 快速启动录制 -> 开始收集数据 ->
-检测到挂断 -> 立即停止录制 -> 生成 Blob -> 上传成功
-```
-
-## 浏览器兼容性
-
-### 支持的浏览器
-- Chrome/Edge 85+
-- Firefox 78+
-- Safari 14.1+
-
-### MIME 类型优先级
-1. `video/webm;codecs=vp9,opus` (最佳)
-2. `video/webm;codecs=vp8,opus`
-3. `video/webm;codecs=vp9`
-4. `video/webm` (兜底)
-
-### 检查浏览器支持
-```javascript
-const mimes = [
- 'video/webm;codecs=vp9,opus',
- 'video/webm;codecs=vp8,opus',
- 'video/webm;codecs=vp9',
- 'video/webm'
-]
-mimes.forEach(m => {
- console.log(m, MediaRecorder.isTypeSupported(m))
-})
-```
-
-## 性能优化建议
-
-1. **Canvas 分辨率**:当前 1280x720,可根据需要调整
-2. **数据块间隔**:当前 250ms,可调整为 500ms 减少开销
-3. **音频混音**:如果不需要音频,可以禁用 AudioContext
-
-## 下一步优化(如果问题仍存在)
-
-1. **进一步缩短启动延迟**:从 500ms 减少到 300ms
-2. **增加录制状态监控**:定期检查 MediaRecorder 状态
-3. **添加降级方案**:如果本地录制失败,提示用户依赖云端录制
-4. **添加手动录制控制**:让用户手动开始/停止录制
-5. **使用 MutationObserver**:监听视频元素的添加,立即启动录制
-
-## 联系支持
-
-如果按照本指南操作后问题仍未解决,请提供:
-1. 完整的控制台日志(从通话开始到结束)
-2. 浏览器版本和操作系统
-3. 通话时长(秒)
-4. 是否看到视频画面
-5. 是否有任何错误提示
-6. 数据块数量(如果有)
diff --git a/LOGISTICS_INTEGRATION_GUIDE.md b/LOGISTICS_INTEGRATION_GUIDE.md
deleted file mode 100644
index c31aa523..00000000
--- a/LOGISTICS_INTEGRATION_GUIDE.md
+++ /dev/null
@@ -1,266 +0,0 @@
-# 物流查询集成指南
-
-## 当前实现
-
-系统已集成快递100 API,支持顺丰速运和京东快递的物流轨迹查询。
-
-### 查询方式优先级
-
-1. **快递100 API**(推荐)- 实时查询物流轨迹
-2. **官网链接跳转**(备用)- 未配置API时使用
-
----
-
-## 快递100 API 配置(推荐)
-
-### 1. 注册快递100账号
-
-访问:https://www.kuaidi100.com/
-- 注册企业账号
-- 申请API接口权限
-- 获取 `customer` 和 `key`
-
-### 2. 配置环境变量
-
-在 `server/.env` 文件中添加:
-
-```env
-# 快递100配置
-LOGISTICS_KUAIDI100_ENABLE=true
-LOGISTICS_KUAIDI100_CUSTOMER=你的customer
-LOGISTICS_KUAIDI100_KEY=你的key
-LOGISTICS_KUAIDI100_QUERY_URL=https://poll.kuaidi100.com/poll/query.do
-```
-
-### 3. 配置文件
-
-在 `server/config/logistics.php` 中:
-
-```php
- [
- 'enable' => env('LOGISTICS_KUAIDI100_ENABLE', false),
- 'customer' => env('LOGISTICS_KUAIDI100_CUSTOMER', ''),
- 'key' => env('LOGISTICS_KUAIDI100_KEY', ''),
- 'query_url' => env('LOGISTICS_KUAIDI100_QUERY_URL', 'https://poll.kuaidi100.com/poll/query.do'),
- ],
-];
-```
-
-### 4. 支持的快递公司
-
-- **顺丰速运** (sf / shunfeng)
-- **京东快递** (jd)
-- 更多快递公司可通过快递100文档查询编码
-
----
-
-## 官网查询链接(备用方案)
-
-当未配置快递100 API时,系统会提供官网查询链接:
-
-### 顺丰速运
-- 链接格式:`https://www.sf-express.com/cn/sc/dynamic_function/waybill/#search/bill-number/{运单号}`
-- 特点:需要输入收件人手机号后4位验证
-
-### 京东物流
-- 链接格式:`https://www.jdl.com/#/trackQuery?waybillCode={运单号}`
-- 特点:直接查询,无需验证
-
----
-
-## 使用说明
-
-### 前端查询界面
-
-位置:`业务订单详情` → `物流轨迹`
-
-功能:
-1. 选择快递公司(自动识别/顺丰/京东)
-2. 点击"查询物流"按钮
-3. 查看实时物流轨迹
-4. 备用:点击"顺丰官网查件"或"京东物流查件"
-
-### 自动识别规则
-
-系统会根据运单号自动识别快递公司:
-- 顺丰:以 `SF` 开头
-- 京东:以 `JD`、`JDV`、`JDK`、`JDEX` 开头
-
-### 物流状态说明
-
-- **0** - 在途
-- **1** - 揽收
-- **2** - 疑难
-- **3** - 已签收
-- **4** - 退签
-- **5** - 派件中
-- **6** - 退回
-- **7** - 转投
-- **10** - 待清关
-- **11** - 清关中
-- **12** - 已清关
-- **13** - 清关异常
-- **14** - 收件人拒签
-
----
-
-## 顺丰查询注意事项
-
-### 为什么查不到顺丰快递?
-
-1. **手机号验证**
- - 顺丰查询需要收件人手机号后4位
- - 系统已自动传入订单中的收件人手机号
- - 如果查询失败,请检查订单中的手机号是否正确
-
-2. **运单号格式**
- - 确保运单号正确无误
- - 顺丰运单号通常以 `SF` 开头
-
-3. **快递100配置**
- - 确认已正确配置 `LOGISTICS_KUAIDI100_*` 环境变量
- - 确认快递100账户余额充足
-
-4. **官网查询**
- - 如果API查询失败,可使用官网链接
- - 官网查询需要手动输入手机号后4位
-
----
-
-## 扩展其他快递公司
-
-### 1. 修改 ExpressTrackService.php
-
-在 `resolveCarrier` 方法中添加新的快递公司识别:
-
-```php
-if ($ec === 'ems') {
- return ['carrier' => 'ems', 'kuaidi_com' => 'ems', 'label' => 'EMS'];
-}
-```
-
-### 2. 添加官网链接
-
-在 `officialUrls` 方法中添加:
-
-```php
-return [
- 'sf' => '...',
- 'jd' => '...',
- 'ems' => 'https://www.ems.com.cn/queryList?mailNum=' . $enc,
-];
-```
-
-### 3. 更新前端选项
-
-在 `order_list.vue` 中添加选项:
-
-```vue
-
-```
-
----
-
-## 故障排查
-
-### 问题1:提示"未配置快递100查询密钥"
-
-**解决方案:**
-1. 检查 `.env` 文件中的配置
-2. 确认 `LOGISTICS_KUAIDI100_ENABLE=true`
-3. 重启服务器
-
-### 问题2:查询返回"查询失败"
-
-**可能原因:**
-1. 运单号错误
-2. 快递尚未揽收
-3. 快递100账户余额不足
-4. 手机号后4位不匹配(顺丰)
-
-**解决方案:**
-1. 核对运单号
-2. 等待快递揽收后再查询
-3. 充值快递100账户
-4. 使用官网链接手动查询
-
-### 问题3:顺丰查询失败但京东正常
-
-**原因:**
-顺丰查询需要手机号后4位验证
-
-**解决方案:**
-1. 确保订单中填写了正确的收件人手机号
-2. 使用官网链接,手动输入手机号后4位
-
----
-
-## API 接口说明
-
-### 查询物流轨迹
-
-**接口:** `GET /tcm.prescriptionOrder/logisticsTrace`
-
-**参数:**
-- `id` (必填): 业务订单ID
-- `express_company` (可选): 快递公司 (auto/sf/jd)
-
-**返回示例:**
-
-```json
-{
- "carrier": "sf",
- "carrier_label": "顺丰速运",
- "kuaidi_com": "shunfeng",
- "traces": [
- {
- "time": "2024-01-15 10:30:00",
- "context": "快件已签收"
- },
- {
- "time": "2024-01-15 08:00:00",
- "context": "派件中"
- }
- ],
- "state": "3",
- "state_text": "已签收",
- "source": "kuaidi100",
- "hint": "",
- "official_url": "https://www.sf-express.com/...",
- "official_urls": {
- "sf": "https://www.sf-express.com/...",
- "jd": "https://www.jdl.com/..."
- },
- "tracking_number": "SF1234567890",
- "express_company_used": "sf"
-}
-```
-
----
-
-## 费用说明
-
-### 快递100 API
-
-- 按查询次数计费
-- 具体价格请咨询快递100官方
-- 建议:配置后台缓存减少重复查询
-
-### 官网查询
-
-- 完全免费
-- 需要用户手动跳转
-- 顺丰需要手机号验证
-
----
-
-## 更新日志
-
-### 2024-01-15
-- 更新顺丰官网查询链接(新版)
-- 更新京东物流查询链接
-- 优化手机号后4位自动传入逻辑
-- 完善错误提示信息
diff --git a/LOGISTICS_UPDATE_README.md b/LOGISTICS_UPDATE_README.md
deleted file mode 100644
index 641bec19..00000000
--- a/LOGISTICS_UPDATE_README.md
+++ /dev/null
@@ -1,308 +0,0 @@
-# 物流查询功能更新说明
-
-## 更新内容
-
-### 1. 更新官网查询链接
-
-#### 顺丰速运
-- **旧链接**:`https://ucmp.sf-express.com/cx/#/waybill/waybill-search?waybillNumber={单号}`
-- **新链接**:`https://www.sf-express.com/cn/sc/dynamic_function/waybill/#search/bill-number/{单号}`
-- **说明**:更新为顺丰官网最新版查询页面
-
-#### 京东物流
-- **旧链接**:`https://www.jdl.com/express/tracking/?waybillCodes={单号}`
-- **新链接**:`https://www.jdl.com/#/trackQuery?waybillCode={单号}`
-- **说明**:更新为京东物流最新版查询页面
-
-### 2. 更新文件列表
-
-- `server/app/common/service/ExpressTrackService.php` - 后端服务类
-- `admin/src/views/consumer/prescription/order_list.vue` - 前端订单列表页面
-
-### 3. 新增文档
-
-- `LOGISTICS_INTEGRATION_GUIDE.md` - 完整的物流集成指南
-- `server/.env.logistics.example` - 环境变量配置示例
-- `server/test_logistics.php` - 物流查询测试脚本
-
----
-
-## 快速开始
-
-### 方案一:使用快递100 API(推荐)
-
-#### 1. 注册快递100账号
-访问:https://www.kuaidi100.com/
-- 注册企业账号
-- 申请API接口权限
-- 获取 Customer 和 Key
-
-#### 2. 配置环境变量
-在 `server/.env` 文件中添加:
-
-```env
-LOGISTICS_KUAIDI100_CUSTOMER=你的customer
-LOGISTICS_KUAIDI100_KEY=你的key
-```
-
-#### 3. 重启服务器
-```bash
-# 重启PHP服务
-systemctl restart php-fpm
-
-# 或重启整个应用
-```
-
-#### 4. 测试配置
-```bash
-cd server
-php test_logistics.php
-```
-
-### 方案二:使用官网链接(免费)
-
-无需配置,系统会自动提供官网查询链接:
-- 顺丰速运:需要输入收件人手机号后4位
-- 京东物流:直接查询
-
----
-
-## 使用说明
-
-### 前端操作
-
-1. 进入"业务订单列表"
-2. 点击订单查看详情
-3. 在"物流轨迹"区域:
- - 选择快递公司(自动识别/顺丰/京东)
- - 点击"查询物流"按钮
- - 查看实时物流轨迹
-4. 备用方案:
- - 点击"顺丰官网查件"或"京东物流查件"
- - 跳转到官网手动查询
-
-### 自动识别规则
-
-系统会根据运单号自动识别快递公司:
-- **顺丰**:以 `SF` 开头的运单号
-- **京东**:以 `JD`、`JDV`、`JDK`、`JDEX` 开头的运单号
-
----
-
-## 顺丰查询问题解决
-
-### 问题:查不到顺丰快递
-
-#### 原因分析
-1. **手机号验证失败**
- - 顺丰查询需要收件人手机号后4位
- - 系统会自动从订单中获取手机号
- - 如果手机号不正确,查询会失败
-
-2. **运单号错误**
- - 确保运单号完整且正确
- - 顺丰运单号通常以 `SF` 开头
-
-3. **快递未揽收**
- - 刚下单的快递可能还未揽收
- - 等待快递员揽收后再查询
-
-4. **快递100配置问题**
- - Customer 或 Key 配置错误
- - 账户余额不足
-
-#### 解决方案
-
-**方案1:检查订单手机号**
-```
-1. 打开订单详情
-2. 确认"收件人手机号"字段填写正确
-3. 重新查询物流
-```
-
-**方案2:使用官网查询**
-```
-1. 点击"顺丰官网查件"链接
-2. 在官网页面手动输入手机号后4位
-3. 查看物流信息
-```
-
-**方案3:检查快递100配置**
-```bash
-# 1. 查看配置
-cat server/.env | grep LOGISTICS_KUAIDI100
-
-# 2. 测试配置
-cd server
-php test_logistics.php
-
-# 3. 检查日志
-tail -f runtime/log/error.log
-```
-
----
-
-## 京东查询问题解决
-
-### 问题:查不到京东快递
-
-#### 原因分析
-1. 运单号错误
-2. 快递未揽收
-3. 快递100配置问题
-
-#### 解决方案
-
-**方案1:核对运单号**
-- 确保运单号完整且正确
-- 京东运单号通常以 `JD` 开头
-
-**方案2:使用官网查询**
-- 点击"京东物流查件"链接
-- 京东查询无需手机号验证
-
-**方案3:等待揽收**
-- 刚下单的快递可能还未揽收
-- 等待1-2小时后再查询
-
----
-
-## API 接口说明
-
-### 查询物流轨迹
-
-**接口地址:** `GET /tcm.prescriptionOrder/logisticsTrace`
-
-**请求参数:**
-| 参数 | 类型 | 必填 | 说明 |
-|------|------|------|------|
-| id | number | 是 | 业务订单ID |
-| express_company | string | 否 | 快递公司 (auto/sf/jd) |
-
-**返回示例:**
-```json
-{
- "carrier": "sf",
- "carrier_label": "顺丰速运",
- "kuaidi_com": "shunfeng",
- "traces": [
- {
- "time": "2024-01-15 10:30:00",
- "context": "快件已签收"
- },
- {
- "time": "2024-01-15 08:00:00",
- "context": "派件中"
- }
- ],
- "state": "3",
- "state_text": "已签收",
- "source": "kuaidi100",
- "hint": "",
- "official_url": "https://www.sf-express.com/...",
- "official_urls": {
- "sf": "https://www.sf-express.com/...",
- "jd": "https://www.jdl.com/..."
- },
- "tracking_number": "SF1234567890",
- "express_company_used": "sf"
-}
-```
-
-**物流状态码:**
-| 状态码 | 说明 |
-|--------|------|
-| 0 | 在途 |
-| 1 | 揽收 |
-| 2 | 疑难 |
-| 3 | 已签收 |
-| 4 | 退签 |
-| 5 | 派件中 |
-| 6 | 退回 |
-| 7 | 转投 |
-| 10 | 待清关 |
-| 11 | 清关中 |
-| 12 | 已清关 |
-| 13 | 清关异常 |
-| 14 | 收件人拒签 |
-
----
-
-## 测试步骤
-
-### 1. 测试配置
-```bash
-cd server
-php test_logistics.php
-```
-
-### 2. 测试官网链接
-1. 打开浏览器
-2. 访问生成的官网链接
-3. 确认能正常打开查询页面
-
-### 3. 测试实时查询(需要配置快递100)
-1. 在订单中填写真实的快递单号
-2. 点击"查询物流"按钮
-3. 查看是否返回物流轨迹
-
-### 4. 测试手机号验证(顺丰)
-1. 创建订单时填写正确的收件人手机号
-2. 查询顺丰快递
-3. 确认能正常返回结果
-
----
-
-## 常见问题
-
-### Q1: 为什么快递100查询失败?
-**A:** 可能原因:
-1. 未配置 Customer 和 Key
-2. 账户余额不足
-3. 运单号错误或快递未揽收
-4. 网络连接问题
-
-### Q2: 顺丰查询为什么需要手机号?
-**A:** 顺丰为了保护用户隐私,查询时需要验证收件人手机号后4位。系统会自动从订单中获取手机号传给快递100。
-
-### Q3: 可以添加其他快递公司吗?
-**A:** 可以。参考 `LOGISTICS_INTEGRATION_GUIDE.md` 中的"扩展其他快递公司"章节。
-
-### Q4: 快递100收费吗?
-**A:** 是的,快递100按查询次数收费。具体价格请咨询快递100官方。
-
-### Q5: 不配置快递100可以用吗?
-**A:** 可以。系统会提供官网查询链接,用户可以跳转到官网手动查询。
-
----
-
-## 技术支持
-
-### 相关文档
-- `LOGISTICS_INTEGRATION_GUIDE.md` - 完整集成指南
-- `server/.env.logistics.example` - 配置示例
-- 快递100官方文档:https://api.kuaidi100.com/
-
-### 日志查看
-```bash
-# 查看错误日志
-tail -f server/runtime/log/error.log
-
-# 查看应用日志
-tail -f server/runtime/log/app.log
-```
-
-### 联系方式
-如有问题,请查看项目文档或联系技术支持。
-
----
-
-## 更新历史
-
-### 2024-01-15
-- 更新顺丰官网查询链接(新版)
-- 更新京东物流查询链接
-- 新增完整的集成指南文档
-- 新增配置示例文件
-- 新增测试脚本
-- 优化错误提示信息
diff --git a/MANUAL_INSTALL_SQL.md b/MANUAL_INSTALL_SQL.md
deleted file mode 100644
index 1eb97d2a..00000000
--- a/MANUAL_INSTALL_SQL.md
+++ /dev/null
@@ -1,101 +0,0 @@
-# 处方管理功能 - 手动安装SQL
-
-如果自动安装脚本无法运行,可以手动在数据库中执行以下SQL语句。
-
-## 方法1:使用安装脚本(推荐)
-
-### Windows系统:
-```bash
-install_prescription_shared.bat
-```
-
-### Linux/Mac系统:
-```bash
-chmod +x install_prescription_shared.sh
-./install_prescription_shared.sh
-```
-
-## 方法2:手动执行SQL
-
-打开你的数据库管理工具(如 phpMyAdmin、Navicat、MySQL Workbench等),选择数据库 `zyt`,然后执行以下SQL语句:
-
-```sql
--- 处方增加共享字段
-ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `is_shared` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否共享:0-不共享(仅自己可见) 1-共享(所有人可见)' AFTER `template_id`;
-
--- 处方名称
-ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `prescription_name` varchar(100) NOT NULL DEFAULT '' COMMENT '处方名称' AFTER `sn`;
-
--- 处方类型
-ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `prescription_type` varchar(50) NOT NULL DEFAULT '浓缩水丸' COMMENT '处方类型:浓缩水丸、饮片、颗粒、丸剂、散剂等' AFTER `prescription_name`;
-
--- 舌象图片
-ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `tongue_image` varchar(255) NOT NULL DEFAULT '' COMMENT '舌象图片' AFTER `tongue`;
-
--- 脉象详情
-ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `pulse_condition` varchar(100) NOT NULL DEFAULT '' COMMENT '脉象详情' AFTER `pulse`;
-
--- 服用天数
-ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `usage_days` int(11) NOT NULL DEFAULT 7 COMMENT '服用天数' AFTER `dose_unit`;
-
--- 服用时间
-ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `usage_time` varchar(50) NOT NULL DEFAULT '饭前' COMMENT '服用时间:饭前、饭后、饭中、空腹、睡前、晨起、随时' AFTER `usage_days`;
-
--- 服用方式
-ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `usage_way` varchar(50) NOT NULL DEFAULT '温水送服' COMMENT '服用方式:温水送服、开水冲服、黄酒送服等' AFTER `usage_time`;
-
--- 忌口
-ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `dietary_taboo` varchar(200) NOT NULL DEFAULT '' COMMENT '忌口(逗号分隔):辛辣食物、生冷食物、油腻食物等' AFTER `usage_way`;
-
--- 其他服用说明
-ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `usage_notes` varchar(200) NOT NULL DEFAULT '' COMMENT '其他服用说明' AFTER `dietary_taboo`;
-```
-
-## 方法3:使用命令行
-
-```bash
-# 进入项目根目录
-cd /path/to/your/project
-
-# 执行SQL文件
-mysql -h127.0.0.1 -P3306 -uroot -proot zyt < server/database/migrations/add_shared_to_prescription.sql
-```
-
-## 验证安装
-
-执行以下SQL查询,检查字段是否已添加:
-
-```sql
-SHOW COLUMNS FROM `zyt_tcm_prescription`;
-```
-
-应该能看到以下新增字段:
-- prescription_name
-- prescription_type
-- is_shared
-- usage_days
-- usage_time
-- usage_way
-- dietary_taboo
-- usage_notes
-- tongue_image
-- pulse_condition
-
-## 注意事项
-
-1. 如果某个字段已存在,会报错但不影响其他字段的添加
-2. 请确保数据库名称为 `zyt`,如果不是请修改SQL中的表名前缀
-3. 执行前建议备份数据库
-
-## 常见问题
-
-### 问题1:字段已存在
-如果提示"Duplicate column name",说明该字段已经存在,可以忽略该错误。
-
-### 问题2:表不存在
-如果提示"Table doesn't exist",请检查:
-1. 数据库名称是否正确
-2. 表名前缀是否正确(默认是 zyt_)
-
-### 问题3:权限不足
-如果提示权限错误,请确保数据库用户有 ALTER TABLE 权限。
diff --git a/MEDICINE_LIBRARY_GUIDE.md b/MEDICINE_LIBRARY_GUIDE.md
deleted file mode 100644
index 17931860..00000000
--- a/MEDICINE_LIBRARY_GUIDE.md
+++ /dev/null
@@ -1,239 +0,0 @@
-# 药品库系统安装指南
-
-## 功能概述
-
-药品库管理系统提供了完整的中药材管理功能,包括:
-
-- 药材名称管理
-- 供应商信息管理
-- 单位规格管理
-- 价格管理(结算价、零售价)
-- 库存管理
-- 药品图片上传
-- 状态管理(启用/停用)
-- 备注信息
-
-## 安装步骤
-
-### 1. 数据库安装
-
-#### Windows 系统
-
-双击运行 `install_medicine_library.bat` 文件,按提示输入数据库信息:
-
-```bash
-install_medicine_library.bat
-```
-
-#### Linux/Mac 系统
-
-执行以下命令:
-
-```bash
-chmod +x install_medicine_library.sh
-./install_medicine_library.sh
-```
-
-或者手动导入 SQL 文件:
-
-```bash
-mysql -h localhost -u root -p your_database < install_medicine_library.sql
-```
-
-### 2. 后端文件说明
-
-安装脚本会创建以下文件:
-
-```
-server/
-├── app/
-│ ├── adminapi/
-│ │ ├── controller/doctor/
-│ │ │ └── MedicineController.php # 药品控制器
-│ │ ├── logic/doctor/
-│ │ │ └── MedicineLogic.php # 药品业务逻辑
-│ │ ├── validate/doctor/
-│ │ │ └── MedicineValidate.php # 药品验证器
-│ │ └── lists/doctor/
-│ │ └── MedicineLists.php # 药品列表
-│ └── common/
-│ └── model/doctor/
-│ └── Medicine.php # 药品模型
-```
-
-### 3. 前端文件说明
-
-```
-admin/
-├── src/
-│ ├── api/
-│ │ └── medicine.ts # 药品API接口
-│ └── views/doctor/
-│ └── medicine.vue # 药品管理页面
-```
-
-### 4. 数据库表结构
-
-创建的数据表:`la_doctor_medicine`
-
-| 字段名 | 类型 | 说明 |
-|--------|------|------|
-| id | int | 主键ID |
-| name | varchar(100) | 药材名称 |
-| supplier | varchar(100) | 供应商名称 |
-| unit | varchar(20) | 单位 |
-| settlement_price | decimal(10,2) | 结算价 |
-| retail_price | decimal(10,2) | 零售价 |
-| stock | int | 库存数量 |
-| image | varchar(255) | 药品图片 |
-| status | tinyint(1) | 状态:0=停用,1=启用 |
-| remark | varchar(500) | 备注 |
-| create_time | int | 创建时间 |
-| update_time | int | 更新时间 |
-| delete_time | int | 删除时间 |
-
-## 功能使用
-
-### 1. 访问药品库
-
-登录后台管理系统后,访问:`/doctor/medicine`
-
-### 2. 添加药品
-
-1. 点击右上角"添加药品"按钮
-2. 填写药品信息:
- - 药材名称(必填)
- - 供应商名称(必填)
- - 单位(必填,可选择:克、千克、斤、盒、瓶、袋、包、片、粒、支)
- - 结算价(必填)
- - 零售价(必填)
- - 库存数量
- - 药品图片(支持jpg、png格式,大小不超过2MB)
- - 状态(启用/停用)
- - 备注信息
-3. 点击"确定"保存
-
-### 3. 编辑药品
-
-1. 在列表中找到需要编辑的药品
-2. 点击"编辑"按钮
-3. 修改药品信息
-4. 点击"确定"保存
-
-### 4. 删除药品
-
-1. 在列表中找到需要删除的药品
-2. 点击"删除"按钮
-3. 确认删除操作
-
-### 5. 查询药品
-
-支持按以下条件查询:
-- 药材名称(模糊搜索)
-- 供应商名称(模糊搜索)
-
-### 6. 库存管理
-
-- 库存为0时显示红色标签(危险)
-- 库存小于10时显示黄色标签(警告)
-- 库存充足时显示绿色标签(正常)
-
-## API 接口说明
-
-### 1. 获取药品列表
-
-```
-GET /api/doctor.medicine/lists
-```
-
-参数:
-- page_no: 页码
-- page_size: 每页数量
-- name: 药材名称(可选)
-- supplier: 供应商名称(可选)
-
-### 2. 添加药品
-
-```
-POST /api/doctor.medicine/add
-```
-
-参数:
-- name: 药材名称
-- supplier: 供应商名称
-- unit: 单位
-- settlement_price: 结算价
-- retail_price: 零售价
-- stock: 库存
-- image: 图片URL
-- status: 状态
-- remark: 备注
-
-### 3. 编辑药品
-
-```
-POST /api/doctor.medicine/edit
-```
-
-参数:同添加接口,需额外传入 id
-
-### 4. 删除药品
-
-```
-POST /api/doctor.medicine/delete
-```
-
-参数:
-- id: 药品ID
-
-### 5. 药品详情
-
-```
-GET /api/doctor.medicine/detail
-```
-
-参数:
-- id: 药品ID
-
-## 示例数据
-
-安装脚本会自动插入5条示例数据:
-
-1. 当归 - 同仁堂
-2. 黄芪 - 云南白药
-3. 枸杞子 - 宁夏枸杞
-4. 人参 - 长白山人参
-5. 甘草 - 内蒙古甘草
-
-## 注意事项
-
-1. 确保数据库连接信息正确
-2. 确保有足够的数据库权限
-3. 图片上传需要配置正确的上传接口
-4. 建议定期备份药品数据
-5. 价格字段保留两位小数
-6. 库存不能为负数
-
-## 常见问题
-
-### 1. 安装失败
-
-- 检查数据库连接信息是否正确
-- 检查数据库用户是否有创建表的权限
-- 检查数据库是否已存在同名表
-
-### 2. 图片上传失败
-
-- 检查上传接口配置
-- 检查文件大小是否超过限制
-- 检查文件格式是否支持
-
-### 3. 数据保存失败
-
-- 检查必填字段是否填写完整
-- 检查价格格式是否正确
-- 检查网络连接是否正常
-
-## 技术支持
-
-如有问题,请联系技术支持团队。
diff --git a/ORDER_DETAIL_ADDRESS_DISPLAY_FIX.md b/ORDER_DETAIL_ADDRESS_DISPLAY_FIX.md
deleted file mode 100644
index b62c76d8..00000000
--- a/ORDER_DETAIL_ADDRESS_DISPLAY_FIX.md
+++ /dev/null
@@ -1,167 +0,0 @@
-# Order Detail Address Display Fix (订单详情地址显示修复)
-
-## Issue
-The order detail view was only showing the detailed address (`shipping_address`) without displaying the province, city, and district information.
-
-## Changes Made
-
-### 1. Added Computed Property for Full Address
-**File**: `admin/src/views/consumer/prescription/order_list.vue`
-
-Added `detailFullAddress` computed property that combines all address components:
-
-```typescript
-// 完整收货地址(省市区 + 详细地址)
-const detailFullAddress = computed(() => {
- const d = detailData.value
- if (!d) return '—'
-
- const parts = []
- if (d.shipping_province) parts.push(d.shipping_province)
- if (d.shipping_city) parts.push(d.shipping_city)
- if (d.shipping_district) parts.push(d.shipping_district)
- if (d.shipping_address) parts.push(d.shipping_address)
-
- return parts.length > 0 ? parts.join(' ') : '—'
-})
-```
-
-### 2. Updated Display Template
-Changed the shipping address display from:
-```vue
-
- {{ detailData.shipping_address }}
-
-```
-
-To:
-```vue
-
- {{ detailFullAddress }}
-
-```
-
-## Display Format
-
-### Before:
-```
-收货地址: 顶顶顶
-```
-
-### After:
-```
-收货地址: 北京市 北京市 丰台区 顶顶顶
-```
-
-## Logic
-
-The `detailFullAddress` computed property:
-1. Checks if `detailData` exists
-2. Collects non-empty address components in order:
- - `shipping_province` (省)
- - `shipping_city` (市)
- - `shipping_district` (区/县)
- - `shipping_address` (详细地址)
-3. Joins them with spaces
-4. Returns '—' if no address components exist
-
-## Examples
-
-### Complete Address:
-```
-Input:
- shipping_province: "四川省"
- shipping_city: "成都市"
- shipping_district: "武侯区"
- shipping_address: "天府大道中段666号"
-
-Output:
- 四川省 成都市 武侯区 天府大道中段666号
-```
-
-### Partial Address (Missing Province):
-```
-Input:
- shipping_province: null
- shipping_city: "成都市"
- shipping_district: "武侯区"
- shipping_address: "天府大道中段666号"
-
-Output:
- 成都市 武侯区 天府大道中段666号
-```
-
-### Only Detailed Address:
-```
-Input:
- shipping_province: null
- shipping_city: null
- shipping_district: null
- shipping_address: "天府大道中段666号"
-
-Output:
- 天府大道中段666号
-```
-
-### No Address:
-```
-Input:
- shipping_province: null
- shipping_city: null
- shipping_district: null
- shipping_address: null
-
-Output:
- —
-```
-
-## Testing Steps
-
-### 1. Test Complete Address Display
-1. Open an order that has province/city/district data
-2. Check the "收货地址" field in the detail view
-3. Verify it shows: `省 市 区 详细地址`
-
-### 2. Test Legacy Orders (No Region Data)
-1. Open an old order that only has `shipping_address`
-2. Verify it still displays the detailed address correctly
-3. Should show just the detailed address without extra spaces
-
-### 3. Test Empty Address
-1. Create an order without any address information
-2. Verify it displays "—" instead of empty string
-
-### 4. Test Different Address Combinations
-Test various combinations:
-- Full address (province + city + district + detail)
-- City + district + detail (no province)
-- District + detail (no province/city)
-- Detail only (no region data)
-- Empty (no data at all)
-
-## Related Files
-
-### Frontend:
-- `admin/src/views/consumer/prescription/order_list.vue`
- - Added `detailFullAddress` computed property
- - Updated shipping address display template
-
-### Backend:
-- `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
- - Already returns `shipping_province`, `shipping_city`, `shipping_district` in detail response
-
-### Database:
-- `tcm_prescription_order` table
- - Columns: `shipping_province`, `shipping_city`, `shipping_district`, `shipping_address`
-
-## Benefits
-
-✅ **Complete Information**: Users can now see the full address including province/city/district
-✅ **Better Readability**: Address components are clearly separated with spaces
-✅ **Backward Compatible**: Works with legacy orders that only have detailed address
-✅ **Graceful Handling**: Shows "—" when no address data exists
-✅ **Flexible**: Handles partial address data (missing province, city, or district)
-
-## Summary
-
-The order detail view now displays the complete shipping address including province, city, district, and detailed address, providing users with full address information at a glance!
diff --git a/ORDER_LIST_REGION_UPDATE.md b/ORDER_LIST_REGION_UPDATE.md
deleted file mode 100644
index c82f3536..00000000
--- a/ORDER_LIST_REGION_UPDATE.md
+++ /dev/null
@@ -1,191 +0,0 @@
-# Order List Region Selector Update (订单列表省市区选择器更新)
-
-## Changes Made
-
-Updated the order list view (`order_list.vue`) to use the same API-based region data loading as the prescription index view.
-
-### 1. Replaced Static Region Data with API Loading
-**File**: `admin/src/views/consumer/prescription/order_list.vue`
-
-#### Before:
-- Static hardcoded region data with limited provinces/cities
-- Only included: 四川省, 北京市, 上海市, 广东省
-
-#### After:
-- Dynamic API-based region data loading
-- Fetches complete China region data from `/api-proxy/area/ChinaCitys.json`
-- Includes all provinces, cities, and districts
-- Falls back to basic data if API fails
-
-### 2. Added Data Transformation Function
-Added `transformRegionData()` function to convert API format to el-cascader format:
-- API format: `{province, citys: [{city, areas: [{area}]}]}`
-- Cascader format: `{value, label, children: [{value, label, children: [{value, label}]}]}`
-
-### 3. Updated Cascader Component
-Added missing props to the cascader:
-- `emitPath: true` - Returns full path array instead of just last value
-- `filterable` - Enables search functionality
-
-### 4. Updated onMounted Hook
-Changed from:
-```typescript
-onMounted(() => {
- getLists()
-})
-```
-
-To:
-```typescript
-onMounted(async () => {
- await loadRegionData()
- getLists()
-})
-```
-
-### 5. Added Region Fields to Quick Edit
-Updated the quick tracking number edit function to include region fields:
-```typescript
-shipping_province: d.shipping_province || '',
-shipping_city: d.shipping_city || '',
-shipping_district: d.shipping_district || '',
-```
-
-This ensures region data is preserved when only updating tracking numbers.
-
-## Data Flow
-
-### Loading Region Data:
-1. Component mounts
-2. `loadRegionData()` fetches from `/api-proxy/area/ChinaCitys.json`
-3. `transformRegionData()` converts API format to cascader format
-4. `regionOptions` is populated with all China regions
-5. If API fails, falls back to basic region data
-
-### Editing Order with Region:
-1. User opens edit dialog
-2. Region data is loaded from order: `[province, city, district]`
-3. Cascader displays the selected region
-4. User can change region selection
-5. On save, region array is split into three fields:
- - `shipping_province`
- - `shipping_city`
- - `shipping_district`
-6. Backend saves the three separate fields
-
-### Quick Edit (Tracking Number):
-1. User clicks quick edit for tracking number
-2. System fetches full order details
-3. Includes region fields in the update payload
-4. Region data is preserved while updating tracking number
-
-## Code Structure
-
-### Region Data Loading:
-```typescript
-const regionOptions = ref([])
-
-const transformRegionData = (data: any[]) => {
- // Converts API format to cascader format
-}
-
-const loadRegionData = async () => {
- // Fetches from API and transforms data
- // Falls back to basic data on error
-}
-```
-
-### Region Field Handling in Edit:
-```typescript
-// Loading data into form:
-const province = d.shipping_province || ''
-const city = d.shipping_city || ''
-const district = d.shipping_district || ''
-if (province || city || district) {
- editForm.region = [province, city, district].filter(v => v !== '')
-}
-
-// Saving data from form:
-if (editForm.region && editForm.region.length > 0) {
- payload.shipping_province = editForm.region[0] || ''
- payload.shipping_city = editForm.region[1] || ''
- payload.shipping_district = editForm.region[2] || ''
-}
-```
-
-## Testing Steps
-
-### 1. Test Region Data Loading
-1. Open order list page
-2. Check browser console for:
- ```
- 开始加载省市区数据...
- 响应状态: 200
- 加载的数据条数: XX
- regionOptions 已设置,条数: XX
- ```
-3. If you see errors, check that dev server was restarted
-
-### 2. Test Edit Order with Region
-1. Click "编辑" on any order
-2. Check that the region cascader shows the current region (if set)
-3. Change the region selection
-4. Save the order
-5. Verify database is updated:
- ```sql
- SELECT shipping_province, shipping_city, shipping_district
- FROM tcm_prescription_order
- WHERE id = XX;
- ```
-
-### 3. Test Region Search
-1. Open edit dialog
-2. Click on region cascader
-3. Type a province/city name in the search box
-4. Verify search results appear
-5. Select a region from search results
-
-### 4. Test Quick Edit Preserves Region
-1. Find an order with region data set
-2. Click "快递单号" quick edit button
-3. Update tracking number
-4. Save
-5. Verify region data is still present in database
-
-### 5. Test Fallback Data
-1. Stop the dev server
-2. Open order list page
-3. Verify fallback region data is used (四川省, 北京市)
-4. Restart dev server
-5. Refresh page
-6. Verify full region data is loaded
-
-## Related Files
-
-### Frontend:
-- `admin/src/views/consumer/prescription/order_list.vue`
- - Added `loadRegionData()` and `transformRegionData()` functions
- - Updated `onMounted()` to load region data
- - Updated cascader component with `emitPath: true` and `filterable`
- - Added region fields to quick edit function
- - Region field handling already existed in main edit function
-
-### Backend:
-- `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
- - Already handles `shipping_province`, `shipping_city`, `shipping_district` in both `create()` and `edit()` methods
-
-### Configuration:
-- `admin/vite.config.ts`
- - Proxy configuration for `/api-proxy` already exists
-
-## Summary
-
-✅ Replaced static region data with API-based loading
-✅ Added data transformation function
-✅ Updated cascader with `emitPath: true` and `filterable`
-✅ Updated `onMounted()` to load region data
-✅ Added region fields to quick edit function
-✅ Fallback data available if API fails
-✅ Search functionality enabled
-
-The order list now uses the same comprehensive region data as the prescription index, providing access to all provinces, cities, and districts in China!
diff --git a/ORDER_STATUS_TABS_UI.md b/ORDER_STATUS_TABS_UI.md
deleted file mode 100644
index 8bb22581..00000000
--- a/ORDER_STATUS_TABS_UI.md
+++ /dev/null
@@ -1,299 +0,0 @@
-# Order Status Tabs UI (订单状态标签页UI)
-
-## Feature
-Redesigned the fulfillment status filter from a dropdown select to clickable tabs for better user experience and faster filtering.
-
-## Changes Made
-
-### File: `admin/src/views/consumer/prescription/order_list.vue`
-
-#### 1. Replaced Dropdown with Tab UI
-**Before:**
-```vue
-
-
-
-
-
-
-
-
-
-```
-
-**After:**
-```vue
-
-
-
-
-
- {{ tab.label }}
-
- {{ tab.count }}
-
-
-
-
-
-```
-
-#### 2. Added Status Tabs Configuration
-```typescript
-// 状态标签页配置
-const statusTabs = ref([
- { label: '全部', value: '' as number | '', count: undefined },
- { label: '待双审通过', value: 1, count: undefined },
- { label: '履约中', value: 2, count: undefined },
- { label: '已发货', value: 5, count: undefined },
- { label: '已完成', value: 3, count: undefined },
- { label: '已取消', value: 4, count: undefined }
-])
-```
-
-#### 3. Added Click Handler
-```typescript
-// 处理状态标签点击
-const handleStatusTabClick = (value: number | '') => {
- queryParams.fulfillment_status = value
- resetPage()
-}
-```
-
-## UI Design
-
-### Visual States
-
-**Active Tab:**
-- Primary color background (`bg-primary`)
-- White text
-- Shadow effect
-- Badge with semi-transparent white background
-
-**Inactive Tab:**
-- Light gray background (`bg-gray-50`)
-- Gray text
-- Hover effect (lighter gray)
-- Badge with gray background
-
-### Layout
-- Horizontal flex layout with gap
-- Wraps on smaller screens
-- Smooth transitions (200ms)
-- Rounded corners
-- Consistent padding
-
-### Badge (Count Display)
-- Small text size
-- Rounded badge
-- Shows count for each status (optional feature)
-- Different styling for active/inactive states
-
-## User Experience Improvements
-
-### Before (Dropdown):
-1. User clicks dropdown
-2. Dropdown opens with options
-3. User scrolls to find status
-4. User clicks option
-5. Dropdown closes
-6. User clicks "查询" button
-
-**Total: 3-4 clicks + scrolling**
-
-### After (Tabs):
-1. User clicks tab
-2. Filter applies immediately
-
-**Total: 1 click**
-
-### Benefits:
-- ✅ **Faster filtering**: One-click access to any status
-- ✅ **Visual feedback**: Clear indication of current filter
-- ✅ **Better discoverability**: All options visible at once
-- ✅ **No extra clicks**: Auto-triggers search on click
-- ✅ **Modern UI**: Follows common tab pattern
-- ✅ **Mobile friendly**: Wraps on small screens
-
-## Features
-
-### 1. Active State Indication
-The currently selected tab is highlighted with primary color, making it immediately clear which filter is active.
-
-### 2. Hover Effects
-Inactive tabs show a subtle hover effect, indicating they are clickable.
-
-### 3. Count Badges (Optional)
-Each tab can display a count badge showing the number of orders in that status. Currently set to `undefined` but can be populated with actual counts.
-
-### 4. Responsive Design
-Tabs wrap to multiple lines on smaller screens using `flex-wrap`.
-
-### 5. Smooth Transitions
-All state changes have smooth 200ms transitions for a polished feel.
-
-## Implementation Details
-
-### Tab Configuration
-```typescript
-{
- label: string, // Display text
- value: number | '', // Filter value ('' for "All")
- count: number | undefined // Optional count badge
-}
-```
-
-### Click Handler Logic
-```typescript
-const handleStatusTabClick = (value: number | '') => {
- queryParams.fulfillment_status = value // Update filter
- resetPage() // Trigger search
-}
-```
-
-### Styling Classes
-- `bg-primary`: Primary brand color (active state)
-- `bg-gray-50`: Light gray (inactive state)
-- `hover:bg-gray-100`: Hover effect
-- `shadow-md`: Shadow for active tab
-- `transition-all duration-200`: Smooth transitions
-- `select-none`: Prevent text selection on click
-
-## Future Enhancements
-
-### 1. Add Count Badges
-Fetch and display order counts for each status:
-
-```typescript
-// After fetching data
-const updateStatusCounts = (data: any) => {
- statusTabs.value[0].count = data.total
- statusTabs.value[1].count = data.pending
- statusTabs.value[2].count = data.processing
- // ... etc
-}
-```
-
-### 2. Add Icons
-Add status-specific icons for better visual recognition:
-
-```vue
-
-```
-
-### 3. Add Keyboard Navigation
-Support arrow keys for tab navigation:
-
-```typescript
-const handleKeydown = (e: KeyboardEvent) => {
- if (e.key === 'ArrowLeft') {
- // Navigate to previous tab
- } else if (e.key === 'ArrowRight') {
- // Navigate to next tab
- }
-}
-```
-
-### 4. Add Loading State
-Show loading indicator on active tab while fetching:
-
-```vue
-
-
-
-```
-
-## Testing Steps
-
-### 1. Test Tab Switching
-1. Open order list page
-2. Click on different status tabs
-3. Verify:
- - Active tab is highlighted
- - Table updates with filtered results
- - URL parameters update (if using router)
-
-### 2. Test Visual States
-1. Hover over inactive tabs
-2. Verify hover effect appears
-3. Click a tab
-4. Verify active state styling applies
-
-### 3. Test "全部" Tab
-1. Click "全部" tab
-2. Verify all orders are shown
-3. Verify no status filter is applied
-
-### 4. Test Responsive Behavior
-1. Resize browser window to small width
-2. Verify tabs wrap to multiple lines
-3. Verify all tabs remain accessible
-
-### 5. Test with Search
-1. Enter order number in search
-2. Click different status tabs
-3. Verify both filters work together
-
-## Accessibility
-
-### Current Implementation:
-- ✅ Keyboard accessible (clickable divs)
-- ✅ Visual feedback on hover
-- ✅ Clear active state indication
-- ✅ Sufficient color contrast
-
-### Recommended Improvements:
-- Add `role="tab"` and `aria-selected` attributes
-- Add `tabindex="0"` for keyboard navigation
-- Add keyboard event handlers (Enter/Space)
-- Add `aria-label` for screen readers
-
-### Example:
-```vue
-
-```
-
-## Related Files
-
-### Frontend:
-- `admin/src/views/consumer/prescription/order_list.vue`
- - Added status tabs UI
- - Added `statusTabs` configuration
- - Added `handleStatusTabClick` handler
- - Removed dropdown select
-
-## Summary
-
-✅ Replaced dropdown with clickable tabs
-✅ One-click filtering (no need to click "查询")
-✅ Visual active state indication
-✅ Hover effects for better UX
-✅ Responsive design with flex-wrap
-✅ Smooth transitions
-✅ Support for count badges (optional)
-✅ Cleaner, more modern UI
-
-The status filter is now much more intuitive and efficient, following modern UI patterns commonly seen in admin dashboards!
diff --git a/ORDER_SUPERVISOR_ROLE_CONFIG_FIX.md b/ORDER_SUPERVISOR_ROLE_CONFIG_FIX.md
deleted file mode 100644
index 319756ca..00000000
--- a/ORDER_SUPERVISOR_ROLE_CONFIG_FIX.md
+++ /dev/null
@@ -1,308 +0,0 @@
-# 订单管理员角色配置修复
-
-## 问题描述
-
-编辑业务订单时,关联支付单失败,提示"无权限关联所选支付单"。
-
-**错误信息**:
-```json
-{
- "code": 0,
- "show": 1,
- "msg": "无权限关联所选支付单",
- "data": []
-}
-```
-
-**接口**: `/adminapi/tcm.prescriptionOrder/edit`
-
-## 问题原因
-
-`OrderLogic::isOrderListSupervisor()` 方法中硬编码了管理员角色 `[0, 3]`,没有读取配置文件中的 `order_edit_all_roles` 配置。
-
-### 配置文件
-
-**文件**: `server/config/project.php`
-
-```php
-'order_edit_all_roles' => [0, 3, 6], // 0=超级管理员, 3=管理员, 6=药师
-```
-
-### 代码问题
-
-**文件**: `server/app/adminapi/logic/order/OrderLogic.php`
-
-**修改前**:
-```php
-public static function isOrderListSupervisor(int $adminId, array $adminInfo): bool
-{
- if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) {
- return true;
- }
- $supervisorRoles = [0, 3]; // 硬编码,没有读取配置
- $myRoles = array_map('intval', $adminInfo['role_id'] ?? []);
-
- return count(array_intersect($myRoles, $supervisorRoles)) > 0;
-}
-```
-
-**问题**:
-- 硬编码了 `[0, 3]`,即使配置文件中设置了 `[0, 3, 6]`,角色 6(药师)也无法获得管理员权限
-- 导致药师无法关联其他人创建的支付单
-
-## 解决方案
-
-修改 `isOrderListSupervisor()` 方法,从配置文件读取管理员角色列表。
-
-**修改后**:
-```php
-public static function isOrderListSupervisor(int $adminId, array $adminInfo): bool
-{
- if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) {
- return true;
- }
- $supervisorRoles = config('project.order_edit_all_roles', [0, 3]); // 从配置读取
- $myRoles = array_map('intval', $adminInfo['role_id'] ?? []);
-
- return count(array_intersect($myRoles, $supervisorRoles)) > 0;
-}
-```
-
-**改进点**:
-- 从配置文件读取 `order_edit_all_roles`
-- 支持动态配置管理员角色
-- 默认值为 `[0, 3]`,保持向后兼容
-
-## 权限规则
-
-### 支付单关联权限
-
-在关联支付单到业务订单时,需要满足以下条件之一:
-
-1. **超级管理员** (`root = 1`)
- - 可以关联所有支付单
-
-2. **管理员角色** (`order_edit_all_roles` 配置的角色)
- - 默认:`[0, 3]`(超级管理员、管理员)
- - 可配置:`[0, 3, 6]`(超级管理员、管理员、药师)
- - 可以关联所有支付单
-
-3. **诊单医助** (`assistant_id` 匹配)
- - 可以关联该诊单的所有支付单
-
-4. **支付单创建人** (`creator_id` 匹配)
- - 只能关联自己创建的支付单
-
-### 权限检查逻辑
-
-```php
-public static function assertPayOrdersLinkable(array $ids, int $diagnosisId, int $adminId, array $adminInfo): ?string
-{
- // ... 其他检查 ...
-
- $supervisor = self::isOrderListSupervisor($adminId, $adminInfo);
- $assistantId = (int) Diagnosis::where('id', $diagnosisId)->value('assistant_id');
- $isDiagAssistant = $assistantId === $adminId;
-
- foreach ($orders as $o) {
- // 检查权限
- if (! $supervisor && ! $isDiagAssistant && (int) $o->creator_id !== $adminId) {
- return '无权限关联所选支付单';
- }
- }
-
- return null;
-}
-```
-
-## 配置说明
-
-### 配置文件位置
-
-`server/config/project.php`
-
-### 配置项
-
-```php
-return [
- // 订单管理全部权限角色(可以查看和编辑所有订单)
- 'order_edit_all_roles' => [0, 3, 6],
-
- // 其他相关配置
- 'prescription_library_manage_all_roles' => [0, 3], // 处方库管理全部权限角色
- 'prescription_audit_roles' => [0, 3, 6], // 处方审核权限角色
- 'prescription_order_finance_roles' => [0, 3], // 处方订单财务权限角色
- 'prescription_order_payment_audit_roles' => [0, 3], // 处方订单支付审核权限角色
-];
-```
-
-### 角色ID说明
-
-| 角色ID | 角色名称 | 说明 |
-|-------|---------|------|
-| 0 | 超级管理员 | 拥有所有权限 |
-| 1 | 医生 | 开方、诊断等医疗权限 |
-| 2 | 医助 | 协助医生工作 |
-| 3 | 管理员 | 管理订单、审核等权限 |
-| 6 | 药师 | 审核处方、管理药品等权限 |
-
-## 使用场景
-
-### 场景1:药师关联支付单
-
-**配置前**:
-```php
-'order_edit_all_roles' => [0, 3], // 药师(角色6)无权限
-```
-
-**问题**:
-- 药师无法关联其他人创建的支付单
-- 提示"无权限关联所选支付单"
-
-**配置后**:
-```php
-'order_edit_all_roles' => [0, 3, 6], // 药师(角色6)有权限
-```
-
-**效果**:
-- 药师可以关联所有支付单
-- 可以正常编辑业务订单
-
-### 场景2:医生关联支付单
-
-**情况1:医生是支付单创建人**
-- 可以关联自己创建的支付单
-- 不需要管理员权限
-
-**情况2:医生不是支付单创建人**
-- 如果医生是诊单医助,可以关联该诊单的所有支付单
-- 如果医生不是诊单医助,无法关联其他人创建的支付单
-
-**情况3:医生是管理员角色**
-- 如果医生同时拥有管理员角色(如角色3),可以关联所有支付单
-
-### 场景3:管理员关联支付单
-
-**配置**:
-```php
-'order_edit_all_roles' => [0, 3, 6],
-```
-
-**效果**:
-- 超级管理员(角色0):可以关联所有支付单
-- 管理员(角色3):可以关联所有支付单
-- 药师(角色6):可以关联所有支付单
-
-## 测试建议
-
-### 测试用例 1: 药师关联支付单
-
-**前提条件**:
-- 配置 `order_edit_all_roles` 包含角色 6
-- 用户角色为药师(角色6)
-
-**测试步骤**:
-1. 创建一个业务订单
-2. 选择其他人创建的支付单
-3. 点击保存
-
-**预期结果**:
-- 保存成功
-- 支付单成功关联到业务订单
-
-### 测试用例 2: 医生关联自己的支付单
-
-**前提条件**:
-- 用户角色为医生(角色1)
-- 支付单由该医生创建
-
-**测试步骤**:
-1. 创建一个业务订单
-2. 选择自己创建的支付单
-3. 点击保存
-
-**预期结果**:
-- 保存成功
-- 支付单成功关联到业务订单
-
-### 测试用例 3: 医生关联其他人的支付单(无权限)
-
-**前提条件**:
-- 用户角色为医生(角色1)
-- 支付单由其他人创建
-- 医生不是诊单医助
-
-**测试步骤**:
-1. 创建一个业务订单
-2. 选择其他人创建的支付单
-3. 点击保存
-
-**预期结果**:
-- 保存失败
-- 提示"无权限关联所选支付单"
-
-### 测试用例 4: 诊单医助关联支付单
-
-**前提条件**:
-- 用户是该诊单的医助
-- 支付单由其他人创建
-
-**测试步骤**:
-1. 创建一个业务订单
-2. 选择该诊单的支付单(其他人创建)
-3. 点击保存
-
-**预期结果**:
-- 保存成功
-- 支付单成功关联到业务订单
-
-## 相关配置
-
-### 其他权限配置
-
-在 `server/config/project.php` 中,还有其他相关的权限配置:
-
-```php
-return [
- // 订单管理全部权限角色
- 'order_edit_all_roles' => [0, 3, 6],
-
- // 处方库管理全部权限角色(可以查看所有处方)
- 'prescription_library_manage_all_roles' => [0, 3],
-
- // 处方审核权限角色
- 'prescription_audit_roles' => [0, 3, 6],
-
- // 处方订单财务权限角色(可以查看内部成本)
- 'prescription_order_finance_roles' => [0, 3],
-
- // 处方订单支付审核权限角色
- 'prescription_order_payment_audit_roles' => [0, 3],
-];
-```
-
-### 配置建议
-
-根据实际业务需求配置角色权限:
-
-**推荐配置**:
-```php
-return [
- 'order_edit_all_roles' => [0, 3, 6], // 管理员和药师可以管理所有订单
- 'prescription_library_manage_all_roles' => [0, 3], // 管理员可以查看所有处方
- 'prescription_audit_roles' => [0, 3, 6], // 管理员和药师可以审核处方
- 'prescription_order_finance_roles' => [0, 3], // 管理员可以查看财务数据
- 'prescription_order_payment_audit_roles' => [0, 3], // 管理员可以审核支付单
-];
-```
-
-## 总结
-
-通过修改 `isOrderListSupervisor()` 方法,确保了:
-- ✅ 从配置文件读取管理员角色列表
-- ✅ 支持动态配置管理员角色
-- ✅ 药师(角色6)可以关联所有支付单
-- ✅ 保持向后兼容(默认值为 `[0, 3]`)
-- ✅ 统一权限管理,避免硬编码
-
-现在,只需要在配置文件中添加角色ID,就可以赋予该角色管理员权限,无需修改代码。
diff --git a/PAYMENT_AUDIT_PERMISSION_FIX.md b/PAYMENT_AUDIT_PERMISSION_FIX.md
deleted file mode 100644
index 6fb687de..00000000
--- a/PAYMENT_AUDIT_PERMISSION_FIX.md
+++ /dev/null
@@ -1,84 +0,0 @@
-# 支付单审核权限修复
-
-## 问题描述
-
-接口 `/adminapi/tcm.prescriptionOrder/auditPayment` 返回错误:
-```json
-{"code":0,"show":1,"msg":"无支付单审核权限","data":[]}
-```
-
-药师角色(角色ID=6)无法审核支付单。
-
-## 问题原因
-
-配置文件 `server/config/project.php` 中的 `prescription_order_payment_audit_roles` 已经包含角色 6,但由于 ThinkPHP 的配置缓存机制,旧的配置仍在使用。
-
-## 解决方案
-
-清除 ThinkPHP 缓存,使新配置生效:
-
-```bash
-cd server
-php think clear
-```
-
-## 权限配置确认
-
-文件:`server/config/project.php`
-
-```php
-// 业务订单「关联支付单」审核:以下角色 ID + root 可操作(可与财务角色一致)
-'prescription_order_payment_audit_roles' => [0, 3, 6],
-```
-
-- 角色 0:超级管理员
-- 角色 3:管理员
-- 角色 6:药师
-
-## 权限检查逻辑
-
-文件:`server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
-
-```php
-public static function canAuditPaymentSlipOrder(array $adminInfo): bool
-{
- if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) {
- return true;
- }
- $allow = Config::get('project.prescription_order_payment_audit_roles', [0, 3]);
-
- return self::roleIntersect($adminInfo, is_array($allow) ? $allow : []);
-}
-```
-
-该方法从配置文件读取允许的角色列表,并检查当前用户的角色是否在列表中。
-
-## 测试验证
-
-1. 清除缓存后,药师角色(角色ID=6)应该能够:
- - 调用 `/adminapi/tcm.prescriptionOrder/auditPayment` 接口
- - 审核通过或驳回支付单
-
-2. 验证步骤:
- - 使用药师账号登录
- - 打开业务订单详情
- - 点击"支付单审核"按钮
- - 确认可以正常审核
-
-## 相关配置总结
-
-药师角色(角色ID=6)的完整权限配置:
-
-```php
-'order_edit_all_roles' => [0, 3, 6], // 订单管理全部权限
-'prescription_library_manage_all_roles' => [0, 3], // 处方库管理全部权限
-'prescription_audit_roles' => [0, 3, 6], // 消费者处方审核权限
-'prescription_order_finance_roles' => [0, 3], // 财务字段查看权限
-'prescription_order_payment_audit_roles' => [0, 3, 6], // 支付单审核权限
-```
-
-## 注意事项
-
-1. 每次修改 `server/config/project.php` 后,都需要清除缓存
-2. 生产环境部署时,确保运行 `php think clear` 命令
-3. 如果问题仍然存在,检查用户的角色ID是否正确分配
diff --git a/PHARMACIST_ROLE_PERMISSIONS.md b/PHARMACIST_ROLE_PERMISSIONS.md
deleted file mode 100644
index 32068c37..00000000
--- a/PHARMACIST_ROLE_PERMISSIONS.md
+++ /dev/null
@@ -1,358 +0,0 @@
-# 药师角色权限配置
-
-## 概述
-
-为药师角色(角色ID = 6)配置完整的权限,使其能够:
-- 审核处方
-- 审核支付单
-- 管理订单
-- 关联支付单
-
-## 配置文件
-
-**文件**: `server/config/project.php`
-
-### 完整配置
-
-```php
-return [
- // 订单管理全部权限角色
- 'order_edit_all_roles' => [0, 3, 6],
-
- // 处方库管理全部权限角色
- 'prescription_library_manage_all_roles' => [0, 3],
-
- // 消费者处方审核权限角色
- 'prescription_audit_roles' => [0, 3, 6],
-
- // 处方业务订单财务字段查看权限角色
- 'prescription_order_finance_roles' => [0, 3],
-
- // 业务订单支付单审核权限角色
- 'prescription_order_payment_audit_roles' => [0, 3, 6],
-];
-```
-
-## 权限说明
-
-### 1. 订单管理权限 (`order_edit_all_roles`)
-
-**配置**: `[0, 3, 6]`
-
-**权限内容**:
-- 查看所有订单(不限制创建人)
-- 编辑所有订单
-- 关联所有支付单(不限制创建人)
-
-**适用场景**:
-- 编辑业务订单
-- 关联支付单到业务订单
-- 查看订单列表
-
-**角色**:
-- 0: 超级管理员
-- 3: 管理员
-- 6: 药师 ✅
-
-### 2. 处方库管理权限 (`prescription_library_manage_all_roles`)
-
-**配置**: `[0, 3]`
-
-**权限内容**:
-- 查看所有处方库模板
-- 编辑所有处方库模板
-- 删除所有处方库模板
-
-**适用场景**:
-- 管理处方库
-- 查看处方模板
-
-**角色**:
-- 0: 超级管理员
-- 3: 管理员
-
-**说明**: 药师不需要此权限,因为处方库主要由医生和管理员管理
-
-### 3. 消费者处方审核权限 (`prescription_audit_roles`)
-
-**配置**: `[0, 3, 6]`
-
-**权限内容**:
-- 审核待审核的处方
-- 通过或驳回处方
-- 驳回时会同时作废处方
-
-**适用场景**:
-- 消费者处方列表中的"审核"按钮
-- 处方审核流程
-
-**角色**:
-- 0: 超级管理员
-- 3: 管理员
-- 6: 药师 ✅
-
-### 4. 财务字段查看权限 (`prescription_order_finance_roles`)
-
-**配置**: `[0, 3]`
-
-**权限内容**:
-- 查看业务订单的 `internal_cost`(内部成本)字段
-- 查看其他财务相关字段
-
-**适用场景**:
-- 业务订单详情
-- 业务订单列表
-
-**角色**:
-- 0: 超级管理员
-- 3: 管理员
-
-**说明**: 药师不需要此权限,财务数据仅管理员可见
-
-### 5. 支付单审核权限 (`prescription_order_payment_audit_roles`)
-
-**配置**: `[0, 3, 6]`
-
-**权限内容**:
-- 审核业务订单的关联支付单
-- 通过或驳回支付单审核
-
-**适用场景**:
-- 业务订单详情中的"支付审核"按钮
-- 支付单审核流程
-
-**角色**:
-- 0: 超级管理员
-- 3: 管理员
-- 6: 药师 ✅
-
-## 角色权限矩阵
-
-| 权限 | 超级管理员(0) | 医生(1) | 医助(2) | 管理员(3) | 药师(6) |
-|-----|-------------|---------|---------|----------|---------|
-| 订单管理全部权限 | ✅ | ❌ | ❌ | ✅ | ✅ |
-| 处方库管理全部权限 | ✅ | ❌ | ❌ | ✅ | ❌ |
-| 消费者处方审核 | ✅ | ❌ | ❌ | ✅ | ✅ |
-| 财务字段查看 | ✅ | ❌ | ❌ | ✅ | ❌ |
-| 支付单审核 | ✅ | ❌ | ❌ | ✅ | ✅ |
-
-## 修改记录
-
-### 修改1: 订单管理权限
-
-**文件**: `server/app/adminapi/logic/order/OrderLogic.php`
-
-**问题**: 硬编码了管理员角色 `[0, 3]`,没有读取配置
-
-**修改前**:
-```php
-public static function isOrderListSupervisor(int $adminId, array $adminInfo): bool
-{
- if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) {
- return true;
- }
- $supervisorRoles = [0, 3]; // 硬编码
- $myRoles = array_map('intval', $adminInfo['role_id'] ?? []);
-
- return count(array_intersect($myRoles, $supervisorRoles)) > 0;
-}
-```
-
-**修改后**:
-```php
-public static function isOrderListSupervisor(int $adminId, array $adminInfo): bool
-{
- if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) {
- return true;
- }
- $supervisorRoles = config('project.order_edit_all_roles', [0, 3]); // 从配置读取
- $myRoles = array_map('intval', $adminInfo['role_id'] ?? []);
-
- return count(array_intersect($myRoles, $supervisorRoles)) > 0;
-}
-```
-
-### 修改2: 支付单审核权限
-
-**文件**: `server/config/project.php`
-
-**修改前**:
-```php
-'prescription_order_payment_audit_roles' => [0, 3],
-```
-
-**修改后**:
-```php
-'prescription_order_payment_audit_roles' => [0, 3, 6],
-```
-
-## 使用场景
-
-### 场景1: 药师审核处方
-
-**流程**:
-1. 医生创建处方(待审核状态)
-2. 药师登录系统
-3. 进入消费者处方列表
-4. 点击"审核"按钮
-5. 选择"通过"或"驳回"
-6. 填写审核意见
-7. 提交审核
-
-**权限检查**:
-- `prescription_audit_roles` 包含角色 6 ✅
-- 药师可以审核处方
-
-### 场景2: 药师审核支付单
-
-**流程**:
-1. 管理员创建业务订单并关联支付单
-2. 药师登录系统
-3. 进入业务订单详情
-4. 点击"支付审核"按钮
-5. 选择"通过"或"驳回"
-6. 填写审核意见
-7. 提交审核
-
-**权限检查**:
-- `prescription_order_payment_audit_roles` 包含角色 6 ✅
-- 药师可以审核支付单
-
-### 场景3: 药师关联支付单
-
-**流程**:
-1. 药师创建业务订单
-2. 选择支付单(其他人创建的)
-3. 点击保存
-
-**权限检查**:
-- `order_edit_all_roles` 包含角色 6 ✅
-- 药师可以关联所有支付单
-
-### 场景4: 药师编辑订单
-
-**流程**:
-1. 药师查看业务订单列表
-2. 点击"编辑"按钮(其他人创建的订单)
-3. 修改订单信息
-4. 点击保存
-
-**权限检查**:
-- `order_edit_all_roles` 包含角色 6 ✅
-- 药师可以编辑所有订单
-
-## 测试建议
-
-### 测试用例 1: 药师审核处方
-
-**前提条件**:
-- 用户角色为药师(角色6)
-- 存在待审核的处方
-
-**测试步骤**:
-1. 登录药师账号
-2. 进入消费者处方列表
-3. 找到待审核的处方
-4. 点击"审核"按钮
-5. 选择"通过"
-6. 点击确定
-
-**预期结果**:
-- 审核成功
-- 处方状态变为"已通过"
-
-### 测试用例 2: 药师审核支付单
-
-**前提条件**:
-- 用户角色为药师(角色6)
-- 存在待审核的业务订单
-
-**测试步骤**:
-1. 登录药师账号
-2. 进入业务订单列表
-3. 点击"查看"按钮
-4. 点击"支付审核"按钮
-5. 选择"通过"
-6. 点击确定
-
-**预期结果**:
-- 审核成功
-- 支付单审核状态变为"已通过"
-
-### 测试用例 3: 药师关联支付单
-
-**前提条件**:
-- 用户角色为药师(角色6)
-- 存在其他人创建的支付单
-
-**测试步骤**:
-1. 登录药师账号
-2. 创建业务订单
-3. 选择其他人创建的支付单
-4. 点击保存
-
-**预期结果**:
-- 保存成功
-- 支付单成功关联到业务订单
-
-### 测试用例 4: 药师编辑订单
-
-**前提条件**:
-- 用户角色为药师(角色6)
-- 存在其他人创建的业务订单
-
-**测试步骤**:
-1. 登录药师账号
-2. 进入业务订单列表
-3. 点击"编辑"按钮(其他人创建的订单)
-4. 修改订单信息
-5. 点击保存
-
-**预期结果**:
-- 保存成功
-- 订单信息更新
-
-## 错误排查
-
-### 错误1: "无权限关联所选支付单"
-
-**原因**: `order_edit_all_roles` 配置中没有包含角色 6
-
-**解决方案**:
-```php
-'order_edit_all_roles' => [0, 3, 6], // 添加角色 6
-```
-
-### 错误2: "无支付单审核权限"
-
-**原因**: `prescription_order_payment_audit_roles` 配置中没有包含角色 6
-
-**解决方案**:
-```php
-'prescription_order_payment_audit_roles' => [0, 3, 6], // 添加角色 6
-```
-
-### 错误3: "无处方审核权限"
-
-**原因**: `prescription_audit_roles` 配置中没有包含角色 6
-
-**解决方案**:
-```php
-'prescription_audit_roles' => [0, 3, 6], // 添加角色 6
-```
-
-## 相关文档
-
-- `ORDER_SUPERVISOR_ROLE_CONFIG_FIX.md` - 订单管理员角色配置修复文档
-- `server/config/project.php` - 项目配置文件
-
-## 总结
-
-通过配置文件,为药师角色(角色6)赋予了以下权限:
-- ✅ 订单管理全部权限(查看、编辑、关联支付单)
-- ✅ 消费者处方审核权限(通过、驳回)
-- ✅ 支付单审核权限(通过、驳回)
-- ❌ 处方库管理权限(不需要)
-- ❌ 财务字段查看权限(不需要)
-
-药师现在可以完整地参与处方审核和订单管理流程。
diff --git a/PINYIN_ABBREVIATION_GUIDE.md b/PINYIN_ABBREVIATION_GUIDE.md
deleted file mode 100644
index 3d1f0310..00000000
--- a/PINYIN_ABBREVIATION_GUIDE.md
+++ /dev/null
@@ -1,275 +0,0 @@
-# 药材名称拼音首字母功能说明
-
-## 概述
-系统使用 `overtrue/pinyin` 包自动生成中文药材名称的拼音首字母缩写,存储在 `name_pinyin_abbr` 字段中,用于快速检索。
-
-## 核心实现
-
-### 1. 依赖包
-```json
-"overtrue/pinyin": "^6.0"
-```
-已在 `server/composer.json` 中配置。
-
-### 2. 服务类:`MedicineNameAbbrService`
-**文件位置:** `server/app/common/service/doctor/MedicineNameAbbrService.php`
-
-```php
-join('');
-
- return strtolower($abbr);
- }
-}
-```
-
-**功能说明:**
-- 输入:中文药材名称(如 "当归")
-- 输出:拼音首字母小写连写(如 "dg")
-- 自动处理:去除空格、转小写
-- 容错:如果 Pinyin 类不存在,返回空字符串
-
-### 3. 使用示例
-
-#### 示例 1:当归
-```php
-MedicineNameAbbrService::build('当归');
-// 输出: "dg"
-```
-
-#### 示例 2:黄芪
-```php
-MedicineNameAbbrService::build('黄芪');
-// 输出: "hq"
-```
-
-#### 示例 3:党参
-```php
-MedicineNameAbbrService::build('党参');
-// 输出: "ds"
-```
-
-#### 示例 4:白术
-```php
-MedicineNameAbbrService::build('白术');
-// 输出: "bs"
-```
-
-## 自动生成时机
-
-### 1. 创建药材时
-**文件:** `server/app/adminapi/logic/doctor/MedicineLogic.php`
-
-```php
-Medicine::create([
- 'name' => $params['name'],
- 'name_pinyin_abbr' => MedicineNameAbbrService::build($params['name']),
- // ... 其他字段
-]);
-```
-
-### 2. 编辑药材时
-```php
-Medicine::update([
- 'id' => $params['id'],
- 'name' => $params['name'],
- 'name_pinyin_abbr' => MedicineNameAbbrService::build($params['name']),
- // ... 其他字段
-]);
-```
-
-## 批量回填命令
-
-### 命令:`sync_medicine_pinyin_abbr`
-**文件:** `server/app/common/command/SyncMedicinePinyinAbbr.php`
-
-用于批量生成或更新已有药材的拼音首字母。
-
-### 使用方法
-
-#### 1. 仅更新空字段(默认)
-```bash
-cd server
-php think sync_medicine_pinyin_abbr
-```
-只处理 `name_pinyin_abbr` 为空的记录。
-
-#### 2. 重算全部记录
-```bash
-php think sync_medicine_pinyin_abbr --all
-```
-重新计算所有药材的拼音首字母(覆盖现有值)。
-
-#### 3. 预览模式(不写入数据库)
-```bash
-php think sync_medicine_pinyin_abbr --dry
-```
-只统计将更新多少条,不实际写入。
-
-#### 4. 组合使用
-```bash
-php think sync_medicine_pinyin_abbr --all --dry
-```
-预览全部重算的结果。
-
-### 命令输出示例
-```
-模式: 仅空 abbr,待处理 150 条
-完成: 已更新 145 条,跳过 5 条。
-```
-
-## 检索功能
-
-### 列表搜索
-**文件:** `server/app/adminapi/lists/doctor/MedicineLists.php`
-
-```php
-$query->where(function ($q) use ($kw, $keyword) {
- $q->where('name_pinyin_abbr', 'like', '%' . $kw . '%')
- ->whereOr('name', 'like', '%' . $keyword . '%');
-});
-```
-
-**功能说明:**
-- 用户输入 "dg" 可以搜索到 "当归"
-- 用户输入 "当归" 也可以搜索到
-- 支持模糊匹配(LIKE 查询)
-
-### 搜索示例
-
-| 用户输入 | 匹配药材 |
-|---------|---------|
-| dg | 当归 |
-| hq | 黄芪 |
-| ds | 党参、丹参 |
-| bs | 白术、白芍 |
-| 当归 | 当归 |
-
-## 数据库字段
-
-### 表:`zyt_doctor_medicine`
-```sql
-`name_pinyin_abbr` varchar(64) DEFAULT NULL COMMENT '名称拼音首字母缩写(小写连写)'
-```
-
-### 索引建议
-如果药材数量很大(>10000条),建议添加索引:
-```sql
-ALTER TABLE `zyt_doctor_medicine`
-ADD INDEX `idx_name_pinyin_abbr` (`name_pinyin_abbr`);
-```
-
-## 常见问题
-
-### Q1: 如何安装 overtrue/pinyin?
-```bash
-cd server
-composer require overtrue/pinyin
-```
-(已在项目中安装,无需手动执行)
-
-### Q2: 多音字如何处理?
-`overtrue/pinyin` 会自动选择最常用的读音。例如:
-- "重" → "z" (重量) 或 "c" (重复)
-- 默认使用最常见的读音
-
-### Q3: 如何处理英文或数字?
-- 英文字母:保留原样并转小写
-- 数字:保留原样
-- 示例:`"维生素B12"` → `"wssbB12"` → `"wssbb12"`
-
-### Q4: 为什么有些药材搜索不到?
-可能原因:
-1. `name_pinyin_abbr` 字段为空 → 运行批量回填命令
-2. 药材名称包含特殊字符 → 检查数据质量
-3. 索引未生效 → 检查数据库索引
-
-### Q5: 如何在其他模块使用?
-```php
-use app\common\service\doctor\MedicineNameAbbrService;
-
-// 生成拼音首字母
-$abbr = MedicineNameAbbrService::build('当归');
-echo $abbr; // 输出: dg
-```
-
-## 性能优化建议
-
-### 1. 批量处理
-命令使用 `chunk(200)` 分批处理,避免内存溢出:
-```php
-$makeQuery()->chunk(200, function ($rows) {
- // 每次处理 200 条
-});
-```
-
-### 2. 异步生成
-对于大量数据,建议:
-- 使用队列异步处理
-- 在低峰期运行批量命令
-- 使用 `--dry` 先预览再执行
-
-### 3. 缓存策略
-如果药材数据不常变动,可以:
-- 缓存常用药材的拼音首字母
-- 使用 Redis 存储热门搜索词
-
-## 相关文件
-
-| 文件 | 说明 |
-|------|------|
-| `server/app/common/service/doctor/MedicineNameAbbrService.php` | 拼音首字母生成服务 |
-| `server/app/common/command/SyncMedicinePinyinAbbr.php` | 批量回填命令 |
-| `server/app/adminapi/logic/doctor/MedicineLogic.php` | 药材增删改逻辑 |
-| `server/app/adminapi/lists/doctor/MedicineLists.php` | 药材列表搜索 |
-| `server/app/common/model/doctor/Medicine.php` | 药材模型 |
-
-## 扩展应用
-
-此功能可以扩展到其他需要中文检索的模块:
-
-### 1. 患者姓名检索
-```php
-use app\common\service\doctor\MedicineNameAbbrService;
-
-$patientName = '张三';
-$abbr = MedicineNameAbbrService::build($patientName); // "zs"
-```
-
-### 2. 医生姓名检索
-```php
-$doctorName = '李医生';
-$abbr = MedicineNameAbbrService::build($doctorName); // "lys"
-```
-
-### 3. 诊断名称检索
-```php
-$diagnosis = '感冒';
-$abbr = MedicineNameAbbrService::build($diagnosis); // "gm"
-```
-
-## 总结
-
-- **核心类:** `MedicineNameAbbrService::build()`
-- **依赖包:** `overtrue/pinyin`
-- **存储字段:** `name_pinyin_abbr`
-- **批量命令:** `php think sync_medicine_pinyin_abbr`
-- **应用场景:** 药材快速检索、模糊搜索
-
-这个功能大大提升了中文药材名称的搜索体验,用户可以通过拼音首字母快速找到目标药材。
diff --git a/PRESCRIPTION_APPROVED_LOCK.md b/PRESCRIPTION_APPROVED_LOCK.md
deleted file mode 100644
index 06916ec1..00000000
--- a/PRESCRIPTION_APPROVED_LOCK.md
+++ /dev/null
@@ -1,384 +0,0 @@
-# 处方审核通过后锁定编辑和作废
-
-## 修改说明
-
-处方审核通过后,禁止编辑和作废操作,确保已审核通过的处方不被修改,保证处方的完整性和可追溯性。
-
-## 修改内容
-
-### 1. 前端修改
-
-**文件**: `admin/src/views/consumer/prescription/index.vue`
-
-#### 编辑按钮
-
-**修改前**:
-```vue
-
- 编辑
-
-```
-
-**修改后**:
-```vue
-
- 编辑
-
-```
-
-**改进点**:
-- 移除了业务订单驳回的例外情况
-- 只要处方审核通过且未作废,就不显示编辑按钮
-- 简化了逻辑,更加严格
-
-#### 删除按钮
-
-删除按钮已经使用了 `isApprovedActivePrescription(row)` 判断,无需修改:
-
-```vue
-
- 删除
-
-```
-
-#### 判断函数
-
-```typescript
-/** 已通过审核且未作废:仅可查看,不可编辑/删除 */
-function isApprovedActivePrescription(row: { audit_status?: number; void_status?: number }) {
- return Number(row.audit_status) === 1 && Number(row.void_status) !== 1
-}
-```
-
-### 2. 后端修改
-
-**文件**: `server/app/adminapi/logic/tcm/PrescriptionLogic.php`
-
-#### 编辑方法
-
-**修改前**:
-```php
-// 已通过且未作废:不可编辑;例外:业务订单「处方审核」已驳回时允许医生改方,保存后业务订单侧审核会重置为待审
-if ((int) ($prescription->audit_status ?? 1) === 1 && (int) ($prescription->void_status ?? 0) === 0) {
- if (!PrescriptionOrderLogic::allowDoctorEditApprovedPrescriptionDueToBusinessReject((int) $params['id'])) {
- self::setError('该处方已通过审核,不可编辑');
- return false;
- }
-}
-```
-
-**修改后**:
-```php
-// 已通过且未作废:不可编辑
-if ((int) ($prescription->audit_status ?? 1) === 1 && (int) ($prescription->void_status ?? 0) === 0) {
- self::setError('该处方已通过审核,不可编辑');
- return false;
-}
-```
-
-**改进点**:
-- 移除了业务订单驳回的例外情况
-- 简化了逻辑,更加严格
-- 不再调用 `PrescriptionOrderLogic::allowDoctorEditApprovedPrescriptionDueToBusinessReject()`
-
-#### 删除方法
-
-删除方法已经有正确的检查,无需修改:
-
-```php
-public static function delete(int $id): bool
-{
- try {
- $prescription = Prescription::find($id);
- if (!$prescription) {
- self::setError('处方不存在');
- return false;
- }
-
- if ((int) ($prescription->audit_status ?? 1) === 1 && (int) ($prescription->void_status ?? 0) === 0) {
- self::setError('该处方已通过审核,不可删除');
- return false;
- }
-
- $prescription->delete();
- return true;
- } catch (\Exception $e) {
- self::setError($e->getMessage());
- return false;
- }
-}
-```
-
-#### 审核方法(作废)
-
-审核驳回会同时作废处方,已经有正确的状态检查:
-
-```php
-public static function audit(int $id, string $action, string $remark, int $adminId, array $adminInfo): bool
-{
- // ...
-
- // 只有待审核状态才能进行审核
- if ((int) ($row->audit_status ?? 1) !== 0) {
- self::setError('当前状态不可审核');
- return false;
- }
-
- // ...
-
- if ($action === 'reject') {
- if (trim($remark) === '') {
- self::setError('驳回时请填写审核意见');
- return false;
- }
- $row->audit_status = 2;
- $row->audit_time = $now;
- $row->audit_by = $adminId;
- $row->audit_by_name = $name;
- $row->audit_remark = $remark;
- $row->void_status = 1; // 驳回时同时作废
- $row->void_time = $now;
- $row->void_by = $adminId;
- $row->void_by_name = $name;
-
- $ok = (bool) $row->save();
- if ($ok) {
- self::$lastAuditWecomNotify = self::notifyCreatorAuditResult($row, 'reject', $remark, $adminInfo);
- }
- return $ok;
- }
-
- // ...
-}
-```
-
-## 权限规则
-
-### 编辑权限
-
-处方可以编辑的条件:
-1. **待审核状态** (`audit_status = 0`)
-2. **已驳回状态** (`audit_status = 2`),且满足以下条件:
- - 该诊单当天没有其他已通过的处方
- - 编辑后会清除驳回状态,重新进入待审核
-
-处方不可编辑的条件:
-1. **已通过审核且未作废** (`audit_status = 1` && `void_status = 0`)
- - 即使业务订单驳回了处方审核,也不允许编辑
- - 必须先撤回业务订单,处方审核状态会重置为待审核,然后才能编辑
-
-### 删除权限
-
-处方可以删除的条件:
-1. **待审核状态** (`audit_status = 0`)
-2. **已驳回状态** (`audit_status = 2`)
-3. **已作废状态** (`void_status = 1`)
-
-处方不可删除的条件:
-1. **已通过审核且未作废** (`audit_status = 1` && `void_status = 0`)
-
-### 作废权限(审核驳回)
-
-处方可以作废的条件:
-1. **待审核状态** (`audit_status = 0`)
-2. 有审核权限的用户
-
-处方不可作废的条件:
-1. **已通过审核** (`audit_status = 1`)
-2. **已驳回** (`audit_status = 2`)
-
-## 业务流程
-
-### 场景1:处方审核通过后需要修改
-
-**旧流程**(已废弃):
-1. 处方审核通过
-2. 创建业务订单
-3. 业务订单处方审核驳回
-4. 医生可以直接编辑已通过的处方 ❌
-
-**新流程**(推荐):
-1. 处方审核通过
-2. 创建业务订单
-3. 业务订单处方审核驳回
-4. 管理员撤回业务订单(处方审核状态重置为待审核)
-5. 医生编辑处方
-6. 处方重新审核
-7. 创建新的业务订单
-
-### 场景2:处方审核通过后发现错误
-
-**解决方案**:
-1. 如果已创建业务订单,先撤回业务订单
-2. 处方审核状态会自动重置为待审核
-3. 医生编辑处方
-4. 处方重新审核
-5. 创建新的业务订单
-
-### 场景3:处方审核驳回后修改
-
-**流程**:
-1. 处方审核驳回(同时作废)
-2. 医生编辑处方(编辑后会清除驳回和作废状态)
-3. 处方重新进入待审核状态
-4. 重新审核
-
-## 优势
-
-### 1. 数据完整性
-- 已审核通过的处方不可修改,保证了处方的完整性
-- 避免审核后的处方被随意修改,导致审核记录与实际内容不符
-
-### 2. 可追溯性
-- 每次修改都需要重新审核,留下完整的审核记录
-- 便于追溯处方的修改历史和审核历史
-
-### 3. 流程规范
-- 强制执行"撤回 → 编辑 → 重新审核"的流程
-- 避免绕过审核流程直接修改处方
-
-### 4. 责任明确
-- 审核人员对已审核的处方负责
-- 修改处方需要重新审核,责任链清晰
-
-## 注意事项
-
-### 1. 业务订单撤回
-
-如果需要修改已审核通过的处方,必须先撤回业务订单:
-- 撤回业务订单会自动重置处方审核状态为待审核
-- 撤回后才能编辑处方
-- 编辑后需要重新审核处方
-- 审核通过后可以创建新的业务订单
-
-### 2. 处方驳回
-
-处方驳回会同时作废处方:
-- 驳回后处方状态变为"已驳回"(`audit_status = 2`)
-- 同时处方会被作废(`void_status = 1`)
-- 医生可以编辑已驳回的处方
-- 编辑后会清除驳回和作废状态,重新进入待审核
-
-### 3. 权限配置
-
-确保相关权限配置正确:
-- `cf.prescription/edit` - 编辑处方权限
-- `cf.prescription/del` - 删除处方权限
-- `cf.prescription/audit` - 审核处方权限
-- `tcm.prescriptionOrder/withdraw` - 撤回业务订单权限
-
-## 测试建议
-
-### 1. 编辑权限测试
-
-**测试用例 1: 待审核处方可以编辑**
-- 创建处方(待审核状态)
-- 点击"编辑"按钮
-- 预期:可以正常编辑
-
-**测试用例 2: 已通过处方不可编辑**
-- 创建处方并审核通过
-- 点击"编辑"按钮
-- 预期:按钮不显示或提示"该处方已通过审核,不可编辑"
-
-**测试用例 3: 业务订单驳回后不可直接编辑**
-- 创建处方并审核通过
-- 创建业务订单
-- 业务订单处方审核驳回
-- 点击"编辑"按钮
-- 预期:按钮不显示或提示"该处方已通过审核,不可编辑"
-
-**测试用例 4: 撤回业务订单后可以编辑**
-- 创建处方并审核通过
-- 创建业务订单
-- 撤回业务订单
-- 点击"编辑"按钮
-- 预期:可以正常编辑
-
-**测试用例 5: 已驳回处方可以编辑**
-- 创建处方
-- 审核驳回
-- 点击"编辑"按钮
-- 预期:可以正常编辑,编辑后清除驳回和作废状态
-
-### 2. 删除权限测试
-
-**测试用例 6: 待审核处方可以删除**
-- 创建处方(待审核状态)
-- 点击"删除"按钮
-- 预期:可以正常删除
-
-**测试用例 7: 已通过处方不可删除**
-- 创建处方并审核通过
-- 点击"删除"按钮
-- 预期:按钮不显示或提示"该处方已通过审核,不可删除"
-
-**测试用例 8: 已驳回处方可以删除**
-- 创建处方
-- 审核驳回
-- 点击"删除"按钮
-- 预期:可以正常删除
-
-### 3. 作废权限测试
-
-**测试用例 9: 待审核处方可以驳回作废**
-- 创建处方(待审核状态)
-- 点击"审核"按钮,选择"驳回"
-- 预期:处方被驳回并作废
-
-**测试用例 10: 已通过处方不可驳回作废**
-- 创建处方并审核通过
-- 点击"审核"按钮
-- 预期:按钮不显示或提示"当前状态不可审核"
-
-**测试用例 11: 已驳回处方不可再次驳回**
-- 创建处方
-- 审核驳回
-- 再次点击"审核"按钮
-- 预期:按钮不显示或提示"当前状态不可审核"
-
-### 4. 业务流程测试
-
-**测试用例 12: 完整的修改流程**
-1. 创建处方并审核通过
-2. 创建业务订单
-3. 业务订单处方审核驳回
-4. 尝试编辑处方 → 预期:不可编辑
-5. 撤回业务订单
-6. 编辑处方 → 预期:可以编辑
-7. 处方重新审核通过
-8. 创建新的业务订单 → 预期:成功
-
-## 相关文档
-
-- `PRESCRIPTION_ORDER_WITHDRAW_RESET_AUDIT.md` - 订单撤回重置审核状态文档
-- `BUG_FIXES_SUMMARY.md` - Bug修复总结文档
-
-## 总结
-
-通过移除业务订单驳回的例外情况,确保了:
-- ✅ 已审核通过的处方不可编辑
-- ✅ 已审核通过的处方不可删除
-- ✅ 已审核通过的处方不可作废(驳回)
-- ✅ 强制执行"撤回 → 编辑 → 重新审核"的规范流程
-- ✅ 保证处方的完整性和可追溯性
-- ✅ 责任链清晰,审核记录完整
-- ✅ 前后端双重校验,防止绕过限制
diff --git a/PRESCRIPTION_BUTTON_DISPLAY_CHECK.md b/PRESCRIPTION_BUTTON_DISPLAY_CHECK.md
deleted file mode 100644
index cad8914b..00000000
--- a/PRESCRIPTION_BUTTON_DISPLAY_CHECK.md
+++ /dev/null
@@ -1,204 +0,0 @@
-# 处方按钮显示检查
-
-## 问题描述
-
-用户反馈:处方审核通过后,"开方"按钮不显示了。
-
-## 代码分析
-
-### 前端代码
-
-**文件**: `admin/src/views/tcm/appointment/list.vue`
-
-#### 按钮显示逻辑
-
-```vue
-
- {{ prescriptionActionLabel(row) }}
-
-```
-
-#### 按钮文字逻辑
-
-```typescript
-function prescriptionActionLabel(row: any) {
- if (
- row?.prescription_audit_status === 1 &&
- Number(row?.prescription_void_status) !== 1
- ) {
- return '查看'
- }
- return '开方'
-}
-```
-
-**逻辑说明**:
-- 如果处方审核通过(`prescription_audit_status === 1`)且未作废(`prescription_void_status !== 1`),按钮文字显示"查看"
-- 否则,按钮文字显示"开方"
-
-### 后端代码
-
-**文件**: `server/app/adminapi/lists/doctor/AppointmentLists.php`
-
-```php
-$apptId = (int) ($item['id'] ?? 0);
-$apptRx = $rxByAppointmentId[$apptId] ?? null;
-$item['prescription_audit_status'] = $apptRx !== null ? (int) ($apptRx['audit_status'] ?? -1) : -1;
-$item['prescription_void_status'] = $apptRx !== null ? (int) ($apptRx['void_status'] ?? 0) : 0;
-```
-
-**逻辑说明**:
-- 如果预约有关联的处方,返回处方的审核状态和作废状态
-- 如果预约没有关联的处方,`prescription_audit_status` 为 `-1`,`prescription_void_status` 为 `0`
-
-## 可能的原因
-
-### 1. 权限问题
-
-按钮有权限检查 `v-perms="['tcm.diagnosis/kaifang']"`,如果用户没有这个权限,按钮会被隐藏。
-
-**检查方法**:
-1. 登录用户账号
-2. 检查用户角色是否有 `tcm.diagnosis/kaifang` 权限
-3. 在浏览器控制台查看按钮元素是否存在
-
-### 2. 数据问题
-
-后端返回的 `prescription_audit_status` 或 `prescription_void_status` 字段值不正确。
-
-**检查方法**:
-1. 打开浏览器开发者工具
-2. 查看预约列表接口的响应数据
-3. 检查 `prescription_audit_status` 和 `prescription_void_status` 字段的值
-
-**预期值**:
-- 未开方:`prescription_audit_status = -1`, `prescription_void_status = 0`
-- 待审核:`prescription_audit_status = 0`, `prescription_void_status = 0`
-- 已通过:`prescription_audit_status = 1`, `prescription_void_status = 0`
-- 已驳回:`prescription_audit_status = 2`, `prescription_void_status = 1`
-
-### 3. 按钮文字问题
-
-按钮确实显示了,但是文字从"开方"变成了"查看",用户可能误以为按钮消失了。
-
-**检查方法**:
-1. 查看预约列表
-2. 找到已审核通过的处方对应的预约
-3. 检查按钮文字是否显示为"查看"
-
-## 解决方案
-
-### 方案1:确认权限配置
-
-确保用户角色有 `tcm.diagnosis/kaifang` 权限:
-
-1. 进入后台管理 → 权限管理 → 角色管理
-2. 找到用户所属的角色
-3. 检查是否勾选了"中医诊断 → 开方"权限(`tcm.diagnosis/kaifang`)
-4. 如果没有勾选,勾选后保存
-
-### 方案2:检查数据返回
-
-在浏览器开发者工具中检查接口返回:
-
-1. 打开浏览器开发者工具(F12)
-2. 切换到 Network 标签
-3. 刷新预约列表页面
-4. 找到预约列表接口(通常是 `/doctor.appointment/lists`)
-5. 查看响应数据中的 `prescription_audit_status` 和 `prescription_void_status` 字段
-
-**示例数据**:
-```json
-{
- "id": 123,
- "patient_name": "张三",
- "prescription_audit_status": 1, // 1=已通过
- "prescription_void_status": 0, // 0=未作废
- "has_prescription": 1
-}
-```
-
-### 方案3:按钮文字说明
-
-如果按钮文字从"开方"变成了"查看",这是正常的:
-
-- **未开方或待审核**:按钮显示"开方",点击后可以创建或编辑处方
-- **已审核通过**:按钮显示"查看",点击后只能查看处方(只读模式)
-
-这是为了区分"可编辑"和"只读"两种模式。
-
-## 测试步骤
-
-### 测试1:未开方的预约
-
-1. 找到一个未开方的预约(`has_prescription = 0`)
-2. 检查按钮是否显示"开方"
-3. 点击按钮,应该可以创建新处方
-
-### 测试2:待审核的处方
-
-1. 创建一个处方(待审核状态)
-2. 返回预约列表
-3. 检查按钮是否显示"开方"
-4. 点击按钮,应该可以编辑处方
-
-### 测试3:已审核通过的处方
-
-1. 创建一个处方并审核通过
-2. 返回预约列表
-3. 检查按钮是否显示"查看"
-4. 点击按钮,应该只能查看处方(只读模式)
-
-### 测试4:已驳回的处方
-
-1. 创建一个处方并审核驳回
-2. 返回预约列表
-3. 检查按钮是否显示"开方"
-4. 点击按钮,应该可以编辑处方
-
-## 预期行为
-
-| 处方状态 | `prescription_audit_status` | `prescription_void_status` | 按钮文字 | 点击行为 |
-|---------|---------------------------|--------------------------|---------|---------|
-| 未开方 | -1 | 0 | 开方 | 创建新处方 |
-| 待审核 | 0 | 0 | 开方 | 编辑处方 |
-| 已通过 | 1 | 0 | 查看 | 查看处方(只读) |
-| 已驳回 | 2 | 1 | 开方 | 编辑处方 |
-
-## 常见问题
-
-### Q1: 为什么审核通过后按钮文字变成"查看"?
-
-A: 这是为了区分"可编辑"和"只读"两种模式。已审核通过的处方不可编辑,只能查看。
-
-### Q2: 如果需要修改已审核通过的处方怎么办?
-
-A: 需要先撤回业务订单(如果已创建),处方审核状态会自动重置为待审核,然后才能编辑。
-
-### Q3: 按钮完全不显示怎么办?
-
-A: 检查用户是否有 `tcm.diagnosis/kaifang` 权限。如果没有,需要在角色管理中添加该权限。
-
-### Q4: 点击"查看"按钮后能编辑吗?
-
-A: 不能。已审核通过的处方只能查看,不能编辑。如果需要编辑,需要先撤回业务订单。
-
-## 总结
-
-按钮的显示逻辑是正确的:
-- ✅ 按钮始终显示(只要有 `tcm.diagnosis/kaifang` 权限)
-- ✅ 按钮文字根据处方状态动态变化("开方" 或 "查看")
-- ✅ 点击行为根据处方状态不同(可编辑 或 只读)
-
-如果用户反馈"按钮不显示",可能是以下原因:
-1. 用户没有 `tcm.diagnosis/kaifang` 权限
-2. 按钮文字从"开方"变成了"查看",用户误以为按钮消失了
-3. 数据返回有问题,需要检查后端接口
-
-建议先检查权限配置和数据返回,确认按钮是否真的不显示。
diff --git a/PRESCRIPTION_DOSAGE_COMPLETE_SUMMARY.md b/PRESCRIPTION_DOSAGE_COMPLETE_SUMMARY.md
deleted file mode 100644
index 769fe3fd..00000000
--- a/PRESCRIPTION_DOSAGE_COMPLETE_SUMMARY.md
+++ /dev/null
@@ -1,283 +0,0 @@
-# 处方用量功能完整实现总结
-
-## 概述
-在两个处方页面中实现了用量、单位和代煎功能:
-1. **处方组件** (`admin/src/components/tcm-prescription/index.vue`) - 创建/编辑处方
-2. **处方列表** (`admin/src/views/consumer/prescription/index.vue`) - 查看/编辑处方
-
-## 已实现的功能
-
-### 1. 数据库层 ✅
-**文件**: `add_prescription_dosage_fields.sql`
-
-```sql
-ALTER TABLE `zyt_tcm_prescription`
-ADD COLUMN `dosage_amount` decimal(10,2) DEFAULT NULL COMMENT '用量数值',
-ADD COLUMN `dosage_unit` varchar(10) NOT NULL DEFAULT '' COMMENT '用量单位(g/ml)',
-ADD COLUMN `need_decoction` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否代煎(0否1是,仅饮片)';
-```
-
-### 2. 后端层 ✅
-
-#### 模型 (`server/app/common/model/tcm/Prescription.php`)
-```php
-protected $type = [
- 'dosage_amount' => 'float',
- 'need_decoction' => 'integer',
-];
-```
-
-#### 业务逻辑 (`server/app/adminapi/logic/tcm/PrescriptionLogic.php`)
-- ✅ `add()` 方法:保存新处方
-- ✅ `edit()` 方法:编辑处方
-
-### 3. 前端层 ✅
-
-#### 3.1 处方组件 (`admin/src/components/tcm-prescription/index.vue`)
-
-**数据结构**:
-```typescript
-interface PrescriptionForm {
- prescription_type: string
- dosage_amount: number | undefined
- dosage_unit: string
- need_decoction: boolean
- // ...
-}
-```
-
-**响应式数据**:
-```typescript
-const formData = reactive
({
- prescription_type: '浓缩水丸',
- dosage_amount: 1,
- dosage_unit: 'g',
- need_decoction: false,
- // ...
-})
-```
-
-**自动切换逻辑**:
-```typescript
-watch(() => formData.prescription_type, (newType) => {
- if (newType === '浓缩水丸') {
- formData.dosage_unit = 'g'
- formData.dosage_amount = 1
- formData.need_decoction = false
- } else if (newType === '饮片') {
- formData.dosage_unit = 'ml'
- formData.dosage_amount = 50
- } else {
- formData.dosage_unit = 'g'
- formData.dosage_amount = undefined
- formData.need_decoction = false
- }
-})
-```
-
-#### 3.2 处方列表 (`admin/src/views/consumer/prescription/index.vue`)
-
-**数据结构**:
-```typescript
-const editForm = reactive({
- prescription_type: '浓缩水丸',
- dosage_amount: 1 as number | undefined,
- dosage_unit: 'g',
- need_decoction: false,
- // ...
-})
-```
-
-**自动切换逻辑**:
-```typescript
-watch(() => editForm.prescription_type, (newType) => {
- if (newType === '浓缩水丸') {
- editForm.dosage_unit = 'g'
- editForm.dosage_amount = 1
- editForm.need_decoction = false
- } else if (newType === '饮片') {
- editForm.dosage_unit = 'ml'
- editForm.dosage_amount = 50
- } else {
- editForm.dosage_unit = 'g'
- editForm.dosage_amount = undefined
- editForm.need_decoction = false
- }
-})
-```
-
-## UI 组件实现
-
-### 浓缩水丸 (1-10g)
-```vue
-
-
-
-
-
-```
-
-### 饮片 (50-250ml)
-```vue
-
-
-
-
-
-
-
-
-
-
- 代煎
- 不代煎
-
-```
-
-### 其他类型
-```vue
-
-```
-
-## 功能规则
-
-### 1. 浓缩水丸
-- **单位**: g (克)
-- **范围**: 1-10g
-- **UI**: 下拉选择
-- **代煎**: 不显示
-
-### 2. 饮片
-- **单位**: ml (毫升)
-- **范围**: 50-250ml (步进50ml)
-- **UI**: 下拉选择
-- **代煎**: 显示单选按钮(代煎/不代煎)
-
-### 3. 其他类型(颗粒、丸剂、散剂、膏方、汤剂)
-- **单位**: g (克)
-- **范围**: 不限制
-- **UI**: 数字输入框(支持小数)
-- **代煎**: 不显示
-
-## 数据流转示例
-
-### 创建浓缩水丸处方
-```
-用户操作:
-1. 选择"浓缩水丸"
-2. 自动设置: 单位=g, 用量=1g
-3. 选择用量: 5g
-4. 保存
-
-数据库存储:
-prescription_type: "浓缩水丸"
-dosage_amount: 5.00
-dosage_unit: "g"
-need_decoction: 0
-```
-
-### 创建饮片处方(代煎)
-```
-用户操作:
-1. 选择"饮片"
-2. 自动设置: 单位=ml, 用量=50ml
-3. 选择用量: 150ml
-4. 选择"代煎"
-5. 保存
-
-数据库存储:
-prescription_type: "饮片"
-dosage_amount: 150.00
-dosage_unit: "ml"
-need_decoction: 1
-```
-
-## 部署清单
-
-### 1. 数据库迁移 ⚠️ 必须执行
-```bash
-mysql -u用户名 -p数据库名 < add_prescription_dosage_fields.sql
-```
-
-### 2. 后端文件
-- ✅ `server/app/common/model/tcm/Prescription.php`
-- ✅ `server/app/adminapi/logic/tcm/PrescriptionLogic.php`
-
-### 3. 前端文件
-- ✅ `admin/src/components/tcm-prescription/index.vue`
-- ✅ `admin/src/views/consumer/prescription/index.vue`
-
-## 测试清单
-
-### 处方组件测试
-- [ ] 创建浓缩水丸处方,选择1-10g
-- [ ] 创建饮片处方,选择50-250ml
-- [ ] 饮片处方选择"代煎"
-- [ ] 饮片处方选择"不代煎"
-- [ ] 创建颗粒处方,输入自定义用量
-- [ ] 切换处方类型,验证用量自动调整
-- [ ] 保存处方,验证数据正确存储
-
-### 处方列表测试
-- [ ] 编辑浓缩水丸处方,修改用量
-- [ ] 编辑饮片处方,修改用量和代煎选项
-- [ ] 编辑其他类型处方,输入自定义用量
-- [ ] 切换处方类型,验证用量自动调整
-- [ ] 保存修改,验证数据正确更新
-- [ ] 查看处方详情,验证显示正确
-
-### 数据验证测试
-- [ ] 浓缩水丸用量范围:1-10g
-- [ ] 饮片用量范围:50-250ml
-- [ ] 饮片用量步进:50ml
-- [ ] 代煎选项仅在饮片时显示
-- [ ] 切换类型时代煎选项自动隐藏
-
-## 文件清单
-
-### 数据库
-- `add_prescription_dosage_fields.sql` - 数据库迁移文件
-
-### 后端
-- `server/app/common/model/tcm/Prescription.php` - 处方模型
-- `server/app/adminapi/logic/tcm/PrescriptionLogic.php` - 处方业务逻辑
-
-### 前端
-- `admin/src/components/tcm-prescription/index.vue` - 处方组件
-- `admin/src/views/consumer/prescription/index.vue` - 处方列表
-
-### 文档
-- `PRESCRIPTION_DOSAGE_FEATURE.md` - 功能说明
-- `PRESCRIPTION_DOSAGE_DATABASE_IMPLEMENTATION.md` - 数据库实现
-- `PRESCRIPTION_DOSAGE_COMPLETE_SUMMARY.md` - 本文档
-
-## 注意事项
-
-1. **数据库迁移必须先执行**,否则保存时会报错
-2. **旧数据兼容**:旧处方的用量字段为NULL,不影响显示
-3. **类型切换**:切换处方类型时,用量和单位会自动调整
-4. **代煎选项**:仅在选择"饮片"时显示
-5. **数据验证**:前端使用下拉选择确保数据有效性
-
-## 后续优化建议
-
-1. **后端验证**:添加用量范围的后端验证逻辑
-2. **打印显示**:在处方打印时显示用量和代煎信息
-3. **统计报表**:按处方类型和用量统计
-4. **价格联动**:根据用量自动计算处方价格
-5. **库存检查**:代煎选项关联药房库存
-
-## 总结
-
-✅ **数据库层**:3个字段已定义
-✅ **后端层**:模型和业务逻辑已实现
-✅ **前端层**:2个页面都已实现
-✅ **UI组件**:下拉选择器和单选按钮
-✅ **自动切换**:watch监听实现
-✅ **数据流转**:完整的保存和读取
-
-**状态**: 功能完整实现,只需执行数据库迁移即可使用!🎉
diff --git a/PRESCRIPTION_DOSAGE_DATABASE_IMPLEMENTATION.md b/PRESCRIPTION_DOSAGE_DATABASE_IMPLEMENTATION.md
deleted file mode 100644
index 1fd0165d..00000000
--- a/PRESCRIPTION_DOSAGE_DATABASE_IMPLEMENTATION.md
+++ /dev/null
@@ -1,366 +0,0 @@
-# 处方用量数据库实现完整指南
-
-## 概述
-为处方系统添加用量、单位和代煎字段的完整数据库实现。
-
-## 1. 数据库迁移
-
-### 执行SQL文件
-**文件**: `add_prescription_dosage_fields.sql`
-
-```sql
--- 添加处方用量相关字段
-ALTER TABLE `zyt_tcm_prescription`
-ADD COLUMN `dosage_amount` decimal(10,2) DEFAULT NULL COMMENT '用量数值' AFTER `prescription_type`,
-ADD COLUMN `dosage_unit` varchar(10) NOT NULL DEFAULT '' COMMENT '用量单位(g/ml)' AFTER `dosage_amount`,
-ADD COLUMN `need_decoction` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否代煎(0否1是,仅饮片)' AFTER `dosage_unit`;
-```
-
-### 执行方式
-```bash
-mysql -u用户名 -p数据库名 < add_prescription_dosage_fields.sql
-```
-
-### 字段说明
-
-| 字段名 | 类型 | 默认值 | 说明 |
-|--------|------|--------|------|
-| `dosage_amount` | decimal(10,2) | NULL | 用量数值,支持小数 |
-| `dosage_unit` | varchar(10) | '' | 用量单位 (g/ml) |
-| `need_decoction` | tinyint(1) | 0 | 是否代煎 (0=否, 1=是) |
-
-## 2. 后端实现
-
-### 2.1 模型层 (Model)
-**文件**: `server/app/common/model/tcm/Prescription.php`
-
-```php
-class Prescription extends BaseModel
-{
- // ... 其他配置
-
- // 字段类型转换
- protected $type = [
- 'dosage_amount' => 'float',
- 'need_decoction' => 'integer',
- ];
-}
-```
-
-**作用**: 自动将数据库字段转换为正确的PHP类型。
-
-### 2.2 业务逻辑层 (Logic)
-**文件**: `server/app/adminapi/logic/tcm/PrescriptionLogic.php`
-
-#### add() 方法 - 添加处方
-```php
-$data = [
- // ... 其他字段
- 'prescription_type' => $params['prescription_type'] ?? '浓缩水丸',
- 'dosage_amount' => isset($params['dosage_amount']) ? (float)$params['dosage_amount'] : null,
- 'dosage_unit' => $params['dosage_unit'] ?? '',
- 'need_decoction' => (int)($params['need_decoction'] ?? 0),
- // ... 其他字段
-];
-```
-
-#### edit() 方法 - 编辑处方
-```php
-$data = [
- // ... 其他字段
- 'prescription_type' => $params['prescription_type'] ?? $prescription->prescription_type,
- 'dosage_amount' => isset($params['dosage_amount']) ? (float)$params['dosage_amount'] : $prescription->dosage_amount,
- 'dosage_unit' => $params['dosage_unit'] ?? $prescription->dosage_unit,
- 'need_decoction' => isset($params['need_decoction']) ? (int)$params['need_decoction'] : (int)($prescription->need_decoction ?? 0),
- // ... 其他字段
-];
-```
-
-## 3. 前端实现
-
-### 3.1 TypeScript 接口
-**文件**: `admin/src/components/tcm-prescription/index.vue`
-
-```typescript
-interface PrescriptionForm {
- // ... 其他字段
- prescription_type: string
- dosage_amount: number | undefined
- dosage_unit: string
- need_decoction: boolean
- // ... 其他字段
-}
-```
-
-### 3.2 响应式数据
-```typescript
-const formData = reactive({
- // ... 其他字段
- prescription_type: '浓缩水丸',
- dosage_amount: 1,
- dosage_unit: 'g',
- need_decoction: false,
- // ... 其他字段
-})
-```
-
-### 3.3 自动切换逻辑
-```typescript
-watch(() => formData.prescription_type, (newType) => {
- if (newType === '浓缩水丸') {
- formData.dosage_unit = 'g'
- formData.dosage_amount = 1
- formData.need_decoction = false
- } else if (newType === '饮片') {
- formData.dosage_unit = 'ml'
- formData.dosage_amount = 50
- } else {
- formData.dosage_unit = 'g'
- formData.dosage_amount = undefined
- formData.need_decoction = false
- }
-})
-```
-
-## 4. 数据流转
-
-### 4.1 保存流程
-```
-前端表单
- ↓
-{
- prescription_type: "浓缩水丸",
- dosage_amount: 5,
- dosage_unit: "g",
- need_decoction: false
-}
- ↓
-后端 PrescriptionLogic::add()
- ↓
-数据库 zyt_tcm_prescription
- ↓
-dosage_amount: 5.00
-dosage_unit: "g"
-need_decoction: 0
-```
-
-### 4.2 读取流程
-```
-数据库查询
- ↓
-Prescription Model (类型转换)
- ↓
-{
- dosage_amount: 5.0, // float
- dosage_unit: "g", // string
- need_decoction: 0 // int
-}
- ↓
-前端接收
- ↓
-{
- dosage_amount: 5,
- dosage_unit: "g",
- need_decoction: false
-}
-```
-
-## 5. 数据示例
-
-### 5.1 浓缩水丸处方
-```json
-{
- "prescription_type": "浓缩水丸",
- "dosage_amount": 5.00,
- "dosage_unit": "g",
- "need_decoction": 0
-}
-```
-
-### 5.2 饮片处方(代煎)
-```json
-{
- "prescription_type": "饮片",
- "dosage_amount": 150.00,
- "dosage_unit": "ml",
- "need_decoction": 1
-}
-```
-
-### 5.3 颗粒处方
-```json
-{
- "prescription_type": "颗粒",
- "dosage_amount": 15.50,
- "dosage_unit": "g",
- "need_decoction": 0
-}
-```
-
-## 6. 部署步骤
-
-### 步骤 1: 备份数据库
-```bash
-mysqldump -u用户名 -p数据库名 > backup_$(date +%Y%m%d_%H%M%S).sql
-```
-
-### 步骤 2: 执行数据库迁移
-```bash
-mysql -u用户名 -p数据库名 < add_prescription_dosage_fields.sql
-```
-
-### 步骤 3: 验证字段添加
-```sql
-SHOW COLUMNS FROM `zyt_tcm_prescription` LIKE 'dosage%';
-SHOW COLUMNS FROM `zyt_tcm_prescription` LIKE 'need_decoction';
-```
-
-### 步骤 4: 部署后端代码
-```bash
-# 上传修改的文件
-- server/app/common/model/tcm/Prescription.php
-- server/app/adminapi/logic/tcm/PrescriptionLogic.php
-```
-
-### 步骤 5: 部署前端代码
-```bash
-# 构建前端
-cd admin
-npm run build
-
-# 上传构建产物
-# 上传 dist/ 目录到服务器
-```
-
-### 步骤 6: 清除缓存(如需要)
-```bash
-cd server
-php think clear
-```
-
-### 步骤 7: 测试功能
-1. 创建新处方,选择不同类型
-2. 验证用量字段正确保存
-3. 编辑处方,验证数据正确加载
-4. 查看处方详情,验证显示正确
-
-## 7. 数据验证
-
-### 7.1 前端验证
-```typescript
-// 浓缩水丸:1-10g
-if (formData.prescription_type === '浓缩水丸') {
- if (formData.dosage_amount < 1 || formData.dosage_amount > 10) {
- // 错误提示
- }
-}
-
-// 饮片:50-250ml,步进50
-if (formData.prescription_type === '饮片') {
- if (formData.dosage_amount % 50 !== 0) {
- // 错误提示
- }
-}
-```
-
-### 7.2 后端验证(建议添加)
-```php
-// 在 PrescriptionLogic::add() 中添加
-if ($params['prescription_type'] === '浓缩水丸') {
- $amount = (float)($params['dosage_amount'] ?? 0);
- if ($amount < 1 || $amount > 10) {
- self::setError('浓缩水丸用量必须在1-10g之间');
- return null;
- }
-}
-
-if ($params['prescription_type'] === '饮片') {
- $amount = (float)($params['dosage_amount'] ?? 0);
- if ($amount < 50 || $amount > 250 || $amount % 50 !== 0) {
- self::setError('饮片用量必须为50-250ml,且为50的倍数');
- return null;
- }
-}
-```
-
-## 8. 常见问题
-
-### Q1: 字段已存在错误
-```
-ERROR 1060 (42S21): Duplicate column name 'dosage_amount'
-```
-**解决**: 字段已存在,跳过此步骤或先删除字段再添加。
-
-### Q2: 旧数据如何处理?
-**方案**: 旧数据的 `dosage_amount` 为 NULL,前端显示为空,不影响功能。
-
-### Q3: 如何批量更新旧数据?
-```sql
--- 为浓缩水丸类型设置默认用量
-UPDATE `zyt_tcm_prescription`
-SET
- `dosage_amount` = 1,
- `dosage_unit` = 'g'
-WHERE `prescription_type` = '浓缩水丸'
- AND `dosage_amount` IS NULL;
-
--- 为饮片类型设置默认用量
-UPDATE `zyt_tcm_prescription`
-SET
- `dosage_amount` = 50,
- `dosage_unit` = 'ml'
-WHERE `prescription_type` = '饮片'
- AND `dosage_amount` IS NULL;
-```
-
-## 9. 回滚方案
-
-如果需要回滚,执行以下SQL:
-
-```sql
--- 删除添加的字段
-ALTER TABLE `zyt_tcm_prescription`
-DROP COLUMN `dosage_amount`,
-DROP COLUMN `dosage_unit`,
-DROP COLUMN `need_decoction`;
-```
-
-## 10. 相关文件清单
-
-### 数据库
-- `add_prescription_dosage_fields.sql` - 数据库迁移文件
-
-### 后端
-- `server/app/common/model/tcm/Prescription.php` - 处方模型
-- `server/app/adminapi/logic/tcm/PrescriptionLogic.php` - 处方业务逻辑
-
-### 前端
-- `admin/src/components/tcm-prescription/index.vue` - 处方组件
-
-### 文档
-- `PRESCRIPTION_DOSAGE_FEATURE.md` - 功能说明文档
-- `PRESCRIPTION_DOSAGE_DATABASE_IMPLEMENTATION.md` - 本文档
-
-## 11. 测试清单
-
-- [ ] 数据库字段添加成功
-- [ ] 创建浓缩水丸处方,用量1-10g
-- [ ] 创建饮片处方,用量50-250ml
-- [ ] 饮片处方代煎选项正常
-- [ ] 编辑处方,数据正确加载
-- [ ] 切换处方类型,用量自动调整
-- [ ] 保存处方,数据正确存储
-- [ ] 查看处方详情,显示正确
-- [ ] 旧数据兼容性测试
-
-## 总结
-
-此实现完整覆盖了从数据库到前端的所有层次:
-1. ✅ 数据库字段添加
-2. ✅ 模型层类型转换
-3. ✅ 业务逻辑层数据处理
-4. ✅ 前端表单交互
-5. ✅ 自动切换逻辑
-6. ✅ 数据验证
-
-所有代码已实现,只需执行数据库迁移即可使用!
diff --git a/PRESCRIPTION_DOSAGE_FEATURE.md b/PRESCRIPTION_DOSAGE_FEATURE.md
deleted file mode 100644
index b09fa50d..00000000
--- a/PRESCRIPTION_DOSAGE_FEATURE.md
+++ /dev/null
@@ -1,307 +0,0 @@
-# 处方用量功能实现
-
-## 概述
-根据处方类型自动调整用量单位和范围,并为饮片类型添加代煎选项。
-
-## 功能说明
-
-### 1. 浓缩水丸
-- **单位**: g (克)
-- **范围**: 1-10g
-- **步进**: 1g
-- **默认值**: 1g
-
-### 2. 饮片
-- **单位**: ml (毫升)
-- **范围**: 50-250ml
-- **步进**: 50ml (每50ml一个单位)
-- **默认值**: 50ml
-- **特殊选项**: 是否代煎(代煎/不代煎)
-
-### 3. 其他类型(颗粒、丸剂、散剂、膏方、汤剂)
-- **单位**: g (克)
-- **范围**: 不限制
-- **精度**: 小数点后2位
-- **默认值**: 无
-
-## 前端实现
-
-### 文件:`admin/src/components/tcm-prescription/index.vue`
-
-#### 1. TypeScript 接口
-```typescript
-interface PrescriptionForm {
- // ... 其他字段
- prescription_type: string // 处方类型
- dosage_amount: number | undefined // 用量数值
- dosage_unit: string // 用量单位 (g 或 ml)
- need_decoction: boolean // 是否代煎(仅饮片)
- // ...
-}
-```
-
-#### 2. 响应式数据
-```typescript
-const formData = reactive({
- // ...
- prescription_type: '浓缩水丸',
- dosage_amount: 1,
- dosage_unit: 'g',
- need_decoction: false,
- // ...
-})
-```
-
-#### 3. 自动切换逻辑
-```typescript
-watch(() => formData.prescription_type, (newType) => {
- if (newType === '浓缩水丸') {
- formData.dosage_unit = 'g'
- formData.dosage_amount = 1
- formData.need_decoction = false
- } else if (newType === '饮片') {
- formData.dosage_unit = 'ml'
- formData.dosage_amount = 50
- // need_decoction 保持用户选择
- } else {
- formData.dosage_unit = 'g'
- formData.dosage_amount = undefined
- formData.need_decoction = false
- }
-})
-```
-
-#### 4. UI 组件
-```vue
-
-
-
-
-
-
-
-
-
- {{ formData.dosage_unit }}
-
-
-
-
-
-
-
- 代煎
- 不代煎
-
-
-
-```
-
-## 用户交互流程
-
-### 场景 1:选择浓缩水丸
-1. 用户选择"浓缩水丸"
-2. 系统自动设置:
- - 用量单位:g
- - 默认用量:1g
- - 用量范围:1-10g
- - 隐藏代煎选项
-
-### 场景 2:选择饮片
-1. 用户选择"饮片"
-2. 系统自动设置:
- - 用量单位:ml
- - 默认用量:50ml
- - 用量范围:50-250ml(步进50ml)
- - 显示代煎选项(默认"不代煎")
-3. 用户可选择:
- - 用量:50ml, 100ml, 150ml, 200ml, 250ml
- - 是否代煎:代煎 / 不代煎
-
-### 场景 3:选择其他类型
-1. 用户选择"颗粒"、"丸剂"等
-2. 系统自动设置:
- - 用量单位:g
- - 默认用量:空
- - 用量范围:不限制(可输入小数)
- - 隐藏代煎选项
-
-## 数据示例
-
-### 浓缩水丸处方
-```json
-{
- "prescription_type": "浓缩水丸",
- "dosage_amount": 5,
- "dosage_unit": "g",
- "need_decoction": false
-}
-```
-
-### 饮片处方(代煎)
-```json
-{
- "prescription_type": "饮片",
- "dosage_amount": 150,
- "dosage_unit": "ml",
- "need_decoction": true
-}
-```
-
-### 饮片处方(不代煎)
-```json
-{
- "prescription_type": "饮片",
- "dosage_amount": 200,
- "dosage_unit": "ml",
- "need_decoction": false
-}
-```
-
-### 颗粒处方
-```json
-{
- "prescription_type": "颗粒",
- "dosage_amount": 15.5,
- "dosage_unit": "g",
- "need_decoction": false
-}
-```
-
-## 后端存储建议
-
-### 数据库字段
-建议在处方表中添加以下字段:
-
-```sql
-ALTER TABLE `zyt_tcm_prescription`
-ADD COLUMN `dosage_amount` decimal(10,2) DEFAULT NULL COMMENT '用量数值',
-ADD COLUMN `dosage_unit` varchar(10) NOT NULL DEFAULT '' COMMENT '用量单位(g/ml)',
-ADD COLUMN `need_decoction` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否代煎(0否1是,仅饮片)';
-```
-
-### 后端验证逻辑
-```php
-// 浓缩水丸验证
-if ($prescriptionType === '浓缩水丸') {
- if ($dosageAmount < 1 || $dosageAmount > 10) {
- throw new Exception('浓缩水丸用量必须在1-10g之间');
- }
- if ($dosageUnit !== 'g') {
- throw new Exception('浓缩水丸单位必须为g');
- }
-}
-
-// 饮片验证
-if ($prescriptionType === '饮片') {
- if ($dosageAmount < 50 || $dosageAmount > 250 || $dosageAmount % 50 !== 0) {
- throw new Exception('饮片用量必须为50-250ml,且为50的倍数');
- }
- if ($dosageUnit !== 'ml') {
- throw new Exception('饮片单位必须为ml');
- }
-}
-```
-
-## UI 布局
-
-```
-┌─────────────────────────────────────────────────────────┐
-│ 处方类型: [浓缩水丸 ▼] 用量: [1] g │
-└─────────────────────────────────────────────────────────┘
-
-┌─────────────────────────────────────────────────────────┐
-│ 处方类型: [饮片 ▼] 用量: [150] ml │
-│ 是否代煎: ⦿ 代煎 ○ 不代煎 │
-└─────────────────────────────────────────────────────────┘
-
-┌─────────────────────────────────────────────────────────┐
-│ 处方类型: [颗粒 ▼] 用量: [15.5] g │
-└─────────────────────────────────────────────────────────┘
-```
-
-## 业务规则
-
-### 1. 浓缩水丸
-- 用于浓缩制剂,按克计量
-- 通常用量较小,1-10g范围合理
-- 不需要代煎
-
-### 2. 饮片
-- 用于传统中药饮片
-- 按煎煮后的药液体积计量(ml)
-- 每次50ml为一个标准单位
-- 可选择是否由药房代煎
-- 代煎:药房负责煎煮,患者直接服用
-- 不代煎:患者自行煎煮
-
-### 3. 其他类型
-- 根据实际情况灵活设置用量
-- 支持小数点精度
-
-## 注意事项
-
-1. **单位自动切换**:切换处方类型时,单位和默认值会自动更新
-2. **代煎选项**:仅在选择"饮片"时显示
-3. **用量限制**:不同类型有不同的用量范围限制
-4. **数据验证**:前端和后端都应进行用量范围验证
-5. **用户体验**:使用 `el-input-number` 组件,支持键盘上下键调整
-
-## 扩展建议
-
-1. **价格联动**:根据用量自动计算处方价格
-2. **库存检查**:代煎选项可关联药房库存
-3. **历史记录**:记录患者常用的用量设置
-4. **模板保存**:将用量设置保存到处方模板
-5. **打印显示**:在处方打印时显示用量和代煎信息
-
-## 相关文件
-
-- `admin/src/components/tcm-prescription/index.vue` - 处方组件主文件
-
-## 测试用例
-
-### 测试 1:浓缩水丸用量
-- 选择"浓缩水丸"
-- 验证用量范围:1-10g
-- 验证步进:1g
-- 验证代煎选项隐藏
-
-### 测试 2:饮片用量
-- 选择"饮片"
-- 验证用量范围:50-250ml
-- 验证步进:50ml
-- 验证代煎选项显示
-- 测试代煎/不代煎切换
-
-### 测试 3:类型切换
-- 从"浓缩水丸"切换到"饮片"
-- 验证单位从g变为ml
-- 验证用量自动调整
-- 验证代煎选项出现
-
-### 测试 4:数据保存
-- 填写完整处方信息
-- 保存处方
-- 验证用量数据正确存储
-- 验证代煎选项正确存储
diff --git a/PRESCRIPTION_DUPLICATE_REQUEST_FIX.md b/PRESCRIPTION_DUPLICATE_REQUEST_FIX.md
deleted file mode 100644
index d6a1dd7f..00000000
--- a/PRESCRIPTION_DUPLICATE_REQUEST_FIX.md
+++ /dev/null
@@ -1,321 +0,0 @@
-# 处方组件重复请求修复
-
-## 问题描述
-
-在预约列表页面点击"开处方"或"查看病历"按钮时,会出现重复请求 `/tcm.prescription/detail` 接口的问题,导致错误。
-
-## 问题原因
-
-### 1. 快速连续点击
-
-用户可能快速连续点击按钮,导致 `open()` 或 `openById()` 方法被多次调用。
-
-### 2. 异步操作未加锁
-
-`open()` 和 `openById()` 方法是异步的,但没有加锁机制,导致:
-- 第一次点击开始执行
-- 第二次点击也开始执行
-- 两次请求同时发送
-
-### 3. 代码流程
-
-```javascript
-const open = async (data: any) => {
- // 没有防重复检查
- await loadDictOptions()
-
- if (appointmentId) {
- const existing = await prescriptionGetByAppointment(...) // 请求1
- if (existing?.id) {
- const detail = await prescriptionDetail(...) // 请求2
- }
- }
-}
-```
-
-## 解决方案
-
-### 添加防重复打开标志
-
-使用 `isOpening` 标志来防止重复调用:
-
-```typescript
-// 防止重复打开
-const isOpening = ref(false)
-
-const open = async (data: any, options?: { templateId?: number; stationName?: string }) => {
- // 防止重复打开
- if (isOpening.value) {
- console.warn('处方正在打开中,请勿重复点击')
- return
- }
-
- isOpening.value = true
-
- try {
- // 原有逻辑...
- } finally {
- // 延迟重置,避免快速连续点击
- setTimeout(() => {
- isOpening.value = false
- }, 500)
- }
-}
-```
-
-### 关键点
-
-1. **检查标志**:方法开始时检查 `isOpening.value`,如果为 true 则直接返回
-2. **设置标志**:开始执行时设置 `isOpening.value = true`
-3. **finally 重置**:无论成功还是失败,都在 finally 中重置标志
-4. **延迟重置**:使用 `setTimeout` 延迟 500ms 重置,防止快速连续点击
-
-## 修改的文件
-
-**文件**:`admin/src/components/tcm-prescription/index.vue`
-
-### 1. 添加防重复标志
-
-```typescript
-// 防止重复打开
-const isOpening = ref(false)
-```
-
-### 2. 修改 open 方法
-
-```typescript
-const open = async (data: any, options?: { templateId?: number; stationName?: string }) => {
- // 防止重复打开
- if (isOpening.value) {
- console.warn('处方正在打开中,请勿重复点击')
- return
- }
-
- isOpening.value = true
-
- try {
- // 原有逻辑...
- } finally {
- setTimeout(() => {
- isOpening.value = false
- }, 500)
- }
-}
-```
-
-### 3. 修改 openById 方法
-
-```typescript
-const openById = async (prescriptionId: number) => {
- // 防止重复打开
- if (isOpening.value) {
- console.warn('处方正在打开中,请勿重复点击')
- return
- }
-
- isOpening.value = true
-
- try {
- const res = await prescriptionDetail({ id: prescriptionId })
- savedPrescription.value = res
- if (res?.case_record && Object.keys(res.case_record).length > 0) {
- await loadDictOptions()
- }
- visible.value = true
- } catch (e) {
- feedback.msgError('加载处方失败')
- } finally {
- setTimeout(() => {
- isOpening.value = false
- }, 500)
- }
-}
-```
-
-## 工作流程
-
-### 修复前
-
-```
-用户点击"开处方"按钮
- ↓
-调用 open() 方法
- ↓
-用户再次点击(快速连续点击)
- ↓
-再次调用 open() 方法
- ↓
-两次请求同时发送
- ↓
-❌ 重复请求错误
-```
-
-### 修复后
-
-```
-用户点击"开处方"按钮
- ↓
-调用 open() 方法
- ↓
-设置 isOpening = true
- ↓
-用户再次点击(快速连续点击)
- ↓
-检查 isOpening = true
- ↓
-✓ 直接返回,不执行
- ↓
-第一次请求完成
- ↓
-500ms 后重置 isOpening = false
-```
-
-## 测试场景
-
-### 场景1:快速连续点击
-
-**操作**:快速连续点击"开处方"按钮 2-3 次
-
-**预期结果**:
-- 只发送一次请求
-- 控制台显示警告:"处方正在打开中,请勿重复点击"
-- 处方正常打开
-
----
-
-### 场景2:正常点击
-
-**操作**:正常点击"开处方"按钮
-
-**预期结果**:
-- 发送请求
-- 处方正常打开
-- 无警告信息
-
----
-
-### 场景3:间隔点击
-
-**操作**:点击"开处方",等待 1 秒后再次点击
-
-**预期结果**:
-- 第一次点击正常打开
-- 第二次点击也正常打开(因为已过 500ms)
-
----
-
-### 场景4:查看已有处方
-
-**操作**:点击"查看病历"按钮
-
-**预期结果**:
-- 调用 `prescriptionGetByAppointment` 查询
-- 如果有处方,调用 `prescriptionDetail` 获取详情
-- 只发送一次 detail 请求
-
----
-
-### 场景5:请求失败
-
-**操作**:点击"开处方",但请求失败
-
-**预期结果**:
-- 显示错误提示
-- 500ms 后 `isOpening` 重置
-- 可以再次点击
-
-## 其他优化建议
-
-### 1. 按钮禁用状态
-
-可以在按钮上添加 loading 状态:
-
-```vue
-
- {{ prescriptionActionLabel(row) }}
-
-```
-
-但这需要将 `isOpening` 暴露给父组件,可能不太合适。
-
-### 2. 防抖处理
-
-可以使用 lodash 的 debounce 函数:
-
-```typescript
-import { debounce } from 'lodash-es'
-
-const debouncedOpen = debounce(open, 500, { leading: true, trailing: false })
-```
-
-但当前的解决方案已经足够简单有效。
-
-### 3. 请求取消
-
-可以使用 AbortController 取消重复请求:
-
-```typescript
-let abortController: AbortController | null = null
-
-const open = async (data: any) => {
- if (abortController) {
- abortController.abort()
- }
- abortController = new AbortController()
-
- // 在请求中使用 signal
- await fetch(url, { signal: abortController.signal })
-}
-```
-
-但这需要修改 API 调用层,改动较大。
-
-## 注意事项
-
-### 1. 延迟时间
-
-当前设置为 500ms,可以根据实际情况调整:
-- 太短(< 300ms):可能无法有效防止快速连续点击
-- 太长(> 1000ms):用户体验不好,需要等待较长时间
-
-### 2. 控制台警告
-
-`console.warn` 只在开发环境有用,生产环境可以考虑:
-- 移除警告
-- 或者改为用户提示:`feedback.msgWarning('请勿重复点击')`
-
-### 3. 多个入口
-
-如果有多个地方调用 `open()` 方法,都会受到这个锁的保护。
-
-## 相关文件
-
-### 修改的文件
-- `admin/src/components/tcm-prescription/index.vue` - 处方组件
-
-### 调用的文件
-- `admin/src/views/tcm/appointment/list.vue` - 预约列表
-- 其他可能调用处方组件的页面
-
-## 总结
-
-✅ 添加 `isOpening` 标志防止重复打开
-✅ 使用 try-finally 确保标志正确重置
-✅ 延迟 500ms 重置,防止快速连续点击
-✅ 同时修复 `open()` 和 `openById()` 方法
-✅ 添加控制台警告提示
-
-**效果**:
-- 防止重复请求
-- 避免接口错误
-- 提升用户体验
-- 代码简单易维护
-
-**下一步**:
-1. 测试快速连续点击
-2. 测试正常点击流程
-3. 测试请求失败情况
-4. 考虑是否需要用户提示
diff --git a/PRESCRIPTION_FIELDS_FIX_SUMMARY.md b/PRESCRIPTION_FIELDS_FIX_SUMMARY.md
deleted file mode 100644
index eb3c0f80..00000000
--- a/PRESCRIPTION_FIELDS_FIX_SUMMARY.md
+++ /dev/null
@@ -1,251 +0,0 @@
-# 处方字段完整修复总结
-
-## 问题描述
-前端添加了新字段(用量、代煎、每天几次),但后端没有相应处理,导致编辑保存时数据丢失。
-
-## 修复内容
-
-### 1. 数据库字段 ✅
-
-**文件**: `add_prescription_complete_fields.sql`
-
-```sql
--- 用量相关字段
-ALTER TABLE `zyt_tcm_prescription`
-ADD COLUMN IF NOT EXISTS `dosage_amount` decimal(10,2) DEFAULT NULL COMMENT '用量数值',
-ADD COLUMN IF NOT EXISTS `dosage_unit` varchar(10) NOT NULL DEFAULT '' COMMENT '用量单位(g/ml)',
-ADD COLUMN IF NOT EXISTS `need_decoction` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否代煎(0否1是,仅饮片)';
-
--- 每天几次字段
-ALTER TABLE `zyt_tcm_prescription`
-ADD COLUMN IF NOT EXISTS `times_per_day` int(11) NOT NULL DEFAULT 2 COMMENT '每天服用次数';
-```
-
-### 2. 后端修复 ✅
-
-#### 文件: `server/app/adminapi/logic/tcm/PrescriptionLogic.php`
-
-**add() 方法** - 添加处方时保存新字段:
-```php
-'dosage_amount' => isset($params['dosage_amount']) ? (float)$params['dosage_amount'] : null,
-'dosage_unit' => $params['dosage_unit'] ?? '',
-'need_decoction' => (int)($params['need_decoction'] ?? 0),
-'times_per_day' => (int)($params['times_per_day'] ?? 2),
-```
-
-**edit() 方法** - 编辑处方时更新新字段:
-```php
-'dosage_amount' => isset($params['dosage_amount']) ? (float)$params['dosage_amount'] : $prescription->dosage_amount,
-'dosage_unit' => $params['dosage_unit'] ?? $prescription->dosage_unit,
-'need_decoction' => isset($params['need_decoction']) ? (int)$params['need_decoction'] : (int)($prescription->need_decoction ?? 0),
-'times_per_day' => (int)($params['times_per_day'] ?? $prescription->times_per_day ?? 2),
-```
-
-### 3. 前端修复 ✅
-
-#### 文件: `admin/src/views/consumer/prescription/index.vue`
-
-**editForm 定义** - 添加新字段:
-```typescript
-const editForm = reactive({
- // ...
- dosage_amount: 1 as number | undefined,
- dosage_unit: 'g',
- need_decoction: false,
- times_per_day: 2,
- // ...
-})
-```
-
-**handleEdit() 方法** - 加载数据时填充新字段:
-```typescript
-editForm.dosage_amount = row.dosage_amount ?? undefined
-editForm.dosage_unit = row.dosage_unit || ''
-editForm.need_decoction = row.need_decoction === 1 || row.need_decoction === true
-editForm.times_per_day = row.times_per_day ?? 2
-```
-
-**watch 监听** - 处方类型变化时自动调整:
-```typescript
-watch(() => editForm.prescription_type, (newType) => {
- if (newType === '浓缩水丸') {
- editForm.dosage_unit = 'g'
- editForm.dosage_amount = 1
- editForm.need_decoction = false
- } else if (newType === '饮片') {
- editForm.dosage_unit = 'ml'
- editForm.dosage_amount = 50
- } else {
- editForm.dosage_unit = 'g'
- editForm.dosage_amount = undefined
- editForm.need_decoction = false
- }
-})
-```
-
-## 字段说明
-
-| 字段名 | 类型 | 默认值 | 说明 |
-|--------|------|--------|------|
-| `dosage_amount` | decimal(10,2) | NULL | 用量数值 |
-| `dosage_unit` | varchar(10) | '' | 用量单位 (g/ml) |
-| `need_decoction` | tinyint(1) | 0 | 是否代煎 (0=否, 1=是) |
-| `times_per_day` | int(11) | 2 | 每天服用次数 |
-
-## 数据流转
-
-### 创建处方
-```
-前端表单
- ↓
-{
- prescription_type: "浓缩水丸",
- dosage_amount: 5,
- dosage_unit: "g",
- need_decoction: false,
- times_per_day: 2
-}
- ↓
-PrescriptionLogic::add()
- ↓
-数据库保存
-```
-
-### 编辑处方
-```
-数据库读取
- ↓
-{
- dosage_amount: 5.00,
- dosage_unit: "g",
- need_decoction: 0,
- times_per_day: 2
-}
- ↓
-前端 editForm 填充
- ↓
-用户修改
- ↓
-PrescriptionLogic::edit()
- ↓
-数据库更新
-```
-
-## 部署步骤
-
-### 1. 备份数据库 ⚠️
-```bash
-mysqldump -u用户名 -p数据库名 > backup_$(date +%Y%m%d_%H%M%S).sql
-```
-
-### 2. 执行数据库迁移 ⚠️ 必须
-```bash
-mysql -u用户名 -p数据库名 < add_prescription_complete_fields.sql
-```
-
-### 3. 验证字段添加
-```sql
-SHOW COLUMNS FROM `zyt_tcm_prescription` LIKE 'dosage%';
-SHOW COLUMNS FROM `zyt_tcm_prescription` LIKE 'need_decoction';
-SHOW COLUMNS FROM `zyt_tcm_prescription` LIKE 'times_per_day';
-```
-
-### 4. 部署后端代码
-```bash
-# 上传修改的文件
-- server/app/adminapi/logic/tcm/PrescriptionLogic.php
-- server/app/common/model/tcm/Prescription.php
-```
-
-### 5. 部署前端代码
-```bash
-cd admin
-npm run build
-# 上传 dist/ 目录
-```
-
-### 6. 清除缓存(如需要)
-```bash
-cd server
-php think clear
-```
-
-## 测试清单
-
-### 创建处方测试
-- [ ] 创建浓缩水丸处方,设置用量5g,每天2次
-- [ ] 创建饮片处方,设置用量150ml,代煎,每天3次
-- [ ] 创建颗粒处方,设置用量15.5g,每天2次
-- [ ] 保存后查看数据库,验证字段正确存储
-
-### 编辑处方测试
-- [ ] 编辑已有处方,修改用量
-- [ ] 编辑饮片处方,修改代煎选项
-- [ ] 编辑处方,修改每天几次
-- [ ] 切换处方类型,验证用量自动调整
-- [ ] 保存后查看数据库,验证字段正确更新
-
-### 数据加载测试
-- [ ] 打开编辑对话框,验证用量正确显示
-- [ ] 验证代煎选项正确显示
-- [ ] 验证每天几次正确显示
-- [ ] 验证旧数据(无新字段)不报错
-
-## 修复的问题
-
-### 问题 1: 用量字段不保存 ✅
-**原因**: 后端 `add()` 和 `edit()` 方法没有处理 `dosage_amount`, `dosage_unit`, `need_decoction` 字段
-**修复**: 在两个方法中添加字段处理逻辑
-
-### 问题 2: 每天几次不保存 ✅
-**原因**:
-1. 数据库没有 `times_per_day` 字段
-2. 后端没有处理该字段
-3. 前端 editForm 定义错误(空字符串而非数字)
-
-**修复**:
-1. 添加数据库字段
-2. 后端添加字段处理
-3. 前端修正默认值为数字 2
-
-### 问题 3: 编辑时数据不加载 ✅
-**原因**: `handleEdit()` 方法没有加载新字段
-**修复**: 在加载数据时填充所有新字段
-
-## 相关文件
-
-### 数据库
-- `add_prescription_complete_fields.sql` - 完整字段迁移文件
-
-### 后端
-- `server/app/adminapi/logic/tcm/PrescriptionLogic.php` - 处方业务逻辑
-- `server/app/common/model/tcm/Prescription.php` - 处方模型
-
-### 前端
-- `admin/src/components/tcm-prescription/index.vue` - 处方组件
-- `admin/src/views/consumer/prescription/index.vue` - 处方列表
-
-### 文档
-- `PRESCRIPTION_DOSAGE_FEATURE.md` - 功能说明
-- `PRESCRIPTION_DOSAGE_DATABASE_IMPLEMENTATION.md` - 数据库实现
-- `PRESCRIPTION_DOSAGE_COMPLETE_SUMMARY.md` - 完整总结
-- `PRESCRIPTION_FIELDS_FIX_SUMMARY.md` - 本文档
-
-## 注意事项
-
-1. **必须先执行数据库迁移**,否则保存时会报错
-2. **旧数据兼容**:旧处方的新字段为 NULL 或默认值,不影响显示
-3. **类型转换**:
- - `dosage_amount`: float
- - `need_decoction`: boolean (前端) → int (后端/数据库)
- - `times_per_day`: int
-4. **数据验证**:前端使用下拉和数字输入框确保数据有效性
-
-## 总结
-
-✅ **数据库**: 4个字段已添加
-✅ **后端**: add() 和 edit() 方法已更新
-✅ **前端**: editForm 定义和数据加载已修复
-✅ **数据流转**: 完整的保存和读取流程
-
-**状态**: 所有问题已修复,功能完整可用!🎉
diff --git a/PRESCRIPTION_GET_BY_APPOINTMENT_PERMISSION.md b/PRESCRIPTION_GET_BY_APPOINTMENT_PERMISSION.md
deleted file mode 100644
index 37ef9c3b..00000000
--- a/PRESCRIPTION_GET_BY_APPOINTMENT_PERMISSION.md
+++ /dev/null
@@ -1,228 +0,0 @@
-# 预约处方查询权限控制
-
-## 修改说明
-
-为 `getByAppointment` 接口添加了权限检查,确保用户只能查询自己有权限查看的处方。
-
-## 修改内容
-
-### 1. Controller 层修改
-
-**文件**: `server/app/adminapi/controller/tcm/PrescriptionController.php`
-
-**修改前**:
-```php
-public function getByAppointment()
-{
- $appointmentId = (int)($this->request->get('appointment_id') ?? 0);
- if (!$appointmentId) {
- return $this->fail('预约ID不能为空');
- }
- $prescription = PrescriptionLogic::getByAppointment($appointmentId);
- return $this->data($prescription ?? []);
-}
-```
-
-**修改后**:
-```php
-public function getByAppointment()
-{
- $appointmentId = (int)($this->request->get('appointment_id') ?? 0);
- if (!$appointmentId) {
- return $this->fail('预约ID不能为空');
- }
- $prescription = PrescriptionLogic::getByAppointment($appointmentId, (int)$this->adminId, $this->adminInfo);
- if ($prescription === null) {
- $msg = PrescriptionLogic::getError();
- return $this->fail($msg !== '' ? $msg : '未找到处方或无权限查看');
- }
- return $this->data($prescription);
-}
-```
-
-**改进点**:
-- 传入当前登录用户的 `adminId` 和 `adminInfo`
-- 处理权限检查失败的情况,返回明确的错误信息
-- 区分"未找到处方"和"无权限查看"两种情况
-
-### 2. Logic 层修改
-
-**文件**: `server/app/adminapi/logic/tcm/PrescriptionLogic.php`
-
-**修改前**:
-```php
-/**
- * 根据预约ID获取处方
- */
-public static function getByAppointment(int $appointmentId): ?array
-{
- $row = Prescription::where('appointment_id', $appointmentId)
- ->whereNull('delete_time')
- ->order('id', 'desc')
- ->find();
- return $row ? $row->toArray() : null;
-}
-```
-
-**修改后**:
-```php
-/**
- * 根据预约ID获取处方(带权限检查)
- */
-public static function getByAppointment(int $appointmentId, int $viewerAdminId, array $viewerAdminInfo): ?array
-{
- self::$error = '';
- $row = Prescription::where('appointment_id', $appointmentId)
- ->whereNull('delete_time')
- ->order('id', 'desc')
- ->find();
-
- if (!$row) {
- return null;
- }
-
- // 权限检查:只返回当前用户有权限查看的处方
- if (!self::canViewPrescription($row, $viewerAdminId, $viewerAdminInfo)) {
- self::setError('无权限查看此处方');
- return null;
- }
-
- return $row->toArray();
-}
-```
-
-**改进点**:
-- 添加 `viewerAdminId` 和 `viewerAdminInfo` 参数
-- 使用 `canViewPrescription()` 方法进行权限检查
-- 设置错误信息,便于前端显示
-
-## 权限规则
-
-根据 `canViewPrescription()` 方法,以下用户可以查看处方:
-
-1. **超级管理员** - 可以查看所有处方
-2. **处方创建人** - 可以查看自己创建的处方
-3. **医助** - 可以查看自己协助的处方
-4. **共享处方** - 所有人可以查看标记为共享的处方
-5. **可见角色** - 处方指定的可见角色可以查看
-6. **有开方权限的医生** - 拥有 `tcm.diagnosis/kaifang` 权限的医生可以查看所有处方(只读)
-
-## 使用场景
-
-这个接口主要用于以下场景:
-
-1. **查看病历按钮** - 在预约列表中点击"查看病历"时调用
-2. **处方历史查询** - 根据预约ID查询患者的历史处方
-3. **医生协作** - 其他医生查看患者的处方记录(需要有开方权限)
-
-## 前端调用示例
-
-```typescript
-// admin/src/views/tcm/appointment/list.vue
-const handleViewCase = async (row: any) => {
- if (!row.id) {
- feedback.msgWarning('预约信息不完整')
- return
- }
- try {
- const prescription = await prescriptionGetByAppointment({ appointment_id: row.id })
- if (prescription?.id) {
- prescriptionRef.value?.openById(prescription.id)
- } else {
- feedback.msgWarning('该预约暂无病历记录,请先开方')
- }
- } catch (error: any) {
- // 权限不足或其他错误
- feedback.msgError(error?.msg || '获取病历失败')
- }
-}
-```
-
-## 错误处理
-
-### 可能的错误信息
-
-1. **"预约ID不能为空"** - 未传入 `appointment_id` 参数
-2. **"未找到处方或无权限查看"** - 处方不存在或当前用户无权限查看
-3. **"无权限查看此处方"** - 处方存在但当前用户无权限查看
-
-### 前端处理建议
-
-```typescript
-try {
- const prescription = await prescriptionGetByAppointment({ appointment_id: row.id })
- // 成功获取处方
-} catch (error: any) {
- if (error?.msg?.includes('无权限')) {
- feedback.msgWarning('您没有权限查看此处方')
- } else if (error?.msg?.includes('未找到')) {
- feedback.msgWarning('该预约暂无病历记录')
- } else {
- feedback.msgError(error?.msg || '获取病历失败')
- }
-}
-```
-
-## 测试建议
-
-### 1. 权限测试
-
-**测试用例 1: 创建人查看自己的处方**
-- 医生A创建处方
-- 医生A查看该处方
-- 预期:成功返回处方数据
-
-**测试用例 2: 其他医生查看处方(有开方权限)**
-- 医生A创建处方
-- 医生B(有 `tcm.diagnosis/kaifang` 权限)查看该处方
-- 预期:成功返回处方数据(只读)
-
-**测试用例 3: 其他医生查看处方(无开方权限)**
-- 医生A创建处方
-- 医生C(无 `tcm.diagnosis/kaifang` 权限)查看该处方
-- 预期:返回"无权限查看此处方"错误
-
-**测试用例 4: 医助查看协助的处方**
-- 医生A创建处方,医助B协助
-- 医助B查看该处方
-- 预期:成功返回处方数据
-
-**测试用例 5: 查看共享处方**
-- 医生A创建处方并标记为共享
-- 任何用户查看该处方
-- 预期:成功返回处方数据
-
-### 2. 边界测试
-
-**测试用例 6: 预约不存在**
-- 传入不存在的 `appointment_id`
-- 预期:返回"未找到处方或无权限查看"
-
-**测试用例 7: 预约存在但无处方**
-- 传入存在的 `appointment_id`,但该预约没有创建处方
-- 预期:返回"未找到处方或无权限查看"
-
-**测试用例 8: 处方已删除**
-- 传入已删除处方的 `appointment_id`
-- 预期:返回"未找到处方或无权限查看"
-
-## 安全性说明
-
-1. **防止越权访问** - 用户只能查看自己有权限的处方,防止查看其他医生的私有处方
-2. **数据隔离** - 通过权限检查实现数据隔离,保护患者隐私
-3. **审计追踪** - 所有查询操作都会记录当前用户信息,便于审计
-4. **错误信息脱敏** - 对于无权限的情况,不暴露处方是否存在的详细信息
-
-## 相关文档
-
-- `PRESCRIPTION_VIEW_PERMISSION_FIX.md` - 处方查看权限修复文档
-- `BUG_FIXES_SUMMARY.md` - Bug修复总结文档
-
-## 总结
-
-通过添加权限检查,确保了:
-- ✅ 用户只能查询自己有权限查看的处方
-- ✅ 防止越权访问其他医生的私有处方
-- ✅ 支持医生协作(有开方权限的医生可以查看所有处方)
-- ✅ 保护患者隐私和数据安全
-- ✅ 提供明确的错误信息,便于前端处理
diff --git a/PRESCRIPTION_IMPORT_FEATURE.md b/PRESCRIPTION_IMPORT_FEATURE.md
deleted file mode 100644
index a1d76328..00000000
--- a/PRESCRIPTION_IMPORT_FEATURE.md
+++ /dev/null
@@ -1,234 +0,0 @@
-# 处方导入功能说明
-
-## 功能概述
-
-在中医处方单组件中新增了"从处方库导入"功能,允许医生快速从处方库中选择已保存的处方模板,一键导入药材配方。
-
-## 使用位置
-
-**组件路径:** `admin/src/components/tcm-prescription/index.vue`
-
-**功能位置:** 中药处方 (RP) 区域,"添加中药"按钮旁边
-
-## 使用方法
-
-### 1. 打开处方库
-
-在编辑处方时,点击"从处方库导入"按钮,会弹出处方库选择对话框。
-
-### 2. 搜索处方
-
-- 在搜索框中输入处方名称
-- 按回车键或点击搜索按钮进行搜索
-- 支持模糊搜索
-
-### 3. 选择处方
-
-有两种方式导入处方:
-
-**方式一:点击"导入"按钮**
-- 在列表中找到需要的处方
-- 点击右侧的"导入"按钮
-
-**方式二:点击行**
-- 直接点击处方所在的行
-- 自动导入该处方
-
-### 4. 导入结果
-
-- 导入成功后,药材列表会自动填充
-- 显示成功提示:已导入处方「处方名称」,共X味药材
-- 对话框自动关闭
-
-## 功能特点
-
-### 1. 实时搜索
-- 支持按处方名称搜索
-- 清空搜索框自动刷新列表
-
-### 2. 分页显示
-- 默认每页显示15条
-- 支持切换每页10/15/20/50条
-- 显示总记录数
-
-### 3. 详细信息
-列表显示以下信息:
-- 处方名称
-- 药材数量(X味)
-- 药材明细(药材名称 + 剂量)
-- 创建人
-
-### 4. 权限控制
-- 显示自己创建的所有处方
-- 显示其他人创建的公开处方
-- 不显示其他人的私有处方
-
-### 5. 数据安全
-- 深拷贝药材数据,避免引用问题
-- 导入前验证药材数据是否存在
-
-## 界面示例
-
-```
-┌─────────────────────────────────────────────────────┐
-│ 从处方库导入 [×] │
-├─────────────────────────────────────────────────────┤
-│ [请输入处方名称搜索________________] [搜索] │
-│ │
-│ ┌─────────────────────────────────────────────────┐ │
-│ │ 处方名称 │ 药材数量 │ 药材明细 │ 创建人 │ 操作 │ │
-│ ├─────────────────────────────────────────────────┤ │
-│ │ 六味地黄丸│ 6味 │ 熟地黄24g、山茱萸12g...│ │
-│ │ │ │ │张医生│导入│ │
-│ ├─────────────────────────────────────────────────┤ │
-│ │ 补中益气汤│ 8味 │ 黄芪15g、党参10g... │ │
-│ │ │ │ │李医生│导入│ │
-│ └─────────────────────────────────────────────────┘ │
-│ │
-│ 共2条 [10] [15] [20] [50] [<] [1] [>] │
-└─────────────────────────────────────────────────────┘
-```
-
-## 技术实现
-
-### 1. API接口
-
-使用处方库列表接口:
-```typescript
-prescriptionLibraryLists({
- page_no: 1,
- page_size: 15,
- prescription_name: '搜索关键词',
- is_public: ''
-})
-```
-
-### 2. 数据结构
-
-处方库返回的数据格式:
-```typescript
-{
- lists: [
- {
- id: 1,
- prescription_name: '六味地黄丸',
- herbs: [
- { name: '熟地黄', dosage: 24 },
- { name: '山茱萸', dosage: 12 },
- // ...
- ],
- creator_name: '张医生',
- is_public: 1
- }
- ],
- count: 10
-}
-```
-
-### 3. 导入逻辑
-
-```typescript
-const handleImportLibrary = (row: any) => {
- // 1. 验证数据
- if (!row.herbs || row.herbs.length === 0) {
- feedback.msgWarning('该处方没有药材信息')
- return
- }
-
- // 2. 深拷贝数据
- const importedHerbs = JSON.parse(JSON.stringify(row.herbs))
-
- // 3. 替换当前药材列表
- formData.herbs = importedHerbs
-
- // 4. 提示成功
- feedback.msgSuccess(`已导入处方「${row.prescription_name}」`)
-
- // 5. 关闭对话框
- showLibraryDialog.value = false
-}
-```
-
-## 注意事项
-
-### 1. 数据覆盖
-- 导入处方会**替换**当前的药材列表
-- 如果已经添加了药材,导入后会被覆盖
-- 建议在开始编辑时就导入处方
-
-### 2. 数据验证
-- 导入前会验证处方是否有药材数据
-- 如果处方为空,会提示错误
-
-### 3. 权限限制
-- 只能看到自己的处方和公开的处方
-- 私有处方不会显示在列表中
-
-### 4. 搜索范围
-- 搜索只针对处方名称
-- 不支持按药材名称搜索
-
-## 使用场景
-
-### 场景1:快速开方
-医生经常开"六味地黄丸"处方,可以:
-1. 点击"从处方库导入"
-2. 搜索"六味地黄丸"
-3. 点击导入
-4. 药材自动填充,无需手动输入
-
-### 场景2:参考经典处方
-医生想参考其他医生的处方:
-1. 打开处方库
-2. 浏览公开的处方
-3. 选择合适的处方导入
-4. 根据患者情况调整剂量
-
-### 场景3:标准化处方
-医院推广标准处方:
-1. 管理员创建标准处方并设为公开
-2. 所有医生都能看到和使用
-3. 确保处方的一致性
-
-## 优势
-
-1. **提高效率** - 无需手动输入药材,节省时间
-2. **减少错误** - 避免手动输入导致的错误
-3. **标准化** - 使用标准处方模板,确保质量
-4. **知识共享** - 医生之间可以分享处方经验
-5. **快速学习** - 新医生可以参考资深医生的处方
-
-## 后续优化建议
-
-1. **批量导入** - 支持一次导入多个处方
-2. **处方预览** - 导入前预览处方详情
-3. **智能推荐** - 根据诊断自动推荐处方
-4. **使用统计** - 显示处方使用次数
-5. **收藏功能** - 收藏常用处方,快速访问
-
-## 常见问题
-
-### Q1: 导入后可以修改吗?
-A: 可以。导入后可以自由添加、删除、修改药材。
-
-### Q2: 导入会覆盖已有的药材吗?
-A: 是的。导入会替换当前所有药材,建议在开始时就导入。
-
-### Q3: 为什么看不到某个处方?
-A: 可能是该处方设置为私有,只有创建者可见。
-
-### Q4: 可以导入后再保存到处方库吗?
-A: 可以。导入、修改后,可以通过"是否共享"选项保存为新的处方模板。
-
-### Q5: 搜索不到处方怎么办?
-A: 检查处方名称是否正确,或清空搜索框查看所有处方。
-
-## 技术支持
-
-如有问题或建议,请联系技术支持团队。
-
----
-
-**版本:** v1.0.0
-**更新日期:** 2026-03-22
-**状态:** ✅ 已完成
diff --git a/PRESCRIPTION_LIBRARY_DEMO.md b/PRESCRIPTION_LIBRARY_DEMO.md
deleted file mode 100644
index 75fb96f4..00000000
--- a/PRESCRIPTION_LIBRARY_DEMO.md
+++ /dev/null
@@ -1,222 +0,0 @@
-# 处方库功能演示
-
-## 功能截图说明
-
-### 1. 处方库列表页面
-
-列表页面展示所有可访问的处方:
-- 自己创建的处方(无论是否公开)
-- 其他人创建的公开处方
-
-**列表字段:**
-- ID:处方唯一标识
-- 处方名称:如"六味地黄丸"、"补中益气汤"
-- 药材数量:显示该处方包含多少味药材
-- 药材明细:显示所有药材及其剂量
-- 是否公开:标签显示"所有人可见"或"仅自己可见"
-- 创建人:显示创建者姓名
-- 创建时间:处方创建的时间
-
-**操作按钮:**
-- 查看:查看处方详细信息(只读模式)
-- 编辑:修改处方信息(需要权限)
-- 删除:删除处方(仅创建者可操作)
-
-### 2. 搜索筛选功能
-
-**搜索条件:**
-- 处方名称:支持模糊搜索
-- 是否公开:可筛选"全部"、"仅自己可见"、"所有人可见"
-
-### 3. 新增/编辑处方弹窗
-
-**表单字段:**
-
-1. **处方名称**(必填)
- - 输入框,最多100个字符
- - 示例:六味地黄丸、补中益气汤、逍遥散
-
-2. **药材配方**(必填)
- - 动态表格,可添加多味药材
- - 每味药材包含:
- - 药材名称(文本输入)
- - 剂量/克(数字输入,支持小数)
- - 支持添加和删除药材行
-
-3. **是否公开**
- - 开关按钮
- - 关闭:仅自己可见
- - 开启:所有人可见
- - 提示文字:勾选后,所有医生都可以查看和使用此处方模板
-
-### 4. 查看处方(只读模式)
-
-点击"查看"按钮后,以只读模式展示处方详情:
-- 所有字段不可编辑
-- 不显示"添加药材"和"删除"按钮
-- 底部不显示"确定"按钮
-
-## 使用场景示例
-
-### 场景1:创建常用处方模板
-
-**背景:**
-张医生经常开"六味地黄丸"处方,每次都要重新输入药材和剂量,比较麻烦。
-
-**操作步骤:**
-1. 进入处方库页面
-2. 点击"新增处方"
-3. 输入处方名称:六味地黄丸
-4. 添加6味药材:
- - 熟地黄 24g
- - 山茱萸 12g
- - 山药 12g
- - 泽泻 9g
- - 牡丹皮 9g
- - 茯苓 9g
-5. 设置为"仅自己可见"
-6. 保存
-
-**效果:**
-以后张医生可以随时查看这个处方模板,快速参考药材配方。
-
-### 场景2:分享经典处方
-
-**背景:**
-李主任想把一些经典处方分享给科室的其他医生使用。
-
-**操作步骤:**
-1. 创建处方时,将"是否公开"设置为"所有人可见"
-2. 保存后,科室所有医生都能在处方库中看到这个处方
-
-**效果:**
-- 其他医生可以查看和参考这个处方
-- 但只有李主任可以修改或删除
-- 实现了知识共享
-
-### 场景3:管理个人处方库
-
-**背景:**
-王医生积累了很多个人处方,需要分类管理。
-
-**操作步骤:**
-1. 使用搜索功能快速查找处方
-2. 通过"是否公开"筛选个人处方和公开处方
-3. 编辑或删除不再使用的处方
-
-## 权限说明
-
-### 查看权限
-- ✅ 可以查看自己创建的所有处方(无论是否公开)
-- ✅ 可以查看其他人创建的公开处方
-- ❌ 不能查看其他人创建的私有处方
-
-### 编辑权限
-- ✅ 可以编辑自己创建的处方
-- ✅ 可以编辑其他人创建的公开处方(协作编辑)
-- ❌ 不能编辑其他人创建的私有处方
-
-### 删除权限
-- ✅ 只能删除自己创建的处方
-- ❌ 不能删除其他人创建的处方(即使是公开的)
-
-## 数据示例
-
-### 示例1:六味地黄丸
-
-```json
-{
- "prescription_name": "六味地黄丸",
- "herbs": [
- {"name": "熟地黄", "dosage": 24},
- {"name": "山茱萸", "dosage": 12},
- {"name": "山药", "dosage": 12},
- {"name": "泽泻", "dosage": 9},
- {"name": "牡丹皮", "dosage": 9},
- {"name": "茯苓", "dosage": 9}
- ],
- "is_public": 1
-}
-```
-
-### 示例2:补中益气汤
-
-```json
-{
- "prescription_name": "补中益气汤",
- "herbs": [
- {"name": "黄芪", "dosage": 15},
- {"name": "党参", "dosage": 10},
- {"name": "白术", "dosage": 10},
- {"name": "当归", "dosage": 10},
- {"name": "陈皮", "dosage": 6},
- {"name": "升麻", "dosage": 6},
- {"name": "柴胡", "dosage": 6},
- {"name": "甘草", "dosage": 5}
- ],
- "is_public": 1
-}
-```
-
-### 示例3:逍遥散
-
-```json
-{
- "prescription_name": "逍遥散",
- "herbs": [
- {"name": "柴胡", "dosage": 10},
- {"name": "当归", "dosage": 10},
- {"name": "白芍", "dosage": 10},
- {"name": "白术", "dosage": 10},
- {"name": "茯苓", "dosage": 10},
- {"name": "薄荷", "dosage": 6},
- {"name": "生姜", "dosage": 6},
- {"name": "甘草", "dosage": 5}
- ],
- "is_public": 0
-}
-```
-
-## 技术特点
-
-1. **独立性**
- - 不依赖诊单、预约等其他功能
- - 可以单独使用和维护
-
-2. **灵活性**
- - 支持任意数量的药材
- - 剂量支持小数(精确到0.1克)
-
-3. **安全性**
- - 完善的权限控制
- - 防止未授权访问和操作
-
-4. **易用性**
- - 简洁的界面设计
- - 直观的操作流程
-
-## 未来扩展建议
-
-1. **处方分类**
- - 添加处方分类功能(如:补益类、清热类等)
- - 支持按分类筛选
-
-2. **处方标签**
- - 支持为处方添加标签
- - 支持按标签搜索
-
-3. **使用统计**
- - 记录处方使用次数
- - 显示热门处方排行
-
-4. **处方导入导出**
- - 支持批量导入处方
- - 支持导出为Excel或PDF
-
-5. **处方评论**
- - 允许医生对公开处方进行评论
- - 分享使用心得
-
-6. **版本管理**
- - 记录处方修改历史
- - 支持回退到历史版本
diff --git a/PRESCRIPTION_LIBRARY_FILES.md b/PRESCRIPTION_LIBRARY_FILES.md
deleted file mode 100644
index 28c949f0..00000000
--- a/PRESCRIPTION_LIBRARY_FILES.md
+++ /dev/null
@@ -1,244 +0,0 @@
-# 处方库功能 - 文件清单
-
-## 📁 所有文件列表
-
-### 📄 文档文件(根目录)
-
-| 文件名 | 说明 | 用途 |
-|--------|------|------|
-| PRESCRIPTION_LIBRARY_README.md | 完整使用说明 | 详细的功能说明和使用指南 |
-| PRESCRIPTION_LIBRARY_DEMO.md | 功能演示文档 | 使用场景和示例 |
-| PRESCRIPTION_LIBRARY_SUMMARY.md | 开发总结 | 技术实现和开发总结 |
-| QUICK_START_PRESCRIPTION_LIBRARY.md | 快速开始指南 | 5分钟快速上手 |
-| PRESCRIPTION_LIBRARY_FILES.md | 本文件 | 文件清单 |
-| TEST_PRESCRIPTION_LIBRARY.sql | 测试SQL | 测试数据和查询语句 |
-
-### 🔧 安装脚本(根目录)
-
-| 文件名 | 说明 | 平台 |
-|--------|------|------|
-| install_prescription_library.bat | Windows安装脚本 | Windows |
-| install_prescription_library.sh | Linux/Mac安装脚本 | Linux/Mac |
-
-### 🗄️ 数据库文件
-
-| 文件路径 | 说明 |
-|----------|------|
-| server/database/migrations/create_prescription_library.sql | 数据库表结构 |
-
-### 🔙 后端文件
-
-#### 模型层
-| 文件路径 | 说明 |
-|----------|------|
-| server/app/common/model/tcm/PrescriptionLibrary.php | 处方库数据模型 |
-
-#### 控制器层
-| 文件路径 | 说明 |
-|----------|------|
-| server/app/adminapi/controller/tcm/PrescriptionLibraryController.php | 处方库控制器 |
-
-#### 业务逻辑层
-| 文件路径 | 说明 |
-|----------|------|
-| server/app/adminapi/logic/tcm/PrescriptionLibraryLogic.php | 处方库业务逻辑 |
-
-#### 列表逻辑层
-| 文件路径 | 说明 |
-|----------|------|
-| server/app/adminapi/lists/tcm/PrescriptionLibraryLists.php | 处方库列表逻辑 |
-
-#### 验证器层
-| 文件路径 | 说明 |
-|----------|------|
-| server/app/adminapi/validate/tcm/PrescriptionLibraryValidate.php | 处方库数据验证器 |
-
-### 🎨 前端文件
-
-#### 页面组件
-| 文件路径 | 说明 |
-|----------|------|
-| admin/src/views/consumer/prescription/list.vue | 处方库页面组件 |
-
-#### API接口
-| 文件路径 | 说明 | 修改内容 |
-|----------|------|----------|
-| admin/src/api/tcm.ts | API接口定义 | 新增处方库相关接口 |
-
-## 📊 文件统计
-
-### 按类型统计
-- 文档文件:6个
-- 安装脚本:2个
-- 数据库文件:1个
-- 后端PHP文件:5个
-- 前端Vue文件:1个
-- 前端API文件:1个(修改)
-
-**总计:** 16个文件(15个新建 + 1个修改)
-
-### 按目录统计
-```
-根目录/
-├── 文档文件 × 6
-├── 安装脚本 × 2
-└── 测试SQL × 1
-
-server/
-├── database/migrations/ × 1
-├── app/common/model/tcm/ × 1
-└── app/adminapi/
- ├── controller/tcm/ × 1
- ├── logic/tcm/ × 1
- ├── lists/tcm/ × 1
- └── validate/tcm/ × 1
-
-admin/
-└── src/
- ├── views/consumer/prescription/ × 1
- └── api/ × 1(修改)
-```
-
-## 🔍 文件依赖关系
-
-```
-前端页面 (list.vue)
- ↓
-前端API (tcm.ts)
- ↓
-后端控制器 (PrescriptionLibraryController.php)
- ↓
-后端验证器 (PrescriptionLibraryValidate.php)
- ↓
-后端业务逻辑 (PrescriptionLibraryLogic.php)
- ↓
-后端数据模型 (PrescriptionLibrary.php)
- ↓
-数据库表 (zyt_prescription_library)
-```
-
-## 📝 代码行数统计
-
-| 文件类型 | 文件数 | 预估代码行数 |
-|---------|--------|-------------|
-| 文档 (Markdown) | 6 | ~1500行 |
-| SQL | 2 | ~100行 |
-| PHP | 5 | ~400行 |
-| Vue | 1 | ~350行 |
-| TypeScript | 1 | ~30行(新增部分) |
-| Shell脚本 | 2 | ~100行 |
-| **总计** | **17** | **~2480行** |
-
-## ✅ 文件完整性检查
-
-### 必需文件检查清单
-
-#### 后端文件
-- [x] 数据库表结构文件
-- [x] 数据模型文件
-- [x] 控制器文件
-- [x] 业务逻辑文件
-- [x] 列表逻辑文件
-- [x] 验证器文件
-
-#### 前端文件
-- [x] 页面组件文件
-- [x] API接口文件(已更新)
-
-#### 文档文件
-- [x] 使用说明文档
-- [x] 功能演示文档
-- [x] 开发总结文档
-- [x] 快速开始文档
-- [x] 文件清单文档
-- [x] 测试SQL文件
-
-#### 安装文件
-- [x] Windows安装脚本
-- [x] Linux/Mac安装脚本
-
-**结果:** ✅ 所有必需文件已创建完成
-
-## 🎯 下一步操作
-
-1. **安装数据库表**
- - 运行安装脚本或手动执行SQL
-
-2. **配置菜单权限**
- - 在后台管理系统中添加菜单项
- - 配置访问权限
-
-3. **测试功能**
- - 创建测试处方
- - 验证各项功能
-
-4. **部署上线**
- - 备份数据库
- - 部署代码
- - 验证生产环境
-
-## 📦 打包清单
-
-如需打包交付,建议包含以下文件:
-
-### 核心文件(必需)
-```
-✅ server/database/migrations/create_prescription_library.sql
-✅ server/app/common/model/tcm/PrescriptionLibrary.php
-✅ server/app/adminapi/controller/tcm/PrescriptionLibraryController.php
-✅ server/app/adminapi/logic/tcm/PrescriptionLibraryLogic.php
-✅ server/app/adminapi/lists/tcm/PrescriptionLibraryLists.php
-✅ server/app/adminapi/validate/tcm/PrescriptionLibraryValidate.php
-✅ admin/src/views/consumer/prescription/list.vue
-✅ admin/src/api/tcm.ts(修改部分)
-```
-
-### 文档文件(推荐)
-```
-✅ PRESCRIPTION_LIBRARY_README.md
-✅ QUICK_START_PRESCRIPTION_LIBRARY.md
-✅ PRESCRIPTION_LIBRARY_DEMO.md
-✅ PRESCRIPTION_LIBRARY_SUMMARY.md
-```
-
-### 安装文件(推荐)
-```
-✅ install_prescription_library.bat
-✅ install_prescription_library.sh
-```
-
-### 测试文件(可选)
-```
-✅ TEST_PRESCRIPTION_LIBRARY.sql
-```
-
-## 🔐 文件权限建议
-
-### Linux/Mac 系统
-```bash
-# 安装脚本
-chmod +x install_prescription_library.sh
-
-# PHP文件
-chmod 644 server/app/**/*.php
-
-# Vue文件
-chmod 644 admin/src/**/*.vue
-
-# SQL文件
-chmod 644 server/database/migrations/*.sql
-```
-
-### Windows 系统
-默认权限即可,无需特殊设置。
-
-## 📌 版本信息
-
-- **版本号:** v1.0.0
-- **创建日期:** 2026-03-22
-- **最后更新:** 2026-03-22
-- **状态:** ✅ 已完成
-
----
-
-**所有文件已创建完成,可以开始安装和使用!** 🎉
diff --git a/PRESCRIPTION_LIBRARY_README.md b/PRESCRIPTION_LIBRARY_README.md
deleted file mode 100644
index 9bb72300..00000000
--- a/PRESCRIPTION_LIBRARY_README.md
+++ /dev/null
@@ -1,180 +0,0 @@
-# 处方库功能说明
-
-## 功能概述
-
-处方库是一个独立的处方模板管理系统,允许医生创建、管理和分享常用的中药处方配方。
-
-## 主要特性
-
-1. **处方管理**
- - 创建处方模板,包含处方名称和药材配方
- - 每味药材包含名称和剂量(克)
- - 支持编辑和删除处方
-
-2. **权限控制**
- - 仅自己可见:只有创建者可以查看和使用
- - 所有人可见:所有医生都可以查看和使用(但只有创建者可以删除)
-
-3. **独立功能**
- - 不依赖其他功能模块
- - 不与诊单、预约等功能关联
- - 纯粹的处方模板库
-
-## 安装步骤
-
-### Windows 系统
-
-1. 双击运行 `install_prescription_library.bat`
-2. 按提示输入数据库配置信息
-3. 等待安装完成
-
-### Linux/Mac 系统
-
-1. 给脚本添加执行权限:
- ```bash
- chmod +x install_prescription_library.sh
- ```
-
-2. 运行安装脚本:
- ```bash
- ./install_prescription_library.sh
- ```
-
-3. 按提示输入数据库配置信息
-
-### 手动安装
-
-如果自动安装脚本无法运行,可以手动执行以下步骤:
-
-1. 使用数据库管理工具(如 phpMyAdmin、Navicat 等)
-2. 打开 `server/database/migrations/create_prescription_library.sql` 文件
-3. 执行其中的 SQL 语句
-
-## 数据库表结构
-
-### zyt_prescription_library(处方库表)
-
-| 字段名 | 类型 | 说明 |
-|--------|------|------|
-| id | int | 主键ID |
-| prescription_name | varchar(100) | 处方名称 |
-| herbs | text | 药材列表JSON |
-| is_public | tinyint(1) | 是否公开:0-仅自己可见 1-所有人可见 |
-| creator_id | int | 创建人ID |
-| creator_name | varchar(50) | 创建人姓名 |
-| create_time | int | 创建时间 |
-| update_time | int | 更新时间 |
-| delete_time | int | 删除时间 |
-
-## 使用说明
-
-### 创建处方
-
-1. 点击"新增处方"按钮
-2. 输入处方名称(如:六味地黄丸、补中益气汤)
-3. 点击"添加药材"按钮添加药材
-4. 输入药材名称和剂量(克)
-5. 选择是否公开
-6. 点击"确定"保存
-
-### 编辑处方
-
-1. 在列表中找到要编辑的处方
-2. 点击"编辑"按钮
-3. 修改处方信息
-4. 点击"确定"保存
-
-### 删除处方
-
-1. 在列表中找到要删除的处方
-2. 点击"删除"按钮
-3. 确认删除操作
-
-注意:只有创建者才能删除处方
-
-### 查看处方
-
-1. 在列表中找到要查看的处方
-2. 点击"查看"按钮
-3. 查看处方详细信息
-
-## API 接口
-
-### 后端路由
-
-所有接口都在 `tcm.prescriptionLibrary` 控制器下:
-
-- `GET /tcm.prescriptionLibrary/lists` - 获取处方库列表
-- `POST /tcm.prescriptionLibrary/add` - 添加处方
-- `POST /tcm.prescriptionLibrary/edit` - 编辑处方
-- `POST /tcm.prescriptionLibrary/delete` - 删除处方
-- `GET /tcm.prescriptionLibrary/detail` - 获取处方详情
-
-### 前端 API
-
-在 `admin/src/api/tcm.ts` 中:
-
-```typescript
-// 处方库列表
-prescriptionLibraryLists(params)
-
-// 添加处方库
-prescriptionLibraryAdd(params)
-
-// 编辑处方库
-prescriptionLibraryEdit(params)
-
-// 删除处方库
-prescriptionLibraryDelete({ id })
-
-// 处方库详情
-prescriptionLibraryDetail({ id })
-```
-
-## 文件清单
-
-### 后端文件
-
-- `server/database/migrations/create_prescription_library.sql` - 数据库表结构
-- `server/app/common/model/tcm/PrescriptionLibrary.php` - 数据模型
-- `server/app/adminapi/controller/tcm/PrescriptionLibraryController.php` - 控制器
-- `server/app/adminapi/logic/tcm/PrescriptionLibraryLogic.php` - 业务逻辑
-- `server/app/adminapi/lists/tcm/PrescriptionLibraryLists.php` - 列表逻辑
-- `server/app/adminapi/validate/tcm/PrescriptionLibraryValidate.php` - 验证器
-
-### 前端文件
-
-- `admin/src/views/consumer/prescription/list.vue` - 处方库页面
-- `admin/src/api/tcm.ts` - API 接口(已更新)
-
-### 安装文件
-
-- `install_prescription_library.sh` - Linux/Mac 安装脚本
-- `install_prescription_library.bat` - Windows 安装脚本
-- `PRESCRIPTION_LIBRARY_README.md` - 本说明文档
-
-## 注意事项
-
-1. 处方库功能完全独立,不影响现有的处方管理功能
-2. 药材数据以 JSON 格式存储在数据库中
-3. 权限控制确保只有创建者或公开的处方才能被访问
-4. 删除操作只有创建者才能执行
-5. 建议定期备份数据库
-
-## 常见问题
-
-### Q: 安装脚本执行失败?
-A: 请检查:
-- MySQL 是否已安装并正在运行
-- 数据库配置信息是否正确
-- 是否有足够的数据库权限
-
-### Q: 无法看到其他人创建的处方?
-A: 只有设置为"所有人可见"的处方才能被其他医生查看
-
-### Q: 可以删除别人创建的处方吗?
-A: 不可以,只有创建者才能删除自己创建的处方
-
-## 技术支持
-
-如有问题,请联系技术支持团队。
diff --git a/PRESCRIPTION_LIBRARY_SUMMARY.md b/PRESCRIPTION_LIBRARY_SUMMARY.md
deleted file mode 100644
index ac4a187a..00000000
--- a/PRESCRIPTION_LIBRARY_SUMMARY.md
+++ /dev/null
@@ -1,236 +0,0 @@
-# 处方库功能开发总结
-
-## 项目概述
-
-已完成一个独立的处方库管理系统,用于管理中药处方模板。该功能完全独立,不与其他业务模块关联。
-
-## 核心功能
-
-### 1. 处方管理
-- ✅ 创建处方(处方名称 + 药材配方)
-- ✅ 编辑处方
-- ✅ 删除处方
-- ✅ 查看处方详情
-- ✅ 处方列表展示
-
-### 2. 药材管理
-- ✅ 动态添加药材
-- ✅ 设置药材名称和剂量(克)
-- ✅ 删除药材
-- ✅ 支持小数剂量(精确到0.1克)
-
-### 3. 权限控制
-- ✅ 是否公开设置(仅自己可见/所有人可见)
-- ✅ 查看权限控制
-- ✅ 编辑权限控制
-- ✅ 删除权限控制(仅创建者)
-
-### 4. 搜索筛选
-- ✅ 按处方名称搜索(模糊匹配)
-- ✅ 按是否公开筛选
-- ✅ 分页显示
-
-## 技术实现
-
-### 后端(PHP + ThinkPHP)
-
-#### 数据库表
-- **表名:** `zyt_prescription_library`
-- **字段:**
- - id:主键
- - prescription_name:处方名称
- - herbs:药材列表(JSON格式)
- - is_public:是否公开(0-私有,1-公开)
- - creator_id:创建人ID
- - creator_name:创建人姓名
- - create_time:创建时间
- - update_time:更新时间
- - delete_time:删除时间(软删除)
-
-#### 文件结构
-```
-server/
-├── database/migrations/
-│ └── create_prescription_library.sql # 数据库表结构
-├── app/common/model/tcm/
-│ └── PrescriptionLibrary.php # 数据模型
-├── app/adminapi/
-│ ├── controller/tcm/
-│ │ └── PrescriptionLibraryController.php # 控制器
-│ ├── logic/tcm/
-│ │ └── PrescriptionLibraryLogic.php # 业务逻辑
-│ ├── lists/tcm/
-│ │ └── PrescriptionLibraryLists.php # 列表逻辑
-│ └── validate/tcm/
-│ └── PrescriptionLibraryValidate.php # 数据验证
-```
-
-#### API接口
-- `GET /tcm.prescriptionLibrary/lists` - 获取列表
-- `POST /tcm.prescriptionLibrary/add` - 添加处方
-- `POST /tcm.prescriptionLibrary/edit` - 编辑处方
-- `POST /tcm.prescriptionLibrary/delete` - 删除处方
-- `GET /tcm.prescriptionLibrary/detail` - 获取详情
-
-### 前端(Vue 3 + Element Plus)
-
-#### 文件结构
-```
-admin/
-├── src/
-│ ├── views/consumer/prescription/
-│ │ └── list.vue # 处方库页面
-│ └── api/
-│ └── tcm.ts # API接口(已更新)
-```
-
-#### 主要组件
-- 搜索表单
-- 数据表格
-- 新增/编辑弹窗
-- 药材动态表格
-
-## 安装部署
-
-### 自动安装
-- **Windows:** `install_prescription_library.bat`
-- **Linux/Mac:** `install_prescription_library.sh`
-
-### 手动安装
-执行 SQL 文件:`server/database/migrations/create_prescription_library.sql`
-
-## 文档清单
-
-1. **PRESCRIPTION_LIBRARY_README.md** - 完整使用说明
-2. **PRESCRIPTION_LIBRARY_DEMO.md** - 功能演示和使用场景
-3. **PRESCRIPTION_LIBRARY_SUMMARY.md** - 本文档(开发总结)
-4. **TEST_PRESCRIPTION_LIBRARY.sql** - 测试SQL语句
-
-## 代码质量
-
-### 后端
-- ✅ 遵循 PSR 编码规范
-- ✅ 完整的异常处理
-- ✅ 数据验证
-- ✅ 权限控制
-- ✅ 软删除支持
-- ✅ 无语法错误
-
-### 前端
-- ✅ Vue 3 Composition API
-- ✅ TypeScript 类型定义
-- ✅ 响应式设计
-- ✅ 表单验证
-- ✅ 用户友好的提示
-- ✅ 无语法错误
-
-## 特色亮点
-
-1. **完全独立**
- - 不依赖其他业务模块
- - 可独立部署和维护
-
-2. **权限灵活**
- - 支持私有和公开两种模式
- - 精细的权限控制
-
-3. **数据安全**
- - 软删除机制
- - 权限验证
- - 数据验证
-
-4. **用户体验**
- - 直观的界面
- - 流畅的操作
- - 友好的提示
-
-5. **扩展性强**
- - 清晰的代码结构
- - 易于扩展新功能
-
-## 测试建议
-
-### 功能测试
-1. ✅ 创建处方(私有)
-2. ✅ 创建处方(公开)
-3. ✅ 编辑自己的处方
-4. ✅ 编辑公开的处方
-5. ✅ 删除自己的处方
-6. ✅ 尝试删除他人的处方(应失败)
-7. ✅ 查看处方列表
-8. ✅ 搜索处方
-9. ✅ 筛选处方
-
-### 权限测试
-1. ✅ 用户A创建私有处方,用户B不能查看
-2. ✅ 用户A创建公开处方,用户B可以查看
-3. ✅ 用户A创建处方,用户B不能删除
-4. ✅ 用户A创建公开处方,用户B可以编辑
-
-### 数据验证测试
-1. ✅ 处方名称为空(应提示错误)
-2. ✅ 药材列表为空(应提示错误)
-3. ✅ 药材名称为空(应提示错误)
-4. ✅ 药材剂量为0或负数(应提示错误)
-
-## 性能优化建议
-
-1. **数据库索引**
- - ✅ 已添加 creator_id 索引
- - ✅ 已添加 is_public 索引
- - ✅ 已添加 create_time 索引
-
-2. **查询优化**
- - ✅ 使用字段筛选,避免 SELECT *
- - ✅ 分页查询
- - ✅ 软删除过滤
-
-3. **前端优化**
- - ✅ 按需加载
- - ✅ 防抖处理(搜索)
- - ✅ 加载状态提示
-
-## 后续优化方向
-
-### 短期(1-2周)
-1. 添加处方分类功能
-2. 添加处方标签
-3. 优化搜索功能(支持药材名称搜索)
-
-### 中期(1-2月)
-1. 添加使用统计
-2. 添加热门处方排行
-3. 支持处方导入导出
-
-### 长期(3-6月)
-1. 添加处方评论功能
-2. 添加版本管理
-3. 添加处方推荐算法
-4. 移动端适配
-
-## 维护说明
-
-### 数据备份
-建议定期备份 `zyt_prescription_library` 表数据。
-
-### 日志监控
-关注以下日志:
-- 处方创建失败日志
-- 权限验证失败日志
-- 数据验证失败日志
-
-### 性能监控
-关注以下指标:
-- 列表查询响应时间
-- 数据库连接数
-- 并发用户数
-
-## 联系方式
-
-如有问题或建议,请联系开发团队。
-
----
-
-**开发完成时间:** 2026-03-22
-**版本:** v1.0.0
-**状态:** ✅ 已完成并测试通过
diff --git a/PRESCRIPTION_MANAGEMENT.md b/PRESCRIPTION_MANAGEMENT.md
deleted file mode 100644
index e4882e5f..00000000
--- a/PRESCRIPTION_MANAGEMENT.md
+++ /dev/null
@@ -1,126 +0,0 @@
-# 处方管理功能说明
-
-## 功能概述
-
-医生处方管理功能,支持创建、编辑、查看和删除处方,并支持处方共享功能。
-
-## 主要功能
-
-### 1. 处方列表
-- 查看所有处方(包括自己创建的和共享的)
-- 按处方名称、患者姓名搜索
-- 按是否共享筛选
-- 显示处方基本信息:编号、名称、患者信息、药材数量、剂数、共享状态等
-
-### 2. 新增处方
-- 处方名称:必填,用于标识处方
-- 患者信息:姓名、性别、年龄
-- 处方日期:默认当天
-- 临床诊断:必填,描述病情
-- 药材配方:
- - 支持添加多味药材
- - 每味药材包含:名称、剂量(克)
- - 可动态增删药材
-- 剂数和单位:默认7剂,支持多种单位(剂、丸、袋、盒等)
-- 服用天数:默认7天,可自定义
-- 用法说明:默认"水煎服,一日二次"
-- 服用说明:详细的服用注意事项,如饭前/饭后服用、忌口等
-- 医师姓名:必填
-- 是否共享:
- - 不勾选(默认):仅自己可见
- - 勾选:所有医生都可以查看和使用
-
-### 3. 编辑处方
-- 可编辑处方的所有信息
-- 权限控制:
- - 创建者可以编辑自己的处方
- - 共享的处方所有人都可以编辑
-
-### 4. 查看处方
-- 查看处方的详细信息
-- 只读模式,不可编辑
-
-### 5. 删除处方
-- 删除不需要的处方
-- 需要确认操作
-
-## 共享功能说明
-
-### 共享规则
-1. **不共享(默认)**:
- - 只有创建者自己可以看到
- - 其他医生无法查看和使用
- - 适用于个人专用处方
-
-2. **共享**:
- - 所有医生都可以查看
- - 所有医生都可以编辑
- - 适用于常用处方模板、标准处方等
-
-### 使用场景
-- **个人处方**:医生为特定患者开具的处方,不需要共享
-- **处方模板**:常用的标准处方,可以共享给所有医生使用
-- **经验处方**:经过验证的有效处方,共享给团队学习和使用
-
-## 安装步骤
-
-### 1. 执行数据库迁移
-
-**Windows系统:**
-```bash
-install_prescription_shared.bat
-```
-
-**Linux/Mac系统:**
-```bash
-chmod +x install_prescription_shared.sh
-./install_prescription_shared.sh
-```
-
-### 2. 访问功能
-登录管理后台,进入"消费者管理" -> "处方管理"
-
-## 技术实现
-
-### 前端
-- 文件位置:`admin/src/views/consumer/prescription/index.vue`
-- 使用 Vue 3 + Element Plus
-- 支持表格展示、表单编辑、弹窗操作
-
-### 后端
-- 控制器:`server/app/adminapi/controller/tcm/PrescriptionController.php`
-- 逻辑层:`server/app/adminapi/logic/tcm/PrescriptionLogic.php`
-- 列表类:`server/app/adminapi/lists/tcm/PrescriptionLists.php`
-- 验证器:`server/app/adminapi/validate/tcm/PrescriptionValidate.php`
-- 模型:`server/app/common/model/tcm/Prescription.php`
-
-### API接口
-- `GET /tcm.prescription/lists` - 处方列表
-- `POST /tcm.prescription/add` - 添加处方
-- `POST /tcm.prescription/edit` - 编辑处方
-- `POST /tcm.prescription/delete` - 删除处方
-- `GET /tcm.prescription/detail` - 处方详情
-
-### 数据库字段
-新增字段:
-- `prescription_name` - 处方名称(varchar 100)
-- `is_shared` - 是否共享(tinyint 1,0-不共享,1-共享)
-- `usage_days` - 服用天数(int 11,默认7天)
-- `usage_method` - 服用说明(varchar 200)
-
-## 注意事项
-
-1. 处方名称和临床诊断为必填项
-2. 至少需要添加一味药材
-3. 药材剂量必须大于0
-4. 共享的处方所有人都可以编辑,请谨慎操作
-5. 删除操作不可恢复,请确认后再删除
-
-## 后续优化建议
-
-1. 添加处方模板功能,快速创建常用处方
-2. 支持处方打印和导出
-3. 添加处方审核流程
-4. 支持处方版本管理
-5. 添加处方使用统计
-6. 支持处方收藏功能
diff --git a/PRESCRIPTION_ORDER_MIN_AMOUNT_VALIDATION.md b/PRESCRIPTION_ORDER_MIN_AMOUNT_VALIDATION.md
deleted file mode 100644
index 1745c131..00000000
--- a/PRESCRIPTION_ORDER_MIN_AMOUNT_VALIDATION.md
+++ /dev/null
@@ -1,318 +0,0 @@
-# 处方订单最小金额验证优化
-
-## 需求说明
-
-在创建处方订单时,如果关联的支付单总金额小于配置的最小金额(`PRESCRIPTION_ORDER_LINK_PAY_MIN_AMOUNT`),则不允许创建订单。
-
-## 配置说明
-
-**配置文件**:`server/.env`
-
-```env
-PRESCRIPTION_ORDER_LINK_PAY_MIN_AMOUNT="100"
-```
-
-**说明**:
-- 单位:元(人民币)
-- 默认值:0(不校验)
-- 当前配置:100元
-
-## 验证规则
-
-### 修改前(原逻辑)
-
-```
-1. 订单金额 >= 最小金额 ✓
-2. 每个关联支付单金额 >= 最小金额 ✓
-```
-
-**问题**:
-- 要求每个支付单都必须 >= 100元
-- 不允许多个小额支付单组合(如 60元 + 50元 = 110元)
-
-### 修改后(新逻辑)
-
-```
-1. 订单金额 >= 最小金额 ✓
-2. 必须关联至少一个支付单 ✓
-3. 关联支付单总金额 >= 最小金额 ✓
-```
-
-**优势**:
-- 允许多个小额支付单组合
-- 更灵活的支付方式
-- 总金额达标即可
-
-## 代码修改
-
-**文件**:`server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
-
-**方法**:`assertDepositAndLinkPayRules()`
-
-### 修改内容
-
-```php
-public static function assertDepositAndLinkPayRules(float $orderAmount, array $payOrderIds): ?string
-{
- $min = self::depositMinAmount();
- if ($min <= 0) {
- return null;
- }
-
- // 1. 检查订单金额
- if ($orderAmount < $min) {
- return '订单金额须大于等于定金门槛 ¥' . $min . '(当前 ¥' . $orderAmount . ')';
- }
-
- // 2. 检查是否关联支付单
- if ($payOrderIds === []) {
- return '未关联支付单,关联支付单总金额须大于等于定金门槛 ¥' . $min;
- }
-
- // 3. 检查关联支付单的总金额
- $rows = Order::whereIn('id', $payOrderIds)->whereNull('delete_time')->column('amount', 'id');
- $totalAmount = 0.0;
- foreach ($payOrderIds as $pid) {
- $pid = (int) $pid;
- if ($pid <= 0) {
- continue;
- }
- $amt = round((float) ($rows[$pid] ?? 0), 2);
- $totalAmount += $amt;
- }
-
- if ($totalAmount < $min) {
- return '关联支付单总金额须大于等于定金门槛 ¥' . $min . '(当前总金额 ¥' . $totalAmount . ')';
- }
-
- return null;
-}
-```
-
-## 验证场景
-
-### 场景1:订单金额不足
-
-**配置**:最小金额 = 100元
-
-**输入**:
-- 订单金额:80元
-- 关联支付单:[100元]
-
-**结果**:❌ 失败
-**错误信息**:订单金额须大于等于定金门槛 ¥100(当前 ¥80)
-
----
-
-### 场景2:未关联支付单
-
-**配置**:最小金额 = 100元
-
-**输入**:
-- 订单金额:150元
-- 关联支付单:[]
-
-**结果**:❌ 失败
-**错误信息**:未关联支付单,关联支付单总金额须大于等于定金门槛 ¥100
-
----
-
-### 场景3:关联支付单总金额不足
-
-**配置**:最小金额 = 100元
-
-**输入**:
-- 订单金额:150元
-- 关联支付单:[30元, 40元, 20元]
-- 总金额:90元
-
-**结果**:❌ 失败
-**错误信息**:关联支付单总金额须大于等于定金门槛 ¥100(当前总金额 ¥90)
-
----
-
-### 场景4:多个小额支付单组合达标
-
-**配置**:最小金额 = 100元
-
-**输入**:
-- 订单金额:150元
-- 关联支付单:[60元, 50元]
-- 总金额:110元
-
-**结果**:✅ 成功
-**说明**:虽然单个支付单都小于100元,但总金额达标
-
----
-
-### 场景5:单个大额支付单
-
-**配置**:最小金额 = 100元
-
-**输入**:
-- 订单金额:150元
-- 关联支付单:[120元]
-- 总金额:120元
-
-**结果**:✅ 成功
-
----
-
-### 场景6:配置为0(不校验)
-
-**配置**:最小金额 = 0元
-
-**输入**:
-- 订单金额:10元
-- 关联支付单:[]
-
-**结果**:✅ 成功
-**说明**:最小金额为0时,不进行任何校验
-
----
-
-### 场景7:多个支付单,总金额刚好达标
-
-**配置**:最小金额 = 100元
-
-**输入**:
-- 订单金额:150元
-- 关联支付单:[30元, 30元, 40元]
-- 总金额:100元
-
-**结果**:✅ 成功
-**说明**:总金额刚好等于最小金额,符合"大于等于"的要求
-
-## 前端提示优化
-
-**文件**:`admin/src/views/consumer/prescription/order_list.vue`
-
-**当前提示**:
-```
-支付单审核时可对照此处关联的收款记录。
-列表仅展示金额大于等于定金门槛的收款单(门槛为 0 时展示全部)。
-```
-
-**建议修改为**:
-```
-支付单审核时可对照此处关联的收款记录。
-关联支付单总金额须大于等于定金门槛(当前门槛:¥100)。
-列表仅展示金额大于等于定金门槛的收款单(门槛为 0 时展示全部)。
-```
-
-## 配置管理
-
-### 查看当前配置
-
-```bash
-# 查看 .env 文件
-cat server/.env | grep PRESCRIPTION_ORDER_LINK_PAY_MIN_AMOUNT
-```
-
-### 修改配置
-
-```bash
-# 编辑 .env 文件
-vim server/.env
-
-# 修改为 200 元
-PRESCRIPTION_ORDER_LINK_PAY_MIN_AMOUNT="200"
-
-# 修改为 0(不校验)
-PRESCRIPTION_ORDER_LINK_PAY_MIN_AMOUNT="0"
-```
-
-### 配置生效
-
-修改 `.env` 文件后,配置会立即生效,无需重启服务。
-
-## 业务逻辑说明
-
-### 为什么需要最小金额限制?
-
-1. **定金机制**:确保客户已支付足够的定金
-2. **风险控制**:避免小额订单的履约风险
-3. **业务规范**:统一订单金额标准
-
-### 为什么改为总金额验证?
-
-1. **灵活性**:允许客户分多次支付
-2. **用户体验**:不强制单次支付大额
-3. **实际需求**:多次小额支付也能达到定金要求
-
-### 为什么必须关联支付单?
-
-1. **资金确认**:确保订单有对应的收款记录
-2. **审核依据**:支付单审核时需要对照
-3. **财务管理**:便于对账和统计
-
-## 测试建议
-
-### 1. 单元测试
-
-```php
-// 测试订单金额不足
-$result = PrescriptionOrderLogic::assertDepositAndLinkPayRules(80, [1]);
-assert($result !== null);
-
-// 测试未关联支付单
-$result = PrescriptionOrderLogic::assertDepositAndLinkPayRules(150, []);
-assert($result !== null);
-
-// 测试总金额不足
-$result = PrescriptionOrderLogic::assertDepositAndLinkPayRules(150, [1, 2]); // 假设总金额 < 100
-assert($result !== null);
-
-// 测试总金额达标
-$result = PrescriptionOrderLogic::assertDepositAndLinkPayRules(150, [3, 4]); // 假设总金额 >= 100
-assert($result === null);
-```
-
-### 2. 集成测试
-
-1. 创建多个小额支付单(如 60元、50元)
-2. 创建订单,关联这些支付单
-3. 验证是否能成功创建
-4. 验证错误提示是否正确
-
-### 3. 边界测试
-
-- 总金额刚好等于最小金额(100元)
-- 总金额略小于最小金额(99.99元)
-- 总金额略大于最小金额(100.01元)
-- 最小金额为0
-- 最小金额为负数(应该按0处理)
-
-## 相关文件
-
-### 配置文件
-- `server/.env` - 环境配置
-- `server/config/prescription_order.php` - 订单配置
-
-### 后端文件
-- `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php` - 订单逻辑
-- `server/app/adminapi/validate/tcm/PrescriptionOrderValidate.php` - 订单验证
-
-### 前端文件
-- `admin/src/views/consumer/prescription/order_list.vue` - 订单列表页面
-
-## 总结
-
-✅ 修改验证逻辑:从"每个支付单"改为"支付单总金额"
-✅ 添加必须关联支付单的验证
-✅ 优化错误提示信息,显示当前总金额
-✅ 支持多个小额支付单组合
-✅ 保持订单金额验证不变
-✅ 配置为0时不进行校验
-
-**优势**:
-- 更灵活的支付方式
-- 更好的用户体验
-- 更清晰的错误提示
-- 更符合实际业务需求
-
-**下一步**:
-1. 测试各种场景
-2. 更新前端提示文字
-3. 编写单元测试
-4. 更新用户文档
diff --git a/PRESCRIPTION_ORDER_WITHDRAW_RESET_AUDIT.md b/PRESCRIPTION_ORDER_WITHDRAW_RESET_AUDIT.md
deleted file mode 100644
index c8e59653..00000000
--- a/PRESCRIPTION_ORDER_WITHDRAW_RESET_AUDIT.md
+++ /dev/null
@@ -1,387 +0,0 @@
-# 订单撤回后重置处方审核状态
-
-## 需求说明
-
-当用户在订单列表中点击"撤回"按钮后,除了将订单状态改为"已取消",还需要将关联的处方审核状态重置为"待审核",以便重新审核。
-
-## 业务场景
-
-### 场景描述
-
-1. 医生创建处方
-2. 处方审核通过(`audit_status = 1`)
-3. 创建业务订单(`fulfillment_status = 1` 待双审通过)
-4. 用户点击"撤回"订单
-5. **期望**:处方审核状态重置为"待审核"(`audit_status = 0`)
-
-### 为什么需要重置?
-
-1. **重新审核**:撤回订单可能是因为处方有问题,需要重新审核
-2. **状态一致性**:订单撤回后,处方应该回到初始状态
-3. **业务流程**:允许医生修改处方后重新提交审核
-
-## 数据库表结构
-
-### 处方表(zyt_tcm_prescription)
-
-```sql
-`audit_status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '审核:0待审核 1已通过 2已驳回(同时作废)'
-`audit_time` int(10) unsigned DEFAULT NULL COMMENT '审核时间'
-`audit_by` int(11) unsigned DEFAULT NULL COMMENT '审核人ID'
-`audit_remark` varchar(500) NOT NULL DEFAULT '' COMMENT '审核意见'
-```
-
-**审核状态**:
-- 0 = 待审核
-- 1 = 已通过
-- 2 = 已驳回(同时作废)
-
-### 业务订单表(zyt_tcm_prescription_order)
-
-```sql
-`prescription_id` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '消费者处方ID'
-`fulfillment_status` tinyint(2) unsigned NOT NULL DEFAULT 1 COMMENT '1待双审通过 2履约中 3已完成 4已取消'
-```
-
-**履约状态**:
-- 1 = 待双审通过
-- 2 = 履约中
-- 3 = 已完成
-- 4 = 已取消(撤回)
-
-## 代码修改
-
-**文件**:`server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
-
-**方法**:`withdraw()`
-
-### 修改内容
-
-```php
-public static function withdraw(int $id, int $adminId, array $adminInfo)
-{
- self::$error = '';
- $order = PrescriptionOrder::where('id', $id)->whereNull('delete_time')->find();
- if (!$order) {
- self::$error = '订单不存在';
- return false;
- }
- if (!self::canWithdrawOrder($order, $adminId, $adminInfo)) {
- self::$error = '无权限撤回';
- return false;
- }
- if ((int) $order->fulfillment_status !== 1) {
- self::$error = '仅「待双审通过」状态可撤回;双审通过后请走履约/完成流程';
- return false;
- }
-
- $order->fulfillment_status = 4;
- self::clearPayOrderLinks($order);
-
- try {
- $order->save();
-
- // 撤回订单后,将关联的处方审核状态改回"待审核"
- $prescriptionId = (int) $order->prescription_id;
- if ($prescriptionId > 0) {
- Prescription::where('id', $prescriptionId)
- ->whereNull('delete_time')
- ->update([
- 'audit_status' => 0, // 0=待审核
- 'audit_time' => null,
- 'audit_by' => null,
- 'audit_remark' => '',
- 'update_time' => time(),
- ]);
- }
- } catch (\Throwable $e) {
- self::$error = $e->getMessage();
- return false;
- }
-
- $out = $order->toArray();
- self::maskInternalCostIfNeeded($out, $adminInfo);
- self::attachLinkedPayOrders($out);
-
- return $out;
-}
-```
-
-### 修改说明
-
-1. **保存订单状态**:`$order->save()`
-2. **获取处方ID**:`$prescriptionId = (int) $order->prescription_id`
-3. **重置处方审核状态**:
- - `audit_status = 0`(待审核)
- - `audit_time = null`(清空审核时间)
- - `audit_by = null`(清空审核人)
- - `audit_remark = ''`(清空审核意见)
- - `update_time = time()`(更新时间)
-
-## 工作流程
-
-### 完整流程
-
-```
-用户点击"撤回"按钮
- ↓
-调用 withdraw() 方法
- ↓
-验证权限和状态
- ↓
-更新订单状态为"已取消"(4)
- ↓
-清除支付单关联
- ↓
-保存订单
- ↓
-获取关联的处方ID
- ↓
-重置处方审核状态为"待审核"(0)
- ↓
-清空审核时间、审核人、审核意见
- ↓
-返回成功
-```
-
-### 前端显示
-
-**订单列表页面**:
-- 订单状态:已取消
-- 撤回按钮:消失(只有"待双审通过"状态才显示)
-
-**处方列表页面**:
-- 审核状态:待审核
-- 可以重新审核
-
-## 测试场景
-
-### 场景1:正常撤回
-
-**初始状态**:
-- 处方审核状态:已通过(1)
-- 订单履约状态:待双审通过(1)
-
-**操作**:点击"撤回"按钮
-
-**预期结果**:
-- 订单履约状态:已取消(4)
-- 处方审核状态:待审核(0)
-- 审核时间:null
-- 审核人:null
-- 审核意见:空
-
----
-
-### 场景2:撤回后重新审核
-
-**步骤**:
-1. 撤回订单(处方状态变为"待审核")
-2. 医生修改处方
-3. 重新提交审核
-4. 审核通过
-5. 创建新订单
-
-**预期结果**:流程正常,可以重新审核和创建订单
-
----
-
-### 场景3:无权限撤回
-
-**初始状态**:
-- 当前用户:非创建人、非医助
-
-**操作**:点击"撤回"按钮
-
-**预期结果**:
-- 错误提示:"无权限撤回"
-- 处方状态不变
-
----
-
-### 场景4:非待双审状态撤回
-
-**初始状态**:
-- 订单履约状态:履约中(2)或已完成(3)
-
-**操作**:点击"撤回"按钮
-
-**预期结果**:
-- 错误提示:"仅「待双审通过」状态可撤回;双审通过后请走履约/完成流程"
-- 处方状态不变
-
----
-
-### 场景5:处方不存在
-
-**初始状态**:
-- 订单关联的处方已被删除
-
-**操作**:点击"撤回"按钮
-
-**预期结果**:
-- 订单状态:已取消(4)
-- 不会报错(因为有 `if ($prescriptionId > 0)` 判断)
-
-## 边界情况处理
-
-### 1. 处方ID为0或null
-
-```php
-if ($prescriptionId > 0) {
- // 只有处方ID有效时才更新
-}
-```
-
-**处理**:跳过处方状态更新,不报错
-
-### 2. 处方已被删除
-
-```php
-->whereNull('delete_time')
-```
-
-**处理**:不会更新已删除的处方
-
-### 3. 数据库更新失败
-
-```php
-try {
- $order->save();
- // 更新处方状态
-} catch (\Throwable $e) {
- self::$error = $e->getMessage();
- return false;
-}
-```
-
-**处理**:捕获异常,返回错误信息
-
-### 4. 事务一致性
-
-**问题**:订单保存成功,但处方更新失败怎么办?
-
-**当前实现**:
-- 订单和处方更新在同一个 try-catch 块中
-- 如果处方更新失败,会抛出异常
-- 异常会导致整个方法返回 false
-
-**建议优化**:使用数据库事务
-
-```php
-Db::startTrans();
-try {
- $order->save();
-
- // 更新处方状态
- Prescription::where('id', $prescriptionId)
- ->whereNull('delete_time')
- ->update([...]);
-
- Db::commit();
-} catch (\Throwable $e) {
- Db::rollback();
- self::$error = $e->getMessage();
- return false;
-}
-```
-
-## 相关影响
-
-### 1. 处方列表页面
-
-**文件**:`admin/src/views/consumer/prescription/index.vue`
-
-**影响**:
-- 撤回后,处方审核状态显示为"待审核"
-- 可以重新点击"审核"按钮
-
-### 2. 订单列表页面
-
-**文件**:`admin/src/views/consumer/prescription/order_list.vue`
-
-**影响**:
-- 撤回后,订单状态显示为"已取消"
-- "撤回"按钮消失(只有"待双审通过"状态才显示)
-
-### 3. 业务流程
-
-**影响**:
-- 允许撤回后重新审核处方
-- 允许重新创建订单
-- 支付单关联被清除,可以重新关联
-
-## 注意事项
-
-### 1. 权限控制
-
-只有以下用户可以撤回订单:
-- 订单创建人
-- 诊单医助
-- 主管角色
-
-### 2. 状态限制
-
-只有"待双审通过"状态的订单可以撤回:
-- 待双审通过(1):✅ 可以撤回
-- 履约中(2):❌ 不可撤回
-- 已完成(3):❌ 不可撤回
-- 已取消(4):❌ 不可撤回
-
-### 3. 数据一致性
-
-撤回操作会:
-- 更新订单状态
-- 清除支付单关联
-- 重置处方审核状态
-
-建议使用数据库事务确保一致性。
-
-### 4. 审核历史
-
-当前实现会清空审核历史(审核时间、审核人、审核意见)。
-
-如果需要保留审核历史,可以考虑:
-- 不清空这些字段
-- 或者创建审核历史表记录
-
-## 相关文件
-
-### 后端文件
-- `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php` - 订单逻辑
-- `server/app/common/model/tcm/Prescription.php` - 处方模型
-- `server/app/common/model/tcm/PrescriptionOrder.php` - 订单模型
-
-### 前端文件
-- `admin/src/views/consumer/prescription/order_list.vue` - 订单列表
-- `admin/src/views/consumer/prescription/index.vue` - 处方列表
-
-### 数据库文件
-- `server/database/migrations/2026_03_30_prescription_visible_roles_audit.sql` - 处方审核字段
-- `server/database/migrations/2026_04_07_create_tcm_prescription_order.sql` - 订单表
-
-## 总结
-
-✅ 撤回订单时重置处方审核状态为"待审核"
-✅ 清空审核时间、审核人、审核意见
-✅ 更新处方的 update_time
-✅ 保持订单状态为"已取消"
-✅ 清除支付单关联
-✅ 异常处理和错误提示
-
-**优势**:
-- 支持撤回后重新审核
-- 状态一致性
-- 业务流程完整
-- 用户体验友好
-
-**建议优化**:
-- 使用数据库事务确保一致性
-- 考虑保留审核历史记录
-- 添加操作日志
-
-**下一步**:
-1. 测试撤回功能
-2. 验证处方状态变化
-3. 测试重新审核流程
-4. 考虑添加事务支持
diff --git a/PRESCRIPTION_TEST_GUIDE.md b/PRESCRIPTION_TEST_GUIDE.md
deleted file mode 100644
index 0e0fdea3..00000000
--- a/PRESCRIPTION_TEST_GUIDE.md
+++ /dev/null
@@ -1,255 +0,0 @@
-# 处方管理功能测试指南
-
-## 测试前准备
-
-### 1. 安装数据库字段
-运行安装脚本添加新字段:
-```bash
-# Windows
-install_prescription_shared.bat
-
-# Linux/Mac
-./install_prescription_shared.sh
-```
-
-### 2. 确认路由配置
-确保后端路由已配置处方管理相关接口。
-
-## 测试用例
-
-### 测试用例1:新增处方(不共享)
-
-**步骤:**
-1. 登录管理后台
-2. 进入"消费者管理" -> "处方管理"
-3. 点击"新增处方"按钮
-4. 填写表单:
- - 处方名称:感冒清热方
- - 患者姓名:张三
- - 性别:男
- - 年龄:35
- - 处方日期:选择今天
- - 临床诊断:风寒感冒
- - 点击"添加药材",添加以下药材:
- - 麻黄 9克
- - 桂枝 6克
- - 杏仁 9克
- - 甘草 3克
- - 剂数:7
- - 剂量单位:剂
- - 服用天数:7
- - 用法:水煎服,一日二次
- - 服用说明:饭前30分钟服用,温水送服;忌食辛辣、生冷食物
- - 医师姓名:李医生
- - 是否共享:不勾选
-5. 点击"确定"
-
-**预期结果:**
-- 提示"添加成功"
-- 列表中显示新增的处方
-- "是否共享"列显示"仅自己可见"
-
-### 测试用例2:新增处方(共享)
-
-**步骤:**
-1. 点击"新增处方"
-2. 填写表单(同测试用例1)
-3. 勾选"是否共享"开关
-4. 点击"确定"
-
-**预期结果:**
-- 提示"添加成功"
-- "是否共享"列显示"所有人可见"
-
-### 测试用例3:查看处方
-
-**步骤:**
-1. 在列表中点击某个处方的"查看"按钮
-2. 查看处方详情
-
-**预期结果:**
-- 弹出详情对话框
-- 显示所有处方信息
-- 所有字段为只读状态
-- 没有"确定"按钮
-
-### 测试用例4:编辑处方
-
-**步骤:**
-1. 在列表中点击某个处方的"编辑"按钮
-2. 修改处方名称为"感冒清热方(加强版)"
-3. 添加一味药材:生姜 6克
-4. 点击"确定"
-
-**预期结果:**
-- 提示"编辑成功"
-- 列表中显示更新后的处方名称
-- 药材数量增加1
-
-### 测试用例5:删除处方
-
-**步骤:**
-1. 在列表中点击某个处方的"删除"按钮
-2. 确认删除
-
-**预期结果:**
-- 提示"删除成功"
-- 该处方从列表中消失
-
-### 测试用例6:搜索功能
-
-**步骤:**
-1. 在"处方名称"输入框输入"感冒"
-2. 点击"查询"
-
-**预期结果:**
-- 只显示处方名称包含"感冒"的记录
-
-**步骤:**
-1. 在"是否共享"下拉框选择"所有人可见"
-2. 点击"查询"
-
-**预期结果:**
-- 只显示共享的处方
-
-**步骤:**
-1. 点击"重置"按钮
-
-**预期结果:**
-- 清空所有搜索条件
-- 显示所有处方
-
-### 测试用例7:表单验证
-
-**步骤:**
-1. 点击"新增处方"
-2. 不填写任何信息,直接点击"确定"
-
-**预期结果:**
-- 显示必填项验证错误提示
-
-**步骤:**
-1. 填写基本信息,但不添加药材
-2. 点击"确定"
-
-**预期结果:**
-- 提示"请至少添加一味药材"
-
-**步骤:**
-1. 添加药材,但不填写药材名称
-2. 点击"确定"
-
-**预期结果:**
-- 提示"第X味药材名称不能为空"
-
-**步骤:**
-1. 填写药材名称,但剂量为0
-2. 点击"确定"
-
-**预期结果:**
-- 提示"第X味药材剂量必须大于0"
-
-### 测试用例8:权限测试(多用户)
-
-**步骤:**
-1. 用户A创建一个不共享的处方
-2. 用户B登录系统
-3. 查看处方列表
-
-**预期结果:**
-- 用户B看不到用户A创建的不共享处方
-
-**步骤:**
-1. 用户A创建一个共享的处方
-2. 用户B登录系统
-3. 查看处方列表
-
-**预期结果:**
-- 用户B可以看到用户A创建的共享处方
-- 用户B可以编辑该处方
-
-### 测试用例9:分页功能
-
-**步骤:**
-1. 创建超过10条处方记录
-2. 查看列表底部的分页器
-
-**预期结果:**
-- 显示分页器
-- 可以切换页码
-- 每页显示正确数量的记录
-
-## 常见问题排查
-
-### 问题1:点击"新增处方"没有反应
-**排查:**
-- 检查浏览器控制台是否有错误
-- 检查网络请求是否正常
-
-### 问题2:提交表单后提示"处方名称不能为空"
-**排查:**
-- 确认数据库字段已添加
-- 检查后端验证规则
-
-### 问题3:列表显示为空
-**排查:**
-- 检查API接口是否正常
-- 检查数据库表是否有数据
-- 检查权限过滤逻辑
-
-### 问题4:共享功能不生效
-**排查:**
-- 确认is_shared字段已添加到数据库
-- 检查后端列表查询的权限逻辑
-- 确认用户ID获取正确
-
-## 性能测试
-
-### 测试场景1:大量数据加载
-1. 创建100条处方记录
-2. 测试列表加载速度
-3. 测试搜索响应速度
-
-### 测试场景2:复杂处方
-1. 创建包含50味药材的处方
-2. 测试保存和加载速度
-
-## 兼容性测试
-
-### 浏览器兼容性
-- Chrome(推荐)
-- Firefox
-- Edge
-- Safari
-
-### 分辨率测试
-- 1920x1080
-- 1366x768
-- 1280x720
-
-## 测试报告模板
-
-```
-测试日期:____年__月__日
-测试人员:________
-测试环境:________
-
-| 测试用例 | 测试结果 | 备注 |
-|---------|---------|------|
-| 新增处方(不共享) | ☐ 通过 ☐ 失败 | |
-| 新增处方(共享) | ☐ 通过 ☐ 失败 | |
-| 查看处方 | ☐ 通过 ☐ 失败 | |
-| 编辑处方 | ☐ 通过 ☐ 失败 | |
-| 删除处方 | ☐ 通过 ☐ 失败 | |
-| 搜索功能 | ☐ 通过 ☐ 失败 | |
-| 表单验证 | ☐ 通过 ☐ 失败 | |
-| 权限测试 | ☐ 通过 ☐ 失败 | |
-| 分页功能 | ☐ 通过 ☐ 失败 | |
-
-总体评价:☐ 通过 ☐ 需要修复
-
-问题列表:
-1.
-2.
-3.
-```
diff --git a/PRESCRIPTION_TIMES_PER_DAY_FEATURE.md b/PRESCRIPTION_TIMES_PER_DAY_FEATURE.md
deleted file mode 100644
index 3af9b8b3..00000000
--- a/PRESCRIPTION_TIMES_PER_DAY_FEATURE.md
+++ /dev/null
@@ -1,199 +0,0 @@
-# 处方"每天几次"功能添加说明
-
-## 功能描述
-
-在中医处方单中添加"每天几次"字段,用于记录患者每天需要服用药物的次数。
-
-## 修改内容
-
-### 1. 表单界面(admin/src/components/tcm-prescription/index.vue)
-
-#### 添加表单字段(第 139-145 行)
-
-在"服用天数"字段后面添加"每天几次"输入框:
-
-```vue
-
-
-
-
-
-
-
-
-
-
-```
-
-**字段说明:**
-- 字段名:`times_per_day`
-- 类型:数字输入框
-- 最小值:1
-- 最大值:10
-- 默认值:2
-
-### 2. TypeScript 类型定义(第 580-615 行)
-
-在 `PrescriptionForm` 接口中添加字段:
-
-```typescript
-interface PrescriptionForm {
- // ... 其他字段
- usage_days: number
- times_per_day: number // 新增
- usage_instruction: string
- // ... 其他字段
-}
-```
-
-### 3. 数据模型初始化(第 650-680 行)
-
-在 `formData` 中添加默认值:
-
-```typescript
-const formData = reactive({
- // ... 其他字段
- usage_days: 7,
- times_per_day: 2, // 新增,默认每天2次
- usage_instruction: '水煎服,一日二次',
- // ... 其他字段
-})
-```
-
-### 4. 处方预览显示(第 280-285 行)
-
-在处方打印预览中显示"每天几次"信息:
-
-```vue
-
-```
-
-**显示效果:**
-- 如果设置了 `times_per_day`:显示"服用7天,每天2次, 浓缩水丸 水煎服,一日二次"
-- 如果未设置:显示"服用7天, 浓缩水丸 水煎服,一日二次"
-
-## 后端数据库支持
-
-### 需要添加的数据库字段
-
-如果后端数据库表中还没有 `times_per_day` 字段,需要执行以下 SQL:
-
-```sql
--- 在处方表中添加 times_per_day 字段
-ALTER TABLE `zyt_prescription`
-ADD COLUMN `times_per_day` INT(11) DEFAULT 2 COMMENT '每天服用次数' AFTER `usage_days`;
-
--- 如果需要更新现有数据的默认值
-UPDATE `zyt_prescription` SET `times_per_day` = 2 WHERE `times_per_day` IS NULL;
-```
-
-### 后端 API 修改
-
-确保后端接口支持 `times_per_day` 字段:
-
-1. **创建/更新处方接口**:接收 `times_per_day` 参数
-2. **查询处方接口**:返回 `times_per_day` 字段
-3. **验证规则**:`times_per_day` 应该在 1-10 之间
-
-## 使用说明
-
-### 医生操作流程
-
-1. 打开处方单编辑界面
-2. 填写患者信息和诊断信息
-3. 添加中药处方
-4. 在"服用天数"字段输入天数(如:7)
-5. 在"每天几次"字段输入次数(如:2)
-6. 系统会在处方预览中显示:"服用7天,每天2次"
-
-### 常见用法
-
-- **每天2次**:早晚各一次(最常见)
-- **每天3次**:早中晚各一次
-- **每天1次**:每日一次
-- **每天4次**:每6小时一次
-
-## 测试要点
-
-### 前端测试
-
-1. ✅ 输入框显示正常
-2. ✅ 最小值限制(不能小于1)
-3. ✅ 最大值限制(不能大于10)
-4. ✅ 默认值为2
-5. ✅ 预览显示正确
-6. ✅ 保存后数据正确
-
-### 后端测试
-
-1. ✅ 创建处方时保存 `times_per_day`
-2. ✅ 更新处方时更新 `times_per_day`
-3. ✅ 查询处方时返回 `times_per_day`
-4. ✅ 数据验证(1-10范围)
-
-### 集成测试
-
-1. ✅ 创建处方 → 保存 → 查看预览 → 确认显示正确
-2. ✅ 编辑处方 → 修改次数 → 保存 → 确认更新成功
-3. ✅ 打印处方 → 确认打印内容包含"每天X次"
-
-## 界面效果
-
-### 编辑界面
-
-```
-┌─────────────────────────────────────────────────┐
-│ 处方类型: [浓缩水丸 ▼] │
-├─────────────────────────────────────────────────┤
-│ 服用天数: [ 7 ] 每天几次: [ 2 ] │
-├─────────────────────────────────────────────────┤
-│ 用法: 水煎服,一日二次 │
-└─────────────────────────────────────────────────┘
-```
-
-### 预览界面
-
-```
-服用7天,每天2次, 浓缩水丸 水煎服,一日二次
-服用时间: 饭前 服用方式: 温水送服
-```
-
-## 注意事项
-
-1. **默认值**:新建处方时默认为每天2次(最常见的用法)
-2. **范围限制**:限制在1-10次之间,超出范围的输入会被自动调整
-3. **可选字段**:如果不填写,预览中不会显示"每天X次"
-4. **与用法说明的关系**:`times_per_day` 是结构化数据,`usage_instruction` 是文字描述,两者可以互补
-
-## 兼容性
-
-- ✅ 兼容现有处方数据(旧数据没有此字段时不显示)
-- ✅ 不影响现有功能
-- ✅ 向后兼容
-
-## 后续优化建议
-
-1. **智能提示**:根据处方类型自动推荐合适的服用次数
-2. **关联验证**:与"用法"字段进行一致性检查
-3. **统计分析**:统计不同处方类型的常用服用次数
-4. **模板支持**:在处方模板中包含 `times_per_day` 字段
-
-## 完成状态
-
-- ✅ 前端界面添加完成
-- ✅ 数据模型更新完成
-- ✅ 类型定义更新完成
-- ✅ 预览显示更新完成
-- ⏳ 等待后端数据库字段添加
-- ⏳ 等待后端 API 支持
-
-## 相关文件
-
-- `admin/src/components/tcm-prescription/index.vue` - 处方组件主文件
-- 后端需要修改的文件(待确认):
- - 处方模型文件
- - 处方控制器文件
- - 数据库迁移文件
diff --git a/PRESCRIPTION_VIEW_PERMISSION_FIX.md b/PRESCRIPTION_VIEW_PERMISSION_FIX.md
deleted file mode 100644
index 8d978164..00000000
--- a/PRESCRIPTION_VIEW_PERMISSION_FIX.md
+++ /dev/null
@@ -1,129 +0,0 @@
-# 业务订单中处方查看权限修复
-
-## 问题描述
-
-药师角色(角色ID=6)在查看业务订单详情时,提示"无权限查看此处方"。
-
-虽然药师有业务订单管理权限(`order_edit_all_roles` 包含角色6),但在业务订单详情中需要显示关联的处方信息时,`PrescriptionLogic::canViewPrescription()` 方法会拒绝访问。
-
-## 问题原因
-
-`canViewPrescription` 方法的权限检查层级为:
-1. 超级管理员
-2. 处方创建人
-3. 医助
-4. 共享处方
-5. 可见角色
-6. 有开方权限的医生(`tcm.diagnosis/kaifang`)
-
-药师角色通常没有 `tcm.diagnosis/kaifang` 权限,因此无法通过权限检查。
-
-## 解决方案
-
-在 `canViewPrescription` 方法中添加新的权限检查层级:
-
-**有业务订单管理权限的角色(如药师)可以查看处方(只读)**
-
-这样药师可以在业务订单中查看关联的处方信息,以便进行订单审核和管理。
-
-## 代码修改
-
-文件:`server/app/adminapi/logic/tcm/PrescriptionLogic.php`
-
-```php
-public static function canViewPrescription($row, int $adminId, array $adminInfo): bool
-{
- // 超级管理员
- if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) {
- return true;
- }
-
- // 创建人
- $creatorId = is_array($row) ? (int) ($row['creator_id'] ?? 0) : (int) $row->creator_id;
- if ($creatorId === $adminId) {
- return true;
- }
-
- // 医助
- $assistantId = is_array($row) ? (int) ($row['assistant_id'] ?? 0) : (int) ($row->assistant_id ?? 0);
- if ($assistantId > 0 && $assistantId === $adminId) {
- return true;
- }
-
- // 共享处方
- $isShared = is_array($row) ? (int) ($row['is_shared'] ?? 0) : (int) $row->is_shared;
- if ($isShared === 1) {
- return true;
- }
-
- // 可见角色
- $vis = is_array($row) ? (string) ($row['visible_role_ids'] ?? '') : (string) ($row->visible_role_ids ?? '');
- $allowed = array_filter(array_map('intval', explode(',', $vis)));
- $myRoles = array_map('intval', $adminInfo['role_id'] ?? []);
- if (count(array_intersect($myRoles, $allowed)) > 0) {
- return true;
- }
-
- // 有开方权限的医生可以查看所有处方(只读)
- $permissions = $adminInfo['permissions'] ?? [];
- if (in_array('tcm.diagnosis/kaifang', $permissions, true)) {
- return true;
- }
-
- // 【新增】有业务订单管理权限的角色(如药师)可以查看处方(只读)
- $orderEditAllRoles = Config::get('project.order_edit_all_roles', [0, 3]);
- $myRoles = array_map('intval', $adminInfo['role_id'] ?? []);
- $allowedRoles = array_map('intval', is_array($orderEditAllRoles) ? $orderEditAllRoles : []);
- if (count(array_intersect($myRoles, $allowedRoles)) > 0) {
- return true;
- }
-
- return false;
-}
-```
-
-## 权限层级(更新后)
-
-1. 超级管理员(root=1)
-2. 处方创建人
-3. 医助
-4. 共享处方(is_shared=1)
-5. 可见角色(visible_role_ids)
-6. 有开方权限的医生(`tcm.diagnosis/kaifang`)
-7. **有业务订单管理权限的角色(`order_edit_all_roles`)** ← 新增
-
-## 配置文件
-
-文件:`server/config/project.php`
-
-```php
-// 医助创建订单权限:拥有以下角色ID的用户可修改任意订单,其他用户只能修改自己创建的订单
-'order_edit_all_roles' => [0, 3, 6],
-```
-
-- 角色 0:超级管理员
-- 角色 3:管理员
-- 角色 6:药师
-
-## 使用场景
-
-药师角色现在可以:
-1. 查看业务订单列表
-2. 打开业务订单详情
-3. 查看订单中关联的处方信息(只读)
-4. 进行处方审核
-5. 进行支付单审核
-6. 编辑业务订单信息
-
-## 测试验证
-
-1. 使用药师账号登录
-2. 进入"消费者处方订单"列表
-3. 点击任意订单的"详情"按钮
-4. 确认可以正常查看处方信息,不再提示"无权限查看此处方"
-
-## 注意事项
-
-1. 此权限为只读权限,药师不能编辑处方内容
-2. 药师只能通过业务订单查看处方,不能直接在处方列表中查看其他医生的处方
-3. 修改代码后需要清除缓存:`php think clear`
diff --git a/QUICK_FIX_REFERENCE.md b/QUICK_FIX_REFERENCE.md
deleted file mode 100644
index b84371d3..00000000
--- a/QUICK_FIX_REFERENCE.md
+++ /dev/null
@@ -1,83 +0,0 @@
-# 本地录制修复 - 快速参考
-
-## 🔍 问题
-本地录制总是失败,blob 为空
-
-## 🎯 根本原因
-录制启动太晚(1200ms),在对方挂断后才启动,此时 SDK 已销毁视频轨道
-
-## ✅ 解决方案
-
-### 1. 缩短启动延迟
-```
-1200ms → 500ms
-```
-
-### 2. 快速重试
-```
-300ms 间隔,最多 10 次
-```
-
-### 3. 立即停止录制
-```
-监听 USER_LEAVE 事件,立即调用 beginStopNow()
-```
-
-### 4. 缩短停止延迟
-```
-300ms → 100ms
-```
-
-## 📊 测试检查清单
-
-### 启动阶段(接通后 1 秒内)
-- [ ] 看到 "尝试启动本地录制"
-- [ ] 看到 "视频元素已就绪"
-- [ ] 看到 "✅ 本地录制已成功启动"
-
-### 录制阶段(每 250ms)
-- [ ] 看到 "收到数据块,大小: XXX"
-- [ ] 数据块数持续增加
-
-### 停止阶段(挂断时)
-- [ ] 看到 "检测到通话结束事件"
-- [ ] 看到 "已收集数据块: X"(X > 0)
-- [ ] 看到 "生成 Blob: XXX bytes"(不是 null)
-- [ ] 看到 "本地录制已上传并关联到通话记录"
-
-## 🚨 常见问题
-
-### 问题:数据块数为 0
-**原因**:录制启动太晚或视频流无效
-**检查**:
-```javascript
-// 控制台执行
-document.querySelectorAll('video').forEach((v, i) => {
- console.log(`Video ${i}:`, v.readyState, v.srcObject)
-})
-```
-
-### 问题:Blob 为 null
-**原因**:通话时间太短(< 2 秒)
-**解决**:保持通话至少 5 秒
-
-### 问题:录制启动失败
-**原因**:找不到视频元素
-**检查**:是否看到 "未找到视频元素" 日志
-
-## 📝 修改的文件
-1. `admin/src/components/chat-dialog/index.vue`
-2. `admin/src/utils/call-local-recorder.ts`
-
-## 🔗 详细文档
-- `LOCAL_RECORDING_DEBUG_GUIDE.md` - 完整调试指南
-- `RECORDING_FIX_SUMMARY.md` - 修复总结
-- `admin/test-local-recording.html` - 测试工具
-
-## 💡 测试建议
-1. 清除浏览器缓存
-2. 打开控制台(F12)
-3. 发起视频通话
-4. 保持通话 5 秒以上
-5. 正常挂断
-6. 检查日志和录制结果
diff --git a/QUICK_START_FIRST_LOGIN.md b/QUICK_START_FIRST_LOGIN.md
deleted file mode 100644
index bbe991a5..00000000
--- a/QUICK_START_FIRST_LOGIN.md
+++ /dev/null
@@ -1,80 +0,0 @@
-# 首次登录修改密码 - 快速开始
-
-## 一分钟快速安装
-
-### Windows 用户
-
-1. 双击运行 `install_first_login_password.bat`
-2. 按提示输入数据库信息
-3. 完成!
-
-### Linux/Mac 用户
-
-```bash
-chmod +x install_first_login_password.sh
-./install_first_login_password.sh
-```
-
-## 快速测试
-
-### 1. 设置测试账号
-
-```sql
--- 设置管理员 ID 为 1 的账号需要修改密码
-UPDATE la_admin SET is_paw = 0 WHERE id = 1;
-```
-
-### 2. 登录测试
-
-1. 使用该账号登录系统
-2. 登录后会自动跳转到修改密码页面
-3. 输入新密码(至少 6 位)
-4. 点击"确认修改"
-5. 系统提示"密码修改成功,请重新登录"
-6. 使用新密码重新登录
-
-### 3. 验证成功
-
-```sql
--- 查看 is_paw 字段,应该已经变成 1
-SELECT id, account, is_paw FROM la_admin WHERE id = 1;
-```
-
-## 常用操作
-
-### 强制单个管理员修改密码
-
-```sql
-UPDATE la_admin SET is_paw = 0 WHERE id = <管理员ID>;
-```
-
-### 强制所有管理员修改密码
-
-```sql
-UPDATE la_admin SET is_paw = 0 WHERE delete_time = 0;
-```
-
-### 恢复正常状态
-
-```sql
-UPDATE la_admin SET is_paw = 1 WHERE id = <管理员ID>;
-```
-
-## 功能说明
-
-- `is_paw = 0`:下次登录时强制修改密码
-- `is_paw = 1`:正常状态,无需修改密码
-
-## 文件说明
-
-| 文件 | 说明 |
-|------|------|
-| `add_is_paw_field.sql` | 数据库字段添加 SQL |
-| `install_first_login_password.sh` | Linux/Mac 安装脚本 |
-| `install_first_login_password.bat` | Windows 安装脚本 |
-| `test_first_login.sql` | 测试用 SQL 语句 |
-| `FIRST_LOGIN_PASSWORD_CHANGE.md` | 完整功能文档 |
-
-## 需要帮助?
-
-查看完整文档:`FIRST_LOGIN_PASSWORD_CHANGE.md`
diff --git a/QUICK_START_PRESCRIPTION_LIBRARY.md b/QUICK_START_PRESCRIPTION_LIBRARY.md
deleted file mode 100644
index 49d9f8f7..00000000
--- a/QUICK_START_PRESCRIPTION_LIBRARY.md
+++ /dev/null
@@ -1,135 +0,0 @@
-# 处方库功能 - 快速开始指南
-
-## 🚀 5分钟快速上手
-
-### 第一步:安装数据库表
-
-#### Windows 用户
-双击运行 `install_prescription_library.bat`
-
-#### Linux/Mac 用户
-```bash
-chmod +x install_prescription_library.sh
-./install_prescription_library.sh
-```
-
-#### 手动安装
-使用数据库工具执行:`server/database/migrations/create_prescription_library.sql`
-
-### 第二步:访问功能
-
-1. 登录后台管理系统
-2. 在菜单中找到"处方库"功能
-3. 开始使用!
-
-## 📝 快速创建第一个处方
-
-### 示例:创建"六味地黄丸"处方
-
-1. 点击"新增处方"按钮
-
-2. 填写处方名称:
- ```
- 六味地黄丸
- ```
-
-3. 添加药材(点击6次"添加药材"按钮):
- ```
- 熟地黄 24克
- 山茱萸 12克
- 山药 12克
- 泽泻 9克
- 牡丹皮 9克
- 茯苓 9克
- ```
-
-4. 选择是否公开:
- - 仅自己可见:只有你能看到
- - 所有人可见:所有医生都能看到
-
-5. 点击"确定"保存
-
-✅ 完成!你的第一个处方已创建成功!
-
-## 🎯 常用操作
-
-### 查看处方
-- 在列表中点击"查看"按钮
-- 以只读模式查看处方详情
-
-### 编辑处方
-- 在列表中点击"编辑"按钮
-- 修改处方信息后保存
-
-### 删除处方
-- 在列表中点击"删除"按钮
-- 确认删除(只能删除自己创建的处方)
-
-### 搜索处方
-- 在搜索框输入处方名称
-- 选择是否公开筛选条件
-- 点击"查询"按钮
-
-## 💡 使用技巧
-
-### 技巧1:快速复制处方
-1. 查看已有处方
-2. 点击"编辑"
-3. 修改处方名称
-4. 保存为新处方
-
-### 技巧2:分享经典处方
-创建处方时,将"是否公开"设置为"所有人可见",其他医生就能看到并参考。
-
-### 技巧3:管理个人处方库
-使用"是否公开"筛选,快速查看自己的私有处方。
-
-## 📊 测试数据
-
-想快速测试功能?执行以下SQL插入测试数据:
-
-```sql
--- 插入3个经典处方
-INSERT INTO `zyt_prescription_library`
-(`prescription_name`, `herbs`, `is_public`, `creator_id`, `creator_name`, `create_time`, `update_time`)
-VALUES
-('六味地黄丸', '[{"name":"熟地黄","dosage":24},{"name":"山茱萸","dosage":12},{"name":"山药","dosage":12},{"name":"泽泻","dosage":9},{"name":"牡丹皮","dosage":9},{"name":"茯苓","dosage":9}]', 1, 1, '测试医生', UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
-('补中益气汤', '[{"name":"黄芪","dosage":15},{"name":"党参","dosage":10},{"name":"白术","dosage":10},{"name":"当归","dosage":10},{"name":"陈皮","dosage":6},{"name":"升麻","dosage":6},{"name":"柴胡","dosage":6},{"name":"甘草","dosage":5}]', 1, 1, '测试医生', UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
-('逍遥散', '[{"name":"柴胡","dosage":10},{"name":"当归","dosage":10},{"name":"白芍","dosage":10},{"name":"白术","dosage":10},{"name":"茯苓","dosage":10},{"name":"薄荷","dosage":6},{"name":"生姜","dosage":6},{"name":"甘草","dosage":5}]', 0, 1, '测试医生', UNIX_TIMESTAMP(), UNIX_TIMESTAMP());
-```
-
-## ❓ 常见问题
-
-### Q1: 安装后看不到菜单?
-A: 需要在系统菜单管理中添加处方库菜单项,或联系管理员配置权限。
-
-### Q2: 无法删除处方?
-A: 只有创建者才能删除处方。如果是其他人创建的,即使是公开的也不能删除。
-
-### Q3: 其他人看不到我的处方?
-A: 检查"是否公开"设置,确保设置为"所有人可见"。
-
-### Q4: 药材剂量可以是小数吗?
-A: 可以,支持精确到0.1克,如:3.5克。
-
-### Q5: 可以导入Excel处方吗?
-A: 当前版本暂不支持,需要手动录入。这是未来的扩展方向。
-
-## 📚 更多文档
-
-- **完整说明:** PRESCRIPTION_LIBRARY_README.md
-- **功能演示:** PRESCRIPTION_LIBRARY_DEMO.md
-- **开发总结:** PRESCRIPTION_LIBRARY_SUMMARY.md
-- **测试SQL:** TEST_PRESCRIPTION_LIBRARY.sql
-
-## 🆘 需要帮助?
-
-如遇到问题,请:
-1. 查看完整文档
-2. 检查数据库连接
-3. 查看浏览器控制台错误
-4. 联系技术支持
-
----
-
-**祝使用愉快!** 🎉
diff --git a/QYWX_CUSTOMER_SYNC_FIX.md b/QYWX_CUSTOMER_SYNC_FIX.md
deleted file mode 100644
index 9f48241a..00000000
--- a/QYWX_CUSTOMER_SYNC_FIX.md
+++ /dev/null
@@ -1,164 +0,0 @@
-# 企业微信客户同步问题修复指南
-
-## 问题描述
-
-同步企业微信客户时,能成功获取企业成员列表(207人),但无法获取任何客户数据,所有API调用都返回错误码 `48002: "api forbidden"`。
-
-## 根本原因
-
-企业微信应用未开启"客户联系"API权限。错误码 `48002` 表示API接口被禁用或未授权。
-
-## 解决方案
-
-### 方案一:开启API权限(推荐)
-
-1. 登录企业微信管理后台
- - 访问:https://work.weixin.qq.com/
- - 使用管理员账号登录
-
-2. 进入应用管理
- - 点击"应用管理"
- - 找到您正在使用的应用(使用 `external_pay_secret` 的应用)
-
-3. 开启客户联系权限
- - 在应用详情页面,找到"客户联系"权限设置
- - 开启以下权限:
- * ✓ 客户联系 - 获取客户列表
- * ✓ 客户联系 - 获取客户详情
- * ✓ 客户联系 - 获取客户数据统计(可选)
-
-4. 配置可信IP
- - 在应用设置中找到"企业可信IP"
- - 添加您的服务器IP地址
- - 保存配置
-
-5. 等待生效
- - 权限修改后可能需要几分钟生效
- - 建议等待5-10分钟后再测试
-
-### 方案二:升级企业微信版本
-
-如果您的企业微信版本不支持客户联系API:
-
-1. 检查当前版本是否支持"客户联系"功能
-2. 如需要,升级到支持该功能的版本
-3. 部分功能可能需要付费版本
-
-### 方案三:使用替代方案
-
-如果无法开启API权限:
-
-1. 使用企业微信后台手动导出客户数据
-2. 通过CSV导入到系统
-3. 或使用企业微信的webhook回调功能(需要配置)
-
-## 诊断工具
-
-### 1. 运行权限检查命令
-
-```bash
-php think qywx:check-permissions
-```
-
-这个命令会:
-- 检查应用配置是否正确
-- 测试获取部门成员权限
-- 测试获取客户列表权限
-- 显示详细的错误信息和解决建议
-
-### 2. 查看日志
-
-日志文件位置:`runtime/log/`
-
-关键日志信息:
-```
-企业微信-获取成员客户列表响应 userId=XXX: {"errcode":48002,"errmsg":"api forbidden"...}
-```
-
-如果看到 `errcode: 48002`,说明是权限问题。
-
-## 代码改进
-
-已对代码进行以下改进:
-
-1. **更好的错误处理**
- - `getExternalContactList()` 方法现在会检测 `48002` 错误
- - 权限错误时返回 `false` 而不是空数组
- - 同步逻辑会捕获权限错误并抛出明确的异常
-
-2. **新增诊断方法**
- - `WechatWorkService::checkAppPermissions()` - 检查应用权限
- - `QywxCheckPermissions` 命令 - 完整的权限诊断工具
-
-3. **改进的日志记录**
- - 权限错误会记录更详细的信息
- - 包含解决方案提示
-
-## 测试步骤
-
-1. 确认已开启API权限
-2. 运行诊断命令:
- ```bash
- php think qywx:check-permissions
- ```
-
-3. 如果诊断通过,运行同步:
- ```bash
- php think qywx:sync-customer
- ```
-
-4. 检查同步结果:
- - 查看日志文件
- - 检查数据库表 `la_qywx_external_contact`
- - 在管理后台查看客户列表
-
-## 常见问题
-
-### Q1: 权限已开启,但仍然报错48002
-A:
-- 等待5-10分钟让权限生效
-- 清除企业微信access_token缓存:删除缓存键 `qywx_access_token`
-- 检查服务器IP是否在可信IP列表中
-
-### Q2: 找不到"客户联系"权限选项
-A:
-- 您的企业微信版本可能不支持此功能
-- 需要升级到企业版或专业版
-- 联系企业微信客服确认
-
-### Q3: 部分成员能获取客户,部分不能
-A:
-- 检查成员是否有"客户联系"权限
-- 在企业微信后台为成员分配权限
-- 确认成员确实有添加客户
-
-### Q4: 同步速度很慢
-A:
-- 这是正常的,因为需要逐个成员获取客户列表
-- 207个成员 × 每个成员的客户数 = 大量API调用
-- 建议设置定时任务,每小时或每天同步一次
-
-## 相关文件
-
-- `server/app/adminapi/logic/qywx/CustomerLogic.php` - 同步逻辑
-- `server/app/common/service/wechat/WechatWorkService.php` - API服务
-- `server/app/command/QywxSyncCustomer.php` - 同步命令
-- `server/app/command/QywxCheckPermissions.php` - 诊断命令(新增)
-- `server/config/project.php` - 配置文件
-
-## 联系支持
-
-如果按照以上步骤仍无法解决:
-
-1. 收集以下信息:
- - 诊断命令的完整输出
- - 最近的日志文件
- - 企业微信应用的权限截图
-
-2. 联系企业微信技术支持
- - 官方文档:https://developer.work.weixin.qq.com/
- - 开发者社区:https://developers.weixin.qq.com/community/business
-
----
-
-最后更新:2024-04-11
diff --git a/QYWX_CUSTOMER_SYNC_GUIDE.md b/QYWX_CUSTOMER_SYNC_GUIDE.md
deleted file mode 100644
index 68322fa5..00000000
--- a/QYWX_CUSTOMER_SYNC_GUIDE.md
+++ /dev/null
@@ -1,246 +0,0 @@
-# 企业微信客户同步功能说明
-
-## 功能概述
-
-自动从企业微信同步外部联系人(客户)信息,包括客户基本信息、跟进人等,用于患者信息匹配和自动关联。
-
-同步流程:
-1. 获取企业成员列表(从指定部门开始,递归获取所有子部门成员)
-2. 遍历每个成员,获取其客户列表
-3. 获取客户详情并保存到数据库
-4. 自动去重,避免重复处理同一客户
-
-## 功能特性
-
-1. **手动同步**:点击"立即同步"按钮手动触发同步
-2. **自动同步**:配置定时自动同步,支持多种时间间隔
-3. **客户管理**:查看客户列表、搜索、查看详情
-4. **统计信息**:显示总客户数、今日新增、最后同步时间等
-
-## 安装步骤
-
-### 1. 数据库迁移
-
-执行SQL文件创建数据表:
-
-```bash
-mysql -u用户名 -p密码 数据库名 < server/database/migrations/create_qywx_external_contact.sql
-```
-
-### 2. 配置企业微信
-
-在 `server/.env` 文件中配置企业微信信息:
-
-```env
-# 企业微信配置
-WECHAT_WORK_CORP_ID=你的企业ID
-WECHAT_WORK_EXTERNAL_PAY_SECRET=你的应用Secret
-```
-
-在 `server/config/project.php` 文件中配置同步部门(可选,默认为1即根部门):
-
-```php
-// 企业微信客户同步配置
-// 从哪个部门开始同步(1=根部门,会递归获取所有子部门成员)
-'qywx_sync_department_id' => 1,
-```
-
-获取方式:
-- 企业ID:企业微信管理后台 → 我的企业 → 企业信息 → 企业ID
-- Secret:企业微信管理后台 → 应用管理 → 选择应用 → Secret
-- 部门ID:企业微信管理后台 → 通讯录 → 选择部门 → 查看部门ID(默认根部门ID为1)
-
-### 3. 配置权限
-
-需要在企业微信应用中开启以下权限:
-- 通讯录管理 → 成员信息读取(用于获取企业成员列表)
-- 客户联系 → 客户基础信息
-- 客户联系 → 客户标签
-- 客户联系 → 客户联系人
-
-注意:Secret必须是对应应用的Secret,且该应用需要有上述权限。
-
-### 4. 配置IP白名单(重要!)
-
-企业微信API有IP白名单限制,必须将服务器IP添加到白名单:
-
-1. 获取服务器公网IP:`curl ifconfig.me`
-2. 登录企业微信管理后台 → 应用管理 → 选择应用
-3. 找到"企业可信IP"设置,添加服务器IP
-4. 保存并等待几分钟生效
-
-详细说明请查看:[QYWX_IP_WHITELIST_GUIDE.md](./QYWX_IP_WHITELIST_GUIDE.md)
-
-### 5. 配置定时任务(可选)
-
-如果需要自动同步,配置Linux crontab:
-
-```bash
-# 编辑crontab
-crontab -e
-
-# 添加以下行(每小时执行一次)
-0 * * * * cd /path/to/your/project/server && php think qywx:sync-customer >> /dev/null 2>&1
-```
-
-或者手动强制同步(忽略自动同步设置):
-```bash
-php think qywx:sync-customer --force
-```
-
-## 使用说明
-
-### 前端页面
-
-访问路径:`/fans/qywx`
-
-功能:
-- 查看客户列表
-- 搜索客户(按名称、跟进人)
-- 查看客户详情
-- 手动同步
-- 配置自动同步
-
-### 同步设置
-
-1. 点击"同步设置"按钮
-2. 开启"自动同步"开关
-3. 选择同步间隔(1小时、2小时、4小时、6小时、12小时、24小时)
-4. 点击"保存"
-
-### API接口
-
-#### 1. 获取客户列表
-```
-GET /qywx.customer/lists
-参数:
-- name: 客户名称(可选)
-- follow_user: 跟进人(可选)
-- page: 页码
-- limit: 每页数量
-```
-
-#### 2. 同步客户
-```
-POST /qywx.customer/sync
-返回:
-- sync_count: 同步数量
-- new_count: 新增数量
-- update_count: 更新数量
-```
-
-#### 3. 获取统计信息
-```
-GET /qywx.customer/stats
-返回:
-- total: 总客户数
-- today: 今日新增
-- lastSync: 最后同步时间
-- syncStatus: 同步状态
-```
-
-#### 4. 获取同步设置
-```
-GET /qywx.customer/getSyncSettings
-返回:
-- auto_sync: 是否自动同步
-- interval: 同步间隔(秒)
-- last_sync_time: 最后同步时间
-```
-
-#### 5. 保存同步设置
-```
-POST /qywx.customer/saveSyncSettings
-参数:
-- auto_sync: 是否自动同步(boolean)
-- interval: 同步间隔(秒,3600-86400)
-```
-
-## 数据表结构
-
-### zyt_qywx_external_contact(外部联系人表)
-
-| 字段 | 类型 | 说明 |
-|------|------|------|
-| id | int | 主键ID |
-| external_userid | varchar(64) | 外部联系人ID(唯一) |
-| name | varchar(64) | 客户名称 |
-| avatar | varchar(255) | 头像URL |
-| type | tinyint | 类型:1=微信用户,2=企业微信用户 |
-| gender | tinyint | 性别:0=未知,1=男,2=女 |
-| unionid | varchar(64) | 微信unionid |
-| position | varchar(64) | 职位 |
-| corp_name | varchar(128) | 企业名称 |
-| corp_full_name | varchar(255) | 企业全称 |
-| external_profile | text | 扩展信息JSON |
-| follow_users | text | 跟进人列表JSON |
-| create_time | int | 添加时间 |
-| update_time | int | 更新时间 |
-| delete_time | int | 删除时间 |
-
-### zyt_qywx_sync_settings(同步设置表)
-
-| 字段 | 类型 | 说明 |
-|------|------|------|
-| id | int | 主键ID |
-| setting_key | varchar(64) | 设置键(唯一) |
-| setting_value | text | 设置值JSON |
-| create_time | int | 创建时间 |
-| update_time | int | 更新时间 |
-
-## 注意事项
-
-1. **API调用限制**:企业微信API有调用频率限制,建议同步间隔不要太短
-2. **Secret安全**:请妥善保管企业微信Secret,不要泄露
-3. **权限配置**:确保应用有足够的权限访问客户信息
-4. **错误处理**:同步失败会记录日志,可在系统日志中查看详细错误信息
-5. **数据更新**:同步会更新已存在的客户信息,不会删除已有数据
-
-## 故障排查
-
-### 1. 错误码 60020 - IP白名单限制
-
-错误信息:`not allow to access from your ip`
-
-解决方法:
-- 将服务器IP添加到企业微信应用的"企业可信IP"白名单
-- 详细说明:[QYWX_IP_WHITELIST_GUIDE.md](./QYWX_IP_WHITELIST_GUIDE.md)
-
-### 2. 同步失败
-
-检查:
-- 企业微信配置是否正确(corp_id、secret)
-- 应用权限是否开启
-- IP是否在白名单中(最常见问题)
-- 网络是否正常
-- 查看系统日志:`server/runtime/log/`
-
-### 3. 获取不到客户
-
-检查:
-- 应用是否有客户联系权限和通讯录读取权限
-- 是否有外部联系人
-- Secret是否正确(需要使用对应应用的Secret)
-- 企业成员是否有添加客户
-- 部门ID配置是否正确(默认1为根部门)
-
-### 4. missing field `userid` 错误
-
-这个错误已修复。新版本会先获取企业成员列表,然后遍历每个成员获取其客户。如果仍然出现此错误,请检查:
-- 应用是否有"通讯录管理-成员信息读取"权限
-- 配置的Secret是否正确
-
-### 5. 定时任务不执行
-
-检查:
-- crontab是否配置正确
-- PHP路径是否正确
-- 项目路径是否正确
-- 是否开启了自动同步
-
-## 参考文档
-
-- [企业微信API文档](https://developer.work.weixin.qq.com/document/)
-- [获取部门成员](https://developer.work.weixin.qq.com/document/path/90200)
-- [获取成员客户列表](https://developer.work.weixin.qq.com/document/path/92113)
-- [获取客户详情](https://developer.work.weixin.qq.com/document/path/92114)
diff --git a/QYWX_IP_WHITELIST_GUIDE.md b/QYWX_IP_WHITELIST_GUIDE.md
deleted file mode 100644
index 5527fabf..00000000
--- a/QYWX_IP_WHITELIST_GUIDE.md
+++ /dev/null
@@ -1,88 +0,0 @@
-# 企业微信IP白名单配置指南
-
-## 问题现象
-
-同步企业微信客户时报错:
-```
-errcode: 60020
-errmsg: not allow to access from your ip
-```
-
-## 问题原因
-
-企业微信API有IP白名单限制,只有在白名单中的IP才能调用API。
-
-## 解决方法
-
-### 1. 获取服务器IP地址
-
-在服务器上运行以下命令获取公网IP:
-
-```bash
-curl ifconfig.me
-```
-
-或者查看日志中的IP地址,例如:
-```
-from ip: 8.219.70.152
-```
-
-### 2. 配置企业微信IP白名单
-
-1. 登录企业微信管理后台:https://work.weixin.qq.com/
-2. 进入"应用管理"
-3. 选择你配置的应用(使用该应用的Secret)
-4. 找到"企业可信IP"设置
-5. 点击"配置"或"修改"
-6. 添加服务器的公网IP地址
-7. 保存配置
-
-### 3. 验证配置
-
-配置完成后,等待几分钟让配置生效,然后重新运行同步命令:
-
-```bash
-php think qywx:sync-customer --force
-```
-
-## 注意事项
-
-1. **IP地址格式**:
- - 单个IP:`8.219.70.152`
- - IP段:`8.219.70.0/24`
-
-2. **多个IP**:如果有多个服务器,需要添加所有服务器的IP
-
-3. **动态IP**:如果服务器IP会变化,建议:
- - 使用固定IP的服务器
- - 或配置IP段
- - 或使用代理服务器
-
-4. **测试环境**:开发环境和生产环境可能有不同的IP,都需要添加
-
-## 常见问题
-
-### Q: 添加IP后还是报错?
-A: 等待5-10分钟让配置生效,清除缓存后重试:
-```bash
-php think clear
-php think qywx:sync-customer --force
-```
-
-### Q: 不知道服务器IP?
-A: 在服务器上运行:
-```bash
-curl ifconfig.me
-# 或
-curl ip.sb
-# 或
-curl ipinfo.io/ip
-```
-
-### Q: 本地开发如何测试?
-A: 将本地公网IP也添加到白名单,或使用内网穿透工具(如ngrok)
-
-## 相关文档
-
-- [企业微信API - IP白名单说明](https://developer.work.weixin.qq.com/document/path/90238)
-- [企业微信错误码查询](https://open.work.weixin.qq.com/devtool/query)
diff --git a/QYWX_SYNC_FIX.md b/QYWX_SYNC_FIX.md
deleted file mode 100644
index 0dbdf12e..00000000
--- a/QYWX_SYNC_FIX.md
+++ /dev/null
@@ -1,108 +0,0 @@
-# 企业微信客户同步功能修复
-
-## 问题描述
-
-调用企业微信API获取客户列表时报错:
-```
-missing field `userid`. invalid Request Parameter
-```
-
-## 问题原因
-
-根据企业微信API文档,获取客户列表接口 `/cgi-bin/externalcontact/list` 需要传入 `userid` 参数(企业成员ID)。之前的实现直接调用该接口,没有传入必需的 `userid` 参数。
-
-## 解决方案
-
-修改同步逻辑,采用正确的流程:
-
-1. 先调用 `/cgi-bin/user/list` 获取企业成员列表
-2. 遍历每个成员,调用 `/cgi-bin/externalcontact/list?userid=xxx` 获取该成员的客户列表
-3. 对每个客户调用 `/cgi-bin/externalcontact/get` 获取详情
-4. 保存到数据库,自动去重
-
-## 修改文件
-
-### 1. server/app/common/service/wechat/WechatWorkService.php
-
-新增方法:
-- `getDepartmentUserList()` - 获取部门成员列表
-
-修改方法:
-- `getExternalContactList($userId)` - 添加 `$userId` 参数,获取指定成员的客户列表
-
-### 2. server/app/adminapi/logic/qywx/CustomerLogic.php
-
-修改 `syncCustomers()` 方法:
-- 先获取企业成员列表
-- 遍历成员获取其客户
-- 使用 `$processedCustomers` 数组避免重复处理同一客户
-
-### 3. server/config/project.php
-
-新增配置项:
-```php
-// 企业微信客户同步配置
-// 从哪个部门开始同步(1=根部门,会递归获取所有子部门成员)
-'qywx_sync_department_id' => 1,
-```
-
-### 4. QYWX_CUSTOMER_SYNC_GUIDE.md
-
-更新文档:
-- 添加同步流程说明
-- 添加部门ID配置说明
-- 添加通讯录权限要求
-- 添加 `missing field userid` 错误排查说明
-
-## 权限要求
-
-企业微信应用需要开启以下权限:
-- 通讯录管理 → 成员信息读取(新增)
-- 客户联系 → 客户基础信息
-- 客户联系 → 客户标签
-- 客户联系 → 客户联系人
-
-## 配置说明
-
-### 必需配置(server/.env)
-```env
-WECHAT_WORK_CORP_ID=你的企业ID
-WECHAT_WORK_EXTERNAL_PAY_SECRET=你的应用Secret
-```
-
-### 可选配置(server/config/project.php)
-```php
-'qywx_sync_department_id' => 1, // 默认1(根部门),会递归获取所有子部门成员
-```
-
-## 使用方法
-
-1. 确保配置正确
-2. 清除缓存:`php think clear`
-3. 在前端页面点击"立即同步"按钮
-4. 或运行命令:`php think qywx:sync-customer`
-
-## 测试验证
-
-修复后,同步功能应该能够:
-- 成功获取企业成员列表
-- 遍历每个成员获取其客户
-- 正确保存客户信息到数据库
-- 显示同步统计(总数、新增、更新)
-
-注意:首次使用需要配置IP白名单,否则会报错 60020。
-
-## 常见问题
-
-### 错误码 60020 - IP白名单限制
-
-如果遇到 `not allow to access from your ip` 错误,需要:
-1. 获取服务器公网IP:`curl ifconfig.me`
-2. 在企业微信管理后台添加IP到白名单
-3. 详细说明:[QYWX_IP_WHITELIST_GUIDE.md](./QYWX_IP_WHITELIST_GUIDE.md)
-
-## 参考文档
-
-- [获取部门成员](https://developer.work.weixin.qq.com/document/path/90200)
-- [获取成员客户列表](https://developer.work.weixin.qq.com/document/path/92113)
-- [获取客户详情](https://developer.work.weixin.qq.com/document/path/92114)
diff --git a/README_PRESCRIPTION_LIBRARY.md b/README_PRESCRIPTION_LIBRARY.md
deleted file mode 100644
index 48b29a6b..00000000
--- a/README_PRESCRIPTION_LIBRARY.md
+++ /dev/null
@@ -1,377 +0,0 @@
-# 处方库功能 - 文档索引
-
-## 📚 欢迎使用处方库管理系统
-
-这是一个独立的中药处方模板管理系统,用于创建、管理和分享常用处方配方。
-
-**版本:** v1.0.0
-**状态:** ✅ 已完成并测试通过
-**日期:** 2026-03-22
-
----
-
-## 🚀 快速开始
-
-### 第一次使用?从这里开始!
-
-1. **[快速开始指南](QUICK_START_PRESCRIPTION_LIBRARY.md)** ⭐ 推荐
- - 5分钟快速上手
- - 创建第一个处方
- - 常用操作说明
-
-2. **[安装检查清单](INSTALLATION_CHECKLIST.md)**
- - 详细的安装步骤
- - 环境检查
- - 问题排查
-
----
-
-## 📖 完整文档
-
-### 使用文档
-
-| 文档名称 | 说明 | 适合人群 |
-|---------|------|---------|
-| [完整使用说明](PRESCRIPTION_LIBRARY_README.md) | 详细的功能说明和使用指南 | 所有用户 |
-| [功能演示](PRESCRIPTION_LIBRARY_DEMO.md) | 使用场景和示例 | 新用户 |
-| [快速开始](QUICK_START_PRESCRIPTION_LIBRARY.md) | 5分钟快速上手 | 新用户 ⭐ |
-
-### 技术文档
-
-| 文档名称 | 说明 | 适合人群 |
-|---------|------|---------|
-| [开发总结](PRESCRIPTION_LIBRARY_SUMMARY.md) | 技术实现和开发总结 | 开发人员 |
-| [文件清单](PRESCRIPTION_LIBRARY_FILES.md) | 所有文件列表和说明 | 开发人员 |
-| [交付文档](DELIVERY_PACKAGE.md) | 项目交付信息 | 项目经理 |
-
-### 安装文档
-
-| 文档名称 | 说明 | 适合人群 |
-|---------|------|---------|
-| [安装检查清单](INSTALLATION_CHECKLIST.md) | 详细的安装步骤和验证 | 运维人员 ⭐ |
-| [测试SQL](TEST_PRESCRIPTION_LIBRARY.sql) | 测试数据和查询语句 | 测试人员 |
-
----
-
-## 🔧 安装文件
-
-### 自动安装脚本
-
-| 文件名 | 平台 | 使用方法 |
-|--------|------|---------|
-| `install_prescription_library.bat` | Windows | 双击运行 |
-| `install_prescription_library.sh` | Linux/Mac | `chmod +x install_prescription_library.sh && ./install_prescription_library.sh` |
-
-### 手动安装
-
-如果自动安装脚本无法运行,请执行:
-```sql
-server/database/migrations/create_prescription_library.sql
-```
-
----
-
-## 📂 文件结构
-
-### 后端文件(PHP)
-```
-server/
-├── database/migrations/
-│ └── create_prescription_library.sql # 数据库表结构
-├── app/common/model/tcm/
-│ └── PrescriptionLibrary.php # 数据模型
-└── app/adminapi/
- ├── controller/tcm/
- │ └── PrescriptionLibraryController.php # 控制器
- ├── logic/tcm/
- │ └── PrescriptionLibraryLogic.php # 业务逻辑
- ├── lists/tcm/
- │ └── PrescriptionLibraryLists.php # 列表逻辑
- └── validate/tcm/
- └── PrescriptionLibraryValidate.php # 数据验证
-```
-
-### 前端文件(Vue)
-```
-admin/
-└── src/
- ├── views/consumer/prescription/
- │ └── list.vue # 处方库页面
- └── api/
- └── tcm.ts # API接口(已更新)
-```
-
----
-
-## 🎯 核心功能
-
-### ✅ 已实现功能
-
-1. **处方管理**
- - 创建处方(处方名称 + 药材配方)
- - 编辑处方
- - 删除处方
- - 查看处方详情
-
-2. **药材管理**
- - 动态添加药材
- - 设置药材名称和剂量(克)
- - 支持小数剂量(精确到0.1克)
-
-3. **权限控制**
- - 是否公开设置(仅自己可见/所有人可见)
- - 查看权限控制
- - 编辑权限控制
- - 删除权限控制(仅创建者)
-
-4. **搜索筛选**
- - 按处方名称搜索(模糊匹配)
- - 按是否公开筛选
- - 分页显示
-
-### 🔮 未来扩展
-
-1. 处方分类功能
-2. 处方标签功能
-3. 使用统计功能
-4. 导入导出功能
-5. 处方评论功能
-6. 版本管理功能
-
----
-
-## 🔌 API接口
-
-### 接口列表
-
-| 接口路径 | 方法 | 说明 |
-|---------|------|------|
-| `/tcm.prescriptionLibrary/lists` | GET | 获取处方列表 |
-| `/tcm.prescriptionLibrary/add` | POST | 添加处方 |
-| `/tcm.prescriptionLibrary/edit` | POST | 编辑处方 |
-| `/tcm.prescriptionLibrary/delete` | POST | 删除处方 |
-| `/tcm.prescriptionLibrary/detail` | GET | 获取处方详情 |
-
-详细说明请参阅:[开发总结](PRESCRIPTION_LIBRARY_SUMMARY.md)
-
----
-
-## 💡 使用示例
-
-### 示例1:六味地黄丸
-
-```json
-{
- "prescription_name": "六味地黄丸",
- "herbs": [
- {"name": "熟地黄", "dosage": 24},
- {"name": "山茱萸", "dosage": 12},
- {"name": "山药", "dosage": 12},
- {"name": "泽泻", "dosage": 9},
- {"name": "牡丹皮", "dosage": 9},
- {"name": "茯苓", "dosage": 9}
- ],
- "is_public": 1
-}
-```
-
-### 示例2:补中益气汤
-
-```json
-{
- "prescription_name": "补中益气汤",
- "herbs": [
- {"name": "黄芪", "dosage": 15},
- {"name": "党参", "dosage": 10},
- {"name": "白术", "dosage": 10},
- {"name": "当归", "dosage": 10},
- {"name": "陈皮", "dosage": 6},
- {"name": "升麻", "dosage": 6},
- {"name": "柴胡", "dosage": 6},
- {"name": "甘草", "dosage": 5}
- ],
- "is_public": 1
-}
-```
-
-更多示例请参阅:[功能演示](PRESCRIPTION_LIBRARY_DEMO.md)
-
----
-
-## ❓ 常见问题
-
-### Q1: 如何安装?
-A: 运行安装脚本或手动执行SQL文件。详见:[安装检查清单](INSTALLATION_CHECKLIST.md)
-
-### Q2: 如何创建处方?
-A: 点击"新增处方"按钮,填写信息后保存。详见:[快速开始](QUICK_START_PRESCRIPTION_LIBRARY.md)
-
-### Q3: 如何分享处方?
-A: 创建处方时,将"是否公开"设置为"所有人可见"。
-
-### Q4: 如何删除处方?
-A: 只有创建者才能删除处方,点击"删除"按钮即可。
-
-### Q5: 药材剂量可以是小数吗?
-A: 可以,支持精确到0.1克,如:3.5克。
-
-更多问题请参阅:[完整使用说明](PRESCRIPTION_LIBRARY_README.md)
-
----
-
-## 🧪 测试
-
-### 快速测试
-
-1. 执行测试SQL插入测试数据:
- ```bash
- mysql -u用户名 -p密码 数据库名 < TEST_PRESCRIPTION_LIBRARY.sql
- ```
-
-2. 访问处方库页面,查看测试数据
-
-3. 测试各项功能
-
-详细测试步骤请参阅:[安装检查清单](INSTALLATION_CHECKLIST.md)
-
----
-
-## 📊 技术栈
-
-### 后端
-- **框架:** ThinkPHP 6.x
-- **语言:** PHP 7.4+
-- **数据库:** MySQL 5.7+
-
-### 前端
-- **框架:** Vue 3
-- **UI库:** Element Plus
-- **语言:** TypeScript
-
----
-
-## 🔐 权限说明
-
-### 查看权限
-- ✅ 可以查看自己创建的所有处方
-- ✅ 可以查看其他人创建的公开处方
-- ❌ 不能查看其他人创建的私有处方
-
-### 编辑权限
-- ✅ 可以编辑自己创建的处方
-- ✅ 可以编辑其他人创建的公开处方
-- ❌ 不能编辑其他人创建的私有处方
-
-### 删除权限
-- ✅ 只能删除自己创建的处方
-- ❌ 不能删除其他人创建的处方
-
----
-
-## 📝 更新日志
-
-### v1.0.0 (2026-03-22)
-- ✅ 初始版本发布
-- ✅ 实现核心功能
-- ✅ 完成文档编写
-- ✅ 通过测试验收
-
----
-
-## 🆘 技术支持
-
-### 获取帮助
-
-1. **查看文档**
- - 先查看相关文档
- - 查找常见问题
-
-2. **问题反馈**
- - 提供错误信息截图
- - 提供操作步骤描述
- - 提供浏览器控制台日志
-
-3. **联系支持**
- - 联系技术支持团队
-
----
-
-## 📄 文档导航
-
-### 按角色查看
-
-#### 👨💼 管理员/使用者
-1. [快速开始指南](QUICK_START_PRESCRIPTION_LIBRARY.md) ⭐
-2. [完整使用说明](PRESCRIPTION_LIBRARY_README.md)
-3. [功能演示](PRESCRIPTION_LIBRARY_DEMO.md)
-
-#### 👨💻 开发人员
-1. [开发总结](PRESCRIPTION_LIBRARY_SUMMARY.md) ⭐
-2. [文件清单](PRESCRIPTION_LIBRARY_FILES.md)
-3. [交付文档](DELIVERY_PACKAGE.md)
-
-#### 🔧 运维人员
-1. [安装检查清单](INSTALLATION_CHECKLIST.md) ⭐
-2. [测试SQL](TEST_PRESCRIPTION_LIBRARY.sql)
-
-#### 📋 项目经理
-1. [交付文档](DELIVERY_PACKAGE.md) ⭐
-2. [开发总结](PRESCRIPTION_LIBRARY_SUMMARY.md)
-
-### 按任务查看
-
-#### 🚀 安装部署
-1. [安装检查清单](INSTALLATION_CHECKLIST.md)
-2. [快速开始指南](QUICK_START_PRESCRIPTION_LIBRARY.md)
-
-#### 📖 学习使用
-1. [快速开始指南](QUICK_START_PRESCRIPTION_LIBRARY.md)
-2. [功能演示](PRESCRIPTION_LIBRARY_DEMO.md)
-3. [完整使用说明](PRESCRIPTION_LIBRARY_README.md)
-
-#### 🔧 开发维护
-1. [开发总结](PRESCRIPTION_LIBRARY_SUMMARY.md)
-2. [文件清单](PRESCRIPTION_LIBRARY_FILES.md)
-
-#### 📦 项目交付
-1. [交付文档](DELIVERY_PACKAGE.md)
-2. [文件清单](PRESCRIPTION_LIBRARY_FILES.md)
-
----
-
-## ✅ 检查清单
-
-### 安装前
-- [ ] 已阅读快速开始指南
-- [ ] 已备份数据库
-- [ ] 已检查环境要求
-
-### 安装中
-- [ ] 已执行安装脚本
-- [ ] 已配置菜单权限
-- [ ] 已重启服务
-
-### 安装后
-- [ ] 已测试基本功能
-- [ ] 已创建测试处方
-- [ ] 已验证权限控制
-
----
-
-## 🎉 开始使用
-
-准备好了吗?让我们开始吧!
-
-**推荐路径:**
-1. 阅读 [快速开始指南](QUICK_START_PRESCRIPTION_LIBRARY.md)
-2. 运行安装脚本
-3. 创建第一个处方
-4. 探索更多功能
-
-**祝使用愉快!** 🚀
-
----
-
-**版本:** v1.0.0
-**最后更新:** 2026-03-22
-**状态:** ✅ 已完成
diff --git a/RECORDING_FIX_SUMMARY.md b/RECORDING_FIX_SUMMARY.md
deleted file mode 100644
index e84a3b50..00000000
--- a/RECORDING_FIX_SUMMARY.md
+++ /dev/null
@@ -1,144 +0,0 @@
-# 本地录制修复总结
-
-## 问题根源
-
-通过分析实际日志发现,本地录制失败的根本原因是:
-
-1. **录制启动太晚**:1200ms 延迟导致录制在对方挂断后才启动
-2. **SDK 提前销毁轨道**:`stopLocalVideo/stopLocalAudio` 在录制完成前就销毁了视频轨道
-3. **时间窗口极短**:从对方离开到轨道销毁只有 200-300ms
-
-## 关键时间线(问题场景)
-
-```
-15:30:51.841 - 对方离开(USER_LEAVE 事件)
-15:30:51.xxx - beginStopNow 被调用(但录制还未启动!)
-15:30:52.072 - stopLocalVideo/stopLocalAudio(轨道被销毁)
-15:30:52.102 - exitRoom
-15:30:52.374 - MediaRecorder 已启动(太晚了!)
-15:30:52.xxx - 数据块数: 0(因为轨道已销毁)
-```
-
-## 修复方案
-
-### 1. 大幅缩短录制启动延迟
-- **从 1200ms 缩短到 500ms**
-- 使用 300ms 间隔快速重试(最多 10 次)
-- 目标:在通话接通后 1 秒内启动录制
-
-### 2. 在通话结束事件时立即停止录制
-- 监听 `TUICallEvent.USER_LEAVE`、`CALL_END`、`ON_CALL_NOT_CONNECTED`
-- 检测到事件时立即调用 `beginStopNow()`
-- 抢在 SDK 销毁轨道前完成录制
-
-### 3. 缩短停止延迟
-- **从 300ms 缩短到 100ms**
-- 减少等待时间,避免 SDK 提前销毁轨道
-
-### 4. 智能视频元素检测
-- 首先检查容器内的视频
-- 如果没有,检查 body(TUICallKit Teleport)
-- 过滤出有活跃视频流的元素
-- 验证 readyState >= HAVE_CURRENT_DATA
-
-## 修改的文件
-
-1. **admin/src/components/chat-dialog/index.vue**
- - 缩短录制启动延迟(1200ms → 500ms)
- - 添加快速重试机制(300ms 间隔,最多 10 次)
- - 在 `bindEngineRoomEvents` 中添加立即停止逻辑
- - 增强日志输出
-
-2. **admin/src/utils/call-local-recorder.ts**
- - 缩短停止延迟(300ms → 100ms)
- - 增强日志输出
- - 添加详细的状态跟踪
-
-3. **LOCAL_RECORDING_DEBUG_GUIDE.md**
- - 完整的调试指南
- - 常见问题诊断
- - 测试建议
-
-4. **admin/test-local-recording.html**
- - 独立的测试工具
- - 浏览器兼容性检测
- - MediaRecorder 功能测试
-
-## 测试步骤
-
-### 1. 清除浏览器缓存
-确保使用最新的代码。
-
-### 2. 打开控制台
-F12 打开开发者工具,切换到 Console 标签。
-
-### 3. 发起视频通话
-点击视频通话按钮。
-
-### 4. 观察关键日志
-
-#### 接通后应该看到(1 秒内):
-```
-[chat-dialog] 尝试启动本地录制(第 1 次)
-[chat-dialog] 视频元素已就绪,立即启动录制
-[call-local-recorder] MediaRecorder.start() 调用成功
-[call-local-recorder] MediaRecorder 已启动
-[chat-dialog] ✅ 本地录制已成功启动
-```
-
-#### 通话过程中应该看到(每 250ms):
-```
-[call-local-recorder] 收到数据块,大小: 12345 (总计: 1 块)
-[call-local-recorder] 收到数据块,大小: 15678 (总计: 2 块)
-```
-
-#### 挂断时应该看到:
-```
-[chat-dialog] 检测到通话结束事件,立即停止录制
-[call-local-recorder] beginStopNow 被调用
-[call-local-recorder] 准备停止 MediaRecorder,当前状态: recording 已收集数据块: 15
-[call-local-recorder] 生成 Blob: 1234567 bytes
-[chat-dialog] 开始上传录制文件
-本地录制已上传并关联到通话记录
-```
-
-### 5. 验证结果
-- 检查诊单是否有录制文件
-- 播放录制文件验证内容
-
-## 预期效果
-
-### 修复前
-- 录制成功率:0%
-- 数据块数:0
-- Blob 大小:null
-
-### 修复后
-- 录制成功率:>95%(通话时长 > 2 秒)
-- 数据块数:>= 通话秒数 * 4
-- Blob 大小:>= 通话秒数 * 50KB
-
-## 注意事项
-
-1. **最短通话时长**:建议至少 2 秒,否则可能来不及启动录制
-2. **浏览器兼容性**:Chrome/Edge 85+、Firefox 78+、Safari 14.1+
-3. **网络状况**:网络差时视频流可能不稳定,影响录制质量
-4. **权限问题**:确保浏览器已授予摄像头和麦克风权限
-
-## 如果问题仍然存在
-
-1. 查看完整的控制台日志
-2. 确认录制是否在接通后 1 秒内启动
-3. 确认是否有 "收到数据块" 日志
-4. 检查通话时长是否 > 2 秒
-5. 参考 `LOCAL_RECORDING_DEBUG_GUIDE.md` 进行详细诊断
-
-## 进一步优化方向
-
-如果修复后仍有问题,可以考虑:
-
-1. **进一步缩短启动延迟**:300ms 或更短
-2. **使用 MutationObserver**:监听视频元素添加,立即启动
-3. **添加录制状态监控**:定期检查 MediaRecorder 状态
-4. **添加降级方案**:本地录制失败时提示用户
-5. **添加手动控制**:让用户手动开始/停止录制
diff --git a/REGION_API_ENV_VARIABLE_UPDATE.md b/REGION_API_ENV_VARIABLE_UPDATE.md
deleted file mode 100644
index 4d339344..00000000
--- a/REGION_API_ENV_VARIABLE_UPDATE.md
+++ /dev/null
@@ -1,288 +0,0 @@
-# Region API Environment Variable Update (省市区API环境变量更新)
-
-## Change Summary
-Updated the region data API URL to use the `VITE_APP_BASE_URL` environment variable instead of a hardcoded URL, making it more flexible and maintainable across different environments.
-
-## Changes Made
-
-### 1. Updated prescription/index.vue
-**File**: `admin/src/views/consumer/prescription/index.vue`
-
-**Before:**
-```typescript
-const apiUrl = import.meta.env.DEV
- ? '/api-proxy/area/ChinaCitys.json'
- : 'https://admin.zhenyangtang.com.cn/area/ChinaCitys.json'
-```
-
-**After:**
-```typescript
-const apiUrl = import.meta.env.DEV
- ? '/api-proxy/area/ChinaCitys.json'
- : `${import.meta.env.VITE_APP_BASE_URL}area/ChinaCitys.json`
-```
-
-### 2. Updated prescription/order_list.vue
-**File**: `admin/src/views/consumer/prescription/order_list.vue`
-
-Applied the same change.
-
-## Environment Configuration
-
-### Production Environment
-**File**: `admin/.env.production`
-```env
-VITE_APP_BASE_URL='https://cs.zhenyangtang.com.cn/'
-```
-
-### Development Environment
-**File**: `admin/.env.development` (if exists)
-```env
-VITE_APP_BASE_URL='http://localhost:8080/'
-```
-
-## How It Works
-
-### URL Construction
-The production URL is now constructed dynamically:
-```typescript
-`${import.meta.env.VITE_APP_BASE_URL}area/ChinaCitys.json`
-```
-
-With `VITE_APP_BASE_URL='https://cs.zhenyangtang.com.cn/'`, this becomes:
-```
-https://cs.zhenyangtang.com.cn/area/ChinaCitys.json
-```
-
-### Development vs Production
-
-**Development Mode:**
-- Uses proxy: `/api-proxy/area/ChinaCitys.json`
-- Vite proxy rewrites to external URL
-- Avoids CORS issues
-
-**Production Mode:**
-- Uses: `${VITE_APP_BASE_URL}area/ChinaCitys.json`
-- Resolves to: `https://cs.zhenyangtang.com.cn/area/ChinaCitys.json`
-- Direct fetch from configured base URL
-
-## Benefits
-
-### 1. Environment Flexibility
-- ✅ Different URLs for different environments (dev, staging, production)
-- ✅ No code changes needed when switching environments
-- ✅ Single source of truth for base URL
-
-### 2. Easier Deployment
-- ✅ Change URL by updating `.env.production` file
-- ✅ No need to modify source code
-- ✅ Supports multiple deployment targets
-
-### 3. Consistency
-- ✅ Uses same base URL as other API calls
-- ✅ Maintains consistency across the application
-- ✅ Easier to manage and maintain
-
-### 4. Configuration Management
-- ✅ Centralized configuration in environment files
-- ✅ Easy to override for different environments
-- ✅ Follows best practices for environment-specific settings
-
-## Environment Files
-
-### Structure
-```
-admin/
-├── .env.development # Development environment
-├── .env.production # Production environment
-├── .env.staging # Staging environment (optional)
-└── .env.local # Local overrides (gitignored)
-```
-
-### Example Configurations
-
-**Development** (`.env.development`):
-```env
-VITE_APP_BASE_URL='http://localhost:8080/'
-VITE_APP_REQUEST_TIMEOUT=60000
-```
-
-**Staging** (`.env.staging`):
-```env
-VITE_APP_BASE_URL='https://staging.zhenyangtang.com.cn/'
-VITE_APP_REQUEST_TIMEOUT=60000
-```
-
-**Production** (`.env.production`):
-```env
-VITE_APP_BASE_URL='https://cs.zhenyangtang.com.cn/'
-VITE_APP_REQUEST_TIMEOUT=60000
-```
-
-## Testing Steps
-
-### 1. Verify Environment Variable
-Check that the environment variable is loaded:
-```typescript
-console.log('Base URL:', import.meta.env.VITE_APP_BASE_URL)
-```
-
-Expected output in production:
-```
-Base URL: https://cs.zhenyangtang.com.cn/
-```
-
-### 2. Test Development Mode
-1. Run development server:
- ```bash
- cd admin
- npm run dev
- ```
-2. Open browser console
-3. Check constructed URL uses proxy
-4. Verify region selector loads data
-
-### 3. Test Production Build
-1. Build for production:
- ```bash
- cd admin
- npm run build
- ```
-2. Check built files use production URL
-3. Serve and test:
- ```bash
- npm run preview
- ```
-4. Verify region selector loads from: `https://cs.zhenyangtang.com.cn/area/ChinaCitys.json`
-
-### 4. Test Different Environments
-Create a staging build:
-```bash
-npm run build -- --mode staging
-```
-
-Verify it uses the staging URL from `.env.staging`.
-
-## URL Resolution Examples
-
-### With Trailing Slash (Current)
-```typescript
-VITE_APP_BASE_URL='https://cs.zhenyangtang.com.cn/'
-Result: https://cs.zhenyangtang.com.cn/area/ChinaCitys.json ✅
-```
-
-### Without Trailing Slash
-```typescript
-VITE_APP_BASE_URL='https://cs.zhenyangtang.com.cn'
-Result: https://cs.zhenyangtang.com.cnarea/ChinaCitys.json ❌
-```
-
-**Important**: Ensure `VITE_APP_BASE_URL` always ends with `/`
-
-### Handling Missing Trailing Slash
-If you want to be defensive, you could add:
-```typescript
-const baseUrl = import.meta.env.VITE_APP_BASE_URL.endsWith('/')
- ? import.meta.env.VITE_APP_BASE_URL
- : `${import.meta.env.VITE_APP_BASE_URL}/`
-
-const apiUrl = import.meta.env.DEV
- ? '/api-proxy/area/ChinaCitys.json'
- : `${baseUrl}area/ChinaCitys.json`
-```
-
-## Troubleshooting
-
-### Issue: Region selector not loading in production
-**Check:**
-1. Verify `.env.production` file exists
-2. Check `VITE_APP_BASE_URL` is set correctly
-3. Ensure URL ends with `/`
-4. Verify file exists at: `${VITE_APP_BASE_URL}area/ChinaCitys.json`
-
-### Issue: 404 Not Found
-**Solution:**
-1. Check the constructed URL in browser Network tab
-2. Verify the file is accessible at that URL
-3. Check server configuration for the path
-
-### Issue: CORS Error
-**Solution:**
-1. Ensure server has proper CORS headers
-2. Check `Access-Control-Allow-Origin` header
-3. Verify the domain is allowed
-
-## Server Configuration
-
-### File Location
-Ensure the file exists at:
-```
-https://cs.zhenyangtang.com.cn/area/ChinaCitys.json
-```
-
-### Nginx Configuration Example
-```nginx
-location /area/ {
- alias /path/to/public/area/;
- add_header Access-Control-Allow-Origin *;
- add_header Cache-Control "public, max-age=86400";
-}
-```
-
-### Apache Configuration Example
-```apache
-
- Header set Access-Control-Allow-Origin "*"
- Header set Cache-Control "public, max-age=86400"
-
-```
-
-## Related Files
-
-### Frontend:
-- `admin/src/views/consumer/prescription/index.vue`
- - Updated to use `VITE_APP_BASE_URL`
-- `admin/src/views/consumer/prescription/order_list.vue`
- - Updated to use `VITE_APP_BASE_URL`
-
-### Configuration:
-- `admin/.env.production`
- - Contains `VITE_APP_BASE_URL='https://cs.zhenyangtang.com.cn/'`
-- `admin/.env.development`
- - Should contain development base URL
-- `admin/vite.config.ts`
- - Proxy configuration for development
-
-## Best Practices
-
-### 1. Always Use Environment Variables
-✅ Do: `${import.meta.env.VITE_APP_BASE_URL}path/to/resource`
-❌ Don't: `https://hardcoded-domain.com/path/to/resource`
-
-### 2. Ensure Trailing Slashes
-✅ Do: `VITE_APP_BASE_URL='https://example.com/'`
-❌ Don't: `VITE_APP_BASE_URL='https://example.com'`
-
-### 3. Use Consistent Naming
-All Vite environment variables should start with `VITE_` to be exposed to the client.
-
-### 4. Document Environment Variables
-Keep a `.env.example` file with all required variables:
-```env
-# Base URL for API and static resources
-VITE_APP_BASE_URL='https://example.com/'
-
-# Request timeout in milliseconds
-VITE_APP_REQUEST_TIMEOUT=60000
-```
-
-## Summary
-
-✅ Replaced hardcoded URL with environment variable
-✅ Uses `VITE_APP_BASE_URL` from `.env.production`
-✅ More flexible for different environments
-✅ Easier to deploy and maintain
-✅ Consistent with other API configurations
-✅ Applied to both prescription index and order list views
-
-The region API now uses the configured base URL, making it easier to manage across different environments!
diff --git a/REGION_API_PRODUCTION_FIX.md b/REGION_API_PRODUCTION_FIX.md
deleted file mode 100644
index 7b3795ed..00000000
--- a/REGION_API_PRODUCTION_FIX.md
+++ /dev/null
@@ -1,243 +0,0 @@
-# Region API Production URL Fix (省市区API生产环境URL修复)
-
-## Issue
-The region data API was using a proxy path (`/api-proxy/area/ChinaCitys.json`) which only works in development mode. After building for production, the proxy is not available, causing the region selector to fail.
-
-## Root Cause
-Vite's proxy configuration (`vite.config.ts`) only works during development (`npm run dev`). When the application is built for production (`npm run build`), the proxy is not included in the build output, and the relative path `/api-proxy/...` becomes invalid.
-
-## Solution
-Use environment detection to switch between proxy URL (development) and full URL (production).
-
-## Changes Made
-
-### 1. Updated prescription/index.vue
-**File**: `admin/src/views/consumer/prescription/index.vue`
-
-Changed from:
-```typescript
-const response = await fetch('/api-proxy/area/ChinaCitys.json')
-```
-
-To:
-```typescript
-// 开发环境使用代理,生产环境使用完整URL
-const apiUrl = import.meta.env.DEV
- ? '/api-proxy/area/ChinaCitys.json'
- : 'https://admin.zhenyangtang.com.cn/area/ChinaCitys.json'
-
-const response = await fetch(apiUrl)
-```
-
-### 2. Updated prescription/order_list.vue
-**File**: `admin/src/views/consumer/prescription/order_list.vue`
-
-Applied the same change to the order list view.
-
-## How It Works
-
-### Environment Detection
-Uses Vite's built-in environment variable `import.meta.env.DEV`:
-- **Development** (`npm run dev`): `import.meta.env.DEV = true`
-- **Production** (`npm run build`): `import.meta.env.DEV = false`
-
-### URL Selection Logic
-```typescript
-const apiUrl = import.meta.env.DEV
- ? '/api-proxy/area/ChinaCitys.json' // Development: Use proxy
- : 'https://admin.zhenyangtang.com.cn/area/ChinaCitys.json' // Production: Use full URL
-```
-
-### Development Mode
-- Uses proxy path: `/api-proxy/area/ChinaCitys.json`
-- Vite proxy rewrites to: `https://admin.zhenyangtang.com.cn/area/ChinaCitys.json`
-- Avoids CORS issues during development
-
-### Production Mode
-- Uses full URL: `https://admin.zhenyangtang.com.cn/area/ChinaCitys.json`
-- Direct fetch from the actual server
-- No proxy needed
-
-## Benefits
-
-### 1. Works in Both Environments
-- ✅ Development: Uses proxy for CORS-free development
-- ✅ Production: Uses direct URL for deployed application
-
-### 2. No Build Configuration Changes
-- No need to modify build scripts
-- No environment-specific builds
-- Single codebase for all environments
-
-### 3. Automatic Detection
-- Automatically switches based on environment
-- No manual configuration needed
-- No risk of using wrong URL
-
-### 4. Maintains CORS Handling
-- Development: Proxy handles CORS
-- Production: Server should have proper CORS headers
-
-## Testing Steps
-
-### 1. Test Development Mode
-1. Run development server:
- ```bash
- cd admin
- npm run dev
- ```
-2. Open browser console
-3. Check for log: `开始加载省市区数据...`
-4. Verify it uses proxy URL
-5. Check region selector loads data
-
-### 2. Test Production Build
-1. Build for production:
- ```bash
- cd admin
- npm run build
- ```
-2. Serve the built files:
- ```bash
- npm run preview
- # or deploy to production server
- ```
-3. Open browser console
-4. Check for log: `开始加载省市区数据...`
-5. Verify it uses full URL
-6. Check region selector loads data
-
-### 3. Verify URL in Network Tab
-**Development:**
-- Request URL: `http://localhost:5173/api-proxy/area/ChinaCitys.json`
-- Proxied to: `https://admin.zhenyangtang.com.cn/area/ChinaCitys.json`
-
-**Production:**
-- Request URL: `https://admin.zhenyangtang.com.cn/area/ChinaCitys.json`
-- Direct request (no proxy)
-
-### 4. Test Fallback Data
-If API fails in either environment, verify fallback data is used:
-```typescript
-regionOptions.value = [
- {
- name: '四川省',
- children: [...]
- },
- ...
-]
-```
-
-## Alternative Solutions Considered
-
-### Option 1: Environment Variables (Not Chosen)
-```typescript
-const apiUrl = import.meta.env.VITE_REGION_API_URL || '/api-proxy/area/ChinaCitys.json'
-```
-**Pros**: More flexible for different environments
-**Cons**: Requires environment-specific configuration files
-
-### Option 2: Relative Path (Not Chosen)
-```typescript
-const apiUrl = '/area/ChinaCitys.json'
-```
-**Pros**: Simple
-**Cons**: Requires copying file to public folder, increases bundle size
-
-### Option 3: Backend API Endpoint (Not Chosen)
-Create a backend endpoint to serve region data
-**Pros**: More control, can cache, can update without frontend deploy
-**Cons**: Requires backend changes, adds server load
-
-### Option 4: Current Solution (Chosen) ✅
-Use environment detection with conditional URL
-**Pros**:
-- Simple implementation
-- No additional configuration
-- Works in both environments
-- No backend changes needed
-**Cons**:
-- Hardcoded production URL (acceptable for this use case)
-
-## Production Server Requirements
-
-### CORS Headers
-The production server hosting `ChinaCitys.json` should have proper CORS headers:
-
-```
-Access-Control-Allow-Origin: *
-# or specific domain:
-Access-Control-Allow-Origin: https://your-admin-domain.com
-```
-
-### File Availability
-Ensure the file is accessible at:
-```
-https://admin.zhenyangtang.com.cn/area/ChinaCitys.json
-```
-
-### Alternative: Use Backend Proxy
-If CORS is an issue in production, consider creating a backend endpoint:
-```php
-// server/app/adminapi/controller/RegionController.php
-public function getChinaCitys()
-{
- $url = 'https://admin.zhenyangtang.com.cn/area/ChinaCitys.json';
- $data = file_get_contents($url);
- return json(['data' => json_decode($data, true)]);
-}
-```
-
-Then update frontend to use:
-```typescript
-const apiUrl = import.meta.env.DEV
- ? '/api-proxy/area/ChinaCitys.json'
- : '/adminapi/region/getChinaCitys' // Backend endpoint
-```
-
-## Related Files
-
-### Frontend:
-- `admin/src/views/consumer/prescription/index.vue`
- - Updated `loadRegionData()` function
-- `admin/src/views/consumer/prescription/order_list.vue`
- - Updated `loadRegionData()` function
-
-### Configuration:
-- `admin/vite.config.ts`
- - Proxy configuration (development only)
-
-### Documentation:
-- `REGION_DATA_SETUP.md` (should be updated to mention production URL)
-
-## Environment Variables Reference
-
-### Vite Built-in Variables:
-- `import.meta.env.DEV` - Boolean, true in development
-- `import.meta.env.PROD` - Boolean, true in production
-- `import.meta.env.MODE` - String, 'development' or 'production'
-
-### Usage Example:
-```typescript
-if (import.meta.env.DEV) {
- console.log('Running in development mode')
-}
-
-if (import.meta.env.PROD) {
- console.log('Running in production mode')
-}
-
-console.log('Current mode:', import.meta.env.MODE)
-```
-
-## Summary
-
-✅ Fixed production build issue with region API
-✅ Uses proxy in development for CORS-free development
-✅ Uses direct URL in production for deployed application
-✅ Automatic environment detection
-✅ No build configuration changes needed
-✅ Maintains fallback data for both environments
-✅ Applied to both prescription index and order list views
-
-The region selector now works correctly in both development and production environments!
diff --git a/REGION_DATABASE_SAVE_FIX.md b/REGION_DATABASE_SAVE_FIX.md
deleted file mode 100644
index c78e0267..00000000
--- a/REGION_DATABASE_SAVE_FIX.md
+++ /dev/null
@@ -1,170 +0,0 @@
-# Region Database Save Fix (省市区数据库保存修复)
-
-## Issue
-The region fields (province, city, district) were being sent in the API request payload but were not being saved to the database.
-
-## Root Cause Analysis
-
-### Frontend Issue
-The frontend was sending the wrong parameter names:
-- Sent: `province`, `city`, `district`
-- Expected by backend: `shipping_province`, `shipping_city`, `shipping_district`
-
-### Backend Issue
-The `create()` method in `PrescriptionOrderLogic.php` was not handling the province/city/district fields at all, even though the `edit()` method had the code to handle them.
-
-## Changes Made
-
-### 1. Frontend - Fixed Parameter Names
-**File**: `admin/src/views/consumer/prescription/index.vue`
-
-Changed the payload to use the correct parameter names:
-
-```typescript
-// Before:
-payload.province = createOrderForm.region[0]
-payload.city = createOrderForm.region[1]
-payload.district = createOrderForm.region[2]
-
-// After:
-payload.shipping_province = createOrderForm.region[0]
-payload.shipping_city = createOrderForm.region[1]
-payload.shipping_district = createOrderForm.region[2]
-```
-
-### 2. Backend - Added Region Fields to create() Method
-**File**: `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
-
-Added code to handle province/city/district fields in the `create()` method:
-
-```php
-// 处理省市区字段
-if (isset($params['shipping_province'])) {
- $order->shipping_province = (string) $params['shipping_province'];
-}
-if (isset($params['shipping_city'])) {
- $order->shipping_city = (string) $params['shipping_city'];
-}
-if (isset($params['shipping_district'])) {
- $order->shipping_district = (string) $params['shipping_district'];
-}
-```
-
-This matches the existing code in the `edit()` method.
-
-## Data Flow (After Fix)
-
-### Create Order Flow:
-1. User selects region: 北京市 → 北京市 → 丰台区
-2. Frontend stores: `createOrderForm.region = ['北京市', '北京市', '丰台区']`
-3. Frontend sends payload:
- ```json
- {
- "shipping_province": "北京市",
- "shipping_city": "北京市",
- "shipping_district": "丰台区",
- ...other fields
- }
- ```
-4. Backend `create()` method processes:
- ```php
- $order->shipping_province = "北京市";
- $order->shipping_city = "北京市";
- $order->shipping_district = "丰台区";
- ```
-5. Database columns are populated:
- ```
- shipping_province: 北京市
- shipping_city: 北京市
- shipping_district: 丰台区
- ```
-
-## Testing Steps
-
-### 1. Test Create Order with Region
-1. Open prescription list page
-2. Click "创建订单" on any prescription
-3. Fill in required fields:
- - Select patient
- - Enter recipient name and phone
- - **Select region**: 北京市 → 北京市 → 丰台区
- - Enter detailed address
-4. Click "创建业务订单"
-5. Check browser Network tab:
- - Verify payload includes:
- ```json
- {
- "shipping_province": "北京市",
- "shipping_city": "北京市",
- "shipping_district": "丰台区"
- }
- ```
-
-### 2. Verify Database
-After creating the order, check the database:
-
-```sql
-SELECT
- id,
- order_no,
- shipping_province,
- shipping_city,
- shipping_district,
- shipping_address
-FROM tcm_prescription_order
-ORDER BY id DESC
-LIMIT 1;
-```
-
-Expected result:
-```
-| id | order_no | shipping_province | shipping_city | shipping_district | shipping_address |
-|----|----------|-------------------|---------------|-------------------|------------------|
-| XX | POXXXXXX | 北京市 | 北京市 | 丰台区 | 顶顶顶 |
-```
-
-### 3. Test Different Regions
-Test with various regions to ensure all work correctly:
-- 四川省 → 成都市 → 武侯区
-- 上海市 → 上海市 → 浦东新区
-- 广东省 → 深圳市 → 南山区
-
-### 4. Test Edit Order
-1. Edit an existing order
-2. Change the region
-3. Save and verify the database is updated
-
-## Database Schema
-
-The database should have these columns (from `add_shipping_region_fields.sql`):
-
-```sql
-ALTER TABLE `tcm_prescription_order`
-ADD COLUMN `shipping_province` VARCHAR(50) DEFAULT NULL COMMENT '收货省份' AFTER `shipping_address`,
-ADD COLUMN `shipping_city` VARCHAR(50) DEFAULT NULL COMMENT '收货城市' AFTER `shipping_province`,
-ADD COLUMN `shipping_district` VARCHAR(50) DEFAULT NULL COMMENT '收货区县' AFTER `shipping_city`;
-```
-
-## Related Files
-
-### Frontend:
-- `admin/src/views/consumer/prescription/index.vue`
- - Changed parameter names in `submitCreateOrderFromPrescription()`
-
-### Backend:
-- `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
- - Added province/city/district handling in `create()` method
- - Already had province/city/district handling in `edit()` method
-
-### Database:
-- `add_shipping_region_fields.sql` (migration file)
-
-## Summary
-
-✅ Fixed frontend parameter names: `province` → `shipping_province`, etc.
-✅ Added province/city/district handling to backend `create()` method
-✅ Backend `edit()` method already had the correct code
-✅ Database columns will now be populated correctly on order creation
-✅ Database columns will be updated correctly on order edit
-
-The region fields are now properly saved to the database for both create and edit operations!
diff --git a/REGION_DATA_SETUP.md b/REGION_DATA_SETUP.md
deleted file mode 100644
index 83367146..00000000
--- a/REGION_DATA_SETUP.md
+++ /dev/null
@@ -1,72 +0,0 @@
-# 省市区数据加载说明
-
-## 问题
-省市区下拉框显示为空
-
-## 原因
-1. Vite代理配置需要重启开发服务器才能生效
-2. 如果代理加载失败,会使用后备数据
-
-## 解决步骤
-
-### 1. 重启开发服务器
-```bash
-# 停止当前运行的开发服务器 (Ctrl+C)
-# 然后重新启动
-cd admin
-npm run dev
-```
-
-### 2. 检查控制台日志
-打开浏览器开发者工具的Console标签,应该能看到:
-- "开始加载省市区数据..."
-- "响应状态: 200"
-- "加载的数据条数: XX"
-- "regionOptions 已设置,条数: XX"
-
-### 3. 如果代理失败
-如果看到错误信息,会自动使用后备数据,包含:
-- 四川省(成都市、绵阳市)
-- 北京市
-- 上海市
-- 广东省(广州市、深圳市)
-
-## 配置说明
-
-### Vite代理配置 (admin/vite.config.ts)
-```typescript
-server: {
- proxy: {
- '/api-proxy': {
- target: 'https://admin.zhenyangtang.com.cn',
- changeOrigin: true,
- rewrite: (path) => path.replace(/^\/api-proxy/, '')
- }
- }
-}
-```
-
-### 前端请求 (admin/src/views/consumer/prescription/index.vue)
-```javascript
-const response = await fetch('/api-proxy/area/ChinaCitys.json')
-```
-
-## 生产环境配置
-
-生产环境需要在nginx配置代理:
-
-```nginx
-location /api-proxy/ {
- proxy_pass https://admin.zhenyangtang.com.cn/;
- proxy_set_header Host admin.zhenyangtang.com.cn;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-}
-```
-
-## 测试
-1. 打开创建订单对话框
-2. 查看"省市区"下拉框
-3. 应该能看到省份列表
-4. 选择省份后能看到城市列表
-5. 选择城市后能看到区县列表
diff --git a/REGION_FIELD_SUBMISSION_FIX.md b/REGION_FIELD_SUBMISSION_FIX.md
deleted file mode 100644
index abccae67..00000000
--- a/REGION_FIELD_SUBMISSION_FIX.md
+++ /dev/null
@@ -1,153 +0,0 @@
-# Region Field Submission Fix (省市区字段提交修复)
-
-## Issue
-The region selector (省市区) in the create order form was not submitting the selected province, city, and district values to the database.
-
-## Root Cause
-1. The cascader component had `emitPath: false`, which only returned the last selected value (district name) instead of the full path
-2. The `submitCreateOrderFromPrescription()` function was not including the region fields in the payload sent to the backend
-
-## Changes Made
-
-### 1. Updated Cascader Configuration
-**File**: `admin/src/views/consumer/prescription/index.vue`
-
-Changed `emitPath` from `false` to `true` to get the full path array:
-
-```typescript
-// Before:
-:props="{ value: 'name', label: 'name', children: 'children', emitPath: false, checkStrictly: false }"
-
-// After:
-:props="{ value: 'name', label: 'name', children: 'children', emitPath: true, checkStrictly: false }"
-```
-
-**Effect**: Now `createOrderForm.region` will contain an array like `['四川省', '成都市', '锦江区']` instead of just `'锦江区'`
-
-### 2. Updated Form Submission
-**File**: `admin/src/views/consumer/prescription/index.vue`
-
-Added code to extract province, city, and district from the region array and include them in the payload:
-
-```typescript
-// 添加省市区字段
-if (createOrderForm.region && createOrderForm.region.length >= 3) {
- payload.province = createOrderForm.region[0]
- payload.city = createOrderForm.region[1]
- payload.district = createOrderForm.region[2]
-}
-```
-
-### 3. Updated handleRegionChange Comment
-Added clarification that the value is now an array of the full path.
-
-## Data Flow
-
-### Before Fix:
-1. User selects: 四川省 → 成都市 → 锦江区
-2. `createOrderForm.region` = `'锦江区'` (only last value)
-3. Payload sent to backend: **No province/city/district fields**
-4. Database: province, city, district columns remain empty
-
-### After Fix:
-1. User selects: 四川省 → 成都市 → 锦江区
-2. `createOrderForm.region` = `['四川省', '成都市', '锦江区']` (full path)
-3. Payload sent to backend:
- ```json
- {
- "province": "四川省",
- "city": "成都市",
- "district": "锦江区",
- ...other fields
- }
- ```
-4. Database: province, city, district columns are populated correctly
-
-## Testing Steps
-
-### 1. Test Region Selection and Submission
-1. Open the prescription list page
-2. Click "创建订单" button on any prescription
-3. Fill in the required fields:
- - Select a patient (患者)
- - Enter recipient name (收货人)
- - Enter recipient phone (收货手机)
- - **Select province/city/district** (省市区): e.g., 四川省 → 成都市 → 锦江区
- - Enter detailed address (详细地址)
-4. Click "创建业务订单" button
-5. Check browser Network tab:
- - Find the API request to create order
- - Verify the payload includes:
- ```json
- {
- "province": "四川省",
- "city": "成都市",
- "district": "锦江区"
- }
- ```
-
-### 2. Verify Database Storage
-After creating an order, check the database:
-
-```sql
-SELECT id, order_no, province, city, district, shipping_address
-FROM tcm_prescription_order
-ORDER BY id DESC
-LIMIT 1;
-```
-
-Expected result:
-```
-| id | order_no | province | city | district | shipping_address |
-|----|----------|----------|--------|----------|------------------|
-| XX | XXXXXX | 四川省 | 成都市 | 锦江区 | 中关村... |
-```
-
-### 3. Test Different Regions
-Test with various regions to ensure the data structure works correctly:
-- 北京市 → 北京市 → 朝阳区
-- 上海市 → 上海市 → 浦东新区
-- 广东省 → 深圳市 → 南山区
-
-### 4. Test Edge Cases
-1. **No region selected**: Should still allow submission (if not required)
-2. **Only province selected**: Should not submit incomplete data
-3. **Clear selection**: Click the clear button (×) and verify region is cleared
-
-## Backend Verification
-
-The backend should already have the database columns from the migration `add_shipping_region_fields.sql`:
-- `province` (varchar)
-- `city` (varchar)
-- `district` (varchar)
-
-If the backend validation or logic needs to be updated, check:
-- `server/app/adminapi/validate/tcm/PrescriptionOrderValidate.php`
-- `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
-
-## Console Debugging
-
-If you need to debug, check the browser console for the log message:
-```
-选择的省市区: ['四川省', '成都市', '锦江区']
-```
-
-This confirms the cascader is emitting the correct data structure.
-
-## Related Files
-
-### Frontend:
-- `admin/src/views/consumer/prescription/index.vue` (main changes)
-
-### Backend:
-- `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php` (should handle province/city/district)
-- `add_shipping_region_fields.sql` (database migration)
-
-## Summary
-
-✅ Changed cascader `emitPath` to `true` to get full path array
-✅ Added province/city/district extraction in form submission
-✅ Payload now includes all three region fields
-✅ Database columns will be populated correctly
-
-The region selector now properly submits province, city, and district values to the database.
diff --git a/REGION_SELECTOR_AND_DOSAGE_FIX.md b/REGION_SELECTOR_AND_DOSAGE_FIX.md
deleted file mode 100644
index 2cb8ca3a..00000000
--- a/REGION_SELECTOR_AND_DOSAGE_FIX.md
+++ /dev/null
@@ -1,199 +0,0 @@
-# Region Selector and Dosage Fields Fix
-
-## Issues Identified
-
-### 1. Region Selector Empty (省市区下拉为空)
-**Problem**: The region cascader dropdown is empty when opening the create order form.
-
-**Root Cause**: The Vite proxy configuration is correct, but the development server needs to be restarted for the proxy to take effect.
-
-**Solution**:
-- The proxy configuration in `admin/vite.config.ts` is already set up correctly:
-```typescript
-proxy: {
- '/api-proxy': {
- target: 'https://admin.zhenyangtang.com.cn',
- changeOrigin: true,
- rewrite: (path) => path.replace(/^\/api-proxy/, '')
- }
-}
-```
-- The `transformRegionData()` function correctly converts the API format to el-cascader format
-- **ACTION REQUIRED**: Restart the development server
-
-### 2. Dosage Dropdown Not Showing Selected Value on First Load
-**Problem**: When editing a prescription, the dosage dropdown (用量) appears empty on first load, but shows correctly when reopening the dialog.
-
-**Root Cause**: Timing issue with the `isLoadingData` flag. The `nextTick()` callback was executing before all data was fully rendered.
-
-**Solution**: Changed from `nextTick()` to `setTimeout(..., 0)` to ensure all data is rendered before re-enabling the watch function.
-
-**Changed in**: `admin/src/views/consumer/prescription/index.vue`
-```typescript
-// Before:
-nextTick(() => {
- isLoadingData.value = false
-})
-
-// After:
-setTimeout(() => {
- isLoadingData.value = false
-}, 0)
-```
-
-### 3. Dosage Fields Not Saving to Database
-**Problem**: User reported that dosage fields (用量、单位、代煎、每贴出包数) are not being saved to the database.
-
-**Analysis**:
-- ✅ Backend validation (`PrescriptionValidate.php`) already includes all dosage fields in `sceneAdd()` and `sceneEdit()`
-- ✅ Frontend component (`tcm-prescription/index.vue`) includes all fields in `handleSave()`
-- ✅ Frontend prescription list view (`prescription/index.vue`) includes all fields in form submission
-- ✅ Watch function properly resets values when prescription type changes
-- ✅ `isLoadingData` flag prevents watch from overwriting loaded data
-
-**Verification Needed**:
-1. Check if database migration was executed: `add_prescription_dosage_fields.sql`
-2. Check browser console for any API errors when saving
-3. Verify that the API response shows the fields were saved
-
-## Files Modified
-
-### 1. admin/src/views/consumer/prescription/index.vue
-- Changed `nextTick()` to `setTimeout(..., 0)` in `handleEdit()` function (line ~2187)
-
-## Files Already Correct (No Changes Needed)
-
-### 1. admin/vite.config.ts
-- Proxy configuration is correct
-
-### 2. admin/src/views/consumer/prescription/index.vue
-- `transformRegionData()` function correctly converts API format
-- `loadRegionData()` function properly fetches and transforms data
-- Watch function includes `isLoadingData` flag to prevent overwriting
-- All dosage fields are included in form submission
-
-### 3. admin/src/components/tcm-prescription/index.vue
-- All dosage fields are included in `handleSave()`
-- Watch function properly handles prescription type changes
-- bags_per_dose field is displayed for 饮片 type
-
-### 4. server/app/adminapi/validate/tcm/PrescriptionValidate.php
-- All dosage fields are included in `sceneAdd()` and `sceneEdit()`
-
-## Testing Steps
-
-### Test 1: Region Selector
-1. **Restart the development server**:
- ```bash
- # Stop current server (Ctrl+C)
- cd admin
- npm run dev
- ```
-2. Open the create order form (创建订单)
-3. Click on the "省市区" cascader
-4. Verify that all provinces are displayed
-5. Select a province and verify cities are displayed
-6. Select a city and verify districts are displayed
-7. Check browser console for any errors
-
-### Test 2: Dosage Dropdown Display
-1. Create a new prescription with type "饮片" and dosage "100ml"
-2. Save the prescription
-3. **Refresh the page** (F5)
-4. Edit the same prescription
-5. Verify that the dosage dropdown shows "100ml" immediately on first load
-6. Close and reopen the edit dialog
-7. Verify it still shows "100ml"
-
-### Test 3: Dosage Fields Saving
-1. Create a new prescription:
- - Type: 饮片
- - Dosage: 150ml
- - Decoction: 代煎
- - Bags per dose: 3包
-2. Save the prescription
-3. Check browser Network tab for the API request payload
-4. Verify the payload includes:
- ```json
- {
- "dosage_amount": 150,
- "dosage_unit": "ml",
- "need_decoction": true,
- "bags_per_dose": 3
- }
- ```
-5. Refresh the page and edit the prescription
-6. Verify all fields display the saved values
-
-### Test 4: Different Prescription Types
-1. Test 浓缩水丸:
- - Dosage: 1-10g dropdown
- - No decoction option
- - No bags_per_dose field
-2. Test 饮片:
- - Dosage: 50-250ml dropdown
- - Decoction radio buttons (代煎/不代煎)
- - Bags per dose: 1-9包 dropdown
-3. Test 其他类型 (颗粒、丸剂、散剂、膏方、汤剂):
- - Dosage: Free input with decimal support
- - No decoction option
- - No bags_per_dose field
-
-## Database Verification
-
-If dosage fields are still not saving, verify the database schema:
-
-```sql
--- Check if columns exist
-DESCRIBE tcm_prescription;
-
--- Should show these columns:
--- dosage_amount (decimal or varchar)
--- dosage_unit (varchar)
--- need_decoction (tinyint)
--- bags_per_dose (int)
--- times_per_day (int)
-
--- If columns are missing, run the migration:
-SOURCE add_prescription_dosage_fields.sql;
-```
-
-## Troubleshooting
-
-### Region Selector Still Empty After Restart
-1. Check browser console for CORS errors
-2. Verify the proxy is working:
- ```bash
- # In browser console:
- fetch('/api-proxy/area/ChinaCitys.json').then(r => r.json()).then(console.log)
- ```
-3. Check if the API endpoint is accessible:
- ```bash
- curl https://admin.zhenyangtang.com.cn/area/ChinaCitys.json
- ```
-
-### Dosage Dropdown Still Empty on First Load
-1. Check browser console for errors
-2. Verify `dosage_amount` is a number in the database (not string)
-3. Add console.log in `handleEdit()` to debug:
- ```typescript
- console.log('dosage_amount from DB:', row.dosage_amount, typeof row.dosage_amount)
- console.log('dosage_amount after conversion:', editForm.dosage_amount, typeof editForm.dosage_amount)
- ```
-
-### Dosage Fields Not Saving
-1. Check browser Network tab for API errors
-2. Verify database migration was executed
-3. Check backend logs for validation errors
-4. Verify the API endpoint is receiving the fields:
- ```php
- // In PrescriptionLogic.php
- Log::write('Prescription params: ' . json_encode($params));
- ```
-
-## Summary
-
-- ✅ Region selector: Proxy configured, needs server restart
-- ✅ Dosage dropdown: Fixed timing issue with `setTimeout`
-- ✅ Dosage fields saving: All code is correct, verify database migration
-- ✅ bags_per_dose: Already implemented in both component and list view
diff --git a/REGION_SELECTOR_FEATURE.md b/REGION_SELECTOR_FEATURE.md
deleted file mode 100644
index b74e3a81..00000000
--- a/REGION_SELECTOR_FEATURE.md
+++ /dev/null
@@ -1,357 +0,0 @@
-# 省市区选择器功能说明
-
-## 功能描述
-
-在创建处方业务订单时,添加省市区级联选择器,方便用户选择收货地址的省市区信息。
-
-## 修改内容
-
-### 1. 表单界面(admin/src/views/consumer/prescription/index.vue)
-
-#### 添加省市区选择器(第 792-810 行)
-
-```vue
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-**组件配置说明:**
-- `v-model="createOrderForm.region"`:绑定选中的省市区数组
-- `:options="regionOptions"`:省市区数据源
-- `value: 'name'`:使用 name 字段作为值
-- `label: 'name'`:使用 name 字段作为显示文本
-- `emitPath: false`:只返回最后一级的值(区/县)
-- `checkStrictly: false`:必须选择到最后一级
-- `clearable`:可清空
-- `filterable`:可搜索
-
-### 2. 数据模型更新(第 1175-1195 行)
-
-```typescript
-const createOrderForm = reactive({
- patient_id: '' as number | string,
- recipient_name: '',
- recipient_phone: '',
- region: [] as string[], // 新增:省市区数组
- shipping_address: '', // 改为详细地址
- // ... 其他字段
-})
-```
-
-### 3. 省市区数据(第 1195-1340 行)
-
-添加了常用省市区数据:
-- 四川省(成都市、绵阳市)
-- 北京市
-- 上海市
-- 广东省(广州市、深圳市)
-
-```typescript
-const regionOptions = ref([
- {
- name: '四川省',
- children: [
- {
- name: '成都市',
- children: [
- { name: '锦江区' },
- { name: '青羊区' },
- // ... 更多区县
- ]
- }
- ]
- },
- // ... 更多省份
-])
-```
-
-### 4. 事件处理方法
-
-```typescript
-const handleRegionChange = (value: string[]) => {
- console.log('选择的省市区:', value)
-}
-```
-
-## 使用说明
-
-### 用户操作流程
-
-1. 点击"创建业务订单"按钮
-2. 选择诊单患者
-3. 填写收货人和收货手机
-4. **点击"省市区"选择器**
-5. 依次选择:省 → 市 → 区/县
-6. 在"详细地址"中填写街道、门牌号等信息
-7. 填写其他订单信息
-8. 提交订单
-
-### 界面效果
-
-**编辑时:**
-```
-┌─────────────────────────────────────────────────┐
-│ 收货人: [张三] 收货手机: [138****0000] │
-├─────────────────────────────────────────────────┤
-│ 省市区: [四川省 / 成都市 / 双流区 ▼] │
-├─────────────────────────────────────────────────┤
-│ 详细地址: │
-│ ┌─────────────────────────────────────────────┐ │
-│ │ 黄甲街道黄龙大道二段280号 │ │
-│ └─────────────────────────────────────────────┘ │
-└─────────────────────────────────────────────────┘
-```
-
-**完整地址组合:**
-```
-四川省成都市双流区黄甲街道黄龙大道二段280号
-```
-
-## 后端支持
-
-### 数据库字段建议
-
-如果需要单独存储省市区信息,可以添加以下字段:
-
-```sql
--- 在 prescription_order 表中添加省市区字段
-ALTER TABLE `zyt_prescription_order`
-ADD COLUMN `province` VARCHAR(50) DEFAULT '' COMMENT '省份' AFTER `recipient_phone`,
-ADD COLUMN `city` VARCHAR(50) DEFAULT '' COMMENT '城市' AFTER `province`,
-ADD COLUMN `district` VARCHAR(50) DEFAULT '' COMMENT '区/县' AFTER `city`;
-
--- 或者保持现有的 shipping_address 字段,前端组合完整地址
-```
-
-### 后端 API 修改
-
-#### 方案 A:前端组合完整地址(推荐)
-
-前端在提交时将省市区和详细地址组合:
-
-```typescript
-const submitOrder = async () => {
- const fullAddress = createOrderForm.region.join('') + createOrderForm.shipping_address
-
- await createPrescriptionOrder({
- ...createOrderForm,
- shipping_address: fullAddress // 四川省成都市双流区黄甲街道...
- })
-}
-```
-
-#### 方案 B:分别存储省市区
-
-后端接收独立的省市区字段:
-
-```php
-// 创建订单时
-$order->province = $params['province'] ?? '';
-$order->city = $params['city'] ?? '';
-$order->district = $params['district'] ?? '';
-$order->shipping_address = $params['shipping_address'] ?? '';
-
-// 查询时返回
-return [
- 'province' => $order->province,
- 'city' => $order->city,
- 'district' => $order->district,
- 'shipping_address' => $order->shipping_address,
- 'full_address' => $order->province . $order->city . $order->district . $order->shipping_address
-];
-```
-
-## 扩展功能
-
-### 1. 使用完整的省市区数据
-
-当前只包含了部分常用省市区,实际项目中应该使用完整的数据。
-
-**方案 A:使用 npm 包**
-
-```bash
-npm install element-china-area-data
-```
-
-```typescript
-import { regionData } from 'element-china-area-data'
-
-const regionOptions = ref(regionData)
-```
-
-**方案 B:从后端 API 获取**
-
-```typescript
-import { getRegionTree } from '@/api/common'
-
-const regionOptions = ref([])
-
-onMounted(async () => {
- const res = await getRegionTree()
- regionOptions.value = res.data
-})
-```
-
-### 2. 自动填充历史地址
-
-根据患者历史订单自动填充地址:
-
-```typescript
-const loadPatientLastAddress = async (patientId: number) => {
- const res = await getPatientLastOrder(patientId)
- if (res.data) {
- createOrderForm.region = [res.data.province, res.data.city, res.data.district]
- createOrderForm.shipping_address = res.data.detail_address
- createOrderForm.recipient_name = res.data.recipient_name
- createOrderForm.recipient_phone = res.data.recipient_phone
- }
-}
-```
-
-### 3. 地址簿功能
-
-保存常用地址,快速选择:
-
-```vue
-
-
-
-```
-
-### 4. 地址验证
-
-验证地址的完整性和合法性:
-
-```typescript
-const validateAddress = () => {
- if (createOrderForm.region.length !== 3) {
- feedback.msgError('请选择完整的省市区')
- return false
- }
- if (!createOrderForm.shipping_address.trim()) {
- feedback.msgError('请填写详细地址')
- return false
- }
- return true
-}
-```
-
-## 注意事项
-
-1. **数据格式**:
- - `region` 是数组:`['四川省', '成都市', '双流区']`
- - 提交时需要组合成完整地址字符串
-
-2. **必填验证**:
- - 省市区选择器应该设置为必填
- - 详细地址也应该必填
-
-3. **字符限制**:
- - 详细地址限制 200 字符
- - 建议省市区 + 详细地址总长度不超过 250 字符
-
-4. **兼容性**:
- - 兼容现有的 `shipping_address` 字段
- - 旧数据可能没有省市区信息,需要做好兼容处理
-
-5. **性能优化**:
- - 省市区数据较大时,考虑懒加载
- - 使用虚拟滚动优化长列表
-
-## 表单验证规则
-
-更新验证规则:
-
-```typescript
-const createOrderRules: FormRules = {
- // ... 其他规则
- region: [
- {
- required: true,
- message: '请选择省市区',
- trigger: 'change',
- validator: (rule, value, callback) => {
- if (!value || value.length !== 3) {
- callback(new Error('请选择完整的省市区'))
- } else {
- callback()
- }
- }
- }
- ],
- shipping_address: [
- { required: true, message: '请输入详细地址', trigger: 'blur' },
- { min: 5, message: '详细地址至少5个字符', trigger: 'blur' }
- ]
-}
-```
-
-## 测试要点
-
-### 前端测试
-
-1. ✅ 省市区选择器显示正常
-2. ✅ 可以正常选择省市区
-3. ✅ 搜索功能正常
-4. ✅ 清空功能正常
-5. ✅ 必填验证正常
-6. ✅ 数据绑定正确
-
-### 集成测试
-
-1. ✅ 创建订单 → 选择省市区 → 填写详细地址 → 提交成功
-2. ✅ 查看订单 → 地址显示完整
-3. ✅ 编辑订单 → 省市区回显正确
-4. ✅ 打印订单 → 地址格式正确
-
-## 完成状态
-
-- ✅ 前端界面添加完成
-- ✅ 数据模型更新完成
-- ✅ 省市区数据添加完成(简化版)
-- ✅ 事件处理方法添加完成
-- ⏳ 等待完整省市区数据集成
-- ⏳ 等待后端 API 支持
-- ⏳ 等待表单验证规则更新
-
-## 相关文件
-
-- `admin/src/views/consumer/prescription/index.vue` - 处方订单列表页面
-- 后端需要修改的文件(待确认):
- - 订单模型文件
- - 订单控制器文件
- - 数据库迁移文件(如果需要单独存储省市区)
diff --git a/REVOKE_PRESCRIPTION_AUDIT_FIX.md b/REVOKE_PRESCRIPTION_AUDIT_FIX.md
deleted file mode 100644
index 8602d400..00000000
--- a/REVOKE_PRESCRIPTION_AUDIT_FIX.md
+++ /dev/null
@@ -1,226 +0,0 @@
-# 撤回处方审核功能修复
-
-## 问题描述
-撤回处方审核时,只更新了业务订单表(`zyt_tcm_prescription_order`)的 `prescription_audit_status`,但没有同步更新处方表(`zyt_tcm_prescription`)的 `audit_status`,导致处方表的审核状态不一致。
-
-## 影响
-- 业务订单显示"待审核",但处方表仍显示"已通过"
-- 处方列表页面显示的审核状态与实际不符
-- 可能导致业务流程混乱
-
-## 修复方案
-
-### 文件:`server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
-
-在 `revokeRxAudit()` 方法中添加同步处方表审核状态的逻辑:
-
-```php
-// 同步撤回处方表的审核状态
-$prescriptionId = (int) $order->prescription_id;
-if ($prescriptionId > 0) {
- try {
- $prescription = Prescription::where('id', $prescriptionId)->whereNull('delete_time')->find();
- if ($prescription) {
- $prescription->audit_status = 0;
- $prescription->audit_remark = '';
- $prescription->audit_time = null;
- $prescription->audit_by = null;
- $prescription->audit_by_name = '';
- $prescription->save();
- }
- } catch (\Throwable $e) {
- // 记录日志但不影响主流程
- Log::error('撤回处方审核时同步处方表失败: ' . $e->getMessage(), [
- 'prescription_id' => $prescriptionId,
- 'order_id' => $id
- ]);
- }
-}
-```
-
-## 修复内容
-
-### 1. 业务订单表更新(原有逻辑)
-**表**: `zyt_tcm_prescription_order`
-
-```php
-$order->prescription_audit_status = 0; // 重置为待审核
-$order->prescription_audit_remark = ''; // 清空审核意见
-```
-
-### 2. 处方表同步更新(新增逻辑)
-**表**: `zyt_tcm_prescription`
-
-```php
-$prescription->audit_status = 0; // 重置为待审核
-$prescription->audit_remark = ''; // 清空审核意见
-$prescription->audit_time = null; // 清空审核时间
-$prescription->audit_by = null; // 清空审核人ID
-$prescription->audit_by_name = ''; // 清空审核人姓名
-```
-
-## 数据流转
-
-### 撤回前
-```
-业务订单表 (zyt_tcm_prescription_order):
- prescription_audit_status: 1 (已通过)
- prescription_audit_remark: "审核通过"
-
-处方表 (zyt_tcm_prescription):
- audit_status: 1 (已通过)
- audit_remark: "审核通过"
- audit_time: 1713168000
- audit_by: 5
- audit_by_name: "张医生"
-```
-
-### 撤回后
-```
-业务订单表 (zyt_tcm_prescription_order):
- prescription_audit_status: 0 (待审核)
- prescription_audit_remark: ""
-
-处方表 (zyt_tcm_prescription):
- audit_status: 0 (待审核)
- audit_remark: ""
- audit_time: null
- audit_by: null
- audit_by_name: ""
-```
-
-## 审核状态说明
-
-| 状态值 | 说明 |
-|--------|------|
-| 0 | 待审核 |
-| 1 | 已通过 |
-| 2 | 已驳回 |
-
-## 业务流程
-
-```
-创建订单
- ↓
-处方待审核 (audit_status = 0)
- ↓ 审核通过
-处方已通过 (audit_status = 1)
- ↓ 撤回审核
-处方待审核 (audit_status = 0) ← 两个表都重置
- ↓ 重新审核
-处方已通过 (audit_status = 1)
-```
-
-## 错误处理
-
-### 容错设计
-```php
-try {
- // 更新处方表
- $prescription->save();
-} catch (\Throwable $e) {
- // 记录日志但不影响主流程
- Log::error('撤回处方审核时同步处方表失败: ' . $e->getMessage());
-}
-```
-
-**说明**:
-- 即使处方表更新失败,业务订单表的撤回仍然成功
-- 错误会被记录到日志中,便于排查
-- 不会因为处方表问题导致整个撤回操作失败
-
-## 相关表结构
-
-### 业务订单表 (zyt_tcm_prescription_order)
-```sql
-prescription_id int(11) unsigned NOT NULL DEFAULT 0 COMMENT '消费者处方ID'
-prescription_audit_status tinyint(2) unsigned NOT NULL DEFAULT 0 COMMENT '处方审核 0待审1通过2驳回'
-prescription_audit_remark varchar(500) NOT NULL DEFAULT '' COMMENT '处方审核意见'
-```
-
-### 处方表 (zyt_tcm_prescription)
-```sql
-audit_status tinyint(1) NOT NULL DEFAULT 1 COMMENT '审核:0待审核 1已通过 2已驳回'
-audit_time int(10) unsigned DEFAULT NULL COMMENT '审核时间'
-audit_by int(11) unsigned DEFAULT NULL COMMENT '审核人ID'
-audit_by_name varchar(50) NOT NULL DEFAULT '' COMMENT '审核人姓名'
-audit_remark varchar(500) NOT NULL DEFAULT '' COMMENT '审核意见'
-```
-
-## 测试清单
-
-### 正常流程测试
-- [ ] 创建订单,处方待审核
-- [ ] 审核通过处方
-- [ ] 验证业务订单表和处方表状态都为"已通过"
-- [ ] 撤回处方审核
-- [ ] 验证业务订单表和处方表状态都为"待审核"
-- [ ] 验证处方表的审核时间、审核人等字段已清空
-
-### 边界情况测试
-- [ ] 撤回时处方不存在(已删除)
-- [ ] 撤回时处方ID为0
-- [ ] 撤回时数据库连接失败
-- [ ] 验证错误被正确记录到日志
-
-### 权限测试
-- [ ] 无审核权限的用户不能撤回
-- [ ] 有审核权限的用户可以撤回
-- [ ] 订单已结束时不能撤回
-
-## 日志记录
-
-### 成功日志
-```
-操作日志 (zyt_tcm_prescription_order_log):
- action: "revoke_rx_audit"
- summary: "撤回处方审核,状态重置为待审核"
-```
-
-### 错误日志
-```
-系统日志:
- level: ERROR
- message: "撤回处方审核时同步处方表失败: [错误信息]"
- context: {
- "prescription_id": 123,
- "order_id": 456
- }
-```
-
-## 相关功能
-
-### 1. 审核处方
-**方法**: `PrescriptionOrderLogic::auditPrescription()`
-- 同时更新业务订单表和处方表的审核状态
-
-### 2. 撤回处方审核
-**方法**: `PrescriptionOrderLogic::revokeRxAudit()`
-- 同时重置业务订单表和处方表的审核状态(本次修复)
-
-### 3. 撤回支付单审核
-**方法**: `PrescriptionOrderLogic::revokePayAudit()`
-- 只更新业务订单表的支付审核状态
-
-## 注意事项
-
-1. **数据一致性**:确保两个表的审核状态始终保持一致
-2. **容错处理**:处方表更新失败不影响主流程
-3. **日志记录**:所有错误都会被记录,便于排查
-4. **权限控制**:只有有审核权限的用户才能撤回
-5. **状态限制**:订单已结束时不能撤回
-
-## 相关文件
-
-- `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php` - 业务逻辑
-- `server/app/adminapi/controller/tcm/PrescriptionOrderController.php` - 控制器
-- `admin/src/views/consumer/prescription/order_list.vue` - 前端页面
-
-## 总结
-
-✅ **修复前**:撤回审核只更新业务订单表
-✅ **修复后**:撤回审核同时更新业务订单表和处方表
-✅ **容错处理**:处方表更新失败不影响主流程
-✅ **日志记录**:错误会被记录到系统日志
-
-这样可以确保数据一致性,避免审核状态不同步的问题。
diff --git a/SECURITY_FIX_SUMMARY.md b/SECURITY_FIX_SUMMARY.md
deleted file mode 100644
index 1745d88f..00000000
--- a/SECURITY_FIX_SUMMARY.md
+++ /dev/null
@@ -1,278 +0,0 @@
-# 安全修复总结 - 防止绕过修改密码页面
-
-## 问题描述
-
-原始实现中存在两个安全漏洞:
-
-1. **URL 绕过**:用户登录后虽然会跳转到修改密码页面,但可以通过直接在浏览器地址栏输入其他页面 URL 来绕过修改密码页面
-2. **刷新绕过**:页面刷新后,`isPaw` 状态丢失(只存在内存中),导致用户可以直接进入系统
-
-## 安全风险
-
-- 用户可以使用初始密码继续使用系统
-- 无法强制用户修改弱密码
-- 违反安全策略要求
-
-## 修复方案
-
-### 1. 后端返回 is_paw 字段
-
-在 `AdminLogic::detail()` 方法中添加 `is_paw` 字段:
-
-```php
-$admin = Admin::field([
- 'id', 'account', 'name', 'disable', 'root',
- 'multipoint_login', 'avatar', 'is_paw', // 添加此字段
- // ... 其他字段
-])->findOrEmpty($params['id'])->toArray();
-```
-
-### 2. 状态管理
-
-在 Vuex store 中添加 `isPaw` 状态:
-
-```typescript
-export interface UserState {
- token: string
- userInfo: Record
- routes: RouteRecordRaw[]
- perms: string[]
- isPaw: number // 0=需要修改密码,1=正常
-}
-```
-
-登录时保存 `isPaw` 状态:
-
-```typescript
-login(playload: any) {
- return new Promise((resolve, reject) => {
- login({ account: account.trim(), password: password })
- .then((data) => {
- this.token = data.token
- this.isPaw = data.is_paw ?? 1 // 保存状态
- cache.set(TOKEN_KEY, data.token)
- resolve(data)
- })
- })
-}
-```
-
-### 2. 状态管理
-
-在 Vuex store 中添加 `isPaw` 状态:
-
-```typescript
-export interface UserState {
- token: string
- userInfo: Record
- routes: RouteRecordRaw[]
- perms: string[]
- isPaw: number // 0=需要修改密码,1=正常
-}
-```
-
-登录时保存 `isPaw` 状态:
-
-```typescript
-login(playload: any) {
- return new Promise((resolve, reject) => {
- login({ account: account.trim(), password: password })
- .then((data) => {
- this.token = data.token
- this.isPaw = data.is_paw ?? 1 // 保存状态
- cache.set(TOKEN_KEY, data.token)
- resolve(data)
- })
- })
-}
-```
-
-获取用户信息时更新 `isPaw` 状态(解决刷新问题):
-
-```typescript
-getUserInfo() {
- return new Promise((resolve, reject) => {
- getUserInfo()
- .then((data) => {
- this.userInfo = data.user
- this.perms = data.permissions
- this.routes = filterAsyncRoutes(data.menu)
- this.isPaw = data.user.is_paw ?? 1 // 从后端获取最新状态
- resolve(data)
- })
- })
-}
-```
-
-### 3. 路由守卫
-
-在 `permission.ts` 中添加路由守卫逻辑:
-
-```typescript
-router.beforeEach(async (to, from, next) => {
- const userStore = useUserStore()
-
- // 特殊处理:修改密码页面
- if (to.path === changePasswordPath) {
- if (!userStore.token) {
- // 未登录,跳转到登录页
- next({ path: loginPath })
- return
- }
- if (userStore.isPaw === 1) {
- // 已经修改过密码,跳转到首页
- next({ path: defaultPath })
- return
- }
- // 需要修改密码,允许访问
- next()
- return
- }
-
- // 其他页面:检查是否需要修改密码
- if (userStore.token && userStore.isPaw === 0) {
- // 需要修改密码,强制跳转到修改密码页面
- next({ path: changePasswordPath })
- return
- }
-
- // ... 其他路由逻辑
-})
-```
-
-### 3. 路由守卫
-
-在 `permission.ts` 中添加路由守卫逻辑,关键是在获取用户信息后再检查 `isPaw`:
-
-```typescript
-router.beforeEach(async (to, from, next) => {
- const userStore = useUserStore()
-
- if (userStore.token) {
- const hasGetUserInfo = Object.keys(userStore.userInfo).length !== 0
-
- if (hasGetUserInfo) {
- // 已经获取过用户信息,检查是否需要修改密码
- if (userStore.isPaw === 0) {
- next({ path: changePasswordPath })
- return
- }
- // ... 其他逻辑
- } else {
- // 首次获取用户信息
- await userStore.getUserInfo()
-
- // 获取用户信息后,检查是否需要修改密码
- if (userStore.isPaw === 0) {
- next({ path: changePasswordPath })
- return
- }
- // ... 其他逻辑
- }
- }
-})
-```
-
-### 4. 防护机制
-
-### 4. 防护机制
-
-#### 4.1 强制跳转
-- 当 `isPaw = 0` 时,访问任何页面都会被拦截
-- 自动跳转到修改密码页面
-
-#### 4.2 URL 防护
-- 即使直接输入 URL,路由守卫也会拦截
-- 无法绕过修改密码页面
-
-#### 4.3 状态验证
-- 修改密码页面需要登录才能访问
-- 已修改密码的用户无法再次访问该页面
-
-#### 4.4 刷新防护
-- 页面刷新时重新从后端获取 `is_paw` 状态
-- 确保状态始终与数据库一致
-
-#### 4.5 多标签页防护
-- 所有标签页共享同一个 store 状态
-- 新打开的标签页也会被拦截
-
-## 修复后的安全流程
-
-```
-用户登录 (is_paw = 0)
- ↓
-路由守卫检查 isPaw
- ↓
-强制跳转到修改密码页面
- ↓
-用户尝试访问其他页面(输入 URL)
- ↓
-路由守卫再次检查 isPaw
- ↓
-再次强制跳转到修改密码页面
- ↓
-用户修改密码成功
- ↓
-isPaw 更新为 1
- ↓
-退出登录
- ↓
-使用新密码重新登录
- ↓
-路由守卫检查 isPaw = 1
- ↓
-允许正常访问系统
-```
-
-## 测试验证
-
-### 测试场景 1:直接输入 URL
-1. 使用 `is_paw = 0` 的账号登录
-2. 在地址栏输入 `/workbench/index`
-3. 结果:自动跳转回 `/change-password`
-
-### 测试场景 2:多标签页
-1. 使用 `is_paw = 0` 的账号登录
-2. 打开新标签页访问其他页面
-3. 结果:新标签页也被跳转到修改密码页面
-
-### 测试场景 3:页面刷新
-1. 在修改密码页面刷新浏览器
-2. 结果:仍然停留在修改密码页面
-
-### 测试场景 4:已修改密码的用户
-1. 使用 `is_paw = 1` 的账号登录
-2. 尝试访问 `/change-password`
-3. 结果:自动跳转到首页
-
-## 相关文件
-
-### 修改的文件
-- `admin/src/stores/modules/user.ts` - 添加 isPaw 状态管理
-- `admin/src/permission.ts` - 添加路由守卫逻辑
-- `admin/src/views/account/change-password.vue` - 更新 isPaw 状态
-
-### 新增的文件
-- `TEST_CHECKLIST.md` - 测试清单
-
-## 安全等级
-
-修复前:⚠️ 低(可绕过)
-修复后:✅ 高(无法绕过)
-
-## 建议
-
-1. 定期审计管理员账号的 `is_paw` 状态
-2. 为新创建的管理员默认设置 `is_paw = 0`
-3. 定期要求管理员修改密码(批量设置 `is_paw = 0`)
-4. 考虑添加密码强度验证(大小写、数字、特殊字符)
-5. 考虑添加密码历史记录,防止重复使用旧密码
-
-## 完成时间
-
-2024-XX-XX
-
-## 测试状态
-
-✅ 已通过所有测试场景
diff --git a/SERVICE_PACKAGE_MULTI_SELECT.md b/SERVICE_PACKAGE_MULTI_SELECT.md
deleted file mode 100644
index 8f3afaf4..00000000
--- a/SERVICE_PACKAGE_MULTI_SELECT.md
+++ /dev/null
@@ -1,293 +0,0 @@
-# Service Package Multi-Select Implementation (服务套餐多选实现)
-
-## Changes Made
-
-Updated the service package field from a simple text input to a multi-select dropdown with data loaded from the dictionary API.
-
-### 1. Added Dictionary API Import
-**File**: `admin/src/views/consumer/prescription/order_list.vue`
-
-```typescript
-import { getDictData } from '@/api/app'
-```
-
-### 2. Added Service Package Options State
-```typescript
-// 服务套餐选项
-const servicePackageOptions = ref>([])
-```
-
-### 3. Added Loading Function
-```typescript
-// 加载服务套餐选项
-const loadServicePackageOptions = async () => {
- try {
- const data = await getDictData({ type: 'server_order' })
- servicePackageOptions.value = (data?.server_order || []).filter((item: any) => item.status !== 0)
- console.log('服务套餐选项已加载:', servicePackageOptions.value.length)
- } catch (error) {
- console.error('加载服务套餐选项失败:', error)
- servicePackageOptions.value = []
- }
-}
-```
-
-### 4. Updated onMounted Hook
-```typescript
-onMounted(async () => {
- await loadRegionData()
- await loadServicePackageOptions() // Added
- getLists()
-})
-```
-
-### 5. Changed Form Field Type
-Changed `editForm.service_package` from string to array:
-```typescript
-// Before:
-service_package: '',
-
-// After:
-service_package: [] as string[],
-```
-
-### 6. Updated Edit Form UI
-Changed from text input to multi-select:
-
-**Before:**
-```vue
-
-```
-
-**After:**
-```vue
-
-
-
-```
-
-### 7. Updated Data Loading Logic
-Added logic to handle both array and string formats when loading data:
-
-```typescript
-// 处理服务套餐:如果是字符串,转换为数组
-if (d.service_package) {
- if (Array.isArray(d.service_package)) {
- editForm.service_package = d.service_package
- } else if (typeof d.service_package === 'string') {
- editForm.service_package = d.service_package.split(',').filter(v => v.trim() !== '')
- } else {
- editForm.service_package = []
- }
-} else {
- editForm.service_package = []
-}
-```
-
-### 8. Updated Form Submission
-Convert array back to comma-separated string for backend:
-
-```typescript
-service_package: Array.isArray(editForm.service_package) ? editForm.service_package.join(',') : '',
-```
-
-### 9. Added Display Formatter
-Added `formatServicePackage()` function to display service packages in detail view:
-
-```typescript
-// 格式化服务套餐显示
-function formatServicePackage(value: any): string {
- if (!value) return '—'
-
- let packages: string[] = []
- if (Array.isArray(value)) {
- packages = value
- } else if (typeof value === 'string') {
- packages = value.split(',').filter(v => v.trim() !== '')
- }
-
- if (packages.length === 0) return '—'
-
- // 将值转换为名称
- const names = packages.map(val => {
- const option = servicePackageOptions.value.find(opt => opt.value === val)
- return option ? option.name : val
- })
-
- return names.join('、')
-}
-```
-
-### 10. Updated Detail Display
-```vue
-
- {{ formatServicePackage(detailData.service_package) }}
-
-```
-
-## Data Flow
-
-### Loading Options:
-1. Component mounts
-2. `loadServicePackageOptions()` calls API: `/adminapi/config/dict?type=server_order`
-3. Filters out disabled options (status !== 0)
-4. Stores in `servicePackageOptions`
-
-### Editing Order:
-1. User opens edit dialog
-2. Backend returns `service_package` as string: `"package1,package2,package3"`
-3. Frontend splits string into array: `['package1', 'package2', 'package3']`
-4. Multi-select displays selected options
-5. User can add/remove selections
-6. On save, array is joined back to string: `"package1,package2,package3"`
-7. Backend receives comma-separated string
-
-### Displaying in Detail:
-1. Backend returns `service_package` as string
-2. `formatServicePackage()` splits string into array
-3. Maps values to display names using `servicePackageOptions`
-4. Joins names with Chinese comma: `"套餐A、套餐B、套餐C"`
-
-## UI Features
-
-### Multi-Select Features:
-- **Multiple Selection**: Users can select multiple service packages
-- **Collapse Tags**: Selected items are collapsed with a count badge when many are selected
-- **Collapse Tags Tooltip**: Hover over collapsed tags to see all selections
-- **Clearable**: Users can clear all selections with one click
-- **Searchable**: Built-in search functionality (default for el-select)
-
-### Display Format:
-- Detail view: `套餐A、套餐B、套餐C` (Chinese comma separator)
-- Edit form: Tag-style display with collapse for many items
-- Empty state: Shows `—` when no packages selected
-
-## Backend Compatibility
-
-### Database Storage:
-The backend stores service packages as a comma-separated string in the database:
-```
-service_package: "package1,package2,package3"
-```
-
-### API Response:
-The backend returns the string as-is, and the frontend handles the conversion.
-
-### API Request:
-The frontend sends the string format, maintaining backward compatibility.
-
-## Testing Steps
-
-### 1. Test Options Loading
-1. Open order list page
-2. Check browser console for: `服务套餐选项已加载: X`
-3. Open edit dialog
-4. Click on service package dropdown
-5. Verify options are displayed
-
-### 2. Test Multi-Selection
-1. Edit an order
-2. Click service package dropdown
-3. Select multiple packages
-4. Verify tags appear below the dropdown
-5. Select many packages to test collapse behavior
-6. Hover over collapsed tags to see tooltip
-
-### 3. Test Saving
-1. Select multiple service packages
-2. Save the order
-3. Check browser Network tab
-4. Verify payload contains: `service_package: "value1,value2,value3"`
-5. Verify database is updated
-
-### 4. Test Display
-1. Open order detail view
-2. Check "服务套餐" field
-3. Verify it shows: `套餐A、套餐B、套餐C`
-4. Test with orders that have:
- - Multiple packages
- - Single package
- - No packages (should show `—`)
-
-### 5. Test Backward Compatibility
-1. Edit an old order that has service_package as string
-2. Verify it loads correctly into the multi-select
-3. Verify you can modify and save
-
-### 6. Test Clear Functionality
-1. Edit an order with service packages selected
-2. Click the clear button (×) on the select
-3. Verify all selections are cleared
-4. Save and verify database is updated to empty string
-
-## Dictionary API Format
-
-The API endpoint `/adminapi/config/dict?type=server_order` should return:
-
-```json
-{
- "server_order": [
- {
- "name": "套餐A",
- "value": "package_a",
- "status": 1
- },
- {
- "name": "套餐B",
- "value": "package_b",
- "status": 1
- },
- {
- "name": "套餐C (已停用)",
- "value": "package_c",
- "status": 0
- }
- ]
-}
-```
-
-**Note**: Options with `status: 0` are filtered out and not displayed.
-
-## Related Files
-
-### Frontend:
-- `admin/src/views/consumer/prescription/order_list.vue`
- - Added `getDictData` import
- - Added `servicePackageOptions` state
- - Added `loadServicePackageOptions()` function
- - Updated `editForm.service_package` to array type
- - Updated edit form UI to multi-select
- - Added data loading/saving conversion logic
- - Added `formatServicePackage()` display function
- - Updated detail display
-
-### Backend:
-- `/adminapi/config/dict` endpoint (should already exist)
-- Dictionary data for `type=server_order`
-
-## Summary
-
-✅ Changed from text input to multi-select dropdown
-✅ Loads options from dictionary API
-✅ Supports multiple selection with tag display
-✅ Collapse tags for better UI when many items selected
-✅ Converts between array (frontend) and string (backend)
-✅ Displays formatted names in detail view
-✅ Backward compatible with existing string data
-✅ Filters out disabled options
-✅ Clearable and searchable
-
-The service package field now provides a better user experience with predefined options and multi-selection capability!
diff --git a/SHIPPING_REGION_FEATURE.md b/SHIPPING_REGION_FEATURE.md
deleted file mode 100644
index 45911cc3..00000000
--- a/SHIPPING_REGION_FEATURE.md
+++ /dev/null
@@ -1,179 +0,0 @@
-# 收货地址省市区功能实现
-
-## 概述
-为处方业务订单编辑表单添加省/市/区选择器,将地址信息结构化存储。
-
-## 数据库变更
-
-### 新增字段
-在 `zyt_tcm_prescription_order` 表中添加三个字段:
-
-```sql
-ALTER TABLE `zyt_tcm_prescription_order`
-ADD COLUMN `shipping_province` varchar(50) NOT NULL DEFAULT '' COMMENT '收货省份' AFTER `recipient_phone`,
-ADD COLUMN `shipping_city` varchar(50) NOT NULL DEFAULT '' COMMENT '收货城市' AFTER `shipping_province`,
-ADD COLUMN `shipping_district` varchar(50) NOT NULL DEFAULT '' COMMENT '收货区县' AFTER `shipping_city`;
-```
-
-**执行方式:**
-```bash
-mysql -u用户名 -p数据库名 < add_shipping_region_fields.sql
-```
-
-## 后端变更
-
-### 文件:`server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
-
-在 `edit()` 方法中添加省市区字段处理:
-
-```php
-// 处理省市区字段
-if (isset($params['shipping_province'])) {
- $order->shipping_province = (string) $params['shipping_province'];
-}
-if (isset($params['shipping_city'])) {
- $order->shipping_city = (string) $params['shipping_city'];
-}
-if (isset($params['shipping_district'])) {
- $order->shipping_district = (string) $params['shipping_district'];
-}
-```
-
-## 前端变更
-
-### 文件:`admin/src/views/consumer/prescription/order_list.vue`
-
-#### 1. 数据结构
-在 `editForm` 中添加 `region` 字段:
-```typescript
-const editForm = reactive({
- // ... 其他字段
- region: [] as string[], // 新增
- shipping_address: '',
- // ...
-})
-```
-
-#### 2. UI 组件
-在收货手机下方添加省市区选择器:
-```vue
-
-
-
-```
-
-#### 3. 数据提交
-在 `submitEdit()` 中将 region 数组拆分为三个字段:
-```typescript
-if (editForm.region && editForm.region.length > 0) {
- payload.shipping_province = editForm.region[0] || ''
- payload.shipping_city = editForm.region[1] || ''
- payload.shipping_district = editForm.region[2] || ''
-} else {
- payload.shipping_province = ''
- payload.shipping_city = ''
- payload.shipping_district = ''
-}
-```
-
-#### 4. 数据加载
-在 `openEdit()` 中从后端数据填充 region:
-```typescript
-const province = d.shipping_province || ''
-const city = d.shipping_city || ''
-const district = d.shipping_district || ''
-if (province || city || district) {
- editForm.region = [province, city, district].filter(v => v !== '')
-} else {
- editForm.region = []
-}
-```
-
-## 省市区数据
-
-当前使用简化的示例数据,包含:
-- 四川省(成都市、绵阳市)
-- 北京市
-- 上海市
-- 广东省(广州市、深圳市)
-
-### 生产环境建议
-
-**方案 1:使用完整数据集**
-- 集成中国行政区划完整数据(约 3000+ 区县)
-- 推荐数据源:
- - [element-china-area-data](https://www.npmjs.com/package/element-china-area-data)
- - [china-division](https://github.com/modood/Administrative-divisions-of-China)
-
-**方案 2:使用 API 服务**
-- 调用第三方地址 API(如高德地图、腾讯地图)
-- 优点:数据实时更新,减少前端体积
-- 缺点:依赖网络,需要 API Key
-
-## 使用流程
-
-1. **执行数据库迁移**
- ```bash
- mysql -u用户名 -p数据库名 < add_shipping_region_fields.sql
- ```
-
-2. **重启后端服务**(如需要)
-
-3. **测试功能**
- - 打开处方业务订单列表
- - 点击"编辑"按钮
- - 选择省/市/区
- - 填写详细地址
- - 保存并验证数据已正确存储
-
-## 数据结构
-
-### 前端存储格式
-```typescript
-region: ['四川省', '成都市', '武侯区']
-```
-
-### 后端存储格式
-```php
-[
- 'shipping_province' => '四川省',
- 'shipping_city' => '成都市',
- 'shipping_district' => '武侯区',
- 'shipping_address' => '天府大道中段123号' // 详细地址
-]
-```
-
-### 数据库存储
-```
-shipping_province: 四川省
-shipping_city: 成都市
-shipping_district: 武侯区
-shipping_address: 天府大道中段123号
-```
-
-## 注意事项
-
-1. **向后兼容**:旧数据的省市区字段为空字符串,不影响现有功能
-2. **可选字段**:省市区选择器可清空,允许用户只填写详细地址
-3. **数据验证**:前端使用 cascader 确保选择的层级关系正确
-4. **字段长度**:省市区字段各限制 50 字符
-
-## 相关文件
-
-- `add_shipping_region_fields.sql` - 数据库迁移文件
-- `admin/src/views/consumer/prescription/order_list.vue` - 前端订单列表页面
-- `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php` - 后端业务逻辑
-
-## 后续优化建议
-
-1. 在订单详情页面也显示省市区信息
-2. 在订单列表的收货人列中显示省市信息
-3. 添加按省市区筛选订单的功能
-4. 集成完整的中国行政区划数据
-5. 考虑添加邮政编码字段
diff --git a/TEST_CHECKLIST.md b/TEST_CHECKLIST.md
deleted file mode 100644
index 6dc7c890..00000000
--- a/TEST_CHECKLIST.md
+++ /dev/null
@@ -1,192 +0,0 @@
-# 首次登录修改密码功能 - 测试清单
-
-## 测试前准备
-
-1. ✅ 执行安装脚本或 SQL,添加 `is_paw` 字段
-2. ✅ 设置测试账号:`UPDATE la_admin SET is_paw = 0 WHERE id = 1;`
-3. ✅ 确认前后端代码已更新
-
-## 测试场景
-
-### 场景 1:正常登录流程
-
-**步骤:**
-1. 使用 `is_paw = 0` 的账号登录
-2. 观察是否自动跳转到修改密码页面
-3. 输入新密码(至少 6 位)
-4. 点击"确认修改"
-5. 观察是否提示"密码修改成功,请重新登录"
-6. 使用新密码重新登录
-7. 观察是否能正常进入系统
-
-**预期结果:**
-- ✅ 登录后自动跳转到修改密码页面
-- ✅ 修改密码成功
-- ✅ 使用新密码能正常登录
-- ✅ 数据库中 `is_paw` 已更新为 1
-
-### 场景 2:URL 防护测试(重要!)
-
-**步骤:**
-1. 使用 `is_paw = 0` 的账号登录
-2. 登录后会跳转到修改密码页面
-3. 在浏览器地址栏直接输入其他页面 URL(如:`/workbench/index`)
-4. 按回车访问
-
-**预期结果:**
-- ✅ 无论输入什么 URL,都会被强制跳转回修改密码页面
-- ✅ 无法绕过修改密码页面访问其他页面
-
-### 场景 3:密码验证测试
-
-**步骤:**
-1. 在修改密码页面输入少于 6 位的密码
-2. 点击"确认修改"
-
-**预期结果:**
-- ✅ 提示"密码长度不能少于6位"
-
-**步骤:**
-1. 两次输入不同的密码
-2. 点击"确认修改"
-
-**预期结果:**
-- ✅ 提示"两次输入的密码不一致"
-
-### 场景 4:已修改密码的用户
-
-**步骤:**
-1. 使用 `is_paw = 1` 的账号登录
-2. 观察是否直接进入系统
-
-**预期结果:**
-- ✅ 直接进入系统,不跳转到修改密码页面
-
-**步骤:**
-1. 在浏览器地址栏输入 `/change-password`
-2. 按回车访问
-
-**预期结果:**
-- ✅ 自动跳转到首页,无法访问修改密码页面
-
-### 场景 5:未登录用户
-
-**步骤:**
-1. 退出登录
-2. 在浏览器地址栏输入 `/change-password`
-3. 按回车访问
-
-**预期结果:**
-- ✅ 自动跳转到登录页面
-
-### 场景 6:企业微信登录
-
-**步骤:**
-1. 设置企业微信绑定的管理员 `is_paw = 0`
-2. 使用企业微信登录
-3. 观察是否跳转到修改密码页面
-
-**预期结果:**
-- ✅ 登录后自动跳转到修改密码页面
-- ✅ 修改密码流程与账号密码登录一致
-
-### 场景 7:页面刷新测试(重要!)
-
-**步骤:**
-1. 使用 `is_paw = 0` 的账号登录
-2. 跳转到修改密码页面后,刷新浏览器(F5 或 Ctrl+R)
-3. 观察页面状态
-
-**预期结果:**
-- ✅ 刷新后仍然停留在修改密码页面
-- ✅ 不会跳转到其他页面
-
-**步骤(进阶测试):**
-1. 使用 `is_paw = 0` 的账号登录
-2. 跳转到修改密码页面
-3. 在地址栏输入其他页面 URL(如 `/workbench/index`)
-4. 按回车
-5. 观察是否跳转回修改密码页面
-6. 刷新浏览器
-7. 观察是否仍然在修改密码页面
-
-**预期结果:**
-- ✅ 输入其他 URL 后被拦截,跳转回修改密码页面
-- ✅ 刷新后仍然在修改密码页面
-- ✅ 无法通过任何方式绕过
-
-### 场景 8:多标签页测试
-
-**步骤:**
-1. 使用 `is_paw = 0` 的账号登录
-2. 跳转到修改密码页面
-3. 打开新标签页,访问系统其他页面
-
-**预期结果:**
-- ✅ 新标签页也会被强制跳转到修改密码页面
-
-## 数据库验证
-
-### 修改前
-```sql
-SELECT id, account, is_paw FROM la_admin WHERE id = 1;
--- 应该显示 is_paw = 0
-```
-
-### 修改后
-```sql
-SELECT id, account, is_paw FROM la_admin WHERE id = 1;
--- 应该显示 is_paw = 1
-```
-
-## 常见问题排查
-
-### 问题 1:登录后没有跳转到修改密码页面
-
-**检查:**
-- 数据库中 `is_paw` 字段是否为 0
-- 前端代码是否正确判断 `is_paw` 字段
-- 浏览器控制台是否有错误
-
-### 问题 2:刷新后可以进入系统
-
-**检查:**
-- 后端 `AdminLogic::detail()` 方法是否返回 `is_paw` 字段
-- 前端 `getUserInfo()` 方法是否更新 `isPaw` 状态
-- 路由守卫是否在获取用户信息后检查 `isPaw`
-- 浏览器控制台查看 API 返回的数据是否包含 `is_paw`
-
-### 问题 3:可以通过 URL 绕过修改密码页面
-
-**检查:**
-- `permission.ts` 路由守卫是否正确配置
-- `userStore.isPaw` 状态是否正确存储
-- 浏览器控制台查看路由守卫是否执行
-
-### 问题 4:修改密码后 `is_paw` 没有更新
-
-**检查:**
-- 后端 `changeFirstPassword` 方法是否正确更新数据库
-- 查看数据库确认字段值
-- 检查后端日志是否有错误
-
-### 问题 5:修改密码后无法登录
-
-**检查:**
-- 密码加密方式是否正确(应使用 `create_password` 函数)
-- 检查后端日志
-- 尝试重置密码
-
-## 测试完成标准
-
-- ✅ 所有 8 个测试场景都通过
-- ✅ 数据库字段正确更新
-- ✅ 无法通过任何方式绕过修改密码页面
-- ✅ 密码验证规则正常工作
-- ✅ 修改密码后能正常登录使用系统
-
-## 性能测试
-
-- ✅ 路由守卫不影响正常用户的访问速度
-- ✅ 登录流程响应时间正常(< 2 秒)
-- ✅ 修改密码操作响应时间正常(< 1 秒)
diff --git a/TRTC_COS_STORAGE_MIGRATION.md b/TRTC_COS_STORAGE_MIGRATION.md
deleted file mode 100644
index 8f22db93..00000000
--- a/TRTC_COS_STORAGE_MIGRATION.md
+++ /dev/null
@@ -1,348 +0,0 @@
-# TRTC 录制存储迁移:VOD → COS
-
-## 变更说明
-
-将 TRTC 云端录制的存储方式从**腾讯云点播(VOD)**改为**对象存储(COS)**。
-
-## 修改内容
-
-### File: `server/app/common/service/TrtcCloudRecordingService.php`
-
-#### 1. 存储配置变更
-
-**Before (VOD):**
-```php
-$tencentVod = new \TencentCloud\Trtc\V20190722\Models\TencentVod();
-$tencentVod->ExpireTime = 0;
-$tencentVod->MediaType = 0;
-$tencentVod->UserDefineRecordId = $prefix;
-$tencentVod->SubAppId = $vodSubApp;
-
-$cloudVod = new \TencentCloud\Trtc\V20190722\Models\CloudVod();
-$cloudVod->TencentVod = $tencentVod;
-
-$storage = new \TencentCloud\Trtc\V20190722\Models\StorageParams();
-$storage->CloudVod = $cloudVod;
-```
-
-**After (COS):**
-```php
-$cloudStorage = new \TencentCloud\Trtc\V20190722\Models\CloudStorage();
-$cloudStorage->Vendor = 0; // 0=腾讯云
-$cloudStorage->Region = 'ap-guangzhou';
-$cloudStorage->Bucket = 'your-bucket-name';
-$cloudStorage->Prefix = 'trtc-recording/';
-
-$storage = new \TencentCloud\Trtc\V20190722\Models\StorageParams();
-$storage->CloudStorage = $cloudStorage;
-```
-
-#### 2. 更新日志信息
-- 日志中标注存储类型为 COS
-- 记录 bucket、region、prefix 等信息
-- 更新提示信息
-
-#### 3. 更新路径清理函数
-- 允许路径中包含 `/` 字符(COS 路径分隔符)
-- 从 `sanitizeVodUserDefineRecordId` 改为支持 COS 路径格式
-
-## 配置要求
-
-### 必需配置项
-
-在 `config/trtc.php` 或 `.env` 中添加以下配置:
-
-```php
-// config/trtc.php
-return [
- // ... 其他配置 ...
-
- // COS 存储配置
- 'recording_cos_vendor' => 0, // 0=腾讯云
- 'recording_cos_region' => env('TRTC_RECORDING_COS_REGION', 'ap-guangzhou'),
- 'recording_cos_bucket' => env('TRTC_RECORDING_COS_BUCKET', ''),
- 'recording_cos_prefix' => env('TRTC_RECORDING_COS_PREFIX', 'trtc-recording/'),
-];
-```
-
-### .env 配置示例
-
-```env
-# TRTC 录制 COS 存储配置
-TRTC_RECORDING_COS_REGION=ap-guangzhou
-TRTC_RECORDING_COS_BUCKET=your-bucket-1234567890
-TRTC_RECORDING_COS_PREFIX=trtc-recording/
-```
-
-## COS Bucket 准备
-
-### 1. 创建 COS 存储桶
-
-1. 登录 [腾讯云 COS 控制台](https://console.cloud.tencent.com/cos)
-2. 创建存储桶
- - 名称:例如 `trtc-recording-1234567890`
- - 地域:选择与 TRTC 应用相同或就近的地域
- - 访问权限:私有读写(推荐)
-
-### 2. 配置 CORS(如需前端访问)
-
-如果需要前端直接访问录制文件,配置 CORS 规则:
-
-```json
-{
- "CORSRules": [
- {
- "AllowedOrigins": ["*"],
- "AllowedMethods": ["GET", "HEAD"],
- "AllowedHeaders": ["*"],
- "ExposeHeaders": [],
- "MaxAgeSeconds": 3600
- }
- ]
-}
-```
-
-### 3. 配置生命周期(可选)
-
-设置自动删除过期录制文件:
-
-- 规则名称:`auto-delete-old-recordings`
-- 应用范围:前缀 `trtc-recording/`
-- 删除策略:创建后 30 天删除
-
-## 存储路径结构
-
-### 默认路径格式
-
-```
-bucket-name/
-└── trtc-recording/
- ├── room_123_20240101_120000.mp4
- ├── room_456_20240101_130000.mp4
- └── ...
-```
-
-### 自定义前缀路径
-
-如果传入 `$vodUserDefineRecordId` 参数:
-
-```
-bucket-name/
-└── custom-prefix/
- ├── file1.mp4
- ├── file2.mp4
- └── ...
-```
-
-## 文件命名规则
-
-COS 存储的文件名由 TRTC 自动生成,通常包含:
-- 房间号
-- 时间戳
-- 用户 ID(单流录制)
-- 文件格式(.mp4)
-
-示例:
-```
-trtc-recording/sdkappid_roomid_userid_timestamp.mp4
-```
-
-## 权限配置
-
-### TRTC 服务需要的 COS 权限
-
-确保 TRTC 服务有权限写入 COS:
-
-1. **方式一:使用主账号密钥**(不推荐生产环境)
- - 使用主账号的 SecretId 和 SecretKey
-
-2. **方式二:使用子账号密钥**(推荐)
- - 创建子账号
- - 授予 COS 写入权限策略:
- ```json
- {
- "version": "2.0",
- "statement": [
- {
- "effect": "allow",
- "action": [
- "cos:PutObject",
- "cos:PostObject",
- "cos:InitiateMultipartUpload",
- "cos:UploadPart",
- "cos:CompleteMultipartUpload"
- ],
- "resource": [
- "qcs::cos:ap-guangzhou:uid/1234567890:your-bucket-1234567890/*"
- ]
- }
- ]
- }
- ```
-
-3. **方式三:使用服务角色**(最佳实践)
- - 为 TRTC 服务创建角色
- - 授予角色 COS 写入权限
- - TRTC 自动使用角色权限
-
-## 对比:VOD vs COS
-
-| 特性 | VOD(点播) | COS(对象存储) |
-|------|------------|----------------|
-| **存储成本** | 较高 | 较低 |
-| **功能** | 媒体处理、转码、播放 | 纯存储 |
-| **访问方式** | 点播 API/播放器 | HTTP/HTTPS 直接访问 |
-| **适用场景** | 需要转码、加密、播放统计 | 简单存储和下载 |
-| **计费** | 存储+流量+转码 | 存储+流量 |
-| **管理** | 媒资管理系统 | 文件管理 |
-
-## 迁移步骤
-
-### 1. 准备 COS 存储桶
-按照上述"COS Bucket 准备"步骤创建并配置存储桶
-
-### 2. 更新配置文件
-在 `.env` 中添加 COS 配置项
-
-### 3. 部署代码
-部署更新后的 `TrtcCloudRecordingService.php`
-
-### 4. 测试录制
-1. 发起一次测试录制
-2. 检查 COS 控制台是否有文件上传
-3. 验证文件可以正常下载和播放
-
-### 5. 监控日志
-查看日志确认录制状态:
-```
-CreateCloudRecording mix (COS)
-DescribeCloudRecording(合流校验-COS)
-```
-
-## 获取录制文件
-
-### 方式一:COS 控制台
-1. 登录 COS 控制台
-2. 进入对应存储桶
-3. 浏览 `trtc-recording/` 目录
-4. 下载或获取文件 URL
-
-### 方式二:COS SDK
-```php
-use Qcloud\Cos\Client;
-
-$cosClient = new Client([
- 'region' => 'ap-guangzhou',
- 'credentials' => [
- 'secretId' => 'your-secret-id',
- 'secretKey' => 'your-secret-key',
- ],
-]);
-
-// 列出录制文件
-$result = $cosClient->listObjects([
- 'Bucket' => 'your-bucket-1234567890',
- 'Prefix' => 'trtc-recording/',
-]);
-
-foreach ($result['Contents'] as $file) {
- echo $file['Key'] . "\n";
-}
-```
-
-### 方式三:生成临时访问 URL
-```php
-$url = $cosClient->getObjectUrl(
- 'your-bucket-1234567890',
- 'trtc-recording/file.mp4',
- '+10 minutes' // URL 有效期
-);
-```
-
-## 回调通知
-
-### COS 事件通知
-可以配置 COS 事件通知,在文件上传完成时触发回调:
-
-1. 在 COS 控制台配置事件通知
-2. 选择事件类型:`cos:ObjectCreated:*`
-3. 配置回调 URL
-4. 接收通知并处理
-
-### 回调数据示例
-```json
-{
- "Records": [
- {
- "event": {
- "eventName": "cos:ObjectCreated:Put",
- "eventTime": "2024-01-01T12:00:00Z"
- },
- "cos": {
- "cosObject": {
- "key": "trtc-recording/room_123_20240101_120000.mp4",
- "size": 12345678,
- "url": "https://bucket.cos.ap-guangzhou.myqcloud.com/..."
- }
- }
- }
- ]
-}
-```
-
-## 故障排查
-
-### 问题1:录制文件未上传到 COS
-**检查项:**
-- COS Bucket 名称是否正确
-- Region 是否匹配
-- TRTC 服务是否有 COS 写入权限
-- 查看日志中的错误信息
-
-### 问题2:无法访问录制文件
-**检查项:**
-- Bucket 访问权限设置
-- 文件路径是否正确
-- 是否需要签名 URL
-
-### 问题3:录制状态一直是 Idle
-**检查项:**
-- RoomIdType 是否与客户端一致
-- 房间内是否有用户推流
-- 网络连接是否正常
-
-## 成本估算
-
-### COS 存储成本(以广州地域为例)
-
-**存储费用:**
-- 标准存储:0.118 元/GB/月
-- 低频存储:0.08 元/GB/月
-
-**流量费用:**
-- 外网下行流量:0.5 元/GB
-- CDN 回源流量:0.15 元/GB
-
-**示例计算:**
-- 每天录制 10 小时,每小时 500MB
-- 月存储量:10 × 30 × 0.5 = 150GB
-- 月存储费用:150 × 0.118 = 17.7 元
-- 月流量(假设下载 50GB):50 × 0.5 = 25 元
-- **月总费用:约 42.7 元**
-
-## 相关文档
-
-- [TRTC 云端录制](https://cloud.tencent.com/document/product/647/16823)
-- [COS 对象存储](https://cloud.tencent.com/document/product/436)
-- [COS PHP SDK](https://cloud.tencent.com/document/product/436/12266)
-
-## 总结
-
-✅ 存储方式从 VOD 改为 COS
-✅ 降低存储成本
-✅ 简化文件管理
-✅ 支持自定义路径前缀
-✅ 更新日志和提示信息
-✅ 需要配置 COS Bucket 和权限
-
-迁移到 COS 后,录制文件将直接存储到对象存储桶,成本更低,管理更简单!
diff --git a/clear_cache.php b/clear_cache.php
deleted file mode 100644
index 24aaa7eb..00000000
--- a/clear_cache.php
+++ /dev/null
@@ -1,41 +0,0 @@
-";
- echo "请刷新页面重试。";
-} else {
- echo "缓存目录不存在:" . $cacheDir;
-}
-?>
diff --git a/deploy_gancao_fix.sh b/deploy_gancao_fix.sh
deleted file mode 100644
index 174cc96b..00000000
--- a/deploy_gancao_fix.sh
+++ /dev/null
@@ -1,113 +0,0 @@
-#!/bin/bash
-
-# 甘草 API 修复部署脚本
-# 使用方法: ./deploy_gancao_fix.sh [server_user@server_ip] [project_path]
-
-set -e
-
-# 颜色定义
-RED='\033[0;31m'
-GREEN='\033[0;32m'
-YELLOW='\033[1;33m'
-NC='\033[0m' # No Color
-
-echo -e "${GREEN}=== 甘草 API 修复部署脚本 ===${NC}\n"
-
-# 检查参数
-if [ $# -lt 2 ]; then
- echo -e "${RED}错误:缺少参数${NC}"
- echo "使用方法: $0 [server_user@server_ip] [project_path]"
- echo "示例: $0 root@192.168.1.100 /www/wwwroot/your-project"
- exit 1
-fi
-
-SERVER=$1
-PROJECT_PATH=$2
-
-echo -e "${YELLOW}服务器:${NC}$SERVER"
-echo -e "${YELLOW}项目路径:${NC}$PROJECT_PATH\n"
-
-# 确认
-read -p "确认部署到以上服务器?(y/n) " -n 1 -r
-echo
-if [[ ! $REPLY =~ ^[Yy]$ ]]; then
- echo "已取消"
- exit 0
-fi
-
-echo -e "\n${GREEN}步骤 1: 上传修复文件${NC}"
-
-# 上传 PHP 文件
-echo "上传 GancaoScmRecipelService.php..."
-scp server/app/common/service/gancao/GancaoScmRecipelService.php \
- $SERVER:$PROJECT_PATH/server/app/common/service/gancao/
-
-echo "上传 GancaoOpenApiTransport.php..."
-scp server/app/common/service/gancao/GancaoOpenApiTransport.php \
- $SERVER:$PROJECT_PATH/server/app/common/service/gancao/
-
-echo "上传 PrescriptionOrderLogic.php..."
-scp server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php \
- $SERVER:$PROJECT_PATH/server/app/adminapi/logic/tcm/
-
-echo "上传 PrescriptionOrderController.php..."
-scp server/app/adminapi/controller/tcm/PrescriptionOrderController.php \
- $SERVER:$PROJECT_PATH/server/app/adminapi/controller/tcm/
-
-# 上传测试脚本
-echo "上传测试脚本..."
-scp test_gancao_config.php $SERVER:$PROJECT_PATH/
-scp test_gancao_ssl.php $SERVER:$PROJECT_PATH/
-scp diagnose_gancao_payload.php $SERVER:$PROJECT_PATH/
-
-echo -e "${GREEN}✓ 文件上传完成${NC}\n"
-
-echo -e "${GREEN}步骤 2: 在服务器上执行操作${NC}"
-
-ssh $SERVER << EOF
- set -e
- cd $PROJECT_PATH
-
- echo "检查文件..."
- ls -la server/app/common/service/gancao/GancaoScmRecipelService.php
- ls -la server/app/common/service/gancao/GancaoOpenApiTransport.php
-
- echo ""
- echo "清除缓存..."
- cd server
- php think clear || true
- rm -rf runtime/cache/* || true
- rm -rf runtime/temp/* || true
- cd ..
-
- echo ""
- echo "运行配置检查..."
- php test_gancao_config.php || true
-
- echo ""
- echo "运行 SSL 测试..."
- php test_gancao_ssl.php || true
-
- echo ""
- echo "重启服务..."
- sudo systemctl restart php-fpm || sudo systemctl restart php7.4-fpm || sudo systemctl restart php8.0-fpm || sudo systemctl restart php8.1-fpm || true
- sudo systemctl restart nginx || true
-
- echo ""
- echo "部署完成!"
-EOF
-
-echo -e "\n${GREEN}=== 部署完成 ===${NC}\n"
-
-echo -e "${YELLOW}下一步:${NC}"
-echo "1. 查看日志:"
-echo " ssh $SERVER 'tail -f $PROJECT_PATH/server/runtime/log/\$(date +%Y%m%d).log | grep -i gancao'"
-echo ""
-echo "2. 测试接口:"
-echo " curl -X POST https://your-domain.com/api/tcm.prescriptionOrder/submitGancaoRecipel \\"
-echo " -H 'Content-Type: application/json' \\"
-echo " -H 'token: your_token' \\"
-echo " -d '{\"id\": 订单ID}'"
-echo ""
-echo "3. 如果仍有问题,查看详细文档:"
-echo " cat GANCAO_FINAL_FIX.md"
diff --git a/diagnose_gancao_payload.php b/diagnose_gancao_payload.php
deleted file mode 100644
index 48aa0bf7..00000000
--- a/diagnose_gancao_payload.php
+++ /dev/null
@@ -1,216 +0,0 @@
-initialize();
-
-echo "=== 甘草负载诊断 ===\n\n";
-
-// 模拟处方数据
-$rx = [
- 'id' => 1,
- 'diagnosis_id' => 1,
- 'patient_name' => '测试患者',
- 'age' => 30,
- 'gender' => 0,
- 'phone' => '13800138000',
- 'clinical_diagnosis' => '中医辨证论治',
- 'doctor_name' => '测试医生',
- 'doctor_phone' => '13900139000',
- 'usage_time' => '饭后半小时服用',
- 'dietary_taboo' => '忌辛辣',
- 'usage_way' => '口服',
- 'usage_instruction' => '温水送服',
- 'usage_notes' => '注意休息',
- 'dose_count' => 7,
- 'herbs' => [
- [
- 'name' => '当归',
- 'dosage' => 10,
- 'gc_id' => 1001,
- 'brief' => '',
- ],
- [
- 'name' => '黄芪',
- 'dosage' => 15,
- 'gc_id' => 1002,
- 'brief' => '',
- ],
- ],
-];
-
-// 模拟订单数据
-$order = [
- 'id' => 1,
- 'recipient_name' => '测试患者',
- 'recipient_phone' => '13800138000',
- 'shipping_address' => '四川省成都市武侯区测试街道123号',
- 'remark_extra' => '测试备注',
-];
-
-echo "1. 测试配置读取:\n";
-$config = think\facade\Config::get('gancao_scm', []);
-
-$criticalFields = ['express_type', 'callback_url', 'cradle_store', 'df_id'];
-foreach ($criticalFields as $field) {
- $value = $config[$field] ?? null;
- $type = gettype($value);
- echo " - {$field}: {$type}";
-
- if ($type === 'array') {
- echo " ❌ 错误!这个字段不应该是数组\n";
- echo " 值: " . json_encode($value, JSON_UNESCAPED_UNICODE) . "\n";
- } else {
- echo " ✓\n";
- }
-}
-
-echo "\n2. 测试 buildPreviewPayload:\n";
-
-try {
- $token = 'test_token_' . time();
- $previewPayload = \app\common\service\gancao\GancaoScmRecipelService::buildPreviewPayload($rx, $order, $token);
-
- echo " ✓ buildPreviewPayload 成功\n";
- echo " 字段数量: " . count($previewPayload) . "\n";
-
- // 检查每个字段的类型
- echo "\n 检查字段类型:\n";
- foreach ($previewPayload as $key => $value) {
- $type = gettype($value);
- echo " - {$key}: {$type}";
-
- if ($type === 'array') {
- echo " (包含 " . count($value) . " 个元素)";
-
- // 检查数组内部是否有问题
- $hasArrayValue = false;
- foreach ($value as $subKey => $subValue) {
- if (is_array($subValue) && !in_array($key, ['m_list', 'df101ext', 'df102ext', 'df103ext', 'df104ext'])) {
- $hasArrayValue = true;
- echo "\n ⚠️ 子字段 '{$subKey}' 是数组: " . json_encode($subValue, JSON_UNESCAPED_UNICODE);
- }
- }
- }
- echo "\n";
- }
-
-} catch (\Throwable $e) {
- echo " ❌ 失败: " . $e->getMessage() . "\n";
- echo " 文件: " . $e->getFile() . ":" . $e->getLine() . "\n";
-}
-
-echo "\n3. 测试 buildSubmitPayload:\n";
-
-try {
- $token = 'test_token_' . time();
- $addrParts = \app\common\service\gancao\GancaoScmRecipelService::splitCnAddress($order['shipping_address']);
- $appNo = 'PO' . $order['id'];
-
- $submitPayload = \app\common\service\gancao\GancaoScmRecipelService::buildSubmitPayload(
- $rx,
- $order,
- $token,
- $addrParts,
- $appNo
- );
-
- echo " ✓ buildSubmitPayload 成功\n";
- echo " 字段数量: " . count($submitPayload) . "\n";
-
- // 检查关键字段
- echo "\n 检查关键字段:\n";
- $keyFields = ['express_type', 'callback_url', 'app_order_no', 'cradle_store', 'express_to', 'patient', 'doctor', 'doct_advice'];
-
- foreach ($keyFields as $field) {
- if (!isset($submitPayload[$field])) {
- echo " - {$field}: ❌ 缺失\n";
- continue;
- }
-
- $value = $submitPayload[$field];
- $type = gettype($value);
- echo " - {$field}: {$type}";
-
- if ($type === 'string') {
- if (strlen($value) > 50) {
- echo " (长度: " . strlen($value) . ")";
- } else {
- echo " = " . var_export($value, true);
- }
- } elseif ($type === 'array') {
- echo " (包含 " . count($value) . " 个元素)";
-
- // 检查数组内部
- foreach ($value as $subKey => $subValue) {
- $subType = gettype($subValue);
- if ($subType === 'array') {
- echo "\n ⚠️ 子字段 '{$subKey}' 是数组(可能有问题)";
- }
- }
- }
- echo "\n";
- }
-
-} catch (\Throwable $e) {
- echo " ❌ 失败: " . $e->getMessage() . "\n";
- echo " 文件: " . $e->getFile() . ":" . $e->getLine() . "\n";
-}
-
-echo "\n4. 测试 JSON 编码:\n";
-
-try {
- $token = 'test_token_' . time();
- $addrParts = \app\common\service\gancao\GancaoScmRecipelService::splitCnAddress($order['shipping_address']);
- $appNo = 'PO' . $order['id'];
-
- $submitPayload = \app\common\service\gancao\GancaoScmRecipelService::buildSubmitPayload(
- $rx,
- $order,
- $token,
- $addrParts,
- $appNo
- );
-
- $json = json_encode($submitPayload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
-
- if ($json === false) {
- echo " ❌ JSON 编码失败: " . json_last_error_msg() . "\n";
- } else {
- echo " ✓ JSON 编码成功\n";
- echo " 大小: " . strlen($json) . " 字节\n";
-
- // 检查是否包含 "Array" 字符串
- if (strpos($json, '"Array"') !== false) {
- echo " ❌ 警告:JSON 中包含 'Array' 字符串,说明有数组被转换为字符串\n";
-
- // 尝试找出是哪个字段
- $decoded = json_decode($json, true);
- echo "\n 查找包含 'Array' 的字段:\n";
- array_walk_recursive($decoded, function($value, $key) {
- if (is_string($value) && $value === 'Array') {
- echo " - 字段 '{$key}' 的值是 'Array'\n";
- }
- });
- } else {
- echo " ✓ JSON 内容正常\n";
- }
-
- // 保存到文件以便检查
- $filename = 'gancao_payload_' . date('YmdHis') . '.json';
- file_put_contents($filename, json_encode($submitPayload, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
- echo " 负载已保存到: {$filename}\n";
- }
-
-} catch (\Throwable $e) {
- echo " ❌ 异常: " . $e->getMessage() . "\n";
- echo " 文件: " . $e->getFile() . ":" . $e->getLine() . "\n";
- echo " 堆栈:\n" . $e->getTraceAsString() . "\n";
-}
-
-echo "\n=== 诊断完成 ===\n";
diff --git a/install_appointment_system.bat b/install_appointment_system.bat
deleted file mode 100644
index bb03d8ff..00000000
--- a/install_appointment_system.bat
+++ /dev/null
@@ -1,123 +0,0 @@
-@echo off
-chcp 65001 >nul
-echo ================================
-echo 医生预约系统安装脚本
-echo ================================
-echo.
-
-REM 检查是否在项目根目录
-if not exist "server" (
- echo 错误: 请在项目根目录运行此脚本
- pause
- exit /b 1
-)
-
-if not exist "admin" (
- echo 错误: 请在项目根目录运行此脚本
- pause
- exit /b 1
-)
-
-echo 步骤 1/4: 检查后端文件...
-set all_exist=1
-
-if exist "server\app\common\model\doctor\Appointment.php" (
- echo √ server\app\common\model\doctor\Appointment.php
-) else (
- echo × server\app\common\model\doctor\Appointment.php ^(不存在^)
- set all_exist=0
-)
-
-if exist "server\app\adminapi\controller\doctor\AppointmentController.php" (
- echo √ server\app\adminapi\controller\doctor\AppointmentController.php
-) else (
- echo × server\app\adminapi\controller\doctor\AppointmentController.php ^(不存在^)
- set all_exist=0
-)
-
-if exist "server\app\adminapi\logic\doctor\AppointmentLogic.php" (
- echo √ server\app\adminapi\logic\doctor\AppointmentLogic.php
-) else (
- echo × server\app\adminapi\logic\doctor\AppointmentLogic.php ^(不存在^)
- set all_exist=0
-)
-
-if exist "server\app\adminapi\validate\doctor\AppointmentValidate.php" (
- echo √ server\app\adminapi\validate\doctor\AppointmentValidate.php
-) else (
- echo × server\app\adminapi\validate\doctor\AppointmentValidate.php ^(不存在^)
- set all_exist=0
-)
-
-if exist "server\app\adminapi\lists\doctor\AppointmentLists.php" (
- echo √ server\app\adminapi\lists\doctor\AppointmentLists.php
-) else (
- echo × server\app\adminapi\lists\doctor\AppointmentLists.php ^(不存在^)
- set all_exist=0
-)
-
-if exist "server\sql\doctor_appointment.sql" (
- echo √ server\sql\doctor_appointment.sql
-) else (
- echo × server\sql\doctor_appointment.sql ^(不存在^)
- set all_exist=0
-)
-
-if %all_exist%==0 (
- echo.
- echo 错误: 部分文件不存在,请检查
- pause
- exit /b 1
-)
-
-echo.
-echo 步骤 2/4: 创建数据库表...
-echo 请手动执行以下SQL文件:
-echo server\sql\doctor_appointment.sql
-echo.
-echo 可以使用以下方式之一:
-echo 1. phpMyAdmin导入
-echo 2. MySQL命令行: mysql -u username -p database ^< server\sql\doctor_appointment.sql
-echo 3. Navicat等数据库工具导入
-echo.
-pause
-
-echo.
-echo 步骤 3/4: 清除缓存...
-cd server
-php think clear
-if %errorlevel%==0 (
- echo √ 缓存清除成功
-) else (
- echo × 缓存清除失败
-)
-cd ..
-
-echo.
-echo 步骤 4/4: 验证安装...
-echo 检查控制器文件...
-if exist "server\app\adminapi\controller\doctor\AppointmentController.php" (
- echo √ AppointmentController.php 存在
-) else (
- echo × AppointmentController.php 不存在
-)
-
-echo.
-echo ================================
-echo 安装完成!
-echo ================================
-echo.
-echo 后续步骤:
-echo 1. 确保医生有排班数据 ^(la_doctor_roster表^)
-echo 2. 在后台权限管理中添加预约相关权限
-echo 3. 测试API接口:
-echo GET /adminapi/doctor.appointment/availableSlots
-echo POST /adminapi/doctor.appointment/create
-echo POST /adminapi/doctor.appointment/cancel
-echo GET /adminapi/doctor.appointment/lists
-echo.
-echo 详细文档请查看:
-echo - server\APPOINTMENT_BACKEND_SETUP.md
-echo - admin\APPOINTMENT_SYSTEM.md
-echo.
-pause
diff --git a/install_appointment_system.sh b/install_appointment_system.sh
deleted file mode 100644
index f1814413..00000000
--- a/install_appointment_system.sh
+++ /dev/null
@@ -1,100 +0,0 @@
-#!/bin/bash
-
-echo "================================"
-echo "医生预约系统安装脚本"
-echo "================================"
-echo ""
-
-# 检查是否在项目根目录
-if [ ! -d "server" ] || [ ! -d "admin" ]; then
- echo "错误: 请在项目根目录运行此脚本"
- exit 1
-fi
-
-echo "步骤 1/4: 检查后端文件..."
-files=(
- "server/app/common/model/doctor/Appointment.php"
- "server/app/adminapi/controller/doctor/AppointmentController.php"
- "server/app/adminapi/logic/doctor/AppointmentLogic.php"
- "server/app/adminapi/validate/doctor/AppointmentValidate.php"
- "server/app/adminapi/lists/doctor/AppointmentLists.php"
- "server/sql/doctor_appointment.sql"
-)
-
-all_exist=true
-for file in "${files[@]}"; do
- if [ -f "$file" ]; then
- echo " ✓ $file"
- else
- echo " ✗ $file (不存在)"
- all_exist=false
- fi
-done
-
-if [ "$all_exist" = false ]; then
- echo ""
- echo "错误: 部分文件不存在,请检查"
- exit 1
-fi
-
-echo ""
-echo "步骤 2/4: 创建数据库表..."
-echo "请输入数据库信息:"
-read -p "数据库主机 (默认: localhost): " db_host
-db_host=${db_host:-localhost}
-
-read -p "数据库用户名: " db_user
-read -sp "数据库密码: " db_pass
-echo ""
-
-read -p "数据库名称: " db_name
-
-echo ""
-echo "正在创建数据库表..."
-mysql -h "$db_host" -u "$db_user" -p"$db_pass" "$db_name" < server/sql/doctor_appointment.sql
-
-if [ $? -eq 0 ]; then
- echo " ✓ 数据库表创建成功"
-else
- echo " ✗ 数据库表创建失败"
- echo " 请手动执行: mysql -h $db_host -u $db_user -p $db_name < server/sql/doctor_appointment.sql"
-fi
-
-echo ""
-echo "步骤 3/4: 清除缓存..."
-cd server
-php think clear
-if [ $? -eq 0 ]; then
- echo " ✓ 缓存清除成功"
-else
- echo " ✗ 缓存清除失败"
-fi
-cd ..
-
-echo ""
-echo "步骤 4/4: 验证安装..."
-echo "检查控制器文件..."
-if [ -f "server/app/adminapi/controller/doctor/AppointmentController.php" ]; then
- echo " ✓ AppointmentController.php 存在"
-else
- echo " ✗ AppointmentController.php 不存在"
-fi
-
-echo ""
-echo "================================"
-echo "安装完成!"
-echo "================================"
-echo ""
-echo "后续步骤:"
-echo "1. 确保医生有排班数据 (la_doctor_roster表)"
-echo "2. 在后台权限管理中添加预约相关权限"
-echo "3. 测试API接口:"
-echo " GET /adminapi/doctor.appointment/availableSlots"
-echo " POST /adminapi/doctor.appointment/create"
-echo " POST /adminapi/doctor.appointment/cancel"
-echo " GET /adminapi/doctor.appointment/lists"
-echo ""
-echo "详细文档请查看:"
-echo " - server/APPOINTMENT_BACKEND_SETUP.md"
-echo " - admin/APPOINTMENT_SYSTEM.md"
-echo ""
diff --git a/install_doctor_fields.bat b/install_doctor_fields.bat
deleted file mode 100644
index 349699b5..00000000
--- a/install_doctor_fields.bat
+++ /dev/null
@@ -1,98 +0,0 @@
-@echo off
-chcp 65001 >nul
-setlocal enabledelayedexpansion
-
-REM 医生字段安装脚本 (Windows版本)
-REM 用法: install_doctor_fields.bat
-
-echo ==========================================
-echo 医生字段安装脚本
-echo ==========================================
-echo.
-
-REM 数据库配置
-set DB_HOST=localhost
-set DB_PORT=3306
-set DB_NAME=zyt
-set DB_USER=root
-
-REM 提示输入数据库密码
-set /p DB_PASS=请输入数据库密码:
-echo.
-
-REM 检查MySQL是否可用
-echo 检查MySQL连接...
-mysql -h%DB_HOST% -P%DB_PORT% -u%DB_USER% -p%DB_PASS% -e "SELECT 1;" >nul 2>&1
-
-if errorlevel 1 (
- echo [错误] 无法连接到MySQL数据库
- echo 请检查数据库配置和密码是否正确
- pause
- exit /b 1
-)
-
-echo [成功] 数据库连接成功
-echo.
-
-REM 备份数据库
-set BACKUP_FILE=zyt_backup_%date:~0,4%%date:~5,2%%date:~8,2%_%time:~0,2%%time:~3,2%%time:~6,2%.sql
-set BACKUP_FILE=%BACKUP_FILE: =0%
-echo 正在备份数据库到: %BACKUP_FILE%
-mysqldump -h%DB_HOST% -P%DB_PORT% -u%DB_USER% -p%DB_PASS% %DB_NAME% > %BACKUP_FILE% 2>nul
-
-if errorlevel 1 (
- echo [警告] 数据库备份失败
- set /p continue=是否继续安装? (y/n):
- if /i not "!continue!"=="y" exit /b 1
-) else (
- echo [成功] 数据库备份成功
-)
-echo.
-
-REM 执行迁移脚本
-echo 正在执行数据库迁移...
-mysql -h%DB_HOST% -P%DB_PORT% -u%DB_USER% -p%DB_PASS% %DB_NAME% < server\database\migrations\add_doctor_fields_to_admin.sql 2>&1
-
-if errorlevel 1 (
- echo [错误] 数据库迁移失败
- echo 请检查错误信息,可能字段已经存在
- pause
- exit /b 1
-)
-
-echo [成功] 数据库迁移成功
-echo.
-
-REM 验证安装
-echo 验证字段是否添加成功...
-mysql -h%DB_HOST% -P%DB_PORT% -u%DB_USER% -p%DB_PASS% %DB_NAME% -e "SHOW COLUMNS FROM zyt_admin LIKE 'gender';" 2>nul | find "gender" >nul
-
-if errorlevel 1 (
- echo [错误] 字段验证失败
- pause
- exit /b 1
-)
-
-echo [成功] 字段验证成功
-echo.
-
-echo ==========================================
-echo 安装完成!
-echo ==========================================
-echo.
-echo 新增字段列表:
-echo - gender (性别)
-echo - age (年龄)
-echo - phone (手机号)
-echo - title (职称)
-echo - department (所属科室)
-echo - specialty (擅长领域)
-echo - education (教育背景)
-echo - experience (工作经历)
-echo - honors (荣誉资质)
-echo.
-echo 备份文件: %BACKUP_FILE%
-echo.
-echo 现在可以使用医生管理功能了!
-echo.
-pause
diff --git a/install_doctor_fields.sh b/install_doctor_fields.sh
deleted file mode 100644
index 6af70e0b..00000000
--- a/install_doctor_fields.sh
+++ /dev/null
@@ -1,100 +0,0 @@
-#!/bin/bash
-
-# 医生字段安装脚本
-# 用法: ./install_doctor_fields.sh
-
-echo "=========================================="
-echo "医生字段安装脚本"
-echo "=========================================="
-echo ""
-
-# 颜色定义
-RED='\033[0;31m'
-GREEN='\033[0;32m'
-YELLOW='\033[1;33m'
-NC='\033[0m' # No Color
-
-# 数据库配置
-DB_HOST="localhost"
-DB_PORT="3306"
-DB_NAME="zyt"
-DB_USER="root"
-
-# 提示输入数据库密码
-echo -e "${YELLOW}请输入数据库密码:${NC}"
-read -s DB_PASS
-echo ""
-
-# 检查MySQL是否可用
-echo "检查MySQL连接..."
-mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" -e "SELECT 1;" > /dev/null 2>&1
-
-if [ $? -ne 0 ]; then
- echo -e "${RED}错误: 无法连接到MySQL数据库${NC}"
- echo "请检查数据库配置和密码是否正确"
- exit 1
-fi
-
-echo -e "${GREEN}✓ 数据库连接成功${NC}"
-echo ""
-
-# 备份数据库
-BACKUP_FILE="zyt_backup_$(date +%Y%m%d_%H%M%S).sql"
-echo "正在备份数据库到: $BACKUP_FILE"
-mysqldump -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" > "$BACKUP_FILE" 2>/dev/null
-
-if [ $? -eq 0 ]; then
- echo -e "${GREEN}✓ 数据库备份成功${NC}"
-else
- echo -e "${RED}✗ 数据库备份失败${NC}"
- echo "是否继续安装? (y/n)"
- read -r response
- if [[ ! "$response" =~ ^[Yy]$ ]]; then
- exit 1
- fi
-fi
-echo ""
-
-# 执行迁移脚本
-echo "正在执行数据库迁移..."
-mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" < server/database/migrations/add_doctor_fields_to_admin.sql 2>&1
-
-if [ $? -eq 0 ]; then
- echo -e "${GREEN}✓ 数据库迁移成功${NC}"
-else
- echo -e "${RED}✗ 数据库迁移失败${NC}"
- echo "请检查错误信息,可能字段已经存在"
- exit 1
-fi
-echo ""
-
-# 验证安装
-echo "验证字段是否添加成功..."
-RESULT=$(mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" -e "SHOW COLUMNS FROM zyt_admin LIKE 'gender';" 2>/dev/null | grep -c "gender")
-
-if [ "$RESULT" -eq 1 ]; then
- echo -e "${GREEN}✓ 字段验证成功${NC}"
-else
- echo -e "${RED}✗ 字段验证失败${NC}"
- exit 1
-fi
-echo ""
-
-echo "=========================================="
-echo -e "${GREEN}安装完成!${NC}"
-echo "=========================================="
-echo ""
-echo "新增字段列表:"
-echo " - gender (性别)"
-echo " - age (年龄)"
-echo " - phone (手机号)"
-echo " - title (职称)"
-echo " - department (所属科室)"
-echo " - specialty (擅长领域)"
-echo " - education (教育背景)"
-echo " - experience (工作经历)"
-echo " - honors (荣誉资质)"
-echo ""
-echo "备份文件: $BACKUP_FILE"
-echo ""
-echo "现在可以使用医生管理功能了!"
diff --git a/install_express_tracking.bat b/install_express_tracking.bat
deleted file mode 100644
index 9b63f0ce..00000000
--- a/install_express_tracking.bat
+++ /dev/null
@@ -1,74 +0,0 @@
-@echo off
-chcp 65001 >nul
-REM ============================================
-REM 物流追踪系统安装脚本 (Windows)
-REM ============================================
-
-echo ==========================================
-echo 物流追踪系统安装
-echo ==========================================
-echo.
-
-REM 获取MySQL连接信息
-set /p DB_HOST="数据库主机 [localhost]: "
-if "%DB_HOST%"=="" set DB_HOST=localhost
-
-set /p DB_PORT="数据库端口 [3306]: "
-if "%DB_PORT%"=="" set DB_PORT=3306
-
-set /p DB_NAME="数据库名称: "
-set /p DB_USER="数据库用户名: "
-set /p DB_PASS="数据库密码: "
-
-echo.
-echo 创建数据库表...
-mysql -h%DB_HOST% -P%DB_PORT% -u%DB_USER% -p%DB_PASS% %DB_NAME% < server\database\migrations\create_express_tracking_tables.sql
-
-if %errorlevel% equ 0 (
- echo ✓ 数据库表创建成功
-) else (
- echo ✗ 数据库表创建失败
- pause
- exit /b 1
-)
-
-echo.
-echo 测试定时任务命令...
-cd server
-php think express:auto-update
-
-if %errorlevel% equ 0 (
- echo ✓ 定时任务命令测试成功
-) else (
- echo ✗ 定时任务命令测试失败
- pause
- exit /b 1
-)
-
-cd ..
-
-echo.
-echo ==========================================
-echo 安装完成!
-echo ==========================================
-echo.
-echo 下一步:
-echo 1. 手动添加以下内容到 server/config/console.php:
-echo 'express:auto-update' =^> \app\command\ExpressAutoUpdate::class,
-echo.
-echo 2. 配置Windows计划任务(每10分钟执行):
-echo - 打开"任务计划程序"
-echo - 创建基本任务
-echo - 触发器:每10分钟
-echo - 操作:启动程序
-echo - 程序:php
-echo - 参数:think express:auto-update
-echo - 起始于:%CD%\server
-echo.
-echo 3. 或使用命令行创建计划任务:
-echo schtasks /create /tn "ExpressAutoUpdate" /tr "php %CD%\server\think express:auto-update" /sc minute /mo 10
-echo.
-echo 测试命令:
-echo cd server ^&^& php think express:auto-update
-echo.
-pause
diff --git a/install_express_tracking.sh b/install_express_tracking.sh
deleted file mode 100644
index 77a409d0..00000000
--- a/install_express_tracking.sh
+++ /dev/null
@@ -1,142 +0,0 @@
-#!/bin/bash
-
-# ============================================
-# 物流追踪系统安装脚本
-# ============================================
-
-echo "=========================================="
-echo "物流追踪系统安装"
-echo "=========================================="
-echo ""
-
-# 检查MySQL连接信息
-echo "请输入MySQL连接信息:"
-read -p "数据库主机 [localhost]: " DB_HOST
-DB_HOST=${DB_HOST:-localhost}
-
-read -p "数据库端口 [3306]: " DB_PORT
-DB_PORT=${DB_PORT:-3306}
-
-read -p "数据库名称: " DB_NAME
-read -p "数据库用户名: " DB_USER
-read -sp "数据库密码: " DB_PASS
-echo ""
-
-# 测试数据库连接
-echo ""
-echo "测试数据库连接..."
-mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" -e "SELECT 1" > /dev/null 2>&1
-
-if [ $? -ne 0 ]; then
- echo "❌ 数据库连接失败,请检查连接信息"
- exit 1
-fi
-
-echo "✅ 数据库连接成功"
-
-# 执行SQL脚本
-echo ""
-echo "创建数据库表..."
-mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" < server/database/migrations/create_express_tracking_tables.sql
-
-if [ $? -eq 0 ]; then
- echo "✅ 数据库表创建成功"
-else
- echo "❌ 数据库表创建失败"
- exit 1
-fi
-
-# 注册定时任务命令
-echo ""
-echo "配置定时任务..."
-echo ""
-echo "请手动添加以下内容到 server/config/console.php 的 commands 数组中:"
-echo ""
-echo "'express:auto-update' => \\app\\command\\ExpressAutoUpdate::class,"
-echo ""
-
-# 测试命令
-echo "测试定时任务命令..."
-cd server
-php think express:auto-update
-
-if [ $? -eq 0 ]; then
- echo "✅ 定时任务命令测试成功"
-else
- echo "❌ 定时任务命令测试失败"
- exit 1
-fi
-
-# 配置crontab
-echo ""
-echo "=========================================="
-echo "配置crontab定时任务"
-echo "=========================================="
-echo ""
-echo "请选择更新频率:"
-echo "1) 每5分钟(高频,适合重要业务)"
-echo "2) 每10分钟(推荐)"
-echo "3) 每30分钟(低频,节省费用)"
-echo "4) 手动配置"
-read -p "请选择 [2]: " FREQ_CHOICE
-FREQ_CHOICE=${FREQ_CHOICE:-2}
-
-PROJECT_PATH=$(pwd)
-
-case $FREQ_CHOICE in
- 1)
- CRON_EXPR="*/5 * * * *"
- ;;
- 2)
- CRON_EXPR="*/10 * * * *"
- ;;
- 3)
- CRON_EXPR="*/30 * * * *"
- ;;
- 4)
- echo "请手动配置crontab"
- echo "执行: crontab -e"
- echo "添加: */10 * * * * cd $PROJECT_PATH/server && php think express:auto-update >> /dev/null 2>&1"
- exit 0
- ;;
- *)
- CRON_EXPR="*/10 * * * *"
- ;;
-esac
-
-echo ""
-echo "将添加以下crontab任务:"
-echo "$CRON_EXPR cd $PROJECT_PATH/server && php think express:auto-update >> /dev/null 2>&1"
-echo ""
-read -p "是否自动添加到crontab? (y/n) [y]: " ADD_CRON
-ADD_CRON=${ADD_CRON:-y}
-
-if [ "$ADD_CRON" = "y" ]; then
- # 检查是否已存在
- crontab -l 2>/dev/null | grep -q "express:auto-update"
- if [ $? -eq 0 ]; then
- echo "⚠️ crontab中已存在相关任务,跳过添加"
- else
- # 添加到crontab
- (crontab -l 2>/dev/null; echo "$CRON_EXPR cd $PROJECT_PATH/server && php think express:auto-update >> /dev/null 2>&1") | crontab -
- echo "✅ crontab任务添加成功"
- fi
-else
- echo "请手动添加crontab任务:"
- echo "crontab -e"
- echo "添加: $CRON_EXPR cd $PROJECT_PATH/server && php think express:auto-update >> /dev/null 2>&1"
-fi
-
-echo ""
-echo "=========================================="
-echo "安装完成!"
-echo "=========================================="
-echo ""
-echo "下一步:"
-echo "1. 确认快递100已充值"
-echo "2. 在订单发货时调用 ExpressTrackingService::createOrUpdate()"
-echo "3. 查看文档: EXPRESS_TRACKING_SYSTEM.md"
-echo ""
-echo "测试命令:"
-echo "cd server && php think express:auto-update"
-echo ""
diff --git a/install_first_login_password.bat b/install_first_login_password.bat
deleted file mode 100644
index bbcbb298..00000000
--- a/install_first_login_password.bat
+++ /dev/null
@@ -1,59 +0,0 @@
-@echo off
-chcp 65001 >nul
-setlocal enabledelayedexpansion
-
-echo ==========================================
-echo 首次登录强制修改密码功能安装
-echo ==========================================
-echo.
-
-REM 获取数据库配置
-set /p DB_HOST="请输入数据库主机 (默认: localhost): "
-if "!DB_HOST!"=="" set DB_HOST=localhost
-
-set /p DB_PORT="请输入数据库端口 (默认: 3306): "
-if "!DB_PORT!"=="" set DB_PORT=3306
-
-set /p DB_NAME="请输入数据库名称: "
-if "!DB_NAME!"=="" (
- echo 错误: 数据库名称不能为空
- pause
- exit /b 1
-)
-
-set /p DB_USER="请输入数据库用户名: "
-if "!DB_USER!"=="" (
- echo 错误: 数据库用户名不能为空
- pause
- exit /b 1
-)
-
-set /p DB_PASS="请输入数据库密码: "
-
-REM 执行 SQL
-echo.
-echo 正在添加 is_paw 字段...
-
-mysql -h!DB_HOST! -P!DB_PORT! -u!DB_USER! -p!DB_PASS! !DB_NAME! < add_is_paw_field.sql
-
-if !errorlevel! equ 0 (
- echo √ 数据库字段添加成功
- echo.
- echo ==========================================
- echo 安装完成!
- echo ==========================================
- echo.
- echo 功能说明:
- echo 1. 当管理员的 is_paw 字段为 0 时,登录后会强制跳转到修改密码页面
- echo 2. 修改密码成功后,is_paw 字段会自动更新为 1
- echo 3. 如需设置某个管理员首次登录修改密码,请执行:
- echo UPDATE la_admin SET is_paw = 0 WHERE id = ^<管理员ID^>;
- echo.
-) else (
- echo × 数据库字段添加失败
- echo 请检查数据库连接信息是否正确
- pause
- exit /b 1
-)
-
-pause
diff --git a/install_first_login_password.sh b/install_first_login_password.sh
deleted file mode 100644
index 43dafd81..00000000
--- a/install_first_login_password.sh
+++ /dev/null
@@ -1,55 +0,0 @@
-#!/bin/bash
-
-# 首次登录强制修改密码功能安装脚本
-
-echo "=========================================="
-echo "首次登录强制修改密码功能安装"
-echo "=========================================="
-echo ""
-
-# 获取数据库配置
-read -p "请输入数据库主机 (默认: localhost): " DB_HOST
-DB_HOST=${DB_HOST:-localhost}
-
-read -p "请输入数据库端口 (默认: 3306): " DB_PORT
-DB_PORT=${DB_PORT:-3306}
-
-read -p "请输入数据库名称: " DB_NAME
-if [ -z "$DB_NAME" ]; then
- echo "错误: 数据库名称不能为空"
- exit 1
-fi
-
-read -p "请输入数据库用户名: " DB_USER
-if [ -z "$DB_USER" ]; then
- echo "错误: 数据库用户名不能为空"
- exit 1
-fi
-
-read -sp "请输入数据库密码: " DB_PASS
-echo ""
-
-# 执行 SQL
-echo ""
-echo "正在添加 is_paw 字段..."
-
-mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" < add_is_paw_field.sql
-
-if [ $? -eq 0 ]; then
- echo "✓ 数据库字段添加成功"
- echo ""
- echo "=========================================="
- echo "安装完成!"
- echo "=========================================="
- echo ""
- echo "功能说明:"
- echo "1. 当管理员的 is_paw 字段为 0 时,登录后会强制跳转到修改密码页面"
- echo "2. 修改密码成功后,is_paw 字段会自动更新为 1"
- echo "3. 如需设置某个管理员首次登录修改密码,请执行:"
- echo " UPDATE la_admin SET is_paw = 0 WHERE id = <管理员ID>;"
- echo ""
-else
- echo "✗ 数据库字段添加失败"
- echo "请检查数据库连接信息是否正确"
- exit 1
-fi
diff --git a/install_medicine_library.bat b/install_medicine_library.bat
deleted file mode 100644
index 727ebdfa..00000000
--- a/install_medicine_library.bat
+++ /dev/null
@@ -1,55 +0,0 @@
-@echo off
-chcp 65001 >nul
-echo ======================================
-echo 药品库系统安装脚本
-echo ======================================
-echo.
-
-set /p DB_HOST="请输入数据库主机 (默认: localhost): "
-if "%DB_HOST%"=="" set DB_HOST=localhost
-
-set /p DB_PORT="请输入数据库端口 (默认: 3306): "
-if "%DB_PORT%"=="" set DB_PORT=3306
-
-set /p DB_NAME="请输入数据库名称: "
-if "%DB_NAME%"=="" (
- echo 错误: 数据库名称不能为空
- pause
- exit /b 1
-)
-
-set /p DB_USER="请输入数据库用户名: "
-if "%DB_USER%"=="" (
- echo 错误: 数据库用户名不能为空
- pause
- exit /b 1
-)
-
-set /p DB_PASS="请输入数据库密码: "
-
-echo.
-echo 正在执行数据库迁移...
-mysql -h%DB_HOST% -P%DB_PORT% -u%DB_USER% -p%DB_PASS% %DB_NAME% < install_medicine_library.sql
-
-if %errorlevel% equ 0 (
- echo.
- echo ======================================
- echo 药品库系统安装成功!
- echo ======================================
- echo.
- echo 已创建以下数据表:
- echo - la_doctor_medicine (药品库表^)
- echo.
- echo 已插入示例数据:5条药品记录
- echo.
- echo 您现在可以在后台管理系统中使用药品库功能了。
- echo.
-) else (
- echo.
- echo ======================================
- echo 安装失败!
- echo ======================================
- echo 请检查数据库连接信息是否正确。
-)
-
-pause
diff --git a/install_medicine_library.sh b/install_medicine_library.sh
deleted file mode 100644
index 30f47aca..00000000
--- a/install_medicine_library.sh
+++ /dev/null
@@ -1,55 +0,0 @@
-#!/bin/bash
-
-echo "======================================"
-echo "药品库系统安装脚本"
-echo "======================================"
-echo ""
-
-# 检查 MySQL 配置
-read -p "请输入数据库主机 (默认: localhost): " DB_HOST
-DB_HOST=${DB_HOST:-localhost}
-
-read -p "请输入数据库端口 (默认: 3306): " DB_PORT
-DB_PORT=${DB_PORT:-3306}
-
-read -p "请输入数据库名称: " DB_NAME
-if [ -z "$DB_NAME" ]; then
- echo "错误: 数据库名称不能为空"
- exit 1
-fi
-
-read -p "请输入数据库用户名: " DB_USER
-if [ -z "$DB_USER" ]; then
- echo "错误: 数据库用户名不能为空"
- exit 1
-fi
-
-read -sp "请输入数据库密码: " DB_PASS
-echo ""
-
-# 执行 SQL 文件
-echo ""
-echo "正在执行数据库迁移..."
-mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" < install_medicine_library.sql
-
-if [ $? -eq 0 ]; then
- echo ""
- echo "======================================"
- echo "药品库系统安装成功!"
- echo "======================================"
- echo ""
- echo "已创建以下数据表:"
- echo " - la_doctor_medicine (药品库表)"
- echo ""
- echo "已插入示例数据:5条药品记录"
- echo ""
- echo "您现在可以在后台管理系统中使用药品库功能了。"
- echo ""
-else
- echo ""
- echo "======================================"
- echo "安装失败!"
- echo "======================================"
- echo "请检查数据库连接信息是否正确。"
- exit 1
-fi
diff --git a/install_prescription_library.bat b/install_prescription_library.bat
deleted file mode 100644
index 48324620..00000000
--- a/install_prescription_library.bat
+++ /dev/null
@@ -1,60 +0,0 @@
-@echo off
-chcp 65001 >nul
-setlocal enabledelayedexpansion
-
-echo =========================================
-echo 开始安装处方库功能
-echo =========================================
-echo.
-
-REM 数据库配置
-set DB_HOST=localhost
-set DB_PORT=3306
-set DB_NAME=likeadmin_php
-set DB_USER=root
-set DB_PASS=
-
-REM 读取用户输入
-set /p input_host="请输入数据库地址 [默认: localhost]: "
-if not "!input_host!"=="" set DB_HOST=!input_host!
-
-set /p input_port="请输入数据库端口 [默认: 3306]: "
-if not "!input_port!"=="" set DB_PORT=!input_port!
-
-set /p input_name="请输入数据库名称 [默认: likeadmin_php]: "
-if not "!input_name!"=="" set DB_NAME=!input_name!
-
-set /p input_user="请输入数据库用户名 [默认: root]: "
-if not "!input_user!"=="" set DB_USER=!input_user!
-
-set /p DB_PASS="请输入数据库密码: "
-
-echo.
-echo =========================================
-echo 开始执行数据库迁移...
-echo =========================================
-echo.
-
-REM 执行SQL文件
-mysql -h%DB_HOST% -P%DB_PORT% -u%DB_USER% -p%DB_PASS% %DB_NAME% < server\database\migrations\create_prescription_library.sql
-
-if %errorlevel% equ 0 (
- echo √ 数据库表创建成功
-) else (
- echo × 数据库表创建失败,请检查数据库配置
- pause
- exit /b 1
-)
-
-echo.
-echo =========================================
-echo 处方库功能安装完成!
-echo =========================================
-echo.
-echo 使用说明:
-echo 1. 访问后台管理系统
-echo 2. 在菜单中找到 '处方库' 功能
-echo 3. 可以创建、编辑、删除处方模板
-echo 4. 支持设置处方是否公开给其他医生使用
-echo.
-pause
diff --git a/install_prescription_library.sh b/install_prescription_library.sh
deleted file mode 100644
index abb44901..00000000
--- a/install_prescription_library.sh
+++ /dev/null
@@ -1,58 +0,0 @@
-#!/bin/bash
-
-# 处方库安装脚本
-
-echo "========================================="
-echo "开始安装处方库功能"
-echo "========================================="
-
-# 数据库配置
-DB_HOST="localhost"
-DB_PORT="3306"
-DB_NAME="likeadmin_php"
-DB_USER="root"
-DB_PASS=""
-
-# 读取用户输入
-read -p "请输入数据库地址 [默认: localhost]: " input_host
-DB_HOST=${input_host:-$DB_HOST}
-
-read -p "请输入数据库端口 [默认: 3306]: " input_port
-DB_PORT=${input_port:-$DB_PORT}
-
-read -p "请输入数据库名称 [默认: likeadmin_php]: " input_name
-DB_NAME=${input_name:-$DB_NAME}
-
-read -p "请输入数据库用户名 [默认: root]: " input_user
-DB_USER=${input_user:-$DB_USER}
-
-read -s -p "请输入数据库密码: " input_pass
-echo ""
-DB_PASS=${input_pass:-$DB_PASS}
-
-echo ""
-echo "========================================="
-echo "开始执行数据库迁移..."
-echo "========================================="
-
-# 执行SQL文件
-mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" < server/database/migrations/create_prescription_library.sql
-
-if [ $? -eq 0 ]; then
- echo "✓ 数据库表创建成功"
-else
- echo "✗ 数据库表创建失败,请检查数据库配置"
- exit 1
-fi
-
-echo ""
-echo "========================================="
-echo "处方库功能安装完成!"
-echo "========================================="
-echo ""
-echo "使用说明:"
-echo "1. 访问后台管理系统"
-echo "2. 在菜单中找到 '处方库' 功能"
-echo "3. 可以创建、编辑、删除处方模板"
-echo "4. 支持设置处方是否公开给其他医生使用"
-echo ""
diff --git a/install_prescription_shared.bat b/install_prescription_shared.bat
deleted file mode 100644
index 369c209e..00000000
--- a/install_prescription_shared.bat
+++ /dev/null
@@ -1,48 +0,0 @@
-@echo off
-chcp 65001 >nul
-echo ========================================
-echo 处方共享功能安装脚本
-echo ========================================
-echo.
-
-REM 数据库配置
-set DB_HOST=127.0.0.1
-set DB_PORT=3306
-set DB_NAME=zyt
-set DB_USER=root
-set DB_PASS=root
-
-echo 正在执行数据库迁移...
-echo.
-
-REM 执行SQL文件
-mysql -h%DB_HOST% -P%DB_PORT% -u%DB_USER% -p%DB_PASS% %DB_NAME% < server/database/migrations/add_shared_to_prescription.sql
-
-if %errorlevel% equ 0 (
- echo.
- echo ========================================
- echo 安装成功!
- echo ========================================
- echo.
- echo 已添加以下字段:
- echo 1. prescription_name - 处方名称
- echo 2. prescription_type - 处方类型
- echo 3. is_shared - 是否共享
- echo 4. usage_days - 服用天数
- echo 5. usage_time - 服用时间
- echo 6. usage_way - 服用方式
- echo 7. dietary_taboo - 忌口
- echo 8. usage_notes - 其他说明
- echo 9. tongue_image - 舌象图片
- echo 10. pulse_condition - 脉象详情
- echo.
-) else (
- echo.
- echo ========================================
- echo 安装失败!
- echo ========================================
- echo 请检查数据库配置和连接
- echo.
-)
-
-pause
diff --git a/install_prescription_shared.sh b/install_prescription_shared.sh
deleted file mode 100644
index 51d573e7..00000000
--- a/install_prescription_shared.sh
+++ /dev/null
@@ -1,46 +0,0 @@
-#!/bin/bash
-
-echo "========================================"
-echo "处方共享功能安装脚本"
-echo "========================================"
-echo ""
-
-# 数据库配置
-DB_HOST="127.0.0.1"
-DB_PORT="3306"
-DB_NAME="zyt"
-DB_USER="root"
-DB_PASS="root"
-
-echo "正在执行数据库迁移..."
-echo ""
-
-# 执行SQL文件
-mysql -h${DB_HOST} -P${DB_PORT} -u${DB_USER} -p${DB_PASS} ${DB_NAME} < server/database/migrations/add_shared_to_prescription.sql
-
-if [ $? -eq 0 ]; then
- echo ""
- echo "========================================"
- echo "安装成功!"
- echo "========================================"
- echo ""
- echo "已添加以下字段:"
- echo "1. prescription_name - 处方名称"
- echo "2. prescription_type - 处方类型"
- echo "3. is_shared - 是否共享"
- echo "4. usage_days - 服用天数"
- echo "5. usage_time - 服用时间"
- echo "6. usage_way - 服用方式"
- echo "7. dietary_taboo - 忌口"
- echo "8. usage_notes - 其他说明"
- echo "9. tongue_image - 舌象图片"
- echo "10. pulse_condition - 脉象详情"
- echo ""
-else
- echo ""
- echo "========================================"
- echo "安装失败!"
- echo "========================================"
- echo "请检查数据库配置和连接"
- echo ""
-fi
diff --git a/install_video_call.bat b/install_video_call.bat
deleted file mode 100644
index 8f74bde8..00000000
--- a/install_video_call.bat
+++ /dev/null
@@ -1,179 +0,0 @@
-@echo off
-chcp 65001 >nul
-setlocal enabledelayedexpansion
-
-:: 音视频通话功能安装脚本 (Windows)
-:: 使用方法: install_video_call.bat
-
-echo ==========================================
-echo 音视频通话功能安装脚本
-echo ==========================================
-echo.
-
-:: 检查是否在项目根目录
-if not exist "admin" (
- echo [错误] 请在项目根目录执行此脚本
- pause
- exit /b 1
-)
-
-if not exist "server" (
- echo [错误] 请在项目根目录执行此脚本
- pause
- exit /b 1
-)
-
-echo [步骤 1/5] 安装前端依赖
-cd admin
-if exist "package.json" (
- echo 正在安装 @tencentcloud/call-uikit-vue...
- call npm install @tencentcloud/call-uikit-vue
- if !errorlevel! equ 0 (
- echo [成功] 前端依赖安装成功
- ) else (
- echo [失败] 前端依赖安装失败
- pause
- exit /b 1
- )
-) else (
- echo [错误] 未找到 package.json 文件
- pause
- exit /b 1
-)
-cd ..
-
-echo.
-echo [步骤 2/5] 创建数据库表
-echo 请手动执行以下命令创建数据库表:
-echo mysql -u root -p your_database ^< server/sql/tcm_call_record.sql
-echo.
-pause
-
-echo.
-echo [步骤 3/5] 配置腾讯云 TRTC
-echo.
-echo 请访问腾讯云控制台获取配置信息:
-echo https://console.cloud.tencent.com/trtc
-echo.
-set /p SDK_APP_ID="请输入 SDKAppID: "
-set /p SECRET_KEY="请输入 SecretKey: "
-
-:: 创建或更新 .env 文件
-set ENV_FILE=server\.env
-if exist "%ENV_FILE%" (
- findstr /C:"TRTC_SDK_APP_ID" "%ENV_FILE%" >nul
- if !errorlevel! equ 0 (
- echo [提示] 检测到已存在 TRTC 配置,正在更新...
- powershell -Command "(gc '%ENV_FILE%') -replace 'TRTC_SDK_APP_ID=.*', 'TRTC_SDK_APP_ID=%SDK_APP_ID%' | Out-File -encoding ASCII '%ENV_FILE%'"
- powershell -Command "(gc '%ENV_FILE%') -replace 'TRTC_SECRET_KEY=.*', 'TRTC_SECRET_KEY=%SECRET_KEY%' | Out-File -encoding ASCII '%ENV_FILE%'"
- powershell -Command "(gc '%ENV_FILE%') -replace 'TRTC_ENABLE=.*', 'TRTC_ENABLE=true' | Out-File -encoding ASCII '%ENV_FILE%'"
- ) else (
- echo [提示] 正在添加 TRTC 配置...
- echo. >> "%ENV_FILE%"
- echo # 腾讯云实时音视频(TRTC)配置 >> "%ENV_FILE%"
- echo TRTC_SDK_APP_ID=%SDK_APP_ID% >> "%ENV_FILE%"
- echo TRTC_SECRET_KEY=%SECRET_KEY% >> "%ENV_FILE%"
- echo TRTC_ENABLE=true >> "%ENV_FILE%"
- )
- echo [成功] 配置文件更新成功
-) else (
- echo [错误] 未找到 .env 文件
- pause
- exit /b 1
-)
-
-echo.
-echo [步骤 4/5] 验证文件完整性
-echo.
-
-:: 检查关键文件是否存在
-set ALL_EXISTS=1
-
-if exist "admin\src\components\video-call\index.vue" (
- echo [√] admin\src\components\video-call\index.vue
-) else (
- echo [×] admin\src\components\video-call\index.vue (缺失)
- set ALL_EXISTS=0
-)
-
-if exist "admin\src\api\tcm.ts" (
- echo [√] admin\src\api\tcm.ts
-) else (
- echo [×] admin\src\api\tcm.ts (缺失)
- set ALL_EXISTS=0
-)
-
-if exist "server\app\adminapi\controller\tcm\DiagnosisController.php" (
- echo [√] server\app\adminapi\controller\tcm\DiagnosisController.php
-) else (
- echo [×] server\app\adminapi\controller\tcm\DiagnosisController.php (缺失)
- set ALL_EXISTS=0
-)
-
-if exist "server\app\adminapi\logic\tcm\DiagnosisLogic.php" (
- echo [√] server\app\adminapi\logic\tcm\DiagnosisLogic.php
-) else (
- echo [×] server\app\adminapi\logic\tcm\DiagnosisLogic.php (缺失)
- set ALL_EXISTS=0
-)
-
-if exist "server\app\common\model\tcm\CallRecord.php" (
- echo [√] server\app\common\model\tcm\CallRecord.php
-) else (
- echo [×] server\app\common\model\tcm\CallRecord.php (缺失)
- set ALL_EXISTS=0
-)
-
-if exist "server\config\trtc.php" (
- echo [√] server\config\trtc.php
-) else (
- echo [×] server\config\trtc.php (缺失)
- set ALL_EXISTS=0
-)
-
-if exist "server\sql\tcm_call_record.sql" (
- echo [√] server\sql\tcm_call_record.sql
-) else (
- echo [×] server\sql\tcm_call_record.sql (缺失)
- set ALL_EXISTS=0
-)
-
-if !ALL_EXISTS! equ 0 (
- echo.
- echo [错误] 部分文件缺失,请检查
- pause
- exit /b 1
-)
-
-echo.
-echo [步骤 5/5] 构建前端项目
-set /p BUILD_CHOICE="是否立即构建前端项目? (y/n): "
-if /i "%BUILD_CHOICE%"=="y" (
- cd admin
- call npm run build
- if !errorlevel! equ 0 (
- echo [成功] 前端构建成功
- ) else (
- echo [失败] 前端构建失败
- )
- cd ..
-)
-
-echo.
-echo ==========================================
-echo 安装完成!
-echo ==========================================
-echo.
-echo 后续步骤:
-echo 1. 确保已执行数据库迁移
-echo 2. 在后台管理系统中配置权限: tcm.diagnosis/video-call
-echo 3. 重启后端服务
-echo 4. 访问诊断列表页面测试视频通话功能
-echo.
-echo 详细文档请查看:
-echo - TRTC_SETUP_GUIDE.md (完整配置指南)
-echo - admin\README_VIDEO_CALL.md (功能说明)
-echo.
-echo [注意] 请确保使用 HTTPS 协议或 localhost 访问
-echo.
-pause
diff --git a/install_video_call.sh b/install_video_call.sh
deleted file mode 100644
index 6255b934..00000000
--- a/install_video_call.sh
+++ /dev/null
@@ -1,138 +0,0 @@
-#!/bin/bash
-
-# 音视频通话功能安装脚本
-# 使用方法: bash install_video_call.sh
-
-echo "=========================================="
-echo " 音视频通话功能安装脚本"
-echo "=========================================="
-echo ""
-
-# 颜色定义
-RED='\033[0;31m'
-GREEN='\033[0;32m'
-YELLOW='\033[1;33m'
-NC='\033[0m' # No Color
-
-# 检查是否在项目根目录
-if [ ! -d "admin" ] || [ ! -d "server" ]; then
- echo -e "${RED}错误: 请在项目根目录执行此脚本${NC}"
- exit 1
-fi
-
-echo -e "${GREEN}步骤 1/5: 安装前端依赖${NC}"
-cd admin
-if [ -f "package.json" ]; then
- echo "正在安装 @tencentcloud/call-uikit-vue..."
- npm install @tencentcloud/call-uikit-vue
- if [ $? -eq 0 ]; then
- echo -e "${GREEN}✓ 前端依赖安装成功${NC}"
- else
- echo -e "${RED}✗ 前端依赖安装失败${NC}"
- exit 1
- fi
-else
- echo -e "${RED}✗ 未找到 package.json 文件${NC}"
- exit 1
-fi
-cd ..
-
-echo ""
-echo -e "${GREEN}步骤 2/5: 创建数据库表${NC}"
-echo -e "${YELLOW}请手动执行以下命令创建数据库表:${NC}"
-echo "mysql -u root -p your_database < server/sql/tcm_call_record.sql"
-echo ""
-read -p "按回车键继续..."
-
-echo ""
-echo -e "${GREEN}步骤 3/5: 配置腾讯云 TRTC${NC}"
-echo ""
-echo "请访问腾讯云控制台获取配置信息:"
-echo "https://console.cloud.tencent.com/trtc"
-echo ""
-read -p "请输入 SDKAppID: " SDK_APP_ID
-read -p "请输入 SecretKey: " SECRET_KEY
-
-# 创建或更新 .env 文件
-ENV_FILE="server/.env"
-if [ -f "$ENV_FILE" ]; then
- # 检查是否已存在配置
- if grep -q "TRTC_SDK_APP_ID" "$ENV_FILE"; then
- echo -e "${YELLOW}检测到已存在 TRTC 配置,正在更新...${NC}"
- sed -i "s/TRTC_SDK_APP_ID=.*/TRTC_SDK_APP_ID=$SDK_APP_ID/" "$ENV_FILE"
- sed -i "s/TRTC_SECRET_KEY=.*/TRTC_SECRET_KEY=$SECRET_KEY/" "$ENV_FILE"
- sed -i "s/TRTC_ENABLE=.*/TRTC_ENABLE=true/" "$ENV_FILE"
- else
- echo -e "${GREEN}正在添加 TRTC 配置...${NC}"
- echo "" >> "$ENV_FILE"
- echo "# 腾讯云实时音视频(TRTC)配置" >> "$ENV_FILE"
- echo "TRTC_SDK_APP_ID=$SDK_APP_ID" >> "$ENV_FILE"
- echo "TRTC_SECRET_KEY=$SECRET_KEY" >> "$ENV_FILE"
- echo "TRTC_ENABLE=true" >> "$ENV_FILE"
- fi
- echo -e "${GREEN}✓ 配置文件更新成功${NC}"
-else
- echo -e "${RED}✗ 未找到 .env 文件${NC}"
- exit 1
-fi
-
-echo ""
-echo -e "${GREEN}步骤 4/5: 验证文件完整性${NC}"
-
-# 检查关键文件是否存在
-FILES=(
- "admin/src/components/video-call/index.vue"
- "admin/src/api/tcm.ts"
- "server/app/adminapi/controller/tcm/DiagnosisController.php"
- "server/app/adminapi/logic/tcm/DiagnosisLogic.php"
- "server/app/common/model/tcm/CallRecord.php"
- "server/config/trtc.php"
- "server/sql/tcm_call_record.sql"
-)
-
-ALL_EXISTS=true
-for file in "${FILES[@]}"; do
- if [ -f "$file" ]; then
- echo -e "${GREEN}✓${NC} $file"
- else
- echo -e "${RED}✗${NC} $file ${RED}(缺失)${NC}"
- ALL_EXISTS=false
- fi
-done
-
-if [ "$ALL_EXISTS" = false ]; then
- echo -e "${RED}部分文件缺失,请检查${NC}"
- exit 1
-fi
-
-echo ""
-echo -e "${GREEN}步骤 5/5: 构建前端项目${NC}"
-read -p "是否立即构建前端项目? (y/n): " BUILD_CHOICE
-if [ "$BUILD_CHOICE" = "y" ] || [ "$BUILD_CHOICE" = "Y" ]; then
- cd admin
- npm run build
- if [ $? -eq 0 ]; then
- echo -e "${GREEN}✓ 前端构建成功${NC}"
- else
- echo -e "${RED}✗ 前端构建失败${NC}"
- fi
- cd ..
-fi
-
-echo ""
-echo "=========================================="
-echo -e "${GREEN}安装完成!${NC}"
-echo "=========================================="
-echo ""
-echo "后续步骤:"
-echo "1. 确保已执行数据库迁移"
-echo "2. 在后台管理系统中配置权限: tcm.diagnosis/video-call"
-echo "3. 重启后端服务"
-echo "4. 访问诊断列表页面测试视频通话功能"
-echo ""
-echo "详细文档请查看:"
-echo "- TRTC_SETUP_GUIDE.md (完整配置指南)"
-echo "- admin/README_VIDEO_CALL.md (功能说明)"
-echo ""
-echo -e "${YELLOW}注意: 请确保使用 HTTPS 协议或 localhost 访问${NC}"
-echo ""
diff --git a/server/APPOINTMENT_BACKEND_SETUP.md b/server/APPOINTMENT_BACKEND_SETUP.md
deleted file mode 100644
index ecf5c634..00000000
--- a/server/APPOINTMENT_BACKEND_SETUP.md
+++ /dev/null
@@ -1,379 +0,0 @@
-# 医生预约系统后端安装说明
-
-## 文件清单
-
-已创建以下后端文件:
-
-### 1. 数据库模型
-- `server/app/common/model/doctor/Appointment.php` - 预约模型
-
-### 2. 控制器
-- `server/app/adminapi/controller/doctor/AppointmentController.php` - 预约控制器
-
-### 3. 业务逻辑层
-- `server/app/adminapi/logic/doctor/AppointmentLogic.php` - 预约业务逻辑
-
-### 4. 验证器
-- `server/app/adminapi/validate/doctor/AppointmentValidate.php` - 预约数据验证
-
-### 5. 数据列表
-- `server/app/adminapi/lists/doctor/AppointmentLists.php` - 预约列表
-
-### 6. 数据库脚本
-- `server/sql/doctor_appointment.sql` - 数据库表创建脚本
-
-## 安装步骤
-
-### 1. 创建数据库表
-
-在MySQL中执行以下SQL脚本:
-
-```bash
-mysql -u your_username -p your_database < server/sql/doctor_appointment.sql
-```
-
-或者在phpMyAdmin中导入 `server/sql/doctor_appointment.sql` 文件。
-
-### 2. 验证文件
-
-确认所有文件已正确创建:
-
-```bash
-# 检查模型
-ls -la server/app/common/model/doctor/Appointment.php
-
-# 检查控制器
-ls -la server/app/adminapi/controller/doctor/AppointmentController.php
-
-# 检查逻辑层
-ls -la server/app/adminapi/logic/doctor/AppointmentLogic.php
-
-# 检查验证器
-ls -la server/app/adminapi/validate/doctor/AppointmentValidate.php
-
-# 检查列表
-ls -la server/app/adminapi/lists/doctor/AppointmentLists.php
-```
-
-### 3. 清除缓存
-
-```bash
-cd server
-php think clear
-```
-
-### 4. 测试API接口
-
-#### 4.1 获取可用时间段
-
-```bash
-GET /adminapi/doctor.appointment/availableSlots?doctor_id=1&appointment_date=2026-03-04&period=morning
-```
-
-预期返回:
-```json
-{
- "code": 1,
- "msg": "success",
- "data": {
- "slots": [
- {
- "time": "09:00",
- "available": true,
- "quota": 5
- },
- ...
- ]
- }
-}
-```
-
-#### 4.2 创建预约
-
-```bash
-POST /adminapi/doctor.appointment/create
-Content-Type: application/json
-
-{
- "patient_id": 1,
- "doctor_id": 1,
- "appointment_date": "2026-03-04",
- "period": "morning",
- "appointment_time": "09:00",
- "appointment_type": "video",
- "remark": "测试预约"
-}
-```
-
-预期返回:
-```json
-{
- "code": 1,
- "msg": "预约成功",
- "data": {
- "id": 1
- }
-}
-```
-
-#### 4.3 获取预约列表
-
-```bash
-GET /adminapi/doctor.appointment/lists?page_no=1&page_size=10
-```
-
-#### 4.4 取消预约
-
-```bash
-POST /adminapi/doctor.appointment/cancel
-Content-Type: application/json
-
-{
- "id": 1
-}
-```
-
-#### 4.5 获取医生可用号源数
-
-```bash
-GET /adminapi/doctor.appointment/doctorAvailability?doctor_id=1&date=2026-03-04
-```
-
-预期返回:
-```json
-{
- "code": 1,
- "msg": "success",
- "data": {
- "available_count": 10
- }
-}
-```
-
-## API接口说明
-
-### 1. 获取可用时间段
-
-**接口地址**: `/adminapi/doctor.appointment/availableSlots`
-
-**请求方式**: GET
-
-**请求参数**:
-| 参数名 | 类型 | 必填 | 说明 |
-|--------|------|------|------|
-| doctor_id | int | 是 | 医生ID |
-| appointment_date | date | 是 | 预约日期 YYYY-MM-DD |
-| period | string | 是 | 时段 morning/afternoon |
-
-**返回数据**:
-```json
-{
- "slots": [
- {
- "time": "09:00",
- "available": true,
- "quota": 5
- }
- ]
-}
-```
-
-### 2. 创建预约
-
-**接口地址**: `/adminapi/doctor.appointment/create`
-
-**请求方式**: POST
-
-**请求参数**:
-| 参数名 | 类型 | 必填 | 说明 |
-|--------|------|------|------|
-| patient_id | int | 是 | 患者ID |
-| doctor_id | int | 是 | 医生ID |
-| appointment_date | date | 是 | 预约日期 |
-| period | string | 是 | 时段 morning/afternoon |
-| appointment_time | time | 是 | 预约时间 HH:mm |
-| appointment_type | string | 否 | 预约类型 video/text/phone |
-| remark | string | 否 | 备注 |
-
-**返回数据**:
-```json
-{
- "id": 1
-}
-```
-
-### 3. 取消预约
-
-**接口地址**: `/adminapi/doctor.appointment/cancel`
-
-**请求方式**: POST
-
-**请求参数**:
-| 参数名 | 类型 | 必填 | 说明 |
-|--------|------|------|------|
-| id | int | 是 | 预约ID |
-
-### 4. 预约列表
-
-**接口地址**: `/adminapi/doctor.appointment/lists`
-
-**请求方式**: GET
-
-**请求参数**:
-| 参数名 | 类型 | 必填 | 说明 |
-|--------|------|------|------|
-| page_no | int | 否 | 页码 |
-| page_size | int | 否 | 每页数量 |
-| patient_id | int | 否 | 患者ID |
-| doctor_id | int | 否 | 医生ID |
-| status | int | 否 | 状态 |
-| appointment_date | date | 否 | 预约日期 |
-
-### 5. 预约详情
-
-**接口地址**: `/adminapi/doctor.appointment/detail`
-
-**请求方式**: GET
-
-**请求参数**:
-| 参数名 | 类型 | 必填 | 说明 |
-|--------|------|------|------|
-| id | int | 是 | 预约ID |
-
-### 6. 医生可用号源数
-
-**接口地址**: `/adminapi/doctor.appointment/doctorAvailability`
-
-**请求方式**: GET
-
-**请求参数**:
-| 参数名 | 类型 | 必填 | 说明 |
-|--------|------|------|------|
-| doctor_id | int | 是 | 医生ID |
-| date | date | 是 | 日期 YYYY-MM-DD |
-
-## 业务逻辑说明
-
-### 1. 时间段生成规则
-
-- 上午时段:09:00 - 11:30(30分钟间隔)
-- 下午时段:13:00 - 20:30(30分钟间隔)
-
-### 2. 预约规则
-
-1. 必须有医生排班且状态为"出诊"(status=1)
-2. 时间段不能重复预约
-3. 预约数量不能超过排班设置的号源数(quota)
-4. 使用数据库唯一索引防止并发冲突
-
-### 3. 状态说明
-
-**预约状态**:
-- 1: 已预约
-- 2: 已取消
-- 3: 已完成
-
-**排班状态**:
-- 1: 出诊
-- 2: 停诊
-- 3: 休息
-- 4: 请假
-
-## 注意事项
-
-1. **并发控制**: 使用数据库唯一索引 `unique_appointment` 防止重复预约
-2. **事务处理**: 创建预约时使用事务确保数据一致性
-3. **时间格式**:
- - 日期格式:YYYY-MM-DD
- - 时间格式:HH:mm
- - 时间戳:整型(秒)
-4. **权限控制**: 需要在后台权限管理中添加相应的权限节点
-
-## 权限节点
-
-建议添加以下权限节点:
-
-```
-doctor.appointment/availableSlots - 查看可用时间段
-doctor.appointment/create - 创建预约
-doctor.appointment/cancel - 取消预约
-doctor.appointment/lists - 预约列表
-doctor.appointment/detail - 预约详情
-doctor.appointment/doctorAvailability - 医生可用号源
-```
-
-## 故障排查
-
-### 1. 控制器不存在
-
-**错误**: `控制器不存在: app\adminapi\controller\doctor\AppointmentController`
-
-**解决方案**:
-```bash
-# 清除缓存
-cd server
-php think clear
-
-# 检查文件是否存在
-ls -la server/app/adminapi/controller/doctor/AppointmentController.php
-
-# 检查命名空间是否正确
-head -n 5 server/app/adminapi/controller/doctor/AppointmentController.php
-```
-
-### 2. 数据库表不存在
-
-**错误**: `Table 'database.la_doctor_appointment' doesn't exist`
-
-**解决方案**:
-```bash
-# 执行SQL脚本
-mysql -u username -p database < server/sql/doctor_appointment.sql
-
-# 或在MySQL中手动执行
-mysql> source /path/to/server/sql/doctor_appointment.sql;
-```
-
-### 3. 参数验证失败
-
-**错误**: `参数错误`
-
-**解决方案**:
-- 检查请求参数是否完整
-- 检查参数类型是否正确
-- 查看验证器规则是否匹配
-
-## 测试数据
-
-### 创建测试排班
-
-```sql
-INSERT INTO `la_doctor_roster` (`doctor_id`, `date`, `period`, `status`, `quota`, `max_patients`, `create_time`, `update_time`)
-VALUES
-(1, '2026-03-04', 'morning', 1, 10, 15, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
-(1, '2026-03-04', 'afternoon', 1, 10, 15, UNIX_TIMESTAMP(), UNIX_TIMESTAMP());
-```
-
-### 创建测试预约
-
-```sql
-INSERT INTO `la_doctor_appointment` (`patient_id`, `doctor_id`, `roster_id`, `appointment_date`, `period`, `appointment_time`, `appointment_type`, `status`, `create_time`, `update_time`)
-VALUES
-(1, 1, 1, '2026-03-04', 'morning', '09:00:00', 'video', 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP());
-```
-
-## 完成状态
-
-✅ 数据库模型创建完成
-✅ 控制器创建完成
-✅ 业务逻辑层创建完成
-✅ 验证器创建完成
-✅ 数据列表创建完成
-✅ SQL脚本创建完成
-⏳ 等待数据库表创建
-⏳ 等待接口测试
-
----
-
-**创建时间**: 2024-02-26
-**最后更新**: 2024-02-26
diff --git a/server/BATCH_ROSTER_FIX.md b/server/BATCH_ROSTER_FIX.md
deleted file mode 100644
index 2aac7555..00000000
--- a/server/BATCH_ROSTER_FIX.md
+++ /dev/null
@@ -1,229 +0,0 @@
-# 批量排班接口修复说明
-
-## 问题描述
-
-接口报错:
-```
-app\common\controller\BaseLikeAdminController::success():
-Argument #2 ($data) must be of type array, bool given
-```
-
-## 问题原因
-
-`RosterLogic::batchSave()` 方法返回 `true`(布尔值),但控制器的 `success()` 方法要求第二个参数必须是数组类型。
-
-## 解决方案
-
-### 1. 修改 Logic 层返回值
-
-**修改前**:
-```php
-public static function batchSave(array $params)
-{
- // ...
- Db::commit();
- return true; // 返回布尔值
-}
-```
-
-**修改后**:
-```php
-public static function batchSave(array $params)
-{
- // ...
- Db::commit();
- return [
- 'success_count' => $successCount,
- 'create_count' => $createCount,
- 'update_count' => $updateCount,
- ]; // 返回数组,包含统计信息
-}
-```
-
-### 2. 修改控制器
-
-**修改前**:
-```php
-public function batchSave()
-{
- $params = (new RosterValidate())->post()->goCheck('batchSave');
- $result = RosterLogic::batchSave($params);
- return $this->success('批量保存成功', $result); // $result 是 bool
-}
-```
-
-**修改后**:
-```php
-public function batchSave()
-{
- $params = (new RosterValidate())->post()->goCheck('batchSave');
- $result = RosterLogic::batchSave($params);
- if ($result === false) {
- return $this->fail(RosterLogic::getError());
- }
- return $this->success('批量保存成功', $result); // $result 是 array
-}
-```
-
-## 改进内容
-
-### 1. 返回统计信息
-
-现在批量保存接口会返回详细的统计信息:
-
-```json
-{
- "code": 1,
- "msg": "批量保存成功",
- "data": {
- "success_count": 20,
- "create_count": 15,
- "update_count": 5
- }
-}
-```
-
-- `success_count`: 成功处理的记录数
-- `create_count`: 新创建的记录数
-- `update_count`: 更新的记录数
-
-### 2. 错误处理
-
-如果批量保存失败,会返回错误信息:
-
-```json
-{
- "code": 0,
- "msg": "错误信息",
- "data": []
-}
-```
-
-### 3. 同样修复了 copy 方法
-
-`copy` 方法也有同样的问题,已一并修复。
-
-## 测试验证
-
-### 测试1:批量创建
-
-**请求**:
-```json
-POST /adminapi/doctor.roster/batchSave
-{
- "rosters": [
- {
- "doctor_id": 1,
- "date": "2026-03-04",
- "period": "morning",
- "status": 1,
- "quota": 20,
- "max_patients": 30
- },
- {
- "doctor_id": 1,
- "date": "2026-03-04",
- "period": "afternoon",
- "status": 1,
- "quota": 20,
- "max_patients": 30
- }
- ]
-}
-```
-
-**响应**:
-```json
-{
- "code": 1,
- "msg": "批量保存成功",
- "data": {
- "success_count": 2,
- "create_count": 2,
- "update_count": 0
- }
-}
-```
-
-### 测试2:批量更新
-
-再次执行相同的请求:
-
-**响应**:
-```json
-{
- "code": 1,
- "msg": "批量保存成功",
- "data": {
- "success_count": 2,
- "create_count": 0,
- "update_count": 2
- }
-}
-```
-
-## 相关文件
-
-修改的文件:
-1. `server/app/adminapi/logic/doctor/RosterLogic.php` - 修改返回值类型
-2. `server/app/adminapi/controller/doctor/RosterController.php` - 添加错误处理
-
-## 注意事项
-
-1. 所有返回布尔值的 Logic 方法都应该检查是否会传递给 `success()` 方法
-2. 如果需要返回数据,应该返回数组而不是布尔值
-3. 失败时应该使用 `fail()` 方法而不是 `success()`
-
-## 最佳实践
-
-### Logic 层返回值规范
-
-```php
-// ✅ 正确:返回数组或 false
-public static function someMethod($params)
-{
- try {
- // 处理逻辑
- return ['id' => $id, 'count' => $count];
- } catch (\Exception $e) {
- self::setError($e->getMessage());
- return false;
- }
-}
-
-// ❌ 错误:返回 true
-public static function someMethod($params)
-{
- try {
- // 处理逻辑
- return true; // 不要这样做
- } catch (\Exception $e) {
- return false;
- }
-}
-```
-
-### 控制器层处理规范
-
-```php
-// ✅ 正确:检查返回值
-public function someAction()
-{
- $result = SomeLogic::someMethod($params);
- if ($result === false) {
- return $this->fail(SomeLogic::getError());
- }
- return $this->success('操作成功', $result);
-}
-
-// ❌ 错误:直接传递布尔值
-public function someAction()
-{
- $result = SomeLogic::someMethod($params);
- return $this->success('操作成功', $result); // 如果 $result 是 bool 会报错
-}
-```
-
-## 总结
-
-问题已修复,批量排班接口现在可以正常工作,并且返回详细的统计信息。
diff --git a/server/DOCTOR_ROLE_RELATIONSHIP.md b/server/DOCTOR_ROLE_RELATIONSHIP.md
deleted file mode 100644
index cccad3a7..00000000
--- a/server/DOCTOR_ROLE_RELATIONSHIP.md
+++ /dev/null
@@ -1,264 +0,0 @@
-# 医生角色关系说明
-
-## 概述
-
-医生和角色的关系通过中间表 `zyt_admin_role` 来管理。医生是具有 `role_id = 1` 角色的管理员。
-
-## 数据库表结构
-
-### 1. 管理员表 (zyt_admin)
-
-```sql
-CREATE TABLE `zyt_admin` (
- `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
- `name` varchar(50) NOT NULL COMMENT '管理员名称',
- `account` varchar(50) NOT NULL COMMENT '管理员账号',
- `avatar` varchar(255) DEFAULT NULL COMMENT '头像',
- `mobile` varchar(20) DEFAULT NULL COMMENT '手机号',
- `email` varchar(100) DEFAULT NULL COMMENT '邮箱',
- `disable` tinyint(1) DEFAULT '0' COMMENT '是否禁用',
- `create_time` int(10) unsigned DEFAULT NULL COMMENT '创建时间',
- `update_time` int(10) unsigned DEFAULT NULL COMMENT '更新时间',
- `delete_time` int(10) unsigned DEFAULT NULL COMMENT '删除时间',
- PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管理员表';
-```
-
-### 2. 角色表 (zyt_admin_role)
-
-```sql
-CREATE TABLE `zyt_admin_role` (
- `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
- `admin_id` int(11) NOT NULL COMMENT '管理员ID',
- `role_id` int(11) NOT NULL COMMENT '角色ID',
- PRIMARY KEY (`id`),
- UNIQUE KEY `uk_admin_role` (`admin_id`, `role_id`),
- KEY `idx_admin_id` (`admin_id`),
- KEY `idx_role_id` (`role_id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管理员角色中间表';
-```
-
-### 3. 角色表 (zyt_admin_role_info)
-
-```sql
-CREATE TABLE `zyt_admin_role_info` (
- `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
- `name` varchar(50) NOT NULL COMMENT '角色名称',
- `remark` varchar(255) DEFAULT NULL COMMENT '备注',
- `create_time` int(10) unsigned DEFAULT NULL COMMENT '创建时间',
- `update_time` int(10) unsigned DEFAULT NULL COMMENT '更新时间',
- PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色信息表';
-```
-
-## 角色 ID 说明
-
-| role_id | 角色名称 | 说明 |
-|---------|---------|------|
-| 1 | 医生 | 医生角色 |
-| 2 | 管理员 | 系统管理员 |
-| ... | ... | 其他角色 |
-
-## 查询医生的方式
-
-### 方式1:通过中间表查询(推荐)
-
-```php
-// 获取所有医生的 admin_id
-$adminIds = AdminRole::where('role_id', 1)->column('admin_id');
-
-// 查询医生信息
-$doctors = Admin::whereIn('id', $adminIds)
- ->where('disable', 0)
- ->select();
-```
-
-### 方式2:通过 Admin 模型的 role_id 属性
-
-```php
-// Admin 模型中有 getRoleIdAttr 方法,会自动从中间表获取 role_id
-$admin = Admin::find(1);
-$roleIds = $admin->role_id; // 返回数组 [1, 2, ...]
-
-// 检查是否是医生
-if (in_array(1, $roleIds)) {
- // 是医生
-}
-```
-
-## 后端实现
-
-### DoctorLogic.php
-
-```php
-use app\common\model\auth\AdminRole;
-
-// 获取医生列表
-$adminIds = AdminRole::where('role_id', 1)->column('admin_id');
-
-$doctors = Admin::whereIn('id', $adminIds)
- ->where('disable', 0)
- ->select();
-
-// 获取医生详情
-$hasRole = AdminRole::where('admin_id', $doctorId)
- ->where('role_id', 1)
- ->exists();
-
-if ($hasRole) {
- $doctor = Admin::find($doctorId);
-}
-```
-
-## API 端点
-
-### 获取医生列表
-
-**请求**:
-```
-GET /api/doctor/lists?page_no=1&page_size=10
-```
-
-**流程**:
-1. 从 `zyt_admin_role` 表查询所有 `role_id = 1` 的 `admin_id`
-2. 从 `zyt_admin` 表查询这些 `admin_id` 的管理员信息
-3. 过滤 `disable = 0` 的管理员
-4. 返回医生列表
-
-**响应**:
-```json
-{
- "code": 1,
- "msg": "success",
- "data": {
- "lists": [
- {
- "id": 2,
- "name": "张医生",
- "account": "doctor1",
- "avatar": "http://example.com/avatar.jpg",
- "mobile": "13800138000",
- "email": "doctor@example.com",
- "specialty": "医生",
- "title": "医生",
- "rating": "4.8"
- }
- ],
- "count": 1,
- "page_no": 1,
- "page_size": 10
- }
-}
-```
-
-### 获取医生详情
-
-**请求**:
-```
-GET /api/doctor/detail?id=2
-```
-
-**流程**:
-1. 检查 `admin_id = 2` 是否在 `zyt_admin_role` 表中有 `role_id = 1` 的记录
-2. 如果有,从 `zyt_admin` 表查询医生信息
-3. 返回医生详情
-
-### 获取医生排班
-
-**请求**:
-```
-GET /api/doctor/roster?doctor_id=2&date=2024-03-11
-```
-
-**流程**:
-1. 检查医生是否有医生角色
-2. 从 `la_doctor_roster` 表查询排班信息
-
-## 添加医生的步骤
-
-### 1. 创建管理员
-
-在后台管理系统中创建新的管理员:
-- 名称:张医生
-- 账号:doctor1
-- 头像:上传头像
-- 手机:13800138000
-- 邮箱:doctor@example.com
-
-### 2. 分配医生角色
-
-在后台管理系统中为该管理员分配医生角色(role_id = 1)。
-
-系统会自动在 `zyt_admin_role` 表中插入一条记录:
-```sql
-INSERT INTO zyt_admin_role (admin_id, role_id) VALUES (2, 1);
-```
-
-### 3. 验证
-
-调用 API 验证医生是否已添加:
-```
-GET /api/doctor/lists
-```
-
-## 常见问题
-
-### Q: 如何查询某个管理员的所有角色?
-
-A:
-```php
-$admin = Admin::find($adminId);
-$roleIds = $admin->role_id; // 返回数组
-```
-
-### Q: 如何检查某个管理员是否是医生?
-
-A:
-```php
-$hasRole = AdminRole::where('admin_id', $adminId)
- ->where('role_id', 1)
- ->exists();
-```
-
-### Q: 如何为管理员添加医生角色?
-
-A:
-```php
-AdminRole::create([
- 'admin_id' => $adminId,
- 'role_id' => 1
-]);
-```
-
-### Q: 如何移除管理员的医生角色?
-
-A:
-```php
-AdminRole::where('admin_id', $adminId)
- ->where('role_id', 1)
- ->delete();
-```
-
-### Q: 医生列表为空?
-
-A: 检查以下几点:
-1. 是否有管理员被分配了医生角色(role_id = 1)
-2. 医生的 `disable` 字段是否为 0
-3. 医生是否被软删除(`delete_time` 不为空)
-
-## 相关文件
-
-- 医生逻辑: `server/app/api/logic/DoctorLogic.php`
-- 医生控制器: `server/app/api/controller/DoctorController.php`
-- Admin 模型: `server/app/common/model/auth/Admin.php`
-- AdminRole 模型: `server/app/common/model/auth/AdminRole.php`
-
-## 总结
-
-医生是通过中间表 `zyt_admin_role` 与管理员关联的。查询医生时需要:
-
-1. 从 `zyt_admin_role` 表查询 `role_id = 1` 的所有 `admin_id`
-2. 从 `zyt_admin` 表查询这些 `admin_id` 的管理员信息
-3. 过滤 `disable = 0` 的管理员
-
-这样可以确保只查询出具有医生角色的管理员。
diff --git a/server/DOCTOR_ROSTER_BACKEND.md b/server/DOCTOR_ROSTER_BACKEND.md
deleted file mode 100644
index 2c505cfd..00000000
--- a/server/DOCTOR_ROSTER_BACKEND.md
+++ /dev/null
@@ -1,245 +0,0 @@
-# 医生排班管理 - 后端实现说明
-
-## 文件结构
-
-```
-server/app/
-├── adminapi/
-│ ├── controller/doctor/
-│ │ └── RosterController.php # 排班控制器
-│ ├── lists/doctor/
-│ │ └── RosterLists.php # 排班列表
-│ ├── logic/doctor/
-│ │ └── RosterLogic.php # 排班逻辑
-│ └── validate/doctor/
-│ └── RosterValidate.php # 排班验证器
-└── common/
- └── model/doctor/
- └── Roster.php # 排班模型
-```
-
-## 数据库表
-
-表名:`la_doctor_roster`
-
-```sql
-CREATE TABLE `la_doctor_roster` (
- `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
- `doctor_id` int(11) NOT NULL COMMENT '医生ID(对应 zyt_admin.id)',
- `date` date NOT NULL COMMENT '日期',
- `period` varchar(20) NOT NULL COMMENT '时段 morning/afternoon',
- `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1-出诊 2-停诊 3-休息 4-请假',
- `quota` int(11) DEFAULT '0' COMMENT '号源数',
- `max_patients` int(11) DEFAULT '0' COMMENT '最大接诊数',
- `booked_count` int(11) DEFAULT '0' COMMENT '已预约数',
- `remark` varchar(500) DEFAULT '',
- `create_time` int(11) NOT NULL,
- `update_time` int(11) NOT NULL,
- `delete_time` int(11) DEFAULT NULL,
- PRIMARY KEY (`id`),
- UNIQUE KEY `uk_doctor_date_period` (`doctor_id`,`date`,`period`,`delete_time`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-```
-
-## API 接口
-
-### 1. 获取排班列表
-
-**URL**: `GET /adminapi/doctor.roster/lists`
-
-**请求参数**:
-- `start_date`: 开始日期(必填)
-- `end_date`: 结束日期(必填)
-- `doctor_id`: 医生ID(可选)
-- `period`: 时段(可选,morning/afternoon)
-- `status`: 状态(可选,1/2/3/4)
-
-**返回示例**:
-```json
-{
- "code": 1,
- "msg": "success",
- "data": {
- "lists": [
- {
- "id": 1,
- "doctor_id": 1,
- "date": "2026-03-02",
- "period": "morning",
- "status": 1,
- "quota": 20,
- "max_patients": 30,
- "booked_count": 5,
- "remark": "",
- "create_time": 1709366400,
- "update_time": 1709366400
- }
- ],
- "count": 1,
- "page_no": 1,
- "page_size": 15
- }
-}
-```
-
-### 2. 保存排班
-
-**URL**: `POST /adminapi/doctor.roster/save`
-
-**请求参数**:
-```json
-{
- "id": 1,
- "doctor_id": 1,
- "date": "2026-03-02",
- "period": "morning",
- "status": 1,
- "quota": 20,
- "max_patients": 30,
- "remark": ""
-}
-```
-
-**说明**:
-- `id` 存在时为更新,不存在时为新增
-- `status`: 1-出诊 2-停诊 3-休息 4-请假
-- `period`: morning-上午 afternoon-下午
-
-### 3. 删除排班
-
-**URL**: `POST /adminapi/doctor.roster/delete`
-
-**请求参数**:
-```json
-{
- "id": 1
-}
-```
-
-### 4. 排班详情
-
-**URL**: `GET /adminapi/doctor.roster/detail`
-
-**请求参数**:
-- `id`: 排班ID
-
-### 5. 批量保存排班
-
-**URL**: `POST /adminapi/doctor.roster/batchSave`
-
-**请求参数**:
-```json
-{
- "rosters": [
- {
- "doctor_id": 1,
- "date": "2026-03-02",
- "period": "morning",
- "status": 1,
- "quota": 20,
- "max_patients": 30
- },
- {
- "doctor_id": 1,
- "date": "2026-03-02",
- "period": "afternoon",
- "status": 1,
- "quota": 20,
- "max_patients": 30
- }
- ]
-}
-```
-
-### 6. 复制排班
-
-**URL**: `POST /adminapi/doctor.roster/copy`
-
-**请求参数**:
-```json
-{
- "source_start_date": "2026-03-02",
- "source_end_date": "2026-03-08",
- "target_start_date": "2026-03-09",
- "doctor_id": 1
-}
-```
-
-**说明**:
-- 将源日期范围的排班复制到目标日期
-- `doctor_id` 可选,不传则复制所有医生的排班
-
-## 核心逻辑
-
-### RosterLogic::save()
-
-保存排班逻辑:
-1. 检查是否有 `id` 参数
-2. 有 `id` 则更新,无 `id` 则新增
-3. 自动设置 `create_time` 和 `update_time`
-
-### RosterLogic::batchSave()
-
-批量保存逻辑:
-1. 开启事务
-2. 遍历排班数组
-3. 检查是否已存在(根据 doctor_id + date + period)
-4. 存在则更新,不存在则新增
-5. 提交事务
-
-### RosterLogic::copy()
-
-复制排班逻辑:
-1. 获取源日期范围的排班数据
-2. 计算日期差
-3. 为每条排班创建新记录,日期加上日期差
-4. 检查目标日期是否已存在排班,避免重复
-
-## 数据验证
-
-### RosterValidate
-
-验证规则:
-- `doctor_id`: 必填
-- `date`: 必填,日期格式
-- `period`: 必填,只能是 morning 或 afternoon
-- `status`: 必填,只能是 1/2/3/4
-- `quota`: 数字
-- `max_patients`: 数字
-
-## 模型特性
-
-### Roster Model
-
-自动属性:
-- `status_desc`: 状态描述(出诊/停诊/休息/请假)
-- `period_desc`: 时段描述(上午/下午)
-- `date_text`: 日期文本
-
-软删除:
-- 使用 `delete_time` 字段
-- 删除时不会真正删除记录,只是设置删除时间
-
-## 注意事项
-
-1. **唯一索引**: `doctor_id + date + period + delete_time` 确保同一医生同一天同一时段只有一条有效排班
-
-2. **医生数据来源**: 医生数据来自 `zyt_admin` 表,`role_id=1`
-
-3. **时间格式**:
- - 数据库存储使用整型时间戳
- - 日期字段使用 DATE 类型
-
-4. **事务处理**: 批量操作和复制操作使用事务确保数据一致性
-
-5. **错误处理**: 所有 Logic 方法都有异常捕获,失败时返回 false 并设置错误信息
-
-## 测试建议
-
-1. 测试新增排班
-2. 测试更新排班
-3. 测试删除排班
-4. 测试同一时段重复排班(应该失败)
-5. 测试批量保存
-6. 测试复制排班
-7. 测试日期范围查询
diff --git a/server/ROSTER_DATE_SEARCH_FIX.md b/server/ROSTER_DATE_SEARCH_FIX.md
deleted file mode 100644
index 5552d8fb..00000000
--- a/server/ROSTER_DATE_SEARCH_FIX.md
+++ /dev/null
@@ -1,142 +0,0 @@
-# 医生排班日期范围搜索修复
-
-## 问题描述
-
-访问 `adminapi/doctor.roster/lists?start_date=2026-03-02&end_date=2026-03-08` 时,`searchWhere` 为空,无法按日期范围筛选排班数据。
-
-## 根本原因
-
-`ListsSearchTrait` 中的 `between` 搜索类型只支持固定的参数名 `start` 和 `end`,而不支持自定义参数名如 `start_date` 和 `end_date`。
-
-### 原始代码问题
-
-```php
-public function setSearch(): array
-{
- return [
- '=' => ['doctor_id', 'period', 'status'],
- 'between' => ['date' => ['start_date', 'end_date']], // ❌ 不会生效
- ];
-}
-```
-
-`ListsSearchTrait` 中的 `between` 处理逻辑:
-
-```php
-case 'between':
- if (empty($this->start) || empty($this->end)) { // 只检查 start 和 end
- break;
- }
- $where[] = [$whereFields, 'between', [$this->start, $this->end]];
- break;
-```
-
-## 解决方案
-
-在 `lists()` 和 `count()` 方法中手动处理日期范围搜索。
-
-### 修复后的代码
-
-```php
-public function setSearch(): array
-{
- return [
- '=' => ['doctor_id', 'period', 'status'],
- ];
-}
-
-public function lists(): array
-{
- $where = $this->searchWhere;
-
- // 处理日期范围搜索
- if (!empty($this->params['start_date']) && !empty($this->params['end_date'])) {
- $where[] = ['date', 'between', [$this->params['start_date'], $this->params['end_date']]];
- }
-
- $lists = Roster::where($where)
- ->field(['id', 'doctor_id', 'date', 'period', 'status', 'quota', 'max_patients', 'booked_count', 'remark', 'create_time', 'update_time'])
- ->order(['date' => 'asc', 'period' => 'asc'])
- ->limit($this->limitOffset, $this->limitLength)
- ->select()
- ->toArray();
-
- return $lists;
-}
-
-public function count(): int
-{
- $where = $this->searchWhere;
-
- // 处理日期范围搜索
- if (!empty($this->params['start_date']) && !empty($this->params['end_date'])) {
- $where[] = ['date', 'between', [$this->params['start_date'], $this->params['end_date']]];
- }
-
- return Roster::where($where)->count();
-}
-```
-
-## 测试
-
-### 测试 URL
-
-```
-GET /adminapi/doctor.roster/lists?start_date=2026-03-02&end_date=2026-03-08
-```
-
-### 预期结果
-
-- ✅ 返回 2026-03-02 到 2026-03-08 之间的排班数据
-- ✅ `where` 条件包含日期范围:`['date', 'between', ['2026-03-02', '2026-03-08']]`
-
-### 其他搜索参数
-
-```
-GET /adminapi/doctor.roster/lists?doctor_id=1&start_date=2026-03-02&end_date=2026-03-08
-GET /adminapi/doctor.roster/lists?period=1&status=1&start_date=2026-03-02&end_date=2026-03-08
-```
-
-## 其他说明
-
-### ListsSearchTrait 支持的搜索类型
-
-1. **等值查询**:`=`, `<>`, `>`, `>=`, `<`, `<=`, `in`
-2. **模糊查询**:`%like%`, `%like`, `like%`
-3. **时间范围**:`between_time`(使用 `start_time` 和 `end_time`)
-4. **范围查询**:`between`(使用 `start` 和 `end`)
-5. **集合查询**:`find_in_set`
-
-### 如果要使用框架的 between
-
-需要修改前端传递的参数名:
-
-```javascript
-// 前端修改
-params: {
- start: '2026-03-02', // 改为 start
- end: '2026-03-08' // 改为 end
-}
-```
-
-```php
-// 后端配置
-public function setSearch(): array
-{
- return [
- '=' => ['doctor_id', 'period', 'status'],
- 'between' => 'date', // 简化配置
- ];
-}
-```
-
-但这种方式不够语义化,不推荐使用。
-
-## 已修复的文件
-
-- ✅ `server/app/adminapi/lists/doctor/RosterLists.php`
-
----
-
-**修复日期:** 2024-03-04
-**状态:** ✅ 完成
diff --git a/server/TEST_BATCH_ROSTER.md b/server/TEST_BATCH_ROSTER.md
deleted file mode 100644
index d2bc1160..00000000
--- a/server/TEST_BATCH_ROSTER.md
+++ /dev/null
@@ -1,395 +0,0 @@
-# 批量排班接口测试
-
-## 接口信息
-
-**URL**: `POST /adminapi/doctor.roster/batchSave`
-
-**Content-Type**: `application/json`
-
-## 测试用例
-
-### 测试1:批量创建工作日排班
-
-为2位医生创建一周工作日的排班(周一至周五,上午和下午)。
-
-**请求体**:
-```json
-{
- "rosters": [
- {
- "doctor_id": 1,
- "date": "2026-03-02",
- "period": "morning",
- "status": 1,
- "quota": 20,
- "max_patients": 30,
- "remark": "工作日排班"
- },
- {
- "doctor_id": 1,
- "date": "2026-03-02",
- "period": "afternoon",
- "status": 1,
- "quota": 20,
- "max_patients": 30,
- "remark": "工作日排班"
- },
- {
- "doctor_id": 1,
- "date": "2026-03-03",
- "period": "morning",
- "status": 1,
- "quota": 20,
- "max_patients": 30,
- "remark": "工作日排班"
- },
- {
- "doctor_id": 1,
- "date": "2026-03-03",
- "period": "afternoon",
- "status": 1,
- "quota": 20,
- "max_patients": 30,
- "remark": "工作日排班"
- },
- {
- "doctor_id": 2,
- "date": "2026-03-02",
- "period": "morning",
- "status": 1,
- "quota": 15,
- "max_patients": 25,
- "remark": "工作日排班"
- },
- {
- "doctor_id": 2,
- "date": "2026-03-02",
- "period": "afternoon",
- "status": 1,
- "quota": 15,
- "max_patients": 25,
- "remark": "工作日排班"
- }
- ]
-}
-```
-
-**预期结果**:
-```json
-{
- "code": 1,
- "msg": "批量保存成功",
- "data": true
-}
-```
-
-### 测试2:批量设置周末休息
-
-为所有医生设置周末休息。
-
-**请求体**:
-```json
-{
- "rosters": [
- {
- "doctor_id": 1,
- "date": "2026-03-07",
- "period": "morning",
- "status": 3,
- "quota": 0,
- "max_patients": 0,
- "remark": "周末休息"
- },
- {
- "doctor_id": 1,
- "date": "2026-03-07",
- "period": "afternoon",
- "status": 3,
- "quota": 0,
- "max_patients": 0,
- "remark": "周末休息"
- },
- {
- "doctor_id": 1,
- "date": "2026-03-08",
- "period": "morning",
- "status": 3,
- "quota": 0,
- "max_patients": 0,
- "remark": "周末休息"
- },
- {
- "doctor_id": 1,
- "date": "2026-03-08",
- "period": "afternoon",
- "status": 3,
- "quota": 0,
- "max_patients": 0,
- "remark": "周末休息"
- }
- ]
-}
-```
-
-### 测试3:更新已存在的排班
-
-如果某个排班已存在,应该更新而不是创建新记录。
-
-**第一次请求**:
-```json
-{
- "rosters": [
- {
- "doctor_id": 1,
- "date": "2026-03-04",
- "period": "morning",
- "status": 1,
- "quota": 20,
- "max_patients": 30
- }
- ]
-}
-```
-
-**第二次请求(更新)**:
-```json
-{
- "rosters": [
- {
- "doctor_id": 1,
- "date": "2026-03-04",
- "period": "morning",
- "status": 2,
- "quota": 0,
- "max_patients": 0,
- "remark": "临时停诊"
- }
- ]
-}
-```
-
-**预期结果**: 第二次请求应该更新第一次创建的记录,而不是创建新记录。
-
-### 测试4:空数组
-
-**请求体**:
-```json
-{
- "rosters": []
-}
-```
-
-**预期结果**: 应该返回成功,但不创建任何记录。
-
-### 测试5:数据验证
-
-**请求体(缺少必填字段)**:
-```json
-{
- "rosters": [
- {
- "doctor_id": 1,
- "date": "2026-03-04"
- // 缺少 period 和 status
- }
- ]
-}
-```
-
-**预期结果**: 应该返回验证错误。
-
-## 使用 cURL 测试
-
-### 基本测试
-
-```bash
-curl -X POST http://your-domain/adminapi/doctor.roster/batchSave \
- -H "Content-Type: application/json" \
- -H "token: your-token-here" \
- -d '{
- "rosters": [
- {
- "doctor_id": 1,
- "date": "2026-03-04",
- "period": "morning",
- "status": 1,
- "quota": 20,
- "max_patients": 30
- }
- ]
- }'
-```
-
-### 批量测试(10条记录)
-
-```bash
-curl -X POST http://your-domain/adminapi/doctor.roster/batchSave \
- -H "Content-Type: application/json" \
- -H "token: your-token-here" \
- -d @batch_roster_data.json
-```
-
-**batch_roster_data.json**:
-```json
-{
- "rosters": [
- {"doctor_id": 1, "date": "2026-03-04", "period": "morning", "status": 1, "quota": 20, "max_patients": 30},
- {"doctor_id": 1, "date": "2026-03-04", "period": "afternoon", "status": 1, "quota": 20, "max_patients": 30},
- {"doctor_id": 1, "date": "2026-03-05", "period": "morning", "status": 1, "quota": 20, "max_patients": 30},
- {"doctor_id": 1, "date": "2026-03-05", "period": "afternoon", "status": 1, "quota": 20, "max_patients": 30},
- {"doctor_id": 1, "date": "2026-03-06", "period": "morning", "status": 1, "quota": 20, "max_patients": 30},
- {"doctor_id": 1, "date": "2026-03-06", "period": "afternoon", "status": 1, "quota": 20, "max_patients": 30},
- {"doctor_id": 1, "date": "2026-03-07", "period": "morning", "status": 3, "quota": 0, "max_patients": 0},
- {"doctor_id": 1, "date": "2026-03-07", "period": "afternoon", "status": 3, "quota": 0, "max_patients": 0},
- {"doctor_id": 1, "date": "2026-03-08", "period": "morning", "status": 3, "quota": 0, "max_patients": 0},
- {"doctor_id": 1, "date": "2026-03-08", "period": "afternoon", "status": 3, "quota": 0, "max_patients": 0}
- ]
-}
-```
-
-## 验证结果
-
-### 1. 检查数据库
-
-```sql
--- 查看批量创建的排班
-SELECT * FROM la_doctor_roster
-WHERE doctor_id = 1
- AND date BETWEEN '2026-03-04' AND '2026-03-08'
-ORDER BY date, period;
-```
-
-### 2. 检查唯一索引
-
-```sql
--- 尝试插入重复记录,应该失败
-INSERT INTO la_doctor_roster (doctor_id, date, period, status, create_time, update_time)
-VALUES (1, '2026-03-04', 'morning', 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP());
--- 错误: Duplicate entry '1-2026-03-04-morning' for key 'uk_doctor_date_period'
-```
-
-### 3. 检查更新逻辑
-
-```sql
--- 第一次插入
--- 第二次用相同的 doctor_id, date, period 但不同的 status
--- 应该更新而不是插入新记录
-SELECT COUNT(*) FROM la_doctor_roster
-WHERE doctor_id = 1 AND date = '2026-03-04' AND period = 'morning';
--- 应该返回 1,不是 2
-```
-
-## 性能测试
-
-### 测试100条记录
-
-```bash
-# 生成100条记录的JSON
-php generate_test_data.php 100 > test_100.json
-
-# 测试
-time curl -X POST http://your-domain/adminapi/doctor.roster/batchSave \
- -H "Content-Type: application/json" \
- -H "token: your-token-here" \
- -d @test_100.json
-```
-
-**预期**: 应该在2秒内完成
-
-### 测试500条记录
-
-```bash
-php generate_test_data.php 500 > test_500.json
-
-time curl -X POST http://your-domain/adminapi/doctor.roster/batchSave \
- -H "Content-Type: application/json" \
- -H "token: your-token-here" \
- -d @test_500.json
-```
-
-**预期**: 应该在10秒内完成
-
-## 错误处理测试
-
-### 1. 事务回滚测试
-
-在批量数据中插入一条错误数据,整个批量操作应该回滚。
-
-```json
-{
- "rosters": [
- {
- "doctor_id": 1,
- "date": "2026-03-04",
- "period": "morning",
- "status": 1,
- "quota": 20,
- "max_patients": 30
- },
- {
- "doctor_id": 999999,
- "date": "2026-03-04",
- "period": "morning",
- "status": 1,
- "quota": 20,
- "max_patients": 30
- }
- ]
-}
-```
-
-**预期**: 如果第二条记录失败,第一条也不应该被保存。
-
-### 2. 日期格式错误
-
-```json
-{
- "rosters": [
- {
- "doctor_id": 1,
- "date": "2026/03/04",
- "period": "morning",
- "status": 1,
- "quota": 20,
- "max_patients": 30
- }
- ]
-}
-```
-
-**预期**: 返回日期格式错误
-
-## 常见问题排查
-
-### 问题1: 批量保存失败
-
-**检查**:
-1. 数据库连接是否正常
-2. 表是否存在
-3. 字段类型是否匹配
-4. 唯一索引是否正确
-
-### 问题2: 部分记录保存失败
-
-**检查**:
-1. 查看错误日志
-2. 检查是否有数据验证失败
-3. 检查是否有外键约束
-
-### 问题3: 性能问题
-
-**优化**:
-1. 减少单次批量的记录数
-2. 添加数据库索引
-3. 使用批量插入而不是循环插入
-
-## 总结
-
-批量排班接口已经完整实现,包括:
-- ✅ 批量创建排班
-- ✅ 批量更新排班
-- ✅ 事务处理
-- ✅ 数据验证
-- ✅ 错误处理
-
-可以正常使用了!
diff --git a/server/TEST_ROSTER_API.md b/server/TEST_ROSTER_API.md
deleted file mode 100644
index 193b50b3..00000000
--- a/server/TEST_ROSTER_API.md
+++ /dev/null
@@ -1,101 +0,0 @@
-# 排班接口测试
-
-## 测试前准备
-
-1. 确保数据库已创建 `la_doctor_roster` 表
-2. 确保 `zyt_admin` 表中有 `role_id=1` 的用户(医生)
-
-## 测试步骤
-
-### 1. 测试获取排班列表
-
-```bash
-GET /adminapi/doctor.roster/lists?start_date=2026-03-02&end_date=2026-03-08
-```
-
-预期结果:返回空列表或已有的排班数据
-
-### 2. 测试新增排班
-
-```bash
-POST /adminapi/doctor.roster/save
-Content-Type: application/json
-
-{
- "doctor_id": 1,
- "date": "2026-03-03",
- "period": "morning",
- "status": 1,
- "quota": 20,
- "max_patients": 30,
- "remark": "测试排班"
-}
-```
-
-预期结果:返回成功,包含新增的排班ID
-
-### 3. 测试更新排班
-
-```bash
-POST /adminapi/doctor.roster/save
-Content-Type: application/json
-
-{
- "id": 1,
- "doctor_id": 1,
- "date": "2026-03-03",
- "period": "morning",
- "status": 2,
- "quota": 0,
- "max_patients": 0,
- "remark": "临时停诊"
-}
-```
-
-预期结果:返回成功,排班状态更新为停诊
-
-### 4. 测试删除排班
-
-```bash
-POST /adminapi/doctor.roster/delete
-Content-Type: application/json
-
-{
- "id": 1
-}
-```
-
-预期结果:返回成功,排班被软删除
-
-## 常见问题
-
-### 1. 控制器不存在
-
-错误:`控制器不存在:\\app\\adminapi\\controller\\doctor\\RosterController`
-
-解决:确保文件路径正确,文件名大小写匹配
-
-### 2. 类不存在
-
-错误:`Class 'app\common\model\doctor\Roster' not found`
-
-解决:检查 Model 文件是否创建,命名空间是否正确
-
-### 3. 表不存在
-
-错误:`Table 'database.la_doctor_roster' doesn't exist`
-
-解决:执行 SQL 创建表
-
-### 4. 唯一索引冲突
-
-错误:`Duplicate entry '1-2026-03-03-morning' for key 'uk_doctor_date_period'`
-
-说明:同一医生同一天同一时段已有排班,这是正常的约束
-
-## 调试技巧
-
-1. 查看日志文件:`server/runtime/log/`
-2. 开启调试模式:`.env` 中设置 `APP_DEBUG=true`
-3. 使用 Postman 或类似工具测试接口
-4. 检查数据库表结构是否正确
diff --git a/server/add_qrcode_methods.php b/server/add_qrcode_methods.php
deleted file mode 100644
index 9315e288..00000000
--- a/server/add_qrcode_methods.php
+++ /dev/null
@@ -1,271 +0,0 @@
- $qrcodeUrl,
- 'diagnosis_id' => $diagnosisId,
- 'patient_id' => $patientId
- ];
- } catch (\Exception $e) {
- self::setError($e->getMessage());
- return false;
- }
- }
-
- /**
- * @notes 获取小程序配置
- * @return array|null
- */
- private static function getMiniProgramConfig(): ?array
- {
- try {
- // 从配置表获取小程序配置
- $appId = \app\common\service\ConfigService::get('mnp_setting', 'app_id', '');
- $appSecret = \app\common\service\ConfigService::get('mnp_setting', 'app_secret', '');
-
- if (empty($appId) || empty($appSecret)) {
- return null;
- }
-
- return [
- 'app_id' => $appId,
- 'app_secret' => $appSecret
- ];
- } catch (\Exception $e) {
- return null;
- }
- }
-
- /**
- * @notes 生成微信小程序码
- * @param array $config
- * @param string $page
- * @param string $scene
- * @return string|false
- */
- private static function generateWxQrcode(array $config, string $page, string $scene)
- {
- try {
- // 记录请求参数
- \think\facade\Log::info('开始生成小程序码', [
- 'page' => $page,
- 'scene' => $scene,
- 'app_id' => $config['app_id']
- ]);
-
- // 获取access_token
- $accessToken = self::getWxAccessToken($config['app_id'], $config['app_secret']);
- if (!$accessToken) {
- throw new \Exception('获取access_token失败: ' . self::getError());
- }
-
- // 调用微信接口生成小程序码
- $url = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token={$accessToken}";
-
- $data = [
- 'scene' => $scene,
- 'page' => $page,
- 'check_path' => false,
- 'env_version' => 'release',
- 'width' => 280
- ];
-
- \think\facade\Log::info('调用微信API生成小程序码', ['data' => $data]);
-
- $response = self::httpPost($url, json_encode($data));
-
- // 检查是否返回错误(JSON格式)
- $result = json_decode($response, true);
- if (is_array($result) && isset($result['errcode']) && $result['errcode'] != 0) {
- $errorMsg = "生成小程序码失败: errcode={$result['errcode']}, errmsg=" . ($result['errmsg'] ?? '未知错误');
- \think\facade\Log::error($errorMsg);
-
- // 如果是AccessToken错误,清除缓存
- if ($result['errcode'] == 40001 || $result['errcode'] == 42001) {
- $cacheKey = 'wx_access_token_' . $config['app_id'];
- cache($cacheKey, null);
- \think\facade\Log::info('已清除无效的AccessToken缓存');
- }
-
- throw new \Exception($errorMsg);
- }
-
- // 如果不是JSON错误,说明返回的是图片二进制数据
- // 保存图片
- $filename = 'qrcode_' . $config['app_id'] . '_' . md5($scene) . '_' . time() . '.png';
- $savePath = 'uploads/qrcode/' . date('Ymd') . '/';
- $fullPath = public_path() . $savePath;
-
- if (!is_dir($fullPath)) {
- mkdir($fullPath, 0755, true);
- }
-
- $saved = file_put_contents($fullPath . $filename, $response);
-
- if (!$saved) {
- throw new \Exception('保存二维码图片失败');
- }
-
- $qrcodeUrl = request()->domain() . '/' . $savePath . $filename;
- \think\facade\Log::info('小程序码生成成功', ['url' => $qrcodeUrl]);
-
- return $qrcodeUrl;
- } catch (\Exception $e) {
- \think\facade\Log::error('generateWxQrcode异常: ' . $e->getMessage());
- self::setError($e->getMessage());
- return false;
- }
- }
-
- /**
- * @notes 获取微信access_token
- * @param string $appId
- * @param string $appSecret
- * @return string|false
- */
- private static function getWxAccessToken(string $appId, string $appSecret)
- {
- try {
- // 尝试从缓存获取
- $cacheKey = 'wx_access_token_' . $appId;
- $accessToken = cache($cacheKey);
-
- if ($accessToken) {
- return $accessToken;
- }
-
- // 从微信服务器获取
- $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$appId}&secret={$appSecret}";
- $response = self::httpGet($url);
-
- // 记录原始响应用于调试
- \think\facade\Log::info('微信AccessToken响应: ' . $response);
-
- $result = json_decode($response, true);
-
- // 检查是否有错误
- if (isset($result['errcode']) && $result['errcode'] != 0) {
- $errorMsg = "获取AccessToken失败: errcode={$result['errcode']}, errmsg=" . ($result['errmsg'] ?? '未知错误');
- \think\facade\Log::error($errorMsg);
- throw new \Exception($errorMsg);
- }
-
- if (isset($result['access_token'])) {
- // 缓存access_token,有效期7000秒(微信官方7200秒,提前200秒过期)
- cache($cacheKey, $result['access_token'], 7000);
- \think\facade\Log::info('AccessToken获取成功并已缓存');
- return $result['access_token'];
- }
-
- throw new \Exception('获取access_token失败: 响应中没有access_token字段');
- } catch (\Exception $e) {
- \think\facade\Log::error('getWxAccessToken异常: ' . $e->getMessage());
- self::setError($e->getMessage());
- return false;
- }
- }
-
- /**
- * @notes HTTP GET请求
- * @param string $url
- * @return string|false
- */
- private static function httpGet(string $url)
- {
- $ch = curl_init();
- curl_setopt($ch, CURLOPT_URL, $url);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
- curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
- curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
- $response = curl_exec($ch);
- curl_close($ch);
- return $response;
- }
-
- /**
- * @notes HTTP POST请求
- * @param string $url
- * @param string $data
- * @return string|false
- */
- private static function httpPost(string $url, string $data)
- {
- $ch = curl_init();
- curl_setopt($ch, CURLOPT_URL, $url);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
- curl_setopt($ch, CURLOPT_POST, 1);
- curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
- curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
- curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
- curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
- $response = curl_exec($ch);
- curl_close($ch);
- return $response;
- }
-PHP;
-
-// 在最后一个}之前插入方法
-$lastBrace = strrpos($content, '}');
-if ($lastBrace !== false) {
- $content = substr($content, 0, $lastBrace) . $methods . "\n}";
-} else {
- echo "错误:找不到类的结束括号\n";
- exit(1);
-}
-
-// 写回文件
-file_put_contents($file, $content);
-
-echo "✓ 方法添加成功\n";
-echo "已添加以下方法:\n";
-echo " - generateMiniProgramQrcode()\n";
-echo " - getMiniProgramConfig()\n";
-echo " - generateWxQrcode()\n";
-echo " - getWxAccessToken()\n";
-echo " - httpGet()\n";
-echo " - httpPost()\n";
diff --git a/server/check_logistics_config.php b/server/check_logistics_config.php
deleted file mode 100644
index dfc12797..00000000
--- a/server/check_logistics_config.php
+++ /dev/null
@@ -1,90 +0,0 @@
- 'shunfeng',
- 'num' => $testNumber,
- 'phone' => $phone,
- 'resultv2' => '1',
-];
-
-$paramJson = json_encode($paramArr, JSON_UNESCAPED_UNICODE);
-$sign = strtoupper(md5($paramJson . $key . $customer));
-
-$postData = http_build_query([
- 'customer' => $customer,
- 'param' => $paramJson,
- 'sign' => $sign,
-]);
-
-echo "请求参数已生成\n";
-echo "Customer: " . substr($customer, 0, 8) . "****\n";
-echo "Sign: " . substr($sign, 0, 8) . "****\n\n";
-
-echo "如需测试实际查询,请:\n";
-echo "1. 将上面的 testNumber 替换为真实运单号\n";
-echo "2. 将 phone 替换为收件人手机后4位\n";
-echo "3. 在浏览器或前端系统中测试查询功能\n\n";
-
-echo "===========================================\n";
-echo "检查完成\n";
-echo "===========================================\n";
diff --git a/server/check_tracking_status.php b/server/check_tracking_status.php
deleted file mode 100644
index d5da5e6f..00000000
--- a/server/check_tracking_status.php
+++ /dev/null
@@ -1,91 +0,0 @@
-setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
-$pdo->exec("USE `{$database}`");
-
-echo "========================================\n";
-echo "物流追踪记录状态检查\n";
-echo "========================================\n\n";
-
-// 查询所有记录
-$stmt = $pdo->query("
- SELECT
- id,
- tracking_number,
- express_company,
- current_state,
- current_state_text,
- is_signed,
- auto_update,
- next_update_time,
- FROM_UNIXTIME(next_update_time) as next_update_datetime,
- UNIX_TIMESTAMP() as now_timestamp,
- FROM_UNIXTIME(UNIX_TIMESTAMP()) as now_datetime
- FROM zyt_express_tracking
- WHERE delete_time IS NULL
-");
-
-$records = $stmt->fetchAll(PDO::FETCH_ASSOC);
-
-if (empty($records)) {
- echo "没有找到任何物流追踪记录\n";
- exit;
-}
-
-echo "找到 " . count($records) . " 条记录:\n\n";
-
-foreach ($records as $record) {
- echo "快递单号: {$record['tracking_number']}\n";
- echo "快递公司: {$record['express_company']}\n";
- echo "当前状态: {$record['current_state']} - {$record['current_state_text']}\n";
- echo "是否签收: " . ($record['is_signed'] ? '是' : '否') . "\n";
- echo "自动更新: " . ($record['auto_update'] ? '启用' : '禁用') . "\n";
- echo "下次更新时间: {$record['next_update_datetime']} ({$record['next_update_time']})\n";
- echo "当前时间: {$record['now_datetime']} ({$record['now_timestamp']})\n";
-
- $shouldUpdate = $record['auto_update'] == 1
- && $record['next_update_time'] <= $record['now_timestamp']
- && $record['is_signed'] == 0
- && !in_array($record['current_state'], ['3', '4', '5']);
-
- echo "是否应该更新: " . ($shouldUpdate ? '是' : '否') . "\n";
-
- if (!$shouldUpdate) {
- echo "不更新原因: ";
- if ($record['auto_update'] != 1) echo "自动更新未启用 ";
- if ($record['next_update_time'] > $record['now_timestamp']) echo "未到更新时间 ";
- if ($record['is_signed'] == 1) echo "已签收 ";
- if (in_array($record['current_state'], ['3', '4', '5'])) echo "状态为终态 ";
- echo "\n";
- }
-
- echo "\n" . str_repeat('-', 60) . "\n\n";
-}
-
-// 查询符合更新条件的记录数
-$stmt = $pdo->query("
- SELECT COUNT(*) as count
- FROM zyt_express_tracking
- WHERE auto_update = 1
- AND next_update_time <= UNIX_TIMESTAMP()
- AND is_signed = 0
- AND current_state NOT IN ('3', '4', '5')
- AND delete_time IS NULL
-");
-
-$result = $stmt->fetch(PDO::FETCH_ASSOC);
-echo "符合自动更新条件的记录数: {$result['count']}\n";
diff --git a/server/clear_wx_token_cache.php b/server/clear_wx_token_cache.php
deleted file mode 100644
index 59d1c97c..00000000
--- a/server/clear_wx_token_cache.php
+++ /dev/null
@@ -1,97 +0,0 @@
-initialize();
-
-echo "=== 清除微信AccessToken缓存 ===\n\n";
-
-// 获取小程序配置
-$appId = \app\common\service\ConfigService::get('mnp_setting', 'app_id', '');
-
-if (empty($appId)) {
- echo "✗ 错误: 未配置小程序AppID\n";
- echo "请先在后台配置小程序信息\n";
- exit(1);
-}
-
-echo "小程序AppID: {$appId}\n";
-
-// 清除缓存
-$cacheKey = 'wx_access_token_' . $appId;
-$oldToken = cache($cacheKey);
-
-if ($oldToken) {
- echo "旧Token: {$oldToken}\n";
- cache($cacheKey, null);
- echo "✓ 已清除缓存的AccessToken\n";
-} else {
- echo "✓ 缓存中没有AccessToken\n";
-}
-
-// 尝试重新获取
-echo "\n=== 尝试重新获取AccessToken ===\n\n";
-
-$appSecret = \app\common\service\ConfigService::get('mnp_setting', 'app_secret', '');
-
-if (empty($appSecret)) {
- echo "✗ 错误: 未配置小程序AppSecret\n";
- exit(1);
-}
-
-$url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$appId}&secret={$appSecret}";
-
-$ch = curl_init();
-curl_setopt($ch, CURLOPT_URL, $url);
-curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
-curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
-curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
-$response = curl_exec($ch);
-$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
-curl_close($ch);
-
-echo "HTTP状态码: {$httpCode}\n";
-echo "响应内容: {$response}\n\n";
-
-$result = json_decode($response, true);
-
-if (isset($result['access_token'])) {
- echo "✓ AccessToken获取成功!\n";
- echo "Token: {$result['access_token']}\n";
- echo "有效期: {$result['expires_in']}秒\n";
-
- // 缓存新Token
- cache($cacheKey, $result['access_token'], 7000);
- echo "\n✓ 已缓存新的AccessToken\n";
-
- echo "\n=== 操作完成 ===\n";
- echo "现在可以重新尝试生成小程序码了\n";
-} else {
- echo "✗ AccessToken获取失败!\n";
- if (isset($result['errcode'])) {
- echo "错误码: {$result['errcode']}\n";
- echo "错误信息: {$result['errmsg']}\n\n";
-
- // 常见错误提示
- switch ($result['errcode']) {
- case 40013:
- echo "提示: AppID无效,请检查配置\n";
- break;
- case 40125:
- echo "提示: AppSecret无效,请检查配置或重置AppSecret\n";
- break;
- case 40164:
- echo "提示: IP不在白名单中,请在微信公众平台添加服务器IP\n";
- break;
- default:
- echo "请参考微信官方文档: https://developers.weixin.qq.com/miniprogram/dev/framework/usability/PublicErrno.html\n";
- }
- }
- exit(1);
-}
diff --git a/server/debug_kuaidi100.php b/server/debug_kuaidi100.php
deleted file mode 100644
index 735c50a1..00000000
--- a/server/debug_kuaidi100.php
+++ /dev/null
@@ -1,164 +0,0 @@
- '京东快递',
- 'com' => 'jingdong', // 修正为正确的编码
- 'num' => 'JD0230761381812',
- 'phone' => ''
- ],
- [
- 'name' => '极兔速递',
- 'com' => 'jtexpress',
- 'num' => 'JT5472795424064',
- 'phone' => ''
- ],
-];
-
-foreach ($testCases as $test) {
- echo "测试: {$test['name']}\n";
- echo "-------------------------------------------\n";
- echo "快递公司编码: {$test['com']}\n";
- echo "运单号: {$test['num']}\n\n";
-
- $paramArr = [
- 'com' => $test['com'],
- 'num' => $test['num'],
- 'resultv2' => '1',
- ];
-
- if ($test['phone'] !== '') {
- $paramArr['phone'] = $test['phone'];
- }
-
- $paramJson = json_encode($paramArr, JSON_UNESCAPED_UNICODE);
- $sign = strtoupper(md5($paramJson . $key . $customer));
-
- $postBody = http_build_query([
- 'customer' => $customer,
- 'param' => $paramJson,
- 'sign' => $sign,
- ]);
-
- echo "请求参数:\n";
- echo " customer: " . substr($customer, 0, 8) . "****\n";
- echo " param: {$paramJson}\n";
- echo " sign: " . substr($sign, 0, 16) . "****\n\n";
-
- // 发送请求
- $url = 'https://poll.kuaidi100.com/poll/query.do';
-
- $ch = curl_init();
- curl_setopt($ch, CURLOPT_URL, $url);
- curl_setopt($ch, CURLOPT_POST, true);
- curl_setopt($ch, CURLOPT_POSTFIELDS, $postBody);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
- curl_setopt($ch, CURLOPT_TIMEOUT, 15);
- curl_setopt($ch, CURLOPT_HTTPHEADER, [
- 'Content-Type: application/x-www-form-urlencoded',
- ]);
- curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
- curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
-
- $response = curl_exec($ch);
- $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
- $error = curl_error($ch);
- curl_close($ch);
-
- echo "HTTP状态码: {$httpCode}\n";
-
- if ($error) {
- echo "CURL错误: {$error}\n\n";
- continue;
- }
-
- if ($response === false || $response === '') {
- echo "错误: 无响应\n\n";
- continue;
- }
-
- echo "原始响应:\n";
- echo $response . "\n\n";
-
- $json = json_decode($response, true);
- if (!is_array($json)) {
- echo "错误: 无法解析JSON\n\n";
- continue;
- }
-
- echo "解析后的响应:\n";
- echo " result: " . var_export($json['result'] ?? null, true) . "\n";
- echo " message: " . ($json['message'] ?? 'N/A') . "\n";
- echo " returnCode: " . ($json['returnCode'] ?? 'N/A') . "\n";
-
- if (isset($json['data']) && is_array($json['data'])) {
- echo " data: " . count($json['data']) . " 条轨迹记录\n";
- if (count($json['data']) > 0) {
- echo "\n 最新轨迹:\n";
- $latest = $json['data'][0];
- echo " 时间: " . ($latest['ftime'] ?? $latest['time'] ?? 'N/A') . "\n";
- echo " 内容: " . ($latest['context'] ?? 'N/A') . "\n";
- }
- } else {
- echo " data: 无数据\n";
- }
-
- echo "\n";
-}
-
-echo "===========================================\n";
-echo "调试完成\n";
-echo "===========================================\n\n";
-
-echo "常见错误码说明:\n";
-echo "-------------------------------------------\n";
-echo "200: 查询成功\n";
-echo "500: 服务器错误\n";
-echo "501: 重复提交\n";
-echo "600: 您不是合法的订阅者\n";
-echo "601: SIGN签名错误\n";
-echo "700: 订阅方的订阅数据已达到上限\n";
-echo "701: 快递公司参数异常\n";
-echo "702: 快递单号参数异常\n";
-echo "703: 查询无结果\n";
-echo "704: 快递公司识别失败\n\n";
-
-echo "如果返回 '找不到对应公司',可能原因:\n";
-echo "1. 快递公司编码错误(如:jd 应该是 jingdong)\n";
-echo "2. 快递100不支持该快递公司\n";
-echo "3. 需要使用快递100的自动识别功能\n\n";
diff --git a/server/install_tracking_tables.php b/server/install_tracking_tables.php
deleted file mode 100644
index e235331a..00000000
--- a/server/install_tracking_tables.php
+++ /dev/null
@@ -1,70 +0,0 @@
-setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
-
-// 选择数据库
-$pdo->exec("USE `{$database}`");
-
-echo "开始创建物流追踪表...\n\n";
-
-// 读取SQL文件
-$sql = file_get_contents(__DIR__ . '/database/migrations/create_express_tracking_tables.sql');
-
-// 分割SQL语句
-$statements = array_filter(
- array_map('trim', explode(';', $sql)),
- function($stmt) {
- return !empty($stmt) &&
- !preg_match('/^--/', $stmt) &&
- !preg_match('/^\/\*/', $stmt);
- }
-);
-
-$success = 0;
-$failed = 0;
-
-foreach ($statements as $statement) {
- try {
- $pdo->exec($statement);
-
- // 提取表名
- if (preg_match('/CREATE TABLE.*?`(\w+)`/i', $statement, $matches)) {
- echo "✓ 创建表: {$matches[1]}\n";
- $success++;
- }
- } catch (PDOException $e) {
- if (strpos($e->getMessage(), 'already exists') !== false) {
- if (preg_match('/CREATE TABLE.*?`(\w+)`/i', $statement, $matches)) {
- echo "- 表已存在: {$matches[1]}\n";
- }
- } else {
- echo "✗ 错误: {$e->getMessage()}\n";
- $failed++;
- }
- }
-}
-
-echo "\n========================================\n";
-echo "安装完成!\n";
-echo "========================================\n";
-echo "成功: {$success}\n";
-echo "失败: {$failed}\n";
-echo "\n";
-echo "下一步:\n";
-echo " php think express:sync # 同步现有快递单号\n";
-echo " php think express:auto-update # 测试自动更新\n";
-echo "\n";
diff --git a/ADD_DOSE_UNIT_TO_ORDER_SUMMARY.md b/server/sql/1.9.20260420/ADD_DOSE_UNIT_TO_ORDER_SUMMARY.md
similarity index 100%
rename from ADD_DOSE_UNIT_TO_ORDER_SUMMARY.md
rename to server/sql/1.9.20260420/ADD_DOSE_UNIT_TO_ORDER_SUMMARY.md
diff --git a/ADD_MISSING_FIELDS.sql b/server/sql/1.9.20260420/ADD_MISSING_FIELDS.sql
similarity index 100%
rename from ADD_MISSING_FIELDS.sql
rename to server/sql/1.9.20260420/ADD_MISSING_FIELDS.sql
diff --git a/FINAL_FIX.sql b/server/sql/1.9.20260420/FINAL_FIX.sql
similarity index 100%
rename from FINAL_FIX.sql
rename to server/sql/1.9.20260420/FINAL_FIX.sql
diff --git a/QUICK_FIX_SQL.sql b/server/sql/1.9.20260420/QUICK_FIX_SQL.sql
similarity index 100%
rename from QUICK_FIX_SQL.sql
rename to server/sql/1.9.20260420/QUICK_FIX_SQL.sql
diff --git a/TEST_PRESCRIPTION_LIBRARY.sql b/server/sql/1.9.20260420/TEST_PRESCRIPTION_LIBRARY.sql
similarity index 100%
rename from TEST_PRESCRIPTION_LIBRARY.sql
rename to server/sql/1.9.20260420/TEST_PRESCRIPTION_LIBRARY.sql
diff --git a/UPDATE_OLD_DATA.sql b/server/sql/1.9.20260420/UPDATE_OLD_DATA.sql
similarity index 100%
rename from UPDATE_OLD_DATA.sql
rename to server/sql/1.9.20260420/UPDATE_OLD_DATA.sql
diff --git a/add_bags_per_dose_field.sql b/server/sql/1.9.20260420/add_bags_per_dose_field.sql
similarity index 100%
rename from add_bags_per_dose_field.sql
rename to server/sql/1.9.20260420/add_bags_per_dose_field.sql
diff --git a/add_completion_request_field.sql b/server/sql/1.9.20260420/add_completion_request_field.sql
similarity index 100%
rename from add_completion_request_field.sql
rename to server/sql/1.9.20260420/add_completion_request_field.sql
diff --git a/add_dose_unit_to_prescription_order.sql b/server/sql/1.9.20260420/add_dose_unit_to_prescription_order.sql
similarity index 100%
rename from add_dose_unit_to_prescription_order.sql
rename to server/sql/1.9.20260420/add_dose_unit_to_prescription_order.sql
diff --git a/add_gancao_callback_fields.sql b/server/sql/1.9.20260420/add_gancao_callback_fields.sql
similarity index 100%
rename from add_gancao_callback_fields.sql
rename to server/sql/1.9.20260420/add_gancao_callback_fields.sql
diff --git a/add_is_paw_field.sql b/server/sql/1.9.20260420/add_is_paw_field.sql
similarity index 100%
rename from add_is_paw_field.sql
rename to server/sql/1.9.20260420/add_is_paw_field.sql
diff --git a/add_prescription_complete_fields.sql b/server/sql/1.9.20260420/add_prescription_complete_fields.sql
similarity index 100%
rename from add_prescription_complete_fields.sql
rename to server/sql/1.9.20260420/add_prescription_complete_fields.sql
diff --git a/add_prescription_dosage_fields.sql b/server/sql/1.9.20260420/add_prescription_dosage_fields.sql
similarity index 100%
rename from add_prescription_dosage_fields.sql
rename to server/sql/1.9.20260420/add_prescription_dosage_fields.sql
diff --git a/add_shipping_region_fields.sql b/server/sql/1.9.20260420/add_shipping_region_fields.sql
similarity index 100%
rename from add_shipping_region_fields.sql
rename to server/sql/1.9.20260420/add_shipping_region_fields.sql
diff --git a/check_and_add_indexes.sql b/server/sql/1.9.20260420/check_and_add_indexes.sql
similarity index 100%
rename from check_and_add_indexes.sql
rename to server/sql/1.9.20260420/check_and_add_indexes.sql
diff --git a/install_medicine_library.sql b/server/sql/1.9.20260420/install_medicine_library.sql
similarity index 100%
rename from install_medicine_library.sql
rename to server/sql/1.9.20260420/install_medicine_library.sql
diff --git a/optimize_appointment_indexes.sql b/server/sql/1.9.20260420/optimize_appointment_indexes.sql
similarity index 100%
rename from optimize_appointment_indexes.sql
rename to server/sql/1.9.20260420/optimize_appointment_indexes.sql
diff --git a/test_first_login.sql b/server/sql/1.9.20260420/test_first_login.sql
similarity index 100%
rename from test_first_login.sql
rename to server/sql/1.9.20260420/test_first_login.sql
diff --git a/server/test_appointment_api.php b/server/test_appointment_api.php
deleted file mode 100644
index 0b5ddda1..00000000
--- a/server/test_appointment_api.php
+++ /dev/null
@@ -1,165 +0,0 @@
- 0) {
- echo "✓ 获取时间段成功\n";
- $firstSlot = $response['data']['slots'][0];
- $appointmentTime = $firstSlot['time'];
- echo " 可用时间段数量: " . count($response['data']['slots']) . "\n";
- echo " 第一个时间段: {$appointmentTime}\n\n";
-
- // 测试2: 创建预约
- echo "测试2: 创建预约\n";
- $createData = [
- 'patient_id' => $patientId,
- 'doctor_id' => $doctorId,
- 'appointment_date' => $testDate,
- 'period' => 'morning',
- 'appointment_time' => $appointmentTime,
- 'appointment_type' => 'video',
- 'remark' => '测试预约'
- ];
-
- echo "URL: {$baseUrl}/doctor.appointment/create\n";
- echo "数据: " . json_encode($createData, JSON_UNESCAPED_UNICODE) . "\n";
-
- $url = "{$baseUrl}/doctor.appointment/create";
- $response = makeRequest($url, 'POST', $createData, $token);
- echo "响应: " . json_encode($response, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n\n";
-
- if (isset($response['data']['id'])) {
- $appointmentId = $response['data']['id'];
- echo "✓ 创建预约成功, ID: {$appointmentId}\n\n";
-
- // 测试3: 获取预约详情
- echo "测试3: 获取预约详情\n";
- $url = "{$baseUrl}/doctor.appointment/detail?id={$appointmentId}";
- $response = makeRequest($url, 'GET', null, $token);
- echo "响应: " . json_encode($response, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n\n";
-
- if (isset($response['data']['id'])) {
- echo "✓ 获取详情成功\n\n";
- } else {
- echo "✗ 获取详情失败\n\n";
- }
-
- // 测试4: 取消预约
- echo "测试4: 取消预约\n";
- $url = "{$baseUrl}/doctor.appointment/cancel";
- $response = makeRequest($url, 'POST', ['id' => $appointmentId], $token);
- echo "响应: " . json_encode($response, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n\n";
-
- if ($response['code'] == 1) {
- echo "✓ 取消预约成功\n\n";
- } else {
- echo "✗ 取消预约失败\n\n";
- }
- } else {
- echo "✗ 创建预约失败: " . ($response['msg'] ?? '未知错误') . "\n\n";
- }
-} else {
- echo "✗ 获取时间段失败或无可用时间段\n";
- echo " 请确保:\n";
- echo " 1. 数据库表已创建\n";
- echo " 2. 医生ID={$doctorId}存在\n";
- echo " 3. 该医生在{$testDate}有排班\n\n";
-}
-
-// 测试5: 获取医生可用号源数
-echo "测试5: 获取医生可用号源数\n";
-$url = "{$baseUrl}/doctor.appointment/doctorAvailability?doctor_id={$doctorId}&date={$testDate}";
-$response = makeRequest($url, 'GET', null, $token);
-echo "响应: " . json_encode($response, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n\n";
-
-if (isset($response['data']['available_count'])) {
- echo "✓ 获取可用号源数成功: " . $response['data']['available_count'] . "\n\n";
-} else {
- echo "✗ 获取可用号源数失败\n\n";
-}
-
-// 测试6: 获取预约列表
-echo "测试6: 获取预约列表\n";
-$url = "{$baseUrl}/doctor.appointment/lists?page_no=1&page_size=10";
-$response = makeRequest($url, 'GET', null, $token);
-echo "响应: " . json_encode($response, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n\n";
-
-if (isset($response['data']['lists'])) {
- echo "✓ 获取列表成功, 记录数: " . count($response['data']['lists']) . "\n\n";
-} else {
- echo "✗ 获取列表失败\n\n";
-}
-
-echo "=== 测试完成 ===\n";
-
-/**
- * 发送HTTP请求
- */
-function makeRequest($url, $method = 'GET', $data = null, $token = null)
-{
- $ch = curl_init();
-
- if ($method == 'GET' && $data) {
- $url .= '?' . http_build_query($data);
- }
-
- curl_setopt($ch, CURLOPT_URL, $url);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
- curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
- curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
-
- $headers = ['Content-Type: application/json'];
- if ($token) {
- $headers[] = 'token: ' . $token;
- }
- curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
-
- if ($method == 'POST') {
- curl_setopt($ch, CURLOPT_POST, true);
- if ($data) {
- curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
- }
- }
-
- $response = curl_exec($ch);
- $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
-
- if (curl_errno($ch)) {
- echo "CURL错误: " . curl_error($ch) . "\n";
- curl_close($ch);
- return ['code' => 0, 'msg' => 'CURL错误'];
- }
-
- curl_close($ch);
-
- $result = json_decode($response, true);
- if (!$result) {
- echo "响应解析失败, HTTP状态码: {$httpCode}\n";
- echo "原始响应: {$response}\n";
- return ['code' => 0, 'msg' => '响应解析失败'];
- }
-
- return $result;
-}
diff --git a/server/test_auto_detect.php b/server/test_auto_detect.php
deleted file mode 100644
index 46d0b9b7..00000000
--- a/server/test_auto_detect.php
+++ /dev/null
@@ -1,90 +0,0 @@
- 'auto',
- 'num' => $testNumber,
- 'resultv2' => '1',
-];
-
-$paramJson = json_encode($paramArr, JSON_UNESCAPED_UNICODE);
-$sign = strtoupper(md5($paramJson . $key . $customer));
-
-$postBody = http_build_query([
- 'customer' => $customer,
- 'param' => $paramJson,
- 'sign' => $sign,
-]);
-
-$url = 'https://poll.kuaidi100.com/poll/query.do';
-
-$ch = curl_init();
-curl_setopt($ch, CURLOPT_URL, $url);
-curl_setopt($ch, CURLOPT_POST, true);
-curl_setopt($ch, CURLOPT_POSTFIELDS, $postBody);
-curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
-curl_setopt($ch, CURLOPT_TIMEOUT, 15);
-curl_setopt($ch, CURLOPT_HTTPHEADER, [
- 'Content-Type: application/x-www-form-urlencoded',
-]);
-curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
-curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
-
-$response = curl_exec($ch);
-curl_close($ch);
-
-echo "响应:\n";
-echo $response . "\n\n";
-
-$json = json_decode($response, true);
-if (is_array($json)) {
- echo "解析:\n";
- echo " result: " . var_export($json['result'] ?? null, true) . "\n";
- echo " message: " . ($json['message'] ?? 'N/A') . "\n";
- echo " returnCode: " . ($json['returnCode'] ?? 'N/A') . "\n";
-
- if (isset($json['data']) && is_array($json['data']) && count($json['data']) > 0) {
- echo " 轨迹数量: " . count($json['data']) . "\n";
- echo "\n最新3条轨迹:\n";
- for ($i = 0; $i < min(3, count($json['data'])); $i++) {
- $trace = $json['data'][$i];
- echo " " . ($i + 1) . ". " . ($trace['ftime'] ?? $trace['time'] ?? '') . " - " . ($trace['context'] ?? '') . "\n";
- }
- }
-}
-
-echo "\n===========================================\n";
diff --git a/server/test_doctor_api.php b/server/test_doctor_api.php
deleted file mode 100644
index 0ae2e074..00000000
--- a/server/test_doctor_api.php
+++ /dev/null
@@ -1,91 +0,0 @@
- 1,
- 'page_size' => 10
-]);
-
-// 2. 测试获取医生列表(带搜索)
-echo "\n\n【测试2】获取医生列表(带搜索)\n";
-$result2 = testApi('GET', '/doctor/lists', [
- 'page_no' => 1,
- 'page_size' => 10,
- 'keyword' => '医生'
-]);
-
-// 3. 测试获取医生详情
-if ($result1 && isset($result1['data']['lists'][0])) {
- $doctorId = $result1['data']['lists'][0]['id'];
- echo "\n\n【测试3】获取医生详情 (ID: $doctorId)\n";
- $result3 = testApi('GET', '/doctor/detail', [
- 'id' => $doctorId
- ]);
-
- // 4. 测试获取医生排班
- echo "\n\n【测试4】获取医生排班 (ID: $doctorId)\n";
- $result4 = testApi('GET', '/doctor/roster', [
- 'doctor_id' => $doctorId,
- 'date' => date('Y-m-d')
- ]);
-}
-
-echo "\n\n测试完成!\n";
-?>
diff --git a/server/test_express_auto_update.php b/server/test_express_auto_update.php
deleted file mode 100644
index 2b66a4a9..00000000
--- a/server/test_express_auto_update.php
+++ /dev/null
@@ -1,62 +0,0 @@
- $trace) {
- echo " " . ($idx + 1) . ". {$trace['time']} - {$trace['context']}\n";
- }
- }
-} catch (\Exception $e) {
- echo "查询失败: {$e->getMessage()}\n";
-}
-
-echo "\n===========================================\n";
-echo "测试完成\n";
-echo "===========================================\n";
diff --git a/server/test_logistics.php b/server/test_logistics.php
deleted file mode 100644
index 5db7ba60..00000000
--- a/server/test_logistics.php
+++ /dev/null
@@ -1,135 +0,0 @@
- 'sf',
- 'number' => 'SF1234567890',
- 'phone' => '13800138000',
- 'label' => '顺丰速运'
- ],
- [
- 'company' => 'jd',
- 'number' => 'JD1234567890',
- 'phone' => '13800138000',
- 'label' => '京东快递'
- ],
-];
-
-echo "1. 检查快递100配置\n";
-echo "-------------------------------------------\n";
-
-$config = \think\facade\Config::get('logistics.kuaidi100', []);
-$enabled = !empty($config['enable']);
-$customer = $config['customer'] ?? '';
-$key = $config['key'] ?? '';
-
-if ($enabled && $customer !== '' && $key !== '') {
- echo "✓ 快递100已配置\n";
- echo " Customer: " . substr($customer, 0, 4) . "****\n";
- echo " Key: " . substr($key, 0, 4) . "****\n";
-} else {
- echo "✗ 快递100未配置或已关闭\n";
- echo " 提示:请在 .env 文件中配置 LOGISTICS_KUAIDI100_CUSTOMER 和 LOGISTICS_KUAIDI100_KEY\n";
-}
-
-echo "\n2. 测试官网查询链接生成\n";
-echo "-------------------------------------------\n";
-
-foreach ($testCases as $case) {
- echo "\n{$case['label']} ({$case['number']}):\n";
-
- $urls = ExpressTrackService::officialUrls($case['number']);
-
- echo " 顺丰官网: {$urls['sf']}\n";
- echo " 京东官网: {$urls['jd']}\n";
-}
-
-echo "\n3. 测试快递查询(需要真实运单号)\n";
-echo "-------------------------------------------\n";
-
-if (!$enabled) {
- echo "✗ 跳过:快递100未配置\n";
- echo " 提示:配置后可测试实时查询功能\n";
-} else {
- echo "提示:请将上面的测试运单号替换为真实运单号后再测试\n";
- echo "是否继续测试?(y/n): ";
-
- $handle = fopen("php://stdin", "r");
- $line = fgets($handle);
- fclose($handle);
-
- if (trim($line) === 'y') {
- foreach ($testCases as $case) {
- echo "\n测试 {$case['label']} ({$case['number']}):\n";
-
- try {
- $result = ExpressTrackService::query(
- $case['company'],
- $case['number'],
- $case['phone']
- );
-
- echo " 承运商: {$result['carrier_label']}\n";
- echo " 状态: {$result['state_text']}\n";
- echo " 数据源: {$result['source']}\n";
-
- if (!empty($result['hint'])) {
- echo " 提示: {$result['hint']}\n";
- }
-
- if (!empty($result['traces'])) {
- echo " 轨迹节点数: " . count($result['traces']) . "\n";
- echo " 最新轨迹: {$result['traces'][0]['time']} - {$result['traces'][0]['context']}\n";
- } else {
- echo " 轨迹: 暂无\n";
- }
-
- } catch (\Exception $e) {
- echo " ✗ 查询失败: {$e->getMessage()}\n";
- }
- }
- } else {
- echo "已跳过实时查询测试\n";
- }
-}
-
-echo "\n===========================================\n";
-echo "测试完成\n";
-echo "===========================================\n\n";
-
-echo "故障排查提示:\n";
-echo "1. 如果顺丰查询失败,请确认:\n";
-echo " - 运单号格式正确(通常以SF开头)\n";
-echo " - 收件人手机号后4位正确\n";
-echo " - 快递已揽收\n\n";
-
-echo "2. 如果京东查询失败,请确认:\n";
-echo " - 运单号格式正确(通常以JD开头)\n";
-echo " - 快递已揽收\n\n";
-
-echo "3. 如果快递100返回错误,请检查:\n";
-echo " - Customer 和 Key 是否正确\n";
-echo " - 账户余额是否充足\n";
-echo " - 网络连接是否正常\n\n";
-
-echo "更多帮助请查看:LOGISTICS_INTEGRATION_GUIDE.md\n";
diff --git a/server/test_tracking_query.php b/server/test_tracking_query.php
deleted file mode 100644
index 9fcc1394..00000000
--- a/server/test_tracking_query.php
+++ /dev/null
@@ -1,103 +0,0 @@
-setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
-$pdo->exec("USE `{$database}`");
-
-echo "========================================\n";
-echo "测试物流追踪查询(从数据库)\n";
-echo "========================================\n\n";
-
-$trackingNumber = 'JD0230761381812';
-
-// 1. 查询追踪主记录
-echo "[1] 查询追踪主记录...\n";
-$stmt = $pdo->prepare("
- SELECT * FROM zyt_express_tracking
- WHERE tracking_number = :tracking_number
- AND delete_time IS NULL
-");
-$stmt->execute(['tracking_number' => $trackingNumber]);
-$tracking = $stmt->fetch(PDO::FETCH_ASSOC);
-
-if (!$tracking) {
- echo "❌ 未找到追踪记录\n";
- exit(1);
-}
-
-echo "✓ 找到追踪记录\n";
-echo " 快递公司: {$tracking['express_company_name']}\n";
-echo " 当前状态: {$tracking['current_state_text']}\n";
-echo " 是否签收: " . ($tracking['is_signed'] ? '是' : '否') . "\n";
-echo " 查询次数: {$tracking['query_count']}\n";
-echo " 最后查询: " . date('Y-m-d H:i:s', $tracking['last_query_time']) . "\n\n";
-
-// 2. 查询轨迹明细
-echo "[2] 查询轨迹明细...\n";
-$stmt = $pdo->prepare("
- SELECT * FROM zyt_express_trace
- WHERE tracking_number = :tracking_number
- ORDER BY trace_time_stamp DESC
-");
-$stmt->execute(['tracking_number' => $trackingNumber]);
-$traces = $stmt->fetchAll(PDO::FETCH_ASSOC);
-
-echo "✓ 找到 " . count($traces) . " 条轨迹记录\n\n";
-
-if (!empty($traces)) {
- echo "最新3条轨迹:\n";
- foreach (array_slice($traces, 0, 3) as $trace) {
- echo " [{$trace['trace_time']}] {$trace['trace_context']}\n";
- if ($trace['location']) {
- echo " 位置: {$trace['location']}\n";
- }
- }
- echo "\n";
-}
-
-// 3. 模拟API返回格式
-echo "[3] 模拟API返回格式...\n";
-$payload = [
- 'carrier' => $tracking['express_company'],
- 'carrier_label' => $tracking['express_company_name'],
- 'kuaidi_com' => $tracking['express_company'],
- 'traces' => array_map(function($trace) {
- return [
- 'time' => $trace['trace_time'],
- 'ftime' => $trace['trace_time'],
- 'context' => $trace['trace_context'],
- 'location' => $trace['location'],
- 'status' => $trace['status'],
- 'statusCode' => $trace['status_code'],
- ];
- }, $traces),
- 'state' => $tracking['current_state'],
- 'state_text' => $tracking['current_state_text'],
- 'source' => 'database',
- 'hint' => '',
- 'last_query_time' => date('Y-m-d H:i:s', $tracking['last_query_time']),
- 'query_count' => $tracking['query_count'],
-];
-
-echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
-echo "\n\n";
-
-echo "========================================\n";
-echo "测试完成!\n";
-echo "========================================\n";
-echo "✓ 数据库查询正常\n";
-echo "✓ 数据格式兼容API\n";
-echo "✓ 可以直接返回给前端\n";
diff --git a/server/test_usersig.php b/server/test_usersig.php
deleted file mode 100644
index 7cd6c5fa..00000000
--- a/server/test_usersig.php
+++ /dev/null
@@ -1,101 +0,0 @@
-initialize();
-
-// 加载配置
-$config = config('project.trtc');
-
-echo "=== 腾讯云IM UserSig测试 ===\n\n";
-echo "SDKAppID: " . $config['sdkAppId'] . "\n";
-echo "SecretKey长度: " . strlen($config['secretKey']) . "\n";
-echo "SecretKey前10位: " . substr($config['secretKey'], 0, 10) . "...\n\n";
-
-// 测试生成UserSig
-try {
- $api = new \app\common\service\TLSSigAPIv2($config['sdkAppId'], $config['secretKey']);
-
- // 测试用户ID
- $testUserId = 'test_user_' . time();
- $expire = 86400; // 1天
-
- echo "测试用户ID: $testUserId\n";
- echo "过期时间: $expire 秒\n\n";
-
- // 生成UserSig
- $userSig = $api->genUserSig($testUserId, $expire);
-
- echo "✅ UserSig生成成功!\n";
- echo "UserSig长度: " . strlen($userSig) . "\n";
- echo "UserSig前50位: " . substr($userSig, 0, 50) . "...\n\n";
-
- // 验证UserSig
- $initTime = 0;
- $expireTime = 0;
- $userbuf = '';
- $errorMsg = '';
-
- $isValid = $api->verifySig($userSig, $testUserId, $initTime, $expireTime, $userbuf, $errorMsg);
-
- if ($isValid) {
- echo "✅ UserSig验证成功!\n";
- echo "初始化时间: " . date('Y-m-d H:i:s', $initTime) . "\n";
- echo "过期时间: " . $expireTime . " 秒\n";
- echo "到期时间: " . date('Y-m-d H:i:s', $initTime + $expireTime) . "\n\n";
- } else {
- echo "❌ UserSig验证失败: $errorMsg\n\n";
- }
-
- // 测试IM账号导入
- echo "=== 测试IM账号导入 ===\n\n";
-
- $imService = new \app\common\service\TencentImService();
- $result = $imService->importAccount($testUserId, '测试用户');
-
- if ($result['success']) {
- echo "✅ IM账号导入成功!\n";
- echo "用户ID: " . $result['userId'] . "\n";
- echo "消息: " . $result['message'] . "\n\n";
-
- echo "请登录腾讯云控制台验证:\n";
- echo "1. 进入:即时通信IM > 应用列表\n";
- echo "2. 选择SDKAppID: " . $config['sdkAppId'] . "\n";
- echo "3. 进入:账号管理\n";
- echo "4. 搜索:$testUserId\n";
- echo "5. 应该能看到该账号\n\n";
- } else {
- echo "❌ IM账号导入失败!\n";
- echo "错误信息: " . $result['message'] . "\n\n";
-
- echo "可能的原因:\n";
- echo "1. SDKAppID不正确(请确认是IM应用的SDKAppID)\n";
- echo "2. SecretKey不正确\n";
- echo "3. IM服务未开通或已过期\n";
- echo "4. 网络连接问题\n\n";
-
- echo "排查步骤:\n";
- echo "1. 登录腾讯云控制台\n";
- echo "2. 进入:即时通信IM > 应用列表\n";
- echo "3. 确认SDKAppID: " . $config['sdkAppId'] . "\n";
- echo "4. 进入应用详情 > 密钥管理\n";
- echo "5. 确认SecretKey是否正确\n";
- echo "6. 使用官方工具测试:https://console.cloud.tencent.com/im/tool-usersig\n\n";
- }
-
-} catch (\Exception $e) {
- echo "❌ 发生异常: " . $e->getMessage() . "\n";
- echo "堆栈跟踪:\n" . $e->getTraceAsString() . "\n";
-}
-
-echo "\n=== 测试完成 ===\n";
diff --git a/sync_tracking_numbers.bat b/sync_tracking_numbers.bat
deleted file mode 100644
index a2ad6f0d..00000000
--- a/sync_tracking_numbers.bat
+++ /dev/null
@@ -1,35 +0,0 @@
-@echo off
-chcp 65001 >nul
-echo ========================================
-echo 同步现有订单快递单号到物流追踪表
-echo ========================================
-echo.
-
-cd /d %~dp0server
-
-echo [1/2] 创建物流追踪表...
-php think migrate:run
-if errorlevel 1 (
- echo 错误:创建表失败
- pause
- exit /b 1
-)
-
-echo.
-echo [2/2] 同步现有快递单号...
-mysql -u root -p -e "source database/migrations/sync_existing_tracking_numbers.sql"
-if errorlevel 1 (
- echo 错误:同步失败
- pause
- exit /b 1
-)
-
-echo.
-echo ========================================
-echo 同步完成!
-echo ========================================
-echo.
-echo 现在可以运行定时任务测试:
-echo php think express:auto-update
-echo.
-pause
diff --git a/sync_tracking_numbers.sh b/sync_tracking_numbers.sh
deleted file mode 100644
index c3679944..00000000
--- a/sync_tracking_numbers.sh
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/bin/bash
-
-echo "========================================"
-echo "同步现有订单快递单号到物流追踪表"
-echo "========================================"
-echo ""
-
-cd "$(dirname "$0")/server"
-
-echo "[1/2] 创建物流追踪表..."
-php think migrate:run
-if [ $? -ne 0 ]; then
- echo "错误:创建表失败"
- exit 1
-fi
-
-echo ""
-echo "[2/2] 同步现有快递单号..."
-mysql -u root -p -e "source database/migrations/sync_existing_tracking_numbers.sql"
-if [ $? -ne 0 ]; then
- echo "错误:同步失败"
- exit 1
-fi
-
-echo ""
-echo "========================================"
-echo "同步完成!"
-echo "========================================"
-echo ""
-echo "现在可以运行定时任务测试:"
-echo " php think express:auto-update"
-echo ""
diff --git a/test_gancao_config.php b/test_gancao_config.php
deleted file mode 100644
index f0c1033e..00000000
--- a/test_gancao_config.php
+++ /dev/null
@@ -1,193 +0,0 @@
-initialize();
-
-echo "=== 甘草配置检查 ===\n\n";
-
-// 获取配置
-$config = think\facade\Config::get('gancao_scm', []);
-
-echo "1. 检查配置是否存在:\n";
-if (empty($config)) {
- echo " ❌ 配置为空,请检查 .env 文件\n";
- exit(1);
-} else {
- echo " ✓ 配置已加载\n\n";
-}
-
-echo "2. 检查各项配置:\n";
-
-$requiredFields = [
- 'enabled' => 'boolean',
- 'gateway_url' => 'string',
- 'gateway_ak' => 'string',
- 'gateway_sk' => 'string',
- 'biz_ak' => 'string',
- 'biz_sk' => 'string',
- 'callback_url' => 'string',
- 'express_type' => 'string',
- 'cradle_store' => 'string',
- 'df_id' => 'integer',
-];
-
-$hasError = false;
-
-foreach ($requiredFields as $field => $expectedType) {
- $value = $config[$field] ?? null;
- $actualType = gettype($value);
-
- echo " - {$field}: ";
-
- if ($value === null) {
- echo "❌ 未配置\n";
- $hasError = true;
- continue;
- }
-
- // 检查类型
- if ($actualType === 'array') {
- echo "❌ 错误!值是数组,应该是 {$expectedType}\n";
- echo " 实际值: " . json_encode($value, JSON_UNESCAPED_UNICODE) . "\n";
- $hasError = true;
- continue;
- }
-
- // 转换类型检查
- $typeMatch = false;
- switch ($expectedType) {
- case 'string':
- $typeMatch = is_string($value) || is_numeric($value);
- break;
- case 'integer':
- $typeMatch = is_int($value) || is_numeric($value);
- break;
- case 'boolean':
- $typeMatch = is_bool($value) || in_array($value, [0, 1, '0', '1', 'true', 'false'], true);
- break;
- }
-
- if (!$typeMatch) {
- echo "⚠️ 类型不匹配(期望: {$expectedType}, 实际: {$actualType})\n";
- echo " 值: " . var_export($value, true) . "\n";
- } else {
- echo "✓ {$actualType}";
- if ($expectedType === 'string' && strlen((string)$value) > 50) {
- echo " (长度: " . strlen((string)$value) . ", 前50字符: " . substr((string)$value, 0, 50) . "...)";
- } else {
- echo " = " . var_export($value, true);
- }
- echo "\n";
- }
-}
-
-echo "\n3. 检查可选配置:\n";
-
-$optionalFields = [
- 'default_is_decoct' => 'boolean',
- 'default_times_per_day' => 'integer',
- 'default_num_per_pack' => 'integer',
- 'default_dose_ml' => 'integer',
- 'herb_id_map' => 'array',
-];
-
-foreach ($optionalFields as $field => $expectedType) {
- $value = $config[$field] ?? null;
- $actualType = gettype($value);
-
- echo " - {$field}: ";
-
- if ($value === null) {
- echo "未配置(可选)\n";
- continue;
- }
-
- if ($expectedType === 'array' && !is_array($value)) {
- echo "⚠️ 应该是数组,实际是 {$actualType}\n";
- } elseif ($expectedType !== 'array' && is_array($value)) {
- echo "❌ 错误!值是数组,应该是 {$expectedType}\n";
- $hasError = true;
- } else {
- echo "✓ {$actualType}\n";
- }
-}
-
-echo "\n4. 测试 JSON 编码:\n";
-
-// 模拟构建提交负载
-$testPayload = [
- 'token' => 'test_token_123',
- 'df_id' => (int)($config['df_id'] ?? 101),
- 'amount' => 7,
- 'express_type' => isset($config['express_type']) ? (string)$config['express_type'] : 'sf',
- 'callback_url' => isset($config['callback_url']) ? (string)$config['callback_url'] : '',
- 'cradle_store' => isset($config['cradle_store']) ? (string)$config['cradle_store'] : 'default',
- 'express_to' => [
- 'name' => '测试患者',
- 'phone' => '13800138000',
- 'province' => '四川省',
- 'city' => '成都市',
- 'addr' => '测试地址123号',
- ],
- 'patient' => [
- 'name' => '测试患者',
- 'age' => '30',
- 'sex' => 0,
- 'phone' => '13800138000',
- ],
- 'doctor' => [
- 'name' => '测试医生',
- ],
- 'doct_advice' => [
- 'taboo' => '无',
- 'usage_time' => '饭后半小时服用',
- 'usage_brief' => '遵医嘱',
- 'others' => '',
- 'notes_doctor' => '',
- ],
- 'm_list' => [
- ['id' => 1, 'quantity' => 10.0, 'brief' => ''],
- ],
-];
-
-try {
- $json = json_encode($testPayload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
- if ($json === false) {
- echo " ❌ JSON 编码失败: " . json_last_error_msg() . "\n";
- $hasError = true;
- } else {
- echo " ✓ JSON 编码成功\n";
- echo " 负载大小: " . strlen($json) . " 字节\n";
-
- // 检查是否包含 "Array" 字符串(可能是数组转字符串的迹象)
- if (strpos($json, '"Array"') !== false || strpos($json, 'Array') !== false) {
- echo " ⚠️ 警告:JSON 中包含 'Array' 字符串,可能存在数组转字符串问题\n";
- $hasError = true;
- }
- }
-} catch (\Throwable $e) {
- echo " ❌ 异常: " . $e->getMessage() . "\n";
- $hasError = true;
-}
-
-echo "\n";
-
-if ($hasError) {
- echo "=== 检查结果: ❌ 发现错误 ===\n";
- echo "\n请修复以上错误后重试。\n";
- echo "\n常见问题:\n";
- echo "1. 配置值被错误地设置为数组\n";
- echo "2. .env 文件格式错误(例如:GANCAO_SCM_EXPRESS_TYPE=[\"sf\"] 应该是 GANCAO_SCM_EXPRESS_TYPE=sf)\n";
- echo "3. 配置文件中有多余的引号或括号\n";
- exit(1);
-} else {
- echo "=== 检查结果: ✓ 配置正常 ===\n";
- exit(0);
-}
diff --git a/test_gancao_ssl.php b/test_gancao_ssl.php
deleted file mode 100644
index 78d832da..00000000
--- a/test_gancao_ssl.php
+++ /dev/null
@@ -1,174 +0,0 @@
-initialize();
-
-echo "=== 甘草 SSL 连接测试 ===\n\n";
-
-// 获取配置
-$config = think\facade\Config::get('gancao_scm', []);
-$gatewayUrl = $config['gateway_url'] ?? '';
-
-if ($gatewayUrl === '') {
- echo "❌ 错误:未配置 GANCAO_SCM_GATEWAY_URL\n";
- exit(1);
-}
-
-echo "网关地址: {$gatewayUrl}\n\n";
-
-// 解析 URL
-$urlParts = parse_url($gatewayUrl);
-$host = $urlParts['host'] ?? '';
-$port = $urlParts['port'] ?? ($urlParts['scheme'] === 'https' ? 443 : 80);
-
-echo "1. DNS 解析测试:\n";
-$ip = gethostbyname($host);
-if ($ip === $host) {
- echo " ❌ DNS 解析失败\n";
-} else {
- echo " ✓ {$host} -> {$ip}\n";
-}
-
-echo "\n2. TCP 连接测试:\n";
-$socket = @fsockopen($host, $port, $errno, $errstr, 10);
-if (!$socket) {
- echo " ❌ TCP 连接失败: [{$errno}] {$errstr}\n";
-} else {
- echo " ✓ TCP 连接成功\n";
- fclose($socket);
-}
-
-echo "\n3. SSL/TLS 支持检测:\n";
-$sslVersions = [
- 'SSLv2' => STREAM_CRYPTO_METHOD_SSLv2_CLIENT,
- 'SSLv3' => STREAM_CRYPTO_METHOD_SSLv3_CLIENT,
- 'TLS 1.0' => STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT,
- 'TLS 1.1' => STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT,
- 'TLS 1.2' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT,
-];
-
-if (defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT')) {
- $sslVersions['TLS 1.3'] = STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT;
-}
-
-foreach ($sslVersions as $name => $method) {
- echo " - {$name}: ";
- $context = stream_context_create([
- 'ssl' => [
- 'verify_peer' => false,
- 'verify_peer_name' => false,
- ]
- ]);
- $fp = @stream_socket_client(
- "ssl://{$host}:{$port}",
- $errno,
- $errstr,
- 10,
- STREAM_CLIENT_CONNECT,
- $context
- );
- if ($fp) {
- echo "✓ 支持\n";
- fclose($fp);
- } else {
- echo "✗ 不支持\n";
- }
-}
-
-echo "\n4. cURL SSL 测试:\n";
-
-$testConfigs = [
- 'HTTP/1.0 + TLS 1.2' => [
- CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_0,
- CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1_2,
- ],
- 'HTTP/1.1 + TLS 1.2' => [
- CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
- CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1_2,
- ],
- 'HTTP/1.1 + TLS 1.2 + SECLEVEL=1' => [
- CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
- CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1_2,
- CURLOPT_SSL_CIPHER_LIST => 'DEFAULT@SECLEVEL=1',
- ],
- 'HTTP/2 + TLS 1.2' => [
- CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2_0,
- CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1_2,
- ],
-];
-
-if (defined('CURL_SSLVERSION_TLSv1_3')) {
- $testConfigs['HTTP/1.1 + TLS 1.3'] = [
- CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
- CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1_3,
- ];
-}
-
-foreach ($testConfigs as $name => $options) {
- echo " 测试 {$name}:\n";
-
- $ch = curl_init($gatewayUrl);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
- curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
- curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
- curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
- curl_setopt($ch, CURLOPT_TIMEOUT, 30);
- curl_setopt($ch, CURLOPT_HEADER, true);
- curl_setopt($ch, CURLOPT_NOBODY, true); // HEAD 请求
-
- foreach ($options as $opt => $val) {
- curl_setopt($ch, $opt, $val);
- }
-
- $result = curl_exec($ch);
- $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
- $errno = curl_errno($ch);
- $error = curl_error($ch);
-
- if ($errno === 0) {
- echo " ✓ 成功 (HTTP {$httpCode})\n";
- } else {
- echo " ❌ 失败: [{$errno}] {$error}\n";
- }
-
- curl_close($ch);
-}
-
-echo "\n5. OpenSSL 版本信息:\n";
-$opensslVersion = OPENSSL_VERSION_TEXT;
-echo " 版本: {$opensslVersion}\n";
-
-echo "\n6. cURL 版本信息:\n";
-$curlVersion = curl_version();
-echo " 版本: {$curlVersion['version']}\n";
-echo " SSL 版本: {$curlVersion['ssl_version']}\n";
-echo " 支持的协议: " . implode(', ', $curlVersion['protocols']) . "\n";
-
-echo "\n7. 测试实际 API 调用 (MAKE_TOKEN):\n";
-
-try {
- $token = \app\common\service\gancao\GancaoScmRecipelService::getToken(true);
- if ($token !== null && $token !== '') {
- echo " ✓ 成功获取 token (长度: " . strlen($token) . ")\n";
- echo " Token 前20字符: " . substr($token, 0, 20) . "...\n";
- } else {
- $error = \app\common\service\gancao\GancaoScmRecipelService::getLastGetTokenError();
- echo " ❌ 获取 token 失败: {$error}\n";
- }
-} catch (\Throwable $e) {
- echo " ❌ 异常: " . $e->getMessage() . "\n";
-}
-
-echo "\n=== 测试完成 ===\n\n";
-
-echo "建议:\n";
-echo "1. 如果 TLS 1.2 测试失败,请升级 OpenSSL 到 1.0.2 或更高版本\n";
-echo "2. 如果所有测试都失败,请检查防火墙和网络连接\n";
-echo "3. 如果只有特定配置失败,请使用成功的配置\n";
-echo "4. 联系甘草技术支持确认他们的 SSL/TLS 要求\n";
diff --git a/test_slot_generation.php b/test_slot_generation.php
deleted file mode 100644
index 0db3ed4a..00000000
--- a/test_slot_generation.php
+++ /dev/null
@@ -1,107 +0,0 @@
- 1,
- 'doctor_id' => 1,
- 'date' => '2026-03-06',
- 'period' => 'morning', // 只有上午
- 'status' => 1,
- 'quota' => 20
- ]
-];
-
-echo "模拟排班数据:\n";
-print_r($rosters);
-echo "\n";
-
-// 模拟生成时间段的逻辑
-$slots = [];
-
-foreach ($rosters as $roster) {
- if ($roster['status'] != 1) {
- echo "跳过非出诊排班: roster_id={$roster['id']}\n";
- continue;
- }
-
- $periodValue = $roster['period'];
- echo "处理排班: roster_id={$roster['id']}, period={$periodValue}\n";
-
- $startHour = null;
- $endHour = null;
-
- if ($periodValue == 1 || $periodValue === 'morning' || $periodValue === '上午') {
- $startHour = 9;
- $endHour = 12;
- echo " -> 识别为上午时段 (9:00-12:00)\n";
- } elseif ($periodValue == 2 || $periodValue === 'afternoon' || $periodValue === '下午') {
- $startHour = 14;
- $endHour = 18;
- echo " -> 识别为下午时段 (14:00-18:00)\n";
- } else {
- echo " -> 未知的时段值,跳过\n";
- continue;
- }
-
- if ($startHour === null || $endHour === null) {
- echo " -> 错误:时段参数未设置\n";
- continue;
- }
-
- // 生成15分钟间隔的时间段
- for ($hour = $startHour; $hour < $endHour; $hour++) {
- for ($minute = 0; $minute < 60; $minute += 15) {
- $time = sprintf('%02d:%02d', $hour, $minute);
- $slots[] = [
- 'time' => $time,
- 'available' => true,
- 'quota' => 1,
- 'period' => $periodValue
- ];
- }
- }
-}
-
-echo "\n生成的时间段总数: " . count($slots) . "\n";
-
-// 统计上午和下午的时间段
-$morningCount = 0;
-$afternoonCount = 0;
-
-foreach ($slots as $slot) {
- if ($slot['time'] < '12:00') {
- $morningCount++;
- } elseif ($slot['time'] >= '14:00') {
- $afternoonCount++;
- }
-}
-
-echo "上午时间段数量: {$morningCount}\n";
-echo "下午时间段数量: {$afternoonCount}\n";
-
-echo "\n前5个时间段:\n";
-foreach (array_slice($slots, 0, 5) as $slot) {
- echo " {$slot['time']} - period: {$slot['period']}\n";
-}
-
-echo "\n后5个时间段:\n";
-foreach (array_slice($slots, -5) as $slot) {
- echo " {$slot['time']} - period: {$slot['period']}\n";
-}
-
-echo "\n预期结果:\n";
-echo " - 总数应该是 12 (9:00-11:45, 每15分钟一个)\n";
-echo " - 上午时间段: 12\n";
-echo " - 下午时间段: 0\n";
-echo " - 最后一个时间段应该是 11:45\n";
-
-if (count($slots) == 12 && $morningCount == 12 && $afternoonCount == 0) {
- echo "\n✓ 测试通过!逻辑正确。\n";
-} else {
- echo "\n✗ 测试失败!逻辑有问题。\n";
-}
diff --git a/医生资质图片功能说明.md b/医生资质图片功能说明.md
deleted file mode 100644
index d5b49a24..00000000
--- a/医生资质图片功能说明.md
+++ /dev/null
@@ -1,362 +0,0 @@
-# 医生资质图片功能说明
-
-## 功能概述
-为医生档案添加资质图片上传功能,支持上传多张资质证书、荣誉证书等图片,并在医生详情页面展示。
-
-## 实现内容
-
-### 1. 数据库修改
-**文件**: `server/sql/add_doctor_qualification_images.sql`
-
-添加了 `qualification_images` 字段到医生表(la_admin/zyt_admin):
-- 字段类型:text
-- 存储格式:JSON数组
-- 示例数据:`["https://example.com/image1.jpg", "https://example.com/image2.jpg"]`
-
-**执行SQL**:
-```sql
-ALTER TABLE `la_admin` ADD COLUMN `qualification_images` text NULL COMMENT '医生资质图片(JSON数组格式)' AFTER `license_no`;
-```
-
-如果使用 `zyt_` 前缀,请将 `la_admin` 替换为 `zyt_admin`。
-
-### 2. 后端修改
-
-#### 2.1 管理员逻辑层
-**文件**: `server/app/adminapi/logic/auth/AdminLogic.php`
-
-在 `add`、`edit` 和 `detail` 方法中添加 `qualification_images` 字段处理:
-
-**添加方法 (add)**:
-```php
-// 处理资质图片:如果是数组则转为JSON字符串
-$qualificationImages = '';
-if (isset($params['qualification_images'])) {
- if (is_array($params['qualification_images'])) {
- $qualificationImages = json_encode($params['qualification_images'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
- } else {
- $qualificationImages = $params['qualification_images'];
- }
-}
-
-$admin = Admin::create([
- // ... 其他字段
- 'qualification_images' => $qualificationImages,
- // ... 其他字段
-]);
-```
-
-**编辑方法 (edit)**:
-```php
-// 处理资质图片:如果是数组则转为JSON字符串
-$qualificationImages = '';
-if (isset($params['qualification_images'])) {
- if (is_array($params['qualification_images'])) {
- $qualificationImages = json_encode($params['qualification_images'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
- } else {
- $qualificationImages = $params['qualification_images'];
- }
-}
-
-$data = [
- // ... 其他字段
- 'qualification_images' => $qualificationImages,
- // ... 其他字段
-];
-```
-
-**数据处理说明**:
-- 前端提交的数据格式:`["https://example.com/image1.jpg", "https://example.com/image2.jpg"]`(数组)
-- 后端存储格式:`["https://example.com/image1.jpg","https://example.com/image2.jpg"]`(JSON字符串)
-- 使用 `JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES` 确保中文和URL正确编码
-
-**详情方法 (detail)**:
-```php
-$admin = Admin::field([
- // ... 其他字段
- 'qualification_images',
- // ... 其他字段
-])->findOrEmpty($params['id'])->toArray();
-
-// 将资质图片JSON字符串转换为数组,供前端组件使用
-if (!empty($admin['qualification_images'])) {
- try {
- $images = json_decode($admin['qualification_images'], true);
- if (is_array($images)) {
- $admin['qualification_images'] = $images;
- }
- } catch (\Exception $e) {
- // 解析失败时保持原值
- Log::error('解析资质图片失败: ' . $e->getMessage());
- }
-}
-```
-
-**数据流转说明**:
-1. **前端提交** → 数组格式 `["url1", "url2"]`
-2. **后端接收** → 转换为JSON字符串存储到数据库
-3. **后端返回** → 将JSON字符串转换回数组格式
-4. **前端接收** → 数组格式,`material-picker` 组件可直接使用
-
-#### 2.2 医生详情接口
-**文件**: `server/app/api/logic/DoctorLogic.php`
-
-修改 `getDoctorDetail` 方法:
-- 在查询字段中添加 `qualification_images`
-- 在返回数据中包含 `qualification_images` 字段
-
-```php
-$doctor = Admin::where('id', $doctorId)
- ->where('disable', 0)
- ->field(['id', 'name', 'account', 'avatar','specialty','license_no','qualification_images','enable_image_consult','enable_video_consult','enable_charge'])
- ->find();
-
-return [
- // ... 其他字段
- 'qualification_images' => $doctorData['qualification_images'] ?? '',
- // ... 其他字段
-];
-```
-
-### 3. 前端修改
-
-#### 3.1 管理后台 - 医生编辑页面
-**文件**: `admin/src/views/consumer/doctor/edit.vue`
-
-**新增内容**:
-1. 在"执业证书编号"字段后添加"资质图片"上传组件
-2. 使用 `material-picker` 组件,支持最多上传9张图片
-3. 在 `formData` 中添加 `qualification_images` 字段
-
-**代码示例**:
-```vue
-
-
-
-
-
-
-
上传医生资质证书、荣誉证书等图片,最多9张,建议尺寸:800*600px
-
-
-```
-
-#### 3.2 小程序端 - 医生详情页面
-**文件**: `TUICallKit-Vue3/doctor/pages/doctor/doctor.vue`
-
-**新增内容**:
-1. 在"擅长领域"和"专业领域"之间添加"医生资质"展示区域
-2. 使用3列网格布局展示资质图片
-3. 点击图片可预览大图
-4. 使用 computed 属性解析 JSON 格式的图片数据
-
-**代码示例**:
-```vue
-
-
-```
-
-**JavaScript逻辑**:
-```javascript
-// 医生资质图片
-const qualificationImages = computed(() => {
- if (!doctor.value?.qualification_images) return []
- try {
- const images = typeof doctor.value.qualification_images === 'string'
- ? JSON.parse(doctor.value.qualification_images)
- : doctor.value.qualification_images
- return Array.isArray(images) ? images : []
- } catch (e) {
- console.error('解析资质图片失败:', e)
- return []
- }
-})
-
-// 预览资质图片
-const previewImage = (current) => {
- uni.previewImage({
- current: current,
- urls: qualificationImages.value
- })
-}
-```
-
-**样式**:
-```scss
-.qualification-section {
- margin-bottom: 96rpx;
-}
-
-.qualification-grid {
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 24rpx;
-}
-
-.qualification-image {
- width: 100%;
- height: 200rpx;
- border-radius: 16rpx;
- background: $surface-high;
- object-fit: cover;
-}
-```
-
-## 相关接口
-
-### 管理后台接口
-
-#### 1. 添加医生
-- **接口**: `POST /adminapi/auth.admin/add`
-- **参数**:
- - `qualification_images`: 资质图片(字符串,多个图片URL用逗号分隔或JSON数组格式)
- - 其他医生信息字段...
-
-#### 2. 编辑医生
-- **接口**: `POST /adminapi/auth.admin/edit`
-- **参数**:
- - `id`: 医生ID
- - `qualification_images`: 资质图片
- - 其他医生信息字段...
-
-#### 3. 医生详情
-- **接口**: `GET /adminapi/auth.admin/detail`
-- **参数**:
- - `id`: 医生ID
-- **返回**: 包含 `qualification_images` 字段
-
-### 小程序端接口
-
-#### 医生详情
-- **接口**: `GET /api/doctor/detail`
-- **参数**:
- - `id`: 医生ID
-- **返回**: 包含 `qualification_images` 字段
-
-## 使用流程
-
-### 管理员操作流程
-1. 登录管理后台
-2. 进入"医生管理"页面
-3. 点击"编辑"或"新增"医生
-4. 在"专业信息"部分找到"资质图片"字段
-5. 点击上传按钮,选择资质证书图片(最多9张)
-6. 保存医生信息
-
-### 用户查看流程
-1. 打开小程序
-2. 进入医生详情页面
-3. 在"擅长领域"下方查看"医生资质"部分
-4. 点击任意资质图片可放大预览
-5. 左右滑动可查看所有资质图片
-
-## 数据格式
-
-### 前端提交格式(管理后台)
-前端 `material-picker` 组件提交的数据格式:
-```javascript
-// 单张图片
-["https://www.d.com/uploads/images/20260317/20260317111180ffc63266.png"]
-
-// 多张图片
-[
- "https://www.d.com/uploads/images/20260317/image1.png",
- "https://www.d.com/uploads/images/20260317/image2.png",
- "https://www.d.com/uploads/images/20260317/image3.png"
-]
-```
-
-### 后端存储格式
-后端将数组转换为JSON字符串存储在数据库中:
-```json
-["https://www.d.com/uploads/images/20260317/20260317111180ffc63266.png"]
-```
-
-**转换代码**:
-```php
-if (is_array($params['qualification_images'])) {
- $qualificationImages = json_encode($params['qualification_images'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
-}
-```
-
-### API返回格式
-
-#### 管理后台详情接口返回
-```json
-{
- "code": 1,
- "msg": "success",
- "data": {
- "id": 1,
- "name": "李医生",
- "qualification_images": [
- "https://www.d.com/uploads/images/20260317/image1.png",
- "https://www.d.com/uploads/images/20260317/image2.png"
- ],
- // ... 其他字段
- }
-}
-```
-注意:管理后台返回的是数组格式,方便 `material-picker` 组件直接使用
-
-#### 小程序端详情接口返回
-```json
-{
- "code": 1,
- "msg": "success",
- "data": {
- "id": 1,
- "name": "李医生",
- "qualification_images": "[\"https://www.d.com/uploads/images/20260317/image1.png\",\"https://www.d.com/uploads/images/20260317/image2.png\"]",
- // ... 其他字段
- }
-}
-```
-注意:小程序端返回的是JSON字符串格式,需要前端解析
-
-### 小程序端解析
-小程序端需要将JSON字符串解析为数组:
-```javascript
-const qualificationImages = computed(() => {
- if (!doctor.value?.qualification_images) return []
- try {
- const images = typeof doctor.value.qualification_images === 'string'
- ? JSON.parse(doctor.value.qualification_images)
- : doctor.value.qualification_images
- return Array.isArray(images) ? images : []
- } catch (e) {
- console.error('解析资质图片失败:', e)
- return []
- }
-})
-```
-
-## 注意事项
-
-1. **图片数量限制**: 最多支持上传9张资质图片
-2. **图片格式**: 建议使用 jpg、jpeg、png 格式
-3. **图片尺寸**: 建议尺寸为 800*600px,以保证清晰度
-4. **存储格式**: 后端以JSON数组格式存储,前端需要解析
-5. **容错处理**: 前端已添加JSON解析失败的容错处理
-6. **显示条件**: 只有当医生有资质图片时才显示"医生资质"区域
-7. **预览功能**: 使用uni.previewImage实现图片预览和滑动查看
-
-## 扩展建议
-
-1. **图片压缩**: 可以在上传时对图片进行压缩,减少存储空间
-2. **水印添加**: 可以为资质图片添加水印,防止盗用
-3. **审核机制**: 可以添加资质图片审核功能,确保真实性
-4. **过期提醒**: 可以为资质证书添加有效期,到期前提醒更新
-5. **分类管理**: 可以将资质图片分类(如执业证书、荣誉证书等)
diff --git a/医生资质图片功能部署指南.md b/医生资质图片功能部署指南.md
deleted file mode 100644
index e69de29b..00000000
diff --git a/医生资质图片数据流转说明.md b/医生资质图片数据流转说明.md
deleted file mode 100644
index f4b0da61..00000000
--- a/医生资质图片数据流转说明.md
+++ /dev/null
@@ -1,277 +0,0 @@
-# 医生资质图片数据流转说明
-
-## 完整数据流转过程
-
-### 1. 添加/编辑医生(管理后台)
-
-```
-┌─────────────────────────────────────────────────────────────────┐
-│ 前端(Vue3) │
-├─────────────────────────────────────────────────────────────────┤
-│ │
-│ 用户上传图片 │
-│ ↓ │
-│ material-picker 组件 │
-│ ↓ │
-│ formData.qualification_images = [ │
-│ "https://www.d.com/uploads/images/20260317/image1.png", │
-│ "https://www.d.com/uploads/images/20260317/image2.png" │
-│ ] │
-│ ↓ │
-│ 提交表单(POST请求) │
-│ │
-└─────────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────────┐
-│ 后端接口(AdminController) │
-├─────────────────────────────────────────────────────────────────┤
-│ │
-│ 接收参数: │
-│ $params['qualification_images'] = [ │
-│ "https://www.d.com/uploads/images/20260317/image1.png", │
-│ "https://www.d.com/uploads/images/20260317/image2.png" │
-│ ] │
-│ ↓ │
-│ 调用 AdminLogic::add() 或 AdminLogic::edit() │
-│ │
-└─────────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────────┐
-│ 业务逻辑层(AdminLogic) │
-├─────────────────────────────────────────────────────────────────┤
-│ │
-│ // 数据转换处理 │
-│ if (is_array($params['qualification_images'])) { │
-│ $qualificationImages = json_encode( │
-│ $params['qualification_images'], │
-│ JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES │
-│ ); │
-│ } │
-│ ↓ │
-│ 转换后的数据: │
-│ $qualificationImages = '["https://www.d.com/uploads/images/ │
-│ 20260317/image1.png","https://www.d.com/uploads/images/ │
-│ 20260317/image2.png"]' │
-│ ↓ │
-│ 保存到数据库 │
-│ │
-└─────────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────────┐
-│ 数据库(MySQL) │
-├─────────────────────────────────────────────────────────────────┤
-│ │
-│ la_admin 表 │
-│ ┌──────────────────────────────────────────────────────────┐ │
-│ │ qualification_images (text) │ │
-│ ├──────────────────────────────────────────────────────────┤ │
-│ │ ["https://www.d.com/uploads/images/20260317/image1.png",│ │
-│ │ "https://www.d.com/uploads/images/20260317/image2.png"] │ │
-│ └──────────────────────────────────────────────────────────┘ │
-│ │
-└─────────────────────────────────────────────────────────────────┘
-```
-
-### 2. 查看医生详情(管理后台)
-
-```
-┌─────────────────────────────────────────────────────────────────┐
-│ 前端(Vue3) │
-├─────────────────────────────────────────────────────────────────┤
-│ │
-│ 点击编辑按钮 │
-│ ↓ │
-│ 调用 adminDetail({ id: 1 }) │
-│ ↓ │
-│ GET /adminapi/auth.admin/detail?id=1 │
-│ │
-└─────────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────────┐
-│ 后端接口(AdminController) │
-├─────────────────────────────────────────────────────────────────┤
-│ │
-│ 调用 AdminLogic::detail(['id' => 1]) │
-│ │
-└─────────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────────┐
-│ 业务逻辑层(AdminLogic) │
-├─────────────────────────────────────────────────────────────────┤
-│ │
-│ // 从数据库查询 │
-│ $admin = Admin::field([...])->find()->toArray(); │
-│ ↓ │
-│ 查询结果: │
-│ $admin['qualification_images'] = '["https://www.d.com/uploads/ │
-│ images/20260317/image1.png","https://www.d.com/uploads/images/ │
-│ 20260317/image2.png"]' │
-│ ↓ │
-│ // 转换为数组格式 │
-│ if (!empty($admin['qualification_images'])) { │
-│ $images = json_decode($admin['qualification_images'], true);│
-│ if (is_array($images)) { │
-│ $admin['qualification_images'] = $images; │
-│ } │
-│ } │
-│ ↓ │
-│ 转换后的数据: │
-│ $admin['qualification_images'] = [ │
-│ "https://www.d.com/uploads/images/20260317/image1.png", │
-│ "https://www.d.com/uploads/images/20260317/image2.png" │
-│ ] │
-│ ↓ │
-│ 返回给前端 │
-│ │
-└─────────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────────┐
-│ 前端(Vue3) │
-├─────────────────────────────────────────────────────────────────┤
-│ │
-│ 接收响应数据: │
-│ formData.qualification_images = [ │
-│ "https://www.d.com/uploads/images/20260317/image1.png", │
-│ "https://www.d.com/uploads/images/20260317/image2.png" │
-│ ] │
-│ ↓ │
-│ material-picker 组件直接使用数组数据 │
-│ ↓ │
-│ 显示图片列表 │
-│ │
-└─────────────────────────────────────────────────────────────────┘
-```
-
-### 3. 小程序端查看医生详情
-
-```
-┌─────────────────────────────────────────────────────────────────┐
-│ 小程序前端(uni-app) │
-├─────────────────────────────────────────────────────────────────┤
-│ │
-│ 进入医生详情页 │
-│ ↓ │
-│ GET /api/doctor/detail?id=1 │
-│ │
-└─────────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────────┐
-│ 后端接口(DoctorController) │
-├─────────────────────────────────────────────────────────────────┤
-│ │
-│ 调用 DoctorLogic::getDoctorDetail($doctorId) │
-│ │
-└─────────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────────┐
-│ 业务逻辑层(DoctorLogic) │
-├─────────────────────────────────────────────────────────────────┤
-│ │
-│ // 从数据库查询 │
-│ $doctor = Admin::field([...])->find()->toArray(); │
-│ ↓ │
-│ 查询结果(保持JSON字符串格式): │
-│ $doctorData['qualification_images'] = '["https://www.d.com/ │
-│ uploads/images/20260317/image1.png","https://www.d.com/uploads/ │
-│ images/20260317/image2.png"]' │
-│ ↓ │
-│ 直接返回(不转换) │
-│ │
-└─────────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────────┐
-│ 小程序前端(uni-app) │
-├─────────────────────────────────────────────────────────────────┤
-│ │
-│ 接收响应数据: │
-│ doctor.value.qualification_images = '["https://www.d.com/ │
-│ uploads/images/20260317/image1.png","https://www.d.com/uploads/ │
-│ images/20260317/image2.png"]' │
-│ ↓ │
-│ // 使用 computed 解析 │
-│ const qualificationImages = computed(() => { │
-│ if (!doctor.value?.qualification_images) return [] │
-│ try { │
-│ const images = typeof doctor.value.qualification_images │
-│ === 'string' │
-│ ? JSON.parse(doctor.value.qualification_images) │
-│ : doctor.value.qualification_images │
-│ return Array.isArray(images) ? images : [] │
-│ } catch (e) { │
-│ return [] │
-│ } │
-│ }) │
-│ ↓ │
-│ 解析后的数据: │
-│ qualificationImages.value = [ │
-│ "https://www.d.com/uploads/images/20260317/image1.png", │
-│ "https://www.d.com/uploads/images/20260317/image2.png" │
-│ ] │
-│ ↓ │
-│ 渲染图片网格 │
-│ │
-└─────────────────────────────────────────────────────────────────┘
-```
-
-## 关键点总结
-
-### 1. 数据格式转换时机
-
-| 场景 | 输入格式 | 输出格式 | 转换位置 |
-|------|---------|---------|---------|
-| 添加医生 | 数组 | JSON字符串 | AdminLogic::add() |
-| 编辑医生 | 数组 | JSON字符串 | AdminLogic::edit() |
-| 管理后台查看 | JSON字符串 | 数组 | AdminLogic::detail() |
-| 小程序端查看 | JSON字符串 | JSON字符串 | 不转换,前端解析 |
-
-### 2. 为什么管理后台要转换回数组?
-
-因为 `material-picker` 组件需要数组格式的数据:
-```vue
-
-```
-
-如果传入JSON字符串,组件无法正确显示已上传的图片。
-
-### 3. 为什么小程序端不转换?
-
-1. 保持API返回数据的一致性
-2. 减少后端处理逻辑
-3. 前端有完善的解析和容错机制
-4. 便于缓存和传输
-
-### 4. JSON编码参数说明
-
-```php
-json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
-```
-
-- `JSON_UNESCAPED_UNICODE`: 不转义中文字符
-- `JSON_UNESCAPED_SLASHES`: 不转义URL中的斜杠 `/`
-
-这样可以保证:
-- URL正确性:`https://` 不会变成 `https:\/\/`
-- 中文文件名正确显示
-- 减少字符串长度
-
-## 测试验证
-
-### 验证数据转换是否正确
-
-```php
-// 测试代码
-$testData = [
- "https://www.d.com/uploads/images/20260317/测试图片1.png",
- "https://www.d.com/uploads/images/20260317/测试图片2.png"
-];
-
-// 编码
-$encoded = json_encode($testData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
-echo "编码后: " . $encoded . "\n";
-// 输出: ["https://www.d.com/uploads/images/20260317/测试图片1.png","https://www.d.com/uploads/images/20260317/测试图片2.png"]
-
-// 解码
-$decoded = json_decode($encoded, true);
-var_dump($decoded);
-// 输出: array(2) { [0]=> string(...) [1]=> string(...) }
-```
diff --git a/处方单字段修改说明.md b/处方单字段修改说明.md
deleted file mode 100644
index 7cb73c03..00000000
--- a/处方单字段修改说明.md
+++ /dev/null
@@ -1,166 +0,0 @@
-# 处方单字段修改说明
-
-## 修改内容
-
-将处方单中的"数量规格"和"用法"字段修改为更符合实际使用的"服用天数"、"类型"和"服用说明"。
-
-## 修改前后对比
-
-### 修改前
-```
-数量规格:[数字输入框] [下拉选择:剂/丸/袋/盒/瓶/膏/贴/包/片/粒]
-用法:[文本输入框,如:水煎服一日二次]
-```
-
-### 修改后
-```
-服用天数:[数字输入框,1-999天]
-类型:[下拉选择:浓缩水丸/饮片]
-服用说明:[下拉选择:饭前/饭后]
-```
-
-## 详细修改
-
-### 1. 表单字段布局
-**文件**: `admin/src/components/tcm-prescription/index.vue`
-
-**修改前**:
-- 数量规格(12列):数字输入框 + 单位下拉框
-- 用法(12列):文本输入框
-
-**修改后**:
-- 服用天数(8列):数字输入框(1-999)
-- 类型(8列):下拉选择框(浓缩水丸/饮片)
-- 服用说明(8列):下拉选择框(饭前/饭后)
-
-### 2. 字段定义
-
-#### 服用天数 (dose_count)
-- 类型:数字
-- 范围:1-999
-- 默认值:1
-- 说明:患者需要服用的天数
-
-#### 类型 (dose_unit)
-- 类型:字符串
-- 选项:
- - 浓缩水丸
- - 饮片
-- 默认值:浓缩水丸
-- 说明:药品的类型
-
-#### 服用说明 (usage_instruction)
-- 类型:字符串
-- 选项:
- - 饭前
- - 饭后
-- 默认值:饭前
-- 说明:服用时间说明
-
-### 3. 打印预览显示
-
-**修改前**:
-```
-共7剂, 水煎服一日二次
-```
-
-**修改后**:
-```
-服用7天, 浓缩水丸, 饭前
-```
-
-### 4. 代码修改位置
-
-#### 4.1 表单布局 (第112-127行)
-```vue
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-#### 4.2 打印预览显示 (第215-218行)
-```vue
-
-```
-
-#### 4.3 默认值设置 (多处)
-```javascript
-// formData 初始化
-dose_count: 1,
-dose_unit: '浓缩水丸',
-usage_instruction: '饭前',
-
-// open 方法中
-formData.dose_count = 1
-formData.dose_unit = '浓缩水丸'
-formData.usage_instruction = '饭前'
-
-// handleNewPrescription 方法中
-formData.dose_count = 1
-formData.dose_unit = saved.dose_unit || '浓缩水丸'
-formData.usage_instruction = '饭前'
-
-// handleSave 方法中
-dose_unit: formData.dose_unit || '浓缩水丸',
-```
-
-## 数据库字段
-
-这些字段在数据库中的存储保持不变:
-- `dose_count`: int - 服用天数
-- `dose_unit`: varchar - 类型(浓缩水丸/饮片)
-- `usage_instruction`: varchar - 服用说明(饭前/饭后)
-
-## 使用示例
-
-### 示例1:浓缩水丸,饭前服用
-```
-服用天数:7
-类型:浓缩水丸
-服用说明:饭前
-
-打印显示:服用7天, 浓缩水丸, 饭前
-```
-
-### 示例2:饮片,饭后服用
-```
-服用天数:14
-类型:饮片
-服用说明:饭后
-
-打印显示:服用14天, 饮片, 饭后
-```
-
-## 注意事项
-
-1. **字段必填性**: 所有三个字段都应该填写,系统会使用默认值
-2. **数据兼容性**: 旧数据中的 `dose_unit` 可能包含其他值(如"剂"、"丸"等),系统会正常显示
-3. **打印格式**: 打印时会按照"服用X天, 类型, 服用说明"的格式显示
-4. **扩展性**: 如需添加更多类型或服用说明选项,只需在下拉框中添加 `` 即可
-
-## 后续优化建议
-
-1. **字典管理**: 可以将"类型"和"服用说明"的选项移到字典表中管理,便于后期维护
-2. **自定义输入**: 可以考虑添加"其他"选项,允许用户自定义输入
-3. **服用次数**: 可以考虑添加"每日服用次数"字段,如"一日二次"
-4. **服用量**: 可以考虑添加"每次服用量"字段,如"每次10克"
diff --git a/测试医生资质图片功能.md b/测试医生资质图片功能.md
deleted file mode 100644
index 992edc7a..00000000
--- a/测试医生资质图片功能.md
+++ /dev/null
@@ -1,267 +0,0 @@
-# 测试医生资质图片功能
-
-## 测试步骤
-
-### 1. 数据库准备
-
-首先执行SQL文件添加字段:
-
-```bash
-# 如果使用 la_ 前缀
-mysql -u root -p your_database < server/sql/add_doctor_qualification_images.sql
-
-# 如果使用 zyt_ 前缀,先修改SQL文件中的表名
-# 将 la_admin 替换为 zyt_admin
-```
-
-验证字段是否添加成功:
-```sql
-DESC la_admin;
--- 或
-DESC zyt_admin;
-
--- 应该能看到 qualification_images 字段
-```
-
-### 2. 管理后台测试
-
-#### 2.1 添加医生并上传资质图片
-
-1. 登录管理后台
-2. 进入"医生管理"页面
-3. 点击"新增医生"按钮
-4. 填写基本信息:
- - 账号:test_doctor
- - 姓名:测试医生
- - 密码:123456
- - 其他必填字段...
-5. 在"专业信息"部分找到"资质图片"字段
-6. 点击上传按钮,选择1-3张测试图片
-7. 点击"确定"保存
-
-#### 2.2 编辑医生资质图片
-
-1. 在医生列表中找到刚创建的医生
-2. 点击"编辑"按钮
-3. 在"资质图片"字段中:
- - 可以删除已有图片
- - 可以添加新图片
- - 可以调整图片顺序
-4. 点击"确定"保存
-
-#### 2.3 查看医生详情
-
-1. 点击医生列表中的"详情"按钮
-2. 确认能看到 `qualification_images` 字段
-3. 确认图片URL格式正确
-
-### 3. API接口测试
-
-#### 3.1 测试添加医生接口
-
-```bash
-curl -X POST "http://your-domain/adminapi/auth.admin/add" \
- -H "Content-Type: application/json" \
- -H "token: your_admin_token" \
- -d '{
- "account": "test_doctor2",
- "name": "测试医生2",
- "password": "123456",
- "password_confirm": "123456",
- "gender": 1,
- "role_id": [1],
- "dept_id": [1],
- "qualification_images": "[\"https://example.com/cert1.jpg\",\"https://example.com/cert2.jpg\"]",
- "disable": 0,
- "multipoint_login": 1
- }'
-```
-
-#### 3.2 测试编辑医生接口
-
-```bash
-curl -X POST "http://your-domain/adminapi/auth.admin/edit" \
- -H "Content-Type: application/json" \
- -H "token: your_admin_token" \
- -d '{
- "id": 1,
- "account": "test_doctor",
- "name": "测试医生",
- "qualification_images": "[\"https://example.com/cert1.jpg\",\"https://example.com/cert2.jpg\",\"https://example.com/cert3.jpg\"]",
- "disable": 0,
- "multipoint_login": 1
- }'
-```
-
-#### 3.3 测试医生详情接口
-
-```bash
-curl -X GET "http://your-domain/adminapi/auth.admin/detail?id=1" \
- -H "token: your_admin_token"
-```
-
-预期返回:
-```json
-{
- "code": 1,
- "msg": "success",
- "data": {
- "id": 1,
- "name": "测试医生",
- "qualification_images": "[\"https://example.com/cert1.jpg\",\"https://example.com/cert2.jpg\"]",
- ...
- }
-}
-```
-
-#### 3.4 测试小程序端医生详情接口
-
-```bash
-curl -X GET "http://your-domain/api/doctor/detail?id=1"
-```
-
-预期返回:
-```json
-{
- "code": 1,
- "msg": "success",
- "data": {
- "id": 1,
- "name": "测试医生",
- "qualification_images": "[\"https://example.com/cert1.jpg\",\"https://example.com/cert2.jpg\"]",
- ...
- }
-}
-```
-
-### 4. 小程序端测试
-
-#### 4.1 查看医生详情页
-
-1. 打开小程序
-2. 进入医生列表页面
-3. 点击任意医生进入详情页
-4. 滚动到"擅长领域"下方
-5. 应该能看到"医生资质"区域(如果该医生有资质图片)
-6. 资质图片以3列网格布局展示
-
-#### 4.2 测试图片预览
-
-1. 点击任意资质图片
-2. 应该弹出图片预览界面
-3. 可以左右滑动查看所有资质图片
-4. 可以双指缩放查看图片细节
-5. 点击关闭按钮或返回键退出预览
-
-### 5. 边界情况测试
-
-#### 5.1 无资质图片
-- 医生没有上传资质图片时,小程序端不显示"医生资质"区域
-- 管理后台显示空的上传区域
-
-#### 5.2 单张图片
-- 上传1张图片,正常显示
-- 预览时只有1张图片
-
-#### 5.3 多张图片
-- 上传9张图片(最大限制)
-- 所有图片都能正常显示
-- 预览时可以滑动查看所有图片
-
-#### 5.4 图片格式
-- 测试 JPG 格式 ✓
-- 测试 PNG 格式 ✓
-- 测试 JPEG 格式 ✓
-
-#### 5.5 错误数据处理
-- 空字符串:不显示资质区域
-- null:不显示资质区域
-- 无效JSON:不显示资质区域(有容错处理)
-- 非数组JSON:不显示资质区域
-
-### 6. 性能测试
-
-#### 6.1 图片加载速度
-- 测试3张图片的加载时间
-- 测试9张图片的加载时间
-- 确认图片懒加载是否生效
-
-#### 6.2 预览性能
-- 测试预览大图的加载速度
-- 测试滑动切换的流畅度
-
-## 测试检查清单
-
-- [ ] 数据库字段添加成功
-- [ ] 管理后台可以上传资质图片
-- [ ] 管理后台可以编辑资质图片
-- [ ] 管理后台可以删除资质图片
-- [ ] 添加医生接口正常工作
-- [ ] 编辑医生接口正常工作
-- [ ] 医生详情接口返回资质图片
-- [ ] 小程序端正常显示资质图片
-- [ ] 小程序端图片预览功能正常
-- [ ] 无资质图片时不显示该区域
-- [ ] 支持多种图片格式
-- [ ] 错误数据有容错处理
-- [ ] 图片加载性能良好
-
-## 常见问题排查
-
-### 问题1:管理后台上传图片后不显示
-**排查步骤**:
-1. 检查浏览器控制台是否有错误
-2. 检查图片上传接口是否成功
-3. 检查 `material-picker` 组件是否正常工作
-4. 检查 `formData.qualification_images` 是否有值
-
-### 问题2:小程序端不显示资质图片
-**排查步骤**:
-1. 检查医生详情接口返回的数据
-2. 检查 `qualification_images` 字段是否有值
-3. 检查 `qualificationImages` computed 是否正确解析
-4. 检查图片URL是否可访问
-5. 检查小程序域名白名单配置
-
-### 问题3:图片预览失败
-**排查步骤**:
-1. 检查 `uni.previewImage` 是否正常调用
-2. 检查图片URL格式是否正确
-3. 检查小程序权限配置
-4. 查看小程序控制台错误信息
-
-### 问题4:数据库字段不存在
-**排查步骤**:
-1. 确认是否执行了SQL文件
-2. 确认表名前缀是否正确(la_ 或 zyt_)
-3. 手动执行 `DESC` 命令检查字段
-4. 检查数据库连接是否正确
-
-## 测试数据示例
-
-### 示例1:单张图片
-```json
-["https://example.com/doctor-cert-1.jpg"]
-```
-
-### 示例2:多张图片
-```json
-[
- "https://example.com/doctor-cert-1.jpg",
- "https://example.com/doctor-cert-2.jpg",
- "https://example.com/doctor-cert-3.jpg"
-]
-```
-
-### 示例3:完整医生数据
-```json
-{
- "id": 1,
- "name": "李医生",
- "title": "主任医师",
- "license_no": "110101199001011234",
- "qualification_images": "[\"https://example.com/cert1.jpg\",\"https://example.com/cert2.jpg\"]",
- "enable_image_consult": 1,
- "enable_video_consult": 1
-}
-```