소스 검색

refactor(frontend): 重构 UI 样式,参考 V2 原型设计

主要变更:
- 更新全局 CSS 变量,增加阴影、圆角等设计令牌
- 重构左侧面板样式:Tab 使用填充式圆角设计,报告列表卡片化
- 重构中间编辑区:欢迎页视觉增强,编辑器标题栏优化
- 重构右侧面板:要素管理区 Tab 切换样式,标签形状优化
- 重构工具栏:按钮渐变背景,间距和交互效果增强
- 优化右键菜单、弹出框等组件样式
- 删除未使用的页面:Home、Templates、Generations 等
- 简化路由配置,编辑器页面作为主入口
何文松 3 주 전
부모
커밋
748fa6d818

+ 51 - 162
frontend/vue-demo/src/App.vue

@@ -5,30 +5,28 @@
       <router-view />
     </template>
     
-    <!-- 正常布局 -->
+    <!-- 正常布局:无侧边栏,编辑器全宽 -->
     <div v-else class="app-container">
       <!-- 顶部导航 -->
       <header class="app-header">
         <div class="header-left">
-          <div class="logo" @click="router.push('/')">
-            <div class="logo-icon">📊</div>
+          <div class="logo">
+            <div class="logo-icon"></div>
             <span>灵越智报</span>
           </div>
-          <el-input
-            v-model="searchKeyword"
-            placeholder="搜索报告、模板..."
-            prefix-icon="Search"
-            class="search-input"
-            clearable
-          />
         </div>
         <div class="header-right">
+          <el-button class="header-icon-btn" circle>
+            <el-icon><Search /></el-icon>
+          </el-button>
           <el-badge :value="3" class="notification-badge">
-            <el-button :icon="Bell" circle />
+            <el-button class="header-icon-btn" circle>
+              <el-icon><Bell /></el-icon>
+            </el-button>
           </el-badge>
           <el-dropdown trigger="click" @command="handleUserCommand">
             <div class="user-menu">
-              <el-avatar :size="32" class="user-avatar">{{ userInitial }}</el-avatar>
+              <el-avatar :size="28" class="user-avatar">{{ userInitial }}</el-avatar>
               <span class="user-name">{{ username }}</span>
             </div>
             <template #dropdown>
@@ -42,37 +40,9 @@
         </div>
       </header>
 
-      <!-- 主体区域 -->
+      <!-- 主体区域:全宽编辑器 -->
       <div class="app-body">
-        <!-- 侧边栏 -->
-        <aside class="app-sidebar" v-if="!isEditorPage">
-          <el-menu
-            :default-active="currentRoute"
-            router
-            class="sidebar-menu"
-          >
-            <el-menu-item index="/">
-              <el-icon><HomeFilled /></el-icon>
-              <span>首页</span>
-            </el-menu-item>
-            <el-menu-item index="/templates">
-              <el-icon><Files /></el-icon>
-              <span>模板管理</span>
-            </el-menu-item>
-            <el-menu-item index="/generations">
-              <el-icon><Document /></el-icon>
-              <span>生成记录</span>
-            </el-menu-item>
-            <el-divider />
-            <el-menu-item index="/help">
-              <el-icon><QuestionFilled /></el-icon>
-              <span>帮助中心</span>
-            </el-menu-item>
-          </el-menu>
-        </aside>
-
-        <!-- 内容区 -->
-        <main class="app-main" :class="{ 'full-width': isEditorPage }">
+        <main class="app-main">
           <router-view />
         </main>
       </div>
@@ -85,9 +55,9 @@
 </template>
 
 <script setup>
-import { ref, computed } from 'vue'
+import { computed } from 'vue'
 import { useRouter, useRoute } from 'vue-router'
-import { Bell, HomeFilled, Files, Document, QuestionFilled } from '@element-plus/icons-vue'
+import { Bell, Search } from '@element-plus/icons-vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
 import TaskCenterFab from '@/components/TaskCenter/TaskCenterFab.vue'
@@ -96,10 +66,6 @@ import TaskCenterPanel from '@/components/TaskCenter/TaskCenterPanel.vue'
 const router = useRouter()
 const route = useRoute()
 
-const searchKeyword = ref('')
-
-const currentRoute = computed(() => route.path)
-const isEditorPage = computed(() => route.path.startsWith('/editor'))
 const hideLayout = computed(() => route.meta.hideLayout === true)
 
 // 用户信息
@@ -142,7 +108,7 @@ function handleUserCommand(command) {
 }
 
 // ==========================================
