1.回收站功能前后端实现

2.文件管理前后端实现
3.样式未调整、功能初步测试通过
This commit is contained in:
Sh1yu
2023-10-24 15:41:26 +08:00
parent b25e0cd03b
commit f99b278e51
17 changed files with 609 additions and 222 deletions

View File

@@ -5,6 +5,9 @@ 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)}),
deleted: (data) => request({url: '/zyplayer-doc-wiki/page/deleted', method: 'post', data: Qs.stringify(data)}),
revert: (data) => request({url: '/zyplayer-doc-wiki/page/revert', method: 'post', data: Qs.stringify(data)}),
destroy: (data) => request({url: '/zyplayer-doc-wiki/page/destroy', method: 'post', data: Qs.stringify(data)}),
updatePage: (data) => request({url: '/zyplayer-doc-wiki/page/update', 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)}),
@@ -26,7 +29,9 @@ export default {
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)}),
destoryFile: (data) => request({url: '/zyplayer-doc-wiki/page/file/destory', method: 'post', data: Qs.stringify(data)}),
deletePageFile: (data) => request({url: '/zyplayer-doc-wiki/page/file/delete', method: 'post', data: Qs.stringify(data)}),
fileInfoList: (data) => request({url: '/zyplayer-doc-wiki/page/file/list', 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)}),

View File

