文档展示样式优化

This commit is contained in:
sswiki
2023-09-26 16:13:45 +08:00
parent 66904f0bb4
commit 75577350ba
16 changed files with 1779 additions and 10701 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -12,8 +12,7 @@
"@soerenmartius/vue3-clipboard": "^0.1.2",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"ant-design": "^1.0.0",
"ant-design-vue": "^4.0.0-rc.6",
"ant-design-vue": "^4.0.3",
"axios": "^0.19.2",
"core-js": "^3.26.1",
"echarts": "^5.4.0",
@@ -34,15 +33,17 @@
"xss": "^1.0.14"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.2.3",
"@vue/cli-plugin-babel": "^5.0.8",
"@vue/compiler-sfc": "^3.3.4",
"less": "^4.1.3",
"less-loader": "^11.1.3",
"node-sass": "^9.0.0",
"sass-loader": "^13.3.2",
"vite": "^4.3.9",
"vite-plugin-style-import": "^2.0.0",
"vue-template-compiler": "^2.7.14"
"vue-template-compiler": "^2.7.14",
"@vitejs/plugin-vue": "^4.2.3",
"@vue/compiler-sfc": "^3.3.4",
"unplugin-auto-import": "^0.5.11",
"unplugin-vue-components": "^0.25.1",
"vite": "^4.4.9",
"vite-plugin-style-import": "^2.0.0"
}
}

View File

@@ -0,0 +1,3 @@
.i-icon {
vertical-align: middle;
}

View File

@@ -0,0 +1,14 @@
<template>
<span class="i-icon">
<svg viewBox="0 0 1024 1024" width="1em" height="1em">
<path :fill="fill" d="M832 384H576V128H192v768h640V384zm-26.496-64L640 154.496V320h165.504zM160 64h480l256 256v608a32 32 0 0 1-32 32H160a32 32 0 0 1-32-32V96a32 32 0 0 1 32-32zm160 448h384v64H320v-64zm0-192h160v64H320v-64zm0 384h384v64H320v-64z"></path>
</svg>
</span>
</template>
<script setup>
import {toRefs, ref, reactive, onMounted, provide, computed, watch, useSlots, defineEmits, defineProps, defineExpose} from 'vue';
const props = defineProps({
fill: {type: String, default: '#515151'},
});
</script>

View File

