Pengxy 9 ماه پیش
والد
کامیت
b3b2aad406
100فایلهای تغییر یافته به همراه7237 افزوده شده و 1 حذف شده
  1. 1 1
      .env.local
  2. 0 0
      .image/Java鐩戞帶.jpg
  3. 0 0
      .image/OA璇峰亣-璇︽儏.jpg
  4. 0 0
      .image/OA璇峰亣-鍒楄〃.jpg
  5. 0 0
      .image/OA璇峰亣-鍙戣捣.jpg
  6. BIN
      .image/大屏设计器-列表.jpg
  7. BIN
      .image/大屏设计器-编辑.jpg
  8. BIN
      .image/大屏设计器-预览.jpg
  9. 0 0
      .image/娴佺▼妯″瀷-瀹氫箟.jpg
  10. 0 0
      .image/娴佺▼妯″瀷-璁捐.jpg
  11. 0 0
      .image/娴佺▼妯″瀷-鍒楄〃.jpg
  12. 0 0
      .image/娴佺▼琛ㄥ崟.jpg
  13. 0 0
      .image/宀椾綅绠$悊.jpg
  14. 0 0
      .image/宸ヤ綔娴佽璁″櫒-bpmn.jpg
  15. 0 0
      .image/宸ヤ綔娴佽璁″櫒-simple.jpg
  16. BIN
      .image/报表设计器-图形报表.jpg
  17. BIN
      .image/报表设计器-打印设计.jpg
  18. BIN
      .image/报表设计器-数据报表.jpg
  19. 0 0
      .image/搴旂敤淇℃伅-缂栬緫.jpg
  20. 0 0
      .image/搴旂敤淇℃伅-鍒楄〃.jpg
  21. 0 0
      .image/搴旂敤绠$悊.jpg
  22. BIN
      .image/敏感词.jpg
  23. BIN
      .image/数据库文档.jpg
  24. 0 0
      .image/棣栭〉.jpg
  25. 0 0
      .image/浠ょ墝绠$悊.jpg
  26. 0 0
      .image/浠诲姟鍒楄〃-宸插姙.jpg
  27. 0 0
      .image/浠诲姟鍒楄〃-寰呭姙.jpg
  28. 0 0
      .image/浠诲姟鍒楄〃-瀹℃壒.jpg
  29. 0 0
      .image/浠诲姟鏃ュ織.jpg
  30. 0 0
      .image/浠g爜鐢熸垚.jpg
  31. 0 0
      .image/涓汉涓績.jpg
  32. 0 0
      .image/瀛楀吀绫诲瀷.jpg
  33. 0 0
      .image/瀛楀吀鏁版嵁.jpg
  34. 0 0
      .image/瀹氭椂浠诲姟.jpg
  35. 0 0
      .image/琛ㄥ崟鏋勫缓.jpg
  36. 0 0
      .image/瑙掕壊绠$悊.jpg
  37. 0 0
      .image/璁块棶鏃ュ織.jpg
  38. 0 0
      .image/绉熸埛濂楅.png
  39. 0 0
      .image/绉熸埛绠$悊.jpg
  40. 0 0
      .image/绯荤粺鎺ュ彛.jpg
  41. BIN
      .image/退款订单.jpg
  42. 0 0
      .image/鍟嗘埛淇℃伅.jpg
  43. 0 0
      .image/鍦ㄧ嚎鐢ㄦ埛.jpg
  44. 0 0
      .image/鎴戠殑娴佺▼-璇︽儏.jpg
  45. 0 0
      .image/鎴戠殑娴佺▼-鍒楄〃.jpg
  46. 0 0
      .image/鎴戠殑娴佺▼-鍙戣捣.jpg
  47. 0 0
      .image/鎿嶄綔鏃ュ織.jpg
  48. 0 0
      .image/鏀粯璁㈠崟.jpg
  49. 0 0
      .image/鏂囦欢绠$悊.jpg
  50. 0 0
      .image/鏂囦欢绠$悊2.jpg
  51. 0 0
      .image/鏂囦欢閰嶇疆.jpg
  52. 0 0
      .image/鏃ュ織涓績.jpg
  53. 0 0
      .image/鐢ㄦ埛绠$悊.jpg
  54. 0 0
      .image/鐢ㄦ埛鍒嗙粍.jpg
  55. 0 0
      .image/鐢熸垚鏁堟灉.jpg
  56. 0 0
      .image/鐧诲綍.jpg
  57. 0 0
      .image/鐧诲綍鏃ュ織.jpg
  58. 0 0
      .image/鐭俊妯℃澘.jpg
  59. 0 0
      .image/鐭俊娓犻亾.jpg
  60. 0 0
      .image/鐭俊鏃ュ織.jpg
  61. 0 0
      .image/鑿滃崟绠$悊.jpg
  62. BIN
      .image/错误码管理.jpg
  63. 0 0
      .image/閫氱煡鍏憡.jpg
  64. 0 0
      .image/閮ㄩ棬绠$悊.jpg
  65. 0 0
      .image/閰嶇疆绠$悊.jpg
  66. 0 0
      .image/閾捐矾杩借釜.jpg
  67. 0 0
      .image/閿欒鏃ュ織.jpg
  68. 283 0
      MODULES_INTEGRATION_GUIDE.md
  69. 87 0
      QMS_INTEGRATION_GUIDE.md
  70. 120 0
      caigou/DEPLOYMENT_GUIDE.md
  71. 65 0
      caigou/README.md
  72. 40 0
      caigou/index.html
  73. 768 0
      caigou/material-delivery-plan.html
  74. 257 0
      caigou/script.js
  75. 426 0
      caigou/styles.css
  76. 295 0
      caigou/vue-index.html
  77. BIN
      caigou/鐗╂枡浜よ揣璁″垝.xlsx
  78. 24 0
      dingdan/.gitignore
  79. 13 0
      dingdan/index.html
  80. 13 0
      dingdan/manage-vue/index.html
  81. 1420 0
      dingdan/manage-vue/package-lock.json
  82. 20 0
      dingdan/manage-vue/package.json
  83. 226 0
      dingdan/manage-vue/src/App.vue
  84. 12 0
      dingdan/manage-vue/src/main.js
  85. 64 0
      dingdan/manage-vue/src/router/index.js
  86. 135 0
      dingdan/manage-vue/src/style.css
  87. 111 0
      dingdan/manage-vue/src/views/Help.vue
  88. 36 0
      dingdan/manage-vue/src/views/MyProcess.vue
  89. 693 0
      dingdan/manage-vue/src/views/purchase-order/MaterialPurchaseOrder.vue
  90. 845 0
      dingdan/manage-vue/src/views/purchase-order/MaterialPurchaseRequest.vue
  91. 751 0
      dingdan/manage-vue/src/views/purchase-order/SubcontractOrder.vue
  92. 11 0
      dingdan/manage-vue/vite.config.js
  93. 19 0
      dingdan/package.json
  94. 1 0
      dingdan/public/vite.svg
  95. 249 0
      dingdan/src/App.vue
  96. 5 0
      dingdan/src/main.js
  97. 112 0
      dingdan/src/style.css
  98. 6 0
      dingdan/vite.config.js
  99. 43 0
      iqc/manage-vue/.gitignore
  100. 86 0
      iqc/manage-vue/ApplicationList鏂囨。.md

+ 1 - 1
.env.local

@@ -28,7 +28,7 @@ VITE_BASE_PATH=/
 VITE_MALL_H5_DOMAIN='http://localhost:3000'
 
 # 验证码的开关
-VITE_APP_CAPTCHA_ENABLE=false
+VITE_APP_CAPTCHA_ENABLE=true
 
 # GoView域名
 VITE_GOVIEW_URL='http://127.0.0.1:3000'

+ 0 - 0
.image/Java监控.jpg → .image/Java鐩戞帶.jpg


+ 0 - 0
.image/OA请假-详情.jpg → .image/OA璇峰亣-璇︽儏.jpg


+ 0 - 0
.image/OA请假-列表.jpg → .image/OA璇峰亣-鍒楄〃.jpg


+ 0 - 0
.image/OA请假-发起.jpg → .image/OA璇峰亣-鍙戣捣.jpg


BIN
.image/大屏设计器-列表.jpg


BIN
.image/大屏设计器-编辑.jpg


BIN
.image/大屏设计器-预览.jpg


+ 0 - 0
.image/流程模型-定义.jpg → .image/娴佺▼妯″瀷-瀹氫箟.jpg


+ 0 - 0
.image/流程模型-设计.jpg → .image/娴佺▼妯″瀷-璁捐.jpg


+ 0 - 0
.image/流程模型-列表.jpg → .image/娴佺▼妯″瀷-鍒楄〃.jpg


+ 0 - 0
.image/流程表单.jpg → .image/娴佺▼琛ㄥ崟.jpg


+ 0 - 0
.image/岗位管理.jpg → .image/宀椾綅绠$悊.jpg


+ 0 - 0
.image/工作流设计器-bpmn.jpg → .image/宸ヤ綔娴佽璁″櫒-bpmn.jpg


+ 0 - 0
.image/工作流设计器-simple.jpg → .image/宸ヤ綔娴佽璁″櫒-simple.jpg


BIN
.image/报表设计器-图形报表.jpg


BIN
.image/报表设计器-打印设计.jpg


BIN
.image/报表设计器-数据报表.jpg


+ 0 - 0
.image/应用信息-编辑.jpg → .image/搴旂敤淇℃伅-缂栬緫.jpg


+ 0 - 0
.image/应用信息-列表.jpg → .image/搴旂敤淇℃伅-鍒楄〃.jpg


+ 0 - 0
.image/应用管理.jpg → .image/搴旂敤绠$悊.jpg


BIN
.image/敏感词.jpg


BIN
.image/数据库文档.jpg


+ 0 - 0
.image/首页.jpg → .image/棣栭〉.jpg


+ 0 - 0
.image/令牌管理.jpg → .image/浠ょ墝绠$悊.jpg


+ 0 - 0
.image/任务列表-已办.jpg → .image/浠诲姟鍒楄〃-宸插姙.jpg


+ 0 - 0
.image/任务列表-待办.jpg → .image/浠诲姟鍒楄〃-寰呭姙.jpg


+ 0 - 0
.image/任务列表-审批.jpg → .image/浠诲姟鍒楄〃-瀹℃壒.jpg


+ 0 - 0
.image/任务日志.jpg → .image/浠诲姟鏃ュ織.jpg


+ 0 - 0
.image/代码生成.jpg → .image/浠g爜鐢熸垚.jpg


+ 0 - 0
.image/个人中心.jpg → .image/涓汉涓績.jpg


+ 0 - 0
.image/字典类型.jpg → .image/瀛楀吀绫诲瀷.jpg


+ 0 - 0
.image/字典数据.jpg → .image/瀛楀吀鏁版嵁.jpg


+ 0 - 0
.image/定时任务.jpg → .image/瀹氭椂浠诲姟.jpg


+ 0 - 0
.image/表单构建.jpg → .image/琛ㄥ崟鏋勫缓.jpg


+ 0 - 0
.image/角色管理.jpg → .image/瑙掕壊绠$悊.jpg


+ 0 - 0
.image/访问日志.jpg → .image/璁块棶鏃ュ織.jpg


+ 0 - 0
.image/租户套餐.png → .image/绉熸埛濂楅.png


+ 0 - 0
.image/租户管理.jpg → .image/绉熸埛绠$悊.jpg


+ 0 - 0
.image/系统接口.jpg → .image/绯荤粺鎺ュ彛.jpg


BIN
.image/退款订单.jpg


+ 0 - 0
.image/商户信息.jpg → .image/鍟嗘埛淇℃伅.jpg


+ 0 - 0
.image/在线用户.jpg → .image/鍦ㄧ嚎鐢ㄦ埛.jpg


+ 0 - 0
.image/我的流程-详情.jpg → .image/鎴戠殑娴佺▼-璇︽儏.jpg


+ 0 - 0
.image/我的流程-列表.jpg → .image/鎴戠殑娴佺▼-鍒楄〃.jpg


+ 0 - 0
.image/我的流程-发起.jpg → .image/鎴戠殑娴佺▼-鍙戣捣.jpg


+ 0 - 0
.image/操作日志.jpg → .image/鎿嶄綔鏃ュ織.jpg


+ 0 - 0
.image/支付订单.jpg → .image/鏀粯璁㈠崟.jpg


+ 0 - 0
.image/文件管理.jpg → .image/鏂囦欢绠$悊.jpg


+ 0 - 0
.image/文件管理2.jpg → .image/鏂囦欢绠$悊2.jpg


+ 0 - 0
.image/文件配置.jpg → .image/鏂囦欢閰嶇疆.jpg


+ 0 - 0
.image/日志中心.jpg → .image/鏃ュ織涓績.jpg


+ 0 - 0
.image/用户管理.jpg → .image/鐢ㄦ埛绠$悊.jpg


+ 0 - 0
.image/用户分组.jpg → .image/鐢ㄦ埛鍒嗙粍.jpg


+ 0 - 0
.image/生成效果.jpg → .image/鐢熸垚鏁堟灉.jpg


+ 0 - 0
.image/登录.jpg → .image/鐧诲綍.jpg


+ 0 - 0
.image/登录日志.jpg → .image/鐧诲綍鏃ュ織.jpg


+ 0 - 0
.image/短信模板.jpg → .image/鐭俊妯℃澘.jpg


+ 0 - 0
.image/短信渠道.jpg → .image/鐭俊娓犻亾.jpg


+ 0 - 0
.image/短信日志.jpg → .image/鐭俊鏃ュ織.jpg


+ 0 - 0
.image/菜单管理.jpg → .image/鑿滃崟绠$悊.jpg


BIN
.image/错误码管理.jpg


+ 0 - 0
.image/通知公告.jpg → .image/閫氱煡鍏憡.jpg


+ 0 - 0
.image/部门管理.jpg → .image/閮ㄩ棬绠$悊.jpg


+ 0 - 0
.image/配置管理.jpg → .image/閰嶇疆绠$悊.jpg


+ 0 - 0
.image/链路追踪.jpg → .image/閾捐矾杩借釜.jpg


+ 0 - 0
.image/错误日志.jpg → .image/閿欒鏃ュ織.jpg


+ 283 - 0
MODULES_INTEGRATION_GUIDE.md

@@ -0,0 +1,283 @@
+# 自定义模块整合指南
+
+本文档说明如何将 6 个自定义模块(caigou, dingdan, iqc, jianmo, jiaohuo, yichang)整合到芋道管理后台项目中。
+
+## 已完成的工作
+
+### 1. 页面组件复制
+
+已将以下模块的页面组件复制到 `src/views` 目录:
+
+| 模块 | 源目录 | 目标目录 | 状态 |
+|------|--------|----------|------|
+| jiaohuo (交货管理) | `jiaohuo/manage-vue/src/views/` | `src/views/jiaohuo/` | ✅ 已复制 |
+| iqc (IQC检验) | `iqc/manage-vue/src/views/` | `src/views/iqc/` | ✅ 已复制 |
+| yichang (异常管理) | `yichang/manage-vue/src/modules/` | `src/views/yichang/` | ✅ 已复制 |
+| dingdan (订单管理) | `dingdan/manage-vue/src/views/` | `src/views/dingdan/` | ✅ 已复制 |
+| jianmo (建模管理) | `jianmo/s0-operating-modeling/src/` | `src/views/jianmo/` | ✅ 已复制 |
+| caigou (采购管理) | 原为静态HTML | `src/views/caigou/index.vue` | ✅ 已转换为Vue组件 |
+| caigou (物料采购) | `dingdan/manage-vue/src/views/purchase-order/` | `src/views/caigou/` | ✅ 已复制 |
+
+### 2. 菜单配置 SQL
+
+已创建菜单配置 SQL 脚本:
+- `sql/mysql/custom_modules_menu.sql` - 自定义模块菜单
+- `sql/mysql/material_purchase_menu.sql` - 物料采购模块菜单
+
+### 3. API 接口文件
+
+已创建 jiaohuo 模块的 API 接口文件:
+
+| 文件 | 说明 |
+|------|------|
+| `src/api/jiaohuo/order.ts` | 订单相关接口 |
+| `src/api/jiaohuo/schedule.ts` | 排程相关接口 |
+| `src/api/jiaohuo/production.ts` | 生产相关接口 |
+| `src/api/jiaohuo/delivery.ts` | 交货相关接口 |
+| `src/api/jiaohuo/supplier.ts` | 供应商相关接口 |
+| `src/api/jiaohuo/material.ts` | 物料相关接口 |
+| `src/api/jiaohuo/workorder.ts` | 工单相关接口 |
+
+### 4. 工具函数
+
+已创建格式化工具函数:`src/utils/format.ts`
+
+### 5. 依赖安装
+
+已安装 xlsx 依赖(用于 caigou 模块的 Excel 解析)
+
+### 6. 公共组件
+
+已创建以下公共组件:
+
+| 组件 | 路径 | 说明 |
+|------|------|------|
+| SearchFilter | `src/components/SearchFilter/index.vue` | 搜索筛选组件 |
+| StatusBadge | `src/components/StatusBadge/index.vue` | 状态标签组件 |
+| PermissionButton | `src/components/PermissionButton/index.vue` | 权限按钮组件 |
+| EmptyState | `src/components/EmptyState/index.vue` | 空状态组件 |
+| BatchAddDelivery | `src/components/BatchAddDelivery/index.vue` | 批量添加交货单组件 |
+
+### 7. Store 模块
+
+已创建 IQC 模块的 Store:
+
+| Store | 路径 | 说明 |
+|-------|------|------|
+| application | `src/store/modules/iqc/application.ts` | 申请管理 Store |
+| task | `src/store/modules/iqc/task.ts` | 任务管理 Store |
+| user | `src/store/modules/iqc/user.ts` | 用户管理 Store |
+
+### 8. 配置文件
+
+已创建模块配置文件:`src/config/modules.ts`
+
+## 待完成的工作
+
+### 步骤 1:执行菜单 SQL 脚本
+
+在 MySQL 数据库中执行菜单配置脚本:
+
+```bash
+# 连接到 MySQL
+mysql -h 127.0.0.1 -P 3306 -u root -p ruoyi-vue-pro
+
+# 执行 SQL 脚本
+source sql/mysql/custom_modules_menu.sql
+```
+
+或者在 MySQL 客户端(如 Navicat、DBeaver)中打开并执行该脚本。
+
+### 步骤 2:启动前端服务
+
+```bash
+cd yudao-ui/yudao-ui-admin-vue3
+npm run dev
+# 或
+pnpm dev
+```
+
+### 步骤 3:分配菜单权限
+
+1. 登录管理后台
+2. 进入 **系统管理** -> **角色管理**
+3. 编辑需要访问新模块的角色
+4. 在菜单权限中勾选新增的模块菜单
+5. 保存并重新登录
+
+## 模块说明
+
+### 1. 交货管理 (jiaohuo)
+
+包含以下功能页面:
+- 订单评审 (OrderReview)
+- 订单交付 (OrderDelivery)
+- 出货计划 (ShippingPlan)
+- 订单发货 (OrderShipment)
+- 工单池下达 (WorkOrderPool)
+- 工单工序排产 (ProductionSchedule)
+- 工单物料明细 (WorkOrderMaterial)
+- 工单工序明细 (WorkOrderProcess)
+- 可执行日计划 (DailyPlan)
+- 产线工作日历 (WorkCalendar)
+- 物料需求计划 (MaterialDemand)
+- 物料交货计划 (DeliverySchedule)
+- 供应商交货管理 (SupplierDelivery)
+- 供应商发货单 (SupplierShippingList)
+
+### 2. IQC检验 (iqc)
+
+包含以下功能页面:
+- 检验申请列表 (ApplicationList)
+- 检验申请表单 (ApplicationForm)
+- 检验任务列表 (TaskList)
+- 检验任务表单 (TaskForm)
+- 我的收藏 (Favorites)
+- 帮助中心 (Help)
+
+### 3. 异常管理 (yichang)
+
+包含以下子模块:
+- Andon管理 (andon/)
+- 工损管理 (loss/)
+- SIM管理 (sim/)
+
+### 4. 订单管理 (dingdan)
+
+包含以下功能页面:
+- 我的流程 (MyProcess)
+- 帮助中心 (Help)
+- 采购订单 (purchase-order/)
+
+### 5. 采购管理 (caigou)
+
+包含以下功能页面:
+- 物料交货计划分析 (index.vue) - Excel 文件分析工具
+
+### 5.1 物料采购 (caigou - 物料采购子模块)
+
+物料采购模块从 `dingdan/manage-vue` 整合而来,包含以下功能页面:
+
+| 组件文件 | 菜单名称 | 功能说明 |
+|----------|----------|----------|
+| MaterialPurchaseOrder.vue | 物料采购订单 | 管理物料采购订单,支持搜索、筛选、分页、导出 |
+| SubcontractOrder.vue | 委外加工订单 | 管理委外加工订单,支持搜索、筛选、分页、导出 |
+| MaterialPurchaseRequest.vue | 物料采购申请 | 管理物料采购申请,支持搜索、筛选、分页、导出 |
+
+#### 菜单配置
+
+物料采购模块使用独立的菜单 SQL 脚本:`sql/mysql/material_purchase_menu.sql`
+
+菜单层级结构:
+```
+物料采购 (ID: 6500, 一级菜单)
+├── 物料采购订单 (ID: 6501)
+├── 委外加工订单 (ID: 6502)
+└── 物料采购申请 (ID: 6503)
+```
+
+#### 执行菜单配置
+
+```bash
+# 连接到 MySQL
+mysql -h 127.0.0.1 -P 3306 -u root -p ruoyi-vue-pro
+
+# 执行物料采购菜单 SQL 脚本
+source sql/mysql/material_purchase_menu.sql
+```
+
+#### 权限标识
+
+| 菜单 | 权限标识 |
+|------|----------|
+| 物料采购订单 | caigou:purchase-order:list |
+| 委外加工订单 | caigou:subcontract-order:list |
+| 物料采购申请 | caigou:purchase-request:list |
+
+### 6. 建模管理 (jianmo)
+
+包含以下功能页面:
+- 客户管理 (CustomerManagement)
+- 物料管理 (MaterialManagement)
+- 订单优先级配置 (OrderPriorityConfig)
+- 设备管理 (device/)
+- 仓储建模 (warehouse/)
+- 供应建模 (supply/)
+- 制造建模 (manufacturing/)
+- 质量建模 (quality/)
+
+## 常见问题
+
+### Q1: 菜单不显示
+
+1. 确认 SQL 脚本已正确执行
+2. 确认当前用户的角色已分配新菜单权限
+3. 清除浏览器缓存并重新登录
+
+### Q2: 页面报错 "找不到组件"
+
+1. 检查组件路径是否正确
+2. 确认组件文件已复制到正确位置
+3. 检查组件名称是否与菜单配置中的 `component` 字段匹配
+
+### Q3: 样式问题
+
+原模块可能使用了不同的 UI 框架或样式,需要:
+1. 将样式改为使用 Element Plus 组件
+2. 使用主项目的公共样式变量
+3. 移除冲突的全局样式
+
+### Q4: API 调用失败
+
+原模块的 API 可能指向不同的后端服务,需要:
+1. 在后端添加对应的 API 接口
+2. 或修改前端 API 调用指向现有接口
+
+## 后续优化建议
+
+1. **统一代码风格**:将原模块代码改为 TypeScript,使用主项目的代码规范
+2. **复用公共组件**:使用主项目的 ContentWrap、Dialog 等公共组件
+3. **统一 API 层**:在 `src/api/` 目录下创建对应的 API 模块
+4. **添加权限控制**:为各功能添加细粒度的权限控制
+5. **国际化支持**:添加多语言支持
+
+## 文件结构
+
+```
+src/views/
+├── caigou/           # 采购管理 & 物料采购
+│   ├── index.vue                    # 物料交货计划分析
+│   ├── MaterialPurchaseOrder.vue    # 物料采购订单
+│   ├── SubcontractOrder.vue         # 委外加工订单
+│   └── MaterialPurchaseRequest.vue  # 物料采购申请
+├── dingdan/          # 订单管理
+│   ├── Help.vue
+│   ├── MyProcess.vue
+│   └── purchase-order/
+├── iqc/              # IQC检验
+│   ├── ApplicationForm.vue
+│   ├── ApplicationList.vue
+│   ├── Favorites.vue
+│   ├── Help.vue
+│   ├── TaskForm.vue
+│   └── TaskList.vue
+├── jianmo/           # 建模管理
+│   ├── views/
+│   │   ├── CustomerManagement.vue
+│   │   ├── MaterialManagement.vue
+│   │   ├── OrderPriorityConfig.vue
+│   │   ├── device/
+│   │   ├── warehouse/
+│   │   └── ...
+│   └── ...
+├── jiaohuo/          # 交货管理
+│   ├── DailyPlan.vue
+│   ├── DeliverySchedule.vue
+│   ├── OrderDelivery.vue
+│   ├── OrderReview.vue
+│   └── ...
+└── yichang/          # 异常管理
+    ├── andon/
+    ├── loss/
+    └── sim/
+```

+ 87 - 0
QMS_INTEGRATION_GUIDE.md

