|
@@ -1,830 +0,0 @@
|
|
|
-<template>
|
|
|
|
|
- <div class="home-page">
|
|
|
|
|
- <!-- 欢迎区 + 快捷操作 -->
|
|
|
|
|
- <div class="welcome-section">
|
|
|
|
|
- <div class="welcome-left">
|
|
|
|
|
- <h1>{{ greeting }},{{ username }}!<span class="gradient-text">智能报告,洞察未来。</span></h1>
|
|
|
|
|
- <p>今天是个创作的好日子,开始您的智能报告之旅吧</p>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="welcome-actions">
|
|
|
|
|
- <el-button type="primary" size="large" @click="showUploadDialog = true">
|
|
|
|
|
- <span class="action-icon">📤</span>
|
|
|
|
|
- 上传模板
|
|
|
|
|
- </el-button>
|
|
|
|
|
- <el-button size="large" @click="showCreateDialog = true">
|
|
|
|
|
- <span class="action-icon">🛠️</span>
|
|
|
|
|
- 创建模板
|
|
|
|
|
- </el-button>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 统计卡片 -->
|
|
|
|
|
- <el-row :gutter="16" class="stats-row">
|
|
|
|
|
- <el-col :span="6">
|
|
|
|
|
- <div class="stat-card card" @click="router.push('/generations')">
|
|
|
|
|
- <div class="stat-icon blue">📄</div>
|
|
|
|
|
- <div class="stat-value">{{ stats.reportCount }}</div>
|
|
|
|
|
- <div class="stat-label">生成任务</div>
|
|
|
|
|
- <div class="stat-trend">报告生成记录</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="6">
|
|
|
|
|
- <div class="stat-card card" @click="router.push('/templates')">
|
|
|
|
|
- <div class="stat-icon purple">🎨</div>
|
|
|
|
|
- <div class="stat-value">{{ stats.templateCount }}</div>
|
|
|
|
|
- <div class="stat-label">可用模板</div>
|
|
|
|
|
- <div class="stat-trend">包含公开模板</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="6">
|
|
|
|
|
- <div class="stat-card card">
|
|
|
|
|
- <div class="stat-icon green">📊</div>
|
|
|
|
|
- <div class="stat-value">{{ stats.variableCount }}</div>
|
|
|
|
|
- <div class="stat-label">定义变量</div>
|
|
|
|
|
- <div class="stat-trend">模板变量总数</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="6">
|
|
|
|
|
- <div class="stat-card card">
|
|
|
|
|
- <div class="stat-icon orange">📁</div>
|
|
|
|
|
- <div class="stat-value">{{ stats.sourceFileCount }}</div>
|
|
|
|
|
- <div class="stat-label">来源文件</div>
|
|
|
|
|
- <div class="stat-trend">数据来源定义</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- </el-row>
|
|
|
|
|
-
|
|
|
|
|
- <!-- AI 对话入口 -->
|
|
|
|
|
- <div class="ai-card card">
|
|
|
|
|
- <div class="ai-welcome">
|
|
|
|
|
- <div class="ai-avatar">🤖</div>
|
|
|
|
|
- <div class="ai-title">你好!我是灵越AI助手,可以帮你:</div>
|
|
|
|
|
- <ul class="ai-list">
|
|
|
|
|
- <li>快速生成各类报告</li>
|
|
|
|
|
- <li>分析和解读数据</li>
|
|
|
|
|
- <li>回答业务相关问题</li>
|
|
|
|
|
- </ul>
|
|
|
|
|
- <div class="ai-tip">试试输入:"帮我生成一份智慧园区建设项目可行性研究报告"</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <el-input
|
|
|
|
|
- v-model="aiInput"
|
|
|
|
|
- placeholder="输入您的需求,或 @知识库 引用资料..."
|
|
|
|
|
- size="large"
|
|
|
|
|
- class="ai-input"
|
|
|
|
|
- >
|
|
|
|
|
- <template #suffix>
|
|
|
|
|
- <el-button type="primary" :icon="Promotion" circle @click="handleAiSubmit" />
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-input>
|
|
|
|
|
- <div class="thinking-modes">
|
|
|
|
|
- <el-radio-group v-model="thinkingMode" size="small">
|
|
|
|
|
- <el-radio-button label="deep">🧠 深度思考</el-radio-button>
|
|
|
|
|
- <el-radio-button label="quick">⚡ 快速回答</el-radio-button>
|
|
|
|
|
- <el-radio-button label="search">🌐 联网搜索</el-radio-button>
|
|
|
|
|
- <el-radio-button label="data">📊 数据分析</el-radio-button>
|
|
|
|
|
- </el-radio-group>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 推荐模板 -->
|
|
|
|
|
- <div class="section">
|
|
|
|
|
- <div class="section-header">
|
|
|
|
|
- <h2>📋 推荐模板</h2>
|
|
|
|
|
- <el-button type="primary" link @click="router.push('/templates')">
|
|
|
|
|
- 查看全部 →
|
|
|
|
|
- </el-button>
|
|
|
|
|
- </div>
|
|
|
|
|
- <el-row :gutter="16">
|
|
|
|
|
- <el-col :span="8" v-for="tpl in recommendTemplates" :key="tpl.id">
|
|
|
|
|
- <div
|
|
|
|
|
- class="tpl-card card"
|
|
|
|
|
- :class="{ 'tpl-parsing': tpl.parseStatus === 'processing' || tpl.parseStatus === 'pending' }"
|
|
|
|
|
- @click="useTemplate(tpl)"
|
|
|
|
|
- >
|
|
|
|
|
- <el-button
|
|
|
|
|
- class="tpl-delete-btn"
|
|
|
|
|
- type="danger"
|
|
|
|
|
- :icon="Delete"
|
|
|
|
|
- circle
|
|
|
|
|
- size="small"
|
|
|
|
|
- @click.stop="handleDeleteTemplate(tpl)"
|
|
|
|
|
- />
|
|
|
|
|
- <div class="tpl-preview">{{ tpl.icon }}</div>
|
|
|
|
|
- <!-- 状态徽章 -->
|
|
|
|
|
- <div class="tpl-status-badge parsing" v-if="tpl.parseStatus === 'processing' || tpl.parseStatus === 'pending'">
|
|
|
|
|
- <el-icon class="is-loading"><Loading /></el-icon>
|
|
|
|
|
- 解析中 {{ tpl.parseProgress }}%
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="tpl-status-badge failed" v-if="tpl.parseStatus === 'failed'">
|
|
|
|
|
- <el-icon><WarningFilled /></el-icon>
|
|
|
|
|
- 解析失败
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="tpl-info">
|
|
|
|
|
- <div class="tpl-name">{{ tpl.name }}</div>
|
|
|
|
|
- <div class="tpl-meta">
|
|
|
|
|
- <span>📊 {{ tpl.useCount }}次</span>
|
|
|
|
|
- <span>⭐ {{ (tpl.rating || 0).toFixed(1) }}</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="tpl-tags">
|
|
|
|
|
- <span class="tpl-tag" v-if="tpl.isOfficial">官方</span>
|
|
|
|
|
- <span class="tpl-tag hot" v-if="tpl.isHot">热门</span>
|
|
|
|
|
- <span class="tpl-tag parsing" v-if="tpl.parseStatus === 'processing' || tpl.parseStatus === 'pending'">解析中</span>
|
|
|
|
|
- <span class="tpl-tag failed" v-if="tpl.parseStatus === 'failed'">解析失败</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <el-button
|
|
|
|
|
- type="primary"
|
|
|
|
|
- size="small"
|
|
|
|
|
- class="tpl-btn"
|
|
|
|
|
- :disabled="tpl.parseStatus === 'processing' || tpl.parseStatus === 'pending'"
|
|
|
|
|
- >
|
|
|
|
|
- {{ (tpl.parseStatus === 'processing' || tpl.parseStatus === 'pending') ? '解析中...' : '使用此模板' }}
|
|
|
|
|
- </el-button>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- </el-row>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 创建模板对话框 -->
|
|
|
|
|
- <el-dialog v-model="showCreateDialog" title="创建新模板" width="500">
|
|
|
|
|
- <el-form :model="newTemplate" label-width="80px">
|
|
|
|
|
- <el-form-item label="模板名称" required>
|
|
|
|
|
- <el-input v-model="newTemplate.name" placeholder="请输入模板名称" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item label="描述">
|
|
|
|
|
- <el-input
|
|
|
|
|
- v-model="newTemplate.description"
|
|
|
|
|
- type="textarea"
|
|
|
|
|
- :rows="3"
|
|
|
|
|
- placeholder="请输入模板描述(可选)"
|
|
|
|
|
- />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-form>
|
|
|
|
|
- <template #footer>
|
|
|
|
|
- <el-button @click="showCreateDialog = false">取消</el-button>
|
|
|
|
|
- <el-button type="primary" @click="handleCreateTemplate" :loading="creating">创建</el-button>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-dialog>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 上传模板对话框 -->
|
|
|
|
|
- <el-dialog v-model="showUploadDialog" title="上传新模板" width="600">
|
|
|
|
|
- <el-form :model="uploadTemplate" label-width="80px">
|
|
|
|
|
- <el-form-item label="模板名称" required>
|
|
|
|
|
- <el-input v-model="uploadTemplate.name" placeholder="请输入模板名称" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item label="描述">
|
|
|
|
|
- <el-input
|
|
|
|
|
- v-model="uploadTemplate.description"
|
|
|
|
|
- type="textarea"
|
|
|
|
|
- :rows="2"
|
|
|
|
|
- placeholder="请输入模板描述(可选)"
|
|
|
|
|
- />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item label="基础文档" required>
|
|
|
|
|
- <el-upload
|
|
|
|
|
- class="upload-area"
|
|
|
|
|
- drag
|
|
|
|
|
- action="/api/v1/parse/upload"
|
|
|
|
|
- :data="uploadData"
|
|
|
|
|
- :before-upload="beforeUpload"
|
|
|
|
|
- :on-success="handleUploadSuccess"
|
|
|
|
|
- :on-error="handleUploadError"
|
|
|
|
|
- :show-file-list="true"
|
|
|
|
|
- :limit="1"
|
|
|
|
|
- accept=".doc,.docx,.pdf"
|
|
|
|
|
- >
|
|
|
|
|
- <div v-if="uploadTemplate.baseDocumentId" class="upload-success">
|
|
|
|
|
- <el-icon size="40" color="#52c41a"><CircleCheckFilled /></el-icon>
|
|
|
|
|
- <div>文档已上传</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <template v-else>
|
|
|
|
|
- <el-icon class="el-icon--upload" size="40"><UploadFilled /></el-icon>
|
|
|
|
|
- <div class="el-upload__text">
|
|
|
|
|
- 拖拽文件到此处,或<em>点击上传</em>
|
|
|
|
|
- </div>
|
|
|
|
|
- </template>
|
|
|
|
|
- <template #tip>
|
|
|
|
|
- <div class="el-upload__tip">
|
|
|
|
|
- 支持 Word (.doc, .docx) 和 PDF 格式,上传后可标记变量
|
|
|
|
|
- </div>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-upload>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-form>
|
|
|
|
|
- <template #footer>
|
|
|
|
|
- <el-button @click="showUploadDialog = false">取消</el-button>
|
|
|
|
|
- <el-button type="primary" @click="handleUploadTemplate" :loading="uploading">
|
|
|
|
|
- 创建模板
|
|
|
|
|
- </el-button>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-dialog>
|
|
|
|
|
- </div>
|
|
|
|
|
-</template>
|
|
|
|
|
-
|
|
|
|
|
-<script setup>
|
|
|
|
|
-import { ref, reactive, computed, onMounted, onUnmounted, watch } from 'vue'
|
|
|
|
|
-import { useRouter } from 'vue-router'
|
|
|
|
|
-import { Promotion, UploadFilled, CircleCheckFilled, Delete, Loading, WarningFilled } from '@element-plus/icons-vue'
|
|
|
|
|
-import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
|
|
-import { useTemplateStore } from '@/stores/template'
|
|
|
|
|
-import { useTaskCenterStore } from '@/stores/taskCenter'
|
|
|
|
|
-import { templateApi, parseApi, taskCenterApi } from '@/api'
|
|
|
|
|
-
|
|
|
|
|
-const router = useRouter()
|
|
|
|
|
-const templateStore = useTemplateStore()
|
|
|
|
|
-const taskCenterStore = useTaskCenterStore()
|
|
|
|
|
-
|
|
|
|
|
-const aiInput = ref('')
|
|
|
|
|
-const thinkingMode = ref('deep')
|
|
|
|
|
-const showUploadDialog = ref(false)
|
|
|
|
|
-const showCreateDialog = ref(false)
|
|
|
|
|
-
|
|
|
|
|
-// 用户信息
|
|
|
|
|
-const username = computed(() => localStorage.getItem('username') || '用户')
|
|
|
|
|
-const userId = computed(() => localStorage.getItem('userId') || '')
|
|
|
|
|
-
|
|
|
|
|
-// 上传时附带的数据
|
|
|
|
|
-const uploadData = computed(() => ({
|
|
|
|
|
- userId: userId.value
|
|
|
|
|
-}))
|
|
|
|
|
-
|
|
|
|
|
-// 问候语
|
|
|
|
|
-const greeting = computed(() => {
|
|
|
|
|
- const hour = new Date().getHours()
|
|
|
|
|
- if (hour < 6) return '夜深了'
|
|
|
|
|
- if (hour < 9) return '早上好'
|
|
|
|
|
- if (hour < 12) return '上午好'
|
|
|
|
|
- if (hour < 14) return '中午好'
|
|
|
|
|
- if (hour < 18) return '下午好'
|
|
|
|
|
- return '晚上好'
|
|
|
|
|
-})
|
|
|
|
|
-
|
|
|
|
|
-const stats = reactive({
|
|
|
|
|
- reportCount: 0,
|
|
|
|
|
- templateCount: 0,
|
|
|
|
|
- variableCount: 0,
|
|
|
|
|
- sourceFileCount: 0
|
|
|
|
|
-})
|
|
|
|
|
-
|
|
|
|
|
-const newTemplate = reactive({
|
|
|
|
|
- name: '',
|
|
|
|
|
- description: ''
|
|
|
|
|
-})
|
|
|
|
|
-const creating = ref(false)
|
|
|
|
|
-
|
|
|
|
|
-const uploadTemplate = reactive({
|
|
|
|
|
- name: '',
|
|
|
|
|
- description: '',
|
|
|
|
|
- baseDocumentId: ''
|
|
|
|
|
-})
|
|
|
|
|
-const uploading = ref(false)
|
|
|
|
|
-
|
|
|
|
|
-// 推荐模板数据(从 API 获取)
|
|
|
|
|
-const recommendTemplates = ref([])
|
|
|
|
|
-
|
|
|
|
|
-const templateIcons = ['📊', '🏢', '📅', '💼', '📋', '📈', '🎯', '📝']
|
|
|
|
|
-
|
|
|
|
|
-// 刷新统计数据
|
|
|
|
|
-async function refreshStats() {
|
|
|
|
|
- try {
|
|
|
|
|
- const statsData = await templateApi.getStats()
|
|
|
|
|
- stats.reportCount = statsData.reportCount || 0
|
|
|
|
|
- stats.templateCount = statsData.templateCount || 0
|
|
|
|
|
- stats.variableCount = statsData.variableCount || 0
|
|
|
|
|
- stats.sourceFileCount = statsData.sourceFileCount || 0
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error('获取统计数据失败:', error)
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 刷新推荐模板列表
|
|
|
|
|
-async function refreshRecommendTemplates() {
|
|
|
|
|
- try {
|
|
|
|
|
- await templateStore.fetchTemplates()
|
|
|
|
|
- // 取前3个模板作为推荐(按使用次数排序)
|
|
|
|
|
- const sortedTemplates = [...templateStore.templates].sort((a, b) => (b.useCount || 0) - (a.useCount || 0))
|
|
|
|
|
- const templates = sortedTemplates.slice(0, 3).map((t, i) => ({
|
|
|
|
|
- ...t,
|
|
|
|
|
- icon: templateIcons[i % templateIcons.length],
|
|
|
|
|
- useCount: t.useCount || 0,
|
|
|
|
|
- rating: t.rating || 0,
|
|
|
|
|
- isOfficial: t.isPublic,
|
|
|
|
|
- isHot: (t.useCount || 0) >= 100,
|
|
|
|
|
- parseStatus: null, // 解析状态
|
|
|
|
|
- parseProgress: 0 // 解析进度
|
|
|
|
|
- }))
|
|
|
|
|
-
|
|
|
|
|
- // 获取每个模板的解析状态
|
|
|
|
|
- await Promise.all(templates.map(async (tpl) => {
|
|
|
|
|
- if (tpl.baseDocumentId) {
|
|
|
|
|
- try {
|
|
|
|
|
- const task = await taskCenterApi.getByDocumentId(tpl.baseDocumentId)
|
|
|
|
|
- if (task) {
|
|
|
|
|
- tpl.parseStatus = task.status
|
|
|
|
|
- tpl.parseProgress = task.progress || 0
|
|
|
|
|
- }
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- // 没有解析任务,认为是完成状态
|
|
|
|
|
- tpl.parseStatus = 'completed'
|
|
|
|
|
- tpl.parseProgress = 100
|
|
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- // 没有基础文档,认为是空白模板
|
|
|
|
|
- tpl.parseStatus = 'completed'
|
|
|
|
|
- tpl.parseProgress = 100
|
|
|
|
|
- }
|
|
|
|
|
- }))
|
|
|
|
|
-
|
|
|
|
|
- recommendTemplates.value = templates
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error('获取模板列表失败:', error)
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 监听任务中心的任务列表变化,更新模板解析状态
|
|
|
|
|
-watch(
|
|
|
|
|
- () => taskCenterStore.list,
|
|
|
|
|
- async (newList) => {
|
|
|
|
|
- if (!newList || newList.length === 0) return
|
|
|
|
|
-
|
|
|
|
|
- // 遍历推荐模板,更新解析状态
|
|
|
|
|
- for (const tpl of recommendTemplates.value) {
|
|
|
|
|
- if (tpl.baseDocumentId) {
|
|
|
|
|
- const task = newList.find(t => t.documentId === tpl.baseDocumentId)
|
|
|
|
|
- if (task) {
|
|
|
|
|
- tpl.parseStatus = task.status
|
|
|
|
|
- tpl.parseProgress = task.progress || 0
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- { deep: true }
|
|
|
|
|
-)
|
|
|
|
|
-
|
|
|
|
|
-onMounted(async () => {
|
|
|
|
|
- await Promise.all([refreshStats(), refreshRecommendTemplates()])
|
|
|
|
|
-
|
|
|
|
|
- // 如果有解析中的模板,启动轮询刷新状态
|
|
|
|
|
- startParseStatusPolling()
|
|
|
|
|
-})
|
|
|
|
|
-
|
|
|
|
|
-onUnmounted(() => {
|
|
|
|
|
- stopParseStatusPolling()
|
|
|
|
|
-})
|
|
|
|
|
-
|
|
|
|
|
-// 解析状态轮询
|
|
|
|
|
-let parseStatusTimer = null
|
|
|
|
|
-
|
|
|
|
|
-function startParseStatusPolling() {
|
|
|
|
|
- stopParseStatusPolling()
|
|
|
|
|
-
|
|
|
|
|
- parseStatusTimer = setInterval(async () => {
|
|
|
|
|
- // 检查是否有解析中的模板
|
|
|
|
|
- const parsingTemplates = recommendTemplates.value.filter(
|
|
|
|
|
- t => t.parseStatus === 'processing' || t.parseStatus === 'pending'
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- if (parsingTemplates.length === 0) {
|
|
|
|
|
- stopParseStatusPolling()
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 更新解析中模板的状态
|
|
|
|
|
- for (const tpl of parsingTemplates) {
|
|
|
|
|
- if (tpl.baseDocumentId) {
|
|
|
|
|
- try {
|
|
|
|
|
- const task = await taskCenterApi.getByDocumentId(tpl.baseDocumentId)
|
|
|
|
|
- if (task) {
|
|
|
|
|
- tpl.parseStatus = task.status
|
|
|
|
|
- tpl.parseProgress = task.progress || 0
|
|
|
|
|
-
|
|
|
|
|
- // 如果解析完成,刷新整个列表
|
|
|
|
|
- if (task.status === 'completed' || task.status === 'failed') {
|
|
|
|
|
- await refreshRecommendTemplates()
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- // 忽略错误
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }, 3000) // 每3秒轮询一次
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function stopParseStatusPolling() {
|
|
|
|
|
- if (parseStatusTimer) {
|
|
|
|
|
- clearInterval(parseStatusTimer)
|
|
|
|
|
- parseStatusTimer = null
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function handleAiSubmit() {
|
|
|
|
|
- if (!aiInput.value.trim()) {
|
|
|
|
|
- ElMessage.warning('请输入您的需求')
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
- ElMessage.info('AI 功能开发中...')
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 使用模板(检查解析状态)
|
|
|
|
|
-function useTemplate(tpl) {
|
|
|
|
|
- if (tpl.parseStatus === 'processing' || tpl.parseStatus === 'pending') {
|
|
|
|
|
- ElMessage.warning('模板正在解析中,请稍后再试')
|
|
|
|
|
- // 打开任务中心
|
|
|
|
|
- taskCenterStore.open = true
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
- if (tpl.parseStatus === 'failed') {
|
|
|
|
|
- ElMessage.error('模板解析失败,请重新上传或联系管理员')
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
- router.push(`/editor/${tpl.id}`)
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-async function handleDeleteTemplate(tpl) {
|
|
|
|
|
- try {
|
|
|
|
|
- await ElMessageBox.confirm(
|
|
|
|
|
- `确定要删除模板「${tpl.name}」吗?此操作不可恢复。`,
|
|
|
|
|
- '删除确认',
|
|
|
|
|
- {
|
|
|
|
|
- type: 'warning',
|
|
|
|
|
- confirmButtonText: '删除',
|
|
|
|
|
- cancelButtonText: '取消',
|
|
|
|
|
- confirmButtonClass: 'el-button--danger'
|
|
|
|
|
- }
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- await templateStore.deleteTemplate(tpl.id)
|
|
|
|
|
- ElMessage.success('删除成功')
|
|
|
|
|
- // 刷新统计和模板列表
|
|
|
|
|
- refreshStats()
|
|
|
|
|
- refreshRecommendTemplates()
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- if (error !== 'cancel') {
|
|
|
|
|
- console.error('删除模板失败:', error)
|
|
|
|
|
- ElMessage.error('删除失败')
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-async function handleCreateTemplate() {
|
|
|
|
|
- if (!newTemplate.name) {
|
|
|
|
|
- ElMessage.warning('请输入模板名称')
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- creating.value = true
|
|
|
|
|
- try {
|
|
|
|
|
- const template = await templateStore.createTemplate({
|
|
|
|
|
- name: newTemplate.name,
|
|
|
|
|
- description: newTemplate.description || ''
|
|
|
|
|
- })
|
|
|
|
|
- showCreateDialog.value = false
|
|
|
|
|
- // 重置表单
|
|
|
|
|
- Object.assign(newTemplate, { name: '', description: '' })
|
|
|
|
|
- ElMessage.success('模板创建成功')
|
|
|
|
|
- // 刷新统计和列表
|
|
|
|
|
- refreshStats()
|
|
|
|
|
- refreshRecommendTemplates()
|
|
|
|
|
- router.push(`/editor/${template.id}`)
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- ElMessage.error('创建失败: ' + error.message)
|
|
|
|
|
- } finally {
|
|
|
|
|
- creating.value = false
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function beforeUpload(file) {
|
|
|
|
|
- // 检查用户是否已登录
|
|
|
|
|
- if (!userId.value) {
|
|
|
|
|
- ElMessage.warning('请先登录后再上传文件')
|
|
|
|
|
- router.push('/login')
|
|
|
|
|
- return false
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 检查文件类型(使用扩展名判断,更可靠)
|
|
|
|
|
- const fileName = file.name.toLowerCase()
|
|
|
|
|
- const allowedExtensions = ['.doc', '.docx', '.pdf']
|
|
|
|
|
- const hasValidExtension = allowedExtensions.some(ext => fileName.endsWith(ext))
|
|
|
|
|
- if (!hasValidExtension) {
|
|
|
|
|
- ElMessage.error('仅支持 Word (.doc, .docx) 和 PDF 格式')
|
|
|
|
|
- return false
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 检查文件大小(最大 100MB)
|
|
|
|
|
- const maxSize = 100 * 1024 * 1024
|
|
|
|
|
- if (file.size > maxSize) {
|
|
|
|
|
- ElMessage.error('文件大小不能超过 100MB')
|
|
|
|
|
- return false
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return true
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function handleUploadSuccess(response) {
|
|
|
|
|
- if (response.code === 200) {
|
|
|
|
|
- uploadTemplate.baseDocumentId = response.data.documentId
|
|
|
|
|
- ElMessage.success('文档上传成功')
|
|
|
|
|
- } else {
|
|
|
|
|
- ElMessage.error('上传失败: ' + (response.msg || '未知错误'))
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function handleUploadError(error) {
|
|
|
|
|
- ElMessage.error('上传失败: ' + error.message)
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-async function handleUploadTemplate() {
|
|
|
|
|
- if (!uploadTemplate.name) {
|
|
|
|
|
- ElMessage.warning('请输入模板名称')
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
- if (!uploadTemplate.baseDocumentId) {
|
|
|
|
|
- ElMessage.warning('请上传基础文档')
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- uploading.value = true
|
|
|
|
|
- try {
|
|
|
|
|
- const documentId = uploadTemplate.baseDocumentId
|
|
|
|
|
- await templateStore.createTemplate({
|
|
|
|
|
- name: uploadTemplate.name,
|
|
|
|
|
- description: uploadTemplate.description || '',
|
|
|
|
|
- baseDocumentId: documentId
|
|
|
|
|
- })
|
|
|
|
|
- showUploadDialog.value = false
|
|
|
|
|
- // 重置表单
|
|
|
|
|
- Object.assign(uploadTemplate, { name: '', description: '', baseDocumentId: '' })
|
|
|
|
|
-
|
|
|
|
|
- // 触发文档解析
|
|
|
|
|
- try {
|
|
|
|
|
- await parseApi.startParse(documentId)
|
|
|
|
|
- ElMessage.success('模板创建成功,正在解析文档,请在任务中心查看进度')
|
|
|
|
|
- // 打开任务中心并追踪此文档的解析任务
|
|
|
|
|
- taskCenterStore.notifyTaskStarted({ documentId })
|
|
|
|
|
- } catch (parseError) {
|
|
|
|
|
- console.error('启动解析失败:', parseError)
|
|
|
|
|
- ElMessage.warning('模板创建成功,但解析启动失败,请稍后手动触发')
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 刷新统计和模板列表
|
|
|
|
|
- refreshStats()
|
|
|
|
|
- refreshRecommendTemplates()
|
|
|
|
|
- // 不自动跳转编辑页面,等解析完成后用户自行进入
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- ElMessage.error('创建失败: ' + error.message)
|
|
|
|
|
- } finally {
|
|
|
|
|
- uploading.value = false
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-</script>
|
|
|
|
|
-
|
|
|
|
|
-<style lang="scss" scoped>
|
|
|
|
|
-.home-page {
|
|
|
|
|
- max-width: 1200px;
|
|
|
|
|
- margin: 0 auto;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 欢迎区
|
|
|
|
|
-.welcome-section {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- justify-content: space-between;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- margin-bottom: 20px;
|
|
|
|
|
- padding: 24px;
|
|
|
|
|
- background: var(--white);
|
|
|
|
|
- border-radius: 12px;
|
|
|
|
|
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
|
|
|
|
-
|
|
|
|
|
- .welcome-left {
|
|
|
|
|
- h1 {
|
|
|
|
|
- font-size: 24px;
|
|
|
|
|
- font-weight: 600;
|
|
|
|
|
- margin-bottom: 4px;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .gradient-text {
|
|
|
|
|
- background: var(--ai-gradient);
|
|
|
|
|
- -webkit-background-clip: text;
|
|
|
|
|
- -webkit-text-fill-color: transparent;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- p {
|
|
|
|
|
- color: var(--text-2);
|
|
|
|
|
- margin: 0;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .welcome-actions {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- gap: 12px;
|
|
|
|
|
-
|
|
|
|
|
- .el-button {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- gap: 6px;
|
|
|
|
|
- height: 44px;
|
|
|
|
|
- padding: 0 20px;
|
|
|
|
|
- font-size: 14px;
|
|
|
|
|
- font-weight: 500;
|
|
|
|
|
- border-radius: 8px;
|
|
|
|
|
-
|
|
|
|
|
- .action-icon {
|
|
|
|
|
- font-size: 16px;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .el-button--primary {
|
|
|
|
|
- background: var(--primary-gradient);
|
|
|
|
|
- border: none;
|
|
|
|
|
-
|
|
|
|
|
- &:hover {
|
|
|
|
|
- box-shadow: 0 4px 12px rgba(24, 144, 255, 0.4);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.stats-row {
|
|
|
|
|
- margin-bottom: 20px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// AI 对话入口 - 使用全局样式
|
|
|
|
|
-.ai-card {
|
|
|
|
|
- .ai-input {
|
|
|
|
|
- margin-bottom: 14px;
|
|
|
|
|
-
|
|
|
|
|
- :deep(.el-input__wrapper) {
|
|
|
|
|
- border-radius: 23px;
|
|
|
|
|
- padding: 0 10px 0 18px;
|
|
|
|
|
- height: 46px;
|
|
|
|
|
- border: 2px solid var(--border);
|
|
|
|
|
- box-shadow: none;
|
|
|
|
|
-
|
|
|
|
|
- &:hover, &.is-focus {
|
|
|
|
|
- border-color: var(--primary);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .thinking-modes {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- justify-content: center;
|
|
|
|
|
-
|
|
|
|
|
- :deep(.el-radio-group) {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- gap: 8px;
|
|
|
|
|
- flex-wrap: wrap;
|
|
|
|
|
- justify-content: center;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- :deep(.el-radio-button) {
|
|
|
|
|
- // 移除默认的连接样式
|
|
|
|
|
- &:first-child .el-radio-button__inner {
|
|
|
|
|
- border-radius: 18px;
|
|
|
|
|
- border-left: 1px solid var(--border);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- &:last-child .el-radio-button__inner {
|
|
|
|
|
- border-radius: 18px;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .el-radio-button__inner {
|
|
|
|
|
- border-radius: 18px;
|
|
|
|
|
- border: 1px solid var(--border);
|
|
|
|
|
- background: var(--bg);
|
|
|
|
|
- font-size: 12px;
|
|
|
|
|
- padding: 8px 16px;
|
|
|
|
|
- box-shadow: none;
|
|
|
|
|
- transition: all 0.2s;
|
|
|
|
|
-
|
|
|
|
|
- &:hover {
|
|
|
|
|
- border-color: var(--primary);
|
|
|
|
|
- color: var(--primary);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- &.is-active .el-radio-button__inner {
|
|
|
|
|
- background: var(--primary-light);
|
|
|
|
|
- border-color: var(--primary);
|
|
|
|
|
- color: var(--primary);
|
|
|
|
|
- box-shadow: none;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 区块
|
|
|
|
|
-.section {
|
|
|
|
|
- margin-bottom: 20px;
|
|
|
|
|
-
|
|
|
|
|
- .section-header {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- justify-content: space-between;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- margin-bottom: 14px;
|
|
|
|
|
-
|
|
|
|
|
- h2 {
|
|
|
|
|
- font-size: 15px;
|
|
|
|
|
- font-weight: 600;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 模板卡片
|
|
|
|
|
-.tpl-card {
|
|
|
|
|
- position: relative;
|
|
|
|
|
- margin-bottom: 16px;
|
|
|
|
|
-
|
|
|
|
|
- &.tpl-parsing {
|
|
|
|
|
- .tpl-preview {
|
|
|
|
|
- filter: grayscale(30%);
|
|
|
|
|
- opacity: 0.6;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .tpl-delete-btn {
|
|
|
|
|
- position: absolute;
|
|
|
|
|
- top: 8px;
|
|
|
|
|
- right: 8px;
|
|
|
|
|
- opacity: 0;
|
|
|
|
|
- transition: opacity 0.2s;
|
|
|
|
|
- z-index: 10;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- &:hover .tpl-delete-btn {
|
|
|
|
|
- opacity: 1;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .tpl-status-badge {
|
|
|
|
|
- position: absolute;
|
|
|
|
|
- top: 12px;
|
|
|
|
|
- left: 12px;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- gap: 4px;
|
|
|
|
|
- padding: 4px 10px;
|
|
|
|
|
- border-radius: 12px;
|
|
|
|
|
- font-size: 11px;
|
|
|
|
|
- font-weight: 500;
|
|
|
|
|
- z-index: 5;
|
|
|
|
|
-
|
|
|
|
|
- .el-icon {
|
|
|
|
|
- font-size: 12px;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- &.parsing {
|
|
|
|
|
- background: var(--primary);
|
|
|
|
|
- color: white;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- &.failed {
|
|
|
|
|
- background: var(--danger);
|
|
|
|
|
- color: white;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .tpl-tags {
|
|
|
|
|
- .tpl-tag {
|
|
|
|
|
- &.parsing {
|
|
|
|
|
- background: var(--primary-light);
|
|
|
|
|
- color: var(--primary);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- &.failed {
|
|
|
|
|
- background: #fff1f0;
|
|
|
|
|
- color: var(--danger);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .tpl-btn {
|
|
|
|
|
- width: 100%;
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 上传区域
|
|
|
|
|
-.upload-area {
|
|
|
|
|
- width: 100%;
|
|
|
|
|
-
|
|
|
|
|
- :deep(.el-upload-dragger) {
|
|
|
|
|
- border: 2px dashed var(--border);
|
|
|
|
|
- border-radius: 10px;
|
|
|
|
|
-
|
|
|
|
|
- &:hover {
|
|
|
|
|
- border-color: var(--primary);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .upload-success {
|
|
|
|
|
- text-align: center;
|
|
|
|
|
- padding: 20px;
|
|
|
|
|
- color: var(--success);
|
|
|
|
|
-
|
|
|
|
|
- div {
|
|
|
|
|
- margin-top: 8px;
|
|
|
|
|
- font-size: 14px;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-</style>
|
|
|