feat: 导航页SEO/GEO优化 + 手机端完全适配 + App候选码 + 业务系统板块

- SEO: 添加meta description/keywords/Open Graph/结构化数据/地理信息
- GEO: 添加geo.region/geo.position/ICBM等地理标记
- 手机端完全重写响应式: 咨询表单/导航卡片/hero区/标签全部适配
- 新增App候选码板块: 后台管理+导航页动态展示(支持录屏视频/下载链接)
- 新增业务系统板块: 进销存/OA/财务/工单等,支持按年/按月/定制定价
- 后台新增App候选码管理页面(增删改查+上下架)
- 后台新增业务系统管理页面(增删改查+上下架)
- 导航页设置新增新板块标题配置
- 公开API: GET /api/v1/showcase/apps + /api/v1/showcase/systems
This commit is contained in:
2026-09-09 03:32:22 +08:00
parent 278b07aa97
commit 69c59f4fbd
9 changed files with 629 additions and 9 deletions

View File

@@ -67,6 +67,7 @@ func Init(cfg *config.DatabaseConfig) (*DB, error) {
&model.GameCategory{}, &model.Game{},
&model.AppCategory{}, &model.App{},
&model.MusicCategory{}, &model.Song{},
&model.AppShowcase{}, &model.BizSystem{},
); err != nil {
return nil, fmt.Errorf("自动建表失败: %w", err)
}

View File

@@ -142,6 +142,11 @@ func (h *ConfigHandler) GetNavSettings(c *gin.Context) {
"nav_sec_app_sub",
"nav_sec_music_title",
"nav_sec_music_sub",
// 新板块
"nav_sec_appshow_title",
"nav_sec_appshow_sub",
"nav_sec_bizsys_title",
"nav_sec_bizsys_sub",
}
settings := map[string]string{}

View File

