fix(admin): clarify billing inputs and correct location and filter display

Scoped TEST release of three reviewed display fixes; no API, field or workflow changes.
This commit is contained in:
2026-10-20 16:53:42 +08:00
parent 291535e242
commit a781db819a
6 changed files with 71 additions and 6 deletions
@@ -821,7 +821,6 @@ onMounted(async () => { await Promise.all([loadCustomers(), loadRows()]) })
<style scoped>
.container-page { display: grid; gap: var(--admin-space-5); min-width: 0; }.container-page small { display: block; margin-top: var(--admin-space-1); color: var(--cc-muted); }.container-query-panel { min-width: 0; }.container-query-toolbar { border: 0; }.row-actions { flex-wrap: nowrap; white-space: nowrap; }.row-actions :deep(.el-button + .el-button) { margin-left: 0; }.container-editor-table { table-layout: fixed; }.fleet-select { width: 100%; }.arrival-time-cell { display: flex; align-items: center; width: 100%; min-width: 0; padding-right: var(--admin-space-1); box-sizing: border-box; }.arrival-time-picker { width: 200px !important; max-width: 100%; min-width: 0; box-sizing: border-box; }.details :deep(.el-descriptions__label) { width: 120px; }.drayage-load-alert { margin-bottom: var(--admin-space-5); }.drayage-summary { margin-bottom: var(--admin-space-5); }
.container-query-panel { width: 100%; max-width: 100%; min-width: 0; box-sizing: border-box; }
.container-query-toolbar :deep(.query-toolbar__tail) { width: 100%; flex: 0 0 100%; }
.container-page .table-panel { width: 100%; max-width: 100%; min-width: 0; }
@media(max-width:720px){.details :deep(.el-descriptions__body table),.details :deep(tbody),.details :deep(tr),.details :deep(td){display:block;width:100%!important}.row-actions { min-width: 180px; }}
@media(max-width:720px){.container-editor-table :is(tbody,tr,td){display:block}}
@@ -2403,15 +2403,15 @@ function orderDialogTitle(action: string, inboundCode?: string | null) {
</tr>
<tr>
<th scope="row"><label for="fee-confirm-weight">计费毛重</label></th>
<td><div class="density-table-editor"><el-input-number id="fee-confirm-weight" v-model="feeConfirmWeight" :min="0" :precision="3" :controls="false" placeholder="请输入计费毛重" /><span>kg</span></div></td>
<td><div class="density-table-editor density-table-editor--outlined"><el-input-number id="fee-confirm-weight" v-model="feeConfirmWeight" :min="0" :precision="3" :controls="false" placeholder="请输入计费毛重" /><span>kg</span></div></td>
<th scope="row"><label for="fee-confirm-volume">计费体积</label></th>
<td><div class="density-table-editor"><el-input-number id="fee-confirm-volume" v-model="feeConfirmVolume" :min="0" :precision="6" :controls="false" placeholder="请输入计费体积" /><span>m³</span></div></td>
<td><div class="density-table-editor density-table-editor--outlined"><el-input-number id="fee-confirm-volume" v-model="feeConfirmVolume" :min="0" :precision="6" :controls="false" placeholder="请输入计费体积" /><span>m³</span></div></td>
</tr>
<tr>
<th scope="row">价格表参考单价</th>
<td>{{ feeConfirmPriceMissing ? '未命中' : feeConfirmReference?.unitPrice == null ? '正在读取参考单价' : `${formatNumber(Number(feeConfirmReference.unitPrice), 4)} ${feeConfirmPriceUnit}` }}</td>
<th scope="row"><label for="fee-confirm-unit-price">本次对客单价</label></th>
<td><div class="density-table-editor"><el-input-number id="fee-confirm-unit-price" v-model="feeConfirmUnitPrice" :min="0" :precision="4" :controls="false" :disabled="feeConfirmReferenceLoading" placeholder="请输入对客单价" /><span>{{ feeConfirmPriceUnit }}</span></div></td>
<td><div class="density-table-editor density-table-editor--outlined"><el-input-number id="fee-confirm-unit-price" v-model="feeConfirmUnitPrice" :min="0" :precision="4" :controls="false" :disabled="feeConfirmReferenceLoading" placeholder="请输入对客单价" /><span>{{ feeConfirmPriceUnit }}</span></div></td>
</tr>
<tr v-if="feeConfirmReference">
<th scope="row">参考计费量</th><td>{{ formatNumber(Number(feeConfirmReference.chargeableQuantity), feeConfirmReference.roundingScale) }} {{ feeConfirmReference.chargeUnit }}</td>
@@ -15,6 +15,7 @@ import {
import { listAllPlatformWarehouses, type PlatformWarehouseResource } from '../../../warehouses/api/platformWarehouses'
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
import DataTable from '@/shared/components/DataTable.vue'
import LayoutPreferenceList from '@/shared/components/LayoutPreferenceList.vue'
import LocationsView from './LocationsView.vue'
const locationsSource = readFileSync(resolve(process.cwd(), 'src/modules/warehouse-resources/locations/pages/LocationsView.vue'), 'utf8')
@@ -179,6 +180,27 @@ describe('LocationsView', () => {
vi.mocked(createPlatformLocation).mockReset()
})
it('renders missing maximum weights as a dash and preserves zero and populated weights', async () => {
const missingWeight = { ...availableLocation }
Reflect.deleteProperty(missingWeight, 'maxWeightKg')
const records = [missingWeight, ...[null, '', 0, 2000, '10000.125'].map((maxWeightKg, index) => ({
...availableLocation, id: `weight-${index}`, locationCode: `WEIGHT-${index}`, maxWeightKg,
}))]
vi.mocked(listPlatformLocations).mockResolvedValue({ records, page: 1, size: 20, total: records.length, pages: 1 })
const wrapper = await mountView()
await wrapper.get('button[aria-label="列设置"]').trigger('click')
await flushPromises()
const preferences = wrapper.findComponent(DataTable).findComponent(LayoutPreferenceList)
preferences.vm.$emit('visibility', 'maxWeightKg', true)
await flushPromises()
const column = wrapper.findAll('thead th').findIndex(header => header.text().includes('最大承重'))
expect(column).toBeGreaterThan(-1)
expect(wrapper.findAll('tbody tr').map(row => row.findAll('td')[column]!.text())).toEqual([
'—', '—', '—', '0 kg', '2,000 kg', '10,000.125 kg',
])
expect(wrapper.text()).not.toContain('undefined kg')
})
it('loads only domestic warehouse candidates for querying and writes', async () => {
const wrapper = await mountView()
const toolbar = wrapper.findComponent(QueryToolbar)
@@ -188,8 +188,8 @@ function optionalText(value: string) {
return value.trim() || null
}
function formatWeight(value: number | string | null) {
if (value === null || value === '') return '—'
function formatWeight(value: number | string | null | undefined) {
if (value == null || value === '') return '—'
const numeric = Number(value)
return Number.isFinite(numeric) ? `${numeric.toLocaleString('zh-CN', { maximumFractionDigits: 3 })} kg` : `${value} kg`
}
+5
View File
@@ -252,6 +252,11 @@
--el-input-hover-border-color: var(--admin-table-border);
--el-input-bg-color: transparent;
}
:is(.admin-density, .admin-density-dialog) .density-table--editable .density-table-editor--outlined .el-input {
--el-input-border-color: var(--el-color-primary-light-5);
--el-input-hover-border-color: var(--el-color-primary);
--el-input-bg-color: var(--admin-surface);
}
:is(.admin-density, .admin-density-dialog) .density-table-editor { display: flex; align-items: center; gap: var(--admin-space-3); min-width: 0; }
:is(.admin-density, .admin-density-dialog) .density-table-editor .el-input-number { flex: 1; width: 100%; min-width: 0; }
:is(.admin-density, .admin-density-dialog) .density-table--editable .el-input__inner { text-align: left; }
+39
View File
@@ -178,6 +178,45 @@ const cssRoot = (css: string) => {
}
describe('approved Admin density ownership', () => {
it('outlines only the three opted-in customer billing editors without overriding native focus or disabled styles', () => {
const rules = new Map<string, Record<string, string>>()
cssRoot(shared).walkRules(rule => {
const values: Record<string, string> = {}
rule.walkDecls(decl => { values[decl.prop] = decl.value })
rules.set(rule.selector, values)
})
expect(rules.get(':is(.admin-density, .admin-density-dialog) .density-table--editable .density-table-editor--outlined .el-input')).toEqual({
'--el-input-border-color': 'var(--el-color-primary-light-5)',
'--el-input-hover-border-color': 'var(--el-color-primary)',
'--el-input-bg-color': 'var(--admin-surface)',
})
const ast = parse(read('modules/unified-orders/pages/UnifiedOrdersView.vue')).descriptor.template!.ast!
const inputIds: string[] = []
const visit = (nodes: typeof ast.children) => { for (const node of nodes) {
if (node.type !== 1) continue
if (classesOf(node).includes('density-table-editor--outlined')) {
const input = node.children.find(child => child.type === 1 && child.tag === 'el-input-number') as ElementNode | undefined
expect(input).toBeDefined()
const id = input!.props.find(prop => prop.type === 6 && prop.name === 'id')
inputIds.push(id?.type === 6 ? id.value?.content || '' : '')
}
visit(node.children)
} }
visit(ast.children)
expect(inputIds).toEqual(['fee-confirm-weight', 'fee-confirm-volume', 'fee-confirm-unit-price'])
})
it('allows container filters and their final action group to wrap by available width', () => {
const source = read('modules/container-loading/pages/ContainersView.vue')
for (const style of parse(source).descriptor.styles) {
cssRoot(style.content).walkRules(rule => {
if (!rule.selector.includes('.query-toolbar__tail')) return
rule.walkDecls(decl => {
if (decl.prop === 'width') expect(decl.value).not.toBe('100%')
if (decl.prop === 'flex') expect(decl.value).not.toBe('0 0 100%')
})
})
}
})
it('keeps declaration order fields on the continuous cell-editor baseline', () => {
const source = read('modules/unified-orders/components/OrderDeclarationEditorDialog.vue')
const orderFields = source.slice(source.indexOf('<el-form label-position="left"'), source.indexOf('</el-form>') + '</el-form>'.length)