feat(web): Skills管理页面新增markdown格式的预览

This commit is contained in:
Wenjie Zhang 2026-03-05 02:05:15 +08:00
parent c288c04a82
commit 30df84efc1
2 changed files with 91 additions and 34 deletions

View File

@ -125,17 +125,30 @@
<span>{{ selectedPath || '未选择文件' }}</span> <span>{{ selectedPath || '未选择文件' }}</span>
<span v-if="canSave" class="save-hint"></span> <span v-if="canSave" class="save-hint"></span>
</div> </div>
<a-button <div class="header-actions">
type="primary" <a-button
size="small" v-if="isMarkdownFile && selectedPath"
@click="saveCurrentFile" size="small"
:disabled="!canSave" @click="viewMode = viewMode === 'edit' ? 'preview' : 'edit'"
:loading="savingFile" class="lucide-icon-btn view-toggle-btn"
class="lucide-icon-btn" :title="viewMode === 'edit' ? '预览' : '编辑'"
> >
<Save :size="14" /> <Eye v-if="viewMode === 'edit'" :size="14" />
<span>保存</span> <Edit3 v-else :size="14" />
</a-button> <span>{{ viewMode === 'edit' ? '预览' : '编辑' }}</span>
</a-button>
<a-button
type="primary"
size="small"
@click="saveCurrentFile"
:disabled="!canSave"
:loading="savingFile"
class="lucide-icon-btn"
>
<Save :size="14" />
<span>保存</span>
</a-button>
</div>
</div> </div>
<div class="editor-main"> <div class="editor-main">
<a-empty <a-empty
@ -143,12 +156,21 @@
description="选择文件以开始编辑" description="选择文件以开始编辑"
class="mt-40" class="mt-40"
/> />
<a-textarea <template v-else>
v-else <MdPreview
v-model:value="fileContent" v-if="viewMode === 'preview'"
class="pure-editor" :modelValue="fileContent"
spellcheck="false" :theme="theme"
/> previewTheme="github"
class="markdown-preview flat-md-preview"
/>
<a-textarea
v-else
v-model:value="fileContent"
class="pure-editor"
spellcheck="false"
/>
</template>
</div> </div>
</div> </div>
</div> </div>
@ -237,11 +259,14 @@
</template> </template>
<script setup> <script setup>
import { computed, onMounted, reactive, ref } from 'vue' import { computed, onMounted, reactive, ref, watch } from 'vue'
import { message, Modal } from 'ant-design-vue' import { message, Modal } from 'ant-design-vue'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime' import relativeTime from 'dayjs/plugin/relativeTime'
import 'dayjs/locale/zh-cn' import 'dayjs/locale/zh-cn'
import { MdPreview } from 'md-editor-v3'
import 'md-editor-v3/lib/preview.css'
import { useThemeStore } from '@/stores/theme'
import { import {
Upload, Upload,
RotateCw, RotateCw,
@ -255,7 +280,9 @@ import {
File, File,
Search, Search,
Box, Box,
FileCode FileCode,
Eye,
Edit3
} from 'lucide-vue-next' } from 'lucide-vue-next'
import { skillApi } from '@/apis/skill_api' import { skillApi } from '@/apis/skill_api'
import FileTreeComponent from '@/components/FileTreeComponent.vue' import FileTreeComponent from '@/components/FileTreeComponent.vue'
@ -263,6 +290,9 @@ import FileTreeComponent from '@/components/FileTreeComponent.vue'
dayjs.extend(relativeTime) dayjs.extend(relativeTime)
dayjs.locale('zh-cn') dayjs.locale('zh-cn')
const themeStore = useThemeStore()
const theme = computed(() => (themeStore.isDark ? 'dark' : 'light'))
const loading = ref(false) const loading = ref(false)
const importing = ref(false) const importing = ref(false)
const savingFile = ref(false) const savingFile = ref(false)
@ -270,6 +300,7 @@ const creatingNode = ref(false)
const savingDependencies = ref(false) const savingDependencies = ref(false)
const activeTab = ref('editor') const activeTab = ref('editor')
const searchQuery = ref('') const searchQuery = ref('')
const viewMode = ref('edit') // 'edit' | 'preview'
const skills = ref([]) const skills = ref([])
const currentSkill = ref(null) const currentSkill = ref(null)
@ -303,6 +334,18 @@ const canSave = computed(() => {
return fileContent.value !== originalFileContent.value return fileContent.value !== originalFileContent.value
}) })
const isMarkdownFile = computed(() => {
if (!selectedPath.value) return false
return selectedPath.value.toLowerCase().endsWith('.md')
})
// markdown
watch(selectedPath, (newPath) => {
if (newPath && !newPath.toLowerCase().endsWith('.md')) {
viewMode.value = 'edit'
}
})
const formatRelativeTime = (time) => (time ? dayjs(time).fromNow() : '-') const formatRelativeTime = (time) => (time ? dayjs(time).fromNow() : '-')
const toolDependencyOptions = computed(() => const toolDependencyOptions = computed(() =>
@ -336,6 +379,7 @@ const resetFileState = () => {
expandedKeys.value = [] expandedKeys.value = []
fileContent.value = '' fileContent.value = ''
originalFileContent.value = '' originalFileContent.value = ''
viewMode.value = 'edit'
} }
const expandAllKeys = (nodes) => const expandAllKeys = (nodes) =>
@ -744,6 +788,21 @@ defineExpose({
margin-left: 4px; margin-left: 4px;
} }
} }
.header-actions {
display: flex;
align-items: center;
gap: 8px;
}
.view-toggle-btn {
background-color: var(--gray-100);
border-color: var(--gray-300);
&:hover {
background-color: var(--gray-200);
border-color: var(--gray-400);
}
}
} }
.editor-main { .editor-main {
@ -779,6 +838,19 @@ defineExpose({
outline: none; outline: none;
} }
} }
.markdown-preview {
flex: 1;
height: 100%;
overflow-y: auto;
:deep(.md-editor) {
height: 100%;
background: var(--gray-0);
}
:deep(.md-editor-preview-wrapper) {
padding: 16px 20px;
}
}
} }
/* 依赖配置 */ /* 依赖配置 */

View File

@ -1,15 +0,0 @@
<template>
<div class="skills-view">
<SkillsManagerComponent />
</div>
</template>
<script setup>
import SkillsManagerComponent from '@/components/SkillsManagerComponent.vue'
</script>
<style scoped lang="less">
.skills-view {
height: 100%;
}
</style>