优化
This commit is contained in:
@@ -20,7 +20,7 @@ import {
|
||||
|
||||
const payload = {
|
||||
id: '9007199254740995',
|
||||
tenantId: '10001',
|
||||
tenantId: '0',
|
||||
rateCode: 'RATE-US-WEST',
|
||||
rateVersion: 2,
|
||||
originWarehouseId: '20001',
|
||||
@@ -48,38 +48,36 @@ const payload = {
|
||||
describe('platformCarrierRates api', () => {
|
||||
beforeEach(() => vi.clearAllMocks())
|
||||
|
||||
it('queries the real carrier-rate path and keeps every long id as a string', async () => {
|
||||
it('queries the platform-wide carrier-rate path and keeps every long id as a string', async () => {
|
||||
mocks.get.mockResolvedValue({ data: [payload] })
|
||||
|
||||
const result = await listPlatformCarrierRates({ customerId: ' 10001 ', rateCode: ' RATE-US ' })
|
||||
const result = await listPlatformCarrierRates({ rateCode: ' RATE-US ' })
|
||||
|
||||
expect(mocks.get).toHaveBeenCalledWith('/platform/v1/fba-pricing/carrier-rates', {
|
||||
params: { tenantId: '10001', rateCode: 'RATE-US' },
|
||||
params: { rateCode: 'RATE-US' },
|
||||
})
|
||||
expect(result[0]).toMatchObject({
|
||||
id: '9007199254740995',
|
||||
tenantId: '10001',
|
||||
tenantId: '0',
|
||||
originWarehouseId: '20001',
|
||||
carrierId: '30001',
|
||||
})
|
||||
})
|
||||
|
||||
it('omits the compatibility tenant parameter for an all-customer list query', async () => {
|
||||
it('omits the tenant parameter for the platform-wide list query', async () => {
|
||||
mocks.get.mockResolvedValue({ data: [payload] })
|
||||
|
||||
await listPlatformCarrierRates()
|
||||
await listPlatformCarrierRates({ customerId: '', rateCode: ' ' })
|
||||
await listPlatformCarrierRates({ rateCode: ' ' })
|
||||
|
||||
expect(mocks.get).toHaveBeenNthCalledWith(1, '/platform/v1/fba-pricing/carrier-rates', { params: {} })
|
||||
expect(mocks.get).toHaveBeenNthCalledWith(2, '/platform/v1/fba-pricing/carrier-rates', { params: {} })
|
||||
})
|
||||
|
||||
it('loads a version detail with tenant scope', async () => {
|
||||
it('loads a platform-wide version detail without tenant scope', async () => {
|
||||
mocks.get.mockResolvedValue({ data: payload })
|
||||
await getPlatformCarrierRate('10001', '9001')
|
||||
expect(mocks.get).toHaveBeenCalledWith('/platform/v1/fba-pricing/carrier-rates/9001', {
|
||||
params: { tenantId: '10001' },
|
||||
})
|
||||
await getPlatformCarrierRate('9001')
|
||||
expect(mocks.get).toHaveBeenCalledWith('/platform/v1/fba-pricing/carrier-rates/9001')
|
||||
})
|
||||
|
||||
it('creates immutable versions and retires an active version', async () => {
|
||||
@@ -103,14 +101,10 @@ describe('platformCarrierRates api', () => {
|
||||
remark: null,
|
||||
}
|
||||
|
||||
await createPlatformCarrierRateVersion('10001', request)
|
||||
await retirePlatformCarrierRate('10001', '9001')
|
||||
await createPlatformCarrierRateVersion(request)
|
||||
await retirePlatformCarrierRate('9001')
|
||||
|
||||
expect(mocks.post).toHaveBeenCalledWith('/platform/v1/fba-pricing/carrier-rates/versions', request, {
|
||||
params: { tenantId: '10001' },
|
||||
})
|
||||
expect(mocks.delete).toHaveBeenCalledWith('/platform/v1/fba-pricing/carrier-rates/9001', {
|
||||
params: { tenantId: '10001' },
|
||||
})
|
||||
expect(mocks.post).toHaveBeenCalledWith('/platform/v1/fba-pricing/carrier-rates/versions', request)
|
||||
expect(mocks.delete).toHaveBeenCalledWith('/platform/v1/fba-pricing/carrier-rates/9001')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -59,7 +59,6 @@ export interface CreatePlatformCarrierRateVersionRequest {
|
||||
}
|
||||
|
||||
export interface PlatformCarrierRateQuery {
|
||||
customerId?: string
|
||||
rateCode?: string
|
||||
}
|
||||
|
||||
@@ -87,38 +86,32 @@ function normalizeCarrierRate(value: PlatformCarrierRatePayload): PlatformCarrie
|
||||
}
|
||||
|
||||
export async function listPlatformCarrierRates(query: PlatformCarrierRateQuery = {}) {
|
||||
const customerId = query.customerId?.trim()
|
||||
const rateCode = query.rateCode?.trim()
|
||||
const response = await http.get<ApiEnvelope<PlatformCarrierRatePayload[]>, ApiEnvelope<PlatformCarrierRatePayload[]>>(
|
||||
basePath,
|
||||
{ params: { ...(customerId ? { tenantId: customerId } : {}), ...(rateCode ? { rateCode } : {}) } },
|
||||
{ params: rateCode ? { rateCode } : {} },
|
||||
)
|
||||
return unwrapApiResponse(response).map(normalizeCarrierRate)
|
||||
}
|
||||
|
||||
export async function getPlatformCarrierRate(tenantId: string, id: string) {
|
||||
export async function getPlatformCarrierRate(id: string) {
|
||||
const response = await http.get<ApiEnvelope<PlatformCarrierRatePayload>, ApiEnvelope<PlatformCarrierRatePayload>>(
|
||||
`${basePath}/${id}`,
|
||||
{ params: { tenantId } },
|
||||
)
|
||||
return normalizeCarrierRate(unwrapApiResponse(response))
|
||||
}
|
||||
|
||||
export async function createPlatformCarrierRateVersion(
|
||||
tenantId: string,
|
||||
payload: CreatePlatformCarrierRateVersionRequest,
|
||||
) {
|
||||
const response = await http.post<ApiEnvelope<PlatformCarrierRatePayload>, ApiEnvelope<PlatformCarrierRatePayload>>(
|
||||
`${basePath}/versions`,
|
||||
payload,
|
||||
{ params: { tenantId } },
|
||||
)
|
||||
return normalizeCarrierRate(unwrapApiResponse(response))
|
||||
}
|
||||
|
||||
export async function retirePlatformCarrierRate(tenantId: string, id: string) {
|
||||
const response = await http.delete<ApiEnvelope<null>, ApiEnvelope<null>>(`${basePath}/${id}`, {
|
||||
params: { tenantId },
|
||||
})
|
||||
export async function retirePlatformCarrierRate(id: string) {
|
||||
const response = await http.delete<ApiEnvelope<null>, ApiEnvelope<null>>(`${basePath}/${id}`)
|
||||
return unwrapApiResponse(response)
|
||||
}
|
||||
|
||||
@@ -7,10 +7,10 @@ const queryToolbarSource = readFileSync(join(process.cwd(), 'src/shared/componen
|
||||
|
||||
describe('CarrierRatesView source contract', () => {
|
||||
it('keeps operation column first and covers every required business field', () => {
|
||||
const customerColumn = "key: 'customer', label: '所属企业'"
|
||||
const scopeColumn = "key: 'scope', label: '适用范围'"
|
||||
const destinationColumn = "key: 'destinationCode', prop: 'destinationCode'"
|
||||
expect(source.indexOf("key: 'actions'")).toBeLessThan(source.indexOf(customerColumn))
|
||||
expect(source.indexOf(customerColumn)).toBeLessThan(source.indexOf(destinationColumn))
|
||||
expect(source.indexOf("key: 'actions'")).toBeLessThan(source.indexOf(scopeColumn))
|
||||
expect(source.indexOf(scopeColumn)).toBeLessThan(source.indexOf(destinationColumn))
|
||||
expect(source.indexOf(destinationColumn)).toBeLessThan(source.indexOf("key: 'rate'"))
|
||||
for (const label of ['起运仓', '业务类型', '渠道编码', '船司', 'FBA 目的仓', '计费单位', '单价', '币种', '固定附加费', '有效期', '备注', '生效时间', '结束时间']) {
|
||||
expect(source).toContain(label)
|
||||
@@ -27,41 +27,35 @@ describe('CarrierRatesView source contract', () => {
|
||||
expect(source).not.toContain('编辑价格')
|
||||
})
|
||||
|
||||
it('keeps the customer company scope in the query toolbar without exposing tenant concepts', () => {
|
||||
expect(source).toContain("key: 'customerId'")
|
||||
expect(source.indexOf("key: 'customerId'")).toBeLessThan(source.indexOf("key: 'rateCode'"))
|
||||
expect(source).toContain("'aria-label': '所属企业'")
|
||||
expect(source).toContain('listPlatformTenants')
|
||||
expect(source).toContain('masterDataLoaded')
|
||||
it('treats carrier rates as platform-wide data without a customer selector', () => {
|
||||
expect(source).toContain("key: 'scope', label: '适用范围'")
|
||||
expect(source).toContain('全部客户')
|
||||
expect(source).not.toContain("key: 'customerId'")
|
||||
expect(source).not.toContain('listPlatformTenants')
|
||||
expect(source).not.toContain('PlatformTenantSelector')
|
||||
expect(source).not.toContain('selectedTenantId')
|
||||
expect(source).not.toContain('租户')
|
||||
})
|
||||
|
||||
it('uses semantic compact filter widths and keeps the customer selection empty by default', () => {
|
||||
it('uses semantic compact filter widths without a customer field', () => {
|
||||
for (const field of [
|
||||
"key: 'customerId',\n type: 'select',\n width: 280",
|
||||
"key: 'rateCode', type: 'input', width: 220",
|
||||
"key: 'destinationCode', type: 'input', width: 220",
|
||||
"key: 'channelCode', type: 'input', width: 220",
|
||||
"key: 'chargeUnit', type: 'select', width: 180",
|
||||
"key: 'status', type: 'select', width: 160",
|
||||
]) expect(source).toContain(field)
|
||||
expect(source).toContain("customerId: ''")
|
||||
expect(source).toContain("placeholder: customerLoadError.value ? '企业列表加载失败' : '全部客户'")
|
||||
expect(source).not.toContain(':search-on-reset="false"')
|
||||
expect(source).not.toContain('请先选择所属企业')
|
||||
expect(source).not.toContain('请选择所属企业并查询价格数据')
|
||||
expect(source).not.toContain('customers.value[0]')
|
||||
})
|
||||
|
||||
it('loads all customers initially and after reset while preserving row scope for record actions', () => {
|
||||
it('loads the platform-wide list and keeps row actions independent of customer scope', () => {
|
||||
expect(source).toContain('void loadRateData()')
|
||||
expect(source).toContain("listPlatformCarrierRates(customerId ? { customerId } : {})")
|
||||
expect(source).toContain('loadRateData(customerId)')
|
||||
expect(source).toContain('getPlatformCarrierRate(String(row.tenantId), row.id)')
|
||||
expect(source).toContain('retirePlatformCarrierRate(String(row.tenantId), row.id)')
|
||||
expect(source).toContain('customerLabel(detail.tenantId)')
|
||||
expect(source).toContain('listPlatformCarrierRates({ rateCode: filters.value.rateCode })')
|
||||
expect(source).toContain('getPlatformCarrierRate(row.id)')
|
||||
expect(source).toContain('retirePlatformCarrierRate(row.id)')
|
||||
expect(source).toContain('<dt>适用范围</dt><dd>全部客户</dd>')
|
||||
})
|
||||
|
||||
it('loads shared carrier master data under the platform public scope', () => {
|
||||
@@ -73,8 +67,8 @@ describe('CarrierRatesView source contract', () => {
|
||||
expect(source).not.toContain('loadCustomerMasterData')
|
||||
})
|
||||
|
||||
it('keeps customer rate queries independent from edit-only master data', () => {
|
||||
expect(source).toContain('await loadRateData(customerId)')
|
||||
it('keeps public rate queries independent from edit-only master data', () => {
|
||||
expect(source).toContain('await loadRateData()')
|
||||
expect(source).not.toContain('loadPlatformMasterData(customerId)')
|
||||
})
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'elem
|
||||
import { CalendarClock, Eye, Plus, Power, Ship, Warehouse } from '@lucide/vue'
|
||||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
import { useUnsavedWorkRegistration } from '@/core/unsaved-work/useUnsavedWorkRegistration'
|
||||
import { listPlatformTenants, type PlatformTenantResource as PlatformCustomerResource } from '@/modules/iam'
|
||||
import { listPlatformWarehouses, type PlatformWarehouseResource } from '@/modules/warehouses'
|
||||
import { listPlatformPartners, type PlatformPartnerResource } from '@/modules/master-data'
|
||||
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
||||
@@ -26,7 +25,6 @@ import {
|
||||
} from '../api/platformCarrierRates'
|
||||
|
||||
type RateFilters = QueryModel & {
|
||||
customerId: string
|
||||
rateCode: string
|
||||
destinationCode: string
|
||||
channelCode: string
|
||||
@@ -51,9 +49,6 @@ interface RateForm {
|
||||
}
|
||||
|
||||
const access = useAccessPreviewStore()
|
||||
const customers = ref<PlatformCustomerResource[]>([])
|
||||
const customersLoading = ref(false)
|
||||
const customerLoadError = ref<ApiErrorDetails | null>(null)
|
||||
const masterDataLoaded = ref(false)
|
||||
const rows = ref<PlatformCarrierRateResource[]>([])
|
||||
const warehouses = ref<PlatformWarehouseResource[]>([])
|
||||
@@ -69,7 +64,6 @@ const detailLoading = ref(false)
|
||||
const saving = ref(false)
|
||||
const detail = ref<PlatformCarrierRateResource | null>(null)
|
||||
const retiringIds = ref(new Set<string>())
|
||||
const editorCustomerId = ref('')
|
||||
const formRef = ref<FormInstance>()
|
||||
let loadSequence = 0
|
||||
let masterDataSequence = 0
|
||||
@@ -100,18 +94,9 @@ const { captureBaseline } = useUnsavedWorkRegistration({
|
||||
requiredPermission,
|
||||
})
|
||||
|
||||
const filters = ref<RateFilters>({ customerId: '', rateCode: '', destinationCode: '', channelCode: '', chargeUnit: '', status: '' })
|
||||
const filters = ref<RateFilters>({ rateCode: '', destinationCode: '', channelCode: '', chargeUnit: '', status: '' })
|
||||
const appliedFilters = ref<RateFilters>({ ...filters.value })
|
||||
const customerSwitchDisabled = computed(() => editorOpen.value || detailOpen.value || saving.value || detailLoading.value || retiringIds.value.size > 0)
|
||||
const queryFields = computed<QueryField[]>(() => [
|
||||
{
|
||||
key: 'customerId',
|
||||
type: 'select',
|
||||
width: 280,
|
||||
placeholder: customerLoadError.value ? '企业列表加载失败' : '全部客户',
|
||||
props: { 'aria-label': '所属企业', filterable: true, disabled: customersLoading.value || Boolean(customerLoadError.value) || customerSwitchDisabled.value },
|
||||
options: customers.value.map((customer) => ({ value: String(customer.id), label: `${customer.tenantName}(${customer.tenantCode || customer.id})` })),
|
||||
},
|
||||
{ key: 'rateCode', type: 'input', width: 220, placeholder: '价格编码', props: { 'aria-label': '价格编码' } },
|
||||
{ key: 'destinationCode', type: 'input', width: 220, placeholder: 'FBA 目的仓编码', props: { 'aria-label': 'FBA 目的仓编码' } },
|
||||
{ key: 'channelCode', type: 'input', width: 220, placeholder: '渠道编码', props: { 'aria-label': '渠道编码' } },
|
||||
@@ -131,7 +116,7 @@ const queryFields = computed<QueryField[]>(() => [
|
||||
|
||||
const columns: DataTableColumn[] = [
|
||||
{ key: 'actions', label: '操作', width: 230, fixed: 'left', slotName: 'actions' },
|
||||
{ key: 'customer', label: '所属企业', minWidth: 200, slotName: 'customer' },
|
||||
{ key: 'scope', label: '适用范围', minWidth: 140, slotName: 'scope' },
|
||||
{ key: 'destinationCode', prop: 'destinationCode', label: 'FBA 目的仓', minWidth: 135 },
|
||||
{ key: 'rate', label: '价格版本', minWidth: 180, slotName: 'rate' },
|
||||
{ key: 'originWarehouseName', prop: 'originWarehouseName', label: '起运仓', minWidth: 170, showOverflowTooltip: true },
|
||||
@@ -188,10 +173,6 @@ const rules: FormRules<RateForm> = {
|
||||
effectiveTo: [{ validator: validateEffectiveTo, trigger: 'change' }],
|
||||
}
|
||||
|
||||
watch(() => filters.value.customerId, () => {
|
||||
page.value = 1
|
||||
})
|
||||
|
||||
watch(
|
||||
() => access.can('firstleg:fba-pricing:rate:edit'),
|
||||
(canEdit) => {
|
||||
@@ -214,43 +195,16 @@ function statusLabel(status: CarrierRateStatus) {
|
||||
return status === 'ACTIVE' ? '启用' : '停用'
|
||||
}
|
||||
|
||||
function customerLabel(customerId: string) {
|
||||
const customer = customers.value.find((item) => String(item.id) === String(customerId))
|
||||
return customer ? `${customer.tenantName}(${customer.tenantCode || '未设置编码'})` : '客户信息待加载'
|
||||
}
|
||||
|
||||
function nullable(value: string) {
|
||||
return value.trim() || null
|
||||
}
|
||||
|
||||
async function loadCustomers() {
|
||||
customersLoading.value = true
|
||||
customerLoadError.value = null
|
||||
try {
|
||||
const records: PlatformCustomerResource[] = []
|
||||
let currentPage = 1
|
||||
let pages = 1
|
||||
do {
|
||||
const result = await listPlatformTenants({ page: currentPage, size: 200, status: 'ACTIVE' })
|
||||
records.push(...result.records)
|
||||
pages = Math.max(1, result.pages)
|
||||
currentPage += 1
|
||||
} while (currentPage <= pages)
|
||||
customers.value = records
|
||||
} catch (error) {
|
||||
customers.value = []
|
||||
customerLoadError.value = getApiErrorDetails(error, '企业列表加载失败,暂时无法按所属企业筛选或显示企业名称;价格列表仍可查询')
|
||||
} finally {
|
||||
customersLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRateData(customerId = filters.value.customerId) {
|
||||
async function loadRateData() {
|
||||
const sequence = ++loadSequence
|
||||
loading.value = true
|
||||
loadError.value = null
|
||||
try {
|
||||
const rateResult = await listPlatformCarrierRates(customerId ? { customerId } : {})
|
||||
const rateResult = await listPlatformCarrierRates({ rateCode: filters.value.rateCode })
|
||||
if (sequence !== loadSequence) return
|
||||
rows.value = rateResult
|
||||
} catch (error) {
|
||||
@@ -292,9 +246,7 @@ async function loadPlatformMasterData() {
|
||||
}
|
||||
|
||||
async function applyQuery(model: QueryModel) {
|
||||
const customerId = String(model.customerId || '')
|
||||
appliedFilters.value = {
|
||||
customerId,
|
||||
rateCode: String(model.rateCode || ''),
|
||||
destinationCode: String(model.destinationCode || ''),
|
||||
channelCode: String(model.channelCode || ''),
|
||||
@@ -302,12 +254,11 @@ async function applyQuery(model: QueryModel) {
|
||||
status: (model.status || '') as CarrierRateStatus | '',
|
||||
}
|
||||
page.value = 1
|
||||
await loadRateData(customerId)
|
||||
await loadRateData()
|
||||
}
|
||||
|
||||
function resetQuery(model: QueryModel) {
|
||||
appliedFilters.value = {
|
||||
customerId: String(model.customerId || ''),
|
||||
rateCode: String(model.rateCode || ''),
|
||||
destinationCode: String(model.destinationCode || ''),
|
||||
channelCode: String(model.channelCode || ''),
|
||||
@@ -336,16 +287,10 @@ function resetForm(rate?: PlatformCarrierRateResource) {
|
||||
}
|
||||
|
||||
async function openCreate(rate?: PlatformCarrierRateResource) {
|
||||
const customerId = String(rate?.tenantId || filters.value.customerId || '')
|
||||
if (!customerId) {
|
||||
ElMessage.warning('新增价格版本需要先选择所属企业')
|
||||
return
|
||||
}
|
||||
if (!masterDataLoaded.value) {
|
||||
const loaded = await loadPlatformMasterData()
|
||||
if (!loaded) return
|
||||
}
|
||||
editorCustomerId.value = customerId
|
||||
resetForm(rate)
|
||||
captureBaseline()
|
||||
editorOpen.value = true
|
||||
@@ -357,7 +302,7 @@ async function openDetail(row: PlatformCarrierRateResource) {
|
||||
detailLoading.value = true
|
||||
detail.value = null
|
||||
try {
|
||||
detail.value = await getPlatformCarrierRate(String(row.tenantId), row.id)
|
||||
detail.value = await getPlatformCarrierRate(row.id)
|
||||
} catch (error) {
|
||||
const meta = getApiErrorDetails(error, '价格版本详情加载失败')
|
||||
ElMessage.error(meta.requestId ? `${meta.message}(requestId: ${meta.requestId})` : meta.message)
|
||||
@@ -399,8 +344,6 @@ function buildPayload(): CreatePlatformCarrierRateVersionRequest | null {
|
||||
}
|
||||
|
||||
async function saveVersion() {
|
||||
const customerId = editorCustomerId.value
|
||||
if (!customerId) return
|
||||
const valid = await formRef.value?.validate().catch(() => false)
|
||||
if (!valid) return
|
||||
const payload = buildPayload()
|
||||
@@ -410,10 +353,10 @@ async function saveVersion() {
|
||||
}
|
||||
saving.value = true
|
||||
try {
|
||||
await createPlatformCarrierRateVersion(customerId, payload)
|
||||
await createPlatformCarrierRateVersion(payload)
|
||||
editorOpen.value = false
|
||||
ElMessage.success('船司 FBA 价格新版本已创建')
|
||||
await loadRateData(filters.value.customerId)
|
||||
await loadRateData()
|
||||
} catch (error) {
|
||||
const meta = getApiErrorDetails(error, '价格版本创建失败,请检查有效期和匹配维度后重试')
|
||||
ElMessage.error(meta.requestId ? `${meta.message}(requestId: ${meta.requestId})` : meta.message)
|
||||
@@ -430,9 +373,9 @@ async function retireRate(row: PlatformCarrierRateResource) {
|
||||
{ type: 'warning', confirmButtonText: '确认停用', cancelButtonText: '取消' },
|
||||
)
|
||||
retiringIds.value = new Set(retiringIds.value).add(row.id)
|
||||
await retirePlatformCarrierRate(String(row.tenantId), row.id)
|
||||
await retirePlatformCarrierRate(row.id)
|
||||
ElMessage.success('价格版本已停用')
|
||||
await loadRateData(filters.value.customerId)
|
||||
await loadRateData()
|
||||
} catch (error) {
|
||||
if (error === 'cancel' || error === 'close') return
|
||||
const meta = getApiErrorDetails(error, '价格版本停用失败,请重试')
|
||||
@@ -450,7 +393,6 @@ function closeEditor() {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
void loadCustomers()
|
||||
void loadRateData()
|
||||
})
|
||||
</script>
|
||||
@@ -465,11 +407,6 @@ onMounted(() => {
|
||||
@reset="resetQuery"
|
||||
/>
|
||||
|
||||
<el-alert v-if="customerLoadError" type="warning" :closable="false" show-icon role="alert">
|
||||
<template #title><div class="api-error-alert-title"><span>{{ customerLoadError.message }}</span><ApiErrorMeta :details="customerLoadError" /></div></template>
|
||||
<el-button link type="warning" @click="loadCustomers">重新加载企业列表</el-button>
|
||||
</el-alert>
|
||||
|
||||
<LoadErrorState
|
||||
v-if="loadError"
|
||||
title="船司 FBA 价格加载失败"
|
||||
@@ -489,7 +426,7 @@ onMounted(() => {
|
||||
<el-button
|
||||
v-if="access.can('firstleg:fba-pricing:rate:edit')"
|
||||
type="primary"
|
||||
:disabled="!filters.customerId || masterDataLoading"
|
||||
:disabled="masterDataLoading"
|
||||
@click="openCreate()"
|
||||
><Plus :size="16" />新增价格版本</el-button>
|
||||
</div>
|
||||
@@ -524,7 +461,7 @@ onMounted(() => {
|
||||
><Power :size="15" />停用</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<template #customer="{ row }"><strong>{{ customerLabel(String((row as PlatformCarrierRateResource).tenantId)) }}</strong></template>
|
||||
<template #scope><strong>全部客户</strong></template>
|
||||
<template #rate="{ row }">
|
||||
<div class="primary-cell"><strong>{{ (row as PlatformCarrierRateResource).rateCode }}</strong><span>版本 V{{ (row as PlatformCarrierRateResource).rateVersion }}</span></div>
|
||||
</template>
|
||||
@@ -601,7 +538,7 @@ onMounted(() => {
|
||||
<div><strong>{{ detail.rateCode }}</strong><span>版本 V{{ detail.rateVersion }}</span></div>
|
||||
<StatusBadge :status="statusLabel(detail.status)" />
|
||||
</header>
|
||||
<section class="detail-section"><h3><Warehouse :size="17" />运输匹配</h3><dl class="detail-grid"><div><dt>所属企业</dt><dd>{{ customerLabel(detail.tenantId) }}</dd></div><div><dt>起运仓</dt><dd>{{ detail.originWarehouseName }}</dd></div><div><dt>FBA 目的仓</dt><dd>{{ detail.destinationCode }}</dd></div><div><dt>业务类型</dt><dd>{{ detail.businessType }}</dd></div><div><dt>渠道编码</dt><dd>{{ detail.channelCode }}</dd></div><div><dt>船司</dt><dd>{{ detail.carrierName }}</dd></div><div><dt>船司 ID</dt><dd>{{ detail.carrierId }}</dd></div></dl></section>
|
||||
<section class="detail-section"><h3><Warehouse :size="17" />运输匹配</h3><dl class="detail-grid"><div><dt>适用范围</dt><dd>全部客户</dd></div><div><dt>起运仓</dt><dd>{{ detail.originWarehouseName }}</dd></div><div><dt>FBA 目的仓</dt><dd>{{ detail.destinationCode }}</dd></div><div><dt>业务类型</dt><dd>{{ detail.businessType }}</dd></div><div><dt>渠道编码</dt><dd>{{ detail.channelCode }}</dd></div><div><dt>船司</dt><dd>{{ detail.carrierName }}</dd></div><div><dt>船司 ID</dt><dd>{{ detail.carrierId }}</dd></div></dl></section>
|
||||
<section class="detail-section"><h3><Ship :size="17" />计费口径</h3><dl class="detail-grid"><div><dt>计费单位</dt><dd>{{ detail.chargeUnit }}</dd></div><div><dt>币种</dt><dd>{{ detail.currency }}</dd></div><div><dt>单价</dt><dd>{{ formatMoney(detail.unitPrice, detail.currency) }}</dd></div><div><dt>固定附加费</dt><dd>{{ formatMoney(detail.additionalFee, detail.currency) }}</dd></div></dl></section>
|
||||
<section class="detail-section"><h3><CalendarClock :size="17" />版本有效期</h3><dl class="detail-grid"><div><dt>生效时间</dt><dd>{{ formatDateTime(detail.effectiveFrom) }}</dd></div><div><dt>结束时间</dt><dd>{{ formatDateTime(detail.effectiveTo) }}</dd></div><div><dt>更新时间</dt><dd>{{ formatDateTime(detail.updatedAt) }}</dd></div><div><dt>记录版本</dt><dd>{{ detail.version ?? '-' }}</dd></div></dl><p v-if="detail.remark" class="detail-remark">{{ detail.remark }}</p></section>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { http } from '@/shared/api/http'
|
||||
import { getPlatformUnifiedOrder, listPlatformUnifiedOrders } from './unifiedOrders'
|
||||
import {
|
||||
approvePlatformUnifiedOrder,
|
||||
assignPlatformUnifiedOrderCarrier,
|
||||
getPlatformUnifiedOrder,
|
||||
listPlatformUnifiedOrders,
|
||||
} from './unifiedOrders'
|
||||
|
||||
vi.mock('@/shared/api/http', () => ({
|
||||
http: { get: vi.fn() },
|
||||
http: { get: vi.fn(), post: vi.fn(), put: vi.fn() },
|
||||
unwrapApiResponse: <T>(response: { data: T }) => response.data,
|
||||
}))
|
||||
|
||||
describe('unified orders API contract', () => {
|
||||
beforeEach(() => vi.mocked(http.get).mockReset())
|
||||
beforeEach(() => {
|
||||
vi.mocked(http.get).mockReset()
|
||||
vi.mocked(http.post).mockReset()
|
||||
vi.mocked(http.put).mockReset()
|
||||
})
|
||||
|
||||
it('uses the C02 platform orders endpoint and contract filters', async () => {
|
||||
vi.mocked(http.get).mockResolvedValue({ data: { records: [], page: '2', size: '20', total: '0', pages: '0' } })
|
||||
@@ -62,7 +71,14 @@ describe('unified orders API contract', () => {
|
||||
expect(http.get).toHaveBeenCalledWith('/platform/v1/orders/90001', { params: { tenantId: '208' } })
|
||||
})
|
||||
|
||||
it('does not expose a guessed generic action endpoint', async () => {
|
||||
expect(http).not.toHaveProperty('post')
|
||||
it('uses the explicit first-leg carrier and approval boundaries for pending review', async () => {
|
||||
vi.mocked(http.put).mockResolvedValue({ data: null })
|
||||
vi.mocked(http.post).mockResolvedValue({ data: null })
|
||||
|
||||
await assignPlatformUnifiedOrderCarrier('208', '90001', '30001')
|
||||
await approvePlatformUnifiedOrder('208', '90001')
|
||||
|
||||
expect(http.put).toHaveBeenCalledWith('/platform/v1/orders/first-leg/90001/carrier', { carrierId: '30001' }, { params: { tenantId: '208' } })
|
||||
expect(http.post).toHaveBeenCalledWith('/platform/v1/orders/first-leg/90001/approve', undefined, { params: { tenantId: '208' } })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -116,6 +116,8 @@ export interface UnifiedOrderDetailOrderSnapshot {
|
||||
receivingWarehouseId: number | string | null
|
||||
receivingWarehouseName: string | null
|
||||
channelName: string | null
|
||||
carrierId: number | string | null
|
||||
carrierName: string | null
|
||||
createdAt: string | null
|
||||
updatedAt: string | null
|
||||
}
|
||||
@@ -271,6 +273,22 @@ export async function getPlatformUnifiedOrder(tenantId: string, orderId: string)
|
||||
return { ...result, summary: normalizeRow(result.summary) }
|
||||
}
|
||||
|
||||
export function assignPlatformUnifiedOrderCarrier(tenantId: string, orderId: string, carrierId: string) {
|
||||
return http.put<ApiEnvelope<void>, ApiEnvelope<void>>(
|
||||
`/platform/v1/orders/first-leg/${orderId}/carrier`,
|
||||
{ carrierId },
|
||||
{ params: { tenantId } },
|
||||
).then(unwrapApiResponse)
|
||||
}
|
||||
|
||||
export function approvePlatformUnifiedOrder(tenantId: string, orderId: string) {
|
||||
return http.post<ApiEnvelope<void>, ApiEnvelope<void>>(
|
||||
`/platform/v1/orders/first-leg/${orderId}/approve`,
|
||||
undefined,
|
||||
{ params: { tenantId } },
|
||||
).then(unwrapApiResponse)
|
||||
}
|
||||
|
||||
export function unifiedOrderStatusLabel(status: UnifiedOrderMainStatus) {
|
||||
return unifiedOrderStatusOptions.find((item) => item.value === status)?.label || status
|
||||
}
|
||||
|
||||
@@ -66,6 +66,17 @@ describe('UnifiedOrdersView contract', () => {
|
||||
expect(source).not.toContain('/actions/')
|
||||
})
|
||||
|
||||
it('implements pending review with an active public carrier assignment', () => {
|
||||
expect(source).toContain("row.nextAction === 'APPROVE_ORDER'")
|
||||
expect(source).toContain("can('platform:order:approve')")
|
||||
expect(source).toContain("listPlatformPartners({ page: currentPage, size: 200, type: 'CARRIER', status: 'ACTIVE' })")
|
||||
expect(source).toContain('await assignPlatformUnifiedOrderCarrier(tenantId, orderId, String(carrier.id))')
|
||||
expect(source).toContain('await approvePlatformUnifiedOrder(tenantId, orderId)')
|
||||
expect(source.indexOf('await assignPlatformUnifiedOrderCarrier')).toBeLessThan(source.indexOf('await approvePlatformUnifiedOrder'))
|
||||
expect(source).toContain('暂无启用的承运商,请先到往来单位维护承运商主数据。')
|
||||
expect(source).toContain("detail.order.order?.carrierName || '待平台指派'")
|
||||
})
|
||||
|
||||
it('provides unified detail, history and internal note states', () => {
|
||||
expect(source).toContain('getPlatformUnifiedOrder')
|
||||
expect(source).toContain('aria-label="状态历史"')
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { ChevronDown, ChevronUp, ClipboardList, Eye, RefreshCw, SlidersHorizontal } from '@lucide/vue'
|
||||
import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
|
||||
import { ChevronDown, ChevronUp, CircleCheck, ClipboardList, Eye, RefreshCw, SlidersHorizontal } from '@lucide/vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import {
|
||||
getPlatformUnifiedOrder,
|
||||
approvePlatformUnifiedOrder,
|
||||
assignPlatformUnifiedOrderCarrier,
|
||||
listPlatformUnifiedOrders,
|
||||
unifiedOrderStatusOptions,
|
||||
type UnifiedOrderDetailResource,
|
||||
@@ -12,9 +15,11 @@ import {
|
||||
} from '@/modules/unified-orders/api/unifiedOrders'
|
||||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
import { listPlatformTenants, type PlatformTenantResource } from '@/modules/iam'
|
||||
import { listPlatformPartners, type PlatformPartnerResource } from '@/modules/master-data'
|
||||
import { listAllPlatformWarehouses, type PlatformWarehouseSummaryResource } from '@/modules/warehouses'
|
||||
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
||||
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
||||
import AppDialog from '@/shared/components/AppDialog.vue'
|
||||
import AppDrawer from '@/shared/components/AppDrawer.vue'
|
||||
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
|
||||
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
||||
@@ -62,8 +67,20 @@ const detail = ref<UnifiedOrderDetailResource | null>(null)
|
||||
const detailLoading = ref(false)
|
||||
const detailError = ref<ApiErrorDetails | null>(null)
|
||||
const detailOpen = ref(false)
|
||||
const approvalOpen = ref(false)
|
||||
const approvalLoadingId = ref('')
|
||||
const approvalSaving = ref(false)
|
||||
const carrierLoading = ref(false)
|
||||
const carrierLoadError = ref<ApiErrorDetails | null>(null)
|
||||
const carriers = ref<PlatformPartnerResource[]>([])
|
||||
const approvalOrder = ref<UnifiedOrderResource | null>(null)
|
||||
const carrierFormRef = ref<FormInstance>()
|
||||
const carrierForm = reactive({ carrierId: '' })
|
||||
const showAdvancedFilters = ref(false)
|
||||
const filters = ref<FilterModel>(emptyFilters())
|
||||
const carrierRules: FormRules = {
|
||||
carrierId: [{ required: true, message: '请选择承运商', trigger: 'change' }],
|
||||
}
|
||||
|
||||
const orderId = computed(() => typeof route.params.orderId === 'string' ? route.params.orderId : '')
|
||||
|
||||
@@ -208,10 +225,85 @@ const nextActionLabels: Record<string, string> = {
|
||||
}
|
||||
|
||||
function nextActionBoundary(row: UnifiedOrderResource) {
|
||||
if (row.nextAction === 'APPROVE_ORDER') return ''
|
||||
const label = row.nextAction ? nextActionLabels[row.nextAction] || row.nextAction : ''
|
||||
return label ? `待接入:${label}` : ''
|
||||
}
|
||||
|
||||
function canApprove(row: UnifiedOrderResource) {
|
||||
return row.mainStatus === 'PENDING_REVIEW' && row.nextAction === 'APPROVE_ORDER' && can('platform:order:approve')
|
||||
}
|
||||
|
||||
async function loadCarriers() {
|
||||
carrierLoading.value = true
|
||||
carrierLoadError.value = null
|
||||
try {
|
||||
const records: PlatformPartnerResource[] = []
|
||||
let currentPage = 1
|
||||
let pages = 1
|
||||
do {
|
||||
const result = await listPlatformPartners({ page: currentPage, size: 200, type: 'CARRIER', status: 'ACTIVE' })
|
||||
records.push(...result.records)
|
||||
pages = Math.max(1, result.pages)
|
||||
currentPage += 1
|
||||
} while (currentPage <= pages)
|
||||
carriers.value = records.filter((item) => item.status === 'ACTIVE' && item.types.includes('CARRIER'))
|
||||
} catch (error) {
|
||||
carriers.value = []
|
||||
carrierLoadError.value = getApiErrorDetails(error, '承运商列表加载失败,请稍后重试')
|
||||
} finally {
|
||||
carrierLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function openApproval(row: UnifiedOrderResource) {
|
||||
if (!canApprove(row)) return
|
||||
approvalOrder.value = row
|
||||
const currentSnapshot = detail.value?.summary.orderId === row.orderId ? detail.value.order.order : null
|
||||
carrierForm.carrierId = currentSnapshot?.carrierId == null ? '' : String(currentSnapshot.carrierId)
|
||||
approvalOpen.value = true
|
||||
await nextTick()
|
||||
carrierFormRef.value?.clearValidate()
|
||||
await loadCarriers()
|
||||
}
|
||||
|
||||
function closeApproval() {
|
||||
if (approvalSaving.value) return
|
||||
approvalOpen.value = false
|
||||
approvalOrder.value = null
|
||||
carrierForm.carrierId = ''
|
||||
}
|
||||
|
||||
async function assignCarrierAndApprove() {
|
||||
if (!approvalOrder.value) return
|
||||
const valid = await carrierFormRef.value?.validate().catch(() => false)
|
||||
if (!valid) return
|
||||
const carrier = carriers.value.find((item) => String(item.id) === carrierForm.carrierId)
|
||||
if (!carrier) {
|
||||
ElMessage.error('所选承运商已停用或不存在,请重新选择')
|
||||
return
|
||||
}
|
||||
const order = approvalOrder.value
|
||||
const orderId = String(order.orderId)
|
||||
const tenantId = String(order.tenantId)
|
||||
approvalSaving.value = true
|
||||
approvalLoadingId.value = orderId
|
||||
try {
|
||||
await assignPlatformUnifiedOrderCarrier(tenantId, orderId, String(carrier.id))
|
||||
await approvePlatformUnifiedOrder(tenantId, orderId)
|
||||
approvalOpen.value = false
|
||||
await loadOrders()
|
||||
if (detail.value?.summary.orderId === order.orderId) await loadDetail()
|
||||
approvalOrder.value = null
|
||||
ElMessage.success('承运商已指派,订单审核通过')
|
||||
} catch (error) {
|
||||
ElMessage.error(getApiErrorDetails(error, '承运商指派或订单审核失败,请稍后重试').message)
|
||||
} finally {
|
||||
approvalSaving.value = false
|
||||
approvalLoadingId.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
async function loadCustomers() {
|
||||
customerLoading.value = true
|
||||
customerLoadError.value = null
|
||||
@@ -397,6 +489,7 @@ onMounted(() => {
|
||||
<template #actions="{ row }">
|
||||
<div class="row-actions">
|
||||
<el-button link type="primary" @click="openDetail(row as UnifiedOrderResource)"><Eye :size="15" />详情</el-button>
|
||||
<el-button v-if="canApprove(row as UnifiedOrderResource)" link type="success" :loading="approvalLoadingId === String((row as UnifiedOrderResource).orderId)" @click="openApproval(row as UnifiedOrderResource)"><CircleCheck :size="15" />审核通过</el-button>
|
||||
<el-button v-if="(row as UnifiedOrderResource).mainStatus === 'WAIT_MEASURE' && (row as UnifiedOrderResource).inboundId && can('platform:firstleg:inbound:view')" link type="primary" @click="openMeasurement(row as UnifiedOrderResource)">测量作业</el-button>
|
||||
<el-button v-if="workflowEligible(row as UnifiedOrderResource) && can('platform:firstleg:inbound:view')" link type="primary" @click="openWorkflow(row as UnifiedOrderResource)"><ClipboardList :size="15" />后半程作业</el-button>
|
||||
<span v-if="nextActionBoundary(row as UnifiedOrderResource)" class="action-boundary">{{ nextActionBoundary(row as UnifiedOrderResource) }}</span>
|
||||
@@ -418,13 +511,29 @@ onMounted(() => {
|
||||
<LoadErrorState v-if="detailError" title="订单详情暂时无法加载" :message="detailError.message" :loading="detailLoading" @retry="loadDetail"><template #details><ApiErrorMeta :details="detailError" /></template></LoadErrorState>
|
||||
<div v-else-if="detail" class="detail-content">
|
||||
<section class="detail-summary" aria-label="订单概览">
|
||||
<div class="detail-summary__header"><div><span class="eyebrow">订单主状态</span><h2>{{ detail.summary.orderNo || `订单 ${detail.summary.orderId}` }}</h2></div><div class="detail-summary__actions"><StatusBadge :status="statusLabel(detail.summary.mainStatus)" /><el-button v-if="workflowEligible(detail.summary) && can('platform:firstleg:inbound:view')" type="primary" plain @click="openWorkflow(detail.summary)"><ClipboardList :size="15" />后半程作业</el-button></div></div>
|
||||
<dl class="detail-grid"><div><dt>客户</dt><dd>{{ customerName(detail.summary) }}</dd></div><div><dt>入仓码</dt><dd>{{ detail.summary.inboundCode || '—' }}</dd></div><div><dt>平台收货仓</dt><dd>{{ detail.summary.receivingWarehouseName || '—' }}</dd></div><div><dt>目的仓</dt><dd>{{ detail.summary.destinationWarehouseName || '—' }}</dd></div><div><dt>下一步</dt><dd>{{ nextActionBoundary(detail.summary) || '—' }}</dd></div><div><dt>最近更新</dt><dd>{{ formatDate(detail.summary.updatedAt) }}</dd></div></dl>
|
||||
<div class="detail-summary__header"><div><span class="eyebrow">订单主状态</span><h2>{{ detail.summary.orderNo || `订单 ${detail.summary.orderId}` }}</h2></div><div class="detail-summary__actions"><StatusBadge :status="statusLabel(detail.summary.mainStatus)" /><el-button v-if="canApprove(detail.summary)" type="success" plain :loading="approvalLoadingId === String(detail.summary.orderId)" @click="openApproval(detail.summary)"><CircleCheck :size="15" />审核通过</el-button><el-button v-if="workflowEligible(detail.summary) && can('platform:firstleg:inbound:view')" type="primary" plain @click="openWorkflow(detail.summary)"><ClipboardList :size="15" />后半程作业</el-button></div></div>
|
||||
<dl class="detail-grid"><div><dt>客户</dt><dd>{{ customerName(detail.summary) }}</dd></div><div><dt>入仓码</dt><dd>{{ detail.summary.inboundCode || '—' }}</dd></div><div><dt>平台收货仓</dt><dd>{{ detail.summary.receivingWarehouseName || '—' }}</dd></div><div><dt>目的仓</dt><dd>{{ detail.summary.destinationWarehouseName || '—' }}</dd></div><div><dt>承运商</dt><dd>{{ detail.order.order?.carrierName || '待平台指派' }}</dd></div><div><dt>下一步</dt><dd>{{ canApprove(detail.summary) ? '选择承运商并审核' : nextActionBoundary(detail.summary) || '—' }}</dd></div><div><dt>最近更新</dt><dd>{{ formatDate(detail.summary.updatedAt) }}</dd></div></dl>
|
||||
</section>
|
||||
<section class="detail-section" aria-label="状态历史"><h3>状态历史</h3><el-empty v-if="detail.timeline.length === 0" description="暂无状态历史" :image-size="56" /><ol v-else class="history-list"><li v-for="(item, index) in detail.timeline" :key="`${item.source || 'event'}-${item.occurredAt || index}`"><StatusBadge :status="statusLabel(item.mainStatus)" /><div><strong>{{ item.action || item.eventType || '状态更新' }}</strong><p>{{ item.operatorName || '系统' }} · {{ formatDate(item.occurredAt) }}</p><p v-if="item.remark">{{ item.remark }}</p></div></li></ol></section>
|
||||
<section class="detail-section" aria-label="内部备注"><h3>内部备注</h3><el-empty v-if="detail.order.internalNotes.length === 0" description="暂无内部备注" :image-size="56" /><ul v-else class="notes-list"><li v-for="item in detail.order.internalNotes" :key="String(item.id)"><p>{{ item.content }}</p><span>操作人 {{ item.createdBy || '系统' }} · {{ formatDate(item.createdAt) }}</span></li></ul></section>
|
||||
</div>
|
||||
</AppDrawer>
|
||||
|
||||
<AppDialog v-model="approvalOpen" title="选择承运商并审核" confirm-text="指派并审核通过" confirm-type="success" :loading="carrierLoading" :confirm-loading="approvalSaving" :confirm-disabled="Boolean(carrierLoadError) || carriers.length === 0" destroy-on-close @confirm="assignCarrierAndApprove" @cancel="closeApproval">
|
||||
<el-alert title="订单审核通过后将进入履约流程;承运商名称由后端公共主数据生成快照。" type="info" :closable="false" show-icon />
|
||||
<el-alert v-if="carrierLoadError" class="carrier-load-error" type="error" :closable="false" show-icon>
|
||||
<template #title><div class="api-error-alert-title"><span>{{ carrierLoadError.message }}</span><ApiErrorMeta :details="carrierLoadError" /></div></template>
|
||||
<el-button link type="danger" :loading="carrierLoading" @click="loadCarriers">重新加载</el-button>
|
||||
</el-alert>
|
||||
<el-alert v-else-if="!carrierLoading && carriers.length === 0" class="carrier-load-error" title="暂无启用的承运商,请先到往来单位维护承运商主数据。" type="warning" :closable="false" show-icon />
|
||||
<el-form ref="carrierFormRef" :model="carrierForm" :rules="carrierRules" label-position="top" class="carrier-form" :disabled="carrierLoading || approvalSaving || Boolean(carrierLoadError)">
|
||||
<el-form-item label="承运商" prop="carrierId" required>
|
||||
<el-select v-model="carrierForm.carrierId" filterable :loading="carrierLoading" placeholder="请选择启用的承运商">
|
||||
<el-option v-for="carrier in carriers" :key="String(carrier.id)" :label="`${carrier.partnerName}(${carrier.partnerCode})`" :value="String(carrier.id)" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</AppDialog>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -446,6 +555,9 @@ onMounted(() => {
|
||||
.table-card { margin-top: 16px; overflow: hidden; border: 1px solid var(--cc-border); border-radius: 8px; background: #fff; }
|
||||
.row-actions { display: flex; align-items: center; gap: 4px; white-space: nowrap; }
|
||||
.row-actions :deep(.el-button) { margin-left: 0; }
|
||||
.carrier-form { margin-top: 18px; }
|
||||
.carrier-form :deep(.el-select) { width: 100%; }
|
||||
.carrier-load-error { margin-top: 14px; }
|
||||
.primary-cell { display: grid; min-width: 0; gap: 4px; }
|
||||
.primary-cell strong { overflow-wrap: anywhere; }
|
||||
.primary-cell span { color: var(--cc-muted); font-size: 12px; overflow-wrap: anywhere; }
|
||||
|
||||
Reference in New Issue
Block a user