feat(sns): 朋友圈页支持联系人侧栏、导出与 Live Photo

- 左侧新增朋友圈联系人列表(按发圈数),支持搜索与“全部/单人”筛选
- 新增“导出全部/导出此人”,展示导出状态并支持下载 ZIP(SSE 优先,轮询兜底)
- Live Photo/实况:悬停播放、静音切换与预览弹窗
- 媒体请求统一透传 use_cache;关闭缓存时追加时间戳避免浏览器缓存
This commit is contained in:
2977094657
2026-02-17 23:41:34 +08:00
parent d0fed14381
commit 3dbf5993d1
3 changed files with 1034 additions and 55 deletions

View File

@@ -0,0 +1,29 @@
<template>
<svg
:width="size"
:height="size"
viewBox="0 0 24 24"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<!-- Keep the SVG identical to WeFlow/src/components/LivePhotoIcon.tsx for visual consistency -->
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g stroke="currentColor" stroke-width="2">
<circle fill="currentColor" stroke="none" cx="12" cy="12" r="2.5" />
<circle cx="12" cy="12" r="5.5" />
<circle cx="12" cy="12" r="9" stroke-dasharray="1 3.7" />
</g>
</g>
</svg>
</template>
<script setup>
defineProps({
size: {
type: [Number, String],
default: 24
}
})
</script>

View File

@@ -236,6 +236,16 @@ export const useApi = () => {
return await request(url)
}
// 朋友圈联系人列表(按发圈数统计)
const listSnsUsers = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.account) query.set('account', params.account)
if (params && params.keyword) query.set('keyword', String(params.keyword))
if (params && params.limit != null) query.set('limit', String(params.limit))
const url = '/sns/users' + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
// 朋友圈图片本地缓存候选(用于错图时手动选择)
const listSnsMediaCandidates = async (params = {}) => {
const query = new URLSearchParams()
@@ -356,6 +366,31 @@ export const useApi = () => {
return await request(`/chat/exports/${encodeURIComponent(String(exportId))}`, { method: 'DELETE' })
}
// 朋友圈导出(离线 HTML zip
const createSnsExport = async (data = {}) => {
return await request('/sns/exports', {
method: 'POST',
body: {
account: data.account || null,
scope: data.scope || 'selected',
usernames: Array.isArray(data.usernames) ? data.usernames : [],
use_cache: data.use_cache == null ? true : !!data.use_cache,
output_dir: data.output_dir == null ? null : String(data.output_dir || '').trim(),
file_name: data.file_name || null
}
})
}
const getSnsExport = async (exportId) => {
if (!exportId) throw new Error('Missing exportId')
return await request(`/sns/exports/${encodeURIComponent(String(exportId))}`)
}
const cancelSnsExport = async (exportId) => {
if (!exportId) throw new Error('Missing exportId')
return await request(`/sns/exports/${encodeURIComponent(String(exportId))}`, { method: 'DELETE' })
}
// 联系人
const listChatContacts = async (params = {}) => {
const query = new URLSearchParams()
@@ -454,6 +489,7 @@ export const useApi = () => {
resolveNestedChatHistory,
resolveAppMsg,
listSnsTimeline,
listSnsUsers,
listSnsMediaCandidates,
saveSnsMediaPicks,
openChatMediaFolder,
@@ -465,6 +501,9 @@ export const useApi = () => {
getChatExport,
listChatExports,
cancelChatExport,
createSnsExport,
getSnsExport,
cancelSnsExport,
listChatContacts,
exportChatContacts,
getWrappedAnnual,

File diff suppressed because it is too large Load Diff