🎨 添加图库和客服插件(有问题-待修改)

This commit is contained in:
2025-05-14 16:51:31 +08:00
parent 5faff4afa0
commit a5ae680f94
41 changed files with 2592 additions and 12 deletions

View File

@@ -0,0 +1,113 @@
## GVA 图库功能
### 手动安装方法
1.解压zip获得picturelibrary文件夹
2.将 picturelibrary/web/plugin/picturelibrary 放置在web/plugin下
3.将 picturelibrary/server/plugin/picturelibrary 放置在server/plugin下
#### 执行如下注册方法
### 注册(手动自动都需要)
#### 1. 前往GVA主程序下的initialize/router.go 在Routers 方法最末尾按照你需要的及安全模式添加本插件
PluginInit(PrivateGroup, picturelibrary.CreatePictureLibraryPlug())
到gva系统角色管理分配角色的api权限即可插件会自动注册api需要手动分配。
会生成一个表sys_attachment_category表exa_file_upload_and_downloads会新增一个cat_id字段
### 2. 配置说明
#### 2-1 全局配置结构体说明
无配置
#### 2-2 使用说明
在你需要图片选择的前端页面引入图库组件,如下:
<div class="selected-images">
<div class="selected-image" v-for="image in selectedImages" :key="image">
<el-image v-if="fileTypeList.includes(image.tag) === true" :src="image.url" style="width: 100%; height: 100%; object-fit: cover;margin-right: 10px;"></el-image>
<video v-else controls style="width: 100%; height: 100%;">
<source :src="image.url" />
</video>
<span class="remove-icon" @click="removeSelectedImage(image)"><el-icon><circle-close></circle-close></el-icon></span>
</div>
<el-icon v-if="isMultiple || selectedImages.length === 0" class="avatar-uploader-icon" @click="openImageLibrary"><Plus /></el-icon>
</div>
图库弹窗:
<el-dialog v-model="isDialogVisible" title="图片库" width="950px" destroy-on-close>
<ImageLibrary @select="handleImageSelect" :multiple="isMultiple"/>
</el-dialog>
js代码
import {CircleClose, Plus} from '@element-plus/icons-vue'
import ImageLibrary from "@/plugin/picturelibrary/view/components/imageLibrary.vue";
const isDialogVisible = ref(false)
const isMultiple = ref(false) // 设置是否允许多选
const selectedImages = ref([])
const openImageLibrary = () => {
isDialogVisible.value = true
}
const fileTypeList = ['png', 'jpg', 'jpeg', 'gif']
const handleImageSelect = (images) => {
if (isMultiple.value) {
selectedImages.value = [...selectedImages.value, ...images]
} else {
selectedImages.value = Array.isArray(images) ? images : [images]
}
// 此处是测试项目里上传头像的参数,根据实际情况进行修改
formData.value.avatar = selectedImages.value[0]
isDialogVisible.value = false
}
const removeSelectedImage = (image) => {
const index = selectedImages.value.indexOf(image)
if (index !== -1) {
selectedImages.value.splice(index, 1)
}
}
style代码
.selected-images {
position: relative;
display: flex;
flex-wrap: wrap;
}
.selected-image {
position: relative;
margin-right: 10px;
margin-bottom:10px;
width: 100px;
height: 100px;
}
.selected-image .remove-icon {
position: absolute;
top: 0; /* 微调位置 */
right: 0; /* 微调位置 */
color: black;
padding: 5px;
cursor: pointer;
font-size: 22px;
line-height: 22px;
text-align: center;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 100px;
height: 100px;
text-align: center;
border: 1px dashed #d4d9e1;
}
#### 2-3 参数说明
isMultiple 是控制能不能选择多张图的参数false:只能选一张true:可以选择多张
### 3. 方法API

View File

@@ -0,0 +1,230 @@
package api
import (
"git.echol.cn/loser/lckt/global"
"git.echol.cn/loser/lckt/model/common/response"
"git.echol.cn/loser/lckt/model/example"
picModel "git.echol.cn/loser/lckt/plugin/picturelibrary/model"
"git.echol.cn/loser/lckt/plugin/picturelibrary/service"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"strconv"
)
type PictureLibraryApi struct{}
var picService = service.ServiceGroupApp
// @Tags PictureLibrary
// @Summary 请手动填写接口功能
// @Produce application/json
// @Success 200 {string} string "{"success":true,"data":{},"msg":"发送成功"}"
// @Router /图片库/routerName [post]
func (p *PictureLibraryApi) ApiName(c *gin.Context) {
if err := picService.PlugService(); err != nil {
global.GVA_LOG.Error("失败!", zap.Error(err))
response.FailWithMessage("失败", c)
} else {
response.OkWithData("成功", c)
}
}
// GetFileList
// @Tags sysAttachment
// @Summary 分页文件列表
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param data body request.PageInfo true "页码, 每页大小"
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页文件列表,返回包括列表,总数,页码,每页数量"
// @Router /attachment/getFileList [post]
func (p *PictureLibraryApi) GetFileList(c *gin.Context) {
var pageInfo picModel.PageInfo
_ = c.ShouldBindJSON(&pageInfo)
list, total, err := picService.GetFileRecordInfoList(pageInfo, c)
if err != nil {
global.GVA_LOG.Error("获取失败!", zap.Error(err))
response.FailWithMessage("获取失败", c)
return
}
response.OkWithDetailed(response.PageResult{
List: list,
Total: total,
Page: pageInfo.Page,
PageSize: pageInfo.Limit,
}, "获取成功", c)
}
// GetCategoryList
// @Tags sysAttachment
// @Summary 分类列表
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param data body request.PageInfo true "页码, 每页大小"
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页文件列表,返回包括列表,总数,页码,每页数量"
// @Router /attachment/getFileList [post]
func (p *PictureLibraryApi) GetCategoryList(c *gin.Context) {
db := global.GVA_DB.Model(&picModel.SysAttachmentCategory{})
var fileLists []picModel.SysAttachmentCategory
err := db.Find(&fileLists).Error
if err != nil {
global.GVA_LOG.Error("获取失败!", zap.Error(err))
response.FailWithMessage("获取失败", c)
return
}
data := picService.BuildTree(fileLists, 0)
response.OkWithDetailed(response.PageResult{
List: data,
}, "获取成功", c)
}
// AddCategory
// @Tags sysAttachment
// @Summary 添加分类
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param data body request.PageInfo true "页码, 每页大小"
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页文件列表,返回包括列表,总数,页码,每页数量"
// @Router /attachment/getFileList [post]
func (p *PictureLibraryApi) AddCategory(c *gin.Context) {
var input struct {
Name string `json:"name" binding:"required"`
Pid uint `json:"pid"`
Id uint `json:"id"`
}
if err := c.ShouldBindJSON(&input); err != nil {
global.GVA_LOG.Error("参数错误!", zap.Error(err))
response.FailWithMessage("参数错误", c)
return
}
// 检查是否已存在相同名称的分类
var existingCategory picModel.SysAttachmentCategory
result := global.GVA_DB.Where("name = ? ", input.Name).First(&existingCategory)
if result.Error == nil && existingCategory.Id != input.Id {
response.FailWithMessage("分类名称已存在", c)
return
}
// 创建新的分类
newCategory := picModel.SysAttachmentCategory{Name: input.Name, Pid: input.Pid}
var err error
var title string
if input.Id > 0 {
err = global.GVA_DB.Where("id = ?", input.Id).Updates(newCategory).Error
title = "更新失败"
} else {
err = global.GVA_DB.Create(&newCategory).Error
title = "创建失败"
}
if err != nil {
response.FailWithMessage(title, c)
return
}
response.OkWithDetailed(response.PageResult{}, "创建成功", c)
}
// UploadHandler
// @Tags sysAttachment
// @Summary 多文件上传
// @Security ApiKeyAuth
// @accept multipart/form-data
// @Produce application/json
// @Param file formData [file] true "上传文件示例"
// @Success 200 {object} response.Response{data=exampleRes.ExaFileResponse,msg=string} "上传文件示例,返回包括文件详情"
// @Router /fileUploadAndDownload/upload [post]
func (p *PictureLibraryApi) UploadHandler(c *gin.Context) {
form, _ := c.MultipartForm()
files := form.File["files"]
categoryIDStr := c.PostForm("cat_id")
categoryID, _ := strconv.ParseUint(categoryIDStr, 10, 32)
noSave := c.DefaultQuery("noSave", "0")
for _, file := range files {
classId, _ := strconv.Atoi(c.DefaultPostForm("classId", "0"))
fileData, err := fileUploadAndDownloadService.UploadFile(file, noSave, classId)
if err != nil {
global.GVA_LOG.Error("上传失败!", zap.Error(err))
response.FailWithMessage("上传失败", c)
return
}
var attachment picModel.SysAttachment
if err := global.GVA_DB.Where("`key` = ? ", fileData.Key).First(&attachment).Error; err != nil {
response.FailWithMessage("上传失败", c)
return
}
// 根据key更新数据
attachment.CatId = uint(categoryID)
if err := global.GVA_DB.Save(&attachment).Error; err != nil {
response.FailWithMessage("上传文件失败", c)
return
}
}
response.OkWithDetailed(response.PageResult{}, "上传成功", c)
}
// DeleteFile
// @Tags sysAttachment
// @Summary 删除文件
// @Security ApiKeyAuth
// @Produce application/json
// @Param data body example.ExaFileUploadAndDownload true "传入文件里面id即可"
// @Success 200 {object} response.Response{msg=string} "删除文件"
// @Router /fileUploadAndDownload/deleteFile [post]
func (p *PictureLibraryApi) DeleteFile(c *gin.Context) {
var files []example.ExaFileUploadAndDownload
err := c.ShouldBindJSON(&files)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
for _, file := range files {
if err := fileUploadAndDownloadService.DeleteFile(file); err != nil {
global.GVA_LOG.Error("删除失败!", zap.Error(err))
response.FailWithMessage("删除失败", c)
return
}
}
response.OkWithMessage("删除成功", c)
}
// DeleteCategory
// @Tags sysAttachment
// @Summary 删除分类
// @Security ApiKeyAuth
// @Produce application/json
// @Param data body example.ExaFileUploadAndDownload true "传入文件里面id即可"
// @Success 200 {object} response.Response{msg=string} "删除文件"
// @Router /fileUploadAndDownload/deleteFile [post]
func (p *PictureLibraryApi) DeleteCategory(c *gin.Context) {
var input struct {
CatId uint `json:"cat_id"`
}
if err := c.ShouldBindJSON(&input); err != nil {
response.FailWithMessage("参数错误", c)
return
}
if input.CatId == 0 {
response.FailWithMessage("参数错误-1", c)
return
}
var childCount int64
global.GVA_DB.Model(&picModel.SysAttachmentCategory{}).Where("pid = ?", input.CatId).Count(&childCount)
if childCount > 0 {
response.FailWithMessage("请先删除子级", c)
return
}
result := global.GVA_DB.Delete(&picModel.SysAttachmentCategory{}, input.CatId)
if result.Error != nil {
response.FailWithMessage("删除失败", c)
return
}
response.OkWithMessage("删除成功", c)
}