@@ -1,5 +1,5 @@
<template>
<div style="padding: 10px;height: 100%;box-sizing: border-box;background: #fafafa;">
<div style="padding: 10px;height: 100%;box-sizing: border-box;background: #fafafa;position: relative">
<div style="margin-bottom: 5px">
<el-select :model-value="choiceSpace" filterable placeholder="选择空间" @change="spaceChangeEvents" style="width: 100%">
<el-option-group label="" v-if="!props.readOnly">
@@ -54,9 +54,9 @@
</template>
</el-tree>
</div>
<div class="sidebar-tool-box">
<a-divider />
<span >
<div v-if="!props.readOnly" class="sidebar-tool-box">
<span class="sidebar-tool-box-bottom">
<a-divider />
<a-button type="text" @click="routeToFileCtl()">
<template #icon><DatabaseOutlined /></template>
文件管理
@@ -78,6 +78,7 @@ import pageApi from '../../assets/api/page'
import {useStoreDisplay} from "@/store/wikiDisplay";
import {useStorePageData} from "@/store/pageData";
import {DatabaseOutlined,DeleteOutlined} from '@ant-design/icons-vue';
import {useStoreSpaceData} from "@/store/spaceData";
let emit = defineEmits(['doGetPageList', 'spaceChangeEvents', 'setNowPageId'])
@@ -91,7 +92,9 @@ let router = useRouter();
let defaultProps = ref({children: 'children', label: 'name',});
let wikiPage = ref({});
let wikiPageTreeRef = ref();
let storePage = useStorePageData();
let storeDisplay = useStoreDisplay();
let storeSpace = useStoreSpaceData();
let props = defineProps({
wikiPageList: Array,
spaceOptions: Array,
@@ -155,7 +158,6 @@ const filterPageNode = (value, data) => {
const searchByKeywords = () => {
wikiPageTreeRef.value.filter(searchKeywords.value)
}
let storePage = useStorePageData();
const handleNodeClick = (data) => {
//console.log('点击节点:', data, props.nowPageId)
storeDisplay.showHeader = true
@@ -197,7 +199,14 @@ defineExpose({searchByKeywords})
</script>
<style scoped>
.sidebar-tool-box{
text-align:center;
position: absolute;
bottom: 0;
left: 0;
right: 0;
}
.sidebar-tool-box-bottom{
}
</style>

View File

@@ -1,17 +1,17 @@
<template>
<div >
<div>
<span class="up-query-param-span">
<a-form :model="queryParam" :label-col="2" :wrapper-col="3">
<a-form :model="queryParam" :label-col="{span: 8}" :wrapper-col="{span: 16}">
<a-row :gutter="16">
<a-col :span="5">
<a-form-item label="文件名">
<a-input v-model:value="queryParam.name" placeholder="Basic usage" />
<a-input v-model:value="queryParam.name" placeholder="请输入文件名"/>
</a-form-item>
</a-col>
<a-col :span="5">
<a-form-item label="文件状态">
<a-select ref="select" v-model:value="queryParam.status">
<a-select-option value="0">全部</a-select-option>
<a-select-option value="9">全部</a-select-option>
<a-select-option value="1">未删除</a-select-option>
<a-select-option value="2">已删除</a-select-option>
</a-select>
@@ -20,7 +20,7 @@
<a-col :span="5">
<a-form-item label="文档状态">
<a-select ref="select" v-model:value="queryParam.file">
<a-select-option value="0">全部</a-select-option>
<a-select-option value="9">全部</a-select-option>
<a-select-option value="1">未删除</a-select-option>
<a-select-option value="2">已删除</a-select-option>
</a-select>
@@ -28,10 +28,7 @@
</a-col>
<a-col :span="5">
<a-form-item label="文件类型">
<a-select
ref="select"
v-model:value="queryParam.type"
>
<a-select ref="select" v-model:value="queryParam.type">
<a-select-option value="0">全部</a-select-option>
<a-select-option value="1">附件</a-select-option>
<a-select-option value="2">文档内含附件</a-select-option>
@@ -39,69 +36,236 @@
</a-form-item>
</a-col>
<a-col :span="4">
<a-button type="primary" @click="onSubmit">查询</a-button>
<a-button type="primary" @click="query">
<template #icon><SearchOutlined/></template>
查询
</a-button>
</a-col>
</a-row>
</a-form>
</span>
<a-divider />
<a-table :columns="columns" :data-source="table" :pagination="{ pageSize: 10 }" :scroll="{ x: 1200, y: 'calc(100vh - 280px)' }">
<a-divider/>
<a-button type="primary" danger @click="doDeleteSelected()">
<template #icon>
<DeleteFilled/>
</template>
删除选中文件
</a-button>
<a-radio-group v-model:value="showStyle" size="large" button-style="solid" style="margin-left: 20px">
<a-radio-button value="table">
<TableOutlined/>
列表展示
</a-radio-button>
<a-radio-button value="card">
卡片展示
<ProfileOutlined/>
</a-radio-button>
</a-radio-group>
<a-table v-if="showStyle === 'table'" :columns="columns" :data-source="table"
:scroll="{ x: 1200, y: 'calc(100vh - 280px)' }" :row-selection="rowSelection" :pagination=false
rowKey="id">
<template v-slot:bodyCell="{ column, record ,index}">
<span v-if="column.key === 'action'">
<a-button @click="showDoc(record)">
<template #icon><AimOutlined /></template>
关联文档
</a-button>
</span>
<a-tag v-if="column.dataIndex === 'size'" color="#f50">
{{ sizeConvert(record.size) }}
</a-tag>
<a-tag v-if="column.dataIndex === 'status'" color="#f50">
{{ statusDesc(record.status) }}
</a-tag>
<a-tag v-if="column.dataIndex === 'file'" color="#f50">
{{ statusDesc(record.file) }}
</a-tag>
<a-tag v-if="column.dataIndex === 'type'" color="#f50">
{{ typeDesc(record.type) }}
</a-tag>
</template>
</a-table>
<div v-if="showStyle === 'card'">
<span v-for="item in table" @click="mark(item)">
<div v-if="item.type === '1'" :title="item.name">
<a-descriptions title="文件名" bordered size="small"
:column="{ xxl: 1, xl: 1, lg: 1, md: 1, sm: 1, xs: 1 }"
style="width: 250px;height:350px">
<a-descriptions-item label="文件名称">{{item.name}}</a-descriptions-item>
<a-descriptions-item label="文件大小">{{sizeConvert(item.size)}}</a-descriptions-item>
<a-descriptions-item label="文件状态">{{statusDesc(item.status)}}</a-descriptions-item>
<a-descriptions-item label="文件状态">{{statusDesc(item.file)}}</a-descriptions-item>
<a-descriptions-item label="文件类型">{{typeDesc(item.type)}}</a-descriptions-item>
<a-descriptions-item label="上传者">{{item.user}}</a-descriptions-item>
<a-descriptions-item label="上传时间">{{item.time}}</a-descriptions-item>
</a-descriptions>
</div>
<a-tabs v-if="item.type === '2'" v-model:activeKey="activeKey" @mouseenter="showImage()"
@mouseleave="showDetail()">
<a-tab-pane key='1' tab="图片页">
<a-image :width="250" :height="300" :src="buildSrc(item.uuid)"/>
</a-tab-pane>
<a-tab-pane key='2' tab="详情页">
<div style="width: 250px;height:300px;">
<a-descriptions title="图像详情" bordered size="small"
:column="{ xxl: 1, xl: 1, lg: 1, md: 1, sm: 1, xs: 1 }">
<a-descriptions-item label="文件名称">{{item.name}}</a-descriptions-item>
<a-descriptions-item label="文件大小">{{sizeConvert(item.size)}}</a-descriptions-item>
<a-descriptions-item label="文件状态">{{statusDesc(item.status)}}</a-descriptions-item>
<a-descriptions-item label="文件状态">{{statusDesc(item.file)}}</a-descriptions-item>
<a-descriptions-item label="文件类型">{{typeDesc(item.type)}}</a-descriptions-item>
<a-descriptions-item label="上传者">{{item.user}}</a-descriptions-item>
<a-descriptions-item label="上传时间">{{item.time}}</a-descriptions-item>
</a-descriptions>
</div>
</a-tab-pane>
<template #renderTabBar="{ DefaultTabBar, ...props }"></template>
</a-tabs>
<CheckCircleTwoTone v-if="item.choose" twoToneColor="#52c41a" style="margin-right: 200px"/>
<CheckCircleTwoTone v-else twoToneColor="#e5ecf7" style="margin-right: 200px"/>
</span>
</div>
<a-pagination v-model:current="queryParam.current"
:page-size-options="activeKey === 'card'?pageSizeOptionsCard:pageSizeOptions"
:total="queryParam.total" :page-size="queryParam.pageSize" show-size-changer
@showSizeChange="onShowSizeChange" @change="pageQuery">
<template #buildOptionText="props">
<span v-if="props.value !== '50'">{{ props.value }}/</span>
<span v-else>全部</span>
</template>
</a-pagination>
</div>
</template>
<script setup>
import {AimOutlined} from '@ant-design/icons-vue';
import {ref} from "vue";
import {
AimOutlined,
SearchOutlined,
DeleteFilled,
TableOutlined,
ProfileOutlined,
CheckCircleTwoTone
} from '@ant-design/icons-vue';
import {onMounted, ref} from "vue";
import {mavonEditor} from "mavon-editor";
import pageApi from '../../assets/api/page'
import {ElMessage, ElMessageBox} from "element-plus";
let visible = ref(false)
let showStyle = ref('table')
let activeKey = ref('1')
let pageSizeOptionsCard = ref(['4', '8']);
let pageSizeOptions = ref(['10', '20']);
let deleteQueue = ref([]);
let queryParam = ref({
name:'',
status:'0',
file:'0',
type:'0'
current: 1,
total: 0,
pageSize: 10,
name: '',
status: '9',
file: '9',
type: '0'
})
let table = ref([
{key: 321, name: 'Edward Kingsssssssssssssssssssssssssssssssssssss ', age: 32, address: 'London, Park Lane no. ',},
{key: 321, name: 'Edward Kingsssssssssssssssssssssssssssssssssssss ', age: 32, address: 'London, Park Lane no. ',},
{key: 321, name: 'Edward Kingsssssssssssssssssssssssssssssssssssss ', age: 32, address: 'London, Park Lane no. ',},
{key: 321, name: 'Edward Kingsssssssssssssssssssssssssssssssssssss ', age: 32, address: 'London, Park Lane no. ',},
{key: 321, name: 'Edward Kingsssssssssssssssssssssssssssssssssssss ', age: 32, address: 'London, Park Lane no. ',},
{key: 321, name: 'Edward Kingsssssssssssssssssssssssssssssssssssss ', age: 32, address: 'London, Park Lane no. ',},
{key: 321, name: 'Edward Kingsssssssssssssssssssssssssssssssssssss ', age: 32, address: 'London, Park Lane no. ',},
{key: 321, name: 'Edward Kingsssssssssssssssssssssssssssssssssssss ', age: 32, address: 'London, Park Lane no. ',},
{key: 321, name: 'Edward Kingsssssssssssssssssssssssssssssssssssss ', age: 32, address: 'London, Park Lane no. ',},
{key: 321, name: 'Edward Kingsssssssssssssssssssssssssssssssssssss ', age: 32, address: 'London, Park Lane no. ',},
{key: 321, name: 'Edward Kingsssssssssssssssssssssssssssssssssssss ', age: 32, address: 'London, Park Lane no. ',},
{key: 321, name: 'Edward Kingsssssssssssssssssssssssssssssssssssss ', age: 32, address: 'London, Park Lane no. ',},
{key: 321, name: 'Edward Kingsssssssssssssssssssssssssssssssssssss ', age: 32, address: 'London, Park Lane no. ',},
{key: 321, name: 'Edward Kingsssssssssssssssssssssssssssssssssssss ', age: 32, address: 'London, Park Lane no. ',},
{key: 321, name: 'Edward Kingsssssssssssssssssssssssssssssssssssss ', age: 32, address: 'London, Park Lane no. ',},
{key: 321, name: 'Edward Kingsssssssssssssssssssssssssssssssssssss ', age: 32, address: 'London, Park Lane no. ',}
]);
let table = ref([]);
let columns = ref([
{title: '文件名称', dataIndex: 'name', width: '500' ,},
{title: '文件大小', dataIndex: 'age', width: '100',},
{title: '文件状态', dataIndex: 'address', width: '100',},
{title: '文档状态', dataIndex: 'address', width: '100',},
{title: '上传者', dataIndex: 'address', width: '150',},
{title: '上传时间', dataIndex: 'address', width: '150',},
{ title: '详情查看', key: 'action', fixed: 'right',width: '150',},
{title: '文件名称', dataIndex: 'name', width: '500',},
{title: '文件大小', dataIndex: 'size', width: '100',},
{title: '文件状态', dataIndex: 'status', width: '100',},
{title: '文档状态', dataIndex: 'file', width: '100',},
{title: '文档类型', dataIndex: 'type', width: '100',},
{title: '上传', dataIndex: 'user', width: '150',},
{title: '上传时间', dataIndex: 'time', width: '150',},
])
const onSubmit = () => {
onMounted(() => {
query()
})
const rowSelection = {
selectedRowKeys: deleteQueue,
onChange: (selectedRowKeys, selectedRows) => {
deleteQueue.value = selectedRowKeys
console.log(selectedRowKeys)
},
onSelect: (record, selected, selectedRows) => {
//console.log(record, selected, selectedRows);
},
onSelectAll: (selected, selectedRows, changeRows) => {
//console.log(selected, selectedRows, changeRows);
},
};
const statusDesc = (status) => {
const statusMap = {
0: '未删除',
1: '已删除',
}
return statusMap[status]
}
const showDoc = (record) => {
const typeDesc = (type) => {
const typeMap = {
1: '附件',
2: '影像',
}
return typeMap[type]
}
const sizeConvert = (size) => {
size = size / 1024 / 1024
return size.toFixed(2) + 'M'
}
const query = () => {
pageApi.fileInfoList(queryParam.value).then((json) => {
table.value = json.data || []
queryParam.value.total = json.total
})
}
const doDeleteSelected = () => {
ElMessageBox.confirm("确认要删除选中的文件吗?删除后不可恢复!", '重要提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
if (showStyle.value === 'card') {
let deleteList = [];
let temp = table
temp = temp.value.filter(item => (item.choose))
temp.forEach(item => {
deleteList.push(item.id)
})
pageApi.destoryFile({arr: deleteList}).then(() => {
query()
})
return
}
pageApi.destoryFile({arr: deleteQueue.value}).then(() => {
query()
})
})
}
const buildSrc = (uuid) => {
return "zyplayer-doc-wiki/common/file?uuid=" + uuid;
};
const showImage = () => {
activeKey.value = '2'
};
const showDetail = () => {
activeKey.value = '1'
};
const pageQuery = () => {
deleteQueue.value = []
query()
};
const onShowSizeChange = (current, size) => {
queryParam.value.pageSize = size;
deleteQueue.value = []
query()
};
const mark = (item) => {
let choosed = item.choose;
if (choosed === undefined) {
choosed = false
}
item.choose = !choosed
};
</script>
<style scoped>
.up-query-param-span{
.up-query-param-span {
}
</style>

View File

@@ -1,12 +1,8 @@
<template>
<div>
<a-table :columns="columns" :data-source="table" :pagination="{ pageSize: 10 }" :scroll="{ y: 240 }">
<a-table :columns="columns" :data-source="table" :pagination=false :scroll="{ y: 'calc(100vh - 170px)' }">
<template v-slot:bodyCell="{ column, record ,index}">
<span v-if="column.key === 'action'">
<a-button @click="showFiles(record)">
<template #icon><TableOutlined /></template>
相关文件
</a-button>
<a-divider type="vertical"/>
<a-button @click="revertConfirm(record)">
<template #icon><UndoOutlined /></template>
@@ -20,88 +16,91 @@
</span>
</template>
</a-table>
</div>
<a-modal :open="visible" title="详情展示" :closable="false">
<template #footer>
<a-button key="Submit" type="primary" @click="handleOk">好的</a-button>
</template>
<a-carousel arrows>
<template #nextArrow>
<div class="custom-slick-arrow" style="right: 10px">
<right-circle-outlined />
</div>
<a-pagination v-model:current="queryParam.current"
:page-size-options="pageSizeOptions"
:total="queryParam.total" :page-size="queryParam.pageSize" show-size-changer
@showSizeChange="onShowSizeChange" @change="initDeleted">
<template #buildOptionText="props">
<span v-if="props.value !== '50'">{{ props.value }}/</span>
<span v-else>全部</span>
</template>
<div><h3>1</h3></div>
<div><h3>2</h3></div>
<div><h3>3</h3></div>
<div><h3>4</h3></div>
</a-carousel>
</a-modal>
</a-pagination>
</div>
</template>
<script setup>
import {UndoOutlined,DeleteFilled,TableOutlined,RightCircleOutlined,} from '@ant-design/icons-vue';
import {ref} from "vue";
let visible = ref(false)
let table = ref([
{key: 321, name: 'Edward King ', age: 32, address: 'London, Park Lane no. ',},
{key: 321, name: 'Edward King ', age: 32, address: 'London, Park Lane no. ',},
{key: 321, name: 'Edward King ', age: 32, address: 'London, Park Lane no. ',},
{key: 321, name: 'Edward King ', age: 32, address: 'London, Park Lane no. ',},
{key: 321, name: 'Edward King ', age: 32, address: 'London, Park Lane no. ',},
{key: 321, name: 'Edward King ', age: 32, address: 'London, Park Lane no. ',},
{key: 321, name: 'Edward King ', age: 32, address: 'London, Park Lane no. ',},
{key: 321, name: 'Edward King ', age: 32, address: 'London, Park Lane no. ',},
{key: 321, name: 'Edward King ', age: 32, address: 'London, Park Lane no. ',},
{key: 321, name: 'Edward King ', age: 32, address: 'London, Park Lane no. ',},
{key: 321, name: 'Edward King ', age: 32, address: 'London, Park Lane no. ',},
{key: 321, name: 'Edward King ', age: 32, address: 'London, Park Lane no. ',},
{key: 321, name: 'Edward King ', age: 32, address: 'London, Park Lane no. ',},
{key: 321, name: 'Edward King ', age: 32, address: 'London, Park Lane no. ',},
{key: 321, name: 'Edward King ', age: 32, address: 'London, Park Lane no. ',},
{key: 321, name: 'Edward King ', age: 32, address: 'London, Park Lane no. ',}
]);
import {onBeforeUnmount, onMounted, ref} from "vue";
import pageApi from '../../assets/api/page'
import {ElMessage, ElMessageBox} from "element-plus";
import {useStoreSpaceData} from "@/store/spaceData";
import {useStorePageData} from "@/store/pageData";
let storeSpace = useStoreSpaceData();
let storePage = useStorePageData();
let table = ref([]);
let pageSizeOptions = ref(['10', '20']);
let columns = ref([
{title: '名称', dataIndex: 'name', width: '50%' ,},
{title: '删除日期', dataIndex: 'age', width: '15%',},
{title: '操作者', dataIndex: 'address', width: '15%',},
{title: '名称', dataIndex: 'name', width: '40%' ,},
{title: '删除日期', dataIndex: 'updateTime', width: '17%',},
{title: '操作者', dataIndex: 'updateUserName', width: '16%',},
{title: '', key: 'action'},
])
const handleOk = (e) => {
visible.value = false;
}
const showFiles = (record) => {
visible.value = true;
let queryParam = ref({
current: 1,
total: 0,
pageSize: 10,
spaceId: storeSpace.chooseSpaceId
})
onMounted(() => {
initDeleted()
})
const onShowSizeChange = (current, size) => {
queryParam.value.pageSize = size;
initDeleted()
};
const initDeleted = () => {
pageApi.deleted(queryParam.value).then((json) => {
table.value = json.data || []
queryParam.value.total = json.total
})
console.log()
}
const revertConfirm = (record) => {
ElMessageBox.confirm('确定要还原此文档吗(仅此文档,若父级文档不存在还原当前空间根目录)?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
let param = {pageId: record.id}
pageApi.revert(param).then(() => {
ElMessage.success('还原成功!')
doGetPageList()
initDeleted()
})
})
}
const doGetPageList = (parentId, node) => {
let param = {spaceId: storeSpace.chooseSpaceId}
pageApi.pageList(param).then((json) => {
storePage.wikiPageList = json.data || []
})
}
const destroy = (record) => {
ElMessageBox.confirm('确定要物理删除此文档吗?(关联的文件不会被删除仅删除文档)', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
let param = {pageId: record.id}
pageApi.destroy(param).then(() => {
ElMessage.success('删除成功')
doGetPageList()
initDeleted()
})
})
}
</script>
<style scoped>
.ant-carousel :deep(.slick-slide) {
text-align: center;
height: 160px;
line-height: 160px;
background: #364d79;
overflow: hidden;
}
.ant-carousel :deep(.slick-arrow.custom-slick-arrow) {
width: 25px;
height: 25px;
font-size: 25px;
color: #fff;
background-color: rgba(31, 45, 61, 0.11);
opacity: 0.3;
}
.ant-carousel :deep(.custom-slick-arrow:before) {
display: none;
}
.ant-carousel :deep(.custom-slick-arrow:hover) {
opacity: 0.5;
}
.ant-carousel :deep(.slick-slide h3) {
color: #fff;
}
</style>