-// 全局头部 - 匹配原型
+// 全局头部 - 参考 V2 设计
 // ==========================================
 .app-header {
   position: fixed;
@@ -155,73 +121,62 @@ function handleUserCommand(command) {
   display: flex;
   align-items: center;
   justify-content: space-between;
-  padding: 0 20px;
+  padding: 0 18px;
   z-index: 1000;
 }
 
 .header-left {
   display: flex;
   align-items: center;
-  gap: 20px;
+  gap: 10px;
 }
 
 .logo {
   display: flex;
   align-items: center;
-  gap: 8px;
-  font-size: 17px;
+  gap: 10px;
+  font-size: 16px;
   font-weight: 600;
-  color: var(--primary);
-  cursor: pointer;
+  color: var(--text-1);
 
   .logo-icon {
-    width: 32px;
-    height: 32px;
-    background: var(--primary-gradient);
+    width: 36px;
+    height: 36px;
+    background: linear-gradient(135deg, var(--primary) 0%, #69c0ff 100%);
     border-radius: 8px;
     display: flex;
     align-items: center;
     justify-content: center;
     color: white;
+    font-weight: 700;
     font-size: 18px;
   }
 }
 
-.search-input {
-  width: 320px;
-  
-  :deep(.el-input__wrapper) {
-    border-radius: 18px;
-    background: var(--bg);
-    box-shadow: none;
-    border: 1px solid var(--border);
-    
-    &:hover, &.is-focus {
-      background: var(--white);
-      border-color: var(--primary);
-    }
-  }
-}
-
 .header-right {
   display: flex;
   align-items: center;
-  gap: 10px;
+  gap: 8px;
+}
+
+.header-icon-btn {
+  width: 32px;
+  height: 32px;
+  border: none;
+  background: transparent;
+  font-size: 16px;
+  color: #8c8c8c;
+  
+  &:hover {
+    background: #f0f0f0;
+    color: #555;
+  }
 }
 
 .notification-badge {
-  .el-button {
-    width: 36px;
-    height: 36px;
-    border: none;
-    background: transparent;
-    font-size: 18px;
-    color: var(--text-2);
-    
-    &:hover {
-      background: var(--bg);
-      color: var(--primary);
-    }
+  :deep(.el-badge__content) {
+    top: 2px;
+    right: 4px;
   }
 }
 
@@ -232,6 +187,7 @@ function handleUserCommand(command) {
   cursor: pointer;
   padding: 4px 8px 4px 4px;
   border-radius: 20px;
+  margin-left: 4px;
   
   &:hover {
     background: var(--bg);
@@ -239,99 +195,32 @@ function handleUserCommand(command) {
 }
 
 .user-avatar {
-  width: 30px;
-  height: 30px;
-  background: var(--primary-gradient);
-  font-weight: 600;
+  width: 28px;
+  height: 28px;
+  background: var(--primary);
+  font-weight: 700;
   font-size: 12px;
 }
 
 .user-name {
   font-size: 13px;
   font-weight: 500;
+  color: var(--text-1);
 }
 
 // ==========================================
-// 侧边栏 - 匹配原型
+// 主体区域:全宽
 // ==========================================
 .app-body {
   flex: 1;
   display: flex;
   overflow: hidden;
-  margin-top: 56px; // 为固定头部留出空间
-}
-
-.app-sidebar {
-  width: 200px;
-  background: var(--white);
-  border-right: 1px solid var(--border);
-  flex-shrink: 0;
-  display: flex;
-  flex-direction: column;
-  transition: width 0.3s;
-
-  .sidebar-menu {
-    border-right: none;
-    height: 100%;
-    padding: 10px;
-    
-    // 菜单项样式
-    .el-menu-item {
-      height: 40px;
-      line-height: 40px;
-      border-radius: 8px;
-      margin-bottom: 2px;
-      padding: 0 12px !important;
-      font-size: 13px;
-      font-weight: 500;
-      position: relative;
-      
-      &:hover {
-        background: var(--primary-light);
-        color: var(--primary);
-      }
-      
-      &.is-active {
-        background: var(--primary-light);
-        color: var(--primary);
-        
-        &::before {
-          content: '';
-          position: absolute;
-          left: 0;
-          top: 50%;
-          transform: translateY(-50%);
-          width: 3px;
-          height: 18px;
-          background: var(--primary);
-          border-radius: 0 2px 2px 0;
-        }
-      }
-      
-      .el-icon {
-        font-size: 16px;
-        margin-right: 10px;
-      }
-    }
-    
-    // 分隔线
-    .el-divider {
-      margin: 10px 0;
-    }
-  }
+  margin-top: 56px;
 }
 
-// ==========================================
-// 主内容区
-// ==========================================
 .app-main {
   flex: 1;
-  overflow-y: auto;
+  overflow: hidden;
   background: var(--bg);
-  padding: 20px;
-
-  &.full-width {
-    padding: 0;
-  }
 }
 </style>

+ 11 - 2
frontend/vue-demo/src/assets/main.scss

@@ -1,8 +1,8 @@
 // ==========================================
-// 灵越智报 - 全局样式(匹配原型 UI)
+// 灵越智报 - 全局样式(匹配 V2 原型 UI)
 // ==========================================
 
-// 全局 CSS 变量(与原型保持一致)
+// 全局 CSS 变量(与 V2 原型保持一致)
 :root {
   // 主色调
   --primary: #1890ff;
@@ -28,6 +28,15 @@
   // 渐变色
   --ai-gradient: linear-gradient(135deg, #1890ff 0%, #722ed1 100%);
   --data-gradient: linear-gradient(135deg, #52c41a 0%, #13c2c2 100%);
+  
+  // V2 新增
+  --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.06);
+  --shadow-md: 0 6px 18px rgba(17, 24, 39, 0.06);
+  --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.15);
+  --radius-sm: 6px;
+  --radius-md: 10px;
+  --radius-lg: 14px;
+  --radius-full: 9999px;
 }
 
 // 重置样式

+ 7 - 29
frontend/vue-demo/src/router/index.js

@@ -14,42 +14,20 @@ const routes = [
     component: () => import('@/views/Register.vue'),
     meta: { requiresAuth: false, hideLayout: true }
   },
-  // 业务页面(需要登录
+  // 主页面:编辑器(登录后直接进入
   {
     path: '/',
-    name: 'Home',
-    component: () => import('@/views/Home.vue'),
-    meta: { requiresAuth: true }
-  },
-  {
-    path: '/templates',
-    name: 'Templates',
-    component: () => import('@/views/Templates.vue'),
-    meta: { requiresAuth: true }
-  },
-  {
-    path: '/templates/:id',
-    name: 'TemplateDetail',
-    component: () => import('@/views/TemplateDetail.vue'),
-    meta: { requiresAuth: true }
-  },
-  {
-    path: '/editor/:templateId',
     name: 'Editor',
     component: () => import('@/views/Editor.vue'),
     meta: { requiresAuth: true }
   },
+  // 兼容旧路由:/editor/:documentId 重定向到根路径
   {
-    path: '/generations',
-    name: 'Generations',
-    component: () => import('@/views/Generations.vue'),
-    meta: { requiresAuth: true }
-  },
-  {
-    path: '/generations/:id',
-    name: 'GenerationDetail',
-    component: () => import('@/views/GenerationDetail.vue'),
-    meta: { requiresAuth: true }
+    path: '/editor/:documentId?',
+    redirect: to => {
+      const docId = to.params.documentId
+      return docId ? { path: '/', query: { doc: docId } } : '/'
+    }
   }
 ]
 

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 658 - 144
frontend/vue-demo/src/views/Editor.vue


+ 0 - 381
frontend/vue-demo/src/views/GenerationDetail.vue

@@ -1,381 +0,0 @@
-<template>
-  <div class="generation-detail-page">
-    <div class="page-header">
-      <div class="header-left">
-        <el-button :icon="ArrowLeft" @click="router.back()">返回</el-button>
-        <h1>{{ generation?.name || '生成任务详情' }}</h1>
-        <el-tag :type="getStatusType(generation?.status)" size="large">
-          {{ getStatusLabel(generation?.status) }}
-        </el-tag>
-      </div>
-      <div class="header-right">
-        <el-button v-if="generation?.status === 'pending'" @click="executeExtraction">
-          开始提取
-        </el-button>
-        <el-button v-if="generation?.status === 'review'" type="success" @click="confirmGeneration">
-          确认生成
-        </el-button>
-        <el-button v-if="generation?.status === 'completed'" type="primary" @click="downloadDocument">
-          下载文档
-        </el-button>
-      </div>
-    </div>
-
-    <el-row :gutter="20">
-      <el-col :span="16">
-        <!-- 变量提取结果 -->
-        <div class="card section">
-          <div class="section-header">
-            <h3>变量提取结果</h3>
-            <el-tag v-if="generation?.status === 'extracting'">
-              提取中 {{ generation?.progress }}%
-            </el-tag>
-          </div>
-
-          <el-progress
-            v-if="generation?.status === 'extracting'"
-            :percentage="generation?.progress"
-            :stroke-width="8"
-            class="progress-bar"
-          />
-
-          <el-table :data="variableValues" stripe>
-            <el-table-column prop="displayName" label="变量名" width="150" />
-            <el-table-column prop="value" label="提取值">
-              <template #default="{ row }">
-                <el-input
-                  v-if="generation?.status === 'review'"
-                  v-model="row.value"
-                  size="small"
-                  @change="markAsModified(row)"
-                />
-                <span v-else>{{ row.value || '-' }}</span>
-              </template>
-            </el-table-column>
-            <el-table-column prop="confidence" label="置信度" width="100">
-              <template #default="{ row }">
-                <el-progress
-                  :percentage="row.confidence * 100"
-                  :stroke-width="6"
-                  :color="getConfidenceColor(row.confidence)"
-                  :show-text="false"
-                />
-              </template>
-            </el-table-column>
-            <el-table-column prop="status" label="状态" width="100">
-              <template #default="{ row }">
-                <el-tag :type="getVariableStatusType(row.status)" size="small">
-                  {{ getVariableStatusLabel(row.status) }}
-                </el-tag>
-              </template>
-            </el-table-column>
-          </el-table>
-        </div>
-      </el-col>
-
-      <el-col :span="8">
-        <!-- 基本信息 -->
-        <div class="card section">
-          <h3>任务信息</h3>
-          <div class="info-item">
-            <span class="info-label">使用模板</span>
-            <span class="info-value">{{ generation?.templateName }}</span>
-          </div>
-          <div class="info-item">
-            <span class="info-label">创建时间</span>
-            <span class="info-value">{{ generation?.createTime }}</span>
-          </div>
-          <div class="info-item">
-            <span class="info-label">变量数量</span>
-            <span class="info-value">{{ variableValues.length }}</span>
-          </div>
-          <div class="info-item">
-            <span class="info-label">任务状态</span>
-            <span class="info-value">
-              <el-tag :type="getStatusType(generation?.status)" size="small">
-                {{ getStatusLabel(generation?.status) }}
-              </el-tag>
-            </span>
-          </div>
-        </div>
-
-        <!-- 来源文件 -->
-        <div class="card section">
-          <h3>来源文件</h3>
-          <div class="source-file-list">
-            <div v-for="(docId, alias) in sourceFileMap" :key="alias" class="source-file-item">
-              <span class="sf-icon">📄</span>
-              <div class="sf-info">
-                <div class="sf-name">{{ alias }}</div>
-                <div class="sf-id">{{ docId }}</div>
-              </div>
-              <el-button size="small" :icon="View">查看</el-button>
-            </div>
-          </div>
-        </div>
-      </el-col>
-    </el-row>
-  </div>
-</template>
-
-<script setup>
-import { ref, onMounted } from 'vue'
-import { useRouter, useRoute } from 'vue-router'
-import { ArrowLeft, View } from '@element-plus/icons-vue'
-import { ElMessage } from 'element-plus'
-import { generationApi } from '@/api'
-
-const router = useRouter()
-const route = useRoute()
-const generationId = route.params.id
-
-const loading = ref(false)
-
-// 从 API 获取数据
-const generation = ref(null)
-const sourceFileMap = ref({})
-const variableValues = ref([])
-
-onMounted(async () => {
-  await fetchGenerationDetail()
-})
-
-async function fetchGenerationDetail() {
-  loading.value = true
-  try {
-    const data = await generationApi.getById(generationId)
-    generation.value = {
-      ...data,
-      templateName: data.templateName || '未知模板',
-      progress: data.status === 'completed' ? 100 : (data.status === 'extracting' ? 50 : 0)
-    }
-    
-    // 解析来源文件映射
-    sourceFileMap.value = data.sourceFileMap || {}
-    
-    // 解析提取结果
-    const extractedValues = data.extractedValues || {}
-    variableValues.value = Object.entries(extractedValues).map(([name, val]) => ({
-      name,
-      displayName: val.displayName || name,
-      value: val.value || '',
-      confidence: val.confidence || 0,
-      status: val.value ? 'extracted' : 'pending'
-    }))
-  } catch (error) {
-    console.error('获取生成任务详情失败:', error)
-    ElMessage.error('获取生成任务详情失败')
-  } finally {
-    loading.value = false
-  }
-}
-
-function getStatusType(status) {
-  const map = {
-    pending: 'info',
-    extracting: 'warning',
-    review: '',
-    completed: 'success',
-    error: 'danger'
-  }
-  return map[status] || 'info'
-}
-
-function getStatusLabel(status) {
-  const map = {
-    pending: '待执行',
-    extracting: '提取中',
-    review: '待确认',
-    completed: '已完成',
-    error: '错误'
-  }
-  return map[status] || status
-}
-
-function getVariableStatusType(status) {
-  const map = {
-    extracted: 'success',
-    modified: 'warning',
-    low_confidence: '',
-    pending: 'info',
-    error: 'danger'
-  }
-  return map[status] || 'info'
-}
-
-function getVariableStatusLabel(status) {
-  const map = {
-    extracted: '已提取',
-    modified: '已修改',
-    low_confidence: '低置信',
-    pending: '待提取',
-    error: '错误'
-  }
-  return map[status] || status
-}
-
-function getConfidenceColor(confidence) {
-  if (confidence >= 0.8) return '#52c41a'
-  if (confidence >= 0.5) return '#faad14'
-  return '#ff4d4f'
-}
-
-function markAsModified(row) {
-  row.status = 'modified'
-}
-
-async function executeExtraction() {
-  try {
-    generation.value.status = 'extracting'
-    generation.value.progress = 0
-    
-    await generationApi.execute(generationId)
-    
-    // 轮询进度
-    const interval = setInterval(async () => {
-      try {
-        const progress = await generationApi.getProgress(generationId)
-        generation.value.progress = progress.progress || 0
-        generation.value.status = progress.status
-        
-        if (progress.status === 'review' || progress.status === 'completed' || progress.status === 'error') {
-          clearInterval(interval)
-          await fetchGenerationDetail() // 重新获取完整数据
-          ElMessage.success('变量提取完成')
-        }
-      } catch {
-        clearInterval(interval)
-      }
-    }, 2000)
-  } catch (error) {
-    ElMessage.error('执行失败: ' + error.message)
-    generation.value.status = 'error'
-  }
-}
-
-async function confirmGeneration() {
-  try {
-    await generationApi.confirm(generationId)
-    generation.value.status = 'completed'
-    ElMessage.success('文档生成成功')
-  } catch (error) {
-    ElMessage.error('生成失败: ' + error.message)
-  }
-}
-
-function downloadDocument() {
-  const url = generationApi.getDownloadUrl(generationId)
-  window.open(url, '_blank')
-}
-</script>
-
-<style lang="scss" scoped>
-.generation-detail-page {
-  max-width: 1200px;
-  margin: 0 auto;
-}
-
-.page-header {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  margin-bottom: 20px;
-
-  .header-left {
-    display: flex;
-    align-items: center;
-    gap: 12px;
-
-    h1 {
-      font-size: 20px;
-      margin: 0;
-    }
-  }
-
-  .header-right {
-    display: flex;
-    gap: 8px;
-  }
-}
-
-.section {
-  padding: 20px;
-  margin-bottom: 20px;
-
-  h3 {
-    font-size: 15px;
-    margin-bottom: 16px;
-    padding-bottom: 10px;
-    border-bottom: 1px solid var(--border);
-  }
-
-  .section-header {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    margin-bottom: 16px;
-    padding-bottom: 10px;
-    border-bottom: 1px solid var(--border);
-
-    h3 {
-      margin: 0;
-      border: none;
-      padding: 0;
-    }
-  }
-}
-
-.progress-bar {
-  margin-bottom: 20px;
-}
-
-.info-item {
-  display: flex;
-  justify-content: space-between;
-  padding: 10px 0;
-  border-bottom: 1px solid var(--border);
-
-  &:last-child {
-    border-bottom: none;
-  }
-
-  .info-label {
-    color: var(--text-2);
-  }
-
-  .info-value {
-    font-weight: 500;
-  }
-}
-
-.source-file-list {
-  .source-file-item {
-    display: flex;
-    align-items: center;
-    gap: 12px;
-    padding: 12px;
-    background: var(--bg);
-    border-radius: 8px;
-    margin-bottom: 8px;
-
-    .sf-icon {
-      font-size: 24px;
-    }
-
-    .sf-info {
-      flex: 1;
-
-      .sf-name {
-        font-weight: 600;
-        margin-bottom: 2px;
-      }
-
-      .sf-id {
-        font-size: 11px;
-        color: var(--text-3);
-        font-family: monospace;
-      }
-    }
-  }
-}
-</style>

+ 0 - 428
frontend/vue-demo/src/views/Generations.vue

@@ -1,428 +0,0 @@
-<template>
-  <div class="generations-page">
-    <div class="page-header">
-      <h1>📋 生成记录</h1>
-      <el-button type="primary" :icon="Plus" @click="showCreateDialog = true">
-        新建生成任务
-      </el-button>
-    </div>
-
-    <!-- 筛选 -->
-    <div class="filter-bar">
-      <el-select v-model="statusFilter" placeholder="全部状态" clearable style="width: 140px">
-        <el-option label="待执行" value="pending" />
-        <el-option label="提取中" value="extracting" />
-        <el-option label="待确认" value="review" />
-        <el-option label="已完成" value="completed" />
-        <el-option label="错误" value="error" />
-      </el-select>
-      <el-input
-        v-model="searchKeyword"
-        placeholder="🔍 搜索..."
-        clearable
-        style="width: 240px"
-      />
-      <el-radio-group v-model="timeFilter" style="margin-left: auto">
-        <el-radio-button label="all">全部</el-radio-button>
-        <el-radio-button label="week">本周</el-radio-button>
-        <el-radio-button label="month">本月</el-radio-button>
-      </el-radio-group>
-    </div>
-
-    <!-- 生成记录列表 -->
-    <div class="generation-list" v-loading="loading">
-      <div
-        v-for="gen in filteredGenerations"
-        :key="gen.id"
-        class="generation-card card"
-      >
-        <div class="gen-header">
-          <span class="gen-icon">📄</span>
-          <span class="gen-name">{{ gen.name }}</span>
-          <el-tag :type="getStatusType(gen.status)" size="small">
-            {{ getStatusLabel(gen.status) }}
-          </el-tag>
-        </div>
-        <div class="gen-meta">
-          <span>📅 {{ gen.createTime }}</span>
-          <span>🎨 {{ gen.templateName }}</span>
-          <span>📊 {{ gen.variableCount }} 个变量</span>
-        </div>
-        <div class="gen-progress" v-if="gen.status === 'extracting'">
-          <el-progress :percentage="gen.progress" :stroke-width="6" />
-        </div>
-        <div class="gen-actions">
-          <el-button type="primary" size="small" @click="viewGeneration(gen)">
-            查看详情
-          </el-button>
-          <el-button
-            v-if="gen.status === 'pending'"
-            size="small"
-            @click="executeGeneration(gen)"
-          >
-            开始提取
-          </el-button>
-          <el-button
-            v-if="gen.status === 'review'"
-            type="success"
-            size="small"
-            @click="confirmGeneration(gen)"
-          >
-            确认生成
-          </el-button>
-          <el-button
-            v-if="gen.status === 'completed'"
-            size="small"
-            @click="downloadGeneration(gen)"
-          >
-            下载文档
-          </el-button>
-        </div>
-      </div>
-    </div>
-
-    <!-- 空状态 -->
-    <el-empty v-if="!loading && filteredGenerations.length === 0" description="暂无生成记录">
-      <el-button type="primary" @click="showCreateDialog = true">创建生成任务</el-button>
-    </el-empty>
-
-    <!-- 创建生成任务对话框 -->
-    <el-dialog v-model="showCreateDialog" title="新建生成任务" width="600">
-      <el-form :model="newGeneration" label-width="100px">
-        <el-form-item label="任务名称">
-          <el-input v-model="newGeneration.name" placeholder="可选,默认使用模板名称" />
-        </el-form-item>
-        <el-form-item label="选择模板" required>
-          <el-select v-model="newGeneration.templateId" style="width: 100%" placeholder="请选择模板">
-            <el-option
-              v-for="tpl in templates"
-              :key="tpl.id"
-              :label="tpl.name"
-              :value="tpl.id"
-            />
-          </el-select>
-        </el-form-item>
-        <el-form-item label="来源文件" v-if="selectedTemplate">
-          <div class="source-files-upload">
-            <div
-              v-for="sf in selectedTemplate.sourceFiles"
-              :key="sf.id"
-              class="source-file-item"
-            >
-              <div class="sf-info">
-                <span class="sf-name">{{ sf.alias }}</span>
-                <el-tag size="small" v-if="sf.required" type="danger">必需</el-tag>
-              </div>
-              <el-upload
-                :action="`/api/v1/parse/upload`"
-                :on-success="(res) => handleSourceFileUpload(sf.alias, res)"
-                :show-file-list="false"
-              >
-                <el-button size="small" :icon="Upload">
-                  {{ newGeneration.sourceFileMap[sf.alias] ? '已上传' : '上传文件' }}
-                </el-button>
-              </el-upload>
-            </div>
-          </div>
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <el-button @click="showCreateDialog = false">取消</el-button>
-        <el-button type="primary" @click="createGeneration" :loading="creating">
-          创建
-        </el-button>
-      </template>
-    </el-dialog>
-  </div>
-</template>
-
-<script setup>
-import { ref, reactive, computed, onMounted } from 'vue'
-import { useRouter } from 'vue-router'
-import { Plus, Upload } from '@element-plus/icons-vue'
-import { ElMessage } from 'element-plus'
-import { generationApi, templateApi, sourceFileApi } from '@/api'
-
-const router = useRouter()
-
-const loading = ref(false)
-const creating = ref(false)
-const statusFilter = ref('')
-const searchKeyword = ref('')
-const timeFilter = ref('all')
-const showCreateDialog = ref(false)
-
-// 从 API 获取数据
-const generations = ref([])
-const templates = ref([])
-
-// 加载数据
-onMounted(async () => {
-  await Promise.all([fetchGenerations(), fetchTemplates()])
-})
-
-async function fetchGenerations() {
-  loading.value = true
-  try {
-    const data = await generationApi.list()
-    generations.value = (data.records || data || []).map(g => ({
-      ...g,
-      templateName: g.templateName || '未知模板',
-      variableCount: g.extractedValues ? Object.keys(g.extractedValues).length : 0,
-      progress: g.status === 'completed' ? 100 : (g.status === 'extracting' ? 50 : 0)
-    }))
-  } catch (error) {
-    console.error('获取生成任务失败:', error)
-    ElMessage.error('获取生成任务列表失败')
-  } finally {
-    loading.value = false
-  }
-}
-
-async function fetchTemplates() {
-  try {
-    const data = await templateApi.list()
-    const templateList = data.records || data || []
-    // 获取每个模板的来源文件
-    templates.value = await Promise.all(templateList.map(async (t) => {
-      try {
-        const sourceFiles = await sourceFileApi.list(t.id)
-        return { ...t, sourceFiles: sourceFiles || [] }
-      } catch {
-        return { ...t, sourceFiles: [] }
-      }
-    }))
-  } catch (error) {
-    console.error('获取模板列表失败:', error)
-  }
-}
-
-const newGeneration = reactive({
-  name: '',
-  templateId: '',
-  sourceFileMap: {}
-})
-
-const selectedTemplate = computed(() => {
-  return templates.value.find(t => t.id === newGeneration.templateId)
-})
-
-const filteredGenerations = computed(() => {
-  let result = generations.value
-
-  if (statusFilter.value) {
-    result = result.filter(g => g.status === statusFilter.value)
-  }
-
-  if (searchKeyword.value) {
-    result = result.filter(g =>
-      g.name.toLowerCase().includes(searchKeyword.value.toLowerCase())
-    )
-  }
-
-  return result
-})
-
-function getStatusType(status) {
-  const map = {
-    pending: 'info',
-    extracting: 'warning',
-    review: '',
-    completed: 'success',
-    error: 'danger'
-  }
-  return map[status] || 'info'
-}
-
-function getStatusLabel(status) {
-  const map = {
-    pending: '待执行',
-    extracting: '提取中',
-    review: '待确认',
-    completed: '已完成',
-    error: '错误'
-  }
-  return map[status] || status
-}
-
-function viewGeneration(gen) {
-  router.push(`/generations/${gen.id}`)
-}
-
-async function executeGeneration(gen) {
-  try {
-    gen.status = 'extracting'
-    gen.progress = 0
-    
-    await generationApi.execute(gen.id)
-    
-    // 轮询进度
-    const interval = setInterval(async () => {
-      try {
-        const progress = await generationApi.getProgress(gen.id)
-        gen.progress = progress.progress || 0
-        gen.status = progress.status
-        if (progress.status === 'review' || progress.status === 'completed' || progress.status === 'error') {
-          clearInterval(interval)
-        }
-      } catch {
-        clearInterval(interval)
-      }
-    }, 2000)
-    
-    ElMessage.success('开始提取变量')
-  } catch (error) {
-    ElMessage.error('执行失败: ' + error.message)
-    gen.status = 'error'
-  }
-}
-
-async function confirmGeneration(gen) {
-  try {
-    await generationApi.confirm(gen.id)
-    gen.status = 'completed'
-    ElMessage.success('文档生成成功')
-  } catch (error) {
-    ElMessage.error('生成失败: ' + error.message)
-  }
-}
-
-function downloadGeneration(gen) {
-  const url = generationApi.getDownloadUrl(gen.id)
-  window.open(url, '_blank')
-}
-
-function handleSourceFileUpload(alias, response) {
-  if (response.code === 200) {
-    newGeneration.sourceFileMap[alias] = response.data.id
-    ElMessage.success(`${alias} 上传成功`)
-  }
-}
-
-async function createGeneration() {
-  if (!newGeneration.templateId) {
-    ElMessage.warning('请选择模板')
-    return
-  }
-
-  // 检查必需文件
-  const template = selectedTemplate.value
-  const missingFiles = (template.sourceFiles || [])
-    .filter(sf => sf.required && !newGeneration.sourceFileMap[sf.alias])
-    .map(sf => sf.alias)
-
-  if (missingFiles.length > 0) {
-    ElMessage.warning(`请上传必需文件: ${missingFiles.join(', ')}`)
-    return
-  }
-
-  creating.value = true
-  try {
-    const data = {
-      templateId: newGeneration.templateId,
-      name: newGeneration.name || template.name,
-      sourceFileMap: newGeneration.sourceFileMap
-    }
-    
-    const gen = await generationApi.create(data)
-    
-    // 添加到列表
-    generations.value.unshift({
-      ...gen,
-      templateName: template.name,
-      variableCount: 0,
-      progress: 0
-    })
-    
-    showCreateDialog.value = false
-    ElMessage.success('生成任务创建成功')
-    
-    // 重置表单
-    Object.assign(newGeneration, { name: '', templateId: '', sourceFileMap: {} })
-  } catch (error) {
-    ElMessage.error('创建失败: ' + error.message)
-  } finally {
-    creating.value = false
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-.generations-page {
-  max-width: 1000px;
-  margin: 0 auto;
-}
-
-.filter-bar {
-  display: flex;
-  gap: 12px;
-  margin-bottom: 20px;
-  align-items: center;
-}
-
-.generation-list {
-  display: flex;
-  flex-direction: column;
-  gap: 12px;
-}
-
-.generation-card {
-  padding: 18px;
-
-  .gen-header {
-    display: flex;
-    align-items: center;
-    gap: 12px;
-    margin-bottom: 10px;
-
-    .gen-icon {
-      font-size: 22px;
-    }
-
-    .gen-name {
-      flex: 1;
-      font-size: 15px;
-      font-weight: 600;
-    }
-  }
-
-  .gen-meta {
-    display: flex;
-    gap: 20px;
-    font-size: 12px;
-    color: var(--text-2);
-    margin-bottom: 12px;
-  }
-
-  .gen-progress {
-    margin-bottom: 12px;
-  }
-
-  .gen-actions {
-    display: flex;
-    gap: 8px;
-  }
-}
-
-.source-files-upload {
-  .source-file-item {
-    display: flex;
-    align-items: center;
-    justify-content: space-between;
-    padding: 10px 12px;
-    background: var(--bg);
-    border-radius: 6px;
-    margin-bottom: 8px;
-
-    .sf-info {
-      display: flex;
-      align-items: center;
-      gap: 8px;
-
-      .sf-name {
-        font-weight: 500;
-      }
-    }
-  }
-}
-</style>

+ 0 - 830
frontend/vue-demo/src/views/Home.vue

@@ -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>

+ 0 - 291
frontend/vue-demo/src/views/TemplateDetail.vue

@@ -1,291 +0,0 @@
-<template>
-  <div class="template-detail-page">
-    <div class="page-header">
-      <div class="header-left">
-        <el-button :icon="ArrowLeft" @click="router.back()">返回</el-button>
-        <h1>{{ template?.name || '模板详情' }}</h1>
-        <el-tag :type="getStatusType(template?.status)">
-          {{ getStatusLabel(template?.status) }}
-        </el-tag>
-      </div>
-      <div class="header-right">
-        <el-button @click="router.push(`/editor/${templateId}`)">编辑模板</el-button>
-        <el-button type="primary" @click="useTemplate">使用模板</el-button>
-      </div>
-    </div>
-
-    <el-row :gutter="20">
-      <el-col :span="16">
-        <!-- 基本信息 -->
-        <div class="card section">
-          <h3>基本信息</h3>
-          <el-descriptions :column="2" border>
-            <el-descriptions-item label="模板名称">{{ template?.name }}</el-descriptions-item>
-            <el-descriptions-item label="状态">{{ getStatusLabel(template?.status) }}</el-descriptions-item>
-            <el-descriptions-item label="使用次数">{{ template?.useCount || 0 }} 次</el-descriptions-item>
-            <el-descriptions-item label="是否公开">{{ template?.isPublic ? '是' : '否' }}</el-descriptions-item>
-            <el-descriptions-item label="创建时间" :span="2">{{ template?.createTime }}</el-descriptions-item>
-            <el-descriptions-item label="描述" :span="2">{{ template?.description || '暂无描述' }}</el-descriptions-item>
-          </el-descriptions>
-        </div>
-
-        <!-- 变量列表 -->
-        <div class="card section">
-          <div class="section-header">
-            <h3>变量列表 ({{ variables.length }})</h3>
-          </div>
-          <el-table :data="variables" stripe>
-            <el-table-column prop="displayName" label="显示名称" width="150" />
-            <el-table-column prop="name" label="变量名" width="150" />
-            <el-table-column prop="category" label="类别" width="120">
-              <template #default="{ row }">
-                <el-tag :color="getCategoryColor(row.category)" effect="dark" size="small">
-                  {{ getCategoryLabel(row.category) }}
-                </el-tag>
-              </template>
-            </el-table-column>
-            <el-table-column prop="sourceType" label="来源类型" width="120">
-              <template #default="{ row }">
-                {{ getSourceTypeLabel(row.sourceType) }}
-              </template>
-            </el-table-column>
-            <el-table-column prop="exampleValue" label="示例值" />
-          </el-table>
-        </div>
-      </el-col>
-
-      <el-col :span="8">
-        <!-- 来源文件定义 -->
-        <div class="card section">
-          <h3>来源文件定义 ({{ sourceFiles.length }})</h3>
-          <div class="source-file-list">
-            <div v-for="sf in sourceFiles" :key="sf.id" class="source-file-item">
-              <span class="sf-icon">📄</span>
-              <div class="sf-info">
-                <div class="sf-name">{{ sf.alias }}</div>
-                <div class="sf-desc">{{ sf.description || '暂无描述' }}</div>
-              </div>
-              <el-tag size="small" :type="sf.required ? 'danger' : 'info'">
-                {{ sf.required ? '必需' : '可选' }}
-              </el-tag>
-            </div>
-          </div>
-        </div>
-
-        <!-- 统计信息 -->
-        <div class="card section">
-          <h3>统计信息</h3>
-          <div class="stat-item">
-            <span class="stat-label">变量总数</span>
-            <span class="stat-value">{{ variables.length }}</span>
-          </div>
-          <div class="stat-item">
-            <span class="stat-label">来源文件</span>
-            <span class="stat-value">{{ sourceFiles.length }}</span>
-          </div>
-          <div class="stat-item">
-            <span class="stat-label">生成次数</span>
-            <span class="stat-value">{{ template?.useCount || 0 }}</span>
-          </div>
-        </div>
-      </el-col>
-    </el-row>
-  </div>
-</template>
-
-<script setup>
-import { ref, onMounted } from 'vue'
-import { useRouter, useRoute } from 'vue-router'
-import { ArrowLeft } from '@element-plus/icons-vue'
-import { ElMessage } from 'element-plus'
-import { templateApi, sourceFileApi, variableApi } from '@/api'
-
-const router = useRouter()
-const route = useRoute()
-const templateId = route.params.id
-
-const loading = ref(false)
-
-// 从 API 获取数据
-const template = ref(null)
-const sourceFiles = ref([])
-const variables = ref([])
-
-onMounted(async () => {
-  await fetchTemplateDetail()
-})
-
-async function fetchTemplateDetail() {
-  loading.value = true
-  try {
-    // 并行获取模板详情、来源文件和变量
-    const [templateData, sourceFilesData, variablesData] = await Promise.all([
-      templateApi.getById(templateId),
-      sourceFileApi.list(templateId),
-      variableApi.list(templateId)
-    ])
-    
-    template.value = templateData
-    sourceFiles.value = sourceFilesData || []
-    variables.value = variablesData || []
-  } catch (error) {
-    console.error('获取模板详情失败:', error)
-    ElMessage.error('获取模板详情失败')
-  } finally {
-    loading.value = false
-  }
-}
-
-function getStatusType(status) {
-  const map = { draft: 'info', published: 'success', archived: 'warning' }
-  return map[status] || 'info'
-}
-
-function getStatusLabel(status) {
-  const map = { draft: '草稿', published: '已发布', archived: '已归档' }
-  return map[status] || status
-}
-
-function getCategoryColor(category) {
-  const map = {
-    entity: '#1890ff',
-    concept: '#722ed1',
-    data: '#52c41a',
-    location: '#faad14',
-    asset: '#eb2f96'
-  }
-  return map[category] || '#8c8c8c'
-}
-
-function getCategoryLabel(category) {
-  const map = {
-    entity: '核心实体',
-    concept: '概念/技术',
-    data: '数据/指标',
-    location: '地点/组织',
-    asset: '资源模板'
-  }
-  return map[category] || '其他'
-}
-
-function getSourceTypeLabel(type) {
-  const map = {
-    document: '文档提取',
-    manual: '手动输入',
-    reference: '引用变量',
-    fixed: '固定值'
-  }
-  return map[type] || type
-}
-
-function useTemplate() {
-  router.push(`/generations?templateId=${templateId}`)
-  ElMessage.info('请上传来源文件后开始生成')
-}
-</script>
-
-<style lang="scss" scoped>
-.template-detail-page {
-  max-width: 1200px;
-  margin: 0 auto;
-}
-
-.page-header {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  margin-bottom: 20px;
-
-  .header-left {
-    display: flex;
-    align-items: center;
-    gap: 12px;
-
-    h1 {
-      font-size: 20px;
-      margin: 0;
-    }
-  }
-
-  .header-right {
-    display: flex;
-    gap: 8px;
-  }
-}
-
-.section {
-  padding: 20px;
-  margin-bottom: 20px;
-
-  h3 {
-    font-size: 15px;
-    margin-bottom: 16px;
-    padding-bottom: 10px;
-    border-bottom: 1px solid var(--border);
-  }
-
-  .section-header {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    margin-bottom: 16px;
-    padding-bottom: 10px;
-    border-bottom: 1px solid var(--border);
-
-    h3 {
-      margin: 0;
-      border: none;
-      padding: 0;
-    }
-  }
-}
-
-.source-file-list {
-  .source-file-item {
-    display: flex;
-    align-items: center;
-    gap: 12px;
-    padding: 12px;
-    background: var(--bg);
-    border-radius: 8px;
-    margin-bottom: 8px;
-
-    .sf-icon {
-      font-size: 24px;
-    }
-
-    .sf-info {
-      flex: 1;
-
-      .sf-name {
-        font-weight: 600;
-        margin-bottom: 2px;
-      }
-
-      .sf-desc {
-        font-size: 12px;
-        color: var(--text-3);
-      }
-    }
-  }
-}
-
-.stat-item {
-  display: flex;
-  justify-content: space-between;
-  padding: 10px 0;
-  border-bottom: 1px solid var(--border);
-
-  &:last-child {
-    border-bottom: none;
-  }
-
-  .stat-label {
-    color: var(--text-2);
-  }
-
-  .stat-value {
-    font-weight: 600;
-  }
-}
-</style>

+ 0 - 331
frontend/vue-demo/src/views/Templates.vue

@@ -1,331 +0,0 @@
-<template>
-  <div class="templates-page">
-    <div class="page-header">
-      <h1>🎨 模板管理</h1>
-      <el-button type="primary" :icon="Plus" @click="showCreateDialog = true">
-        创建模板
-      </el-button>
-    </div>
-
-    <!-- 搜索和筛选 -->
-    <div class="filter-bar">
-      <el-input
-        v-model="searchKeyword"
-        placeholder="🔍 搜索模板..."
-        clearable
-        style="width: 280px"
-        @input="handleSearch"
-      />
-      <el-radio-group v-model="filterType" @change="handleFilterChange">
-        <el-radio-button label="all">全部</el-radio-button>
-        <el-radio-button label="official">官方模板</el-radio-button>
-        <el-radio-button label="mine">我的模板</el-radio-button>
-      </el-radio-group>
-    </div>
-
-    <!-- 模板列表 -->
-    <el-row :gutter="16" v-loading="loading">
-      <el-col :span="6" v-for="tpl in filteredTemplates" :key="tpl.id">
-        <div class="tpl-card card">
-          <div class="tpl-preview" @click="goToDetail(tpl)">
-            {{ getTemplateIcon(tpl) }}
-          </div>
-          <div class="tpl-info">
-            <div class="tpl-name">{{ tpl.name }}</div>
-            <div class="tpl-meta">
-              <span>📊 {{ tpl.useCount || 0 }}次</span>
-              <span>
-                <el-tag size="small" :type="getStatusType(tpl.status)">
-                  {{ getStatusLabel(tpl.status) }}
-                </el-tag>
-              </span>
-            </div>
-            <div class="tpl-tags">
-              <span class="tpl-tag" v-if="tpl.isPublic">公开</span>
-              <span class="tpl-tag" v-else>私有</span>
-            </div>
-            <div class="tpl-actions">
-              <el-button type="primary" size="small" @click="useTemplate(tpl)">
-                使用
-              </el-button>
-              <el-dropdown trigger="click" @command="(cmd) => handleCommand(cmd, tpl)">
-                <el-button size="small" :icon="More" />
-                <template #dropdown>
-                  <el-dropdown-menu>
-                    <el-dropdown-item command="edit">编辑</el-dropdown-item>
-                    <el-dropdown-item command="duplicate">复制</el-dropdown-item>
-                    <el-dropdown-item command="publish" v-if="tpl.status === 'draft'">发布</el-dropdown-item>
-                    <el-dropdown-item command="archive" v-if="tpl.status === 'published'">归档</el-dropdown-item>
-                    <el-dropdown-item command="delete" divided>
-                      <span style="color: #ff4d4f">删除</span>
-                    </el-dropdown-item>
-                  </el-dropdown-menu>
-                </template>
-              </el-dropdown>
-            </div>
-          </div>
-        </div>
-      </el-col>
-    </el-row>
-
-    <!-- 空状态 -->
-    <el-empty v-if="!loading && filteredTemplates.length === 0" description="暂无模板">
-      <el-button type="primary" @click="showCreateDialog = true">创建模板</el-button>
-    </el-empty>
-
-    <!-- 创建模板对话框 -->
-    <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>
-  </div>
-</template>
-
-<script setup>
-import { ref, reactive, computed, onMounted } from 'vue'
-import { useRouter } from 'vue-router'
-import { Plus, More } from '@element-plus/icons-vue'
-import { ElMessage, ElMessageBox } from 'element-plus'
-import { useTemplateStore } from '@/stores/template'
-
-const router = useRouter()
-const templateStore = useTemplateStore()
-
-const loading = ref(false)
-const creating = ref(false)
-const searchKeyword = ref('')
-const filterType = ref('all')
-const showCreateDialog = ref(false)
-
-const newTemplate = reactive({
-  name: '',
-  description: ''
-})
-
-// 模板列表数据(从 API 获取)
-const templates = ref([])
-
-const filteredTemplates = computed(() => {
-  let result = templates.value
-
-  if (searchKeyword.value) {
-    result = result.filter(t => 
-      t.name.toLowerCase().includes(searchKeyword.value.toLowerCase())
-    )
-  }
-
-  if (filterType.value === 'official') {
-    result = result.filter(t => t.isPublic)
-  } else if (filterType.value === 'mine') {
-    result = result.filter(t => !t.isPublic)
-  }
-
-  return result
-})
-
-onMounted(async () => {
-  loading.value = true
-  try {
-    // 从 API 获取模板列表
-    await templateStore.fetchTemplates()
-    templates.value = templateStore.templates
-  } catch (error) {
-    console.error('获取模板列表失败:', error)
-    ElMessage.error('获取模板列表失败')
-  } finally {
-    loading.value = false
-  }
-})
-
-function getTemplateIcon(tpl) {
-  const icons = ['📊', '🏢', '📅', '💼', '📋', '📈']
-  return icons[parseInt(tpl.id) % icons.length]
-}
-
-function getStatusType(status) {
-  const map = {
-    draft: 'info',
-    published: 'success',
-    archived: 'warning'
-  }
-  return map[status] || 'info'
-}
-
-function getStatusLabel(status) {
-  const map = {
-    draft: '草稿',
-    published: '已发布',
-    archived: '已归档'
-  }
-  return map[status] || status
-}
-
-function handleSearch() {
-  // 搜索逻辑已在 computed 中处理
-}
-
-function handleFilterChange() {
-  // 筛选逻辑已在 computed 中处理
-}
-
-function goToDetail(tpl) {
-  router.push(`/templates/${tpl.id}`)
-}
-
-function useTemplate(tpl) {
-  router.push(`/editor/${tpl.id}`)
-}
-
-async function handleCommand(command, tpl) {
-  switch (command) {
-    case 'edit':
-      router.push(`/editor/${tpl.id}`)
-      break
-    case 'duplicate':
-      try {
-        await templateStore.createTemplate({
-          ...tpl,
-          name: `${tpl.name} (副本)`
-        })
-        ElMessage.success('复制成功')
-      } catch (error) {
-        ElMessage.error('复制失败')
-      }
-      break
-    case 'publish':
-      try {
-        await templateStore.updateTemplate(tpl.id, { status: 'published' })
-        tpl.status = 'published'
-        ElMessage.success('发布成功')
-      } catch (error) {
-        ElMessage.error('发布失败')
-      }
-      break
-    case 'archive':
-      try {
-        await templateStore.updateTemplate(tpl.id, { status: 'archived' })
-        tpl.status = 'archived'
-        ElMessage.success('归档成功')
-      } catch (error) {
-        ElMessage.error('归档失败')
-      }
-      break
-    case 'delete':
-      ElMessageBox.confirm('确定要删除该模板吗?此操作不可恢复。', '删除确认', {
-        type: 'warning',
-        confirmButtonText: '删除',
-        cancelButtonText: '取消'
-      }).then(async () => {
-        try {
-          await templateStore.deleteTemplate(tpl.id)
-          templates.value = templates.value.filter(t => t.id !== tpl.id)
-          ElMessage.success('删除成功')
-        } catch (error) {
-          ElMessage.error('删除失败')
-        }
-      })
-      break
-  }
-}
-
-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('模板创建成功')
-    // 跳转到空白编辑页
-    router.push(`/editor/${template.id}`)
-  } catch (error) {
-    ElMessage.error('创建失败: ' + error.message)
-  } finally {
-    creating.value = false
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-.templates-page {
-  max-width: 1200px;
-  margin: 0 auto;
-}
-
-// 筛选栏
-.filter-bar {
-  display: flex;
-  gap: 16px;
-  margin-bottom: 20px;
-  
-  :deep(.el-input__wrapper) {
-    border-radius: 6px;
-  }
-  
-  :deep(.el-radio-button__inner) {
-    border-radius: 0;
-    
-    &:first-child {
-      border-radius: 6px 0 0 6px;
-    }
-    
-    &:last-child {
-      border-radius: 0 6px 6px 0;
-    }
-  }
-}
-
-// 模板卡片
-.tpl-card {
-  margin-bottom: 16px;
-  
-  .tpl-preview {
-    cursor: pointer;
-    transition: all 0.3s;
-    
-    &:hover {
-      transform: scale(1.02);
-    }
-  }
-
-  .tpl-actions {
-    display: flex;
-    gap: 8px;
-    margin-top: 10px;
-
-    .el-button:first-child {
-      flex: 1;
-    }
-  }
-}
-
-// 空状态
-:deep(.el-empty) {
-  padding: 60px 0;
-}
-</style>

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.