@@ -0,0 +1,87 @@
+# 质量管理系统 (QMS) 整合指南
+
+## 菜单结构
+
+```
+质量管理系统 (6100) - /qms
+├── IQC 来料检验 (6110)
+│   ├── 来料检验申请 (6111) - /qms/iqc/apply/list
+│   └── 来料检验任务 (6112) - /qms/iqc/task/list
+├── IPQC 过程检验 (6120)
+│   ├── 过程检验申请 (6121) - /qms/ipqc/apply/list
+│   └── 过程检验任务 (6122) - /qms/ipqc/task/list
+├── FQC 成品检验 (6130)
+│   ├── 成品检验申请 (6131) - /qms/fqc/apply/list
+│   └── 成品检验任务 (6132) - /qms/fqc/task/list
+└── SPEC 检规 (6140)
+    ├── 检规索引 (6141) - /qms/spec/index
+    └── 导入检规 (6142) - /qms/spec/import
+```
+
+## 目录结构
+
+```
+src/
+├── views/qms/                    # 质量管理视图
+│   ├── ApplicationList.vue       # 申请列表(IQC/IPQC/FQC 共用)
+│   ├── ApplicationForm.vue       # 申请表单
+│   ├── TaskList.vue              # 任务列表
+│   ├── TaskForm.vue              # 任务表单
+│   ├── Favorites.vue             # 我的关注
+│   ├── Help.vue                  # 帮助
+│   └── spec/                     # 检规管理
+│       ├── SpecIndex.vue         # 检规索引
+│       ├── SpecDetail.vue        # 检规详情
+│       ├── SpecVersion.vue       # 版本对比
+│       └── SpecImport.vue        # 导入检规
+├── components/Iqc/               # 质量管理组件
+│   ├── StatusBadge.vue           # 状态徽标
+│   ├── SearchFilter.vue          # 搜索筛选
+│   ├── PermissionButton.vue      # 权限按钮
+│   └── EmptyState.vue            # 空状态
+├── store/modules/iqc/            # 状态管理
+│   ├── user.ts                   # 用户权限
+│   ├── application.ts            # 申请管理
+│   └── task.ts                   # 任务管理
+└── config/iqcModules.ts          # 模块配置
+```
+
+## 路由配置
+
+路由基础路径: `/qms`
+
+| 路由路径 | 名称 | 组件 |
+|---------|------|------|
+| /qms/iqc/apply/list | IqcApplicationList | ApplicationList.vue |
+| /qms/iqc/task/list | IqcTaskList | TaskList.vue |
+| /qms/ipqc/apply/list | IpqcApplicationList | ApplicationList.vue |
+| /qms/ipqc/task/list | IpqcTaskList | TaskList.vue |
+| /qms/fqc/apply/list | FqcApplicationList | ApplicationList.vue |
+| /qms/fqc/task/list | FqcTaskList | TaskList.vue |
+| /qms/spec/index | SpecIndex | spec/SpecIndex.vue |
+| /qms/spec/import | SpecImport | spec/SpecImport.vue |
+
+## 数据库菜单
+
+执行 `sql/mysql/custom_modules_menu.sql` 中的质量管理系统部分。
+
+## 权限标识
+
+- `qms:iqc:apply:list` - IQC 来料检验申请
+- `qms:iqc:task:list` - IQC 来料检验任务
+- `qms:ipqc:apply:list` - IPQC 过程检验申请
+- `qms:ipqc:task:list` - IPQC 过程检验任务
+- `qms:fqc:apply:list` - FQC 成品检验申请
+- `qms:fqc:task:list` - FQC 成品检验任务
+- `qms:spec:index` - 检规索引
+- `qms:spec:import` - 导入检规
+
+## 组件复用
+
+IQC/IPQC/FQC 三种检验类型共用同一套组件,通过 `route.meta.module` 区分模块类型:
+- `iqc` - 来料检验
+- `ipqc` - 过程检验
+- `fqc` - 成品检验
+- `spec` - 检规管理
+
+配置文件 `iqcModules.ts` 定义了各模块的差异化配置。

+ 120 - 0
caigou/DEPLOYMENT_GUIDE.md