View File

@@ -0,0 +1,11 @@
package api
import "git.echol.cn/loser/lckt/service"
type ApiGroup struct {
PictureLibraryApi
}
var ApiGroupApp = new(ApiGroup)
var fileUploadAndDownloadService = service.ServiceGroupApp.ExampleServiceGroup.FileUploadAndDownloadService

View File

@@ -0,0 +1 @@
package config

View File

@@ -0,0 +1 @@
package global

View File

@@ -0,0 +1,77 @@
package picturelibrary
import (
gvaGlobal "git.echol.cn/loser/lckt/global"
"git.echol.cn/loser/lckt/model/system"
"git.echol.cn/loser/lckt/plugin/picturelibrary/model"
"git.echol.cn/loser/lckt/plugin/picturelibrary/router"
"git.echol.cn/loser/lckt/plugin/plugin-tool/utils"
"github.com/gin-gonic/gin"
)
type PictureLibraryPlugin struct {
}
func CreatePictureLibraryPlug() *PictureLibraryPlugin {
gvaGlobal.GVA_DB.AutoMigrate(model.SysAttachment{}, model.SysAttachmentCategory{}) // 此处可以把插件依赖的数据库结构体自动创建表 需要填写对应的结构体
// 下方会自动注册菜单 第一个参数为菜单一级路由信息一般为定义好的组名 第二个参数为真实使用的web页面路由信息
//utils.RegisterMenus(
// system.SysBaseMenu{
// Path: "picturelibrary",
// Name: "picturelibrary",
// Hidden: false,
// Component: "plugin/picturelibrary/view/index.vue",
// Sort: 0,
// Meta: system.Meta{
// Title: "图片库",
// Icon: "folder",
// },
// },
//)
// 下方会自动注册api 以下格式为示例格式,请按照实际情况修改
utils.RegisterApis(
system.SysApi{
Path: "/pic/pic_library/list",
Description: "图片列表",
ApiGroup: "图片库",
Method: "POST",
},
system.SysApi{
Path: "/pic/pic_library/cat_list",
Description: "图片分类列表",
ApiGroup: "图片库",
Method: "POST",
},
system.SysApi{
Path: "/pic/pic_library/add_cat",
Description: "添加分类",
ApiGroup: "图片库",
Method: "POST",
},
system.SysApi{
Path: "/pic/pic_library/upload_handler",
Description: "上传文件",
ApiGroup: "图片库",
Method: "POST",
},
system.SysApi{
Path: "/pic/pic_library/delete_file",
Description: "删除文件",
ApiGroup: "图片库",
Method: "POST",
},
)
return &PictureLibraryPlugin{}
}
func (*PictureLibraryPlugin) Register(group *gin.RouterGroup) {
router.RouterGroupApp.InitPictureLibraryRouter(group)
}
func (*PictureLibraryPlugin) RouterPath() string {
return "pic"
}

View File

