查看页面展示开发

This commit is contained in:
sswiki
2025-05-24 17:55:14 +08:00
parent 42386da126
commit e54761b24b
12 changed files with 5872 additions and 8763 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -34,17 +34,16 @@
"xss": "^1.0.14" "xss": "^1.0.14"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-babel": "^5.0.8",
"less": "^4.1.3",
"less-loader": "^11.1.3",
"node-sass": "^9.0.0",
"sass-loader": "^13.3.2",
"vue-template-compiler": "^2.7.14",
"@vitejs/plugin-vue": "^4.2.3", "@vitejs/plugin-vue": "^4.2.3",
"@vue/cli-plugin-babel": "^5.0.8",
"@vue/compiler-sfc": "^3.3.4", "@vue/compiler-sfc": "^3.3.4",
"less": "^4.3.0",
"less-loader": "^12.3.0",
"sass": "^1.89.0",
"unplugin-auto-import": "^0.5.11", "unplugin-auto-import": "^0.5.11",
"unplugin-vue-components": "^0.25.1", "unplugin-vue-components": "^0.25.1",
"vite": "^4.4.9", "vite": "^4.4.9",
"vite-plugin-style-import": "^2.0.0" "vite-plugin-style-import": "^2.0.0",
"vue-template-compiler": "^2.7.14"
} }
} }

View File

@@ -0,0 +1,13 @@
export default function applyOnce(fn, timeout) {
let inCall = false;
let lastParam = null;
return function (param) {
lastParam = param;
if (inCall) return;
inCall = true;
setTimeout(() => {
inCall = false;
fn(lastParam);
}, timeout);
}
};

View File

@@ -1,3 +1,23 @@
.i-icon { .i-icon {
vertical-align: middle; display: inline-flex;
align-items: center;
color: inherit;
font-style: normal;
line-height: 0;
text-align: center;
text-transform: none;
vertical-align: -0.125em;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
}
.ant-btn {
.i-icon + span {
margin-inline-start: 8px;
}
span + .i-icon {
margin-inline-start: 6px;
}
} }

View File

