From 97a39ba60691c6810097911a4187e25d136e38d2 Mon Sep 17 00:00:00 2001 From: task <121913992@qq.com> Date: Mon, 6 Jan 2025 21:36:02 +0800 Subject: [PATCH 01/21] =?UTF-8?q?=E5=B0=86=E5=8F=82=E6=95=B0=E7=BC=93?= =?UTF-8?q?=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/pinia/modules/params.js | 31 ++++++++++++++++++++ web/src/utils/dictionary.js | 8 ----- web/src/utils/params.js | 14 +++++++++ web/src/view/superAdmin/params/sysParams.vue | 4 ++- 4 files changed, 48 insertions(+), 9 deletions(-) create mode 100644 web/src/pinia/modules/params.js create mode 100644 web/src/utils/params.js diff --git a/web/src/pinia/modules/params.js b/web/src/pinia/modules/params.js new file mode 100644 index 0000000000..54cdbf97a8 --- /dev/null +++ b/web/src/pinia/modules/params.js @@ -0,0 +1,31 @@ +import { getSysParam } from '@/api/sysParams' +import { defineStore } from 'pinia' +import { ref } from 'vue' + +export const useParamsStore = defineStore('params', () => { + const paramsMap = ref({}) + + const setParamsMap = (paramsRes) => { + paramsMap.value = { ...paramsMap.value, ...paramsRes } + } + + const getParams = async(key) => { + if (paramsMap.value[key] && paramsMap.value[key].length) { + return paramsMap.value[key] + } else { + const res = await getSysParam({ key }) + if (res.code === 0) { + const paramsRes = {} + paramsRes[key] = res.data.value + setParamsMap(paramsRes) + return paramsMap.value[key] + } + } + } + + return { + paramsMap, + setParamsMap, + getParams + } +}) diff --git a/web/src/utils/dictionary.js b/web/src/utils/dictionary.js index 679a6b9886..89ec656e43 100644 --- a/web/src/utils/dictionary.js +++ b/web/src/utils/dictionary.js @@ -1,5 +1,4 @@ import { useDictionaryStore } from '@/pinia/modules/dictionary' -import { getSysParam } from '@/api/sysParams' // 获取字典方法 使用示例 getDict('sex').then(res) 或者 async函数下 const res = await getDict('sex') export const getDict = async (type) => { const dictionaryStore = useDictionaryStore() @@ -25,10 +24,3 @@ export const showDictLabel = ( }) return Reflect.has(dictMap, code) ? dictMap[code] : '' } - -export const getParams = async (key) => { - const res = await getSysParam({ key }) - if (res.code === 0) { - return res.data.value - } -} diff --git a/web/src/utils/params.js b/web/src/utils/params.js new file mode 100644 index 0000000000..b03d539a39 --- /dev/null +++ b/web/src/utils/params.js @@ -0,0 +1,14 @@ +import { useParamsStore } from '@/pinia/modules/params' +/* + * 获取参数方法 使用示例 getParams('key').then(res) 或者 async函数下 const res = await getParams('key') + * const res = ref('') + * const fun = async () => { + * res.value = await getParams('test') + * } + * fun() + */ +export const getParams = async(key) => { + const paramsStore = useParamsStore() + await paramsStore.getParams(key) + return paramsStore.paramsMap[key] +} diff --git a/web/src/view/superAdmin/params/sysParams.vue b/web/src/view/superAdmin/params/sysParams.vue index 1feeda1e68..89fe6f95e4 100644 --- a/web/src/view/superAdmin/params/sysParams.vue +++ b/web/src/view/superAdmin/params/sysParams.vue @@ -1,5 +1,6 @@ @@ -16,6 +17,7 @@ import ImageCompress from '@/utils/image' import { ElMessage } from 'element-plus' import { getBaseUrl } from '@/utils/format' + import {Upload} from "@element-plus/icons-vue"; defineOptions({ name: 'UploadImage' @@ -34,6 +36,10 @@ maxWH: { type: Number, default: 1920 // 图片长宽上限 + }, + classId: { + type: Number, + default: 0 } }) diff --git a/web/src/view/example/upload/upload.vue b/web/src/view/example/upload/upload.vue index 42a4c9161d..9124308197 100644 --- a/web/src/view/example/upload/upload.vue +++ b/web/src/view/example/upload/upload.vue @@ -1,161 +1,232 @@ +} + +const handleNodeClick = (node) => { + search.value.keyword = null + search.value.classId = node.ID + page.value = 1 + getTableData() +} - +} + +const categoryDialogVisible = ref(false) +const categoryFormData = ref({ + ID: 0, + pid: 0, + name: '' +}) + +const categoryForm = ref(null) +const rules = ref({ + name: [ + {required: true, message: '请输入分类名称', trigger: 'blur'}, + {max: 20, message: '最多20位字符', trigger: 'blur'} + ] +}) + +const addCategoryFun = (category) => { + categoryDialogVisible.value = true + categoryFormData.value.ID = 0 + categoryFormData.value.pid = category.ID +} + +const editCategory = (category) => { + categoryFormData.value = { + ID: category.ID, + pid: category.pid, + name: category.name + } + categoryDialogVisible.value = true +} + +const deleteCategoryFun = async (id) => { + const res = await deleteCategory({id: id}) + if (res.code === 0) { + ElMessage.success({type: 'success', message: '删除成功'}) + await fetchCategories() + } +} + +const confirmAddCategory = async () => { + categoryForm.value.validate(async valid => { + if (valid) { + const res = await addCategory(categoryFormData.value) + if (res.code === 0) { + ElMessage({type: 'success', message: '操作成功'}) + await fetchCategories() + closeAddCategoryDialog() + } + } + }) +} + +const closeAddCategoryDialog = () => { + categoryDialogVisible.value = false + categoryFormData.value = { + ID: 0, + pid: 0, + name: '' + } +} + +fetchCategories() + From 6e3156758d73f0af3b6552945f759702fb8ba70a Mon Sep 17 00:00:00 2001 From: task <121913992@qq.com> Date: Wed, 8 Jan 2025 10:26:07 +0800 Subject: [PATCH 03/21] =?UTF-8?q?=E7=BC=96=E8=BE=91=E5=8F=AF=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E5=88=86=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/selectImage/selectImage.vue | 61 ++++++++----------- web/src/view/example/upload/upload.vue | 61 ++++++++----------- 2 files changed, 48 insertions(+), 74 deletions(-) diff --git a/web/src/components/selectImage/selectImage.vue b/web/src/components/selectImage/selectImage.vue index 1f76d17501..601686a810 100644 --- a/web/src/components/selectImage/selectImage.vue +++ b/web/src/components/selectImage/selectImage.vue @@ -14,14 +14,6 @@
-
-
- 全部分类 -
- - - -
{{ data.name }}
- - - - + + + @@ -113,6 +104,16 @@ draggable > + + + @@ -296,14 +297,21 @@ const deleteCheck = (item) => { const defaultProps = { children: 'children', label: 'name', - value: 'id' + value: 'ID' } const categories = ref([]) const fetchCategories = async() => { const res = await getCategoryList() + let data = { + name: '全部分类', + ID: 0, + pid: 0, + children:[] + } if (res.code === 0) { categories.value = res.data + categories.value.unshift(data) } } @@ -314,33 +322,12 @@ const handleNodeClick = (node) => { getImageList() } -const getAll = () => { - search.value.keyword = null - search.value.classId = 0 - page.value = 1 - getImageList() -} - const onSuccess = () => { search.value.keyword = null page.value = 1 getImageList() } -const handleCommand = (category, command) => { - switch (command) { - case 'add': - addCategoryFun(category) - break - case 'edit': - editCategory(category) - break - case 'delete': - deleteCategoryFun(category.ID) - break - } -} - const categoryDialogVisible = ref(false) const categoryFormData = ref({ ID: 0, diff --git a/web/src/view/example/upload/upload.vue b/web/src/view/example/upload/upload.vue index 9124308197..8836d5d191 100644 --- a/web/src/view/example/upload/upload.vue +++ b/web/src/view/example/upload/upload.vue @@ -3,14 +3,6 @@
-
-
- 全部分类 -
- - - -
{{ data.name }}
- - - - + + + @@ -139,6 +130,16 @@ draggable > + + + @@ -344,13 +345,6 @@ const importUrlFunc = () => { }) } -const getAll = () => { - search.value.keyword = null - search.value.classId = 0 - page.value = 1 - getTableData() -} - const onSuccess = () => { search.value.keyword = null page.value = 1 @@ -360,14 +354,21 @@ const onSuccess = () => { const defaultProps = { children: 'children', label: 'name', - value: 'id' + value: 'ID' } const categories = ref([]) const fetchCategories = async () => { const res = await getCategoryList() + let data = { + name: '全部分类', + ID: 0, + pid: 0, + children:[] + } if (res.code === 0) { categories.value = res.data + categories.value.unshift(data) } } @@ -378,20 +379,6 @@ const handleNodeClick = (node) => { getTableData() } -const handleCommand = (category, command) => { - switch (command) { - case 'add': - addCategoryFun(category) - break - case 'edit': - editCategory(category) - break - case 'delete': - deleteCategoryFun(category.ID) - break - } -} - const categoryDialogVisible = ref(false) const categoryFormData = ref({ ID: 0, From fcb58dffae1852fbfab414b6868b17ab464d2031 Mon Sep 17 00:00:00 2001 From: task <121913992@qq.com> Date: Thu, 9 Jan 2025 09:27:04 +0800 Subject: [PATCH 04/21] =?UTF-8?q?fix=E5=88=86=E7=B1=BB=E7=BC=96=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/service/example/exa_attachment_category.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/server/service/example/exa_attachment_category.go b/server/service/example/exa_attachment_category.go index 5c5df5d848..1e74fc9425 100644 --- a/server/service/example/exa_attachment_category.go +++ b/server/service/example/exa_attachment_category.go @@ -12,12 +12,14 @@ type AttachmentCategoryService struct{} // AddCategory 创建/更新的分类 func (a *AttachmentCategoryService) AddCategory(req *example.ExaAttachmentCategory) (err error) { // 检查是否已存在相同名称的分类 - if (!errors.Is(global.GVA_DB.Take(&example.ExaAttachmentCategory{}, "name = ?", req.Name).Error, gorm.ErrRecordNotFound)) { + if (!errors.Is(global.GVA_DB.Take(&example.ExaAttachmentCategory{}, "name = ? and pid = ?", req.Name, req.Pid).Error, gorm.ErrRecordNotFound)) { return errors.New("分类名称已存在") } - if req.ID > 0 { - if err = global.GVA_DB.Model(&example.ExaAttachmentCategory{}).Where("id = ?", req.ID).Update("name", req.Name).Error; err != nil { + if err = global.GVA_DB.Model(&example.ExaAttachmentCategory{}).Where("id = ?", req.ID).Updates(&example.ExaAttachmentCategory{ + Name: req.Name, + Pid: req.Pid, + }).Error; err != nil { return err } } else { From 4c8127b9aaf8a87c892434c6d30935d8406d4fe0 Mon Sep 17 00:00:00 2001 From: "Feng.YJ" <32027253+huiyifyj@users.noreply.github.com> Date: Mon, 13 Jan 2025 21:26:38 +0800 Subject: [PATCH 05/21] refactor(jwt): optimize JWT using `New()` instead of create instance directly (#1983) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重构 JWT token 生成,使用 `New()` 函数代替直接创建实例 - 优化 SetUserAuthority 中的错误处理流程,移除 else 代码块 - 移除多余的 err != nil 判断 --- server/api/v1/system/sys_user.go | 17 ++++++++--------- server/utils/claims.go | 10 ++++------ 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/server/api/v1/system/sys_user.go b/server/api/v1/system/sys_user.go index 117b8d0637..d228d217a4 100644 --- a/server/api/v1/system/sys_user.go +++ b/server/api/v1/system/sys_user.go @@ -1,18 +1,17 @@ package system import ( - "github.com/flipped-aurora/gin-vue-admin/server/model/common" "strconv" "time" "github.com/flipped-aurora/gin-vue-admin/server/global" + "github.com/flipped-aurora/gin-vue-admin/server/model/common" "github.com/flipped-aurora/gin-vue-admin/server/model/common/request" "github.com/flipped-aurora/gin-vue-admin/server/model/common/response" "github.com/flipped-aurora/gin-vue-admin/server/model/system" systemReq "github.com/flipped-aurora/gin-vue-admin/server/model/system/request" systemRes "github.com/flipped-aurora/gin-vue-admin/server/model/system/response" "github.com/flipped-aurora/gin-vue-admin/server/utils" - "github.com/gin-gonic/gin" "github.com/redis/go-redis/v9" "go.uber.org/zap" @@ -257,17 +256,17 @@ func (b *BaseApi) SetUserAuthority(c *gin.Context) { return } claims := utils.GetUserInfo(c) - j := &utils.JWT{SigningKey: []byte(global.GVA_CONFIG.JWT.SigningKey)} // 唯一签名 claims.AuthorityId = sua.AuthorityId - if token, err := j.CreateToken(*claims); err != nil { + token, err := utils.NewJWT().CreateToken(*claims) + if err != nil { global.GVA_LOG.Error("修改失败!", zap.Error(err)) response.FailWithMessage(err.Error(), c) - } else { - c.Header("new-token", token) - c.Header("new-expires-at", strconv.FormatInt(claims.ExpiresAt.Unix(), 10)) - utils.SetToken(c, token, int((claims.ExpiresAt.Unix()-time.Now().Unix())/60)) - response.OkWithMessage("修改成功", c) + return } + c.Header("new-token", token) + c.Header("new-expires-at", strconv.FormatInt(claims.ExpiresAt.Unix(), 10)) + utils.SetToken(c, token, int((claims.ExpiresAt.Unix()-time.Now().Unix())/60)) + response.OkWithMessage("修改成功", c) } // SetUserAuthorities diff --git a/server/utils/claims.go b/server/utils/claims.go index 69216700a0..d1738d7c3e 100644 --- a/server/utils/claims.go +++ b/server/utils/claims.go @@ -1,13 +1,14 @@ package utils import ( + "net" + "time" + "github.com/flipped-aurora/gin-vue-admin/server/global" "github.com/flipped-aurora/gin-vue-admin/server/model/system" systemReq "github.com/flipped-aurora/gin-vue-admin/server/model/system/request" "github.com/gin-gonic/gin" "github.com/gofrs/uuid/v5" - "net" - "time" ) func ClearToken(c *gin.Context) { @@ -134,7 +135,7 @@ func GetUserName(c *gin.Context) string { } func LoginToken(user system.Login) (token string, claims systemReq.CustomClaims, err error) { - j := &JWT{SigningKey: []byte(global.GVA_CONFIG.JWT.SigningKey)} // 唯一签名 + j := NewJWT() claims = j.CreateClaims(systemReq.BaseClaims{ UUID: user.GetUUID(), ID: user.GetUserId(), @@ -143,8 +144,5 @@ func LoginToken(user system.Login) (token string, claims systemReq.CustomClaims, AuthorityId: user.GetAuthorityId(), }) token, err = j.CreateToken(claims) - if err != nil { - return - } return } From 17d4b15951147d39690d0e5924d1b721d0112594 Mon Sep 17 00:00:00 2001 From: will0523 Date: Mon, 13 Jan 2025 21:26:58 +0800 Subject: [PATCH 06/21] =?UTF-8?q?[selectComponent.vue]=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E6=94=AF=E6=8C=81=E6=9F=A5=E7=9C=8B=E5=A4=A7?= =?UTF-8?q?=E5=9B=BE=20(#1982)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 上传组件支持查看大图 --- .../selectImage/selectComponent.vue | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/web/src/components/selectImage/selectComponent.vue b/web/src/components/selectImage/selectComponent.vue index 6124531d1d..29774049f1 100644 --- a/web/src/components/selectImage/selectComponent.vue +++ b/web/src/components/selectImage/selectComponent.vue @@ -18,21 +18,21 @@ - 图片
From 09c0c3f0800f2246898c7d106837d8e6444f8ceb Mon Sep 17 00:00:00 2001 From: task Date: Mon, 13 Jan 2025 21:32:17 +0800 Subject: [PATCH 07/21] =?UTF-8?q?=E5=B0=86=E5=8F=82=E6=95=B0=E7=BC=93?= =?UTF-8?q?=E5=AD=98=EF=BC=8C=E5=AA=92=E4=BD=93=E5=BA=93=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=88=86=E7=B1=BB=EF=BC=8C=E5=9B=BE=E5=BA=93=E5=A4=9A=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E6=97=B6=E4=BC=98=E5=8C=96=E3=80=82=20(#1978)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 媒体库增加分类,图库多选择时优化。 --- server/api/v1/example/enter.go | 2 + .../api/v1/example/exa_attachment_category.go | 83 +++ .../v1/example/exa_file_upload_download.go | 10 +- server/initialize/gorm.go | 1 + server/initialize/router.go | 35 +- .../model/example/exa_attachment_category.go | 16 + .../model/example/exa_file_upload_download.go | 9 +- .../request/exa_file_upload_and_downloads.go | 10 + server/router/example/enter.go | 2 + .../router/example/exa_attachment_category.go | 16 + server/service/example/enter.go | 1 + .../example/exa_attachment_category.go | 66 ++ .../example/exa_file_upload_download.go | 33 +- server/source/system/api.go | 3 + server/source/system/casbin.go | 3 + web/src/api/attachmentCategory.js | 26 + .../components/selectImage/selectImage.vue | 652 +++++++++++------- web/src/components/upload/common.vue | 11 +- web/src/components/upload/image.vue | 8 +- web/src/pinia/modules/params.js | 31 + web/src/utils/dictionary.js | 8 - web/src/utils/params.js | 14 + web/src/view/example/upload/upload.vue | 517 +++++++++----- web/src/view/superAdmin/params/sysParams.vue | 4 +- 24 files changed, 1087 insertions(+), 474 deletions(-) create mode 100644 server/api/v1/example/exa_attachment_category.go create mode 100644 server/model/example/exa_attachment_category.go create mode 100644 server/model/example/request/exa_file_upload_and_downloads.go create mode 100644 server/router/example/exa_attachment_category.go create mode 100644 server/service/example/exa_attachment_category.go create mode 100644 web/src/api/attachmentCategory.js create mode 100644 web/src/pinia/modules/params.js create mode 100644 web/src/utils/params.js diff --git a/server/api/v1/example/enter.go b/server/api/v1/example/enter.go index c182328ea1..68263dfe2e 100644 --- a/server/api/v1/example/enter.go +++ b/server/api/v1/example/enter.go @@ -5,9 +5,11 @@ import "github.com/flipped-aurora/gin-vue-admin/server/service" type ApiGroup struct { CustomerApi FileUploadAndDownloadApi + AttachmentCategoryApi } var ( customerService = service.ServiceGroupApp.ExampleServiceGroup.CustomerService fileUploadAndDownloadService = service.ServiceGroupApp.ExampleServiceGroup.FileUploadAndDownloadService + attachmentCategoryService = service.ServiceGroupApp.ExampleServiceGroup.AttachmentCategoryService ) diff --git a/server/api/v1/example/exa_attachment_category.go b/server/api/v1/example/exa_attachment_category.go new file mode 100644 index 0000000000..5cd22930bf --- /dev/null +++ b/server/api/v1/example/exa_attachment_category.go @@ -0,0 +1,83 @@ +package example + +import ( + "github.com/flipped-aurora/gin-vue-admin/server/global" + common "github.com/flipped-aurora/gin-vue-admin/server/model/common/request" + "github.com/flipped-aurora/gin-vue-admin/server/model/common/response" + "github.com/flipped-aurora/gin-vue-admin/server/model/example" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +type AttachmentCategoryApi struct{} + +// GetCategoryList +// @Tags GetCategoryList +// @Summary 媒体库分类列表 +// @Security AttachmentCategory +// @Produce application/json +// @Success 200 {object} response.Response{data=systemRes.SysAttachmentCategoryResponse,msg=string} "媒体库分类列表" +// @Router /attachmentCategory/getCategoryList [get] +func (a *AttachmentCategoryApi) GetCategoryList(c *gin.Context) { + res, err := attachmentCategoryService.GetCategoryList() + if err != nil { + global.GVA_LOG.Error("获取分类列表失败!", zap.Error(err)) + response.FailWithMessage("获取分类列表失败", c) + return + } + response.OkWithData(res, c) +} + +// AddCategory +// @Tags AddCategory +// @Summary 添加媒体库分类 +// @Security AttachmentCategory +// @accept application/json +// @Produce application/json +// @Param data body example.ExaAttachmentCategory true "" +// @Success 200 {object} response.Response{msg=string} "添加媒体库分类" +// @Router /attachmentCategory/addCategory [post] +func (a *AttachmentCategoryApi) AddCategory(c *gin.Context) { + var req example.ExaAttachmentCategory + if err := c.ShouldBindJSON(&req); err != nil { + global.GVA_LOG.Error("参数错误!", zap.Error(err)) + response.FailWithMessage("参数错误", c) + return + } + + if err := attachmentCategoryService.AddCategory(&req); err != nil { + global.GVA_LOG.Error("创建/更新失败!", zap.Error(err)) + response.FailWithMessage("创建/更新失败:"+err.Error(), c) + return + } + response.OkWithMessage("创建/更新成功", c) +} + +// DeleteCategory +// @Tags DeleteCategory +// @Summary 删除分类 +// @Security AttachmentCategory +// @accept application/json +// @Produce application/json +// @Param data body common.GetById true "分类id" +// @Success 200 {object} response.Response{msg=string} "删除分类" +// @Router /attachmentCategory/deleteCategory [post] +func (a *AttachmentCategoryApi) DeleteCategory(c *gin.Context) { + var req common.GetById + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage("参数错误", c) + return + } + + if req.ID == 0 { + response.FailWithMessage("参数错误", c) + return + } + + if err := attachmentCategoryService.DeleteCategory(&req.ID); err != nil { + response.FailWithMessage("删除失败", c) + return + } + + response.OkWithMessage("删除成功", c) +} diff --git a/server/api/v1/example/exa_file_upload_download.go b/server/api/v1/example/exa_file_upload_download.go index 6905936d7e..9497c6dd66 100644 --- a/server/api/v1/example/exa_file_upload_download.go +++ b/server/api/v1/example/exa_file_upload_download.go @@ -2,12 +2,13 @@ package example import ( "github.com/flipped-aurora/gin-vue-admin/server/global" - "github.com/flipped-aurora/gin-vue-admin/server/model/common/request" "github.com/flipped-aurora/gin-vue-admin/server/model/common/response" "github.com/flipped-aurora/gin-vue-admin/server/model/example" + "github.com/flipped-aurora/gin-vue-admin/server/model/example/request" exampleRes "github.com/flipped-aurora/gin-vue-admin/server/model/example/response" "github.com/gin-gonic/gin" "go.uber.org/zap" + "strconv" ) type FileUploadAndDownloadApi struct{} @@ -25,12 +26,13 @@ func (b *FileUploadAndDownloadApi) UploadFile(c *gin.Context) { var file example.ExaFileUploadAndDownload noSave := c.DefaultQuery("noSave", "0") _, header, err := c.Request.FormFile("file") + classId, _ := strconv.Atoi(c.DefaultPostForm("classId", "0")) if err != nil { global.GVA_LOG.Error("接收文件失败!", zap.Error(err)) response.FailWithMessage("接收文件失败", c) return } - file, err = fileUploadAndDownloadService.UploadFile(header, noSave) // 文件上传后拿到文件路径 + file, err = fileUploadAndDownloadService.UploadFile(header, noSave, classId) // 文件上传后拿到文件路径 if err != nil { global.GVA_LOG.Error("上传文件失败!", zap.Error(err)) response.FailWithMessage("上传文件失败", c) @@ -85,11 +87,11 @@ func (b *FileUploadAndDownloadApi) DeleteFile(c *gin.Context) { // @Security ApiKeyAuth // @accept application/json // @Produce application/json -// @Param data body request.PageInfo true "页码, 每页大小" +// @Param data body request.ExaAttachmentCategorySearch true "页码, 每页大小, 分类id" // @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页文件列表,返回包括列表,总数,页码,每页数量" // @Router /fileUploadAndDownload/getFileList [post] func (b *FileUploadAndDownloadApi) GetFileList(c *gin.Context) { - var pageInfo request.PageInfo + var pageInfo request.ExaAttachmentCategorySearch err := c.ShouldBindJSON(&pageInfo) if err != nil { response.FailWithMessage(err.Error(), c) diff --git a/server/initialize/gorm.go b/server/initialize/gorm.go index 398a823e4e..e5cdfa7c7b 100644 --- a/server/initialize/gorm.go +++ b/server/initialize/gorm.go @@ -61,6 +61,7 @@ func RegisterTables() { example.ExaCustomer{}, example.ExaFileChunk{}, example.ExaFileUploadAndDownload{}, + example.ExaAttachmentCategory{}, ) if err != nil { global.GVA_LOG.Error("register table failed", zap.Error(err)) diff --git a/server/initialize/router.go b/server/initialize/router.go index 499f87d8b4..b3f1786d78 100644 --- a/server/initialize/router.go +++ b/server/initialize/router.go @@ -77,23 +77,24 @@ func Routers() *gin.Engine { } { - systemRouter.InitApiRouter(PrivateGroup, PublicGroup) // 注册功能api路由 - systemRouter.InitJwtRouter(PrivateGroup) // jwt相关路由 - systemRouter.InitUserRouter(PrivateGroup) // 注册用户路由 - systemRouter.InitMenuRouter(PrivateGroup) // 注册menu路由 - systemRouter.InitSystemRouter(PrivateGroup) // system相关路由 - systemRouter.InitCasbinRouter(PrivateGroup) // 权限相关路由 - systemRouter.InitAutoCodeRouter(PrivateGroup, PublicGroup) // 创建自动化代码 - systemRouter.InitAuthorityRouter(PrivateGroup) // 注册角色路由 - systemRouter.InitSysDictionaryRouter(PrivateGroup) // 字典管理 - systemRouter.InitAutoCodeHistoryRouter(PrivateGroup) // 自动化代码历史 - systemRouter.InitSysOperationRecordRouter(PrivateGroup) // 操作记录 - systemRouter.InitSysDictionaryDetailRouter(PrivateGroup) // 字典详情管理 - systemRouter.InitAuthorityBtnRouterRouter(PrivateGroup) // 按钮权限管理 - systemRouter.InitSysExportTemplateRouter(PrivateGroup) // 导出模板 - systemRouter.InitSysParamsRouter(PrivateGroup, PublicGroup) // 参数管理 - exampleRouter.InitCustomerRouter(PrivateGroup) // 客户路由 - exampleRouter.InitFileUploadAndDownloadRouter(PrivateGroup) // 文件上传下载功能路由 + systemRouter.InitApiRouter(PrivateGroup, PublicGroup) // 注册功能api路由 + systemRouter.InitJwtRouter(PrivateGroup) // jwt相关路由 + systemRouter.InitUserRouter(PrivateGroup) // 注册用户路由 + systemRouter.InitMenuRouter(PrivateGroup) // 注册menu路由 + systemRouter.InitSystemRouter(PrivateGroup) // system相关路由 + systemRouter.InitCasbinRouter(PrivateGroup) // 权限相关路由 + systemRouter.InitAutoCodeRouter(PrivateGroup, PublicGroup) // 创建自动化代码 + systemRouter.InitAuthorityRouter(PrivateGroup) // 注册角色路由 + systemRouter.InitSysDictionaryRouter(PrivateGroup) // 字典管理 + systemRouter.InitAutoCodeHistoryRouter(PrivateGroup) // 自动化代码历史 + systemRouter.InitSysOperationRecordRouter(PrivateGroup) // 操作记录 + systemRouter.InitSysDictionaryDetailRouter(PrivateGroup) // 字典详情管理 + systemRouter.InitAuthorityBtnRouterRouter(PrivateGroup) // 按钮权限管理 + systemRouter.InitSysExportTemplateRouter(PrivateGroup) // 导出模板 + systemRouter.InitSysParamsRouter(PrivateGroup, PublicGroup) // 参数管理 + exampleRouter.InitCustomerRouter(PrivateGroup) // 客户路由 + exampleRouter.InitFileUploadAndDownloadRouter(PrivateGroup) // 文件上传下载功能路由 + exampleRouter.InitAttachmentCategoryRouterRouter(PrivateGroup) // 文件上传下载分类 } diff --git a/server/model/example/exa_attachment_category.go b/server/model/example/exa_attachment_category.go new file mode 100644 index 0000000000..a643b61096 --- /dev/null +++ b/server/model/example/exa_attachment_category.go @@ -0,0 +1,16 @@ +package example + +import ( + "github.com/flipped-aurora/gin-vue-admin/server/global" +) + +type ExaAttachmentCategory struct { + global.GVA_MODEL + Name string `json:"name" form:"name" gorm:"default:null;type:varchar(255);column:name;comment:分类名称;"` + Pid uint `json:"pid" form:"pid" gorm:"default:0;type:int;column:pid;comment:父节点ID;"` + Children []*ExaAttachmentCategory `json:"children" gorm:"-"` +} + +func (ExaAttachmentCategory) TableName() string { + return "exa_attachment_category" +} diff --git a/server/model/example/exa_file_upload_download.go b/server/model/example/exa_file_upload_download.go index bf4c7df7e1..4fe342dc81 100644 --- a/server/model/example/exa_file_upload_download.go +++ b/server/model/example/exa_file_upload_download.go @@ -6,10 +6,11 @@ import ( type ExaFileUploadAndDownload struct { global.GVA_MODEL - Name string `json:"name" gorm:"comment:文件名"` // 文件名 - Url string `json:"url" gorm:"comment:文件地址"` // 文件地址 - Tag string `json:"tag" gorm:"comment:文件标签"` // 文件标签 - Key string `json:"key" gorm:"comment:编号"` // 编号 + Name string `json:"name" form:"name" gorm:"column:name;comment:文件名"` // 文件名 + ClassId int `json:"classId" form:"classId" gorm:"default:0;type:int;column:class_id;comment:分类id;"` // 分类id + Url string `json:"url" form:"url" gorm:"column:url;comment:文件地址"` // 文件地址 + Tag string `json:"tag" form:"tag" gorm:"column:tag;comment:文件标签"` // 文件标签 + Key string `json:"key" form:"key" gorm:"column:key;comment:编号"` // 编号 } func (ExaFileUploadAndDownload) TableName() string { diff --git a/server/model/example/request/exa_file_upload_and_downloads.go b/server/model/example/request/exa_file_upload_and_downloads.go new file mode 100644 index 0000000000..b9ff68403b --- /dev/null +++ b/server/model/example/request/exa_file_upload_and_downloads.go @@ -0,0 +1,10 @@ +package request + +import ( + "github.com/flipped-aurora/gin-vue-admin/server/model/common/request" +) + +type ExaAttachmentCategorySearch struct { + ClassId int `json:"classId" form:"classId"` + request.PageInfo +} diff --git a/server/router/example/enter.go b/server/router/example/enter.go index ce87aa2f98..bf17697023 100644 --- a/server/router/example/enter.go +++ b/server/router/example/enter.go @@ -7,9 +7,11 @@ import ( type RouterGroup struct { CustomerRouter FileUploadAndDownloadRouter + AttachmentCategoryRouter } var ( exaCustomerApi = api.ApiGroupApp.ExampleApiGroup.CustomerApi exaFileUploadAndDownloadApi = api.ApiGroupApp.ExampleApiGroup.FileUploadAndDownloadApi + attachmentCategoryApi = api.ApiGroupApp.ExampleApiGroup.AttachmentCategoryApi ) diff --git a/server/router/example/exa_attachment_category.go b/server/router/example/exa_attachment_category.go new file mode 100644 index 0000000000..4900292cc4 --- /dev/null +++ b/server/router/example/exa_attachment_category.go @@ -0,0 +1,16 @@ +package example + +import ( + "github.com/gin-gonic/gin" +) + +type AttachmentCategoryRouter struct{} + +func (r *AttachmentCategoryRouter) InitAttachmentCategoryRouterRouter(Router *gin.RouterGroup) { + router := Router.Group("attachmentCategory") + { + router.GET("getCategoryList", attachmentCategoryApi.GetCategoryList) // 分类列表 + router.POST("addCategory", attachmentCategoryApi.AddCategory) // 添加/编辑分类 + router.POST("deleteCategory", attachmentCategoryApi.DeleteCategory) // 删除分类 + } +} diff --git a/server/service/example/enter.go b/server/service/example/enter.go index c5a7ddaa25..f7198da452 100644 --- a/server/service/example/enter.go +++ b/server/service/example/enter.go @@ -3,4 +3,5 @@ package example type ServiceGroup struct { CustomerService FileUploadAndDownloadService + AttachmentCategoryService } diff --git a/server/service/example/exa_attachment_category.go b/server/service/example/exa_attachment_category.go new file mode 100644 index 0000000000..1e74fc9425 --- /dev/null +++ b/server/service/example/exa_attachment_category.go @@ -0,0 +1,66 @@ +package example + +import ( + "errors" + "github.com/flipped-aurora/gin-vue-admin/server/global" + "github.com/flipped-aurora/gin-vue-admin/server/model/example" + "gorm.io/gorm" +) + +type AttachmentCategoryService struct{} + +// AddCategory 创建/更新的分类 +func (a *AttachmentCategoryService) AddCategory(req *example.ExaAttachmentCategory) (err error) { + // 检查是否已存在相同名称的分类 + if (!errors.Is(global.GVA_DB.Take(&example.ExaAttachmentCategory{}, "name = ? and pid = ?", req.Name, req.Pid).Error, gorm.ErrRecordNotFound)) { + return errors.New("分类名称已存在") + } + if req.ID > 0 { + if err = global.GVA_DB.Model(&example.ExaAttachmentCategory{}).Where("id = ?", req.ID).Updates(&example.ExaAttachmentCategory{ + Name: req.Name, + Pid: req.Pid, + }).Error; err != nil { + return err + } + } else { + if err = global.GVA_DB.Create(&example.ExaAttachmentCategory{ + Name: req.Name, + Pid: req.Pid, + }).Error; err != nil { + return err + } + } + return nil +} + +// DeleteCategory 删除分类 +func (a *AttachmentCategoryService) DeleteCategory(id *int) error { + var childCount int64 + global.GVA_DB.Model(&example.ExaAttachmentCategory{}).Where("pid = ?", id).Count(&childCount) + if childCount > 0 { + return errors.New("请先删除子级") + } + return global.GVA_DB.Where("id = ?", id).Unscoped().Delete(&example.ExaAttachmentCategory{}).Error +} + +// GetCategoryList 分类列表 +func (a *AttachmentCategoryService) GetCategoryList() (res []*example.ExaAttachmentCategory, err error) { + var fileLists []example.ExaAttachmentCategory + err = global.GVA_DB.Model(&example.ExaAttachmentCategory{}).Find(&fileLists).Error + if err != nil { + return res, err + } + return a.getChildrenList(fileLists, 0), nil +} + +// getChildrenList 子类 +func (a *AttachmentCategoryService) getChildrenList(categories []example.ExaAttachmentCategory, parentID uint) []*example.ExaAttachmentCategory { + var tree []*example.ExaAttachmentCategory + for _, category := range categories { + if category.Pid == parentID { + category.Children = a.getChildrenList(categories, category.ID) + tree = append(tree, &category) + } + } + return tree +} diff --git a/server/service/example/exa_file_upload_download.go b/server/service/example/exa_file_upload_download.go index cca3ec5160..c5519fc084 100644 --- a/server/service/example/exa_file_upload_download.go +++ b/server/service/example/exa_file_upload_download.go @@ -6,8 +6,8 @@ import ( "strings" "github.com/flipped-aurora/gin-vue-admin/server/global" - "github.com/flipped-aurora/gin-vue-admin/server/model/common/request" "github.com/flipped-aurora/gin-vue-admin/server/model/example" + "github.com/flipped-aurora/gin-vue-admin/server/model/example/request" "github.com/flipped-aurora/gin-vue-admin/server/utils/upload" ) @@ -62,24 +62,28 @@ func (e *FileUploadAndDownloadService) EditFileName(file example.ExaFileUploadAn //@author: [piexlmax](https://github.com/piexlmax) //@function: GetFileRecordInfoList //@description: 分页获取数据 -//@param: info request.PageInfo +//@param: info request.ExaAttachmentCategorySearch //@return: list interface{}, total int64, err error -func (e *FileUploadAndDownloadService) GetFileRecordInfoList(info request.PageInfo) (list interface{}, total int64, err error) { +func (e *FileUploadAndDownloadService) GetFileRecordInfoList(info request.ExaAttachmentCategorySearch) (list []example.ExaFileUploadAndDownload, total int64, err error) { limit := info.PageSize offset := info.PageSize * (info.Page - 1) - keyword := info.Keyword db := global.GVA_DB.Model(&example.ExaFileUploadAndDownload{}) - var fileLists []example.ExaFileUploadAndDownload - if len(keyword) > 0 { - db = db.Where("name LIKE ?", "%"+keyword+"%") + + if len(info.Keyword) > 0 { + db = db.Where("name LIKE ?", "%"+info.Keyword+"%") + } + + if info.ClassId > 0 { + db = db.Where("class_id = ?", info.ClassId) } + err = db.Count(&total).Error if err != nil { return } - err = db.Limit(limit).Offset(offset).Order("updated_at desc").Find(&fileLists).Error - return fileLists, total, err + err = db.Limit(limit).Offset(offset).Order("id desc").Find(&list).Error + return list, total, err } //@author: [piexlmax](https://github.com/piexlmax) @@ -88,7 +92,7 @@ func (e *FileUploadAndDownloadService) GetFileRecordInfoList(info request.PageIn //@param: header *multipart.FileHeader, noSave string //@return: file model.ExaFileUploadAndDownload, err error -func (e *FileUploadAndDownloadService) UploadFile(header *multipart.FileHeader, noSave string) (file example.ExaFileUploadAndDownload, err error) { +func (e *FileUploadAndDownloadService) UploadFile(header *multipart.FileHeader, noSave string, classId int) (file example.ExaFileUploadAndDownload, err error) { oss := upload.NewOss() filePath, key, uploadErr := oss.UploadFile(header) if uploadErr != nil { @@ -96,10 +100,11 @@ func (e *FileUploadAndDownloadService) UploadFile(header *multipart.FileHeader, } s := strings.Split(header.Filename, ".") f := example.ExaFileUploadAndDownload{ - Url: filePath, - Name: header.Filename, - Tag: s[len(s)-1], - Key: key, + Url: filePath, + Name: header.Filename, + ClassId: classId, + Tag: s[len(s)-1], + Key: key, } if noSave == "0" { return f, e.Upload(f) diff --git a/server/source/system/api.go b/server/source/system/api.go index e657f34f5c..a623bd73e1 100644 --- a/server/source/system/api.go +++ b/server/source/system/api.go @@ -182,6 +182,9 @@ func (i *initApi) InitializeData(ctx context.Context) (context.Context, error) { {ApiGroup: "参数管理", Method: "GET", Path: "/sysParams/findSysParams", Description: "根据ID获取参数"}, {ApiGroup: "参数管理", Method: "GET", Path: "/sysParams/getSysParamsList", Description: "获取参数列表"}, {ApiGroup: "参数管理", Method: "GET", Path: "/sysParams/getSysParam", Description: "获取参数列表"}, + {ApiGroup: "媒体库分类", Method: "GET", Path: "/attachmentCategory/getCategoryList", Description: "分类列表"}, + {ApiGroup: "媒体库分类", Method: "GET", Path: "/attachmentCategory/addCategory", Description: "添加/编辑分类"}, + {ApiGroup: "媒体库分类", Method: "GET", Path: "/attachmentCategory/deleteCategory", Description: "删除分类"}, } if err := db.Create(&entities).Error; err != nil { return ctx, errors.Wrap(err, sysModel.SysApi{}.TableName()+"表数据初始化失败!") diff --git a/server/source/system/casbin.go b/server/source/system/casbin.go index c4528336df..eda525d100 100644 --- a/server/source/system/casbin.go +++ b/server/source/system/casbin.go @@ -185,6 +185,9 @@ func (i *initCasbin) InitializeData(ctx context.Context) (context.Context, error {Ptype: "p", V0: "888", V1: "/sysParams/findSysParams", V2: "GET"}, {Ptype: "p", V0: "888", V1: "/sysParams/getSysParamsList", V2: "GET"}, {Ptype: "p", V0: "888", V1: "/sysParams/getSysParam", V2: "GET"}, + {Ptype: "p", V0: "888", V1: "/attachmentCategory/getCategoryList", V2: "GET"}, + {Ptype: "p", V0: "888", V1: "/attachmentCategory/addCategory", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/attachmentCategory/deleteCategory", V2: "POST"}, {Ptype: "p", V0: "8881", V1: "/user/admin_register", V2: "POST"}, {Ptype: "p", V0: "8881", V1: "/api/createApi", V2: "POST"}, diff --git a/web/src/api/attachmentCategory.js b/web/src/api/attachmentCategory.js new file mode 100644 index 0000000000..58980f622b --- /dev/null +++ b/web/src/api/attachmentCategory.js @@ -0,0 +1,26 @@ +import service from '@/utils/request' +// 分类列表 +export const getCategoryList = () => { + return service({ + url: '/attachmentCategory/getCategoryList', + method: 'get', + }) +} + +// 添加/编辑分类 +export const addCategory = (data) => { + return service({ + url: '/attachmentCategory/addCategory', + method: 'post', + data + }) +} + +// 删除分类 +export const deleteCategory = (data) => { + return service({ + url: '/attachmentCategory/deleteCategory', + method: 'post', + data + }) +} diff --git a/web/src/components/selectImage/selectImage.vue b/web/src/components/selectImage/selectImage.vue index d8960254fb..601686a810 100644 --- a/web/src/components/selectImage/selectImage.vue +++ b/web/src/components/selectImage/selectImage.vue @@ -1,290 +1,448 @@ @@ -19,11 +20,19 @@ import { ElMessage } from 'element-plus' import { isVideoMime, isImageMime } from '@/utils/image' import { getBaseUrl } from '@/utils/format' + import {Upload} from "@element-plus/icons-vue"; defineOptions({ name: 'UploadCommon' }) + const props = defineProps({ + classId: { + type: Number, + default: 0 + } + }) + const emit = defineEmits(['on-success']) const fullscreenLoading = ref(false) diff --git a/web/src/components/upload/image.vue b/web/src/components/upload/image.vue index 7423cc7def..60d226f492 100644 --- a/web/src/components/upload/image.vue +++ b/web/src/components/upload/image.vue @@ -6,8 +6,9 @@ :on-success="handleImageSuccess" :before-upload="beforeImageUpload" :multiple="false" + :data="{'classId': props.classId}" > - 压缩上传 + 压缩上传
@@ -16,6 +17,7 @@ import ImageCompress from '@/utils/image' import { ElMessage } from 'element-plus' import { getBaseUrl } from '@/utils/format' + import {Upload} from "@element-plus/icons-vue"; defineOptions({ name: 'UploadImage' @@ -34,6 +36,10 @@ maxWH: { type: Number, default: 1920 // 图片长宽上限 + }, + classId: { + type: Number, + default: 0 } }) diff --git a/web/src/pinia/modules/params.js b/web/src/pinia/modules/params.js new file mode 100644 index 0000000000..54cdbf97a8 --- /dev/null +++ b/web/src/pinia/modules/params.js @@ -0,0 +1,31 @@ +import { getSysParam } from '@/api/sysParams' +import { defineStore } from 'pinia' +import { ref } from 'vue' + +export const useParamsStore = defineStore('params', () => { + const paramsMap = ref({}) + + const setParamsMap = (paramsRes) => { + paramsMap.value = { ...paramsMap.value, ...paramsRes } + } + + const getParams = async(key) => { + if (paramsMap.value[key] && paramsMap.value[key].length) { + return paramsMap.value[key] + } else { + const res = await getSysParam({ key }) + if (res.code === 0) { + const paramsRes = {} + paramsRes[key] = res.data.value + setParamsMap(paramsRes) + return paramsMap.value[key] + } + } + } + + return { + paramsMap, + setParamsMap, + getParams + } +}) diff --git a/web/src/utils/dictionary.js b/web/src/utils/dictionary.js index 679a6b9886..89ec656e43 100644 --- a/web/src/utils/dictionary.js +++ b/web/src/utils/dictionary.js @@ -1,5 +1,4 @@ import { useDictionaryStore } from '@/pinia/modules/dictionary' -import { getSysParam } from '@/api/sysParams' // 获取字典方法 使用示例 getDict('sex').then(res) 或者 async函数下 const res = await getDict('sex') export const getDict = async (type) => { const dictionaryStore = useDictionaryStore() @@ -25,10 +24,3 @@ export const showDictLabel = ( }) return Reflect.has(dictMap, code) ? dictMap[code] : '' } - -export const getParams = async (key) => { - const res = await getSysParam({ key }) - if (res.code === 0) { - return res.data.value - } -} diff --git a/web/src/utils/params.js b/web/src/utils/params.js new file mode 100644 index 0000000000..b03d539a39 --- /dev/null +++ b/web/src/utils/params.js @@ -0,0 +1,14 @@ +import { useParamsStore } from '@/pinia/modules/params' +/* + * 获取参数方法 使用示例 getParams('key').then(res) 或者 async函数下 const res = await getParams('key') + * const res = ref('') + * const fun = async () => { + * res.value = await getParams('test') + * } + * fun() + */ +export const getParams = async(key) => { + const paramsStore = useParamsStore() + await paramsStore.getParams(key) + return paramsStore.paramsMap[key] +} diff --git a/web/src/view/example/upload/upload.vue b/web/src/view/example/upload/upload.vue index 42a4c9161d..8836d5d191 100644 --- a/web/src/view/example/upload/upload.vue +++ b/web/src/view/example/upload/upload.vue @@ -1,161 +1,233 @@ + if (res.code === 0) { + categories.value = res.data + categories.value.unshift(data) + } +} + +const handleNodeClick = (node) => { + search.value.keyword = null + search.value.classId = node.ID + page.value = 1 + getTableData() +} + +const categoryDialogVisible = ref(false) +const categoryFormData = ref({ + ID: 0, + pid: 0, + name: '' +}) + +const categoryForm = ref(null) +const rules = ref({ + name: [ + {required: true, message: '请输入分类名称', trigger: 'blur'}, + {max: 20, message: '最多20位字符', trigger: 'blur'} + ] +}) - +} + +fetchCategories() + diff --git a/web/src/view/superAdmin/params/sysParams.vue b/web/src/view/superAdmin/params/sysParams.vue index 1feeda1e68..89fe6f95e4 100644 --- a/web/src/view/superAdmin/params/sysParams.vue +++ b/web/src/view/superAdmin/params/sysParams.vue @@ -1,5 +1,6 @@ +} +.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); +} + \ No newline at end of file From 1e727d24c9afd6655d6d9730ac8365cf4836466b Mon Sep 17 00:00:00 2001 From: "Feng.YJ" <32027253+huiyifyj@users.noreply.github.com> Date: Fri, 17 Jan 2025 11:13:35 +0800 Subject: [PATCH 09/21] refactor: rename error with `Err` prefix to follow Go conventions (#1987) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Replace `strings.Index` with `strings.Contains` for better readability Replace `strings.Index(str, substr) > -1` with `strings.Contains(str, substr)` 使用 strings.Contains 替代 strings.Index 方法 * refactor: rename error with `Err` prefix to follow Go conventions - Rename JWT error variables with `Err` prefix following Go conventions. - Improve error messages to be more consistent and lowercase, more to see https://go.dev/wiki/CodeReviewComments#error-strings - Update JWT error references in middleware This change improves code readability and follows better Go naming conventions for error variables. --- server/middleware/jwt.go | 9 ++++----- server/utils/breakpoint_continue.go | 2 +- server/utils/jwt.go | 23 +++++++++++------------ server/utils/zip.go | 2 +- 4 files changed, 17 insertions(+), 19 deletions(-) diff --git a/server/middleware/jwt.go b/server/middleware/jwt.go index 38b56dcf33..65b8db2d6a 100644 --- a/server/middleware/jwt.go +++ b/server/middleware/jwt.go @@ -2,16 +2,15 @@ package middleware import ( "errors" - "github.com/flipped-aurora/gin-vue-admin/server/global" - "github.com/flipped-aurora/gin-vue-admin/server/utils" - "github.com/golang-jwt/jwt/v4" "strconv" "time" + "github.com/flipped-aurora/gin-vue-admin/server/global" "github.com/flipped-aurora/gin-vue-admin/server/model/common/response" "github.com/flipped-aurora/gin-vue-admin/server/service" - + "github.com/flipped-aurora/gin-vue-admin/server/utils" "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v4" ) var jwtService = service.ServiceGroupApp.SystemServiceGroup.JwtService @@ -35,7 +34,7 @@ func JWTAuth() gin.HandlerFunc { // parseToken 解析token包含的信息 claims, err := j.ParseToken(token) if err != nil { - if errors.Is(err, utils.TokenExpired) { + if errors.Is(err, utils.ErrTokenExpired) { response.NoAuth("授权已过期", c) utils.ClearToken(c) c.Abort() diff --git a/server/utils/breakpoint_continue.go b/server/utils/breakpoint_continue.go index c0baee57c1..bce6686780 100644 --- a/server/utils/breakpoint_continue.go +++ b/server/utils/breakpoint_continue.go @@ -55,7 +55,7 @@ func CheckMd5(content []byte, chunkMd5 string) (CanUpload bool) { //@return: string, error func makeFileContent(content []byte, fileName string, FileDir string, contentNumber int) (string, error) { - if strings.Index(fileName, "..") > -1 || strings.Index(FileDir, "..") > -1 { + if strings.Contains(fileName, "..") || strings.Contains(FileDir, "..") { return "", errors.New("文件名或路径不合法") } path := FileDir + fileName + "_" + strconv.Itoa(contentNumber) diff --git a/server/utils/jwt.go b/server/utils/jwt.go index e8eb9721dc..f518e2be45 100644 --- a/server/utils/jwt.go +++ b/server/utils/jwt.go @@ -4,10 +4,9 @@ import ( "errors" "time" - jwt "github.com/golang-jwt/jwt/v4" - "github.com/flipped-aurora/gin-vue-admin/server/global" "github.com/flipped-aurora/gin-vue-admin/server/model/system/request" + "github.com/golang-jwt/jwt/v4" ) type JWT struct { @@ -15,10 +14,10 @@ type JWT struct { } var ( - TokenExpired = errors.New("Token is expired") - TokenNotValidYet = errors.New("Token not active yet") - TokenMalformed = errors.New("That's not even a token") - TokenInvalid = errors.New("Couldn't handle this token:") + ErrTokenExpired = errors.New("token is expired") + ErrTokenNotValidYet = errors.New("token not active yet") + ErrTokenMalformed = errors.New("that's not even a token") + ErrTokenInvalid = errors.New("couldn't handle this token") ) func NewJWT() *JWT { @@ -65,14 +64,14 @@ func (j *JWT) ParseToken(tokenString string) (*request.CustomClaims, error) { if err != nil { if ve, ok := err.(*jwt.ValidationError); ok { if ve.Errors&jwt.ValidationErrorMalformed != 0 { - return nil, TokenMalformed + return nil, ErrTokenMalformed } else if ve.Errors&jwt.ValidationErrorExpired != 0 { // Token is expired - return nil, TokenExpired + return nil, ErrTokenExpired } else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 { - return nil, TokenNotValidYet + return nil, ErrTokenNotValidYet } else { - return nil, TokenInvalid + return nil, ErrTokenInvalid } } } @@ -80,9 +79,9 @@ func (j *JWT) ParseToken(tokenString string) (*request.CustomClaims, error) { if claims, ok := token.Claims.(*request.CustomClaims); ok && token.Valid { return claims, nil } - return nil, TokenInvalid + return nil, ErrTokenInvalid } else { - return nil, TokenInvalid + return nil, ErrTokenInvalid } } diff --git a/server/utils/zip.go b/server/utils/zip.go index bee0a0bf43..ef35d105a1 100644 --- a/server/utils/zip.go +++ b/server/utils/zip.go @@ -19,7 +19,7 @@ func Unzip(zipFile string, destDir string) ([]string, error) { defer zipReader.Close() for _, f := range zipReader.File { - if strings.Index(f.Name, "..") > -1 { + if strings.Contains(f.Name, "..") { return []string{}, fmt.Errorf("%s 文件名不合法", f.Name) } fpath := filepath.Join(destDir, f.Name) From ea7fa5cd6562683c7fea4844d1a47eb1e46f6580 Mon Sep 17 00:00:00 2001 From: bypanghu Date: Mon, 20 Jan 2025 13:26:24 +0800 Subject: [PATCH 10/21] =?UTF-8?q?fix:=E6=B7=BB=E5=8A=A0=E5=86=85=E9=83=A8?= =?UTF-8?q?=20iframe=20=E5=B1=95=E7=A4=BA=E7=BD=91=E9=A1=B5=EF=BC=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=20permission=20=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/commandMenu/index.vue | 5 +- web/src/pathInfo.json | 1 + web/src/permission.js | 192 +++++++++--------- web/src/router/index.js | 5 + web/src/view/dashboard/index.vue | 8 +- web/src/view/layout/aside/combinationMode.vue | 11 +- web/src/view/layout/aside/headMode.vue | 11 +- web/src/view/layout/aside/normalMode.vue | 13 +- web/src/view/layout/iframe.vue | 107 ++++++++++ web/src/view/layout/index.vue | 3 +- web/src/view/person/person.vue | 77 ++++--- 11 files changed, 301 insertions(+), 132 deletions(-) create mode 100644 web/src/view/layout/iframe.vue diff --git a/web/src/components/commandMenu/index.vue b/web/src/components/commandMenu/index.vue index eb403d4a1e..27ef93fc48 100644 --- a/web/src/components/commandMenu/index.vue +++ b/web/src/components/commandMenu/index.vue @@ -54,7 +54,8 @@ const options = reactive([]) const deepMenus = (menus) => { const arr = [] - menus.forEach((menu) => { + menus?.forEach((menu) => { + if (!menu?.children) return if (menu.children && menu.children.length > 0) { arr.push(...deepMenus(menu.children)) } else { @@ -77,7 +78,7 @@ label: '跳转', children: [] } - const menus = deepMenus(routerStore.asyncRouters[0].children) + const menus = deepMenus(routerStore.asyncRouters[0]?.children || []) option.children.push(...menus) options.push(option) } diff --git a/web/src/pathInfo.json b/web/src/pathInfo.json index e08798c046..bf0f039458 100644 --- a/web/src/pathInfo.json +++ b/web/src/pathInfo.json @@ -27,6 +27,7 @@ "/src/view/layout/aside/normalMode.vue": "GvaAside", "/src/view/layout/header/index.vue": "Index", "/src/view/layout/header/tools.vue": "Tools", + "/src/view/layout/iframe.vue": "GvaLayoutIframe", "/src/view/layout/index.vue": "GvaLayout", "/src/view/layout/screenfull/index.vue": "Screenfull", "/src/view/layout/search/search.vue": "BtnBox", diff --git a/web/src/permission.js b/web/src/permission.js index 70c21bee89..b61bf651e2 100644 --- a/web/src/permission.js +++ b/web/src/permission.js @@ -4,134 +4,138 @@ import getPageTitle from '@/utils/page' import router from '@/router' import Nprogress from 'nprogress' import 'nprogress/nprogress.css' -Nprogress.configure({ showSpinner: false, ease: 'ease', speed: 500 }) -const whiteList = ['Login', 'Init'] +// 配置 NProgress +Nprogress.configure({ + showSpinner: false, + ease: 'ease', + speed: 500 +}) -const getRouter = async (userStore) => { - const routerStore = useRouterStore() - await routerStore.SetAsyncRouter() - await userStore.GetUserInfo() - const asyncRouters = routerStore.asyncRouters - asyncRouters.forEach((asyncRouter) => { - router.addRoute(asyncRouter) - }) +// 白名单路由 +const WHITE_LIST = ['Login', 'Init', 'Iframe'] + +// 处理路由加载 +const setupRouter = async (userStore) => { + try { + const routerStore = useRouterStore() + await Promise.all([routerStore.SetAsyncRouter(), userStore.GetUserInfo()]) + + routerStore.asyncRouters.forEach((route) => router.addRoute(route)) + return true + } catch (error) { + console.error('Setup router failed:', error) + return false + } } +// 移除加载动画 const removeLoading = () => { const element = document.getElementById('gva-loading-box') - if (element) { - element.remove() - } + element?.remove() } -async function handleKeepAlive(to) { - if (to.matched.some((item) => item.meta.keepAlive)) { - if (to.matched && to.matched.length > 2) { - for (let i = 1; i < to.matched.length; i++) { - const element = to.matched[i - 1] - if (element.name === 'layout') { - to.matched.splice(i, 1) - await handleKeepAlive(to) - } - // 如果没有按需加载完成则等待加载 - if (typeof element.components.default === 'function') { - await element.components.default() - await handleKeepAlive(to) - } +// 处理组件缓存 +const handleKeepAlive = async (to) => { + if (!to.matched.some((item) => item.meta.keepAlive)) return + + if (to.matched?.length > 2) { + for (let i = 1; i < to.matched.length; i++) { + const element = to.matched[i - 1] + + if (element.name === 'layout') { + to.matched.splice(i, 1) + await handleKeepAlive(to) + continue + } + + if (typeof element.components.default === 'function') { + await element.components.default() + await handleKeepAlive(to) } } } } +// 处理路由重定向 +const handleRedirect = (to, userStore) => { + if (router.hasRoute(userStore.userInfo.authority.defaultRouter)) { + return { ...to, replace: true } + } + return { path: '/layout/404' } +} + +// 路由守卫 router.beforeEach(async (to, from) => { + const userStore = useUserStore() const routerStore = useRouterStore() + const token = userStore.token + Nprogress.start() - const userStore = useUserStore() + + // 处理元数据和缓存 to.meta.matched = [...to.matched] - handleKeepAlive(to) - const token = userStore.token - // 在白名单中的判断情况 + await handleKeepAlive(to) + + // 设置页面标题 document.title = getPageTitle(to.meta.title, to) - if (to.meta.client) { + // 白名单路由处理 + if (WHITE_LIST.includes(to.name)) { + if ( + token && + !routerStore.asyncRouterFlag && + !WHITE_LIST.includes(from.name) + ) { + await setupRouter(userStore) + } return true } - if (whiteList.indexOf(to.name) > -1) { - if (token) { - if (!routerStore.asyncRouterFlag && whiteList.indexOf(from.name) < 0) { - await getRouter(userStore) - } - // token 可以解析但是却是不存在的用户 id 或角色 id 会导致无限调用 - if (userStore.userInfo?.authority?.defaultRouter != null) { - if (router.hasRoute(userStore.userInfo.authority.defaultRouter)) { - return { name: userStore.userInfo.authority.defaultRouter } - } else { - return { path: '/layout/404' } - } - } else { - // 强制退出账号 - userStore.ClearStorage() - return { - name: 'Login', - query: { - redirect: document.location.hash - } - } - } - } else { - return true + + // 需要登录的路由处理 + if (token) { + // 处理需要跳转到首页的情况 + if (sessionStorage.getItem('needToHome') === 'true') { + sessionStorage.removeItem('needToHome') + return { path: '/' } } - } else { - // 不在白名单中并且已经登录的时候 - if (token) { - if (sessionStorage.getItem('needToHome') === 'true') { - sessionStorage.removeItem('needToHome') - return { path: '/' } - } - // 添加flag防止多次获取动态路由和栈溢出 - if (!routerStore.asyncRouterFlag && whiteList.indexOf(from.name) < 0) { - await getRouter(userStore) - if (userStore.token) { - if (router.hasRoute(userStore.userInfo.authority.defaultRouter)) { - return { ...to, replace: true } - } else { - return { path: '/layout/404' } - } - } else { - return { - name: 'Login', - query: { redirect: to.href } - } - } - } else { - if (to.matched.length) { - return true - } else { - return { path: '/layout/404' } - } + + // 处理异步路由 + if (!routerStore.asyncRouterFlag && !WHITE_LIST.includes(from.name)) { + const setupSuccess = await setupRouter(userStore) + + if (setupSuccess && userStore.token) { + return handleRedirect(to, userStore) } - } - // 不在白名单中并且未登录的时候 - if (!token) { + return { name: 'Login', - query: { - redirect: document.location.hash - } + query: { redirect: to.href } } } + + return to.matched.length ? true : { path: '/layout/404' } + } + + // 未登录跳转登录页 + return { + name: 'Login', + query: { + redirect: document.location.hash + } } }) +// 路由加载完成 router.afterEach(() => { - // 路由加载完成后关闭进度条 - document.getElementsByClassName('main-cont main-right')[0]?.scrollTo(0, 0) + document.querySelector('.main-cont.main-right')?.scrollTo(0, 0) Nprogress.done() }) -router.onError(() => { - // 路由发生错误后销毁进度条 +// 路由错误处理 +router.onError((error) => { + console.error('Router error:', error) Nprogress.remove() }) +// 移除初始加载动画 removeLoading() diff --git a/web/src/router/index.js b/web/src/router/index.js index d5203b2f87..6928e0845e 100644 --- a/web/src/router/index.js +++ b/web/src/router/index.js @@ -21,6 +21,11 @@ const routes = [ closeTab: true }, component: () => import('@/view/error/index.vue') + }, + { + path: '/iframe', + name: 'Iframe', + component: () => import('@/view/layout/iframe.vue') } ] diff --git a/web/src/view/dashboard/index.vue b/web/src/view/dashboard/index.vue index ac58ee523d..353bca608e 100644 --- a/web/src/view/dashboard/index.vue +++ b/web/src/view/dashboard/index.vue @@ -2,19 +2,19 @@
- + - + - + diff --git a/web/src/view/layout/aside/combinationMode.vue b/web/src/view/layout/aside/combinationMode.vue index 22c6c38df6..ebd159095e 100644 --- a/web/src/view/layout/aside/combinationMode.vue +++ b/web/src/view/layout/aside/combinationMode.vue @@ -97,6 +97,10 @@ }) watchEffect(() => { + if (route.name === 'Iframe') { + active.value = decodeURIComponent(route.query.url) + return + } active.value = route.meta.activeName || route.name }) @@ -123,7 +127,12 @@ }) if (index === route.name) return if (index.indexOf('http://') > -1 || index.indexOf('https://') > -1) { - window.open(index) + query.url = decodeURIComponent(index) + router.push({ + name: 'Iframe', + query, + params + }) } else { if (!top) { router.push({ name: index, query, params }) diff --git a/web/src/view/layout/aside/headMode.vue b/web/src/view/layout/aside/headMode.vue index 9157c59261..2c37ba0d8a 100644 --- a/web/src/view/layout/aside/headMode.vue +++ b/web/src/view/layout/aside/headMode.vue @@ -40,6 +40,10 @@ const isCollapse = ref(false) const active = ref('') watchEffect(() => { + if (route.name === 'Iframe') { + active.value = decodeURIComponent(route.query.url) + return + } active.value = route.meta.activeName || route.name }) @@ -66,7 +70,12 @@ }) if (index === route.name) return if (index.indexOf('http://') > -1 || index.indexOf('https://') > -1) { - window.open(index) + query.url = decodeURIComponent(index) + router.push({ + name: 'Iframe', + query, + params + }) } else { router.push({ name: index, query, params }) } diff --git a/web/src/view/layout/aside/normalMode.vue b/web/src/view/layout/aside/normalMode.vue index a3bc7f4241..a1a1cb6ddc 100644 --- a/web/src/view/layout/aside/normalMode.vue +++ b/web/src/view/layout/aside/normalMode.vue @@ -15,7 +15,7 @@ unique-opened @select="selectMenuItem" > -