Browse Source

文档逻辑

master
wangqi 6 months ago
parent
commit
57a3c229a2
  1. 1
      auto-imports.d.ts
  2. 3
      src/api/module/eam/device/document.ts
  3. 2
      src/assets/image/dashboard/file/document.svg
  4. 84
      src/assets/image/dashboard/file/document2.svg
  5. 38
      src/pages/fileDoc/components/markdownDrawer.vue
  6. 110
      src/pages/fileDoc/index.vue
  7. 31
      src/pages/fileDoc/utils.ts
  8. 34
      src/pages/layout.vue
  9. 6
      src/styles/var.css

1
auto-imports.d.ts vendored

@ -6,6 +6,7 @@ @@ -6,6 +6,7 @@
export {}
declare global {
const EffectScope: typeof import('vue')['EffectScope']
const ElLoading: typeof import('element-plus/es')['ElLoading']
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
const computed: typeof import('vue')['computed']
const createApp: typeof import('vue')['createApp']

3
src/api/module/eam/device/document.ts

@ -12,6 +12,8 @@ interface DocFileVO { @@ -12,6 +12,8 @@ interface DocFileVO {
id: number
name: string
parentId: number
isDraft?: 0 | 1
path: string
}
export function createFolder(data: Omit<DocFileVO, 'id'>) {
@ -64,7 +66,6 @@ export function deleteFile(params: { id: number }) { @@ -64,7 +66,6 @@ export function deleteFile(params: { id: number }) {
interface MarkdownVO {
folderId: number
content: string
id: number
isDraft: 0 | 1 // 0: 文章 1: 草稿
}
export interface ContentType {

2
src/assets/image/dashboard/file/document.svg

@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
<style type="text/css">
.st0{clip-path:url(#SVGID_1_);}
.st1{clip-path:url(#SVGID_2_);}
.st2{fill:#3A6ACC;}
.st2{fill:#619925;}
.st3{opacity:0.4;}
.st4{clip-path:url(#SVGID_3_);}
.st5{fill:#FFFFFF;}

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

84
src/assets/image/dashboard/file/document2.svg

@ -0,0 +1,84 @@ @@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1024 1024" style="enable-background:new 0 0 1024 1024;" xml:space="preserve">
<style type="text/css">
.st0{clip-path:url(#SVGID_1_);}
.st1{clip-path:url(#SVGID_2_);}
.st2{fill:#909399;}
.st3{opacity:0.4;}
.st4{clip-path:url(#SVGID_3_);}
.st5{fill:#FFFFFF;}
</style>
<g>
<g>
<g>
<defs>
<rect id="SVGID_7_" x="96" y="96" width="832" height="832"/>
</defs>
<clipPath id="SVGID_1_">
<use xlink:href="#SVGID_7_" style="overflow:visible;"/>
</clipPath>
<g id="组_244" class="st0">
<g id="组_243" transform="translate(8 2)">
<g>
<g>
<g>
<defs>
<rect id="SVGID_9_" x="192" y="120" width="624" height="780"/>
</defs>
<clipPath id="SVGID_2_">
<use xlink:href="#SVGID_9_" style="overflow:visible;"/>
</clipPath>
<g id="组_242" class="st1">
<g id="路径_134">
<path class="st2" d="M270,120c-43,0.1-77.9,35-78,78v624c0.1,43,35,77.9,78,78h468c43-0.1,77.9-35,78-78V352.1L584,120
H270z"/>
</g>
<g id="组_241" transform="translate(30.15)" class="st3">
<g id="组_240">
<g>
<g>
<g>
<defs>
<rect id="SVGID_11_" x="553.8" y="120" width="232.1" height="232.1"/>
</defs>
<clipPath id="SVGID_3_">
<use xlink:href="#SVGID_11_" style="overflow:visible;"/>
</clipPath>
<g id="组_239" class="st4">
<g id="路径_135">
<path class="st5" d="M631.8,352.1h154L553.8,120v154.1C553.9,317.1,588.8,351.9,631.8,352.1"/>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
<g id="直线_87">
<path class="st5" d="M480.3,754.9h-156c-4.5,0-8.1-3.6-8.1-8.1s3.6-8.1,8.1-8.1h156c4.5,0,8.1,3.6,8.1,8.1
S484.8,754.9,480.3,754.9z"/>
</g>
<g id="直线_88">
<path class="st5" d="M558.3,676.5h-234c-4.5,0-8.1-3.6-8.1-8.1s3.6-8.1,8.1-8.1h234c4.5,0,8.1,3.6,8.1,8.1
S562.8,676.5,558.3,676.5z"/>
</g>
<g id="直线_89">
<path class="st5" d="M558.3,598.5h-234c-4.5,0-8.1-3.6-8.1-8.1s3.6-8.1,8.1-8.1h234c4.5,0,8.1,3.6,8.1,8.1
S562.8,598.5,558.3,598.5z"/>
</g>
<g id="直线_90">
<path class="st5" d="M558.3,520.5h-234c-4.5,0-8.1-3.6-8.1-8.1s3.6-8.1,8.1-8.1h234c4.5,0,8.1,3.6,8.1,8.1
C566.4,516.9,562.8,520.5,558.3,520.5z"/>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

38
src/pages/fileDoc/components/markdownDrawer.vue

@ -9,7 +9,7 @@ @@ -9,7 +9,7 @@
>
<template #header>
<div class="header">
<span>{{ data.title }}</span>
<span>{{ data.fileName }}</span>
<div>
<EDFSButton innerText="保存为草稿" type="info" @click="onSave(1)" />
<EDFSButton innerText="发布文章" type="primary" @click="onSave(0)" />
@ -29,8 +29,9 @@ import EDFSButton from '@/components/dashboard/Edfs-button/index.vue' @@ -29,8 +29,9 @@ import EDFSButton from '@/components/dashboard/Edfs-button/index.vue'
import Vditor from 'vditor'
import { type ContentType } from '@/api/module/eam/device/document'
import 'vditor/dist/index.css'
import { vditorToolbar } from '../utils'
interface Props {
data: ContentType
data: ContentType & { fileName: string }
}
const emits = defineEmits(['on-save'])
const props = withDefaults(defineProps<Props>(), {})
@ -65,34 +66,7 @@ function initEditor() { @@ -65,34 +66,7 @@ function initEditor() {
pin: true,
},
mode: 'wysiwyg', // "wysiwyg" | "sv" | "ir" // wysiwyg: sv: ir:
toolbar: [
'edit-mode',
'headings',
'bold',
'italic',
'strike',
'emoji',
'line',
'quote',
'list',
'ordered-list',
'check',
'outdent',
'indent',
'table',
'code',
'inline-code',
'insert-after',
'insert-before',
'link',
'both',
'outline',
'code-theme',
'export',
'undo',
'redo',
'fullscreen',
],
toolbar: vditorToolbar,
counter: {
enable: true,
},
@ -105,10 +79,6 @@ function initEditor() { @@ -105,10 +79,6 @@ function initEditor() {
},
after: () => {
contentEditor.value?.setValue(props?.data?.content ?? '')
console.log(contentEditor.value?.getValue()) //
setTimeout(() => {
console.log(contentEditor.value?.getValue()) //
}, 10000) //
},
})
}

110
src/pages/fileDoc/index.vue

@ -18,7 +18,7 @@ @@ -18,7 +18,7 @@
@visibleChange="onVisibleChange"
ref="contextMenuRef"
>
<div class="file-list" v-if="loading">
<div class="file-list">
<div v-for="item in dataList" :key="item.id" @click="onFileOpen(item)">
<div class="item SourceClass" v-setItem="item">
<div class="file-header">
@ -30,9 +30,15 @@ @@ -30,9 +30,15 @@
></Icon>
</div>
<div class="file-icon">
<!-- {{ !!item.isEdit }} -->
<img :src="item.type === floeType.folder ? Folder : Document" />
<img
:src="
item.type === floeType.folder
? Folder
: item.isDraft === 0
? Document
: Document2
"
/>
</div>
<div class="file-name">
<el-input
@ -103,20 +109,29 @@ import { ArrowRight } from '@element-plus/icons-vue' @@ -103,20 +109,29 @@ import { ArrowRight } from '@element-plus/icons-vue'
import Folder from '@/assets/image/dashboard/file/folder.svg'
import Upload from '@/assets/image/dashboard/file/upload.svg'
import Document from '@/assets/image/dashboard/file/document.svg'
import Document2 from '@/assets/image/dashboard/file/document2.svg'
import { Icon } from '@/components/dashboard/Icon'
import EdfsContextMenu from '@/components/dashboard/Edfs-context-menu/index.vue'
import markdownDrawer from './components/markdownDrawer.vue'
const dropdownMenu = computed(() => {
return menuTarget.value ? fileDropdownMenu : operationDropdownMenu
return menuTarget.value
? fileDropdownMenu
: currentFolderId.value === 0
? operationDropdownMenu.filter(v => v.command === 'newFolder')
: operationDropdownMenu.filter(v => v.command === 'newMarkdown')
})
const message = useMessage()
const currentId = ref(0)
watch(breadcrumbList.value, () => {
currentId.value = breadcrumbList.value[breadcrumbList.value.length - 1].id
})
const currentFolderId = ref(0)
watch(
() => breadcrumbList.value.length,
() => {
currentFolderId.value = breadcrumbList.value[breadcrumbList.value.length - 1].id
}
)
const dataList = ref<any>([])
const loading = ref(false)
@ -125,17 +140,14 @@ async function loadData(id: number = 0) { @@ -125,17 +140,14 @@ async function loadData(id: number = 0) {
const res = await getFileList({
parentId: id,
})
loading.value = false
if (isResError(res)) return
dataList.value = res.data.map((item: any) => ({
...item,
isEdit: false,
}))
loading.value = true
dataList.value = res.data
}
async function onDelete(item: any) {
try {
let res
let res: any
await message.delConfirm()
switch (item.type) {
case floeType.folder:
@ -147,7 +159,7 @@ async function onDelete(item: any) { @@ -147,7 +159,7 @@ async function onDelete(item: any) {
}
if (isResError(res)) return
message.success('删除成功')
loadData(currentId.value)
loadData(currentFolderId.value)
} catch (error) {}
}
@ -158,7 +170,7 @@ function onFile(e: any) { @@ -158,7 +170,7 @@ function onFile(e: any) {
function onNewFolder() {
const folder = {
name: '新建文件夹',
name: '未命名文件夹',
type: floeType.folder,
isEdit: true,
}
@ -168,7 +180,7 @@ function onNewFolder() { @@ -168,7 +180,7 @@ function onNewFolder() {
function onNewMarkdown() {
const markdown = {
name: '新建文档',
name: '未命名文档',
type: floeType.file,
isEdit: true,
}
@ -234,23 +246,26 @@ async function onSaveRename(item: any) { @@ -234,23 +246,26 @@ async function onSaveRename(item: any) {
if (!item.isEdit) return
item.name = editFileName.value
await nextTick()
item.isEdit = false
await nextTick()
const find = findItem(item.id)
find.isEdit = false
await onSaveFile(item)
editFileName.value = ''
}
async function onRename(item: any) {
if (!item) return
item.isEdit = true
await nextTick()
const find = findItem(item.id)
find.isEdit = true
editFileName.value = item.name
await nextTick()
editInputRefs.value[`editInputRef_${item.id}`].focus()
editInputRefs.value[`editInputRef_${item.id}`].select()
}
const findItem = (id: number) => dataList.value.find((v: any) => v.id === id)
async function onSaveFile(item: any) {
const isAdd = !item.id
loading.value = true
switch (item.type) {
case floeType.folder:
await saveFolder(isAdd, item)
@ -260,21 +275,30 @@ async function onSaveFile(item: any) { @@ -260,21 +275,30 @@ async function onSaveFile(item: any) {
break
}
loadData(currentId.value)
loadData(currentFolderId.value)
}
function getPath() {
return currentFolderId.value === 0
? ''
: breadcrumbList.value[breadcrumbList.value.length - 1].path
}
async function saveFolder(isAdd: boolean, item: any) {
getPath()
let res
if (isAdd) {
res = await createFolder({
name: item.name,
parentId: currentId.value,
parentId: currentFolderId.value,
path: getPath(),
})
} else {
res = await updateFolder({
id: item.id,
name: item.name,
parentId: currentId.value,
parentId: currentFolderId.value,
path: getPath(),
})
}
if (isResError(res)) return
@ -284,34 +308,56 @@ async function saveFile(isAdd: boolean, item: any) { @@ -284,34 +308,56 @@ async function saveFile(isAdd: boolean, item: any) {
if (isAdd) {
res = await createdFile({
name: item.name + '.md',
parentId: currentId.value,
parentId: currentFolderId.value,
isDraft: 1,
path: getPath(),
})
} else {
res = await updateFile({
id: item.id,
name: item.name,
parentId: currentId.value,
parentId: currentFolderId.value,
path: getPath(),
})
}
if (isResError(res)) return
}
const fullscreenLoading = ref()
function openFullScreen() {
fullscreenLoading.value = ElLoading.service({
lock: true,
text: '数据加载中请稍等...',
target: document.querySelector('.firmware-management-wrap') as HTMLElement,
})
}
watch(loading, val => {
if (val) {
openFullScreen()
} else {
fullscreenLoading.value?.close()
}
})
// markdown
const curMarkdown = ref() as Ref<ContentType>
const curMarkdown = ref() as Ref<ContentType & { fileName: string }>
const curMdId = ref()
const isShowMdDrawer = ref(false)
async function onOpenMarkdownDrawer(id: number) {
curMdId.value = id
const res = await getMarkdown({ folderId: id })
if (isResError(res)) return
curMarkdown.value = res?.data
curMarkdown.value = Object.assign({}, res.data, { fileName: findItem(id).name })
isShowMdDrawer.value = true
}
async function onSaveMd(isDraft: 0 | 1, data: string) {
const res = await updateMarkdown({
content: data,
folderId: currentId.value,
id: curMarkdown.value?.id,
folderId: curMdId.value,
isDraft,
})
@ -319,7 +365,7 @@ async function onSaveMd(isDraft: 0 | 1, data: string) { @@ -319,7 +365,7 @@ async function onSaveMd(isDraft: 0 | 1, data: string) {
message.success(isDraft === 1 ? '保存成功' : '发布成功')
isShowMdDrawer.value = false
curMarkdown.value = null as any
loadData(currentId.value)
loadData(currentFolderId.value)
}
onMounted(() => {
loadData()

31
src/pages/fileDoc/utils.ts

@ -10,10 +10,39 @@ export const breadcrumbList = ref([{ name: '全部文件', id: 0 }]) @@ -10,10 +10,39 @@ export const breadcrumbList = ref([{ name: '全部文件', id: 0 }])
export const fileDropdownMenu = [
{ command: 'open', label: '打开', icon: '' },
{ command: 'rename', label: '重命名', icon: '' },
{ command: 'delete', label: '删除', icon: '' },
// { command: 'delete', label: '删除', icon: '' },
]
export const operationDropdownMenu = [
{ command: 'newFolder', label: '新建文件夹', icon: Folder },
// { command: 'upload', label: '上传', icon: Upload },
{ command: 'newMarkdown', label: '新建文档', icon: Document },
]
export const vditorToolbar = [
'edit-mode',
'headings',
'bold',
'italic',
'strike',
'emoji',
'line',
'quote',
'list',
'ordered-list',
'check',
'outdent',
'indent',
'table',
'code',
'inline-code',
'insert-after',
'insert-before',
'link',
'both',
'outline',
'code-theme',
'export',
'undo',
'redo',
'fullscreen',
]

34
src/pages/layout.vue

@ -69,7 +69,14 @@ @@ -69,7 +69,14 @@
{{ currentTime }}
</div>
<div class="right">
<el-dropdown
<el-switch
v-model="themeSwitch"
:active-action-icon="Moon"
:inactive-action-icon="Sunny"
style="--el-switch-on-color: #2C2C2C; --el-switch-off-color: #F2F2F2 "
@change="toggle"
/>
<!-- <el-dropdown
style="width: 100%; height: 100%"
@command="command => handleCommand(command)"
>
@ -84,14 +91,13 @@ @@ -84,14 +91,13 @@
<el-dropdown-item command="logout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-dropdown> -->
</div>
</el-header>
<main class="layout-main">
<el-config-provider :locale="zhCn">
<RouterView />
</el-config-provider>
</main>
</div>
</div>
@ -114,6 +120,7 @@ import { THEME_KEY, INIT_OPTIONS_KEY } from 'vue-echarts' @@ -114,6 +120,7 @@ import { THEME_KEY, INIT_OPTIONS_KEY } from 'vue-echarts'
import { registerTheme } from 'echarts/core'
const { theme, toggle } = useTheme()
import { customDark, customLight } from '@/utils/dark'
import { useIcon } from '@/utils/useIcon'
registerTheme('dark', customDark)
registerTheme('light', customLight)
provide(THEME_KEY, theme)
@ -121,6 +128,11 @@ provide(INIT_OPTIONS_KEY, { @@ -121,6 +128,11 @@ provide(INIT_OPTIONS_KEY, {
devicePixelRatio: 2,
})
const Sunny = useIcon({icon: 'line-md:moon-filled-to-sunny-filled-loop-transition', color: '#2C2C2C'})
const Moon = useIcon({icon: 'line-md:sunny-filled-loop-to-moon-filled-transition', color: '#2C2C2C'})
const themeSwitch = ref(theme.value === 'dark')
const route = useRoute()
const breadcrumbItems = computed(() => {
@ -235,8 +247,6 @@ async function loginOut() { @@ -235,8 +247,6 @@ async function loginOut() {
push('/file/document')
} catch {}
}
</script>
<style scoped lang="scss">
@ -245,10 +255,11 @@ $header-height: 48px; @@ -245,10 +255,11 @@ $header-height: 48px;
width: 100%;
height: 100%;
display: flex;
background: var(--bg);
.menu-box {
max-width: 216px;
height: 100%;
height: calc(100% - 16px);
position: relative;
transition: width 0.3s ease;
overflow: hidden;
@ -267,7 +278,7 @@ $header-height: 48px; @@ -267,7 +278,7 @@ $header-height: 48px;
padding-left: 20px;
column-gap: 6px;
font-size: 16px;
color: #fff;
color: var(--text-color);
font-weight: 400;
.logo-icon {
@ -278,7 +289,7 @@ $header-height: 48px; @@ -278,7 +289,7 @@ $header-height: 48px;
.logo-label {
font-size: 16px;
line-height: 22px;
font-weight: 400;
font-weight: 500;
}
.icon {
@ -297,21 +308,22 @@ $header-height: 48px; @@ -297,21 +308,22 @@ $header-height: 48px;
background-color: var(--layout-menu-bg);
:deep(.el-menu-item:hover),
:deep(.el-sub-menu__title:hover) {
background: #272b35;
background: var(--menu-hover-bg);
}
:deep(.el-menu-item),
:deep(.el-sub-menu__title) {
font-family: Alibaba-PuHuiTi-R;
font-size: 14px;
color: #fff;
color: var(--text-color);
line-height: 22px;
font-weight: 400;
height: 40px;
border-left: 3px solid transparent;
}
:deep(.el-menu-item.is-active) {
background-color: #2c342c;
background-color: var(--menu-active-bg);
color: #619925;
border-left: 3px solid #619925;
}
.mr-5px {

6
src/styles/var.css

@ -3,6 +3,8 @@ html[data-theme='dark'] { @@ -3,6 +3,8 @@ html[data-theme='dark'] {
--text-color: #ffffff;
--layout-header-bg: #1b1d23;
--layout-menu-bg: #1b1d23;
--menu-active-bg: #2c342c;
--menu-hover-bg: #272b35;
--warp-bg: #212327;
--table-header-bg: #3b3d40;
--table-header-text-color: #cdcecf;
@ -25,7 +27,9 @@ html[data-theme='light'] { @@ -25,7 +27,9 @@ html[data-theme='light'] {
--bg: #f1f2f6;
--text-color: #4d4d4d;
--layout-header-bg: #ffffff;
--layout-menu-bg: #1b1d23;
--layout-menu-bg: #fff;
--menu-active-bg: #F5FCEE;
--menu-hover-bg: #f5f5f5;
--warp-bg: #ffffff;
--table-header-bg: #e8e9ee;
--table-header-text-color: #030303;

Loading…
Cancel
Save