diff --git a/furniture-3d-system/src/app/supply-chain/components/SupplyChainTabBar.test.tsx b/furniture-3d-system/src/app/supply-chain/components/SupplyChainTabBar.test.tsx index 4f31371e..4eeb7ffe 100644 --- a/furniture-3d-system/src/app/supply-chain/components/SupplyChainTabBar.test.tsx +++ b/furniture-3d-system/src/app/supply-chain/components/SupplyChainTabBar.test.tsx @@ -13,7 +13,7 @@ jest.mock('./supply-chain-tabs', () => ({ useSupplyChainTabs: () => ({ tabs: [ { path: '/supply-chain/dashboard', title: '工作台', closable: false }, - { path: '/supply-chain/management/owner-config', title: '负责人配置', closable: true }, + { path: '/supply-chain/management/owner-config', title: '人员信息', closable: true }, { path: '/supply-chain/management/custom-table', title: '限价管理', closable: true }, { path: '/supply-chain/management/blank-pages/blank-page-1', title: '空白页 1', closable: true }, ], @@ -47,7 +47,7 @@ describe('SupplyChainTabBar owner config labels', () => { it('keeps the page title read-only when editing is disabled', () => { render() - expect(screen.getByText('负责人配置')).toBeInTheDocument() + expect(screen.getByText('人员信息')).toBeInTheDocument() expect(screen.queryByRole('button', { name: '修改页签名称' })).not.toBeInTheDocument() }) diff --git a/furniture-3d-system/src/app/supply-chain/components/supply-chain-tabs.tsx b/furniture-3d-system/src/app/supply-chain/components/supply-chain-tabs.tsx index defc9fcf..3954ce5c 100644 --- a/furniture-3d-system/src/app/supply-chain/components/supply-chain-tabs.tsx +++ b/furniture-3d-system/src/app/supply-chain/components/supply-chain-tabs.tsx @@ -111,7 +111,7 @@ export const SUPPLY_CHAIN_ROUTE_TITLES: Record = { '/supply-chain/script-monitor': '脚本监控', '/supply-chain/operation-logs': '操作日志', '/supply-chain/user-permissions': '用户权限', - '/supply-chain/management/owner-config': '负责人配置', + '/supply-chain/management/owner-config': '人员信息', '/supply-chain/management/custom-table': '限价管理', ...Object.fromEntries(MANAGEMENT_BLANK_PAGES.map((page) => [page.route, page.label])), } diff --git a/furniture-3d-system/src/app/supply-chain/config/supply-chain-menu.test.ts b/furniture-3d-system/src/app/supply-chain/config/supply-chain-menu.test.ts index f36de3db..1f065617 100644 --- a/furniture-3d-system/src/app/supply-chain/config/supply-chain-menu.test.ts +++ b/furniture-3d-system/src/app/supply-chain/config/supply-chain-menu.test.ts @@ -108,7 +108,7 @@ describe('supply chain menu', () => { expect(flattenSupplyChainMenu()).toEqual( expect.arrayContaining([ expect.objectContaining({ - label: '负责人配置', + label: '人员信息', module: '经营管理', route: '/supply-chain/management/owner-config', }), diff --git a/furniture-3d-system/src/app/supply-chain/config/supply-chain-menu.ts b/furniture-3d-system/src/app/supply-chain/config/supply-chain-menu.ts index 96230646..beb2a6e1 100644 --- a/furniture-3d-system/src/app/supply-chain/config/supply-chain-menu.ts +++ b/furniture-3d-system/src/app/supply-chain/config/supply-chain-menu.ts @@ -88,7 +88,7 @@ export const SUPPLY_CHAIN_MENU: SupplyChainMenuModule[] = [ { items: [ { - label: '负责人配置', + label: '人员信息', route: '/supply-chain/management/owner-config', targetKey: 'businessManagement', }, diff --git a/furniture-3d-system/src/app/supply-chain/management/OwnerConfigView.test.tsx b/furniture-3d-system/src/app/supply-chain/management/OwnerConfigView.test.tsx index 20285730..1245b794 100644 --- a/furniture-3d-system/src/app/supply-chain/management/OwnerConfigView.test.tsx +++ b/furniture-3d-system/src/app/supply-chain/management/OwnerConfigView.test.tsx @@ -45,27 +45,33 @@ beforeEach(() => { }) afterAll(() => { global.fetch = originalFetch }) -describe('负责人配置工作台', () => { +describe('人员信息工作台', () => { it('只展示三级负责人字段,只读用户不显示编辑控件', async () => { render() await screen.findByText('运营甲') - for (const name of ['运营负责人', '部门', '二级部门', '利润中心', '部门负责人', '总负责人', '在职状态']) { - expect(screen.getByRole('columnheader', { name })).toBeInTheDocument() - } + expect(screen.getAllByRole('columnheader').map((header) => header.textContent)).toEqual([ + '人员', + '部门负责人', + '总负责人', + '部门', + '二级部门', + '利润中心', + '在职状态', + ]) for (const name of ['SPU', '货号', '商品编码']) { expect(screen.queryByRole('columnheader', { name })).not.toBeInTheDocument() } expect(screen.queryByRole('button', { name: '新增配置' })).not.toBeInTheDocument() - expect(screen.queryByRole('button', { name: '编辑负责人配置' })).not.toBeInTheDocument() + expect(screen.queryByRole('button', { name: '编辑人员信息' })).not.toBeInTheDocument() expect(screen.getByText('在职')).toHaveClass('employed') }) it('选择通讯录员工后自动带出部门和总负责人', async () => { - render() + render() await screen.findByText('运营甲') fireEvent.click(screen.getByRole('button', { name: '新增配置' })) await screen.findByRole('dialog') - fireEvent.change(screen.getByRole('combobox', { name: '运营负责人' }), { target: { value: 'employee-2' } }) + fireEvent.change(screen.getByRole('combobox', { name: '人员' }), { target: { value: 'employee-2' } }) expect(screen.getByRole('textbox', { name: '部门' })).toHaveValue('运营二部') expect(screen.getByRole('textbox', { name: '二级部门' })).toHaveValue('运营二组') expect(screen.getByRole('textbox', { name: '利润中心' })).toHaveValue('运营中心B') @@ -75,12 +81,12 @@ describe('负责人配置工作台', () => { }) it('允许开发环境人工修改并保存列标题', async () => { - render() + render() await screen.findByText('运营甲') expect(screen.getByRole('button', { name: '修改操作列标题' })).toBeInTheDocument() - fireEvent.click(screen.getByRole('button', { name: '修改运营负责人列标题' })) - const input = screen.getByRole('textbox', { name: '修改运营负责人列标题' }) + fireEvent.click(screen.getByRole('button', { name: '修改人员列标题' })) + const input = screen.getByRole('textbox', { name: '修改人员列标题' }) fireEvent.change(input, { target: { value: '成员' } }) fireEvent.blur(input) @@ -106,11 +112,39 @@ describe('负责人配置工作台', () => { render() await screen.findByText('运营甲') const headers = screen.getAllByRole('columnheader').map((header) => header.textContent) + expect(screen.getByRole('columnheader', { name: '人员' })).toBeInTheDocument() + expect(screen.queryByRole('columnheader', { name: '运营负责人' })).not.toBeInTheDocument() expect(headers.indexOf('二级部门')).toBe(headers.indexOf('部门') + 1) }) + it('把历史人工表头配置补全为当前源码中的完整顺序', async () => { + window.localStorage.setItem(OWNER_CONFIG_SETTINGS_STORAGE_KEY, JSON.stringify({ + pageTitle: '负责人配置', + actionsLabel: '操作', + fields: [ + { id: 'operationOwner', label: '运营负责人', builtinKey: 'operationOwner', visible: true }, + { id: 'departmentOwner', label: '部门负责人', builtinKey: 'departmentOwner', visible: true }, + { id: 'generalOwner', label: '总负责人', builtinKey: 'generalOwner', visible: true }, + ], + })) + + render() + await screen.findByText('运营甲') + + expect(screen.getAllByRole('columnheader').map((header) => header.textContent)).toEqual([ + '人员', + '部门负责人', + '总负责人', + '部门', + '二级部门', + '利润中心', + '在职状态', + ]) + expect(screen.getByRole('navigation', { name: '人员信息分页' })).toBeInTheDocument() + }) + it('支持新增自定义字段、填写值并删除字段', async () => { - render() + render() await screen.findByText('运营甲') fireEvent.click(screen.getByRole('button', { name: '字段配置' })) @@ -120,7 +154,7 @@ describe('负责人配置工作台', () => { fireEvent.click(screen.getByRole('button', { name: '完成' })) expect(screen.getByRole('columnheader', { name: '备注' })).toBeInTheDocument() - fireEvent.click(screen.getByRole('button', { name: '编辑负责人配置' })) + fireEvent.click(screen.getByRole('button', { name: '编辑人员信息' })) fireEvent.change(screen.getByRole('textbox', { name: '备注' }), { target: { value: '重点成员' } }) mockFetch.mockResolvedValueOnce(reply(row)) .mockResolvedValueOnce(reply({ items: [row], pageInfo })) @@ -145,9 +179,9 @@ describe('负责人配置工作台', () => { it('保存编辑后重新读取负责人数据', async () => { render() await screen.findByText('运营甲') - fireEvent.click(screen.getByRole('button', { name: '编辑负责人配置' })) + fireEvent.click(screen.getByRole('button', { name: '编辑人员信息' })) await screen.findByRole('dialog') - fireEvent.change(screen.getByRole('combobox', { name: '运营负责人' }), { target: { value: 'employee-2' } }) + fireEvent.change(screen.getByRole('combobox', { name: '人员' }), { target: { value: 'employee-2' } }) fireEvent.change(screen.getByRole('textbox', { name: '部门负责人' }), { target: { value: '人工部门负责人' } }) fireEvent.change(screen.getByRole('textbox', { name: '总负责人' }), { target: { value: '人工总负责人' } }) mockFetch.mockResolvedValueOnce(reply({ ...row, operationOwner: '运营丁' })) @@ -201,6 +235,30 @@ describe('负责人配置工作台', () => { expect(screen.getByRole('button', { name: '导出' })).toBeInTheDocument() }) + it('在底部分页区域显示总条数,单页时禁用翻页按钮', async () => { + render() + await screen.findByText('运营甲') + + const pagination = screen.getByRole('navigation', { name: '人员信息分页' }) + expect(pagination).toHaveTextContent('共 1 条') + expect(pagination).toHaveTextContent('1 / 1') + expect(screen.getByRole('button', { name: '上一页' })).toBeDisabled() + expect(screen.getByRole('button', { name: '下一页' })).toBeDisabled() + expect(document.querySelector('.owner-config-tools')).not.toHaveTextContent('共 1 条') + }) + + it('默认隐藏字段配置、批量删除、新增配置和批量勾选列', async () => { + render() + await screen.findByText('运营甲') + + expect(screen.queryByRole('button', { name: '字段配置' })).not.toBeInTheDocument() + expect(screen.queryByRole('button', { name: '批量删除' })).not.toBeInTheDocument() + expect(screen.queryByRole('button', { name: '新增配置' })).not.toBeInTheDocument() + expect(screen.queryByRole('checkbox', { name: '全选当前页' })).not.toBeInTheDocument() + expect(screen.queryByRole('checkbox', { name: '选择运营甲' })).not.toBeInTheDocument() + expect(screen.getByRole('button', { name: '编辑人员信息' })).toBeInTheDocument() + }) + it('支持勾选当前页记录并批量删除', async () => { jest.spyOn(window, 'confirm').mockReturnValue(true) mockFetch.mockImplementation(async (input: RequestInfo | URL, options?: RequestInit) => { @@ -210,7 +268,7 @@ describe('负责人配置工作台', () => { return reply({ items: [row], pageInfo }) }) - render() + render() await screen.findByText('运营甲') const bulkDeleteButton = screen.getByRole('button', { name: '批量删除' }) @@ -224,7 +282,7 @@ describe('负责人配置工作台', () => { && options?.method === 'POST' && JSON.parse(options.body as string).ids[0] === 'config-1' ))).toBe(true)) - expect(window.confirm).toHaveBeenCalledWith('确认删除已选择的 1 条负责人配置?') + expect(window.confirm).toHaveBeenCalledWith('确认删除已选择的 1 条人员信息?') }) it('分别筛选五个字段并按当前筛选条件导出全部匹配数据', async () => { @@ -243,7 +301,7 @@ describe('负责人配置工作台', () => { render() await screen.findByText('运营甲') for (const [name, value] of [ - ['筛选运营负责人', '运营甲'], + ['筛选人员', '运营甲'], ['筛选部门负责人', '部门乙'], ['筛选总负责人', '负责人丙'], ['筛选利润中心', '运营中心A'], @@ -288,7 +346,7 @@ describe('负责人配置工作台', () => { render() await screen.findByText('运营甲') - const operationOwnerInput = screen.getByRole('combobox', { name: '筛选运营负责人' }) + const operationOwnerInput = screen.getByRole('combobox', { name: '筛选人员' }) const profitCenterInput = screen.getByRole('combobox', { name: '筛选利润中心' }) const departmentInput = screen.getByRole('combobox', { name: '筛选部门' }) const statusInput = screen.getByRole('combobox', { name: '筛选在职状态' }) diff --git a/furniture-3d-system/src/app/supply-chain/management/OwnerConfigView.tsx b/furniture-3d-system/src/app/supply-chain/management/OwnerConfigView.tsx index f3a5a45d..bb53e272 100644 --- a/furniture-3d-system/src/app/supply-chain/management/OwnerConfigView.tsx +++ b/furniture-3d-system/src/app/supply-chain/management/OwnerConfigView.tsx @@ -180,11 +180,13 @@ export function OwnerConfigView({ canExport = false, allowLabelEditing = false, autoSyncDirectory = false, + showManagementActions = false, }: { canEdit: boolean canExport?: boolean allowLabelEditing?: boolean autoSyncDirectory?: boolean + showManagementActions?: boolean }) { const { labels, @@ -378,7 +380,7 @@ export function OwnerConfigView({ } const deleteItem = async (item: OwnerConfig) => { - if (!canEdit || deletingId || !window.confirm('确认删除该负责人配置?')) return + if (!canEdit || deletingId || !window.confirm('确认删除该人员信息?')) return setDeletingId(item.id) try { await readResponse(await fetch( @@ -397,7 +399,7 @@ export function OwnerConfigView({ const bulkDeleteItems = async () => { const ids = [...selectedIds] if (!canEdit || bulkDeleting || ids.length === 0) return - if (!window.confirm(`确认删除已选择的 ${ids.length} 条负责人配置?`)) return + if (!window.confirm(`确认删除已选择的 ${ids.length} 条人员信息?`)) return setBulkDeleting(true) setError(null) try { @@ -536,8 +538,9 @@ export function OwnerConfigView({ setExporting(false) } } + const canBulkSelect = canEdit && showManagementActions const allCurrentPageSelected = items.length > 0 && items.every((item) => selectedIds.has(item.id)) - const tableColumnCount = Math.max(1, visibleFields.length + (canEdit ? 2 : 0)) + const tableColumnCount = Math.max(1, visibleFields.length + (canEdit ? 1 : 0) + (canBulkSelect ? 1 : 0)) return (
@@ -568,7 +571,6 @@ export function OwnerConfigView({
- 共 {pageInfo.total} 条 @@ -577,17 +579,17 @@ export function OwnerConfigView({ {exporting ? '导出中...' : '导出'} ) : null} - {allowLabelEditing ? ( + {allowLabelEditing && showManagementActions ? ( ) : null} - {canEdit ? ( + {canBulkSelect ? ( ) : null} - {canEdit ? ( + {canEdit && showManagementActions ? ( @@ -602,16 +604,16 @@ export function OwnerConfigView({ - {canEdit ? : null} + {canBulkSelect ? : null} {visibleFields.map((field) => )} {canEdit ? : null} - {canEdit ? ( + {canBulkSelect ? ( - {canEdit ? ( + {canBulkSelect ? (
(
setSelectedIds((current) => { @@ -690,17 +692,16 @@ export function OwnerConfigView({
- {pageInfo.totalPages > 1 ? ( - - ) : null} + {draft ? ( @@ -822,6 +823,7 @@ export function OwnerConfigView({ .owner-config-pagination button{display:inline-flex; width:28px; height:28px; align-items:center; justify-content:center; border:0; border-radius:4px; background:transparent; color:var(--sc-ink); cursor:pointer} .owner-config-pagination button:hover:not(:disabled){background:var(--sc-surface-muted)} .owner-config-pagination button:disabled{opacity:.35; cursor:not-allowed} + .owner-config-total{margin-right:4px; padding-right:12px; border-right:1px solid var(--sc-border); white-space:nowrap} .owner-config-fields{display:grid; grid-template-columns:1fr 1fr; gap:14px} .owner-config-fields label{display:flex; min-width:0; flex-direction:column; gap:6px; color:var(--sc-muted); font-size:12px} .owner-config-fields input,.owner-config-fields select{box-sizing:border-box; width:100%; height:36px; border:1px solid var(--sc-border); border-radius:5px; background:var(--sc-surface); padding:0 10px; font-size:13px; outline:0} diff --git a/furniture-3d-system/src/app/supply-chain/management/owner-config-labels.ts b/furniture-3d-system/src/app/supply-chain/management/owner-config-labels.ts index 5940fb65..19f4f134 100644 --- a/furniture-3d-system/src/app/supply-chain/management/owner-config-labels.ts +++ b/furniture-3d-system/src/app/supply-chain/management/owner-config-labels.ts @@ -40,30 +40,30 @@ type OwnerConfigViewSettings = { } const DEFAULT_FIELDS: OwnerConfigField[] = [ - { id: 'operationOwner', label: '运营负责人', builtinKey: 'operationOwner', visible: true }, + { id: 'operationOwner', label: '人员', builtinKey: 'operationOwner', visible: true }, + { id: 'departmentOwner', label: '部门负责人', builtinKey: 'departmentOwner', visible: true }, + { id: 'generalOwner', label: '总负责人', builtinKey: 'generalOwner', visible: true }, { id: 'department', label: '部门', builtinKey: 'department', visible: true }, { id: 'secondaryDepartment', label: '二级部门', builtinKey: 'secondaryDepartment', visible: true }, { id: 'profitCenter', label: '利润中心', builtinKey: 'profitCenter', visible: true }, - { id: 'departmentOwner', label: '部门负责人', builtinKey: 'departmentOwner', visible: true }, - { id: 'generalOwner', label: '总负责人', builtinKey: 'generalOwner', visible: true }, { id: 'employmentStatus', label: '在职状态', builtinKey: 'employmentStatus', visible: true }, ] const DEFAULT_SETTINGS: OwnerConfigViewSettings = { - pageTitle: '负责人配置', + pageTitle: '人员信息', actionsLabel: '操作', fields: DEFAULT_FIELDS, } export const DEFAULT_OWNER_CONFIG_LABELS: OwnerConfigLabels = { pageTitle: DEFAULT_SETTINGS.pageTitle, - operationOwner: DEFAULT_FIELDS[0].label, - department: DEFAULT_FIELDS[1].label, - secondaryDepartment: DEFAULT_FIELDS[2].label, - profitCenter: DEFAULT_FIELDS[3].label, - departmentOwner: DEFAULT_FIELDS[4].label, - generalOwner: DEFAULT_FIELDS[5].label, - employmentStatus: DEFAULT_FIELDS[6].label, + operationOwner: DEFAULT_FIELDS.find((field) => field.builtinKey === 'operationOwner')!.label, + department: DEFAULT_FIELDS.find((field) => field.builtinKey === 'department')!.label, + secondaryDepartment: DEFAULT_FIELDS.find((field) => field.builtinKey === 'secondaryDepartment')!.label, + profitCenter: DEFAULT_FIELDS.find((field) => field.builtinKey === 'profitCenter')!.label, + departmentOwner: DEFAULT_FIELDS.find((field) => field.builtinKey === 'departmentOwner')!.label, + generalOwner: DEFAULT_FIELDS.find((field) => field.builtinKey === 'generalOwner')!.label, + employmentStatus: DEFAULT_FIELDS.find((field) => field.builtinKey === 'employmentStatus')!.label, actions: DEFAULT_SETTINGS.actionsLabel, } @@ -84,12 +84,14 @@ function readLegacyLabels(): OwnerConfigLabels { const value = JSON.parse(window.localStorage.getItem(OWNER_CONFIG_LABELS_STORAGE_KEY) ?? 'null') if (!value || typeof value !== 'object') return DEFAULT_OWNER_CONFIG_LABELS const source = value as Partial> - return Object.fromEntries( + const labels = Object.fromEntries( Object.entries(DEFAULT_OWNER_CONFIG_LABELS).map(([key, fallback]) => [ key, cleanLabel(source[key as OwnerConfigLabelKey], fallback), ]), ) as OwnerConfigLabels + if (labels.operationOwner === '运营负责人') labels.operationOwner = DEFAULT_OWNER_CONFIG_LABELS.operationOwner + return labels } catch { return DEFAULT_OWNER_CONFIG_LABELS } @@ -144,9 +146,16 @@ function normalizeSettings(value: unknown): OwnerConfigViewSettings { function readSettings() { try { - return normalizeSettings(JSON.parse( + const settings = normalizeSettings(JSON.parse( window.localStorage.getItem(OWNER_CONFIG_SETTINGS_STORAGE_KEY) ?? 'null', )) + if (settings.pageTitle === '负责人配置') settings.pageTitle = DEFAULT_SETTINGS.pageTitle + settings.fields = settings.fields.map((field) => ( + field.builtinKey === 'operationOwner' && field.label === '运营负责人' + ? { ...field, label: DEFAULT_OWNER_CONFIG_LABELS.operationOwner } + : field + )) + return settings } catch { return normalizeSettings(null) } diff --git a/furniture-3d-system/src/app/supply-chain/management/owner-config/page.tsx b/furniture-3d-system/src/app/supply-chain/management/owner-config/page.tsx index dd677f1b..97655034 100644 --- a/furniture-3d-system/src/app/supply-chain/management/owner-config/page.tsx +++ b/furniture-3d-system/src/app/supply-chain/management/owner-config/page.tsx @@ -3,7 +3,7 @@ import { requireSession } from '@/lib/server-session' import { supplyChainPermissionSettingsService } from '@/server/services/supply-chain-permission-settings.service' import { OwnerConfigView } from '../OwnerConfigView' -export const metadata = { title: '负责人配置 · 供应链中心' } +export const metadata = { title: '人员信息 · 供应链中心' } export default async function OwnerConfigPage() { const { authUser } = await requireSession('/login')