提交
This commit is contained in:
1 parent
299609706c
commit
e46c0480d4
87 files changed
+11975
-15543
No files matched your search
@@ -1,159 +1,25 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="innerVisible"
|
||||
<SystemViewDialog
|
||||
v-model:visible="innerVisible"
|
||||
title="查看关联数据"
|
||||
:icon="Link"
|
||||
:sections="sections"
|
||||
:loading="loading"
|
||||
width="1200px"
|
||||
align-center
|
||||
destroy-on-close
|
||||
class="dialog-form-layout db-related-table-dialog"
|
||||
modal-class="order-module-dialog-mask"
|
||||
>
|
||||
<div v-loading="loading" class="db-related-body">
|
||||
<el-empty v-if="!hasData" description="暂无关联数据" />
|
||||
<template v-else>
|
||||
<section v-if="data.plan" class="db-related-table-section">
|
||||
<h3 class="db-related-table-title">计划</h3>
|
||||
<div class="view-subtable-wrap">
|
||||
<el-table :data="planRows" class="view-subtable list-table" stripe>
|
||||
<el-table-column prop="planNo" label="计划号" min-width="120" align="center" />
|
||||
<el-table-column prop="businessType" label="业务类型" min-width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="businessTypeTagType(row.businessType)" size="small">
|
||||
{{ row.businessType }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="customerName"
|
||||
label="往来单位"
|
||||
min-width="150"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="product"
|
||||
label="产品"
|
||||
min-width="120"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column prop="planDate" label="计划日期" min-width="120" align="center" />
|
||||
<el-table-column prop="startDate" label="起始日期" min-width="120" align="center" />
|
||||
<el-table-column prop="endDate" label="截止日期" min-width="120" align="center" />
|
||||
<el-table-column prop="executionStatus" label="执行状态" min-width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="planExecutionTagType(row.executionStatus)" size="small">
|
||||
{{ row.executionStatus || '—' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="plannedQuantity" label="计划量(吨)" min-width="110" align="right" />
|
||||
<el-table-column label="已执行量(吨)" min-width="110" align="right">
|
||||
<template #default="{ row }">
|
||||
{{ formatKgToTon(row.executedQuantity) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="remarks"
|
||||
label="备注"
|
||||
min-width="150"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
</el-table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-if="data.weighing" class="db-related-table-section">
|
||||
<h3 class="db-related-table-title">地磅单</h3>
|
||||
<div class="view-subtable-wrap">
|
||||
<el-table :data="weighingRows" class="view-subtable list-table" stripe>
|
||||
<el-table-column
|
||||
prop="batchNo"
|
||||
label="出入库单号"
|
||||
min-width="130"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column prop="vehicleNo" label="车牌号" min-width="110" align="center" />
|
||||
<el-table-column
|
||||
prop="cargoName"
|
||||
label="货物名称"
|
||||
min-width="120"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column prop="businessType" label="业务类型" min-width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="businessTypeTagType(row.businessType)" size="small">
|
||||
{{ row.businessType }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="supplier"
|
||||
label="供货单位"
|
||||
min-width="150"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="consignee"
|
||||
label="收货单位"
|
||||
min-width="150"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column prop="grossWeight" label="毛重(kg)" min-width="110" align="right" />
|
||||
<el-table-column
|
||||
prop="grossWeightTime"
|
||||
label="毛重时间"
|
||||
min-width="160"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column prop="tareWeight" label="皮重(kg)" min-width="110" align="right" />
|
||||
<el-table-column
|
||||
prop="tareWeightTime"
|
||||
label="皮重时间"
|
||||
min-width="160"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column prop="deduction" label="扣杂(kg)" min-width="110" align="right">
|
||||
<template #default="{ row }">
|
||||
{{ row.deduction ?? 0 }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="netWeight" label="净重(kg)" min-width="110" align="right" />
|
||||
<el-table-column
|
||||
prop="remarks"
|
||||
label="备注"
|
||||
min-width="150"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
</el-table>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="innerVisible = false">
|
||||
<el-icon><Close /></el-icon>
|
||||
关闭
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* UI 模式 P09 · 标准查看弹窗(3 列 KV 表格)
|
||||
* 参考:DbPlanRelatedViewDialog · src/views/db/plans/index.vue
|
||||
*/
|
||||
import { computed } from 'vue'
|
||||
import { Close } from '@element-plus/icons-vue'
|
||||
import { Link } from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import type { InventoryRelatedData } from '@/api/db/inventory'
|
||||
import {
|
||||
businessTypeTagType,
|
||||
formatKgToTon,
|
||||
planExecutionTagType
|
||||
} from '@/components/business/db/db-related-view'
|
||||
import { buildInventoryRelatedSections } from '@/components/business/db/db-related-view'
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
@@ -170,9 +36,5 @@
|
||||
set: (val) => emit('update:visible', val)
|
||||
})
|
||||
|
||||
const hasData = computed(() => Boolean(props.data.plan || props.data.weighing))
|
||||
|
||||
const planRows = computed(() => (props.data.plan ? [props.data.plan] : []))
|
||||
|
||||
const weighingRows = computed(() => (props.data.weighing ? [props.data.weighing] : []))
|
||||
const sections = computed(() => buildInventoryRelatedSections(props.data))
|
||||
</script>
|
||||
@@ -1,131 +1,25 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="innerVisible"
|
||||
<SystemViewDialog
|
||||
v-model:visible="innerVisible"
|
||||
title="查看关联数据"
|
||||
:icon="Link"
|
||||
:sections="sections"
|
||||
:loading="loading"
|
||||
width="1200px"
|
||||
align-center
|
||||
destroy-on-close
|
||||
class="dialog-form-layout db-related-table-dialog"
|
||||
modal-class="order-module-dialog-mask"
|
||||
>
|
||||
<div v-loading="loading" class="db-related-body">
|
||||
<el-empty v-if="!hasData" description="暂无关联数据" />
|
||||
<template v-else>
|
||||
<section v-if="data.inventories.length" class="db-related-table-section">
|
||||
<h3 class="db-related-table-title">出入库单</h3>
|
||||
<div class="view-subtable-wrap">
|
||||
<el-table :data="data.inventories" class="view-subtable list-table" stripe>
|
||||
<el-table-column type="index" label="序号" min-width="60" align="center" />
|
||||
<el-table-column
|
||||
prop="batchNo"
|
||||
label="出入库单号"
|
||||
min-width="130"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column prop="date" label="日期" min-width="120" align="center" />
|
||||
<el-table-column
|
||||
prop="partnerCompany"
|
||||
label="往来单位"
|
||||
min-width="150"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column prop="businessType" label="业务类型" min-width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="businessTypeTagType(row.businessType)" size="small">
|
||||
{{ row.businessType }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="product"
|
||||
label="产品"
|
||||
min-width="120"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column prop="vehicleNo" label="车牌号" min-width="110" align="center" />
|
||||
<el-table-column prop="quantity" label="数量(吨)" min-width="110" align="right" />
|
||||
<el-table-column prop="status" label="状态" min-width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="inventoryStatusTagType(row.status)" size="small">
|
||||
{{ row.status || '待处理' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="remarks"
|
||||
label="备注"
|
||||
min-width="150"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
</el-table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-if="data.weighings.length" class="db-related-table-section">
|
||||
<h3 class="db-related-table-title">地磅单</h3>
|
||||
<div class="view-subtable-wrap">
|
||||
<el-table :data="data.weighings" class="view-subtable list-table" stripe>
|
||||
<el-table-column type="index" label="序号" min-width="60" align="center" />
|
||||
<el-table-column
|
||||
prop="batchNo"
|
||||
label="出入库单号"
|
||||
min-width="130"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column prop="vehicleNo" label="车牌号" min-width="110" align="center" />
|
||||
<el-table-column
|
||||
prop="cargoName"
|
||||
label="货物名称"
|
||||
min-width="120"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column prop="businessType" label="业务类型" min-width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="businessTypeTagType(row.businessType)" size="small">
|
||||
{{ row.businessType }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="grossWeight" label="毛重(kg)" min-width="110" align="right" />
|
||||
<el-table-column
|
||||
prop="grossWeightTime"
|
||||
label="毛重时间"
|
||||
min-width="160"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column prop="tareWeight" label="皮重(kg)" min-width="110" align="right" />
|
||||
<el-table-column
|
||||
prop="tareWeightTime"
|
||||
label="皮重时间"
|
||||
min-width="160"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column prop="netWeight" label="净重(kg)" min-width="110" align="right" />
|
||||
</el-table>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="innerVisible = false">
|
||||
<el-icon><Close /></el-icon>
|
||||
关闭
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* UI 模式 P09 · 标准查看弹窗(3 列 KV 表格)
|
||||
* 参考:DbRelatedViewDialog · src/views/db/weighing/index.vue
|
||||
*/
|
||||
import { computed } from 'vue'
|
||||
import { Close } from '@element-plus/icons-vue'
|
||||
import { Link } from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import type { PlanRelatedData } from '@/api/db/plans'
|
||||
import { businessTypeTagType, inventoryStatusTagType } from '@/components/business/db/db-related-view'
|
||||
import { buildPlanRelatedSections } from '@/components/business/db/db-related-view'
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
@@ -142,7 +36,5 @@
|
||||
set: (val) => emit('update:visible', val)
|
||||
})
|
||||
|
||||
const hasData = computed(
|
||||
() => props.data.inventories.length > 0 || props.data.weighings.length > 0
|
||||
)
|
||||
const sections = computed(() => buildPlanRelatedSections(props.data))
|
||||
</script>
|
||||
@@ -1,95 +0,0 @@
|
||||
<template>
|
||||
<el-select
|
||||
v-model="model"
|
||||
:placeholder="placeholder"
|
||||
filterable
|
||||
remote
|
||||
:remote-method="loadOptions"
|
||||
:loading="loading"
|
||||
:allow-create="allowCreate"
|
||||
default-first-option
|
||||
clearable
|
||||
style="width: 100%"
|
||||
@focus="handleFocus"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:key="item.id"
|
||||
:label="item.vehicleNo"
|
||||
:value="item.vehicleNo"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { vehicleApi, type Vehicle } from '@/api/db/vehicles'
|
||||
|
||||
defineOptions({ name: 'DbVehicleSelect' })
|
||||
|
||||
const model = defineModel<string>({ default: '' })
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
placeholder?: string
|
||||
allowCreate?: boolean
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择或输入车牌号',
|
||||
allowCreate: true
|
||||
}
|
||||
)
|
||||
|
||||
const loading = ref(false)
|
||||
const options = ref<Vehicle[]>([])
|
||||
|
||||
const loadOptions = async (keyword: string) => {
|
||||
loading.value = true
|
||||
try {
|
||||
options.value = await vehicleApi.list(keyword || undefined)
|
||||
} catch (error) {
|
||||
console.error('加载车牌号列表失败', error)
|
||||
options.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleFocus = () => {
|
||||
if (options.value.length === 0) {
|
||||
loadOptions('')
|
||||
}
|
||||
}
|
||||
|
||||
const isSameVehicleNo = (left: string, right: string) =>
|
||||
left.trim().toLowerCase() === right.trim().toLowerCase()
|
||||
|
||||
const vehicleExists = async (vehicleNo: string) => {
|
||||
const trimmed = vehicleNo.trim()
|
||||
if (options.value.some((item) => isSameVehicleNo(item.vehicleNo, trimmed))) {
|
||||
return true
|
||||
}
|
||||
|
||||
const list = await vehicleApi.list(trimmed)
|
||||
const matched = list.find((item) => isSameVehicleNo(item.vehicleNo, trimmed))
|
||||
if (matched && !options.value.some((item) => item.id === matched.id)) {
|
||||
options.value = [...options.value, matched]
|
||||
}
|
||||
return Boolean(matched)
|
||||
}
|
||||
|
||||
/** 保存前确保车牌号已写入基础资料(已存在则跳过,与原地磅系统一致) */
|
||||
const ensureVehicleExists = async () => {
|
||||
const vehicleNo = model.value?.trim()
|
||||
if (!vehicleNo) return
|
||||
if (await vehicleExists(vehicleNo)) return
|
||||
|
||||
try {
|
||||
await vehicleApi.create({ vehicleNo, remarks: null }, { showErrorMessage: false })
|
||||
} catch (err: unknown) {
|
||||
const status = (err as { response?: { status?: number } })?.response?.status
|
||||
if (status !== 409) throw err
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ ensureVehicleExists })
|
||||
</script>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="db-chart-card">
|
||||
<div class="db-chart-card db-dual-bar">
|
||||
<h3 class="db-chart-title">{{ title }}</h3>
|
||||
<div class="db-dual-bar__body">
|
||||
<el-empty v-if="!data.length" description="今日无数据" />
|
||||
@@ -14,8 +14,9 @@
|
||||
实际量
|
||||
</span>
|
||||
</div>
|
||||
<div class="chart-container">
|
||||
<div v-for="item in data" :key="item.label" class="chart-item">
|
||||
<span class="chart-label">{{ item.label }}</span>
|
||||
<span class="chart-label" :title="item.label">{{ item.label }}</span>
|
||||
<div class="chart-bar-group">
|
||||
<div class="chart-bar-wrapper">
|
||||
<div
|
||||
@@ -41,6 +42,7 @@
|
||||
<span class="value actual">{{ item.actual.toFixed(2) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -30,6 +30,8 @@ function sectionTitle(base: string, index: number, total: number): string {
|
||||
export function buildPlanViewSection(plan: Plan): ViewSection {
|
||||
return {
|
||||
title: '计划',
|
||||
fieldLayout: 'table',
|
||||
tableMode: 'triple',
|
||||
fields: [
|
||||
{ label: '计划号', value: plan.planNo },
|
||||
{ label: '业务类型', value: plan.businessType, tagType: businessTypeTagType(plan.businessType) },
|
||||
@@ -57,6 +59,8 @@ export function buildInventoryViewSection(
|
||||
): ViewSection {
|
||||
return {
|
||||
title,
|
||||
fieldLayout: 'table',
|
||||
tableMode: 'triple',
|
||||
fields: [
|
||||
{ label: '出入库单号', value: record.batchNo },
|
||||
{ label: '日期', value: record.date },
|
||||
@@ -120,7 +124,7 @@ export function buildWeighingViewSection(
|
||||
fields.push({ label: '备注', value: record.remarks, span: true })
|
||||
}
|
||||
|
||||
return { title, fields }
|
||||
return { title, fieldLayout: 'table', tableMode: 'triple', fields }
|
||||
}
|
||||
|
||||
type MergedPlanRelatedItem = (PlanInventoryRecord | PlanWeighingRecord) & {
|
||||
@@ -160,7 +164,14 @@ function mergePlanRelatedItems(data: PlanRelatedData): MergedPlanRelatedItem[] {
|
||||
export function buildPlanRelatedSections(data: PlanRelatedData): ViewSection[] {
|
||||
const items = mergePlanRelatedItems(data)
|
||||
if (!items.length) {
|
||||
return [{ title: '关联数据', fields: [{ label: '说明', value: '暂无关联数据', span: true }] }]
|
||||
return [
|
||||
{
|
||||
title: '关联数据',
|
||||
fieldLayout: 'table',
|
||||
tableMode: 'triple',
|
||||
fields: [{ label: '说明', value: '暂无关联数据', span: 'full' }]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const inventoryCount = items.filter((item) => item.type === 'inventory').length
|
||||
@@ -185,7 +196,14 @@ export function buildInventoryRelatedSections(data: InventoryRelatedData): ViewS
|
||||
if (data.plan) sections.push(buildPlanViewSection(data.plan))
|
||||
if (data.weighing) sections.push(buildWeighingViewSection(data.weighing))
|
||||
if (!sections.length) {
|
||||
return [{ title: '关联数据', fields: [{ label: '说明', value: '暂无关联数据', span: true }] }]
|
||||
return [
|
||||
{
|
||||
title: '关联数据',
|
||||
fieldLayout: 'table',
|
||||
tableMode: 'triple',
|
||||
fields: [{ label: '说明', value: '暂无关联数据', span: 'full' }]
|
||||
}
|
||||
]
|
||||
}
|
||||
return sections
|
||||
}
|
||||
@@ -195,7 +213,14 @@ export function buildWeighingRelatedSections(data: WeighingRelatedData): ViewSec
|
||||
if (data.plan) sections.push(buildPlanViewSection(data.plan))
|
||||
if (data.inventory) sections.push(buildInventoryViewSection(data.inventory))
|
||||
if (!sections.length) {
|
||||
return [{ title: '关联数据', fields: [{ label: '说明', value: '暂无关联数据', span: true }] }]
|
||||
return [
|
||||
{
|
||||
title: '关联数据',
|
||||
fieldLayout: 'table',
|
||||
tableMode: 'triple',
|
||||
fields: [{ label: '说明', value: '暂无关联数据', span: 'full' }]
|
||||
}
|
||||
]
|
||||
}
|
||||
return sections
|
||||
}
|
||||
@@ -670,13 +670,13 @@
|
||||
|
||||
dt {
|
||||
margin: 0 0 4px;
|
||||
font-size: 11px;
|
||||
font-size: 13px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
word-break: break-word;
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<el-select
|
||||
v-model="model"
|
||||
:placeholder="placeholder"
|
||||
filterable
|
||||
remote
|
||||
:remote-method="loadOptions"
|
||||
:loading="loading"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
@focus="handleFocus"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in displayOptions"
|
||||
:key="item.id"
|
||||
:label="item.licensePlate"
|
||||
:value="item.licensePlate"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { vehicleApi, type Vehicle } from '@/api/pl'
|
||||
import { pickBxList } from '@/utils/http/bx'
|
||||
|
||||
defineOptions({ name: 'PlVehicleSelect' })
|
||||
|
||||
const model = defineModel<string>({ default: '' })
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
placeholder?: string
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择车牌号'
|
||||
}
|
||||
)
|
||||
|
||||
const loading = ref(false)
|
||||
const options = ref<Vehicle[]>([])
|
||||
|
||||
const loadOptions = async (keyword: string) => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await vehicleApi.list(keyword.trim() ? { q: keyword.trim() } : undefined)
|
||||
options.value = pickBxList<Vehicle>(res)
|
||||
} catch (error) {
|
||||
console.error('加载运输车辆列表失败', error)
|
||||
options.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleFocus = () => {
|
||||
if (options.value.length === 0) {
|
||||
loadOptions('')
|
||||
}
|
||||
}
|
||||
|
||||
/** 编辑态回显:历史出入库单中的车牌若不在运输车辆库,仍展示只读选项 */
|
||||
const displayOptions = computed(() => {
|
||||
const current = model.value?.trim()
|
||||
if (!current) return options.value
|
||||
if (options.value.some((item) => item.licensePlate === current)) {
|
||||
return options.value
|
||||
}
|
||||
return [
|
||||
{
|
||||
id: `legacy-${current}`,
|
||||
licensePlate: current,
|
||||
vehicleType: '',
|
||||
vehicleStatus: '',
|
||||
createTime: ''
|
||||
},
|
||||
...options.value
|
||||
]
|
||||
})
|
||||
</script>
|
||||
@@ -63,12 +63,12 @@
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
font-size: 13px;
|
||||
font-size: 14px;
|
||||
align-items: stretch;
|
||||
|
||||
dt {
|
||||
margin: 0;
|
||||
font-size: 11px;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
color: #000;
|
||||
line-height: 1.4;
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
</template>
|
||||
</tr>
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-else-if="mode === 'triple'">
|
||||
<tr
|
||||
v-for="(row, idx) in tripleRows"
|
||||
:key="tripleRowKey(row, idx)"
|
||||
@@ -72,22 +72,52 @@
|
||||
</template>
|
||||
</tr>
|
||||
</template>
|
||||
<template v-else>
|
||||
<tr
|
||||
v-for="(row, idx) in quadRows"
|
||||
:key="quadRowKey(row, idx)"
|
||||
:class="{ 'row-long': row.type === 'long' }"
|
||||
>
|
||||
<template v-if="row.type === 'long'">
|
||||
<th>{{ row.field.label }}</th>
|
||||
<td colspan="7">
|
||||
<ViewKvTableCell :field="row.field" />
|
||||
</td>
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-for="slot in 4" :key="slot">
|
||||
<template v-if="row.fields[slot - 1]">
|
||||
<th>{{ row.fields[slot - 1].label }}</th>
|
||||
<td>
|
||||
<ViewKvTableCell :field="row.fields[slot - 1]" />
|
||||
</td>
|
||||
</template>
|
||||
<template v-else>
|
||||
<th></th>
|
||||
<td></td>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { computed, provide } from 'vue'
|
||||
import type { ViewField } from './types'
|
||||
import { formatViewValue } from './types'
|
||||
import ViewKvTableCell from './ViewKvTableCell.vue'
|
||||
import {
|
||||
buildViewKvTableDualRows,
|
||||
buildViewKvTableQuadRows,
|
||||
buildViewKvTableTripleRows,
|
||||
getViewFieldDisplayText,
|
||||
isViewFieldFullSpan,
|
||||
isViewFieldLong,
|
||||
type ViewKvTableQuadRow,
|
||||
type ViewKvTableRow,
|
||||
type ViewKvTableTripleRow
|
||||
} from '@/utils/view-field-layout'
|
||||
@@ -95,8 +125,8 @@
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
fields: ViewField[]
|
||||
/** pair:一行一组;dual:一行两组(四列);triple:一行三组(六列) */
|
||||
mode?: 'pair' | 'dual' | 'triple'
|
||||
/** pair:一行一组;dual:一行两组(四列);triple:一行三组(六列);quad:一行四组(八列) */
|
||||
mode?: 'pair' | 'dual' | 'triple' | 'quad'
|
||||
}>(),
|
||||
{
|
||||
mode: 'pair'
|
||||
@@ -105,6 +135,13 @@
|
||||
|
||||
const dualRows = computed(() => buildViewKvTableDualRows(props.fields, formatViewValue))
|
||||
const tripleRows = computed(() => buildViewKvTableTripleRows(props.fields, formatViewValue))
|
||||
const quadRows = computed(() => buildViewKvTableQuadRows(props.fields, formatViewValue))
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'field-view', field: ViewField): void
|
||||
}>()
|
||||
|
||||
provide('viewKvFieldView', (field: ViewField) => emit('field-view', field))
|
||||
|
||||
function isLong(field: ViewField) {
|
||||
const text = getViewFieldDisplayText(field, formatViewValue)
|
||||
@@ -120,6 +157,11 @@
|
||||
if (row.type === 'long') return row.field.label
|
||||
return row.fields.map((f) => f.label).join('-') || String(idx)
|
||||
}
|
||||
|
||||
function quadRowKey(row: ViewKvTableQuadRow, idx: number) {
|
||||
if (row.type === 'long') return row.field.label
|
||||
return row.fields.map((f) => f.label).join('-') || String(idx)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -133,14 +175,14 @@
|
||||
.view-kv-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 13px;
|
||||
font-size: 14px;
|
||||
table-layout: fixed;
|
||||
|
||||
th {
|
||||
padding: 10px 12px;
|
||||
background: var(--table-head, #f8fafc);
|
||||
color: var(--text-muted, #64748b);
|
||||
font-size: 11px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
@@ -203,4 +245,20 @@
|
||||
.view-kv-table--triple tr.row-long td {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.view-kv-table--quad th {
|
||||
width: 8%;
|
||||
}
|
||||
|
||||
.view-kv-table--quad td {
|
||||
width: 17%;
|
||||
}
|
||||
|
||||
.view-kv-table--quad tr.row-long th {
|
||||
width: 8%;
|
||||
}
|
||||
|
||||
.view-kv-table--quad tr.row-long td {
|
||||
width: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -1,30 +1,73 @@
|
||||
<template>
|
||||
<el-tag v-if="field.tagType" :type="field.tagType" size="small">{{
|
||||
formatViewValue(field.value)
|
||||
}}</el-tag>
|
||||
<ul v-else-if="field.items?.length" class="view-plain-list">
|
||||
<li v-for="(item, idx) in field.items" :key="idx">{{ item }}</li>
|
||||
</ul>
|
||||
<span v-else :class="{ 'money-display': field.money, 'muted-display': isMuted }">{{
|
||||
formatViewValue(field.value)
|
||||
}}</span>
|
||||
<div class="view-kv-table-cell">
|
||||
<el-tag v-if="field.tagType" :type="field.tagType" size="small">{{
|
||||
formatViewValue(field.value)
|
||||
}}</el-tag>
|
||||
<ul v-else-if="field.items?.length" class="view-plain-list">
|
||||
<li v-for="(item, idx) in field.items" :key="idx">{{ item }}</li>
|
||||
</ul>
|
||||
<span v-else :class="{ 'money-display': field.money, 'muted-display': isMuted }">{{
|
||||
formatViewValue(field.value)
|
||||
}}</span>
|
||||
<el-button
|
||||
v-if="showViewBtn"
|
||||
link
|
||||
type="primary"
|
||||
size="small"
|
||||
class="view-kv-field-link"
|
||||
@click="handleView"
|
||||
>
|
||||
<el-icon><View /></el-icon>查看
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { computed, inject } from 'vue'
|
||||
import { View } from '@element-plus/icons-vue'
|
||||
import type { ViewField } from './types'
|
||||
import { formatViewValue } from './types'
|
||||
|
||||
const VIEW_KV_FIELD_VIEW_KEY = 'viewKvFieldView'
|
||||
|
||||
const props = defineProps<{
|
||||
field: ViewField
|
||||
}>()
|
||||
|
||||
const onFieldView = inject<((field: ViewField) => void) | undefined>(VIEW_KV_FIELD_VIEW_KEY)
|
||||
|
||||
const isMuted = computed(
|
||||
() => !props.field.money && formatViewValue(props.field.value) === '—'
|
||||
)
|
||||
|
||||
const showViewBtn = computed(() => {
|
||||
if (!props.field.viewTarget || !props.field.viewId || !onFieldView) return false
|
||||
if (props.field.viewTarget !== 'customer' && formatViewValue(props.field.value) === '—') {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
function handleView() {
|
||||
onFieldView?.(props.field)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.view-kv-table-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.view-kv-field-link {
|
||||
margin: 0;
|
||||
padding: 0 4px;
|
||||
height: auto;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.view-plain-list {
|
||||
margin: 0;
|
||||
padding-left: 1.15em;
|
||||
|
||||
@@ -52,14 +52,24 @@
|
||||
>
|
||||
{{ group.title }}
|
||||
</div>
|
||||
<ViewKvTable
|
||||
v-if="group.fields.length && group.fieldLayout === 'table'"
|
||||
:fields="group.fields"
|
||||
:mode="group.tableMode || 'triple'"
|
||||
/>
|
||||
<ViewKvGrid
|
||||
v-if="group.fields.length"
|
||||
v-else-if="group.fields.length"
|
||||
:fields="group.fields"
|
||||
:columns="splitGridColumns"
|
||||
/>
|
||||
<p v-else-if="group.title" class="view-empty-hint">{{ group.emptyHint || '—' }}</p>
|
||||
</div>
|
||||
</template>
|
||||
<ViewKvTable
|
||||
v-else-if="section.fields.length && section.fieldLayout === 'table'"
|
||||
:fields="section.fields"
|
||||
:mode="section.tableMode || 'triple'"
|
||||
/>
|
||||
<ViewKvGrid
|
||||
v-else-if="section.fields.length"
|
||||
:fields="section.fields"
|
||||
@@ -118,14 +128,24 @@
|
||||
>
|
||||
{{ group.title }}
|
||||
</div>
|
||||
<ViewKvTable
|
||||
v-if="group.fields.length && group.fieldLayout === 'table'"
|
||||
:fields="group.fields"
|
||||
:mode="group.tableMode || 'triple'"
|
||||
/>
|
||||
<ViewKvGrid
|
||||
v-if="group.fields.length"
|
||||
v-else-if="group.fields.length"
|
||||
:fields="group.fields"
|
||||
:columns="effectiveGridColumns"
|
||||
/>
|
||||
<p v-else-if="group.title" class="view-empty-hint">{{ group.emptyHint || '—' }}</p>
|
||||
</div>
|
||||
</template>
|
||||
<ViewKvTable
|
||||
v-else-if="section.fields.length && section.fieldLayout === 'table'"
|
||||
:fields="section.fields"
|
||||
:mode="section.tableMode || 'triple'"
|
||||
/>
|
||||
<ViewKvGrid
|
||||
v-else-if="section.fields.length"
|
||||
:fields="section.fields"
|
||||
@@ -158,16 +178,19 @@
|
||||
import { computed, useSlots, type Component } from 'vue'
|
||||
import { Close, Document } from '@element-plus/icons-vue'
|
||||
import ViewKvGrid from './ViewKvGrid.vue'
|
||||
import ViewKvTable from './ViewKvTable.vue'
|
||||
import {
|
||||
isIconComponent,
|
||||
type TagType,
|
||||
type ViewField,
|
||||
type ViewFieldGroup,
|
||||
type ViewFieldLayout,
|
||||
type ViewGridColumns,
|
||||
type ViewSection
|
||||
type ViewSection,
|
||||
type ViewTableMode
|
||||
} from './types'
|
||||
|
||||
export type { TagType, ViewField, ViewFieldGroup, ViewSection }
|
||||
export type { TagType, ViewField, ViewFieldGroup, ViewFieldLayout, ViewSection, ViewTableMode }
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@@ -405,7 +428,7 @@
|
||||
|
||||
.view-field-label {
|
||||
margin: 0 0 8px;
|
||||
font-size: 11px;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
color: #000;
|
||||
line-height: 1.4;
|
||||
@@ -483,7 +506,7 @@
|
||||
}
|
||||
|
||||
:deep(.view-kv) {
|
||||
font-size: 12px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.view-field-label {
|
||||
|
||||
@@ -18,13 +18,22 @@ export interface ViewField {
|
||||
fullSpan?: boolean
|
||||
tagType?: TagType
|
||||
money?: boolean
|
||||
/** 值后展示「查看」按钮,点击时 ViewKvTable 抛出 field-view */
|
||||
viewTarget?: 'customer' | 'opportunity' | 'quote'
|
||||
viewId?: string | number | null
|
||||
}
|
||||
|
||||
export type ViewFieldLayout = 'grid' | 'table'
|
||||
export type ViewTableMode = 'pair' | 'dual' | 'triple' | 'quad'
|
||||
|
||||
export interface ViewFieldGroup {
|
||||
title?: string
|
||||
titleStyle?: 'section' | 'field'
|
||||
fields: ViewField[]
|
||||
emptyHint?: string
|
||||
/** 默认 grid;table 时使用 ViewKvTable */
|
||||
fieldLayout?: ViewFieldLayout
|
||||
tableMode?: ViewTableMode
|
||||
}
|
||||
|
||||
export interface ViewSection {
|
||||
@@ -32,6 +41,9 @@ export interface ViewSection {
|
||||
fields: ViewField[]
|
||||
fieldGroups?: ViewFieldGroup[]
|
||||
titleStyle?: 'section' | 'field'
|
||||
/** 默认 grid;table 时使用 ViewKvTable(如 triple = 一行三组 / 2 行 3 列) */
|
||||
fieldLayout?: ViewFieldLayout
|
||||
tableMode?: ViewTableMode
|
||||
slotTitle?: string
|
||||
slotTitleStyle?: 'section' | 'field'
|
||||
slot?: string
|
||||
|
||||
Reference in new issue
Block a user