@@ -0,0 +1,120 @@
+# 物料交货计划系统部署指南
+
+本指南将帮助您将物料交货计划系统部署到可从Internet访问的环境,方便供应商远程访问。
+
+## 方案一:使用Python HTTP服务器(简单快速)
+
+如果您的计算机有公网IP地址,可以使用Python的内置HTTP服务器快速发布系统:
+
+### 步骤:
+
+1. **确保已安装Python**
+   - 在Windows上,按Win+R,输入`cmd`,然后输入`python --version`检查是否已安装
+   - 如果未安装,请从官网下载并安装:https://www.python.org/downloads/
+
+2. **打开命令提示符**
+   - 按Win+R,输入`cmd`,然后按Enter
+
+3. **导航到项目目录**
+   ```cmd
+   cd f:\采购执行
+   ```
+
+4. **启动HTTP服务器**
+   ```cmd
+   python -m http.server 80 --bind 0.0.0.0
+   ```
+   - 这将在80端口启动服务器,并绑定到所有网络接口
+   - 注意:使用80端口可能需要管理员权限
+
+5. **获取您的公网IP地址**
+   - 在浏览器中访问:https://www.ip138.com/ 或 https://ipinfo.io/
+   - 记录显示的公网IP地址
+
+6. **配置防火墙**
+   - 确保Windows防火墙允许通过80端口的HTTP流量
+   - 参考:[如何在Windows防火墙中添加端口例外](https://support.microsoft.com/zh-cn/windows/%E5%9C%A8-windows-%E4%B8%AD%E6%93%8D%E4%BD%9C-windows-%E9%98%B2%E7%81%AB%E5%A2%99-585e9618-99ef-2cc7-b208-2e93ef66681f)
+
+7. **提供访问地址给供应商**
+   - 供应商可以通过 `http://您的公网IP地址/material-delivery-plan.html` 访问系统
+
+## 方案二:部署到云存储或静态网站托管服务(推荐)
+
+对于长期稳定的访问,推荐将文件部署到云存储服务或静态网站托管服务:
+
+### 云存储服务(以阿里云OSS为例):
+
+1. 注册阿里云账号并登录
+2. 创建OSS存储空间
+3. 开启静态网站托管功能
+4. 上传所有项目文件到存储空间
+5. 配置域名解析(可选)
+
+### GitHub Pages:
+
+1. 注册GitHub账号
+2. 创建新的仓库
+3. 将项目文件上传到仓库
+4. 在仓库设置中开启GitHub Pages功能
+5. 获取生成的访问地址
+
+### 其他选项:
+- Netlify:https://www.netlify.com/
+- Vercel:https://vercel.com/
+- AWS S3:https://aws.amazon.com/cn/s3/
+
+## 方案三:部署到Web服务器(专业)
+
+如果您有自己的Web服务器(如Nginx、Apache),可以按照以下步骤部署:
+
+### Nginx部署步骤:
+
+1. 安装Nginx
+2. 将项目文件复制到Nginx的网站根目录(通常是 `/usr/share/nginx/html` 或 `C:\nginx\html`)
+3. 修改Nginx配置文件,确保正确配置了静态文件服务
+4. 重启Nginx服务
+5. 配置域名和SSL证书(可选,推荐用于安全性)
+
+## 安全注意事项
+
+1. **数据安全**:
+   - 此系统目前在客户端浏览器中处理数据,所有数据仅保存在用户的浏览器内存中
+   - 刷新页面会导致数据丢失
+   - 如果需要长期保存数据,建议添加后端服务
+
+2. **访问控制**:
+   - 当前系统没有用户认证机制
+   - 对于敏感数据,建议添加身份验证功能
+   - 可以考虑使用简单的HTTP Basic Authentication或集成专业的认证系统
+
+3. **跨域资源**:
+   - 系统使用了CDN加载Vue 3和SheetJS库
+   - 确保这些CDN资源在您的网络环境中可访问
+
+## 故障排除
+
+- **无法从外部访问**:
+  - 检查防火墙设置是否正确
+  - 确认网络是否有NAT或端口转发限制
+  - 尝试使用不同的端口(如8080)
+
+- **页面加载错误**:
+  - 检查浏览器控制台是否有错误信息
+  - 确认所有文件路径正确
+  - 验证CSS和JavaScript文件是否成功加载
+
+- **Excel文件处理问题**:
+  - 确保上传的Excel文件格式正确
+  - 检查文件是否包含系统所需的所有字段
+
+## 系统升级建议
+
+为了提高系统的实用性和安全性,建议考虑以下升级:
+
+1. 添加后端服务以持久化保存数据
+2. 实现用户认证和授权机制
+3. 添加数据加密功能
+4. 定期备份重要数据
+5. 考虑使用WebSocket实现实时更新
+
+需要更多帮助或有定制需求,请联系系统管理员或技术支持。

+ 65 - 0
caigou/README.md

@@ -0,0 +1,65 @@
+# 采购执行系统 - 物料交货计划管理
+
+## 项目概述
+
+这是一个基于SCOR供应链模型的物料交货计划管理系统,旨在实现客户与供应商之间的高效交互。系统提供了Excel上传、ERP数据刷新、交货计划列表管理、过滤搜索和统计分析等功能,帮助企业更有效地管理采购执行过程。
+
+## 文件说明
+
+- **index.html**: 普通HTML版本的Excel分析工具
+- **vue-index.html**: Vue 3版本的Excel分析工具,提供更丰富的交互体验
+- **material-delivery-plan.html**: 基于SCOR模型的物料交货计划管理系统
+- **styles.css**: 全局样式表
+- **script.js**: 通用JavaScript功能
+- **供应商交货计划.xlsx**: 示例数据文件
+
+## 功能特点
+
+### 1. 物料交货计划管理
+
+- **14个关键字段展示**: 工厂代码、物料号、物料描述、采购订单号、订单行号、订单数量、已交数量、在途数量、待交数量、交货数量、交货日期、供应商回复数量、供应商回复交期、备注
+- **供应商交互功能**: 供应商可以直接编辑和保存"供应商回复数量"和"供应商回复交期"字段
+- **数据获取方式**: 支持通过Excel文件上传或模拟ERP接口获取数据
+
+### 2. 过滤和搜索功能
+
+- 支持按工厂代码、物料号、采购订单号进行快速筛选
+- 提供供应商和日期范围的筛选选项
+
+### 3. 统计分析
+
+- 实时显示计划总数、已完成数量、逾期数量等关键指标
+- 提供完成率、准时率等绩效指标
+
+## 使用方法
+
+1. 打开 `material-delivery-plan.html` 文件
+2. 点击"上传Excel"按钮选择并上传包含交货计划数据的Excel文件
+3. 系统会自动解析并展示交货计划列表
+4. 供应商可以点击"编辑"按钮修改"供应商回复数量"和"供应商回复交期"字段
+5. 修改完成后点击"保存"按钮提交更改,或点击"取消"按钮放弃修改
+6. 使用左侧的过滤控件进行数据筛选
+7. 查看统计区域了解整体交货计划状态
+
+## 技术说明
+
+- **前端框架**: 使用原生JavaScript实现,同时兼容Vue.js组件
+- **数据处理**: 集成SheetJS库处理Excel文件解析
+- **样式设计**: 采用现代化UI设计,支持响应式布局
+- **浏览器兼容性**: 支持Chrome、Firefox、Safari、Edge等主流浏览器
+
+## 项目升级指南
+
+如需升级或扩展项目功能,请遵循以下步骤:
+
+1. 保持代码风格一致性
+2. 确保新功能与现有功能兼容
+3. 为新功能添加适当的注释说明
+4. 验证所有功能在不同浏览器中正常运行
+
+## 注意事项
+
+- 上传的Excel文件必须包含系统所需的所有字段
+- 供应商只能修改指定的两个字段,确保数据安全性
+- 为获得最佳体验,建议使用最新版本的Chrome或Firefox浏览器
+- 系统支持离线使用,但数据仅保存在浏览器内存中,刷新页面会丢失

+ 40 - 0
caigou/index.html

@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>供应商交货计划分析</title>
+    <link rel="stylesheet" href="styles.css">
+    <script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
+</head>
+<body>
+    <div class="container">
+        <h1>供应商交货计划分析</h1>
+        
+        <div class="upload-section">
+            <label for="file-upload" class="upload-btn">
+                选择Excel文件
+                <input type="file" id="file-upload" accept=".xlsx, .xls" style="display: none;">
+            </label>
+            <span id="file-name" class="file-name">未选择文件</span>
+            <button id="analyze-btn" class="analyze-btn" disabled>开始分析</button>
+        </div>
+        
+        <div class="analysis-section">
+            <h2>字段分析结果</h2>
+            <div id="field-analysis" class="field-analysis">
+                <p class="placeholder">请上传Excel文件以查看分析结果</p>
+            </div>
+        </div>
+        
+        <div class="preview-section">
+            <h2>数据预览</h2>
+            <div id="data-preview" class="data-preview">
+                <p class="placeholder">请上传Excel文件以查看数据预览</p>
+            </div>
+        </div>
+    </div>
+    
+    <script src="script.js"></script>
+</body>
+</html>

+ 768 - 0
caigou/material-delivery-plan.html

@@ -0,0 +1,768 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>物料交货计划 - SCOR供应链模型</title>
+    <link rel="stylesheet" href="styles.css">
+    <!-- 引入Vue 3 -->
+    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
+    <!-- 引入SheetJS用于Excel解析 -->
+    <script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
+</head>
+<body>
+    <div id="app">
+        <div class="container">
+            <h1>物料交货计划管理系统</h1>
+            
+            <!-- 操作区域 -->
+            <div class="upload-section">
+                <label for="file-upload" class="upload-btn">
+                    上传Excel数据
+                    <input 
+                        type="file" 
+                        id="file-upload" 
+                        accept=".xlsx, .xls" 
+                        style="display: none;" 
+                        @change="handleFileUpload">
+                </label>
+                <span id="file-name" class="file-name">{{ fileName || '未选择文件' }}</span>
+                <button 
+                    id="refresh-btn" 
+                    class="analyze-btn"
+                    @click="refreshFromERP"
+                >
+                    从ERP刷新数据
+                </button>
+                <button 
+                    id="save-btn" 
+                    class="analyze-btn"
+                    :disabled="!hasUnsavedChanges"
+                    @click="saveChanges"
+                >
+                    保存修改
+                </button>
+            </div>
+            
+            <!-- 过滤和搜索区域 -->
+            <div class="filter-section">
+                <div class="filter-controls">
+                    <select v-model="filterFactoryCode" class="filter-input">
+                        <option value="">所有工厂</option>
+                        <option v-for="factory in factories" :key="factory" :value="factory">{{ factory }}</option>
+                    </select>
+                    <select v-model="filterSupplier" class="filter-input">
+                        <option value="">所有供应商</option>
+                        <option v-for="supplier in suppliers" :key="supplier" :value="supplier">{{ supplier }}</option>
+                    </select>
+                    <input 
+                        type="text" 
+                        v-model="searchKeyword" 
+                        class="filter-input" 
+                        placeholder="搜索物料编码/描述/订单号/图号/供应商"
+                    >
+                </div>
+            </div>
+            
+            <!-- 交货计划列表 -->
+            <div class="delivery-plan-section">
+                <h2>物料交货计划列表</h2>
+                
+                <div v-if="isLoading" class="loading-indicator">
+                    <p>数据加载中...</p>
+                </div>
+                
+                <div v-else-if="deliveryPlanData.length === 0" class="placeholder">
+                    <p>暂无数据,请上传Excel文件或从ERP刷新数据</p>
+                </div>
+                
+                <div v-else class="delivery-plan-table-container">
+                    <table class="data-table">
+                        <thead>
+                            <tr>
+                                <th>工厂代码</th>
+                                <th>物料编码</th>
+                                <th>物料描述</th>
+                                <th>图号</th>
+                                <th>版本</th>
+                                <th>采购订单号</th>
+                                <th>订单行</th>
+                                <th>供应商代码</th>
+                                <th>采购员</th>
+                                <th>供应类型</th>
+                                <th>订单数量</th>
+                                <th>入库数量</th>
+                                <th>在途数量</th>
+                                <th>待交数量</th>
+                                <th>交货数量</th>
+                                <th>交货日期</th>
+                                <th>供应商回复数量</th>
+                                <th>供应商回复交期</th>
+                                <th>备注</th>
+                                <th>操作</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            <tr v-for="(row, index) in filteredData" :key="index" :class="{ 'modified-row': isRowModified(row) }">
+                                <td>{{ row.工厂代码 || '-' }}</td>
+                                <td>{{ row.物料编码 || '-' }}</td>
+                                <td>{{ row.物料描述 || '-' }}</td>
+                                <td>{{ row.图号 || '-' }}</td>
+                                <td>{{ row.版本 || '-' }}</td>
+                                <td>{{ row.采购订单号 || '-' }}</td>
+                                <td>{{ row.订单行 || '-' }}</td>
+                                <td>{{ row.供应商代码 || '-' }}</td>
+                                <td>{{ row.采购员 || '-' }}</td>
+                                <td>{{ row.供应类型 || '-' }}</td>
+                                <td>{{ formatNumber(row.订单数量) }}</td>
+                                <td>{{ formatNumber(row.入库数量) }}</td>
+                                <td>{{ formatNumber(row.在途数量) }}</td>
+                                <td>{{ formatNumber(row.待交数量) }}</td>
+                                <td>{{ formatNumber(row.交货数量) }}</td>
+                                <td>{{ formatDate(row.交货日期) }}</td>
+                                <td>
+                                    <input 
+                                        type="number" 
+                                        :value="row.供应商回复数量 || ''" 
+                                        @input="updateSupplierReplyQuantity(row, $event)"
+                                        class="editable-input supplier-reply-quantity"
+                                        min="0"
+                                        step="1"
+                                    >
+                                </td>
+                                <td>
+                                    <input 
+                                        type="date" 
+                                        v-model="row.供应商回复交期" 
+                                        class="editable-input"
+                                        @change="markAsModified(row)"
+                                    >
+                                </td>
+                                <td>
+                                    <input 
+                                        type="text" 
+                                        v-model="row.备注" 
+                                        class="editable-input"
+                                        @change="markAsModified(row)"
+                                    >
+                                </td>
+                                <td>
+                                    <button class="action-btn save-btn" @click="saveRow(row)">保存</button>
+                                    <button class="action-btn cancel-btn" @click="cancelEdit(row)">取消</button>
+                                </td>
+                            </tr>
+                        </tbody>
+                    </table>
+                    <div class="table-footer">
+                        <p>共 {{ filteredData.length }} 条记录 / 总计 {{ deliveryPlanData.length }} 条</p>
+                    </div>
+                </div>
+            </div>
+            
+            <!-- 统计信息区域 -->
+            <div class="stats-section" v-if="deliveryPlanData.length > 0">
+                <h2>交货计划统计</h2>
+                <div class="stats-cards">
+                    <div class="stat-card">
+                        <div class="stat-value">{{ formatNumber(totalOrderQuantity) }}</div>
+                        <div class="stat-label">总订单数量</div>
+                    </div>
+                    <div class="stat-card">
+                        <div class="stat-value">{{ formatNumber(totalDeliveredQuantity) }}</div>
+                        <div class="stat-label">已交货数量</div>
+                    </div>
+                    <div class="stat-card">
+                        <div class="stat-value">{{ formatNumber(totalPendingQuantity) }}</div>
+                        <div class="stat-label">待交货数量</div>
+                    </div>
+                    <div class="stat-card">
+                        <div class="stat-value">{{ formatNumber(totalInTransitQuantity) }}</div>
+                        <div class="stat-label">在途数量</div>
+                    </div>
+                    <div class="stat-card">
+                        <div class="stat-value">{{ onTimeRate }}%</div>
+                        <div class="stat-label">按时交货率</div>
+                        <div class="stat-formula">公式: 按时交货记录数/总记录数×100%</div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    
+    <script>
+        const { createApp, ref, reactive, computed } = Vue;
+        
+        createApp({
+            setup() {
+                // 响应式数据
+                const fileName = ref('');
+                const selectedFile = ref(null);
+                const deliveryPlanData = ref([]);
+                const originalData = ref([]);
+                const isLoading = ref(false);
+                const hasUnsavedChanges = ref(false);
+                const modifiedRows = ref(new Set());
+                
+                // 过滤和搜索
+                const filterFactoryCode = ref('');
+                const filterSupplier = ref('');
+                const searchKeyword = ref('');
+                
+                // 计算属性 - 工厂列表
+                const factories = computed(() => {
+                    const uniqueFactories = new Set(deliveryPlanData.value.map(row => row.工厂代码).filter(Boolean));
+                    return Array.from(uniqueFactories).sort();
+                });
+                
+                // 计算属性 - 供应商列表
+                const suppliers = computed(() => {
+                    const uniqueSuppliers = new Set(deliveryPlanData.value.map(row => row.供应商代码).filter(Boolean));
+                    return Array.from(uniqueSuppliers).sort();
+                });
+                
+                // 计算属性 - 过滤后的数据
+                const filteredData = computed(() => {
+                    return deliveryPlanData.value.filter(row => {
+                        // 工厂代码过滤
+                        if (filterFactoryCode.value && row.工厂代码 !== filterFactoryCode.value) {
+                            return false;
+                        }
+                        // 供应商过滤
+                        if (filterSupplier.value && row.供应商代码 !== filterSupplier.value) {
+                            return false;
+                        }
+                        // 搜索关键词
+                        if (searchKeyword.value) {
+                            const keyword = searchKeyword.value.toLowerCase();
+                            return (
+                                (row.物料编码 && row.物料编码.toLowerCase().includes(keyword)) ||
+                                (row.物料描述 && row.物料描述.toLowerCase().includes(keyword)) ||
+                                (row.采购订单号 && row.采购订单号.toLowerCase().includes(keyword)) ||
+                                (row.图号 && row.图号.toLowerCase().includes(keyword)) ||
+                                (row.供应商代码 && row.供应商代码.toLowerCase().includes(keyword))
+                            );
+                        }
+                        return true;
+                    });
+                });
+                
+                // 计算属性 - 统计数据
+                const totalOrderQuantity = computed(() => {
+                    return deliveryPlanData.value.reduce((sum, row) => sum + (parseFloat(row.订单数量) || 0), 0);
+                });
+                
+                const totalDeliveredQuantity = computed(() => {
+                    return deliveryPlanData.value.reduce((sum, row) => sum + (parseFloat(row.入库数量) || 0), 0);
+                });
+                
+                const totalPendingQuantity = computed(() => {
+                    return deliveryPlanData.value.reduce((sum, row) => sum + (parseFloat(row.待交数量) || 0), 0);
+                });
+                
+                // 计算属性 - 在途数量总计
+                const totalInTransitQuantity = computed(() => {
+                    return deliveryPlanData.value.reduce((sum, row) => sum + (parseFloat(row.在途数量) || 0), 0);
+                });
+                
+                const onTimeRate = computed(() => {
+                    const totalLines = deliveryPlanData.value.length;
+                    if (totalLines === 0) return 0;
+                    
+                    const onTimeLines = deliveryPlanData.value.filter(row => {
+                        if (!row.供应商回复交期 || !row.交货日期) return false;
+                        return new Date(row.供应商回复交期) <= new Date(row.交货日期);
+                    }).length;
+                    
+                    return ((onTimeLines / totalLines) * 100).toFixed(2);
+                });
+                
+                // 处理文件上传
+                const handleFileUpload = (event) => {
+                    const file = event.target.files[0];
+                    if (file) {
+                        fileName.value = file.name;
+                        selectedFile.value = file;
+                        parseExcelFile(file);
+                    }
+                };
+                
+                // 解析Excel文件
+                const parseExcelFile = (file) => {
+                    isLoading.value = true;
+                    const reader = new FileReader();
+                    
+                    // 添加文件读取进度反馈
+                    reader.onprogress = function(e) {
+                        if (e.lengthComputable) {
+                            const percentComplete = Math.round((e.loaded / e.total) * 100);
+                            console.log(`文件加载进度: ${percentComplete}%`);
+                        }
+                    };
+                    
+                    // 处理文件读取错误
+                    reader.onerror = function() {
+                        console.error('文件读取失败');
+                        alert('无法读取文件,请确保文件格式正确且没有被占用。');
+                        isLoading.value = false;
+                    };
+                    
+                    reader.onload = function(e) {
+                        try {
+                            const data = new Uint8Array(e.target.result);
+                            
+                            // 增强兼容性配置
+                            const workbook = XLSX.read(data, {
+                                type: 'array',
+                                cellDates: true,  // 自动识别日期
+                                cellText: false,  // 保持数值类型
+                                cellNF: false,    // 禁用数字格式
+                                raw: true         // 获取原始值
+                            });
+                            
+                            // 获取第一个工作表
+                            if (workbook.SheetNames.length === 0) {
+                                throw new Error('Excel文件中没有找到工作表');
+                            }
+                            
+                            const firstSheetName = workbook.SheetNames[0];
+                            const worksheet = workbook.Sheets[firstSheetName];
+                            
+                            // 转换为JSON格式,增加头部行处理选项
+                            const jsonData = XLSX.utils.sheet_to_json(worksheet, {
+                                header: 1,  // 先获取原始行数据,处理可能的表头问题
+                                raw: false  // 转换为适当的JavaScript类型
+                            });
+                            
+                            // 处理表头和数据
+                            if (jsonData.length === 0) {
+                                throw new Error('Excel文件中没有数据');
+                            }
+                            
+                            // 获取表头行
+                            const headerRow = jsonData[0];
+                            // 准备转换为对象数组
+                            const processedData = [];
+                            
+                            // 从第二行开始处理数据
+                            for (let i = 1; i < jsonData.length; i++) {
+                                const row = jsonData[i];
+                                const rowObj = {};
+                                
+                                // 遍历表头,将数据映射到对应的字段
+                                headerRow.forEach((header, index) => {
+                                    if (header && header.trim() !== '') {
+                                        // 转换列索引为字母(如0->A, 1->B, ..., 12->M)
+                                        const colIndex = String.fromCharCode(65 + index);
+                                        // 同时保存原始列名和字母列名,增强兼容性
+                                        rowObj[header] = row[index];
+                                        rowObj[colIndex] = row[index];
+                                    }
+                                });
+                                
+                                if (Object.keys(rowObj).length > 0) {
+                                    processedData.push(rowObj);
+                                }
+                            }
+                            
+                            // 处理数据,确保所有必需字段存在
+                            deliveryPlanData.value = processedData.map(row => {
+                                // 创建行的深拷贝以支持修改检测
+                                const newRow = JSON.parse(JSON.stringify(row));
+                                
+                                // 重命名和转换字段,增加更多别名支持
+                                const aliases = {
+                                    '物料号': '物料编码',
+                                    '物料编码': '物料编码',
+                                    'Material': '物料编码',
+                                    'Material Code': '物料编码',
+                                    '订单行号': '订单行',
+                                    '行号': '订单行',
+                                    'Line': '订单行',
+                                    '已交数量': '入库数量',
+                                    '已交货数量': '入库数量',
+                                    'Delivered Qty': '入库数量',
+                                    '供应商回复数量': '供应商回复数量',
+                                    'Reply Qty': '供应商回复数量',
+                                    '交货日期': '交货日期',
+                                    'Delivery Date': '交货日期',
+                                    '供应商回复交期': '供应商回复交期',
+                                    'Reply Date': '供应商回复交期'
+                                };
+                                
+                                // 应用别名映射
+                                Object.keys(aliases).forEach(alias => {
+                                    if (row[alias] !== undefined && newRow[aliases[alias]] === undefined) {
+                                        newRow[aliases[alias]] = row[alias];
+                                    }
+                                });
+                                
+                                // 确保在途数量从Excel的M列获取,增加更多可能的列名
+                                if (row['M'] !== undefined) newRow.在途数量 = row['M'];
+                                if (row['在途数量'] !== undefined) newRow.在途数量 = row['在途数量'];
+                                if (row['In Transit'] !== undefined) newRow.在途数量 = row['In Transit'];
+                                
+                                // 计算待交数量 = 订单数量 - 入库数量 - 在途数量
+                                const orderQty = parseFloat(newRow.订单数量 || 0);
+                                const deliveredQty = parseFloat(newRow.入库数量 || 0);
+                                const inTransitQty = parseFloat(newRow.在途数量 || 0);
+                                newRow.待交数量 = orderQty - deliveredQty - inTransitQty;
+                                
+                                // 确保数字字段是数字类型
+                                ['订单数量', '入库数量', '在途数量', '待交数量', '交货数量', '供应商回复数量'].forEach(field => {
+                                    if (newRow[field] !== undefined && newRow[field] !== null && newRow[field] !== '') {
+                                        newRow[field] = parseFloat(newRow[field]);
+                                    }
+                                });
+                                
+                                // 处理日期格式,确保与Excel数据一致
+                                if (newRow.交货日期) {
+                                    newRow.交货日期 = formatDateForStorage(newRow.交货日期);
+                                }
+                                if (newRow.供应商回复交期) {
+                                    newRow.供应商回复交期 = formatDateForStorage(newRow.供应商回复交期);
+                                }
+                                
+                                return newRow;
+                            });
+                            
+                            // 保存原始数据用于比较和取消操作
+                            originalData.value = JSON.parse(JSON.stringify(deliveryPlanData.value));
+                            
+                            // 重置状态
+                            hasUnsavedChanges.value = false;
+                            modifiedRows.value.clear();
+                            
+                            // 显示成功消息
+                            if (deliveryPlanData.value.length > 0) {
+                                console.log(`成功解析Excel文件,共${deliveryPlanData.value.length}条数据`);
+                            } else {
+                                console.warn('Excel文件已解析,但没有找到有效数据行');
+                                alert('Excel文件已解析,但没有找到有效数据行,请检查文件格式。');
+                            }
+                        } catch (error) {
+                            console.error('Excel解析错误:', error);
+                            alert(`解析Excel文件时发生错误: ${error.message}\n\n请确保文件格式正确,并包含必需的数据字段。`);
+                            deliveryPlanData.value = [];
+                        } finally {
+                            isLoading.value = false;
+                        }
+                    };
+                    
+                    try {
+                        reader.readAsArrayBuffer(file);
+                    } catch (error) {
+                        console.error('文件读取启动失败:', error);
+                        alert('无法启动文件读取,请稍后重试。');
+                        isLoading.value = false;
+                    }
+                };
+                
+                // 从ERP刷新数据(模拟)
+                const refreshFromERP = () => {
+                    isLoading.value = true;
+                    
+                    // 模拟API请求延迟
+                    setTimeout(() => {
+                        // 这里应该是实际的API调用,现在使用模拟数据
+                        const mockERPData = generateMockERPData();
+                        
+                        deliveryPlanData.value = mockERPData;
+                        originalData.value = JSON.parse(JSON.stringify(mockERPData));
+                        
+                        hasUnsavedChanges.value = false;
+                        modifiedRows.value.clear();
+                        fileName.value = '';
+                        selectedFile.value = null;
+                        
+                        isLoading.value = false;
+                    }, 1000);
+                };
+                
+                // 生成模拟ERP数据
+                const generateMockERPData = () => {
+                    const factories = ['F001', 'F002', 'F003'];
+                    const materials = [
+                        { id: 'M001', desc: '电子元件A', drawing: 'DWG001', version: 'V1.0' },
+                        { id: 'M002', desc: '机械零件B', drawing: 'DWG002', version: 'V2.1' },
+                        { id: 'M003', desc: '包装材料C', drawing: 'DWG003', version: 'V1.5' },
+                        { id: 'M004', desc: '原材料D', drawing: 'DWG004', version: 'V3.0' },
+                        { id: 'M005', desc: '配件E', drawing: 'DWG005', version: 'V2.2' }
+                    ];
+                    const buyers = ['张三', '李四', '王五', '赵六'];
+                    const supplyTypes = ['常规采购', '紧急采购', '寄售', 'VMI'];
+                    
+                    const mockData = [];
+                    
+                    for (let i = 1; i <= 20; i++) {
+                        const factory = factories[Math.floor(Math.random() * factories.length)];
+                        const material = materials[Math.floor(Math.random() * materials.length)];
+                        const orderQuantity = Math.floor(Math.random() * 1000) + 100;
+                        const deliveredQuantity = Math.floor(Math.random() * orderQuantity);
+                        const inTransitQuantity = Math.floor(Math.random() * (orderQuantity - deliveredQuantity));
+                        const pendingQuantity = orderQuantity - deliveredQuantity - inTransitQuantity; // 待交数量=订单数量-入库数量-在途数量
+                        const deliveryQuantity = Math.floor(Math.random() * pendingQuantity) + 1;
+                        const buyer = buyers[Math.floor(Math.random() * buyers.length)];
+                        const supplyType = supplyTypes[Math.floor(Math.random() * supplyTypes.length)];
+                        
+                        // 生成交货日期(未来30天内)
+                        const deliveryDate = new Date();
+                        deliveryDate.setDate(deliveryDate.getDate() + Math.floor(Math.random() * 30) + 1);
+                        
+                        mockData.push({
+                            工厂代码: factory,
+                            物料编码: material.id,
+                            物料描述: material.desc,
+                            图号: material.drawing,
+                            版本: material.version,
+                            采购订单号: `PO${factory}-${String(i).padStart(5, '0')}`,
+                            订单行: Math.floor(Math.random() * 10) + 1,
+                            供应商代码: `S${String(Math.floor(Math.random() * 100) + 1).padStart(3, '0')}`,
+                            采购员: buyer,
+                            供应类型: supplyType,
+                            订单数量: orderQuantity,
+                            入库数量: deliveredQuantity,
+                            在途数量: inTransitQuantity,
+                            待交数量: pendingQuantity,
+                            交货数量: deliveryQuantity,
+                            交货日期: deliveryDate.toISOString().split('T')[0],
+                            供应商回复数量: null,
+                            供应商回复交期: null,
+                            备注: '',
+                            id: `ROW-${i}`
+                        });
+                    }
+                    
+                    return mockData;
+                };
+                
+                // 更新供应商回复数量
+                const updateSupplierReplyQuantity = (row, event) => {
+                    const value = event.target.value;
+                    // 允许空字符串或数字
+                    if (value === '' || (value !== '' && !isNaN(parseFloat(value)))) {
+                        row.供应商回复数量 = value === '' ? null : parseFloat(value);
+                        markAsModified(row);
+                    } else {
+                        // 如果输入的不是有效数字,清空输入
+                        event.target.value = '';
+                    }
+                };
+
+                // 标记行为已修改
+                const markAsModified = (row) => {
+                    modifiedRows.value.add(row.id || row.采购订单号 + '-' + row.订单行号);
+                    hasUnsavedChanges.value = true;
+                };
+                
+                // 检查行是否已修改
+                const isRowModified = (row) => {
+                    return modifiedRows.value.has(row.id || row.采购订单号 + '-' + row.订单行号);
+                };
+                
+                // 保存单行修改
+                const saveRow = (row) => {
+                    const rowKey = row.id || row.采购订单号 + '-' + row.订单行号;
+                    
+                    // 这里应该是实际的API调用,现在仅更新状态
+                    console.log('保存行修改:', row);
+                    
+                    // 在实际应用中,这里会调用API保存数据
+                    // 保存成功后更新原始数据
+                    const originalRowIndex = originalData.value.findIndex(
+                        r => (r.id || r.采购订单号 + '-' + r.订单行号) === rowKey
+                    );
+                    
+                    if (originalRowIndex !== -1) {
+                        originalData.value[originalRowIndex] = JSON.parse(JSON.stringify(row));
+                    }
+                    
+                    modifiedRows.value.delete(rowKey);
+                    hasUnsavedChanges.value = modifiedRows.value.size > 0;
+                };
+                
+                // 取消编辑
+                const cancelEdit = (row) => {
+                    const rowKey = row.id || row.采购订单号 + '-' + row.订单行号;
+                    
+                    // 查找原始数据
+                    const originalRow = originalData.value.find(
+                        r => (r.id || r.采购订单号 + '-' + r.订单行号) === rowKey
+                    );
+                    
+                    if (originalRow) {
+                        // 恢复原始数据
+                        Object.keys(originalRow).forEach(key => {
+                            row[key] = originalRow[key];
+                        });
+                    }
+                    
+                    modifiedRows.value.delete(rowKey);
+                    hasUnsavedChanges.value = modifiedRows.value.size > 0;
+                };
+                
+                // 保存所有修改
+                const saveChanges = () => {
+                    // 获取所有修改过的行
+                    const modifiedData = deliveryPlanData.value.filter(row => 
+                        isRowModified(row)
+                    );
+                    
+                    if (modifiedData.length === 0) {
+                        alert('没有需要保存的修改');
+                        return;
+                    }
+                    
+                    // 这里应该是实际的API调用,现在仅显示信息
+                    console.log('保存所有修改:', modifiedData);
+                    
+                    // 保存成功后更新原始数据
+                    modifiedData.forEach(row => {
+                        const rowKey = row.id || row.采购订单号 + '-' + row.订单行号;
+                        const originalRowIndex = originalData.value.findIndex(
+                            r => (r.id || r.采购订单号 + '-' + r.订单行号) === rowKey
+                        );
+                        
+                        if (originalRowIndex !== -1) {
+                            originalData.value[originalRowIndex] = JSON.parse(JSON.stringify(row));
+                        }
+                        
+                        modifiedRows.value.delete(rowKey);
+                    });
+                    
+                    hasUnsavedChanges.value = false;
+                    alert('修改已成功保存');
+                };
+                
+                // 格式化数字
+                const formatNumber = (num) => {
+                    if (num === null || num === undefined || isNaN(num)) return '-';
+                    return parseFloat(num).toLocaleString('zh-CN');
+                };
+                
+                // 格式化日期显示
+                const formatDate = (dateString) => {
+                    if (!dateString) return '-';
+                    
+                    try {
+                        let date;
+                        
+                        // 检查是否是Excel日期数值(从1900年1月1日开始的天数)
+                        if (!isNaN(dateString) && typeof dateString === 'number') {
+                            // Excel从1900年1月1日开始,但JavaScript从1970年1月1日开始
+                            // 计算从1900-01-01到1970-01-01的毫秒数,加上Excel天数的毫秒数
+                            const excelEpoch = new Date(1900, 0, 1).getTime();
+                            const msPerDay = 24 * 60 * 60 * 1000;
+                            // 注意:Excel错误地认为1900年是闰年,所以需要减去1天
+                            const correctedDays = dateString < 60 ? dateString - 1 : dateString - 2;
+                            date = new Date(excelEpoch + correctedDays * msPerDay);
+                        } else if (typeof dateString === 'string') {
+                            // 处理YYYY-MM-DD格式
+                            if (/^\d{4}-\d{2}-\d{2}$/.test(dateString)) {
+                                return dateString;
+                            }
+                            // 处理其他字符串格式
+                            date = new Date(dateString);
+                        } else {
+                            // 尝试直接使用
+                            date = new Date(dateString);
+                        }
+                        
+                        // 检查日期是否有效
+                        if (isNaN(date.getTime())) return dateString; // 如果无法解析,返回原始值
+                        
+                        // 格式化日期为YYYY-MM-DD格式
+                        const year = date.getFullYear();
+                        const month = String(date.getMonth() + 1).padStart(2, '0');
+                        const day = String(date.getDate()).padStart(2, '0');
+                        
+                        return `${year}-${month}-${day}`;
+                    } catch (e) {
+                        console.error('日期格式化错误:', e);
+                        return dateString; // 如果出错,返回原始值
+                    }
+                };
+                
+                // 格式化日期为存储格式(确保与Excel数据一致)
+                const formatDateForStorage = (dateString) => {
+                    if (!dateString) return null;
+                    
+                    try {
+                        // 尝试直接解析日期字符串
+                        let date;
+                        
+                        // 如果已经是YYYY-MM-DD格式,直接返回
+                        if (typeof dateString === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(dateString)) {
+                            return dateString;
+                        }
+                        
+                        // 检查是否是Excel日期数值(从1900年1月1日开始的天数)
+                        if (!isNaN(dateString) && typeof dateString === 'number') {
+                            // Excel从1900年1月1日开始,但JavaScript从1970年1月1日开始
+                            // 计算从1900-01-01到1970-01-01的毫秒数,加上Excel天数的毫秒数
+                            const excelEpoch = new Date(1900, 0, 1).getTime();
+                            const msPerDay = 24 * 60 * 60 * 1000;
+                            // 注意:Excel错误地认为1900年是闰年,所以需要减去1天
+                            const correctedDays = dateString < 60 ? dateString - 1 : dateString - 2;
+                            date = new Date(excelEpoch + correctedDays * msPerDay);
+                        } else if (typeof dateString === 'string') {
+                            // 处理ISO格式日期
+                            if (dateString.includes('T')) {
+                                date = new Date(dateString);
+                            } else {
+                                // 处理其他字符串格式
+                                date = new Date(dateString);
+                            }
+                        } else {
+                            // 尝试直接使用
+                            date = new Date(dateString);
+                        }
+                        
+                        // 检查日期是否有效
+                        if (isNaN(date.getTime())) return dateString; // 如果无法解析,返回原始值
+                        
+                        // 格式化日期为YYYY-MM-DD格式
+                        const year = date.getFullYear();
+                        const month = String(date.getMonth() + 1).padStart(2, '0');
+                        const day = String(date.getDate()).padStart(2, '0');
+                        
+                        return `${year}-${month}-${day}`;
+                    } catch (e) {
+                        console.error('日期存储格式化错误:', e);
+                        return dateString; // 如果出错,返回原始值
+                    }
+                };
+                
+                return {
+                    fileName,
+                    selectedFile,
+                    deliveryPlanData,
+                    isLoading,
+                    hasUnsavedChanges,
+                    filterFactoryCode,
+                    filterSupplier,
+                    searchKeyword,
+                    factories,
+                    suppliers,
+                    filteredData,
+                    totalOrderQuantity,
+                    totalDeliveredQuantity,
+                    totalPendingQuantity,
+                    totalInTransitQuantity,
+                    onTimeRate,
+                    handleFileUpload,
+                    refreshFromERP,
+                    saveChanges,
+                    markAsModified,
+                    isRowModified,
+                    saveRow,
+                    cancelEdit,
+                    formatNumber,
+                    formatDate,
+                    updateSupplierReplyQuantity,
+                    formatDateForStorage
+                };
+            }
+        }).mount('#app');
+    </script>
+</body>
+</html>

+ 257 - 0
caigou/script.js

@@ -0,0 +1,257 @@
+// 供应商交货计划分析工具
+// 用于分析Excel文件中的字段信息,包括数据类型、空值统计等
+
+// 确保DOM加载完成后执行代码
+document.addEventListener('DOMContentLoaded', function() {
+    // 获取DOM元素
+    const fileUpload = document.getElementById('file-upload');
+    const fileName = document.getElementById('file-name');
+    const fieldAnalysis = document.getElementById('field-analysis');
+    const dataPreview = document.getElementById('data-preview');
+    const analyzeBtn = document.getElementById('analyze-btn');
+    
+    // 存储当前选择的文件
+    let selectedFile = null;
+
+    // 监听文件上传事件
+    fileUpload.addEventListener('change', function(e) {
+        const file = e.target.files[0];
+        if (file) {
+            fileName.textContent = file.name;
+            selectedFile = file;
+            analyzeBtn.disabled = false; // 启用分析按钮
+        }
+    });
+    
+    // 监听分析按钮点击事件
+    analyzeBtn.addEventListener('click', function() {
+        if (selectedFile) {
+            // 显示加载状态
+            fieldAnalysis.innerHTML = '<p class="placeholder">正在分析Excel文件,请稍候...</p>';
+            dataPreview.innerHTML = '';
+            parseExcelFile(selectedFile);
+        }
+    });
+
+    // 解析Excel文件
+    function parseExcelFile(file) {
+        const reader = new FileReader();
+        
+        reader.onload = function(e) {
+            const data = new Uint8Array(e.target.result);
+            const workbook = XLSX.read(data, { type: 'array' });
+            
+            // 获取第一个工作表
+            const firstSheetName = workbook.SheetNames[0];
+            const worksheet = workbook.Sheets[firstSheetName];
+            
+            // 转换为JSON格式
+            const jsonData = XLSX.utils.sheet_to_json(worksheet);
+            
+            // 分析字段
+            analyzeFields(jsonData);
+            
+            // 显示数据预览
+            showDataPreview(jsonData);
+        };
+        
+        reader.readAsArrayBuffer(file);
+    }
+
+    // 分析Excel中的每个字段
+    function analyzeFields(data) {
+        if (!data || data.length === 0) {
+            fieldAnalysis.innerHTML = '<p class="placeholder">Excel文件中没有数据</p>';
+            return;
+        }
+        
+        fieldAnalysis.innerHTML = '';
+        
+        // 获取所有字段名
+        const fields = Object.keys(data[0]);
+        
+        fields.forEach(field => {
+            const fieldStats = analyzeField(data, field);
+            const fieldCard = createFieldCard(field, fieldStats);
+            fieldAnalysis.appendChild(fieldCard);
+        });
+    }
+
+    // 分析单个字段的统计信息
+    function analyzeField(data, fieldName) {
+        const values = data.map(row => row[fieldName]).filter(val => val !== undefined && val !== null && val !== '');
+        const stats = {
+            fieldName,
+            totalCount: data.length,
+            nonEmptyCount: values.length,
+            emptyCount: data.length - values.length,
+            dataType: values.length > 0 ? getDataType(values[0], fieldName) : 'unknown',
+            uniqueValues: [...new Set(values)].length,
+            sampleValues: values.slice(0, 5) // 显示前5个样本值
+        };
+        
+        // 如果是数值类型,计算额外的统计信息
+        if (stats.dataType === 'number' || stats.dataType === 'date') {
+            const numericValues = values.map(val => {
+                if (stats.dataType === 'date') {
+                    // 处理日期类型
+                    return new Date(val).getTime();
+                }
+                return parseFloat(val);
+            }).filter(val => !isNaN(val));
+            
+            if (numericValues.length > 0) {
+                stats.min = Math.min(...numericValues);
+                stats.max = Math.max(...numericValues);
+                stats.average = numericValues.reduce((sum, val) => sum + val, 0) / numericValues.length;
+                
+                // 如果是日期类型,格式化最小和最大值
+                if (stats.dataType === 'date') {
+                    stats.min = new Date(stats.min).toLocaleDateString();
+                    stats.max = new Date(stats.max).toLocaleDateString();
+                }
+            }
+        }
+        
+        return stats;
+    }
+
+    // 特定字段名称列表(确保这些字段被识别为日期类型)
+    const DATE_FIELDS = ['交货日期', '交期回复'];
+
+    // 获取数据类型
+    function getDataType(value, fieldName = '') {
+        if (value === null || value === undefined) return 'unknown';
+        
+        // 对于特定的日期字段,强制识别为日期类型
+        if (DATE_FIELDS.includes(fieldName)) {
+            // 尝试各种日期格式解析
+            const dateFormats = [
+                value,                           // 原值
+                value.replace(/\//g, '-'),       // 将/替换为-(例如2023/05/20 -> 2023-05-20)
+                value.replace(/\./g, '-')        // 将.替换为-(例如2023.05.20 -> 2023-05-20)
+            ];
+            
+            for (const format of dateFormats) {
+                const date = new Date(format);
+                if (!isNaN(date.getTime()) && date.toISOString() !== '0001-01-01T00:00:00.000Z' && date.getFullYear() > 1900) {
+                    return 'date';
+                }
+            }
+        }
+        
+        // 尝试检查是否为日期
+        if (typeof value === 'string') {
+            const date = new Date(value);
+            if (!isNaN(date.getTime()) && date.toISOString() !== '0001-01-01T00:00:00.000Z' && date.getFullYear() > 1900) {
+                return 'date';
+            }
+        }
+        
+        // 检查是否为数字
+        if (typeof value === 'number' || !isNaN(parseFloat(value)) && isFinite(value)) {
+            return 'number';
+        }
+        
+        // 检查是否为布尔值
+        if (typeof value === 'boolean' || value === 'true' || value === 'false') {
+            return 'boolean';
+        }
+        
+        return 'string';
+    }
+
+    // 创建字段分析卡片
+    function createFieldCard(fieldName, stats) {
+        const card = document.createElement('div');
+        card.className = 'field-card';
+        
+        let content = `<h3>${fieldName}</h3><div class="field-details">`;
+        content += `<div class="field-detail">数据类型: ${stats.dataType}</div>`;
+        content += `<div class="field-detail">总记录数: ${stats.totalCount}</div>`;
+        content += `<div class="field-detail">非空值数: ${stats.nonEmptyCount} (${((stats.nonEmptyCount / stats.totalCount) * 100).toFixed(2)}%)</div>`;
+        content += `<div class="field-detail">空值数: ${stats.emptyCount} (${((stats.emptyCount / stats.totalCount) * 100).toFixed(2)}%)</div>`;
+        content += `<div class="field-detail">唯一值数量: ${stats.uniqueValues}</div>`;
+        
+        // 添加数值类型的统计信息
+        if (stats.min !== undefined) {
+            content += `<div class="field-detail">最小值: ${stats.min}</div>`;
+        }
+        if (stats.max !== undefined) {
+            content += `<div class="field-detail">最大值: ${stats.max}</div>`;
+        }
+        if (stats.average !== undefined) {
+            content += `<div class="field-detail">平均值: ${stats.average.toFixed(2)}</div>`;
+        }
+        
+        // 添加样本值
+        if (stats.sampleValues && stats.sampleValues.length > 0) {
+            content += `<div class="field-detail">样本值: ${stats.sampleValues.join(', ')}</div>`;
+        }
+        
+        content += '</div>';
+        card.innerHTML = content;
+        
+        return card;
+    }
+
+    // 显示数据预览
+    function showDataPreview(data) {
+        if (!data || data.length === 0) {
+            dataPreview.innerHTML = '<p class="placeholder">Excel文件中没有数据</p>';
+            return;
+        }
+        
+        // 获取所有字段名
+        const fields = Object.keys(data[0]);
+        
+        // 创建表格
+        const table = document.createElement('table');
+        table.className = 'data-table';
+        
+        // 创建表头
+        const thead = document.createElement('thead');
+        const headerRow = document.createElement('tr');
+        
+        fields.forEach(field => {
+            const th = document.createElement('th');
+            th.textContent = field;
+            headerRow.appendChild(th);
+        });
+        
+        thead.appendChild(headerRow);
+        table.appendChild(thead);
+        
+        // 创建表体
+        const tbody = document.createElement('tbody');
+        
+        // 只显示前10行数据
+        const previewData = data.slice(0, 10);
+        
+        previewData.forEach(row => {
+            const tr = document.createElement('tr');
+            
+            fields.forEach(field => {
+                const td = document.createElement('td');
+                td.textContent = row[field] !== undefined && row[field] !== null ? row[field] : '';
+                tr.appendChild(td);
+            });
+            
+            tbody.appendChild(tr);
+        });
+        
+        table.appendChild(tbody);
+        
+        // 清空并添加表格
+        dataPreview.innerHTML = '';
+        dataPreview.appendChild(table);
+        
+        // 如果有更多数据,显示提示
+        if (data.length > 10) {
+            const moreInfo = document.createElement('p');
+            moreInfo.className = 'placeholder';
+            moreInfo.textContent = `仅显示前10行数据,共 ${data.length} 行`;
+            dataPreview.appendChild(moreInfo);
+        }
+    }
+});

+ 426 - 0
caigou/styles.css

@@ -0,0 +1,426 @@
+/* 全局样式 */
+* {
+    margin: 0;
+    padding: 0;
+    box-sizing: border-box;
+}
+
+body {
+    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
+    line-height: 1.6;
+    color: #333;
+    background-color: #f5f5f5;
+}
+
+/* 容器样式 */
+.container {
+    max-width: 1400px;
+    margin: 0 auto;
+    padding: 20px;
+    background-color: white;
+    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+    border-radius: 8px;
+    margin-top: 50px;
+}
+
+/* 标题样式 */
+h1 {
+    color: #2c3e50;
+    margin-bottom: 30px;
+    text-align: center;
+    font-size: 2.2rem;
+}
+
+h2 {
+    color: #34495e;
+    margin: 20px 0;
+    font-size: 1.5rem;
+    border-bottom: 2px solid #ecf0f1;
+    padding-bottom: 10px;
+}
+
+/* 文件上传区域样式 */
+.upload-section {
+    text-align: center;
+    margin-bottom: 40px;
+    padding: 20px;
+    background-color: #f8f9fa;
+    border-radius: 8px;
+}
+
+.upload-section > * {
+    display: inline-block;
+    margin: 0 10px;
+    vertical-align: middle;
+}
+
+.upload-btn {
+    padding: 12px 24px;
+    background-color: #3498db;
+    color: white;
+    border: none;
+    border-radius: 6px;
+    cursor: pointer;
+    font-size: 16px;
+    transition: background-color 0.3s ease;
+}
+
+.upload-btn:hover {
+    background-color: #2980b9;
+}
+
+.file-name {
+    color: #666;
+    min-width: 150px;
+    text-align: left;
+}
+
+.analyze-btn {
+    padding: 12px 24px;
+    background-color: #2ecc71;
+    color: white;
+    border: none;
+    border-radius: 6px;
+    cursor: pointer;
+    font-size: 16px;
+    transition: background-color 0.3s ease;
+}
+
+.analyze-btn:hover:not(:disabled) {
+    background-color: #27ae60;
+}
+
+.analyze-btn:disabled {
+    background-color: #95a5a6;
+    cursor: not-allowed;
+    opacity: 0.6;
+}
+
+/* 分析结果区域样式 */
+.analysis-section,
+.preview-section {
+    margin-bottom: 40px;
+}
+
+.field-analysis,
+.data-preview {
+    background-color: #f8f9fa;
+    border-radius: 8px;
+    padding: 20px;
+    min-height: 200px;
+    overflow-x: auto;
+}
+
+.field-analysis .placeholder,
+.data-preview .placeholder {
+    text-align: center;
+    color: #95a5a6;
+    font-style: italic;
+    margin: 20px 0;
+    padding: 40px;
+    background-color: #f8f9fa;
+    border-radius: 8px;
+}
+
+/* 字段卡片样式 */
+.field-card {
+    background-color: white;
+    padding: 15px;
+    margin-bottom: 15px;
+    border-radius: 6px;
+    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
+}
+
+.field-card h3 {
+    color: #2c3e50;
+    margin-bottom: 10px;
+    font-size: 1.2rem;
+}
+
+.field-details {
+    margin-left: 20px;
+}
+
+.field-detail {
+    margin-bottom: 5px;
+    color: #555;
+}
+
+/* 表格样式 */
+.data-table {
+    width: 100%;
+    border-collapse: collapse;
+    margin-top: 20px;
+}
+
+.data-table th,
+.data-table td {
+    padding: 12px;
+    text-align: left;
+    border-bottom: 1px solid #ddd;
+}
+
+.data-table th {
+    background-color: #34495e;
+    color: white;
+    font-weight: 500;
+}
+
+.data-table tr:nth-child(even) {
+    background-color: #f9f9f9;
+}
+
+.data-table tr:hover {
+    background-color: #f1f1f1;
+}
+
+/* 过滤区域样式 */
+.filter-section {
+    margin-bottom: 30px;
+}
+
+.filter-controls {
+    display: flex;
+    gap: 15px;
+    align-items: center;
+    flex-wrap: wrap;
+}
+
+.filter-input {
+    padding: 10px 15px;
+    border: 1px solid #ddd;
+    border-radius: 6px;
+    font-size: 14px;
+    min-width: 150px;
+}
+
+.filter-input:focus {
+    outline: none;
+    border-color: #3498db;
+    box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
+}
+
+/* 交货计划区域样式 */
+.delivery-plan-section {
+    margin-bottom: 40px;
+}
+
+.delivery-plan-table-container {
+    overflow-x: auto;
+    background-color: #f8f9fa;
+    border-radius: 8px;
+    padding: 20px;
+}
+
+/* 交货计划表格样式 */
+.delivery-plan-table {
+    width: 100%;
+    border-collapse: collapse;
+    margin-top: 20px;
+    background-color: #fff;
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+    overflow-x: auto;
+    display: block;
+}
+
+.delivery-plan-table th,
+.delivery-plan-table td {
+    border: 1px solid #ddd;
+    padding: 10px;
+    text-align: left;
+    min-width: 100px;
+}
+
+/* 特定列的宽度调整 */
+.delivery-plan-table th:nth-child(1),
+.delivery-plan-table td:nth-child(1) { min-width: 80px; }  /* 工厂代码 */
+.delivery-plan-table th:nth-child(2),
+.delivery-plan-table td:nth-child(2) { min-width: 100px; } /* 物料编码 */
+.delivery-plan-table th:nth-child(3),
+.delivery-plan-table td:nth-child(3) { min-width: 150px; } /* 物料描述 */
+.delivery-plan-table th:nth-child(4),
+.delivery-plan-table td:nth-child(4) { min-width: 100px; } /* 图号 */
+.delivery-plan-table th:nth-child(5),
+.delivery-plan-table td:nth-child(5) { min-width: 80px; }  /* 版本 */
+.delivery-plan-table th:nth-child(6),
+.delivery-plan-table td:nth-child(6) { min-width: 120px; } /* 采购订单号 */
+.delivery-plan-table th:nth-child(7),
+.delivery-plan-table td:nth-child(7) { min-width: 80px; }  /* 订单行 */
+.delivery-plan-table th:nth-child(8),
+.delivery-plan-table td:nth-child(8) { min-width: 100px; } /* 供应商代码 */
+.delivery-plan-table th:nth-child(9),
+.delivery-plan-table td:nth-child(9) { min-width: 80px; }  /* 采购员 */
+.delivery-plan-table th:nth-child(10),
+.delivery-plan-table td:nth-child(10) { min-width: 100px; } /* 供应类型 */
+.delivery-plan-table th:nth-child(11),
+.delivery-plan-table td:nth-child(11) { min-width: 100px; } /* 订单数量 */
+.delivery-plan-table th:nth-child(12),
+.delivery-plan-table td:nth-child(12) { min-width: 100px; } /* 入库数量 */
+.delivery-plan-table th:nth-child(13),
+.delivery-plan-table td:nth-child(13) { min-width: 100px; } /* 在途数量 */
+.delivery-plan-table th:nth-child(14),
+.delivery-plan-table td:nth-child(14) { min-width: 100px; } /* 待交数量 */
+.delivery-plan-table th:nth-child(15),
+.delivery-plan-table td:nth-child(15) { min-width: 100px; } /* 交货数量 */
+.delivery-plan-table th:nth-child(16),
+.delivery-plan-table td:nth-child(16) { min-width: 120px; } /* 交货日期 */
+.delivery-plan-table th:nth-child(17),
+.delivery-plan-table td:nth-child(17) { min-width: 120px; } /* 供应商回复数量 */
+.delivery-plan-table th:nth-child(18),
+.delivery-plan-table td:nth-child(18) { min-width: 120px; } /* 供应商回复交期 */
+.delivery-plan-table th:nth-child(19),
+.delivery-plan-table td:nth-child(19) { min-width: 150px; } /* 备注 */
+.delivery-plan-table th:nth-child(20),
+.delivery-plan-table td:nth-child(20) { min-width: 120px; } /* 操作 */
+
+.editable-input {
+    padding: 6px 10px;
+    border: 1px solid #ddd;
+    border-radius: 4px;
+    width: 100%;
+    box-sizing: border-box;
+}
+
+/* 供应商回复数量输入框样式 - 确保能显示3位数 */
+.supplier-reply-quantity {
+    width: 60px; /* 足够显示3位数字的宽度 */
+    text-align: center;
+}
+
+.editable-input:focus {
+    outline: none;
+    border-color: #3498db;
+    box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
+}
+
+/* 操作按钮样式 */
+.action-btn {
+    padding: 6px 12px;
+    border: none;
+    border-radius: 4px;
+    cursor: pointer;
+    font-size: 12px;
+    margin-right: 5px;
+    transition: background-color 0.3s ease;
+}
+
+.save-btn {
+    background-color: #2ecc71;
+    color: white;
+}
+
+.save-btn:hover {
+    background-color: #27ae60;
+}
+
+.cancel-btn {
+    background-color: #e74c3c;
+    color: white;
+}
+
+.cancel-btn:hover {
+    background-color: #c0392b;
+}
+
+/* 修改行高亮样式 */
+.modified-row {
+    background-color: #fff3cd !important;
+}
+
+.modified-row:hover {
+    background-color: #ffeaa7 !important;
+}
+
+/* 表格底部样式 */
+.table-footer {
+    margin-top: 20px;
+    text-align: right;
+    color: #666;
+    font-size: 14px;
+}
+
+/* 统计信息区域样式 */
+.stats-section {
+    margin-bottom: 40px;
+}
+
+.stats-cards {
+    display: flex;
+    gap: 20px;
+    flex-wrap: wrap;
+}
+
+.stat-card {
+    flex: 1;
+    min-width: 150px;
+    background-color: white;
+    padding: 20px;
+    border-radius: 8px;
+    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
+    text-align: center;
+}
+
+.stat-value {
+    font-size: 2rem;
+    font-weight: bold;
+    color: #3498db;
+    margin-bottom: 10px;
+}
+
+.stat-label {
+    color: #666;
+    font-size: 14px;
+}
+
+.stat-formula {
+    font-size: 12px;
+    color: #95a5a6;
+    margin-top: 5px;
+    font-style: italic;
+}
+
+/* 加载指示器样式 */
+.loading-indicator {
+    text-align: center;
+    padding: 40px;
+    color: #666;
+    font-style: italic;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+    .container {
+        margin-top: 20px;
+        padding: 15px;
+    }
+    
+    h1 {
+        font-size: 1.8rem;
+    }
+    
+    h2 {
+        font-size: 1.3rem;
+    }
+    
+    .upload-btn {
+        padding: 10px 20px;
+        font-size: 14px;
+    }
+    
+    .filter-controls {
+        flex-direction: column;
+        align-items: stretch;
+    }
+    
+    .filter-input {
+        width: 100%;
+    }
+    
+    .stats-cards {
+        flex-direction: column;
+    }
+    
+    .stat-card {
+        width: 100%;
+    }
+}

+ 295 - 0
caigou/vue-index.html

@@ -0,0 +1,295 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>供应商交货计划分析 (Vue版)</title>
+    <link rel="stylesheet" href="styles.css">
+    <!-- 引入Vue 3 -->
+    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
+    <!-- 引入SheetJS用于Excel解析 -->
+    <script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
+</head>
+<body>
+    <div id="app">
+        <div class="container">
+            <h1>供应商交货计划分析</h1>
+            
+            <div class="upload-section">
+                <label for="file-upload" class="upload-btn">
+                    选择Excel文件
+                    <input 
+                        type="file" 
+                        id="file-upload" 
+                        accept=".xlsx, .xls" 
+                        style="display: none;" 
+                        @change="handleFileUpload">
+                </label>
+                <span id="file-name" class="file-name">{{ fileName || '未选择文件' }}</span>
+                <button 
+                    id="analyze-btn" 
+                    class="analyze-btn"
+                    :disabled="!fileName || isAnalyzing"
+                    @click="analyzeFile"
+                >
+                    {{ isAnalyzing ? '分析中...' : '开始分析' }}
+                </button>
+            </div>
+            
+            <!-- 分析结果区域 -->
+            <div class="analysis-section" v-if="fieldStats.length > 0">
+                <h2>字段分析结果</h2>
+                <div class="field-analysis">
+                    <div class="field-card" v-for="field in fieldStats" :key="field.fieldName">
+                        <h3>{{ field.fieldName }}</h3>
+                        <div class="field-details">
+                            <div class="field-detail">数据类型: {{ field.dataType }}</div>
+                            <div class="field-detail">总记录数: {{ field.totalCount }}</div>
+                            <div class="field-detail">非空值数: {{ field.nonEmptyCount }} ({{ field.nonEmptyPercentage.toFixed(2) }}%)</div>
+                            <div class="field-detail">空值数: {{ field.emptyCount }} ({{ field.emptyPercentage.toFixed(2) }}%)</div>
+                            <div class="field-detail">唯一值数量: {{ field.uniqueValues }}</div>
+                            
+                            <!-- 数值类型的统计信息 -->
+                            <div v-if="field.min !== undefined" class="field-detail">最小值: {{ field.min }}</div>
+                            <div v-if="field.max !== undefined" class="field-detail">最大值: {{ field.max }}</div>
+                            <div v-if="field.average !== undefined" class="field-detail">平均值: {{ field.average.toFixed(2) }}</div>
+                            
+                            <!-- 样本值 -->
+                            <div v-if="field.sampleValues && field.sampleValues.length > 0" class="field-detail">
+                                样本值: {{ field.sampleValues.join(', ') }}
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            
+            <!-- 数据预览区域 -->
+            <div class="preview-section" v-if="dataPreview.length > 0">
+                <h2>数据预览</h2>
+                <div class="data-preview">
+                    <table class="data-table">
+                        <thead>
+                            <tr>
+                                <th v-for="header in headers" :key="header">{{ header }}</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            <tr v-for="(row, index) in dataPreview" :key="index">
+                                <td v-for="header in headers" :key="header">{{ row[header] || '' }}</td>
+                            </tr>
+                        </tbody>
+                    </table>
+                    <p v-if="totalRows > dataPreview.length" class="placeholder">
+                        仅显示前{{ dataPreview.length }}行数据,共 {{ totalRows }} 行
+                    </p>
+                </div>
+            </div>
+            
+            <!-- 初始提示 -->
+            <div v-if="!fileName && fieldStats.length === 0" class="placeholder">
+                <p>请上传Excel文件以分析供应商交货计划数据</p>
+            </div>
+        </div>
+    </div>
+    
+    <script>
+        const { createApp, ref, reactive, computed } = Vue;
+        
+        createApp({
+            setup() {
+                // 响应式数据
+                const fileName = ref('');
+                const selectedFile = ref(null);
+                const rawData = ref([]);
+                const fieldStats = ref([]);
+                const dataPreview = ref([]);
+                const headers = ref([]);
+                const totalRows = ref(0);
+                const isAnalyzing = ref(false);
+                
+                // 特定日期字段列表
+                const DATE_FIELDS = ['交货日期', '交期回复'];
+                
+                // 处理文件上传
+                const handleFileUpload = (event) => {
+                    const file = event.target.files[0];
+                    if (file) {
+                        fileName.value = file.name;
+                        selectedFile.value = file;
+                        // 重置之前的分析结果
+                        fieldStats.value = [];
+                        dataPreview.value = [];
+                        rawData.value = [];
+                        headers.value = [];
+                        totalRows.value = 0;
+                    }
+                };
+                
+                // 分析文件
+                const analyzeFile = () => {
+                    if (selectedFile.value) {
+                        isAnalyzing.value = true;
+                        parseExcelFile(selectedFile.value);
+                    }
+                };
+                
+                // 解析Excel文件
+                const parseExcelFile = (file) => {
+                    const reader = new FileReader();
+                    
+                    reader.onload = function(e) {
+                        const data = new Uint8Array(e.target.result);
+                        const workbook = XLSX.read(data, { type: 'array' });
+                        
+                        // 获取第一个工作表
+                        const firstSheetName = workbook.SheetNames[0];
+                        const worksheet = workbook.Sheets[firstSheetName];
+                        
+                        // 转换为JSON格式
+                        rawData.value = XLSX.utils.sheet_to_json(worksheet);
+                        totalRows.value = rawData.value.length;
+                        
+                        // 分析字段
+                        analyzeFields();
+                        
+                        // 显示数据预览
+                        showDataPreview();
+                        
+                        // 分析完成,更新状态
+                        isAnalyzing.value = false;
+                    };
+                    
+                    reader.readAsArrayBuffer(file);
+                };
+                
+                // 获取数据类型
+                const getDataType = (value, fieldName = '') => {
+                    if (value === null || value === undefined) return 'unknown';
+                    
+                    // 对于特定的日期字段,强制识别为日期类型
+                    if (DATE_FIELDS.includes(fieldName)) {
+                        // 尝试各种日期格式解析
+                        const dateFormats = [
+                            value,                           // 原值
+                            value.replace(/\//g, '-'),       // 将/替换为-(例如2023/05/20 -> 2023-05-20)
+                            value.replace(/\./g, '-')        // 将.替换为-(例如2023.05.20 -> 2023-05-20)
+                        ];
+                        
+                        for (const format of dateFormats) {
+                            const date = new Date(format);
+                            if (!isNaN(date.getTime()) && date.toISOString() !== '0001-01-01T00:00:00.000Z' && date.getFullYear() > 1900) {
+                                return 'date';
+                            }
+                        }
+                    }
+                    
+                    // 尝试检查是否为日期
+                    if (typeof value === 'string') {
+                        const date = new Date(value);
+                        if (!isNaN(date.getTime()) && date.toISOString() !== '0001-01-01T00:00:00.000Z' && date.getFullYear() > 1900) {
+                            return 'date';
+                        }
+                    }
+                    
+                    // 检查是否为数字
+                    if (typeof value === 'number' || !isNaN(parseFloat(value)) && isFinite(value)) {
+                        return 'number';
+                    }
+                    
+                    // 检查是否为布尔值
+                    if (typeof value === 'boolean' || value === 'true' || value === 'false') {
+                        return 'boolean';
+                    }
+                    
+                    return 'string';
+                };
+                
+                // 分析单个字段的统计信息
+                const analyzeField = (fieldName) => {
+                    const values = rawData.value
+                        .map(row => row[fieldName])
+                        .filter(val => val !== undefined && val !== null && val !== '');
+                    
+                    const stats = {
+                        fieldName,
+                        totalCount: rawData.value.length,
+                        nonEmptyCount: values.length,
+                        emptyCount: rawData.value.length - values.length,
+                        dataType: values.length > 0 ? getDataType(values[0], fieldName) : 'unknown',
+                        uniqueValues: [...new Set(values)].length,
+                        sampleValues: values.slice(0, 5), // 显示前5个样本值
+                        nonEmptyPercentage: 0,
+                        emptyPercentage: 0
+                    };
+                    
+                    // 计算百分比
+                    stats.nonEmptyPercentage = (stats.nonEmptyCount / stats.totalCount) * 100;
+                    stats.emptyPercentage = (stats.emptyCount / stats.totalCount) * 100;
+                    
+                    // 如果是数值类型,计算额外的统计信息
+                    if (stats.dataType === 'number' || stats.dataType === 'date') {
+                        const numericValues = values.map(val => {
+                            if (stats.dataType === 'date') {
+                                // 处理日期类型
+                                return new Date(val).getTime();
+                            }
+                            return parseFloat(val);
+                        }).filter(val => !isNaN(val));
+                        
+                        if (numericValues.length > 0) {
+                            stats.min = Math.min(...numericValues);
+                            stats.max = Math.max(...numericValues);
+                            stats.average = numericValues.reduce((sum, val) => sum + val, 0) / numericValues.length;
+                            
+                            // 如果是日期类型,格式化最小和最大值
+                            if (stats.dataType === 'date') {
+                                stats.min = new Date(stats.min).toLocaleDateString();
+                                stats.max = new Date(stats.max).toLocaleDateString();
+                            }
+                        }
+                    }
+                    
+                    return stats;
+                };
+                
+                // 分析所有字段
+                const analyzeFields = () => {
+                    if (!rawData.value || rawData.value.length === 0) {
+                        fieldStats.value = [];
+                        return;
+                    }
+                    
+                    // 获取所有字段名
+                    headers.value = Object.keys(rawData.value[0]);
+                    
+                    // 分析每个字段
+                    fieldStats.value = headers.value.map(field => analyzeField(field));
+                };
+                
+                // 显示数据预览
+                const showDataPreview = () => {
+                    if (!rawData.value || rawData.value.length === 0) {
+                        dataPreview.value = [];
+                        return;
+                    }
+                    
+                    // 只显示前10行数据
+                    dataPreview.value = rawData.value.slice(0, 10);
+                };
+                
+                return {
+                    fileName,
+                    selectedFile,
+                    fieldStats,
+                    dataPreview,
+                    headers,
+                    totalRows,
+                    isAnalyzing,
+                    handleFileUpload,
+                    analyzeFile
+                };
+            }
+        }).mount('#app');
+    </script>
+</body>
+</html>

BIN
caigou/鐗╂枡浜よ揣璁″垝.xlsx


+ 24 - 0
dingdan/.gitignore

@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 13 - 0
dingdan/index.html

@@ -0,0 +1,13 @@
+<!doctype html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>物料交货计划系统</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.js"></script>
+  </body>
+</html>

+ 13 - 0
dingdan/manage-vue/index.html

@@ -0,0 +1,13 @@
+<!doctype html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>物料采购订单管理系统</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.js"></script>
+  </body>
+</html>

+ 1420 - 0
dingdan/manage-vue/package-lock.json

@@ -0,0 +1,1420 @@
+{
+  "name": "material-purchase-order-system",
+  "version": "0.0.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "material-purchase-order-system",
+      "version": "0.0.0",
+      "dependencies": {
+        "element-plus": "^2.6.1",
+        "vue": "^3.4.21",
+        "vue-router": "^4.3.0"
+      },
+      "devDependencies": {
+        "@vitejs/plugin-vue": "^5.0.4",
+        "vite": "^5.2.0"
+      }
+    },
+    "node_modules/@babel/helper-string-parser": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+      "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-validator-identifier": {
+      "version": "7.28.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+      "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/parser": {
+      "version": "7.28.5",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
+      "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/types": "^7.28.5"
+      },
+      "bin": {
+        "parser": "bin/babel-parser.js"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@babel/types": {
+      "version": "7.28.5",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
+      "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.27.1",
+        "@babel/helper-validator-identifier": "^7.28.5"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@ctrl/tinycolor": {
+      "version": "3.6.1",
+      "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
+      "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@element-plus/icons-vue": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz",
+      "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==",
+      "license": "MIT",
+      "peerDependencies": {
+        "vue": "^3.2.0"
+      }
+    },
+    "node_modules/@esbuild/aix-ppc64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+      "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "aix"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/android-arm": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+      "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/android-arm64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+      "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/android-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+      "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/darwin-arm64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+      "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/darwin-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+      "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/freebsd-arm64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+      "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/freebsd-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+      "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-arm": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+      "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-arm64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+      "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-ia32": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+      "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-loong64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+      "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-mips64el": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+      "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+      "cpu": [
+        "mips64el"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-ppc64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+      "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-riscv64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+      "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-s390x": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+      "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+      "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/netbsd-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+      "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/openbsd-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+      "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/sunos-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+      "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "sunos"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/win32-arm64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+      "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/win32-ia32": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+      "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/win32-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+      "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@floating-ui/core": {
+      "version": "1.7.3",
+      "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz",
+      "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==",
+      "license": "MIT",
+      "dependencies": {
+        "@floating-ui/utils": "^0.2.10"
+      }
+    },
+    "node_modules/@floating-ui/dom": {
+      "version": "1.7.4",
+      "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz",
+      "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==",
+      "license": "MIT",
+      "dependencies": {
+        "@floating-ui/core": "^1.7.3",
+        "@floating-ui/utils": "^0.2.10"
+      }
+    },
+    "node_modules/@floating-ui/utils": {
+      "version": "0.2.10",
+      "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz",
+      "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
+      "license": "MIT"
+    },
+    "node_modules/@jridgewell/sourcemap-codec": {
+      "version": "1.5.5",
+      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+      "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+      "license": "MIT"
+    },
+    "node_modules/@popperjs/core": {
+      "name": "@sxzz/popperjs-es",
+      "version": "2.11.7",
+      "resolved": "https://registry.npmjs.org/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz",
+      "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==",
+      "license": "MIT",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/popperjs"
+      }
+    },
+    "node_modules/@rollup/rollup-android-arm-eabi": {
+      "version": "4.52.5",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz",
+      "integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-android-arm64": {
+      "version": "4.52.5",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz",
+      "integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-arm64": {
+      "version": "4.52.5",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz",
+      "integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-x64": {
+      "version": "4.52.5",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz",
+      "integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-freebsd-arm64": {
+      "version": "4.52.5",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz",
+      "integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-freebsd-x64": {
+      "version": "4.52.5",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz",
+      "integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+      "version": "4.52.5",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz",
+      "integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+      "version": "4.52.5",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz",
+      "integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-gnu": {
+      "version": "4.52.5",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz",
+      "integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-musl": {
+      "version": "4.52.5",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz",
+      "integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-loong64-gnu": {
+      "version": "4.52.5",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz",
+      "integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+      "version": "4.52.5",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz",
+      "integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+      "version": "4.52.5",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz",
+      "integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-riscv64-musl": {
+      "version": "4.52.5",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz",
+      "integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-s390x-gnu": {
+      "version": "4.52.5",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz",
+      "integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-gnu": {
+      "version": "4.52.5",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz",
+      "integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-musl": {
+      "version": "4.52.5",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz",
+      "integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-openharmony-arm64": {
+      "version": "4.52.5",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz",
+      "integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openharmony"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-arm64-msvc": {
+      "version": "4.52.5",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz",
+      "integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-ia32-msvc": {
+      "version": "4.52.5",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz",
+      "integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-x64-gnu": {
+      "version": "4.52.5",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz",
+      "integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-x64-msvc": {
+      "version": "4.52.5",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz",
+      "integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@types/estree": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+      "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/lodash": {
+      "version": "4.17.20",
+      "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz",
+      "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==",
+      "license": "MIT"
+    },
+    "node_modules/@types/lodash-es": {
+      "version": "4.17.12",
+      "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz",
+      "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/lodash": "*"
+      }
+    },
+    "node_modules/@types/web-bluetooth": {
+      "version": "0.0.16",
+      "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
+      "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==",
+      "license": "MIT"
+    },
+    "node_modules/@vitejs/plugin-vue": {
+      "version": "5.2.4",
+      "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
+      "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": "^18.0.0 || >=20.0.0"
+      },
+      "peerDependencies": {
+        "vite": "^5.0.0 || ^6.0.0",
+        "vue": "^3.2.25"
+      }
+    },
+    "node_modules/@vue/compiler-core": {
+      "version": "3.5.22",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.22.tgz",
+      "integrity": "sha512-jQ0pFPmZwTEiRNSb+i9Ow/I/cHv2tXYqsnHKKyCQ08irI2kdF5qmYedmF8si8mA7zepUFmJ2hqzS8CQmNOWOkQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/parser": "^7.28.4",
+        "@vue/shared": "3.5.22",
+        "entities": "^4.5.0",
+        "estree-walker": "^2.0.2",
+        "source-map-js": "^1.2.1"
+      }
+    },
+    "node_modules/@vue/compiler-dom": {
+      "version": "3.5.22",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.22.tgz",
+      "integrity": "sha512-W8RknzUM1BLkypvdz10OVsGxnMAuSIZs9Wdx1vzA3mL5fNMN15rhrSCLiTm6blWeACwUwizzPVqGJgOGBEN/hA==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/compiler-core": "3.5.22",
+        "@vue/shared": "3.5.22"
+      }
+    },
+    "node_modules/@vue/compiler-sfc": {
+      "version": "3.5.22",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.22.tgz",
+      "integrity": "sha512-tbTR1zKGce4Lj+JLzFXDq36K4vcSZbJ1RBu8FxcDv1IGRz//Dh2EBqksyGVypz3kXpshIfWKGOCcqpSbyGWRJQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/parser": "^7.28.4",
+        "@vue/compiler-core": "3.5.22",
+        "@vue/compiler-dom": "3.5.22",
+        "@vue/compiler-ssr": "3.5.22",
+        "@vue/shared": "3.5.22",
+        "estree-walker": "^2.0.2",
+        "magic-string": "^0.30.19",
+        "postcss": "^8.5.6",
+        "source-map-js": "^1.2.1"
+      }
+    },
+    "node_modules/@vue/compiler-ssr": {
+      "version": "3.5.22",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.22.tgz",
+      "integrity": "sha512-GdgyLvg4R+7T8Nk2Mlighx7XGxq/fJf9jaVofc3IL0EPesTE86cP/8DD1lT3h1JeZr2ySBvyqKQJgbS54IX1Ww==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/compiler-dom": "3.5.22",
+        "@vue/shared": "3.5.22"
+      }
+    },
+    "node_modules/@vue/devtools-api": {
+      "version": "6.6.4",
+      "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
+      "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
+      "license": "MIT"
+    },
+    "node_modules/@vue/reactivity": {
+      "version": "3.5.22",
+      "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.22.tgz",
+      "integrity": "sha512-f2Wux4v/Z2pqc9+4SmgZC1p73Z53fyD90NFWXiX9AKVnVBEvLFOWCEgJD3GdGnlxPZt01PSlfmLqbLYzY/Fw4A==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/shared": "3.5.22"
+      }
+    },
+    "node_modules/@vue/runtime-core": {
+      "version": "3.5.22",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.22.tgz",
+      "integrity": "sha512-EHo4W/eiYeAzRTN5PCextDUZ0dMs9I8mQ2Fy+OkzvRPUYQEyK9yAjbasrMCXbLNhF7P0OUyivLjIy0yc6VrLJQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/reactivity": "3.5.22",
+        "@vue/shared": "3.5.22"
+      }
+    },
+    "node_modules/@vue/runtime-dom": {
+      "version": "3.5.22",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.22.tgz",
+      "integrity": "sha512-Av60jsryAkI023PlN7LsqrfPvwfxOd2yAwtReCjeuugTJTkgrksYJJstg1e12qle0NarkfhfFu1ox2D+cQotww==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/reactivity": "3.5.22",
+        "@vue/runtime-core": "3.5.22",
+        "@vue/shared": "3.5.22",
+        "csstype": "^3.1.3"
+      }
+    },
+    "node_modules/@vue/server-renderer": {
+      "version": "3.5.22",
+      "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.22.tgz",
+      "integrity": "sha512-gXjo+ao0oHYTSswF+a3KRHZ1WszxIqO7u6XwNHqcqb9JfyIL/pbWrrh/xLv7jeDqla9u+LK7yfZKHih1e1RKAQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/compiler-ssr": "3.5.22",
+        "@vue/shared": "3.5.22"
+      },
+      "peerDependencies": {
+        "vue": "3.5.22"
+      }
+    },
+    "node_modules/@vue/shared": {
+      "version": "3.5.22",
+      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.22.tgz",
+      "integrity": "sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w==",
+      "license": "MIT"
+    },
+    "node_modules/@vueuse/core": {
+      "version": "9.13.0",
+      "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-9.13.0.tgz",
+      "integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/web-bluetooth": "^0.0.16",
+        "@vueuse/metadata": "9.13.0",
+        "@vueuse/shared": "9.13.0",
+        "vue-demi": "*"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@vueuse/core/node_modules/vue-demi": {
+      "version": "0.14.10",
+      "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
+      "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "bin": {
+        "vue-demi-fix": "bin/vue-demi-fix.js",
+        "vue-demi-switch": "bin/vue-demi-switch.js"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.0.0-rc.1",
+        "vue": "^3.0.0-0 || ^2.6.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@vueuse/metadata": {
+      "version": "9.13.0",
+      "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-9.13.0.tgz",
+      "integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@vueuse/shared": {
+      "version": "9.13.0",
+      "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-9.13.0.tgz",
+      "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==",
+      "license": "MIT",
+      "dependencies": {
+        "vue-demi": "*"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@vueuse/shared/node_modules/vue-demi": {
+      "version": "0.14.10",
+      "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
+      "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "bin": {
+        "vue-demi-fix": "bin/vue-demi-fix.js",
+        "vue-demi-switch": "bin/vue-demi-switch.js"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.0.0-rc.1",
+        "vue": "^3.0.0-0 || ^2.6.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/async-validator": {
+      "version": "4.2.5",
+      "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz",
+      "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==",
+      "license": "MIT"
+    },
+    "node_modules/csstype": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+      "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+      "license": "MIT"
+    },
+    "node_modules/dayjs": {
+      "version": "1.11.19",
+      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz",
+      "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==",
+      "license": "MIT"
+    },
+    "node_modules/element-plus": {
+      "version": "2.11.7",
+      "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.11.7.tgz",
+      "integrity": "sha512-Bh47wuzsqaNBNDkbtlOlZER1cGcOB8GsXp/+C9b95MOrk0wvoHUV4NKKK7xMkfYNFYdYysQ752oMhnExgAL6+g==",
+      "license": "MIT",
+      "dependencies": {
+        "@ctrl/tinycolor": "^3.4.1",
+        "@element-plus/icons-vue": "^2.3.2",
+        "@floating-ui/dom": "^1.0.1",
+        "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7",
+        "@types/lodash": "^4.17.20",
+        "@types/lodash-es": "^4.17.12",
+        "@vueuse/core": "^9.1.0",
+        "async-validator": "^4.2.5",
+        "dayjs": "^1.11.18",
+        "lodash": "^4.17.21",
+        "lodash-es": "^4.17.21",
+        "lodash-unified": "^1.0.3",
+        "memoize-one": "^6.0.0",
+        "normalize-wheel-es": "^1.2.0"
+      },
+      "peerDependencies": {
+        "vue": "^3.2.0"
+      }
+    },
+    "node_modules/entities": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+      "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+      "license": "BSD-2-Clause",
+      "engines": {
+        "node": ">=0.12"
+      },
+      "funding": {
+        "url": "https://github.com/fb55/entities?sponsor=1"
+      }
+    },
+    "node_modules/esbuild": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+      "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "bin": {
+        "esbuild": "bin/esbuild"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "optionalDependencies": {
+        "@esbuild/aix-ppc64": "0.21.5",
+        "@esbuild/android-arm": "0.21.5",
+        "@esbuild/android-arm64": "0.21.5",
+        "@esbuild/android-x64": "0.21.5",
+        "@esbuild/darwin-arm64": "0.21.5",
+        "@esbuild/darwin-x64": "0.21.5",
+        "@esbuild/freebsd-arm64": "0.21.5",
+        "@esbuild/freebsd-x64": "0.21.5",
+        "@esbuild/linux-arm": "0.21.5",
+        "@esbuild/linux-arm64": "0.21.5",
+        "@esbuild/linux-ia32": "0.21.5",
+        "@esbuild/linux-loong64": "0.21.5",
+        "@esbuild/linux-mips64el": "0.21.5",
+        "@esbuild/linux-ppc64": "0.21.5",
+        "@esbuild/linux-riscv64": "0.21.5",
+        "@esbuild/linux-s390x": "0.21.5",
+        "@esbuild/linux-x64": "0.21.5",
+        "@esbuild/netbsd-x64": "0.21.5",
+        "@esbuild/openbsd-x64": "0.21.5",
+        "@esbuild/sunos-x64": "0.21.5",
+        "@esbuild/win32-arm64": "0.21.5",
+        "@esbuild/win32-ia32": "0.21.5",
+        "@esbuild/win32-x64": "0.21.5"
+      }
+    },
+    "node_modules/estree-walker": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+      "license": "MIT"
+    },
+    "node_modules/fsevents": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "node_modules/lodash": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+      "license": "MIT"
+    },
+    "node_modules/lodash-es": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
+      "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
+      "license": "MIT"
+    },
+    "node_modules/lodash-unified": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/lodash-unified/-/lodash-unified-1.0.3.tgz",
+      "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/lodash-es": "*",
+        "lodash": "*",
+        "lodash-es": "*"
+      }
+    },
+    "node_modules/magic-string": {
+      "version": "0.30.21",
+      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+      "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/sourcemap-codec": "^1.5.5"
+      }
+    },
+    "node_modules/memoize-one": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
+      "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
+      "license": "MIT"
+    },
+    "node_modules/nanoid": {
+      "version": "3.3.11",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+      "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "bin": {
+        "nanoid": "bin/nanoid.cjs"
+      },
+      "engines": {
+        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+      }
+    },
+    "node_modules/normalize-wheel-es": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
+      "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+      "license": "ISC"
+    },
+    "node_modules/postcss": {
+      "version": "8.5.6",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+      "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/postcss"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "nanoid": "^3.3.11",
+        "picocolors": "^1.1.1",
+        "source-map-js": "^1.2.1"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      }
+    },
+    "node_modules/rollup": {
+      "version": "4.52.5",
+      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz",
+      "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/estree": "1.0.8"
+      },
+      "bin": {
+        "rollup": "dist/bin/rollup"
+      },
+      "engines": {
+        "node": ">=18.0.0",
+        "npm": ">=8.0.0"
+      },
+      "optionalDependencies": {
+        "@rollup/rollup-android-arm-eabi": "4.52.5",
+        "@rollup/rollup-android-arm64": "4.52.5",
+        "@rollup/rollup-darwin-arm64": "4.52.5",
+        "@rollup/rollup-darwin-x64": "4.52.5",
+        "@rollup/rollup-freebsd-arm64": "4.52.5",
+        "@rollup/rollup-freebsd-x64": "4.52.5",
+        "@rollup/rollup-linux-arm-gnueabihf": "4.52.5",
+        "@rollup/rollup-linux-arm-musleabihf": "4.52.5",
+        "@rollup/rollup-linux-arm64-gnu": "4.52.5",
+        "@rollup/rollup-linux-arm64-musl": "4.52.5",
+        "@rollup/rollup-linux-loong64-gnu": "4.52.5",
+        "@rollup/rollup-linux-ppc64-gnu": "4.52.5",
+        "@rollup/rollup-linux-riscv64-gnu": "4.52.5",
+        "@rollup/rollup-linux-riscv64-musl": "4.52.5",
+        "@rollup/rollup-linux-s390x-gnu": "4.52.5",
+        "@rollup/rollup-linux-x64-gnu": "4.52.5",
+        "@rollup/rollup-linux-x64-musl": "4.52.5",
+        "@rollup/rollup-openharmony-arm64": "4.52.5",
+        "@rollup/rollup-win32-arm64-msvc": "4.52.5",
+        "@rollup/rollup-win32-ia32-msvc": "4.52.5",
+        "@rollup/rollup-win32-x64-gnu": "4.52.5",
+        "@rollup/rollup-win32-x64-msvc": "4.52.5",
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/source-map-js": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+      "license": "BSD-3-Clause",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/vite": {
+      "version": "5.4.21",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+      "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "esbuild": "^0.21.3",
+        "postcss": "^8.4.43",
+        "rollup": "^4.20.0"
+      },
+      "bin": {
+        "vite": "bin/vite.js"
+      },
+      "engines": {
+        "node": "^18.0.0 || >=20.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/vitejs/vite?sponsor=1"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.3"
+      },
+      "peerDependencies": {
+        "@types/node": "^18.0.0 || >=20.0.0",
+        "less": "*",
+        "lightningcss": "^1.21.0",
+        "sass": "*",
+        "sass-embedded": "*",
+        "stylus": "*",
+        "sugarss": "*",
+        "terser": "^5.4.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/node": {
+          "optional": true
+        },
+        "less": {
+          "optional": true
+        },
+        "lightningcss": {
+          "optional": true
+        },
+        "sass": {
+          "optional": true
+        },
+        "sass-embedded": {
+          "optional": true
+        },
+        "stylus": {
+          "optional": true
+        },
+        "sugarss": {
+          "optional": true
+        },
+        "terser": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vue": {
+      "version": "3.5.22",
+      "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.22.tgz",
+      "integrity": "sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/compiler-dom": "3.5.22",
+        "@vue/compiler-sfc": "3.5.22",
+        "@vue/runtime-dom": "3.5.22",
+        "@vue/server-renderer": "3.5.22",
+        "@vue/shared": "3.5.22"
+      },
+      "peerDependencies": {
+        "typescript": "*"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vue-router": {
+      "version": "4.6.3",
+      "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.3.tgz",
+      "integrity": "sha512-ARBedLm9YlbvQomnmq91Os7ck6efydTSpRP3nuOKCvgJOHNrhRoJDSKtee8kcL1Vf7nz6U+PMBL+hTvR3bTVQg==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/devtools-api": "^6.6.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/posva"
+      },
+      "peerDependencies": {
+        "vue": "^3.5.0"
+      }
+    }
+  }
+}

