---wiki正式切换到vue3版本---

This commit is contained in:
暮光:城中城
2023-01-11 20:33:42 +08:00
parent bc28320565
commit b8068ef29e
115 changed files with 15723 additions and 40477 deletions

View File

@@ -1,43 +1,39 @@
<template>
<div id="app">
<router-view></router-view>
</div>
<router-view></router-view>
</template>
<script>
export default {
name: 'app',
components: {},
data() {
return {};
},
mounted() {
// console.log("VUE_APP_TEST_ENV" + process.env.VUE_APP_TEST_ENV);
},
methods: {}
}
<script setup>
import {onBeforeUnmount, ref, onMounted, watch, defineProps, nextTick, defineEmits, defineExpose, computed} from 'vue';
import {onBeforeRouteUpdate, useRouter, useRoute} from "vue-router";
import {ElMessageBox, ElMessage} from 'element-plus'
</script>
<style>
html, body {
margin: 0;
padding: 0;
height: 100%;
}
html,
body {
margin: 0;
padding: 0;
height: 100%;
}
#app, .el-container, .el-menu {
height: 100%;
}
::-webkit-scrollbar {
width: 6px;
height: 9px;
-webkit-appearance: none;
}
::-webkit-scrollbar-thumb {
background: #ddd;
border-radius: 10px;
}
::-webkit-scrollbar-track-piece {
background: #eee;
}
#app,
.el-container,
.el-menu {
height: 100%;
}
::-webkit-scrollbar {
width: 6px;
height: 9px;
-webkit-appearance: none;
}
::-webkit-scrollbar-thumb {
background: #ddd;
border-radius: 10px;
}
::-webkit-scrollbar-track-piece {
background: #eee;
}
</style>

View File

@@ -0,0 +1,8 @@
import Qs from 'qs'
import request from './request'
export default {
getUserBaseInfo: (data) => {
return request({url: '/zyplayer-doc-wiki/common/user/base', method: 'post', data: Qs.stringify(data)});
},
}

View File

@@ -0,0 +1,37 @@
import Qs from 'qs'
import request from './request'
export default {
pageUpdate: (data) => request({url: '/zyplayer-doc-wiki/page/update', method: 'post', data: Qs.stringify(data)}),
pageChangeParent: (data) => request({url: '/zyplayer-doc-wiki/page/changeParent', method: 'post', data: Qs.stringify(data)}),
pageList: (data) => request({url: '/zyplayer-doc-wiki/page/list', method: 'post', data: Qs.stringify(data)}),
updatePage: (data) => request({url: '/zyplayer-doc-wiki/page/update', method: 'post', data: Qs.stringify(data)}),
pageDetail: (data) => request({url: '/zyplayer-doc-wiki/page/detail', method: 'post', data: Qs.stringify(data)}),
pageDelete: (data) => request({url: '/zyplayer-doc-wiki/page/delete', method: 'post', data: Qs.stringify(data)}),
pageHistoryList: (data) => request({url: '/zyplayer-doc-wiki/page/history/list', method: 'post', data: Qs.stringify(data)}),
pageHistoryDetail: (data) => request({url: '/zyplayer-doc-wiki/page/history/detail', method: 'post', data: Qs.stringify(data)}),
pageNews: (data) => request({url: '/zyplayer-doc-wiki/page/news', method: 'post', data: Qs.stringify(data)}),
pageSearchByEs: (data) => request({url: '/zyplayer-doc-wiki/page/searchByEs', method: 'post', data: Qs.stringify(data)}),
pageLock: (data) => request({url: '/zyplayer-doc-wiki/page/lock', method: 'post', data: Qs.stringify(data)}),
pageUnlock: (data) => request({url: '/zyplayer-doc-wiki/page/unlock', method: 'post', data: Qs.stringify(data)}),
spaceFavoriteUpdate: (data) => request({url: '/zyplayer-doc-wiki/space/favorite/update', method: 'post', data: Qs.stringify(data)}),
spaceAuthAssign: (data) => request({url: '/zyplayer-doc-wiki/space/auth/assign', method: 'post', data: Qs.stringify(data)}),
spaceAuthList: (data) => request({url: '/zyplayer-doc-wiki/space/auth/list', method: 'post', data: Qs.stringify(data)}),
spaceSettingList: (data) => request({url: '/zyplayer-doc-wiki/space/setting/list', method: 'post', data: Qs.stringify(data)}),
spaceSettingUpdate: (data) => request({url: '/zyplayer-doc-wiki/space/setting/update', method: 'post', data: Qs.stringify(data)}),
spaceList: (data) => request({url: '/zyplayer-doc-wiki/space/list', method: 'post', data: Qs.stringify(data)}),
updateSpace: (data) => request({url: '/zyplayer-doc-wiki/space/update', method: 'post', data: Qs.stringify(data)}),
getPageUserAuthList: (data) => request({url: '/zyplayer-doc-wiki/page/auth/list', method: 'post', data: Qs.stringify(data)}),
assignPageUserAuth: (data) => request({url: '/zyplayer-doc-wiki/page/auth/assign', method: 'post', data: Qs.stringify(data)}),
deletePageFile: (data) => request({url: '/zyplayer-doc-wiki/page/file/delete', method: 'post', data: Qs.stringify(data)}),
pageCommentList: (data) => request({url: '/zyplayer-doc-wiki/page/comment/list', method: 'post', data: Qs.stringify(data)}),
updatePageComment: (data) => request({url: '/zyplayer-doc-wiki/page/comment/update', method: 'post', data: Qs.stringify(data)}),
deletePageComment: (data) => request({url: '/zyplayer-doc-wiki/page/comment/delete', method: 'post', data: Qs.stringify(data)}),
pageZanList: (data) => request({url: '/zyplayer-doc-wiki/page/zan/list', method: 'post', data: Qs.stringify(data)}),
updatePageZan: (data) => request({url: '/zyplayer-doc-wiki/page/zan/update', method: 'post', data: Qs.stringify(data)}),
openPageDetail: (data) => request({url: '/zyplayer-doc-wiki/open-api/page/detail', method: 'post', data: Qs.stringify(data)}),
openPageList: (data) => request({url: '/zyplayer-doc-wiki/open-api/page/list', method: 'post', data: Qs.stringify(data)}),
openSpaceInfo: (data) => request({url: '/zyplayer-doc-wiki/open-api/space/info', method: 'post', data: Qs.stringify(data)}),
openPageNews: (data) => request({url: '/zyplayer-doc-wiki/open-api/page/news', method: 'post', data: Qs.stringify(data)}),
xxxxxxxxxxxx: (data) => request({url: 'update', method: 'post', data: Qs.stringify(data)}),
}

View File

@@ -0,0 +1,54 @@
import axios from 'axios'
import {ElMessageBox, ElMessage} from 'element-plus'
const service = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API, // url = base url + request url process.env.APP_BASE_API
timeout: 10000,
headers: {'Content-type': 'application/x-www-form-urlencoded'},
withCredentials: true,
})
// 增加不需要验证结果的标记
const noValidate = {
'/zyplayer-doc-db/executor/execute': true,
}
service.interceptors.request.use((config) => {
config.needValidateResult = true
// 增加不需要验证结果的标记
if (noValidate[config.url]) {
config.needValidateResult = false
}
return config
}, (error) => {
console.log(error)
return Promise.reject(error)
}
)
let lastToastLoginTime = new Date().getTime();
service.interceptors.response.use(
(response) => {
if (!!response.message) {
ElMessage.error('请求错误:' + response.message)
} else {
if (!response.config.needValidateResult || response.data.errCode === 200) {
return response.data
} else if (response.data.errCode === 400) {
// 两秒钟只提示一次
if (new Date().getTime() - lastToastLoginTime > 2000) {
ElMessage.warning('请先登录');
lastToastLoginTime = new Date().getTime();
}
let href = encodeURIComponent(window.location.href)
window.location = import.meta.env.VITE_APP_BASE_API + '#/user/login?redirect=' + href
} else if (response.data.errCode !== 200) {
ElMessage.error(response.data.errMsg || '未知错误')
}
}
return Promise.reject('请求错误')
}, (error) => {
console.log('err' + error)
ElMessage.info('请求错误:' + error.message)
return Promise.reject(error)
}
)
export default service

View File

@@ -0,0 +1,6 @@
import Qs from 'qs'
import request from './request'
export default {
systemUpgradeInfo: (data) => request({url: '/system/info/upgrade', method: 'post', data: Qs.stringify(data)}),
}

View File

@@ -0,0 +1,13 @@
import Qs from 'qs'
import request from './request'
export default {
userLogin: (data) => request({url: '/login', method: 'post', data: Qs.stringify(data)}),
userLogout: () => request({url: '/logout', method: 'post', data: Qs.stringify({})}),
getSelfUserInfo: () => request({url: '/user/info/selfInfo', method: 'post', data: Qs.stringify({})}),
getUserBaseInfo: (data) => request({url: '/zyplayer-doc-wiki/common/user/base', method: 'post', data: Qs.stringify(data)}),
userGroupList: (data) => request({url: '/user/group/list', method: 'post', data: Qs.stringify(data)}),
getUserMessageList: (data) => request({url: '/user/message/list', method: 'post', data: Qs.stringify(data)}),
readUserMessage: (data) => request({url: '/user/message/read', method: 'post', data: Qs.stringify(data)}),
deleteUserMessage: (data) => request({url: '/user/message/delete', method: 'post', data: Qs.stringify(data)}),
}

View File

@@ -0,0 +1,23 @@
export default {
createNavigationHeading() {
let headArr = []
let headNodeArr = document
.querySelector('.wiki-page-content')
.querySelectorAll('h1,h2,h3,h4,h5,h6')
if (headNodeArr.length <= 0) {
return []
}
headNodeArr.forEach((node) => {
let text = node.innerHTML
.replace(/^\s+/g, '')
.replace(/\s+$/g, '')
.replace(/<\/?[^>]+(>|$)/g, '')
headArr.push({
node: node,
level: parseInt(node.tagName.replace(/[h]/i, ''), 10),
text: text,
})
})
return headArr
},
}

View File

@@ -0,0 +1,24 @@
export default {
computeFileSize(fileSize) {
if (!fileSize) {
return '-'
}
let size = ''
if (fileSize < 0.1 * 1024) {
size = fileSize.toFixed(2) + 'B'
} else if (fileSize < 0.1 * 1024 * 1024) {
size = (fileSize / 1024).toFixed(2) + 'KB'
} else if (fileSize < 0.1 * 1024 * 1024 * 1024) {
size = (fileSize / (1024 * 1024)).toFixed(2) + 'MB'
} else {
size = (fileSize / (1024 * 1024 * 1024)).toFixed(2) + 'GB'
}
let sizeStr = size + ''
let index = sizeStr.indexOf('.')
let dou = sizeStr.substr(index + 1, 2)
if (dou == '00') {
return sizeStr.substring(0, index) + sizeStr.substr(index + 3, 2)
}
return size
},
}

View File

@@ -0,0 +1,46 @@
/**展示内容的样式*/
.wang-editor-body {
font-size: 14px;
padding: 6px;
overflow-y: auto;
}
.wang-editor-body table {
border-top: 1px solid #ccc;
border-left: 1px solid #ccc;
}
.wang-editor-body table td, .wang-editor-body table th {
border-bottom: 1px solid #ccc;
border-right: 1px solid #ccc;
padding: 3px 5px;
}
.wang-editor-body table th {
border-bottom: 2px solid #ccc;
text-align: center;
}
.wang-editor-body blockquote {
display: block;
border-left: 8px solid #d0e5f2;
padding: 5px 10px;
margin: 10px 0;
line-height: 1.4;
font-size: 100%;
background-color: #f1f1f1;
}
.wang-editor-body code {
display: inline-block;
*display: inline;
*zoom: 1;
background-color: #f1f1f1;
border-radius: 3px;
padding: 3px 5px;
margin: 0 3px;
}
.wang-editor-body pre code {
display: block;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -1,8 +0,0 @@
import Qs from 'qs'
import request from './request'
export default {
getUserBaseInfo: data => {
return request({url: '/zyplayer-doc-wiki/common/user/base', method: 'post', data: Qs.stringify(data)});
},
};

View File

@@ -1,102 +0,0 @@
import Qs from 'qs'
import request from './request'
export default {
pageUpdate: data => {
return request({url: '/zyplayer-doc-wiki/page/update', method: 'post', data: Qs.stringify(data)});
},
pageChangeParent: data => {
return request({url: '/zyplayer-doc-wiki/page/changeParent', method: 'post', data: Qs.stringify(data)});
},
pageList: data => {
return request({url: '/zyplayer-doc-wiki/page/list', method: 'post', data: Qs.stringify(data)});
},
updatePage: data => {
return request({url: '/zyplayer-doc-wiki/page/update', method: 'post', data: Qs.stringify(data)});
},
pageDetail: data => {
return request({url: '/zyplayer-doc-wiki/page/detail', method: 'post', data: Qs.stringify(data)});
},
pageDelete: data => {
return request({url: '/zyplayer-doc-wiki/page/delete', method: 'post', data: Qs.stringify(data)});
},
pageHistoryList: data => {
return request({url: '/zyplayer-doc-wiki/page/history/list', method: 'post', data: Qs.stringify(data)});
},
pageHistoryDetail: data => {
return request({url: '/zyplayer-doc-wiki/page/history/detail', method: 'post', data: Qs.stringify(data)});
},
pageNews: data => {
return request({url: '/zyplayer-doc-wiki/page/news', method: 'post', data: Qs.stringify(data)});
},
pageSearchByEs: data => {
return request({url: '/zyplayer-doc-wiki/page/searchByEs', method: 'post', data: Qs.stringify(data)});
},
pageLock: data => {
return request({url: '/zyplayer-doc-wiki/page/lock', method: 'post', data: Qs.stringify(data)});
},
pageUnlock: data => {
return request({url: '/zyplayer-doc-wiki/page/unlock', method: 'post', data: Qs.stringify(data)});
},
spaceFavoriteUpdate: data => {
return request({url: '/zyplayer-doc-wiki/space/favorite/update', method: 'post', data: Qs.stringify(data)});
},
spaceAuthAssign: data => {
return request({url: '/zyplayer-doc-wiki/space/auth/assign', method: 'post', data: Qs.stringify(data)});
},
spaceAuthList: data => {
return request({url: '/zyplayer-doc-wiki/space/auth/list', method: 'post', data: Qs.stringify(data)});
},
spaceSettingList: data => {
return request({url: '/zyplayer-doc-wiki/space/setting/list', method: 'post', data: Qs.stringify(data)});
},
spaceSettingUpdate: data => {
return request({url: '/zyplayer-doc-wiki/space/setting/update', method: 'post', data: Qs.stringify(data)});
},
spaceList: data => {
return request({url: '/zyplayer-doc-wiki/space/list', method: 'post', data: Qs.stringify(data)});
},
updateSpace: data => {
return request({url: '/zyplayer-doc-wiki/space/update', method: 'post', data: Qs.stringify(data)});
},
getPageUserAuthList: data => {
return request({url: '/zyplayer-doc-wiki/page/auth/list', method: 'post', data: Qs.stringify(data)});
},
assignPageUserAuth: data => {
return request({url: '/zyplayer-doc-wiki/page/auth/assign', method: 'post', data: Qs.stringify(data)});
},
deletePageFile: data => {
return request({url: '/zyplayer-doc-wiki/page/file/delete', method: 'post', data: Qs.stringify(data)});
},
pageCommentList: data => {
return request({url: '/zyplayer-doc-wiki/page/comment/list', method: 'post', data: Qs.stringify(data)});
},
updatePageComment: data => {
return request({url: '/zyplayer-doc-wiki/page/comment/update', method: 'post', data: Qs.stringify(data)});
},
deletePageComment: data => {
return request({url: '/zyplayer-doc-wiki/page/comment/delete', method: 'post', data: Qs.stringify(data)});
},
pageZanList: data => {
return request({url: '/zyplayer-doc-wiki/page/zan/list', method: 'post', data: Qs.stringify(data)});
},
updatePageZan: data => {
return request({url: '/zyplayer-doc-wiki/page/zan/update', method: 'post', data: Qs.stringify(data)});
},
openPageDetail: data => {
return request({url: '/zyplayer-doc-wiki/open-api/page/detail', method: 'post', data: Qs.stringify(data)});
},
openPageList: data => {
return request({url: '/zyplayer-doc-wiki/open-api/page/list', method: 'post', data: Qs.stringify(data)});
},
openSpaceInfo: data => {
return request({url: '/zyplayer-doc-wiki/open-api/space/info', method: 'post', data: Qs.stringify(data)});
},
openPageNews: data => {
return request({url: '/zyplayer-doc-wiki/open-api/page/news', method: 'post', data: Qs.stringify(data)});
},
xxxxxxxxxxxx: data => {
return request({url: 'update', method: 'post', data: Qs.stringify(data)});
},
};

View File

@@ -1,55 +0,0 @@
import axios from 'axios'
import vue from '../../main'
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url process.env.APP_BASE_API
timeout: 10000,
headers: {'Content-type': 'application/x-www-form-urlencoded'},
withCredentials: true
});
// 增加不需要验证结果的标记
const noValidate = {
"/zyplayer-doc-db/executor/execute": true,
};
service.interceptors.request.use(
config => {
config.needValidateResult = true;
// 增加不需要验证结果的标记
if (noValidate[config.url]) {
config.needValidateResult = false;
}
return config
},
error => {
console.log(error);
return Promise.reject(error);
}
);
service.interceptors.response.use(
response => {
if (!!response.message) {
vue.$message.error('请求错误:' + response.message);
}else {
if (!response.config.needValidateResult || response.data.errCode == 200) {
return response.data;
} else if (response.data.errCode == 400) {
vue.$message.error('请先登录');
var href = encodeURIComponent(window.location.href);
window.location = process.env.VUE_APP_BASE_API + "#/user/login?redirect=" + href;
} else if (response.data.errCode == 402) {
vue.$router.push("/common/noAuth");
} else if (response.data.errCode !== 200) {
vue.$message.error(response.data.errMsg || "未知错误");
}
}
return Promise.reject('请求错误');
},
error => {
console.log('err' + error);
vue.$message.info('请求错误:' + error.message);
return Promise.reject(error)
}
);
export default service;

View File

@@ -1,8 +0,0 @@
import Qs from 'qs'
import request from './request'
export default {
systemUpgradeInfo: data => {
return request({url: '/system/info/upgrade', method: 'post', data: Qs.stringify(data)});
},
};

View File

@@ -1,34 +0,0 @@
import Qs from 'qs'
import request from './request'
export default {
userLogin: data => {
return request({url: '/login', method: 'post', data: Qs.stringify(data)});
},
userLogout: () => {
return request({url: '/logout', method: 'post', data: Qs.stringify({})});
},
getSelfUserInfo: () => {
return request({url: '/user/info/selfInfo', method: 'post', data: Qs.stringify({})});
},
getUserBaseInfo: data => {
return request({url: '/zyplayer-doc-wiki/common/user/base', method: 'post', data: Qs.stringify(data)});
},
userGroupList: data => {
return request({url: '/user/group/list', method: 'post', data: Qs.stringify(data)});
},
getUserMessageList: data => {
return request({url: '/user/message/list', method: 'post', data: Qs.stringify(data)});
},
readUserMessage: data => {
return request({url: '/user/message/read', method: 'post', data: Qs.stringify(data)});
},
deleteUserMessage: data => {
return request({url: '/user/message/delete', method: 'post', data: Qs.stringify(data)});
},
};
// userLogin: '/login',
// userLogout: '/logout',
// getSelfUserInfo: '/user/info/selfInfo',
// getUserBaseInfo: '/zyplayer-doc-wiki/common/user/base',

View File

@@ -1,19 +0,0 @@
export default {
createNavigationHeading() {
let headArr = [];
let headNodeArr = document.querySelector('.wiki-page-content').querySelectorAll('h1,h2,h3,h4,h5,h6');
if (headNodeArr.length <= 0) {
return [];
}
headNodeArr.forEach(node => {
let text = node.innerHTML.replace(/^\s+/g, '').replace(/\s+$/g, '').replace(/<\/?[^>]+(>|$)/g, '');
headArr.push({
node: node,
level: parseInt(node.tagName.replace(/[h]/i, ''), 10),
text: text
});
});
return headArr;
},
}

View File

@@ -1,26 +0,0 @@
export default {
computeFileSize(fileSize) {
if (!fileSize) {
return '-';
}
let size = "";
if (fileSize < 0.1 * 1024) {
size = fileSize.toFixed(2) + "B"
} else if (fileSize < 0.1 * 1024 * 1024) {
size = (fileSize / 1024).toFixed(2) + "KB"
} else if (fileSize < 0.1 * 1024 * 1024 * 1024) {
size = (fileSize / (1024 * 1024)).toFixed(2) + "MB"
} else {
size = (fileSize / (1024 * 1024 * 1024)).toFixed(2) + "GB"
}
let sizeStr = size + "";
let index = sizeStr.indexOf(".");
let dou = sizeStr.substr(index + 1, 2);
if (dou == "00") {
return sizeStr.substring(0, index) + sizeStr.substr(index + 3, 2)
}
return size;
},
}

View File

@@ -1,25 +0,0 @@
/**展示内容的样式*/
.wang-editor-body {
font-size: 14px;padding: 6px;overflow-y: auto;
}
.wang-editor-body table {
border-top: 1px solid #ccc; border-left: 1px solid #ccc;
}
.wang-editor-body table td, .wang-editor-body table th {
border-bottom: 1px solid #ccc; border-right: 1px solid #ccc; padding: 3px 5px;
}
.wang-editor-body table th {
border-bottom: 2px solid #ccc; text-align: center;
}
.wang-editor-body blockquote {
display: block; border-left: 8px solid #d0e5f2; padding: 5px 10px; margin: 10px 0;
line-height: 1.4; font-size: 100%; background-color: #f1f1f1;
}
.wang-editor-body code {
display: inline-block; *display: inline; *zoom: 1;
background-color: #f1f1f1; border-radius: 3px; padding: 3px 5px; margin: 0 3px;
}
.wang-editor-body pre code {
display: block;
}

View File

