feat: 书库导入导出/图片变体/书籍搜索/小组件运行时等
新增: - 书库导入导出(library_import/library_export)及测试 - 图片变体生成(image_variants)与响应式图片(responsiveImage) - 书籍搜索(bookSearch)+ BookSearch/LibrarySearchGrid 组件 - 小组件运行时(widgetRuntime)与静态检查(widgetLint) - 上传缓存中间件(upload_cache)与字体 CSS 提取脚本 其它: - 后端 handlers/services 全量调整 - 前端页面、组件、库函数与配置更新
This commit is contained in:
@@ -54,6 +54,7 @@ type AdminUserItem struct {
|
||||
BoardIDs []uint `json:"board_ids"`
|
||||
Banned bool `json:"banned"`
|
||||
CanManageMessages bool `json:"can_manage_messages"`
|
||||
PermOverrides []string `json:"perm_overrides"` // 账号级渠道权限(角色固有之外的增量授予)
|
||||
PostCount int64 `json:"post_count"`
|
||||
CommentCount int64 `json:"comment_count"`
|
||||
Points int `json:"points"`
|
||||
@@ -71,7 +72,7 @@ type AdminUserItem struct {
|
||||
// - total / admins / banned:筛选 Tab 角标(不在顶部卡片展示,避免与仪表盘重复)
|
||||
type AdminUserSummary struct {
|
||||
Online int64 `json:"online"`
|
||||
InCooldown int64 `json:"in_cooldown"`
|
||||
InCooldown int64 `json:"in_cooldown"`
|
||||
Active7d int64 `json:"active_7d"`
|
||||
FailedLogins24h int64 `json:"failed_logins_24h"`
|
||||
Total int64 `json:"total"`
|
||||
@@ -237,6 +238,7 @@ func (s *AdminUserService) toItems(users []model.User) []AdminUserItem {
|
||||
BoardIDs: []uint{},
|
||||
Banned: u.Banned,
|
||||
CanManageMessages: u.CanManageMessages,
|
||||
PermOverrides: ParsePermOverrides(u.PermOverrides),
|
||||
Points: u.Points,
|
||||
TotalPoints: u.TotalPoints,
|
||||
Level: u.Level,
|
||||
@@ -407,6 +409,12 @@ func (s *AdminUserService) SetStaff(operator *Actor, targetID uint, role model.R
|
||||
return err
|
||||
}
|
||||
}
|
||||
// 升至管理员及以上后渠道权限变为角色自带,账号级授予清空以免误导
|
||||
if roleChanged && model.RoleLevel(role) >= model.RoleLevel(model.RoleAdmin) && u.PermOverrides != "" && u.PermOverrides != "[]" {
|
||||
if err := tx.Model(&u).Update("perm_overrides", "[]").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
@@ -415,6 +423,53 @@ func (s *AdminUserService) SetStaff(operator *Actor, targetID uint, role model.R
|
||||
return s.getItem(s.db, targetID)
|
||||
}
|
||||
|
||||
// GetPermOverrides 读取目标账号的渠道权限(PermUsers 持有者可读,供授权弹窗回显)
|
||||
func (s *AdminUserService) GetPermOverrides(operator *Actor, targetID uint) ([]string, error) {
|
||||
if operator == nil || !operator.HasPerm(PermUsers) {
|
||||
return nil, ErrCannotAssignRole
|
||||
}
|
||||
var u model.User
|
||||
if err := s.db.Select("id", "perm_overrides").First(&u, targetID).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ParsePermOverrides(u.PermOverrides), nil
|
||||
}
|
||||
|
||||
// SetPermOverrides 授予/撤销账号级渠道权限(公告/单页/书库/广告)。
|
||||
// 授予者需持有 PermUsers(超管/站长);目标为管理员及以上时渠道为角色自带,
|
||||
// 授权无意义,强制清空。Actor 每次请求现查 DB,变更即时生效,无需强制下线。
|
||||
func (s *AdminUserService) SetPermOverrides(operator *Actor, targetID uint, perms []string) (*AdminUserItem, error) {
|
||||
if operator == nil || !operator.HasPerm(PermUsers) {
|
||||
return nil, ErrCannotAssignRole
|
||||
}
|
||||
if operator.ID == targetID {
|
||||
return nil, ErrAdminSelfAction
|
||||
}
|
||||
norm := NormalizePermOverrides(perms)
|
||||
err := s.db.Transaction(func(tx *gorm.DB) error {
|
||||
var u model.User
|
||||
if err := tx.First(&u, targetID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if u.Role == model.RoleOwner {
|
||||
return ErrProtectedOwner
|
||||
}
|
||||
// 管理员及以上角色自带全部渠道权限,账号级授予保持为空
|
||||
if model.RoleLevel(u.Role) >= model.RoleLevel(model.RoleAdmin) {
|
||||
norm = []string{}
|
||||
}
|
||||
raw := SerializePermOverrides(norm)
|
||||
if u.PermOverrides == raw {
|
||||
return nil
|
||||
}
|
||||
return tx.Model(&u).Update("perm_overrides", raw).Error
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.getItem(s.db, targetID)
|
||||
}
|
||||
|
||||
// SetCanManageMessages 站长授予/撤销站点消息管理权限(不可操作站长账号与自己)
|
||||
func (s *AdminUserService) SetCanManageMessages(operator *Actor, targetID uint, enabled bool) (*AdminUserItem, error) {
|
||||
if operator == nil || operator.Role != model.RoleOwner {
|
||||
@@ -564,12 +619,12 @@ func normalizeAuditPage(page, size int) (int, int) {
|
||||
|
||||
// AdminUserAuditProfile 站长查看用户档案摘要
|
||||
type AdminUserAuditProfile struct {
|
||||
User AdminUserItem `json:"user"`
|
||||
PostsTotal int64 `json:"posts_total"` // 含各状态与软删
|
||||
CommentsTotal int64 `json:"comments_total"` // 含各状态与软删
|
||||
MessagesTotal int64 `json:"messages_total"` // 含撤回与软删
|
||||
PublishedPosts int64 `json:"published_posts"`
|
||||
PublishedComments int64 `json:"published_comments"`
|
||||
User AdminUserItem `json:"user"`
|
||||
PostsTotal int64 `json:"posts_total"` // 含各状态与软删
|
||||
CommentsTotal int64 `json:"comments_total"` // 含各状态与软删
|
||||
MessagesTotal int64 `json:"messages_total"` // 含撤回与软删
|
||||
PublishedPosts int64 `json:"published_posts"`
|
||||
PublishedComments int64 `json:"published_comments"`
|
||||
}
|
||||
|
||||
// AdminAuditPostItem 审计帖子行
|
||||
@@ -594,6 +649,8 @@ type AdminAuditCommentItem struct {
|
||||
Deleted bool `json:"deleted"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
DeletedAt *time.Time `json:"deleted_at,omitempty"`
|
||||
Floor uint `json:"floor"`
|
||||
IsRoot bool `json:"is_root"`
|
||||
}
|
||||
|
||||
// AdminAuditMessageItem 审计聊天消息行
|
||||
@@ -686,6 +743,11 @@ func (s *AdminUserService) ListAuditComments(actor *Actor, userID uint, page, si
|
||||
Offset((page - 1) * size).Limit(size).Find(&comments).Error; err != nil {
|
||||
return nil, 0, page, err
|
||||
}
|
||||
commentIDs := make([]uint, 0, len(comments))
|
||||
for _, c := range comments {
|
||||
commentIDs = append(commentIDs, c.ID)
|
||||
}
|
||||
anchors := CommentAnchors(s.db, commentIDs)
|
||||
items := make([]AdminAuditCommentItem, 0, len(comments))
|
||||
for _, c := range comments {
|
||||
it := AdminAuditCommentItem{
|
||||
@@ -695,6 +757,10 @@ func (s *AdminUserService) ListAuditComments(actor *Actor, userID uint, page, si
|
||||
Status: c.Status,
|
||||
CreatedAt: c.CreatedAt,
|
||||
}
|
||||
if a, ok := anchors[c.ID]; ok {
|
||||
it.Floor = a.Floor
|
||||
it.IsRoot = a.IsRoot
|
||||
}
|
||||
if c.DeletedAt.Valid {
|
||||
it.Deleted = true
|
||||
t := c.DeletedAt.Time
|
||||
|
||||
Reference in New Issue
Block a user