+ 20 - 0
dingdan/manage-vue/package.json

@@ -0,0 +1,20 @@
+{
+  "name": "material-purchase-order-system",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "vue": "^3.4.21",
+    "vue-router": "^4.3.0",
+    "element-plus": "^2.6.1"
+  },
+  "devDependencies": {
+    "@vitejs/plugin-vue": "^5.0.4",
+    "vite": "^5.2.0"
+  }
+}

+ 226 - 0
dingdan/manage-vue/src/App.vue

@@ -0,0 +1,226 @@
+<template>
+  <el-container class="app-container">
+    <!-- 顶部导航 -->
+    <el-header height="60px">
+      <div class="header-content">
+        <div class="logo">
+          <span>物料采购订单管理系统</span>
+        </div>
+        <el-menu
+          :default-active="activeTopMenu"
+          class="top-menu"
+          mode="horizontal"
+          @select="handleTopMenuSelect"
+        >
+          <el-menu-item index="my-process">我的流程</el-menu-item>
+          <el-menu-item index="my-favorites" v-if="showFavorites">我的关注/常用</el-menu-item>
+          <el-menu-item index="help">帮助</el-menu-item>
+        </el-menu>
+      </div>
+    </el-header>
+    
+    <el-container>
+      <!-- 左侧菜单 -->
+      <el-aside width="240px" height="calc(100vh - 120px)">
+        <el-menu
+          :default-active="activeSideMenu"
+          class="side-menu"
+          @select="handleSideMenuSelect"
+          :collapse-transition="false"
+        >
+          <el-sub-menu index="purchase-order">
+            <template #title>
+              <el-icon><Document /></el-icon>
+              <span>S4采购执行</span>
+            </template>
+            <el-sub-menu index="order-management">
+              <template #title>
+                <span>订单管理</span>
+              </template>
+              <el-menu-item index="material-purchase-order">
+                <span>物料采购订单</span>
+              </el-menu-item>
+              <el-menu-item index="subcontract-order">
+                <span>委外加工订单</span>
+              </el-menu-item>
+              <el-menu-item index="material-purchase-request">
+                <span>物料采购申请</span>
+              </el-menu-item>
+            </el-sub-menu>
+          </el-sub-menu>
+        </el-menu>
+      </el-aside>
+      
+      <!-- 主内容区 -->
+      <el-main>
+        <router-view v-slot="{ Component }">
+          <transition name="fade" mode="out-in">
+            <keep-alive :include="['MaterialPurchaseOrder', 'SubcontractOrder', 'MaterialPurchaseRequest']">
+              <component :is="Component" />
+            </keep-alive>
+          </transition>
+        </router-view>
+      </el-main>
+    </el-container>
+    
+    <!-- 页脚 -->
+    <el-footer height="60px">
+      <div class="footer-content">
+        <span>© 2024 物料采购订单管理系统 - 版权所有</span>
+      </div>
+    </el-footer>
+  </el-container>
+</template>
+
+<script>
+import { Document } from '@element-plus/icons-vue'
+
+export default {
+  name: 'App',
+  components: {
+    Document
+  },
+  data() {
+    return {
+      activeTopMenu: 'my-process',
+      activeSideMenu: 'material-purchase-order',
+      showFavorites: true,
+      userRole: 'planner' // 可以从登录信息中获取,这里默认为计划员
+    }
+  },
+  mounted() {
+    // 初始化时根据路由设置活动菜单项
+    this.updateActiveMenus()
+  },
+  watch: {
+    $route: {
+      handler() {
+        this.updateActiveMenus()
+      },
+      immediate: true
+    }
+  },
+  methods: {
+    handleTopMenuSelect(key) {
+      this.activeTopMenu = key
+      switch (key) {
+        case 'my-process':
+          this.$router.push('/my-process')
+          break
+        case 'my-favorites':
+          // 跳转到我的关注页面
+          break
+        case 'help':
+          this.$router.push('/help')
+          break
+      }
+    },
+    handleSideMenuSelect(key) {
+      this.activeSideMenu = key
+      switch (key) {
+        case 'material-purchase-order':
+          this.$router.push('/purchase-order/order-management/material-purchase-order')
+          break
+        case 'subcontract-order':
+          this.$router.push('/purchase-order/order-management/subcontract-order')
+          break
+        case 'material-purchase-request':
+          this.$router.push('/purchase-order/order-management/material-purchase-request')
+          break
+      }
+    },
+    updateActiveMenus() {
+      const path = this.$route.path
+      if (path.includes('/purchase-order')) {
+        this.activeTopMenu = 'my-process'
+        // 根据实际路径设置活动菜单项
+        if (path.includes('subcontract-order')) {
+          this.activeSideMenu = 'subcontract-order'
+        } else if (path.includes('material-purchase-request')) {
+          this.activeSideMenu = 'material-purchase-request'
+        } else if (path.includes('material-purchase-order')) {
+          this.activeSideMenu = 'material-purchase-order'
+        }
+      } else if (path === '/help') {
+        this.activeTopMenu = 'help'
+      } else if (path === '/my-process') {
+        this.activeTopMenu = 'my-process'
+      }
+    }
+  }
+}
+</script>
+
+<style>
+.app-container {
+  height: 100vh;
+}
+
+.header-content {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  height: 100%;
+}
+
+.logo {
+  font-size: 18px;
+  font-weight: bold;
+}
+
+.top-menu {
+  background-color: transparent;
+  border-bottom: none;
+}
+
+.top-menu .el-menu-item {
+  color: white;
+  min-width: 100px;
+  font-size: 14px;
+}
+
+.top-menu .el-menu-item.is-active {
+  background-color: rgba(255, 255, 255, 0.2);
+  color: white;
+}
+
+.top-menu .el-menu-item:hover {
+  background-color: rgba(255, 255, 255, 0.1);
+  color: white;
+}
+
+.side-menu {
+  height: 100%;
+  overflow-y: auto;
+}
+
+.side-menu .el-menu-item {
+  font-size: 14px;
+  padding-left: 45px !important;
+}
+
+.side-menu .el-sub-menu__title {
+  font-size: 14px;
+  padding-left: 25px !important;
+}
+
+.side-menu .el-sub-menu__title i {
+  margin-right: 10px;
+}
+
+.footer-content {
+  text-align: center;
+  font-size: 12px;
+}
+
+/* 过渡动画 */
+.fade-enter-active,
+.fade-leave-active {
+  transition: opacity 0.3s ease;
+}
+
+.fade-enter-from,
+.fade-leave-to {
+  opacity: 0;
+}
+</style>