@@ -0,0 +1,191 @@
package handler
import (
"strconv"
"gotest/internal/model"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// ShowcaseHandler App 候选码 & 业务系统管理
type ShowcaseHandler struct {
db *gorm.DB
}
func NewShowcaseHandler(db *gorm.DB) *ShowcaseHandler {
return &ShowcaseHandler{db: db}
}
// ========== App 候选码 ==========
func (h *ShowcaseHandler) ListAppShowcase(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 100 {
pageSize = 20
}
var total int64
h.db.Model(&model.AppShowcase{}).Where("status = 1").Count(&total)
var items []model.AppShowcase
h.db.Where("status = 1").Order("sort ASC, id DESC").
Offset((page - 1) * pageSize).Limit(pageSize).Find(&items)
Success(c, gin.H{
"list": items, "total": total, "page": page, "page_size": pageSize,
})
}
func (h *ShowcaseHandler) AdminListAppShowcase(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 100 {
pageSize = 20
}
var total int64
h.db.Model(&model.AppShowcase{}).Count(&total)
var items []model.AppShowcase
h.db.Order("sort ASC, id DESC").
Offset((page - 1) * pageSize).Limit(pageSize).Find(&items)
Success(c, gin.H{
"list": items, "total": total, "page": page, "page_size": pageSize,
})
}
func (h *ShowcaseHandler) CreateAppShowcase(c *gin.Context) {
var item model.AppShowcase
if err := c.ShouldBindJSON(&item); err != nil {
FailBadRequest(c, "参数错误")
return
}
if err := h.db.Create(&item).Error; err != nil {
FailInternal(c, "创建失败: "+err.Error())
return
}
Success(c, item)
}
func (h *ShowcaseHandler) UpdateAppShowcase(c *gin.Context) {
id := c.Param("id")
var item model.AppShowcase
if err := h.db.First(&item, id).Error; err != nil {
Fail(c, 404, "记录不存在")
return
}
if err := c.ShouldBindJSON(&item); err != nil {
FailBadRequest(c, "参数错误")
return
}
if err := h.db.Save(&item).Error; err != nil {
FailInternal(c, "更新失败: "+err.Error())
return
}
Success(c, item)
}
func (h *ShowcaseHandler) DeleteAppShowcase(c *gin.Context) {
id := c.Param("id")
if err := h.db.Delete(&model.AppShowcase{}, id).Error; err != nil {
FailInternal(c, "删除失败: "+err.Error())
return
}
SuccessMsg(c, "删除成功")
}
// ========== 业务系统 ==========
func (h *ShowcaseHandler) ListBizSystem(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 100 {
pageSize = 20
}
var total int64
h.db.Model(&model.BizSystem{}).Where("status = 1").Count(&total)
var items []model.BizSystem
h.db.Where("status = 1").Order("sort ASC, id DESC").
Offset((page - 1) * pageSize).Limit(pageSize).Find(&items)
Success(c, gin.H{
"list": items, "total": total, "page": page, "page_size": pageSize,
})
}
func (h *ShowcaseHandler) AdminListBizSystem(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 100 {
pageSize = 20
}
var total int64
h.db.Model(&model.BizSystem{}).Count(&total)
var items []model.BizSystem
h.db.Order("sort ASC, id DESC").
Offset((page - 1) * pageSize).Limit(pageSize).Find(&items)
Success(c, gin.H{
"list": items, "total": total, "page": page, "page_size": pageSize,
})
}
func (h *ShowcaseHandler) CreateBizSystem(c *gin.Context) {
var item model.BizSystem
if err := c.ShouldBindJSON(&item); err != nil {
FailBadRequest(c, "参数错误")
return
}
if err := h.db.Create(&item).Error; err != nil {
FailInternal(c, "创建失败: "+err.Error())
return
}
Success(c, item)
}
func (h *ShowcaseHandler) UpdateBizSystem(c *gin.Context) {
id := c.Param("id")
var item model.BizSystem
if err := h.db.First(&item, id).Error; err != nil {
Fail(c, 404, "记录不存在")
return
}
if err := c.ShouldBindJSON(&item); err != nil {
FailBadRequest(c, "参数错误")
return
}
if err := h.db.Save(&item).Error; err != nil {
FailInternal(c, "更新失败: "+err.Error())
return
}
Success(c, item)
}
func (h *ShowcaseHandler) DeleteBizSystem(c *gin.Context) {
id := c.Param("id")
if err := h.db.Delete(&model.BizSystem{}, id).Error; err != nil {
FailInternal(c, "删除失败: "+err.Error())
return
}
SuccessMsg(c, "删除成功")
}

View File

@@ -0,0 +1,49 @@
package model
import "time"
// AppShowcase App 候选码展示模型
type AppShowcase struct {
ID uint `gorm:"primaryKey" json:"id"`
Name string `gorm:"size:100;not null" json:"name"` // App 名称
Icon string `gorm:"size:255" json:"icon"` // 图标 URL
Description string `gorm:"type:text" json:"description"` // 简介
Category string `gorm:"size:50" json:"category"` // 分类:工具/社交/教育/生活/娱乐
Platform string `gorm:"size:100" json:"platform"` // 平台:Android/iOS/跨平台
Version string `gorm:"size:50" json:"version"` // 版本
Size string `gorm:"size:50" json:"size"` // 包大小
Downloads int `gorm:"default:0" json:"downloads"` // 下载量
Rating float64 `gorm:"type:decimal(2,1);default:5.0" json:"rating"` // 评分
Screenshots string `gorm:"type:text" json:"screenshots"` // JSON: 截图 URL 数组
VideoUrl string `gorm:"size:500" json:"video_url"` // 录屏视频 URL
DownloadUrl string `gorm:"size:500" json:"download_url"` // 下载链接
Tags string `gorm:"type:text" json:"tags"` // JSON: 标签
IsFeatured bool `gorm:"default:false" json:"is_featured"` // 精选推荐
Sort int `gorm:"default:0" json:"sort"`
Status int `gorm:"default:1;not null" json:"status"` // 1=上架 0=下架
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// BizSystem 业务系统模型
type BizSystem struct {
ID uint `gorm:"primaryKey" json:"id"`
Name string `gorm:"size:100;not null" json:"name"` // 系统名称
Icon string `gorm:"size:255" json:"icon"` // 图标
Logo string `gorm:"size:255" json:"logo"` // Logo 大图
Description string `gorm:"type:text" json:"description"` // 一句话介绍
Detail string `gorm:"type:text" json:"detail"` // 详细说明(富文本)
Category string `gorm:"size:50" json:"category"` // 分类:进销存/OA/财务/工单/ERP/CRM/自定义
Features string `gorm:"type:text" json:"features"` // JSON: 核心功能列表
Screenshots string `gorm:"type:text" json:"screenshots"` // JSON: 系统截图 URL 数组
PriceType string `gorm:"size:50" json:"price_type"` // 价格类型:按年/按月/一次性/定制
Price string `gorm:"size:100" json:"price"` // 价格描述:如 "3000元/年"
DemoUrl string `gorm:"size:500" json:"demo_url"` // 在线演示链接
ContactWay string `gorm:"size:50" json:"contact_way"` // 联系方式类型:微信/电话/表单
ContactInfo string `gorm:"size:200" json:"contact_info"` // 联系方式内容
IsFeatured bool `gorm:"default:false" json:"is_featured"` // 精选推荐
Sort int `gorm:"default:0" json:"sort"`
Status int `gorm:"default:1;not null" json:"status"` // 1=上线 0=下线
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}

View File

@@ -32,6 +32,7 @@ func Setup(
accessLogH *handler.AccessLogHandler,
configH *handler.ConfigHandler,
ipLocationService *geoip.Service,
showcaseH *handler.ShowcaseHandler,
) *gin.Engine {
if cfg.Env.Name == "prod" {
gin.SetMode(gin.ReleaseMode)
@@ -165,6 +166,10 @@ func Setup(
// 导航页设置(公开,无需认证)
api.GET("/nav-settings", configH.GetNavSettings)
// App 候选码 & 业务系统(公开)
api.GET("/showcase/apps", showcaseH.ListAppShowcase)
api.GET("/showcase/systems", showcaseH.ListBizSystem)
// 需要登录的路由
authed := api.Group("")
authed.Use(middleware.JWTAuth(cfg.JWT.Secret))
@@ -212,6 +217,18 @@ func Setup(
// Webhook 测试
admin.POST("/webhook/test", customerH.TestWebhook)
// App 候选码管理
admin.GET("/showcase/apps", showcaseH.AdminListAppShowcase)
admin.POST("/showcase/apps", showcaseH.CreateAppShowcase)
admin.PUT("/showcase/apps/:id", showcaseH.UpdateAppShowcase)
admin.DELETE("/showcase/apps/:id", showcaseH.DeleteAppShowcase)
// 业务系统管理
admin.GET("/showcase/systems", showcaseH.AdminListBizSystem)
admin.POST("/showcase/systems", showcaseH.CreateBizSystem)
admin.PUT("/showcase/systems/:id", showcaseH.UpdateBizSystem)
admin.DELETE("/showcase/systems/:id", showcaseH.DeleteBizSystem)
}
}