feat: employee page with channel tabs (WeChat/WhatsApp account lists)

Replace the sparse staff-record table on /staff with a channel-tabbed
employee account view mirroring the 云客 backend's 员工微信列表 +
WhatsApp 员工列表. Each tab loads /api/accounts?channel=<channel> with
its own pagination state; rows open the existing account all-fields
drawer (reused from AccountList via accountFields.ts).

WeChat tab columns: 头像/微信昵称/微信号/手机号/员工姓名/好友数/群数/
发送/接收/数据库大小/在职/在线状态/最后更新. WhatsApp tab columns:
头像(el-avatar initials)/昵称/员工姓名/手机/销售手机/好友数/群数/
联系人数/新增好友/在职/最后更新. Reads raw_json via the structured-first
readField helper; tolerates boolean and 1/0 forms of isOnJob/onlineStatus.

Also fix the avatar field key in accountFields.ts (headImageUrl, with
headImgUrl as a legacy alias) so the WeChat drawer avatar renders — the
upstream wxUser record uses headImageUrl.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
浅念
2026-08-05 14:13:21 +08:00
parent df29dd027a
commit 62dc663294
2 changed files with 512 additions and 191 deletions
+3 -2
View File
@@ -24,6 +24,7 @@ export const ACCOUNT_FIELD_LABELS: Record<string, string> = {
channel: '渠道',
nickName: '昵称',
alias: '微信号',
headImageUrl: '头像',
headImgUrl: '头像',
// 归属员工
userName: '员工姓名',
@@ -75,7 +76,7 @@ const STRUCTURED_ALIAS: Record<string, keyof Account> = {
}
/** Fields whose value is an image URL → render as a thumbnail. */
const IMAGE_KEYS = new Set(['headImgUrl'])
const IMAGE_KEYS = new Set(['headImageUrl', 'headImgUrl'])
/** Fields whose value is a millisecond epoch → format to local datetime. */
const TIMESTAMP_KEYS = new Set(['lastUpdate'])
@@ -93,7 +94,7 @@ export interface AccountFieldGroup {
export const ACCOUNT_FIELD_GROUPS: AccountFieldGroup[] = [
{
title: '基本信息',
keys: ['wechatId', 'uid', 'channel', 'nickName', 'alias', 'headImgUrl'],
keys: ['wechatId', 'uid', 'channel', 'nickName', 'alias', 'headImageUrl', 'headImgUrl'],
},
{
title: '归属员工',
+509 -189
View File
@@ -1,128 +1,219 @@
<script setup lang="ts">
import { computed, onMounted, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import { computed, onMounted, reactive, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { getStaff } from '@/api'
import type { Staff } from '@/api/types'
import { useOrgStore } from '@/stores/org'
import { buildStaffSections, readField } from '@/utils/staffFields'
import { getAccounts } from '@/api'
import type { Account, Channel } from '@/api/types'
import { channelLabel, formatTime } from '@/utils/format'
import { buildAccountSections, formatBytes, readField } from '@/utils/accountFields'
const route = useRoute()
const org = useOrgStore()
/**
* 员工 list — channel-tabbed account view mirroring the 云客 backend's
* 员工微信列表 (WeChat staff accounts) and WhatsApp 员工列表. The earlier sparse
* staff-record table is replaced by the underlying messaging accounts, which
* carry the verbatim upstream record on `raw_json` (nickName/alias/userName/
* sendNum/receiveNum/dbSize/onlineStatus/lastUpdate/talkerCount/...).
*
* Each tab loads `/api/accounts?channel=<channel>` independently with its own
* pagination state. Row click opens the all-fields drawer reused from
* AccountList.vue (see accountFields.ts).
*/
// All-fields drawer state.
const drawerOpen = ref(false)
const drawerStaff = ref<Staff | null>(null)
const staff = ref<Staff[]>([])
const total = ref(0)
const page = ref(1)
const pageSize = ref(20)
const loading = ref(false)
const errorMsg = ref<string | null>(null)
// On-job filter: 'all' (no filter) / 'on' (is_on_job=true) / 'off' (false).
const onJobFilter = ref<'all' | 'on' | 'off'>('all')
const departmentId = computed<string | undefined>(() =>
route.query.department_id ? String(route.query.department_id) : undefined,
)
const onJobOptions: { value: 'all' | 'on' | 'off'; label: string }[] = [
{ value: 'all', label: '全部' },
{ value: 'on', label: '在职' },
{ value: 'off', label: '离职' },
]
function onJobTagType(onJob?: boolean | null): 'success' | 'info' | '' {
if (onJob === true) return 'success'
if (onJob === false) return 'info'
return ''
type TabKey = Channel
interface TabState {
items: Account[]
total: number
page: number
pageSize: number
loading: boolean
errorMsg: string | null
/** Has this tab been loaded at least once? Tabs lazy-load on first activation. */
loaded: boolean
}
function onJobLabel(onJob?: boolean | null): string {
if (onJob === true) return '在职'
if (onJob === false) return '离职'
return '--'
}
function deptName(deptId?: string | null): string | null {
if (!deptId) return null
return org.findNode(deptId)?.label ?? deptId
}
async function load(): Promise<void> {
loading.value = true
errorMsg.value = null
try {
const res = await getStaff({
department_id: departmentId.value,
is_on_job:
onJobFilter.value === 'all' ? undefined : onJobFilter.value === 'on',
page: page.value,
page_size: pageSize.value,
})
staff.value = res.items
total.value = res.total
} catch (e) {
errorMsg.value = (e as Error).message
ElMessage.error(`员工列表加载失败:${(e as Error).message}`)
} finally {
loading.value = false
function freshTabState(): TabState {
return {
items: [],
total: 0,
page: 1,
pageSize: 20,
loading: false,
errorMsg: null,
loaded: false,
}
}
function handlePageChange(p: number): void {
page.value = p
load()
}
function handlePageSizeChange(s: number): void {
pageSize.value = s
page.value = 1
load()
}
// React to the on-job filter + any dept query param.
watch(
() => [onJobFilter.value, departmentId.value] as const,
() => {
page.value = 1
load()
},
)
onMounted(async () => {
// Dept names come from the org tree; best-effort load so the 部门 column
// can render names instead of bare ids. A load failure is non-fatal — the
// column falls back to the raw dept_id.
try {
await org.ensureLoaded()
} catch {
/* surfaced in the aside placeholder */
}
await load()
const activeTab = ref<TabKey>('wechat')
const tabState = reactive<Record<TabKey, TabState>>({
wechat: freshTabState(),
whatsapp: freshTabState(),
})
/** Read a raw_json camelCase key off a staff row, preferring the structured
* snake_case column when it is populated. */
function cell(row: Staff, camelKey: string): unknown {
// All-fields drawer state (shared across tabs).
const drawerOpen = ref(false)
const drawerAccount = ref<Account | null>(null)
// ---------------------------------------------------------------------------
// Cell helpers (structured-first, raw_json fallback via accountFields.readField)
// ---------------------------------------------------------------------------
function cell(row: Account, camelKey: string): unknown {
return readField(row, camelKey)
}
function cellText(row: Staff, camelKey: string): string {
function cellText(row: Account, camelKey: string): string {
const v = cell(row, camelKey)
return v === null || v === undefined || v === '' ? '--' : String(v)
}
function openDrawer(row: Staff): void {
drawerStaff.value = row
function cellNumber(row: Account, camelKey: string): number | undefined {
const v = cell(row, camelKey)
return typeof v === 'number' && Number.isFinite(v) ? v : undefined
}
function cellTime(row: Account, camelKey: string): number | undefined {
return cellNumber(row, camelKey)
}
function imageCell(row: Account, camelKey: string): string | undefined {
const v = cell(row, camelKey)
return typeof v === 'string' && v !== '' ? v : undefined
}
/** First character of userName/nickName/account_id for avatar fallbacks. */
function initials(row: Account): string {
const name =
cell(row, 'userName') ?? cell(row, 'nickName') ?? row.account_id ?? '?'
const s = String(name).trim()
return s ? s.slice(0, 1).toUpperCase() : '?'
}
function avatarUrl(row: Account): string | undefined {
// WeChat records key the avatar as `headImageUrl`; tolerate the legacy
// `headImg` / `headImgUrl` shapes too so the column degrades gracefully.
return (
imageCell(row, 'headImageUrl') ??
imageCell(row, 'headImgUrl') ??
imageCell(row, 'headImg')
)
}
function dbSizeText(row: Account): string {
const v = cellNumber(row, 'dbSize')
return v === undefined ? '--' : formatBytes(v)
}
// 在职: structured column is boolean; raw_json may carry 1/0. Handle both so
// the tag is correct regardless of which source populated the row.
function onJobTagType(v: unknown): 'success' | 'info' | '' {
if (v === true || v === 1 || v === '1') return 'success'
if (v === false || v === 0 || v === '0') return 'info'
return ''
}
function onJobLabel(v: unknown): string {
if (v === true || v === 1 || v === '1') return '在职'
if (v === false || v === 0 || v === '0') return '离职'
return '--'
}
// 在线状态: upstream sends "online"/"offline" (sometimes 1/0). Render as a tag
// when recognized; pass through other strings so info isn't hidden.
function onlineTagType(v: unknown): 'success' | 'info' | '' {
if (v === 'online' || v === 1 || v === '1') return 'success'
if (v === 'offline' || v === 0 || v === '0') return 'info'
return ''
}
function onlineLabel(v: unknown): string {
if (v === 'online' || v === 1 || v === '1') return '在线'
if (v === 'offline' || v === 0 || v === '0') return '离线'
if (v === null || v === undefined || v === '') return '--'
return String(v)
}
function channelTagType(c: Channel): 'success' | 'warning' {
return c === 'wechat' ? 'success' : 'warning'
}
// ---------------------------------------------------------------------------
// Per-tab loading
// ---------------------------------------------------------------------------
async function loadTab(ch: TabKey): Promise<void> {
const st = tabState[ch]
st.loading = true
st.errorMsg = null
try {
const res = await getAccounts({
channel: ch,
page: st.page,
page_size: st.pageSize,
})
st.items = res.items
st.total = res.total
st.loaded = true
} catch (e) {
st.errorMsg = (e as Error).message
ElMessage.error(`员工列表加载失败:${(e as Error).message}`)
} finally {
st.loading = false
}
}
function handlePageChange(ch: TabKey, p: number): void {
tabState[ch].page = p
loadTab(ch)
}
function handlePageSizeChange(ch: TabKey, s: number): void {
tabState[ch].pageSize = s
tabState[ch].page = 1
loadTab(ch)
}
// Per-tab wrappers so template bindings carry proper types (vue-tsc strict
// mode does not infer inline-arrow params in @event handlers).
function onWechatPageChange(p: number): void {
handlePageChange('wechat', p)
}
function onWechatPageSizeChange(s: number): void {
handlePageSizeChange('wechat', s)
}
function onWhatsappPageChange(p: number): void {
handlePageChange('whatsapp', p)
}
function onWhatsappPageSizeChange(s: number): void {
handlePageSizeChange('whatsapp', s)
}
function onTabChange(name: string | number): void {
const ch = String(name) as TabKey
if (!tabState[ch].loaded) loadTab(ch)
}
// ---------------------------------------------------------------------------
// Drawer
// ---------------------------------------------------------------------------
function openDrawer(row: Account): void {
drawerAccount.value = row
drawerOpen.value = true
}
const drawerSections = computed(() =>
drawerStaff.value ? buildStaffSections(drawerStaff.value) : [],
drawerAccount.value ? buildAccountSections(drawerAccount.value) : [],
)
const drawerAvatarSrc = computed<string | undefined>(() => {
const a = drawerAccount.value
return a ? avatarUrl(a) : undefined
})
onMounted(() => {
// The visible tab on first paint is 微信员工 — load it. The other tab
// lazy-loads on activation.
loadTab('wechat')
})
</script>
<template>
@@ -130,103 +221,273 @@ const drawerSections = computed(() =>
<div class="yk-staff__header">
<div>
<h2 style="margin: 0 0 4px; font-size: 16px">员工列表</h2>
<span class="yk-staff__sub">共 {{ total }} 条</span>
</div>
<div class="yk-staff__filters">
<el-radio-group v-model="onJobFilter" size="small">
<el-radio-button
v-for="opt in onJobOptions"
:key="opt.value"
:value="opt.value"
>
{{ opt.label }}
</el-radio-button>
</el-radio-group>
<span class="yk-staff__sub">
按渠道展示员工账号(微信号 / WhatsApp 号),点击行查看全部字段。
</span>
</div>
</div>
<el-alert
v-if="errorMsg"
:title="errorMsg"
type="error"
show-icon
:closable="false"
style="margin-bottom: 12px"
/>
<el-tabs v-model="activeTab" class="yk-staff__tabs" @tab-change="onTabChange">
<!-- ============================================================
微信员工 (WeChat staff accounts)
============================================================ -->
<el-tab-pane label="微信员工" name="wechat">
<el-alert
v-if="tabState.wechat.errorMsg"
:title="tabState.wechat.errorMsg"
type="error"
show-icon
:closable="false"
style="margin-bottom: 12px"
/>
<el-table
v-loading="tabState.wechat.loading"
:data="tabState.wechat.items"
stripe
border
row-key="account_id"
style="width: 100%"
:row-class-name="() => 'yk-clickable-row'"
empty-text="暂无微信员工账号"
@row-click="openDrawer"
>
<el-table-column label="头像" width="64" align="center">
<template #default="{ row }">
<template v-if="avatarUrl(row)">
<el-image
:src="avatarUrl(row)"
fit="cover"
class="yk-avatar-round"
>
<template #error>
<div class="yk-avatar-fallback">{{ initials(row) }}</div>
</template>
</el-image>
</template>
<el-avatar v-else :size="36" shape="circle">
{{ initials(row) }}
</el-avatar>
</template>
</el-table-column>
<el-table-column label="微信昵称" min-width="120">
<template #default="{ row }">{{ cellText(row, 'nickName') }}</template>
</el-table-column>
<el-table-column label="微信号" min-width="120">
<template #default="{ row }">{{ cellText(row, 'alias') }}</template>
</el-table-column>
<el-table-column label="手机号" min-width="120">
<template #default="{ row }">{{ cellText(row, 'phone') }}</template>
</el-table-column>
<el-table-column label="员工姓名" min-width="110">
<template #default="{ row }">{{ cellText(row, 'userName') }}</template>
</el-table-column>
<el-table-column label="好友数" width="90" align="right">
<template #default="{ row }">
{{ cellNumber(row, 'contactsNum') ?? 0 }}
</template>
</el-table-column>
<el-table-column label="群数" width="80" align="right">
<template #default="{ row }">
{{ cellNumber(row, 'groupNum') ?? 0 }}
</template>
</el-table-column>
<el-table-column label="发送" width="90" align="right">
<template #default="{ row }">{{ cellText(row, 'sendNum') }}</template>
</el-table-column>
<el-table-column label="接收" width="90" align="right">
<template #default="{ row }">{{ cellText(row, 'receiveNum') }}</template>
</el-table-column>
<el-table-column label="数据库大小" width="110" align="right">
<template #default="{ row }">{{ dbSizeText(row) }}</template>
</el-table-column>
<el-table-column label="在职" width="90" align="center">
<template #default="{ row }">
<el-tag
:type="onJobTagType(cell(row, 'isOnJob'))"
size="small"
effect="plain"
>
{{ onJobLabel(cell(row, 'isOnJob')) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="在线状态" width="100" align="center">
<template #default="{ row }">
<el-tag
v-if="cell(row, 'onlineStatus') !== undefined && cell(row, 'onlineStatus') !== null && cell(row, 'onlineStatus') !== ''"
:type="onlineTagType(cell(row, 'onlineStatus'))"
size="small"
effect="plain"
>
{{ onlineLabel(cell(row, 'onlineStatus')) }}
</el-tag>
<span v-else style="color: #c0c4cc">--</span>
</template>
</el-table-column>
<el-table-column label="最后更新" width="160">
<template #default="{ row }">
{{ formatTime(cellTime(row, 'lastUpdate')) }}
</template>
</el-table-column>
</el-table>
<el-table
v-loading="loading"
:data="staff"
stripe
border
row-key="user_id"
style="width: 100%"
:row-class-name="() => 'yk-clickable-row'"
empty-text="暂无员工数据"
@row-click="openDrawer"
>
<el-table-column label="员工ID" min-width="120">
<template #default="{ row }">{{ row.user_id }}</template>
</el-table-column>
<el-table-column label="姓名" min-width="120">
<template #default="{ row }">
<span v-if="row.name">{{ row.name }}</span>
<span v-else style="color: #c0c4cc">--</span>
</template>
</el-table-column>
<el-table-column label="手机" min-width="130">
<template #default="{ row }">{{ cellText(row, 'phone') }}</template>
</el-table-column>
<el-table-column label="销售手机" min-width="130">
<template #default="{ row }">{{ cellText(row, 'sale_phone') }}</template>
</el-table-column>
<el-table-column label="部门" min-width="140">
<template #default="{ row }">
<span v-if="deptName(row.dept_id)">{{ deptName(row.dept_id) }}</span>
<span v-else style="color: #c0c4cc">--</span>
</template>
</el-table-column>
<el-table-column label="在职" width="100" align="center">
<template #default="{ row }">
<el-tag
:type="onJobTagType(row.is_on_job)"
size="small"
effect="plain"
>
{{ onJobLabel(row.is_on_job) }}
</el-tag>
</template>
</el-table-column>
</el-table>
<div class="yk-staff__pager">
<el-pagination
:current-page="tabState.wechat.page"
:page-size="tabState.wechat.pageSize"
:page-sizes="[20, 50, 100]"
:total="tabState.wechat.total"
layout="total, sizes, prev, pager, next, jumper"
background
@current-change="onWechatPageChange"
@size-change="onWechatPageSizeChange"
/>
</div>
</el-tab-pane>
<div class="yk-staff__pager">
<el-pagination
:current-page="page"
:page-size="pageSize"
:page-sizes="[20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
background
@current-change="handlePageChange"
@size-change="handlePageSizeChange"
/>
</div>
<p class="yk-staff__hint">点击任意一行查看员工全部字段。</p>
<!-- ============================================================
WhatsApp员工 (WhatsApp staff accounts)
============================================================ -->
<el-tab-pane label="WhatsApp员工" name="whatsapp">
<el-alert
v-if="tabState.whatsapp.errorMsg"
:title="tabState.whatsapp.errorMsg"
type="error"
show-icon
:closable="false"
style="margin-bottom: 12px"
/>
<el-table
v-loading="tabState.whatsapp.loading"
:data="tabState.whatsapp.items"
stripe
border
row-key="account_id"
style="width: 100%"
:row-class-name="() => 'yk-clickable-row'"
empty-text="暂无 WhatsApp 员工账号"
@row-click="openDrawer"
>
<el-table-column label="头像" width="64" align="center">
<template #default="{ row }">
<el-avatar :size="36" shape="circle">
{{ initials(row) }}
</el-avatar>
</template>
</el-table-column>
<el-table-column label="昵称" min-width="120">
<template #default="{ row }">{{ cellText(row, 'nickName') }}</template>
</el-table-column>
<el-table-column label="员工姓名" min-width="110">
<template #default="{ row }">{{ cellText(row, 'userName') }}</template>
</el-table-column>
<el-table-column label="手机" min-width="120">
<template #default="{ row }">{{ cellText(row, 'phone') }}</template>
</el-table-column>
<el-table-column label="销售手机" min-width="120">
<template #default="{ row }">{{ cellText(row, 'salePhone') }}</template>
</el-table-column>
<el-table-column label="好友数" width="90" align="right">
<template #default="{ row }">
{{ cellNumber(row, 'contactsNum') ?? 0 }}
</template>
</el-table-column>
<el-table-column label="群数" width="80" align="right">
<template #default="{ row }">
{{ cellNumber(row, 'groupNum') ?? 0 }}
</template>
</el-table-column>
<el-table-column label="联系人数" width="100" align="right">
<template #default="{ row }">{{ cellText(row, 'talkerCount') }}</template>
</el-table-column>
<el-table-column label="新增好友" width="100" align="right">
<template #default="{ row }">{{ cellText(row, 'friendAddNum') }}</template>
</el-table-column>
<el-table-column label="在职" width="90" align="center">
<template #default="{ row }">
<el-tag
:type="onJobTagType(cell(row, 'isOnJob'))"
size="small"
effect="plain"
>
{{ onJobLabel(cell(row, 'isOnJob')) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="最后更新" width="160">
<template #default="{ row }">
{{ formatTime(cellTime(row, 'lastUpdate')) }}
</template>
</el-table-column>
</el-table>
<!-- All-fields drawer -->
<div class="yk-staff__pager">
<el-pagination
:current-page="tabState.whatsapp.page"
:page-size="tabState.whatsapp.pageSize"
:page-sizes="[20, 50, 100]"
:total="tabState.whatsapp.total"
layout="total, sizes, prev, pager, next, jumper"
background
@current-change="onWhatsappPageChange"
@size-change="onWhatsappPageSizeChange"
/>
</div>
</el-tab-pane>
</el-tabs>
<p class="yk-staff__hint">点击任意一行查看员工账号全部字段。</p>
<!-- All-fields drawer (reused from AccountList.vue / accountFields.ts) -->
<el-drawer
v-model="drawerOpen"
:title="drawerStaff ? `员工详情 · ${drawerStaff.name ?? drawerStaff.user_id}` : '员工详情'"
:title="drawerAccount ? `员工详情 · ${drawerAccount.user_name ?? drawerAccount.nickname ?? drawerAccount.account_id}` : '员工详情'"
direction="rtl"
size="520px"
size="560px"
>
<template v-if="drawerStaff">
<template v-if="drawerAccount">
<div class="yk-drawer__avatar">
<el-avatar :size="56" :src="drawerAvatarSrc" shape="square">
{{ initials(drawerAccount) }}
</el-avatar>
<div class="yk-drawer__avatar-meta">
<div class="yk-drawer__avatar-name">
{{ drawerAccount.user_name ?? drawerAccount.nickname ?? drawerAccount.account_id }}
</div>
<div class="yk-drawer__avatar-sub">
<el-tag
:type="channelTagType(drawerAccount.channel)"
size="small"
effect="plain"
>
{{ channelLabel(drawerAccount.channel) }}
</el-tag>
<el-tag
:type="onJobTagType(cell(drawerAccount, 'isOnJob'))"
size="small"
effect="plain"
style="margin-left: 6px"
>
{{ onJobLabel(cell(drawerAccount, 'isOnJob')) }}
</el-tag>
</div>
</div>
</div>
<div v-for="section in drawerSections" :key="section.title" class="yk-drawer__section">
<el-divider content-position="left">{{ section.title }}</el-divider>
<dl class="yk-drawer__kv">
<div v-for="f in section.fields" :key="f.key" class="yk-drawer__kv-item">
<dt>{{ f.label }}</dt>
<dd v-if="f.render === 'empty'" class="yk-drawer__empty">—</dd>
<dd v-if="f.render === 'image'" class="yk-drawer__img">
<el-image
:src="String(f.value)"
:preview-src-list="[String(f.value)]"
fit="cover"
hide-on-click-modal
/>
</dd>
<dd v-else-if="f.render === 'empty'" class="yk-drawer__empty">—</dd>
<dd v-else>{{ f.text }}</dd>
</div>
</dl>
@@ -251,11 +512,8 @@ const drawerSections = computed(() =>
font-size: 13px;
}
.yk-staff__filters {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
.yk-staff__tabs {
margin-top: 4px;
}
.yk-staff__pager {
@@ -270,6 +528,61 @@ const drawerSections = computed(() =>
color: #c0c4cc;
}
/* Round 36px avatar — el-image renders an <img> inside; size + round it. */
.yk-avatar-round {
width: 36px;
height: 36px;
border-radius: 50%;
overflow: hidden;
display: inline-flex;
align-items: center;
justify-content: center;
background: #f0f2f5;
}
.yk-avatar-round :deep(img) {
width: 36px;
height: 36px;
object-fit: cover;
}
.yk-avatar-fallback {
width: 36px;
height: 36px;
line-height: 36px;
text-align: center;
color: #909399;
font-size: 14px;
background: #f0f2f5;
}
.yk-drawer__avatar {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 4px;
}
.yk-drawer__avatar-meta {
min-width: 0;
}
.yk-drawer__avatar-name {
font-size: 16px;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.yk-drawer__avatar-sub {
font-size: 12px;
color: #909399;
margin-top: 4px;
display: flex;
align-items: center;
}
.yk-drawer__kv {
margin: 0 0 8px;
}
@@ -295,6 +608,13 @@ const drawerSections = computed(() =>
min-width: 0;
}
.yk-drawer__img :deep(.el-image) {
width: 64px;
height: 64px;
border-radius: 4px;
border: 1px solid var(--el-border-color-light, #e4e7ed);
}
.yk-drawer__empty {
color: #c0c4cc;
}