b6eea3070a
Co-Authored-By: Claude <noreply@anthropic.com>
127 lines
4.4 KiB
Vue
127 lines
4.4 KiB
Vue
<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'
|
||
/** el-table-column formatter (row, column, cellValue, index) → display string. */
|
||
formatter?: (row: any, column: any, cellValue: any, index: number) => string
|
||
/** 该列单元格可点:渲染为 link button,点击触发回调(用于 modal 内跳聊天等)。 */
|
||
onClick?: (row: any) => void
|
||
}
|
||
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>>
|
||
/** 弹框顶部汇总说明(对齐云客) */
|
||
summaryText?: string
|
||
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)"
|
||
>
|
||
<div v-if="summaryText" class="yk-detail-modal__summary">{{ summaryText }}</div>
|
||
|
||
<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"
|
||
:formatter="c.formatter"
|
||
show-overflow-tooltip
|
||
>
|
||
<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 }">
|
||
<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>
|
||
</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>
|