|
|
|
@@ -1,8 +1,545 @@
|
|
|
|
|
<!-- 占位组件: Task 13 实现通讯录视图,签名锁定如下。 -->
|
|
|
|
|
<!-- 员工聊天窗口右列·通讯录(对齐云客 wechat-window 通讯录 tab): 微信客户/微信群双表 +
|
|
|
|
|
搜索(客户 q=昵称/微信号/备注名/手机号,群 q=群名称,均服务端过滤) + 查询/导出CSV/
|
|
|
|
|
批量勾选导出HTML聊天记录(串行+300ms间隔) + 10条/页分页。
|
|
|
|
|
跳转单聊/群聊走本组件内部 router.push(staff-chat),不向父组件 emit。
|
|
|
|
|
根元素必须保持单 div——父组件 StaffChat 靠 attrs fallthrough 挂 sc-root__pane 列宽。 -->
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
defineProps<{ ownerId: string }>()
|
|
|
|
|
import { computed, ref, watch } from 'vue'
|
|
|
|
|
import { useRouter } from 'vue-router'
|
|
|
|
|
import { ElMessage } from 'element-plus'
|
|
|
|
|
import { Download } from '@element-plus/icons-vue'
|
|
|
|
|
import { getCustomers, getGroups } from '@/api'
|
|
|
|
|
import { downloadBlob } from '@/api/client'
|
|
|
|
|
import type { Customer, Group } from '@/api/types'
|
|
|
|
|
import { formatTime } from '@/utils/format'
|
|
|
|
|
import { exportListToCsv } from '@/utils/csvExport'
|
|
|
|
|
|
|
|
|
|
const props = defineProps<{ ownerId: string }>()
|
|
|
|
|
const router = useRouter()
|
|
|
|
|
|
|
|
|
|
/** 云客通讯录每页 10 条(两 tab 同)。 */
|
|
|
|
|
const PAGE_SIZE = 10
|
|
|
|
|
|
|
|
|
|
type TabKey = 'customers' | 'groups'
|
|
|
|
|
const activeTab = ref<TabKey>('customers')
|
|
|
|
|
/** 输入框值 / 已应用值:点「查询」(或回车)才生效重查,对齐云客。 */
|
|
|
|
|
const keyword = ref('')
|
|
|
|
|
const appliedKeyword = ref('')
|
|
|
|
|
|
|
|
|
|
// ---- 客户表状态 ----
|
|
|
|
|
const customers = ref<Customer[]>([])
|
|
|
|
|
const customerTotal = ref(0)
|
|
|
|
|
const customerPage = ref(1)
|
|
|
|
|
const customerLoading = ref(false)
|
|
|
|
|
const customerError = ref<string | null>(null)
|
|
|
|
|
const selectedCustomers = ref<Customer[]>([])
|
|
|
|
|
|
|
|
|
|
// ---- 群表状态 ----
|
|
|
|
|
const groups = ref<Group[]>([])
|
|
|
|
|
const groupTotal = ref(0)
|
|
|
|
|
const groupPage = ref(1)
|
|
|
|
|
const groupLoading = ref(false)
|
|
|
|
|
const groupError = ref<string | null>(null)
|
|
|
|
|
const selectedGroups = ref<Group[]>([])
|
|
|
|
|
|
|
|
|
|
async function loadCustomers(): Promise<void> {
|
|
|
|
|
customerLoading.value = true
|
|
|
|
|
customerError.value = null
|
|
|
|
|
try {
|
|
|
|
|
const res = await getCustomers({
|
|
|
|
|
channel: 'wechat',
|
|
|
|
|
owner_account_id: props.ownerId,
|
|
|
|
|
q: appliedKeyword.value || undefined,
|
|
|
|
|
page: customerPage.value,
|
|
|
|
|
page_size: PAGE_SIZE,
|
|
|
|
|
})
|
|
|
|
|
customers.value = res.items
|
|
|
|
|
customerTotal.value = res.total
|
|
|
|
|
} catch (e) {
|
|
|
|
|
customerError.value = (e as Error).message
|
|
|
|
|
customers.value = []
|
|
|
|
|
customerTotal.value = 0
|
|
|
|
|
} finally {
|
|
|
|
|
customerLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadGroups(): Promise<void> {
|
|
|
|
|
groupLoading.value = true
|
|
|
|
|
groupError.value = null
|
|
|
|
|
try {
|
|
|
|
|
const res = await getGroups({
|
|
|
|
|
channel: 'wechat',
|
|
|
|
|
owner_account_id: props.ownerId,
|
|
|
|
|
q: appliedKeyword.value || undefined,
|
|
|
|
|
page: groupPage.value,
|
|
|
|
|
page_size: PAGE_SIZE,
|
|
|
|
|
})
|
|
|
|
|
groups.value = res.items
|
|
|
|
|
groupTotal.value = res.total
|
|
|
|
|
} catch (e) {
|
|
|
|
|
groupError.value = (e as Error).message
|
|
|
|
|
groups.value = []
|
|
|
|
|
groupTotal.value = 0
|
|
|
|
|
} finally {
|
|
|
|
|
groupLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 当前 tab 重查(tab 切换时总是重查,另一 tab 不会残留旧关键词的旧数据)。 */
|
|
|
|
|
function loadActiveTab(): void {
|
|
|
|
|
if (activeTab.value === 'customers') void loadCustomers()
|
|
|
|
|
else void loadGroups()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 查询: 应用关键词 + 两 tab 回第 1 页 + 重查当前 tab。 */
|
|
|
|
|
function search(): void {
|
|
|
|
|
appliedKeyword.value = keyword.value.trim()
|
|
|
|
|
customerPage.value = 1
|
|
|
|
|
groupPage.value = 1
|
|
|
|
|
loadActiveTab()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onCustomerPageChange(p: number): void {
|
|
|
|
|
customerPage.value = p
|
|
|
|
|
void loadCustomers()
|
|
|
|
|
}
|
|
|
|
|
function onGroupPageChange(p: number): void {
|
|
|
|
|
groupPage.value = p
|
|
|
|
|
void loadGroups()
|
|
|
|
|
}
|
|
|
|
|
/** el-table selection-change 的模板内联箭头带 TS 注解会触发 vue 模板解析错误,收敛到 script。 */
|
|
|
|
|
function onCustomerSelection(rows: Customer[]): void {
|
|
|
|
|
selectedCustomers.value = rows
|
|
|
|
|
}
|
|
|
|
|
function onGroupSelection(rows: Group[]): void {
|
|
|
|
|
selectedGroups.value = rows
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
watch(activeTab, loadActiveTab)
|
|
|
|
|
watch(
|
|
|
|
|
() => props.ownerId,
|
|
|
|
|
() => {
|
|
|
|
|
keyword.value = ''
|
|
|
|
|
appliedKeyword.value = ''
|
|
|
|
|
customerPage.value = 1
|
|
|
|
|
groupPage.value = 1
|
|
|
|
|
loadActiveTab()
|
|
|
|
|
},
|
|
|
|
|
{ immediate: true },
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// ---- 操作列: 查看聊天记录(内部跳转 single/group,无 emit) ----
|
|
|
|
|
function viewCustomerChat(row: Customer): void {
|
|
|
|
|
router.push({
|
|
|
|
|
name: 'staff-chat',
|
|
|
|
|
params: { ownerId: props.ownerId, tab: 'single', talkerId: row.friend_id },
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
function viewGroupChat(row: Group): void {
|
|
|
|
|
router.push({
|
|
|
|
|
name: 'staff-chat',
|
|
|
|
|
params: { ownerId: props.ownerId, tab: 'group', talkerId: row.room_id },
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---- 操作列: 单行导出HTML聊天记录 ----
|
|
|
|
|
async function exportCustomerHtml(row: Customer): Promise<void> {
|
|
|
|
|
try {
|
|
|
|
|
await downloadBlob('/export/chat-html', {
|
|
|
|
|
channel: 'wechat',
|
|
|
|
|
owner_account_id: props.ownerId,
|
|
|
|
|
talker_id: row.friend_id,
|
|
|
|
|
})
|
|
|
|
|
ElMessage.success('导出已开始')
|
|
|
|
|
} catch (e) {
|
|
|
|
|
ElMessage.error(`导出失败,请稍后重试:${(e as Error).message}`)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
async function exportGroupHtml(row: Group): Promise<void> {
|
|
|
|
|
try {
|
|
|
|
|
await downloadBlob('/export/chat-html', {
|
|
|
|
|
channel: 'wechat',
|
|
|
|
|
owner_account_id: props.ownerId,
|
|
|
|
|
room_id: row.room_id,
|
|
|
|
|
})
|
|
|
|
|
ElMessage.success('导出已开始')
|
|
|
|
|
} catch (e) {
|
|
|
|
|
ElMessage.error(`导出失败,请稍后重试:${(e as Error).message}`)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---- 批量导出HTML聊天记录(勾选行,串行 + 300ms 间隔防浏览器拦截多下载) ----
|
|
|
|
|
const batchExporting = ref(false)
|
|
|
|
|
const selectedCount = computed(() =>
|
|
|
|
|
activeTab.value === 'customers'
|
|
|
|
|
? selectedCustomers.value.length
|
|
|
|
|
: selectedGroups.value.length,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
function sleep(ms: number): Promise<void> {
|
|
|
|
|
return new Promise(resolve => setTimeout(resolve, ms))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function exportSelectedHtml(): Promise<void> {
|
|
|
|
|
const targets: { talker_id?: string; room_id?: string }[] =
|
|
|
|
|
activeTab.value === 'customers'
|
|
|
|
|
? selectedCustomers.value.map(c => ({ talker_id: c.friend_id }))
|
|
|
|
|
: selectedGroups.value.map(g => ({ room_id: g.room_id }))
|
|
|
|
|
if (targets.length === 0 || batchExporting.value) return
|
|
|
|
|
batchExporting.value = true
|
|
|
|
|
let done = 0
|
|
|
|
|
try {
|
|
|
|
|
for (const t of targets) {
|
|
|
|
|
await downloadBlob('/export/chat-html', {
|
|
|
|
|
channel: 'wechat',
|
|
|
|
|
owner_account_id: props.ownerId,
|
|
|
|
|
talker_id: t.talker_id,
|
|
|
|
|
room_id: t.room_id,
|
|
|
|
|
})
|
|
|
|
|
done++
|
|
|
|
|
if (done < targets.length) await sleep(300)
|
|
|
|
|
}
|
|
|
|
|
ElMessage.success(`已导出 ${done} 份`)
|
|
|
|
|
} catch (e) {
|
|
|
|
|
ElMessage.error(`导出中断(已完成 ${done}/${targets.length} 份):${(e as Error).message}`)
|
|
|
|
|
} finally {
|
|
|
|
|
batchExporting.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---- 导出 CSV(当前 tab + 当前关键词的全量过滤结果) ----
|
|
|
|
|
const csvExporting = ref(false)
|
|
|
|
|
|
|
|
|
|
async function exportCsv(): Promise<void> {
|
|
|
|
|
if (activeTab.value === 'customers' && customerTotal.value === 0) {
|
|
|
|
|
ElMessage.warning('当前列表为空,无数据可导出')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if (activeTab.value === 'groups' && groupTotal.value === 0) {
|
|
|
|
|
ElMessage.warning('当前列表为空,无数据可导出')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
csvExporting.value = true
|
|
|
|
|
try {
|
|
|
|
|
const result =
|
|
|
|
|
activeTab.value === 'customers'
|
|
|
|
|
? await exportListToCsv<Customer>({
|
|
|
|
|
fetchPage: (page, pageSize) =>
|
|
|
|
|
getCustomers({
|
|
|
|
|
channel: 'wechat',
|
|
|
|
|
owner_account_id: props.ownerId,
|
|
|
|
|
q: appliedKeyword.value || undefined,
|
|
|
|
|
page,
|
|
|
|
|
page_size: pageSize,
|
|
|
|
|
}),
|
|
|
|
|
columns: [
|
|
|
|
|
{ key: 'friend_nickname', label: '客户昵称' },
|
|
|
|
|
{ key: 'friend_id', label: '微信号' },
|
|
|
|
|
{ key: 'friend_remark', label: '备注名' },
|
|
|
|
|
{ key: 'phone', label: '手机号' },
|
|
|
|
|
{
|
|
|
|
|
key: 'last_chat_time',
|
|
|
|
|
label: '最近沟通时间',
|
|
|
|
|
format: r => (r.last_chat_time ? formatTime(r.last_chat_time) : ''),
|
|
|
|
|
},
|
|
|
|
|
{ key: 'send_today', label: '发送条数' },
|
|
|
|
|
{ key: 'receive_today', label: '接收条数' },
|
|
|
|
|
],
|
|
|
|
|
filename: `微信客户_${props.ownerId}`,
|
|
|
|
|
})
|
|
|
|
|
: await exportListToCsv<Group>({
|
|
|
|
|
fetchPage: (page, pageSize) =>
|
|
|
|
|
getGroups({
|
|
|
|
|
channel: 'wechat',
|
|
|
|
|
owner_account_id: props.ownerId,
|
|
|
|
|
q: appliedKeyword.value || undefined,
|
|
|
|
|
page,
|
|
|
|
|
page_size: pageSize,
|
|
|
|
|
}),
|
|
|
|
|
columns: [
|
|
|
|
|
{ key: 'room_name', label: '群名称', format: r => r.room_name ?? r.room_id },
|
|
|
|
|
{ key: 'room_id', label: '群ID' },
|
|
|
|
|
{ key: 'owner_name', label: '群主昵称' },
|
|
|
|
|
{ key: 'owner_wxid', label: '群主微信号' },
|
|
|
|
|
{ key: 'member_count', label: '群成员数量' },
|
|
|
|
|
{ key: 'friend_count', label: '群成员好友数量' },
|
|
|
|
|
{ key: 'message_count', label: '消息数量' },
|
|
|
|
|
],
|
|
|
|
|
filename: `微信群_${props.ownerId}`,
|
|
|
|
|
})
|
|
|
|
|
ElMessage.success(`已导出 ${result.rows} 条`)
|
|
|
|
|
} catch (e) {
|
|
|
|
|
ElMessage.error(`导出失败:${(e as Error).message}`)
|
|
|
|
|
} finally {
|
|
|
|
|
csvExporting.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---- cell helpers ----
|
|
|
|
|
/** 客户显示名: 昵称空时回退 friend_id。 */
|
|
|
|
|
function nickOrId(row: Customer): string {
|
|
|
|
|
return row.friend_nickname || row.friend_id
|
|
|
|
|
}
|
|
|
|
|
/** 发送/接收条数: send_today(后端今日聚合,总有值)缺省时回退 raw_json.sendNum。 */
|
|
|
|
|
function rawNum(row: Customer, camelKey: string): number {
|
|
|
|
|
const v = row.raw_json?.[camelKey]
|
|
|
|
|
return typeof v === 'number' && Number.isFinite(v) ? v : 0
|
|
|
|
|
}
|
|
|
|
|
/** 文本列: 空显 -。 */
|
|
|
|
|
function textOrDash(v: string | null | undefined): string {
|
|
|
|
|
return v && v.trim() ? v : '-'
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<div class="yk-empty">通讯录 — Task 13 实现</div>
|
|
|
|
|
<div class="sc-contacts yk-panel-bg yk-panel-sep">
|
|
|
|
|
<!-- ── 顶部: 客户/群 tab + 搜索框 + 按钮行(查询/导出CSV/批量导出聊天记录) ── -->
|
|
|
|
|
<div class="sc-contacts__top">
|
|
|
|
|
<el-tabs v-model="activeTab" class="sc-contacts__tabs">
|
|
|
|
|
<el-tab-pane label="微信客户" name="customers" />
|
|
|
|
|
<el-tab-pane label="微信群" name="groups" />
|
|
|
|
|
</el-tabs>
|
|
|
|
|
<div class="sc-contacts__row">
|
|
|
|
|
<el-input
|
|
|
|
|
v-model="keyword"
|
|
|
|
|
size="small"
|
|
|
|
|
clearable
|
|
|
|
|
class="sc-contacts__search"
|
|
|
|
|
:placeholder="
|
|
|
|
|
activeTab === 'customers'
|
|
|
|
|
? '支持微信昵称、微信号、备注名、手机号搜索'
|
|
|
|
|
: '请输入群名称'
|
|
|
|
|
"
|
|
|
|
|
@keyup.enter="search"
|
|
|
|
|
/>
|
|
|
|
|
<el-button size="small" type="primary" @click="search">查询</el-button>
|
|
|
|
|
<el-button size="small" :loading="csvExporting" @click="exportCsv">
|
|
|
|
|
<el-icon><Download /></el-icon> 导出
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button
|
|
|
|
|
size="small"
|
|
|
|
|
type="primary"
|
|
|
|
|
plain
|
|
|
|
|
:disabled="selectedCount === 0"
|
|
|
|
|
:loading="batchExporting"
|
|
|
|
|
@click="exportSelectedHtml"
|
|
|
|
|
>导出聊天记录</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- ── 表格区(列内滚动) ── -->
|
|
|
|
|
<div class="sc-contacts__body">
|
|
|
|
|
<!-- 微信客户表(云客 7+1 列) -->
|
|
|
|
|
<template v-if="activeTab === 'customers'">
|
|
|
|
|
<el-alert v-if="customerError" :title="customerError" type="error" :closable="false" />
|
|
|
|
|
<el-table
|
|
|
|
|
v-loading="customerLoading"
|
|
|
|
|
:data="customers"
|
|
|
|
|
row-key="friend_id"
|
|
|
|
|
stripe
|
|
|
|
|
empty-text="暂无客户数据"
|
|
|
|
|
@selection-change="onCustomerSelection"
|
|
|
|
|
>
|
|
|
|
|
<el-table-column type="selection" width="42" />
|
|
|
|
|
<el-table-column label="客户微信" min-width="190">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<div class="sc-contacts__user">
|
|
|
|
|
<el-avatar :size="32" :src="row.head_image_url ?? undefined">
|
|
|
|
|
{{ nickOrId(row).slice(0, 1) }}
|
|
|
|
|
</el-avatar>
|
|
|
|
|
<div class="sc-contacts__meta">
|
|
|
|
|
<div class="sc-contacts__name" :title="nickOrId(row)">{{ nickOrId(row) }}</div>
|
|
|
|
|
<div class="sc-contacts__sub" :title="row.friend_id">{{ row.friend_id }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="数据来源" width="90">
|
|
|
|
|
<template #default>工作手机</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="客户备注名" min-width="100">
|
|
|
|
|
<template #default="{ row }">{{ textOrDash(row.friend_remark) }}</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="最近沟通时间" width="150">
|
|
|
|
|
<template #default="{ row }">{{ formatTime(row.last_chat_time) }}</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="发送条数" width="85" align="right">
|
|
|
|
|
<template #default="{ row }">{{ row.send_today ?? rawNum(row, 'sendNum') }}</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="接收条数" width="85" align="right">
|
|
|
|
|
<template #default="{ row }">{{ row.receive_today ?? rawNum(row, 'receiveNum') }}</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="操作" width="190" fixed="right" align="center">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-button link type="primary" size="small" @click="viewCustomerChat(row)">
|
|
|
|
|
查看聊天记录
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button link type="primary" size="small" @click="exportCustomerHtml(row)">
|
|
|
|
|
导出HTML聊天记录
|
|
|
|
|
</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
<div class="sc-contacts__pager">
|
|
|
|
|
<el-pagination
|
|
|
|
|
:current-page="customerPage"
|
|
|
|
|
:page-size="PAGE_SIZE"
|
|
|
|
|
:total="customerTotal"
|
|
|
|
|
layout="total, prev, pager, next"
|
|
|
|
|
background
|
|
|
|
|
@current-change="onCustomerPageChange"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<!-- 微信群表(云客 8+1 列) -->
|
|
|
|
|
<template v-else>
|
|
|
|
|
<el-alert v-if="groupError" :title="groupError" type="error" :closable="false" />
|
|
|
|
|
<el-table
|
|
|
|
|
v-loading="groupLoading"
|
|
|
|
|
:data="groups"
|
|
|
|
|
row-key="room_id"
|
|
|
|
|
stripe
|
|
|
|
|
empty-text="暂无群数据"
|
|
|
|
|
@selection-change="onGroupSelection"
|
|
|
|
|
>
|
|
|
|
|
<el-table-column type="selection" width="42" />
|
|
|
|
|
<el-table-column label="群信息" min-width="170">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<div class="sc-contacts__name" :title="row.room_name || row.room_id">
|
|
|
|
|
{{ row.room_name || row.room_id }}
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="数据来源" width="90">
|
|
|
|
|
<template #default>工作手机</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="群主信息" min-width="130">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<div class="sc-contacts__owner">
|
|
|
|
|
<div class="sc-contacts__name" :title="row.owner_name ?? undefined">
|
|
|
|
|
{{ textOrDash(row.owner_name) }}
|
|
|
|
|
</div>
|
|
|
|
|
<div class="sc-contacts__sub" :title="row.owner_wxid ?? undefined">
|
|
|
|
|
{{ textOrDash(row.owner_wxid) }}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="群成员数量" width="100" align="right">
|
|
|
|
|
<template #default="{ row }">{{ row.member_count ?? 0 }}</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="群成员好友数量" width="120" align="right">
|
|
|
|
|
<template #default="{ row }">{{ row.friend_count ?? 0 }}</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="消息数量" width="90" align="right">
|
|
|
|
|
<template #default="{ row }">{{ row.message_count ?? 0 }}</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="操作" width="190" fixed="right" align="center">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-button link type="primary" size="small" @click="viewGroupChat(row)">
|
|
|
|
|
查看聊天记录
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button link type="primary" size="small" @click="exportGroupHtml(row)">
|
|
|
|
|
导出HTML聊天记录
|
|
|
|
|
</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
<div class="sc-contacts__pager">
|
|
|
|
|
<el-pagination
|
|
|
|
|
:current-page="groupPage"
|
|
|
|
|
:page-size="PAGE_SIZE"
|
|
|
|
|
:total="groupTotal"
|
|
|
|
|
layout="total, prev, pager, next"
|
|
|
|
|
background
|
|
|
|
|
@current-change="onGroupPageChange"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
/* ══ 通讯录右列(墨玉主题,手法沿用 SessionPane/MomentsPane) ══
|
|
|
|
|
点阵底/右缘玉绿线走全局 yk-panel-bg/yk-panel-sep(style.css);这里只写布局。 */
|
|
|
|
|
.sc-contacts {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
height: 100%;
|
|
|
|
|
min-width: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ── 顶部区(tab + 搜索 + 按钮行,随列固定不滚动) ── */
|
|
|
|
|
.sc-contacts__top {
|
|
|
|
|
flex: none;
|
|
|
|
|
padding: 4px 12px 0;
|
|
|
|
|
background: rgba(255, 255, 255, 0.85);
|
|
|
|
|
backdrop-filter: blur(8px);
|
|
|
|
|
border-bottom: 1px solid rgba(23, 29, 43, 0.05);
|
|
|
|
|
}
|
|
|
|
|
.sc-contacts__tabs :deep(.el-tabs__header) {
|
|
|
|
|
margin: 0 0 6px;
|
|
|
|
|
}
|
|
|
|
|
.sc-contacts__row {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
padding-bottom: 10px;
|
|
|
|
|
}
|
|
|
|
|
.sc-contacts__search {
|
|
|
|
|
flex: 1;
|
|
|
|
|
min-width: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ── 表格区(列内滚动) ── */
|
|
|
|
|
.sc-contacts__body {
|
|
|
|
|
flex: 1;
|
|
|
|
|
min-height: 0;
|
|
|
|
|
overflow: auto;
|
|
|
|
|
padding: 12px;
|
|
|
|
|
}
|
|
|
|
|
.sc-contacts__pager {
|
|
|
|
|
margin-top: 10px;
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 客户微信富单元格(头像 + 昵称 + 微信号,手法对齐 SessionPane 会话卡) */
|
|
|
|
|
.sc-contacts__user {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
}
|
|
|
|
|
.sc-contacts__user .el-avatar {
|
|
|
|
|
flex: none;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
border: 1px solid rgba(23, 29, 43, 0.05);
|
|
|
|
|
box-shadow: 0 1px 3px rgba(23, 29, 43, 0.06);
|
|
|
|
|
}
|
|
|
|
|
.sc-contacts__meta {
|
|
|
|
|
min-width: 0;
|
|
|
|
|
}
|
|
|
|
|
.sc-contacts__name {
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: #2b3240;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
}
|
|
|
|
|
.sc-contacts__sub {
|
|
|
|
|
margin-top: 2px;
|
|
|
|
|
font-size: 11.5px;
|
|
|
|
|
color: #8a94a4;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 群主信息两行(昵称 + wxid,可空显 -) */
|
|
|
|
|
.sc-contacts__owner {
|
|
|
|
|
min-width: 0;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|