空间设置和回收站功能开发

This commit is contained in:
sswiki
2025-05-24 23:23:11 +08:00
parent e54761b24b
commit 56175e30ec
17 changed files with 622 additions and 48 deletions

View File

@@ -87,7 +87,7 @@ public class WikiPage implements Serializable {
private Date updateTime;
/**
* 0=有效 1=删除
* 0=有效 1=删除 2=永久删除
*/
private Integer delFlag;

View File

@@ -77,7 +77,7 @@ public class WikiPageServiceImpl extends ServiceImpl<WikiPageMapper, WikiPage> i
}
private void deletePageAndSon(WikiPage wikiPage) {
wikiPage.setDelFlag(1);
wikiPage.setDelFlag(wikiPage.getDelFlag());
this.updateById(wikiPage);
QueryWrapper<WikiPage> wrapper = new QueryWrapper<>();

View File

@@ -0,0 +1,10 @@
package org.dromara.zyplayer.data.service.params;
import lombok.Data;
@Data
public class PageDeleteParam {
private Long pageId;
private Integer delFlag;
private Long spaceId;
}

View File

@@ -0,0 +1,18 @@
package org.dromara.zyplayer.data.service.params;
import lombok.Data;
import java.io.Serializable;
/**
* 回收站列表参数
*
* @author 暮光:城中城
* @since 2023-05-01
*/
@Data
public class PageRecycleListParam implements Serializable {
private Long spaceId;
private Long pageNum;
}

View File

@@ -0,0 +1,9 @@
package org.dromara.zyplayer.data.service.params;
import lombok.Data;
@Data
public class RecycleDeletePageParam {
private String pageIds;
private Long spaceId;
}

View File

@@ -0,0 +1,8 @@
package org.dromara.zyplayer.data.service.params;
import lombok.Data;
@Data
public class RestorePageParam {
private String pageIds;
}

View File

@@ -6,6 +6,9 @@ export default {
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)}),
recyclePageListList: data => request({url: '/zyplayer-doc-wiki/page/recycleList', method: 'post', data: Qs.stringify(data)}),
pageRestore: data => request({url: '/zyplayer-doc-wiki/page/restore', method: 'post', data: Qs.stringify(data)}),
recycleDeletePage: data => request({url: '/zyplayer-doc-wiki/page/recycleDelete', method: 'post', data: Qs.stringify(data)}),
copyPage: (data) => request({url: '/zyplayer-doc-wiki/page/copy', method: 'post', data: Qs.stringify(data)}),
movePage: (data) => request({url: '/zyplayer-doc-wiki/page/move', method: 'post', data: Qs.stringify(data)}),
renamePage: (data) => request({url: '/zyplayer-doc-wiki/page/rename', method: 'post', data: Qs.stringify(data)}),

View File

