340 lines
8.5 KiB
Go
340 lines
8.5 KiB
Go
package controllers
|
||
|
||
import (
|
||
"encoding/json"
|
||
"io"
|
||
"regexp"
|
||
"strings"
|
||
"time"
|
||
|
||
"server/models"
|
||
"server/pkg/jwtutil"
|
||
|
||
"github.com/beego/beego/v2/client/orm"
|
||
beego "github.com/beego/beego/v2/server/web"
|
||
)
|
||
|
||
// BackendThemeController 官网模板选用(租户端只读选用,模板管理在 platform 端)
|
||
type BackendThemeController struct {
|
||
beego.Controller
|
||
}
|
||
|
||
func (c *BackendThemeController) cmsClaims() (*jwtutil.Claims, error) {
|
||
return cmsBackendClaims(&c.Controller)
|
||
}
|
||
|
||
func (c *BackendThemeController) cmsJSONErr(httpStatus, bizCode int, msg string) {
|
||
c.Ctx.Output.SetStatus(httpStatus)
|
||
c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg}
|
||
_ = c.ServeJSON()
|
||
}
|
||
|
||
var reThemeCode = regexp.MustCompile(`^[a-zA-Z0-9_\-]+$`)
|
||
|
||
// List GET /backend/theme 仅返回启用中的模板(含内置 default)
|
||
func (c *BackendThemeController) List() {
|
||
claims, err := c.cmsClaims()
|
||
if err != nil {
|
||
c.cmsJSONErr(401, 401, err.Error())
|
||
return
|
||
}
|
||
tid := cmsEffectiveTid(&c.Controller, claims)
|
||
if tid == 0 {
|
||
c.cmsJSONErr(400, 400, "tid不能为空")
|
||
return
|
||
}
|
||
|
||
var rows []models.CmsFrontendTemplate
|
||
_, err = models.Orm.QueryTable(new(models.CmsFrontendTemplate)).
|
||
Filter("status", 1).
|
||
Filter("delete_time__isnull", true).
|
||
OrderBy("sort", "id").
|
||
All(&rows)
|
||
if err != nil && err != orm.ErrNoRows {
|
||
c.cmsJSONErr(500, 500, "获取模板列表失败")
|
||
return
|
||
}
|
||
|
||
list := make([]map[string]interface{}, 0, len(rows)+1)
|
||
hasDefault := false
|
||
for _, r := range rows {
|
||
if r.Code == "default" {
|
||
hasDefault = true
|
||
}
|
||
list = append(list, map[string]interface{}{
|
||
"key": r.Code,
|
||
"name": r.Name,
|
||
"description": r.Description,
|
||
"preview": r.Preview,
|
||
"version": "1.0",
|
||
"author": "",
|
||
})
|
||
}
|
||
// 内置 default 模板始终可选,即使未登记入库
|
||
if !hasDefault {
|
||
list = append([]map[string]interface{}{{
|
||
"key": "default",
|
||
"name": "默认模板",
|
||
"description": "系统内置默认模板",
|
||
"preview": "/themes/default/static/preview.png",
|
||
"version": "1.0",
|
||
"author": "",
|
||
}}, list...)
|
||
}
|
||
|
||
current := cmsTenantTemplateCode(tid)
|
||
if current == "" {
|
||
current = "default"
|
||
}
|
||
|
||
c.Data["json"] = map[string]interface{}{
|
||
"code": 200,
|
||
"msg": "success",
|
||
"data": map[string]interface{}{"list": list, "currentTheme": current},
|
||
}
|
||
_ = c.ServeJSON()
|
||
}
|
||
|
||
// cmsTenantTemplateCode 读取租户当前选用的模板编码
|
||
func cmsTenantTemplateCode(tid uint64) string {
|
||
var row models.TenantSiteSetting
|
||
err := models.Orm.QueryTable(new(models.TenantSiteSetting)).
|
||
Filter("tid", tid).
|
||
Filter("delete_time__isnull", true).
|
||
One(&row)
|
||
if err != nil {
|
||
return ""
|
||
}
|
||
return row.TemplateCode
|
||
}
|
||
|
||
// Switch POST /backend/theme/switch 切换租户使用的模板(写入 template_code)
|
||
func (c *BackendThemeController) Switch() {
|
||
claims, err := c.cmsClaims()
|
||
if err != nil {
|
||
c.cmsJSONErr(401, 401, err.Error())
|
||
return
|
||
}
|
||
tid := cmsEffectiveTid(&c.Controller, claims)
|
||
|
||
raw, err := io.ReadAll(c.Ctx.Request.Body)
|
||
if err != nil {
|
||
c.cmsJSONErr(400, 400, "参数错误")
|
||
return
|
||
}
|
||
var body struct {
|
||
Tid uint64 `json:"tid"`
|
||
ThemeKey string `json:"theme_key"`
|
||
}
|
||
if err := json.Unmarshal(raw, &body); err != nil {
|
||
c.cmsJSONErr(400, 400, "参数错误")
|
||
return
|
||
}
|
||
if tid == 0 {
|
||
tid = body.Tid
|
||
}
|
||
if tid == 0 {
|
||
c.cmsJSONErr(400, 400, "tid不能为空")
|
||
return
|
||
}
|
||
|
||
code := strings.TrimSpace(body.ThemeKey)
|
||
if code == "" || !reThemeCode.MatchString(code) {
|
||
c.cmsJSONErr(400, 400, "模板编码不合法")
|
||
return
|
||
}
|
||
// 非内置 default 模板必须已登记且启用中
|
||
if code != "default" {
|
||
cnt, _ := models.Orm.QueryTable(new(models.CmsFrontendTemplate)).
|
||
Filter("code", code).
|
||
Filter("status", 1).
|
||
Filter("delete_time__isnull", true).
|
||
Count()
|
||
if cnt == 0 {
|
||
c.cmsJSONErr(400, 400, "模板不存在或已停用")
|
||
return
|
||
}
|
||
}
|
||
|
||
if err := cmsUpsertTenantTemplate(tid, code); err != nil {
|
||
c.cmsJSONErr(500, 500, "切换失败")
|
||
return
|
||
}
|
||
|
||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "切换成功", "data": map[string]interface{}{"currentTheme": code}}
|
||
_ = c.ServeJSON()
|
||
}
|
||
|
||
// cmsUpsertTenantTemplate 写入租户模板编码(站点设置不存在则创建)
|
||
func cmsUpsertTenantTemplate(tid uint64, code string) error {
|
||
now := time.Now()
|
||
cnt, err := models.Orm.QueryTable(new(models.TenantSiteSetting)).
|
||
Filter("tid", tid).
|
||
Filter("delete_time__isnull", true).
|
||
Count()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if cnt == 0 {
|
||
_, err = models.Orm.Insert(&models.TenantSiteSetting{
|
||
Tid: tid,
|
||
TemplateCode: code,
|
||
CreateTime: now,
|
||
UpdateTime: &now,
|
||
})
|
||
return err
|
||
}
|
||
_, err = models.Orm.QueryTable(new(models.TenantSiteSetting)).
|
||
Filter("tid", tid).
|
||
Filter("delete_time__isnull", true).
|
||
Update(map[string]interface{}{"template_code": code, "update_time": now})
|
||
return err
|
||
}
|
||
|
||
// GetData GET /backend/theme/data 返回站点设置(供模板配置展示)
|
||
func (c *BackendThemeController) GetData() {
|
||
claims, err := c.cmsClaims()
|
||
if err != nil {
|
||
c.cmsJSONErr(401, 401, err.Error())
|
||
return
|
||
}
|
||
tid := cmsEffectiveTid(&c.Controller, claims)
|
||
if tid == 0 {
|
||
c.cmsJSONErr(400, 400, "tid不能为空")
|
||
return
|
||
}
|
||
|
||
var row models.TenantSiteSetting
|
||
err = models.Orm.QueryTable(new(models.TenantSiteSetting)).
|
||
Filter("tid", tid).
|
||
Filter("delete_time__isnull", true).
|
||
One(&row)
|
||
if err != nil && err != orm.ErrNoRows {
|
||
c.cmsJSONErr(500, 500, "获取失败")
|
||
return
|
||
}
|
||
|
||
c.Data["json"] = map[string]interface{}{
|
||
"code": 200,
|
||
"msg": "success",
|
||
"data": map[string]interface{}{
|
||
"tid": tid,
|
||
"sitename": row.Sitename,
|
||
"logo": row.Logo,
|
||
"logow": row.Logow,
|
||
"ico": row.Ico,
|
||
"description": row.Description,
|
||
"copyright": row.Copyright,
|
||
"companyname": row.Companyname,
|
||
"icp": row.Icp,
|
||
"companyintroduction": row.Companyintroduction,
|
||
"template_code": row.TemplateCode,
|
||
},
|
||
}
|
||
_ = c.ServeJSON()
|
||
}
|
||
|
||
// SaveData POST /backend/theme/data 保存站点设置(模板配置用,白名单字段)
|
||
func (c *BackendThemeController) SaveData() {
|
||
claims, err := c.cmsClaims()
|
||
if err != nil {
|
||
c.cmsJSONErr(401, 401, err.Error())
|
||
return
|
||
}
|
||
tid := cmsEffectiveTid(&c.Controller, claims)
|
||
if tid == 0 {
|
||
c.cmsJSONErr(400, 400, "tid不能为空")
|
||
return
|
||
}
|
||
|
||
raw, err := io.ReadAll(c.Ctx.Request.Body)
|
||
if err != nil {
|
||
c.cmsJSONErr(400, 400, "参数错误")
|
||
return
|
||
}
|
||
var body map[string]interface{}
|
||
if err := json.Unmarshal(raw, &body); err != nil {
|
||
c.cmsJSONErr(400, 400, "参数错误")
|
||
return
|
||
}
|
||
|
||
fields := map[string]interface{}{}
|
||
allowed := []string{"sitename", "logo", "logow", "ico", "description", "copyright", "companyname", "icp", "companyintroduction"}
|
||
for _, k := range allowed {
|
||
if v, ok := body[k]; ok {
|
||
fields[k] = strings.TrimSpace(toString(v))
|
||
}
|
||
}
|
||
if len(fields) == 0 {
|
||
c.cmsJSONErr(400, 400, "没有可保存的字段")
|
||
return
|
||
}
|
||
fields["update_time"] = time.Now()
|
||
|
||
cnt, err := models.Orm.QueryTable(new(models.TenantSiteSetting)).
|
||
Filter("tid", tid).
|
||
Filter("delete_time__isnull", true).
|
||
Count()
|
||
if err != nil {
|
||
c.cmsJSONErr(500, 500, "保存失败")
|
||
return
|
||
}
|
||
if cnt == 0 {
|
||
now := time.Now()
|
||
row := &models.TenantSiteSetting{Tid: tid, CreateTime: now, UpdateTime: &now}
|
||
if v, ok := fields["sitename"]; ok {
|
||
row.Sitename = v.(string)
|
||
}
|
||
if v, ok := fields["logo"]; ok {
|
||
row.Logo = v.(string)
|
||
}
|
||
if v, ok := fields["logow"]; ok {
|
||
row.Logow = v.(string)
|
||
}
|
||
if v, ok := fields["ico"]; ok {
|
||
row.Ico = v.(string)
|
||
}
|
||
if v, ok := fields["description"]; ok {
|
||
row.Description = v.(string)
|
||
}
|
||
if v, ok := fields["copyright"]; ok {
|
||
row.Copyright = v.(string)
|
||
}
|
||
if v, ok := fields["companyname"]; ok {
|
||
row.Companyname = v.(string)
|
||
}
|
||
if v, ok := fields["icp"]; ok {
|
||
row.Icp = v.(string)
|
||
}
|
||
if v, ok := fields["companyintroduction"]; ok {
|
||
row.Companyintroduction = v.(string)
|
||
}
|
||
if _, err := models.Orm.Insert(row); err != nil {
|
||
c.cmsJSONErr(500, 500, "保存失败")
|
||
return
|
||
}
|
||
} else {
|
||
if _, err := models.Orm.QueryTable(new(models.TenantSiteSetting)).
|
||
Filter("tid", tid).
|
||
Filter("delete_time__isnull", true).
|
||
Update(fields); err != nil {
|
||
c.cmsJSONErr(500, 500, "保存失败")
|
||
return
|
||
}
|
||
}
|
||
|
||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"}
|
||
_ = c.ServeJSON()
|
||
}
|
||
|
||
// toString 宽松地把 JSON 值转成字符串
|
||
func toString(v interface{}) string {
|
||
if v == nil {
|
||
return ""
|
||
}
|
||
if s, ok := v.(string); ok {
|
||
return s
|
||
}
|
||
return ""
|
||
}
|