feat(ai): 助手页支持写作/生图等记录的历史列表与按类型筛选

- 三模式各自独立工作区:切模式不丢消息、草稿与当前记录
- 历史面板加类型徽标与筛选(全部/聊天/写作/图片/创意/活动策划),文案改「历史记录」
- 生成成功认领末帧 chatId 并刷新列表;聊天 __END__ 帧的 chatId 同样接受
- 写作/图片记录支持「按原参数再来一次」(只回填、不自动发送)
- 文稿/创意/活动策划三页加历史入口,经 storage 传递筛选后 switchTab
- 面板提示仅展示最近 30 条
This commit is contained in:
2026-10-24 17:37:51 +08:00
parent a2f973bd91
commit 15f886259b
5 changed files with 630 additions and 53 deletions
+13
View File
@@ -9,6 +9,7 @@
<view class="hero-action" @tap="fillExample('volunteer')">志愿服务</view>
<view class="hero-action" @tap="fillExample('salon')">主题沙龙</view>
<view class="hero-action" @tap="fillExample('sports')">校园赛事</view>
<view class="hero-action" @tap="goHistoryPage">历史记录</view>
</view>
</view>
@@ -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'
})
}
}
}
+8
View File
@@ -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`,
+583 -53
View File
@@ -45,7 +45,7 @@
>
<view v-if="!currentUser.userId" class="notice-card">
<view class="notice-title">请先登录后使用 AI 助手</view>
<view class="notice-desc">当前页面已接入真实会话逻辑,发送消息和历史会话依赖登录态。</view>
<view class="notice-desc">当前页面已接入真实会话逻辑,发送消息和历史记录依赖登录态。</view>
</view>
<view v-else-if="!messageList.length" class="empty-card">
@@ -262,38 +262,91 @@
<view class="history-panel" @tap.stop>
<view class="history-head">
<view>
<view class="history-title">历史会话</view>
<view class="history-subtitle">按最近更新时间展示</view>
<view class="history-title">历史记录</view>
<view class="history-subtitle">仅展示最近 30 条,按更新时间排序</view>
</view>
<view class="history-close" @tap="closeHistoryPanel">关闭</view>
</view>
<!-- 类型筛选:默认「全部」,前端过滤,不额外请求 -->
<scroll-view scroll-x :show-scrollbar="false" class="history-filter">
<view class="history-filter-track">
<view
v-for="option in historyTypeOptions"
:key="option.value"
class="history-filter-chip"
:class="{ 'history-filter-chip--active': historyTypeFilter === option.value }"
@tap="pickHistoryType(option.value)"
>{{ option.label }}</view>
</view>
</scroll-view>
<scroll-view scroll-y class="history-scroll">
<view v-if="historySections.length">
<view v-for="section in historySections" :key="section.label" class="history-section">
<view class="history-label">{{ section.label }}</view>
<view
v-for="item in section.items"
:key="getHistoryKey(item)"
class="history-item"
:class="{ 'history-item--active': activeHistoryKey === getHistoryKey(item) }"
>
<view class="history-item-main" @tap="selectChat(item)">
<view class="history-item-title">{{ getHistoryTitle(item) }}</view>
<view class="history-item-time">
{{ formatHistoryItemTime(item.createTime) }}
<view v-for="item in section.items" :key="getHistoryKey(item)" class="history-item-wrap">
<view
class="history-item"
:class="{ 'history-item--active': activeHistoryKey === getHistoryKey(item) }"
>
<view class="history-item-main" @tap="selectChat(item)">
<view class="history-item-head">
<text
class="history-type-badge"
:class="`history-type-badge--${getHistoryType(item)}`"
>{{ getHistoryTypeLabel(item) }}</text>
<view class="history-item-title">{{ getHistoryTitle(item) }}</view>
</view>
<view class="history-item-time">
{{ formatHistoryItemTime(item.createTime) }}
</view>
</view>
<view
class="history-delete"
@tap.stop="removeChat(item)"
>
{{ deletingChatId === item.id ? '删除中' : '删除' }}
</view>
</view>
<view
class="history-delete"
@tap.stop="removeChat(item)"
>
{{ deletingChatId === item.id ? '删除中' : '删除' }}
<!--
非聊天记录在面板内展开查看:写作 / 图片可「按原参数再来一次」,
创意 / 活动策划只读(契约里没有可回填的界面参数)。
-->
<view v-if="isHistoryPreview(item)" class="history-preview" @tap.stop>
<view class="history-preview-head">
<text class="history-preview-caption">记录详情</text>
<text class="history-preview-close" @tap.stop="closeHistoryPreview">收起</text>
</view>
<view v-if="historyPreview.loading" class="history-preview-hint">正在加载记录…</view>
<template v-else>
<view
v-for="message in historyPreview.messages"
:key="message.id"
class="history-preview-row"
>
<view class="history-preview-role">{{ isUserMessage(message) ? '你' : 'AI' }}</view>
<view v-if="isUserMessage(message)" class="history-preview-text">{{ message.content }}</view>
<zero-markdown-view
v-else
class="history-preview-text history-preview-markdown"
:markdown="getAssistantDisplayContent(message.content)"
/>
</view>
<view v-if="canReplayHistory(item)" class="history-preview-actions">
<view class="history-preview-replay" @tap.stop="replayHistoryItem(item)">按原参数再来一次</view>
<text class="history-preview-tip">不会自动发送,回填后可修改</text>
</view>
<view v-else class="history-preview-actions">
<text class="history-preview-tip">该记录仅可查看,暂不支持再来一次</text>
</view>
</template>
</view>
</view>
</view>
</view>
<view v-else class="history-empty">暂无历史对话</view>
<view v-else class="history-empty">暂无历史记录</view>
</scroll-view>
</view>
</view>
@@ -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;
+13
View File
@@ -10,6 +10,7 @@
<view class="hero-action" @tap="fillExample('video')">短视频创意</view>
<view class="hero-action" @tap="fillExample('script')">脚本创作</view>
<view class="hero-action" @tap="fillExample('image')">海报排版</view>
<view class="hero-action" @tap="goHistoryPage">历史记录</view>
</view>
</view>
@@ -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'
})
}
}
}
+13
View File
@@ -10,6 +10,7 @@
<view class="hero-action" @tap="fillDemo('wechat')">公众号推文</view>
<view class="hero-action" @tap="fillDemo('notice')">通知公告</view>
<view class="hero-action" @tap="fillDemo('free')">自由创作示例</view>
<view class="hero-action" @tap="goHistoryPage">历史记录</view>
<view class="hero-action hero-action--primary" @tap="goChatPage">进入问答助手</view>
</view>
</view>
@@ -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'
})
}
}
}