+ 12 - 0
dingdan/manage-vue/src/main.js

@@ -0,0 +1,12 @@
+import { createApp } from 'vue'
+import App from './App.vue'
+import router from './router'
+import ElementPlus from 'element-plus'
+import 'element-plus/dist/index.css'
+import './style.css'
+
+const app = createApp(App)
+
+app.use(router)
+app.use(ElementPlus)
+app.mount('#app')

+ 64 - 0
dingdan/manage-vue/src/router/index.js

@@ -0,0 +1,64 @@
+import { createRouter, createWebHistory } from 'vue-router'
+
+const routes = [
+  {
+    path: '/',
+    redirect: '/purchase-order/order-management/material-purchase-request'
+  },
+  {
+    path: '/my-process',
+    name: 'MyProcess',
+    component: () => import('../views/MyProcess.vue')
+  },
+  {
+    path: '/purchase-order',
+    name: 'PurchaseOrder',
+    meta: { title: 'S4采购执行' },
+    children: [
+      {
+        path: 'order-management',
+        name: 'OrderManagement',
+        meta: { title: '订单管理' },
+        children: [
+          {
+            path: 'material-purchase-order',
+            name: 'MaterialPurchaseOrder',
+            meta: { title: '物料采购订单' },
+            component: () => import('../views/purchase-order/MaterialPurchaseOrder.vue')
+          },
+          {
+            path: 'subcontract-order',
+            name: 'SubcontractOrder',
+            meta: { title: '委外加工订单' },
+            component: () => import('../views/purchase-order/SubcontractOrder.vue')
+          },
+          {
+            path: 'material-purchase-request',
+            name: 'MaterialPurchaseRequest',
+            meta: { title: '物料采购申请' },
+            component: () => import('../views/purchase-order/MaterialPurchaseRequest.vue')
+          }
+        ]
+      }
+    ]
+  },
+  {
+    path: '/help',
+    name: 'Help',
+    component: () => import('../views/Help.vue')
+  }
+]
+
+const router = createRouter({
+  history: createWebHistory(),
+  routes
+})
+
+// 全局前置守卫,设置页面标题
+router.beforeEach((to, from, next) => {
+  const title = to.meta.title ? `${to.meta.title} - 物料采购订单管理系统` : '物料采购订单管理系统'
+  document.title = title
+  next()
+})
+
+export default router

+ 135 - 0
dingdan/manage-vue/src/style.css

@@ -0,0 +1,135 @@
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+}
+
+body {
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+    'Helvetica', 'Arial', sans-serif;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  background-color: #f5f7fa;
+}
+
+#app {
+  width: 100%;
+  height: 100vh;
+  overflow: hidden;
+}
+
+.el-header {
+  background-color: #545c64;
+  color: white;
+  line-height: 60px;
+  padding: 0 20px;
+}
+
+.el-footer {
+  background-color: #f0f2f5;
+  color: #606266;
+  text-align: center;
+  line-height: 60px;
+}
+
+.el-menu {
+  border-right: none;
+}
+
+.el-sub-menu__title:hover {
+  background-color: #ecf5ff;
+}
+
+.el-menu-item {
+  padding: 0 20px;
+}
+
+.el-menu-item:hover {
+  background-color: #ecf5ff;
+  color: #409eff;
+}
+
+.el-menu-item.is-active {
+  background-color: #ecf5ff;
+  color: #409eff;
+}
+
+.main-container {
+  padding: 20px;
+  overflow: auto;
+  height: 100%;
+}
+
+.filter-container {
+  background: white;
+  padding: 20px;
+  margin-bottom: 20px;
+  border-radius: 4px;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
+}
+
+.table-container {
+  background: white;
+  padding: 20px;
+  border-radius: 4px;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
+}
+
+.empty-state {
+  text-align: center;
+  padding: 60px 0;
+  color: #909399;
+}
+
+.empty-state img {
+  width: 200px;
+  margin-bottom: 20px;
+}
+
+.error-state {
+  text-align: center;
+  padding: 60px 0;
+  color: #f56c6c;
+}
+
+/* 骨架屏样式 */
+.skeleton {n  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);n  background-size: 200% 100%;n  animation: skeleton-loading 1.5s infinite;n}
+
+@keyframes skeleton-loading {
+  0% {n    background-position: 200% 0;n  }
+  100% {n    background-position: -200% 0;n  }
+}n
+/* 响应式调整 */
+@media (max-width: 768px) {
+  .el-layout {
+    flex-direction: column;
+  }
+  
+  .el-aside {
+    width: 100% !important;
+    height: auto;
+  }
+  
+  .el-main {
+    padding: 10px;
+  }
+}n
+/* 滚动条样式优化 */
+::-webkit-scrollbar {
+  width: 6px;
+  height: 6px;
+}
+
+::-webkit-scrollbar-track {
+  background: #f1f1f1;
+}
+
+::-webkit-scrollbar-thumb {
+  background: #c0c4cc;
+  border-radius: 3px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+  background: #909399;
+}

+ 111 - 0
dingdan/manage-vue/src/views/Help.vue

@@ -0,0 +1,111 @@
+<template>
+  <div class="help">
+    <h2 class="page-title">帮助中心</h2>
+    <div class="content">
+      <h3>物料采购订单系统使用指南</h3>
+      <div class="help-section">
+        <h4>1. 系统概述</h4>
+        <p>本系统用于管理物料采购订单,支持计划员和供应商查看和操作采购订单信息。</p>
+      </div>
+      <div class="help-section">
+        <h4>2. 功能说明</h4>
+        <ul>
+          <li><strong>订单查询:</strong>可通过采购单号、物料编码、供应商等条件搜索订单</li>
+          <li><strong>高级筛选:</strong>提供多维度的筛选条件,帮助精准定位数据</li>
+          <li><strong>数据导出:</strong>支持将当前查询结果导出为文件</li>
+          <li><strong>列显示控制:</strong>可自定义表格显示的列</li>
+          <li><strong>列宽拖拽:</strong>支持拖动调整列宽</li>
+        </ul>
+      </div>
+      <div class="help-section">
+        <h4>3. 关键词搜索规则</h4>
+        <ul>
+          <li>支持<strong>全部包含(AND)</strong>和<strong>任一包含(OR)</strong>两种模式</li>
+          <li>多关键词以<strong>空格</strong>分隔</li>
+          <li>精确短语用<strong>双引号</strong>包裹</li>
+        </ul>
+      </div>
+      <div class="help-section">
+        <h4>4. 常见问题</h4>
+        <div class="faq-item">
+          <p class="faq-question">Q: 如何重置筛选条件?</p>
+          <p class="faq-answer">A: 点击查询区域的"重置"按钮,可清空所有筛选条件。</p>
+        </div>
+        <div class="faq-item">
+          <p class="faq-question">Q: 如何自定义表格显示?</p>
+          <p class="faq-answer">A: 点击"列显示控制",可勾选或取消勾选需要显示的列。</p>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'Help'
+}
+</script>
+
+<style scoped>
+.help {
+  background: white;
+  padding: 20px;
+  border-radius: 4px;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
+}
+
+.page-title {
+  font-size: 20px;
+  font-weight: 500;
+  margin-bottom: 20px;
+  color: #303133;
+}
+
+.content {
+  max-width: 800px;
+  margin: 0 auto;
+}
+
+h3 {
+  color: #303133;
+  margin-bottom: 20px;
+  font-size: 18px;
+}
+
+.help-section {
+  margin-bottom: 30px;
+  padding: 20px;
+  background-color: #f8f9fa;
+  border-radius: 4px;
+}
+
+h4 {
+  color: #409eff;
+  margin-bottom: 15px;
+  font-size: 16px;
+}
+
+ul {
+  padding-left: 20px;
+}
+
+li {
+  margin-bottom: 10px;
+  line-height: 1.6;
+}
+
+.faq-item {
+  margin-bottom: 15px;
+}
+
+.faq-question {
+  font-weight: 500;
+  color: #303133;
+  margin-bottom: 5px;
+}
+
+.faq-answer {
+  color: #606266;
+  line-height: 1.6;
+}
+</style>

+ 36 - 0
dingdan/manage-vue/src/views/MyProcess.vue

@@ -0,0 +1,36 @@
+<template>
+  <div class="my-process">
+    <h2 class="page-title">我的流程</h2>
+    <div class="content">
+      <p>此页面展示用户的流程信息</p>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'MyProcess'
+}
+</script>
+
+<style scoped>
+.my-process {
+  background: white;
+  padding: 20px;
+  border-radius: 4px;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
+}
+
+.page-title {
+  font-size: 20px;
+  font-weight: 500;
+  margin-bottom: 20px;
+  color: #303133;
+}
+
+.content {
+  text-align: center;
+  padding: 60px 0;
+  color: #909399;
+}
+</style>

+ 693 - 0
dingdan/manage-vue/src/views/purchase-order/MaterialPurchaseOrder.vue

