查看页面展示开发

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"
},
"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",
"@vue/cli-plugin-babel": "^5.0.8",
"@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-vue-components": "^0.25.1",
"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 {
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>
<div style="height: 100%" class="page-edit-vue">
<div style="height: 100%;" class="page-edit-vue">
<el-row class="fake-header">
<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>
<el-button @click="turnLeftCollapse" v-else text :icon="ElIconExpand" class="fold-btn"></el-button>
<a-button @click="turnLeftCollapse" v-if="storeDisplay.showMenu" type="text" :icon="h(MenuFoldOutlined)"></a-button>
<a-button @click="turnLeftCollapse" v-else type="text" :icon="h(MenuUnfoldOutlined)"></a-button>
</el-col>
<el-col style="flex: 1 1 auto;">
<el-input v-model="pageTitleEdit" :maxlength="40" placeholder="请输入标题" class="page-title-input" ></el-input>
</el-col>
<el-col style="flex: 0 0 180px;text-align: right;">
<el-button type="primary" @click="createWikiSave(1)" :icon="ElIconDocumentChecked">保存</el-button>
<el-button @click="createWikiCancel" :icon="ElIconBack" style="margin-right: 5px;">取消</el-button>
<el-col style="flex: 0 0 190px;text-align: right;">
<a-button @click="createWikiSave(1)" :icon="h(SaveOutlined)" :loading="saveContentLoading" type="primary">保存</a-button>
<a-button @click="createWikiCancel" :icon="h(IconParkBack)" style="margin-left: 10px;">取消</a-button>
</el-col>
</el-row>
<div style="box-sizing: border-box;background: #f5f5f5;overflow: hidden">
@@ -27,7 +27,9 @@
</template>
<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 {ElMessageBox, ElMessage} from 'element-plus';
import {
@@ -133,7 +135,7 @@ const createWikiSave = (saveAfter) => {
preview = wangEditorRef.value.getPreview();
} else if (wikiPage.value.editorType === 2) {
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) {
preview = showContentSelector[0].textContent;
}
@@ -221,6 +223,9 @@ const addMarkdownImage = (pos, file) => {
color: #333;
height: 50px !important;
line-height: 50px !important;
padding: 0 8px 0 8px;
font-size: 14px;
background: #fff;
.fold-btn {
font-size: 18px;

View File

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

View File

@@ -27,24 +27,20 @@
</template>
</el-autocomplete>
<div class="space-folder-box" v-if="!props.readOnly">
<el-row justify="space-between">
<el-col :span="16">
<div class="page-group-tag">
<el-tooltip :content="spaceTreeIsClose?'点击展开目录':'点击收起目录'" placement="top">
<span class="label" @click="changeDropdownStatus">空间目录</span>
</el-tooltip>
</div>
</el-col>
<el-col :span="8" style="text-align: right;">
<AddMenu/>
</el-col>
</el-row>
<a-flex align="center" justify="space-between">
<div class="page-group-tag">
<el-tooltip :content="spaceTreeIsClose?'点击展开目录':'点击收起目录'" placement="top">
<span class="label" @click="changeDropdownStatus">空间目录</span>
</el-tooltip>
</div>
<AddMenu/>
</a-flex>
</div>
</div>
<div v-show="!spaceTreeIsClose" class="wiki-page-tree-box">
<el-tree ref="wikiPageTreeRef" :current-node-key="props.nowPageId" :data="storePage.pageList"
: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
style="background-color: #fafafa;">
<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">
<a-tag color="warning" style="margin-inline-end: 4px;padding-inline: 4px;"> {{data.shareStatus === 1 ? '公共模板' : '个人模板'}}</a-tag>
</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>
</span>
<!--操作-->
@@ -279,18 +275,20 @@ const doGetPageList = () => {
let spaceId = storeSpace.chooseSpaceId;
pageApi.pageList({spaceId: spaceId}).then((json) => {
storePage.pageList = json.data || [];
// 查看页面
if (storePage.pageList.length <= 0) {
router.push({path: `/view/${spaceId}`});
} else {
let routePageId = parseInt(route.params.pageId);
let findPage = storePage.getPageById(routePageId);
if (findPage) {
router.replace({path: `/view/${spaceId}/${routePageId}`});
if (route.path.startsWith('/view')) {
// 查看页面
if (storePage.pageList.length <= 0) {
router.push({path: `/view/${spaceId}`});
} else {
let firstPage = storePage.getFirstViewPage();
if (firstPage) {
router.replace({path: `/view/${spaceId}/${firstPage.id}`});
let routePageId = parseInt(route.params.pageId);
let findPage = storePage.getPageById(routePageId);
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) => {
searchKeywords.value = '';
router.push({path: '/page/show', query: {pageId: item.pageId}});
router.push({path: `/view/${item.spaceId}/${item.pageId}`});
}
let spaceTreeIsClose = ref(false);
const changeDropdownStatus = () => {
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 = () => {
wikiPageTreeRef.value.filter(searchKeywords.value);
}
@@ -413,13 +405,8 @@ defineExpose({searchByKeywords});
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;
@@ -454,6 +441,24 @@ defineExpose({searchByKeywords});
.search-autocomplete-popper {
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 {

View File

@@ -36,12 +36,12 @@
<a-button class="hover-button hover-bg" size="large" :icon="h(EllipsisOutlined)"></a-button>
<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-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" :icon="h(ShareAltOutlined)">查看开放文档</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" :icon="h(DownloadOutlined)">导出为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-item @click="deleteWikiPage" v-if="storePage.pageAuth.canDelete === 1" :icon="h(DeleteOutlined)" danger class="delete">删除</a-menu-item>
</a-menu>
</template>
</a-dropdown>
@@ -70,20 +70,8 @@
<script setup>
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,
UserFilled as ElIconUserFilled,
} from '@element-plus/icons-vue'
import {
UserOutlined, EditOutlined, MessageOutlined, CheckOutlined, EllipsisOutlined,
MenuFoldOutlined, MenuUnfoldOutlined, LoadingOutlined
UserOutlined, EditOutlined, MessageOutlined, CheckOutlined, EllipsisOutlined, DeleteOutlined, ShareAltOutlined,
MenuFoldOutlined, MenuUnfoldOutlined, LoadingOutlined, SafetyOutlined, DownloadOutlined, MobileOutlined
} from '@ant-design/icons-vue';
import {toRefs, ref, reactive, onMounted, watch, defineEmits, h, computed} from 'vue';
import {useRouter, useRoute} from "vue-router";

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff