2026-08-05 18:51:35 +08:00
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
import { ref, watch } from 'vue'
|
|
|
|
|
|
import type { Page } from '@/api/types'
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 通用明细 modal —— 复刻云客"统计数字点击弹明细表"。
|
|
|
|
|
|
* 调用方传 fetcher(返回 Page<T>)+ 列定义;组件负责 dialog/table/pagination。
|
|
|
|
|
|
*/
|
|
|
|
|
|
interface Col {
|
|
|
|
|
|
prop: string
|
|
|
|
|
|
label: string
|
|
|
|
|
|
width?: number | string
|
|
|
|
|
|
minWidth?: number | string
|
|
|
|
|
|
align?: 'left' | 'center' | 'right'
|
2026-08-05 18:58:06 +08:00
|
|
|
|
/** el-table-column formatter (row, column, cellValue, index) → display string. */
|
|
|
|
|
|
formatter?: (row: any, column: any, cellValue: any, index: number) => string
|
2026-08-06 17:32:05 +08:00
|
|
|
|
/** 该列单元格可点:渲染为 link button,点击触发回调(用于 modal 内跳聊天等)。 */
|
|
|
|
|
|
onClick?: (row: any) => void
|
2026-08-05 18:51:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
const props = defineProps<{
|
|
|
|
|
|
modelValue: boolean
|
|
|
|
|
|
title: string
|
|
|
|
|
|
cols: Col[]
|
|
|
|
|
|
/** fetcher(page, page_size) → Page<T>。宽松 Page<any>:Customer 无 index signature,
|
|
|
|
|
|
* 不可直接赋 Page<Record<string,unknown>>,故用 any 接纳 Page<Customer> 等。 */
|
|
|
|
|
|
fetcher: (page: number, page_size: number) => Promise<Page<any>>
|
2026-08-25 10:08:44 +08:00
|
|
|
|
/** 弹框顶部汇总说明(对齐云客) */
|
|
|
|
|
|
summaryText?: string
|
2026-08-05 18:51:35 +08:00
|
|
|
|
pageSizes?: number[]
|
|
|
|
|
|
}>()
|
|
|
|
|
|
const emit = defineEmits<{ 'update:modelValue': [boolean] }>()
|
|
|
|
|
|
|
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
|
const items = ref<any[]>([])
|
|
|
|
|
|
const total = ref(0)
|
|
|
|
|
|
const page = ref(1)
|
|
|
|
|
|
const pageSize = ref(props.pageSizes?.[0] ?? 10)
|
|
|
|
|
|
|
|
|
|
|
|
async function load() {
|
|
|
|
|
|
loading.value = true
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await props.fetcher(page.value, pageSize.value)
|
|
|
|
|
|
items.value = res.items
|
|
|
|
|
|
total.value = res.total
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
loading.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
watch(
|
|
|
|
|
|
() => props.modelValue,
|
|
|
|
|
|
(open) => {
|
|
|
|
|
|
if (open) {
|
|
|
|
|
|
page.value = 1
|
|
|
|
|
|
load()
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
)
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<el-dialog
|
|
|
|
|
|
:model-value="modelValue"
|
|
|
|
|
|
:title="title"
|
|
|
|
|
|
width="900px"
|
|
|
|
|
|
@update:model-value="emit('update:modelValue', $event)"
|
|
|
|
|
|
>
|
2026-08-25 10:08:44 +08:00
|
|
|
|
<div v-if="summaryText" class="yk-detail-modal__summary">{{ summaryText }}</div>
|
|
|
|
|
|
|
2026-08-05 18:51:35 +08:00
|
|
|
|
<el-table v-loading="loading" :data="items" border style="width: 100%" size="small">
|
|
|
|
|
|
<el-table-column
|
|
|
|
|
|
v-for="c in cols"
|
|
|
|
|
|
:key="c.prop"
|
|
|
|
|
|
:prop="c.prop"
|
|
|
|
|
|
:label="c.label"
|
|
|
|
|
|
:width="c.width"
|
|
|
|
|
|
:min-width="c.minWidth"
|
|
|
|
|
|
:align="c.align"
|
2026-08-05 18:58:06 +08:00
|
|
|
|
:formatter="c.formatter"
|
2026-08-05 18:51:35 +08:00
|
|
|
|
show-overflow-tooltip
|
2026-08-06 17:32:05 +08:00
|
|
|
|
>
|
2026-08-25 10:24:07 +08:00
|
|
|
|
<template v-if="c.type === 'person'" #default="{ row }">
|
|
|
|
|
|
<div style="display: flex; align-items: center; gap: 8px; padding: 2px 0">
|
|
|
|
|
|
<el-avatar :size="28" :src="row[c.prop + '_avatar'] || undefined" shape="square" style="flex: none; border-radius: 6px;">
|
|
|
|
|
|
{{ (row[c.prop + '_name'] || '?').slice(0, 1) }}
|
|
|
|
|
|
</el-avatar>
|
|
|
|
|
|
<div style="min-width: 0; flex: 1">
|
|
|
|
|
|
<div v-if="c.onClick" >
|
|
|
|
|
|
<el-button link type="primary" size="small" @click="c.onClick?.(row)">
|
|
|
|
|
|
{{ row[c.prop + '_name'] || row[c.prop] || '--' }}
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-else style="font-size: 13px; color: #2b3240; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
|
|
|
|
|
{{ row[c.prop + '_name'] || row[c.prop] || '--' }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-if="row[c.prop + '_sub']" style="font-size: 11px; color: #8a94a4; margin-top: 1px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
|
|
|
|
|
{{ row[c.prop + '_sub'] }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-if="row[c.prop + '_real']" style="font-size: 11px; color: #5a6472; margin-top: 1px;">
|
|
|
|
|
|
{{ row[c.prop + '_real'] }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<template v-else-if="c.onClick" #default="{ row }">
|
2026-08-06 17:32:05 +08:00
|
|
|
|
<el-button link type="primary" size="small" @click="c.onClick?.(row)">
|
|
|
|
|
|
{{ c.formatter ? c.formatter(row, c, row[c.prop], 0) : row[c.prop] }}
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
2026-08-05 18:51:35 +08:00
|
|
|
|
</el-table>
|
|
|
|
|
|
<div style="margin-top: 10px; display: flex; justify-content: flex-end">
|
|
|
|
|
|
<el-pagination
|
|
|
|
|
|
:current-page="page"
|
|
|
|
|
|
:page-size="pageSize"
|
|
|
|
|
|
:page-sizes="pageSizes ?? [10, 20, 50]"
|
|
|
|
|
|
:total="total"
|
|
|
|
|
|
layout="total, sizes, prev, pager, next"
|
|
|
|
|
|
background
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
@current-change="(p: number) => { page = p; load() }"
|
|
|
|
|
|
@size-change="(s: number) => { pageSize = s; page = 1; load() }"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
</template>
|