fix: 全链路运行时修复 — 登录/路由/菜单/门店CRUD

登录流程:
- login() 不再设置 userInfo,由 router guard 触发 getInfo() + 路由生成
- validate() 从 callback 改为 Promise 式,修复异步链断裂
- getInfo() 适配 /auth/me 扁平响应结构

动态路由:
- import.meta.glob 替代 Vite 不支持的多级动态 import
- 目录菜单检测: !component || 'Layout' 而非仅 'Layout'
- resolveComponent 支持多种 key 格式 + 后缀匹配兜底

侧边栏:
- menu.title → menu.name (匹配后端字段)
- 空 path 目录节点加 fallback index

门店管理:
- StoreController: region_id 改 nullable, 新增 contact/capacity/description 验证
- has('status') → filled('status') 修复空字符串导致列表为空
- capacity null → 0 兜底,避免 NOT NULL 约束报错
- 前端: capacity 初始值 '' → 1, handleSubmit 过滤冗余字段
- region 列渲染 region.name 而非 [object Object]
- 新增 stores 表迁移: contact/capacity/description 列

种子数据:
- DatabaseSeeder 改用 InitSeeder
- createMenus() 补全 52 个叶子页面菜单 (13 模块)
- 目录菜单添加 path 属性

其他:
- vite proxy 端口 8000 → 8001
- request.js 添加 Accept: application/json
- UserStatus 枚举值对齐 (Active=1, Disabled=0)
- 新增 positions/index.vue 页面
This commit is contained in:
li
2026-03-13 23:53:30 +08:00
parent 058ee507fa
commit 4a68855a88
14 changed files with 478 additions and 93 deletions
+2 -11
View File
@@ -4,16 +4,7 @@ namespace App\Enums;
enum UserStatus: int
{
case Pending = 0;
case Active = 1;
case Disabled = 2;
public function label(): string
{
return match ($this) {
self::Pending => '待审核',
self::Active => '正常',
self::Disabled => '禁用',
};
}
case Disabled = 0;
case Pending = 2;
}
@@ -16,7 +16,7 @@ class StoreController extends Controller
if ($request->keyword) {
$query->where('name', 'like', "%{$request->keyword}%");
}
if ($request->has('status')) {
if ($request->filled('status')) {
$query->where('status', $request->status);
}
@@ -29,14 +29,20 @@ class StoreController extends Controller
public function store(Request $request): JsonResponse
{
$data = $request->validate([
'region_id' => 'required|exists:regions,id',
'region_id' => 'nullable|exists:regions,id',
'name' => 'required|string|max:100',
'code' => 'nullable|string|max:20|unique:stores,code',
'address' => 'nullable|string|max:255',
'phone' => 'nullable|string|max:20',
'contact' => 'nullable|string|max:50',
'capacity' => 'nullable|integer|min:0|max:9999',
'description' => 'nullable|string|max:500',
'status' => 'in:0,1',
]);
// capacity 列有 NOT NULL + default(0),空值时用默认值
$data['capacity'] = $data['capacity'] ?? 0;
$store = Store::create($data);
return $this->success($store, '创建成功');
}
@@ -50,14 +56,21 @@ class StoreController extends Controller
public function update(Request $request, Store $store): JsonResponse
{
$data = $request->validate([
'region_id' => 'exists:regions,id',
'region_id' => 'nullable|exists:regions,id',
'name' => 'string|max:100',
'code' => 'nullable|string|max:20|unique:stores,code,' . $store->id,
'address' => 'nullable|string|max:255',
'phone' => 'nullable|string|max:20',
'contact' => 'nullable|string|max:50',
'capacity' => 'nullable|integer|min:0|max:9999',
'description' => 'nullable|string|max:500',
'status' => 'in:0,1',
]);
if (array_key_exists('capacity', $data) && $data['capacity'] === null) {
$data['capacity'] = 0;
}
$store->update($data);
return $this->success($store, '更新成功');
}
+2 -1
View File
@@ -9,7 +9,8 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
class Store extends Model
{
protected $fillable = [
'region_id', 'name', 'code', 'address', 'phone', 'logo', 'status',
'region_id', 'name', 'code', 'address', 'phone', 'logo',
'contact', 'capacity', 'description', 'status',
];
protected $casts = [
@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('stores', function (Blueprint $table) {
// Make region_id nullable (frontend may not always provide it)
$table->foreignId('region_id')->nullable()->change();
// Add fields that frontend form expects
$table->string('contact', 50)->nullable()->after('phone')->comment('联系人');
$table->unsignedSmallInteger('capacity')->default(0)->after('contact')->comment('床位数');
$table->string('description', 500)->nullable()->after('capacity')->comment('备注');
});
}
public function down(): void
{
Schema::table('stores', function (Blueprint $table) {
$table->dropColumn(['contact', 'capacity', 'description']);
});
}
};
+1 -6
View File
@@ -15,11 +15,6 @@ class DatabaseSeeder extends Seeder
*/
public function run(): void
{
// User::factory(10)->create();
User::factory()->create([
'name' => 'Test User',
'email' => 'test@example.com',
]);
$this->call(InitSeeder::class);
}
}
+70 -15
View File
@@ -204,32 +204,87 @@ class InitSeeder extends Seeder
{
$menus = [
['name' => '首页', 'path' => '/dashboard', 'component' => 'dashboard/index', 'icon' => 'HomeFilled', 'sort' => 0],
['name' => 'CRM管理', 'icon' => 'User', 'sort' => 1, 'permission_code' => 'crm', 'children' => [
['name' => 'CRM管理', 'path' => '/crm', 'icon' => 'User', 'sort' => 1, 'permission_code' => 'crm', 'children' => [
['name' => '渠道管理', 'path' => '/crm/channels', 'component' => 'crm/channels/index', 'permission_code' => 'crm'],
['name' => '线索管理', 'path' => '/crm/leads', 'component' => 'crm/leads/index', 'permission_code' => 'crm.lead'],
['name' => '客户管理', 'path' => '/crm/customers', 'component' => 'crm/customers/index', 'permission_code' => 'crm.customer'],
['name' => '合同管理', 'path' => '/crm/contracts', 'component' => 'crm/contracts/index', 'permission_code' => 'crm.contract'],
['name' => '投诉管理', 'path' => '/crm/complaints', 'component' => 'crm/complaints/index', 'permission_code' => 'crm'],
]],
['name' => '房务管理', 'icon' => 'House', 'sort' => 2, 'permission_code' => 'room', 'children' => [
['name' => '房态看板', 'path' => '/room/board', 'component' => 'room/board/index', 'permission_code' => 'room.room'],
['name' => '房务管理', 'path' => '/room', 'icon' => 'House', 'sort' => 2, 'permission_code' => 'room', 'children' => [
['name' => '房型管理', 'path' => '/room/room-types', 'component' => 'room/room-types/index', 'permission_code' => 'room.room'],
['name' => '房间管理', 'path' => '/room/rooms', 'component' => 'room/rooms/index', 'permission_code' => 'room.room'],
['name' => '预定管理', 'path' => '/room/reservations', 'component' => 'room/reservations/index', 'permission_code' => 'room.reservation'],
]],
['name' => '护理管理', 'icon' => 'FirstAidKit', 'sort' => 3, 'permission_code' => 'care', 'children' => [
['name' => '护理管理', 'path' => '/care', 'icon' => 'FirstAidKit', 'sort' => 3, 'permission_code' => 'care', 'children' => [
['name' => '护理档案', 'path' => '/care/profiles', 'component' => 'care/profiles/index', 'permission_code' => 'care.profile'],
['name' => '护理计划', 'path' => '/care/plans', 'component' => 'care/plans/index', 'permission_code' => 'care.profile'],
['name' => '护理记录', 'path' => '/care/records', 'component' => 'care/records/index', 'permission_code' => 'care.record'],
]],
['name' => '月子餐', 'icon' => 'Bowl', 'sort' => 4, 'permission_code' => 'meal', 'children' => [
['name' => '月子餐', 'path' => '/meal', 'icon' => 'Bowl', 'sort' => 4, 'permission_code' => 'meal', 'children' => [
['name' => '菜品管理', 'path' => '/meal/dishes', 'component' => 'meal/dishes/index', 'permission_code' => 'meal.dish'],
['name' => '排餐管理', 'path' => '/meal/plans', 'component' => 'meal/plans/index', 'permission_code' => 'meal.plan'],
['name' => '每日排餐', 'path' => '/meal/daily-plans', 'component' => 'meal/daily-plans/index', 'permission_code' => 'meal.plan'],
]],
['name' => '服务产康', 'icon' => 'Promotion', 'sort' => 5, 'permission_code' => 'service'],
['name' => '月嫂管理', 'icon' => 'Avatar', 'sort' => 6, 'permission_code' => 'nanny'],
['name' => '进销存', 'icon' => 'Box', 'sort' => 7, 'permission_code' => 'stock'],
['name' => '财务管理', 'icon' => 'Money', 'sort' => 8, 'permission_code' => 'finance'],
['name' => '人事薪资', 'icon' => 'Stamp', 'sort' => 9, 'permission_code' => 'hr'],
['name' => '办公协同', 'icon' => 'ChatDotRound', 'sort' => 10, 'permission_code' => 'office'],
['name' => '统计报表', 'icon' => 'DataAnalysis', 'sort' => 11, 'permission_code' => 'report'],
['name' => '知识库', 'icon' => 'Reading', 'sort' => 12, 'permission_code' => 'kb'],
['name' => '系统设置', 'icon' => 'Setting', 'sort' => 99, 'permission_code' => 'system', 'children' => [
['name' => '服务产康', 'path' => '/service', 'icon' => 'Promotion', 'sort' => 5, 'permission_code' => 'service', 'children' => [
['name' => '服务项目', 'path' => '/service/items', 'component' => 'service/items/index', 'permission_code' => 'service.item'],
['name' => '服务套餐', 'path' => '/service/packages', 'component' => 'service/packages/index', 'permission_code' => 'service.item'],
['name' => '服务订单', 'path' => '/service/orders', 'component' => 'service/orders/index', 'permission_code' => 'service.item'],
]],
['name' => '月嫂管理', 'path' => '/nanny', 'icon' => 'Avatar', 'sort' => 6, 'permission_code' => 'nanny', 'children' => [
['name' => '月嫂信息', 'path' => '/nanny/nannies', 'component' => 'nanny/nannies/index', 'permission_code' => 'nanny.profile'],
['name' => '月嫂订单', 'path' => '/nanny/orders', 'component' => 'nanny/orders/index', 'permission_code' => 'nanny.profile'],
['name' => '月嫂排班', 'path' => '/nanny/schedules', 'component' => 'nanny/schedules/index', 'permission_code' => 'nanny.profile'],
]],
['name' => '进销存', 'path' => '/inventory', 'icon' => 'Box', 'sort' => 7, 'permission_code' => 'stock', 'children' => [
['name' => '仓库管理', 'path' => '/inventory/warehouses', 'component' => 'inventory/warehouses/index', 'permission_code' => 'stock.inventory'],
['name' => '供应商管理', 'path' => '/inventory/suppliers', 'component' => 'inventory/suppliers/index', 'permission_code' => 'stock.inventory'],
['name' => '物料管理', 'path' => '/inventory/materials', 'component' => 'inventory/materials/index', 'permission_code' => 'stock.inventory'],
['name' => '采购订单', 'path' => '/inventory/purchase-orders', 'component' => 'inventory/purchase-orders/index', 'permission_code' => 'stock.inventory'],
['name' => '出入库流水', 'path' => '/inventory/stock-movements', 'component' => 'inventory/stock-movements/index', 'permission_code' => 'stock.inventory'],
]],
['name' => '财务管理', 'path' => '/finance', 'icon' => 'Money', 'sort' => 8, 'permission_code' => 'finance', 'children' => [
['name' => '收支分类', 'path' => '/finance/categories', 'component' => 'finance/categories/index', 'permission_code' => 'finance.record'],
['name' => '收支记录', 'path' => '/finance/records', 'component' => 'finance/records/index', 'permission_code' => 'finance.record'],
['name' => '客户账户', 'path' => '/finance/accounts', 'component' => 'finance/accounts/index', 'permission_code' => 'finance.record'],
['name' => '储值卡', 'path' => '/finance/prepaid-cards', 'component' => 'finance/prepaid-cards/index', 'permission_code' => 'finance.record'],
['name' => '发票管理', 'path' => '/finance/invoices', 'component' => 'finance/invoices/index', 'permission_code' => 'finance.record'],
]],
['name' => '人事薪资', 'path' => '/hr', 'icon' => 'Stamp', 'sort' => 9, 'permission_code' => 'hr', 'children' => [
['name' => '员工档案', 'path' => '/hr/profiles', 'component' => 'hr/profiles/index', 'permission_code' => 'hr.employee'],
['name' => '排班管理', 'path' => '/hr/schedules', 'component' => 'hr/schedules/index', 'permission_code' => 'hr.employee'],
['name' => '考勤管理', 'path' => '/hr/attendances', 'component' => 'hr/attendances/index', 'permission_code' => 'hr.employee'],
['name' => '请假管理', 'path' => '/hr/leaves', 'component' => 'hr/leaves/index', 'permission_code' => 'hr.employee'],
['name' => '工资管理', 'path' => '/hr/salaries', 'component' => 'hr/salaries/index', 'permission_code' => 'hr.employee'],
]],
['name' => '办公协同', 'path' => '/office', 'icon' => 'ChatDotRound', 'sort' => 10, 'permission_code' => 'office', 'children' => [
['name' => '审批模板', 'path' => '/office/approval-templates', 'component' => 'office/approval-templates/index', 'permission_code' => 'office.approval'],
['name' => '审批管理', 'path' => '/office/approvals', 'component' => 'office/approvals/index', 'permission_code' => 'office.approval'],
['name' => '公告管理', 'path' => '/office/announcements', 'component' => 'office/announcements/index', 'permission_code' => 'office.approval'],
['name' => '交接班', 'path' => '/office/handovers', 'component' => 'office/handovers/index', 'permission_code' => 'office.approval'],
['name' => '消息通知', 'path' => '/office/notifications', 'component' => 'office/notifications/index', 'permission_code' => 'office.approval'],
]],
['name' => '统计报表', 'path' => '/report', 'icon' => 'DataAnalysis', 'sort' => 11, 'permission_code' => 'report', 'children' => [
['name' => '数据概览', 'path' => '/report/overview', 'component' => 'report/overview/index', 'permission_code' => 'report.view'],
['name' => '导出记录', 'path' => '/report/exports', 'component' => 'report/exports/index', 'permission_code' => 'report.export'],
]],
['name' => '知识库', 'path' => '/kb', 'icon' => 'Reading', 'sort' => 12, 'permission_code' => 'kb', 'children' => [
['name' => '知识分类', 'path' => '/kb/categories', 'component' => 'kb/categories/index', 'permission_code' => 'kb.article'],
['name' => '知识文章', 'path' => '/kb/articles', 'component' => 'kb/articles/index', 'permission_code' => 'kb.article'],
]],
['name' => '系统设置', 'path' => '/system', 'icon' => 'Setting', 'sort' => 99, 'permission_code' => 'system', 'children' => [
['name' => '门店管理', 'path' => '/system/stores', 'component' => 'system/stores/index', 'permission_code' => 'system.store'],
['name' => '部门管理', 'path' => '/system/departments', 'component' => 'system/departments/index', 'permission_code' => 'system.store'],
['name' => '职务管理', 'path' => '/system/positions', 'component' => 'system/positions/index', 'permission_code' => 'system.store'],