feat(staff): 弹框客户微信/员工微信列改富单元格(头像+昵称+ID+真实姓名,对齐云客)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -81,7 +81,7 @@ async def _aggregate(session: AsyncSession, owner: str, start: int, end: int) ->
|
||||
return agg_rows, last_map
|
||||
|
||||
|
||||
async def _customer_names(session: AsyncSession, owner: str, talker_ids: list[str]) -> dict[str, str]:
|
||||
async def _customer_names(session: AsyncSession, owner: str, talker_ids: list[str]) -> dict[str, tuple[str, str, str]]:
|
||||
if not talker_ids:
|
||||
return {}
|
||||
stmt = sa.select(
|
||||
@@ -89,15 +89,16 @@ async def _customer_names(session: AsyncSession, owner: str, talker_ids: list[st
|
||||
Customer.friend_remark,
|
||||
Customer.friend_nickname,
|
||||
Customer.real_name,
|
||||
Customer.head_image_url,
|
||||
).where(
|
||||
Customer.channel == "wechat",
|
||||
Customer.owner_account_id == owner,
|
||||
Customer.friend_id.in_(talker_ids),
|
||||
)
|
||||
names: dict[str, str] = {}
|
||||
for fid, remark, nick, real in (await session.execute(stmt)).all():
|
||||
names[fid] = remark or nick or real or fid
|
||||
return names
|
||||
info: dict[str, tuple[str, str, str]] = {}
|
||||
for fid, remark, nick, real, head in (await session.execute(stmt)).all():
|
||||
info[fid] = (remark or nick or real or fid, real or "", head or "")
|
||||
return info
|
||||
|
||||
|
||||
@router.get("/staff-chat-stats")
|
||||
@@ -124,7 +125,9 @@ async def get_staff_chat_stats(
|
||||
customers.append(
|
||||
StaffChatCustomerOut(
|
||||
talker_id=tid,
|
||||
friend_name=names.get(tid, tid),
|
||||
friend_name=(names.get(tid) or (tid, "", ""))[0],
|
||||
real_name=(names.get(tid) or ("", "", ""))[1] or None,
|
||||
head_image_url=(names.get(tid) or ("", "", ""))[2] or None,
|
||||
send_count=int(r.send_count or 0),
|
||||
receive_count=int(r.receive_count or 0),
|
||||
interaction_days=int(r.interaction_days or 0),
|
||||
|
||||
@@ -366,6 +366,8 @@ class StaffChatCustomerOut(BaseModel):
|
||||
|
||||
talker_id: str
|
||||
friend_name: str # remark > nickname > real_name > talker_id
|
||||
real_name: str | None = None # 真实姓名(弹框客户微信列显示)
|
||||
head_image_url: str | None = None # 头像(弹框客户微信列显示)
|
||||
send_count: int # 员工发出(is_send True)
|
||||
receive_count: int # 客户发出(is_send False)
|
||||
interaction_days: int # 窗口内不同日期数
|
||||
|
||||
@@ -316,6 +316,8 @@ export interface Message {
|
||||
export interface StaffChatCustomer {
|
||||
talker_id: string
|
||||
friend_name: string
|
||||
real_name?: string | null
|
||||
head_image_url?: string | null
|
||||
send_count: number
|
||||
receive_count: number
|
||||
interaction_days: number
|
||||
|
||||
@@ -79,7 +79,30 @@ watch(
|
||||
:formatter="c.formatter"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template v-if="c.onClick" #default="{ row }">
|
||||
<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>
|
||||
|
||||
@@ -387,7 +387,8 @@ function commModalColsFor(metric: ChatMetric, row: Account): any[] {
|
||||
{
|
||||
prop: 'customer_wx',
|
||||
label: '客户微信',
|
||||
minWidth: 180,
|
||||
minWidth: 200,
|
||||
type: 'person',
|
||||
onClick: (c: any) => openCustomerChat(row, c),
|
||||
},
|
||||
]
|
||||
@@ -398,8 +399,7 @@ function commModalColsFor(metric: ChatMetric, row: Account): any[] {
|
||||
)
|
||||
}
|
||||
cols.push(
|
||||
{ prop: 'staff_info', label: '员工信息', minWidth: 140 },
|
||||
{ prop: 'staff_wx', label: '员工微信', minWidth: 160 },
|
||||
{ prop: 'staff_wx', label: '员工微信', minWidth: 200, type: 'person' },
|
||||
)
|
||||
return cols
|
||||
}
|
||||
@@ -447,9 +447,16 @@ function openWechatCommModal(row: Account, metric: ChatMetric): void {
|
||||
const items = filtered.slice(startIdx, startIdx + page_size).map((c, i) => ({
|
||||
...c,
|
||||
_serial: startIdx + i + 1,
|
||||
customer_wx: joinFields(c.friend_name, c.talker_id),
|
||||
staff_info: staffInfo,
|
||||
customer_wx: c.friend_name,
|
||||
customer_wx_name: c.friend_name,
|
||||
customer_wx_sub: c.talker_id,
|
||||
customer_wx_real: c.real_name || '',
|
||||
customer_wx_avatar: c.head_image_url || '',
|
||||
staff_wx: staffWx,
|
||||
staff_wx_name: cellText(row, 'nickName'),
|
||||
staff_wx_sub: row.account_id,
|
||||
staff_wx_real: cellText(row, 'userName') || '',
|
||||
staff_wx_avatar: cellText(row, 'headImg') || (row.raw_json as any)?.headImg || '',
|
||||
}))
|
||||
// 汇总说明(对齐云客)
|
||||
const metricTitle = METRIC_TITLES[metric]
|
||||
|
||||
Reference in New Issue
Block a user