增加栏目管理

增加媒体库
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'], extends: ['plugin:vue/recommended', 'eslint:recommended'],
rules: { rules: {
'vue/no-v-model-argument': 0, '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/singleline-html-element-content-newline': 'off',
'vue/multiline-html-element-content-newline': 'off', 'vue/multiline-html-element-content-newline': 'off',
'vue/multi-word-component-names': 'off', 'vue/multi-word-component-names': 'off',
'vue/html-closing-bracket-newline': 'off',
'vue/no-v-html': 'off', 'vue/no-v-html': 'off',
'vue/html-indent': 'off',
'accessor-pairs': 2, 'accessor-pairs': 2,
'arrow-spacing': [ 'arrow-spacing': [
2, 2,

View File

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

View File

@ -1,19 +1,8 @@
<template> <template>
<div class="border border-solid border-gray-100 h-full"> <div class="border border-solid border-gray-100 h-full">
<Toolbar <Toolbar :editor="editorRef" :default-config="toolbarConfig" mode="default" />
:editor="editorRef" <Editor v-model="valueHtml" class="overflow-y-hidden mt-0.5" style="height: 18rem;" :default-config="editorConfig"
:default-config="toolbarConfig" mode="default" @onCreated="handleCreated" @onChange="change" />
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> </div>
</template> </template>
@ -56,7 +45,7 @@ const editorConfig = {
} }
editorConfig.MENU_CONF['uploadImage'] = { editorConfig.MENU_CONF['uploadImage'] = {
fieldName: 'file', fieldName: 'file',
server: basePath + '/fileUploadAndDownload/upload?noSave=1', server: basePath + '/mediaFile/upload?noSave=1',
customInsert(res, insertFn) { customInsert(res, insertFn) {
if (res.code === 0) { if (res.code === 0) {
const urlPath = getUrl(res.data.file.url) const urlPath = getUrl(res.data.file.url)
@ -85,6 +74,4 @@ watch(() => props.modelValue, () => {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

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

View File

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

View File

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

View File

@ -1,14 +1,8 @@
<template> <template>
<div> <div>
<el-upload <el-upload :action="`${path}/mediaFile/upload`" :show-file-list="false" :on-success="handleImageSuccess"
:action="`${path}/fileUploadAndDownload/upload`" :on-error="handleImageFailure" :before-upload="beforeImageUpload" :multiple="false">
:show-file-list="false" <el-button type="primary">图片自动压缩上传</el-button>
:on-success="handleImageSuccess"
:before-upload="beforeImageUpload"
:multiple="false"
>
<el-button type="primary">压缩上传</el-button>
</el-upload> </el-upload>
</div> </div>
</template> </template>
@ -17,13 +11,12 @@
import ImageCompress from '@/utils/image' import ImageCompress from '@/utils/image'
import { ref } from 'vue' import { ref } from 'vue'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { useUserStore } from '@/pinia/modules/user'
defineOptions({ defineOptions({
name: 'UploadImage', name: 'UploadImage',
}) })
const emit = defineEmits(['on-success']) const emit = defineEmits(['on-success', 'on-failure', 'on-before-upload'])
const props = defineProps({ const props = defineProps({
imageUrl: { imageUrl: {
type: String, type: String,
@ -41,8 +34,6 @@ const props = defineProps({
const path = ref(import.meta.env.VITE_BASE_API) const path = ref(import.meta.env.VITE_BASE_API)
const userStore = useUserStore()
const beforeImageUpload = (file) => { const beforeImageUpload = (file) => {
const isJPG = file.type === 'image/jpeg' const isJPG = file.type === 'image/jpeg'
const isPng = file.type === 'image/png' const isPng = file.type === 'image/png'
@ -51,19 +42,26 @@ const beforeImageUpload = (file) => {
return false return false
} }
emit('on-before-upload')
const isRightSize = file.size / 1024 < props.fileSize const isRightSize = file.size / 1024 < props.fileSize
if (!isRightSize) { if (!isRightSize) {
// //
const compress = new ImageCompress(file, props.fileSize, props.maxWH) const compress = new ImageCompress(file, props.fileSize, props.maxWH)
return compress.compress() return compress.compress()
} }
return isRightSize return isRightSize
} }
const handleImageFailure = (res) => {
emit('on-failure')
}
const handleImageSuccess = (res) => { const handleImageSuccess = (res) => {
const { data } = res const { data } = res
if (data.file) { if (data.mediaFile) {
emit('on-success', data.file.url) emit('on-success', data.mediaFile.url)
} }
} }
@ -78,9 +76,11 @@ const handleImageSuccess = (res) => {
position: relative; position: relative;
overflow: hidden; overflow: hidden;
} }
.image-uploader { .image-uploader {
border-color: #409eff; border-color: #409eff;
} }
.image-uploader-icon { .image-uploader-icon {
font-size: 28px; font-size: 28px;
color: #8c939d; color: #8c939d;
@ -89,6 +89,7 @@ const handleImageSuccess = (res) => {
line-height: 178px; line-height: 178px;
text-align: center; text-align: center;
} }
.image { .image {
width: 178px; width: 178px;
height: 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> <template>
<div <div id="userLayout" class="w-full h-full relative">
id="userLayout"
class="w-full h-full relative"
>
<div <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="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="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 class="z-[999] pt-12 pb-10 md:w-96 w-full rounded-lg flex flex-col justify-between box-border">
<div> <div>
<div class="flex items-center justify-center"> <div class="flex items-center justify-center">
<img <img class="w-24" src="@/assets/logo.png" alt style="margin-bottom: 15px;">
class="w-24"
src="@/assets/logo.png"
alt
style="margin-bottom: 15px;"
>
</div> </div>
<div class="mb-9"> <div class="mb-9">
<p class="text-center text-4xl font-bold">{{ $GIN_VUE_ADMIN.appName }}</p> <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> --> <!-- <p class="text-center text-sm font-normal text-gray-500 mt-2.5">A management platform using Golang and Vue</p> -->
</div> </div>
<el-form <el-form ref="loginForm" :model="loginFormData" :rules="rules" :validate-on-rule-change="false"
ref="loginForm" @keyup.enter="submitForm">
:model="loginFormData" <el-form-item prop="username" class="mb-6">
:rules="rules" <el-input v-model="loginFormData.username" size="large" placeholder="请输入用户名" suffix-icon="user" />
: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>
<el-form-item <el-form-item prop="password" class="mb-6">
prop="password" <el-input v-model="loginFormData.password" show-password size="large" type="password"
class="mb-6" placeholder="请输入密码" />
>
<el-input
v-model="loginFormData.password"
show-password
size="large"
type="password"
placeholder="请输入密码"
/>
</el-form-item> </el-form-item>
<el-form-item <el-form-item v-if="loginFormData.openCaptcha" prop="captcha" class="mb-6">
v-if="loginFormData.openCaptcha"
prop="captcha"
class="mb-6"
>
<div class="flex w-full justify-between"> <div class="flex w-full justify-between">
<el-input <el-input v-model="loginFormData.captcha" placeholder="请输入验证码" size="large" class="flex-1 mr-5" />
v-model="loginFormData.captcha"
placeholder="请输入验证码"
size="large"
class="flex-1 mr-5"
/>
<div class="w-1/3 h-11 bg-[#c3d4f2] rounded"> <div class="w-1/3 h-11 bg-[#c3d4f2] rounded">
<img <img v-if="picPath" class="w-full h-full" :src="picPath" alt="请输入验证码" @click="loginVerify()">
v-if="picPath"
class="w-full h-full"
:src="picPath"
alt="请输入验证码"
@click="loginVerify()"
>
</div> </div>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item class="mb-6"> <el-form-item class="mb-6">
<el-button <el-button class="shadow shadow-blue-600 h-11 w-full" type="primary" size="large" @click="submitForm">
class="shadow shadow-blue-600 h-11 w-full" </el-button>
type="primary"
size="large"
@click="submitForm"
> </el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
</div> </div>
</div> </div>
<div class="hidden md:block w-1/2 h-full float-right bg-[#194bfb]"> <div class="hidden md:block w-1/2 h-full float-right bg-[#194bfb]">
<img <img class="h-full" style="width: 90%" src="@/assets/login_left.svg" alt="banner">
class="h-full"
style="width: 90%"
src="@/assets/login_left.svg"
alt="banner"
>
</div> </div>
</div> </div>
</div> </div>
@ -127,7 +73,7 @@ const checkPassword = (rule, value, callback) => {
} }
// //
const loginVerify = async() => { const loginVerify = async () => {
const ele = await captcha() const ele = await captcha()
rules.captcha.push({ rules.captcha.push({
max: ele.data.captchaLength, max: ele.data.captchaLength,
@ -163,11 +109,11 @@ const rules = reactive({
}) })
const userStore = useUserStore() const userStore = useUserStore()
const login = async() => { const login = async () => {
return await userStore.LoginIn(loginFormData) return await userStore.LoginIn(loginFormData)
} }
const submitForm = () => { const submitForm = () => {
loginForm.value.validate(async(v) => { loginForm.value.validate(async (v) => {
if (v) { if (v) {
const flag = await login() const flag = await login()
if (!flag) { 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="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="user-card px-6 text-center bg-white shrink-0">
<div class="flex justify-center"> <div class="flex justify-center">
<SelectImage <SelectImage v-model="userStore.userInfo.headerImg" file-type="image" />
v-model="userStore.userInfo.headerImg"
file-type="image"
/>
</div> </div>
<div class="py-6 text-center"> <div class="py-6 text-center">
<p <p v-if="!editFlag" class="text-3xl flex justify-center items-center gap-4">
v-if="!editFlag"
class="text-3xl flex justify-center items-center gap-4"
>
{{ userStore.userInfo.nickName }} {{ userStore.userInfo.nickName }}
<el-icon <el-icon class="cursor-pointer text-sm" color="#66b1ff" @click="openEdit">
class="cursor-pointer text-sm"
color="#66b1ff"
@click="openEdit"
>
<edit /> <edit />
</el-icon> </el-icon>
</p> </p>
<p <p v-if="editFlag" class="flex justify-center items-center gap-4">
v-if="editFlag"
class="flex justify-center items-center gap-4"
>
<el-input v-model="nickName" /> <el-input v-model="nickName" />
<el-icon <el-icon class="cursor-pointer" color="#67c23a" @click="enterEdit">
class="cursor-pointer"
color="#67c23a"
@click="enterEdit"
>
<check /> <check />
</el-icon> </el-icon>
<el-icon <el-icon class="cursor-pointer" color="#f23c3c" @click="closeEdit">
class="cursor-pointer"
color="#f23c3c"
@click="closeEdit"
>
<close /> <close />
</el-icon> </el-icon>
</p> </p>
@ -61,14 +40,8 @@
</div> </div>
<div class="col-span-9 "> <div class="col-span-9 ">
<div class="bg-white h-full px-4 py-8 rounded-lg shadow-lg box-border"> <div class="bg-white h-full px-4 py-8 rounded-lg shadow-lg box-border">
<el-tabs <el-tabs v-model="activeName" @tab-click="handleClick">
v-model="activeName" <el-tab-pane label="账号绑定" name="second">
@tab-click="handleClick"
>
<el-tab-pane
label="账号绑定"
name="second"
>
<ul> <ul>
<li class="borderd"> <li class="borderd">
<p class="pb-2.5 text-xl text-gray-600">手机</p> <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-xl text-gray-600">修改密码</p>
<p class="pb-2.5 text-lg text-gray-400"> <p class="pb-2.5 text-lg text-gray-400">
修改个人密码 修改个人密码
<a <a href="javascript:void(0)" class="float-right text-blue-400" @click="showPassword = true">修改密码</a>
href="javascript:void(0)"
class="float-right text-blue-400"
@click="showPassword = true"
>修改密码</a>
</p> </p>
</li> </li>
</ul> </ul>
@ -110,162 +79,66 @@
</div> </div>
</div> </div>
<el-dialog <el-dialog v-model="showPassword" title="修改密码" width="360px" @close="clearPassword">
v-model="showPassword" <el-form ref="modifyPwdForm" :model="pwdModify" :rules="rules" label-width="80px">
title="修改密码" <el-form-item :minlength="6" label="原密码" prop="password">
width="360px" <el-input v-model="pwdModify.password" show-password />
@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>
<el-form-item <el-form-item :minlength="6" label="新密码" prop="newPassword">
:minlength="6" <el-input v-model="pwdModify.newPassword" show-password />
label="新密码"
prop="newPassword"
>
<el-input
v-model="pwdModify.newPassword"
show-password
/>
</el-form-item> </el-form-item>
<el-form-item <el-form-item :minlength="6" label="确认密码" prop="confirmPassword">
:minlength="6" <el-input v-model="pwdModify.confirmPassword" show-password />
label="确认密码"
prop="confirmPassword"
>
<el-input
v-model="pwdModify.confirmPassword"
show-password
/>
</el-form-item> </el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button <el-button @click="showPassword = false"> </el-button>
<el-button type="primary" @click="savePassword"> </el-button>
@click="showPassword = false"
> </el-button>
<el-button
type="primary"
@click="savePassword"
> </el-button>
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
<el-dialog <el-dialog v-model="changePhoneFlag" title="绑定手机" width="600px">
v-model="changePhoneFlag"
title="绑定手机"
width="600px"
>
<el-form :model="phoneForm"> <el-form :model="phoneForm">
<el-form-item <el-form-item label="手机号" label-width="120px">
label="手机号" <el-input v-model="phoneForm.phone" placeholder="请输入手机号" autocomplete="off" />
label-width="120px"
>
<el-input
v-model="phoneForm.phone"
placeholder="请输入手机号"
autocomplete="off"
/>
</el-form-item> </el-form-item>
<el-form-item <el-form-item label="验证码" label-width="120px">
label="验证码"
label-width="120px"
>
<div class="flex w-full gap-4"> <div class="flex w-full gap-4">
<el-input <el-input v-model="phoneForm.code" class="flex-1" autocomplete="off" placeholder="请自行设计短信服务,此处为模拟随便写"
v-model="phoneForm.code" style="width:300px" />
class="flex-1" <el-button type="primary" :disabled="time > 0" @click="getCode">{{ time > 0 ? `(${time}s)后重新获取` : '获取验证码'
autocomplete="off" }}</el-button>
placeholder="请自行设计短信服务,此处为模拟随便写"
style="width:300px"
/>
<el-button
type="primary"
:disabled="time>0"
@click="getCode"
>{{ time>0?`(${time}s)后重新获取`:'获取验证码' }}</el-button>
</div> </div>
</el-form-item> </el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button <el-button @click="closeChangePhone">取消</el-button>
<el-button type="primary" @click="changePhone">更改</el-button>
@click="closeChangePhone"
>取消</el-button>
<el-button
type="primary"
@click="changePhone"
>更改</el-button>
</span> </span>
</template> </template>
</el-dialog> </el-dialog>
<el-dialog <el-dialog v-model="changeEmailFlag" title="绑定邮箱" width="600px">
v-model="changeEmailFlag"
title="绑定邮箱"
width="600px"
>
<el-form :model="emailForm"> <el-form :model="emailForm">
<el-form-item <el-form-item label="邮箱" label-width="120px">
label="邮箱" <el-input v-model="emailForm.email" placeholder="请输入邮箱" autocomplete="off" />
label-width="120px"
>
<el-input
v-model="emailForm.email"
placeholder="请输入邮箱"
autocomplete="off"
/>
</el-form-item> </el-form-item>
<el-form-item <el-form-item label="验证码" label-width="120px">
label="验证码"
label-width="120px"
>
<div class="flex w-full gap-4"> <div class="flex w-full gap-4">
<el-input <el-input v-model="emailForm.code" class="flex-1" placeholder="请自行设计邮件服务,此处为模拟随便写" autocomplete="off"
v-model="emailForm.code" style="width:300px" />
class="flex-1" <el-button type="primary" :disabled="emailTime > 0" @click="getEmailCode">{{
placeholder="请自行设计邮件服务,此处为模拟随便写" emailTime > 0 ? `(${emailTime}s)后重新获取` : '获取验证码' }}</el-button>
autocomplete="off"
style="width:300px"
/>
<el-button
type="primary"
:disabled="emailTime>0"
@click="getEmailCode"
>{{ emailTime>0?`(${emailTime}s)后重新获取`:'获取验证码' }}</el-button>
</div> </div>
</el-form-item> </el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button <el-button @click="closeChangeEmail">取消</el-button>
<el-button type="primary" @click="changeEmail">更改</el-button>
@click="closeChangeEmail"
>取消</el-button>
<el-button
type="primary"
@click="changeEmail"
>更改</el-button>
</span> </span>
</template> </template>
</el-dialog> </el-dialog>
@ -315,7 +188,7 @@ const showPassword = ref(false)
const pwdModify = ref({}) const pwdModify = ref({})
const nickName = ref('') const nickName = ref('')
const editFlag = ref(false) const editFlag = ref(false)
const savePassword = async() => { const savePassword = async () => {
modifyPwdForm.value.validate((valid) => { modifyPwdForm.value.validate((valid) => {
if (valid) { if (valid) {
changePassword({ changePassword({
@ -342,7 +215,7 @@ const clearPassword = () => {
modifyPwdForm.value.clearValidate() modifyPwdForm.value.clearValidate()
} }
watch(() => userStore.userInfo.headerImg, async(val) => { watch(() => userStore.userInfo.headerImg, async (val) => {
const res = await setSelfInfo({ headerImg: val }) const res = await setSelfInfo({ headerImg: val })
if (res.code === 0) { if (res.code === 0) {
userStore.ResetUserInfo({ headerImg: val }) userStore.ResetUserInfo({ headerImg: val })
@ -363,7 +236,7 @@ const closeEdit = () => {
editFlag.value = false editFlag.value = false
} }
const enterEdit = async() => { const enterEdit = async () => {
const res = await setSelfInfo({ const res = await setSelfInfo({
nickName: nickName.value nickName: nickName.value
}) })
@ -389,7 +262,7 @@ const phoneForm = reactive({
code: '' code: ''
}) })
const getCode = async() => { const getCode = async () => {
time.value = 60 time.value = 60
let timer = setInterval(() => { let timer = setInterval(() => {
time.value-- time.value--
@ -406,7 +279,7 @@ const closeChangePhone = () => {
phoneForm.code = '' phoneForm.code = ''
} }
const changePhone = async() => { const changePhone = async () => {
const res = await setSelfInfo({ phone: phoneForm.phone }) const res = await setSelfInfo({ phone: phoneForm.phone })
if (res.code === 0) { if (res.code === 0) {
ElMessage.success('修改成功') ElMessage.success('修改成功')
@ -422,7 +295,7 @@ const emailForm = reactive({
code: '' code: ''
}) })
const getEmailCode = async() => { const getEmailCode = async () => {
emailTime.value = 60 emailTime.value = 60
let timer = setInterval(() => { let timer = setInterval(() => {
emailTime.value-- emailTime.value--
@ -439,7 +312,7 @@ const closeChangeEmail = () => {
emailForm.code = '' emailForm.code = ''
} }
const changeEmail = async() => { const changeEmail = async () => {
const res = await setSelfInfo({ email: emailForm.email }) const res = await setSelfInfo({ email: emailForm.email })
if (res.code === 0) { if (res.code === 0) {
ElMessage.success('修改成功') ElMessage.success('修改成功')
@ -453,13 +326,13 @@ const changeEmail = async() => {
<style lang="scss"> <style lang="scss">
.borderd { .borderd {
@apply border-b-2 border-solid border-gray-100 border-t-0 border-r-0 border-l-0; @apply border-b-2 border-solid border-gray-100 border-t-0 border-r-0 border-l-0;
&:last-child{
@apply border-b-0;
}
}
.info-list{ &:last-child {
@apply w-full whitespace-nowrap overflow-hidden text-ellipsis py-3 text-lg text-gray-700 @apply border-b-0;
}
} }
.info-list {
@apply w-full whitespace-nowrap overflow-hidden text-ellipsis py-3 text-lg text-gray-700
}
</style> </style>

View File

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

View File

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

View File

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