From 5ead63c160d77207fcf501126040467908da2207 Mon Sep 17 00:00:00 2001 From: Nick Zeng Date: Wed, 18 Mar 2026 15:51:34 +0800 Subject: [PATCH] Fix edit mode PUT payload (status field) and add error handling to fetchUsers --- frontend/src/components/UserFormModal.vue | 2 +- frontend/src/pages/users/__tests__/index.spec.ts | 9 +++++++-- .../src/pages/users/__tests__/user-form.spec.ts | 15 +++++++++++++-- frontend/src/stores/user-manage.ts | 3 +++ 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/UserFormModal.vue b/frontend/src/components/UserFormModal.vue index a3d2f2f..96aed38 100644 --- a/frontend/src/components/UserFormModal.vue +++ b/frontend/src/components/UserFormModal.vue @@ -87,7 +87,7 @@ async function handleSubmit() { await api.put(`/api/v1/users/${props.userData!.id}`, { username: formState.username, email: formState.email, - ext: props.userData!.ext, + status: formState.status, }) } message.success('操作成功') diff --git a/frontend/src/pages/users/__tests__/index.spec.ts b/frontend/src/pages/users/__tests__/index.spec.ts index 47bb16e..abd8ff4 100644 --- a/frontend/src/pages/users/__tests__/index.spec.ts +++ b/frontend/src/pages/users/__tests__/index.spec.ts @@ -2,6 +2,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import { setActivePinia, createPinia } from 'pinia' import { mount, flushPromises } from '@vue/test-utils' import { nextTick } from 'vue' +import { message } from 'ant-design-vue' import { useUserManageStore, type UserRecord } from '@/stores/user-manage' // jsdom 不支持 matchMedia,Ant Design Vue 响应式布局需要 @@ -133,13 +134,17 @@ describe('useUserManageStore', () => { expect(store.pagination.page).toBe(2) }) - it('sets loading to false even on error', async () => { + it('sets loading to false and shows error message on failure', async () => { const { api } = await import('@/utils/request') vi.mocked(api.get).mockRejectedValueOnce(new Error('Network error')) + const errorSpy = vi.spyOn(message, 'error').mockImplementation(() => ({}) as any) const store = useUserManageStore() - await expect(store.fetchUsers()).rejects.toThrow('Network error') + await store.fetchUsers() expect(store.loading).toBe(false) + expect(store.users).toEqual([]) + expect(errorSpy).toHaveBeenCalledWith('Network error') + errorSpy.mockRestore() }) }) diff --git a/frontend/src/pages/users/__tests__/user-form.spec.ts b/frontend/src/pages/users/__tests__/user-form.spec.ts index 899166c..7303676 100644 --- a/frontend/src/pages/users/__tests__/user-form.spec.ts +++ b/frontend/src/pages/users/__tests__/user-form.spec.ts @@ -157,17 +157,28 @@ describe('UserFormModal', () => { await flushPromises() }) - it('calls api.put for edit mode', async () => { + it('calls api.put for edit mode with status field', async () => { const { api } = await import('@/utils/request') vi.mocked(api.put).mockResolvedValueOnce({}) - await mountModal({ mode: 'edit', userData: mockUserData }) + // 先以 open=false 挂载,再切换为 true 触发 watch 预填数据,使表单验证通过 + const w = await mountModal({ mode: 'edit', userData: mockUserData, open: false }) + await w.setProps({ open: true }) + await flushPromises() + await nextTick() // 点击确定按钮 const okBtn = document.body.querySelector('.ant-modal-footer .ant-btn-primary') as HTMLElement expect(okBtn).toBeTruthy() okBtn?.click() await flushPromises() + + // 验证 PUT payload 包含 status 且不包含 ext + expect(api.put).toHaveBeenCalledOnce() + const [url, payload] = vi.mocked(api.put).mock.calls[0] + expect(url).toContain('/api/v1/users/') + expect(payload).toHaveProperty('status') + expect(payload).not.toHaveProperty('ext') }) }) }) diff --git a/frontend/src/stores/user-manage.ts b/frontend/src/stores/user-manage.ts index 5bb93c5..2d684d4 100644 --- a/frontend/src/stores/user-manage.ts +++ b/frontend/src/stores/user-manage.ts @@ -45,6 +45,9 @@ export const useUserManageStore = defineStore('user-manage', () => { users.value = data.items pagination.total = data.total pagination.page = data.page + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : '获取用户列表失败' + message.error(msg) } finally { loading.value = false }