@@ -0,0 +1,693 @@
+<template>
+  <div class="material-purchase-order">
+    <h2 class="page-title">物料采购订单</h2>
+    
+    <!-- 查询条件区域 -->
+    <div class="filter-container">
+      <el-form :inline="true" :model="searchForm" label-width="80px">
+        <el-form-item label="采购单号">
+          <el-input
+            v-model="searchForm.ponumber"
+            placeholder="请输入采购单号"
+            clearable
+          />
+        </el-form-item>
+        <el-form-item label="物料编码">
+          <el-input
+            v-model="searchForm.itemnum"
+            placeholder="请输入物料编码"
+            clearable
+          />
+        </el-form-item>
+        <el-form-item label="供应商">
+          <el-input
+            v-model="searchForm.supplier"
+            placeholder="请输入供应商名称"
+            clearable
+          />
+        </el-form-item>
+        <el-form-item label="状态">
+          <el-select
+            v-model="searchForm.status"
+            placeholder="请选择状态"
+            clearable
+          >
+            <el-option label="初始(N)" value="N" />
+            <el-option label="已发布(P)" value="P" />
+            <el-option label="关闭(C)" value="C" />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleSearch">查询</el-button>
+          <el-button @click="handleReset">重置</el-button>
+          <el-button type="success" @click="handleAdvancedFilter">
+            高级筛选
+          </el-button>
+        </el-form-item>
+      </el-form>
+      
+      <!-- 关键词搜索模式切换 -->
+      <div class="keyword-mode" v-if="showKeywordMode">
+        <el-radio-group v-model="keywordMode" size="small">
+          <el-radio-button label="and">全部包含(AND)</el-radio-button>
+          <el-radio-button label="or">任一包含(OR)</el-radio-button>
+        </el-radio-group>
+        
+      </div>
+    </div>
+    
+    <!-- 表格操作按钮 -->
+    <div class="table-actions">
+      <el-button type="primary" @click="handleExport" :icon="Download">
+        导出
+      </el-button>
+    </div>
+    
+    <!-- 表格区域 -->
+    <div class="table-container">
+      <el-table
+        v-loading="loading"
+        :data="tableData"
+        style="width: 100%"
+        border
+        :row-key="row => `${row.ponumber}-${row.poline}`"
+        :default-sort="{ prop: 'OrdDate', order: 'descending' }"
+        @selection-change="handleSelectionChange"
+        @header-dragend="handleHeaderDragend"
+      >
+        <!-- 复选框列 -->
+        <el-table-column type="selection" width="55" fixed="left" />
+        
+        <!-- 采购单号(左固定) -->
+        <el-table-column
+          prop="ponumber"
+          label="采购单号"
+          min-width="150"
+          fixed="left"
+          :show-overflow-tooltip="true"
+        />
+        
+        <!-- 采购单行号 -->
+        <el-table-column
+          prop="poline"
+          label="采购单行号"
+          min-width="100"
+          :show-overflow-tooltip="true"
+        />
+        
+        <!-- 物料编码 -->
+        <el-table-column
+          prop="itemnum"
+          label="物料编码"
+          min-width="120"
+          :show-overflow-tooltip="true"
+        />
+        
+        <!-- 物料描述 -->
+        <el-table-column
+          prop="Descr"
+          label="物料描述"
+          min-width="200"
+          :show-overflow-tooltip="true"
+        />
+        
+        <!-- 供应商代码 -->
+        <el-table-column
+          prop="suppliercode"
+          label="供应商代码"
+          min-width="100"
+          :show-overflow-tooltip="true"
+        />
+        
+        <!-- 供应商名称 -->
+        <el-table-column
+          prop="supplier"
+          label="供应商名称"
+          min-width="150"
+          :show-overflow-tooltip="true"
+        />
+        
+        <!-- 采购组 -->
+        <el-table-column
+          prop="purgroup"
+          label="采购组"
+          min-width="80"
+          :show-overflow-tooltip="true"
+        />
+        
+        <!-- 凭证日期 -->
+        <el-table-column
+          prop="OrdDate"
+          label="凭证日期"
+          min-width="120"
+          :formatter="formatDate"
+          sortable
+        />
+        
+        <!-- 订单类型 -->
+        <el-table-column
+          prop="po_order_type"
+          label="订单类型"
+          min-width="100"
+          :show-overflow-tooltip="true"
+        />
+        
+        <!-- 采购订单数量 -->
+        <el-table-column
+          prop="poqty"
+          label="采购订单数量"
+          min-width="120"
+          :formatter="formatNumber"
+        />
+        
+        <!-- 已交数量 -->
+        <el-table-column
+          prop="sentqty"
+          label="已交数量"
+          min-width="100"
+          :formatter="formatNumber"
+        />
+        
+        <!-- 待交数量 -->
+        <el-table-column
+          prop="restqty"
+          label="待交数量"
+          min-width="100"
+          :formatter="formatNumber"
+        />
+        
+        <!-- 发布状态 -->
+        <el-table-column
+          prop="status"
+          label="发布"
+          min-width="80"
+          :show-overflow-tooltip="true"
+          :formatter="formatStatus"
+          fixed="right"
+        />
+      </el-table>
+      
+      <!-- 分页 -->
+      <div class="pagination">
+        <el-pagination
+          v-model:current-page="pagination.currentPage"
+          v-model:page-size="pagination.pageSize"
+          :page-sizes="[20, 50, 100]"
+          layout="total, sizes, prev, pager, next, jumper"
+          :total="pagination.total"
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange"
+        />
+      </div>
+    </div>
+    
+    <!-- 列显示控制 -->
+    <el-dropdown class="column-control" @command="handleColumnCommand">
+      <span class="el-dropdown-link">
+          列显示控制
+        </span>
+      <template #dropdown>
+        <el-dropdown-menu>
+          <el-dropdown-item command="reset">重置列</el-dropdown-item>
+          <el-dropdown-item divided />
+          <el-dropdown-item v-for="column in columns" :key="column.prop" :command="column.prop">
+            <el-checkbox v-model="column.visible" @change="() => updateTableColumns">
+              {{ column.label }}
+            </el-checkbox>
+          </el-dropdown-item>
+        </el-dropdown-menu>
+      </template>
+    </el-dropdown>
+    
+    <!-- 高级筛选抽屉 -->
+    <el-drawer
+      v-model="advancedFilterVisible"
+      title="高级筛选"
+      size="30%"
+      direction="rtl"
+    >
+      <el-form :model="advancedForm" label-width="100px">
+        <el-form-item label="供应商代码">
+          <el-input v-model="advancedForm.suppliercode" placeholder="请输入供应商代码" clearable />
+        </el-form-item>
+        <el-form-item label="采购组">
+          <el-input v-model="advancedForm.purgroup" placeholder="请输入采购组" clearable />
+        </el-form-item>
+        <el-form-item label="凭证日期范围">
+          <el-date-picker
+            v-model="advancedForm.dateRange"
+            type="daterange"
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            value-format="YYYY-MM-DD"
+          />
+        </el-form-item>
+        <el-form-item label="订单类型">
+          <el-input v-model="advancedForm.po_order_type" placeholder="请输入订单类型" clearable />
+        </el-form-item>
+      </el-form>
+      <div class="drawer-footer">
+        <el-button @click="resetAdvancedFilter">重置</el-button>
+        <el-button type="primary" @click="applyAdvancedFilter">确认</el-button>
+      </div>
+    </el-drawer>
+    
+    <!-- 空状态 -->
+    <div v-if="!loading && tableData.length === 0" class="empty-state">
+      <el-empty description="暂无数据" :image="Empty">
+        <template #description>
+          <span>暂无符合条件的数据</span>
+        </template>
+        <el-button type="primary" @click="handleReset">清空筛选</el-button>
+      </el-empty>
+    </div>
+  </div>
+</template>
+
+<script>
+import { Download } from '@element-plus/icons-vue'
+
+export default {
+  name: 'MaterialPurchaseOrder',
+  components: { Download },
+  data() {
+    return {
+      // 搜索表单
+      searchForm: {
+        ponumber: '',
+        itemnum: '',
+        supplier: '',
+        status: ''
+      },
+      // 高级筛选表单
+      advancedForm: {
+        suppliercode: '',
+        purgroup: '',
+        dateRange: [],
+        po_order_type: ''
+      },
+      // 关键词搜索模式
+      keywordMode: 'and',
+      showKeywordMode: true,
+      // 表格数据
+      tableData: [],
+      // 加载状态
+      loading: false,
+      // 分页信息
+      pagination: {
+        currentPage: 1,
+        pageSize: 20,
+        total: 0
+      },
+      // 选中的行
+      selectedRows: [],
+      // 列配置
+      columns: [
+        { prop: 'ponumber', label: '采购单号', visible: true, fixed: 'left' },
+        { prop: 'poline', label: '采购单行号', visible: true },
+        { prop: 'itemnum', label: '物料编码', visible: true },
+        { prop: 'Descr', label: '物料描述', visible: true },
+        { prop: 'suppliercode', label: '供应商代码', visible: true },
+        { prop: 'supplier', label: '供应商名称', visible: true },
+        { prop: 'purgroup', label: '采购组', visible: true },
+        { prop: 'OrdDate', label: '凭证日期', visible: true },
+        { prop: 'po_order_type', label: '订单类型', visible: true },
+        { prop: 'poqty', label: '采购订单数量', visible: true },
+        { prop: 'sentqty', label: '已交数量', visible: true },
+        { prop: 'restqty', label: '待交数量', visible: true },
+        { prop: 'status', label: '发布', visible: true, fixed: 'right' }
+      ],
+      // 高级筛选抽屉
+      advancedFilterVisible: false,
+      // 模拟数据
+      mockData: [
+        {
+          suppliercode: 'S001',
+          supplier: '供应商A',
+          ponumber: 'PO20240001',
+          poline: '00010',
+          purgroup: 'PG01',
+          OrdDate: '2024-01-15',
+          itemnum: 'M001001',
+          Descr: '发动机气缸盖',
+          po_order_type: 'ZNB',
+          poqty: 1000,
+          sentqty: 300,
+          restqty: 700,
+          status: 'P'
+        },
+        {
+          suppliercode: 'S002',
+          supplier: '供应商B',
+          ponumber: 'PO20240002',
+          poline: '00010',
+          purgroup: 'PG02',
+          OrdDate: '2024-01-16',
+          itemnum: 'M002001',
+          Descr: '变速箱齿轮',
+          po_order_type: 'ZNB',
+          poqty: 500,
+          sentqty: 500,
+          restqty: 0,
+          status: 'C'
+        },
+        {
+          suppliercode: 'S003',
+          supplier: '供应商C',
+          ponumber: 'PO20240003',
+          poline: '00010',
+          purgroup: 'PG01',
+          OrdDate: '2024-01-17',
+          itemnum: 'M003001',
+          Descr: '刹车片',
+          po_order_type: 'ZNB',
+          poqty: 2000,
+          sentqty: 800,
+          restqty: 1200,
+          status: 'P'
+        },
+        {
+          suppliercode: 'S001',
+          supplier: '供应商A',
+          ponumber: 'PO20240004',
+          poline: '00010',
+          purgroup: 'PG03',
+          OrdDate: '2024-01-18',
+          itemnum: 'M004001',
+          Descr: '轮胎',
+          po_order_type: 'ZNB',
+          poqty: 1500,
+          sentqty: 0,
+          restqty: 1500,
+          status: 'N'
+        },
+        {
+          suppliercode: 'S004',
+          supplier: '供应商D',
+          ponumber: 'PO20240005',
+          poline: '00010',
+          purgroup: 'PG02',
+          OrdDate: '2024-01-19',
+          itemnum: 'M005001',
+          Descr: '座椅骨架',
+          po_order_type: 'ZNB',
+          poqty: 800,
+          sentqty: 400,
+          restqty: 400,
+          status: 'P'
+        }
+      ]
+    }
+  },
+  mounted() {
+    // 从本地存储恢复分页设置
+    this.restorePaginationSettings()
+    // 加载数据
+    this.loadData()
+  },
+  methods: {
+    // 加载数据
+    loadData() {
+      this.loading = true
+      
+      // 模拟API请求延迟
+      setTimeout(() => {
+        // 在实际项目中,这里应该调用真实的API
+        let filteredData = [...this.mockData]
+        
+        // 应用搜索条件
+        if (this.searchForm.ponumber) {
+          filteredData = filteredData.filter(item => 
+            item.ponumber.toLowerCase().includes(this.searchForm.ponumber.toLowerCase())
+          )
+        }
+        if (this.searchForm.itemnum) {
+          filteredData = filteredData.filter(item => 
+            item.itemnum.toLowerCase().includes(this.searchForm.itemnum.toLowerCase())
+          )
+        }
+        if (this.searchForm.supplier) {
+          filteredData = filteredData.filter(item => 
+            item.supplier.toLowerCase().includes(this.searchForm.supplier.toLowerCase())
+          )
+        }
+        if (this.searchForm.status) {
+          filteredData = filteredData.filter(item => item.status === this.searchForm.status)
+        }
+        
+        // 应用高级筛选条件
+        if (this.advancedForm.suppliercode) {
+          filteredData = filteredData.filter(item => 
+            item.suppliercode.toLowerCase().includes(this.advancedForm.suppliercode.toLowerCase())
+          )
+        }
+        if (this.advancedForm.purgroup) {
+          filteredData = filteredData.filter(item => 
+            item.purgroup.toLowerCase().includes(this.advancedForm.purgroup.toLowerCase())
+          )
+        }
+        if (this.advancedForm.dateRange && this.advancedForm.dateRange.length === 2) {
+          filteredData = filteredData.filter(item => 
+            item.OrdDate >= this.advancedForm.dateRange[0] && 
+            item.OrdDate <= this.advancedForm.dateRange[1]
+          )
+        }
+        if (this.advancedForm.po_order_type) {
+          filteredData = filteredData.filter(item => 
+            item.po_order_type.toLowerCase().includes(this.advancedForm.po_order_type.toLowerCase())
+          )
+        }
+        
+        // 更新分页信息
+        this.pagination.total = filteredData.length
+        
+        // 计算分页数据
+        const start = (this.pagination.currentPage - 1) * this.pagination.pageSize
+        const end = start + this.pagination.pageSize
+        this.tableData = filteredData.slice(start, end)
+        
+        this.loading = false
+      }, 500)
+    },
+    
+    // 处理搜索
+    handleSearch() {
+      this.pagination.currentPage = 1
+      this.loadData()
+    },
+    
+    // 处理重置
+    handleReset() {
+      this.searchForm = {
+        ponumber: '',
+        itemnum: '',
+        supplier: '',
+        status: ''
+      }
+      this.pagination.currentPage = 1
+      this.loadData()
+    },
+    
+    // 处理高级筛选
+    handleAdvancedFilter() {
+      this.advancedFilterVisible = true
+    },
+    
+    // 应用高级筛选
+    applyAdvancedFilter() {
+      this.pagination.currentPage = 1
+      this.advancedFilterVisible = false
+      this.loadData()
+    },
+    
+    // 重置高级筛选
+    resetAdvancedFilter() {
+      this.advancedForm = {
+        suppliercode: '',
+        purgroup: '',
+        dateRange: [],
+        po_order_type: ''
+      }
+    },
+    
+    // 处理导出
+    handleExport() {
+      // 在实际项目中,这里应该调用导出API
+      this.$message.success('导出成功')
+    },
+    
+    // 处理选择变化
+    handleSelectionChange(selection) {
+      this.selectedRows = selection
+    },
+    
+    // 处理列拖拽
+    handleHeaderDragend(newWidth, oldWidth, column) {
+      // 在实际项目中,可以保存列宽设置
+      console.log('Column width changed:', column.prop, newWidth)
+    },
+    
+    // 处理列命令
+    handleColumnCommand(command) {
+      if (command === 'reset') {
+        this.resetColumns()
+      }
+    },
+    
+    // 重置列
+    resetColumns() {
+      this.columns.forEach(column => {
+        column.visible = true
+      })
+      this.updateTableColumns()
+    },
+    
+    // 更新表格列
+    updateTableColumns() {
+      // 在实际项目中,可以保存列显示设置
+      console.log('Columns updated:', this.columns)
+    },
+    
+    // 处理分页大小变化
+    handleSizeChange(size) {
+      this.pagination.pageSize = size
+      this.pagination.currentPage = 1
+      this.savePaginationSettings()
+      this.loadData()
+    },
+    
+    // 处理分页当前页变化
+    handleCurrentChange(current) {
+      this.pagination.currentPage = current
+      this.savePaginationSettings()
+      this.loadData()
+    },
+    
+    // 保存分页设置
+    savePaginationSettings() {
+      try {
+        localStorage.setItem('material-purchase-order-pagination', JSON.stringify({
+          pageSize: this.pagination.pageSize
+        }))
+      } catch (e) {
+        console.error('Failed to save pagination settings:', e)
+      }
+    },
+    
+    // 恢复分页设置
+    restorePaginationSettings() {
+      try {
+        const saved = localStorage.getItem('material-purchase-order-pagination')
+        if (saved) {
+          const settings = JSON.parse(saved)
+          this.pagination.pageSize = settings.pageSize || 20
+        }
+      } catch (e) {
+        console.error('Failed to restore pagination settings:', e)
+      }
+    },
+    
+    // 格式化日期
+    formatDate(row, column, cellValue) {
+      if (!cellValue) return ''
+      return cellValue
+    },
+    
+    // 格式化数字
+    formatNumber(row, column, cellValue) {
+      if (cellValue === null || cellValue === undefined || cellValue === '') return '0'
+      return Number(cellValue).toLocaleString('zh-CN', {
+        minimumFractionDigits: 2,
+        maximumFractionDigits: 2
+      })
+    },
+    
+    // 格式化状态
+    formatStatus(row, column, cellValue) {
+      const statusMap = {
+        'N': '初始(N)',
+        'P': '已发布(P)',
+        'C': '关闭(C)'
+      }
+      return statusMap[cellValue] || cellValue
+    }
+  }
+}
+</script>
+
+<style scoped>
+.material-purchase-order {
+  position: relative;
+  height: 100%;
+}
+
+.page-title {
+  font-size: 20px;
+  font-weight: 500;
+  margin-bottom: 20px;
+  color: #303133;
+}
+
+.filter-container {
+  background: white;
+  padding: 20px;
+  margin-bottom: 16px;
+  border-radius: 4px;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
+}
+
+.keyword-mode {
+  margin-top: 10px;
+  display: flex;
+  align-items: center;
+  gap: 10px;
+}
+
+.help-icon {
+  color: #909399;
+  cursor: pointer;
+}
+
+.table-actions {
+  margin-bottom: 16px;
+  display: flex;
+  justify-content: flex-end;
+}
+
+.table-container {
+  background: white;
+  border-radius: 4px;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
+  overflow: hidden;
+}
+
+.pagination {
+  margin-top: 16px;
+  display: flex;
+  justify-content: flex-end;
+}
+
+.column-control {
+  position: absolute;
+  top: 80px;
+  right: 20px;
+  z-index: 1000;
+}
+
+.el-dropdown-link {
+  cursor: pointer;
+  color: #409eff;
+}
+
+.drawer-footer {
+  margin-top: 24px;
+  display: flex;
+  justify-content: flex-end;
+  gap: 10px;
+}
+
+.empty-state {
+  margin-top: 60px;
+}
+</style>

+ 845 - 0
dingdan/manage-vue/src/views/purchase-order/MaterialPurchaseRequest.vue

