155 lines
5.2 KiB
Go
155 lines
5.2 KiB
Go
// Package services 中本文件负责「展示端(platforms)」维度的读写与过滤。
|
|
//
|
|
// 背景:模块与菜单原先只有 views([1]平台端 / [2]租户端)一个维度,
|
|
// 无法表达「这个子系统/功能只在 Web 端出现,还是只在小程序出现,还是两端都出现」。
|
|
// 这里新增 platforms 维度:
|
|
// ["web"] —— 仅在 Web 端(租户管理后台)展示
|
|
// ["app"] —— 仅在移动端(uniapp APP / 微信小程序)展示
|
|
// ["web","app"] —— 两端都展示
|
|
// 空串 / NULL —— 视为全端展示(保证历史数据行为不变)
|
|
package services
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
|
|
"server/models"
|
|
)
|
|
|
|
// ParsePlatforms 解析展示端配置(JSON 数组字符串)。
|
|
// 兼容非数组的旧值(如单个字符串),解析失败一律返回空切片。
|
|
func ParsePlatforms(raw *string) []string {
|
|
if raw == nil || strings.TrimSpace(*raw) == "" {
|
|
return nil
|
|
}
|
|
var arr []string
|
|
if err := json.Unmarshal([]byte(*raw), &arr); err == nil {
|
|
out := make([]string, 0, len(arr))
|
|
for _, s := range arr {
|
|
s = strings.ToLower(strings.TrimSpace(s))
|
|
if s != "" {
|
|
out = append(out, s)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
// 非数组格式:按单个值处理
|
|
v := strings.ToLower(strings.TrimSpace(*raw))
|
|
if v == "" {
|
|
return nil
|
|
}
|
|
return []string{v}
|
|
}
|
|
|
|
// HasPlatform 判断是否在指定端展示。
|
|
// 未配置(空)视为全端展示,保证历史数据与新建记录默认两端可见。
|
|
func HasPlatform(list []string, platform string) bool {
|
|
if len(list) == 0 {
|
|
return true
|
|
}
|
|
p := strings.ToLower(strings.TrimSpace(platform))
|
|
for _, s := range list {
|
|
if strings.ToLower(strings.TrimSpace(s)) == p {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// NormalizePlatforms 规范化并去重展示端配置,返回待落库的 JSON 字符串。
|
|
// 空输入返回空串(表示全端),便于前端「未配置 = 全端」的语义保持一致。
|
|
func NormalizePlatforms(list []string) string {
|
|
set := map[string]bool{}
|
|
out := make([]string, 0, len(list))
|
|
for _, s := range list {
|
|
v := strings.ToLower(strings.TrimSpace(s))
|
|
if v == "" || set[v] {
|
|
continue
|
|
}
|
|
set[v] = true
|
|
out = append(out, v)
|
|
}
|
|
if len(out) == 0 {
|
|
return ""
|
|
}
|
|
if b, err := json.Marshal(out); err == nil {
|
|
return string(b)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// ListAllModulesForScope 读取全部未删除模块(含停用与 type=2 系统设置)。
|
|
// 展示端继承判定必须用全量模块:系统设置类模块默认不启用,
|
|
// 若只用启用模块,其菜单会落到"无归属"被误判为全端可见。
|
|
func ListAllModulesForScope() ([]models.SystemModules, error) {
|
|
var rows []models.SystemModules
|
|
_, err := models.Orm.QueryTable(new(models.SystemModules)).
|
|
Filter("delete_time__isnull", true).
|
|
OrderBy("sort", "id").
|
|
All(&rows)
|
|
return rows, err
|
|
}
|
|
|
|
// FilterModulesByPlatform 按展示端过滤模块
|
|
func FilterModulesByPlatform(rows []models.SystemModules, platform string) []models.SystemModules {
|
|
out := make([]models.SystemModules, 0, len(rows))
|
|
for _, m := range rows {
|
|
if HasPlatform(ParsePlatforms(m.Platforms), platform) {
|
|
out = append(out, m)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// FilterMenusByPlatform 按展示端过滤菜单(只看菜单自身的 platforms,不做模块继承)
|
|
func FilterMenusByPlatform(rows []models.SystemMenu, platform string) []models.SystemMenu {
|
|
out := make([]models.SystemMenu, 0, len(rows))
|
|
for _, m := range rows {
|
|
if HasPlatform(ParsePlatforms(m.Platforms), platform) {
|
|
out = append(out, m)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// SortedModulesForScope 把模块按 path 长度倒序,供 MenuVisibleOn 的归属判定复用。
|
|
func SortedModulesForScope(modules []models.SystemModules) []models.SystemModules {
|
|
return sortModulesByPathDesc(modules)
|
|
}
|
|
|
|
// MenuVisibleOn 判断单条菜单是否在指定端展示。
|
|
// 判定顺序:菜单自身 platforms → 继承所属模块 platforms(按 path 前缀归属)→ 全端可见。
|
|
// 这样后台只需在模块上配一次「仅 Web / 仅小程序」,其下未单独配置的菜单自动跟随。
|
|
func MenuVisibleOn(m models.SystemMenu, sortedModules []models.SystemModules, allModules []models.SystemModules, platform string) bool {
|
|
if own := ParsePlatforms(m.Platforms); len(own) > 0 {
|
|
return HasPlatform(own, platform)
|
|
}
|
|
path := ""
|
|
if m.Path != nil {
|
|
path = strings.TrimSpace(*m.Path)
|
|
}
|
|
code, ok := ownerModuleCode(sortedModules, path)
|
|
if !ok {
|
|
return true // 不属于任何模块 → 全端
|
|
}
|
|
for _, mod := range allModules {
|
|
if NormalizeModuleCode(mod.Code) == NormalizeModuleCode(code) {
|
|
return HasPlatform(ParsePlatforms(mod.Platforms), platform)
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// FilterMenusByPlatformWithModules 按展示端过滤菜单,并支持继承所属模块的展示端。
|
|
// modules 应传入全部模块(含停用与 type=2 系统设置),否则系统设置类菜单会误判为全端。
|
|
func FilterMenusByPlatformWithModules(rows []models.SystemMenu, modules []models.SystemModules, platform string) []models.SystemMenu {
|
|
sorted := sortModulesByPathDesc(modules)
|
|
out := make([]models.SystemMenu, 0, len(rows))
|
|
for _, m := range rows {
|
|
if MenuVisibleOn(m, sorted, modules, platform) {
|
|
out = append(out, m)
|
|
}
|
|
}
|
|
return out
|
|
}
|