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
|
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:
|
if not talker_ids:
|
||||||
return {}
|
return {}
|
||||||
stmt = sa.select(
|
stmt = sa.select(
|
||||||
@@ -89,15 +89,16 @@ async def _customer_names(session: AsyncSession, owner: str, talker_ids: list[st
|
|||||||
Customer.friend_remark,
|
Customer.friend_remark,
|
||||||
Customer.friend_nickname,
|
Customer.friend_nickname,
|
||||||
Customer.real_name,
|
Customer.real_name,
|
||||||
|
Customer.head_image_url,
|
||||||
).where(
|
).where(
|
||||||
Customer.channel == "wechat",
|
Customer.channel == "wechat",
|
||||||
Customer.owner_account_id == owner,
|
Customer.owner_account_id == owner,
|
||||||
Customer.friend_id.in_(talker_ids),
|
Customer.friend_id.in_(talker_ids),
|
||||||
)
|
)
|
||||||
names: dict[str, str] = {}
|
info: dict[str, tuple[str, str, str]] = {}
|
||||||
for fid, remark, nick, real in (await session.execute(stmt)).all():
|
for fid, remark, nick, real, head in (await session.execute(stmt)).all():
|
||||||
names[fid] = remark or nick or real or fid
|
info[fid] = (remark or nick or real or fid, real or "", head or "")
|
||||||
return names
|
return info
|
||||||
|
|
||||||
|
|
||||||
@router.get("/staff-chat-stats")
|
@router.get("/staff-chat-stats")
|
||||||
@@ -124,7 +125,9 @@ async def get_staff_chat_stats(
|
|||||||
customers.append(
|
customers.append(
|
||||||
StaffChatCustomerOut(
|
StaffChatCustomerOut(
|
||||||
talker_id=tid,
|
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),
|
send_count=int(r.send_count or 0),
|
||||||
receive_count=int(r.receive_count or 0),
|
receive_count=int(r.receive_count or 0),
|
||||||
interaction_days=int(r.interaction_days or 0),
|
interaction_days=int(r.interaction_days or 0),
|
||||||
|
|||||||
@@ -366,6 +366,8 @@ class StaffChatCustomerOut(BaseModel):
|
|||||||
|
|
||||||
talker_id: str
|
talker_id: str
|
||||||
friend_name: str # remark > nickname > real_name > talker_id
|
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)
|
send_count: int # 员工发出(is_send True)
|
||||||
receive_count: int # 客户发出(is_send False)
|
receive_count: int # 客户发出(is_send False)
|
||||||
interaction_days: int # 窗口内不同日期数
|
interaction_days: int # 窗口内不同日期数
|
||||||
|
|||||||
@@ -316,6 +316,8 @@ export interface Message {
|
|||||||
export interface StaffChatCustomer {
|
export interface StaffChatCustomer {
|
||||||
talker_id: string
|
talker_id: string
|
||||||
friend_name: string
|
friend_name: string
|
||||||
|
real_name?: string | null
|
||||||
|
head_image_url?: string | null
|
||||||
send_count: number
|
send_count: number
|
||||||
receive_count: number
|
receive_count: number
|
||||||
interaction_days: number
|
interaction_days: number
|
||||||
|
|||||||
@@ -79,7 +79,30 @@ watch(
|
|||||||
:formatter="c.formatter"
|
:formatter="c.formatter"
|
||||||
show-overflow-tooltip
|
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)">
|
<el-button link type="primary" size="small" @click="c.onClick?.(row)">
|
||||||
{{ c.formatter ? c.formatter(row, c, row[c.prop], 0) : row[c.prop] }}
|
{{ c.formatter ? c.formatter(row, c, row[c.prop], 0) : row[c.prop] }}
|
||||||
</el-button>
|
</el-button>
|
||||||
|
|||||||
@@ -387,7 +387,8 @@ function commModalColsFor(metric: ChatMetric, row: Account): any[] {
|
|||||||
{
|
{
|
||||||
prop: 'customer_wx',
|
prop: 'customer_wx',
|
||||||
label: '客户微信',
|
label: '客户微信',
|
||||||
minWidth: 180,
|
minWidth: 200,
|
||||||
|
type: 'person',
|
||||||
onClick: (c: any) => openCustomerChat(row, c),
|
onClick: (c: any) => openCustomerChat(row, c),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -398,8 +399,7 @@ function commModalColsFor(metric: ChatMetric, row: Account): any[] {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
cols.push(
|
cols.push(
|
||||||
{ prop: 'staff_info', label: '员工信息', minWidth: 140 },
|
{ prop: 'staff_wx', label: '员工微信', minWidth: 200, type: 'person' },
|
||||||
{ prop: 'staff_wx', label: '员工微信', minWidth: 160 },
|
|
||||||
)
|
)
|
||||||
return cols
|
return cols
|
||||||
}
|
}
|
||||||
@@ -447,9 +447,16 @@ function openWechatCommModal(row: Account, metric: ChatMetric): void {
|
|||||||
const items = filtered.slice(startIdx, startIdx + page_size).map((c, i) => ({
|
const items = filtered.slice(startIdx, startIdx + page_size).map((c, i) => ({
|
||||||
...c,
|
...c,
|
||||||
_serial: startIdx + i + 1,
|
_serial: startIdx + i + 1,
|
||||||
customer_wx: joinFields(c.friend_name, c.talker_id),
|
customer_wx: c.friend_name,
|
||||||
staff_info: staffInfo,
|
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: 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]
|
const metricTitle = METRIC_TITLES[metric]
|
||||||
|
|||||||
Reference in New Issue
Block a user