diff --git a/mobile-h5/.gitignore b/mobile-h5/.gitignore new file mode 100644 index 000000000..7417743b9 --- /dev/null +++ b/mobile-h5/.gitignore @@ -0,0 +1,11 @@ +node_modules +dist +.DS_Store +*.log +.env.local +.env.*.local +.auto-import.json +src/types/*.d.ts + + +.env diff --git a/mobile-h5/DEVELOPMENT.md b/mobile-h5/DEVELOPMENT.md new file mode 100644 index 000000000..4ddcd738d --- /dev/null +++ b/mobile-h5/DEVELOPMENT.md @@ -0,0 +1,115 @@ +# 移动端开发说明 + +## 已完成功能 + +### 1. 登录页面 (`/login`) +- ✅ 用户名/密码登录 +- ✅ 使用背景图片 `sx1.jpg` +- ✅ Logo 显示 `logo-sx.png` +- ✅ 登录成功后跳转到仪表盘 + +### 2. 仪表盘 (`/dashboard`) +- ✅ 今日入库/出库统计(车辆数) +- ✅ 功能菜单导航(卡片式) +- ✅ 用户信息显示(头像 + 用户名) +- ✅ 退出登录功能 +- ✅ 管理员可见用户管理入口 + +### 3. 计划管理 (`/plans`) +- ✅ 计划列表(卡片式展示) +- ✅ 搜索功能(往来单位/计划号) +- ✅ 新增计划 +- ✅ 编辑计划 +- ✅ 查看关联数据(出入库单、地磅单) +- ✅ 生成出入库单(跳转到出入库页面) +- ✅ 删除计划 +- ✅ 执行状态显示(待入库/入库中/已入库等) + +### 4. 出入库管理 (`/inventory`) +- ✅ 出入库记录列表(卡片式展示) +- ✅ 搜索功能(往来单位/产品) +- ✅ 新增记录 +- ✅ 编辑记录 +- ✅ 查看关联数据(计划、地磅单) +- ✅ 生成地磅单(跳转到地磅单页面) +- ✅ 删除记录 +- ✅ 支持从计划管理跳转并自动填充数据 + +### 5. 地磅单管理 (`/weighing`) +- ✅ 地磅单列表(卡片式展示) +- ✅ 搜索功能(车牌号/货物名称) +- ✅ 新增地磅单 +- ✅ 编辑地磅单 +- ✅ 毛重/皮重/净重计算(自动) +- ✅ 毛重/皮重时间自动填充 +- ✅ 作废功能 +- ✅ 删除功能 +- ✅ 查看关联数据(计划、出入库单) +- ✅ 支持从出入库管理跳转并自动填充数据 + +### 6. 基础资料 (`/master-data`) +- ✅ 标签页切换(往来单位/货物) +- ✅ 往来单位管理 + - 列表展示 + - 新增/编辑/删除 + - 搜索功能 +- ✅ 货物管理 + - 列表展示 + - 新增/编辑/删除 + - 搜索功能 + +### 7. 用户管理 (`/user`) +- ✅ 用户列表(卡片式展示) +- ✅ 新增用户 +- ✅ 编辑用户(修改密码、角色) +- ✅ 删除用户(admin 用户不可删除) +- ✅ 角色显示(管理员/操作员) + +## 技术实现 + +### 组件使用 +- **列表**: `van-list` + 自定义卡片组件 +- **表单**: `van-form` + `van-field` +- **选择器**: `van-picker`(公司、货物) +- **日期选择**: 原生 `input[type="date"]` +- **弹窗**: `van-popup`(底部弹出,高度 90%) +- **按钮**: `van-button` +- **标签**: `van-tag` +- **搜索**: `van-search` +- **反馈**: `Toast`、`Dialog`、`Notify` +- **加载**: `van-loading` +- **空状态**: `van-empty` + +### 数据流 +- API 调用与 Web 端完全一致 +- Store 状态管理与 Web 端共享 +- 路由跳转支持参数传递 +- 表单验证与 Web 端一致 + +### 移动端优化 +- 卡片式布局,信息层次清晰 +- 底部弹窗表单,符合移动端操作习惯 +- 触摸友好的按钮尺寸 +- 下拉加载更多数据 +- 原生日期选择器,体验更好 + +## 功能对比 + +| 功能 | Web 端 | 移动端 | 状态 | +|------|--------|--------|------| +| 登录 | ✅ | ✅ | 一致 | +| 仪表盘 | ✅ | ✅ | 一致 | +| 计划管理 | ✅ | ✅ | 一致 | +| 出入库管理 | ✅ | ✅ | 一致 | +| 地磅单管理 | ✅ | ✅ | 一致 | +| 基础资料 | ✅ | ✅ | 一致 | +| 用户管理 | ✅ | ✅ | 一致 | + +## 下一步 + +项目已完整实现所有功能,可以: +1. 运行 `npm install` 安装依赖 +2. 运行 `npm run dev` 启动开发服务器 +3. 测试各项功能 +4. 根据实际使用情况优化 UI/UX + diff --git a/mobile-h5/FUNCTION_COMPARISON.md b/mobile-h5/FUNCTION_COMPARISON.md new file mode 100644 index 000000000..d09c8db76 --- /dev/null +++ b/mobile-h5/FUNCTION_COMPARISON.md @@ -0,0 +1,289 @@ +# Mobile-H5 与 Web 端功能对比报告 + +## 总体概览 + +| 界面 | Mobile-H5 | Web端 | 功能一致性 | 备注 | +|------|-----------|-------|-----------|------| +| 登录 | ✅ | ✅ | ✅ 基本一致 | UI适配移动端 | +| 仪表盘 | ✅ | ✅ | ⚠️ 部分差异 | 缺少图表展示 | +| 计划管理 | ✅ | ✅ | ⚠️ 部分差异 | 缺少导出功能、查看关联数据 | +| 出入库管理 | ✅ | ✅ | ⚠️ 部分差异 | 缺少导出功能、查看关联数据 | +| 地磅单管理 | ✅ | ✅ | ⚠️ 部分差异 | 缺少导出功能、查看关联数据 | +| 基础资料 | ✅ | ✅ | ⚠️ 部分差异 | 缺少弹窗编辑(使用详情页) | +| 用户管理 | ✅ | ✅ | ⚠️ 部分差异 | 缺少弹窗编辑(使用详情页) | + +--- + +## 详细对比 + +### 1. 登录界面 ✅ + +**功能一致性:基本一致** + +**Mobile-H5:** +- ✅ 用户名/密码输入 +- ✅ 登录按钮 +- ✅ 登录成功跳转 +- ✅ 错误提示 + +**Web端:** +- ✅ 用户名/密码输入 +- ✅ 记住密码选项 +- ✅ 登录按钮 +- ✅ 登录成功跳转 +- ✅ 错误提示 +- ✅ 主题切换 +- ✅ 语言切换 + +**差异:** +- Web端有"记住密码"功能 +- Web端有主题和语言切换功能(移动端不需要) + +--- + +### 2. 仪表盘 ⚠️ + +**功能一致性:部分差异** + +**Mobile-H5:** +- ✅ 今日入库/出库统计(车数) +- ✅ 菜单导航(计划管理、出入库管理、地磅单管理、基础资料、用户管理) +- ✅ 用户信息显示 +- ✅ 退出登录 + +**Web端:** +- ✅ 今日入库/出库统计(车数) +- ✅ 今日入库量图表(按货物,t) +- ✅ 今日出库量图表(按货物,t) + +**差异:** +- ❌ Mobile-H5 **缺少**图表展示功能 +- ❌ Mobile-H5 **缺少**按货物统计的图表 +- ✅ Mobile-H5 有菜单导航(Web端通过侧边栏导航) + +**建议:** +- 考虑在移动端添加简化的图表展示(可选,因为移动端屏幕较小) + +--- + +### 3. 计划管理 ⚠️ + +**功能一致性:部分差异** + +**Mobile-H5:** +- ✅ 列表展示(卡片式) +- ✅ 搜索功能(往来单位/计划号) +- ✅ 新增计划 +- ✅ 编辑计划 +- ✅ 删除计划 +- ✅ 生成出入库单 +- ✅ 分页加载 + +**Web端:** +- ✅ 列表展示(表格) +- ✅ 搜索功能(往来单位/计划号) +- ✅ 新增计划(弹窗) +- ✅ 编辑计划(弹窗) +- ✅ 删除计划 +- ✅ 生成出入库单 +- ✅ 查看关联数据(弹窗显示关联的出入库单和地磅单) +- ✅ 导出Excel +- ✅ 分页 + +**差异:** +- ❌ Mobile-H5 **缺少**导出Excel功能 +- ❌ Mobile-H5 **缺少**查看关联数据功能(Web端可以查看计划关联的出入库单和地磅单) +- ✅ Mobile-H5 使用详情页编辑(Web端使用弹窗) +- ✅ Mobile-H5 使用卡片式展示(Web端使用表格) + +**建议:** +- 添加导出功能(可以导出为CSV或Excel) +- 添加查看关联数据功能(可以在详情页或弹窗中显示) + +--- + +### 4. 出入库管理 ⚠️ + +**功能一致性:部分差异** + +**Mobile-H5:** +- ✅ 列表展示(卡片式) +- ✅ 搜索功能(往来单位/产品) +- ✅ 新增记录 +- ✅ 编辑记录 +- ✅ 删除记录 +- ✅ 生成地磅单 +- ✅ 分页加载 + +**Web端:** +- ✅ 列表展示(表格) +- ✅ 搜索功能(往来单位/产品) +- ✅ 新增记录(弹窗) +- ✅ 编辑记录(弹窗) +- ✅ 删除记录 +- ✅ 生成地磅单 +- ✅ 查看关联数据(弹窗显示关联的计划和地磅单) +- ✅ 导出Excel +- ✅ 分页 +- ✅ 显示更多字段(净重、毛重、皮重、扣杂等) + +**差异:** +- ❌ Mobile-H5 **缺少**导出Excel功能 +- ❌ Mobile-H5 **缺少**查看关联数据功能(Web端可以查看出入库单关联的计划和地磅单) +- ❌ Mobile-H5 **缺少**显示净重、毛重、皮重、扣杂等字段(这些字段在地磅单中) +- ✅ Mobile-H5 使用详情页编辑(Web端使用弹窗) +- ✅ Mobile-H5 使用卡片式展示(Web端使用表格) + +**建议:** +- 添加导出功能 +- 添加查看关联数据功能 +- 考虑在详情页显示更多字段 + +--- + +### 5. 地磅单管理 ⚠️ + +**功能一致性:部分差异** + +**Mobile-H5:** +- ✅ 列表展示(卡片式) +- ✅ 搜索功能(车牌号/货物名称) +- ✅ 新增地磅单 +- ✅ 编辑地磅单 +- ✅ 删除地磅单 +- ✅ 作废地磅单 +- ✅ 分页加载 +- ✅ 显示作废状态 + +**Web端:** +- ✅ 列表展示(表格) +- ✅ 搜索功能(车牌号/货物名称) +- ✅ 新增地磅单(弹窗) +- ✅ 编辑地磅单(弹窗) +- ✅ 删除地磅单 +- ✅ 作废地磅单 +- ✅ 查看关联数据(弹窗显示关联的计划和出入库单) +- ✅ 导出Excel +- ✅ 分页 +- ✅ 显示更多字段(计划单号、出入库单号、本次量、净重、毛重、皮重、扣杂等) + +**差异:** +- ❌ Mobile-H5 **缺少**导出Excel功能 +- ❌ Mobile-H5 **缺少**查看关联数据功能(Web端可以查看地磅单关联的计划和出入库单) +- ❌ Mobile-H5 **缺少**显示计划单号、出入库单号、本次量等字段 +- ✅ Mobile-H5 使用详情页编辑(Web端使用弹窗) +- ✅ Mobile-H5 使用卡片式展示(Web端使用表格) + +**建议:** +- 添加导出功能 +- 添加查看关联数据功能 +- 在列表或详情页显示更多字段 + +--- + +### 6. 基础资料 ⚠️ + +**功能一致性:部分差异** + +**Mobile-H5:** +- ✅ 往来单位管理(Tab切换) +- ✅ 货物管理(Tab切换) +- ✅ 搜索功能 +- ✅ 新增(跳转到详情页) +- ✅ 编辑(跳转到详情页) +- ✅ 删除 +- ✅ 分页加载 + +**Web端:** +- ✅ 往来单位管理(Tab切换) +- ✅ 货物管理(Tab切换) +- ✅ 搜索功能 +- ✅ 新增(弹窗) +- ✅ 编辑(弹窗) +- ✅ 删除 +- ✅ 分页 + +**差异:** +- ✅ Mobile-H5 使用详情页编辑(Web端使用弹窗) +- ✅ Mobile-H5 使用卡片式展示(Web端使用表格) + +**功能基本一致,只是交互方式不同(弹窗 vs 详情页)** + +--- + +### 7. 用户管理 ⚠️ + +**功能一致性:部分差异** + +**Mobile-H5:** +- ✅ 列表展示(卡片式) +- ✅ 新增用户(跳转到详情页) +- ✅ 编辑用户(跳转到详情页) +- ✅ 删除用户 +- ✅ 禁止删除admin用户 +- ✅ 分页加载 + +**Web端:** +- ✅ 列表展示(表格) +- ✅ 新增用户(弹窗) +- ✅ 编辑用户(弹窗) +- ✅ 删除用户 +- ✅ 禁止删除admin用户 +- ✅ 分页 + +**差异:** +- ✅ Mobile-H5 使用详情页编辑(Web端使用弹窗) +- ✅ Mobile-H5 使用卡片式展示(Web端使用表格) + +**功能基本一致,只是交互方式不同(弹窗 vs 详情页)** + +--- + +## 总结 + +### 功能缺失清单 + +1. **仪表盘** + - ❌ 缺少图表展示(按货物统计的入库/出库量) + +2. **计划管理** + - ❌ 缺少导出Excel功能 + - ❌ 缺少查看关联数据功能 + +3. **出入库管理** + - ❌ 缺少导出Excel功能 + - ❌ 缺少查看关联数据功能 + - ❌ 缺少显示净重、毛重、皮重、扣杂等字段 + +4. **地磅单管理** + - ❌ 缺少导出Excel功能 + - ❌ 缺少查看关联数据功能 + - ❌ 缺少显示计划单号、出入库单号、本次量等字段 + +### 交互方式差异(可接受) + +- Mobile-H5 使用详情页进行新增/编辑,Web端使用弹窗 +- Mobile-H5 使用卡片式列表,Web端使用表格 +- 这些差异是合理的,因为移动端和PC端的交互习惯不同 + +### 建议优先级 + +**高优先级:** +1. 添加查看关联数据功能(计划、出入库、地磅单之间的关联关系) +2. 添加导出功能(Excel/CSV) + +**中优先级:** +3. 在详情页或列表中添加更多字段显示 +4. 考虑添加简化的图表展示(可选) + +**低优先级:** +5. 保持当前的交互方式差异(详情页 vs 弹窗)是合理的 + +--- + +## 备注 + +- Mobile-H5 的详情页功能需要单独检查,本报告主要对比列表页功能 +- 所有界面都支持基本的CRUD操作,核心功能一致 +- 移动端适配良好,交互体验符合移动端习惯 + diff --git a/mobile-h5/PAGINATION.md b/mobile-h5/PAGINATION.md new file mode 100644 index 000000000..84b10ffca --- /dev/null +++ b/mobile-h5/PAGINATION.md @@ -0,0 +1,148 @@ +# 分页加载实现说明 + +## ✅ 已实现分页加载的页面 + +所有数据列表页面都已实现**滚动分页加载**功能: + +1. **计划管理** (`/plans`) ✅ +2. **出入库管理** (`/inventory`) ✅ +3. **地磅单管理** (`/weighing`) ✅ +4. **基础资料 - 往来单位** (`/master-data`) ✅ +5. **基础资料 - 货物** (`/master-data`) ✅ +6. **用户管理** (`/user`) ✅ + +## 实现原理 + +### 1. 数据结构 + +每个列表页面都维护两个数据数组: +- `allData`: 存储从 API 获取的完整数据 +- `tableData`: 当前显示的数据(分页后的数据) + +### 2. 分页参数 + +- `currentPage`: 当前页码(从 1 开始) +- `pageSize`: 每页数据量(默认 20 条) + +### 3. 加载逻辑 + +```typescript +const loadData = async (reset = false) => { + // 重置时清空数据 + if (reset) { + currentPage.value = 1 + allData.value = [] + tableData.value = [] + finished.value = false + } + + loading.value = true + try { + // 第一页或重置时,重新获取所有数据 + if (reset || currentPage.value === 1) { + const data = await api.list(params) + allData.value = data + } + + // 分页切片 + const start = (currentPage.value - 1) * pageSize.value + const end = start + pageSize.value + const newData = allData.value.slice(start, end) + + // 追加或替换数据 + if (reset || currentPage.value === 1) { + tableData.value = newData + } else { + tableData.value.push(...newData) + } + + // 判断是否加载完成 + if (tableData.value.length >= allData.value.length) { + finished.value = true + } else { + currentPage.value++ + } + } catch (error) { + Toast.fail('加载数据失败') + finished.value = true + } finally { + loading.value = false + } +} +``` + +### 4. 滚动加载 + +使用 Vant 的 `van-list` 组件实现滚动加载: + +```vue + + + +``` + +```typescript +const onLoad = () => { + if (!finished.value) { + loadData() // 加载下一页 + } +} +``` + +## 使用场景 + +### 初始加载 +```typescript +onMounted(() => { + loadData(true) // 重置并加载第一页 +}) +``` + +### 搜索时 +```typescript +const handleSearch = () => { + loadData(true) // 重置并重新加载 +} +``` + +### 增删改后 +```typescript +// 新增/编辑/删除后 +await api.create(data) +Toast.success('操作成功') +loadData(true) // 重置并重新加载 +``` + +## 用户体验 + +1. **首次加载**: 显示第一页数据(20 条) +2. **滚动到底部**: 自动加载下一页数据 +3. **加载中**: 显示加载状态 +4. **加载完成**: 显示"没有更多了"提示 +5. **搜索**: 重置分页,重新加载数据 +6. **增删改**: 重置分页,刷新列表 + +## 性能优化 + +- **前端分页**: 首次获取所有数据,后续分页在前端完成 +- **按需加载**: 每次只加载 20 条数据,减少 DOM 渲染压力 +- **数据缓存**: `allData` 缓存完整数据,避免重复请求 + +## 注意事项 + +1. **搜索会重置分页**: 搜索时会重新获取数据并重置分页 +2. **增删改会刷新**: 增删改操作后会重置分页并刷新列表 +3. **数据量限制**: 如果数据量非常大(>1000 条),建议后端实现分页接口 + +## 未来优化 + +如果数据量很大,可以考虑: +1. 后端分页接口(需要后端支持) +2. 虚拟滚动(使用 `vue-virtual-scroller`) +3. 数据懒加载(只加载可见区域的数据) + diff --git a/mobile-h5/README.md b/mobile-h5/README.md new file mode 100644 index 000000000..465ba3113 --- /dev/null +++ b/mobile-h5/README.md @@ -0,0 +1,242 @@ +# 移动端 H5 项目 + +基于 Vue 3 + Vite + Vant 的纯 H5 移动端应用,功能与 Web 端(art-design-pro)完全一致,仅展示形式适配移动端。 + +## 技术栈 + +- **框架**: Vue 3 +- **构建工具**: Vite +- **UI 组件库**: Vant 4(自动引入) +- **状态管理**: Pinia +- **路由**: Vue Router +- **HTTP 客户端**: Axios +- **样式**: SCSS +- **日期处理**: dayjs + +## 项目结构 + +``` +mobile-h5/ +├── src/ +│ ├── api/ # API 接口(与 Web 端共享) +│ ├── assets/ # 静态资源 +│ │ ├── img/ # 图片资源(logo、头像、背景图等) +│ │ └── styles/ # 样式文件 +│ ├── router/ # 路由配置 +│ ├── store/ # 状态管理(与 Web 端共享) +│ │ └── modules/ # Store 模块 +│ ├── utils/ # 工具函数(与 Web 端共享) +│ │ └── http/ # HTTP 请求封装 +│ ├── views/ # 页面 +│ │ ├── login/ # 登录页 ✅ +│ │ ├── dashboard/ # 仪表盘 ✅ +│ │ ├── plans/ # 计划管理 ✅ +│ │ ├── inventory/ # 出入库管理 ✅ +│ │ ├── weighing/ # 地磅单管理 ✅ +│ │ ├── master-data/ # 基础资料 ✅ +│ │ └── user/ # 用户管理 ✅ +│ ├── App.vue # 根组件 +│ └── main.ts # 入口文件 +├── index.html +├── package.json +├── vite.config.ts +└── tsconfig.json +``` + +## 资源文件 + +已复制的资源文件: +- `logo-sx.png` - Logo +- `favicon.ico` - 网站图标 +- `avatar11.png` - 默认头像 +- `sx1.jpg` - 登录页背景图 + +## 安装依赖 + +```bash +cd mobile-h5 +npm install +``` + +## 开发 + +```bash +npm run dev +``` + +访问 http://localhost:5174 + +## 构建 + +```bash +npm run build +``` + +构建产物在 `dist` 目录。 + +## 功能模块 + +### ✅ 已完成 + +1. **登录页面** + - 用户名/密码登录 + - 使用背景图片 `sx1.jpg` + - Logo 显示 + +2. **仪表盘** + - 今日入库/出库统计 + - 功能菜单导航 + - 用户信息显示 + +3. **计划管理** + - 计划列表(卡片式展示) + - 新增/编辑计划 + - 查看关联数据 + - 生成出入库单 + - 删除计划 + - 搜索功能 + +4. **出入库管理** + - 出入库记录列表(卡片式展示) + - 新增/编辑记录 + - 查看关联数据 + - 生成地磅单 + - 删除记录 + - 搜索功能 + +5. **地磅单管理** + - 地磅单列表(卡片式展示) + - 新增/编辑地磅单 + - 毛重/皮重/净重计算 + - 自动填充时间 + - 作废功能 + - 删除功能 + - 搜索功能 + +6. **基础资料** + - 往来单位管理(标签页) + - 货物管理(标签页) + - 新增/编辑/删除功能 + - 搜索功能 + +7. **用户管理** + - 用户列表(卡片式展示) + - 新增/编辑用户 + - 删除用户 + - 角色管理(管理员/操作员) + +## 移动端适配特点 + +### 布局方式 +- 使用卡片式布局替代表格 +- 列表使用 `van-list` 组件支持下拉加载 +- 表单使用底部弹窗(`van-popup`)展示 + +### 组件使用 +- **列表**: `van-list` + 卡片式布局 +- **表单**: `van-form` + `van-field` +- **选择器**: `van-picker`(公司、货物选择) +- **日期选择**: 原生 `input type="date"`(移动端原生体验更好) +- **弹窗**: `van-popup`(底部弹出) +- **按钮**: `van-button` +- **标签**: `van-tag` +- **搜索**: `van-search` +- **反馈**: `Toast`、`Dialog`、`Notify` + +### 交互优化 +- 点击卡片查看详情 +- 底部弹窗编辑表单 +- 下拉刷新和上拉加载 +- 触摸友好的按钮尺寸 + +## 与 Web 端的差异 + +| 功能 | Web 端 | 移动端 | +|------|--------|--------| +| UI 框架 | Element Plus | Vant 4 | +| 列表展示 | 表格(el-table) | 卡片列表 | +| 表单弹窗 | 居中对话框 | 底部弹窗 | +| 日期选择 | el-date-picker | 原生 input[type="date"] | +| 消息提示 | ElMessage | Toast | +| 确认对话框 | ElMessageBox | Dialog | + +## 开发说明 + +### 组件自动引入 + +所有 Vant 组件已配置自动引入,可直接使用: + +```vue + + + +``` + +### API 调用 + +API 文件已从 Web 端复制,可直接使用: + +```typescript +import { UserService } from '@/api/usersApi' +import { weighingApi } from '@/api/weighingApi' +import { planApi } from '@/api/plansApi' +``` + +### 状态管理 + +Store 文件已从 Web 端复制,可直接使用: + +```typescript +import { useUserStore } from '@/store/modules/user' + +const userStore = useUserStore() +const userInfo = userStore.getUserInfo +``` + +### 路由跳转 + +```typescript +import { useRouter } from 'vue-router' + +const router = useRouter() +router.push('/plans') +``` + +## 注意事项 + +1. **功能一致性**: 所有功能逻辑与 Web 端保持一致 +2. **数据格式**: API 接口、数据格式与 Web 端完全相同 +3. **权限控制**: 用户角色判断与 Web 端一致 +4. **移动端优化**: 仅展示形式适配移动端,功能逻辑不变 + +## 环境变量 + +创建 `.env` 文件(参考 `.env.example`): + +```env +VITE_API_BASE_URL=http://localhost:8000 +``` + +## 浏览器支持 + +- iOS Safari 12+ +- Android Chrome 80+ +- 微信内置浏览器 +- 其他现代移动浏览器 + +## 后续优化建议 + +1. 添加下拉刷新功能 +2. 优化长列表性能(虚拟滚动) +3. 添加离线缓存 +4. 优化图片加载 +5. 添加骨架屏加载效果 diff --git a/mobile-h5/REFACTORING.md b/mobile-h5/REFACTORING.md new file mode 100644 index 000000000..25d93f3ad --- /dev/null +++ b/mobile-h5/REFACTORING.md @@ -0,0 +1,143 @@ +# 移动端重构完成总结 + +## ✅ 已完成的工作 + +### 1. 弹窗改为跳转新页面 + +所有模块的新增、编辑、查看功能都已从弹窗改为跳转到独立页面: + +#### 计划管理 (`/plans`) +- ✅ 创建详情页面:`/plans/detail.vue` +- ✅ 列表页面改为跳转 +- ✅ 路由配置完成 + +#### 出入库管理 (`/inventory`) +- ✅ 创建详情页面:`/inventory/detail.vue` +- ✅ 列表页面改为跳转 +- ✅ 路由配置完成 +- ✅ 支持从计划管理跳转并自动填充数据 + +#### 地磅单管理 (`/weighing`) +- ✅ 创建详情页面:`/weighing/detail.vue` +- ✅ 列表页面改为跳转 +- ✅ 路由配置完成 +- ✅ 支持从出入库管理跳转并自动填充数据 +- ✅ 毛重/皮重/净重自动计算 +- ✅ 时间自动填充 + +#### 基础资料 (`/master-data`) +- ✅ 创建公司详情页面:`/master-data/company-detail.vue` +- ✅ 创建货物详情页面:`/master-data/cargo-detail.vue` +- ✅ 列表页面改为跳转 +- ✅ 路由配置完成 + +#### 用户管理 (`/user`) +- ✅ 创建详情页面:`/user/detail.vue` +- ✅ 列表页面改为跳转 +- ✅ 路由配置完成 + +### 2. 搜索功能与Web端一致 + +所有搜索框的占位符和功能都已与Web端保持一致: + +- ✅ **计划管理**:`搜索往来单位 / 计划号` +- ✅ **出入库管理**:`搜索往来单位 / 产品` +- ✅ **地磅单管理**:`搜索车牌号 / 货物名称` +- ✅ **基础资料-公司**:`搜索公司名称 / 备注` +- ✅ **基础资料-货物**:`搜索货物名称 / 备注` + +### 3. 按钮和Tag配色与Web端一致 + +- ✅ 创建了 `vant-theme.scss` 主题配置文件 +- ✅ 配置了与Web端一致的颜色: + - primary: `#52B788` (绿色) + - success: `#13deb9` (青绿色) + - warning: `#ffae1f` (橙色) + - danger: `#ff4d4f` (红色) + - info: `#52B788` (绿色) +- ✅ 统一了Tag类型映射(与Web端一致) + +### 4. API完全复用 + +- ✅ 所有API文件已从Web端复制 +- ✅ 使用相同的API接口和数据结构 +- ✅ 功能逻辑与Web端完全一致 + +### 5. 表单字段与Web端一致 + +所有详情页面的表单字段都与Web端保持一致: + +- ✅ **计划管理**:计划号、业务类型、往来单位、产品、计划开始时间、计划结束时间、计划量、备注 +- ✅ **出入库管理**:日期、往来单位、业务类型、产品、数量、车牌号、计划号、备注 +- ✅ **地磅单管理**:车牌号、货物名称、业务类型、供货单位、收货单位、毛重(kg)、毛重时间、皮重(kg)、皮重时间、扣杂(kg)、净重(kg)、计划号、备注 +- ✅ **基础资料-公司**:公司名称、备注 +- ✅ **基础资料-货物**:货物名称、备注 +- ✅ **用户管理**:用户名、密码、角色 + +## 路由配置 + +所有新增的路由已添加到 `router/index.ts`: + +```typescript +// 计划管理 +/plans/detail - 新增 +/plans/detail/:id - 编辑/查看 + +// 出入库管理 +/inventory/detail - 新增 +/inventory/detail/:id - 编辑/查看 + +// 地磅单管理 +/weighing/detail - 新增 +/weighing/detail/:id - 编辑/查看 + +// 基础资料 +/master-data/company/detail - 新增公司 +/master-data/company/detail/:id - 编辑/查看公司 +/master-data/cargo/detail - 新增货物 +/master-data/cargo/detail/:id - 编辑/查看货物 + +// 用户管理 +/user/detail - 新增 +/user/detail/:id - 编辑/查看 +``` + +## 页面模式 + +所有详情页面支持三种模式(通过路由参数控制): + +- **新增模式**:`/module/detail` - 创建新记录 +- **编辑模式**:`/module/detail/:id?mode=edit` - 编辑现有记录 +- **查看模式**:`/module/detail/:id?mode=view` - 只读查看 + +## 通用样式 + +创建了 `detail-page.scss` 通用样式文件,所有详情页面共享相同的样式: + +- 页面头部(返回按钮 + 标题) +- 表单容器 +- 表单项样式 +- 按钮样式 + +## 功能完整性 + +所有功能与Web端保持一致: + +1. ✅ 列表展示(卡片式,支持滚动分页) +2. ✅ 搜索功能(与Web端一致) +3. ✅ 新增功能(跳转到详情页面) +4. ✅ 编辑功能(跳转到详情页面) +5. ✅ 查看功能(跳转到详情页面,只读模式) +6. ✅ 删除功能(确认对话框) +7. ✅ 数据关联(计划-出入库-地磅单的关联关系) +8. ✅ 自动填充(从关联页面跳转时自动填充数据) + +## 下一步 + +项目重构已完成,可以: + +1. 运行 `npm install` 安装依赖 +2. 运行 `npm run dev` 启动开发服务器 +3. 测试所有功能,确保与Web端一致 +4. 根据实际使用情况优化UI/UX + diff --git a/mobile-h5/index.html b/mobile-h5/index.html new file mode 100644 index 000000000..108fe8a44 --- /dev/null +++ b/mobile-h5/index.html @@ -0,0 +1,14 @@ + + + + + + + 地磅系统 - 移动端 + + +
+ + + + diff --git a/mobile-h5/package-lock.json b/mobile-h5/package-lock.json new file mode 100644 index 000000000..c7a08e0bb --- /dev/null +++ b/mobile-h5/package-lock.json @@ -0,0 +1,2893 @@ +{ + "name": "mobile-h5", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mobile-h5", + "version": "1.0.0", + "dependencies": { + "axios": "^1.7.5", + "dayjs": "^1.11.19", + "html2canvas": "^1.4.1", + "mitt": "^3.0.1", + "pinia": "^2.2.2", + "vant": "^4.9.21", + "vue": "^3.5.12", + "vue-router": "^4.4.2" + }, + "devDependencies": { + "@types/node": "^22.1.0", + "@vitejs/plugin-vue": "^5.2.1", + "@vue/tsconfig": "^0.1.3", + "sass": "^1.81.0", + "typescript": "~5.6.3", + "unplugin-auto-import": "^0.18.3", + "unplugin-vue-components": "^0.27.4", + "vite": "^6.1.0", + "vue-tsc": "~2.1.6" + } + }, + "node_modules/@antfu/utils": { + "version": "0.7.10", + "resolved": "https://registry.npmmirror.com/@antfu/utils/-/utils-0.7.10.tgz", + "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "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==" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "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.1", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "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.1", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "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.1", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "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.1", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "dev": true, + "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.1", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "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.1", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "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.1", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "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.1", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "dev": true, + "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.1", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "dev": true, + "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.1", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "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.1", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "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.1", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "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/pluginutils/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", + "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", + "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true + }, + "node_modules/@types/node": { + "version": "22.19.1", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-22.19.1.tgz", + "integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==", + "dev": true, + "peer": true, + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@vant/popperjs": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/@vant/popperjs/-/popperjs-1.3.0.tgz", + "integrity": "sha512-hB+czUG+aHtjhaEmCJDuXOep0YTZjdlRR+4MSmIFnkCQIxJaXLQdSsR90XWvAI2yvKUI7TCGqR8pQg2RtvkMHw==" + }, + "node_modules/@vant/use": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/@vant/use/-/use-1.6.0.tgz", + "integrity": "sha512-PHHxeAASgiOpSmMjceweIrv2AxDZIkWXyaczksMoWvKV2YAYEhoizRuk/xFnKF+emUIi46TsQ+rvlm/t2BBCfA==", + "peerDependencies": { + "vue": "^3.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==", + "dev": true, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.26", + "resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.26.tgz", + "integrity": "sha512-hH0SMitMxnB43OZpyF1IFPS9bgb2I3bpCh76m2WEK7BE0A0EzpYsRp0CCH2xNKshr7kacU5TQBLYn4zj7CG60A==", + "dev": true, + "dependencies": { + "@volar/source-map": "2.4.26" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.26", + "resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.26.tgz", + "integrity": "sha512-JJw0Tt/kSFsIRmgTQF4JSt81AUSI1aEye5Zl65EeZ8H35JHnTvFGmpDOBn5iOxd48fyGE+ZvZBp5FcgAy/1Qhw==", + "dev": true + }, + "node_modules/@volar/typescript": { + "version": "2.4.26", + "resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.26.tgz", + "integrity": "sha512-N87ecLD48Sp6zV9zID/5yuS1+5foj0DfuYGdQ6KHj/IbKvyKv1zNX6VCmnKYwtmHadEO6mFc2EKISiu3RDPAvA==", + "dev": true, + "dependencies": { + "@volar/language-core": "2.4.26", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.25", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.25.tgz", + "integrity": "sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==", + "dependencies": { + "@babel/parser": "^7.28.5", + "@vue/shared": "3.5.25", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.25", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.25.tgz", + "integrity": "sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q==", + "dependencies": { + "@vue/compiler-core": "3.5.25", + "@vue/shared": "3.5.25" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.25", + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.25.tgz", + "integrity": "sha512-PUgKp2rn8fFsI++lF2sO7gwO2d9Yj57Utr5yEsDf3GNaQcowCLKL7sf+LvVFvtJDXUp/03+dC6f2+LCv5aK1ag==", + "dependencies": { + "@babel/parser": "^7.28.5", + "@vue/compiler-core": "3.5.25", + "@vue/compiler-dom": "3.5.25", + "@vue/compiler-ssr": "3.5.25", + "@vue/shared": "3.5.25", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.25", + "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.25.tgz", + "integrity": "sha512-ritPSKLBcParnsKYi+GNtbdbrIE1mtuFEJ4U1sWeuOMlIziK5GtOL85t5RhsNy4uWIXPgk+OUdpnXiTdzn8o3A==", + "dependencies": { + "@vue/compiler-dom": "3.5.25", + "@vue/shared": "3.5.25" + } + }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmmirror.com/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "dev": true, + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.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==" + }, + "node_modules/@vue/language-core": { + "version": "2.1.10", + "resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-2.1.10.tgz", + "integrity": "sha512-DAI289d0K3AB5TUG3xDp9OuQ71CnrujQwJrQnfuZDwo6eGNf0UoRlPuaVNO+Zrn65PC3j0oB2i7mNmVPggeGeQ==", + "dev": true, + "dependencies": { + "@volar/language-core": "~2.4.8", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^0.2.0", + "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.25", + "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.25.tgz", + "integrity": "sha512-5xfAypCQepv4Jog1U4zn8cZIcbKKFka3AgWHEFQeK65OW+Ys4XybP6z2kKgws4YB43KGpqp5D/K3go2UPPunLA==", + "dependencies": { + "@vue/shared": "3.5.25" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.25", + "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.25.tgz", + "integrity": "sha512-Z751v203YWwYzy460bzsYQISDfPjHTl+6Zzwo/a3CsAf+0ccEjQ8c+0CdX1WsumRTHeywvyUFtW6KvNukT/smA==", + "dependencies": { + "@vue/reactivity": "3.5.25", + "@vue/shared": "3.5.25" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.25", + "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.25.tgz", + "integrity": "sha512-a4WrkYFbb19i9pjkz38zJBg8wa/rboNERq3+hRRb0dHiJh13c+6kAbgqCPfMaJ2gg4weWD3APZswASOfmKwamA==", + "dependencies": { + "@vue/reactivity": "3.5.25", + "@vue/runtime-core": "3.5.25", + "@vue/shared": "3.5.25", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.25", + "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.25.tgz", + "integrity": "sha512-UJaXR54vMG61i8XNIzTSf2Q7MOqZHpp8+x3XLGtE3+fL+nQd+k7O5+X3D/uWrnQXOdMw5VPih+Uremcw+u1woQ==", + "dependencies": { + "@vue/compiler-ssr": "3.5.25", + "@vue/shared": "3.5.25" + }, + "peerDependencies": { + "vue": "3.5.25" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.25", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.25.tgz", + "integrity": "sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==" + }, + "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, + "peerDependencies": { + "@types/node": "*" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/alien-signals": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/alien-signals/-/alien-signals-0.2.2.tgz", + "integrity": "sha512-cZIRkbERILsBOXTQmMrxc9hgpxglstn69zm+F1ARf4aPAzdAFYd6sBq87ErO0Fj3DV94tglcyHG5kQz9nDC/8A==", + "dev": true + }, + "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==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.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 + }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.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==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "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==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "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==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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==", + "dev": true + }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, + "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==" + }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==" + }, + "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 + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "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==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "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==", + "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==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==" + }, + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "dev": true + }, + "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==", + "dev": true, + "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.19.1", + "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "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==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "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==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "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==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "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, + "bin": { + "he": "bin/he" + } + }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/immutable": { + "version": "5.1.4", + "resolved": "https://registry.npmmirror.com/immutable/-/immutable-5.1.4.tgz", + "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", + "dev": true + }, + "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==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "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==", + "dev": true + }, + "node_modules/local-pkg": { + "version": "0.5.1", + "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-0.5.1.tgz", + "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", + "dev": true, + "dependencies": { + "mlly": "^1.7.3", + "pkg-types": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "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==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "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==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "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==", + "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==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==" + }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "dev": true, + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "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==", + "dev": true + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "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, + "optional": true + }, + "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==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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 + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pinia": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.3.1.tgz", + "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==", + "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/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==", + "dev": true, + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "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==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ] + }, + "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==", + "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" + } + ] + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.53.3.tgz", + "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", + "dev": true, + "peer": true, + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.53.3", + "@rollup/rollup-android-arm64": "4.53.3", + "@rollup/rollup-darwin-arm64": "4.53.3", + "@rollup/rollup-darwin-x64": "4.53.3", + "@rollup/rollup-freebsd-arm64": "4.53.3", + "@rollup/rollup-freebsd-x64": "4.53.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", + "@rollup/rollup-linux-arm64-gnu": "4.53.3", + "@rollup/rollup-linux-arm64-musl": "4.53.3", + "@rollup/rollup-linux-loong64-gnu": "4.53.3", + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-musl": "4.53.3", + "@rollup/rollup-linux-s390x-gnu": "4.53.3", + "@rollup/rollup-linux-x64-gnu": "4.53.3", + "@rollup/rollup-linux-x64-musl": "4.53.3", + "@rollup/rollup-openharmony-arm64": "4.53.3", + "@rollup/rollup-win32-arm64-msvc": "4.53.3", + "@rollup/rollup-win32-ia32-msvc": "4.53.3", + "@rollup/rollup-win32-x64-gnu": "4.53.3", + "@rollup/rollup-win32-x64-msvc": "4.53.3", + "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==", + "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" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/sass": { + "version": "1.94.2", + "resolved": "https://registry.npmmirror.com/sass/-/sass-1.94.2.tgz", + "integrity": "sha512-N+7WK20/wOr7CzA2snJcUSSNTCzeCGUTFY3OgeQP3mZ1aj9NMQ0mSTXwlrnd89j33zzQJGqIN52GIOmYrfq46A==", + "dev": true, + "peer": true, + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "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==", + "dev": true + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-literal": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/strip-literal/-/strip-literal-2.1.1.tgz", + "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==", + "dev": true, + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "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, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "devOptional": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.1", + "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "dev": true + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true + }, + "node_modules/unimport": { + "version": "3.14.6", + "resolved": "https://registry.npmmirror.com/unimport/-/unimport-3.14.6.tgz", + "integrity": "sha512-CYvbDaTT04Rh8bmD8jz3WPmHYZRG/NnvYVzwD6V1YAlvvKROlAeNDUBhkBGzNav2RKaeuXvlWYaa1V4Lfi/O0g==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.1.4", + "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.1", + "picomatch": "^4.0.2", + "pkg-types": "^1.3.0", + "scule": "^1.3.0", + "strip-literal": "^2.1.1", + "unplugin": "^1.16.1" + } + }, + "node_modules/unimport/node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "dev": true + }, + "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==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/unimport/node_modules/local-pkg": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-1.1.2.tgz", + "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", + "dev": true, + "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/unimport/node_modules/local-pkg/node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "dev": true, + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, + "node_modules/unimport/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/unplugin": { + "version": "1.16.1", + "resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-1.16.1.tgz", + "integrity": "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==", + "dev": true, + "dependencies": { + "acorn": "^8.14.0", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/unplugin-auto-import": { + "version": "0.18.6", + "resolved": "https://registry.npmmirror.com/unplugin-auto-import/-/unplugin-auto-import-0.18.6.tgz", + "integrity": "sha512-LMFzX5DtkTj/3wZuyG5bgKBoJ7WSgzqSGJ8ppDRdlvPh45mx6t6w3OcbExQi53n3xF5MYkNGPNR/HYOL95KL2A==", + "dev": true, + "dependencies": { + "@antfu/utils": "^0.7.10", + "@rollup/pluginutils": "^5.1.3", + "fast-glob": "^3.3.2", + "local-pkg": "^0.5.1", + "magic-string": "^0.30.14", + "minimatch": "^9.0.5", + "unimport": "^3.13.4", + "unplugin": "^1.16.0" + }, + "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/unplugin-vue-components": { + "version": "0.27.5", + "resolved": "https://registry.npmmirror.com/unplugin-vue-components/-/unplugin-vue-components-0.27.5.tgz", + "integrity": "sha512-m9j4goBeNwXyNN8oZHHxvIIYiG8FQ9UfmKWeNllpDvhU7btKNNELGPt+o3mckQKuPwrE7e0PvCsx+IWuDSD9Vg==", + "dev": true, + "dependencies": { + "@antfu/utils": "^0.7.10", + "@rollup/pluginutils": "^5.1.3", + "chokidar": "^3.6.0", + "debug": "^4.3.7", + "fast-glob": "^3.3.2", + "local-pkg": "^0.5.1", + "magic-string": "^0.30.14", + "minimatch": "^9.0.5", + "mlly": "^1.7.3", + "unplugin": "^1.16.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@babel/parser": "^7.15.8", + "@nuxt/kit": "^3.2.2", + "vue": "2 || 3" + }, + "peerDependenciesMeta": { + "@babel/parser": { + "optional": true + }, + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/unplugin-vue-components/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "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/unplugin-vue-components/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, + "node_modules/vant": { + "version": "4.9.21", + "resolved": "https://registry.npmmirror.com/vant/-/vant-4.9.21.tgz", + "integrity": "sha512-hXUoZMrLLjykimFRLDlGNd+K2iYSRh9YwLMKnsVdVZ+9inUKxpqnjhOqlZwocbnYkvJlS+febf9u9aJpDol4Pw==", + "dependencies": { + "@vant/popperjs": "^1.3.0", + "@vant/use": "^1.6.0", + "@vue/shared": "^3.5.17" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmmirror.com/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "dev": true, + "peer": true, + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.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 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/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, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true + }, + "node_modules/vue": { + "version": "3.5.25", + "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.25.tgz", + "integrity": "sha512-YLVdgv2K13WJ6n+kD5owehKtEXwdwXuj2TTyJMsO7pSeKw2bfRNZGjhB7YzrpbMYj5b5QsUebHpOqR3R3ziy/g==", + "peer": true, + "dependencies": { + "@vue/compiler-dom": "3.5.25", + "@vue/compiler-sfc": "3.5.25", + "@vue/runtime-dom": "3.5.25", + "@vue/server-renderer": "3.5.25", + "@vue/shared": "3.5.25" + }, + "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, + "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-router": { + "version": "4.6.3", + "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.6.3.tgz", + "integrity": "sha512-ARBedLm9YlbvQomnmq91Os7ck6efydTSpRP3nuOKCvgJOHNrhRoJDSKtee8kcL1Vf7nz6U+PMBL+hTvR3bTVQg==", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/vue-tsc": { + "version": "2.1.10", + "resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-2.1.10.tgz", + "integrity": "sha512-RBNSfaaRHcN5uqVqJSZh++Gy/YUzryuv9u1aFWhsammDJXNtUiJMNoJ747lZcQ68wUQFx6E73y4FY3D8E7FGMA==", + "dev": true, + "dependencies": { + "@volar/typescript": "~2.4.8", + "@vue/language-core": "2.1.10", + "semver": "^7.5.4" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "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==", + "dev": true + } + } +} diff --git a/mobile-h5/public/favicon.ico b/mobile-h5/public/favicon.ico new file mode 100644 index 000000000..daad51d88 Binary files /dev/null and b/mobile-h5/public/favicon.ico differ diff --git a/mobile-h5/src/App.vue b/mobile-h5/src/App.vue new file mode 100644 index 000000000..1ee7f0005 --- /dev/null +++ b/mobile-h5/src/App.vue @@ -0,0 +1,56 @@ + + + + + + diff --git a/mobile-h5/src/api/articleApi.ts b/mobile-h5/src/api/articleApi.ts new file mode 100644 index 000000000..b1bdcf3cb --- /dev/null +++ b/mobile-h5/src/api/articleApi.ts @@ -0,0 +1,45 @@ +import request from '@/utils/http' +import { PaginationResult, BaseResult } from '@/types/axios' +import { ArticleType, ArticleCategoryType } from './model/articleModel' + +// 文章 +export class ArticleService { + // 获取文章列表 + static getArticleList(params: any) { + const { page, size, searchVal, year } = params + return request.get>({ + url: `/api/articles/${page}/${size}?title=${searchVal}&year=${year}` + }) + } + + // 获取文章类型 + static getArticleTypes(params: object) { + return request.get>({ + url: '/api/articles/types', + params + }) + } + + // 获取文章详情 + static getArticleDetail(id: number) { + return request.get>({ + url: `/api/articles/${id}` + }) + } + + // 新增文章 + static addArticle(params: any) { + return request.post({ + url: '/api/articles/', + data: params + }) + } + + // 编辑文章 + static editArticle(id: number, params: any) { + return request.put({ + url: `/api/articles/${id}`, + data: params + }) + } +} diff --git a/mobile-h5/src/api/dashboardApi.ts b/mobile-h5/src/api/dashboardApi.ts new file mode 100644 index 000000000..eef13c83a --- /dev/null +++ b/mobile-h5/src/api/dashboardApi.ts @@ -0,0 +1,45 @@ +import api from '@/utils/http' + +// ==================== 仪表盘 API ==================== + +export interface DashboardCargoStat { + label: string + planned: number + actual: number +} + +export interface DashboardPartnerCargoStat { + label: string + planned: number + actual: number +} + +export const dashboardApi = { + // 获取今日入库量按货物统计 + getInboundByCargo: async (): Promise => { + return api.get({ + url: '/dashboard/inbound/by-cargo' + }) + }, + + // 获取今日入库量按往来单位+货物统计 + getInboundByPartnerCargo: async (): Promise => { + return api.get({ + url: '/dashboard/inbound/by-partner-cargo' + }) + }, + + // 获取今日出库量按货物统计 + getOutboundByCargo: async (): Promise => { + return api.get({ + url: '/dashboard/outbound/by-cargo' + }) + }, + + // 获取今日出库量按往来单位+货物统计 + getOutboundByPartnerCargo: async (): Promise => { + return api.get({ + url: '/dashboard/outbound/by-partner-cargo' + }) + } +} diff --git a/mobile-h5/src/api/inventoryApi.ts b/mobile-h5/src/api/inventoryApi.ts new file mode 100644 index 000000000..66e8bca9a --- /dev/null +++ b/mobile-h5/src/api/inventoryApi.ts @@ -0,0 +1,112 @@ +import api from '@/utils/http' +import type { Plan } from './plansApi' +import type { WeighingRecord } from './weighingApi' + +// ==================== 出入库管理 API ==================== + +export interface InventoryRecord { + id: number + date: string + partnerCompany: string + businessType: '入库' | '出库' + product: string + quantity: number + planNo?: string + batchNo?: string + vehicleNo: string + status?: string + remarks?: string + purchasePlanId?: number + isDeleted: number + createdAt: string + grossWeight?: number // 毛重(kg)- 汇总自关联地磅单 + tareWeight?: number // 皮重(kg)- 汇总自关联地磅单 + deduction?: number // 扣杂(kg)- 汇总自关联地磅单 + netWeight?: number // 净重(kg)- 汇总自关联地磅单 + grossWeightTime?: string // 毛重时间 - 来自关联地磅单 + tareWeightTime?: string // 皮重时间 - 来自关联地磅单 + hasWeighingSlip?: boolean // 是否已有关联的地磅单 +} + +export interface InventoryCreate { + date: string + partnerCompany: string + businessType: '入库' | '出库' + product: string + quantity: number + planNo?: string + batchNo?: string + vehicleNo: string + status?: string + remarks?: string + purchasePlanId?: number +} + +export interface InventoryUpdate { + date?: string + partnerCompany?: string + businessType?: '入库' | '出库' + product?: string + quantity?: number + planNo?: string + batchNo?: string + vehicleNo?: string + status?: string + remarks?: string | null + purchasePlanId?: number +} + +export interface InventoryRelatedData { + plan: Plan | null + weighing: WeighingRecord | null +} + +export const inventoryApi = { + list: async (params?: { + q?: string + dateFrom?: string + dateTo?: string + status?: string + businessType?: '入库' | '出库' + }): Promise => { + return api.get({ + url: '/inventory', + params, + timeout: 60000 // 列表可能数据较多,60 秒超时避免被取消 + }) + }, + get: async (id: number): Promise => { + return api.get({ + url: `/inventory/${id}` + }) + }, + getRelated: async (id: number): Promise => { + return api.get({ + url: `/inventory/${id}/related` + }) + }, + create: async (payload: InventoryCreate): Promise => { + return api.post({ + url: '/inventory', + data: payload + }) + }, + update: async (id: number, payload: InventoryUpdate): Promise => { + return api.patch({ + url: `/inventory/${id}`, + data: payload + }) + }, + delete: async (id: number): Promise => { + return api.del({ + url: `/inventory/${id}` + }) + }, + createLinkForPlan: async (purchasePlanId: number, remarks?: string): Promise => { + return api.post({ + url: '/inventory/create-link-for-plan', + data: { purchasePlanId, remarks } + }) + } +} + diff --git a/mobile-h5/src/api/masterDataApi.ts b/mobile-h5/src/api/masterDataApi.ts new file mode 100644 index 000000000..b9a1ef305 --- /dev/null +++ b/mobile-h5/src/api/masterDataApi.ts @@ -0,0 +1,149 @@ +import api from '@/utils/http' + +// ==================== 公司(往来单位)API ==================== + +export interface Company { + id: number + name: string + remarks?: string | null +} + +export interface CompanyCreate { + name: string + remarks?: string | null +} + +export interface CompanyUpdate { + name?: string + remarks?: string | null +} + +export const companyApi = { + list: async (q?: string): Promise => { + return api.get({ + url: '/master-data/companies', + params: q ? { q } : undefined + }) + }, + get: async (id: number): Promise => { + return api.get({ + url: `/master-data/companies/${id}` + }) + }, + create: async (payload: CompanyCreate): Promise => { + return api.post({ + url: '/master-data/companies', + data: payload + }) + }, + update: async (id: number, payload: CompanyUpdate): Promise => { + return api.put({ + url: `/master-data/companies/${id}`, + data: payload + }) + }, + delete: async (id: number): Promise => { + return api.del({ + url: `/master-data/companies/${id}` + }) + } +} + +// ==================== 货物(产品)API ==================== + +export interface Cargo { + id: number + name: string + remarks?: string | null +} + +export interface CargoCreate { + name: string + remarks?: string | null +} + +export interface CargoUpdate { + name?: string + remarks?: string | null +} + +export const cargoApi = { + list: async (q?: string): Promise => { + return api.get({ + url: '/master-data/cargos', + params: q ? { q } : undefined + }) + }, + get: async (id: number): Promise => { + return api.get({ + url: `/master-data/cargos/${id}` + }) + }, + create: async (payload: CargoCreate): Promise => { + return api.post({ + url: '/master-data/cargos', + data: payload + }) + }, + update: async (id: number, payload: CargoUpdate): Promise => { + return api.put({ + url: `/master-data/cargos/${id}`, + data: payload + }) + }, + delete: async (id: number): Promise => { + return api.del({ + url: `/master-data/cargos/${id}` + }) + } +} + +// ==================== 车牌号 API ==================== + +export interface Vehicle { + id: number + vehicleNo: string + remarks?: string | null +} + +export interface VehicleCreate { + vehicleNo: string + remarks?: string | null +} + +export interface VehicleUpdate { + vehicleNo?: string + remarks?: string | null +} + +export const vehicleApi = { + list: async (q?: string): Promise => { + return api.get({ + url: '/master-data/vehicles', + params: q ? { q } : undefined + }) + }, + get: async (id: number): Promise => { + return api.get({ + url: `/master-data/vehicles/${id}` + }) + }, + create: async (payload: VehicleCreate): Promise => { + return api.post({ + url: '/master-data/vehicles', + data: payload + }) + }, + update: async (id: number, payload: VehicleUpdate): Promise => { + return api.put({ + url: `/master-data/vehicles/${id}`, + data: payload + }) + }, + delete: async (id: number): Promise => { + return api.del({ + url: `/master-data/vehicles/${id}` + }) + } +} + diff --git a/mobile-h5/src/api/menuApi.ts b/mobile-h5/src/api/menuApi.ts new file mode 100755 index 000000000..2612b4726 --- /dev/null +++ b/mobile-h5/src/api/menuApi.ts @@ -0,0 +1,60 @@ +import { MenuListType } from '@/types/menu' +import { processRoute } from '@/utils/menu' +import { showLoadingToast, closeToast } from 'vant' + +// 简单的路由数据(移动端不需要复杂的路由配置) +const simpleRoutes: any[] = [ + { + path: '/dashboard', + name: 'Dashboard', + meta: { title: '工作台', icon: '', isHide: false } + }, + { + path: '/plans', + name: 'Plans', + meta: { title: '计划管理', icon: '', isHide: false } + }, + { + path: '/inventory', + name: 'Inventory', + meta: { title: '出入库管理', icon: '', isHide: false } + }, + { + path: '/weighing', + name: 'Weighing', + meta: { title: '地磅单管理', icon: '', isHide: false } + }, + { + path: '/master-data', + name: 'MasterData', + meta: { title: '基础资料', icon: '', isHide: false } + } +] + +// 菜单接口 +export const menuService = { + // 获取菜单列表,模拟网络请求 + getMenuList( + delay: number = 300 + ): Promise<{ menuList: MenuListType[]; closeLoading: () => void }> { + // 获取到的菜单数据 + const menuList = simpleRoutes + // 处理后的菜单数据 + const processedMenuList: MenuListType[] = menuList.map((route: any) => processRoute(route)) + + showLoadingToast({ + message: '加载中...', + forbidClick: true, + duration: 0 + }) + + return new Promise((resolve) => { + setTimeout(() => { + resolve({ + menuList: processedMenuList, + closeLoading: () => closeToast() + }) + }, delay) + }) + } +} diff --git a/mobile-h5/src/api/model/articleModel.ts b/mobile-h5/src/api/model/articleModel.ts new file mode 100644 index 000000000..cd75b6d25 --- /dev/null +++ b/mobile-h5/src/api/model/articleModel.ts @@ -0,0 +1,18 @@ +export interface ArticleType { + id?: number + blog_class: string + title: string + count?: number + html_content: string + create_time: string // 或者使用 Date 类型 + home_img: string + brief: string + type_name?: string +} + +export interface ArticleCategoryType { + id: number + name: string + icon: string + count: number +} diff --git a/mobile-h5/src/api/plansApi.ts b/mobile-h5/src/api/plansApi.ts new file mode 100644 index 000000000..e89822529 --- /dev/null +++ b/mobile-h5/src/api/plansApi.ts @@ -0,0 +1,121 @@ +import api from '@/utils/http' +import type { InventoryRecord } from './inventoryApi' +import type { WeighingRecord } from './weighingApi' + +// ==================== 计划管理 API ==================== + +export interface Plan { + id: number + planNo?: string + planDate: string + startDate?: string + endDate?: string + businessType: '入库' | '出库' + product: string + specification?: string + plannedQuantity: number + vehicleNo?: string + customerName?: string + remarks?: string + executionStatus?: string + executedAt?: string + lastWeighingSlipId?: number + executedQuantity?: number + isDeleted: number + createdAt: string + /** 生成链接交三方填写时对应的出入库单 id */ + fillLinkInventoryId?: number +} + +export interface PlanCreate { + planNo?: string + planDate: string + startDate?: string + endDate?: string + businessType: '入库' | '出库' + product: string + specification?: string + plannedQuantity: number + vehicleNo?: string + customerName?: string + remarks?: string + executionStatus?: string +} + +export interface PlanUpdate { + planNo?: string + planDate?: string + startDate?: string + endDate?: string + businessType?: '入库' | '出库' + product?: string + specification?: string + plannedQuantity?: number + vehicleNo?: string + customerName?: string + remarks?: string + executionStatus?: string + executedAt?: string + lastWeighingSlipId?: number + executedQuantity?: number +} + +export interface PlanRelatedData { + inventories: InventoryRecord[] + weighings: WeighingRecord[] +} + +export interface ThirdPartyPlanPageData { + plan: Plan + today_inventories: InventoryRecord[] + /** 本月历史出入库单(不含今天),按日期倒序 */ + month_inventories: InventoryRecord[] +} + +export const planApi = { + list: async (params?: { + q?: string + businessType?: '入库' | '出库' + executionStatus?: string + createdFrom?: string + createdTo?: string + }): Promise => { + return api.get({ + url: '/plans', + params + }) + }, + get: async (id: number): Promise => { + return api.get({ + url: `/plans/${id}` + }) + }, + getRelated: async (id: number): Promise => { + return api.get({ + url: `/plans/${id}/related` + }) + }, + getThirdPartyPage: async (id: number): Promise => { + return api.get({ + url: `/plans/${id}/third-party-page` + }) + }, + create: async (payload: PlanCreate): Promise => { + return api.post({ + url: '/plans', + data: payload + }) + }, + update: async (id: number, payload: PlanUpdate): Promise => { + return api.patch({ + url: `/plans/${id}`, + data: payload + }) + }, + delete: async (id: number): Promise => { + return api.del({ + url: `/plans/${id}` + }) + } +} + diff --git a/mobile-h5/src/api/usersApi.ts b/mobile-h5/src/api/usersApi.ts new file mode 100644 index 000000000..24037dd1d --- /dev/null +++ b/mobile-h5/src/api/usersApi.ts @@ -0,0 +1,126 @@ +import api from '@/utils/http' +import type { BaseResult } from '@/types/axios' +import { ApiStatus } from '@/utils/http/status' + +// ==================== 用户管理 API ==================== + +export interface User { + id: number + username: string + role: 'admin' | 'operator' + createdAt?: string +} + +export interface UserCreate { + username: string + password: string + role: 'admin' | 'operator' + remark?: string +} + +export interface UserUpdate { + username?: string + password?: string + role?: 'admin' | 'operator' + remark?: string +} + +// 登录响应 +export interface LoginResponse { + accessToken: string + refreshToken?: string +} + +// 用户信息(用于登录后获取) +export interface UserInfo { + id: number + username: string + name?: string + avatar?: string + email?: string + role?: 'admin' | 'operator' +} + +// 用户管理 API(CRUD) +export const userApi = { + list: async (): Promise => { + return api.get({ + url: '/users' + }) + }, + get: async (id: number): Promise => { + return api.get({ + url: `/users/${id}` + }) + }, + create: async (payload: UserCreate): Promise => { + return api.post({ + url: '/users', + data: payload + }) + }, + update: async (id: number, payload: UserUpdate): Promise => { + return api.put({ + url: `/users/${id}`, + data: payload + }) + }, + delete: async (id: number): Promise => { + return api.del({ + url: `/users/${id}` + }) + } +} + +// 用户服务(登录、获取用户信息等) +export const UserService = { + // 登录 + login: async (username: string, password: string): Promise> => { + try { + const response = await api.post({ + url: '/auth/login', + data: { + username, + password + } + }) + + // 后端直接返回 LoginResponse,需要包装成 BaseResult 格式 + return { + code: ApiStatus.success, + message: '登录成功', + data: response + } + } catch (error: any) { + const errorMessage = error.response?.data?.detail || error.response?.data?.message || error.message || '登录失败' + return { + code: error.response?.status || 500, + message: errorMessage, + data: null as any + } + } + }, + + // 获取用户信息 + getUserInfo: async (): Promise> => { + try { + const response = await api.get({ + url: '/auth/userinfo' + }) + + // 后端直接返回 UserInfo,需要包装成 BaseResult 格式 + return { + code: ApiStatus.success, + message: '获取成功', + data: response + } + } catch (error: any) { + const errorMessage = error.response?.data?.detail || error.response?.data?.message || error.message || '获取用户信息失败' + return { + code: error.response?.status || 500, + message: errorMessage, + data: null as any + } + } + } +} diff --git a/mobile-h5/src/api/weighingApi.ts b/mobile-h5/src/api/weighingApi.ts new file mode 100644 index 000000000..b64ecdb08 --- /dev/null +++ b/mobile-h5/src/api/weighingApi.ts @@ -0,0 +1,132 @@ +import api from '@/utils/http' +import type { Plan } from './plansApi' +import type { InventoryRecord } from './inventoryApi' + +// ==================== 地磅单管理 API ==================== + +export interface WeighingRecord { + id: number + vehicleNo: string + planNo?: string + cargoName: string + businessType: '入库' | '出库' + batchNo?: string + supplier: string + consignee: string + grossWeight: number + grossWeightTime: string + tareWeight: number + tareWeightTime: string + deduction?: number + netWeight: number + plannedQuantity?: number + executedQuantity?: number + purchasePlanId?: number + inboundOutboundId?: number + weighingPoint?: string + remarks?: string + currentQuantity?: number + isVoided: number + createdAt: string + inventoryDate?: string // 出入库单日期 +} + +export interface WeighingCreate { + vehicleNo: string + planNo?: string + cargoName: string + businessType: '入库' | '出库' + batchNo?: string + supplier: string + consignee: string + grossWeight: number + grossWeightTime: string + tareWeight: number + tareWeightTime: string + deduction?: number + netWeight: number + plannedQuantity?: number + executedQuantity?: number + purchasePlanId?: number + inboundOutboundId?: number + weighingPoint?: string + remarks?: string + currentQuantity?: number +} + +export interface WeighingUpdate { + vehicleNo?: string + planNo?: string + cargoName?: string + businessType?: '入库' | '出库' + batchNo?: string + supplier?: string + consignee?: string + grossWeight?: number + grossWeightTime?: string + tareWeight?: number + tareWeightTime?: string + deduction?: number + netWeight?: number + plannedQuantity?: number + executedQuantity?: number + purchasePlanId?: number + inboundOutboundId?: number + weighingPoint?: string + remarks?: string + currentQuantity?: number + isVoided?: number +} + +export interface WeighingRelatedData { + plan: Plan | null + inventory: InventoryRecord | null +} + +export const weighingApi = { + list: async (params?: { + dateFrom?: string + dateTo?: string + vehicleNo?: string + cargoName?: string + businessType?: '入库' | '出库' + }): Promise => { + return api.get({ + url: '/weighing', + params + }) + }, + get: async (id: number): Promise => { + return api.get({ + url: `/weighing/${id}` + }) + }, + getRelated: async (id: number): Promise => { + return api.get({ + url: `/weighing/${id}/related` + }) + }, + create: async (payload: WeighingCreate): Promise => { + return api.post({ + url: '/weighing', + data: payload + }) + }, + update: async (id: number, payload: WeighingUpdate): Promise => { + return api.patch({ + url: `/weighing/${id}`, + data: payload + }) + }, + void: async (id: number): Promise => { + return api.post({ + url: `/weighing/${id}/void` + }) + }, + delete: async (id: number): Promise => { + return api.del({ + url: `/weighing/${id}` + }) + } +} + diff --git a/mobile-h5/src/assets/img/avatar11.png b/mobile-h5/src/assets/img/avatar11.png new file mode 100644 index 000000000..b89e9d66e Binary files /dev/null and b/mobile-h5/src/assets/img/avatar11.png differ diff --git a/mobile-h5/src/assets/img/favicon.ico b/mobile-h5/src/assets/img/favicon.ico new file mode 100644 index 000000000..cd1c19376 Binary files /dev/null and b/mobile-h5/src/assets/img/favicon.ico differ diff --git a/mobile-h5/src/assets/img/logo-sx.png b/mobile-h5/src/assets/img/logo-sx.png new file mode 100644 index 000000000..53a164eb3 Binary files /dev/null and b/mobile-h5/src/assets/img/logo-sx.png differ diff --git a/mobile-h5/src/assets/img/logo.gif b/mobile-h5/src/assets/img/logo.gif new file mode 100644 index 000000000..b1badef11 Binary files /dev/null and b/mobile-h5/src/assets/img/logo.gif differ diff --git a/mobile-h5/src/assets/img/logo.png b/mobile-h5/src/assets/img/logo.png new file mode 100644 index 000000000..b5771e209 Binary files /dev/null and b/mobile-h5/src/assets/img/logo.png differ diff --git a/mobile-h5/src/assets/img/sx1.jpg b/mobile-h5/src/assets/img/sx1.jpg new file mode 100644 index 000000000..f7ce87d83 Binary files /dev/null and b/mobile-h5/src/assets/img/sx1.jpg differ diff --git a/mobile-h5/src/assets/img/tab-records.png b/mobile-h5/src/assets/img/tab-records.png new file mode 100644 index 000000000..b5771e209 Binary files /dev/null and b/mobile-h5/src/assets/img/tab-records.png differ diff --git a/mobile-h5/src/assets/img/tab-weigh.png b/mobile-h5/src/assets/img/tab-weigh.png new file mode 100644 index 000000000..b5771e209 Binary files /dev/null and b/mobile-h5/src/assets/img/tab-weigh.png differ diff --git a/mobile-h5/src/assets/styles/detail-page.scss b/mobile-h5/src/assets/styles/detail-page.scss new file mode 100644 index 000000000..6f3076f16 --- /dev/null +++ b/mobile-h5/src/assets/styles/detail-page.scss @@ -0,0 +1,104 @@ +// 详情页面通用样式 +.page-content { + min-height: 100vh; + background: #f5f7fa; +} + +.page-header { + display: flex; + align-items: center; + padding: 16px; + background-color: #fff; + border-bottom: 1px solid #eee; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); + margin-bottom: 16px; + + &__back { + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + margin-right: 12px; + } + + .back-arrow { + width: 16px; + height: 16px; + border-left: 2px solid #333; + border-bottom: 2px solid #333; + transform: rotate(45deg); + } + + &__title { + font-size: 18px; + font-weight: bold; + color: #333; + } +} + +.form-container { + padding: 16px; +} + +.form-section { + background-color: #fff; + border-radius: 12px; + padding: 16px; + margin-bottom: 16px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); +} + +.form-item { + margin-bottom: 24px; + &:last-child { + margin-bottom: 0; + } +} + +.form-label { + font-size: 14px; + color: #333; + margin-bottom: 8px; + display: block; + font-weight: 500; + + .required { + color: #ee0a24; + margin-left: 4px; + } +} + +.form-input { + display: flex; + align-items: center; + background-color: #f7f7f7; + border-radius: 8px; + padding: 12px 16px; + font-size: 14px; + color: #333; + min-height: 44px; + + .placeholder { + color: #999; + } + + .arrow-icon { + margin-left: auto; + color: #999; + } +} + +.radio-wrapper { + padding: 12px 0; +} + +.form-actions { + display: flex; + flex-direction: column; + gap: 12px; + margin-top: 24px; + padding: 0 16px; +} + diff --git a/mobile-h5/src/assets/styles/index.scss b/mobile-h5/src/assets/styles/index.scss new file mode 100644 index 000000000..8057313e5 --- /dev/null +++ b/mobile-h5/src/assets/styles/index.scss @@ -0,0 +1,26 @@ +// 全局样式 +@import './variables.scss'; + +// 重置样式 +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + background-color: #f5f7fa; +} + +// Vant 样式覆盖 +.van-button { + border-radius: 8px; +} + +.van-cell { + border-radius: 8px; +} + diff --git a/mobile-h5/src/assets/styles/vant-theme.scss b/mobile-h5/src/assets/styles/vant-theme.scss new file mode 100644 index 000000000..014025c54 --- /dev/null +++ b/mobile-h5/src/assets/styles/vant-theme.scss @@ -0,0 +1,438 @@ +// Vant 主题配置 - 与 Web 端保持一致 +// Web 端颜色: +// primary: #52B788 (绿色) +// success: #13deb9 (青绿色) +// warning: #ffae1f (橙色) +// danger: #ff4d4f (红色) +// info: #52B788 (绿色) + +:root { + // Vant 4 主题色 CSS 变量 + --van-primary-color: #52B788 !important; + --van-success-color: #13deb9 !important; + --van-warning-color: #ffae1f !important; + --van-danger-color: #ff4d4f !important; + --van-info-color: #52B788 !important; + + // 确保所有使用 primary-color 的地方都使用我们的绿色 + --van-button-primary-background-color: #52B788 !important; + --van-button-primary-border-color: #52B788 !important; + --van-tag-primary-color: #52B788 !important; + --van-tag-primary-background-color: rgba(82, 183, 136, 0.1) !important; + --van-tag-primary-border-color: #52B788 !important; +} + +// 覆盖 Vant Button 样式 - 使用 !important 确保优先级 +.van-button--primary { + background-color: #52B788 !important; + border-color: #52B788 !important; + color: #fff !important; + + &:hover { + background-color: #45a075 !important; + border-color: #45a075 !important; + } + + &:active { + background-color: #45a075 !important; + border-color: #45a075 !important; + } + + &.van-button--loading { + background-color: #52B788 !important; + border-color: #52B788 !important; + } + + &.van-button--disabled { + background-color: #a5d6a7 !important; + border-color: #a5d6a7 !important; + opacity: 0.6; + } +} + +.van-button--success { + background-color: #13deb9; + border-color: #13deb9; + + &:active { + background-color: #10c5a3; + border-color: #10c5a3; + } +} + +.van-button--warning { + background-color: #ffae1f; + border-color: #ffae1f; + + &:active { + background-color: #e69d1c; + border-color: #e69d1c; + } +} + +.van-button--danger { + background-color: #ff4d4f; + border-color: #ff4d4f; + + &:active { + background-color: #e63e40; + border-color: #e63e40; + } +} + +// 覆盖 Vant Tag 样式 +.van-tag--primary { + background-color: rgba(82, 183, 136, 0.1); + color: #52B788; + border-color: #52B788; +} + +.van-tag--success { + background-color: rgba(19, 222, 185, 0.1); + color: #13deb9; + border-color: #13deb9; +} + +.van-tag--warning { + background-color: rgba(255, 174, 31, 0.1); + color: #ffae1f; + border-color: #ffae1f; +} + +.van-tag--danger { + background-color: rgba(255, 77, 79, 0.1); + color: #ff4d4f; + border-color: #ff4d4f; +} + +.van-tag--info { + background-color: rgba(82, 183, 136, 0.1); + color: #52B788; + border-color: #52B788; +} + +// Plain 模式的 Button +.van-button--primary.van-button--plain { + background-color: transparent !important; + color: #52B788 !important; + border-color: #52B788 !important; + + &:hover { + background-color: rgba(82, 183, 136, 0.1) !important; + } + + &:active { + background-color: rgba(82, 183, 136, 0.2) !important; + } +} + +// Plain 模式的 Tag +.van-tag--primary.van-tag--plain { + background-color: transparent !important; + color: #52B788 !important; + border-color: #52B788 !important; +} + +.van-tag--success.van-tag--plain { + background-color: transparent; + color: #13deb9; + border-color: #13deb9; +} + +.van-tag--warning.van-tag--plain { + background-color: transparent; + color: #ffae1f; + border-color: #ffae1f; +} + +.van-tag--danger.van-tag--plain { + background-color: transparent; + color: #ff4d4f; + border-color: #ff4d4f; +} + +.van-tag--info.van-tag--plain { + background-color: transparent; + color: #52B788; + border-color: #52B788; +} + +// 覆盖 Vant Tabs 样式 +.van-tabs { + .van-tabs__line { + background-color: #52B788 !important; + } + + .van-tab--active { + color: #52B788 !important; + } +} + +// 覆盖 Vant NavBar 样式 +.van-nav-bar { + .van-nav-bar__title { + color: #52B788; + } + + .van-nav-bar__arrow { + color: #52B788; + } +} + +// 覆盖 Vant Tabbar 样式 +.van-tabbar-item--active { + color: #52B788 !important; +} + +// 覆盖 Vant Switch 样式 +.van-switch--on { + background-color: #52B788 !important; +} + +// 覆盖 Vant Checkbox 样式 +.van-checkbox__icon--checked { + background-color: #52B788 !important; + border-color: #52B788 !important; +} + +// 覆盖 Vant Radio 样式 +.van-radio__icon--checked { + .van-icon { + color: #52B788 !important; + } +} + +// 覆盖 Vant Stepper 样式 +.van-stepper__plus { + background-color: #52B788 !important; + border-color: #52B788 !important; +} + +.van-stepper__minus { + border-color: #52B788 !important; + color: #52B788 !important; +} + +// 覆盖 Vant Loading 样式 +.van-loading__spinner { + color: #52B788 !important; +} + +// 覆盖 Vant Progress 样式 +.van-progress__portion { + background-color: #52B788 !important; +} + +// 覆盖 Vant Circle 样式 +.van-circle__text { + color: #52B788 !important; +} + +// 覆盖 Vant NoticeBar 样式 +.van-notice-bar { + background-color: rgba(82, 183, 136, 0.1) !important; + color: #52B788 !important; +} + +// 覆盖 Vant Cell 样式(链接颜色) +.van-cell--clickable:active { + background-color: rgba(82, 183, 136, 0.05) !important; +} + +// 覆盖 Vant Field 样式(聚焦时的边框颜色) +.van-field__control:focus { + border-color: #52B788 !important; +} + +// 覆盖 Vant Search 样式 +.van-search__action { + color: #52B788 !important; +} + +// 覆盖 Vant Picker 样式 +.van-picker__confirm { + color: #52B788 !important; +} + +// 覆盖 Vant Calendar 样式 +.van-calendar__selected-day { + background-color: #52B788 !important; + color: #fff !important; +} + +// 覆盖 Vant DatetimePicker 样式 +.van-datetime-picker__confirm { + color: #52B788 !important; +} + +// 覆盖 Vant Rate 样式 +.van-rate__icon--full { + color: #52B788 !important; +} + +// 覆盖 Vant Slider 样式 +.van-slider__button { + background-color: #52B788 !important; + border-color: #52B788 !important; +} + +.van-slider__bar { + background-color: #52B788 !important; +} + +// 覆盖 Vant Uploader 样式 +.van-uploader__upload { + border-color: #52B788 !important; +} + +.van-uploader__preview-delete { + background-color: #52B788 !important; +} + +// 覆盖 Vant ActionSheet 样式 +.van-action-sheet__item--active { + color: #52B788 !important; +} + +// 覆盖 Vant Popup 样式(关闭按钮等) +.van-popup__close-icon { + color: #52B788 !important; +} + +// 覆盖 Vant Dialog 样式 +.van-dialog__confirm { + color: #52B788 !important; +} + +// 覆盖 Vant Toast 样式 +.van-toast--success { + background-color: #52B788 !important; +} + +// 覆盖 Vant Notify 样式 +.van-notify--primary { + background-color: #52B788 !important; +} + +// 覆盖 Vant ImagePreview 样式 +.van-image-preview__index { + color: #52B788 !important; +} + +// 覆盖 Vant Skeleton 样式 +.van-skeleton__content { + background-color: rgba(82, 183, 136, 0.1) !important; +} + +// 覆盖 Vant PullRefresh 样式 +.van-pull-refresh__head { + color: #52B788 !important; +} + +// 覆盖 Vant List 样式 +.van-list__finished-text { + color: #52B788 !important; +} + +// 覆盖 Vant Empty 样式 +.van-empty__description { + color: #52B788 !important; +} + +// 覆盖 Vant Sticky 样式 +.van-sticky { + background-color: rgba(82, 183, 136, 0.05) !important; +} + +// 覆盖 Vant IndexBar 样式 +.van-index-bar__index--active { + color: #52B788 !important; +} + +// 覆盖 Vant Sidebar 样式 +.van-sidebar-item--select { + color: #52B788 !important; + border-color: #52B788 !important; +} + +// 覆盖 Vant TreeSelect 样式 +.van-tree-select__item--active { + color: #52B788 !important; +} + +// 覆盖 Vant ContactCard 样式 +.van-contact-card { + border-color: #52B788 !important; +} + +// 覆盖 Vant Coupon 样式 +.van-coupon__tag { + background-color: #52B788 !important; +} + +// 覆盖 Vant GoodsAction 样式 +.van-goods-action-button--first { + background-color: #52B788 !important; +} + +// 覆盖 Vant SubmitBar 样式 +.van-submit-bar__button { + background-color: #52B788 !important; +} + +// 覆盖 Vant SwipeCell 样式 +.van-swipe-cell__right { + background-color: #52B788 !important; +} + +// 覆盖 Vant Card 样式(如果有主题色相关) +.van-card__tag { + background-color: #52B788 !important; +} + +// 覆盖 Vant Divider 样式 +.van-divider { + border-color: rgba(82, 183, 136, 0.2) !important; +} + +// 覆盖 Vant Grid 样式 +.van-grid-item__icon { + color: #52B788 !important; +} + +// 覆盖 Vant Panel 样式 +.van-panel__header { + color: #52B788 !important; +} + +// 覆盖 Vant Collapse 样式 +.van-collapse-item__title--expanded { + color: #52B788 !important; +} + +// 覆盖 Vant Steps 样式 +.van-step__circle { + background-color: #52B788 !important; +} + +.van-step__line { + background-color: #52B788 !important; +} + +// 覆盖 Vant CountDown 样式 +.van-count-down { + color: #52B788 !important; +} + +// 覆盖 Vant Badge 样式 +.van-badge { + background-color: #52B788 !important; +} + +// 覆盖 Vant Icon 样式(如果使用了主题色) +.van-icon { + &[class*="primary"] { + color: #52B788 !important; + } +} + diff --git a/mobile-h5/src/assets/styles/variables.scss b/mobile-h5/src/assets/styles/variables.scss new file mode 100644 index 000000000..c53ef1c2f --- /dev/null +++ b/mobile-h5/src/assets/styles/variables.scss @@ -0,0 +1,26 @@ +// 主题色 +$primary-color: #52B788; +$success-color: #13deb9; +$warning-color: #ffae1f; +$danger-color: #ff4d4f; + +// 文字颜色 +$text-primary: #111827; +$text-secondary: #6b7280; +$text-tertiary: #9ca3af; + +// 背景色 +$bg-primary: #ffffff; +$bg-secondary: #f5f7fa; +$bg-tertiary: #f8fafc; + +// 边框色 +$border-color: #e5e7eb; + +// 间距 +$spacing-xs: 8px; +$spacing-sm: 12px; +$spacing-md: 16px; +$spacing-lg: 24px; +$spacing-xl: 32px; + diff --git a/mobile-h5/src/components/Charts/DualBarChart.vue b/mobile-h5/src/components/Charts/DualBarChart.vue new file mode 100644 index 000000000..b2dfd1681 --- /dev/null +++ b/mobile-h5/src/components/Charts/DualBarChart.vue @@ -0,0 +1,192 @@ + + + + + diff --git a/mobile-h5/src/components/Charts/SimpleBarChart.vue b/mobile-h5/src/components/Charts/SimpleBarChart.vue new file mode 100644 index 000000000..bd1bdf901 --- /dev/null +++ b/mobile-h5/src/components/Charts/SimpleBarChart.vue @@ -0,0 +1,112 @@ + + + + + + diff --git a/mobile-h5/src/composables/useCeremony.ts b/mobile-h5/src/composables/useCeremony.ts new file mode 100644 index 000000000..0f4d07368 --- /dev/null +++ b/mobile-h5/src/composables/useCeremony.ts @@ -0,0 +1,99 @@ +import { useSettingStore } from '@/store/modules/setting' +import { festivalList } from '@/config/core/ceremony' +import mittBus from '@/utils/mittBus' +import { computed } from 'vue' + +// 格式化日期为 YYYY-MM-DD +function formatDate(date: Date): string { + const year = date.getFullYear() + const month = String(date.getMonth() + 1).padStart(2, '0') + const day = String(date.getDate()).padStart(2, '0') + return `${year}-${month}-${day}` +} + +// 节日庆祝相关配置 +export function useCeremony() { + const settingStore = useSettingStore() + + // 节日礼花效果是否已加载 + const holidayFireworksLoaded = computed(() => settingStore.holidayFireworksLoaded) + // 节日礼花是否显示 + const isShowFireworks = computed(() => settingStore.isShowFireworks) + + // 烟花间隔引用,用于清理 + let fireworksInterval: number | null = null + let fireworksTimeout: number | null = null + + // 判断当前日期是否是节日 + const currentFestivalData = computed(() => { + const currentDate = formatDate(new Date()) + return festivalList.find((item) => item.date === currentDate) + }) + + // 节日庆祝相关配置 + const FESTIVAL_CONFIG = { + INITIAL_DELAY: 300, // 初始延迟时间,单位毫秒 + FIREWORK_INTERVAL: 1000, // 烟花效果触发间隔,单位毫秒 + TEXT_DELAY: 2000, // 文本显示延迟时间,单位毫秒 + MAX_TRIGGERS: 6 // 最大触发次数 + } as const + + // 根据节日列表显示节日祝福 + const openFestival = () => { + // 没有节日数据,不显示 + if (!currentFestivalData.value) return + // 礼花效果结束,不显示 + if (!isShowFireworks.value) return + + let triggers = 0 + + fireworksTimeout = window.setTimeout(() => { + fireworksInterval = window.setInterval(() => { + mittBus.emit('triggerFireworks', currentFestivalData.value?.image) + triggers++ + + if (triggers >= FESTIVAL_CONFIG.MAX_TRIGGERS) { + if (fireworksInterval) { + clearInterval(fireworksInterval) + fireworksInterval = null + } + settingStore.setholidayFireworksLoaded(true) + + // 主页显示节日文本 + window.setTimeout(() => { + settingStore.setShowFestivalText(true) + setFestivalDate() + }, FESTIVAL_CONFIG.TEXT_DELAY) + } + }, FESTIVAL_CONFIG.FIREWORK_INTERVAL) + }, FESTIVAL_CONFIG.INITIAL_DELAY) + } + + // 清理函数 + const cleanup = () => { + if (fireworksInterval) { + clearInterval(fireworksInterval) + fireworksInterval = null + } + if (fireworksTimeout) { + clearTimeout(fireworksTimeout) + fireworksTimeout = null + } + settingStore.setShowFestivalText(false) + setFestivalDate() + } + + // 设置节日日期 + const setFestivalDate = () => { + settingStore.setFestivalDate(currentFestivalData.value?.date || '') + } + + return { + openFestival, + cleanup, + holidayFireworksLoaded, + currentFestivalData, + isShowFireworks + } +} + diff --git a/mobile-h5/src/composables/useCommon.ts b/mobile-h5/src/composables/useCommon.ts new file mode 100644 index 000000000..f6108ae14 --- /dev/null +++ b/mobile-h5/src/composables/useCommon.ts @@ -0,0 +1,12 @@ +// 通用组合函数(移动端简化版本) +export function useCommon() { + const refresh = () => { + // 刷新页面逻辑 + window.location.reload() + } + + return { + refresh + } +} + diff --git a/mobile-h5/src/composables/useTheme.ts b/mobile-h5/src/composables/useTheme.ts new file mode 100644 index 000000000..0a49b802c --- /dev/null +++ b/mobile-h5/src/composables/useTheme.ts @@ -0,0 +1,12 @@ +// 主题组合函数(移动端简化版本) +export function useTheme() { + const switchThemeStyles = (theme: string) => { + // 切换主题逻辑 + document.documentElement.setAttribute('data-theme', theme) + } + + return { + switchThemeStyles + } +} + diff --git a/mobile-h5/src/config/core/ceremony.ts b/mobile-h5/src/config/core/ceremony.ts new file mode 100644 index 000000000..80d899899 --- /dev/null +++ b/mobile-h5/src/config/core/ceremony.ts @@ -0,0 +1,18 @@ +// 节日礼花、文字滚动配置 + +// 节日列表配置 +export const festivalList = [ + { + date: '2025-01-01', + name: '元旦', + image: '', + scrollText: '新年快乐!祝您在2025年万事如意,事业腾飞,阖家幸福,好运连连!' + }, + { + date: '2024-12-25', + name: '圣诞节', + image: '', + scrollText: 'Merry Christmas!祝您圣诞快乐,愿节日的欢乐与祝福如雪花般纷至沓来!' + } +] + diff --git a/mobile-h5/src/config/index.ts b/mobile-h5/src/config/index.ts new file mode 100644 index 000000000..80cbd727c --- /dev/null +++ b/mobile-h5/src/config/index.ts @@ -0,0 +1,78 @@ +import { MenuThemeEnum } from '@/enums/appEnum' +import { MenuThemeType } from '@/types/store' + +// 系统配置 +const config = { + // Element Plus 主题 (保持兼容性,虽然移动端可能不使用) + elementPlusTheme: { + primary: '#52B788' + }, + // 菜单主题列表 + themeList: [ + { + theme: MenuThemeEnum.DESIGN, + background: '#FFFFFF', + systemNameColor: 'var(--art-text-gray-800)', + iconColor: '#6B6B6B', + textColor: '#29343D', + textActiveColor: '#52B788', + iconActiveColor: '#333333', + tabBarBackground: '#FAFBFC', + systemBackground: '#FAFBFC', + leftLineColor: '#EDEEF0', + rightLineColor: '#EDEEF0' + }, + { + theme: MenuThemeEnum.DARK, + background: '#191A23', + systemNameColor: '#BABBBD', + iconColor: '#BABBBD', + textColor: '#BABBBD', + textActiveColor: '#FFFFFF', + iconActiveColor: '#FFFFFF', + tabBarBackground: '#FFFFFF', + systemBackground: '#F8F8F8', + leftLineColor: '#3F4257', + rightLineColor: '#EDEEF0' + }, + { + theme: MenuThemeEnum.LIGHT, + background: '#ffffff', + systemNameColor: '#68758E', + iconColor: '#6B6B6B', + textColor: '#29343D', + textActiveColor: '#52B788', + iconActiveColor: '#333333', + tabBarBackground: '#FFFFFF', + systemBackground: '#F8F8F8', + leftLineColor: '#EDEEF0', + rightLineColor: '#EDEEF0' + } + ] as MenuThemeType[], + // 暗黑菜单样式 + darkMenuStyles: [ + { + theme: MenuThemeEnum.DARK, + background: '#161618', + systemNameColor: '#DDDDDD', + iconColor: '#BABBBD', + textColor: 'rgba(255, 255, 255, 0.7)', + textActiveColor: '', + iconActiveColor: '#FFFFFF', + tabBarBackground: '#FFFFFF', + systemBackground: '#F8F8F8', + leftLineColor: '#3F4257', + rightLineColor: '#EDEEF0' + } + ] as MenuThemeType[], + // 系统其他项默认配置 + systemSetting: { + defaultMenuWidth: 252, // 菜单宽度 + defaultCustomRadius: '0.75', // 自定义圆角 + defaultTabStyle: 'tab-default' // 标签样式 + } +} + +// 冻结对象防止运行时修改 +export default Object.freeze(config) + diff --git a/mobile-h5/src/enums/appEnum.ts b/mobile-h5/src/enums/appEnum.ts new file mode 100644 index 000000000..04d537a19 --- /dev/null +++ b/mobile-h5/src/enums/appEnum.ts @@ -0,0 +1,36 @@ +// 系统级别枚举 + +// 菜单类型 +export enum MenuTypeEnum { + LEFT = 'left', + TOP = 'top', + TOP_LEFT = 'top-left', + DUAL_MENU = 'dual-menu' +} + +// App theme enum +export enum SystemThemeEnum { + DARK = 'dark', + LIGHT = 'light', + AUTO = 'auto' +} + +// Menu theme enum +export enum MenuThemeEnum { + DARK = 'dark', + LIGHT = 'light', + DESIGN = 'design' +} + +// Language +export enum LanguageEnum { + ZH = 'zh', + EN = 'en' +} + +// Container width +export enum ContainerWidthEnum { + FULL = '100%', + BOXED = '1200px' +} + diff --git a/mobile-h5/src/env.d.ts b/mobile-h5/src/env.d.ts new file mode 100644 index 000000000..329760e6b --- /dev/null +++ b/mobile-h5/src/env.d.ts @@ -0,0 +1,17 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + const component: DefineComponent<{}, {}, any> + export default component +} + +interface ImportMetaEnv { + readonly VITE_API_BASE_URL?: string + readonly VITE_API_URL?: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} + diff --git a/mobile-h5/src/language/index.ts b/mobile-h5/src/language/index.ts new file mode 100644 index 000000000..dc3953e05 --- /dev/null +++ b/mobile-h5/src/language/index.ts @@ -0,0 +1,10 @@ +/** + * 简单的国际化函数 + * 目前返回原始字符串,后续可以扩展为完整的 i18n 实现 + */ +export const $t = (key: string): string => { + // 简单的实现,直接返回 key + // 后续可以集成 vue-i18n 或其他 i18n 库 + return key +} + diff --git a/mobile-h5/src/main.ts b/mobile-h5/src/main.ts new file mode 100644 index 000000000..ae1462a5f --- /dev/null +++ b/mobile-h5/src/main.ts @@ -0,0 +1,28 @@ +import { createApp } from 'vue' +import App from './App.vue' +import { initStore } from './store' +import { initRouter } from './router' +import { initState } from './utils/storage' +import { Toast, Dialog, Notify } from 'vant' +import 'vant/lib/index.css' +import './assets/styles/vant-theme.scss' +import './assets/styles/index.scss' + +const app = createApp(App) + +// 初始化 Store +initStore(app) + +// 初始化 Router +initRouter(app) + +// 初始化本地存储状态(必须在 Store 和 Router 初始化之后) +initState() + +// 注册 Vant 函数式组件 +app.use(Toast) +app.use(Dialog) +app.use(Notify) + +app.mount('#app') + diff --git a/mobile-h5/src/mock/upgradeLog.ts b/mobile-h5/src/mock/upgradeLog.ts new file mode 100644 index 000000000..475db6404 --- /dev/null +++ b/mobile-h5/src/mock/upgradeLog.ts @@ -0,0 +1,17 @@ +// 升级日志(移动端简化版本) +import { ref } from 'vue' + +export interface UpgradeLog { + title: string + content: string + requireReLogin: boolean +} + +export const upgradeLogList = ref([ + { + title: '系统升级', + content: '

