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:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
|
||||
191
internal/handler/showcase.go
Normal file
191
internal/handler/showcase.go
Normal 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, "删除成功")
|
||||
}
|
||||
49
internal/model/showcase.go
Normal file
49
internal/model/showcase.go
Normal 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"`
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user