提交
This commit is contained in:
1 parent
594e4ce38e
commit
e233269abf
144 files changed
+228178
-111974
No files matched your search
@@ -59,7 +59,7 @@
|
||||
|
||||
<div class="panel dialog-list-panel">
|
||||
<div class="list-body list-wrap dialog-list-body-only">
|
||||
<el-table
|
||||
<ListTable table-key="business-contract-selector"
|
||||
ref="tableRef"
|
||||
v-loading="loading"
|
||||
:data="contractList"
|
||||
@@ -138,7 +138,7 @@
|
||||
{{ planCountMap[String(row.id)] ?? 0 }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</ListTable>
|
||||
|
||||
<ListPagination
|
||||
v-show="total > 0"
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
:remote-method="searchRemote"
|
||||
:loading="loading"
|
||||
:allow-create="allowCreate"
|
||||
default-first-option
|
||||
:default-first-option="allowCreate"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
@focus="handleFocus"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
v-for="item in displayOptions"
|
||||
:key="item.id"
|
||||
:label="item.customerName"
|
||||
:value="item.customerName"
|
||||
@@ -98,6 +98,22 @@
|
||||
loadDefault()
|
||||
}
|
||||
}
|
||||
|
||||
/** 编辑回显:历史往来单位若不在客户库,仍展示只读选项(禁止 allow-create 时) */
|
||||
const displayOptions = computed(() => {
|
||||
const current = model.value?.trim()
|
||||
if (!current) return options.value
|
||||
if (options.value.some((item) => item.customerName === current)) {
|
||||
return options.value
|
||||
}
|
||||
return [
|
||||
{
|
||||
id: `legacy-${current}`,
|
||||
customerName: current
|
||||
},
|
||||
...options.value
|
||||
]
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
productName: string
|
||||
model?: string
|
||||
unit?: string
|
||||
status?: string
|
||||
}
|
||||
|
||||
const dictStore = useDictStore()
|
||||
@@ -60,11 +61,14 @@
|
||||
const options = ref<ProductOption[]>([])
|
||||
|
||||
const formatProductMeta = (item: ProductOption): string => {
|
||||
const parts: string[] = []
|
||||
const model = item.model?.trim() || ''
|
||||
const unitRaw = item.unit?.trim() || ''
|
||||
const unit = unitRaw ? dictStore.getLabel('crm_product_unit', unitRaw) : ''
|
||||
if (model && unit) return `${model} / ${unit}`
|
||||
return model || unit || ''
|
||||
if (model && unit) parts.push(`${model} / ${unit}`)
|
||||
else if (model || unit) parts.push(model || unit)
|
||||
if (item.status === 'off') parts.push('已下架')
|
||||
return parts.join(' · ')
|
||||
}
|
||||
|
||||
const extractList = (res: unknown): ProductOption[] => {
|
||||
@@ -84,6 +88,7 @@
|
||||
specification.value = item.model?.trim() || ''
|
||||
}
|
||||
|
||||
/** 编辑回显:当前产品即使已下架也保留在选项中,新建/重选仅上架产品 */
|
||||
const ensureCurrentOption = async () => {
|
||||
const id = productId.value?.trim()
|
||||
if (!id) return
|
||||
@@ -103,7 +108,7 @@
|
||||
const loadDefault = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await fetchProductList({ page: 1, pageSize: 20 })
|
||||
const res = await fetchProductList({ page: 1, pageSize: 20, status: 'on' })
|
||||
options.value = extractList(res)
|
||||
await ensureCurrentOption()
|
||||
} catch (error) {
|
||||
@@ -124,7 +129,8 @@
|
||||
const res = await fetchProductList({
|
||||
keyword: query.trim(),
|
||||
page: 1,
|
||||
pageSize: 50
|
||||
pageSize: 50,
|
||||
status: 'on'
|
||||
})
|
||||
options.value = extractList(res)
|
||||
await ensureCurrentOption()
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
<!--
|
||||
主列表薄封装:在 el-table 上启用列宽拖拽,并按 table-key 持久化到 localStorage。
|
||||
用法:将 <el-table class="list-table" …> 换成 <ListTable table-key="crm-customer" class="list-table" …>
|
||||
-->
|
||||
<template>
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
v-bind="tableAttrs"
|
||||
:border="resolvedBorder"
|
||||
:allow-drag-last-column="true"
|
||||
:class="tableClass"
|
||||
@header-dragend="handleHeaderDragEnd"
|
||||
>
|
||||
<slot />
|
||||
</el-table>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onMounted, ref, unref, useAttrs, watch } from 'vue'
|
||||
import {
|
||||
resolveListColumnKey,
|
||||
usePersistedListColumnWidths
|
||||
} from '@/composables/usePersistedListColumnWidths'
|
||||
|
||||
defineOptions({ name: 'ListTable', inheritAttrs: false })
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
/** 表格唯一标识,用于 localStorage 分区(同页多表必须不同) */
|
||||
tableKey: string
|
||||
/** 是否持久化列宽,默认 true */
|
||||
persistColumnWidth?: boolean
|
||||
/** 是否开启 border(列宽拖拽依赖);默认 true */
|
||||
border?: boolean
|
||||
}>(),
|
||||
{
|
||||
persistColumnWidth: true,
|
||||
border: true
|
||||
}
|
||||
)
|
||||
|
||||
const attrs = useAttrs()
|
||||
const tableRef = ref<any>(null)
|
||||
const { setWidth, applyToTable, resetWidths, getWidths } = usePersistedListColumnWidths(
|
||||
computed(() => props.tableKey)
|
||||
)
|
||||
|
||||
const resolvedBorder = computed(() => props.border !== false)
|
||||
|
||||
const tableClass = computed(() => {
|
||||
const extra = attrs.class
|
||||
return ['list-table', extra]
|
||||
})
|
||||
|
||||
const tableAttrs = computed(() => {
|
||||
const raw = { ...attrs } as Record<string, unknown>
|
||||
delete raw.class
|
||||
delete raw.border
|
||||
// 避免与内部 handler 重复绑定(父级若仍监听,下面会手动转发)
|
||||
delete raw.onHeaderDragend
|
||||
delete raw.onHeaderDragEnd
|
||||
return raw
|
||||
})
|
||||
|
||||
const parentHeaderDragEnd = computed(() => {
|
||||
const a = attrs as Record<string, unknown>
|
||||
return (a.onHeaderDragend || a.onHeaderDragEnd) as
|
||||
| ((...args: any[]) => void)
|
||||
| Array<(...args: any[]) => void>
|
||||
| undefined
|
||||
})
|
||||
|
||||
let applyTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
const scheduleApply = () => {
|
||||
if (!props.persistColumnWidth) return
|
||||
if (!Object.keys(getWidths()).length) return
|
||||
if (applyTimer) clearTimeout(applyTimer)
|
||||
applyTimer = setTimeout(() => {
|
||||
nextTick(() => {
|
||||
applyToTable(tableRef.value)
|
||||
})
|
||||
}, 16)
|
||||
}
|
||||
|
||||
const handleHeaderDragEnd = (
|
||||
newWidth: number,
|
||||
oldWidth: number,
|
||||
column: any,
|
||||
event: MouseEvent
|
||||
) => {
|
||||
if (props.persistColumnWidth) {
|
||||
const key = resolveListColumnKey(column)
|
||||
if (key) setWidth(key, newWidth)
|
||||
}
|
||||
|
||||
const parent = parentHeaderDragEnd.value
|
||||
if (typeof parent === 'function') {
|
||||
parent(newWidth, oldWidth, column, event)
|
||||
} else if (Array.isArray(parent)) {
|
||||
parent.forEach((fn) => fn?.(newWidth, oldWidth, column, event))
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
scheduleApply()
|
||||
// 首屏列尚未完全注册时再补一次
|
||||
setTimeout(() => scheduleApply(), 100)
|
||||
})
|
||||
|
||||
watch(
|
||||
() => (attrs as any).data,
|
||||
() => scheduleApply(),
|
||||
{ deep: false }
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.tableKey,
|
||||
() => scheduleApply()
|
||||
)
|
||||
|
||||
// 列显隐导致列数量变化时写回(避免在 drag 过程中用 onUpdated 反复 apply)
|
||||
watch(
|
||||
() => {
|
||||
const cols = tableRef.value?.store?.states?.columns
|
||||
return unref(cols)?.length ?? 0
|
||||
},
|
||||
() => scheduleApply()
|
||||
)
|
||||
|
||||
/**
|
||||
* 代理底层 el-table,保证父组件 ref 上的 clearSelection / doLayout 等仍可用
|
||||
*/
|
||||
const exposed = new Proxy(
|
||||
{},
|
||||
{
|
||||
get(_target, prop) {
|
||||
if (prop === 'elTableRef') return tableRef
|
||||
if (prop === 'applyColumnWidths') {
|
||||
return () => applyToTable(tableRef.value)
|
||||
}
|
||||
if (prop === 'resetColumnWidths') {
|
||||
return () => {
|
||||
resetWidths()
|
||||
}
|
||||
}
|
||||
const table = tableRef.value
|
||||
if (!table) return undefined
|
||||
const value = table[prop as keyof typeof table]
|
||||
return typeof value === 'function' ? value.bind(table) : value
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
defineExpose(exposed)
|
||||
</script>
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<div v-if="!certList.length" class="cert-empty">{{ emptyHint }}</div>
|
||||
|
||||
<el-table
|
||||
<ListTable table-key="business-pl-cert-form-list"
|
||||
v-else-if="layout === 'table'"
|
||||
:data="certList"
|
||||
stripe
|
||||
@@ -113,7 +113,7 @@
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</ListTable>
|
||||
|
||||
<div v-else class="cert-card-list">
|
||||
<div v-for="(cert, index) in certList" :key="cert._key" class="cert-card">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<div v-else-if="layout === 'table'" class="panel dialog-list-panel">
|
||||
<div class="list-body list-wrap dialog-list-body-only">
|
||||
<el-table
|
||||
<ListTable table-key="business-pl-cert-view-list"
|
||||
:data="certList"
|
||||
class="list-table"
|
||||
stripe
|
||||
@@ -77,7 +77,7 @@
|
||||
<span v-else>—</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</ListTable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
<h3 class="dialog-section-title">关联运输信息</h3>
|
||||
<div class="panel dialog-list-panel">
|
||||
<div class="list-body list-wrap dialog-list-body-only">
|
||||
<el-table
|
||||
<ListTable table-key="business-pl-freight-disburse-dialog"
|
||||
:data="settlement.transports || []"
|
||||
class="list-table"
|
||||
stripe
|
||||
@@ -159,7 +159,7 @@
|
||||
<el-table-column prop="freightAmount" label="运费(元)" min-width="110" align="right">
|
||||
<template #default="{ row }">{{ formatAmount(row.freightAmount) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</ListTable>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
</div>
|
||||
<div class="panel dialog-list-panel">
|
||||
<div class="list-body list-wrap dialog-list-body-only">
|
||||
<el-table
|
||||
<ListTable table-key="business-pl-freight-settlement-form-dialog"
|
||||
:data="editableTransports"
|
||||
class="list-table"
|
||||
stripe
|
||||
@@ -152,7 +152,7 @@
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</ListTable>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="canEditTransports" class="dialog-section-tip">至少保留一条关联运输;删除后可在「添加运输」中重新加入</p>
|
||||
@@ -222,7 +222,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-body list-wrap dialog-list-body-only">
|
||||
<el-table
|
||||
<ListTable table-key="business-pl-freight-settlement-form-dialog-2"
|
||||
:data="removedTransports"
|
||||
class="list-table"
|
||||
stripe
|
||||
@@ -309,7 +309,7 @@
|
||||
>
|
||||
<template #default="{ row }">{{ formatAmount(row.freightAmount) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</ListTable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -322,7 +322,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-body list-wrap dialog-list-body-only">
|
||||
<el-table
|
||||
<ListTable table-key="business-pl-freight-settlement-form-dialog-3"
|
||||
v-loading="addLoading"
|
||||
:data="availablePlans"
|
||||
class="list-table"
|
||||
@@ -412,7 +412,7 @@
|
||||
row.archiveTime ? formatDateTime(row.archiveTime) : '—'
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</ListTable>
|
||||
|
||||
<div class="pagination list-pager-bar">
|
||||
<div class="list-pager-selected">
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<h4 class="view-section-title">发票信息</h4>
|
||||
<div class="panel dialog-list-panel">
|
||||
<div class="list-body list-wrap dialog-list-body-only">
|
||||
<el-table
|
||||
<ListTable table-key="business-pl-freight-settlement-view-dialog"
|
||||
:data="invoiceRows"
|
||||
class="list-table"
|
||||
stripe
|
||||
@@ -61,7 +61,7 @@
|
||||
<span v-else class="view-kv-value">—</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</ListTable>
|
||||
</div>
|
||||
</div>
|
||||
<div class="view-kv-grid view-kv-grid--cols-2 view-kv-grid--mt">
|
||||
@@ -80,7 +80,7 @@
|
||||
<h4 class="view-section-title">关联运输信息</h4>
|
||||
<div class="panel dialog-list-panel">
|
||||
<div class="list-body list-wrap dialog-list-body-only">
|
||||
<el-table
|
||||
<ListTable table-key="business-pl-freight-settlement-view-dialog-2"
|
||||
:data="settlement.transports || []"
|
||||
class="list-table"
|
||||
stripe
|
||||
@@ -126,7 +126,7 @@
|
||||
<el-table-column prop="freightAmount" label="运费(元)" min-width="110" align="right">
|
||||
<template #default="{ row }">{{ formatAmount(row.freightAmount) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</ListTable>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="panel dialog-list-panel pl-plan-product-table-wrap">
|
||||
<div class="list-body list-wrap dialog-list-body-only">
|
||||
<el-table :data="products" stripe class="list-table pl-plan-product-table" empty-text="暂无产品">
|
||||
<ListTable table-key="business-pl-plan-form-dialog-pl-plan-form-product-table" :data="products" stripe class="list-table pl-plan-product-table" empty-text="暂无产品">
|
||||
<el-table-column label="序号" type="index" min-width="60" align="center" header-align="center" />
|
||||
<el-table-column label="产品名称" min-width="140" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
@@ -140,7 +140,7 @@
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</ListTable>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<p v-if="!hasDriverFeedback" class="pl-plan-form-empty-hint">暂无司机回传</p>
|
||||
<template v-else>
|
||||
<div class="view-subtable-wrap">
|
||||
<el-table :data="driverFeedbackTableRows" class="view-subtable list-table" stripe>
|
||||
<ListTable table-key="business-pl-plan-transport-feedback-blocks" :data="driverFeedbackTableRows" class="view-subtable list-table" stripe>
|
||||
<el-table-column type="index" label="序号" min-width="68" align="center" />
|
||||
<el-table-column
|
||||
prop="productNo"
|
||||
@@ -98,7 +98,7 @@
|
||||
{{ row.remark || '—' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</ListTable>
|
||||
</div>
|
||||
<div class="pl-plan-io-site-meta">
|
||||
<div class="pl-plan-io-site-meta__item">
|
||||
@@ -148,7 +148,7 @@
|
||||
<template v-if="ioOrder">
|
||||
<ViewKvTable :fields="ioOrderViewFields" mode="quint" />
|
||||
<div class="view-subtable-wrap" style="margin-top: 12px">
|
||||
<el-table :data="resolvedIoOrderItems" class="view-subtable list-table" stripe>
|
||||
<ListTable table-key="business-pl-plan-transport-feedback-blocks-2" :data="resolvedIoOrderItems" class="view-subtable list-table" stripe>
|
||||
<el-table-column type="index" label="序号" min-width="68" align="center" />
|
||||
<el-table-column
|
||||
prop="productNo"
|
||||
@@ -215,7 +215,7 @@
|
||||
{{ row.remark || '—' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</ListTable>
|
||||
</div>
|
||||
<div class="pl-plan-io-site-meta">
|
||||
<div class="pl-plan-io-site-meta__item">
|
||||
@@ -238,7 +238,7 @@
|
||||
暂无现场链接
|
||||
</p>
|
||||
<div v-else class="view-subtable-wrap">
|
||||
<el-table :data="siteFeedbackTableRows" class="view-subtable list-table" stripe>
|
||||
<ListTable table-key="business-pl-plan-transport-feedback-blocks-3" :data="siteFeedbackTableRows" class="view-subtable list-table" stripe>
|
||||
<el-table-column type="index" label="序号" min-width="68" align="center" />
|
||||
<el-table-column
|
||||
prop="productName"
|
||||
@@ -294,7 +294,7 @@
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</ListTable>
|
||||
</div>
|
||||
</template>
|
||||
</section>
|
||||
@@ -329,7 +329,7 @@
|
||||
<template v-if="row.weighing">
|
||||
<h5 class="pl-plan-dibang-weighing-title">地磅单</h5>
|
||||
<div class="view-subtable-wrap">
|
||||
<el-table :data="[row.weighing]" class="view-subtable list-table" stripe>
|
||||
<ListTable table-key="business-pl-plan-transport-feedback-blocks-4" :data="[row.weighing]" class="view-subtable list-table" stripe>
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
@@ -402,7 +402,7 @@
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
</el-table>
|
||||
</ListTable>
|
||||
</div>
|
||||
</template>
|
||||
<p v-else class="pl-plan-form-empty-hint pl-plan-dibang-pending">尚未关联地磅单</p>
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
</div>
|
||||
|
||||
<div class="list-body list-wrap dialog-list-body-only">
|
||||
<el-table
|
||||
<ListTable table-key="business-pl-transport-address-select-dialog"
|
||||
class="list-table pl-transport-address-select-table"
|
||||
:data="rows"
|
||||
v-loading="loading"
|
||||
@@ -107,7 +107,7 @@
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</ListTable>
|
||||
|
||||
<ListPagination
|
||||
v-show="pagination.total > 0"
|
||||
|
||||
@@ -15,7 +15,14 @@
|
||||
:key="item.id"
|
||||
:label="item.licensePlate"
|
||||
:value="item.licensePlate"
|
||||
/>
|
||||
>
|
||||
<div class="pl-vehicle-option">
|
||||
<span>{{ item.licensePlate }}</span>
|
||||
<span v-if="vehicleTypeLabel(item)" class="pl-vehicle-option__meta">{{
|
||||
vehicleTypeLabel(item)
|
||||
}}</span>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
@@ -32,17 +39,29 @@
|
||||
placeholder?: string
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择车牌号'
|
||||
placeholder: '请选择车牌号(三方/自有车辆)'
|
||||
}
|
||||
)
|
||||
|
||||
const loading = ref(false)
|
||||
const options = ref<Vehicle[]>([])
|
||||
|
||||
const vehicleTypeLabel = (item: Vehicle): string => {
|
||||
if (item.id?.startsWith('legacy-')) return '历史'
|
||||
if (item.vehicleType === 'self') return '自有'
|
||||
if (item.vehicleType === 'carrier') return '三方'
|
||||
return ''
|
||||
}
|
||||
|
||||
/** 三方车辆 + 山峡自有车辆,禁止手填新建 */
|
||||
const loadOptions = async (keyword: string) => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await vehicleApi.list(keyword.trim() ? { q: keyword.trim() } : undefined)
|
||||
const res = await vehicleApi.list({
|
||||
...(keyword.trim() ? { q: keyword.trim() } : {}),
|
||||
page: 1,
|
||||
pageSize: 100
|
||||
})
|
||||
options.value = pickBxList<Vehicle>(res)
|
||||
} catch (error) {
|
||||
console.error('加载运输车辆列表失败', error)
|
||||
@@ -58,7 +77,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
/** 编辑态回显:历史出入库单中的车牌若不在运输车辆库,仍展示只读选项 */
|
||||
/** 编辑态回显:历史出入库单中的车牌若不在车辆库,仍展示只读选项 */
|
||||
const displayOptions = computed(() => {
|
||||
const current = model.value?.trim()
|
||||
if (!current) return options.value
|
||||
@@ -77,3 +96,19 @@
|
||||
]
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.pl-vehicle-option {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.pl-vehicle-option__meta {
|
||||
flex-shrink: 0;
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user