refactor: split project into web and server directories
- Move frontend to web/ directory - Add server/ directory for backend - Restructure project for前后端分离架构 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
338
web/src/views/TravelReimbursementCreateView.vue
Normal file
338
web/src/views/TravelReimbursementCreateView.vue
Normal file
@@ -0,0 +1,338 @@
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<Transition name="assistant-modal">
|
||||
<div class="assistant-overlay" @click.self="emit('close')">
|
||||
<section class="assistant-modal">
|
||||
<header class="assistant-header">
|
||||
<div class="assistant-header-main">
|
||||
<span class="assistant-badge">AI Workspace</span>
|
||||
<div>
|
||||
<h2>统一对话工作台</h2>
|
||||
<p>个人工作台、发起报销、智能录入统一走这里,右侧会根据你的意图实时切换状态视图。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="assistant-header-actions">
|
||||
<span class="source-pill">{{ sourceLabel }}</span>
|
||||
<button class="close-btn" type="button" aria-label="关闭对话工作台" @click="emit('close')">
|
||||
<i class="mdi mdi-close"></i>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="assistant-layout" :class="{ 'has-insight': showInsightPanel }">
|
||||
<section class="dialog-panel">
|
||||
<div class="dialog-toolbar">
|
||||
<button
|
||||
v-for="shortcut in shortcuts"
|
||||
:key="shortcut.label"
|
||||
type="button"
|
||||
class="shortcut-chip"
|
||||
@click="runShortcut(shortcut.prompt)"
|
||||
>
|
||||
<i :class="shortcut.icon"></i>
|
||||
<span>{{ shortcut.label }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div ref="messageListRef" class="message-list" aria-live="polite">
|
||||
<article
|
||||
v-for="message in messages"
|
||||
:key="message.id"
|
||||
class="message-row"
|
||||
:class="message.role"
|
||||
>
|
||||
<span class="message-avatar">
|
||||
<i :class="message.role === 'assistant' ? 'mdi mdi-robot-excited-outline' : 'mdi mdi-account-circle-outline'"></i>
|
||||
</span>
|
||||
|
||||
<div class="message-bubble">
|
||||
<header class="message-meta">
|
||||
<strong>{{ message.role === 'assistant' ? 'AI 助手' : '我' }}</strong>
|
||||
<time>{{ message.time }}</time>
|
||||
</header>
|
||||
<p>{{ message.text }}</p>
|
||||
|
||||
<div v-if="message.attachments?.length" class="message-files">
|
||||
<span v-for="file in message.attachments" :key="file" class="file-chip">
|
||||
<i class="mdi mdi-paperclip"></i>
|
||||
{{ file }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<form class="composer" @submit.prevent="submitComposer">
|
||||
<input
|
||||
ref="fileInputRef"
|
||||
class="hidden-file-input"
|
||||
type="file"
|
||||
multiple
|
||||
accept=".pdf,.jpg,.jpeg,.png,.webp,.doc,.docx,.xls,.xlsx"
|
||||
@change="handleFilesChange"
|
||||
/>
|
||||
|
||||
<div class="composer-shell">
|
||||
<textarea
|
||||
v-model="composerDraft"
|
||||
rows="3"
|
||||
:placeholder="composerPlaceholder"
|
||||
@keydown.ctrl.enter.prevent="submitComposer"
|
||||
/>
|
||||
|
||||
<div v-if="attachedFiles.length" class="composer-files">
|
||||
<span v-for="file in attachedFiles" :key="file.name" class="file-chip active">
|
||||
<i class="mdi mdi-paperclip"></i>
|
||||
{{ file.name }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="composer-foot">
|
||||
<div class="composer-tools">
|
||||
<button type="button" class="tool-btn" aria-label="上传附件" @click="triggerFileUpload">
|
||||
<i class="mdi mdi-paperclip"></i>
|
||||
</button>
|
||||
<span class="composer-tip">Ctrl + Enter 发送</span>
|
||||
</div>
|
||||
|
||||
<button class="send-btn" type="submit" :disabled="!canSubmit" aria-label="发送">
|
||||
<i class="mdi mdi-send"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<Transition name="insight-panel">
|
||||
<aside v-if="showInsightPanel" class="insight-panel">
|
||||
<div class="insight-head">
|
||||
<div>
|
||||
<span class="intent-pill" :class="currentInsight.intent">{{ currentIntentLabel }}</span>
|
||||
<h3>{{ currentInsight.title }}</h3>
|
||||
<p>{{ currentInsight.summary }}</p>
|
||||
</div>
|
||||
|
||||
<div class="confidence-card">
|
||||
<span>意图识别</span>
|
||||
<strong>{{ currentInsight.confidence }}%</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Transition name="insight-switch" mode="out-in">
|
||||
<div :key="currentInsight.intent + currentInsight.title" class="insight-body">
|
||||
<template v-if="currentInsight.intent === 'approval'">
|
||||
<section class="insight-card primary">
|
||||
<div class="card-head">
|
||||
<h4>审批状态</h4>
|
||||
<span class="status-pill warning">{{ currentInsight.status.currentStatus }}</span>
|
||||
</div>
|
||||
<div class="metric-grid">
|
||||
<div class="metric-item">
|
||||
<span>单号</span>
|
||||
<strong>{{ currentInsight.status.requestId }}</strong>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<span>当前节点</span>
|
||||
<strong>{{ currentInsight.status.currentNode }}</strong>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<span>下一处理人</span>
|
||||
<strong>{{ currentInsight.status.nextOwner }}</strong>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<span>预计完成</span>
|
||||
<strong>{{ currentInsight.status.eta }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="insight-card">
|
||||
<div class="card-head">
|
||||
<h4>流程节点</h4>
|
||||
</div>
|
||||
<ol class="timeline-list">
|
||||
<li
|
||||
v-for="step in currentInsight.status.timeline"
|
||||
:key="step.label"
|
||||
:class="step.state"
|
||||
>
|
||||
<span class="timeline-dot"></span>
|
||||
<div>
|
||||
<strong>{{ step.label }}</strong>
|
||||
<p>{{ step.time }}</p>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<section class="insight-card">
|
||||
<div class="card-head">
|
||||
<h4>待处理提醒</h4>
|
||||
</div>
|
||||
<ul class="bullet-list">
|
||||
<li v-for="item in currentInsight.status.actions" :key="item">{{ item }}</li>
|
||||
</ul>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<template v-else-if="currentInsight.intent === 'recognition'">
|
||||
<section class="insight-card primary">
|
||||
<div class="card-head">
|
||||
<h4>识别结果</h4>
|
||||
<span class="status-pill success">{{ currentInsight.recognition.state }}</span>
|
||||
</div>
|
||||
<div class="metric-grid">
|
||||
<div class="metric-item">
|
||||
<span>关联单号</span>
|
||||
<strong>{{ currentInsight.recognition.requestId }}</strong>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<span>识别附件</span>
|
||||
<strong>{{ currentInsight.recognition.fileCount }} 份</strong>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<span>建议金额</span>
|
||||
<strong>{{ currentInsight.recognition.amount }}</strong>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<span>完整度</span>
|
||||
<strong>{{ currentInsight.recognition.completeness }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="insight-card">
|
||||
<div class="card-head">
|
||||
<h4>票据明细</h4>
|
||||
</div>
|
||||
<div class="receipt-list">
|
||||
<article v-for="item in currentInsight.recognition.receipts" :key="item.name" class="receipt-row">
|
||||
<div>
|
||||
<strong>{{ item.name }}</strong>
|
||||
<p>{{ item.type }}</p>
|
||||
</div>
|
||||
<div class="receipt-side">
|
||||
<strong>{{ item.amount }}</strong>
|
||||
<span>{{ item.confidence }}</span>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="insight-card">
|
||||
<div class="card-head">
|
||||
<h4>识别建议</h4>
|
||||
</div>
|
||||
<ul class="bullet-list">
|
||||
<li v-for="item in currentInsight.recognition.suggestions" :key="item">{{ item }}</li>
|
||||
</ul>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<template v-else-if="currentInsight.intent === 'note'">
|
||||
<section class="insight-card primary">
|
||||
<div class="card-head">
|
||||
<h4>补充说明</h4>
|
||||
<span class="status-pill note">{{ currentInsight.note.state }}</span>
|
||||
</div>
|
||||
<div class="note-block">
|
||||
<span>关联单号</span>
|
||||
<strong>{{ currentInsight.note.requestId }}</strong>
|
||||
<p>{{ currentInsight.note.generatedNote }}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="insight-card">
|
||||
<div class="card-head">
|
||||
<h4>影响范围</h4>
|
||||
</div>
|
||||
<ul class="bullet-list">
|
||||
<li v-for="item in currentInsight.note.impacts" :key="item">{{ item }}</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="insight-card">
|
||||
<div class="card-head">
|
||||
<h4>下一步</h4>
|
||||
</div>
|
||||
<div class="metric-grid single">
|
||||
<div class="metric-item">
|
||||
<span>当前处理人</span>
|
||||
<strong>{{ currentInsight.note.owner }}</strong>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<span>建议动作</span>
|
||||
<strong>{{ currentInsight.note.nextAction }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<template v-else-if="currentInsight.intent === 'draft'">
|
||||
<section class="insight-card primary">
|
||||
<div class="card-head">
|
||||
<h4>报销草稿</h4>
|
||||
<span class="status-pill success">{{ currentInsight.draft.state }}</span>
|
||||
</div>
|
||||
<div class="metric-grid">
|
||||
<div class="metric-item">
|
||||
<span>单号</span>
|
||||
<strong>{{ currentInsight.draft.requestId }}</strong>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<span>报销类型</span>
|
||||
<strong>{{ currentInsight.draft.type }}</strong>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<span>预计金额</span>
|
||||
<strong>{{ currentInsight.draft.amount }}</strong>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<span>当前进度</span>
|
||||
<strong>{{ currentInsight.draft.progress }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="insight-card">
|
||||
<div class="card-head">
|
||||
<h4>费用建议</h4>
|
||||
</div>
|
||||
<div class="receipt-list">
|
||||
<article v-for="item in currentInsight.draft.items" :key="item.name" class="receipt-row">
|
||||
<div>
|
||||
<strong>{{ item.name }}</strong>
|
||||
<p>{{ item.desc }}</p>
|
||||
</div>
|
||||
<div class="receipt-side">
|
||||
<strong>{{ item.amount }}</strong>
|
||||
<span>{{ item.tag }}</span>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="insight-card">
|
||||
<div class="card-head">
|
||||
<h4>待补信息</h4>
|
||||
</div>
|
||||
<ul class="bullet-list">
|
||||
<li v-for="item in currentInsight.draft.missing" :key="item">{{ item }}</li>
|
||||
</ul>
|
||||
</section>
|
||||
</template>
|
||||
</div>
|
||||
</Transition>
|
||||
</aside>
|
||||
</Transition>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script src="./scripts/TravelReimbursementCreateView.js"></script>
|
||||
|
||||
<style scoped src="../assets/styles/views/travel-reimbursement-create-view.css"></style>
|
||||
Reference in New Issue
Block a user