@@ -1,16 +1,16 @@
<template> <template>
<div style="height: 100%" class="page-edit-vue"> <div style="height: 100%;" class="page-edit-vue">
<el-row class="fake-header"> <el-row class="fake-header">
<el-col style="flex: 0 0 45px;" class="collapse-box"> <el-col style="flex: 0 0 45px;" class="collapse-box">
<el-button @click="turnLeftCollapse" v-if="storeDisplay.showMenu" text :icon="ElIconFold" class="fold-btn"></el-button> <a-button @click="turnLeftCollapse" v-if="storeDisplay.showMenu" type="text" :icon="h(MenuFoldOutlined)"></a-button>
<el-button @click="turnLeftCollapse" v-else text :icon="ElIconExpand" class="fold-btn"></el-button> <a-button @click="turnLeftCollapse" v-else type="text" :icon="h(MenuUnfoldOutlined)"></a-button>
</el-col> </el-col>
<el-col style="flex: 1 1 auto;"> <el-col style="flex: 1 1 auto;">
<el-input v-model="pageTitleEdit" :maxlength="40" placeholder="请输入标题" class="page-title-input" ></el-input> <el-input v-model="pageTitleEdit" :maxlength="40" placeholder="请输入标题" class="page-title-input" ></el-input>
</el-col> </el-col>
<el-col style="flex: 0 0 180px;text-align: right;"> <el-col style="flex: 0 0 190px;text-align: right;">
<el-button type="primary" @click="createWikiSave(1)" :icon="ElIconDocumentChecked">保存</el-button> <a-button @click="createWikiSave(1)" :icon="h(SaveOutlined)" :loading="saveContentLoading" type="primary">保存</a-button>
<el-button @click="createWikiCancel" :icon="ElIconBack" style="margin-right: 5px;">取消</el-button> <a-button @click="createWikiCancel" :icon="h(IconParkBack)" style="margin-left: 10px;">取消</a-button>
</el-col> </el-col>
</el-row> </el-row>
<div style="box-sizing: border-box;background: #f5f5f5;overflow: hidden"> <div style="box-sizing: border-box;background: #f5f5f5;overflow: hidden">
@@ -27,7 +27,9 @@
</template> </template>
<script setup> <script setup>
import {onBeforeUnmount, ref, onMounted, onUnmounted, watch, defineProps, nextTick, defineEmits, defineExpose, computed} from 'vue'; import {Back as IconParkBack} from '@icon-park/vue-next'
import {MenuFoldOutlined, MenuUnfoldOutlined, SaveOutlined} from '@ant-design/icons-vue';
import {onBeforeUnmount, ref, onMounted, onUnmounted, watch, defineProps, h, nextTick, defineEmits, defineExpose, computed} from 'vue';
import {onBeforeRouteUpdate, useRouter, useRoute} from "vue-router"; import {onBeforeRouteUpdate, useRouter, useRoute} from "vue-router";
import {ElMessageBox, ElMessage} from 'element-plus'; import {ElMessageBox, ElMessage} from 'element-plus';
import { import {
@@ -133,7 +135,7 @@ const createWikiSave = (saveAfter) => {
preview = wangEditorRef.value.getPreview(); preview = wangEditorRef.value.getPreview();
} else if (wikiPage.value.editorType === 2) { } else if (wikiPage.value.editorType === 2) {
content = markdownContent.value; content = markdownContent.value;
const showContentSelector = mavonEditorRef.value.querySelectorAll('.v-show-content'); const showContentSelector = mavonEditorRef.value.$el.querySelectorAll('.v-show-content');
if (showContentSelector && showContentSelector.length > 0) { if (showContentSelector && showContentSelector.length > 0) {
preview = showContentSelector[0].textContent; preview = showContentSelector[0].textContent;
} }
@@ -221,6 +223,9 @@ const addMarkdownImage = (pos, file) => {
color: #333; color: #333;
height: 50px !important; height: 50px !important;
line-height: 50px !important; line-height: 50px !important;
padding: 0 8px 0 8px;
font-size: 14px;
background: #fff;
.fold-btn { .fold-btn {
font-size: 18px; font-size: 18px;

View File

@@ -10,7 +10,7 @@
</div> </div>
<PageZan></PageZan> <PageZan></PageZan>
</div> </div>
<Navigation :heading="navigationList"></Navigation> <Navigation v-if="navigationList.length > 0" :heading="navigationList"></Navigation>
</a-col> </a-col>
<a-col v-if="storeDisplay.commentShow" flex="280px"> <a-col v-if="storeDisplay.commentShow" flex="280px">
<a-tabs v-model:activeKey="actionTabActiveName" class="action-tabs-box"> <a-tabs v-model:activeKey="actionTabActiveName" class="action-tabs-box">

View File

@@ -27,24 +27,20 @@
</template> </template>
</el-autocomplete> </el-autocomplete>
<div class="space-folder-box" v-if="!props.readOnly"> <div class="space-folder-box" v-if="!props.readOnly">
<el-row justify="space-between"> <a-flex align="center" justify="space-between">
<el-col :span="16"> <div class="page-group-tag">
<div class="page-group-tag"> <el-tooltip :content="spaceTreeIsClose?'点击展开目录':'点击收起目录'" placement="top">
<el-tooltip :content="spaceTreeIsClose?'点击展开目录':'点击收起目录'" placement="top"> <span class="label" @click="changeDropdownStatus">空间目录</span>
<span class="label" @click="changeDropdownStatus">空间目录</span> </el-tooltip>
</el-tooltip> </div>
</div> <AddMenu/>
</el-col> </a-flex>
<el-col :span="8" style="text-align: right;">
<AddMenu/>
</el-col>
</el-row>
</div> </div>
</div> </div>
<div v-show="!spaceTreeIsClose" class="wiki-page-tree-box"> <div v-show="!spaceTreeIsClose" class="wiki-page-tree-box">
<el-tree ref="wikiPageTreeRef" :current-node-key="props.nowPageId" :data="storePage.pageList" <el-tree ref="wikiPageTreeRef" :current-node-key="props.nowPageId" :data="storePage.pageList"
:default-expanded-keys="wikiPageExpandedKeys" :expand-on-click-node="true" :default-expanded-keys="wikiPageExpandedKeys" :expand-on-click-node="true"
:filter-node-method="filterPageNode" :props="defaultProps" :draggable="!props.readOnly" :props="defaultProps" :draggable="!props.readOnly"
@node-click="handleNodeClick" @node-drop="handlePageDrop" node-key="id" highlight-current @node-click="handleNodeClick" @node-drop="handlePageDrop" node-key="id" highlight-current
style="background-color: #fafafa;"> style="background-color: #fafafa;">
<template v-slot="{ node, data }"> <template v-slot="{ node, data }">
@@ -63,7 +59,7 @@
<el-tooltip v-if="data.shareStatus > 0" :content="data.tags" placement="top-start" :show-after="500"> <el-tooltip v-if="data.shareStatus > 0" :content="data.tags" placement="top-start" :show-after="500">
<a-tag color="warning" style="margin-inline-end: 4px;padding-inline: 4px;"> {{data.shareStatus === 1 ? '公共模板' : '个人模板'}}</a-tag> <a-tag color="warning" style="margin-inline-end: 4px;padding-inline: 4px;"> {{data.shareStatus === 1 ? '公共模板' : '个人模板'}}</a-tag>
</el-tooltip> </el-tooltip>
<span style="vertical-align: middle;margin-left: 5px;"> <span style="margin-left: 5px;">
<el-tooltip :content="node.label" placement="top-start" :show-after="700">{{ node.label }}</el-tooltip> <el-tooltip :content="node.label" placement="top-start" :show-after="700">{{ node.label }}</el-tooltip>
</span> </span>
<!--操作--> <!--操作-->
@@ -279,18 +275,20 @@ const doGetPageList = () => {
let spaceId = storeSpace.chooseSpaceId; let spaceId = storeSpace.chooseSpaceId;
pageApi.pageList({spaceId: spaceId}).then((json) => { pageApi.pageList({spaceId: spaceId}).then((json) => {
storePage.pageList = json.data || []; storePage.pageList = json.data || [];
// 查看页面 if (route.path.startsWith('/view')) {
if (storePage.pageList.length <= 0) { // 查看页面
router.push({path: `/view/${spaceId}`}); if (storePage.pageList.length <= 0) {
} else { router.push({path: `/view/${spaceId}`});
let routePageId = parseInt(route.params.pageId);
let findPage = storePage.getPageById(routePageId);
if (findPage) {
router.replace({path: `/view/${spaceId}/${routePageId}`});
} else { } else {
let firstPage = storePage.getFirstViewPage(); let routePageId = parseInt(route.params.pageId);
if (firstPage) { let findPage = storePage.getPageById(routePageId);
router.replace({path: `/view/${spaceId}/${firstPage.id}`}); if (findPage) {
router.replace({path: `/view/${spaceId}/${routePageId}`});
} else {
let firstPage = storePage.getFirstViewPage();
if (firstPage) {
router.replace({path: `/view/${spaceId}/${firstPage.id}`});
}
} }
} }
} }
@@ -308,18 +306,12 @@ const doSearchByKeywords = (queryString, callback) => {
} }
const handleSearchKeywordsSelect = (item) => { const handleSearchKeywordsSelect = (item) => {
searchKeywords.value = ''; searchKeywords.value = '';
router.push({path: '/page/show', query: {pageId: item.pageId}}); router.push({path: `/view/${item.spaceId}/${item.pageId}`});
} }
let spaceTreeIsClose = ref(false); let spaceTreeIsClose = ref(false);
const changeDropdownStatus = () => { const changeDropdownStatus = () => {
spaceTreeIsClose.value = !spaceTreeIsClose.value; spaceTreeIsClose.value = !spaceTreeIsClose.value;
} }
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 = () => { const searchByKeywords = () => {
wikiPageTreeRef.value.filter(searchKeywords.value); wikiPageTreeRef.value.filter(searchKeywords.value);
} }
@@ -413,13 +405,8 @@ defineExpose({searchByKeywords});
width: 100%; width: 100%;
.label { .label {
.el-icon {
vertical-align: middle;
}
.text { .text {
margin-left: 5px; margin-left: 5px;
vertical-align: middle;
max-width: calc(100% - 40px); max-width: calc(100% - 40px);
display: inline-block; display: inline-block;
overflow: hidden; overflow: hidden;
@@ -454,6 +441,24 @@ defineExpose({searchByKeywords});
.search-autocomplete-popper { .search-autocomplete-popper {
width: 600px !important; width: 600px !important;
.search-option-item {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
.title {
font-weight: bold;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.content {
font-size: 12px;
color: #888;
}
}
} }
.space-folder-box { .space-folder-box {

View File

@@ -36,12 +36,12 @@
<a-button class="hover-button hover-bg" size="large" :icon="h(EllipsisOutlined)"></a-button> <a-button class="hover-button hover-bg" size="large" :icon="h(EllipsisOutlined)"></a-button>
<template #overlay> <template #overlay>
<a-menu> <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="editWikiAuth" v-if="storePage.pageAuth.canConfigAuth === 1" :icon="h(SafetyOutlined)">权限设置</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="showOpenPage" v-if="storeSpace.spaceInfo.openDoc === 1" :icon="h(ShareAltOutlined)">查看开放文档</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="showMobileView" v-if="storeSpace.spaceInfo.openDoc === 1" :icon="h(MobileOutlined)">手机端查看</a-menu-item>
<a-menu-item @click="exportWord"><el-icon><ElIconDownload/></el-icon>导出为Word</a-menu-item> <a-menu-item @click="exportWord" :icon="h(DownloadOutlined)">导出为Word</a-menu-item>
<a-menu-divider /> <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-item @click="deleteWikiPage" v-if="storePage.pageAuth.canDelete === 1" :icon="h(DeleteOutlined)" danger class="delete">删除</a-menu-item>
</a-menu> </a-menu>
</template> </template>
</a-dropdown> </a-dropdown>
@@ -70,20 +70,8 @@
<script setup> <script setup>
import { import {
Fold as ElIconFold, UserOutlined, EditOutlined, MessageOutlined, CheckOutlined, EllipsisOutlined, DeleteOutlined, ShareAltOutlined,
Expand as ElIconExpand, MenuFoldOutlined, MenuUnfoldOutlined, LoadingOutlined, SafetyOutlined, DownloadOutlined, MobileOutlined
Delete as ElIconDelete,
Stamp as ElIconSCheck,
Share as ElIconShare,
Iphone as ElIconMobilePhone,
Download as ElIconDownload,
MoreFilled as ElIconMoreFilled,
Setting as ElIconSetting,
UserFilled as ElIconUserFilled,
} from '@element-plus/icons-vue'
import {
UserOutlined, EditOutlined, MessageOutlined, CheckOutlined, EllipsisOutlined,
MenuFoldOutlined, MenuUnfoldOutlined, LoadingOutlined
} from '@ant-design/icons-vue'; } from '@ant-design/icons-vue';
import {toRefs, ref, reactive, onMounted, watch, defineEmits, h, computed} from 'vue'; import {toRefs, ref, reactive, onMounted, watch, defineEmits, h, computed} from 'vue';
import {useRouter, useRoute} from "vue-router"; import {useRouter, useRoute} from "vue-router";

View File

@@ -10,7 +10,7 @@ import {toRefs, ref, reactive, onMounted, onBeforeUnmount, watch, defineEmits, c
const props = defineProps({ const props = defineProps({
modelValue: Number, modelValue: Number,
max: {type: Number, default: 600}, max: {type: Number, default: 600},
min: {type: Number, default: 200} min: {type: Number, default: 240}
}); });
let emit = defineEmits(['update:modelValue', 'change']); let emit = defineEmits(['update:modelValue', 'change']);

View File

@@ -197,7 +197,7 @@ const getUserHeadBgColor = (userId) => {
.comment-btn-box { .comment-btn-box {
text-align: right; text-align: right;
padding: 4px 15px 6px 0; padding: 4px 6px 6px 0;
} }
} }
</style> </style>

View File

@@ -20,7 +20,10 @@
import {ViewList as IconParkViewList} from '@icon-park/vue-next' import {ViewList as IconParkViewList} from '@icon-park/vue-next'
import {toRefs, ref, reactive, onMounted, watch, defineEmits, defineProps, defineExpose,} from 'vue'; import {toRefs, ref, reactive, onMounted, watch, defineEmits, defineProps, defineExpose,} from 'vue';
import {useResizeEvent} from "@/composable/windowsScroll"; import {useResizeEvent} from "@/composable/windowsScroll";
import applyOnce from "@/assets/js/applyOnce";
import {useStoreDisplay} from "@/store/wikiDisplay";
let storeDisplay = useStoreDisplay();
const props = defineProps({ const props = defineProps({
heading: {type: Array, default: []}, heading: {type: Array, default: []},
}); });
@@ -30,6 +33,9 @@ onMounted(() => {
useResizeEvent(() => { useResizeEvent(() => {
computeNavigationWidth(); computeNavigationWidth();
}); });
watch(() => storeDisplay.rightAsideWidth, (newVal) => {
computeNavigationWidth();
});
let isLeave = false; let isLeave = false;
let leaveTimer = undefined; let leaveTimer = undefined;
const navigationMouseover = () => { const navigationMouseover = () => {
@@ -53,7 +59,7 @@ const navigationToMax = () => {
let navigationMin = ref(false); let navigationMin = ref(false);
let navigationShow = ref(true); let navigationShow = ref(true);
let navigationStyle = ref({width: '200px'}); let navigationStyle = ref({width: '200px'});
const computeNavigationWidth = () => { const computeNavigationWidth = applyOnce(() => {
let pageViewContent = document.getElementById('pageContentBox'); let pageViewContent = document.getElementById('pageContentBox');
let pageContentScrollBox = document.getElementById('pageContentScrollBox'); let pageContentScrollBox = document.getElementById('pageContentScrollBox');
// pageContentScrollBox的宽度减去pageViewContent的宽度除以2 // pageContentScrollBox的宽度减去pageViewContent的宽度除以2
@@ -68,7 +74,7 @@ const computeNavigationWidth = () => {
} }
navigationMin.value = !navigationShow.value; navigationMin.value = !navigationShow.value;
} }
} }, 500);
const headingItemClick = (item) => { const headingItemClick = (item) => {
// 滚动到指定节点 // 滚动到指定节点
item.node.scrollIntoView({ item.node.scrollIntoView({

File diff suppressed because it is too large Load Diff