1.新增菜单后台操作指南,方便管理员查看使用
This commit is contained in:
211
saiadmin-artd/src/views/system/admin_guide/index.vue
Normal file
211
saiadmin-artd/src/views/system/admin_guide/index.vue
Normal file
@@ -0,0 +1,211 @@
|
||||
<template>
|
||||
<div class="art-full-height admin-guide-page">
|
||||
<ElCard class="art-card-xs flex flex-col h-full mt-0" shadow="never">
|
||||
<template #header>
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<b>{{ $t('page.title') }}</b>
|
||||
<div v-if="meta.filePath" class="mt-1 text-xs text-g-500">
|
||||
{{ $t('page.meta.filePath') }}:{{ meta.filePath }}
|
||||
<span v-if="meta.updateTime" class="ml-3">
|
||||
{{ $t('page.meta.updateTime') }}:{{ meta.updateTime }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<ElSpace wrap>
|
||||
<ElButton
|
||||
v-permission="'system:admin_guide:index:read'"
|
||||
:loading="loading"
|
||||
@click="loadContent"
|
||||
>
|
||||
<template #icon>
|
||||
<ArtSvgIcon icon="ri:refresh-line" />
|
||||
</template>
|
||||
{{ $t('page.toolbar.refresh') }}
|
||||
</ElButton>
|
||||
<ElButton
|
||||
v-if="!isEditing"
|
||||
v-permission="'system:admin_guide:index:edit'"
|
||||
type="primary"
|
||||
@click="startEdit"
|
||||
>
|
||||
<template #icon>
|
||||
<ArtSvgIcon icon="ri:pencil-line" />
|
||||
</template>
|
||||
{{ $t('page.toolbar.edit') }}
|
||||
</ElButton>
|
||||
<template v-if="isEditing">
|
||||
<ElButton @click="handleCancel">
|
||||
{{ $t('page.toolbar.cancel') }}
|
||||
</ElButton>
|
||||
<ElButton
|
||||
v-permission="'system:admin_guide:index:save'"
|
||||
type="primary"
|
||||
:loading="saving"
|
||||
@click="handleSave"
|
||||
>
|
||||
<template #icon>
|
||||
<ArtSvgIcon icon="ri:save-line" />
|
||||
</template>
|
||||
{{ $t('page.toolbar.save') }}
|
||||
</ElButton>
|
||||
</template>
|
||||
</ElSpace>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-loading="loading" class="admin-guide-body flex-1 min-h-0 overflow-auto">
|
||||
<SaMdEditor
|
||||
v-if="isEditing"
|
||||
v-model="editContent"
|
||||
height="calc(100vh - 220px)"
|
||||
min-height="480px"
|
||||
/>
|
||||
<MdPreview
|
||||
v-else
|
||||
:model-value="previewContent"
|
||||
:theme="previewTheme"
|
||||
preview-theme="github"
|
||||
class="admin-guide-preview"
|
||||
/>
|
||||
</div>
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import { MdPreview } from 'md-editor-v3'
|
||||
import 'md-editor-v3/lib/preview.css'
|
||||
import SaMdEditor from '@/components/sai/sa-md-editor/index.vue'
|
||||
import { useSettingStore } from '@/store/modules/setting'
|
||||
import api from '@/api/system/admin_guide'
|
||||
|
||||
defineOptions({ name: 'SystemAdminGuide' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const isEditing = ref(false)
|
||||
const previewContent = ref('')
|
||||
const editContent = ref('')
|
||||
const originalContent = ref('')
|
||||
const meta = ref<{ filePath?: string; updateTime?: string }>({})
|
||||
|
||||
const previewTheme = computed(() => (settingStore.isDark ? 'dark' : 'light'))
|
||||
|
||||
/** 静态资源根地址(public 目录,开发环境走代理地址) */
|
||||
const getStaticFileBase = (): string => {
|
||||
const apiUrl = import.meta.env.VITE_API_URL || ''
|
||||
if (apiUrl.startsWith('http')) {
|
||||
return apiUrl.replace(/\/$/, '')
|
||||
}
|
||||
const proxyUrl = import.meta.env.VITE_API_PROXY_URL || ''
|
||||
if (proxyUrl) {
|
||||
return String(proxyUrl).replace(/\/$/, '')
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
const resolveGuideImages = (content: string): string => {
|
||||
const staticBase = getStaticFileBase()
|
||||
if (!staticBase) {
|
||||
return content
|
||||
}
|
||||
return content.replace(/!\[([^\]]*)\]\((\/docs\/picture\/[^)]+)\)/g, (_match, alt, path) => {
|
||||
return ``
|
||||
})
|
||||
}
|
||||
|
||||
const loadContent = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await api.read()
|
||||
const data = res as { content?: string; file_path?: string; update_time?: string }
|
||||
const rawContent = data.content ?? ''
|
||||
previewContent.value = resolveGuideImages(rawContent)
|
||||
editContent.value = rawContent
|
||||
originalContent.value = rawContent
|
||||
meta.value = {
|
||||
filePath: data.file_path,
|
||||
updateTime: data.update_time
|
||||
}
|
||||
} catch {
|
||||
// 错误由 http 工具统一处理
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const startEdit = () => {
|
||||
editContent.value = previewContent.value
|
||||
originalContent.value = previewContent.value
|
||||
isEditing.value = true
|
||||
}
|
||||
|
||||
const handleCancel = async () => {
|
||||
if (editContent.value !== originalContent.value) {
|
||||
try {
|
||||
await ElMessageBox.confirm(t('page.message.cancelConfirm'), {
|
||||
type: 'warning'
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
}
|
||||
editContent.value = originalContent.value
|
||||
isEditing.value = false
|
||||
}
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!isEditing.value) {
|
||||
return
|
||||
}
|
||||
saving.value = true
|
||||
try {
|
||||
const res = await api.save({ content: editContent.value })
|
||||
const data = res as { content?: string; file_path?: string; update_time?: string }
|
||||
const savedContent = data.content ?? editContent.value
|
||||
editContent.value = savedContent
|
||||
originalContent.value = savedContent
|
||||
previewContent.value = resolveGuideImages(savedContent)
|
||||
meta.value = {
|
||||
filePath: data.file_path ?? meta.value.filePath,
|
||||
updateTime: data.update_time ?? meta.value.updateTime
|
||||
}
|
||||
isEditing.value = false
|
||||
} catch {
|
||||
// 错误由 http 工具统一处理
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadContent()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.admin-guide-page {
|
||||
:deep(.el-card__body) {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
padding-top: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.admin-guide-body {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.admin-guide-preview {
|
||||
padding: 8px 4px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user