增加栏目管理

增加媒体库
This commit is contained in:
jacky 2024-04-11 18:02:58 +08:00
parent d95ad35903
commit 8c5129f63a
18 changed files with 1158 additions and 965 deletions

View File

@ -12,11 +12,14 @@ module.exports = {
extends: ['plugin:vue/recommended', 'eslint:recommended'],
rules: {
'vue/no-v-model-argument': 0,
'vue/max-attributes-per-line': 2,
'vue/max-attributes-per-line': 0,
'vue/first-attribute-linebreak': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/multiline-html-element-content-newline': 'off',
'vue/multi-word-component-names': 'off',
'vue/html-closing-bracket-newline': 'off',
'vue/no-v-html': 'off',
'vue/html-indent': 'off',
'accessor-pairs': 2,
'arrow-spacing': [
2,

View File

@ -9,7 +9,7 @@ import service from '@/utils/request'
export const findFile = (params) => {
return service({
url: '/fileUploadAndDownload/findFile',
url: '/mediaFile/findFile',
method: 'get',
params
})
@ -17,7 +17,7 @@ export const findFile = (params) => {
export const breakpointContinue = (data) => {
return service({
url: '/fileUploadAndDownload/breakpointContinue',
url: '/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: '/fileUploadAndDownload/breakpointContinueFinish',
url: '/mediaFile/breakpointContinueFinish',
method: 'post',
params
})
@ -35,7 +35,7 @@ export const breakpointContinueFinish = (params) => {
export const removeChunk = (data, params) => {
return service({
url: '/fileUploadAndDownload/removeChunk',
url: '/mediaFile/removeChunk',
method: 'post',
data,
params

63
src/api/channel.js Normal file
View File

@ -0,0 +1,63 @@
import service from '@/utils/request'
// @Summary 获取栏目树
// @Produce application/json
// @Router /channel/getChannelTree [post]
export const getChannelTree = () => {
return service({
url: '/channel/getChannelTree',
method: 'post',
})
}
// @Summary 新增栏目
// @Produce application/json
// @Param menu Object
// @Router /channel/addChannel [post]
export const addChannel = (data) => {
return service({
url: '/channel/addChannel',
method: 'post',
data
})
}
// @Summary 删除栏目
// @Produce application/json
// @Param ID int
// @Router /channel/deleteChannel [post]
export const deleteChannel = (data) => {
return service({
url: '/channel/deleteChannel',
method: 'post',
data
})
}
// @Summary 修改栏目
// @Produce application/json
// @Param menu Object
// @Router /channel/updateChannel [post]
export const updateChannel = (data) => {
return service({
url: '/channel/updateChannel',
method: 'post',
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 /channel/getChannelById [post]
export const getChannelById = (data) => {
return service({
url: '/channel/getChannelById',
method: 'post',
data
})
}

44
src/api/mediaFile.js Normal file
View File

@ -0,0 +1,44 @@
import service from '@/utils/request'
// @Tags MediaFile
// @Summary 分页文件列表
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param data body modelInterface.PageInfo true "分页获取文件户列表"
// @Success 200 {string} json "{"success":true,"data":{},"msg":"获取成功"}"
// @Router /mediaFile/getFileList [post]
export const getFileList = (data) => {
return service({
url: '/mediaFile/getFileList',
method: 'post',
data
})
}
// @Tags MediaFile
// @Summary 删除文件
// @Security ApiKeyAuth
// @Produce application/json
// @Param data body dbModel.MediaFile true "传入文件里面id即可"
// @Success 200 {string} json "{"success":true,"data":{},"msg":"返回成功"}"
// @Router /mediaFile/deleteFile [post]
export const deleteFile = (data) => {
return service({
url: '/mediaFile/deleteFile',
method: 'post',
data
})
}
/**
* 编辑文件名或者备注
* @param data
* @returns {*}
*/
export const editFileName = (data) => {
return service({
url: '/mediaFile/editFileName',
method: 'post',
data
})
}

View File

@ -1,59 +1,24 @@
<template>
<el-drawer
v-model="drawer"
title="媒体库"
size="650px"
>
<warning-bar
title="点击“文件名/备注”可以编辑文件名或者备注内容。"
/>
<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"
>
<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-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-button type="primary" icon="search" @click="open">查询</el-button>
</el-form-item>
</el-form>
</div>
<div class="media">
<div
v-for="(item,key) in picList"
:key="key"
class="media-box"
>
<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)"
>
<el-image :key="key" :src="getUrl(item.url)" @click="chooseImg(item.url, target, targetKey)">
<template #error>
<div class="header-img-box-list">
<el-icon>
@ -63,28 +28,18 @@
</template>
</el-image>
</div>
<div
class="img-title"
@click="editFileNameFunc(item)"
>{{ item.name }}</div>
<div class="img-title" @click="editFileNameFunc(item)">{{ item.name }}</div>
</div>
</div>
<el-pagination
:current-page="page"
:page-size="pageSize"
:total="total"
:style="{'justify-content':'center'}"
layout="total, prev, pager, next, jumper"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
<el-pagination :current-page="page" :page-size="pageSize" :total="total" :style="{ 'justify-content': 'center' }"
layout="total, prev, pager, next, jumper" @current-change="handleCurrentChange" @size-change="handleSizeChange" />
</el-drawer>
</template>
<script setup>
import { getUrl } from '@/utils/image'
import { ref } from 'vue'
import { getFileList, editFileName } from '@/api/fileUploadAndDownload'
import { getFileList, editFileName } from '@/api/mediaFile'
import UploadImage from '@/components/upload/image.vue'
import UploadCommon from '@/components/upload/common.vue'
import { ElMessage, ElMessageBox } from 'element-plus'
@ -132,7 +87,7 @@ const chooseImg = (url, target, targetKey) => {
drawer.value = false
}
const open = async() => {
const open = async () => {
const res = await getFileList({ page: page.value, pageSize: pageSize.value, ...search.value })
if (res.code === 0) {
picList.value = res.data.list
@ -148,14 +103,14 @@ const open = async() => {
* @param row
* @returns {Promise<void>}
*/
const editFileNameFunc = async(row) => {
const editFileNameFunc = async (row) => {
ElMessageBox.prompt('请输入文件名或者备注', '编辑', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /\S/,
inputErrorMessage: '不能为空',
inputValue: row.name
}).then(async({ value }) => {
}).then(async ({ value }) => {
row.name = value
// console.log(row)
const res = await editFileName(row)
@ -208,6 +163,7 @@ defineExpose({ open })
line-height: 120px;
cursor: pointer;
overflow: hidden;
.el-image__inner {
max-width: 120px;
max-height: 120px;
@ -218,5 +174,4 @@ defineExpose({ open })
}
}
}
</style>

View File

@ -1,19 +1,8 @@
<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"
mode="default"
@onCreated="handleCreated"
@onChange="change"
/>
<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"
mode="default" @onCreated="handleCreated" @onChange="change" />
</div>
</template>
@ -56,7 +45,7 @@ const editorConfig = {
}
editorConfig.MENU_CONF['uploadImage'] = {
fieldName: 'file',
server: basePath + '/fileUploadAndDownload/upload?noSave=1',
server: basePath + '/mediaFile/upload?noSave=1',
customInsert(res, insertFn) {
if (res.code === 0) {
const urlPath = getUrl(res.data.file.url)
@ -85,6 +74,4 @@ watch(() => props.modelValue, () => {
</script>
<style scoped lang="scss">
</style>
<style scoped lang="scss"></style>

View File

@ -1,16 +1,8 @@
<template>
<div>
<el-upload
multiple
:action="`${path}/fileUploadAndDownload/upload?noSave=1`"
:on-error="uploadError"
:on-success="uploadSuccess"
:show-file-list="true"
:file-list="fileList"
:limit="limit"
:accept="accept"
class="upload-btn"
>
<el-upload multiple :action="`${path}/mediaFile/upload?noSave=1`" :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>
</el-upload>
</div>
@ -82,4 +74,3 @@ const uploadError = () => {
}
</script>

View File

@ -1,184 +1,85 @@
<template>
<div>
<div
v-if="!multiple"
class="update-image"
:style="{
<div v-if="!multiple" class="update-image" :style="{
'background-image': `url(${getUrl(model)})`,
'position': 'relative',
}"
>
<el-icon
v-if="isVideoExt(model || '')"
:size="32"
class="video video-icon"
style=""
>
}">
<el-icon v-if="isVideoExt(model || '')" :size="32" class="video video-icon" style="">
<VideoPlay />
</el-icon>
<video
v-if="isVideoExt(model || '')"
class="avatar video-avatar video"
muted
preload="metadata"
style=""
@click="openChooseImg"
>
<video v-if="isVideoExt(model || '')" class="avatar video-avatar video" muted preload="metadata" style=""
@click="openChooseImg">
<source :src="getUrl(model) + '#t=1'">
</video>
<span
v-if="model"
class="update"
style="position: absolute;"
@click="openChooseImg"
>
<span v-if="model" class="update" style="position: absolute;" @click="openChooseImg">
<el-icon>
<delete />
</el-icon>
删除</span>
<span
v-else
class="update text-gray-600"
@click="openChooseImg"
>
<span v-else class="update text-gray-600" @click="openChooseImg">
<el-icon>
<plus />
</el-icon>
上传</span>
</div>
<div
v-else
class="multiple-img"
>
<div
v-for="(item, index) in multipleValue"
:key="index"
class="update-image"
:style="{
<div v-else class="multiple-img">
<div v-for="(item, index) in multipleValue" :key="index" class="update-image" :style="{
'background-image': `url(${getUrl(item)})`,
'position': 'relative',
}"
>
<el-icon
v-if="isVideoExt(item || '')"
:size="32"
class="video video-icon"
>
}">
<el-icon v-if="isVideoExt(item || '')" :size="32" class="video video-icon">
<VideoPlay />
</el-icon>
<video
v-if="isVideoExt(item || '')"
class="avatar video-avatar video"
muted
preload="metadata"
@click="deleteImg(index)"
>
<video v-if="isVideoExt(item || '')" class="avatar video-avatar video" muted preload="metadata"
@click="deleteImg(index)">
<source :src="getUrl(item) + '#t=1'">
</video>
<span
class="update"
style="position: absolute;"
@click="deleteImg(index)"
>
<span class="update" style="position: absolute;" @click="deleteImg(index)">
<el-icon>
<delete />
</el-icon>
删除</span>
</div>
<div
v-if="!maxUpdateCount || maxUpdateCount>multipleValue.length"
class="add-image"
>
<span
class="update text-gray-600"
@click="openChooseImg"
>
<div v-if="!maxUpdateCount || maxUpdateCount > multipleValue.length" class="add-image">
<span class="update text-gray-600" @click="openChooseImg">
<el-icon>
<Plus />
</el-icon>
上传</span>
</div>
</div>
<el-drawer
v-model="drawer"
title="媒体库"
size="650px"
>
<warning-bar
title="点击“文件名/备注”可以编辑文件名或者备注内容。"
/>
<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="getImageList"
/>
<upload-image
:image-url="imageUrl"
:file-size="512"
:max-w-h="1080"
class="upload-btn-media-library"
@on-success="getImageList"
/>
<el-form
ref="searchForm"
:inline="true"
:model="search"
>
<upload-common :image-common="imageCommon" class="upload-btn-media-library" @on-success="getImageList" />
<upload-image :image-url="imageUrl" :file-size="512" :max-w-h="1080" class="upload-btn-media-library"
@on-success="getImageList" />
<el-form ref="searchForm" :inline="true" :model="search">
<el-form-item label="">
<el-input
v-model="search.keyword"
class="keyword"
placeholder="请输入文件名或备注"
/>
<el-input v-model="search.keyword" class="keyword" placeholder="请输入文件名或备注" />
</el-form-item>
<el-form-item>
<el-button
type="primary"
icon="search"
@click="getImageList"
>查询
<el-button type="primary" icon="search" @click="getImageList">查询
</el-button>
</el-form-item>
</el-form>
</div>
<div class="media">
<div
v-for="(item,key) in picList"
:key="key"
class="media-box"
>
<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)"
fit="cover"
style="width: 100%;height: 100%;"
@click="chooseImg(item.url)"
>
<el-image :key="key" :src="getUrl(item.url)" fit="cover" style="width: 100%;height: 100%;"
@click="chooseImg(item.url)">
<template #error>
<el-icon
v-if="isVideoExt(item.url || '')"
:size="32"
class="video video-icon"
>
<el-icon v-if="isVideoExt(item.url || '')" :size="32" class="video video-icon">
<VideoPlay />
</el-icon>
<video
v-if="isVideoExt(item.url || '')"
class="avatar video-avatar video"
muted
preload="metadata"
@click="chooseImg(item.url)"
>
<video v-if="isVideoExt(item.url || '')" class="avatar video-avatar video" muted preload="metadata"
@click="chooseImg(item.url)">
<source :src="getUrl(item.url) + '#t=1'">
您的浏览器不支持视频播放
</video>
<div
v-else
class="header-img-box-list"
>
<div v-else class="header-img-box-list">
<el-icon class="lost-image">
<icon-picture />
</el-icon>
@ -186,22 +87,13 @@
</template>
</el-image>
</div>
<div
class="img-title"
@click="editFileNameFunc(item)"
>{{ item.name }}
<div class="img-title" @click="editFileNameFunc(item)">{{ item.name }}
</div>
</div>
</div>
<el-pagination
:current-page="page"
:page-size="pageSize"
:total="total"
:style="{'justify-content':'center'}"
layout="total, prev, pager, next, jumper"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
<el-pagination :current-page="page" :page-size="pageSize" :total="total" :style="{ 'justify-content': 'center' }"
layout="total, prev, pager, next, jumper" @current-change="handleCurrentChange"
@size-change="handleSizeChange" />
</el-drawer>
</div>
</template>
@ -210,7 +102,7 @@
import { getUrl, isVideoExt } from '@/utils/image'
import { onMounted, ref } from 'vue'
import { getFileList, editFileName } from '@/api/fileUploadAndDownload'
import { getFileList, editFileName } from '@/api/mediaFile'
import UploadImage from '@/components/upload/image.vue'
import UploadCommon from '@/components/upload/common.vue'
import WarningBar from '@/components/warningBar/warningBar.vue'
@ -263,14 +155,14 @@ const handleCurrentChange = (val) => {
page.value = val
getImageList()
}
const editFileNameFunc = async(row) => {
const editFileNameFunc = async (row) => {
ElMessageBox.prompt('请输入文件名或者备注', '编辑', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /\S/,
inputErrorMessage: '不能为空',
inputValue: row.name
}).then(async({ value }) => {
}).then(async ({ value }) => {
row.name = value
// console.log(row)
const res = await editFileName(row)
@ -324,7 +216,7 @@ const chooseImg = (url) => {
}
drawer.value = false
}
const openChooseImg = async() => {
const openChooseImg = async () => {
if (model.value && !props.multiple) {
model.value = ''
return
@ -333,7 +225,7 @@ const openChooseImg = async() => {
drawer.value = true
}
const getImageList = async() => {
const getImageList = async () => {
const res = await getFileList({ page: page.value, pageSize: pageSize.value, ...search.value })
if (res.code === 0) {
picList.value = res.data.list
@ -346,7 +238,6 @@ const getImageList = async() => {
</script>
<style scoped lang="scss">
.multiple-img {
display: flex;
gap: 8px;
@ -381,16 +272,12 @@ const getImageList = async() => {
&:hover {
color: #fff;
background: linear-gradient(
to bottom,
background: linear-gradient(to bottom,
rgba(255, 255, 255, 0.15) 0%,
rgba(0, 0, 0, 0.15) 100%
),
radial-gradient(
at top center,
rgba(0, 0, 0, 0.15) 100%),
radial-gradient(at top center,
rgba(255, 255, 255, 0.4) 0%,
rgba(0, 0, 0, 0.4) 120%
) #989898;
rgba(0, 0, 0, 0.4) 120%) #989898;
background-blend-mode: multiply, multiply;
background-size: cover;

View File

@ -1,13 +1,7 @@
<template>
<div>
<el-upload
:action="`${path}/fileUploadAndDownload/upload`"
:before-upload="checkFile"
:on-error="uploadError"
:on-success="uploadSuccess"
:show-file-list="false"
class="upload-btn"
>
<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>
</div>
@ -23,43 +17,37 @@ defineOptions({
name: 'UploadCommon',
})
const emit = defineEmits(['on-success'])
const emit = defineEmits(['on-success', 'on-failure', 'on-before-upload'])
const path = ref(import.meta.env.VITE_BASE_API)
const fullscreenLoading = ref(false)
const checkFile = (file) => {
fullscreenLoading.value = true
const isLt500K = file.size / 1024 / 1024 < 0.5 // 500K, @todo
const isLt5M = file.size / 1024 / 1024 < 5 // 5MB, @todo
const isVideo = isVideoMime(file.type)
const isImage = isImageMime(file.type)
let pass = true
if (!isVideo && !isImage) {
ElMessage.error('上传图片只能是 jpg,png,svg,webp 格式, 上传视频只能是 mp4,webm 格式!')
fullscreenLoading.value = false
pass = false
return false
}
if (!isLt5M && isVideo) {
ElMessage.error('上传视频大小不能超过 5MB')
fullscreenLoading.value = false
pass = false
return false
}
if (!isLt500K && isImage) {
ElMessage.error('未压缩的上传图片大小不能超过 500KB请使用压缩上传')
fullscreenLoading.value = false
pass = false
return false
}
console.log('upload file check result: ', pass)
emit('on-before-upload', file)
return pass
return true
}
const uploadSuccess = (res) => {
const { data } = res
if (data.file) {
emit('on-success', data.file.url)
if (data.mediaFile) {
emit('on-success', data.mediaFile.url)
}
}
@ -68,8 +56,7 @@ const uploadError = () => {
type: 'error',
message: '上传失败'
})
fullscreenLoading.value = false
emit('on-failure')
}
</script>

View File

@ -1,14 +1,8 @@
<template>
<div>
<el-upload
:action="`${path}/fileUploadAndDownload/upload`"
:show-file-list="false"
:on-success="handleImageSuccess"
:before-upload="beforeImageUpload"
:multiple="false"
>
<el-button type="primary">压缩上传</el-button>
<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>
</div>
</template>
@ -17,13 +11,12 @@
import ImageCompress from '@/utils/image'
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { useUserStore } from '@/pinia/modules/user'
defineOptions({
name: 'UploadImage',
})
const emit = defineEmits(['on-success'])
const emit = defineEmits(['on-success', 'on-failure', 'on-before-upload'])
const props = defineProps({
imageUrl: {
type: String,
@ -41,8 +34,6 @@ const props = defineProps({
const path = ref(import.meta.env.VITE_BASE_API)
const userStore = useUserStore()
const beforeImageUpload = (file) => {
const isJPG = file.type === 'image/jpeg'
const isPng = file.type === 'image/png'
@ -51,19 +42,26 @@ const beforeImageUpload = (file) => {
return false
}
emit('on-before-upload')
const isRightSize = file.size / 1024 < props.fileSize
if (!isRightSize) {
//
const compress = new ImageCompress(file, props.fileSize, props.maxWH)
return compress.compress()
}
return isRightSize
}
const handleImageFailure = (res) => {
emit('on-failure')
}
const handleImageSuccess = (res) => {
const { data } = res
if (data.file) {
emit('on-success', data.file.url)
if (data.mediaFile) {
emit('on-success', data.mediaFile.url)
}
}
@ -78,9 +76,11 @@ const handleImageSuccess = (res) => {
position: relative;
overflow: hidden;
}
.image-uploader {
border-color: #409eff;
}
.image-uploader-icon {
font-size: 28px;
color: #8c939d;
@ -89,6 +89,7 @@ const handleImageSuccess = (res) => {
line-height: 178px;
text-align: center;
}
.image {
width: 178px;
height: 178px;

View File

@ -0,0 +1,266 @@
<template>
<div>
<div class="gva-table-box">
<div class="gva-btn-list">
<el-button type="primary" icon="plus" @click="clickAdd('0')">新增根栏目</el-button>
</div>
<!-- 由于此处菜单跟左侧列表一一对应所以不需要分页 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="名称" min-width="100" prop="name" />
<el-table-column align="left" label="标题" min-width="120" prop="title" />
<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="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>
</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"
:model="form" :rules="rules">
<el-form-item label="名称(导航用)" prop="name" style="width:45%">
<el-input v-model="form.name" autocomplete="off" />
</el-form-item>
<el-form-item label="标题(栏目页使用)" prop="title" style="width:45%">
<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-input-number v-model="form.pageNum" autocomplete="off" style="width:100%" />
</el-form-item>
<el-form-item label="父节点ID" style="width:45%">
<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>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { reactive, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
getChannelTree,
addChannel,
updateChannel,
deleteChannel,
getChannelById
} from '@/api/channel'
const rules = reactive({
name: [
{ required: true, message: '请输入栏目名称', trigger: 'blur' }
],
title: [
{ required: true, message: '请输入栏目标题', trigger: 'blur' }
],
pageNum: [
{ required: true, type: 'number', message: '默认显示文章条数必须为大于0的整数', trigger: 'blur' }
],
})
const tableData = ref([])
//
const getTableData = async () => {
const res = await getChannelTree()
if (res.code === 0) {
tableData.value = res.data.channelTree
}
}
getTableData()
const channelOption = ref([
{
ID: '0',
name: '根栏目'
}
])
const setOptions = () => {
channelOption.value = [
{
ID: '0',
name: '根栏目'
}
]
setChannelOptions(tableData.value, channelOption.value, false)
}
const setChannelOptions = (channelData, optionsData, disabled) => {
channelData &&
channelData.forEach(item => {
if (item.children && item.children.length) {
const option = {
title: item.name,
ID: String(item.ID),
disabled: disabled || item.ID === form.value.ID,
children: []
}
setChannelOptions(
item.children,
option.children,
disabled || item.ID === form.value.ID
)
optionsData.push(option)
} else {
const option = {
title: item.name,
ID: String(item.ID),
disabled: disabled || item.ID === form.value.ID
}
optionsData.push(option)
}
})
}
const form = ref({
ID: 0,
parentId: 0,
name: '',
title: '',
description: '',
keyword: '',
introduce: '',
templateId: 0,
templateArticleId: 0,
pageNum: 10,
})
//
const enterDialog = async () => {
channelForm.value.validate(async valid => {
if (valid) {
let res
if (isEdit.value) {
res = await updateChannel(form.value)
} else {
res = await addChannel(form.value)
}
if (res.code === 0) {
ElMessage({
type: 'success',
message: isEdit.value ? '编辑成功' : '添加成功!'
})
getTableData()
}
initForm()
dialogFormVisible.value = false
}
})
}
//
const channelForm = ref(null)
const checkFlag = ref(false)
const initForm = () => {
checkFlag.value = false
channelForm.value.resetFields()
form.value = {
ID: 0,
parentId: 0,
name: '',
title: '',
description: '',
keyword: '',
introduce: '',
templateId: 0,
templateArticleId: 0,
pageNum: 10,
}
}
const handleClose = (done) => {
initForm()
done()
}
//
const dialogFormVisible = ref(false)
const closeDialog = () => {
initForm()
dialogFormVisible.value = false
}
//
const clickDelete = (ID) => {
ElMessageBox.confirm('此操作将永久删除栏目, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(async () => {
const res = await deleteChannel({ ID })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功!'
})
if (tableData.value.length === 1 && page.value > 1) {
page.value--
}
getTableData()
}
})
.catch(() => {
ElMessage({
type: 'info',
message: '已取消删除'
})
})
}
// id 0
const isEdit = ref(false)
const dialogTitle = ref('新增栏目')
const clickAdd = (id) => {
dialogTitle.value = '新增栏目'
form.value.parentId = String(id)
isEdit.value = false
setOptions()
dialogFormVisible.value = true
}
//
const clickEdit = async (id) => {
dialogTitle.value = '编辑栏目'
const res = await getChannelById({ id })
form.value = res.data.channel
isEdit.value = true
setOptions()
dialogFormVisible.value = true
}
</script>

View File

@ -0,0 +1,279 @@
<template>
<div class="break-point">
<div class="gva-table-box">
<el-divider content-position="left">大文件上传</el-divider>
<form id="fromCont" method="post">
<div class="fileUpload" @click="inputChange">
选择文件
<input v-show="false" id="file" ref="FileInput" multiple="multiple" type="file" @change="choseFile">
</div>
</form>
<el-button :disabled="limitFileSize" type="primary" class="uploadBtn" @click="getFile">上传文件</el-button>
<div class="el-upload__tip">请上传不超过5MB的文件</div>
<div class="list">
<transition name="list" tag="p">
<div v-if="file" class="list-item">
<el-icon>
<document />
</el-icon>
<span>{{ file.name }}</span>
<span class="percentage">{{ percentage }}%</span>
<el-progress :show-text="false" :text-inside="false" :stroke-width="2" :percentage="percentage" />
</div>
</transition>
</div>
</div>
</div>
</template>
<script setup>
import SparkMD5 from 'spark-md5'
import {
findFile,
breakpointContinueFinish,
removeChunk,
breakpointContinue
} from '@/api/breakpoint'
import { ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
defineOptions({
name: 'BreakPoint'
})
const file = ref(null)
const fileMd5 = ref('')
const formDataList = ref([])
const waitUpLoad = ref([])
const waitNum = ref(NaN)
const limitFileSize = ref(false)
const percentage = ref(0)
const percentageFlage = ref(true)
//
const choseFile = async (e) => {
const fileR = new FileReader() // reader
const fileInput = e.target.files[0] //
const maxSize = 5 * 1024 * 1024
file.value = fileInput // file 便 func
percentage.value = 0
if (file.value.size < maxSize) {
fileR.readAsArrayBuffer(file.value) // ArrayBuffer
fileR.onload = async e => {
// arrayBuffer e dome e.target.result
const blob = e.target.result
const spark = new SparkMD5.ArrayBuffer() // md5 md5
spark.append(blob) //
fileMd5.value = spark.end() // a md5
const FileSliceCap = 1 * 1024 * 1024 //
let start = 0 //
let end = 0 // a
let i = 0 //
formDataList.value = [] //
while (end < file.value.size) {
// size
start = i * FileSliceCap //
end = (i + 1) * FileSliceCap //
var fileSlice = file.value.slice(start, end) // file.slice h5
const formData = new window.FormData() // FormData
formData.append('fileMd5', fileMd5.value) // Md5
formData.append('file', fileSlice) //
formData.append('chunkNumber', i) //
formData.append('fileName', file.value.name) // formData.appen formData
formDataList.value.push({ key: i, formData }) //
i++
}
const params = {
fileName: file.value.name,
fileMd5: fileMd5.value,
chunkTotal: formDataList.value.length
}
const res = await findFile(params)
//
const finishList = res.data.file.ExaFileChunk //
const IsFinish = res.data.file.IsFinish // md5
if (!IsFinish) {
//
waitUpLoad.value = formDataList.value.filter(all => {
return !(
finishList &&
finishList.some(fi => fi.FileChunkNumber === all.key)
) //
})
} else {
waitUpLoad.value = [] //
ElMessage.success('文件已秒传')
}
waitNum.value = waitUpLoad.value.length //
}
} else {
limitFileSize.value = true
ElMessage('请上传小于5M文件')
}
}
const getFile = () => {
//
if (file.value === null) {
ElMessage('请先上传文件')
return
}
if (percentage.value === 100) {
percentageFlage.value = false
}
sliceFile() //
}
const sliceFile = () => {
waitUpLoad.value &&
waitUpLoad.value.forEach(item => {
//
item.formData.append('chunkTotal', formDataList.value.length) //
const fileR = new FileReader() //
const fileF = item.formData.get('file')
fileR.readAsArrayBuffer(fileF)
fileR.onload = e => {
const spark = new SparkMD5.ArrayBuffer()
spark.append(e.target.result)
item.formData.append('chunkMd5', spark.end()) // md5
upLoadFileSlice(item)
}
})
}
watch(() => waitNum.value, () => { percentage.value = Math.floor(((formDataList.value.length - waitNum.value) / formDataList.value.length) * 100) })
const upLoadFileSlice = async (item) => {
//
const fileRe = await breakpointContinue(item.formData)
if (fileRe.code !== 0) {
return
}
waitNum.value-- //
if (waitNum.value === 0) {
//
const params = {
fileName: file.value.name,
fileMd5: fileMd5.value
}
const res = await breakpointContinueFinish(params)
if (res.code === 0) {
//
const params = {
fileName: file.value.name,
fileMd5: fileMd5.value,
filePath: res.data.filePath,
}
ElMessage.success('上传成功')
await removeChunk(params)
}
}
}
const FileInput = ref(null)
const inputChange = () => {
FileInput.value.dispatchEvent(new MouseEvent('click'))
}
</script>
<style lang='scss' scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
#fromCont {
display: inline-block;
}
.fileUpload {
padding: 3px 10px;
font-size: 12px;
height: 20px;
line-height: 20px;
position: relative;
cursor: pointer;
color: #000;
border: 1px solid #c1c1c1;
border-radius: 4px;
overflow: hidden;
display: inline-block;
input {
position: absolute;
font-size: 100px;
right: 0;
top: 0;
opacity: 0;
cursor: pointer;
}
}
.fileName {
display: inline-block;
vertical-align: top;
margin: 6px 15px 0 15px;
}
.uploadBtn {
position: relative;
top: -10px;
margin-left: 15px;
}
.tips {
margin-top: 30px;
font-size: 14px;
font-weight: 400;
color: #606266;
}
.el-divider {
margin: 0 0 30px 0;
}
.list {
margin-top: 15px;
}
.list-item {
display: block;
margin-right: 10px;
color: #606266;
line-height: 25px;
margin-bottom: 5px;
width: 40%;
.percentage {
float: right;
}
}
.list-enter-active,
.list-leave-active {
transition: all 1s;
}
.list-enter,
.list-leave-to
/* .list-leave-active for below version 2.1.8 */
{
opacity: 0;
transform: translateY(-30px);
}
</style>

View File

@ -0,0 +1,192 @@
<template>
<div v-loading.fullscreen.lock="fullscreenLoading">
<div class="gva-table-box">
<warning-bar title="点击“文件名/备注”可以编辑文件名或者备注内容。" />
<div class="gva-btn-list">
<upload-common :image-common="imageCommon" @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"
@on-failure="uploadFailure" @on-before-upload="beforeUpload" />
<el-input v-model="search.keyword" class="keyword" placeholder="请输入文件名或备注" />
<el-button type="primary" icon="search" @click="getTableData">查询</el-button>
</div>
<el-table :data="tableData">
<el-table-column align="left" label="预览" width="100">
<template #default="scope">
<CustomPic pic-type="file" :pic-src="scope.row.url" preview />
</template>
</el-table-column>
<el-table-column align="left" label="日期" prop="UpdatedAt" width="180">
<template #default="scope">
<div>{{ formatDate(scope.row.UpdatedAt) }}</div>
</template>
</el-table-column>
<el-table-column align="left" label="文件名/备注" prop="name" width="180">
<template #default="scope">
<div class="name" @click="editFileNameFunc(scope.row)">{{ scope.row.name }}</div>
</template>
</el-table-column>
<el-table-column align="left" label="链接" prop="url" min-width="300" />
<el-table-column align="left" label="标签" prop="tag" width="100">
<template #default="scope">
<el-tag :type="scope.row.tag === 'jpg' ? 'info' : 'success'" disable-transitions>{{ scope.row.tag }}
</el-tag>
</template>
</el-table-column>
<el-table-column align="left" label="操作" width="160">
<template #default="scope">
<el-button icon="download" type="primary" link @click="downloadFile(scope.row)">下载</el-button>
<el-button icon="delete" type="primary" link @click="deleteFileFunc(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]"
:style="{ float: 'right', padding: '20px' }" :total="total" layout="total, sizes, prev, pager, next, jumper"
@current-change="handleCurrentChange" @size-change="handleSizeChange" />
</div>
</div>
</div>
</template>
<script setup>
import { getFileList, deleteFile, editFileName } from '@/api/mediaFile'
import { downloadImage } from '@/utils/downloadImg'
import CustomPic from '@/components/customPic/index.vue'
import UploadImage from '@/components/upload/image.vue'
import UploadCommon from '@/components/upload/common.vue'
import { formatDate } from '@/utils/format'
import WarningBar from '@/components/warningBar/warningBar.vue'
import { ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
defineOptions({
name: 'Upload',
})
const path = ref(import.meta.env.VITE_BASE_API)
const imageUrl = ref('')
const imageCommon = ref('')
const page = ref(1)
const total = ref(0)
const pageSize = ref(10)
const search = ref({})
const tableData = ref([])
const fullscreenLoading = ref(false)
//
const handleSizeChange = (val) => {
pageSize.value = val
getTableData()
}
const handleCurrentChange = (val) => {
page.value = val
getTableData()
}
const beforeUpload = () => {
console.log('beforeUpload')
fullscreenLoading.value = true
}
const uploadSuccess = () => {
console.log('uploadSuccess')
fullscreenLoading.value = false
getTableData()
}
const uploadFailure = () => {
console.log('uploadFailure')
fullscreenLoading.value = false
}
//
const getTableData = async () => {
const table = await getFileList({ page: page.value, pageSize: pageSize.value, ...search.value })
if (table.code === 0) {
tableData.value = table.data.list
total.value = table.data.total
page.value = table.data.page
pageSize.value = table.data.pageSize
}
}
getTableData()
const deleteFileFunc = async (row) => {
ElMessageBox.confirm('此操作将永久删除文件, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(async () => {
const res = await deleteFile(row)
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功!',
})
if (tableData.value.length === 1 && page.value > 1) {
page.value--
}
getTableData()
}
})
.catch(() => {
ElMessage({
type: 'info',
message: '已取消删除',
})
})
}
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)
}
}
/**
* 编辑文件名或者备注
* @param row
* @returns {Promise<void>}
*/
const editFileNameFunc = async (row) => {
ElMessageBox.prompt('请输入文件名或者备注', '编辑', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /\S/,
inputErrorMessage: '不能为空',
inputValue: row.name
}).then(async ({ value }) => {
row.name = value
// console.log(row)
const res = await editFileName(row)
if (res.code === 0) {
ElMessage({
type: 'success',
message: '编辑成功!',
})
getTableData()
}
}).catch(() => {
ElMessage({
type: 'info',
message: '取消修改'
})
})
}
</script>
<style scoped>
.name {
cursor: pointer;
}
</style>

View File

@ -1,100 +1,46 @@
<template>
<div id="userLayout" class="w-full h-full relative">
<div
id="userLayout"
class="w-full h-full relative"
>
<div
class="rounded-lg flex items-center justify-evenly w-full h-full bg-white md:w-screen md:h-screen md:bg-[#194bfb]"
>
class="rounded-lg flex items-center justify-evenly w-full h-full bg-white md:w-screen md:h-screen md:bg-[#194bfb]">
<div class="md:w-3/5 w-10/12 h-full flex items-center justify-evenly">
<div class="oblique h-[130%] w-3/5 bg-white transform -rotate-12 absolute -ml-52" />
<!-- 分割斜块 -->
<div class="z-[999] pt-12 pb-10 md:w-96 w-full rounded-lg flex flex-col justify-between box-border">
<div>
<div class="flex items-center justify-center">
<img
class="w-24"
src="@/assets/logo.png"
alt
style="margin-bottom: 15px;"
>
<img class="w-24" src="@/assets/logo.png" alt style="margin-bottom: 15px;">
</div>
<div class="mb-9">
<p class="text-center text-4xl font-bold">{{ $GIN_VUE_ADMIN.appName }}</p>
<!-- <p class="text-center text-sm font-normal text-gray-500 mt-2.5">A management platform using Golang and Vue</p> -->
</div>
<el-form
ref="loginForm"
:model="loginFormData"
:rules="rules"
:validate-on-rule-change="false"
@keyup.enter="submitForm"
>
<el-form-item
prop="username"
class="mb-6"
>
<el-input
v-model="loginFormData.username"
size="large"
placeholder="请输入用户名"
suffix-icon="user"
/>
<el-form ref="loginForm" :model="loginFormData" :rules="rules" :validate-on-rule-change="false"
@keyup.enter="submitForm">
<el-form-item prop="username" class="mb-6">
<el-input v-model="loginFormData.username" size="large" placeholder="请输入用户名" suffix-icon="user" />
</el-form-item>
<el-form-item
prop="password"
class="mb-6"
>
<el-input
v-model="loginFormData.password"
show-password
size="large"
type="password"
placeholder="请输入密码"
/>
<el-form-item prop="password" class="mb-6">
<el-input v-model="loginFormData.password" show-password size="large" type="password"
placeholder="请输入密码" />
</el-form-item>
<el-form-item
v-if="loginFormData.openCaptcha"
prop="captcha"
class="mb-6"
>
<el-form-item v-if="loginFormData.openCaptcha" prop="captcha" class="mb-6">
<div class="flex w-full justify-between">
<el-input
v-model="loginFormData.captcha"
placeholder="请输入验证码"
size="large"
class="flex-1 mr-5"
/>
<el-input v-model="loginFormData.captcha" placeholder="请输入验证码" size="large" class="flex-1 mr-5" />
<div class="w-1/3 h-11 bg-[#c3d4f2] rounded">
<img
v-if="picPath"
class="w-full h-full"
:src="picPath"
alt="请输入验证码"
@click="loginVerify()"
>
<img v-if="picPath" class="w-full h-full" :src="picPath" alt="请输入验证码" @click="loginVerify()">
</div>
</div>
</el-form-item>
<el-form-item class="mb-6">
<el-button
class="shadow shadow-blue-600 h-11 w-full"
type="primary"
size="large"
@click="submitForm"
> </el-button>
<el-button class="shadow shadow-blue-600 h-11 w-full" type="primary" size="large" @click="submitForm">
</el-button>
</el-form-item>
</el-form>
</div>
</div>
</div>
<div class="hidden md:block w-1/2 h-full float-right bg-[#194bfb]">
<img
class="h-full"
style="width: 90%"
src="@/assets/login_left.svg"
alt="banner"
>
<img class="h-full" style="width: 90%" src="@/assets/login_left.svg" alt="banner">
</div>
</div>
</div>
@ -127,7 +73,7 @@ const checkPassword = (rule, value, callback) => {
}
//
const loginVerify = async() => {
const loginVerify = async () => {
const ele = await captcha()
rules.captcha.push({
max: ele.data.captchaLength,
@ -163,11 +109,11 @@ const rules = reactive({
})
const userStore = useUserStore()
const login = async() => {
const login = async () => {
return await userStore.LoginIn(loginFormData)
}
const submitForm = () => {
loginForm.value.validate(async(v) => {
loginForm.value.validate(async (v) => {
if (v) {
const flag = await login()
if (!flag) {

View File

@ -5,42 +5,21 @@
<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"
/>
<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"
>
<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"
>
<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"
>
<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"
>
<el-icon class="cursor-pointer" color="#67c23a" @click="enterEdit">
<check />
</el-icon>
<el-icon
class="cursor-pointer"
color="#f23c3c"
@click="closeEdit"
>
<el-icon class="cursor-pointer" color="#f23c3c" @click="closeEdit">
<close />
</el-icon>
</p>
@ -61,14 +40,8 @@
</div>
<div class="col-span-9 ">
<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"
>
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="账号绑定" name="second">
<ul>
<li class="borderd">
<p class="pb-2.5 text-xl text-gray-600">手机</p>
@ -96,11 +69,7 @@
<p class="pb-2.5 text-xl text-gray-600">修改密码</p>
<p class="pb-2.5 text-lg text-gray-400">
修改个人密码
<a
href="javascript:void(0)"
class="float-right text-blue-400"
@click="showPassword = true"
>修改密码</a>
<a href="javascript:void(0)" class="float-right text-blue-400" @click="showPassword = true">修改密码</a>
</p>
</li>
</ul>
@ -110,162 +79,66 @@
</div>
</div>
<el-dialog
v-model="showPassword"
title="修改密码"
width="360px"
@close="clearPassword"
>
<el-form
ref="modifyPwdForm"
:model="pwdModify"
:rules="rules"
label-width="80px"
>
<el-form-item
:minlength="6"
label="原密码"
prop="password"
>
<el-input
v-model="pwdModify.password"
show-password
/>
<el-dialog v-model="showPassword" title="修改密码" width="360px" @close="clearPassword">
<el-form ref="modifyPwdForm" :model="pwdModify" :rules="rules" label-width="80px">
<el-form-item :minlength="6" label="原密码" prop="password">
<el-input v-model="pwdModify.password" show-password />
</el-form-item>
<el-form-item
:minlength="6"
label="新密码"
prop="newPassword"
>
<el-input
v-model="pwdModify.newPassword"
show-password
/>
<el-form-item :minlength="6" label="新密码" prop="newPassword">
<el-input v-model="pwdModify.newPassword" show-password />
</el-form-item>
<el-form-item
:minlength="6"
label="确认密码"
prop="confirmPassword"
>
<el-input
v-model="pwdModify.confirmPassword"
show-password
/>
<el-form-item :minlength="6" label="确认密码" prop="confirmPassword">
<el-input v-model="pwdModify.confirmPassword" show-password />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button
@click="showPassword = false"
> </el-button>
<el-button
type="primary"
@click="savePassword"
> </el-button>
<el-button @click="showPassword = false"> </el-button>
<el-button type="primary" @click="savePassword"> </el-button>
</div>
</template>
</el-dialog>
<el-dialog
v-model="changePhoneFlag"
title="绑定手机"
width="600px"
>
<el-dialog v-model="changePhoneFlag" title="绑定手机" width="600px">
<el-form :model="phoneForm">
<el-form-item
label="手机号"
label-width="120px"
>
<el-input
v-model="phoneForm.phone"
placeholder="请输入手机号"
autocomplete="off"
/>
<el-form-item label="手机号" label-width="120px">
<el-input v-model="phoneForm.phone" placeholder="请输入手机号" autocomplete="off" />
</el-form-item>
<el-form-item
label="验证码"
label-width="120px"
>
<el-form-item label="验证码" label-width="120px">
<div class="flex w-full gap-4">
<el-input
v-model="phoneForm.code"
class="flex-1"
autocomplete="off"
placeholder="请自行设计短信服务,此处为模拟随便写"
style="width:300px"
/>
<el-button
type="primary"
:disabled="time>0"
@click="getCode"
>{{ time>0?`(${time}s)后重新获取`:'获取验证码' }}</el-button>
<el-input v-model="phoneForm.code" class="flex-1" autocomplete="off" placeholder="请自行设计短信服务,此处为模拟随便写"
style="width:300px" />
<el-button type="primary" :disabled="time > 0" @click="getCode">{{ time > 0 ? `(${time}s)后重新获取` : '获取验证码'
}}</el-button>
</div>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button
@click="closeChangePhone"
>取消</el-button>
<el-button
type="primary"
@click="changePhone"
>更改</el-button>
<el-button @click="closeChangePhone">取消</el-button>
<el-button type="primary" @click="changePhone">更改</el-button>
</span>
</template>
</el-dialog>
<el-dialog
v-model="changeEmailFlag"
title="绑定邮箱"
width="600px"
>
<el-dialog v-model="changeEmailFlag" title="绑定邮箱" width="600px">
<el-form :model="emailForm">
<el-form-item
label="邮箱"
label-width="120px"
>
<el-input
v-model="emailForm.email"
placeholder="请输入邮箱"
autocomplete="off"
/>
<el-form-item label="邮箱" label-width="120px">
<el-input v-model="emailForm.email" placeholder="请输入邮箱" autocomplete="off" />
</el-form-item>
<el-form-item
label="验证码"
label-width="120px"
>
<el-form-item label="验证码" label-width="120px">
<div class="flex w-full gap-4">
<el-input
v-model="emailForm.code"
class="flex-1"
placeholder="请自行设计邮件服务,此处为模拟随便写"
autocomplete="off"
style="width:300px"
/>
<el-button
type="primary"
:disabled="emailTime>0"
@click="getEmailCode"
>{{ emailTime>0?`(${emailTime}s)后重新获取`:'获取验证码' }}</el-button>
<el-input v-model="emailForm.code" class="flex-1" placeholder="请自行设计邮件服务,此处为模拟随便写" autocomplete="off"
style="width:300px" />
<el-button type="primary" :disabled="emailTime > 0" @click="getEmailCode">{{
emailTime > 0 ? `(${emailTime}s)后重新获取` : '获取验证码' }}</el-button>
</div>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button
@click="closeChangeEmail"
>取消</el-button>
<el-button
type="primary"
@click="changeEmail"
>更改</el-button>
<el-button @click="closeChangeEmail">取消</el-button>
<el-button type="primary" @click="changeEmail">更改</el-button>
</span>
</template>
</el-dialog>
@ -315,7 +188,7 @@ const showPassword = ref(false)
const pwdModify = ref({})
const nickName = ref('')
const editFlag = ref(false)
const savePassword = async() => {
const savePassword = async () => {
modifyPwdForm.value.validate((valid) => {
if (valid) {
changePassword({
@ -342,7 +215,7 @@ const clearPassword = () => {
modifyPwdForm.value.clearValidate()
}
watch(() => userStore.userInfo.headerImg, async(val) => {
watch(() => userStore.userInfo.headerImg, async (val) => {
const res = await setSelfInfo({ headerImg: val })
if (res.code === 0) {
userStore.ResetUserInfo({ headerImg: val })
@ -363,7 +236,7 @@ const closeEdit = () => {
editFlag.value = false
}
const enterEdit = async() => {
const enterEdit = async () => {
const res = await setSelfInfo({
nickName: nickName.value
})
@ -389,7 +262,7 @@ const phoneForm = reactive({
code: ''
})
const getCode = async() => {
const getCode = async () => {
time.value = 60
let timer = setInterval(() => {
time.value--
@ -406,7 +279,7 @@ const closeChangePhone = () => {
phoneForm.code = ''
}
const changePhone = async() => {
const changePhone = async () => {
const res = await setSelfInfo({ phone: phoneForm.phone })
if (res.code === 0) {
ElMessage.success('修改成功')
@ -422,7 +295,7 @@ const emailForm = reactive({
code: ''
})
const getEmailCode = async() => {
const getEmailCode = async () => {
emailTime.value = 60
let timer = setInterval(() => {
emailTime.value--
@ -439,7 +312,7 @@ const closeChangeEmail = () => {
emailForm.code = ''
}
const changeEmail = async() => {
const changeEmail = async () => {
const res = await setSelfInfo({ email: emailForm.email })
if (res.code === 0) {
ElMessage.success('修改成功')
@ -453,13 +326,13 @@ const changeEmail = async() => {
<style lang="scss">
.borderd {
@apply border-b-2 border-solid border-gray-100 border-t-0 border-r-0 border-l-0;
&:last-child{
&:last-child {
@apply border-b-0;
}
}
.info-list{
@apply w-full whitespace-nowrap overflow-hidden text-ellipsis py-3 text-lg text-gray-700
}
.info-list {
@apply w-full whitespace-nowrap overflow-hidden text-ellipsis py-3 text-lg text-gray-700
}
</style>

View File

@ -1,28 +1,14 @@
<template>
<div>
<el-select
v-model="metaData.icon"
clearable
filterable
placeholder="请选择"
>
<el-select v-model="metaData.icon" clearable filterable placeholder="请选择">
<template #prefix>
<el-icon>
<component :is="metaData.icon" />
</el-icon>
</template>
<el-option
v-for="item in options"
:key="item.key"
class="select__option_item"
:label="item.key"
:value="item.key"
>
<span
class="gva-icon"
style=" padding: 3px 0 0; "
:class="item.label"
>
<el-option v-for="item in options" :key="item.key" class="select__option_item" :label="item.key"
:value="item.key">
<span class="gva-icon" style=" padding: 3px 0 0; " :class="item.label">
<el-icon>
<component :is="item.label" />
</el-icon>
@ -43,7 +29,7 @@ defineOptions({
const props = defineProps({
meta: {
default: function() {
default: function () {
return {}
},
type: Object,

View File

@ -2,45 +2,20 @@
<div>
<div class="gva-table-box">
<div class="gva-btn-list">
<el-button
type="primary"
icon="plus"
@click="addMenu('0')"
>新增根菜单</el-button>
<el-button type="primary" icon="plus" @click="addMenu('0')">新增根菜单</el-button>
</div>
<!-- 由于此处菜单跟左侧列表一一对应所以不需要分页 pageSize默认999 -->
<el-table
:data="tableData"
row-key="ID"
>
<el-table-column
align="left"
label="ID"
min-width="100"
prop="ID"
/>
<el-table-column
align="left"
label="展示名称"
min-width="120"
prop="authorityName"
>
<el-table :data="tableData" row-key="ID">
<el-table-column align="left" label="ID" min-width="100" prop="ID" />
<el-table-column align="left" label="展示名称" min-width="180" prop="authorityName">
<template #default="scope">
<span>{{ scope.row.meta.title }}</span>
</template>
</el-table-column>
<el-table-column
align="left"
label="图标"
min-width="140"
prop="authorityName"
>
<el-table-column align="left" label="图标" min-width="180" prop="authorityName">
<template #default="scope">
<div
v-if="scope.row.meta.icon"
class="icon-column"
>
<div v-if="scope.row.meta.icon" class="icon-column">
<el-icon>
<component :is="scope.row.meta.icon" />
</el-icon>
@ -48,353 +23,136 @@
</div>
</template>
</el-table-column>
<el-table-column
align="left"
label="路由Name"
show-overflow-tooltip
min-width="160"
prop="name"
/>
<el-table-column
align="left"
label="路由Path"
show-overflow-tooltip
min-width="160"
prop="path"
/>
<el-table-column
align="left"
label="是否隐藏"
min-width="100"
prop="hidden"
>
<el-table-column align="left" label="路由Name" show-overflow-tooltip min-width="160" prop="name" />
<el-table-column align="left" label="路由Path" show-overflow-tooltip min-width="160" prop="path" />
<el-table-column align="left" label="是否隐藏" min-width="100" prop="hidden">
<template #default="scope">
<span>{{ scope.row.hidden?"隐藏":"显示" }}</span>
<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="70"
prop="sort"
/>
<el-table-column
align="left"
label="文件路径"
min-width="360"
prop="component"
/>
<el-table-column
align="left"
fixed="right"
label="操作"
width="300"
>
<el-table-column align="left" label="父节点" min-width="90" prop="parentId" />
<el-table-column align="left" label="排序" min-width="70" prop="sort" />
<el-table-column align="left" label="文件路径" min-width="360" prop="component" />
<el-table-column align="left" fixed="right" label="操作" width="300">
<template #default="scope">
<el-button
type="primary"
link
icon="plus"
@click="addMenu(scope.row.ID)"
>添加子菜单</el-button>
<el-button
type="primary"
link
icon="edit"
@click="editMenu(scope.row.ID)"
>编辑</el-button>
<el-button
type="primary"
link
icon="delete"
@click="deleteMenu(scope.row.ID)"
>删除</el-button>
<el-button type="primary" link icon="plus" @click="addMenu(scope.row.ID)">添加子菜单</el-button>
<el-button type="primary" link icon="edit" @click="editMenu(scope.row.ID)">编辑</el-button>
<el-button type="primary" link icon="delete" @click="deleteMenu(scope.row.ID)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<el-dialog
v-model="dialogFormVisible"
:before-close="handleClose"
:title="dialogTitle"
>
<el-dialog v-model="dialogFormVisible" :before-close="handleClose" :title="dialogTitle">
<warning-bar title="新增菜单,需要在角色管理内配置权限才可使用" />
<el-form
v-if="dialogFormVisible"
ref="menuForm"
:inline="true"
:model="form"
:rules="rules"
label-position="top"
label-width="85px"
>
<el-form-item
label="路由Name"
prop="path"
style="width:30%"
>
<el-input
v-model="form.name"
autocomplete="off"
placeholder="唯一英文字符串"
@change="changeName"
/>
<el-form v-if="dialogFormVisible" ref="menuForm" :inline="true" :model="form" :rules="rules" label-position="top"
label-width="85px">
<el-form-item label="路由Name" prop="path" style="width:30%">
<el-input v-model="form.name" autocomplete="off" placeholder="唯一英文字符串" @change="changeName" />
</el-form-item>
<el-form-item
prop="path"
style="width:30%"
>
<el-form-item prop="path" style="width:30%">
<template #label>
<span style="display: inline-flex;align-items: center;">
<span>路由Path</span>
<el-checkbox
v-model="checkFlag"
style="margin-left:12px;height: auto"
>添加参数</el-checkbox>
<el-checkbox v-model="checkFlag" style="margin-left:12px;height: auto">添加参数</el-checkbox>
</span>
</template>
<el-input
v-model="form.path"
:disabled="!checkFlag"
autocomplete="off"
placeholder="建议只在后方拼接参数"
/>
<el-input v-model="form.path" :disabled="!checkFlag" autocomplete="off" placeholder="建议只在后方拼接参数" />
</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-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>
<el-form-item
label="父节点ID"
style="width:30%"
>
<el-cascader
v-model="form.parentId"
style="width:100%"
:disabled="!isEdit"
:options="menuOption"
:props="{ checkStrictly: true,label:'title',value:'ID',disabled:'disabled',emitPath:false}"
:show-all-levels="false"
filterable
/>
<el-form-item label="父节点ID" style="width:30%">
<el-cascader v-model="form.parentId" style="width:100%" :disabled="!isEdit" :options="menuOption"
:props="{ checkStrictly: true, label: 'title', value: 'ID', disabled: 'disabled', emitPath: false }"
:show-all-levels="false" filterable />
</el-form-item>
<el-form-item
label="文件路径"
prop="component"
style="width:60%"
>
<el-input
v-model="form.component"
autocomplete="off"
placeholder="页面:view/xxx/xx.vue 插件:plugin/xx/xx.vue"
@blur="fmtComponent"
/>
<el-form-item label="文件路径" prop="component" style="width:60%">
<el-input v-model="form.component" autocomplete="off" placeholder="页面:view/xxx/xx.vue 插件:plugin/xx/xx.vue"
@blur="fmtComponent" />
<span style="font-size:12px;margin-right:12px;">如果菜单包含子菜单请创建router-view二级路由页面或者</span><el-button
style="margin-top:4px"
@click="form.component = 'view/routerHolder.vue'"
>点我设置</el-button>
style="margin-top:4px" @click="form.component = 'view/routerHolder.vue'">点我设置</el-button>
</el-form-item>
<el-form-item
label="展示名称"
prop="meta.title"
style="width:30%"
>
<el-input
v-model="form.meta.title"
autocomplete="off"
/>
<el-form-item label="展示名称" prop="meta.title" style="width:30%">
<el-input v-model="form.meta.title" autocomplete="off" />
</el-form-item>
<el-form-item
label="图标"
prop="meta.icon"
style="width:30%"
>
<icon
:meta="form.meta"
style="width:100%"
/>
<el-form-item label="图标" prop="meta.icon" style="width:30%">
<icon :meta="form.meta" style="width:100%" />
</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:30%">
<el-input v-model.number="form.sort" autocomplete="off" />
</el-form-item>
<el-form-item
prop="meta.activeName"
style="width:30%"
>
<el-form-item prop="meta.activeName" style="width:30%">
<template #label>
<div>
<span> 高亮菜单 </span>
<el-tooltip
content="注当到达此路由时候指定左侧菜单指定name会处于活跃状态亮起可为空为空则为本路由Name。"
placement="top"
effect="light"
>
<el-icon><QuestionFilled /></el-icon>
<el-tooltip content="注当到达此路由时候指定左侧菜单指定name会处于活跃状态亮起可为空为空则为本路由Name。" placement="top" effect="light">
<el-icon>
<QuestionFilled />
</el-icon>
</el-tooltip>
</div>
</template>
<el-input
v-model="form.meta.activeName"
:placeholder="form.name"
autocomplete="off"
/>
<el-input v-model="form.meta.activeName" :placeholder="form.name" autocomplete="off" />
</el-form-item>
<el-form-item
label="KeepAlive"
prop="meta.keepAlive"
style="width:30%"
>
<el-select
v-model="form.meta.keepAlive"
style="width:100%"
placeholder="是否keepAlive缓存页面"
>
<el-option
:value="false"
label="否"
/>
<el-option
:value="true"
label="是"
/>
<el-form-item label="KeepAlive" prop="meta.keepAlive" style="width:30%">
<el-select v-model="form.meta.keepAlive" style="width:100%" placeholder="是否keepAlive缓存页面">
<el-option :value="false" label="否" />
<el-option :value="true" label="是" />
</el-select>
</el-form-item>
<el-form-item
label="CloseTab"
prop="meta.closeTab"
style="width:30%"
>
<el-select
v-model="form.meta.closeTab"
style="width:100%"
placeholder="是否自动关闭tab"
>
<el-option
:value="false"
label="否"
/>
<el-option
:value="true"
label="是"
/>
<el-form-item label="CloseTab" prop="meta.closeTab" style="width:30%">
<el-select v-model="form.meta.closeTab" style="width:100%" placeholder="是否自动关闭tab">
<el-option :value="false" label="否" />
<el-option :value="true" label="是" />
</el-select>
</el-form-item>
<el-form-item style="width:30%">
<template #label>
<div>
<span> 是否为基础页面 </span>
<el-tooltip
content="此项选择为是,则不会展示左侧菜单以及顶部信息。"
placement="top"
effect="light"
>
<el-icon><QuestionFilled /></el-icon>
<el-tooltip content="此项选择为是,则不会展示左侧菜单以及顶部信息。" placement="top" effect="light">
<el-icon>
<QuestionFilled />
</el-icon>
</el-tooltip>
</div>
</template>
<el-select
v-model="form.meta.defaultMenu"
style="width:100%"
placeholder="是否为基础页面"
>
<el-option
:value="false"
label="否"
/>
<el-option
:value="true"
label="是"
/>
<el-select v-model="form.meta.defaultMenu" style="width:100%" placeholder="是否为基础页面">
<el-option :value="false" label="否" />
<el-option :value="true" label="是" />
</el-select>
</el-form-item>
</el-form>
<div>
<div class="flex items-center gap-2">
<el-button
type="primary"
icon="edit"
@click="addParameter(form)"
>新增菜单参数</el-button>
<el-icon
class="cursor-pointer"
@click="toDoc('https://www.bilibili.com/video/BV1kv4y1g7nT?p=9&vd_source=f2640257c21e3b547a790461ed94875e')"
><VideoCameraFilled /></el-icon>
<el-button type="primary" icon="edit" @click="addParameter(form)">新增菜单参数</el-button>
<el-icon class="cursor-pointer"
@click="toDoc('https://www.bilibili.com/video/BV1kv4y1g7nT?p=9&vd_source=f2640257c21e3b547a790461ed94875e')">
<VideoCameraFilled />
</el-icon>
</div>
<el-table
:data="form.parameters"
style="width: 100%;margin-top: 12px;"
>
<el-table-column
align="left"
prop="type"
label="参数类型"
width="180"
>
<el-table :data="form.parameters" style="width: 100%;margin-top: 12px;">
<el-table-column align="left" prop="type" label="参数类型" width="180">
<template #default="scope">
<el-select
v-model="scope.row.type"
placeholder="请选择"
>
<el-option
key="query"
value="query"
label="query"
/>
<el-option
key="params"
value="params"
label="params"
/>
<el-select v-model="scope.row.type" placeholder="请选择">
<el-option key="query" value="query" label="query" />
<el-option key="params" value="params" label="params" />
</el-select>
</template>
</el-table-column>
<el-table-column
align="left"
prop="key"
label="参数key"
width="180"
>
<el-table-column align="left" prop="key" label="参数key" width="180">
<template #default="scope">
<div>
<el-input v-model="scope.row.key" />
</div>
</template>
</el-table-column>
<el-table-column
align="left"
prop="value"
label="参数值"
>
<el-table-column align="left" prop="value" label="参数值">
<template #default="scope">
<div>
<el-input v-model="scope.row.value" />
@ -404,52 +162,31 @@
<el-table-column align="left">
<template #default="scope">
<div>
<el-button
type="danger"
icon="delete"
@click="deleteParameter(form.parameters,scope.$index)"
>删除</el-button>
<el-button type="danger" icon="delete"
@click="deleteParameter(form.parameters, scope.$index)">删除</el-button>
</div>
</template>
</el-table-column>
</el-table>
<div class="flex items-center gap-2 mt-3">
<el-button
type="primary"
icon="edit"
@click="addBtn(form)"
>新增可控按钮
<el-button type="primary" icon="edit" @click="addBtn(form)">新增可控按钮
</el-button>
<el-icon
class="cursor-pointer"
@click="toDoc('https://www.bilibili.com/video/BV1kv4y1g7nT?p=11&vd_source=f2640257c21e3b547a790461ed94875e')"
><VideoCameraFilled /></el-icon>
<el-icon class="cursor-pointer"
@click="toDoc('https://www.bilibili.com/video/BV1kv4y1g7nT?p=11&vd_source=f2640257c21e3b547a790461ed94875e')">
<VideoCameraFilled />
</el-icon>
</div>
<el-table
:data="form.menuBtn"
style="width: 100%;margin-top: 12px;"
>
<el-table-column
align="left"
prop="name"
label="按钮名称"
width="180"
>
<el-table :data="form.menuBtn" style="width: 100%;margin-top: 12px;">
<el-table-column align="left" prop="name" label="按钮名称" width="180">
<template #default="scope">
<div>
<el-input v-model="scope.row.name" />
</div>
</template>
</el-table-column>
<el-table-column
align="left"
prop="name"
label="备注"
width="180"
>
<el-table-column align="left" prop="name" label="备注" width="180">
<template #default="scope">
<div>
<el-input v-model="scope.row.desc" />
@ -459,12 +196,7 @@
<el-table-column align="left">
<template #default="scope">
<div>
<el-button
type="danger"
icon="delete"
@click="deleteBtn(form.menuBtn,scope.$index)"
>删除</el-button>
<el-button type="danger" icon="delete" @click="deleteBtn(form.menuBtn, scope.$index)">删除</el-button>
</div>
</template>
</el-table-column>
@ -473,10 +205,7 @@
<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>
@ -520,7 +249,7 @@ const pageSize = ref(999)
const tableData = ref([])
const searchInfo = ref({})
//
const getTableData = async() => {
const getTableData = async () => {
const table = await getMenuList({ page: page.value, pageSize: pageSize.value, ...searchInfo.value })
if (table.code === 0) {
tableData.value = table.data.list
@ -564,7 +293,7 @@ const addBtn = (form) => {
})
}
//
const deleteBtn = async(btns, index) => {
const deleteBtn = async (btns, index) => {
const btn = btns[index]
if (btn.ID === 0) {
btns.splice(index, 1)
@ -609,7 +338,7 @@ const deleteMenu = (ID) => {
cancelButtonText: '取消',
type: 'warning'
})
.then(async() => {
.then(async () => {
const res = await deleteBaseMenu({ ID })
if (res.code === 0) {
ElMessage({
@ -659,7 +388,7 @@ const closeDialog = () => {
dialogFormVisible.value = false
}
//
const enterDialog = async() => {
const enterDialog = async () => {
menuForm.value.validate(async valid => {
if (valid) {
let res
@ -734,7 +463,7 @@ const addMenu = (id) => {
dialogFormVisible.value = true
}
//
const editMenu = async(id) => {
const editMenu = async (id) => {
dialogTitle.value = '编辑菜单'
const res = await getBaseMenuById({ id })
form.value = res.data.menu
@ -749,10 +478,12 @@ const editMenu = async(id) => {
.warning {
color: #dc143c;
}
.icon-column{
.icon-column {
display: flex;
align-items: center;
.el-icon{
.el-icon {
margin-right: 8px;
}
}

View File

@ -102,14 +102,16 @@ export default ({
fullImportPlugin()
)
} else {
config.plugins.push(AutoImport({
config.plugins.push(
AutoImport({
resolvers: [ElementPlusResolver()]
}),
Components({
resolvers: [ElementPlusResolver({
importStyle: 'sass'
})]
}))
})
)
}
return config
}