From 7a97ba8c661d9fdae1dd1828c079b74d4e19c236 Mon Sep 17 00:00:00 2001
From: whm <973418690@qq.com>
Date: Wed, 18 Mar 2026 18:26:08 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E8=A7=92=E8=89=B2=E5=88=9B=E5=BB=BA?=
=?UTF-8?q?=E4=B8=8E=E8=B5=8B=E6=9D=83=E3=80=81=E6=96=87=E4=BB=B6=E7=AE=A1?=
=?UTF-8?q?=E7=90=86=E5=8D=95=E9=A1=B5=E5=A4=9A=E7=BA=A7=E7=9B=AE=E5=BD=95?=
=?UTF-8?q?=E4=B8=8E=E4=B8=8A=E4=BC=A0=E5=8F=AF=E4=B8=8B=E8=BD=BD=E3=80=81?=
=?UTF-8?q?api=E4=B8=8A=E4=BC=A0=E7=9B=AE=E5=BD=95=E5=8F=AF=E5=86=99?=
=?UTF-8?q?=E5=8D=B7?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Made-with: Cursor
---
admin/src/api/admin.js | 12 +-
admin/src/layouts/AdminLayout.vue | 11 +-
admin/src/router/index.js | 14 +-
admin/src/views/files/FileManage.vue | 223 +++++++++++++++++++
admin/src/views/settings/RolePermissions.vue | 98 +++++++-
docker-compose.yml | 1 +
server/handlers/module_upload.go | 133 +++++++++--
server/handlers/role_permission.go | 146 +++++++++---
server/main.go | 3 +
server/models/permission.go | 10 +-
server/models/site.go | 15 +-
11 files changed, 586 insertions(+), 80 deletions(-)
create mode 100644 admin/src/views/files/FileManage.vue
diff --git a/admin/src/api/admin.js b/admin/src/api/admin.js
index 1c14a98..c2d2ace 100644
--- a/admin/src/api/admin.js
+++ b/admin/src/api/admin.js
@@ -8,7 +8,9 @@ export const getMyPermissions = () => request.get('/admin/my-permissions')
// 角色权限管理
export const getRolePermissionsList = () => request.get('/admin/role-permissions')
+export const createRole = (data) => request.post('/admin/role-permissions', data)
export const updateRolePermissions = (roleId, data) => request.put(`/admin/role-permissions/${roleId}`, data)
+export const deleteRole = (roleId) => request.delete(`/admin/role-permissions/${roleId}`)
// 后台注册(手机号+验证码)
export const sendCode = (mobile) => request.post('/admin/send-code', { mobile })
@@ -63,11 +65,15 @@ export const getHomepage = (siteId) => request.get(`/admin/sites/${siteId}/homep
export const updateHomepage = (siteId, data) => request.put(`/admin/sites/${siteId}/homepage`, data)
export const downloadHomepage = (siteId) => request.get(`/admin/sites/${siteId}/homepage/download`, { responseType: 'blob' })
-// 功能模块上传
-export const getSiteAssets = (siteId) => request.get(`/admin/sites/${siteId}/assets`)
-export const uploadSiteAsset = (siteId, file) => {
+// 文件管理(功能模块:多级目录、可下载)
+export const getSiteAssets = (siteId, path) =>
+ request.get(`/admin/sites/${siteId}/assets`, { params: path ? { path } : {} })
+export const uploadSiteAsset = (siteId, file, opts = {}) => {
const form = new FormData()
form.append('file', file)
+ if (opts.folder != null) form.append('folder', opts.folder)
+ form.append('downloadable', opts.downloadable ? 'true' : 'false')
return request.post(`/admin/sites/${siteId}/assets`, form, { headers: { 'Content-Type': 'multipart/form-data' } })
}
+export const createSiteFolder = (siteId, path) => request.post(`/admin/sites/${siteId}/folders`, { path })
export const deleteSiteAsset = (siteId, id) => request.delete(`/admin/sites/${siteId}/assets/${id}`)
diff --git a/admin/src/layouts/AdminLayout.vue b/admin/src/layouts/AdminLayout.vue
index 2b0a300..c4aa425 100644
--- a/admin/src/layouts/AdminLayout.vue
+++ b/admin/src/layouts/AdminLayout.vue
@@ -71,16 +71,7 @@ const menuItems = computed(() => {
{ index: '/sites', title: '站点管理', icon: Monitor, permission: 'site:manage' },
{ index: '/pages', title: '网页管理', icon: Document, permission: 'page:manage' },
{ index: '/homepage-edit', title: '首页编辑', icon: EditPen, permission: 'homepage:edit' },
- {
- index: 'files',
- title: '文件管理',
- icon: Folder,
- permission: null,
- children: [
- { index: '/files/images', title: '图片管理(含图标)', permission: null },
- { index: '/module-upload', title: '功能模块上传', permission: 'module:upload' }
- ]
- },
+ { index: '/files', title: '文件管理', icon: Folder, permission: null },
{ index: '/role-permissions', title: '角色权限管理', icon: Key, permission: 'role:permission' }
]
return all.filter((item) => {
diff --git a/admin/src/router/index.js b/admin/src/router/index.js
index 78af748..bf4b8da 100644
--- a/admin/src/router/index.js
+++ b/admin/src/router/index.js
@@ -67,16 +67,10 @@ const routes = [
meta: { title: '首页编辑', permission: 'homepage:edit' }
},
{
- path: 'files/images',
- name: 'FileImages',
- component: () => import('../views/files/FileImages.vue'),
- meta: { title: '图片管理', permission: null }
- },
- {
- path: 'module-upload',
- name: 'ModuleUpload',
- component: () => import('../views/sites/ModuleUpload.vue'),
- meta: { title: '功能模块上传', permission: 'module:upload' }
+ path: 'files',
+ name: 'FileManage',
+ component: () => import('../views/files/FileManage.vue'),
+ meta: { title: '文件管理', permission: null }
},
{
path: 'role-permissions',
diff --git a/admin/src/views/files/FileManage.vue b/admin/src/views/files/FileManage.vue
new file mode 100644
index 0000000..1c24d06
--- /dev/null
+++ b/admin/src/views/files/FileManage.vue
@@ -0,0 +1,223 @@
+
+
+
+
+ 文件管理
+
+
+
+ 图片与图标统一在此管理,支持可下载/不可下载。功能开发中。
+
+
+
+
+
+
+ 新建文件夹
+
+ 上传文件
+
+
+
+
+
+
+
+ 子目录:
+ {{ d }}/
+
+
+
+
+
+ {{ row.downloadable ? '是' : '否' }}
+
+
+ {{ formatSize(row.size) }}
+
+
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ currentPath || '根目录' }}
+
+
+
+
+
+
+ 取消
+ 确定上传
+
+
+
+
+
+
+
+
+
+
+
+ 取消
+ 创建
+
+
+
+
+
+
+
+
diff --git a/admin/src/views/settings/RolePermissions.vue b/admin/src/views/settings/RolePermissions.vue
index be84215..1876bc7 100644
--- a/admin/src/views/settings/RolePermissions.vue
+++ b/admin/src/views/settings/RolePermissions.vue
@@ -4,14 +4,22 @@
-
超级管理员(9527)拥有全部权限且不可修改。为其他角色勾选其可用的后台权限。
+ 超级管理员(9527)拥有全部权限且不可修改。为其他角色勾选其可用的后台权限;可创建自定义角色并赋权。
-
+
+
+
+ {{ row.role_name }}
+
+
-
+
(全部权限,不可修改)
@@ -26,20 +34,48 @@
+
+
+ 删除
+ —
+
+
+
+
+
+
+
+
+
+
+
+ {{ p.name }}
+
+
+
+
+
+ 取消
+ 创建
+
+
diff --git a/docker-compose.yml b/docker-compose.yml
index ccb1bb3..e5f9a51 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -13,6 +13,7 @@ services:
container_name: yh_api
volumes:
- ./deploy/api:/app:ro
+ - ./data/uploads:/app/uploads
env_file:
- ./server/.env
environment:
diff --git a/server/handlers/module_upload.go b/server/handlers/module_upload.go
index 7ff83ce..1d29cfa 100644
--- a/server/handlers/module_upload.go
+++ b/server/handlers/module_upload.go
@@ -5,6 +5,9 @@ import (
"net/http"
"os"
"path/filepath"
+ "regexp"
+ "sort"
+ "strings"
"time"
"go.mongodb.org/mongo-driver/v2/bson"
@@ -18,20 +21,35 @@ import (
const uploadDir = "uploads"
-// ListSiteAssets 站点功能模块/上传文件列表
+// pathPrefix 站点下相对路径前缀,用于多级目录
+func pathPrefix(siteID string) string {
+ return "sites/" + siteID + "/"
+}
+
+// ListSiteAssets 站点功能模块/上传文件列表;query path 为当前目录相对路径(空为根)
func ListSiteAssets(c *gin.Context) {
siteID := c.Param("site_id")
if siteID == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "请提供 site_id"})
return
}
+ path := c.Query("path")
+ prefix := pathPrefix(siteID)
+ if path != "" {
+ prefix = prefix + path
+ if prefix[len(prefix)-1] != '/' {
+ prefix += "/"
+ }
+ }
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
coll := config.GetDB(config.DBName).Collection("site_assets")
+ // 仅当前目录下直接文件(file_path 为 prefix + 不含 / 的文件名)
+ filter := bson.M{"site_id": siteID, "file_path": bson.M{"$regex": "^" + regexp.QuoteMeta(prefix) + "[^/]+$"}}
opts := options.Find().SetSort(bson.D{{Key: "created_at", Value: -1}})
- cursor, err := coll.Find(ctx, bson.M{"site_id": siteID}, opts)
+ cursor, err := coll.Find(ctx, filter, opts)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
@@ -43,11 +61,60 @@ func ListSiteAssets(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
- total, _ := coll.CountDocuments(ctx, bson.M{"site_id": siteID})
- c.JSON(http.StatusOK, gin.H{"list": list, "total": total})
+ total, _ := coll.CountDocuments(ctx, filter)
+ // 子目录列表:从 file_path 中提取当前 path 下的一级子目录名
+ subDirs := listSubDirs(c, siteID, path)
+ c.JSON(http.StatusOK, gin.H{"list": list, "total": total, "sub_dirs": subDirs})
}
-// UploadSiteAsset 上传功能模块/文件
+func listSubDirs(c *gin.Context, siteID, currentPath string) []string {
+ prefix := pathPrefix(siteID)
+ if currentPath != "" {
+ prefix = prefix + currentPath
+ if prefix[len(prefix)-1] != '/' {
+ prefix += "/"
+ }
+ }
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+ coll := config.GetDB(config.DBName).Collection("site_assets")
+ cursor, err := coll.Find(ctx, bson.M{"site_id": siteID, "file_path": bson.M{"$regex": "^" + regexp.QuoteMeta(prefix)}})
+ if err != nil {
+ return nil
+ }
+ defer cursor.Close(ctx)
+ var docs []struct {
+ FilePath string `bson:"file_path"`
+ }
+ _ = cursor.All(ctx, &docs)
+ seen := make(map[string]bool)
+ for _, d := range docs {
+ rel := strings.TrimPrefix(d.FilePath, prefix)
+ if rel == "" || rel == d.FilePath {
+ continue
+ }
+ parts := strings.SplitN(rel, "/", 2)
+ if len(parts) > 0 && parts[0] != "" {
+ seen[parts[0]] = true
+ }
+ }
+ // 再扫描物理目录
+ baseDir := filepath.Join(uploadDir, filepath.FromSlash(prefix))
+ entries, _ := os.ReadDir(baseDir)
+ for _, e := range entries {
+ if e.IsDir() {
+ seen[e.Name()] = true
+ }
+ }
+ names := make([]string, 0, len(seen))
+ for n := range seen {
+ names = append(names, n)
+ }
+ sort.Strings(names)
+ return names
+}
+
+// UploadSiteAsset 上传功能模块/文件;form 可选:folder(当前目录相对路径)、downloadable(true/false)
func UploadSiteAsset(c *gin.Context) {
siteID := c.Param("site_id")
if siteID == "" {
@@ -61,19 +128,21 @@ func UploadSiteAsset(c *gin.Context) {
return
}
- baseDir := filepath.Join(uploadDir, "sites", siteID)
+ folder := c.PostForm("folder")
+ downloadable := c.PostForm("downloadable") == "true" || c.PostForm("downloadable") == "1"
+ baseDir := filepath.Join(uploadDir, "sites", siteID, filepath.Clean(folder))
if err := os.MkdirAll(baseDir, 0755); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "创建目录失败"})
return
}
- // 避免覆盖:加时间戳
name := file.Filename
ext := filepath.Ext(name)
nameNoExt := name[:len(name)-len(ext)]
saveName := nameNoExt + "_" + time.Now().Format("20060102150405") + ext
- relPath := filepath.Join("sites", siteID, saveName)
- destPath := filepath.Join(uploadDir, relPath)
+ relPath := filepath.Join("sites", siteID, filepath.Clean(folder), saveName)
+ relPath = filepath.ToSlash(relPath)
+ destPath := filepath.Join(uploadDir, filepath.FromSlash(relPath))
if err := c.SaveUploadedFile(file, destPath); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "保存文件失败"})
@@ -84,12 +153,13 @@ func UploadSiteAsset(c *gin.Context) {
defer cancel()
doc := models.SiteAsset{
- SiteID: siteID,
- Name: file.Filename,
- FilePath: relPath,
- Size: file.Size,
- ContentType: file.Header.Get("Content-Type"),
- CreatedAt: time.Now().Format(time.RFC3339),
+ SiteID: siteID,
+ Name: file.Filename,
+ FilePath: relPath,
+ Size: file.Size,
+ ContentType: file.Header.Get("Content-Type"),
+ Downloadable: downloadable,
+ CreatedAt: time.Now().Format(time.RFC3339),
}
res, err := config.GetDB(config.DBName).Collection("site_assets").InsertOne(ctx, bson.M{
"site_id": doc.SiteID,
@@ -97,6 +167,7 @@ func UploadSiteAsset(c *gin.Context) {
"file_path": doc.FilePath,
"size": doc.Size,
"content_type": doc.ContentType,
+ "downloadable": doc.Downloadable,
"created_at": doc.CreatedAt,
})
if err != nil {
@@ -133,7 +204,7 @@ func DeleteSiteAsset(c *gin.Context) {
return
}
- fullPath := filepath.Join(uploadDir, asset.FilePath)
+ fullPath := filepath.Join(uploadDir, filepath.FromSlash(asset.FilePath))
os.Remove(fullPath)
_, err = coll.DeleteOne(ctx, bson.M{"_id": oid})
@@ -143,3 +214,33 @@ func DeleteSiteAsset(c *gin.Context) {
}
c.JSON(http.StatusOK, gin.H{"message": "删除成功"})
}
+
+// CreateSiteFolderInput 创建目录
+type CreateSiteFolderInput struct {
+ Path string `json:"path" binding:"required"`
+}
+
+// CreateSiteFolder 在站点下创建多级目录
+func CreateSiteFolder(c *gin.Context) {
+ siteID := c.Param("site_id")
+ if siteID == "" {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "请提供 site_id"})
+ return
+ }
+ var input CreateSiteFolderInput
+ if err := c.ShouldBindJSON(&input); err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "请填写目录路径"})
+ return
+ }
+ clean := filepath.Clean(input.Path)
+ if clean == "." || clean == ".." || strings.HasPrefix(clean, "..") {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "无效的目录路径"})
+ return
+ }
+ baseDir := filepath.Join(uploadDir, "sites", siteID, clean)
+ if err := os.MkdirAll(baseDir, 0755); err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "创建目录失败"})
+ return
+ }
+ c.JSON(http.StatusOK, gin.H{"message": "创建成功", "path": filepath.ToSlash(clean)})
+}
diff --git a/server/handlers/role_permission.go b/server/handlers/role_permission.go
index ad1e9ac..e835716 100644
--- a/server/handlers/role_permission.go
+++ b/server/handlers/role_permission.go
@@ -15,17 +15,9 @@ import (
"github.com/gin-gonic/gin"
)
-// 预定义角色(与 users.role_id 对应)
-var roleMeta = []struct {
- RoleID int `json:"role_id"`
- RoleName string `json:"role_name"`
-}{
- {models.RoleIDSuperAdmin, "超级管理员"},
- {models.RoleIDSuperUser, "超级用户"},
- {models.RoleIDUser, "普通用户"},
-}
+const customRoleIDStart = 1000 // 自定义角色 role_id 从此值起
-// GetRolePermissionsList 返回所有角色及其权限(用于角色权限管理页)
+// GetRolePermissionsList 返回所有角色及其权限(含预定义与自定义)
func GetRolePermissionsList(c *gin.Context) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
@@ -44,23 +36,56 @@ func GetRolePermissionsList(c *gin.Context) {
return
}
permMap := make(map[int][]string)
+ nameMap := make(map[int]string)
for _, d := range docs {
permMap[d.RoleID] = d.Permissions
+ if d.RoleName != "" {
+ nameMap[d.RoleID] = d.RoleName
+ }
}
-
- list := make([]gin.H, 0, len(roleMeta))
- for _, r := range roleMeta {
- perms := permMap[r.RoleID]
+ allKeys := allPermissionKeys()
+ // 预定义角色固定在前(9527, 0, 1),再按 role_id 排自定义
+ predef := []int{models.RoleIDSuperAdmin, models.RoleIDSuperUser, models.RoleIDUser}
+ seen := make(map[int]bool)
+ list := make([]gin.H, 0)
+ for _, rid := range predef {
+ seen[rid] = true
+ perms := permMap[rid]
if perms == nil {
perms = []string{}
}
- if r.RoleID == models.RoleIDSuperAdmin {
- perms = allPermissionKeys()
+ if rid == models.RoleIDSuperAdmin {
+ perms = allKeys
+ }
+ name := nameMap[rid]
+ if name == "" {
+ name = models.DefaultRoleNames[rid]
}
list = append(list, gin.H{
- "role_id": r.RoleID,
- "role_name": r.RoleName,
+ "role_id": rid,
+ "role_name": name,
"permissions": perms,
+ "is_custom": false,
+ })
+ }
+ for _, d := range docs {
+ if seen[d.RoleID] {
+ continue
+ }
+ seen[d.RoleID] = true
+ name := d.RoleName
+ if name == "" {
+ name = "角色" + strconv.Itoa(d.RoleID)
+ }
+ perms := d.Permissions
+ if perms == nil {
+ perms = []string{}
+ }
+ list = append(list, gin.H{
+ "role_id": d.RoleID,
+ "role_name": name,
+ "permissions": perms,
+ "is_custom": true,
})
}
c.JSON(http.StatusOK, gin.H{
@@ -69,11 +94,6 @@ func GetRolePermissionsList(c *gin.Context) {
})
}
-// UpdateRolePermissionsInput 更新某角色权限
-type UpdateRolePermissionsInput struct {
- Permissions []string `json:"permissions"`
-}
-
// UpdateRolePermissions 更新指定角色的权限
func UpdateRolePermissions(c *gin.Context) {
roleIDStr := c.Param("role_id")
@@ -87,7 +107,10 @@ func UpdateRolePermissions(c *gin.Context) {
return
}
- var input UpdateRolePermissionsInput
+ var input struct {
+ RoleName string `json:"role_name"`
+ Permissions []string `json:"permissions"`
+ }
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
@@ -98,7 +121,11 @@ func UpdateRolePermissions(c *gin.Context) {
coll := config.GetDB(config.DBName).Collection("role_permissions")
filter := bson.M{"role_id": roleID}
- update := bson.M{"$set": bson.M{"role_id": roleID, "permissions": input.Permissions}}
+ set := bson.M{"role_id": roleID, "permissions": input.Permissions}
+ if input.RoleName != "" && roleID >= customRoleIDStart {
+ set["role_name"] = input.RoleName
+ }
+ update := bson.M{"$set": set}
opts := options.UpdateOne().SetUpsert(true)
_, err = coll.UpdateOne(ctx, filter, update, opts)
if err != nil {
@@ -107,3 +134,72 @@ func UpdateRolePermissions(c *gin.Context) {
}
c.JSON(http.StatusOK, gin.H{"message": "保存成功", "role_id": roleID, "permissions": input.Permissions})
}
+
+// CreateRoleInput 创建角色
+type CreateRoleInput struct {
+ RoleName string `json:"role_name" binding:"required"`
+ Permissions []string `json:"permissions"`
+}
+
+// CreateRole 创建自定义角色
+func CreateRole(c *gin.Context) {
+ var input CreateRoleInput
+ if err := c.ShouldBindJSON(&input); err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "请填写角色名称"})
+ return
+ }
+ if input.Permissions == nil {
+ input.Permissions = []string{}
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ coll := config.GetDB(config.DBName).Collection("role_permissions")
+ cursor, _ := coll.Find(ctx, bson.M{"role_id": bson.M{"$gte": customRoleIDStart}}, options.Find().SetSort(bson.D{{Key: "role_id", Value: -1}}).SetLimit(1))
+ var docs []models.RolePermissionsDoc
+ _ = cursor.All(ctx, &docs)
+ cursor.Close(ctx)
+ nextID := customRoleIDStart
+ for _, d := range docs {
+ if d.RoleID >= customRoleIDStart {
+ nextID = d.RoleID + 1
+ break
+ }
+ }
+
+ doc := models.RolePermissionsDoc{
+ RoleID: nextID,
+ RoleName: input.RoleName,
+ Permissions: input.Permissions,
+ }
+ _, err := coll.InsertOne(ctx, bson.M{"role_id": doc.RoleID, "role_name": doc.RoleName, "permissions": doc.Permissions})
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
+ return
+ }
+ c.JSON(http.StatusOK, gin.H{"message": "创建成功", "role_id": doc.RoleID, "role_name": doc.RoleName, "permissions": doc.Permissions})
+}
+
+// DeleteRole 删除自定义角色(仅 role_id >= customRoleIDStart)
+func DeleteRole(c *gin.Context) {
+ roleIDStr := c.Param("role_id")
+ roleID, err := strconv.Atoi(roleIDStr)
+ if err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "无效的 role_id"})
+ return
+ }
+ if roleID < customRoleIDStart {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "预定义角色不可删除"})
+ return
+ }
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+ coll := config.GetDB(config.DBName).Collection("role_permissions")
+ _, err = coll.DeleteOne(ctx, bson.M{"role_id": roleID})
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
+ return
+ }
+ c.JSON(http.StatusOK, gin.H{"message": "删除成功"})
+}
diff --git a/server/main.go b/server/main.go
index 5ea46e7..a60c8df 100644
--- a/server/main.go
+++ b/server/main.go
@@ -163,6 +163,7 @@ func main() {
admin.PUT("/sites/:site_id/homepage", handlers.RequirePermission(models.PermHomepageEdit), handlers.UpdateHomepage)
admin.GET("/sites/:site_id/assets", handlers.RequirePermission(models.PermSiteManage), handlers.ListSiteAssets)
admin.POST("/sites/:site_id/assets", handlers.RequirePermission(models.PermModuleUpload), handlers.UploadSiteAsset)
+ admin.POST("/sites/:site_id/folders", handlers.RequirePermission(models.PermModuleUpload), handlers.CreateSiteFolder)
admin.DELETE("/sites/:site_id/assets/:asset_id", handlers.RequirePermission(models.PermSiteManage), handlers.DeleteSiteAsset)
admin.GET("/sites", handlers.RequirePermission(models.PermSiteManage), handlers.GetSites)
admin.GET("/sites/:site_id", handlers.RequirePermission(models.PermSiteManage), handlers.GetSiteByID)
@@ -174,7 +175,9 @@ func main() {
// 角色权限管理
admin.GET("/role-permissions", handlers.RequirePermission(models.PermRolePermission), handlers.GetRolePermissionsList)
+ admin.POST("/role-permissions", handlers.RequirePermission(models.PermRolePermission), handlers.CreateRole)
admin.PUT("/role-permissions/:role_id", handlers.RequirePermission(models.PermRolePermission), handlers.UpdateRolePermissions)
+ admin.DELETE("/role-permissions/:role_id", handlers.RequirePermission(models.PermRolePermission), handlers.DeleteRole)
// 网页管理(按站点)
admin.GET("/pages", handlers.RequirePermission(models.PermPageManage), handlers.GetPages)
diff --git a/server/models/permission.go b/server/models/permission.go
index 7543e4c..fcb8e89 100644
--- a/server/models/permission.go
+++ b/server/models/permission.go
@@ -31,8 +31,16 @@ var AllPermissions = []struct {
{PermRolePermission, "角色权限管理"},
}
-// RolePermissionsDoc MongoDB 文档:角色 ID -> 权限列表
+// RolePermissionsDoc MongoDB 文档:角色 ID -> 名称与权限列表(支持自定义角色)
type RolePermissionsDoc struct {
RoleID int `bson:"role_id" json:"role_id"`
+ RoleName string `bson:"role_name,omitempty" json:"role_name"`
Permissions []string `bson:"permissions" json:"permissions"`
}
+
+// 预定义角色 ID 的默认名称(未在 DB 中存 role_name 时使用)
+var DefaultRoleNames = map[int]string{
+ RoleIDSuperAdmin: "超级管理员",
+ RoleIDSuperUser: "超级用户",
+ RoleIDUser: "普通用户",
+}
diff --git a/server/models/site.go b/server/models/site.go
index d66e28a..647a402 100644
--- a/server/models/site.go
+++ b/server/models/site.go
@@ -56,11 +56,12 @@ type FeatureItem struct {
// SiteAsset 站点功能模块/上传文件
type SiteAsset struct {
- ID bson.ObjectID `bson:"_id,omitempty" json:"id"`
- SiteID string `bson:"site_id" json:"site_id"`
- Name string `bson:"name" json:"name"`
- FilePath string `bson:"file_path" json:"file_path"` // 相对路径
- Size int64 `bson:"size" json:"size"`
- ContentType string `bson:"content_type" json:"content_type"`
- CreatedAt string `bson:"created_at" json:"created_at"`
+ ID bson.ObjectID `bson:"_id,omitempty" json:"id"`
+ SiteID string `bson:"site_id" json:"site_id"`
+ Name string `bson:"name" json:"name"`
+ FilePath string `bson:"file_path" json:"file_path"` // 相对路径,可含多级目录
+ Size int64 `bson:"size" json:"size"`
+ ContentType string `bson:"content_type" json:"content_type"`
+ Downloadable bool `bson:"downloadable" json:"downloadable"` // 是否允许下载
+ CreatedAt string `bson:"created_at" json:"created_at"`
}