@@ -0,0 +1,845 @@
+<template>
+  <div class="material-purchase-request-container">
+    <!-- 查询条件区域 -->
+    <div class="query-section">
+      <el-card shadow="never" class="query-card">
+        <el-collapse v-model="queryCollapse">
+          <el-collapse-item title="查询条件" name="1">
+            <el-row :gutter="20" class="query-row">
+              <el-col :span="8">
+                <el-form-item label="采购申请单号" :label-width="formLabelWidth">
+                  <el-input
+                    v-model="queryForm.prnumber"
+                    placeholder="请输入采购申请单号"
+                    clearable
+                  />
+                </el-form-item>
+              </el-col>
+              <el-col :span="8">
+                <el-form-item label="物料编码" :label-width="formLabelWidth">
+                  <el-input
+                    v-model="queryForm.itemnum"
+                    placeholder="请输入物料编码"
+                    clearable
+                  />
+                </el-form-item>
+              </el-col>
+              <el-col :span="8">
+                <el-form-item label="供应商" :label-width="formLabelWidth">
+                  <el-input
+                    v-model="queryForm.supplier"
+                    placeholder="请输入供应商名称"
+                    clearable
+                  />
+                </el-form-item>
+              </el-col>
+              <el-col :span="8">
+                <el-form-item label="状态" :label-width="formLabelWidth">
+                  <el-select
+                    v-model="queryForm.status"
+                    placeholder="请选择状态"
+                    clearable
+                  >
+                    <el-option label="初始" value="N" />
+                    <el-option label="已发布" value="P" />
+                    <el-option label="关闭" value="C" />
+                  </el-select>
+                </el-form-item>
+              </el-col>
+              <el-col :span="8">
+                <el-form-item label="关键词模式" :label-width="formLabelWidth">
+                  <el-radio-group v-model="keywordMode">
+                    <el-radio value="AND">全部包含</el-radio>
+                    <el-radio value="OR">任一包含</el-radio>
+                  </el-radio-group>
+                </el-form-item>
+              </el-col>
+            </el-row>
+          </el-collapse-item>
+        </el-collapse>
+        <div class="query-actions">
+          <el-button type="primary" @click="handleSearch">查询</el-button>
+          <el-button @click="handleReset">重置</el-button>
+          <el-button @click="showAdvancedFilter = true">高级筛选</el-button>
+        </div>
+      </el-card>
+    </div>
+
+    <!-- 表格操作按钮 -->
+    <div class="table-actions">
+      <el-button type="primary" @click="handleExport">导出</el-button>
+      <el-button type="success" @click="handleAdd">添加</el-button>
+    </div>
+
+    <!-- 表格区域 -->
+    <el-card shadow="never" class="table-card">
+      <template #header>
+        <div class="table-header">
+          <span>物料采购申请列表</span>
+          <el-dropdown @command="handleTableOption">
+            <el-button type="text">
+              列显示控制
+            </el-button>
+            <template #dropdown>
+              <el-dropdown-menu>
+                <el-dropdown-item command="reset">重置列宽</el-dropdown-item>
+                <el-dropdown-item command="fit">自适应内容</el-dropdown-item>
+                <el-dropdown-item command="fixed">固定列设置</el-dropdown-item>
+              </el-dropdown-menu>
+            </template>
+          </el-dropdown>
+        </div>
+      </template>
+
+      <!-- 骨架屏 -->
+      <el-skeleton :rows="10" animated v-if="loading" />
+
+      <!-- 空状态 -->
+      <el-empty
+        v-else-if="!loading && dataList.length === 0"
+        description="暂无数据"
+        :image-size="120"
+      >
+        <template #description>
+          <p>暂无符合条件的物料采购申请</p>
+          <p>请尝试调整筛选条件或关键词</p>
+        </template>
+        <el-button type="primary" @click="handleReset">清空筛选</el-button>
+        <el-button @click="$router.push('/help')">查看帮助</el-button>
+      </el-empty>
+
+      <!-- 表格内容 -->
+      <el-table
+        v-else
+        v-loading="loading"
+        :data="dataList"
+        style="width: 100%"
+        border
+        fit
+        stripe
+        :row-key="row => row.id"
+        @selection-change="handleSelectionChange"
+        @sort-change="handleSortChange"
+      >
+        <el-table-column
+          type="selection"
+          width="55"
+          fixed="left"
+          v-if="currentRole === '计划员'"
+        />
+
+        <el-table-column prop="prnumber" label="采购申请单号" min-width="150" fixed="left">
+          <template #default="scope">
+            {{ scope.row.prnumber || '-' }}
+          </template>
+        </el-table-column>
+
+        <el-table-column prop="po_order_type" label="订单类型" min-width="120">
+          <template #default="scope">
+            {{ scope.row.po_order_type || '-' }}
+          </template>
+        </el-table-column>
+
+        <el-table-column prop="supplier_type" label="供应类别" min-width="120">
+          <template #default="scope">
+            {{ scope.row.supplier_type || '-' }}
+          </template>
+        </el-table-column>
+
+        <el-table-column prop="itemnum" label="物料编码" min-width="150">
+          <template #default="scope">
+            {{ scope.row.itemnum || '-' }}
+          </template>
+        </el-table-column>
+
+        <el-table-column prop="Descr" label="物料描述" min-width="200">
+          <template #default="scope">
+            {{ scope.row.Descr || '-' }}
+          </template>
+        </el-table-column>
+
+        <el-table-column prop="suppliercode" label="供应商代码" min-width="120">
+          <template #default="scope">
+            {{ scope.row.suppliercode || '-' }}
+          </template>
+        </el-table-column>
+
+        <el-table-column prop="supplier" label="供应商名称" min-width="180">
+          <template #default="scope">
+            {{ scope.row.supplier || '-' }}
+          </template>
+        </el-table-column>
+
+        <el-table-column prop="prqty" label="采购申请数量" min-width="120">
+          <template #default="scope">
+            {{ formatNumber(scope.row.prqty || 0) }}
+          </template>
+        </el-table-column>
+
+        <el-table-column prop="stockqty" label="库存数量" min-width="120">
+          <template #default="scope">
+            {{ formatNumber(scope.row.stockqty || 0) }}
+          </template>
+        </el-table-column>
+
+        <el-table-column prop="pr_purchaser" label="采购员" min-width="120">
+          <template #default="scope">
+            {{ scope.row.pr_purchaser || '-' }}
+          </template>
+        </el-table-column>
+
+        <el-table-column prop="OrdDate" label="凭证日期" min-width="120">
+          <template #default="scope">
+            {{ formatDate(scope.row.OrdDate) }}
+          </template>
+        </el-table-column>
+
+        <el-table-column prop="status" label="状态" min-width="100" fixed="right">
+          <template #default="scope">
+            <el-tag
+              :type="
+                scope.row.status === 'N' ? 'info' :
+                scope.row.status === 'P' ? 'success' :
+                scope.row.status === 'C' ? 'danger' : 'default'
+              "
+            >
+              {{ scope.row.status === 'N' ? '初始' :
+                 scope.row.status === 'P' ? '已发布' :
+                 scope.row.status === 'C' ? '关闭' : '未知' }}
+            </el-tag>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="操作" min-width="180" fixed="right" v-if="currentRole === '计划员'">
+          <template #default="scope">
+            <el-button
+              type="primary"
+              size="small"
+              @click="handleEdit(scope.row)"
+              v-if="scope.row.status === 'N' || scope.row.status === 'P'"
+            >
+              编辑
+            </el-button>
+            <el-button
+              type="danger"
+              size="small"
+              @click="handleDelete(scope.row)"
+              v-if="scope.row.status === 'N' || scope.row.status === 'P'"
+            >
+              删除
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-card>
+
+    <!-- 分页 -->
+    <div class="pagination-container">
+      <el-pagination
+        v-model:current-page="pagination.currentPage"
+        v-model:page-size="pagination.pageSize"
+        :page-sizes="[20, 50, 100]"
+        layout="total, sizes, prev, pager, next, jumper"
+        :total="pagination.total"
+        @size-change="handleSizeChange"
+        @current-change="handleCurrentChange"
+      />
+    </div>
+
+    <!-- 高级筛选抽屉 -->
+    <el-drawer
+      v-model="showAdvancedFilter"
+      title="高级筛选"
+      size="30%"
+      direction="rtl"
+      destroy-on-close
+    >
+      <el-form label-position="top" :model="advancedFilterForm">
+        <el-form-item label="采购申请单号">
+          <el-input
+            v-model="advancedFilterForm.prnumber"
+            placeholder="支持模糊查询"
+            clearable
+          />
+        </el-form-item>
+        <el-form-item label="物料编码">
+          <el-input
+            v-model="advancedFilterForm.itemnum"
+            placeholder="支持模糊查询"
+            clearable
+          />
+        </el-form-item>
+        <el-form-item label="供应商代码">
+          <el-input
+            v-model="advancedFilterForm.suppliercode"
+            placeholder="支持模糊查询"
+            clearable
+          />
+        </el-form-item>
+        <el-form-item label="状态">
+          <el-select
+            v-model="advancedFilterForm.status"
+            placeholder="请选择状态"
+            multiple
+            collapse-tags
+          >
+            <el-option label="初始" value="N" />
+            <el-option label="已发布" value="P" />
+            <el-option label="关闭" value="C" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="凭证日期范围">
+          <el-date-picker
+            v-model="advancedFilterForm.dateRange"
+            type="daterange"
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            value-format="YYYY-MM-DD"
+          />
+        </el-form-item>
+      </el-form>
+      <div class="drawer-footer">
+        <el-button @click="handleResetAdvancedFilter">重置</el-button>
+        <el-button type="primary" @click="handleConfirmAdvancedFilter">确认</el-button>
+      </div>
+    </el-drawer>
+
+    <!-- 编辑/添加对话框 -->
+    <el-dialog
+      v-model="dialogVisible"
+      :title="dialogTitle"
+      width="50%"
+      destroy-on-close
+    >
+      <el-form
+        :model="form"
+        :rules="rules"
+        ref="formRef"
+        label-width="120px"
+      >
+        <el-form-item label="采购申请单号" prop="prnumber" v-if="dialogTitle === '编辑物料采购申请'">
+          <el-input
+            v-model="form.prnumber"
+            placeholder="请输入采购申请单号"
+            :disabled="true"
+          />
+        </el-form-item>
+        <el-form-item label="订单类型" prop="po_order_type">
+          <el-input
+            v-model="form.po_order_type"
+            placeholder="请输入订单类型"
+            :disabled="form.status === 'C'"
+          />
+        </el-form-item>
+        <el-form-item label="供应类别" prop="supplier_type">
+          <el-input
+            v-model="form.supplier_type"
+            placeholder="请输入供应类别"
+            :disabled="form.status === 'C'"
+          />
+        </el-form-item>
+        <el-form-item label="物料编码" prop="itemnum">
+          <el-input
+            v-model="form.itemnum"
+            placeholder="请输入物料编码"
+            :disabled="form.status === 'C'"
+          />
+        </el-form-item>
+        <el-form-item label="物料描述" prop="Descr">
+          <el-input
+            v-model="form.Descr"
+            placeholder="请输入物料描述"
+            :disabled="form.status === 'C'"
+          />
+        </el-form-item>
+        <el-form-item label="供应商代码" prop="suppliercode">
+          <el-input
+            v-model="form.suppliercode"
+            placeholder="请输入供应商代码"
+            :disabled="form.status === 'C'"
+          />
+        </el-form-item>
+        <el-form-item label="供应商名称" prop="supplier">
+          <el-input
+            v-model="form.supplier"
+            placeholder="请输入供应商名称"
+            :disabled="form.status === 'C'"
+          />
+        </el-form-item>
+        <el-form-item label="采购申请数量" prop="prqty">
+          <el-input-number
+            v-model="form.prqty"
+            placeholder="请输入采购申请数量"
+            :min="0"
+            :step="1"
+            :disabled="form.status === 'C'"
+          />
+        </el-form-item>
+        <el-form-item label="库存数量" prop="stockqty">
+          <el-input-number
+            v-model="form.stockqty"
+            placeholder="请输入库存数量"
+            :min="0"
+            :step="1"
+            :disabled="true"
+          />
+        </el-form-item>
+        <el-form-item label="采购员" prop="pr_purchaser">
+          <el-input
+            v-model="form.pr_purchaser"
+            placeholder="请输入采购员"
+            :disabled="form.status === 'C'"
+          />
+        </el-form-item>
+        <el-form-item label="凭证日期" prop="OrdDate">
+          <el-date-picker
+            v-model="form.OrdDate"
+            type="date"
+            placeholder="选择日期"
+            value-format="YYYY-MM-DD"
+            :disabled="form.status === 'C'"
+          />
+        </el-form-item>
+        <el-form-item label="状态" prop="status">
+          <el-select
+            v-model="form.status"
+            placeholder="请选择状态"
+            :disabled="form.status === 'C'"
+          >
+            <el-option label="初始" value="N" />
+            <el-option label="已发布" value="P" />
+            <el-option label="关闭" value="C" />
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="handleSubmit">确认</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { ref, reactive, onMounted, nextTick } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+
+export default {
+  name: 'MaterialPurchaseRequest',
+  setup() {
+    // 状态管理
+    const loading = ref(false)
+    const showAdvancedFilter = ref(false)
+    const dialogVisible = ref(false)
+    const dialogTitle = ref('')
+    const queryCollapse = ref(['1'])
+    const currentRole = ref('计划员') // 默认角色,可以从登录状态获取
+    const keywordMode = ref('AND')
+    const formLabelWidth = '100px'
+    const formRef = ref(null)
+
+    // 表单数据
+    const form = reactive({
+      id: '',
+      prnumber: '',
+      po_order_type: '',
+      supplier_type: '',
+      itemnum: '',
+      Descr: '',
+      suppliercode: '',
+      supplier: '',
+      prqty: 0,
+      stockqty: 0,
+      pr_purchaser: '',
+      OrdDate: '',
+      status: 'N'
+    })
+
+    // 表单验证规则
+    const rules = {
+      po_order_type: [{ required: true, message: '请输入订单类型', trigger: 'blur' }],
+      supplier_type: [{ required: true, message: '请输入供应类别', trigger: 'blur' }],
+      itemnum: [{ required: true, message: '请输入物料编码', trigger: 'blur' }],
+      Descr: [{ required: true, message: '请输入物料描述', trigger: 'blur' }],
+      suppliercode: [{ required: true, message: '请输入供应商代码', trigger: 'blur' }],
+      supplier: [{ required: true, message: '请输入供应商名称', trigger: 'blur' }],
+      prqty: [{ required: true, message: '请输入采购申请数量', trigger: 'change' }],
+      pr_purchaser: [{ required: true, message: '请输入采购员', trigger: 'blur' }],
+      OrdDate: [{ required: true, message: '请选择凭证日期', trigger: 'change' }],
+      status: [{ required: true, message: '请选择状态', trigger: 'change' }]
+    }
+
+    // 查询表单
+    const queryForm = reactive({
+      prnumber: '',
+      itemnum: '',
+      supplier: '',
+      status: ''
+    })
+
+    // 高级筛选表单
+    const advancedFilterForm = reactive({
+      prnumber: '',
+      itemnum: '',
+      suppliercode: '',
+      status: [],
+      dateRange: null
+    })
+
+    // 分页数据
+    const pagination = reactive({
+      currentPage: 1,
+      pageSize: 20,
+      total: 0
+    })
+
+    // 数据列表
+    const dataList = ref([])
+
+    // 格式化日期
+    const formatDate = (date) => {
+      if (!date) return '-'
+      const d = new Date(date)
+      const year = d.getFullYear()
+      const month = String(d.getMonth() + 1).padStart(2, '0')
+      const day = String(d.getDate()).padStart(2, '0')
+      return `${year}-${month}-${day}`
+    }
+
+    // 格式化数字
+    const formatNumber = (num) => {
+      return Number(num).toLocaleString('zh-CN', {
+        minimumFractionDigits: 0,
+        maximumFractionDigits: 2
+      })
+    }
+
+    // 加载数据
+    const loadData = async () => {
+      loading.value = true
+      try {
+        // 模拟API调用
+        await new Promise(resolve => setTimeout(resolve, 500))
+        
+        // 模拟数据
+        dataList.value = [
+          {
+            id: '1',
+            prnumber: 'PR2023001',
+            po_order_type: 'ZNB',
+            supplier_type: '外购',
+            itemnum: 'MAT001',
+            Descr: '原材料A',
+            suppliercode: 'SUP001',
+            supplier: '供应商A',
+            prqty: 100,
+            stockqty: 20,
+            pr_purchaser: '张三',
+            OrdDate: '2023-01-15',
+            status: 'P'
+          },
+          {
+            id: '2',
+            prnumber: 'PR2023002',
+            po_order_type: 'ZNB',
+            supplier_type: '外购',
+            itemnum: 'MAT002',
+            Descr: '原材料B',
+            suppliercode: 'SUP002',
+            supplier: '供应商B',
+            prqty: 200,
+            stockqty: 50,
+            pr_purchaser: '李四',
+            OrdDate: '2023-01-20',
+            status: 'N'
+          },
+          {
+            id: '3',
+            prnumber: 'PR2023003',
+            po_order_type: 'ZNB',
+            supplier_type: '外购',
+            itemnum: 'MAT003',
+            Descr: '原材料C',
+            suppliercode: 'SUP003',
+            supplier: '供应商C',
+            prqty: 150,
+            stockqty: 0,
+            pr_purchaser: '王五',
+            OrdDate: '2023-02-10',
+            status: 'C'
+          },
+          {
+            id: '4',
+            prnumber: 'PR2023004',
+            po_order_type: 'ZNB',
+            supplier_type: '外购',
+            itemnum: 'MAT004',
+            Descr: '原材料D',
+            suppliercode: 'SUP001',
+            supplier: '供应商A',
+            prqty: 300,
+            stockqty: 100,
+            pr_purchaser: '张三',
+            OrdDate: '2023-02-15',
+            status: 'P'
+          },
+          {
+            id: '5',
+            prnumber: 'PR2023005',
+            po_order_type: 'ZNB',
+            supplier_type: '外购',
+            itemnum: 'MAT005',
+            Descr: '原材料E',
+            suppliercode: 'SUP002',
+            supplier: '供应商B',
+            prqty: 120,
+            stockqty: 30,
+            pr_purchaser: '李四',
+            OrdDate: '2023-02-20',
+            status: 'N'
+          }
+        ]
+        
+        pagination.total = dataList.value.length
+      } catch (error) {
+        ElMessage.error('数据加载失败,请重试')
+        console.error('加载数据失败:', error)
+      } finally {
+        loading.value = false
+      }
+    }
+
+    // 搜索
+    const handleSearch = () => {
+      pagination.currentPage = 1
+      loadData()
+    }
+
+    // 重置
+    const handleReset = () => {
+      Object.keys(queryForm).forEach(key => {
+        queryForm[key] = ''
+      })
+      pagination.currentPage = 1
+      loadData()
+    }
+
+    // 高级筛选确认
+    const handleConfirmAdvancedFilter = () => {
+      showAdvancedFilter.value = false
+      pagination.currentPage = 1
+      loadData()
+    }
+
+    // 重置高级筛选
+    const handleResetAdvancedFilter = () => {
+      Object.keys(advancedFilterForm).forEach(key => {
+        advancedFilterForm[key] = ''
+      })
+      advancedFilterForm.status = []
+      advancedFilterForm.dateRange = null
+    }
+
+    // 分页处理
+    const handleSizeChange = (size) => {
+      pagination.pageSize = size
+      loadData()
+    }
+
+    const handleCurrentChange = (current) => {
+      pagination.currentPage = current
+      loadData()
+    }
+
+    // 添加
+    const handleAdd = () => {
+      dialogTitle.value = '添加物料采购申请'
+      // 清空表单
+      Object.keys(form).forEach(key => {
+        form[key] = ''
+      })
+      form.prqty = 0
+      form.stockqty = 0
+      form.status = 'N'
+      // 生成采购申请单号(实际应用中应该由后端生成)
+      form.prnumber = 'PR' + new Date().getTime()
+      dialogVisible.value = true
+    }
+
+    // 编辑
+    const handleEdit = (row) => {
+      dialogTitle.value = '编辑物料采购申请'
+      Object.assign(form, row)
+      dialogVisible.value = true
+    }
+
+    // 删除
+    const handleDelete = (row) => {
+      ElMessageBox.confirm(
+        `确定要删除采购申请单号为 ${row.prnumber} 的记录吗?`,
+        '删除确认',
+        {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        }
+      ).then(() => {
+        // 模拟删除
+        ElMessage.success('删除成功')
+        loadData()
+      }).catch(() => {
+        ElMessage.info('已取消删除')
+      })
+    }
+
+    // 导出
+    const handleExport = () => {
+      ElMessage.success('数据导出成功')
+    }
+
+    // 提交表单
+    const handleSubmit = async () => {
+      try {
+        if (formRef.value) {
+          await formRef.value.validate()
+        }
+        
+        // 模拟提交
+        await new Promise(resolve => setTimeout(resolve, 300))
+        
+        ElMessage.success(dialogTitle.value.includes('添加') ? '添加成功' : '修改成功')
+        dialogVisible.value = false
+        loadData()
+      } catch (error) {
+        if (error !== false) {
+          ElMessage.error('保存失败,请重试')
+        }
+      }
+    }
+
+    // 表格选项处理
+    const handleTableOption = (command) => {
+      switch (command) {
+        case 'reset':
+          ElMessage.info('已重置列宽')
+          break
+        case 'fit':
+          ElMessage.info('已自适应内容')
+          break
+        case 'fixed':
+          ElMessage.info('固定列设置已应用')
+          break
+      }
+    }
+
+    // 选择变化
+    const handleSelectionChange = (selection) => {
+      console.log('选择变化:', selection)
+    }
+
+    // 排序变化
+    const handleSortChange = (column, prop, order) => {
+      console.log('排序变化:', column, prop, order)
+    }
+
+    // 组件挂载时加载数据
+    onMounted(() => {
+      loadData()
+    })
+
+    return {
+      loading,
+      showAdvancedFilter,
+      dialogVisible,
+      dialogTitle,
+      queryCollapse,
+      currentRole,
+      keywordMode,
+      formLabelWidth,
+      formRef,
+      form,
+      rules,
+      queryForm,
+      advancedFilterForm,
+      pagination,
+      dataList,
+      formatDate,
+      formatNumber,
+      handleSearch,
+      handleReset,
+      handleConfirmAdvancedFilter,
+      handleResetAdvancedFilter,
+      handleSizeChange,
+      handleCurrentChange,
+      handleAdd,
+      handleEdit,
+      handleDelete,
+      handleExport,
+      handleSubmit,
+      handleTableOption,
+      handleSelectionChange,
+      handleSortChange
+    }
+  }
+}
+</script>
+
+<style scoped>
+.material-purchase-request-container {
+  padding: 20px;
+}
+
+.query-section {
+  margin-bottom: 20px;
+}
+
+.query-card {
+  margin-bottom: 20px;
+}
+
+.query-row {
+  margin-bottom: 10px;
+}
+
+.query-actions {
+  margin-top: 20px;
+  text-align: right;
+}
+
+.table-actions {
+  margin-bottom: 15px;
+}
+
+.table-card {
+  margin-bottom: 20px;
+}
+
+.table-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.pagination-container {
+  margin-top: 20px;
+  text-align: right;
+}
+
+.drawer-footer {
+  margin-top: 20px;
+  text-align: right;
+}
+
+/* 响应式调整 */
+@media (max-width: 768px) {
+  .material-purchase-request-container {
+    padding: 10px;
+  }
+  
+  .query-row .el-col {
+    :span: 24;
+    margin-bottom: 10px;
+  }
+}
+</style>

+ 751 - 0
dingdan/manage-vue/src/views/purchase-order/SubcontractOrder.vue

