修改一大堆东西

This commit is contained in:
jacky 2024-04-16 00:38:49 +08:00
parent ce590d1ebd
commit 1c83cb1bf8
28 changed files with 1753 additions and 879 deletions

View File

@ -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": {

100
src/api/article.js Normal file
View File

@ -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
})
}

View File

@ -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

View File

@ -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
})
}

View File

@ -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',
})
}

23
src/api/fetcher.js Normal file
View File

@ -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
})
}

View File

@ -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
})
}

12
src/api/source.js Normal file
View File

@ -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
})
}

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -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)

View File

@ -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>

View File

@ -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>

View File

@ -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: '上传失败'

View File

@ -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>

View File

@ -681,7 +681,7 @@ li {
}
.gva-btn-list {
@apply mb-3 flex gap-3 items-center;
@apply mb-3 flex gap-2 items-center;
}

15
src/utils/arr.js Normal file
View File

@ -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
}

View File

@ -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()
// urlID find createupdate idurl
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>

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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({

View File

@ -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;

View File

@ -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>

View File

@ -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">

View File

@ -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>

View File

@ -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>