优化
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
import { http } from '@/shared/api/http'
|
import { http } from '@/shared/api/http'
|
||||||
|
import { overseasHttp } from '@/shared/api/overseasHttp'
|
||||||
import {
|
import {
|
||||||
approveEnterpriseReview,
|
approveEnterpriseReview,
|
||||||
archivePlatformTenant,
|
archivePlatformTenant,
|
||||||
@@ -24,6 +25,7 @@ import {
|
|||||||
listPlatformRoles,
|
listPlatformRoles,
|
||||||
listPlatformInternalUsers,
|
listPlatformInternalUsers,
|
||||||
listPlatformManagedUsers,
|
listPlatformManagedUsers,
|
||||||
|
listSharedReceivingOverseasWarehouseOptions,
|
||||||
getPlatformUserOptions,
|
getPlatformUserOptions,
|
||||||
qualificationFileName,
|
qualificationFileName,
|
||||||
replacePlatformRolePermissions,
|
replacePlatformRolePermissions,
|
||||||
@@ -49,6 +51,10 @@ vi.mock('@/shared/api/http', () => ({
|
|||||||
unwrapApiResponse: <T>(response: { data: T }) => response.data,
|
unwrapApiResponse: <T>(response: { data: T }) => response.data,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/shared/api/overseasHttp', () => ({
|
||||||
|
overseasHttp: { get: vi.fn() },
|
||||||
|
}))
|
||||||
|
|
||||||
const role: PlatformRoleResource = {
|
const role: PlatformRoleResource = {
|
||||||
id: 4000002,
|
id: 4000002,
|
||||||
tenantId: 0,
|
tenantId: 0,
|
||||||
@@ -108,6 +114,7 @@ const managedUser: PlatformManagedUser = {
|
|||||||
roleNames: ['运营管理员'],
|
roleNames: ['运营管理员'],
|
||||||
lastLoginAt: '2026-06-01T10:30:00',
|
lastLoginAt: '2026-06-01T10:30:00',
|
||||||
protectedAccount: false,
|
protectedAccount: false,
|
||||||
|
overseasWarehouseId: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('platform IAM roles', () => {
|
describe('platform IAM roles', () => {
|
||||||
@@ -116,6 +123,7 @@ describe('platform IAM roles', () => {
|
|||||||
vi.mocked(http.post).mockReset()
|
vi.mocked(http.post).mockReset()
|
||||||
vi.mocked(http.put).mockReset()
|
vi.mocked(http.put).mockReset()
|
||||||
vi.mocked(http.delete).mockReset()
|
vi.mocked(http.delete).mockReset()
|
||||||
|
vi.mocked(overseasHttp.get).mockReset()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('loads roles from the platform IAM prefix', async () => {
|
it('loads roles from the platform IAM prefix', async () => {
|
||||||
@@ -212,15 +220,15 @@ describe('platform IAM roles', () => {
|
|||||||
it('maintains paginated platform users through the unified user contract', async () => {
|
it('maintains paginated platform users through the unified user contract', async () => {
|
||||||
const userOptions = {
|
const userOptions = {
|
||||||
tenants: [{ id: '0', name: '平台', status: 'ACTIVE' }],
|
tenants: [{ id: '0', name: '平台', status: 'ACTIVE' }],
|
||||||
roles: [{ id: '40001', tenantId: '0', name: '运营管理员', status: 'ACTIVE' }],
|
roles: [{ id: '40001', tenantId: '0', roleCode: 'PLATFORM_OPERATOR', name: '运营管理员', status: 'ACTIVE' }],
|
||||||
}
|
}
|
||||||
const create = {
|
const create = {
|
||||||
username: 'zhangwei', displayName: '张伟', phone: '13800138000', email: 'zhangwei@example.com',
|
username: 'zhangwei', displayName: '张伟', phone: '13800138000', email: 'zhangwei@example.com',
|
||||||
tenantId: '0', roleIds: ['40001'], password: 'Password1', status: 'ACTIVE' as const,
|
tenantId: '0', roleIds: ['40001'], overseasWarehouseId: null, password: 'Password1', status: 'ACTIVE' as const,
|
||||||
}
|
}
|
||||||
const update = {
|
const update = {
|
||||||
displayName: '张伟', phone: '13800138000', email: 'zhangwei@example.com',
|
displayName: '张伟', phone: '13800138000', email: 'zhangwei@example.com',
|
||||||
tenantId: '0', roleIds: ['40001'],
|
tenantId: '0', roleIds: ['40001'], overseasWarehouseId: null,
|
||||||
}
|
}
|
||||||
const userPage = { records: [managedUser], page: 2, size: 10, total: 11, pages: 2 }
|
const userPage = { records: [managedUser], page: 2, size: 10, total: 11, pages: 2 }
|
||||||
vi.mocked(http.get).mockResolvedValueOnce({
|
vi.mocked(http.get).mockResolvedValueOnce({
|
||||||
@@ -247,6 +255,25 @@ describe('platform IAM roles', () => {
|
|||||||
expect(http.put).toHaveBeenNthCalledWith(3, `/platform/v1/iam/users/${managedUser.membershipId}/password`, { newPassword: 'ChangeMe123' })
|
expect(http.put).toHaveBeenNthCalledWith(3, `/platform/v1/iam/users/${managedUser.membershipId}/password`, { newPassword: 'ChangeMe123' })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('loads only active shared receiving overseas warehouses through the overseas gateway', async () => {
|
||||||
|
vi.mocked(overseasHttp.get).mockResolvedValue({
|
||||||
|
code: '200', message: 'success', data: {
|
||||||
|
records: [
|
||||||
|
{ id: '90071992547409931', customerId: '0', warehouseCode: 'LAX-01', warehouseName: '洛杉矶收货仓', warehouseType: 'OVERSEAS', countryCode: 'US', status: 'ACTIVE' },
|
||||||
|
{ id: '2', customerId: '100', warehouseCode: 'CUST', warehouseName: '客户仓', warehouseType: 'OVERSEAS', countryCode: 'US', status: 'ACTIVE' },
|
||||||
|
{ id: '3', customerId: '0', warehouseCode: 'PARTNER', warehouseName: '合作仓', warehouseType: 'OVERSEAS_PARTNER', countryCode: 'US', status: 'ACTIVE' },
|
||||||
|
], page: 1, size: 200, total: 3, pages: 1,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await expect(listSharedReceivingOverseasWarehouseOptions()).resolves.toEqual([
|
||||||
|
{ id: '90071992547409931', warehouseCode: 'LAX-01', warehouseName: '洛杉矶收货仓', countryCode: 'US' },
|
||||||
|
])
|
||||||
|
expect(overseasHttp.get).toHaveBeenCalledWith('/platform/v1/warehouse-admin', {
|
||||||
|
params: { customerId: '0', page: 1, size: 200, capabilityCode: 'RECEIVING', status: 'ACTIVE' },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it('loads qualifications for the selected tenant', async () => {
|
it('loads qualifications for the selected tenant', async () => {
|
||||||
const qualification = {
|
const qualification = {
|
||||||
id: 12,
|
id: 12,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
|
import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
|
||||||
|
import { overseasHttp } from '@/shared/api/overseasHttp'
|
||||||
import type { PlatformPermissionResource } from '@/shared/types/platformPermission'
|
import type { PlatformPermissionResource } from '@/shared/types/platformPermission'
|
||||||
|
|
||||||
export type { PlatformPermissionResource } from '@/shared/types/platformPermission'
|
export type { PlatformPermissionResource } from '@/shared/types/platformPermission'
|
||||||
@@ -253,6 +254,7 @@ export interface PlatformManagedUser {
|
|||||||
roleNames: string[]
|
roleNames: string[]
|
||||||
lastLoginAt: string | null
|
lastLoginAt: string | null
|
||||||
protectedAccount: boolean
|
protectedAccount: boolean
|
||||||
|
overseasWarehouseId: number | string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PlatformManagedUserPage {
|
export interface PlatformManagedUserPage {
|
||||||
@@ -272,7 +274,14 @@ export interface PlatformManagedUserQuery {
|
|||||||
|
|
||||||
export interface PlatformUserOptions {
|
export interface PlatformUserOptions {
|
||||||
tenants: Array<{ id: number | string; name: string; status: string }>
|
tenants: Array<{ id: number | string; name: string; status: string }>
|
||||||
roles: Array<{ id: number | string; tenantId: number | string; name: string; status: string }>
|
roles: Array<{ id: number | string; tenantId: number | string; roleCode: string; name: string; status: string }>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SharedReceivingOverseasWarehouseOption {
|
||||||
|
id: string
|
||||||
|
warehouseCode: string
|
||||||
|
warehouseName: string
|
||||||
|
countryCode: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreatePlatformManagedUserRequest {
|
export interface CreatePlatformManagedUserRequest {
|
||||||
@@ -282,6 +291,7 @@ export interface CreatePlatformManagedUserRequest {
|
|||||||
email: string
|
email: string
|
||||||
tenantId: number | string
|
tenantId: number | string
|
||||||
roleIds: Array<number | string>
|
roleIds: Array<number | string>
|
||||||
|
overseasWarehouseId: number | string | null
|
||||||
password: string
|
password: string
|
||||||
status: 'ACTIVE' | 'DISABLED'
|
status: 'ACTIVE' | 'DISABLED'
|
||||||
}
|
}
|
||||||
@@ -292,6 +302,7 @@ export interface UpdatePlatformManagedUserRequest {
|
|||||||
email: string
|
email: string
|
||||||
tenantId: number | string
|
tenantId: number | string
|
||||||
roleIds: Array<number | string>
|
roleIds: Array<number | string>
|
||||||
|
overseasWarehouseId: number | string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getData<T>(path: string): Promise<T> {
|
async function getData<T>(path: string): Promise<T> {
|
||||||
@@ -558,6 +569,34 @@ export function getPlatformUserOptions() {
|
|||||||
return getData<PlatformUserOptions>('/platform/v1/iam/users/options')
|
return getData<PlatformUserOptions>('/platform/v1/iam/users/options')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function listSharedReceivingOverseasWarehouseOptions() {
|
||||||
|
interface WarehousePage {
|
||||||
|
records: Array<{
|
||||||
|
id: number | string
|
||||||
|
customerId: number | string
|
||||||
|
warehouseCode: string
|
||||||
|
warehouseName: string
|
||||||
|
warehouseType: string
|
||||||
|
countryCode: string
|
||||||
|
status: string
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
const response = await overseasHttp.get<ApiEnvelope<WarehousePage>, ApiEnvelope<WarehousePage>>(
|
||||||
|
'/platform/v1/warehouse-admin',
|
||||||
|
{ params: { customerId: '0', page: 1, size: 200, capabilityCode: 'RECEIVING', status: 'ACTIVE' } },
|
||||||
|
)
|
||||||
|
return unwrapApiResponse(response).records
|
||||||
|
.filter((warehouse) => String(warehouse.customerId) === '0'
|
||||||
|
&& warehouse.warehouseType === 'OVERSEAS'
|
||||||
|
&& warehouse.status === 'ACTIVE')
|
||||||
|
.map((warehouse) => ({
|
||||||
|
id: String(warehouse.id),
|
||||||
|
warehouseCode: warehouse.warehouseCode,
|
||||||
|
warehouseName: warehouse.warehouseName,
|
||||||
|
countryCode: warehouse.countryCode,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
export function createPlatformManagedUser(payload: CreatePlatformManagedUserRequest) {
|
export function createPlatformManagedUser(payload: CreatePlatformManagedUserRequest) {
|
||||||
return postData<PlatformManagedUser>('/platform/v1/iam/users', payload)
|
return postData<PlatformManagedUser>('/platform/v1/iam/users', payload)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,11 @@ import { createPinia, setActivePinia } from 'pinia'
|
|||||||
import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils'
|
import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils'
|
||||||
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
import {
|
import {
|
||||||
|
createPlatformManagedUser,
|
||||||
getPlatformUserOptions,
|
getPlatformUserOptions,
|
||||||
|
listSharedReceivingOverseasWarehouseOptions,
|
||||||
listPlatformManagedUsers,
|
listPlatformManagedUsers,
|
||||||
|
updatePlatformManagedUser,
|
||||||
type PlatformManagedUser,
|
type PlatformManagedUser,
|
||||||
} from '@/modules/iam/api/platformIam'
|
} from '@/modules/iam/api/platformIam'
|
||||||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||||
@@ -18,6 +21,7 @@ const internalUsersSource = readFileSync(resolve(process.cwd(), 'src/modules/iam
|
|||||||
vi.mock('@/modules/iam/api/platformIam', () => ({
|
vi.mock('@/modules/iam/api/platformIam', () => ({
|
||||||
listPlatformManagedUsers: vi.fn(),
|
listPlatformManagedUsers: vi.fn(),
|
||||||
getPlatformUserOptions: vi.fn(),
|
getPlatformUserOptions: vi.fn(),
|
||||||
|
listSharedReceivingOverseasWarehouseOptions: vi.fn(),
|
||||||
createPlatformManagedUser: vi.fn(),
|
createPlatformManagedUser: vi.fn(),
|
||||||
updatePlatformManagedUser: vi.fn(),
|
updatePlatformManagedUser: vi.fn(),
|
||||||
changePlatformManagedUserStatus: vi.fn(),
|
changePlatformManagedUserStatus: vi.fn(),
|
||||||
@@ -48,6 +52,7 @@ const user: PlatformManagedUser = {
|
|||||||
memberType: 'INTERNAL', memberNo: 'PLATFORM-30', status: 'ACTIVE', roleIds: ['40'],
|
memberType: 'INTERNAL', memberNo: 'PLATFORM-30', status: 'ACTIVE', roleIds: ['40'],
|
||||||
roleNames: ['运营管理员'], lastLoginAt: '2026-06-01T10:30:00',
|
roleNames: ['运营管理员'], lastLoginAt: '2026-06-01T10:30:00',
|
||||||
protectedAccount: false,
|
protectedAccount: false,
|
||||||
|
overseasWarehouseId: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
async function mountView() {
|
async function mountView() {
|
||||||
@@ -101,10 +106,15 @@ describe('InternalUsersView platform internal user management', () => {
|
|||||||
{ id: '10001', name: '华远贸易', status: 'ACTIVE' },
|
{ id: '10001', name: '华远贸易', status: 'ACTIVE' },
|
||||||
],
|
],
|
||||||
roles: [
|
roles: [
|
||||||
{ id: '40', tenantId: '0', name: '运营管理员', status: 'ACTIVE' },
|
{ id: '40', tenantId: '0', roleCode: 'PLATFORM_OPERATOR', name: '运营管理员', status: 'ACTIVE' },
|
||||||
{ id: '41', tenantId: '10001', name: '租户管理员', status: 'ACTIVE' },
|
{ id: '41', tenantId: '10001', roleCode: 'CUSTOMER_ADMIN', name: '客户管理员', status: 'ACTIVE' },
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
vi.mocked(listSharedReceivingOverseasWarehouseOptions).mockReset().mockResolvedValue([
|
||||||
|
{ id: '90071992547409931', warehouseCode: 'LAX-01', warehouseName: '洛杉矶收货仓', countryCode: 'US' },
|
||||||
|
])
|
||||||
|
vi.mocked(createPlatformManagedUser).mockReset().mockResolvedValue(user)
|
||||||
|
vi.mocked(updatePlatformManagedUser).mockReset().mockResolvedValue(user)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('loads a server page and only renders internal-user fields', async () => {
|
it('loads a server page and only renders internal-user fields', async () => {
|
||||||
@@ -143,6 +153,73 @@ describe('InternalUsersView platform internal user management', () => {
|
|||||||
expect(document.body.textContent).toContain('确认添加')
|
expect(document.body.textContent).toContain('确认添加')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('requires a shared receiving warehouse for WAREHOUSE_PICKER', async () => {
|
||||||
|
vi.mocked(getPlatformUserOptions).mockResolvedValueOnce({
|
||||||
|
tenants: [{ id: '0', name: '平台', status: 'ACTIVE' }],
|
||||||
|
roles: [
|
||||||
|
{ id: '40', tenantId: '0', roleCode: 'PLATFORM_OPERATOR', name: '运营管理员', status: 'ACTIVE' },
|
||||||
|
{ id: '4000005', tenantId: '0', roleCode: 'WAREHOUSE_PICKER', name: '仓库拣货员', status: 'ACTIVE' },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
const wrapper = await mountView()
|
||||||
|
await wrapper.findAll('button').find((button) => button.text().trim() === '新增用户')?.trigger('click')
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
expect(document.body.textContent).not.toContain('实际海外收货仓')
|
||||||
|
const roleSelect = wrapper.findAllComponents({ name: 'ElSelect' })
|
||||||
|
.find((component) => component.props('placeholder') === '选择角色')
|
||||||
|
expect(roleSelect).toBeDefined()
|
||||||
|
roleSelect?.vm.$emit('update:modelValue', ['4000005'])
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
expect(listSharedReceivingOverseasWarehouseOptions).toHaveBeenCalledOnce()
|
||||||
|
expect(document.body.textContent).toContain('实际海外收货仓')
|
||||||
|
const warehouseSelect = wrapper.findAllComponents({ name: 'ElSelect' })
|
||||||
|
.find((component) => component.props('placeholder') === '请选择平台共享海外收货仓')
|
||||||
|
expect(warehouseSelect).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('uses the stable role code instead of localized role names to activate the warehouse binding', () => {
|
||||||
|
expect(internalUsersSource).toContain("role.roleCode === 'WAREHOUSE_PICKER'")
|
||||||
|
expect(internalUsersSource).toContain('overseasWarehouseId: hasWarehousePickerRole.value ? form.overseasWarehouseId : null')
|
||||||
|
expect(internalUsersSource).not.toContain("role.name === '仓库拣货员'")
|
||||||
|
})
|
||||||
|
|
||||||
|
it('preserves the picker warehouse binding when editing a WAREHOUSE_PICKER user', async () => {
|
||||||
|
const pickerUser: PlatformManagedUser = {
|
||||||
|
...user,
|
||||||
|
roleIds: ['4000005'],
|
||||||
|
roleNames: ['仓库拣货员'],
|
||||||
|
overseasWarehouseId: '90071992547409931',
|
||||||
|
}
|
||||||
|
vi.mocked(listPlatformManagedUsers).mockResolvedValueOnce({
|
||||||
|
records: [pickerUser], page: 1, size: 10, total: 1, pages: 1,
|
||||||
|
})
|
||||||
|
vi.mocked(getPlatformUserOptions).mockResolvedValueOnce({
|
||||||
|
tenants: [{ id: '0', name: '平台', status: 'ACTIVE' }],
|
||||||
|
roles: [{ id: '4000005', tenantId: '0', roleCode: 'WAREHOUSE_PICKER', name: '仓库拣货员', status: 'ACTIVE' }],
|
||||||
|
})
|
||||||
|
vi.mocked(updatePlatformManagedUser).mockResolvedValueOnce(pickerUser)
|
||||||
|
|
||||||
|
const wrapper = await mountView()
|
||||||
|
const setupState = (wrapper.vm.$ as unknown as {
|
||||||
|
setupState: { openEdit: (row: PlatformManagedUser) => void }
|
||||||
|
}).setupState
|
||||||
|
setupState.openEdit(pickerUser)
|
||||||
|
await flushPromises()
|
||||||
|
await wrapper.findAll('button').find((button) => button.text().trim() === '保存修改')?.trigger('click')
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
expect(updatePlatformManagedUser).toHaveBeenCalledWith('30', {
|
||||||
|
displayName: '张伟',
|
||||||
|
phone: '13800138000',
|
||||||
|
email: 'zhangwei@example.com',
|
||||||
|
tenantId: '0',
|
||||||
|
roleIds: ['4000005'],
|
||||||
|
overseasWarehouseId: '90071992547409931',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it('requests server-side pages and filters instead of slicing locally', async () => {
|
it('requests server-side pages and filters instead of slicing locally', async () => {
|
||||||
const wrapper = await mountView()
|
const wrapper = await mountView()
|
||||||
await wrapper.find('button[aria-label="第 2 页"]').trigger('click')
|
await wrapper.find('button[aria-label="第 2 页"]').trigger('click')
|
||||||
|
|||||||
@@ -6,11 +6,13 @@ import {
|
|||||||
changePlatformManagedUserStatus,
|
changePlatformManagedUserStatus,
|
||||||
createPlatformManagedUser,
|
createPlatformManagedUser,
|
||||||
getPlatformUserOptions,
|
getPlatformUserOptions,
|
||||||
|
listSharedReceivingOverseasWarehouseOptions,
|
||||||
listPlatformManagedUsers,
|
listPlatformManagedUsers,
|
||||||
resetPlatformManagedUserPassword,
|
resetPlatformManagedUserPassword,
|
||||||
updatePlatformManagedUser,
|
updatePlatformManagedUser,
|
||||||
type PlatformManagedUser,
|
type PlatformManagedUser,
|
||||||
type PlatformUserOptions,
|
type PlatformUserOptions,
|
||||||
|
type SharedReceivingOverseasWarehouseOption,
|
||||||
} from '@/modules/iam/api/platformIam'
|
} from '@/modules/iam/api/platformIam'
|
||||||
import { getApiErrorDetails, getApiErrorMessage, type ApiErrorDetails } from '@/shared/api/http'
|
import { getApiErrorDetails, getApiErrorMessage, type ApiErrorDetails } from '@/shared/api/http'
|
||||||
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
||||||
@@ -30,6 +32,9 @@ const access = useAccessPreviewStore()
|
|||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
const users = ref<PlatformManagedUser[]>([])
|
const users = ref<PlatformManagedUser[]>([])
|
||||||
const options = ref<PlatformUserOptions>({ tenants: [], roles: [] })
|
const options = ref<PlatformUserOptions>({ tenants: [], roles: [] })
|
||||||
|
const overseasWarehouses = ref<SharedReceivingOverseasWarehouseOption[]>([])
|
||||||
|
const overseasWarehousesLoading = ref(false)
|
||||||
|
const overseasWarehousesError = ref<ApiErrorDetails | null>(null)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
const loadError = ref<ApiErrorDetails | null>(null)
|
const loadError = ref<ApiErrorDetails | null>(null)
|
||||||
@@ -38,6 +43,7 @@ const formRef = ref<FormInstance>()
|
|||||||
const editingUser = ref<PlatformManagedUser | null>(null)
|
const editingUser = ref<PlatformManagedUser | null>(null)
|
||||||
const rowActionId = ref('')
|
const rowActionId = ref('')
|
||||||
let loadRequestId = 0
|
let loadRequestId = 0
|
||||||
|
let warehouseRequestId = 0
|
||||||
const page = ref(1)
|
const page = ref(1)
|
||||||
const pageSize = ref(10)
|
const pageSize = ref(10)
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
@@ -72,6 +78,7 @@ const emptyForm = () => ({
|
|||||||
email: '',
|
email: '',
|
||||||
tenantId: '0',
|
tenantId: '0',
|
||||||
roleIds: [] as string[],
|
roleIds: [] as string[],
|
||||||
|
overseasWarehouseId: '',
|
||||||
password: '',
|
password: '',
|
||||||
confirmPassword: '',
|
confirmPassword: '',
|
||||||
status: 'ACTIVE' as 'ACTIVE' | 'DISABLED',
|
status: 'ACTIVE' as 'ACTIVE' | 'DISABLED',
|
||||||
@@ -98,6 +105,10 @@ const availableRoles = computed(() => options.value.roles.filter((role) =>
|
|||||||
String(role.tenantId) === '0'
|
String(role.tenantId) === '0'
|
||||||
&& (role.status === 'ACTIVE' || form.roleIds.includes(String(role.id))),
|
&& (role.status === 'ACTIVE' || form.roleIds.includes(String(role.id))),
|
||||||
))
|
))
|
||||||
|
const warehousePickerRoleIds = computed(() => new Set(
|
||||||
|
availableRoles.value.filter((role) => role.roleCode === 'WAREHOUSE_PICKER').map((role) => String(role.id)),
|
||||||
|
))
|
||||||
|
const hasWarehousePickerRole = computed(() => form.roleIds.some((roleId) => warehousePickerRoleIds.value.has(roleId)))
|
||||||
|
|
||||||
const formRules = computed<FormRules>(() => ({
|
const formRules = computed<FormRules>(() => ({
|
||||||
username: editingUser.value ? [] : [
|
username: editingUser.value ? [] : [
|
||||||
@@ -111,6 +122,9 @@ const formRules = computed<FormRules>(() => ({
|
|||||||
],
|
],
|
||||||
email: editingUser.value && !canEdit.value ? [] : [{ type: 'email', message: '请输入有效的邮箱地址', trigger: 'blur' }],
|
email: editingUser.value && !canEdit.value ? [] : [{ type: 'email', message: '请输入有效的邮箱地址', trigger: 'blur' }],
|
||||||
roleIds: editingUser.value && !canEdit.value ? [] : [{ type: 'array', required: true, min: 1, message: '请选择角色', trigger: 'change' }],
|
roleIds: editingUser.value && !canEdit.value ? [] : [{ type: 'array', required: true, min: 1, message: '请选择角色', trigger: 'change' }],
|
||||||
|
overseasWarehouseId: !hasWarehousePickerRole.value || (editingUser.value && !canEdit.value) ? [] : [
|
||||||
|
{ required: true, message: '请选择实际海外收货仓', trigger: 'change' },
|
||||||
|
],
|
||||||
password: editingUser.value && !form.resetPassword ? [] : [
|
password: editingUser.value && !form.resetPassword ? [] : [
|
||||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||||
{ pattern: /^(?=.*[A-Za-z])(?=.*\d).{8,64}$/, message: '密码为 8-64 位,且必须包含字母和数字', trigger: 'blur' },
|
{ pattern: /^(?=.*[A-Za-z])(?=.*\d).{8,64}$/, message: '密码为 8-64 位,且必须包含字母和数字', trigger: 'blur' },
|
||||||
@@ -200,6 +214,7 @@ function openEdit(row: PlatformManagedUser) {
|
|||||||
email: row.email ?? '',
|
email: row.email ?? '',
|
||||||
tenantId: '0',
|
tenantId: '0',
|
||||||
roleIds: row.roleIds.map(String),
|
roleIds: row.roleIds.map(String),
|
||||||
|
overseasWarehouseId: row.overseasWarehouseId == null ? '' : String(row.overseasWarehouseId),
|
||||||
password: '',
|
password: '',
|
||||||
confirmPassword: '',
|
confirmPassword: '',
|
||||||
status: row.status,
|
status: row.status,
|
||||||
@@ -209,6 +224,23 @@ function openEdit(row: PlatformManagedUser) {
|
|||||||
nextTick(() => formRef.value?.clearValidate())
|
nextTick(() => formRef.value?.clearValidate())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadOverseasWarehouses() {
|
||||||
|
const requestId = ++warehouseRequestId
|
||||||
|
overseasWarehousesLoading.value = true
|
||||||
|
overseasWarehousesError.value = null
|
||||||
|
try {
|
||||||
|
const records = await listSharedReceivingOverseasWarehouseOptions()
|
||||||
|
if (requestId !== warehouseRequestId) return
|
||||||
|
overseasWarehouses.value = records
|
||||||
|
} catch (error) {
|
||||||
|
if (requestId !== warehouseRequestId) return
|
||||||
|
overseasWarehouses.value = []
|
||||||
|
overseasWarehousesError.value = getApiErrorDetails(error, '共享海外收货仓加载失败,请重试')
|
||||||
|
} finally {
|
||||||
|
if (requestId === warehouseRequestId) overseasWarehousesLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function isCurrentUser(row: PlatformManagedUser | null) {
|
function isCurrentUser(row: PlatformManagedUser | null) {
|
||||||
if (!row) return false
|
if (!row) return false
|
||||||
return String(row.userId) === auth.user?.id || String(row.authUserId) === auth.user?.id
|
return String(row.userId) === auth.user?.id || String(row.authUserId) === auth.user?.id
|
||||||
@@ -231,6 +263,7 @@ async function saveUser() {
|
|||||||
email: form.email.trim(),
|
email: form.email.trim(),
|
||||||
tenantId: '0',
|
tenantId: '0',
|
||||||
roleIds: form.roleIds,
|
roleIds: form.roleIds,
|
||||||
|
overseasWarehouseId: hasWarehousePickerRole.value ? form.overseasWarehouseId : null,
|
||||||
password: form.password,
|
password: form.password,
|
||||||
status: form.status,
|
status: form.status,
|
||||||
})
|
})
|
||||||
@@ -244,6 +277,7 @@ async function saveUser() {
|
|||||||
email: form.email.trim(),
|
email: form.email.trim(),
|
||||||
tenantId: '0',
|
tenantId: '0',
|
||||||
roleIds: form.roleIds,
|
roleIds: form.roleIds,
|
||||||
|
overseasWarehouseId: hasWarehousePickerRole.value ? form.overseasWarehouseId : null,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (canChangeStatus.value && form.status !== row.status) {
|
if (canChangeStatus.value && form.status !== row.status) {
|
||||||
@@ -296,6 +330,14 @@ function applyFilters() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
watch([page, pageSize], () => void loadUsers())
|
watch([page, pageSize], () => void loadUsers())
|
||||||
|
watch(hasWarehousePickerRole, (selected) => {
|
||||||
|
if (!selected) {
|
||||||
|
form.overseasWarehouseId = ''
|
||||||
|
overseasWarehousesError.value = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
void loadOverseasWarehouses()
|
||||||
|
})
|
||||||
onMounted(loadInitialData)
|
onMounted(loadInitialData)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -377,6 +419,20 @@ onMounted(loadInitialData)
|
|||||||
<el-option label="禁用" value="DISABLED" />
|
<el-option label="禁用" value="DISABLED" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item v-if="hasWarehousePickerRole" label="实际海外收货仓" prop="overseasWarehouseId" required>
|
||||||
|
<el-select v-model="form.overseasWarehouseId" filterable
|
||||||
|
:loading="overseasWarehousesLoading" :disabled="Boolean(editingUser) && !canEdit"
|
||||||
|
placeholder="请选择平台共享海外收货仓">
|
||||||
|
<el-option v-for="warehouse in overseasWarehouses" :key="warehouse.id"
|
||||||
|
:label="`${warehouse.warehouseName}(${warehouse.warehouseCode} · ${warehouse.countryCode})`"
|
||||||
|
:value="warehouse.id" />
|
||||||
|
</el-select>
|
||||||
|
<p v-if="overseasWarehousesError" class="field-error">
|
||||||
|
{{ overseasWarehousesError.message }}
|
||||||
|
<el-button link type="primary" :loading="overseasWarehousesLoading" @click="loadOverseasWarehouses">重新加载</el-button>
|
||||||
|
</p>
|
||||||
|
<p v-else class="field-help">仓库将写入登录 Token,海外 PDA 不提供仓库切换。</p>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item v-if="editingUser" label="最后登录">
|
<el-form-item v-if="editingUser" label="最后登录">
|
||||||
<el-input :model-value="formatLogin(editingUser.lastLoginAt)" disabled />
|
<el-input :model-value="formatLogin(editingUser.lastLoginAt)" disabled />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -401,6 +457,9 @@ onMounted(loadInitialData)
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.password-fields { margin-top: 16px; }
|
.password-fields { margin-top: 16px; }
|
||||||
|
.field-help, .field-error { margin: 6px 0 0; font-size: 12px; line-height: 1.5; }
|
||||||
|
.field-help { color: var(--cc-text-secondary); }
|
||||||
|
.field-error { color: var(--el-color-danger); }
|
||||||
:global(.internal-user-editor-dialog) { max-height: min(760px, calc(100vh - 32px)); display: flex; flex-direction: column; }
|
:global(.internal-user-editor-dialog) { max-height: min(760px, calc(100vh - 32px)); display: flex; flex-direction: column; }
|
||||||
:global(.internal-user-editor-dialog .el-dialog__body) { min-height: 0; overflow-y: auto; }
|
:global(.internal-user-editor-dialog .el-dialog__body) { min-height: 0; overflow-y: auto; }
|
||||||
:global(.internal-user-editor-dialog .el-dialog__footer) { flex: 0 0 auto; }
|
:global(.internal-user-editor-dialog .el-dialog__footer) { flex: 0 0 auto; }
|
||||||
|
|||||||
Reference in New Issue
Block a user