@@ -0,0 +1,751 @@
+<template>
+  <div class="subcontract-order-container">
+    <!-- 查询条件区域 -->
+    <div class="query-section">
+      <el-card shadow="never" class="query-card">
+        <el-collapse v-model="queryCollapse">
+          <el-collapse-item title="查询条件" name="1">
+            <el-row :gutter="20" class="query-row">
+              <el-col :span="8">
+                <el-form-item label="采购单号" :label-width="formLabelWidth">
+                  <el-input
+                    v-model="queryForm.ponumber"
+                    placeholder="请输入采购单号"
+                    clearable
+                  />
+                </el-form-item>
+              </el-col>
+              <el-col :span="8">
+                <el-form-item label="物料编码" :label-width="formLabelWidth">
+                  <el-input
+                    v-model="queryForm.materialcode"
+                    placeholder="请输入物料编码"
+                    clearable
+                  />
+                </el-form-item>
+              </el-col>
+              <el-col :span="8">
+                <el-form-item label="供应商" :label-width="formLabelWidth">
+                  <el-input
+                    v-model="queryForm.supplier"
+                    placeholder="请输入供应商名称"
+                    clearable
+                  />
+                </el-form-item>
+              </el-col>
+              <el-col :span="8">
+                <el-form-item label="状态" :label-width="formLabelWidth">
+                  <el-select
+                    v-model="queryForm.status"
+                    placeholder="请选择状态"
+                    clearable
+                  >
+                    <el-option label="初始" value="N" />
+                    <el-option label="已发布" value="P" />
+                    <el-option label="关闭" value="C" />
+                  </el-select>
+                </el-form-item>
+              </el-col>
+              <el-col :span="8">
+                <el-form-item label="关键词模式" :label-width="formLabelWidth">
+                  <el-radio-group v-model="keywordMode">
+                    <el-radio value="AND">全部包含</el-radio>
+                    <el-radio value="OR">任一包含</el-radio>
+                  </el-radio-group>
+                </el-form-item>
+              </el-col>
+            </el-row>
+          </el-collapse-item>
+        </el-collapse>
+        <div class="query-actions">
+          <el-button type="primary" @click="handleSearch">查询</el-button>
+          <el-button @click="handleReset">重置</el-button>
+          <el-button @click="showAdvancedFilter = true">高级筛选</el-button>
+        </div>
+      </el-card>
+    </div>
+
+    <!-- 表格操作按钮 -->
+    <div class="table-actions">
+      <el-button type="primary" @click="handleExport">导出</el-button>
+    </div>
+
+    <!-- 表格区域 -->
+    <el-card shadow="never" class="table-card">
+      <template #header>
+        <div class="table-header">
+          <span>委外加工订单列表</span>
+          <el-dropdown @command="handleTableOption">
+            <el-button type="text">
+              列显示控制
+            </el-button>
+            <template #dropdown>
+              <el-dropdown-menu>
+                <el-dropdown-item command="reset">重置列宽</el-dropdown-item>
+                <el-dropdown-item command="fit">自适应内容</el-dropdown-item>
+                <el-dropdown-item command="fixed">固定列设置</el-dropdown-item>
+              </el-dropdown-menu>
+            </template>
+          </el-dropdown>
+        </div>
+      </template>
+
+      <!-- 骨架屏 -->
+      <el-skeleton :rows="10" animated v-if="loading" />
+
+      <!-- 空状态 -->
+      <el-empty
+        v-else-if="!loading && dataList.length === 0"
+        description="暂无数据"
+        :image-size="120"
+      >
+        <template #description>
+          <p>暂无符合条件的委外加工订单</p>
+          <p>请尝试调整筛选条件或关键词</p>
+        </template>
+        <el-button type="primary" @click="handleReset">清空筛选</el-button>
+        <el-button @click="$router.push('/help')">查看帮助</el-button>
+      </el-empty>
+
+      <!-- 表格内容 -->
+      <el-table
+        v-else
+        v-loading="loading"
+        :data="dataList"
+        style="width: 100%"
+        border
+        fit
+        stripe
+        :row-key="row => row.id"
+        @selection-change="handleSelectionChange"
+        @sort-change="handleSortChange"
+      >
+        <el-table-column
+          type="selection"
+          width="55"
+          fixed="left"
+          v-if="currentRole === '计划员' || currentRole === '供应商'"
+        />
+
+        <el-table-column prop="suppliercode" label="供应商代码" min-width="120" fixed="left">
+          <template #default="scope">
+            {{ scope.row.suppliercode || '-' }}
+          </template>
+        </el-table-column>
+
+        <el-table-column prop="supplier" label="供应商名称" min-width="180">
+          <template #default="scope">
+            {{ scope.row.supplier || '-' }}
+          </template>
+        </el-table-column>
+
+        <el-table-column prop="ponumber" label="采购单号" min-width="150">
+          <template #default="scope">
+            {{ scope.row.ponumber || '-' }}
+          </template>
+        </el-table-column>
+
+        <el-table-column prop="poline" label="采购单行号" min-width="120">
+          <template #default="scope">
+            {{ scope.row.poline || '-' }}
+          </template>
+        </el-table-column>
+
+        <el-table-column prop="purgroup" label="采购组" min-width="120">
+          <template #default="scope">
+            {{ scope.row.purgroup || '-' }}
+          </template>
+        </el-table-column>
+
+        <el-table-column prop="OrdDate" label="凭证日期" min-width="150">
+          <template #default="scope">
+            {{ formatDate(scope.row.OrdDate) }}
+          </template>
+        </el-table-column>
+
+        <el-table-column prop="status" label="状态" min-width="100">
+          <template #default="scope">
+            <el-tag
+              :type="
+                scope.row.status === 'N' ? 'info' :
+                scope.row.status === 'P' ? 'success' :
+                scope.row.status === 'C' ? 'danger' : 'default'
+              "
+            >
+              {{ scope.row.status === 'N' ? '初始' :
+                 scope.row.status === 'P' ? '已发布' :
+                 scope.row.status === 'C' ? '关闭' : '未知' }}
+            </el-tag>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="操作" min-width="180" fixed="right" v-if="currentRole === '计划员' || currentRole === '供应商'">
+          <template #default="scope">
+            <el-button
+              type="primary"
+              size="small"
+              @click="handleEdit(scope.row)"
+              v-if="scope.row.status === 'N' || scope.row.status === 'P'"
+            >
+              编辑
+            </el-button>
+            <el-button
+              type="danger"
+              size="small"
+              @click="handleDelete(scope.row)"
+              v-if="scope.row.status === 'N' || scope.row.status === 'P'"
+            >
+              删除
+            </el-button>
+            <el-button
+              type="info"
+              size="small"
+              @click="handleShowDetails(scope.row)"
+            >
+              委外采购明细
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-card>
+
+    <!-- 分页 -->
+    <div class="pagination-container">
+      <el-pagination
+        v-model:current-page="pagination.currentPage"
+        v-model:page-size="pagination.pageSize"
+        :page-sizes="[20, 50, 100]"
+        layout="total, sizes, prev, pager, next, jumper"
+        :total="pagination.total"
+        @size-change="handleSizeChange"
+        @current-change="handleCurrentChange"
+      />
+    </div>
+
+    <!-- 高级筛选抽屉 -->
+    <el-drawer
+      v-model="showAdvancedFilter"
+      title="高级筛选"
+      size="30%"
+      direction="rtl"
+      destroy-on-close
+    >
+      <el-form label-position="top" :model="advancedFilterForm">
+        <el-form-item label="采购单号">
+          <el-input
+            v-model="advancedFilterForm.ponumber"
+            placeholder="支持模糊查询"
+            clearable
+          />
+        </el-form-item>
+        <el-form-item label="供应商代码">
+          <el-input
+            v-model="advancedFilterForm.suppliercode"
+            placeholder="支持模糊查询"
+            clearable
+          />
+        </el-form-item>
+        <el-form-item label="采购组">
+          <el-input
+            v-model="advancedFilterForm.purgroup"
+            placeholder="支持模糊查询"
+            clearable
+          />
+        </el-form-item>
+        <el-form-item label="状态">
+          <el-select
+            v-model="advancedFilterForm.status"
+            placeholder="请选择状态"
+            multiple
+            collapse-tags
+          >
+            <el-option label="初始" value="N" />
+            <el-option label="已发布" value="P" />
+            <el-option label="关闭" value="C" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="凭证日期范围">
+          <el-date-picker
+            v-model="advancedFilterForm.dateRange"
+            type="daterange"
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            value-format="YYYY-MM-DD"
+          />
+        </el-form-item>
+      </el-form>
+      <div class="drawer-footer">
+        <el-button @click="handleResetAdvancedFilter">重置</el-button>
+        <el-button type="primary" @click="handleConfirmAdvancedFilter">确认</el-button>
+      </div>
+    </el-drawer>
+
+    <!-- 编辑对话框 -->
+    <el-dialog
+      v-model="dialogVisible"
+      :title="dialogTitle"
+      width="50%"
+      destroy-on-close
+    >
+      <el-form
+        :model="form"
+        :rules="rules"
+        ref="formRef"
+        label-width="120px"
+      >
+        <el-form-item label="供应商代码" prop="suppliercode">
+          <el-input
+            v-model="form.suppliercode"
+            placeholder="请输入供应商代码"
+            :disabled="form.status === 'C'"
+          />
+        </el-form-item>
+        <el-form-item label="供应商名称" prop="supplier">
+          <el-input
+            v-model="form.supplier"
+            placeholder="请输入供应商名称"
+            :disabled="form.status === 'C'"
+          />
+        </el-form-item>
+        <el-form-item label="采购单号" prop="ponumber">
+          <el-input
+            v-model="form.ponumber"
+            placeholder="请输入采购单号"
+            :disabled="true"
+          />
+        </el-form-item>
+        <el-form-item label="采购单行号" prop="poline">
+          <el-input
+            v-model="form.poline"
+            placeholder="请输入采购单行号"
+            :disabled="true"
+          />
+        </el-form-item>
+        <el-form-item label="采购组" prop="purgroup">
+          <el-input
+            v-model="form.purgroup"
+            placeholder="请输入采购组"
+            :disabled="form.status === 'C'"
+          />
+        </el-form-item>
+        <el-form-item label="凭证日期" prop="OrdDate">
+          <el-date-picker
+            v-model="form.OrdDate"
+            type="date"
+            placeholder="选择日期"
+            value-format="YYYY-MM-DD"
+            :disabled="form.status === 'C'"
+          />
+        </el-form-item>
+        <el-form-item label="状态" prop="status">
+          <el-select
+            v-model="form.status"
+            placeholder="请选择状态"
+            :disabled="form.status === 'C'"
+          >
+            <el-option label="初始" value="N" />
+            <el-option label="已发布" value="P" />
+            <el-option label="关闭" value="C" />
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="handleSubmit">确认</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 委外采购明细对话框 -->
+    <el-dialog
+      v-model="detailDialogVisible"
+      title="委外采购明细"
+      width="70%"
+      destroy-on-close
+    >
+      <div class="detail-content">
+        <h3>订单信息</h3>
+        <el-descriptions :column="2" :border="true">
+          <el-descriptions-item label="采购单号">{{ selectedRow?.ponumber }}</el-descriptions-item>
+          <el-descriptions-item label="采购单行号">{{ selectedRow?.poline }}</el-descriptions-item>
+          <el-descriptions-item label="供应商">{{ selectedRow?.supplier }}</el-descriptions-item>
+          <el-descriptions-item label="凭证日期">{{ formatDate(selectedRow?.OrdDate) }}</el-descriptions-item>
+        </el-descriptions>
+        
+        <h3 style="margin-top: 20px;">委外加工物料明细</h3>
+        <el-table :data="subcontractDetails" border style="width: 100%;">
+          <el-table-column prop="materialCode" label="物料编码" min-width="120" />
+          <el-table-column prop="materialName" label="物料名称" min-width="150" />
+          <el-table-column prop="quantity" label="数量" min-width="100" />
+          <el-table-column prop="unit" label="单位" min-width="80" />
+          <el-table-column prop="issueDate" label="发料日期" min-width="120" />
+        </el-table>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { ref, reactive, onMounted, nextTick } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+
+export default {
+  name: 'SubcontractOrder',
+  setup() {
+    // 状态管理
+    const loading = ref(false)
+    const showAdvancedFilter = ref(false)
+    const dialogVisible = ref(false)
+    const detailDialogVisible = ref(false)
+    const dialogTitle = ref('')
+    const queryCollapse = ref(['1'])
+    const selectedRow = ref(null)
+    const currentRole = ref('计划员') // 默认角色,可以从登录状态获取
+    const keywordMode = ref('AND')
+    const formLabelWidth = '100px'
+
+    // 表单数据
+    const form = reactive({
+      id: '',
+      suppliercode: '',
+      supplier: '',
+      ponumber: '',
+      poline: '',
+      purgroup: '',
+      OrdDate: '',
+      status: ''
+    })
+
+    // 表单验证规则
+    const rules = {
+      suppliercode: [{ required: true, message: '请输入供应商代码', trigger: 'blur' }],
+      supplier: [{ required: true, message: '请输入供应商名称', trigger: 'blur' }],
+      ponumber: [{ required: true, message: '请输入采购单号', trigger: 'blur' }],
+      poline: [{ required: true, message: '请输入采购单行号', trigger: 'blur' }],
+      purgroup: [{ required: true, message: '请输入采购组', trigger: 'blur' }],
+      OrdDate: [{ required: true, message: '请选择凭证日期', trigger: 'change' }],
+      status: [{ required: true, message: '请选择状态', trigger: 'change' }]
+    }
+
+    // 查询表单
+    const queryForm = reactive({
+      ponumber: '',
+      materialcode: '',
+      supplier: '',
+      status: ''
+    })
+
+    // 高级筛选表单
+    const advancedFilterForm = reactive({
+      ponumber: '',
+      suppliercode: '',
+      purgroup: '',
+      status: [],
+      dateRange: null
+    })
+
+    // 分页数据
+    const pagination = reactive({
+      currentPage: 1,
+      pageSize: 20,
+      total: 0
+    })
+
+    // 数据列表
+    const dataList = ref([])
+    // 委外明细数据
+    const subcontractDetails = ref([])
+
+    // 格式化日期
+    const formatDate = (date) => {
+      if (!date) return '-'
+      const d = new Date(date)
+      const year = d.getFullYear()
+      const month = String(d.getMonth() + 1).padStart(2, '0')
+      const day = String(d.getDate()).padStart(2, '0')
+      return `${year}-${month}-${day}`
+    }
+
+    // 加载数据
+    const loadData = async () => {
+      loading.value = true
+      try {
+        // 模拟API调用
+        await new Promise(resolve => setTimeout(resolve, 500))
+        
+        // 模拟数据
+        dataList.value = [
+          {
+            id: '1',
+            suppliercode: 'SUP001',
+            supplier: '供应商A',
+            ponumber: 'PO2023001',
+            poline: '00010',
+            purgroup: 'PG001',
+            OrdDate: '2023-01-15',
+            status: 'P'
+          },
+          {
+            id: '2',
+            suppliercode: 'SUP002',
+            supplier: '供应商B',
+            ponumber: 'PO2023002',
+            poline: '00020',
+            purgroup: 'PG002',
+            OrdDate: '2023-01-20',
+            status: 'N'
+          },
+          {
+            id: '3',
+            suppliercode: 'SUP003',
+            supplier: '供应商C',
+            ponumber: 'PO2023003',
+            poline: '00030',
+            purgroup: 'PG001',
+            OrdDate: '2023-02-10',
+            status: 'C'
+          }
+        ]
+        
+        pagination.total = dataList.value.length
+      } catch (error) {
+        ElMessage.error('数据加载失败,请重试')
+        console.error('加载数据失败:', error)
+      } finally {
+        loading.value = false
+      }
+    }
+
+    // 搜索
+    const handleSearch = () => {
+      pagination.currentPage = 1
+      loadData()
+    }
+
+    // 重置
+    const handleReset = () => {
+      Object.keys(queryForm).forEach(key => {
+        queryForm[key] = ''
+      })
+      pagination.currentPage = 1
+      loadData()
+    }
+
+    // 高级筛选确认
+    const handleConfirmAdvancedFilter = () => {
+      showAdvancedFilter.value = false
+      pagination.currentPage = 1
+      loadData()
+    }
+
+    // 重置高级筛选
+    const handleResetAdvancedFilter = () => {
+      Object.keys(advancedFilterForm).forEach(key => {
+        advancedFilterForm[key] = ''
+      })
+      advancedFilterForm.status = []
+      advancedFilterForm.dateRange = null
+    }
+
+    // 分页处理
+    const handleSizeChange = (size) => {
+      pagination.pageSize = size
+      loadData()
+    }
+
+    const handleCurrentChange = (current) => {
+      pagination.currentPage = current
+      loadData()
+    }
+
+    // 编辑
+    const handleEdit = (row) => {
+      dialogTitle.value = '编辑委外加工订单'
+      Object.assign(form, row)
+      dialogVisible.value = true
+    }
+
+    // 删除
+    const handleDelete = (row) => {
+      ElMessageBox.confirm(
+        `确定要删除采购单号为 ${row.ponumber} 的记录吗?`,
+        '删除确认',
+        {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        }
+      ).then(() => {
+        ElMessage.success('删除成功')
+        loadData()
+      }).catch(() => {
+        ElMessage.info('已取消删除')
+      })
+    }
+
+    // 导出
+    const handleExport = () => {
+      ElMessage.success('数据导出成功')
+    }
+
+    // 查看委外采购明细
+    const handleShowDetails = (row) => {
+      selectedRow.value = row
+      // 模拟加载明细数据
+      subcontractDetails.value = [
+        {
+          materialCode: 'MAT001',
+          materialName: '原材料A',
+          quantity: 100,
+          unit: '个',
+          issueDate: '2023-01-16'
+        },
+        {
+          materialCode: 'MAT002',
+          materialName: '原材料B',
+          quantity: 50,
+          unit: 'KG',
+          issueDate: '2023-01-16'
+        }
+      ]
+      detailDialogVisible.value = true
+    }
+
+    // 提交表单
+    const handleSubmit = () => {
+      // 模拟提交
+      ElMessage.success('保存成功')
+      dialogVisible.value = false
+      loadData()
+    }
+
+    // 表格选项处理
+    const handleTableOption = (command) => {
+      switch (command) {
+        case 'reset':
+          ElMessage.info('已重置列宽')
+          break
+        case 'fit':
+          ElMessage.info('已自适应内容')
+          break
+        case 'fixed':
+          ElMessage.info('固定列设置已应用')
+          break
+      }
+    }
+
+    // 选择变化
+    const handleSelectionChange = (selection) => {
+      console.log('选择变化:', selection)
+    }
+
+    // 排序变化
+    const handleSortChange = (column, prop, order) => {
+      console.log('排序变化:', column, prop, order)
+    }
+
+    // 组件挂载时加载数据
+    onMounted(() => {
+      loadData()
+    })
+
+    return {
+      loading,
+      showAdvancedFilter,
+      dialogVisible,
+      detailDialogVisible,
+      dialogTitle,
+      queryCollapse,
+      selectedRow,
+      currentRole,
+      keywordMode,
+      formLabelWidth,
+      form,
+      rules,
+      queryForm,
+      advancedFilterForm,
+      pagination,
+      dataList,
+      subcontractDetails,
+      formatDate,
+      handleSearch,
+      handleReset,
+      handleConfirmAdvancedFilter,
+      handleResetAdvancedFilter,
+      handleSizeChange,
+      handleCurrentChange,
+      handleEdit,
+      handleDelete,
+      handleExport,
+      handleShowDetails,
+      handleSubmit,
+      handleTableOption,
+      handleSelectionChange,
+      handleSortChange
+    }
+  }
+}
+</script>
+
+<style scoped>
+.subcontract-order-container {
+  padding: 20px;
+}
+
+.query-section {
+  margin-bottom: 20px;
+}
+
+.query-card {
+  margin-bottom: 20px;
+}
+
+.query-row {
+  margin-bottom: 10px;
+}
+
+.query-actions {
+  margin-top: 20px;
+  text-align: right;
+}
+
+.table-actions {
+  margin-bottom: 15px;
+}
+
+.table-card {
+  margin-bottom: 20px;
+}
+
+.table-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.pagination-container {
+  margin-top: 20px;
+  text-align: right;
+}
+
+.drawer-footer {
+  margin-top: 20px;
+  text-align: right;
+}
+
+.detail-content {
+  padding: 10px;
+}
+
+/* 响应式调整 */
+@media (max-width: 768px) {
+  .subcontract-order-container {
+    padding: 10px;
+  }
+  
+  .query-row .el-col {
+    :span: 24;
+    margin-bottom: 10px;
+  }
+}
+</style>

+ 11 - 0
dingdan/manage-vue/vite.config.js

@@ -0,0 +1,11 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+
+export default defineConfig({
+  plugins: [vue()],
+  resolve: {
+    alias: {
+      '@': '/src'
+    }
+  }
+})

+ 19 - 0
dingdan/package.json

@@ -0,0 +1,19 @@
+{
+  "name": "material-delivery-system",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "vue": "^3.4.21",
+    "xlsx": "^0.18.5"
+  },
+  "devDependencies": {
+    "@vitejs/plugin-vue": "^5.0.4",
+    "vite": "^5.2.0"
+  }
+}

+ 1 - 0
dingdan/public/vite.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1838190fe11c01f6" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1838190fe11c01f7" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1838190fe11c01f6)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1838190fe11c01f7)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

+ 249 - 0
dingdan/src/App.vue

@@ -0,0 +1,249 @@
+<template>
+  <div class="container">
+    <h1>物料交货计划系统</h1>
+    
+    <!-- 文件上传区域 -->
+    <div class="file-upload">
+      <h2>上传Excel数据</h2>
+      <input type="file" accept=".xlsx,.xls" @change="handleFileUpload" />
+      <div v-if="errorMessage" class="error-message">{{ errorMessage }}</div>
+    </div>
+    
+    <!-- 数据表格区域 -->
+    <div class="data-table" v-if="data.length > 0">
+      <h2>物料交货计划列表</h2>
+      <table>
+        <thead>
+          <tr>
+            <th>工厂代码</th>
+            <th>物料编码</th>
+            <th>物料描述</th>
+            <th>图号</th>
+            <th>版本</th>
+            <th>采购订单号</th>
+            <th>订单行</th>
+            <th>供应商代码</th>
+            <th>采购员</th>
+            <th>供应类型</th>
+            <th>订单数量</th>
+            <th>入库数量</th>
+            <th>在途数量</th>
+            <th>待交数量</th>
+            <th>交货数量</th>
+            <th>交货日期</th>
+            <th>供应商回复数量</th>
+            <th>供应商回复交期</th>
+            <th>备注</th>
+            <th>操作</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr v-for="(item, index) in data" :key="`${item.purchaseOrder}-${item.orderLine}`">
+            <td>{{ item.factoryCode }}</td>
+            <td>{{ item.materialCode }}</td>
+            <td>{{ item.materialDescription }}</td>
+            <td>{{ item.drawingNumber }}</td>
+            <td>{{ item.version }}</td>
+            <td>{{ item.purchaseOrder }}</td>
+            <td>{{ item.orderLine }}</td>
+            <td>{{ item.supplierCode }}</td>
+            <td>{{ item.buyer }}</td>
+            <td>{{ item.supplyType }}</td>
+            <td>{{ item.orderQuantity }}</td>
+            <td>{{ item.receivedQuantity }}</td>
+            <td>{{ item.inTransitQuantity }}</td>
+            <td>{{ item.pendingQuantity }}</td>
+            <td>{{ item.deliveryQuantity }}</td>
+            <td>{{ formatDate(item.deliveryDate) }}</td>
+            <td>
+              <input 
+                type="number" 
+                v-model="item.supplierReplyQuantity" 
+                :disabled="!isEditing(index)"
+              />
+            </td>
+            <td>
+              <input 
+                type="date" 
+                v-model="item.supplierReplyDate" 
+                :disabled="!isEditing(index)"
+                @change="formatDateInput(index)"
+              />
+            </td>
+            <td>{{ item.remarks }}</td>
+            <td>
+              <button @click="startEditing(index)" v-if="!isEditing(index)">编辑</button>
+              <button @click="saveEditing(index)" class="save-btn" v-if="isEditing(index)">保存</button>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+    
+    <!-- 空状态提示 -->
+    <div class="empty-state" v-else>
+      <p>请上传Excel文件以查看物料交货计划数据</p>
+    </div>
+  </div>
+</template>
+
+<script>
+import * as XLSX from 'xlsx';
+
+export default {
+  name: 'App',
+  data() {
+    return {
+      data: [],
+      errorMessage: '',
+      editingIndex: null,
+      // 模拟数据,用于演示
+      mockData: [
+        {
+          factoryCode: 'F001',
+          materialCode: 'M001',
+          materialDescription: '发动机零件',
+          drawingNumber: 'DRW-001',
+          version: 'V1.0',
+          purchaseOrder: 'PO-001',
+          orderLine: 1,
+          supplierCode: 'S001',
+          buyer: '张三',
+          supplyType: '标准',
+          orderQuantity: 1000,
+          receivedQuantity: 500,
+          inTransitQuantity: 200,
+          pendingQuantity: 300,
+          deliveryQuantity: 300,
+          deliveryDate: '2024/03/15',
+          supplierReplyQuantity: null,
+          supplierReplyDate: null,
+          remarks: '急需'
+        },
+        {
+          factoryCode: 'F001',
+          materialCode: 'M002',
+          materialDescription: '变速箱零件',
+          drawingNumber: 'DRW-002',
+          version: 'V2.0',
+          purchaseOrder: 'PO-002',
+          orderLine: 1,
+          supplierCode: 'S001',
+          buyer: '李四',
+          supplyType: '定制',
+          orderQuantity: 500,
+          receivedQuantity: 100,
+          inTransitQuantity: 0,
+          pendingQuantity: 400,
+          deliveryQuantity: 400,
+          deliveryDate: '2024/03/20',
+          supplierReplyQuantity: null,
+          supplierReplyDate: null,
+          remarks: '按计划'
+        }
+      ]
+    };
+  },
+  mounted() {
+    // 默认加载模拟数据以便展示
+    this.data = JSON.parse(JSON.stringify(this.mockData));
+    this.calculatePendingQuantity();
+  },
+  methods: {
+    handleFileUpload(event) {
+      const file = event.target.files[0];
+      if (!file) return;
+      
+      this.errorMessage = '';
+      
+      const reader = new FileReader();
+      reader.onload = (e) => {
+        try {
+          const data = new Uint8Array(e.target.result);
+          const workbook = XLSX.read(data, { type: 'array' });
+          const firstSheetName = workbook.SheetNames[0];
+          const worksheet = workbook.Sheets[firstSheetName];
+          const jsonData = XLSX.utils.sheet_to_json(worksheet);
+          
+          // 处理数据,确保字段匹配
+          this.data = jsonData.map(item => ({
+            factoryCode: item['工厂代码'] || item.factoryCode || '',
+            materialCode: item['物料编码'] || item.materialCode || '',
+            materialDescription: item['物料描述'] || item.materialDescription || '',
+            drawingNumber: item['图号'] || item.drawingNumber || '',
+            version: item['版本'] || item.version || '',
+            purchaseOrder: item['采购订单号'] || item.purchaseOrder || '',
+            orderLine: item['订单行'] || item.orderLine || '',
+            supplierCode: item['供应商代码'] || item.supplierCode || '',
+            buyer: item['采购员'] || item.buyer || '',
+            supplyType: item['供应类型'] || item.supplyType || '',
+            orderQuantity: parseFloat(item['订单数量'] || item.orderQuantity || 0),
+            receivedQuantity: parseFloat(item['入库数量'] || item.receivedQuantity || 0),
+            inTransitQuantity: parseFloat(item['在途数量'] || item.inTransitQuantity || 0),
+            pendingQuantity: 0, // 将在calculatePendingQuantity中计算
+            deliveryQuantity: parseFloat(item['交货数量'] || item.deliveryQuantity || 0),
+            deliveryDate: item['交货日期'] || item.deliveryDate || '',
+            supplierReplyQuantity: item['供应商回复数量'] || item.supplierReplyQuantity || null,
+            supplierReplyDate: item['供应商回复交期'] || item.supplierReplyDate || null,
+            remarks: item['备注'] || item.remarks || ''
+          }));
+          
+          this.calculatePendingQuantity();
+        } catch (error) {
+          this.errorMessage = 'Excel文件解析错误:' + error.message;
+          console.error('Excel parsing error:', error);
+        }
+      };
+      reader.readAsArrayBuffer(file);
+    },
+    
+    calculatePendingQuantity() {
+      this.data.forEach(item => {
+        item.pendingQuantity = item.orderQuantity - item.receivedQuantity - item.inTransitQuantity;
+      });
+    },
+    
+    formatDate(date) {
+      if (!date) return '';
+      // 检查是否已经是YYYY/MM/DD格式
+      if (date.includes('/')) return date;
+      
+      // 尝试从其他格式转换
+      try {
+        const d = new Date(date);
+        if (!isNaN(d.getTime())) {
+          return `${d.getFullYear()}/${String(d.getMonth() + 1).padStart(2, '0')}/${String(d.getDate()).padStart(2, '0')}`;
+        }
+      } catch (e) {
+        console.error('Date formatting error:', e);
+      }
+      return date;
+    },
+    
+    formatDateInput(index) {
+      const date = this.data[index].supplierReplyDate;
+      if (date) {
+        const d = new Date(date);
+        if (!isNaN(d.getTime())) {
+          this.data[index].supplierReplyDate = `${d.getFullYear()}/${String(d.getMonth() + 1).padStart(2, '0')}/${String(d.getDate()).padStart(2, '0')}`;
+        }
+      }
+    },
+    
+    startEditing(index) {
+      this.editingIndex = index;
+    },
+    
+    saveEditing(index) {
+      // 这里可以添加保存逻辑,比如发送API请求保存到后端
+      console.log('保存数据:', this.data[index]);
+      this.editingIndex = null;
+      alert('保存成功!');
+    },
+    
+    isEditing(index) {
+      return this.editingIndex === index;
+    }
+  }
+};
+</script>

+ 5 - 0
dingdan/src/main.js

@@ -0,0 +1,5 @@
+import { createApp } from 'vue'
+import './style.css'
+import App from './App.vue'
+
+createApp(App).mount('#app')

+ 112 - 0
dingdan/src/style.css

@@ -0,0 +1,112 @@
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+}
+
+body {
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+    sans-serif;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  background-color: #f5f5f5;
+}
+
+#app {
+  max-width: 1280px;
+  margin: 0 auto;
+  padding: 2rem;
+  text-align: center;
+}
+
+.container {
+  max-width: 1400px;
+  margin: 0 auto;
+  padding: 20px;
+}
+
+h1 {
+  margin-bottom: 20px;
+  color: #333;
+}
+
+.file-upload {
+  margin-bottom: 20px;
+  padding: 20px;
+  background-color: white;
+  border-radius: 8px;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.file-upload input {
+  margin-top: 10px;
+}
+
+.data-table {
+  background-color: white;
+  border-radius: 8px;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+  overflow-x: auto;
+  padding: 20px;
+}
+
+table {
+  width: 100%;
+  border-collapse: collapse;
+}
+
+th, td {
+  padding: 12px;
+  text-align: left;
+  border-bottom: 1px solid #ddd;
+}
+
+th {
+  background-color: #f2f2f2;
+  font-weight: 600;
+}
+
+tr:hover {
+  background-color: #f5f5f5;
+}
+
+input[type="number"], input[type="date"] {
+  width: 100%;
+  padding: 8px;
+  border: 1px solid #ddd;
+  border-radius: 4px;
+}
+
+button {
+  padding: 8px 16px;
+  background-color: #4CAF50;
+  color: white;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 14px;
+}
+
+button:hover {
+  background-color: #45a049;
+}
+
+button.save-btn {
+  background-color: #2196F3;
+}
+
+button.save-btn:hover {
+  background-color: #0b7dda;
+}
+
+.empty-state {
+  padding: 40px;
+  text-align: center;
+  color: #666;
+}
+
+.error-message {
+  color: red;
+  margin-top: 10px;
+}

+ 6 - 0
dingdan/vite.config.js

@@ -0,0 +1,6 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+
+export default defineConfig({
+  plugins: [vue()],
+})

+ 43 - 0
iqc/manage-vue/.gitignore

@@ -0,0 +1,43 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+# Environment variables
+.env
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+# Build outputs
+build/
+out/
+
+# Cache
+.cache/
+.parcel-cache/
+
+# Temporary files
+*.tmp
+*.temp

+ 86 - 0
iqc/manage-vue/ApplicationList鏂囨。.md

@@ -0,0 +1,86 @@
+# 来料检验申请列表页面文档
+
+## 页面基本信息
+- **页面名称**: 来料检验申请列表
+- **页面路径**: `/applications`
+- **组件文件**: `src/views/ApplicationList.vue`
+- **功能描述**: 展示所有来料检验申请单的列表,支持搜索、筛选、新建、编辑等操作
+- **目标用户**: 申请人、审核员、管理员
+
+## 页面布局设计
+```
+┌─────────────────────────────────────────────────────────────────┐
+│ 来料检验申请                                 [新建申请] [导入] [导出] │
+├─────────────────────────────────────────────────────────────────┤
+│ 搜索: [关键词输入框] [AND/OR切换] [搜索] [高级筛选]              │
+├─────────────────────────────────────────────────────────────────┤
+│ 单据编号 │ 申请人 │ 申请时间 │ 单据状态 │ 物料数 │ 备注 │ 操作    │
+├─────────────────────────────────────────────────────────────────┤
+│ IQC001 │ 张三 │ 2024-01-15 │ 已提交 │ 5 │ 紧急物料... │ [编辑][查看] │
+│ IQC002 │ 李四 │ 2024-01-14 │ 草稿   │ 3 │ 常规检验... │ [编辑][查看] │
+└─────────────────────────────────────────────────────────────────┘
+```
+
+## 功能模块
+### 1. 主要功能
+- [x] 申请单列表展示
+- [x] 搜索和筛选功能
+- [x] 新建申请单
+- [x] 编辑申请单
+- [x] 查看申请单详情
+- [x] 批量导入/导出
+- [x] 状态筛选
+
+### 2. 交互行为
+- **点击新建**: 跳转到申请单表单页面
+- **点击编辑**: 跳转到编辑模式
+- **点击查看**: 跳转到只读模式
+- **搜索功能**: 支持关键词搜索和高级筛选
+- **状态筛选**: 按申请单状态筛选显示
+
+### 3. 数据流
+- **输入数据**: 从API获取申请单列表数据
+- **输出数据**: 用户操作触发相应的API调用
+- **API调用**: 
+  - `GET /api/applications` - 获取申请单列表
+  - `DELETE /api/applications/:id` - 删除申请单
+  - `POST /api/applications/export` - 导出申请单
+
+## 组件依赖
+- **父组件**: `App.vue`
+- **子组件**: 
+  - `SearchFilter.vue` - 搜索筛选组件
+  - `TaskTable.vue` - 表格组件
+  - `StatusBadge.vue` - 状态标签组件
+  - `PermissionButton.vue` - 权限按钮组件
+- **共享组件**: `HeaderNav.vue`
+
+## 样式规范
+- **主色调**: #409EFF (Element Plus 主色)
+- **状态颜色**: 
+  - 草稿: #909399
+  - 已提交: #409EFF
+  - 已审批: #67C23A
+  - 已驳回: #F56C6C
+- **字体大小**: 14px (表格内容), 16px (标题)
+- **间距规范**: 16px (组件间距), 8px (按钮间距)
+
+## 开发注意事项
+- [x] 需要实现分页功能
+- [x] 需要处理权限控制
+- [x] 需要实现响应式设计
+- [x] 需要处理加载状态和错误状态
+- [x] 需要实现批量操作功能
+
+## 测试要点
+- [x] 列表数据正确显示
+- [x] 搜索功能正常工作
+- [x] 新建/编辑/查看功能正常
+- [x] 权限控制正确
+- [x] 响应式布局适配
+
+## 相关文档
+- [申请单表单页面](ApplicationForm.md)
+- [API接口文档](../03-开发文档/API接口文档.md)
+- [权限管理文档](../03-开发文档/权限管理.md)
+

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است