@@ -3,30 +3,21 @@
<el-container>
<el-aside v-show="leftCollapse" :style="{ width: rightAsideWidth + 'px' }">
<div style="padding: 10px;height: 100%;box-sizing: border-box;background: #fafafa;">
<div style="margin-bottom: 10px;">
<el-select :value="choiceSpace" filterable placeholder="选择空间" style="width: 100%;"
@change="spaceChangeEvents">
<div style="margin-bottom: 10px">
<el-select :model-value="choiceSpace" filterable placeholder="选择空间" style="width: 100%" @change="spaceChangeEvents">
<el-option-group label="">
<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 v-for="item in spaceOptions" :key="item.value" :label="item.label"
:value="item.value"></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-select>
</div>
<div align="center">
<el-button icon="el-icon-plus" style="width: 100%;" v-on:click="createWiki">创建文档</el-button>
<el-button :icon="ElIconPlus" style="width: 100%" v-on:click="createWiki">创建文档</el-button>
</div>
<el-autocomplete
v-model="searchKeywords" :fetch-suggestions="doSearchByKeywords"
placeholder="在当前空间搜索"
popper-class="search-autocomplete"
style="width: 100%;margin: 10px 0;"
@select="handleSearchKeywordsSelect"
>
<template slot-scope="{ item }">
<el-autocomplete v-model="searchKeywords" :fetch-suggestions="doSearchByKeywords" placeholder="在当前空间搜索" popper-class="search-autocomplete" style="width: 100%; margin: 10px 0" @select="handleSearchKeywordsSelect">
<template v-slot="{ item }">
<div class="search-option-item">
<div class="title">
<span v-html="item.pageTitle || '-'"></span>
@@ -36,385 +27,417 @@
</template>
</el-autocomplete>
<div class="wiki-page-tree-box">
<el-tree ref="wikiPageTree"
:current-node-key="nowPageId"
:data="wikiPageList"
:default-expanded-keys="wikiPageExpandedKeys"
:expand-on-click-node="false"
:filter-node-method="filterPageNode"
:props="defaultProps"
draggable
highlight-current
node-key="id"
style="background-color: #fafafa;"
@node-click="handleNodeClick"
@node-expand="handleNodeExpand"
@node-drop="handlePageDrop">
<span slot-scope="{node,data}" style="font-size:14px;">
<i class="el-icon-document"></i>
<span style="margin-left: 6px;">{{ node.label }}</span>
</span>
<el-tree
ref="wikiPageTreeRef"
:current-node-key="nowPageId"
:data="wikiPageList"
:default-expanded-keys="wikiPageExpandedKeys"
:expand-on-click-node="true"
:filter-node-method="filterPageNode"
:props="defaultProps"
draggable
highlight-current
node-key="id"
style="background-color: #fafafa"
@node-click="handleNodeClick"
@node-expand="handleNodeExpand"
@node-drop="handlePageDrop"
>
<template v-slot="{ node, data }">
<span style="font-size: 14px">
<el-icon><el-icon-document/></el-icon>
<span style="margin-left: 6px">{{ node.label }}</span>
</span>
</template>
</el-tree>
</div>
</div>
</el-aside>
<RightResize v-show="leftCollapse" v-model="rightAsideWidth" @change="rightAsideWidthChange"></RightResize>
<RightResize v-show="leftCollapse" v-model:value="rightAsideWidth" @change="rightAsideWidthChange"></RightResize>
<el-container>
<el-header>
<i v-if="leftCollapse" class="el-icon-fold el-icon-s-fold" @click="turnLeftCollapse"></i>
<i v-else class="el-icon-fold el-icon-s-unfold" @click="turnLeftCollapse"></i>
<span class="header-right-user-name">{{ userSelfInfo.userName }}</span>
<el-popover v-model="userMessagePopVisible" placement="bottom" trigger="click" width="600">
<el-badge slot="reference" :is-dot="haveNotReadUserMessage"
style="line-height: 20px;margin: 0 15px;">
<i class="el-icon-bell head-icon" style="margin-right: 0;"
@click="loadUserMessageIfPopVisible"></i>
</el-badge>
<div style="margin-bottom: 10px;">
<span style="font-size: 14px;font-weight: bold;">通知</span>
<el-link v-if="haveNotReadUserMessage" icon="el-icon-check" style="float: right;"
type="primary" v-on:click="readAllUserMessage">本页标记已读
</el-link>
</div>
<div class="header-user-message">
<el-table :data="userMessageList" border max-height="500"
size="mini" 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 slot-scope="scope">
{{ scope.row.msgContent }}
<el-badge slot="reference" :is-dot="scope.row.msgStatus==0"
style="line-height: 10px;padding-right: 5px;">
<el-link type="primary" v-on: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">
<i class="el-icon-setting head-icon"></i>
<el-dropdown-menu slot="dropdown">
<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>
</el-dropdown>
<el-row>
<el-col :span="12">
<el-button @click="turnLeftCollapse" v-if="leftCollapse" 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="12" style="text-align: right;">
<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" style="margin: 0 20px">
<el-icon class="head-icon" style="margin-right: 0">
<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" v-on: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" v-on: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: middle;">
<el-icon class="head-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>
</el-col>
</el-row>
</el-header>
<el-main style="padding: 0;border-left: 1px solid #dcdfe6;">
<router-view :spaceId="choiceSpace"
:spaceInfo="getSpaceInfo(choiceSpace)"
@changeExpandedKeys="changeWikiPageExpandedKeys"
@loadPageList="loadPageList"
@loadSpace="loadSpaceList"
@switchSpace="switchSpacePage">
<el-main style="padding: 0; border-left: 1px solid #dcdfe6">
<router-view
v-slot="{ Component }"
:spaceId="choiceSpace"
:spaceInfo="getSpaceInfo(choiceSpace)"
@changeExpandedKeys="changeWikiPageExpandedKeys"
@loadPageList="loadPageList"
@loadSpace="loadSpaceList"
@switchSpace="switchSpacePage">
<component :is="Component"></component>
</router-view>
</el-main>
</el-container>
</el-container>
<create-space ref="createSpace" @success="loadSpaceList"></create-space>
<about-dialog ref="aboutDialog"></about-dialog>
<create-space ref="createSpaceRef" @success="loadSpaceList"></create-space>
<about-dialog ref="aboutDialogRef"></about-dialog>
</div>
</template>
<script>
import userApi from '../../common/api/user'
import pageApi from '../../common/api/page'
<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 {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 userApi from '../../assets/api/user'
import pageApi from '../../assets/api/page'
import CreateSpace from '../space/CreateSpace'
import RightResize from './RightResize.vue'
import aboutDialog from "../../views/common/AboutDialog";
import AboutDialog from '../../views/common/AboutDialog'
import {useStoreDisplay} from '@/store/wikiDisplay.js'
import {useStoreUserData} from "@/store/userData";
export default {
data() {
return {
leftCollapse: true,
rightContentLoading: false,
pathIndex: [],
defaultProps: {
children: 'children',
label: 'name'
},
// 空间搜索相关
spaceOptions: [],
spaceList: [],
choiceSpace: "",
nowSpaceShow: {},
nowPageId: '',
// 依据目录树存储的map全局对象
treePathDataMap: new Map(),
// 搜索的输入内容
searchKeywords: "",
// 页面展示相关
wikiPageList: [],
wikiPage: {},
wikiPageExpandedKeys: [],
userSelfInfo: {},
userMessageList: [],
haveNotReadUserMessage: false,
userMessagePopVisible: false,
userMsgTotalCount: 0,
userMsgParam: {
sysType: 2,
pageNum: 1,
pageSize: 20,
},
rightAsideWidth: 300,
let route = useRoute();
let router = useRouter();
const storeDisplay = useStoreDisplay();
let leftCollapse = ref(true);
let rightContentLoading = ref(false);
let pathIndex = ref([]);
let defaultProps = ref({children: 'children', label: 'name',});
// 空间搜索相关
let spaceOptions = ref([]);
let spaceList = ref([]);
let choiceSpace = ref('');
let nowSpaceShow = ref({});
let nowPageId = ref('');
// 依据目录树存储的map全局对象
let treePathDataMap = ref(new Map());
// 搜索的输入内容
let searchKeywords = ref('');
// 页面展示相关
let wikiPageList = ref([]);
let wikiPage = ref({});
let wikiPageExpandedKeys = ref([]);
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 rightAsideWidth = ref(300);
onMounted(() => {
loadSpaceList()
loadUserMessageList()
getSelfUserInfo()
});
const loadPageList = (param) => {
param = param || {}
doGetPageList(param.parentId, param.node)
}
const turnLeftCollapse = () => {
leftCollapse.value = !leftCollapse.value
setTimeout(() => {
if (leftCollapse.value) {
rightAsideWidthChange(rightAsideWidth.value + 1)
} else {
rightAsideWidthChange(1)
}
},
components: {
RightResize,
"create-space": CreateSpace,
'about-dialog': aboutDialog
},
computed: {},
mounted: function () {
this.loadSpaceList();
this.loadUserMessageList();
this.getSelfUserInfo();
},
methods: {
loadPageList(param) {
param = param || {};
this.doGetPageList(param.parentId, param.node);
},
turnLeftCollapse() {
this.leftCollapse = !this.leftCollapse;
setTimeout(() => {
if (this.leftCollapse) {
this.rightAsideWidthChange(this.rightAsideWidth + 1);
} else {
this.rightAsideWidthChange(1);
}
}, 100);
},
createWiki() {
if (this.choiceSpace > 0) {
this.$router.push({path: '/page/edit', query: {parentId: this.nowPageId}});
} else {
this.$message.warning("请先选择或创建空间");
}
},
changeWikiPageExpandedKeys(pageId) {
// 展开没有触发子节点的加载如果去加载子节点有还找不到当前的node暂不展开
// this.wikiPageExpandedKeys = [pageId];
},
doSearchByKeywords(queryString, callback) {
if (!queryString || !queryString.trim()) {
callback([]);
return;
}
pageApi.pageNews({spaceId: this.choiceSpace, keywords: queryString}).then(json => {
let spacePageNews = json.data || [];
callback(spacePageNews);
});
},
handleSearchKeywordsSelect(item) {
this.searchKeywords = '';
this.$router.push({path: '/page/show', query: {pageId: item.pageId}});
},
searchByKeywords() {
this.$refs.wikiPageTree.filter(this.searchKeywords);
},
searchByKeywordsNewPage() {
var routeUrl = this.$router.resolve({path: '/page/search', query: {keywords: this.searchKeywords}});
window.open(routeUrl.href, '_blank');
},
handleNodeClick(data) {
console.log("点击节点:", data, this.nowPageId);
this.nowPageId = data.id;
this.$router.push({path: '/page/show', query: {pageId: data.id}});
this.handleNodeExpand(data);
},
handleNodeExpand(node) {
if (node.children && node.children.length > 0 && node.children[0].needLoad) {
console.log("加载节点:", node);
this.doGetPageList(node.id, node);
}
},
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 => {
this.doGetPageList(null);
});
},
loadUserMessageIfPopVisible() {
if (!this.userMessagePopVisible) {
this.loadUserMessageList();
}
},
loadUserMessageList() {
userApi.getUserMessageList(this.userMsgParam).then(res => {
this.userMessageList = res.data || [];
this.userMsgTotalCount = res.total || 0;
this.haveNotReadUserMessage = (this.userMessageList.filter(item => item.msgStatus == 0).length) > 0;
});
},
showUserMessage(row) {
if (row.msgStatus == 0) {
userApi.readUserMessage({ids: row.id}).then(() => {
this.loadUserMessageList();
});
}
if (row.msgType >= 2 && row.msgType <= 12) {
this.$router.push({path: '/page/show', query: {pageId: row.dataId}});
this.userMessagePopVisible = false;
}
},
readAllUserMessage() {
let msgIds = [];
this.userMessageList.filter(item => item.msgStatus == 0).forEach(item => {
msgIds.push(item.id);
});
if (msgIds.length <= 0) return;
userApi.readUserMessage({ids: msgIds.join(',')}).then(() => {
this.$message.success("标记成功");
this.loadUserMessageList();
});
},
handleCurrentChange(val) {
this.userMsgParam.pageNum = val;
this.loadUserMessageList();
},
filterPageNode(value, data) {
if (!value || !data.name) return true;
// issues:I2CG72 忽略大小写
let name = data.name.toLowerCase();
return name.indexOf(value.toLowerCase()) !== -1;
},
spaceChangeEvents(data) {
if (data == 0) {
// 新建空间
this.$refs.createSpace.show();
} else if (data == -1) {
// 管理空间
this.$router.push({path: '/space/manage'});
} else {
this.choiceSpace = data;
for (let i = 0; i < this.spaceList.length; i++) {
if (this.spaceList[i].id == data) {
this.nowSpaceShow = this.spaceList[i];
break;
}
}
this.nowPageId = '';
this.doGetPageList(null);
this.$router.push({path: '/home', query: {spaceId: data}});
}
},
loadSpaceList(spaceId) {
pageApi.spaceList({}).then(json => {
this.spaceList = json.data || [];
let spaceOptions = [];
this.spaceList.forEach(item => spaceOptions.push({label: item.name, value: item.id}));
this.spaceOptions = spaceOptions;
if (this.spaceList.length > 0) {
let nowSpaceId = spaceId;
let nowSpaceShow = this.spaceList.find(item => item.id == spaceId);
if (!nowSpaceShow) {
nowSpaceShow = this.spaceList[0];
nowSpaceId = nowSpaceShow.id;
}
this.nowSpaceShow = nowSpaceShow;
this.choiceSpace = nowSpaceId;
this.nowPageId = '';
this.doGetPageList(null);
// TODO 在首页时跳转
try {
if (this.$router.app._route.path == "/home") {
this.$router.push({path: '/home', query: {spaceId: nowSpaceId}});
}
} catch (e) {
console.log(e);
}
}
});
},
doGetPageList(parentId, node) {
let param = {spaceId: this.choiceSpace};
pageApi.pageList(param).then(json => {
this.wikiPageList = json.data || [];
// 设置默认选中效果
this.$nextTick(() => {
this.nowPageId = this.$route.query.pageId
if (this.nowPageId) {
console.log("moern?")
this.$refs['wikiPageTree'].setCurrentKey(this.nowPageId)
}
})
});
},
userSettingDropdown(command) {
console.log("command:" + command);
if (command == 'userSignOut') {
this.userSignOut();
} else if (command == 'aboutDoc') {
this.$refs.aboutDialog.show();
} else if (command == 'myInfo') {
this.$router.push({path: '/user/myInfo'});
} else if (command == 'console') {
window.open(process.env.VUE_APP_BASE_API, '_blank');
} else {
this.$message.warning("暂未开放");
}
},
userSignOut() {
userApi.userLogout().then(() => {
location.reload();
});
},
getSelfUserInfo() {
userApi.getSelfUserInfo().then(json => {
this.userSelfInfo = json.data;
});
},
getSpaceInfo(spaceId) {
for (let i = 0; i < this.spaceList.length; i++) {
if (this.spaceList[i].id == spaceId) {
return this.spaceList[i];
}
}
return {};
},
switchSpacePage(spaceId) {
spaceId = parseInt(spaceId);
if (this.choiceSpace == spaceId) {
return;
}
this.choiceSpace = spaceId;
this.doGetPageList(null);
},
rightAsideWidthChange(width) {
this.$store.commit('global/setRightAsideWidth', width);
},
}, 100)
}
const createWiki = () => {
if (choiceSpace.value > 0) {
router.push({
path: '/page/edit',
query: {parentId: nowPageId.value}
})
} else {
ElMessage.warning('请先选择或创建空间')
}
}
const changeWikiPageExpandedKeys = (pageId) => {
// 展开没有触发子节点的加载如果去加载子节点有还找不到当前的node暂不展开
// wikiPageExpandedKeys.value= [pageId];
}
const doSearchByKeywords = (queryString, callback) => {
if (!queryString || !queryString.trim()) {
callback([])
return
}
pageApi
.pageNews({spaceId: choiceSpace.value, keywords: queryString})
.then((json) => {
let spacePageNews = json.data || []
callback(spacePageNews)
})
}
const handleSearchKeywordsSelect = (item) => {
searchKeywords.value = ''
router.push({path: '/page/show', query: {pageId: item.pageId}})
}
let wikiPageTreeRef = ref();
const searchByKeywords = () => {
wikiPageTreeRef.value.filter(searchKeywords.value)
}
const searchByKeywordsNewPage = () => {
var routeUrl = router.resolve({
path: '/page/search',
query: {keywords: searchKeywords.value}
})
window.open(routeUrl.href, '_blank')
}
const handleNodeClick = (data) => {
// console.log('点击节点:', data, nowPageId.value)
nowPageId.value = data.id
router.push({path: '/page/show', query: {pageId: data.id}})
handleNodeExpand(data)
}
const handleNodeExpand = (node) => {
if (
node.children &&
node.children.length > 0 &&
node.children[0].needLoad
) {
console.log('加载节点:', node)
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) => {
doGetPageList(null)
})
}
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
})
}
const showUserMessage = (row) => {
if (row.msgStatus == 0) {
userApi.readUserMessage({ids: row.id}).then(() => {
loadUserMessageList()
})
}
if (row.msgType >= 2 && row.msgType <= 12) {
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()
}
const filterPageNode = (value, data) => {
if (!value || !data.name) return true
// issues:I2CG72 忽略大小写
let name = data.name.toLowerCase()
return name.indexOf(value.toLowerCase()) !== -1
}
let createSpaceRef = ref();
const spaceChangeEvents = (data) => {
if (data == 0) {
// 新建空间
createSpaceRef.value.show()
} else if (data == -1) {
// 管理空间
router.push({path: '/space/manage'})
} else {
choiceSpace.value = data
for (let i = 0; i < spaceList.value.length; i++) {
if (spaceList.value[i].id == data) {
nowSpaceShow.value = spaceList.value[i]
break
}
}
nowPageId.value = ''
doGetPageList(null)
router.push({path: '/home', query: {spaceId: data}})
}
}
const loadSpaceList = (spaceId) => {
pageApi.spaceList({}).then((json) => {
spaceList.value = json.data || []
let spaceOptionsNew = []
spaceList.value.forEach((item) =>
spaceOptionsNew.push({label: item.name, value: item.id})
)
spaceOptions.value = spaceOptionsNew
if (spaceList.value.length > 0) {
let nowSpaceId = spaceId
let nowSpaceShow = spaceList.value.find((item) => item.id == spaceId)
if (!nowSpaceShow) {
nowSpaceShow = spaceList.value[0]
nowSpaceId = nowSpaceShow.id
}
nowSpaceShow.value = nowSpaceShow
choiceSpace.value = nowSpaceId
nowPageId.value = ''
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: choiceSpace.value}
pageApi.pageList(param).then((json) => {
wikiPageList.value = json.data || []
// 设置默认选中效果
nextTick(() => {
nowPageId.value = route.query.pageId
if (nowPageId.value) {
wikiPageTreeRef.value.setCurrentKey(nowPageId.value)
}
})
})
}
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()
})
}
let storeUser = useStoreUserData();
const getSelfUserInfo = () => {
userApi.getSelfUserInfo().then((json) => {
userSelfInfo.value = json.data;
storeUser.userInfo = json.data;
})
}
const getSpaceInfo = (spaceId) => {
for (let i = 0; i < spaceList.value.length; i++) {
if (spaceList.value[i].id == spaceId) {
return spaceList.value[i]
}
}
return {}
}
const switchSpacePage = (spaceId) => {
spaceId = parseInt(spaceId)
if (choiceSpace.value == spaceId) {
return
}
choiceSpace.value = spaceId
doGetPageList(null)
}
const rightAsideWidthChange = (width) => {
storeDisplay.viewMenuWidth = width
}
</script>
<style>
html, body {
html,
body {
margin: 0;
padding: 0;
height: 100%;
@@ -424,36 +447,31 @@ html, body {
height: 100%;
}
#app, .el-container, .el-menu {
#app,
.el-container,
.el-menu {
height: 100%;
}
.el-header {
background-color: #1D4E89 !important;
background-color: #1d4e89 !important;
}
.header-right-user-name {
color: #fff;
padding-right: 5px;
vertical-align: middle;
}
.el-header {
color: #333;
line-height: 40px;
text-align: right;
height: 40px !important;
}
.el-icon-fold {
float: left;
font-size: 25px;
color: #aaa;
margin-top: 8px;
cursor: pointer;
}
.el-icon-fold:hover {
color: #eee;
.fold-btn {
color: #ccc !important;
font-size: 18px;
}
.head-icon {
@@ -461,6 +479,7 @@ html, body {
font-size: 16px;
cursor: pointer;
color: #fff;
vertical-align: middle;
}
.header-user-message .page-info-box {
@@ -505,7 +524,6 @@ html, body {
padding-bottom: 30px;
.el-tree-node {
.el-tree-node__content {
height: 35px;

View File

@@ -1,34 +1,29 @@
<template>
<van-collapse v-model="pageSelect">
<template v-for="page in pageList">
<div v-if="!page.children" @click="pageSelectChange(page.id)" class="van-cell van-cell--clickable">{{page.name}}</div>
<div v-if="!page.children" @click="pageSelectChange(page.id)" class="van-cell van-cell--clickable">
{{ page.name }}
</div>
<van-collapse-item :name="page.id" v-else>
<span slot="title" @click="pageSelectChange(page.id)">{{page.name}}</span>
<template v-slot:title>
<span @click="pageSelectChange(page.id)">{{ page.name }}</span>
</template>
<page-tree :page-list="page.children" @pageChange="pageSelectChange"></page-tree>
</van-collapse-item>
</template>
</van-collapse>
</template>
<script>
export default {
name: 'pageTree',
data() {
return {
pageSelect: [],
}
},
props: {pageList: Array},
mounted () {
},
methods: {
pageSelectChange(value) {
this.$emit('pageChange', value);
},
}
};
<script setup>
import {onBeforeUnmount, ref, onMounted, watch, defineProps, nextTick, defineEmits, defineExpose, computed} from 'vue';
let emit = defineEmits(['pageChange']);
const props = defineProps({
pageList: Array,
});
const pageSelectChange = (value) => {
emit('pageChange', value);
}
let pageSelect = ref([]);
</script>
<style scoped>
</style>

View File

@@ -1,77 +1,73 @@
<template>
<div ref="rightResize" class="right-resize">
<i ref="rightResizeBar">...</i>
<div ref="rightResizeRef" class="right-resize">
<i ref="rightResizeBarRef">...</i>
</div>
</template>
<script>
export default {
data() {
return {
rightAsideWidth: 300,
}
},
mounted() {
this.dragChangeRightAsideWidth();
},
methods: {
dragChangeRightAsideWidth() {
// 保留this引用
let resize = this.$refs.rightResize;
let resizeBar = this.$refs.rightResizeBar;
resize.onmousedown = e => {
let startX = e.clientX;
// 颜色改变提醒
resize.style.background = "#ccc";
resizeBar.style.background = "#aaa";
resize.left = resize.offsetLeft;
document.onmousemove = e2 => {
// 计算并应用位移量
let endX = e2.clientX;
let moveLen = startX - endX;
if ((moveLen < 0 && this.rightAsideWidth < 600) || (moveLen > 0 && this.rightAsideWidth > 300)) {
startX = endX;
this.rightAsideWidth -= moveLen;
if (this.rightAsideWidth < 300) {
this.rightAsideWidth = 300;
}
this.$emit('input', this.rightAsideWidth);
this.$emit('change', this.rightAsideWidth);
}
};
document.onmouseup = () => {
// 颜色恢复
resize.style.background = "#fafafa";
resizeBar.style.background = "#ccc";
document.onmousemove = null;
document.onmouseup = null;
};
return false;
};
<script setup>
import {onBeforeUnmount, ref, onMounted, watch, defineProps, nextTick, defineEmits, defineExpose, computed} from 'vue';
let rightAsideWidth = 300;
let emit = defineEmits(['update:value', 'change']);
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) => {
// 计算并应用位移量
let endX = e2.clientX
let moveLen = startX - endX
if ((moveLen < 0 && rightAsideWidth < 600) || (moveLen > 0 && rightAsideWidth > 300)) {
startX = endX
rightAsideWidth -= moveLen
if (rightAsideWidth < 300) {
rightAsideWidth = 300
}
emit('update:value', rightAsideWidth)
emit('change', rightAsideWidth)
}
}
};
document.onmouseup = () => {
// 颜色恢复
resize.style.background = '#fafafa'
resizeBar.style.background = '#ccc'
document.onmousemove = null
document.onmouseup = null
}
return false
}
}
</script>
<style scoped>
.right-resize {
width: 5px;
height: 100%;
cursor: w-resize;
background: #fafafa;
}
.right-resize {
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;
background: #ccc;
color: #888;
}
.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;
background: #ccc;
color: #888;
}
</style>

View File

@@ -1,28 +1,37 @@
<template>
<div style="height: 100%;">
<div style="height: 100%">
<el-container>
<el-aside width="300px" style="background-color: #fafafa;" :style="{ width: rightAsideWidth + 'px' }" v-show="leftCollapse">
<div class="logo">{{nowSpaceShow.name}}</div>
<div style="padding: 10px;box-sizing: border-box;background: #fafafa;">
<el-input v-model="searchKeywords" @keyup.enter.native="searchByKeywords" placeholder="搜索文档" style="margin: 10px 0;">
<el-button slot="append" icon="el-icon-search" v-on:click="searchByKeywords"></el-button>
<el-aside width="300px" style="background-color: #fafafa" :style="{ width: rightAsideWidth + 'px' }" v-show="leftCollapse">
<div class="logo">{{ nowSpaceShow.name }}</div>
<div style="padding: 10px; box-sizing: border-box; background: #fafafa">
<el-input v-model="searchKeywords" @keyup.enter="searchByKeywords" placeholder="搜索文档" style="margin: 10px 0">
<template v-slot:append>
<el-button :icon="ElIconSearch" v-on:click="searchByKeywords"></el-button>
</template>
</el-input>
<el-tree :props="defaultProps" :data="wikiPageList" @node-click="handleNodeClick"
ref="wikiPageTree" :filter-node-method="filterPageNode" highlight-current
:expand-on-click-node="false" :default-expanded-keys="wikiPageExpandedKeys"
node-key="id"
style="background-color: #fafafa;padding-bottom: 30px;">
<span slot-scope="{node,data}" style="font-size:14px;">
<i class="el-icon-document"></i>&nbsp;
<span>{{ node.label }}</span>
</span>
<el-tree
:props="defaultProps"
:data="wikiPageList"
@node-click="handleNodeClick"
ref="wikiPageTreeRef"
:filter-node-method="filterPageNode"
highlight-current
:expand-on-click-node="true"
:default-expanded-keys="wikiPageExpandedKeys"
node-key="id"
style="background-color: #fafafa; padding-bottom: 30px">
<template v-slot="{ node, data }">
<span style="font-size: 14px">
<el-icon><el-icon-document/></el-icon>&nbsp;
<span>{{ node.label }}</span>
</span>
</template>
</el-tree>
<!--手下留情别删我(^)给我一个露脸的机会我长的不碍眼的-->
<!--保留申明-->
<div class="build-info">本文档使用<a target="_blank" href="https://gitee.com/zyplayer/zyplayer-doc">zyplayer-doc</a>构建</div>
</div>
</el-aside>
<RightResize v-model="rightAsideWidth" v-show="leftCollapse"></RightResize>
<RightResize v-model:value="rightAsideWidth" v-show="leftCollapse"></RightResize>
<el-container>
<el-main class="doc-body-box">
<router-view></router-view>
@@ -32,96 +41,167 @@
</div>
</template>
<script>
import pageApi from '../../common/api/page'
import RightResize from './RightResize.vue'
<script setup>
import {onBeforeUnmount, ref, onMounted, watch, defineProps, nextTick, defineEmits, defineExpose, computed} from 'vue';
import {onBeforeRouteUpdate, useRouter, useRoute} from "vue-router";
import {ElMessageBox, ElMessage} from 'element-plus'
import {
Document as ElIconDocument,
Search as ElIconSearch,
} from '@element-plus/icons-vue'
import pageApi from '../../assets/api/page'
import RightResize from './RightResize.vue'
export default {
data() {
return {
leftCollapse: true,
defaultProps: {
children: 'children',
label: 'name'
},
// 空间搜索相关
spaceUuid: '',
nowPageId: '',
nowSpaceShow: {},
// 搜索的输入内容
searchKeywords: "",
// 页面展示相关
wikiPageList:[],
wikiPageExpandedKeys: [],
rightAsideWidth: 300,
}
},
components: {RightResize},
mounted: function () {
this.spaceUuid = this.$route.query.space || '';
this.getSpaceInfo();
this.doGetPageList(null);
},
methods: {
filterPageNode(value, data) {
if (!value || !data.name) return true;
// issues:I2CG72 忽略大小写
let name = data.name.toLowerCase();
return name.indexOf(value.toLowerCase()) !== -1;
},
handleNodeClick(data) {
if (this.nowPageId == data.id) {
return;
}
console.log("点击节点:", data);
this.nowPageId = data.id;
this.$router.push({path: '/page/share/view', query: {pageId: data.id, space: this.spaceUuid}});
},
searchByKeywords() {
this.$refs.wikiPageTree.filter(this.searchKeywords);
},
doGetPageList() {
pageApi.openPageList({space: this.spaceUuid}).then(json => {
this.wikiPageList = json.data || [];
this.nowPageId = '';
});
},
getSpaceInfo() {
pageApi.openSpaceInfo({space: this.spaceUuid}).then(json => {
this.nowSpaceShow = json.data;
});
},
}
};
let leftCollapse = ref(true);
let defaultProps = ref({children: 'children', label: 'name',});
// 空间搜索相关
let spaceUuid = ref('');
let nowPageId = ref('');
let nowSpaceShow = ref({});
// 搜索的输入内容
let searchKeywords = ref('');
// 页面展示相关
let wikiPageList = ref([]);
let wikiPageExpandedKeys = ref([]);
let rightAsideWidth = ref(300);
let route = useRoute();
let router = useRouter();
onMounted(() => {
spaceUuid.value = route.query.space || ''
getSpaceInfo()
doGetPageList(null)
});
const filterPageNode = (value, data) => {
if (!value || !data.name) return true
// issues:I2CG72 忽略大小写
let name = data.name.toLowerCase()
return name.indexOf(value.toLowerCase()) !== -1
}
const handleNodeClick = (data) => {
if (nowPageId.value == data.id) {
return
}
// console.log('点击节点:', data)
nowPageId.value = data.id
router.push({
path: '/page/share/view',
query: {pageId: data.id, space: spaceUuid.value}
})
}
let wikiPageTreeRef = ref();
const searchByKeywords = () => {
wikiPageTreeRef.value.filter(searchKeywords.value)
}
const doGetPageList = () => {
pageApi.openPageList({space: spaceUuid.value}).then((json) => {
wikiPageList.value = json.data || []
nowPageId.value = ''
})
}
const getSpaceInfo = () => {
pageApi.openSpaceInfo({space: spaceUuid.value}).then((json) => {
nowSpaceShow.value = json.data
})
}
</script>
<style scoped>
html,body,#app {margin: 0; padding: 0; height: 100%;}
pre{margin: 0;white-space: pre-wrap;font-size: 14px; font-family: auto;}
.el-menu {box-sizing: border-box;border-right: 0;margin-right: 3px;}
.el-header {background-color: #409EFF; color: #333; line-height: 40px; text-align: right;height: 40px !important;}
.doc-body-box{
overflow-x: hidden;overflow-y: auto;width: 100%;
padding: 10px;border-left: 1px solid #f1f1f1; box-sizing: border-box;
}
.el-tree{margin-right: 3px;}
.logo{
/*background: #409EFF; cursor: pointer;*/
border-bottom: 1px solid #f1f1f1;
overflow: hidden;white-space: nowrap;text-overflow: ellipsis; padding: 5px 10px;
width: 260px; height:40px;line-height:40px;font-size: 25px;color: #666;text-align: center;
}
.icon-collapse{float: left;font-size: 25px;color: #aaa;cursor: pointer;position: fixed;}
.icon-collapse:hover{color: #ccc;}
/*评论*/
.comment-box .head{
float: left;background-color: #ccc;border-radius: 50%;margin-right: 10px;
width: 45px; height: 45px; line-height: 45px;text-align: center;color: #fff;
}
.build-info{
position: fixed;bottom: 0;left: 0;background: #fafafa;width: 240px;text-align: center;
padding: 5px 0;color: #aaa;font-size: 12px;
}
.build-info a{color: #4183c4;cursor: pointer;text-decoration:none;}
</style>
html,
body,
#app {
margin: 0;
padding: 0;
height: 100%;
}
pre {
margin: 0;
white-space: pre-wrap;
font-size: 14px;
font-family: auto;
}
.el-menu {
box-sizing: border-box;
border-right: 0;
margin-right: 3px;
}
.el-header {
background-color: #409eff;
color: #333;
line-height: 40px;
text-align: right;
height: 40px !important;
}
.doc-body-box {
overflow-x: hidden;
overflow-y: auto;
width: 100%;
padding: 10px;
border-left: 1px solid #f1f1f1;
box-sizing: border-box;
}
.el-tree {
margin-right: 3px;
}
.logo {
border-bottom: 1px solid #f1f1f1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
padding: 5px 10px;
width: 260px;
height: 40px;
line-height: 40px;
font-size: 25px;
color: #666;
text-align: center;
}
.icon-collapse {
float: left;
font-size: 25px;
color: #aaa;
cursor: pointer;
position: fixed;
}
.icon-collapse:hover {
color: #ccc;
}
.comment-box .head {
float: left;
background-color: #ccc;
border-radius: 50%;
margin-right: 10px;
width: 45px;
height: 45px;
line-height: 45px;
text-align: center;
color: #fff;
}
.build-info {
position: fixed;
bottom: 0;
left: 0;
background: #fafafa;
width: 240px;
text-align: center;
padding: 5px 0;
color: #aaa;
font-size: 12px;
}
.build-info a {
color: #4183c4;
cursor: pointer;
text-decoration: none;
}
</style>

View File

@@ -1,6 +1,6 @@
<template>
<div class="share-mobile-layout">
<van-popup v-model="popupShow" closeable position="left" class="popup-module" :style="{ height: '100%', width: '80%' }">
<van-popup v-model:show="popupShow" closeable position="left" class="popup-module" :style="{ height: '100%', width: '80%' }">
<div class="header">
<van-nav-bar :title="nowSpaceShow.name"></van-nav-bar>
</div>
@@ -13,107 +13,184 @@
</div>
</template>
<script>
import Vue from 'vue';
import pageApi from '../../common/api/page'
import PageTree from './PageTree'
import Vant from 'vant';
import 'vant/lib/icon/index.css';
import 'vant/lib/popup/index.css';
import 'vant/lib/cell/index.css';
import 'vant/lib/nav-bar/index.css';
import 'vant/lib/collapse-item/index.css';
Vue.use(Vant);
<script setup>
import {onBeforeUnmount, ref, 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 PageTree from './PageTree'
import 'vant/es/icon/style/index';
import 'vant/es/popup/style/index';
import 'vant/es/cell/style/index';
import 'vant/es/nav-bar/style/index';
import 'vant/es/image-preview/style/index';
import 'vant/es/collapse-item/style/index';
import 'vant/es/dialog/style/index';
import 'vant/es/checkbox/style/index';
export default {
data() {
return {
defaultProps: {
children: 'children',
label: 'name'
},
// 空间搜索相关
spaceUuid: '',
nowPageId: '',
nowSpaceShow: {},
// 搜索的输入内容
searchKeywords: "",
// 页面展示相关
wikiPageList:[],
wikiPageExpandedKeys: [],
popupShow: false,
pageSelect: [],
}
},
components: {
PageTree: PageTree
},
mounted: function () {
this.spaceUuid = this.$route.query.space || '';
this.getSpaceInfo();
this.doGetPageList(null);
},
methods: {
filterPageNode(value, data) {
if (!value) return true;
return data.name.indexOf(value) !== -1;
},
pageSelectChange(value) {
console.log("页面修改:" + value);
this.popupShow = false;
this.$router.replace({path: '/page/share/mobile/view', query: {pageId: value, space: this.spaceUuid}});
},
popupShowChange(value) {
this.popupShow = value;
console.log(this.pageSelect)
},
doGetPageList() {
pageApi.openPageList({space: this.spaceUuid}).then(json => {
this.wikiPageList = json.data || [];
this.nowPageId = '';
});
},
getSpaceInfo() {
pageApi.openSpaceInfo({space: this.spaceUuid}).then(json => {
this.nowSpaceShow = json.data;
});
},
}
};
let defaultProps = ref({children: 'children', label: 'name',});
// 空间搜索相关
let spaceUuid = ref('');
let nowPageId = ref('');
let nowSpaceShow = ref({});
// 页面展示相关
let wikiPageList = ref([]);
let popupShow = ref(false);
let pageSelect = ref([]);
let route = useRoute();
let router = useRouter();
onMounted(() => {
spaceUuid.value = route.query.space || ''
getSpaceInfo()
doGetPageList(null)
});
const filterPageNode = (value, data) => {
if (!value) return true
return data.name.indexOf(value) !== -1
}
const pageSelectChange = (value) => {
// console.log('页面修改:' + value)
popupShow.value = false
router.replace({
path: '/page/share/mobile/view',
query: {pageId: value, space: spaceUuid.value}
})
}
const popupShowChange = (value) => {
popupShow.value = value
// console.log(pageSelect.value)
}
const doGetPageList = () => {
pageApi.openPageList({space: spaceUuid.value}).then((json) => {
wikiPageList.value = json.data || []
nowPageId.value = ''
})
}
const getSpaceInfo = () => {
pageApi.openSpaceInfo({space: spaceUuid.value}).then((json) => {
nowSpaceShow.value = json.data
})
}
</script>
<style scoped>
html,body,#app {margin: 0; padding: 0; height: 100%;}
html,
body,
#app {
margin: 0;
padding: 0;
height: 100%;
}
.share-mobile-layout{height: 100%;}
.popup-module .header{width:100%;height:46px;}
.popup-module .main{position:absolute;top:46px;bottom: 0;right:0;left:0;overflow:auto;}
.popup-module .footer{width:100%;height:26px;position:fixed;bottom:0}
.share-mobile-layout {
height: 100%;
}
pre{margin: 0;white-space: pre-wrap;font-size: 14px; font-family: auto;}
.el-menu {box-sizing: border-box;border-right: 0;margin-right: 3px;}
.el-header {background-color: #409EFF; color: #333; line-height: 40px; text-align: right;height: 40px !important;}
.doc-body-box{
overflow-x: hidden;overflow-y: auto;width: 100%;
padding: 10px;border-left: 1px solid #f1f1f1; box-sizing: border-box;
}
.el-tree{margin-right: 3px;}
.logo{
/*background: #409EFF; cursor: pointer;*/
border-bottom: 1px solid #f1f1f1;
overflow: hidden;white-space: nowrap;text-overflow: ellipsis; padding: 5px 10px;
width: 260px; height:40px;line-height:40px;font-size: 25px;color: #666;text-align: center;
}
.icon-collapse{float: left;font-size: 25px;color: #aaa;cursor: pointer;position: fixed;}
.icon-collapse:hover{color: #ccc;}
/*评论*/
.comment-box .head{
float: left;background-color: #ccc;border-radius: 50%;margin-right: 10px;
width: 45px; height: 45px; line-height: 45px;text-align: center;color: #fff;
}
.build-info{
text-align: center; padding: 5px 0;color: #aaa;font-size: 12px; margin: 10px 0;
}
.build-info a{color: #4183c4;cursor: pointer;text-decoration:none;}
.popup-module .header {
width: 100%;
height: 46px;
}
.popup-module .main {
position: absolute;
top: 46px;
bottom: 0;
right: 0;
left: 0;
overflow: auto;
}
.popup-module .footer {
width: 100%;
height: 26px;
position: fixed;
bottom: 0;
}
pre {
margin: 0;
white-space: pre-wrap;
font-size: 14px;
font-family: auto;
}
.el-menu {
box-sizing: border-box;
border-right: 0;
margin-right: 3px;
}
.el-header {
background-color: #409eff;
color: #333;
line-height: 40px;
text-align: right;
height: 40px !important;
}
.doc-body-box {
overflow-x: hidden;
overflow-y: auto;
width: 100%;
padding: 10px;
border-left: 1px solid #f1f1f1;
box-sizing: border-box;
}
.el-tree {
margin-right: 3px;
}
.logo {
border-bottom: 1px solid #f1f1f1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
padding: 5px 10px;
width: 260px;
height: 40px;
line-height: 40px;
font-size: 25px;
color: #666;
text-align: center;
}
.icon-collapse {
float: left;
font-size: 25px;
color: #aaa;
cursor: pointer;
position: fixed;
}
.icon-collapse:hover {
color: #ccc;
}
.comment-box .head {
float: left;
background-color: #ccc;
border-radius: 50%;
margin-right: 10px;
width: 45px;
height: 45px;
line-height: 45px;
text-align: center;
color: #fff;
}
.build-info {
text-align: center;
padding: 5px 0;
color: #aaa;
font-size: 12px;
margin: 10px 0;
}
.build-info a {
color: #4183c4;
cursor: pointer;
text-decoration: none;
}
</style>

View File

@@ -1,8 +1,8 @@
<template>
<div class="create-space-vue">
<!--新建空间弹窗-->
<el-dialog title="创建空间" :visible.sync="newSpaceDialogVisible" width="600px" :close-on-click-modal="false">
<el-form label-width="100px" :model="newSpaceForm" :rules="newSpaceFormRules" ref="newSpaceForm">
<el-dialog title="创建空间" v-model="newSpaceDialogVisible" width="600px" :close-on-click-modal="false">
<el-form label-width="100px" :model="newSpaceForm" :rules="newSpaceFormRules" ref="newSpaceFormRef">
<el-form-item label="空间名:" prop="name">
<el-input v-model="newSpaceForm.name"></el-input>
</el-form-item>
@@ -13,24 +13,24 @@
<el-switch v-model="newSpaceForm.openDoc" inactive-text="需要登录" :inactive-value="0" active-text="开放访问" :active-value="1"></el-switch>
</el-form-item>
<el-form-item label="空间类型:">
<el-select v-model="newSpaceForm.type" filterable placeholder="选择类型" style="width: 100%;">
<el-select v-model="newSpaceForm.type" filterable placeholder="选择类型" style="width: 100%">
<el-option :key="1" label="公共空间" :value="1">
<span style="float: left">公共空间</span>
<span style="float: right; color: #8492a6; font-size: 13px;">属于公共登录用户可访问编辑</span>
<span style="float: right; color: #8492a6; font-size: 13px">属于公共登录用户可访问编辑</span>
</el-option>
<el-option :key="2" label="个人空间" :value="2">
<span style="float: left">个人空间</span>
<span style="float: right; color: #8492a6; font-size: 13px;">属于个人所有登录用户可访问</span>
<span style="float: right; color: #8492a6; font-size: 13px">属于个人所有登录用户可访问</span>
</el-option>
<el-option :key="3" label="隐私空间" :value="3">
<span style="float: left">隐私空间</span>
<span style="float: right; color: #8492a6; font-size: 13px;">属于个人仅创建者可访问</span>
<span style="float: right; color: #8492a6; font-size: 13px">属于个人仅创建者可访问</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" v-if="newSpaceForm.id > 0" @click="onNewSpaceSubmit('newSpaceForm')">保存修改</el-button>
<el-button type="primary" v-else @click="onNewSpaceSubmit('newSpaceForm')">立即创建</el-button>
<el-button type="primary" v-if="newSpaceForm.id > 0" @click="onNewSpaceSubmit">保存修改</el-button>
<el-button type="primary" v-else @click="onNewSpaceSubmit">立即创建</el-button>
<el-button @click="onNewSpaceCancel">取消</el-button>
</el-form-item>
</el-form>
@@ -38,81 +38,130 @@
</div>
</template>
<script>
import pageApi from '../../common/api/page'
<script setup>
import {onBeforeUnmount, ref, 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'
export default {
data() {
return {
newSpaceDialogVisible: false,
manageSpaceDialogVisible: false,
newSpaceForm: {id: '', name: '', spaceExplain: '', treeLazyLoad: 0, openDoc: 0, uuid: '', type: 1},
newSpaceFormRules: {
name: [
{required: true, message: '请输入空间名', trigger: 'blur'},
{min: 2, max: 25, message: '长度在 2 到 25 个字符', trigger: 'blur'}
],
},
editSpaceId: ''
};
let editSpaceId = ref('');
let newSpaceFormRules = ref({
name: [
{required: true, message: '请输入空间名', trigger: 'blur'},
{
min: 2,
max: 25,
message: '长度在 2 到 25 个字符',
trigger: 'blur',
},
mounted() {
},
methods: {
show(spaceId) {
this.newSpaceForm = {id: '', name: '', spaceExplain: '', treeLazyLoad: 0, openDoc: 0, uuid: '', type: 1};
this.editSpaceId = spaceId || '';
if (!!this.editSpaceId) {
pageApi.spaceList({id: this.editSpaceId}).then(json => {
let spaceList = json.data || [];
if (spaceList.length > 0) {
this.newSpaceForm = spaceList[0];
}
});
}
this.newSpaceDialogVisible = true;
},
onNewSpaceSubmit(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
let param = {
id: this.newSpaceForm.id,
name: this.newSpaceForm.name,
type: this.newSpaceForm.type,
openDoc: this.newSpaceForm.openDoc,
spaceExplain: this.newSpaceForm.spaceExplain,
treeLazyLoad: this.newSpaceForm.treeLazyLoad,
};
pageApi.updateSpace(param).then(json => {
this.$message.success("创建成功");
this.newSpaceDialogVisible = false;
this.$emit("success", json.data.id);
});
}
});
},
onNewSpaceCancel() {
this.newSpaceDialogVisible = false;
},
],
});
let newSpaceForm = ref({
id: '',
name: '',
spaceExplain: '',
treeLazyLoad: 0,
openDoc: 0,
uuid: '',
type: 1,
})
let newSpaceDialogVisible = ref(false);
let manageSpaceDialogVisible = ref(false);
let emit = defineEmits(['success']);
const show = (spaceId) => {
newSpaceForm.value = {
id: '',
name: '',
spaceExplain: '',
treeLazyLoad: 0,
openDoc: 0,
uuid: '',
type: 1,
}
editSpaceId.value = spaceId || ''
if (!!editSpaceId.value) {
pageApi.spaceList({id: editSpaceId.value}).then((json) => {
let spaceList = json.data || []
if (spaceList.length > 0) {
newSpaceForm.value = spaceList[0]
}
})
}
newSpaceDialogVisible.value = true
}
let newSpaceFormRef = ref();
const onNewSpaceSubmit = () => {
newSpaceFormRef.value.validate((valid) => {
if (valid) {
let param = {
id: newSpaceForm.value.id,
name: newSpaceForm.value.name,
type: newSpaceForm.value.type,
openDoc: newSpaceForm.value.openDoc,
spaceExplain: newSpaceForm.value.spaceExplain,
treeLazyLoad: newSpaceForm.value.treeLazyLoad,
}
pageApi.updateSpace(param).then((json) => {
ElMessage.success('创建成功')
newSpaceDialogVisible.value = false
emit('success', json.data.id)
})
}
}
})
}
const onNewSpaceCancel = () => {
newSpaceDialogVisible.value = false
}
defineExpose({show});
</script>
<style>
.create-space-vue .empty-news{text-align: center;padding: 100px;}
.create-space-vue .empty-news {
text-align: center;
padding: 100px;
}
.create-space-vue .text-link {
color: #444;
/*cursor: pointer;*/
/*font-weight: bold;*/
}
.create-space-vue .line-box{color: #666;border-bottom: 1px solid #eee;padding: 20px 0;}
.create-space-vue .line-title{font-size: 14px;}
.create-space-vue .page-preview-box{}
.create-space-vue .page-preview-title{font-size: 18px;margin: 10px 0 5px 0;color: #3a8ee6;cursor: pointer;}
.create-space-vue .page-preview-content{font-size: 16px;margin-bottom: 5px;}
.create-space-vue .zan-img{vertical-align: middle;margin-top: -3px;}
.create-space-vue .view-img{font-size: 16px;color: #666;}
.create-space-vue .text-link {
color: #444;
}
.create-space-vue .page-info-box{text-align: right;margin: 20px 0 50px 0;}
.create-space-vue .line-box {
color: #666;
border-bottom: 1px solid #eee;
padding: 20px 0;
}
.create-space-vue .line-title {
font-size: 14px;
}
.create-space-vue .page-preview-box {
}
.create-space-vue .page-preview-title {
font-size: 18px;
margin: 10px 0 5px 0;
color: #3a8ee6;
cursor: pointer;
}
.create-space-vue .page-preview-content {
font-size: 16px;
margin-bottom: 5px;
}
.create-space-vue .zan-img {
vertical-align: middle;
margin-top: -3px;
}
.create-space-vue .view-img {
font-size: 16px;
color: #666;
}
.create-space-vue .page-info-box {
text-align: right;
margin: 20px 0 50px 0;
}
</style>

View File

@@ -1,56 +1,35 @@
import Vue from 'vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import App from './App.vue'
import VueRouter from 'vue-router'
import routes from './routes'
import store from './store/index'
import axios from 'axios'
import VueAxios from 'vue-axios'
// 注册一个全局自定义指令
import hljs from 'highlight.js'
import 'highlight.js/styles/googlecode.css'
Vue.use(ElementUI);
Vue.use(VueRouter);
Vue.use(VueAxios, axios);
import {createApp} from 'vue'
import App from './App.vue'
import {createRouter, createWebHashHistory} from 'vue-router'
// 公用方法
Vue.prototype.$store = store;
// 路由重复点击报错处理
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
return originalPush.call(this, location).catch(err => err)
}
import ElementUI from 'element-plus'
import 'element-plus/dist/index.css'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import routes from './routes'
import Vant from 'vant'
import 'highlight.js/styles/monokai-sublime.css'
import {createPinia} from 'pinia'
const router = new VueRouter({routes});
// 路由跳转时判断处理
router.beforeEach((to, from, next) => {
if (to.name) {
document.title = to.name;
}
next();
const router = createRouter({
history: createWebHashHistory(),
routes,
});
let vue = new Vue({
el: '#app',
router,
render(h) {
return h(App);
}
const app = createApp(App);
app.config.productionTip = false;
app.use(ElementUI, {
locale: zhCn,
});
app.use(Vant);
app.use(router);
app.use(createPinia());
app.mount('#app');
Vue.directive('highlight', function (el) {
app.directive('highlight', function (el) {
let blocks = el.querySelectorAll('pre code');
blocks.forEach((block) => {
hljs.highlightBlock(block);
})
});
});
export default vue;

View File

@@ -22,33 +22,45 @@ let routes = [
{path: '/common/noAuth', name: 'WIKI-没有权限', component: NoAuth},
{
path: '/',
name: '页面管理',
name: '文档管理',
component: GlobalLayout,
children: [
{path: '/home', name: 'WIKI文档管理', component: Home},
{path: '/user/myInfo', name: 'WIKI-我的信息', component: MyInfo},
{path: '/page/show', name: 'WIKI-内容展示', component: Show},
{path: '/page/show', name: 'WIKI-页面查看', component: Show},
{path: '/page/edit', name: 'WIKI-编辑内容', component: Edit},
{path: '/space/manage', name: 'WIKI-空间管理', component: spaceManage},
]
],
},
{
path: '/',
name: '页面管理',
name: 'PC端开放文档',
component: ShareLayout,
children: [
{path: '/page/share/home', name: 'WIKI-开放文档', component: sharePcHome},
{path: '/page/share/view', name: 'WIKI-内容展示', component: sharePcView},
]
{
path: '/page/share/home',
name: 'WIKI-开放文档',
component: sharePcHome,
},
{
path: '/page/share/view',
name: 'WIKI-内容展示',
component: sharePcView,
},
],
},
{
path: '/',
name: '页面管理',
name: 'APP端开放文档',
component: ShareMobileLayout,
children: [
{path: '/page/share/mobile/view', name: 'WIKI-开放文档', component: shareMobileView},
]
{
path: '/page/share/mobile/view',
name: 'WIKI-开放文档',
component: shareMobileView,
},
],
},
];
]
export default routes;
export default routes

View File

@@ -1,11 +0,0 @@
import Vue from 'vue'
import Vuex from 'vuex'
import global from './modules/global'
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
global,
}
});

View File

@@ -1,22 +0,0 @@
export default {
namespaced: true,
state: {
pageTabNameMap: {},
rightAsideWidth: 0,
},
getters: {
getPageTabNameMap(state) {
return state.pageTabNameMap;
},
},
mutations: {
addTableName(state, item) {
let sameObj = Object.assign({}, state.pageTabNameMap);
sameObj[item.key] = item.val;
state.pageTabNameMap = sameObj;
},
setRightAsideWidth(state, rightAsideWidth) {
state.rightAsideWidth = rightAsideWidth;
},
}
}

View File

@@ -0,0 +1,10 @@
import {defineStore} from 'pinia'
export const useStorePageData = defineStore('pageData', {
state: () => {
return {
pageInfo: {},
}
},
});

View File

@@ -0,0 +1,10 @@
import {defineStore} from 'pinia'
export const useStoreUserData = defineStore('userData', {
state: () => {
return {
// 用户信息
userInfo: {},
}
},
})

View File

@@ -0,0 +1,10 @@
import {defineStore} from 'pinia'
export const useStoreDisplay = defineStore('wikiDisplay', {
state: () => {
return {
// 左边目录栏宽度
viewMenuWidth: 300,
}
},
})

View File

@@ -1,25 +1,26 @@
<template>
<!--关于弹窗-->
<el-dialog title="关于" :visible.sync="aboutDialogVisible" width="600px" custom-class="about-zyplayer-doc">
<el-dialog title="关于" v-model="aboutDialogVisible" width="600px" class="about-zyplayer-doc">
<div style="">
<div style="font-weight: bold;font-size: 25px;">zyplayer-doc</div>
<div style="line-height: 30px;padding: 10px 0;">
<div>版本 {{upgradeInfo.nowVersion || '1.0.0'}}</div>
<div>版权所有 © 2018-2021 <a target="_blank" href="http://doc.zyplayer.com">doc.zyplayer.com</a></div>
<div style="font-weight: bold; font-size: 25px">zyplayer-doc</div>
<div style="line-height: 30px; padding: 10px 0">
<div>版本 {{ upgradeInfo.nowVersion || '1.0.0' }}</div>
<div>
版权所有 © 2018-2021
<a target="_blank" href="http://doc.zyplayer.com">doc.zyplayer.com</a>
</div>
</div>
<el-tabs type="border-card">
<el-tab-pane label="支持">
<div style="line-height: 30px;">
<div style="line-height: 30px">
<div>文档<a target="_blank" href="http://doc.zyplayer.com/doc-wiki#/docs/w4eSzPWvQRSBvaCHZS8t6d">http://doc.zyplayer.com</a></div>
<div>主页<a target="_blank" href="https://gitee.com/zyplayer/zyplayer-doc">https://gitee.com/zyplayer/zyplayer-doc</a></div>
<div>反馈<a target="_blank" href="https://gitee.com/zyplayer/zyplayer-doc/issues">https://gitee.com/zyplayer/zyplayer-doc/issues</a></div>
<div>特性关注&技术交流QQ群466363173</div>
<el-divider content-position="left">UI/设计/开发/测试</el-divider>
<div><a target="_blank" href="http://zyplayer.com">暮光城中城</a></div>
<div>新功能关注&技术交流QQ群466363173</div>
</div>
</el-tab-pane>
<el-tab-pane label="开源软件">
<div style="line-height: 30px;">
<div style="line-height: 30px">
<div>此项目基于以下开源软件构建</div>
<el-divider content-position="left">后端</el-divider>
<div>
@@ -34,22 +35,25 @@
<a target="_blank" href="http://www.eclipse.org/jgit">JGit</a>...
</div>
<el-divider content-position="left">前端</el-divider>
Vueelement-uiwangeditormavon-editorqrcodejs2vantvue-routeraxiosvue-hljsbraceechartssql-formattervue-clipboard2...
<div>
Vueelement-uiwangeditormavon-editorqrcodejs2vantvue-routeraxiosvue-hljsbraceechartssql-formattervue-clipboard2...
</div>
</div>
</el-tab-pane>
<el-tab-pane label="软件更新" v-if="upgradeInfo.lastVersion">
<span slot="label">
软件更新
<sup class="el-badge__content el-badge__content--undefined is-fixed is-dot" style="top: 10px;right: 20px;"></sup>
</span>
<div style="line-height: 30px;">
<div>当前版本{{upgradeInfo.nowVersion}}</div>
<div>最新版本{{upgradeInfo.lastVersion}}</div>
<div>升级地址<a target="_blank" :href="upgradeInfo.upgradeUrl">{{upgradeInfo.upgradeUrl}}</a></div>
<template v-slot:label>
<span>
软件更新
<sup class="el-badge__content el-badge__content--undefined is-fixed is-dot" style="top: 10px; right: 20px"></sup></span>
</template>
<div style="line-height: 30px">
<div>当前版本{{ upgradeInfo.nowVersion }}</div>
<div>最新版本{{ upgradeInfo.lastVersion }}</div>
<div>
升级地址<a target="_blank" :href="upgradeInfo.upgradeUrl">{{upgradeInfo.upgradeUrl}}</a>
</div>
<div>升级内容</div>
<pre style="margin: 0; max-height: 250px; overflow: auto;">{{upgradeInfo.upgradeContent}}</pre>
<pre style="margin: 0; max-height: 250px; overflow: auto">{{upgradeInfo.upgradeContent}}</pre>
</div>
</el-tab-pane>
</el-tabs>
@@ -57,44 +61,47 @@
</el-dialog>
</template>
<script>
import systemApi from "../../common/api/system";
<script setup>
import {onBeforeUnmount, ref, onMounted, watch, defineProps, nextTick, defineEmits, defineExpose, computed} from 'vue';
import {onBeforeRouteUpdate, useRouter, useRoute} from "vue-router";
import {ElMessageBox, ElMessage} from 'element-plus'
import systemApi from '../../assets/api/system'
export default {
data() {
return {
aboutDialogVisible: false,
upgradeInfo: {},
};
},
mounted() {
this.checkSystemUpgrade();
},
methods: {
show() {
this.aboutDialogVisible = true;
},
checkSystemUpgrade() {
systemApi.systemUpgradeInfo({}).then(json => {
if (!!json.data) {
this.upgradeInfo = json.data;
if (!!this.upgradeInfo.upgradeContent) {
this.upgradeInfo.upgradeContent = this.upgradeInfo.upgradeContent.replaceAll('', '\n');
console.log("zyplayer-doc发现新版本"
+ "\n升级地址" + json.data.upgradeUrl
+ "\n当前版本" + json.data.nowVersion
+ "\n最新版本" + json.data.lastVersion
+ "\n升级内容" + json.data.upgradeContent
);
}
}
});
},
onMounted(() => {
checkSystemUpgrade()
});
let aboutDialogVisible = ref(false);
let upgradeInfo = ref({});
const show = () => {
aboutDialogVisible.value = true
}
const checkSystemUpgrade = () => {
systemApi.systemUpgradeInfo({}).then((json) => {
if (!!json.data) {
upgradeInfo.value = json.data
if (!!upgradeInfo.value.upgradeContent) {
upgradeInfo.value.upgradeContent = upgradeInfo.value.upgradeContent.replaceAll('', '\n')
console.log('zyplayer-doc发现新版本'
+ '\n升级地址' + json.data.upgradeUrl
+ '\n当前版本' + json.data.nowVersion
+ '\n最新版本' + json.data.lastVersion
+ '\n升级内容' + json.data.upgradeContent
)
}
}
}
})
}
defineExpose({show});
</script>
<style>
.about-zyplayer-doc{text-align: left; line-height: normal;}
.about-zyplayer-doc .el-dialog__body{padding: 20px;}
</style>
<style>
.about-zyplayer-doc {
text-align: left;
line-height: normal;
}
.about-zyplayer-doc .el-dialog__body {
padding: 20px;
}
</style>

View File

@@ -1,18 +1,9 @@
<template>
<div>没有权限访问该模块</div>
<div>没有权限访问该模块</div>
</template>
<script>
export default {
data() {
return {};
},
mounted: function () {
},
methods: {}
}
<script setup>
import {onBeforeUnmount, ref, onMounted, watch, defineProps, nextTick, defineEmits, defineExpose, computed} from 'vue';
import {onBeforeRouteUpdate, useRouter, useRoute} from "vue-router";
import {ElMessageBox, ElMessage} from 'element-plus'
</script>
<style>
</style>

View File

@@ -1,108 +1,141 @@
<template>
<div style="padding: 10px;" class="home-vue">
<div style="max-width: 800px;margin: 0 auto;">
<el-tabs value="first">
<el-tab-pane :label="newsTypesMap[searchParam.newsType]" name="first">
<div v-if="spacePageNews.length <= 0" class="empty-news">暂无数据</div>
<div v-else class="line-box" v-for="item in spacePageNews">
<div class="line-title">
<span class="text-link">{{item.createUserName}}</span> 发布于 <span class="text-link">{{item.spaceName}}</span>
</div>
<div class="page-preview-box">
<div class="page-preview-title" v-on:click="showPageDetail(item)" v-html="item.pageTitle"></div>
<div class="page-preview-content" v-html="item.previewContent"></div>
<div>
<span><img src="../../assets/img/zan.png" class="zan-img"> {{item.zanNum}} </span>
<span><i class="el-icon-view view-img"></i> {{item.viewNum}} </span>
<span>{{item.updateTime||item.createTime}}</span>
</div>
</div>
</div>
</el-tab-pane>
</el-tabs>
</div>
</div>
<div style="padding: 10px" class="home-vue">
<div style="max-width: 800px; margin: 0 auto">
<el-tabs model-value="first">
<el-tab-pane :label="newsTypesMap[searchParam.newsType]" name="first">
<div v-if="spacePageNews.length <= 0" class="empty-news">
暂无数据
</div>
<div v-else class="line-box" v-for="item in spacePageNews">
<div class="line-title">
<span class="text-link">{{ item.createUserName }}</span> 发布于
<span class="text-link">{{ item.spaceName }}</span>
</div>
<div class="page-preview-box">
<div class="page-preview-title" v-on:click="showPageDetail(item)" v-html="item.pageTitle"></div>
<div class="page-preview-content" v-html="item.previewContent"></div>
<div>
<span><img src="../../assets/img/zan.png" class="zan-img"/>{{ item.zanNum }} </span>
<span><el-icon class="view-img"><el-icon-view/></el-icon>{{ item.viewNum }} </span>
<span>{{ item.updateTime || item.createTime }}</span>
</div>
</div>
</div>
</el-tab-pane>
</el-tabs>
</div>
</div>
</template>
<script>
import pageApi from '../../common/api/page'
<script setup>
import {onBeforeUnmount, ref, onMounted, watch, defineProps, nextTick, defineEmits, defineExpose, computed} from 'vue';
import {onBeforeRouteUpdate, useRouter, useRoute} from "vue-router";
import {ElMessageBox, ElMessage} from 'element-plus'
import {View as ElIconView} from '@element-plus/icons-vue'
import pageApi from '../../assets/api/page'
export default {
data() {
return {
totalCount: 0,
searchParam: {
spaceId: '',
newsType: 1,
pageNum: 1,
pageSize: 20,
},
spacePageNews: [],
// 列表类型
newsTypesArr: [
{key: 1, val: '最近更新'}, {key: 2, val: '最新创建'}, {key: 3, val: '查看最多'},
{key: 4, val: '点赞最多'}, {key: 5, val: '查看+点赞最多'}
],
newsTypesMap: {},
};
},
beforeRouteUpdate(to, from, next) {
this.initQueryParam(to);
next();
},
mounted: function () {
this.initQueryParam(this.$route);
},
methods: {
getSpacePageNews() {
pageApi.pageNews(this.searchParam).then(json => {
this.spacePageNews = json.data || [];
});
},
handleSizeChange(val) {
this.searchParam.pageSize = val;
this.getSpacePageNews();
},
showPageDetail(row) {
this.nowClickPath = {pageId: row.pageId};
this.$router.push({path: '/page/show', query: this.nowClickPath});
},
handleCurrentChange(val) {
this.searchParam.pageNum = val;
this.getSpacePageNews();
},
initQueryParam(to) {
this.searchParam = {
spaceId: to.query.spaceId,
newsType: 1,
pageNum: 1,
pageSize: 20,
};
if (!!this.searchParam.spaceId) {
this.getSpacePageNews();
}
this.newsTypesMap = {};
this.newsTypesArr.forEach(item => this.newsTypesMap[item.key] = item.val);
},
}
}
let totalCount = ref(0);
let searchParam = ref({spaceId: '', newsType: 1, pageNum: 1, pageSize: 20,});
let spacePageNews = ref([]);
// 列表类型
let newsTypesArr = ref([
{key: 1, val: '最近更新'},
{key: 2, val: '最新创建'},
{key: 3, val: '查看最多'},
{key: 4, val: '点赞最多'},
{key: 5, val: '查看+点赞最多'},
]);
let newsTypesMap = ref({});
onBeforeRouteUpdate((to) => {
initQueryParam(to);
});
let route = useRoute();
let router = useRouter();
onMounted(() => {
initQueryParam(route);
});
const getSpacePageNews = () => {
pageApi.pageNews(searchParam.value).then((json) => {
spacePageNews.value = json.data || []
})
}
const handleSizeChange = (val) => {
searchParam.value.pageSize = val
getSpacePageNews()
}
const showPageDetail = (row) => {
let nowClickPath = {pageId: row.pageId}
router.push({path: '/page/show', query: nowClickPath})
}
const handleCurrentChange = (val) => {
searchParam.value.pageNum = val
getSpacePageNews()
}
const initQueryParam = (to) => {
searchParam.value = {
spaceId: to.query.spaceId,
newsType: 1,
pageNum: 1,
pageSize: 20,
}
if (!!searchParam.value.spaceId) {
getSpacePageNews()
}
newsTypesMap.value = {}
newsTypesArr.value.forEach(
(item) => (newsTypesMap.value[item.key] = item.val)
)
}
</script>
<style>
.home-vue .empty-news{text-align: center;padding: 100px;}
.home-vue .empty-news {
text-align: center;
padding: 100px;
}
.home-vue .text-link {
color: #444;
/*cursor: pointer;*/
/*font-weight: bold;*/
}
.home-vue .line-box{color: #666;border-bottom: 1px solid #eee;padding: 20px 0;}
.home-vue .line-title{font-size: 14px;}
.home-vue .page-preview-box{}
.home-vue .page-preview-title{cursor: pointer;font-size: 20px;margin: 10px 0 5px 0;color: #3a8ee6;}
.home-vue .page-preview-content{font-size: 16px;margin-bottom: 5px;}
.home-vue .zan-img{vertical-align: middle;margin-top: -3px;}
.home-vue .view-img{font-size: 16px;color: #666;}
.home-vue .text-link {
color: #444;
}
.home-vue .page-info-box{text-align: right;margin: 20px 0 50px 0;}
.home-vue .line-box {
color: #666;
border-bottom: 1px solid #eee;
padding: 20px 0;
}
.home-vue .line-title {
font-size: 14px;
}
.home-vue .page-preview-box {
}
.home-vue .page-preview-title {
cursor: pointer;
font-size: 20px;
margin: 10px 0 5px 0;
color: #3a8ee6;
}
.home-vue .page-preview-content {
font-size: 16px;
margin-bottom: 5px;
}
.home-vue .zan-img {
vertical-align: middle;
margin-top: -3px;
}
.home-vue .view-img {
font-size: 16px;
color: #666;
}
.home-vue .page-info-box {
text-align: right;
margin: 20px 0 50px 0;
}
</style>

View File

@@ -1,307 +1,350 @@
<template>
<div style="height: 100%;" class="page-edit-vue">
<div style="box-sizing: border-box;background: #f5f5f5;overflow: hidden;">
<div style="padding: 8px;font-size: 14px;background: #fff;">
<div style="height: 100%" class="page-edit-vue">
<div style="box-sizing: border-box; background: #f5f5f5; overflow: hidden">
<div style="padding: 8px; font-size: 14px; background: #fff">
<el-row>
<el-col :span="16">
<template v-if="pageId">
<span>编辑方式</span>
<el-select v-model="wikiPageEdit.editorType" v-on:change="editorTypeChange" :disabled="!!pageId" size="mini">
<el-select v-model="wikiPageEdit.editorType" v-on:change="editorTypeChange" :disabled="!!pageId" size="small">
<el-option label="Markdown" :value="2"></el-option>
<el-option label="HTML" :value="1"></el-option>
</el-select>
</template>
<template v-else>
<span style="margin-right: 20px;">父级{{parentWikiPage.name || '/'}}</span>
<span style="margin-right: 20px">父级{{ parentWikiPage.name || '/' }}</span>
<el-tooltip class="item" content="在根目录创建文档" v-if="parentId">
<el-button type="text" @click="changeToRootPath" size="mini" style="padding: 0 10px;">根目录</el-button>
<el-link @click="changeToRootPath" size="small" style="padding: 0 10px">根目录</el-link>
</el-tooltip>
<span style="margin-left: 50px;">编辑方式</span>
<el-select v-model="wikiPageEdit.editorType" v-on:change="editorTypeChange" :disabled="!!pageId" size="mini">
<span style="margin-left: 50px">编辑方式</span>
<el-select v-model="wikiPageEdit.editorType" v-on:change="editorTypeChange" :disabled="!!pageId" size="small">
<el-option label="Markdown" :value="2"></el-option>
<el-option label="HTML" :value="1"></el-option>
</el-select>
</template>
</el-col>
<el-col :span="8" style="text-align: right;">
<el-button type="primary" v-on:click="createWikiSave(1)" size="mini" icon="el-icon-document-checked">保存并查看</el-button>
<el-button type="success" v-on:click="createWikiSave(0)" size="mini" icon="el-icon-check">仅保存</el-button>
<el-button v-on:click="createWikiCancel" size="mini" icon="el-icon-back">取消</el-button>
<el-col :span="8" style="text-align: right">
<el-button type="primary" v-on:click="createWikiSave(1)" size="small" :icon="ElIconDocumentChecked">保存并查看</el-button>
<el-button type="success" v-on:click="createWikiSave(0)" size="small" :icon="ElIconCheck">仅保存</el-button>
<el-button v-on:click="createWikiCancel" size="small" :icon="ElIconBack">取消</el-button>
</el-col>
</el-row>
</div>
<div v-show="wikiPageEdit.editorType===2" style="padding: 0 10px 10px 10px;background: #fff;">
<div v-show="wikiPageEdit.editorType === 2" style="padding: 0 10px 10px 10px; background: #fff">
<el-input v-model="wikiPageEdit.pageTitle" placeholder="请输入标题" class="page-title-input"></el-input>
<mavon-editor ref="mavonEditor" v-model="markdownContent" :toolbars="toolbars"
:externalLink="false"
style="height: calc(100vh - 165px);"
@save="createWikiSave(0)" @imgAdd="addMarkdownImage"
placeholder="请录入文档内容" class="page-content-editor wang-editor-body"/>
<mavonEditor
ref="mavonEditorRef"
v-model="markdownContent"
:toolbars="toolbars"
:externalLink="false"
style="height: calc(100vh - 165px)"
@save="createWikiSave(0)"
@imgAdd="addMarkdownImage"
placeholder="请录入文档内容"
class="page-content-editor wang-editor-body"
/>
</div>
<div v-show="wikiPageEdit.editorType===1">
<WangEditor ref="wangEditor"></WangEditor>
<div v-show="wikiPageEdit.editorType === 1">
<WangEditor ref="wangEditorRef"></WangEditor>
</div>
<!-- <div v-show="wikiPageEdit.editorType===1" id="newPageContentDiv" class="page-content-editor" style="height: calc(100vh - 250px);"></div>-->
</div>
</div>
</template>
<script>
import pageApi from '../../common/api/page'
import {mavonEditor} from 'mavon-editor'
import 'mavon-editor/dist/markdown/github-markdown.min.css'
import 'mavon-editor/dist/css/index.css'
import axios from 'axios'
import WangEditor from './editor/WangEditor.vue'
<script setup>
import {onBeforeUnmount, ref, onMounted, onUnmounted, watch, defineProps, nextTick, defineEmits, defineExpose, computed} from 'vue';
import {onBeforeRouteUpdate, useRouter, useRoute} from "vue-router";
import {ElMessageBox, ElMessage} from 'element-plus'
import {
DocumentChecked as ElIconDocumentChecked,
Check as ElIconCheck,
Back as ElIconBack,
} from '@element-plus/icons-vue'
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 axios from 'axios'
import WangEditor from './editor/WangEditor.vue'
export default {
props: ['spaceId'],
data() {
return {
editor: {},
// 编辑相关
wikiPageEdit: {
editorType: 2,
pageTitle: "",
},
wikiPage: {},
parentWikiPage: {},
isUnlock: false,
// 页面ID有值代表编辑
pageId: '',
// 父级,有值代表在此父级新建文档
parentId: '',
markdownContent: '',
toolbars: {
bold: true, // 粗体
italic: true, // 斜体
header: true, // 标
underline: true, // 下划线
strikethrough: true, // 中划线
mark: true, // 标记
superscript: true, // 上角标
subscript: true, // 下角标
quote: true, // 引用
ol: true, // 有序列表
ul: true, // 无序列
link: true, // 链接
imagelink: true, // 图片链接
code: true, // code
table: true, // 表格
fullscreen: true, // 全屏编辑
readmodel: true, // 沉浸式阅读
/* 1.3.5 */
undo: true, // 上一步
redo: true, // 下一步
trash: true, // 清空
save: true, // 保存触发events中的save事件
/* 1.4.2 */
navigation: true, // 导航目录
/* 2.1.8 */
alignleft: true, // 左对齐
aligncenter: true, // 居中
alignright: true, // 右对齐
/* 2.2.1 */
subfield: true, // 单双栏模式
preview: true, // 预览
},
fileUploadUrl: process.env.VUE_APP_BASE_API + '/zyplayer-doc-wiki/page/file/wangEditor/upload',
};
},
components: {
WangEditor, mavonEditor,
},
destroyed: function () {
this.unlockPage();
},
beforeRouteUpdate(to, from, next) {
this.initQueryParam(to);
next();
},
mounted: function () {
this.initEditor();
this.initQueryParam(this.$route);
let that = this;
window.onunload = function () {
that.unlockPage();
};
window.onbeforeunload = function () {
that.unlockPage();
};
},
methods: {
changeToRootPath() {
// 没有父级,就是在根目录创建
this.parentId = '';
this.parentWikiPage = {};
},
editorTypeChange() {
},
unlockPage() {
// 防止各种事件重复调这个接口,只需要调一次就好了
if (this.isUnlock) return;
this.isUnlock = true;
pageApi.pageUnlock({pageId: this.pageId});
},
createWikiCancel() {
this.$confirm('确定要取消编辑吗?您编辑的内容将不会被保存哦~', '提示', {
confirmButtonText: '确定',
cancelButtonText: '继续编辑',
type: 'warning'
}).then(() => {
this.unlockPage();
this.$router.back();
});
},
createWikiSave(saveAfter) {
let content = '', preview = '';
if (this.wikiPageEdit.editorType === 2) {
content = this.markdownContent;
preview = this.markdownContent;
} else {
let pageData = this.$refs.wangEditor.getPageData();
content = pageData.html;
preview = pageData.text;
this.wikiPageEdit.pageTitle = pageData.title;
}
if (!this.wikiPageEdit.pageTitle) {
this.$message.warning("标题不能为空");
return;
}
// 修改内容时强制不能修改父路径,只能在目录上拖动修改
let parentId = (this.pageId > 0) ? '' : this.parentId;
let param = {
spaceId: this.spaceId,
parentId: parentId,
id: this.wikiPage.id,
name: this.wikiPageEdit.pageTitle,
editorType: this.wikiPageEdit.editorType,
content: content,
preview: preview,
};
pageApi.updatePage(param).then(json => {
this.$message.success("保存成功!");
// 重新加载左侧列表,跳转到展示页面
this.$emit('loadPageList');
this.pageId = json.data.id;
if (saveAfter == 1) {
this.$router.push({path: '/page/show', query: {pageId: this.pageId}});
} else {
this.loadPageDetail(this.pageId);
}
});
},
loadPageDetail(pageId) {
pageApi.pageDetail({id: pageId}).then(json => {
this.wikiPage = json.data.wikiPage || {};
this.pageContent = json.data.pageContent || {};
this.pageFileList = json.data.fileList || [];
// 内容
this.wikiPageEdit.pageTitle = this.wikiPage.name;
this.wikiPageEdit.editorType = this.wikiPage.editorType;
if (this.wikiPageEdit.editorType === 2) {
this.markdownContent = this.pageContent.content || "";
} else {
// this.editor.txt.html(this.pageContent.content || "");
setTimeout(() => {
this.$refs.wangEditor.setTitle(this.wikiPage.name || "");
this.$refs.wangEditor.setHtml(this.pageContent.content || "");
}, 0);
}
});
},
loadParentPageDetail(pageId) {
if (!pageId) return;
pageApi.pageDetail({id: pageId}).then(json => {
this.parentWikiPage = json.data.wikiPage || {};
});
},
cleanPage() {
this.wikiPage = {};
this.pageContent = {};
this.pageFileList = [];
this.wikiPageEdit.pageTitle = "";
if (!!this.editor.txt) {
this.editor.txt.html("");
}
},
initQueryParam(to) {
// pageId和parentId二选一传了pageId代表编辑页面否则代表新建页面
this.pageId = to.query.pageId;
this.parentId = to.query.parentId;
if (!!this.pageId) {
this.loadPageDetail(this.pageId);
pageApi.pageLock({pageId: this.pageId}).catch(json => {
let that = this;
this.$alert(json.errMsg || '未知错误', '错误', {
confirmButtonText: '确定',
callback: () => {
that.$router.back();
}
});
});
} else {
this.loadParentPageDetail(this.parentId);
this.cleanPage();
}
},
addMarkdownImage(pos, file) {
let formData = new FormData();
formData.append('files', file);
axios({
url: this.fileUploadUrl,
method: 'post',
data: formData,
headers: {'Content-Type': 'multipart/form-data'},
timeout: 10000,
withCredentials: true
}).then(res => {
let urlObj = res.data.data || {};
if (urlObj.url) {
this.$refs.mavonEditor.$img2Url(pos, urlObj.url);
} else {
this.$message.warning("上传失败,返回数据为空");
}
}).catch(e => {
this.$message.warning("上传失败:" + e.message);
});
},
initEditor() {
// this.editor = new WangEditor('#newPageContentDiv');
// this.editor.customConfig.uploadImgServer = process.env.VUE_APP_BASE_API + '/zyplayer-doc-wiki/page/file/wangEditor/upload';
// this.editor.customConfig.zIndex = 100;
// this.editor.customConfig.uploadFileName = 'files';
// this.editor.customConfig.uploadImgMaxLength = 1;
// this.editor.customConfig.pasteFilterStyle = false;
// this.editor.customConfig.withCredentials = true;
// this.editor.create();
},
let editor = ref({});
// 编辑相关
let wikiPageEdit = ref({editorType: 2, pageTitle: '',});
let wikiPage = ref({});
let pageContent = ref({});
let pageFileList = ref([]);
let parentWikiPage = ref({});
let isUnlock = ref(false);
// 页面ID有值代表编辑
let pageId = ref('');
// 父级,有值代表在此父级新建文档
let parentId = ref('');
let markdownContent = ref('');
let fileUploadUrl = ref(import.meta.env.VITE_APP_BASE_API + '/zyplayer-doc-wiki/page/file/wangEditor/upload');
let toolbars = {
bold: true, // 粗体
italic: true, // 斜体
header: true, // 标题
underline: true, // 下划线
strikethrough: true, // 中划线
mark: true, // 标记
superscript: true, // 上角
subscript: true, // 下角标
quote: true, // 引用
ol: true, // 有序列表
ul: true, // 无序列表
link: true, // 链接
imagelink: true, // 图片链接
code: true, // code
table: true, // 表
fullscreen: true, // 全屏编辑
readmodel: true, // 沉浸式阅读
/* 1.3.5 */
undo: true, // 上一步
redo: true, // 下一步
trash: true, // 清空
save: true, // 保存触发events中的save事件
/* 1.4.2 */
navigation: true, // 导航目录
/* 2.1.8 */
alignleft: true, // 左对齐
aligncenter: true, // 居中
alignright: true, // 右对齐
/* 2.2.1 */
subfield: true, // 单双栏模式
preview: true, // 预览
};
const props = defineProps({
spaceId: Number,
});
let emit = defineEmits(['loadPageList']);
onBeforeRouteUpdate((to) => {
initQueryParam(to);
});
let route = useRoute();
let router = useRouter();
onUnmounted(() => {
unlockPage()
});
onMounted(() => {
initEditor()
initQueryParam(route)
let that = this
window.onunload = function () {
that.unlockPage()
}
window.onbeforeunload = function () {
that.unlockPage()
}
})
const changeToRootPath = () => {
// 没有父级,就是在根目录创建
parentId.value = ''
parentWikiPage.value = {}
}
const editorTypeChange = () => {
}
const unlockPage = () => {
// 防止各种事件重复调这个接口,只需要调一次就好了
if (isUnlock.value) return
isUnlock.value = true
pageApi.pageUnlock({pageId: pageId.value})
}
const createWikiCancel = () => {
ElMessageBox.confirm('确定要取消编辑吗?您编辑的内容将不会被保存哦~', '提示', {
confirmButtonText: '确定',
cancelButtonText: '继续编辑',
type: 'warning',
}).then(() => {
unlockPage()
router.back()
})
}
let wangEditorRef = ref();
const createWikiSave = (saveAfter) => {
let content = '',
preview = ''
if (wikiPageEdit.value.editorType === 2) {
content = markdownContent.value
preview = markdownContent.value
} else {
let pageData = wangEditorRef.value.getPageData()
content = pageData.html
preview = pageData.text
wikiPageEdit.value.pageTitle = pageData.title
}
if (!wikiPageEdit.value.pageTitle) {
ElMessage.warning('标题不能为空')
return
}
// 修改内容时强制不能修改父路径,只能在目录上拖动修改
let parentIdVal = pageId.value > 0 ? '' : parentId.value
let param = {
spaceId: props.spaceId,
parentId: parentIdVal,
id: wikiPage.value.id,
name: wikiPageEdit.value.pageTitle,
editorType: wikiPageEdit.value.editorType,
content: content,
preview: preview,
}
pageApi.updatePage(param).then((json) => {
ElMessage.success('保存成功!')
// 重新加载左侧列表,跳转到展示页面
emit('loadPageList')
pageId.value = json.data.id
if (saveAfter == 1) {
router.push({
path: '/page/show',
query: {pageId: pageId.value},
})
} else {
loadPageDetail.value(pageId.value)
}
})
}
const loadPageDetail = (pageId) => {
pageApi.pageDetail({id: pageId}).then((json) => {
wikiPage.value = json.data.wikiPage || {}
pageContent.value = json.data.pageContent || {}
pageFileList.value = json.data.fileList || []
// 内容
wikiPageEdit.value.pageTitle = wikiPage.value.name
wikiPageEdit.value.editorType = wikiPage.value.editorType
if (wikiPageEdit.value.editorType === 2) {
markdownContent.value = pageContent.value.content || ''
} else {
// editor.value.txt.html(pageContent.value.content || "");
setTimeout(() => {
wangEditorRef.value.setTitle(wikiPage.value.name || '')
wangEditorRef.value.setHtml(pageContent.value.content || '')
}, 0)
}
})
}
const loadParentPageDetail = (pageId) => {
if (!pageId) return
pageApi.pageDetail({id: pageId}).then((json) => {
parentWikiPage.value = json.data.wikiPage || {}
})
}
const cleanPage = () => {
wikiPage.value = {}
pageContent.value = {}
pageFileList.value = []
wikiPageEdit.value.pageTitle = ''
if (!!editor.value.txt) {
editor.value.txt.html('')
}
}
const initQueryParam = (to) => {
// pageId和parentId二选一传了pageId代表编辑页面否则代表新建页面
pageId.value = to.query.pageId
parentId.value = to.query.parentId
if (!!pageId.value) {
loadPageDetail(pageId.value)
pageApi.pageLock({pageId: pageId.value}).catch((json) => {
ElMessageBox.alert(json.errMsg || '未知错误', '错误', {
confirmButtonText: '确定',
callback: () => {
router.back()
},
})
})
} else {
loadParentPageDetail(parentId.value)
cleanPage()
}
}
let mavonEditorRef = ref();
const addMarkdownImage = (pos, file) => {
let formData = new FormData()
formData.append('files', file)
axios({
url: fileUploadUrl.value,
method: 'post',
data: formData,
headers: {'Content-Type': 'multipart/form-data'},
timeout: 10000,
withCredentials: true,
}).then((res) => {
let urlObj = res.data.data || {}
if (urlObj.url) {
mavonEditorRef.value.$img2Url(pos, urlObj.url)
} else {
ElMessage.warning('上传失败,返回数据为空')
}
}).catch((e) => {
ElMessage.warning('上传失败:' + e.message)
})
}
const initEditor = () => {
}
</script>
<style>
.page-edit-vue .icon-collapse{float: left;font-size: 25px;color: #aaa;margin-top: 8px;cursor: pointer;}
.page-edit-vue .icon-collapse:hover{color: #eee;}
.page-edit-vue .wiki-title{font-size: 20px;}
.page-edit-vue .wiki-author{font-size: 14px;color: #888;padding: 20px 0;height: 40px;line-height: 40px;}
.page-edit-vue .wiki-content{font-size: 14px;}
.page-edit-vue .wiki-content.w-e-text{overflow-y: auto;}
.page-edit-vue .icon-collapse {
float: left;
font-size: 25px;
color: #aaa;
margin-top: 8px;
cursor: pointer;
}
.page-edit-vue .upload-page-file .el-upload-list{display: none;}
.page-edit-vue .is-link{color: #1e88e5;cursor: pointer;}
/*编辑框高度*/
.page-edit-vue #newPageContentDiv .w-e-text-container{height: 100% !important;}
/*评论*/
.page-edit-vue .comment-box .head{
float: left;background-color: #ccc;border-radius: 50%;margin-right: 10px;
width: 45px; height: 45px; line-height: 45px;text-align: center;color: #fff;
}
.page-edit-vue .page-content-editor{
padding: 10px 0;
}
.page-edit-vue .page-title-input{
padding-bottom: 10px;
}
.page-edit-vue .icon-collapse:hover {
color: #eee;
}
.page-edit-vue .wiki-title {
font-size: 20px;
}
.page-edit-vue .wiki-author {
font-size: 14px;
color: #888;
padding: 20px 0;
height: 40px;
line-height: 40px;
}
.page-edit-vue .wiki-content {
font-size: 14px;
}
.page-edit-vue .wiki-content.w-e-text {
overflow-y: auto;
}
.page-edit-vue .upload-page-file .el-upload-list {
display: none;
}
.page-edit-vue .is-link {
color: #1e88e5;
cursor: pointer;
}
.page-edit-vue #newPageContentDiv .w-e-text-container {
height: 100% !important;
}
.page-edit-vue .comment-box .head {
float: left;
background-color: #ccc;
border-radius: 50%;
margin-right: 10px;
width: 45px;
height: 45px;
line-height: 45px;
text-align: center;
color: #fff;
}
.page-edit-vue .page-content-editor {
padding: 10px 0;
}
.page-edit-vue .page-title-input {
padding-bottom: 10px;
}
</style>

View File

@@ -1,118 +1,144 @@
<template>
<div style="background: #f1f1f1;min-height: 100%;" class="page-search-vue">
<div style="background: #f1f1f1; min-height: 100%" class="page-search-vue">
<div style="max-width: 1200px;margin: 0 auto;background: #fff;padding: 20px;min-height: 100%;box-sizing: border-box;">
<div style="margin-bottom: 20px;">
<el-row :gutter="20" style="max-width: 700px; margin: 0 auto;">
<div style="margin-bottom: 20px">
<el-row :gutter="20" style="max-width: 700px; margin: 0 auto">
<el-col :span="20">
<el-input v-model="searchParam.keywords" @keyup.enter.native="getSpacePageNews" placeholder="" style="width: 100%;"></el-input>
<el-input v-model="searchParam.keywords" @keyup.enter="getSpacePageNews" placeholder="" style="width: 100%"></el-input>
</el-col>
<el-col :span="4">
<el-button type="primary" @click="getSpacePageNews" icon="el-icon-search">搜索一下</el-button>
<el-button type="primary" @click="getSpacePageNews" :icon="ElIconSearch">搜索一下</el-button>
</el-col>
</el-row>
</div>
<div v-if="spacePageNews.length <= 0" class="empty-news">暂无数据</div>
<div v-else class="line-box" v-for="item in spacePageNews">
<div class="line-title">
<span class="text-link">{{item.createUserName}}</span> 发布于 <span class="text-link">{{item.spaceName}}</span>
<span class="text-link">{{ item.createUserName }}</span> 发布于
<span class="text-link">{{ item.spaceName }}</span>
</div>
<div class="page-preview-box">
<div class="page-preview-title" v-on:click="showPageDetail(item)" v-html="item.pageTitle"></div>
<div class="page-preview-content" v-html="item.previewContent"></div>
<div>
<span><img src="../../assets/img/zan.png" class="zan-img"> {{item.zanNum}} </span>
<span><i class="el-icon-view view-img"></i> {{item.viewNum}} </span>
<span>{{item.updateTime||item.createTime}}</span>
<div>
<span><img src="../../assets/img/zan.png" class="zan-img"/>{{ item.zanNum }} </span>
<span><el-icon class="view-img"><el-icon-view/></el-icon>{{ item.viewNum }} </span>
<span>{{ item.updateTime || item.createTime }}</span>
</div>
</div>
</div>
<div class="page-info-box">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:page-sizes="[20, 50, 100]"
:page-size="20"
:current-page="searchParam.pageNum"
layout="prev, pager, next, jumper, sizes, total"
:total="totalCount"
>
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:page-sizes="[20, 50, 100]"
:page-size="20"
:current-page="searchParam.pageNum"
layout="prev, pager, next, jumper, sizes, total"
:total="totalCount">
</el-pagination>
</div>
</div>
</div>
</template>
<script>
import pageApi from '../../common/api/page'
<script setup>
import {onBeforeUnmount, ref, onMounted, watch, defineProps, nextTick, defineEmits, defineExpose, computed} from 'vue';
import {onBeforeRouteUpdate, useRouter, useRoute} from "vue-router";
import {ElMessageBox, ElMessage} from 'element-plus'
import {View as ElIconView, Search as ElIconSearch} from '@element-plus/icons-vue'
import pageApi from '../../assets/api/page'
export default {
data() {
return {
totalCount: 0,
searchParam: {
spaceId: '',
keywords: '',
newsType: 1,
pageNum: 1,
pageSize: 20,
},
spacePageNews:[],
};
},
beforeRouteUpdate(to, from, next) {
this.initQueryParam(to);
next();
},
mounted: function () {
this.initQueryParam(this.$route);
},
methods: {
getSpacePageNews() {
pageApi.pageSearchByEs(this.searchParam).then(json => {
this.spacePageNews = json.data || [];
this.totalCount = json.total;
});
},
handleSizeChange(val) {
this.searchParam.pageSize = val;
this.getSpacePageNews();
},
showPageDetail(row) {
window.open('#/page/show?pageId=' + row.pageId);
},
handleCurrentChange(val) {
this.searchParam.pageNum = val;
this.getSpacePageNews();
},
initQueryParam(to) {
this.searchParam = {
keywords: to.query.keywords,
spaceId: to.query.spaceId,
newsType: 1,
pageNum: 1,
pageSize: 20,
};
this.getSpacePageNews();
},
}
let route = useRoute();
let router = useRouter();
onMounted(() => {
initQueryParam(route);
});
let totalCount = ref(0);
let searchParam = ref({
spaceId: '',
keywords: '',
newsType: 1,
pageNum: 1,
pageSize: 20,
});
let spacePageNews = ref([]);
const getSpacePageNews = () => {
pageApi.pageSearchByEs(searchParam.value).then((json) => {
spacePageNews.value = json.data || []
totalCount.value = json.total
})
}
const handleSizeChange = (val) => {
searchParam.value.pageSize = val
getSpacePageNews()
}
const showPageDetail = (row) => {
window.open('#/page/show?pageId=' + row.pageId)
}
const handleCurrentChange = (val) => {
searchParam.value.pageNum = val
getSpacePageNews()
}
const initQueryParam = (to) => {
searchParam.value = {
keywords: to.query.keywords,
spaceId: to.query.spaceId,
newsType: 1,
pageNum: 1,
pageSize: 20,
}
getSpacePageNews()
}
</script>
<style>
.page-search-vue .empty-news{text-align: center;padding: 100px;}
.page-search-vue .empty-news {
text-align: center;
padding: 100px;
}
.page-search-vue .text-link {
color: #444;
/*cursor: pointer;*/
/*font-weight: bold;*/
}
.page-search-vue .line-box{color: #666;border-bottom: 1px solid #eee;padding: 20px 0;}
.page-search-vue .line-title{font-size: 14px;}
.page-search-vue .page-preview-box{}
.page-search-vue .page-preview-title{font-size: 18px;margin: 10px 0 5px 0;color: #3a8ee6;cursor: pointer;}
.page-search-vue .page-preview-content{font-size: 16px;margin-bottom: 5px;}
.page-search-vue .zan-img{vertical-align: middle;margin-top: -3px;}
.page-search-vue .view-img{font-size: 16px;color: #666;}
.page-search-vue .text-link {
color: #444;
}
.page-search-vue .page-info-box{text-align: right;margin: 20px 0 50px 0;}
.page-search-vue .line-box {
color: #666;
border-bottom: 1px solid #eee;
padding: 20px 0;
}
.page-search-vue .line-title {
font-size: 14px;
}
.page-search-vue .page-preview-box {
}
.page-search-vue .page-preview-title {
font-size: 18px;
margin: 10px 0 5px 0;
color: #3a8ee6;
cursor: pointer;
}
.page-search-vue .page-preview-content {
font-size: 16px;
margin-bottom: 5px;
}
.page-search-vue .zan-img {
vertical-align: middle;
margin-top: -3px;
}
.page-search-vue .view-img {
font-size: 16px;
color: #666;
}
.page-search-vue .page-info-box {
text-align: right;
margin: 20px 0 50px 0;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -1,102 +1,113 @@
<template>
<div class="navigation">
<div ref="navigation" style="display: inline-block;width: 100%;"></div>
<div class="navigation-heading" :style="{width: navigationWidth}">
<div v-for="item in heading" :class="'heading-item heading-'+item.level" @click="headingItemClick(item)">
{{item.text}}
<div ref="navigationRef" style="display: inline-block; width: 100%"></div>
<div class="navigation-heading" :style="{ width: navigationWidth }">
<div v-for="item in heading" :class="'heading-item heading-' + item.level" @click="headingItemClick(item)">
{{ item.text }}
</div>
</div>
</div>
</template>
<script>
export default {
props: {
heading: {
type: Array,
default: []
},
},
data() {
return {
navigationWidth: '100px',
};
},
watch: {
'$store.state.global.rightAsideWidth'() {
this.computeNavigationWidth();
}
},
mounted() {
window.onresize = () => {
this.computeNavigationWidth();
}
setTimeout(() => this.computeNavigationWidth(), 100);
},
methods: {
computeNavigationWidth() {
this.navigationWidth = window.getComputedStyle(this.$refs.navigation, null).width;
},
headingItemClick(item) {
// 滚动到指定节点
item.node.scrollIntoView({behavior: "smooth", block: "start", inline: "nearest"});
// 距离顶部高度
//console.log(item.node.offsetTop - item.node.scrollHeight)
},
}
<script setup>
import {
toRefs,
ref,
reactive,
onMounted,
watch,
defineEmits,
defineProps,
defineExpose,
} from 'vue'
import {useStoreDisplay} from '@/store/wikiDisplay.js'
const storeDisplay = useStoreDisplay()
let navigationWidth = ref('100px')
const props = defineProps({
heading: {
type: Array,
default: [],
},
})
onMounted(() => {
window.onresize = () => {
computeNavigationWidth()
}
setTimeout(() => computeNavigationWidth(), 100)
})
watch(() => storeDisplay.viewMenuWidth, (newVal) => {
computeNavigationWidth()
})
let navigationRef = ref();
const computeNavigationWidth = () => {
navigationWidth.value = window.getComputedStyle(
navigationRef.value,
null
).width
}
const headingItemClick = (item) => {
// 滚动到指定节点
item.node.scrollIntoView({
behavior: 'smooth',
block: 'start',
inline: 'nearest',
})
// 距离顶部高度
//console.log(item.node.offsetTop - item.node.scrollHeight)
}
</script>
<style>
.navigation {
width: 100%;
}
.navigation {
width: 100%;
}
.navigation-heading {
position: fixed;
z-index: 4;
top: 150px;
max-height: calc(100vh - 250px);
width: 100%;
overflow-y: auto;
padding-left: 16px;
box-sizing: border-box;
}
.navigation-heading {
position: fixed;
z-index: 4;
top: 150px;
max-height: calc(100vh - 250px);
width: 100%;
overflow-y: auto;
padding-left: 16px;
box-sizing: border-box;
}
.navigation-heading .heading-item {
padding: 5px 0;
cursor: pointer;
color: #646a73;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.navigation-heading .heading-item {
padding: 5px 0;
cursor: pointer;
color: #646a73;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.navigation-heading .heading-item:hover {
color: #3370ff;
}
.navigation-heading .heading-item:hover {
color: #3370ff;
}
.navigation-heading .heading-1 {
padding-left: 0;
}
.navigation-heading .heading-1 {
padding-left: 0;
}
.navigation-heading .heading-2 {
padding-left: 16px;
}
.navigation-heading .heading-2 {
padding-left: 16px;
}
.navigation-heading .heading-3 {
padding-left: 32px;
}
.navigation-heading .heading-3 {
padding-left: 32px;
}
.navigation-heading .heading-4 {
padding-left: 48px;
}
.navigation-heading .heading-4 {
padding-left: 48px;
}
.navigation-heading .heading-5 {
padding-left: 64px;
}
.navigation-heading .heading-5 {
padding-left: 64px;
}
.navigation-heading .heading-6 {
padding-left: 80px;
}
.navigation-heading .heading-6 {
padding-left: 80px;
}
</style>

View File

@@ -2,105 +2,105 @@
<div class="wang-editor-box">
<div class="editor-toolbar-box fix-top">
<Toolbar class="editor-toolbar"
:editorId="editorId"
:defaultConfig="toolbarConfig"
:mode="mode"
:defaultConfig="toolbarConfig"
:mode="mode"
:editor="editorRef"
/>
</div>
<div class="wang-editor-content">
<div class="editor-container">
<div class="title-container">
<input v-model="pageTitle" placeholder="请输入标题">
<input v-model="pageTitle" placeholder="请输入标题" :maxlength="50">
</div>
<div @click="pageEditorBodyClick" class="page-editor-body">
<Editor :defaultConfig="editorConfig" :mode="mode" @onCreated="handleCreated"/>
</div>
<Editor ref="editorTextArea" class="editor-text-area"
:editorId="editorId"
:defaultConfig="editorConfig"
:mode="mode"
/>
</div>
</div>
</div>
</template>
<script>
import '@wangeditor/editor/dist/css/style.css'
import { Editor, Toolbar, getEditor, removeEditor } from '@wangeditor/editor-for-vue'
export default {
props: ['spaceId'],
data() {
return {
editorId: `w-e-${Math.random().toString().slice(-5)}`,
toolbarConfig: {
excludeKeys: [
"fullScreen", "undo" , "redo"
],
},
editorConfig: {
placeholder: '请输入文档内容',
scroll: false,
MENU_CONF: {
uploadImage: {
server: process.env.VUE_APP_BASE_API + '/zyplayer-doc-wiki/page/file/wangEditor/upload',
fieldName: 'files',
// 最大支持50M图片上传
maxFileSize: 50 * 1024 * 1024,
withCredentials: true,
}
}
},
mode: 'default', // or 'simple'
defaultHtml: '',
editor: {},
pageTitle: '',
};
<script setup>
import '@wangeditor/editor/dist/css/style.css'
import {DomEditor} from '@wangeditor/editor'
import {onBeforeUnmount, ref, shallowRef, onMounted, watch, defineProps, defineExpose} from 'vue'
import {Editor, Toolbar} from '@wangeditor/editor-for-vue'
import {onBeforeRouteUpdate, useRouter, useRoute} from "vue-router";
const props = defineProps({
});
let toolbarConfig = {
excludeKeys: [
"fullScreen", "undo", "redo", "emotion", "|", "lineHeight", "fontFamily"
],
};
let route = useRoute();
let editorConfig = ref({
placeholder: '请输入文档内容',
scroll: false,
MENU_CONF: {
uploadImage: {
server: import.meta.env.VITE_APP_BASE_API + '/zyplayer-doc-wiki/page/file/wangEditor/upload',
fieldName: 'files',
// 最大支持50M图片上传
maxFileSize: 50 * 1024 * 1024,
withCredentials: true,
meta: {pageId: route.params.pageId},
},
components: {
Editor, Toolbar,
},
beforeDestroy() {
let editor = getEditor(this.editorId);
if (editor == null) return;
editor.destroy();
removeEditor(this.editorId);
},
mounted: function () {
setTimeout(() => {
this.initEditor();
}, 0);
},
methods: {
initEditor() {
// 点击空白处 focus 编辑器
let editorTextArea = this.$refs.editorTextArea.$el;
editorTextArea.addEventListener('click', e => {
if (e.target.className === 'editor-text-area') {
let editor = getEditor(this.editorId);
editor.blur();
editor.focus(true); // focus 到末尾
}
});
},
getPageData() {
let editor = getEditor(this.editorId);
return {
title: this.pageTitle,
html: editor.getHtml(),
text: editor.getText(),
};
},
setTitle(title) {
this.pageTitle = title;
},
setHtml(content) {
let editor = getEditor(this.editorId);
editor.select([]);
editor.deleteFragment();
editor.dangerouslyInsertHtml(content);
},
uploadVideo: {
server: import.meta.env.VITE_APP_BASE_API + '/zyplayer-doc-wiki/page/file/wangEditor/upload',
fieldName: 'files',
// 最大支持300M图片上传
maxFileSize: 300 * 1024 * 1024,
withCredentials: true,
meta: {pageId: route.params.pageId},
}
}
});
let mode = 'default';
let defaultHtml = '';
const editorRef = shallowRef()
let pageTitle = ref('');
const handleCreated = (editor) => {
editorRef.value = editor;
}
const pageEditorBodyClick = (e) => {
if (e.target.classList && e.target.classList.contains('page-editor-body')) {
editorRef.value.blur();
editorRef.value.focus(true);
}
}
const getPageData = () => {
return {
title: pageTitle.value,
html: editorRef.value.getHtml(),
text: editorRef.value.getText(),
};
}
const setTitle = (title) => {
pageTitle.value = title;
}
const setHtml = (content) => {
editorRef.value.select([]);
editorRef.value.deleteFragment();
editorRef.value.dangerouslyInsertHtml(content);
}
onBeforeUnmount(() => {
const editor = editorRef.value;
if (editor == null) return;
editor.destroy();
});
defineExpose({setTitle, setHtml, getPageData});
</script>
<style>
.wang-editor-box {
background-color: #f5f5f5;
@@ -110,8 +110,12 @@
padding-left: 30px;
}
.wang-editor-box .editor-toolbar .w-e-bar {
background: #FCFCFC;
}
.wang-editor-box .editor-toolbar {
width: 1200px;
width: 980px;
background-color: #FCFCFC;
margin: 0 auto;
}
@@ -130,7 +134,10 @@
.wang-editor-box .wang-editor-content {
padding: 20px 0;
overflow: auto;
height: calc(100vh - 170px);
height: calc(100vh - 136px);
}
.wang-editor-box .w-e-bar-item {
height: 39px;
}
.wang-editor-box .editor-container {
@@ -155,7 +162,7 @@
line-height: 1;
}
.wang-editor-box .editor-text-area {
.wang-editor-box .page-editor-body {
margin-top: 20px;
min-height: 600px;
}

View File

@@ -1,29 +1,31 @@
<template>
<div class="page-share-view-vue">
<van-nav-bar :title="wikiPage.name" class="header">
<van-icon name="wap-nav" slot="left" size="20" @click="popupShowChange"></van-icon>
<template v-slot:left>
<van-icon name="wap-nav" size="20" @click="popupShowChange"></van-icon>
</template>
</van-nav-bar>
<el-row type="border-card" class="main">
<div style="max-width: 950px;margin: 0 auto;">
<div style="max-width: 950px; margin: 0 auto">
<div class="wiki-author">
<span v-if="wikiPage.updateTime">最后修改{{wikiPage.updateTime}}</span>
<span v-else>创建时间{{wikiPage.createTime}}</span>
<span v-if="wikiPage.updateTime">最后修改{{ wikiPage.updateTime }}</span>
<span v-else>创建时间{{ wikiPage.createTime }}</span>
</div>
<div class="wiki-files">
<el-table v-show="pageFileList.length > 0" :data="pageFileList" border style="width: 100%; margin-bottom: 5px;">
<el-table v-show="pageFileList.length > 0" :data="pageFileList" border style="width: 100%; margin-bottom: 5px">
<el-table-column label="文件名">
<template slot-scope="scope">
<a target="_blank" :href="scope.row.fileUrl">{{scope.row.fileName}}</a>
<template v-slot="scope">
<a target="_blank" :href="scope.row.fileUrl">{{scope.row.fileName }}</a>
</template>
</el-table-column>
<el-table-column label="文件大小">
<template slot-scope="scope">{{computeFileSize(scope.row.fileSize)}}</template>
<template v-slot="scope">{{computeFileSize(scope.row.fileSize) }}</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="180px"></el-table-column>
<el-table-column prop="downloadNum" label="下载次数" width="80px"></el-table-column>
</el-table>
</div>
<div ref="pageContent" class="wiki-page-content">
<div ref="pageContentRef" class="wiki-page-content">
<div v-html="pageShowDetail" class="markdown-body" v-if="wikiPage.editorType == 2"></div>
<div v-html="pageShowDetail" class="wang-editor-body" v-else></div>
</div>
@@ -32,119 +34,156 @@
</div>
</template>
<script>
import pageApi from '../../../../common/api/page'
import {mavonEditor} from 'mavon-editor'
import ElImageViewer from 'element-ui/packages/image/src/image-viewer'
import { ImagePreview } from 'vant';
import 'mavon-editor/dist/markdown/github-markdown.min.css'
import 'mavon-editor/dist/css/index.css'
<script setup>
import {onBeforeUnmount, ref, 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 {mavonEditor} from 'mavon-editor'
import {ImagePreview} from 'vant'
import 'mavon-editor/dist/markdown/github-markdown.min.css'
import 'mavon-editor/dist/css/index.css'
export default {
data() {
return {
spaceUuid: '',
nowPageId: '',
// 页面展示相关
wikiPage: {},
pageFileList: [],
pageShowDetail: '',
// 大图预览
previewInitialIndex: 0,
showImagePreviewList: [],
};
},
components: {'el-image-viewer': ElImageViewer},
beforeRouteUpdate(to, from, next) {
this.initQueryParam(to);
next();
},
mounted() {
this.initQueryParam(this.$route);
},
methods: {
popupShowChange() {
this.$emit("popupShow", true);
},
loadPageDetail(pageId) {
let param = {pageId: pageId, space: this.spaceUuid};
pageApi.openPageDetail(param).then(json => {
let wikiPage = json.data.wikiPage || {};
wikiPage.selfZan = json.data.selfZan || 0;
this.wikiPage = wikiPage;
let pageContent = json.data.pageContent || {};
this.pageFileList = json.data.fileList || [];
if (this.wikiPage.editorType === 2) {
pageContent.content = mavonEditor.getMarkdownIt().render(pageContent.content);
}
this.pageShowDetail = pageContent.content;
document.title = wikiPage.name || 'WIKI-内容展示';
setTimeout(() => this.previewPageImage(), 500);
});
},
initQueryParam(to) {
this.spaceUuid = to.query.space;
this.nowPageId = to.query.pageId;
if (!!this.nowPageId) {
this.loadPageDetail(this.nowPageId);
}
},
computeFileSize(fileSize) {
if (!fileSize) {
return '-';
}
let size = "";
if (fileSize < 0.1 * 1024) {
size = fileSize.toFixed(2) + "B"
} else if (fileSize < 0.1 * 1024 * 1024) {
size = (fileSize / 1024).toFixed(2) + "KB"
} else if (fileSize < 0.1 * 1024 * 1024 * 1024) {
size = (fileSize / (1024 * 1024)).toFixed(2) + "MB"
} else {
size = (fileSize / (1024 * 1024 * 1024)).toFixed(2) + "GB"
}
let sizeStr = size + "";
let index = sizeStr.indexOf(".");
let dou = sizeStr.substr(index + 1, 2);
if (dou == "00") {
return sizeStr.substring(0, index) + sizeStr.substr(index + 3, 2)
}
return size;
},
previewPageImage() {
const imgArr = [];
const imgSelector = this.$refs.pageContent.querySelectorAll('img');
imgSelector.forEach((item, index) => {
imgArr.push(item.src);
item.onclick = () => {
this.previewInitialIndex = index;
this.showImagePreviewList = imgArr;
ImagePreview({
images: imgArr,
startPosition: index,
});
}
});
},
let spaceUuid = ref('');
let nowPageId = ref('');
// 页面展示相关
let wikiPage = ref({});
let pageFileList = ref([]);
let pageShowDetail = ref('');
// 大图预览
let previewInitialIndex = ref(0);
let showImagePreviewList = ref([]);
let emit = defineEmits(['popupShow']);
onBeforeRouteUpdate((to) => {
initQueryParam(to);
});
let route = useRoute();
let router = useRouter();
onMounted(() => {
initQueryParam(route);
});
const popupShowChange = () => {
emit('popupShow', true)
}
const loadPageDetail = (pageId) => {
let param = {pageId: pageId, space: spaceUuid.value}
pageApi.openPageDetail(param).then((json) => {
let wikiPageRes = json.data.wikiPage || {}
wikiPageRes.selfZan = json.data.selfZan || 0
wikiPage.value = wikiPageRes
let pageContent = json.data.pageContent || {}
pageFileList.value = json.data.fileList || []
if (wikiPage.value.editorType === 2) {
pageContent.content = mavonEditor.getMarkdownIt().render(pageContent.content)
}
pageShowDetail.value = pageContent.content
document.title = wikiPageRes.name || 'WIKI-内容展示'
setTimeout(() => previewPageImage(), 500)
})
}
const initQueryParam = (to) => {
spaceUuid.value = to.query.space
nowPageId.value = to.query.pageId
if (!!nowPageId.value) {
loadPageDetail(nowPageId.value)
}
}
const computeFileSize = (fileSize) => {
if (!fileSize) {
return '-'
}
let size = ''
if (fileSize < 0.1 * 1024) {
size = fileSize.toFixed(2) + 'B'
} else if (fileSize < 0.1 * 1024 * 1024) {
size = (fileSize / 1024).toFixed(2) + 'KB'
} else if (fileSize < 0.1 * 1024 * 1024 * 1024) {
size = (fileSize / (1024 * 1024)).toFixed(2) + 'MB'
} else {
size = (fileSize / (1024 * 1024 * 1024)).toFixed(2) + 'GB'
}
let sizeStr = size + ''
let index = sizeStr.indexOf('.')
let dou = sizeStr.substr(index + 1, 2)
if (dou == '00') {
return sizeStr.substring(0, index) + sizeStr.substr(index + 3, 2)
}
return size
}
let pageContentRef = ref();
const previewPageImage = () => {
const imgArr = []
const imgSelector = pageContentRef.value.querySelectorAll('img')
imgSelector.forEach((item, index) => {
imgArr.push(item.src)
item.onclick = () => {
previewInitialIndex.value = index
showImagePreviewList.value = imgArr
ImagePreview({
images: imgArr,
startPosition: index,
})
}
})
}
</script>
<style>
@import "../../../../common/lib/wangEditor.css";
@import '../../../../assets/lib/wangEditor.css';
.page-share-view-vue{}
.page-share-view-vue .wiki-title{font-size: 20px;text-align: center;}
.page-share-view-vue .wiki-author{font-size: 14px;color: #888;height: 40px;line-height: 40px;}
.page-share-view-vue {
}
.page-share-view-vue .wiki-page-content img{cursor: pointer;max-width: 100%;}
.page-share-view-vue .wiki-page-content img:hover{box-shadow: 0 2px 6px 0 rgba(0,0,0,.3);}
.page-share-view-vue .wiki-title {
font-size: 20px;
text-align: center;
}
.page-share-view-vue .upload-page-file .el-upload-list{display: none;}
.page-share-view-vue .is-link{color: #1e88e5;cursor: pointer;}
.page-share-view-vue .wiki-author {
font-size: 14px;
color: #888;
height: 40px;
line-height: 40px;
}
.page-share-view-vue .header{width:100%;height:46px;}
.page-share-view-vue .main{position:absolute;top:46px;bottom: 0;right:0;left:0;overflow:auto;padding: 10px;}
.page-share-view-vue .footer{width:100%;height:26px;position:fixed;bottom:0}
.page-share-view-vue .wiki-page-content img {
cursor: pointer;
max-width: 100%;
}
.page-share-view-vue .wiki-page-content img:hover {
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.3);
}
.page-share-view-vue .upload-page-file .el-upload-list {
display: none;
}
.page-share-view-vue .is-link {
color: #1e88e5;
cursor: pointer;
}
.page-share-view-vue .header {
width: 100%;
height: 46px;
}
.page-share-view-vue .main {
position: absolute;
top: 46px;
bottom: 0;
right: 0;
left: 0;
overflow: auto;
padding: 10px;
}
.page-share-view-vue .footer {
width: 100%;
height: 26px;
position: fixed;
bottom: 0;
}
</style>

View File

@@ -1,18 +1,19 @@
<template>
<div style="padding: 10px;" class="page-share-home-vue">
<div style="max-width: 800px;margin: 0 auto;">
<div style="padding: 10px" class="page-share-home-vue">
<div style="max-width: 800px; margin: 0 auto">
<div v-if="spacePageNews.length <= 0" class="empty-news">暂无数据</div>
<div v-else class="line-box" v-for="item in spacePageNews">
<div class="line-title">
<span class="text-link">{{item.createUserName}}</span> 发布于 <span class="text-link">{{item.spaceName}}</span>
<span class="text-link">{{ item.createUserName }}</span> 发布于
<span class="text-link">{{ item.spaceName }}</span>
</div>
<div class="page-preview-box">
<div class="page-preview-title" v-on:click="showPageDetail(item)" v-html="item.pageTitle"></div>
<div class="page-preview-content" v-html="item.previewContent"></div>
<div>
<span><img src="../../../../assets/img/zan.png" class="zan-img"> {{item.zanNum}} </span>
<span><i class="el-icon-view view-img"></i> {{item.viewNum}} </span>
<span>{{item.updateTime||item.createTime}}</span>
<span><img src="../../../../assets/img/zan.png" class="zan-img"/>{{ item.zanNum }} </span>
<span><el-icon class="view-img"><el-icon-view/></el-icon> {{ item.viewNum }} </span>
<span>{{ item.updateTime || item.createTime }}</span>
</div>
</div>
</div>
@@ -24,83 +25,109 @@
:page-size="20"
:current-page="searchParam.pageNum"
layout="prev, pager, next, jumper, sizes, total"
:total="totalCount"
>
:total="totalCount">
</el-pagination>
</div>
</div>
</div>
</template>
<script>
import pageApi from '../../../../common/api/page'
<script setup>
import {onBeforeUnmount, ref, onMounted, watch, defineProps, nextTick, defineEmits, defineExpose, computed} from 'vue';
import {onBeforeRouteUpdate, useRouter, useRoute} from "vue-router";
import {ElMessageBox, ElMessage} from 'element-plus'
import {View as ElIconView} from '@element-plus/icons-vue'
import pageApi from '../../../../assets/api/page'
export default {
data() {
return {
totalCount: 0,
searchParam: {
spaceId: '',
newsType: 1,
pageNum: 1,
pageSize: 20,
},
spacePageNews: [],
};
},
beforeRouteUpdate(to, from, next) {
this.initQueryParam(to);
next();
},
mounted: function () {
this.initQueryParam(this.$route);
},
methods: {
getSpacePageNews() {
pageApi.openPageNews(this.searchParam).then(json => {
this.spacePageNews = json.data || [];
this.totalCount = json.total;
});
},
handleSizeChange(val) {
this.searchParam.pageSize = val;
this.getSpacePageNews();
},
showPageDetail(row) {
this.nowClickPath = {space: row.space, pageId: row.pageId};
this.$router.push({path: '/page/share/view', query: this.nowClickPath});
},
handleCurrentChange(val) {
this.searchParam.pageNum = val;
this.getSpacePageNews();
},
initQueryParam(to) {
this.searchParam = {
space: to.query.space,
newsType: 1,
pageNum: 1,
pageSize: 20,
};
if (!!this.searchParam.space) {
this.getSpacePageNews();
}
},
}
let totalCount = ref(0);
let searchParam = ref({spaceId: '', newsType: 1, pageNum: 1, pageSize: 20,});
let spacePageNews = ref([]);
onBeforeRouteUpdate((to) => {
initQueryParam(to);
});
let route = useRoute();
let router = useRouter();
onMounted(() => {
initQueryParam(route);
});
const getSpacePageNews = () => {
pageApi.openPageNews(searchParam.value).then((json) => {
spacePageNews.value = json.data || []
totalCount.value = json.total
})
}
const handleSizeChange = (val) => {
searchParam.value.pageSize = val
getSpacePageNews()
}
const showPageDetail = (row) => {
let nowClickPath = {space: row.space, pageId: row.pageId}
router.push({path: '/page/share/view', query: nowClickPath})
}
const handleCurrentChange = (val) => {
searchParam.value.pageNum = val
getSpacePageNews()
}
const initQueryParam = (to) => {
searchParam.value = {
space: to.query.space,
newsType: 1,
pageNum: 1,
pageSize: 20,
}
if (!!searchParam.value.space) {
getSpacePageNews()
}
}
</script>
<style>
.page-share-home-vue .empty-news{text-align: center;padding: 100px;}
.page-share-home-vue .text-link {
color: #444;
}
.page-share-home-vue .line-box{color: #666;border-bottom: 1px solid #eee;padding: 20px 0;}
.page-share-home-vue .line-title{font-size: 14px;}
.page-share-home-vue .page-preview-box{}
.page-share-home-vue .page-preview-title{cursor: pointer;font-size: 20px;margin: 10px 0 5px 0;color: #3a8ee6;}
.page-share-home-vue .page-preview-content{font-size: 16px;margin-bottom: 5px;}
.page-share-home-vue .zan-img{vertical-align: middle;margin-top: -3px;}
.page-share-home-vue .view-img{font-size: 16px;color: #666;}
.page-share-home-vue .empty-news {
text-align: center;
padding: 100px;
}
.page-share-home-vue .page-info-box{text-align: right;margin: 20px 0 50px 0;}
.page-share-home-vue .text-link {
color: #444;
}
.page-share-home-vue .line-box {
color: #666;
border-bottom: 1px solid #eee;
padding: 20px 0;
}
.page-share-home-vue .line-title {
font-size: 14px;
}
.page-share-home-vue .page-preview-box {
}
.page-share-home-vue .page-preview-title {
cursor: pointer;
font-size: 20px;
margin: 10px 0 5px 0;
color: #3a8ee6;
}
.page-share-home-vue .page-preview-content {
font-size: 16px;
margin-bottom: 5px;
}
.page-share-home-vue .zan-img {
vertical-align: middle;
margin-top: -3px;
}
.page-share-home-vue .view-img {
font-size: 16px;
color: #666;
}
.page-share-home-vue .page-info-box {
text-align: right;
margin: 20px 0 50px 0;
}
</style>

View File

@@ -5,28 +5,28 @@
<el-col :xs="0" :sm="4" :md="4" :lg="6" :xl="6" v-if="navigationList.length > 0">
<Navigation :heading="navigationList"></Navigation>
</el-col>
<el-col :xs="24" :sm="navigationList.length > 0?20:24" :md="navigationList.length > 0?20:24" :lg="navigationList.length > 0?18:24" :xl="navigationList.length > 0?18:24">
<div style="max-width: 1000px;padding-left: 10px;margin: 0 auto;">
<div class="wiki-title" ref="wikiTitle">{{wikiPage.name}}</div>
<el-col :xs="24" :sm="navigationList.length > 0 ? 20 : 24" :md="navigationList.length > 0 ? 20 : 24" :lg="navigationList.length > 0 ? 18 : 24" :xl="navigationList.length > 0 ? 18 : 24">
<div style="max-width: 1000px; padding-left: 10px; margin: 0 auto">
<div class="wiki-title" ref="wikiTitleRef">{{ wikiPage.name }}</div>
<div class="wiki-author">
<span v-if="wikiPage.updateTime">最后修改{{wikiPage.updateTime}}</span>
<span v-else>创建时间{{wikiPage.createTime}}</span>
<span v-if="wikiPage.updateTime">最后修改{{ wikiPage.updateTime }}</span>
<span v-else>创建时间{{ wikiPage.createTime }}</span>
</div>
<div class="wiki-files">
<el-table v-show="pageFileList.length > 0" :data="pageFileList" border style="width: 100%; margin-bottom: 5px;">
<el-table v-show="pageFileList.length > 0" :data="pageFileList" border style="width: 100%; margin-bottom: 5px">
<el-table-column label="文件名">
<template slot-scope="scope">
<a target="_blank" :href="scope.row.fileUrl">{{scope.row.fileName}}</a>
<template v-slot="scope">
<a target="_blank" :href="scope.row.fileUrl">{{scope.row.fileName }}</a>
</template>
</el-table-column>
<el-table-column label="文件大小">
<template slot-scope="scope">{{computeFileSize(scope.row.fileSize)}}</template>
<template v-slot="scope">{{computeFileSize(scope.row.fileSize) }}</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="180px"></el-table-column>
<el-table-column prop="downloadNum" label="下载次数" width="80px"></el-table-column>
</el-table>
</div>
<div ref="pageContent" class="wiki-page-content">
<div ref="pageContentRef" class="wiki-page-content">
<div v-html="pageShowDetail" class="markdown-body" v-if="wikiPage.editorType == 2"></div>
<div v-html="pageShowDetail" class="wang-editor-body" v-else></div>
</div>
@@ -34,142 +34,169 @@
</el-col>
</el-row>
</el-row>
<div ref="imagePreview">
<el-image-viewer v-if="showImagePreview" :url-list="showImagePreviewList" :on-close="closeImagePreview" :initial-index="previewInitialIndex"></el-image-viewer>
<div ref="imagePreviewRef">
<el-image-viewer v-if="showImagePreview"
:url-list="showImagePreviewList"
:on-close="closeImagePreview"
:initial-index="previewInitialIndex">
</el-image-viewer>
</div>
</div>
</template>
<script>
import pageApi from '../../../../common/api/page'
import {mavonEditor} from 'mavon-editor'
import unitUtil from '../../../../common/lib/UnitUtil.js'
import htmlUtil from '../../../../common/lib/HtmlUtil.js'
import Navigation from '../../components/Navigation.vue'
import ElImageViewer from 'element-ui/packages/image/src/image-viewer'
import 'mavon-editor/dist/markdown/github-markdown.min.css'
import 'mavon-editor/dist/css/index.css'
<script setup>
import {onBeforeUnmount, ref, 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 {mavonEditor} from 'mavon-editor'
import unitUtil from '../../../../assets/lib/UnitUtil.js'
import htmlUtil from '../../../../assets/lib/HtmlUtil.js'
import Navigation from '../../components/Navigation.vue'
import 'mavon-editor/dist/markdown/github-markdown.min.css'
import 'mavon-editor/dist/css/index.css'
export default {
data() {
return {
spaceUuid: '',
nowPageId: '',
// 页面展示相关
wikiPage: {},
pageFileList: [],
pageShowDetail: '',
// 大图预览
previewInitialIndex: 0,
showImagePreview: false,
showImagePreviewList: [],
navigationList: [],
};
},
components: {'el-image-viewer': ElImageViewer, Navigation},
beforeRouteUpdate(to, from, next) {
this.initQueryParam(to);
next();
},
mounted() {
this.initQueryParam(this.$route);
},
methods: {
loadPageDetail(pageId) {
let param = {pageId: pageId, space: this.spaceUuid};
pageApi.openPageDetail(param).then(json => {
let wikiPage = json.data.wikiPage || {};
wikiPage.selfZan = json.data.selfZan || 0;
this.wikiPage = wikiPage;
let pageContent = json.data.pageContent || {};
this.pageFileList = json.data.fileList || [];
if (this.wikiPage.editorType === 2) {
pageContent.content = mavonEditor.getMarkdownIt().render(pageContent.content);
}
this.pageShowDetail = pageContent.content;
let wikiTile = wikiPage.name || 'WIKI-内容展示';
document.title = wikiTile;
setTimeout(() => {
this.previewPageImage();
let navigationList = htmlUtil.createNavigationHeading();
// 标题加到导航里面去
if (navigationList.length > 0) {
navigationList.unshift({
level: 1,
node: this.$refs.wikiTitle,
text: wikiTile
});
}
this.navigationList = navigationList;
}, 500);
});
},
initQueryParam(to) {
this.spaceUuid = to.query.space;
this.nowPageId = to.query.pageId;
if (!!this.nowPageId) {
this.loadPageDetail(this.nowPageId);
}
},
computeFileSize(fileSize) {
if (!fileSize) {
return '-';
}
let size = "";
if (fileSize < 0.1 * 1024) {
size = fileSize.toFixed(2) + "B"
} else if (fileSize < 0.1 * 1024 * 1024) {
size = (fileSize / 1024).toFixed(2) + "KB"
} else if (fileSize < 0.1 * 1024 * 1024 * 1024) {
size = (fileSize / (1024 * 1024)).toFixed(2) + "MB"
} else {
size = (fileSize / (1024 * 1024 * 1024)).toFixed(2) + "GB"
}
let sizeStr = size + "";
let index = sizeStr.indexOf(".");
let dou = sizeStr.substr(index + 1, 2);
if (dou == "00") {
return sizeStr.substring(0, index) + sizeStr.substr(index + 3, 2)
}
return size;
},
closeImagePreview() {
this.showImagePreview = false;
},
previewPageImage() {
const imgArr = [];
const imgSelector = this.$refs.pageContent.querySelectorAll('img');
imgSelector.forEach((item, index) => {
imgArr.push(item.src);
item.onclick = () => {
this.previewInitialIndex = index;
this.showImagePreviewList = imgArr;
this.showImagePreview = true;
setTimeout(() => this.initImageViewerMask(), 0);
}
});
},
initImageViewerMask() {
// 图片预览遮罩点击隐藏预览框
let imageViewerMask = this.$refs.imagePreview.querySelectorAll('.el-image-viewer__mask');
imageViewerMask.forEach(item => {
item.onclick = () => this.showImagePreview = false;
});
},
let spaceUuid = ref('');
let nowPageId = ref('');
// 页面展示相关
let wikiPage = ref({});
let pageFileList = ref([]);
let pageShowDetail = ref('');
// 大图预览
let previewInitialIndex = ref(0);
let showImagePreview = ref(false);
let showImagePreviewList = ref([]);
let navigationList = ref([]);
onBeforeRouteUpdate((to) => {
initQueryParam(to);
});
let route = useRoute();
let router = useRouter();
onMounted(() => {
initQueryParam(route);
});
let wikiTitleRef = ref();
const loadPageDetail = (pageId) => {
let param = {pageId: pageId, space: spaceUuid.value}
pageApi.openPageDetail(param).then((json) => {
let wikiPage = json.data.wikiPage || {}
wikiPage.selfZan = json.data.selfZan || 0
wikiPage.value = wikiPage
let pageContent = json.data.pageContent || {}
pageFileList.value = json.data.fileList || []
if (wikiPage.value.editorType === 2) {
pageContent.content = mavonEditor
.getMarkdownIt()
.render(pageContent.content)
}
pageShowDetail.value = pageContent.content
let wikiTile = wikiPage.name || 'WIKI-内容展示'
document.title = wikiTile
setTimeout(() => {
previewPageImage()
let navigationList = htmlUtil.createNavigationHeading()
// 标题加到导航里面去
if (navigationList.length > 0) {
navigationList.unshift({
level: 1,
node: wikiTitleRef.value,
text: wikiTile,
})
}
navigationList.value = navigationList
}, 500)
})
}
const initQueryParam = (to) => {
spaceUuid.value = to.query.space
nowPageId.value = to.query.pageId
if (!!nowPageId.value) {
loadPageDetail(nowPageId.value)
}
}
const computeFileSize = (fileSize) => {
if (!fileSize) {
return '-'
}
let size = ''
if (fileSize < 0.1 * 1024) {
size = fileSize.toFixed(2) + 'B'
} else if (fileSize < 0.1 * 1024 * 1024) {
size = (fileSize / 1024).toFixed(2) + 'KB'
} else if (fileSize < 0.1 * 1024 * 1024 * 1024) {
size = (fileSize / (1024 * 1024)).toFixed(2) + 'MB'
} else {
size = (fileSize / (1024 * 1024 * 1024)).toFixed(2) + 'GB'
}
let sizeStr = size + ''
let index = sizeStr.indexOf('.')
let dou = sizeStr.substr(index + 1, 2)
if (dou == '00') {
return sizeStr.substring(0, index) + sizeStr.substr(index + 3, 2)
}
return size
}
const closeImagePreview = () => {
showImagePreview.value = false
}
let pageContentRef = ref();
const previewPageImage = () => {
const imgArr = []
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
setTimeout(() => initImageViewerMask.value(), 0)
}
})
}
let imagePreviewRef = ref();
const initImageViewerMask = () => {
// 图片预览遮罩点击隐藏预览框
let imageViewerMask = imagePreviewRef.value.querySelectorAll(
'.el-image-viewer__mask'
)
imageViewerMask.forEach((item) => {
item.onclick = () => (showImagePreview.value = false)
})
}
</script>
<style>
@import "../../../../common/lib/wangEditor.css";
@import '../../../../assets/lib/wangEditor.css';
.page-share-view-vue .wiki-title{font-size: 20px;text-align: center;}
.page-share-view-vue .wiki-author{font-size: 14px;color: #888;padding: 20px 0;height: 40px;line-height: 40px;}
.page-share-view-vue .wiki-title {
font-size: 20px;
text-align: center;
}
.page-share-view-vue .wiki-page-content img{cursor: pointer;max-width: 100%;}
.page-share-view-vue .wiki-page-content img:hover{box-shadow: 0 2px 6px 0 rgba(0,0,0,.3);}
.page-share-view-vue .wiki-author {
font-size: 14px;
color: #888;
padding: 20px 0;
height: 40px;
line-height: 40px;
}
.page-share-view-vue .upload-page-file .el-upload-list{display: none;}
.page-share-view-vue .is-link{color: #1e88e5;cursor: pointer;}
.page-share-view-vue .wiki-page-content img {
cursor: pointer;
max-width: 100%;
}
.page-share-view-vue .wiki-page-content img:hover {
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.3);
}
.page-share-view-vue .upload-page-file .el-upload-list {
display: none;
}
.page-share-view-vue .is-link {
color: #1e88e5;
cursor: pointer;
}
</style>

View File

@@ -0,0 +1,202 @@
<template>
<div class="comment-box" ref="actionTabCommentRef">
<div v-if="commentList.length <= 0" class="action-box-empty">
暂无评论
</div>
<div v-else class="comment-list">
<el-timeline>
<el-timeline-item :timestamp="comment.createTime" placement="top" v-for="comment in commentList">
<el-card class="box-card comment-card" :body-style="{ padding: '10px' }">
<div :style="'background-color: ' + comment.color" class="head">
{{ comment.createUserName.substr(0, 1) }}
</div>
<div class="comment-user-name">
{{ comment.createUserName }}
<el-popconfirm v-if="canDeleteComment(comment)"
placement="top" width="160" trigger="click"
confirm-button-text="删除"
cancel-button-text="取消"
@confirm="deleteComment(comment.id)"
title="确定要删除此评论吗?">
<template #reference>
<el-icon class="icon-delete"><ElIconDelete /></el-icon>
</template>
</el-popconfirm>
</div>
<pre class="comment-content">{{ comment.content }}</pre>
</el-card>
</el-timeline-item>
</el-timeline>
</div>
</div>
<div class="comment-input-box">
<textarea rows="5" placeholder="发表评论" v-model="commentTextInput"></textarea>
<el-button style="float: right; margin: 2px 5px" type="primary" size="small" v-on:click="submitPageComment">发送</el-button>
</div>
</template>
<script setup>
import {
Delete as ElIconDelete,
Loading as ElIconLoading,
} from '@element-plus/icons-vue'
import {toRefs, ref, reactive, onMounted, watch, defineProps, defineEmits, defineExpose, computed} from 'vue';
import {onBeforeRouteUpdate, useRoute, useRouter} from "vue-router";
import { ElMessageBox, ElMessage, ElNotification } from 'element-plus';
import pageApi from '@/assets/api/page'
import {useStorePageData} from "@/store/pageData";
import {useStoreUserData} from "@/store/userData";
let page = {
colorArr: ['#67C23A', '#409EFF', '#E6A23C', '#F56C6C', '#909399', '#303133'],
userHeadColor: {},
}
// 评论相关
let commentTextInput = ref('');
let commentList = ref([]);
let recommentInfo = ref({});
let route = useRoute();
let router = useRouter();
let storePage = useStorePageData();
let storeUser = useStoreUserData();
watch(() => storePage.pageInfo, (newVal) => {
loadCommentList();
})
onMounted(() => {
loadCommentList();
});
let actionTabCommentRef = ref();
const scrollActionTabComment = () => {
setTimeout(() => {
let actionTabComment = actionTabCommentRef.value
actionTabComment.scrollTop = actionTabComment.scrollHeight
}, 0)
}
const loadCommentList = () => {
if (!storePage.pageInfo || !storePage.pageInfo.id) {
return;
}
cancelCommentUser()
pageApi.pageCommentList({pageId: storePage.pageInfo.id}).then((json) => {
let commentListRes = json.data || []
for (let i = 0; i < commentListRes.length; i++) {
commentListRes[i].color = getUserHeadBgColor(commentListRes[i].createUserId)
let subCommentList = commentListRes[i].commentList || []
for (let j = 0; j < subCommentList.length; j++) {
let subItem = subCommentList[j]
subItem.color = getUserHeadBgColor.value(subItem.createUserId)
}
commentListRes[i].commentList = subCommentList
commentListRes[i].visible = false
}
commentList.value = commentListRes
scrollActionTabComment()
})
}
const recommentUser = (id, index) => {
recommentInfo.value = {
id: id,
index: index,
placeholder: '回复' + (index + 1) + '楼',
}
}
let canDeleteComment = (row) => {
return (
storeUser.userInfo.id === row.createUserId || storeUser.userInfo.id === storePage.pageInfo.createUserId
)
}
const deleteComment = (id) => {
pageApi.deletePageComment({id: id}).then(() => {
// ElMessage.success("删除成功!");
loadCommentList()
})
}
const cancelCommentUser = () => {
recommentInfo.value = {}
}
const submitPageComment = () => {
if (commentTextInput.value.length <= 0) {
ElMessage.error('请输入评论内容')
return
}
let param = {
pageId: storePage.pageInfo.id,
content: commentTextInput.value,
parentId: recommentInfo.value.id,
}
pageApi.updatePageComment(param).then((json) => {
let data = json.data
data.color = getUserHeadBgColor(data.createUserId)
commentTextInput.value = ''
loadCommentList()
})
}
const getUserHeadBgColor = (userId) => {
let color = page.userHeadColor[userId]
if (!color) {
color = page.colorArr[Math.ceil(Math.random() * page.colorArr.length) - 1]
page.userHeadColor[userId] = color
}
return color
}
</script>
<style lang="scss">
.comment-box {
padding: 8px;
height: calc(100vh - 100px);
overflow: auto;
.comment-list {
padding-bottom: 130px;
}
.comment-card {
.comment-user-name {
margin-bottom: 10px;
.icon-delete {
color: #888;
font-size: 13px;
cursor: pointer;
float: right;
display: none;
}
}
.comment-content {
padding: 0;
color: #666;
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
line-height: 20px;
}
}
.comment-card:hover {
.icon-delete {
display: inline-block;
}
}
}
.comment-input-box {
position: absolute;
bottom: 0;
width: 100%;
background: #fff;
border-top: 1px solid #f1f1f1;
textarea {
resize: none;
width: 100%;
box-sizing: border-box;
border: 0;
outline: none !important;
padding: 10px;
}
}
</style>

View File

@@ -0,0 +1,49 @@
<template>
<div class="action-tab-box">
<div v-if="pageHistoryList.length <= 0" class="action-box-empty">
暂无修改历史记录
</div>
<el-timeline v-else>
<el-timeline-item v-for="history in pageHistoryList">
<el-tag :type="pageHistoryChoice.id === history.id ? history.loading === 3 ? 'danger' : 'success' : 'info'" class="history-item" @click="historyClick(history)">
<div>{{ history.createUserName }}</div>
<div>{{ history.createTime }}</div>
</el-tag>
<el-icon class="history-loading-status" v-show="history.loading===1"><el-icon-loading/></el-icon>
<el-icon class="history-loading-status" v-show="history.loading===2"><el-icon-circle-check/></el-icon>
<el-icon class="history-loading-status" v-show="history.loading===3"><el-icon-circle-close/></el-icon>
</el-timeline-item>
</el-timeline>
</div>
</template>
<script setup>
import {
Delete as ElIconDelete,
Loading as ElIconLoading,
} from '@element-plus/icons-vue'
import {toRefs, ref, reactive, onMounted, watch, defineProps, defineEmits, defineExpose, computed} from 'vue';
import {onBeforeRouteUpdate, useRoute, useRouter} from "vue-router";
import { ElMessageBox, ElMessage, ElNotification } from 'element-plus';
import pageApi from '@/assets/api/page'
import {useStorePageData} from "@/store/pageData";
import {useStoreUserData} from "@/store/userData";
let route = useRoute();
let router = useRouter();
let storePage = useStorePageData();
let storeUser = useStoreUserData();
let pageHistoryDetail = ref('');
let pageHistoryChoice = ref({});
let pageHistoryList = ref([]);
let pageHistoryPageNum = ref(1);
watch(() => storePage.pageInfo, (newVal) => {
})
onMounted(() => {
});
</script>
<style lang="scss">
</style>

View File

@@ -1,62 +1,79 @@
<template>
<div style="min-height: 100%;" class="space-manage-vue">
<el-breadcrumb separator-class="el-icon-arrow-right" style="padding: 20px 10px;">
<el-breadcrumb separator-class="el-icon-arrow-right" style="padding: 20px 10px">
<el-breadcrumb-item>WIKI文档</el-breadcrumb-item>
<el-breadcrumb-item>空间管理</el-breadcrumb-item>
</el-breadcrumb>
<div style="max-width: 1200px;margin: 0 auto;background: #fff;padding: 20px;min-height: 100%;box-sizing: border-box;">
<div style="text-align: right;margin-bottom: 10px;">
<span style="float:left;line-height: 40px;">
仅展示我收藏的空间
<el-switch v-model="userSetting.wiki_only_show_favorite" inactive-value="0" active-value="1" @change="wikiOnlyShowFavoriteChange"></el-switch>
<el-tooltip class="item" effect="dark" content="控制左上角空间下拉列表仅展示我收藏的空间" placement="top-start">
<i class="el-icon-warning-outline" style="vertical-align: middle;margin-left: 10px;color: #999;"></i>
</el-tooltip>
</span>
<el-button @click="loadSpaceList" icon="refresh" :loading="spaceListLoading">刷新</el-button>
<el-button type="primary" @click="showCreateSpace" icon="el-icon-plus">创建空间</el-button>
<div style="text-align: right; margin-bottom: 10px">
<span style="float: left; line-height: 40px">
仅展示我收藏的空间
<el-switch v-model="userSetting.wiki_only_show_favorite" inactive-value="0" active-value="1" @change="wikiOnlyShowFavoriteChange"></el-switch>
<el-tooltip class="item" effect="dark" content="控制左上角空间下拉列表仅展示我收藏的空间" placement="top-start">
<el-icon style="vertical-align: middle; margin-left: 10px; color: #999"><el-icon-warning-outline/></el-icon>
</el-tooltip>
</span>
<el-button @click="loadSpaceList" :icon="ElIconRefresh" :loading="spaceListLoading">刷新</el-button>
<el-button type="primary" @click="showCreateSpace" :icon="ElIconPlus">创建空间</el-button>
</div>
<el-table :data="spaceList" border style="width: 100%; margin-bottom: 5px;">
<el-table :data="spaceList" border style="width: 100%; margin-bottom: 5px">
<el-table-column prop="id" label="ID" width="60"></el-table-column>
<el-table-column prop="name" label="名字"></el-table-column>
<el-table-column prop="spaceExplain" label="说明"></el-table-column>
<el-table-column label="开放地址">
<template slot-scope="scope">
<el-button type="text" @click="showOpenSpace(scope.row.uuid)" v-if="scope.row.openDoc == 1">{{scope.row.name}}</el-button>
<template v-slot="scope">
<el-link @click="showOpenSpace(scope.row.uuid)" v-if="scope.row.openDoc == 1">{{ scope.row.name }}</el-link>
<span v-else>暂未开放</span>
</template>
</el-table-column>
<el-table-column prop="createUserName" label="创建人"></el-table-column>
<el-table-column prop="createTime" label="创建时间"></el-table-column>
<el-table-column prop="favorite" label="收藏" width="60">
<template slot-scope="scope">
<i class="el-icon-star-on favorite-icon" v-if="scope.row.favorite == 1" @click="updateSpaceFavorite(scope.row)"></i>
<i class="el-icon-star-off favorite-icon" v-else @click="updateSpaceFavorite(scope.row)"></i>
<template v-slot="scope">
<el-icon @click="updateSpaceFavorite(scope.row)" v-if="scope.row.favorite === 1" class="favorite-icon el-icon-star-on">
<el-icon-star-on/>
</el-icon>
<el-icon @click="updateSpaceFavorite(scope.row)" v-else class="favorite-icon el-icon-star-off">
<el-icon-star-off/>
</el-icon>
</template>
</el-table-column>
<el-table-column label="操作" width="220">
<template slot-scope="scope" v-if="userSelfInfo.id == scope.row.createUserId">
<el-button size="small" type="primary" v-on:click="editSpaceInfo(scope.row)">编辑</el-button>
<el-button size="small" type="warning" v-on:click="editSpaceAuth(scope.row)">授权</el-button>
<el-button size="small" type="danger" v-on:click="deleteSpaceInfo(scope.row)">删除</el-button>
<template v-slot="scope">
<template v-if="userSelfInfo.id == scope.row.createUserId">
<el-button size="small" type="primary" v-on:click="editSpaceInfo(scope.row)">编辑</el-button>
<el-button size="small" type="warning" v-on:click="editSpaceAuth(scope.row)">授权</el-button>
<el-button size="small" type="danger" v-on:click="deleteSpaceInfo(scope.row)">删除</el-button>
</template>
</template>
</el-table-column>
</el-table>
<div class="page-info-box">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:page-sizes="[10, 30, 50]"
:page-size="10"
:current-page="searchParam.pageNum"
layout="prev, pager, next, jumper, sizes, total"
:total="totalCount">
</el-pagination>
</div>
</div>
<!--分组权限弹窗-->
<el-dialog title="权限管理" :visible.sync="spaceAuthDialogVisible" width="900px">
<el-dialog title="权限管理" v-model="spaceAuthDialogVisible" width="900px" :close-on-click-modal="false">
<el-row>
<el-select v-model="spaceAuthNewGroupId" filterable placeholder="请选择分组" style="width: 750px;margin-right: 10px;">
<el-select v-model="spaceAuthNewGroupId" filterable placeholder="请选择分组" style="width: 750px; margin-right: 10px">
<el-option v-for="item in searchGroupList" :key="item.id" :label="searchGroupMap[item.id]" :value="item.id"></el-option>
</el-select>
<el-button v-on:click="addSpaceAuthUserGroup">添加</el-button>
</el-row>
<el-table :data="spaceAuthGroupList" border style="width: 100%; margin: 10px 0;">
<el-table :data="spaceAuthGroupList" border style="width: 100%; margin: 10px 0">
<el-table-column prop="groupId" label="分组名" width="150">
<template slot-scope="scope">{{searchGroupMap[scope.row.groupId]}}</template>
<template v-slot="scope">{{searchGroupMap[scope.row.groupId]}}</template>
</el-table-column>
<el-table-column label="权限">
<template slot-scope="scope">
<template v-slot="scope">
<el-checkbox :true-label="1" :false-label="0" v-model="scope.row.editPage">编辑</el-checkbox>
<el-checkbox :true-label="1" :false-label="0" v-model="scope.row.deletePage">删除</el-checkbox>
<el-checkbox :true-label="1" :false-label="0" v-model="scope.row.pageFileUpload">文件上传</el-checkbox>
@@ -65,190 +82,268 @@
</template>
</el-table-column>
<el-table-column label="操作" width="80">
<template slot-scope="scope">
<template v-slot="scope">
<el-button size="small" type="danger" plain @click="deleteGroupSpaceAuth(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div style="text-align: right;">
<div style="text-align: right">
<el-button v-on:click="manageUserGroup">分组管理</el-button>
<el-button type="primary" v-on:click="saveGroupSpaceAuth">保存配置</el-button>
</div>
</el-dialog>
<create-space ref="createSpace" @success="loadSpaceList"></create-space>
<create-space ref="createSpaceRef" @success="loadSpaceList"></create-space>
</div>
</template>
<script>
import pageApi from '../../common/api/page'
import userApi from '../../common/api/user'
import CreateSpace from '../../components/space/CreateSpace'
<script setup>
import {onBeforeUnmount, ref, onMounted, watch, defineProps, nextTick, defineEmits, defineExpose, computed} from 'vue';
import {onBeforeRouteUpdate, useRouter, useRoute} from "vue-router";
import {ElMessageBox, ElMessage} from 'element-plus'
import {
Warning as ElIconWarningOutline,
StarFilled as ElIconStarOn,
Star as ElIconStarOff,
Refresh as ElIconRefresh,
Plus as ElIconPlus,
} from '@element-plus/icons-vue'
import pageApi from '../../assets/api/page'
import userApi from '../../assets/api/user'
import CreateSpace from '../../components/space/CreateSpace'
export default {
data() {
return {
spaceListLoading: false,
spaceOptions: [],
spaceList:[],
choiceSpace: "",
nowSpaceShow: {},
newSpaceDialogVisible: false,
manageSpaceDialogVisible: false,
newSpaceForm: {id: '', name: '', spaceExplain: '', treeLazyLoad: 0, openDoc: 0, uuid: '', type: 1},
newSpaceFormRules: {
name: [
{required: true, message: '请输入空间名', trigger: 'blur'},
{min: 2, max: 25, message: '长度在 2 到 25 个字符', trigger: 'blur'}
],
},
userSelfInfo: {},
// 空间授权
editSpaceId: '',
spaceAuthDialogVisible: false,
spaceAuthNewGroupId: '',
spaceAuthGroupLoading: false,
searchGroupList: [],
searchGroupMap: {},
spaceAuthGroupList: [],
// 设置
userSetting: {
wiki_only_show_favorite: 0,
},
};
},
components: {
"create-space": CreateSpace,
},
mounted() {
this.loadSpaceList();
this.getSelfUserInfo();
this.getSpaceSettingList();
},
methods: {
showOpenSpace(space) {
let routeUrl = this.$router.resolve({path: '/page/share/home', query: {space: space}});
window.open(routeUrl.href, '_blank');
},
showCreateSpace() {
this.$refs.createSpace.show();
},
editSpaceInfo(row) {
this.$refs.createSpace.show(row.id);
},
addSpaceAuthUserGroup() {
if (!this.spaceAuthNewGroupId) {
this.$message.warning("请先选择分组");
return;
}
if (!!this.spaceAuthGroupList.find(item => item.groupId == this.spaceAuthNewGroupId)) {
this.spaceAuthNewGroupId = "";
return;
}
this.spaceAuthGroupList.push({
groupId: this.spaceAuthNewGroupId,
editPage: 0,
commentPage: 0,
deletePage: 0,
pageFileUpload: 0,
pageFileDelete: 0,
pageAuthManage: 0,
});
this.spaceAuthNewGroupId = '';
},
updateSpaceFavorite(row) {
let delFlag = (row.favorite == 1) ? 1 : 0;
pageApi.spaceFavoriteUpdate({spaceId: row.id, delFlag: delFlag}).then(json => {
row.favorite = (row.favorite == 1) ? 0 : 1;
});
},
saveGroupSpaceAuth() {
let param = {spaceId: this.editSpaceId, authList: JSON.stringify(this.spaceAuthGroupList)};
pageApi.spaceAuthAssign(param).then(json => {
this.$message.success("授权成功!");
});
},
manageUserGroup() {
let manageUrl = location.href.substring(0, location.href.indexOf("/doc-wiki")) + '#/console/userGroupList';
window.open(manageUrl, '_blank');
},
deleteGroupSpaceAuth(row) {
this.spaceAuthGroupList = this.spaceAuthGroupList.filter(item => item.groupId != row.groupId);
},
editSpaceAuth(row) {
this.editSpaceId = row.id;
this.spaceAuthNewGroupId = '';
this.spaceAuthGroupList = [];
userApi.userGroupList().then(json => {
this.searchGroupList = json.data || [];
this.searchGroupList.forEach(item => this.searchGroupMap[item.id] = item.name);
});
pageApi.spaceAuthList({spaceId: row.id}).then(json => {
this.spaceAuthGroupList = json.data || [];
this.spaceAuthDialogVisible = true;
});
},
deleteSpaceInfo(row) {
this.$confirm('确定要删除此空间及下面的所有文档吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
let param = {id: row.id, delFlag: 1};
pageApi.updateSpace(param).then(() => {
this.$message.success("删除成功");
this.loadSpaceList();
this.$emit('loadSpace');
});
});
},
loadSpaceList() {
this.spaceListLoading = true;
pageApi.spaceList({ignoreFavorite: 1}).then(json => {
this.spaceList = json.data || [];
setTimeout(() => this.spaceListLoading = false, 500);
});
},
wikiOnlyShowFavoriteChange() {
let param = {name: 'wiki_only_show_favorite', value: this.userSetting.wiki_only_show_favorite};
pageApi.spaceSettingUpdate(param).then(json => {
this.$emit('loadSpace');
});
},
getSpaceSettingList() {
pageApi.spaceSettingList().then(json => {
let result = json.data || {};
this.userSetting = {
wiki_only_show_favorite: result.wiki_only_show_favorite || 0,
};
});
},
getSelfUserInfo() {
userApi.getSelfUserInfo().then(json=>{
this.userSelfInfo = json.data;
});
},
let spaceListLoading = ref(false);
let spaceOptions = ref([]);
let spaceList = ref([]);
let choiceSpace = ref('');
let nowSpaceShow = ref({});
let newSpaceForm = ref({id: '', name: '', spaceExplain: '', treeLazyLoad: 0, openDoc: 0, uuid: '', type: 1,});
let userSelfInfo = ref({});
// 空间授权
let editSpaceId = ref('');
let spaceAuthDialogVisible = ref(false);
let spaceAuthNewGroupId = ref('');
let searchGroupList = ref([]);
let searchGroupMap = ref({});
let spaceAuthGroupList = ref([]);
// 设置
let userSetting = ref({wiki_only_show_favorite: 0,});
let route = useRoute();
let router = useRouter();
let emit = defineEmits('loadSpace');
onMounted(() => {
loadSpaceList()
getSelfUserInfo()
getSpaceSettingList()
});
const showOpenSpace = (space) => {
let routeUrl = router.resolve({
path: '/page/share/home',
query: {space: space}
})
window.open(routeUrl.href, '_blank')
}
let createSpaceRef = ref();
const showCreateSpace = () => {
createSpaceRef.value.show()
}
const editSpaceInfo = (row) => {
createSpaceRef.value.show(row.id)
}
const addSpaceAuthUserGroup = () => {
if (!spaceAuthNewGroupId.value) {
ElMessage.warning('请先选择分组')
return
}
if (
!!spaceAuthGroupList.value.find(
(item) => item.groupId == spaceAuthNewGroupId.value
)
) {
spaceAuthNewGroupId.value = ''
return
}
spaceAuthGroupList.value.push({
groupId: spaceAuthNewGroupId.value,
editPage: 0,
commentPage: 0,
deletePage: 0,
pageFileUpload: 0,
pageFileDelete: 0,
pageAuthManage: 0,
})
spaceAuthNewGroupId.value = ''
}
const updateSpaceFavorite = (row) => {
let delFlag = row.favorite == 1 ? 1 : 0
pageApi.spaceFavoriteUpdate({spaceId: row.id, delFlag: delFlag}).then((json) => {
row.favorite = row.favorite == 1 ? 0 : 1
})
}
const saveGroupSpaceAuth = () => {
let param = {
spaceId: editSpaceId.value,
authList: JSON.stringify(spaceAuthGroupList.value),
}
pageApi.spaceAuthAssign(param).then((json) => {
ElMessage.success('授权成功!')
})
}
const manageUserGroup = () => {
let manageUrl = location.href.substring(0, location.href.indexOf('/doc-wiki')) + '#/console/userGroupList'
window.open(manageUrl, '_blank')
}
const deleteGroupSpaceAuth = (row) => {
spaceAuthGroupList.value = spaceAuthGroupList.value.filter(
(item) => item.groupId != row.groupId
)
}
const editSpaceAuth = (row) => {
editSpaceId.value = row.id
spaceAuthNewGroupId.value = ''
spaceAuthGroupList.value = []
userApi.userGroupList().then((json) => {
searchGroupList.value = json.data || []
searchGroupList.value.forEach((item) => (searchGroupMap.value[item.id] = item.name))
})
pageApi.spaceAuthList({spaceId: row.id}).then((json) => {
spaceAuthGroupList.value = json.data || []
spaceAuthDialogVisible.value = true
})
}
const deleteSpaceInfo = (row) => {
ElMessageBox.confirm('确定要删除此空间及下面的所有文档吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
let param = {id: row.id, delFlag: 1}
pageApi.updateSpace(param).then(() => {
ElMessage.success('删除成功')
loadSpaceList()
emit('loadSpace')
})
})
}
let totalCount = ref(0);
let searchParam = ref({
ignoreFavorite: 1,
pageNum: 1,
pageSize: 10,
});
const loadSpaceList = () => {
spaceListLoading.value = true
pageApi.spaceList(searchParam.value).then((json) => {
spaceList.value = json.data || []
if (searchParam.value.pageNum === 1) {
totalCount.value = json.total;
}
setTimeout(() => (spaceListLoading.value = false), 500)
})
}
const handleSizeChange = (val) => {
searchParam.value.pageSize = val
loadSpaceList()
}
const handleCurrentChange = (val) => {
searchParam.value.pageNum = val
loadSpaceList()
}
const wikiOnlyShowFavoriteChange = () => {
let param = {
name: 'wiki_only_show_favorite',
value: userSetting.value.wiki_only_show_favorite,
}
pageApi.spaceSettingUpdate(param).then((json) => {
emit('loadSpace')
})
}
const getSpaceSettingList = () => {
pageApi.spaceSettingList().then((json) => {
let result = json.data || {}
userSetting.value = {
wiki_only_show_favorite: result.wiki_only_show_favorite || 0,
}
})
}
const getSelfUserInfo = () => {
userApi.getSelfUserInfo().then((json) => {
userSelfInfo.value = json.data
})
}
</script>
<style>
.space-manage-vue .empty-news{text-align: center;padding: 100px;}
.space-manage-vue .text-link {
color: #444;
/*cursor: pointer;*/
/*font-weight: bold;*/
<style lang="scss">
.space-manage-vue {
.page-info-box {
margin-top: 10px;
.el-pagination {
justify-content: end;
}
.space-manage-vue .line-box{color: #666;border-bottom: 1px solid #eee;padding: 20px 0;}
.space-manage-vue .line-title{font-size: 14px;}
.space-manage-vue .page-preview-box{}
.space-manage-vue .page-preview-title{font-size: 18px;margin: 10px 0 5px 0;color: #3a8ee6;cursor: pointer;}
.space-manage-vue .page-preview-content{font-size: 16px;margin-bottom: 5px;}
.space-manage-vue .zan-img{vertical-align: middle;margin-top: -3px;}
.space-manage-vue .view-img{font-size: 16px;color: #666;}
.space-manage-vue .page-info-box{text-align: right;margin: 20px 0 50px 0;}
.space-manage-vue .favorite-icon{cursor: pointer; font-size: 20px;}
.space-manage-vue .favorite-icon.el-icon-star-on{color: #E6A23C; font-size: 24px;}
}
}
</style>
<style>
.space-manage-vue .empty-news {
text-align: center;
padding: 100px;
}
.space-manage-vue .text-link {
color: #444;
}
.space-manage-vue .line-box {
color: #666;
border-bottom: 1px solid #eee;
padding: 20px 0;
}
.space-manage-vue .line-title {
font-size: 14px;
}
.space-manage-vue .page-preview-box {
}
.space-manage-vue .page-preview-title {
font-size: 18px;
margin: 10px 0 5px 0;
color: #3a8ee6;
cursor: pointer;
}
.space-manage-vue .page-preview-content {
font-size: 16px;
margin-bottom: 5px;
}
.space-manage-vue .zan-img {
vertical-align: middle;
margin-top: -3px;
}
.space-manage-vue .view-img {
font-size: 16px;
color: #666;
}
.space-manage-vue .page-info-box {
text-align: right;
margin: 20px 0 50px 0;
}
.space-manage-vue .favorite-icon {
cursor: pointer;
font-size: 20px;
}
.space-manage-vue .favorite-icon.el-icon-star-on {
color: #e6a23c;
font-size: 24px;
}
</style>

View File

@@ -1,44 +1,45 @@
<template>
<div class="my-info-vue">
<div style="margin: 0 auto;max-width: 1000px;">
<el-card class="box-card">
<div slot="header" class="clearfix">我的信息</div>
<el-form class="search-form-box" label-width="100px">
<el-form-item label="账号:">{{userInfo.userNo}}</el-form-item>
<el-form-item label="用户名:">{{userInfo.userName}}</el-form-item>
<el-form-item label="手机号:">{{userInfo.phone}}</el-form-item>
<el-form-item label="邮箱">{{userInfo.email}}</el-form-item>
<el-form-item label="状态">{{userInfo.delFlag==0?'正常':'停用'}}</el-form-item>
<el-form-item label="性别">{{userInfo.sex==0?'女':'男'}}</el-form-item>
</el-form>
</el-card>
</div>
</div>
<div class="my-info-vue">
<div style="margin: 0 auto; max-width: 1000px">
<el-card class="box-card">
<template v-slot:header>
<div class="clearfix">我的信息</div>
</template>
<el-form class="search-form-box" label-width="100px">
<el-form-item label="号:">{{ userInfo.userNo }}</el-form-item>
<el-form-item label="用户名">{{ userInfo.userName }}</el-form-item>
<el-form-item label="手机号">{{ userInfo.phone }}</el-form-item>
<el-form-item label="邮箱">{{ userInfo.email }}</el-form-item>
<el-form-item label="状态:">{{userInfo.delFlag == 0 ? '正常' : '停用'}}</el-form-item>
<el-form-item label="性别:">{{userInfo.sex == 0 ? '女' : '男'}}</el-form-item>
</el-form>
</el-card>
</div>
</div>
</template>
<script>
import userApi from '../../common/api/user'
export default {
data() {
return {
userInfo: {}
};
},
mounted: function () {
this.getUserInfo();
},
methods: {
getUserInfo() {
userApi.getSelfUserInfo().then(json => {
this.userInfo = json.data;
});
},
}
}
<script setup>
import {onBeforeUnmount, ref, onMounted, watch, defineProps, nextTick, defineEmits, defineExpose, computed} from 'vue';
import {onBeforeRouteUpdate, useRouter, useRoute} from "vue-router";
import {ElMessageBox, ElMessage} from 'element-plus'
import userApi from '../../assets/api/user'
let userInfo = ref({});
onMounted(() => {
getUserInfo();
});
const getUserInfo = () => {
userApi.getSelfUserInfo().then((json) => {
userInfo.value = json.data
})
}
</script>
<style>
.my-info-vue{}
.my-info-vue .box-card{margin: 10px;}
</style>
<style>
.my-info-vue {
}
.my-info-vue .box-card {
margin: 10px;
}
</style>