优化企业网站功能
This commit is contained in:
@@ -88,12 +88,6 @@ func cmsEnsureTables(c *beego.Controller) bool {
|
||||
_ = c.ServeJSON()
|
||||
return false
|
||||
}
|
||||
if err := models.EnsureCmsArticleDefaultCategories(); err != nil {
|
||||
c.Ctx.Output.SetStatus(500)
|
||||
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "初始化文章分类失败: " + err.Error()}
|
||||
_ = c.ServeJSON()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,12 @@ func (c *BackendMenuFrontController) checkAuth() (uint64, bool) {
|
||||
_ = c.ServeJSON()
|
||||
return 0, false
|
||||
}
|
||||
// tenant_id=0 为全局默认菜单保留值,不允许任何接口以租户身份操作
|
||||
if claims.TenantId <= 0 {
|
||||
c.Data["json"] = map[string]interface{}{"code": 401, "msg": "租户ID缺失"}
|
||||
_ = c.ServeJSON()
|
||||
return 0, false
|
||||
}
|
||||
return uint64(claims.TenantId), true
|
||||
}
|
||||
|
||||
@@ -43,11 +49,15 @@ func (c *BackendMenuFrontController) List() {
|
||||
return
|
||||
}
|
||||
|
||||
// 保证全局默认导航(tenant_id=0)齐全,缺失自动补齐
|
||||
models.EnsureGlobalDefaultFrontMenus()
|
||||
|
||||
// 全局默认菜单(tenant_id=0,不可删改)+ 租户自定义菜单
|
||||
var menus []models.BackendMenuFront
|
||||
_, err := models.Orm.QueryTable("yz_backend_menu_front").
|
||||
Filter("tenant_id", tid).
|
||||
Filter("tenant_id__in", []uint64{0, tid}).
|
||||
Filter("delete_time__isnull", true).
|
||||
OrderBy("sort").
|
||||
OrderBy("sort", "id").
|
||||
All(&menus)
|
||||
|
||||
if err != nil {
|
||||
@@ -151,4 +161,4 @@ func (c *BackendMenuFrontController) Delete() {
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"}
|
||||
}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,528 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"server/models"
|
||||
"server/pkg/jwtutil"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
)
|
||||
|
||||
// BackendProductController CMS 产品管理
|
||||
type BackendProductController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
// BackendProductCategoryController CMS 产品分类管理
|
||||
type BackendProductCategoryController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
func (c *BackendProductController) cmsClaims() (*jwtutil.Claims, error) {
|
||||
return cmsBackendClaims(&c.Controller)
|
||||
}
|
||||
|
||||
func (c *BackendProductCategoryController) cmsClaims() (*jwtutil.Claims, error) {
|
||||
return cmsBackendClaims(&c.Controller)
|
||||
}
|
||||
|
||||
func (c *BackendProductController) cmsJSONErr(httpStatus, bizCode int, msg string) {
|
||||
c.Ctx.Output.SetStatus(httpStatus)
|
||||
c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *BackendProductCategoryController) cmsJSONErr(httpStatus, bizCode int, msg string) {
|
||||
c.Ctx.Output.SetStatus(httpStatus)
|
||||
c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
func cmsEnsureProductTables(c *beego.Controller) bool {
|
||||
if err := models.EnsureCmsProductTables(); err != nil {
|
||||
c.Ctx.Output.SetStatus(500)
|
||||
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "初始化产品表失败: " + err.Error()}
|
||||
_ = c.ServeJSON()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func cmsProductToMap(row models.CmsProduct) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"id": row.ID,
|
||||
"tid": row.Tid,
|
||||
"title": row.Title,
|
||||
"thumb": row.Thumb,
|
||||
"desc": row.Desc,
|
||||
"content": row.Content,
|
||||
"url": row.URL,
|
||||
"sort": row.Sort,
|
||||
"status": row.Status,
|
||||
"create_time": row.CreateTime.Format("2006-01-02 15:04:05"),
|
||||
"update_time": models.CmsFormatTime(row.UpdateTime),
|
||||
}
|
||||
}
|
||||
|
||||
func cmsProductCateToMap(row models.CmsProductCategory) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"id": row.ID,
|
||||
"tid": row.Tid,
|
||||
"title": row.Title,
|
||||
"pid": row.Pid,
|
||||
"desc": row.Desc,
|
||||
"sort": row.Sort,
|
||||
"status": row.Status,
|
||||
"create_time": row.CreateTime.Format("2006-01-02 15:04:05"),
|
||||
"update_time": models.CmsFormatTime(row.UpdateTime),
|
||||
}
|
||||
}
|
||||
|
||||
// List GET /backend/productsList
|
||||
func (c *BackendProductController) List() {
|
||||
if !cmsEnsureProductTables(&c.Controller) {
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
page, _ := c.GetInt("page", 1)
|
||||
limit, _ := c.GetInt("limit", 10)
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if limit < 1 || limit > 100 {
|
||||
limit = 10
|
||||
}
|
||||
|
||||
keyword := strings.TrimSpace(c.GetString("keyword"))
|
||||
qs := models.Orm.QueryTable(new(models.CmsProduct)).
|
||||
Filter("tid", tid).
|
||||
Filter("delete_time__isnull", true)
|
||||
if keyword != "" {
|
||||
qs = qs.Filter("title__icontains", keyword)
|
||||
}
|
||||
if status, err := c.GetInt("status", -1); err == nil && status >= 0 {
|
||||
qs = qs.Filter("status", status)
|
||||
}
|
||||
|
||||
total, _ := qs.Count()
|
||||
var rows []models.CmsProduct
|
||||
offset := (page - 1) * limit
|
||||
_, err = qs.OrderBy("sort", "-id").Limit(limit, offset).All(&rows)
|
||||
if err != nil && err != orm.ErrNoRows {
|
||||
c.cmsJSONErr(500, 500, "获取产品列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
list := make([]map[string]interface{}, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
list = append(list, cmsProductToMap(r))
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": map[string]interface{}{"list": list, "total": total},
|
||||
}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
type cmsProductPayload struct {
|
||||
Title string `json:"title"`
|
||||
Thumb string `json:"thumb"`
|
||||
Desc string `json:"desc"`
|
||||
Content string `json:"content"`
|
||||
URL string `json:"url"`
|
||||
Sort int `json:"sort"`
|
||||
Status int8 `json:"status"`
|
||||
}
|
||||
|
||||
// Create POST /backend/addProducts
|
||||
func (c *BackendProductController) Create() {
|
||||
if !cmsEnsureProductTables(&c.Controller) {
|
||||
return
|
||||
}
|
||||
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 p cmsProductPayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
c.cmsJSONErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
title := strings.TrimSpace(p.Title)
|
||||
if title == "" {
|
||||
c.cmsJSONErr(400, 400, "产品名称不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
row := models.CmsProduct{
|
||||
Tid: tid,
|
||||
Title: title,
|
||||
Thumb: strings.TrimSpace(p.Thumb),
|
||||
Desc: strings.TrimSpace(p.Desc),
|
||||
Content: p.Content,
|
||||
URL: strings.TrimSpace(p.URL),
|
||||
Sort: p.Sort,
|
||||
Status: p.Status,
|
||||
CreateTime: now,
|
||||
UpdateTime: &now,
|
||||
}
|
||||
id, err := models.Orm.Insert(&row)
|
||||
if err != nil {
|
||||
c.cmsJSONErr(500, 500, "添加失败")
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "添加成功", "data": map[string]interface{}{"id": id}}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// Update PUT /backend/editProducts/:id
|
||||
func (c *BackendProductController) Update() {
|
||||
if !cmsEnsureProductTables(&c.Controller) {
|
||||
return
|
||||
}
|
||||
claims, err := c.cmsClaims()
|
||||
if err != nil {
|
||||
c.cmsJSONErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
tid := cmsEffectiveTid(&c.Controller, claims)
|
||||
id, _ := c.GetUint64(":id")
|
||||
if id == 0 {
|
||||
c.cmsJSONErr(400, 400, "无效ID")
|
||||
return
|
||||
}
|
||||
|
||||
raw, err := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err != nil {
|
||||
c.cmsJSONErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var p cmsProductPayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
c.cmsJSONErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
title := strings.TrimSpace(p.Title)
|
||||
if title == "" {
|
||||
c.cmsJSONErr(400, 400, "产品名称不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
n, err := models.Orm.QueryTable(new(models.CmsProduct)).
|
||||
Filter("id", id).
|
||||
Filter("tid", tid).
|
||||
Filter("delete_time__isnull", true).
|
||||
Update(orm.Params{
|
||||
"title": title,
|
||||
"thumb": strings.TrimSpace(p.Thumb),
|
||||
"desc": strings.TrimSpace(p.Desc),
|
||||
"content": p.Content,
|
||||
"url": strings.TrimSpace(p.URL),
|
||||
"sort": p.Sort,
|
||||
"status": p.Status,
|
||||
"update_time": now,
|
||||
})
|
||||
if err != nil {
|
||||
c.cmsJSONErr(500, 500, "更新失败")
|
||||
return
|
||||
}
|
||||
if n == 0 {
|
||||
c.cmsJSONErr(404, 404, "产品不存在")
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// Delete DELETE /backend/deleteProducts/:id
|
||||
func (c *BackendProductController) Delete() {
|
||||
if !cmsEnsureProductTables(&c.Controller) {
|
||||
return
|
||||
}
|
||||
claims, err := c.cmsClaims()
|
||||
if err != nil {
|
||||
c.cmsJSONErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
tid := cmsEffectiveTid(&c.Controller, claims)
|
||||
id, _ := c.GetUint64(":id")
|
||||
if id == 0 {
|
||||
c.cmsJSONErr(400, 400, "无效ID")
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
n, err := models.Orm.QueryTable(new(models.CmsProduct)).
|
||||
Filter("id", id).
|
||||
Filter("tid", tid).
|
||||
Filter("delete_time__isnull", true).
|
||||
Update(orm.Params{"delete_time": now})
|
||||
if err != nil {
|
||||
c.cmsJSONErr(500, 500, "删除失败")
|
||||
return
|
||||
}
|
||||
if n == 0 {
|
||||
c.cmsJSONErr(404, 404, "产品不存在")
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// List GET /backend/productsTypesList
|
||||
func (c *BackendProductCategoryController) List() {
|
||||
if !cmsEnsureProductTables(&c.Controller) {
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
page, _ := c.GetInt("page", 1)
|
||||
limit, _ := c.GetInt("limit", 10)
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if limit < 1 || limit > 1000 {
|
||||
limit = 10
|
||||
}
|
||||
|
||||
keyword := strings.TrimSpace(c.GetString("keyword"))
|
||||
qs := models.Orm.QueryTable(new(models.CmsProductCategory)).
|
||||
Filter("tid", tid).
|
||||
Filter("delete_time__isnull", true)
|
||||
if keyword != "" {
|
||||
qs = qs.Filter("title__icontains", keyword)
|
||||
}
|
||||
|
||||
total, _ := qs.Count()
|
||||
var rows []models.CmsProductCategory
|
||||
offset := (page - 1) * limit
|
||||
_, err = qs.OrderBy("sort", "id").Limit(limit, offset).All(&rows)
|
||||
if err != nil && err != orm.ErrNoRows {
|
||||
c.cmsJSONErr(500, 500, "获取产品分类列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
list := make([]map[string]interface{}, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
list = append(list, cmsProductCateToMap(r))
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": map[string]interface{}{"list": list, "total": total},
|
||||
}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
type cmsProductCategoryPayload struct {
|
||||
Title string `json:"title"`
|
||||
Pid uint64 `json:"pid"`
|
||||
Desc string `json:"desc"`
|
||||
Sort int `json:"sort"`
|
||||
}
|
||||
|
||||
// Create POST /backend/addProductsTypes
|
||||
func (c *BackendProductCategoryController) Create() {
|
||||
if !cmsEnsureProductTables(&c.Controller) {
|
||||
return
|
||||
}
|
||||
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 p cmsProductCategoryPayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
c.cmsJSONErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
title := strings.TrimSpace(p.Title)
|
||||
if title == "" {
|
||||
c.cmsJSONErr(400, 400, "分类名称不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
row := models.CmsProductCategory{
|
||||
Tid: tid,
|
||||
Title: title,
|
||||
Pid: p.Pid,
|
||||
Desc: strings.TrimSpace(p.Desc),
|
||||
Sort: p.Sort,
|
||||
Status: 1,
|
||||
CreateTime: now,
|
||||
UpdateTime: &now,
|
||||
}
|
||||
id, err := models.Orm.Insert(&row)
|
||||
if err != nil {
|
||||
c.cmsJSONErr(500, 500, "添加失败")
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "添加成功", "data": map[string]interface{}{"id": id}}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// Update PUT /backend/editProductsTypes/:id
|
||||
func (c *BackendProductCategoryController) Update() {
|
||||
if !cmsEnsureProductTables(&c.Controller) {
|
||||
return
|
||||
}
|
||||
claims, err := c.cmsClaims()
|
||||
if err != nil {
|
||||
c.cmsJSONErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
tid := cmsEffectiveTid(&c.Controller, claims)
|
||||
id, _ := c.GetUint64(":id")
|
||||
if id == 0 {
|
||||
c.cmsJSONErr(400, 400, "无效ID")
|
||||
return
|
||||
}
|
||||
|
||||
raw, err := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err != nil {
|
||||
c.cmsJSONErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var p cmsProductCategoryPayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
c.cmsJSONErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
title := strings.TrimSpace(p.Title)
|
||||
if title == "" {
|
||||
c.cmsJSONErr(400, 400, "分类名称不能为空")
|
||||
return
|
||||
}
|
||||
if p.Pid == id {
|
||||
c.cmsJSONErr(400, 400, "父级分类不能是自己")
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
n, err := models.Orm.QueryTable(new(models.CmsProductCategory)).
|
||||
Filter("id", id).
|
||||
Filter("tid", tid).
|
||||
Filter("delete_time__isnull", true).
|
||||
Update(orm.Params{
|
||||
"title": title,
|
||||
"pid": p.Pid,
|
||||
"desc": strings.TrimSpace(p.Desc),
|
||||
"sort": p.Sort,
|
||||
"update_time": now,
|
||||
})
|
||||
if err != nil {
|
||||
c.cmsJSONErr(500, 500, "更新失败")
|
||||
return
|
||||
}
|
||||
if n == 0 {
|
||||
c.cmsJSONErr(404, 404, "分类不存在")
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// Delete DELETE /backend/deleteProductsTypes/:id
|
||||
func (c *BackendProductCategoryController) Delete() {
|
||||
if !cmsEnsureProductTables(&c.Controller) {
|
||||
return
|
||||
}
|
||||
claims, err := c.cmsClaims()
|
||||
if err != nil {
|
||||
c.cmsJSONErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
tid := cmsEffectiveTid(&c.Controller, claims)
|
||||
id, _ := c.GetUint64(":id")
|
||||
if id == 0 {
|
||||
c.cmsJSONErr(400, 400, "无效ID")
|
||||
return
|
||||
}
|
||||
|
||||
// 存在子分类时不允许删除
|
||||
childCnt, _ := models.Orm.QueryTable(new(models.CmsProductCategory)).
|
||||
Filter("tid", tid).
|
||||
Filter("pid", id).
|
||||
Filter("delete_time__isnull", true).
|
||||
Count()
|
||||
if childCnt > 0 {
|
||||
c.cmsJSONErr(400, 400, "该分类下存在子分类,请先删除子分类")
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
n, err := models.Orm.QueryTable(new(models.CmsProductCategory)).
|
||||
Filter("id", id).
|
||||
Filter("tid", tid).
|
||||
Filter("delete_time__isnull", true).
|
||||
Update(orm.Params{"delete_time": now})
|
||||
if err != nil {
|
||||
c.cmsJSONErr(500, 500, "删除失败")
|
||||
return
|
||||
}
|
||||
if n == 0 {
|
||||
c.cmsJSONErr(404, 404, "分类不存在")
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
@@ -0,0 +1,524 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"server/models"
|
||||
"server/pkg/jwtutil"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
)
|
||||
|
||||
// BackendSolutionController CMS 解决方案(特色服务)管理
|
||||
type BackendSolutionController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
// BackendSolutionCategoryController CMS 解决方案分类管理
|
||||
type BackendSolutionCategoryController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
func (c *BackendSolutionController) cmsClaims() (*jwtutil.Claims, error) {
|
||||
return cmsBackendClaims(&c.Controller)
|
||||
}
|
||||
|
||||
func (c *BackendSolutionCategoryController) cmsClaims() (*jwtutil.Claims, error) {
|
||||
return cmsBackendClaims(&c.Controller)
|
||||
}
|
||||
|
||||
func (c *BackendSolutionController) cmsJSONErr(httpStatus, bizCode int, msg string) {
|
||||
c.Ctx.Output.SetStatus(httpStatus)
|
||||
c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *BackendSolutionCategoryController) cmsJSONErr(httpStatus, bizCode int, msg string) {
|
||||
c.Ctx.Output.SetStatus(httpStatus)
|
||||
c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
func cmsEnsureSolutionTables(c *beego.Controller) bool {
|
||||
if err := models.EnsureCmsSolutionTables(); err != nil {
|
||||
c.Ctx.Output.SetStatus(500)
|
||||
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "初始化解决方案表失败: " + err.Error()}
|
||||
_ = c.ServeJSON()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func cmsSolutionToMap(row models.CmsSolution) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"id": row.ID,
|
||||
"tid": row.Tid,
|
||||
"title": row.Title,
|
||||
"thumb": row.Thumb,
|
||||
"desc": row.Desc,
|
||||
"url": row.URL,
|
||||
"sort": row.Sort,
|
||||
"status": row.Status,
|
||||
"create_time": row.CreateTime.Format("2006-01-02 15:04:05"),
|
||||
"update_time": models.CmsFormatTime(row.UpdateTime),
|
||||
}
|
||||
}
|
||||
|
||||
func cmsSolutionCateToMap(row models.CmsSolutionCategory) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"id": row.ID,
|
||||
"tid": row.Tid,
|
||||
"title": row.Title,
|
||||
"pid": row.Pid,
|
||||
"desc": row.Desc,
|
||||
"sort": row.Sort,
|
||||
"status": row.Status,
|
||||
"create_time": row.CreateTime.Format("2006-01-02 15:04:05"),
|
||||
"update_time": models.CmsFormatTime(row.UpdateTime),
|
||||
}
|
||||
}
|
||||
|
||||
// List GET /backend/servicesList
|
||||
func (c *BackendSolutionController) List() {
|
||||
if !cmsEnsureSolutionTables(&c.Controller) {
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
page, _ := c.GetInt("page", 1)
|
||||
limit, _ := c.GetInt("limit", 10)
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if limit < 1 || limit > 100 {
|
||||
limit = 10
|
||||
}
|
||||
|
||||
keyword := strings.TrimSpace(c.GetString("keyword"))
|
||||
qs := models.Orm.QueryTable(new(models.CmsSolution)).
|
||||
Filter("tid", tid).
|
||||
Filter("delete_time__isnull", true)
|
||||
if keyword != "" {
|
||||
qs = qs.Filter("title__icontains", keyword)
|
||||
}
|
||||
if status, err := c.GetInt("status", -1); err == nil && status >= 0 {
|
||||
qs = qs.Filter("status", status)
|
||||
}
|
||||
|
||||
total, _ := qs.Count()
|
||||
var rows []models.CmsSolution
|
||||
offset := (page - 1) * limit
|
||||
_, err = qs.OrderBy("sort", "-id").Limit(limit, offset).All(&rows)
|
||||
if err != nil && err != orm.ErrNoRows {
|
||||
c.cmsJSONErr(500, 500, "获取解决方案列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
list := make([]map[string]interface{}, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
list = append(list, cmsSolutionToMap(r))
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": map[string]interface{}{"list": list, "total": total},
|
||||
}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
type cmsSolutionPayload struct {
|
||||
Title string `json:"title"`
|
||||
Thumb string `json:"thumb"`
|
||||
Desc string `json:"desc"`
|
||||
URL string `json:"url"`
|
||||
Sort int `json:"sort"`
|
||||
Status int8 `json:"status"`
|
||||
}
|
||||
|
||||
// Create POST /backend/addServices
|
||||
func (c *BackendSolutionController) Create() {
|
||||
if !cmsEnsureSolutionTables(&c.Controller) {
|
||||
return
|
||||
}
|
||||
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 p cmsSolutionPayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
c.cmsJSONErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
title := strings.TrimSpace(p.Title)
|
||||
if title == "" {
|
||||
c.cmsJSONErr(400, 400, "名称不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
row := models.CmsSolution{
|
||||
Tid: tid,
|
||||
Title: title,
|
||||
Thumb: strings.TrimSpace(p.Thumb),
|
||||
Desc: strings.TrimSpace(p.Desc),
|
||||
URL: strings.TrimSpace(p.URL),
|
||||
Sort: p.Sort,
|
||||
Status: p.Status,
|
||||
CreateTime: now,
|
||||
UpdateTime: &now,
|
||||
}
|
||||
id, err := models.Orm.Insert(&row)
|
||||
if err != nil {
|
||||
c.cmsJSONErr(500, 500, "添加失败")
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "添加成功", "data": map[string]interface{}{"id": id}}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// Update PUT /backend/editServices/:id
|
||||
func (c *BackendSolutionController) Update() {
|
||||
if !cmsEnsureSolutionTables(&c.Controller) {
|
||||
return
|
||||
}
|
||||
claims, err := c.cmsClaims()
|
||||
if err != nil {
|
||||
c.cmsJSONErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
tid := cmsEffectiveTid(&c.Controller, claims)
|
||||
id, _ := c.GetUint64(":id")
|
||||
if id == 0 {
|
||||
c.cmsJSONErr(400, 400, "无效ID")
|
||||
return
|
||||
}
|
||||
|
||||
raw, err := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err != nil {
|
||||
c.cmsJSONErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var p cmsSolutionPayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
c.cmsJSONErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
title := strings.TrimSpace(p.Title)
|
||||
if title == "" {
|
||||
c.cmsJSONErr(400, 400, "名称不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
n, err := models.Orm.QueryTable(new(models.CmsSolution)).
|
||||
Filter("id", id).
|
||||
Filter("tid", tid).
|
||||
Filter("delete_time__isnull", true).
|
||||
Update(orm.Params{
|
||||
"title": title,
|
||||
"thumb": strings.TrimSpace(p.Thumb),
|
||||
"desc": strings.TrimSpace(p.Desc),
|
||||
"url": strings.TrimSpace(p.URL),
|
||||
"sort": p.Sort,
|
||||
"status": p.Status,
|
||||
"update_time": now,
|
||||
})
|
||||
if err != nil {
|
||||
c.cmsJSONErr(500, 500, "更新失败")
|
||||
return
|
||||
}
|
||||
if n == 0 {
|
||||
c.cmsJSONErr(404, 404, "记录不存在")
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// Delete DELETE /backend/deleteServices/:id
|
||||
func (c *BackendSolutionController) Delete() {
|
||||
if !cmsEnsureSolutionTables(&c.Controller) {
|
||||
return
|
||||
}
|
||||
claims, err := c.cmsClaims()
|
||||
if err != nil {
|
||||
c.cmsJSONErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
tid := cmsEffectiveTid(&c.Controller, claims)
|
||||
id, _ := c.GetUint64(":id")
|
||||
if id == 0 {
|
||||
c.cmsJSONErr(400, 400, "无效ID")
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
n, err := models.Orm.QueryTable(new(models.CmsSolution)).
|
||||
Filter("id", id).
|
||||
Filter("tid", tid).
|
||||
Filter("delete_time__isnull", true).
|
||||
Update(orm.Params{"delete_time": now})
|
||||
if err != nil {
|
||||
c.cmsJSONErr(500, 500, "删除失败")
|
||||
return
|
||||
}
|
||||
if n == 0 {
|
||||
c.cmsJSONErr(404, 404, "记录不存在")
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// List GET /backend/servicesTypesList
|
||||
func (c *BackendSolutionCategoryController) List() {
|
||||
if !cmsEnsureSolutionTables(&c.Controller) {
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
page, _ := c.GetInt("page", 1)
|
||||
limit, _ := c.GetInt("limit", 10)
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if limit < 1 || limit > 1000 {
|
||||
limit = 10
|
||||
}
|
||||
|
||||
keyword := strings.TrimSpace(c.GetString("keyword"))
|
||||
qs := models.Orm.QueryTable(new(models.CmsSolutionCategory)).
|
||||
Filter("tid", tid).
|
||||
Filter("delete_time__isnull", true)
|
||||
if keyword != "" {
|
||||
qs = qs.Filter("title__icontains", keyword)
|
||||
}
|
||||
|
||||
total, _ := qs.Count()
|
||||
var rows []models.CmsSolutionCategory
|
||||
offset := (page - 1) * limit
|
||||
_, err = qs.OrderBy("sort", "id").Limit(limit, offset).All(&rows)
|
||||
if err != nil && err != orm.ErrNoRows {
|
||||
c.cmsJSONErr(500, 500, "获取解决方案分类列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
list := make([]map[string]interface{}, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
list = append(list, cmsSolutionCateToMap(r))
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": map[string]interface{}{"list": list, "total": total},
|
||||
}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
type cmsSolutionCategoryPayload struct {
|
||||
Title string `json:"title"`
|
||||
Pid uint64 `json:"pid"`
|
||||
Desc string `json:"desc"`
|
||||
Sort int `json:"sort"`
|
||||
}
|
||||
|
||||
// Create POST /backend/addServicesTypes
|
||||
func (c *BackendSolutionCategoryController) Create() {
|
||||
if !cmsEnsureSolutionTables(&c.Controller) {
|
||||
return
|
||||
}
|
||||
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 p cmsSolutionCategoryPayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
c.cmsJSONErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
title := strings.TrimSpace(p.Title)
|
||||
if title == "" {
|
||||
c.cmsJSONErr(400, 400, "分类名称不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
row := models.CmsSolutionCategory{
|
||||
Tid: tid,
|
||||
Title: title,
|
||||
Pid: p.Pid,
|
||||
Desc: strings.TrimSpace(p.Desc),
|
||||
Sort: p.Sort,
|
||||
Status: 1,
|
||||
CreateTime: now,
|
||||
UpdateTime: &now,
|
||||
}
|
||||
id, err := models.Orm.Insert(&row)
|
||||
if err != nil {
|
||||
c.cmsJSONErr(500, 500, "添加失败")
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "添加成功", "data": map[string]interface{}{"id": id}}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// Update PUT /backend/editServicesTypes/:id
|
||||
func (c *BackendSolutionCategoryController) Update() {
|
||||
if !cmsEnsureSolutionTables(&c.Controller) {
|
||||
return
|
||||
}
|
||||
claims, err := c.cmsClaims()
|
||||
if err != nil {
|
||||
c.cmsJSONErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
tid := cmsEffectiveTid(&c.Controller, claims)
|
||||
id, _ := c.GetUint64(":id")
|
||||
if id == 0 {
|
||||
c.cmsJSONErr(400, 400, "无效ID")
|
||||
return
|
||||
}
|
||||
|
||||
raw, err := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err != nil {
|
||||
c.cmsJSONErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var p cmsSolutionCategoryPayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
c.cmsJSONErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
title := strings.TrimSpace(p.Title)
|
||||
if title == "" {
|
||||
c.cmsJSONErr(400, 400, "分类名称不能为空")
|
||||
return
|
||||
}
|
||||
if p.Pid == id {
|
||||
c.cmsJSONErr(400, 400, "父级分类不能是自己")
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
n, err := models.Orm.QueryTable(new(models.CmsSolutionCategory)).
|
||||
Filter("id", id).
|
||||
Filter("tid", tid).
|
||||
Filter("delete_time__isnull", true).
|
||||
Update(orm.Params{
|
||||
"title": title,
|
||||
"pid": p.Pid,
|
||||
"desc": strings.TrimSpace(p.Desc),
|
||||
"sort": p.Sort,
|
||||
"update_time": now,
|
||||
})
|
||||
if err != nil {
|
||||
c.cmsJSONErr(500, 500, "更新失败")
|
||||
return
|
||||
}
|
||||
if n == 0 {
|
||||
c.cmsJSONErr(404, 404, "分类不存在")
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// Delete DELETE /backend/deleteServicesTypes/:id
|
||||
func (c *BackendSolutionCategoryController) Delete() {
|
||||
if !cmsEnsureSolutionTables(&c.Controller) {
|
||||
return
|
||||
}
|
||||
claims, err := c.cmsClaims()
|
||||
if err != nil {
|
||||
c.cmsJSONErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
tid := cmsEffectiveTid(&c.Controller, claims)
|
||||
id, _ := c.GetUint64(":id")
|
||||
if id == 0 {
|
||||
c.cmsJSONErr(400, 400, "无效ID")
|
||||
return
|
||||
}
|
||||
|
||||
// 存在子分类时不允许删除
|
||||
childCnt, _ := models.Orm.QueryTable(new(models.CmsSolutionCategory)).
|
||||
Filter("tid", tid).
|
||||
Filter("pid", id).
|
||||
Filter("delete_time__isnull", true).
|
||||
Count()
|
||||
if childCnt > 0 {
|
||||
c.cmsJSONErr(400, 400, "该分类下存在子分类,请先删除子分类")
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
n, err := models.Orm.QueryTable(new(models.CmsSolutionCategory)).
|
||||
Filter("id", id).
|
||||
Filter("tid", tid).
|
||||
Filter("delete_time__isnull", true).
|
||||
Update(orm.Params{"delete_time": now})
|
||||
if err != nil {
|
||||
c.cmsJSONErr(500, 500, "删除失败")
|
||||
return
|
||||
}
|
||||
if n == 0 {
|
||||
c.cmsJSONErr(404, 404, "分类不存在")
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
@@ -120,32 +120,43 @@ func (c *TenantSiteController) resolveTemplateCode(tid uint64) string {
|
||||
func (c *TenantSiteController) writeHTML(status int, html string) {
|
||||
c.Ctx.Output.SetStatus(status)
|
||||
c.Ctx.Output.Header("Content-Type", "text/html; charset=utf-8")
|
||||
c.Ctx.WriteString(c.forceHTTPSStorageURLs(html))
|
||||
c.Ctx.WriteString(c.rewriteStorageURLs(html))
|
||||
}
|
||||
|
||||
// forceHTTPSStorageURLs 访客以 HTTPS 访问站点时,把页面中存储(CDN)域名的
|
||||
// http:// 资源升级为 https://,避免混合内容(Mixed Content)被浏览器拦截。
|
||||
// 仅替换存储配置里那个域名,不动正文里的其他外部链接。
|
||||
func (c *TenantSiteController) forceHTTPSStorageURLs(html string) string {
|
||||
proto := c.Ctx.Input.Header("X-Forwarded-Proto")
|
||||
if proto == "" && c.Ctx.Input.IsSecure() {
|
||||
proto = "https"
|
||||
}
|
||||
if proto != "https" {
|
||||
return html
|
||||
}
|
||||
var legacyQiniuDomains = []string{
|
||||
"7colud.yunzer.cn",
|
||||
"7cloud.yunzer.cn",
|
||||
}
|
||||
|
||||
// rewriteStorageURLs 将历史七牛域名替换为当前存储配置的访问域名。
|
||||
// 这样已存入数据库的旧完整 URL 会跟随新的 CDN 或 /qiniu/ 反向代理入口,
|
||||
// 不需要批量更新业务数据。
|
||||
func (c *TenantSiteController) rewriteStorageURLs(html string) string {
|
||||
cfg, err := models.GetStorageConfig()
|
||||
if err != nil || strings.TrimSpace(cfg.QiniuDomain) == "" {
|
||||
return html
|
||||
}
|
||||
host := strings.TrimSpace(cfg.QiniuDomain)
|
||||
host = strings.TrimPrefix(host, "http://")
|
||||
host = strings.TrimPrefix(host, "https://")
|
||||
host = strings.TrimRight(host, "/")
|
||||
if host == "" {
|
||||
return rewriteLegacyQiniuURLs(html, storagePublicBaseURL(cfg.QiniuDomain))
|
||||
}
|
||||
|
||||
func storagePublicBaseURL(domain string) string {
|
||||
base := strings.TrimRight(strings.TrimSpace(domain), "/")
|
||||
if base == "" || strings.HasPrefix(base, "/") || strings.HasPrefix(base, "http://") || strings.HasPrefix(base, "https://") {
|
||||
return base
|
||||
}
|
||||
return "https://" + base
|
||||
}
|
||||
|
||||
func rewriteLegacyQiniuURLs(html, targetBase string) string {
|
||||
if targetBase == "" {
|
||||
return html
|
||||
}
|
||||
return strings.ReplaceAll(html, "http://"+host, "https://"+host)
|
||||
for _, domain := range legacyQiniuDomains {
|
||||
for _, scheme := range []string{"http://", "https://"} {
|
||||
html = strings.ReplaceAll(html, scheme+domain+"/", targetBase+"/")
|
||||
}
|
||||
}
|
||||
return html
|
||||
}
|
||||
|
||||
// rewriteThemeAssets 将模板内书写的相对资源路径(href/src/url() 等)
|
||||
@@ -211,6 +222,11 @@ func rewriteThemeAssets(html, code string) string {
|
||||
|
||||
// render 解析并输出模板文件
|
||||
func (c *TenantSiteController) render(file string, mod func(ctx *tagengine.RenderCtx)) {
|
||||
c.renderFile(file, "", mod)
|
||||
}
|
||||
|
||||
// renderFile 解析并输出模板;preferred 不存在时尝试 fallback(fallback 为空则直接 404)
|
||||
func (c *TenantSiteController) renderFile(preferred, fallback string, mod func(ctx *tagengine.RenderCtx)) {
|
||||
tid, ok := c.resolveTid()
|
||||
if !ok {
|
||||
c.writeHTML(404, siteNotFoundPage)
|
||||
@@ -219,6 +235,12 @@ func (c *TenantSiteController) render(file string, mod func(ctx *tagengine.Rende
|
||||
|
||||
code := c.resolveTemplateCode(tid)
|
||||
dir := filepath.Join(cmsThemesRoot(), code)
|
||||
file := preferred
|
||||
if _, err := os.Stat(filepath.Join(dir, file)); err != nil {
|
||||
if fallback != "" {
|
||||
file = fallback
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(dir, file)); err != nil {
|
||||
c.writeHTML(404, fmt.Sprintf(renderErrorPage, "模板页面不存在"))
|
||||
return
|
||||
@@ -271,7 +293,7 @@ func (c *TenantSiteController) Page() {
|
||||
c.writeHTML(404, fmt.Sprintf(renderErrorPage, "页面不存在"))
|
||||
return
|
||||
}
|
||||
c.render("page.html", func(ctx *tagengine.RenderCtx) {
|
||||
c.renderFile(path+".html", "page.html", func(ctx *tagengine.RenderCtx) {
|
||||
ctx.PagePath = path
|
||||
})
|
||||
}
|
||||
|
||||
@@ -30,4 +30,4 @@ func (m *BackendMenuFront) TableName() string {
|
||||
|
||||
func init() {
|
||||
orm.RegisterModel(new(BackendMenuFront))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,47 +109,6 @@ CREATE TABLE IF NOT EXISTS yz_cms_article (
|
||||
return err
|
||||
}
|
||||
|
||||
func EnsureCmsArticleDefaultCategories() error {
|
||||
// 只初始化两级全局分类:文章中心(顶级)和新闻中心(文章中心的子分类)。
|
||||
// 不在启动时创建其它业务分类,后续分类由管理员按需新增。
|
||||
defaults := []struct {
|
||||
name string
|
||||
cid uint64
|
||||
sort int
|
||||
}{
|
||||
{name: "文章中心", cid: 0, sort: 1},
|
||||
{name: "新闻中心", cid: 1, sort: 2},
|
||||
}
|
||||
|
||||
for _, item := range defaults {
|
||||
count, err := Orm.QueryTable(new(CmsArticleCategory)).
|
||||
Filter("tid", 0).
|
||||
Filter("name", item.name).
|
||||
Filter("cid", item.cid).
|
||||
Filter("delete_time__isnull", true).
|
||||
Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
continue
|
||||
}
|
||||
now := time.Now()
|
||||
_, err = Orm.Insert(&CmsArticleCategory{
|
||||
Tid: 0,
|
||||
Cid: item.cid,
|
||||
Name: item.name,
|
||||
Sort: item.sort,
|
||||
Status: 1,
|
||||
CreateTime: now,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CmsCategoryNameMap(tid uint64, ids []uint64) map[uint64]string {
|
||||
out := make(map[uint64]string)
|
||||
if len(ids) == 0 {
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CmsProduct CMS 产品 yz_cms_product
|
||||
type CmsProduct struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
Tid uint64 `orm:"column(tid);default(0)" json:"tid"`
|
||||
Title string `orm:"column(title);size(100)" json:"title"`
|
||||
Thumb string `orm:"column(thumb);size(500);default()" json:"thumb"`
|
||||
Desc string `orm:"column(desc);size(500);default()" json:"desc"`
|
||||
Content string `orm:"column(content);type(mediumtext);null" json:"content"`
|
||||
URL string `orm:"column(url);size(500);default()" json:"url"`
|
||||
Sort int `orm:"column(sort);default(0)" json:"sort"`
|
||||
Status int8 `orm:"column(status);default(1)" json:"status"`
|
||||
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
|
||||
UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||
}
|
||||
|
||||
func (m *CmsProduct) TableName() string {
|
||||
return "yz_cms_product"
|
||||
}
|
||||
|
||||
// CmsProductCategory CMS 产品分类 yz_cms_product_category
|
||||
type CmsProductCategory struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
Tid uint64 `orm:"column(tid);default(0)" json:"tid"`
|
||||
Title string `orm:"column(title);size(100)" json:"title"`
|
||||
Pid uint64 `orm:"column(pid);default(0)" json:"pid"`
|
||||
Desc string `orm:"column(desc);size(500);default()" json:"desc"`
|
||||
Sort int `orm:"column(sort);default(0)" json:"sort"`
|
||||
Status int8 `orm:"column(status);default(1)" json:"status"`
|
||||
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
|
||||
UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||
}
|
||||
|
||||
func (m *CmsProductCategory) TableName() string {
|
||||
return "yz_cms_product_category"
|
||||
}
|
||||
|
||||
var cmsProductTablesOnce sync.Once
|
||||
|
||||
// EnsureCmsProductTables 首次使用时自动建表(若不存在)。
|
||||
func EnsureCmsProductTables() error {
|
||||
var err error
|
||||
cmsProductTablesOnce.Do(func() {
|
||||
_, err = Orm.Raw(`
|
||||
CREATE TABLE IF NOT EXISTS yz_cms_product (
|
||||
id bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
tid bigint unsigned NOT NULL DEFAULT 0,
|
||||
title varchar(100) NOT NULL DEFAULT '',
|
||||
thumb varchar(500) NOT NULL DEFAULT '',
|
||||
` + "`desc`" + ` varchar(500) NOT NULL DEFAULT '',
|
||||
content mediumtext,
|
||||
url varchar(500) NOT NULL DEFAULT '',
|
||||
sort int NOT NULL DEFAULT 0,
|
||||
status tinyint NOT NULL DEFAULT 1,
|
||||
create_time datetime NOT NULL,
|
||||
update_time datetime DEFAULT NULL,
|
||||
delete_time datetime DEFAULT NULL,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_tid_status (tid, status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`).Exec()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = Orm.Raw(`
|
||||
CREATE TABLE IF NOT EXISTS yz_cms_product_category (
|
||||
id bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
tid bigint unsigned NOT NULL DEFAULT 0,
|
||||
title varchar(100) NOT NULL DEFAULT '',
|
||||
pid bigint unsigned NOT NULL DEFAULT 0,
|
||||
` + "`desc`" + ` varchar(500) NOT NULL DEFAULT '',
|
||||
sort int NOT NULL DEFAULT 0,
|
||||
status tinyint NOT NULL DEFAULT 1,
|
||||
create_time datetime NOT NULL,
|
||||
update_time datetime DEFAULT NULL,
|
||||
delete_time datetime DEFAULT NULL,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_tid_pid (tid, pid)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`).Exec()
|
||||
})
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CmsSolution CMS 解决方案(特色服务) yz_cms_solution
|
||||
type CmsSolution struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
Tid uint64 `orm:"column(tid);default(0)" json:"tid"`
|
||||
Title string `orm:"column(title);size(100)" json:"title"`
|
||||
Thumb string `orm:"column(thumb);size(500);default()" json:"thumb"`
|
||||
Desc string `orm:"column(desc);size(500);default()" json:"desc"`
|
||||
URL string `orm:"column(url);size(500);default()" json:"url"`
|
||||
Sort int `orm:"column(sort);default(0)" json:"sort"`
|
||||
Status int8 `orm:"column(status);default(1)" json:"status"`
|
||||
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
|
||||
UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||
}
|
||||
|
||||
func (m *CmsSolution) TableName() string {
|
||||
return "yz_cms_solution"
|
||||
}
|
||||
|
||||
// CmsSolutionCategory CMS 解决方案分类 yz_cms_solution_category
|
||||
type CmsSolutionCategory struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
Tid uint64 `orm:"column(tid);default(0)" json:"tid"`
|
||||
Title string `orm:"column(title);size(100)" json:"title"`
|
||||
Pid uint64 `orm:"column(pid);default(0)" json:"pid"`
|
||||
Desc string `orm:"column(desc);size(500);default()" json:"desc"`
|
||||
Sort int `orm:"column(sort);default(0)" json:"sort"`
|
||||
Status int8 `orm:"column(status);default(1)" json:"status"`
|
||||
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
|
||||
UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||
}
|
||||
|
||||
func (m *CmsSolutionCategory) TableName() string {
|
||||
return "yz_cms_solution_category"
|
||||
}
|
||||
|
||||
var cmsSolutionTablesOnce sync.Once
|
||||
|
||||
// EnsureCmsSolutionTables 首次使用时自动建表(若不存在)。
|
||||
func EnsureCmsSolutionTables() error {
|
||||
var err error
|
||||
cmsSolutionTablesOnce.Do(func() {
|
||||
_, err = Orm.Raw(`
|
||||
CREATE TABLE IF NOT EXISTS yz_cms_solution (
|
||||
id bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
tid bigint unsigned NOT NULL DEFAULT 0,
|
||||
title varchar(100) NOT NULL DEFAULT '',
|
||||
thumb varchar(500) NOT NULL DEFAULT '',
|
||||
` + "`desc`" + ` varchar(500) NOT NULL DEFAULT '',
|
||||
url varchar(500) NOT NULL DEFAULT '',
|
||||
sort int NOT NULL DEFAULT 0,
|
||||
status tinyint NOT NULL DEFAULT 1,
|
||||
create_time datetime NOT NULL,
|
||||
update_time datetime DEFAULT NULL,
|
||||
delete_time datetime DEFAULT NULL,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_tid_status (tid, status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`).Exec()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = Orm.Raw(`
|
||||
CREATE TABLE IF NOT EXISTS yz_cms_solution_category (
|
||||
id bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
tid bigint unsigned NOT NULL DEFAULT 0,
|
||||
title varchar(100) NOT NULL DEFAULT '',
|
||||
pid bigint unsigned NOT NULL DEFAULT 0,
|
||||
` + "`desc`" + ` varchar(500) NOT NULL DEFAULT '',
|
||||
sort int NOT NULL DEFAULT 0,
|
||||
status tinyint NOT NULL DEFAULT 1,
|
||||
create_time datetime NOT NULL,
|
||||
update_time datetime DEFAULT NULL,
|
||||
delete_time datetime DEFAULT NULL,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_tid_pid (tid, pid)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`).Exec()
|
||||
})
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// FrontMenuDefault 全局默认前端导航项定义
|
||||
type FrontMenuDefault struct {
|
||||
Title string
|
||||
Path string
|
||||
Type int8 // 菜单类型:2 页面 / 4 单页
|
||||
Sort int
|
||||
}
|
||||
|
||||
// FrontMenuDefaults 所有租户共用的默认导航(不可删除)。
|
||||
// 以 tenant_id=0 存放于同一张菜单表,表示全局共享:
|
||||
// 租户端增删改接口都带 tenant_id 过滤,天然无法触碰这些行。
|
||||
// 路径对应官网前台路由:新闻中心 /news,其余为单页 /page/:path
|
||||
// (单页内容在 backend 端 - 单页管理 中按 path 维护)。
|
||||
var FrontMenuDefaults = []FrontMenuDefault{
|
||||
{Title: "新闻中心", Path: "/news", Type: 2, Sort: 1},
|
||||
{Title: "产品展示", Path: "/page/products", Type: 4, Sort: 2},
|
||||
{Title: "解决方案", Path: "/page/solutions", Type: 4, Sort: 3},
|
||||
{Title: "关于我们", Path: "/page/about", Type: 4, Sort: 4},
|
||||
{Title: "联系我们", Path: "/page/contact", Type: 4, Sort: 5},
|
||||
}
|
||||
|
||||
// EnsureGlobalDefaultFrontMenus 保证全局默认导航(tenant_id=0)齐全:
|
||||
// 缺失的自动新增;被误删(软删)的自动恢复;已存在(含改名)的不动。
|
||||
// 以"在库生效数量 >= 默认项总数"作为短路条件,避免改名后重复插入。
|
||||
// 供后台菜单列表与前台导航渲染前调用,任何失败均静默忽略不阻断主流程。
|
||||
func EnsureGlobalDefaultFrontMenus() {
|
||||
if Orm == nil {
|
||||
return
|
||||
}
|
||||
qs := Orm.QueryTable(new(BackendMenuFront)).Filter("tenant_id", 0)
|
||||
|
||||
activeCnt, err := qs.Filter("delete_time__isnull", true).Count()
|
||||
if err != nil || activeCnt >= int64(len(FrontMenuDefaults)) {
|
||||
return
|
||||
}
|
||||
|
||||
// 含软删记录一起取,用于恢复被误删的默认菜单
|
||||
var rows []BackendMenuFront
|
||||
if _, err := qs.All(&rows); err != nil {
|
||||
return
|
||||
}
|
||||
exist := make(map[string]*BackendMenuFront, len(rows))
|
||||
for i := range rows {
|
||||
exist[rows[i].Title] = &rows[i]
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
for _, def := range FrontMenuDefaults {
|
||||
if row, ok := exist[def.Title]; ok {
|
||||
if row.DeleteTime != nil {
|
||||
_, _ = Orm.QueryTable(new(BackendMenuFront)).
|
||||
Filter("id", row.ID).
|
||||
Update(map[string]interface{}{
|
||||
"delete_time": nil,
|
||||
"status": 1,
|
||||
"is_visible": 1,
|
||||
"update_time": now,
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
_, _ = Orm.Insert(&BackendMenuFront{
|
||||
TenantID: 0,
|
||||
Pid: 0,
|
||||
Title: def.Title,
|
||||
Path: def.Path,
|
||||
Sort: def.Sort,
|
||||
Status: 1,
|
||||
IsVisible: 1,
|
||||
Type: def.Type,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -26,8 +26,12 @@ type SelfCloseHandler func(tid uint64, params map[string]string, ctx *RenderCtx)
|
||||
// GlobalProvider 全局标签提供器:返回 标签名->值 映射
|
||||
type GlobalProvider func(tid uint64, ctx *RenderCtx) (map[string]string, error)
|
||||
|
||||
// BodyProvider 带循环体的块标签:自行组装 HTML(如带 Tab 的新闻中心)
|
||||
type BodyProvider func(tid uint64, params map[string]string, ctx *RenderCtx, body string) (string, error)
|
||||
|
||||
var (
|
||||
blockProviders = map[string]Provider{}
|
||||
blockProviders = map[string]Provider{}
|
||||
bodyProviders = map[string]BodyProvider{}
|
||||
selfCloseHandlers = map[string]SelfCloseHandler{}
|
||||
globalProvider GlobalProvider
|
||||
)
|
||||
@@ -35,6 +39,9 @@ var (
|
||||
// RegisterBlock 注册循环标签({yz:name}...{/yz:name})
|
||||
func RegisterBlock(name string, p Provider) { blockProviders[name] = p }
|
||||
|
||||
// RegisterBodyBlock 注册自行组装循环体的块标签
|
||||
func RegisterBodyBlock(name string, p BodyProvider) { bodyProviders[name] = p }
|
||||
|
||||
// RegisterSelfClose 注册自闭合标签({yz:name ... /})
|
||||
func RegisterSelfClose(name string, h SelfCloseHandler) { selfCloseHandlers[name] = h }
|
||||
|
||||
@@ -67,6 +74,13 @@ func parseAttrs(attrStr string) map[string]string {
|
||||
return out
|
||||
}
|
||||
|
||||
func applyFields(body string, row map[string]string) string {
|
||||
return reField.ReplaceAllStringFunc(body, func(fm string) string {
|
||||
field := reField.FindStringSubmatch(fm)[1]
|
||||
return row[field]
|
||||
})
|
||||
}
|
||||
|
||||
// Render 渲染模板目录下的指定文件
|
||||
// 流水线:include 展开 → 块标签 → 自闭合标签 → 全局标签
|
||||
func Render(templateDir, file string, ctx *RenderCtx) (string, error) {
|
||||
@@ -152,18 +166,21 @@ func renderBlocks(content string, ctx *RenderCtx) (string, error) {
|
||||
}
|
||||
|
||||
rendered := ""
|
||||
if p, ok := blockProviders[closeName]; ok {
|
||||
body := out[openEnd:c[0]]
|
||||
if bp, ok := bodyProviders[closeName]; ok {
|
||||
s, err := bp(ctx.Tid, parseAttrs(attrs), ctx, body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
rendered = s
|
||||
} else if p, ok := blockProviders[closeName]; ok {
|
||||
rows, err := p(ctx.Tid, parseAttrs(attrs), ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
body := out[openEnd:c[0]]
|
||||
var sb strings.Builder
|
||||
for _, row := range rows {
|
||||
sb.WriteString(reField.ReplaceAllStringFunc(body, func(fm string) string {
|
||||
field := reField.FindStringSubmatch(fm)[1]
|
||||
return row[field]
|
||||
}))
|
||||
sb.WriteString(applyFields(body, row))
|
||||
}
|
||||
rendered = sb.String()
|
||||
}
|
||||
|
||||
+63
-17
@@ -4,11 +4,11 @@ package tagengine
|
||||
// 新增标签时在此登记一条,文档页自动同步,避免文档与实现脱节。
|
||||
type TagMeta struct {
|
||||
Name string `json:"name"` // 标签名
|
||||
Category string `json:"category"` // 分类:global 全局 / loop 循环 / single 单页 / other 其他
|
||||
Category string `json:"category"` // 分类:guide 入门 / global 全局 / loop 循环 / single 单页 / other 其他
|
||||
Syntax string `json:"syntax"` // 语法示例
|
||||
Desc string `json:"desc"` // 用途说明
|
||||
Params []string `json:"params"` // 可用参数(key=说明)
|
||||
Fields []string `json:"fields"` // 循环体内可用 [field:xxx/] 字段
|
||||
Fields []string `json:"fields"` // 循环体内可用 [field:xxx/] 字段;global 分类下为标签清单(名称 说明 数据来源)
|
||||
Example string `json:"example"` // 模板内示例代码
|
||||
}
|
||||
|
||||
@@ -16,31 +16,68 @@ type TagMeta struct {
|
||||
func TagDocs() []TagMeta {
|
||||
return []TagMeta{
|
||||
{
|
||||
Name: "全局站点信息",
|
||||
Category: "global",
|
||||
Syntax: "{yz:sitename} {yz:logo} {yz:logow} {yz:ico} {yz:icp} {yz:copyright} {yz:companyname} {yz:description} {yz:companyintroduction}",
|
||||
Desc: "取自当前租户的站点设置(backend 端 - 站点设置 - 基本信息),直接替换为对应文本/地址,无需闭合。logo 彩色Logo / logow 白色Logo / ico 站点图标 / companyintroduction 企业介绍(富文本)。",
|
||||
Name: "基本用法",
|
||||
Category: "guide",
|
||||
Syntax: "{yz:标签} / {yz:标签 参数=\"值\"/} / {yz:标签}...[field:字段/]...{/yz:标签}",
|
||||
Desc: "模板中用 {yz:xxx} 调用数据。三种形态:① 全局标签直接替换为文本,无需闭合;② 自闭合标签带参数、以 /} 结尾;③ 循环标签成对出现,循环体内用 [field:字段/] 输出每一行数据的字段。未配置的项输出空字符串,不会报错。",
|
||||
Params: nil,
|
||||
Fields: nil,
|
||||
Example: "<title>{yz:sitename}</title>\n<img src=\"{yz:logo}\" alt=\"{yz:sitename}\" />\n<p>{yz:icp} {yz:copyright}</p>",
|
||||
Example: "<!-- 全局标签:直接替换 -->\n<h1>{yz:sitename}</h1>\n\n<!-- 自闭合标签:带参数 -->\n{yz:onepage path=\"about\" field=\"content\"/}\n\n<!-- 循环标签:成对 + 字段占位 -->\n{yz:arclist row=\"6\"}\n<li><a href=\"[field:arcurl/]\">[field:title/]</a></li>\n{/yz:arclist}",
|
||||
},
|
||||
{
|
||||
Name: "全局联系方式",
|
||||
Category: "global",
|
||||
Syntax: "{yz:phone} {yz:email} {yz:address} {yz:worktime}",
|
||||
Desc: "取自租户的公司信息(backend 端 - 站点设置 - 公司信息),直接替换为文本,无需闭合。phone 联系电话(别名 {yz:tel} / {yz:mobile}) / email 电子邮箱 / address 公司地址 / worktime 工作时间。",
|
||||
Name: "模板文件结构",
|
||||
Category: "guide",
|
||||
Syntax: "themes/{模板编码}/",
|
||||
Desc: "模板存放在服务器 themes/{编码}/ 目录,按请求域名识别租户后用对应模板渲染。路由与文件的对应关系:首页 / → index.html;新闻列表 /news → news.html;文章详情 /news/:id → news_detail.html;单页 /page/:path → page.html。公共头部/底部建议拆成 header.html / footer.html,用 {yz:include/} 引入。模板内的相对资源路径(assets/css/xxx.css、style.css 等)渲染时会自动改写为 /themes/{编码}/ 绝对路径,无需手工改。",
|
||||
Params: nil,
|
||||
Fields: nil,
|
||||
Example: "<a href=\"tel:{yz:phone}\">{yz:phone}</a>\n<a href=\"mailto:{yz:email}\">{yz:email}</a>\n<p>{yz:address}</p>\n<p>工作时间:{yz:worktime}</p>",
|
||||
Example: "themes/business/\n├── index.html ← 首页 /\n├── news.html ← 新闻列表 /news?page=N\n├── news_detail.html ← 文章详情 /news/:id\n├── page.html ← 单页 /page/about\n├── header.html ← 公共头部(include 引入)\n├── footer.html ← 公共底部(include 引入)\n└── assets/ ← 模板自带的 css/js/图片",
|
||||
},
|
||||
{
|
||||
Name: "全局 SEO 信息",
|
||||
Name: "站点基本信息",
|
||||
Category: "global",
|
||||
Syntax: "{yz:seo_title} {yz:keywords} {yz:seo_description}",
|
||||
Desc: "取自租户的 SEO 设置(backend 端 - 站点设置 - SEO 设置),用于 head 区域的 meta 标签。seo_title 未填时建议回退用 {yz:sitename}。",
|
||||
Syntax: "{yz:sitename}、{yz:logo}、{yz:ico} …直接写在模板任意位置",
|
||||
Desc: "取自当前租户的站点设置(backend 端 - 站点设置 - 基本信息)。直接替换为文本或地址,无需闭合;未配置的项输出空字符串。点击行末复制按钮可复制标签。",
|
||||
Params: nil,
|
||||
Fields: nil,
|
||||
Example: "<title>{yz:seo_title}</title>\n<meta name=\"keywords\" content=\"{yz:keywords}\">\n<meta name=\"description\" content=\"{yz:seo_description}\">",
|
||||
Fields: []string{
|
||||
"sitename 站点名称,常用于 <title> 和页头 来源:基本信息-站点名称",
|
||||
"logo 彩色 Logo 图片地址,适合浅色背景 来源:基本信息-站点LOGO",
|
||||
"logow 白色 Logo 图片地址,适合深色背景/页脚 来源:基本信息-白色LOGO",
|
||||
"ico 站点图标地址,用于 <link rel=\"icon\"> 来源:基本信息-站点图标",
|
||||
"companyname 公司全称 来源:基本信息-公司名称",
|
||||
"companyintroduction 企业介绍(富文本HTML,直接用 div 承接) 来源:基本信息-企业介绍",
|
||||
"description 站点简介,可用于 meta description 来源:基本信息-站点描述",
|
||||
"copyright 版权信息,常用于页脚 来源:基本信息-版权信息",
|
||||
"icp ICP 备案号,常用于页脚 来源:基本信息-ICP备案",
|
||||
},
|
||||
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>",
|
||||
},
|
||||
{
|
||||
Name: "联系方式",
|
||||
Category: "global",
|
||||
Syntax: "{yz:phone}、{yz:email}、{yz:address}、{yz:worktime}",
|
||||
Desc: "取自租户的公司信息(backend 端 - 公司信息),常用于页头联系栏和页脚。直接替换为文本,无需闭合。",
|
||||
Params: nil,
|
||||
Fields: []string{
|
||||
"phone 联系电话,别名 {yz:tel}、{yz:mobile} 三者等价 来源:公司信息-联系电话",
|
||||
"email 电子邮箱,可配合 mailto: 使用 来源:公司信息-邮箱",
|
||||
"address 公司地址 来源:公司信息-地址",
|
||||
"worktime 工作时间 来源:公司信息-工作时间",
|
||||
},
|
||||
Example: "<!-- 页头联系栏 -->\n<a href=\"tel:{yz:phone}\">服务热线:{yz:phone}</a>\n<a href=\"mailto:{yz:email}\">{yz:email}</a>\n\n<!-- 页脚 -->\n<p>地址:{yz:address}</p>\n<p>工作时间:{yz:worktime}</p>",
|
||||
},
|
||||
{
|
||||
Name: "SEO 信息",
|
||||
Category: "global",
|
||||
Syntax: "{yz:seotitle}、{yz:keywords}、{yz:seodescription}",
|
||||
Desc: "取自租户的 SEO 设置(backend 端 - SEO 设置),用于 <head> 区域。seotitle 未填时输出为空,建议模板里用 {yz:sitename} 兜底。",
|
||||
Params: nil,
|
||||
Fields: []string{
|
||||
"seotitle SEO 标题,未填时建议回退 {yz:sitename} 来源:SEO设置-SEO标题",
|
||||
"keywords SEO 关键词,用于 meta keywords 来源:SEO设置-关键词",
|
||||
"seodescription SEO 描述,用于 meta description 来源:SEO设置-描述",
|
||||
},
|
||||
Example: "<title>{yz:seotitle}</title>\n<meta name=\"keywords\" content=\"{yz:keywords}\">\n<meta name=\"description\" content=\"{yz:seodescription}\">",
|
||||
},
|
||||
{
|
||||
Name: "nav 导航菜单",
|
||||
@@ -69,6 +106,15 @@ func TagDocs() []TagMeta {
|
||||
Fields: []string{"id 文章ID", "title 标题(受 titlelen 影响)", "titlefull 完整标题", "desc 摘要", "image 缩略图", "arcurl 详情页链接(/news/ID)", "pubdate 发布日期", "views 阅读量", "author 作者"},
|
||||
Example: "{yz:arclist row=\"6\" titlelen=\"20\"}\n<li><a href=\"[field:arcurl/]\">[field:title/]</a><span>[field:pubdate/]</span></li>\n{/yz:arclist}",
|
||||
},
|
||||
{
|
||||
Name: "newscenter 新闻中心(带Tab)",
|
||||
Category: "loop",
|
||||
Syntax: "{yz:newscenter row=\"8\" titlelen=\"30\"}...{/yz:newscenter}",
|
||||
Desc: "首页新闻中心区块。循环体是单条新闻卡片。若「新闻中心」分类下有子分类,则按子分类输出 Tab 并可切换,每个 Tab 展示 row 条;没有子分类则不输出 Tab,直接列出 row 条(含新闻中心及其子类文章)。",
|
||||
Params: []string{"row 每个分类输出条数,默认 8,最大 50", "titlelen 标题截断字数,不填为完整标题"},
|
||||
Fields: []string{"id 文章ID", "title 标题(受 titlelen 影响)", "titlefull 完整标题", "desc 摘要", "image 缩略图", "arcurl 详情页链接(/news/ID)", "pubdate 发布日期", "views 阅读量", "author 作者"},
|
||||
Example: "{yz:newscenter row=\"8\" titlelen=\"28\"}\n<div class=\"col-lg-3 col-md-6\">\n <a href=\"[field:arcurl/]\">\n <img src=\"[field:image/]\" alt=\"[field:titlefull/]\">\n <h4>[field:title/]</h4>\n <p>[field:desc/]</p>\n <span>[field:pubdate/]</span>\n </a>\n</div>\n{/yz:newscenter}",
|
||||
},
|
||||
{
|
||||
Name: "arcview 文章详情",
|
||||
Category: "single",
|
||||
|
||||
@@ -2,6 +2,7 @@ package tagengine
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -23,6 +24,7 @@ func init() {
|
||||
RegisterBlock("arclist", arclistProvider)
|
||||
RegisterBlock("arcview", arcviewProvider)
|
||||
RegisterBlock("friendlink", friendlinkProvider)
|
||||
RegisterBodyBlock("newscenter", newscenterHandler)
|
||||
RegisterSelfClose("onepage", onepageHandler)
|
||||
RegisterSelfClose("pagelist", pagelistHandler)
|
||||
}
|
||||
@@ -61,7 +63,8 @@ func globalSiteInfo(tid uint64, ctx *RenderCtx) (map[string]string, error) {
|
||||
"copyright": "", "companyname": "", "description": "",
|
||||
"companyintroduction": "",
|
||||
"phone": "", "tel": "", "mobile": "", "email": "", "address": "", "worktime": "",
|
||||
"keywords": "", "seo_title": "", "seo_description": "",
|
||||
"keywords": "", "seotitle": "", "seodescription": "",
|
||||
"aboutus": "/page/about", "contact": "/page/contact", "news": "/news", "team": "/page/team",
|
||||
}
|
||||
var row models.TenantSiteSetting
|
||||
err := models.Orm.QueryTable(new(models.TenantSiteSetting)).
|
||||
@@ -105,8 +108,8 @@ func globalSiteInfo(tid uint64, ctx *RenderCtx) (map[string]string, error) {
|
||||
// SEO 三项取自租户扩展设置表(backend 端 - 站点设置 - SEO 设置)
|
||||
if vals, err := queryTenantSettingItems(tid, "seoKeywords", "seoTitle", "seoDescription"); err == nil {
|
||||
out["keywords"] = vals["seoKeywords"]
|
||||
out["seo_title"] = vals["seoTitle"]
|
||||
out["seo_description"] = vals["seoDescription"]
|
||||
out["seotitle"] = vals["seoTitle"]
|
||||
out["seodescription"] = vals["seoDescription"]
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
@@ -136,11 +139,14 @@ func queryTenantSettingItems(tid uint64, keys ...string) (map[string]string, err
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// navProvider 导航菜单:一级菜单循环,[field:children/] 输出子菜单 HTML
|
||||
// navProvider 导航菜单:一级菜单循环,[field:children/] 输出子菜单 HTML。
|
||||
// 一级菜单含全局默认(tenant_id=0,不可删)与租户自定义;子菜单仅租户自有。
|
||||
func navProvider(tid uint64, params map[string]string, ctx *RenderCtx) ([]map[string]string, error) {
|
||||
models.EnsureGlobalDefaultFrontMenus()
|
||||
|
||||
var rows []models.BackendMenuFront
|
||||
_, err := models.Orm.QueryTable(new(models.BackendMenuFront)).
|
||||
Filter("tenant_id", tid).
|
||||
Filter("tenant_id__in", []uint64{0, tid}).
|
||||
Filter("pid", 0).
|
||||
Filter("delete_time__isnull", true).
|
||||
OrderBy("sort", "id").
|
||||
@@ -169,7 +175,7 @@ func navProvider(tid uint64, params map[string]string, ctx *RenderCtx) ([]map[st
|
||||
childHTML := ""
|
||||
if subs, ok := childMap[int64(m.ID)]; ok && len(subs) > 0 {
|
||||
var sb strings.Builder
|
||||
sb.WriteString(`<ul class="yz-sub-menu">`)
|
||||
sb.WriteString(`<ul class="sub-menu">`)
|
||||
for _, s := range subs {
|
||||
sb.WriteString(fmt.Sprintf(`<li><a href="%s">%s</a></li>`, s.Path, s.Title))
|
||||
}
|
||||
@@ -373,6 +379,102 @@ func onepageHandler(tid uint64, params map[string]string, ctx *RenderCtx) (strin
|
||||
}
|
||||
}
|
||||
|
||||
// newscenterHandler 首页新闻中心:循环体为单条新闻卡片。
|
||||
// 有「新闻中心」子分类时输出 Bootstrap Tab,每个 Tab 8 条;无子分类则不输出 Tab,直接列出 8 条。
|
||||
func newscenterHandler(tid uint64, params map[string]string, ctx *RenderCtx, body string) (string, error) {
|
||||
row := atoiDefault(params["row"], 8)
|
||||
if row <= 0 || row > 50 {
|
||||
row = 8
|
||||
}
|
||||
titleLen := atoiDefault(params["titlelen"], 0)
|
||||
|
||||
parent, err := findNewsCenterCategory(tid)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
children := []models.CmsArticleCategory{}
|
||||
if parent != nil {
|
||||
_, _ = models.Orm.QueryTable(new(models.CmsArticleCategory)).
|
||||
Filter("cid", parent.ID).
|
||||
Filter("tid__in", []uint64{0, tid}).
|
||||
Filter("status", 1).
|
||||
Filter("delete_time__isnull", true).
|
||||
OrderBy("sort", "id").
|
||||
All(&children)
|
||||
}
|
||||
|
||||
renderCards := func(cateID uint64) string {
|
||||
qs := publishedCond(models.Orm.QueryTable(new(models.CmsArticle)).
|
||||
Filter("tid", tid).
|
||||
Filter("delete_time__isnull", true))
|
||||
if cateID > 0 {
|
||||
qs = qs.Filter("cate_id", cateID)
|
||||
} else if parent != nil {
|
||||
ids := []uint64{parent.ID}
|
||||
for _, c := range children {
|
||||
ids = append(ids, c.ID)
|
||||
}
|
||||
qs = qs.Filter("cate_id__in", ids)
|
||||
}
|
||||
var arts []models.CmsArticle
|
||||
_, _ = qs.OrderBy("-top", "-id").Limit(row).All(&arts)
|
||||
var sb strings.Builder
|
||||
for _, a := range articleRows(arts, titleLen) {
|
||||
sb.WriteString(applyFields(body, a))
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
if len(children) == 0 {
|
||||
return `<div class="row">` + renderCards(0) + `</div>`, nil
|
||||
}
|
||||
|
||||
var tabs, panes strings.Builder
|
||||
tabs.WriteString(`<nav class="news-center-tabs"><div class="nav nav-tabs" role="tablist">`)
|
||||
panes.WriteString(`<div class="tab-content">`)
|
||||
for i, c := range children {
|
||||
paneID := fmt.Sprintf("news-cate-%d", c.ID)
|
||||
tabID := paneID + "-tab"
|
||||
active := ""
|
||||
show := ""
|
||||
selected := "false"
|
||||
if i == 0 {
|
||||
active = " active"
|
||||
show = " show active"
|
||||
selected = "true"
|
||||
}
|
||||
tabs.WriteString(fmt.Sprintf(
|
||||
`<button class="nav-link%s" id="%s" data-bs-toggle="tab" data-bs-target="#%s" type="button" role="tab" aria-controls="%s" aria-selected="%s">%s</button>`,
|
||||
active, tabID, paneID, paneID, selected, html.EscapeString(c.Name),
|
||||
))
|
||||
panes.WriteString(fmt.Sprintf(
|
||||
`<div class="tab-pane fade%s" id="%s" role="tabpanel" aria-labelledby="%s"><div class="row">%s</div></div>`,
|
||||
show, paneID, tabID, renderCards(c.ID),
|
||||
))
|
||||
}
|
||||
tabs.WriteString(`</div></nav>`)
|
||||
panes.WriteString(`</div>`)
|
||||
return tabs.String() + panes.String(), nil
|
||||
}
|
||||
|
||||
func findNewsCenterCategory(tid uint64) (*models.CmsArticleCategory, error) {
|
||||
var row models.CmsArticleCategory
|
||||
err := models.Orm.QueryTable(new(models.CmsArticleCategory)).
|
||||
Filter("name", "新闻中心").
|
||||
Filter("tid__in", []uint64{0, tid}).
|
||||
Filter("delete_time__isnull", true).
|
||||
OrderBy("-tid", "id").
|
||||
One(&row)
|
||||
if err == orm.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &row, nil
|
||||
}
|
||||
|
||||
// newsQueryBase 新闻列表统一查询条件(pagelist 与 news.html 保持一致)
|
||||
func newsQueryBase(tid uint64) orm.QuerySeter {
|
||||
return publishedCond(models.Orm.QueryTable(new(models.CmsArticle)).
|
||||
|
||||
@@ -132,6 +132,28 @@ func RegisterAuthRoutes() {
|
||||
beego.Router("/backend/editCategory/:id", &controllers.BackendArticleCategoryController{}, "post:Update")
|
||||
beego.Router("/backend/categories/:id/status", &controllers.BackendArticleCategoryController{}, "patch:UpdateStatus")
|
||||
|
||||
// 产品管理(yz_cms_product / yz_cms_product_category,按租户隔离)
|
||||
beego.Router("/backend/productsList", &controllers.BackendProductController{}, "get:List")
|
||||
beego.Router("/backend/addProducts", &controllers.BackendProductController{}, "post:Create")
|
||||
beego.Router("/backend/editProducts/:id", &controllers.BackendProductController{}, "put:Update")
|
||||
beego.Router("/backend/deleteProducts/:id", &controllers.BackendProductController{}, "delete:Delete")
|
||||
|
||||
beego.Router("/backend/productsTypesList", &controllers.BackendProductCategoryController{}, "get:List")
|
||||
beego.Router("/backend/addProductsTypes", &controllers.BackendProductCategoryController{}, "post:Create")
|
||||
beego.Router("/backend/editProductsTypes/:id", &controllers.BackendProductCategoryController{}, "put:Update")
|
||||
beego.Router("/backend/deleteProductsTypes/:id", &controllers.BackendProductCategoryController{}, "delete:Delete")
|
||||
|
||||
// 解决方案/特色服务管理(yz_cms_solution / yz_cms_solution_category,按租户隔离)
|
||||
beego.Router("/backend/servicesList", &controllers.BackendSolutionController{}, "get:List")
|
||||
beego.Router("/backend/addServices", &controllers.BackendSolutionController{}, "post:Create")
|
||||
beego.Router("/backend/editServices/:id", &controllers.BackendSolutionController{}, "put:Update")
|
||||
beego.Router("/backend/deleteServices/:id", &controllers.BackendSolutionController{}, "delete:Delete")
|
||||
|
||||
beego.Router("/backend/servicesTypesList", &controllers.BackendSolutionCategoryController{}, "get:List")
|
||||
beego.Router("/backend/addServicesTypes", &controllers.BackendSolutionCategoryController{}, "post:Create")
|
||||
beego.Router("/backend/editServicesTypes/:id", &controllers.BackendSolutionCategoryController{}, "put:Update")
|
||||
beego.Router("/backend/deleteServicesTypes/:id", &controllers.BackendSolutionCategoryController{}, "delete:Delete")
|
||||
|
||||
// 官网前台内容(Banner / 友链 / 单页,yz_cms_frontend_*,按租户隔离)
|
||||
beego.Router("/backend/allbanners", &controllers.BackendBannerController{}, "get:ListAll")
|
||||
beego.Router("/backend/createbanner", &controllers.BackendBannerController{}, "post:Create")
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||
<meta name="description" content="{yz:description}" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||
<title>{yz:sitename}</title>
|
||||
<link rel="shortcut icon" href="{yz:ico}" type="image/svg" />
|
||||
<link rel="stylesheet" href="assets/css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="assets/css/lineicons.css" />
|
||||
<link rel="stylesheet" href="assets/css/tiny-slider.css" />
|
||||
<link rel="stylesheet" href="assets/css/glightbox.min.css" />
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<section class="navbar-area navbar-nine">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<a class="navbar-brand" href="/">
|
||||
<img src="{yz:logo}" alt="{yz:sitename}" />
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNine"
|
||||
aria-controls="navbarNine" aria-expanded="false" aria-label="切换导航">
|
||||
<span class="toggler-icon"></span>
|
||||
<span class="toggler-icon"></span>
|
||||
<span class="toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse sub-menu-bar" id="navbarNine">
|
||||
<ul class="navbar-nav me-auto">
|
||||
<li class="nav-item"><a href="/">主页</a></li>
|
||||
{yz:nav}
|
||||
<li class="nav-item">
|
||||
<a href="[field:url/]">[field:name/]</a>
|
||||
[field:children/]
|
||||
</li>
|
||||
{/yz:nav}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="navbar-btn d-none d-lg-inline-block">
|
||||
<a class="menu-bar" href="#side-menu-left"><i class="lni lni-menu"></i></a>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="sidebar-left">
|
||||
<div class="sidebar-close"><a class="close" href="#close"><i class="lni lni-close"></i></a></div>
|
||||
<div class="sidebar-content">
|
||||
<div class="sidebar-logo"><a href="/"><img src="{yz:logo}" alt="{yz:sitename}" /></a></div>
|
||||
<p class="text">{yz:description}</p>
|
||||
<div class="sidebar-menu">
|
||||
<h5 class="menu-title">快速链接</h5>
|
||||
<ul>
|
||||
{yz:nav}
|
||||
<li><a href="[field:url/]">[field:name/]</a></li>
|
||||
{/yz:nav}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="sidebar-social align-items-center justify-content-center">
|
||||
<h5 class="social-title">关注我们</h5>
|
||||
<ul>
|
||||
<li><a href="javascript:void(0)"><i class="lni lni-facebook-filled"></i></a></li>
|
||||
<li><a href="javascript:void(0)"><i class="lni lni-twitter-original"></i></a></li>
|
||||
<li><a href="javascript:void(0)"><i class="lni lni-linkedin-original"></i></a></li>
|
||||
<li><a href="javascript:void(0)"><i class="lni lni-youtube"></i></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overlay-left"></div>
|
||||
|
||||
<section class="about-five page-inner-page">
|
||||
<div class="article-detail-breadcrumb-wrap">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb article-detail-breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/">主页</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">关于我们</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-lg-6 order-lg-2 order-1">
|
||||
<div class="about-image-five">
|
||||
<img src="assets/images/about/about-img1.jpg" alt="关于我们" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6 order-lg-1 order-2">
|
||||
<div class="about-five-content">
|
||||
<h6 class="small-title">{yz:companyname}</h6>
|
||||
<h2 class="main-title fw-bold">关于我们</h2>
|
||||
<div class="onepage-content">
|
||||
{yz:onepage path="about"/}
|
||||
{yz:companyintroduction}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="footer-area footer-eleven">
|
||||
<div class="footer-top">
|
||||
<div class="container">
|
||||
<div class="inner-content">
|
||||
<div class="row">
|
||||
<div class="col-lg-4 col-md-6 col-12">
|
||||
<div class="footer-widget f-about">
|
||||
<div class="logo"><a href="/"><img src="{yz:logo}" alt="{yz:sitename}" class="img-fluid" /></a></div>
|
||||
<p>{yz:description}</p>
|
||||
<p class="copyright-text">
|
||||
<span>备案号:{yz:icp}<br>{yz:copyright} <a href="www.yunzer.cn" rel="nofollow"> 云泽网 </a></span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-6 col-12">
|
||||
<div class="footer-widget f-link">
|
||||
<h5>解决方案</h5>
|
||||
<ul>
|
||||
<li><a href="javascript:void(0)">市场营销</a></li>
|
||||
<li><a href="javascript:void(0)">数据分析</a></li>
|
||||
<li><a href="javascript:void(0)">电子商务</a></li>
|
||||
<li><a href="javascript:void(0)">商业洞察</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-6 col-12">
|
||||
<div class="footer-widget f-link">
|
||||
<h5>技术支持</h5>
|
||||
<ul>
|
||||
<li><a href="javascript:void(0)">价格方案</a></li>
|
||||
<li><a href="javascript:void(0)">开发文档</a></li>
|
||||
<li><a href="javascript:void(0)">使用指南</a></li>
|
||||
<li><a href="javascript:void(0)">API 状态</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-6 col-12">
|
||||
<div class="footer-widget newsletter">
|
||||
<h5>订阅我们</h5>
|
||||
<p>订阅我们,获取最新资讯</p>
|
||||
<form action="#" method="get" target="_blank" class="newsletter-form">
|
||||
<input name="EMAIL" placeholder="邮箱地址" required="required" type="email" />
|
||||
<div class="button"><button class="sub-btn"><i class="lni lni-envelope"></i></button></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<a href="#" class="scroll-top btn-hover"><i class="lni lni-chevron-up"></i></a>
|
||||
|
||||
<script src="assets/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="assets/js/glightbox.min.js"></script>
|
||||
<script src="assets/js/main.js"></script>
|
||||
<script src="assets/js/tiny-slider.js"></script>
|
||||
<script>
|
||||
document.querySelector(".navbar-nine .navbar-toggler").addEventListener("click", function () {
|
||||
this.classList.toggle("active");
|
||||
});
|
||||
var sidebarLeft = document.querySelector(".sidebar-left");
|
||||
var overlayLeft = document.querySelector(".overlay-left");
|
||||
overlayLeft.addEventListener("click", function () {
|
||||
sidebarLeft.classList.toggle("open");
|
||||
overlayLeft.classList.toggle("open");
|
||||
});
|
||||
document.querySelector(".sidebar-close .close").addEventListener("click", function () {
|
||||
sidebarLeft.classList.remove("open");
|
||||
overlayLeft.classList.remove("open");
|
||||
});
|
||||
document.querySelector(".navbar-nine .menu-bar").addEventListener("click", function () {
|
||||
sidebarLeft.classList.add("open");
|
||||
overlayLeft.classList.add("open");
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -24,7 +24,7 @@
|
||||
}
|
||||
};
|
||||
|
||||
// section menu active
|
||||
// section menu active(仅处理页内锚点)
|
||||
function onScroll(event) {
|
||||
var sections = document.querySelectorAll('.page-scroll');
|
||||
var scrollPos = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
|
||||
@@ -32,10 +32,16 @@
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
var currLink = sections[i];
|
||||
var val = currLink.getAttribute('href');
|
||||
if (!val || val.charAt(0) !== '#') {
|
||||
continue;
|
||||
}
|
||||
var refElement = document.querySelector(val);
|
||||
if (!refElement) {
|
||||
continue;
|
||||
}
|
||||
var scrollTopMinus = scrollPos + 73;
|
||||
if (refElement.offsetTop <= scrollTopMinus && (refElement.offsetTop + refElement.offsetHeight > scrollTopMinus)) {
|
||||
document.querySelector('.page-scroll').classList.remove('active');
|
||||
sections.forEach(function (link) { link.classList.remove('active'); });
|
||||
currLink.classList.add('active');
|
||||
} else {
|
||||
currLink.classList.remove('active');
|
||||
@@ -45,15 +51,23 @@
|
||||
|
||||
window.document.addEventListener('scroll', onScroll);
|
||||
|
||||
// for menu scroll
|
||||
// 仅页内锚点(#xxx)平滑滚动;/news、/ 等路由链接正常跳转
|
||||
var pageLink = document.querySelectorAll('.page-scroll');
|
||||
|
||||
pageLink.forEach(elem => {
|
||||
elem.addEventListener('click', e => {
|
||||
var href = elem.getAttribute('href');
|
||||
if (!href || href.charAt(0) !== '#') {
|
||||
return;
|
||||
}
|
||||
var target = document.querySelector(href);
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
document.querySelector(elem.getAttribute('href')).scrollIntoView({
|
||||
target.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
offsetTop: 1 - 60,
|
||||
block: 'start',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||
<meta name="description" content="{yz:description}" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||
<title>{yz:sitename}</title>
|
||||
<link rel="shortcut icon" href="{yz:ico}" type="image/svg" />
|
||||
<link rel="stylesheet" href="assets/css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="assets/css/lineicons.css" />
|
||||
<link rel="stylesheet" href="assets/css/tiny-slider.css" />
|
||||
<link rel="stylesheet" href="assets/css/glightbox.min.css" />
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<section class="navbar-area navbar-nine">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<a class="navbar-brand" href="/">
|
||||
<img src="{yz:logo}" alt="{yz:sitename}" />
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNine"
|
||||
aria-controls="navbarNine" aria-expanded="false" aria-label="切换导航">
|
||||
<span class="toggler-icon"></span>
|
||||
<span class="toggler-icon"></span>
|
||||
<span class="toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse sub-menu-bar" id="navbarNine">
|
||||
<ul class="navbar-nav me-auto">
|
||||
<li class="nav-item"><a href="/">主页</a></li>
|
||||
{yz:nav}
|
||||
<li class="nav-item">
|
||||
<a href="[field:url/]">[field:name/]</a>
|
||||
[field:children/]
|
||||
</li>
|
||||
{/yz:nav}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="navbar-btn d-none d-lg-inline-block">
|
||||
<a class="menu-bar" href="#side-menu-left"><i class="lni lni-menu"></i></a>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="sidebar-left">
|
||||
<div class="sidebar-close"><a class="close" href="#close"><i class="lni lni-close"></i></a></div>
|
||||
<div class="sidebar-content">
|
||||
<div class="sidebar-logo"><a href="/"><img src="{yz:logo}" alt="{yz:sitename}" /></a></div>
|
||||
<p class="text">{yz:description}</p>
|
||||
<div class="sidebar-menu">
|
||||
<h5 class="menu-title">快速链接</h5>
|
||||
<ul>
|
||||
{yz:nav}
|
||||
<li><a href="[field:url/]">[field:name/]</a></li>
|
||||
{/yz:nav}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="sidebar-social align-items-center justify-content-center">
|
||||
<h5 class="social-title">关注我们</h5>
|
||||
<ul>
|
||||
<li><a href="javascript:void(0)"><i class="lni lni-facebook-filled"></i></a></li>
|
||||
<li><a href="javascript:void(0)"><i class="lni lni-twitter-original"></i></a></li>
|
||||
<li><a href="javascript:void(0)"><i class="lni lni-linkedin-original"></i></a></li>
|
||||
<li><a href="javascript:void(0)"><i class="lni lni-youtube"></i></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overlay-left"></div>
|
||||
|
||||
<section id="contact" class="contact-section page-inner-page">
|
||||
<div class="article-detail-breadcrumb-wrap">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb article-detail-breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/">主页</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">联系我们</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-xl-4">
|
||||
<div class="contact-item-wrapper">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6 col-xl-12">
|
||||
<div class="contact-item">
|
||||
<div class="contact-icon"><i class="lni lni-phone"></i></div>
|
||||
<div class="contact-content">
|
||||
<h4>联系方式</h4>
|
||||
<p>{yz:phone}</p>
|
||||
<p>{yz:email}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 col-xl-12">
|
||||
<div class="contact-item">
|
||||
<div class="contact-icon"><i class="lni lni-map-marker"></i></div>
|
||||
<div class="contact-content">
|
||||
<h4>公司地址</h4>
|
||||
<p>{yz:address}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 col-xl-12">
|
||||
<div class="contact-item">
|
||||
<div class="contact-icon"><i class="lni lni-alarm-clock"></i></div>
|
||||
<div class="contact-content">
|
||||
<h4>营业时间</h4>
|
||||
<p>{yz:worktime}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-8">
|
||||
<div class="contact-form-wrapper">
|
||||
<div class="row">
|
||||
<div class="col-xl-10 col-lg-8 mx-auto">
|
||||
<div class="section-title text-center">
|
||||
<span>联系我们</span>
|
||||
<h2>准备开始吧!</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="onepage-content contact-page-content">
|
||||
{yz:onepage path="contact"/}
|
||||
</div>
|
||||
<form action="#" class="contact-form">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<input type="text" name="name" id="name" placeholder="姓名" required />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<input type="email" name="email" id="email" placeholder="邮箱" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<input type="text" name="phone" id="phone" placeholder="手机号" required />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<input type="text" name="title" id="title" placeholder="标题" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<textarea name="message" id="message" placeholder="请输入内容" rows="5"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="button text-center rounded-buttons">
|
||||
<button type="submit" class="btn primary-btn rounded-full">发送</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="footer-area footer-eleven">
|
||||
<div class="footer-top">
|
||||
<div class="container">
|
||||
<div class="inner-content">
|
||||
<div class="row">
|
||||
<div class="col-lg-4 col-md-6 col-12">
|
||||
<div class="footer-widget f-about">
|
||||
<div class="logo"><a href="/"><img src="{yz:logo}" alt="{yz:sitename}" class="img-fluid" /></a></div>
|
||||
<p>{yz:description}</p>
|
||||
<p class="copyright-text">
|
||||
<span>备案号:{yz:icp}<br>{yz:copyright} <a href="www.yunzer.cn" rel="nofollow"> 云泽网 </a></span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-6 col-12">
|
||||
<div class="footer-widget f-link">
|
||||
<h5>解决方案</h5>
|
||||
<ul>
|
||||
<li><a href="javascript:void(0)">市场营销</a></li>
|
||||
<li><a href="javascript:void(0)">数据分析</a></li>
|
||||
<li><a href="javascript:void(0)">电子商务</a></li>
|
||||
<li><a href="javascript:void(0)">商业洞察</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-6 col-12">
|
||||
<div class="footer-widget f-link">
|
||||
<h5>技术支持</h5>
|
||||
<ul>
|
||||
<li><a href="javascript:void(0)">价格方案</a></li>
|
||||
<li><a href="javascript:void(0)">开发文档</a></li>
|
||||
<li><a href="javascript:void(0)">使用指南</a></li>
|
||||
<li><a href="javascript:void(0)">API 状态</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-6 col-12">
|
||||
<div class="footer-widget newsletter">
|
||||
<h5>订阅我们</h5>
|
||||
<p>订阅我们,获取最新资讯</p>
|
||||
<form action="#" method="get" target="_blank" class="newsletter-form">
|
||||
<input name="EMAIL" placeholder="邮箱地址" required="required" type="email" />
|
||||
<div class="button"><button class="sub-btn"><i class="lni lni-envelope"></i></button></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<a href="#" class="scroll-top btn-hover"><i class="lni lni-chevron-up"></i></a>
|
||||
|
||||
<script src="assets/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="assets/js/glightbox.min.js"></script>
|
||||
<script src="assets/js/main.js"></script>
|
||||
<script src="assets/js/tiny-slider.js"></script>
|
||||
<script>
|
||||
document.querySelector(".navbar-nine .navbar-toggler").addEventListener("click", function () {
|
||||
this.classList.toggle("active");
|
||||
});
|
||||
var sidebarLeft = document.querySelector(".sidebar-left");
|
||||
var overlayLeft = document.querySelector(".overlay-left");
|
||||
overlayLeft.addEventListener("click", function () {
|
||||
sidebarLeft.classList.toggle("open");
|
||||
overlayLeft.classList.toggle("open");
|
||||
});
|
||||
document.querySelector(".sidebar-close .close").addEventListener("click", function () {
|
||||
sidebarLeft.classList.remove("open");
|
||||
overlayLeft.classList.remove("open");
|
||||
});
|
||||
document.querySelector(".navbar-nine .menu-bar").addEventListener("click", function () {
|
||||
sidebarLeft.classList.add("open");
|
||||
overlayLeft.classList.add("open");
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
+119
-404
@@ -5,11 +5,11 @@
|
||||
<!--====== 必需的元标签 ======-->
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||
<meta name="description" content="云泽网 - 数字化解决方案与商业服务" />
|
||||
<meta name="description" content="{yz:description}" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||
|
||||
<!--====== 标题 ======-->
|
||||
<title>云泽网 - {yz:sitename}</title>
|
||||
<title>{yz:sitename}</title>
|
||||
|
||||
<!--====== 网站图标 ======-->
|
||||
<link rel="shortcut icon" href="{yz:ico}" type="image/svg" />
|
||||
@@ -38,7 +38,7 @@
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<a class="navbar-brand" href="index.html">
|
||||
<a class="navbar-brand" href="/">
|
||||
<img src="{yz:logo}" alt="{yz:sitename}" />
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNine"
|
||||
@@ -51,11 +51,11 @@
|
||||
<div class="collapse navbar-collapse sub-menu-bar" id="navbarNine">
|
||||
<ul class="navbar-nav me-auto">
|
||||
<li class="nav-item">
|
||||
<a class="page-scroll" href="/">主页</a>
|
||||
<a href="/">主页</a>
|
||||
</li>
|
||||
{yz:nav}
|
||||
<li class="nav-item">
|
||||
<a class="page-scroll" href="[field:url/]">[field:name/]</a>
|
||||
<a href="[field:url/]">[field:name/]</a>
|
||||
[field:children/]
|
||||
</li>
|
||||
{/yz:nav}
|
||||
@@ -84,17 +84,16 @@
|
||||
</div>
|
||||
<div class="sidebar-content">
|
||||
<div class="sidebar-logo">
|
||||
<a href="index.html"><img src="assets/images/logo.svg" alt="云泽网" /></a>
|
||||
<a href="/"><img src="{yz:logo}" alt="{yz:sitename}" /></a>
|
||||
</div>
|
||||
<p class="text">我们致力于为品牌提供卓越的数字解决方案,用技术创造无限可能。</p>
|
||||
<p class="text">{yz:description}</p>
|
||||
<!-- logo -->
|
||||
<div class="sidebar-menu">
|
||||
<h5 class="menu-title">快速链接</h5>
|
||||
<ul>
|
||||
<li><a href="javascript:void(0)">关于我们</a></li>
|
||||
<li><a href="javascript:void(0)">我们的团队</a></li>
|
||||
<li><a href="javascript:void(0)">最新动态</a></li>
|
||||
<li><a href="javascript:void(0)">联系我们</a></li>
|
||||
{yz:nav}
|
||||
<li><a href="[field:url/]">[field:name/]</a></li>
|
||||
{/yz:nav}
|
||||
</ul>
|
||||
</div>
|
||||
<!-- menu -->
|
||||
@@ -148,137 +147,48 @@
|
||||
</section>
|
||||
<!-- 头部区域 结束 -->
|
||||
|
||||
<!--====== 关于我们 开始 ======-->
|
||||
<!--====== 新闻中心 开始 ======-->
|
||||
|
||||
<section class="about-area about-five">
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-lg-6 col-12">
|
||||
<div class="about-image-five">
|
||||
<svg class="shape" width="106" height="134" viewBox="0 0 106 134" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="1.66654" cy="1.66679" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="1.66654" cy="16.3335" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="1.66654" cy="31.0001" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="1.66654" cy="45.6668" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="1.66654" cy="60.3335" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="1.66654" cy="88.6668" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="1.66654" cy="117.667" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="1.66654" cy="74.6668" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="1.66654" cy="103" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="1.66654" cy="132" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="16.3333" cy="1.66679" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="16.3333" cy="16.3335" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="16.3333" cy="31.0001" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="16.3333" cy="45.6668" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="16.333" cy="60.3335" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="16.333" cy="88.6668" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="16.333" cy="117.667" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="16.333" cy="74.6668" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="16.333" cy="103" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="16.333" cy="132" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="30.9998" cy="1.66679" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="74.6665" cy="1.66679" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="30.9998" cy="16.3335" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="74.6665" cy="16.3335" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="30.9998" cy="31.0001" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="74.6665" cy="31.0001" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="30.9998" cy="45.6668" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="74.6665" cy="45.6668" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="31" cy="60.3335" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="74.6668" cy="60.3335" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="31" cy="88.6668" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="74.6668" cy="88.6668" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="31" cy="117.667" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="74.6668" cy="117.667" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="31" cy="74.6668" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="74.6668" cy="74.6668" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="31" cy="103" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="74.6668" cy="103" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="31" cy="132" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="74.6668" cy="132" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="45.6665" cy="1.66679" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="89.3333" cy="1.66679" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="45.6665" cy="16.3335" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="89.3333" cy="16.3335" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="45.6665" cy="31.0001" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="89.3333" cy="31.0001" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="45.6665" cy="45.6668" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="89.3333" cy="45.6668" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="45.6665" cy="60.3335" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="89.3333" cy="60.3335" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="45.6665" cy="88.6668" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="89.3333" cy="88.6668" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="45.6665" cy="117.667" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="89.3333" cy="117.667" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="45.6665" cy="74.6668" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="89.3333" cy="74.6668" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="45.6665" cy="103" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="89.3333" cy="103" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="45.6665" cy="132" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="89.3333" cy="132" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="60.3333" cy="1.66679" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="104" cy="1.66679" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="60.3333" cy="16.3335" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="104" cy="16.3335" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="60.3333" cy="31.0001" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="104" cy="31.0001" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="60.3333" cy="45.6668" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="104" cy="45.6668" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="60.333" cy="60.3335" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="104" cy="60.3335" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="60.333" cy="88.6668" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="104" cy="88.6668" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="60.333" cy="117.667" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="104" cy="117.667" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="60.333" cy="74.6668" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="104" cy="74.6668" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="60.333" cy="103" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="104" cy="103" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="60.333" cy="132" r="1.66667" fill="#DADADA" />
|
||||
<circle cx="104" cy="132" r="1.66667" fill="#DADADA" />
|
||||
</svg>
|
||||
<img src="assets/images/about/about-img1.jpg" alt="关于我们 - 团队与经验" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6 col-12">
|
||||
<div class="about-five-content">
|
||||
<h6 class="small-title text-lg">我们的故事</h6>
|
||||
<h2 class="main-title fw-bold">我们的团队拥有丰富的经验和专业知识</h2>
|
||||
<div class="about-five-tab">
|
||||
<nav>
|
||||
<div class="nav nav-tabs" id="nav-tab" role="tablist">
|
||||
<button class="nav-link active" id="nav-who-tab" data-bs-toggle="tab" data-bs-target="#nav-who"
|
||||
type="button" role="tab" aria-controls="nav-who" aria-selected="true">关于我们</button>
|
||||
<button class="nav-link" id="nav-vision-tab" data-bs-toggle="tab" data-bs-target="#nav-vision"
|
||||
type="button" role="tab" aria-controls="nav-vision" aria-selected="false">我们的愿景</button>
|
||||
<button class="nav-link" id="nav-history-tab" data-bs-toggle="tab" data-bs-target="#nav-history"
|
||||
type="button" role="tab" aria-controls="nav-history" aria-selected="false">发展历程</button>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="tab-content" id="nav-tabContent">
|
||||
<div class="tab-pane fade show active" id="nav-who" role="tabpanel" aria-labelledby="nav-who-tab">
|
||||
<p>我们相信,优秀的设计和清晰的技术能够塑造品牌的未来。我们专注于提供定制化的数字解决方案,帮助客户在竞争中脱颖而出。</p>
|
||||
<p>从初创企业到行业巨头,我们与各类品牌合作,通过创新驱动业务增长。我们的团队始终以客户需求为核心,打造兼具美感与功能的产品。</p>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="nav-vision" role="tabpanel" aria-labelledby="nav-vision-tab">
|
||||
<p>我们的愿景是成为数字创新领域的引领者,通过科技赋能商业,让每一个创意都能落地生根,创造真正的社会与商业价值。</p>
|
||||
<p>我们期待构建一个开放、协作的生态系统,与合作伙伴共同探索未来的可能性,推动行业持续进步。</p>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="nav-history" role="tabpanel" aria-labelledby="nav-history-tab">
|
||||
<p>自成立以来,我们始终秉持匠心精神,不断打磨技术与服务。从第一个项目到如今服务全球客户,每一步都见证着我们的成长与坚持。</p>
|
||||
<p>我们珍视每一次合作,并将其视为推动自我革新的契机。未来的路,我们期待与更多伙伴携手同行。</p>
|
||||
</div>
|
||||
</div>
|
||||
<section id="news-center" class="news-center-area">
|
||||
<div class="section-title-five">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="content">
|
||||
<h6>资讯动态</h6>
|
||||
<h2 class="fw-bold">新闻中心</h2>
|
||||
<p>了解最新动态、行业资讯与公司公告。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- container -->
|
||||
<div class="container">
|
||||
{yz:newscenter row="8" titlelen="28"}
|
||||
<div class="col-lg-3 col-md-6 col-12">
|
||||
<div class="single-news news-center-item">
|
||||
<div class="image">
|
||||
<a href="[field:arcurl/]"><img class="thumb" src="[field:image/]" alt="[field:titlefull/]" onerror="this.style.display='none'" /></a>
|
||||
<div class="meta-details">
|
||||
<span>[field:pubdate/]</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-body">
|
||||
<h4 class="title">
|
||||
<a href="[field:arcurl/]" title="[field:titlefull/]">[field:title/]</a>
|
||||
</h4>
|
||||
<p>[field:desc/]</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/yz:newscenter}
|
||||
<div class="news-center-more">
|
||||
<a href="/news" class="btn primary-btn-outline">查看更多</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!--====== 关于我们 结束 ======-->
|
||||
<!--====== 新闻中心 结束 ======-->
|
||||
|
||||
<!-- ===== 服务区域 开始 ===== -->
|
||||
<section id="services" class="services-area services-eight">
|
||||
@@ -288,10 +198,10 @@
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="content">
|
||||
<h6>我们的服务</h6>
|
||||
<h2 class="fw-bold">核心业务</h2>
|
||||
<h6>产品中心</h6>
|
||||
<h2 class="fw-bold">精选产品展示</h2>
|
||||
<p>
|
||||
我们提供从策略到执行的全链路服务,助力品牌实现数字化升级。
|
||||
面向企业数字化运营,提供开箱即用、可持续扩展的产品能力。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -302,84 +212,33 @@
|
||||
</div>
|
||||
<!--====== 标题区域 结束 ======-->
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="row product-showcase-grid">
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<div class="single-services">
|
||||
<div class="service-icon">
|
||||
<i class="lni lni-capsule"></i>
|
||||
</div>
|
||||
<div class="service-content">
|
||||
<h4>焕新设计</h4>
|
||||
<p>
|
||||
以用户为中心,打造兼具视觉冲击与极致体验的界面设计,让品牌形象焕然一新。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<article class="product-showcase-card product-showcase-card--primary">
|
||||
<div class="product-showcase-icon"><i class="lni lni-dashboard"></i></div>
|
||||
<span class="product-showcase-tag">运营增长</span>
|
||||
<h3>企业运营中台</h3>
|
||||
<p>统一连接客户、业务和数据,帮助团队快速构建可度量的运营闭环。</p>
|
||||
<a href="#contact" class="product-showcase-link">了解产品 <i class="lni lni-arrow-right"></i></a>
|
||||
</article>
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<div class="single-services">
|
||||
<div class="service-icon">
|
||||
<i class="lni lni-bootstrap"></i>
|
||||
</div>
|
||||
<div class="service-content">
|
||||
<h4>坚实 Bootstrap 5</h4>
|
||||
<p>
|
||||
基于最新 Bootstrap 5 框架,确保项目具有出色的响应式布局和稳定的性能表现。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<article class="product-showcase-card">
|
||||
<div class="product-showcase-icon"><i class="lni lni-layers"></i></div>
|
||||
<span class="product-showcase-tag">协同办公</span>
|
||||
<h3>智能协作平台</h3>
|
||||
<p>聚合任务、审批与知识资产,让跨部门协作清晰有序、高效推进。</p>
|
||||
<a href="#contact" class="product-showcase-link">了解产品 <i class="lni lni-arrow-right"></i></a>
|
||||
</article>
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<div class="single-services">
|
||||
<div class="service-icon">
|
||||
<i class="lni lni-shortcode"></i>
|
||||
</div>
|
||||
<div class="service-content">
|
||||
<h4>100+ 组件库</h4>
|
||||
<p>
|
||||
内置丰富的组件与模块,灵活组合,快速搭建企业级应用与营销页面。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<div class="single-services">
|
||||
<div class="service-icon">
|
||||
<i class="lni lni-dashboard"></i>
|
||||
</div>
|
||||
<div class="service-content">
|
||||
<h4>性能优化</h4>
|
||||
<p>
|
||||
从代码到资源加载,全方位优化,确保网站与应用拥有闪电般的访问速度。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<div class="single-services">
|
||||
<div class="service-icon">
|
||||
<i class="lni lni-layers"></i>
|
||||
</div>
|
||||
<div class="service-content">
|
||||
<h4>完全自定义</h4>
|
||||
<p>
|
||||
所有设计均支持深度定制,贴合品牌调性与业务需求,拒绝模板化。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<div class="single-services">
|
||||
<div class="service-icon">
|
||||
<i class="lni lni-reload"></i>
|
||||
</div>
|
||||
<div class="service-content">
|
||||
<h4>持续迭代</h4>
|
||||
<p>
|
||||
我们提供长期的技术支持与功能更新,保障产品始终与时俱进。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<article class="product-showcase-card">
|
||||
<div class="product-showcase-icon"><i class="lni lni-bar-chart"></i></div>
|
||||
<span class="product-showcase-tag">数据洞察</span>
|
||||
<h3>经营分析系统</h3>
|
||||
<p>将分散经营数据转化为实时洞察,为每一次关键决策提供可靠依据。</p>
|
||||
<a href="#contact" class="product-showcase-link">了解产品 <i class="lni lni-arrow-right"></i></a>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -387,117 +246,71 @@
|
||||
<!-- ===== 服务区域 结束 ===== -->
|
||||
|
||||
|
||||
<!-- 价格区域 开始 -->
|
||||
<section id="pricing" class="pricing-area pricing-fourteen">
|
||||
<!--====== 标题区域 开始 ======-->
|
||||
<!-- 解决方案区域 开始 -->
|
||||
<section id="pricing" class="solution-area">
|
||||
<div class="section-title-five">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="content">
|
||||
<h6>定价方案</h6>
|
||||
<h2 class="fw-bold">灵活的价格策略</h2>
|
||||
<h6>解决方案</h6>
|
||||
<h2 class="fw-bold">适配不同业务场景</h2>
|
||||
<p>
|
||||
我们提供多种套餐,满足不同规模企业的需求。所有方案均包含核心功能,无隐藏费用。
|
||||
从业务诊断到平台落地,为不同阶段、不同规模的企业提供针对性支持。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- row -->
|
||||
</div>
|
||||
<!-- container -->
|
||||
</div>
|
||||
<!--====== 标题区域 结束 ======-->
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-4 col-md-6 col-12">
|
||||
<div class="pricing-style-fourteen">
|
||||
<div class="table-head">
|
||||
<h6 class="title">入门版</h4>
|
||||
<p>适合个人开发者或小型项目,快速起步。</p>
|
||||
<div class="price">
|
||||
<h2 class="amount">
|
||||
<span class="currency">$</span>0<span class="duration">/月 </span>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="light-rounded-buttons">
|
||||
<a href="javascript:void(0)" class="btn primary-btn-outline">
|
||||
免费试用
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="table-content">
|
||||
<ul class="table-list">
|
||||
<li> <i class="lni lni-checkmark-circle"></i> 基础组件使用</li>
|
||||
<li> <i class="lni lni-checkmark-circle"></i> 社区支持</li>
|
||||
<li> <i class="lni lni-checkmark-circle deactive"></i> 高级组件库</li>
|
||||
<li> <i class="lni lni-checkmark-circle deactive"></i> 专属技术支持</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-4">
|
||||
<article class="solution-showcase-card">
|
||||
<span class="solution-showcase-index">01</span>
|
||||
<div class="solution-showcase-icon"><i class="lni lni-briefcase"></i></div>
|
||||
<h3>中小企业数字化</h3>
|
||||
<p>以轻量化产品快速梳理业务流程,降低协同和管理成本。</p>
|
||||
<ul>
|
||||
<li>业务流程在线化</li>
|
||||
<li>经营数据可视化</li>
|
||||
<li>模块化按需启用</li>
|
||||
</ul>
|
||||
<a href="#contact" class="solution-showcase-link">咨询方案 <i class="lni lni-arrow-right"></i></a>
|
||||
</article>
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-6 col-12">
|
||||
<div class="pricing-style-fourteen middle">
|
||||
<div class="table-head">
|
||||
<h6 class="title">进阶版</h4>
|
||||
<p>为成长型企业打造,提供更多扩展功能。</p>
|
||||
<div class="price">
|
||||
<h2 class="amount">
|
||||
<span class="currency">$</span>99<span class="duration">/月 </span>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="light-rounded-buttons">
|
||||
<a href="javascript:void(0)" class="btn primary-btn">
|
||||
免费试用
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="table-content">
|
||||
<ul class="table-list">
|
||||
<li> <i class="lni lni-checkmark-circle"></i> 全组件使用</li>
|
||||
<li> <i class="lni lni-checkmark-circle"></i> 优先邮件支持</li>
|
||||
<li> <i class="lni lni-checkmark-circle"></i> 自定义主题</li>
|
||||
<li> <i class="lni lni-checkmark-circle deactive"></i> 专属客户经理</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<article class="solution-showcase-card solution-showcase-card--featured">
|
||||
<span class="solution-showcase-index">02</span>
|
||||
<div class="solution-showcase-icon"><i class="lni lni-network"></i></div>
|
||||
<h3>集团协同管理</h3>
|
||||
<p>构建跨组织、跨区域的一体化管理平台,让总部与业务单元高效联动。</p>
|
||||
<ul>
|
||||
<li>多组织权限体系</li>
|
||||
<li>统一业务数据标准</li>
|
||||
<li>全链路经营管控</li>
|
||||
</ul>
|
||||
<a href="#contact" class="solution-showcase-link">咨询方案 <i class="lni lni-arrow-right"></i></a>
|
||||
</article>
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-6 col-12">
|
||||
<div class="pricing-style-fourteen">
|
||||
<div class="table-head">
|
||||
<h6 class="title">尊享版</h4>
|
||||
<p>面向大型组织,提供全方位定制与专属服务。</p>
|
||||
<div class="price">
|
||||
<h2 class="amount">
|
||||
<span class="currency">$</span>150<span class="duration">/月 </span>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="light-rounded-buttons">
|
||||
<a href="javascript:void(0)" class="btn primary-btn-outline">
|
||||
免费试用
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="table-content">
|
||||
<ul class="table-list">
|
||||
<li> <i class="lni lni-checkmark-circle"></i> 所有高级功能</li>
|
||||
<li> <i class="lni lni-checkmark-circle"></i> 7x24 技术支持</li>
|
||||
<li> <i class="lni lni-checkmark-circle"></i> 专属定制开发</li>
|
||||
<li> <i class="lni lni-checkmark-circle"></i> 战略咨询顾问</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<article class="solution-showcase-card">
|
||||
<span class="solution-showcase-index">03</span>
|
||||
<div class="solution-showcase-icon"><i class="lni lni-rocket"></i></div>
|
||||
<h3>业务增长与创新</h3>
|
||||
<p>以数据驱动产品迭代和客户运营,持续验证并放大增长机会。</p>
|
||||
<ul>
|
||||
<li>客户全生命周期运营</li>
|
||||
<li>实时指标监测分析</li>
|
||||
<li>敏捷迭代支持</li>
|
||||
</ul>
|
||||
<a href="#contact" class="solution-showcase-link">咨询方案 <i class="lni lni-arrow-right"></i></a>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!--/ 价格区域 结束 -->
|
||||
<!-- 解决方案区域 结束 -->
|
||||
|
||||
|
||||
|
||||
@@ -521,103 +334,6 @@
|
||||
</section>
|
||||
<!-- 行动号召区域 结束 -->
|
||||
|
||||
|
||||
|
||||
<!-- 最新动态区域 开始 -->
|
||||
<div id="blog" class="latest-news-area section">
|
||||
<!--====== 标题区域 开始 ======-->
|
||||
<div class="section-title-five">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="content">
|
||||
<h6>最新资讯</h6>
|
||||
<h2 class="fw-bold">新闻与博客</h2>
|
||||
<p>
|
||||
分享行业洞察、技术干货与团队动态,与您一同探索数字世界的边界。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- row -->
|
||||
</div>
|
||||
<!-- container -->
|
||||
</div>
|
||||
<!--====== 标题区域 结束 ======-->
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-4 col-md-6 col-12">
|
||||
<!-- 单篇新闻 -->
|
||||
<div class="single-news">
|
||||
<div class="image">
|
||||
<a href="javascript:void(0)"><img class="thumb" src="assets/images/blog/1.jpg" alt="博客文章" /></a>
|
||||
<div class="meta-details">
|
||||
<img class="thumb" src="assets/images/blog/b6.jpg" alt="作者头像" />
|
||||
<span>作者:张明</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-body">
|
||||
<h4 class="title">
|
||||
<a href="javascript:void(0)"> 如何打造设计驱动的团队文化 </a>
|
||||
</h4>
|
||||
<p>
|
||||
设计不仅仅是美学,更是解决问题的思维方式。本文分享了我们在团队中推行设计驱动的实践与经验。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 单篇新闻结束 -->
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-6 col-12">
|
||||
<!-- 单篇新闻 -->
|
||||
<div class="single-news">
|
||||
<div class="image">
|
||||
<a href="javascript:void(0)"><img class="thumb" src="assets/images/blog/2.jpg" alt="博客文章" /></a>
|
||||
<div class="meta-details">
|
||||
<img class="thumb" src="assets/images/blog/b6.jpg" alt="作者头像" />
|
||||
<span>作者:李薇</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-body">
|
||||
<h4 class="title">
|
||||
<a href="javascript:void(0)">
|
||||
2026 年最值得关注的前端框架
|
||||
</a>
|
||||
</h4>
|
||||
<p>
|
||||
技术演进日新月异,我们梳理了当前最具潜力与社区活力的前端框架,助你做出更明智的技术选型。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 单篇新闻结束 -->
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-6 col-12">
|
||||
<!-- 单篇新闻 -->
|
||||
<div class="single-news">
|
||||
<div class="image">
|
||||
<a href="javascript:void(0)"><img class="thumb" src="assets/images/blog/3.jpg" alt="博客文章" /></a>
|
||||
<div class="meta-details">
|
||||
<img class="thumb" src="assets/images/blog/b6.jpg" alt="作者头像" />
|
||||
<span>作者:王磊</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-body">
|
||||
<h4 class="title">
|
||||
<a href="javascript:void(0)">
|
||||
提升用户留存的 5 个关键策略
|
||||
</a>
|
||||
</h4>
|
||||
<p>
|
||||
用户留存是增长的基石。我们结合案例,总结了提升用户粘性与活跃度的实用方法,值得收藏。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 单篇新闻结束 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 最新动态区域 结束 -->
|
||||
|
||||
<!-- 品牌展示区域 开始 -->
|
||||
<div id="clients" class="brand-area section">
|
||||
<!--====== 标题区域 开始 ======-->
|
||||
@@ -682,8 +398,8 @@
|
||||
</div>
|
||||
<div class="contact-content">
|
||||
<h4>联系方式</h4>
|
||||
<p>0984537278623</p>
|
||||
<p>yourmail@yunzer.cn</p>
|
||||
<p>{yz:phone}</p>
|
||||
<p>{yz:email}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -694,8 +410,7 @@
|
||||
</div>
|
||||
<div class="contact-content">
|
||||
<h4>公司地址</h4>
|
||||
<p>北京市朝阳区建国路88号SOHO现代城</p>
|
||||
<p>中国 · 北京</p>
|
||||
<p>{yz:address}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -786,7 +501,7 @@
|
||||
<!-- 单个小组件 -->
|
||||
<div class="footer-widget f-about">
|
||||
<div class="logo">
|
||||
<a href="index.html">
|
||||
<a href="/">
|
||||
<img src="{yz:logo}" alt="云泽网" class="img-fluid" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||
<meta name="description" content="{yz:description}" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||
<title>{yz:sitename}</title>
|
||||
<link rel="shortcut icon" href="{yz:ico}" type="image/svg" />
|
||||
<link rel="stylesheet" href="assets/css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="assets/css/lineicons.css" />
|
||||
<link rel="stylesheet" href="assets/css/tiny-slider.css" />
|
||||
<link rel="stylesheet" href="assets/css/glightbox.min.css" />
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<!--====== 导航栏 开始 ======-->
|
||||
<section class="navbar-area navbar-nine">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<a class="navbar-brand" href="/">
|
||||
<img src="{yz:logo}" alt="{yz:sitename}" />
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNine"
|
||||
aria-controls="navbarNine" aria-expanded="false" aria-label="切换导航">
|
||||
<span class="toggler-icon"></span>
|
||||
<span class="toggler-icon"></span>
|
||||
<span class="toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse sub-menu-bar" id="navbarNine">
|
||||
<ul class="navbar-nav me-auto">
|
||||
<li class="nav-item">
|
||||
<a href="/">主页</a>
|
||||
</li>
|
||||
{yz:nav}
|
||||
<li class="nav-item">
|
||||
<a href="[field:url/]">[field:name/]</a>
|
||||
[field:children/]
|
||||
</li>
|
||||
{/yz:nav}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="navbar-btn d-none d-lg-inline-block">
|
||||
<a class="menu-bar" href="#side-menu-left"><i class="lni lni-menu"></i></a>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!--====== 导航栏 结束 ======-->
|
||||
|
||||
<!--====== 侧边栏 开始 ======-->
|
||||
<div class="sidebar-left">
|
||||
<div class="sidebar-close">
|
||||
<a class="close" href="#close"><i class="lni lni-close"></i></a>
|
||||
</div>
|
||||
<div class="sidebar-content">
|
||||
<div class="sidebar-logo">
|
||||
<a href="/"><img src="{yz:logo}" alt="{yz:sitename}" /></a>
|
||||
</div>
|
||||
<p class="text">{yz:description}</p>
|
||||
<div class="sidebar-menu">
|
||||
<h5 class="menu-title">快速链接</h5>
|
||||
<ul>
|
||||
{yz:nav}
|
||||
<li><a href="[field:url/]">[field:name/]</a></li>
|
||||
{/yz:nav}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="sidebar-social align-items-center justify-content-center">
|
||||
<h5 class="social-title">关注我们</h5>
|
||||
<ul>
|
||||
<li><a href="javascript:void(0)"><i class="lni lni-facebook-filled"></i></a></li>
|
||||
<li><a href="javascript:void(0)"><i class="lni lni-twitter-original"></i></a></li>
|
||||
<li><a href="javascript:void(0)"><i class="lni lni-linkedin-original"></i></a></li>
|
||||
<li><a href="javascript:void(0)"><i class="lni lni-youtube"></i></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overlay-left"></div>
|
||||
<!--====== 侧边栏 结束 ======-->
|
||||
|
||||
<!--====== 新闻中心 开始 ======-->
|
||||
<section id="news-center" class="news-center-area news-list-page">
|
||||
<div class="article-detail-breadcrumb-wrap">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb article-detail-breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/">主页</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">新闻中心</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-title-five">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="content">
|
||||
<h6>资讯动态</h6>
|
||||
<h2 class="fw-bold">新闻中心</h2>
|
||||
<p>了解最新动态、行业资讯与公司公告。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
{yz:arclist row="10" titlelen="40"}
|
||||
<div class="col-lg-3 col-md-6 col-12">
|
||||
<div class="single-news news-center-item">
|
||||
<div class="image">
|
||||
<a href="[field:arcurl/]"><img class="thumb" src="[field:image/]" alt="[field:titlefull/]" onerror="this.style.display='none'" /></a>
|
||||
<div class="meta-details">
|
||||
<span>[field:pubdate/]</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-body">
|
||||
<h4 class="title">
|
||||
<a href="[field:arcurl/]" title="[field:titlefull/]">[field:title/]</a>
|
||||
</h4>
|
||||
<p>[field:desc/]</p>
|
||||
<div class="news-item-meta">
|
||||
<span><i class="lni lni-user"></i> [field:author/]</span>
|
||||
<span><i class="lni lni-eye"></i> [field:views/]</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/yz:arclist}
|
||||
</div>
|
||||
|
||||
<div class="news-list-pager">{yz:pagelist/}</div>
|
||||
</div>
|
||||
</section>
|
||||
<!--====== 新闻中心 结束 ======-->
|
||||
|
||||
<!-- 页脚区域 开始 -->
|
||||
<footer class="footer-area footer-eleven">
|
||||
<div class="footer-top">
|
||||
<div class="container">
|
||||
<div class="inner-content">
|
||||
<div class="row">
|
||||
<div class="col-lg-4 col-md-6 col-12">
|
||||
<div class="footer-widget f-about">
|
||||
<div class="logo">
|
||||
<a href="/">
|
||||
<img src="{yz:logo}" alt="{yz:sitename}" class="img-fluid" />
|
||||
</a>
|
||||
</div>
|
||||
<p>{yz:description}</p>
|
||||
<p class="copyright-text">
|
||||
<span>备案号:{yz:icp}<br>{yz:copyright} <a href="www.yunzer.cn" rel="nofollow"> 云泽网 </a></span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-6 col-12">
|
||||
<div class="footer-widget f-link">
|
||||
<h5>解决方案</h5>
|
||||
<ul>
|
||||
<li><a href="javascript:void(0)">市场营销</a></li>
|
||||
<li><a href="javascript:void(0)">数据分析</a></li>
|
||||
<li><a href="javascript:void(0)">电子商务</a></li>
|
||||
<li><a href="javascript:void(0)">商业洞察</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-6 col-12">
|
||||
<div class="footer-widget f-link">
|
||||
<h5>技术支持</h5>
|
||||
<ul>
|
||||
<li><a href="javascript:void(0)">价格方案</a></li>
|
||||
<li><a href="javascript:void(0)">开发文档</a></li>
|
||||
<li><a href="javascript:void(0)">使用指南</a></li>
|
||||
<li><a href="javascript:void(0)">API 状态</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-6 col-12">
|
||||
<div class="footer-widget newsletter">
|
||||
<h5>订阅我们</h5>
|
||||
<p>订阅我们,获取最新资讯</p>
|
||||
<form action="#" method="get" target="_blank" class="newsletter-form">
|
||||
<input name="EMAIL" placeholder="邮箱地址" required="required" type="email" />
|
||||
<div class="button">
|
||||
<button class="sub-btn">
|
||||
<i class="lni lni-envelope"></i>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<!-- 页脚区域 结束 -->
|
||||
|
||||
<a href="#" class="scroll-top btn-hover">
|
||||
<i class="lni lni-chevron-up"></i>
|
||||
</a>
|
||||
|
||||
<script src="assets/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="assets/js/glightbox.min.js"></script>
|
||||
<script src="assets/js/main.js"></script>
|
||||
<script src="assets/js/tiny-slider.js"></script>
|
||||
<script>
|
||||
let navbarTogglerNine = document.querySelector(".navbar-nine .navbar-toggler");
|
||||
navbarTogglerNine.addEventListener("click", function () {
|
||||
navbarTogglerNine.classList.toggle("active");
|
||||
});
|
||||
|
||||
let sidebarLeft = document.querySelector(".sidebar-left");
|
||||
let overlayLeft = document.querySelector(".overlay-left");
|
||||
let sidebarClose = document.querySelector(".sidebar-close .close");
|
||||
|
||||
overlayLeft.addEventListener("click", function () {
|
||||
sidebarLeft.classList.toggle("open");
|
||||
overlayLeft.classList.toggle("open");
|
||||
});
|
||||
sidebarClose.addEventListener("click", function () {
|
||||
sidebarLeft.classList.remove("open");
|
||||
overlayLeft.classList.remove("open");
|
||||
});
|
||||
|
||||
let sideMenuLeftNine = document.querySelector(".navbar-nine .menu-bar");
|
||||
sideMenuLeftNine.addEventListener("click", function () {
|
||||
sidebarLeft.classList.add("open");
|
||||
overlayLeft.classList.add("open");
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,243 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||
<meta name="description" content="{yz:description}" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||
<title>{yz:sitename}</title>
|
||||
<link rel="shortcut icon" href="{yz:ico}" type="image/svg" />
|
||||
<link rel="stylesheet" href="assets/css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="assets/css/lineicons.css" />
|
||||
<link rel="stylesheet" href="assets/css/tiny-slider.css" />
|
||||
<link rel="stylesheet" href="assets/css/glightbox.min.css" />
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<!--====== 导航栏 开始 ======-->
|
||||
<section class="navbar-area navbar-nine">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<a class="navbar-brand" href="/">
|
||||
<img src="{yz:logo}" alt="{yz:sitename}" />
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNine"
|
||||
aria-controls="navbarNine" aria-expanded="false" aria-label="切换导航">
|
||||
<span class="toggler-icon"></span>
|
||||
<span class="toggler-icon"></span>
|
||||
<span class="toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse sub-menu-bar" id="navbarNine">
|
||||
<ul class="navbar-nav me-auto">
|
||||
<li class="nav-item">
|
||||
<a href="/">主页</a>
|
||||
</li>
|
||||
{yz:nav}
|
||||
<li class="nav-item">
|
||||
<a href="[field:url/]">[field:name/]</a>
|
||||
[field:children/]
|
||||
</li>
|
||||
{/yz:nav}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="navbar-btn d-none d-lg-inline-block">
|
||||
<a class="menu-bar" href="#side-menu-left"><i class="lni lni-menu"></i></a>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!--====== 导航栏 结束 ======-->
|
||||
|
||||
<!--====== 侧边栏 开始 ======-->
|
||||
<div class="sidebar-left">
|
||||
<div class="sidebar-close">
|
||||
<a class="close" href="#close"><i class="lni lni-close"></i></a>
|
||||
</div>
|
||||
<div class="sidebar-content">
|
||||
<div class="sidebar-logo">
|
||||
<a href="/"><img src="{yz:logo}" alt="{yz:sitename}" /></a>
|
||||
</div>
|
||||
<p class="text">{yz:description}</p>
|
||||
<div class="sidebar-menu">
|
||||
<h5 class="menu-title">快速链接</h5>
|
||||
<ul>
|
||||
{yz:nav}
|
||||
<li><a href="[field:url/]">[field:name/]</a></li>
|
||||
{/yz:nav}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="sidebar-social align-items-center justify-content-center">
|
||||
<h5 class="social-title">关注我们</h5>
|
||||
<ul>
|
||||
<li><a href="javascript:void(0)"><i class="lni lni-facebook-filled"></i></a></li>
|
||||
<li><a href="javascript:void(0)"><i class="lni lni-twitter-original"></i></a></li>
|
||||
<li><a href="javascript:void(0)"><i class="lni lni-linkedin-original"></i></a></li>
|
||||
<li><a href="javascript:void(0)"><i class="lni lni-youtube"></i></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overlay-left"></div>
|
||||
<!--====== 侧边栏 结束 ======-->
|
||||
|
||||
<!--====== 文章详情 开始 ======-->
|
||||
<section class="article-detail-area">
|
||||
<div class="article-detail-breadcrumb-wrap">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb article-detail-breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/">主页</a></li>
|
||||
<li class="breadcrumb-item"><a href="/news">新闻中心</a></li>
|
||||
{yz:arcview}
|
||||
<li class="breadcrumb-item active" aria-current="page">[field:title/]</li>
|
||||
{/yz:arcview}
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-10 col-xl-9">
|
||||
{yz:arcview}
|
||||
<article class="article-detail-card">
|
||||
<div class="article-detail-thumb" style="display:none;">
|
||||
<img src="[field:image/]" alt="[field:title/]" class="article-cover" onerror="this.closest('.article-detail-thumb').style.display='none'" onload="this.closest('.article-detail-thumb').style.display='block'" />
|
||||
</div>
|
||||
|
||||
<h1 class="article-detail-title">[field:title/]</h1>
|
||||
|
||||
<div class="article-detail-meta">
|
||||
<span><i class="lni lni-calendar"></i> [field:pubdate/]</span>
|
||||
<span><i class="lni lni-user"></i> [field:author/]</span>
|
||||
<span><i class="lni lni-eye"></i> [field:views/] 阅读</span>
|
||||
</div>
|
||||
|
||||
<div class="article-detail-content">
|
||||
[field:content/]
|
||||
</div>
|
||||
</article>
|
||||
{/yz:arcview}
|
||||
|
||||
<div class="article-detail-back">
|
||||
<a href="/news" class="btn primary-btn-outline">
|
||||
<i class="lni lni-arrow-left"></i> 返回新闻中心
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!--====== 文章详情 结束 ======-->
|
||||
|
||||
<!-- 页脚区域 开始 -->
|
||||
<footer class="footer-area footer-eleven">
|
||||
<div class="footer-top">
|
||||
<div class="container">
|
||||
<div class="inner-content">
|
||||
<div class="row">
|
||||
<div class="col-lg-4 col-md-6 col-12">
|
||||
<div class="footer-widget f-about">
|
||||
<div class="logo">
|
||||
<a href="/">
|
||||
<img src="{yz:logo}" alt="{yz:sitename}" class="img-fluid" />
|
||||
</a>
|
||||
</div>
|
||||
<p>{yz:description}</p>
|
||||
<p class="copyright-text">
|
||||
<span>备案号:{yz:icp}<br>{yz:copyright} <a href="www.yunzer.cn" rel="nofollow"> 云泽网 </a></span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-6 col-12">
|
||||
<div class="footer-widget f-link">
|
||||
<h5>解决方案</h5>
|
||||
<ul>
|
||||
<li><a href="javascript:void(0)">市场营销</a></li>
|
||||
<li><a href="javascript:void(0)">数据分析</a></li>
|
||||
<li><a href="javascript:void(0)">电子商务</a></li>
|
||||
<li><a href="javascript:void(0)">商业洞察</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-6 col-12">
|
||||
<div class="footer-widget f-link">
|
||||
<h5>技术支持</h5>
|
||||
<ul>
|
||||
<li><a href="javascript:void(0)">价格方案</a></li>
|
||||
<li><a href="javascript:void(0)">开发文档</a></li>
|
||||
<li><a href="javascript:void(0)">使用指南</a></li>
|
||||
<li><a href="javascript:void(0)">API 状态</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-6 col-12">
|
||||
<div class="footer-widget newsletter">
|
||||
<h5>订阅我们</h5>
|
||||
<p>订阅我们,获取最新资讯</p>
|
||||
<form action="#" method="get" target="_blank" class="newsletter-form">
|
||||
<input name="EMAIL" placeholder="邮箱地址" required="required" type="email" />
|
||||
<div class="button">
|
||||
<button class="sub-btn">
|
||||
<i class="lni lni-envelope"></i>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<!-- 页脚区域 结束 -->
|
||||
|
||||
<a href="#" class="scroll-top btn-hover">
|
||||
<i class="lni lni-chevron-up"></i>
|
||||
</a>
|
||||
|
||||
<script src="assets/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="assets/js/glightbox.min.js"></script>
|
||||
<script src="assets/js/main.js"></script>
|
||||
<script src="assets/js/tiny-slider.js"></script>
|
||||
<script>
|
||||
let navbarTogglerNine = document.querySelector(".navbar-nine .navbar-toggler");
|
||||
navbarTogglerNine.addEventListener("click", function () {
|
||||
navbarTogglerNine.classList.toggle("active");
|
||||
});
|
||||
|
||||
let sidebarLeft = document.querySelector(".sidebar-left");
|
||||
let overlayLeft = document.querySelector(".overlay-left");
|
||||
let sidebarClose = document.querySelector(".sidebar-close .close");
|
||||
|
||||
overlayLeft.addEventListener("click", function () {
|
||||
sidebarLeft.classList.toggle("open");
|
||||
overlayLeft.classList.toggle("open");
|
||||
});
|
||||
sidebarClose.addEventListener("click", function () {
|
||||
sidebarLeft.classList.remove("open");
|
||||
overlayLeft.classList.remove("open");
|
||||
});
|
||||
|
||||
let sideMenuLeftNine = document.querySelector(".navbar-nine .menu-bar");
|
||||
sideMenuLeftNine.addEventListener("click", function () {
|
||||
sidebarLeft.classList.add("open");
|
||||
overlayLeft.classList.add("open");
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,189 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||
<meta name="description" content="{yz:description}" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||
<title>{yz:sitename}</title>
|
||||
<link rel="shortcut icon" href="{yz:ico}" type="image/svg" />
|
||||
<link rel="stylesheet" href="assets/css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="assets/css/lineicons.css" />
|
||||
<link rel="stylesheet" href="assets/css/tiny-slider.css" />
|
||||
<link rel="stylesheet" href="assets/css/glightbox.min.css" />
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<section class="navbar-area navbar-nine">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<a class="navbar-brand" href="/">
|
||||
<img src="{yz:logo}" alt="{yz:sitename}" />
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNine"
|
||||
aria-controls="navbarNine" aria-expanded="false" aria-label="切换导航">
|
||||
<span class="toggler-icon"></span>
|
||||
<span class="toggler-icon"></span>
|
||||
<span class="toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse sub-menu-bar" id="navbarNine">
|
||||
<ul class="navbar-nav me-auto">
|
||||
<li class="nav-item"><a href="/">主页</a></li>
|
||||
{yz:nav}
|
||||
<li class="nav-item">
|
||||
<a href="[field:url/]">[field:name/]</a>
|
||||
[field:children/]
|
||||
</li>
|
||||
{/yz:nav}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="navbar-btn d-none d-lg-inline-block">
|
||||
<a class="menu-bar" href="#side-menu-left"><i class="lni lni-menu"></i></a>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="sidebar-left">
|
||||
<div class="sidebar-close"><a class="close" href="#close"><i class="lni lni-close"></i></a></div>
|
||||
<div class="sidebar-content">
|
||||
<div class="sidebar-logo"><a href="/"><img src="{yz:logo}" alt="{yz:sitename}" /></a></div>
|
||||
<p class="text">{yz:description}</p>
|
||||
<div class="sidebar-menu">
|
||||
<h5 class="menu-title">快速链接</h5>
|
||||
<ul>
|
||||
{yz:nav}
|
||||
<li><a href="[field:url/]">[field:name/]</a></li>
|
||||
{/yz:nav}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="sidebar-social align-items-center justify-content-center">
|
||||
<h5 class="social-title">关注我们</h5>
|
||||
<ul>
|
||||
<li><a href="javascript:void(0)"><i class="lni lni-facebook-filled"></i></a></li>
|
||||
<li><a href="javascript:void(0)"><i class="lni lni-twitter-original"></i></a></li>
|
||||
<li><a href="javascript:void(0)"><i class="lni lni-linkedin-original"></i></a></li>
|
||||
<li><a href="javascript:void(0)"><i class="lni lni-youtube"></i></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overlay-left"></div>
|
||||
|
||||
<section class="article-detail-area page-inner-page">
|
||||
<div class="article-detail-breadcrumb-wrap">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb article-detail-breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/">主页</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">{yz:onepage field="title"/}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-10 col-xl-9">
|
||||
<article class="article-detail-card">
|
||||
<h1 class="article-detail-title">{yz:onepage field="title"/}</h1>
|
||||
<div class="onepage-content">
|
||||
{yz:onepage/}
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="footer-area footer-eleven">
|
||||
<div class="footer-top">
|
||||
<div class="container">
|
||||
<div class="inner-content">
|
||||
<div class="row">
|
||||
<div class="col-lg-4 col-md-6 col-12">
|
||||
<div class="footer-widget f-about">
|
||||
<div class="logo"><a href="/"><img src="{yz:logo}" alt="{yz:sitename}" class="img-fluid" /></a></div>
|
||||
<p>{yz:description}</p>
|
||||
<p class="copyright-text">
|
||||
<span>备案号:{yz:icp}<br>{yz:copyright} <a href="www.yunzer.cn" rel="nofollow"> 云泽网 </a></span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-6 col-12">
|
||||
<div class="footer-widget f-link">
|
||||
<h5>解决方案</h5>
|
||||
<ul>
|
||||
<li><a href="javascript:void(0)">市场营销</a></li>
|
||||
<li><a href="javascript:void(0)">数据分析</a></li>
|
||||
<li><a href="javascript:void(0)">电子商务</a></li>
|
||||
<li><a href="javascript:void(0)">商业洞察</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-6 col-12">
|
||||
<div class="footer-widget f-link">
|
||||
<h5>技术支持</h5>
|
||||
<ul>
|
||||
<li><a href="javascript:void(0)">价格方案</a></li>
|
||||
<li><a href="javascript:void(0)">开发文档</a></li>
|
||||
<li><a href="javascript:void(0)">使用指南</a></li>
|
||||
<li><a href="javascript:void(0)">API 状态</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-6 col-12">
|
||||
<div class="footer-widget newsletter">
|
||||
<h5>订阅我们</h5>
|
||||
<p>订阅我们,获取最新资讯</p>
|
||||
<form action="#" method="get" target="_blank" class="newsletter-form">
|
||||
<input name="EMAIL" placeholder="邮箱地址" required="required" type="email" />
|
||||
<div class="button"><button class="sub-btn"><i class="lni lni-envelope"></i></button></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<a href="#" class="scroll-top btn-hover"><i class="lni lni-chevron-up"></i></a>
|
||||
|
||||
<script src="assets/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="assets/js/glightbox.min.js"></script>
|
||||
<script src="assets/js/main.js"></script>
|
||||
<script src="assets/js/tiny-slider.js"></script>
|
||||
<script>
|
||||
document.querySelector(".navbar-nine .navbar-toggler").addEventListener("click", function () {
|
||||
this.classList.toggle("active");
|
||||
});
|
||||
var sidebarLeft = document.querySelector(".sidebar-left");
|
||||
var overlayLeft = document.querySelector(".overlay-left");
|
||||
overlayLeft.addEventListener("click", function () {
|
||||
sidebarLeft.classList.toggle("open");
|
||||
overlayLeft.classList.toggle("open");
|
||||
});
|
||||
document.querySelector(".sidebar-close .close").addEventListener("click", function () {
|
||||
sidebarLeft.classList.remove("open");
|
||||
overlayLeft.classList.remove("open");
|
||||
});
|
||||
document.querySelector(".navbar-nine .menu-bar").addEventListener("click", function () {
|
||||
sidebarLeft.classList.add("open");
|
||||
overlayLeft.classList.add("open");
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1478,6 +1478,167 @@ p {
|
||||
|
||||
|
||||
|
||||
/*===== news-center-area =====*/
|
||||
.news-center-area {
|
||||
background-color: var(--light-3);
|
||||
padding-top: 100px;
|
||||
padding-bottom: 80px;
|
||||
}
|
||||
@media only screen and (min-width: 768px) and (max-width: 991px) {
|
||||
.news-center-area {
|
||||
padding-top: 80px;
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.news-center-area {
|
||||
padding-top: 60px;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
}
|
||||
.news-center-area .news-center-tabs {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.news-center-area .news-center-tabs .nav-tabs {
|
||||
border: none;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.news-center-area .news-center-tabs .nav-link {
|
||||
border: none;
|
||||
color: var(--dark-1);
|
||||
font-weight: 600;
|
||||
background-color: var(--white);
|
||||
padding: 10px 22px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.news-center-area .news-center-tabs .nav-link:hover {
|
||||
color: var(--primary);
|
||||
}
|
||||
.news-center-area .news-center-tabs .nav-link.active {
|
||||
background-color: var(--primary);
|
||||
color: var(--white);
|
||||
}
|
||||
.news-center-area .news-center-item {
|
||||
margin-top: 30px;
|
||||
background: var(--white);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
height: calc(100% - 30px);
|
||||
}
|
||||
.news-center-area .news-center-item .image {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
min-height: 160px;
|
||||
background: var(--light-2);
|
||||
}
|
||||
.news-center-area .news-center-item .image img {
|
||||
width: 100%;
|
||||
height: 180px;
|
||||
object-fit: cover;
|
||||
transition: all 0.4s ease;
|
||||
}
|
||||
.news-center-area .news-center-item .image .meta-details {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
bottom: 12px;
|
||||
background-color: var(--primary);
|
||||
color: var(--white);
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.news-center-area .news-center-item .content-body {
|
||||
padding: 18px 16px 20px;
|
||||
}
|
||||
.news-center-area .news-center-item .content-body .title {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
.news-center-area .news-center-item .content-body .title a {
|
||||
color: var(--black);
|
||||
}
|
||||
.news-center-area .news-center-item .content-body .title a:hover {
|
||||
color: var(--primary);
|
||||
}
|
||||
.news-center-area .news-center-item .content-body p {
|
||||
color: var(--dark-3);
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
.news-center-area .news-center-item:hover .image .thumb {
|
||||
transform: scale(1.08);
|
||||
}
|
||||
.news-center-area .news-center-more {
|
||||
text-align: center;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
/*===== news-list-page =====*/
|
||||
.news-list-page {
|
||||
padding-top: 0;
|
||||
}
|
||||
.news-list-page .section-title-five {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.news-list-page .news-item-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px 16px;
|
||||
margin-top: 12px;
|
||||
color: var(--gray-1);
|
||||
font-size: 12px;
|
||||
}
|
||||
.news-list-page .news-item-meta span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.news-list-page .news-item-meta i {
|
||||
color: var(--primary);
|
||||
font-size: 14px;
|
||||
}
|
||||
.news-list-pager {
|
||||
margin-top: 40px;
|
||||
margin-bottom: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
.news-list-pager .yz-pagination {
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.news-list-pager .yz-page {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 38px;
|
||||
height: 38px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid var(--gray-4);
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--dark-2);
|
||||
background: var(--white);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.news-list-pager .yz-page:hover {
|
||||
color: var(--primary);
|
||||
border-color: var(--primary);
|
||||
}
|
||||
.news-list-pager .yz-page.current {
|
||||
background: var(--primary);
|
||||
border-color: var(--primary);
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
/*===== latest-news-area =====*/
|
||||
.latest-news-area {
|
||||
background: var(--white);
|
||||
@@ -1905,3 +2066,400 @@ p {
|
||||
color: var(--white);
|
||||
background-color: var(--primary-dark);
|
||||
}
|
||||
|
||||
/*===== article-detail-area =====*/
|
||||
.article-detail-area {
|
||||
padding-bottom: 80px;
|
||||
background-color: var(--light-3);
|
||||
}
|
||||
.article-detail-breadcrumb-wrap {
|
||||
padding-top: 120px;
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
@media (max-width: 991px) {
|
||||
.article-detail-breadcrumb-wrap {
|
||||
padding-top: 100px;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
}
|
||||
.article-detail-breadcrumb {
|
||||
justify-content: flex-start;
|
||||
margin-bottom: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
font-size: 14px;
|
||||
}
|
||||
.article-detail-breadcrumb .breadcrumb-item a {
|
||||
color: var(--dark-3);
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
.article-detail-breadcrumb .breadcrumb-item a:hover {
|
||||
color: var(--primary);
|
||||
}
|
||||
.article-detail-breadcrumb .breadcrumb-item.active {
|
||||
color: var(--black);
|
||||
font-weight: 500;
|
||||
max-width: 420px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.article-detail-breadcrumb .breadcrumb-item + .breadcrumb-item::before {
|
||||
color: var(--gray-2);
|
||||
}
|
||||
.article-detail-card {
|
||||
background: var(--white);
|
||||
border-radius: 12px;
|
||||
box-shadow: var(--shadow-2);
|
||||
padding: 40px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.article-detail-card {
|
||||
padding: 24px 20px;
|
||||
}
|
||||
}
|
||||
.article-detail-thumb {
|
||||
margin-bottom: 28px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.article-detail-thumb .article-cover {
|
||||
width: 100%;
|
||||
max-height: 420px;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
.article-detail-title {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
color: var(--black);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.article-detail-title {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
.article-detail-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px 24px;
|
||||
padding-bottom: 24px;
|
||||
margin-bottom: 28px;
|
||||
border-bottom: 1px solid var(--gray-4);
|
||||
color: var(--dark-3);
|
||||
font-size: 14px;
|
||||
}
|
||||
.article-detail-meta span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.article-detail-meta i {
|
||||
color: var(--primary);
|
||||
font-size: 16px;
|
||||
}
|
||||
.article-detail-content {
|
||||
color: var(--dark-2);
|
||||
font-size: 16px;
|
||||
line-height: 1.85;
|
||||
word-break: break-word;
|
||||
}
|
||||
.article-detail-content p {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.article-detail-content img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
margin: 12px 0 20px;
|
||||
}
|
||||
.article-detail-content h2,
|
||||
.article-detail-content h3,
|
||||
.article-detail-content h4 {
|
||||
color: var(--black);
|
||||
font-weight: 700;
|
||||
margin: 28px 0 14px;
|
||||
}
|
||||
.article-detail-content h2 {
|
||||
font-size: 24px;
|
||||
}
|
||||
.article-detail-content h3 {
|
||||
font-size: 20px;
|
||||
}
|
||||
.article-detail-content h4 {
|
||||
font-size: 18px;
|
||||
}
|
||||
.article-detail-content ul,
|
||||
.article-detail-content ol {
|
||||
margin: 0 0 16px 20px;
|
||||
padding: 0;
|
||||
}
|
||||
.article-detail-content li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.article-detail-content blockquote {
|
||||
margin: 20px 0;
|
||||
padding: 16px 20px;
|
||||
border-left: 4px solid var(--primary);
|
||||
background: var(--primary-light);
|
||||
border-radius: 0 8px 8px 0;
|
||||
color: var(--dark-2);
|
||||
}
|
||||
.article-detail-content a {
|
||||
color: var(--primary);
|
||||
}
|
||||
.article-detail-content a:hover {
|
||||
color: var(--primary-dark);
|
||||
}
|
||||
.article-detail-content table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.article-detail-content table th,
|
||||
.article-detail-content table td {
|
||||
border: 1px solid var(--gray-4);
|
||||
padding: 10px 12px;
|
||||
}
|
||||
.article-detail-back {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.article-detail-back .btn i {
|
||||
margin-right: 6px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/*===== 首页产品与解决方案展示 =====*/
|
||||
.product-showcase-grid {
|
||||
row-gap: 24px;
|
||||
}
|
||||
.product-showcase-card {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
min-height: 320px;
|
||||
padding: 36px 32px 30px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--gray-4);
|
||||
border-radius: 16px;
|
||||
background: var(--white);
|
||||
box-shadow: var(--shadow-2);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease;
|
||||
}
|
||||
.product-showcase-card::after {
|
||||
position: absolute;
|
||||
right: -46px;
|
||||
bottom: -58px;
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
border-radius: 50%;
|
||||
background: var(--primary-light);
|
||||
content: "";
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
.product-showcase-card:hover {
|
||||
transform: translateY(-8px);
|
||||
border-color: var(--primary);
|
||||
box-shadow: var(--shadow-5);
|
||||
}
|
||||
.product-showcase-card:hover::after {
|
||||
transform: scale(1.25);
|
||||
}
|
||||
.product-showcase-card--primary {
|
||||
border-color: var(--primary);
|
||||
background: var(--gradient-1);
|
||||
}
|
||||
.product-showcase-card--primary::after {
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
.product-showcase-icon,
|
||||
.product-showcase-tag,
|
||||
.product-showcase-card h3,
|
||||
.product-showcase-card p,
|
||||
.product-showcase-link {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.product-showcase-icon {
|
||||
display: flex;
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 26px;
|
||||
border-radius: 14px;
|
||||
background: var(--primary-light);
|
||||
color: var(--primary);
|
||||
font-size: 24px;
|
||||
}
|
||||
.product-showcase-tag {
|
||||
display: inline-block;
|
||||
margin-bottom: 12px;
|
||||
color: var(--primary);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
.product-showcase-card h3 {
|
||||
margin-bottom: 14px;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.product-showcase-card p {
|
||||
margin-bottom: 25px;
|
||||
color: var(--dark-3);
|
||||
line-height: 1.75;
|
||||
}
|
||||
.product-showcase-link,
|
||||
.solution-showcase-link {
|
||||
color: var(--primary);
|
||||
font-weight: 700;
|
||||
}
|
||||
.product-showcase-link i,
|
||||
.solution-showcase-link i {
|
||||
margin-left: 6px;
|
||||
font-size: 12px;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
.product-showcase-link:hover i,
|
||||
.solution-showcase-link:hover i {
|
||||
transform: translateX(4px);
|
||||
}
|
||||
.product-showcase-card--primary .product-showcase-icon {
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
color: var(--white);
|
||||
}
|
||||
.product-showcase-card--primary .product-showcase-tag,
|
||||
.product-showcase-card--primary .product-showcase-link,
|
||||
.product-showcase-card--primary h3,
|
||||
.product-showcase-card--primary p {
|
||||
color: var(--white);
|
||||
}
|
||||
.solution-area {
|
||||
padding: 100px 0;
|
||||
background: var(--light-3);
|
||||
}
|
||||
.solution-showcase-card {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
padding: 38px 34px 32px;
|
||||
border: 1px solid var(--gray-4);
|
||||
border-radius: 16px;
|
||||
background: var(--white);
|
||||
box-shadow: var(--shadow-2);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
.solution-showcase-card:hover {
|
||||
transform: translateY(-8px);
|
||||
box-shadow: var(--shadow-5);
|
||||
}
|
||||
.solution-showcase-card--featured {
|
||||
border-color: var(--primary);
|
||||
}
|
||||
.solution-showcase-index {
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
right: 28px;
|
||||
color: var(--gray-3);
|
||||
font-size: 42px;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
}
|
||||
.solution-showcase-icon {
|
||||
display: flex;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 28px;
|
||||
border-radius: 50%;
|
||||
background: var(--primary-light);
|
||||
color: var(--primary);
|
||||
font-size: 22px;
|
||||
}
|
||||
.solution-showcase-card h3 {
|
||||
margin-bottom: 14px;
|
||||
font-size: 23px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.solution-showcase-card p {
|
||||
min-height: 78px;
|
||||
margin-bottom: 20px;
|
||||
color: var(--dark-3);
|
||||
line-height: 1.75;
|
||||
}
|
||||
.solution-showcase-card ul {
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
.solution-showcase-card li {
|
||||
margin-bottom: 10px;
|
||||
color: var(--dark-2);
|
||||
}
|
||||
.solution-showcase-card li::before {
|
||||
display: inline-block;
|
||||
width: 18px;
|
||||
color: var(--primary);
|
||||
content: "✓";
|
||||
font-weight: 700;
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.product-showcase-card,
|
||||
.solution-showcase-card {
|
||||
min-height: 0;
|
||||
padding: 30px 24px;
|
||||
}
|
||||
.solution-area {
|
||||
padding: 70px 0;
|
||||
}
|
||||
.solution-showcase-card p {
|
||||
min-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.page-inner-page.about-five,
|
||||
.page-inner-page.contact-section {
|
||||
padding-top: 0;
|
||||
padding-bottom: 80px;
|
||||
background-color: var(--light-3);
|
||||
}
|
||||
.page-inner-page.contact-section {
|
||||
background-color: var(--white);
|
||||
}
|
||||
.onepage-content {
|
||||
color: var(--dark-2);
|
||||
font-size: 16px;
|
||||
line-height: 1.85;
|
||||
word-break: break-word;
|
||||
}
|
||||
.onepage-content p {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.onepage-content img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
margin: 12px 0 20px;
|
||||
}
|
||||
.onepage-content h2,
|
||||
.onepage-content h3,
|
||||
.onepage-content h4 {
|
||||
color: var(--black);
|
||||
font-weight: 700;
|
||||
margin: 28px 0 14px;
|
||||
}
|
||||
.onepage-content a {
|
||||
color: var(--primary);
|
||||
}
|
||||
.onepage-content a:hover {
|
||||
color: var(--primary-dark);
|
||||
}
|
||||
.about-five-content .onepage-content {
|
||||
margin-top: 24px;
|
||||
}
|
||||
.contact-page-content {
|
||||
margin-bottom: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user