更新website后端
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"server/models"
|
||||
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
)
|
||||
|
||||
// PlatformWebsiteSettingController 平台官网站点设置(平台端专用)
|
||||
// 对应前端 views/website/setting/index.vue:
|
||||
// - GET /platform/website/setting/info
|
||||
// - POST /platform/website/setting/save
|
||||
//
|
||||
// 数据落在 yz_platform_site_setting(通用键值结构:title/value/tag/remark)。
|
||||
// 该表完全数据驱动:表里插一行就是一个新的设置项,模板即可用 {yz:tag} 输出,
|
||||
// platform 端界面也会自动出现该项,无需改动后端代码。
|
||||
type PlatformWebsiteSettingController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
func (c *PlatformWebsiteSettingController) jsonErr(httpStatus, bizCode int, msg string) {
|
||||
c.Ctx.Output.SetStatus(httpStatus)
|
||||
c.Data["json"] = map[string]any{"code": bizCode, "msg": msg}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// Info GET /platform/website/setting/info
|
||||
// 直接把表里所有设置项返回,前端按行渲染(新增设置项无需改代码):
|
||||
// data.list = [{ id, title, value, tag, remark }]
|
||||
// data.values = { "sitename": "云泽科技", ... }(key 即标签名,方便直接使用)
|
||||
func (c *PlatformWebsiteSettingController) Info() {
|
||||
if _, err := requirePlatform(&c.Controller); err != nil {
|
||||
c.jsonErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
rows := models.GetPlatformSiteSettingRows()
|
||||
list := make([]map[string]any, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
list = append(list, map[string]any{
|
||||
"id": r.ID,
|
||||
"title": r.Title,
|
||||
"value": r.Value,
|
||||
"tag": r.Tag,
|
||||
"remark": r.Remark,
|
||||
})
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]any{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": map[string]any{
|
||||
"list": list,
|
||||
"values": models.GetPlatformSiteTagMap(),
|
||||
},
|
||||
}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// Save POST /platform/website/setting/save
|
||||
// 入参 { "sitename": "云泽科技", "subtitle": "...", ... },key 为标签名,按标签逐项 upsert。
|
||||
// 通用:表里已存在或新增的任意合法标签都可写入,不需要登记。
|
||||
func (c *PlatformWebsiteSettingController) Save() {
|
||||
if _, err := requirePlatform(&c.Controller); err != nil {
|
||||
c.jsonErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
raw, err := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err != nil {
|
||||
c.jsonErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
var payload map[string]string
|
||||
if uerr := json.Unmarshal(raw, &payload); uerr != nil {
|
||||
c.jsonErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
if len(payload) == 0 {
|
||||
c.jsonErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
if strings.TrimSpace(payload["sitename"]) == "" {
|
||||
c.jsonErr(400, 400, "站点名称不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
// 统一去掉首尾空格,避免模板里输出多余空白
|
||||
values := make(map[string]string, len(payload))
|
||||
for k, v := range payload {
|
||||
values[k] = strings.TrimSpace(v)
|
||||
}
|
||||
|
||||
if err = models.SavePlatformSiteSettings(values); err != nil {
|
||||
c.jsonErr(500, 500, "保存失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]any{"code": 200, "msg": "保存成功"}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
@@ -291,6 +291,21 @@ func (c *TenantSiteController) NewsDetail() {
|
||||
})
|
||||
}
|
||||
|
||||
// SiteSetting GET /site/setting 官网前台站点设置(公开接口,无需登录)
|
||||
// 按访问域名识别租户,返回 标签名 -> 值,数据来自 yz_platform_site_setting,与模板标签同源。
|
||||
// 完全数据驱动:表里有什么 tag,这里就返回什么标签,新增设置项无需改代码。
|
||||
// 供 SPA 官网(website/)读取,使 SPA 与 themes 直出模板的站点信息保持一致。
|
||||
func (c *TenantSiteController) SiteSetting() {
|
||||
out := map[string]string{}
|
||||
if tid, ok := c.resolveTid(); ok {
|
||||
for k, v := range tagengine.SiteGlobals(tid) {
|
||||
out[k] = v
|
||||
}
|
||||
}
|
||||
c.Data["json"] = map[string]any{"code": 200, "msg": "success", "data": out}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// Page GET /page/:path 单页
|
||||
func (c *TenantSiteController) Page() {
|
||||
path := strings.TrimSpace(c.GetString(":path"))
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
-- 平台官网站点设置表 yz_platform_site_setting(通用键值结构)
|
||||
-- 一行一个设置项:title 名称 / value 值 / tag 标签名 / remark 说明。
|
||||
-- 模板里用 {yz:tag} 输出对应 value;新增设置项只需插一行,无需加字段。
|
||||
-- 服务端启动时会自动建表并补齐默认项(models.EnsurePlatformSiteSettingTable)。
|
||||
-- 已登记的默认设置项:
|
||||
-- sitename 站点名称
|
||||
-- subtitle 副标题
|
||||
-- logo 深色 Logo(浅色背景)
|
||||
-- logow 浅色 Logo(深色背景)
|
||||
-- icp ICP 备案号
|
||||
-- policeicp 公安备案号
|
||||
-- copyright 版权信息
|
||||
-- privacy 隐私政策链接
|
||||
-- terms 使用条款链接
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `yz_platform_site_setting` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键ID',
|
||||
`title` varchar(128) NOT NULL DEFAULT '' COMMENT '设置项名称',
|
||||
`value` text COMMENT '设置项的值',
|
||||
`tag` varchar(64) NOT NULL DEFAULT '' COMMENT '标签名,模板中写作 {yz:tag}',
|
||||
`remark` varchar(255) NOT NULL DEFAULT '' COMMENT '备注/填写说明',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`delete_time` datetime DEFAULT NULL COMMENT '删除时间(软删除)',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_tag` (`tag`, `delete_time`),
|
||||
KEY `idx_delete_time` (`delete_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='平台官网站点设置表';
|
||||
@@ -0,0 +1,67 @@
|
||||
-- 平台官网站点设置 - 初始化/调整数据(yz_platform_site_setting,通用键值结构)
|
||||
--
|
||||
-- 前提:表已按 create_platform_site_setting.sql 建成通用结构(title/value/tag/remark)。
|
||||
--
|
||||
-- remark 可选标记(用 | 分隔,未识别片段作为说明文字展示):
|
||||
-- group:分组名 决定 platform 端界面落在哪个 tab(缺省「基本设置」)
|
||||
-- type:控件类型 text(默认) / textarea / image(图片上传)
|
||||
--
|
||||
-- 当前分组:基本设置 / 公司信息 / 备案信息 / 政策条款
|
||||
-- value 为建议初始值,请按实际情况修改(备案号请替换为真实备案号)。
|
||||
--
|
||||
-- ============================================================
|
||||
-- PART 1:全新环境(表中无数据)—— 直接执行
|
||||
-- ============================================================
|
||||
INSERT INTO `yz_platform_site_setting`
|
||||
(`title`, `value`, `tag`, `remark`, `create_time`, `update_time`)
|
||||
VALUES
|
||||
-- 基本设置
|
||||
('站点名称', '云泽科技', 'sitename', '常用于 <title> 和页头 | group:基本设置', NOW(), NOW()),
|
||||
('副标题', 'YUNZER TECH', 'subtitle', '站点名称下方的补充说明,常用于页头/首页 | group:基本设置', NOW(), NOW()),
|
||||
('Logo(深色)', '', 'logo', '深色 Logo,用于浅色背景(如页头) | group:基本设置 | type:image', NOW(), NOW()),
|
||||
('Logo(浅色)', '', 'logow', '浅色 Logo,用于深色背景(如页脚) | group:基本设置 | type:image', NOW(), NOW()),
|
||||
-- 公司信息
|
||||
('公司名称', '云泽科技有限公司', 'companyname', '公司全称,常用于页脚与「关于我们」 | group:公司信息', NOW(), NOW()),
|
||||
('联系电话', '400-000-0000', 'phone', '服务热线,常用于页头与页脚 | group:公司信息', NOW(), NOW()),
|
||||
('电子邮箱', 'contact@yunzer.com', 'email', '商务合作邮箱,可配合 mailto: 使用 | group:公司信息', NOW(), NOW()),
|
||||
('公司地址', '北京市海淀区中关村软件园 25 号楼', 'address', '公司所在地址,展示于页脚/联系页面 | group:公司信息', NOW(), NOW()),
|
||||
('工作时间', '工作日 9:00 - 18:00', 'worktime', '如:工作日 9:00 - 18:00 | group:公司信息', NOW(), NOW()),
|
||||
-- 备案信息
|
||||
('ICP 备案号', '京ICP备00000000号-1', 'icp', '请替换为真实备案号,常用于页脚 | group:备案信息', NOW(), NOW()),
|
||||
('公安备案', '京公网安备11010800000000号', 'policeicp', '请替换为真实公安备案号,常用于页脚 | group:备案信息', NOW(), NOW()),
|
||||
('版权信息', 'COPYRIGHT (C) 2026 YUNZER TECHNOLOGY CO., LTD. ALL RIGHTS RESERVED.', 'copyright', '常用于页脚 | group:备案信息', NOW(), NOW()),
|
||||
-- 政策条款
|
||||
('隐私政策', '/policy/privacy', 'privacy', '隐私政策页面链接 | group:政策条款', NOW(), NOW()),
|
||||
('使用条款', '/policy/terms', 'terms', '使用条款页面链接 | group:政策条款', NOW(), NOW());
|
||||
|
||||
-- ============================================================
|
||||
-- PART 2:已有数据(表里有旧的 9 项)—— 只做增量调整,可重复执行
|
||||
-- ============================================================
|
||||
|
||||
-- 2.1 隐私政策 / 使用条款 归入「政策条款」tab
|
||||
UPDATE `yz_platform_site_setting`
|
||||
SET `remark` = '隐私政策页面链接 | group:政策条款', `update_time` = NOW()
|
||||
WHERE `tag` = 'privacy' AND `delete_time` IS NULL;
|
||||
|
||||
UPDATE `yz_platform_site_setting`
|
||||
SET `remark` = '使用条款页面链接 | group:政策条款', `update_time` = NOW()
|
||||
WHERE `tag` = 'terms' AND `delete_time` IS NULL;
|
||||
|
||||
-- 2.2 新增「公司信息」分组的 5 个设置项(已存在同 tag 的记录不会重复插入)
|
||||
INSERT INTO `yz_platform_site_setting`
|
||||
(`title`, `value`, `tag`, `remark`, `create_time`, `update_time`)
|
||||
SELECT v.`title`, v.`value`, v.`tag`, v.`remark`, NOW(), NOW()
|
||||
FROM (
|
||||
SELECT '公司名称' AS `title`, '云泽科技有限公司' AS `value`, 'companyname' AS `tag`,
|
||||
'公司全称,常用于页脚与「关于我们」 | group:公司信息' AS `remark`
|
||||
UNION ALL SELECT '联系电话', '400-000-0000', 'phone', '服务热线,常用于页头与页脚 | group:公司信息'
|
||||
UNION ALL SELECT '电子邮箱', 'contact@yunzer.com', 'email', '商务合作邮箱,可配合 mailto: 使用 | group:公司信息'
|
||||
UNION ALL SELECT '公司地址', '北京市海淀区中关村软件园 25 号楼', 'address', '公司所在地址,展示于页脚/联系页面 | group:公司信息'
|
||||
UNION ALL SELECT '工作时间', '工作日 9:00 - 18:00', 'worktime', '如:工作日 9:00 - 18:00 | group:公司信息'
|
||||
) v
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM `yz_platform_site_setting` s
|
||||
WHERE s.`tag` = v.`tag` AND s.`delete_time` IS NULL
|
||||
);
|
||||
|
||||
-- 校验:SELECT tag, title, value, remark FROM yz_platform_site_setting WHERE delete_time IS NULL ORDER BY id;
|
||||
@@ -57,6 +57,7 @@ func Init(_ string) {
|
||||
new(SystemModules),
|
||||
new(StorageConfig),
|
||||
new(TenantSiteSetting),
|
||||
new(PlatformSiteSetting),
|
||||
new(ComplaintCategory),
|
||||
new(PlatformComplaint),
|
||||
new(SystemSoftwareUpgrade),
|
||||
@@ -157,6 +158,7 @@ func Init(_ string) {
|
||||
EnsureCrmProductDevColumns()
|
||||
EnsureCrmUnitTable()
|
||||
EnsureErpCompanyContactMobilesColumn()
|
||||
EnsurePlatformSiteSettingTable()
|
||||
}
|
||||
|
||||
// EnsureCrmPaybackTable 回款计划表建表(CREATE TABLE IF NOT EXISTS,可重复执行;
|
||||
|
||||
@@ -0,0 +1,289 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PlatformSiteSetting 平台官网站点设置表 yz_platform_site_setting(通用键值结构)
|
||||
//
|
||||
// 一行一个设置项:title 名称 / value 值 / tag 标签名 / remark 说明。
|
||||
// 模板里用 {yz:tag} 即可输出对应 value,例如 tag=sitename → {yz:sitename}。
|
||||
// 新增设置项只需在 PlatformSiteSettingDefs 里登记一条(或直接在表里插一行),
|
||||
// 无需再加表字段,因此该表对所有站点级设置通用。
|
||||
type PlatformSiteSetting struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
Title string `orm:"column(title);size(128);default('')" json:"title"` // 设置项名称
|
||||
Value string `orm:"column(value);type(text);null" json:"value"` // 设置项的值
|
||||
Tag string `orm:"column(tag);size(64);default('')" json:"tag"` // 标签名,模板中写作 {yz:tag}
|
||||
Remark string `orm:"column(remark);size(255);default('')" json:"remark"` // 备注/填写说明
|
||||
|
||||
CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add;null" json:"create_time"`
|
||||
UpdateTime *time.Time `orm:"column(update_time);type(datetime);auto_now;null" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||
}
|
||||
|
||||
func (m *PlatformSiteSetting) TableName() string {
|
||||
return "yz_platform_site_setting"
|
||||
}
|
||||
|
||||
// PlatformSiteSettingDef 站点设置项定义(顺序即 platform 端界面展示顺序)
|
||||
type PlatformSiteSettingDef struct {
|
||||
Tag string // 标签名,模板中 {yz:tag}
|
||||
Title string // 设置项名称
|
||||
Remark string // 填写说明
|
||||
}
|
||||
|
||||
// PlatformSiteSettingDefs 站点设置项定义清单(仅作初始化种子,不限制可新增的设置项)
|
||||
//
|
||||
// 该表现在完全由数据驱动:直接在表里插一行即成为一个新设置项,无需改动任何代码:
|
||||
// - tag 即模板标签名,表里写了什么标签,模板就能用 {yz:tag}
|
||||
// - remark 支持可选标记,用 | 分隔,未识别的片段当作说明文字展示:
|
||||
// group:分组名 决定 platform 端界面落在哪个 tab(缺省归入「基本设置」)
|
||||
// type:控件类型 text(默认,单行输入) / textarea(多行) / image(图片上传)
|
||||
// 例:深色 Logo,用于浅色背景 | group:基本设置 | type:image
|
||||
var PlatformSiteSettingDefs = []PlatformSiteSettingDef{
|
||||
// 基本设置
|
||||
{Tag: "sitename", Title: "站点名称", Remark: "常用于 <title> 和页头 | group:基本设置"},
|
||||
{Tag: "subtitle", Title: "副标题", Remark: "站点名称下方的补充说明,常用于页头/首页 | group:基本设置"},
|
||||
{Tag: "logo", Title: "Logo(深色)", Remark: "深色 Logo,用于浅色背景(如页头) | group:基本设置 | type:image"},
|
||||
{Tag: "logow", Title: "Logo(浅色)", Remark: "浅色 Logo,用于深色背景(如页脚) | group:基本设置 | type:image"},
|
||||
|
||||
// 公司信息
|
||||
{Tag: "companyname", Title: "公司名称", Remark: "公司全称,常用于页脚与「关于我们」 | group:公司信息"},
|
||||
{Tag: "phone", Title: "联系电话", Remark: "服务热线,常用于页头与页脚 | group:公司信息"},
|
||||
{Tag: "email", Title: "电子邮箱", Remark: "商务合作邮箱,可配合 mailto: 使用 | group:公司信息"},
|
||||
{Tag: "address", Title: "公司地址", Remark: "公司所在地址,展示于页脚/联系页面 | group:公司信息"},
|
||||
{Tag: "worktime", Title: "工作时间", Remark: "如:工作日 9:00 - 18:00 | group:公司信息"},
|
||||
|
||||
// 备案信息
|
||||
{Tag: "icp", Title: "ICP 备案号", Remark: "如:苏ICP备2026000000号-1,常用于页脚 | group:备案信息"},
|
||||
{Tag: "policeicp", Title: "公安备案", Remark: "如:苏公网安备32010000000000号,常用于页脚 | group:备案信息"},
|
||||
{Tag: "copyright", Title: "版权信息", Remark: "如:© 2026 公司名称 版权所有 | group:备案信息"},
|
||||
|
||||
// 政策条款
|
||||
{Tag: "privacy", Title: "隐私政策", Remark: "隐私政策链接,如 /page/privacy | group:政策条款"},
|
||||
{Tag: "terms", Title: "使用条款", Remark: "使用条款链接,如 /page/terms | group:政策条款"},
|
||||
}
|
||||
|
||||
// rePlatformSiteTag 标签名校验:与 tagengine 全局标签正则保持一致
|
||||
var rePlatformSiteTag = regexp.MustCompile(`^[a-zA-Z_]\w*$`)
|
||||
|
||||
// platformSiteSettingDef 按 tag 取定义;未登记时返回空定义
|
||||
func platformSiteSettingDef(tag string) PlatformSiteSettingDef {
|
||||
for _, d := range PlatformSiteSettingDefs {
|
||||
if d.Tag == tag {
|
||||
return d
|
||||
}
|
||||
}
|
||||
return PlatformSiteSettingDef{Tag: tag, Title: tag, Remark: ""}
|
||||
}
|
||||
|
||||
// GetPlatformSiteTagMap 返回 标签名 -> 值。
|
||||
// 已登记的定义项必定出现在返回结果中(未配置时为 ""),保证调用方不会缺 key。
|
||||
func GetPlatformSiteTagMap() map[string]string {
|
||||
out := make(map[string]string, len(PlatformSiteSettingDefs))
|
||||
for _, d := range PlatformSiteSettingDefs {
|
||||
out[d.Tag] = ""
|
||||
}
|
||||
var rows []PlatformSiteSetting
|
||||
if _, err := Orm.QueryTable(new(PlatformSiteSetting)).
|
||||
Filter("delete_time__isnull", true).
|
||||
All(&rows); err != nil {
|
||||
return out
|
||||
}
|
||||
for _, r := range rows {
|
||||
if r.Tag != "" {
|
||||
out[r.Tag] = r.Value
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// SavePlatformSiteSettings 按标签名批量写入设置项(tag -> value)。
|
||||
// 已存在则更新 value,不存在则按定义插入;标签名非法的项直接忽略。
|
||||
func SavePlatformSiteSettings(values map[string]string) error {
|
||||
now := time.Now()
|
||||
for tag, val := range values {
|
||||
tag = strings.TrimSpace(tag)
|
||||
if !rePlatformSiteTag.MatchString(tag) {
|
||||
continue
|
||||
}
|
||||
|
||||
cnt, err := Orm.QueryTable(new(PlatformSiteSetting)).
|
||||
Filter("tag", tag).
|
||||
Filter("delete_time__isnull", true).
|
||||
Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cnt == 0 {
|
||||
def := platformSiteSettingDef(tag)
|
||||
if _, err = Orm.Insert(&PlatformSiteSetting{
|
||||
Title: def.Title,
|
||||
Tag: tag,
|
||||
Value: val,
|
||||
Remark: def.Remark,
|
||||
UpdateTime: &now,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err = Orm.QueryTable(new(PlatformSiteSetting)).
|
||||
Filter("tag", tag).
|
||||
Filter("delete_time__isnull", true).
|
||||
Update(map[string]any{"value": val, "update_time": now}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPlatformSiteSettingRows 返回库中实际存在的设置行(不做默认值补齐)
|
||||
func GetPlatformSiteSettingRows() []PlatformSiteSetting {
|
||||
var rows []PlatformSiteSetting
|
||||
if _, err := Orm.QueryTable(new(PlatformSiteSetting)).
|
||||
Filter("delete_time__isnull", true).
|
||||
OrderBy("id").
|
||||
All(&rows); err != nil {
|
||||
return nil
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
// EnsurePlatformSiteSettingDefaults 补齐已登记但未入库的设置项(缺失时插入空值行)
|
||||
// 注意:须用库中实际行判断,不能用 GetPlatformSiteTagMap —— 它预置了所有默认 tag 的 key,
|
||||
// 会导致「已存在」判断永远成立、一行都插不进去。
|
||||
func EnsurePlatformSiteSettingDefaults() {
|
||||
exist := map[string]bool{}
|
||||
for _, r := range GetPlatformSiteSettingRows() {
|
||||
if r.Tag != "" {
|
||||
exist[r.Tag] = true
|
||||
}
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
for _, d := range PlatformSiteSettingDefs {
|
||||
if exist[d.Tag] {
|
||||
continue
|
||||
}
|
||||
if _, err := Orm.Insert(&PlatformSiteSetting{
|
||||
Title: d.Title,
|
||||
Tag: d.Tag,
|
||||
Value: "",
|
||||
Remark: d.Remark,
|
||||
UpdateTime: &now,
|
||||
}); err != nil {
|
||||
continue
|
||||
}
|
||||
exist[d.Tag] = true
|
||||
}
|
||||
}
|
||||
|
||||
// EnsurePlatformSiteSettingTable 建表(通用键值结构,CREATE TABLE IF NOT EXISTS,可重复执行)。
|
||||
// 若检测到旧宽表结构(含 logo_dark 列),先把历史数据迁移为键值行,再补齐默认项。
|
||||
func EnsurePlatformSiteSettingTable() {
|
||||
table := new(PlatformSiteSetting).TableName()
|
||||
sql := `CREATE TABLE IF NOT EXISTS ` + table + ` (
|
||||
id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键ID',
|
||||
title varchar(128) NOT NULL DEFAULT '' COMMENT '设置项名称',
|
||||
value text COMMENT '设置项的值',
|
||||
tag varchar(64) NOT NULL DEFAULT '' COMMENT '标签名,模板中写作 {yz:tag}',
|
||||
remark varchar(255) NOT NULL DEFAULT '' COMMENT '备注/填写说明',
|
||||
create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
update_time datetime DEFAULT NULL COMMENT '更新时间',
|
||||
delete_time datetime DEFAULT NULL COMMENT '删除时间(软删除)',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_tag (tag, delete_time),
|
||||
KEY idx_delete_time (delete_time)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='平台官网站点设置表(通用键值结构)'`
|
||||
if _, err := Orm.Raw(sql).Exec(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 旧宽表升级场景:CREATE TABLE IF NOT EXISTS 不会改已有表,需补齐通用结构列
|
||||
ensurePlatformSiteSettingColumns(table)
|
||||
|
||||
if hasTableColumn(table, "logo_dark") {
|
||||
migrateLegacyPlatformSiteSetting(table)
|
||||
}
|
||||
EnsurePlatformSiteSettingDefaults()
|
||||
}
|
||||
|
||||
// ensurePlatformSiteSettingColumns 补齐通用结构列(列缺失时新增,已存在则忽略)
|
||||
func ensurePlatformSiteSettingColumns(table string) {
|
||||
cols := []struct{ name, def string }{
|
||||
{"title", "varchar(128) NOT NULL DEFAULT '' COMMENT '设置项名称'"},
|
||||
{"value", "text COMMENT '设置项的值'"},
|
||||
{"tag", "varchar(64) NOT NULL DEFAULT '' COMMENT '标签名,模板中写作 {yz:tag}'"},
|
||||
{"remark", "varchar(255) NOT NULL DEFAULT '' COMMENT '备注/填写说明'"},
|
||||
}
|
||||
for _, c := range cols {
|
||||
if hasTableColumn(table, c.name) {
|
||||
continue
|
||||
}
|
||||
_, _ = Orm.Raw("ALTER TABLE " + table + " ADD COLUMN " + c.def).Exec()
|
||||
}
|
||||
}
|
||||
|
||||
// hasTableColumn 判断表是否存在指定列(表不存在或查询异常时返回 false)
|
||||
func hasTableColumn(table, column string) bool {
|
||||
var cnt int64
|
||||
err := Orm.Raw(
|
||||
"SELECT COUNT(1) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?",
|
||||
table, column,
|
||||
).QueryRow(&cnt)
|
||||
return err == nil && cnt > 0
|
||||
}
|
||||
|
||||
// migrateLegacyPlatformSiteSetting 旧宽表结构迁移:
|
||||
// 旧列为 sitename/logo_dark/logo_light/icp/police_icp/copyright/privacy_policy/terms_of_service,
|
||||
// 迁移时仅在目标标签行为空的情况下写入,避免覆盖新配置。
|
||||
func migrateLegacyPlatformSiteSetting(table string) {
|
||||
type legacyRow struct {
|
||||
Sitename string
|
||||
LogoDark string
|
||||
LogoLight string
|
||||
Icp string
|
||||
PoliceIcp string
|
||||
Copyright string
|
||||
Privacy string
|
||||
Terms string
|
||||
Companyname string
|
||||
}
|
||||
var rows []legacyRow
|
||||
if _, err := Orm.Raw(
|
||||
"SELECT IFNULL(sitename,'') sitename, IFNULL(logo_dark,'') logo_dark, IFNULL(logo_light,'') logo_light," +
|
||||
" IFNULL(icp,'') icp, IFNULL(police_icp,'') police_icp, IFNULL(copyright,'') copyright," +
|
||||
" IFNULL(privacy_policy,'') privacy_policy, IFNULL(terms_of_service,'') terms_of_service" +
|
||||
" FROM " + table + " WHERE delete_time IS NULL ORDER BY id LIMIT 1").QueryRows(&rows); err != nil {
|
||||
return
|
||||
}
|
||||
if len(rows) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
old := rows[0]
|
||||
current := GetPlatformSiteTagMap()
|
||||
pending := map[string]string{
|
||||
"sitename": old.Sitename,
|
||||
"logo": old.LogoDark,
|
||||
"logow": old.LogoLight,
|
||||
"icp": old.Icp,
|
||||
"policeicp": old.PoliceIcp,
|
||||
"copyright": old.Copyright,
|
||||
"privacy": old.Privacy,
|
||||
"terms": old.Terms,
|
||||
}
|
||||
for tag, val := range pending {
|
||||
if val == "" || strings.TrimSpace(current[tag]) != "" {
|
||||
continue
|
||||
}
|
||||
current[tag] = val
|
||||
}
|
||||
_ = SavePlatformSiteSettings(pending)
|
||||
}
|
||||
@@ -213,6 +213,19 @@ func renderSelfClose(content string, ctx *RenderCtx) (string, error) {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// SiteGlobals 返回站点级全局标签值(key 即标签名),与模板内 {yz:xxx} 完全同源。
|
||||
// 供前台公开接口(GET /site/setting)输出给 SPA 官网使用,保证 SPA 与 themes 直出模板展示一致。
|
||||
func SiteGlobals(tid uint64) map[string]string {
|
||||
if globalProvider == nil {
|
||||
return map[string]string{}
|
||||
}
|
||||
vals, err := globalProvider(tid, &RenderCtx{Tid: tid})
|
||||
if err != nil || vals == nil {
|
||||
return map[string]string{}
|
||||
}
|
||||
return vals
|
||||
}
|
||||
|
||||
// renderGlobals 替换全局标签;未提供的标签置空
|
||||
func renderGlobals(content string, ctx *RenderCtx) string {
|
||||
var vals map[string]string
|
||||
|
||||
@@ -37,26 +37,30 @@ func TagDocs() []TagMeta {
|
||||
Name: "站点基本信息",
|
||||
Category: "global",
|
||||
Syntax: "{yz:sitename}、{yz:logo}、{yz:ico} …直接写在模板任意位置",
|
||||
Desc: "取自当前租户的站点设置(backend 端 - 站点设置 - 基本信息)。直接替换为文本或地址,无需闭合;未配置的项输出空字符串。点击行末复制按钮可复制标签。",
|
||||
Desc: "取自当前站点的站点设置。租户站点来自「站点设置 - 基本信息」(backend 端);平台官网(平台主域名)优先取「官网管理 - 站点设置」(platform 端),未填写时回退基本信息。直接替换为文本或地址,无需闭合;未配置的项输出空字符串。点击行末复制按钮可复制标签。",
|
||||
Params: nil,
|
||||
Fields: []string{
|
||||
"sitename 站点名称,常用于 <title> 和页头 来源:基本信息-站点名称",
|
||||
"logo 彩色 Logo 图片地址,适合浅色背景 来源:基本信息-站点LOGO",
|
||||
"logow 白色 Logo 图片地址,适合深色背景/页脚 来源:基本信息-白色LOGO",
|
||||
"sitename 站点名称,常用于 <title> 和页头 来源:站点设置-站点名称",
|
||||
"subtitle 副标题,站点名称下方的补充说明 来源:官网管理-站点设置-副标题",
|
||||
"logo 深色 Logo 图片地址,适合浅色背景 来源:站点设置-深色LOGO",
|
||||
"logow 浅色 Logo 图片地址,适合深色背景/页脚 来源:站点设置-浅色LOGO",
|
||||
"ico 站点图标地址,用于 <link rel=\"icon\"> 来源:基本信息-站点图标",
|
||||
"companyname 公司全称 来源:基本信息-公司名称",
|
||||
"companyintroduction 企业介绍(富文本HTML,直接用 div 承接) 来源:基本信息-企业介绍",
|
||||
"description 站点简介,可用于 meta description 来源:基本信息-站点描述",
|
||||
"copyright 版权信息,常用于页脚 来源:基本信息-版权信息",
|
||||
"icp ICP 备案号,常用于页脚 来源:基本信息-ICP备案",
|
||||
"copyright 版权信息,常用于页脚 来源:站点设置-版权信息",
|
||||
"icp ICP 备案号,常用于页脚 来源:站点设置-ICP备案",
|
||||
"policeicp 公安备案号/公安网备标识,常用于页脚 来源:官网管理-站点设置-公安备案",
|
||||
"privacy 隐私政策链接地址,如 /page/privacy 来源:官网管理-站点设置-隐私政策",
|
||||
"terms 使用条款链接地址,如 /page/terms 来源:官网管理-站点设置-使用条款",
|
||||
},
|
||||
Example: "<head>\n <title>{yz:sitename}</title>\n <link rel=\"icon\" href=\"{yz:ico}\">\n <meta name=\"description\" content=\"{yz:description}\">\n</head>\n<img src=\"{yz:logo}\" alt=\"{yz:sitename}\">\n<div class=\"about\">{yz:companyintroduction}</div>\n<footer>\n <p>{yz:copyright}</p>\n <p>{yz:icp}</p>\n</footer>",
|
||||
Example: "<head>\n <title>{yz:sitename}</title>\n <link rel=\"icon\" href=\"{yz:ico}\">\n <meta name=\"description\" content=\"{yz:description}\">\n</head>\n<img src=\"{yz:logo}\" alt=\"{yz:sitename}\">\n<div class=\"about\">{yz:companyintroduction}</div>\n<footer>\n <p>{yz:copyright}</p>\n <p>{yz:icp}</p>\n <p>{yz:policeicp}</p>\n <p>\n <a href=\"{yz:privacy}\">隐私政策</a>\n <a href=\"{yz:terms}\">使用条款</a>\n </p>\n</footer>",
|
||||
},
|
||||
{
|
||||
Name: "联系方式",
|
||||
Category: "global",
|
||||
Syntax: "{yz:phone}、{yz:email}、{yz:address}、{yz:worktime}",
|
||||
Desc: "取自租户的公司信息(backend 端 - 公司信息),常用于页头联系栏和页脚。直接替换为文本,无需闭合。",
|
||||
Desc: "取自公司信息。平台官网优先取「官网管理 - 站点设置 - 公司信息」,未填写时回退租户的公司信息(backend 端 - 公司信息)。直接替换为文本,无需闭合。",
|
||||
Params: nil,
|
||||
Fields: []string{
|
||||
"phone 联系电话,别名 {yz:tel}、{yz:mobile} 三者等价 来源:公司信息-联系电话",
|
||||
|
||||
@@ -68,6 +68,7 @@ func globalSiteInfo(tid uint64, ctx *RenderCtx) (map[string]string, error) {
|
||||
"companyintroduction": "",
|
||||
"phone": "", "tel": "", "mobile": "", "email": "", "address": "", "worktime": "",
|
||||
"keywords": "", "seotitle": "", "seodescription": "",
|
||||
"policeicp": "", "privacy": "", "terms": "",
|
||||
"aboutus": "/page/about", "contact": "/page/contact", "news": "/news", "team": "/page/team",
|
||||
}
|
||||
var row models.TenantSiteSetting
|
||||
@@ -115,6 +116,21 @@ func globalSiteInfo(tid uint64, ctx *RenderCtx) (map[string]string, error) {
|
||||
out["seotitle"] = vals["seoTitle"]
|
||||
out["seodescription"] = vals["seoDescription"]
|
||||
}
|
||||
|
||||
// 平台官网(tid 1)额外读取 platform 端「官网管理 - 站点设置」
|
||||
// (yz_platform_site_setting,通用键值结构:tag 即标签名)。
|
||||
// 值非空时覆盖同名全局标签,为空时保证标签存在(输出空串);租户站点不受影响。
|
||||
if tid == 1 {
|
||||
for k, v := range models.GetPlatformSiteTagMap() {
|
||||
if v == "" {
|
||||
if _, ok := out[k]; !ok {
|
||||
out[k] = ""
|
||||
}
|
||||
continue
|
||||
}
|
||||
out[k] = v
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,9 @@ func Register() {
|
||||
beego.Router("/news/:id", &controllers.TenantSiteController{}, "get:NewsDetail")
|
||||
beego.Router("/page/:path", &controllers.TenantSiteController{}, "get:Page")
|
||||
|
||||
// 官网前台站点设置(公开接口,供 SPA 官网读取,字段与模板标签 {yz:xxx} 同源)
|
||||
beego.Router("/site/setting", &controllers.TenantSiteController{}, "get:SiteSetting")
|
||||
|
||||
beego.Router("/index/headmenu", &controllers.IndexPortalController{}, "get:GetHeadMenu")
|
||||
beego.Router("/index/footerdata", &controllers.IndexPortalController{}, "get:GetFooterData")
|
||||
beego.Router("/index/newscentertop4", &controllers.IndexPortalController{}, "get:GetNewsCenterTop4")
|
||||
|
||||
@@ -305,4 +305,8 @@ func Register() {
|
||||
beego.Router("/platform/template/status", &controllers.PlatformTemplateController{}, "post:Status")
|
||||
beego.Router("/platform/template/delete/:id", &controllers.PlatformTemplateController{}, "post:Delete")
|
||||
beego.Router("/platform/template/tags", &controllers.PlatformTemplateController{}, "get:Tags")
|
||||
|
||||
// 平台官网站点设置(yz_platform_site_setting)
|
||||
beego.Router("/platform/website/setting/info", &controllers.PlatformWebsiteSettingController{}, "get:Info")
|
||||
beego.Router("/platform/website/setting/save", &controllers.PlatformWebsiteSettingController{}, "post:Save")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user