refactor: 完成全局 emoji 到 SVG 图标迁移

- IconLibrary 新增 6 个图标: star, lightbulb, pin, clipboard, microphone, bookmark
- 迁移 12 个组件中的 emoji 为 IconLibrary 组件
- 更新 paradigms.js 中的 icon 字段为图标名称
- 修复深度模式开关点击无效的问题 (改用 Vue 动态类绑定)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
empty
2026-01-12 03:25:08 +08:00
parent 97e593c145
commit b063afb2a1
14 changed files with 182 additions and 98 deletions

View File

@@ -60,7 +60,8 @@
>
<div class="paradigm-card-header">
<h4 class="paradigm-card-title">
{{ paradigm.icon }} {{ paradigm.name }}
<IconLibrary :name="paradigm.icon" :size="16" class="shrink-0" />
{{ paradigm.name }}
<span v-if="paradigm.isNew" class="badge-new animate-pulse">NEW</span>
<span v-if="isCustomParadigm(paradigm)" class="badge-custom">自定义</span>
<span v-if="paradigm.createdAt" class="text-[10px] text-muted ml-auto opacity-70">
@@ -202,7 +203,7 @@ const activeTab = ref('paradigms') // 'paradigms' or 'tools'
// 编辑表单
const editForm = reactive({
icon: '📝',
icon: 'edit',
name: '',
description: '',
tagsInput: '',
@@ -211,8 +212,8 @@ const editForm = reactive({
specializedPrompt: ''
})
// 图标选项 (保留 emoji 用于用户自定义范式图标选择器TODO: 后续优化为 IconLibrary 选择器)
const iconOptions = ['📝', '💻', '📊', '🚀', '📚', '🏛️', '🔥', '🏢', '💡', '🎯', '📋', '✨']
// 图标选项 (使用 IconLibrary 图标名称)
const iconOptions = ['edit', 'document', 'chart', 'sparkles', 'folder', 'article', 'star', 'lightbulb', 'clipboard', 'bookmark']
// 颜色选项
const colorOptions = [
@@ -259,7 +260,7 @@ const openAddModal = () => {
// 重置表单
const form = appStore.paradigmEditState.editForm
form.icon = '📝'
form.icon = 'edit'
form.name = ''
form.description = ''
form.tagsInput = ''
@@ -293,7 +294,7 @@ const openEditModal = (paradigm) => {
// 填充表单数据
const form = appStore.paradigmEditState.editForm
form.icon = paradigm.icon || '📝'
form.icon = paradigm.icon || 'edit'
form.name = paradigm.name || ''
form.description = paradigm.description || ''
form.tagsInput = (paradigm.tags || []).join(', ')
@@ -310,7 +311,7 @@ const closeEditModal = () => {
// 重置表单
const resetEditForm = () => {
editForm.icon = '📝'
editForm.icon = 'edit'
editForm.name = ''
editForm.description = ''
editForm.tagsInput = ''

View File

@@ -4,7 +4,7 @@
<header class="p-3 border-b border-slate-700 bg-slate-800 flex items-center justify-between shrink-0">
<div class="flex items-center gap-3">
<h1 class="font-bold text-white flex items-center gap-2">
<span class="text-xl">🔍</span> 对照检查
<IconLibrary name="search" :size="20" /> 对照检查
</h1>
<span class="text-xs text-slate-500">选中左右两侧对应段落进行检查</span>
</div>
@@ -13,7 +13,9 @@
<!-- 范式上下文提示条 -->
<div v-if="hasParadigmRules" class="px-4 py-2 bg-indigo-900/30 border-b border-indigo-700/50 flex items-center justify-between shrink-0">
<div class="flex items-center gap-3">
<span class="text-indigo-400 text-sm">📚 当前写作范式</span>
<span class="text-indigo-400 text-sm flex items-center gap-1">
<IconLibrary name="folder" :size="14" /> 当前写作范式
</span>
<span class="text-white text-sm font-medium">{{ activeParadigmName }}</span>
<!-- 检查模式切换 -->
<div class="flex bg-slate-900/50 rounded p-0.5 border border-indigo-700/50">
@@ -61,7 +63,7 @@
<div :class="['w-4 h-4 rounded border flex items-center justify-center shrink-0 mt-0.5 transition',
enabledRuleIdxs.has(rule.idx) ? 'bg-indigo-600 border-indigo-500' : 'bg-slate-800 border-slate-600'
]">
<span v-if="enabledRuleIdxs.has(rule.idx)" class="text-white text-[10px]"></span>
<IconLibrary v-if="enabledRuleIdxs.has(rule.idx)" name="check" :size="10" class="text-white" />
</div>
<!-- 规则标签和文本 -->
<div class="flex-1 min-w-0">
@@ -80,7 +82,7 @@
<div class="w-[40%] flex flex-col border-r border-slate-700 min-w-0">
<div class="p-3 bg-slate-800 border-b border-slate-700 flex items-center justify-between">
<h2 class="text-sm font-medium text-amber-400 flex items-center gap-2">
📋 要求原文
<IconLibrary name="clipboard" :size="14" /> 要求原文
</h2>
<div class="flex items-center gap-2">
<span class="text-xs text-slate-500">{{ leftParagraphs.length }} </span>
@@ -95,11 +97,11 @@
: 'bg-slate-700 text-slate-400 cursor-not-allowed'"
>
<span v-if="isLeftSaving" class="animate-spin"></span>
<span v-else>💾</span>
<IconLibrary v-else name="save" :size="12" />
{{ isLeftSaving ? '保存中...' : (hasLeftContentChanged ? '保存版本' : '已保存') }}
</button>
<span v-if="leftSourceType === 'document' && leftSourceDocTitle" class="text-xs text-amber-400">
📄 {{ leftSourceDocTitle }}
<span v-if="leftSourceType === 'document' && leftSourceDocTitle" class="text-xs text-amber-400 flex items-center gap-1">
<IconLibrary name="document" :size="12" /> {{ leftSourceDocTitle }}
</span>
</div>
</div>
@@ -132,7 +134,9 @@
<div class="flex-1 overflow-y-auto p-4 space-y-3 min-h-0">
<div v-if="!leftContent" class="h-full flex items-center justify-center">
<div class="text-center">
<span class="text-4xl opacity-20 block mb-2">📝</span>
<div class="w-12 h-12 flex items-center justify-center mx-auto mb-2 opacity-20">
<IconLibrary name="edit" :size="32" />
</div>
<p class="text-slate-600 text-sm">请在下方粘贴写作要求的原文</p>
<p class="text-slate-700 text-xs mt-1">红头文件通知要求等</p>
</div>
@@ -151,7 +155,8 @@
<div class="flex items-start gap-2">
<span :class="['text-xs shrink-0 mt-0.5 w-5 h-5 rounded flex items-center justify-center',
selectedLeftIdxs.includes(idx) ? 'bg-amber-500 text-white' : 'text-amber-500/70']">
{{ selectedLeftIdxs.includes(idx) ? '✓' : (idx + 1) }}
<span v-if="selectedLeftIdxs.includes(idx)"><IconLibrary name="check" :size="10" /></span>
<span v-else>{{ idx + 1 }}</span>
</span>
<p class="text-sm text-slate-300 whitespace-pre-wrap">{{ para }}</p>
</div>
@@ -186,7 +191,7 @@
<div class="flex-1 flex flex-col min-w-0">
<div class="p-3 bg-slate-800 border-b border-slate-700 flex items-center justify-between">
<h2 class="text-sm font-medium text-blue-400 flex items-center gap-2">
写作内容
<IconLibrary name="edit" :size="14" /> 写作内容
</h2>
<div class="flex items-center gap-2">
<span class="text-xs text-slate-500">{{ rightParagraphs.length }} </span>
@@ -201,11 +206,11 @@
: 'bg-slate-700 text-slate-400 cursor-not-allowed'"
>
<span v-if="isSaving" class="animate-spin"></span>
<span v-else>💾</span>
<IconLibrary v-else name="save" :size="12" />
{{ isSaving ? '保存中...' : (hasContentChanged ? '保存版本' : '已保存') }}
</button>
<span v-if="rightSourceType === 'document' && rightSourceDocTitle" class="text-xs text-blue-400">
📄 {{ rightSourceDocTitle }}
<span v-if="rightSourceType === 'document' && rightSourceDocTitle" class="text-xs text-blue-400 flex items-center gap-1">
<IconLibrary name="document" :size="12" /> {{ rightSourceDocTitle }}
</span>
</div>
</div>
@@ -228,7 +233,9 @@
<div class="flex-1 overflow-y-auto p-4 space-y-3 min-h-0">
<div v-if="!rightContent" class="h-full flex items-center justify-center">
<div class="text-center">
<span class="text-4xl opacity-20 block mb-2">📄</span>
<div class="w-12 h-12 flex items-center justify-center mx-auto mb-2 opacity-20">
<IconLibrary name="document" :size="32" />
</div>
<p class="text-slate-600 text-sm">请在下方输入写作内容</p>
</div>
</div>
@@ -246,7 +253,8 @@
<div class="flex items-start gap-2">
<span :class="['text-xs shrink-0 mt-0.5 w-5 h-5 rounded flex items-center justify-center',
selectedRightIdxs.includes(idx) ? 'bg-blue-500 text-white' : 'text-blue-500/70']">
{{ selectedRightIdxs.includes(idx) ? '✓' : (idx + 1) }}
<span v-if="selectedRightIdxs.includes(idx)"><IconLibrary name="check" :size="10" /></span>
<span v-else>{{ idx + 1 }}</span>
</span>
<p class="text-sm text-slate-300 whitespace-pre-wrap">{{ para }}</p>
</div>
@@ -287,7 +295,9 @@
checkResults[idx].status === 'warning' ? 'bg-yellow-900/30 text-yellow-400' :
'bg-red-900/30 text-red-400'
]">
<span>{{ checkResults[idx].status === 'pass' ? '✅' : checkResults[idx].status === 'warning' ? '⚠️' : '❌' }}</span>
<IconLibrary v-if="checkResults[idx].status === 'pass'" name="success" :size="14" />
<IconLibrary v-else-if="checkResults[idx].status === 'warning'" name="warning" :size="14" />
<IconLibrary v-else name="error" :size="14" />
{{ checkResults[idx].message }}
</div>
</div>
@@ -316,7 +326,9 @@
]">
<div class="flex items-center gap-2 mb-1">
<span class="text-lg">
{{ lastCheckResult.overall === 'pass' ? '✅' : lastCheckResult.overall === 'warning' ? '⚠️' : '❌' }}
<IconLibrary v-if="lastCheckResult.overall === 'pass'" name="success" :size="18" />
<IconLibrary v-else-if="lastCheckResult.overall === 'warning'" name="warning" :size="18" />
<IconLibrary v-else name="error" :size="18" />
</span>
<span :class="[
'font-medium',
@@ -340,7 +352,7 @@
<div v-if="showDetailedSuggestions && lastCheckResult.suggestions?.length"
class="mt-3 p-3 bg-slate-800/50 rounded-lg border border-slate-700">
<h4 class="text-xs text-slate-400 mb-2 flex items-center gap-1">
<span>💡</span> 改进建议
<IconLibrary name="lightbulb" :size="12" /> 改进建议
</h4>
<div class="space-y-2">
<div v-for="(suggestion, idx) in lastCheckResult.suggestions" :key="idx"
@@ -353,7 +365,7 @@
hover:bg-indigo-500 transition flex items-center gap-1
disabled:opacity-50 disabled:cursor-not-allowed ml-2 shrink-0">
<span v-if="rewritingSuggestionIdx === idx" class="animate-spin"></span>
<span v-else>🔄</span>
<IconLibrary v-else name="refresh" :size="12" />
{{ rewritingSuggestionIdx === idx ? '重写中...' : '一键重写' }}
</button>
</div>
@@ -384,7 +396,7 @@
<span v-if="isDetectingTypes" class="flex items-center gap-2">
<span class="animate-spin"></span> 识别中...
</span>
<span v-else>🧠 智能识别</span>
<span v-else><IconLibrary name="sparkles" :size="14" /> 智能识别</span>
</button>
<button
@click="runCompare"
@@ -395,7 +407,7 @@
<span v-if="isComparing" class="flex items-center gap-2">
<span class="animate-spin"></span> 检查中...
</span>
<span v-else>🔍 对照检查</span>
<span v-else><IconLibrary name="search" :size="14" /> 对照检查</span>
</button>
</div>
</div>
@@ -438,18 +450,20 @@
<!-- 浮窗头部 -->
<div class="p-4 border-b border-slate-700 flex items-center justify-between">
<h3 class="text-lg font-medium text-white flex items-center gap-2">
<span>🔄</span> AI 重写预览
<IconLibrary name="refresh" :size="18" /> AI 重写预览
</h3>
<button
@click="resetRewriteModal"
class="text-slate-400 hover:text-white text-xl"
></button>
><IconLibrary name="close" :size="20" /></button>
</div>
<!-- 建议信息 + 视图切换 -->
<div class="px-4 py-2 bg-indigo-900/30 border-b border-indigo-700/50 flex items-center justify-between">
<div>
<span class="text-xs text-indigo-300">📋 根据建议</span>
<span class="text-xs text-indigo-300 flex items-center gap-1">
<IconLibrary name="clipboard" :size="12" /> 根据建议
</span>
<span class="text-xs text-white ml-1">{{ currentSuggestion }}</span>
</div>
<!-- 视图切换按钮 -->
@@ -512,7 +526,9 @@
<div class="flex gap-4">
<!-- 左边原文 -->
<div class="flex-1">
<div class="text-xs text-amber-400 mb-2 font-medium">📄 原文点击选中需要替换的部分</div>
<div class="text-xs text-amber-400 mb-2 font-medium flex items-center gap-1">
<IconLibrary name="document" :size="12" /> 原文点击选中需要替换的部分
</div>
<div class="bg-slate-900 rounded-lg p-3 border border-slate-700">
<template v-for="segment in diffSegments" :key="'orig-' + segment.idx">
<span
@@ -535,7 +551,9 @@
</div>
<!-- 右边重写后 -->
<div class="flex-1">
<div class="text-xs text-blue-400 mb-2 font-medium"> 重写后选中部分将替换为此内容</div>
<div class="text-xs text-blue-400 mb-2 font-medium flex items-center gap-1">
<IconLibrary name="sparkles" :size="12" /> 重写后选中部分将替换为此内容
</div>
<div class="bg-slate-900 rounded-lg p-3 border border-slate-700">
<template v-for="segment in diffSegments" :key="'new-' + segment.idx">
<span
@@ -607,7 +625,7 @@
: 'bg-slate-700 text-slate-300 hover:bg-slate-600'
]"
>
{{ acceptedChanges.has(currentReviewItem.idx) ? '已接受' : '接受修改' }}
{{ acceptedChanges.has(currentReviewItem.idx) ? '' : '' }}<IconLibrary v-if="acceptedChanges.has(currentReviewItem.idx)" name="check" :size="12" class="inline mr-1" />{{ acceptedChanges.has(currentReviewItem.idx) ? '已接受' : '接受修改' }}
</button>
</div>
<button
@@ -628,7 +646,7 @@
<!-- 左边原文句子 -->
<div class="flex-1">
<div class="text-xs text-amber-400 mb-2 font-medium flex items-center justify-between">
<span>📄 原文点击选中要被替换的句子</span>
<span class="flex items-center gap-1"><IconLibrary name="document" :size="12" /> 原文点击选中要被替换的句子</span>
<span class="text-slate-500">已选 {{ partialLeftSelected.size }} </span>
</div>
<div class="bg-slate-900 rounded-lg p-3 border border-slate-700 max-h-[400px] overflow-y-auto">
@@ -648,7 +666,7 @@
<!-- 右边重写后句子 -->
<div class="flex-1">
<div class="text-xs text-blue-400 mb-2 font-medium flex items-center justify-between">
<span> 重写后点击选中用于替换的句子</span>
<span class="flex items-center gap-1"><IconLibrary name="sparkles" :size="12" /> 重写后点击选中用于替换的句子</span>
<span class="text-slate-500">已选 {{ partialRightSelected.size }} </span>
</div>
<div class="bg-slate-900 rounded-lg p-3 border border-slate-700 max-h-[400px] overflow-y-auto">
@@ -749,6 +767,7 @@ import { storeToRefs } from 'pinia'
import { useAppStore } from '../stores/app'
import { useDatabaseStore } from '../stores/database'
import { updateDocument, saveDocumentVersion, getDocumentById } from '../db/index.js'
import IconLibrary from './icons/IconLibrary.vue'
import { SECTION_TYPES, getSectionTypeById, getSectionTypeClasses } from '../config/sectionTypes'
import { getLogicParadigmById, buildLogicPrompt } from '../config/logicParadigms'
import { computeDiff, applySelectedChanges as applyDiffChanges, getDiffStats, splitIntoSentencesWithPosition } from '../utils/textDiff'

View File

@@ -4,7 +4,7 @@
<header class="h-14 border-b border-slate-800 flex items-center justify-between px-6 bg-slate-950 shrink-0">
<div class="flex items-center gap-4">
<h1 class="text-lg font-bold text-white flex items-center gap-2">
📊 文稿差异标注
<IconLibrary name="chart" :size="20" /> 文稿差异标注
</h1>
<button
@click="goBack"
@@ -32,7 +32,7 @@
:disabled="!leftContent || !rightContent"
class="text-sm px-4 py-2 rounded bg-blue-600 text-white hover:bg-blue-500 disabled:opacity-50 disabled:cursor-not-allowed transition flex items-center gap-2"
>
📥 导出 Word
<IconLibrary name="download" :size="14" /> 导出 Word
</button>
</div>
</header>
@@ -43,7 +43,7 @@
<div class="flex-1 flex flex-col border-r border-slate-700 min-w-0">
<div class="p-3 bg-slate-800 border-b border-slate-700 flex items-center justify-between shrink-0">
<h2 class="text-sm font-medium text-amber-400 flex items-center gap-2">
📄 原文
<IconLibrary name="document" :size="14" /> 原文
</h2>
<div class="flex items-center gap-2">
<span class="text-xs text-slate-500">来源</span>
@@ -81,7 +81,9 @@
</template>
</div>
<div v-else class="h-full flex flex-col items-center justify-center text-slate-500">
<span class="text-5xl block mb-3 opacity-30">📝</span>
<div class="w-16 h-16 flex items-center justify-center mb-3 opacity-30">
<IconLibrary name="edit" :size="40" />
</div>
<p class="text-sm">在下方输入原文内容</p>
<p class="text-xs text-slate-600 mt-1">或从文稿库选择</p>
</div>
@@ -109,7 +111,7 @@
<div class="flex-1 flex flex-col min-w-0">
<div class="p-3 bg-slate-800 border-b border-slate-700 flex items-center justify-between shrink-0">
<h2 class="text-sm font-medium text-blue-400 flex items-center gap-2">
修改文
<IconLibrary name="edit" :size="14" /> 修改文
</h2>
<div class="flex items-center gap-2">
<span class="text-xs text-slate-500">来源</span>
@@ -147,7 +149,9 @@
</template>
</div>
<div v-else class="h-full flex flex-col items-center justify-center text-slate-500">
<span class="text-5xl block mb-3 opacity-30"></span>
<div class="w-16 h-16 flex items-center justify-center mb-3 opacity-30">
<IconLibrary name="edit" :size="40" />
</div>
<p class="text-sm">在下方输入修改后的内容</p>
<p class="text-xs text-slate-600 mt-1">或从文稿库选择</p>
</div>
@@ -192,6 +196,7 @@
import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
import { useAppStore } from '../stores/app'
import { computePreciseDiff, getPreciseDiffStats } from '../utils/preciseDiff.js'
import IconLibrary from './icons/IconLibrary.vue'
import { Document, Packer, Paragraph, TextRun } from 'docx'
import { saveAs } from 'file-saver'
import DocumentSelectorModal from './DocumentSelectorModal.vue'

View File

@@ -3,8 +3,12 @@
<div class="bg-slate-800 rounded-lg w-[500px] max-h-[70vh] overflow-hidden border border-slate-600 flex flex-col">
<!-- 头部 -->
<div class="p-4 border-b border-slate-700 flex items-center justify-between shrink-0">
<h3 class="text-lg font-bold text-white">📂 选择文稿</h3>
<button @click="$emit('close')" class="text-slate-400 hover:text-white"></button>
<h3 class="text-lg font-bold text-white flex items-center gap-2">
<IconLibrary name="folder" :size="18" /> 选择文稿
</h3>
<button @click="$emit('close')" class="text-slate-400 hover:text-white">
<IconLibrary name="close" :size="18" />
</button>
</div>
<!-- 筛选 -->
@@ -25,7 +29,9 @@
<!-- 文稿列表 -->
<div class="flex-1 overflow-y-auto p-4 space-y-2 min-h-0">
<div v-if="filteredDocuments.length === 0" class="text-center text-slate-500 py-8">
<span class="text-4xl block mb-2">📄</span>
<div class="w-12 h-12 flex items-center justify-center mx-auto mb-2">
<IconLibrary name="document" :size="32" />
</div>
<p class="text-sm">暂无文稿</p>
</div>
@@ -76,6 +82,7 @@
<script setup>
import { ref, computed, watch } from 'vue'
import IconLibrary from './icons/IconLibrary.vue'
const props = defineProps({
visible: Boolean

View File

@@ -6,13 +6,13 @@
<!-- 头部 -->
<header class="p-4 border-b border-slate-700 flex items-center justify-between">
<h2 class="font-bold text-white flex items-center gap-2">
<span class="text-xl">🕐</span> 版本历史
<IconLibrary name="clock" :size="18" /> 版本历史
</h2>
<button
<button
@click="$emit('close')"
class="text-slate-400 hover:text-white transition"
>
<IconLibrary name="close" :size="18" />
</button>
</header>
@@ -69,7 +69,9 @@
<!-- 无版本记录 -->
<div v-else class="flex-1 flex flex-col items-center justify-center text-slate-500">
<span class="text-4xl mb-2">📋</span>
<div class="w-12 h-12 flex items-center justify-center mb-2">
<IconLibrary name="clipboard" :size="32" />
</div>
<p class="text-sm">暂无版本记录</p>
<p class="text-xs text-slate-600 mt-1">保存文稿后将自动记录版本</p>
</div>
@@ -92,10 +94,10 @@
<span><span class="inline-block w-3 h-3 rounded bg-amber-500/50 mr-1"></span>修改</span>
</div>
</div>
<button
<button
@click="showCompareModal = false"
class="text-slate-400 hover:text-white"
></button>
><IconLibrary name="close" :size="18" /></button>
</header>
<div class="flex-1 overflow-y-auto p-4">
<div class="flex gap-4">
@@ -170,6 +172,7 @@
import { ref, watch, computed, defineProps, defineEmits } from 'vue'
import { getDocumentVersions, getDocumentById, saveDocumentVersion, updateDocument } from '../db/index.js'
import { computeDiff, getDiffStats } from '../utils/textDiff.js'
import IconLibrary from './icons/IconLibrary.vue'
const props = defineProps({
visible: Boolean,

View File

@@ -63,7 +63,7 @@
<div v-if="currentPage === 'writer'">
<div v-if="!generatedContent && !isGenerating" class="h-full flex flex-col items-center justify-center text-slate-700 mt-20">
<div class="w-20 h-20 flex items-center justify-center rounded-2xl bg-slate-800/50 border border-slate-700 mb-4 opacity-40">
<IconLibrary name="keyboard" :size="48" />
<IconLibrary name="edit" :size="48" />
</div>
<p>在左侧配置参数点击生成开始写作</p>
</div>
@@ -297,12 +297,12 @@
v-for="icon in iconOptions"
:key="icon"
@click="paradigmEditState.editForm.icon = icon"
:class="['w-10 h-10 text-xl rounded border transition',
paradigmEditState.editForm.icon === icon
? 'border-blue-500 bg-blue-500/20'
:class="['w-10 h-10 rounded border transition flex items-center justify-center',
paradigmEditState.editForm.icon === icon
? 'border-blue-500 bg-blue-500/20'
: 'border-slate-600 hover:border-slate-500']"
>
{{ icon }}
<IconLibrary :name="icon" :size="20" />
</button>
</div>
</div>
@@ -584,8 +584,8 @@ const exportParadigmToWord = async () => {
}
}
// 图标选项 (TODO: 后续优化为 IconLibrary 图标网格选择器)
const iconOptions = ['📝', '📄', '📊', '📈', '📉', '🏛️', '🍂', '💻', '📌', '💡']
// 图标选项 (使用 IconLibrary 图标名称)
const iconOptions = ['edit', 'document', 'chart', 'sparkles', 'folder', 'article', 'star', 'lightbulb', 'pin', 'clipboard']
// 颜色选项
const colorOptions = [

View File

@@ -11,7 +11,7 @@
<button
@click="$emit('close')"
class="text-slate-400 hover:text-white"
></button>
><IconLibrary name="close" :size="18" /></button>
</div>
<!-- 筛选器 -->
@@ -71,6 +71,7 @@
<script setup>
import { ref, computed } from 'vue'
import { useDatabaseStore } from '../stores/database'
import IconLibrary from './icons/IconLibrary.vue'
const props = defineProps({
visible: Boolean

View File

@@ -4,9 +4,11 @@
<!-- 头部 -->
<div class="p-4 border-b border-slate-700 flex items-center justify-between shrink-0">
<h3 class="text-lg font-bold text-white flex items-center gap-2">
<span>📚</span> 选择写作范式
<IconLibrary name="folder" :size="18" /> 选择写作范式
</h3>
<button @click="$emit('close')" class="text-slate-400 hover:text-white transition"></button>
<button @click="$emit('close')" class="text-slate-400 hover:text-white transition">
<IconLibrary name="close" :size="18" />
</button>
</div>
<!-- 搜索/过滤 -->
@@ -22,7 +24,9 @@
<!-- 范式列表 -->
<div class="flex-1 overflow-y-auto p-4 space-y-3 min-h-0 bg-slate-900/20">
<div v-if="filteredParadigms.length === 0" class="text-center text-slate-500 py-12">
<span class="text-4xl block mb-2">🔍</span>
<div class="w-12 h-12 flex items-center justify-center mx-auto mb-2">
<IconLibrary name="search" :size="32" />
</div>
<p class="text-sm">未找到相关范式</p>
</div>
@@ -70,7 +74,7 @@
<!-- 勾选指示 -->
<div class="w-6 h-6 rounded-full border-2 flex items-center justify-center shrink-0 mt-1 transition-colors"
:class="selectedId === paradigm.id ? 'border-blue-500 bg-blue-600' : 'border-slate-600'">
<span v-if="selectedId === paradigm.id" class="text-white text-xs"></span>
<IconLibrary v-if="selectedId === paradigm.id" name="check" :size="12" class="text-white" />
</div>
</div>
</div>
@@ -83,7 +87,7 @@
class="py-2.5 px-4 rounded-lg bg-purple-600 text-white hover:bg-purple-500 transition font-medium shadow-lg shadow-purple-900/20 flex items-center gap-2"
title="从需求文档创建自定义范式"
>
<span>🎯</span>
<IconLibrary name="sparkles" :size="14" />
<span>新建范式</span>
</button>
<div class="flex-1 flex gap-3">
@@ -109,6 +113,7 @@
<script setup>
import { ref, computed, watch } from 'vue'
import { getParadigmList } from '../config/paradigms.js'
import IconLibrary from './icons/IconLibrary.vue'
const props = defineProps({
visible: Boolean

View File

@@ -3,7 +3,8 @@
<!-- 头部 -->
<header class="writer-header">
<h1 class="writer-header-title">
<span style="font-size: var(--text-xl)">📝</span> 范式分段写作
<IconLibrary name="edit" :size="20" />
<span>范式分段写作</span>
</h1>
<span class="badge badge-primary">新功能</span>
</header>
@@ -61,23 +62,25 @@
<div v-if="currentSectionIndex === index" class="section-card-body mt-2 space-y-3">
<!-- 输入类型选择 -->
<div class="flex gap-2 mb-2">
<button
<button
@click.stop="section.inputType = 'material'"
:class="['input-type-btn', section.inputType === 'material' ? 'active' : '']"
>
📊 素材型
<IconLibrary name="chart" :size="12" /> 素材型
</button>
<button
<button
@click.stop="section.inputType = 'idea'"
:class="['input-type-btn', section.inputType === 'idea' || !section.inputType ? 'active' : '']"
>
💡 思路型
<IconLibrary name="lightbulb" :size="12" /> 思路型
</button>
</div>
<!-- 结构化输入核心论点 -->
<div>
<label class="text-[10px] text-muted block mb-1">📌 核心论点必填</label>
<label class="text-[10px] text-muted block mb-1 flex items-center gap-1">
<IconLibrary name="pin" :size="10" /> 核心论点必填
</label>
<textarea
v-model="section.corePoint"
class="writer-textarea min-h-[50px]"
@@ -88,8 +91,8 @@
<!-- 结构化输入数据/案例 -->
<div>
<label class="text-[10px] text-muted block mb-1">
📊 数据/案例
<label class="text-[10px] text-muted block mb-1 flex items-center gap-1">
<IconLibrary name="chart" :size="10" /> 数据/案例
<span class="text-warning">({{ section.inputType === 'material' ? '严格引用' : '可润色' }})</span>
</label>
<textarea
@@ -102,7 +105,9 @@
<!-- 结构化输入补充说明 -->
<div>
<label class="text-[10px] text-muted block mb-1">💡 补充说明可自由发挥</label>
<label class="text-[10px] text-muted block mb-1 flex items-center gap-1">
<IconLibrary name="lightbulb" :size="10" /> 补充说明可自由发挥
</label>
<textarea
v-model="section.supplementNote"
class="writer-textarea min-h-[40px]"
@@ -119,13 +124,13 @@
>
{{ section.isGenerating ? '正在生成...' : (section.generatedContent ? '重新生成本节' : '生成本节') }}
</button>
<button
<button
v-if="section.generatedContent"
@click.stop="copySectionContent(section)"
class="btn btn-secondary text-[10px] py-1.5"
title="复制内容"
>
📋
<IconLibrary name="clipboard" :size="12" />
</button>
</div>
</div>
@@ -135,7 +140,9 @@
<!-- 提示未选择范式 -->
<div v-else class="empty-state">
<div class="empty-icon text-center">📂</div>
<div class="empty-icon text-center">
<IconLibrary name="folder" :size="32" />
</div>
<p class="text-sm text-muted text-center">请先选择一个写作范式以加载大纲</p>
</div>
</div>
@@ -170,6 +177,7 @@
import { ref, computed, onMounted } from 'vue'
import { storeToRefs } from 'pinia'
import { useAppStore } from '../stores/app'
import IconLibrary from './icons/IconLibrary.vue'
import { getParadigmList } from '../config/paradigms.js'
import { marked } from 'marked'
import { Document, Packer, Paragraph, TextRun, HeadingLevel } from 'docx'

View File

@@ -4,7 +4,7 @@
<!-- 头部 -->
<header class="p-4 border-b border-slate-700 flex items-center justify-between shrink-0">
<h2 class="font-bold text-lg text-white flex items-center gap-2">
<span class="text-2xl">🎯</span> 需求文档解析
<IconLibrary name="sparkles" :size="20" /> 需求文档解析
</h2>
<button
@click="$emit('close')"
@@ -54,8 +54,8 @@
></textarea>
<div class="flex items-center justify-between mt-2 text-xs text-slate-500">
<span>{{ requirementText.length }} 字符</span>
<span v-if="requirementText.length > 10000" class="text-amber-500">
文档过长可能影响解析质量建议控制在10000字符以内
<span v-if="requirementText.length > 10000" class="text-amber-500 flex items-center gap-1">
<IconLibrary name="warning" :size="12" /> 文档过长可能影响解析质量建议控制在10000字符以内
</span>
</div>
</div>
@@ -73,7 +73,7 @@
@click="$refs.fileInput.click()"
class="w-full p-6 border-2 border-dashed border-slate-700 rounded-lg hover:border-indigo-500 transition text-slate-400 hover:text-indigo-400 flex flex-col items-center gap-2"
>
<span class="text-3xl">📄</span>
<IconLibrary name="document" :size="24" />
<span class="text-sm">点击选择文件支持 .txt, .md</span>
<span v-if="selectedFileName" class="text-xs text-indigo-400 mt-2">
已选择{{ selectedFileName }}
@@ -246,7 +246,7 @@
@click="saveParadigm"
class="px-4 py-2 text-sm rounded bg-green-600 text-white hover:bg-green-500 transition flex items-center gap-2"
>
<span></span>
<IconLibrary name="check" :size="14" />
<span>保存并使用</span>
</button>
</div>
@@ -255,7 +255,9 @@
<!-- 解析失败 -->
<section v-if="step === 'error'" class="space-y-4">
<div class="bg-red-900/20 border border-red-700 rounded-lg p-6 text-center">
<span class="text-4xl block mb-3"></span>
<div class="w-12 h-12 flex items-center justify-center mx-auto mb-3">
<IconLibrary name="warning" :size="32" />
</div>
<p class="text-red-400 mb-2">解析失败</p>
<p class="text-xs text-slate-500">{{ errorMessage }}</p>
<button
@@ -275,6 +277,7 @@
import { ref } from 'vue'
import { useParadigmStore } from '../stores/paradigm'
import DeepSeekAPI from '../api/deepseek'
import IconLibrary from './icons/IconLibrary.vue'
import { config } from '../utils/config'
import {
buildRequirementParserPrompt,

View File

@@ -43,8 +43,10 @@
</div>
<div class="status-row">
<span class="status-label">API Key</span>
<span :class="['status-value', currentProvider?.apiKey ? 'configured' : 'not-configured']">
{{ currentProvider?.apiKey ? '✓ 已配置' : '✗ 未配置' }}
<span :class="['status-value flex items-center gap-1', currentProvider?.apiKey ? 'configured' : 'not-configured']">
<IconLibrary v-if="currentProvider?.apiKey" name="check" :size="12" />
<IconLibrary v-else name="close" :size="12" />
{{ currentProvider?.apiKey ? '已配置' : '未配置' }}
</span>
</div>
<div class="status-row" v-if="currentProvider?.model">

View File

@@ -3,7 +3,8 @@
<!-- 头部 -->
<header class="writer-header">
<h1 class="writer-header-title">
<span style="font-size: var(--text-xl)"></span> AI 写作工坊
<IconLibrary name="edit" :size="20" />
<span>AI 写作工坊</span>
</h1>
<span class="badge badge-default">Pro版</span>
</header>
@@ -63,7 +64,7 @@
<div v-for="(ref, index) in references" :key="index" class="ref-card">
<div class="ref-card-header">
<div class="ref-card-title">
<span style="font-size: var(--text-base)">📄</span>
<IconLibrary name="document" :size="14" />
<span class="ref-card-title-text">{{ ref.title }}</span>
</div>
<button @click="removeReference(index)" class="ref-remove-btn px-2">×</button>
@@ -94,8 +95,9 @@
<!-- 专家指令范式预设时显示 -->
<section v-if="activeParadigm" class="writer-section">
<div class="flex justify-between items-center mb-2">
<label class="writer-label writer-label-alt">
专家指令 (Expert Guidelines)
<label class="writer-label writer-label-alt flex items-center gap-1.5">
<IconLibrary name="star" :size="14" />
<span>专家指令 (Expert Guidelines)</span>
</label>
<button
@click="clearParadigm"
@@ -149,14 +151,15 @@
<div class="deep-mode-toggle mt-4">
<div class="flex flex-col">
<span class="text-sm font-bold flex items-center gap-1" style="color: var(--accent-primary)">
🧠 深度模式 (Deep Mode)
<IconLibrary name="sparkles" :size="14" />
深度模式 (Deep Mode)
</span>
<span class="text-[10px] text-muted">模拟人类"初稿-反思-润色"的思维链</span>
</div>
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" v-model="isDeepMode" class="hidden peer">
<div style="width: 36px; height: 20px; background: var(--bg-elevated); border-radius: 9999px; position: relative; transition: all var(--transition-normal);" class="peer-checked:bg-indigo-600">
<div style="width: 16px; height: 16px; background: white; border-radius: 50%; position: absolute; top: 2px; left: 2px; transition: all var(--transition-normal);" class="peer-checked:translate-x-4"></div>
<input type="checkbox" v-model="isDeepMode" class="hidden">
<div class="toggle-switch" :class="{'active': isDeepMode}">
<div class="toggle-thumb" :class="{'active': isDeepMode}"></div>
</div>
</label>
</div>
@@ -193,6 +196,7 @@
import { computed } from 'vue'
import { storeToRefs } from 'pinia'
import { useAppStore } from '../stores/app'
import IconLibrary from './icons/IconLibrary.vue'
const appStore = useAppStore()
const {

View File

@@ -200,6 +200,32 @@ const icons = {
'loading': {
paths: ['M12 4V2A10 10 0 0 0 2 12h2a8 8 0 0 1 8-8z'],
viewBox: '0 0 24 24'
},
// 新增图标
'star': {
paths: ['M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z'],
viewBox: '0 0 24 24'
},
'lightbulb': {
paths: ['M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7z'],
viewBox: '0 0 24 24'
},
'pin': {
paths: ['M16 12V4h1V2H7v2h1v8l-2 2v2h5.2v6h1.6v-6H18v-2l-2-2z'],
viewBox: '0 0 24 24'
},
'clipboard': {
paths: ['M19 2h-4.18C14.4.84 13.3 0 12 0c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm7 18H5V4h2v3h10V4h2v16z'],
viewBox: '0 0 24 24'
},
'microphone': {
paths: ['M12 14c1.66 0 2.99-1.34 2.99-3L15 5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.3-3c0 3-2.54 5.1-5.3 5.1S6.7 14 6.7 11H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c3.28-.48 6-3.3 6-6.72h-1.7z'],
viewBox: '0 0 24 24'
},
'bookmark': {
paths: ['M17 3H7c-1.1 0-1.99.9-1.99 2L5 21l7-3 7 3V5c0-1.1-.9-2-2-2z'],
viewBox: '0 0 24 24'
}
}

View File

@@ -14,7 +14,7 @@ export const PARADIGMS = {
'tech': {
id: 'tech',
name: '技术博客范式',
icon: '💻',
icon: 'document',
description: '适用于技术分享、教程类文章',
tags: ['问题引入', '解决方案', '代码示例', '总结'],
tagClass: 'bg-blue-900/30 text-blue-300',
@@ -63,7 +63,7 @@ export const PARADIGMS = {
'business': {
id: 'business',
name: '商业分析范式',
icon: '📊',
icon: 'chart',
description: '适用于行业分析、市场报告',
tags: ['背景介绍', '数据支撑', '趋势分析', '建议'],
tagClass: 'bg-green-900/30 text-green-300',
@@ -107,7 +107,7 @@ export const PARADIGMS = {
'marketing': {
id: 'marketing',
name: '产品文案范式',
icon: '🚀',
icon: 'sparkles',
description: '适用于产品介绍、营销文案',
tags: ['痛点切入', '价值主张', '功能亮点', '行动号召'],
tagClass: 'bg-purple-900/30 text-purple-300',
@@ -151,7 +151,7 @@ export const PARADIGMS = {
'academic': {
id: 'academic',
name: '学术论文范式',
icon: '📚',
icon: 'folder',
description: '适用于学术写作、研究报告',
tags: ['摘要', '引言', '文献综述', '方法论', '结果'],
tagClass: 'bg-orange-900/30 text-orange-300',
@@ -195,7 +195,7 @@ export const PARADIGMS = {
'gov-report': {
id: 'gov-report',
name: '政府工作报告',
icon: '🏢',
icon: 'article',
description: '适用于政府工作总结、述职报告',
tags: ['工作回顾', '成绩总结', '问题分析', '下步计划'],
tagClass: 'bg-cyan-900/30 text-cyan-300',
@@ -242,7 +242,7 @@ export const PARADIGMS = {
'deputy-secretary-2025': {
id: 'deputy-secretary-2025',
name: '2025党委副书记对照检查',
icon: '🎖️',
icon: 'star',
description: '适用于党委副书记2025年组织生活会个人对照检查强调政治敏感性与深度剖析',
tags: ['五个带头', '思想根源', '行为习惯', '典型案例'],
tagClass: 'bg-indigo-900/30 text-indigo-300',