388 lines
22 KiB
Vue
388 lines
22 KiB
Vue
<script setup lang="ts">
|
||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||
import { useRoute, useRouter } from 'vue-router'
|
||
import { ElMessage, type FormInstance } from 'element-plus'
|
||
import TransportDemandForm from './TransportDemandForm.vue'
|
||
import { createTransportDemandForm, transportDemandError, buildTransportDemand, type TransportDemandForm as DemandFormModel } from '../transportDemandForm'
|
||
import DataTable, { type DataTableColumn } from '@/components/common/DataTable.vue'
|
||
import QueryToolbar, { type QueryField, type QueryModel } from '@/components/common/QueryToolbar.vue'
|
||
import type { TableAction } from '@/components/common/tableActions'
|
||
import { useAuthStore } from '@/modules/auth/public'
|
||
import {
|
||
createTransport,
|
||
getBusinessOrder,
|
||
listBusinessOrders,
|
||
type CrossBorderBusinessKind,
|
||
type TransportDetail,
|
||
type TransportSummary,
|
||
} from '../api/businessOrders'
|
||
import { poolOrderStatusLabels } from '../fleetArrangement'
|
||
|
||
const props = withDefaults(defineProps<{ kind: CrossBorderBusinessKind; createPage?: boolean; detailPage?: boolean }>(), { createPage: false, detailPage: false })
|
||
const auth = useAuthStore()
|
||
const router = useRouter()
|
||
const route = useRoute()
|
||
const detailOrderNo = computed(() => String(route.params.orderNo || ''))
|
||
const canCreate = computed(() => auth.hasPermission(({
|
||
trucking: 'merchant:cross-border:domestic-trucking:create',
|
||
looseboard: 'merchant:cross-border:pallet-shipping:create',
|
||
} as Record<CrossBorderBusinessKind, string>)[props.kind]))
|
||
|
||
type BusinessRow = TransportSummary
|
||
type BusinessDetail = TransportDetail
|
||
type PageMode = 'list' | 'create' | 'detail'
|
||
|
||
interface ColumnDefinition { key: string; label: string; width?: number }
|
||
|
||
type BusinessForm = DemandFormModel
|
||
|
||
const configuration = computed(() => ({
|
||
trucking: {
|
||
title: '美国本土卡',
|
||
description: '查询并管理美国本土卡订单',
|
||
columns: transportColumns(false),
|
||
},
|
||
looseboard: {
|
||
title: '美本土散板',
|
||
description: '查询并管理美本土散板订单',
|
||
columns: transportColumns(true),
|
||
},
|
||
})[props.kind])
|
||
|
||
function transportColumns(looseboard: boolean): ColumnDefinition[] {
|
||
return [
|
||
{ key: 'orderNo', label: '订单编号', width: 180 },
|
||
...(!looseboard ? [{ key: 'vehicleType', label: '车辆类型', width: 110 }] : []),
|
||
{ key: 'origin', label: '起运地', width: 150 },
|
||
{ key: 'destination', label: '目的地', width: 150 },
|
||
...(looseboard ? [{ key: 'productName', label: '商品名称', width: 150 }, { key: 'estimatedPalletCount', label: '预计托数', width: 100 }] : []),
|
||
{ key: 'totalWeightKg', label: '总重量(kg)', width: 120 },
|
||
{ key: 'budgetAmount', label: '预算总价', width: 110 },
|
||
...([
|
||
{ key: 'pickupAt', label: '取货时间', width: 165 }, { key: 'deliveryAt', label: '送货时间', width: 165 },
|
||
]),
|
||
{ key: 'status', label: '状态', width: 130 },
|
||
{ key: 'fleetArrangement', label: '承运安排', width: 190 },
|
||
]
|
||
}
|
||
|
||
const mode = ref<PageMode>(props.detailPage ? 'detail' : props.createPage ? 'create' : 'list')
|
||
const loading = ref(false)
|
||
const submitting = ref(false)
|
||
const submitKey = ref('')
|
||
const error = ref('')
|
||
const rows = ref<BusinessRow[]>([])
|
||
const detail = ref<BusinessDetail | null>(null)
|
||
const fleetArrangement = computed(() => detail.value ? (detail.value as TransportDetail).summary.fleetArrangement : null)
|
||
const formRef = ref<FormInstance>()
|
||
const query = reactive({ page: 1, size: 20, orderNo: '', routeKeyword: '', status: '' })
|
||
const total = ref(0)
|
||
|
||
const queryModel = computed<QueryModel>({
|
||
get: () => ({
|
||
orderNo: query.orderNo,
|
||
routeKeyword: query.routeKeyword,
|
||
status: query.status,
|
||
}),
|
||
set: (value) => {
|
||
query.orderNo = String(value.orderNo || '')
|
||
query.routeKeyword = String(value.routeKeyword || '')
|
||
query.status = String(value.status || '')
|
||
},
|
||
})
|
||
|
||
const queryFields = computed<QueryField[]>(() => [
|
||
{ key: 'orderNo', label: '订单号', type: 'input', placeholder: '请输入订单号' },
|
||
{ key: 'routeKeyword', label: '运输路线', type: 'input', placeholder: '请输入起运地或目的地' },
|
||
{
|
||
key: 'status',
|
||
label: '状态',
|
||
type: 'select',
|
||
placeholder: '全部',
|
||
options: [
|
||
{ label: '待审核', value: 'PENDING_REVIEW' },
|
||
{ label: '已通过', value: 'APPROVED' },
|
||
{ label: '已拒绝', value: 'REJECTED' },
|
||
{ label: '待接单', value: 'PENDING_ACCEPTANCE' },
|
||
...Object.entries(poolOrderStatusLabels).filter(([value]) => !['IN_TRANSIT', 'COMPLETED', 'CANCELLED'].includes(value)).map(([value, label]) => ({ value, label })),
|
||
{ label: '处理中', value: 'PROCESSING' },
|
||
{ label: '运输中', value: 'IN_TRANSIT' },
|
||
{ label: '已完成', value: 'COMPLETED' },
|
||
{ label: '已取消', value: 'CANCELLED' },
|
||
],
|
||
},
|
||
])
|
||
|
||
const form = reactive<BusinessForm>(emptyForm())
|
||
|
||
const detailFields = computed(() => {
|
||
if (!detail.value) return []
|
||
const summary = detail.value.summary
|
||
const current = detail.value as TransportDetail
|
||
return [
|
||
...(props.kind === 'looseboard' ? [
|
||
['商品名称', summary.productName], ['预计托数', summary.estimatedPalletCount],
|
||
] : [['车辆类型', vehicleTypeLabel(summary.vehicleType)]]),
|
||
['起运地', read(summary, 'origin')], ['起运地址', current.originAddress],
|
||
['目的地', read(summary, 'destination')], ['目的地址', current.destinationAddress],
|
||
['件数', read(summary, 'packageCount')], ['总重量(kg)', read(summary, 'totalWeightKg')],
|
||
['总体积(m³)', read(summary, 'totalVolumeCbm')], ['预算总价', money(read(summary, 'budgetAmount'), read(summary, 'currency'))],
|
||
['起运地联系人', summary.originContactName], ['起运地联系电话', summary.originContactPhone],
|
||
['目的地联系人', summary.destinationContactName], ['目的地联系电话', summary.destinationContactPhone],
|
||
['取货时间', localMinute(summary.pickupAt)], ['送货时间', localMinute(summary.deliveryAt)], ['备注', current.remark],
|
||
]
|
||
})
|
||
|
||
const tableColumns = computed<DataTableColumn[]>(() => [
|
||
{ key: 'actions', label: '操作', width: 90, fixed: 'left', slotName: 'actions' },
|
||
...configuration.value.columns.map((column) => ({
|
||
...column,
|
||
prop: column.key,
|
||
slotName: ['orderNo', 'status', 'fleetArrangement'].includes(column.key) ? column.key : undefined,
|
||
formatter: (row: object) => displayCell(row, column.key),
|
||
})),
|
||
])
|
||
|
||
const detailStatus = computed(() => detail.value ? statusLabel(detail.value.summary.status) : '—')
|
||
|
||
function emptyForm(): BusinessForm { return createTransportDemandForm() }
|
||
|
||
function resetForm() { Object.assign(form, emptyForm()); submitKey.value = ''; formRef.value?.clearValidate() }
|
||
function read(source: object, key: string) { return Reflect.get(source, key) as unknown }
|
||
function text(value: unknown) { return value === null || value === undefined || value === '' ? '—' : String(value) }
|
||
function money(amount: unknown, currency: unknown) { return amount === null || amount === undefined ? '—' : `${text(currency)} ${Number(amount).toFixed(2)}` }
|
||
function dateTime(value: unknown) { return value ? new Date(String(value)).toLocaleString('zh-CN', { hour12: false }) : '—' }
|
||
function yesNo(value: unknown) { return value === 'YES' ? '是' : value === 'NO' ? '否' : text(value) }
|
||
function vehicleTypeLabel(value: unknown) { return ({ PALLET_15: '15拖', PALLET_30: '30拖' } as Record<string, string>)[String(value)] || text(value) }
|
||
function localMinute(value: unknown) { return value ? String(value).replace('T', ' ').slice(0, 16) : '—' }
|
||
function transportTypeLabel(value: unknown) { return ({ PORT_PICKUP: '港口提柜', WAREHOUSE_TO_WAREHOUSE: '仓到仓', WAREHOUSE_TO_DOOR: '仓到门' } as Record<string, string>)[String(value)] || text(value) }
|
||
function statusLabel(value: unknown) {
|
||
if (poolOrderStatusLabels[String(value)]) return poolOrderStatusLabels[String(value)]
|
||
return ({ PENDING_REVIEW: '待审核', APPROVED: '已通过', REJECTED: '已拒绝', PENDING_ACCEPTANCE: '待接单', PROCESSING: '处理中', IN_TRANSIT: '运输中', COMPLETED: '已完成', CANCELLED: '已取消' } as Record<string, string>)[String(value)] || text(value)
|
||
}
|
||
function statusTone(value: unknown) {
|
||
return ({ APPROVED: 'success', COMPLETED: 'success', REJECTED: 'danger', CANCELLED: 'info', PENDING_REVIEW: 'warning', PENDING_ACCEPTANCE: 'warning', PROCESSING: 'primary', IN_TRANSIT: 'primary' } as Record<string, 'success' | 'warning' | 'danger' | 'info' | 'primary'>)[String(value)] || 'info'
|
||
}
|
||
function displayCell(candidate: unknown, key: string) {
|
||
const row = candidate as BusinessRow
|
||
const value = read(row, key)
|
||
if (key === 'status') return statusLabel(value)
|
||
if (key === 'vehicleType') return vehicleTypeLabel(value)
|
||
if (key === 'pickupAt' || key === 'deliveryAt') return localMinute(value)
|
||
if (key === 'transportType') return transportTypeLabel(value)
|
||
if (key === 'palletRequired') return yesNo(value)
|
||
if (key === 'createdAt') return dateTime(value)
|
||
if (key === 'budgetAmount') return money(value, read(row, 'currency'))
|
||
return text(value)
|
||
}
|
||
function idempotencyKey() { return globalThis.crypto?.randomUUID?.() || `client-${Date.now()}-${Math.random().toString(16).slice(2)}` }
|
||
function errorMessage(cause: unknown) { return cause instanceof Error ? cause.message : '请求失败,请稍后重试' }
|
||
|
||
async function load() {
|
||
loading.value = true
|
||
error.value = ''
|
||
try {
|
||
const params = {
|
||
page: query.page, size: query.size, orderNo: query.orderNo || undefined, status: query.status || undefined,
|
||
routeKeyword: query.routeKeyword || undefined,
|
||
}
|
||
const result = await listBusinessOrders<TransportSummary>(props.kind, params)
|
||
rows.value = result.records
|
||
total.value = result.total
|
||
} catch (cause) {
|
||
error.value = errorMessage(cause)
|
||
rows.value = []
|
||
total.value = 0
|
||
} finally { loading.value = false }
|
||
}
|
||
|
||
function resetQuery() {
|
||
const reloadDirectly = query.page === 1
|
||
Object.assign(query, { page: 1, orderNo: '', routeKeyword: '', status: '' })
|
||
if (reloadDirectly) void load()
|
||
}
|
||
|
||
function searchOrders() {
|
||
if (query.page !== 1) query.page = 1
|
||
else void load()
|
||
}
|
||
|
||
function getRowActions(candidate: object): TableAction[] {
|
||
return [{ key: 'detail', label: '详情', run: () => openDetail(candidate) }]
|
||
}
|
||
|
||
function detailFieldClass(label: unknown) {
|
||
return ['备注', '起运地址', '目的地址'].includes(String(label)) ? 'is-wide' : ''
|
||
}
|
||
|
||
const listRouteName = computed(() => ({
|
||
trucking: 'cross-border-domestic-trucking',
|
||
looseboard: 'cross-border-pallet-shipping',
|
||
} as Record<CrossBorderBusinessKind, string>)[props.kind])
|
||
const detailRouteName = computed(() => ({
|
||
trucking: 'cross-border-domestic-trucking-detail',
|
||
looseboard: 'cross-border-pallet-shipping-detail',
|
||
} as Record<CrossBorderBusinessKind, string>)[props.kind])
|
||
|
||
function returnToList() {
|
||
if (props.createPage || props.detailPage) {
|
||
void router.push({ name: listRouteName.value })
|
||
return
|
||
}
|
||
mode.value = 'list'
|
||
}
|
||
|
||
function openDetail(candidate: unknown) {
|
||
const row = candidate as BusinessRow
|
||
void router.push({ name: detailRouteName.value, params: { orderNo: row.orderNo } })
|
||
}
|
||
async function loadDetail() {
|
||
if (!detailOrderNo.value) return
|
||
loading.value = true
|
||
error.value = ''
|
||
try {
|
||
detail.value = await getBusinessOrder<TransportDetail>(props.kind, detailOrderNo.value)
|
||
mode.value = 'detail'
|
||
} catch (cause) { detail.value = null; error.value = errorMessage(cause); ElMessage.error(error.value) }
|
||
finally { loading.value = false }
|
||
}
|
||
|
||
async function submit() {
|
||
const failure = transportDemandError(props.kind === 'trucking' ? 'truck' : 'looseboard', form)
|
||
if (failure) { ElMessage.error(failure); return }
|
||
if (!await formRef.value?.validate().catch(() => false)) return
|
||
submitting.value = true
|
||
const stableSubmitKey = submitKey.value || idempotencyKey()
|
||
submitKey.value = stableSubmitKey
|
||
try {
|
||
if (props.kind === 'trucking') await createTransport('trucking', { ...buildTransportDemand('truck', form), idempotencyKey: stableSubmitKey })
|
||
else await createTransport('looseboard', { ...buildTransportDemand('looseboard', form), idempotencyKey: stableSubmitKey })
|
||
ElMessage.success('订单提交成功')
|
||
resetForm()
|
||
if (props.createPage) await router.push({ name: listRouteName.value })
|
||
else { mode.value = 'list'; await load() }
|
||
} catch (cause) { ElMessage.error(errorMessage(cause)) }
|
||
finally { submitting.value = false }
|
||
}
|
||
|
||
watch(() => [query.page, query.size], () => { if (!props.createPage && mode.value === 'list') void load() })
|
||
onMounted(() => { if (props.detailPage) void loadDetail(); else if (!props.createPage) void load() })
|
||
</script>
|
||
|
||
<template>
|
||
<section v-loading="loading" class="business-orders client-ui" :aria-busy="loading">
|
||
<header v-if="mode === 'create'" class="page-head" :class="{ 'looseboard-page-head': props.kind === 'looseboard' }">
|
||
<div>
|
||
<h2>{{ configuration.title }}下单</h2>
|
||
<p>请填写订单信息,带 * 项为必填项</p>
|
||
</div>
|
||
<el-button @click="returnToList">返回列表</el-button>
|
||
</header>
|
||
|
||
<template v-if="mode === 'list'">
|
||
<QueryToolbar v-model="queryModel" layout="grid" ariaLabel="订单查询条件" :fields="queryFields" :search-on-reset="false" @search="searchOrders" @reset="resetQuery" />
|
||
<div class="business-table-card">
|
||
<DataTable v-model:page="query.page" v-model:page-size="query.size" :data="rows" :columns="tableColumns" :loading="loading" :total="total" :page-sizes="[10, 20, 50, 100]" :row-actions="getRowActions" row-key="orderNo" empty-text="暂无订单">
|
||
<template #toolbar-actions><el-button v-if="canCreate" type="primary" @click="resetForm(); mode = 'create'">新增下单</el-button></template>
|
||
<template v-if="error" #error><div class="business-load-error" role="alert"><span>{{ error }}</span><el-button size="small" @click="load">重新加载</el-button></div></template>
|
||
<template #orderNo="{ row }"><button class="business-order-link" type="button" @click="openDetail(row)">{{ (row as BusinessRow).orderNo }}</button></template>
|
||
<template #status="{ row }"><el-tag :type="statusTone((row as BusinessRow).status)" effect="light" size="small">{{ statusLabel((row as BusinessRow).status) }}</el-tag></template>
|
||
<template #fleetArrangement="{ row }"><template v-if="(row as TransportSummary).fleetArrangement"><div>{{ poolOrderStatusLabels[(row as TransportSummary).fleetArrangement!.fulfillmentStage] || '待核对履约进度' }}</div><small>{{ dateTime((row as TransportSummary).fleetArrangement!.plannedDepartureAt) }}</small></template><span v-else>—</span></template>
|
||
</DataTable>
|
||
</div>
|
||
</template>
|
||
|
||
<section v-else-if="mode === 'detail'" class="business-detail-page" aria-label="订单详情">
|
||
<header class="business-detail-heading">
|
||
<div class="business-detail-heading-main"><el-button link type="primary" @click="returnToList">← 返回列表</el-button><div><h2>{{ detail?.summary.orderNo || '订单详情' }}</h2><p>{{ configuration.title }}订单</p></div></div>
|
||
<el-tag :type="statusTone(detail?.summary.status)" effect="light">{{ detailStatus }}</el-tag>
|
||
</header>
|
||
<div v-if="error" class="business-detail-error" role="alert"><strong>{{ error }}</strong><el-button size="small" @click="loadDetail">重新加载</el-button></div>
|
||
<section v-else class="business-detail-section">
|
||
<h3>订单信息</h3>
|
||
<dl class="detail-grid" :class="{ 'trucking-detail-grid': kind === 'trucking' }">
|
||
<div v-for="field in detailFields" :key="String(field[0])" :class="detailFieldClass(field[0])"><dt>{{ field[0] }}</dt><dd>{{ text(field[1]) }}</dd></div>
|
||
</dl>
|
||
</section>
|
||
<section v-if="!error && fleetArrangement" class="business-detail-section" aria-label="承运安排">
|
||
<h3>承运安排</h3><p>{{ fleetArrangement.readOnlyReason }}</p>
|
||
<dl class="detail-grid arrangement-grid">
|
||
<div><dt>预订号</dt><dd>{{ fleetArrangement.bookingNo }}</dd></div>
|
||
<div><dt>计划发车</dt><dd>{{ dateTime(fleetArrangement.plannedDepartureAt) }}</dd></div>
|
||
<div><dt>起运仓</dt><dd>{{ text(fleetArrangement.originWarehouseName) }}</dd></div>
|
||
<div><dt>目的地</dt><dd>{{ text(fleetArrangement.destinationLabel) }}</dd></div>
|
||
<div><dt>交货方式</dt><dd>{{ fleetArrangement.deliveryMode === 'SELF_DELIVERY' ? '自行送至起运仓' : '线下安排提货至起运仓' }}</dd></div>
|
||
<div><dt>履约进度</dt><dd>{{ poolOrderStatusLabels[fleetArrangement.fulfillmentStage] || '待核对履约进度' }}</dd></div>
|
||
<div><dt>更新时间</dt><dd>{{ dateTime(fleetArrangement.stageOccurredAt) }}</dd></div>
|
||
<div><dt>约定运费(非实收)</dt><dd>{{ money(fleetArrangement.agreedFreightAmount, fleetArrangement.currency) }}</dd></div>
|
||
<div><dt>来货单号</dt><dd>{{ text(fleetArrangement.incomingNo) }}</dd></div>
|
||
<div><dt>派送单号</dt><dd>{{ text(fleetArrangement.deliveryNo) }}</dd></div>
|
||
<div><dt>已签 / 未签 / 退回箱数</dt><dd>{{ fleetArrangement.signedPackages ?? '—' }} / {{ fleetArrangement.unsignedPackages ?? '—' }} / {{ fleetArrangement.returnedPackages ?? '—' }}</dd></div>
|
||
<div class="is-wide"><dt>交货说明</dt><dd>{{ text(fleetArrangement.deliveryInstructions) }}</dd></div>
|
||
<div v-if="fleetArrangement.exceptionSummary" class="is-wide"><dt>履约说明</dt><dd>{{ fleetArrangement.exceptionSummary }}</dd></div>
|
||
</dl>
|
||
</section>
|
||
</section>
|
||
|
||
<el-card v-else shadow="never" class="form-card">
|
||
<TransportDemandForm ref="formRef" :model="form" :kind="kind === 'trucking' ? 'truck' : 'looseboard'" :submitting="submitting" :can-submit="canCreate" @submit="submit" @cancel="returnToList" />
|
||
</el-card>
|
||
</section>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.business-orders { display: grid; gap: var(--client-section-gap); }
|
||
.page-head { display: flex; align-items: center; justify-content: space-between; gap: 24px; }
|
||
.looseboard-page-head { max-width: 1180px; padding-right: 30px; box-sizing: border-box; }
|
||
.page-head h2 { margin: 0; color: #111827; font-size: 26px; }
|
||
.page-head p { margin: 7px 0 0; color: #64748b; font-size: 14px; }
|
||
.business-table-card { overflow: hidden; border: 1px solid var(--client-border); border-radius: var(--client-radius); background: #fff; }
|
||
.business-order-link { max-width: 100%; overflow: hidden; padding: 0; border: 0; background: transparent; color: var(--client-action); cursor: pointer; font: inherit; font-weight: 650; text-align: left; text-overflow: ellipsis; white-space: nowrap; }
|
||
.business-order-link:hover { text-decoration: underline; text-underline-offset: 3px; }
|
||
.business-load-error { display: flex; min-height: 84px; align-items: center; justify-content: center; gap: 12px; padding: 14px; color: #b42318; }
|
||
.business-detail-error { display: flex; min-height: 180px; align-items: center; justify-content: center; gap: 12px; color: #b42318; }
|
||
.business-detail-page { min-width: 0; padding: 10px 14px 20px; overflow: hidden; border: 1px solid var(--client-border); border-radius: var(--client-radius); background: #fff; }
|
||
.business-detail-heading { display: flex; min-height: 50px; align-items: center; justify-content: space-between; gap: 16px; padding: 5px 0; border-bottom: 1px solid var(--border); }
|
||
.business-detail-heading-main { display: flex; min-width: 0; align-items: center; gap: 12px; }
|
||
.business-detail-heading-main > div { min-width: 0; }
|
||
.business-detail-heading h2 { margin: 0; overflow: hidden; color: var(--navy); font-size: 18px; text-overflow: ellipsis; white-space: nowrap; }
|
||
.business-detail-heading p { margin: 3px 0 0; color: var(--muted); font-size: 11px; }
|
||
.business-detail-section { margin-top: 10px; }
|
||
.business-detail-section h3 { margin: 0; padding: 0 0 5px; border-bottom: 1px solid #eaf0f7; color: var(--navy); font-size: 13px; }
|
||
.detail-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); margin: 0; border-top: 1px solid var(--border); border-left: 1px solid var(--border); }
|
||
.detail-grid div { display: grid; min-width: 0; min-height: 34px; grid-template-columns: 76px minmax(0, 1fr); align-items: center; gap: 7px; padding: 3px 8px; border-right: 1px solid var(--border); border-bottom: 1px solid var(--border); }
|
||
.trucking-detail-grid div { grid-template-columns: 112px minmax(0, 1fr); }
|
||
.detail-grid div.is-wide { grid-column: span 2; }
|
||
.detail-grid dt { color: var(--muted); font-size: 10px; white-space: nowrap; }
|
||
.arrangement-grid div { grid-template-columns: 124px minmax(0, 1fr); }
|
||
.arrangement-grid dt { white-space: normal; overflow-wrap: anywhere; }
|
||
.detail-grid dd { min-width: 0; margin: 0; overflow-wrap: anywhere; color: var(--navy); font-size: 12px; font-weight: 600; line-height: 1.4; }
|
||
.form-card :deep(.el-card__body) { padding: 0; }
|
||
|
||
@media (max-width: 767px) {
|
||
.business-orders { gap: 14px; }
|
||
.page-head { align-items: flex-start; gap: 12px; }
|
||
.page-head h2 { font-size: 24px; }
|
||
.page-head > div { min-width: 0; }
|
||
|
||
.detail-grid { grid-template-columns: 1fr; }
|
||
.detail-grid div, .detail-grid div.is-wide { grid-column: auto; }
|
||
.detail-grid dd { overflow-wrap: break-word; word-break: normal; }
|
||
.business-detail-heading { align-items: flex-start; flex-direction: column; }
|
||
|
||
.form-card :deep(.el-form-item) { display: block; }
|
||
.form-card :deep(.el-form-item__label) {
|
||
width: auto !important;
|
||
height: auto;
|
||
margin-bottom: 7px;
|
||
line-height: 1.4;
|
||
justify-content: flex-start;
|
||
}
|
||
.form-card :deep(.el-form-item__content) { width: 100%; margin-left: 0 !important; }
|
||
.form-card :deep(.el-input-number) { width: 100%; }
|
||
.form-card .budget-form-item :deep(.el-form-item__content) { display: flex; flex-wrap: nowrap; }
|
||
.form-card .budget-form-item :deep(.el-input-number) { width: auto; min-width: 0; flex: 1 1 auto; }
|
||
.form-card .currency { width: 96px; flex: 0 0 96px; margin-left: 8px; }
|
||
}
|
||
</style>
|