Parcourir la source

Initial commit: 清道夫微信小程序项目

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
xuyunhui il y a 1 mois
commit
b78f298291
100 fichiers modifiés avec 20362 ajouts et 0 suppressions
  1. 21 0
      .gitignore
  2. 422 0
      DEV_LOG.md
  3. 40 0
      emoji_report.txt
  4. 20 0
      index.html
  5. 9467 0
      package-lock.json
  6. 55 0
      package.json
  7. 10 0
      shims-uni.d.ts
  8. 18 0
      src/App.vue
  9. 36 0
      src/api/auth.ts
  10. 21 0
      src/api/dashboard.ts
  11. 29 0
      src/api/notice.ts
  12. 29 0
      src/api/project.ts
  13. 103 0
      src/api/request.ts
  14. 21 0
      src/api/schedule.ts
  15. 17 0
      src/api/staff.ts
  16. 46 0
      src/api/task.ts
  17. 13 0
      src/api/vehicle.ts
  18. 23 0
      src/components/common/AppContainer.vue
  19. 68 0
      src/components/common/BottomNav.vue
  20. 30 0
      src/components/common/Card.vue
  21. 206 0
      src/components/common/ChartView.vue
  22. 20 0
      src/components/common/EmptyState.vue
  23. 67 0
      src/components/common/FilterTabs.vue
  24. 86 0
      src/components/common/ImageUploader.vue
  25. 65 0
      src/components/common/Loading.vue
  26. 65 0
      src/components/common/MapView.vue
  27. 111 0
      src/components/common/Modal.vue
  28. 118 0
      src/components/common/SignaturePad.vue
  29. 47 0
      src/components/common/StatusTag.vue
  30. 99 0
      src/components/common/Toast.vue
  31. 42 0
      src/components/common/TopBar.vue
  32. 12 0
      src/components/index.ts
  33. 62 0
      src/components/project/ProjectCard.vue
  34. 59 0
      src/components/task/TaskCard.vue
  35. 89 0
      src/components/uni-card/uni-card.vue
  36. 204 0
      src/components/uni-list/uni-list-item.vue
  37. 44 0
      src/components/uni-list/uni-list.vue
  38. 107 0
      src/components/uni-segmented-control/uni-segmented-control.vue
  39. 144 0
      src/components/uni-swipe-action/uni-swipe-action.vue
  40. 8 0
      src/env.d.ts
  41. 17 0
      src/main.ts
  42. 72 0
      src/manifest.json
  43. 346 0
      src/mock/index.ts
  44. 414 0
      src/pages.json
  45. 237 0
      src/pages/common/addProject.vue
  46. 114 0
      src/pages/common/changePassword.vue
  47. 80 0
      src/pages/common/helpCenter.vue
  48. 72 0
      src/pages/common/knowledgeDetail.vue
  49. 103 0
      src/pages/common/knowledgeList.vue
  50. 60 0
      src/pages/common/messageDetail.vue
  51. 104 0
      src/pages/common/messageList.vue
  52. 88 0
      src/pages/common/noticeDetail.vue
  53. 132 0
      src/pages/common/noticeList.vue
  54. 140 0
      src/pages/common/profile.vue
  55. 136 0
      src/pages/common/projectDetail.vue
  56. 180 0
      src/pages/common/projectList.vue
  57. 501 0
      src/pages/common/publishTask.vue
  58. 265 0
      src/pages/common/taskDetail.vue
  59. 163 0
      src/pages/common/taskList.vue
  60. 86 0
      src/pages/construction/basicInfo.vue
  61. 85 0
      src/pages/construction/changePassword.vue
  62. 143 0
      src/pages/construction/dailyTask.vue
  63. 139 0
      src/pages/construction/helpCenter.vue
  64. 55 0
      src/pages/construction/historyTask.vue
  65. 199 0
      src/pages/construction/home.vue
  66. 149 0
      src/pages/construction/myPage.vue
  67. 77 0
      src/pages/construction/noticeDetail.vue
  68. 114 0
      src/pages/construction/noticeList.vue
  69. 65 0
      src/pages/construction/notification.vue
  70. 326 0
      src/pages/construction/taskDetail.vue
  71. 78 0
      src/pages/construction/taskList.vue
  72. 60 0
      src/pages/construction/tomorrowTask.vue
  73. 138 0
      src/pages/dispatch/availableVehicles.vue
  74. 59 0
      src/pages/dispatch/basicInfo.vue
  75. 120 0
      src/pages/dispatch/changePassword.vue
  76. 94 0
      src/pages/dispatch/dutyPersonnel.vue
  77. 66 0
      src/pages/dispatch/emergencyStandby.vue
  78. 103 0
      src/pages/dispatch/emergencyTeam.vue
  79. 76 0
      src/pages/dispatch/helpCenter.vue
  80. 177 0
      src/pages/dispatch/home.vue
  81. 120 0
      src/pages/dispatch/myPage.vue
  82. 47 0
      src/pages/dispatch/noticeDetail.vue
  83. 114 0
      src/pages/dispatch/noticeList.vue
  84. 65 0
      src/pages/dispatch/notification.vue
  85. 63 0
      src/pages/dispatch/onDutyStaff.vue
  86. 133 0
      src/pages/dispatch/personnel.vue
  87. 223 0
      src/pages/dispatch/publishTask.vue
  88. 256 0
      src/pages/dispatch/schedule.vue
  89. 255 0
      src/pages/dispatch/taskDetail.vue
  90. 93 0
      src/pages/dispatch/taskList.vue
  91. 133 0
      src/pages/dispatch/vehicleList.vue
  92. 148 0
      src/pages/dispatch/visualization.vue
  93. 39 0
      src/pages/index/index.vue
  94. 153 0
      src/pages/login/login.vue
  95. 83 0
      src/pages/sales/basicInfo.vue
  96. 120 0
      src/pages/sales/changePassword.vue
  97. 124 0
      src/pages/sales/helpCenter.vue
  98. 210 0
      src/pages/sales/home.vue
  99. 90 0
      src/pages/sales/knowledgeDetail.vue
  100. 116 0
      src/pages/sales/knowledgeList.vue

+ 21 - 0
.gitignore

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

+ 422 - 0
DEV_LOG.md

@@ -0,0 +1,422 @@
+# 清道夫App 开发文档
+
+> 项目: 清道夫系统 (环境服务管理平台)
+> 技术栈: uni-app + Vue 3 + Vite + TypeScript + Tailwind CSS + Pinia
+> 创建时间: 2026-06-22
+> 原型参考: `清道夫系统原型_完整版_V21_SOP.html`
+
+---
+
+## 一、项目结构
+
+```
+清道夫App/
+├── src/
+│   ├── api/              # 后端API接口
+│   ├── components/       # 公共组件
+│   │   ├── common/       # 通用组件
+│   │   │   ├── BottomNav.vue
+│   │   │   ├── TopBar.vue
+│   │   │   ├── EmptyState.vue
+│   │   │   └── StatusTag.vue
+│   │   ├── uni-list/     # 列表组件 (小程序风格)
+│   │   │   ├── uni-list.vue
+│   │   │   └── uni-list-item.vue
+│   │   ├── uni-card/     # 卡片组件 (小程序风格)
+│   │   │   └── uni-card.vue
+│   │   ├── uni-segmented-control/  # 分段器/Tab
+│   │   │   └── uni-segmented-control.vue
+│   │   ├── uni-swipe-action/       # 左滑操作
+│   │   │   └── uni-swipe-action.vue
+│   │   └── task/
+│   │       └── TaskCard.vue
+│   ├── pages/            # 页面
+│   ├── stores/           # Pinia状态管理
+│   ├── styles/           # 样式文件
+│   │   ├── global.scss
+│   │   ├── variables.scss
+│   │   ├── tailwind.css
+│   │   └── uni-icons.css # uni-icons字体图标
+│   ├── static/fonts/     # 字体文件
+│   │   └── uniicons.ttf
+│   ├── types/            # TypeScript类型
+│   ├── utils/            # 工具函数
+│   ├── App.vue
+│   ├── main.ts
+│   └── pages.json
+├── package.json
+├── vite.config.ts
+├── tailwind.config.js
+└── tsconfig.json
+```
+
+---
+
+## 二、开发规范
+
+### 2.1 样式规范
+
+- **不使用 emoji 表情符号**,统一使用 `uni-icons` 字体图标
+- 小程序优先设计,避免使用 Web 专属特性
+- 移动端最大宽度 430px,居中显示
+- 圆角统一使用 `12px` (卡片) / `8px` (按钮) / `20px` (标签)
+- 阴影统一使用 `0 2px 8px rgba(0,0,0,0.06)`
+
+### 2.2 图标使用 (uni-icons)
+
+在任意页面/组件中使用:
+```html
+<text class="uni-icons uniui-home-filled text-blue-500 text-lg"></text>
+```
+
+常用图标对照:
+| 功能 | 类名 |
+|------|------|
+| 首页 | `uniui-home-filled` |
+| 用户/人员 | `uniui-person-filled` |
+| 通知/铃铛 | `uniui-notification-filled` |
+| 设置/齿轮 | `uniui-gear-filled` |
+| 搜索 | `uniui-search` |
+| 返回 | `uniui-back` |
+| 添加 | `uniui-plusempty` |
+| 删除 | `uniui-trash` |
+| 完成/勾选 | `uniui-checkmarkempty` |
+| 关闭 | `uniui-close` |
+| 电话 | `uniui-phone-filled` |
+| 日历 | `uniui-calendar-filled` |
+| 地图/位置 | `uniui-location-filled` |
+| 车辆 | `uniui-cart-filled` |
+| 文件夹/项目 | `uniui-folder-add-filled` |
+| 任务/旗帜 | `uniui-flag-filled` |
+| 消息/聊天 | `uniui-chatbubble-filled` |
+| 眼睛/查看 | `uniui-eye-filled` |
+| 锁定 | `uniui-locked-filled` |
+| 上传/相机 | `uniui-camera-filled` |
+| 帮助 | `uniui-help-filled` |
+| 更多/箭头 | `uniui-arrowright` |
+
+### 2.3 组件使用规范
+
+#### uni-list / uni-list-item
+```html
+<uni-list border>
+  <uni-list-item 
+    title="标题"
+    note="描述信息"
+    right-text="右侧文字"
+    show-arrow
+    @click="onClick"
+  >
+    <template #header>
+      <text class="uni-icons uniui-person-filled"></text>
+    </template>
+  </uni-list-item>
+</uni-list>
+```
+
+#### uni-card
+```html
+<uni-card title="卡片标题" extra="额外信息" icon="uniui-flag-filled">
+  <view>卡片内容</view>
+  <template #footer>
+    <button>操作按钮</button>
+  </template>
+</uni-card>
+```
+
+#### uni-segmented-control (顶部Tab栏)
+```html
+<uni-segmented-control 
+  :current="currentIndex" 
+  :values="['全部', '待办', '已完成']"
+  style-type="text"
+  active-color="#4f8ef7"
+  @clickItem="onTabChange"
+/>
+```
+
+#### uni-swipe-action (左滑删除)
+```html
+<uni-swipe-action :actions="[{text:'删除', color:'#ff3b30'}]" @click="onDelete">
+  <uni-list-item title="标题" note="描述" />
+</uni-swipe-action>
+```
+
+### 2.4 命名规范
+
+- 页面文件名: 小驼峰,如 `projectList.vue`
+- 组件名: 大驼峰,如 `BottomNav.vue`
+- Store: 小驼峰,如 `auth.ts`
+- API 函数: 小驼峰,如 `getProjectList`
+- 类型定义: 大驼峰,如 `TaskStatus`
+
+---
+
+## 三、原型页面清单 (54页)
+
+> 来源: `原型功能清单_完整详细版(含登录).md`
+
+### 3.1 登录模块 (1页)
+
+| 页面ID | 页面名称 | 状态 | 备注 |
+|--------|---------|------|------|
+| loginPage | 清道夫系统登录 | ✅ | 账号密码 + 角色切换 |
+
+### 3.2 销售端 - SalesApp (21页)
+
+| # | 页面ID | 页面名称 | 状态 | 备注 |
+|---|--------|---------|------|------|
+| 1 | salesPage | 首页 | ✅ | 统计卡片 + 快捷入口 |
+| 2 | taskPage | 我的任务 | ✅ | 状态筛选 + 列表 |
+| 3 | profilePage | 个人中心 | ✅ | 用户信息 + 菜单 |
+| 4 | sales_basicInfoPage | 基本信息 | ✅ | 已重构为uni-card+uni-list |
+| 5 | sales_changePasswordPage | 修改密码 | ✅ | 已重构为uni-card |
+| 6 | sales_notificationPage | 消息通知 | ✅ | 分类: 任务/系统 |
+| 7 | sales_helpCenterPage | 帮助中心 | ✅ | 共用组件 |
+| 8 | sales_taskReminderPage | 任务提醒 | ✅ | 提醒列表 |
+| 9 | projectListPage | 项目库 | ✅ | 筛选: 全部/季度/包年/月付/单次 |
+| 10 | addProjectPage | 新增项目 | ✅ | 完整表单 |
+| 11 | projectDetailPage | 项目详情 | ✅ | 信息区块展示 |
+| 12 | messageListPage | 消息通知列表 | ✅ | 分类筛选 + 左滑删除 |
+| 13 | messageDetailPage | 消息详情 | ✅ | 标记已读 + 关联任务 |
+| 14 | changePasswordPage | 修改密码(通用) | ✅ | 可复用组件 |
+| 15 | helpCenterPage | 帮助中心(通用) | ✅ | FAQ + 指南 |
+| 16 | noticeListPage | 通知公告列表 | ✅ | 列表展示 |
+| 17 | noticeDetailPage | 公告详情 | ✅ | 详情展示 |
+| 18 | knowledgeListPage | 行业知识列表 | ✅ | 分类 + 搜索 |
+| 19 | knowledgeDetailPage | 知识详情 | ✅ | 内容 + 附件 |
+| 20 | publishTaskPage | 发布任务 | ✅ | 完整表单 + 图片上传 |
+| 21 | taskDetailPage | 任务详情 | ✅ | 进度条 + 时间线 + SOP |
+
+### 3.3 调度端 - DispatchApp (19页)
+
+| # | 页面ID | 页面名称 | 状态 | 备注 |
+|---|--------|---------|------|------|
+| 1 | dispatch_homePage | 首页 | ✅ | 统计 + 资源状态 |
+| 2 | dispatch_schedulePage | 排班管理 | ✅ | 日历 + 排班列表 |
+| 3 | dispatch_taskPage | 任务管理 | ✅ | 筛选: 全部/进行中/已完成/待审核/待排班/已驳回 |
+| 4 | dispatch_myPage | 个人中心 | ✅ | 共用profile |
+| 5 | dispatch_basicInfoPage | 基本信息 | ✅ | 已重构为uni-card+uni-list |
+| 6 | dispatch_changePasswordPage | 修改密码 | ✅ | 已重构为uni-card |
+| 7 | dispatch_helpCenterPage | 帮助中心 | ✅ | 共用 |
+| 8 | dispatch_noticeListPage | 通知公告 | ✅ | 共用 |
+| 9 | dispatch_noticeDetailPage | 公告详情 | ✅ | 共用 |
+| 10 | dispatch_onDutyStaffPage | 在岗人员 | ✅ | 人员状态展示 |
+| 11 | dispatch_availableVehiclesPage | 可用车辆 | ✅ | 状态筛选 + 统计卡片 |
+| 12 | dispatch_emergencyStandbyPage | 应急待命 | ✅ | 应急班组 |
+| 13 | dispatch_publishTaskPage | 发布任务 | ✅ | 完整表单 |
+| 14 | dispatch_taskDetailPage | 任务详情 | ✅ | 调度审核/安排/驳回 |
+| 15 | dispatch_personnelPage | 人员列表 | ✅ | 状态筛选 |
+| 16 | dispatch_vehicleListPage | 车辆列表 | ✅ | 状态筛选 |
+| 17 | dispatch_emergencyTeamPage | 应急班组 | ✅ | 班组信息 |
+| 18 | dispatch_notificationPage | 消息通知 | ✅ | 分类: 全部/审核结果/任务分配/系统 |
+| 19 | dispatch_visualizationPage | 可视化大屏 | ✅ | 统计图表 + 排班 |
+
+### 3.4 施工端 - ConstructionApp (13页)
+
+| # | 页面ID | 页面名称 | 状态 | 备注 |
+|---|--------|---------|------|------|
+| 1 | construction_homePage | 首页 | ✅ | 统计 + 快捷入口 |
+| 2 | construction_dailyTaskPage | 每日任务 | ✅ | 车辆检查清单 |
+| 3 | construction_taskPage | 我的任务 | ✅ | 筛选: 待出车/已出车/施工中/待验收/待确认 |
+| 4 | construction_taskDetailPage | 任务详情 | ✅ | SOP流程: 出车→现场→勘验→施工 |
+| 5 | construction_myPage | 个人中心 | ✅ | 共用profile |
+| 6 | construction_basicInfoPage | 个人信息 | ✅ | 已重构为uni-card+uni-list |
+| 7 | construction_changePasswordPage | 修改密码 | ✅ | 已重构为uni-card |
+| 8 | construction_helpCenterPage | 帮助中心 | ✅ | 共用 |
+| 9 | construction_noticeListPage | 通知公告 | ✅ | 共用 |
+| 10 | construction_noticeDetailPage | 公告详情 | ✅ | 共用 |
+| 11 | construction_notificationPage | 消息通知 | ✅ | 分类: 系统/任务/全部 |
+| 12 | construction_historyTaskPage | 历史任务 | ✅ | 已完成任务列表 |
+| 13 | construction_tomorrowTaskPage | 明日任务 | ✅ | 预约/取消/催单 |
+
+---
+
+## 四、开发进度总览
+
+### 4.1 基础架构 ✅ 已完成
+
+| 模块 | 功能 | 状态 | 备注 |
+|------|------|------|------|
+| 项目初始化 | uni-app + Vue3 + Vite + TS | ✅ | 2026-06-22 |
+| 样式配置 | Tailwind CSS + 全局样式 | ✅ | 2026-06-22 |
+| 后端对接 | request封装 + 所有API模块 | ✅ | 2026-06-22 |
+| Pinia Store | auth/project/task/notification | ✅ | 2026-06-22 |
+| 公共组件 | BottomNav/TopBar/EmptyState/StatusTag | ✅ | 2026-06-22 |
+| uni-icons | 字体图标CSS + ttf文件 | ✅ | 2026-06-22 |
+| emoji清理 | 38个文件全部清理 | ✅ | 2026-06-22 |
+| uni-list | 列表组件 + 列表项 | ✅ | 2026-06-22 |
+| uni-card | 卡片组件 | ✅ | 2026-06-22 |
+| uni-segmented-control | 顶部Tab分段器 | ✅ | 2026-06-22 |
+| uni-swipe-action | 左滑操作组件 | ✅ | 2026-06-22 |
+
+### 4.2 阶段一:页面重构 (✅ 已完成)
+
+将已有页面重构为小程序风格 (uni-card/uni-list/uni-icons):
+
+| 页面 | 状态 | 备注 |
+|------|------|------|
+| sales/home.vue | ✅ | uni-card统计卡片 + uni-icons快捷入口 |
+| dispatch/home.vue | ✅ | uni-card + uni-list |
+| construction/home.vue | ✅ | uni-card + uni-icons |
+| common/projectList.vue | ✅ | uni-segmented-control + uni-list + uni-swipe-action |
+| common/taskList.vue | ✅ | uni-segmented-control + uni-list |
+| dispatch/personnel.vue | ✅ | uni-segmented-control + uni-list |
+| dispatch/vehicleList.vue | ✅ | uni-segmented-control + uni-list |
+| common/noticeList.vue | ✅ | uni-segmented-control + uni-list + uni-swipe-action |
+| common/profile.vue | ✅ | uni-card + uni-list + uni-icons |
+| common/taskDetail.vue | ✅ | uni-card布局 + 进度时间轴 |
+| common/projectDetail.vue | ✅ | uni-card列表 + 服务点 |
+| common/addProject.vue | ✅ | uni-card表单区块 + uni-icons |
+| dispatch/schedule.vue | ✅ | 日历 + uni-list排班 |
+| sales/taskReminder.vue | ✅ | uni-card + uni-icons |
+| dispatch/visualization.vue | ✅ | uni-card统计 + uni-list |
+| construction/dailyTask.vue | ✅ | uni-card + uni-list检查项 |
+| construction/tomorrowTask.vue | ✅ | uni-list |
+| construction/historyTask.vue | ✅ | uni-list |
+| dispatch/dutyPersonnel.vue | ✅ | uni-segmented-control + uni-list |
+| dispatch/emergencyTeam.vue | ✅ | uni-card统计 + uni-list |
+| sales/notification.vue | ✅ | uni-list + 未读红点 |
+| dispatch/notification.vue | ✅ | uni-list + 未读红点 |
+| construction/notification.vue | ✅ | uni-list + 未读红点 |
+| construction/helpCenter.vue | ✅ | uni-card + uni-list + uni-icons |
+| **阶段一完成** | ✅ | **24个页面全部重构完成** |
+
+### 4.3 阶段二:补充缺失页面 (✅ 已完成)
+
+根据原型清单,以下页面**已补充/重构**:
+
+| 优先级 | 页面 | 所属角色 | 状态 | 备注 |
+|--------|------|---------|------|------|
+| 🔴 高 | sales_basicInfoPage | 销售端 | ✅ | 基本信息查看 uni-card |
+| 🔴 高 | sales_changePasswordPage | 销售端 | ✅ | 修改密码 uni-card |
+| 🔴 高 | dispatch_basicInfoPage | 调度端 | ✅ | 基本信息查看 uni-card |
+| 🔴 高 | dispatch_changePasswordPage | 调度端 | ✅ | 修改密码 uni-card |
+| 🔴 高 | construction_basicInfoPage | 施工端 | ✅ | 个人信息编辑 uni-card |
+| 🔴 高 | construction_changePasswordPage | 施工端 | ✅ | 修改密码 uni-card |
+| 🟡 中 | messageListPage | 销售端 | ✅ | 消息分类筛选 + 左滑删除 |
+| 🟡 中 | messageDetailPage | 销售端 | ✅ | 消息详情 + 标记已读 |
+| 🟡 中 | knowledgeListPage | 销售端 | ✅ | 行业知识分类 + 搜索 |
+| 🟡 中 | knowledgeDetailPage | 销售端 | ✅ | 知识内容 + 附件下载 |
+| 🟡 中 | dispatch_publishTaskPage | 调度端 | ✅ | 调度发布任务表单 |
+| 🟡 中 | dispatch_availableVehiclesPage | 调度端 | ✅ | 可用车辆筛选 + 统计 |
+
+**阶段二完成:12个缺失页面全部补充完成!**
+
+### 4.4 阶段三:高级功能 (✅ 已完成)
+
+| 功能 | 优先级 | 技术方案 | 状态 | 备注 |
+|------|--------|---------|------|------|
+| 图片/视频上传 | 🔴 高 | `uni.chooseImage` + 自定义ImageUploader组件 | ✅ | 任务详情/发布任务页面集成 |
+| 地图定位 | 🟡 中 | `<map>`组件 + `uni.openLocation` | ✅ | 任务详情展示项目位置 |
+| 电子签名 | 🟡 中 | Canvas手写签名 + `uni.canvasToTempFilePath` | ✅ | 施工完成客户签字 |
+| 图表可视化 | 🟢 低 | 自定义Canvas图表组件(饼图/柱状图/折线图) | ✅ | 可视化大屏页面集成 |
+| 实时定位 | 🟢 低 | `uni.getLocation` | ⏳ 待接入真实设备 | 框架已就绪 |
+| 消息推送 | 🟢 低 | `uni-push` | ⏳ 需后端配合 | 框架已就绪 |
+
+### 4.5 阶段三新增组件
+
+| 组件 | 路径 | 功能 | 使用页面 |
+|------|------|------|---------|
+| ImageUploader | `components/common/ImageUploader.vue` | 图片选择/预览/删除 | taskDetail, publishTask |
+| MapView | `components/common/MapView.vue` | 地图展示/导航 | taskDetail |
+| SignaturePad | `components/common/SignaturePad.vue` | Canvas手写签名 | taskDetail |
+| ChartView | `components/common/ChartView.vue` | 饼图/柱状图/折线图 | visualization |
+
+---
+
+## 五、后端接口对接
+
+### 5.1 基础信息
+
+- **Base URL:** `http://localhost:8080/api`
+- **Auth Header:** `Bearer {token}`
+- **Response Format:** `{ code: 200, msg: "success", data: T, timestamp }`
+
+### 5.2 接口列表
+
+| 模块 | 接口 | 方法 | 说明 |
+|------|------|------|------|
+| 认证 | /auth/login | POST | 账号密码登录 |
+| 项目 | /project/list | GET | 项目列表 |
+| 项目 | /project/{id} | GET | 项目详情 |
+| 项目 | /project | POST | 新增项目 |
+| 任务 | /task/list | GET | 任务列表 |
+| 任务 | /task/{id} | GET | 任务详情 |
+| 任务 | /task/{id}/status | PUT | 更新任务状态 |
+| 车辆 | /vehicle/list | GET | 车辆列表 |
+| 人员 | /staff/list | GET | 人员列表 |
+| 排班 | /schedule/list | GET | 排班列表 |
+| 公告 | /notice/list | GET | 公告列表 |
+| 统计 | /dashboard/stats | GET | 统计数据 |
+
+### 5.3 角色映射
+
+| 前端角色 | 后端 roleType | 说明 |
+|----------|--------------|------|
+| sales | front | 前端销售 |
+| dispatch | dispatch | 调度管理员 |
+| construction | construction | 施工人员 |
+
+---
+
+## 六、已知问题与注意事项
+
+### 6.1 样式问题
+
+1. **Tailwind CSS 编译** - 已解决:PostCSS 配置移至 vite.config.ts
+2. **小程序不支持 `*` 选择器** - global.scss 中避免使用
+3. **uni-icons 字体** - 已配置 @font-face,全局可用
+
+### 6.2 兼容性问题
+
+1. **uni.navigateTo** 页面栈限制为 10 层 - 首页使用 `uni.reLaunch`
+2. **图片上传** - 使用 `uni.chooseImage` 而非原生 input
+3. **本地存储** - 使用 `uni.setStorageSync` 而非 localStorage
+
+### 6.3 性能优化
+
+1. 列表页使用分页加载
+2. 图片使用懒加载
+3. 避免在 v-for 中使用复杂表达式
+
+---
+
+## 七、Git 提交规范
+
+```
+feat: 新增功能
+fix: 修复bug
+docs: 文档更新
+style: 样式调整
+refactor: 代码重构
+perf: 性能优化
+test: 测试相关
+chore: 构建/工具相关
+```
+
+---
+
+## 八、开发人员
+
+- 开发: AI Assistant
+- 确认: Ethan
+
+---
+
+## 九、Bug修复记录
+
+### 2026-06-22 用户反馈修复
+
+| # | 问题 | 原因 | 修复方案 | 状态 |
+|---|------|------|---------|------|
+| 1 | 底部TabBar图标显示为文字 | 缺少 `uni-icons` 字体类名 | `BottomNav.vue` 添加 `class="uni-icons"` | ✅ 已修复 |
+| 2 | 退出登录不生效 | 仅调用store.logout,未跳转页面 | `profile.vue` 添加 `uni.reLaunch` 跳转到登录页 | ✅ 已修复 |
+| 3 | 缺少顶部操作栏 | 部分子页面缺少 `TopBar` 组件 | `projectList.vue`、`profile.vue` 添加 `TopBar` | ✅ 已修复 |
+
+---
+
+*最后更新: 2026-06-22*
+*原型页面总数: 54页 | 全部实现: ✅ 54/54*
+*阶段一(页面重构): ✅ 完成 | 阶段二(补充页面): ✅ 完成 | 阶段三(高级功能): ✅ 完成*
+*高级组件: ImageUploader / MapView / SignaturePad / ChartView | 实时定位/消息推送框架就绪*

+ 40 - 0
emoji_report.txt

@@ -0,0 +1,40 @@
+Files with emoji/symbols: 38
+
+components\common\Toast.vue: ●, ✓, ✗
+components\task\TaskCard.vue: 👷, 📍, 🚗
+pages\common\helpCenter.vue: ▼, ✉, ❓, 🎧, 📞, 🕐
+pages\common\knowledgeDetail.vue: 🏷, 📈, 📋, 📖, 📚, 🔧, 🕐, 🛡
+pages\common\knowledgeList.vue: ✅, 💡, 📄, 📊, 🤝
+pages\common\messageDetail.vue: 🕐
+pages\common\noticeList.vue: 👁
+pages\common\profile.vue: ⚙, ❓, 👤
+pages\common\projectDetail.vue: 👤, 📅, 📋, 📍, 📞
+pages\common\projectList.vue: 👤, 📍, 🔍
+pages\common\publishTask.vue: ▼, ☁, ✅, 🏢, 🔧, 🗺
+pages\common\taskDetail.vue: 👤, 👷, 📍, 📞, 🚗
+pages\construction\dailyTask.vue: ✅, ✓
+pages\construction\helpCenter.vue: ▶, ▼, ⛑, 📋, 📞, 📧, 🕐, 🚛
+pages\construction\home.vue: ⛑, ✅, ❓, 🏠, 👤, 📋, 📚, 🔔, 🚛
+pages\construction\myPage.vue: ❓, 🏠, 👤, 👥, 📋, 📞, 🔒, 🔔
+pages\construction\noticeDetail.vue: 👤, 📢, 🕐
+pages\construction\noticeList.vue: 👁
+pages\construction\taskDetail.vue: ○, ●, ✅, ✓, ❌, 🎉, 💬, 📅, 📋, 📍, 🔧, 🚀, 🚗
+pages\dispatch\availableVehicles.vue: 👤, 🚛
+pages\dispatch\basicInfo.vue: 👤
+pages\dispatch\dutyPersonnel.vue: 🏗, 👤, 📞
+pages\dispatch\emergencyStandby.vue: 👷, 📞, 🚨
+pages\dispatch\helpCenter.vue: ▲, ▼, ❓, 📄, 📚
+pages\dispatch\home.vue: 🏠, 👤, 📅, 📊, 📋, 🔔, 🚛
+pages\dispatch\myPage.vue: ❓, 🎖, 🏠, 🏢, 👤, 📋, 📞, 🔒, 🔔
+pages\dispatch\noticeList.vue: 👁
+pages\dispatch\onDutyStaff.vue: 👷
+pages\dispatch\personnel.vue: 🏗, 👤, 📞
+pages\dispatch\publishTask.vue: ▼, ✅, 🏢, 📅, 🔧, 🚧
+pages\dispatch\schedule.vue: 👤, 🚗
+pages\dispatch\taskDetail.vue: ▼, ⚠, 👤, 👥, 📅, 📋, 📍, 🔧, 🚛
+pages\dispatch\vehicleList.vue: 👤, 🚗
+pages\login\login.vue: ✓, 👤, 🔐, 🧹
+pages\sales\basicInfo.vue: ✏, 🎖, 🏢, 👤, 📅, 📧, 📱
+pages\sales\helpCenter.vue: ▲, ▼, ✉, ❓, 🎧, 📄, 📚, 📞, 🕐
+pages\sales\home.vue: 🏠, 👤, 📁, 📋, 📢, 🔔
+pages\sales\taskReminder.vue: 📍

+ 20 - 0
index.html

@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="UTF-8" />
+    <script>
+      var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
+        CSS.supports('top: constant(a)'))
+      document.write(
+        '<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
+        (coverSupport ? ', viewport-fit=cover' : '') + '" />')
+    </script>
+    <title></title>
+    <!--preload-links-->
+    <!--app-context-->
+  </head>
+  <body>
+    <div id="app"><!--app-html--></div>
+    <script type="module" src="/src/main.ts"></script>
+  </body>
+</html>

+ 9467 - 0
package-lock.json

@@ -0,0 +1,9467 @@
+{
+  "name": "清道夫App",
+  "version": "1.0.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "清道夫App",
+      "version": "1.0.0",
+      "dependencies": {
+        "@dcloudio/uni-app": "3.0.0-4080420251103001",
+        "@dcloudio/uni-app-harmony": "3.0.0-4080420251103001",
+        "@dcloudio/uni-app-plus": "3.0.0-4080420251103001",
+        "@dcloudio/uni-components": "3.0.0-4080420251103001",
+        "@dcloudio/uni-h5": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-alipay": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-baidu": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-harmony": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-jd": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-kuaishou": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-lark": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-qq": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-toutiao": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-weixin": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-xhs": "3.0.0-4080420251103001",
+        "@dcloudio/uni-quickapp-webview": "3.0.0-4080420251103001",
+        "pinia": "^2.3.1",
+        "uni-ui": "^1.0.0",
+        "vue": "^3.4.21",
+        "vue-i18n": "^9.1.9"
+      },
+      "devDependencies": {
+        "@dcloudio/types": "^3.4.8",
+        "@dcloudio/uni-automator": "3.0.0-4080420251103001",
+        "@dcloudio/uni-cli-shared": "3.0.0-4080420251103001",
+        "@dcloudio/uni-stacktracey": "3.0.0-4080420251103001",
+        "@dcloudio/vite-plugin-uni": "3.0.0-4080420251103001",
+        "@types/node": "^26.0.0",
+        "@vue/runtime-core": "^3.4.21",
+        "@vue/tsconfig": "^0.1.3",
+        "autoprefixer": "^10.5.0",
+        "postcss": "^8.5.15",
+        "sass": "^1.101.0",
+        "tailwindcss": "^3.4.19",
+        "typescript": "^4.9.4",
+        "vite": "5.2.8",
+        "vue-tsc": "^1.0.24"
+      }
+    },
+    "node_modules/@alloc/quick-lru": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmmirror.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+      "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/@ampproject/remapping": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.3.0.tgz",
+      "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@jridgewell/gen-mapping": "^0.3.5",
+        "@jridgewell/trace-mapping": "^0.3.24"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@babel/code-frame": {
+      "version": "7.24.7",
+      "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.24.7.tgz",
+      "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/highlight": "^7.24.7",
+        "picocolors": "^1.0.0"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/compat-data": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.7.tgz",
+      "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/core": {
+      "version": "7.25.2",
+      "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.25.2.tgz",
+      "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==",
+      "license": "MIT",
+      "dependencies": {
+        "@ampproject/remapping": "^2.2.0",
+        "@babel/code-frame": "^7.24.7",
+        "@babel/generator": "^7.25.0",
+        "@babel/helper-compilation-targets": "^7.25.2",
+        "@babel/helper-module-transforms": "^7.25.2",
+        "@babel/helpers": "^7.25.0",
+        "@babel/parser": "^7.25.0",
+        "@babel/template": "^7.25.0",
+        "@babel/traverse": "^7.25.2",
+        "@babel/types": "^7.25.2",
+        "convert-source-map": "^2.0.0",
+        "debug": "^4.1.0",
+        "gensync": "^1.0.0-beta.2",
+        "json5": "^2.2.3",
+        "semver": "^6.3.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/babel"
+      }
+    },
+    "node_modules/@babel/generator": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.29.7.tgz",
+      "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/parser": "^7.29.7",
+        "@babel/types": "^7.29.7",
+        "@jridgewell/gen-mapping": "^0.3.12",
+        "@jridgewell/trace-mapping": "^0.3.28",
+        "jsesc": "^3.0.2"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/generator/node_modules/@babel/parser": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.7.tgz",
+      "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/types": "^7.29.7"
+      },
+      "bin": {
+        "parser": "bin/babel-parser.js"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@babel/generator/node_modules/@babel/types": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.7.tgz",
+      "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.29.7",
+        "@babel/helper-validator-identifier": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-annotate-as-pure": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz",
+      "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/types": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-annotate-as-pure/node_modules/@babel/types": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.7.tgz",
+      "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.29.7",
+        "@babel/helper-validator-identifier": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-compilation-targets": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
+      "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/compat-data": "^7.29.7",
+        "@babel/helper-validator-option": "^7.29.7",
+        "browserslist": "^4.24.0",
+        "lru-cache": "^5.1.1",
+        "semver": "^6.3.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-create-class-features-plugin": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz",
+      "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-annotate-as-pure": "^7.29.7",
+        "@babel/helper-member-expression-to-functions": "^7.29.7",
+        "@babel/helper-optimise-call-expression": "^7.29.7",
+        "@babel/helper-replace-supers": "^7.29.7",
+        "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7",
+        "@babel/traverse": "^7.29.7",
+        "semver": "^6.3.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0"
+      }
+    },
+    "node_modules/@babel/helper-create-regexp-features-plugin": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.29.7.tgz",
+      "integrity": "sha512-907Uymvqgg1dwUA+7IGwFAOSYzQOuzPXKNJ1yxzwPffzkYFg2q2eHi1fIOs6sXkG9NbIUMunnUlkYsfRFNvomg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-annotate-as-pure": "^7.29.7",
+        "regexpu-core": "^6.3.1",
+        "semver": "^6.3.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0"
+      }
+    },
+    "node_modules/@babel/helper-define-polyfill-provider": {
+      "version": "0.6.8",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz",
+      "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-compilation-targets": "^7.28.6",
+        "@babel/helper-plugin-utils": "^7.28.6",
+        "debug": "^4.4.3",
+        "lodash.debounce": "^4.0.8",
+        "resolve": "^1.22.11"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+      }
+    },
+    "node_modules/@babel/helper-define-polyfill-provider/node_modules/debug": {
+      "version": "4.4.3",
+      "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz",
+      "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@babel/helper-define-polyfill-provider/node_modules/resolve": {
+      "version": "1.22.12",
+      "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.12.tgz",
+      "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "is-core-module": "^2.16.1",
+        "path-parse": "^1.0.7",
+        "supports-preserve-symlinks-flag": "^1.0.0"
+      },
+      "bin": {
+        "resolve": "bin/resolve"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/@babel/helper-globals": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
+      "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-member-expression-to-functions": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz",
+      "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/traverse": "^7.29.7",
+        "@babel/types": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-member-expression-to-functions/node_modules/@babel/types": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.7.tgz",
+      "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.29.7",
+        "@babel/helper-validator-identifier": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-module-imports": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
+      "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/traverse": "^7.29.7",
+        "@babel/types": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-module-imports/node_modules/@babel/types": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.7.tgz",
+      "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.29.7",
+        "@babel/helper-validator-identifier": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-module-transforms": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
+      "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-module-imports": "^7.29.7",
+        "@babel/helper-validator-identifier": "^7.29.7",
+        "@babel/traverse": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0"
+      }
+    },
+    "node_modules/@babel/helper-optimise-call-expression": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz",
+      "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/types": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-optimise-call-expression/node_modules/@babel/types": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.7.tgz",
+      "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.29.7",
+        "@babel/helper-validator-identifier": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-plugin-utils": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
+      "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-remap-async-to-generator": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.29.7.tgz",
+      "integrity": "sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-annotate-as-pure": "^7.29.7",
+        "@babel/helper-wrap-function": "^7.29.7",
+        "@babel/traverse": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0"
+      }
+    },
+    "node_modules/@babel/helper-replace-supers": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz",
+      "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-member-expression-to-functions": "^7.29.7",
+        "@babel/helper-optimise-call-expression": "^7.29.7",
+        "@babel/traverse": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0"
+      }
+    },
+    "node_modules/@babel/helper-skip-transparent-expression-wrappers": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz",
+      "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/traverse": "^7.29.7",
+        "@babel/types": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-skip-transparent-expression-wrappers/node_modules/@babel/types": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.7.tgz",
+      "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.29.7",
+        "@babel/helper-validator-identifier": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-string-parser": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
+      "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-validator-identifier": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
+      "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-validator-option": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
+      "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-wrap-function": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-wrap-function/-/helper-wrap-function-7.29.7.tgz",
+      "integrity": "sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/template": "^7.29.7",
+        "@babel/traverse": "^7.29.7",
+        "@babel/types": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-wrap-function/node_modules/@babel/types": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.7.tgz",
+      "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.29.7",
+        "@babel/helper-validator-identifier": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helpers": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.29.7.tgz",
+      "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/template": "^7.29.7",
+        "@babel/types": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helpers/node_modules/@babel/types": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.7.tgz",
+      "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.29.7",
+        "@babel/helper-validator-identifier": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/highlight": {
+      "version": "7.25.9",
+      "resolved": "https://registry.npmmirror.com/@babel/highlight/-/highlight-7.25.9.tgz",
+      "integrity": "sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-validator-identifier": "^7.25.9",
+        "chalk": "^2.4.2",
+        "js-tokens": "^4.0.0",
+        "picocolors": "^1.0.0"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/parser": {
+      "version": "7.25.6",
+      "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.25.6.tgz",
+      "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/types": "^7.25.6"
+      },
+      "bin": {
+        "parser": "bin/babel-parser.js"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.29.7.tgz",
+      "integrity": "sha512-j8SrR0zLZrRsC09DlszEx8FpMiwukKffYXMK0d5LmOglO7vGG6sz/BR/20yHqWH+Lnn31JTt2PE3hIWNgM2J6w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7",
+        "@babel/traverse": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0"
+      }
+    },
+    "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.29.7.tgz",
+      "integrity": "sha512-r8j8escF+U2FUHo0KOhPUdMzUO+jp9fInva6+ACVAF3Y97Ev+5iNZwiqTghmzNeWwDkOPlYuTcfb1vDaoZKmAQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0"
+      }
+    },
+    "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.29.7.tgz",
+      "integrity": "sha512-GE1TFSiuFeGsCxmYXZl8HwoPrVlwe4rHPFE8weieGKZqnDORK+Ar3vgWMgW+AOxQ6/2TgLSKx9p6W7O4rC6qgQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0"
+      }
+    },
+    "node_modules/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/-/plugin-bugfix-safari-rest-destructuring-rhs-array-7.29.7.tgz",
+      "integrity": "sha512-oBNVCvnO5tND+xSopWvV8WNGfpTfgP4Zr/YXXSj8zfmcPktp5Ku/aZlsIowgSD4fjmgHn6sGmB9APVsU5zOdhA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7",
+        "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0"
+      }
+    },
+    "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.29.7.tgz",
+      "integrity": "sha512-QQt9qKHZ2sg/kivaLr7lnQr8HVrQDdBNSfCsTjiDxRuX/K5ORyKq+Bu8Xr0cDE3Dfkv0cw28Ve0EKyKMvulkOw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7",
+        "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7",
+        "@babel/plugin-transform-optional-chaining": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.13.0"
+      }
+    },
+    "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.29.7.tgz",
+      "integrity": "sha512-pn6QacGLgvCcwc+syUhKE/qSjV2D1IHDB84RNxWYSt1mW3K/SCtjinZ2p0cETJxAWBjPy3K/1lHwG5BjjPxNlw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7",
+        "@babel/traverse": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0"
+      }
+    },
+    "node_modules/@babel/plugin-proposal-private-property-in-object": {
+      "version": "7.21.0-placeholder-for-preset-env.2",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz",
+      "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-syntax-import-assertions": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.29.7.tgz",
+      "integrity": "sha512-/An1OCBN93thpBAGyfsK2pcf0jvju1SAtKkL2Ny++B5Sy6sqgzXDQH1cZxWbF96Wuk+bn41MDA9bLd4VVAw6rw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-syntax-import-attributes": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.29.7.tgz",
+      "integrity": "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-syntax-import-meta": {
+      "version": "7.10.4",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
+      "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.10.4"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-syntax-jsx": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz",
+      "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-syntax-typescript": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz",
+      "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-syntax-unicode-sets-regex": {
+      "version": "7.18.6",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz",
+      "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-create-regexp-features-plugin": "^7.18.6",
+        "@babel/helper-plugin-utils": "^7.18.6"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-arrow-functions": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.29.7.tgz",
+      "integrity": "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-async-generator-functions": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.7.tgz",
+      "integrity": "sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7",
+        "@babel/helper-remap-async-to-generator": "^7.29.7",
+        "@babel/traverse": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-async-to-generator": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.29.7.tgz",
+      "integrity": "sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-module-imports": "^7.29.7",
+        "@babel/helper-plugin-utils": "^7.29.7",
+        "@babel/helper-remap-async-to-generator": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-block-scoped-functions": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.29.7.tgz",
+      "integrity": "sha512-cUSmjh72N+rN4PrkFlN1dJwNCwjVp5d38/CQrEsFggkD10UiFlBFgdH3tv5dNsLuHY+3S8db2xCHjhZcv5WgvA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-block-scoping": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.29.7.tgz",
+      "integrity": "sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-class-properties": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.29.7.tgz",
+      "integrity": "sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-create-class-features-plugin": "^7.29.7",
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-class-static-block": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.29.7.tgz",
+      "integrity": "sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-create-class-features-plugin": "^7.29.7",
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.12.0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-classes": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.29.7.tgz",
+      "integrity": "sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-annotate-as-pure": "^7.29.7",
+        "@babel/helper-compilation-targets": "^7.29.7",
+        "@babel/helper-globals": "^7.29.7",
+        "@babel/helper-plugin-utils": "^7.29.7",
+        "@babel/helper-replace-supers": "^7.29.7",
+        "@babel/traverse": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-computed-properties": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.29.7.tgz",
+      "integrity": "sha512-RK7/IyU5phpuCdBAuig5VkzG/EnbDaui5SQGdU9BFrHdV+mV4cUjLMQ9lJDjLNtWHsqtiefpGZUXQP2BiTYMsA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7",
+        "@babel/template": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-destructuring": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.29.7.tgz",
+      "integrity": "sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7",
+        "@babel/traverse": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-dotall-regex": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.29.7.tgz",
+      "integrity": "sha512-3qc18hsD2RdZiyJNDNc7HQpv6xbncwh8FYtxNFFzclSyh/trPD9KkVR9BDECUjDLvb7yJVF15GfYUuC+LMkkiQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-create-regexp-features-plugin": "^7.29.7",
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-duplicate-keys": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.29.7.tgz",
+      "integrity": "sha512-6IvRRriEMqnBwD6chtxdLpMYCHWEzN+oL5cyQtjykya19UgzbmKhxmhZgKC/LHxS2nYr9Q/qYPZ5Lr6jOL9+yQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.7.tgz",
+      "integrity": "sha512-2wiIyo2BjtgU7HufSeDnL9L2O7zr8jmhFKuSr65VpRkUiRKRNpb0mdlk56+XPPKoIrfHqzbMuglDvZun0RISsA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-create-regexp-features-plugin": "^7.29.7",
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-dynamic-import": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.29.7.tgz",
+      "integrity": "sha512-giOlEm/EFjfjr+te9NsdjkUo2v4f8rS/SXPumRVHAtbNcyNlvtREkU1dZzaIDclNpnaVhlCqRdFKhJBjBikzLg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-explicit-resource-management": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.29.7.tgz",
+      "integrity": "sha512-Rstj7coNz8sE+7Ju7ihpHLI564lsK5pUpNNlvptCIC/16E/S5hbl6n3kESPKdNRmqEWlpn5xpS5Q2dvXBsySLw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7",
+        "@babel/plugin-transform-destructuring": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-exponentiation-operator": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.29.7.tgz",
+      "integrity": "sha512-zFpMOTLZBdW5LfObqcSbL6kefg4R4eLdmvS0wbN9M6D5Mym/sKm9toOoWyVOa+xDjvCnuWcHls2YonXwHvH3CQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-export-namespace-from": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.29.7.tgz",
+      "integrity": "sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-for-of": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.29.7.tgz",
+      "integrity": "sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7",
+        "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-function-name": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.29.7.tgz",
+      "integrity": "sha512-otRWaHXE6fbAGkePvaj/kvs3HsqXfPhlnzwSOlnFgbqCPMd975dW+4wZ00WFBt+/YlBGcJwNrARQTOJOb4ZrIg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-compilation-targets": "^7.29.7",
+        "@babel/helper-plugin-utils": "^7.29.7",
+        "@babel/traverse": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-json-strings": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.29.7.tgz",
+      "integrity": "sha512-RRnE2+eon1rJAq8MnoF1b5kTpY1vU88twHcvcKMrsqP/jxIRqDVs9iJB5fqPuqyeFAW0wJo4MlUIPpQCq/aRsg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-literals": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.29.7.tgz",
+      "integrity": "sha512-DZ/oLP21ZuWx1vKqnoNv6/tvEK48AQOBRai40CX9dTjGluvT/YZCyY3rryDtyUqCEoyNroy5KKPwX2iQCiRvyw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-logical-assignment-operators": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.29.7.tgz",
+      "integrity": "sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-member-expression-literals": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.29.7.tgz",
+      "integrity": "sha512-hl1kwFZCCiDyfH25Xmco9jTrkPgnS9pmOzSG7W5I4SaGbLeqKv417hcU2RKmaxoPEgsoJh7ZPOrnPGq99bHoUg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-modules-amd": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.29.7.tgz",
+      "integrity": "sha512-fxtQoH3m5ywUSIfaH0FGCzWu4McsYon5bD3K4XnskC7f+OyQMj7rsOMi4NvvmJ83WwBAg4UCe+ov4VZlqEvyew==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-module-transforms": "^7.29.7",
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-modules-commonjs": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz",
+      "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-module-transforms": "^7.29.7",
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-modules-systemjs": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.7.tgz",
+      "integrity": "sha512-TM2ZcQLoG2/y4HODiStCo10DibYhWhGWAwVv+EQKmG/7GFl0N+AAmUiXOMKM+aiJ9XBJ9AHVZBvTzMnJ2sM3cQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-module-transforms": "^7.29.7",
+        "@babel/helper-plugin-utils": "^7.29.7",
+        "@babel/helper-validator-identifier": "^7.29.7",
+        "@babel/traverse": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-modules-umd": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.29.7.tgz",
+      "integrity": "sha512-B4UkaTK3QpgCwJnrxKfMPKdo92CN7OKXAlpAAnM3UPu0Q0lCCk57ylA9AJbRy2v8dDKOPAAWcoR6CMyeoHwRCA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-module-transforms": "^7.29.7",
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-named-capturing-groups-regex": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.7.tgz",
+      "integrity": "sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-create-regexp-features-plugin": "^7.29.7",
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-new-target": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.29.7.tgz",
+      "integrity": "sha512-fEo41GmsOUhOBlw8ioo6zvjX5Xc2Lqkzlyfqbpsk3eB6TReV18uhxZ0esfEokVbY2+PVJAQHNKxER6lGrzNd3A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-nullish-coalescing-operator": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.29.7.tgz",
+      "integrity": "sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-numeric-separator": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.29.7.tgz",
+      "integrity": "sha512-zR7fv/z14OjgHl4AgRtkDBvBMhIzCxqV/qN/2BCRC7LjFwvuzjYe7gDWxC4Wl/SNsLM6SE1IWvRPYMgSJaUvNw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-object-rest-spread": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.29.7.tgz",
+      "integrity": "sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-compilation-targets": "^7.29.7",
+        "@babel/helper-plugin-utils": "^7.29.7",
+        "@babel/plugin-transform-destructuring": "^7.29.7",
+        "@babel/plugin-transform-parameters": "^7.29.7",
+        "@babel/traverse": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-object-super": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.29.7.tgz",
+      "integrity": "sha512-Ea/diGcw0twB5IlZPO5sgET6fJsLJqPABqTuFWIR+iMPGPZJkATEIWx0wa+aEQ5UY1CBQyP/gkAiLEqn1vBiQA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7",
+        "@babel/helper-replace-supers": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-optional-catch-binding": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.29.7.tgz",
+      "integrity": "sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-optional-chaining": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.29.7.tgz",
+      "integrity": "sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7",
+        "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-parameters": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.29.7.tgz",
+      "integrity": "sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-private-methods": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.29.7.tgz",
+      "integrity": "sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-create-class-features-plugin": "^7.29.7",
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-private-property-in-object": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.29.7.tgz",
+      "integrity": "sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-annotate-as-pure": "^7.29.7",
+        "@babel/helper-create-class-features-plugin": "^7.29.7",
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-property-literals": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.29.7.tgz",
+      "integrity": "sha512-bOMRLQuI0A5ZqHq3OWJ89/rXpJ/NJrbVhXiP4zwPGMs6kpcVsuTUNjwoE30K0Qm3mf48a/TnRYYD6vPNqcg6jA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-regenerator": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.7.tgz",
+      "integrity": "sha512-rNNFV0DBAJp988xW2DOntfDoYn1eR8GGF5AT5vYc+rjyfaQkM242c9tZUHHPe7KYaiJizXPWhQTzzdbXySyhBw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-regexp-modifiers": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.29.7.tgz",
+      "integrity": "sha512-mB5Fs0VWrJ42ZCmc8114v60qetdaUVNkj9PmSZRmanCZM3S9hm0CFRLjRmYIsuXav14l2jvZ+4T8iiCGnhj3nQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-create-regexp-features-plugin": "^7.29.7",
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-reserved-words": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.29.7.tgz",
+      "integrity": "sha512-5+YhdpVgmfSmwZyLMftfaiffLRMHjzIRHFHHLdibcSyJm2pasMrKHrO3Ptrt2DRshjvpgjEJJ1zVW14WPq/6QA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-shorthand-properties": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.29.7.tgz",
+      "integrity": "sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-spread": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.29.7.tgz",
+      "integrity": "sha512-/u5K1QWada7tbYNqTjMh96718g9NTwh9tfPJMsSmVsQwGT447FskV+KcfeXkXq2GWki4EM/MuTdmBec+hOuVTQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7",
+        "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-sticky-regex": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.29.7.tgz",
+      "integrity": "sha512-BCHzNYJGe9l7EpwwDBN/ztlL2NYFFq8hp9ddjtUEM9f2O7S7kKV/lL6Fwo7IF7NSkYhPK2vO+86nIGltA90MsA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-template-literals": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.29.7.tgz",
+      "integrity": "sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-typeof-symbol": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.29.7.tgz",
+      "integrity": "sha512-223mNGoTkBiTEWFoK+Q6Go3tueMRclO8vxxxxquNCYuNI4jWOofFKJRRDu6SDrB8Sgo1UEGW9T4GAQ8ZyRso1A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-typescript": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz",
+      "integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-annotate-as-pure": "^7.29.7",
+        "@babel/helper-create-class-features-plugin": "^7.29.7",
+        "@babel/helper-plugin-utils": "^7.29.7",
+        "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7",
+        "@babel/plugin-syntax-typescript": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-unicode-escapes": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.29.7.tgz",
+      "integrity": "sha512-jCfXxSjf94lf4E0hKE0AByxF6F3/pVFqRdUUNkDJhsY0m1ZKjnN6ZYyMeHNpzflxb/0q5b7t3p+BE+SLF1WOtA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-unicode-property-regex": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.29.7.tgz",
+      "integrity": "sha512-OgZ+zoAJgZLUCunsTRQ5LAjOywDv5zzZ2/hQ5aMw1pGXyY2rtE8/chXYUmu3AlVHKpm10KEdG9aMwbI/K76ZGw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-create-regexp-features-plugin": "^7.29.7",
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-unicode-regex": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.29.7.tgz",
+      "integrity": "sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-create-regexp-features-plugin": "^7.29.7",
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-unicode-sets-regex": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.29.7.tgz",
+      "integrity": "sha512-BLOhLht9DOJwIxlmp91wHvkXv1lguuHS3/FwUO8HL1H0u8s4hR1gASVFyilu9iGtcTRYqjTZmlsFFeQletntEg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-create-regexp-features-plugin": "^7.29.7",
+        "@babel/helper-plugin-utils": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0"
+      }
+    },
+    "node_modules/@babel/preset-env": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/preset-env/-/preset-env-7.29.7.tgz",
+      "integrity": "sha512-GYzX36n1nsciIb0uyH0GHwxwtNwPQIcpxSeiVLDtG/B7jB5xXgchnmL1f/jCX5o+pwnaDBtO60ONSJhEBJfxYA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/compat-data": "^7.29.7",
+        "@babel/helper-compilation-targets": "^7.29.7",
+        "@babel/helper-plugin-utils": "^7.29.7",
+        "@babel/helper-validator-option": "^7.29.7",
+        "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.29.7",
+        "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.29.7",
+        "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.29.7",
+        "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": "^7.29.7",
+        "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.29.7",
+        "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.29.7",
+        "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2",
+        "@babel/plugin-syntax-import-assertions": "^7.29.7",
+        "@babel/plugin-syntax-import-attributes": "^7.29.7",
+        "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6",
+        "@babel/plugin-transform-arrow-functions": "^7.29.7",
+        "@babel/plugin-transform-async-generator-functions": "^7.29.7",
+        "@babel/plugin-transform-async-to-generator": "^7.29.7",
+        "@babel/plugin-transform-block-scoped-functions": "^7.29.7",
+        "@babel/plugin-transform-block-scoping": "^7.29.7",
+        "@babel/plugin-transform-class-properties": "^7.29.7",
+        "@babel/plugin-transform-class-static-block": "^7.29.7",
+        "@babel/plugin-transform-classes": "^7.29.7",
+        "@babel/plugin-transform-computed-properties": "^7.29.7",
+        "@babel/plugin-transform-destructuring": "^7.29.7",
+        "@babel/plugin-transform-dotall-regex": "^7.29.7",
+        "@babel/plugin-transform-duplicate-keys": "^7.29.7",
+        "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.7",
+        "@babel/plugin-transform-dynamic-import": "^7.29.7",
+        "@babel/plugin-transform-explicit-resource-management": "^7.29.7",
+        "@babel/plugin-transform-exponentiation-operator": "^7.29.7",
+        "@babel/plugin-transform-export-namespace-from": "^7.29.7",
+        "@babel/plugin-transform-for-of": "^7.29.7",
+        "@babel/plugin-transform-function-name": "^7.29.7",
+        "@babel/plugin-transform-json-strings": "^7.29.7",
+        "@babel/plugin-transform-literals": "^7.29.7",
+        "@babel/plugin-transform-logical-assignment-operators": "^7.29.7",
+        "@babel/plugin-transform-member-expression-literals": "^7.29.7",
+        "@babel/plugin-transform-modules-amd": "^7.29.7",
+        "@babel/plugin-transform-modules-commonjs": "^7.29.7",
+        "@babel/plugin-transform-modules-systemjs": "^7.29.7",
+        "@babel/plugin-transform-modules-umd": "^7.29.7",
+        "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.7",
+        "@babel/plugin-transform-new-target": "^7.29.7",
+        "@babel/plugin-transform-nullish-coalescing-operator": "^7.29.7",
+        "@babel/plugin-transform-numeric-separator": "^7.29.7",
+        "@babel/plugin-transform-object-rest-spread": "^7.29.7",
+        "@babel/plugin-transform-object-super": "^7.29.7",
+        "@babel/plugin-transform-optional-catch-binding": "^7.29.7",
+        "@babel/plugin-transform-optional-chaining": "^7.29.7",
+        "@babel/plugin-transform-parameters": "^7.29.7",
+        "@babel/plugin-transform-private-methods": "^7.29.7",
+        "@babel/plugin-transform-private-property-in-object": "^7.29.7",
+        "@babel/plugin-transform-property-literals": "^7.29.7",
+        "@babel/plugin-transform-regenerator": "^7.29.7",
+        "@babel/plugin-transform-regexp-modifiers": "^7.29.7",
+        "@babel/plugin-transform-reserved-words": "^7.29.7",
+        "@babel/plugin-transform-shorthand-properties": "^7.29.7",
+        "@babel/plugin-transform-spread": "^7.29.7",
+        "@babel/plugin-transform-sticky-regex": "^7.29.7",
+        "@babel/plugin-transform-template-literals": "^7.29.7",
+        "@babel/plugin-transform-typeof-symbol": "^7.29.7",
+        "@babel/plugin-transform-unicode-escapes": "^7.29.7",
+        "@babel/plugin-transform-unicode-property-regex": "^7.29.7",
+        "@babel/plugin-transform-unicode-regex": "^7.29.7",
+        "@babel/plugin-transform-unicode-sets-regex": "^7.29.7",
+        "@babel/preset-modules": "0.1.6-no-external-plugins",
+        "babel-plugin-polyfill-corejs2": "^0.4.15",
+        "babel-plugin-polyfill-corejs3": "^0.14.0",
+        "babel-plugin-polyfill-regenerator": "^0.6.6",
+        "core-js-compat": "^3.48.0",
+        "semver": "^6.3.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/preset-modules": {
+      "version": "0.1.6-no-external-plugins",
+      "resolved": "https://registry.npmmirror.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz",
+      "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.0.0",
+        "@babel/types": "^7.4.4",
+        "esutils": "^2.0.2"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0"
+      }
+    },
+    "node_modules/@babel/runtime": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.29.7.tgz",
+      "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/template": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.29.7.tgz",
+      "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/code-frame": "^7.29.7",
+        "@babel/parser": "^7.29.7",
+        "@babel/types": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/template/node_modules/@babel/code-frame": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.29.7.tgz",
+      "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-validator-identifier": "^7.29.7",
+        "js-tokens": "^4.0.0",
+        "picocolors": "^1.1.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/template/node_modules/@babel/parser": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.7.tgz",
+      "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/types": "^7.29.7"
+      },
+      "bin": {
+        "parser": "bin/babel-parser.js"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@babel/template/node_modules/@babel/types": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.7.tgz",
+      "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.29.7",
+        "@babel/helper-validator-identifier": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/template/node_modules/picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+      "license": "ISC"
+    },
+    "node_modules/@babel/traverse": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.29.7.tgz",
+      "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/code-frame": "^7.29.7",
+        "@babel/generator": "^7.29.7",
+        "@babel/helper-globals": "^7.29.7",
+        "@babel/parser": "^7.29.7",
+        "@babel/template": "^7.29.7",
+        "@babel/types": "^7.29.7",
+        "debug": "^4.3.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/traverse/node_modules/@babel/code-frame": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.29.7.tgz",
+      "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-validator-identifier": "^7.29.7",
+        "js-tokens": "^4.0.0",
+        "picocolors": "^1.1.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/traverse/node_modules/@babel/parser": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.7.tgz",
+      "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/types": "^7.29.7"
+      },
+      "bin": {
+        "parser": "bin/babel-parser.js"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@babel/traverse/node_modules/@babel/types": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.7.tgz",
+      "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.29.7",
+        "@babel/helper-validator-identifier": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/traverse/node_modules/picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+      "license": "ISC"
+    },
+    "node_modules/@babel/types": {
+      "version": "7.25.6",
+      "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.25.6.tgz",
+      "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.24.8",
+        "@babel/helper-validator-identifier": "^7.24.7",
+        "to-fast-properties": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@dcloudio/types": {
+      "version": "3.4.19",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/types/-/types-3.4.19.tgz",
+      "integrity": "sha512-1foayOFEAQ+jnQLt3ACsovCNjer3/fXn1I2VBpmDOzs2nk/n4UHwRLAxZV/RpxRqaGOPEvKrO/Pq+VI6sAmuRw==",
+      "dev": true,
+      "license": "Apache-2.0"
+    },
+    "node_modules/@dcloudio/uni-app": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-app/-/uni-app-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-pzBWZiICfMmAxzBvAoXlTcDYoVNrV+ztsUyDouUxZJShpquQdVqHJqHxAlvGLR7c5gHCVtnKilCKwmu6zjNGrA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@dcloudio/uni-cloud": "3.0.0-4080420251103001",
+        "@dcloudio/uni-components": "3.0.0-4080420251103001",
+        "@dcloudio/uni-console": "3.0.0-4080420251103001",
+        "@dcloudio/uni-i18n": "3.0.0-4080420251103001",
+        "@dcloudio/uni-push": "3.0.0-4080420251103001",
+        "@dcloudio/uni-shared": "3.0.0-4080420251103001",
+        "@dcloudio/uni-stat": "3.0.0-4080420251103001",
+        "@vue/shared": "3.4.21"
+      },
+      "peerDependencies": {
+        "@dcloudio/types": "3.4.19"
+      }
+    },
+    "node_modules/@dcloudio/uni-app-harmony": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-app-harmony/-/uni-app-harmony-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-WjCLttxacAoRywMl3hhA2LGAso8NqqgupXbTvoWRyYF11tuSw2duUM7n58mZ3A/cI4SQk/y/x9MzoFXlDiYzAw==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@dcloudio/uni-app-uts": "3.0.0-4080420251103001",
+        "@dcloudio/uni-app-vite": "3.0.0-4080420251103001",
+        "debug": "4.3.7",
+        "fs-extra": "10.1.0",
+        "licia": "1.41.1",
+        "postcss-selector-parser": "6.1.2"
+      }
+    },
+    "node_modules/@dcloudio/uni-app-plus": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-app-plus/-/uni-app-plus-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-HbEpRIyJ4q6A+s+2WJsBvW/AlStyzBUHFkZbs/1vnTV5jjjvlT8e9Zb+2HptC95olZmD97QWcu31M5U/a6Z0PA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@dcloudio/uni-app-uts": "3.0.0-4080420251103001",
+        "@dcloudio/uni-app-vite": "3.0.0-4080420251103001",
+        "@dcloudio/uni-app-vue": "3.0.0-4080420251103001",
+        "debug": "4.3.7",
+        "fs-extra": "10.1.0",
+        "licia": "1.41.1",
+        "postcss-selector-parser": "6.1.2"
+      }
+    },
+    "node_modules/@dcloudio/uni-app-uts": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-app-uts/-/uni-app-uts-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-YdKLXUY4Ix64ajRAv6JKiiSL5FZViDYuP83TXI5zaBSbw/fofzO1ZYRZKrQ2bZi51yH0Huq8u6MPvDIUQTLjJw==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@babel/parser": "7.25.6",
+        "@babel/types": "7.25.6",
+        "@dcloudio/uni-cli-shared": "3.0.0-4080420251103001",
+        "@dcloudio/uni-console": "3.0.0-4080420251103001",
+        "@dcloudio/uni-i18n": "3.0.0-4080420251103001",
+        "@dcloudio/uni-nvue-styler": "3.0.0-4080420251103001",
+        "@dcloudio/uni-shared": "3.0.0-4080420251103001",
+        "@jridgewell/gen-mapping": "^0.3.3",
+        "@jridgewell/trace-mapping": "^0.3.19",
+        "@rollup/pluginutils": "5.1.0",
+        "@vue/compiler-core": "3.4.21",
+        "@vue/compiler-dom": "3.4.21",
+        "@vue/compiler-sfc": "3.4.21",
+        "@vue/consolidate": "1.0.0",
+        "@vue/shared": "3.4.21",
+        "debug": "4.3.7",
+        "es-module-lexer": "1.5.4",
+        "estree-walker": "2.0.2",
+        "fast-glob": "3.3.3",
+        "fs-extra": "10.1.0",
+        "magic-string": "0.30.11",
+        "picocolors": "1.1.0",
+        "source-map-js": "1.2.1",
+        "unimport": "4.1.1"
+      }
+    },
+    "node_modules/@dcloudio/uni-app-vite": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-app-vite/-/uni-app-vite-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-hti+d0OoT/B69ApgKj8SEgi2rGownaHSxgfZQx1AYmWiRXsc/uWEzC7lakvsRLV3aPiUPD0aJNuI97IgMGBvnw==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@dcloudio/uni-cli-shared": "3.0.0-4080420251103001",
+        "@dcloudio/uni-i18n": "3.0.0-4080420251103001",
+        "@dcloudio/uni-nvue-styler": "3.0.0-4080420251103001",
+        "@dcloudio/uni-shared": "3.0.0-4080420251103001",
+        "@rollup/pluginutils": "5.1.0",
+        "@vitejs/plugin-vue": "5.2.4",
+        "@vue/compiler-dom": "3.4.21",
+        "@vue/compiler-sfc": "3.4.21",
+        "debug": "4.3.7",
+        "fs-extra": "10.1.0",
+        "picocolors": "1.1.0"
+      }
+    },
+    "node_modules/@dcloudio/uni-app-vue": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-app-vue/-/uni-app-vue-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-nK3ORcnBUQQ4BwqCvAMNHOVtBevfs+iwT31SZDtV0HikWq2fF1O6ae1bmMQJ3de3/fCLvu+3ZYCE773+D3S9aQ==",
+      "license": "Apache-2.0"
+    },
+    "node_modules/@dcloudio/uni-automator": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-automator/-/uni-automator-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-zAHmFiZxbP3PmuTh5lz16NDThfrm4MTkwN80ZLn+xlJl5vNqX5yMfqfDwJrEBNY7Wycfh+qB3EvwWQ8CFMpAxw==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@dcloudio/uni-cli-shared": "3.0.0-4080420251103001",
+        "address": "^1.1.2",
+        "cross-env": "^7.0.3",
+        "debug": "4.3.7",
+        "default-gateway": "^6.0.3",
+        "fs-extra": "10.1.0",
+        "jsonc-parser": "3.3.1",
+        "licia": "1.41.1",
+        "merge": "2.1.1",
+        "qrcode-reader": "1.0.4",
+        "qrcode-terminal": "0.12.0",
+        "ws": "8.18.0"
+      },
+      "peerDependencies": {
+        "jest": "27.0.4",
+        "jest-environment-node": "27.5.1"
+      }
+    },
+    "node_modules/@dcloudio/uni-cli-shared": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-cli-shared/-/uni-cli-shared-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-CI9gfUSrneTJFp52CBpAwDE9vsaxdwg4uA7n2ehKB+WLXgP2zmLp+0QoSQoFYISYll6MnfQK6Pcl8Oj1c7mKUA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@ampproject/remapping": "^2.1.2",
+        "@babel/code-frame": "7.24.7",
+        "@babel/core": "7.25.2",
+        "@babel/parser": "7.25.6",
+        "@babel/types": "7.25.6",
+        "@dcloudio/uni-i18n": "3.0.0-4080420251103001",
+        "@dcloudio/uni-shared": "3.0.0-4080420251103001",
+        "@intlify/core-base": "9.1.9",
+        "@intlify/shared": "9.1.9",
+        "@intlify/vue-devtools": "9.1.9",
+        "@rollup/pluginutils": "5.1.0",
+        "@vue/compiler-core": "3.4.21",
+        "@vue/compiler-dom": "3.4.21",
+        "@vue/compiler-sfc": "3.4.21",
+        "@vue/compiler-ssr": "3.4.21",
+        "@vue/server-renderer": "3.4.21",
+        "@vue/shared": "3.4.21",
+        "adm-zip": "0.5.16",
+        "autoprefixer": "10.4.20",
+        "base64url": "^3.0.1",
+        "chokidar": "3.6.0",
+        "compare-versions": "^3.6.0",
+        "debug": "4.3.7",
+        "entities": "^4.5.0",
+        "es-module-lexer": "1.5.4",
+        "esbuild": "0.20.2",
+        "estree-walker": "2.0.2",
+        "fast-glob": "3.3.3",
+        "fs-extra": "10.1.0",
+        "hash-sum": "2.0.0",
+        "isbinaryfile": "5.0.2",
+        "jsonc-parser": "3.3.1",
+        "lines-and-columns": "^2.0.4",
+        "magic-string": "0.30.11",
+        "merge": "2.1.1",
+        "mime": "3.0.0",
+        "module-alias": "2.2.3",
+        "os-locale-s-fix": "^1.0.8-fix-1",
+        "picocolors": "1.1.0",
+        "postcss-import": "^14.0.2",
+        "postcss-load-config": "^3.1.1",
+        "postcss-modules": "^4.3.0",
+        "postcss-selector-parser": "6.1.2",
+        "resolve": "1.22.8",
+        "source-map-js": "1.2.1",
+        "tapable": "^2.2.0",
+        "unimport": "4.1.1",
+        "unplugin-auto-import": "19.1.0",
+        "xregexp": "3.1.0"
+      },
+      "engines": {
+        "node": "^14.18.0 || >=16.0.0"
+      }
+    },
+    "node_modules/@dcloudio/uni-cli-shared/node_modules/autoprefixer": {
+      "version": "10.4.20",
+      "resolved": "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.20.tgz",
+      "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==",
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "browserslist": "^4.23.3",
+        "caniuse-lite": "^1.0.30001646",
+        "fraction.js": "^4.3.7",
+        "normalize-range": "^0.1.2",
+        "picocolors": "^1.0.1",
+        "postcss-value-parser": "^4.2.0"
+      },
+      "bin": {
+        "autoprefixer": "bin/autoprefixer"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      },
+      "peerDependencies": {
+        "postcss": "^8.1.0"
+      }
+    },
+    "node_modules/@dcloudio/uni-cli-shared/node_modules/fraction.js": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmmirror.com/fraction.js/-/fraction.js-4.3.7.tgz",
+      "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
+      "license": "MIT",
+      "engines": {
+        "node": "*"
+      },
+      "funding": {
+        "type": "patreon",
+        "url": "https://github.com/sponsors/rawify"
+      }
+    },
+    "node_modules/@dcloudio/uni-cli-shared/node_modules/picomatch": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.4.tgz",
+      "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/@dcloudio/uni-cli-shared/node_modules/unplugin-auto-import": {
+      "version": "19.1.0",
+      "resolved": "https://registry.npmmirror.com/unplugin-auto-import/-/unplugin-auto-import-19.1.0.tgz",
+      "integrity": "sha512-B+TGBEBHqY9aR+7YfShfLujETOHstzpV+yaqgy5PkfV0QG7Py+TYMX7vJ9W4SrysHR+UzR+gzcx/nuZjmPeclA==",
+      "license": "MIT",
+      "dependencies": {
+        "local-pkg": "^1.0.0",
+        "magic-string": "^0.30.17",
+        "picomatch": "^4.0.2",
+        "unimport": "^4.1.1",
+        "unplugin": "^2.2.0",
+        "unplugin-utils": "^0.2.4"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@nuxt/kit": "^3.2.2",
+        "@vueuse/core": "*"
+      },
+      "peerDependenciesMeta": {
+        "@nuxt/kit": {
+          "optional": true
+        },
+        "@vueuse/core": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@dcloudio/uni-cli-shared/node_modules/unplugin-auto-import/node_modules/magic-string": {
+      "version": "0.30.21",
+      "resolved": "https://registry.npmmirror.com/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/@dcloudio/uni-cloud": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-cloud/-/uni-cloud-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-RQp+MkmrW/OxfaTbN1ohNnCcj7q55ub2F0pMAr5OCKHOV/sB4NhnwTKzB7C4B1Ha1oR8ulTAfmwUiBP89dSEmg==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@dcloudio/uni-cli-shared": "3.0.0-4080420251103001",
+        "@dcloudio/uni-i18n": "3.0.0-4080420251103001",
+        "@dcloudio/uni-shared": "3.0.0-4080420251103001",
+        "@vue/shared": "3.4.21",
+        "fast-glob": "3.3.3"
+      }
+    },
+    "node_modules/@dcloudio/uni-components": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-components/-/uni-components-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-YBsUWVx6OrJVR/59QU9H6QX+ulEB5GK+Fp3xZPU3NiVKczSAEsE2eBU3+xSYSyndUrP4BDqV1qhjt5cgpFJcsA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@dcloudio/uni-cloud": "3.0.0-4080420251103001",
+        "@dcloudio/uni-h5": "3.0.0-4080420251103001",
+        "@dcloudio/uni-i18n": "3.0.0-4080420251103001"
+      }
+    },
+    "node_modules/@dcloudio/uni-console": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-console/-/uni-console-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-bVdk8iVfld8mTUX8tB6keRm54htGMLOeJhuG1Cx8R8aOJfqTU8yuowf8ZIxIRsqs2dXV/5fHNGcbd/uFosDwDA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@dcloudio/uni-cli-shared": "3.0.0-4080420251103001",
+        "fs-extra": "10.1.0"
+      }
+    },
+    "node_modules/@dcloudio/uni-h5": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-h5/-/uni-h5-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-Xl4bR2znjF6QJ2IHPen5eQiISpSWFZZaXXpFa0IyTf93xSBcSvsg9Nq5izBYPxkH4YXD/hiVHdbgwyHciq/cog==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@dcloudio/uni-h5-vite": "3.0.0-4080420251103001",
+        "@dcloudio/uni-h5-vue": "3.0.0-4080420251103001",
+        "@dcloudio/uni-i18n": "3.0.0-4080420251103001",
+        "@dcloudio/uni-shared": "3.0.0-4080420251103001",
+        "@vue/server-renderer": "3.4.21",
+        "@vue/shared": "3.4.21",
+        "debug": "4.3.7",
+        "localstorage-polyfill": "^1.0.1",
+        "postcss-selector-parser": "6.1.2",
+        "safe-area-insets": "1.4.1",
+        "vue-router": "4.4.4",
+        "xmlhttprequest": "^1.8.0"
+      }
+    },
+    "node_modules/@dcloudio/uni-h5-vite": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-h5-vite/-/uni-h5-vite-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-Z9wMRSI+v9aDVlvQYHPnv1gggYJk6WVMINvty/tg01bFXMkBs99TyjaT4XShPM46q8TVySd3lDRQOcY6cfUY1A==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@dcloudio/uni-cli-shared": "3.0.0-4080420251103001",
+        "@dcloudio/uni-shared": "3.0.0-4080420251103001",
+        "@rollup/pluginutils": "5.1.0",
+        "@vue/compiler-dom": "3.4.21",
+        "@vue/compiler-sfc": "3.4.21",
+        "@vue/server-renderer": "3.4.21",
+        "@vue/shared": "3.4.21",
+        "debug": "4.3.7",
+        "fs-extra": "10.1.0",
+        "mime": "3.0.0",
+        "module-alias": "2.2.3"
+      }
+    },
+    "node_modules/@dcloudio/uni-h5-vue": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-h5-vue/-/uni-h5-vue-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-NbQCQFbnXIOKdek/ntwh1NslnmrZbuYRrfbe2ZVZOz8gXKfaR7GEO/GPPbTpI7mw3+iM906msksBh9HBzsD5TA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@dcloudio/uni-shared": "3.0.0-4080420251103001",
+        "@vue/server-renderer": "3.4.21"
+      }
+    },
+    "node_modules/@dcloudio/uni-i18n": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-i18n/-/uni-i18n-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-FHUQ8Ex0GbJsYxVZR1CrUoRc9Rm2OnfudCxpXIJSxpd1tp4sje8QhIeXwRIMEXcATc00hngTSgQrnuMuu5g72Q==",
+      "license": "Apache-2.0"
+    },
+    "node_modules/@dcloudio/uni-mp-alipay": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-mp-alipay/-/uni-mp-alipay-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-cAfDeDAHEMVjwa3YgiREEewbx5iSkEu2qS7aueCvWNwKaz9u9vw7OnE+D31lNTzcW/jD+ESTgeKEL0tSH+1nyg==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@dcloudio/uni-cli-shared": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-vite": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-vue": "3.0.0-4080420251103001",
+        "@dcloudio/uni-shared": "3.0.0-4080420251103001",
+        "@vue/compiler-core": "3.4.21",
+        "@vue/shared": "3.4.21"
+      }
+    },
+    "node_modules/@dcloudio/uni-mp-baidu": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-mp-baidu/-/uni-mp-baidu-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-n/BzswNgJpD+XvMTHd8N7SosmIm2UPkvbV3ncPzGOcoW6kiF3OOh6jxYCf4fgPzj6RNf/avOyQv2QS0RgdYy1w==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@dcloudio/uni-app": "3.0.0-4080420251103001",
+        "@dcloudio/uni-cli-shared": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-compiler": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-vite": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-vue": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-weixin": "3.0.0-4080420251103001",
+        "@dcloudio/uni-shared": "3.0.0-4080420251103001",
+        "@vue/compiler-core": "3.4.21",
+        "@vue/shared": "3.4.21",
+        "jimp": "0.10.3",
+        "licia": "1.41.1",
+        "qrcode-reader": "1.0.4",
+        "qrcode-terminal": "0.12.0",
+        "ws": "8.18.0"
+      }
+    },
+    "node_modules/@dcloudio/uni-mp-compiler": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-mp-compiler/-/uni-mp-compiler-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-tMyKHyUyNl/yy/6D7M66IVxJhkaieimL2F/ZprNP8fl1QWBZqBBxokxAVeFeGbX7f9Sd1Vi+HdAowQkMCNfdDw==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@babel/generator": "7.25.6",
+        "@babel/parser": "7.25.6",
+        "@babel/types": "7.25.6",
+        "@dcloudio/uni-cli-shared": "3.0.0-4080420251103001",
+        "@dcloudio/uni-shared": "3.0.0-4080420251103001",
+        "@vue/compiler-core": "3.4.21",
+        "@vue/compiler-dom": "3.4.21",
+        "@vue/shared": "3.4.21",
+        "estree-walker": "2.0.2"
+      }
+    },
+    "node_modules/@dcloudio/uni-mp-compiler/node_modules/@babel/generator": {
+      "version": "7.25.6",
+      "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.25.6.tgz",
+      "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/types": "^7.25.6",
+        "@jridgewell/gen-mapping": "^0.3.5",
+        "@jridgewell/trace-mapping": "^0.3.25",
+        "jsesc": "^2.5.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@dcloudio/uni-mp-compiler/node_modules/jsesc": {
+      "version": "2.5.2",
+      "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-2.5.2.tgz",
+      "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+      "license": "MIT",
+      "bin": {
+        "jsesc": "bin/jsesc"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/@dcloudio/uni-mp-harmony": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-mp-harmony/-/uni-mp-harmony-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-/TYd3wijGxRktHWLPFguDQvsIC+tvqzKK1rcQoo3hgnSbjJiKUXBMLbnH1RH9fZNqVDqVMX/t07KrlnLE7K6IA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@dcloudio/uni-cli-shared": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-toutiao": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-vite": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-vue": "3.0.0-4080420251103001",
+        "@dcloudio/uni-quickapp-webview": "3.0.0-4080420251103001",
+        "@dcloudio/uni-shared": "3.0.0-4080420251103001",
+        "@vue/shared": "3.4.21"
+      }
+    },
+    "node_modules/@dcloudio/uni-mp-jd": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-mp-jd/-/uni-mp-jd-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-jkLglxCUX45juoZGfeejYs3Don1TdUqpZ1Jdm9DEjJ9QV0vS48fyVdnWzl423bJ5IvIDYTh5DTfGh0epRgxw6Q==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@dcloudio/uni-cli-shared": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-compiler": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-vite": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-vue": "3.0.0-4080420251103001",
+        "@dcloudio/uni-shared": "3.0.0-4080420251103001",
+        "@vue/shared": "3.4.21"
+      }
+    },
+    "node_modules/@dcloudio/uni-mp-kuaishou": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-mp-kuaishou/-/uni-mp-kuaishou-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-7XH9qlfGlrPJr5nLReCVtVilH/W3dXxXUNwk2ISH8udTjRT1NB/LaBD9/TxvoY7k8+LplEFOpIq+PBKWImABPw==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@dcloudio/uni-cli-shared": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-compiler": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-vite": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-vue": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-weixin": "3.0.0-4080420251103001",
+        "@dcloudio/uni-shared": "3.0.0-4080420251103001",
+        "@vue/compiler-core": "3.4.21",
+        "@vue/shared": "3.4.21"
+      }
+    },
+    "node_modules/@dcloudio/uni-mp-lark": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-mp-lark/-/uni-mp-lark-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-VFj/IWul1aaDZaZig+9IooPg3v3Cu+jaBvklfkP58c6wQa0PvnzE3j9j6WhfbhZ7CUW/rTmC7cxqNjTe2hwPcQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@dcloudio/uni-cli-shared": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-compiler": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-toutiao": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-vite": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-vue": "3.0.0-4080420251103001",
+        "@dcloudio/uni-shared": "3.0.0-4080420251103001",
+        "@vue/compiler-core": "3.4.21",
+        "@vue/shared": "3.4.21"
+      }
+    },
+    "node_modules/@dcloudio/uni-mp-qq": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-mp-qq/-/uni-mp-qq-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-BBWL2wVTG1tv8PPmnaA/7Aae4grbPevEkapsYl6WAEkJDBv5AIissq/ltusEnXnYNKtU/ZB8GVnb//lLW3xa/A==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@dcloudio/uni-cli-shared": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-vite": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-vue": "3.0.0-4080420251103001",
+        "@dcloudio/uni-shared": "3.0.0-4080420251103001",
+        "@vue/shared": "3.4.21",
+        "fs-extra": "10.1.0"
+      }
+    },
+    "node_modules/@dcloudio/uni-mp-toutiao": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-mp-toutiao/-/uni-mp-toutiao-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-iuEfUje5sEn72kFiyhb1tqu1Qx8iuC4cbGTbtyD7P6MSozwzURJgISoZiFsx1EKYIgn3w8ZHL+oh6hXgysP+Lw==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@dcloudio/uni-cli-shared": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-compiler": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-vite": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-vue": "3.0.0-4080420251103001",
+        "@dcloudio/uni-shared": "3.0.0-4080420251103001",
+        "@vue/compiler-core": "3.4.21",
+        "@vue/shared": "3.4.21"
+      }
+    },
+    "node_modules/@dcloudio/uni-mp-vite": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-mp-vite/-/uni-mp-vite-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-/MQElW4cWS0nL1gofvpY0xV1XjAcF7zVrjYIFoiht1rEwW6ec64umKOI9YGMUKi+Gx8o2Qa4XTsqChlE/MzSFg==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@dcloudio/uni-cli-shared": "3.0.0-4080420251103001",
+        "@dcloudio/uni-i18n": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-compiler": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-vue": "3.0.0-4080420251103001",
+        "@dcloudio/uni-shared": "3.0.0-4080420251103001",
+        "@vue/compiler-dom": "3.4.21",
+        "@vue/compiler-sfc": "3.4.21",
+        "@vue/shared": "3.4.21",
+        "debug": "4.3.7"
+      }
+    },
+    "node_modules/@dcloudio/uni-mp-vue": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-mp-vue/-/uni-mp-vue-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-H1xF0jLrN3WnN/xCWKy+74bSt7AHa8grz3Nw75u+77vmkuIzxiGpauUQ4FG6l2zBtiW3EM+gfcfFUMunTlHM4A==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@dcloudio/uni-shared": "3.0.0-4080420251103001",
+        "@vue/shared": "3.4.21"
+      }
+    },
+    "node_modules/@dcloudio/uni-mp-weixin": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-mp-weixin/-/uni-mp-weixin-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-AoaXYxNngQBjNQ6hJJ4Jzz6KDDEjYeEUs8idhX+dujUO1JgBGpWbE9gP66VdaPNmD7+aZ/1tK1fZG6JjuHW+8w==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@dcloudio/uni-cli-shared": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-vite": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-vue": "3.0.0-4080420251103001",
+        "@dcloudio/uni-shared": "3.0.0-4080420251103001",
+        "@vue/shared": "3.4.21",
+        "jimp": "0.10.3",
+        "licia": "1.41.1",
+        "qrcode-reader": "1.0.4",
+        "qrcode-terminal": "0.12.0",
+        "ws": "8.18.0"
+      }
+    },
+    "node_modules/@dcloudio/uni-mp-xhs": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-mp-xhs/-/uni-mp-xhs-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-Fl+gUYLhDOllrDkwkaR1rnGs+rwIbHzSJUZ/s6epocNhod1QetpzS5FBXwDryf898czjA9FcHteJotALXNkBjQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@dcloudio/uni-cli-shared": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-compiler": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-vite": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-vue": "3.0.0-4080420251103001",
+        "@dcloudio/uni-shared": "3.0.0-4080420251103001",
+        "@vue/shared": "3.4.21"
+      }
+    },
+    "node_modules/@dcloudio/uni-nvue-styler": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-nvue-styler/-/uni-nvue-styler-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-LXwlJyfusm/bIC2qceJuJm72XNQtRXt5REG/6p+mtEYum8l4qloFJ5za1TdQXRsECXPGz6rS8YqU8iDTe70vdw==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "parse-css-font": "^4.0.0",
+        "postcss": "8.4.45"
+      }
+    },
+    "node_modules/@dcloudio/uni-nvue-styler/node_modules/postcss": {
+      "version": "8.4.45",
+      "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.45.tgz",
+      "integrity": "sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q==",
+      "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.7",
+        "picocolors": "^1.0.1",
+        "source-map-js": "^1.2.0"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      }
+    },
+    "node_modules/@dcloudio/uni-push": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-push/-/uni-push-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-PZ766hB4OBRuHLNJ6umNNGo8WljC67LMFgpD7ORHa6+SmS+JfncMqXufWGsuh/Q/1/QPqe4Pcm8w1zG5Bke1vw==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@dcloudio/uni-cli-shared": "3.0.0-4080420251103001"
+      }
+    },
+    "node_modules/@dcloudio/uni-quickapp-webview": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-quickapp-webview/-/uni-quickapp-webview-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-/hn2zxUnUQOkkj37QqsQL5f7ffQ8EBm/27FrO8lYx7wGyRaehcuXhJ/vHWPRpJv9tq4Cl8w0iidrVsYPchu2pw==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@dcloudio/uni-cli-shared": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-vite": "3.0.0-4080420251103001",
+        "@dcloudio/uni-mp-vue": "3.0.0-4080420251103001",
+        "@dcloudio/uni-shared": "3.0.0-4080420251103001",
+        "@vue/shared": "3.4.21"
+      }
+    },
+    "node_modules/@dcloudio/uni-shared": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-shared/-/uni-shared-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-+EfZmMVToYOaKBkzXix/LMGosZaFiFUyZ1vnGg3giLEcr5r8EYQ/NQ3GfthcD6pC9A114imv5Az+4HeKhcKZiw==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@vue/shared": "3.4.21"
+      }
+    },
+    "node_modules/@dcloudio/uni-stacktracey": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-stacktracey/-/uni-stacktracey-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-coPXA+6PbbSWZx8NDCyNnkSfUx9FusVB6lhxZ+aYKYJkbl/28pRJAM9oCeGV3sZHpjCYyOZaBzxv0kQ0/O7W8A==",
+      "dev": true,
+      "license": "Apache-2.0"
+    },
+    "node_modules/@dcloudio/uni-stat": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/uni-stat/-/uni-stat-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-CCADC5plW/etPbhORExii2pJ6m337YeHB+V1R7w6JBmFdGqqYj7+3Vq24wWSSECc4hpiZFLk3dNzu5/NNw2p1Q==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@dcloudio/uni-cli-shared": "3.0.0-4080420251103001",
+        "@dcloudio/uni-shared": "3.0.0-4080420251103001",
+        "debug": "4.3.7"
+      }
+    },
+    "node_modules/@dcloudio/vite-plugin-uni": {
+      "version": "3.0.0-4080420251103001",
+      "resolved": "https://registry.npmmirror.com/@dcloudio/vite-plugin-uni/-/vite-plugin-uni-3.0.0-4080420251103001.tgz",
+      "integrity": "sha512-r9UOGDrHqvOV2p9IW+4wql8BJJjKgPRWH9Y0rkztp7GjvtUqCkkoVwlh5sJXYwqPNZL5JWPuj0Cl+ZtTg1njyw==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@babel/core": "7.25.2",
+        "@babel/plugin-syntax-import-meta": "^7.10.4",
+        "@babel/plugin-transform-typescript": "^7.23.3",
+        "@dcloudio/uni-cli-shared": "3.0.0-4080420251103001",
+        "@dcloudio/uni-nvue-styler": "3.0.0-4080420251103001",
+        "@dcloudio/uni-shared": "3.0.0-4080420251103001",
+        "@rollup/pluginutils": "5.1.0",
+        "@vitejs/plugin-legacy": "5.3.2",
+        "@vitejs/plugin-vue": "5.2.4",
+        "@vitejs/plugin-vue-jsx": "3.1.0",
+        "@vue/compiler-core": "3.4.21",
+        "@vue/compiler-dom": "3.4.21",
+        "@vue/compiler-sfc": "3.4.21",
+        "@vue/shared": "3.4.21",
+        "cac": "6.7.9",
+        "debug": "4.3.7",
+        "estree-walker": "2.0.2",
+        "express": "4.20.0",
+        "fast-glob": "3.3.3",
+        "fs-extra": "10.1.0",
+        "hash-sum": "2.0.0",
+        "jsonc-parser": "3.3.1",
+        "magic-string": "0.30.11",
+        "picocolors": "1.1.0",
+        "terser": "^5.4.0",
+        "unplugin-auto-import": "19.1.0"
+      },
+      "bin": {
+        "uni": "bin/uni.js"
+      },
+      "engines": {
+        "node": "^14.18.0 || >=16.0.0"
+      },
+      "peerDependencies": {
+        "vite": "5.2.8"
+      }
+    },
+    "node_modules/@dcloudio/vite-plugin-uni/node_modules/picomatch": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.4.tgz",
+      "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/@dcloudio/vite-plugin-uni/node_modules/unplugin-auto-import": {
+      "version": "19.1.0",
+      "resolved": "https://registry.npmmirror.com/unplugin-auto-import/-/unplugin-auto-import-19.1.0.tgz",
+      "integrity": "sha512-B+TGBEBHqY9aR+7YfShfLujETOHstzpV+yaqgy5PkfV0QG7Py+TYMX7vJ9W4SrysHR+UzR+gzcx/nuZjmPeclA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "local-pkg": "^1.0.0",
+        "magic-string": "^0.30.17",
+        "picomatch": "^4.0.2",
+        "unimport": "^4.1.1",
+        "unplugin": "^2.2.0",
+        "unplugin-utils": "^0.2.4"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@nuxt/kit": "^3.2.2",
+        "@vueuse/core": "*"
+      },
+      "peerDependenciesMeta": {
+        "@nuxt/kit": {
+          "optional": true
+        },
+        "@vueuse/core": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@dcloudio/vite-plugin-uni/node_modules/unplugin-auto-import/node_modules/magic-string": {
+      "version": "0.30.21",
+      "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz",
+      "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/sourcemap-codec": "^1.5.5"
+      }
+    },
+    "node_modules/@esbuild/aix-ppc64": {
+      "version": "0.20.2",
+      "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
+      "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
+      "cpu": [
+        "ppc64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "aix"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/android-arm": {
+      "version": "0.20.2",
+      "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
+      "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
+      "cpu": [
+        "arm"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/android-arm64": {
+      "version": "0.20.2",
+      "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
+      "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
+      "cpu": [
+        "arm64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/android-x64": {
+      "version": "0.20.2",
+      "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
+      "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
+      "cpu": [
+        "x64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/darwin-arm64": {
+      "version": "0.20.2",
+      "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
+      "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
+      "cpu": [
+        "arm64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/darwin-x64": {
+      "version": "0.20.2",
+      "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
+      "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
+      "cpu": [
+        "x64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/freebsd-arm64": {
+      "version": "0.20.2",
+      "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
+      "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
+      "cpu": [
+        "arm64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/freebsd-x64": {
+      "version": "0.20.2",
+      "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
+      "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
+      "cpu": [
+        "x64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-arm": {
+      "version": "0.20.2",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
+      "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
+      "cpu": [
+        "arm"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-arm64": {
+      "version": "0.20.2",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
+      "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
+      "cpu": [
+        "arm64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-ia32": {
+      "version": "0.20.2",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
+      "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
+      "cpu": [
+        "ia32"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-loong64": {
+      "version": "0.20.2",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
+      "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
+      "cpu": [
+        "loong64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-mips64el": {
+      "version": "0.20.2",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
+      "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
+      "cpu": [
+        "mips64el"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-ppc64": {
+      "version": "0.20.2",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
+      "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
+      "cpu": [
+        "ppc64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-riscv64": {
+      "version": "0.20.2",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
+      "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
+      "cpu": [
+        "riscv64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-s390x": {
+      "version": "0.20.2",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
+      "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
+      "cpu": [
+        "s390x"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-x64": {
+      "version": "0.20.2",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
+      "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
+      "cpu": [
+        "x64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/netbsd-x64": {
+      "version": "0.20.2",
+      "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
+      "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
+      "cpu": [
+        "x64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/openbsd-x64": {
+      "version": "0.20.2",
+      "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
+      "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
+      "cpu": [
+        "x64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/sunos-x64": {
+      "version": "0.20.2",
+      "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
+      "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
+      "cpu": [
+        "x64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "sunos"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/win32-arm64": {
+      "version": "0.20.2",
+      "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
+      "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/win32-ia32": {
+      "version": "0.20.2",
+      "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
+      "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
+      "cpu": [
+        "ia32"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/win32-x64": {
+      "version": "0.20.2",
+      "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
+      "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
+      "cpu": [
+        "x64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@intlify/core-base": {
+      "version": "9.1.9",
+      "resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-9.1.9.tgz",
+      "integrity": "sha512-x5T0p/Ja0S8hs5xs+ImKyYckVkL4CzcEXykVYYV6rcbXxJTe2o58IquSqX9bdncVKbRZP7GlBU1EcRaQEEJ+vw==",
+      "license": "MIT",
+      "dependencies": {
+        "@intlify/devtools-if": "9.1.9",
+        "@intlify/message-compiler": "9.1.9",
+        "@intlify/message-resolver": "9.1.9",
+        "@intlify/runtime": "9.1.9",
+        "@intlify/shared": "9.1.9",
+        "@intlify/vue-devtools": "9.1.9"
+      },
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@intlify/devtools-if": {
+      "version": "9.1.9",
+      "resolved": "https://registry.npmmirror.com/@intlify/devtools-if/-/devtools-if-9.1.9.tgz",
+      "integrity": "sha512-oKSMKjttG3Ut/1UGEZjSdghuP3fwA15zpDPcjkf/1FjlOIm6uIBGMNS5jXzsZy593u+P/YcnrZD6cD3IVFz9vQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@intlify/shared": "9.1.9"
+      },
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@intlify/message-compiler": {
+      "version": "9.1.9",
+      "resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-9.1.9.tgz",
+      "integrity": "sha512-6YgCMF46Xd0IH2hMRLCssZI3gFG4aywidoWQ3QP4RGYQXQYYfFC54DxhSgfIPpVoPLQ+4AD29eoYmhiHZ+qLFQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@intlify/message-resolver": "9.1.9",
+        "@intlify/shared": "9.1.9",
+        "source-map": "0.6.1"
+      },
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@intlify/message-resolver": {
+      "version": "9.1.9",
+      "resolved": "https://registry.npmmirror.com/@intlify/message-resolver/-/message-resolver-9.1.9.tgz",
+      "integrity": "sha512-Lx/DBpigeK0sz2BBbzv5mu9/dAlt98HxwbG7xLawC3O2xMF9MNWU5FtOziwYG6TDIjNq0O/3ZbOJAxwITIWXEA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@intlify/runtime": {
+      "version": "9.1.9",
+      "resolved": "https://registry.npmmirror.com/@intlify/runtime/-/runtime-9.1.9.tgz",
+      "integrity": "sha512-XgPw8+UlHCiie3fI41HPVa/VDJb3/aSH7bLhY1hJvlvNV713PFtb4p4Jo+rlE0gAoMsMCGcsiT982fImolSltg==",
+      "license": "MIT",
+      "dependencies": {
+        "@intlify/message-compiler": "9.1.9",
+        "@intlify/message-resolver": "9.1.9",
+        "@intlify/shared": "9.1.9"
+      },
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@intlify/shared": {
+      "version": "9.1.9",
+      "resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-9.1.9.tgz",
+      "integrity": "sha512-xKGM1d0EAxdDFCWedcYXOm6V5Pfw/TMudd6/qCdEb4tv0hk9EKeg7lwQF1azE0dP2phvx0yXxrt7UQK+IZjNdw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@intlify/vue-devtools": {
+      "version": "9.1.9",
+      "resolved": "https://registry.npmmirror.com/@intlify/vue-devtools/-/vue-devtools-9.1.9.tgz",
+      "integrity": "sha512-YPehH9uL4vZcGXky4Ev5qQIITnHKIvsD2GKGXgqf+05osMUI6WSEQHaN9USRa318Rs8RyyPCiDfmA0hRu3k7og==",
+      "license": "MIT",
+      "dependencies": {
+        "@intlify/message-resolver": "9.1.9",
+        "@intlify/runtime": "9.1.9",
+        "@intlify/shared": "9.1.9"
+      },
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@jimp/bmp": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/@jimp/bmp/-/bmp-0.10.3.tgz",
+      "integrity": "sha512-keMOc5woiDmONXsB/6aXLR4Z5Q+v8lFq3EY2rcj2FmstbDMhRuGbmcBxlEgOqfRjwvtf/wOtJ3Of37oAWtVfLg==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.7.2",
+        "@jimp/utils": "^0.10.3",
+        "bmp-js": "^0.1.0",
+        "core-js": "^3.4.1"
+      },
+      "peerDependencies": {
+        "@jimp/custom": ">=0.3.5"
+      }
+    },
+    "node_modules/@jimp/core": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/@jimp/core/-/core-0.10.3.tgz",
+      "integrity": "sha512-Gd5IpL3U2bFIO57Fh/OA3HCpWm4uW/pU01E75rI03BXfTdz3T+J7TwvyG1XaqsQ7/DSlS99GXtLQPlfFIe28UA==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.7.2",
+        "@jimp/utils": "^0.10.3",
+        "any-base": "^1.1.0",
+        "buffer": "^5.2.0",
+        "core-js": "^3.4.1",
+        "exif-parser": "^0.1.12",
+        "file-type": "^9.0.0",
+        "load-bmfont": "^1.3.1",
+        "mkdirp": "^0.5.1",
+        "phin": "^2.9.1",
+        "pixelmatch": "^4.0.2",
+        "tinycolor2": "^1.4.1"
+      }
+    },
+    "node_modules/@jimp/custom": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/@jimp/custom/-/custom-0.10.3.tgz",
+      "integrity": "sha512-nZmSI+jwTi5IRyNLbKSXQovoeqsw+D0Jn0SxW08wYQvdkiWA8bTlDQFgQ7HVwCAKBm8oKkDB/ZEo9qvHJ+1gAQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.7.2",
+        "@jimp/core": "^0.10.3",
+        "core-js": "^3.4.1"
+      }
+    },
+    "node_modules/@jimp/gif": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/@jimp/gif/-/gif-0.10.3.tgz",
+      "integrity": "sha512-vjlRodSfz1CrUvvrnUuD/DsLK1GHB/yDZXHthVdZu23zYJIW7/WrIiD1IgQ5wOMV7NocfrvPn2iqUfBP81/WWA==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.7.2",
+        "@jimp/utils": "^0.10.3",
+        "core-js": "^3.4.1",
+        "omggif": "^1.0.9"
+      },
+      "peerDependencies": {
+        "@jimp/custom": ">=0.3.5"
+      }
+    },
+    "node_modules/@jimp/jpeg": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/@jimp/jpeg/-/jpeg-0.10.3.tgz",
+      "integrity": "sha512-AAANwgUZOt6f6P7LZxY9lyJ9xclqutYJlsxt3JbriXUGJgrrFAIkcKcqv1nObgmQASSAQKYaMV9KdHjMlWFKlQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.7.2",
+        "@jimp/utils": "^0.10.3",
+        "core-js": "^3.4.1",
+        "jpeg-js": "^0.3.4"
+      },
+      "peerDependencies": {
+        "@jimp/custom": ">=0.3.5"
+      }
+    },
+    "node_modules/@jimp/plugin-blit": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/@jimp/plugin-blit/-/plugin-blit-0.10.3.tgz",
+      "integrity": "sha512-5zlKlCfx4JWw9qUVC7GI4DzXyxDWyFvgZLaoGFoT00mlXlN75SarlDwc9iZ/2e2kp4bJWxz3cGgG4G/WXrbg3Q==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.7.2",
+        "@jimp/utils": "^0.10.3",
+        "core-js": "^3.4.1"
+      },
+      "peerDependencies": {
+        "@jimp/custom": ">=0.3.5"
+      }
+    },
+    "node_modules/@jimp/plugin-blur": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/@jimp/plugin-blur/-/plugin-blur-0.10.3.tgz",
+      "integrity": "sha512-cTOK3rjh1Yjh23jSfA6EHCHjsPJDEGLC8K2y9gM7dnTUK1y9NNmkFS23uHpyjgsWFIoH9oRh2SpEs3INjCpZhQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.7.2",
+        "@jimp/utils": "^0.10.3",
+        "core-js": "^3.4.1"
+      },
+      "peerDependencies": {
+        "@jimp/custom": ">=0.3.5"
+      }
+    },
+    "node_modules/@jimp/plugin-circle": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/@jimp/plugin-circle/-/plugin-circle-0.10.3.tgz",
+      "integrity": "sha512-51GAPIVelqAcfuUpaM5JWJ0iWl4vEjNXB7p4P7SX5udugK5bxXUjO6KA2qgWmdpHuCKtoNgkzWU9fNSuYp7tCA==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.7.2",
+        "@jimp/utils": "^0.10.3",
+        "core-js": "^3.4.1"
+      },
+      "peerDependencies": {
+        "@jimp/custom": ">=0.3.5"
+      }
+    },
+    "node_modules/@jimp/plugin-color": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/@jimp/plugin-color/-/plugin-color-0.10.3.tgz",
+      "integrity": "sha512-RgeHUElmlTH7vpI4WyQrz6u59spiKfVQbsG/XUzfWGamFSixa24ZDwX/yV/Ts+eNaz7pZeIuv533qmKPvw2ujg==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.7.2",
+        "@jimp/utils": "^0.10.3",
+        "core-js": "^3.4.1",
+        "tinycolor2": "^1.4.1"
+      },
+      "peerDependencies": {
+        "@jimp/custom": ">=0.3.5"
+      }
+    },
+    "node_modules/@jimp/plugin-contain": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/@jimp/plugin-contain/-/plugin-contain-0.10.3.tgz",
+      "integrity": "sha512-bYJKW9dqzcB0Ihc6u7jSyKa3juStzbLs2LFr6fu8TzA2WkMS/R8h+ddkiO36+F9ILTWHP0CIA3HFe5OdOGcigw==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.7.2",
+        "@jimp/utils": "^0.10.3",
+        "core-js": "^3.4.1"
+      },
+      "peerDependencies": {
+        "@jimp/custom": ">=0.3.5",
+        "@jimp/plugin-blit": ">=0.3.5",
+        "@jimp/plugin-resize": ">=0.3.5",
+        "@jimp/plugin-scale": ">=0.3.5"
+      }
+    },
+    "node_modules/@jimp/plugin-cover": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/@jimp/plugin-cover/-/plugin-cover-0.10.3.tgz",
+      "integrity": "sha512-pOxu0cM0BRPzdV468n4dMocJXoMbTnARDY/EpC3ZW15SpMuc/dr1KhWQHgoQX5kVW1Wt8zgqREAJJCQ5KuPKDA==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.7.2",
+        "@jimp/utils": "^0.10.3",
+        "core-js": "^3.4.1"
+      },
+      "peerDependencies": {
+        "@jimp/custom": ">=0.3.5",
+        "@jimp/plugin-crop": ">=0.3.5",
+        "@jimp/plugin-resize": ">=0.3.5",
+        "@jimp/plugin-scale": ">=0.3.5"
+      }
+    },
+    "node_modules/@jimp/plugin-crop": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/@jimp/plugin-crop/-/plugin-crop-0.10.3.tgz",
+      "integrity": "sha512-nB7HgOjjl9PgdHr076xZ3Sr6qHYzeBYBs9qvs3tfEEUeYMNnvzgCCGtUl6eMakazZFCMk3mhKmcB9zQuHFOvkg==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.7.2",
+        "@jimp/utils": "^0.10.3",
+        "core-js": "^3.4.1"
+      },
+      "peerDependencies": {
+        "@jimp/custom": ">=0.3.5"
+      }
+    },
+    "node_modules/@jimp/plugin-displace": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/@jimp/plugin-displace/-/plugin-displace-0.10.3.tgz",
+      "integrity": "sha512-8t3fVKCH5IVqI4lewe4lFFjpxxr69SQCz5/tlpDLQZsrNScNJivHdQ09zljTrVTCSgeCqQJIKgH2Q7Sk/pAZ0w==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.7.2",
+        "@jimp/utils": "^0.10.3",
+        "core-js": "^3.4.1"
+      },
+      "peerDependencies": {
+        "@jimp/custom": ">=0.3.5"
+      }
+    },
+    "node_modules/@jimp/plugin-dither": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/@jimp/plugin-dither/-/plugin-dither-0.10.3.tgz",
+      "integrity": "sha512-JCX/oNSnEg1kGQ8ffZ66bEgQOLCY3Rn+lrd6v1jjLy/mn9YVZTMsxLtGCXpiCDC2wG/KTmi4862ysmP9do9dAQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.7.2",
+        "@jimp/utils": "^0.10.3",
+        "core-js": "^3.4.1"
+      },
+      "peerDependencies": {
+        "@jimp/custom": ">=0.3.5"
+      }
+    },
+    "node_modules/@jimp/plugin-fisheye": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/@jimp/plugin-fisheye/-/plugin-fisheye-0.10.3.tgz",
+      "integrity": "sha512-RRZb1wqe+xdocGcFtj2xHU7sF7xmEZmIa6BmrfSchjyA2b32TGPWKnP3qyj7p6LWEsXn+19hRYbjfyzyebPElQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.7.2",
+        "@jimp/utils": "^0.10.3",
+        "core-js": "^3.4.1"
+      },
+      "peerDependencies": {
+        "@jimp/custom": ">=0.3.5"
+      }
+    },
+    "node_modules/@jimp/plugin-flip": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/@jimp/plugin-flip/-/plugin-flip-0.10.3.tgz",
+      "integrity": "sha512-0epbi8XEzp0wmSjoW9IB0iMu0yNF17aZOxLdURCN3Zr+8nWPs5VNIMqSVa1Y62GSyiMDpVpKF/ITiXre+EqrPg==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.7.2",
+        "@jimp/utils": "^0.10.3",
+        "core-js": "^3.4.1"
+      },
+      "peerDependencies": {
+        "@jimp/custom": ">=0.3.5",
+        "@jimp/plugin-rotate": ">=0.3.5"
+      }
+    },
+    "node_modules/@jimp/plugin-gaussian": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/@jimp/plugin-gaussian/-/plugin-gaussian-0.10.3.tgz",
+      "integrity": "sha512-25eHlFbHUDnMMGpgRBBeQ2AMI4wsqCg46sue0KklI+c2BaZ+dGXmJA5uT8RTOrt64/K9Wz5E+2n7eBnny4dfpQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.7.2",
+        "@jimp/utils": "^0.10.3",
+        "core-js": "^3.4.1"
+      },
+      "peerDependencies": {
+        "@jimp/custom": ">=0.3.5"
+      }
+    },
+    "node_modules/@jimp/plugin-invert": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/@jimp/plugin-invert/-/plugin-invert-0.10.3.tgz",
+      "integrity": "sha512-effYSApWY/FbtlzqsKXlTLkgloKUiHBKjkQnqh5RL4oQxh/33j6aX+HFdDyQKtsXb8CMd4xd7wyiD2YYabTa0g==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.7.2",
+        "@jimp/utils": "^0.10.3",
+        "core-js": "^3.4.1"
+      },
+      "peerDependencies": {
+        "@jimp/custom": ">=0.3.5"
+      }
+    },
+    "node_modules/@jimp/plugin-mask": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/@jimp/plugin-mask/-/plugin-mask-0.10.3.tgz",
+      "integrity": "sha512-twrg8q8TIhM9Z6Jcu9/5f+OCAPaECb0eKrrbbIajJqJ3bCUlj5zbfgIhiQIzjPJ6KjpnFPSqHQfHkU1Vvk/nVw==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.7.2",
+        "@jimp/utils": "^0.10.3",
+        "core-js": "^3.4.1"
+      },
+      "peerDependencies": {
+        "@jimp/custom": ">=0.3.5"
+      }
+    },
+    "node_modules/@jimp/plugin-normalize": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/@jimp/plugin-normalize/-/plugin-normalize-0.10.3.tgz",
+      "integrity": "sha512-xkb5eZI/mMlbwKkDN79+1/t/+DBo8bBXZUMsT4gkFgMRKNRZ6NQPxlv1d3QpRzlocsl6UMxrHnhgnXdLAcgrXw==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.7.2",
+        "@jimp/utils": "^0.10.3",
+        "core-js": "^3.4.1"
+      },
+      "peerDependencies": {
+        "@jimp/custom": ">=0.3.5"
+      }
+    },
+    "node_modules/@jimp/plugin-print": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/@jimp/plugin-print/-/plugin-print-0.10.3.tgz",
+      "integrity": "sha512-wjRiI6yjXsAgMe6kVjizP+RgleUCLkH256dskjoNvJzmzbEfO7xQw9g6M02VET+emnbY0CO83IkrGm2q43VRyg==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.7.2",
+        "@jimp/utils": "^0.10.3",
+        "core-js": "^3.4.1",
+        "load-bmfont": "^1.4.0"
+      },
+      "peerDependencies": {
+        "@jimp/custom": ">=0.3.5",
+        "@jimp/plugin-blit": ">=0.3.5"
+      }
+    },
+    "node_modules/@jimp/plugin-resize": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/@jimp/plugin-resize/-/plugin-resize-0.10.3.tgz",
+      "integrity": "sha512-rf8YmEB1d7Sg+g4LpqF0Mp+dfXfb6JFJkwlAIWPUOR7lGsPWALavEwTW91c0etEdnp0+JB9AFpy6zqq7Lwkq6w==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.7.2",
+        "@jimp/utils": "^0.10.3",
+        "core-js": "^3.4.1"
+      },
+      "peerDependencies": {
+        "@jimp/custom": ">=0.3.5"
+      }
+    },
+    "node_modules/@jimp/plugin-rotate": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/@jimp/plugin-rotate/-/plugin-rotate-0.10.3.tgz",
+      "integrity": "sha512-YXLlRjm18fkW9MOHUaVAxWjvgZM851ofOipytz5FyKp4KZWDLk+dZK1JNmVmK7MyVmAzZ5jsgSLhIgj+GgN0Eg==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.7.2",
+        "@jimp/utils": "^0.10.3",
+        "core-js": "^3.4.1"
+      },
+      "peerDependencies": {
+        "@jimp/custom": ">=0.3.5",
+        "@jimp/plugin-blit": ">=0.3.5",
+        "@jimp/plugin-crop": ">=0.3.5",
+        "@jimp/plugin-resize": ">=0.3.5"
+      }
+    },
+    "node_modules/@jimp/plugin-scale": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/@jimp/plugin-scale/-/plugin-scale-0.10.3.tgz",
+      "integrity": "sha512-5DXD7x7WVcX1gUgnlFXQa8F+Q3ThRYwJm+aesgrYvDOY+xzRoRSdQvhmdd4JEEue3lyX44DvBSgCIHPtGcEPaw==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.7.2",
+        "@jimp/utils": "^0.10.3",
+        "core-js": "^3.4.1"
+      },
+      "peerDependencies": {
+        "@jimp/custom": ">=0.3.5",
+        "@jimp/plugin-resize": ">=0.3.5"
+      }
+    },
+    "node_modules/@jimp/plugin-shadow": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/@jimp/plugin-shadow/-/plugin-shadow-0.10.3.tgz",
+      "integrity": "sha512-/nkFXpt2zVcdP4ETdkAUL0fSzyrC5ZFxdcphbYBodqD7fXNqChS/Un1eD4xCXWEpW8cnG9dixZgQgStjywH0Mg==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.7.2",
+        "@jimp/utils": "^0.10.3",
+        "core-js": "^3.4.1"
+      },
+      "peerDependencies": {
+        "@jimp/custom": ">=0.3.5",
+        "@jimp/plugin-blur": ">=0.3.5",
+        "@jimp/plugin-resize": ">=0.3.5"
+      }
+    },
+    "node_modules/@jimp/plugin-threshold": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/@jimp/plugin-threshold/-/plugin-threshold-0.10.3.tgz",
+      "integrity": "sha512-Dzh0Yq2wXP2SOnxcbbiyA4LJ2luwrdf1MghNIt9H+NX7B+IWw/N8qA2GuSm9n4BPGSLluuhdAWJqHcTiREriVA==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.7.2",
+        "@jimp/utils": "^0.10.3",
+        "core-js": "^3.4.1"
+      },
+      "peerDependencies": {
+        "@jimp/custom": ">=0.3.5",
+        "@jimp/plugin-color": ">=0.8.0",
+        "@jimp/plugin-resize": ">=0.8.0"
+      }
+    },
+    "node_modules/@jimp/plugins": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/@jimp/plugins/-/plugins-0.10.3.tgz",
+      "integrity": "sha512-jTT3/7hOScf0EIKiAXmxwayHhryhc1wWuIe3FrchjDjr9wgIGNN2a7XwCgPl3fML17DXK1x8EzDneCdh261bkw==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.7.2",
+        "@jimp/plugin-blit": "^0.10.3",
+        "@jimp/plugin-blur": "^0.10.3",
+        "@jimp/plugin-circle": "^0.10.3",
+        "@jimp/plugin-color": "^0.10.3",
+        "@jimp/plugin-contain": "^0.10.3",
+        "@jimp/plugin-cover": "^0.10.3",
+        "@jimp/plugin-crop": "^0.10.3",
+        "@jimp/plugin-displace": "^0.10.3",
+        "@jimp/plugin-dither": "^0.10.3",
+        "@jimp/plugin-fisheye": "^0.10.3",
+        "@jimp/plugin-flip": "^0.10.3",
+        "@jimp/plugin-gaussian": "^0.10.3",
+        "@jimp/plugin-invert": "^0.10.3",
+        "@jimp/plugin-mask": "^0.10.3",
+        "@jimp/plugin-normalize": "^0.10.3",
+        "@jimp/plugin-print": "^0.10.3",
+        "@jimp/plugin-resize": "^0.10.3",
+        "@jimp/plugin-rotate": "^0.10.3",
+        "@jimp/plugin-scale": "^0.10.3",
+        "@jimp/plugin-shadow": "^0.10.3",
+        "@jimp/plugin-threshold": "^0.10.3",
+        "core-js": "^3.4.1",
+        "timm": "^1.6.1"
+      },
+      "peerDependencies": {
+        "@jimp/custom": ">=0.3.5"
+      }
+    },
+    "node_modules/@jimp/png": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/@jimp/png/-/png-0.10.3.tgz",
+      "integrity": "sha512-YKqk/dkl+nGZxSYIDQrqhmaP8tC3IK8H7dFPnnzFVvbhDnyYunqBZZO3SaZUKTichClRw8k/CjBhbc+hifSGWg==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.7.2",
+        "@jimp/utils": "^0.10.3",
+        "core-js": "^3.4.1",
+        "pngjs": "^3.3.3"
+      },
+      "peerDependencies": {
+        "@jimp/custom": ">=0.3.5"
+      }
+    },
+    "node_modules/@jimp/tiff": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/@jimp/tiff/-/tiff-0.10.3.tgz",
+      "integrity": "sha512-7EsJzZ5Y/EtinkBGuwX3Bi4S+zgbKouxjt9c82VJTRJOQgLWsE/RHqcyRCOQBhHAZ9QexYmDz34medfLKdoX0g==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.7.2",
+        "core-js": "^3.4.1",
+        "utif": "^2.0.1"
+      },
+      "peerDependencies": {
+        "@jimp/custom": ">=0.3.5"
+      }
+    },
+    "node_modules/@jimp/types": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/@jimp/types/-/types-0.10.3.tgz",
+      "integrity": "sha512-XGmBakiHZqseSWr/puGN+CHzx0IKBSpsKlmEmsNV96HKDiP6eu8NSnwdGCEq2mmIHe0JNcg1hqg59hpwtQ7Tiw==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.7.2",
+        "@jimp/bmp": "^0.10.3",
+        "@jimp/gif": "^0.10.3",
+        "@jimp/jpeg": "^0.10.3",
+        "@jimp/png": "^0.10.3",
+        "@jimp/tiff": "^0.10.3",
+        "core-js": "^3.4.1",
+        "timm": "^1.6.1"
+      },
+      "peerDependencies": {
+        "@jimp/custom": ">=0.3.5"
+      }
+    },
+    "node_modules/@jimp/utils": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/@jimp/utils/-/utils-0.10.3.tgz",
+      "integrity": "sha512-VcSlQhkil4ReYmg1KkN+WqHyYfZ2XfZxDsKAHSfST1GEz/RQHxKZbX+KhFKtKflnL0F4e6DlNQj3vznMNXCR2w==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.7.2",
+        "core-js": "^3.4.1",
+        "regenerator-runtime": "^0.13.3"
+      }
+    },
+    "node_modules/@jimp/utils/node_modules/regenerator-runtime": {
+      "version": "0.13.11",
+      "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
+      "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
+      "license": "MIT"
+    },
+    "node_modules/@jridgewell/gen-mapping": {
+      "version": "0.3.13",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+      "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/sourcemap-codec": "^1.5.0",
+        "@jridgewell/trace-mapping": "^0.3.24"
+      }
+    },
+    "node_modules/@jridgewell/remapping": {
+      "version": "2.3.5",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+      "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/gen-mapping": "^0.3.5",
+        "@jridgewell/trace-mapping": "^0.3.24"
+      }
+    },
+    "node_modules/@jridgewell/resolve-uri": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+      "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/source-map": {
+      "version": "0.3.11",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/source-map/-/source-map-0.3.11.tgz",
+      "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/gen-mapping": "^0.3.5",
+        "@jridgewell/trace-mapping": "^0.3.25"
+      }
+    },
+    "node_modules/@jridgewell/sourcemap-codec": {
+      "version": "1.5.5",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+      "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+      "license": "MIT"
+    },
+    "node_modules/@jridgewell/trace-mapping": {
+      "version": "0.3.31",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+      "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/resolve-uri": "^3.1.0",
+        "@jridgewell/sourcemap-codec": "^1.4.14"
+      }
+    },
+    "node_modules/@nodelib/fs.scandir": {
+      "version": "2.1.5",
+      "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+      "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+      "license": "MIT",
+      "dependencies": {
+        "@nodelib/fs.stat": "2.0.5",
+        "run-parallel": "^1.1.9"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@nodelib/fs.stat": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+      "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@nodelib/fs.walk": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+      "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+      "license": "MIT",
+      "dependencies": {
+        "@nodelib/fs.scandir": "2.1.5",
+        "fastq": "^1.6.0"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@parcel/watcher": {
+      "version": "2.5.6",
+      "resolved": "https://registry.npmmirror.com/@parcel/watcher/-/watcher-2.5.6.tgz",
+      "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "detect-libc": "^2.0.3",
+        "is-glob": "^4.0.3",
+        "node-addon-api": "^7.0.0",
+        "picomatch": "^4.0.3"
+      },
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      },
+      "optionalDependencies": {
+        "@parcel/watcher-android-arm64": "2.5.6",
+        "@parcel/watcher-darwin-arm64": "2.5.6",
+        "@parcel/watcher-darwin-x64": "2.5.6",
+        "@parcel/watcher-freebsd-x64": "2.5.6",
+        "@parcel/watcher-linux-arm-glibc": "2.5.6",
+        "@parcel/watcher-linux-arm-musl": "2.5.6",
+        "@parcel/watcher-linux-arm64-glibc": "2.5.6",
+        "@parcel/watcher-linux-arm64-musl": "2.5.6",
+        "@parcel/watcher-linux-x64-glibc": "2.5.6",
+        "@parcel/watcher-linux-x64-musl": "2.5.6",
+        "@parcel/watcher-win32-arm64": "2.5.6",
+        "@parcel/watcher-win32-ia32": "2.5.6",
+        "@parcel/watcher-win32-x64": "2.5.6"
+      }
+    },
+    "node_modules/@parcel/watcher-android-arm64": {
+      "version": "2.5.6",
+      "resolved": "https://registry.npmmirror.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz",
+      "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-darwin-arm64": {
+      "version": "2.5.6",
+      "resolved": "https://registry.npmmirror.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz",
+      "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-darwin-x64": {
+      "version": "2.5.6",
+      "resolved": "https://registry.npmmirror.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz",
+      "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-freebsd-x64": {
+      "version": "2.5.6",
+      "resolved": "https://registry.npmmirror.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz",
+      "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-arm-glibc": {
+      "version": "2.5.6",
+      "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz",
+      "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-arm-musl": {
+      "version": "2.5.6",
+      "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz",
+      "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-arm64-glibc": {
+      "version": "2.5.6",
+      "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz",
+      "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-arm64-musl": {
+      "version": "2.5.6",
+      "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz",
+      "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-x64-glibc": {
+      "version": "2.5.6",
+      "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz",
+      "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-x64-musl": {
+      "version": "2.5.6",
+      "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz",
+      "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-win32-arm64": {
+      "version": "2.5.6",
+      "resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz",
+      "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-win32-ia32": {
+      "version": "2.5.6",
+      "resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz",
+      "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-win32-x64": {
+      "version": "2.5.6",
+      "resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz",
+      "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher/node_modules/picomatch": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.4.tgz",
+      "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/@rollup/pluginutils": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.1.0.tgz",
+      "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/estree": "^1.0.0",
+        "estree-walker": "^2.0.2",
+        "picomatch": "^2.3.1"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      },
+      "peerDependencies": {
+        "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
+      },
+      "peerDependenciesMeta": {
+        "rollup": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@rollup/rollup-android-arm-eabi": {
+      "version": "4.62.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz",
+      "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-android-arm64": {
+      "version": "4.62.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz",
+      "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-arm64": {
+      "version": "4.62.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz",
+      "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-x64": {
+      "version": "4.62.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz",
+      "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-freebsd-arm64": {
+      "version": "4.62.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz",
+      "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-freebsd-x64": {
+      "version": "4.62.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz",
+      "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+      "version": "4.62.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz",
+      "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+      "version": "4.62.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz",
+      "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-gnu": {
+      "version": "4.62.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz",
+      "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-musl": {
+      "version": "4.62.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz",
+      "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-loong64-gnu": {
+      "version": "4.62.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz",
+      "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-loong64-musl": {
+      "version": "4.62.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz",
+      "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+      "version": "4.62.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz",
+      "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-ppc64-musl": {
+      "version": "4.62.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz",
+      "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+      "version": "4.62.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz",
+      "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-riscv64-musl": {
+      "version": "4.62.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz",
+      "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-s390x-gnu": {
+      "version": "4.62.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz",
+      "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-gnu": {
+      "version": "4.62.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz",
+      "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-musl": {
+      "version": "4.62.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz",
+      "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-openbsd-x64": {
+      "version": "4.62.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz",
+      "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openbsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-openharmony-arm64": {
+      "version": "4.62.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz",
+      "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openharmony"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-arm64-msvc": {
+      "version": "4.62.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz",
+      "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-ia32-msvc": {
+      "version": "4.62.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz",
+      "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-x64-gnu": {
+      "version": "4.62.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz",
+      "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-x64-msvc": {
+      "version": "4.62.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz",
+      "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@types/estree": {
+      "version": "1.0.9",
+      "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.9.tgz",
+      "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
+      "license": "MIT"
+    },
+    "node_modules/@types/node": {
+      "version": "26.0.0",
+      "resolved": "https://registry.npmmirror.com/@types/node/-/node-26.0.0.tgz",
+      "integrity": "sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "undici-types": "~8.3.0"
+      }
+    },
+    "node_modules/@vitejs/plugin-legacy": {
+      "version": "5.3.2",
+      "resolved": "https://registry.npmmirror.com/@vitejs/plugin-legacy/-/plugin-legacy-5.3.2.tgz",
+      "integrity": "sha512-8moCOrIMaZ/Rjln0Q6GsH6s8fAt1JOI3k8nmfX4tXUxE5KAExVctSyOBk+A25GClsdSWqIk2yaUthH3KJ2X4tg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/core": "^7.23.9",
+        "@babel/preset-env": "^7.23.9",
+        "browserslist": "^4.23.0",
+        "browserslist-to-esbuild": "^2.1.1",
+        "core-js": "^3.36.0",
+        "magic-string": "^0.30.7",
+        "regenerator-runtime": "^0.14.1",
+        "systemjs": "^6.14.3"
+      },
+      "engines": {
+        "node": "^18.0.0 || >=20.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/vitejs/vite?sponsor=1"
+      },
+      "peerDependencies": {
+        "terser": "^5.4.0",
+        "vite": "^5.0.0"
+      }
+    },
+    "node_modules/@vitejs/plugin-vue": {
+      "version": "5.2.4",
+      "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
+      "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==",
+      "license": "MIT",
+      "engines": {
+        "node": "^18.0.0 || >=20.0.0"
+      },
+      "peerDependencies": {
+        "vite": "^5.0.0 || ^6.0.0",
+        "vue": "^3.2.25"
+      }
+    },
+    "node_modules/@vitejs/plugin-vue-jsx": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue-jsx/-/plugin-vue-jsx-3.1.0.tgz",
+      "integrity": "sha512-w9M6F3LSEU5kszVb9An2/MmXNxocAnUb3WhRr8bHlimhDrXNt6n6D2nJQR3UXpGlZHh/EsgouOHCsM8V3Ln+WA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/core": "^7.23.3",
+        "@babel/plugin-transform-typescript": "^7.23.3",
+        "@vue/babel-plugin-jsx": "^1.1.5"
+      },
+      "engines": {
+        "node": "^14.18.0 || >=16.0.0"
+      },
+      "peerDependencies": {
+        "vite": "^4.0.0 || ^5.0.0",
+        "vue": "^3.0.0"
+      }
+    },
+    "node_modules/@volar/language-core": {
+      "version": "1.11.1",
+      "resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-1.11.1.tgz",
+      "integrity": "sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@volar/source-map": "1.11.1"
+      }
+    },
+    "node_modules/@volar/source-map": {
+      "version": "1.11.1",
+      "resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-1.11.1.tgz",
+      "integrity": "sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "muggle-string": "^0.3.1"
+      }
+    },
+    "node_modules/@volar/typescript": {
+      "version": "1.11.1",
+      "resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-1.11.1.tgz",
+      "integrity": "sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@volar/language-core": "1.11.1",
+        "path-browserify": "^1.0.1"
+      }
+    },
+    "node_modules/@vue/babel-helper-vue-transform-on": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmmirror.com/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.5.0.tgz",
+      "integrity": "sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@vue/babel-plugin-jsx": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmmirror.com/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.5.0.tgz",
+      "integrity": "sha512-mneBhw1oOqCd2247O0Yw/mRwC9jIGACAJUlawkmMBiNmL4dGA2eMzuNZVNqOUfYTa6vqmND4CtOPzmEEEqLKFw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-module-imports": "^7.27.1",
+        "@babel/helper-plugin-utils": "^7.27.1",
+        "@babel/plugin-syntax-jsx": "^7.27.1",
+        "@babel/template": "^7.27.2",
+        "@babel/traverse": "^7.28.0",
+        "@babel/types": "^7.28.2",
+        "@vue/babel-helper-vue-transform-on": "1.5.0",
+        "@vue/babel-plugin-resolve-type": "1.5.0",
+        "@vue/shared": "^3.5.18"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      },
+      "peerDependenciesMeta": {
+        "@babel/core": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@vue/babel-plugin-jsx/node_modules/@babel/types": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.7.tgz",
+      "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.29.7",
+        "@babel/helper-validator-identifier": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@vue/babel-plugin-jsx/node_modules/@vue/shared": {
+      "version": "3.5.38",
+      "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.38.tgz",
+      "integrity": "sha512-FTW0AFZNaK5/mOqvGBwVfUlNLU38TiQn4+DQgIFUnrBBJQ1crMJ82yeGQLV5jyKFsO8yRukpbuP7x+nRbH6aug==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@vue/babel-plugin-resolve-type": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmmirror.com/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.5.0.tgz",
+      "integrity": "sha512-Wm/60o+53JwJODm4Knz47dxJnLDJ9FnKnGZJbUUf8nQRAtt6P+undLUAVU3Ha33LxOJe6IPoifRQ6F/0RrU31w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/code-frame": "^7.27.1",
+        "@babel/helper-module-imports": "^7.27.1",
+        "@babel/helper-plugin-utils": "^7.27.1",
+        "@babel/parser": "^7.28.0",
+        "@vue/compiler-sfc": "^3.5.18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sxzz"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@vue/babel-plugin-resolve-type/node_modules/@babel/code-frame": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.29.7.tgz",
+      "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-validator-identifier": "^7.29.7",
+        "js-tokens": "^4.0.0",
+        "picocolors": "^1.1.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@vue/babel-plugin-resolve-type/node_modules/@babel/parser": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.7.tgz",
+      "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/types": "^7.29.7"
+      },
+      "bin": {
+        "parser": "bin/babel-parser.js"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@vue/babel-plugin-resolve-type/node_modules/@babel/types": {
+      "version": "7.29.7",
+      "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.7.tgz",
+      "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.29.7",
+        "@babel/helper-validator-identifier": "^7.29.7"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@vue/babel-plugin-resolve-type/node_modules/@vue/compiler-core": {
+      "version": "3.5.38",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.38.tgz",
+      "integrity": "sha512-s99aGxWYig9ErHbct27KXEGhrBYlRI6c4MwAgXErOAbX9xiW37/uMa+XUDO69zLz83dng8UUZ70CTOJrLrYrEQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/parser": "^7.29.7",
+        "@vue/shared": "3.5.38",
+        "entities": "^7.0.1",
+        "estree-walker": "^2.0.2",
+        "source-map-js": "^1.2.1"
+      }
+    },
+    "node_modules/@vue/babel-plugin-resolve-type/node_modules/@vue/compiler-dom": {
+      "version": "3.5.38",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.38.tgz",
+      "integrity": "sha512-JTqp25l8aFfJYF7/KmsXZjAxJz7T+SjmTJLoXVjHtc2BrSgSiW2n9Aem/cWq1OPe68A8JL06B3eVdhlP0H4TVw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@vue/compiler-core": "3.5.38",
+        "@vue/shared": "3.5.38"
+      }
+    },
+    "node_modules/@vue/babel-plugin-resolve-type/node_modules/@vue/compiler-sfc": {
+      "version": "3.5.38",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.38.tgz",
+      "integrity": "sha512-DuA2GiZawSEW442iw/9+Fkol8hTgb4Ke5KkhmSry65QA7YuyMbIdy8p0XZRMvNwJdgRz307W8g1CSzdvS4nuNg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/parser": "^7.29.7",
+        "@vue/compiler-core": "3.5.38",
+        "@vue/compiler-dom": "3.5.38",
+        "@vue/compiler-ssr": "3.5.38",
+        "@vue/shared": "3.5.38",
+        "estree-walker": "^2.0.2",
+        "magic-string": "^0.30.21",
+        "postcss": "^8.5.15",
+        "source-map-js": "^1.2.1"
+      }
+    },
+    "node_modules/@vue/babel-plugin-resolve-type/node_modules/@vue/compiler-ssr": {
+      "version": "3.5.38",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.38.tgz",
+      "integrity": "sha512-7s+W5Gc42FGxZMcuwl8H5B29T8BJPMdBT7KHFE+BbAuZ/iTEdTtv7z2XiMjiaUUw4w3ZcCEdHs36RuYJ2VA7bA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@vue/compiler-dom": "3.5.38",
+        "@vue/shared": "3.5.38"
+      }
+    },
+    "node_modules/@vue/babel-plugin-resolve-type/node_modules/@vue/shared": {
+      "version": "3.5.38",
+      "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.38.tgz",
+      "integrity": "sha512-FTW0AFZNaK5/mOqvGBwVfUlNLU38TiQn4+DQgIFUnrBBJQ1crMJ82yeGQLV5jyKFsO8yRukpbuP7x+nRbH6aug==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@vue/babel-plugin-resolve-type/node_modules/entities": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmmirror.com/entities/-/entities-7.0.1.tgz",
+      "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
+      "dev": true,
+      "license": "BSD-2-Clause",
+      "engines": {
+        "node": ">=0.12"
+      },
+      "funding": {
+        "url": "https://github.com/fb55/entities?sponsor=1"
+      }
+    },
+    "node_modules/@vue/babel-plugin-resolve-type/node_modules/magic-string": {
+      "version": "0.30.21",
+      "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz",
+      "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/sourcemap-codec": "^1.5.5"
+      }
+    },
+    "node_modules/@vue/babel-plugin-resolve-type/node_modules/picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+      "dev": true,
+      "license": "ISC"
+    },
+    "node_modules/@vue/compiler-core": {
+      "version": "3.4.21",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.4.21.tgz",
+      "integrity": "sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/parser": "^7.23.9",
+        "@vue/shared": "3.4.21",
+        "entities": "^4.5.0",
+        "estree-walker": "^2.0.2",
+        "source-map-js": "^1.0.2"
+      }
+    },
+    "node_modules/@vue/compiler-dom": {
+      "version": "3.4.21",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.4.21.tgz",
+      "integrity": "sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/compiler-core": "3.4.21",
+        "@vue/shared": "3.4.21"
+      }
+    },
+    "node_modules/@vue/compiler-sfc": {
+      "version": "3.4.21",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.4.21.tgz",
+      "integrity": "sha512-me7epoTxYlY+2CUM7hy9PCDdpMPfIwrOvAXud2Upk10g4YLv9UBW7kL798TvMeDhPthkZ0CONNrK2GoeI1ODiQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/parser": "^7.23.9",
+        "@vue/compiler-core": "3.4.21",
+        "@vue/compiler-dom": "3.4.21",
+        "@vue/compiler-ssr": "3.4.21",
+        "@vue/shared": "3.4.21",
+        "estree-walker": "^2.0.2",
+        "magic-string": "^0.30.7",
+        "postcss": "^8.4.35",
+        "source-map-js": "^1.0.2"
+      }
+    },
+    "node_modules/@vue/compiler-ssr": {
+      "version": "3.4.21",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.4.21.tgz",
+      "integrity": "sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/compiler-dom": "3.4.21",
+        "@vue/shared": "3.4.21"
+      }
+    },
+    "node_modules/@vue/consolidate": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/@vue/consolidate/-/consolidate-1.0.0.tgz",
+      "integrity": "sha512-oTyUE+QHIzLw2PpV14GD/c7EohDyP64xCniWTcqcEmTd699eFqTIwOmtDYjcO1j3QgdXoJEoWv1/cCdLrRoOfg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.12.0"
+      }
+    },
+    "node_modules/@vue/devtools-api": {
+      "version": "6.6.4",
+      "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
+      "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
+      "license": "MIT"
+    },
+    "node_modules/@vue/language-core": {
+      "version": "1.8.27",
+      "resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-1.8.27.tgz",
+      "integrity": "sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@volar/language-core": "~1.11.1",
+        "@volar/source-map": "~1.11.1",
+        "@vue/compiler-dom": "^3.3.0",
+        "@vue/shared": "^3.3.0",
+        "computeds": "^0.0.1",
+        "minimatch": "^9.0.3",
+        "muggle-string": "^0.3.1",
+        "path-browserify": "^1.0.1",
+        "vue-template-compiler": "^2.7.14"
+      },
+      "peerDependencies": {
+        "typescript": "*"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@vue/language-core/node_modules/brace-expansion": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.1.1.tgz",
+      "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "balanced-match": "^1.0.0"
+      }
+    },
+    "node_modules/@vue/language-core/node_modules/minimatch": {
+      "version": "9.0.9",
+      "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.9.tgz",
+      "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "brace-expansion": "^2.0.2"
+      },
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/@vue/reactivity": {
+      "version": "3.5.38",
+      "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.38.tgz",
+      "integrity": "sha512-pG6LV/NDNRbKizcUjFFLAfjaL8mcv4DmR9avNcUw2gDHBzZneuS2TWCmp633ynzxz9YYKNeEPK2I8Wraqy2HUQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@vue/shared": "3.5.38"
+      }
+    },
+    "node_modules/@vue/reactivity/node_modules/@vue/shared": {
+      "version": "3.5.38",
+      "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.38.tgz",
+      "integrity": "sha512-FTW0AFZNaK5/mOqvGBwVfUlNLU38TiQn4+DQgIFUnrBBJQ1crMJ82yeGQLV5jyKFsO8yRukpbuP7x+nRbH6aug==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@vue/runtime-core": {
+      "version": "3.5.38",
+      "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.38.tgz",
+      "integrity": "sha512-iyW8WVfF1CpCXxncZY5Ei6rSd6oZr5DgEom//fUjRBRl56AXPD+s9ATvukRt77ZFTuYlnVA1bxY+dJB94tWVYw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@vue/reactivity": "3.5.38",
+        "@vue/shared": "3.5.38"
+      }
+    },
+    "node_modules/@vue/runtime-core/node_modules/@vue/shared": {
+      "version": "3.5.38",
+      "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.38.tgz",
+      "integrity": "sha512-FTW0AFZNaK5/mOqvGBwVfUlNLU38TiQn4+DQgIFUnrBBJQ1crMJ82yeGQLV5jyKFsO8yRukpbuP7x+nRbH6aug==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@vue/runtime-dom": {
+      "version": "3.4.21",
+      "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.4.21.tgz",
+      "integrity": "sha512-gvf+C9cFpevsQxbkRBS1NpU8CqxKw0ebqMvLwcGQrNpx6gqRDodqKqA+A2VZZpQ9RpK2f9yfg8VbW/EpdFUOJw==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/runtime-core": "3.4.21",
+        "@vue/shared": "3.4.21",
+        "csstype": "^3.1.3"
+      }
+    },
+    "node_modules/@vue/runtime-dom/node_modules/@vue/reactivity": {
+      "version": "3.4.21",
+      "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.4.21.tgz",
+      "integrity": "sha512-UhenImdc0L0/4ahGCyEzc/pZNwVgcglGy9HVzJ1Bq2Mm9qXOpP8RyNTjookw/gOCUlXSEtuZ2fUg5nrHcoqJcw==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/shared": "3.4.21"
+      }
+    },
+    "node_modules/@vue/runtime-dom/node_modules/@vue/runtime-core": {
+      "version": "3.4.21",
+      "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.4.21.tgz",
+      "integrity": "sha512-pQthsuYzE1XcGZznTKn73G0s14eCJcjaLvp3/DKeYWoFacD9glJoqlNBxt3W2c5S40t6CCcpPf+jG01N3ULyrA==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/reactivity": "3.4.21",
+        "@vue/shared": "3.4.21"
+      }
+    },
+    "node_modules/@vue/server-renderer": {
+      "version": "3.4.21",
+      "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.4.21.tgz",
+      "integrity": "sha512-aV1gXyKSN6Rz+6kZ6kr5+Ll14YzmIbeuWe7ryJl5muJ4uwSwY/aStXTixx76TwkZFJLm1aAlA/HSWEJ4EyiMkg==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/compiler-ssr": "3.4.21",
+        "@vue/shared": "3.4.21"
+      },
+      "peerDependencies": {
+        "vue": "3.4.21"
+      }
+    },
+    "node_modules/@vue/shared": {
+      "version": "3.4.21",
+      "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.4.21.tgz",
+      "integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==",
+      "license": "MIT"
+    },
+    "node_modules/@vue/tsconfig": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmmirror.com/@vue/tsconfig/-/tsconfig-0.1.3.tgz",
+      "integrity": "sha512-kQVsh8yyWPvHpb8gIc9l/HIDiiVUy1amynLNpCy8p+FoCiZXCo6fQos5/097MmnNZc9AtseDsCrfkhqCrJ8Olg==",
+      "dev": true,
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/node": "*"
+      },
+      "peerDependenciesMeta": {
+        "@types/node": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/accepts": {
+      "version": "1.3.8",
+      "resolved": "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz",
+      "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "mime-types": "~2.1.34",
+        "negotiator": "0.6.3"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/acorn": {
+      "version": "8.17.0",
+      "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.17.0.tgz",
+      "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==",
+      "license": "MIT",
+      "bin": {
+        "acorn": "bin/acorn"
+      },
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/address": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmmirror.com/address/-/address-1.2.2.tgz",
+      "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 10.0.0"
+      }
+    },
+    "node_modules/adm-zip": {
+      "version": "0.5.16",
+      "resolved": "https://registry.npmmirror.com/adm-zip/-/adm-zip-0.5.16.tgz",
+      "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=12.0"
+      }
+    },
+    "node_modules/ansi-styles": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-3.2.1.tgz",
+      "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+      "license": "MIT",
+      "dependencies": {
+        "color-convert": "^1.9.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/any-base": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/any-base/-/any-base-1.1.0.tgz",
+      "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==",
+      "license": "MIT"
+    },
+    "node_modules/any-promise": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz",
+      "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/anymatch": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz",
+      "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+      "license": "ISC",
+      "dependencies": {
+        "normalize-path": "^3.0.0",
+        "picomatch": "^2.0.4"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/arg": {
+      "version": "5.0.2",
+      "resolved": "https://registry.npmmirror.com/arg/-/arg-5.0.2.tgz",
+      "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/array-flatten": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/array-flatten/-/array-flatten-1.1.1.tgz",
+      "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/autoprefixer": {
+      "version": "10.5.0",
+      "resolved": "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.5.0.tgz",
+      "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "browserslist": "^4.28.2",
+        "caniuse-lite": "^1.0.30001787",
+        "fraction.js": "^5.3.4",
+        "picocolors": "^1.1.1",
+        "postcss-value-parser": "^4.2.0"
+      },
+      "bin": {
+        "autoprefixer": "bin/autoprefixer"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      },
+      "peerDependencies": {
+        "postcss": "^8.1.0"
+      }
+    },
+    "node_modules/autoprefixer/node_modules/picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+      "dev": true,
+      "license": "ISC"
+    },
+    "node_modules/babel-plugin-polyfill-corejs2": {
+      "version": "0.4.17",
+      "resolved": "https://registry.npmmirror.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz",
+      "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/compat-data": "^7.28.6",
+        "@babel/helper-define-polyfill-provider": "^0.6.8",
+        "semver": "^6.3.1"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+      }
+    },
+    "node_modules/babel-plugin-polyfill-corejs3": {
+      "version": "0.14.2",
+      "resolved": "https://registry.npmmirror.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz",
+      "integrity": "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-define-polyfill-provider": "^0.6.8",
+        "core-js-compat": "^3.48.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+      }
+    },
+    "node_modules/babel-plugin-polyfill-regenerator": {
+      "version": "0.6.8",
+      "resolved": "https://registry.npmmirror.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz",
+      "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-define-polyfill-provider": "^0.6.8"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+      }
+    },
+    "node_modules/balanced-match": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
+      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/base64-js": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz",
+      "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/base64url": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/base64url/-/base64url-3.0.1.tgz",
+      "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/baseline-browser-mapping": {
+      "version": "2.10.38",
+      "resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz",
+      "integrity": "sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==",
+      "license": "Apache-2.0",
+      "bin": {
+        "baseline-browser-mapping": "dist/cli.cjs"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/binary-extensions": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz",
+      "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/bmp-js": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmmirror.com/bmp-js/-/bmp-js-0.1.0.tgz",
+      "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==",
+      "license": "MIT"
+    },
+    "node_modules/body-parser": {
+      "version": "1.20.3",
+      "resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.3.tgz",
+      "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "bytes": "3.1.2",
+        "content-type": "~1.0.5",
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "destroy": "1.2.0",
+        "http-errors": "2.0.0",
+        "iconv-lite": "0.4.24",
+        "on-finished": "2.4.1",
+        "qs": "6.13.0",
+        "raw-body": "2.5.2",
+        "type-is": "~1.6.18",
+        "unpipe": "1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8",
+        "npm": "1.2.8000 || >= 1.4.16"
+      }
+    },
+    "node_modules/body-parser/node_modules/debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "ms": "2.0.0"
+      }
+    },
+    "node_modules/body-parser/node_modules/ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/body-parser/node_modules/qs": {
+      "version": "6.13.0",
+      "resolved": "https://registry.npmmirror.com/qs/-/qs-6.13.0.tgz",
+      "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
+      "dev": true,
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "side-channel": "^1.0.6"
+      },
+      "engines": {
+        "node": ">=0.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/braces": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz",
+      "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+      "license": "MIT",
+      "dependencies": {
+        "fill-range": "^7.1.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/browserslist": {
+      "version": "4.28.2",
+      "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.2.tgz",
+      "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/browserslist"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "baseline-browser-mapping": "^2.10.12",
+        "caniuse-lite": "^1.0.30001782",
+        "electron-to-chromium": "^1.5.328",
+        "node-releases": "^2.0.36",
+        "update-browserslist-db": "^1.2.3"
+      },
+      "bin": {
+        "browserslist": "cli.js"
+      },
+      "engines": {
+        "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+      }
+    },
+    "node_modules/browserslist-to-esbuild": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmmirror.com/browserslist-to-esbuild/-/browserslist-to-esbuild-2.1.1.tgz",
+      "integrity": "sha512-KN+mty6C3e9AN8Z5dI1xeN15ExcRNeISoC3g7V0Kax/MMF9MSoYA2G7lkTTcVUFntiEjkpI0HNgqJC1NjdyNUw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "meow": "^13.0.0"
+      },
+      "bin": {
+        "browserslist-to-esbuild": "cli/index.js"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "browserslist": "*"
+      }
+    },
+    "node_modules/buffer": {
+      "version": "5.7.1",
+      "resolved": "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz",
+      "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "base64-js": "^1.3.1",
+        "ieee754": "^1.1.13"
+      }
+    },
+    "node_modules/buffer-equal": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmmirror.com/buffer-equal/-/buffer-equal-0.0.1.tgz",
+      "integrity": "sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/buffer-from": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz",
+      "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/bytes": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz",
+      "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/cac": {
+      "version": "6.7.9",
+      "resolved": "https://registry.npmmirror.com/cac/-/cac-6.7.9.tgz",
+      "integrity": "sha512-XN5qEpfNQCJ8jRaZgitSkkukjMRCGio+X3Ks5KUbGGlPbV+pSem1l9VuzooCBXOiMFshUZgyYqg6rgN8rjkb/w==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/call-bind-apply-helpers": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+      "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/call-bound": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz",
+      "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "get-intrinsic": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/camelcase-css": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/camelcase-css/-/camelcase-css-2.0.1.tgz",
+      "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/caniuse-lite": {
+      "version": "1.0.30001799",
+      "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz",
+      "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==",
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "CC-BY-4.0"
+    },
+    "node_modules/centra": {
+      "version": "2.7.0",
+      "resolved": "https://registry.npmmirror.com/centra/-/centra-2.7.0.tgz",
+      "integrity": "sha512-PbFMgMSrmgx6uxCdm57RUos9Tc3fclMvhLSATYN39XsDV29B89zZ3KA89jmY0vwSGazyU+uerqwa6t+KaodPcg==",
+      "license": "MIT",
+      "dependencies": {
+        "follow-redirects": "^1.15.6"
+      }
+    },
+    "node_modules/chalk": {
+      "version": "2.4.2",
+      "resolved": "https://registry.npmmirror.com/chalk/-/chalk-2.4.2.tgz",
+      "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-styles": "^3.2.1",
+        "escape-string-regexp": "^1.0.5",
+        "supports-color": "^5.3.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/chokidar": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz",
+      "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+      "license": "MIT",
+      "dependencies": {
+        "anymatch": "~3.1.2",
+        "braces": "~3.0.2",
+        "glob-parent": "~5.1.2",
+        "is-binary-path": "~2.1.0",
+        "is-glob": "~4.0.1",
+        "normalize-path": "~3.0.0",
+        "readdirp": "~3.6.0"
+      },
+      "engines": {
+        "node": ">= 8.10.0"
+      },
+      "funding": {
+        "url": "https://paulmillr.com/funding/"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/color-convert": {
+      "version": "1.9.3",
+      "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz",
+      "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+      "license": "MIT",
+      "dependencies": {
+        "color-name": "1.1.3"
+      }
+    },
+    "node_modules/color-name": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz",
+      "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+      "license": "MIT"
+    },
+    "node_modules/commander": {
+      "version": "2.20.3",
+      "resolved": "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz",
+      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/compare-versions": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmmirror.com/compare-versions/-/compare-versions-3.6.0.tgz",
+      "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==",
+      "license": "MIT"
+    },
+    "node_modules/computeds": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmmirror.com/computeds/-/computeds-0.0.1.tgz",
+      "integrity": "sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/confbox": {
+      "version": "0.1.8",
+      "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz",
+      "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
+      "license": "MIT"
+    },
+    "node_modules/content-disposition": {
+      "version": "0.5.4",
+      "resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-0.5.4.tgz",
+      "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "safe-buffer": "5.2.1"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/content-type": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz",
+      "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/convert-source-map": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz",
+      "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+      "license": "MIT"
+    },
+    "node_modules/cookie": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.6.0.tgz",
+      "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/cookie-signature": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.0.6.tgz",
+      "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/core-js": {
+      "version": "3.49.0",
+      "resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.49.0.tgz",
+      "integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/core-js"
+      }
+    },
+    "node_modules/core-js-compat": {
+      "version": "3.49.0",
+      "resolved": "https://registry.npmmirror.com/core-js-compat/-/core-js-compat-3.49.0.tgz",
+      "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "browserslist": "^4.28.1"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/core-js"
+      }
+    },
+    "node_modules/cross-env": {
+      "version": "7.0.3",
+      "resolved": "https://registry.npmmirror.com/cross-env/-/cross-env-7.0.3.tgz",
+      "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "cross-spawn": "^7.0.1"
+      },
+      "bin": {
+        "cross-env": "src/bin/cross-env.js",
+        "cross-env-shell": "src/bin/cross-env-shell.js"
+      },
+      "engines": {
+        "node": ">=10.14",
+        "npm": ">=6",
+        "yarn": ">=1"
+      }
+    },
+    "node_modules/cross-spawn": {
+      "version": "7.0.6",
+      "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz",
+      "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "path-key": "^3.1.0",
+        "shebang-command": "^2.0.0",
+        "which": "^2.0.1"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/css-font-size-keywords": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/css-font-size-keywords/-/css-font-size-keywords-1.0.0.tgz",
+      "integrity": "sha512-Q+svMDbMlelgCfH/RVDKtTDaf5021O486ZThQPIpahnIjUkMUslC+WuOQSWTgGSrNCH08Y7tYNEmmy0hkfMI8Q==",
+      "license": "MIT"
+    },
+    "node_modules/css-font-stretch-keywords": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/css-font-stretch-keywords/-/css-font-stretch-keywords-1.0.1.tgz",
+      "integrity": "sha512-KmugPO2BNqoyp9zmBIUGwt58UQSfyk1X5DbOlkb2pckDXFSAfjsD5wenb88fNrD6fvS+vu90a/tsPpb9vb0SLg==",
+      "license": "MIT"
+    },
+    "node_modules/css-font-style-keywords": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/css-font-style-keywords/-/css-font-style-keywords-1.0.1.tgz",
+      "integrity": "sha512-0Fn0aTpcDktnR1RzaBYorIxQily85M2KXRpzmxQPgh8pxUN9Fcn00I8u9I3grNr1QXVgCl9T5Imx0ZwKU973Vg==",
+      "license": "MIT"
+    },
+    "node_modules/css-font-weight-keywords": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/css-font-weight-keywords/-/css-font-weight-keywords-1.0.0.tgz",
+      "integrity": "sha512-5So8/NH+oDD+EzsnF4iaG4ZFHQ3vaViePkL1ZbZ5iC/KrsCY+WHq/lvOgrtmuOQ9pBBZ1ADGpaf+A4lj1Z9eYA==",
+      "license": "MIT"
+    },
+    "node_modules/css-list-helpers": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/css-list-helpers/-/css-list-helpers-2.0.0.tgz",
+      "integrity": "sha512-9Bj8tZ0jWbAM3u/U6m/boAzAwLPwtjzFvwivr2piSvyVa3K3rChJzQy4RIHkNkKiZCHrEMWDJWtTR8UyVhdDnQ==",
+      "license": "MIT"
+    },
+    "node_modules/css-system-font-keywords": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/css-system-font-keywords/-/css-system-font-keywords-1.0.0.tgz",
+      "integrity": "sha512-1umTtVd/fXS25ftfjB71eASCrYhilmEsvDEI6wG/QplnmlfmVM5HkZ/ZX46DT5K3eblFPgLUHt5BRCb0YXkSFA==",
+      "license": "MIT"
+    },
+    "node_modules/cssesc": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz",
+      "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+      "license": "MIT",
+      "bin": {
+        "cssesc": "bin/cssesc"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/csstype": {
+      "version": "3.2.3",
+      "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz",
+      "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+      "license": "MIT"
+    },
+    "node_modules/de-indent": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz",
+      "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/debug": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.7.tgz",
+      "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/default-gateway": {
+      "version": "6.0.3",
+      "resolved": "https://registry.npmmirror.com/default-gateway/-/default-gateway-6.0.3.tgz",
+      "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==",
+      "dev": true,
+      "license": "BSD-2-Clause",
+      "dependencies": {
+        "execa": "^5.0.0"
+      },
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/depd": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz",
+      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/destroy": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/destroy/-/destroy-1.2.0.tgz",
+      "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8",
+        "npm": "1.2.8000 || >= 1.4.16"
+      }
+    },
+    "node_modules/detect-libc": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz",
+      "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "optional": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/didyoumean": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmmirror.com/didyoumean/-/didyoumean-1.2.2.tgz",
+      "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+      "dev": true,
+      "license": "Apache-2.0"
+    },
+    "node_modules/dlv": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmmirror.com/dlv/-/dlv-1.1.3.tgz",
+      "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/dom-walk": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmmirror.com/dom-walk/-/dom-walk-0.1.2.tgz",
+      "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w=="
+    },
+    "node_modules/dunder-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz",
+      "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "gopd": "^1.2.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/electron-to-chromium": {
+      "version": "1.5.376",
+      "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.376.tgz",
+      "integrity": "sha512-cUVA7/RvbFTEuw/i3obUwDTRIXojaxkResf+ibByPFxjc6XK3VNtcQXV0NSbAlJ0FMjcJGgftVVB4Qo184EXvA==",
+      "license": "ISC"
+    },
+    "node_modules/encodeurl": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz",
+      "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/entities": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmmirror.com/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/es-define-property": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
+      "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-errors": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz",
+      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-module-lexer": {
+      "version": "1.5.4",
+      "resolved": "https://registry.npmmirror.com/es-module-lexer/-/es-module-lexer-1.5.4.tgz",
+      "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==",
+      "license": "MIT"
+    },
+    "node_modules/es-object-atoms": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
+      "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/esbuild": {
+      "version": "0.20.2",
+      "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.20.2.tgz",
+      "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "bin": {
+        "esbuild": "bin/esbuild"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "optionalDependencies": {
+        "@esbuild/aix-ppc64": "0.20.2",
+        "@esbuild/android-arm": "0.20.2",
+        "@esbuild/android-arm64": "0.20.2",
+        "@esbuild/android-x64": "0.20.2",
+        "@esbuild/darwin-arm64": "0.20.2",
+        "@esbuild/darwin-x64": "0.20.2",
+        "@esbuild/freebsd-arm64": "0.20.2",
+        "@esbuild/freebsd-x64": "0.20.2",
+        "@esbuild/linux-arm": "0.20.2",
+        "@esbuild/linux-arm64": "0.20.2",
+        "@esbuild/linux-ia32": "0.20.2",
+        "@esbuild/linux-loong64": "0.20.2",
+        "@esbuild/linux-mips64el": "0.20.2",
+        "@esbuild/linux-ppc64": "0.20.2",
+        "@esbuild/linux-riscv64": "0.20.2",
+        "@esbuild/linux-s390x": "0.20.2",
+        "@esbuild/linux-x64": "0.20.2",
+        "@esbuild/netbsd-x64": "0.20.2",
+        "@esbuild/openbsd-x64": "0.20.2",
+        "@esbuild/sunos-x64": "0.20.2",
+        "@esbuild/win32-arm64": "0.20.2",
+        "@esbuild/win32-ia32": "0.20.2",
+        "@esbuild/win32-x64": "0.20.2"
+      }
+    },
+    "node_modules/escalade": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz",
+      "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/escape-string-regexp": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+      "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.8.0"
+      }
+    },
+    "node_modules/estree-walker": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
+      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+      "license": "MIT"
+    },
+    "node_modules/esutils": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz",
+      "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+      "dev": true,
+      "license": "BSD-2-Clause",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/etag": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz",
+      "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/execa": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmmirror.com/execa/-/execa-5.1.1.tgz",
+      "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "cross-spawn": "^7.0.3",
+        "get-stream": "^6.0.0",
+        "human-signals": "^2.1.0",
+        "is-stream": "^2.0.0",
+        "merge-stream": "^2.0.0",
+        "npm-run-path": "^4.0.1",
+        "onetime": "^5.1.2",
+        "signal-exit": "^3.0.3",
+        "strip-final-newline": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sindresorhus/execa?sponsor=1"
+      }
+    },
+    "node_modules/exif-parser": {
+      "version": "0.1.12",
+      "resolved": "https://registry.npmmirror.com/exif-parser/-/exif-parser-0.1.12.tgz",
+      "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="
+    },
+    "node_modules/express": {
+      "version": "4.20.0",
+      "resolved": "https://registry.npmmirror.com/express/-/express-4.20.0.tgz",
+      "integrity": "sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "accepts": "~1.3.8",
+        "array-flatten": "1.1.1",
+        "body-parser": "1.20.3",
+        "content-disposition": "0.5.4",
+        "content-type": "~1.0.4",
+        "cookie": "0.6.0",
+        "cookie-signature": "1.0.6",
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "encodeurl": "~2.0.0",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "finalhandler": "1.2.0",
+        "fresh": "0.5.2",
+        "http-errors": "2.0.0",
+        "merge-descriptors": "1.0.3",
+        "methods": "~1.1.2",
+        "on-finished": "2.4.1",
+        "parseurl": "~1.3.3",
+        "path-to-regexp": "0.1.10",
+        "proxy-addr": "~2.0.7",
+        "qs": "6.11.0",
+        "range-parser": "~1.2.1",
+        "safe-buffer": "5.2.1",
+        "send": "0.19.0",
+        "serve-static": "1.16.0",
+        "setprototypeof": "1.2.0",
+        "statuses": "2.0.1",
+        "type-is": "~1.6.18",
+        "utils-merge": "1.0.1",
+        "vary": "~1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.10.0"
+      }
+    },
+    "node_modules/express/node_modules/debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "ms": "2.0.0"
+      }
+    },
+    "node_modules/express/node_modules/ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/exsolve": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmmirror.com/exsolve/-/exsolve-1.0.8.tgz",
+      "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==",
+      "license": "MIT"
+    },
+    "node_modules/fast-glob": {
+      "version": "3.3.3",
+      "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz",
+      "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+      "license": "MIT",
+      "dependencies": {
+        "@nodelib/fs.stat": "^2.0.2",
+        "@nodelib/fs.walk": "^1.2.3",
+        "glob-parent": "^5.1.2",
+        "merge2": "^1.3.0",
+        "micromatch": "^4.0.8"
+      },
+      "engines": {
+        "node": ">=8.6.0"
+      }
+    },
+    "node_modules/fastq": {
+      "version": "1.20.1",
+      "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.20.1.tgz",
+      "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
+      "license": "ISC",
+      "dependencies": {
+        "reusify": "^1.0.4"
+      }
+    },
+    "node_modules/file-type": {
+      "version": "9.0.0",
+      "resolved": "https://registry.npmmirror.com/file-type/-/file-type-9.0.0.tgz",
+      "integrity": "sha512-Qe/5NJrgIOlwijpq3B7BEpzPFcgzggOTagZmkXQY4LA6bsXKTUstK7Wp12lEJ/mLKTpvIZxmIuRcLYWT6ov9lw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/fill-range": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz",
+      "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+      "license": "MIT",
+      "dependencies": {
+        "to-regex-range": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/finalhandler": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-1.2.0.tgz",
+      "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "debug": "2.6.9",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "on-finished": "2.4.1",
+        "parseurl": "~1.3.3",
+        "statuses": "2.0.1",
+        "unpipe": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/finalhandler/node_modules/debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "ms": "2.0.0"
+      }
+    },
+    "node_modules/finalhandler/node_modules/encodeurl": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-1.0.2.tgz",
+      "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/finalhandler/node_modules/ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/follow-redirects": {
+      "version": "1.16.0",
+      "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.16.0.tgz",
+      "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://github.com/sponsors/RubenVerborgh"
+        }
+      ],
+      "license": "MIT",
+      "engines": {
+        "node": ">=4.0"
+      },
+      "peerDependenciesMeta": {
+        "debug": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/forwarded": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz",
+      "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/fraction.js": {
+      "version": "5.3.4",
+      "resolved": "https://registry.npmmirror.com/fraction.js/-/fraction.js-5.3.4.tgz",
+      "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": "*"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/rawify"
+      }
+    },
+    "node_modules/fresh": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz",
+      "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/fs-extra": {
+      "version": "10.1.0",
+      "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz",
+      "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
+      "license": "MIT",
+      "dependencies": {
+        "graceful-fs": "^4.2.0",
+        "jsonfile": "^6.0.1",
+        "universalify": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/fsevents": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
+      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "node_modules/function-bind": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
+      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/generic-names": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/generic-names/-/generic-names-4.0.0.tgz",
+      "integrity": "sha512-ySFolZQfw9FoDb3ed9d80Cm9f0+r7qj+HJkWjeD9RBfpxEVTlVhol+gvaQB/78WbwYfbnNh8nWHHBSlg072y6A==",
+      "license": "MIT",
+      "dependencies": {
+        "loader-utils": "^3.2.0"
+      }
+    },
+    "node_modules/gensync": {
+      "version": "1.0.0-beta.2",
+      "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz",
+      "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/get-intrinsic": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+      "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "es-define-property": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "es-object-atoms": "^1.1.1",
+        "function-bind": "^1.1.2",
+        "get-proto": "^1.0.1",
+        "gopd": "^1.2.0",
+        "has-symbols": "^1.1.0",
+        "hasown": "^2.0.2",
+        "math-intrinsics": "^1.1.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz",
+      "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "dunder-proto": "^1.0.1",
+        "es-object-atoms": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/get-stream": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmmirror.com/get-stream/-/get-stream-6.0.1.tgz",
+      "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/glob-parent": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz",
+      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+      "license": "ISC",
+      "dependencies": {
+        "is-glob": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/global": {
+      "version": "4.4.0",
+      "resolved": "https://registry.npmmirror.com/global/-/global-4.4.0.tgz",
+      "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==",
+      "license": "MIT",
+      "dependencies": {
+        "min-document": "^2.19.0",
+        "process": "^0.11.10"
+      }
+    },
+    "node_modules/gopd": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
+      "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/graceful-fs": {
+      "version": "4.2.11",
+      "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz",
+      "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+      "license": "ISC"
+    },
+    "node_modules/has-flag": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz",
+      "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/has-symbols": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
+      "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/hash-sum": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/hash-sum/-/hash-sum-2.0.0.tgz",
+      "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==",
+      "license": "MIT"
+    },
+    "node_modules/hasown": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.4.tgz",
+      "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
+      "license": "MIT",
+      "dependencies": {
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/he": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/he/-/he-1.2.0.tgz",
+      "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+      "dev": true,
+      "license": "MIT",
+      "bin": {
+        "he": "bin/he"
+      }
+    },
+    "node_modules/http-errors": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz",
+      "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "depd": "2.0.0",
+        "inherits": "2.0.4",
+        "setprototypeof": "1.2.0",
+        "statuses": "2.0.1",
+        "toidentifier": "1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/human-signals": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/human-signals/-/human-signals-2.1.0.tgz",
+      "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=10.17.0"
+      }
+    },
+    "node_modules/iconv-lite": {
+      "version": "0.4.24",
+      "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz",
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/icss-replace-symbols": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz",
+      "integrity": "sha512-chIaY3Vh2mh2Q3RGXttaDIzeiPvaVXJ+C4DAh/w3c37SKZ/U6PGMmuicR2EQQp9bKG8zLMCl7I+PtIoOOPp8Gg==",
+      "license": "ISC"
+    },
+    "node_modules/icss-utils": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmmirror.com/icss-utils/-/icss-utils-5.1.0.tgz",
+      "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==",
+      "license": "ISC",
+      "engines": {
+        "node": "^10 || ^12 || >= 14"
+      },
+      "peerDependencies": {
+        "postcss": "^8.1.0"
+      }
+    },
+    "node_modules/ieee754": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz",
+      "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/immutable": {
+      "version": "5.1.6",
+      "resolved": "https://registry.npmmirror.com/immutable/-/immutable-5.1.6.tgz",
+      "integrity": "sha512-q1swsS8K7L8usSHuOqF2TAoCCkonYz0SG38wLAggaa4Wml70zixIvt2ql4coQ2C2B3hTjltJry4r6bULwgAXLQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "dev": true,
+      "license": "ISC"
+    },
+    "node_modules/invert-kv": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/invert-kv/-/invert-kv-3.0.1.tgz",
+      "integrity": "sha512-CYdFeFexxhv/Bcny+Q0BfOV+ltRlJcd4BBZBYFX/O0u4npJrgZtIcjokegtiSMAvlMTJ+Koq0GBCc//3bueQxw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sindresorhus/invert-kv?sponsor=1"
+      }
+    },
+    "node_modules/ipaddr.js": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+      "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/is-binary-path": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz",
+      "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+      "license": "MIT",
+      "dependencies": {
+        "binary-extensions": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/is-core-module": {
+      "version": "2.16.2",
+      "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.2.tgz",
+      "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==",
+      "license": "MIT",
+      "dependencies": {
+        "hasown": "^2.0.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-function": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/is-function/-/is-function-1.0.2.tgz",
+      "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==",
+      "license": "MIT"
+    },
+    "node_modules/is-glob": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz",
+      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+      "license": "MIT",
+      "dependencies": {
+        "is-extglob": "^2.1.1"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-number": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz",
+      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.12.0"
+      }
+    },
+    "node_modules/is-stream": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/is-stream/-/is-stream-2.0.1.tgz",
+      "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/isbinaryfile": {
+      "version": "5.0.2",
+      "resolved": "https://registry.npmmirror.com/isbinaryfile/-/isbinaryfile-5.0.2.tgz",
+      "integrity": "sha512-GvcjojwonMjWbTkfMpnVHVqXW/wKMYDfEpY94/8zy8HFMOqb/VL6oeONq9v87q4ttVlaTLnGXnJD4B5B1OTGIg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 18.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/gjtorikian/"
+      }
+    },
+    "node_modules/isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+      "dev": true,
+      "license": "ISC"
+    },
+    "node_modules/jimp": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/jimp/-/jimp-0.10.3.tgz",
+      "integrity": "sha512-meVWmDMtyUG5uYjFkmzu0zBgnCvvxwWNi27c4cg55vWNVC9ES4Lcwb+ogx+uBBQE3Q+dLKjXaLl0JVW+nUNwbQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.7.2",
+        "@jimp/custom": "^0.10.3",
+        "@jimp/plugins": "^0.10.3",
+        "@jimp/types": "^0.10.3",
+        "core-js": "^3.4.1",
+        "regenerator-runtime": "^0.13.3"
+      }
+    },
+    "node_modules/jimp/node_modules/regenerator-runtime": {
+      "version": "0.13.11",
+      "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
+      "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
+      "license": "MIT"
+    },
+    "node_modules/jiti": {
+      "version": "1.21.7",
+      "resolved": "https://registry.npmmirror.com/jiti/-/jiti-1.21.7.tgz",
+      "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
+      "dev": true,
+      "license": "MIT",
+      "bin": {
+        "jiti": "bin/jiti.js"
+      }
+    },
+    "node_modules/jpeg-js": {
+      "version": "0.3.7",
+      "resolved": "https://registry.npmmirror.com/jpeg-js/-/jpeg-js-0.3.7.tgz",
+      "integrity": "sha512-9IXdWudL61npZjvLuVe/ktHiA41iE8qFyLB+4VDTblEsWBzeg8WQTlktdUK4CdncUqtUgUg0bbOmTE2bKBKaBQ==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/js-tokens": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz",
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+      "license": "MIT"
+    },
+    "node_modules/jsesc": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz",
+      "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+      "license": "MIT",
+      "bin": {
+        "jsesc": "bin/jsesc"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/json5": {
+      "version": "2.2.3",
+      "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz",
+      "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+      "license": "MIT",
+      "bin": {
+        "json5": "lib/cli.js"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/jsonc-parser": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmmirror.com/jsonc-parser/-/jsonc-parser-3.3.1.tgz",
+      "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==",
+      "license": "MIT"
+    },
+    "node_modules/jsonfile": {
+      "version": "6.2.1",
+      "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.2.1.tgz",
+      "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==",
+      "license": "MIT",
+      "dependencies": {
+        "universalify": "^2.0.0"
+      },
+      "optionalDependencies": {
+        "graceful-fs": "^4.1.6"
+      }
+    },
+    "node_modules/lcid": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmmirror.com/lcid/-/lcid-3.1.1.tgz",
+      "integrity": "sha512-M6T051+5QCGLBQb8id3hdvIW8+zeFV2FyBGFS9IEK5H9Wt4MueD4bW1eWikpHgZp+5xR3l5c8pZUkQsIA0BFZg==",
+      "license": "MIT",
+      "dependencies": {
+        "invert-kv": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/licia": {
+      "version": "1.41.1",
+      "resolved": "https://registry.npmmirror.com/licia/-/licia-1.41.1.tgz",
+      "integrity": "sha512-XqObV8u1KEMdYWaNK0leRrTwhzKnLQEkhbnuUu7qGNH3zJoN7l9sfvF6PfHstSCuUOmpEP+0SBjRrk0I9uZs8g==",
+      "license": "MIT"
+    },
+    "node_modules/lilconfig": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/lilconfig/-/lilconfig-2.1.0.tgz",
+      "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/lines-and-columns": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-2.0.4.tgz",
+      "integrity": "sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==",
+      "license": "MIT",
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      }
+    },
+    "node_modules/load-bmfont": {
+      "version": "1.4.2",
+      "resolved": "https://registry.npmmirror.com/load-bmfont/-/load-bmfont-1.4.2.tgz",
+      "integrity": "sha512-qElWkmjW9Oq1F9EI5Gt7aD9zcdHb9spJCW1L/dmPf7KzCCEJxq8nhHz5eCgI9aMf7vrG/wyaCqdsI+Iy9ZTlog==",
+      "license": "MIT",
+      "dependencies": {
+        "buffer-equal": "0.0.1",
+        "mime": "^1.3.4",
+        "parse-bmfont-ascii": "^1.0.3",
+        "parse-bmfont-binary": "^1.0.5",
+        "parse-bmfont-xml": "^1.1.4",
+        "phin": "^3.7.1",
+        "xhr": "^2.0.1",
+        "xtend": "^4.0.0"
+      }
+    },
+    "node_modules/load-bmfont/node_modules/mime": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz",
+      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+      "license": "MIT",
+      "bin": {
+        "mime": "cli.js"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/load-bmfont/node_modules/phin": {
+      "version": "3.7.1",
+      "resolved": "https://registry.npmmirror.com/phin/-/phin-3.7.1.tgz",
+      "integrity": "sha512-GEazpTWwTZaEQ9RhL7Nyz0WwqilbqgLahDM3D0hxWwmVDI52nXEybHqiN6/elwpkJBhcuj+WbBu+QfT0uhPGfQ==",
+      "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
+      "license": "MIT",
+      "dependencies": {
+        "centra": "^2.7.0"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/loader-utils": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmmirror.com/loader-utils/-/loader-utils-3.3.1.tgz",
+      "integrity": "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 12.13.0"
+      }
+    },
+    "node_modules/local-pkg": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-1.2.1.tgz",
+      "integrity": "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==",
+      "license": "MIT",
+      "dependencies": {
+        "mlly": "^1.7.4",
+        "pkg-types": "^2.3.0",
+        "quansync": "^0.2.11"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/local-pkg/node_modules/confbox": {
+      "version": "0.2.4",
+      "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.2.4.tgz",
+      "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==",
+      "license": "MIT"
+    },
+    "node_modules/local-pkg/node_modules/pkg-types": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-2.3.1.tgz",
+      "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==",
+      "license": "MIT",
+      "dependencies": {
+        "confbox": "^0.2.4",
+        "exsolve": "^1.0.8",
+        "pathe": "^2.0.3"
+      }
+    },
+    "node_modules/localstorage-polyfill": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/localstorage-polyfill/-/localstorage-polyfill-1.0.1.tgz",
+      "integrity": "sha512-m4iHVZxFH5734oQcPKU08025gIz2+4bjWR9lulP8ZYxEJR0BpA0w32oJmkzh8y3UI9ci7xCBehQDc3oA1X+VHw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/lodash.camelcase": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmmirror.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
+      "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
+      "license": "MIT"
+    },
+    "node_modules/lodash.debounce": {
+      "version": "4.0.8",
+      "resolved": "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
+      "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/lru-cache": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz",
+      "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+      "license": "ISC",
+      "dependencies": {
+        "yallist": "^3.0.2"
+      }
+    },
+    "node_modules/magic-string": {
+      "version": "0.30.11",
+      "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.11.tgz",
+      "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==",
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/sourcemap-codec": "^1.5.0"
+      }
+    },
+    "node_modules/math-intrinsics": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+      "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/media-typer": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz",
+      "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/meow": {
+      "version": "13.2.0",
+      "resolved": "https://registry.npmmirror.com/meow/-/meow-13.2.0.tgz",
+      "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/merge": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmmirror.com/merge/-/merge-2.1.1.tgz",
+      "integrity": "sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w==",
+      "license": "MIT"
+    },
+    "node_modules/merge-descriptors": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+      "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+      "dev": true,
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/merge-stream": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/merge-stream/-/merge-stream-2.0.0.tgz",
+      "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/merge2": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz",
+      "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/methods": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/methods/-/methods-1.1.2.tgz",
+      "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/micromatch": {
+      "version": "4.0.8",
+      "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz",
+      "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+      "license": "MIT",
+      "dependencies": {
+        "braces": "^3.0.3",
+        "picomatch": "^2.3.1"
+      },
+      "engines": {
+        "node": ">=8.6"
+      }
+    },
+    "node_modules/mime": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/mime/-/mime-3.0.0.tgz",
+      "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
+      "license": "MIT",
+      "bin": {
+        "mime": "cli.js"
+      },
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
+    "node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mimic-fn": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-2.1.0.tgz",
+      "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/min-document": {
+      "version": "2.19.2",
+      "resolved": "https://registry.npmmirror.com/min-document/-/min-document-2.19.2.tgz",
+      "integrity": "sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A==",
+      "license": "MIT",
+      "dependencies": {
+        "dom-walk": "^0.1.0"
+      }
+    },
+    "node_modules/minimist": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz",
+      "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/mkdirp": {
+      "version": "0.5.6",
+      "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.6.tgz",
+      "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
+      "license": "MIT",
+      "dependencies": {
+        "minimist": "^1.2.6"
+      },
+      "bin": {
+        "mkdirp": "bin/cmd.js"
+      }
+    },
+    "node_modules/mlly": {
+      "version": "1.8.2",
+      "resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.8.2.tgz",
+      "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==",
+      "license": "MIT",
+      "dependencies": {
+        "acorn": "^8.16.0",
+        "pathe": "^2.0.3",
+        "pkg-types": "^1.3.1",
+        "ufo": "^1.6.3"
+      }
+    },
+    "node_modules/module-alias": {
+      "version": "2.2.3",
+      "resolved": "https://registry.npmmirror.com/module-alias/-/module-alias-2.2.3.tgz",
+      "integrity": "sha512-23g5BFj4zdQL/b6tor7Ji+QY4pEfNH784BMslY9Qb0UnJWRAt+lQGLYmRaM0KDBwIG23ffEBELhZDP2rhi9f/Q==",
+      "license": "MIT"
+    },
+    "node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "license": "MIT"
+    },
+    "node_modules/muggle-string": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.3.1.tgz",
+      "integrity": "sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/mz": {
+      "version": "2.7.0",
+      "resolved": "https://registry.npmmirror.com/mz/-/mz-2.7.0.tgz",
+      "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "any-promise": "^1.0.0",
+        "object-assign": "^4.0.1",
+        "thenify-all": "^1.0.0"
+      }
+    },
+    "node_modules/nanoid": {
+      "version": "3.3.15",
+      "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.15.tgz",
+      "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==",
+      "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/negotiator": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz",
+      "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/node-addon-api": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-7.1.1.tgz",
+      "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
+      "dev": true,
+      "license": "MIT",
+      "optional": true
+    },
+    "node_modules/node-releases": {
+      "version": "2.0.48",
+      "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.48.tgz",
+      "integrity": "sha512-1uz8041X6LoI6ZSdZacM9lVY28vuzDlSKitnpbSNK0RfKoIJkX29NBPVEFXhnuSuEOA9Ww0xnPJ+ILWbGAv8DA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/normalize-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz",
+      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/normalize-range": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmmirror.com/normalize-range/-/normalize-range-0.1.2.tgz",
+      "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/npm-run-path": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-4.0.1.tgz",
+      "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "path-key": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/object-hash": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/object-hash/-/object-hash-3.0.0.tgz",
+      "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/object-inspect": {
+      "version": "1.13.4",
+      "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz",
+      "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/omggif": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmmirror.com/omggif/-/omggif-1.0.10.tgz",
+      "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==",
+      "license": "MIT"
+    },
+    "node_modules/on-finished": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz",
+      "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "ee-first": "1.1.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/onetime": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmmirror.com/onetime/-/onetime-5.1.2.tgz",
+      "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "mimic-fn": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/os-locale-s-fix": {
+      "version": "1.0.8-fix-1",
+      "resolved": "https://registry.npmmirror.com/os-locale-s-fix/-/os-locale-s-fix-1.0.8-fix-1.tgz",
+      "integrity": "sha512-Sv0OvhPiMutICiwORAUefv02DCPb62IelBmo8ZsSrRHyI3FStqIWZvjqDkvtjU+lcujo7UNir+dCwKSqlEQ/5w==",
+      "license": "MIT",
+      "dependencies": {
+        "lcid": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=10",
+        "yarn": "^1.22.4"
+      }
+    },
+    "node_modules/pako": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz",
+      "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
+      "license": "(MIT AND Zlib)"
+    },
+    "node_modules/parse-bmfont-ascii": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmmirror.com/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz",
+      "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==",
+      "license": "MIT"
+    },
+    "node_modules/parse-bmfont-binary": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmmirror.com/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz",
+      "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==",
+      "license": "MIT"
+    },
+    "node_modules/parse-bmfont-xml": {
+      "version": "1.1.6",
+      "resolved": "https://registry.npmmirror.com/parse-bmfont-xml/-/parse-bmfont-xml-1.1.6.tgz",
+      "integrity": "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==",
+      "license": "MIT",
+      "dependencies": {
+        "xml-parse-from-string": "^1.0.0",
+        "xml2js": "^0.5.0"
+      }
+    },
+    "node_modules/parse-css-font": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/parse-css-font/-/parse-css-font-4.0.0.tgz",
+      "integrity": "sha512-lnY7dTUfjRXsSo5G5C639L8RaBBaVSgL+5hacIFKsNHzeCJQ5SFSZv1DZmc7+wZv/22PFGOq2YbaEHLdaCS/mQ==",
+      "license": "MIT",
+      "dependencies": {
+        "css-font-size-keywords": "^1.0.0",
+        "css-font-stretch-keywords": "^1.0.1",
+        "css-font-style-keywords": "^1.0.1",
+        "css-font-weight-keywords": "^1.0.0",
+        "css-list-helpers": "^2.0.0",
+        "css-system-font-keywords": "^1.0.0",
+        "unquote": "^1.1.1"
+      }
+    },
+    "node_modules/parse-headers": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmmirror.com/parse-headers/-/parse-headers-2.0.6.tgz",
+      "integrity": "sha512-Tz11t3uKztEW5FEVZnj1ox8GKblWn+PvHY9TmJV5Mll2uHEwRdR/5Li1OlXoECjLYkApdhWy44ocONwXLiKO5A==",
+      "license": "MIT"
+    },
+    "node_modules/parseurl": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz",
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/path-browserify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz",
+      "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/path-key": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz",
+      "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/path-parse": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz",
+      "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+      "license": "MIT"
+    },
+    "node_modules/path-to-regexp": {
+      "version": "0.1.10",
+      "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
+      "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/pathe": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz",
+      "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+      "license": "MIT"
+    },
+    "node_modules/phin": {
+      "version": "2.9.3",
+      "resolved": "https://registry.npmmirror.com/phin/-/phin-2.9.3.tgz",
+      "integrity": "sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==",
+      "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
+      "license": "MIT"
+    },
+    "node_modules/picocolors": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.0.tgz",
+      "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==",
+      "license": "ISC"
+    },
+    "node_modules/picomatch": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.2.tgz",
+      "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/pify": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmmirror.com/pify/-/pify-2.3.0.tgz",
+      "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/pinia": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.3.1.tgz",
+      "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/devtools-api": "^6.6.3",
+        "vue-demi": "^0.14.10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/posva"
+      },
+      "peerDependencies": {
+        "typescript": ">=4.4.4",
+        "vue": "^2.7.0 || ^3.5.11"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/pirates": {
+      "version": "4.0.7",
+      "resolved": "https://registry.npmmirror.com/pirates/-/pirates-4.0.7.tgz",
+      "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/pixelmatch": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmmirror.com/pixelmatch/-/pixelmatch-4.0.2.tgz",
+      "integrity": "sha512-J8B6xqiO37sU/gkcMglv6h5Jbd9xNER7aHzpfRdNmV4IbQBzBpe4l9XmbG+xPF/znacgu2jfEw+wHffaq/YkXA==",
+      "license": "ISC",
+      "dependencies": {
+        "pngjs": "^3.0.0"
+      },
+      "bin": {
+        "pixelmatch": "bin/pixelmatch"
+      }
+    },
+    "node_modules/pkg-types": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz",
+      "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
+      "license": "MIT",
+      "dependencies": {
+        "confbox": "^0.1.8",
+        "mlly": "^1.7.4",
+        "pathe": "^2.0.1"
+      }
+    },
+    "node_modules/pngjs": {
+      "version": "3.4.0",
+      "resolved": "https://registry.npmmirror.com/pngjs/-/pngjs-3.4.0.tgz",
+      "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4.0.0"
+      }
+    },
+    "node_modules/postcss": {
+      "version": "8.5.15",
+      "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.15.tgz",
+      "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
+      "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.12",
+        "picocolors": "^1.1.1",
+        "source-map-js": "^1.2.1"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      }
+    },
+    "node_modules/postcss-import": {
+      "version": "14.1.0",
+      "resolved": "https://registry.npmmirror.com/postcss-import/-/postcss-import-14.1.0.tgz",
+      "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==",
+      "license": "MIT",
+      "dependencies": {
+        "postcss-value-parser": "^4.0.0",
+        "read-cache": "^1.0.0",
+        "resolve": "^1.1.7"
+      },
+      "engines": {
+        "node": ">=10.0.0"
+      },
+      "peerDependencies": {
+        "postcss": "^8.0.0"
+      }
+    },
+    "node_modules/postcss-js": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmmirror.com/postcss-js/-/postcss-js-4.1.0.tgz",
+      "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "camelcase-css": "^2.0.1"
+      },
+      "engines": {
+        "node": "^12 || ^14 || >= 16"
+      },
+      "peerDependencies": {
+        "postcss": "^8.4.21"
+      }
+    },
+    "node_modules/postcss-load-config": {
+      "version": "3.1.4",
+      "resolved": "https://registry.npmmirror.com/postcss-load-config/-/postcss-load-config-3.1.4.tgz",
+      "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==",
+      "license": "MIT",
+      "dependencies": {
+        "lilconfig": "^2.0.5",
+        "yaml": "^1.10.2"
+      },
+      "engines": {
+        "node": ">= 10"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/postcss/"
+      },
+      "peerDependencies": {
+        "postcss": ">=8.0.9",
+        "ts-node": ">=9.0.0"
+      },
+      "peerDependenciesMeta": {
+        "postcss": {
+          "optional": true
+        },
+        "ts-node": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/postcss-modules": {
+      "version": "4.3.1",
+      "resolved": "https://registry.npmmirror.com/postcss-modules/-/postcss-modules-4.3.1.tgz",
+      "integrity": "sha512-ItUhSUxBBdNamkT3KzIZwYNNRFKmkJrofvC2nWab3CPKhYBQ1f27XXh1PAPE27Psx58jeelPsxWB/+og+KEH0Q==",
+      "license": "MIT",
+      "dependencies": {
+        "generic-names": "^4.0.0",
+        "icss-replace-symbols": "^1.1.0",
+        "lodash.camelcase": "^4.3.0",
+        "postcss-modules-extract-imports": "^3.0.0",
+        "postcss-modules-local-by-default": "^4.0.0",
+        "postcss-modules-scope": "^3.0.0",
+        "postcss-modules-values": "^4.0.0",
+        "string-hash": "^1.1.1"
+      },
+      "peerDependencies": {
+        "postcss": "^8.0.0"
+      }
+    },
+    "node_modules/postcss-modules-extract-imports": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz",
+      "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==",
+      "license": "ISC",
+      "engines": {
+        "node": "^10 || ^12 || >= 14"
+      },
+      "peerDependencies": {
+        "postcss": "^8.1.0"
+      }
+    },
+    "node_modules/postcss-modules-local-by-default": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmmirror.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz",
+      "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==",
+      "license": "MIT",
+      "dependencies": {
+        "icss-utils": "^5.0.0",
+        "postcss-selector-parser": "^7.0.0",
+        "postcss-value-parser": "^4.1.0"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >= 14"
+      },
+      "peerDependencies": {
+        "postcss": "^8.1.0"
+      }
+    },
+    "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": {
+      "version": "7.1.4",
+      "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz",
+      "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==",
+      "license": "MIT",
+      "dependencies": {
+        "cssesc": "^3.0.0",
+        "util-deprecate": "^1.0.2"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/postcss-modules-scope": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmmirror.com/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz",
+      "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==",
+      "license": "ISC",
+      "dependencies": {
+        "postcss-selector-parser": "^7.0.0"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >= 14"
+      },
+      "peerDependencies": {
+        "postcss": "^8.1.0"
+      }
+    },
+    "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": {
+      "version": "7.1.4",
+      "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz",
+      "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==",
+      "license": "MIT",
+      "dependencies": {
+        "cssesc": "^3.0.0",
+        "util-deprecate": "^1.0.2"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/postcss-modules-values": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz",
+      "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==",
+      "license": "ISC",
+      "dependencies": {
+        "icss-utils": "^5.0.0"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >= 14"
+      },
+      "peerDependencies": {
+        "postcss": "^8.1.0"
+      }
+    },
+    "node_modules/postcss-nested": {
+      "version": "6.2.0",
+      "resolved": "https://registry.npmmirror.com/postcss-nested/-/postcss-nested-6.2.0.tgz",
+      "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "postcss-selector-parser": "^6.1.1"
+      },
+      "engines": {
+        "node": ">=12.0"
+      },
+      "peerDependencies": {
+        "postcss": "^8.2.14"
+      }
+    },
+    "node_modules/postcss-selector-parser": {
+      "version": "6.1.2",
+      "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
+      "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
+      "license": "MIT",
+      "dependencies": {
+        "cssesc": "^3.0.0",
+        "util-deprecate": "^1.0.2"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/postcss-value-parser": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+      "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+      "license": "MIT"
+    },
+    "node_modules/postcss/node_modules/picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+      "license": "ISC"
+    },
+    "node_modules/process": {
+      "version": "0.11.10",
+      "resolved": "https://registry.npmmirror.com/process/-/process-0.11.10.tgz",
+      "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6.0"
+      }
+    },
+    "node_modules/proxy-addr": {
+      "version": "2.0.7",
+      "resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz",
+      "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "forwarded": "0.2.0",
+        "ipaddr.js": "1.9.1"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/qrcode-reader": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmmirror.com/qrcode-reader/-/qrcode-reader-1.0.4.tgz",
+      "integrity": "sha512-rRjALGNh9zVqvweg1j5OKIQKNsw3bLC+7qwlnead5K/9cb1cEIAGkwikt/09U0K+2IDWGD9CC6SP7tHAjUeqvQ==",
+      "license": "Apache-2.0"
+    },
+    "node_modules/qrcode-terminal": {
+      "version": "0.12.0",
+      "resolved": "https://registry.npmmirror.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz",
+      "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==",
+      "bin": {
+        "qrcode-terminal": "bin/qrcode-terminal.js"
+      }
+    },
+    "node_modules/qs": {
+      "version": "6.11.0",
+      "resolved": "https://registry.npmmirror.com/qs/-/qs-6.11.0.tgz",
+      "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
+      "dev": true,
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "side-channel": "^1.0.4"
+      },
+      "engines": {
+        "node": ">=0.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/quansync": {
+      "version": "0.2.11",
+      "resolved": "https://registry.npmmirror.com/quansync/-/quansync-0.2.11.tgz",
+      "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://github.com/sponsors/antfu"
+        },
+        {
+          "type": "individual",
+          "url": "https://github.com/sponsors/sxzz"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/queue-microtask": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz",
+      "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/range-parser": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz",
+      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/raw-body": {
+      "version": "2.5.2",
+      "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-2.5.2.tgz",
+      "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "bytes": "3.1.2",
+        "http-errors": "2.0.0",
+        "iconv-lite": "0.4.24",
+        "unpipe": "1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/read-cache": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/read-cache/-/read-cache-1.0.0.tgz",
+      "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+      "license": "MIT",
+      "dependencies": {
+        "pify": "^2.3.0"
+      }
+    },
+    "node_modules/readdirp": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz",
+      "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+      "license": "MIT",
+      "dependencies": {
+        "picomatch": "^2.2.1"
+      },
+      "engines": {
+        "node": ">=8.10.0"
+      }
+    },
+    "node_modules/regenerate": {
+      "version": "1.4.2",
+      "resolved": "https://registry.npmmirror.com/regenerate/-/regenerate-1.4.2.tgz",
+      "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/regenerate-unicode-properties": {
+      "version": "10.2.2",
+      "resolved": "https://registry.npmmirror.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz",
+      "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "regenerate": "^1.4.2"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/regenerator-runtime": {
+      "version": "0.14.1",
+      "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+      "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/regexpu-core": {
+      "version": "6.4.0",
+      "resolved": "https://registry.npmmirror.com/regexpu-core/-/regexpu-core-6.4.0.tgz",
+      "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "regenerate": "^1.4.2",
+        "regenerate-unicode-properties": "^10.2.2",
+        "regjsgen": "^0.8.0",
+        "regjsparser": "^0.13.0",
+        "unicode-match-property-ecmascript": "^2.0.0",
+        "unicode-match-property-value-ecmascript": "^2.2.1"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/regjsgen": {
+      "version": "0.8.0",
+      "resolved": "https://registry.npmmirror.com/regjsgen/-/regjsgen-0.8.0.tgz",
+      "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/regjsparser": {
+      "version": "0.13.2",
+      "resolved": "https://registry.npmmirror.com/regjsparser/-/regjsparser-0.13.2.tgz",
+      "integrity": "sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==",
+      "dev": true,
+      "license": "BSD-2-Clause",
+      "dependencies": {
+        "jsesc": "~3.1.0"
+      },
+      "bin": {
+        "regjsparser": "bin/parser"
+      }
+    },
+    "node_modules/resolve": {
+      "version": "1.22.8",
+      "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.8.tgz",
+      "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
+      "license": "MIT",
+      "dependencies": {
+        "is-core-module": "^2.13.0",
+        "path-parse": "^1.0.7",
+        "supports-preserve-symlinks-flag": "^1.0.0"
+      },
+      "bin": {
+        "resolve": "bin/resolve"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/reusify": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz",
+      "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+      "license": "MIT",
+      "engines": {
+        "iojs": ">=1.0.0",
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/rollup": {
+      "version": "4.62.2",
+      "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.62.2.tgz",
+      "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/estree": "1.0.9"
+      },
+      "bin": {
+        "rollup": "dist/bin/rollup"
+      },
+      "engines": {
+        "node": ">=18.0.0",
+        "npm": ">=8.0.0"
+      },
+      "optionalDependencies": {
+        "@rollup/rollup-android-arm-eabi": "4.62.2",
+        "@rollup/rollup-android-arm64": "4.62.2",
+        "@rollup/rollup-darwin-arm64": "4.62.2",
+        "@rollup/rollup-darwin-x64": "4.62.2",
+        "@rollup/rollup-freebsd-arm64": "4.62.2",
+        "@rollup/rollup-freebsd-x64": "4.62.2",
+        "@rollup/rollup-linux-arm-gnueabihf": "4.62.2",
+        "@rollup/rollup-linux-arm-musleabihf": "4.62.2",
+        "@rollup/rollup-linux-arm64-gnu": "4.62.2",
+        "@rollup/rollup-linux-arm64-musl": "4.62.2",
+        "@rollup/rollup-linux-loong64-gnu": "4.62.2",
+        "@rollup/rollup-linux-loong64-musl": "4.62.2",
+        "@rollup/rollup-linux-ppc64-gnu": "4.62.2",
+        "@rollup/rollup-linux-ppc64-musl": "4.62.2",
+        "@rollup/rollup-linux-riscv64-gnu": "4.62.2",
+        "@rollup/rollup-linux-riscv64-musl": "4.62.2",
+        "@rollup/rollup-linux-s390x-gnu": "4.62.2",
+        "@rollup/rollup-linux-x64-gnu": "4.62.2",
+        "@rollup/rollup-linux-x64-musl": "4.62.2",
+        "@rollup/rollup-openbsd-x64": "4.62.2",
+        "@rollup/rollup-openharmony-arm64": "4.62.2",
+        "@rollup/rollup-win32-arm64-msvc": "4.62.2",
+        "@rollup/rollup-win32-ia32-msvc": "4.62.2",
+        "@rollup/rollup-win32-x64-gnu": "4.62.2",
+        "@rollup/rollup-win32-x64-msvc": "4.62.2",
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/run-parallel": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz",
+      "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "queue-microtask": "^1.2.2"
+      }
+    },
+    "node_modules/safe-area-insets": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmmirror.com/safe-area-insets/-/safe-area-insets-1.4.1.tgz",
+      "integrity": "sha512-r/nRWTjFGhhm3w1Z6Kd/jY11srN+lHt2mNl1E/emQGW8ic7n3Avu4noibklfSM+Y34peNphHD/BSZecav0sXYQ==",
+      "license": "ISC"
+    },
+    "node_modules/safe-buffer": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz",
+      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/sass": {
+      "version": "1.101.0",
+      "resolved": "https://registry.npmmirror.com/sass/-/sass-1.101.0.tgz",
+      "integrity": "sha512-OL3GoQyoUdDt843DpVmDO6y2k1sc5IhUDSpu8XucEI+35neq5QivZ1iuegnpraEVTJXlQGK1gl27zKcTLEPbQw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "chokidar": "^5.0.0",
+        "immutable": "^5.1.5",
+        "source-map-js": ">=0.6.2 <2.0.0"
+      },
+      "bin": {
+        "sass": "sass.js"
+      },
+      "engines": {
+        "node": ">=20.19.0"
+      },
+      "optionalDependencies": {
+        "@parcel/watcher": "^2.4.1"
+      }
+    },
+    "node_modules/sass/node_modules/chokidar": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-5.0.0.tgz",
+      "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "readdirp": "^5.0.0"
+      },
+      "engines": {
+        "node": ">= 20.19.0"
+      },
+      "funding": {
+        "url": "https://paulmillr.com/funding/"
+      }
+    },
+    "node_modules/sass/node_modules/readdirp": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-5.0.0.tgz",
+      "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 20.19.0"
+      },
+      "funding": {
+        "type": "individual",
+        "url": "https://paulmillr.com/funding/"
+      }
+    },
+    "node_modules/sax": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmmirror.com/sax/-/sax-1.6.0.tgz",
+      "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==",
+      "license": "BlueOak-1.0.0",
+      "engines": {
+        "node": ">=11.0.0"
+      }
+    },
+    "node_modules/scule": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz",
+      "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==",
+      "license": "MIT"
+    },
+    "node_modules/semver": {
+      "version": "6.3.1",
+      "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz",
+      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+      "license": "ISC",
+      "bin": {
+        "semver": "bin/semver.js"
+      }
+    },
+    "node_modules/send": {
+      "version": "0.19.0",
+      "resolved": "https://registry.npmmirror.com/send/-/send-0.19.0.tgz",
+      "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "destroy": "1.2.0",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "fresh": "0.5.2",
+        "http-errors": "2.0.0",
+        "mime": "1.6.0",
+        "ms": "2.1.3",
+        "on-finished": "2.4.1",
+        "range-parser": "~1.2.1",
+        "statuses": "2.0.1"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/send/node_modules/debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "ms": "2.0.0"
+      }
+    },
+    "node_modules/send/node_modules/debug/node_modules/ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/send/node_modules/encodeurl": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-1.0.2.tgz",
+      "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/send/node_modules/mime": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz",
+      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+      "dev": true,
+      "license": "MIT",
+      "bin": {
+        "mime": "cli.js"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/serve-static": {
+      "version": "1.16.0",
+      "resolved": "https://registry.npmmirror.com/serve-static/-/serve-static-1.16.0.tgz",
+      "integrity": "sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "parseurl": "~1.3.3",
+        "send": "0.18.0"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/serve-static/node_modules/debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "ms": "2.0.0"
+      }
+    },
+    "node_modules/serve-static/node_modules/debug/node_modules/ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/serve-static/node_modules/encodeurl": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-1.0.2.tgz",
+      "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/serve-static/node_modules/mime": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz",
+      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+      "dev": true,
+      "license": "MIT",
+      "bin": {
+        "mime": "cli.js"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/serve-static/node_modules/send": {
+      "version": "0.18.0",
+      "resolved": "https://registry.npmmirror.com/send/-/send-0.18.0.tgz",
+      "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "destroy": "1.2.0",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "fresh": "0.5.2",
+        "http-errors": "2.0.0",
+        "mime": "1.6.0",
+        "ms": "2.1.3",
+        "on-finished": "2.4.1",
+        "range-parser": "~1.2.1",
+        "statuses": "2.0.1"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/setprototypeof": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz",
+      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+      "dev": true,
+      "license": "ISC"
+    },
+    "node_modules/shebang-command": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz",
+      "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "shebang-regex": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/shebang-regex": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz",
+      "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/side-channel": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.1.tgz",
+      "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "object-inspect": "^1.13.4",
+        "side-channel-list": "^1.0.1",
+        "side-channel-map": "^1.0.1",
+        "side-channel-weakmap": "^1.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/side-channel-list": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.1.tgz",
+      "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "object-inspect": "^1.13.4"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/side-channel-map": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz",
+      "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "call-bound": "^1.0.2",
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.5",
+        "object-inspect": "^1.13.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/side-channel-weakmap": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+      "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "call-bound": "^1.0.2",
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.5",
+        "object-inspect": "^1.13.3",
+        "side-channel-map": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/signal-exit": {
+      "version": "3.0.7",
+      "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz",
+      "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+      "dev": true,
+      "license": "ISC"
+    },
+    "node_modules/source-map": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz",
+      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+      "license": "BSD-3-Clause",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/source-map-js": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/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/source-map-support": {
+      "version": "0.5.21",
+      "resolved": "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz",
+      "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "buffer-from": "^1.0.0",
+        "source-map": "^0.6.0"
+      }
+    },
+    "node_modules/statuses": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz",
+      "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/string-hash": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmmirror.com/string-hash/-/string-hash-1.1.3.tgz",
+      "integrity": "sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A==",
+      "license": "CC0-1.0"
+    },
+    "node_modules/strip-final-newline": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+      "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/strip-literal": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/strip-literal/-/strip-literal-3.1.0.tgz",
+      "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==",
+      "license": "MIT",
+      "dependencies": {
+        "js-tokens": "^9.0.1"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/strip-literal/node_modules/js-tokens": {
+      "version": "9.0.1",
+      "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.1.tgz",
+      "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
+      "license": "MIT"
+    },
+    "node_modules/sucrase": {
+      "version": "3.35.1",
+      "resolved": "https://registry.npmmirror.com/sucrase/-/sucrase-3.35.1.tgz",
+      "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/gen-mapping": "^0.3.2",
+        "commander": "^4.0.0",
+        "lines-and-columns": "^1.1.6",
+        "mz": "^2.7.0",
+        "pirates": "^4.0.1",
+        "tinyglobby": "^0.2.11",
+        "ts-interface-checker": "^0.1.9"
+      },
+      "bin": {
+        "sucrase": "bin/sucrase",
+        "sucrase-node": "bin/sucrase-node"
+      },
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      }
+    },
+    "node_modules/sucrase/node_modules/commander": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmmirror.com/commander/-/commander-4.1.1.tgz",
+      "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/sucrase/node_modules/lines-and-columns": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+      "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/supports-color": {
+      "version": "5.5.0",
+      "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz",
+      "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+      "license": "MIT",
+      "dependencies": {
+        "has-flag": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/supports-preserve-symlinks-flag": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+      "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/systemjs": {
+      "version": "6.15.1",
+      "resolved": "https://registry.npmmirror.com/systemjs/-/systemjs-6.15.1.tgz",
+      "integrity": "sha512-Nk8c4lXvMB98MtbmjX7JwJRgJOL8fluecYCfCeYBznwmpOs8Bf15hLM6z4z71EDAhQVrQrI+wt1aLWSXZq+hXA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/tailwindcss": {
+      "version": "3.4.19",
+      "resolved": "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-3.4.19.tgz",
+      "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@alloc/quick-lru": "^5.2.0",
+        "arg": "^5.0.2",
+        "chokidar": "^3.6.0",
+        "didyoumean": "^1.2.2",
+        "dlv": "^1.1.3",
+        "fast-glob": "^3.3.2",
+        "glob-parent": "^6.0.2",
+        "is-glob": "^4.0.3",
+        "jiti": "^1.21.7",
+        "lilconfig": "^3.1.3",
+        "micromatch": "^4.0.8",
+        "normalize-path": "^3.0.0",
+        "object-hash": "^3.0.0",
+        "picocolors": "^1.1.1",
+        "postcss": "^8.4.47",
+        "postcss-import": "^15.1.0",
+        "postcss-js": "^4.0.1",
+        "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0",
+        "postcss-nested": "^6.2.0",
+        "postcss-selector-parser": "^6.1.2",
+        "resolve": "^1.22.8",
+        "sucrase": "^3.35.0"
+      },
+      "bin": {
+        "tailwind": "lib/cli.js",
+        "tailwindcss": "lib/cli.js"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/tailwindcss/node_modules/glob-parent": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz",
+      "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "is-glob": "^4.0.3"
+      },
+      "engines": {
+        "node": ">=10.13.0"
+      }
+    },
+    "node_modules/tailwindcss/node_modules/lilconfig": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmmirror.com/lilconfig/-/lilconfig-3.1.3.tgz",
+      "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antonk52"
+      }
+    },
+    "node_modules/tailwindcss/node_modules/picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+      "dev": true,
+      "license": "ISC"
+    },
+    "node_modules/tailwindcss/node_modules/postcss-import": {
+      "version": "15.1.0",
+      "resolved": "https://registry.npmmirror.com/postcss-import/-/postcss-import-15.1.0.tgz",
+      "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "postcss-value-parser": "^4.0.0",
+        "read-cache": "^1.0.0",
+        "resolve": "^1.1.7"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      },
+      "peerDependencies": {
+        "postcss": "^8.0.0"
+      }
+    },
+    "node_modules/tailwindcss/node_modules/postcss-load-config": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmmirror.com/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
+      "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "lilconfig": "^3.1.1"
+      },
+      "engines": {
+        "node": ">= 18"
+      },
+      "peerDependencies": {
+        "jiti": ">=1.21.0",
+        "postcss": ">=8.0.9",
+        "tsx": "^4.8.1",
+        "yaml": "^2.4.2"
+      },
+      "peerDependenciesMeta": {
+        "jiti": {
+          "optional": true
+        },
+        "postcss": {
+          "optional": true
+        },
+        "tsx": {
+          "optional": true
+        },
+        "yaml": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/tapable": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmmirror.com/tapable/-/tapable-2.3.3.tgz",
+      "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/webpack"
+      }
+    },
+    "node_modules/terser": {
+      "version": "5.48.0",
+      "resolved": "https://registry.npmmirror.com/terser/-/terser-5.48.0.tgz",
+      "integrity": "sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==",
+      "dev": true,
+      "license": "BSD-2-Clause",
+      "dependencies": {
+        "@jridgewell/source-map": "^0.3.3",
+        "acorn": "^8.15.0",
+        "commander": "^2.20.0",
+        "source-map-support": "~0.5.20"
+      },
+      "bin": {
+        "terser": "bin/terser"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/thenify": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmmirror.com/thenify/-/thenify-3.3.1.tgz",
+      "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "any-promise": "^1.0.0"
+      }
+    },
+    "node_modules/thenify-all": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmmirror.com/thenify-all/-/thenify-all-1.6.0.tgz",
+      "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "thenify": ">= 3.1.0 < 4"
+      },
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
+    "node_modules/timm": {
+      "version": "1.7.1",
+      "resolved": "https://registry.npmmirror.com/timm/-/timm-1.7.1.tgz",
+      "integrity": "sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw==",
+      "license": "MIT"
+    },
+    "node_modules/tinycolor2": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmmirror.com/tinycolor2/-/tinycolor2-1.6.0.tgz",
+      "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==",
+      "license": "MIT"
+    },
+    "node_modules/tinyglobby": {
+      "version": "0.2.17",
+      "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.17.tgz",
+      "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "fdir": "^6.5.0",
+        "picomatch": "^4.0.4"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/SuperchupuDev"
+      }
+    },
+    "node_modules/tinyglobby/node_modules/fdir": {
+      "version": "6.5.0",
+      "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz",
+      "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "peerDependencies": {
+        "picomatch": "^3 || ^4"
+      },
+      "peerDependenciesMeta": {
+        "picomatch": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/tinyglobby/node_modules/picomatch": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.4.tgz",
+      "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/to-fast-properties": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+      "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/to-regex-range": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz",
+      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+      "license": "MIT",
+      "dependencies": {
+        "is-number": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=8.0"
+      }
+    },
+    "node_modules/toidentifier": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz",
+      "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.6"
+      }
+    },
+    "node_modules/ts-interface-checker": {
+      "version": "0.1.13",
+      "resolved": "https://registry.npmmirror.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
+      "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
+      "dev": true,
+      "license": "Apache-2.0"
+    },
+    "node_modules/type-is": {
+      "version": "1.6.18",
+      "resolved": "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz",
+      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "media-typer": "0.3.0",
+        "mime-types": "~2.1.24"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/typescript": {
+      "version": "4.9.5",
+      "resolved": "https://registry.npmmirror.com/typescript/-/typescript-4.9.5.tgz",
+      "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=4.2.0"
+      }
+    },
+    "node_modules/ufo": {
+      "version": "1.6.4",
+      "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.4.tgz",
+      "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==",
+      "license": "MIT"
+    },
+    "node_modules/undici-types": {
+      "version": "8.3.0",
+      "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-8.3.0.tgz",
+      "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/uni-ui": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/uni-ui/-/uni-ui-1.0.0.tgz",
+      "integrity": "sha512-NDN1mfZhMa0FCnEt3vyqBcNUCHcz+At2+OgcPakCwm3SsXeKZ8Qk7u60CpxqVmhnk14jhgJdxgtCpwx1yW96VQ==",
+      "license": "ISC"
+    },
+    "node_modules/unicode-canonical-property-names-ecmascript": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz",
+      "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/unicode-match-property-ecmascript": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz",
+      "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "unicode-canonical-property-names-ecmascript": "^2.0.0",
+        "unicode-property-aliases-ecmascript": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/unicode-match-property-value-ecmascript": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmmirror.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz",
+      "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/unicode-property-aliases-ecmascript": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmmirror.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz",
+      "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/unimport": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmmirror.com/unimport/-/unimport-4.1.1.tgz",
+      "integrity": "sha512-j9+fijH6aDd05yv1fXlyt7HSxtOWtGtrZeYTVBsSUg57Iuf+Ps2itIZjeyu7bEQ4k0WOgYhHrdW8m/pJgOpl5g==",
+      "license": "MIT",
+      "dependencies": {
+        "acorn": "^8.14.0",
+        "escape-string-regexp": "^5.0.0",
+        "estree-walker": "^3.0.3",
+        "fast-glob": "^3.3.3",
+        "local-pkg": "^1.0.0",
+        "magic-string": "^0.30.17",
+        "mlly": "^1.7.4",
+        "pathe": "^2.0.2",
+        "picomatch": "^4.0.2",
+        "pkg-types": "^1.3.1",
+        "scule": "^1.3.0",
+        "strip-literal": "^3.0.0",
+        "unplugin": "^2.1.2",
+        "unplugin-utils": "^0.2.3"
+      },
+      "engines": {
+        "node": ">=18.12.0"
+      }
+    },
+    "node_modules/unimport/node_modules/escape-string-regexp": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
+      "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/unimport/node_modules/estree-walker": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz",
+      "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/estree": "^1.0.0"
+      }
+    },
+    "node_modules/unimport/node_modules/magic-string": {
+      "version": "0.30.21",
+      "resolved": "https://registry.npmmirror.com/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/unimport/node_modules/picomatch": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.4.tgz",
+      "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/universalify": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz",
+      "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 10.0.0"
+      }
+    },
+    "node_modules/unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/unplugin": {
+      "version": "2.3.11",
+      "resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-2.3.11.tgz",
+      "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==",
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/remapping": "^2.3.5",
+        "acorn": "^8.15.0",
+        "picomatch": "^4.0.3",
+        "webpack-virtual-modules": "^0.6.2"
+      },
+      "engines": {
+        "node": ">=18.12.0"
+      }
+    },
+    "node_modules/unplugin-utils": {
+      "version": "0.2.5",
+      "resolved": "https://registry.npmmirror.com/unplugin-utils/-/unplugin-utils-0.2.5.tgz",
+      "integrity": "sha512-gwXJnPRewT4rT7sBi/IvxKTjsms7jX7QIDLOClApuZwR49SXbrB1z2NLUZ+vDHyqCj/n58OzRRqaW+B8OZi8vg==",
+      "license": "MIT",
+      "dependencies": {
+        "pathe": "^2.0.3",
+        "picomatch": "^4.0.3"
+      },
+      "engines": {
+        "node": ">=18.12.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sxzz"
+      }
+    },
+    "node_modules/unplugin-utils/node_modules/picomatch": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.4.tgz",
+      "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/unplugin/node_modules/picomatch": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.4.tgz",
+      "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/unquote": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/unquote/-/unquote-1.1.1.tgz",
+      "integrity": "sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg==",
+      "license": "MIT"
+    },
+    "node_modules/update-browserslist-db": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+      "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/browserslist"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "escalade": "^3.2.0",
+        "picocolors": "^1.1.1"
+      },
+      "bin": {
+        "update-browserslist-db": "cli.js"
+      },
+      "peerDependencies": {
+        "browserslist": ">= 4.21.0"
+      }
+    },
+    "node_modules/update-browserslist-db/node_modules/picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+      "license": "ISC"
+    },
+    "node_modules/utif": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/utif/-/utif-2.0.1.tgz",
+      "integrity": "sha512-Z/S1fNKCicQTf375lIP9G8Sa1H/phcysstNrrSdZKj1f9g58J4NMgb5IgiEZN9/nLMPDwF0W7hdOe9Qq2IYoLg==",
+      "license": "MIT",
+      "dependencies": {
+        "pako": "^1.0.5"
+      }
+    },
+    "node_modules/util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+      "license": "MIT"
+    },
+    "node_modules/utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4.0"
+      }
+    },
+    "node_modules/vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/vite": {
+      "version": "5.2.8",
+      "resolved": "https://registry.npmmirror.com/vite/-/vite-5.2.8.tgz",
+      "integrity": "sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "esbuild": "^0.20.1",
+        "postcss": "^8.4.38",
+        "rollup": "^4.13.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": "*",
+        "stylus": "*",
+        "sugarss": "*",
+        "terser": "^5.4.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/node": {
+          "optional": true
+        },
+        "less": {
+          "optional": true
+        },
+        "lightningcss": {
+          "optional": true
+        },
+        "sass": {
+          "optional": true
+        },
+        "stylus": {
+          "optional": true
+        },
+        "sugarss": {
+          "optional": true
+        },
+        "terser": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vue": {
+      "version": "3.4.21",
+      "resolved": "https://registry.npmmirror.com/vue/-/vue-3.4.21.tgz",
+      "integrity": "sha512-5hjyV/jLEIKD/jYl4cavMcnzKwjMKohureP8ejn3hhEjwhWIhWeuzL2kJAjzl/WyVsgPY56Sy4Z40C3lVshxXA==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/compiler-dom": "3.4.21",
+        "@vue/compiler-sfc": "3.4.21",
+        "@vue/runtime-dom": "3.4.21",
+        "@vue/server-renderer": "3.4.21",
+        "@vue/shared": "3.4.21"
+      },
+      "peerDependencies": {
+        "typescript": "*"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vue-demi": {
+      "version": "0.14.10",
+      "resolved": "https://registry.npmmirror.com/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/vue-i18n": {
+      "version": "9.14.4",
+      "resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-9.14.4.tgz",
+      "integrity": "sha512-B934C8yUyWLT0EMud3DySrwSUJI7ZNiWYsEEz2gknTthqKiG4dzWE/WSa8AzCuSQzwBEv4HtG1jZDhgzPfWSKQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@intlify/core-base": "9.14.4",
+        "@intlify/shared": "9.14.4",
+        "@vue/devtools-api": "^6.5.0"
+      },
+      "engines": {
+        "node": ">= 16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/kazupon"
+      },
+      "peerDependencies": {
+        "vue": "^3.0.0"
+      }
+    },
+    "node_modules/vue-i18n/node_modules/@intlify/core-base": {
+      "version": "9.14.4",
+      "resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-9.14.4.tgz",
+      "integrity": "sha512-vtZCt7NqWhKEtHa3SD/322DlgP5uR9MqWxnE0y8Q0tjDs9H5Lxhss+b5wv8rmuXRoHKLESNgw9d+EN9ybBbj9g==",
+      "license": "MIT",
+      "dependencies": {
+        "@intlify/message-compiler": "9.14.4",
+        "@intlify/shared": "9.14.4"
+      },
+      "engines": {
+        "node": ">= 16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/kazupon"
+      }
+    },
+    "node_modules/vue-i18n/node_modules/@intlify/message-compiler": {
+      "version": "9.14.4",
+      "resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-9.14.4.tgz",
+      "integrity": "sha512-vcyCLiVRN628U38c3PbahrhbbXrckrM9zpy0KZVlDk2Z0OnGwv8uQNNXP3twwGtfLsCf4gu3ci6FMIZnPaqZsw==",
+      "license": "MIT",
+      "dependencies": {
+        "@intlify/shared": "9.14.4",
+        "source-map-js": "^1.0.2"
+      },
+      "engines": {
+        "node": ">= 16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/kazupon"
+      }
+    },
+    "node_modules/vue-i18n/node_modules/@intlify/shared": {
+      "version": "9.14.4",
+      "resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-9.14.4.tgz",
+      "integrity": "sha512-P9zv6i1WvMc9qDBWvIgKkymjY2ptIiQ065PjDv7z7fDqH3J/HBRBN5IoiR46r/ujRcU7hCuSIZWvCAFCyuOYZA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/kazupon"
+      }
+    },
+    "node_modules/vue-router": {
+      "version": "4.4.4",
+      "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.4.4.tgz",
+      "integrity": "sha512-3MlnDqwRwZwCQVbtVfpsU+nrNymNjnXSsQtXName5925NVC1+326VVfYH9vSrA0N13teGEo8z5x7gbRnGjCDiQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/devtools-api": "^6.6.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/posva"
+      },
+      "peerDependencies": {
+        "vue": "^3.2.0"
+      }
+    },
+    "node_modules/vue-template-compiler": {
+      "version": "2.7.16",
+      "resolved": "https://registry.npmmirror.com/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz",
+      "integrity": "sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "de-indent": "^1.0.2",
+        "he": "^1.2.0"
+      }
+    },
+    "node_modules/vue-tsc": {
+      "version": "1.8.27",
+      "resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-1.8.27.tgz",
+      "integrity": "sha512-WesKCAZCRAbmmhuGl3+VrdWItEvfoFIPXOvUJkjULi+x+6G/Dy69yO3TBRJDr9eUlmsNAwVmxsNZxvHKzbkKdg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@volar/typescript": "~1.11.1",
+        "@vue/language-core": "1.8.27",
+        "semver": "^7.5.4"
+      },
+      "bin": {
+        "vue-tsc": "bin/vue-tsc.js"
+      },
+      "peerDependencies": {
+        "typescript": "*"
+      }
+    },
+    "node_modules/vue-tsc/node_modules/semver": {
+      "version": "7.8.5",
+      "resolved": "https://registry.npmmirror.com/semver/-/semver-7.8.5.tgz",
+      "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
+      "dev": true,
+      "license": "ISC",
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/webpack-virtual-modules": {
+      "version": "0.6.2",
+      "resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
+      "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
+      "license": "MIT"
+    },
+    "node_modules/which": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz",
+      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "isexe": "^2.0.0"
+      },
+      "bin": {
+        "node-which": "bin/node-which"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/ws": {
+      "version": "8.18.0",
+      "resolved": "https://registry.npmmirror.com/ws/-/ws-8.18.0.tgz",
+      "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.0.0"
+      },
+      "peerDependencies": {
+        "bufferutil": "^4.0.1",
+        "utf-8-validate": ">=5.0.2"
+      },
+      "peerDependenciesMeta": {
+        "bufferutil": {
+          "optional": true
+        },
+        "utf-8-validate": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/xhr": {
+      "version": "2.6.0",
+      "resolved": "https://registry.npmmirror.com/xhr/-/xhr-2.6.0.tgz",
+      "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==",
+      "license": "MIT",
+      "dependencies": {
+        "global": "~4.4.0",
+        "is-function": "^1.0.1",
+        "parse-headers": "^2.0.0",
+        "xtend": "^4.0.0"
+      }
+    },
+    "node_modules/xml-parse-from-string": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz",
+      "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==",
+      "license": "MIT"
+    },
+    "node_modules/xml2js": {
+      "version": "0.5.0",
+      "resolved": "https://registry.npmmirror.com/xml2js/-/xml2js-0.5.0.tgz",
+      "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==",
+      "license": "MIT",
+      "dependencies": {
+        "sax": ">=0.6.0",
+        "xmlbuilder": "~11.0.0"
+      },
+      "engines": {
+        "node": ">=4.0.0"
+      }
+    },
+    "node_modules/xmlbuilder": {
+      "version": "11.0.1",
+      "resolved": "https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
+      "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4.0"
+      }
+    },
+    "node_modules/xmlhttprequest": {
+      "version": "1.8.0",
+      "resolved": "https://registry.npmmirror.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz",
+      "integrity": "sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/xregexp": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/xregexp/-/xregexp-3.1.0.tgz",
+      "integrity": "sha512-4Y1x6DyB8xRoxosooa6PlGWqmmSKatbzhrftZ7Purmm4B8R4qIEJG1A2hZsdz5DhmIqS0msC0I7KEq93GphEVg==",
+      "license": "MIT"
+    },
+    "node_modules/xtend": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz",
+      "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.4"
+      }
+    },
+    "node_modules/yallist": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz",
+      "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+      "license": "ISC"
+    },
+    "node_modules/yaml": {
+      "version": "1.10.3",
+      "resolved": "https://registry.npmmirror.com/yaml/-/yaml-1.10.3.tgz",
+      "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==",
+      "license": "ISC",
+      "engines": {
+        "node": ">= 6"
+      }
+    }
+  }
+}

+ 55 - 0
package.json

@@ -0,0 +1,55 @@
+{
+  "name": "清道夫App",
+  "version": "1.0.0",
+  "scripts": {
+    "dev:custom": "uni -p",
+    "dev:h5": "uni",
+    "dev:h5:ssr": "uni --ssr",
+    "dev:mp-weixin": "uni -p mp-weixin",
+    "build:custom": "uni build -p",
+    "build:h5": "uni build",
+    "build:h5:ssr": "uni build --ssr",
+    "build:mp-weixin": "uni build -p mp-weixin",
+    "type-check": "vue-tsc --noEmit"
+  },
+  "dependencies": {
+    "@dcloudio/uni-app": "3.0.0-4080420251103001",
+    "@dcloudio/uni-app-harmony": "3.0.0-4080420251103001",
+    "@dcloudio/uni-app-plus": "3.0.0-4080420251103001",
+    "@dcloudio/uni-components": "3.0.0-4080420251103001",
+    "@dcloudio/uni-h5": "3.0.0-4080420251103001",
+    "@dcloudio/uni-mp-alipay": "3.0.0-4080420251103001",
+    "@dcloudio/uni-mp-baidu": "3.0.0-4080420251103001",
+    "@dcloudio/uni-mp-harmony": "3.0.0-4080420251103001",
+    "@dcloudio/uni-mp-jd": "3.0.0-4080420251103001",
+    "@dcloudio/uni-mp-kuaishou": "3.0.0-4080420251103001",
+    "@dcloudio/uni-mp-lark": "3.0.0-4080420251103001",
+    "@dcloudio/uni-mp-qq": "3.0.0-4080420251103001",
+    "@dcloudio/uni-mp-toutiao": "3.0.0-4080420251103001",
+    "@dcloudio/uni-mp-weixin": "3.0.0-4080420251103001",
+    "@dcloudio/uni-mp-xhs": "3.0.0-4080420251103001",
+    "@dcloudio/uni-quickapp-webview": "3.0.0-4080420251103001",
+    "pinia": "^2.3.1",
+    "uni-ui": "^1.0.0",
+    "vue": "^3.4.21",
+    "vue-i18n": "^9.1.9"
+  },
+  "devDependencies": {
+    "@dcloudio/types": "^3.4.8",
+    "@dcloudio/uni-automator": "3.0.0-4080420251103001",
+    "@dcloudio/uni-cli-shared": "3.0.0-4080420251103001",
+    "@dcloudio/uni-stacktracey": "3.0.0-4080420251103001",
+    "@dcloudio/vite-plugin-uni": "3.0.0-4080420251103001",
+    "@types/node": "^26.0.0",
+    "@vue/runtime-core": "^3.4.21",
+    "@vue/tsconfig": "^0.1.3",
+    "autoprefixer": "^10.5.0",
+    "postcss": "^8.5.15",
+    "sass": "^1.101.0",
+    "tailwindcss": "^3.4.19",
+    "typescript": "^4.9.4",
+    "vite": "5.2.8",
+    "vue-tsc": "^1.0.24"
+  },
+  "description": "清道夫系统 - 环境服务管理平台"
+}

+ 10 - 0
shims-uni.d.ts

@@ -0,0 +1,10 @@
+/// <reference types='@dcloudio/types' />
+import 'vue'
+
+declare module '@vue/runtime-core' {
+  type Hooks = App.AppInstance & Page.PageInstance;
+
+  interface ComponentCustomOptions extends Hooks {
+
+  }
+}

+ 18 - 0
src/App.vue

@@ -0,0 +1,18 @@
+<script setup lang="ts">
+import { onLaunch, onShow } from "@dcloudio/uni-app";
+import { useAuthStore } from "./stores/auth";
+
+onLaunch(() => {
+  console.log("App Launch");
+  const authStore = useAuthStore();
+  authStore.checkLogin();
+});
+
+onShow(() => {
+  console.log("App Show");
+});
+</script>
+
+<style>
+@import "@/styles/uni-icons.css";
+</style>

+ 36 - 0
src/api/auth.ts

@@ -0,0 +1,36 @@
+import { post } from './request'
+
+export interface LoginParams {
+  username: string
+  password: string
+  roleType?: string
+}
+
+export interface LoginResult {
+  token: string
+  userId: number
+  userName: string
+  nickName: string
+  roleType: string
+  avatar: string
+}
+
+export function login(params: LoginParams) {
+  return post<LoginResult>('/auth/login', params)
+}
+
+export function logout() {
+  return post('/auth/logout')
+}
+
+export function debugPassword() {
+  return post('/auth/debug-password')
+}
+
+export function resetPassword() {
+  return post('/auth/reset-password')
+}
+
+export function initData() {
+  return post('/auth/init-data')
+}

+ 21 - 0
src/api/dashboard.ts

@@ -0,0 +1,21 @@
+import { get } from './request'
+
+export function getDashboardStats() {
+  return get('/dashboard/stats')
+}
+
+export function getTaskOverview() {
+  return get('/dashboard/task-overview')
+}
+
+export function getVehicleStatus() {
+  return get('/dashboard/vehicle-status')
+}
+
+export function getPersonnelStatus() {
+  return get('/dashboard/personnel-status')
+}
+
+export function getEmergencyStatus() {
+  return get('/dashboard/emergency-status')
+}

+ 29 - 0
src/api/notice.ts

@@ -0,0 +1,29 @@
+import { get, post, put } from './request'
+
+export function getNoticeList(pageNum = 1, pageSize = 10, type?: string) {
+  return get('/notice/list', { pageNum, pageSize, type })
+}
+
+export function getNoticeDetail(noticeId: number) {
+  return get(`/notice/${noticeId}`)
+}
+
+export function getMessageList(pageNum = 1, pageSize = 10) {
+  return get('/message/list', { pageNum, pageSize })
+}
+
+export function getMessageDetail(messageId: number) {
+  return get(`/message/${messageId}`)
+}
+
+export function markNoticeRead(noticeId: number) {
+  return put(`/notice/${noticeId}/read`)
+}
+
+export function markAllRead() {
+  return put('/notice/read-all')
+}
+
+export function getUnreadCount() {
+  return get('/notice/unread-count')
+}

+ 29 - 0
src/api/project.ts

@@ -0,0 +1,29 @@
+import { get, post, put, del } from './request'
+
+export interface ProjectParams {
+  projectName: string
+  projectType: string
+  customerName: string
+  customerPhone: string
+  address: string
+}
+
+export function getProjectList(pageNum = 1, pageSize = 10) {
+  return get('/project/list', { pageNum, pageSize })
+}
+
+export function getProjectDetail(projectId: number) {
+  return get(`/project/${projectId}`)
+}
+
+export function createProject(data: ProjectParams) {
+  return post('/project', data)
+}
+
+export function updateProject(data: ProjectParams & { projectId: number }) {
+  return put('/project', data)
+}
+
+export function deleteProject(projectId: number) {
+  return del(`/project/${projectId}`)
+}

+ 103 - 0
src/api/request.ts

@@ -0,0 +1,103 @@
+// HTTP 请求封装
+
+const BASE_URL = 'http://localhost:8080/api'
+
+interface RequestOptions {
+  url: string
+  method?: 'GET' | 'POST' | 'PUT' | 'DELETE'
+  data?: any
+  params?: Record<string, any>
+  header?: Record<string, string>
+  loading?: boolean
+}
+
+interface ApiResponse<T = any> {
+  code: number
+  msg: string
+  data: T
+  timestamp: number
+}
+
+export async function request<T = any>(options: RequestOptions): Promise<T> {
+  const { url, method = 'GET', data, params, header = {}, loading = true } = options
+
+  if (loading) {
+    uni.showLoading({ title: '加载中...', mask: true })
+  }
+
+  // 构建完整 URL
+  let fullUrl = url.startsWith('http') ? url : `${BASE_URL}${url}`
+  if (params) {
+    const queryString = Object.entries(params)
+      .filter(([, value]) => value !== undefined && value !== null)
+      .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`)
+      .join('&')
+    if (queryString) {
+      fullUrl += `${fullUrl.includes('?') ? '&' : '?'}${queryString}`
+    }
+  }
+
+  // 获取 token
+  const token = uni.getStorageSync('token')
+  const requestHeader: Record<string, string> = {
+    'Content-Type': 'application/json',
+    ...header,
+  }
+  if (token) {
+    requestHeader['Authorization'] = `Bearer ${token}`
+  }
+
+  return new Promise((resolve, reject) => {
+    uni.request({
+      url: fullUrl,
+      method,
+      data,
+      header: requestHeader,
+      success: (res) => {
+        if (loading) uni.hideLoading()
+
+        const response = res.data as ApiResponse<T>
+
+        if (res.statusCode === 200) {
+          if (response.code === 200) {
+            resolve(response.data)
+          } else {
+            uni.showToast({ title: response.msg || '请求失败', icon: 'none' })
+            reject(new Error(response.msg))
+          }
+        } else if (res.statusCode === 401) {
+          uni.showToast({ title: '登录已过期,请重新登录', icon: 'none' })
+          uni.removeStorageSync('token')
+          uni.removeStorageSync('user')
+          uni.reLaunch({ url: '/pages/login/login' })
+          reject(new Error('Unauthorized'))
+        } else {
+          uni.showToast({ title: `请求失败: ${res.statusCode}`, icon: 'none' })
+          reject(new Error(`HTTP ${res.statusCode}`))
+        }
+      },
+      fail: (err) => {
+        if (loading) uni.hideLoading()
+        uni.showToast({ title: '网络请求失败', icon: 'none' })
+        reject(err)
+      },
+    })
+  })
+}
+
+// 便捷方法
+export function get<T = any>(url: string, params?: Record<string, any>, options?: Partial<RequestOptions>) {
+  return request<T>({ url, method: 'GET', params, ...options })
+}
+
+export function post<T = any>(url: string, data?: any, options?: Partial<RequestOptions>) {
+  return request<T>({ url, method: 'POST', data, ...options })
+}
+
+export function put<T = any>(url: string, data?: any, options?: Partial<RequestOptions>) {
+  return request<T>({ url, method: 'PUT', data, ...options })
+}
+
+export function del<T = any>(url: string, params?: Record<string, any>, options?: Partial<RequestOptions>) {
+  return request<T>({ url, method: 'DELETE', params, ...options })
+}

+ 21 - 0
src/api/schedule.ts

@@ -0,0 +1,21 @@
+import { get, post, put, del } from './request'
+
+export function getScheduleList(year?: number, month?: number) {
+  return get('/schedule/list', { year, month })
+}
+
+export function getScheduleDetail(scheduleId: number) {
+  return get(`/schedule/${scheduleId}`)
+}
+
+export function createSchedule(data: { taskId: number; staffId: number; scheduleDate: string; remark?: string }) {
+  return post('/schedule', data)
+}
+
+export function updateSchedule(scheduleId: number, data: Partial<{ status: string; remark: string }>) {
+  return put(`/schedule/${scheduleId}`, data)
+}
+
+export function deleteSchedule(scheduleId: number) {
+  return del(`/schedule/${scheduleId}`)
+}

+ 17 - 0
src/api/staff.ts

@@ -0,0 +1,17 @@
+import { get, post, put, del } from './request'
+
+export function getStaffList(pageNum = 1, pageSize = 10, status?: string) {
+  return get('/staff/list', { pageNum, pageSize, status })
+}
+
+export function getStaffDetail(staffId: number) {
+  return get(`/staff/${staffId}`)
+}
+
+export function getOnDutyStaff() {
+  return get('/staff/on-duty')
+}
+
+export function getEmergencyTeam() {
+  return get('/staff/emergency-team')
+}

+ 46 - 0
src/api/task.ts

@@ -0,0 +1,46 @@
+import { get, post, put, del } from './request'
+
+export interface TaskParams {
+  projectId?: number
+  taskType: string
+  urgentLevel: string
+  remark?: string
+  staffId?: number
+  vehicleId?: number
+}
+
+export function getTaskList(pageNum = 1, pageSize = 10) {
+  return get('/task/list', { pageNum, pageSize })
+}
+
+export function getTaskDetail(taskId: number) {
+  return get(`/task/${taskId}`)
+}
+
+export function createTask(data: TaskParams) {
+  return post('/task', data)
+}
+
+export function updateTask(taskId: number, data: Partial<TaskParams>) {
+  return put('/task', { taskId, ...data })
+}
+
+export function deleteTask(taskId: number) {
+  return del(`/task/${taskId}`)
+}
+
+export function advanceTaskStep(taskId: number, step: number, data?: Record<string, any>) {
+  return post(`/task/${taskId}/step/${step}`, data)
+}
+
+export function urgeTask(taskId: number, urgeReason: string, operatorId: number, operatorName: string) {
+  return post(`/task/${taskId}/urge`, { taskId, urgeReason, operatorId, operatorName })
+}
+
+export function cancelTask(taskId: number, cancelReason: string, operatorId: number, operatorName: string) {
+  return post(`/task/${taskId}/cancel`, { taskId, cancelReason, operatorId, operatorName })
+}
+
+export function getTaskLogs(taskId: number) {
+  return get(`/task/${taskId}/logs`)
+}

+ 13 - 0
src/api/vehicle.ts

@@ -0,0 +1,13 @@
+import { get, post, put, del } from './request'
+
+export function getVehicleList(pageNum = 1, pageSize = 10) {
+  return get('/vehicle/list', { pageNum, pageSize })
+}
+
+export function getAvailableVehicles() {
+  return get('/vehicle/available')
+}
+
+export function getVehicleDetail(vehicleId: number) {
+  return get(`/vehicle/${vehicleId}`)
+}

+ 23 - 0
src/components/common/AppContainer.vue

@@ -0,0 +1,23 @@
+<template>
+  <view class="app-container min-h-screen bg-background" :class="{ 'pb-16': showNav }">
+    <slot />
+  </view>
+</template>
+
+<script setup lang="ts">withDefaults(
+  defineProps<{
+    showNav?: boolean
+  }>(),
+  {
+    showNav: false
+  }
+)
+</script>
+
+<style scoped>
+.app-container {
+  max-width: 430px;
+  margin: 0 auto;
+  position: relative;
+}
+</style>

+ 68 - 0
src/components/common/BottomNav.vue

@@ -0,0 +1,68 @@
+<template>
+  <view class="bottom-nav fixed bottom-0 left-0 right-0 bg-white z-50 safe-bottom">
+    <view class="max-w-app mx-auto flex justify-around items-center h-14">
+      <view
+        v-for="item in items"
+        :key="item.path"
+        class="nav-item flex flex-col items-center justify-center flex-1 py-1 clickable"
+        :class="{ active: isActive(item.path) }"
+        @click="onNavClick(item.path)"
+      >
+        <view
+          class="nav-icon w-10 h-10 rounded-xl flex items-center justify-center mb-0.5"
+          :class="isActive(item.path) ? 'bg-primary-light' : ''"
+        >
+          <text
+            class="uni-icons text-xl"
+            :class="isActive(item.path) ? 'text-primary' : 'text-gray-400'"
+          >
+            {{ isActive(item.path) && item.activeIcon ? item.activeIcon : item.icon }}
+          </text>
+        </view>
+        <text
+          class="text-xs"
+          :class="isActive(item.path) ? 'text-primary font-medium' : 'text-gray-400'"
+        >
+          {{ item.text }}
+        </text>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue'
+import type { NavItem } from '@/types'
+
+const props = defineProps<{
+  items: NavItem[]
+  currentPath: string
+}>()
+
+const emit = defineEmits<{
+  (e: 'navigate', path: string): void
+}>()
+
+const activeIndex = computed(() =>
+  props.items.findIndex((item) => isActive(item.path))
+)
+
+function isActive(path: string): boolean {
+  return props.currentPath === path || props.currentPath.startsWith(path + '/')
+}
+
+function onNavClick(path: string) {
+  if (isActive(path)) return
+  emit('navigate', path)
+}
+</script>
+
+<style scoped>
+.bottom-nav {
+  box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.08);
+}
+
+.nav-item.active .nav-icon {
+  background: #eff6ff;
+}
+</style>

+ 30 - 0
src/components/common/Card.vue

@@ -0,0 +1,30 @@
+<template>
+  <view class="card bg-white rounded-card p-4 mb-3 clickable" :class="cardClass" @click="onClick">
+    <slot />
+  </view>
+</template>
+
+<script setup lang="ts">
+const props = defineProps<{
+  cardClass?: string
+}>()
+
+const emit = defineEmits<{
+  (e: 'click'): void
+}>()
+
+function onClick() {
+  emit('click')
+}
+</script>
+
+<style scoped>
+.card {
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+}
+
+.card:active {
+  transform: scale(0.98);
+  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
+}
+</style>

+ 206 - 0
src/components/common/ChartView.vue

@@ -0,0 +1,206 @@
+<template>
+  <view class="chart-container" :style="{ height: height + 'px' }">
+    <canvas
+      :id="canvasId"
+      :canvas-id="canvasId"
+      class="w-full h-full"
+      @touchstart="touchStart"
+      @touchmove="touchMove"
+    />
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted, watch } from 'vue'
+
+interface Props {
+  type: 'bar' | 'line' | 'pie' | 'ring'
+  data: { label: string; value: number; color?: string }[]
+  height?: number
+  canvasId?: string
+  title?: string
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  height: 200,
+  canvasId: 'chart-canvas',
+  title: '',
+})
+
+const colors = ['#4f8ef7', '#34c759', '#ff9500', '#ff3b30', '#5856d6', '#ff2d55', '#5ac8fa', '#af52de']
+
+let ctx: UniApp.CanvasContext | null = null
+
+onMounted(() => {
+  drawChart()
+})
+
+watch(() => props.data, () => {
+  drawChart()
+}, { deep: true })
+
+function drawChart() {
+  ctx = uni.createCanvasContext(props.canvasId)
+  if (!ctx) return
+
+  const { width } = uni.getSystemInfoSync()
+  const canvasWidth = width - 32 // 减去padding
+  const canvasHeight = props.height
+
+  ctx.clearRect(0, 0, canvasWidth, canvasHeight)
+
+  if (props.type === 'pie' || props.type === 'ring') {
+    drawPie(canvasWidth, canvasHeight)
+  } else if (props.type === 'bar') {
+    drawBar(canvasWidth, canvasHeight)
+  } else if (props.type === 'line') {
+    drawLine(canvasWidth, canvasHeight)
+  }
+
+  ctx.draw()
+}
+
+function drawPie(width: number, height: number) {
+  if (!ctx) return
+  const total = props.data.reduce((sum, item) => sum + item.value, 0)
+  let startAngle = -Math.PI / 2
+  const centerX = width / 2
+  const centerY = height / 2
+  const radius = Math.min(centerX, centerY) - 40
+
+  props.data.forEach((item, index) => {
+    const angle = (item.value / total) * Math.PI * 2
+    const endAngle = startAngle + angle
+    const color = item.color || colors[index % colors.length]
+
+    ctx!.beginPath()
+    ctx!.moveTo(centerX, centerY)
+    ctx!.arc(centerX, centerY, radius, startAngle, endAngle)
+    ctx!.closePath()
+    ctx!.setFillStyle(color)
+    ctx!.fill()
+
+    // 标签
+    const midAngle = startAngle + angle / 2
+    const labelX = centerX + Math.cos(midAngle) * (radius + 15)
+    const labelY = centerY + Math.sin(midAngle) * (radius + 15)
+    ctx!.setFontSize(10)
+    ctx!.setFillStyle('#666')
+    ctx!.fillText(`${item.label}`, labelX - 15, labelY)
+    ctx!.fillText(`${item.value}`, labelX - 5, labelY + 12)
+
+    startAngle = endAngle
+  })
+
+  // 如果是 ring 类型,绘制中心圆
+  if (props.type === 'ring') {
+    ctx!.beginPath()
+    ctx!.arc(centerX, centerY, radius * 0.6, 0, Math.PI * 2)
+    ctx!.setFillStyle('#fff')
+    ctx!.fill()
+    ctx!.setFontSize(14)
+    ctx!.setFillStyle('#333')
+    ctx!.setTextAlign('center')
+    ctx!.fillText('总计', centerX, centerY - 5)
+    ctx!.fillText(String(total), centerX, centerY + 15)
+  }
+}
+
+function drawBar(width: number, height: number) {
+  if (!ctx) return
+  const padding = 30
+  const chartHeight = height - padding * 2
+  const chartWidth = width - padding * 2
+  const maxValue = Math.max(...props.data.map(d => d.value))
+  const barWidth = (chartWidth / props.data.length) * 0.6
+  const gap = (chartWidth / props.data.length) * 0.4
+
+  // 绘制坐标轴
+  ctx.setStrokeStyle('#e0e0e0')
+  ctx.beginPath()
+  ctx.moveTo(padding, padding)
+  ctx.lineTo(padding, height - padding)
+  ctx.lineTo(width - padding, height - padding)
+  ctx.stroke()
+
+  // 绘制柱状图
+  props.data.forEach((item, index) => {
+    const x = padding + gap / 2 + index * (barWidth + gap)
+    const barHeight = (item.value / maxValue) * chartHeight
+    const y = height - padding - barHeight
+    const color = item.color || colors[index % colors.length]
+
+    ctx!.setFillStyle(color)
+    ctx!.fillRect(x, y, barWidth, barHeight)
+
+    // 数值
+    ctx!.setFontSize(10)
+    ctx!.setFillStyle('#333')
+    ctx!.fillText(String(item.value), x + barWidth / 2 - 5, y - 5)
+
+    // 标签
+    ctx!.setFontSize(10)
+    ctx!.setFillStyle('#666')
+    ctx!.fillText(item.label, x, height - padding + 15)
+  })
+}
+
+function drawLine(width: number, height: number) {
+  if (!ctx) return
+  const padding = 30
+  const chartHeight = height - padding * 2
+  const chartWidth = width - padding * 2
+  const maxValue = Math.max(...props.data.map(d => d.value))
+  const pointGap = chartWidth / (props.data.length - 1)
+
+  // 绘制坐标轴
+  ctx.setStrokeStyle('#e0e0e0')
+  ctx.beginPath()
+  ctx.moveTo(padding, padding)
+  ctx.lineTo(padding, height - padding)
+  ctx.lineTo(width - padding, height - padding)
+  ctx.stroke()
+
+  // 绘制线条
+  ctx.setStrokeStyle('#4f8ef7')
+  ctx.setLineWidth(2)
+  ctx.beginPath()
+
+  const points: { x: number; y: number }[] = []
+  props.data.forEach((item, index) => {
+    const x = padding + index * pointGap
+    const y = height - padding - (item.value / maxValue) * chartHeight
+    points.push({ x, y })
+    if (index === 0) {
+      ctx!.moveTo(x, y)
+    } else {
+      ctx!.lineTo(x, y)
+    }
+  })
+  ctx.stroke()
+
+  // 绘制数据点和数值
+  points.forEach((point, index) => {
+    ctx!.beginPath()
+    ctx!.arc(point.x, point.y, 4, 0, Math.PI * 2)
+    ctx!.setFillStyle('#4f8ef7')
+    ctx!.fill()
+
+    ctx!.setFontSize(10)
+    ctx!.setFillStyle('#333')
+    ctx!.fillText(String(props.data[index].value), point.x - 5, point.y - 10)
+    ctx!.setFillStyle('#666')
+    ctx!.fillText(props.data[index].label, point.x - 10, height - padding + 15)
+  })
+}
+
+// 触摸事件处理
+let touchStartX = 0
+function touchStart(e: any) {
+  touchStartX = e.touches[0].x
+}
+
+function touchMove(e: any) {
+  // 可以添加滑动交互
+}
+</script>

+ 20 - 0
src/components/common/EmptyState.vue

@@ -0,0 +1,20 @@
+<template>
+  <view class="empty-state flex flex-col items-center justify-center py-16">
+    <text class="text-5xl mb-4">{{ icon }}</text>
+    <text class="text-gray-400 text-sm">{{ message }}</text>
+    <slot />
+  </view>
+</template>
+
+<script setup lang="ts">
+withDefaults(
+  defineProps<{
+    message?: string
+    icon?: string
+  }>(),
+  {
+    message: '暂无数据',
+    icon: ''
+  }
+)
+</script>

+ 67 - 0
src/components/common/FilterTabs.vue

@@ -0,0 +1,67 @@
+<template>
+  <scroll-view
+    class="filter-tabs bg-white flex gap-2 px-4 py-3 hide-scrollbar"
+    scroll-x
+    show-scrollbar="false"
+  >
+    <view
+      v-for="tab in tabs"
+      :key="tab.value"
+      class="filter-tab clickable"
+      :class="{ active: modelValue === tab.value }"
+      @click="onSelect(tab.value)"
+    >
+      {{ tab.label }}
+    </view>
+  </scroll-view>
+</template>
+
+<script setup lang="ts">
+export interface FilterTab {
+  label: string
+  value: string
+}
+
+const props = defineProps<{
+  tabs: FilterTab[]
+  modelValue: string
+}>()
+
+const emit = defineEmits<{
+  (e: 'update:modelValue', value: string): void
+  (e: 'change', value: string): void
+}>()
+
+function onSelect(value: string) {
+  if (value === props.modelValue) return
+  emit('update:modelValue', value)
+  emit('change', value)
+}
+</script>
+
+<style scoped>
+.filter-tabs {
+  white-space: nowrap;
+}
+
+.filter-tab {
+  flex-shrink: 0;
+  padding: 6px 16px;
+  border-radius: 20px;
+  font-size: 13px;
+  color: #666;
+  background: #f5f5f5;
+  border: 1px solid #e5e7eb;
+  transition: all 0.2s ease;
+}
+
+.filter-tab.active {
+  background: #3b82f6;
+  color: #fff;
+  border-color: #3b82f6;
+}
+
+.filter-tab:active {
+  transform: scale(0.95);
+}
+</style>

+ 86 - 0
src/components/common/ImageUploader.vue

@@ -0,0 +1,86 @@
+<template>
+  <view class="image-uploader">
+    <!-- 已上传图片列表 -->
+    <view class="flex flex-wrap gap-2">
+      <view 
+        v-for="(url, index) in modelValue" 
+        :key="index" 
+        class="relative w-20 h-20 rounded-lg overflow-hidden border border-gray-200"
+      >
+        <image 
+          :src="url" 
+          class="w-full h-full object-cover" 
+          mode="aspectFill"
+          @click="previewImage(index)"
+        />
+        <view 
+          class="absolute top-0 right-0 w-5 h-5 bg-red-500 rounded-full flex items-center justify-center -mt-1 -mr-1 z-10"
+          @click="removeImage(index)"
+        >
+          <text class="uni-icons uniui-close text-white text-xs"></text>
+        </view>
+      </view>
+      
+      <!-- 上传按钮 -->
+      <view 
+        v-if="modelValue.length < maxCount"
+        class="w-20 h-20 rounded-lg border-2 border-dashed border-gray-300 flex flex-col items-center justify-center cursor-pointer bg-gray-50"
+        @click="chooseImage"
+      >
+        <text class="uni-icons uniui-camera-filled text-gray-400 text-xl"></text>
+        <text class="text-xs text-gray-400 mt-1">{{ modelValue.length }}/{{ maxCount }}</text>
+      </view>
+    </view>
+    
+    <!-- 提示文字 -->
+    <text v-if="tip" class="text-xs text-gray-400 mt-2 block">{{ tip }}</text>
+  </view>
+</template>
+
+<script setup lang="ts">
+interface Props {
+  modelValue: string[]
+  maxCount?: number
+  tip?: string
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  maxCount: 9,
+  tip: '',
+})
+
+const emit = defineEmits<{
+  'update:modelValue': [value: string[]]
+}>()
+
+function chooseImage() {
+  const remainCount = props.maxCount - props.modelValue.length
+  if (remainCount <= 0) {
+    uni.showToast({ title: '最多上传' + props.maxCount + '张', icon: 'none' })
+    return
+  }
+
+  uni.chooseImage({
+    count: remainCount,
+    sizeType: ['compressed'],
+    sourceType: ['album', 'camera'],
+    success: (res) => {
+      const newImages = [...props.modelValue, ...res.tempFilePaths]
+      emit('update:modelValue', newImages)
+    },
+  })
+}
+
+function removeImage(index: number) {
+  const newImages = [...props.modelValue]
+  newImages.splice(index, 1)
+  emit('update:modelValue', newImages)
+}
+
+function previewImage(index: number) {
+  uni.previewImage({
+    urls: props.modelValue,
+    current: props.modelValue[index],
+  })
+}
+</script>

+ 65 - 0
src/components/common/Loading.vue

@@ -0,0 +1,65 @@
+<template>
+  <view v-if="visible" class="loading-wrap">
+    <view class="loading-content">
+      <view class="loading-spinner"></view>
+      <text v-if="title" class="loading-text">{{ title }}</text>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">withDefaults(
+  defineProps<{
+    visible: boolean
+    title?: string
+  }>(),
+  {
+    title: '加载中...'
+  }
+)
+</script>
+
+<style scoped>
+.loading-wrap {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 9999;
+  background: rgba(255, 255, 255, 0.6);
+}
+
+.loading-content {
+  padding: 20px 24px;
+  background: rgba(0, 0, 0, 0.7);
+  border-radius: 8px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
+
+.loading-spinner {
+  width: 28px;
+  height: 28px;
+  border: 3px solid rgba(255, 255, 255, 0.3);
+  border-top-color: #fff;
+  border-radius: 50%;
+  animation: spin 0.8s linear infinite;
+}
+
+.loading-text {
+  margin-top: 10px;
+  font-size: 14px;
+  color: #fff;
+}
+
+@keyframes spin {
+  to {
+    transform: rotate(360deg);
+  }
+}
+</style>

+ 65 - 0
src/components/common/MapView.vue

@@ -0,0 +1,65 @@
+<template>
+  <view class="map-container" :style="{ height: height + 'px' }">
+    <map
+      v-if="showMap"
+      :style="{ width: '100%', height: '100%' }"
+      :latitude="latitude"
+      :longitude="longitude"
+      :markers="markers"
+      :scale="scale"
+      :show-location="showLocation"
+      @markertap="onMarkerTap"
+    />
+    <view v-else class="flex items-center justify-center h-full bg-gray-100">
+      <text class="text-gray-400">地图加载中...</text>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue'
+
+interface Props {
+  latitude: number
+  longitude: number
+  height?: number
+  scale?: number
+  showLocation?: boolean
+  title?: string
+  address?: string
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  height: 200,
+  scale: 15,
+  showLocation: true,
+  title: '',
+  address: '',
+})
+
+const showMap = computed(() => {
+  return props.latitude !== 0 && props.longitude !== 0
+})
+
+const markers = computed(() => {
+  return [{
+    id: 1,
+    latitude: props.latitude,
+    longitude: props.longitude,
+    title: props.title,
+    iconPath: '/static/images/location.png',
+    width: 32,
+    height: 32,
+  }]
+})
+
+function onMarkerTap() {
+  // 打开导航
+  uni.openLocation({
+    latitude: props.latitude,
+    longitude: props.longitude,
+    name: props.title,
+    address: props.address,
+  })
+}
+</script>

+ 111 - 0
src/components/common/Modal.vue

@@ -0,0 +1,111 @@
+<template>
+  <view v-if="visible" class="modal-wrap">
+    <view class="modal-mask" @click="onMaskClick"></view>
+    <view class="modal-content bg-white rounded-card p-4 mx-6">
+      <text v-if="title" class="modal-title text-lg font-semibold text-gray-800 text-center mb-3">{{ title }}</text>
+      <view class="modal-body text-sm text-gray-600 text-center mb-5">
+        <slot name="content">
+          <text>{{ content }}</text>
+        </slot>
+      </view>
+      <view class="modal-footer flex gap-3">
+        <button
+          v-if="showCancel"
+          class="flex-1 py-2.5 rounded-button border border-gray-200 text-gray-600 text-sm font-medium clickable"
+          @click="onCancel"
+        >
+          {{ cancelText }}
+        </button>
+        <button
+          class="flex-1 py-2.5 rounded-button bg-primary text-white text-sm font-medium clickable"
+          @click="onConfirm"
+        >
+          {{ confirmText }}
+        </button>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+const props = withDefaults(
+  defineProps<{
+    visible: boolean
+    title?: string
+    content?: string
+    showCancel?: boolean
+    cancelText?: string
+    confirmText?: string
+    closeOnMask?: boolean
+  }>(),
+  {
+    title: '',
+    content: '',
+    showCancel: true,
+    cancelText: '取消',
+    confirmText: '确定',
+    closeOnMask: false
+  }
+)
+
+const emit = defineEmits<{
+  (e: 'update:visible', value: boolean): void
+  (e: 'confirm'): void
+  (e: 'cancel'): void
+}>()
+
+function onMaskClick() {
+  if (props.closeOnMask) {
+    emit('update:visible', false)
+    emit('cancel')
+  }
+}
+
+function onCancel() {
+  emit('update:visible', false)
+  emit('cancel')
+}
+
+function onConfirm() {
+  emit('update:visible', false)
+  emit('confirm')
+}
+</script>
+
+<style scoped>
+.modal-wrap {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 10000;
+}
+
+.modal-mask {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+}
+
+.modal-content {
+  position: relative;
+  width: 100%;
+  max-width: 320px;
+  z-index: 1;
+}
+
+.modal-title {
+  display: block;
+}
+
+.modal-body {
+  display: block;
+}
+</style>

+ 118 - 0
src/components/common/SignaturePad.vue

@@ -0,0 +1,118 @@
+<template>
+  <view class="signature-container">
+    <view class="flex justify-between items-center mb-2">
+      <text class="text-sm text-gray-600">{{ title }}</text>
+      <text class="text-xs text-blue-500" @click="clear">
+        <text class="uni-icons uniui-trash mr-1"></text>
+        清除
+      </text>
+    </view>
+    
+    <canvas
+      :id="canvasId"
+      :canvas-id="canvasId"
+      class="w-full rounded-lg border border-gray-200 bg-white"
+      :style="{ height: height + 'px' }"
+      @touchstart="touchStart"
+      @touchmove="touchMove"
+      @touchend="touchEnd"
+    />
+    
+    <view v-if="hasSignature" class="mt-2 flex gap-2">
+      <button class="flex-1 py-2 bg-gray-200 text-gray-700 rounded-lg text-sm" @click="clear">
+        重新签名
+      </button>
+      <button class="flex-1 py-2 bg-blue-500 text-white rounded-lg text-sm" @click="save">
+        <text class="uni-icons uniui-checkmarkempty mr-1"></text>
+        确认签名
+      </button>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from 'vue'
+
+interface Props {
+  title?: string
+  height?: number
+  canvasId?: string
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  title: '请在下方签字',
+  height: 200,
+  canvasId: 'signature-canvas',
+})
+
+const emit = defineEmits<{
+  save: [imagePath: string]
+}>()
+
+const hasSignature = ref(false)
+let ctx: UniApp.CanvasContext | null = null
+let isDrawing = false
+let points: { x: number; y: number }[] = []
+
+onMounted(() => {
+  ctx = uni.createCanvasContext(props.canvasId)
+  if (ctx) {
+    ctx.setLineWidth(3)
+    ctx.setLineCap('round')
+    ctx.setLineJoin('round')
+    ctx.setStrokeStyle('#333')
+  }
+})
+
+function touchStart(e: any) {
+  isDrawing = true
+  const { x, y } = e.touches[0]
+  points = [{ x, y }]
+  ctx?.moveTo(x, y)
+}
+
+function touchMove(e: any) {
+  if (!isDrawing) return
+  const { x, y } = e.touches[0]
+  points.push({ x, y })
+  ctx?.lineTo(x, y)
+  ctx?.stroke()
+  ctx?.draw(true)
+  hasSignature.value = true
+}
+
+function touchEnd() {
+  isDrawing = false
+  points = []
+}
+
+function clear() {
+  ctx?.clearRect(0, 0, 1000, 1000)
+  ctx?.draw()
+  hasSignature.value = false
+}
+
+function save() {
+  if (!hasSignature.value) {
+    uni.showToast({ title: '请先签名', icon: 'none' })
+    return
+  }
+
+  uni.canvasToTempFilePath({
+    canvasId: props.canvasId,
+    success: (res) => {
+      emit('save', res.tempFilePath)
+      uni.showToast({ title: '签名已保存', icon: 'success' })
+    },
+    fail: () => {
+      uni.showToast({ title: '保存失败', icon: 'none' })
+    },
+  })
+}
+
+// 暴露方法给父组件
+defineExpose({
+  clear,
+  save,
+})
+</script>

+ 47 - 0
src/components/common/StatusTag.vue

@@ -0,0 +1,47 @@
+<template>
+  <view class="tag" :style="{ backgroundColor: bgColor, color: textColor }">
+    {{ label }}
+  </view>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue'
+import {
+  getStatusText,
+  getStatusColor,
+  getStatusBgColor,
+  getProjectStatusText
+} from '@/utils'
+
+type StatusTagType = 'task' | 'project'
+
+const props = withDefaults(
+  defineProps<{
+    status: string
+    type?: StatusTagType
+  }>(),
+  {
+    type: 'task'
+  }
+)
+
+const label = computed(() => {
+  return props.type === 'project'
+    ? getProjectStatusText(props.status)
+    : getStatusText(props.status)
+})
+
+const textColor = computed(() => getStatusColor(props.status))
+const bgColor = computed(() => getStatusBgColor(props.status))
+</script>
+
+<style scoped>
+.tag {
+  display: inline-block;
+  padding: 2px 8px;
+  border-radius: 4px;
+  font-size: 12px;
+  font-weight: 500;
+  line-height: 1.4;
+}
+</style>

+ 99 - 0
src/components/common/Toast.vue

@@ -0,0 +1,99 @@
+<template>
+  <view v-if="visible" class="toast-wrap">
+    <view class="toast-content">
+      <text v-if="icon === 'success'" class="toast-icon"></text>
+      <text v-else-if="icon === 'error'" class="toast-icon"></text>
+      <text v-else-if="icon === 'loading'" class="toast-icon loading"></text>
+      <text class="toast-text">{{ title }}</text>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { watch } from 'vue'
+
+const props = withDefaults(
+  defineProps<{
+    visible: boolean
+    title: string
+    icon?: 'success' | 'loading' | 'none' | 'error'
+    duration?: number
+  }>(),
+  {
+    icon: 'none',
+    duration: 2000
+  }
+)
+
+const emit = defineEmits<{
+  (e: 'update:visible', value: boolean): void
+  (e: 'close'): void
+}>()
+
+let timer: ReturnType<typeof setTimeout> | null = null
+
+watch(
+  () => props.visible,
+  (val) => {
+    if (timer) clearTimeout(timer)
+    if (val && props.duration > 0) {
+      timer = setTimeout(() => {
+        emit('update:visible', false)
+        emit('close')
+      }, props.duration)
+    }
+  }
+)
+</script>
+
+<style scoped>
+.toast-wrap {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 9999;
+  pointer-events: none;
+}
+
+.toast-content {
+  min-width: 120px;
+  max-width: 70%;
+  padding: 16px 24px;
+  background: rgba(0, 0, 0, 0.8);
+  border-radius: 8px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
+
+.toast-icon {
+  font-size: 24px;
+  color: #fff;
+  margin-bottom: 8px;
+}
+
+.toast-icon.loading {
+  animation: spin 1s linear infinite;
+}
+
+.toast-text {
+  font-size: 14px;
+  color: #fff;
+  text-align: center;
+}
+
+@keyframes spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+</style>

+ 42 - 0
src/components/common/TopBar.vue

@@ -0,0 +1,42 @@
+<template>
+  <view class="top-bar bg-white px-4 h-12 flex items-center justify-between sticky top-0 z-40 border-b border-gray-100">
+    <view class="flex items-center flex-1">
+      <view
+        v-if="showBack"
+        class="mr-2 p-2 -ml-2 clickable rounded-lg"
+        @click="goBack"
+      >
+        <text class="text-gray-600 text-lg">←</text>
+      </view>
+      <slot name="left" />
+      <text class="text-base font-semibold text-gray-800 truncate">{{ title }}</text>
+    </view>
+    <view class="flex items-center">
+      <slot name="right" />
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+const props = withDefaults(
+  defineProps<{
+    title: string
+    showBack?: boolean
+  }>(),
+  {
+    showBack: true
+  }
+)
+
+function goBack() {
+  if (props.showBack) {
+    uni.navigateBack({ delta: 1 })
+  }
+}
+</script>
+
+<style scoped>
+.top-bar {
+  height: 44px;
+}
+</style>

+ 12 - 0
src/components/index.ts

@@ -0,0 +1,12 @@
+export { default as AppContainer } from './common/AppContainer.vue'
+export { default as BottomNav } from './common/BottomNav.vue'
+export { default as TopBar } from './common/TopBar.vue'
+export { default as Card } from './common/Card.vue'
+export { default as TaskCard } from './task/TaskCard.vue'
+export { default as ProjectCard } from './project/ProjectCard.vue'
+export { default as FilterTabs } from './common/FilterTabs.vue'
+export { default as StatusTag } from './common/StatusTag.vue'
+export { default as EmptyState } from './common/EmptyState.vue'
+export { default as Toast } from './common/Toast.vue'
+export { default as Loading } from './common/Loading.vue'
+export { default as Modal } from './common/Modal.vue'

+ 62 - 0
src/components/project/ProjectCard.vue

@@ -0,0 +1,62 @@
+<template>
+  <view class="project-card bg-white rounded-card p-4 mb-3 clickable" @click="onClick">
+    <view class="flex justify-between items-start mb-2">
+      <view class="flex-1 min-w-0 mr-2">
+        <text class="project-name text-sm font-semibold text-gray-800 truncate">{{ project.name }}</text>
+      </view>
+      <StatusTag type="project" :status="project.status" />
+    </view>
+
+    <view class="space-y-1 mb-3">
+      <view class="text-xs text-gray-500 flex items-center">
+        <text class="mr-1">客</text>
+        <text class="truncate">{{ project.customerName }} {{ project.customerPhone }}</text>
+      </view>
+      <view class="text-xs text-gray-500 flex items-center">
+        <text class="mr-1">地</text>
+        <text class="truncate">{{ project.address }}</text>
+      </view>
+      <view class="text-xs text-gray-500 flex items-center">
+        <text class="mr-1">类</text>
+        <text>{{ getProjectTypeText(project.type) }}</text>
+      </view>
+    </view>
+
+    <view class="flex justify-between items-center pt-3 border-t border-gray-100">
+      <view class="flex gap-2">
+        <text class="text-xs text-gray-400">{{ project.servicePoints.length }} 个服务点</text>
+        <text class="text-xs text-gray-400">{{ project.contacts.length }} 位联系人</text>
+      </view>
+      <text class="text-xs text-gray-400">{{ formatDate(project.createTime, 'YYYY-MM-DD') }}</text>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import type { Project } from '@/types'
+import StatusTag from '../common/StatusTag.vue'
+import { formatDate, getProjectTypeText } from '@/utils'
+
+const props = defineProps<{
+  project: Project
+}>()
+
+const emit = defineEmits<{
+  (e: 'click', projectId: string): void
+}>()
+
+function onClick() {
+  emit('click', props.project.id)
+}
+</script>
+
+<style scoped>
+.project-card {
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+}
+
+.project-card:active {
+  transform: scale(0.98);
+  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
+}
+</style>

+ 59 - 0
src/components/task/TaskCard.vue

@@ -0,0 +1,59 @@
+
+<template>
+  <view class="card p-4 mb-3 clickable" @click="$emit('click', task.id)">
+    <view class="flex justify-between items-start mb-2.5">
+      <view class="flex-1 min-w-0 mr-3">
+        <text class="text-[15px] font-semibold text-gray-800 truncate block">{{ task.projectName || '未命名任务' }}</text>
+      </view>
+      <StatusTag :status="task.status" />
+    </view>
+    
+    <view class="space-y-1.5 mb-3">
+      <view class="flex items-center">
+        <text class="text-gray-400 text-xs mr-1.5"></text>
+        <text class="text-gray-500 text-xs truncate">{{ task.address || '暂无地址' }}</text>
+      </view>
+      <view class="flex items-center">
+        <text class="text-gray-400 text-xs mr-1.5">⏰</text>
+        <text class="text-gray-500 text-xs">{{ task.scheduleDate || '待定' }} {{ task.scheduleTime || '' }}</text>
+      </view>
+    </view>
+    
+    <view class="flex items-center justify-between pt-2.5 border-t border-gray-100">
+      <view class="flex items-center">
+        <view v-if="task.vehicleNo" class="flex items-center mr-3">
+          <text class="text-gray-400 text-xs mr-1"></text>
+          <text class="text-gray-500 text-xs">{{ task.vehicleNo }}</text>
+        </view>
+        <view v-if="task.staffName" class="flex items-center">
+          <text class="text-gray-400 text-xs mr-1"></text>
+          <text class="text-gray-500 text-xs">{{ task.staffName }}</text>
+        </view>
+      </view>
+      <text class="text-gray-300 text-lg leading-none">›</text>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import StatusTag from '../common/StatusTag.vue'
+
+interface Task {
+  id: string
+  projectName: string
+  status: string
+  address: string
+  scheduleDate: string
+  scheduleTime: string
+  vehicleNo?: string
+  staffName?: string
+}
+
+defineProps<{
+  task: Task
+}>()
+
+defineEmits<{
+  click: [taskId: string]
+}>()
+</script>

+ 89 - 0
src/components/uni-card/uni-card.vue

@@ -0,0 +1,89 @@
+<template>
+  <view class="uni-card" :class="{ 'uni-card--full': isFull, 'uni-card--shadow': isShadow }">
+    <!-- 卡片头部 -->
+    <view v-if="title || $slots.title" class="uni-card__header">
+      <slot name="title">
+        <view class="uni-card__header-content">
+          <text v-if="icon" class="uni-icons mr-2" :class="icon"></text>
+          <text class="uni-card__title">{{ title }}</text>
+        </view>
+        <text v-if="extra" class="uni-card__extra">{{ extra }}</text>
+      </slot>
+    </view>
+
+    <!-- 卡片内容 -->
+    <view class="uni-card__content" :class="{ 'uni-card__content--no-padding': noPadding }">
+      <slot />
+    </view>
+
+    <!-- 卡片底部 -->
+    <view v-if="$slots.footer" class="uni-card__footer">
+      <slot name="footer" />
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+interface Props {
+  title?: string
+  extra?: string
+  icon?: string
+  isFull?: boolean
+  isShadow?: boolean
+  noPadding?: boolean
+}
+
+withDefaults(defineProps<Props>(), {
+  isShadow: true,
+  noPadding: false
+})
+</script>
+
+<style scoped>
+.uni-card {
+  background-color: #fff;
+  border-radius: 12px;
+  overflow: hidden;
+  margin: 12px 16px;
+}
+.uni-card--full {
+  margin: 0;
+  border-radius: 0;
+}
+.uni-card--shadow {
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+}
+.uni-card__header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 14px 16px;
+  border-bottom: 1px solid #f0f0f0;
+}
+.uni-card__header-content {
+  display: flex;
+  align-items: center;
+}
+.uni-card__title {
+  font-size: 15px;
+  font-weight: 600;
+  color: #333;
+}
+.uni-card__extra {
+  font-size: 13px;
+  color: #999;
+}
+.uni-card__content {
+  padding: 16px;
+}
+.uni-card__content--no-padding {
+  padding: 0;
+}
+.uni-card__footer {
+  padding: 12px 16px;
+  border-top: 1px solid #f0f0f0;
+  display: flex;
+  justify-content: flex-end;
+  gap: 8px;
+}
+</style>

+ 204 - 0
src/components/uni-list/uni-list-item.vue

@@ -0,0 +1,204 @@
+<template>
+  <view 
+    class="uni-list-item" 
+    :class="{ 'uni-list-item--hover': hoverClass, 'uni-list-item--disabled': disabled }"
+    @click="onClick"
+  >
+    <view class="uni-list-item__container">
+      <!-- 左侧图标/插槽 -->
+      <view v-if="showArrow || $slots.header" class="uni-list-item__header">
+        <slot name="header">
+          <view v-if="thumb" class="uni-list-item__icon">
+            <image :src="thumb" class="uni-list-item__thumb" mode="aspectFill" />
+          </view>
+          <view v-else-if="icon" class="uni-list-item__icon">
+            <text class="uni-icons" :class="icon"></text>
+          </view>
+        </slot>
+      </view>
+
+      <!-- 内容区 -->
+      <view class="uni-list-item__content">
+        <slot>
+          <text v-if="title" class="uni-list-item__title">{{ title }}</text>
+          <text v-if="note" class="uni-list-item__note">{{ note }}</text>
+        </slot>
+      </view>
+
+      <!-- 右侧 -->
+      <view class="uni-list-item__extra">
+        <slot name="footer">
+          <text v-if="rightText" class="uni-list-item__right-text">{{ rightText }}</text>
+        </slot>
+        <text v-if="showArrow" class="uni-icons uniui-arrowright text-gray-400 ml-2"></text>
+      </view>
+    </view>
+
+    <!-- 分割线 -->
+    <view v-if="border" class="uni-list-item__border"></view>
+
+    <!-- 左滑操作区 -->
+    <view v-if="showSwipe" class="uni-list-item__swipe">
+      <slot name="swipe">
+        <view class="uni-list-item__swipe-actions">
+          <view 
+            v-for="(action, index) in swipeActions" 
+            :key="index"
+            class="uni-list-item__swipe-btn"
+            :style="{ backgroundColor: action.color }"
+            @click.stop="onSwipeClick(action)"
+          >
+            <text class="text-white text-sm">{{ action.text }}</text>
+          </view>
+        </view>
+      </slot>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+
+interface SwipeAction {
+  text: string
+  color: string
+  key?: string
+}
+
+interface Props {
+  title?: string
+  note?: string
+  thumb?: string
+  icon?: string
+  rightText?: string
+  showArrow?: boolean
+  border?: boolean
+  hoverClass?: boolean
+  disabled?: boolean
+  showSwipe?: boolean
+  swipeActions?: SwipeAction[]
+  clickable?: boolean
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  border: true,
+  showArrow: false,
+  hoverClass: true,
+  disabled: false,
+  showSwipe: false,
+  clickable: true,
+  swipeActions: () => []
+})
+
+const emit = defineEmits<{
+  click: []
+  swipeClick: [action: SwipeAction]
+}>()
+
+function onClick() {
+  if (props.disabled || !props.clickable) return
+  emit('click')
+}
+
+function onSwipeClick(action: SwipeAction) {
+  emit('swipeClick', action)
+}
+</script>
+
+<style scoped>
+.uni-list-item {
+  position: relative;
+  overflow: hidden;
+  background-color: #fff;
+}
+.uni-list-item__container {
+  display: flex;
+  align-items: center;
+  padding: 12px 15px;
+  position: relative;
+  background-color: #fff;
+  z-index: 1;
+  transition: transform 0.3s;
+}
+.uni-list-item--hover:active .uni-list-item__container {
+  background-color: #f5f5f5;
+}
+.uni-list-item--disabled {
+  opacity: 0.6;
+}
+.uni-list-item__header {
+  display: flex;
+  align-items: center;
+  margin-right: 10px;
+}
+.uni-list-item__thumb {
+  width: 40px;
+  height: 40px;
+  border-radius: 6px;
+}
+.uni-list-item__icon {
+  width: 40px;
+  height: 40px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: #f5f5f5;
+  border-radius: 8px;
+}
+.uni-list-item__content {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+.uni-list-item__title {
+  font-size: 15px;
+  color: #333;
+  line-height: 1.5;
+}
+.uni-list-item__note {
+  font-size: 12px;
+  color: #999;
+  margin-top: 4px;
+  line-height: 1.4;
+}
+.uni-list-item__extra {
+  display: flex;
+  align-items: center;
+  margin-left: 10px;
+}
+.uni-list-item__right-text {
+  font-size: 14px;
+  color: #999;
+}
+.uni-list-item__border {
+  position: absolute;
+  bottom: 0;
+  right: 0;
+  left: 15px;
+  height: 1px;
+  background-color: #e5e5e5;
+  transform: scaleY(0.5);
+}
+.uni-list-item__swipe {
+  position: absolute;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+  z-index: 0;
+}
+.uni-list-item__swipe-actions {
+  display: flex;
+  height: 100%;
+}
+.uni-list-item__swipe-btn {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 0 20px;
+  height: 100%;
+}
+</style>

+ 44 - 0
src/components/uni-list/uni-list.vue

@@ -0,0 +1,44 @@
+<template>
+  <view class="uni-list" :class="{ 'uni-list--border': border }">
+    <slot />
+  </view>
+</template>
+
+<script setup lang="ts">
+interface Props {
+  border?: boolean
+}
+withDefaults(defineProps<Props>(), {
+  border: true
+})
+</script>
+
+<style scoped>
+.uni-list {
+  background-color: #fff;
+  position: relative;
+  width: 100%;
+  display: flex;
+  flex-direction: column;
+}
+.uni-list--border::after {
+  position: absolute;
+  top: 0;
+  right: 0;
+  left: 0;
+  height: 1px;
+  content: '';
+  background-color: #e5e5e5;
+  transform: scaleY(0.5);
+}
+.uni-list--border::before {
+  position: absolute;
+  bottom: 0;
+  right: 0;
+  left: 0;
+  height: 1px;
+  content: '';
+  background-color: #e5e5e5;
+  transform: scaleY(0.5);
+}
+</style>

+ 107 - 0
src/components/uni-segmented-control/uni-segmented-control.vue

@@ -0,0 +1,107 @@
+<template>
+  <view class="uni-segmented-control">
+    <view 
+      v-for="(item, index) in values" 
+      :key="index"
+      class="uni-segmented-control__item"
+      :class="{ 
+        'uni-segmented-control__item--active': currentIndex === index,
+        'uni-segmented-control__item--disabled': disabled
+      }"
+      :style="itemStyle(index)"
+      @click="onClick(index)"
+    >
+      <text class="uni-segmented-control__text" :style="textStyle(index)">
+        {{ item }}
+      </text>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue'
+
+interface Props {
+  current: number
+  values: string[]
+  activeColor?: string
+  styleType?: 'button' | 'text'
+  disabled?: boolean
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  current: 0,
+  activeColor: '#4f8ef7',
+  styleType: 'button',
+  disabled: false
+})
+
+const emit = defineEmits<{
+  clickItem: [index: number]
+}>()
+
+const currentIndex = computed(() => props.current)
+
+function itemStyle(index: number) {
+  if (props.styleType === 'text') return {}
+  const isActive = currentIndex.value === index
+  return {
+    backgroundColor: isActive ? props.activeColor : 'transparent',
+    borderColor: props.activeColor
+  }
+}
+
+function textStyle(index: number) {
+  const isActive = currentIndex.value === index
+  if (props.styleType === 'text') {
+    return {
+      color: isActive ? props.activeColor : '#666',
+      borderBottom: isActive ? `2px solid ${props.activeColor}` : '2px solid transparent'
+    }
+  }
+  return {
+    color: isActive ? '#fff' : props.activeColor
+  }
+}
+
+function onClick(index: number) {
+  if (props.disabled) return
+  emit('clickItem', index)
+}
+</script>
+
+<style scoped>
+.uni-segmented-control {
+  display: flex;
+  flex-direction: row;
+  background-color: #fff;
+  padding: 8px 16px;
+}
+.uni-segmented-control__item {
+  flex: 1;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 8px 0;
+  border: 1px solid;
+  border-right-width: 0;
+  cursor: pointer;
+  transition: all 0.3s;
+}
+.uni-segmented-control__item:first-child {
+  border-top-left-radius: 6px;
+  border-bottom-left-radius: 6px;
+}
+.uni-segmented-control__item:last-child {
+  border-right-width: 1px;
+  border-top-right-radius: 6px;
+  border-bottom-right-radius: 6px;
+}
+.uni-segmented-control__item--disabled {
+  opacity: 0.5;
+}
+.uni-segmented-control__text {
+  font-size: 14px;
+  transition: all 0.3s;
+}
+</style>

+ 144 - 0
src/components/uni-swipe-action/uni-swipe-action.vue

@@ -0,0 +1,144 @@
+<template>
+  <view class="uni-swipe">
+    <view 
+      class="uni-swipe__content"
+      :style="contentStyle"
+      @touchstart="onTouchStart"
+      @touchmove="onTouchMove"
+      @touchend="onTouchEnd"
+    >
+      <slot />
+    </view>
+    <view class="uni-swipe__actions" :style="actionsStyle">
+      <slot name="actions">
+        <view 
+          v-for="(action, index) in actions" 
+          :key="index"
+          class="uni-swipe__action"
+          :style="{ backgroundColor: action.color }"
+          @click="onActionClick(action)"
+        >
+          <text class="uni-swipe__action-text">{{ action.text }}</text>
+        </view>
+      </slot>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed } from 'vue'
+
+interface SwipeAction {
+  text: string
+  color: string
+  key?: string
+}
+
+interface Props {
+  actions?: SwipeAction[]
+  threshold?: number
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  actions: () => [],
+  threshold: 60
+})
+
+const emit = defineEmits<{
+  click: [action: SwipeAction]
+}>()
+
+const offset = ref(0)
+const startX = ref(0)
+const startY = ref(0)
+const isOpen = ref(false)
+const maxOffset = ref(0)
+
+const contentStyle = computed(() => ({
+  transform: `translateX(${-offset.value}px)`,
+  transition: isDragging.value ? 'none' : 'transform 0.3s'
+}))
+
+const actionsStyle = computed(() => ({
+  width: `${offset.value}px`
+}))
+
+const isDragging = ref(false)
+
+function onTouchStart(e: TouchEvent) {
+  const touch = e.touches[0]
+  startX.value = touch.clientX
+  startY.value = touch.clientY
+  isDragging.value = true
+  maxOffset.value = props.actions.length * 80
+}
+
+function onTouchMove(e: TouchEvent) {
+  if (!isDragging.value) return
+  const touch = e.touches[0]
+  const deltaX = startX.value - touch.clientX
+  const deltaY = startY.value - touch.clientY
+
+  if (Math.abs(deltaX) > Math.abs(deltaY)) {
+    e.preventDefault()
+    offset.value = Math.min(Math.max(deltaX, 0), maxOffset.value)
+  }
+}
+
+function onTouchEnd() {
+  isDragging.value = false
+  if (offset.value > props.threshold) {
+    offset.value = maxOffset.value
+    isOpen.value = true
+  } else {
+    offset.value = 0
+    isOpen.value = false
+  }
+}
+
+function onActionClick(action: SwipeAction) {
+  offset.value = 0
+  isOpen.value = false
+  emit('click', action)
+}
+
+function close() {
+  offset.value = 0
+  isOpen.value = false
+}
+
+defineExpose({ close })
+</script>
+
+<style scoped>
+.uni-swipe {
+  position: relative;
+  overflow: hidden;
+}
+.uni-swipe__content {
+  position: relative;
+  z-index: 1;
+  background-color: #fff;
+}
+.uni-swipe__actions {
+  position: absolute;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+  overflow: hidden;
+}
+.uni-swipe__action {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 80px;
+  height: 100%;
+}
+.uni-swipe__action-text {
+  color: #fff;
+  font-size: 14px;
+}
+</style>

+ 8 - 0
src/env.d.ts

@@ -0,0 +1,8 @@
+/// <reference types="vite/client" />
+
+declare module '*.vue' {
+  import { DefineComponent } from 'vue'
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
+  const component: DefineComponent<{}, {}, any>
+  export default component
+}

+ 17 - 0
src/main.ts

@@ -0,0 +1,17 @@
+import { createSSRApp } from 'vue'
+import { createPinia } from 'pinia'
+import App from './App.vue'
+import './styles/tailwind.css'
+import './styles/global.scss'
+
+export function createApp() {
+  const app = createSSRApp(App)
+  const pinia = createPinia()
+
+  app.use(pinia)
+
+  return {
+    app,
+    pinia
+  }
+}

+ 72 - 0
src/manifest.json

@@ -0,0 +1,72 @@
+{
+    "name" : "清道夫App",
+    "appid" : "__UNI__QINGDAOFU",
+    "description" : "清道夫系统 - 环境服务管理平台",
+    "versionName" : "1.0.0",
+    "versionCode" : "100",
+    "transformPx" : false,
+    /* 5+App特有相关 */
+    "app-plus" : {
+        "usingComponents" : true,
+        "nvueStyleCompiler" : "uni-app",
+        "compilerVersion" : 3,
+        "splashscreen" : {
+            "alwaysShowBeforeRender" : true,
+            "waiting" : true,
+            "autoclose" : true,
+            "delay" : 0
+        },
+        /* 模块配置 */
+        "modules" : {},
+        /* 应用发布信息 */
+        "distribute" : {
+            /* android打包配置 */
+            "android" : {
+                "permissions" : [
+                    "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
+                    "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CAMERA\"/>",
+                    "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
+                    "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
+                ]
+            },
+            /* ios打包配置 */
+            "ios" : {},
+            /* SDK配置 */
+            "sdkConfigs" : {}
+        }
+    },
+    /* 快应用特有相关 */
+    "quickapp" : {},
+    /* 小程序特有相关 */
+    "mp-weixin" : {
+        "appid" : "",
+        "setting" : {
+            "urlCheck" : false
+        },
+        "usingComponents" : true
+    },
+    "mp-alipay" : {
+        "usingComponents" : true
+    },
+    "mp-baidu" : {
+        "usingComponents" : true
+    },
+    "mp-toutiao" : {
+        "usingComponents" : true
+    },
+    "uniStatistics": {  
+        "enable": false
+    },
+    "vueVersion" : "3"
+}

+ 346 - 0
src/mock/index.ts

@@ -0,0 +1,346 @@
+import type {
+  User,
+  Project,
+  Task,
+  Schedule,
+  Vehicle,
+  Personnel,
+  Notification,
+  Knowledge,
+  FAQ
+} from '@/types'
+
+export const mockUsers: User[] = [
+  {
+    id: 'u1',
+    name: '张销售',
+    phone: '13800138001',
+    avatar: '',
+    role: 'sales',
+    department: '销售部',
+    status: 'active'
+  },
+  {
+    id: 'u2',
+    name: '李调度',
+    phone: '13800138002',
+    avatar: '',
+    role: 'dispatch',
+    department: '调度中心',
+    status: 'active'
+  },
+  {
+    id: 'u3',
+    name: '张师傅',
+    phone: '13800138003',
+    avatar: '',
+    role: 'construction',
+    department: '施工部',
+    team: '一班组',
+    status: 'active'
+  }
+]
+
+export const mockProjects: Project[] = [
+  {
+    id: 'p1',
+    name: '绿城小区年度清洁服务',
+    type: '包年',
+    status: 'processing',
+    customerName: '王经理',
+    customerPhone: '13900139001',
+    address: '杭州市西湖区绿城小区1-10栋',
+    contacts: [
+      { name: '王经理', phone: '13900139001', role: '物业经理' },
+      { name: '李主任', phone: '13900139002', role: '工程主任' }
+    ],
+    servicePoints: [
+      { id: 'sp1', name: '1栋地下车库', location: '绿城小区1栋', serviceType: '开荒保洁' },
+      { id: 'sp2', name: '中心花园', location: '绿城小区中心', serviceType: '日常保洁' }
+    ],
+    createTime: '2026-01-15T09:30:00',
+    updateTime: '2026-06-20T14:20:00'
+  },
+  {
+    id: 'p2',
+    name: '阿里巴巴园区单次深度清洁',
+    type: '单次',
+    status: 'confirmed',
+    customerName: '赵主管',
+    customerPhone: '13900139003',
+    address: '杭州市余杭区文一西路969号',
+    contacts: [
+      { name: '赵主管', phone: '13900139003', role: '行政主管' }
+    ],
+    servicePoints: [
+      { id: 'sp3', name: 'A区办公楼', location: '园区A区', serviceType: '深度清洁' }
+    ],
+    createTime: '2026-06-18T10:00:00',
+    updateTime: '2026-06-21T09:15:00'
+  },
+  {
+    id: 'p3',
+    name: '未来科技城季度保洁',
+    type: '季度',
+    status: 'pending',
+    customerName: '陈总',
+    customerPhone: '13900139005',
+    address: '杭州市余杭区仓前街道',
+    contacts: [
+      { name: '陈总', phone: '13900139005', role: '总经理' },
+      { name: '小刘', phone: '13900139006', role: '对接人' }
+    ],
+    servicePoints: [
+      { id: 'sp4', name: '科技展厅', location: '未来科技城A座', serviceType: '展厅保洁' }
+    ],
+    createTime: '2026-06-20T16:00:00',
+    updateTime: '2026-06-20T16:00:00'
+  },
+  {
+    id: 'p4',
+    name: '滨江月付写字楼保洁',
+    type: '月付',
+    status: 'completed',
+    customerName: '孙经理',
+    customerPhone: '13900139007',
+    address: '杭州市滨江区江南大道',
+    contacts: [
+      { name: '孙经理', phone: '13900139007', role: '楼宇经理' }
+    ],
+    servicePoints: [
+      { id: 'sp5', name: '1-5层办公区', location: '滨江写字楼', serviceType: '日常保洁' }
+    ],
+    createTime: '2026-03-01T08:00:00',
+    updateTime: '2026-05-31T18:00:00'
+  },
+  {
+    id: 'p5',
+    name: '西湖景区包年环卫',
+    type: '包年',
+    status: 'processing',
+    customerName: '周主任',
+    customerPhone: '13900139008',
+    address: '杭州市西湖区龙井路',
+    contacts: [
+      { name: '周主任', phone: '13900139008', role: '景区主任' }
+    ],
+    servicePoints: [
+      { id: 'sp6', name: '断桥区域', location: '西湖断桥', serviceType: '环卫清洁' },
+      { id: 'sp7', name: '苏堤区域', location: '西湖苏堤', serviceType: '环卫清洁' }
+    ],
+    createTime: '2026-02-10T10:30:00',
+    updateTime: '2026-06-18T11:00:00'
+  }
+]
+
+export const mockTasks: Task[] = [
+  {
+    id: 't1',
+    projectId: 'p1',
+    projectName: '绿城小区年度清洁服务',
+    type: 'regular',
+    status: 'constructing',
+    urgent: 'normal',
+    assignee: '张师傅',
+    vehicle: '浙A·12345',
+    team: '一班组',
+    scheduledDate: '2026-06-22',
+    createTime: '2026-06-20T09:00:00',
+    timeline: [
+      { time: '2026-06-20T09:00:00', action: '任务创建', operator: '张销售', role: 'sales' },
+      { time: '2026-06-20T10:30:00', action: '任务确认', operator: '李调度', role: 'dispatch' },
+      { time: '2026-06-21T16:00:00', action: '排班完成', operator: '李调度', role: 'dispatch' },
+      { time: '2026-06-22T08:00:00', action: '出车出发', operator: '张师傅', role: 'construction' },
+      { time: '2026-06-22T08:40:00', action: '到达现场', operator: '张师傅', role: 'construction' },
+      { time: '2026-06-22T09:00:00', action: '开始施工', operator: '张师傅', role: 'construction' }
+    ]
+  },
+  {
+    id: 't2',
+    projectId: 'p2',
+    projectName: '阿里巴巴园区单次深度清洁',
+    type: 'emergency',
+    status: 'pending',
+    urgent: 'high',
+    createTime: '2026-06-22T07:30:00',
+    timeline: [
+      { time: '2026-06-22T07:30:00', action: '任务创建', operator: '张销售', role: 'sales' }
+    ]
+  },
+  {
+    id: 't3',
+    projectId: 'p5',
+    projectName: '西湖景区包年环卫',
+    type: 'regular',
+    status: 'scheduled',
+    urgent: 'normal',
+    assignee: '王师傅',
+    vehicle: '浙A·67890',
+    team: '二班组',
+    scheduledDate: '2026-06-23',
+    createTime: '2026-06-19T14:00:00',
+    timeline: [
+      { time: '2026-06-19T14:00:00', action: '任务创建', operator: '张销售', role: 'sales' },
+      { time: '2026-06-19T15:00:00', action: '任务确认', operator: '李调度', role: 'dispatch' },
+      { time: '2026-06-21T09:00:00', action: '排班完成', operator: '李调度', role: 'dispatch' }
+    ]
+  },
+  {
+    id: 't4',
+    projectId: 'p1',
+    projectName: '绿城小区年度清洁服务',
+    type: 'regular',
+    status: 'completed',
+    urgent: 'normal',
+    assignee: '张师傅',
+    vehicle: '浙A·12345',
+    team: '一班组',
+    scheduledDate: '2026-06-15',
+    createTime: '2026-06-10T10:00:00',
+    timeline: [
+      { time: '2026-06-10T10:00:00', action: '任务创建', operator: '张销售', role: 'sales' },
+      { time: '2026-06-12T16:00:00', action: '施工完成', operator: '张师傅', role: 'construction' },
+      { time: '2026-06-15T10:00:00', action: '验收通过', operator: '王经理', role: 'sales' }
+    ]
+  },
+  {
+    id: 't5',
+    projectId: 'p3',
+    projectName: '未来科技城季度保洁',
+    type: 'regular',
+    status: 'pending',
+    urgent: 'normal',
+    createTime: '2026-06-20T16:30:00',
+    timeline: [
+      { time: '2026-06-20T16:30:00', action: '任务创建', operator: '张销售', role: 'sales' }
+    ]
+  }
+]
+
+export const mockSchedules: Schedule[] = [
+  { id: 's1', date: '2026-06-22', team: '一班组', taskId: 't1', taskName: '绿城小区年度清洁服务', status: 'scheduled' },
+  { id: 's2', date: '2026-06-23', team: '二班组', taskId: 't3', taskName: '西湖景区包年环卫', status: 'scheduled' },
+  { id: 's3', date: '2026-06-22', team: '三班组', taskId: 't2', taskName: '阿里巴巴园区单次深度清洁', status: 'scheduled' }
+]
+
+export const mockVehicles: Vehicle[] = [
+  { id: 'v1', plateNumber: '浙A·12345', type: '清洁车', status: 'in_use', driver: '张师傅' },
+  { id: 'v2', plateNumber: '浙A·67890', type: '垃圾清运车', status: 'in_use', driver: '王师傅' },
+  { id: 'v3', plateNumber: '浙A·11111', type: '高压清洗车', status: 'available', driver: '李师傅' },
+  { id: 'v4', plateNumber: '浙A·22222', type: '洒水车', status: 'maintenance', driver: '' },
+  { id: 'v5', plateNumber: '浙A·33333', type: '清洁车', status: 'available', driver: '赵师傅' }
+]
+
+export const mockPersonnel: Personnel[] = [
+  { id: 'pe1', name: '张师傅', phone: '13800138003', team: '一班组', status: 'working' },
+  { id: 'pe2', name: '王师傅', phone: '13800138004', team: '二班组', status: 'working' },
+  { id: 'pe3', name: '李师傅', phone: '13800138005', team: '三班组', status: 'idle' },
+  { id: 'pe4', name: '赵师傅', phone: '13800138006', team: '三班组', status: 'idle' },
+  { id: 'pe5', name: '刘师傅', phone: '13800138007', team: '应急组', status: 'standby' },
+  { id: 'pe6', name: '陈师傅', phone: '13800138008', team: '应急组', status: 'standby' },
+  { id: 'pe7', name: '杨师傅', phone: '13800138009', team: '一班组', status: 'rest' },
+  { id: 'pe8', name: '黄师傅', phone: '13800138010', team: '二班组', status: 'rest' }
+]
+
+export const mockNotifications: Notification[] = [
+  {
+    id: 'n1',
+    title: '新任务提醒',
+    content: '您有一个新的待确认任务:阿里巴巴园区单次深度清洁',
+    type: 'task',
+    read: false,
+    createTime: '2026-06-22T07:35:00'
+  },
+  {
+    id: 'n2',
+    title: '系统维护通知',
+    content: '系统将于今晚22:00-23:00进行例行维护,请提前保存工作数据。',
+    type: 'system',
+    read: false,
+    createTime: '2026-06-21T18:00:00'
+  },
+  {
+    id: 'n3',
+    title: '任务完成审核',
+    content: '绿城小区年度清洁服务任务已完成,请及时验收。',
+    type: 'audit',
+    read: true,
+    createTime: '2026-06-15T11:00:00'
+  },
+  {
+    id: 'n4',
+    title: '应急任务',
+    content: '西湖景区突发油污清理,请应急组尽快响应。',
+    type: 'task',
+    read: false,
+    createTime: '2026-06-22T06:00:00'
+  },
+  {
+    id: 'n5',
+    title: '欢迎加入清道夫系统',
+    content: '欢迎使用清道夫系统,如有问题请查看帮助中心。',
+    type: 'system',
+    read: true,
+    createTime: '2026-06-01T09:00:00'
+  }
+]
+
+export const mockKnowledges: Knowledge[] = [
+  {
+    id: 'k1',
+    title: '高空作业安全规范',
+    summary: '施工人员进行高空清洁作业时必须遵守的安全操作规范。',
+    content: '1. 必须佩戴安全帽、安全带...',
+    category: '安全规范',
+    createTime: '2026-05-10T10:00:00'
+  },
+  {
+    id: 'k2',
+    title: '清洁剂使用指南',
+    summary: '不同场景下清洁剂的选择与配比说明。',
+    content: '1. 中性清洁剂适用于大理石地面...',
+    category: '操作指南',
+    createTime: '2026-05-15T14:00:00'
+  },
+  {
+    id: 'k3',
+    title: '设备日常保养手册',
+    summary: '清洁车辆与设备的日常检查、保养要点。',
+    content: '1. 每日出车前检查油量、水位...',
+    category: '设备维护',
+    createTime: '2026-05-20T09:00:00'
+  }
+]
+
+export const mockFAQs: FAQ[] = [
+  {
+    id: 'f1',
+    question: '如何新增一个项目?',
+    answer: '进入销售端首页,点击"项目库",再点击右上角"新增项目"按钮,填写项目信息、服务点和联系人后保存。',
+    category: '销售端'
+  },
+  {
+    id: 'f2',
+    question: '任务状态有哪些?',
+    answer: '任务状态包括:待确认、待排班、待出车、施工中、待验收、已完成、已取消、已驳回。',
+    category: '任务管理'
+  },
+  {
+    id: 'f3',
+    question: '如何进行排班?',
+    answer: '调度端进入"排班管理",选择日期和班组,点击"添加排班"并关联任务。',
+    category: '调度端'
+  },
+  {
+    id: 'f4',
+    question: '施工人员如何上报任务进度?',
+    answer: '施工端进入"我的任务",选择任务后依次点击"出发"、"到达"、"开始施工"、"完成",并可上传现场照片。',
+    category: '施工端'
+  },
+  {
+    id: 'f5',
+    question: '忘记密码怎么办?',
+    answer: '请联系管理员重置密码,或在个人中心"修改密码"中按提示操作。',
+    category: '账号安全'
+  }
+]

+ 414 - 0
src/pages.json

@@ -0,0 +1,414 @@
+{
+  "easycom": {
+    "autoscan": true,
+    "custom": {
+      "^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue"
+    }
+  },
+  "pages": [
+    {
+      "path": "pages/index/index",
+      "style": {
+        "navigationBarTitleText": "清道夫系统",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/login/login",
+      "style": {
+        "navigationBarTitleText": "登录",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/sales/home",
+      "style": {
+        "navigationBarTitleText": "销售端首页",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/dispatch/home",
+      "style": {
+        "navigationBarTitleText": "调度端首页",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/construction/home",
+      "style": {
+        "navigationBarTitleText": "施工端首页",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/common/projectList",
+      "style": {
+        "navigationBarTitleText": "项目库",
+        "navigationStyle": "custom",
+        "enablePullDownRefresh": true
+      }
+    },
+    {
+      "path": "pages/common/projectDetail",
+      "style": {
+        "navigationBarTitleText": "项目详情",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/common/addProject",
+      "style": {
+        "navigationBarTitleText": "新增项目",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/common/publishTask",
+      "style": {
+        "navigationBarTitleText": "发布任务",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/common/taskList",
+      "style": {
+        "navigationBarTitleText": "任务列表",
+        "navigationStyle": "custom",
+        "enablePullDownRefresh": true
+      }
+    },
+    {
+      "path": "pages/common/taskDetail",
+      "style": {
+        "navigationBarTitleText": "任务详情",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/common/profile",
+      "style": {
+        "navigationBarTitleText": "个人中心",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/common/changePassword",
+      "style": {
+        "navigationBarTitleText": "修改密码",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/common/noticeList",
+      "style": {
+        "navigationBarTitleText": "通知公告",
+        "navigationStyle": "custom",
+        "enablePullDownRefresh": true
+      }
+    },
+    {
+      "path": "pages/common/noticeDetail",
+      "style": {
+        "navigationBarTitleText": "通知详情",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/common/messageList",
+      "style": {
+        "navigationBarTitleText": "消息列表",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/common/messageDetail",
+      "style": {
+        "navigationBarTitleText": "消息详情",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/common/helpCenter",
+      "style": {
+        "navigationBarTitleText": "帮助中心",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/common/knowledgeList",
+      "style": {
+        "navigationBarTitleText": "知识库",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/common/knowledgeDetail",
+      "style": {
+        "navigationBarTitleText": "知识详情",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/sales/basicInfo",
+      "style": {
+        "navigationBarTitleText": "基本信息",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/sales/changePassword",
+      "style": {
+        "navigationBarTitleText": "修改密码",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/sales/helpCenter",
+      "style": {
+        "navigationBarTitleText": "帮助中心",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/sales/notification",
+      "style": {
+        "navigationBarTitleText": "通知",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/sales/taskReminder",
+      "style": {
+        "navigationBarTitleText": "任务提醒",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/dispatch/schedule",
+      "style": {
+        "navigationBarTitleText": "排班管理",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/dispatch/taskList",
+      "style": {
+        "navigationBarTitleText": "任务列表",
+        "navigationStyle": "custom",
+        "enablePullDownRefresh": true
+      }
+    },
+    {
+      "path": "pages/dispatch/taskDetail",
+      "style": {
+        "navigationBarTitleText": "任务详情",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/dispatch/publishTask",
+      "style": {
+        "navigationBarTitleText": "发布任务",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/dispatch/vehicleList",
+      "style": {
+        "navigationBarTitleText": "车辆列表",
+        "navigationStyle": "custom",
+        "enablePullDownRefresh": true
+      }
+    },
+    {
+      "path": "pages/dispatch/availableVehicles",
+      "style": {
+        "navigationBarTitleText": "可用车辆",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/dispatch/personnel",
+      "style": {
+        "navigationBarTitleText": "人员管理",
+        "navigationStyle": "custom",
+        "enablePullDownRefresh": true
+      }
+    },
+    {
+      "path": "pages/dispatch/onDutyStaff",
+      "style": {
+        "navigationBarTitleText": "当班人员",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/dispatch/emergencyTeam",
+      "style": {
+        "navigationBarTitleText": "应急班组",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/dispatch/emergencyStandby",
+      "style": {
+        "navigationBarTitleText": "应急待命",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/dispatch/visualization",
+      "style": {
+        "navigationBarTitleText": "数据可视化",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/dispatch/noticeList",
+      "style": {
+        "navigationBarTitleText": "通知列表",
+        "navigationStyle": "custom",
+        "enablePullDownRefresh": true
+      }
+    },
+    {
+      "path": "pages/dispatch/noticeDetail",
+      "style": {
+        "navigationBarTitleText": "通知详情",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/dispatch/notification",
+      "style": {
+        "navigationBarTitleText": "通知消息",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/dispatch/basicInfo",
+      "style": {
+        "navigationBarTitleText": "基本信息",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/dispatch/changePassword",
+      "style": {
+        "navigationBarTitleText": "修改密码",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/dispatch/helpCenter",
+      "style": {
+        "navigationBarTitleText": "帮助中心",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/dispatch/myPage",
+      "style": {
+        "navigationBarTitleText": "我的",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/construction/taskList",
+      "style": {
+        "navigationBarTitleText": "任务列表",
+        "navigationStyle": "custom",
+        "enablePullDownRefresh": true
+      }
+    },
+    {
+      "path": "pages/construction/taskDetail",
+      "style": {
+        "navigationBarTitleText": "任务详情",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/construction/dailyTask",
+      "style": {
+        "navigationBarTitleText": "每日任务",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/construction/historyTask",
+      "style": {
+        "navigationBarTitleText": "历史任务",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/construction/tomorrowTask",
+      "style": {
+        "navigationBarTitleText": "明日任务",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/construction/noticeList",
+      "style": {
+        "navigationBarTitleText": "通知列表",
+        "navigationStyle": "custom",
+        "enablePullDownRefresh": true
+      }
+    },
+    {
+      "path": "pages/construction/noticeDetail",
+      "style": {
+        "navigationBarTitleText": "通知详情",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/construction/notification",
+      "style": {
+        "navigationBarTitleText": "通知消息",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/construction/basicInfo",
+      "style": {
+        "navigationBarTitleText": "基本信息",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/construction/changePassword",
+      "style": {
+        "navigationBarTitleText": "修改密码",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/construction/helpCenter",
+      "style": {
+        "navigationBarTitleText": "帮助中心",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/construction/myPage",
+      "style": {
+        "navigationBarTitleText": "我的",
+        "navigationStyle": "custom"
+      }
+    }
+  ],
+  "globalStyle": {
+    "navigationBarTextStyle": "black",
+    "navigationBarTitleText": "清道夫系统",
+    "navigationBarBackgroundColor": "#F8F8F8",
+    "backgroundColor": "#f5f5f5"
+  },
+  "tabBar": {
+    "list": []
+  },
+  "condition": {}
+}

+ 237 - 0
src/pages/common/addProject.vue

@@ -0,0 +1,237 @@
+<template>
+  <view class="app-container pb-24">
+    <TopBar title="新增项目" show-back />
+    
+    <view class="px-4 py-4 space-y-4">
+      <!-- 基本信息 -->
+      <uni-card title="基本信息" :is-shadow="true">
+        <view class="space-y-4">
+          <view>
+            <text class="form-label">项目名称 <text class="text-red-500">*</text></text>
+            <input 
+              class="form-input"
+              placeholder="请输入项目名称"
+              v-model="form.name"
+              @blur="validateField('name')"
+            />
+            <text v-if="errors.name" class="text-red-500 text-xs mt-1">{{ errors.name }}</text>
+          </view>
+          <view>
+            <text class="form-label">项目类型 <text class="text-red-500">*</text></text>
+            <picker mode="selector" :range="types" :value="typeIndex" @change="onTypeChange">
+              <view class="form-input flex items-center" :class="{ 'text-gray-400': !form.type }">
+                <text class="uni-icons uniui-arrowdown text-gray-400 mr-2"></text>
+                {{ form.type || '请选择项目类型' }}
+              </view>
+            </picker>
+            <text v-if="errors.type" class="text-red-500 text-xs mt-1">{{ errors.type }}</text>
+          </view>
+          <view>
+            <text class="form-label">项目地址 <text class="text-red-500">*</text></text>
+            <input 
+              class="form-input"
+              placeholder="请输入项目地址"
+              v-model="form.address"
+              @blur="validateField('address')"
+            />
+            <text v-if="errors.address" class="text-red-500 text-xs mt-1">{{ errors.address }}</text>
+          </view>
+        </view>
+      </uni-card>
+
+      <!-- 客户信息 -->
+      <uni-card title="客户信息" :is-shadow="true">
+        <view class="space-y-4">
+          <view>
+            <text class="form-label">客户名称 <text class="text-red-500">*</text></text>
+            <input 
+              class="form-input"
+              placeholder="请输入客户名称"
+              v-model="form.customerName"
+              @blur="validateField('customerName')"
+            />
+            <text v-if="errors.customerName" class="text-red-500 text-xs mt-1">{{ errors.customerName }}</text>
+          </view>
+          <view>
+            <text class="form-label">联系电话 <text class="text-red-500">*</text></text>
+            <input 
+              class="form-input"
+              placeholder="请输入联系电话"
+              v-model="form.customerPhone"
+              type="number"
+              maxlength="11"
+              @blur="validateField('customerPhone')"
+            />
+            <text v-if="errors.customerPhone" class="text-red-500 text-xs mt-1">{{ errors.customerPhone }}</text>
+          </view>
+        </view>
+      </uni-card>
+
+      <!-- 服务点信息 -->
+      <uni-card title="服务点信息" :is-shadow="true">
+        <template #footer>
+          <button class="flex items-center justify-center text-blue-500" @click="addServicePoint">
+            <text class="uni-icons uniui-plusempty mr-1"></text>
+            添加服务点
+          </button>
+        </template>
+        <view class="space-y-4">
+          <view 
+            v-for="(point, index) in form.servicePoints" 
+            :key="index"
+            class="border border-gray-200 rounded-xl p-4"
+          >
+            <view class="flex justify-between items-center mb-3">
+              <text class="text-sm font-semibold text-gray-700">服务点 {{ index + 1 }}</text>
+              <text class="text-red-500 text-sm flex items-center" @click="removeServicePoint(index)">
+                <text class="uni-icons uniui-trash text-sm mr-1"></text>
+                删除
+              </text>
+            </view>
+            <view class="space-y-3">
+              <input 
+                class="form-input"
+                placeholder="服务点地址"
+                v-model="point.address"
+              />
+              <input 
+                class="form-input"
+                placeholder="联系人"
+                v-model="point.contactName"
+              />
+              <input 
+                class="form-input"
+                placeholder="联系电话"
+                v-model="point.contactPhone"
+                type="number"
+                maxlength="11"
+              />
+            </view>
+          </view>
+        </view>
+      </uni-card>
+
+      <!-- 提交按钮 -->
+      <view class="px-4 pt-2">
+        <button 
+          class="w-full py-4 btn-primary rounded-2xl text-base font-semibold flex items-center justify-center"
+          @click="submitForm"
+          :disabled="submitting"
+        >
+          <text v-if="submitting" class="uni-icons uniui-spinner-cycle mr-2 animate-spin"></text>
+          <text class="uni-icons uniui-checkmarkempty mr-2" v-else></text>
+          {{ submitting ? '提交中...' : '提交项目' }}
+        </button>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive } from 'vue'
+import { useProjectStore } from '../../stores/project'
+import TopBar from '../../components/common/TopBar.vue'
+import UniCard from '../../components/uni-card/uni-card.vue'
+import { createProject } from '../../api/project'
+
+const projectStore = useProjectStore()
+
+const types = ['包年', '单次', '季度', '月付']
+const typeIndex = ref(0)
+
+const form = ref({
+  name: '',
+  type: '',
+  address: '',
+  customerName: '',
+  customerPhone: '',
+  servicePoints: [] as any[],
+})
+
+const errors = reactive<Record<string, string>>({
+  name: '',
+  type: '',
+  address: '',
+  customerName: '',
+  customerPhone: '',
+})
+
+const submitting = ref(false)
+
+function onTypeChange(e: any) {
+  typeIndex.value = e.detail.value
+  form.value.type = types[typeIndex.value]
+  errors.type = ''
+}
+
+function validateField(field: string) {
+  const value = form.value[field as keyof typeof form.value] as string
+  errors[field] = ''
+
+  if (!value || !value.trim()) {
+    const fieldNames: Record<string, string> = {
+      name: '项目名称',
+      address: '项目地址',
+      customerName: '客户名称',
+      customerPhone: '联系电话',
+    }
+    errors[field] = `请输入${fieldNames[field] || field}`
+    return false
+  }
+
+  if (field === 'customerPhone') {
+    const phoneRegex = /^1[3-9]\d{9}$/
+    if (!phoneRegex.test(value)) {
+      errors[field] = '请输入正确的手机号'
+      return false
+    }
+  }
+
+  return true
+}
+
+function validateForm() {
+  let isValid = true
+  const requiredFields = ['name', 'type', 'address', 'customerName', 'customerPhone']
+  
+  for (const field of requiredFields) {
+    if (!validateField(field)) {
+      isValid = false
+    }
+  }
+  
+  return isValid
+}
+
+function addServicePoint() {
+  form.value.servicePoints.push({
+    address: '',
+    contactName: '',
+    contactPhone: '',
+  })
+}
+
+function removeServicePoint(index: number) {
+  form.value.servicePoints.splice(index, 1)
+}
+
+async function submitForm() {
+  if (!validateForm()) {
+    uni.showToast({ title: '请完善表单信息', icon: 'none' })
+    return
+  }
+  
+  submitting.value = true
+  try {
+    await createProject(form.value)
+    uni.showToast({ title: '创建成功', icon: 'success' })
+    setTimeout(() => {
+      uni.navigateBack()
+    }, 1500)
+  } catch (error) {
+    uni.showToast({ title: '创建失败', icon: 'none' })
+  } finally {
+    submitting.value = false
+  }
+}
+</script>

+ 114 - 0
src/pages/common/changePassword.vue

@@ -0,0 +1,114 @@
+
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="修改密码" show-back />
+
+    <view class="px-4 py-4">
+      <view class="card p-4">
+        <view class="form-group">
+          <text class="form-label">
+            <text class="text-red-500">*</text>当前密码
+          </text>
+          <input class="form-input" type="password" v-model="currentPassword" placeholder="请输入当前密码" />
+        </view>
+        <view class="form-group">
+          <text class="form-label">
+            <text class="text-red-500">*</text>新密码
+          </text>
+          <input class="form-input" type="password" v-model="newPassword" placeholder="请输入新密码(6-20位)" />
+        </view>
+        <view class="form-group">
+          <text class="form-label">
+            <text class="text-red-500">*</text>确认新密码
+          </text>
+          <input class="form-input" type="password" v-model="confirmNewPassword" placeholder="请再次输入新密码" />
+        </view>
+
+        <view class="mt-4">
+          <view class="bg-blue-50 rounded-lg p-3 mb-4">
+            <text class="text-blue-600 text-sm block">ℹ️ 密码要求:</text>
+            <view class="text-gray-500 text-xs mt-2 space-y-1">
+              <text class="block">• 长度6-20位</text>
+              <text class="block">• 必须包含字母和数字</text>
+              <text class="block">• 不能与当前密码相同</text>
+            </view>
+          </view>
+
+          <button class="w-full py-3 bg-blue-500 text-white rounded-xl font-medium" @click="submitChangePassword">
+            确认修改
+          </button>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+import TopBar from '@/components/common/TopBar.vue'
+import { showToast } from '@/utils'
+
+const currentPassword = ref('')
+const newPassword = ref('')
+const confirmNewPassword = ref('')
+
+function validateForm(): boolean {
+  if (!currentPassword.value.trim()) {
+    showToast('请输入当前密码')
+    return false
+  }
+  if (!newPassword.value.trim()) {
+    showToast('请输入新密码')
+    return false
+  }
+  if (newPassword.value.length < 6 || newPassword.value.length > 20) {
+    showToast('密码长度需在6-20位之间')
+    return false
+  }
+  if (!/[a-zA-Z]/.test(newPassword.value) || !/[0-9]/.test(newPassword.value)) {
+    showToast('密码必须包含字母和数字')
+    return false
+  }
+  if (newPassword.value === currentPassword.value) {
+    showToast('新密码不能与当前密码相同')
+    return false
+  }
+  if (newPassword.value !== confirmNewPassword.value) {
+    showToast('两次输入的新密码不一致')
+    return false
+  }
+  return true
+}
+
+function submitChangePassword() {
+  if (!validateForm()) return
+  showToast({ title: '密码修改成功', icon: 'success' })
+  setTimeout(() => {
+    uni.navigateBack()
+  }, 1500)
+}
+</script>
+
+<style scoped>
+.form-group {
+  margin-bottom: 16px;
+}
+
+.form-label {
+  display: block;
+  font-size: 14px;
+  color: #666;
+  margin-bottom: 8px;
+}
+
+.form-input {
+  width: 100%;
+  padding: 12px 16px;
+  border: 1px solid #e5e7eb;
+  border-radius: 8px;
+  font-size: 14px;
+  color: #333;
+  background: #fff;
+  box-sizing: border-box;
+}
+</style>

+ 80 - 0
src/pages/common/helpCenter.vue

@@ -0,0 +1,80 @@
+
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="帮助中心" show-back />
+
+    <view class="px-4 py-4">
+      <!-- 常见问题 -->
+      <view class="card p-4 mb-4">
+        <view class="flex items-center mb-4">
+          <text class="text-blue-500 mr-2"></text>
+          <text class="text-base font-semibold text-gray-800">常见问题</text>
+        </view>
+
+        <view class="space-y-3">
+          <view
+            v-for="(faq, index) in faqs"
+            :key="faq.id"
+            class="border-b border-gray-100 pb-3"
+            :class="index === faqs.length - 1 ? 'border-b-0 pb-0' : ''"
+          >
+            <view class="flex justify-between items-center" @click="toggleFaq(index)">
+              <text class="text-sm text-gray-700 flex-1 pr-2">{{ faq.question }}</text>
+              <text class="text-gray-400 text-xs transition-transform" :class="expandedIndex === index ? 'rotate-180' : ''"></text>
+            </view>
+            <view v-if="expandedIndex === index" class="mt-2 text-gray-500 text-sm bg-gray-50 rounded-lg p-3">
+              {{ faq.answer }}
+            </view>
+          </view>
+        </view>
+      </view>
+
+      <!-- 联系客服 -->
+      <view class="card p-4">
+        <view class="flex items-center mb-4">
+          <text class="text-green-500 mr-2"></text>
+          <text class="text-base font-semibold text-gray-800">联系客服</text>
+        </view>
+        <view class="flex items-center justify-between py-3 border-b border-gray-100" @click="makePhoneCall('400-888-8888')">
+          <view class="flex items-center">
+            <text class="text-blue-500 mr-3"></text>
+            <text class="text-sm text-gray-700">客服热线</text>
+          </view>
+          <text class="text-blue-500 text-sm">400-888-8888</text>
+        </view>
+        <view class="flex items-center justify-between py-3 border-b border-gray-100">
+          <view class="flex items-center">
+            <text class="text-blue-500 mr-3">️</text>
+            <text class="text-sm text-gray-700">电子邮箱</text>
+          </view>
+          <text class="text-gray-600 text-sm">service@qingdaofu.com</text>
+        </view>
+        <view class="flex items-center justify-between py-3">
+          <view class="flex items-center">
+            <text class="text-blue-500 mr-3"></text>
+            <text class="text-sm text-gray-700">服务时间</text>
+          </view>
+          <text class="text-gray-600 text-sm">9:00-18:00</text>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+import TopBar from '@/components/common/TopBar.vue'
+import { useCommonStore } from '@/stores/common'
+
+const commonStore = useCommonStore()
+const faqs = commonStore.faqs
+const expandedIndex = ref<number | null>(null)
+
+function toggleFaq(index: number) {
+  expandedIndex.value = expandedIndex.value === index ? null : index
+}
+
+function makePhoneCall(phone: string) {
+  uni.makePhoneCall({ phoneNumber: phone })
+}
+</script>

+ 72 - 0
src/pages/common/knowledgeDetail.vue

@@ -0,0 +1,72 @@
+
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="知识详情" show-back />
+
+    <view class="px-4 py-4">
+      <view class="card overflow-hidden">
+        <!-- 封面图 -->
+        <view class="h-48 bg-gradient-to-br from-blue-400 to-blue-600 flex items-center justify-center">
+          <text class="text-white text-5xl">{{ icon }}</text>
+        </view>
+
+        <view class="p-4">
+          <text class="text-lg font-bold text-gray-800 mb-2 block">{{ knowledge?.title || '知识详情' }}</text>
+          <view class="flex items-center text-gray-400 text-sm mb-4">
+            <text class="mr-1"></text>
+            <text>{{ knowledge?.createTime ? formatDate(knowledge.createTime, 'YYYY-MM-DD') : '' }}</text>
+          </view>
+
+          <view class="border-t border-gray-100 pt-4 space-y-4">
+            <view class="bg-blue-50 rounded-xl p-4">
+              <text class="font-medium text-blue-600 mb-2 block text-sm"> 摘要</text>
+              <text class="text-gray-600 text-sm block">{{ knowledge?.summary || '暂无摘要' }}</text>
+            </view>
+
+            <view class="bg-green-50 rounded-xl p-4">
+              <text class="font-medium text-green-600 mb-2 block text-sm"> 内容</text>
+              <text class="text-gray-600 text-sm block whitespace-pre-wrap">{{ knowledge?.content || '暂无内容' }}</text>
+            </view>
+
+            <view class="bg-purple-50 rounded-xl p-4">
+              <text class="font-medium text-purple-600 mb-2 block text-sm">️ 分类</text>
+              <text class="text-gray-600 text-sm block">{{ knowledge?.category || '-' }}</text>
+            </view>
+          </view>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue'
+import TopBar from '@/components/common/TopBar.vue'
+import { formatDate } from '@/utils'
+import type { Knowledge } from '@/types'
+
+const knowledge = ref<Knowledge | null>(null)
+
+const icon = computed(() => {
+  const category = knowledge.value?.category || ''
+  const iconMap: Record<string, string> = {
+    '安全规范': '️',
+    '操作指南': '',
+    '设备维护': '',
+  }
+  return iconMap[category] || ''
+})
+
+onMounted(() => {
+  const data = uni.getStorageSync('current_knowledge')
+  if (data) {
+    knowledge.value = data
+  }
+})
+</script>
+
+<style scoped>
+.bg-gradient-to-br {
+  background: linear-gradient(to bottom right, #60a5fa, #2563eb);
+}
+</style>

+ 103 - 0
src/pages/common/knowledgeList.vue

@@ -0,0 +1,103 @@
+
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="行业知识" show-back />
+
+    <view class="px-4 py-4">
+      <view
+        v-for="(item, index) in knowledges"
+        :key="item.id"
+        class="knowledge-card flex"
+        @click="goToDetail(item)"
+      >
+        <view
+          class="w-24 h-24 rounded-xl flex items-center justify-center mr-3 flex-shrink-0"
+          :class="getGradientClass(index)"
+        >
+          <text class="text-white text-2xl">{{ getIcon(index) }}</text>
+        </view>
+        <view class="flex-1 flex flex-col justify-center">
+          <text class="font-medium text-gray-800 mb-1 text-sm">{{ item.title }}</text>
+          <text class="text-gray-400 text-xs line-clamp-2 mb-1">{{ item.summary }}</text>
+          <text class="text-gray-300 text-xs">{{ formatDate(item.createTime, 'YYYY-MM-DD') }}</text>
+        </view>
+      </view>
+    </view>
+
+    <view v-if="knowledges.length === 0" class="px-4 mt-3">
+      <EmptyState message="暂无知识内容" />
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue'
+import TopBar from '@/components/common/TopBar.vue'
+import EmptyState from '@/components/common/EmptyState.vue'
+import { useCommonStore } from '@/stores/common'
+import { formatDate, navigateTo } from '@/utils'
+import type { Knowledge } from '@/types'
+
+const commonStore = useCommonStore()
+const knowledges = computed(() => commonStore.knowledges)
+
+const gradients = [
+  'bg-gradient-to-br from-blue-400 to-blue-600',
+  'bg-gradient-to-br from-yellow-400 to-orange-500',
+  'bg-gradient-to-br from-green-400 to-emerald-600',
+  'bg-gradient-to-br from-purple-400 to-violet-600',
+  'bg-gradient-to-br from-red-400 to-rose-600',
+]
+
+const icons = ['', '', '', '', '']
+
+function getGradientClass(index: number): string {
+  return gradients[index % gradients.length]
+}
+
+function getIcon(index: number): string {
+  return icons[index % icons.length]
+}
+
+function goToDetail(item: Knowledge) {
+  commonStore.setCurrentKnowledge(item.id)
+  uni.setStorageSync('current_knowledge', item)
+  navigateTo('/pages/common/knowledgeDetail')
+}
+</script>
+
+<style scoped>
+.knowledge-card {
+  background: white;
+  padding: 12px;
+  border-radius: 12px;
+  margin-bottom: 12px;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.06);
+}
+
+.knowledge-card:active {
+  transform: scale(0.98);
+}
+
+.line-clamp-2 {
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+  overflow: hidden;
+}
+
+.bg-gradient-to-br {
+  background: linear-gradient(to bottom right, var(--tw-gradient-from), var(--tw-gradient-to));
+}
+
+.from-blue-400 { --tw-gradient-from: #60a5fa; }
+.to-blue-600 { --tw-gradient-to: #2563eb; }
+.from-yellow-400 { --tw-gradient-from: #facc15; }
+.to-orange-500 { --tw-gradient-to: #f97316; }
+.from-green-400 { --tw-gradient-from: #4ade80; }
+.to-emerald-600 { --tw-gradient-to: #059669; }
+.from-purple-400 { --tw-gradient-from: #c084fc; }
+.to-violet-600 { --tw-gradient-to: #7c3aed; }
+.from-red-400 { --tw-gradient-from: #f87171; }
+.to-rose-600 { --tw-gradient-to: #e11d48; }
+</style>

+ 60 - 0
src/pages/common/messageDetail.vue

@@ -0,0 +1,60 @@
+
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="消息详情" show-back />
+
+    <view class="px-4 py-4">
+      <view class="card p-4">
+        <view class="flex items-center justify-between mb-3">
+          <text class="text-base font-bold text-gray-800">{{ message?.title || '消息详情' }}</text>
+        </view>
+        <view class="flex items-center text-gray-400 text-sm mb-4">
+          <text class="mr-1"></text>
+          <text>{{ message?.createTime ? formatDate(message.createTime, 'YYYY-MM-DD HH:mm') : '' }}</text>
+        </view>
+        <view class="border-t border-gray-100 pt-4">
+          <text class="text-gray-600 text-sm leading-relaxed block mb-4">{{ message?.content || '暂无内容' }}</text>
+
+          <view v-if="message?.type === 'task'" class="bg-gray-50 rounded-lg p-4 mb-4">
+            <view class="flex justify-between text-sm mb-2">
+              <text class="text-gray-500">任务名称:</text>
+              <text class="text-gray-700">{{ message.title }}</text>
+            </view>
+            <view class="flex justify-between text-sm mb-2">
+              <text class="text-gray-500">执行班组:</text>
+              <text class="text-gray-700">张师傅组</text>
+            </view>
+            <view class="flex justify-between text-sm mb-2">
+              <text class="text-gray-500">预计到达:</text>
+              <text class="text-gray-700">09:30</text>
+            </view>
+            <view class="flex justify-between text-sm">
+              <text class="text-gray-500">联系电话:</text>
+              <text class="text-gray-700">139****6666</text>
+            </view>
+          </view>
+
+          <text class="text-gray-600 text-sm leading-relaxed block">
+            施工过程中如有任何问题,请及时联系调度中心。服务完成后,请及时验收确认。
+          </text>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from 'vue'
+import TopBar from '@/components/common/TopBar.vue'
+import { formatDate } from '@/utils'
+import type { Notification } from '@/types'
+
+const message = ref<Notification | null>(null)
+
+onMounted(() => {
+  const data = uni.getStorageSync('current_message')
+  if (data) {
+    message.value = data
+  }
+})
+</script>

+ 104 - 0
src/pages/common/messageList.vue

@@ -0,0 +1,104 @@
+
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="消息通知" show-back>
+      <template #right>
+        <text class="text-blue-500 text-sm" @click="markAllRead">全部已读</text>
+      </template>
+    </TopBar>
+
+    <!-- 筛选标签 -->
+    <view class="bg-white px-4 py-3 flex gap-2">
+      <view
+        v-for="tab in filterTabs"
+        :key="tab.value"
+        class="px-4 py-2 rounded-full text-sm whitespace-nowrap"
+        :class="currentFilter === tab.value ? 'bg-blue-500 text-white' : 'bg-gray-100 text-gray-600'"
+        @click="currentFilter = tab.value"
+      >
+        {{ tab.label }}
+      </view>
+    </view>
+
+    <!-- 消息列表 -->
+    <view class="bg-white">
+      <view
+        v-for="msg in filteredMessages"
+        :key="msg.id"
+        class="notice-item"
+        @click="goToDetail(msg)"
+      >
+        <view class="flex justify-between items-start mb-2">
+          <view class="flex items-center flex-1">
+            <text v-if="!msg.read" class="w-2 h-2 bg-red-500 rounded-full mr-2 flex-shrink-0"></text>
+            <text v-else class="w-2 h-2 rounded-full mr-2 flex-shrink-0"></text>
+            <text class="font-medium text-sm" :class="msg.read ? 'text-gray-600' : 'text-gray-800'">{{ msg.title }}</text>
+          </view>
+          <text class="text-gray-400 text-xs whitespace-nowrap">{{ formatRelativeTime(msg.createTime) }}</text>
+        </view>
+        <text class="text-gray-500 text-sm line-clamp-2">{{ msg.content }}</text>
+      </view>
+    </view>
+
+    <view v-if="filteredMessages.length === 0" class="px-4 mt-3">
+      <EmptyState message="暂无消息" />
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed } from 'vue'
+import TopBar from '@/components/common/TopBar.vue'
+import EmptyState from '@/components/common/EmptyState.vue'
+import { useNotificationStore } from '@/stores/notification'
+import { formatRelativeTime, navigateTo } from '@/utils'
+
+const notificationStore = useNotificationStore()
+const currentFilter = ref('all')
+
+const filterTabs = [
+  { label: '全部', value: 'all' },
+  { label: '任务消息', value: 'task' },
+  { label: '系统消息', value: 'system' },
+]
+
+const filteredMessages = computed(() => {
+  let list = notificationStore.notifications
+  if (currentFilter.value !== 'all') {
+    list = list.filter(n => n.type === currentFilter.value)
+  }
+  return list.map(n => ({
+    ...n,
+    content: n.content.length > 60 ? n.content.slice(0, 60) + '...' : n.content
+  }))
+})
+
+function markAllRead() {
+  notificationStore.markAllAsRead()
+}
+
+function goToDetail(msg: typeof filteredMessages.value[0]) {
+  notificationStore.markAsRead(msg.id)
+  uni.setStorageSync('current_message', msg)
+  navigateTo('/pages/common/messageDetail')
+}
+</script>
+
+<style scoped>
+.notice-item {
+  background: white;
+  padding: 16px;
+  border-bottom: 1px solid #f0f0f0;
+}
+
+.notice-item:active {
+  background: #f9fafb;
+}
+
+.line-clamp-2 {
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+  overflow: hidden;
+}
+</style>

+ 88 - 0
src/pages/common/noticeDetail.vue

@@ -0,0 +1,88 @@
+
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="通知详情" show-back />
+    
+    <view v-if="loading" class="py-20 text-center">
+      <text class="text-gray-400">加载中...</text>
+    </view>
+    
+    <view v-else-if="notice" class="px-4 py-4 space-y-4">
+      <view class="card p-4">
+        <view class="flex justify-between items-start mb-3">
+          <view class="flex items-center">
+            <view v-if="notice.priority === 3" class="w-2 h-2 bg-red-500 rounded-full mr-2"></view>
+            <text class="text-lg font-semibold text-gray-800">{{ notice.title }}</text>
+          </view>
+          <view class="tag" :class="getTypeClass(notice.type)">
+            {{ getTypeText(notice.type) }}
+          </view>
+        </view>
+        
+        <view class="text-sm text-gray-500 mb-4">
+          <text class="block">发布人: {{ notice.publisherName }}</text>
+          <text class="block mt-1">发布时间: {{ formatDate(notice.publishTime) }}</text>
+          <text class="block mt-1">阅读量: {{ notice.readCount }}</text>
+        </view>
+        
+        <view class="text-sm text-gray-800 leading-relaxed">
+          {{ notice.content }}
+        </view>
+      </view>
+    </view>
+    
+    <view v-else class="px-4 py-6">
+      <EmptyState message="通知不存在" />
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from 'vue'
+import TopBar from '../../components/common/TopBar.vue'
+import EmptyState from '../../components/common/EmptyState.vue'
+import { getNoticeDetail } from '../../api/notice'
+import { formatDate } from '../../utils'
+
+const loading = ref(true)
+const notice = ref<any>(null)
+
+const props = defineProps<{
+  id?: string
+}>()
+
+function getTypeText(type: string) {
+  const map: Record<string, string> = {
+    system: '系统',
+    emergency: '应急',
+    task: '任务',
+  }
+  return map[type] || type
+}
+
+function getTypeClass(type: string) {
+  const map: Record<string, string> = {
+    system: 'tag-blue',
+    emergency: 'tag-red',
+    task: 'tag-green',
+  }
+  return map[type] || 'tag-gray'
+}
+
+onMounted(async () => {
+  const noticeId = props.id || uni.getStorageSync('currentNoticeId')
+  if (!noticeId) {
+    loading.value = false
+    return
+  }
+  
+  try {
+    const res = await getNoticeDetail(Number(noticeId))
+    notice.value = res
+  } catch (error) {
+    console.error('获取通知详情失败:', error)
+  } finally {
+    loading.value = false
+  }
+})
+</script>

+ 132 - 0
src/pages/common/noticeList.vue

@@ -0,0 +1,132 @@
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="通知公告" show-back />
+    
+    <!-- 筛选标签 - 使用 uni-segmented-control -->
+    <uni-segmented-control 
+      :current="currentFilterIndex" 
+      :values="filterLabels"
+      style-type="text"
+      active-color="#4f8ef7"
+      @clickItem="onFilterChange"
+    />
+
+    <!-- 通知列表 -->
+    <view class="mt-2">
+      <view v-if="loading" class="py-10 text-center">
+        <text class="text-gray-400">加载中...</text>
+      </view>
+      
+      <view v-else-if="filteredNotices.length === 0">
+        <EmptyState message="暂无通知" />
+      </view>
+      
+      <uni-list v-else border>
+        <uni-swipe-action 
+          v-for="notice in filteredNotices" 
+          :key="notice.noticeId"
+          :actions="[{text: '标记已读', color: '#4f8ef7', key: 'read'}]"
+          @click="(action: any) => onSwipeAction(notice.noticeId, action)"
+        >
+          <uni-list-item 
+            :title="notice.title"
+            :note="notice.publisherName + ' · ' + formatDate(notice.publishTime)"
+            :right-text="getTypeText(notice.type)"
+            show-arrow
+            @click="goToDetail(notice.noticeId)"
+          >
+            <template #header>
+              <view class="w-10 h-10 rounded-xl flex items-center justify-center mr-3" :class="getTypeBgClass(notice.type)">
+                <text class="uni-icons text-lg" :class="getTypeIconClass(notice.type)"></text>
+              </view>
+            </template>
+          </uni-list-item>
+        </uni-swipe-action>
+      </uni-list>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue'
+import TopBar from '../../components/common/TopBar.vue'
+import EmptyState from '../../components/common/EmptyState.vue'
+import UniList from '../../components/uni-list/uni-list.vue'
+import UniListItem from '../../components/uni-list/uni-list-item.vue'
+import UniSwipeAction from '../../components/uni-swipe-action/uni-swipe-action.vue'
+import UniSegmentedControl from '../../components/uni-segmented-control/uni-segmented-control.vue'
+import { getNoticeList } from '../../api/notice'
+import { formatDate } from '../../utils'
+
+const loading = ref(false)
+const notices = ref<any[]>([])
+const currentFilterIndex = ref(0)
+
+const filterTypes = [
+  { value: 'all', label: '全部' },
+  { value: 'system', label: '系统' },
+  { value: 'emergency', label: '应急' },
+  { value: 'task', label: '任务' },
+]
+
+const filterLabels = filterTypes.map(t => t.label)
+
+const filteredNotices = computed(() => {
+  const filterValue = filterTypes[currentFilterIndex.value].value
+  if (filterValue === 'all') return notices.value
+  return notices.value.filter(n => n.type === filterValue)
+})
+
+function getTypeText(type: string) {
+  const map: Record<string, string> = {
+    system: '系统',
+    emergency: '应急',
+    task: '任务',
+  }
+  return map[type] || type
+}
+
+function getTypeBgClass(type: string) {
+  const map: Record<string, string> = {
+    system: 'bg-blue-50',
+    emergency: 'bg-red-50',
+    task: 'bg-green-50',
+  }
+  return map[type] || 'bg-gray-50'
+}
+
+function getTypeIconClass(type: string) {
+  const map: Record<string, string> = {
+    system: 'uniui-sound text-blue-500',
+    emergency: 'uniui-notification-filled text-red-500',
+    task: 'uniui-flag text-green-500',
+  }
+  return map[type] || 'uniui-sound text-gray-400'
+}
+
+function onFilterChange(index: number) {
+  currentFilterIndex.value = index
+}
+
+function onSwipeAction(noticeId: number, action: { key: string; text: string }) {
+  if (action.key === 'read') {
+    uni.showToast({ title: '已标记已读', icon: 'success' })
+  }
+}
+
+function goToDetail(noticeId: number) {
+  uni.navigateTo({ url: `/pages/common/noticeDetail?id=${noticeId}` })
+}
+
+onMounted(async () => {
+  loading.value = true
+  try {
+    const res = await getNoticeList()
+    notices.value = res.rows || []
+  } catch (error) {
+    console.error('获取通知列表失败:', error)
+  } finally {
+    loading.value = false
+  }
+})
+</script>

+ 140 - 0
src/pages/common/profile.vue

@@ -0,0 +1,140 @@
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="个人中心" />
+    
+    <!-- 顶部个人信息 -->
+    <view class="bg-gradient-to-r from-blue-500 to-blue-600 px-4 pt-8 pb-6">
+      <view class="flex items-center">
+        <view class="w-16 h-16 bg-white rounded-full flex items-center justify-center mr-4">
+          <text class="uni-icons uniui-person-filled text-blue-500 text-2xl"></text>
+        </view>
+        <view>
+          <text class="text-white text-lg font-semibold block">{{ userInfo?.nickName || userInfo?.userName || '用户' }}</text>
+          <text class="text-blue-100 text-sm">{{ roleText }}</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 功能列表 -->
+    <view class="px-4 -mt-4 space-y-4">
+      <!-- 基本信息 -->
+      <uni-card title="账号信息" icon="uniui-person-filled" :is-shadow="true">
+        <uni-list border="false">
+          <uni-list-item title="用户名" :note="userInfo?.userName || '-'" />
+          <uni-list-item title="昵称" :note="userInfo?.nickName || '-'" />
+          <uni-list-item title="角色" :note="roleText" />
+          <uni-list-item title="手机号" :note="userInfo?.phonenumber || '-'" />
+        </uni-list>
+      </uni-card>
+
+      <!-- 功能菜单 -->
+      <uni-card title="功能菜单" icon="uniui-gear-filled" :is-shadow="true">
+        <uni-list border="false">
+          <uni-list-item 
+            title="基本信息" 
+            icon="uniui-person-filled" 
+            show-arrow 
+            @click="goToBasicInfo"
+          />
+          <uni-list-item 
+            title="修改密码" 
+            icon="uniui-locked-filled" 
+            show-arrow 
+            @click="goToChangePassword"
+          />
+          <uni-list-item 
+            title="通知公告" 
+            icon="uniui-notification-filled" 
+            show-arrow 
+            @click="goToNotices"
+          />
+          <uni-list-item 
+            title="帮助中心" 
+            icon="uniui-help-filled" 
+            show-arrow 
+            @click="goToHelp"
+          />
+        </uni-list>
+      </uni-card>
+
+      <!-- 退出登录 -->
+      <view class="card p-4">
+        <view class="flex items-center justify-center py-2 cursor-pointer" @click="handleLogout">
+          <text class="uni-icons uniui-close text-red-500 mr-2"></text>
+          <text class="text-red-500 text-sm">退出登录</text>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue'
+import { useAuthStore } from '../../stores/auth'
+import UniCard from '../../components/uni-card/uni-card.vue'
+import UniList from '../../components/uni-list/uni-list.vue'
+import UniListItem from '../../components/uni-list/uni-list-item.vue'
+
+const authStore = useAuthStore()
+
+const userInfo = computed(() => authStore.user)
+
+const roleText = computed(() => {
+  const role = userInfo.value?.role
+  const map: Record<string, string> = {
+    sales: '销售端',
+    dispatch: '调度端',
+    construction: '施工端',
+  }
+  return map[role] || role || '未知'
+})
+
+const role = computed(() => userInfo.value?.role || 'sales')
+
+function goToBasicInfo() {
+  const paths: Record<string, string> = {
+    sales: '/pages/sales/basicInfo',
+    dispatch: '/pages/dispatch/basicInfo',
+    construction: '/pages/construction/basicInfo',
+  }
+  uni.navigateTo({ url: paths[role.value] || paths.sales })
+}
+
+function goToChangePassword() {
+  const paths: Record<string, string> = {
+    sales: '/pages/sales/changePassword',
+    dispatch: '/pages/dispatch/changePassword',
+    construction: '/pages/construction/changePassword',
+  }
+  uni.navigateTo({ url: paths[role.value] || paths.sales })
+}
+
+function goToNotices() {
+  const paths: Record<string, string> = {
+    sales: '/pages/common/noticeList',
+    dispatch: '/pages/dispatch/noticeList',
+    construction: '/pages/construction/noticeList',
+  }
+  uni.navigateTo({ url: paths[role.value] || paths.sales })
+}
+
+function goToHelp() {
+  uni.navigateTo({ url: '/pages/construction/helpCenter' })
+}
+
+function handleLogout() {
+  uni.showModal({
+    title: '确认退出',
+    content: '确定要退出登录吗?',
+    success: (res) => {
+      if (res.confirm) {
+        authStore.logout()
+        // 清除本地存储并跳转到登录页
+        uni.removeStorageSync('token')
+        uni.removeStorageSync('user')
+        uni.reLaunch({ url: '/pages/login/login' })
+      }
+    },
+  })
+}
+</script>

+ 136 - 0
src/pages/common/projectDetail.vue

@@ -0,0 +1,136 @@
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="项目详情" show-back />
+    
+    <view v-if="projectStore.loading" class="py-20 text-center">
+      <text class="text-gray-400">加载中...</text>
+    </view>
+    
+    <view v-else-if="!project" class="px-4 py-6">
+      <EmptyState message="项目不存在" />
+    </view>
+    
+    <view v-else class="px-4 py-4 space-y-4">
+      <!-- 项目基本信息 -->
+      <uni-card :title="project.name" :is-shadow="true">
+        <view class="flex justify-between items-start mb-3">
+          <StatusTag :status="project.status" />
+          <text class="text-xs text-gray-400">{{ project.createTime }}</text>
+        </view>
+        
+        <uni-list border="false">
+          <uni-list-item 
+            title="地址" 
+            :note="project.address" 
+            icon="uniui-location-filled"
+          />
+          <uni-list-item 
+            title="客户" 
+            :note="project.customerName" 
+            icon="uniui-person-filled"
+          />
+          <uni-list-item 
+            title="电话" 
+            :note="project.customerPhone" 
+            icon="uniui-phone-filled"
+            @click="callPhone(project.customerPhone)"
+          />
+          <uni-list-item 
+            title="类型" 
+            :note="project.type" 
+            icon="uniui-folder-add-filled"
+          />
+        </uni-list>
+      </uni-card>
+
+      <!-- 服务点信息 -->
+      <uni-card title="服务点信息" :is-shadow="true">
+        <view v-if="project.servicePoints && project.servicePoints.length > 0">
+          <uni-list border="false">
+            <uni-list-item 
+              v-for="(point, index) in project.servicePoints" 
+              :key="index"
+              :title="'服务点 ' + (index + 1)"
+              :note="point.address"
+              icon="uniui-location-filled"
+            >
+              <template #footer>
+                <text class="text-xs text-gray-400">{{ point.contactName }} | {{ point.contactPhone }}</text>
+              </template>
+            </uni-list-item>
+          </uni-list>
+        </view>
+        <view v-else class="text-center py-4">
+          <text class="text-sm text-gray-400">暂无服务点信息</text>
+        </view>
+      </uni-card>
+
+      <!-- 关联任务 -->
+      <uni-card title="关联任务" :is-shadow="true">
+        <view v-if="projectTasks.length > 0">
+          <uni-list border="false">
+            <uni-list-item 
+              v-for="task in projectTasks" 
+              :key="task.id"
+              :title="task.projectName"
+              :note="task.scheduleDate + ' ' + task.scheduleTime"
+              show-arrow
+              @click="goToTask(task.id)"
+            >
+              <template #footer>
+                <StatusTag :status="task.status" />
+              </template>
+            </uni-list-item>
+          </uni-list>
+        </view>
+        <view v-else class="text-center py-4">
+          <text class="text-sm text-gray-400">暂无关联任务</text>
+        </view>
+      </uni-card>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue'
+import { useProjectStore } from '../../stores/project'
+import { useTaskStore } from '../../stores/task'
+import TopBar from '../../components/common/TopBar.vue'
+import StatusTag from '../../components/common/StatusTag.vue'
+import EmptyState from '../../components/common/EmptyState.vue'
+import UniCard from '../../components/uni-card/uni-card.vue'
+import UniList from '../../components/uni-list/uni-list.vue'
+import UniListItem from '../../components/uni-list/uni-list-item.vue'
+
+const projectStore = useProjectStore()
+const taskStore = useTaskStore()
+
+const project = computed(() => projectStore.currentProject)
+const projectTasks = ref<any[]>([])
+
+function callPhone(phone: string) {
+  if (!phone) return
+  uni.makePhoneCall({ phoneNumber: phone })
+}
+
+function goToTask(taskId: string) {
+  taskStore.setCurrentTask(taskId)
+  uni.navigateTo({ url: '/pages/common/taskDetail' })
+}
+
+onMounted(async () => {
+  if (!project.value) {
+    const pages = getCurrentPages()
+    const currentPage = pages[pages.length - 1]
+    const projectId = currentPage.options?.id
+    if (projectId) {
+      projectStore.setCurrentProject(projectId)
+    }
+  }
+  
+  if (project.value) {
+    await taskStore.fetchTasks()
+    projectTasks.value = taskStore.tasks.filter(t => t.projectId === project.value?.id)
+  }
+})
+</script>

+ 180 - 0
src/pages/common/projectList.vue

@@ -0,0 +1,180 @@
+
+<template>
+  <view class="app-container pb-24">
+    <TopBar title="项目库" show-back />
+    
+    <!-- 顶部搜索栏 -->
+    <view class="bg-white px-4 py-3 sticky top-0 z-40">
+      <view class="flex items-center bg-gray-100 rounded-2xl px-4 py-2.5">
+        <text class="uni-icons uniui-search text-gray-400 mr-2 text-base"></text>
+        <input 
+          class="flex-1 bg-transparent text-sm text-gray-700" 
+          placeholder="搜索项目名称" 
+          v-model="searchKeyword"
+        />
+      </view>
+    </view>
+
+    <!-- 顶部筛选标签 - 使用 uni-segmented-control -->
+    <uni-segmented-control 
+      :current="currentFilterIndex" 
+      :values="filterLabels"
+      style-type="text"
+      active-color="#4f8ef7"
+      @clickItem="onFilterChange"
+    />
+
+    <!-- 项目列表 - 使用 uni-list + uni-card + uni-swipe-action -->
+    <view class="mt-2">
+      <view v-if="projectStore.loading" class="py-16 text-center">
+        <text class="text-gray-400">加载中...</text>
+      </view>
+      
+      <view v-else-if="filteredProjects.length === 0">
+        <EmptyState message="暂无项目" />
+      </view>
+      
+      <uni-list v-else border>
+        <uni-swipe-action 
+          v-for="project in filteredProjects" 
+          :key="project.id"
+          :actions="swipeActions"
+          @click="(action: any) => onSwipeAction(project.id, action)"
+        >
+          <uni-list-item 
+            :title="project.name"
+            :note="project.address + ' | ' + project.customerName"
+            :right-text="project.type"
+            show-arrow
+            @click="goToDetail(project.id)"
+          >
+            <template #header>
+              <view class="w-10 h-10 bg-blue-50 rounded-xl flex items-center justify-center mr-3">
+                <text class="uni-icons uniui-folder-add-filled text-blue-500 text-lg"></text>
+              </view>
+            </template>
+          </uni-list-item>
+        </uni-swipe-action>
+      </uni-list>
+    </view>
+
+    <!-- 加载更多 -->
+    <view v-if="filteredProjects.length > 0" class="px-4 mt-4 mb-4">
+      <view v-if="projectStore.hasMore" class="text-center py-4">
+        <text 
+          class="text-sm text-blue-500 font-medium"
+          @click="loadMore"
+        >
+          {{ projectStore.loadingMore ? '加载中...' : '加载更多' }}
+        </text>
+      </view>
+      <view v-else class="text-center py-4">
+        <text class="text-xs text-gray-400">已经到底了</text>
+      </view>
+    </view>
+
+    <!-- 新增按钮 -->
+    <view class="fixed bottom-20 right-4">
+      <button 
+        class="w-14 h-14 rounded-full bg-blue-500 text-white flex items-center justify-center shadow-lg"
+        @click="goToAdd"
+      >
+        <text class="uni-icons uniui-plusempty text-white text-2xl"></text>
+      </button>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue'
+import { useProjectStore } from '../../stores/project'
+import EmptyState from '../../components/common/EmptyState.vue'
+import UniList from '../../components/uni-list/uni-list.vue'
+import UniListItem from '../../components/uni-list/uni-list-item.vue'
+import UniSwipeAction from '../../components/uni-swipe-action/uni-swipe-action.vue'
+import UniSegmentedControl from '../../components/uni-segmented-control/uni-segmented-control.vue'
+
+const projectStore = useProjectStore()
+const searchKeyword = ref('')
+const currentFilterIndex = ref(0)
+
+const filterTypes = [
+  { value: 'all', label: '全部' },
+  { value: '包年', label: '包年' },
+  { value: '单次', label: '单次' },
+  { value: '季度', label: '季度' },
+  { value: '月付', label: '月付' },
+]
+
+const filterLabels = filterTypes.map(t => t.label)
+
+const filteredProjects = computed(() => {
+  let list = projectStore.projects
+  const filterValue = filterTypes[currentFilterIndex.value].value
+  
+  if (filterValue !== 'all') {
+    list = list.filter(p => p.type === filterValue)
+  }
+  
+  if (searchKeyword.value) {
+    list = list.filter(p => p.name.includes(searchKeyword.value))
+  }
+  
+  return list
+})
+
+const swipeActions = [
+  { text: '编辑', color: '#4f8ef7', key: 'edit' },
+  { text: '删除', color: '#ff3b30', key: 'delete' },
+]
+
+function onFilterChange(index: number) {
+  currentFilterIndex.value = index
+}
+
+function onSwipeAction(projectId: string, action: { key: string; text: string }) {
+  if (action.key === 'delete') {
+    uni.showModal({
+      title: '确认删除',
+      content: '删除后无法恢复,是否继续?',
+      success: (res) => {
+        if (res.confirm) {
+          projectStore.deleteProject(projectId)
+          uni.showToast({ title: '已删除', icon: 'success' })
+        }
+      }
+    })
+  } else if (action.key === 'edit') {
+    projectStore.setCurrentProject(projectId)
+    uni.navigateTo({ url: '/pages/common/addProject?mode=edit' })
+  }
+}
+
+function goToDetail(projectId: string) {
+  projectStore.setCurrentProject(projectId)
+  uni.navigateTo({ url: '/pages/common/projectDetail' })
+}
+
+function goToAdd() {
+  uni.navigateTo({ url: '/pages/common/addProject' })
+}
+
+async function refreshData() {
+  await projectStore.fetchProjects(true)
+  uni.stopPullDownRefresh()
+}
+
+function loadMore() {
+  if (projectStore.loadingMore) return
+  projectStore.loadMore()
+}
+
+onMounted(() => {
+  projectStore.fetchProjects(true)
+})
+
+// 下拉刷新
+onPullDownRefresh(() => {
+  refreshData()
+})
+</script>

+ 501 - 0
src/pages/common/publishTask.vue

@@ -0,0 +1,501 @@
+<template>
+  <view class="app-container pb-24">
+    <TopBar title="发布任务" show-back />
+
+    <view class="px-4 py-4">
+      <!-- 板块一:项目基本信息 -->
+      <view class="card p-4 mb-4">
+        <view class="flex items-center mb-4">
+          <view class="w-8 h-8 bg-blue-100 rounded-lg flex items-center justify-center mr-3">
+            <text class="uni-icons uniui-flag text-blue-500"></text>
+          </view>
+          <text class="text-base font-semibold text-gray-800">项目基本信息</text>
+        </view>
+
+        <!-- 项目选择提示 -->
+        <view class="bg-blue-50 rounded-lg px-3 py-2 mb-4 flex items-center">
+          <text class="text-blue-500 mr-2 text-sm">ℹ️</text>
+          <text class="text-blue-600 text-sm">请从项目库选择或新增项目</text>
+        </view>
+
+        <!-- 项目选择 -->
+        <view class="form-group">
+          <text class="form-label">
+            <text class="text-red-500">*</text>选择项目
+          </text>
+          <picker mode="selector" :range="projectNames" :value="projectIndex" @change="onProjectSelectChange">
+            <view class="form-select flex justify-between items-center">
+              <text :class="selectedProject ? 'text-gray-800' : 'text-gray-400'">{{ selectedProject?.name || '请选择项目' }}</text>
+              <text class="text-gray-400 text-xs"></text>
+            </view>
+          </picker>
+        </view>
+
+        <!-- 已选项目信息 -->
+        <view v-if="selectedProject" class="bg-green-50 rounded-xl p-3 mb-4">
+          <view class="flex items-center justify-between mb-3">
+            <text class="text-green-600 text-sm font-medium"> 已选择:{{ selectedProject.name }}</text>
+            <text class="text-green-500 text-xs" @click="resetProjectSelection">重新选择</text>
+          </view>
+          <view class="space-y-2">
+            <view class="flex justify-between text-sm">
+              <text class="text-gray-500">合作类型:</text>
+              <text class="text-gray-700">{{ selectedProject.type }}</text>
+            </view>
+            <view class="flex justify-between text-sm">
+              <text class="text-gray-500">设施类型:</text>
+              <text class="text-gray-700">油污管、化粪池</text>
+            </view>
+            <view class="flex justify-between text-sm">
+              <text class="text-gray-500">项目地址:</text>
+              <text class="text-gray-700">{{ selectedProject.address }}</text>
+            </view>
+          </view>
+        </view>
+
+        <!-- 联系人列表 -->
+        <view class="form-group mb-0">
+          <text class="form-label">联系人</text>
+          <view v-if="selectedProject" class="space-y-2 mb-2">
+            <view v-for="(contact, index) in selectedProject.contacts" :key="index" class="bg-gray-50 rounded-lg p-3 flex justify-between items-center">
+              <view>
+                <text class="text-sm text-gray-800">{{ contact.name }}</text>
+                <text class="text-xs text-gray-400 ml-2">{{ contact.role }}</text>
+              </view>
+              <text class="text-sm text-blue-500">{{ contact.phone }}</text>
+            </view>
+          </view>
+        </view>
+      </view>
+
+      <!-- 板块二:服务信息 -->
+      <view class="card p-4">
+        <view class="flex items-center mb-4">
+          <view class="w-8 h-8 bg-purple-100 rounded-lg flex items-center justify-center mr-3">
+            <text class="text-purple-500"></text>
+          </view>
+          <text class="text-base font-semibold text-gray-800">服务信息</text>
+        </view>
+
+        <!-- 服务时间 -->
+        <view class="form-group">
+          <text class="form-label">
+            <text class="text-red-500">*</text>服务时间
+          </text>
+          <view class="flex gap-2">
+            <picker mode="date" :value="serviceDate" @change="onServiceDateChange">
+              <view class="form-select flex justify-between items-center">
+                <text :class="serviceDate ? 'text-gray-800' : 'text-gray-400'">{{ serviceDate || '选择日期' }}</text>
+              </view>
+            </picker>
+            <picker mode="time" :value="serviceTime" @change="onServiceTimeChange">
+              <view class="form-select flex justify-between items-center" style="flex: 0.6;">
+                <text :class="serviceTime ? 'text-gray-800' : 'text-gray-400'">{{ serviceTime || '选择时间' }}</text>
+              </view>
+            </picker>
+          </view>
+        </view>
+
+        <!-- 故障位置 -->
+        <view class="form-group">
+          <text class="form-label">
+            <text class="text-red-500">*</text>故障位置
+          </text>
+          <input class="form-input" v-model="faultLocation" placeholder="请输入故障位置" />
+        </view>
+
+        <!-- 地理位置 -->
+        <view class="form-group">
+          <text class="form-label">地理位置</text>
+          <view class="bg-gray-100 rounded-xl h-24 flex items-center justify-center border border-gray-200" @click="showToast('地图定位功能开发中')">
+            <view class="text-center">
+              <text class="text-gray-400 text-2xl block mb-1">️</text>
+              <text class="text-gray-400 text-xs">点击获取当前位置</text>
+            </view>
+          </view>
+        </view>
+
+        <!-- 故障类型 -->
+        <view class="form-group">
+          <text class="form-label">
+            <text class="text-red-500">*</text>故障类型
+          </text>
+          <view class="flex flex-wrap gap-2">
+            <view
+              v-for="type in faultTypes"
+              :key="type"
+              class="px-3 py-1.5 rounded-full text-sm border"
+              :class="selectedFaultTypes.includes(type) ? 'bg-blue-500 text-white border-blue-500' : 'bg-gray-100 text-gray-600 border-gray-200'"
+              @click="toggleFaultType(type)"
+            >
+              {{ type }}
+            </view>
+          </view>
+        </view>
+
+        <!-- 客户原话 -->
+        <view class="form-group">
+          <text class="form-label">客户原话</text>
+          <textarea class="form-textarea" v-model="customerWords" placeholder="请记录客户原话描述..."></textarea>
+        </view>
+
+        <!-- 故障描述 -->
+        <view class="form-group">
+          <text class="form-label">
+            <text class="text-red-500">*</text>故障描述
+          </text>
+          <textarea class="form-textarea" v-model="faultDesc" placeholder="请详细描述故障情况..."></textarea>
+        </view>
+
+        <!-- 施工类型 -->
+        <view class="form-group">
+          <text class="form-label">
+            <text class="text-red-500">*</text>施工类型
+          </text>
+          <picker mode="selector" :range="serviceTypes" :value="serviceTypeIndex" @change="onServiceTypeChange">
+            <view class="form-select flex justify-between items-center">
+              <text :class="serviceType ? 'text-gray-800' : 'text-gray-400'">{{ serviceType || '请选择施工类型' }}</text>
+              <text class="text-gray-400 text-xs"></text>
+            </view>
+          </picker>
+        </view>
+
+        <!-- 施工设施 -->
+        <view class="form-group">
+          <text class="form-label">
+            <text class="text-red-500">*</text>施工设施
+          </text>
+          <picker mode="selector" :range="facilityOptions" :value="facilityIndex" @change="onFacilityChange">
+            <view class="form-select flex justify-between items-center">
+              <text :class="facility ? 'text-gray-800' : 'text-gray-400'">{{ facility || '请选择施工设施' }}</text>
+              <text class="text-gray-400 text-xs"></text>
+            </view>
+          </picker>
+        </view>
+
+        <!-- 服务方式 -->
+        <view class="form-group">
+          <text class="form-label">
+            <text class="text-red-500">*</text>服务方式
+          </text>
+          <picker mode="selector" :range="serviceModes" :value="serviceModeIndex" @change="onServiceModeChange">
+            <view class="form-select flex justify-between items-center">
+              <text :class="serviceMode ? 'text-gray-800' : 'text-gray-400'">{{ serviceMode || '请选择服务方式' }}</text>
+              <text class="text-gray-400 text-xs"></text>
+            </view>
+          </picker>
+        </view>
+
+        <!-- 建议出车 -->
+        <view class="form-group">
+          <text class="form-label">
+            <text class="text-red-500">*</text>建议出车
+          </text>
+          <picker mode="selector" :range="vehicleOptions" :value="vehicleIndex" @change="onVehicleChange">
+            <view class="form-select flex justify-between items-center">
+              <text :class="vehicle ? 'text-gray-800' : 'text-gray-400'">{{ vehicle || '请选择车辆' }}</text>
+              <text class="text-gray-400 text-xs"></text>
+            </view>
+          </picker>
+        </view>
+
+        <!-- 取水距离 -->
+        <view class="form-group">
+          <text class="form-label">取水距离</text>
+          <picker mode="selector" :range="waterOptions" :value="waterIndex" @change="onWaterChange">
+            <view class="form-select flex justify-between items-center">
+              <text :class="waterSource ? 'text-gray-800' : 'text-gray-400'">{{ waterSource || '请选择' }}</text>
+              <text class="text-gray-400 text-xs"></text>
+            </view>
+          </picker>
+        </view>
+
+        <!-- 施工助力信息 -->
+        <view class="form-group">
+          <text class="form-label">施工助力信息</text>
+          <textarea class="form-textarea" v-model="constructionHelp" placeholder="如地库限高、需搭脚手架、登高作业等"></textarea>
+        </view>
+
+        <!-- 预计作业量 -->
+        <view class="form-group">
+          <text class="form-label">预计作业量</text>
+          <input class="form-input" v-model="estimatedQuantity" placeholder="如 2个堵点" />
+        </view>
+
+        <!-- 预计工时 -->
+        <view class="form-group">
+          <text class="form-label">预计工时</text>
+          <input class="form-input" v-model="estimatedHours" placeholder="如 2小时" />
+        </view>
+
+        <!-- 验收要求 -->
+        <view class="form-group">
+          <text class="form-label">验收要求</text>
+          <input class="form-input" v-model="acceptanceReq" placeholder="如 正常走水" />
+        </view>
+
+        <!-- 验收方式 -->
+        <view class="form-group">
+          <text class="form-label">
+            <text class="text-red-500">*</text>验收方式
+          </text>
+          <picker mode="selector" :range="acceptTypes" :value="acceptTypeIndex" @change="onAcceptTypeChange">
+            <view class="form-select flex justify-between items-center">
+              <text :class="acceptType ? 'text-gray-800' : 'text-gray-400'">{{ acceptType || '请选择验收方式' }}</text>
+              <text class="text-gray-400 text-xs"></text>
+            </view>
+          </picker>
+        </view>
+
+        <!-- 故障图片 -->
+        <view class="form-group">
+          <text class="form-label">故障图片/视频</text>
+          <view class="border-2 border-dashed border-gray-200 rounded-xl p-6 text-center" @click="showToast('上传功能开发中')">
+            <text class="text-gray-300 text-2xl block mb-2">️</text>
+            <text class="text-gray-400 text-sm">点击上传图片或视频</text>
+            <text class="text-gray-300 text-xs block mt-1">支持 JPG、PNG、MP4 格式</text>
+          </view>
+        </view>
+      </view>
+
+      <!-- 提交按钮 -->
+      <view class="pt-4 pb-6">
+        <button class="w-full py-3 bg-blue-500 text-white rounded-xl font-medium" @click="submitTask">发布任务</button>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed } from 'vue'
+import { onLoad } from '@dcloudio/uni-app'
+import TopBar from '@/components/common/TopBar.vue'
+import { useProjectStore } from '@/stores/project'
+import { useTaskStore } from '@/stores/task'
+import { showToast, generateId, navigateTo } from '@/utils'
+import type { Task } from '@/types'
+
+const projectStore = useProjectStore()
+const taskStore = useTaskStore()
+
+const projectNames = computed(() => [...projectStore.projects.map(p => p.name), '+ 新增项目'])
+const projects = computed(() => projectStore.projects)
+
+const selectedProject = ref<typeof projects.value[0] | null>(null)
+const projectIndex = computed(() => {
+  if (!selectedProject.value) return -1
+  return projects.value.findIndex(p => p.id === selectedProject.value?.id)
+})
+
+const serviceDate = ref('')
+const serviceTime = ref('')
+const faultLocation = ref('')
+const faultTypes = ['堵塞', '满溢', '排水慢', '异味', '其他']
+const selectedFaultTypes = ref<string[]>([])
+const customerWords = ref('')
+const faultDesc = ref('')
+const serviceTypes = ['疏通', '清淤', '排查']
+const serviceType = ref('')
+const serviceTypeIndex = computed(() => serviceTypes.indexOf(serviceType.value))
+const facilityOptions = ['PVC', '直抽直排', '油污', '排污', '油污管', '化粪池']
+const facility = ref('')
+const facilityIndex = computed(() => facilityOptions.indexOf(facility.value))
+const serviceModes = ['应急', '月计划']
+const serviceMode = ref('')
+const serviceModeIndex = computed(() => serviceModes.indexOf(serviceMode.value))
+const vehicleOptions = ['疏通车', '吸污车', '高压清洗车']
+const vehicle = ref('')
+const vehicleIndex = computed(() => vehicleOptions.indexOf(vehicle.value))
+const waterOptions = ['有消防水', '无消防水']
+const waterSource = ref('')
+const waterIndex = computed(() => waterOptions.indexOf(waterSource.value))
+const constructionHelp = ref('')
+const estimatedQuantity = ref('')
+const estimatedHours = ref('')
+const acceptanceReq = ref('')
+const acceptTypes = ['签署验收计量单', '累计自动计费']
+const acceptType = ref('')
+const acceptTypeIndex = computed(() => acceptTypes.indexOf(acceptType.value))
+
+onLoad((query) => {
+  if (query?.projectId) {
+    const project = projectStore.projects.find(p => p.id === query.projectId)
+    if (project) {
+      selectedProject.value = project
+    }
+  }
+})
+
+function onProjectSelectChange(e: any) {
+  const index = e.detail.value
+  if (index >= projects.value.length) {
+    navigateTo('/pages/common/addProject')
+    return
+  }
+  selectedProject.value = projects.value[index]
+}
+
+function resetProjectSelection() {
+  selectedProject.value = null
+}
+
+function toggleFaultType(type: string) {
+  const idx = selectedFaultTypes.value.indexOf(type)
+  if (idx > -1) {
+    selectedFaultTypes.value.splice(idx, 1)
+  } else {
+    selectedFaultTypes.value.push(type)
+  }
+}
+
+function onServiceDateChange(e: any) {
+  serviceDate.value = e.detail.value
+}
+
+function onServiceTimeChange(e: any) {
+  serviceTime.value = e.detail.value
+}
+
+function onServiceTypeChange(e: any) {
+  serviceType.value = serviceTypes[e.detail.value]
+}
+
+function onFacilityChange(e: any) {
+  facility.value = facilityOptions[e.detail.value]
+}
+
+function onServiceModeChange(e: any) {
+  serviceMode.value = serviceModes[e.detail.value]
+}
+
+function onVehicleChange(e: any) {
+  vehicle.value = vehicleOptions[e.detail.value]
+}
+
+function onWaterChange(e: any) {
+  waterSource.value = waterOptions[e.detail.value]
+}
+
+function onAcceptTypeChange(e: any) {
+  acceptType.value = acceptTypes[e.detail.value]
+}
+
+function validateForm(): boolean {
+  if (!selectedProject.value) {
+    showToast('请选择项目')
+    return false
+  }
+  if (!serviceDate.value) {
+    showToast('请选择服务时间')
+    return false
+  }
+  if (!faultLocation.value.trim()) {
+    showToast('请输入故障位置')
+    return false
+  }
+  if (selectedFaultTypes.value.length === 0) {
+    showToast('请选择故障类型')
+    return false
+  }
+  if (!faultDesc.value.trim()) {
+    showToast('请输入故障描述')
+    return false
+  }
+  if (!serviceType.value) {
+    showToast('请选择施工类型')
+    return false
+  }
+  if (!facility.value) {
+    showToast('请选择施工设施')
+    return false
+  }
+  if (!serviceMode.value) {
+    showToast('请选择服务方式')
+    return false
+  }
+  if (!vehicle.value) {
+    showToast('请选择建议出车')
+    return false
+  }
+  if (!acceptType.value) {
+    showToast('请选择验收方式')
+    return false
+  }
+  return true
+}
+
+function submitTask() {
+  if (!validateForm()) return
+
+  const newTask: Task = {
+    id: generateId('t'),
+    projectId: selectedProject.value!.id,
+    projectName: selectedProject.value!.name,
+    type: serviceMode.value === '应急' ? 'emergency' : 'regular',
+    status: 'pending',
+    urgent: serviceMode.value === '应急' ? 'high' : 'normal',
+    createTime: new Date().toISOString(),
+    timeline: [
+      {
+        time: new Date().toISOString(),
+        action: '任务创建',
+        operator: '张销售',
+        role: 'sales'
+      }
+    ]
+  }
+
+  taskStore.tasks.push(newTask)
+  showToast({ title: '任务发布成功', icon: 'success' })
+  setTimeout(() => {
+    navigateTo('/pages/common/taskList')
+  }, 1500)
+}
+</script>
+
+<style scoped>
+.form-group {
+  margin-bottom: 16px;
+}
+
+.form-label {
+  display: block;
+  font-size: 14px;
+  color: #666;
+  margin-bottom: 8px;
+}
+
+.form-input {
+  width: 100%;
+  padding: 12px 16px;
+  border: 1px solid #e5e7eb;
+  border-radius: 8px;
+  font-size: 14px;
+  color: #333;
+  background: #fff;
+  box-sizing: border-box;
+}
+
+.form-select {
+  width: 100%;
+  padding: 12px 16px;
+  border: 1px solid #e5e7eb;
+  border-radius: 8px;
+  font-size: 14px;
+  color: #333;
+  background: #fff;
+  box-sizing: border-box;
+}
+
+.form-textarea {
+  width: 100%;
+  padding: 12px 16px;
+  border: 1px solid #e5e7eb;
+  border-radius: 8px;
+  font-size: 14px;
+  color: #333;
+  background: #fff;
+  min-height: 100px;
+  box-sizing: border-box;
+}
+</style>

+ 265 - 0
src/pages/common/taskDetail.vue

@@ -0,0 +1,265 @@
+<template>
+  <view class="app-container pb-20">
+    <TopBar :title="'任务详情'" show-back />
+    
+    <view v-if="taskStore.loading" class="py-20 text-center">
+      <text class="text-gray-400">加载中...</text>
+    </view>
+    
+    <view v-else-if="!task" class="px-4 py-6">
+      <EmptyState message="任务不存在" />
+    </view>
+    
+    <view v-else class="px-4 py-4 space-y-4">
+      <!-- 任务基本信息 -->
+      <uni-card :title="task.projectName" :is-shadow="true">
+        <view class="flex justify-between items-start mb-3">
+          <StatusTag :status="task.status" />
+          <text class="text-xs text-gray-400">{{ task.createTime }}</text>
+        </view>
+        
+        <uni-list border="false">
+          <uni-list-item 
+            title="地址" 
+            :note="task.address" 
+            icon="uniui-location-filled"
+          />
+          <uni-list-item 
+            title="客户" 
+            :note="task.customerName" 
+            icon="uniui-person-filled"
+          />
+          <uni-list-item 
+            title="电话" 
+            :note="task.customerPhone" 
+            icon="uniui-phone-filled"
+            @click="callPhone(task.customerPhone)"
+          />
+          <uni-list-item 
+            title="时间" 
+            :note="task.scheduleDate + ' ' + task.scheduleTime" 
+            icon="uniui-calendar-filled"
+          />
+          <uni-list-item 
+            title="车辆" 
+            :note="task.vehicleNo || '未分配'" 
+            icon="uniui-cart-filled"
+          />
+          <uni-list-item 
+            title="人员" 
+            :note="task.staffName || '未分配'" 
+            icon="uniui-personadd-filled"
+          />
+        </uni-list>
+      </uni-card>
+
+      <!-- 任务进度 -->
+      <uni-card title="任务进度" :is-shadow="true">
+        <view class="relative">
+          <view class="absolute left-4 top-0 bottom-0 w-0.5 bg-gray-200"></view>
+          <view class="space-y-4">
+            <view v-for="(step, index) in taskSteps" :key="index" class="flex items-center relative">
+              <view 
+                class="w-8 h-8 rounded-full flex items-center justify-center z-10 mr-4"
+                :class="step.completed ? 'bg-blue-500' : 'bg-gray-200'"
+              >
+                <text class="uni-icons text-white text-xs" :class="step.completed ? 'uniui-checkmarkempty' : ''"></text>
+              </view>
+              <view>
+                <text class="text-sm" :class="step.completed ? 'text-gray-800' : 'text-gray-400'">{{ step.name }}</text>
+                <text v-if="step.time" class="text-xs text-gray-400 block">{{ step.time }}</text>
+              </view>
+            </view>
+          </view>
+        </view>
+      </uni-card>
+
+      <!-- 施工照片 -->
+      <uni-card title="施工照片" icon="uniui-camera-filled" :is-shadow="true">
+        <ImageUploader v-model="taskImages" tip="上传施工过程照片" />
+      </uni-card>
+
+      <!-- 电子签名 -->
+      <uni-card v-if="canComplete" title="客户签名" icon="uniui-compose" :is-shadow="true">
+        <SignaturePad ref="signatureRef" title="请客户签字确认" @save="onSignatureSave" />
+      </uni-card>
+
+      <!-- 地图位置 -->
+      <uni-card title="项目位置" icon="uniui-location-filled" :is-shadow="true">
+        <MapView
+          :latitude="taskLatitude"
+          :longitude="taskLongitude"
+          :title="task.projectName"
+          :address="task.address"
+          :height="180"
+        />
+      </uni-card>
+
+      <!-- 操作按钮 -->
+      <uni-card :is-shadow="true">
+        <view class="grid grid-cols-2 gap-3">
+          <button 
+            v-if="canDepart"
+            class="py-3 bg-blue-500 text-white rounded-xl text-sm flex items-center justify-center"
+            @click="advanceStep(3)"
+          >
+            <text class="uni-icons uniui-cart-filled mr-1"></text>
+            确认出车
+          </button>
+          <button 
+            v-if="canArrive"
+            class="py-3 bg-blue-500 text-white rounded-xl text-sm flex items-center justify-center"
+            @click="advanceStep(4)"
+          >
+            <text class="uni-icons uniui-location-filled mr-1"></text>
+            到达现场
+          </button>
+          <button 
+            v-if="canStart"
+            class="py-3 bg-blue-500 text-white rounded-xl text-sm flex items-center justify-center"
+            @click="advanceStep(5)"
+          >
+            <text class="uni-icons uniui-gear-filled mr-1"></text>
+            开始施工
+          </button>
+          <button 
+            v-if="canComplete"
+            class="py-3 bg-green-500 text-white rounded-xl text-sm flex items-center justify-center"
+            @click="advanceStep(6)"
+          >
+            <text class="uni-icons uniui-checkmarkempty mr-1"></text>
+            完成任务
+          </button>
+          <button 
+            v-if="canUrge"
+            class="py-3 bg-orange-500 text-white rounded-xl text-sm flex items-center justify-center"
+            @click="urgeTask"
+          >
+            <text class="uni-icons uniui-notification-filled mr-1"></text>
+            催单
+          </button>
+          <button 
+            v-if="canCancel"
+            class="py-3 bg-red-500 text-white rounded-xl text-sm flex items-center justify-center"
+            @click="cancelTask"
+          >
+            <text class="uni-icons uniui-closeempty mr-1"></text>
+            取消任务
+          </button>
+        </view>
+      </uni-card>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted, onUnmounted } from 'vue'
+import { useTaskStore } from '../../stores/task'
+import TopBar from '../../components/common/TopBar.vue'
+import StatusTag from '../../components/common/StatusTag.vue'
+import EmptyState from '../../components/common/EmptyState.vue'
+import UniCard from '../../components/uni-card/uni-card.vue'
+import UniList from '../../components/uni-list/uni-list.vue'
+import UniListItem from '../../components/uni-list/uni-list-item.vue'
+import { advanceTaskStep, urgeTask as apiUrgeTask, cancelTask as apiCancelTask } from '../../api/task'
+
+import ImageUploader from '../../components/common/ImageUploader.vue'
+import SignaturePad from '../../components/common/SignaturePad.vue'
+import MapView from '../../components/common/MapView.vue'
+
+const taskStore = useTaskStore()
+const task = computed(() => taskStore.currentTask)
+const taskImages = ref<string[]>([])
+const signatureRef = ref()
+
+// 模拟位置数据(实际应从后端获取)
+const taskLatitude = computed(() => task.value?.location?.lat || 39.9)
+const taskLongitude = computed(() => task.value?.location?.lng || 116.4)
+
+const taskSteps = computed(() => {
+  if (!task.value) return []
+  const steps = [
+    { name: '任务创建', completed: true, time: task.value.createTime },
+    { name: '任务确认', completed: ['confirmed', 'scheduled', 'departed', 'arrived', 'constructing', 'completed'].includes(task.value.status), time: '' },
+    { name: '任务排班', completed: ['scheduled', 'departed', 'arrived', 'constructing', 'completed'].includes(task.value.status), time: '' },
+    { name: '出车', completed: ['departed', 'arrived', 'constructing', 'completed'].includes(task.value.status), time: task.value.departTime },
+    { name: '到达现场', completed: ['arrived', 'constructing', 'completed'].includes(task.value.status), time: task.value.arriveTime },
+    { name: '开始施工', completed: ['constructing', 'completed'].includes(task.value.status), time: task.value.constructTime },
+    { name: '任务完成', completed: task.value.status === 'completed', time: task.value.completeTime },
+  ]
+  return steps
+})
+
+const canDepart = computed(() => task.value?.status === 'scheduled')
+const canArrive = computed(() => task.value?.status === 'departed')
+const canStart = computed(() => task.value?.status === 'arrived')
+const canComplete = computed(() => task.value?.status === 'constructing')
+const canUrge = computed(() => ['pending', 'confirmed', 'scheduled'].includes(task.value?.status || ''))
+const canCancel = computed(() => ['pending', 'confirmed', 'scheduled'].includes(task.value?.status || ''))
+
+function onSignatureSave(imagePath: string) {
+  console.log('签名已保存:', imagePath)
+  // 这里可以上传到后端
+  uni.showToast({ title: '签名已保存', icon: 'success' })
+}
+
+function callPhone(phone: string) {
+  if (!phone) return
+  uni.makePhoneCall({ phoneNumber: phone })
+}
+
+async function advanceStep(step: number) {
+  if (!task.value) return
+  try {
+    await advanceTaskStep(Number(task.value.id), step)
+    uni.showToast({ title: '操作成功', icon: 'success' })
+    await taskStore.fetchTaskDetail(Number(task.value.id))
+  } catch (error) {
+    uni.showToast({ title: '操作失败', icon: 'none' })
+  }
+}
+
+async function urgeTask() {
+  if (!task.value) return
+  try {
+    await apiUrgeTask(Number(task.value.id))
+    uni.showToast({ title: '催单成功', icon: 'success' })
+  } catch (error) {
+    uni.showToast({ title: '催单失败', icon: 'none' })
+  }
+}
+
+async function cancelTask() {
+  if (!task.value) return
+  uni.showModal({
+    title: '确认取消',
+    content: '确定要取消此任务吗?',
+    success: async (res) => {
+      if (res.confirm) {
+        try {
+          await apiCancelTask(Number(task.value!.id))
+          uni.showToast({ title: '取消成功', icon: 'success' })
+          await taskStore.fetchTaskDetail(Number(task.value!.id))
+        } catch (error) {
+          uni.showToast({ title: '取消失败', icon: 'none' })
+        }
+      }
+    },
+  })
+}
+
+onMounted(() => {
+  if (!task.value) {
+    const pages = getCurrentPages()
+    const currentPage = pages[pages.length - 1]
+    const taskId = currentPage.options?.id
+    if (taskId) {
+      taskStore.fetchTaskDetail(Number(taskId))
+    }
+  }
+})
+
+onUnmounted(() => {
+  taskStore.currentTask = null
+})
+</script>

+ 163 - 0
src/pages/common/taskList.vue

@@ -0,0 +1,163 @@
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="任务列表" />
+    
+    <!-- 筛选标签 - 使用 uni-segmented-control -->
+    <uni-segmented-control 
+      :current="currentFilterIndex" 
+      :values="filterLabels"
+      style-type="text"
+      active-color="#4f8ef7"
+      @clickItem="onFilterChange"
+    />
+
+    <!-- 任务列表 -->
+    <view class="mt-2">
+      <view v-if="taskStore.loading" class="py-10 text-center">
+        <text class="text-gray-400">加载中...</text>
+      </view>
+      
+      <view v-else-if="filteredTasks.length === 0">
+        <EmptyState message="暂无任务" />
+      </view>
+      
+      <uni-list v-else border>
+        <uni-swipe-action 
+          v-for="task in filteredTasks" 
+          :key="task.id"
+          :actions="getSwipeActions(task)"
+          @click="(action: any) => onSwipeAction(task, action)"
+        >
+          <uni-list-item 
+            :title="task.name"
+            :note="task.address"
+            :right-text="formatStatus(task.status)"
+            show-arrow
+            @click="goToDetail(task.id)"
+          >
+            <template #header>
+              <view class="w-10 h-10 rounded-xl flex items-center justify-center mr-3" :class="getStatusBgClass(task.status)">
+                <text class="uni-icons text-lg" :class="getStatusIconClass(task.status)"></text>
+              </view>
+            </template>
+          </uni-list-item>
+        </uni-swipe-action>
+      </uni-list>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue'
+import { useTaskStore } from '../../stores/task'
+import TopBar from '../../components/common/TopBar.vue'
+import EmptyState from '../../components/common/EmptyState.vue'
+import UniList from '../../components/uni-list/uni-list.vue'
+import UniListItem from '../../components/uni-list/uni-list-item.vue'
+import UniSwipeAction from '../../components/uni-swipe-action/uni-swipe-action.vue'
+import UniSegmentedControl from '../../components/uni-segmented-control/uni-segmented-control.vue'
+
+const taskStore = useTaskStore()
+const currentFilterIndex = ref(0)
+
+const filterStatuses = [
+  { value: 'all', label: '全部' },
+  { value: 'pending', label: '待确认' },
+  { value: 'confirmed', label: '待排班' },
+  { value: 'scheduled', label: '待出车' },
+  { value: 'constructing', label: '施工中' },
+  { value: 'completed', label: '已完成' },
+]
+
+const filterLabels = filterStatuses.map(s => s.label)
+
+const filteredTasks = computed(() => {
+  const filterValue = filterStatuses[currentFilterIndex.value].value
+  if (filterValue === 'all') return taskStore.tasks
+  return taskStore.tasks.filter(t => t.status === filterValue)
+})
+
+function formatStatus(status: string) {
+  const map: Record<string, string> = {
+    pending: '待确认',
+    confirmed: '待排班',
+    scheduled: '待出车',
+    constructing: '施工中',
+    completed: '已完成',
+    cancelled: '已取消',
+  }
+  return map[status] || status
+}
+
+function getStatusBgClass(status: string) {
+  const map: Record<string, string> = {
+    pending: 'bg-yellow-50',
+    confirmed: 'bg-blue-50',
+    scheduled: 'bg-purple-50',
+    constructing: 'bg-orange-50',
+    completed: 'bg-green-50',
+    cancelled: 'bg-gray-50',
+  }
+  return map[status] || 'bg-gray-50'
+}
+
+function getStatusIconClass(status: string) {
+  const map: Record<string, string> = {
+    pending: 'uniui-compose text-yellow-500',
+    confirmed: 'uniui-flag text-blue-500',
+    scheduled: 'uniui-cart-filled text-purple-500',
+    constructing: 'uniui-gear-filled text-orange-500',
+    completed: 'uniui-checkmarkempty text-green-500',
+    cancelled: 'uniui-close text-gray-400',
+  }
+  return map[status] || 'uniui-flag text-gray-400'
+}
+
+function getSwipeActions(task: any) {
+  const actions = []
+  if (task.status === 'pending') {
+    actions.push({ text: '催办', color: '#ff9500', key: 'urge' })
+  }
+  if (task.status !== 'completed' && task.status !== 'cancelled') {
+    actions.push({ text: '取消', color: '#ff3b30', key: 'cancel' })
+  }
+  return actions
+}
+
+function onFilterChange(index: number) {
+  currentFilterIndex.value = index
+}
+
+function onSwipeAction(task: any, action: { key: string; text: string }) {
+  if (action.key === 'cancel') {
+    uni.showModal({
+      title: '确认取消',
+      content: '取消后无法恢复,是否继续?',
+      success: (res) => {
+        if (res.confirm) {
+          taskStore.cancelTask(task.id)
+          uni.showToast({ title: '已取消', icon: 'success' })
+        }
+      }
+    })
+  } else if (action.key === 'urge') {
+    taskStore.urgeTask(task.id)
+    uni.showToast({ title: '已催办', icon: 'success' })
+  }
+}
+
+function goToDetail(taskId: string) {
+  taskStore.setCurrentTask(taskId)
+  uni.navigateTo({ url: '/pages/common/taskDetail' })
+}
+
+onMounted(() => {
+  taskStore.fetchTasks()
+})
+
+// 下拉刷新
+onPullDownRefresh(async () => {
+  await taskStore.fetchTasks()
+  uni.stopPullDownRefresh()
+})
+</script>

+ 86 - 0
src/pages/construction/basicInfo.vue

@@ -0,0 +1,86 @@
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="个人信息" show-back />
+
+    <view class="px-4 py-4 space-y-4">
+      <!-- 头像区域 -->
+      <uni-card :is-shadow="true">
+        <view class="flex items-center py-2">
+          <view class="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mr-4">
+            <text class="uni-icons uniui-person-filled text-blue-500 text-2xl"></text>
+          </view>
+          <view>
+            <text class="text-lg font-semibold text-gray-800 block">{{ form.name }}</text>
+            <text class="text-sm text-gray-500">{{ form.team }} · 施工员</text>
+          </view>
+        </view>
+      </uni-card>
+
+      <!-- 可编辑信息 -->
+      <uni-card title="个人信息" icon="uniui-person-filled" :is-shadow="true">
+        <view class="space-y-4 pt-2">
+          <view>
+            <text class="text-sm text-gray-600 block mb-2">姓名</text>
+            <input
+              v-model="form.name"
+              type="text"
+              class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm text-gray-800 bg-white"
+            />
+          </view>
+          <view>
+            <text class="text-sm text-gray-600 block mb-2">工号</text>
+            <input
+              v-model="form.id"
+              type="text"
+              disabled
+              class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm text-gray-400 bg-gray-50"
+            />
+          </view>
+          <view>
+            <text class="text-sm text-gray-600 block mb-2">班组</text>
+            <input
+              v-model="form.team"
+              type="text"
+              class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm text-gray-800 bg-white"
+            />
+          </view>
+          <view>
+            <text class="text-sm text-gray-600 block mb-2">联系电话</text>
+            <input
+              v-model="form.phone"
+              type="tel"
+              class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm text-gray-800 bg-white"
+            />
+          </view>
+        </view>
+
+        <view class="pt-4">
+          <button class="w-full py-3 bg-blue-500 text-white rounded-xl font-medium" @click="saveInfo">
+            <text class="uni-icons uniui-checkmarkempty mr-1"></text>
+            保存修改
+          </button>
+        </view>
+      </uni-card>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed } from 'vue'
+import TopBar from '../../components/common/TopBar.vue'
+import UniCard from '../../components/uni-card/uni-card.vue'
+import { useAuthStore } from '../../stores/auth'
+
+const authStore = useAuthStore()
+
+const form = ref({
+  name: authStore.userName || '张师傅',
+  id: authStore.userInfo?.id || 'u3',
+  team: authStore.userInfo?.team || '一班组',
+  phone: authStore.userInfo?.phone || '13800138003',
+})
+
+function saveInfo() {
+  uni.showToast({ title: '保存成功', icon: 'success' })
+}
+</script>

+ 85 - 0
src/pages/construction/changePassword.vue

@@ -0,0 +1,85 @@
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="修改密码" show-back />
+
+    <view class="px-4 py-4">
+      <uni-card title="密码修改" icon="uniui-locked-filled" :is-shadow="true">
+        <view class="space-y-4 pt-2">
+          <view>
+            <text class="text-sm text-gray-600 block mb-2">
+              <text class="text-red-500">*</text> 当前密码
+            </text>
+            <input
+              v-model="form.oldPassword"
+              type="password"
+              placeholder="请输入当前密码"
+              class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm text-gray-800 bg-white"
+            />
+          </view>
+          <view>
+            <text class="text-sm text-gray-600 block mb-2">
+              <text class="text-red-500">*</text> 新密码
+            </text>
+            <input
+              v-model="form.newPassword"
+              type="password"
+              placeholder="请输入新密码"
+              class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm text-gray-800 bg-white"
+            />
+          </view>
+          <view>
+            <text class="text-sm text-gray-600 block mb-2">
+              <text class="text-red-500">*</text> 确认新密码
+            </text>
+            <input
+              v-model="form.confirmPassword"
+              type="password"
+              placeholder="请再次输入新密码"
+              class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm text-gray-800 bg-white"
+            />
+          </view>
+        </view>
+
+        <view class="pt-4">
+          <button class="w-full py-3 bg-blue-500 text-white rounded-xl font-medium" @click="changePassword">
+            <text class="uni-icons uniui-checkmarkempty mr-1"></text>
+            确认修改
+          </button>
+        </view>
+      </uni-card>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+import TopBar from '../../components/common/TopBar.vue'
+import UniCard from '../../components/uni-card/uni-card.vue'
+
+const form = ref({
+  oldPassword: '',
+  newPassword: '',
+  confirmPassword: '',
+})
+
+function changePassword() {
+  if (!form.value.oldPassword) {
+    uni.showToast({ title: '请输入当前密码', icon: 'none' })
+    return
+  }
+  if (!form.value.newPassword) {
+    uni.showToast({ title: '请输入新密码', icon: 'none' })
+    return
+  }
+  if (form.value.newPassword.length < 6 || form.value.newPassword.length > 20) {
+    uni.showToast({ title: '密码长度应为6-20位', icon: 'none' })
+    return
+  }
+  if (form.value.newPassword !== form.value.confirmPassword) {
+    uni.showToast({ title: '两次输入的密码不一致', icon: 'none' })
+    return
+  }
+  uni.showToast({ title: '密码修改成功', icon: 'success' })
+  form.value = { oldPassword: '', newPassword: '', confirmPassword: '' }
+}
+</script>

+ 143 - 0
src/pages/construction/dailyTask.vue

@@ -0,0 +1,143 @@
+<template>
+  <view class="app-container pb-24">
+    <TopBar title="每日任务" show-back />
+    
+    <view class="px-4 py-4 space-y-4">
+      <!-- 今日任务概览 -->
+      <uni-card title="今日概览" icon="uniui-calendar-filled">
+        <view class="grid grid-cols-2 gap-3">
+          <view class="bg-blue-50 rounded-xl p-4 text-center">
+            <text class="text-2xl font-bold text-blue-600 block">{{ todayTasks.length }}</text>
+            <text class="text-xs text-gray-500 mt-1 block">今日任务</text>
+          </view>
+          <view class="bg-green-50 rounded-xl p-4 text-center">
+            <text class="text-2xl font-bold text-green-500 block">{{ completedTasks }}</text>
+            <text class="text-xs text-gray-500 mt-1 block">已完成</text>
+          </view>
+        </view>
+      </uni-card>
+
+      <!-- 车辆检查 -->
+      <uni-card 
+        title="车辆检查" 
+        icon="uniui-cart-filled"
+        :extra="vehicleCheckStatus ? '已完成' : '待检查'"
+        :extra-color="vehicleCheckStatus ? '#34c759' : '#ff9500'"
+      >
+        <view v-if="!vehicleCheckStatus">
+          <uni-list border>
+            <uni-list-item 
+              v-for="item in checkItems" 
+              :key="item.key"
+              :title="item.label"
+              clickable
+              @click="toggleCheck(item.key)"
+            >
+              <template #header>
+                <view 
+                  class="w-6 h-6 rounded-full border-2 flex items-center justify-center mr-3"
+                  :class="checks[item.key] ? 'bg-blue-500 border-blue-500' : 'border-gray-300'"
+                >
+                  <text v-if="checks[item.key]" class="uni-icons uniui-checkmarkempty text-white text-xs"></text>
+                </view>
+              </template>
+            </uni-list-item>
+          </uni-list>
+        </view>
+        
+        <view v-else class="text-center py-8">
+          <view class="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-3">
+            <text class="uni-icons uniui-checkmarkempty text-green-500 text-2xl"></text>
+          </view>
+          <text class="text-sm text-gray-600 font-medium">今日车辆检查已完成</text>
+          <text class="text-xs text-gray-400 mt-1 block">{{ today }}</text>
+        </view>
+      </uni-card>
+
+      <!-- 今日任务列表 -->
+      <uni-card title="今日任务列表" icon="uniui-flag-filled">
+        <view v-if="taskStore.loading" class="py-10 text-center">
+          <text class="text-gray-400">加载中...</text>
+        </view>
+        <view v-else-if="todayTasks.length === 0">
+          <EmptyState message="今日无任务" />
+        </view>
+        <view v-else class="space-y-3">
+          <TaskCard 
+            v-for="task in todayTasks" 
+            :key="task.id"
+            :task="task"
+            @click="goToDetail"
+          />
+        </view>
+      </uni-card>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue'
+import { useTaskStore } from '../../stores/task'
+import TopBar from '../../components/common/TopBar.vue'
+import TaskCard from '../../components/task/TaskCard.vue'
+import EmptyState from '../../components/common/EmptyState.vue'
+import UniCard from '../../components/uni-card/uni-card.vue'
+import UniList from '../../components/uni-list/uni-list.vue'
+import UniListItem from '../../components/uni-list/uni-list-item.vue'
+
+const taskStore = useTaskStore()
+
+const today = new Date().toISOString().split('T')[0]
+const storageKey = `vehicle_check_${today}`
+
+const checkItems = [
+  { key: 'exterior', label: '车辆外观检查' },
+  { key: 'tire', label: '轮胎气压检查' },
+  { key: 'fluid', label: '油液检查' },
+  { key: 'equipment', label: '设备功能检查' },
+  { key: 'safety', label: '安全设备检查' },
+]
+
+const checks = ref<Record<string, boolean>>({
+  exterior: false,
+  tire: false,
+  fluid: false,
+  equipment: false,
+  safety: false,
+})
+
+const vehicleCheckStatus = computed(() => {
+  return Object.values(checks.value).every(v => v)
+})
+
+const completedTasks = computed(() => {
+  return todayTasks.value.filter(t => t.status === 'completed').length
+})
+
+const todayTasks = computed(() => {
+  return taskStore.tasks.filter(t => t.scheduleDate === today)
+})
+
+function toggleCheck(type: string) {
+  checks.value[type] = !checks.value[type]
+  uni.setStorageSync(storageKey, JSON.stringify(checks.value))
+}
+
+function goToDetail(taskId: string) {
+  taskStore.setCurrentTask(taskId)
+  uni.navigateTo({ url: '/pages/construction/taskDetail' })
+}
+
+onMounted(() => {
+  const saved = uni.getStorageSync(storageKey)
+  if (saved) {
+    try {
+      checks.value = JSON.parse(saved)
+    } catch {
+      // 解析失败则使用默认值
+    }
+  }
+  
+  taskStore.fetchTasks()
+})
+</script>

+ 139 - 0
src/pages/construction/helpCenter.vue

@@ -0,0 +1,139 @@
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="帮助中心" show-back />
+    
+    <view class="px-4 py-4 space-y-4">
+      <!-- 常见问题 -->
+      <uni-card title="常见问题" icon="uniui-help-filled">
+        <view class="space-y-0">
+          <view 
+            v-for="(faq, index) in faqs" 
+            :key="index"
+            class="border-b border-gray-100 py-3 last:border-0"
+            @click="toggleFaq(index)"
+          >
+            <view class="flex justify-between items-center">
+              <text class="text-sm text-gray-800">{{ faq.question }}</text>
+              <text class="uni-icons text-gray-400" :class="faq.open ? 'uniui-arrowup' : 'uniui-arrowdown'"></text>
+            </view>
+            <view v-if="faq.open" class="mt-2 bg-gray-50 rounded-lg p-3">
+              <text class="text-xs text-gray-500 leading-relaxed">{{ faq.answer }}</text>
+            </view>
+          </view>
+        </view>
+      </uni-card>
+
+      <!-- 联系方式 -->
+      <uni-card title="联系方式" icon="uniui-phone-filled">
+        <uni-list border>
+          <uni-list-item 
+            title="调度中心" 
+            note="400-888-9999"
+          >
+            <template #header>
+              <text class="uni-icons uniui-phone-filled text-blue-500 mr-3"></text>
+            </template>
+          </uni-list-item>
+          <uni-list-item 
+            title="邮箱" 
+            note="support@aquagreen.com"
+          >
+            <template #header>
+              <text class="uni-icons uniui-email-filled text-blue-500 mr-3"></text>
+            </template>
+          </uni-list-item>
+          <uni-list-item 
+            title="服务时间" 
+            note="7x24小时"
+          >
+            <template #header>
+              <text class="uni-icons uniui-calendar-filled text-blue-500 mr-3"></text>
+            </template>
+          </uni-list-item>
+        </uni-list>
+      </uni-card>
+
+      <!-- 操作指南 -->
+      <uni-card title="操作指南" icon="uniui-compose">
+        <uni-list border>
+          <uni-list-item 
+            title="任务处理流程" 
+            show-arrow
+            @click="showGuide('task')"
+          >
+            <template #header>
+              <text class="uni-icons uniui-flag-filled text-blue-500 mr-3"></text>
+            </template>
+          </uni-list-item>
+          <uni-list-item 
+            title="车辆检查规范" 
+            show-arrow
+            @click="showGuide('vehicle')"
+          >
+            <template #header>
+              <text class="uni-icons uniui-cart-filled text-blue-500 mr-3"></text>
+            </template>
+          </uni-list-item>
+          <uni-list-item 
+            title="安全操作规范" 
+            show-arrow
+            @click="showGuide('safety')"
+          >
+            <template #header>
+              <text class="uni-icons uniui-locked-filled text-blue-500 mr-3"></text>
+            </template>
+          </uni-list-item>
+        </uni-list>
+      </uni-card>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+import TopBar from '../../components/common/TopBar.vue'
+import UniCard from '../../components/uni-card/uni-card.vue'
+import UniList from '../../components/uni-list/uni-list.vue'
+import UniListItem from '../../components/uni-list/uni-list-item.vue'
+
+const faqs = ref([
+  {
+    question: '如何查看今日任务?',
+    answer: '在首页点击"每日任务"卡片或在底部导航切换到"任务"页面,即可查看今日所有任务。',
+    open: false,
+  },
+  {
+    question: '车辆检查未完成怎么办?',
+    answer: '请在每日出车前完成所有检查项目,如有异常请及时上报调度中心。',
+    open: false,
+  },
+  {
+    question: '如何上报任务异常?',
+    answer: '在任务详情页面点击"异常上报"按钮,填写异常描述并提交。',
+    open: false,
+  },
+  {
+    question: '任务状态如何推进?',
+    answer: '按照SOP流程,完成任务后点击"确认完成"按钮,系统会自动推进到下一步。',
+    open: false,
+  },
+])
+
+function toggleFaq(index: number) {
+  faqs.value[index].open = !faqs.value[index].open
+}
+
+function showGuide(type: string) {
+  const guides: Record<string, string> = {
+    task: '任务处理流程:\n1. 查看任务详情\n2. 确认出车\n3. 到达现场\n4. 开始施工\n5. 完成任务\n6. 确认完成',
+    vehicle: '车辆检查规范:\n1. 检查车辆外观\n2. 检查轮胎气压\n3. 检查油液液位\n4. 检查设备功能\n5. 检查安全设备',
+    safety: '安全操作规范:\n1. 佩戴安全装备\n2. 遵守交通规则\n3. 现场安全警示\n4. 设备安全操作\n5. 紧急情况处理',
+  }
+  
+  uni.showModal({
+    title: '操作指南',
+    content: guides[type],
+    showCancel: false,
+  })
+}
+</script>

+ 55 - 0
src/pages/construction/historyTask.vue

@@ -0,0 +1,55 @@
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="历史任务" show-back />
+    
+    <view class="px-4 mt-3">
+      <view v-if="taskStore.loading" class="py-10 text-center">
+        <text class="text-gray-400">加载中...</text>
+      </view>
+      
+      <view v-else-if="completedTasks.length === 0">
+        <EmptyState message="暂无历史任务" />
+      </view>
+      
+      <uni-list v-else border>
+        <uni-list-item 
+          v-for="task in completedTasks" 
+          :key="task.id"
+          :title="task.projectName"
+          :note="task.address"
+          right-text="已完成"
+          show-arrow
+          @click="goToDetail(task.id)"
+        >
+          <template #header>
+            <view class="w-10 h-10 rounded-xl bg-green-50 flex items-center justify-center mr-3">
+              <text class="uni-icons uniui-checkmarkempty text-green-500"></text>
+            </view>
+          </template>
+        </uni-list-item>
+      </uni-list>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { computed, onMounted } from 'vue'
+import { useTaskStore } from '../../stores/task'
+import TopBar from '../../components/common/TopBar.vue'
+import EmptyState from '../../components/common/EmptyState.vue'
+import UniList from '../../components/uni-list/uni-list.vue'
+import UniListItem from '../../components/uni-list/uni-list-item.vue'
+
+const taskStore = useTaskStore()
+
+const completedTasks = computed(() => taskStore.tasks.filter(t => t.status === 'completed'))
+
+function goToDetail(taskId: string) {
+  taskStore.setCurrentTask(taskId)
+  uni.navigateTo({ url: '/pages/construction/taskDetail' })
+}
+
+onMounted(() => {
+  taskStore.fetchTasks()
+})
+</script>

+ 199 - 0
src/pages/construction/home.vue

@@ -0,0 +1,199 @@
+<template>
+  <view class="app-container">
+    <!-- 顶部区域 -->
+    <view class="relative overflow-hidden">
+      <view class="absolute inset-0 bg-gradient-to-br from-emerald-500 to-teal-600"></view>
+      <view class="absolute -top-20 -right-20 w-60 h-60 bg-white opacity-5 rounded-full"></view>
+      <view class="absolute -bottom-10 -left-10 w-40 h-40 bg-white opacity-5 rounded-full"></view>
+      
+      <view class="relative px-5 pt-12 pb-8">
+        <view class="flex items-center justify-between">
+          <view class="flex items-center">
+            <view class="w-12 h-12 bg-white/20 backdrop-blur rounded-2xl flex items-center justify-center mr-3">
+              <text class="uni-icons uniui-person-filled text-white text-xl"></text>
+            </view>
+            <view>
+              <text class="text-white/70 text-xs block">工程一班</text>
+              <text class="text-white text-lg font-bold">张师傅</text>
+            </view>
+          </view>
+          <button class="w-10 h-10 bg-white/15 backdrop-blur rounded-full flex items-center justify-center relative" @click="goToNotification">
+            <text class="uni-icons uniui-notification-filled text-white text-lg"></text>
+          </button>
+        </view>
+      </view>
+    </view>
+
+    <!-- 主内容区域 -->
+    <view class="px-4 -mt-5 space-y-4 pb-24">
+      <!-- 统计卡片 -->
+      <uni-card title="工作统计" :is-shadow="true">
+        <view class="grid grid-cols-3 gap-3">
+          <view class="bg-emerald-50 rounded-2xl p-4 text-center" @click="goToHistoryTask">
+            <text class="stat-number text-emerald-600 block">{{ completedCount }}</text>
+            <text class="text-desc mt-1 block">已完成</text>
+          </view>
+          <view class="bg-blue-50 rounded-2xl p-4 text-center" @click="goToTaskList">
+            <text class="stat-number text-blue-500 block">{{ currentTaskCount }}</text>
+            <text class="text-desc mt-1 block">进行中</text>
+          </view>
+          <view class="bg-purple-50 rounded-2xl p-4 text-center" @click="goToTomorrowTask">
+            <text class="stat-number text-purple-500 block">{{ tomorrowCount }}</text>
+            <text class="text-desc mt-1 block">明日任务</text>
+          </view>
+        </view>
+      </uni-card>
+
+      <!-- 每日任务入口 -->
+      <uni-card :is-shadow="true" @click="goToDailyTask">
+        <view class="flex items-center justify-between cursor-pointer">
+          <view class="flex items-center">
+            <view class="w-12 h-12 bg-emerald-100 rounded-2xl flex items-center justify-center mr-4">
+              <text class="uni-icons uniui-gear-filled text-emerald-500 text-2xl"></text>
+            </view>
+            <view>
+              <text class="text-base font-bold text-gray-800 block">每日任务</text>
+              <text class="text-sm text-gray-400 mt-0.5 block">今日检查待完成</text>
+            </view>
+          </view>
+          <view class="flex items-center">
+            <view class="w-2.5 h-2.5 bg-red-400 rounded-full mr-2"></view>
+            <text class="uni-icons uniui-arrowright text-gray-300 text-lg"></text>
+          </view>
+        </view>
+      </uni-card>
+
+      <!-- 快捷功能 -->
+      <uni-card title="快捷功能" :is-shadow="true">
+        <view class="grid grid-cols-4 gap-4">
+          <view class="flex flex-col items-center" @click="goToTaskList">
+            <view class="w-14 h-14 bg-blue-50 rounded-2xl flex items-center justify-center mb-2 transition-transform active:scale-95">
+              <text class="uni-icons uniui-flag-filled text-blue-500 text-2xl"></text>
+            </view>
+            <text class="text-xs text-gray-600 font-medium">我的任务</text>
+          </view>
+          <view class="flex flex-col items-center" @click="goToDailyTask">
+            <view class="w-14 h-14 bg-emerald-50 rounded-2xl flex items-center justify-center mb-2 transition-transform active:scale-95">
+              <text class="uni-icons uniui-gear-filled text-emerald-500 text-2xl"></text>
+            </view>
+            <text class="text-xs text-gray-600 font-medium">车辆检查</text>
+          </view>
+          <view class="flex flex-col items-center" @click="goToHistoryTask">
+            <view class="w-14 h-14 bg-purple-50 rounded-2xl flex items-center justify-center mb-2 transition-transform active:scale-95">
+              <text class="uni-icons uniui-calendar-filled text-purple-500 text-2xl"></text>
+            </view>
+            <text class="text-xs text-gray-600 font-medium">历史任务</text>
+          </view>
+          <view class="flex flex-col items-center" @click="goToHelpCenter">
+            <view class="w-14 h-14 bg-orange-50 rounded-2xl flex items-center justify-center mb-2 transition-transform active:scale-95">
+              <text class="uni-icons uniui-help-filled text-orange-500 text-2xl"></text>
+            </view>
+            <text class="text-xs text-gray-600 font-medium">帮助中心</text>
+          </view>
+        </view>
+      </uni-card>
+
+      <!-- 通知公告 -->
+      <uni-card 
+        title="通知公告" 
+        extra="查看全部" 
+        :is-shadow="true"
+        @click-extra="goToNoticeList"
+      >
+        <uni-list border="false">
+          <uni-list-item 
+            v-for="(notice, index) in notices" 
+            :key="index"
+            :title="notice.title"
+            :note="notice.date"
+            show-arrow
+            @click="goToNoticeDetail"
+          >
+            <template #header>
+              <view v-if="notice.important" class="px-2 py-0.5 bg-red-50 text-red-500 text-[10px] rounded-lg font-bold mr-2 flex-shrink-0">
+                重要
+              </view>
+            </template>
+          </uni-list-item>
+        </uni-list>
+      </uni-card>
+    </view>
+
+    <!-- 底部导航 -->
+    <BottomNav :items="navItems" :currentPath="currentPath" @navigate="onNavChange" />
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue'
+import { useTaskStore } from '../../stores/task'
+import BottomNav from '../../components/common/BottomNav.vue'
+import UniCard from '../../components/uni-card/uni-card.vue'
+import UniList from '../../components/uni-list/uni-list.vue'
+import UniListItem from '../../components/uni-list/uni-list-item.vue'
+
+const taskStore = useTaskStore()
+const currentPath = ref('/pages/construction/home')
+
+const completedCount = computed(() => taskStore.tasks.filter(t => t.status === 'completed').length)
+const currentTaskCount = computed(() => taskStore.tasks.filter(t => ['scheduled', 'departed', 'arrived', 'constructing'].includes(t.status)).length)
+const tomorrowCount = computed(() => 2)
+
+const navItems = [
+  { name: '首页', path: '/pages/construction/home', icon: 'uniui-home-filled' },
+  { name: '任务', path: '/pages/construction/taskList', icon: 'uniui-flag-filled' },
+  { name: '我的', path: '/pages/construction/myPage', icon: 'uniui-person-filled' },
+]
+
+const notices = [
+  { title: '关于规范施工现场安全管理的通知', date: '2026-04-20', important: true },
+  { title: '下周安全培训会议安排', date: '2026-04-18', important: false },
+]
+
+function onNavChange(path: string) {
+  uni.reLaunch({ url: path })
+}
+
+function goToTaskList() {
+  uni.navigateTo({ url: '/pages/construction/taskList' })
+}
+
+function goToDailyTask() {
+  uni.navigateTo({ url: '/pages/construction/dailyTask' })
+}
+
+function goToHistoryTask() {
+  uni.navigateTo({ url: '/pages/construction/historyTask' })
+}
+
+function goToTomorrowTask() {
+  uni.navigateTo({ url: '/pages/construction/tomorrowTask' })
+}
+
+function goToHelpCenter() {
+  uni.navigateTo({ url: '/pages/construction/helpCenter' })
+}
+
+function goToNotification() {
+  uni.navigateTo({ url: '/pages/construction/notification' })
+}
+
+function goToNoticeList() {
+  uni.navigateTo({ url: '/pages/construction/noticeList' })
+}
+
+function goToNoticeDetail() {
+  uni.navigateTo({ url: '/pages/construction/noticeDetail' })
+}
+
+onMounted(() => {
+  taskStore.fetchTasks()
+})
+</script>
+
+<style scoped>
+.stat-number {
+  font-size: 24px;
+  font-weight: 800;
+}
+</style>

+ 149 - 0
src/pages/construction/myPage.vue

@@ -0,0 +1,149 @@
+<template>
+  <view class="app-container">
+    <!-- 顶部用户信息区域 -->
+    <view class="bg-gradient-to-br from-blue-900 to-blue-800 text-white pt-10 pb-6 px-5">
+      <view class="flex items-center gap-4">
+        <view class="w-14 h-14 bg-white/20 rounded-full flex items-center justify-center text-2xl">
+          <text></text>
+        </view>
+        <view>
+          <text class="text-lg font-bold block">{{ userName }}</text>
+          <text class="text-blue-200 text-xs">{{ userTeam }} · 工号 {{ userId }}</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 功能列表 -->
+    <view class="px-4 -mt-3 space-y-4 pb-4">
+      <!-- 基本信息卡片 -->
+      <view class="bg-white rounded-xl p-4 shadow-sm">
+        <view class="flex justify-between items-center py-2 border-b border-gray-100">
+          <view class="flex items-center">
+            <text class="text-blue-400 mr-3 w-5 text-center"></text>
+            <text class="text-gray-500 text-sm">班组</text>
+          </view>
+          <text class="text-sm">{{ userTeam }}</text>
+        </view>
+        <view class="flex justify-between items-center py-2 border-b border-gray-100">
+          <view class="flex items-center">
+            <text class="text-blue-400 mr-3 w-5 text-center">🆔</text>
+            <text class="text-gray-500 text-sm">职位</text>
+          </view>
+          <text class="text-sm">施工员</text>
+        </view>
+        <view class="flex justify-between items-center py-2">
+          <view class="flex items-center">
+            <text class="text-blue-400 mr-3 w-5 text-center"></text>
+            <text class="text-gray-500 text-sm">电话</text>
+          </view>
+          <text class="text-sm">{{ userPhone }}</text>
+        </view>
+      </view>
+
+      <!-- 功能入口卡片 -->
+      <view class="bg-white rounded-xl p-4 shadow-sm">
+        <view class="flex justify-between items-center py-3 border-b border-gray-100 cursor-pointer" @click="goToBasicInfo">
+          <view class="flex items-center">
+            <text class="text-gray-400 mr-3 w-5 text-center"></text>
+            <text class="text-sm text-gray-700">基本信息</text>
+          </view>
+          <text class="text-gray-300 text-xs">></text>
+        </view>
+        <view class="flex justify-between items-center py-3 border-b border-gray-100 cursor-pointer" @click="goToChangePassword">
+          <view class="flex items-center">
+            <text class="text-gray-400 mr-3 w-5 text-center"></text>
+            <text class="text-sm text-gray-700">修改密码</text>
+          </view>
+          <text class="text-gray-300 text-xs">></text>
+        </view>
+        <view class="flex justify-between items-center py-3 border-b border-gray-100 cursor-pointer" @click="goToNotification">
+          <view class="flex items-center">
+            <text class="text-gray-400 mr-3 w-5 text-center"></text>
+            <text class="text-sm text-gray-700">消息通知</text>
+          </view>
+          <view class="flex items-center">
+            <view v-if="unreadCount > 0" class="w-2 h-2 bg-red-500 rounded-full mr-2"></view>
+            <text class="text-gray-300 text-xs">></text>
+          </view>
+        </view>
+        <view class="flex justify-between items-center py-3 cursor-pointer" @click="goToHelpCenter">
+          <view class="flex items-center">
+            <text class="text-gray-400 mr-3 w-5 text-center"></text>
+            <text class="text-sm text-gray-700">帮助中心</text>
+          </view>
+          <text class="text-gray-300 text-xs">></text>
+        </view>
+      </view>
+
+      <!-- 退出登录按钮 -->
+      <button class="w-full py-3 mt-4 bg-red-500 text-white rounded-xl font-medium" @click="logout">
+        退出登录
+      </button>
+    </view>
+
+    <!-- 底部导航 -->
+    <BottomNav :items="navItems" :currentPath="currentPath" @navigate="onNavChange" />
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed } from 'vue'
+import BottomNav from '../../components/common/BottomNav.vue'
+import { useAuthStore } from '../../stores/auth'
+import { useNotificationStore } from '../../stores/notification'
+import { navigateTo, redirectTo, showModal } from '../../utils'
+
+const authStore = useAuthStore()
+const notificationStore = useNotificationStore()
+const currentPath = ref('/pages/construction/myPage')
+
+const userName = computed(() => authStore.userName || '张师傅')
+const userTeam = computed(() => authStore.userInfo?.team || '一班组')
+const userId = computed(() => authStore.userInfo?.id || 'u3')
+const userPhone = computed(() => {
+  const phone = authStore.userInfo?.phone || '13800138003'
+  return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
+})
+const unreadCount = computed(() => notificationStore.unreadCount)
+
+const navItems = [
+  { text: '首页', path: '/pages/construction/home', icon: '' },
+  { text: '任务', path: '/pages/construction/taskList', icon: '' },
+  { text: '我的', path: '/pages/construction/myPage', icon: '' },
+]
+
+function onNavChange(path: string) {
+  uni.navigateTo({ url: path })
+}
+
+function goToBasicInfo() {
+  navigateTo('/pages/construction/basicInfo')
+}
+
+function goToChangePassword() {
+  navigateTo('/pages/construction/changePassword')
+}
+
+function goToNotification() {
+  navigateTo('/pages/construction/notification')
+}
+
+function goToHelpCenter() {
+  navigateTo('/pages/construction/helpCenter')
+}
+
+function logout() {
+  showModal({
+    title: '确认退出',
+    content: '确定要退出登录吗?',
+    showCancel: true,
+    confirmText: '退出',
+    success: (res) => {
+      if (res.confirm) {
+        authStore.logout()
+        redirectTo('/pages/login/login')
+      }
+    }
+  })
+}
+</script>

+ 77 - 0
src/pages/construction/noticeDetail.vue

@@ -0,0 +1,77 @@
+<template>
+  <view class="app-container pb-4">
+    <TopBar title="公告详情" showBack />
+
+    <view class="px-4 py-4">
+      <view v-if="notice" class="bg-white rounded-xl p-4 shadow-sm">
+        <view class="flex justify-between items-start mb-3">
+          <text class="text-base font-bold text-gray-800">{{ notice.title }}</text>
+          <view v-if="notice.important" class="px-2 py-0.5 bg-red-50 text-red-500 text-xs rounded">重要</view>
+        </view>
+        <view class="text-xs text-gray-400 mb-4">
+          <text> {{ formatDate(notice.createTime, 'YYYY-MM-DD') }}</text>
+          <text class="mx-2">|</text>
+          <text> 调度中心</text>
+        </view>
+        <view class="text-sm text-gray-600 leading-relaxed">
+          <text class="block mb-3">各位施工同事:</text>
+          <text class="block mb-3">{{ notice.summary }}</text>
+          <text class="block mb-2">一、进入施工现场必须佩戴安全帽、安全背心等防护用品。</text>
+          <text class="block mb-2">二、施工前必须进行安全检查,确保设备正常运转。</text>
+          <text class="block mb-2">三、施工现场必须设置警示标志,做好安全防护措施。</text>
+          <text class="block mb-2">四、发现安全隐患及时上报,不得隐瞒。</text>
+          <text class="block mb-3">请各位同事严格遵守以上规定,确保施工安全。</text>
+          <text class="block">调度中心</text>
+          <text class="block">{{ formatDate(notice.createTime, 'YYYY年MM月DD日') }}</text>
+        </view>
+      </view>
+      <EmptyState v-else message="公告不存在" icon="" />
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+import { onLoad } from '@dcloudio/uni-app'
+import TopBar from '../../components/common/TopBar.vue'
+import EmptyState from '../../components/common/EmptyState.vue'
+import { formatDate } from '../../utils'
+
+interface Notice {
+  id: string
+  title: string
+  summary: string
+  createTime: string
+  important?: boolean
+}
+
+const notice = ref<Notice | null>(null)
+
+const allNotices: Notice[] = [
+  {
+    id: 'n1',
+    title: '关于规范施工现场安全管理的通知',
+    summary: '为进一步规范施工现场安全管理,确保施工人员人身安全和施工质量,现就相关事项通知如下:',
+    createTime: '2026-04-20T09:00:00',
+    important: true,
+  },
+  {
+    id: 'n2',
+    title: '下周安全培训会议安排',
+    summary: '各位同事,下周二将举行安全培训会议,请准时参加。',
+    createTime: '2026-04-18T10:00:00',
+  },
+  {
+    id: 'n3',
+    title: '设备维护保养规范更新',
+    summary: '最新设备维护保养规范已更新,请各位同事查阅学习。',
+    createTime: '2026-04-15T14:00:00',
+  },
+]
+
+onLoad((options) => {
+  if (options?.id) {
+    notice.value = allNotices.find(n => n.id === options.id) || null
+  }
+})
+</script>

+ 114 - 0
src/pages/construction/noticeList.vue

@@ -0,0 +1,114 @@
+
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="通知公告" show-back />
+    
+    <!-- 筛选 -->
+    <view class="bg-white px-4 py-3 flex gap-2 overflow-x-auto hide-scrollbar">
+      <view 
+        v-for="type in filterTypes" 
+        :key="type.value"
+        class="filter-tab"
+        :class="{ active: currentFilter === type.value }"
+        @click="currentFilter = type.value"
+      >
+        {{ type.label }}
+      </view>
+    </view>
+
+    <!-- 通知列表 -->
+    <view class="px-4 mt-3">
+      <view v-if="loading" class="py-10 text-center">
+        <text class="text-gray-400">加载中...</text>
+      </view>
+      
+      <view v-else-if="filteredNotices.length === 0">
+        <EmptyState message="暂无通知" />
+      </view>
+      
+      <view v-else class="space-y-3">
+        <view 
+          v-for="notice in filteredNotices" 
+          :key="notice.noticeId"
+          class="card p-4 cursor-pointer"
+          @click="goToDetail(notice.noticeId)"
+        >
+          <view class="flex justify-between items-start mb-2">
+            <view class="flex items-center">
+              <view v-if="notice.priority === 3" class="w-2 h-2 bg-red-500 rounded-full mr-2"></view>
+              <text class="text-sm font-semibold text-gray-800">{{ notice.title }}</text>
+            </view>
+            <view class="tag" :class="getTypeClass(notice.type)">
+              {{ getTypeText(notice.type) }}
+            </view>
+          </view>
+          
+          <text class="text-xs text-gray-500 line-clamp-2 mb-2 block">{{ notice.content }}</text>
+          
+          <view class="flex justify-between items-center">
+            <text class="text-xs text-gray-400">{{ notice.publisherName }} · {{ formatDate(notice.publishTime) }}</text>
+            <text class="text-xs text-gray-400"> {{ notice.readCount }}</text>
+          </view>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue'
+import TopBar from '../../components/common/TopBar.vue'
+import EmptyState from '../../components/common/EmptyState.vue'
+import { getNoticeList } from '../../api/notice'
+import { formatDate } from '../../utils'
+
+const loading = ref(false)
+const notices = ref<any[]>([])
+const currentFilter = ref('all')
+
+const filterTypes = [
+  { value: 'all', label: '全部' },
+  { value: 'system', label: '系统' },
+  { value: 'emergency', label: '应急' },
+  { value: 'task', label: '任务' },
+]
+
+const filteredNotices = computed(() => {
+  if (currentFilter.value === 'all') return notices.value
+  return notices.value.filter(n => n.type === currentFilter.value)
+})
+
+function getTypeText(type: string) {
+  const map: Record<string, string> = {
+    system: '系统',
+    emergency: '应急',
+    task: '任务',
+  }
+  return map[type] || type
+}
+
+function getTypeClass(type: string) {
+  const map: Record<string, string> = {
+    system: 'tag-blue',
+    emergency: 'tag-red',
+    task: 'tag-green',
+  }
+  return map[type] || 'tag-gray'
+}
+
+function goToDetail(noticeId: number) {
+  uni.navigateTo({ url: `/pages/common/noticeDetail?id=${noticeId}` })
+}
+
+onMounted(async () => {
+  loading.value = true
+  try {
+    const res = await getNoticeList()
+    notices.value = res.rows || []
+  } catch (error) {
+    console.error('获取通知列表失败:', error)
+  } finally {
+    loading.value = false
+  }
+})
+</script>

+ 65 - 0
src/pages/construction/notification.vue

@@ -0,0 +1,65 @@
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="通知消息" show-back />
+    
+    <view class="px-4 mt-3">
+      <view v-if="loading" class="py-10 text-center">
+        <text class="text-gray-400">加载中...</text>
+      </view>
+      
+      <view v-else-if="noticeList.length === 0">
+        <EmptyState message="暂无通知" />
+      </view>
+      
+      <uni-list v-else border>
+        <uni-list-item 
+          v-for="notice in noticeList" 
+          :key="notice.noticeId"
+          :title="notice.title"
+          :note="notice.content"
+          :right-text="formatDate(notice.publishTime)"
+          show-arrow
+          @click="goToDetail(notice.noticeId)"
+        >
+          <template #header>
+            <view class="relative mr-3">
+              <view class="w-10 h-10 rounded-xl bg-blue-50 flex items-center justify-center">
+                <text class="uni-icons uniui-notification-filled text-blue-500"></text>
+              </view>
+              <view v-if="!notice.read" class="absolute -top-1 -right-1 w-3 h-3 bg-red-500 rounded-full border-2 border-white"></view>
+            </view>
+          </template>
+        </uni-list-item>
+      </uni-list>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from 'vue'
+import TopBar from '../../components/common/TopBar.vue'
+import EmptyState from '../../components/common/EmptyState.vue'
+import UniList from '../../components/uni-list/uni-list.vue'
+import UniListItem from '../../components/uni-list/uni-list-item.vue'
+import { getNoticeList } from '../../api/notice'
+import { formatDate } from '../../utils'
+
+const loading = ref(false)
+const noticeList = ref<any[]>([])
+
+function goToDetail(noticeId: number) {
+  uni.navigateTo({ url: `/pages/common/noticeDetail?id=${noticeId}` })
+}
+
+onMounted(async () => {
+  loading.value = true
+  try {
+    const res = await getNoticeList()
+    noticeList.value = res.rows || []
+  } catch (error) {
+    console.error('获取通知列表失败:', error)
+  } finally {
+    loading.value = false
+  }
+})
+</script>

+ 326 - 0
src/pages/construction/taskDetail.vue

@@ -0,0 +1,326 @@
+<template>
+  <view class="app-container pb-4">
+    <TopBar title="任务详情" showBack />
+
+    <view v-if="task" class="px-4 py-4 space-y-4">
+      <!-- 施工流程进度 -->
+      <view class="bg-white rounded-card p-4 shadow-card">
+        <view class="relative flex justify-between items-center mb-2">
+          <view class="absolute top-3 left-0 right-0 h-0.5 bg-gray-200 -z-0"></view>
+          <view
+            class="absolute top-3 left-0 h-0.5 bg-primary -z-0 transition-all"
+            :style="{ width: flowProgress + '%' }"
+          ></view>
+          <view
+            v-for="(step, index) in flowSteps"
+            :key="step.key"
+            class="flex flex-col items-center z-10"
+            :style="{ width: '12%' }"
+          >
+            <view
+              class="w-6 h-6 rounded-full flex items-center justify-center text-xs"
+              :class="stepStatusClass(step.key)"
+            >
+              <text v-if="isStepCompleted(step.key)"></text>
+              <text v-else-if="isStepActive(step.key)"></text>
+              <text v-else></text>
+            </view>
+            <text class="text-xs mt-1" :class="isStepCompleted(step.key) || isStepActive(step.key) ? 'text-primary font-medium' : 'text-gray-400'">
+              {{ step.label }}
+            </text>
+          </view>
+        </view>
+      </view>
+
+      <!-- 操作按钮区 -->
+      <view class="bg-white rounded-card p-4 shadow-card">
+        <!-- 待确认 -->
+        <view v-if="task.status === 'pending' || task.status === 'confirmed'" class="space-y-3">
+          <view class="bg-blue-50 rounded-xl p-4 mb-4 text-center">
+            <text class="uni-icons uniui-compose text-4xl block mb-2 text-blue-500"></text>
+            <text class="text-lg font-semibold text-gray-800 block">待确认任务</text>
+            <text class="text-gray-500 text-sm mt-1 block">请查看任务详情,确认是否接受</text>
+          </view>
+          <button class="w-full py-3 bg-green-500 text-white rounded-xl font-medium flex items-center justify-center gap-2" @click="onConfirm">
+            <text></text>
+            <text>确认任务</text>
+          </button>
+          <button class="w-full py-3 bg-orange-50 border border-orange-200 text-orange-600 rounded-xl font-medium flex items-center justify-center gap-2" @click="onFeedback">
+            <text></text>
+            <text>反馈问题</text>
+          </button>
+          <button class="w-full py-3 bg-red-50 border border-red-200 text-red-500 rounded-xl font-medium flex items-center justify-center gap-2" @click="onReject">
+            <text></text>
+            <text>拒绝任务</text>
+          </button>
+        </view>
+
+        <!-- 待出车/已排班 -->
+        <view v-else-if="task.status === 'scheduled'" class="space-y-3">
+          <view class="bg-orange-50 rounded-xl p-4 mb-4 text-center">
+            <text class="uni-icons uniui-cart-filled text-4xl block mb-2 text-orange-500"></text>
+            <text class="text-lg font-semibold text-gray-800 block">准备出发</text>
+            <text class="text-gray-500 text-sm mt-1 block">请确认设备准备完毕后出发</text>
+          </view>
+          <view class="bg-white rounded-lg p-3 border border-gray-100 mb-3">
+            <view class="flex justify-between text-sm">
+              <text class="text-gray-400">目的地</text>
+              <text class="text-gray-800">{{ task.projectName }}</text>
+            </view>
+            <view v-if="task.vehicle" class="flex justify-between text-sm mt-2">
+              <text class="text-gray-400">车辆</text>
+              <text class="text-gray-800">{{ task.vehicle }}</text>
+            </view>
+          </view>
+          <button class="w-full py-3 bg-gradient-to-r from-orange-500 to-orange-600 text-white rounded-xl font-semibold flex items-center justify-center gap-2" @click="onDepart">
+            <text></text>
+            <text>出发</text>
+          </button>
+        </view>
+
+        <!-- 已出车 -->
+        <view v-else-if="task.status === 'departed'" class="space-y-3">
+          <view class="bg-purple-50 rounded-xl p-4 mb-4 text-center">
+            <text class="uni-icons uniui-location-filled text-4xl block mb-2 text-purple-500"></text>
+            <text class="text-lg font-semibold text-gray-800 block">前往现场</text>
+            <text class="text-gray-500 text-sm mt-1 block">正在前往施工地点</text>
+          </view>
+          <button class="w-full py-3 bg-gradient-to-r from-green-500 to-green-600 text-white rounded-xl font-semibold flex items-center justify-center gap-2" @click="onArrive">
+            <text></text>
+            <text>到达现场</text>
+          </button>
+        </view>
+
+        <!-- 已到达 -->
+        <view v-else-if="task.status === 'arrived'" class="space-y-3">
+          <view class="bg-green-50 rounded-xl p-4 mb-4 text-center">
+            <text class="uni-icons uniui-location text-4xl block mb-2 text-green-500"></text>
+            <text class="text-lg font-semibold text-gray-800 block">已到达现场</text>
+            <text class="text-gray-500 text-sm mt-1 block">请开始施工作业</text>
+          </view>
+          <button class="w-full py-3 bg-gradient-to-r from-blue-500 to-blue-600 text-white rounded-xl font-semibold flex items-center justify-center gap-2" @click="onStartWork">
+            <text></text>
+            <text>开始施工</text>
+          </button>
+        </view>
+
+        <!-- 施工中 -->
+        <view v-else-if="task.status === 'constructing'" class="space-y-3">
+          <view class="bg-blue-50 rounded-xl p-4 mb-4 text-center">
+            <text class="uni-icons uniui-gear-filled text-4xl block mb-2 text-blue-500"></text>
+            <text class="text-lg font-semibold text-gray-800 block">施工中</text>
+            <text class="text-gray-500 text-sm mt-1 block">请按规范完成施工作业</text>
+          </view>
+          <button class="w-full py-3 bg-gradient-to-r from-green-500 to-green-600 text-white rounded-xl font-semibold flex items-center justify-center gap-2" @click="onComplete">
+            <text></text>
+            <text>完成施工</text>
+          </button>
+        </view>
+
+        <!-- 已完成 -->
+        <view v-else-if="task.status === 'completed'" class="space-y-3">
+          <view class="bg-green-50 rounded-xl p-4 text-center">
+            <text class="uni-icons uniui-checkmarkempty text-4xl block mb-2 text-green-500"></text>
+            <text class="text-lg font-semibold text-green-700 block">任务已完成</text>
+            <text class="text-gray-500 text-sm mt-1 block">该任务已验收通过</text>
+          </view>
+        </view>
+      </view>
+
+      <!-- 任务信息 -->
+      <view class="bg-white rounded-card shadow-card overflow-hidden">
+        <view class="detail-section-title">
+          <text class="text-green-500 mr-2"></text>
+          <text>{{ task.projectName }}</text>
+          <StatusTag :status="task.status" class="ml-2" />
+        </view>
+        <view class="p-4 space-y-3">
+          <view class="flex justify-between text-sm">
+            <text class="text-gray-400">任务类型</text>
+            <text class="text-gray-800">{{ getTypeText(task.type) }}</text>
+          </view>
+          <view class="flex justify-between text-sm">
+            <text class="text-gray-400">紧急程度</text>
+            <text :class="task.urgent === 'high' ? 'text-red-500' : 'text-gray-800'">{{ getUrgentText(task.urgent) }}</text>
+          </view>
+          <view v-if="task.assignee" class="flex justify-between text-sm">
+            <text class="text-gray-400">负责人</text>
+            <text class="text-gray-800">{{ task.assignee }}</text>
+          </view>
+          <view v-if="task.vehicle" class="flex justify-between text-sm">
+            <text class="text-gray-400">车辆</text>
+            <text class="text-gray-800">{{ task.vehicle }}</text>
+          </view>
+          <view v-if="task.team" class="flex justify-between text-sm">
+            <text class="text-gray-400">班组</text>
+            <text class="text-gray-800">{{ task.team }}</text>
+          </view>
+          <view v-if="task.scheduledDate" class="flex justify-between text-sm">
+            <text class="text-gray-400">计划日期</text>
+            <text class="text-gray-800">{{ task.scheduledDate }}</text>
+          </view>
+          <view class="flex justify-between text-sm">
+            <text class="text-gray-400">创建时间</text>
+            <text class="text-gray-800">{{ formatDate(task.createTime, 'YYYY-MM-DD HH:mm') }}</text>
+          </view>
+        </view>
+      </view>
+
+      <!-- 时间轴 -->
+      <view class="bg-white rounded-card shadow-card overflow-hidden">
+        <view class="detail-section-title">
+          <text class="text-blue-500 mr-2"></text>
+          <text>操作记录</text>
+        </view>
+        <view class="p-4">
+          <view v-for="(record, index) in sortedTimeline" :key="index" class="flex items-start mb-4 last:mb-0">
+            <view class="flex flex-col items-center mr-3">
+              <view class="w-2 h-2 bg-primary rounded-full"></view>
+              <view v-if="index < sortedTimeline.length - 1" class="w-0.5 h-10 bg-gray-200"></view>
+            </view>
+            <view class="flex-1">
+              <text class="text-sm text-gray-800 block">{{ record.action }}</text>
+              <view class="flex justify-between mt-1">
+                <text class="text-xs text-gray-400">{{ record.operator }}</text>
+                <text class="text-xs text-gray-400">{{ formatDate(record.time, 'MM-DD HH:mm') }}</text>
+              </view>
+            </view>
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <EmptyState v-else message="任务不存在" icon="" />
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed } from 'vue'
+import { onLoad } from '@dcloudio/uni-app'
+import TopBar from '../../components/common/TopBar.vue'
+import StatusTag from '../../components/common/StatusTag.vue'
+import EmptyState from '../../components/common/EmptyState.vue'
+import { useTaskStore } from '../../stores/task'
+import { useAuthStore } from '../../stores/auth'
+import { formatDate, getTypeText, getUrgentText, showToast, showModal, goBack } from '../../utils'
+import type { TaskStatus } from '../../types'
+
+const taskStore = useTaskStore()
+const authStore = useAuthStore()
+const task = computed(() => taskStore.currentTask)
+
+const flowSteps = [
+  { key: 'confirmed', label: '确认' },
+  { key: 'departed', label: '出车' },
+  { key: 'arrived', label: '到达' },
+  { key: 'constructing', label: '施工' },
+  { key: 'completed', label: '完成' },
+]
+
+const statusOrder: TaskStatus[] = ['pending', 'confirmed', 'scheduled', 'departed', 'arrived', 'constructing', 'inspecting', 'completed']
+
+const flowProgress = computed(() => {
+  if (!task.value) return 0
+  const index = statusOrder.indexOf(task.value.status)
+  if (index <= 0) return 0
+  return Math.min((index / (statusOrder.length - 1)) * 100, 100)
+})
+
+const sortedTimeline = computed(() => {
+  if (!task.value) return []
+  return [...task.value.timeline].sort((a, b) => new Date(b.time).getTime() - new Date(a.time).getTime())
+})
+
+function isStepCompleted(stepKey: string): boolean {
+  if (!task.value) return false
+  const stepIndex = statusOrder.indexOf(stepKey as TaskStatus)
+  const currentIndex = statusOrder.indexOf(task.value.status)
+  return currentIndex > stepIndex
+}
+
+function isStepActive(stepKey: string): boolean {
+  if (!task.value) return false
+  return task.value.status === stepKey
+}
+
+function stepStatusClass(stepKey: string): string {
+  if (isStepCompleted(stepKey)) return 'bg-green-500 text-white'
+  if (isStepActive(stepKey)) return 'bg-primary text-white'
+  return 'bg-gray-200 text-gray-400'
+}
+
+function updateStatus(status: TaskStatus, action: string) {
+  if (!task.value) return
+  const user = authStore.userInfo
+  taskStore.updateTaskStatus(task.value.id, status)
+  taskStore.addTaskLog(task.value.id, action, user?.name || '施工人员', 'construction')
+  showToast(`${action}成功`)
+}
+
+function onConfirm() {
+  updateStatus('confirmed', '确认任务')
+}
+
+function onDepart() {
+  updateStatus('departed', '出车出发')
+}
+
+function onArrive() {
+  updateStatus('arrived', '到达现场')
+}
+
+function onStartWork() {
+  updateStatus('constructing', '开始施工')
+}
+
+function onComplete() {
+  updateStatus('inspecting', '施工完成')
+}
+
+function onFeedback() {
+  showModal({
+    title: '反馈问题',
+    content: '请描述遇到的问题,将提交给调度中心处理',
+    showCancel: true,
+    confirmText: '提交',
+    success: (res) => {
+      if (res.confirm) {
+        showToast('反馈已提交')
+      }
+    }
+  })
+}
+
+function onReject() {
+  showModal({
+    title: '拒绝任务',
+    content: '确定要拒绝此任务吗?',
+    showCancel: true,
+    confirmText: '确认拒绝',
+    success: (res) => {
+      if (res.confirm) {
+        updateStatus('rejected', '拒绝任务')
+        goBack()
+      }
+    }
+  })
+}
+
+onLoad((options) => {
+  if (options?.id) {
+    taskStore.setCurrentTask(options.id)
+  }
+})
+</script>
+
+<style scoped>
+.detail-section-title {
+  padding: 12px 16px;
+  font-size: 14px;
+  font-weight: 600;
+  color: #333;
+  border-bottom: 1px solid #f0f0f0;
+  display: flex;
+  align-items: center;
+}
+</style>

+ 78 - 0
src/pages/construction/taskList.vue

@@ -0,0 +1,78 @@
+
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="我的任务" />
+    
+    <!-- 筛选标签 -->
+    <view class="bg-white px-4 py-3 flex gap-2 overflow-x-auto hide-scrollbar">
+      <view 
+        v-for="status in filterStatuses" 
+        :key="status.value"
+        class="filter-tab"
+        :class="{ active: currentFilter === status.value }"
+        @click="currentFilter = status.value"
+      >
+        {{ status.label }}
+      </view>
+    </view>
+
+    <!-- 任务列表 -->
+    <view class="px-4 mt-3">
+      <view v-if="taskStore.loading" class="py-10 text-center">
+        <text class="text-gray-400">加载中...</text>
+      </view>
+      
+      <view v-else-if="filteredTasks.length === 0">
+        <EmptyState message="暂无任务" />
+      </view>
+      
+      <view v-else class="space-y-3">
+        <TaskCard 
+          v-for="task in filteredTasks" 
+          :key="task.id"
+          :task="task"
+          @click="goToDetail"
+        />
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue'
+import { useTaskStore } from '../../stores/task'
+import TopBar from '../../components/common/TopBar.vue'
+import TaskCard from '../../components/task/TaskCard.vue'
+import EmptyState from '../../components/common/EmptyState.vue'
+
+const taskStore = useTaskStore()
+const currentFilter = ref('all')
+
+const filterStatuses = [
+  { value: 'all', label: '全部' },
+  { value: 'scheduled', label: '待出车' },
+  { value: 'departed', label: '已出车' },
+  { value: 'constructing', label: '施工中' },
+  { value: 'completed', label: '已完成' },
+]
+
+const filteredTasks = computed(() => {
+  if (currentFilter.value === 'all') return taskStore.tasks
+  return taskStore.tasks.filter(t => t.status === currentFilter.value)
+})
+
+function goToDetail(taskId: string) {
+  taskStore.setCurrentTask(taskId)
+  uni.navigateTo({ url: '/pages/construction/taskDetail' })
+}
+
+onMounted(() => {
+  taskStore.fetchTasks()
+})
+
+// 下拉刷新
+onPullDownRefresh(async () => {
+  await taskStore.fetchTasks()
+  uni.stopPullDownRefresh()
+})
+</script>

+ 60 - 0
src/pages/construction/tomorrowTask.vue

@@ -0,0 +1,60 @@
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="明日任务" show-back />
+    
+    <view class="px-4 mt-3">
+      <view v-if="taskStore.loading" class="py-10 text-center">
+        <text class="text-gray-400">加载中...</text>
+      </view>
+      
+      <view v-else-if="tomorrowTasks.length === 0">
+        <EmptyState message="暂无明日任务" />
+      </view>
+      
+      <uni-list v-else border>
+        <uni-list-item 
+          v-for="task in tomorrowTasks" 
+          :key="task.id"
+          :title="task.projectName"
+          :note="task.address"
+          :right-text="task.scheduleTime"
+          show-arrow
+          @click="goToDetail(task.id)"
+        >
+          <template #header>
+            <view class="w-10 h-10 rounded-xl bg-blue-50 flex items-center justify-center mr-3">
+              <text class="uni-icons uniui-calendar-filled text-blue-500"></text>
+            </view>
+          </template>
+        </uni-list-item>
+      </uni-list>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { computed, onMounted } from 'vue'
+import { useTaskStore } from '../../stores/task'
+import TopBar from '../../components/common/TopBar.vue'
+import EmptyState from '../../components/common/EmptyState.vue'
+import UniList from '../../components/uni-list/uni-list.vue'
+import UniListItem from '../../components/uni-list/uni-list-item.vue'
+
+const taskStore = useTaskStore()
+
+const tomorrowTasks = computed(() => {
+  const tomorrow = new Date()
+  tomorrow.setDate(tomorrow.getDate() + 1)
+  const tomorrowStr = tomorrow.toISOString().split('T')[0]
+  return taskStore.tasks.filter(t => t.scheduleDate === tomorrowStr)
+})
+
+function goToDetail(taskId: string) {
+  taskStore.setCurrentTask(taskId)
+  uni.navigateTo({ url: '/pages/construction/taskDetail' })
+}
+
+onMounted(() => {
+  taskStore.fetchTasks()
+})
+</script>

+ 138 - 0
src/pages/dispatch/availableVehicles.vue

@@ -0,0 +1,138 @@
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="可用车辆" show-back />
+    
+    <!-- 统计卡片 -->
+    <view class="px-4 py-4">
+      <view class="grid grid-cols-3 gap-3">
+        <view class="bg-green-50 rounded-xl p-3 text-center">
+          <text class="text-xl font-bold text-green-600 block">{{ availableCount }}</text>
+          <text class="text-xs text-gray-500">可用</text>
+        </view>
+        <view class="bg-blue-50 rounded-xl p-3 text-center">
+          <text class="text-xl font-bold text-blue-600 block">{{ inUseCount }}</text>
+          <text class="text-xs text-gray-500">使用中</text>
+        </view>
+        <view class="bg-yellow-50 rounded-xl p-3 text-center">
+          <text class="text-xl font-bold text-yellow-600 block">{{ maintenanceCount }}</text>
+          <text class="text-xs text-gray-500">维修中</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 筛选 -->
+    <view class="px-4 pb-2">
+      <uni-segmented-control 
+        :current="currentFilterIndex" 
+        :values="filterLabels"
+        style-type="text"
+        active-color="#4f8ef7"
+        @clickItem="onFilterChange"
+      />
+    </view>
+
+    <!-- 车辆列表 -->
+    <view class="px-4 mt-2">
+      <view v-if="loading" class="py-10 text-center">
+        <text class="text-gray-400">加载中...</text>
+      </view>
+      
+      <view v-else-if="filteredVehicles.length === 0">
+        <EmptyState message="暂无车辆" />
+      </view>
+      
+      <uni-list v-else border>
+        <uni-list-item 
+          v-for="vehicle in filteredVehicles" 
+          :key="vehicle.vehicleId"
+          :title="vehicle.vehicleNo"
+          :note="(vehicle.type || '未分类') + ' | ' + (vehicle.driver || '未分配司机')"
+          :right-text="getStatusText(vehicle.status)"
+        >
+          <template #header>
+            <view class="w-10 h-10 rounded-xl flex items-center justify-center mr-3" :class="getStatusBgClass(vehicle.status)">
+              <text class="uni-icons text-lg" :class="getStatusIconClass(vehicle.status)"></text>
+            </view>
+          </template>
+        </uni-list-item>
+      </uni-list>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue'
+import TopBar from '../../components/common/TopBar.vue'
+import EmptyState from '../../components/common/EmptyState.vue'
+import UniList from '../../components/uni-list/uni-list.vue'
+import UniListItem from '../../components/uni-list/uni-list-item.vue'
+import UniSegmentedControl from '../../components/uni-segmented-control/uni-segmented-control.vue'
+
+const loading = ref(false)
+const vehicles = ref<any[]>([])
+const currentFilterIndex = ref(0)
+
+const filterTypes = [
+  { value: 'all', label: '全部' },
+  { value: 'available', label: '可用' },
+  { value: 'in_use', label: '使用中' },
+  { value: 'maintenance', label: '维修中' },
+]
+
+const filterLabels = filterTypes.map(t => t.label)
+
+const filteredVehicles = computed(() => {
+  const filterValue = filterTypes[currentFilterIndex.value].value
+  if (filterValue === 'all') return vehicles.value
+  return vehicles.value.filter(v => v.status === filterValue)
+})
+
+const availableCount = computed(() => vehicles.value.filter(v => v.status === 'available').length)
+const inUseCount = computed(() => vehicles.value.filter(v => v.status === 'in_use').length)
+const maintenanceCount = computed(() => vehicles.value.filter(v => v.status === 'maintenance').length)
+
+function getStatusText(status: string) {
+  const map: Record<string, string> = {
+    available: '可用',
+    in_use: '使用中',
+    maintenance: '维修中',
+    offline: '离线',
+  }
+  return map[status] || status
+}
+
+function getStatusBgClass(status: string) {
+  const map: Record<string, string> = {
+    available: 'bg-green-50',
+    in_use: 'bg-blue-50',
+    maintenance: 'bg-yellow-50',
+    offline: 'bg-gray-50',
+  }
+  return map[status] || 'bg-gray-50'
+}
+
+function getStatusIconClass(status: string) {
+  const map: Record<string, string> = {
+    available: 'uniui-checkmarkempty text-green-500',
+    in_use: 'uniui-cart-filled text-blue-500',
+    maintenance: 'uniui-gear-filled text-yellow-500',
+    offline: 'uniui-close text-gray-400',
+  }
+  return map[status] || 'uniui-cart text-gray-400'
+}
+
+function onFilterChange(index: number) {
+  currentFilterIndex.value = index
+}
+
+onMounted(() => {
+  // 模拟数据
+  vehicles.value = [
+    { vehicleId: '1', vehicleNo: '云A12345', type: '清洁车', status: 'available', driver: '张师傅' },
+    { vehicleId: '2', vehicleNo: '云A67890', type: '高压清洗车', status: 'available', driver: '李师傅' },
+    { vehicleId: '3', vehicleNo: '云A11111', type: '垃圾清运车', status: 'in_use', driver: '王师傅' },
+    { vehicleId: '4', vehicleNo: '云A22222', type: '洒水车', status: 'maintenance', driver: '' },
+    { vehicleId: '5', vehicleNo: '云A33333', type: '清洁车', status: 'available', driver: '赵师傅' },
+  ]
+})
+</script>

+ 59 - 0
src/pages/dispatch/basicInfo.vue

@@ -0,0 +1,59 @@
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="基本信息" show-back />
+
+    <view class="px-4 py-4 space-y-4">
+      <!-- 头像区域 -->
+      <uni-card :is-shadow="true">
+        <view class="flex items-center py-2">
+          <view class="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mr-4">
+            <text class="uni-icons uniui-person-filled text-blue-500 text-2xl"></text>
+          </view>
+          <view>
+            <text class="text-lg font-semibold text-gray-800 block">{{ userInfo?.name || '李调度' }}</text>
+            <text class="text-sm text-gray-500">{{ userInfo?.department || '调度中心' }} · 调度主管</text>
+          </view>
+        </view>
+      </uni-card>
+
+      <!-- 基本信息 -->
+      <uni-card title="基本信息" icon="uniui-person-filled" :is-shadow="true">
+        <uni-list border="false">
+          <uni-list-item title="姓名" :note="userInfo?.name || '李调度'" />
+          <uni-list-item title="工号" note="DIS001" />
+          <uni-list-item title="部门" :note="userInfo?.department || '调度中心'" />
+          <uni-list-item title="职位" note="调度主管" />
+          <uni-list-item title="电话" :note="userInfo?.phone || '13800138002'" />
+          <uni-list-item title="状态" :right-text="'在职'" right-color="#34c759" />
+        </uni-list>
+      </uni-card>
+
+      <!-- 操作按钮 -->
+      <view class="pt-2">
+        <button
+          class="w-full py-3 bg-blue-500 text-white rounded-xl font-medium flex items-center justify-center"
+          @click="handleEdit"
+        >
+          <text class="uni-icons uniui-compose mr-2"></text>
+          编辑信息
+        </button>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue'
+import TopBar from '../../components/common/TopBar.vue'
+import UniCard from '../../components/uni-card/uni-card.vue'
+import UniList from '../../components/uni-list/uni-list.vue'
+import UniListItem from '../../components/uni-list/uni-list-item.vue'
+import { useAuthStore } from '../../stores/auth'
+
+const authStore = useAuthStore()
+const userInfo = computed(() => authStore.userInfo)
+
+function handleEdit() {
+  uni.showToast({ title: '编辑功能开发中', icon: 'none' })
+}
+</script>

+ 120 - 0
src/pages/dispatch/changePassword.vue

@@ -0,0 +1,120 @@
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="修改密码" show-back />
+
+    <view class="px-4 py-4">
+      <uni-card title="密码修改" icon="uniui-locked-filled" :is-shadow="true">
+        <view class="space-y-4 pt-2">
+          <view>
+            <text class="text-sm text-gray-600 block mb-2">
+              <text class="text-red-500">*</text> 当前密码
+            </text>
+            <input
+              v-model="form.currentPassword"
+              class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm text-gray-800 bg-white"
+              type="password"
+              placeholder="请输入当前密码"
+            />
+          </view>
+          <view>
+            <text class="text-sm text-gray-600 block mb-2">
+              <text class="text-red-500">*</text> 新密码
+            </text>
+            <input
+              v-model="form.newPassword"
+              class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm text-gray-800 bg-white"
+              type="password"
+              placeholder="请输入新密码(6-20位)"
+            />
+          </view>
+          <view>
+            <text class="text-sm text-gray-600 block mb-2">
+              <text class="text-red-500">*</text> 确认新密码
+            </text>
+            <input
+              v-model="form.confirmPassword"
+              class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm text-gray-800 bg-white"
+              type="password"
+              placeholder="请再次输入新密码"
+            />
+          </view>
+        </view>
+
+        <view class="mt-4">
+          <view class="bg-blue-50 rounded-lg p-3 mb-4">
+            <text class="text-blue-600 text-sm block font-medium">密码要求:</text>
+            <view class="text-gray-500 text-xs mt-2 space-y-1">
+              <text class="block">• 长度6-20位</text>
+              <text class="block">• 必须包含字母和数字</text>
+              <text class="block">• 不能与当前密码相同</text>
+            </view>
+          </view>
+
+          <button
+            class="w-full py-3 bg-blue-500 text-white rounded-xl font-medium"
+            @click="handleSubmit"
+          >
+            确认修改
+          </button>
+        </view>
+      </uni-card>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { reactive } from 'vue'
+import TopBar from '../../components/common/TopBar.vue'
+import UniCard from '../../components/uni-card/uni-card.vue'
+
+const form = reactive({
+  currentPassword: '',
+  newPassword: '',
+  confirmPassword: ''
+})
+
+function validateForm(): boolean {
+  if (!form.currentPassword) {
+    uni.showToast({ title: '请输入当前密码', icon: 'none' })
+    return false
+  }
+  if (!form.newPassword) {
+    uni.showToast({ title: '请输入新密码', icon: 'none' })
+    return false
+  }
+  if (form.newPassword.length < 6 || form.newPassword.length > 20) {
+    uni.showToast({ title: '密码长度应为6-20位', icon: 'none' })
+    return false
+  }
+  if (!/^(?=.*[a-zA-Z])(?=.*\d).+$/.test(form.newPassword)) {
+    uni.showToast({ title: '密码必须包含字母和数字', icon: 'none' })
+    return false
+  }
+  if (form.newPassword === form.currentPassword) {
+    uni.showToast({ title: '新密码不能与当前密码相同', icon: 'none' })
+    return false
+  }
+  if (form.newPassword !== form.confirmPassword) {
+    uni.showToast({ title: '两次输入的新密码不一致', icon: 'none' })
+    return false
+  }
+  return true
+}
+
+async function handleSubmit() {
+  if (!validateForm()) return
+
+  uni.showModal({
+    title: '确认修改',
+    content: '确定要修改密码吗?修改后需要重新登录。',
+    success: (res) => {
+      if (res.confirm) {
+        uni.showToast({ title: '密码修改成功', icon: 'success' })
+        form.currentPassword = ''
+        form.newPassword = ''
+        form.confirmPassword = ''
+      }
+    }
+  })
+}
+</script>

+ 94 - 0
src/pages/dispatch/dutyPersonnel.vue

@@ -0,0 +1,94 @@
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="当班人员" show-back />
+    
+    <!-- 筛选 -->
+    <view class="bg-white px-4 py-3">
+      <uni-segmented-control 
+        :current="currentIndex" 
+        :values="teams.map(t => t.label)"
+        style-type="text"
+        active-color="#4f8ef7"
+        @clickItem="onTabChange"
+      />
+    </view>
+
+    <!-- 当班人员列表 -->
+    <view class="px-4 mt-3">
+      <view v-if="loading" class="py-10 text-center">
+        <text class="text-gray-400">加载中...</text>
+      </view>
+      
+      <view v-else-if="filteredStaff.length === 0">
+        <EmptyState message="暂无当班人员" />
+      </view>
+      
+      <uni-list v-else border>
+        <uni-list-item 
+          v-for="staff in filteredStaff" 
+          :key="staff.staffId"
+          :title="staff.name"
+          :note="(staff.phone || '') + ' | ' + (staff.teamName || '未分配班组')"
+          :right-text="getStatusText(staff.status)"
+        >
+          <template #header>
+            <view class="w-10 h-10 rounded-full bg-blue-50 flex items-center justify-center mr-3">
+              <text class="uni-icons uniui-person-filled text-blue-500"></text>
+            </view>
+          </template>
+        </uni-list-item>
+      </uni-list>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue'
+import TopBar from '../../components/common/TopBar.vue'
+import EmptyState from '../../components/common/EmptyState.vue'
+import UniSegmentedControl from '../../components/uni-segmented-control/uni-segmented-control.vue'
+import UniList from '../../components/uni-list/uni-list.vue'
+import UniListItem from '../../components/uni-list/uni-list-item.vue'
+import { getStaffList } from '../../api/staff'
+
+const loading = ref(false)
+const staffList = ref<any[]>([])
+const currentIndex = ref(0)
+
+const teams = [
+  { value: 'all', label: '全部' },
+  { value: '一班', label: '工程一班' },
+  { value: '二班', label: '工程二班' },
+  { value: '三班', label: '工程三班' },
+]
+
+const filteredStaff = computed(() => {
+  const filter = teams[currentIndex.value].value
+  if (filter === 'all') return staffList.value.filter(s => s.status === 'on_duty' || s.status === 'working')
+  return staffList.value.filter(s => (s.status === 'on_duty' || s.status === 'working') && s.teamName?.includes(filter))
+})
+
+function onTabChange(index: number) {
+  currentIndex.value = index
+}
+
+function getStatusText(status: string) {
+  const map: Record<string, string> = {
+    on_duty: '在岗',
+    working: '施工中',
+  }
+  return map[status] || status
+}
+
+onMounted(async () => {
+  loading.value = true
+  try {
+    const res = await getStaffList()
+    staffList.value = res.rows || []
+  } catch (error) {
+    console.error('获取人员列表失败:', error)
+  } finally {
+    loading.value = false
+  }
+})
+</script>

+ 66 - 0
src/pages/dispatch/emergencyStandby.vue

@@ -0,0 +1,66 @@
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="应急待命" show-back />
+
+    <!-- 应急状态说明 -->
+    <view class="px-4 mt-4">
+      <view class="p-3 bg-red-50 rounded-lg flex items-center mb-4">
+        <text class="text-red-500 mr-2"></text>
+        <text class="text-sm text-red-600">应急待命人员保持通讯畅通,随时准备响应紧急任务</text>
+      </view>
+    </view>
+
+    <view class="px-4 py-4">
+      <view v-if="emergencyTeam.length > 0" class="space-y-3">
+        <view
+          v-for="person in emergencyTeam"
+          :key="person.id"
+          class="card p-4"
+        >
+          <view class="flex items-center justify-between">
+            <view class="flex items-center">
+              <view class="w-12 h-12 bg-red-100 rounded-full flex items-center justify-center mr-3">
+                <text class="text-xl"></text>
+              </view>
+              <view>
+                <text class="font-medium text-gray-800 block">{{ person.name }}</text>
+                <text class="text-sm text-gray-500">{{ person.team }} · {{ person.phone }}</text>
+              </view>
+            </view>
+            <text class="tag tag-red">{{ getPersonnelStatusText(person.status) }}</text>
+          </view>
+          <view class="mt-3 pt-3 border-t border-gray-100">
+            <text class="text-xs text-gray-400"> 联系电话:{{ person.phone }}</text>
+          </view>
+        </view>
+      </view>
+      <EmptyState v-else message="暂无应急待命人员" />
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue'
+import TopBar from '../../components/common/TopBar.vue'
+import EmptyState from '../../components/common/EmptyState.vue'
+import { usePersonnelStore } from '../../stores/personnel'
+import { getPersonnelStatusText } from '../../utils'
+
+const personnelStore = usePersonnelStore()
+const emergencyTeam = computed(() => personnelStore.emergencyTeam)
+</script>
+
+<style scoped>
+.tag {
+  display: inline-block;
+  padding: 2px 8px;
+  border-radius: 4px;
+  font-size: 12px;
+  font-weight: 500;
+}
+
+.tag-red {
+  background: #fef2f2;
+  color: #ef4444;
+}
+</style>

+ 103 - 0
src/pages/dispatch/emergencyTeam.vue

@@ -0,0 +1,103 @@
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="应急班组" show-back />
+    
+    <view class="px-4 py-4 space-y-4">
+      <!-- 应急状态概览 -->
+      <uni-card title="应急状态" icon="uniui-notification-filled" extra="实时">
+        <view class="grid grid-cols-3 gap-3">
+          <view class="bg-red-50 rounded-xl p-3 text-center">
+            <text class="text-xl font-bold text-red-600 block">{{ standbyTeams }}</text>
+            <text class="text-xs text-gray-500">待命班组</text>
+          </view>
+          <view class="bg-orange-50 rounded-xl p-3 text-center">
+            <text class="text-xl font-bold text-orange-600 block">{{ standbyVehicles }}</text>
+            <text class="text-xs text-gray-500">待命车辆</text>
+          </view>
+          <view class="bg-purple-50 rounded-xl p-3 text-center">
+            <text class="text-xl font-bold text-purple-600 block">{{ emergencyTasks }}</text>
+            <text class="text-xs text-gray-500">应急任务</text>
+          </view>
+        </view>
+      </uni-card>
+
+      <!-- 应急任务列表 -->
+      <uni-card title="应急任务" icon="uniui-flag-filled">
+        <view v-if="loading" class="py-6 text-center">
+          <text class="text-gray-400">加载中...</text>
+        </view>
+        <view v-else-if="emergencyTaskList.length === 0">
+          <EmptyState message="暂无应急任务" />
+        </view>
+        <uni-list v-else border>
+          <uni-list-item 
+            v-for="task in emergencyTaskList" 
+            :key="task.id"
+            :title="task.projectName || '应急任务'"
+            :note="task.address"
+            right-text="应急"
+            show-arrow
+            @click="goToTaskDetail(task.id)"
+          >
+            <template #header>
+              <view class="w-10 h-10 rounded-xl bg-red-50 flex items-center justify-center mr-3">
+                <text class="uni-icons uniui-notification-filled text-red-500"></text>
+              </view>
+            </template>
+          </uni-list-item>
+        </uni-list>
+      </uni-card>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue'
+import TopBar from '../../components/common/TopBar.vue'
+import EmptyState from '../../components/common/EmptyState.vue'
+import UniCard from '../../components/uni-card/uni-card.vue'
+import UniList from '../../components/uni-list/uni-list.vue'
+import UniListItem from '../../components/uni-list/uni-list-item.vue'
+import { getTaskList } from '../../api/task'
+import { getStaffList } from '../../api/staff'
+import { getVehicleList } from '../../api/vehicle'
+
+const loading = ref(false)
+const taskList = ref<any[]>([])
+const staffList = ref<any[]>([])
+const vehicleList = ref<any[]>([])
+
+const emergencyTaskList = computed(() => taskList.value.filter(t => t.type === 'emergency'))
+
+const standbyTeams = computed(() => {
+  return staffList.value.filter(s => s.status === 'on_duty').length
+})
+
+const standbyVehicles = computed(() => {
+  return vehicleList.value.filter(v => v.status === 'available').length
+})
+
+const emergencyTasks = computed(() => emergencyTaskList.value.length)
+
+function goToTaskDetail(taskId: string) {
+  uni.navigateTo({ url: `/pages/dispatch/taskDetail?id=${taskId}` })
+}
+
+onMounted(async () => {
+  loading.value = true
+  try {
+    const [taskRes, staffRes, vehicleRes] = await Promise.all([
+      getTaskList(),
+      getStaffList(),
+      getVehicleList(),
+    ])
+    taskList.value = taskRes.rows || []
+    staffList.value = staffRes.rows || []
+    vehicleList.value = vehicleRes.rows || []
+  } catch (error) {
+    console.error('获取数据失败:', error)
+  } finally {
+    loading.value = false
+  }
+})
+</script>

+ 76 - 0
src/pages/dispatch/helpCenter.vue

@@ -0,0 +1,76 @@
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="帮助中心" show-back />
+
+    <view class="px-4 py-4">
+      <!-- 常见问题 -->
+      <view class="card p-4 mb-4">
+        <text class="text-base font-semibold text-gray-800 mb-4 block"> 常见问题</text>
+        <view v-if="faqs.length > 0" class="space-y-3">
+          <view
+            v-for="faq in faqs"
+            :key="faq.id"
+            class="pb-3 border-b border-gray-100 last:border-b-0 last:pb-0"
+          >
+            <view class="flex items-center justify-between" @click="toggleFaq(faq.id)">
+              <text class="text-sm text-gray-800 flex-1">{{ faq.question }}</text>
+              <text class="text-gray-400 ml-2">{{ expandedFaq === faq.id ? '' : '' }}</text>
+            </view>
+            <view v-if="expandedFaq === faq.id" class="mt-2 bg-gray-50 rounded-lg p-3">
+              <text class="text-sm text-gray-600">{{ faq.answer }}</text>
+            </view>
+          </view>
+        </view>
+        <EmptyState v-else message="暂无常见问题" />
+      </view>
+
+      <!-- 知识库 -->
+      <view class="card p-4">
+        <text class="text-base font-semibold text-gray-800 mb-4 block"> 知识库</text>
+        <view v-if="knowledges.length > 0" class="space-y-3">
+          <view
+            v-for="knowledge in knowledges"
+            :key="knowledge.id"
+            class="pb-3 border-b border-gray-100 last:border-b-0 last:pb-0"
+            @click="goToKnowledgeDetail(knowledge.id)"
+          >
+            <view class="flex items-start">
+              <view class="w-8 h-8 bg-blue-100 rounded-lg flex items-center justify-center mr-3 flex-shrink-0">
+                <text class="text-blue-500 text-sm"></text>
+              </view>
+              <view class="flex-1 min-w-0">
+                <text class="text-sm font-medium text-gray-800 block">{{ knowledge.title }}</text>
+                <text class="text-xs text-gray-500 mt-1 block">{{ knowledge.summary }}</text>
+                <text class="text-xs text-blue-500 mt-1 block">{{ knowledge.category }}</text>
+              </view>
+            </view>
+          </view>
+        </view>
+        <EmptyState v-else message="暂无知识库内容" />
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { computed, ref } from 'vue'
+import TopBar from '../../components/common/TopBar.vue'
+import EmptyState from '../../components/common/EmptyState.vue'
+import { useCommonStore } from '../../stores/common'
+import { navigateTo, showToast } from '../../utils'
+
+const commonStore = useCommonStore()
+const expandedFaq = ref<string | null>(null)
+
+const faqs = computed(() => commonStore.faqs)
+const knowledges = computed(() => commonStore.knowledges)
+
+function toggleFaq(id: string) {
+  expandedFaq.value = expandedFaq.value === id ? null : id
+}
+
+function goToKnowledgeDetail(id: string) {
+  commonStore.setCurrentKnowledge(id)
+  showToast('知识详情:' + commonStore.currentKnowledge?.title)
+}
+</script>

+ 177 - 0
src/pages/dispatch/home.vue

@@ -0,0 +1,177 @@
+<template>
+  <view class="app-container">
+    <!-- 顶部区域 -->
+    <view class="relative overflow-hidden">
+      <view class="absolute inset-0 bg-gradient-to-br from-indigo-500 to-purple-600"></view>
+      <view class="absolute -top-20 -right-20 w-60 h-60 bg-white opacity-5 rounded-full"></view>
+      <view class="absolute -bottom-10 -left-10 w-40 h-40 bg-white opacity-5 rounded-full"></view>
+      
+      <view class="relative px-5 pt-12 pb-8">
+        <view class="flex items-center justify-between">
+          <view class="flex items-center">
+            <view class="w-12 h-12 bg-white/20 backdrop-blur rounded-2xl flex items-center justify-center mr-3">
+              <text class="uni-icons uniui-person-filled text-white text-xl"></text>
+            </view>
+            <view>
+              <text class="text-white/70 text-xs block">调度中心</text>
+              <text class="text-white text-lg font-bold">控制台</text>
+            </view>
+          </view>
+          <button class="w-10 h-10 bg-white/15 backdrop-blur rounded-full flex items-center justify-center relative" @click="goToNotification">
+            <text class="uni-icons uniui-notification-filled text-white text-lg"></text>
+          </button>
+        </view>
+      </view>
+    </view>
+
+    <!-- 主内容区域 -->
+    <view class="px-4 -mt-5 space-y-4 pb-24">
+      <!-- 统计卡片 -->
+      <uni-card title="今日概览" :is-shadow="true">
+        <view class="grid grid-cols-3 gap-3">
+          <view class="bg-indigo-50 rounded-2xl p-4 text-center" @click="goToTaskList">
+            <text class="stat-number text-indigo-600 block">{{ taskStore.total }}</text>
+            <text class="text-desc mt-1 block">今日任务</text>
+          </view>
+          <view class="bg-orange-50 rounded-2xl p-4 text-center" @click="goToTaskList">
+            <text class="stat-number text-orange-500 block">{{ taskStore.pendingCount }}</text>
+            <text class="text-desc mt-1 block">待排班</text>
+          </view>
+          <view class="bg-red-50 rounded-2xl p-4 text-center" @click="goToEmergencyTeam">
+            <text class="stat-number text-red-500 block">{{ emergencyCount }}</text>
+            <text class="text-desc mt-1 block">应急中</text>
+          </view>
+        </view>
+      </uni-card>
+
+      <!-- 快捷功能 -->
+      <uni-card title="快捷功能" :is-shadow="true">
+        <view class="grid grid-cols-4 gap-4">
+          <view class="flex flex-col items-center" @click="goToSchedule">
+            <view class="w-14 h-14 bg-indigo-50 rounded-2xl flex items-center justify-center mb-2 transition-transform active:scale-95">
+              <text class="uni-icons uniui-calendar-filled text-indigo-500 text-2xl"></text>
+            </view>
+            <text class="text-xs text-gray-600 font-medium">排班</text>
+          </view>
+          <view class="flex flex-col items-center" @click="goToTaskList">
+            <view class="w-14 h-14 bg-green-50 rounded-2xl flex items-center justify-center mb-2 transition-transform active:scale-95">
+              <text class="uni-icons uniui-flag-filled text-green-500 text-2xl"></text>
+            </view>
+            <text class="text-xs text-gray-600 font-medium">任务</text>
+          </view>
+          <view class="flex flex-col items-center" @click="goToVehicleList">
+            <view class="w-14 h-14 bg-blue-50 rounded-2xl flex items-center justify-center mb-2 transition-transform active:scale-95">
+              <text class="uni-icons uniui-cart-filled text-blue-500 text-2xl"></text>
+            </view>
+            <text class="text-xs text-gray-600 font-medium">车辆</text>
+          </view>
+          <view class="flex flex-col items-center" @click="goToVisualization">
+            <view class="w-14 h-14 bg-purple-50 rounded-2xl flex items-center justify-center mb-2 transition-transform active:scale-95">
+              <text class="uni-icons uniui-map-filled text-purple-500 text-2xl"></text>
+            </view>
+            <text class="text-xs text-gray-600 font-medium">数据</text>
+          </view>
+        </view>
+      </uni-card>
+
+      <!-- 最新任务 -->
+      <uni-card 
+        title="最新任务" 
+        extra="查看全部" 
+        :is-shadow="true"
+        @click-extra="goToTaskList"
+      >
+        <view v-if="taskStore.tasks.length === 0" class="py-6 text-center">
+          <text class="text-desc">暂无任务</text>
+        </view>
+        <uni-list v-else border="false">
+          <uni-list-item 
+            v-for="task in taskStore.tasks.slice(0, 3)" 
+            :key="task.id"
+            :title="task.projectName"
+            :note="task.scheduleDate"
+            show-arrow
+            @click="goToTaskDetail(task.id)"
+          >
+            <template #header>
+              <view class="w-10 h-10 bg-indigo-50 rounded-xl flex items-center justify-center mr-3">
+                <text class="uni-icons uniui-flag text-indigo-500"></text>
+              </view>
+            </template>
+            <template #footer>
+              <StatusTag :status="task.status" />
+            </template>
+          </uni-list-item>
+        </uni-list>
+      </uni-card>
+    </view>
+
+    <!-- 底部导航 -->
+    <BottomNav :items="navItems" :currentPath="currentPath" @navigate="onNavChange" />
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue'
+import { useTaskStore } from '../../stores/task'
+import BottomNav from '../../components/common/BottomNav.vue'
+import StatusTag from '../../components/common/StatusTag.vue'
+import UniCard from '../../components/uni-card/uni-card.vue'
+import UniList from '../../components/uni-list/uni-list.vue'
+import UniListItem from '../../components/uni-list/uni-list-item.vue'
+
+const taskStore = useTaskStore()
+const currentPath = ref('/pages/dispatch/home')
+
+const emergencyCount = computed(() => taskStore.tasks.filter(t => t.type === 'emergency').length)
+
+const navItems = [
+  { name: '首页', path: '/pages/dispatch/home', icon: 'uniui-home-filled' },
+  { name: '任务', path: '/pages/dispatch/taskList', icon: 'uniui-flag-filled' },
+  { name: '我的', path: '/pages/dispatch/myPage', icon: 'uniui-person-filled' },
+]
+
+function onNavChange(path: string) {
+  uni.reLaunch({ url: path })
+}
+
+function goToTaskList() {
+  uni.navigateTo({ url: '/pages/dispatch/taskList' })
+}
+
+function goToTaskDetail(taskId: string) {
+  taskStore.setCurrentTask(taskId)
+  uni.navigateTo({ url: '/pages/dispatch/taskDetail' })
+}
+
+function goToSchedule() {
+  uni.navigateTo({ url: '/pages/dispatch/schedule' })
+}
+
+function goToVehicleList() {
+  uni.navigateTo({ url: '/pages/dispatch/vehicleList' })
+}
+
+function goToVisualization() {
+  uni.navigateTo({ url: '/pages/dispatch/visualization' })
+}
+
+function goToEmergencyTeam() {
+  uni.navigateTo({ url: '/pages/dispatch/emergencyTeam' })
+}
+
+function goToNotification() {
+  uni.navigateTo({ url: '/pages/dispatch/notification' })
+}
+
+onMounted(() => {
+  taskStore.fetchTasks()
+})
+</script>
+
+<style scoped>
+.stat-number {
+  font-size: 24px;
+  font-weight: 800;
+}
+</style>

+ 120 - 0
src/pages/dispatch/myPage.vue

@@ -0,0 +1,120 @@
+<template>
+  <view class="app-container">
+    <!-- 顶部用户信息区域 -->
+    <view class="bg-gradient-to-br from-blue-900 to-blue-800 text-white pt-10 pb-6 px-5">
+      <view class="flex items-center gap-4">
+        <view class="w-14 h-14 bg-white/20 rounded-full flex items-center justify-center text-2xl">
+          <text></text>
+        </view>
+        <view>
+          <text class="text-lg font-bold block">{{ userName || '李调度' }}</text>
+          <text class="text-blue-200 text-xs">调度中心 · 工号 DIS001</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 功能列表 -->
+    <view class="px-4 -mt-3 space-y-4 pb-4">
+      <!-- 基本信息卡片 -->
+      <view class="bg-white rounded-xl p-4 shadow-sm">
+        <view class="flex justify-between items-center py-2 border-b border-gray-100">
+          <text class="text-gray-500 text-sm"> 部门</text>
+          <text class="text-sm">调度中心</text>
+        </view>
+        <view class="flex justify-between items-center py-2 border-b border-gray-100">
+          <text class="text-gray-500 text-sm">️ 职位</text>
+          <text class="text-sm">调度主管</text>
+        </view>
+        <view class="flex justify-between items-center py-2">
+          <text class="text-gray-500 text-sm"> 电话</text>
+          <text class="text-sm">{{ userPhone || '138****8002' }}</text>
+        </view>
+      </view>
+
+      <!-- 功能入口卡片 -->
+      <view class="bg-white rounded-xl p-4 shadow-sm">
+        <view class="flex justify-between items-center py-3 border-b border-gray-100" @click="goToBasicInfo">
+          <text class="text-sm text-gray-700"> 基本信息</text>
+          <text class="text-gray-300 text-xs">></text>
+        </view>
+        <view class="flex justify-between items-center py-3 border-b border-gray-100" @click="goToChangePassword">
+          <text class="text-sm text-gray-700"> 修改密码</text>
+          <text class="text-gray-300 text-xs">></text>
+        </view>
+        <view class="flex justify-between items-center py-3 border-b border-gray-100" @click="goToNotification">
+          <text class="text-sm text-gray-700"> 消息通知</text>
+          <view class="flex items-center">
+            <view v-if="unreadCount > 0" class="w-2 h-2 bg-red-500 rounded-full mr-2"></view>
+            <text class="text-gray-300 text-xs">></text>
+          </view>
+        </view>
+        <view class="flex justify-between items-center py-3" @click="goToHelpCenter">
+          <text class="text-sm text-gray-700"> 帮助中心</text>
+          <text class="text-gray-300 text-xs">></text>
+        </view>
+      </view>
+
+      <!-- 退出登录按钮 -->
+      <button class="w-full py-3 mt-4 bg-red-500 text-white rounded-xl font-medium" @click="logout">
+        退出登录
+      </button>
+    </view>
+
+    <!-- 底部导航 -->
+    <BottomNav :items="navItems" :currentPath="currentPath" @navigate="onNavChange" />
+  </view>
+</template>
+
+<script setup lang="ts">
+import { computed, ref } from 'vue'
+import BottomNav from '../../components/common/BottomNav.vue'
+import { useAuthStore } from '../../stores/auth'
+import { useNotificationStore } from '../../stores/notification'
+import { navigateTo, showModal, showToast } from '../../utils'
+
+const currentPath = ref('/pages/dispatch/myPage')
+const authStore = useAuthStore()
+const notificationStore = useNotificationStore()
+
+const userName = computed(() => authStore.userName)
+const userPhone = computed(() => authStore.userInfo?.phone)
+const unreadCount = computed(() => notificationStore.unreadCount)
+
+const navItems = [
+  { text: '首页', path: '/pages/dispatch/home', icon: '' },
+  { text: '任务', path: '/pages/dispatch/taskList', icon: '' },
+  { text: '我的', path: '/pages/dispatch/myPage', icon: '' },
+]
+
+function onNavChange(path: string) {
+  navigateTo(path)
+}
+
+function goToBasicInfo() {
+  navigateTo('/pages/dispatch/basicInfo')
+}
+
+function goToChangePassword() {
+  navigateTo('/pages/dispatch/changePassword')
+}
+
+function goToNotification() {
+  navigateTo('/pages/dispatch/notification')
+}
+
+function goToHelpCenter() {
+  navigateTo('/pages/dispatch/helpCenter')
+}
+
+async function logout() {
+  const res = await showModal({
+    title: '确认退出',
+    content: '确定要退出登录吗?'
+  })
+  if (res.confirm) {
+    authStore.logout()
+    showToast('已退出登录')
+    uni.reLaunch({ url: '/pages/login/login' })
+  }
+}
+</script>

+ 47 - 0
src/pages/dispatch/noticeDetail.vue

@@ -0,0 +1,47 @@
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="通知详情" show-back />
+
+    <view v-if="notice" class="px-4 py-4">
+      <view class="card p-4">
+        <text class="text-lg font-semibold text-gray-800 mb-2 block">{{ notice.title }}</text>
+        <text class="text-xs text-gray-400 mb-4 block">{{ formatDate(notice.createTime, 'YYYY-MM-DD HH:mm') }}</text>
+        <view class="border-t border-gray-100 pt-4">
+          <text class="text-sm text-gray-600 leading-relaxed block">{{ notice.content }}</text>
+        </view>
+      </view>
+    </view>
+
+    <EmptyState v-else message="公告不存在" />
+  </view>
+</template>
+
+<script setup lang="ts">
+import { computed, ref } from 'vue'
+import { onLoad } from '@dcloudio/uni-app'
+import TopBar from '../../components/common/TopBar.vue'
+import EmptyState from '../../components/common/EmptyState.vue'
+import { formatDate } from '../../utils'
+
+interface Notice {
+  id: string
+  title: string
+  content: string
+  createTime: string
+}
+
+const allNotices: Notice[] = [
+  { id: 'n1', title: '关于调度系统升级的通知', content: '为了提升系统性能和用户体验,我们将于本周末对调度系统进行升级维护。升级期间系统可能短暂不可用,请提前做好工作安排。升级后新增排班自动提醒功能,敬请期待。', createTime: '2026-01-20T09:00:00' },
+  { id: 'n2', title: '春节期间值班安排公告', content: '春节将至,为确保节日期间服务不中断,请各班组做好值班安排。具体值班表已发送至各班组负责人,如有疑问请联系调度中心。祝大家新春快乐!', createTime: '2026-01-15T09:00:00' },
+  { id: 'n3', title: '安全生产月活动通知', content: '本月为安全生产月,公司将开展安全培训和应急演练。请所有员工参加,提升安全意识和应急处理能力。具体安排另行通知。', createTime: '2026-02-28T10:00:00' },
+  { id: 'n4', title: '新员工培训计划安排', content: '本月将有新员工入职,请各班组安排老员工进行带教。培训内容包括:系统操作、安全规范、服务流程等。请各班组提前做好准备。', createTime: '2026-03-10T14:00:00' },
+]
+
+const notice = ref<Notice | null>(null)
+
+onLoad((options) => {
+  if (options?.id) {
+    notice.value = allNotices.find(n => n.id === options.id) || null
+  }
+})
+</script>

+ 114 - 0
src/pages/dispatch/noticeList.vue

@@ -0,0 +1,114 @@
+
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="通知公告" show-back />
+    
+    <!-- 筛选 -->
+    <view class="bg-white px-4 py-3 flex gap-2 overflow-x-auto hide-scrollbar">
+      <view 
+        v-for="type in filterTypes" 
+        :key="type.value"
+        class="filter-tab"
+        :class="{ active: currentFilter === type.value }"
+        @click="currentFilter = type.value"
+      >
+        {{ type.label }}
+      </view>
+    </view>
+
+    <!-- 通知列表 -->
+    <view class="px-4 mt-3">
+      <view v-if="loading" class="py-10 text-center">
+        <text class="text-gray-400">加载中...</text>
+      </view>
+      
+      <view v-else-if="filteredNotices.length === 0">
+        <EmptyState message="暂无通知" />
+      </view>
+      
+      <view v-else class="space-y-3">
+        <view 
+          v-for="notice in filteredNotices" 
+          :key="notice.noticeId"
+          class="card p-4 cursor-pointer"
+          @click="goToDetail(notice.noticeId)"
+        >
+          <view class="flex justify-between items-start mb-2">
+            <view class="flex items-center">
+              <view v-if="notice.priority === 3" class="w-2 h-2 bg-red-500 rounded-full mr-2"></view>
+              <text class="text-sm font-semibold text-gray-800">{{ notice.title }}</text>
+            </view>
+            <view class="tag" :class="getTypeClass(notice.type)">
+              {{ getTypeText(notice.type) }}
+            </view>
+          </view>
+          
+          <text class="text-xs text-gray-500 line-clamp-2 mb-2 block">{{ notice.content }}</text>
+          
+          <view class="flex justify-between items-center">
+            <text class="text-xs text-gray-400">{{ notice.publisherName }} · {{ formatDate(notice.publishTime) }}</text>
+            <text class="text-xs text-gray-400"> {{ notice.readCount }}</text>
+          </view>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue'
+import TopBar from '../../components/common/TopBar.vue'
+import EmptyState from '../../components/common/EmptyState.vue'
+import { getNoticeList } from '../../api/notice'
+import { formatDate } from '../../utils'
+
+const loading = ref(false)
+const notices = ref<any[]>([])
+const currentFilter = ref('all')
+
+const filterTypes = [
+  { value: 'all', label: '全部' },
+  { value: 'system', label: '系统' },
+  { value: 'emergency', label: '应急' },
+  { value: 'task', label: '任务' },
+]
+
+const filteredNotices = computed(() => {
+  if (currentFilter.value === 'all') return notices.value
+  return notices.value.filter(n => n.type === currentFilter.value)
+})
+
+function getTypeText(type: string) {
+  const map: Record<string, string> = {
+    system: '系统',
+    emergency: '应急',
+    task: '任务',
+  }
+  return map[type] || type
+}
+
+function getTypeClass(type: string) {
+  const map: Record<string, string> = {
+    system: 'tag-blue',
+    emergency: 'tag-red',
+    task: 'tag-green',
+  }
+  return map[type] || 'tag-gray'
+}
+
+function goToDetail(noticeId: number) {
+  uni.navigateTo({ url: `/pages/common/noticeDetail?id=${noticeId}` })
+}
+
+onMounted(async () => {
+  loading.value = true
+  try {
+    const res = await getNoticeList()
+    notices.value = res.rows || []
+  } catch (error) {
+    console.error('获取通知列表失败:', error)
+  } finally {
+    loading.value = false
+  }
+})
+</script>

+ 65 - 0
src/pages/dispatch/notification.vue

@@ -0,0 +1,65 @@
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="通知消息" show-back />
+    
+    <view class="px-4 mt-3">
+      <view v-if="loading" class="py-10 text-center">
+        <text class="text-gray-400">加载中...</text>
+      </view>
+      
+      <view v-else-if="noticeList.length === 0">
+        <EmptyState message="暂无通知" />
+      </view>
+      
+      <uni-list v-else border>
+        <uni-list-item 
+          v-for="notice in noticeList" 
+          :key="notice.noticeId"
+          :title="notice.title"
+          :note="notice.content"
+          :right-text="formatDate(notice.publishTime)"
+          show-arrow
+          @click="goToDetail(notice.noticeId)"
+        >
+          <template #header>
+            <view class="relative mr-3">
+              <view class="w-10 h-10 rounded-xl bg-blue-50 flex items-center justify-center">
+                <text class="uni-icons uniui-notification-filled text-blue-500"></text>
+              </view>
+              <view v-if="!notice.read" class="absolute -top-1 -right-1 w-3 h-3 bg-red-500 rounded-full border-2 border-white"></view>
+            </view>
+          </template>
+        </uni-list-item>
+      </uni-list>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from 'vue'
+import TopBar from '../../components/common/TopBar.vue'
+import EmptyState from '../../components/common/EmptyState.vue'
+import UniList from '../../components/uni-list/uni-list.vue'
+import UniListItem from '../../components/uni-list/uni-list-item.vue'
+import { getNoticeList } from '../../api/notice'
+import { formatDate } from '../../utils'
+
+const loading = ref(false)
+const noticeList = ref<any[]>([])
+
+function goToDetail(noticeId: number) {
+  uni.navigateTo({ url: `/pages/common/noticeDetail?id=${noticeId}` })
+}
+
+onMounted(async () => {
+  loading.value = true
+  try {
+    const res = await getNoticeList()
+    noticeList.value = res.rows || []
+  } catch (error) {
+    console.error('获取通知列表失败:', error)
+  } finally {
+    loading.value = false
+  }
+})
+</script>

+ 63 - 0
src/pages/dispatch/onDutyStaff.vue

@@ -0,0 +1,63 @@
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="在岗人员" show-back />
+
+    <view class="px-4 py-4">
+      <view v-if="onDutyStaff.length > 0" class="space-y-3">
+        <view
+          v-for="person in onDutyStaff"
+          :key="person.id"
+          class="card p-4"
+        >
+          <view class="flex items-center justify-between">
+            <view class="flex items-center">
+              <view class="w-12 h-12 rounded-full flex items-center justify-center mr-3"
+                :class="getPersonIconClass(person.id)"
+              >
+                <text class="text-xl"></text>
+              </view>
+              <view>
+                <text class="font-medium text-gray-800 block">{{ person.name }}</text>
+                <text class="text-sm text-gray-500">{{ person.team }} · {{ person.phone }}</text>
+              </view>
+            </view>
+            <text class="tag tag-green">{{ getPersonnelStatusText(person.status) }}</text>
+          </view>
+        </view>
+      </view>
+      <EmptyState v-else message="暂无在岗人员" />
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue'
+import TopBar from '../../components/common/TopBar.vue'
+import EmptyState from '../../components/common/EmptyState.vue'
+import { usePersonnelStore } from '../../stores/personnel'
+import { getPersonnelStatusText } from '../../utils'
+
+const personnelStore = usePersonnelStore()
+const onDutyStaff = computed(() => personnelStore.onDutyStaff)
+
+function getPersonIconClass(id: string) {
+  const colors = ['bg-blue-100', 'bg-green-100', 'bg-purple-100', 'bg-orange-100', 'bg-red-100']
+  const index = parseInt(id.replace(/\D/g, '')) % colors.length
+  return colors[index]
+}
+</script>
+
+<style scoped>
+.tag {
+  display: inline-block;
+  padding: 2px 8px;
+  border-radius: 4px;
+  font-size: 12px;
+  font-weight: 500;
+}
+
+.tag-green {
+  background: #e6f7f1;
+  color: #059669;
+}
+</style>

+ 133 - 0
src/pages/dispatch/personnel.vue

@@ -0,0 +1,133 @@
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="人员管理" show-back />
+    
+    <!-- 筛选标签 - 使用 uni-segmented-control -->
+    <uni-segmented-control 
+      :current="currentFilterIndex" 
+      :values="filterLabels"
+      style-type="text"
+      active-color="#4f8ef7"
+      @clickItem="onFilterChange"
+    />
+
+    <!-- 人员列表 -->
+    <view class="mt-2">
+      <view v-if="loading" class="py-10 text-center">
+        <text class="text-gray-400">加载中...</text>
+      </view>
+      
+      <view v-else-if="filteredStaff.length === 0">
+        <EmptyState message="暂无人员" />
+      </view>
+      
+      <uni-list v-else border>
+        <uni-list-item 
+          v-for="staff in filteredStaff" 
+          :key="staff.staffId"
+          :title="staff.name"
+          :note="staff.phone + ' | ' + (staff.teamName || '未分配班组')"
+          :right-text="getStatusText(staff.status)"
+          show-arrow
+          @click="showStaffDetail(staff)"
+        >
+          <template #header>
+            <view class="w-10 h-10 rounded-full flex items-center justify-center mr-3" :class="getStatusBgClass(staff.status)">
+              <text class="uni-icons uniui-person-filled text-lg" :class="getStatusIconClass(staff.status)"></text>
+            </view>
+          </template>
+        </uni-list-item>
+      </uni-list>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue'
+import TopBar from '../../components/common/TopBar.vue'
+import EmptyState from '../../components/common/EmptyState.vue'
+import UniList from '../../components/uni-list/uni-list.vue'
+import UniListItem from '../../components/uni-list/uni-list-item.vue'
+import UniSegmentedControl from '../../components/uni-segmented-control/uni-segmented-control.vue'
+import { getStaffList } from '../../api/staff'
+
+const loading = ref(false)
+const staffList = ref<any[]>([])
+const currentFilterIndex = ref(0)
+
+const filterStatuses = [
+  { value: 'all', label: '全部' },
+  { value: 'on_duty', label: '在岗' },
+  { value: 'working', label: '施工中' },
+  { value: 'rest', label: '休息' },
+]
+
+const filterLabels = filterStatuses.map(s => s.label)
+
+const filteredStaff = computed(() => {
+  const filterValue = filterStatuses[currentFilterIndex.value].value
+  if (filterValue === 'all') return staffList.value
+  return staffList.value.filter(s => s.status === filterValue)
+})
+
+function getStatusText(status: string) {
+  const map: Record<string, string> = {
+    on_duty: '在岗',
+    working: '施工中',
+    rest: '休息',
+  }
+  return map[status] || status
+}
+
+function getStatusBgClass(status: string) {
+  const map: Record<string, string> = {
+    on_duty: 'bg-green-50',
+    working: 'bg-blue-50',
+    rest: 'bg-orange-50',
+  }
+  return map[status] || 'bg-gray-50'
+}
+
+function getStatusIconClass(status: string) {
+  const map: Record<string, string> = {
+    on_duty: 'text-green-500',
+    working: 'text-blue-500',
+    rest: 'text-orange-500',
+  }
+  return map[status] || 'text-gray-400'
+}
+
+function onFilterChange(index: number) {
+  currentFilterIndex.value = index
+}
+
+function showStaffDetail(staff: any) {
+  uni.showModal({
+    title: staff.name,
+    content: `电话: ${staff.phone}\n班组: ${staff.teamName || '未分配'}\n状态: ${getStatusText(staff.status)}`,
+    showCancel: false
+  })
+}
+
+onMounted(async () => {
+  await fetchStaff()
+})
+
+async function fetchStaff() {
+  loading.value = true
+  try {
+    const res = await getStaffList()
+    staffList.value = res.rows || []
+  } catch (error) {
+    console.error('获取人员列表失败:', error)
+  } finally {
+    loading.value = false
+  }
+}
+
+// 下拉刷新
+onPullDownRefresh(async () => {
+  await fetchStaff()
+  uni.stopPullDownRefresh()
+})
+</script>

+ 223 - 0
src/pages/dispatch/publishTask.vue

@@ -0,0 +1,223 @@
+<template>
+  <view class="app-container pb-24">
+    <TopBar title="发布任务" show-back />
+
+    <view class="px-4 py-4 space-y-4">
+      <!-- 项目基本信息 -->
+      <uni-card title="项目信息" icon="uniui-flag-filled" :is-shadow="true">
+        <view class="space-y-4 pt-2">
+          <view>
+            <text class="text-sm text-gray-600 block mb-2">
+              <text class="text-red-500">*</text> 选择项目
+            </text>
+            <picker mode="selector" :range="projectNames" :value="selectedProjectIndex" @change="onProjectChange">
+              <view class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm flex items-center justify-between bg-white">
+                <text :class="selectedProject ? 'text-gray-800' : 'text-gray-400'">{{ selectedProject || '请选择项目' }}</text>
+                <text class="uni-icons uniui-arrowright text-gray-400"></text>
+              </view>
+            </picker>
+          </view>
+
+          <view v-if="selectedProjectObj" class="bg-green-50 rounded-xl p-3">
+            <view class="flex items-center justify-between mb-2">
+              <text class="text-green-600 text-sm font-medium">已选择:{{ selectedProjectObj.name }}</text>
+              <text class="text-green-500 text-xs" @click="selectedProject = ''; selectedProjectObj = null">重新选择</text>
+            </view>
+            <view class="space-y-1">
+              <view class="flex justify-between text-sm">
+                <text class="text-gray-500">合作类型:</text>
+                <text class="text-gray-700">{{ selectedProjectObj.type }}</text>
+              </view>
+              <view class="flex justify-between text-sm">
+                <text class="text-gray-500">项目地址:</text>
+                <text class="text-gray-700">{{ selectedProjectObj.address }}</text>
+              </view>
+            </view>
+          </view>
+        </view>
+      </uni-card>
+
+      <!-- 服务信息 -->
+      <uni-card title="服务信息" icon="uniui-calendar-filled" :is-shadow="true">
+        <view class="space-y-4 pt-2">
+          <view>
+            <text class="text-sm text-gray-600 block mb-2">
+              <text class="text-red-500">*</text> 服务时间
+            </text>
+            <picker mode="date" :value="serviceDate" @change="onServiceDateChange">
+              <view class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm flex items-center justify-between bg-white">
+                <text :class="serviceDate ? 'text-gray-800' : 'text-gray-400'">{{ serviceDate || '请选择日期' }}</text>
+                <text class="uni-icons uniui-calendar-filled text-gray-400"></text>
+              </view>
+            </picker>
+          </view>
+
+          <view>
+            <text class="text-sm text-gray-600 block mb-2">
+              <text class="text-red-500">*</text> 故障位置
+            </text>
+            <input v-model="faultLocation" class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm text-gray-800 bg-white" placeholder="请输入故障位置" />
+          </view>
+
+          <view>
+            <text class="text-sm text-gray-600 block mb-2">故障描述</text>
+            <textarea v-model="faultDesc" class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm text-gray-800 bg-white" style="min-height: 80px;" placeholder="请描述故障情况..." />
+          </view>
+
+          <view>
+            <text class="text-sm text-gray-600 block mb-2">服务方式</text>
+            <picker mode="selector" :range="serviceModes" :value="selectedServiceModeIndex" @change="onServiceModeChange">
+              <view class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm flex items-center justify-between bg-white">
+                <text :class="serviceMode ? 'text-gray-800' : 'text-gray-400'">{{ serviceMode || '请选择服务方式' }}</text>
+                <text class="uni-icons uniui-arrowright text-gray-400"></text>
+              </view>
+            </picker>
+          </view>
+
+          <view>
+            <text class="text-sm text-gray-600 block mb-2">紧急程度</text>
+            <view class="flex gap-3">
+              <view
+                class="flex-1 py-2 px-3 rounded-lg border text-center text-sm"
+                :class="urgentLevel === 'normal' ? 'border-blue-500 bg-blue-50 text-blue-600' : 'border-gray-200 text-gray-600'"
+                @click="urgentLevel = 'normal'"
+              >
+                <text class="uni-icons mr-1" :class="urgentLevel === 'normal' ? 'uniui-checkmarkempty' : ''"></text>
+                普通
+              </view>
+              <view
+                class="flex-1 py-2 px-3 rounded-lg border text-center text-sm"
+                :class="urgentLevel === 'high' ? 'border-red-500 bg-red-50 text-red-600' : 'border-gray-200 text-gray-600'"
+                @click="urgentLevel = 'high'"
+              >
+                <text class="uni-icons mr-1" :class="urgentLevel === 'high' ? 'uniui-checkmarkempty' : ''"></text>
+                加急
+              </view>
+            </view>
+          </view>
+          <!-- 图片上传 -->
+          <view>
+            <text class="text-sm text-gray-600 block mb-2">现场照片</text>
+            <ImageUploader v-model="taskImages" :max-count="6" tip="最多上传6张照片" />
+          </view>
+
+        </view>
+      </uni-card>
+
+      <!-- 施工信息 -->
+      <uni-card title="施工信息" icon="uniui-cart-filled" :is-shadow="true">
+        <view class="space-y-4 pt-2">
+          <view>
+            <text class="text-sm text-gray-600 block mb-2">施工设施</text>
+            <input v-model="facility" class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm text-gray-800 bg-white" placeholder="请输入施工设施" />
+          </view>
+
+          <view>
+            <text class="text-sm text-gray-600 block mb-2">建议出车</text>
+            <picker mode="selector" :range="vehicleOptions" :value="selectedVehicleIndex" @change="onVehicleSelectChange">
+              <view class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm flex items-center justify-between bg-white">
+                <text :class="selectedVehicle ? 'text-gray-800' : 'text-gray-400'">{{ selectedVehicle || '请选择车辆' }}</text>
+                <text class="uni-icons uniui-arrowright text-gray-400"></text>
+              </view>
+            </picker>
+          </view>
+
+          <view>
+            <text class="text-sm text-gray-600 block mb-2">预计工时</text>
+            <input v-model="estimatedHours" class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm text-gray-800 bg-white" placeholder="例如:2小时" />
+          </view>
+
+          <view>
+            <text class="text-sm text-gray-600 block mb-2">验收要求</text>
+            <input v-model="acceptReq" class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm text-gray-800 bg-white" placeholder="请输入验收要求" />
+          </view>
+
+          <view>
+            <text class="text-sm text-gray-600 block mb-2">备注</text>
+            <textarea v-model="remark" class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm text-gray-800 bg-white" style="min-height: 80px;" placeholder="请输入备注信息..." />
+          </view>
+        </view>
+      </uni-card>
+
+      <!-- 提交按钮 -->
+      <button class="w-full py-3 bg-blue-500 text-white rounded-xl font-medium flex items-center justify-center" @click="submitTask">
+        <text class="uni-icons uniui-plusempty mr-2"></text>
+        发布任务
+      </button>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { computed, ref } from 'vue'
+import TopBar from '../../components/common/TopBar.vue'
+import UniCard from '../../components/uni-card/uni-card.vue'
+import ImageUploader from '../../components/common/ImageUploader.vue'
+
+const selectedProject = ref('')
+const selectedProjectObj = ref<any>(null)
+const serviceDate = ref('')
+const faultLocation = ref('')
+const faultDesc = ref('')
+const serviceMode = ref('')
+const urgentLevel = ref<'normal' | 'high'>('normal')
+const facility = ref('')
+const selectedVehicle = ref('')
+const estimatedHours = ref('')
+const acceptReq = ref('')
+const remark = ref('')
+const taskImages = ref<string[]>([])
+
+// 模拟项目数据
+const mockProjects = [
+  { name: '积丹白鹭小区', type: '包年', address: 'XX市XX区XX路123号' },
+  { name: '俊发城', type: '单次', address: 'XX市XX区XX路456号' },
+  { name: '万科金域华府', type: '季度', address: 'XX市XX区XX路789号' },
+]
+
+const projectNames = mockProjects.map(p => p.name)
+const serviceModes = ['常规', '应急']
+const vehicleOptions = ['清洁车', '垃圾清运车', '高压清洗车', '洒水车']
+
+const selectedProjectIndex = computed(() => projectNames.indexOf(selectedProject.value))
+const selectedServiceModeIndex = computed(() => serviceModes.indexOf(serviceMode.value))
+const selectedVehicleIndex = computed(() => vehicleOptions.indexOf(selectedVehicle.value))
+
+function onProjectChange(e: any) {
+  const index = e.detail.value
+  selectedProject.value = projectNames[index]
+  selectedProjectObj.value = mockProjects[index]
+}
+
+function onServiceDateChange(e: any) {
+  serviceDate.value = e.detail.value
+}
+
+function onServiceModeChange(e: any) {
+  serviceMode.value = serviceModes[e.detail.value]
+}
+
+function onVehicleSelectChange(e: any) {
+  selectedVehicle.value = vehicleOptions[e.detail.value]
+}
+
+function submitTask() {
+  if (!selectedProject.value) {
+    uni.showToast({ title: '请选择项目', icon: 'none' })
+    return
+  }
+  if (!serviceDate.value) {
+    uni.showToast({ title: '请选择服务时间', icon: 'none' })
+    return
+  }
+  if (!faultLocation.value) {
+    uni.showToast({ title: '请输入故障位置', icon: 'none' })
+    return
+  }
+
+  uni.showToast({ title: '任务发布成功', icon: 'success' })
+  setTimeout(() => {
+    uni.navigateTo({ url: '/pages/dispatch/taskList' })
+  }, 1500)
+}
+</script>

+ 256 - 0
src/pages/dispatch/schedule.vue

@@ -0,0 +1,256 @@
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="排班管理" show-back />
+    
+    <!-- 月份切换 -->
+    <view class="bg-white px-4 py-3 flex items-center justify-between border-b border-gray-100">
+      <view class="flex items-center">
+        <view class="p-2" @click="changeMonth(-1)">
+          <text class="uni-icons uniui-arrowleft text-gray-600"></text>
+        </view>
+        <text class="text-base font-semibold text-gray-800 mx-2">{{ currentYear }}年{{ currentMonth }}月</text>
+        <view class="p-2" @click="changeMonth(1)">
+          <text class="uni-icons uniui-arrowright text-gray-600"></text>
+        </view>
+      </view>
+      <view class="text-sm text-blue-500 flex items-center" @click="goToToday">
+        <text class="uni-icons uniui-calendar-filled mr-1"></text>
+        今天
+      </view>
+    </view>
+
+    <!-- 日历 -->
+    <view class="bg-white px-4 py-3">
+      <!-- 星期标题 -->
+      <view class="flex mb-2">
+        <view v-for="day in weekDays" :key="day" class="flex-1 text-center">
+          <text class="text-xs text-gray-400">{{ day }}</text>
+        </view>
+      </view>
+      
+      <!-- 日期 -->
+      <view v-for="(week, weekIndex) in calendarWeeks" :key="weekIndex" class="flex mb-2">
+        <view 
+          v-for="date in week" 
+          :key="date.day + (date.isOtherMonth ? '-other' : '')"
+          class="flex-1 h-11 flex flex-col items-center justify-center relative mx-0.5"
+          :class="{ 
+            'opacity-30': date.isOtherMonth,
+            'bg-blue-500 rounded-xl shadow-md': date.isToday && !isSelected(date),
+            'bg-white border-2 border-blue-500 rounded-xl shadow-sm': isSelected(date) && !date.isToday,
+            'bg-blue-600 rounded-xl shadow-md': isSelected(date) && date.isToday,
+          }"
+          @click="selectDate(date)"
+        >
+          <text 
+            class="text-sm font-medium"
+            :class="{ 
+              'text-white': date.isToday || isSelected(date), 
+              'text-gray-800': !date.isToday && !isSelected(date) 
+            }"
+          >
+            {{ date.day }}
+          </text>
+          <view 
+            v-if="date.hasSchedule" 
+            class="w-1.5 h-1.5 rounded-full mt-0.5"
+            :class="date.isToday || isSelected(date) ? 'bg-white' : 'bg-blue-500'"
+          ></view>
+        </view>
+      </view>
+    </view>
+
+    <!-- 排班列表 -->
+    <view class="px-4 mt-4">
+      <view class="flex justify-between items-center mb-3">
+        <text class="text-base font-semibold text-gray-800">{{ selectedDate }} 排班</text>
+        <view class="text-blue-500 text-sm flex items-center" @click="addSchedule">
+          <text class="uni-icons uniui-plusempty mr-1"></text>
+          新增排班
+        </view>
+      </view>
+      
+      <view v-if="loading" class="py-10 text-center">
+        <text class="text-gray-400">加载中...</text>
+      </view>
+      
+      <view v-else-if="daySchedules.length === 0">
+        <EmptyState message="该日无排班" />
+      </view>
+      
+      <uni-list v-else border>
+        <uni-list-item 
+          v-for="schedule in daySchedules" 
+          :key="schedule.scheduleId"
+          :title="schedule.taskName"
+          :note="schedule.staffName + ' | ' + (schedule.vehicleNo || '未分配车辆')"
+          :right-text="getStatusText(schedule.status)"
+          show-arrow
+        >
+          <template #header>
+            <view class="w-10 h-10 rounded-xl flex items-center justify-center mr-3" :class="getStatusBgClass(schedule.status)">
+              <text class="uni-icons text-lg" :class="getStatusIconClass(schedule.status)"></text>
+            </view>
+          </template>
+        </uni-list-item>
+      </uni-list>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted, watch } from 'vue'
+import TopBar from '../../components/common/TopBar.vue'
+import EmptyState from '../../components/common/EmptyState.vue'
+import UniList from '../../components/uni-list/uni-list.vue'
+import UniListItem from '../../components/uni-list/uni-list-item.vue'
+import { getScheduleList } from '../../api/schedule'
+
+const loading = ref(false)
+const schedules = ref<any[]>([])
+const currentYear = ref(new Date().getFullYear())
+const currentMonth = ref(new Date().getMonth() + 1)
+const selectedDate = ref('')
+
+const weekDays = ['日', '一', '二', '三', '四', '五', '六']
+
+const calendarWeeks = computed(() => {
+  const year = currentYear.value
+  const month = currentMonth.value
+  const firstDay = new Date(year, month - 1, 1)
+  const lastDay = new Date(year, month, 0)
+  const firstWeekDay = firstDay.getDay()
+  const daysInMonth = lastDay.getDate()
+  
+  const weeks: any[] = []
+  let currentWeek: any[] = []
+  
+  const prevMonthDays = new Date(year, month - 1, 0).getDate()
+  for (let i = firstWeekDay - 1; i >= 0; i--) {
+    currentWeek.push({
+      day: prevMonthDays - i,
+      isOtherMonth: true,
+      isToday: false,
+      hasSchedule: false,
+    })
+  }
+  
+  for (let day = 1; day <= daysInMonth; day++) {
+    const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`
+    const isToday = dateStr === new Date().toISOString().split('T')[0]
+    const hasSchedule = schedules.value.some(s => s.scheduleDate === dateStr)
+    
+    currentWeek.push({
+      day,
+      isOtherMonth: false,
+      isToday,
+      hasSchedule,
+      dateStr,
+    })
+    
+    if (currentWeek.length === 7) {
+      weeks.push(currentWeek)
+      currentWeek = []
+    }
+  }
+  
+  if (currentWeek.length > 0) {
+    for (let i = 1; currentWeek.length < 7; i++) {
+      currentWeek.push({
+        day: i,
+        isOtherMonth: true,
+        isToday: false,
+        hasSchedule: false,
+      })
+    }
+    weeks.push(currentWeek)
+  }
+  
+  return weeks
+})
+
+const daySchedules = computed(() => {
+  if (!selectedDate.value) return []
+  return schedules.value.filter(s => s.scheduleDate === selectedDate.value)
+})
+
+function changeMonth(delta: number) {
+  let newMonth = currentMonth.value + delta
+  if (newMonth > 12) {
+    newMonth = 1
+    currentYear.value++
+  } else if (newMonth < 1) {
+    newMonth = 12
+    currentYear.value--
+  }
+  currentMonth.value = newMonth
+}
+
+function goToToday() {
+  const now = new Date()
+  currentYear.value = now.getFullYear()
+  currentMonth.value = now.getMonth() + 1
+  selectedDate.value = now.toISOString().split('T')[0]
+}
+
+function selectDate(date: any) {
+  if (!date.isOtherMonth) {
+    selectedDate.value = date.dateStr
+  }
+}
+
+function isSelected(date: any): boolean {
+  return date.dateStr === selectedDate.value
+}
+
+function getStatusText(status: string) {
+  const map: Record<string, string> = {
+    scheduled: '已排班',
+    completed: '已完成',
+    cancelled: '已取消',
+  }
+  return map[status] || status
+}
+
+function getStatusBgClass(status: string) {
+  const map: Record<string, string> = {
+    scheduled: 'bg-blue-50',
+    completed: 'bg-green-50',
+    cancelled: 'bg-red-50',
+  }
+  return map[status] || 'bg-gray-50'
+}
+
+function getStatusIconClass(status: string) {
+  const map: Record<string, string> = {
+    scheduled: 'uniui-calendar-filled text-blue-500',
+    completed: 'uniui-checkmarkempty text-green-500',
+    cancelled: 'uniui-close text-red-500',
+  }
+  return map[status] || 'uniui-calendar text-gray-400'
+}
+
+function addSchedule() {
+  uni.showToast({ title: '排班功能开发中', icon: 'none' })
+}
+
+async function fetchSchedules() {
+  loading.value = true
+  try {
+    const res = await getScheduleList(currentYear.value, currentMonth.value)
+    schedules.value = res.rows || []
+  } catch (error) {
+    console.error('获取排班列表失败:', error)
+  } finally {
+    loading.value = false
+  }
+}
+
+watch(() => [currentYear.value, currentMonth.value], fetchSchedules)
+
+onMounted(() => {
+  const today = new Date().toISOString().split('T')[0]
+  selectedDate.value = today
+  fetchSchedules()
+})
+</script>

+ 255 - 0
src/pages/dispatch/taskDetail.vue

@@ -0,0 +1,255 @@
+<template>
+  <view class="app-container pb-24">
+    <TopBar title="任务详情" show-back />
+
+    <view v-if="task" class="px-4 py-4">
+      <!-- 基本信息卡片 -->
+      <view class="card p-4 mb-4">
+        <view class="flex items-center justify-between mb-4">
+          <text class="text-base font-semibold text-gray-800">基本信息</text>
+          <StatusTag :status="task.status" />
+        </view>
+        <view class="space-y-3">
+          <view class="flex items-start">
+            <text class="text-gray-500 text-sm w-24"> 任务名称</text>
+            <text class="text-sm text-gray-800 flex-1">{{ task.projectName }}</text>
+          </view>
+          <view class="flex items-start">
+            <text class="text-gray-500 text-sm w-24"> 项目</text>
+            <text class="text-sm text-gray-800 flex-1">{{ task.projectName }}</text>
+          </view>
+          <view class="flex items-start">
+            <text class="text-gray-500 text-sm w-24"> 类型</text>
+            <text class="text-sm text-gray-800 flex-1">{{ getTypeText(task.type) }}</text>
+          </view>
+          <view class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">⏰ 创建时间</text>
+            <text class="text-sm text-gray-800 flex-1">{{ formatDate(task.createTime, 'YYYY-MM-DD HH:mm') }}</text>
+          </view>
+          <view v-if="task.assignee" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24"> 负责人</text>
+            <text class="text-sm text-gray-800 flex-1">{{ task.assignee }}</text>
+          </view>
+          <view v-if="task.vehicle" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24"> 车辆</text>
+            <text class="text-sm text-gray-800 flex-1">{{ task.vehicle }}</text>
+          </view>
+          <view v-if="task.team" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24"> 班组</text>
+            <text class="text-sm text-gray-800 flex-1">{{ task.team }}</text>
+          </view>
+          <view v-if="task.scheduledDate" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24"> 排班日期</text>
+            <text class="text-sm text-gray-800 flex-1">{{ task.scheduledDate }}</text>
+          </view>
+        </view>
+      </view>
+
+      <!-- 任务进度时间线 -->
+      <view class="card p-4 mb-4">
+        <text class="text-base font-semibold text-gray-800 mb-4 block">任务进度</text>
+        <view class="space-y-4">
+          <view v-for="(record, index) in task.timeline" :key="index" class="flex items-start">
+            <view class="flex flex-col items-center mr-3">
+              <view class="w-3 h-3 rounded-full" :class="index === 0 ? 'bg-green-500' : 'bg-blue-500'"></view>
+              <view v-if="index < task.timeline.length - 1" class="w-0.5 h-8 bg-gray-200 mt-1"></view>
+            </view>
+            <view class="flex-1 pb-2">
+              <text class="text-sm font-medium text-gray-800">{{ record.action }}</text>
+              <view class="flex justify-between mt-1">
+                <text class="text-xs text-gray-500">{{ record.operator }} · {{ getRoleText(record.role) }}</text>
+                <text class="text-xs text-gray-400">{{ formatDate(record.time, 'MM-DD HH:mm') }}</text>
+              </view>
+            </view>
+          </view>
+        </view>
+      </view>
+
+      <!-- 审核操作卡片(待审核状态显示) -->
+      <view v-if="task.status === 'pending'" class="card p-4 mb-4">
+        <text class="text-base font-semibold text-gray-800 mb-4 block">审核操作</text>
+        <view v-if="!showArrangeForm">
+          <text class="text-sm text-gray-500 mb-4 block">请确认该任务是否可以执行,然后进行后续安排。</text>
+          <view class="flex gap-3">
+            <button class="flex-1 py-3 bg-red-500 text-white rounded-lg text-sm font-medium" @click="rejectTask">驳回</button>
+            <button class="flex-1 py-3 bg-green-500 text-white rounded-lg text-sm font-medium" @click="confirmTask">确认</button>
+          </view>
+        </view>
+        <view v-else>
+          <text class="text-sm text-yellow-600 mb-4 block">️ 任务已确认,请安排班组和车辆</text>
+          <view class="form-group mb-4">
+            <text class="form-label">调度班组</text>
+            <picker mode="selector" :range="teamOptions" :value="selectedTeamIndex" @change="onTeamChange">
+              <view class="form-input flex items-center justify-between">
+                <text :class="selectedTeam ? 'text-gray-800' : 'text-gray-400'">{{ selectedTeam || '请选择班组' }}</text>
+                <text class="text-gray-400"></text>
+              </view>
+            </picker>
+          </view>
+          <view class="form-group mb-4">
+            <text class="form-label">调度车辆</text>
+            <picker mode="selector" :range="vehicleOptions" :value="selectedVehicleIndex" @change="onVehicleChange">
+              <view class="form-input flex items-center justify-between">
+                <text :class="selectedVehicle ? 'text-gray-800' : 'text-gray-400'">{{ selectedVehicle || '请选择车辆' }}</text>
+                <text class="text-gray-400"></text>
+              </view>
+            </picker>
+          </view>
+          <view class="form-group mb-4">
+            <text class="form-label">审核备注(可选)</text>
+            <textarea v-model="auditRemark" class="form-textarea" placeholder="请输入审核备注..."></textarea>
+          </view>
+          <view class="flex gap-3">
+            <button class="flex-1 py-3 bg-gray-200 text-gray-700 rounded-lg text-sm font-medium" @click="showArrangeForm = false">返回</button>
+            <button class="flex-1 py-3 bg-blue-500 text-white rounded-lg text-sm font-medium" @click="arrangeTeam">安排</button>
+          </view>
+        </view>
+      </view>
+
+      <!-- 待排班操作卡片(confirmed状态显示) -->
+      <view v-if="task.status === 'confirmed'" class="card p-4 mb-4">
+        <text class="text-base font-semibold text-gray-800 mb-4 block">安排班组</text>
+        <text class="text-sm text-yellow-600 mb-4 block">️ 任务已确认,请安排班组和车辆</text>
+        <view class="form-group mb-4">
+          <text class="form-label">调度班组</text>
+          <picker mode="selector" :range="teamOptions" :value="selectedTeamIndex" @change="onTeamChange">
+            <view class="form-input flex items-center justify-between">
+              <text :class="selectedTeam ? 'text-gray-800' : 'text-gray-400'">{{ selectedTeam || '请选择班组' }}</text>
+              <text class="text-gray-400"></text>
+            </view>
+          </picker>
+        </view>
+        <view class="form-group mb-4">
+          <text class="form-label">调度车辆</text>
+          <picker mode="selector" :range="vehicleOptions" :value="selectedVehicleIndex" @change="onVehicleChange">
+            <view class="form-input flex items-center justify-between">
+              <text :class="selectedVehicle ? 'text-gray-800' : 'text-gray-400'">{{ selectedVehicle || '请选择车辆' }}</text>
+              <text class="text-gray-400"></text>
+            </view>
+          </picker>
+        </view>
+        <view class="form-group mb-4">
+          <text class="form-label">审核备注(可选)</text>
+          <textarea v-model="auditRemark" class="form-textarea" placeholder="请输入审核备注..."></textarea>
+        </view>
+        <button class="w-full py-3 bg-blue-500 text-white rounded-lg text-sm font-medium" @click="arrangeTeam">安排</button>
+      </view>
+
+      <!-- 驳回原因(已驳回状态显示) -->
+      <view v-if="task.status === 'rejected'" class="card p-4 mb-4">
+        <text class="text-base font-semibold text-gray-800 mb-4 block">驳回原因</text>
+        <view class="p-3 bg-red-50 rounded-lg">
+          <text class="text-sm text-red-600">任务不符合执行条件,已驳回</text>
+        </view>
+      </view>
+    </view>
+
+    <EmptyState v-else message="任务不存在" />
+  </view>
+</template>
+
+<script setup lang="ts">
+import { computed, ref } from 'vue'
+import { onLoad } from '@dcloudio/uni-app'
+import TopBar from '../../components/common/TopBar.vue'
+import StatusTag from '../../components/common/StatusTag.vue'
+import EmptyState from '../../components/common/EmptyState.vue'
+import { useTaskStore } from '../../stores/task'
+import { useAuthStore } from '../../stores/auth'
+import { formatDate, getTypeText, getRoleText, showToast, showModal } from '../../utils'
+
+const taskStore = useTaskStore()
+const authStore = useAuthStore()
+
+const task = computed(() => taskStore.currentTask)
+const showArrangeForm = ref(false)
+const selectedTeam = ref('')
+const selectedVehicle = ref('')
+const auditRemark = ref('')
+
+const teamOptions = ['一班组', '二班组', '三班组', '应急组']
+const vehicleOptions = ['浙A·12345 清洁车', '浙A·67890 垃圾清运车', '浙A·11111 高压清洗车', '浙A·33333 清洁车']
+
+const selectedTeamIndex = computed(() => teamOptions.indexOf(selectedTeam.value))
+const selectedVehicleIndex = computed(() => vehicleOptions.indexOf(selectedVehicle.value))
+
+onLoad((options) => {
+  if (options?.id) {
+    taskStore.setCurrentTask(options.id)
+  }
+})
+
+function onTeamChange(e: any) {
+  selectedTeam.value = teamOptions[e.detail.value]
+}
+
+function onVehicleChange(e: any) {
+  selectedVehicle.value = vehicleOptions[e.detail.value]
+}
+
+async function confirmTask() {
+  showArrangeForm.value = true
+}
+
+async function rejectTask() {
+  const res = await showModal({
+    title: '确认驳回',
+    content: '确定要驳回该任务吗?'
+  })
+  if (res.confirm && task.value) {
+    taskStore.updateTaskStatus(task.value.id, 'rejected')
+    showToast('任务已驳回')
+  }
+}
+
+async function arrangeTeam() {
+  if (!selectedTeam.value) {
+    showToast('请选择班组')
+    return
+  }
+  if (!selectedVehicle.value) {
+    showToast('请选择车辆')
+    return
+  }
+  if (task.value) {
+    taskStore.updateTaskStatus(task.value.id, 'scheduled')
+    taskStore.addTaskLog(task.value.id, '排班完成', authStore.userName || '调度员', 'dispatch')
+    showToast('排班安排成功')
+    showArrangeForm.value = false
+  }
+}
+</script>
+
+<style scoped>
+.form-group {
+  margin-bottom: 16px;
+}
+
+.form-label {
+  display: block;
+  font-size: 14px;
+  color: #666;
+  margin-bottom: 8px;
+}
+
+.form-input {
+  width: 100%;
+  padding: 12px 16px;
+  border: 1px solid #e5e7eb;
+  border-radius: 8px;
+  font-size: 14px;
+  color: #333;
+  background: #fff;
+}
+
+.form-textarea {
+  width: 100%;
+  padding: 12px 16px;
+  border: 1px solid #e5e7eb;
+  border-radius: 8px;
+  font-size: 14px;
+  color: #333;
+  background: #fff;
+  min-height: 80px;
+}
+</style>

+ 93 - 0
src/pages/dispatch/taskList.vue

@@ -0,0 +1,93 @@
+
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="任务列表" />
+    
+    <!-- 筛选标签 -->
+    <view class="bg-white px-4 py-3 flex gap-2 overflow-x-auto hide-scrollbar">
+      <view 
+        v-for="status in filterStatuses" 
+        :key="status.value"
+        class="filter-tab"
+        :class="{ active: currentFilter === status.value }"
+        @click="currentFilter = status.value"
+      >
+        {{ status.label }}
+      </view>
+    </view>
+
+    <!-- 任务列表 -->
+    <view class="px-4 mt-3">
+      <view v-if="taskStore.loading" class="py-10 text-center">
+        <text class="text-gray-400">加载中...</text>
+      </view>
+      
+      <view v-else-if="filteredTasks.length === 0">
+        <EmptyState message="暂无任务" />
+      </view>
+      
+      <view v-else class="space-y-3">
+        <TaskCard 
+          v-for="task in filteredTasks" 
+          :key="task.id"
+          :task="task"
+          @click="goToDetail"
+        />
+      </view>
+    </view>
+
+    <!-- 新增任务按钮 -->
+    <view class="fixed bottom-20 right-4">
+      <button 
+        class="w-14 h-14 rounded-full bg-blue-500 text-white flex items-center justify-center shadow-lg text-2xl"
+        @click="goToPublishTask"
+      >
+        +
+      </button>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue'
+import { useTaskStore } from '../../stores/task'
+import TopBar from '../../components/common/TopBar.vue'
+import TaskCard from '../../components/task/TaskCard.vue'
+import EmptyState from '../../components/common/EmptyState.vue'
+
+const taskStore = useTaskStore()
+const currentFilter = ref('all')
+
+const filterStatuses = [
+  { value: 'all', label: '全部' },
+  { value: 'pending', label: '待确认' },
+  { value: 'confirmed', label: '待排班' },
+  { value: 'scheduled', label: '待出车' },
+  { value: 'constructing', label: '施工中' },
+  { value: 'completed', label: '已完成' },
+]
+
+const filteredTasks = computed(() => {
+  if (currentFilter.value === 'all') return taskStore.tasks
+  return taskStore.tasks.filter(t => t.status === currentFilter.value)
+})
+
+function goToDetail(taskId: string) {
+  taskStore.setCurrentTask(taskId)
+  uni.navigateTo({ url: '/pages/dispatch/taskDetail' })
+}
+
+function goToPublishTask() {
+  uni.navigateTo({ url: '/pages/dispatch/publishTask' })
+}
+
+onMounted(() => {
+  taskStore.fetchTasks()
+})
+
+// 下拉刷新
+onPullDownRefresh(async () => {
+  await taskStore.fetchTasks()
+  uni.stopPullDownRefresh()
+})
+</script>

+ 133 - 0
src/pages/dispatch/vehicleList.vue

@@ -0,0 +1,133 @@
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="车辆列表" show-back />
+    
+    <!-- 筛选标签 - 使用 uni-segmented-control -->
+    <uni-segmented-control 
+      :current="currentFilterIndex" 
+      :values="filterLabels"
+      style-type="text"
+      active-color="#4f8ef7"
+      @clickItem="onFilterChange"
+    />
+
+    <!-- 车辆列表 -->
+    <view class="mt-2">
+      <view v-if="loading" class="py-10 text-center">
+        <text class="text-gray-400">加载中...</text>
+      </view>
+      
+      <view v-else-if="filteredVehicles.length === 0">
+        <EmptyState message="暂无车辆" />
+      </view>
+      
+      <uni-list v-else border>
+        <uni-list-item 
+          v-for="vehicle in filteredVehicles" 
+          :key="vehicle.vehicleId"
+          :title="vehicle.plateNo"
+          :note="vehicle.model + ' | ' + (vehicle.driverName || '未分配司机')"
+          :right-text="getStatusText(vehicle.status)"
+          show-arrow
+          @click="showVehicleDetail(vehicle)"
+        >
+          <template #header>
+            <view class="w-10 h-10 rounded-xl flex items-center justify-center mr-3" :class="getStatusBgClass(vehicle.status)">
+              <text class="uni-icons uniui-cart-filled text-lg" :class="getStatusIconClass(vehicle.status)"></text>
+            </view>
+          </template>
+        </uni-list-item>
+      </uni-list>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue'
+import TopBar from '../../components/common/TopBar.vue'
+import EmptyState from '../../components/common/EmptyState.vue'
+import UniList from '../../components/uni-list/uni-list.vue'
+import UniListItem from '../../components/uni-list/uni-list-item.vue'
+import UniSegmentedControl from '../../components/uni-segmented-control/uni-segmented-control.vue'
+import { getVehicleList } from '../../api/vehicle'
+
+const loading = ref(false)
+const vehicles = ref<any[]>([])
+const currentFilterIndex = ref(0)
+
+const filterStatuses = [
+  { value: 'all', label: '全部' },
+  { value: 'available', label: '可用' },
+  { value: 'in_use', label: '使用中' },
+  { value: 'maintenance', label: '维修中' },
+]
+
+const filterLabels = filterStatuses.map(s => s.label)
+
+const filteredVehicles = computed(() => {
+  const filterValue = filterStatuses[currentFilterIndex.value].value
+  if (filterValue === 'all') return vehicles.value
+  return vehicles.value.filter(v => v.status === filterValue)
+})
+
+function getStatusText(status: string) {
+  const map: Record<string, string> = {
+    available: '可用',
+    in_use: '使用中',
+    maintenance: '维修中',
+  }
+  return map[status] || status
+}
+
+function getStatusBgClass(status: string) {
+  const map: Record<string, string> = {
+    available: 'bg-green-50',
+    in_use: 'bg-blue-50',
+    maintenance: 'bg-orange-50',
+  }
+  return map[status] || 'bg-gray-50'
+}
+
+function getStatusIconClass(status: string) {
+  const map: Record<string, string> = {
+    available: 'text-green-500',
+    in_use: 'text-blue-500',
+    maintenance: 'text-orange-500',
+  }
+  return map[status] || 'text-gray-400'
+}
+
+function onFilterChange(index: number) {
+  currentFilterIndex.value = index
+}
+
+function showVehicleDetail(vehicle: any) {
+  uni.showModal({
+    title: vehicle.plateNo,
+    content: `型号: ${vehicle.model}\n类型: ${vehicle.vehicleType}\n司机: ${vehicle.driverName || '未分配'}\n状态: ${getStatusText(vehicle.status)}`,
+    showCancel: false
+  })
+}
+
+onMounted(async () => {
+  await fetchVehicles()
+})
+
+async function fetchVehicles() {
+  loading.value = true
+  try {
+    const res = await getVehicleList()
+    vehicles.value = res.rows || []
+  } catch (error) {
+    console.error('获取车辆列表失败:', error)
+  } finally {
+    loading.value = false
+  }
+}
+
+// 下拉刷新
+onPullDownRefresh(async () => {
+  await fetchVehicles()
+  uni.stopPullDownRefresh()
+})
+</script>

+ 148 - 0
src/pages/dispatch/visualization.vue

@@ -0,0 +1,148 @@
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="数据可视化" show-back />
+    
+    <view v-if="loading" class="py-20 text-center">
+      <text class="text-gray-400">加载中...</text>
+    </view>
+    
+    <view v-else class="px-4 py-4 space-y-4">
+      <!-- 任务统计 -->
+      <uni-card title="任务统计" icon="uniui-flag-filled" extra="实时">
+        <view class="grid grid-cols-2 gap-3">
+          <view class="bg-blue-50 rounded-xl p-3 text-center">
+            <text class="text-xl font-bold text-blue-600 block">{{ stats.task?.total || 0 }}</text>
+            <text class="text-xs text-gray-500">总任务</text>
+          </view>
+          <view class="bg-yellow-50 rounded-xl p-3 text-center">
+            <text class="text-xl font-bold text-yellow-600 block">{{ stats.task?.pending || 0 }}</text>
+            <text class="text-xs text-gray-500">待处理</text>
+          </view>
+          <view class="bg-green-50 rounded-xl p-3 text-center">
+            <text class="text-xl font-bold text-green-600 block">{{ stats.task?.completed || 0 }}</text>
+            <text class="text-xs text-gray-500">已完成</text>
+          </view>
+          <view class="bg-purple-50 rounded-xl p-3 text-center">
+            <text class="text-xl font-bold text-purple-600 block">{{ stats.task?.inProgress || 0 }}</text>
+            <text class="text-xs text-gray-500">进行中</text>
+          </view>
+        </view>
+      </uni-card>
+
+      <!-- 任务分布图表 -->
+      <uni-card title="任务分布" icon="uniui-chart" extra="图表">
+        <ChartView type="pie" :data="taskPieData" height="200" />
+      </uni-card>
+
+      <!-- 任务趋势 -->
+      <uni-card title="任务趋势" icon="uniui-trend-up" extra="图表">
+        <ChartView type="line" :data="taskTrendData" height="200" />
+      </uni-card>
+
+      <!-- 车辆统计 -->
+      <uni-card title="车辆状态" icon="uniui-cart-filled" extra="实时">
+        <uni-list border>
+          <uni-list-item 
+            title="总车辆数" 
+            :right-text="String(stats.vehicle?.total || 0)"
+          />
+          <uni-list-item 
+            title="可用" 
+            :right-text="String(stats.vehicle?.available || 0)"
+          />
+          <uni-list-item 
+            title="使用中" 
+            :right-text="String(stats.vehicle?.inUse || 0)"
+          />
+          <uni-list-item 
+            title="维修中" 
+            :right-text="String(stats.vehicle?.maintenance || 0)"
+          />
+        </uni-list>
+      </uni-card>
+
+      <!-- 人员统计 -->
+      <uni-card title="人员状态" icon="uniui-person-filled" extra="实时">
+        <uni-list border>
+          <uni-list-item 
+            title="总人数" 
+            :right-text="String(stats.personnel?.total || 0)"
+          />
+          <uni-list-item 
+            title="在岗" 
+            :right-text="String(stats.personnel?.onDuty || 0)"
+          />
+          <uni-list-item 
+            title="施工中" 
+            :right-text="String(stats.personnel?.working || 0)"
+          />
+          <uni-list-item 
+            title="休息" 
+            :right-text="String(stats.personnel?.rest || 0)"
+          />
+        </uni-list>
+      </uni-card>
+
+      <!-- 应急统计 -->
+      <uni-card title="应急状态" icon="uniui-notification-filled" extra="实时">
+        <uni-list border>
+          <uni-list-item 
+            title="待命班组" 
+            :right-text="String(stats.emergency?.standbyTeams || 0)"
+          />
+          <uni-list-item 
+            title="待命车辆" 
+            :right-text="String(stats.emergency?.standbyVehicles || 0)"
+          />
+          <uni-list-item 
+            title="应急任务" 
+            :right-text="String(stats.emergency?.emergencyTasks || 0)"
+          />
+        </uni-list>
+      </uni-card>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from 'vue'
+import TopBar from '../../components/common/TopBar.vue'
+import UniCard from '../../components/uni-card/uni-card.vue'
+import UniList from '../../components/uni-list/uni-list.vue'
+import UniListItem from '../../components/uni-list/uni-list-item.vue'
+import { getDashboardStats } from '../../api/dashboard'
+
+import ChartView from '../../components/common/ChartView.vue'
+
+const loading = ref(true)
+const stats = ref<Record<string, any>>({})
+
+// 图表数据
+const taskPieData = ref([
+  { label: '已完成', value: 42, color: '#34c759' },
+  { label: '进行中', value: 15, color: '#4f8ef7' },
+  { label: '待处理', value: 8, color: '#ff9500' },
+  { label: '已取消', value: 3, color: '#8e8e93' },
+])
+
+const taskTrendData = ref([
+  { label: '周一', value: 5 },
+  { label: '周二', value: 8 },
+  { label: '周三', value: 6 },
+  { label: '周四', value: 12 },
+  { label: '周五', value: 10 },
+  { label: '周六', value: 15 },
+  { label: '周日', value: 9 },
+])
+
+onMounted(async () => {
+  try {
+    const res = await getDashboardStats()
+    stats.value = res
+  } catch (error) {
+    console.error('获取统计数据失败:', error)
+  } finally {
+    loading.value = false
+  }
+})
+</script>

+ 39 - 0
src/pages/index/index.vue

@@ -0,0 +1,39 @@
+<template>
+  <view class="launch-page flex flex-col items-center justify-center min-h-screen bg-background">
+    <view class="w-20 h-20 bg-primary rounded-2xl flex items-center justify-center mb-6 shadow-lg">
+      <text class="text-3xl font-bold text-white">清</text>
+    </view>
+    <text class="text-xl font-bold text-gray-800 mb-2">清道夫系统</text>
+    <text class="text-sm text-gray-400">智能清洁管理平台</text>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { onLoad } from '@dcloudio/uni-app'
+import { useAuthStore } from '@/stores'
+import { delay } from '@/utils'
+
+const authStore = useAuthStore()
+
+onLoad(async () => {
+  await delay(500)
+  authStore.checkLogin()
+  if (authStore.isLoggedIn && authStore.userRole) {
+    const homeMap: Record<string, string> = {
+      sales: '/pages/sales/home',
+      dispatch: '/pages/dispatch/home',
+      construction: '/pages/construction/home'
+    }
+    uni.reLaunch({ url: homeMap[authStore.userRole] || '/pages/login/login' })
+  } else {
+    uni.reLaunch({ url: '/pages/login/login' })
+  }
+})
+</script>
+
+<style scoped>
+.launch-page {
+  max-width: 430px;
+  margin: 0 auto;
+}
+</style>

+ 153 - 0
src/pages/login/login.vue

@@ -0,0 +1,153 @@
+
+<template>
+  <view class="app-container min-h-screen bg-gradient-to-b from-blue-500 to-blue-600 flex flex-col items-center justify-center px-8">
+    <!-- Logo -->
+    <view class="mb-8 text-center">
+      <view class="w-20 h-20 bg-white rounded-2xl flex items-center justify-center mb-4 mx-auto shadow-lg">
+        <text class="text-4xl"></text>
+      </view>
+      <text class="text-white text-2xl font-bold">清道夫系统</text>
+      <text class="text-blue-100 text-sm mt-2 block">环境服务管理平台</text>
+    </view>
+
+    <!-- 登录表单 -->
+    <view class="w-full bg-white rounded-2xl p-6 shadow-xl">
+      <text class="text-gray-800 text-lg font-semibold mb-6 block text-center">账号登录</text>
+      
+      <view class="space-y-4">
+        <!-- 用户名 -->
+        <view>
+          <text class="text-sm text-gray-600 block mb-2">用户名</text>
+          <view class="flex items-center bg-gray-100 rounded-xl px-3 py-3">
+            <text class="text-gray-400 mr-2"></text>
+            <input 
+              class="flex-1 bg-transparent text-sm" 
+              placeholder="请输入用户名"
+              v-model="form.username"
+            />
+          </view>
+        </view>
+        
+        <!-- 密码 -->
+        <view>
+          <text class="text-sm text-gray-600 block mb-2">密码</text>
+          <view class="flex items-center bg-gray-100 rounded-xl px-3 py-3">
+            <text class="text-gray-400 mr-2"></text>
+            <input 
+              class="flex-1 bg-transparent text-sm" 
+              placeholder="请输入密码"
+              type="password"
+              v-model="form.password"
+            />
+          </view>
+        </view>
+
+        <!-- 记住密码 -->
+        <view class="flex items-center justify-between">
+          <view class="flex items-center" @click="rememberMe = !rememberMe">
+            <view 
+              class="w-5 h-5 rounded border-2 flex items-center justify-center mr-2 transition-colors"
+              :class="rememberMe ? 'bg-blue-500 border-blue-500' : 'border-gray-300'"
+            >
+              <text v-if="rememberMe" class="text-white text-xs"></text>
+            </view>
+            <text class="text-sm text-gray-600">记住密码</text>
+          </view>
+        </view>
+      </view>
+
+      <button
+        class="w-full mt-6 py-3.5 rounded-xl text-white font-medium text-base login-btn"
+        :class="canLogin ? 'opacity-100' : 'opacity-50'"
+        :disabled="!canLogin || loading"
+        @click="handleLogin"
+      >
+        {{ loading ? '登录中...' : '立即登录' }}
+      </button>
+
+      <!-- 测试账号提示 -->
+      <view class="mt-4 text-center">
+        <text class="text-gray-400 text-xs">测试账号: admin / ***</text>
+      </view>
+    </view>
+
+    <!-- 版本号 -->
+    <text class="text-blue-200 text-xs mt-6">v1.0.0</text>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue'
+import { useAuthStore } from '../../stores/auth'
+import { showToast } from '../../utils'
+
+const authStore = useAuthStore()
+
+const form = ref({
+  username: '',
+  password: '',
+})
+
+const loading = ref(false)
+const rememberMe = ref(false)
+
+const canLogin = computed(() => form.value.username.trim() && form.value.password.trim())
+
+onMounted(() => {
+  // 读取记住的账号密码
+  const savedUsername = uni.getStorageSync('remember_username')
+  const savedPassword = uni.getStorageSync('remember_password')
+  const savedRemember = uni.getStorageSync('remember_me')
+  
+  if (savedRemember === 'true' && savedUsername) {
+    form.value.username = savedUsername
+    form.value.password = savedPassword || ''
+    rememberMe.value = true
+  }
+})
+
+async function handleLogin() {
+  if (!canLogin.value) return
+
+  loading.value = true
+  try {
+    const role = await authStore.doLogin(form.value.username, form.value.password)
+
+    // 处理记住密码
+    if (rememberMe.value) {
+      uni.setStorageSync('remember_username', form.value.username)
+      uni.setStorageSync('remember_password', form.value.password)
+      uni.setStorageSync('remember_me', 'true')
+    } else {
+      uni.removeStorageSync('remember_username')
+      uni.removeStorageSync('remember_password')
+      uni.setStorageSync('remember_me', 'false')
+    }
+
+    // 根据角色跳转到对应首页
+    const homeMap: Record<string, string> = {
+      sales: '/pages/sales/home',
+      dispatch: '/pages/dispatch/home',
+      construction: '/pages/construction/home',
+    }
+
+    const homeUrl = homeMap[role] || '/pages/sales/home'
+    uni.reLaunch({ url: homeUrl })
+  } catch (error: any) {
+    showToast({ title: error.message || '登录失败', icon: 'error' })
+  } finally {
+    loading.value = false
+  }
+}
+</script>
+
+<style scoped>
+.login-btn {
+  background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
+}
+
+.login-btn:active {
+  transform: scale(0.98);
+  opacity: 0.9;
+}
+</style>

+ 83 - 0
src/pages/sales/basicInfo.vue

@@ -0,0 +1,83 @@
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="基本信息" show-back />
+
+    <view class="px-4 py-4 space-y-4">
+      <!-- 头像区域 -->
+      <uni-card :is-shadow="true">
+        <view class="flex items-center py-2">
+          <view class="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mr-4">
+            <text class="uni-icons uniui-person-filled text-blue-500 text-2xl"></text>
+          </view>
+          <view>
+            <text class="text-lg font-semibold text-gray-800 block">{{ userInfo?.name || '-' }}</text>
+            <text class="text-sm text-gray-500">{{ userInfo?.department || '销售部' }} · {{ roleText }}</text>
+          </view>
+        </view>
+      </uni-card>
+
+      <!-- 基本信息 -->
+      <uni-card title="基本信息" icon="uniui-person-filled" :is-shadow="true">
+        <uni-list border="false">
+          <uni-list-item title="姓名" :note="userInfo?.name || '-'" />
+          <uni-list-item title="工号" :note="userInfo?.id || '-'" />
+          <uni-list-item title="部门" :note="userInfo?.department || '销售部'" />
+          <uni-list-item title="职位" :note="roleText" />
+          <uni-list-item title="电话" :note="maskedPhone" />
+          <uni-list-item title="邮箱" :note="email" />
+          <uni-list-item title="状态" :right-text="'在职'" right-color="#34c759" />
+        </uni-list>
+      </uni-card>
+
+      <!-- 操作按钮 -->
+      <view class="pt-2">
+        <button
+          class="w-full py-3 bg-blue-500 text-white rounded-xl font-medium flex items-center justify-center"
+          @click="handleEdit"
+        >
+          <text class="uni-icons uniui-compose mr-2"></text>
+          编辑信息
+        </button>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue'
+import TopBar from '../../components/common/TopBar.vue'
+import UniCard from '../../components/uni-card/uni-card.vue'
+import UniList from '../../components/uni-list/uni-list.vue'
+import UniListItem from '../../components/uni-list/uni-list-item.vue'
+import { useAuthStore } from '../../stores/auth'
+
+const authStore = useAuthStore()
+
+const userInfo = computed(() => authStore.userInfo)
+
+const roleText = computed(() => {
+  const roleMap: Record<string, string> = {
+    sales: '销售经理',
+    dispatch: '调度员',
+    construction: '施工员'
+  }
+  return roleMap[authStore.userRole] || '销售经理'
+})
+
+const maskedPhone = computed(() => {
+  const phone = userInfo.value?.phone || ''
+  if (phone.length === 11) {
+    return phone.slice(0, 3) + '****' + phone.slice(7)
+  }
+  return phone || '-'
+})
+
+const email = computed(() => {
+  const name = userInfo.value?.name || 'user'
+  return `${name.toLowerCase()}@qingdaofu.com`
+})
+
+function handleEdit() {
+  uni.showToast({ title: '编辑功能开发中', icon: 'none' })
+}
+</script>

+ 120 - 0
src/pages/sales/changePassword.vue

@@ -0,0 +1,120 @@
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="修改密码" show-back />
+
+    <view class="px-4 py-4">
+      <uni-card title="密码修改" icon="uniui-locked-filled" :is-shadow="true">
+        <view class="space-y-4 pt-2">
+          <view>
+            <text class="text-sm text-gray-600 block mb-2">
+              <text class="text-red-500">*</text> 当前密码
+            </text>
+            <input
+              v-model="form.currentPassword"
+              class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm text-gray-800 bg-white"
+              type="password"
+              placeholder="请输入当前密码"
+            />
+          </view>
+          <view>
+            <text class="text-sm text-gray-600 block mb-2">
+              <text class="text-red-500">*</text> 新密码
+            </text>
+            <input
+              v-model="form.newPassword"
+              class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm text-gray-800 bg-white"
+              type="password"
+              placeholder="请输入新密码(6-20位)"
+            />
+          </view>
+          <view>
+            <text class="text-sm text-gray-600 block mb-2">
+              <text class="text-red-500">*</text> 确认新密码
+            </text>
+            <input
+              v-model="form.confirmPassword"
+              class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm text-gray-800 bg-white"
+              type="password"
+              placeholder="请再次输入新密码"
+            />
+          </view>
+        </view>
+
+        <view class="mt-4">
+          <view class="bg-blue-50 rounded-lg p-3 mb-4">
+            <text class="text-blue-600 text-sm block font-medium">密码要求:</text>
+            <view class="text-gray-500 text-xs mt-2 space-y-1">
+              <text class="block">• 长度6-20位</text>
+              <text class="block">• 必须包含字母和数字</text>
+              <text class="block">• 不能与当前密码相同</text>
+            </view>
+          </view>
+
+          <button
+            class="w-full py-3 bg-blue-500 text-white rounded-xl font-medium"
+            @click="handleSubmit"
+          >
+            确认修改
+          </button>
+        </view>
+      </uni-card>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { reactive } from 'vue'
+import TopBar from '../../components/common/TopBar.vue'
+import UniCard from '../../components/uni-card/uni-card.vue'
+
+const form = reactive({
+  currentPassword: '',
+  newPassword: '',
+  confirmPassword: ''
+})
+
+function validateForm(): boolean {
+  if (!form.currentPassword) {
+    uni.showToast({ title: '请输入当前密码', icon: 'none' })
+    return false
+  }
+  if (!form.newPassword) {
+    uni.showToast({ title: '请输入新密码', icon: 'none' })
+    return false
+  }
+  if (form.newPassword.length < 6 || form.newPassword.length > 20) {
+    uni.showToast({ title: '密码长度应为6-20位', icon: 'none' })
+    return false
+  }
+  if (!/^(?=.*[a-zA-Z])(?=.*\d).+$/.test(form.newPassword)) {
+    uni.showToast({ title: '密码必须包含字母和数字', icon: 'none' })
+    return false
+  }
+  if (form.newPassword === form.currentPassword) {
+    uni.showToast({ title: '新密码不能与当前密码相同', icon: 'none' })
+    return false
+  }
+  if (form.newPassword !== form.confirmPassword) {
+    uni.showToast({ title: '两次输入的新密码不一致', icon: 'none' })
+    return false
+  }
+  return true
+}
+
+async function handleSubmit() {
+  if (!validateForm()) return
+
+  uni.showModal({
+    title: '确认修改',
+    content: '确定要修改密码吗?修改后需要重新登录。',
+    success: (res) => {
+      if (res.confirm) {
+        uni.showToast({ title: '密码修改成功', icon: 'success' })
+        form.currentPassword = ''
+        form.newPassword = ''
+        form.confirmPassword = ''
+      }
+    }
+  })
+}
+</script>

+ 124 - 0
src/pages/sales/helpCenter.vue

@@ -0,0 +1,124 @@
+<template>
+  <view class="app-container">
+    <TopBar title="帮助中心" show-back />
+
+    <view class="px-4 py-4">
+      <!-- 常见问题 -->
+      <view class="card p-4 mb-4">
+        <view class="flex items-center mb-4">
+          <text class="text-blue-500 mr-2"></text>
+          <text class="text-base font-semibold text-gray-800">常见问题</text>
+        </view>
+
+        <view class="space-y-3">
+          <view
+            v-for="faq in faqs"
+            :key="faq.id"
+            class="border-b border-gray-100 pb-3 last:border-b-0 last:pb-0"
+          >
+            <view
+              class="flex justify-between items-center"
+              @click="toggleFaq(faq.id)"
+            >
+              <text class="text-sm text-gray-700 flex-1">{{ faq.question }}</text>
+              <text
+                class="text-gray-400 text-xs ml-2 transition-transform"
+                :class="{ 'rotate-180': expandedFaq === faq.id }"
+              >
+                {{ expandedFaq === faq.id ? '' : '' }}
+              </text>
+            </view>
+            <view
+              v-if="expandedFaq === faq.id"
+              class="mt-2 text-gray-500 text-sm bg-gray-50 p-3 rounded-lg"
+            >
+              {{ faq.answer }}
+            </view>
+          </view>
+        </view>
+      </view>
+
+      <!-- 知识库 -->
+      <view class="card p-4 mb-4">
+        <view class="flex items-center mb-4">
+          <text class="text-green-500 mr-2"></text>
+          <text class="text-base font-semibold text-gray-800">知识库</text>
+        </view>
+
+        <view class="space-y-3">
+          <view
+            v-for="knowledge in knowledges"
+            :key="knowledge.id"
+            class="flex items-start py-2 border-b border-gray-100 last:border-b-0"
+            @click="showKnowledgeDetail(knowledge)"
+          >
+            <view class="w-10 h-10 bg-blue-50 rounded-lg flex items-center justify-center mr-3 flex-shrink-0">
+              <text class="text-blue-400"></text>
+            </view>
+            <view class="flex-1">
+              <text class="text-sm text-gray-800 block">{{ knowledge.title }}</text>
+              <text class="text-xs text-gray-400 mt-1 block">{{ knowledge.summary }}</text>
+            </view>
+            <text class="text-gray-300 text-xs ml-2">›</text>
+          </view>
+        </view>
+      </view>
+
+      <!-- 联系客服 -->
+      <view class="card p-4">
+        <view class="flex items-center mb-4">
+          <text class="text-green-500 mr-2"></text>
+          <text class="text-base font-semibold text-gray-800">联系客服</text>
+        </view>
+        <view class="flex items-center justify-between py-3 border-b border-gray-100">
+          <view class="flex items-center">
+            <text class="text-blue-500 mr-3"></text>
+            <text class="text-sm text-gray-700">客服热线</text>
+          </view>
+          <text class="text-blue-500 text-sm">400-888-8888</text>
+        </view>
+        <view class="flex items-center justify-between py-3 border-b border-gray-100">
+          <view class="flex items-center">
+            <text class="text-blue-500 mr-3">️</text>
+            <text class="text-sm text-gray-700">电子邮箱</text>
+          </view>
+          <text class="text-gray-600 text-sm">service@qingdaofu.com</text>
+        </view>
+        <view class="flex items-center justify-between py-3">
+          <view class="flex items-center">
+            <text class="text-blue-500 mr-3"></text>
+            <text class="text-sm text-gray-700">服务时间</text>
+          </view>
+          <text class="text-gray-600 text-sm">周一至周五 9:00-18:00</text>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+import TopBar from '../../components/common/TopBar.vue'
+import { useCommonStore } from '../../stores/common'
+import { showToast, showModal } from '../../utils'
+import type { Knowledge } from '../../types'
+
+const commonStore = useCommonStore()
+
+const faqs = ref(commonStore.faqs)
+const knowledges = ref(commonStore.knowledges)
+const expandedFaq = ref<string | null>(null)
+
+function toggleFaq(id: string) {
+  expandedFaq.value = expandedFaq.value === id ? null : id
+}
+
+async function showKnowledgeDetail(knowledge: Knowledge) {
+  await showModal({
+    title: knowledge.title,
+    content: knowledge.content,
+    showCancel: false,
+    confirmText: '知道了'
+  })
+}
+</script>

+ 210 - 0
src/pages/sales/home.vue

@@ -0,0 +1,210 @@
+<template>
+  <view class="app-container">
+    <!-- 顶部区域 -->
+    <view class="relative overflow-hidden">
+      <view class="absolute inset-0 bg-gradient-to-br from-blue-500 to-blue-700"></view>
+      <view class="absolute -top-20 -right-20 w-60 h-60 bg-white opacity-5 rounded-full"></view>
+      <view class="absolute -bottom-10 -left-10 w-40 h-40 bg-white opacity-5 rounded-full"></view>
+      
+      <view class="relative px-5 pt-12 pb-8">
+        <view class="flex items-center justify-between">
+          <view class="flex items-center">
+            <view class="w-12 h-12 bg-white/20 backdrop-blur rounded-2xl flex items-center justify-center mr-3">
+              <text class="uni-icons uniui-person-filled text-white text-xl"></text>
+            </view>
+            <view>
+              <text class="text-blue-100 text-xs block">下午好</text>
+              <text class="text-white text-lg font-bold">{{ userName }}</text>
+            </view>
+          </view>
+          <button class="w-10 h-10 bg-white/15 backdrop-blur rounded-full flex items-center justify-center relative" @click="goToNotification">
+            <text class="uni-icons uniui-notification-filled text-white text-lg"></text>
+          </button>
+        </view>
+      </view>
+    </view>
+
+    <!-- 主内容区域 -->
+    <view class="px-4 -mt-5 space-y-4 pb-24">
+      <!-- 统计卡片 -->
+      <uni-card title="数据概览" :is-shadow="true" :is-full="false">
+        <view class="grid grid-cols-3 gap-3">
+          <view class="bg-blue-50 rounded-2xl p-4 text-center" @click="goToProjectList">
+            <text class="stat-number text-blue-600 block">{{ projectStore.totalCount }}</text>
+            <text class="text-desc mt-1 block">本月项目</text>
+          </view>
+          <view class="bg-orange-50 rounded-2xl p-4 text-center" @click="goToTaskList">
+            <text class="stat-number text-orange-500 block">{{ taskStore.pendingCount }}</text>
+            <text class="text-desc mt-1 block">待确认</text>
+          </view>
+          <view class="bg-green-50 rounded-2xl p-4 text-center" @click="goToTaskList">
+            <text class="stat-number text-green-500 block">{{ taskStore.todayCount }}</text>
+            <text class="text-desc mt-1 block">进行中</text>
+          </view>
+        </view>
+      </uni-card>
+
+      <!-- 快捷入口 -->
+      <uni-card title="快捷功能" :is-shadow="true">
+        <view class="grid grid-cols-4 gap-4">
+          <view class="flex flex-col items-center" @click="goToProjectList">
+            <view class="w-14 h-14 bg-blue-50 rounded-2xl flex items-center justify-center mb-2 transition-transform active:scale-95">
+              <text class="uni-icons uniui-folder-add-filled text-blue-500 text-2xl"></text>
+            </view>
+            <text class="text-xs text-gray-600 font-medium">项目库</text>
+          </view>
+          <view class="flex flex-col items-center" @click="goToTaskList">
+            <view class="w-14 h-14 bg-green-50 rounded-2xl flex items-center justify-center mb-2 transition-transform active:scale-95">
+              <text class="uni-icons uniui-flag-filled text-green-500 text-2xl"></text>
+            </view>
+            <text class="text-xs text-gray-600 font-medium">任务</text>
+          </view>
+          <view class="flex flex-col items-center" @click="goToTaskReminder">
+            <view class="w-14 h-14 bg-orange-50 rounded-2xl flex items-center justify-center mb-2 transition-transform active:scale-95">
+              <text class="uni-icons uniui-notification-filled text-orange-500 text-2xl"></text>
+            </view>
+            <text class="text-xs text-gray-600 font-medium">提醒</text>
+          </view>
+          <view class="flex flex-col items-center" @click="goToNoticeList">
+            <view class="w-14 h-14 bg-purple-50 rounded-2xl flex items-center justify-center mb-2 transition-transform active:scale-95">
+              <text class="uni-icons uniui-sound-filled text-purple-500 text-2xl"></text>
+            </view>
+            <text class="text-xs text-gray-600 font-medium">公告</text>
+          </view>
+        </view>
+      </uni-card>
+
+      <!-- 最新任务 -->
+      <uni-card 
+        title="最新任务" 
+        extra="查看全部" 
+        :is-shadow="true"
+        @click-extra="goToTaskList"
+      >
+        <view v-if="taskStore.tasks.length === 0" class="py-6 text-center">
+          <text class="text-desc">暂无任务</text>
+        </view>
+        <uni-list v-else border="false">
+          <uni-list-item 
+            v-for="task in taskStore.tasks.slice(0, 3)" 
+            :key="task.id"
+            :title="task.projectName"
+            :note="task.scheduleDate"
+            show-arrow
+            @click="goToTaskDetail(task.id)"
+          >
+            <template #header>
+              <view class="w-10 h-10 bg-blue-50 rounded-xl flex items-center justify-center mr-3">
+                <text class="uni-icons uniui-flag text-blue-500"></text>
+              </view>
+            </template>
+            <template #footer>
+              <StatusTag :status="task.status" />
+            </template>
+          </uni-list-item>
+        </uni-list>
+      </uni-card>
+
+      <!-- 通知公告 -->
+      <uni-card 
+        title="通知公告" 
+        extra="更多" 
+        :is-shadow="true"
+        @click-extra="goToNoticeList"
+      >
+        <uni-list border="false">
+          <uni-list-item 
+            v-for="(notice, index) in notices" 
+            :key="index"
+            :title="notice.title"
+            :note="notice.date"
+            show-arrow
+            @click="goToNoticeDetail"
+          >
+            <template #header>
+              <view class="w-2 h-2 rounded-full mt-1.5 mr-2 flex-shrink-0" :class="index === 0 ? 'bg-red-400' : 'bg-gray-300'"></view>
+            </template>
+          </uni-list-item>
+        </uni-list>
+      </uni-card>
+    </view>
+
+    <!-- 底部导航 -->
+    <BottomNav :items="navItems" :currentPath="currentPath" @navigate="onNavChange" />
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from 'vue'
+import { useAuthStore } from '../../stores/auth'
+import { useProjectStore } from '../../stores/project'
+import { useTaskStore } from '../../stores/task'
+import BottomNav from '../../components/common/BottomNav.vue'
+import StatusTag from '../../components/common/StatusTag.vue'
+import UniCard from '../../components/uni-card/uni-card.vue'
+import UniList from '../../components/uni-list/uni-list.vue'
+import UniListItem from '../../components/uni-list/uni-list-item.vue'
+
+const authStore = useAuthStore()
+const projectStore = useProjectStore()
+const taskStore = useTaskStore()
+
+const userName = ref(authStore.user?.name || '张销售')
+const currentPath = ref('/pages/sales/home')
+
+const navItems = [
+  { name: '首页', path: '/pages/sales/home', icon: 'uniui-home-filled' },
+  { name: '任务', path: '/pages/common/taskList', icon: 'uniui-flag-filled' },
+  { name: '我的', path: '/pages/common/profile', icon: 'uniui-person-filled' },
+]
+
+const notices = [
+  { title: '关于2024年第一季度销售激励方案的通知', date: '2024-01-15' },
+  { title: '春节期间服务安排调整公告', date: '2024-01-10' },
+]
+
+function onNavChange(path: string) {
+  uni.reLaunch({ url: path })
+}
+
+function goToProjectList() {
+  uni.navigateTo({ url: '/pages/common/projectList' })
+}
+
+function goToTaskList() {
+  uni.navigateTo({ url: '/pages/common/taskList' })
+}
+
+function goToTaskDetail(taskId: string) {
+  taskStore.setCurrentTask(taskId)
+  uni.navigateTo({ url: '/pages/common/taskDetail' })
+}
+
+function goToTaskReminder() {
+  uni.navigateTo({ url: '/pages/sales/taskReminder' })
+}
+
+function goToNotification() {
+  uni.navigateTo({ url: '/pages/sales/notification' })
+}
+
+function goToNoticeList() {
+  uni.navigateTo({ url: '/pages/common/noticeList' })
+}
+
+function goToNoticeDetail() {
+  uni.navigateTo({ url: '/pages/common/noticeDetail' })
+}
+
+onMounted(() => {
+  projectStore.fetchProjects()
+  taskStore.fetchTasks()
+})
+</script>
+
+<style scoped>
+.stat-number {
+  font-size: 24px;
+  font-weight: 800;
+}
+</style>

+ 90 - 0
src/pages/sales/knowledgeDetail.vue

@@ -0,0 +1,90 @@
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="知识详情" show-back />
+    
+    <view v-if="knowledge" class="px-4 py-4 space-y-4">
+      <!-- 知识标题 -->
+      <uni-card :is-shadow="true">
+        <view class="mb-3">
+          <text class="text-lg font-semibold text-gray-800 block">{{ knowledge.title }}</text>
+          <view class="flex items-center mt-2 gap-4">
+            <view class="px-2 py-0.5 bg-blue-50 rounded-md">
+              <text class="text-xs text-blue-500">{{ knowledge.category }}</text>
+            </view>
+            <view class="flex items-center text-xs text-gray-400">
+              <text class="uni-icons uniui-calendar-filled mr-1"></text>
+              {{ formatDate(knowledge.createTime) }}
+            </view>
+            <view class="flex items-center text-xs text-gray-400">
+              <text class="uni-icons uniui-eye-filled mr-1"></text>
+              {{ knowledge.viewCount }}
+            </view>
+          </view>
+        </view>
+      </uni-card>
+
+      <!-- 知识内容 -->
+      <uni-card title="正文内容" icon="uniui-compose" :is-shadow="true">
+        <view class="text-gray-700 text-sm leading-relaxed space-y-3">
+          <text class="block">{{ knowledge.content }}</text>
+        </view>
+      </uni-card>
+
+      <!-- 附件列表 -->
+      <uni-card v-if="knowledge.attachments?.length" title="相关附件" icon="uniui-folder-add-filled" :is-shadow="true">
+        <uni-list border>
+          <uni-list-item 
+            v-for="(file, index) in knowledge.attachments" 
+            :key="index"
+            :title="file.name"
+            :note="file.size"
+            show-arrow
+            @click="downloadFile(file.url)"
+          >
+            <template #header>
+              <view class="w-10 h-10 rounded-xl bg-blue-50 flex items-center justify-center mr-3">
+                <text class="uni-icons uniui-download-filled text-blue-500"></text>
+              </view>
+            </template>
+          </uni-list-item>
+        </uni-list>
+      </uni-card>
+    </view>
+
+    <view v-else class="py-20 text-center">
+      <EmptyState message="知识内容不存在" />
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from 'vue'
+import TopBar from '../../components/common/TopBar.vue'
+import EmptyState from '../../components/common/EmptyState.vue'
+import UniCard from '../../components/uni-card/uni-card.vue'
+import UniList from '../../components/uni-list/uni-list.vue'
+import UniListItem from '../../components/uni-list/uni-list-item.vue'
+import { formatDate } from '../../utils'
+
+const knowledge = ref<any>(null)
+
+function downloadFile(url: string) {
+  uni.showToast({ title: '开始下载...', icon: 'none' })
+}
+
+onMounted(() => {
+  // 模拟数据
+  knowledge.value = {
+    id: '1',
+    title: '化粪池清掏操作规范',
+    category: '操作规范',
+    content: '一、作业前准备\n1. 检查作业人员安全防护装备是否齐全\n2. 确认作业区域通风良好\n3. 设置安全警示标识,禁止无关人员进入\n4. 检查作业设备是否正常\n\n二、作业流程\n1. 打开井盖,进行通风换气\n2. 使用气体检测仪检测有害气体浓度\n3. 确认安全后,使用专业设备进行清掏\n4. 清掏完成后,对井内进行冲洗\n5. 恢复井盖,清理作业现场\n\n三、安全注意事项\n1. 作业人员必须佩戴防毒面具和安全绳\n2. 井下作业时间不得超过30分钟\n3. 必须配备监护人员在井上监护\n4. 发现异常情况立即停止作业并撤离',
+    createTime: '2026-06-20',
+    viewCount: 128,
+    attachments: [
+      { name: '操作规范V2.0.pdf', size: '2.3MB', url: '#' },
+      { name: '安全流程图.png', size: '1.1MB', url: '#' },
+    ],
+  }
+})
+</script>

+ 116 - 0
src/pages/sales/knowledgeList.vue

@@ -0,0 +1,116 @@
+<template>
+  <view class="app-container pb-20">
+    <TopBar title="行业知识" show-back />
+    
+    <!-- 搜索栏 -->
+    <view class="px-4 py-3">
+      <view class="relative">
+        <text class="uni-icons uniui-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"></text>
+        <input 
+          v-model="searchText"
+          class="w-full pl-10 pr-4 py-2.5 bg-gray-100 rounded-xl text-sm text-gray-800"
+          placeholder="搜索知识标题或关键词"
+        />
+      </view>
+    </view>
+
+    <!-- 分类筛选 -->
+    <view class="px-4 pb-2">
+      <view class="flex gap-2 overflow-x-auto">
+        <view 
+          v-for="(cat, index) in categories" 
+          :key="index"
+          class="px-3 py-1.5 rounded-full text-sm whitespace-nowrap"
+          :class="currentCategory === cat.value ? 'bg-blue-500 text-white' : 'bg-gray-100 text-gray-600'"
+          @click="currentCategory = cat.value"
+        >
+          {{ cat.label }}
+        </view>
+      </view>
+    </view>
+
+    <!-- 知识列表 -->
+    <view class="px-4 mt-2">
+      <view v-if="loading" class="py-10 text-center">
+        <text class="text-gray-400">加载中...</text>
+      </view>
+      
+      <view v-else-if="filteredKnowledges.length === 0">
+        <EmptyState message="暂无知识内容" />
+      </view>
+      
+      <view v-else class="space-y-3">
+        <uni-card 
+          v-for="item in filteredKnowledges" 
+          :key="item.id"
+          :title="item.title"
+          :extra="item.category"
+          icon="uniui-folder-add-filled"
+          :is-shadow="true"
+          @click="goToDetail(item.id)"
+        >
+          <text class="text-sm text-gray-500 line-clamp-2">{{ item.summary }}</text>
+          <template #footer>
+            <view class="flex justify-between items-center">
+              <view class="flex items-center text-xs text-gray-400">
+                <text class="uni-icons uniui-calendar-filled mr-1"></text>
+                {{ formatDate(item.createTime) }}
+              </view>
+              <view class="flex items-center text-xs text-gray-400">
+                <text class="uni-icons uniui-eye-filled mr-1"></text>
+                {{ item.viewCount }}
+              </view>
+            </view>
+          </template>
+        </uni-card>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue'
+import TopBar from '../../components/common/TopBar.vue'
+import EmptyState from '../../components/common/EmptyState.vue'
+import UniCard from '../../components/uni-card/uni-card.vue'
+import { formatDate } from '../../utils'
+
+const loading = ref(false)
+const searchText = ref('')
+const currentCategory = ref('all')
+
+const categories = [
+  { value: 'all', label: '全部' },
+  { value: 'operation', label: '操作规范' },
+  { value: 'safety', label: '安全知识' },
+  { value: 'equipment', label: '设备维护' },
+  { value: 'technology', label: '技术文档' },
+]
+
+const knowledges = ref([
+  { id: '1', title: '化粪池清掏操作规范', category: '操作规范', summary: '详细介绍了化粪池清掏的标准操作流程、安全注意事项及应急处理方案...', createTime: '2026-06-20', viewCount: 128 },
+  { id: '2', title: '管道疏通技术要点', category: '技术文档', summary: '管道疏通的常用方法、设备选型及常见问题解决方案...', createTime: '2026-06-18', viewCount: 95 },
+  { id: '3', title: '施工现场安全守则', category: '安全知识', summary: '施工现场的安全防护要求、危险源识别及个人防护措施...', createTime: '2026-06-15', viewCount: 210 },
+  { id: '4', title: '高压清洗车维护指南', category: '设备维护', summary: '高压清洗车的日常保养、常见故障排查及维修方法...', createTime: '2026-06-10', viewCount: 76 },
+])
+
+const filteredKnowledges = computed(() => {
+  let result = knowledges.value
+  if (currentCategory.value !== 'all') {
+    result = result.filter(k => k.category === categories.find(c => c.value === currentCategory.value)?.label)
+  }
+  if (searchText.value) {
+    const keyword = searchText.value.toLowerCase()
+    result = result.filter(k => k.title.toLowerCase().includes(keyword) || k.summary.toLowerCase().includes(keyword))
+  }
+  return result
+})
+
+function goToDetail(id: string) {
+  uni.navigateTo({ url: `/pages/sales/knowledgeDetail?id=${id}` })
+}
+
+onMounted(() => {
+  loading.value = false
+})
+</script>

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff