本次提交包含以下修改: 1. 新增微信登录相关服务及页面逻辑,实现首次微信绑定手机号流程 2. 优化产品匹配卡片 UI,移除不必要状态显示 3. 调整首页 scroll-view 属性及 banner 背景样式 4. 完善积分规则校验逻辑,增加时区及策略格式验证 5. 更新协议草案文档,补充资料清单及功能说明 6. 修复分析结果路由路径,适配新的匹配结果页面 7. 调整品牌 Logo 安卓端字体对齐样式
240 lines
24 KiB
Plaintext
240 lines
24 KiB
Plaintext
<template>
|
||
<view class="page assistant-page" :style="pageStyle()">
|
||
<AppHeader title="AI融资助手" :customBack="true" @back="back" />
|
||
<view class="chat-shell">
|
||
<view class="chat-toolbar"><text class="toolbar-note">{{ chatId.length > 0 ? '对话已保存' : '您的融资问题,继续聊聊' }}</text><view class="toolbar-actions"><text class="toolbar-link history-entry" @click="showHistory">历史对话</text><text class="toolbar-link" @click="newChat">新对话</text></view></view>
|
||
<scroll-view scroll-y class="chat-scroll" :scroll-into-view="scrollTarget" :style="contentStyle(300, false)">
|
||
<view v-if="active" class="chat-content">
|
||
<view v-if="historyOpen" class="history-panel">
|
||
<view class="history-title"><text class="context-title">历史对话</text><text class="toolbar-link" @click="historyOpen = false">收起</text></view>
|
||
<text v-if="histories.length == 0 && !historyLoading" class="status-text">暂无历史对话</text>
|
||
<view v-for="item in histories" :key="item.id" class="history-item" @click="openHistory(item.id)"><text class="history-label">{{ item.title }}</text><text class="history-date">{{ item.createdAt }} ›</text></view>
|
||
<text v-if="historyError.length > 0" class="error-text">{{ historyError }}</text>
|
||
<text v-if="historyMore || historyError.length > 0" class="toolbar-link history-more" @click="loadHistory">{{ historyLoading ? '正在加载…' : '加载更多对话' }}</text>
|
||
</view>
|
||
<text v-if="loading" class="status-text">正在读取对话上下文…</text>
|
||
<view v-if="pageError.length > 0" class="error-panel"><text class="error-title">暂时无法继续对话</text><text class="error-text">{{ pageError }}</text><text v-if="routeError.length == 0" class="toolbar-link" @click="load">重新加载</text><text class="toolbar-link" @click="back">返回来源页面</text></view>
|
||
<template v-if="context != null">
|
||
<view v-if="context!.assessmentId.length > 0" class="context-card">
|
||
<view class="context-heading" @click="contextOpen = !contextOpen"><text class="context-summary">{{ context!.title }} · {{ context!.products.length > 0 ? context!.products.length.toString() + '项产品' : '报告解读' }}</text><text class="context-toggle">{{ contextOpen ? '收起' : '展开' }}</text></view>
|
||
<template v-if="contextOpen">
|
||
<text v-if="context!.products.length == 0" class="context-note">围绕这份已保存报告解答,不重新评估</text>
|
||
<view v-for="item in context!.products" :key="item.id" class="context-product"><text class="context-product-name">{{ item.name }}</text></view>
|
||
<text class="context-link context-source" @click="source">{{ sourceLabel }}</text>
|
||
</template>
|
||
</view>
|
||
<view class="assistant-intro"><image src="/packages/finance/static/robot.png" mode="aspectFit" class="intro-image" /><view class="intro-bubble"><text class="bubble-text">您好!我是您的 AI 融资助手。
|
||
{{ context!.assessmentId.length > 0 ? '我可以结合本次报告,帮您理解产品条件、匹配原因和需要准备的资料。' : '我可以帮您解答关于银行产品、申请条件、资料准备等问题。' }}</text></view></view>
|
||
<view v-if="messages.length == 0" class="quick-questions"><view v-for="question in context!.questions" :key="question" class="question" @click="chooseQuestion(question)"><text class="question-text">{{ question }}</text></view><text class="question-hint">点击问题后,可编辑并发送</text></view>
|
||
<text v-if="more" class="load-more" @click="older">{{ paging ? '正在加载…' : '加载更早的消息' }}</text>
|
||
<view v-for="item in messages" :key="item.requestId" class="turn">
|
||
<view class="message-row user-row"><view class="user-bubble"><text class="user-text" :selectable="true">{{ item.message }}</text></view></view>
|
||
<view class="message-row"><image class="message-avatar" src="/packages/finance/static/robot.png" mode="aspectFit" /><view class="message-bubble"><text v-if="item.status == 'completed'" class="bubble-text" :selectable="true">{{ item.reply }}</text><text v-else class="status-text">{{ statusLabel(item) }}</text><text v-if="item.canRetry && item.requestId == lastRequestId && !pending" class="retry-link" @click="retryTurn(item)">重新生成回复</text></view></view>
|
||
</view>
|
||
<view v-if="sendError.length > 0" class="send-error"><text class="error-text">{{ sendError }}</text><text v-if="uncertain" class="retry-link" @click="retryTransport">重试本次发送</text></view>
|
||
<text v-if="pollStopped || queryError.length > 0" class="load-more" @click="resumeQuery">{{ queryError.length > 0 ? queryError + ' · 重新查询' : '回复仍在处理中,点击继续查询' }}</text>
|
||
<view id="chat-end" class="chat-end" />
|
||
</template>
|
||
</view>
|
||
</scroll-view>
|
||
<view class="composer" :style="bottomStyle(16)"><view class="input-row"><input v-model="draft" :disabled="context == null || sending || pending || uncertain" :maxlength="800" confirm-type="send" @confirm="send" placeholder="请输入您的问题…" class="chat-input" /><button class="send-button" :disabled="!canSend" @click="send"><image src="/packages/finance/static/send.svg" class="send-icon" mode="aspectFit" /></button></view><text class="composer-note">AI仅供参考,不代表审批结果 · 请勿输入证件或银行卡号</text></view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
<script setup lang="uts">
|
||
import AppHeader from '@/components/AppHeader.uvue'
|
||
import { usePageLayout } from '@/services/layout.uts'
|
||
import { getSession } from '@/services/api.uts'
|
||
import { validRecordId } from '@/packages/finance/services/matches.uts'
|
||
import { parseCompareIds } from '@/packages/finance/services/compare.uts'
|
||
import { AssistantProduct, AssistantContext, AssistantTurn, AssistantHistory, assistantKey, assistantRoute, previewAssistant, loadAssistant, createAssistant, assistantMessages, submitAssistant, assistantHistory } from '@/packages/finance/services/assistant.uts'
|
||
const { pageStyle, contentStyle, bottomStyle } = usePageLayout()
|
||
const revisionId = ref(''); const assessmentId = ref(''); const productIds = ref<string[]>([]); const chatId = ref(''); const from = ref('')
|
||
const context = ref<AssistantContext | null>(null); const messages = ref<AssistantTurn[]>([])
|
||
// 展开只影响展示;切换会话或账户时恢复折叠,不改变已绑定的报告和产品。
|
||
const contextOpen = ref(false)
|
||
const active = ref(false); const loading = ref(false); const sending = ref(false); const paging = ref(false)
|
||
const draft = ref(''); const pageError = ref(''); const routeError = ref(''); const sendError = ref(''); const queryError = ref('')
|
||
const scrollTarget = ref(''); const more = ref(false); const cursor = ref(''); const pollStopped = ref(false)
|
||
const historyOpen = ref(false); const histories = ref<AssistantHistory[]>([]); const historyMore = ref(false); const historyLoading = ref(false); const historyError = ref('')
|
||
const uncertain = ref(false)
|
||
let historyCursor = ''; let createKey = assistantKey(); let bindingSpecified = false; let generation = 0; let scope = ''; let timer = 0; let polling = false; let pollCount = 0
|
||
let sendKey = ''; let sendText = ''; let sendAttempt = 0
|
||
const pending = computed((): boolean => messages.value.some((t: AssistantTurn): boolean => t.status == 'queued' || t.status == 'running'))
|
||
const lastRequestId = computed((): string => messages.value.length > 0 ? messages.value[messages.value.length - 1].requestId : '')
|
||
const canSend = computed((): boolean => context.value != null && !sending.value && !pending.value && !uncertain.value && draft.value.trim().length > 0)
|
||
const sourceLabel = computed((): string => from.value == 'next-step' ? '返回下一步行动 ›' : (from.value == 'product' && productIds.value.length == 1 ? '返回产品 ›' : (from.value == 'compare' ? '返回对比 ›' : '查看报告 ›')))
|
||
const statusLabel = (item: AssistantTurn): string => item.status == 'queued' ? '问题已收到,等待AI回复…' : (item.status == 'running' ? '正在整理建议…' : (item.status == 'local' ? (sending.value ? '正在确认发送结果…' : '尚未确认发送结果,请查看下方提示。') : '回复暂未完成。' + (item.canRetry ? '您可以重新尝试。' : '已达到本条消息重试上限。')))
|
||
const source = () => {
|
||
if (assessmentId.value.length == 0) { uni.reLaunch({ url: '/pages/home/home' }); return }
|
||
let url = '/packages/finance/report/report?assessmentId=' + assessmentId.value + (revisionId.value.length > 0 ? '&revisionId=' + revisionId.value : '')
|
||
if (from.value == 'product' && productIds.value.length == 1) url = '/packages/finance/product/product?assessmentId=' + assessmentId.value + (revisionId.value.length > 0 ? '&revisionId=' + revisionId.value : '') + '&productId=' + productIds.value[0] + '&tab=explanation'
|
||
if (from.value == 'compare') url = '/packages/finance/compare/compare?assessmentId=' + assessmentId.value + (revisionId.value.length > 0 ? '&revisionId=' + revisionId.value : '') + '&productIds=' + productIds.value.join(',')
|
||
if (from.value == 'next-step') url = '/packages/finance/next-step/next-step?assessmentId=' + assessmentId.value + (revisionId.value.length > 0 ? '&revisionId=' + revisionId.value : '') + (productIds.value.length == 1 ? '&productId=' + productIds.value[0] + '§ion=prepare' : '§ion=actions')
|
||
uni.redirectTo({ url })
|
||
}
|
||
const back = () => { if (getCurrentPages().length > 1) uni.navigateBack(); else source() }
|
||
const login = () => { uni.redirectTo({ url: '/packages/account/login/login?resumeAssistant=1&assessmentId=' + assessmentId.value + (revisionId.value.length > 0 ? '&revisionId=' + revisionId.value : '') + '&productIds=' + productIds.value.join(',') + '&chatId=' + chatId.value + '&from=' + from.value }) }
|
||
const stopTimer = () => { if (timer != 0) { clearInterval(timer); timer = 0 } }
|
||
const clearContent = () => { context.value = null; contextOpen.value = false; messages.value = []; draft.value = ''; sendText = ''; sendKey = ''; uncertain.value = false; histories.value = []; historyCursor = ''; historyOpen.value = false; sendError.value = ''; queryError.value = '' }
|
||
// 请求代次与登录Token双重校验;离页、退出、换账号后旧响应不得回填个人对话。
|
||
const current = (ticket: number, token: string): boolean => {
|
||
if (!active.value || ticket != generation) return false
|
||
if (getSession()?.getString('token') == token) return true
|
||
generation++; clearContent(); stopTimer(); loading.value = false; sending.value = false
|
||
pageError.value = '登录状态已变化,请重新登录后读取本人对话。'
|
||
if (getSession() == null) login()
|
||
return false
|
||
}
|
||
const scrollEnd = async () => { scrollTarget.value = ''; await nextTick(); if (active.value) scrollTarget.value = 'chat-end' }
|
||
const merge = (items: AssistantTurn[]) => { items.forEach((item: AssistantTurn) => { const index = messages.value.findIndex((old: AssistantTurn): boolean => old.requestId == item.requestId); if (index >= 0) messages.value[index] = item; else messages.value.push(item) }) }
|
||
const refreshMessages = async () => {
|
||
if (chatId.value.length == 0 || polling || loading.value) return
|
||
const ticket = generation; const token = scope; polling = true; queryError.value = ''
|
||
try { const page = await assistantMessages(chatId.value, ''); if (!current(ticket, token)) return; merge(page.items); pollStopped.value = false; if (pending.value) pollCount++; else { pollCount = 0; scrollEnd() } }
|
||
catch (error) { if (current(ticket, token)) queryError.value = error instanceof Error ? error.message : '状态读取失败' }
|
||
finally { if (ticket == generation) polling = false }
|
||
}
|
||
const resumeQuery = () => { pollCount = 0; refreshMessages() }
|
||
const beginTimer = () => {
|
||
stopTimer(); timer = setInterval(() => {
|
||
if (!current(generation, scope)) return
|
||
if (pending.value && !polling && !sending.value && queryError.value.length == 0) { if (pollCount >= 60) { pollStopped.value = true; return }; refreshMessages() }
|
||
}, 3000)
|
||
}
|
||
const load = async () => {
|
||
const ticket = ++generation; clearContent(); stopTimer(); polling = false; more.value = false; cursor.value = ''; pageError.value = ''; loading.value = false
|
||
if (routeError.value.length > 0) { pageError.value = routeError.value; return }
|
||
const session = getSession()
|
||
if (session == null) { pageError.value = '请先登录后使用AI助手。'; login(); return }
|
||
scope = session.getString('token') ?? ''; const token = scope; loading.value = true
|
||
try {
|
||
if (chatId.value.length > 0) {
|
||
const chat = await loadAssistant(chatId.value, assessmentId.value, productIds.value, bindingSpecified, revisionId.value)
|
||
if (!current(ticket, token)) return
|
||
const page = await assistantMessages(chatId.value, '')
|
||
if (!current(ticket, token)) return
|
||
context.value = chat.context; revisionId.value = chat.context.revisionId; assessmentId.value = chat.context.assessmentId; productIds.value = chat.context.productIds
|
||
messages.value = page.items; more.value = page.more; cursor.value = page.cursor; scrollEnd()
|
||
} else { const data = await previewAssistant(assessmentId.value, productIds.value, revisionId.value); if (!current(ticket, token)) return; context.value = data; revisionId.value = data.revisionId }
|
||
pollCount = 0; pollStopped.value = false; beginTimer()
|
||
} catch (error) { if (current(ticket, token)) { context.value = null; messages.value = []; pageError.value = error instanceof Error ? error.message : '对话读取失败' } }
|
||
finally { if (ticket == generation) loading.value = false }
|
||
}
|
||
const older = async () => {
|
||
if (paging.value || !more.value) return
|
||
const ticket = generation; const token = scope; paging.value = true; queryError.value = ''
|
||
try { const page = await assistantMessages(chatId.value, cursor.value); if (!current(ticket, token)) return; messages.value = page.items.filter((t: AssistantTurn): boolean => !messages.value.some((old: AssistantTurn): boolean => old.requestId == t.requestId)).concat(messages.value); more.value = page.more; cursor.value = page.cursor }
|
||
catch (error) { if (current(ticket, token)) queryError.value = error instanceof Error ? error.message : '历史消息读取失败' }
|
||
finally { if (ticket == generation) paging.value = false }
|
||
}
|
||
const updateUrl = () => {
|
||
// H5仅更新地址中的ID,刷新后重新鉴权;不把消息、Token或报告写入URL/本地缓存。
|
||
// #ifdef WEB
|
||
history.replaceState(history.state, '', location.pathname + location.search + '#' + assistantRoute(assessmentId.value, productIds.value, from.value, chatId.value, revisionId.value))
|
||
// #endif
|
||
}
|
||
const transmit = async () => {
|
||
if (sending.value || context.value == null) return
|
||
// 发请求前核对账户,避免轮询尚未发现切换时将旧草稿带新Token发送。
|
||
if (!current(generation, scope)) return
|
||
const ticket = generation; const token = scope; sending.value = true; sendError.value = ''; uncertain.value = true
|
||
try {
|
||
if (chatId.value.length == 0) { const chat = await createAssistant(assessmentId.value, productIds.value, createKey, revisionId.value); if (!current(ticket, token)) return; chatId.value = chat.id; updateUrl() }
|
||
const item = await submitAssistant(chatId.value, sendKey, sendText, sendAttempt)
|
||
if (!current(ticket, token)) return
|
||
merge([item]); uncertain.value = false; draft.value = ''; pollCount = 0; pollStopped.value = false; scrollEnd()
|
||
} catch (error) { if (current(ticket, token)) sendError.value = error instanceof Error ? error.message : '发送结果未确认,请重试本次发送' }
|
||
finally { if (ticket == generation) sending.value = false }
|
||
}
|
||
const send = () => { if (!canSend.value) return; sendKey = assistantKey(); sendText = draft.value.trim(); sendAttempt = 0; merge([{ id: '', requestId: sendKey, message: sendText, reply: '', status: 'local', attempt: 0, canRetry: false }]); scrollEnd(); transmit() }
|
||
const retryTransport = () => { if (!sending.value && uncertain.value) transmit() }
|
||
const retryTurn = (item: AssistantTurn) => { if (sending.value || pending.value || uncertain.value || !item.canRetry || item.requestId != lastRequestId.value) return; sendKey = item.requestId; sendText = item.message; sendAttempt = item.attempt + 1; transmit() }
|
||
const chooseQuestion = (text: string) => { if (!sending.value && !pending.value && !uncertain.value) draft.value = text }
|
||
const loadHistory = async () => {
|
||
if (historyLoading.value) return
|
||
const ticket = generation; const token = scope; historyLoading.value = true; historyError.value = ''
|
||
try { const page = await assistantHistory(historyCursor); if (!current(ticket, token)) return; histories.value = histories.value.concat(page.items); historyMore.value = page.more; historyCursor = page.cursor }
|
||
catch (error) { if (current(ticket, token)) historyError.value = error instanceof Error ? error.message : '历史对话读取失败' }
|
||
finally { if (ticket == generation) historyLoading.value = false }
|
||
}
|
||
const showHistory = () => { historyOpen.value = !historyOpen.value; if (historyOpen.value && histories.value.length == 0) loadHistory() }
|
||
const openHistory = (id: string) => { uni.redirectTo({ url: assistantRoute('', [], 'archive', id) }) }
|
||
const newChat = () => { uni.redirectTo({ url: assistantRoute(assessmentId.value, productIds.value, from.value, '', revisionId.value) }) }
|
||
onLoad((options) => {
|
||
revisionId.value = options?.['revisionId'] ?? '';
|
||
if (revisionId.value.length > 0 && !validRecordId(revisionId.value)) routeError.value = '版本编号无效,请返回重新选择。'
|
||
assessmentId.value = options?.['assessmentId'] ?? ''; chatId.value = options?.['chatId'] ?? ''; from.value = options?.['from'] ?? ''; bindingSpecified = options?.['assessmentId'] != null || options?.['productIds'] != null
|
||
try {
|
||
productIds.value = parseCompareIds(options?.['productIds'] ?? '')
|
||
if ((assessmentId.value.length > 0 && !validRecordId(assessmentId.value)) || (chatId.value.length > 0 && !validRecordId(chatId.value)) || (productIds.value.length > 0 && assessmentId.value.length == 0)) throw new Error('会话或评估参数无效,请返回重新选择。')
|
||
if (!['','matches','product','compare','report','next-step','archive'].includes(from.value)) throw new Error('来源页面无效。')
|
||
} catch (error) { routeError.value = error instanceof Error ? error.message : '参数无效' }
|
||
})
|
||
onShow(() => { active.value = true; load() })
|
||
onHide(() => { active.value = false; generation++; stopTimer(); clearContent(); loading.value = false; sending.value = false; paging.value = false; historyLoading.value = false })
|
||
onUnload(() => { active.value = false; generation++; stopTimer(); clearContent() })
|
||
</script>
|
||
<style>
|
||
.context-summary { flex: 1; font-size: 24rpx; line-height: 36rpx; color: #45617f; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||
.context-toggle { flex-shrink: 0; color: #527ba8; font-size: 22rpx; margin-left: 16rpx; line-height: 36rpx; }
|
||
.context-source { margin-top: 16rpx; }
|
||
.assistant-page { background-color: #fcfdff; }
|
||
.chat-shell { flex: 1; }
|
||
.chat-toolbar { padding: 8rpx 30rpx 18rpx; min-height: 60rpx; flex-direction: row; align-items: center; justify-content: space-between; }
|
||
.toolbar-note { color: #95a1b3; font-size: 21rpx; }
|
||
.toolbar-actions { flex-direction: row; }
|
||
.toolbar-link { color: #2672d9; font-size: 23rpx; line-height: 40rpx; margin-left: 22rpx; }
|
||
.history-entry { color: #7b899c; font-size: 21rpx; }
|
||
.chat-content { padding: 14rpx 30rpx 36rpx; }
|
||
.context-card { padding: 20rpx 22rpx; margin-bottom: 30rpx; border-radius: 14rpx; background-color: #f0f6ff; border: 1rpx solid #e0ebfb; }
|
||
.context-heading, .context-product, .history-title { flex-direction: row; justify-content: space-between; align-items: center; }
|
||
.context-title { color: #315578; font-size: 24rpx; font-weight: 600; }
|
||
.context-link { color: #2672d9; font-size: 21rpx; }
|
||
.context-note { color: #7b8da7; font-size: 21rpx; line-height: 34rpx; margin-top: 6rpx; }
|
||
.context-product { margin-top: 12rpx; align-items: flex-start; }
|
||
.context-product-name { flex: 1; color: #506580; font-size: 23rpx; line-height: 34rpx; margin-right: 14rpx; }
|
||
.product-state { color: #bc8151; font-size: 21rpx; line-height: 34rpx; flex-shrink: 0; }
|
||
.good-state { color: #289875; }
|
||
.assistant-intro { flex-direction: row; align-items: flex-start; margin-top: 10rpx; }
|
||
.intro-image { width: 120rpx; height: 138rpx; margin: 6rpx 12rpx 0 -6rpx; flex-shrink: 0; }
|
||
.intro-bubble { flex: 1; padding: 22rpx 24rpx; border-radius: 8rpx 22rpx 22rpx 22rpx; background-color: #f3f8ff; }
|
||
.bubble-text { font-size: 26rpx; line-height: 44rpx; color: #4e5e75; }
|
||
.quick-questions { margin: 34rpx 0 30rpx 26rpx; align-items: flex-start; }
|
||
.question { padding: 12rpx 23rpx; margin-bottom: 18rpx; max-width: 630rpx; border-radius: 32rpx; background-color: #edf7ff; }
|
||
.question-text { color: #2674bd; font-size: 25rpx; line-height: 38rpx; }
|
||
.question-hint { color: #a1abba; font-size: 20rpx; margin: 0 0 0 20rpx; }
|
||
.message-row { flex-direction: row; align-items: flex-start; margin-top: 24rpx; }
|
||
.user-row { justify-content: flex-end; }
|
||
.message-avatar { width: 62rpx; height: 70rpx; margin-right: 12rpx; flex-shrink: 0; }
|
||
.message-bubble { max-width: 560rpx; flex-shrink: 1; padding: 20rpx 24rpx; background-color: #f1f6fd; border-radius: 6rpx 22rpx 22rpx 22rpx; }
|
||
.user-bubble { max-width: 570rpx; padding: 20rpx 24rpx; border-radius: 22rpx 6rpx 22rpx 22rpx; background-color: #1673ef; }
|
||
.user-text { color: #ffffff; font-size: 26rpx; line-height: 42rpx; }
|
||
.status-text { font-size: 24rpx; color: #8796aa; line-height: 40rpx; }
|
||
.composer { position: fixed; bottom: 0; left: 0; right: 0; background-color: #fcfdff; padding: 16rpx 30rpx; border-top: 1rpx solid #eef1f6; }
|
||
.input-row { flex-direction: row; align-items: center; }
|
||
.chat-input { flex: 1; height: 78rpx; color: #344259; font-size: 26rpx; padding: 0 12rpx; }
|
||
.send-button { width: 72rpx; height: 72rpx; border-radius: 36rpx; background-color: #1673ef; padding: 0; margin-left: 16rpx; align-items: center; justify-content: center; }
|
||
.send-icon { width: 38rpx; height: 38rpx; }
|
||
.composer-note { color: #a0aaba; font-size: 19rpx; text-align: center; margin-top: 10rpx; line-height: 28rpx; }
|
||
.error-panel, .history-panel { background-color: #f4f7fc; border-radius: 16rpx; padding: 24rpx; margin-bottom: 28rpx; }
|
||
.error-title { color: #5c6680; font-size: 28rpx; margin-bottom: 14rpx; }
|
||
.error-text { color: #bb6571; font-size: 23rpx; line-height: 38rpx; }
|
||
.retry-link { color: #2672d9; font-size: 24rpx; line-height: 42rpx; margin-top: 10rpx; }
|
||
.send-error { margin: 24rpx 0 0 74rpx; }
|
||
.load-more { text-align: center; color: #2672d9; font-size: 23rpx; line-height: 40rpx; padding: 18rpx; }
|
||
.history-item { flex-direction: row; align-items: center; padding: 20rpx 0; border-bottom: 1rpx solid #e4ecf6; }
|
||
.history-label { flex: 1; color: #4e5e75; font-size: 24rpx; }
|
||
.history-date { font-size: 20rpx; color: #93a0b2; }
|
||
.history-more { text-align: center; margin: 14rpx 0 0; }
|
||
.chat-end { height: 30rpx; }
|
||
/* #ifndef APP */
|
||
.send-button { display: flex; }
|
||
.send-button[disabled] { background-color: #6dacf8; color: #ffffff; }
|
||
.bubble-text, .user-text { white-space: pre-wrap; overflow-wrap: anywhere; }
|
||
/* #endif */
|
||
/* #ifdef WEB */
|
||
@media (min-width: 800px) { .chat-shell { max-width: 760px; width: 100%; margin: 0 auto; } .composer { max-width: 760px; margin: 0 auto; } }
|
||
/* #endif */
|
||
</style>
|