@@ -0,0 +1,8 @@
package model
type PageInfo struct {
Page int `json:"page" form:"page"`
Limit int `json:"limit" form:"limit"`
Cid int `json:"cid" form:"cid"` //分类id
Keyword string `json:"keyword" form:"keyword"`
}

View File

@@ -0,0 +1,14 @@
package model
import (
"git.echol.cn/loser/lckt/model/example"
)
type SysAttachment struct {
example.ExaFileUploadAndDownload
CatId uint `json:"cat_id" form:"cat_id" gorm:"default:0;type:int;column:cat_id;comment:分类id;"`
}
func (SysAttachment) TableName() string {
return "exa_file_upload_and_downloads"
}

View File

@@ -0,0 +1,13 @@
package model
type SysAttachmentCategory struct {
Id uint `gorm:"primarykey" json:"id"` // 主键ID
Name string `json:"name" form:"name" gorm:"default:'';type:varchar(255);column:name;comment:分类名称;"`
Pid uint `json:"pid" form:"pid" gorm:"default:0;type:int;column:pid;comment:父节点ID;"`
AddTime int `json:"add_time" form:"add_time" gorm:"default:0;type:int;column:add_time;comment:添加时间;"`
Children []*SysAttachmentCategory `json:"children" gorm:"-"`
}
func (SysAttachmentCategory) TableName() string {
return "sys_attachment_category"
}

View File

@@ -0,0 +1,7 @@
package router
type RouterGroup struct {
PictureLibraryRouter
}
var RouterGroupApp = new(RouterGroup)

View File

@@ -0,0 +1,22 @@
package router
import (
"git.echol.cn/loser/lckt/plugin/picturelibrary/api"
"github.com/gin-gonic/gin"
)
type PictureLibraryRouter struct {
}
func (s *PictureLibraryRouter) InitPictureLibraryRouter(Router *gin.RouterGroup) {
plugRouter := Router.Use()
plugApi := api.ApiGroupApp.PictureLibraryApi
{
plugRouter.POST("/pic_library/list", plugApi.GetFileList)
plugRouter.POST("/pic_library/cat_list", plugApi.GetCategoryList)
plugRouter.POST("/pic_library/add_cat", plugApi.AddCategory)
plugRouter.POST("/pic_library/upload_handler", plugApi.UploadHandler)
plugRouter.POST("/pic_library/delete_file", plugApi.DeleteFile)
plugRouter.POST("/pic_library/delete_cat", plugApi.DeleteCategory)
}
}

View File

@@ -0,0 +1,7 @@
package service
type ServiceGroup struct {
PictureLibraryService
}
var ServiceGroupApp = new(ServiceGroup)

View File

@@ -0,0 +1,70 @@
package service
import (
"git.echol.cn/loser/lckt/global"
picModel "git.echol.cn/loser/lckt/plugin/picturelibrary/model"
"github.com/gin-gonic/gin"
"strings"
)
type PictureLibraryService struct{}
func (e *PictureLibraryService) PlugService() (err error) {
// 写你的业务逻辑
return nil
}
func (e *PictureLibraryService) GetFileRecordInfoList(info picModel.PageInfo, c *gin.Context) (list interface{}, total int64, err error) {
limit := info.Limit
offset := info.Limit * (info.Page - 1)
keyword := info.Keyword
cid := info.Cid
db := global.GVA_DB.Model(&picModel.SysAttachment{})
var fileLists []picModel.SysAttachment
if len(keyword) > 0 {
db = db.Where("`name` LIKE ?", "%"+keyword+"%")
}
if cid > 0 {
db = db.Where("cat_id = ?", cid)
}
err = db.Count(&total).Error
if err != nil {
return
}
err = db.Limit(limit).Offset(offset).Order("created_at desc").Find(&fileLists).Error
urlHost := e.GetUrlHost(c)
for k, v := range fileLists {
if !strings.HasPrefix(v.Url, "http://") && !strings.HasPrefix(v.Url, "https://") {
v.Url = urlHost + "api/" + v.Url
fileLists[k] = v
}
}
return fileLists, total, err
}
// 构建树形结构
func (e *PictureLibraryService) BuildTree(categories []picModel.SysAttachmentCategory, parentID uint) []*picModel.SysAttachmentCategory {
var tree []*picModel.SysAttachmentCategory
for _, category := range categories {
if category.Pid == parentID {
children := e.BuildTree(categories, category.Id)
category.Children = children
tree = append(tree, &category)
}
}
return tree
}
func (e *PictureLibraryService) GetUrlHost(c *gin.Context) string {
host := c.Request.Host
scheme := "http"
if c.Request.TLS != nil {
scheme = "https"
}
referer := c.Request.Referer()
if referer != "" {
return referer
}
return scheme + "://" + host + "/"
}