修改一大堆东西
This commit is contained in:
parent
ce590d1ebd
commit
1c83cb1bf8
|
|
@ -33,6 +33,7 @@
|
|||
"spark-md5": "^3.0.2",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"vue": "^3.4.21",
|
||||
"vue-quill-editor": "^3.0.6",
|
||||
"vue-router": "^4.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,100 @@
|
|||
import service from '@/utils/request'
|
||||
|
||||
// @Summary 获取文章列表
|
||||
// @Produce application/json
|
||||
// @Router /cms/article/getList [post]
|
||||
export const getArticleList = (data) => {
|
||||
return service({
|
||||
url: '/cms/article/getList',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// @Summary 新增文章
|
||||
// @Produce application/json
|
||||
// @Param menu Object
|
||||
// @Router /cms/article/add [post]
|
||||
export const addArticle = (data) => {
|
||||
return service({
|
||||
url: '/cms/article/add',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// @Summary 删除文章
|
||||
// @Produce application/json
|
||||
// @Param ID int
|
||||
// @Router /cms/article/delete [delete]
|
||||
export const deleteArticle = (data) => {
|
||||
return service({
|
||||
url: '/cms/article/delete',
|
||||
method: 'delete',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// @Summary 修改文章
|
||||
// @Produce application/json
|
||||
// @Param menu Object
|
||||
// @Router /cms/article/update [put]
|
||||
export const updateArticle = (data) => {
|
||||
return service({
|
||||
url: '/cms/article/update',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags 文章文章
|
||||
// @Summary 根据id获取文章
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body api.GetById true "根据id获取文章"
|
||||
// @Success 200 {string} json "{"success":true,"data":{},"msg":"获取成功"}"
|
||||
// @Router /cms/article/get [get]
|
||||
export const getArticleById = (params) => {
|
||||
return service({
|
||||
url: '/cms/article/get',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
// @Summary 发布文章
|
||||
// @Produce application/json
|
||||
// @Param menu Object
|
||||
// @Router /cms/article/release [put]
|
||||
export const releaseArticle = (data) => {
|
||||
return service({
|
||||
url: '/cms/article/release',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// @Summary 更新文章所属栏目
|
||||
// @Produce application/json
|
||||
// @Param menu Object
|
||||
// @Router /cms/article/setChannels [put]
|
||||
export const setArticleChannels = (data) => {
|
||||
return service({
|
||||
url: '/cms/article/setChannels',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// @Summary 更新文章分类
|
||||
// @Produce application/json
|
||||
// @Param menu Object
|
||||
// @Router /cms/article/setChannels [put]
|
||||
export const setArticleCategories = (data) => {
|
||||
return service({
|
||||
url: '/cms/article/setCategories',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ import service from '@/utils/request'
|
|||
|
||||
export const findFile = (params) => {
|
||||
return service({
|
||||
url: '/mediaFile/findFile',
|
||||
url: '/cms/mediaFile/findFile',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
|
|
@ -17,7 +17,7 @@ export const findFile = (params) => {
|
|||
|
||||
export const breakpointContinue = (data) => {
|
||||
return service({
|
||||
url: '/mediaFile/breakpointContinue',
|
||||
url: '/cms/mediaFile/breakpointContinue',
|
||||
method: 'post',
|
||||
donNotShowLoading: true,
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
|
|
@ -27,7 +27,7 @@ export const breakpointContinue = (data) => {
|
|||
|
||||
export const breakpointContinueFinish = (params) => {
|
||||
return service({
|
||||
url: '/mediaFile/breakpointContinueFinish',
|
||||
url: '/cms/mediaFile/breakpointContinueFinish',
|
||||
method: 'post',
|
||||
params
|
||||
})
|
||||
|
|
@ -35,7 +35,7 @@ export const breakpointContinueFinish = (params) => {
|
|||
|
||||
export const removeChunk = (data, params) => {
|
||||
return service({
|
||||
url: '/mediaFile/removeChunk',
|
||||
url: '/cms/mediaFile/removeChunk',
|
||||
method: 'post',
|
||||
data,
|
||||
params
|
||||
|
|
|
|||
|
|
@ -2,21 +2,21 @@ import service from '@/utils/request'
|
|||
|
||||
// @Summary 获取分类树
|
||||
// @Produce application/json
|
||||
// @Router /articleCategory/getCategoryTree [post]
|
||||
// @Router /category/getTree [get]
|
||||
export const getCategoryTree = () => {
|
||||
return service({
|
||||
url: '/articleCategory/getCategoryTree',
|
||||
method: 'post',
|
||||
url: '/cms/category/getTree',
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
// @Summary 新增分类
|
||||
// @Produce application/json
|
||||
// @Param menu Object
|
||||
// @Router /articleCategory/addCategory [post]
|
||||
// @Router /category/add [post]
|
||||
export const addCategory = (data) => {
|
||||
return service({
|
||||
url: '/articleCategory/addCategory',
|
||||
url: '/cms/category/add',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
|
|
@ -25,11 +25,11 @@ export const addCategory = (data) => {
|
|||
// @Summary 删除分类
|
||||
// @Produce application/json
|
||||
// @Param ID int
|
||||
// @Router /articleCategory/deleteCategory [post]
|
||||
// @Router /category/delete [delete]
|
||||
export const deleteCategory = (data) => {
|
||||
return service({
|
||||
url: '/articleCategory/deleteCategory',
|
||||
method: 'post',
|
||||
url: '/cms/category/delete',
|
||||
method: 'delete',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
|
@ -37,11 +37,11 @@ export const deleteCategory = (data) => {
|
|||
// @Summary 修改分类
|
||||
// @Produce application/json
|
||||
// @Param menu Object
|
||||
// @Router /articleCategory/updateCategory [post]
|
||||
// @Router /category/update [put]
|
||||
export const updateCategory = (data) => {
|
||||
return service({
|
||||
url: '/articleCategory/updateCategory',
|
||||
method: 'post',
|
||||
url: '/cms/category/update',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
|
@ -51,13 +51,13 @@ export const updateCategory = (data) => {
|
|||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body api.GetById true "根据id获取菜单"
|
||||
// @Param id int true "根据id获取菜单"
|
||||
// @Success 200 {string} json "{"success":true,"data":{},"msg":"获取成功"}"
|
||||
// @Router /articleCategory/getCategoryById [post]
|
||||
export const getCategoryById = (data) => {
|
||||
// @Router /category/get [get]
|
||||
export const getCategoryById = (params) => {
|
||||
return service({
|
||||
url: '/articleCategory/getCategoryById',
|
||||
method: 'post',
|
||||
data
|
||||
url: '/cms/category/get',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
|
@ -2,21 +2,21 @@ import service from '@/utils/request'
|
|||
|
||||
// @Summary 获取栏目树
|
||||
// @Produce application/json
|
||||
// @Router /channel/getChannelTree [post]
|
||||
// @Router /cms/channel/getTree [get]
|
||||
export const getChannelTree = () => {
|
||||
return service({
|
||||
url: '/channel/getChannelTree',
|
||||
method: 'post',
|
||||
url: '/cms/channel/getTree',
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
// @Summary 新增栏目
|
||||
// @Produce application/json
|
||||
// @Param menu Object
|
||||
// @Router /channel/addChannel [post]
|
||||
// @Param channel Object
|
||||
// @Router /cms/channel/add [post]
|
||||
export const addChannel = (data) => {
|
||||
return service({
|
||||
url: '/channel/addChannel',
|
||||
url: '/cms/channel/add',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
|
|
@ -25,23 +25,23 @@ export const addChannel = (data) => {
|
|||
// @Summary 删除栏目
|
||||
// @Produce application/json
|
||||
// @Param ID int
|
||||
// @Router /channel/deleteChannel [post]
|
||||
// @Router /cms/channel/delete [delete]
|
||||
export const deleteChannel = (data) => {
|
||||
return service({
|
||||
url: '/channel/deleteChannel',
|
||||
method: 'post',
|
||||
url: '/cms/channel/delete',
|
||||
method: 'delete',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// @Summary 修改栏目
|
||||
// @Produce application/json
|
||||
// @Param menu Object
|
||||
// @Router /channel/updateChannel [post]
|
||||
// @Param channel Object
|
||||
// @Router /cms/channel/update [put]
|
||||
export const updateChannel = (data) => {
|
||||
return service({
|
||||
url: '/channel/updateChannel',
|
||||
method: 'post',
|
||||
url: '/cms/channel/update',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
|
@ -51,13 +51,27 @@ export const updateChannel = (data) => {
|
|||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body api.GetById true "根据id获取栏目"
|
||||
// @Param id int true "根据id获取栏目"
|
||||
// @Success 200 {string} json "{"success":true,"data":{},"msg":"获取成功"}"
|
||||
// @Router /channel/getChannelById [post]
|
||||
export const getChannelById = (data) => {
|
||||
// @Router /cms/channel/get [get]
|
||||
export const getChannelById = (params) => {
|
||||
return service({
|
||||
url: '/channel/getChannelById',
|
||||
method: 'post',
|
||||
data
|
||||
url: '/cms/channel/get',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags 文章栏目
|
||||
// @Summary 获取模板options
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {string} json "{"success":true,"data":{},"msg":"获取成功"}"
|
||||
// @Router /cms/channel/getTplOptions [get]
|
||||
export const getTplOptions = () => {
|
||||
return service({
|
||||
url: '/cms/channel/getTplOptions',
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
import service from '@/utils/request'
|
||||
|
||||
// @Summary 获取文章列表
|
||||
// @Produce application/json
|
||||
// @Router /cms/fetcher/getArticleList [post]
|
||||
export const getFetcherArticleList = (data) => {
|
||||
return service({
|
||||
url: '/cms/fetcher/getArticleList',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// @Summary 根据id获取文章
|
||||
// @accept application/json
|
||||
// @Router /cms/fetcher/getArticle [get]
|
||||
export const getFetcherArticleById = (params) => {
|
||||
return service({
|
||||
url: '/cms/fetcher/getArticle',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import service from '@/utils/request'
|
||||
|
||||
// @Tags MediaFile
|
||||
// @Summary 分页文件列表
|
||||
// @Security ApiKeyAuth
|
||||
|
|
@ -6,10 +7,10 @@ import service from '@/utils/request'
|
|||
// @Produce application/json
|
||||
// @Param data body modelInterface.PageInfo true "分页获取文件户列表"
|
||||
// @Success 200 {string} json "{"success":true,"data":{},"msg":"获取成功"}"
|
||||
// @Router /mediaFile/getFileList [post]
|
||||
// @Router /cms/mediaFile/getList [post]
|
||||
export const getFileList = (data) => {
|
||||
return service({
|
||||
url: '/mediaFile/getFileList',
|
||||
url: '/cms/mediaFile/getList',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
|
|
@ -21,11 +22,11 @@ export const getFileList = (data) => {
|
|||
// @Produce application/json
|
||||
// @Param data body dbModel.MediaFile true "传入文件里面id即可"
|
||||
// @Success 200 {string} json "{"success":true,"data":{},"msg":"返回成功"}"
|
||||
// @Router /mediaFile/deleteFile [post]
|
||||
// @Router /cms/mediaFile/delete [delete]
|
||||
export const deleteFile = (data) => {
|
||||
return service({
|
||||
url: '/mediaFile/deleteFile',
|
||||
method: 'post',
|
||||
url: '/cms/mediaFile/delete',
|
||||
method: 'delete',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
|
@ -37,8 +38,8 @@ export const deleteFile = (data) => {
|
|||
*/
|
||||
export const editFileName = (data) => {
|
||||
return service({
|
||||
url: '/mediaFile/editFileName',
|
||||
method: 'post',
|
||||
url: '/cms/mediaFile/editName',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
import service from '@/utils/request'
|
||||
|
||||
// @Summary 获取来源列表
|
||||
// @Produce application/json
|
||||
// @Router /cms/source/getList [post]
|
||||
export const getSourceList = (data) => {
|
||||
return service({
|
||||
url: '/cms/source/getList',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
|
@ -1,26 +1,30 @@
|
|||
<template>
|
||||
<el-drawer v-model="drawer" title="媒体库" size="650px">
|
||||
<warning-bar title="点击“文件名/备注”可以编辑文件名或者备注内容。" />
|
||||
<div class="gva-btn-list">
|
||||
<upload-common :image-common="imageCommon" class="upload-btn-media-library" @on-success="open" />
|
||||
<upload-image :image-url="imageUrl" :file-size="512" :max-w-h="1080" class="upload-btn-media-library"
|
||||
@on-success="open" />
|
||||
<el-form ref="searchForm" :inline="true" :model="search">
|
||||
<el-form-item label="">
|
||||
<el-input v-model="search.keyword" class="keyword" placeholder="请输入文件名或备注" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="search" @click="open">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-drawer v-model="drawer" title="媒体库" direction="ltr" :size="props.size">
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-lg">媒体库</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="gva-btn-list">
|
||||
<upload-common :image-common="imageCommon" class="upload-btn-media-library" :category="props.category"
|
||||
@on-success="open" />
|
||||
<el-tooltip effect="dark" content="图片超过 512K 或者 长宽 > 1080 会自动压缩后再上传" placement="top-start">
|
||||
<upload-image :image-url="imageUrl" :file-size="512" :max-w-h="1080" :category="props.category"
|
||||
@on-success="open" />
|
||||
</el-tooltip>
|
||||
<el-input v-model="search.keyword" class="keyword" placeholder="请输入文件名或备注" clearable />
|
||||
<el-button type="primary" plain icon="search" @click="open">查询</el-button>
|
||||
</div>
|
||||
|
||||
<warning-bar title="点击“文件名/备注”可以编辑文件名或者备注内容。" />
|
||||
<div class="media">
|
||||
<div v-for="(item, key) in picList" :key="key" class="media-box">
|
||||
<div class="header-img-box-list">
|
||||
<el-image :key="key" :src="getUrl(item.url)" @click="chooseImg(item.url, target, targetKey)">
|
||||
<div class="img-box-list">
|
||||
<el-image :key="key" fit="cover" :src="getUrl(item.url)" style="width: 138px; height: 138px;"
|
||||
@click="chooseImg(item)">
|
||||
<template #error>
|
||||
<div class="header-img-box-list">
|
||||
<div class="img-box-list">
|
||||
<el-icon>
|
||||
<picture />
|
||||
</el-icon>
|
||||
|
|
@ -64,30 +68,24 @@ const handleCurrentChange = (val) => {
|
|||
open()
|
||||
}
|
||||
|
||||
const emit = defineEmits(['enterImg'])
|
||||
defineProps({
|
||||
target: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
targetKey: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
const emit = defineEmits(['on-select', 'on-before-upload', 'on-upload-success', 'on-upload-failure'])
|
||||
const props = defineProps({
|
||||
category: { type: String, required: true },
|
||||
size: { type: String, default: '800px' },
|
||||
target: { type: Object, default: null },
|
||||
targetKey: { type: String, default: '' },
|
||||
})
|
||||
|
||||
const drawer = ref(false)
|
||||
const picList = ref([])
|
||||
|
||||
const chooseImg = (url, target, targetKey) => {
|
||||
if (target && targetKey) {
|
||||
target[targetKey] = url
|
||||
}
|
||||
emit('enterImg', url)
|
||||
const chooseImg = (item) => {
|
||||
emit('on-select', item)
|
||||
drawer.value = false
|
||||
}
|
||||
|
||||
const open = async () => {
|
||||
search.value.category = props.category
|
||||
const res = await getFileList({ page: page.value, pageSize: pageSize.value, ...search.value })
|
||||
if (res.code === 0) {
|
||||
picList.value = res.data.list
|
||||
|
|
@ -131,46 +129,29 @@ const editFileNameFunc = async (row) => {
|
|||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.upload-btn-media-library {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.media {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
|
||||
.media-box {
|
||||
width: 120px;
|
||||
margin-left: 20px;
|
||||
width: 148px;
|
||||
background: #F0F2F5;
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.img-title {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 36px;
|
||||
line-height: 28px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-img-box-list {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border: 1px dashed #ccc;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
line-height: 120px;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
|
||||
.el-image__inner {
|
||||
max-width: 120px;
|
||||
max-height: 120px;
|
||||
vertical-align: middle;
|
||||
width: unset;
|
||||
height: unset;
|
||||
}
|
||||
font-size: 12px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,36 +1,16 @@
|
|||
<template>
|
||||
<span class="headerAvatar">
|
||||
<template v-if="picType === 'avatar'">
|
||||
<el-avatar
|
||||
v-if="userStore.userInfo.headerImg"
|
||||
:size="30"
|
||||
:src="avatar"
|
||||
/>
|
||||
<el-avatar
|
||||
v-else
|
||||
:size="30"
|
||||
:src="noAvatar"
|
||||
/>
|
||||
<el-avatar v-if="userStore.userInfo.headerImg" :size="30" :src="avatar" />
|
||||
<el-avatar v-else :size="30" :src="noAvatar" />
|
||||
</template>
|
||||
<template v-if="picType === 'img'">
|
||||
<img
|
||||
v-if="userStore.userInfo.headerImg"
|
||||
:src="avatar"
|
||||
class="avatar"
|
||||
>
|
||||
<img
|
||||
v-else
|
||||
:src="noAvatar"
|
||||
class="avatar"
|
||||
>
|
||||
<img v-if="userStore.userInfo.headerImg" :src="avatar" class="avatar">
|
||||
<img v-else :src="noAvatar" class="avatar">
|
||||
</template>
|
||||
<template v-if="picType === 'file'">
|
||||
<el-image
|
||||
:src="file"
|
||||
class="file"
|
||||
:preview-src-list="previewSrcList"
|
||||
:preview-teleported="true"
|
||||
/>
|
||||
<el-image :src="file" class="file" fit="cover" :preview-src-list="previewSrcList" :preview-teleported="true"
|
||||
style="width: 80px; height: 80px" />
|
||||
</template>
|
||||
</span>
|
||||
</template>
|
||||
|
|
@ -90,13 +70,14 @@ const previewSrcList = computed(() => props.preview ? [file.value] : [])
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
.headerAvatar{
|
||||
.headerAvatar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.file{
|
||||
|
||||
.file {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
position: relative;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="border border-solid border-gray-100 h-full">
|
||||
<Toolbar :editor="editorRef" :default-config="toolbarConfig" mode="default" />
|
||||
<Editor v-model="valueHtml" class="overflow-y-hidden mt-0.5" style="height: 18rem;" :default-config="editorConfig"
|
||||
<Editor v-model="valueHtml" class="overflow-y-hidden mt-0.5" :style="style" :default-config="editorConfig"
|
||||
mode="default" @onCreated="handleCreated" @onChange="change" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -14,15 +14,10 @@ const basePath = import.meta.env.VITE_BASE_API
|
|||
|
||||
import { onBeforeUnmount, ref, shallowRef, watch } from 'vue'
|
||||
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
|
||||
|
||||
import { useUserStore } from '@/pinia/modules/user'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getUrl } from '@/utils/image'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const emits = defineEmits(['change', 'update:modelValue'])
|
||||
|
||||
const change = (editor) => {
|
||||
emits('change', editor)
|
||||
emits('update:modelValue', valueHtml.value)
|
||||
|
|
@ -32,6 +27,13 @@ const props = defineProps({
|
|||
modelValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// eslint-disable-next-line vue/no-reserved-props
|
||||
style: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
height: '20rem',
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -45,11 +47,11 @@ const editorConfig = {
|
|||
}
|
||||
editorConfig.MENU_CONF['uploadImage'] = {
|
||||
fieldName: 'file',
|
||||
server: basePath + '/mediaFile/upload?noSave=1',
|
||||
server: basePath + '/cms/mediaFile/upload?category=rich_edit',
|
||||
customInsert(res, insertFn) {
|
||||
if (res.code === 0) {
|
||||
const urlPath = getUrl(res.data.file.url)
|
||||
insertFn(urlPath, res.data.file.name)
|
||||
const urlPath = getUrl(res.data.mediaFile.url)
|
||||
insertFn(urlPath, res.data.mediaFile.name)
|
||||
return
|
||||
}
|
||||
ElMessage.error(res.msg)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,7 @@
|
|||
<template>
|
||||
<div class="border border-solid border-gray-100 h-full">
|
||||
<Editor
|
||||
v-model="valueHtml"
|
||||
class="overflow-y-hidden mt-0.5"
|
||||
:default-config="editorConfig"
|
||||
mode="default"
|
||||
@onCreated="handleCreated"
|
||||
@onChange="change"
|
||||
/>
|
||||
<Editor v-model="valueHtml" class="overflow-y-hidden mt-0.5" :default-config="editorConfig" mode="default"
|
||||
@onCreated="handleCreated" @onChange="change" />
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
|
|
@ -57,6 +51,4 @@ watch(() => props.modelValue, () => {
|
|||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
<style scoped lang="scss"></style>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-upload multiple :action="`${path}/mediaFile/upload?noSave=1`" :on-error="uploadError"
|
||||
<el-upload multiple :action="`${path}/cms/mediaFile/upload?catgory=file`" :on-error="uploadError"
|
||||
:on-success="uploadSuccess" :show-file-list="true" :file-list="fileList" :limit="limit" :accept="accept"
|
||||
class="upload-btn">
|
||||
<el-button type="primary">上传文件</el-button>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-upload :action="`${path}/mediaFile/upload`" :before-upload="checkFile" :on-error="uploadError"
|
||||
:on-success="uploadSuccess" :show-file-list="false" class="upload-btn">
|
||||
<el-button type="primary">普通上传</el-button>
|
||||
<el-upload :action="`${path}/cms/mediaFile/upload?category=${props.category}`" :show-file-list="false"
|
||||
:multiple="false" :before-upload="beforeUpload" :on-error="uploadFailure" :on-success="uploadSuccess">
|
||||
<el-button type="primary" plain icon="upload">{{ props.label }}</el-button>
|
||||
</el-upload>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { isVideoMime, isImageMime } from '@/utils/image'
|
||||
|
|
@ -17,10 +16,20 @@ defineOptions({
|
|||
name: 'UploadCommon',
|
||||
})
|
||||
|
||||
const emit = defineEmits(['on-success', 'on-failure', 'on-before-upload'])
|
||||
const emit = defineEmits([
|
||||
'on-before-upload',
|
||||
'on-failure',
|
||||
'on-success',
|
||||
])
|
||||
|
||||
const props = defineProps({
|
||||
category: { type: String, required: true },
|
||||
label: { type: String, default: '上传' }
|
||||
})
|
||||
|
||||
const path = ref(import.meta.env.VITE_BASE_API)
|
||||
|
||||
const checkFile = (file) => {
|
||||
const beforeUpload = (file) => {
|
||||
const isLt500K = file.size / 1024 / 1024 < 0.5 // 500K, @todo 应支持在项目中设置
|
||||
const isLt5M = file.size / 1024 / 1024 < 5 // 5MB, @todo 应支持项目中设置
|
||||
const isVideo = isVideoMime(file.type)
|
||||
|
|
@ -45,13 +54,19 @@ const checkFile = (file) => {
|
|||
}
|
||||
|
||||
const uploadSuccess = (res) => {
|
||||
const { data } = res
|
||||
if (data.mediaFile) {
|
||||
emit('on-success', data.mediaFile.url)
|
||||
const { code, data, msg } = res
|
||||
if (code !== 0) {
|
||||
ElMessage({ type: 'error', message: msg })
|
||||
emit('on-failure')
|
||||
}
|
||||
if (!data.mediaFile) {
|
||||
ElMessage({ type: 'error', message: '返回错误,上传失败' })
|
||||
emit('on-failure')
|
||||
}
|
||||
emit('on-success', data.mediaFile)
|
||||
}
|
||||
|
||||
const uploadError = () => {
|
||||
const uploadFailure = () => {
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: '上传失败'
|
||||
|
|
|
|||
|
|
@ -1,22 +1,26 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-upload :action="`${path}/mediaFile/upload`" :show-file-list="false" :on-success="handleImageSuccess"
|
||||
:on-error="handleImageFailure" :before-upload="beforeImageUpload" :multiple="false">
|
||||
<el-button type="primary">图片自动压缩上传</el-button>
|
||||
<el-upload :action="`${path}/cms/mediaFile/upload?category=${props.category}`" :show-file-list="false"
|
||||
:multiple="false" :before-upload="beforeUpload" :on-error="uploadFailure" :on-success="uploadSuccess">
|
||||
<el-button type="primary" plain icon="picture">{{ props.label }}</el-button>
|
||||
</el-upload>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ImageCompress from '@/utils/image'
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import ImageCompress from '@/utils/image'
|
||||
|
||||
defineOptions({
|
||||
name: 'UploadImage',
|
||||
})
|
||||
|
||||
const emit = defineEmits(['on-success', 'on-failure', 'on-before-upload'])
|
||||
const emit = defineEmits([
|
||||
'on-before-upload',
|
||||
'on-failure',
|
||||
'on-success',
|
||||
])
|
||||
const props = defineProps({
|
||||
imageUrl: {
|
||||
type: String,
|
||||
|
|
@ -29,12 +33,15 @@ const props = defineProps({
|
|||
maxWH: {
|
||||
type: Number,
|
||||
default: 1920 // 图片长宽上限
|
||||
},
|
||||
label: {
|
||||
type: String, default: '压缩上传'
|
||||
}
|
||||
})
|
||||
|
||||
const path = ref(import.meta.env.VITE_BASE_API)
|
||||
|
||||
const beforeImageUpload = (file) => {
|
||||
const beforeUpload = (file) => {
|
||||
const isJPG = file.type === 'image/jpeg'
|
||||
const isPng = file.type === 'image/png'
|
||||
if (!isJPG && !isPng) {
|
||||
|
|
@ -54,15 +61,27 @@ const beforeImageUpload = (file) => {
|
|||
return isRightSize
|
||||
}
|
||||
|
||||
const handleImageFailure = (res) => {
|
||||
const uploadFailure = () => {
|
||||
console.log('on-failure')
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: '上传失败'
|
||||
})
|
||||
emit('on-failure')
|
||||
}
|
||||
|
||||
const handleImageSuccess = (res) => {
|
||||
const { data } = res
|
||||
if (data.mediaFile) {
|
||||
emit('on-success', data.mediaFile.url)
|
||||
const uploadSuccess = (res) => {
|
||||
console.log('on-success')
|
||||
const { code, data, msg } = res
|
||||
if (code !== 0) {
|
||||
ElMessage({ type: 'error', message: msg })
|
||||
emit('on-failure')
|
||||
}
|
||||
if (!data.mediaFile) {
|
||||
ElMessage({ type: 'error', message: '返回错误,上传失败' })
|
||||
emit('on-failure')
|
||||
}
|
||||
emit('on-success', data.mediaFile)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -681,7 +681,7 @@ li {
|
|||
}
|
||||
|
||||
.gva-btn-list {
|
||||
@apply mb-3 flex gap-3 items-center;
|
||||
@apply mb-3 flex gap-2 items-center;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
export const equalArr = (arr1, arr2) => {
|
||||
if (arr1.length !== arr2.length) {
|
||||
return false
|
||||
}
|
||||
let i = 0
|
||||
arr1.forEach(item => {
|
||||
arr2.forEach(item2 => {
|
||||
if (item == item2) {
|
||||
i++
|
||||
}
|
||||
})
|
||||
})
|
||||
return i === arr1.length
|
||||
}
|
||||
|
|
@ -0,0 +1,417 @@
|
|||
<template>
|
||||
|
||||
<div v-loading="fullscreenLoading">
|
||||
<div class="gva-form-box">
|
||||
<div v-if="showErrMessage != ''">
|
||||
{{ showErrMessage }}
|
||||
</div>
|
||||
<el-form v-else ref="elEditFormRef" label-position="top" :model="editForm" :rules="formRules">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="17">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="16">
|
||||
<el-form-item label="标题" prop="title">
|
||||
<el-input v-model="editForm.title" autocomplete="off" :show-word-limit="true" maxlength="50" />
|
||||
</el-form-item>
|
||||
<el-form-item label="副标题" prop="subtitle">
|
||||
<el-input v-model="editForm.subtitle" autocomplete="off" :show-word-limit="true" maxlength="50" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="文章类型" prop="articleType">
|
||||
<el-select v-model="editForm.articleType" placeholder="请选择">
|
||||
<el-option v-for="item in articleTypeOptions" :key="item.key" :label="item.label"
|
||||
:value="item.key" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="作者" prop="author">
|
||||
<el-input v-model="editForm.author" autocomplete="off" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="所属栏目" prop="channels">
|
||||
<el-cascader v-model="channelIdList" :options="channelOptions" style="width:100%"
|
||||
:props="{ label: 'title', value: 'ID', emitPath: false, expandTrigger: 'hover', multiple: true }"
|
||||
:collapse-tags="true" :collapse-tags-tooltip="true" :max-collapse-tags="3" filterable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="文章分类" prop="categories">
|
||||
<el-cascader v-model="categoryIdList" :options="categoryOptions" style="width:100%"
|
||||
:props="{ label: 'title', value: 'ID', emitPath: false, expandTrigger: 'hover', multiple: true }"
|
||||
:collapse-tags="true" :collapse-tags-tooltip="true" :max-collapse-tags="2" filterable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="文章内容" prop="content">
|
||||
<RichEdit v-model="editForm.content" style="height: 50rem;" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="1" align="center">
|
||||
<el-divider direction="vertical" style="height: 100%" />
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="摘要" prop="desc">
|
||||
<el-input v-model="editForm.desc" type="textarea" rows="7" :show-word-limit="true" maxlength="100"
|
||||
autocomplete="off" resize="none" />
|
||||
</el-form-item>
|
||||
<el-form-item label="来源" prop="source">
|
||||
<el-select v-model="editForm.source" placeholder="请选择" allow-create clearable filterable>
|
||||
<el-option v-for="item in sourceOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="跳转链接" prop="source">
|
||||
<el-input v-model="editForm.url" autocomplete="off" placeholder="https://" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="发布时间">
|
||||
<el-date-picker v-model="editForm.publishDate" type="datetime" placeholder="请选择" style="width:100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="文章标签" prop="tags">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<el-tag v-for="(item, index) in tagList" :key="index" type="info" closable size="large"
|
||||
@close="tagList.splice(index, 1)">
|
||||
{{ item }}
|
||||
</el-tag>
|
||||
<el-input v-if="tagInputFlag" ref="InputRef" v-model="inputTag" class="w-20" size="default"
|
||||
style="width: 80px;" @keyup.enter="handleTagOK" @keyup.esc="handleTagCancel" @blur="handleTagOK" />
|
||||
<el-button v-else type="primary" plain icon="plus" class="button-new-tag" size="default"
|
||||
style="width: 80px;" @click="handleTagAdd">
|
||||
添加
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="图片(首张图可用来做栏目列表用)" prop="imgs">
|
||||
<ChooseImg ref="chooseImg" category="article_imgs" size="800px" @on-select="handleSelectImg" />
|
||||
<template #label>
|
||||
<div style="margin-bottom: 10px;">图片(首张图可用来做栏目列表用)</div>
|
||||
<div class="flex gap-3">
|
||||
<el-button type="success" plain icon="folder" @click="handleChooseImg">媒体库</el-button>
|
||||
<upload-common class="upload-btn-media-library" category="article_imgs"
|
||||
@on-success="handleImgUpload" />
|
||||
<el-tooltip effect="dark" content="图片超过 512K 或者 长宽 > 1080 会自动压缩后再上传" placement="top-start">
|
||||
<upload-image :file-size="512" :max-w-h="1080" category="article_imgs"
|
||||
@on-success="handleImgUpload" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<div class="media">
|
||||
<div v-for="(item, key) in imgFileList" :key="key" class="media-box">
|
||||
<div class="img-box-list">
|
||||
<el-image :key="key" :src="getUrl(item.url)" fit="cover" :preview-src-list="[getUrl(item.url)]"
|
||||
hide-on-click-modal style="width: 138px; height: 138px;">
|
||||
<template #error>
|
||||
<div class="img-box-list">
|
||||
<el-icon>
|
||||
<picture />
|
||||
</el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
</div>
|
||||
<div class="img-title">
|
||||
<el-button size="small" type="default" icon="delete" @click="handleImgRemove(item)">删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="check" style="width: 150px;" @click="handleFormSubmit">保存</el-button>
|
||||
<el-button type="default" icon="close" @click="handleFormClose">关闭</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getUrl } from '@/utils/image'
|
||||
import { reactive, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import RichEdit from '@/components/richtext/rich-edit.vue'
|
||||
import ChooseImg from '@/components/chooseImg/index.vue'
|
||||
import { getSourceList } from '@/api/source'
|
||||
import { getCategoryTree } from '@/api/category'
|
||||
import { getChannelTree } from '@/api/channel'
|
||||
import UploadCommon from '@/components/upload/common.vue'
|
||||
import UploadImage from '@/components/upload/image.vue'
|
||||
import {
|
||||
addArticle,
|
||||
updateArticle,
|
||||
getArticleById,
|
||||
} from '@/api/article'
|
||||
import { getFetcherArticleById } from '@/api/fetcher'
|
||||
|
||||
const showErrMessage = ref('')
|
||||
|
||||
const fullscreenLoading = ref(true)
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const isEdit = ref(false) // 确定是修改还是增加 isEdit = true 为修改,否则为增加
|
||||
const elEditFormRef = ref()
|
||||
const formRules = reactive({
|
||||
articleType: [
|
||||
{ required: true, message: '请输选择文章类型', trigger: 'blur' }
|
||||
],
|
||||
author: [
|
||||
{ required: true, message: '请输入作者', trigger: 'blur' }
|
||||
],
|
||||
title: [
|
||||
{ required: true, message: '请输入文章名称', trigger: 'blur' }
|
||||
],
|
||||
subtitle: [
|
||||
{ required: true, message: '请输入文章副标题', trigger: 'blur' }
|
||||
],
|
||||
content: [
|
||||
{ required: true, message: '请输入文章内容', trigger: 'blur' }
|
||||
],
|
||||
})
|
||||
const articleTypeOptions = ref([
|
||||
{ key: 1, label: '图文' },
|
||||
{ key: 2, label: '视频' },
|
||||
])
|
||||
const editForm = ref({
|
||||
ID: 0,
|
||||
articleType: '',
|
||||
title: '',
|
||||
subtitle: '',
|
||||
author: '',
|
||||
channels: [],
|
||||
categories: [],
|
||||
desc: '',
|
||||
source: '',
|
||||
url: '',
|
||||
publishDate: '',
|
||||
articleTime: '',
|
||||
tags: '',
|
||||
imgs: '',
|
||||
})
|
||||
// options 相关
|
||||
const initOptions = (data, optionsData) => {
|
||||
data &&
|
||||
data.forEach(item => {
|
||||
if (item.children && item.children.length) {
|
||||
const option = {
|
||||
title: item.title,
|
||||
ID: String(item.ID),
|
||||
children: []
|
||||
}
|
||||
initOptions(
|
||||
item.children,
|
||||
option.children,
|
||||
)
|
||||
optionsData.push(option)
|
||||
} else {
|
||||
const option = {
|
||||
title: item.title,
|
||||
ID: String(item.ID),
|
||||
}
|
||||
optionsData.push(option)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 以下为 options 使用
|
||||
const channelOptions = ref([])
|
||||
const categoryOptions = ref([])
|
||||
const sourceOptions = ref([])
|
||||
|
||||
const getCategoryData = async () => {
|
||||
categoryOptions.value = []
|
||||
const res = await getCategoryTree()
|
||||
if (res.code === 0) {
|
||||
initOptions(res.data.categoryTree, categoryOptions.value, false)
|
||||
}
|
||||
}
|
||||
const getChannelData = async () => {
|
||||
channelOptions.value = []
|
||||
const res = await getChannelTree()
|
||||
if (res.code === 0) {
|
||||
initOptions(res.data.channelTree, channelOptions.value, false)
|
||||
}
|
||||
}
|
||||
const getSourceData = async () => {
|
||||
sourceOptions.value = []
|
||||
const res = await getSourceList({ page: 1, pageSize: 999 })
|
||||
if (res.code === 0) {
|
||||
sourceOptions.value = res.data.list && res.data.list.map(item => item.name)
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化方法
|
||||
const initPage = async () => {
|
||||
getCategoryData()
|
||||
getChannelData()
|
||||
getSourceData()
|
||||
|
||||
// 建议通过url传参获取目标数据ID 调用 find方法进行查询数据操作 从而决定本页面是create还是update 以下为id作为url参数示例
|
||||
if (route.query && route.query.id && route.query.id > 0) {
|
||||
const res = await getArticleById({ ID: route.query.id })
|
||||
if (res.code === 0) {
|
||||
// 初始化表单数据
|
||||
editForm.value = res.data.article
|
||||
isEdit.value = true
|
||||
// 初始化关联数据
|
||||
const { categories, channels, imgs, tags } = res.data.article
|
||||
categoryIdList.value = categories && categories.map(item => String(item.ID))
|
||||
channelIdList.value = channels && channels.map(item => String(item.ID))
|
||||
if (imgs && (imgs !== '')) {
|
||||
imgFileList.value = JSON.parse(imgs)
|
||||
}
|
||||
tagList.value = tags && tags.split(',')
|
||||
fullscreenLoading.value = false
|
||||
} else {
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: '获取数据失败'
|
||||
})
|
||||
showErrMessage.value = '获取数据失败'
|
||||
}
|
||||
} else if (route.query && route.query.fetcherId && route.query.fetcherId > 0) {
|
||||
// 从爬虫数据中获取内容并填充至文章内容中
|
||||
const res = await getFetcherArticleById({ ID: route.query.fetcherId })
|
||||
if (res.code === 0 && res.data && res.data.article) {
|
||||
console.log(res.data)
|
||||
const { title, author, source, content, publicTime } = res.data.article
|
||||
editForm.value.title = title
|
||||
editForm.value.subtitle = title
|
||||
editForm.value.author = author
|
||||
editForm.value.source = source
|
||||
editForm.value.content = content
|
||||
editForm.value.publishDate = publicTime
|
||||
editForm.value.articleTime = publicTime
|
||||
} else {
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: '获取数据失败'
|
||||
})
|
||||
showErrMessage.value = '获取数据失败'
|
||||
}
|
||||
}
|
||||
fullscreenLoading.value = false
|
||||
}
|
||||
|
||||
initPage()
|
||||
|
||||
// ---- 以下为图片相关操作 ----
|
||||
const chooseImg = ref(null)
|
||||
const imgFileList = ref([])
|
||||
const handleChooseImg = (event) => {
|
||||
chooseImg.value.open()
|
||||
event.preventDefault()
|
||||
return false
|
||||
}
|
||||
const handleSelectImg = (file) => {
|
||||
imgFileList.value.push({
|
||||
key: file.key,
|
||||
name: file.name,
|
||||
url: file.url,
|
||||
})
|
||||
}
|
||||
const handleImgRemove = (file) => {
|
||||
imgFileList.value = imgFileList.value.filter(item => item.key !== file.key)
|
||||
}
|
||||
const handleImgUpload = (file) => {
|
||||
imgFileList.value.push({
|
||||
key: file.key,
|
||||
name: file.name,
|
||||
url: file.url,
|
||||
})
|
||||
}
|
||||
|
||||
// ---- 以下为tag相关操作 ----
|
||||
const tagInputFlag = ref(false)
|
||||
const inputTag = ref('')
|
||||
const tagList = ref([])
|
||||
const handleTagOK = (key) => {
|
||||
const val = inputTag.value.trim()
|
||||
if (val !== '' && !tagList.value.includes(val)) {
|
||||
tagList.value.push(val)
|
||||
tagInputFlag.value = false
|
||||
inputTag.value = ''
|
||||
}
|
||||
}
|
||||
const handleTagCancel = (key) => {
|
||||
if (key.keyCode === 27) {
|
||||
// 按下esc
|
||||
tagInputFlag.value = false
|
||||
inputTag.value = ''
|
||||
}
|
||||
}
|
||||
const handleTagAdd = () => {
|
||||
tagInputFlag.value = true
|
||||
}
|
||||
|
||||
const categoryIdList = ref([])
|
||||
const channelIdList = ref([])
|
||||
|
||||
// 添加/保存修改
|
||||
const handleFormSubmit = async () => {
|
||||
const rel = {}
|
||||
rel.categoryIds = categoryIdList.value.map(item => parseInt(item, 10))
|
||||
rel.channelIds = channelIdList.value.map(item => parseInt(item, 10))
|
||||
rel.imgList = imgFileList.value
|
||||
rel.tagList = tagList.value
|
||||
if (editForm.value.publishDate === '') {
|
||||
editForm.value.publishDate = null
|
||||
}
|
||||
if (editForm.value.articleTime === '') {
|
||||
editForm.value.articleTime = null
|
||||
}
|
||||
elEditFormRef.value.validate(async valid => {
|
||||
if (valid) {
|
||||
let res
|
||||
if (isEdit.value) {
|
||||
res = await updateArticle({ article: editForm.value, rel: rel })
|
||||
} else {
|
||||
res = await addArticle({ article: editForm.value, rel: rel })
|
||||
}
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: isEdit.value ? '修改成功' : '添加成功!'
|
||||
})
|
||||
if (!isEdit.value && res.data && res.data.article) {
|
||||
const query = { id: res.data.article.ID }
|
||||
router.replace({ name: 'articleEdit', query })
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 返回按钮
|
||||
const handleFormClose = () => {
|
||||
router.go(-1)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style type="scss">
|
||||
.admin-box .el-table td .cell {
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.text-truncate .cell {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 4;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.img-item {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,481 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="gva-search-box">
|
||||
<el-form ref="elSearchFormRef" :inline="true" :model="searchInfo" label-width="90px" class="demo-form-inline"
|
||||
@keyup.enter="handleSubmitSearch">
|
||||
<el-form-item label="创建日期" prop="createdAt" style="width:300px">
|
||||
<template #label>
|
||||
<span>
|
||||
<el-tooltip content="搜索范围是创建开始日期(包含)至创建结束日期(包含)" placement="top-start">
|
||||
<el-icon>
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
创建日期
|
||||
</span>
|
||||
</template>
|
||||
<el-date-picker v-model="searchInfo.dateRange" type="daterange" value-format="YYYY-MM-DD" :clearable="false"
|
||||
:editable="false" />
|
||||
</el-form-item>
|
||||
<el-form-item label="关键词" style="width:300px">
|
||||
<template #label>
|
||||
<span>
|
||||
<el-tooltip content="从标题、副标题、摘要中搜索" placement="top-start">
|
||||
<el-icon>
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
关键词
|
||||
</span>
|
||||
</template>
|
||||
<el-input v-model="searchInfo.keyword" class="keyword" placeholder="请输入" clearable style="width:100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="所属栏目" prop="channelId" style="width:300px">
|
||||
<el-cascader v-model="searchChannelId" :options="channelOptions" style="width:100%"
|
||||
:props="{ label: 'title', value: 'ID', disabled: 'disabled', emitPath: false }" :show-all-levels="true"
|
||||
clearable placeholder="请选择" filterable />
|
||||
</el-form-item>
|
||||
<el-form-item label="文章分类" prop="categoryId" style="width:300px">
|
||||
<el-cascader v-model="searchCategoryId" :options="categoryOptions" style="width:100%"
|
||||
:props="{ label: 'title', value: 'ID', disabled: 'disabled', emitPath: false }" :show-all-levels="true"
|
||||
clearable placeholder="请选择" filterable />
|
||||
</el-form-item>
|
||||
<el-form-item label="发布状态" prop="status" style="width:300px">
|
||||
<el-select v-model="searchInfo.status" placeholder="请选择" clearable style="width:300px">
|
||||
<el-option v-for="item in statusOptions" :key="item.key" :label="item.label" :value="item.key" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="search" @click="handleSubmitSearch">查询</el-button>
|
||||
<el-button icon="refresh" @click="handleResetSearch">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button type="primary" icon="plus" @click="handleAdd('0')">新增文章</el-button>
|
||||
<el-button icon="delete" style="margin-left: 10px;" :disabled="!multipleSelection.length"
|
||||
@click="handleMultiDelete">删除</el-button>
|
||||
<el-button icon="plus" style="margin-left: 10px;" :disabled="!multipleSelection.length"
|
||||
@click="handleMultiPublish">发布</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 由于此处菜单跟左侧列表一一对应所以不需要分页 pageSize默认999 -->
|
||||
<el-table ref="multipleTable" :data="tableData" row-key="ID" @selection-change="handleSelectionChange">
|
||||
<el-table-column fixed type="selection" width="40" align="center" />
|
||||
<el-table-column align="left" label="ID" min-width="60" prop="ID" />
|
||||
<el-table-column align="left" label="标题/副标题" min-width="270" prop="title">
|
||||
<template #default="scope">
|
||||
{{ scope.row.title }}
|
||||
<div><small>{{ scope.row.subtitle }}</small></div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="摘要" min-width="400" prop="desc" class-name="text-truncate">
|
||||
<template #default="scope">
|
||||
<div>{{ scope.row.desc }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="标签" min-width="150" prop="tags">
|
||||
<template #default="scope">
|
||||
<el-tag v-for="(item, index) in formatTags(scope.row.tags)" :key="index" size="small"
|
||||
style="margin-right: 5px;">
|
||||
{{ item }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="时间" width="280">
|
||||
<template #default="scope">
|
||||
<div><b>发布时间:</b>{{ formatDate(scope.row.publishDate) }}</div>
|
||||
<div><b>最后更新:</b>{{ formatDate(scope.row.UpdatedAt) }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="所属栏目" prop="categories" width="180">
|
||||
<template #default="scope">
|
||||
<el-cascader v-model="scope.row.channelIds" :options="channelOptions" style="width:100%"
|
||||
:props="{ label: 'title', value: 'ID', emitPath: false, expandTrigger: 'hover', multiple: true }"
|
||||
:collapse-tags="true" :max-collapse-tags="3" filterable
|
||||
@visible-change="(flag) => { if (!flag) handleChangeChannels(scope.row) }"
|
||||
@remove-tag="() => { handleChangeChannels(scope.row) }" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=" left" label="文章分类" prop="categories" width="180">
|
||||
<template #default="scope">
|
||||
<el-cascader v-model="scope.row.categoryIds" :options="categoryOptions" style="width:100%"
|
||||
:props="{ label: 'title', value: 'ID', emitPath: false, expandTrigger: 'hover', multiple: true }"
|
||||
:collapse-tags="true" :max-collapse-tags="3" filterable
|
||||
@visible-change="(flag) => { if (!flag) handleChangeCategories(scope.row) }"
|
||||
@remove-tag="() => { handleChangeCategories(scope.row) }" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="作者" min-width="80" prop="author" />
|
||||
<el-table-column align="left" label="类型" min-width="80" prop="articleType">
|
||||
<template #default="scope">{{ formatArticleType(scope.row.articleType) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="状态" width="80">
|
||||
<template #default="scope">{{ formatStatus(scope.row.status) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" fixed="right" label="操作" width="80">
|
||||
<template #default="scope">
|
||||
<div>
|
||||
<el-button type="primary" link icon="memo" :disabled="scope.row.status === 2"
|
||||
@click="handleRowPreview(scope.row)">预览</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<el-button type="primary" link icon="plus" :disabled="scope.row.status === 2"
|
||||
@click="handleRowChange(scope.row)">发布</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<el-button type="primary" link icon="edit" @click="handleRowEdit(scope.row.ID)">编辑</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<el-button type="primary" link icon="delete" @click="handleRowDelete(scope.row.ID)">删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="gva-pagination">
|
||||
<el-pagination layout="total, sizes, prev, pager, next, jumper" :current-page="page" :page-size="pageSize"
|
||||
:page-sizes="[10, 30, 50, 100]" :total="total" @current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, nextTick } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { formatDate } from '@/utils/format'
|
||||
import { getSourceList } from '@/api/source'
|
||||
import { getCategoryTree } from '@/api/category'
|
||||
import { getChannelTree } from '@/api/channel'
|
||||
import { formatTimeToStr } from '@/utils/date'
|
||||
import { equalArr } from '@/utils/arr'
|
||||
import {
|
||||
getArticleList,
|
||||
releaseArticle,
|
||||
deleteArticle,
|
||||
setArticleChannels,
|
||||
setArticleCategories
|
||||
} from '@/api/article'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = ref(10)
|
||||
const searchInfo = ref({})
|
||||
const searchCategoryId = ref('')
|
||||
const searchChannelId = ref('')
|
||||
const tableData = ref([])
|
||||
const multipleSelection = ref([])// 多选数据
|
||||
const elSearchFormRef = ref()
|
||||
const articleTypeOptions = ref([
|
||||
{ key: 1, label: '图文' },
|
||||
{ key: 2, label: '视频' },
|
||||
])
|
||||
const statusOptions = ref([
|
||||
{ key: 1, label: '未发布' },
|
||||
{ key: 2, label: '已发布' },
|
||||
])
|
||||
|
||||
const formatArticleType = (value) => {
|
||||
const rowLabel = articleTypeOptions.value.filter(item => item.key === value)
|
||||
return rowLabel && rowLabel[0] && rowLabel[0].label
|
||||
}
|
||||
|
||||
const formatStatus = (value) => {
|
||||
const rowLabel = statusOptions.value.filter(item => item.key === value)
|
||||
return rowLabel && rowLabel[0] && rowLabel[0].label
|
||||
}
|
||||
|
||||
const formatTags = (tags) => {
|
||||
return tags && tags.split(',')
|
||||
}
|
||||
|
||||
const initSearchInfo = () => {
|
||||
const endDate = new Date()
|
||||
const startDate = new Date(endDate.getTime() - 7 * 24 * 60 * 60 * 1000)
|
||||
|
||||
searchChannelId.value = ''
|
||||
searchCategoryId.value = ''
|
||||
|
||||
searchInfo.value = {
|
||||
dateRange: [
|
||||
formatTimeToStr(startDate, 'yyyy-MM-dd'),
|
||||
formatTimeToStr(endDate, 'yyyy-MM-dd'),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
initSearchInfo()
|
||||
|
||||
// 多选
|
||||
const handleSelectionChange = (val) => {
|
||||
multipleSelection.value = val
|
||||
}
|
||||
|
||||
const handleMultiDelete = () => {
|
||||
// @todo 批量删除
|
||||
}
|
||||
const handleMultiPublish = () => {
|
||||
// @todo 批量发布
|
||||
}
|
||||
|
||||
// 重置
|
||||
const handleResetSearch = () => {
|
||||
initSearchInfo()
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const handleSubmitSearch = () => {
|
||||
elSearchFormRef.value?.validate(async (valid) => {
|
||||
if (!valid) return
|
||||
if (searchInfo.value.status === '') {
|
||||
searchInfo.value.status = null
|
||||
}
|
||||
if (searchChannelId.value !== '') {
|
||||
searchInfo.value.channelId = parseInt(searchChannelId.value, 10)
|
||||
}
|
||||
if (searchCategoryId.value !== '') {
|
||||
searchInfo.value.categoryId = parseInt(searchCategoryId.value, 10)
|
||||
}
|
||||
getTableData()
|
||||
})
|
||||
}
|
||||
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 修改页面容量
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 添加
|
||||
const handleAdd = () => {
|
||||
router.push({ name: 'articleEdit' })
|
||||
}
|
||||
|
||||
// 修改
|
||||
const handleRowEdit = (ID) => {
|
||||
const query = { id: ID }
|
||||
router.push({ name: 'articleEdit', query })
|
||||
}
|
||||
|
||||
// 删除
|
||||
const handleRowDelete = (ID) => {
|
||||
ElMessageBox.confirm('此操作将永久删除文章, 是否继续?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
const res = await deleteArticle({ ID })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功!'
|
||||
})
|
||||
if (tableData.value.length === 1 && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 发布
|
||||
const handleRowChange = (row) => {
|
||||
ElMessageBox.confirm('此操作将发布文章, 是否继续?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
const res = await releaseArticle({ ID: row.ID, status: 2 })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '发布成功!'
|
||||
})
|
||||
if (tableData.value.length === 1 && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 预览
|
||||
const handleRowPreview = (row) => {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '开发中。。。'
|
||||
})
|
||||
}
|
||||
|
||||
// ----- 列表更新栏目相关 -----
|
||||
const handleChangeChannels = async (row) => {
|
||||
await nextTick()
|
||||
const ids = row.channelIds.map(i => { return parseInt(i) })
|
||||
if (ids.length === 0) {
|
||||
restoreChannels(row, '所属栏目不能为空')
|
||||
return
|
||||
}
|
||||
// 比较是否有变化
|
||||
const existArr = row.channels && row.channels.map(i => { return String(i.ID) })
|
||||
if (equalArr(existArr, ids)) {
|
||||
return
|
||||
}
|
||||
const res = await setArticleChannels({
|
||||
ID: row.ID,
|
||||
channelIds: ids
|
||||
})
|
||||
if (res.code === 0) {
|
||||
ElMessage({ type: 'success', message: '所属栏目设置成功' })
|
||||
getTableData()
|
||||
} else {
|
||||
// 恢复回去
|
||||
restoreChannels(row, res.message)
|
||||
}
|
||||
}
|
||||
const restoreChannels = (row, message) => {
|
||||
row.channelIds = row.channels && row.channels.map(i => { return String(i.ID) })
|
||||
ElMessage({ type: 'error', message: message })
|
||||
}
|
||||
// ----- 列表分类相关 -----
|
||||
const handleChangeCategories = async (row) => {
|
||||
await nextTick()
|
||||
const ids = row.categoryIds.map(i => { return parseInt(i) })
|
||||
if (ids.length === 0) {
|
||||
restoreCategories(row, '文章分类不能为空')
|
||||
return
|
||||
}
|
||||
// 比较是否有变化
|
||||
const existArr = row.categories && row.categories.map(i => { return String(i.ID) })
|
||||
if (equalArr(existArr, ids)) {
|
||||
return
|
||||
}
|
||||
const res = await setArticleCategories({
|
||||
ID: row.ID,
|
||||
categoryIds: ids
|
||||
})
|
||||
if (res.code === 0) {
|
||||
ElMessage({ type: 'success', message: '文章分类设置成功' })
|
||||
getTableData()
|
||||
} else {
|
||||
// 恢复回去
|
||||
restoreCategories(row, res.message)
|
||||
}
|
||||
}
|
||||
const restoreCategories = (row, message) => {
|
||||
row.categoryIds = row.categories && row.categories.map(i => { return String(i.ID) })
|
||||
ElMessage({ type: 'error', message: message })
|
||||
}
|
||||
|
||||
watch(() => tableData.value, () => {
|
||||
// 初始化数据
|
||||
setCategoryIds()
|
||||
setChannelIds()
|
||||
})
|
||||
|
||||
// ----- 查询 -----
|
||||
const getTableData = async () => {
|
||||
const res = await getArticleList({ page: page.value, pageSize: pageSize.value, ...searchInfo.value })
|
||||
if (res.code === 0) {
|
||||
tableData.value = res.data.list
|
||||
total.value = res.data.total
|
||||
}
|
||||
}
|
||||
|
||||
const setCategoryIds = () => {
|
||||
tableData.value && tableData.value.forEach((row) => {
|
||||
row.categoryIds = row.categories && row.categories.map(i => { return String(i.ID) })
|
||||
})
|
||||
}
|
||||
|
||||
const setChannelIds = () => {
|
||||
tableData.value && tableData.value.forEach((row) => {
|
||||
row.channelIds = row.channels && row.channels.map(i => { return String(i.ID) })
|
||||
})
|
||||
}
|
||||
|
||||
// options 相关
|
||||
const initOptions = (data, optionsData, disabled) => {
|
||||
data &&
|
||||
data.forEach(item => {
|
||||
if (item.children && item.children.length) {
|
||||
const option = {
|
||||
title: item.title,
|
||||
ID: String(item.ID),
|
||||
children: []
|
||||
}
|
||||
initOptions(
|
||||
item.children,
|
||||
option.children,
|
||||
)
|
||||
optionsData.push(option)
|
||||
} else {
|
||||
const option = {
|
||||
title: item.title,
|
||||
ID: String(item.ID),
|
||||
}
|
||||
optionsData.push(option)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const channelOptions = ref([])
|
||||
const categoryOptions = ref([])
|
||||
|
||||
const getCategoryData = async () => {
|
||||
categoryOptions.value = []
|
||||
const res = await getCategoryTree()
|
||||
if (res.code === 0) {
|
||||
initOptions(res.data.categoryTree, categoryOptions.value, false)
|
||||
}
|
||||
}
|
||||
const getChannelData = async () => {
|
||||
channelOptions.value = []
|
||||
const res = await getChannelTree()
|
||||
if (res.code === 0) {
|
||||
initOptions(res.data.channelTree, channelOptions.value, false)
|
||||
}
|
||||
}
|
||||
const getSourceData = async () => {
|
||||
channelOptions.value = []
|
||||
const res = await getSourceList({ page: 1, pageSize: 999 })
|
||||
if (res.code === 0) {
|
||||
res.data.list && res.data.list.map(item => item.name)
|
||||
}
|
||||
}
|
||||
|
||||
const setOptions = () => {
|
||||
getCategoryData()
|
||||
getChannelData()
|
||||
getSourceData()
|
||||
}
|
||||
|
||||
setOptions()
|
||||
getTableData()
|
||||
|
||||
</script>
|
||||
|
||||
<style type="scss">
|
||||
.admin-box .el-table td .cell {
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.text-truncate .cell {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 4;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -5,21 +5,16 @@
|
|||
<el-button type="primary" icon="plus" @click="clickAdd('0')">新增根分类</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 由于此处菜单跟左侧列表一一对应所以不需要分页 pageSize默认999 -->
|
||||
<!-- 由于此处分类跟左侧列表一一对应所以不需要分页 pageSize默认999 -->
|
||||
<el-table :data="tableData" row-key="ID">
|
||||
<el-table-column align="left" label="ID" min-width="80" prop="ID"/>
|
||||
<el-table-column align="left" label="ID" min-width="80" prop="ID" />
|
||||
<el-table-column align="left" label="分类名称" min-width="200" prop="categoryName">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.title }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="是否隐藏" min-width="100" prop="hidden">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.hidden?"隐藏":"显示" }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="父节点" min-width="90" prop="parentId"/>
|
||||
<el-table-column align="left" label="排序" min-width="100" prop="sort"/>
|
||||
<el-table-column align="left" label="父节点" min-width="90" prop="parentId" />
|
||||
<el-table-column align="left" label="排序" min-width="100" prop="sort" />
|
||||
<el-table-column align="left" fixed="right" label="操作" width="300">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link icon="plus" @click="clickAdd(scope.row.ID)">添加子类</el-button>
|
||||
|
|
@ -30,23 +25,19 @@
|
|||
</el-table>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="dialogFormVisible" :before-close="handleClose" :title="dialogTitle">
|
||||
<warning-bar title="新增分类,需要在角色管理内配置权限才可使用" />
|
||||
<el-form v-if="dialogFormVisible" ref="categoryForm" :inline="true" :model="form" :rules="rules" label-position="top" label-width="85px">
|
||||
<el-form-item label="分类名称" prop="path" style="width:30%">
|
||||
<el-input v-model="form.title" autocomplete="off"/>
|
||||
<el-dialog v-model="dialogFormVisible" :before-close="handleClose" :title="dialogTitle" style="max-width: 400px;">
|
||||
<el-form v-if="dialogFormVisible" ref="categoryForm" :inline="true" :model="form" :rules="rules"
|
||||
label-position="top" label-width="85px">
|
||||
<el-form-item label="分类名称" prop="path" style="width:90%">
|
||||
<el-input v-model="form.title" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否隐藏" style="width:30%">
|
||||
<el-select v-model="form.hidden" placeholder="是否在列表隐藏">
|
||||
<el-option :value="false" label="否" />
|
||||
<el-option :value="true" label="是" />
|
||||
</el-select>
|
||||
<el-form-item label="父节点ID" style="width:90%">
|
||||
<el-cascader v-model="form.parentId" style="width:100%" :disabled="!isEdit" :options="categoryOption"
|
||||
:props="{ checkStrictly: true, label: 'title', value: 'ID', disabled: 'disabled', emitPath: false }"
|
||||
:show-all-levels="false" filterable />
|
||||
</el-form-item>
|
||||
<el-form-item label="父节点ID" style="width:30%">
|
||||
<el-cascader v-model="form.parentId" style="width:100%" :disabled="!isEdit" :options="categoryOption" :props="{ checkStrictly: true,label:'title',value:'ID',disabled:'disabled',emitPath:false}" :show-all-levels="false" filterable/>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sort" style="width:30%">
|
||||
<el-input v-model.number="form.sort" autocomplete="off"/>
|
||||
<el-form-item label="排序" prop="sort" style="width:90%">
|
||||
<el-input v-model.number="form.sort" autocomplete="off" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
|
|
@ -69,7 +60,7 @@ import {
|
|||
updateCategory,
|
||||
deleteCategory,
|
||||
getCategoryById
|
||||
} from '@/api/articleCategory'
|
||||
} from '@/api/category'
|
||||
|
||||
import WarningBar from '@/components/warningBar/warningBar.vue'
|
||||
|
||||
|
|
@ -82,7 +73,7 @@ const rules = reactive({
|
|||
const tableData = ref([])
|
||||
|
||||
// 查询
|
||||
const getTableData = async() => {
|
||||
const getTableData = async () => {
|
||||
const res = await getCategoryTree()
|
||||
if (res.code === 0) {
|
||||
tableData.value = res.data.categoryTree
|
||||
|
|
@ -137,12 +128,11 @@ const form = ref({
|
|||
ID: 0,
|
||||
parentId: '',
|
||||
title: '',
|
||||
hidden: false,
|
||||
sort: '',
|
||||
})
|
||||
|
||||
// 添加分类
|
||||
const enterDialog = async() => {
|
||||
// 添加
|
||||
const enterDialog = async () => {
|
||||
categoryForm.value.validate(async valid => {
|
||||
if (valid) {
|
||||
let res
|
||||
|
|
@ -166,15 +156,12 @@ const enterDialog = async() => {
|
|||
|
||||
// 初始化弹窗内表格方法
|
||||
const categoryForm = ref(null)
|
||||
const checkFlag = ref(false)
|
||||
const initForm = () => {
|
||||
checkFlag.value = false
|
||||
categoryForm.value.resetFields()
|
||||
form.value = {
|
||||
ID: 0,
|
||||
parentId: '',
|
||||
title: '',
|
||||
hidden: false,
|
||||
sort: '',
|
||||
}
|
||||
}
|
||||
|
|
@ -190,14 +177,14 @@ const closeDialog = () => {
|
|||
dialogFormVisible.value = false
|
||||
}
|
||||
|
||||
// 删除菜单
|
||||
// 删除
|
||||
const clickDelete = (ID) => {
|
||||
ElMessageBox.confirm('此操作将永久删除分类, 是否继续?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async() => {
|
||||
.then(async () => {
|
||||
const res = await deleteCategory({ ID })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
|
|
@ -218,7 +205,7 @@ const clickDelete = (ID) => {
|
|||
})
|
||||
}
|
||||
|
||||
// 添加分类方法,id为 0则为添加根分类
|
||||
// 添加方法,id为 0则为添加根
|
||||
const isEdit = ref(false)
|
||||
const dialogTitle = ref('新增分类')
|
||||
const clickAdd = (id) => {
|
||||
|
|
@ -228,10 +215,10 @@ const clickAdd = (id) => {
|
|||
setOptions()
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
// 修改菜单方法
|
||||
const clickEdit = async(id) => {
|
||||
// 修改方法
|
||||
const clickEdit = async (ID) => {
|
||||
dialogTitle.value = '编辑分类'
|
||||
const res = await getCategoryById({ id })
|
||||
const res = await getCategoryById({ ID })
|
||||
form.value = res.data.category
|
||||
isEdit.value = true
|
||||
setOptions()
|
||||
|
|
@ -239,4 +226,4 @@ const clickEdit = async(id) => {
|
|||
}
|
||||
|
||||
</script>
|
||||
|
||||
@/api/category
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
<div>
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button type="primary" icon="plus" @click="clickAdd('0')">新增根栏目</el-button>
|
||||
<el-button type="primary" icon="plus" @click="handleAdd('0')">新增根栏目</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 由于此处菜单跟左侧列表一一对应所以不需要分页 pageSize默认999 -->
|
||||
|
|
@ -13,60 +13,99 @@
|
|||
<el-table-column align="left" label="摘要" min-width="260" prop="description" />
|
||||
<el-table-column align="left" label="关键词" min-width="260" prop="keyword" />
|
||||
<el-table-column align="left" label="介绍" min-width="260" prop="introduce" />
|
||||
<el-table-column align="left" label="模板ID" min-width="80" prop="templateId" />
|
||||
<el-table-column align="left" label="文章页模板ID" min-width="140" prop="templateArticleId" />
|
||||
<el-table-column align="left" label="栏目模板" min-width="80" prop="channelTplId" />
|
||||
<el-table-column align="left" label="文章页模板" min-width="140" prop="articleTplId" />
|
||||
<el-table-column align="left" label="默认显示文章数" min-width="140" prop="pageNum" />
|
||||
<el-table-column align="left" label="父节点" min-width="90" prop="parentId" />
|
||||
<el-table-column align="left" fixed="right" label="操作" width="300">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link icon="plus" @click="clickAdd(scope.row.ID)">添加子栏目</el-button>
|
||||
<el-button type="primary" link icon="edit" @click="clickEdit(scope.row.ID)">编辑</el-button>
|
||||
<el-button type="primary" link icon="delete" @click="clickDelete(scope.row.ID)">删除</el-button>
|
||||
<el-button type="primary" link icon="plus" @click="handleAdd(scope.row.ID)">添加子栏目</el-button>
|
||||
<el-button type="primary" link icon="edit" @click="handleEdit(scope.row.ID)">编辑</el-button>
|
||||
<el-button type="primary" link icon="delete" @click="handleDelete(scope.row.ID)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="dialogFormVisible" :before-close="handleClose" :title="dialogTitle">
|
||||
<el-form v-if="dialogFormVisible" ref="channelForm" label-position="top" label-width="85px" :inline="true"
|
||||
<el-drawer v-model="dialogFormVisible" size="550" :show-close="false" :before-close="handleCloseDialog"
|
||||
:close-on-click-modal="false" :close-on-press-escape="false">
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-lg">{{ !isEdit ? '添加' : '修改' }}栏目</span>
|
||||
<div>
|
||||
<el-button type="primary" @click="handleFormSubmit">确 定</el-button>
|
||||
<el-button @click="handleCloseDialog">取 消</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-form v-if="dialogFormVisible" ref="channelForm" label-position="right" label-width="auto" :inline="true"
|
||||
:model="form" :rules="rules">
|
||||
<el-form-item label="名称(导航用)" prop="name" style="width:45%">
|
||||
<div class="section-title">
|
||||
<span>基本设置</span>
|
||||
</div>
|
||||
<el-form-item label="名称" prop="name" style="width: 88%">
|
||||
<template #label>
|
||||
<span>名称</span>
|
||||
<span>
|
||||
<el-tooltip content="导航用" placement="top">
|
||||
<el-icon style="margin-left: 3px; margin-top: 9px;">
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-input v-model="form.name" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="标题(栏目页使用)" prop="title" style="width:45%">
|
||||
<el-form-item label="标题" prop="title" style="width: 88%">
|
||||
<template #label>
|
||||
<span>标题</span>
|
||||
<span>
|
||||
<el-tooltip content="栏目页使用" placement="top">
|
||||
<el-icon style="margin-left: 3px; margin-top: 9px;">
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-input v-model="form.title" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="摘要" prop="description" style="width:94%">
|
||||
<el-input v-model="form.description" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="关键词" prop="keyword" style="width:94%">
|
||||
<el-input v-model="form.keyword" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="介绍" prop="introduce" style="width:94%">
|
||||
<el-input v-model="form.introduce" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="模板ID" prop="templateId" style="width:45%">
|
||||
<el-input v-model="form.templateId" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="文章页模板ID" prop="templateArticleId" style="width:45%">
|
||||
<el-input v-model="form.templateArticleId" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="默认显示文章条数" prop="pageNum" style="width:45%">
|
||||
<el-form-item label="每页数量" prop="pageNum" style="width: 88%">
|
||||
<el-input-number v-model="form.pageNum" autocomplete="off" style="width:100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="父节点ID" style="width:45%">
|
||||
<el-form-item label="父节点ID" style="width: 88%">
|
||||
<el-cascader v-model="form.parentId" style="width:100%" :disabled="!isEdit" :options="channelOption"
|
||||
:props="{ checkStrictly: true, label: 'title', value: 'ID', disabled: 'disabled', emitPath: false }"
|
||||
:show-all-levels="false" filterable />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="closeDialog">取 消</el-button>
|
||||
<el-button type="primary" @click="enterDialog">确 定</el-button>
|
||||
<el-divider />
|
||||
<div class="section-title">
|
||||
<span>Meta设置</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-form-item label="摘要" prop="description" style="width: 88%">
|
||||
<el-input v-model="form.description" type="textarea" rows="2" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="关键词" prop="keyword" style="width: 88%">
|
||||
<el-input v-model="form.keyword" type="textarea" rows="2" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="介绍" prop="introduce" style="width: 88%">
|
||||
<el-input v-model="form.introduce" type="textarea" rows="2" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-divider />
|
||||
<div class="section-title">
|
||||
<span>模板设置</span>
|
||||
</div>
|
||||
<el-form-item label="栏目模板" prop="channelTplId" style="width: 88%">
|
||||
<el-select v-model="form.channelTplId" placeholder="请选择" style="width: 240px">
|
||||
<el-option v-for="item in channelTplOptions" :key="item.ID" :label="item.title" :value="item.ID" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="文章页模板" prop="articleTplId" style="width: 88%">
|
||||
<el-select v-model="form.articleTplId" placeholder="请选择" style="width: 240px">
|
||||
<el-option v-for="item in articleTplOptions" :key="item.ID" :label="item.title" :value="item.ID" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -94,9 +133,8 @@ const rules = reactive({
|
|||
],
|
||||
})
|
||||
|
||||
const tableData = ref([])
|
||||
|
||||
// 查询
|
||||
const tableData = ref([])
|
||||
const getTableData = async () => {
|
||||
const res = await getChannelTree()
|
||||
if (res.code === 0) {
|
||||
|
|
@ -109,14 +147,14 @@ getTableData()
|
|||
const channelOption = ref([
|
||||
{
|
||||
ID: '0',
|
||||
name: '根栏目'
|
||||
title: '根栏目'
|
||||
}
|
||||
])
|
||||
const setOptions = () => {
|
||||
channelOption.value = [
|
||||
{
|
||||
ID: '0',
|
||||
name: '根栏目'
|
||||
title: '根栏目'
|
||||
}
|
||||
]
|
||||
setChannelOptions(tableData.value, channelOption.value, false)
|
||||
|
|
@ -148,6 +186,22 @@ const setChannelOptions = (channelData, optionsData, disabled) => {
|
|||
})
|
||||
}
|
||||
|
||||
// ------ 获取模板 options ------
|
||||
import { getTplOptions } from '@/api/channel'
|
||||
const articleTplOptions = ref([])
|
||||
const channelTplOptions = ref([])
|
||||
const setTplOptions = async () => {
|
||||
channelTplOptions.value = []
|
||||
const res = await getTplOptions()
|
||||
if (res.code === 0) {
|
||||
articleTplOptions.value = res.data.articleTplOptions
|
||||
channelTplOptions.value = res.data.channelTplOptions
|
||||
}
|
||||
}
|
||||
// 初始化数据
|
||||
setTplOptions()
|
||||
|
||||
// ----- 以下为 form 操作 -----
|
||||
const form = ref({
|
||||
ID: 0,
|
||||
parentId: 0,
|
||||
|
|
@ -156,13 +210,13 @@ const form = ref({
|
|||
description: '',
|
||||
keyword: '',
|
||||
introduce: '',
|
||||
templateId: 0,
|
||||
templateArticleId: 0,
|
||||
channelTplId: 0,
|
||||
articleTplId: 0,
|
||||
pageNum: 10,
|
||||
})
|
||||
|
||||
// 添加栏目
|
||||
const enterDialog = async () => {
|
||||
// 提交表单
|
||||
const handleFormSubmit = async () => {
|
||||
channelForm.value.validate(async valid => {
|
||||
if (valid) {
|
||||
let res
|
||||
|
|
@ -198,25 +252,21 @@ const initForm = () => {
|
|||
description: '',
|
||||
keyword: '',
|
||||
introduce: '',
|
||||
templateId: 0,
|
||||
templateArticleId: 0,
|
||||
channelTplId: 0,
|
||||
articleTplId: 0,
|
||||
pageNum: 10,
|
||||
}
|
||||
}
|
||||
const handleClose = (done) => {
|
||||
initForm()
|
||||
done()
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
const dialogFormVisible = ref(false)
|
||||
const closeDialog = () => {
|
||||
const handleCloseDialog = () => {
|
||||
initForm()
|
||||
dialogFormVisible.value = false
|
||||
}
|
||||
|
||||
// 删除菜单
|
||||
const clickDelete = (ID) => {
|
||||
const handleDelete = (ID) => {
|
||||
ElMessageBox.confirm('此操作将永久删除栏目, 是否继续?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
|
|
@ -246,7 +296,7 @@ const clickDelete = (ID) => {
|
|||
// 添加栏目方法,id为 0则为添加根栏目
|
||||
const isEdit = ref(false)
|
||||
const dialogTitle = ref('新增栏目')
|
||||
const clickAdd = (id) => {
|
||||
const handleAdd = (id) => {
|
||||
dialogTitle.value = '新增栏目'
|
||||
form.value.parentId = String(id)
|
||||
isEdit.value = false
|
||||
|
|
@ -254,9 +304,9 @@ const clickAdd = (id) => {
|
|||
dialogFormVisible.value = true
|
||||
}
|
||||
// 修改菜单方法
|
||||
const clickEdit = async (id) => {
|
||||
const handleEdit = async (ID) => {
|
||||
dialogTitle.value = '编辑栏目'
|
||||
const res = await getChannelById({ id })
|
||||
const res = await getChannelById({ ID })
|
||||
form.value = res.data.channel
|
||||
isEdit.value = true
|
||||
setOptions()
|
||||
|
|
@ -264,3 +314,11 @@ const clickEdit = async (id) => {
|
|||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style type="scss">
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="gva-search-box">
|
||||
<el-form ref="elSearchFormRef" :inline="true" :model="searchInfo" label-width="90px" class="demo-form-inline"
|
||||
@keyup.enter="handleSubmitSearch">
|
||||
<el-form-item label="抓取日期" prop="publishDate" style="width:300px">
|
||||
<template #label>
|
||||
<span>
|
||||
<el-tooltip content="搜索范围是开始日期(包含)至结束日期(包含)" placement="top-start">
|
||||
<el-icon>
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
抓取日期
|
||||
</span>
|
||||
</template>
|
||||
<el-date-picker v-model="searchInfo.dateRange" type="daterange" value-format="YYYY-MM-DD" :clearable="false"
|
||||
:editable="false" />
|
||||
</el-form-item>
|
||||
<el-form-item label="关键词" style="width:300px">
|
||||
<template #label>
|
||||
<span>
|
||||
<el-tooltip content="从标题中搜索" placement="top-start">
|
||||
<el-icon>
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
关键词
|
||||
</span>
|
||||
</template>
|
||||
<el-input v-model="searchInfo.keyword" class="keyword" placeholder="请输入" clearable style="width:100%" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="search" @click="handleSubmitSearch">查询</el-button>
|
||||
<el-button icon="refresh" @click="handleResetSearch">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="gva-table-box">
|
||||
<!-- 由于此处菜单跟左侧列表一一对应所以不需要分页 pageSize默认999 -->
|
||||
<el-table ref="multipleTable" :data="tableData" row-key="ID">
|
||||
<el-table-column align="left" label="ID" min-width="60" prop="ID" />
|
||||
<el-table-column align="left" label="标题" min-width="270" prop="title" />
|
||||
<el-table-column align="left" label="来源" min-width="120" prop="source">
|
||||
<template #default="scope">
|
||||
<a :href="scope.row.sourceUrl" target="_blank">{{ scope.row.source }}</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="作者" min-width="100" prop="author" />
|
||||
<el-table-column align="left" label="采集时间" min-width="120" prop="createtime">
|
||||
<template #default="scope">
|
||||
{{ formatDate(scope.row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="发布时间" min-width="120" prop="createtime">
|
||||
<template #default="scope">
|
||||
{{ formatDate(scope.row.publicTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" fixed="right" label="操作" width="80">
|
||||
<template #default="scope">
|
||||
<div>
|
||||
<el-button type="primary" link icon="plus" :disabled="scope.row.status === 2"
|
||||
@click="handleRowEdit(scope.row.ID)">发布</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="gva-pagination">
|
||||
<el-pagination layout="total, sizes, prev, pager, next, jumper" :current-page="page" :page-size="pageSize"
|
||||
:page-sizes="[10, 30, 50, 100]" :total="total" @current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { formatDate } from '@/utils/format'
|
||||
import { formatTimeToStr } from '@/utils/date'
|
||||
import { getFetcherArticleList } from '@/api/fetcher'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = ref(10)
|
||||
const searchInfo = ref({})
|
||||
const tableData = ref([])
|
||||
const elSearchFormRef = ref()
|
||||
|
||||
const initSearchInfo = () => {
|
||||
const endDate = new Date()
|
||||
const startDate = new Date(endDate.getTime() - 7 * 24 * 60 * 60 * 1000)
|
||||
searchInfo.value = {
|
||||
dateRange: [
|
||||
formatTimeToStr(startDate, 'yyyy-MM-dd'),
|
||||
formatTimeToStr(endDate, 'yyyy-MM-dd'),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
initSearchInfo()
|
||||
|
||||
// 重置
|
||||
const handleResetSearch = () => {
|
||||
initSearchInfo()
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const handleSubmitSearch = () => {
|
||||
elSearchFormRef.value?.validate(async (valid) => {
|
||||
if (!valid) return
|
||||
getTableData()
|
||||
})
|
||||
}
|
||||
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 修改页面容量
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 修改
|
||||
const handleRowEdit = (ID) => {
|
||||
const query = { fetcherId: ID }
|
||||
router.push({ name: 'articleEdit', query })
|
||||
}
|
||||
|
||||
// ----- 查询 -----
|
||||
const getTableData = async () => {
|
||||
const res = await getFetcherArticleList({ page: page.value, pageSize: pageSize.value, ...searchInfo.value })
|
||||
if (res.code === 0) {
|
||||
tableData.value = res.data.list
|
||||
total.value = res.data.total
|
||||
}
|
||||
}
|
||||
getTableData()
|
||||
|
||||
</script>
|
||||
|
||||
<style type="scss">
|
||||
.admin-box .el-table td .cell {
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.text-truncate .cell {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 4;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -3,16 +3,16 @@
|
|||
<div class="gva-table-box">
|
||||
<warning-bar title="点击“文件名/备注”可以编辑文件名或者备注内容。" />
|
||||
<div class="gva-btn-list">
|
||||
<upload-common :image-common="imageCommon" @on-success="uploadSuccess" @on-failure="uploadFailure"
|
||||
<upload-common category="media" label="直接上传" @on-success="uploadSuccess" @on-failure="uploadFailure"
|
||||
@on-before-upload="beforeUpload" />
|
||||
<upload-image :image-url="imageUrl" :file-size="512" :max-w-h="1080" @on-success="uploadSuccess"
|
||||
<upload-image category="media" label="图片压缩上传" :file-size="512" :max-w-h="1080" @on-success="uploadSuccess"
|
||||
@on-failure="uploadFailure" @on-before-upload="beforeUpload" />
|
||||
<el-input v-model="search.keyword" class="keyword" placeholder="请输入文件名或备注" />
|
||||
<el-input v-model="search.keyword" class="keyword" placeholder="请输入文件名或备注" clearable />
|
||||
<el-button type="primary" icon="search" @click="getTableData">查询</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="tableData">
|
||||
<el-table-column align="left" label="预览" width="100">
|
||||
<el-table-column align="left" label="预览" width="120">
|
||||
<template #default="scope">
|
||||
<CustomPic pic-type="file" :pic-src="scope.row.url" preview />
|
||||
</template>
|
||||
|
|
@ -121,7 +121,7 @@ const deleteFileFunc = async (row) => {
|
|||
type: 'warning',
|
||||
})
|
||||
.then(async () => {
|
||||
const res = await deleteFile(row)
|
||||
const res = await deleteFile({ ID: row.ID })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
|
|
@ -145,7 +145,6 @@ const downloadFile = (row) => {
|
|||
if (row.url.indexOf('http://') > -1 || row.url.indexOf('https://') > -1) {
|
||||
downloadImage(row.url, row.name)
|
||||
} else {
|
||||
debugger
|
||||
downloadImage(path.value + '/' + row.url, row.name)
|
||||
}
|
||||
}
|
||||
|
|
@ -164,7 +163,6 @@ const editFileNameFunc = async (row) => {
|
|||
inputValue: row.name
|
||||
}).then(async ({ value }) => {
|
||||
row.name = value
|
||||
// console.log(row)
|
||||
const res = await editFileName(row)
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
|
|
|
|||
|
|
@ -1,29 +1,13 @@
|
|||
<template>
|
||||
<div :style="{ background: userStore.sideMode }">
|
||||
<el-scrollbar style="height: calc(100vh - 60px)">
|
||||
<transition
|
||||
:duration="{ enter: 800, leave: 100 }"
|
||||
mode="out-in"
|
||||
name="el-fade-in-linear"
|
||||
>
|
||||
<el-menu
|
||||
:collapse="isCollapse"
|
||||
:collapse-transition="false"
|
||||
:default-active="active"
|
||||
:background-color="theme.background"
|
||||
:active-text-color="theme.active"
|
||||
class="el-menu-vertical"
|
||||
unique-opened
|
||||
@select="selectMenuItem"
|
||||
>
|
||||
<transition :duration="{ enter: 800, leave: 100 }" mode="out-in" name="el-fade-in-linear">
|
||||
<el-menu :collapse="isCollapse" :collapse-transition="false" :default-active="active"
|
||||
:background-color="theme.background" :active-text-color="theme.active" class="el-menu-vertical" unique-opened
|
||||
@select="selectMenuItem">
|
||||
<template v-for="item in routerStore.asyncRouters[0].children">
|
||||
<aside-component
|
||||
v-if="!item.hidden"
|
||||
:key="item.name"
|
||||
:is-collapse="isCollapse"
|
||||
:router-info="item"
|
||||
:theme="theme"
|
||||
/>
|
||||
<aside-component v-if="!item.hidden" :key="item.name" :is-collapse="isCollapse" :router-info="item"
|
||||
:theme="theme" />
|
||||
</template>
|
||||
</el-menu>
|
||||
</transition>
|
||||
|
|
@ -121,13 +105,13 @@ const selectMenuItem = (index, _, ele, aaa) => {
|
|||
if (index.indexOf('http://') > -1 || index.indexOf('https://') > -1) {
|
||||
window.open(index)
|
||||
} else {
|
||||
console.log(index, query, params)
|
||||
router.push({ name: index, query, params })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
.el-sub-menu__title:hover,
|
||||
.el-menu-item:hover {
|
||||
background: transparent;
|
||||
|
|
@ -138,6 +122,7 @@ const selectMenuItem = (index, _, ele, aaa) => {
|
|||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-info {
|
||||
.menu-contorl {
|
||||
line-height: 52px;
|
||||
|
|
|
|||
|
|
@ -1,106 +1,49 @@
|
|||
<template>
|
||||
<el-container class="layout-cont">
|
||||
<el-container :class="[isSider?'openside':'hideside',isMobile ? 'mobile': '']">
|
||||
<el-container :class="[isSider ? 'openside' : 'hideside', isMobile ? 'mobile' : '']">
|
||||
<el-row
|
||||
:class="[isShadowBg && isMobile?'bg-black opacity-30 w-full h-full absolute top-0 left-0 z-[1001]':'']"
|
||||
@click="changeShadow()"
|
||||
/>
|
||||
<el-aside
|
||||
class="main-cont gva-aside"
|
||||
:style="{width:asideWidth()}"
|
||||
>
|
||||
<div
|
||||
class="min-h-[60px] text-center transition-all duration-300 flex items-center justify-center gap-2"
|
||||
:style="{background: backgroundColor}"
|
||||
>
|
||||
<img
|
||||
alt
|
||||
class="w-9 h-9 p-1 bg-white rounded-full"
|
||||
src="@/assets/logo.png"
|
||||
>
|
||||
<div
|
||||
v-if="isSider"
|
||||
class="inline-flex font-bold text-2xl"
|
||||
:style="{color:textColor}"
|
||||
>{{ $GIN_VUE_ADMIN.appName }}</div>
|
||||
:class="[isShadowBg && isMobile ? 'bg-black opacity-30 w-full h-full absolute top-0 left-0 z-[1001]' : '']"
|
||||
@click="changeShadow()" />
|
||||
<el-aside class="main-cont gva-aside" :style="{ width: asideWidth() }">
|
||||
<div class="min-h-[60px] text-center transition-all duration-300 flex items-center justify-center gap-2"
|
||||
:style="{ background: backgroundColor }">
|
||||
<img alt class="w-9 h-9 p-1 bg-white rounded-full" src="@/assets/logo.png">
|
||||
<div v-if="isSider" class="inline-flex font-bold text-2xl" :style="{ color: textColor }">
|
||||
{{ $GIN_VUE_ADMIN.appName }}
|
||||
</div>
|
||||
</div>
|
||||
<Aside class="aside" />
|
||||
</el-aside>
|
||||
<!-- 分块滑动功能 -->
|
||||
<el-main class="main-cont main-right">
|
||||
<transition
|
||||
:duration="{ enter: 800, leave: 100 }"
|
||||
mode="out-in"
|
||||
name="el-fade-in-linear"
|
||||
>
|
||||
<div
|
||||
:style="{width: `calc(100% - ${getAsideWidth()})`}"
|
||||
class="fixed top-0 box-border z-50"
|
||||
>
|
||||
<transition :duration="{ enter: 800, leave: 100 }" mode="out-in" name="el-fade-in-linear">
|
||||
<div :style="{ width: `calc(100% - ${getAsideWidth()})` }" class="fixed top-0 box-border z-50">
|
||||
<el-row>
|
||||
<el-col>
|
||||
<el-header class="header-cont">
|
||||
<el-row class="p-0 h-full">
|
||||
<el-col
|
||||
:xs="2"
|
||||
:lg="1"
|
||||
:md="1"
|
||||
:sm="1"
|
||||
:xl="1"
|
||||
class="z-50 flex items-center pl-3"
|
||||
>
|
||||
<div
|
||||
class="text-black cursor-pointer text-lg leading-5"
|
||||
@click="totalCollapse"
|
||||
>
|
||||
<div
|
||||
v-if="isCollapse"
|
||||
class="gvaIcon gvaIcon-arrow-double-right"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="gvaIcon gvaIcon-arrow-double-left"
|
||||
/>
|
||||
<el-col :xs="2" :lg="1" :md="1" :sm="1" :xl="1" class="z-50 flex items-center pl-3">
|
||||
<div class="text-black cursor-pointer text-lg leading-5" @click="totalCollapse">
|
||||
<div v-if="isCollapse" class="gvaIcon gvaIcon-arrow-double-right" />
|
||||
<div v-else class="gvaIcon gvaIcon-arrow-double-left" />
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col
|
||||
:xs="10"
|
||||
:lg="14"
|
||||
:md="14"
|
||||
:sm="9"
|
||||
:xl="14"
|
||||
:pull="1"
|
||||
class="flex items-center"
|
||||
>
|
||||
<el-col :xs="10" :lg="14" :md="14" :sm="9" :xl="14" :pull="1" class="flex items-center">
|
||||
<!-- 修改为手机端不显示顶部标签 -->
|
||||
<el-breadcrumb
|
||||
v-show="!isMobile"
|
||||
class="breadcrumb"
|
||||
>
|
||||
<el-breadcrumb-item
|
||||
v-for="item in matched.slice(1,matched.length)"
|
||||
:key="item.path"
|
||||
>{{ fmtTitle(item.meta.title,route) }}</el-breadcrumb-item>
|
||||
<el-breadcrumb v-show="!isMobile" class="breadcrumb">
|
||||
<el-breadcrumb-item v-for="item in matched.slice(1, matched.length)" :key="item.path">
|
||||
{{ fmtTitle(item.meta.title, route) }}
|
||||
</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</el-col>
|
||||
<el-col
|
||||
:xs="12"
|
||||
:lg="9"
|
||||
:md="9"
|
||||
:sm="14"
|
||||
:xl="9"
|
||||
class="flex items-center justify-end"
|
||||
>
|
||||
<el-col :xs="12" :lg="9" :md="9" :sm="14" :xl="9" class="flex items-center justify-end">
|
||||
<div class="mr-1.5 flex items-center">
|
||||
<Search />
|
||||
<el-dropdown>
|
||||
<div class="flex justify-center items-center h-full w-full">
|
||||
<span class="cursor-pointer flex justify-center items-center">
|
||||
<CustomPic />
|
||||
<span
|
||||
v-show="!isMobile"
|
||||
style="margin-left: 5px"
|
||||
>{{ userStore.userInfo.nickName }}</span>
|
||||
<!-- <CustomPic /> -->
|
||||
<span v-show="!isMobile" style="margin-left: 5px">{{ userStore.userInfo.nickName }}</span>
|
||||
<el-icon>
|
||||
<arrow-down />
|
||||
</el-icon>
|
||||
|
|
@ -115,21 +58,15 @@
|
|||
</el-dropdown-item>
|
||||
<template v-if="userStore.userInfo.authorities">
|
||||
<el-dropdown-item
|
||||
v-for="item in userStore.userInfo.authorities.filter(i=>i.authorityId!==userStore.userInfo.authorityId)"
|
||||
:key="item.authorityId"
|
||||
@click="changeUserAuth(item.authorityId)"
|
||||
>
|
||||
v-for="item in userStore.userInfo.authorities.filter(i => i.authorityId !== userStore.userInfo.authorityId)"
|
||||
:key="item.authorityId" @click="changeUserAuth(item.authorityId)">
|
||||
<span>
|
||||
切换为:{{ item.authorityName }}
|
||||
</span>
|
||||
</el-dropdown-item>
|
||||
</template>
|
||||
<el-dropdown-item icon="avatar">
|
||||
<div
|
||||
class="command-box"
|
||||
style="display: flex"
|
||||
@click="handleCommand"
|
||||
>
|
||||
<div class="command-box" style="display: flex" @click="handleCommand">
|
||||
<div>指令菜单</div>
|
||||
<div style="margin-left: 8px">
|
||||
<span class="button">{{ first }}</span>
|
||||
|
|
@ -138,14 +75,8 @@
|
|||
</div>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
icon="avatar"
|
||||
@click="toPerson"
|
||||
>个人信息</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
icon="reading-lamp"
|
||||
@click="userStore.LoginOut"
|
||||
>登 出</el-dropdown-item>
|
||||
<el-dropdown-item icon="avatar" @click="toPerson">个人信息</el-dropdown-item>
|
||||
<el-dropdown-item icon="reading-lamp" @click="userStore.LoginOut">登 出</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
|
|
@ -156,23 +87,13 @@
|
|||
</el-col>
|
||||
</el-row>
|
||||
<!-- 当前面包屑用路由自动生成可根据需求修改 -->
|
||||
<!--
|
||||
:to="{ path: item.path }" 暂时注释不用-->
|
||||
<!-- :to="{ path: item.path }" 暂时注释不用-->
|
||||
<HistoryComponent ref="layoutHistoryComponent" />
|
||||
</div>
|
||||
</transition>
|
||||
<router-view
|
||||
v-if="reloadFlag"
|
||||
v-slot="{ Component }"
|
||||
class="admin-box"
|
||||
>
|
||||
<div
|
||||
id="gva-base-load-dom"
|
||||
>
|
||||
<transition
|
||||
mode="out-in"
|
||||
name="el-fade-in-linear"
|
||||
>
|
||||
<router-view v-if="reloadFlag" v-slot="{ Component }" class="admin-box">
|
||||
<div id="gva-base-load-dom">
|
||||
<transition mode="out-in" name="el-fade-in-linear">
|
||||
<keep-alive :include="routerStore.keepAliveRouters">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
|
|
@ -310,7 +231,7 @@ const backgroundColor = computed(() => {
|
|||
|
||||
const matched = computed(() => route.meta.matched)
|
||||
|
||||
const changeUserAuth = async(id) => {
|
||||
const changeUserAuth = async (id) => {
|
||||
const res = await setUserAuthority({
|
||||
authorityId: id
|
||||
})
|
||||
|
|
@ -322,18 +243,18 @@ const changeUserAuth = async(id) => {
|
|||
|
||||
const reloadFlag = ref(true)
|
||||
let reloadTimer = null
|
||||
const reload = async() => {
|
||||
const reload = async () => {
|
||||
if (reloadTimer) {
|
||||
window.clearTimeout(reloadTimer)
|
||||
}
|
||||
reloadTimer = window.setTimeout(async() => {
|
||||
reloadTimer = window.setTimeout(async () => {
|
||||
if (route.meta.keepAlive) {
|
||||
reloadFlag.value = false
|
||||
await nextTick()
|
||||
reloadFlag.value = true
|
||||
} else {
|
||||
const title = route.meta.title
|
||||
router.push({ name: 'Reload', params: { title }})
|
||||
router.push({ name: 'Reload', params: { title } })
|
||||
}
|
||||
}, 400)
|
||||
}
|
||||
|
|
@ -360,15 +281,15 @@ const changeShadow = () => {
|
|||
.button {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
background: rgb(250,250,250);
|
||||
width: 25px!important;
|
||||
background: rgb(250, 250, 250);
|
||||
width: 25px !important;
|
||||
padding: 4px 8px !important;
|
||||
border: 1px solid #eaeaea;
|
||||
margin-right: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
:deep .el-overlay {
|
||||
background-color: hsla(0,0%,100%,.9) !important;
|
||||
}
|
||||
|
||||
:deep .el-overlay {
|
||||
background-color: hsla(0, 0%, 100%, .9) !important;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,44 +1,7 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="grid grid-cols-12 w-full gap-2">
|
||||
<div class="col-span-3 h-full">
|
||||
<div class="w-full h-full bg-white px-4 py-8 rounded-lg shadow-lg box-border">
|
||||
<div class="user-card px-6 text-center bg-white shrink-0">
|
||||
<div class="flex justify-center">
|
||||
<SelectImage v-model="userStore.userInfo.headerImg" file-type="image" />
|
||||
</div>
|
||||
<div class="py-6 text-center">
|
||||
<p v-if="!editFlag" class="text-3xl flex justify-center items-center gap-4">
|
||||
{{ userStore.userInfo.nickName }}
|
||||
<el-icon class="cursor-pointer text-sm" color="#66b1ff" @click="openEdit">
|
||||
<edit />
|
||||
</el-icon>
|
||||
</p>
|
||||
<p v-if="editFlag" class="flex justify-center items-center gap-4">
|
||||
<el-input v-model="nickName" />
|
||||
<el-icon class="cursor-pointer" color="#67c23a" @click="enterEdit">
|
||||
<check />
|
||||
</el-icon>
|
||||
<el-icon class="cursor-pointer" color="#f23c3c" @click="closeEdit">
|
||||
<close />
|
||||
</el-icon>
|
||||
</p>
|
||||
<!-- <p class="text-gray-500 mt-2 text-md">这个家伙很懒,什么都没有留下</p> -->
|
||||
</div>
|
||||
<!-- <div class="w-full h-full text-left">
|
||||
<ul class="inline-block h-full w-full">
|
||||
<li class="info-list">
|
||||
<el-icon>
|
||||
<user />
|
||||
</el-icon>
|
||||
{{ userStore.userInfo.nickName }}
|
||||
</li>
|
||||
</ul>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-9 ">
|
||||
<div class="col-span-12">
|
||||
<div class="bg-white h-full px-4 py-8 rounded-lg shadow-lg box-border">
|
||||
<el-tabs v-model="activeName" @tab-click="handleClick">
|
||||
<el-tab-pane label="账号绑定" name="second">
|
||||
|
|
@ -47,22 +10,29 @@
|
|||
<p class="pb-2.5 text-xl text-gray-600">手机</p>
|
||||
<p class="pb-2.5 text-lg text-gray-400">
|
||||
{{ userStore.userInfo.phone }}
|
||||
<!-- <a
|
||||
href="javascript:void(0)"
|
||||
class="float-right text-blue-400"
|
||||
@click="changePhoneFlag = true"
|
||||
>立即修改</a> -->
|
||||
</p>
|
||||
</li>
|
||||
<li class="borderd pt-2.5">
|
||||
<p class="pb-2.5 text-xl text-gray-600">邮箱</p>
|
||||
<p class="pb-2.5 text-lg text-gray-400">
|
||||
{{ userStore.userInfo.email }}
|
||||
<!-- <a
|
||||
href="javascript:void(0)"
|
||||
class="float-right text-blue-400"
|
||||
@click="changeEmailFlag = true"
|
||||
>立即修改</a> -->
|
||||
</p>
|
||||
</li>
|
||||
<li class="borderd">
|
||||
<p class="pb-2.5 text-xl text-gray-600">昵称</p>
|
||||
<p v-if="!editFlag" class="pb-2.5 text-lg text-gray-400">
|
||||
{{ userStore.userInfo.nickName }}
|
||||
<a v-if="!editFlag" href="javascript:void(0)" class="float-right text-blue-400"
|
||||
@click="openEdit">立即修改</a>
|
||||
</p>
|
||||
<p v-if="editFlag" class="pb-2.5 text-lg text-gray-400">
|
||||
<el-input v-model="nickName" style="width: 200px;" />
|
||||
<el-icon class="cursor-pointer" color="#67c23a" @click="enterEdit">
|
||||
<check />
|
||||
</el-icon>
|
||||
<el-icon class="cursor-pointer" color="#f23c3c" @click="closeEdit">
|
||||
<close />
|
||||
</el-icon>
|
||||
</p>
|
||||
</li>
|
||||
<li class="borderd pt-2.5">
|
||||
|
|
|
|||
|
|
@ -3,167 +3,61 @@
|
|||
<warning-bar title="注:右上角头像下拉可切换角色" />
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="plus"
|
||||
@click="addAuthority(0)"
|
||||
>新增角色</el-button>
|
||||
<el-icon
|
||||
class="cursor-pointer"
|
||||
@click="toDoc('https://www.bilibili.com/video/BV1kv4y1g7nT?p=8&vd_source=f2640257c21e3b547a790461ed94875e')"
|
||||
><VideoCameraFilled /></el-icon>
|
||||
<el-button type="primary" icon="plus" @click="addAuthority(0)">新增角色</el-button>
|
||||
<el-icon class="cursor-pointer"
|
||||
@click="toDoc('https://www.bilibili.com/video/BV1kv4y1g7nT?p=8&vd_source=f2640257c21e3b547a790461ed94875e')">
|
||||
<VideoCameraFilled />
|
||||
</el-icon>
|
||||
</div>
|
||||
<el-table
|
||||
:data="tableData"
|
||||
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
|
||||
row-key="authorityId"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column
|
||||
label="角色ID"
|
||||
min-width="180"
|
||||
prop="authorityId"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="角色名称"
|
||||
min-width="180"
|
||||
prop="authorityName"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="操作"
|
||||
width="460"
|
||||
>
|
||||
<el-table :data="tableData" :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
row-key="authorityId" style="width: 100%">
|
||||
<el-table-column label="角色ID" min-width="180" prop="authorityId" />
|
||||
<el-table-column align="left" label="角色名称" min-width="180" prop="authorityName" />
|
||||
<el-table-column align="left" label="操作" width="460">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
icon="setting"
|
||||
|
||||
type="primary"
|
||||
link
|
||||
@click="openDrawer(scope.row)"
|
||||
>设置权限</el-button>
|
||||
<el-button
|
||||
icon="plus"
|
||||
|
||||
type="primary"
|
||||
link
|
||||
@click="addAuthority(scope.row.authorityId)"
|
||||
>新增子角色</el-button>
|
||||
<el-button
|
||||
icon="copy-document"
|
||||
|
||||
type="primary"
|
||||
link
|
||||
@click="copyAuthorityFunc(scope.row)"
|
||||
>拷贝</el-button>
|
||||
<el-button
|
||||
icon="edit"
|
||||
|
||||
type="primary"
|
||||
link
|
||||
@click="editAuthority(scope.row)"
|
||||
>编辑</el-button>
|
||||
<el-button
|
||||
icon="delete"
|
||||
|
||||
type="primary"
|
||||
link
|
||||
@click="deleteAuth(scope.row)"
|
||||
>删除</el-button>
|
||||
<el-button icon="setting" type="primary" link @click="openDrawer(scope.row)">设置权限</el-button>
|
||||
<el-button icon="plus" type="primary" link @click="addAuthority(scope.row.authorityId)">新增子角色</el-button>
|
||||
<el-button icon="copy-document" type="primary" link @click="copyAuthorityFunc(scope.row)">拷贝</el-button>
|
||||
<el-button icon="edit" type="primary" link @click="editAuthority(scope.row)">编辑</el-button>
|
||||
<el-button icon="delete" type="primary" link @click="deleteAuth(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<!-- 新增角色弹窗 -->
|
||||
<el-dialog
|
||||
v-model="dialogFormVisible"
|
||||
:title="dialogTitle"
|
||||
>
|
||||
<el-form
|
||||
ref="authorityForm"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-form-item
|
||||
label="父级角色"
|
||||
prop="parentId"
|
||||
>
|
||||
<el-cascader
|
||||
v-model="form.parentId"
|
||||
style="width:100%"
|
||||
:disabled="dialogType==='add'"
|
||||
<el-dialog v-model="dialogFormVisible" :title="dialogTitle">
|
||||
<el-form ref="authorityForm" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="父级角色" prop="parentId">
|
||||
<el-cascader v-model="form.parentId" style="width:100%" :disabled="dialogType === 'add'"
|
||||
:options="AuthorityOption"
|
||||
:props="{ checkStrictly: true,label:'authorityName',value:'authorityId',disabled:'disabled',emitPath:false}"
|
||||
:show-all-levels="false"
|
||||
filterable
|
||||
/>
|
||||
:props="{ checkStrictly: true, label: 'authorityName', value: 'authorityId', disabled: 'disabled', emitPath: false }"
|
||||
:show-all-levels="false" filterable />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="角色ID"
|
||||
prop="authorityId"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.authorityId"
|
||||
:disabled="dialogType==='edit'"
|
||||
autocomplete="off"
|
||||
maxlength="15"
|
||||
/>
|
||||
<el-form-item label="角色ID" prop="authorityId">
|
||||
<el-input v-model="form.authorityId" :disabled="dialogType === 'edit'" autocomplete="off" maxlength="15" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="角色姓名"
|
||||
prop="authorityName"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.authorityName"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<el-form-item label="角色姓名" prop="authorityName">
|
||||
<el-input v-model="form.authorityName" autocomplete="off" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="closeDialog">取 消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="enterDialog"
|
||||
>确 定</el-button>
|
||||
<el-button type="primary" @click="enterDialog">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-drawer
|
||||
v-if="drawer"
|
||||
v-model="drawer"
|
||||
:with-header="false"
|
||||
size="40%"
|
||||
title="角色配置"
|
||||
>
|
||||
<el-tabs
|
||||
:before-leave="autoEnter"
|
||||
type="border-card"
|
||||
>
|
||||
<el-drawer v-if="drawer" v-model="drawer" :with-header="false" size="40%" title="角色配置">
|
||||
<el-tabs :before-leave="autoEnter" type="border-card">
|
||||
<el-tab-pane label="角色菜单">
|
||||
<Menus
|
||||
ref="menus"
|
||||
:row="activeRow"
|
||||
@changeRow="changeRow"
|
||||
/>
|
||||
<Menus ref="menus" :row="activeRow" @changeRow="changeRow" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="角色api">
|
||||
<Apis
|
||||
ref="apis"
|
||||
:row="activeRow"
|
||||
@changeRow="changeRow"
|
||||
/>
|
||||
<Apis ref="apis" :row="activeRow" @changeRow="changeRow" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="资源权限">
|
||||
<Datas
|
||||
ref="datas"
|
||||
:authority="tableData"
|
||||
:row="activeRow"
|
||||
@changeRow="changeRow"
|
||||
/>
|
||||
<Datas ref="datas" :authority="tableData" :row="activeRow" @changeRow="changeRow" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-drawer>
|
||||
|
|
@ -240,7 +134,7 @@ const tableData = ref([])
|
|||
const searchInfo = ref({})
|
||||
|
||||
// 查询
|
||||
const getTableData = async() => {
|
||||
const getTableData = async () => {
|
||||
const table = await getAuthorityList({ page: page.value, pageSize: pageSize.value, ...searchInfo.value })
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.list
|
||||
|
|
@ -289,7 +183,7 @@ const deleteAuth = (row) => {
|
|||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async() => {
|
||||
.then(async () => {
|
||||
const res = await deleteAuthority({ authorityId: row.authorityId })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
|
|
@ -454,15 +348,16 @@ const editAuthority = (row) => {
|
|||
.authority {
|
||||
.el-input-number {
|
||||
margin-left: 15px;
|
||||
|
||||
span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.tree-content{
|
||||
|
||||
.tree-content {
|
||||
margin-top: 10px;
|
||||
height: calc(100vh - 158px);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -3,227 +3,85 @@
|
|||
<warning-bar title="注:右上角头像下拉可切换角色" />
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="plus"
|
||||
@click="addUser"
|
||||
>新增用户</el-button>
|
||||
<el-button type="primary" icon="plus" @click="addUser">新增用户</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
:data="tableData"
|
||||
row-key="ID"
|
||||
>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="头像"
|
||||
min-width="75"
|
||||
>
|
||||
<el-table :data="tableData" row-key="ID">
|
||||
<!-- <el-table-column align="left" label="头像" min-width="75">
|
||||
<template #default="scope">
|
||||
<CustomPic
|
||||
style="margin-top:8px"
|
||||
:pic-src="scope.row.headerImg"
|
||||
/>
|
||||
<CustomPic style="margin-top:8px" :pic-src="scope.row.headerImg" />
|
||||
</template>
|
||||
</el-table-column> -->
|
||||
<el-table-column align="left" label="ID" min-width="50" prop="ID" />
|
||||
<el-table-column align="left" label="用户名" min-width="150" prop="userName" />
|
||||
<el-table-column align="left" label="昵称" min-width="150" prop="nickName" />
|
||||
<el-table-column align="left" label="手机号" min-width="180" prop="phone" />
|
||||
<el-table-column align="left" label="邮箱" min-width="180" prop="email" />
|
||||
<el-table-column align="left" label="用户角色" min-width="200">
|
||||
<template #default="scope">
|
||||
<el-cascader v-model="scope.row.authorityIds" :options="authOptions" :show-all-levels="false" collapse-tags
|
||||
:props="{ multiple: true, checkStrictly: true, label: 'authorityName', value: 'authorityId', disabled: 'disabled', emitPath: false }"
|
||||
:clearable="false" @visible-change="(flag) => { changeAuthority(scope.row, flag, 0) }"
|
||||
@remove-tag="(removeAuth) => { changeAuthority(scope.row, false, removeAuth) }" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="ID"
|
||||
min-width="50"
|
||||
prop="ID"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="用户名"
|
||||
min-width="150"
|
||||
prop="userName"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="昵称"
|
||||
min-width="150"
|
||||
prop="nickName"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="手机号"
|
||||
min-width="180"
|
||||
prop="phone"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="邮箱"
|
||||
min-width="180"
|
||||
prop="email"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="用户角色"
|
||||
min-width="200"
|
||||
>
|
||||
<el-table-column align="left" label="启用" min-width="150">
|
||||
<template #default="scope">
|
||||
<el-cascader
|
||||
v-model="scope.row.authorityIds"
|
||||
:options="authOptions"
|
||||
:show-all-levels="false"
|
||||
collapse-tags
|
||||
:props="{ multiple:true,checkStrictly: true,label:'authorityName',value:'authorityId',disabled:'disabled',emitPath:false}"
|
||||
:clearable="false"
|
||||
@visible-change="(flag)=>{changeAuthority(scope.row,flag,0)}"
|
||||
@remove-tag="(removeAuth)=>{changeAuthority(scope.row,false,removeAuth)}"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="启用"
|
||||
min-width="150"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
v-model="scope.row.enable"
|
||||
inline-prompt
|
||||
:active-value="1"
|
||||
:inactive-value="2"
|
||||
@change="()=>{switchEnable(scope.row)}"
|
||||
/>
|
||||
<el-switch v-model="scope.row.enable" inline-prompt :active-value="1" :inactive-value="2"
|
||||
@change="() => { switchEnable(scope.row) }" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="250"
|
||||
fixed="right"
|
||||
>
|
||||
<el-table-column label="操作" min-width="250" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
icon="delete"
|
||||
@click="deleteUserFunc(scope.row)"
|
||||
>删除</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
icon="edit"
|
||||
@click="openEdit(scope.row)"
|
||||
>编辑</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
icon="magic-stick"
|
||||
@click="resetPasswordFunc(scope.row)"
|
||||
>重置密码</el-button>
|
||||
<el-button type="primary" link icon="delete" @click="deleteUserFunc(scope.row)">删除</el-button>
|
||||
<el-button type="primary" link icon="edit" @click="openEdit(scope.row)">编辑</el-button>
|
||||
<el-button type="primary" link icon="magic-stick" @click="resetPasswordFunc(scope.row)">重置密码</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
<div class="gva-pagination">
|
||||
<el-pagination
|
||||
:current-page="page"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[10, 30, 50, 100]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
<el-pagination :current-page="page" :page-size="pageSize" :page-sizes="[10, 30, 50, 100]" :total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper" @current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange" />
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog
|
||||
v-model="addUserDialog"
|
||||
title="用户"
|
||||
:show-close="false"
|
||||
:close-on-press-escape="false"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div style="height:60vh;overflow:auto;padding:0 12px;">
|
||||
<el-form
|
||||
ref="userForm"
|
||||
:rules="rules"
|
||||
:model="userInfo"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-form-item
|
||||
v-if="dialogFlag === 'add'"
|
||||
label="用户名"
|
||||
prop="userName"
|
||||
>
|
||||
<el-dialog v-model="addUserDialog" title="用户" :show-close="false" :close-on-press-escape="false"
|
||||
:close-on-click-modal="false">
|
||||
<div>
|
||||
<el-form ref="userForm" :rules="rules" :model="userInfo" label-width="80px">
|
||||
<el-form-item v-if="dialogFlag === 'add'" label="用户名" prop="userName">
|
||||
<el-input v-model="userInfo.userName" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="dialogFlag === 'add'"
|
||||
label="密码"
|
||||
prop="password"
|
||||
>
|
||||
<el-form-item v-if="dialogFlag === 'add'" label="密码" prop="password">
|
||||
<el-input v-model="userInfo.password" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="昵称"
|
||||
prop="nickName"
|
||||
>
|
||||
<el-form-item label="昵称" prop="nickName">
|
||||
<el-input v-model="userInfo.nickName" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="手机号"
|
||||
prop="phone"
|
||||
>
|
||||
<el-form-item label="手机号" prop="phone">
|
||||
<el-input v-model="userInfo.phone" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="邮箱"
|
||||
prop="email"
|
||||
>
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input v-model="userInfo.email" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="用户角色"
|
||||
prop="authorityId"
|
||||
>
|
||||
<el-cascader
|
||||
v-model="userInfo.authorityIds"
|
||||
style="width:100%"
|
||||
:options="authOptions"
|
||||
<el-form-item label="用户角色" prop="authorityId">
|
||||
<el-cascader v-model="userInfo.authorityIds" style="width:100%" :options="authOptions"
|
||||
:show-all-levels="false"
|
||||
:props="{ multiple:true,checkStrictly: true,label:'authorityName',value:'authorityId',disabled:'disabled',emitPath:false}"
|
||||
:clearable="false"
|
||||
/>
|
||||
:props="{ multiple: true, checkStrictly: true, label: 'authorityName', value: 'authorityId', disabled: 'disabled', emitPath: false }"
|
||||
:clearable="false" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="启用"
|
||||
prop="disabled"
|
||||
>
|
||||
<el-switch
|
||||
v-model="userInfo.enable"
|
||||
inline-prompt
|
||||
:active-value="1"
|
||||
:inactive-value="2"
|
||||
/>
|
||||
<el-form-item label="启用" prop="disabled">
|
||||
<el-switch v-model="userInfo.enable" inline-prompt :active-value="1" :inactive-value="2" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="头像"
|
||||
label-width="80px"
|
||||
>
|
||||
<div
|
||||
style="display:inline-block"
|
||||
@click="openHeaderChange"
|
||||
>
|
||||
<img
|
||||
v-if="userInfo.headerImg"
|
||||
alt="头像"
|
||||
class="header-img-box"
|
||||
:src="(userInfo.headerImg && userInfo.headerImg.slice(0, 4) !== 'http')?path+userInfo.headerImg:userInfo.headerImg"
|
||||
>
|
||||
<div
|
||||
v-else
|
||||
class="header-img-box"
|
||||
>从媒体库选择</div>
|
||||
<ChooseImg
|
||||
ref="chooseImg"
|
||||
:target="userInfo"
|
||||
:target-key="`headerImg`"
|
||||
/>
|
||||
<!-- <el-form-item label="头像" label-width="80px">
|
||||
<div style="display:inline-block" @click="openHeaderChange">
|
||||
<img v-if="userInfo.headerImg" alt="头像" class="header-img-box"
|
||||
:src="(userInfo.headerImg && userInfo.headerImg.slice(0, 4) !== 'http') ? path + userInfo.headerImg : userInfo.headerImg">
|
||||
<div v-else class="header-img-box">从媒体库选择</div>
|
||||
<ChooseImg ref="chooseImg" category="avatar" @on-select="handleSelectImg" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form-item> -->
|
||||
|
||||
</el-form>
|
||||
|
||||
|
|
@ -232,10 +90,7 @@
|
|||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="closeAddUserDialog">取 消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="enterAddUserDialog"
|
||||
>确 定</el-button>
|
||||
<el-button type="primary" @click="enterAddUserDialog">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
|
@ -252,13 +107,14 @@ import {
|
|||
} from '@/api/user'
|
||||
|
||||
import { getAuthorityList } from '@/api/authority'
|
||||
import CustomPic from '@/components/customPic/index.vue'
|
||||
// import CustomPic from '@/components/customPic/index.vue'
|
||||
import ChooseImg from '@/components/chooseImg/index.vue'
|
||||
import WarningBar from '@/components/warningBar/warningBar.vue'
|
||||
import { setUserInfo, resetPassword } from '@/api/user.js'
|
||||
|
||||
import { nextTick, ref, watch } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { equalArr } from '@/utils/arr'
|
||||
|
||||
defineOptions({
|
||||
name: 'User',
|
||||
|
|
@ -303,7 +159,7 @@ const handleCurrentChange = (val) => {
|
|||
}
|
||||
|
||||
// 查询
|
||||
const getTableData = async() => {
|
||||
const getTableData = async () => {
|
||||
const table = await getUserList({ page: page.value, pageSize: pageSize.value })
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.list
|
||||
|
|
@ -317,7 +173,7 @@ watch(() => tableData.value, () => {
|
|||
setAuthorityIds()
|
||||
})
|
||||
|
||||
const initPage = async() => {
|
||||
const initPage = async () => {
|
||||
getTableData()
|
||||
const res = await getAuthorityList({ page: 1, pageSize: 999 })
|
||||
setOptions(res.data.list)
|
||||
|
|
@ -334,7 +190,7 @@ const resetPasswordFunc = (row) => {
|
|||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}
|
||||
).then(async() => {
|
||||
).then(async () => {
|
||||
const res = await resetPassword({
|
||||
ID: row.ID,
|
||||
})
|
||||
|
|
@ -363,6 +219,9 @@ const chooseImg = ref(null)
|
|||
const openHeaderChange = () => {
|
||||
chooseImg.value.open()
|
||||
}
|
||||
const handleSelectImg = (mediaFile) => {
|
||||
userInfo.value.headerImg = mediaFile.url
|
||||
}
|
||||
|
||||
const authOptions = ref([])
|
||||
const setOptions = (authData) => {
|
||||
|
|
@ -370,7 +229,7 @@ const setOptions = (authData) => {
|
|||
setAuthorityOptions(authData, authOptions.value)
|
||||
}
|
||||
|
||||
const deleteUserFunc = async(row) => {
|
||||
const deleteUserFunc = async (row) => {
|
||||
ElMessageBox.confirm('确定要删除吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
|
|
@ -418,7 +277,7 @@ const rules = ref({
|
|||
]
|
||||
})
|
||||
const userForm = ref(null)
|
||||
const enterAddUserDialog = async() => {
|
||||
const enterAddUserDialog = async () => {
|
||||
userInfo.value.authorityId = userInfo.value.authorityIds[0]
|
||||
userForm.value.validate(async valid => {
|
||||
if (valid) {
|
||||
|
|
@ -461,7 +320,7 @@ const addUser = () => {
|
|||
}
|
||||
|
||||
const tempAuth = {}
|
||||
const changeAuthority = async(row, flag, removeAuth) => {
|
||||
const changeAuthority = async (row, flag, removeAuth) => {
|
||||
if (flag) {
|
||||
if (!removeAuth) {
|
||||
tempAuth[row.ID] = [...row.authorityIds]
|
||||
|
|
@ -489,29 +348,13 @@ const changeAuthority = async(row, flag, removeAuth) => {
|
|||
}
|
||||
}
|
||||
|
||||
const equalArr = (arr1, arr2) => {
|
||||
console.log(arr1.length, arr2.length)
|
||||
if (arr1.length !== arr2.length) {
|
||||
return false
|
||||
}
|
||||
let i = 0
|
||||
arr1.forEach(item => {
|
||||
arr2.forEach(item2 => {
|
||||
if (item == item2) {
|
||||
i++
|
||||
}
|
||||
})
|
||||
})
|
||||
return i === arr1.length
|
||||
}
|
||||
|
||||
const openEdit = (row) => {
|
||||
dialogFlag.value = 'edit'
|
||||
userInfo.value = JSON.parse(JSON.stringify(row))
|
||||
addUserDialog.value = true
|
||||
}
|
||||
|
||||
const switchEnable = async(row) => {
|
||||
const switchEnable = async (row) => {
|
||||
userInfo.value = JSON.parse(JSON.stringify(row))
|
||||
await nextTick()
|
||||
const req = {
|
||||
|
|
@ -529,7 +372,7 @@ const switchEnable = async(row) => {
|
|||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.header-img-box {
|
||||
.header-img-box {
|
||||
@apply w-52 h-52 border border-solid border-gray-300 rounded-xl flex justify-center items-center cursor-pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Reference in New Issue