系统已升级到最新版本

', + requireReLogin: false + } +]) + diff --git a/mobile-h5/src/plugins/echarts.ts b/mobile-h5/src/plugins/echarts.ts new file mode 100644 index 000000000..136cc372c --- /dev/null +++ b/mobile-h5/src/plugins/echarts.ts @@ -0,0 +1,23 @@ +// ECharts 插件(移动端简化版本,如果不需要图表功能可以返回空对象) +// 注意:如果项目不需要 echarts,可以安装:npm install echarts +// 或者移除所有使用 echarts 的代码 + +let echartsInstance: any = null + +// 动态导入 echarts(如果已安装) +try { + // eslint-disable-next-line @typescript-eslint/no-var-requires + echartsInstance = require('echarts') +} catch { + // echarts 未安装,返回一个空对象 + echartsInstance = { + init: () => ({ + setOption: () => {}, + resize: () => {}, + dispose: () => {} + }) + } +} + +export default echartsInstance + diff --git a/mobile-h5/src/router/index.ts b/mobile-h5/src/router/index.ts new file mode 100644 index 000000000..e05656206 --- /dev/null +++ b/mobile-h5/src/router/index.ts @@ -0,0 +1,192 @@ +import type { App } from 'vue' +import { createRouter, createWebHashHistory, RouteRecordRaw, RouteLocationNormalized } from 'vue-router' +import { useUserStore } from '@/store/modules/user' +import { formatMenuTitle } from '@/utils/menu' + +/** 首页路径常量 */ +export const HOME_PAGE = '/dashboard' + +const routes: RouteRecordRaw[] = [ + { + path: '/', + redirect: HOME_PAGE + }, + { + path: '/login', + name: 'Login', + component: () => import('@/views/login/index.vue'), + meta: { title: '登录', noAuth: true } + }, + { + path: '/dashboard', + name: 'Dashboard', + component: () => import('@/views/dashboard/index.vue'), + meta: { title: '仪表盘' } + }, + { + path: '/plans', + name: 'Plans', + component: () => import('@/views/plans/index.vue'), + meta: { title: '计划管理' } + }, + { + path: '/plans/detail', + name: 'PlanDetail', + component: () => import('@/views/plans/detail.vue'), + meta: { title: '计划详情' } + }, + { + path: '/plans/detail/:id', + name: 'PlanDetailEdit', + component: () => import('@/views/plans/detail.vue'), + meta: { title: '计划详情' } + }, + { + path: '/inventory', + name: 'Inventory', + component: () => import('@/views/inventory/index.vue'), + meta: { title: '出入库管理' } + }, + { + path: '/inventory/detail', + name: 'InventoryDetail', + component: () => import('@/views/inventory/detail.vue'), + meta: { title: '出入库详情' } + }, + { + path: '/inventory/detail/:id', + name: 'InventoryDetailEdit', + component: () => import('@/views/inventory/detail.vue'), + meta: { title: '出入库详情' } + }, + { + path: '/inventory/fill/:id', + name: 'InventoryFill', + component: () => import('@/views/inventory/fill.vue'), + meta: { title: '山峡出入库信息', noAuth: true } + }, + { + path: '/inventory/plan/:id', + name: 'InventoryPlanThirdParty', + component: () => import('@/views/inventory/plan.vue'), + meta: { title: '计划出入库填写', noAuth: true } + }, + { + path: '/weighing', + name: 'Weighing', + component: () => import('@/views/weighing/index.vue'), + meta: { title: '地磅单管理' } + }, + { + path: '/weighing/detail', + name: 'WeighingDetail', + component: () => import('@/views/weighing/detail.vue'), + meta: { title: '地磅单详情' } + }, + { + path: '/weighing/detail/:id', + name: 'WeighingDetailEdit', + component: () => import('@/views/weighing/detail.vue'), + meta: { title: '地磅单详情' } + }, + { + path: '/master-data', + name: 'MasterData', + component: () => import('@/views/master-data/index.vue'), + meta: { title: '基础资料' } + }, + { + path: '/master-data/company/detail', + name: 'CompanyDetail', + component: () => import('@/views/master-data/company-detail.vue'), + meta: { title: '公司详情' } + }, + { + path: '/master-data/company/detail/:id', + name: 'CompanyDetailEdit', + component: () => import('@/views/master-data/company-detail.vue'), + meta: { title: '公司详情' } + }, + { + path: '/master-data/cargo/detail', + name: 'CargoDetail', + component: () => import('@/views/master-data/cargo-detail.vue'), + meta: { title: '货物详情' } + }, + { + path: '/master-data/cargo/detail/:id', + name: 'CargoDetailEdit', + component: () => import('@/views/master-data/cargo-detail.vue'), + meta: { title: '货物详情' } + }, + { + path: '/master-data/vehicle/detail', + name: 'VehicleDetail', + component: () => import('@/views/master-data/vehicle-detail.vue'), + meta: { title: '车牌号详情' } + }, + { + path: '/master-data/vehicle/detail/:id', + name: 'VehicleDetailEdit', + component: () => import('@/views/master-data/vehicle-detail.vue'), + meta: { title: '车牌号详情' } + } +] + +export const router = createRouter({ + history: createWebHashHistory(), + routes +}) + +/** + * 设置页面标题,根据路由元信息拼接标题 + * @param to 当前路由对象 + */ +export const setPageTitle = (to: RouteLocationNormalized): void => { + const { title } = to.meta + if (title) { + setTimeout(() => { + document.title = `${formatMenuTitle(String(title))} - 地磅系统` + }, 150) + } +} + +// 路由守卫 +router.beforeEach((to, _from, next) => { + const userStore = useUserStore() + + // 设置页面标题 + setPageTitle(to) + + // 如果访问登录页且已登录,跳转到首页 + if (to.path === '/login' && userStore.isLogin) { + next(HOME_PAGE) + return + } + + // 检查登录状态 + // 如果有 token 但 isLogin 为 false,尝试从 sessionStorage 恢复 + if (!to.meta.noAuth) { + const hasToken = userStore.accessToken || sessionStorage.getItem('accessToken') + if (!userStore.isLogin && hasToken) { + // 如果有 token,恢复登录状态 + userStore.setLoginStatus(true) + // 从 sessionStorage 恢复 token + if (!userStore.accessToken && hasToken) { + userStore.accessToken = hasToken as string + } + } + + if (!userStore.isLogin && !hasToken) { + next('/login') + return + } + } + + next() +}) + +export function initRouter(app: App) { + app.use(router) +} + diff --git a/mobile-h5/src/store/index.ts b/mobile-h5/src/store/index.ts new file mode 100755 index 000000000..51be9b8c5 --- /dev/null +++ b/mobile-h5/src/store/index.ts @@ -0,0 +1,8 @@ +import type { App } from 'vue' +import { createPinia } from 'pinia' + +export const store = createPinia() + +export function initStore(app: App) { + app.use(store) +} diff --git a/mobile-h5/src/store/modules/menu.ts b/mobile-h5/src/store/modules/menu.ts new file mode 100755 index 000000000..eec2f5cd2 --- /dev/null +++ b/mobile-h5/src/store/modules/menu.ts @@ -0,0 +1,28 @@ +import { defineStore } from 'pinia' +import { MenuListType } from '@/types/menu' + +interface MenuState { + menuList: MenuListType[] + menuWidth: string +} + +export const useMenuStore = defineStore({ + id: 'menuStore', + state: (): MenuState => ({ + menuList: [], + menuWidth: '' + }), + getters: { + getMenuList(): MenuListType[] { + return this.menuList + } + }, + actions: { + setMenuList(list: []) { + this.menuList = list + }, + setMenuWidth(width: string) { + this.menuWidth = width + } + } +}) diff --git a/mobile-h5/src/store/modules/setting.ts b/mobile-h5/src/store/modules/setting.ts new file mode 100755 index 000000000..2b4187643 --- /dev/null +++ b/mobile-h5/src/store/modules/setting.ts @@ -0,0 +1,267 @@ +import { defineStore } from 'pinia' +import { MenuThemeType } from '@/types/store' +import AppConfig from '@/config' +import { SystemThemeEnum, MenuThemeEnum, MenuTypeEnum, ContainerWidthEnum } from '@/enums/appEnum' +import { colourBlend, handleElementThemeColor } from '@/utils/colors' +import { getSysStorage } from '@/utils/storage' +import { useCeremony } from '@/composables/useCeremony' + +const { defaultMenuWidth, defaultCustomRadius, defaultTabStyle } = AppConfig.systemSetting + +export interface SettingState { + menuType: MenuTypeEnum // 菜单类型 + menuOpenWidth: number // 菜单展开宽度 + systemThemeType: SystemThemeEnum // 全局主题类型 light dark + systemThemeMode: SystemThemeEnum // 全局主题模式 light dark auto + menuThemeType: MenuThemeEnum // 菜单主题类型 + systemThemeColor: string // 系统主题颜色 + boxBorderMode: boolean // 盒子模式 border | shadow + uniqueOpened: boolean // 是否开启手风琴模式 + showMenuButton: boolean // 是否显示菜单展开按钮 + showRefreshButton: boolean // 是否显示页面刷新按钮 + showCrumbs: boolean // 是否显示全局面包屑 + autoClose: boolean // 设置后是否自动关闭窗口 + showWorkTab: boolean // 是否显示多标签 + showLanguage: boolean // 是否显示多语言选择 + showNprogress: boolean // 是否显示顶部进度条 + colorWeak: boolean // 是否显示顶部进度条 + showSettingGuide: boolean // 是否显示设置引导 + pageTransition: string // 页面切换动画 + tabStyle: string // 标签页风格 + menuOpen: boolean // 菜单是否展开 + refresh: boolean + watermarkVisible: boolean // 水印是否显示 + customRadius: string // 自定义圆角 + holidayFireworksLoaded: boolean // 是否加载完礼花 + showFestivalText: boolean // 是否显示节日文本 + festivalDate: string // 节日日期 + dualMenuShowText: boolean // 双列菜单是否显示文本 + containerWidth: ContainerWidthEnum // 容器宽度 +} + +export const useSettingStore = defineStore({ + id: 'settingStore', + state: (): SettingState => ({ + menuType: MenuTypeEnum.TOP, + menuOpenWidth: defaultMenuWidth, + systemThemeType: SystemThemeEnum.LIGHT, + systemThemeMode: SystemThemeEnum.LIGHT, + menuThemeType: MenuThemeEnum.DESIGN, + boxBorderMode: true, + uniqueOpened: true, + systemThemeColor: AppConfig.elementPlusTheme.primary, + showMenuButton: true, + showRefreshButton: true, + showCrumbs: true, + autoClose: false, + showWorkTab: true, + showLanguage: true, + showNprogress: true, + colorWeak: false, + showSettingGuide: true, + pageTransition: 'slide-right', + tabStyle: defaultTabStyle, + menuOpen: true, + refresh: false, + watermarkVisible: false, + customRadius: defaultCustomRadius, + holidayFireworksLoaded: false, + showFestivalText: false, + festivalDate: '', + dualMenuShowText: false, + containerWidth: ContainerWidthEnum.FULL + }), + getters: { + getMenuTheme(): MenuThemeType { + const list = AppConfig.themeList.filter((item) => item.theme === this.menuThemeType) + if (this.isDark) { + return AppConfig.darkMenuStyles[0] + } else { + return list[0] + } + }, + // 是否为暗黑模式 + isDark(): boolean { + return this.systemThemeType === SystemThemeEnum.DARK + }, + // 获取菜单展开宽度 + getMenuOpenWidth(): string { + return this.menuOpenWidth + 'px' || defaultMenuWidth + 'px' + }, + // 获取自定义圆角 + getCustomRadius(): string { + return this.customRadius + 'rem' || defaultCustomRadius + 'rem' + }, + // 节日礼花否显示 + isShowFireworks(): boolean { + return this.festivalDate === useCeremony().currentFestivalData.value?.date ? false : true + } + }, + actions: { + // 初始化state + initState() { + let sys = getSysStorage() + + if (sys) { + sys = JSON.parse(sys) + const { setting } = sys.user + this.menuType = setting.menuType || MenuTypeEnum.TOP + this.menuOpenWidth = Number(setting.menuOpenWidth) || defaultMenuWidth + this.systemThemeType = setting.systemThemeType || SystemThemeEnum.LIGHT + this.systemThemeMode = setting.systemThemeMode || SystemThemeEnum.LIGHT + this.menuThemeType = setting.menuThemeType || MenuThemeEnum.DESIGN + this.containerWidth = setting.containerWidth || ContainerWidthEnum.FULL + this.systemThemeColor = setting.systemThemeColor || AppConfig.elementPlusTheme.primary + this.boxBorderMode = setting.boxBorderMode + this.uniqueOpened = setting.uniqueOpened + this.showMenuButton = setting.showMenuButton + this.showRefreshButton = setting.showRefreshButton + this.showCrumbs = setting.showCrumbs + this.autoClose = setting.autoClose + this.showWorkTab = setting.showWorkTab + this.showLanguage = setting.showLanguage + this.showNprogress = setting.showNprogress + this.colorWeak = setting.colorWeak + this.showSettingGuide = setting.showSettingGuide + this.pageTransition = setting.pageTransition + this.tabStyle = setting.tabStyle || defaultTabStyle + this.menuOpen = setting.menuOpen + this.watermarkVisible = setting.watermarkVisible + this.customRadius = setting.customRadius || defaultCustomRadius + this.holidayFireworksLoaded = setting.holidayFireworksLoaded + this.showFestivalText = setting.showFestivalText + this.festivalDate = setting.festivalDate + this.dualMenuShowText = setting.dualMenuShowText + this.setCustomRadius(this.customRadius) + setElementThemeColor(setting.systemThemeColor) + } else { + this.setCustomRadius(this.customRadius) + setElementThemeColor(AppConfig.elementPlusTheme.primary) + } + }, + switchMenuLayouts(type: MenuTypeEnum) { + this.menuType = type + }, + setMenuOpenWidth(width: number) { + this.menuOpenWidth = width + }, + setGlopTheme(theme: SystemThemeEnum, themeMode: SystemThemeEnum) { + this.systemThemeType = theme + this.systemThemeMode = themeMode + }, + switchMenuStyles(theme: MenuThemeEnum) { + this.menuThemeType = theme + }, + setElementTheme(theme: string) { + this.systemThemeColor = theme + setElementThemeColor(theme) + }, + // 设置盒子模式 + setBorderMode() { + this.boxBorderMode = !this.boxBorderMode + }, + // 设置容器宽度 + setContainerWidth(width: ContainerWidthEnum) { + this.containerWidth = width + }, + // 设置菜单是否为手风琴模式 + setUniqueOpened() { + this.uniqueOpened = !this.uniqueOpened + }, + // 显示侧边栏折叠按钮 + setButton() { + this.showMenuButton = !this.showMenuButton + }, + // 是否自动关闭个性化设置 + setAutoClose() { + this.autoClose = !this.autoClose + }, + // 是否显示页面刷新按钮 + setShowRefreshButton() { + this.showRefreshButton = !this.showRefreshButton + }, + // 是否显示面包屑导航 + setCrumbs() { + this.showCrumbs = !this.showCrumbs + }, + // 是否显示多标签 + setWorkTab(show: boolean) { + this.showWorkTab = show + }, + // 是否显示多语言选择 + setLanguage() { + this.showLanguage = !this.showLanguage + }, + // 是否显示顶部进度条 + setNprogress() { + this.showNprogress = !this.showNprogress + }, + // 色弱模式 + setColorWeak() { + this.colorWeak = !this.colorWeak + }, + // 隐藏设置引导 + hideSettingGuide() { + this.showSettingGuide = false + }, + // 显示设置引导 + openSettingGuide() { + this.showSettingGuide = true + }, + // 设置页面切换动画 + setPageTransition(transition: string) { + this.pageTransition = transition + }, + // 设置标签页风格 + setTabStyle(style: string) { + this.tabStyle = style + }, + // 设置菜单是否展开 + setMenuOpen(open: boolean) { + this.menuOpen = open + }, + // 刷新当前页 + reload() { + this.refresh = !this.refresh + }, + // 设置水印是否显示 + setWatermarkVisible(visible: boolean) { + this.watermarkVisible = visible + }, + // 设置自定义圆角 + setCustomRadius(radius: string) { + this.customRadius = radius + document.documentElement.style.setProperty('--custom-radius', `${radius}rem`) + }, + // 设置是否加载完礼花 + setholidayFireworksLoaded(isLoad: boolean) { + this.holidayFireworksLoaded = isLoad + }, + // 设置是否显示节日文本 + setShowFestivalText(show: boolean) { + this.showFestivalText = show + }, + // 设置节日日期 + setFestivalDate(date: string) { + this.festivalDate = date + }, + // 设置双列菜单是否显示文本 + setDualMenuShowText(show: boolean) { + this.dualMenuShowText = show + } + } +}) + +function setElementThemeColor(color: string) { + const mixColor = '#ffffff' + const elStyle = document.documentElement.style + + elStyle.setProperty('--el-color-primary', color) + handleElementThemeColor(color, useSettingStore().isDark) + + // 生成更淡一点的颜色 + for (let i = 1; i < 16; i++) { + const itemColor = colourBlend(color, mixColor, i / 16) + elStyle.setProperty(`--el-color-primary-custom-${i}`, itemColor) + } +} diff --git a/mobile-h5/src/store/modules/user.ts b/mobile-h5/src/store/modules/user.ts new file mode 100755 index 000000000..338e3ccb2 --- /dev/null +++ b/mobile-h5/src/store/modules/user.ts @@ -0,0 +1,143 @@ +import { defineStore } from 'pinia' +import { LanguageEnum } from '@/enums/appEnum' +import { router, setPageTitle } from '@/router' +import { UserInfo } from '@/types/store' +import { useSettingStore } from './setting' +import { useWorktabStore } from './worktab' +import { getSysStorage } from '@/utils/storage' +import { MenuListType } from '@/types/menu' + +interface UserState { + language: LanguageEnum // 语言 + isLogin: boolean // 是否登录 + isLock: boolean // 是否锁屏 + lockPassword: string // 锁屏密码 + info: Partial // 用户信息 + searchHistory: MenuListType[] // 搜索历史 + accessToken: string // 添加访问令牌 + refreshToken: string // 添加刷新令牌 +} + +export const useUserStore = defineStore({ + id: 'userStore', + state: (): UserState => ({ + language: LanguageEnum.ZH, + isLogin: false, + isLock: false, + lockPassword: '', + info: {}, + searchHistory: [], + accessToken: '', + refreshToken: '' + }), + getters: { + getUserInfo(): Partial { + return this.info + }, + getSettingState() { + return useSettingStore().$state + }, + getWorktabState() { + return useWorktabStore().$state + } + }, + actions: { + initState() { + let sys = getSysStorage() + + if (sys) { + sys = JSON.parse(sys) + const { info, isLogin, language, searchHistory, isLock, lockPassword, refreshToken } = + sys.user + + this.info = info || {} + this.isLogin = isLogin || false + this.isLock = isLock || false + this.language = language || LanguageEnum.ZH + this.searchHistory = searchHistory || [] + this.lockPassword = lockPassword || '' + this.refreshToken = refreshToken || '' + this.accessToken = sessionStorage.getItem('accessToken') || '' + } + }, + // 用户数据持久化存储 + saveUserData() { + saveStoreStorage({ + user: { + info: this.info, + isLogin: this.isLogin, + language: this.language, + isLock: this.isLock, + lockPassword: this.lockPassword, + searchHistory: this.searchHistory, + refreshToken: this.refreshToken, + worktab: this.getWorktabState, + setting: this.getSettingState + } + }) + }, + setUserInfo(info: UserInfo) { + this.info = info + }, + setLoginStatus(isLogin: boolean) { + this.isLogin = isLogin + // 登录状态改变时立即保存 + this.saveUserData() + }, + setLanguage(lang: LanguageEnum) { + setPageTitle(router.currentRoute.value) + this.language = lang + }, + setSearchHistory(list: Array) { + this.searchHistory = list + }, + setLockStatus(isLock: boolean) { + this.isLock = isLock + }, + setLockPassword(password: string) { + this.lockPassword = password + }, + setToken(accessToken: string, refreshToken?: string) { + this.accessToken = accessToken + if (refreshToken) { + this.refreshToken = refreshToken + } + sessionStorage.setItem('accessToken', accessToken) + this.saveUserData() + }, + logOut() { + setTimeout(() => { + this.info = {} + this.isLogin = false + this.isLock = false + this.lockPassword = '' + this.accessToken = '' + this.refreshToken = '' + sessionStorage.removeItem('accessToken') + useWorktabStore().opened = [] + this.saveUserData() + sessionStorage.removeItem('iframeRoutes') + router.push('/login') + }, 300) + } + } +}) + +function initVersion(version: string) { + const vs = localStorage.getItem('version') + if (!vs) { + localStorage.setItem('version', version) + } +} + +// 数据持久化存储 +function saveStoreStorage(newData: T) { + const version = import.meta.env.VITE_VERSION + initVersion(version) + const vs = localStorage.getItem('version') || version + const storedData = JSON.parse(localStorage.getItem(`sys-v${vs}`) || '{}') + + // 合并新数据与现有数据 + const mergedData = { ...storedData, ...newData } + localStorage.setItem(`sys-v${vs}`, JSON.stringify(mergedData)) +} diff --git a/mobile-h5/src/store/modules/worktab.ts b/mobile-h5/src/store/modules/worktab.ts new file mode 100755 index 000000000..082edee3b --- /dev/null +++ b/mobile-h5/src/store/modules/worktab.ts @@ -0,0 +1,190 @@ +import { defineStore } from 'pinia' +import { WorkTabType } from '@/types/store' +import { HOME_PAGE } from '@/router/index' +import { router } from '@/router' +import { getSysStorage } from '@/utils/storage' + +interface WorktabState { + opened: WorkTabType[] + current: Partial + keepAliveExclude: string[] +} + +export const useWorktabStore = defineStore({ + id: 'worktabStore', + state: (): WorktabState => ({ + current: {}, + opened: [], + keepAliveExclude: [] + }), + actions: { + /** + * 初始化状态,从系统存储中加载工作台配置 + */ + initState() { + const sysStorage = getSysStorage() + if (sysStorage) { + const sys = JSON.parse(sysStorage) + const { worktab } = sys.user + this.current = worktab.current || {} + this.opened = worktab.opened || [] + this.checkFirstHomePage() + } + }, + + /** + * 打开或激活一个选项卡 + * @param tab 目标选项卡信息 + */ + openTab(tab: WorkTabType): void { + this.removeKeepAliveExclude(tab.name as string) + + const index = this.opened.findIndex((item) => item.path === tab.path) + + if (index === -1) { + this.opened.push({ ...tab }) + } else { + const existingTab = this.opened[index] + if (!this.areQueriesEqual(existingTab.query, tab.query)) { + this.opened[index] = { + ...existingTab, + query: tab.query, + // 可选:支持更多属性更新 + title: tab.title || existingTab.title + } + } + } + + this.current = this.opened[index === -1 ? this.opened.length - 1 : index] + }, + // 辅助函数 + areQueriesEqual(query1: any, query2: any): boolean { + return JSON.stringify(query1) === JSON.stringify(query2) + }, + /** + * 关闭指定的选项卡,并处理激活状态和路由跳转 + * @param path 要关闭的路由路径 + */ + removeTab(path: string) { + const noCurrentTab = this.opened.find((tab) => tab.path === path) + const index = this.opened.findIndex((tab) => tab.path === path) + if (index === -1) return + + this.opened.splice(index, 1) + + // 若关闭后无选项卡,且关闭的不是首页,则跳转首页 + if (!this.opened.length && path !== HOME_PAGE) { + router.push(HOME_PAGE) + return + } + + // 若关闭的是当前激活标签,则标记其为缓存排除,并激活相邻标签 + if (this.current.path === path) { + if (this.current.name) { + this.addKeepAliveExclude(this.current as WorkTabType) + } + const newIndex = index >= this.opened.length ? this.opened.length - 1 : index + this.current = this.opened[newIndex] + router.push(this.current.path as string) + } else { + if (noCurrentTab?.name) { + this.addKeepAliveExclude(noCurrentTab) + } + } + }, + + /** + * 关闭当前标签左侧(不包括首页)的所有选项卡 + * @param path 当前选项卡的路由路径 + */ + removeLeft(path: string) { + const index = this.opened.findIndex((tab) => tab.path === path) + if (index > 1) { + // 保留首页和当前标签 + const tabsToRemove = this.opened.slice(1, index) + this.markTabsToRemove(tabsToRemove) + this.opened.splice(1, index - 1) + } + }, + + /** + * 关闭当前标签右侧的所有选项卡 + * @param path 当前选项卡的路由路径 + */ + removeRight(path: string) { + const index = this.opened.findIndex((tab) => tab.path === path) + if (index !== -1 && index < this.opened.length - 1) { + const tabsToRemove = this.opened.slice(index + 1) + this.markTabsToRemove(tabsToRemove) + this.opened.splice(index + 1) + } + }, + + /** + * 关闭除当前标签和首页外的所有选项卡 + * @param path 当前选项卡的路由路径 + */ + removeOthers(path: string) { + const tabsToRemove = this.opened.filter((tab) => tab.path !== path && tab.path !== HOME_PAGE) + this.markTabsToRemove(tabsToRemove) + this.opened = this.opened.filter((tab) => tab.path === path || tab.path === HOME_PAGE) + }, + + /** + * 关闭所有选项卡(当传入的 path 不是首页时关闭全部;首页时只保留首页) + * @param path 当前选项卡的路由路径 + */ + removeAll(path: string) { + if (path !== HOME_PAGE) { + this.markTabsToRemove(this.opened) + this.current = {} + this.opened = [] + router.push(HOME_PAGE) + } else { + const tabsToRemove = this.opened.filter((tab) => tab.path !== HOME_PAGE) + this.markTabsToRemove(tabsToRemove) + this.opened = this.opened.filter((tab) => tab.path === HOME_PAGE) + if (this.opened.length === 0) router.push(HOME_PAGE) + } + }, + + /** + * 检查第一个选项卡是否为首页,否则清空所有标签并跳转首页 + */ + checkFirstHomePage() { + if (this.opened.length && this.opened[0].path !== HOME_PAGE) { + this.removeAll(HOME_PAGE) + } + }, + + /** + * 将指定选项卡添加到 keepAlive 排除列表中,只有当该选项卡的 keepAlive 为 true 时才进行添加 + * @param tab 选项卡对象 + */ + addKeepAliveExclude(tab: WorkTabType) { + if (tab.keepAlive && tab.name && !this.keepAliveExclude.includes(tab.name)) { + this.keepAliveExclude.push(tab.name) + } + }, + + /** + * 从 keepAlive 排除列表中移除指定组件名称 + * @param name 路由组件名称 + */ + removeKeepAliveExclude(name: string) { + this.keepAliveExclude = this.keepAliveExclude.filter((item) => item !== name) + }, + + /** + * 将传入的一组选项卡的组件名称标记为排除缓存 + * @param tabs 需要标记的选项卡数组 + */ + markTabsToRemove(tabs: WorkTabType[]) { + tabs.forEach((tab) => { + if (tab.name) { + this.addKeepAliveExclude(tab) + } + }) + } + } +}) diff --git a/mobile-h5/src/types/axios.ts b/mobile-h5/src/types/axios.ts new file mode 100644 index 000000000..553edd691 --- /dev/null +++ b/mobile-h5/src/types/axios.ts @@ -0,0 +1,15 @@ +// API 响应基础类型 +export interface BaseResult { + code: number + message: string + data: T +} + +// 分页结果类型 +export interface PaginationResult { + list: T[] + total: number + page: number + size: number +} + diff --git a/mobile-h5/src/types/menu.ts b/mobile-h5/src/types/menu.ts new file mode 100644 index 000000000..d5497906b --- /dev/null +++ b/mobile-h5/src/types/menu.ts @@ -0,0 +1,24 @@ +// 菜单项类型 +export interface MenuItem { + id?: number | string // 添加 id 属性 + path: string + name: string + component?: string | (() => Promise) + meta?: { + title?: string + icon?: string + isHide?: boolean + isKeepAlive?: boolean + isAffix?: boolean + isLink?: string // 外部链接 + link?: string // 兼容 link 属性 + isIframe?: boolean + roles?: string[] + auth?: string[] + } + children?: MenuItem[] +} + +// 菜单列表类型 +export type MenuListType = MenuItem + diff --git a/mobile-h5/src/types/store.ts b/mobile-h5/src/types/store.ts new file mode 100644 index 000000000..76e4bd584 --- /dev/null +++ b/mobile-h5/src/types/store.ts @@ -0,0 +1,61 @@ +import { MenuThemeEnum, SystemThemeEnum } from '@/enums/appEnum' + +// 用户信息 +export interface UserInfo { + id: number + name?: string // 改为可选,与 API 返回类型一致 + username: string + avatar?: string // 改为可选,与 API 返回类型一致 + email?: string // 改为可选,与 API 返回类型一致 +} + +// 系统主题样式(light | dark) +export interface SystemThemeType { + className: string // className +} + +// 定义包含多个主题的类型 +export type SystemThemeTypes = { + [key in Exclude]: SystemThemeType +} + +// 菜单主题样式 +export interface MenuThemeType { + theme: MenuThemeEnum // 主题名称 + background: string // 背景色 + systemNameColor: string // 系统标题颜色 + textColor: string // 文字颜色 + textActiveColor: string // 文字选中颜色 + iconColor: string // 图标颜色 + iconActiveColor: string // 图标选中颜色 + tabBarBackground: string // 顶栏背景色 + systemBackground: string // 系统背景色 + leftLineColor: string // 左侧线条颜色 + rightLineColor: string // 右侧线条颜色 + img?: string // 图片 +} + +// 设置中心 +export interface Setting { + theme: string // 主题 + uniqueOpened: boolean // 是否开启手风琴模式 + menuButton: boolean // 是否显示菜单展开按钮 + showRefreshButton: boolean // 是否显示页面刷新按钮 + showCrumbs: boolean // 是否显示全局面包屑 + autoClose: boolean // 设置后是否自动关闭窗口 + showWorkTab: boolean // 是否显示多标签 + showLanguage: boolean // 是否显示多语言选择 + showNprogress: boolean // 是否显示顶部进度条 + themeModel: string // 主题模式 +} + +// 多标签 +export interface WorkTabType { + title: string + path: string + name: string + keepAlive: boolean + params?: object + query?: object +} + diff --git a/mobile-h5/src/utils/color.ts b/mobile-h5/src/utils/color.ts new file mode 100644 index 000000000..7fbf8f017 --- /dev/null +++ b/mobile-h5/src/utils/color.ts @@ -0,0 +1,78 @@ +import { showToast } from 'vant' + +/** + * hex颜色转rgb颜色 + * @param str 颜色值字符串 + * @returns 返回处理后的颜色值 + */ +export function hexToRgb(str: any) { + let hexs: any = '' + const reg = /^#?[0-9A-Fa-f]{6}$/ + if (!reg.test(str)) { + showToast({ type: 'fail', message: '输入错误的hex' }) + return + } + str = str.replace('#', '') + + hexs = str.match(/../g) + + for (let i = 0; i < 3; i++) hexs[i] = parseInt(hexs[i], 16) + return hexs +} + +/** + * rgb颜色转Hex颜色 + * @param r 代表红色 + * @param g 代表绿色 + * @param b 代表蓝色 + * @returns 返回处理后的颜色值 + */ +export function rgbToHex(r: any, g: any, b: any) { + const reg = /^\d{1,3}$/ + if (!reg.test(r) || !reg.test(g) || !reg.test(b)) { + showToast({ type: 'fail', message: '输入错误的rgb颜色值' }) + return + } + const hexs = [r.toString(16), g.toString(16), b.toString(16)] + + for (let i = 0; i < 3; i++) if (hexs[i].length == 1) hexs[i] = `0${hexs[i]}` + + return `#${hexs.join('')}` +} + +/** + * 加深颜色值 + * @param color 颜色值字符串 + * @param level 加深的程度,限0-1之间 + * @returns 返回处理后的颜色值 + */ +export function getDarkColor(color: string, level: number) { + const reg = /^#?[0-9A-Fa-f]{6}$/ + if (!reg.test(color)) { + showToast({ type: 'fail', message: '输入错误的hex颜色值' }) + return + } + const rgb = hexToRgb(color) + for (let i = 0; i < 3; i++) rgb[i] = Math.round(20.5 * level + rgb[i] * (1 - level)) + return rgbToHex(rgb[0], rgb[1], rgb[2]) +} + +/** + * 变浅颜色值 + * @param color 颜色值字符串 + * @param level 加深的程度,限0-1之间 + * @returns 返回处理后的颜色值 + */ +export function getLightColor(color: string, level: number) { + const reg = /^#?[0-9A-Fa-f]{6}$/ + if (!reg.test(color)) { + showToast({ type: 'fail', message: '输入错误的hex颜色值' }) + return + } + const rgb = hexToRgb(color) + + for (let i = 0; i < 3; i++) { + rgb[i] = Math.round(255 * level + rgb[i] * (1 - level)) + } + return rgbToHex(rgb[0], rgb[1], rgb[2]) +} diff --git a/mobile-h5/src/utils/colors.ts b/mobile-h5/src/utils/colors.ts new file mode 100755 index 000000000..2f7575ee6 --- /dev/null +++ b/mobile-h5/src/utils/colors.ts @@ -0,0 +1,109 @@ +export function getCssVariable(str: string) { + return getComputedStyle(document.documentElement).getPropertyValue(str) +} + +// 将hex颜色转成rgb 例如(#F55442, 1) +export function hexToRgba( + hex: string, + opacity: number +): { red: number; green: number; blue: number; rgba: string } { + // 移除可能存在的 # 前缀并转换为大写 + hex = hex.replace(/^#/, '').toUpperCase() + + // 如果是缩写形式(如 FFF),转换为完整形式 + if (hex.length === 3) { + hex = hex + .split('') + .map((char) => char.repeat(2)) + .join('') + } + + // 验证 hex 格式 + if (!/^[0-9A-F]{6}$/.test(hex)) { + throw new Error('Invalid hex color format') + } + + // 解析 RGB 值 + const [red, green, blue] = hex.match(/\w\w/g)!.map((x) => parseInt(x, 16)) + + // 确保 opacity 在有效范围内 + opacity = Math.max(0, Math.min(1, opacity)) + + // 构建 RGBA 字符串 + const rgba = `rgba(${red}, ${green}, ${blue}, ${opacity.toFixed(2)})` + + return { red, green, blue, rgba } +} + +// 将rgb颜色转成hex 例如(24,12,255) +export function rgbToHex(r: any, g: any, b: any) { + const hex = '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1) + return hex +} + +// 颜色混合 +export function colourBlend(c1: string, c2: string, ratio: any) { + ratio = Math.max(Math.min(Number(ratio), 1), 0) + const r1 = parseInt(c1.substring(1, 3), 16) + const g1 = parseInt(c1.substring(3, 5), 16) + const b1 = parseInt(c1.substring(5, 7), 16) + const r2 = parseInt(c2.substring(1, 3), 16) + const g2 = parseInt(c2.substring(3, 5), 16) + const b2 = parseInt(c2.substring(5, 7), 16) + let r: any = Math.round(r1 * (1 - ratio) + r2 * ratio) + let g: any = Math.round(g1 * (1 - ratio) + g2 * ratio) + let b: any = Math.round(b1 * (1 - ratio) + b2 * ratio) + r = ('0' + (r || 0).toString(16)).slice(-2) + g = ('0' + (g || 0).toString(16)).slice(-2) + b = ('0' + (b || 0).toString(16)).slice(-2) + return '#' + r + g + b +} + +export const getCssVar = (name: string) => { + return getComputedStyle(document.documentElement).getPropertyValue(name) +} + +// 处理 Element Plus 主题颜色 +export function handleElementThemeColor(theme: string, isDark: boolean = false): void { + document.documentElement.style.setProperty('--el-color-primary', theme) + for (let i = 1; i <= 9; i++) { + document.documentElement.style.setProperty( + `--el-color-primary-light-${i}`, + `${getLightColor(theme, i / 10, isDark)}` + ) + } + for (let i = 1; i <= 9; i++) { + document.documentElement.style.setProperty( + `--el-color-primary-dark-${i}`, + `${getDarkColor(theme, i / 10)}` + ) + } +} + +// hex颜色转rgb颜色 +export function hexToRgb(str: string): number[] { + str = str.replace('#', '') + const hexs = str.match(/../g) + if (!hexs) { + throw new Error('Invalid hex color') + } + return hexs.map((hex) => parseInt(hex, 16)) +} + +// 变浅颜色值 +export function getLightColor(color: string, level: number, isDark: boolean = false): string { + if (isDark) { + return getDarkColor(color, level) + } else { + const rgb = hexToRgb(color) + const newRgb = rgb.map((value) => Math.floor((255 - value) * level + value)) + return rgbToHex(newRgb[0], newRgb[1], newRgb[2]) + } +} + +// 变深颜色值 +export function getDarkColor(color: string, level: number): string { + const rgb = hexToRgb(color) + const newRgb = rgb.map((value) => Math.floor(value * (1 - level))) + return rgbToHex(newRgb[0], newRgb[1], newRgb[2]) +} diff --git a/mobile-h5/src/utils/console.ts b/mobile-h5/src/utils/console.ts new file mode 100644 index 000000000..c008543f3 --- /dev/null +++ b/mobile-h5/src/utils/console.ts @@ -0,0 +1,21 @@ +// ANSI 转义码生成网站 https://patorjk.com/software/taag/#p=display&f=Big&t=ABB%0A +const asciiArt = ` +\x1b[32m✨ 你好!欢迎使用 Art Design Pro! +\x1b[0m +\x1b[36m💡 如果您觉得项目对您有帮助,请点击下面的链接为我点个 ★Star 支持一下!祝您使用愉快! +\x1b[0m +\x1b[33m🌟 GitHub: https://github.com/Daymychen/art-design-pro +\x1b[0m +\x1b[31m✨ 技术支持(QQ群): 821834289,如果你有任何问题,请加入QQ群,我们会在第一时间为你解答! +\x1b[0m + +\x1b[36m _ _______ _________ ______ ________ ______ _____ ______ ____ _____ _______ _______ ___ +\x1b[36m / \\ |_ __ \\ | _ _ | |_ _ \`.|_ __ |.' ____ \\ |_ _|.' ___ ||_ \\|_ _| |_ __ \\|_ __ \\ .' \`. +\x1b[36m / _ \\ | |__) ||_/ | | \\_| | | \`. \\ | |_ \\_|| (___ \\_| | | / .' \\_| | \\ | | | |__) | | |__) | / .-. \\ +\x1b[36m / ___ \\ | __ / | | | | | | | _| _ _.____\`. | | | | ____ | |\\ \\| | | ___/ | __ / | | | | +\x1b[36m _/ / \\ \\_ _| | \\ \\_ _| |_ _| |_.' /_| |__/ || \\____) | _| |_\\ \`.___] |_| |_\\ |_ _| |_ _| | \\ \\_\\ \`-' / +\x1b[36m|____| |____||____| |___||_____| |______.'|________| \\______.'|_____|\\\`._____.'|_____|\____| |_____| |____| |___|\\\`.___.' +\x1b[0m +` + +console.log(asciiArt) diff --git a/mobile-h5/src/utils/echarts/README.md b/mobile-h5/src/utils/echarts/README.md new file mode 100755 index 000000000..3f7035eb9 --- /dev/null +++ b/mobile-h5/src/utils/echarts/README.md @@ -0,0 +1,9 @@ +ECharts 配置项参考地址 + +地址:https://echarts.apache.org/examples/zh/editor.html?c=line-simple + +示例编辑:可以在这里方便快捷的配置 + +完整代码:配置后的所有代码 + +配置项:根据需求选择配置项配置 diff --git a/mobile-h5/src/utils/echarts/defaultOpstions.ts b/mobile-h5/src/utils/echarts/defaultOpstions.ts new file mode 100755 index 000000000..609c79b20 --- /dev/null +++ b/mobile-h5/src/utils/echarts/defaultOpstions.ts @@ -0,0 +1,56 @@ +// 默认配置 + +const defaultOpstions = { + grid: { + left: '0', + right: '4%', + bottom: '0%', + top: '0', + containLabel: true + }, + tooltip: {}, + yAxis: { + axisLabel: { + show: true, + color: '#8c8c8c' + }, + axisTick: { + show: false + }, + axisLine: { + show: true, + lineStyle: { + color: '#e8e8e8', + width: 1, + type: 'solid' + } + } + }, + xAxis: { + axisLine: { + show: true, + lineStyle: { + color: '#e8e8e8', + width: 1, + type: 'solid' + } + }, + splitLine: { + show: true, + lineStyle: { + color: '#e8e8e8', + width: 0, + type: 'solid' + } + }, + axisLabel: { + show: true, + color: 'rgba(255,255,255, 0.6)' + }, + axisTick: { + show: false + } + } +} + +export { defaultOpstions } diff --git a/mobile-h5/src/utils/echarts/useECharts.ts b/mobile-h5/src/utils/echarts/useECharts.ts new file mode 100755 index 000000000..684c4b82e --- /dev/null +++ b/mobile-h5/src/utils/echarts/useECharts.ts @@ -0,0 +1,117 @@ +// src/utils/echarts/useECharts.ts +import { unref, Ref, nextTick } from 'vue' +import echarts from '@/plugins/echarts' +import { defaultOpstions } from './defaultOpstions' + +// ECharts 类型定义(如果 echarts 未安装) +type EChartsOption = any +type EChartsInstance = { + setOption: (option: any) => void + resize: () => void + dispose: () => void +} + +export function useECharts( + elRef: Ref, + theme: 'light' | 'dark' | 'default' = 'light' +) { + let chartInstance: EChartsInstance | null = null + let resizeHandler: () => void + + // 初始化echarts + function initCharts() { + const el = unref(elRef) + if (!el) { + return + } + chartInstance = echarts.init ? echarts.init(el, theme) : null + addResize() + } + + // 配置 + function setOptions(options: EChartsOption | any) { + // 默认配置 + if (!options.grid) { + options.grid = defaultOpstions.grid + } + + if (!options.tooltip) { + options.tooltip = defaultOpstions.tooltip + } + + if (options.yAxis) { + const { axisLine, axisTick } = defaultOpstions.yAxis + + if (!options.yAxis.axisLine) { + options.yAxis.axisLine = axisLine + } + if (!options.yAxis.axisTick) { + options.yAxis.axisTick = axisTick + } + } + + if (options.xAxis) { + const { axisLine, splitLine, axisTick } = defaultOpstions.xAxis + + if (!options.xAxis.axisLine) { + options.xAxis.axisLine = axisLine + } + if (!options.xAxis.splitLine) { + options.xAxis.splitLine = splitLine + } + if (!options.xAxis.axisTick) { + options.xAxis.axisTick = axisTick + } + } + + if (unref(elRef).offsetHeight === 0) { + setTimeout(() => { + setOptions(options) + }, 30) + return + } + + nextTick(() => { + setTimeout(() => { + if (!chartInstance) { + initCharts() + if (!chartInstance) return + } + chartInstance.setOption(options) + }, 30) + }) + } + + // 监听窗口大小变化 + function addResize() { + resizeHandler = () => { + resize() + } + window.addEventListener('resize', resizeHandler) + } + + // 移除监听 + function removeResize() { + if (resizeHandler) { + window.removeEventListener('resize', resizeHandler) + } + } + + // 暴露 resize 方法 + function resize() { + if (chartInstance) { + chartInstance.resize() + } + } + + // 初始化时创建图表 + initCharts() + + return { + setOptions, + addResize, + removeResize, + resize, + echarts + } +} diff --git a/mobile-h5/src/utils/emojo.ts b/mobile-h5/src/utils/emojo.ts new file mode 100644 index 000000000..056505d21 --- /dev/null +++ b/mobile-h5/src/utils/emojo.ts @@ -0,0 +1,21 @@ +/** + * 表情 + * 用于在消息提示的时候显示对应的表情 + * + * 用法 + * ElMessage.success(`${EmojiText[200]} 图片上传成功`) + * ElMessage.error(`${EmojiText[400]} 图片上传失败`) + * ElMessage.error(`${EmojiText[500]} 图片上传失败`) + */ + +// macos 用户 按 shift + 6 可以唤出更多表情…… +const EmojiText: { [key: string]: string } = { + '0': 'O_O', // 空 + '200': '^_^', // 成功 + '400': 'T_T', // 错误请求 + '500': 'X_X' // 服务器内部错误,无法完成请求 +} + +// const EmojiIcon = ['🟢', '🔴', '🟡 ', '🚀', '✨', '💡', '🛠️', '🔥', '🎉', '🌟', '🌈'] + +export default EmojiText diff --git a/mobile-h5/src/utils/http/index.ts b/mobile-h5/src/utils/http/index.ts new file mode 100755 index 000000000..b909efe9f --- /dev/null +++ b/mobile-h5/src/utils/http/index.ts @@ -0,0 +1,174 @@ +import axios, { InternalAxiosRequestConfig, AxiosRequestConfig, AxiosResponse } from 'axios' +import { showToast } from 'vant' +import { useUserStore } from '@/store/modules/user' + +const getBaseURL = () => { + if (import.meta.env.DEV) { + return '/api' + } + return import.meta.env.VITE_API_URL || '/api' +} + +const axiosInstance = axios.create({ + timeout: 15000, + baseURL: getBaseURL(), + withCredentials: true, + transformRequest: [ + (data) => { + if (data && typeof data === 'object' && !(data instanceof FormData)) { + return JSON.stringify(data) + } + return data + } + ], + validateStatus: (status) => status >= 200 && status < 300, + headers: { + get: { 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8' }, + post: { 'Content-Type': 'application/json;charset=utf-8' } + }, + transformResponse: [ + (data, headers) => { + // 如果 data 是 undefined 或 null,直接返回 + if (data === undefined || data === null) { + return data + } + + const contentType = headers['content-type'] + if (contentType && contentType.includes('application/json')) { + try { + // 如果 data 是字符串,尝试解析 + if (typeof data === 'string') { + return JSON.parse(data) + } + // 如果已经是对象,直接返回 + return data + } catch (error) { + console.error('JSON parse error:', error, 'data:', data) + return data + } + } + return data + } + ] +}) + +// 请求拦截器 +axiosInstance.interceptors.request.use( + (config: InternalAxiosRequestConfig) => { + const userStore = useUserStore() + if (userStore.accessToken) { + config.headers.Authorization = `Bearer ${userStore.accessToken}` + } + + // 对于有 body 的请求(POST, PUT, PATCH),确保 Content-Type 正确 + const method = config.method?.toUpperCase() + if (method === 'POST' || method === 'PUT' || method === 'PATCH') { + if (config.data && typeof config.data === 'object' && !(config.data instanceof FormData)) { + // 如果 data 是对象,确保 Content-Type 是 application/json + config.headers['Content-Type'] = 'application/json;charset=utf-8' + } + } + + return config + }, + (error) => { + return Promise.reject(error) + } +) + +// 响应拦截器 +axiosInstance.interceptors.response.use( + (response: AxiosResponse) => { + // 如果 response.data 是 undefined,直接返回 + if (!response.data) { + return response.data + } + + // 如果响应数据没有 code 字段,说明后端直接返回数据,直接返回 + if (typeof response.data !== 'object' || !('code' in response.data)) { + return response.data + } + + const { code, message } = response.data + + // 成功响应 + if (code === 200 || code === 0) { + return response.data + } + + // 401 未授权,清除 token 并跳转登录 + if (code === 401) { + const userStore = useUserStore() + userStore.logOut() + showToast({ + type: 'fail', + message: message || '登录已过期,请重新登录' + }) + setTimeout(() => { + window.location.href = '/login' + }, 1500) + return Promise.reject(new Error(message || 'Unauthorized')) + } + + // 其他错误 + showToast({ + type: 'fail', + message: message || '请求失败' + }) + return Promise.reject(new Error(message || 'Request failed')) + }, + (error) => { + if (error.response) { + const { status, data } = error.response + if (status === 401) { + const userStore = useUserStore() + userStore.logOut() + showToast({ + type: 'fail', + message: '登录已过期,请重新登录' + }) + setTimeout(() => { + window.location.href = '/login' + }, 1500) + } else { + showToast({ + type: 'fail', + message: data?.message || `请求失败: ${status}` + }) + } + } else if (error.request) { + showToast({ + type: 'fail', + message: '网络错误,请检查网络连接' + }) + } else { + showToast({ + type: 'fail', + message: error.message || '请求失败' + }) + } + return Promise.reject(error) + } +) + +// API 方法封装 +const api = { + get(config: AxiosRequestConfig): Promise { + return axiosInstance.request({ ...config, method: 'GET' }) + }, + post(config: AxiosRequestConfig): Promise { + return axiosInstance.request({ ...config, method: 'POST' }) + }, + put(config: AxiosRequestConfig): Promise { + return axiosInstance.request({ ...config, method: 'PUT' }) + }, + patch(config: AxiosRequestConfig): Promise { + return axiosInstance.request({ ...config, method: 'PATCH' }) + }, + del(config: AxiosRequestConfig): Promise { + return axiosInstance.request({ ...config, method: 'DELETE' }) + } +} + +export default api +export { axiosInstance } diff --git a/mobile-h5/src/utils/http/status.ts b/mobile-h5/src/utils/http/status.ts new file mode 100755 index 000000000..6743d1e07 --- /dev/null +++ b/mobile-h5/src/utils/http/status.ts @@ -0,0 +1,3 @@ +export enum ApiStatus { + success = 200 +} diff --git a/mobile-h5/src/utils/iconfont.ts b/mobile-h5/src/utils/iconfont.ts new file mode 100644 index 000000000..6e63b749e --- /dev/null +++ b/mobile-h5/src/utils/iconfont.ts @@ -0,0 +1,66 @@ +// 提取 iconfont 图标 + +export interface IconfontType { + className: string + unicode?: string +} + +interface StyleSheetError { + sheet: CSSStyleSheet + error: unknown +} + +function extractIconFromRule(rule: CSSRule): IconfontType | null { + if (!(rule instanceof CSSStyleRule)) return null + + const { selectorText, style } = rule + if (!selectorText?.startsWith('.iconsys-') || !selectorText.includes('::before')) return null + + const className = selectorText.substring(1).replace('::before', '') + const content = style.getPropertyValue('content') + if (!content) return null + + const unicode = content.replace(/['"\\]/g, '') + return { + className, + unicode: unicode ? `&#x${getUnicode(unicode)};` : undefined + } +} + +const processedErrors = new Set() + +export function extractIconClasses(): IconfontType[] { + const iconInfos: IconfontType[] = [] + + try { + Array.from(document.styleSheets).forEach((sheet) => { + try { + const rules = Array.from(sheet.cssRules || sheet.rules) + rules.forEach((rule) => { + const iconInfo = extractIconFromRule(rule) + if (iconInfo) { + iconInfos.push(iconInfo) + } + }) + } catch (error) { + const styleSheetError: StyleSheetError = { sheet, error } + if (!processedErrors.has(styleSheetError)) { + console.warn('Cannot read cssRules from stylesheet:', { + error, + sheetHref: sheet.href + }) + processedErrors.add(styleSheetError) + } + } + }) + } catch (error) { + console.error('Failed to process stylesheets:', error) + } + + return iconInfos +} + +export function getUnicode(charCode: string): string { + if (!charCode) return '' + return charCode.charCodeAt(0).toString(16).padStart(4, '0') +} diff --git a/mobile-h5/src/utils/jump.ts b/mobile-h5/src/utils/jump.ts new file mode 100644 index 000000000..96778d9e5 --- /dev/null +++ b/mobile-h5/src/utils/jump.ts @@ -0,0 +1,41 @@ +import { MenuListType } from '@/types/menu' +import { router } from '@/router' + +// 打开外部链接 +export const openExternalLink = (link: string) => { + window.open(link, '_blank') +} + +/** + * 菜单跳转 + * @param item 菜单项 + * @param jumpToFirst 是否跳转到第一个子菜单 + * @returns + */ +export const handleMenuJump = (item: MenuListType, jumpToFirst: boolean = false) => { + // 处理外部链接 + const link = item.meta?.link || item.meta?.isLink + const isIframe = item.meta?.isIframe + if (link && !isIframe) { + return openExternalLink(link) + } + + // 如果不需要跳转到第一个子菜单,或者没有子菜单,直接跳转当前路径 + if (!jumpToFirst || !item.children?.length) { + return router.push(item.path) + } + + // 获取第一个可见的子菜单,如果没有则取第一个子菜单 + const firstChild = item.children.find((child: MenuListType) => !child.meta?.isHide) || item.children[0] + + // 如果第一个子菜单是外部链接则打开新窗口 + const childLink = firstChild.meta?.link || firstChild.meta?.isLink + if (childLink) { + return openExternalLink(childLink) + } + + // 跳转到子菜单路径 + router.push(firstChild.path) +} + +// ... existing code ... diff --git a/mobile-h5/src/utils/links.ts b/mobile-h5/src/utils/links.ts new file mode 100644 index 000000000..1cb6a733a --- /dev/null +++ b/mobile-h5/src/utils/links.ts @@ -0,0 +1,22 @@ +export const WEB_LINKS = { + // Github 主页 + GITHUB_HOME: 'https://github.com/Daymychen', + + // 项目 Github 主页 + GITHUB: 'https://github.com/Daymychen/art-design-pro', + + // 个人博客 + BLOG: 'https://www.lingchen.kim', + + // 项目文档 + DOCS: 'https://www.lingchen.kim/art-design-pro/docs', + + // 项目社区 + COMMUNITY: 'https://www.lingchen.kim/art-design-pro/docs/guide/community/communicate.html', + + // 个人 Bilibili 主页 + BILIBILI: 'https://space.bilibili.com/425500936?spm_id_from=333.1007.0.0', + + // 项目介绍 + INTRODUCE: 'https://www.lingchen.kim/art-design-pro/docs/guide/introduce.html' +} diff --git a/mobile-h5/src/utils/menu.ts b/mobile-h5/src/utils/menu.ts new file mode 100755 index 000000000..cc8427688 --- /dev/null +++ b/mobile-h5/src/utils/menu.ts @@ -0,0 +1,55 @@ +import { $t } from '@/language' +import { MenuListType } from '@/types/menu' + +// 创建递归函数处理嵌套路由 +/** + * 处理路由配置,转换为菜单数据结构 + * @param route 路由配置对象 + * @param parentPath 父级路径 + * @returns 处理后的菜单项 + */ +export const processRoute = (route: MenuListType, parentPath = ''): MenuListType => { + // 构建完整路径 + const currentPath = route.path + ? route.meta?.isIframe + ? route.path // isIframe 为 true 时直接使用原始路径 + : parentPath + ? `${parentPath}/${route.path}`.replace(/\/+/g, '/') // 规范化路径,避免多余的斜杠 + : route.path + : '' + + return { + ...(route.id && { id: route.id }), // 如果有 id 则包含 + name: route.name, + path: currentPath, + component: route.component, + meta: route.meta ?? {}, // 使用空值合并运算符 + children: Array.isArray(route.children) + ? route.children.map((child: MenuListType) => processRoute(child, currentPath)) + : [] + } +} + +/** + * 保存 iframe 路由到 sessionStorage 中 + * @param list iframe 路由列表 + */ +export const saveIframeRoutes = (list: MenuListType[]): void => { + if (list.length > 0) { + sessionStorage.setItem('iframeRoutes', JSON.stringify(list)) + } +} + +// 获取 iframe 路由 +export const getIframeRoutes = () => { + return JSON.parse(sessionStorage.getItem('iframeRoutes') || '[]') +} + +/** + * 格式化菜单标题 + * @param title 菜单标题,可以是 i18n 的 key,也可以是字符串,比如:'用户列表' + * @returns 格式化后的菜单标题 + */ +export const formatMenuTitle = (title: string) => { + return title.startsWith('menus.') ? $t(title) : title +} diff --git a/mobile-h5/src/utils/mittBus.ts b/mobile-h5/src/utils/mittBus.ts new file mode 100644 index 000000000..4010146b3 --- /dev/null +++ b/mobile-h5/src/utils/mittBus.ts @@ -0,0 +1,9 @@ +/** + * 全局事件总线,用于全局事件的发布与订阅 + * 用法: + * mittBus.on('event', callback) + * mittBus.emit('event', data) + */ +import mitt from 'mitt' +const mittBus = mitt() +export default mittBus diff --git a/mobile-h5/src/utils/storage.ts b/mobile-h5/src/utils/storage.ts new file mode 100644 index 000000000..1e8fbd4ba --- /dev/null +++ b/mobile-h5/src/utils/storage.ts @@ -0,0 +1,167 @@ +import { showToast } from 'vant' +import { router } from '@/router' +import { useUserStore } from '@/store/modules/user' +import { useSettingStore } from '@/store/modules/setting' +import { useWorktabStore } from '@/store/modules/worktab' + +// 初始化本地数据 +export function initState() { + if (validateStorageData() || isLocalStorageEmpty()) { + const userStore = useUserStore() + const worktabStore = useWorktabStore() + const settingStore = useSettingStore() + + userStore.initState() + worktabStore.initState() + settingStore.initState() + } +} + +// 获取系统存储数据 +export function getSysStorage() { + const version = getSysVersion() || import.meta.env.VITE_VERSION + return localStorage.getItem(`sys-v${version}`) as any +} + +// 获取系统版本 +export function getSysVersion() { + return localStorage.getItem('version') +} + +// 验证本地存储数据的类型 +function validate(obj: any, schema: any, path: string = ''): boolean { + // 如果 obj 不存在或为 null,且不是根对象,则跳过验证(允许可选字段) + if (!obj && path !== '') { + return true + } + + // 如果 obj 不是对象,验证失败 + if (obj && typeof obj !== 'object') { + return false + } + + return Object.keys(schema).every((key) => { + const fullPath = path ? `${path}.${key}` : key + const expectedType = schema[key] + const value = obj?.[key] + const actualType = typeof value + + // 如果字段不存在或为 undefined,且是嵌套对象,允许跳过(可选字段) + if (value === undefined || value === null) { + if (typeof expectedType === 'object' && !Array.isArray(expectedType)) { + return true // 允许嵌套对象为 undefined + } + // 对于基本类型,如果值为 undefined,也允许(可选字段) + return true + } + + if (typeof expectedType === 'object' && !Array.isArray(expectedType)) { + return validate(value, expectedType, fullPath) + } else if (actualType !== expectedType) { + console.error( + `检测到本地数据异常 path:[/utils/storage.ts] ${fullPath} 数据类型应为 ${expectedType},实际为 ${actualType}` + ) + return false + } + + return true + }) +} + +// 显示错误消息并处理登出逻辑 +function handleError() { + showToast({ + type: 'fail', + message: '系统检测到本地数据异常,请重新登录系统恢复使用!', + duration: 5000 + }) + + logOut() +} + +function logOut() { + setTimeout(() => { + localStorage.clear() + useUserStore().logOut() + router.push('/login') + initState() + }, 1000) +} + +// 验证本地存储数据并处理异常 +export function validateStorageData() { + if (location.href.includes('/login')) return true + + const schema = { + user: { + info: 'object', + isLogin: 'boolean', + language: 'string', + worktab: { + // current 是 Partial 类型,所有字段都是可选的 + current: {}, + opened: 'object' + }, + setting: { + systemThemeType: 'string', + systemThemeMode: 'string', + menuThemeType: 'string', + boxBorderMode: 'boolean', + uniqueOpened: 'boolean', + systemThemeColor: 'string', + showMenuButton: 'boolean', + showRefreshButton: 'boolean', + showCrumbs: 'boolean', + autoClose: 'boolean', + showWorkTab: 'boolean', + showLanguage: 'boolean', + showNprogress: 'boolean', + colorWeak: 'boolean', + showSettingGuide: 'boolean', + refresh: 'boolean' + } + } + } + + try { + const data = getLocalStorageData() + // 模拟本地数据类型错误 + // data.user.language = 2024 + + if (Object.keys(data).length === 0) { + logOut() + return false + } + + if (!validate(data, schema)) { + throw new Error('本地存储数据结构异常') + } + + return true + } catch { + handleError() + return false + } +} + +// 获取本地存储数据 +function getLocalStorageData() { + return JSON.parse(getSysStorage() || '{}') +} + +// 本地存储是否为空 +function isLocalStorageEmpty() { + return Object.keys(getLocalStorageData()).length === 0 +} + +// 将 vuex 中的数据保存到 localStorage 中(在即将离开页面(刷新或关闭)时执行) +export function saveUserData() { + const isiOS = /iPhone|iPad|iPod/i.test(navigator.userAgent) + const eventType = isiOS ? 'pagehide' : 'beforeunload' + + window.addEventListener(eventType, () => { + if (getSysVersion()) { + useUserStore().saveUserData() + } + }) +} diff --git a/mobile-h5/src/utils/tabs.ts b/mobile-h5/src/utils/tabs.ts new file mode 100644 index 000000000..246595140 --- /dev/null +++ b/mobile-h5/src/utils/tabs.ts @@ -0,0 +1,33 @@ +/** + * 标签页容器高度以及顶部距离配置 + * @param {Object} tab-default - 默认标签页样式配置 + * @param {number} openTop - 标签页打开时的顶部填充高度 + * @param {number} closeTop - 标签页关闭时的顶部填充高度 + * @param {number} openHeight - 标签页打开时的总高度 + * @param {number} closeHeight - 标签页关闭时的总高度 + */ +export const TAB_CONFIG = { + 'tab-default': { + openTop: 70, + closeTop: 60, + openHeight: 85, + closeHeight: 75 + }, + 'tab-card': { + openTop: 122, + closeTop: 78, + openHeight: 139, + closeHeight: 95 + }, + 'tab-google': { + openTop: 122, + closeTop: 78, + openHeight: 139, + closeHeight: 95 + } +} + +// 获取当前 tab 样式配置,设置默认值 +export const getTabConfig = (style: string) => { + return TAB_CONFIG[style as keyof typeof TAB_CONFIG] || TAB_CONFIG['tab-card'] // 默认使用 tab-card 配置 +} diff --git a/mobile-h5/src/utils/theme/animation.ts b/mobile-h5/src/utils/theme/animation.ts new file mode 100644 index 000000000..07845c278 --- /dev/null +++ b/mobile-h5/src/utils/theme/animation.ts @@ -0,0 +1,35 @@ +import { useCommon } from '@/composables/useCommon' +import { useTheme } from '@/composables/useTheme' +import { SystemThemeEnum } from '@/enums/appEnum' +import { useSettingStore } from '@/store/modules/setting' +const { LIGHT, DARK } = SystemThemeEnum + +/** + * 主题切换动画 + * @param e 鼠标点击事件 + */ +export const themeAnimation = (e: any) => { + const x = e.clientX + const y = e.clientY + // 计算鼠标点击位置距离视窗的最大圆半径 + const endRadius = Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y)) + + // 设置CSS变量 + document.documentElement.style.setProperty('--x', x + 'px') + document.documentElement.style.setProperty('--y', y + 'px') + document.documentElement.style.setProperty('--r', endRadius + 'px') + + if (document.startViewTransition) { + document.startViewTransition(() => toggleTheme()) + } else { + toggleTheme() + } +} + +/** + * 切换主题 + */ +const toggleTheme = () => { + useTheme().switchThemeStyles(useSettingStore().systemThemeType === LIGHT ? DARK : LIGHT) + useCommon().refresh() +} diff --git a/mobile-h5/src/utils/upgrade.ts b/mobile-h5/src/utils/upgrade.ts new file mode 100644 index 000000000..c53c7512e --- /dev/null +++ b/mobile-h5/src/utils/upgrade.ts @@ -0,0 +1,47 @@ +import { upgradeLogList } from '@/mock/upgradeLog' +import { showNotify } from 'vant' +import { useUserStore } from '@/store/modules/user' + +// 系统升级 +export function systemUpgrade() { + const version = import.meta.env.VITE_VERSION + // 跳过版本为1.0.0的提示 + if (version === '1.0.0') return + + setTimeout(() => { + const { title: content, requireReLogin } = upgradeLogList.value[0] + // 系统升级公告 + const message = [ + `

