feat(staffchat): 通讯录(客户表+群表列对齐云客+批量勾选导出+分页)+朋友圈翻页失败内联重试
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -112,7 +112,9 @@ const headerInitial = computed(
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-alert v-if="errorMsg" :title="errorMsg" type="error" :closable="false" />
|
||||
<!-- 首屏失败才在顶部 alert;翻页失败(items>0)改在 feed 底部内联提示+重试,
|
||||
避免反馈出现在远离滚动位置的地方(Task 12 评审 Important #1)。 -->
|
||||
<el-alert v-if="errorMsg && items.length === 0" :title="errorMsg" type="error" :closable="false" />
|
||||
|
||||
<!-- ── feed 流(列内滚动,距底 100px 翻页) ── -->
|
||||
<div
|
||||
@@ -166,6 +168,12 @@ const headerInitial = computed(
|
||||
|
||||
<div v-if="loading && items.length > 0" class="sc-moments__more">加载中…</div>
|
||||
<div v-else-if="!hasMore && items.length > 0" class="sc-moments__more">已到底</div>
|
||||
<!-- 翻页失败的内联错误+重试(page 未推进,loadMore 重试同一页) -->
|
||||
<div
|
||||
v-if="errorMsg && items.length > 0"
|
||||
class="sc-moments__retry"
|
||||
@click="loadMore"
|
||||
>加载失败,点击重试</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -307,4 +315,15 @@ const headerInitial = computed(
|
||||
color: #8a94a4;
|
||||
text-align: center;
|
||||
}
|
||||
/* 翻页失败内联重试行 */
|
||||
.sc-moments__retry {
|
||||
padding: 6px 0 2px;
|
||||
font-size: 11.5px;
|
||||
color: #f56c6c;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
.sc-moments__retry:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user