@@ -19,9 +19,9 @@
<script setup>
import {useStoreDisplay} from '@/store/wikiDisplay.js'
import LeftSideBar from './LeftSideBar'
import RightHeader from './RightHeader'
import RightResize from './RightResize'
import LeftSideBar from './LeftSidebar.vue'
import RightHeader from './RightHeader.vue'
import RightResize from './RightResize.vue'
let storeDisplay = useStoreDisplay();
const rightAsideWidthChange = (width) =>{
storeDisplay.rightAsideWidth = width
@@ -63,16 +63,10 @@
.el-header {
color: #333;
line-height: 100px;
height: 60px !important;
border-bottom: 0.5px solid #eaeaea;
}
.fold-btn {
color: #ccc !important;
font-size: 18px;
}
.head-icon {
margin-right: 15px;
margin-top: 15px;
@@ -119,81 +113,74 @@
</style>
<style lang="scss">
.space-folder-box {
margin-left: 10px;
margin-bottom: 10px;
position: relative;
.space-folder-box {
margin-left: 10px;
margin-bottom: 10px;
position: relative;
}
.folder-action-dropdown-btn {
padding: 0 8px;
height: 25px;
position: absolute;
right: 0;
.wiki-page-tree-box {
overflow-y: auto;
overflow-x: hidden;
padding-bottom: 30px;
.el-tree-node__content {
height: 35px;
position: relative;
.page-tree-node {
width: 100%;
.label {
.el-icon {
vertical-align: middle;
}
.text {
margin-left: 5px;
vertical-align: middle;
max-width: calc(100% - 40px);
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.rename-input {
width: 90%;
}
.page-action-box {
position: absolute;
right: 0;
top: 0;
height: 35px;
line-height: 35px;
background: #fff;
border-radius: 4px;
display: none;
.page-action-dropdown-btn {
padding: 0 8px;
height: 35px;
margin-top: -1px;
}
.el-button + .el-button {
margin-left: 0;
}
}
.page-action-box.renaming {
display: none !important;
}
}
.wiki-page-tree-box {
overflow-y: auto;
overflow-x: hidden;
padding-bottom: 30px;
.el-tree-node__content {
height: 35px;
position: relative;
.page-tree-node {
width: 100%;
.label {
.el-icon {
vertical-align: middle;
}
.text {
margin-left: 5px;
vertical-align: middle;
max-width: calc(100% - 40px);
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.rename-input {
width: 90%;
}
.page-action-box {
position: absolute;
right: 0;
top: 0;
height: 35px;
line-height: 35px;
background: #fff;
border-radius: 4px;
display: none;
.page-action-dropdown-btn {
padding: 0 8px;
height: 35px;
margin-top: -1px;
}
.el-button+.el-button {
margin-left: 0;
}
}
.page-action-box.renaming {
display: none !important;
}
}
&:hover .page-action-box {
display: block;
}
}
&:hover .page-action-box {
display: block;
}
}
}
</style>

View File

@@ -21,43 +21,24 @@
</template>
<template v-slot:addMenuNode="{node,data}">
<div class="page-tree-node" @mouseover="changeNodeOptionStatus(data) ">
<div style="width: calc(100% - 30px);overflow: hidden;text-overflow: ellipsis;white-space: nowrap;">
<div class="node-content">
<!--图标-->
<el-icon v-if="data.editorType === 0" class="clickAddIcon"
style="margin-right: 5px;vertical-align: middle;">
<svg width="1em" height="1em" viewBox="0 0 48 48" fill="none">
<path d="M5 8C5 6.89543 5.89543 6 7 6H19L24 12H41C42.1046 12 43 12.8954 43 14V40C43 41.1046 42.1046 42 41 42H7C5.89543 42 5 41.1046 5 40V8Z"
fill="none" stroke="currentColor" stroke-width="4" stroke-linejoin="round"></path>
<path d="M43 22H5" stroke="currentColor" stroke-width="4" stroke-linejoin="round"></path>
<path d="M5 16V28" stroke="currentColor" stroke-width="4" stroke-linecap="round"
stroke-linejoin="round"></path>
<path d="M43 16V28" stroke="currentColor" stroke-width="4" stroke-linecap="round"
stroke-linejoin="round"></path>
</svg>
</el-icon>
<el-icon v-else-if="data.editorType === 1" class="clickAddIcon"
style="margin-right: 5px;vertical-align: middle;">
<svg width="1em" height="1em" viewBox="0 0 48 48" fill="none">
<rect x="6" y="6" width="36" height="36" rx="3" fill="none" stroke="currentColor"
stroke-width="4"></rect>
<path d="M14 16L18 32L24 19L30 32L34 16" stroke="currentColor" stroke-width="4"
stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
</el-icon>
<el-icon v-else-if="data.editorType === 2" class="clickAddIcon"
style="margin-right: 5px;vertical-align: middle;">
<el-icon-document/>
</el-icon>
<span class="left-icon">
<template v-if="data.editorType === 0">
<FolderOpen v-if="node.expanded" class="el-icon"/>
<FolderClose v-else class="el-icon"/>
</template>
<template v-else-if="data.editorType === 1"><IconParkWord class="el-icon"/></template>
<template v-else-if="data.editorType === 2"><IconDocument class="el-icon"/></template>
</span>
<!--标题-->
<el-tooltip :content="data.tags" placement="top-start" :show-after="500"
v-if="data.shareStatus !== undefined">
<a-tag color="#f50">{{filterShareStatus(data.shareStatus)}}</a-tag>
<el-tooltip :content="data.tags" placement="top-start" :show-after="500" v-if="data.shareStatus !== undefined">
<a-tag color="warning" style="margin-inline-end: 4px;padding-inline: 4px;">{{filterShareStatus(data.shareStatus)}}</a-tag>
</el-tooltip>
<a-input v-if="data.renaming" v-model:value="data.name" class="rename-input" placeholder="请输入文档名称"
@blur="doRename(node,data)" @click.stop/>
<a-input v-if="data.renaming" v-model:value="data.name" class="rename-input" placeholder="请输入文档名称" @blur="doRename(node,data)" @click.stop/>
<span v-else style="vertical-align: middle;margin-right: 5px">
<el-tooltip :content="node.label" placement="top-start" :show-after="700">{{ node.label }}</el-tooltip>
</span>
<el-tooltip :content="node.label" placement="top-start" :show-after="700">{{ node.label }}</el-tooltip>
</span>
<!--操作-->
<div class="page-action-box" :class="data.renaming?'renaming':''" @click.stop>
<AddMenu
@@ -73,59 +54,23 @@
<el-button :icon="MoreFilled" text class="page-action-dropdown-btn"></el-button>
<template #overlay>
<a-menu>
<a-menu-item key="0" @click="rename(node,data)">
<el-icon class="clickAddIcon" style="margin-right: 5px">
<svg width="1em" height="1em" viewBox="0 0 48 48" fill="none">
<path d="M42 26V40C42 41.1046 41.1046 42 40 42H8C6.89543 42 6 41.1046 6 40V8C6 6.89543 6.89543 6 8 6L22 6"
stroke="currentColor" stroke-width="4" stroke-linecap="round"
stroke-linejoin="round"></path>
<path d="M14 26.7199V34H21.3172L42 13.3081L34.6951 6L14 26.7199Z"
fill="none" stroke="currentColor" stroke-width="4"
stroke-linejoin="round"></path>
</svg>
</el-icon>
重命名
<a-menu-item @click="rename(node,data)">
<IconParkEditTwo class="el-icon"/> 重命名
</a-menu-item>
<a-menu-item key="1" @click="deleteWikiPage(data.shareStatus)">
<el-icon class="clickAddIcon" style="margin-right: 5px">
<svg width="1em" height="1em" viewBox="0 0 48 48" fill="none">
<path d="M9 10V44H39V10H9Z" fill="none" stroke="currentColor"
stroke-width="4" stroke-linejoin="round"></path>
<path d="M20 20V33" stroke="currentColor" stroke-width="4"
stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M28 20V33" stroke="currentColor" stroke-width="4"
stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M4 10H44" stroke="currentColor" stroke-width="4"
stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M16 10L19.289 4H28.7771L32 10H16Z" fill="none"
stroke="currentColor" stroke-width="4"
stroke-linejoin="round"></path>
</svg>
</el-icon>
删除
<a-menu-item @click="deleteWikiPage(data.shareStatus)">
<IconParkDelete class="el-icon"/> 删除
</a-menu-item>
<a-sub-menu key="2" title="移动文档">
<template #icon>
<BlockOutlined/>
</template>
<a-menu-item key="3" @click="openMoveMenu(false)">
<el-icon class="clickAddIcon" style="margin-right: 5px">
<DocumentCopy/>
</el-icon>
复制文档
<a-sub-menu title="移动文档">
<template #icon><IconParkIntersection/></template>
<a-menu-item @click="openMoveMenu(false)">
<IconParkCopy/> 复制文档
</a-menu-item>
<a-menu-item key="4" @click="openMoveMenu(true)">
<el-icon class="clickAddIcon" style="margin-right: 5px">
<Scissor/>
</el-icon>
迁移文档
<a-menu-item @click="openMoveMenu(true)">
<IconParkCuttingOne/> 迁移文档
</a-menu-item>
</a-sub-menu>
<a-menu-item key="5" @click="openTemplateCreate(data.shareStatus !== undefined)"
v-if="data.editorType !== 0">
<BuildOutlined/>
设为模板
<a-menu-item v-if="data.editorType !== 0" @click="openTemplateCreate(data.shareStatus !== undefined)">
<IconParkPageTemplate/> 设为模板
</a-menu-item>
</a-menu>
</template>
@@ -135,268 +80,278 @@
</div>
</template>
</LeftSidebarCli>
<templateManage ref="templateManageRef" :pageId="storePage.optionPageId" :spaceId="storeSpace.chooseSpaceId"
@doGetPageList="doGetPageList"/>
<create-space ref="createSpaceRef" @success="loadSpaceList"></create-space>
<a-modal
v-model:open="visibleMoveMenu"
title="选择"
@ok="handleOk"
@cancel="handleCancel"
ok-text="确认"
cancel-text="取消"
:confirm-loading="aModalWaiting"
:destroyOnClose=true
:closable=false>
<LeftSidebarCli
:readOnly=true
:wikiPageList="moveToWikiPageList"
:spaceOptions="storeSpace.spaceOptions"
:nowPageId="moveToPageId"
:choiceSpace="moveToSpaceId"
@setNowPageId="setNowPageId"
@doGetPageList="doGetPageList"
@spaceChangeEvents="spaceChangeEvents"/>
<TemplateManage ref="templateManageRef" :pageId="storePage.optionPageId" :spaceId="storeSpace.chooseSpaceId" @doGetPageList="doGetPageList"/>
<create-space ref="createSpaceRef" @success="loadSpaceList"/>
<a-modal v-model:open="visibleMoveMenu"
title="选择"
@ok="handleOk"
@cancel="handleCancel"
ok-text="确认"
cancel-text="取消"
:confirm-loading="aModalWaiting"
:destroyOnClose="true"
:closable="false">
<LeftSidebarCli :readOnly="true"
:wikiPageList="moveToWikiPageList"
:spaceOptions="storeSpace.spaceOptions"
:nowPageId="moveToPageId"
:choiceSpace="moveToSpaceId"
@setNowPageId="setNowPageId"
@doGetPageList="doGetPageList"
@spaceChangeEvents="spaceChangeEvents"/>
</a-modal>
</template>
<script setup>
import {
Document as ElIconDocument,
Fold as ElIconFold,
Expand as ElIconExpand,
Upload as ElIconUpload,
Bell as ElIconBell,
Setting as ElIconSetting,
Plus as ElIconPlus,
Check as ElIconCheck,
Files,
Scissor,
DocumentCopy,
MoreFilled
} from '@element-plus/icons-vue'
import {
MoreFilled
} from '@element-plus/icons-vue'
import {
FolderClose,
FolderOpen,
Word as IconParkWord,
Copy as IconParkCopy,
CuttingOne as IconParkCuttingOne,
Intersection as IconParkIntersection,
Delete as IconParkDelete,
EditTwo as IconParkEditTwo,
PageTemplate as IconParkPageTemplate,
} from '@icon-park/vue-next'
import {ref, onMounted,} from 'vue';
import {useRouter, useRoute} from "vue-router";
import {ElMessageBox, ElMessage} from 'element-plus'
import pageApi from '../../assets/api/page'
import CreateSpace from '../space/CreateSpace.vue'
import TemplateManage from '../template/TemplateManage.vue'
import AddMenu from '../leftSideBar/AddMenu.vue'
import IconDocument from '@/components/base/IconDocument.vue'
import LeftSidebarCli from '../leftSideBar/LeftSidebarCli.vue'
import {useStoreDisplay} from '@/store/wikiDisplay.js'
import {useStorePageData} from "@/store/pageData";
import {DownOutlined, BuildOutlined, BlockOutlined} from '@ant-design/icons-vue';
import {useStoreSpaceData} from "@/store/spaceData";
import {ref, onMounted,} from 'vue';
import { useRouter, useRoute} from "vue-router";
import {ElMessageBox, ElMessage} from 'element-plus'
import pageApi from '../../assets/api/page'
import CreateSpace from '../space/CreateSpace'
import TemplateManage from '../template/TemplateManage'
import RightResize from './RightResize.vue'
import AddMenu from '../LeftSidebar/AddMenu.vue'
import LeftSidebarCli from '../LeftSidebar/LeftSidebarCli.vue'
import {useStoreDisplay} from '@/store/wikiDisplay.js'
import {useStorePageData} from "@/store/pageData";
import {DownOutlined, BuildOutlined, BlockOutlined} from '@ant-design/icons-vue';
import {useStoreSpaceData} from "@/store/spaceData";
let route = useRoute();
let router = useRouter();
let storePage = useStorePageData();
let storeDisplay = useStoreDisplay();
let storeSpace = useStoreSpaceData();
let route = useRoute();
let router = useRouter();
let storePage = useStorePageData();
let storeDisplay = useStoreDisplay();
let storeSpace = useStoreSpaceData();
// 空间搜索相关
let nowSpaceShow = ref({});
let moveToPageId = ref(0);
let moveToSpaceId = ref(0);
let moveToWikiPageList = ref([]);
// 空间搜索相关
let nowSpaceShow = ref({});
let moveToPageId = ref(0);
let moveToSpaceId = ref(0);
let moveToWikiPageList = ref([]);
// 页面展示相关
let wikiPage = ref({});
let wikiPageExpandedKeys = ref([]);
let rightAsideWidth = ref(300);
let optionPageId = ref('');
let visibleMoveMenu = ref(false);
let onlyMoveMode = ref(false);
let aModalWaiting = ref(false);
let templateManageRef = ref(null)
// 页面展示相关
let wikiPage = ref({});
let wikiPageExpandedKeys = ref([]);
let rightAsideWidth = ref(300);
let optionPageId = ref('');
let visibleMoveMenu = ref(false);
let onlyMoveMode = ref(false);
let aModalWaiting = ref(false);
let templateManageRef = ref(null)
onMounted(() => {
init()
})
const init = () => {
loadSpaceList()
}
onMounted(()=>{
init()
})
const init = () => {
loadSpaceList()
const openTemplateCreate = (exsit) => {
templateManageRef.value.showTemplateCreate(exsit)
}
const createWikiByTemplate = () => {
templateManageRef.value.showTemplateManage()
}
const filterShareStatus = (data) => {
if (data === 1) {
return '公共模板'
}
return '个人模板'
}
const openTemplateCreate = (exsit) => {
templateManageRef.value.showTemplateCreate(exsit)
}
const createWikiByTemplate = () => {
templateManageRef.value.showTemplateManage()
}
const filterShareStatus = (data) => {
if (data === 1) {
return '公共模板'
}
return '个人模板'
}
const openMoveMenu = (onlyMove) => {
onlyMoveMode.value = onlyMove
visibleMoveMenu.value = true
moveToPageId.value = storePage.choosePageId
moveToSpaceId.value = storeSpace.chooseSpaceId
moveToWikiPageList.value = storePage.wikiPageList
}
const handleOk = (onlyMove) => {
aModalWaiting.value = true
if (onlyMoveMode.value) {
pageApi.movePage({
const openMoveMenu = (onlyMove) => {
onlyMoveMode.value = onlyMove
visibleMoveMenu.value = true
moveToPageId.value = storePage.choosePageId
moveToSpaceId.value = storeSpace.chooseSpaceId
moveToWikiPageList.value = storePage.wikiPageList
}
const handleOk = (onlyMove) => {
aModalWaiting.value = true
if (onlyMoveMode.value) {
pageApi.movePage({
"id": storePage.optionPageId,
"spaceId": storeSpace.chooseSpaceId,
"moveToPageId": moveToPageId.value,
"moveToSpaceId": moveToSpaceId.value
})
.then((json) => {
doGetPageList(null)
ElMessage.success('迁移成功')
handleCancel()
aModalWaiting.value = false
}).catch((e) => {
.then((json) => {
doGetPageList(null)
ElMessage.success('迁移成功')
handleCancel()
aModalWaiting.value = false
})
return
}
pageApi.copyPage({
}).catch((e) => {
aModalWaiting.value = false
})
return
}
pageApi.copyPage({
"id": storePage.optionPageId,
"spaceId": storeSpace.chooseSpaceId,
"moveToPageId": moveToPageId.value,
"moveToSpaceId": moveToSpaceId.value
})
.then((json) => {
doGetPageList(null)
ElMessage.success('复制成功')
handleCancel()
aModalWaiting.value = false
}).catch((e) => {
.then((json) => {
doGetPageList(null)
ElMessage.success('复制成功')
handleCancel()
aModalWaiting.value = false
}).catch((e) => {
aModalWaiting.value = false
})
return
}
const handleCancel = () => {
visibleMoveMenu.value = false
moveToPageId.value = 0
moveToSpaceId.value = 0
moveToWikiPageList.value = []
}
const deleteWikiPage = (share) => {
let msg = '确定要删除此页面及其所有子页面吗?'
if (share !== undefined) {
msg = '选中的页面是:' + filterShareStatus(share) + '删除后无法使用此模板! 确定要删除此页面及其所有子页面吗?'
}
ElMessageBox.confirm(msg, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
let param = {pageId: optionPageId.value};
pageApi.pageDelete(param).then(() => {
ElMessage.success('已删除')
doGetPageList(null)
});
}).catch(() => {
});
}
const choosePageIdFunc = (id) => {
storePage.optionPageId = id
}
const setNowPageId = (id, readOnly) => {
if (readOnly) {
moveToPageId.value = id
return
}
storePage.choosePageId = id
}
const rename = (node, data) => {
data.renaming = true
}
const doRename = (node, data) => {
pageApi.renamePage({"id": data.id, "name": data.name})
.then((json) => {
doGetPageList(null)
ElMessage.success('重命名成功')
data.renaming = false
})
}
const changeNodeOptionStatus = (param) => {
optionPageId.value = param.id
}
let createSpaceRef = ref();
const spaceChangeEvents = (data, readonly) => {
storePage.pageInfo = {}
if (readonly) {
moveToSpaceId.value = data
setNowPageId(0, readonly)
let param = {spaceId: moveToSpaceId.value}
pageApi.pageList(param).then((json) => {
moveToWikiPageList.value = json.data || []
})
return
}
const handleCancel = () => {
visibleMoveMenu.value = false
moveToPageId.value = 0
moveToSpaceId.value = 0
moveToWikiPageList.value = []
if (data === 0) {
// 新建空间
createSpaceRef.value.show();
} else if (data === -1) {
// 管理空间
router.push({path: '/space/manage'});
} else {
storePage.choosePageId = 0;
storeSpace.chooseSpaceId = Number(data);
nowSpaceShow.value = storeSpace.spaceList.find((item) => item.id === data);
storeSpace.spaceInfo = nowSpaceShow.value;
doGetPageList(null);
router.push({path: '/home', query: {spaceId: data}});
}
const deleteWikiPage = (share) => {
let msg = '确定要删除此页面及其所有子页面吗?'
if (share !== undefined) {
msg = '选中的页面是:' + filterShareStatus(share) + '删除后无法使用此模板! 确定要删除此页面及其所有子页面吗?'
}
ElMessageBox.confirm(msg, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
let param = {pageId: optionPageId.value};
pageApi.pageDelete(param).then(() => {
ElMessage.success('已删除')
doGetPageList(null)
});
}).catch(() => {
});
}
const choosePageIdFunc = (id) => {
storePage.optionPageId = id
}
const setNowPageId = (id, readOnly) => {
if (readOnly) {
moveToPageId.value = id
return
}
storePage.choosePageId = id
}
const rename = (node, data) => {
data.renaming = true
}
const doRename = (node, data) => {
pageApi.renamePage({"id": data.id, "name": data.name})
.then((json) => {
doGetPageList(null)
ElMessage.success('重命名成功')
data.renaming = false
})
}
const changeNodeOptionStatus = (param) => {
optionPageId.value = param.id
}
let createSpaceRef = ref();
const spaceChangeEvents = (data, readonly) => {
storePage.pageInfo = {}
if (readonly) {
moveToSpaceId.value = data
setNowPageId(0, readonly)
let param = {spaceId: moveToSpaceId.value}
pageApi.pageList(param).then((json) => {
moveToWikiPageList.value = json.data || []
})
return
}
if (data === 0) {
// 新建空间
createSpaceRef.value.show();
} else if (data === -1) {
// 管理空间
router.push({path: '/space/manage'});
} else {
storePage.choosePageId = 0;
storeSpace.chooseSpaceId = Number(data);
nowSpaceShow.value = storeSpace.spaceList.find((item) => item.id === data);
storeSpace.spaceInfo = nowSpaceShow.value;
doGetPageList(null);
router.push({path: '/home', query: {spaceId: data}});
}
}
const loadSpaceList = (spaceId) => {
pageApi.spaceList({}).then((json) => {
storeSpace.spaceList = json.data || [];
let spaceOptionsNew = [];
storeSpace.spaceList.forEach((item) => spaceOptionsNew.push({label: item.name, value: item.id}));
storeSpace.spaceOptions = spaceOptionsNew;
if (storeSpace.spaceList.length > 0) {
let nowSpaceId = spaceId;
let nowSpaceShowTemp = storeSpace.spaceList.find((item) => item.id === spaceId);
if (!nowSpaceShowTemp) {
nowSpaceShowTemp = storeSpace.spaceList[0];
nowSpaceId = nowSpaceShowTemp.id;
}
nowSpaceShow.value = nowSpaceShowTemp;
storeSpace.spaceInfo = nowSpaceShowTemp;
storeSpace.chooseSpaceId = nowSpaceId;
storePage.choosePageId = 0;
doGetPageList(null);
// TODO 在首页时跳转
try {
if (route.path === '/home') {
router.push({path: '/home', query: {spaceId: nowSpaceId}});
}
} catch (e) {
console.log(e);
}
}
const loadSpaceList = (spaceId) => {
pageApi.spaceList({}).then((json) => {
storeSpace.spaceList = json.data || [];
let spaceOptionsNew = [];
storeSpace.spaceList.forEach((item) => spaceOptionsNew.push({label: item.name, value: item.id}));
storeSpace.spaceOptions = spaceOptionsNew;
if (storeSpace.spaceList.length > 0) {
let nowSpaceId = spaceId;
let nowSpaceShowTemp = storeSpace.spaceList.find((item) => item.id === spaceId);
if (!nowSpaceShowTemp) {
nowSpaceShowTemp = storeSpace.spaceList[0];
nowSpaceId = nowSpaceShowTemp.id;
}
})
}
nowSpaceShow.value = nowSpaceShowTemp;
storeSpace.spaceInfo = nowSpaceShowTemp;
storeSpace.chooseSpaceId = nowSpaceId;
storePage.choosePageId = 0;
doGetPageList(null);
// TODO 在首页时跳转
try {
if (route.path === '/home') {
router.push({path: '/home', query: {spaceId: nowSpaceId}});
}
} catch (e) {
console.log(e);
}
}
})
}
const doGetPageList = (parentId, node) => {
let param = {spaceId: storeSpace.chooseSpaceId}
pageApi.pageList(param).then((json) => {
storePage.wikiPageList = json.data || []
})
}
defineExpose({init})
const doGetPageList = (parentId, node) => {
let param = {spaceId: storeSpace.chooseSpaceId}
pageApi.pageList(param).then((json) => {
storePage.wikiPageList = json.data || []
})
}
defineExpose({init})
</script>
<style lang="scss">
.page-tree-node {
.node-content {
width: calc(100% - 30px);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
.left-icon {
margin-right: 6px;
}
}
}
</style>

View File

@@ -1,115 +1,57 @@
<template>
<el-row class="right-header-box">
<el-col :span="1">
<el-button @click="turnLeftCollapse" v-if="storeDisplay.showMenu" text :icon="ElIconFold" class="fold-btn"></el-button>
<el-button @click="turnLeftCollapse" v-else text :icon="ElIconExpand" class="fold-btn"></el-button>
</el-col>
<el-col :span="9" class="title-info-view">
<div class="wikititle" ref="wikiTitleRef" v-if="storePage.pageInfo.name">
<span class="create-user-time">
{{storePage.pageInfo.name}}
</span>
<br/>
<span v-if="storePage.pageInfo.updateUserName">
{{ storePage.pageInfo.updateUserName }}
<span class="split"></span>
{{ storePage.pageInfo.updateTime }}
<span class="split">修改</span>
</span>
<span v-else class="create-user-time">
{{ storePage.pageInfo.createUserName }}
<span class="split"></span>
{{ storePage.pageInfo.createTime }}
<span class="split">创建</span>
</span>
<el-row>
<el-col :span="12">
<div class="left-action-box">
<div class="collapse-box">
<el-button @click="turnLeftCollapse" v-if="storeDisplay.showMenu" text :icon="ElIconFold" class="fold-btn"></el-button>
<el-button @click="turnLeftCollapse" v-else text :icon="ElIconExpand" class="fold-btn"></el-button>
</div>
<div class="title-time-box">
<div class="title">
<span class="text">{{storePage.pageInfo.name || ''}}</span>
</div>
<div class="time">最近修改{{storePage.pageInfo.updateTime || ''}}</div>
</div>
</div>
</el-col>
<el-col :span="12" style="text-align: right" class = "dropdown-menu">
<el-tooltip v-if="storePage.pageAuth.canEdit === 1 && storePage.pageInfo.name && storePage.pageInfo.editorType !== 0" effect="dark" content="编辑文档"
placement="top">
<ElIconEdit @click="editWiki" type="primary" class="right-header-icon"></ElIconEdit>
</el-tooltip>
<el-tooltip v-if="storePage.pageAuth.canEdit === 1 && storePage.pageInfo.name && storePage.pageInfo.editorType !== 0" effect="dark" content="文档相关"
placement="top">
<ElIconChatLineRound @click="showCommentWiki" class="right-header-icon"></ElIconChatLineRound>
</el-tooltip>
<el-tooltip v-if="storePage.pageInfo.name" effect="dark" content="更多操作" placement="top">
<el-dropdown trigger="click" class="action-btn more-dropdown" >
<el-icon class="right-header-icon" style="margin-top: 18px;margin-left: 5px">
<el-icon-more/>
</el-icon>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="editWikiAuth" v-if="storePage.pageAuth.canConfigAuth === 1 && storePage.pageInfo.editorType !== 0"
:icon="ElIconSCheck">权限设置
</el-dropdown-item>
<el-dropdown-item @click="showOpenPage" v-if="storeSpace.spaceInfo.openDoc === 1 && storePage.pageInfo.editorType !== 0"
:icon="ElIconShare">查看开放文档
</el-dropdown-item>
<el-dropdown-item @click="showMobileView" v-if="storeSpace.spaceInfo.openDoc === 1 && storePage.pageInfo.editorType !== 0"
:icon="ElIconMobilePhone">手机端查看
</el-dropdown-item>
<el-dropdown-item @click="exportWord" :icon="ElIconDownload" v-if="storePage.pageInfo.editorType !== 0">导出为Word文档</el-dropdown-item>
<el-dropdown-item @click="deleteWikiPage" v-if="storePage.pageAuth.canDelete === 1"
:icon="ElIconDelete">删除
</el-dropdown-item>
</el-dropdown-menu>
<el-col :span="12" style="text-align: right;">
<div class="header-action-box">
<el-tooltip v-if="storePage.pageAuth.canEdit === 1" content="编辑文档">
<el-button class="hover-button" @click="editWiki" text><IconParkEdit size="18"/></el-button>
</el-tooltip>
<el-tooltip content="文档沟通">
<el-button class="hover-button" @click="showCommentWiki" text><IconParkCommunication size="18"/></el-button>
</el-tooltip>
<UserMessagePopover/>
<a-dropdown trigger="click" placement="bottom" overlayClassName="header-action-more-dropdown">
<span style="line-height: 60px;display:inline-block;margin: 0 12px;">
<el-button :icon="ElIconMoreFilled" class="hover-button" text></el-button>
</span>
<template #overlay>
<a-menu>
<a-menu-item @click="editWikiAuth" v-if="storePage.pageAuth.canConfigAuth === 1"><el-icon><ElIconSCheck/></el-icon> 权限设置</a-menu-item>
<a-menu-item @click="showOpenPage" v-if="storeSpace.spaceInfo.openDoc === 1"><el-icon><ElIconShare/></el-icon> 查看开放文档</a-menu-item>
<a-menu-item @click="showMobileView" v-if="storeSpace.spaceInfo.openDoc === 1"><el-icon><ElIconMobilePhone/></el-icon> 手机端查看</a-menu-item>
<a-menu-item @click="exportWord"><el-icon><ElIconDownload/></el-icon>导出为Word</a-menu-item>
<a-menu-divider />
<a-menu-item @click="deleteWikiPage" v-if="storePage.pageAuth.canDelete === 1" class="delete"><el-icon><ElIconDelete/></el-icon> 删除</a-menu-item>
</a-menu>
</template>
</el-dropdown>
</el-tooltip>
<span class="header-right-user-name">{{userSelfInfo.userName}}</span>
<el-popover v-model:visible="userMessagePopVisible" placement="bottom" trigger="click" width="600">
<template v-slot:reference>
<el-badge :is-dot="haveNotReadUserMessage" >
<el-icon class="right-header-icon" >
<el-icon-bell/>
</el-icon>
</el-badge>
</template>
<div style="margin-bottom: 10px">
<span style="font-size: 14px; font-weight: bold">通知</span>
<el-link v-if="haveNotReadUserMessage" :icon="ElIconCheck" style="float: right" type="primary"
@click="readAllUserMessage">本页标记已读
</el-link>
</div>
<div class="header-user-message">
<el-table :data="userMessageList" border max-height="500" size="small"
style="width: 100%; margin-bottom: 5px">
<el-table-column label="操作人" prop="operatorUserName" width="100px"></el-table-column>
<el-table-column label="操作时间" prop="creationTime" width="140px"></el-table-column>
<el-table-column label="内容">
<template v-slot="scope">
{{ scope.row.msgContent }}
<el-badge :is-dot="scope.row.msgStatus == 0"
style="line-height: 10px; padding-right: 5px">
<el-link type="primary" @click="showUserMessage(scope.row)">查看</el-link>
</el-badge>
</template>
</el-table-column>
</el-table>
<div class="page-info-box">
<el-pagination
:current-page="userMsgParam.pageNum"
:page-size="userMsgParam.pageSize"
:total="userMsgTotalCount"
layout="prev, pager, next, total"
@current-change="handleCurrentChange">
</el-pagination>
</div>
</div>
</el-popover>
<el-dropdown trigger="click" @command="userSettingDropdown" style="vertical-align: bottom;">
<el-icon class="right-header-icon">
<el-icon-setting/>
</el-icon>
<template v-slot:dropdown>
<el-dropdown-menu>
<el-dropdown-item command="console">控制台</el-dropdown-item>
<el-dropdown-item command="aboutDoc">关于</el-dropdown-item>
<el-dropdown-item command="userSignOut" divided>退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</a-dropdown>
<a-dropdown trigger="click" placement="bottom" overlayClassName="header-action-user-dropdown">
<span style="line-height: 60px;display:inline-block;">
<el-button :icon="ElIconUser" class="hover-button" text></el-button>
</span>
<template #overlay>
<a-menu>
<a-menu-item @click="showConsole">控制台</a-menu-item>
<a-menu-item @click="showAbout">关于</a-menu-item>
<a-menu-divider />
<a-menu-item @click="userSignOut">退出登录</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</div>
</el-col>
</el-row>
<MobileQrScanDialog v-model:visible="mobileScanDialogVisible"/>
@@ -121,315 +63,273 @@
</template>
<script setup>
import {
Setting as ElIconSetting,
ArrowDown as ElIconArrowDown,
View as ElIconView,
Close as ElIconClose,
Delete as ElIconDelete,
Loading as ElIconLoading,
CircleCheck as ElIconCircleCheck,
CircleClose as ElIconCircleClose,
ChatLineRound as ElIconChatLineRound,
Upload as ElIconUpload,
Edit as ElIconEdit,
Timer as ElIconTime,
More as ElIconMore,
Stamp as ElIconSCheck,
Bell as ElIconBell,
Share as ElIconShare,
Iphone as ElIconMobilePhone,
Download as ElIconDownload,
Fold as ElIconFold,
Expand as ElIconExpand,
Check as ElIconCheck,
Back as ElIconBack,
} from '@element-plus/icons-vue'
import {toRefs, ref, reactive, onMounted, watch, computed} from 'vue';
import {onBeforeRouteUpdate, useRoute, useRouter} from "vue-router";
import {ElMessageBox, ElMessage, ElNotification} from 'element-plus';
import pageApi from '@/assets/api/page'
import PageAuthDialog from '../../views/page/show/PageAuthDialog.vue'
import MobileQrScanDialog from '../../views/page/show/MobileQrScanDialog.vue'
import {useStorePageData} from "@/store/pageData";
import {useStoreDisplay} from "@/store/wikiDisplay";
import {useStoreSpaceData} from "@/store/spaceData";
import {useStoreUserData} from "@/store/userData";
import userApi from "@/assets/api/user";
import AboutDialog from "../../views/common/AboutDialog"
import {
Fold as ElIconFold,
Expand as ElIconExpand,
Delete as ElIconDelete,
Stamp as ElIconSCheck,
Share as ElIconShare,
Iphone as ElIconMobilePhone,
Download as ElIconDownload,
MoreFilled as ElIconMoreFilled,
Setting as ElIconSetting,
User as ElIconUser,
} from '@element-plus/icons-vue'
import {
Star as IconParkStar,
Edit as IconParkEdit,
Communication as IconParkCommunication,
} from '@icon-park/vue-next'
import {toRefs, ref, reactive, onMounted, watch, defineEmits, computed} from 'vue';
import {useRouter, useRoute} from "vue-router";
import { ElMessageBox, ElMessage } from 'element-plus'
import { useStoreDisplay } from '@/store/wikiDisplay.js'
import { useStorePageData } from '@/store/pageData.js'
import { useStoreUserData } from '@/store/userData.js'
import pageApi from "@/assets/api/page";
import {useStoreSpaceData} from "@/store/spaceData";
import userApi from "@/assets/api/user";
import PageAuthDialog from '@/views/page/show/PageAuthDialog.vue'
import MobileQrScanDialog from '@/views/page/show/MobileQrScanDialog.vue'
import AboutDialog from "@/views/common/AboutDialog.vue"
import UserMessagePopover from "./UserMessagePopover.vue"
let storePage = useStorePageData();
let storeDisplay = useStoreDisplay();
let router = useRouter();
let storeSpace = useStoreSpaceData();
let storeUser = useStoreUserData();
let storePage = useStorePageData();
let storeDisplay = useStoreDisplay();
let storeUser = useStoreUserData();
let storeSpace = useStoreSpaceData();
let userSelfInfo = ref({});
let userMessageList = ref([]);
let haveNotReadUserMessage = ref(false);
let userMessagePopVisible = ref(false);
let userMsgTotalCount = ref(0);
let userMsgParam = ref({sysType: 2, pageNum: 1, pageSize: 20,});
let titleInput = ref('')
const emit = defineEmits(['collapse']);
onMounted(()=>{
init()
})
const init=()=>{
getSelfUserInfo()
loadUserMessageList();
}
let pageAuthDialogVisible = ref(false);
const editWikiAuth = () => {
pageAuthDialogVisible.value = true;
}
const showOpenPage = () => {
if (storeSpace.spaceInfo.openDoc !== 1) {
ElMessage.warning('该空间未开放,无法查看开放文档地址');
let turnLeftCollapse = () => {
storeDisplay.showMenu = !storeDisplay.showMenu;
setTimeout(() => {
if (storeDisplay.showMenu) {
storeDisplay.rightAsideWidth = 301;
} else {
let routeUrl = router.resolve({
path: '/page/share/view',
query: {pageId: storePage.pageInfo.id, space: storeSpace.spaceInfo.uuid}
});
window.open(routeUrl.href, '_blank');
storeDisplay.rightAsideWidth = 1;
}
}, 100);
};
// 清除控制台,和设置逻辑放远一点不容易被察觉
watch(() => storeUser.ts, () => {
console.clear();
});
watch(() => storePage.favoritePageChange, (newVal) => {
if (storePage.needVersion) {
ElMessage.warning("不支持在指定版本后收藏页面");
return;
}
const deleteWikiPage = () => {
ElMessageBox.confirm('确定要删除此页面及其所有子页面吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
pageApi.pageDelete({pageId: storePage.pageInfo.id}).then(() => {
pageApi.pageList({spaceId: storeSpace.chooseSpaceId}).then((json) => {
storePage.wikiPageList = json.data || []
}).then(()=>{
router.push({path: '/home', query: {spaceId: storePage.pageInfo.spaceId}});
})
});
}).catch((e) => {
console.log(e)
});
let favoritePage = (favorite) => {
storePage.favoritePageChange = {
id: storePage.pageInfo.id,
favorite: favorite,
};
};
const editWiki = () => {
// 锁定页面并进入编辑页面
storePage.pageIsUnlock = false;
let param = {pageId: storePage.pageInfo.id};
pageApi.pageLock(param).then(() => {
router.push({path: '/page/edit', query: {pageId: storePage.pageInfo.id}});
});
}
const showCommentWiki = () => {
storeDisplay.commentShow = !storeDisplay.commentShow;
}
let pageAuthDialogVisible = ref(false);
const editWikiAuth = () => {
pageAuthDialogVisible.value = true;
}
const showOpenPage = () => {
if (storeSpace.spaceInfo.openDoc !== 1) {
ElMessage.warning('该空间未开放,无法查看开放文档地址');
} else {
let routeUrl = router.resolve({
path: '/page/share/view',
query: {pageId: storePage.pageInfo.id, space: storeSpace.spaceInfo.uuid}
});
window.open(routeUrl.href, '_blank');
}
// 下载为Word
let downloadFormRef = ref();
let downloadFormParam = ref({url: 'zyplayer-doc-wiki/page/download', param: {}});
const exportWord = () => {
downloadFormParam.value.param = {pageId: storePage.pageInfo.id};
setTimeout(() => downloadFormRef.value.submit(), 0);
}
// 手机扫码
let mobileScanDialogVisible = ref(false);
const showMobileView = () => {
if (storeSpace.spaceInfo.openDoc !== 1) {
ElMessage.warning('该空间未开放,无法查看开放文档地址');
} else {
mobileScanDialogVisible.value = true;
}
}
const showCommentWiki = () => {
storePage.commentShow = true;
storePage.commentActiveTab = 'comment';
}
const editWiki = () => {
// 锁定页面并进入编辑页面
storePage.pageIsUnlock = false
titleInput.value = storePage.pageInfo.name
let param = {pageId: storePage.pageInfo.id};
pageApi.pageLock(param).then(() => {
router.push({path: '/page/edit', query: {pageId: storePage.pageInfo.id}});
});
}
const turnLeftCollapse = () => {
storeDisplay.showMenu = !storeDisplay.showMenu
setTimeout(() => {
if (storeDisplay.showMenu) {
storeDisplay.rightAsideWidth = 301
} else {
storeDisplay.rightAsideWidth = 1
}
}, 100)
}
const loadUserMessageIfPopVisible = () => {
if (!userMessagePopVisible.value) {
loadUserMessageList()
}
}
const loadUserMessageList = () => {
userApi.getUserMessageList(userMsgParam.value).then((res) => {
userMessageList.value = res.data || []
userMsgTotalCount.value = res.total || 0
haveNotReadUserMessage.value =
userMessageList.value.filter((item) => item.msgStatus == 0).length > 0
})
}
let aboutDialogRef = ref()
const userSettingDropdown = (command) => {
console.log('command:' + command)
if (command == 'userSignOut') {
userSignOut()
} else if (command == 'aboutDoc') {
aboutDialogRef.value.show()
} else if (command == 'myInfo') {
router.push({path: '/user/myInfo'})
} else if (command == 'console') {
window.open(import.meta.env.VITE_APP_BASE_API, '_blank')
} else {
ElMessage.warning('暂未开放')
}
}
const userSignOut = () => {
userApi.userLogout().then(() => {
location.reload()
})
}
const readAllUserMessage = () => {
let msgIds = []
userMessageList.value
.filter((item) => item.msgStatus == 0)
.forEach((item) => {
msgIds.push(item.id)
})
if (msgIds.length <= 0) return
userApi.readUserMessage({ids: msgIds.join(',')}).then(() => {
ElMessage.success('标记成功')
loadUserMessageList()
})
}
const showUserMessage = (row) => {
if (row.msgStatus == 0) {
userApi.readUserMessage({ids: row.id}).then(() => {
loadUserMessageList()
}
const deleteWikiPage = () => {
ElMessageBox.confirm('确定要删除此页面及其所有子页面吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
pageApi.pageDelete({pageId: storePage.pageInfo.id}).then(() => {
pageApi.pageList({spaceId: storeSpace.chooseSpaceId}).then((json) => {
storePage.wikiPageList = json.data || []
}).then(()=>{
router.push({path: '/home', query: {spaceId: storePage.pageInfo.spaceId}});
})
}
if (row.msgType >= 2 && row.msgType <= 14) {
router.push({path: '/page/show', query: {pageId: row.dataId}})
userMessagePopVisible.value = false
}
});
}).catch((e) => {
console.log(e)
});
}
// 下载为Word
let downloadFormRef = ref();
let downloadFormParam = ref({url: 'zyplayer-doc-wiki/page/download', param: {}});
const exportWord = () => {
downloadFormParam.value.param = {pageId: storePage.pageInfo.id};
setTimeout(() => downloadFormRef.value.submit(), 0);
}
// 手机扫码
let mobileScanDialogVisible = ref(false);
const showMobileView = () => {
if (storeSpace.spaceInfo.openDoc !== 1) {
ElMessage.warning('该空间未开放,无法查看开放文档地址');
} else {
mobileScanDialogVisible.value = true;
}
const handleCurrentChange = (val) => {
userMsgParam.value.pageNum = val
loadUserMessageList()
}
const getSelfUserInfo = () => {
userApi.getSelfUserInfo().then((json) => {
userSelfInfo.value = json.data
storeUser.userInfo = json.data
})
}
defineExpose({init})
}
const userSignOut = () => {
userApi.userLogout().then(() => {
location.reload();
});
}
let aboutDialogRef = ref();
const showAbout = () => {
aboutDialogRef.value.show();
}
const showConsole = () => {
window.open(import.meta.env.VITE_APP_BASE_API, '_blank')
}
</script>
<style scoped lang="scss">
.page-action-box {
padding: 30px 0;
<style lang="scss" scoped>
.left-action-box {
display: flex;
.page-create-info {
font-size: 14px;
color: #888;
.collapse-box {
line-height: 60px;
.split {
padding: 0 4px;
}
}
.page-action-list {
text-align: right;
}
.fold-btn {
font-size: 18px;
padding: 4px 10px;
color: #888 !important;
}
}
.title-time-box {
flex: 1;
padding: 5px 0 6px 10px;
overflow: hidden;
.title {
overflow: hidden;
white-space: nowrap;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
line-height: 28px;
.text {
vertical-align: middle;
}
}
.time {
font-size: 12px;
overflow: hidden;
white-space: nowrap;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
}
}
.title-setting-box {
.setting-title {
font-size: 18px;
line-height: 60px;
padding-left: 10px;
.text {
display: inline-block;
}
}
}
}
</style>
<style lang="scss">
.right-header-box {
.page-action-list {
.header-right-user-name{
margin-left: 15px;
margin-right: 10px;
}
.dropdown-menu{
display: flex;
align-items: center;
position: absolute;
top: 10px;
right:0;
}
text-align: right;
.left-action-box {
.title-time-box {
.title {
.i-icon {
vertical-align: middle;
.el-icon {
margin-right: 4px;
}
.action-btn + .action-btn {
margin-left: 15px;
}
.upload-page-file {
display: inline;
vertical-align: middle;
}
.more-dropdown {
vertical-align: middle;
}
svg {
vertical-align: unset;
}
}
}
</style>
<style lang="scss">
.right-header-box{
padding: 5px 0;
overflow: hidden;;
white-space: nowrap;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
line-height: 28px;
.fold-btn{
margin-top: 7px;
padding: 10px 0;
color: #3d3a3a !important;
font-size: 18px;
}
.title-info-view{
font-size: 14px;
color: #454343;
.split{
padding: 0 4px;
}
}
.title-info-view-right{
text-align: right;
margin-left: 5px;
font-size: 14px;
color: #454343;
.split{
padding: 0 4px;
}
}
.page-title-input{
margin-top: 3px;
margin-left: 5px;
width: 100%;
height: 45px;
}
.page-info-news{
font-size: 14px;
line-height: 20px;
margin-top: 3px
}
.right-header-icon{
cursor: pointer;
width: 20px;
margin: 15px 10px;
vertical-align: bottom;
}
}
}
.title-setting-box {
.setting-title {
.tips-icon {
vertical-align: 0.1em;
margin-left: 6px;
}
}
}
.header-action-box {
display: inline-block;
line-height: 60px;
.disabled-btn-box {
margin-right: 10px;
}
.hover-button {
border: 0;
color: #888;
.i-icon svg {
vertical-align: middle;
}
}
.hover-button:focus {
color: #888;
background: #fff;
}
.hover-button:hover {
color: #888;
background: #eaeaea;
}
}
.header-action-user-dropdown {
width: 120px;
}
.header-action-more-dropdown {
width: 140px;
.delete {
color: #f00;
}
.delete.disabled {
cursor: not-allowed;
color: var(--el-text-color-disabled);
}
.cant-hover {
cursor: default;
}
.cant-hover:hover {
background: #fff;
}
}
</style>

View File

@@ -1,7 +1,5 @@
<template>
<div ref="rightResizeRef" class="right-resize">
<i ref="rightResizeBarRef">...</i>
</div>
<div ref="rightResizeRef" class="right-resize"></div>
</template>
<script setup>
@@ -13,16 +11,12 @@ onMounted(() => {
dragChangeRightAsideWidth();
});
let rightResizeRef = ref();
let rightResizeBarRef = ref();
const dragChangeRightAsideWidth = () => {
// 保留this引用
let resize = rightResizeRef.value
let resizeBar = rightResizeBarRef.value
resize.onmousedown = (e) => {
let startX = e.clientX
// 颜色改变提醒
resize.style.background = '#ccc'
resizeBar.style.background = '#aaa'
resize.left = resize.offsetLeft
document.onmousemove = (e2) => {
// 计算并应用位移量
@@ -39,9 +33,6 @@ const dragChangeRightAsideWidth = () => {
}
}
document.onmouseup = () => {
// 颜色恢复
resize.style.background = '#fafafa'
resizeBar.style.background = '#ccc'
document.onmousemove = null
document.onmouseup = null
}
@@ -50,24 +41,15 @@ const dragChangeRightAsideWidth = () => {
}
</script>
<style scoped>
<style scoped lang="scss">
.right-resize {
width: 5px;
height: 100%;
cursor: w-resize;
background: #fafafa;
}
width: 5px;
height: 100%;
cursor: w-resize;
background: #fafafa;
.right-resize i {
margin-top: 300px;
width: 5px;
height: 35px;
display: inline-block;
word-wrap: break-word;
word-break: break-all;
line-height: 8px;
border-radius: 5px;
&:hover {
background: #ccc;
color: #888;
}
}
</style>

View File

@@ -18,7 +18,7 @@ import {onBeforeUnmount, ref, onMounted, watch, defineProps, nextTick, defineEmi
import {onBeforeRouteUpdate, useRouter, useRoute} from "vue-router";
import {ElMessageBox, ElMessage} from 'element-plus'
import pageApi from '../../assets/api/page'
import PageTree from '../shareLayout/PageTree'
import PageTree from '../shareLayout/PageTree.vue'
import 'vant/es/icon/style/index';
import 'vant/es/popup/style/index';
import 'vant/es/cell/style/index';

View File

@@ -0,0 +1,156 @@
<template>
<span class="user-message-popover">
<el-tooltip content="文档通知">
<el-badge :value="notReadMessageNum" :max="99" :hidden="notReadMessageNum <= 0">
<el-button ref="remindButtonRef" class="hover-button" text><IconParkRemind size="18"/></el-button>
</el-badge>
</el-tooltip>
<el-popover v-model:visible="userMessagePopVisible" placement="bottom" :width="700" trigger="click"
popper-class="header-user-remind" ref="popoverRef"
:virtual-ref="remindButtonRef" virtual-triggering>
<div class="header">
<span class="title">文档通知</span>
<el-link v-if="notReadMessageNum > 0" :icon="ElIconCheck" type="primary" @click="readAllUserMessage">本页标记已读</el-link>
</div>
<div class="header-user-message">
<el-table :data="userMessageList" stripe max-height="400" style="width: 100%; margin-bottom: 5px">
<el-table-column label="操作时间" prop="creationTime" width="150px"></el-table-column>
<el-table-column label="内容" prop="msgContent" show-overflow-tooltip></el-table-column>
<el-table-column width="60px">
<template v-slot="scope">
<el-badge :is-dot="scope.row.msgStatus === 0" style="line-height: 10px; padding-right: 5px">
<el-link type="primary" @click="showUserMessage(scope.row)">查看</el-link>
</el-badge>
</template>
</el-table-column>
</el-table>
<div class="page-info-box">
<el-pagination background
:current-page="userMsgParam.pageNum"
:page-size="userMsgParam.pageSize"
:total="userMsgTotalCount"
layout="prev, pager, next, total"
@current-change="handleCurrentChange">
</el-pagination>
</div>
</div>
</el-popover>
</span>
</template>
<script setup>
import {
Document as ElIconDocument,
Fold as ElIconFold,
Expand as ElIconExpand,
Bell as ElIconBell,
Setting as ElIconSetting,
Plus as ElIconPlus,
Check as ElIconCheck,
} from '@element-plus/icons-vue'
import {
Remind as IconParkRemind,
} from '@icon-park/vue-next'
import {onBeforeUnmount, toRefs, ref, reactive, onMounted, watch, defineProps, nextTick, defineEmits, defineExpose, computed} from 'vue';
import {onBeforeRouteUpdate, useRouter, useRoute} from "vue-router";
import {ElMessageBox, ElMessage} from 'element-plus'
import pageApi from "@/assets/api/page";
import userApi from "@/assets/api/user";
import {useStoreDisplay} from '@/store/wikiDisplay.js'
let route = useRoute();
let router = useRouter();
const storeDisplay = useStoreDisplay();
let remindButtonRef = ref();
let userMessageList = ref([]);
let notReadMessageNum = ref(0);
let userMessagePopVisible = ref(false);
let userMsgTotalCount = ref(0);
let userMsgParam = ref({sysType: 2, pageNum: 1, pageSize: 20});
let messageInterval;
onMounted(() => {
loadUserMessageList();
messageInterval = setInterval(() => {
loadUserMessageList();
}, 10 * 1000);
});
onBeforeUnmount(() => {
if (messageInterval) {
clearInterval(messageInterval);
}
});
const loadUserMessageList = () => {
userApi.getUserMessageList(userMsgParam.value).then((res) => {
userMessageList.value = res.data || [];
userMsgTotalCount.value = res.total || 0;
notReadMessageNum.value = userMessageList.value.filter((item) => item.msgStatus === 0).length;
});
}
const showUserMessage = (row) => {
if (row.msgStatus === 0) {
userApi.readUserMessage({ids: row.id}).then(() => {
loadUserMessageList()
})
}
if (row.msgType >= 2 && row.msgType <= 14) {
router.push({path: '/page/show', query: {pageId: row.dataId}})
userMessagePopVisible.value = false
}
}
const readAllUserMessage = () => {
let msgIds = []
userMessageList.value.filter((item) => item.msgStatus === 0).forEach((item) => {
msgIds.push(item.id)
})
if (msgIds.length <= 0) return
userApi.readUserMessage({ids: msgIds.join(',')}).then(() => {
ElMessage.success('标记成功')
loadUserMessageList()
})
}
const handleCurrentChange = (val) => {
userMsgParam.value.pageNum = val
loadUserMessageList()
}
</script>
<style lang="scss">
.user-message-popover {
margin-left: 12px;
}
.header-user-remind {
.header {
margin-bottom: 10px;
display: flex;
align-items: center;
justify-content: space-between;
.title {
font-size: 14px;
font-weight: bold;
}
}
.header-user-message {
.el-table {
.cell {
padding: 0 6px;
}
/**覆盖箭头颜色*/
.el-popper__arrow::before {
border: 1px solid var(--el-text-color-primary);
background: var(--el-text-color-primary);
}
}
.page-info-box {
.el-pagination {
justify-content: end;
}
}
}
}
</style>

View File

@@ -1,41 +1,24 @@
<template>
<a-dropdown :trigger="['click']" @click="choosePageIdFunc(props.funcId)">
<el-button :icon="ElIconPlus" text class="folder-action-dropdown-btn"></el-button>
<el-button :icon="ElIconPlus" text class="add-menu-dropdown-btn"></el-button>
<template #overlay>
<a-menu>
<a-menu-item key="1" @click="createWiki(1,props.funcId)">
<el-icon class="clickAddIcon" style="margin-right: 5px">
<svg width="1em" height="1em" viewBox="0 0 48 48" fill="none"><rect x="6" y="6" width="36" height="36" rx="3" fill="none" stroke="currentColor" stroke-width="4"></rect><path d="M14 16L18 32L24 19L30 32L34 16" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path></svg>
</el-icon>
创建富文本
<IconDocument fill="#498ba7"/> 创建富文本
</a-menu-item>
<a-menu-item key="2" @click="createWiki(2,props.funcId)">
<el-icon class="clickAddIcon" style="margin-right: 5px">
<el-icon-document/>
</el-icon>
创建Markdown
<IconParkWord fill="#558ff2"/> 创建Markdown
</a-menu-item>
<a-menu-item key="0" @click="createWiki(0,props.funcId)">
<el-icon class="clickAddIcon" style="margin-right: 5px">
<svg width="1em" height="1em" viewBox="0 0 48 48" fill="none"><path d="M5 8C5 6.89543 5.89543 6 7 6H19L24 12H41C42.1046 12 43 12.8954 43 14V40C43 41.1046 42.1046 42 41 42H7C5.89543 42 5 41.1046 5 40V8Z" fill="none" stroke="currentColor" stroke-width="4" stroke-linejoin="round"></path><path d="M43 22H5" stroke="currentColor" stroke-width="4" stroke-linejoin="round"></path><path d="M5 16V28" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path><path d="M43 16V28" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path></svg>
</el-icon>
创建文件夹
<FolderOpen fill="#ffd149"/> 创建文件夹
</a-menu-item>
<a-menu-item key="4" @click="createWikiByTemplate(props.funcId)">
<BuildOutlined/>
从模板创建
<IconParkPageTemplate/> 从模板创建
</a-menu-item>
<a-menu-item key="3">
<el-tooltip content="支持MDZIP格式图片和MD文件请放到同级目录并配置同级相对路径" placement="right-start" :show-after="300">
<a-upload
v-model:file-list="fileList"
name="file"
:multiple="false"
:customRequest="doAUpload">
<el-icon class="clickAddIcon" style="margin-right: 5px" type="primary">
<ElIconUpload/>
</el-icon>
导入
<a-upload v-model:file-list="fileList" name="file" :multiple="false" :customRequest="doAUpload">
<IconParkAfferent/> 导入
</a-upload>
</el-tooltip>
</a-menu-item>
@@ -45,102 +28,103 @@
</template>
<script setup>
import {
Document as ElIconDocument,
Upload as ElIconUpload,
Plus as ElIconPlus,
} from '@element-plus/icons-vue'
import {
ref,
defineProps,
defineEmits,
} from 'vue';
import {useRouter} from "vue-router";
import {ElMessage} from 'element-plus'
import pageApi from '../../assets/api/page'
import axios from "axios";
import { BuildOutlined } from '@ant-design/icons-vue';
import {
Plus as ElIconPlus,
} from '@element-plus/icons-vue'
import {
FolderOpen,
Word as IconParkWord,
Afferent as IconParkAfferent,
PageTemplate as IconParkPageTemplate,
} from '@icon-park/vue-next'
import {ref, defineProps, defineEmits} from 'vue';
import {useRouter} from "vue-router";
import {ElMessage} from 'element-plus'
import pageApi from '../../assets/api/page'
import axios from "axios";
import IconDocument from '@/components/base/IconDocument.vue'
let router = useRouter();
let uploadFileUrl = ref(import.meta.env.VITE_APP_BASE_API + '/zyplayer-doc-wiki/page/file/upload');
let fileList = ref([]);
let emit = defineEmits(['choosePageIdFunc', 'doGetPageList','createWikiByTemplate'])
let props = defineProps({
choiceSpace: Number,
choosePageId: Number,
nowPageId: Number,
funcId: Number
});
const doAUpload = (data) => {
let formData = new FormData()
formData.append('files', data.file)
formData.append('pageId', props.choosePageId)
if (props.choosePageId === 0) {
formData.append('id', props.choiceSpace)
let router = useRouter();
let uploadFileUrl = ref(import.meta.env.VITE_APP_BASE_API + '/zyplayer-doc-wiki/page/file/upload');
let fileList = ref([]);
let emit = defineEmits(['choosePageIdFunc', 'doGetPageList', 'createWikiByTemplate'])
let props = defineProps({
choiceSpace: Number,
choosePageId: Number,
nowPageId: Number,
funcId: Number
});
const doAUpload = (data) => {
let formData = new FormData()
formData.append('files', data.file)
formData.append('pageId', props.choosePageId)
if (props.choosePageId === 0) {
formData.append('id', props.choiceSpace)
}
formData.append('importFlag', true)
axios({
url: uploadFileUrl.value,
method: 'post',
data: formData,
headers: {'Content-Type': 'multipart/form-data'},
timeout: 10000,
withCredentials: true,
}).then((res) => {
fileList.value = []
if (res.data.errCode === 200) {
ElMessage.success('导入成功')
}
formData.append('importFlag', true)
axios({
url: uploadFileUrl.value,
method: 'post',
data: formData,
headers: {'Content-Type': 'multipart/form-data'},
timeout: 10000,
withCredentials: true,
}).then((res) => {
fileList.value = []
if (res.data.errCode === 200) {
ElMessage.success('导入成功')
}
if (res.data.errCode === 300) {
ElMessage.warning(res.data.errMsg)
ElMessage.warning('文件太多可能超时,如果是超时,请稍等后刷新查看列表~')
}
emit('doGetPageList', null)
if (res.data.errCode === 300) {
ElMessage.warning(res.data.errMsg)
ElMessage.warning('文件太多可能超时,如果是超时,请稍等后刷新查看列表~')
}
emit('doGetPageList', null)
}).catch((e) => {
fileList.value = []
}).catch((e) => {
fileList.value = []
emit('doGetPageList', null)
ElMessage.error('导入失败:' + e.message)
})
}
const choosePageIdFunc = (id) => {
emit('choosePageIdFunc', id)
}
const createWikiByTemplate = (id) => {
emit('createWikiByTemplate', id)
}
const createWiki = (editorType, parentId) => {
if (props.choiceSpace > 0) {
let name = "新建文档"
if (editorType === 0) {
name = "新建文件夹"
}
pageApi.updatePage({
spaceId: props.choiceSpace,
parentId: parentId,
editorType: editorType,
name: name,
content: '',
preview: ''
}).then((json) => {
emit('doGetPageList', null)
ElMessage.error('导入失败:' + e.message)
ElMessage.success('创建成功')
if (editorType !== 0) {
router.push({
path: '/page/edit',
query: {parentId: props.nowPageId.value, pageId: json.data.id}
})
}
})
} else {
ElMessage.warning('请先选择或创建空间')
}
const choosePageIdFunc = (id) => {
emit('choosePageIdFunc', id)
}
const createWikiByTemplate = (id) => {
emit('createWikiByTemplate', id)
}
const createWiki = (editorType, parentId) => {
if (props.choiceSpace > 0) {
let name = "新建文档"
if (editorType === 0) {
name = "新建文件夹"
}
pageApi.updatePage({
spaceId: props.choiceSpace,
parentId: parentId,
editorType: editorType,
name: name,
content: '',
preview: ''
}).then((json) => {
emit('doGetPageList', null)
ElMessage.success('创建成功')
if (editorType !== 0) {
router.push({
path: '/page/edit',
query: {parentId: props.nowPageId.value, pageId: json.data.id}
})
}
})
} else {
ElMessage.warning('请先选择或创建空间')
}
}
}
</script>
<style lang="scss">
.add-menu-dropdown-btn {
padding: 0 8px;
height: 35px;
margin-top: -1px;
}
</style>

View File

@@ -1,15 +1,13 @@
<template>
<div style="padding: 10px;height: 100%;box-sizing: border-box;background: #fafafa;">
<div style="margin-bottom: 5px">
<el-select :model-value="choiceSpace" filterable placeholder="选择空间" style="width: 100%"
@change="spaceChangeEvents">
<el-select :model-value="choiceSpace" filterable placeholder="选择空间" @change="spaceChangeEvents" style="width: 100%">
<el-option-group label="" v-if="!props.readOnly">
<el-option :key="0" label="创建空间" :value="0"></el-option>
<el-option :key="-1" label="空间管理" :value="-1"></el-option>
</el-option-group>
<el-option-group label=""></el-option-group>
<el-option v-for="item in spaceOptions" :key="item.value" :label="item.label"
:value="item.value"></el-option>
<el-option v-for="item in spaceOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
</el-select>
</div>
<el-autocomplete v-model="searchKeywords" v-if="!props.readOnly" :fetch-suggestions="doSearchByKeywords"
@@ -26,29 +24,33 @@
</template>
</el-autocomplete>
<div class="space-folder-box" v-if="!props.readOnly">
<el-tooltip style="margin: 4px" effect="dark" :content="descriptorForTree" placement="top">
<span style="color:#888;font-size: 12px;cursor: pointer" @click="changeDropWownStatus">空间目录</span>
</el-tooltip>
<slot name="addMenuDir"/>
<el-row justify="space-between">
<el-col :span="12">
<el-tooltip style="margin: 4px" effect="dark" :content="descriptorForTree" placement="top">
<span style="color:#888;font-size: 12px;cursor: pointer;line-height: 32px;" @click="changeDropWownStatus">空间目录</span>
</el-tooltip>
</el-col>
<el-col :span="12" style="text-align: right;">
<slot name="addMenuDir"/>
</el-col>
</el-row>
</div>
<div class="wiki-page-tree-box">
<el-tree
:class="explanClass"
ref="wikiPageTreeRef"
:current-node-key="props.nowPageId"
:data="props.wikiPageList"
:default-expanded-keys="wikiPageExpandedKeys"
:expand-on-click-node="true"
:filter-node-method="filterPageNode"
:props="defaultProps"
:draggable="!props.readOnly"
highlight-current
node-key="id"
style="background-color: #fafafa"
@node-click="handleNodeClick"
@node-expand="handleNodeExpand"
@node-drop="handlePageDrop">
<template v-if="!props.readOnly" v-slot="{ node, data }" >
<el-tree :class="explanClass"
ref="wikiPageTreeRef"
:current-node-key="props.nowPageId"
:data="props.wikiPageList"
:default-expanded-keys="wikiPageExpandedKeys"
:expand-on-click-node="true"
:filter-node-method="filterPageNode"
:props="defaultProps"
:draggable="!props.readOnly"
highlight-current
node-key="id"
style="background-color: #fafafa"
@node-click="handleNodeClick"
@node-drop="handlePageDrop">
<template v-if="!props.readOnly" v-slot="{ node, data }">
<slot name="addMenuNode" :node="node" :data="data"></slot>
</template>
</el-tree>
@@ -57,135 +59,116 @@
</template>
<script setup>
import {ref, defineProps, defineEmits, defineExpose} from 'vue';
import {useRouter, useRoute} from "vue-router";
import pageApi from '../../assets/api/page'
import {useStoreDisplay} from "@/store/wikiDisplay";
import {useStorePageData} from "@/store/pageData";
let emit = defineEmits(['doGetPageList', 'spaceChangeEvents', 'setNowPageId'])
let searchKeywords = ref('');
let descriptorForTree = ref("点击收起目录");
let explan = ref(false);
let explanClass = ref("el-tree");
let wikiPageExpandedKeys = ref([]);
let route = useRoute();
let router = useRouter();
let defaultProps = ref({children: 'children', label: 'name',});
let wikiPage = ref({});
let wikiPageTreeRef = ref();
let storeDisplay = useStoreDisplay();
let props = defineProps({
wikiPageList: Array,
spaceOptions: Array,
nowPageId: Number,
choiceSpace: Number,
readOnly: Boolean
})
import {ref, defineProps, defineEmits, defineExpose} from 'vue';
import {useRouter, useRoute} from "vue-router";
import pageApi from '../../assets/api/page'
import {useStoreDisplay} from "@/store/wikiDisplay";
import {useStorePageData} from "@/store/pageData";
let emit = defineEmits(['doGetPageList', 'spaceChangeEvents', 'setNowPageId'])
let searchKeywords = ref('');
let descriptorForTree = ref("点击收起目录");
let explan = ref(false);
let explanClass = ref("el-tree");
let wikiPageExpandedKeys = ref([]);
let route = useRoute();
let router = useRouter();
let defaultProps = ref({children: 'children', label: 'name',});
let wikiPage = ref({});
let wikiPageTreeRef = ref();
let storeDisplay = useStoreDisplay();
let props = defineProps({
wikiPageList: Array,
spaceOptions: Array,
nowPageId: Number,
choiceSpace: Number,
readOnly: Boolean
})
const assisSetCurrentKey = () => {
emit('setNowPageId', route.query.pageId, props.readOnly)
if (props.nowPageId) {
wikiPageTreeRef.value.setCurrentKey(nowPageId.value)
}
const assisSetCurrentKey = () => {
emit('setNowPageId', route.query.pageId, props.readOnly)
if (props.nowPageId) {
wikiPageTreeRef.value.setCurrentKey(nowPageId.value)
}
}
const changeWikiPageExpandedKeys = (pageId) => {
// 展开没有触发子节点的加载如果去加载子节点有还找不到当前的node暂不展开
// wikiPageExpandedKeys.value= [pageId];
}
const changeWikiPageExpandedKeys = (pageId) => {
// 展开没有触发子节点的加载如果去加载子节点有还找不到当前的node暂不展开
// wikiPageExpandedKeys.value= [pageId];
}
const spaceChangeEvents = (data) => {
emit('spaceChangeEvents', data, props.readOnly)
}
const spaceChangeEvents = (data) => {
emit('spaceChangeEvents', data, props.readOnly)
}
const doSearchByKeywords = (queryString, callback) => {
if (!queryString || !queryString.trim()) {
callback([])
return
}
pageApi
.pageNews({spaceId: props.choiceSpace, keywords: queryString})
.then((json) => {
let spacePageNews = json.data || []
callback(spacePageNews)
})
const doSearchByKeywords = (queryString, callback) => {
if (!queryString || !queryString.trim()) {
callback([])
return
}
const handleSearchKeywordsSelect = (item) => {
searchKeywords.value = ''
router.push({path: '/page/show', query: {pageId: item.pageId}})
}
const changeDropWownStatus = () => {
if (explan.value) {
explanClass.value = "el-tree"
descriptorForTree.value = "点击收起目录"
explan.value = false
} else {
explanClass.value = "hidTree"
descriptorForTree.value = "点击展开目录"
explan.value = true
}
}
const filterPageNode = (value, data) => {
if (!value || !data.name) return true;
// issues:I2CG72 忽略大小写
let name = data.name.toLowerCase();
return name.indexOf(value.toLowerCase()) !== -1;
}
const searchByKeywords = () => {
wikiPageTreeRef.value.filter(searchKeywords.value)
}
let storePage = useStorePageData();
const handleNodeClick = (data) => {
//console.log('点击节点:', data, props.nowPageId)
storeDisplay.showHeader = true
emit('setNowPageId', data.id, props.readOnly)
if (props.readOnly) {
return
}
if (data.editorType !== 0) {
router.push({path: '/page/show', query: {pageId: data.id}})
}
if (data.editorType === 0) {
storePage.pageInfo = data
router.push({path: '/home', query: {spaceId:props.choiceSpace,dirId: data.id}})
}
handleNodeExpand(data)
}
const handleNodeExpand = (node) => {
if (props.readOnly) {
return
}
if (
node.children &&
node.children.length > 0 &&
node.children[0].needLoad
) {
console.log('加载节点:', node)
emit('doGetPageList',node.id, node)
}
}
const handlePageDrop = (draggingNode, dropNode, dropType, ev) => {
console.log('tree drop: ', draggingNode.data, dropNode.data, dropType)
// 'prev'、'inner'、'next'
// before、after、inner
var param = {id: draggingNode.data.id, parentId: dropNode.data.parentId}
if (dropType == 'inner') {
param.parentId = dropNode.data.id
} else if (dropType == 'before') {
param.beforeSeq = dropNode.data.seqNo
} else if (dropType == 'after') {
param.afterSeq = dropNode.data.seqNo
}
pageApi.pageChangeParent(param).then((res) => {
emit('doGetPageList',node.id, node)
pageApi
.pageNews({spaceId: props.choiceSpace, keywords: queryString})
.then((json) => {
let spacePageNews = json.data || []
callback(spacePageNews)
})
}
const handleSearchKeywordsSelect = (item) => {
searchKeywords.value = ''
router.push({path: '/page/show', query: {pageId: item.pageId}})
}
const changeDropWownStatus = () => {
if (explan.value) {
explanClass.value = "el-tree"
descriptorForTree.value = "点击收起目录"
explan.value = false
} else {
explanClass.value = "hidTree"
descriptorForTree.value = "点击展开目录"
explan.value = true
}
defineExpose({searchByKeywords})
}
const filterPageNode = (value, data) => {
if (!value || !data.name) return true;
// issues:I2CG72 忽略大小写
let name = data.name.toLowerCase();
return name.indexOf(value.toLowerCase()) !== -1;
}
const searchByKeywords = () => {
wikiPageTreeRef.value.filter(searchKeywords.value)
}
let storePage = useStorePageData();
const handleNodeClick = (data) => {
//console.log('点击节点:', data, props.nowPageId)
storeDisplay.showHeader = true
emit('setNowPageId', data.id, props.readOnly)
if (props.readOnly) {
return
}
if (data.editorType !== 0) {
router.push({path: '/page/show', query: {pageId: data.id}})
}
}
const handlePageDrop = (draggingNode, dropNode, dropType, ev) => {
console.log('tree drop: ', draggingNode.data, dropNode.data, dropType)
// 'prev'、'inner'、'next'
// before、after、inner
var param = {id: draggingNode.data.id, parentId: dropNode.data.parentId}
if (dropType == 'inner') {
param.parentId = dropNode.data.id
} else if (dropType == 'before') {
param.beforeSeq = dropNode.data.seqNo
} else if (dropType == 'after') {
param.afterSeq = dropNode.data.seqNo
}
pageApi.pageChangeParent(param).then((res) => {
emit('doGetPageList', node.id, node)
})
}
defineExpose({searchByKeywords})
</script>

View File

@@ -1,324 +1,317 @@
<template>
<div class="template-manage">
<el-dialog title="设置为模板" v-model="newTemplateDialogVisible" width="600px" :close-on-click-modal="false">
<el-form label-width="100px" :model="templateNewForm">
<el-form-item label="模板标签">
<el-input v-model="templateNewForm.tagName"></el-input>
</el-form-item>
<el-form-item label="是否公开">
<el-switch v-model="templateNewForm.shareStatus" inactive-text="个人模板" :inactive-value="0"
active-text="公共模板" :active-value="1"></el-switch>
</el-form-item>
<el-form-item>
<el-button type="primary" v-if="exsit" @click="onNewTemplateSubmit">保存修改</el-button>
<el-button type="primary" v-else @click="onNewTemplateSubmit">立即创建</el-button>
<el-button @click="onNewTemplateCancel">取消</el-button>
</el-form-item>
</el-form>
</el-dialog>
<a-modal
v-model:open="templateChooseDialogVisible"
title="模板库"
width="100%"
wrapClassName="full-modal"
:confirm-loading="aModalWaiting"
:destroyOnClose=true
:closable=true>
<div>
<el-switch v-model="open" inactive-text="个人模板" :inactive-value="0" active-text="公共模板" :active-value="1"
@change="filterByOpen"></el-switch>
<a-divider type="vertical"/>
<el-input v-model="name" style="width: 30%" @change="filterByName"></el-input>
<el-dialog title="设置为模板" v-model="newTemplateDialogVisible" width="600px" :close-on-click-modal="false">
<el-form label-width="100px" :model="templateNewForm">
<el-form-item label="模板标签">
<el-input v-model="templateNewForm.tagName"></el-input>
</el-form-item>
<el-form-item label="是否公开">
<el-switch v-model="templateNewForm.shareStatus" inactive-text="个人模板" :inactive-value="0" active-text="公共模板" :active-value="1"></el-switch>
</el-form-item>
<el-form-item>
<el-button type="primary" v-if="exsit" @click="onNewTemplateSubmit">保存修改</el-button>
<el-button type="primary" v-else @click="onNewTemplateSubmit">立即创建</el-button>
<el-button @click="onNewTemplateCancel">取消</el-button>
</el-form-item>
</el-form>
</el-dialog>
<a-modal
v-model:open="templateChooseDialogVisible"
title="模板库"
width="100%"
wrapClassName="full-modal"
:confirm-loading="aModalWaiting"
:destroyOnClose=true
:closable=true>
<div>
<el-switch v-model="open" inactive-text="个人模板" :inactive-value="0" active-text="公共模板" :active-value="1" @change="filterByOpen"></el-switch>
<a-divider type="vertical"/>
<el-input v-model="name" style="width: 30%" @change="filterByName"></el-input>
</div>
<a-divider>模板标签</a-divider>
<a-checkable-tag v-for="tag in tags" @click="filterByTags(tag.tagName,tag.show)" v-model:checked="tag.show" style="margin: 5px" size="big">{{tag.tagName}}</a-checkable-tag>
<a-divider/>
<a-list :grid="{gutter:1,column:4,xs:1,sm:1,md:2,lg:2,xl:4,xxl:4}" :data-source="templateList">
<template #renderItem="{item}">
<a-list-item>
<a-card :title="item.name">
<a-tag color="#f50">{{filterShareStatus(item.shareStatus)}}</a-tag>
<a-tag color="#87d068">{{item.tags}}</a-tag>
<br/>
{{item.createUserName}}
<br/>
{{item.createTime}}
<template #actions>
<el-tooltip effect="dark" content="转到原文档" placement="top">
<AimOutlined @click="turnToSource(item)"/>
</el-tooltip>
<el-tooltip effect="dark" content="预览模板" placement="top">
<BorderOutlined @click="showPreview(item)"/>
</el-tooltip>
<el-tooltip effect="dark" content="使用模板" placement="top">
<AlertOutlined @click="chooseTemplate(item)"/>
</el-tooltip>
</template>
</a-card>
</a-list-item>
</template>
</a-list>
<a-pagination simple v-model:current="nowTemplateNum" :total="totalTemplate" style="float: right"
:page-size="8" :hide-on-single-page=true @change="pageUpDown"/>
<template #footer/>
</a-modal>
<a-modal
v-model:open="previewVisible"
title="模板预览"
width="100%"
wrapClassName="full-modal"
:destroyOnClose=true
:closable=true>
<el-row>
<div ref="pageContentRef" class="wiki-page-content">
<div v-html="pageShowDetail" class="markdown-body" v-if="editorType.value === 2" v-highlight></div>
<div v-html="pageShowDetail" class="wang-editor-body" v-else></div>
</div>
<a-divider>模板标签</a-divider>
<a-checkable-tag v-for="tag in tags" @click="filterByTags(tag.tagName,tag.show)" v-model:checked="tag.show"
style="margin: 5px" size="big">{{tag.tagName}}
</a-checkable-tag>
<a-divider/>
<a-list :grid="{gutter:1,column:4,xs:1,sm:1,md:2,lg:2,xl:4,xxl:4}" :data-source="templateList">
<template #renderItem="{item}">
<a-list-item>
<a-card :title="item.name">
<a-tag color="#f50">{{filterShareStatus(item.shareStatus)}}</a-tag>
<a-tag color="#87d068">{{item.tags}}</a-tag>
<br/>
{{item.createUserName}}
<br/>
{{item.createTime}}
<template #actions>
<el-tooltip effect="dark" content="转到原文档" placement="top">
<AimOutlined @click="turnToSource(item)"/>
</el-tooltip>
<el-tooltip effect="dark" content="预览模板" placement="top">
<BorderOutlined @click="showPreview(item)"/>
</el-tooltip>
<el-tooltip effect="dark" content="使用模板" placement="top">
<AlertOutlined @click="chooseTemplate(item)"/>
</el-tooltip>
</template>
</a-card>
</a-list-item>
</template>
</a-list>
<a-pagination simple v-model:current="nowTemplateNum" :total="totalTemplate" style="float: right"
:page-size="8" :hide-on-single-page=true @change="pageUpDown"></a-pagination>
<template #footer/>
</a-modal>
<a-modal
v-model:open="previewVisible"
title="模板预览"
width="100%"
wrapClassName="full-modal"
:destroyOnClose=true
:closable=true>
<el-row>
<div ref="pageContentRef" class="wiki-page-content">
<div v-html="pageShowDetail" class="markdown-body" v-if="editorType.value === 2" v-highlight></div>
<div v-html="pageShowDetail" class="wang-editor-body" v-else></div>
</div>
</el-row>
<template #footer/>
</a-modal>
</div>
</el-row>
<template #footer/>
</a-modal>
</template>
<script setup>
import {
onBeforeUnmount,
ref,
onMounted,
watch,
defineProps,
nextTick,
defineEmits,
defineExpose,
computed
} from 'vue';
import {AlertOutlined, AimOutlined, BorderOutlined} from '@ant-design/icons-vue';
import {onBeforeRouteUpdate, useRouter, useRoute} from "vue-router";
import {ElMessageBox, ElMessage} from 'element-plus'
import pageApi from '../../assets/api/page'
import {mavonEditor} from 'mavon-editor'
import 'mavon-editor/dist/markdown/github-markdown.min.css'
import 'mavon-editor/dist/css/index.css'
import {
onBeforeUnmount,
ref,
onMounted,
watch,
defineProps,
nextTick,
defineEmits,
defineExpose,
computed
} from 'vue';
import {AlertOutlined, AimOutlined, BorderOutlined} from '@ant-design/icons-vue';
import {onBeforeRouteUpdate, useRouter, useRoute} from "vue-router";
import {ElMessageBox, ElMessage} from 'element-plus'
import pageApi from '../../assets/api/page'
import {mavonEditor} from 'mavon-editor'
import 'mavon-editor/dist/markdown/github-markdown.min.css'
import 'mavon-editor/dist/css/index.css'
let emit = defineEmits('doGetPageList');
let router = useRouter()
let nowTemplateNum = ref(1)
let totalTemplate = ref(0)
let exsit = ref(false)
let emit = defineEmits('doGetPageList');
let router = useRouter()
let nowTemplateNum = ref(1)
let totalTemplate = ref(0)
let exsit = ref(false)
let props = defineProps({
pageId: Number,
spaceId: Number
})
let templateNewForm = ref({
pageId: 0,
spaceId: 0,
let props = defineProps({
pageId: Number,
spaceId: Number
})
let templateNewForm = ref({
pageId: 0,
spaceId: 0,
tagName: '',
shareStatus: false
})
let newTemplateDialogVisible = ref(false);
const showTemplateCreate = (exsited) => {
exsit.value = exsited
templateNewForm.value = {
pageId: props.pageId,
spaceId: props.spaceId,
tagName: '',
shareStatus: false
}
newTemplateDialogVisible.value = true
}
const filterShareStatus = (data) => {
if (data === 1) {
return '公共模板'
}
return '个人模板'
}
const onNewTemplateSubmit = () => {
pageApi.addTemplate(templateNewForm.value).then((json) => {
ElMessage.success('模板记录成功')
emit('doGetPageList', null)
})
let newTemplateDialogVisible = ref(false);
const showTemplateCreate = (exsited) => {
exsit.value = exsited
templateNewForm.value = {
pageId: props.pageId,
spaceId: props.spaceId,
tagName: '',
shareStatus: false
}
newTemplateDialogVisible.value = true
}
const filterShareStatus = (data) => {
if (data === 1) {
return '公共模板'
}
return '个人模板'
}
const onNewTemplateSubmit = () => {
pageApi.addTemplate(templateNewForm.value).then((json) => {
ElMessage.success('模板记录成功')
emit('doGetPageList', null)
})
newTemplateDialogVisible.value = false
}
const onNewTemplateCancel = () => {
newTemplateDialogVisible.value = false
}
newTemplateDialogVisible.value = false
}
const onNewTemplateCancel = () => {
newTemplateDialogVisible.value = false
}
let templateChooseDialogVisible = ref(false)
let previewVisible = ref(false)
let aModalWaiting = ref(false)
let tags = ref([])
let filterTags = ref([])
let open = ref(false)
let name = ref('')
let templateList = ref()
let editorType = ref(1)
let templateChooseDialogVisible = ref(false)
let previewVisible = ref(false)
let aModalWaiting = ref(false)
let tags = ref([])
let filterTags = ref([])
let open = ref(false)
let name = ref('')
let templateList = ref()
let editorType = ref(1)
const showTemplateManage = () => {
templateChooseDialogVisible.value = true
filterTags.value = [{show: true, tagName: ''}]
totalTemplate.value = 0
nowTemplateNum.value = 1
templateList.value = []
filterByOpen()
}
const chooseTemplate = (item) => {
pageApi.useTemplate({
spaceId: props.spaceId,
parentId: props.pageId,
templateId: item.templateId
}).then((json) => {
templateChooseDialogVisible.value = false
emit('doGetPageList', null)
ElMessage.success('创建成功')
router.push({
path: '/page/edit',
query: {parentId: props.pageId, pageId: json.data.id}
})
})
}
const turnToSource = (item) => {
const showTemplateManage = () => {
templateChooseDialogVisible.value = true
filterTags.value = [{show: true, tagName: ''}]
totalTemplate.value = 0
nowTemplateNum.value = 1
templateList.value = []
filterByOpen()
}
const chooseTemplate = (item) => {
pageApi.useTemplate({
spaceId: props.spaceId,
parentId: props.pageId,
templateId: item.templateId
}).then((json) => {
templateChooseDialogVisible.value = false
emit('doGetPageList', null)
ElMessage.success('创建成功')
router.push({
path: '/page/show',
query: {spaceId: item.spaceId, pageId: item.id}
path: '/page/edit',
query: {parentId: props.pageId, pageId: json.data.id}
})
}
const pageUpDown = () => {
templateList.value = []
pageApi.getTemplate({
name: name.value,
open: open.value,
tags: filterTags.value,
pageNum: nowTemplateNum.value
}).then((json) => {
totalTemplate.value = json.total || 0
templateList.value = json.data || []
})
}
const simpleQryTemplate = () => {
templateList.value = []
pageApi.getTemplate({
name: name.value,
open: open.value,
tags: filterTags.value,
}).then((json) => {
totalTemplate.value = json.total || 0
templateList.value = json.data || []
nowTemplateNum.value = 1
})
}
const filterByOpen = () => {
pageApi.getTags({open: open.value}).then((json) => {
tags.value = json.data || []
filterTags.value = json.data || []
simpleQryTemplate()
})
}
const filterByTags = () => {
filterTags.value = tags.value.filter((item) => {
return item.show
})
if (filterTags.value.length === 0) {
filterTags.value = ['']
}
setTimeout(simpleQryTemplate(), 200)
}
const filterByName = () => {
})
}
const turnToSource = (item) => {
templateChooseDialogVisible.value = false
router.push({
path: '/page/show',
query: {spaceId: item.spaceId, pageId: item.id}
})
}
const pageUpDown = () => {
templateList.value = []
pageApi.getTemplate({
name: name.value,
open: open.value,
tags: filterTags.value,
pageNum: nowTemplateNum.value
}).then((json) => {
totalTemplate.value = json.total || 0
templateList.value = json.data || []
})
}
const simpleQryTemplate = () => {
templateList.value = []
pageApi.getTemplate({
name: name.value,
open: open.value,
tags: filterTags.value,
}).then((json) => {
totalTemplate.value = json.total || 0
templateList.value = json.data || []
nowTemplateNum.value = 1
})
}
const filterByOpen = () => {
pageApi.getTags({open: open.value}).then((json) => {
tags.value = json.data || []
filterTags.value = json.data || []
simpleQryTemplate()
})
}
const filterByTags = () => {
filterTags.value = tags.value.filter((item) => {
return item.show
})
if (filterTags.value.length === 0) {
filterTags.value = ['']
}
setTimeout(simpleQryTemplate(), 200)
}
const filterByName = () => {
simpleQryTemplate()
}
let pageShowDetail = ref('')
let pageContentRef = ref(null)
const showPreview = (item) => {
editorType.value = item.editorType
if (item.editorType === 1) {
pageShowDetail.value = item.content
}
if (item.editorType === 2) {
pageShowDetail.value = mavonEditor.getMarkdownIt().render(item.content)
}
setTimeout(previewPageImage(), 500);
previewVisible.value = true
let pageShowDetail = ref('')
let pageContentRef = ref(null)
const showPreview = (item) => {
editorType.value = item.editorType
if (item.editorType === 1) {
pageShowDetail.value = item.content
}
const previewPageImage = () => {
const imgArr = []
if (pageContentRef.value !== undefined || pageContentRef.value !== '') {
return
}
const imgSelector = pageContentRef.value.querySelectorAll('img')
imgSelector.forEach((item, index) => {
imgArr.push(item.src)
item.onclick = () => {
previewInitialIndex.value = index
showImagePreviewList.value = imgArr
showImagePreview.value = true
}
})
if (item.editorType === 2) {
pageShowDetail.value = mavonEditor.getMarkdownIt().render(item.content)
}
defineExpose({showTemplateCreate, showTemplateManage});
setTimeout(previewPageImage(), 500);
previewVisible.value = true
}
const previewPageImage = () => {
const imgArr = []
if (pageContentRef.value !== undefined || pageContentRef.value !== '') {
return
}
const imgSelector = pageContentRef.value.querySelectorAll('img')
imgSelector.forEach((item, index) => {
imgArr.push(item.src)
item.onclick = () => {
previewInitialIndex.value = index
showImagePreviewList.value = imgArr
showImagePreview.value = true
}
})
}
defineExpose({showTemplateCreate, showTemplateManage});
</script>
<style>
.template-manage .wiki-page-content {
margin-top: 5px;
height: calc(100vh);
overflow: hidden;
position: relative
}
.template-manage .wiki-page-content {
margin-top: 5px;
height: calc(100vh);
overflow: hidden;
position: relative
}
.template-manage .markdown-body table {
display: table;
}
.template-manage .markdown-body table {
display: table;
}
.template-manage .wiki-page-content img {
cursor: pointer;
max-width: 100%;
}
.template-manage .wiki-page-content img {
cursor: pointer;
max-width: 100%;
}
.template-manage .wiki-page-content img:hover {
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.3);
}
.template-manage .wiki-page-content img:hover {
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.3);
}
</style>
<style lang="less">
.full-modal {
.ant-modal {
max-width: 100%;
top: 0;
padding-bottom: 0;
margin: 0;
}
.full-modal {
.ant-modal {
max-width: 100%;
top: 0;
padding-bottom: 0;
margin: 0;
}
.ant-modal-content {
display: flex;
flex-direction: column;
height: calc(100vh);
overflow: auto;
position: relative
}
.ant-modal-content {
display: flex;
flex-direction: column;
height: calc(100vh);
overflow: auto;
position: relative
}
.ant-modal-body {
flex: 1;
}
}
.ant-modal-body {
flex: 1;
}
}
</style>
<style lang="scss">
.template-manage {
height: 100%;
overflow: hidden;
.template-manage {
height: 100%;
overflow: hidden;
.wiki-page-content {
ol {
list-style: decimal;
}
ul {
list-style: disc;
}
}
.wiki-page-content {
ol {
list-style: decimal;
}
ul {
list-style: disc;
}
}
}
</style>

View File

@@ -8,6 +8,7 @@ import {createRouter, createWebHashHistory} from 'vue-router'
import ElementUI from 'element-plus'
import 'element-plus/dist/index.css'
import './assets/scss/base.scss'
import Antd from 'ant-design-vue';
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import routes from './routes'

View File

@@ -6,7 +6,7 @@ export const useStoreDisplay = defineStore('wikiDisplay', {
// 左边目录栏宽度
viewMenuWidth: 300,
// 是否显示右边评论栏
commentShow: false,
commentShow: true,
commentActiveTab: 'comment',
showMenu: true,
rightAsideWidth: 300,

View File

@@ -1,7 +1,7 @@
<template>
<div class="page-show-vue" v-if="storePage.pageInfo.editorType !== 0">
<el-row type="border-card" style="height: 100%">
<el-col :span="storePage.commentShow ? 18 : 24" style="padding: 20px;border-right: 1px solid #f1f1f1;height: 100%;overflow: auto;">
<el-col :span="storeDisplay.commentShow ? 18 : 24" style="padding: 20px;border-right: 1px solid #f1f1f1;height: 100%;overflow: auto;">
<el-row>
<el-col :span="navigationList.length > 0 ? 18 : 24">
<div style="max-width: 1000px; margin: 0 auto; padding-left: 10px">
@@ -17,11 +17,11 @@
</el-col>
</el-row>
</el-col>
<el-col :span="6" style="height: 100%" v-show="storePage.commentShow">
<el-col :span="6" style="height: 100%" v-show="storeDisplay.commentShow">
<el-icon @click="closeActionTab" class="close-action-tab">
<el-icon-close/>
</el-icon>
<el-tabs v-model="storePage.commentActiveTab">
<el-tabs v-model="storeDisplay.commentActiveTab">
<el-tab-pane label="评论" name="comment">
<Comment/>
</el-tab-pane>
@@ -85,6 +85,7 @@ import {mavonEditor} from 'mavon-editor'
import 'mavon-editor/dist/markdown/github-markdown.min.css'
import 'mavon-editor/dist/css/index.css'
import {useStorePageData} from "@/store/pageData";
import {useStoreDisplay} from "@/store/wikiDisplay";
let page = {
colorArr: ['#67C23A', '#409EFF', '#E6A23C', '#F56C6C', '#909399', '#303133'],
@@ -126,6 +127,7 @@ let downloadFormParam = ref({url: 'zyplayer-doc-wiki/page/download', param: {},}
let route = useRoute();
let router = useRouter();
let storeDisplay = useStoreDisplay();
let storePage = useStorePageData();
const props = defineProps({
@@ -202,7 +204,7 @@ const deleteUserPageAuth = (row) => {
pageAuthUserList.value = pageAuthUserList;
}
const closeActionTab = () => {
storePage.commentShow = false;
storeDisplay.commentShow = false;
clearHistory();
}
const getPageHistoryByScroll = () => {