`, + `系统已升级到 ${version} 版本,此次更新带来了以下改进:`, + `

`, + content, + requireReLogin + ? `

升级完成,请重新登录后继续使用。

` + : '' + ].join('') + + const oldSysKey = Object.keys(localStorage).find( + (key) => key.startsWith('sys-v') && key !== `sys-v${version}` + ) + + if (oldSysKey) { + showNotify({ + type: 'success', + message: message, + duration: 0 + }) + + setTimeout(() => { + // 更新版本号 + localStorage.setItem('version', version) + // 删除旧版本号 + localStorage.removeItem(oldSysKey) + // 需要重新登录 + if (requireReLogin) { + useUserStore().logOut() + } + }, 1000) + } + }, 1000) +} diff --git a/mobile-h5/src/utils/utils.ts b/mobile-h5/src/utils/utils.ts new file mode 100755 index 000000000..c46eac704 --- /dev/null +++ b/mobile-h5/src/utils/utils.ts @@ -0,0 +1,293 @@ +/** + * 常用 JavaScript 函数片段 + * author chen tao + * date 2020-10-6 + */ + +/* 数组相关函数 */ + +// 数组去重 +export function noRepeat(arr: T[]): T[] { + return [...new Set(arr)] +} + +// 查找数组最大值 +export function arrayMax(arr: number[]): number { + if (!arr.length) throw new Error('Array is empty') + return Math.max(...arr) +} + +// 查找数组最小值 +export function arrayMin(arr: number[]): number { + if (!arr.length) throw new Error('Array is empty') + return Math.min(...arr) +} + +// 数组分割 +export function chunk(arr: T[], size: number = 1): T[][] { + if (size <= 0) return [arr.slice()] + return Array.from({ length: Math.ceil(arr.length / size) }, (_, i) => + arr.slice(i * size, i * size + size) + ) +} + +// 检查元素出现次数 +export function countOccurrences(arr: T[], value: T): number { + return arr.reduce((count, current) => (current === value ? count + 1 : count), 0) +} + +// 扁平化数组 +export function flatten(arr: any[], depth: number = Infinity): T[] { + return arr.flat(depth) +} + +// 返回两个数组的差集 +export function difference(arrA: T[], arrB: T[]): T[] { + const setB = new Set(arrB) + return arrA.filter((item) => !setB.has(item)) +} + +// 返回两个数组的交集 +export function intersection(arr1: T[], arr2: T[]): T[] { + const set2 = new Set(arr2) + return arr1.filter((item) => set2.has(item)) +} + +// 从右删除 n 个元素 +export function dropRight(arr: T[], n: number = 0): T[] { + return arr.slice(0, Math.max(0, arr.length - n)) +} + +// 返回间隔 nth 的元素 +export function everyNth(arr: T[], nth: number): T[] { + if (nth <= 0) return [] + return arr.filter((_, i) => i % nth === nth - 1) +} + +// 返回第 n 个元素 +export function nthElement(arr: T[], n: number = 0): T | undefined { + const index = n >= 0 ? n : arr.length + n + return arr[index] +} + +// 数组乱序 +export function shuffle(arr: T[]): T[] { + const array = [...arr] // 创建新数组避免修改原数组 + for (let i = array.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)) + ;[array[i], array[j]] = [array[j], array[i]] + } + return array +} + +/* 浏览器对象 BOM */ + +// 当前网页地址 +export function currentURL(): string { + return window.location.href +} + +// 获取滚动条位置 +export function getScrollPosition(el: HTMLElement | Window = window): { x: number; y: number } { + return el === window + ? { + x: window.scrollX || document.documentElement.scrollLeft, + y: window.scrollY || document.documentElement.scrollTop + } + : { + x: (el as HTMLElement).scrollLeft, + y: (el as HTMLElement).scrollTop + } +} + +// 获取 URL 参数 +export function getURLParameters(url: string): Record { + return Object.fromEntries(new URLSearchParams(url.split('?')[1]).entries()) +} + +// 兼容非 HTTPS 等环境:使用 execCommand 复制 +function fallbackCopy(str: string): boolean { + try { + const textarea = document.createElement('textarea') + textarea.value = str + textarea.style.position = 'fixed' + textarea.style.left = '-9999px' + textarea.setAttribute('readonly', '') + document.body.appendChild(textarea) + textarea.select() + const ok = document.execCommand('copy') + document.body.removeChild(textarea) + return ok + } catch { + return false + } +} + +// 复制文本(同步,优先 clipboard API,不可用时回退 execCommand) +export function copy(str: string): boolean { + try { + if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') { + navigator.clipboard.writeText(str) + return true + } + return fallbackCopy(str) + } catch (err) { + console.error('Copy failed:', err) + return fallbackCopy(str) + } +} + +/** + * 异步复制到剪贴板,生产环境安全:优先 clipboard API,不可用时回退 execCommand。 + */ +export async function copyToClipboard(str: string): Promise { + try { + if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') { + await navigator.clipboard.writeText(str) + return true + } + return fallbackCopy(str) + } catch { + return fallbackCopy(str) + } +} + +/** + * 从图片 URL 拉取图片并复制到剪贴板(需支持 ClipboardItem,通常为 HTTPS 环境)。 + */ +export async function copyImageFromUrl(imageUrl: string): Promise { + try { + if (!navigator.clipboard || typeof navigator.clipboard.write !== 'function') return false + const res = await fetch(imageUrl, { mode: 'cors' }) + if (!res.ok) return false + const blob = await res.blob() + const type = blob.type || 'image/png' + await navigator.clipboard.write([new ClipboardItem({ [type]: blob })]) + return true + } catch { + return false + } +} + +/** + * 将 Blob 图片写入剪贴板(用于 html2canvas 等生成的图片)。 + */ +export async function copyImageBlobToClipboard(blob: Blob): Promise { + try { + if (!navigator.clipboard || typeof navigator.clipboard.write !== 'function') return false + const type = blob.type || 'image/png' + await navigator.clipboard.write([new ClipboardItem({ [type]: blob })]) + return true + } catch { + return false + } +} + +// 检测设备类型 +export function detectDeviceType(): 'Mobile' | 'Desktop' { + return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) + ? 'Mobile' + : 'Desktop' +} + +/* Cookie 操作 */ + +// 设置 Cookie +export function setCookie(key: string, value: string, expireDays: number): void { + const date = new Date() + date.setDate(date.getDate() + expireDays) + document.cookie = `${key}=${encodeURIComponent(value)}${ + expireDays ? `;expires=${date.toUTCString()}` : '' + }` +} + +// 删除 Cookie +export function delCookie(name: string): void { + document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT` +} + +// 获取 Cookie +export function getCookie(name: string): string | null { + const match = document.cookie.match(new RegExp(`(?:^|;\\s*)${name}=([^;]*)`)) + return match ? decodeURIComponent(match[1]) : null +} + +/* 日期相关 */ + +// 时间戳转时间 +export function timestampToTime(timestamp: number = Date.now(), isMs: boolean = true): string { + const date = new Date(isMs ? timestamp : timestamp * 1000) + return date.toISOString().replace('T', ' ').slice(0, 19) +} + +/* DOM 操作 */ + +let scrollTop = 0 + +export function preventScroll(): void { + scrollTop = window.scrollY + Object.assign(document.body.style, { + position: 'fixed', + width: '100%', + top: `-${scrollTop}px`, + overflowY: 'hidden' + }) +} + +export function recoverScroll(): void { + Object.assign(document.body.style, { + position: '', + width: '', + top: '', + overflowY: '' + }) + window.scrollTo(0, scrollTop) +} + +// 判断是否到达页面底部 +export function bottomVisible(): boolean { + return window.innerHeight + window.scrollY >= document.documentElement.scrollHeight +} + +// 判断元素是否在可视区域 +export function elementIsVisibleInViewport( + el: HTMLElement, + partiallyVisible: boolean = false +): boolean { + const { top, left, bottom, right } = el.getBoundingClientRect() + const { innerHeight, innerWidth } = window + + return partiallyVisible + ? top + el.offsetHeight > 0 && + top < innerHeight && + left + el.offsetWidth > 0 && + left < innerWidth + : top >= 0 && left >= 0 && bottom <= innerHeight && right <= innerWidth +} + +// 获取元素样式 +export function getStyle(el: HTMLElement, ruleName: string): string { + return window.getComputedStyle(el)[ruleName as any] +} + +/* 数字操作 */ + +export function commafy(num: number): string { + return num.toLocaleString('en-US') +} + +export function randomNum(min: number, max?: number): number { + if (max === undefined) return Math.floor(Math.random() * min) + return Math.floor(Math.random() * (max - min + 1)) + min +} + +/* 字符串操作 */ + +export function removeHtmlTags(str: string = ''): string { + return str.replace(/<[^>]*>/g, '') +} + +// 判断是不是iframe +export function isIframe(url: string) { + return url.includes('/outside/iframe') +} diff --git a/mobile-h5/src/utils/worktab.ts b/mobile-h5/src/utils/worktab.ts new file mode 100644 index 000000000..b3de4d873 --- /dev/null +++ b/mobile-h5/src/utils/worktab.ts @@ -0,0 +1,41 @@ +import { useWorktabStore } from '@/store/modules/worktab' +import { RouteLocationNormalized } from 'vue-router' +import { getIframeRoutes } from './menu' +import { isIframe } from './utils' +import { useSettingStore } from '@/store/modules/setting' +import { HOME_PAGE } from '@/router' + +/** + * 根据当前路由信息设置工作标签页(worktab) + * @param to 当前路由对象 + */ +export const setWorktab = (to: RouteLocationNormalized): void => { + const worktabStore = useWorktabStore() + const { meta, path, name, params, query } = to + if (!meta.isHideTab) { + // 如果是 iframe 页面,则特殊处理工作标签页 + if (isIframe(path)) { + const iframeRoute = getIframeRoutes().find((route: any) => route.path === to.path) + + if (iframeRoute?.meta) { + worktabStore.openTab({ + title: iframeRoute.meta.title, + path, + name: name as string, + keepAlive: meta.keepAlive as boolean, + params, + query + }) + } + } else if (useSettingStore().showWorkTab || path === HOME_PAGE) { + worktabStore.openTab({ + title: meta.title as string, + path, + name: name as string, + keepAlive: meta.keepAlive as boolean, + params, + query + }) + } + } +} diff --git a/mobile-h5/src/views/dashboard/index.vue b/mobile-h5/src/views/dashboard/index.vue new file mode 100644 index 000000000..01a335eb8 --- /dev/null +++ b/mobile-h5/src/views/dashboard/index.vue @@ -0,0 +1,232 @@ + + + + + + diff --git a/mobile-h5/src/views/inventory/detail.vue b/mobile-h5/src/views/inventory/detail.vue new file mode 100644 index 000000000..e788b0d2a --- /dev/null +++ b/mobile-h5/src/views/inventory/detail.vue @@ -0,0 +1,556 @@ + + + + + + diff --git a/mobile-h5/src/views/inventory/fill.vue b/mobile-h5/src/views/inventory/fill.vue new file mode 100644 index 000000000..ec6042a26 --- /dev/null +++ b/mobile-h5/src/views/inventory/fill.vue @@ -0,0 +1,314 @@ + + + + + diff --git a/mobile-h5/src/views/inventory/index.vue b/mobile-h5/src/views/inventory/index.vue new file mode 100644 index 000000000..f05ba268a --- /dev/null +++ b/mobile-h5/src/views/inventory/index.vue @@ -0,0 +1,1062 @@ + + + + + + + + diff --git a/mobile-h5/src/views/inventory/plan.vue b/mobile-h5/src/views/inventory/plan.vue new file mode 100644 index 000000000..ea61a3931 --- /dev/null +++ b/mobile-h5/src/views/inventory/plan.vue @@ -0,0 +1,791 @@ + + + + + + diff --git a/mobile-h5/src/views/login/index.vue b/mobile-h5/src/views/login/index.vue new file mode 100644 index 000000000..e1e0248f6 --- /dev/null +++ b/mobile-h5/src/views/login/index.vue @@ -0,0 +1,177 @@ + + + + + + diff --git a/mobile-h5/src/views/master-data/cargo-detail.vue b/mobile-h5/src/views/master-data/cargo-detail.vue new file mode 100644 index 000000000..5903fb7bc --- /dev/null +++ b/mobile-h5/src/views/master-data/cargo-detail.vue @@ -0,0 +1,129 @@ + + + + + + diff --git a/mobile-h5/src/views/master-data/company-detail.vue b/mobile-h5/src/views/master-data/company-detail.vue new file mode 100644 index 000000000..2f3b8e672 --- /dev/null +++ b/mobile-h5/src/views/master-data/company-detail.vue @@ -0,0 +1,129 @@ + + + + + + diff --git a/mobile-h5/src/views/master-data/index.vue b/mobile-h5/src/views/master-data/index.vue new file mode 100644 index 000000000..72620c371 --- /dev/null +++ b/mobile-h5/src/views/master-data/index.vue @@ -0,0 +1,609 @@ + + + + + diff --git a/mobile-h5/src/views/master-data/vehicle-detail.vue b/mobile-h5/src/views/master-data/vehicle-detail.vue new file mode 100644 index 000000000..0ec7f2441 --- /dev/null +++ b/mobile-h5/src/views/master-data/vehicle-detail.vue @@ -0,0 +1,127 @@ + + + + + diff --git a/mobile-h5/src/views/plans/detail.vue b/mobile-h5/src/views/plans/detail.vue new file mode 100644 index 000000000..a0e7030c0 --- /dev/null +++ b/mobile-h5/src/views/plans/detail.vue @@ -0,0 +1,510 @@ + + + + + + diff --git a/mobile-h5/src/views/plans/index.vue b/mobile-h5/src/views/plans/index.vue new file mode 100644 index 000000000..ac54aaa00 --- /dev/null +++ b/mobile-h5/src/views/plans/index.vue @@ -0,0 +1,792 @@ + + + + + diff --git a/mobile-h5/src/views/user/detail.vue b/mobile-h5/src/views/user/detail.vue new file mode 100644 index 000000000..c0b2a4a55 --- /dev/null +++ b/mobile-h5/src/views/user/detail.vue @@ -0,0 +1,169 @@ + + + + + + diff --git a/mobile-h5/src/views/user/index.vue b/mobile-h5/src/views/user/index.vue new file mode 100644 index 000000000..b6475ebe3 --- /dev/null +++ b/mobile-h5/src/views/user/index.vue @@ -0,0 +1,254 @@ + + + + + diff --git a/mobile-h5/src/views/weighing/detail.vue b/mobile-h5/src/views/weighing/detail.vue new file mode 100644 index 000000000..0ecb90390 --- /dev/null +++ b/mobile-h5/src/views/weighing/detail.vue @@ -0,0 +1,853 @@ + + + + + + diff --git a/mobile-h5/src/views/weighing/index.vue b/mobile-h5/src/views/weighing/index.vue new file mode 100644 index 000000000..d8f718753 --- /dev/null +++ b/mobile-h5/src/views/weighing/index.vue @@ -0,0 +1,758 @@ + + + + + diff --git a/mobile-h5/tsconfig.json b/mobile-h5/tsconfig.json new file mode 100644 index 000000000..5c03a798f --- /dev/null +++ b/mobile-h5/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} + diff --git a/mobile-h5/tsconfig.node.json b/mobile-h5/tsconfig.node.json new file mode 100644 index 000000000..e428d5060 --- /dev/null +++ b/mobile-h5/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} +