From 15f886259b0effc61561739681528e764bac8c35 Mon Sep 17 00:00:00 2001
From: weicw <594098497@qq.com>
Date: Sat, 24 Oct 2026 17:37:51 +0800
Subject: [PATCH] =?UTF-8?q?feat(ai):=20=E5=8A=A9=E6=89=8B=E9=A1=B5?=
=?UTF-8?q?=E6=94=AF=E6=8C=81=E5=86=99=E4=BD=9C/=E7=94=9F=E5=9B=BE?=
=?UTF-8?q?=E7=AD=89=E8=AE=B0=E5=BD=95=E7=9A=84=E5=8E=86=E5=8F=B2=E5=88=97?=
=?UTF-8?q?=E8=A1=A8=E4=B8=8E=E6=8C=89=E7=B1=BB=E5=9E=8B=E7=AD=9B=E9=80=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 三模式各自独立工作区:切模式不丢消息、草稿与当前记录
- 历史面板加类型徽标与筛选(全部/聊天/写作/图片/创意/活动策划),文案改「历史记录」
- 生成成功认领末帧 chatId 并刷新列表;聊天 __END__ 帧的 chatId 同样接受
- 写作/图片记录支持「按原参数再来一次」(只回填、不自动发送)
- 文稿/创意/活动策划三页加历史入口,经 storage 传递筛选后 switchTab
- 面板提示仅展示最近 30 条
---
src/pages/activity-outline/index.vue | 13 +
src/pages/assistant/chat-service.js | 8 +
src/pages/assistant/index.vue | 636 ++++++++++++++++++++++---
src/pages/creative-assistant/index.vue | 13 +
src/pages/manuscript/index.vue | 13 +
5 files changed, 630 insertions(+), 53 deletions(-)
diff --git a/src/pages/activity-outline/index.vue b/src/pages/activity-outline/index.vue
index 1e55aca..9e1bfc0 100644
--- a/src/pages/activity-outline/index.vue
+++ b/src/pages/activity-outline/index.vue
@@ -9,6 +9,7 @@
志愿服务
主题沙龙
校园赛事
+ 历史记录
@@ -458,6 +459,18 @@
this.form.event_type = '校园赛事 + 开幕仪式 + 分组淘汰赛'
this.form.event_location = '学校室外篮球场'
this.form.event_meme = '需要体现赛事组织、赛程安排、安全保障、医疗预案和宣传报道计划。'
+ },
+ /**
+ * 历史记录统一在助手页查看。
+ *
+ * 助手页是 tabBar 页,`switchTab` 不能带参数,所以先把类型筛选
+ * 写进 storage,助手页 onShow 读取后立即清除。
+ */
+ goHistoryPage() {
+ uni.setStorageSync('ai_history_type', 'activity_outline')
+ uni.switchTab({
+ url: '/pages/assistant/index'
+ })
}
}
}
diff --git a/src/pages/assistant/chat-service.js b/src/pages/assistant/chat-service.js
index e92bcf6..665883c 100644
--- a/src/pages/assistant/chat-service.js
+++ b/src/pages/assistant/chat-service.js
@@ -370,6 +370,14 @@ export function apiRequest({
})
}
+/**
+ * 历史记录列表。
+ *
+ * @param {object} [params]
+ * @param {string} [params.chatType] 可选,按记录类型精确筛选:
+ * `chat` / `writing` / `image` / `creative` / `activity_outline`;
+ * 不传则返回该用户全部类型的记录(助手页即拉全量后在前端过滤)。
+ */
export function listAiChatList(params) {
return apiRequest({
url: `${MODULES_API_URL}/ai/ai-chat-list`,
diff --git a/src/pages/assistant/index.vue b/src/pages/assistant/index.vue
index fafe8e0..b8d39e3 100644
--- a/src/pages/assistant/index.vue
+++ b/src/pages/assistant/index.vue
@@ -45,7 +45,7 @@
>
请先登录后使用 AI 助手
- 当前页面已接入真实会话逻辑,发送消息和历史会话依赖登录态。
+ 当前页面已接入真实会话逻辑,发送消息和历史记录依赖登录态。
@@ -262,38 +262,91 @@
- 历史会话
- 按最近更新时间展示
+ 历史记录
+ 仅展示最近 30 条,按更新时间排序
关闭
+
+
+
+ {{ option.label }}
+
+
+
{{ section.label }}
-
-
- {{ getHistoryTitle(item) }}
-
- {{ formatHistoryItemTime(item.createTime) }}
+
+
+
+
+ {{ getHistoryTypeLabel(item) }}
+ {{ getHistoryTitle(item) }}
+
+
+ {{ formatHistoryItemTime(item.createTime) }}
+
+
+
+ {{ deletingChatId === item.id ? '删除中' : '删除' }}
-
- {{ deletingChatId === item.id ? '删除中' : '删除' }}
+
+
+
+
+ 记录详情
+ 收起
+
+ 正在加载记录…
+
+
+ {{ isUserMessage(message) ? '你' : 'AI' }}
+ {{ message.content }}
+
+
+
+ 按原参数再来一次
+ 不会自动发送,回填后可修改
+
+
+ 该记录仅可查看,暂不支持再来一次
+
+
- 暂无历史对话
+ 暂无历史记录
@@ -334,6 +387,36 @@
{ label: '写作', value: 'writing' },
{ label: '图片', value: 'image' }
]
+ /*
+ 历史记录的类型(后端 chat_type)。旧数据没有 chat_type,按 chat 处理。
+ 「全部」只用于筛选,不是一种记录类型。
+ */
+ const HISTORY_TYPE_OPTIONS = [
+ { label: '全部', value: 'all' },
+ { label: '聊天', value: 'chat' },
+ { label: '写作', value: 'writing' },
+ { label: '图片', value: 'image' },
+ { label: '创意', value: 'creative' },
+ { label: '活动策划', value: 'activity_outline' }
+ ]
+ const HISTORY_TYPE_META = {
+ chat: { label: '聊天', replayable: false },
+ writing: { label: '写作', replayable: true },
+ image: { label: '图片', replayable: true },
+ creative: { label: '创意', replayable: false },
+ activity_outline: { label: '活动策划', replayable: false }
+ }
+ /* 独立页 switchTab 不能带参,类型经 storage 传进来(见接口契约 §6) */
+ const HISTORY_TYPE_STORAGE_KEY = 'ai_history_type'
+
+ /** 每个模式各自的工作区:消息、当前记录、草稿互不干扰 */
+ const createWorkspace = () => ({
+ messageList: [],
+ activeChatId: null,
+ activeConversationId: '',
+ streamingMessageId: '',
+ draft: ''
+ })
// 图片比例选择器已撤掉:上游创意助手开始节点没有比例变量(实测只有 user_content /
// user_files),前端只能把它拼进 user_content —— 那是替上游写 prompt。
// 要恢复这个功能,先在上游开始节点加变量,再按变量契约接上来。
@@ -362,8 +445,13 @@
participants: ''
},
statusBarHeight: 0,
- draft: '',
activeChatMode: 'chat',
+ // 三个模式各自独立的工作区:切换模式不丢消息、当前记录与草稿
+ workspaces: {
+ chat: createWorkspace(),
+ writing: createWorkspace(),
+ image: createWorkspace()
+ },
loading: false,
wsConnected: false,
scrollIntoView: '',
@@ -374,11 +462,12 @@
'优化竞赛申报书摘要',
'整理五四评优申报材料清单'
],
- messageList: [],
historyList: [],
- activeChatId: null,
- activeConversationId: '',
- streamingMessageId: '',
+ // 历史面板的类型筛选,默认「全部」
+ historyTypeFilter: 'all',
+ historyTypeOptions: HISTORY_TYPE_OPTIONS,
+ // 面板内展开的非聊天记录详情(含写作 / 图片的「再来一次」)
+ historyPreview: null,
showHistoryPanel: false,
deletingChatId: null,
currentUser: {
@@ -394,6 +483,50 @@
}
},
computed: {
+ /** 当前模式的工作区;模板里的 messageList / draft 等都指向它 */
+ workspace() {
+ return this.workspaces[this.activeChatMode] || this.workspaces.chat
+ },
+ messageList: {
+ get() {
+ return this.workspace.messageList
+ },
+ set(value) {
+ this.workspace.messageList = value
+ }
+ },
+ draft: {
+ get() {
+ return this.workspace.draft
+ },
+ set(value) {
+ this.workspace.draft = value
+ }
+ },
+ activeChatId: {
+ get() {
+ return this.workspace.activeChatId
+ },
+ set(value) {
+ this.workspace.activeChatId = value
+ }
+ },
+ activeConversationId: {
+ get() {
+ return this.workspace.activeConversationId
+ },
+ set(value) {
+ this.workspace.activeConversationId = value
+ }
+ },
+ streamingMessageId: {
+ get() {
+ return this.workspace.streamingMessageId
+ },
+ set(value) {
+ this.workspace.streamingMessageId = value
+ }
+ },
navbarTotalHeight() {
return this.statusBarHeight + 44
},
@@ -472,6 +605,9 @@
return 'AI 助手待命中'
},
activeHistoryKey() {
+ if (this.historyPreview && this.historyPreview.key) {
+ return this.historyPreview.key
+ }
if (this.activeChatId) {
return `chat-${this.activeChatId}`
}
@@ -491,9 +627,18 @@
)
return target ? this.getHistoryTitle(target) : '当前会话'
},
+ /** 类型筛选之后的历史记录(全部类型时不复制数组) */
+ historyFilteredList() {
+ if (this.historyTypeFilter === 'all') {
+ return this.historyList
+ }
+ return this.historyList.filter(
+ (item) => this.getHistoryType(item) === this.historyTypeFilter
+ )
+ },
historySections() {
const groupMap = {}
- ;[...this.historyList]
+ ;[...this.historyFilteredList]
.sort((left, right) => this.getTimestamp(right.updateTime || right.createTime) - this.getTimestamp(left.updateTime || left.createTime))
.forEach((item) => {
const label = this.getHistoryDateLabel(item.createTime || item.updateTime)
@@ -502,10 +647,6 @@
}
groupMap[label].push(item)
})
- console.log(Object.keys(groupMap).map((label) => ({
- label,
- items: groupMap[label]
- })))
return Object.keys(groupMap).map((label) => ({
label,
items: groupMap[label]
@@ -518,13 +659,15 @@
},
onShow() {
this.refreshUser()
+ // 独立页经 storage 传来的类型:预置筛选并直接打开历史面板
+ const fromHistoryEntry = this.applyPendingHistoryType()
if (!this.initialized) {
- this.initAssistant()
+ this.initAssistant(!fromHistoryEntry)
return
}
if (this.currentUser.userId) {
this.loadHistoryList(false).catch((error) => {
- this.showError(error, '刷新历史会话失败')
+ this.showError(error, '刷新历史记录失败')
})
}
},
@@ -542,19 +685,45 @@
}
return this.currentUser
},
- async initAssistant() {
+ async initAssistant(autoSelect = true) {
try {
this.refreshUser()
if (!this.currentUser.userId) {
return
}
- await this.loadHistoryList(true)
+ await this.loadHistoryList(autoSelect)
} catch (error) {
this.showError(error, '初始化聊天页失败')
} finally {
this.initialized = true
}
},
+ /**
+ * 读取独立页写入的类型筛选。
+ *
+ * 小程序 `switchTab` 不能带参,所以「文稿 / 创意 / 活动策划」页先
+ * `setStorageSync('ai_history_type', ...)` 再跳过来;这里读完立即清掉,
+ * 避免下次从 tabBar 进来又莫名带上筛选。
+ *
+ * @returns {boolean} 是否由独立页的历史入口跳转而来
+ */
+ applyPendingHistoryType() {
+ let pendingType = ''
+ try {
+ pendingType = uni.getStorageSync(HISTORY_TYPE_STORAGE_KEY)
+ } catch (error) {
+ pendingType = ''
+ }
+ if (!pendingType) {
+ return false
+ }
+ uni.removeStorageSync(HISTORY_TYPE_STORAGE_KEY)
+ const matched = Object.keys(HISTORY_TYPE_META).indexOf(pendingType) > -1
+ this.historyTypeFilter = matched ? pendingType : 'all'
+ this.historyPreview = null
+ this.showHistoryPanel = true
+ return true
+ },
showToast(title) {
uni.showToast({
title,
@@ -649,6 +818,33 @@
}
return '未命名对话'
},
+ /** 记录类型;旧数据没有 chatType,按 chat 处理 */
+ getHistoryType(item) {
+ const value = String((item && item.chatType) || 'chat').trim()
+ return HISTORY_TYPE_META[value] ? value : 'chat'
+ },
+ getHistoryTypeLabel(item) {
+ const type = this.getHistoryType(item)
+ return (HISTORY_TYPE_META[type] && HISTORY_TYPE_META[type].label) || '聊天'
+ },
+ /** 只有写作 / 图片记录能回填原参数「再来一次」 */
+ canReplayHistory(item) {
+ const type = this.getHistoryType(item)
+ return !!(HISTORY_TYPE_META[type] && HISTORY_TYPE_META[type].replayable)
+ },
+ pickHistoryType(value) {
+ if (this.historyTypeFilter === value) {
+ return
+ }
+ this.historyTypeFilter = value
+ this.historyPreview = null
+ },
+ isHistoryPreview(item) {
+ return !!this.historyPreview && this.historyPreview.key === this.getHistoryKey(item)
+ },
+ closeHistoryPreview() {
+ this.historyPreview = null
+ },
parseAssistantContent(value) {
if (!value || value === ASSISTANT_PENDING_TEXT) {
return {
@@ -737,6 +933,8 @@
return
}
this.activeChatMode = mode
+ // 切回某个模式时,该模式自己的消息区滚到底部
+ this.scrollToBottom()
},
scrollToBottom() {
this.bottomAnchorId = `chat-bottom-${Date.now()}`
@@ -806,10 +1004,19 @@
target.content === ASSISTANT_PENDING_TEXT ? answer : `${target.content}${answer}`
this.scrollToBottom()
},
- async finishAssistantMessage(conversationId) {
+ /**
+ * 聊天收尾:刷新历史并认领后端回传的记录 id。
+ *
+ * @param {string} [conversationId] 上游会话标识
+ * @param {number|string} [chatId] `__END__` 帧新增的记录 id(仅成功写入时出现)
+ */
+ async finishAssistantMessage(conversationId, chatId) {
if (conversationId) {
this.activeConversationId = conversationId
}
+ if (chatId !== undefined && chatId !== null && chatId !== '') {
+ this.activeChatId = chatId
+ }
const target = this.messageList.find((item) => item.id === this.streamingMessageId)
if (target) {
target.loading = false
@@ -846,7 +1053,8 @@
try {
const data = JSON.parse(raw)
if (data && data.answer === '__END__') {
- await this.finishAssistantMessage(data.conversationId)
+ // 末帧带后端刚写入的记录 id(失败态没有);带上它才能高亮对应记录
+ await this.finishAssistantMessage(data.conversationId, data.chatId)
this.disconnectWebSocket()
return
}
@@ -963,40 +1171,186 @@
!this.activeConversationId &&
this.historyList.length
) {
- await this.selectChat(this.historyList[0], true)
+ // 只自动打开聊天记录:其他类型没有会话,进面板看详情即可
+ const firstChat = this.historyList.find(
+ (item) => this.getHistoryType(item) === 'chat'
+ )
+ if (firstChat) {
+ await this.selectChat(firstChat, true)
+ }
}
},
- async loadChatHistory(item) {
+ async fetchHistoryMessages(item) {
const { userId } = this.refreshUser()
const response = await listAiChatHistory({
chatId: item.id,
conversationId: item.conversationId,
userId
})
- this.messageList = this.formatHistoryMessages(Array.isArray(response) ? response : [])
+ return this.formatHistoryMessages(Array.isArray(response) ? response : [])
+ },
+ async loadChatHistory(item) {
+ this.messageList = await this.fetchHistoryMessages(item)
this.scrollToBottom()
},
async selectChat(item, silent = false) {
if (this.loading && !silent) {
- this.showToast('当前正在生成回复,请稍后再切换会话')
+ this.showToast('当前正在生成回复,请稍后再切换记录')
return
}
+ // 聊天记录打开会话;其余类型在面板内展开详情
+ if (this.getHistoryType(item) !== 'chat') {
+ await this.openHistoryPreview(item)
+ return
+ }
+ this.historyPreview = null
+ this.activeChatMode = 'chat'
this.activeChatId = item.id || null
this.activeConversationId = item.conversationId || ''
this.showHistoryPanel = false
await this.loadChatHistory(item)
},
+ /** 非聊天记录的面板内详情(写作 / 图片另带「再来一次」) */
+ async openHistoryPreview(item) {
+ const key = this.getHistoryKey(item)
+ // 再点同一条即收起
+ if (this.historyPreview && this.historyPreview.key === key && !this.historyPreview.loading) {
+ this.historyPreview = null
+ return
+ }
+ this.historyPreview = {
+ key,
+ item,
+ messages: [],
+ loading: true
+ }
+ try {
+ const messages = await this.fetchHistoryMessages(item)
+ if (!this.historyPreview || this.historyPreview.key !== key) {
+ return
+ }
+ this.historyPreview.messages = messages
+ } catch (error) {
+ if (this.historyPreview && this.historyPreview.key === key) {
+ this.historyPreview = null
+ this.showError(error, '加载历史记录失败')
+ }
+ } finally {
+ if (this.historyPreview && this.historyPreview.key === key) {
+ this.historyPreview.loading = false
+ }
+ }
+ },
+ /** 解析后端 `params`(入参快照 JSON,老记录为空) */
+ parseHistoryParams(value) {
+ if (!value) {
+ return {}
+ }
+ if (typeof value === 'object') {
+ return value
+ }
+ try {
+ const parsed = JSON.parse(String(value))
+ return parsed && typeof parsed === 'object' ? parsed : {}
+ } catch (error) {
+ return {}
+ }
+ },
+ /** 老记录没有 params 时,退回详情里的用户原话(再退回标题) */
+ historyRecordInput(item) {
+ const messages = (this.historyPreview && this.historyPreview.messages) || []
+ const userMessage = messages.find((message) => this.isUserMessage(message))
+ if (userMessage && String(userMessage.content || '').trim()) {
+ return String(userMessage.content || '').trim()
+ }
+ return String((item && item.title) || '').trim()
+ },
+ /**
+ * 按原参数再来一次:只回填参数与输入框,不自动发送。
+ *
+ * 写作回填文体 / 字数 / 写作参数 / 正文(自由创作回填 userContent),
+ * 图片回填提示词;创意与活动策划没有可回填的界面参数,只读。
+ */
+ replayHistoryItem(item) {
+ const type = this.getHistoryType(item)
+ if (!this.canReplayHistory(item)) {
+ this.showToast('该记录仅可查看')
+ return
+ }
+ const params = this.parseHistoryParams(item.params)
+ if (type === 'writing') {
+ this.fillWritingFromHistory(item, params)
+ } else {
+ this.fillImageFromHistory(item, params)
+ }
+ this.activeChatMode = type
+ this.showHistoryPanel = false
+ this.historyPreview = null
+ this.showToast('已按原参数回填,可修改后发送')
+ },
+ fillWritingFromHistory(item, params) {
+ const docType = String(params.docType || params.doc_type || '').trim()
+ this.writingDocType =
+ docType && docType !== WRITING_DOC_TYPE_FREE ? docType : WRITING_DOC_TYPE_FREE
+ const wordCount = params.number
+ this.writingWordCount =
+ wordCount !== undefined && wordCount !== null && String(wordCount).trim()
+ ? String(wordCount)
+ : 'auto'
+ const nextParams = {
+ eventType: String(params.eventType || params.event_type || '').trim(),
+ theme: String(params.theme || '').trim(),
+ time: String(params.time || '').trim(),
+ location: String(params.location || '').trim(),
+ participants: String(params.participants || '').trim()
+ }
+ this.writingParams = nextParams
+ // 有参数就展开,方便用户看到回填结果
+ this.writingParamsOpen = ['theme', 'time', 'location', 'participants'].some(
+ (key) => !!nextParams[key]
+ )
+ const input = String(
+ params.highlights || params.userContent || params.user_content || ''
+ ).trim()
+ this.workspaces.writing.draft = input || this.historyRecordInput(item)
+ },
+ fillImageFromHistory(item, params) {
+ const input = String(params.user_content || params.userContent || '').trim()
+ this.workspaces.image.draft = input || this.historyRecordInput(item)
+ },
+ /**
+ * 认领生成记录:取末帧 `chatId` 设为当前模式的高亮项,并刷新历史。
+ * 失败态没有 chatId,调用方只在成功分支调用。
+ */
+ async claimGenerationRecord(payload) {
+ const chatId =
+ payload && payload.chatId !== undefined && payload.chatId !== null
+ ? payload.chatId
+ : payload && payload.data && payload.data.chatId
+ if (chatId !== undefined && chatId !== null && chatId !== '') {
+ this.activeChatId = chatId
+ }
+ try {
+ await this.loadHistoryList(false)
+ } catch (error) {
+ // 列表刷新失败不该把已成功的生成提示成「发送失败」
+ console.warn('refresh history after generation failed', error)
+ }
+ },
startNewChat() {
if (this.loading) {
this.showToast('当前正在生成回复,请稍后再开启新对话')
return
}
- this.disconnectWebSocket()
+ if (this.activeChatMode === 'chat') {
+ this.disconnectWebSocket()
+ }
this.activeChatId = null
this.activeConversationId = ''
this.streamingMessageId = ''
this.messageList = []
this.draft = ''
+ this.historyPreview = null
this.showHistoryPanel = false
},
/**
@@ -1318,6 +1672,8 @@
}).promise
if (finalPayload && Number(finalPayload.code) === 0) {
setResult(this.formatWritingResult(finalPayload.data))
+ // 末帧带上后端刚写入的记录 id(失败态没有)
+ await this.claimGenerationRecord(finalPayload)
return
}
if (finalPayload) {
@@ -1359,32 +1715,35 @@
return
}
setResult(this.formatCreativeResult(finalPayload.data))
+ await this.claimGenerationRecord(finalPayload)
}
},
openHistoryPanel() {
+ this.historyPreview = null
this.showHistoryPanel = true
if (this.currentUser.userId) {
this.loadHistoryList(false).catch((error) => {
- this.showError(error, '加载历史会话失败')
+ this.showError(error, '加载历史记录失败')
})
}
},
closeHistoryPanel() {
this.showHistoryPanel = false
+ this.historyPreview = null
},
async removeChat(item) {
if (this.loading) {
- this.showToast('当前正在生成回复,请稍后再删除会话')
+ this.showToast('当前正在生成回复,请稍后再删除记录')
return
}
if (!item.id) {
- this.showToast('当前会话缺少标识,无法删除')
+ this.showToast('当前记录缺少标识,无法删除')
return
}
const result = await new Promise((resolve) => {
uni.showModal({
- title: '删除会话',
+ title: '删除记录',
content: `确定删除“${this.getHistoryTitle(item)}”吗?`,
success: resolve,
fail: () => resolve({ confirm: false })
@@ -1394,15 +1753,36 @@
return
}
+ const itemKey = this.getHistoryKey(item)
+ const itemType = this.getHistoryType(item)
+ // 同一条记录可能正开在某个模式的工作区里(三模式各自独立)
+ const affectedModes = Object.keys(this.workspaces).filter((mode) => {
+ const workspace = this.workspaces[mode]
+ return (
+ workspace.activeChatId === item.id ||
+ (item.conversationId && workspace.activeConversationId === item.conversationId)
+ )
+ })
+
this.deletingChatId = item.id
try {
- const isActive =
- this.activeChatId === item.id ||
- (item.conversationId && this.activeConversationId === item.conversationId)
await removeAiChatList(item.id)
+ if (this.historyPreview && this.historyPreview.key === itemKey) {
+ this.historyPreview = null
+ }
await this.loadHistoryList(false)
- if (isActive) {
- const nextChat = this.historyList[0]
+ affectedModes.forEach((mode) => {
+ const workspace = this.workspaces[mode]
+ workspace.activeChatId = null
+ workspace.activeConversationId = ''
+ workspace.streamingMessageId = ''
+ workspace.messageList = []
+ })
+ // 聊天记录被删且正开在聊天模式:接上最近的一条聊天记录
+ if (itemType === 'chat' && this.activeChatMode === 'chat' && affectedModes.indexOf('chat') > -1) {
+ const nextChat = this.historyList.find(
+ (candidate) => this.getHistoryType(candidate) === 'chat'
+ )
if (nextChat) {
this.activeChatId = nextChat.id || null
this.activeConversationId = nextChat.conversationId || ''
@@ -2087,6 +2467,37 @@
font-size: 24rpx;
}
+ /* 类型筛选:与写作参数一致,单行横向滑动,不换行堆叠 */
+ .history-filter {
+ flex-shrink: 0;
+ width: 100%;
+ margin-top: 20rpx;
+ white-space: nowrap;
+ }
+
+ .history-filter-track {
+ display: inline-flex;
+ align-items: center;
+ gap: 10rpx;
+ }
+
+ .history-filter-chip {
+ flex-shrink: 0;
+ padding: 10rpx 18rpx;
+ border-radius: 999rpx;
+ border: 1rpx solid rgba(20, 150, 242, 0.18);
+ background: rgba(255, 255, 255, 0.9);
+ color: #1496f2;
+ font-size: 22rpx;
+ line-height: 1.3;
+ }
+
+ .history-filter-chip--active {
+ border-color: transparent;
+ background: linear-gradient(145deg, #0f7dd1 0%, #1496f2 100%);
+ color: #fff;
+ }
+
.history-scroll {
flex: 1;
min-height: 0;
@@ -2104,6 +2515,10 @@
color: #8b817b;
}
+ .history-item-wrap + .history-item-wrap {
+ margin-top: 14rpx;
+ }
+
.history-item {
display: flex;
align-items: center;
@@ -2115,10 +2530,6 @@
box-shadow: 0 10rpx 28rpx rgba(76, 49, 35, 0.06);
}
- .history-item + .history-item {
- margin-top: 14rpx;
- }
-
.history-item--active {
border-color: rgba(20, 150, 242, 0.2);
background: #fff1ec;
@@ -2129,7 +2540,48 @@
min-width: 0;
}
+ .history-item-head {
+ display: flex;
+ align-items: flex-start;
+ gap: 10rpx;
+ }
+
+ /* 类型徽标:聊天 / 写作 / 图片 / 创意 / 活动策划 */
+ .history-type-badge {
+ flex-shrink: 0;
+ margin-top: 4rpx;
+ padding: 2rpx 12rpx;
+ border-radius: 10rpx;
+ background: rgba(20, 150, 242, 0.1);
+ color: #1496f2;
+ font-size: 20rpx;
+ font-weight: 600;
+ line-height: 1.6;
+ }
+
+ .history-type-badge--writing {
+ background: rgba(31, 143, 73, 0.1);
+ color: #1f8f49;
+ }
+
+ .history-type-badge--image {
+ background: rgba(240, 122, 61, 0.12);
+ color: #c1652d;
+ }
+
+ .history-type-badge--creative {
+ background: rgba(123, 97, 255, 0.1);
+ color: #6a53d6;
+ }
+
+ .history-type-badge--activity_outline {
+ background: rgba(20, 150, 242, 0.08);
+ color: #0f7dd1;
+ }
+
.history-item-title {
+ flex: 1;
+ min-width: 0;
font-size: 26rpx;
font-weight: 700;
color: #2f3136;
@@ -2151,6 +2603,84 @@
font-size: 22rpx;
}
+ /* 面板内的记录详情:仅非聊天类型出现 */
+ .history-preview {
+ margin-top: 10rpx;
+ padding: 18rpx 20rpx;
+ border-radius: 20rpx;
+ border: 1rpx dashed rgba(20, 150, 242, 0.24);
+ background: rgba(255, 255, 255, 0.86);
+ }
+
+ .history-preview-head {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12rpx;
+ }
+
+ .history-preview-caption {
+ font-size: 22rpx;
+ font-weight: 600;
+ color: #1496f2;
+ }
+
+ .history-preview-close {
+ font-size: 20rpx;
+ color: #8a7f79;
+ }
+
+ .history-preview-hint {
+ padding: 14rpx 0;
+ font-size: 22rpx;
+ color: #978b84;
+ }
+
+ .history-preview-row {
+ margin-top: 14rpx;
+ }
+
+ .history-preview-role {
+ margin-bottom: 6rpx;
+ font-size: 20rpx;
+ color: #8e837d;
+ }
+
+ .history-preview-text {
+ font-size: 24rpx;
+ line-height: 1.7;
+ color: #2f3136;
+ word-break: break-all;
+ white-space: pre-wrap;
+ }
+
+ .history-preview-markdown {
+ display: block;
+ white-space: normal;
+ }
+
+ .history-preview-actions {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 12rpx;
+ margin-top: 18rpx;
+ }
+
+ .history-preview-replay {
+ padding: 12rpx 22rpx;
+ border-radius: 999rpx;
+ background: linear-gradient(145deg, #0f7dd1 0%, #1496f2 100%);
+ color: #fff;
+ font-size: 22rpx;
+ font-weight: 600;
+ }
+
+ .history-preview-tip {
+ font-size: 20rpx;
+ color: #978b84;
+ }
+
.history-empty {
padding: 40rpx 20rpx;
text-align: center;
diff --git a/src/pages/creative-assistant/index.vue b/src/pages/creative-assistant/index.vue
index 3c1ce2f..c183a56 100644
--- a/src/pages/creative-assistant/index.vue
+++ b/src/pages/creative-assistant/index.vue
@@ -10,6 +10,7 @@
短视频创意
脚本创作
海报排版
+ 历史记录
@@ -405,6 +406,18 @@
return
}
this.content = '请根据上传的活动照片、LOGO 和文案素材,生成一套公众号头图与活动海报排版,风格青春、明亮,输出图片文件。'
+ },
+ /**
+ * 历史记录统一在助手页查看。
+ *
+ * 助手页是 tabBar 页,`switchTab` 不能带参数,所以先把类型筛选
+ * 写进 storage,助手页 onShow 读取后立即清除。
+ */
+ goHistoryPage() {
+ uni.setStorageSync('ai_history_type', 'creative')
+ uni.switchTab({
+ url: '/pages/assistant/index'
+ })
}
}
}
diff --git a/src/pages/manuscript/index.vue b/src/pages/manuscript/index.vue
index f898c22..a535153 100644
--- a/src/pages/manuscript/index.vue
+++ b/src/pages/manuscript/index.vue
@@ -10,6 +10,7 @@
公众号推文
通知公告
自由创作示例
+ 历史记录
进入问答助手
@@ -743,6 +744,18 @@
uni.switchTab({
url: '/pages/assistant/index'
})
+ },
+ /**
+ * 历史记录统一在助手页查看。
+ *
+ * 助手页是 tabBar 页,`switchTab` 不能带参数,所以先把类型筛选
+ * 写进 storage,助手页 onShow 读取后立即清除。
+ */
+ goHistoryPage() {
+ uni.setStorageSync('ai_history_type', 'writing')
+ uni.switchTab({
+ url: '/pages/assistant/index'
+ })
}
}
}