@@ -12,6 +12,8 @@ import NoAuth from './views/common/NoAuth.vue';
// import Home from './views/home/Home.vue';
// import MyInfo from './views/user/MyInfo.vue';
import Show from './views/view/View.vue';
import Setting from './views/view/Setting.vue';
import Recycle from './views/view/Recycle.vue';
import Edit from './views/view/Edit.vue';
// import spaceManage from './views/space/Manage.vue';
@@ -43,10 +45,12 @@ export default createRouter({
component: PageLayout,
children: [
// {path: '/home', name: 'WIKI文档管理', component: NoAuth},
{path: '/user/myInfo', name: 'WIKI-我的信息', component: NoAuth},
{path: '/view/:spaceId?/:pageId?', name: 'WIKI-页面查看', component: Show},
{path: '/edit/:spaceId/:pageId', name: 'WIKI-编辑内容', component: Edit},
{path: '/space/manage', name: 'WIKI-空间管理', component: NoAuth},
{path: '/user/myInfo', name: '我的信息', component: NoAuth},
{path: '/view/:spaceId?/:pageId?', name: '页面查看', component: Show},
{path: '/edit/:spaceId/:pageId', name: '页面编辑', component: Edit},
{path: '/view/setting/:spaceId', name: '空间设置', component: Setting},
{path: '/view/recycle/:spaceId', name: '回收站', component: Recycle},
{path: '/space/manage', name: '空间管理', component: NoAuth},
],
},
{

View File

@@ -0,0 +1,144 @@
<template>
<div class="space-page-recycle-box">
<a-flex align="center" justify="space-between" style="margin-bottom: 10px;">
<a-space>
<a-button @click="restorePageSelected" :disabled="selectedRowKeys.length <= 0" :icon="h(UndoOutlined)">恢复</a-button>
<a-button @click="deletePageSelected" :disabled="selectedRowKeys.length <= 0" :icon="h(DeleteOutlined)">彻底删除</a-button>
</a-space>
<a-button @click="loadPageList" type="primary" :icon="h(SearchOutlined)">查询</a-button>
</a-flex>
<a-table :dataSource="recyclePageList" :columns="listColumns" :loading="recycleListLoading"
:row-selection="{ selectedRowKeys: selectedRowKeys, onChange: selectionChange }" rowKey="id"
:pagination="paginationConfig" @change="recyclePageSortChange"
size="middle" :scroll="{ y: 'calc(100vh - 220px)' }">
<template #bodyCell="{ column, text, record }">
<template v-if="column.dataIndex === 'actions'">
<a-space>
<a-button @click="restorePage(record)" :icon="h(UndoOutlined)">恢复</a-button>
<a-button @click="deletePage(record)" :icon="h(DeleteOutlined)">彻底删除</a-button>
</a-space>
</template>
</template>
</a-table>
</div>
</template>
<script setup>
import { UndoOutlined, DeleteOutlined, SearchOutlined } from '@ant-design/icons-vue';
import pageApi from "@/assets/api/page";
import {toRefs, ref, reactive, onMounted, onBeforeUnmount, watch, h, defineEmits, computed} from 'vue';
import {onBeforeRouteUpdate, onBeforeRouteLeave, useRouter, useRoute} from "vue-router";
import { message } from 'ant-design-vue';
import { Modal } from 'ant-design-vue';
import { useStoreDisplay } from '@/store/wikiDisplay.js'
import { useStorePageData } from '@/store/pageData.js'
let route = useRoute();
let router = useRouter();
let storeDisplay = useStoreDisplay();
let storePage = useStorePageData();
onMounted(() => {
storeDisplay.currentPage = 'recycle';
storeDisplay.headerShow = true;
loadPageList();
});
onBeforeUnmount(() => {
storeDisplay.currentPage = '';
});
let recycleListParam = ref({
pageNum: 1,
});
let selectedRowKeys = ref([]);
const selectionChange = (rowKeys, selectedRows) => {
selectedRowKeys.value = rowKeys;
}
let recycleListTotalCount = ref(0);
let recycleListLoading = ref(false);
let recyclePageList = ref([]);
const loadPageList = () => {
recycleListLoading.value = true;
let param = {spaceId: route.params.spaceId, ...recycleListParam.value};
pageApi.recyclePageListList(param).then(json => {
recyclePageList.value = json.data || [];
if (recycleListParam.value.pageNum === 1) {
recycleListTotalCount.value = json.total || 0;
}
recycleListLoading.value = false;
}).catch(() => {
recycleListLoading.value = false;
});
}
watch(() => recycleListParam.value.pageNum, loadPageList);
const restorePageSelected = () => {
restorePageConfirm(selectedRowKeys.value.join(','));
}
const deletePageSelected = () => {
deletePageConfirm(selectedRowKeys.value.join(','), route.params.spaceId);
}
const restorePage = (row) => {
restorePageConfirm(row.id);
}
const deletePage = (row) => {
deletePageConfirm(row.id, row.spaceId);
}
const restorePageConfirm = (ids) => {
Modal.confirm({
maskClosable: true,
title: '提示',
content: '确定要恢复此页面吗?如果该页面的父页面也被删除,则需父页面恢复后才可看见此页面',
okText: '确认恢复',
cancelText: '取消',
onOk: () => {
pageApi.pageRestore({pageIds: ids}).then(json => {
loadPageList();
message.success('已恢复');
storePage.eventPageUpdate = !storePage.eventPageUpdate;
});
}
});
}
const deletePageConfirm = (ids, spaceId) => {
Modal.confirm({
maskClosable: true,
title: '提示',
content: '确定要彻底删除此页面吗?',
okText: '彻底删除',
cancelText: '取消',
onOk: () => {
pageApi.recycleDeletePage({pageIds: ids, spaceId: spaceId}).then(() => {
loadPageList();
message.success('已删除');
});
}
});
}
let paginationConfig = ref({
total: 0,
current: 1,
pageSize: 20,
showSizeChanger: true,
pageSizeOptions: ['20', '50', '100'],
showTotal: total => `${total}`
});
const listColumns = computed(() => ([
{title: '名字', dataIndex: 'name'},
{title: '删除时间', dataIndex: 'updateTime', width: 180},
{title: '操作人', dataIndex: 'updateUserName', width: 180},
{title: '', dataIndex: 'actions', width: 280}
]));
const recyclePageSortChange = (pagination, filters, sorter) => {
recycleListParam.value.pageNum = pagination.current;
recycleListParam.value.pageSize = pagination.pageSize;
paginationConfig.value.current = pagination.current;
paginationConfig.value.pageSize = pagination.pageSize;
loadPageList();
}
</script>
<style scoped lang="scss">
.space-page-recycle-box {
padding: 10px 20px 0;
}
</style>

View File

@@ -0,0 +1,44 @@
<template>
<div class="setting-box">
<a-tabs v-model:activeKey="activeTabName">
<a-tab-pane tab="基本信息" key="base">
<BaseInfo></BaseInfo>
</a-tab-pane>
<a-tab-pane tab="更多" key="more">
<MoreAction></MoreAction>
</a-tab-pane>
</a-tabs>
</div>
</template>
<script setup>
import BaseInfo from './setting/BaseInfo.vue'
import MoreAction from './setting/MoreAction.vue'
import { message } from 'ant-design-vue';
import {toRefs, ref, reactive, onMounted, onBeforeUnmount, watch, defineEmits, computed} from 'vue';
import {useRouter, useRoute} from "vue-router";
import { useStoreDisplay } from '@/store/wikiDisplay'
import { useStorePageData } from '@/store/pageData'
let route = useRoute();
let router = useRouter();
let storeDisplay = useStoreDisplay();
let storePage = useStorePageData();
onMounted(() => {
storeDisplay.headerShow = true;
storeDisplay.currentPage = 'setting';
});
onBeforeUnmount(() => {
storeDisplay.currentPage = '';
});
let activeTabName = ref('base');
</script>
<style scoped>
.setting-box {
padding: 0 20px;
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<a-dropdown :trigger="['click']">
<a-dropdown :trigger="['click']" placement="bottom">
<a-button :icon="h(PlusOutlined)" type="text" style="color: #888;"></a-button>
<template #overlay>
<a-menu>

View File

@@ -29,15 +29,13 @@
<div class="space-folder-box" v-if="!props.readOnly">
<a-flex align="center" justify="space-between">
<div class="page-group-tag">
<el-tooltip :content="spaceTreeIsClose?'点击展开目录':'点击收起目录'" placement="top">
<span class="label" @click="changeDropdownStatus">空间目录</span>
</el-tooltip>
<span class="label">空间目录</span>
</div>
<AddMenu/>
</a-flex>
</div>
</div>
<div v-show="!spaceTreeIsClose" class="wiki-page-tree-box">
<div class="wiki-page-tree-box">
<el-tree ref="wikiPageTreeRef" :current-node-key="props.nowPageId" :data="storePage.pageList"
:default-expanded-keys="wikiPageExpandedKeys" :expand-on-click-node="true"
:props="defaultProps" :draggable="!props.readOnly"
@@ -65,7 +63,7 @@
<!--操作-->
<div @click.stop class="page-action-box">
<AddMenu :pageId="data.id"/>
<a-dropdown :trigger="['click']" @click="choosePageIdFunc(data.id)">
<a-dropdown @click="choosePageIdFunc(data.id)" :trigger="['click']" placement="bottom">
<a-button :icon="h(EllipsisOutlined)" type="text" style="color: #888;"></a-button>
<template #overlay>
<a-menu>
@@ -98,6 +96,19 @@
</template>
</el-tree>
</div>
<div class="wiki-page-footer">
<div style="flex: 1;">
<a-tooltip title="空间设置" placement="top">
<a-button @click="openSetting" :icon="h(SettingOutlined)" class="footer-btn">设置</a-button>
</a-tooltip>
</div>
<el-divider direction="vertical" style="margin-top: 8px;"/>
<div style="flex: 1;">
<a-tooltip title="回收站" placement="top">
<a-button @click="openRecycle" :icon="h(DeleteOutlined)" class="footer-btn">回收站</a-button>
</a-tooltip>
</div>
</div>
</div>
</template>
@@ -113,7 +124,7 @@ import {
EditTwo as IconParkEditTwo,
PageTemplate as IconParkPageTemplate,
} from '@icon-park/vue-next'
import { EllipsisOutlined, HomeOutlined } from '@ant-design/icons-vue';
import { EllipsisOutlined, HomeOutlined, SettingOutlined, DeleteOutlined } from '@ant-design/icons-vue';
import {ref, defineProps, defineEmits, defineExpose, onMounted, h, watch} from 'vue';
import {useRouter, useRoute} from "vue-router";
import pageApi from '@/assets/api/page';
@@ -276,6 +287,9 @@ const doGetPageList = () => {
pageApi.pageList({spaceId: spaceId}).then((json) => {
storePage.pageList = json.data || [];
if (route.path.startsWith('/view')) {
if (route.path.startsWith('/view/setting') || route.path.startsWith('/view/recycle')) {
return;
}
// 查看页面
if (storePage.pageList.length <= 0) {
router.push({path: `/view/${spaceId}`});
@@ -308,10 +322,6 @@ const handleSearchKeywordsSelect = (item) => {
searchKeywords.value = '';
router.push({path: `/view/${item.spaceId}/${item.pageId}`});
}
let spaceTreeIsClose = ref(false);
const changeDropdownStatus = () => {
spaceTreeIsClose.value = !spaceTreeIsClose.value;
}
const searchByKeywords = () => {
wikiPageTreeRef.value.filter(searchKeywords.value);
}
@@ -337,6 +347,14 @@ const handlePageDrop = (draggingNode, dropNode, dropType, ev) => {
doGetPageList();
});
}
// 打开设置页面
let openSetting = () => {
router.push({path: `/view/setting/${storeSpace.chooseSpaceId}`});
}
// 打开回收站页面
let openRecycle = () => {
router.push({path: `/view/recycle/${storeSpace.chooseSpaceId}`});
}
defineExpose({searchByKeywords});
</script>
@@ -379,15 +397,10 @@ defineExpose({searchByKeywords});
font-size: 12px;
padding: 4px 8px;
color: #888;
cursor: pointer;
line-height: 32px;
height: 32px;
border-radius: 3px;
box-sizing: border-box;
&:hover {
background: #eee;
}
}
}
}
@@ -396,6 +409,7 @@ defineExpose({searchByKeywords});
overflow-y: auto;
overflow-x: hidden;
padding-bottom: 10px;
height: calc(100vh - 140px);
.el-tree-node__content {
height: 35px;
@@ -437,6 +451,29 @@ defineExpose({searchByKeywords});
}
}
}
.wiki-page-footer {
padding: 5px;
display: flex;
border-top: 1px solid #eee;
.footer-btn {
width: 100%;
border: 0;
color: #666;
background: #fafafa;
box-shadow: unset;
&:focus {
background: #fafafa;
color: #666;
}
&:hover {
background: #eaeaea;
}
}
}
}
.search-autocomplete-popper {

View File

@@ -17,6 +17,12 @@
<div class="time">最近修改{{storePage.pageInfo.updateTime || ''}}</div>
</template>
</div>
<div v-if="storeDisplay.currentPage === 'setting'" class="title-setting-box">
<div class="setting-title">空间设置</div>
</div>
<div v-else-if="storeDisplay.currentPage === 'recycle'" class="title-setting-box">
<div class="setting-title">回收站</div>
</div>
</div>
</el-col>
<el-col :span="12" style="text-align: right;">
@@ -51,9 +57,8 @@
<template #overlay>
<a-menu>
<a-menu-item @click="showAbout">关于</a-menu-item>
<a-menu-item @click="showConsole">控制台</a-menu-item>
<a-menu-divider />
<a-menu-item @click="userSignOut">退出登录</a-menu-item>
<a-menu-item @click="userSignOut" danger>退出登录</a-menu-item>
</a-menu>
</template>
</a-dropdown>

View File

@@ -0,0 +1,66 @@
<template>
<div class="setting-base-info-box">
<a-card title="基本信息">
<template #extra>
<a-button @click="onSpaceUpdateSubmit" type="primary">保存设置</a-button>
</template>
<a-form :model="spaceForm" :rules="spaceFormRules" ref="spaceFormRef" @submit.prevent :label-col="{span: 4}" :wrapper-col="{span: 20}">
<a-form-item label="空间名" name="name">
<a-input v-model:value="spaceForm.name" placeholder="请输入空间名" :maxlength="25"></a-input>
</a-form-item>
<a-form-item label="空间说明" name="spaceExplain">
<a-textarea v-model:value="spaceForm.spaceExplain" placeholder="请输入空间说明" :maxlength="250" :autoSize="{ minRows: 6}" type="textarea" resize="none"/>
</a-form-item>
</a-form>
</a-card>
</div>
</template>
<script setup>
import {toRefs, ref, reactive, onMounted, onBeforeUnmount, watch, h, defineEmits, computed} from 'vue';
import {useRouter, useRoute} from "vue-router";
import { message } from 'ant-design-vue';
import pageApi from "@/assets/api/page";
import {useStorePageData} from "@/store/pageData";
import {useStoreSpaceData} from "@/store/spaceData";
let storePage = useStorePageData();
let storeSpace = useStoreSpaceData();
watch(() => storeSpace.spaceInfo, (newSpace, oldSpace) => {
initSpaceForm();
});
onMounted(() => {
initSpaceForm();
});
let spaceForm = ref({});
let spaceFormRules = ref({
name: [{required: true, message: '请输入空间名', trigger: 'blur'}],
});
let spaceFormRef = ref();
const initSpaceForm = () => {
spaceForm.value = {...storeSpace.spaceInfo};
}
const onSpaceUpdateSubmit = () => {
spaceFormRef.value.validate().then(() => {
let param = {
id: spaceForm.value.id,
name: spaceForm.value.name,
spaceExplain: spaceForm.value.spaceExplain,
};
pageApi.updateSpace(param).then(json => {
message.success('修改成功');
storeSpace.spaceInfo.name = spaceForm.value.name;
storeSpace.spaceInfo.spaceExplain = spaceForm.value.spaceExplain;
});
});
}
</script>
<style lang="scss">
.setting-base-info-box {
width: 80%;
min-width: 400px;
max-width: 700px;
margin-bottom: 40px;
}
</style>

View File

@@ -0,0 +1,108 @@
<template>
<div class="setting-more-action-box">
<a-card title="空间管理" style="margin-top: 20px;">
<a-form :label-col="{span: 4}" :wrapper-col="{span: 20}">
<a-form-item label="删除空间">
<div style="width: 100%;">
<a-button @click="deleteSpaceInfo" type="primary" danger :icon="h(DeleteOutlined)">删除空间</a-button>
<el-alert title="删除该空间及其所有页面,请谨慎操作" show-icon type="warning" :closable="false" style="margin-top: 10px;line-height: normal;"/>
</div>
</a-form-item>
</a-form>
</a-card>
<a-card title="更多信息" class="create-info-card">
<a-row class="create-info-line">
<a-col :span="12">
<div>创建人{{storeSpace.spaceInfo.createUserName}}</div>
</a-col>
<a-col :span="12">
<div>创建时间{{storeSpace.spaceInfo.createTime}}</div>
</a-col>
</a-row>
<a-row v-if="storeSpace.spaceInfo.modified" class="create-info-line">
<a-col :span="12">
<div>修改人{{storeSpace.spaceInfo.modifyUser}}</div>
</a-col>
<a-col :span="12">
<div>修改时间{{storeSpace.spaceInfo.modified}}</div>
</a-col>
</a-row>
</a-card>
</div>
</template>
<script setup>
import {
SyncOutlined, DeleteOutlined, CopyOutlined, DownOutlined,
DownloadOutlined, UploadOutlined, ExclamationCircleOutlined
} from '@ant-design/icons-vue';
import {toRefs, ref, reactive, onMounted, h, watch, defineEmits, computed} from 'vue';
import {useRouter, useRoute} from "vue-router";
import { message } from 'ant-design-vue';
import { Modal } from 'ant-design-vue';
import {useStoreDisplay} from '@/store/wikiDisplay.js'
import pageApi from "@/assets/api/page";
import {useStorePageData} from "@/store/pageData";
import {useStoreSpaceData} from "@/store/spaceData";
import {useStoreUserData} from "@/store/userData";
let storeDisplay = useStoreDisplay();
let storePage = useStorePageData();
let storeSpace = useStoreSpaceData();
let storeUser = useStoreUserData();
let route = useRoute();
let router = useRouter();
onMounted(() => {
});
const deleteSpaceInfo = () => {
Modal.confirm({
maskClosable: true,
title: '提示',
content: '确定要删除此空间及下面的所有文档吗?',
okText: '确定',
cancelText: '取消',
onOk: () => {
setTimeout(() => {
Modal.confirm({
maskClosable: true,
title: '二次确认',
content: '这是一个有风险的操作,需要您再次确认,确定要删除此空间及下面的所有文档吗?',
okText: '确定删除',
cancelText: '我再想想',
okType: 'danger',
onOk: () => {
let param = {id: storeSpace.spaceInfo.id, delFlag: 1};
pageApi.updateSpaceAuth(param).then(() => {
message.success('删除成功');
router.push({path: '/view'});
storeSpace.eventSpaceDelete = !storeSpace.eventSpaceDelete;
});
}
});
}, 500);
}
});
}
</script>
<style lang="scss" scoped>
.setting-more-action-box {
width: 80%;
min-width: 500px;
max-width: 800px;
margin-bottom: 40px;
.create-info-card {
margin-top: 20px;
.create-info-line {
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
}
}
}
</style>

View File

@@ -20,6 +20,10 @@ import org.dromara.zyplayer.data.repository.manage.vo.WikiPageTemplateInfoVo;
import org.dromara.zyplayer.data.repository.support.consts.DocSysType;
import org.dromara.zyplayer.data.repository.support.consts.UserMsgType;
import org.dromara.zyplayer.data.service.manage.*;
import org.dromara.zyplayer.data.service.params.PageDeleteParam;
import org.dromara.zyplayer.data.service.params.PageRecycleListParam;
import org.dromara.zyplayer.data.service.params.RecycleDeletePageParam;
import org.dromara.zyplayer.data.service.params.RestorePageParam;
import org.dromara.zyplayer.data.utils.CachePrefix;
import org.dromara.zyplayer.data.utils.CacheUtil;
import org.dromara.zyplayer.wiki.controller.vo.WikiPageContentVo;
@@ -78,7 +82,6 @@ public class WikiPageController {
if (SpaceType.isOthersPrivate(wikiSpaceSel.getType(), currentUser.getUserId(), wikiSpaceSel.getCreateUserId())) {
return DocResponseJson.warn("您没有权限查看该空间的文章列表!");
}
List<WikiPageTemplateInfoVo> wikiPageList = wikiPageService.wikiPageTemplateInfos(wikiPage.getSpaceId());
Map<Long, List<WikiPageVo>> listMap = wikiPageList.stream().map(WikiPageVo::new).collect(Collectors.groupingBy(WikiPageVo::getParentId));
List<WikiPageVo> nodePageList = listMap.get(0L);
@@ -88,7 +91,22 @@ public class WikiPageController {
}
return DocResponseJson.ok(nodePageList);
}
@PostMapping("/recycleList")
public ResponseJson<List<WikiPageVo>> recycleList(PageRecycleListParam param) {
return wikiPageWebService.recycleList(param);
}
@PostMapping("/restore")
public ResponseJson<Object> restore(RestorePageParam param) {
return wikiPageWebService.restore(param.getPageIds());
}
@PostMapping("/recycleDelete")
public ResponseJson<Object> recycleDelete(RecycleDeletePageParam param) {
return wikiPageWebService.recycleDelete(param);
}
@PostMapping("/detail")
public ResponseJson<WikiPageContentVo> detail(WikiPage wikiPage) {
DocUserDetails currentUser = DocUserUtil.getCurrentUser();
@@ -172,28 +190,8 @@ public class WikiPageController {
}
@PostMapping("/delete")
public ResponseJson<Object> delete(Long pageId) {
DocUserDetails currentUser = DocUserUtil.getCurrentUser();
WikiPage wikiPageSel = wikiPageService.getById(pageId);
// 删除权限判断
WikiSpace wikiSpaceSel = wikiSpaceService.getById(wikiPageSel.getSpaceId());
String canDelete = wikiPageAuthService.canDelete(wikiSpaceSel, wikiPageSel.getEditType(), wikiPageSel.getId(), currentUser.getUserId());
if (canDelete != null) {
return DocResponseJson.warn(canDelete);
}
// 执行删除
WikiPage wikiPage = new WikiPage();
wikiPage.setId(pageId);
wikiPage.setDelFlag(1);
wikiPage.setName(wikiPageSel.getName());
wikiPage.setUpdateTime(new Date());
wikiPage.setUpdateUserId(currentUser.getUserId());
wikiPage.setUpdateUserName(currentUser.getUsername());
wikiPageService.deletePage(wikiPage);
QueryWrapper queryWrapper = new QueryWrapper();
queryWrapper.eq("space_id", wikiPageSel.getSpaceId());
queryWrapper.eq("page_id", wikiPageSel.getId());
wikiPageTemplateService.remove(queryWrapper);
public ResponseJson<Object> delete(PageDeleteParam param) {
wikiPageWebService.delete(param);
return DocResponseJson.ok();
}

View File

@@ -7,15 +7,27 @@ import cn.hutool.core.util.URLUtil;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.system.SystemUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.commons.collections4.CollectionUtils;
import org.dromara.zyplayer.core.json.DocResponseJson;
import org.dromara.zyplayer.core.json.ResponseJson;
import org.dromara.zyplayer.data.config.security.DocUserDetails;
import org.dromara.zyplayer.data.config.security.DocUserUtil;
import org.dromara.zyplayer.data.repository.manage.entity.WikiPage;
import org.dromara.zyplayer.data.repository.manage.entity.WikiPageTemplate;
import org.dromara.zyplayer.data.repository.manage.entity.WikiSpace;
import org.dromara.zyplayer.data.repository.manage.mapper.WikiPageMapper;
import org.dromara.zyplayer.data.service.manage.WikiPageService;
import org.dromara.zyplayer.data.service.manage.WikiPageTemplateService;
import org.dromara.zyplayer.data.service.manage.WikiSpaceService;
import org.dromara.zyplayer.data.service.params.PageDeleteParam;
import org.dromara.zyplayer.data.service.params.PageRecycleListParam;
import org.dromara.zyplayer.data.service.params.RecycleDeletePageParam;
import org.dromara.zyplayer.data.utils.HtmlUtils;
import org.dromara.zyplayer.wiki.controller.vo.WikiPageVo;
import org.dromara.zyplayer.wiki.framework.consts.SpaceType;
import org.apache.commons.lang3.StringUtils;
import org.docx4j.XmlUtils;
@@ -38,7 +50,11 @@ import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
@Service
public class WikiPageWebService {
@@ -48,6 +64,10 @@ public class WikiPageWebService {
WikiPageService wikiPageService;
@Resource
WikiSpaceService wikiSpaceService;
@Resource
WikiPageMapper wikiPageMapper;
@Resource
WikiPageTemplateService wikiPageTemplateService;
public ResponseJson<Object> download(Long pageId, String content, HttpServletRequest request, HttpServletResponse response) {
DocUserDetails currentUser = DocUserUtil.getCurrentUser();
@@ -118,4 +138,104 @@ public class WikiPageWebService {
}
return DocResponseJson.warn("导出失败");
}
/**
* 获取空间已删除的页面
*
* @author 暮光:城中城
* @since 2023-04-29
*/
public ResponseJson<List<WikiPageVo>> recycleList(PageRecycleListParam param) {
// 删除的页面
WikiSpace wikiSpaceSel = wikiSpaceService.getById(param.getSpaceId());
// 不是空间的协作者返回空列表 TODO 判断权限
if (wikiSpaceSel == null) {
return DocResponseJson.ok();
}
LambdaQueryWrapper<WikiPage> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(WikiPage::getDelFlag, 1);
wrapper.eq(WikiPage::getSpaceId, param.getSpaceId());
wrapper.orderByDesc(WikiPage::getUpdateTime).orderByDesc(WikiPage::getId);
IPage<WikiPage> page = new Page<>(param.getPageNum(), 20, true);
wikiPageService.page(page, wrapper);
DocResponseJson<List<WikiPageVo>> responseJson = DocResponseJson.ok();
responseJson.setTotal(page.getTotal());
if (CollectionUtils.isNotEmpty(page.getRecords())) {
responseJson.setData(page.getRecords().stream().map(WikiPageVo::new).collect(Collectors.toList()));
}
return responseJson;
}
public ResponseJson<Object> restore(String pageIds) {
String[] pageIdArr = StringUtils.split(pageIds, ",");
for (String pageId : pageIdArr) {
DocResponseJson<Object> restoreRes = this.restore(Long.valueOf(pageId));
if (!restoreRes.isOk()) {
return restoreRes;
}
}
return DocResponseJson.ok();
}
public DocResponseJson<Object> restore(Long pageId) {
DocUserDetails currentUser = DocUserUtil.getCurrentUser();
WikiPage wikiPageSel = wikiPageService.getById(pageId);
WikiSpace wikiSpaceSel = wikiSpaceService.getById(wikiPageSel.getSpaceId());
// 是否具有编辑权限 TODO
// if (!userDataAuthService.canEditPage(wikiSpaceSel.getId(), pageId, wikiSpaceSel.getCreateUserId())) {
// return DocResponseJson.warn(ErrorEnum.NO_PERMISSION_TO_RESTORE_ARTICLE);
// }
// 执行恢复操作
WikiPage wikiPage = new WikiPage();
wikiPage.setId(pageId);
wikiPage.setDelFlag(0);
wikiPage.setName(wikiPageSel.getName());
wikiPage.setUpdateTime(new Date());
wikiPage.setUpdateUserId(currentUser.getUserId());
wikiPage.setUpdateUserName(currentUser.getUsername());
wikiPageService.updateById(wikiPage);
// 重置当前分支的所有节点seq值
wikiPageMapper.updateChildrenSeq(wikiPageSel.getSpaceId(), wikiPageSel.getParentId());
return DocResponseJson.ok();
}
public DocResponseJson<Object> delete(PageDeleteParam param) {
DocUserDetails currentUser = DocUserUtil.getCurrentUser();
WikiPage wikiPageSel = wikiPageService.getById(param.getPageId());
// 删除权限判断 TODO
WikiSpace wikiSpaceSel = wikiSpaceService.getById(wikiPageSel.getSpaceId());
// String canDelete = wikiPageAuthService.canDelete(wikiSpaceSel, wikiPageSel.getEditType(), wikiPageSel.getId(), currentUser.getUserId());
// if (canDelete != null) {
// return DocResponseJson.warn(canDelete);
// }
// 执行删除
WikiPage wikiPage = new WikiPage();
wikiPage.setId(param.getPageId());
wikiPage.setDelFlag(Optional.ofNullable(param.getDelFlag()).orElse(1));
wikiPage.setName(wikiPageSel.getName());
wikiPage.setUpdateTime(new Date());
wikiPage.setUpdateUserId(currentUser.getUserId());
wikiPage.setUpdateUserName(currentUser.getUsername());
wikiPageService.deletePage(wikiPage);
QueryWrapper<WikiPageTemplate> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("space_id", wikiPageSel.getSpaceId());
queryWrapper.eq("page_id", wikiPageSel.getId());
wikiPageTemplateService.remove(queryWrapper);
return DocResponseJson.ok();
}
public ResponseJson<Object> recycleDelete(RecycleDeletePageParam param) {
String[] pageIdArr = StringUtils.split(param.getPageIds(), ",");
for (String pageId : pageIdArr) {
PageDeleteParam deleteParam = new PageDeleteParam();
deleteParam.setPageId(Long.valueOf(pageId));
deleteParam.setSpaceId(param.getSpaceId());
deleteParam.setDelFlag(2);
DocResponseJson<Object> restoreRes = this.delete(deleteParam);
if (!restoreRes.isOk()) {
return restoreRes;
}
}
return DocResponseJson.ok();
}
}