2026-05-07 11:50:10 +08:00
|
|
|
|
<template>
|
2026-05-05 18:22:47 +08:00
|
|
|
|
<section class="workbench">
|
|
|
|
|
|
<PanelHead
|
|
|
|
|
|
v-if="showHeader"
|
|
|
|
|
|
eyebrow="Personal Workspace"
|
|
|
|
|
|
title="个人工作台"
|
|
|
|
|
|
note="把今天要处理的待办、报销进度和制度更新集中到一个入口。"
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<article class="panel assistant-hero">
|
|
|
|
|
|
<div class="assistant-visual" aria-hidden="true">
|
2026-05-07 11:50:10 +08:00
|
|
|
|
<span class="assistant-glow"></span>
|
|
|
|
|
|
<img class="assistant-image" :src="robotAssistant" alt="" />
|
2026-05-05 18:22:47 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="assistant-copy">
|
2026-05-20 21:00:47 +08:00
|
|
|
|
<h3>嗨,{{ assistantGreetingName }},描述费用或上传票据,AI 直接帮你判断怎么报</h3>
|
2026-05-05 18:22:47 +08:00
|
|
|
|
<p>自动识别报销类别、核对附件完整性,并生成可继续提交的报销草稿。</p>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="assistant-input">
|
2026-05-12 01:27:49 +00:00
|
|
|
|
<input
|
|
|
|
|
|
ref="fileInputRef"
|
|
|
|
|
|
class="assistant-file-input"
|
|
|
|
|
|
type="file"
|
|
|
|
|
|
multiple
|
|
|
|
|
|
accept=".pdf,.jpg,.jpeg,.png,.webp,.doc,.docx,.xls,.xlsx"
|
|
|
|
|
|
@change="handleWorkbenchFilesChange"
|
|
|
|
|
|
/>
|
2026-05-05 23:47:20 +08:00
|
|
|
|
<textarea
|
|
|
|
|
|
v-model="assistantDraft"
|
2026-05-06 11:00:38 +08:00
|
|
|
|
rows="1"
|
2026-05-05 23:47:20 +08:00
|
|
|
|
placeholder="例如:我昨天请客户吃饭花了 860 元,还打车去了客户公司"
|
2026-05-12 01:27:49 +00:00
|
|
|
|
@keydown.enter.prevent="handleWorkbenchEnter"
|
2026-05-05 23:47:20 +08:00
|
|
|
|
/>
|
2026-05-05 18:22:47 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-05-12 06:39:26 +00:00
|
|
|
|
<div v-if="selectedFiles.length" class="assistant-file-strip">
|
|
|
|
|
|
<span class="assistant-file-note">已带入 {{ selectedFiles.length }} 份附件</span>
|
|
|
|
|
|
<span v-for="file in selectedFiles" :key="file.name" class="assistant-file-chip">{{ file.name }}</span>
|
|
|
|
|
|
<button type="button" class="assistant-file-clear" @click="clearSelectedFiles">清空</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-05-05 18:22:47 +08:00
|
|
|
|
<div class="assistant-tools">
|
2026-05-13 13:12:28 +00:00
|
|
|
|
<button type="button" class="ghost-action" :disabled="Boolean(pendingAction)" @click="triggerFileUpload">
|
2026-05-05 18:22:47 +08:00
|
|
|
|
<i class="mdi mdi-upload-outline"></i>
|
|
|
|
|
|
<span>上传票据</span>
|
|
|
|
|
|
</button>
|
2026-05-12 06:39:26 +00:00
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
class="hero-action"
|
2026-05-13 13:12:28 +00:00
|
|
|
|
:disabled="Boolean(pendingAction)"
|
|
|
|
|
|
@click="handleExpenseConversationAction"
|
2026-05-12 06:39:26 +00:00
|
|
|
|
>
|
2026-05-13 13:12:28 +00:00
|
|
|
|
<i :class="pendingAction === 'expense' ? 'mdi mdi-loading mdi-spin' : expenseActionIcon"></i>
|
|
|
|
|
|
<span>{{ pendingAction === 'expense' ? '处理中...' : expenseActionLabel }}</span>
|
2026-05-07 11:50:10 +08:00
|
|
|
|
</button>
|
2026-05-05 18:22:47 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</article>
|
|
|
|
|
|
|
2026-05-15 06:56:51 +00:00
|
|
|
|
<div class="workbench-grid">
|
2026-05-05 18:22:47 +08:00
|
|
|
|
<article class="panel list-panel">
|
|
|
|
|
|
<div class="section-head">
|
2026-05-05 22:35:38 +08:00
|
|
|
|
<div class="title-with-badge">
|
2026-05-14 15:43:28 +00:00
|
|
|
|
<h3>报销待办</h3>
|
2026-05-05 22:35:38 +08:00
|
|
|
|
<span class="alert-badge">{{ todoAlertCount }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<button type="button" class="link-action">查看全部 <i class="mdi mdi-chevron-right"></i></button>
|
2026-05-05 18:22:47 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="list-body">
|
|
|
|
|
|
<div v-for="item in todoItems" :key="item.title" class="todo-row">
|
2026-05-20 21:00:47 +08:00
|
|
|
|
<WorkbenchListIcon
|
|
|
|
|
|
:icon-key="item.iconKey"
|
|
|
|
|
|
:color="item.color"
|
|
|
|
|
|
:accent="item.accent"
|
|
|
|
|
|
/>
|
2026-05-05 18:22:47 +08:00
|
|
|
|
|
|
|
|
|
|
<div class="todo-copy">
|
|
|
|
|
|
<strong>{{ item.title }}</strong>
|
2026-05-05 22:35:38 +08:00
|
|
|
|
<p class="todo-advice">
|
|
|
|
|
|
<span class="todo-advice-label">{{ item.tipLabel }}</span>
|
|
|
|
|
|
<span class="todo-advice-text">{{ item.suggestion }}</span>
|
|
|
|
|
|
</p>
|
2026-05-05 18:22:47 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<button type="button" class="row-action" @click="emit('openAssistant')">{{ item.action }}</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</article>
|
|
|
|
|
|
|
|
|
|
|
|
<article class="panel list-panel">
|
|
|
|
|
|
<div class="section-head">
|
2026-05-05 22:35:38 +08:00
|
|
|
|
<div class="title-with-badge">
|
|
|
|
|
|
<h3>报销进度</h3>
|
|
|
|
|
|
<span class="alert-badge">{{ progressAlertCount }}</span>
|
|
|
|
|
|
</div>
|
2026-05-05 18:22:47 +08:00
|
|
|
|
<button type="button" class="link-action">查看全部 <i class="mdi mdi-chevron-right"></i></button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="list-body">
|
|
|
|
|
|
<div v-for="item in progressItems" :key="item.id" class="progress-row">
|
2026-05-20 21:00:47 +08:00
|
|
|
|
<WorkbenchListIcon
|
|
|
|
|
|
:icon-key="item.iconKey"
|
|
|
|
|
|
:color="item.color"
|
|
|
|
|
|
:accent="item.accent"
|
|
|
|
|
|
/>
|
2026-05-05 18:22:47 +08:00
|
|
|
|
|
|
|
|
|
|
<div class="todo-copy progress-copy">
|
2026-05-05 22:35:38 +08:00
|
|
|
|
<strong>{{ item.title }}</strong>
|
2026-05-05 18:22:47 +08:00
|
|
|
|
<p>提交时间:{{ item.date }}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-05-05 22:35:38 +08:00
|
|
|
|
<strong class="progress-amount">{{ item.amount }}</strong>
|
2026-05-05 18:22:47 +08:00
|
|
|
|
<span class="progress-status" :class="item.tone">{{ item.status }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</article>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<article class="panel policy-panel">
|
|
|
|
|
|
<div class="section-head">
|
|
|
|
|
|
<h3>最新报销制度</h3>
|
|
|
|
|
|
<button type="button" class="link-action">查看全部 <i class="mdi mdi-chevron-right"></i></button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="policy-table">
|
|
|
|
|
|
<div class="policy-head policy-row">
|
2026-05-05 22:35:38 +08:00
|
|
|
|
<span class="policy-title-cell">制度名称</span>
|
|
|
|
|
|
<span class="policy-summary-cell">摘要</span>
|
|
|
|
|
|
<span class="policy-date-cell">发布日期</span>
|
2026-05-05 18:22:47 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div v-for="item in policyItems" :key="item.name" class="policy-row">
|
2026-05-05 22:35:38 +08:00
|
|
|
|
<strong class="policy-title-cell">{{ item.name }}</strong>
|
|
|
|
|
|
<span class="policy-summary-cell">{{ item.summary }}</span>
|
|
|
|
|
|
<span class="policy-date-cell">{{ item.date }}</span>
|
2026-05-05 18:22:47 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</article>
|
|
|
|
|
|
</section>
|
2026-05-12 06:39:26 +00:00
|
|
|
|
|
2026-05-05 18:22:47 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
2026-05-13 13:12:28 +00:00
|
|
|
|
import { computed, onMounted, ref, watch } from 'vue'
|
2026-05-05 18:22:47 +08:00
|
|
|
|
import PanelHead from '../shared/PanelHead.vue'
|
2026-05-20 21:00:47 +08:00
|
|
|
|
import WorkbenchListIcon from '../shared/WorkbenchListIcon.vue'
|
2026-05-07 11:50:10 +08:00
|
|
|
|
import robotAssistant from '../../assets/robot-helper.png'
|
2026-05-12 06:39:26 +00:00
|
|
|
|
import { useSystemState } from '../../composables/useSystemState.js'
|
2026-05-13 03:27:30 +00:00
|
|
|
|
import { useToast } from '../../composables/useToast.js'
|
2026-05-12 06:39:26 +00:00
|
|
|
|
import { clearUserConversations, fetchLatestConversation } from '../../services/orchestrator.js'
|
2026-05-05 18:22:47 +08:00
|
|
|
|
|
2026-05-13 13:12:28 +00:00
|
|
|
|
const props = defineProps({
|
|
|
|
|
|
showHeader: { type: Boolean, default: true },
|
|
|
|
|
|
assistantModalOpen: { type: Boolean, default: false }
|
2026-05-05 18:22:47 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits(['openAssistant'])
|
2026-05-12 06:39:26 +00:00
|
|
|
|
const { currentUser } = useSystemState()
|
2026-05-13 03:27:30 +00:00
|
|
|
|
const { toast } = useToast()
|
2026-05-05 23:47:20 +08:00
|
|
|
|
const assistantDraft = ref('')
|
2026-05-12 01:27:49 +00:00
|
|
|
|
const fileInputRef = ref(null)
|
2026-05-12 06:39:26 +00:00
|
|
|
|
const selectedFiles = ref([])
|
|
|
|
|
|
const pendingAction = ref('')
|
2026-05-13 13:12:28 +00:00
|
|
|
|
const latestExpenseConversation = ref(null)
|
|
|
|
|
|
const MAX_ATTACHMENTS = 10
|
|
|
|
|
|
const SESSION_TYPE_EXPENSE = 'expense'
|
|
|
|
|
|
const SESSION_TYPE_KNOWLEDGE = 'knowledge'
|
|
|
|
|
|
|
|
|
|
|
|
const hasExpenseConversation = computed(() => Boolean(latestExpenseConversation.value?.conversation_id || latestExpenseConversation.value?.conversationId))
|
|
|
|
|
|
const expenseActionLabel = computed(() => (hasExpenseConversation.value ? '继续报销' : '新建报销'))
|
|
|
|
|
|
const expenseActionIcon = computed(() => (hasExpenseConversation.value ? 'mdi mdi-history' : 'mdi mdi-magnify-scan'))
|
2026-05-20 21:00:47 +08:00
|
|
|
|
const assistantGreetingName = computed(() => {
|
|
|
|
|
|
const user = currentUser.value || {}
|
|
|
|
|
|
return String(user.name || user.username || '同事').trim() || '同事'
|
|
|
|
|
|
})
|
2026-05-13 13:12:28 +00:00
|
|
|
|
|
|
|
|
|
|
function buildSelectedFileKey(file) {
|
|
|
|
|
|
return [file?.name, file?.size, file?.lastModified, file?.type].join('__')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function mergeSelectedFiles(existingFiles, incomingFiles) {
|
|
|
|
|
|
const nextFiles = []
|
|
|
|
|
|
const seen = new Set()
|
|
|
|
|
|
|
|
|
|
|
|
for (const file of existingFiles) {
|
|
|
|
|
|
const key = buildSelectedFileKey(file)
|
|
|
|
|
|
if (seen.has(key)) continue
|
|
|
|
|
|
seen.add(key)
|
|
|
|
|
|
nextFiles.push(file)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let overflowCount = 0
|
|
|
|
|
|
|
|
|
|
|
|
for (const file of incomingFiles) {
|
|
|
|
|
|
const key = buildSelectedFileKey(file)
|
|
|
|
|
|
if (seen.has(key)) continue
|
|
|
|
|
|
if (nextFiles.length >= MAX_ATTACHMENTS) {
|
|
|
|
|
|
overflowCount += 1
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
seen.add(key)
|
|
|
|
|
|
nextFiles.push(file)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
files: nextFiles,
|
|
|
|
|
|
overflowCount
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-12 06:39:26 +00:00
|
|
|
|
|
|
|
|
|
|
function resolveCurrentUserId() {
|
|
|
|
|
|
const user = currentUser.value || {}
|
|
|
|
|
|
return String(user.username || user.name || 'anonymous').trim() || 'anonymous'
|
|
|
|
|
|
}
|
2026-05-05 23:47:20 +08:00
|
|
|
|
|
2026-05-12 06:39:26 +00:00
|
|
|
|
function buildAssistantPayload() {
|
|
|
|
|
|
return {
|
2026-05-05 23:47:20 +08:00
|
|
|
|
prompt: assistantDraft.value.trim(),
|
2026-05-12 06:39:26 +00:00
|
|
|
|
source: 'workbench',
|
|
|
|
|
|
files: Array.from(selectedFiles.value)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function clearSelectedFiles() {
|
|
|
|
|
|
selectedFiles.value = []
|
|
|
|
|
|
if (fileInputRef.value) {
|
|
|
|
|
|
fileInputRef.value.value = ''
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resetWorkbenchDraft() {
|
|
|
|
|
|
assistantDraft.value = ''
|
|
|
|
|
|
clearSelectedFiles()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function emitAssistant(payload) {
|
|
|
|
|
|
emit('openAssistant', payload)
|
|
|
|
|
|
resetWorkbenchDraft()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function loadLatestConversation() {
|
2026-05-14 15:43:28 +00:00
|
|
|
|
const payload = await fetchLatestConversation(resolveCurrentUserId(), SESSION_TYPE_EXPENSE, {
|
|
|
|
|
|
preferRecoverable: true
|
|
|
|
|
|
})
|
2026-05-12 06:39:26 +00:00
|
|
|
|
return payload?.found ? payload.conversation || null : null
|
2026-05-05 23:47:20 +08:00
|
|
|
|
}
|
2026-05-05 18:22:47 +08:00
|
|
|
|
|
2026-05-12 01:27:49 +00:00
|
|
|
|
function handleWorkbenchEnter(event) {
|
|
|
|
|
|
if (event.isComposing) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-13 13:12:28 +00:00
|
|
|
|
handleExpenseConversationAction()
|
2026-05-12 01:27:49 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function triggerFileUpload() {
|
|
|
|
|
|
fileInputRef.value?.click()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function handleWorkbenchFilesChange(event) {
|
2026-05-13 13:12:28 +00:00
|
|
|
|
const mergeResult = mergeSelectedFiles(selectedFiles.value, Array.from(event.target.files ?? []))
|
|
|
|
|
|
selectedFiles.value = mergeResult.files
|
|
|
|
|
|
if (mergeResult.overflowCount > 0) {
|
|
|
|
|
|
toast(`一次最多上传 ${MAX_ATTACHMENTS} 份附件,已保留前 ${MAX_ATTACHMENTS} 份。`)
|
2026-05-12 06:39:26 +00:00
|
|
|
|
}
|
2026-05-13 13:12:28 +00:00
|
|
|
|
if (fileInputRef.value) {
|
|
|
|
|
|
fileInputRef.value.value = ''
|
2026-05-12 06:39:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-12 01:27:49 +00:00
|
|
|
|
|
2026-05-13 13:12:28 +00:00
|
|
|
|
async function refreshLatestExpenseConversation() {
|
2026-05-12 06:39:26 +00:00
|
|
|
|
try {
|
2026-05-13 13:12:28 +00:00
|
|
|
|
latestExpenseConversation.value = await loadLatestConversation()
|
2026-05-12 06:39:26 +00:00
|
|
|
|
} catch (error) {
|
2026-05-13 13:12:28 +00:00
|
|
|
|
console.warn('Failed to refresh latest expense conversation:', error)
|
|
|
|
|
|
latestExpenseConversation.value = null
|
2026-05-12 06:39:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-13 13:12:28 +00:00
|
|
|
|
async function clearKnowledgeHistoryBeforeExpense() {
|
|
|
|
|
|
await clearUserConversations(resolveCurrentUserId(), SESSION_TYPE_KNOWLEDGE)
|
2026-05-12 06:39:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-13 13:12:28 +00:00
|
|
|
|
async function handleExpenseConversationAction() {
|
|
|
|
|
|
if (pendingAction.value) {
|
2026-05-12 06:39:26 +00:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-13 13:12:28 +00:00
|
|
|
|
const nextPayload = buildAssistantPayload()
|
2026-05-19 17:24:13 +00:00
|
|
|
|
const shouldOpenImmediately = Boolean(nextPayload.prompt || nextPayload.files.length)
|
|
|
|
|
|
|
|
|
|
|
|
if (shouldOpenImmediately) {
|
|
|
|
|
|
emitAssistant({
|
|
|
|
|
|
...nextPayload,
|
|
|
|
|
|
conversation: null
|
|
|
|
|
|
})
|
|
|
|
|
|
void clearKnowledgeHistoryBeforeExpense().catch((error) => {
|
|
|
|
|
|
console.warn('Failed to clear knowledge history before expense:', error)
|
|
|
|
|
|
})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pendingAction.value = 'expense'
|
2026-05-12 06:39:26 +00:00
|
|
|
|
|
|
|
|
|
|
try {
|
2026-05-13 13:12:28 +00:00
|
|
|
|
await clearKnowledgeHistoryBeforeExpense()
|
|
|
|
|
|
const conversation = await loadLatestConversation()
|
|
|
|
|
|
latestExpenseConversation.value = conversation
|
|
|
|
|
|
emitAssistant({
|
|
|
|
|
|
...nextPayload,
|
|
|
|
|
|
conversation
|
|
|
|
|
|
})
|
2026-05-12 06:39:26 +00:00
|
|
|
|
} catch (error) {
|
2026-05-13 13:12:28 +00:00
|
|
|
|
console.warn('Failed to open expense conversation:', error)
|
|
|
|
|
|
toast(error?.message || '打开报销会话失败,请稍后重试。')
|
2026-05-12 06:39:26 +00:00
|
|
|
|
} finally {
|
2026-05-13 13:12:28 +00:00
|
|
|
|
pendingAction.value = ''
|
2026-05-12 06:39:26 +00:00
|
|
|
|
}
|
2026-05-12 01:27:49 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 18:22:47 +08:00
|
|
|
|
const todoItems = [
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '业务招待报销建议补参与人员',
|
2026-05-05 22:35:38 +08:00
|
|
|
|
tipLabel: 'AI 建议',
|
|
|
|
|
|
suggestion: '补充客户单位、客户人数、我方陪同人员',
|
2026-05-05 18:22:47 +08:00
|
|
|
|
action: '去补充',
|
2026-05-20 21:00:47 +08:00
|
|
|
|
iconKey: 'hospitality',
|
|
|
|
|
|
color: '#0d9668',
|
|
|
|
|
|
accent: '#6ee7b7'
|
2026-05-05 18:22:47 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '差旅报销单待提交',
|
2026-05-05 22:35:38 +08:00
|
|
|
|
tipLabel: 'AI 建议',
|
|
|
|
|
|
suggestion: '补齐出发交通,可直接生成报销单',
|
2026-05-05 18:22:47 +08:00
|
|
|
|
action: '继续填写',
|
2026-05-20 21:00:47 +08:00
|
|
|
|
iconKey: 'travelDraft',
|
|
|
|
|
|
color: '#15803d',
|
|
|
|
|
|
accent: '#86efac'
|
2026-05-05 18:22:47 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '有 5 张票据未关联报销单',
|
2026-05-05 22:35:38 +08:00
|
|
|
|
tipLabel: 'AI 建议',
|
|
|
|
|
|
suggestion: '其中 3 张疑似交通费,可合并生成交通报销',
|
2026-05-05 18:22:47 +08:00
|
|
|
|
action: '去整理',
|
2026-05-20 21:00:47 +08:00
|
|
|
|
iconKey: 'receipts',
|
|
|
|
|
|
color: '#2563eb',
|
|
|
|
|
|
accent: '#93c5fd'
|
2026-05-05 18:22:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
|
2026-05-05 22:35:38 +08:00
|
|
|
|
const todoAlertCount = todoItems.length
|
|
|
|
|
|
|
2026-05-05 18:22:47 +08:00
|
|
|
|
const progressItems = [
|
|
|
|
|
|
{
|
|
|
|
|
|
id: 'travel',
|
|
|
|
|
|
title: '差旅报销',
|
|
|
|
|
|
amount: '¥3,280',
|
|
|
|
|
|
date: '2026-05-03',
|
|
|
|
|
|
status: '主管审批中',
|
|
|
|
|
|
tone: 'success',
|
2026-05-20 21:00:47 +08:00
|
|
|
|
iconKey: 'flight',
|
|
|
|
|
|
color: '#0d9668',
|
|
|
|
|
|
accent: '#6ee7b7'
|
2026-05-05 18:22:47 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: 'transport',
|
|
|
|
|
|
title: '交通报销',
|
|
|
|
|
|
amount: '¥126',
|
|
|
|
|
|
date: '2026-05-02',
|
|
|
|
|
|
status: '财务复核中',
|
|
|
|
|
|
tone: 'info',
|
2026-05-20 21:00:47 +08:00
|
|
|
|
iconKey: 'transport',
|
|
|
|
|
|
color: '#2563eb',
|
|
|
|
|
|
accent: '#93c5fd'
|
2026-05-05 18:22:47 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: 'office',
|
|
|
|
|
|
title: '办公采购',
|
|
|
|
|
|
amount: '¥458',
|
|
|
|
|
|
date: '2026-05-01',
|
|
|
|
|
|
status: '已到账',
|
|
|
|
|
|
tone: 'mint',
|
2026-05-20 21:00:47 +08:00
|
|
|
|
iconKey: 'procurement',
|
|
|
|
|
|
color: '#059669',
|
|
|
|
|
|
accent: '#a7f3d0'
|
2026-05-05 18:22:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
|
2026-05-05 22:35:38 +08:00
|
|
|
|
const progressAlertCount = progressItems.filter((item) => item.status !== '已到账').length
|
|
|
|
|
|
|
2026-05-05 18:22:47 +08:00
|
|
|
|
const policyItems = [
|
|
|
|
|
|
{
|
|
|
|
|
|
name: '差旅报销管理办法(2026版)',
|
|
|
|
|
|
summary: '更新住宿标准与交通等级规则',
|
2026-05-05 22:35:38 +08:00
|
|
|
|
date: '2026-05-04'
|
2026-05-05 18:22:47 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
name: '业务招待费用报销规范',
|
|
|
|
|
|
summary: '明确参与人员与事由填写要求',
|
2026-05-05 22:35:38 +08:00
|
|
|
|
date: '2026-05-02'
|
2026-05-05 18:22:47 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
name: '交通费用报销细则',
|
|
|
|
|
|
summary: '补充网约车与停车费报销说明',
|
2026-05-05 22:35:38 +08:00
|
|
|
|
date: '2026-04-28'
|
2026-05-05 18:22:47 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
name: '票据与附件提交规范通知',
|
|
|
|
|
|
summary: '统一附件命名与上传要求',
|
2026-05-05 22:35:38 +08:00
|
|
|
|
date: '2026-04-25'
|
2026-05-05 18:22:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
]
|
2026-05-13 13:12:28 +00:00
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
refreshLatestExpenseConversation()
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
watch(
|
|
|
|
|
|
() => props.assistantModalOpen,
|
|
|
|
|
|
(open, previous) => {
|
|
|
|
|
|
if (previous && !open) {
|
|
|
|
|
|
refreshLatestExpenseConversation()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
2026-05-05 18:22:47 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.workbench {
|
2026-05-12 15:39:00 +00:00
|
|
|
|
min-width: 0;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 16px;
|
2026-05-12 15:39:00 +00:00
|
|
|
|
padding-bottom: 10px;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.assistant-hero {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
display: grid;
|
2026-05-07 11:50:10 +08:00
|
|
|
|
grid-template-columns: 228px minmax(0, 1fr);
|
|
|
|
|
|
gap: 18px;
|
|
|
|
|
|
padding: 20px 24px 20px 18px;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
border: 1px solid rgba(16, 185, 129, 0.12);
|
|
|
|
|
|
background:
|
|
|
|
|
|
radial-gradient(circle at top left, rgba(16, 185, 129, 0.12), transparent 34%),
|
|
|
|
|
|
radial-gradient(circle at right 20%, rgba(59, 130, 246, 0.07), transparent 28%),
|
|
|
|
|
|
linear-gradient(135deg, #f7fffb 0%, #ffffff 48%, #f5fbff 100%);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.assistant-hero::before,
|
|
|
|
|
|
.assistant-hero::after {
|
|
|
|
|
|
content: "";
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
background: rgba(16, 185, 129, 0.06);
|
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.assistant-hero::before {
|
|
|
|
|
|
right: -48px;
|
|
|
|
|
|
bottom: -58px;
|
|
|
|
|
|
width: 220px;
|
|
|
|
|
|
height: 220px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.assistant-hero::after {
|
|
|
|
|
|
right: 92px;
|
|
|
|
|
|
top: -44px;
|
|
|
|
|
|
width: 140px;
|
|
|
|
|
|
height: 140px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.assistant-visual {
|
|
|
|
|
|
position: relative;
|
2026-05-07 11:50:10 +08:00
|
|
|
|
min-height: 196px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: flex-end;
|
|
|
|
|
|
justify-content: flex-start;
|
|
|
|
|
|
padding: 0 0 10px 8px;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-07 11:50:10 +08:00
|
|
|
|
.assistant-visual::before {
|
2026-05-05 18:22:47 +08:00
|
|
|
|
content: "";
|
|
|
|
|
|
position: absolute;
|
2026-05-07 11:50:10 +08:00
|
|
|
|
inset: auto auto -78px -58px;
|
|
|
|
|
|
width: 264px;
|
|
|
|
|
|
height: 228px;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
background: radial-gradient(circle at 48% 38%, rgba(255, 255, 255, 0.92) 0%, rgba(220, 252, 231, 0.84) 58%, rgba(220, 252, 231, 0) 100%);
|
|
|
|
|
|
pointer-events: none;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-07 11:50:10 +08:00
|
|
|
|
.assistant-visual::after {
|
|
|
|
|
|
content: "";
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
left: 52px;
|
|
|
|
|
|
bottom: 18px;
|
|
|
|
|
|
width: 132px;
|
|
|
|
|
|
height: 18px;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
border-radius: 999px;
|
2026-05-07 11:50:10 +08:00
|
|
|
|
background: rgba(16, 185, 129, 0.14);
|
|
|
|
|
|
filter: blur(12px);
|
|
|
|
|
|
pointer-events: none;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-07 11:50:10 +08:00
|
|
|
|
.assistant-glow {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
left: 24px;
|
|
|
|
|
|
bottom: 22px;
|
|
|
|
|
|
width: 176px;
|
|
|
|
|
|
height: 176px;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
background: radial-gradient(circle, rgba(255, 255, 255, 0.98) 0%, rgba(236, 253, 245, 0.9) 58%, rgba(236, 253, 245, 0) 100%);
|
|
|
|
|
|
box-shadow: 0 24px 48px rgba(16, 185, 129, 0.12);
|
|
|
|
|
|
pointer-events: none;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 22:35:38 +08:00
|
|
|
|
.assistant-image {
|
2026-05-07 11:50:10 +08:00
|
|
|
|
position: relative;
|
|
|
|
|
|
z-index: 1;
|
|
|
|
|
|
width: 184px;
|
|
|
|
|
|
max-width: 100%;
|
|
|
|
|
|
height: auto;
|
2026-05-05 22:35:38 +08:00
|
|
|
|
object-fit: contain;
|
2026-05-07 11:50:10 +08:00
|
|
|
|
object-position: left bottom;
|
|
|
|
|
|
filter: drop-shadow(0 22px 28px rgba(15, 23, 42, 0.16));
|
2026-05-05 22:35:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 18:22:47 +08:00
|
|
|
|
.assistant-copy {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
z-index: 1;
|
|
|
|
|
|
display: grid;
|
2026-05-07 11:50:10 +08:00
|
|
|
|
gap: 10px;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
align-content: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.assistant-copy h3 {
|
|
|
|
|
|
color: #0f172a;
|
2026-05-07 11:50:10 +08:00
|
|
|
|
font-size: 26px;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
line-height: 1.25;
|
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.assistant-copy p {
|
|
|
|
|
|
max-width: 760px;
|
|
|
|
|
|
color: #5b6b83;
|
2026-05-07 11:50:10 +08:00
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
line-height: 1.6;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.assistant-input {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
2026-05-07 11:50:10 +08:00
|
|
|
|
min-height: 48px;
|
|
|
|
|
|
padding: 4px 14px;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
border: 1px solid rgba(148, 163, 184, 0.28);
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
background: rgba(255, 255, 255, 0.92);
|
|
|
|
|
|
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-12 01:27:49 +00:00
|
|
|
|
.assistant-file-input {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 23:47:20 +08:00
|
|
|
|
.assistant-input textarea {
|
2026-05-05 18:22:47 +08:00
|
|
|
|
min-width: 0;
|
|
|
|
|
|
flex: 1;
|
2026-05-07 11:50:10 +08:00
|
|
|
|
height: 22px;
|
|
|
|
|
|
min-height: 22px;
|
|
|
|
|
|
max-height: 22px;
|
2026-05-05 23:47:20 +08:00
|
|
|
|
resize: none;
|
|
|
|
|
|
border: 0;
|
2026-05-06 11:00:38 +08:00
|
|
|
|
padding: 1px 0;
|
2026-05-05 23:47:20 +08:00
|
|
|
|
background: transparent;
|
|
|
|
|
|
color: #0f172a;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
font-size: 15px;
|
2026-05-06 11:00:38 +08:00
|
|
|
|
line-height: 22px;
|
|
|
|
|
|
overflow: hidden;
|
2026-05-05 23:47:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.assistant-input textarea::placeholder {
|
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.assistant-input textarea:focus {
|
|
|
|
|
|
outline: none;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.hero-action,
|
2026-05-12 06:39:26 +00:00
|
|
|
|
.secondary-action,
|
2026-05-05 18:22:47 +08:00
|
|
|
|
.ghost-action,
|
|
|
|
|
|
.row-action,
|
|
|
|
|
|
.link-action,
|
|
|
|
|
|
.row-link {
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.hero-action {
|
2026-05-07 11:50:10 +08:00
|
|
|
|
height: 40px;
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
padding: 0 16px;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
background: linear-gradient(135deg, #10b981, #059669);
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
box-shadow: 0 10px 22px rgba(16, 185, 129, 0.18);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-07 11:50:10 +08:00
|
|
|
|
.hero-action .mdi,
|
2026-05-12 06:39:26 +00:00
|
|
|
|
.secondary-action .mdi,
|
2026-05-07 11:50:10 +08:00
|
|
|
|
.ghost-action .mdi {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
line-height: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.hero-action span,
|
2026-05-12 06:39:26 +00:00
|
|
|
|
.secondary-action span,
|
2026-05-07 11:50:10 +08:00
|
|
|
|
.ghost-action span {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
line-height: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 18:22:47 +08:00
|
|
|
|
.assistant-tools {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
2026-05-07 11:50:10 +08:00
|
|
|
|
gap: 10px;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-12 06:39:26 +00:00
|
|
|
|
.assistant-file-strip {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.assistant-file-note,
|
|
|
|
|
|
.assistant-file-chip {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
min-height: 30px;
|
|
|
|
|
|
padding: 0 12px;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.assistant-file-note {
|
|
|
|
|
|
background: rgba(16, 185, 129, 0.1);
|
|
|
|
|
|
color: #047857;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.assistant-file-chip {
|
|
|
|
|
|
max-width: 220px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
border: 1px solid rgba(148, 163, 184, 0.24);
|
|
|
|
|
|
background: rgba(255, 255, 255, 0.9);
|
|
|
|
|
|
color: #475569;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.assistant-file-clear {
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 18:22:47 +08:00
|
|
|
|
.ghost-action {
|
|
|
|
|
|
height: 40px;
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
2026-05-07 11:50:10 +08:00
|
|
|
|
justify-content: center;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
gap: 8px;
|
|
|
|
|
|
padding: 0 16px;
|
2026-05-07 11:50:10 +08:00
|
|
|
|
border: 1px solid rgba(15, 118, 110, 0.18);
|
2026-05-05 18:22:47 +08:00
|
|
|
|
border-radius: 10px;
|
2026-05-07 11:50:10 +08:00
|
|
|
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.94), rgba(244, 250, 247, 0.88));
|
|
|
|
|
|
color: #0f766e;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: 700;
|
2026-05-07 11:50:10 +08:00
|
|
|
|
box-shadow:
|
|
|
|
|
|
inset 0 1px 0 rgba(255, 255, 255, 0.9),
|
|
|
|
|
|
0 6px 14px rgba(15, 118, 110, 0.06);
|
2026-05-05 18:22:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-07 11:50:10 +08:00
|
|
|
|
.ghost-action .mdi {
|
|
|
|
|
|
color: #10b981;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-12 06:39:26 +00:00
|
|
|
|
.secondary-action {
|
|
|
|
|
|
height: 40px;
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
padding: 0 16px;
|
|
|
|
|
|
border: 1px solid rgba(59, 130, 246, 0.18);
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
background: linear-gradient(180deg, rgba(244, 249, 255, 0.96), rgba(234, 244, 255, 0.9));
|
|
|
|
|
|
color: #1d4ed8;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
box-shadow:
|
|
|
|
|
|
inset 0 1px 0 rgba(255, 255, 255, 0.92),
|
|
|
|
|
|
0 6px 14px rgba(37, 99, 235, 0.08);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.secondary-action .mdi {
|
|
|
|
|
|
color: #2563eb;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.hero-action:disabled,
|
|
|
|
|
|
.secondary-action:disabled,
|
|
|
|
|
|
.ghost-action:disabled {
|
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
|
opacity: 0.68;
|
|
|
|
|
|
box-shadow: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 18:22:47 +08:00
|
|
|
|
.workbench-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
|
|
|
gap: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.list-panel,
|
|
|
|
|
|
.policy-panel {
|
|
|
|
|
|
padding: 20px 22px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.section-head {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.section-head h3 {
|
|
|
|
|
|
color: #0f172a;
|
|
|
|
|
|
font-size: 17px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 22:35:38 +08:00
|
|
|
|
.title-with-badge {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.alert-badge {
|
|
|
|
|
|
min-width: 22px;
|
|
|
|
|
|
height: 22px;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
2026-05-05 22:35:38 +08:00
|
|
|
|
padding: 0 7px;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
border-radius: 999px;
|
2026-05-05 22:35:38 +08:00
|
|
|
|
background: #ef4444;
|
|
|
|
|
|
color: #fff;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
font-weight: 800;
|
2026-05-05 22:35:38 +08:00
|
|
|
|
line-height: 1;
|
|
|
|
|
|
box-shadow: 0 6px 14px rgba(239, 68, 68, 0.22);
|
2026-05-05 18:22:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.link-action {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 4px;
|
|
|
|
|
|
color: #10b981;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.list-body {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.todo-row,
|
|
|
|
|
|
.progress-row {
|
|
|
|
|
|
display: grid;
|
2026-05-20 21:00:47 +08:00
|
|
|
|
grid-template-columns: 56px minmax(0, 1fr) auto;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
gap: 14px;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
padding: 14px 0;
|
|
|
|
|
|
border-top: 1px solid #edf2f7;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.todo-row:first-child,
|
|
|
|
|
|
.progress-row:first-child {
|
|
|
|
|
|
padding-top: 4px;
|
|
|
|
|
|
border-top: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.todo-copy {
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.todo-copy strong {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
color: #0f172a;
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
line-height: 1.4;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.todo-copy p {
|
|
|
|
|
|
margin-top: 4px;
|
|
|
|
|
|
color: #6b7280;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 22:35:38 +08:00
|
|
|
|
.todo-advice {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.todo-advice-label {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
min-height: 22px;
|
|
|
|
|
|
padding: 0 8px;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
background: #ecfdf5;
|
|
|
|
|
|
color: #059669;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.todo-advice-text {
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 18:22:47 +08:00
|
|
|
|
.row-action {
|
|
|
|
|
|
height: 38px;
|
|
|
|
|
|
padding: 0 16px;
|
|
|
|
|
|
border: 1px solid rgba(16, 185, 129, 0.36);
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
color: #10b981;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 22:35:38 +08:00
|
|
|
|
.progress-row {
|
2026-05-20 21:00:47 +08:00
|
|
|
|
grid-template-columns: 56px minmax(0, 1fr) minmax(84px, auto) minmax(104px, auto);
|
2026-05-05 22:35:38 +08:00
|
|
|
|
gap: 14px 16px;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 22:35:38 +08:00
|
|
|
|
.progress-copy strong {
|
|
|
|
|
|
margin-bottom: 2px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.progress-amount {
|
|
|
|
|
|
color: #0f172a;
|
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
|
line-height: 1;
|
|
|
|
|
|
text-align: right;
|
|
|
|
|
|
font-variant-numeric: tabular-nums;
|
|
|
|
|
|
white-space: nowrap;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.progress-status {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
2026-05-05 22:35:38 +08:00
|
|
|
|
min-width: 104px;
|
|
|
|
|
|
min-height: 34px;
|
|
|
|
|
|
padding: 6px 14px;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
|
white-space: nowrap;
|
2026-05-05 22:35:38 +08:00
|
|
|
|
justify-self: end;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.progress-status.success,
|
|
|
|
|
|
.policy-status.success {
|
|
|
|
|
|
background: #eafaf2;
|
|
|
|
|
|
color: #16935f;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.progress-status.info,
|
|
|
|
|
|
.policy-status.info {
|
|
|
|
|
|
background: #eff6ff;
|
|
|
|
|
|
color: #3b82f6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.progress-status.mint {
|
|
|
|
|
|
background: #edfdf5;
|
|
|
|
|
|
color: #10b981;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.policy-table {
|
|
|
|
|
|
border: 1px solid #e7edf5;
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.policy-row {
|
|
|
|
|
|
display: grid;
|
2026-05-05 22:35:38 +08:00
|
|
|
|
grid-template-columns: 2.2fr 2.4fr 1fr;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
gap: 16px;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
min-height: 56px;
|
|
|
|
|
|
padding: 0 18px;
|
|
|
|
|
|
border-top: 1px solid #edf2f7;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.policy-head {
|
|
|
|
|
|
min-height: 44px;
|
|
|
|
|
|
background: #f8fbff;
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
|
border-top: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.policy-row strong,
|
|
|
|
|
|
.policy-row span {
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.policy-row strong {
|
|
|
|
|
|
color: #0f172a;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.policy-row span {
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 22:35:38 +08:00
|
|
|
|
.policy-title-cell,
|
|
|
|
|
|
.policy-summary-cell {
|
|
|
|
|
|
justify-self: stretch;
|
|
|
|
|
|
text-align: left;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 22:35:38 +08:00
|
|
|
|
.policy-date-cell {
|
|
|
|
|
|
justify-self: center;
|
|
|
|
|
|
text-align: center;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 1320px) {
|
|
|
|
|
|
.assistant-copy h3 {
|
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.policy-row {
|
2026-05-05 22:35:38 +08:00
|
|
|
|
grid-template-columns: 1.8fr 1.8fr 1fr;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-12 15:39:00 +00:00
|
|
|
|
@media (max-width: 1440px) {
|
|
|
|
|
|
.workbench {
|
|
|
|
|
|
gap: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.assistant-hero {
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
padding: 18px 20px 18px 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.assistant-copy h3 {
|
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.assistant-visual {
|
|
|
|
|
|
min-height: 184px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.assistant-image {
|
|
|
|
|
|
width: 172px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.workbench-grid {
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.list-panel,
|
|
|
|
|
|
.policy-panel {
|
|
|
|
|
|
padding: 18px 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.policy-row {
|
|
|
|
|
|
min-height: 52px;
|
|
|
|
|
|
padding: 0 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 18:22:47 +08:00
|
|
|
|
@media (max-width: 1080px) {
|
|
|
|
|
|
.assistant-hero {
|
|
|
|
|
|
grid-template-columns: 1fr;
|
2026-05-07 11:50:10 +08:00
|
|
|
|
gap: 8px;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.assistant-visual {
|
2026-05-07 11:50:10 +08:00
|
|
|
|
min-height: 188px;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
padding: 0 0 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.assistant-visual::before,
|
|
|
|
|
|
.assistant-visual::after,
|
|
|
|
|
|
.assistant-glow {
|
|
|
|
|
|
left: 50%;
|
|
|
|
|
|
transform: translateX(-50%);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.assistant-visual::before {
|
|
|
|
|
|
inset: auto auto -82px 50%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.assistant-image {
|
|
|
|
|
|
width: 176px;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-15 06:56:51 +00:00
|
|
|
|
.workbench-grid {
|
2026-05-05 18:22:47 +08:00
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 860px) {
|
|
|
|
|
|
.assistant-hero,
|
|
|
|
|
|
.list-panel,
|
|
|
|
|
|
.policy-panel {
|
|
|
|
|
|
padding: 18px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.assistant-input {
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: stretch;
|
|
|
|
|
|
padding: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-07 11:50:10 +08:00
|
|
|
|
.assistant-visual {
|
|
|
|
|
|
min-height: 160px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.assistant-glow {
|
|
|
|
|
|
width: 148px;
|
|
|
|
|
|
height: 148px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.assistant-image {
|
|
|
|
|
|
width: 150px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 23:47:20 +08:00
|
|
|
|
.assistant-input textarea {
|
2026-05-06 11:00:38 +08:00
|
|
|
|
height: 40px;
|
|
|
|
|
|
min-height: 40px;
|
|
|
|
|
|
max-height: 40px;
|
|
|
|
|
|
line-height: 1.5;
|
2026-05-05 18:22:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.hero-action,
|
2026-05-12 06:39:26 +00:00
|
|
|
|
.secondary-action,
|
2026-05-05 18:22:47 +08:00
|
|
|
|
.ghost-action,
|
|
|
|
|
|
.row-action {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-12 06:39:26 +00:00
|
|
|
|
.assistant-file-chip {
|
|
|
|
|
|
max-width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 18:22:47 +08:00
|
|
|
|
.todo-row,
|
|
|
|
|
|
.progress-row {
|
2026-05-20 21:00:47 +08:00
|
|
|
|
grid-template-columns: 56px minmax(0, 1fr);
|
2026-05-05 18:22:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 22:35:38 +08:00
|
|
|
|
.progress-amount {
|
|
|
|
|
|
grid-column: 2;
|
|
|
|
|
|
text-align: left;
|
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 18:22:47 +08:00
|
|
|
|
.row-action,
|
|
|
|
|
|
.progress-status {
|
|
|
|
|
|
grid-column: 2;
|
|
|
|
|
|
justify-self: start;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.policy-table {
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
border-radius: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.policy-head {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.policy-row {
|
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
padding: 16px 0;
|
|
|
|
|
|
border-top: 1px solid #edf2f7;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.policy-row strong,
|
|
|
|
|
|
.policy-row span {
|
|
|
|
|
|
white-space: normal;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|