From 4a68855a88ed1a28252c7df4e4e6e2ab0ed7deb8 Mon Sep 17 00:00:00 2001 From: li Date: Fri, 13 Mar 2026 23:53:30 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=85=A8=E9=93=BE=E8=B7=AF=E8=BF=90?= =?UTF-8?q?=E8=A1=8C=E6=97=B6=E4=BF=AE=E5=A4=8D=20=E2=80=94=20=E7=99=BB?= =?UTF-8?q?=E5=BD=95/=E8=B7=AF=E7=94=B1/=E8=8F=9C=E5=8D=95/=E9=97=A8?= =?UTF-8?q?=E5=BA=97CRUD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 登录流程: - 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 页面 --- backend/app/Enums/UserStatus.php | 13 +- .../Admin/System/StoreController.php | 19 +- backend/app/Models/Store.php | 3 +- ...53506_add_extra_fields_to_stores_table.php | 28 ++ backend/database/seeders/DatabaseSeeder.php | 7 +- backend/database/seeders/InitSeeder.php | 85 +++++- frontend/src/layout/components/Sidebar.vue | 12 +- frontend/src/stores/permission.js | 41 ++- frontend/src/stores/user.js | 15 +- frontend/src/utils/request.js | 3 +- frontend/src/views/login/index.vue | 30 +- frontend/src/views/system/positions/index.vue | 265 ++++++++++++++++++ frontend/src/views/system/stores/index.vue | 48 ++-- frontend/vite.config.js | 2 +- 14 files changed, 478 insertions(+), 93 deletions(-) create mode 100644 backend/database/migrations/2026_03_13_153506_add_extra_fields_to_stores_table.php create mode 100644 frontend/src/views/system/positions/index.vue diff --git a/backend/app/Enums/UserStatus.php b/backend/app/Enums/UserStatus.php index 94c4d9c..67509dc 100644 --- a/backend/app/Enums/UserStatus.php +++ b/backend/app/Enums/UserStatus.php @@ -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; } diff --git a/backend/app/Http/Controllers/Admin/System/StoreController.php b/backend/app/Http/Controllers/Admin/System/StoreController.php index 34a47b2..d866fe8 100644 --- a/backend/app/Http/Controllers/Admin/System/StoreController.php +++ b/backend/app/Http/Controllers/Admin/System/StoreController.php @@ -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, '更新成功'); } diff --git a/backend/app/Models/Store.php b/backend/app/Models/Store.php index 43e071c..780e29b 100644 --- a/backend/app/Models/Store.php +++ b/backend/app/Models/Store.php @@ -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 = [ diff --git a/backend/database/migrations/2026_03_13_153506_add_extra_fields_to_stores_table.php b/backend/database/migrations/2026_03_13_153506_add_extra_fields_to_stores_table.php new file mode 100644 index 0000000..b5d4437 --- /dev/null +++ b/backend/database/migrations/2026_03_13_153506_add_extra_fields_to_stores_table.php @@ -0,0 +1,28 @@ +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']); + }); + } +}; diff --git a/backend/database/seeders/DatabaseSeeder.php b/backend/database/seeders/DatabaseSeeder.php index 6b901f8..a0928d9 100644 --- a/backend/database/seeders/DatabaseSeeder.php +++ b/backend/database/seeders/DatabaseSeeder.php @@ -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); } } diff --git a/backend/database/seeders/InitSeeder.php b/backend/database/seeders/InitSeeder.php index 1142658..849a0c8 100644 --- a/backend/database/seeders/InitSeeder.php +++ b/backend/database/seeders/InitSeeder.php @@ -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'], diff --git a/frontend/src/layout/components/Sidebar.vue b/frontend/src/layout/components/Sidebar.vue index 9cd7485..db7e1d9 100644 --- a/frontend/src/layout/components/Sidebar.vue +++ b/frontend/src/layout/components/Sidebar.vue @@ -53,13 +53,13 @@ function resolveIcon(iconName) { - + @@ -91,7 +91,7 @@ function resolveIcon(iconName) { - + @@ -104,7 +104,7 @@ function resolveIcon(iconName) { - + diff --git a/frontend/src/stores/permission.js b/frontend/src/stores/permission.js index ef1f1a0..9f7caca 100644 --- a/frontend/src/stores/permission.js +++ b/frontend/src/stores/permission.js @@ -5,6 +5,28 @@ import { constantRoutes } from '@/router/routes.js' // Layout wrapper for authenticated pages const Layout = () => import('@/layout/index.vue') +// Pre-scan all view components — Vite cannot resolve multi-level dynamic imports +const viewModules = import.meta.glob('@/views/**/*.vue') + +function resolveComponent(comp) { + // Try multiple key formats that import.meta.glob may produce + const keys = [ + `/src/views/${comp}.vue`, + `@/views/${comp}.vue`, + `../views/${comp}.vue` + ] + for (const key of keys) { + if (viewModules[key]) return viewModules[key] + } + // Fallback: search by suffix match + const suffix = `/views/${comp}.vue` + for (const [key, loader] of Object.entries(viewModules)) { + if (key.endsWith(suffix)) return loader + } + console.warn(`[permission] View not found: ${comp}`) + return undefined +} + /** * Convert a single server menu node to a vue-router route object. * Recursive — handles children at any depth. @@ -14,7 +36,7 @@ function menuToRoute(menu) { path: menu.path, name: menu.name || menu.path, meta: { - title: menu.title, + title: menu.name, icon: menu.icon || '', id: menu.id, hidden: menu.visible === 0 @@ -24,9 +46,10 @@ function menuToRoute(menu) { if (menu.component === 'Layout') { route.component = Layout } else if (menu.component) { - // Dynamic import from views directory - const comp = menu.component - route.component = () => import(`@/views/${comp}.vue`) + const loader = resolveComponent(menu.component) + if (loader) { + route.component = loader + } } if (menu.children && menu.children.length > 0) { @@ -62,14 +85,16 @@ export const usePermissionStore = defineStore('permission', () => { // Flatten top-level menu items into Layout's children for (const menu of menus) { - if (menu.component === 'Layout' && menu.children) { - // This menu node IS the layout wrapper — add its children directly + if (menu.children && menu.children.length > 0 && (!menu.component || menu.component === 'Layout')) { + // Directory/group node — add its children directly under Layout for (const child of menu.children) { layoutRoute.children.push(menuToRoute(child)) } - } else { + } else if (menu.component) { + // Leaf menu with a component — add as direct child layoutRoute.children.push(menuToRoute(menu)) } + // Skip menus without component and without children (empty directories) } // Always ensure dashboard is accessible @@ -80,7 +105,7 @@ export const usePermissionStore = defineStore('permission', () => { layoutRoute.children.unshift({ path: 'dashboard', name: 'Dashboard', - component: () => import('@/views/dashboard/index.vue'), + component: resolveComponent('dashboard/index'), meta: { title: '工作台', icon: 'Odometer' } }) } diff --git a/frontend/src/stores/user.js b/frontend/src/stores/user.js index ed380fb..6d56e9c 100644 --- a/frontend/src/stores/user.js +++ b/frontend/src/stores/user.js @@ -15,19 +15,22 @@ export const useUserStore = defineStore('user', () => { // Actions async function login(credentials) { const res = await loginApi(credentials) - const { token: newToken, user } = res.data + const { token: newToken } = res.data token.value = newToken - userInfo.value = user + // Only store token here — userInfo, permissions, menus + // will be loaded by router guard via getInfo() which also + // triggers dynamic route generation. localStorage.setItem('token', newToken) return res } async function getInfo() { const res = await getMe() - const { user, permissions: perms, menus: menuList } = res.data - userInfo.value = user - permissions.value = perms || [] - menus.value = menuList || [] + const data = res.data + // /auth/me returns flat structure: { id, username, ..., permissions, menus } + userInfo.value = data + permissions.value = data.permissions || [] + menus.value = data.menus || [] return res } diff --git a/frontend/src/utils/request.js b/frontend/src/utils/request.js index 772b4d6..215a181 100644 --- a/frontend/src/utils/request.js +++ b/frontend/src/utils/request.js @@ -5,7 +5,8 @@ const request = axios.create({ baseURL: '/api/v1', timeout: 15000, headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + 'Accept': 'application/json' } }) diff --git a/frontend/src/views/login/index.vue b/frontend/src/views/login/index.vue index 0ce4a16..e203a5e 100644 --- a/frontend/src/views/login/index.vue +++ b/frontend/src/views/login/index.vue @@ -29,20 +29,22 @@ const rules = { async function handleLogin() { if (!formRef.value) return - await formRef.value.validate(async (valid) => { - if (!valid) return - loading.value = true - try { - await userStore.login({ username: form.username, password: form.password }) - const redirect = route.query.redirect || '/' - await router.push(redirect) - ElMessage.success('登录成功,欢迎回来!') - } catch { - // Error already handled by request interceptor - } finally { - loading.value = false - } - }) + try { + await formRef.value.validate() + } catch { + return // validation failed + } + loading.value = true + try { + await userStore.login({ username: form.username, password: form.password }) + const redirect = route.query.redirect || '/' + await router.push(redirect) + ElMessage.success('登录成功,欢迎回来!') + } catch { + // Error already handled by request interceptor + } finally { + loading.value = false + } } function handleKeydown(e) { diff --git a/frontend/src/views/system/positions/index.vue b/frontend/src/views/system/positions/index.vue new file mode 100644 index 0000000..1fb1198 --- /dev/null +++ b/frontend/src/views/system/positions/index.vue @@ -0,0 +1,265 @@ + + + diff --git a/frontend/src/views/system/stores/index.vue b/frontend/src/views/system/stores/index.vue index e3335fa..9e969bf 100644 --- a/frontend/src/views/system/stores/index.vue +++ b/frontend/src/views/system/stores/index.vue @@ -27,7 +27,7 @@ const form = reactive({ address: '', phone: '', contact: '', - capacity: '', + capacity: 1, description: '', status: 1 }) @@ -73,7 +73,7 @@ function handleAdd() { dialogTitle.value = '新增门店' Object.assign(form, { id: null, name: '', code: '', region: '', address: '', - phone: '', contact: '', capacity: '', description: '', status: 1 + phone: '', contact: '', capacity: 1, description: '', status: 1 }) dialogVisible.value = true } @@ -100,25 +100,29 @@ async function handleEdit(row) { async function handleSubmit() { if (!formRef.value) return - await formRef.value.validate(async (valid) => { - if (!valid) return - submitLoading.value = true - try { - if (isEdit.value) { - await storeApi.update(form.id, form) - ElMessage.success('门店信息更新成功') - } else { - await storeApi.create(form) - ElMessage.success('门店创建成功') - } - dialogVisible.value = false - fetchList() - } catch { - // - } finally { - submitLoading.value = false + try { + await formRef.value.validate() + } catch { + return + } + submitLoading.value = true + try { + // 只发送后端接受的字段,过滤 id / region + const { id, region, ...payload } = form + if (isEdit.value) { + await storeApi.update(id, payload) + ElMessage.success('门店信息更新成功') + } else { + await storeApi.create(payload) + ElMessage.success('门店创建成功') } - }) + dialogVisible.value = false + fetchList() + } catch { + // + } finally { + submitLoading.value = false + } } async function handleDelete(row) { @@ -193,7 +197,9 @@ onMounted(fetchList) - + + + diff --git a/frontend/vite.config.js b/frontend/vite.config.js index d41a255..64bb159 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -12,7 +12,7 @@ export default defineConfig({ server: { proxy: { '/api': { - target: 'http://localhost:8000', + target: 'http://localhost:8001', changeOrigin: true, secure: false }