This commit is contained in:
zlwu committed 2026-06-21 12:41:39 +08:00
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