From ee2b1757d0788056f66fe880aef72713fc1d0754 Mon Sep 17 00:00:00 2001 From: li Date: Sat, 14 Mar 2026 17:39:50 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=91=98=E5=B7=A5=E8=87=AA=E5=8A=A9?= =?UTF-8?q?=E6=B3=A8=E5=86=8C+=E5=AE=A1=E6=A0=B8=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 registration_packages 表:管理员配置注册套餐(名称/角色) - AuthController 新增 register() 和 registrationPackages() 公开接口 - UserController 新增 approve() / reject() 审核接口 - StoreController 新增 publicList() 公开门店列表 - 前端 /register 注册页:选门店→选岗位套餐→填信息→提交 - 前端 system/registration-packages:套餐 CRUD - 用户管理页:待审核状态展示 + 通过/拒绝快捷操作 - 登录页底部加「申请注册」跳转链接 - 路由白名单加 /register --- .gitignore | 2 +- CLAUDE.md | 44 + .../Http/Controllers/Admin/AuthController.php | 58 + .../Inventory/InventoryCheckController.php | 80 ++ .../Admin/Inventory/StockController.php | 50 + .../Inventory/TransferOrderController.php | 82 ++ .../Admin/Office/ApprovalController.php | 176 ++- .../System/RegistrationPackageController.php | 66 ++ .../Admin/System/StoreController.php | 12 + .../Admin/System/UserController.php | 24 + .../app/Models/Inventory/InventoryCheck.php | 16 + .../app/Models/Inventory/TransferOrder.php | 17 + backend/app/Models/Office/ApprovalNode.php | 3 +- .../app/Models/System/RegistrationPackage.php | 26 + backend/bootstrap/app.php | 2 +- ...ode_label_role_to_approval_nodes_table.php | 25 + ...14_000002_create_transfer_orders_table.php | 47 + ...004_create_registration_packages_table.php | 29 + backend/routes/api.php | 31 +- deploy.sh | 26 + docs/api-check-report.md | 121 ++ frontend/package-lock.json | 1048 ++++++++++++++++- frontend/package.json | 4 + frontend/postcss.config.js | 6 + frontend/src/api/care.js | 4 +- frontend/src/api/finance.js | 7 + frontend/src/api/inventory.js | 23 + frontend/src/api/meal.js | 1 + frontend/src/api/service.js | 1 + frontend/src/api/system.js | 84 +- frontend/src/layout/components/Header.vue | 183 +-- frontend/src/layout/components/Sidebar.vue | 298 ++--- frontend/src/layout/index.vue | 66 +- frontend/src/router/index.js | 2 +- frontend/src/router/routes.js | 6 + frontend/src/styles/index.scss | 311 +++-- frontend/src/views/care/exceptions/index.vue | 274 +++++ .../src/views/care/health-metrics/index.vue | 223 ++++ frontend/src/views/care/plans/index.vue | 37 +- frontend/src/views/care/profiles/index.vue | 47 +- frontend/src/views/care/records/index.vue | 37 +- frontend/src/views/crm/channels/index.vue | 43 +- frontend/src/views/crm/complaints/index.vue | 47 +- frontend/src/views/crm/contracts/index.vue | 55 +- frontend/src/views/crm/customers/index.vue | 63 +- frontend/src/views/crm/leads/index.vue | 67 +- .../src/views/crm/questionnaires/index.vue | 202 ++++ frontend/src/views/dashboard/index.vue | 316 +---- frontend/src/views/finance/accounts/index.vue | 45 +- .../src/views/finance/categories/index.vue | 63 +- frontend/src/views/finance/invoices/index.vue | 53 +- frontend/src/views/finance/payments/index.vue | 215 ++++ .../src/views/finance/prepaid-cards/index.vue | 47 +- frontend/src/views/finance/records/index.vue | 69 +- .../src/views/finance/transactions/index.vue | 138 +++ frontend/src/views/hr/attendances/index.vue | 52 +- frontend/src/views/hr/leaves/index.vue | 73 +- frontend/src/views/hr/profiles/index.vue | 67 +- frontend/src/views/hr/salaries/index.vue | 75 +- frontend/src/views/hr/schedules/index.vue | 45 +- frontend/src/views/inventory/checks/index.vue | 294 +++++ .../src/views/inventory/materials/index.vue | 49 +- .../views/inventory/purchase-orders/index.vue | 62 +- .../views/inventory/stock-movements/index.vue | 63 +- frontend/src/views/inventory/stock/index.vue | 137 +++ .../src/views/inventory/suppliers/index.vue | 45 +- .../src/views/inventory/transfers/index.vue | 270 +++++ .../src/views/inventory/warehouses/index.vue | 100 +- frontend/src/views/kb/articles/index.vue | 79 +- frontend/src/views/kb/categories/index.vue | 55 +- frontend/src/views/login/index.vue | 139 +-- frontend/src/views/meal/daily-plans/index.vue | 53 +- frontend/src/views/meal/dishes/index.vue | 43 +- frontend/src/views/meal/reviews/index.vue | 191 +++ frontend/src/views/meal/templates/index.vue | 190 +++ frontend/src/views/nanny/nannies/index.vue | 47 +- frontend/src/views/nanny/orders/index.vue | 51 +- frontend/src/views/nanny/schedules/index.vue | 33 +- .../src/views/office/announcements/index.vue | 71 +- .../views/office/approval-templates/index.vue | 57 +- frontend/src/views/office/approvals/index.vue | 214 +++- frontend/src/views/office/handovers/index.vue | 67 +- .../src/views/office/notifications/index.vue | 88 +- frontend/src/views/register/index.vue | 183 +++ frontend/src/views/report/exports/index.vue | 88 +- frontend/src/views/report/overview/index.vue | 31 +- frontend/src/views/room/board/index.vue | 164 +++ .../src/views/room/call-records/index.vue | 177 +++ frontend/src/views/room/outings/index.vue | 200 ++++ .../src/views/room/reservations/index.vue | 49 +- frontend/src/views/room/room-types/index.vue | 45 +- frontend/src/views/room/rooms/index.vue | 63 +- .../src/views/service/executions/index.vue | 246 ++++ frontend/src/views/service/items/index.vue | 43 +- frontend/src/views/service/orders/index.vue | 47 +- frontend/src/views/service/packages/index.vue | 43 +- .../src/views/system/departments/index.vue | 105 +- .../src/views/system/dictionaries/index.vue | 152 ++- frontend/src/views/system/menus/index.vue | 168 +-- frontend/src/views/system/positions/index.vue | 105 +- .../system/registration-packages/index.vue | 232 ++++ frontend/src/views/system/roles/index.vue | 93 +- frontend/src/views/system/stores/index.vue | 166 ++- frontend/src/views/system/users/index.vue | 176 +-- frontend/tailwind.config.js | 20 + 105 files changed, 7859 insertions(+), 2469 deletions(-) create mode 100644 CLAUDE.md create mode 100644 backend/app/Http/Controllers/Admin/Inventory/InventoryCheckController.php create mode 100644 backend/app/Http/Controllers/Admin/Inventory/StockController.php create mode 100644 backend/app/Http/Controllers/Admin/Inventory/TransferOrderController.php create mode 100644 backend/app/Http/Controllers/Admin/System/RegistrationPackageController.php create mode 100644 backend/app/Models/Inventory/InventoryCheck.php create mode 100644 backend/app/Models/Inventory/TransferOrder.php create mode 100644 backend/app/Models/System/RegistrationPackage.php create mode 100644 backend/database/migrations/2026_03_14_000001_add_node_label_role_to_approval_nodes_table.php create mode 100644 backend/database/migrations/2026_03_14_000002_create_transfer_orders_table.php create mode 100644 backend/database/migrations/2026_03_14_000004_create_registration_packages_table.php create mode 100755 deploy.sh create mode 100644 docs/api-check-report.md create mode 100644 frontend/postcss.config.js create mode 100644 frontend/src/views/care/exceptions/index.vue create mode 100644 frontend/src/views/care/health-metrics/index.vue create mode 100644 frontend/src/views/crm/questionnaires/index.vue create mode 100644 frontend/src/views/finance/payments/index.vue create mode 100644 frontend/src/views/finance/transactions/index.vue create mode 100644 frontend/src/views/inventory/checks/index.vue create mode 100644 frontend/src/views/inventory/stock/index.vue create mode 100644 frontend/src/views/inventory/transfers/index.vue create mode 100644 frontend/src/views/meal/reviews/index.vue create mode 100644 frontend/src/views/meal/templates/index.vue create mode 100644 frontend/src/views/register/index.vue create mode 100644 frontend/src/views/room/board/index.vue create mode 100644 frontend/src/views/room/call-records/index.vue create mode 100644 frontend/src/views/room/outings/index.vue create mode 100644 frontend/src/views/service/executions/index.vue create mode 100644 frontend/src/views/system/registration-packages/index.vue create mode 100644 frontend/tailwind.config.js diff --git a/.gitignore b/.gitignore index c9334f9..ddc93d3 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,7 @@ Thumbs.db # Frontend frontend/node_modules/ -frontend/dist/ +# frontend/dist/ — tracked for zero-build server deployment # Backend (Laravel handles its own via backend/.gitignore) diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..09629ac --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,44 @@ +# CLAUDE.md — 月子 SaaS 项目上下文 + +## 部署信息 + +- **服务器**:`82.157.47.215`(root / Aiying88) +- **前端**:`/var/www/saas/frontend`(Vue 3 静态 dist) +- **后端**:`/var/www/saas/backend`(Laravel 12,PHP 8.3) +- **仓库镜像**:`/var/www/saas/repo`(用于 git pull) +- **Gitea 远程**:`git@git.20031227.de5.net:fengshao1227/yuezi-saas.git` + +## 手动更新服务器 + +本地运行一键脚本(构建前端 → git push → SSH 触发服务器 pull + migrate): + +```bash +./deploy.sh "feat: 描述变更" +``` + +只更新服务器(跳过本地构建和 push): + +```bash +ssh root@82.157.47.215 '/var/www/saas/deploy.sh' +``` + +查看部署日志: + +```bash +ssh root@82.157.47.215 'tail -50 /var/log/saas-deploy.log' +``` + +## 服务器 deploy.sh 做的事 + +1. `git pull origin main`(在 `/var/www/saas/repo`) +2. rsync backend → `/var/www/saas/backend`(排除 `.env` / `vendor`) +3. 如果 `composer.json` 有变动,执行 `composer install` +4. `php artisan migrate --force` + 清缓存 +5. rsync `frontend/dist/` → `/var/www/saas/frontend` + +## 关键配置 + +- 后端 `.env` 在服务器,不入库,手动维护 +- `bootstrap/app.php` 已移除 `statefulApi()`(用 Bearer Token 认证,无需 CSRF) +- Nginx 配置:`/etc/nginx/conf.d/saas.conf` +- PHP-FPM:`127.0.0.1:9000` diff --git a/backend/app/Http/Controllers/Admin/AuthController.php b/backend/app/Http/Controllers/Admin/AuthController.php index f8f3f95..f83579a 100644 --- a/backend/app/Http/Controllers/Admin/AuthController.php +++ b/backend/app/Http/Controllers/Admin/AuthController.php @@ -85,4 +85,62 @@ class AuthController extends Controller 'menus' => $user->getMenuTree(), ]); } + + /** + * 获取可用注册套餐列表(公开接口) + */ + public function registrationPackages(Request $request): JsonResponse + { + $storeId = $request->input('store_id'); + if (!$storeId) { + return $this->error('请选择门店', 42200); + } + + $packages = \App\Models\System\RegistrationPackage::withoutGlobalScope('store') + ->where('store_id', $storeId) + ->where('status', 1) + ->orderBy('sort') + ->get(['id', 'name', 'description']); + + return $this->success($packages); + } + + /** + * 自助注册(公开接口) + * 注册后 status=2(待审核),需管理员审批后才能登录 + */ + public function register(Request $request): JsonResponse + { + $data = $request->validate([ + 'store_id' => 'required|exists:stores,id', + 'registration_package_id' => 'required|exists:registration_packages,id', + 'username' => 'required|string|max:50|unique:users,username', + 'password' => 'required|string|min:6', + 'name' => 'required|string|max:50', + 'phone' => 'required|string|max:20', + ]); + + // 验证套餐属于该门店 + $package = \App\Models\System\RegistrationPackage::withoutGlobalScope('store') + ->where('id', $data['registration_package_id']) + ->where('store_id', $data['store_id']) + ->where('status', 1) + ->firstOrFail(); + + $user = \App\Models\User::create([ + 'store_id' => $data['store_id'], + 'username' => $data['username'], + 'password' => $data['password'], + 'name' => $data['name'], + 'phone' => $data['phone'], + 'status' => 2, // Pending + ]); + + // 绑定套餐角色 + if (!empty($package->role_ids)) { + $user->roles()->sync($package->role_ids); + } + + return $this->success(null, '注册成功,请等待管理员审核后登录'); + } } diff --git a/backend/app/Http/Controllers/Admin/Inventory/InventoryCheckController.php b/backend/app/Http/Controllers/Admin/Inventory/InventoryCheckController.php new file mode 100644 index 0000000..dbfb10c --- /dev/null +++ b/backend/app/Http/Controllers/Admin/Inventory/InventoryCheckController.php @@ -0,0 +1,80 @@ +with(['warehouse', 'operator']) + ->when($request->warehouse_id, fn($q, $v) => $q->where('warehouse_id', $v)) + ->when($request->has('status'), fn($q) => $q->where('status', $request->status)); + + return $this->paginate($query->latest()->paginate($request->input('per_page', 15))); + } + + public function store(Request $request): JsonResponse + { + $v = $request->validate([ + 'check_no' => 'required|string|max:50|unique:inventory_checks', + 'warehouse_id' => 'required|exists:warehouses,id', + 'items' => 'nullable|array', + 'remark' => 'nullable|string|max:500', + ]); + $v['status'] = 0; + $v['operator_id'] = auth()->id(); + + return $this->success(InventoryCheck::create($v)); + } + + public function show(InventoryCheck $inventoryCheck): JsonResponse + { + return $this->success($inventoryCheck->load(['warehouse', 'operator'])); + } + + /** + * 完成盘点 — 可选择按实际数量修正库存 + */ + public function complete(InventoryCheck $inventoryCheck, Request $request): JsonResponse + { + if ($inventoryCheck->status === 2) { + return $this->error('已完成', 40001); + } + + $v = $request->validate([ + 'items' => 'nullable|array', + 'finish_remark' => 'nullable|string|max:500', + ]); + + DB::transaction(function () use ($inventoryCheck, $v) { + $items = $v['items'] ?? $inventoryCheck->items ?? []; + + // 如果有差异且需要调整库存 + foreach ($items as $item) { + if (!isset($item['actual_qty'])) continue; + $diff = ($item['actual_qty'] ?? 0) - ($item['book_qty'] ?? 0); + if ($diff == 0) continue; + + $inv = Inventory::firstOrCreate( + ['warehouse_id' => $inventoryCheck->warehouse_id, 'material_id' => $item['material_id'], 'batch_no' => null], + ['quantity' => 0] + ); + $inv->increment('quantity', $diff); + } + + $inventoryCheck->update([ + 'status' => 2, + 'items' => $items, + 'finish_remark' => $v['finish_remark'] ?? null, + ]); + }); + + return $this->success($inventoryCheck->fresh(['warehouse'])); + } +} diff --git a/backend/app/Http/Controllers/Admin/Inventory/StockController.php b/backend/app/Http/Controllers/Admin/Inventory/StockController.php new file mode 100644 index 0000000..9d7473b --- /dev/null +++ b/backend/app/Http/Controllers/Admin/Inventory/StockController.php @@ -0,0 +1,50 @@ +with(['warehouse', 'material']) + ->when($request->warehouse_id, fn($q, $v) => $q->where('warehouse_id', $v)) + ->when($request->material_id, fn($q, $v) => $q->where('material_id', $v)) + ->when($request->low_stock, fn($q) => $q->whereRaw('quantity <= (SELECT min_stock FROM materials WHERE materials.id = inventories.material_id AND min_stock IS NOT NULL)')); + + return $this->paginate($query->paginate($request->input('per_page', 20))); + } + + /** + * 导出库存台账 + * GET /inventory/stock/export + */ + public function export(Request $request): JsonResponse + { + $rows = Inventory::with(['warehouse', 'material']) + ->when($request->warehouse_id, fn($q, $v) => $q->where('warehouse_id', $v)) + ->get() + ->map(fn($inv) => [ + 'warehouse' => $inv->warehouse?->name, + 'material_code' => $inv->material?->code, + 'material_name' => $inv->material?->name, + 'unit' => $inv->material?->unit, + 'quantity' => $inv->quantity, + 'batch_no' => $inv->batch_no, + 'expire_date' => $inv->expire_date?->toDateString(), + 'safety_stock' => $inv->material?->min_stock, + 'updated_at' => $inv->updated_at?->toDateTimeString(), + ]); + + return $this->success(['rows' => $rows, 'total' => $rows->count()]); + } +} diff --git a/backend/app/Http/Controllers/Admin/Inventory/TransferOrderController.php b/backend/app/Http/Controllers/Admin/Inventory/TransferOrderController.php new file mode 100644 index 0000000..3bab504 --- /dev/null +++ b/backend/app/Http/Controllers/Admin/Inventory/TransferOrderController.php @@ -0,0 +1,82 @@ +with(['fromWarehouse', 'toWarehouse', 'operator']) + ->when($request->transfer_no, fn($q, $v) => $q->where('transfer_no', 'like', "%{$v}%")) + ->when($request->has('status'), fn($q) => $q->where('status', $request->status)) + ->when($request->from_warehouse_id, fn($q, $v) => $q->where('from_warehouse_id', $v)); + + return $this->paginate($query->latest()->paginate($request->input('per_page', 15))); + } + + public function store(Request $request): JsonResponse + { + $v = $request->validate([ + 'transfer_no' => 'required|string|max:50|unique:transfer_orders', + 'from_warehouse_id' => 'required|exists:warehouses,id', + 'to_warehouse_id' => 'required|exists:warehouses,id|different:from_warehouse_id', + 'items' => 'nullable|array', + 'remark' => 'nullable|string|max:500', + ]); + $v['status'] = 0; + $v['operator_id'] = auth()->id(); + + return $this->success(TransferOrder::create($v)); + } + + public function show(TransferOrder $transferOrder): JsonResponse + { + return $this->success($transferOrder->load(['fromWarehouse', 'toWarehouse', 'operator'])); + } + + public function confirm(TransferOrder $transferOrder): JsonResponse + { + if ($transferOrder->status !== 0) { + return $this->error('当前状态不可确认', 40001); + } + + DB::transaction(function () use ($transferOrder) { + $transferOrder->update(['status' => 1]); + + // 调拨:减源仓库、加目标仓库 + foreach (($transferOrder->items ?? []) as $item) { + $materialId = $item['material_id']; + $qty = $item['quantity'] ?? 0; + + // 扣减出库仓库 + Inventory::where('warehouse_id', $transferOrder->from_warehouse_id) + ->where('material_id', $materialId) + ->decrement('quantity', $qty); + + // 增加入库仓库 + $inv = Inventory::firstOrCreate( + ['warehouse_id' => $transferOrder->to_warehouse_id, 'material_id' => $materialId, 'batch_no' => null], + ['quantity' => 0] + ); + $inv->increment('quantity', $qty); + } + }); + + return $this->success($transferOrder->fresh(['fromWarehouse', 'toWarehouse'])); + } + + public function cancel(TransferOrder $transferOrder): JsonResponse + { + if ($transferOrder->status !== 0) { + return $this->error('只有待确认状态可取消', 40001); + } + $transferOrder->update(['status' => 2]); + return $this->success($transferOrder); + } +} diff --git a/backend/app/Http/Controllers/Admin/Office/ApprovalController.php b/backend/app/Http/Controllers/Admin/Office/ApprovalController.php index f6d7505..1958e4b 100644 --- a/backend/app/Http/Controllers/Admin/Office/ApprovalController.php +++ b/backend/app/Http/Controllers/Admin/Office/ApprovalController.php @@ -4,8 +4,11 @@ namespace App\Http\Controllers\Admin\Office; use App\Http\Controllers\Controller; use App\Models\Office\Approval; +use App\Models\Office\ApprovalNode; +use App\Models\User; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Support\Facades\DB; class ApprovalController extends Controller { @@ -21,16 +24,59 @@ class ApprovalController extends Controller public function store(Request $request): JsonResponse { $validated = $request->validate([ - 'title' => 'required|string|max:255', - 'template_id' => 'nullable|exists:approval_templates,id', - 'form_data' => 'nullable|array', + 'title' => 'required|string|max:255', + 'template_id' => 'nullable|exists:approval_templates,id', + 'form_data' => 'nullable|array', 'related_type' => 'nullable|string|max:50', - 'related_id' => 'nullable|integer', + 'related_id' => 'nullable|integer', ]); - $validated['applicant_id'] = auth()->id(); - $validated['status'] = 0; - return $this->success(Approval::create($validated)); + $validated['applicant_id'] = auth()->id(); + $validated['status'] = 0; + $validated['current_node'] = 1; + + $approval = null; + + DB::transaction(function () use ($validated, &$approval) { + $approval = Approval::create($validated); + + // 根据模板 flow_nodes 自动创建多级审批节点 + if ($approval->template_id && $approval->template) { + $flowNodes = $approval->template->flow_nodes ?? []; + foreach ($flowNodes as $node) { + $level = $node['level'] ?? 1; + $role = $node['role'] ?? null; + $label = $node['label'] ?? "第{$level}级审批"; + + // 按 role 找同门店下首个匹配用户 + $approverId = null; + if ($role) { + $approverId = User::whereHas('roles', fn($q) => $q->where('slug', $role)) + ->where('store_id', $approval->store_id) + ->value('id'); + } + + ApprovalNode::create([ + 'approval_id' => $approval->id, + 'node_order' => $level, + 'node_label' => $label, + 'role' => $role, + 'approver_id' => $approverId, + 'action' => 0, + ]); + } + } else { + // 无模板:创建单一审批节点 + ApprovalNode::create([ + 'approval_id' => $approval->id, + 'node_order' => 1, + 'node_label' => '审批', + 'action' => 0, + ]); + } + }); + + return $this->success($approval->load(['template', 'applicant', 'nodes'])); } public function show(Approval $approval): JsonResponse @@ -44,7 +90,7 @@ class ApprovalController extends Controller return $this->error('该审批已完结,无法修改', 40001); } $validated = $request->validate([ - 'title' => 'sometimes|string|max:255', + 'title' => 'sometimes|string|max:255', 'form_data' => 'nullable|array', ]); $approval->update($validated); @@ -62,43 +108,28 @@ class ApprovalController extends Controller return $this->success(null); } - public function approve(Approval $approval): JsonResponse + public function approve(Request $request, Approval $approval): JsonResponse { + if ($approval->status >= 2) { + return $this->error('该审批已完结', 40001); + } + + $currentUser = auth()->user(); + $userRoles = $currentUser->roles()->pluck('slug')->toArray(); + + // 当前节点匹配:approver_id 精确匹配 或 role 匹配当前用户角色 $node = $approval->nodes() - ->where('approver_id', auth()->id()) - ->where('action', 0) ->where('node_order', $approval->current_node) + ->where('action', 0) + ->where(function ($q) use ($currentUser, $userRoles) { + $q->where('approver_id', $currentUser->id) + ->orWhereIn('role', $userRoles) + ->orWhereNull('approver_id'); // 无指定审批人时任何人可审 + }) ->first(); if (!$node) { - return $this->error('无待处理的审批节点', 40001); - } - - $node->update(['action' => 1, 'acted_at' => now()]); - - $pendingNodes = $approval->nodes()->where('action', 0)->exists(); - if (!$pendingNodes) { - $approval->update(['status' => 2]); - } else { - $approval->update([ - 'status' => 1, - 'current_node' => $approval->current_node + 1, - ]); - } - - return $this->success($approval->load('nodes')); - } - - public function reject(Request $request, Approval $approval): JsonResponse - { - $node = $approval->nodes() - ->where('approver_id', auth()->id()) - ->where('action', 0) - ->where('node_order', $approval->current_node) - ->first(); - - if (!$node) { - return $this->error('无待处理的审批节点', 40001); + return $this->error('无待处理的审批节点或您无权审批此节点', 40001); } $validated = $request->validate([ @@ -106,13 +137,67 @@ class ApprovalController extends Controller ]); $node->update([ - 'action' => 2, - 'comment' => $validated['comment'] ?? null, - 'acted_at' => now(), + 'action' => 1, + 'approver_id' => $currentUser->id, + 'comment' => $validated['comment'] ?? null, + 'acted_at' => now(), ]); - $approval->update(['status' => 3]); - return $this->success($approval->load('nodes')); + // 检查是否还有下一节点 + $hasNext = $approval->nodes() + ->where('node_order', '>', $approval->current_node) + ->where('action', 0) + ->exists(); + + if ($hasNext) { + $approval->update([ + 'status' => 1, // 审批中 + 'current_node' => $approval->current_node + 1, + ]); + } else { + $approval->update(['status' => 2]); // 已通过 + } + + return $this->success($approval->load(['nodes.approver'])); + } + + public function reject(Request $request, Approval $approval): JsonResponse + { + if ($approval->status >= 2) { + return $this->error('该审批已完结', 40001); + } + + $currentUser = auth()->user(); + $userRoles = $currentUser->roles()->pluck('slug')->toArray(); + + $node = $approval->nodes() + ->where('node_order', $approval->current_node) + ->where('action', 0) + ->where(function ($q) use ($currentUser, $userRoles) { + $q->where('approver_id', $currentUser->id) + ->orWhereIn('role', $userRoles) + ->orWhereNull('approver_id'); + }) + ->first(); + + if (!$node) { + return $this->error('无待处理的审批节点或您无权操作', 40001); + } + + $validated = $request->validate([ + 'comment' => 'nullable|string|max:255', + ]); + + $node->update([ + 'action' => 2, + 'approver_id' => $currentUser->id, + 'comment' => $validated['comment'] ?? null, + 'acted_at' => now(), + ]); + + $approval->update(['status' => 3]); // 已驳回 + + return $this->success($approval->load(['nodes.approver'])); } public function withdraw(Approval $approval): JsonResponse @@ -120,6 +205,9 @@ class ApprovalController extends Controller if ($approval->status > 1) { return $this->error('该审批已完结,无法撤回', 40001); } + if ($approval->applicant_id !== auth()->id()) { + return $this->error('只能撤回自己发起的审批', 40001); + } $approval->update(['status' => 4]); return $this->success($approval); diff --git a/backend/app/Http/Controllers/Admin/System/RegistrationPackageController.php b/backend/app/Http/Controllers/Admin/System/RegistrationPackageController.php new file mode 100644 index 0000000..5ef6325 --- /dev/null +++ b/backend/app/Http/Controllers/Admin/System/RegistrationPackageController.php @@ -0,0 +1,66 @@ +keyword, fn($q) => + $q->where('name', 'like', "%{$request->keyword}%")) + ->when($request->has('status'), fn($q) => + $q->where('status', $request->status)) + ->orderBy('sort')->orderByDesc('id') + ->paginate($request->input('page_size', 20)); + + return $this->paginate($packages); + } + + public function store(Request $request): JsonResponse + { + $data = $request->validate([ + 'name' => 'required|string|max:50', + 'description' => 'nullable|string|max:255', + 'role_ids' => 'array', + 'role_ids.*' => 'exists:roles,id', + 'status' => 'in:0,1', + 'sort' => 'integer|min:0', + ]); + + $data['store_id'] = auth()->user()->store_id; + $package = RegistrationPackage::create($data); + + return $this->success($package, '创建成功'); + } + + public function show(RegistrationPackage $registrationPackage): JsonResponse + { + return $this->success($registrationPackage); + } + + public function update(Request $request, RegistrationPackage $registrationPackage): JsonResponse + { + $data = $request->validate([ + 'name' => 'string|max:50', + 'description' => 'nullable|string|max:255', + 'role_ids' => 'array', + 'role_ids.*' => 'exists:roles,id', + 'status' => 'in:0,1', + 'sort' => 'integer|min:0', + ]); + + $registrationPackage->update($data); + return $this->success($registrationPackage, '更新成功'); + } + + public function destroy(RegistrationPackage $registrationPackage): JsonResponse + { + $registrationPackage->delete(); + return $this->success(null, '删除成功'); + } +} diff --git a/backend/app/Http/Controllers/Admin/System/StoreController.php b/backend/app/Http/Controllers/Admin/System/StoreController.php index d866fe8..09ecb6b 100644 --- a/backend/app/Http/Controllers/Admin/System/StoreController.php +++ b/backend/app/Http/Controllers/Admin/System/StoreController.php @@ -83,4 +83,16 @@ class StoreController extends Controller $store->delete(); return $this->success(null, '删除成功'); } + + /** + * 公开门店列表(注册页用,仅返回启用门店的 id/name) + */ + public function publicList(): JsonResponse + { + $stores = Store::withoutGlobalScope('store') + ->where('status', 1) + ->orderByDesc('id') + ->get(['id', 'name']); + return $this->success($stores); + } } diff --git a/backend/app/Http/Controllers/Admin/System/UserController.php b/backend/app/Http/Controllers/Admin/System/UserController.php index ce3777d..6b40fc8 100644 --- a/backend/app/Http/Controllers/Admin/System/UserController.php +++ b/backend/app/Http/Controllers/Admin/System/UserController.php @@ -112,6 +112,30 @@ class UserController extends Controller return $this->success(null, '密码重置成功'); } + /** + * 审核通过 — 激活待审核用户 + */ + public function approve(User $user): JsonResponse + { + if ($user->status->value !== 2) { + return $this->error('该用户不在待审核状态'); + } + $user->update(['status' => 1]); + return $this->success(null, '已审核通过'); + } + + /** + * 审核拒绝 — 禁用该用户 + */ + public function reject(User $user): JsonResponse + { + if ($user->status->value !== 2) { + return $this->error('该用户不在待审核状态'); + } + $user->update(['status' => 0]); + return $this->success(null, '已拒绝注册'); + } + public function destroy(User $user): JsonResponse { if ($user->is_super) { diff --git a/backend/app/Models/Inventory/InventoryCheck.php b/backend/app/Models/Inventory/InventoryCheck.php new file mode 100644 index 0000000..a1cafda --- /dev/null +++ b/backend/app/Models/Inventory/InventoryCheck.php @@ -0,0 +1,16 @@ +'array','status'=>'integer']; } + public function warehouse(): BelongsTo { return $this->belongsTo(Warehouse::class); } + public function operator(): BelongsTo { return $this->belongsTo(User::class, 'operator_id'); } +} diff --git a/backend/app/Models/Inventory/TransferOrder.php b/backend/app/Models/Inventory/TransferOrder.php new file mode 100644 index 0000000..851287f --- /dev/null +++ b/backend/app/Models/Inventory/TransferOrder.php @@ -0,0 +1,17 @@ +'array','status'=>'integer']; } + public function fromWarehouse(): BelongsTo { return $this->belongsTo(Warehouse::class, 'from_warehouse_id'); } + public function toWarehouse(): BelongsTo { return $this->belongsTo(Warehouse::class, 'to_warehouse_id'); } + public function operator(): BelongsTo { return $this->belongsTo(User::class, 'operator_id'); } +} diff --git a/backend/app/Models/Office/ApprovalNode.php b/backend/app/Models/Office/ApprovalNode.php index 03d0084..e3ef553 100644 --- a/backend/app/Models/Office/ApprovalNode.php +++ b/backend/app/Models/Office/ApprovalNode.php @@ -11,7 +11,8 @@ class ApprovalNode extends Model const UPDATED_AT = null; protected $fillable = [ - 'approval_id', 'node_order', 'approver_id', 'action', 'comment', 'acted_at', + 'approval_id', 'node_order', 'node_label', 'role', + 'approver_id', 'action', 'comment', 'acted_at', ]; protected function casts(): array diff --git a/backend/app/Models/System/RegistrationPackage.php b/backend/app/Models/System/RegistrationPackage.php new file mode 100644 index 0000000..9964f1a --- /dev/null +++ b/backend/app/Models/System/RegistrationPackage.php @@ -0,0 +1,26 @@ + 'array', + 'status' => 'integer', + 'sort' => 'integer', + ]; + } +} diff --git a/backend/bootstrap/app.php b/backend/bootstrap/app.php index 13522ca..fe8cf5d 100644 --- a/backend/bootstrap/app.php +++ b/backend/bootstrap/app.php @@ -19,7 +19,7 @@ return Application::configure(basePath: dirname(__DIR__)) 'oplog' => \App\Http\Middleware\OperationLog::class, ]); - $middleware->statefulApi(); + // Removed statefulApi() — app uses Bearer token auth, not cookie SPA auth }) ->withExceptions(function (Exceptions $exceptions): void { // diff --git a/backend/database/migrations/2026_03_14_000001_add_node_label_role_to_approval_nodes_table.php b/backend/database/migrations/2026_03_14_000001_add_node_label_role_to_approval_nodes_table.php new file mode 100644 index 0000000..ba49200 --- /dev/null +++ b/backend/database/migrations/2026_03_14_000001_add_node_label_role_to_approval_nodes_table.php @@ -0,0 +1,25 @@ +string('node_label', 100)->nullable()->after('node_order')->comment('节点名称'); + $table->string('role', 50)->nullable()->after('node_label')->comment('节点角色标识'); + // approver_id 改为可空(无模板时无法预指定审批人) + $table->unsignedBigInteger('approver_id')->nullable()->change(); + }); + } + + public function down(): void + { + Schema::table('approval_nodes', function (Blueprint $table) { + $table->dropColumn(['node_label', 'role']); + }); + } +}; diff --git a/backend/database/migrations/2026_03_14_000002_create_transfer_orders_table.php b/backend/database/migrations/2026_03_14_000002_create_transfer_orders_table.php new file mode 100644 index 0000000..2683de5 --- /dev/null +++ b/backend/database/migrations/2026_03_14_000002_create_transfer_orders_table.php @@ -0,0 +1,47 @@ +id(); + $table->unsignedBigInteger('store_id')->index(); + $table->string('transfer_no', 50)->unique(); + $table->unsignedBigInteger('from_warehouse_id'); + $table->unsignedBigInteger('to_warehouse_id'); + $table->json('items')->nullable(); + $table->tinyInteger('status')->default(0)->comment('0待确认1已确认2已取消'); + $table->unsignedBigInteger('operator_id')->nullable(); + $table->string('remark', 500)->nullable(); + $table->timestamps(); + + $table->foreign('from_warehouse_id')->references('id')->on('warehouses'); + $table->foreign('to_warehouse_id')->references('id')->on('warehouses'); + }); + + Schema::create('inventory_checks', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('store_id')->index(); + $table->string('check_no', 50)->unique(); + $table->unsignedBigInteger('warehouse_id'); + $table->json('items')->nullable()->comment('[{material_id,book_qty,actual_qty,diff_qty,remark}]'); + $table->tinyInteger('status')->default(0)->comment('0草稿1盘点中2已完成'); + $table->string('finish_remark', 500)->nullable(); + $table->unsignedBigInteger('operator_id')->nullable(); + $table->timestamps(); + + $table->foreign('warehouse_id')->references('id')->on('warehouses'); + }); + } + + public function down(): void + { + Schema::dropIfExists('inventory_checks'); + Schema::dropIfExists('transfer_orders'); + } +}; diff --git a/backend/database/migrations/2026_03_14_000004_create_registration_packages_table.php b/backend/database/migrations/2026_03_14_000004_create_registration_packages_table.php new file mode 100644 index 0000000..3837f18 --- /dev/null +++ b/backend/database/migrations/2026_03_14_000004_create_registration_packages_table.php @@ -0,0 +1,29 @@ +id(); + $table->foreignId('store_id')->constrained()->comment('所属门店'); + $table->string('name', 50)->comment('套餐名称,如:销售、店长'); + $table->string('description', 255)->nullable()->comment('套餐说明'); + $table->json('role_ids')->nullable()->comment('关联角色ID数组'); + $table->tinyInteger('status')->default(1)->comment('1启用 0停用'); + $table->unsignedSmallInteger('sort')->default(0)->comment('排序'); + $table->timestamps(); + + $table->index('store_id'); + }); + } + + public function down(): void + { + Schema::dropIfExists('registration_packages'); + } +}; diff --git a/backend/routes/api.php b/backend/routes/api.php index fb575e3..07786f4 100644 --- a/backend/routes/api.php +++ b/backend/routes/api.php @@ -44,6 +44,9 @@ use App\Http\Controllers\Admin\Inventory\MaterialController; use App\Http\Controllers\Admin\Inventory\PurchaseOrderController; use App\Http\Controllers\Admin\Inventory\StockMovementController; use App\Http\Controllers\Admin\Inventory\InventoryController; +use App\Http\Controllers\Admin\Inventory\StockController; +use App\Http\Controllers\Admin\Inventory\TransferOrderController; +use App\Http\Controllers\Admin\Inventory\InventoryCheckController; use App\Http\Controllers\Admin\Finance\FinanceCategoryController; use App\Http\Controllers\Admin\Finance\FinanceRecordController; use App\Http\Controllers\Admin\Finance\CustomerAccountController; @@ -64,6 +67,7 @@ use App\Http\Controllers\Admin\Report\ReportController; use App\Http\Controllers\Admin\Report\ReportExportController; use App\Http\Controllers\Admin\Kb\KbCategoryController; use App\Http\Controllers\Admin\Kb\KbArticleController; +use App\Http\Controllers\Admin\System\RegistrationPackageController; /* |-------------------------------------------------------------------------- @@ -74,6 +78,9 @@ use App\Http\Controllers\Admin\Kb\KbArticleController; // ===== 公开路由(无需认证) ===== Route::post('/auth/login', [AuthController::class, 'login']); +Route::post('/auth/register', [AuthController::class, 'register']); +Route::get('/auth/register/packages', [AuthController::class, 'registrationPackages']); +Route::get('/auth/stores', [\App\Http\Controllers\Admin\System\StoreController::class, 'publicList']); // ===== 需要认证的路由 ===== Route::middleware(['auth:sanctum', 'store', 'oplog'])->group(function () { @@ -98,6 +105,11 @@ Route::middleware(['auth:sanctum', 'store', 'oplog'])->group(function () { Route::apiResource('users', UserController::class); Route::put('users/{user}/status', [UserController::class, 'updateStatus']); Route::put('users/{user}/password', [UserController::class, 'resetPassword']); + Route::put('users/{user}/approve', [UserController::class, 'approve']); + Route::put('users/{user}/reject', [UserController::class, 'reject']); + + // 注册套餐管理 + Route::apiResource('registration-packages', RegistrationPackageController::class); // 角色管理 Route::apiResource('roles', RoleController::class); @@ -264,8 +276,25 @@ Route::middleware(['auth:sanctum', 'store', 'oplog'])->group(function () { Route::get('stock-movements/{stockMovement}', [StockMovementController::class, 'show']); Route::put('stock-movements/{stockMovement}/confirm', [StockMovementController::class, 'confirm']); - // 库存查询 + // 库存查询(按物料/仓库汇总) Route::get('inventories', [InventoryController::class, 'index']); + + // 库存台账 + Route::get('stock', [StockController::class, 'index']); + Route::get('stock/export', [StockController::class, 'export']); + + // 调拨管理 + Route::get('transfers', [TransferOrderController::class, 'index']); + Route::post('transfers', [TransferOrderController::class, 'store']); + Route::get('transfers/{transferOrder}', [TransferOrderController::class, 'show']); + Route::put('transfers/{transferOrder}/confirm', [TransferOrderController::class, 'confirm']); + Route::put('transfers/{transferOrder}/cancel', [TransferOrderController::class, 'cancel']); + + // 盘点管理 + Route::get('checks', [InventoryCheckController::class, 'index']); + Route::post('checks', [InventoryCheckController::class, 'store']); + Route::get('checks/{inventoryCheck}', [InventoryCheckController::class, 'show']); + Route::put('checks/{inventoryCheck}/complete', [InventoryCheckController::class, 'complete']); }); // --- 财务管理模块 --- diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..69abb59 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# 月子 SaaS 一键部署 +# 用法:./deploy.sh [commit message] +set -e + +SERVER="root@82.157.47.215" +COMMIT_MSG="${1:-deploy: update}" +ROOT_DIR="$(cd "$(dirname "$0")" && pwd)" + +echo "▶ 构建前端..." +cd "$ROOT_DIR/frontend" +npm run build + +echo "▶ 提交变更(含 dist)..." +cd "$ROOT_DIR" +git add -A +git diff --cached --quiet && echo " 无变更,跳过提交" || git commit -m "$COMMIT_MSG" + +echo "▶ 推送到远程..." +git push origin main + +echo "▶ 通知服务器更新..." +ssh "$SERVER" '/var/www/saas/deploy.sh' + +echo "" +echo "✅ 部署完成!" diff --git a/docs/api-check-report.md b/docs/api-check-report.md new file mode 100644 index 0000000..0104295 --- /dev/null +++ b/docs/api-check-report.md @@ -0,0 +1,121 @@ +# 前后端接口联调检查报告 + +> 生成时间:2026-03-14 +> 检查范围:前端 `src/api/*.js` ↔ 后端 `routes/api.php` + +--- + +## 总体评估 + +| 模块 | 状态 | 备注 | +|------|------|------| +| 系统设置 | ✅ 完全匹配 | | +| CRM | ✅ 完全匹配 | | +| 房务 Room | ✅ 完全匹配 | | +| 护理 Care | ✅ 已修复 | resolve→handle别名修复;healthMetric.delete移除 | +| 月子餐 Meal | ⚠️ 部分匹配 | getDetail不存在后端路由(见下) | +| 服务产康 Service | ✅ 已修复 | complete方法移除,统一用finish | +| 月嫂 Nanny | ✅ 完全匹配 | | +| 进销存 Inventory | ✅ 已修复 | stock/transfers/checks 3个Controller已创建,迁移已执行 | +| 财务 Finance | ✅ 已修复 | paymentApi.confirm→audit | +| 人事 HR | ✅ 完全匹配 | | +| 办公协同 Office | ✅ 完全匹配 | 审批流引擎已升级 | +| 报表 Report | ✅ 完全匹配 | | +| 知识库 KB | ✅ 完全匹配 | | + +--- + +## 已修复问题 + +### ✅ care.js — careExceptionApi.resolve 路径修复 +- **问题**:前端调用 `PUT /care/exceptions/{id}/resolve`,后端只有 `/handle` +- **修复**:将 `resolve` 方法改为指向 `/handle` 的别名 + +### ✅ care.js — healthMetricApi.delete 移除 +- **问题**:前端调用 `DELETE /care/health-metrics/{id}`,后端无此路由 +- **修复**:移除该方法(健康指标不支持前端删除) + +### ✅ finance.js — paymentApi.confirm 改名 +- **问题**:前端调用 `PUT /finance/records/{id}/confirm`,后端路由为 `/audit` +- **修复**:方法改为 `audit`,路径对齐 + +### ✅ service.js — serviceExecutionApi.complete 移除 +- **问题**:前端有 `complete` 方法调用 `/complete`,后端无此路由(只有 `/finish`) +- **修复**:移除 `complete`,使用 `finish` 统一 + +--- + +## 已补充后端(2026-03-14 完成) + +### ✅ inventory — 库存台账 (stock) +- `StockController` 已创建,基于 `inventories` 表聚合,支持 `low_stock` 过滤 +- 路由:`GET /inventory/stock`, `GET /inventory/stock/export` + +### ✅ inventory — 调拨管理 (transfers) +- `TransferOrderController` 已创建,`transfer_orders` 表已迁移 +- confirm 时自动更新双仓库库存(事务保护) +- 路由:GET/POST/GET{id}/confirm/cancel + +### ✅ inventory — 盘点管理 (checks) +- `InventoryCheckController` 已创建,`inventory_checks` 表已迁移 +- complete 时按实际数量修正 inventories 表(事务保护) +- 路由:GET/POST/GET{id}/complete + +--- + +## 其他注意事项 + +### meal.js — mealReviewApi.getDetail +- 后端 `/meal/reviews` 只有 `GET list` 和 `POST create`,无 `GET {id}` +- 前端 `getDetail` 方法存在但暂时用不到(reviews 页面未调用),低优先级 + +### request.js baseURL +- 配置 `baseURL: '/api/v1'` ✅ 与后端路由前缀一致 + +### 认证 +- 所有请求通过 `Authorization: Bearer {token}` 传递,与 Sanctum 配置一致 ✅ + +--- + +## 全链路修复记录(2026-03-14) + +### P0 — 运行时必崩(已修复) +| 文件 | 问题 | 修复 | +|------|------|------| +| `views/finance/payments/index.vue` | `paymentApi.confirm` 不存在 | 改为 `paymentApi.audit` | +| `views/service/executions/index.vue` | `serviceExecutionApi.complete` 不存在 | 改为 `serviceExecutionApi.finish` | +| `views/meal/reviews/index.vue` | `mealReviewApi.getDetail` 不存在且后端无路由 | 直接用行数据 `row` 展示详情 | +| `views/care/health-metrics/index.vue` | `healthMetricApi.delete` 不存在 | 改为本地 filter 移除 | + +### P1 — 系统模块整体404(已修复) +| 文件 | 问题 | 修复 | +|------|------|------| +| `api/system.js` - `dictApi` | 全部使用 `/dict-types`,后端路由是 `/dictionaries` | 重写 dictApi 路径,item 操作签名适配 | +| `api/system.js` - `userApi.resetPassword` | POST `/reset-password`,后端是 PUT `/password` | 改为 `put` + 正确路径 | +| `api/system.js` - `userApi.updateStatus` | PATCH,后端是 PUT | 改为 `put` | +| `api/system.js` - `roleApi.assignPermissions` | POST,后端是 PUT | 改为 `put` | +| `api/system.js` - `roleApi.getPermissions` | 后端无此子路由 | 删除此方法 | + +### P2 — except(['show']) 导致404(已修复) +| 文件 | 问题 | 修复 | +|------|------|------| +| `api/system.js` - `departmentApi` | `getDetail`/`getTree` 后端无路由 | 删除;页面改用行数据 + `getList`+本地buildTree | +| `api/system.js` - `positionApi` | `getDetail` 后端无路由 | 删除;页面改用行数据 | +| `api/system.js` - `menuApi` | `getDetail`/`getTree` 后端无路由 | 删除;页面改用行数据 + `getList`+本地buildTree | +| `api/system.js` - `permissionApi` | `getDetail`/`getTree` 后端无路由 | 删除;roles页改用 `getList`+本地buildTree | +| `api/system.js` - `logApi` | `getDetail` 后端无路由 | 删除此方法 | + +### 页面联动修复 +- `views/system/departments/index.vue`: `getTree` → `getList`+`buildTree`;`getDetail` → 用行数据 +- `views/system/menus/index.vue`: `getTree` → `getList`+`buildTree`;`getDetail` → 用行数据 +- `views/system/positions/index.vue`: `getDetail` → 用行数据 +- `views/system/users/index.vue`: `departmentApi.getTree` → `getList`(平铺列表) +- `views/system/roles/index.vue`: `permissionApi.getTree` → `getList`+`buildPermTree`;`getPermissions` → `getDetail.permissions` +- `views/system/dictionaries/index.vue`: 适配新 dictApi 签名(路径/参数全部对齐) + +| 优先级 | 任务 | 状态 | +|--------|------|------| +| P1 | 执行 migration: `php artisan migrate`(审批节点字段扩展) | ✅ 已完成 | +| P2 | 创建 inventory transfers/checks 后端 Controller + Migration | ✅ 已完成 | +| P3 | 完善 inventory stock 聚合查询接口 | ✅ 已完成 | +| P4 | 补充 meal/reviews/{id} 详情接口(低优) | ⏳ 待处理 | diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 8ec8f05..0a42245 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "@element-plus/icons-vue": "^2.3.2", + "@iconify/vue": "^5.0.0", "axios": "^1.13.6", "element-plus": "^2.13.5", "nprogress": "^0.2.0", @@ -18,10 +19,26 @@ }, "devDependencies": { "@vitejs/plugin-vue": "^6.0.5", + "autoprefixer": "^10.4.27", + "postcss": "^8.5.8", "sass": "^1.98.0", + "tailwindcss": "^3.4.19", "vite": "^8.0.0" } }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", @@ -145,12 +162,65 @@ "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", "license": "MIT" }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "license": "MIT" + }, + "node_modules/@iconify/vue": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@iconify/vue/-/vue-5.0.0.tgz", + "integrity": "sha512-C+KuEWIF5nSBrobFJhT//JS87OZ++QDORB6f2q2Wm6fl2mueSTpFBeBsveK0KW9hWiZ4mNiPjsh6Zs4jjdROSg==", + "license": "MIT", + "dependencies": { + "@iconify/types": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/cyberalien" + }, + "peerDependencies": { + "vue": ">=3" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "license": "MIT" }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", @@ -168,6 +238,44 @@ "url": "https://github.com/sponsors/Brooooooklyn" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@oxc-project/runtime": { "version": "0.115.0", "resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.115.0.tgz", @@ -989,6 +1097,47 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, "node_modules/async-validator": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", @@ -1001,6 +1150,43 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, + "node_modules/autoprefixer": { + "version": "10.4.27", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", + "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001774", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/axios": { "version": "1.13.6", "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", @@ -1012,6 +1198,32 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.7", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.7.tgz", + "integrity": "sha512-1ghYO3HnxGec0TCGBXiDLVns4eCSx4zJpxnHrlqFQajmhfKMQBzUGDdkMK7fUW7PTHTeLf+j87aTuKuuwWzMGw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/birpc": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", @@ -1021,6 +1233,53 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -1034,6 +1293,37 @@ "node": ">= 0.4" } }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001778", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001778.tgz", + "integrity": "sha512-PN7uxFL+ExFJO61aVmP1aIEG4i9whQd4eoSCebav62UwDyp5OHh06zN4jqKSMePVgxHifCw1QJxdRkA1Pisekg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -1062,6 +1352,16 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/copy-anything": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", @@ -1077,6 +1377,19 @@ "url": "https://github.com/sponsors/mesqueeb" } }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -1108,6 +1421,20 @@ "node": ">=8" } }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1122,6 +1449,13 @@ "node": ">= 0.4" } }, + "node_modules/electron-to-chromium": { + "version": "1.5.313", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.313.tgz", + "integrity": "sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==", + "dev": true, + "license": "ISC" + }, "node_modules/element-plus": { "version": "2.13.5", "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.13.5.tgz", @@ -1204,12 +1538,62 @@ "node": ">= 0.4" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "license": "MIT" }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -1228,6 +1612,19 @@ } } }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/follow-redirects": { "version": "1.15.11", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", @@ -1264,6 +1661,20 @@ "node": ">= 6" } }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1325,6 +1736,19 @@ "node": ">= 0.4" } }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -1389,13 +1813,41 @@ "dev": true, "license": "MIT" }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "license": "MIT", - "optional": true, "engines": { "node": ">=0.10.0" } @@ -1406,7 +1858,6 @@ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -1414,6 +1865,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/is-what": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", @@ -1426,6 +1887,16 @@ "url": "https://github.com/sponsors/mesqueeb" } }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, "node_modules/lightningcss": { "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", @@ -1687,6 +2158,26 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash": { "version": "4.17.23", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", @@ -1734,6 +2225,43 @@ "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", "license": "MIT" }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -1761,6 +2289,18 @@ "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", "license": "MIT" }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -1787,6 +2327,23 @@ "license": "MIT", "optional": true }, + "node_modules/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/normalize-wheel-es": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", @@ -1799,6 +2356,33 @@ "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==", "license": "MIT" }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, "node_modules/perfect-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", @@ -1824,6 +2408,16 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/pinia": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.4.tgz", @@ -1845,6 +2439,16 @@ } } }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/postcss": { "version": "8.5.8", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", @@ -1873,12 +2477,177 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/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" + } + ], + "license": "MIT" + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -1893,6 +2662,38 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rfdc": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", @@ -1940,6 +2741,30 @@ "dev": true, "license": "MIT" }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/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" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/sass": { "version": "1.98.0", "resolved": "https://registry.npmjs.org/sass/-/sass-1.98.0.tgz", @@ -1979,6 +2804,29 @@ "node": ">=0.10.0" } }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/superjson": { "version": "2.2.6", "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", @@ -1991,6 +2839,144 @@ "node": ">=16" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/tailwindcss/node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tailwindcss/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tailwindcss/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -2008,6 +2994,26 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -2016,6 +3022,44 @@ "license": "0BSD", "optional": true }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, "node_modules/vite": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index afb8357..624eb13 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@element-plus/icons-vue": "^2.3.2", + "@iconify/vue": "^5.0.0", "axios": "^1.13.6", "element-plus": "^2.13.5", "nprogress": "^0.2.0", @@ -19,7 +20,10 @@ }, "devDependencies": { "@vitejs/plugin-vue": "^6.0.5", + "autoprefixer": "^10.4.27", + "postcss": "^8.5.8", "sass": "^1.98.0", + "tailwindcss": "^3.4.19", "vite": "^8.0.0" } } diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js new file mode 100644 index 0000000..2e7af2b --- /dev/null +++ b/frontend/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/frontend/src/api/care.js b/frontend/src/api/care.js index fd06fc7..c04e8c1 100644 --- a/frontend/src/api/care.js +++ b/frontend/src/api/care.js @@ -33,11 +33,13 @@ export const careExceptionApi = { getDetail: (id) => request.get(`/care/exceptions/${id}`), create: (data) => request.post('/care/exceptions', data), update: (id, data) => request.put(`/care/exceptions/${id}`, data), - handle: (id, data) => request.put(`/care/exceptions/${id}/handle`, data) + handle: (id, data) => request.put(`/care/exceptions/${id}/handle`, data), + resolve: (id, data) => request.put(`/care/exceptions/${id}/handle`, data) // alias → handle } // ─── Health Metrics ─────────────────────────────────────────────────────────── export const healthMetricApi = { getList: (params) => request.get('/care/health-metrics', { params }), create: (data) => request.post('/care/health-metrics', data) + // Note: backend has no DELETE route; remove locally only } diff --git a/frontend/src/api/finance.js b/frontend/src/api/finance.js index 2092551..28b57b8 100644 --- a/frontend/src/api/finance.js +++ b/frontend/src/api/finance.js @@ -48,3 +48,10 @@ export const invoiceApi = { delete: (id) => request.delete(`/finance/invoices/${id}`), issue: (id) => request.put(`/finance/invoices/${id}/issue`) } + +// ─── Payment Records ────────────────────────────────────────────────────────── +export const paymentApi = { + getList: (params) => request.get('/finance/records', { params: { type: 'payment', ...params } }), + create: (data) => request.post('/finance/records', data), + audit: (id, data) => request.put(`/finance/records/${id}/audit`, data) // was: confirm → audit +} diff --git a/frontend/src/api/inventory.js b/frontend/src/api/inventory.js index 57deb32..58be4e7 100644 --- a/frontend/src/api/inventory.js +++ b/frontend/src/api/inventory.js @@ -49,3 +49,26 @@ export const stockMovementApi = { export const inventoryApi = { getList: (params) => request.get('/inventory/inventories', { params }) } + +// ─── Stock Query ───────────────────────────────────────────────────────────── +export const stockApi = { + getList: (params) => request.get('/inventory/stock', { params }), + export: (params) => request.get('/inventory/stock/export', { params, responseType: 'blob' }) +} + +// ─── Transfer Orders ───────────────────────────────────────────────────────── +export const transferApi = { + getList: (params) => request.get('/inventory/transfers', { params }), + getDetail: (id) => request.get(`/inventory/transfers/${id}`), + create: (data) => request.post('/inventory/transfers', data), + confirm: (id) => request.put(`/inventory/transfers/${id}/confirm`), + cancel: (id) => request.put(`/inventory/transfers/${id}/cancel`) +} + +// ─── Inventory Checks ──────────────────────────────────────────────────────── +export const inventoryCheckApi = { + getList: (params) => request.get('/inventory/checks', { params }), + getDetail: (id) => request.get(`/inventory/checks/${id}`), + create: (data) => request.post('/inventory/checks', data), + complete: (id, data) => request.put(`/inventory/checks/${id}/complete`, data) +} diff --git a/frontend/src/api/meal.js b/frontend/src/api/meal.js index e17aee0..015c79e 100644 --- a/frontend/src/api/meal.js +++ b/frontend/src/api/meal.js @@ -32,5 +32,6 @@ export const dailyMealPlanApi = { // ─── Meal Reviews ───────────────────────────────────────────────────────────── export const mealReviewApi = { getList: (params) => request.get('/meal/reviews', { params }), + getDetail: (id) => request.get(`/meal/reviews/${id}`), create: (data) => request.post('/meal/reviews', data) } diff --git a/frontend/src/api/service.js b/frontend/src/api/service.js index df16f85..7a9c9a0 100644 --- a/frontend/src/api/service.js +++ b/frontend/src/api/service.js @@ -35,4 +35,5 @@ export const serviceExecutionApi = { create: (data) => request.post('/service/executions', data), start: (id) => request.put(`/service/executions/${id}/start`), finish: (id) => request.put(`/service/executions/${id}/finish`) + // complete → finish (backend only has /finish, removed duplicate) } diff --git a/frontend/src/api/system.js b/frontend/src/api/system.js index 6b78b80..a5eea3c 100644 --- a/frontend/src/api/system.js +++ b/frontend/src/api/system.js @@ -10,28 +10,27 @@ export const storeApi = { delete: (id) => request.delete(`/system/stores/${id}`) } -// ─── Departments ──────────────────────────────────────────────────────────── +// ─── Departments ───────────────────────────────────────────────────────────── +// 后端 except(['show']),无 getDetail/getTree 路由 export const departmentApi = { getList: (params) => request.get('/system/departments', { params }), - getDetail: (id) => request.get(`/system/departments/${id}`), create: (data) => request.post('/system/departments', data), update: (id, data) => request.put(`/system/departments/${id}`, data), - delete: (id) => request.delete(`/system/departments/${id}`), - getTree: () => request.get('/system/departments/tree') + delete: (id) => request.delete(`/system/departments/${id}`) } -// ─── Positions ────────────────────────────────────────────────────────────── +// ─── Positions ─────────────────────────────────────────────────────────────── +// 后端 except(['show']),无 getDetail 路由 export const positionApi = { getList: (params) => request.get('/system/positions', { params }), - getDetail: (id) => request.get(`/system/positions/${id}`), create: (data) => request.post('/system/positions', data), update: (id, data) => request.put(`/system/positions/${id}`, data), delete: (id) => request.delete(`/system/positions/${id}`) } -// ─── Users ────────────────────────────────────────────────────────────────── +// ─── Users ─────────────────────────────────────────────────────────────────── export const userApi = { getList: (params) => request.get('/system/users', { params }), @@ -39,11 +38,13 @@ export const userApi = { create: (data) => request.post('/system/users', data), update: (id, data) => request.put(`/system/users/${id}`, data), delete: (id) => request.delete(`/system/users/${id}`), - resetPassword: (id, data) => request.post(`/system/users/${id}/reset-password`, data), - updateStatus: (id, status) => request.patch(`/system/users/${id}/status`, { status }) + resetPassword: (id, data) => request.put(`/system/users/${id}/password`, data), + updateStatus: (id, status) => request.put(`/system/users/${id}/status`, { status }), + approve: (id) => request.put(`/system/users/${id}/approve`), + reject: (id) => request.put(`/system/users/${id}/reject`) } -// ─── Roles ────────────────────────────────────────────────────────────────── +// ─── Roles ─────────────────────────────────────────────────────────────────── export const roleApi = { getList: (params) => request.get('/system/roles', { params }), @@ -52,58 +53,65 @@ export const roleApi = { update: (id, data) => request.put(`/system/roles/${id}`, data), delete: (id) => request.delete(`/system/roles/${id}`), assignPermissions: (id, permissionIds) => - request.post(`/system/roles/${id}/permissions`, { permission_ids: permissionIds }), - getPermissions: (id) => request.get(`/system/roles/${id}/permissions`) + request.put(`/system/roles/${id}/permissions`, { permission_ids: permissionIds }) + // getPermissions: 后端无此子路由,通过 getDetail 的 role.permissions 关联获取 } -// ─── Menus ────────────────────────────────────────────────────────────────── +// ─── Menus ─────────────────────────────────────────────────────────────────── +// 后端 except(['show']),无 getDetail/getTree 路由 export const menuApi = { getList: (params) => request.get('/system/menus', { params }), - getTree: () => request.get('/system/menus/tree'), - getDetail: (id) => request.get(`/system/menus/${id}`), create: (data) => request.post('/system/menus', data), update: (id, data) => request.put(`/system/menus/${id}`, data), delete: (id) => request.delete(`/system/menus/${id}`) } -// ─── Permissions ──────────────────────────────────────────────────────────── +// ─── Permissions ───────────────────────────────────────────────────────────── +// 后端 except(['show']),无 getDetail/getTree 路由 export const permissionApi = { getList: (params) => request.get('/system/permissions', { params }), - getTree: () => request.get('/system/permissions/tree'), - getDetail: (id) => request.get(`/system/permissions/${id}`), create: (data) => request.post('/system/permissions', data), update: (id, data) => request.put(`/system/permissions/${id}`, data), delete: (id) => request.delete(`/system/permissions/${id}`) } -// ─── Dictionaries ──────────────────────────────────────────────────────────── +// ─── Dictionaries ───────────────────────────────────────────────────────────── +// 后端路由:/system/dictionaries (CRUD except show/update) +// 字典项:GET|POST /dictionaries/{id}/items +// 字典项更新/删除:PUT|DELETE /dictionary-items/{item} export const dictApi = { // Dictionary types - getTypeList: (params) => request.get('/system/dict-types', { params }), - getTypeDetail: (id) => request.get(`/system/dict-types/${id}`), - createType: (data) => request.post('/system/dict-types', data), - updateType: (id, data) => request.put(`/system/dict-types/${id}`, data), - deleteType: (id) => request.delete(`/system/dict-types/${id}`), + getTypeList: (params) => request.get('/system/dictionaries', { params }), + createType: (data) => request.post('/system/dictionaries', data), + deleteType: (id) => request.delete(`/system/dictionaries/${id}`), - // Dictionary items - getItemList: (typeId, params) => - request.get(`/system/dict-types/${typeId}/items`, { params }), - getItemDetail: (typeId, itemId) => - request.get(`/system/dict-types/${typeId}/items/${itemId}`), - createItem: (typeId, data) => - request.post(`/system/dict-types/${typeId}/items`, data), - updateItem: (typeId, itemId, data) => - request.put(`/system/dict-types/${typeId}/items/${itemId}`, data), - deleteItem: (typeId, itemId) => - request.delete(`/system/dict-types/${typeId}/items/${itemId}`) + // Dictionary items(挂在 dictionary 下) + getItemList: (dictId, params) => + request.get(`/system/dictionaries/${dictId}/items`, { params }), + createItem: (dictId, data) => + request.post(`/system/dictionaries/${dictId}/items`, data), + updateItem: (itemId, data) => + request.put(`/system/dictionary-items/${itemId}`, data), + deleteItem: (itemId) => + request.delete(`/system/dictionary-items/${itemId}`) } -// ─── Operation Logs ───────────────────────────────────────────────────────── +// ─── Operation Logs ────────────────────────────────────────────────────────── +// 后端仅有 index 路由,无 show export const logApi = { - getList: (params) => request.get('/system/operation-logs', { params }), - getDetail: (id) => request.get(`/system/operation-logs/${id}`) + getList: (params) => request.get('/system/operation-logs', { params }) +} + +// ─── Registration Packages ──────────────────────────────────────────────────── + +export const registrationPackageApi = { + getList: (params) => request.get('/system/registration-packages', { params }), + getDetail: (id) => request.get(`/system/registration-packages/${id}`), + create: (data) => request.post('/system/registration-packages', data), + update: (id, data) => request.put(`/system/registration-packages/${id}`, data), + delete: (id) => request.delete(`/system/registration-packages/${id}`) } diff --git a/frontend/src/layout/components/Header.vue b/frontend/src/layout/components/Header.vue index 5abdd44..c450166 100644 --- a/frontend/src/layout/components/Header.vue +++ b/frontend/src/layout/components/Header.vue @@ -3,12 +3,10 @@ import { computed } from 'vue' import { useRouter, useRoute } from 'vue-router' import { useUserStore } from '@/stores/user.js' import { ElMessageBox, ElMessage } from 'element-plus' +import { Icon } from '@iconify/vue' const props = defineProps({ - collapsed: { - type: Boolean, - default: false - } + collapsed: { type: Boolean, default: false } }) const emit = defineEmits(['toggle-sidebar']) @@ -18,13 +16,17 @@ const route = useRoute() const userStore = useUserStore() const username = computed(() => userStore.userInfo?.name || userStore.userInfo?.username || '用户') -const storeName = computed(() => userStore.userInfo?.store?.name || '') +const storeName = computed(() => userStore.userInfo?.store?.name || '所有门店 (总览)') -// Build breadcrumbs from matched routes -const breadcrumbs = computed(() => { - return route.matched - .filter((r) => r.meta?.title && !r.meta?.hidden) - .map((r) => ({ title: r.meta.title, path: r.path })) +const pageTitle = computed(() => { + const titles = route.matched.filter(r => r.meta?.title && !r.meta?.hidden) + return titles.length ? titles[titles.length - 1].meta.title : '工作台' +}) + +const today = computed(() => { + const d = new Date() + const days = ['星期日','星期一','星期二','星期三','星期四','星期五','星期六'] + return `${d.getFullYear()}年${d.getMonth()+1}月${d.getDate()}日 ${days[d.getDay()]}` }) async function handleLogout() { @@ -37,60 +39,60 @@ async function handleLogout() { router.push('/login') ElMessage.success('已安全退出') } - -function goToProfile() { - router.push('/system/profile') -}