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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
3
main.go
3
main.go
@@ -92,12 +92,13 @@ func main() {
|
||||
customerHandler := handler.NewCustomerHandler(db.DB, cfg)
|
||||
accessLogHandler := handler.NewAccessLogHandler(db.DB)
|
||||
configHandler := handler.NewConfigHandler(db.DB)
|
||||
showcaseHandler := handler.NewShowcaseHandler(db.DB)
|
||||
|
||||
// IP 归属地查询服务
|
||||
ipLocationService := geoip.NewService()
|
||||
|
||||
// 8. 设置路由
|
||||
r := router.Setup(cfg, db, authHandler, userHandler, adminHandler, uploadHandler, enterpriseHandler, shopHandler, foodHandler, gameHandler, appHandler, musicHandler, dashboardHandler, customerHandler, accessLogHandler, configHandler, ipLocationService)
|
||||
r := router.Setup(cfg, db, authHandler, userHandler, adminHandler, uploadHandler, enterpriseHandler, shopHandler, foodHandler, gameHandler, appHandler, musicHandler, dashboardHandler, customerHandler, accessLogHandler, configHandler, ipLocationService, showcaseHandler)
|
||||
|
||||
// 9. 配置嵌入式前端:18 套网站模板
|
||||
setupFrontend(r)
|
||||
|
||||
223
web/dist/admin/index.html
vendored
223
web/dist/admin/index.html
vendored
@@ -276,6 +276,12 @@ tbody tr:last-child td{border-bottom:none}
|
||||
<a class="sidebar-link" onclick="navigate('navsettings')" data-page="navsettings">
|
||||
<span class="icon">📝</span> 导航页设置
|
||||
</a>
|
||||
<a class="sidebar-link" onclick="navigate('appshowcase')" data-page="appshowcase">
|
||||
<span class="icon">📱</span> App候选码
|
||||
</a>
|
||||
<a class="sidebar-link" onclick="navigate('bizsystems')" data-page="bizsystems">
|
||||
<span class="icon">💼</span> 业务系统
|
||||
</a>
|
||||
<a class="sidebar-link" onclick="navigate('settings')" data-page="settings">
|
||||
<span class="icon">⚙️</span> 系统设置
|
||||
</a>
|
||||
@@ -458,7 +464,7 @@ function navigate(page, params = {}) {
|
||||
document.querySelectorAll('.sidebar-link').forEach(el => el.classList.remove('active'));
|
||||
const link = document.querySelector(`[data-page="${page}"]`);
|
||||
if (link) link.classList.add('active');
|
||||
const titles = { users: '用户管理', content: '内容管理', settings: '系统设置', sites: '站点导航', accesslog: '访问日志', webhook: 'Webhook配置', navsettings: '导航页设置' };
|
||||
const titles = { users: '用户管理', content: '内容管理', settings: '系统设置', sites: '站点导航', accesslog: '访问日志', webhook: 'Webhook配置', navsettings: '导航页设置', appshowcase: 'App候选码', bizsystems: '业务系统' };
|
||||
document.getElementById('pageTitle').textContent = titles[page] || page;
|
||||
if (page === 'users') renderUsers();
|
||||
else if (page === 'content') renderContent(params.table);
|
||||
@@ -467,6 +473,8 @@ function navigate(page, params = {}) {
|
||||
else if (page === 'accesslog') renderAccessLogs();
|
||||
else if (page === 'webhook') renderWebhook();
|
||||
else if (page === 'navsettings') renderNavSettings();
|
||||
else if (page === 'appshowcase') renderAppShowcase();
|
||||
else if (page === 'bizsystems') renderBizSystems();
|
||||
if (window.innerWidth <= 768) document.getElementById('sidebar').classList.remove('open');
|
||||
}
|
||||
|
||||
@@ -555,7 +563,7 @@ function openUserModal(id) {
|
||||
</select>
|
||||
</div>` : ''}
|
||||
`;
|
||||
document.getElementById('modalSaveBtn').onclick = saveUser;
|
||||
_modalSaveFn = saveUser;
|
||||
showModal();
|
||||
}
|
||||
|
||||
@@ -1342,6 +1350,14 @@ async function renderNavSettings() {
|
||||
<div class="form-group"><label>音乐平台 - 标题</label><input type="text" id="ns_sec_music_title" value="${configs.nav_sec_music_title||''}" placeholder="音乐平台"></div>
|
||||
<div class="form-group"><label>音乐平台 - 副标题</label><input type="text" id="ns_sec_music_sub" value="${configs.nav_sec_music_sub||''}" placeholder="3 套风格"></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>App候选码 - 标题</label><input type="text" id="ns_sec_appshow_title" value="${configs.nav_sec_appshow_title||''}" placeholder="App 候选码"></div>
|
||||
<div class="form-group"><label>App候选码 - 副标题</label><input type="text" id="ns_sec_appshow_sub" value="${configs.nav_sec_appshow_sub||''}" placeholder="精选应用"></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>业务系统 - 标题</label><input type="text" id="ns_sec_bizsys_title" value="${configs.nav_sec_bizsys_title||''}" placeholder="业务系统"></div>
|
||||
<div class="form-group"><label>业务系统 - 副标题</label><input type="text" id="ns_sec_bizsys_sub" value="${configs.nav_sec_bizsys_sub||''}" placeholder="定制 / 年付"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-card" style="margin-top:16px">
|
||||
@@ -1366,6 +1382,7 @@ async function saveNavSettings() {
|
||||
'sec_enterprise_title','sec_enterprise_sub','sec_shop_title','sec_shop_sub',
|
||||
'sec_food_title','sec_food_sub','sec_game_title','sec_game_sub',
|
||||
'sec_app_title','sec_app_sub','sec_music_title','sec_music_sub',
|
||||
'sec_appshow_title','sec_appshow_sub','sec_bizsys_title','sec_bizsys_sub',
|
||||
'consult_title','consult_desc','consult_success','footer_text','company_name'
|
||||
];
|
||||
const items = fields.map(f => ({
|
||||
@@ -1382,6 +1399,203 @@ async function saveNavSettings() {
|
||||
} catch (e) { toast('保存失败', 'error'); }
|
||||
}
|
||||
|
||||
// ===== App 候选码管理 =====
|
||||
let appShowPage = 1;
|
||||
async function renderAppShowcase(page) {
|
||||
if (page) appShowPage = page;
|
||||
const el = document.getElementById('content');
|
||||
el.innerHTML = '<div class="loading"><div class="spinner"></div></div>';
|
||||
try {
|
||||
const data = await api('/admin/showcase/apps?page=' + appShowPage + '&page_size=20');
|
||||
const d = data.data;
|
||||
el.innerHTML = `
|
||||
<div class="table-card">
|
||||
<div class="table-toolbar">
|
||||
<h4 style="font-size:15px">📱 App 候选码管理</h4>
|
||||
<button class="btn btn-primary btn-sm" onclick="openAppShowModal()">+ 新增App</button>
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead><tr><th>ID</th><th>名称</th><th>分类</th><th>平台</th><th>版本</th><th>评分</th><th>状态</th><th>操作</th></tr></thead>
|
||||
<tbody>
|
||||
${d.list.length===0?'<tr class="empty-row"><td colspan="8">暂无数据</td></tr>':d.list.map(a=>`
|
||||
<tr>
|
||||
<td>${a.id}</td>
|
||||
<td><strong>${a.name}</strong></td>
|
||||
<td>${a.category||'-'}</td>
|
||||
<td>${a.platform||'-'}</td>
|
||||
<td>${a.version||'-'}</td>
|
||||
<td>⭐${a.rating}</td>
|
||||
<td><span class="badge-status ${a.status===1?'badge-active':'badge-inactive'}">${a.status===1?'上架':'下架'}</span></td>
|
||||
<td>
|
||||
<button class="btn btn-outline btn-sm" onclick="openAppShowModal(${a.id})">编辑</button>
|
||||
<button class="btn btn-danger btn-sm" onclick="deleteAppShow(${a.id})">删除</button>
|
||||
</td>
|
||||
</tr>`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${renderPagination(d,'renderAppShowcase')}
|
||||
</div>`;
|
||||
} catch(e) { el.innerHTML = '<div class="loading">加载失败</div>'; }
|
||||
}
|
||||
|
||||
let appShowData = [];
|
||||
async function openAppShowModal(id) {
|
||||
if (!appShowData.length) { try { const r=await api('/admin/showcase/apps?page_size=100'); appShowData=r.data.list||[]; } catch(e){} }
|
||||
const item = id ? appShowData.find(a=>a.id===id) : null;
|
||||
document.getElementById('modalTitle').textContent = id ? '编辑App' : '新增App';
|
||||
document.getElementById('modalBody').innerHTML = `
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>App名称 *</label><input type="text" id="m_name" value="${item?.name||''}"></div>
|
||||
<div class="form-group"><label>分类</label><input type="text" id="m_category" value="${item?.category||''}" placeholder="工具/社交/教育/生活/娱乐"></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>平台</label><input type="text" id="m_platform" value="${item?.platform||''}" placeholder="Android/iOS/跨平台"></div>
|
||||
<div class="form-group"><label>版本</label><input type="text" id="m_version" value="${item?.version||''}"></div>
|
||||
</div>
|
||||
<div class="form-group"><label>简介</label><textarea id="m_description" rows="2">${item?.description||''}</textarea></div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>图标URL</label><input type="text" id="m_icon" value="${item?.icon||''}"></div>
|
||||
<div class="form-group"><label>下载链接</label><input type="text" id="m_download_url" value="${item?.download_url||''}"></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>录屏视频URL</label><input type="text" id="m_video_url" value="${item?.video_url||''}"></div>
|
||||
<div class="form-group"><label>包大小</label><input type="text" id="m_size" value="${item?.size||''}"></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>评分</label><input type="number" id="m_rating" value="${item?.rating||5}" step="0.1" min="0" max="5"></div>
|
||||
<div class="form-group"><label>排序</label><input type="number" id="m_sort" value="${item?.sort||0}"></div>
|
||||
</div>
|
||||
<div class="form-group"><label>状态</label><select id="m_status"><option value="1" ${item?.status!==0?'selected':''}>上架</option><option value="0" ${item?.status===0?'selected':''}>下架</option></select></div>`;
|
||||
editingId = id;
|
||||
_modalSaveFn = saveAppShow;
|
||||
openModal();
|
||||
}
|
||||
|
||||
async function saveAppShow() {
|
||||
const body = {
|
||||
name: document.getElementById('m_name').value,
|
||||
category: document.getElementById('m_category').value,
|
||||
platform: document.getElementById('m_platform').value,
|
||||
version: document.getElementById('m_version').value,
|
||||
description: document.getElementById('m_description').value,
|
||||
icon: document.getElementById('m_icon').value,
|
||||
download_url: document.getElementById('m_download_url').value,
|
||||
video_url: document.getElementById('m_video_url').value,
|
||||
size: document.getElementById('m_size').value,
|
||||
rating: parseFloat(document.getElementById('m_rating').value)||5,
|
||||
sort: parseInt(document.getElementById('m_sort').value)||0,
|
||||
status: parseInt(document.getElementById('m_status').value),
|
||||
};
|
||||
try {
|
||||
if (editingId) {
|
||||
await api('/admin/showcase/apps/' + editingId, { method:'PUT', body:JSON.stringify(body) });
|
||||
} else {
|
||||
await api('/admin/showcase/apps', { method:'POST', body:JSON.stringify(body) });
|
||||
}
|
||||
closeModal(); toast('保存成功','success'); renderAppShowcase();
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
async function deleteAppShow(id) {
|
||||
if (!confirm('确定删除?')) return;
|
||||
try { await api('/admin/showcase/apps/'+id,{method:'DELETE'}); toast('删除成功','success'); renderAppShowcase(); } catch(e){}
|
||||
}
|
||||
|
||||
// ===== 业务系统管理 =====
|
||||
let bizSysPage = 1;
|
||||
async function renderBizSystems(page) {
|
||||
if (page) bizSysPage = page;
|
||||
const el = document.getElementById('content');
|
||||
el.innerHTML = '<div class="loading"><div class="spinner"></div></div>';
|
||||
try {
|
||||
const data = await api('/admin/showcase/systems?page=' + bizSysPage + '&page_size=20');
|
||||
const d = data.data;
|
||||
el.innerHTML = `
|
||||
<div class="table-card">
|
||||
<div class="table-toolbar">
|
||||
<h4 style="font-size:15px">💼 业务系统管理</h4>
|
||||
<button class="btn btn-primary btn-sm" onclick="openBizSysModal()">+ 新增系统</button>
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead><tr><th>ID</th><th>名称</th><th>分类</th><th>价格类型</th><th>价格</th><th>状态</th><th>操作</th></tr></thead>
|
||||
<tbody>
|
||||
${d.list.length===0?'<tr class="empty-row"><td colspan="7">暂无数据</td></tr>':d.list.map(s=>`
|
||||
<tr>
|
||||
<td>${s.id}</td>
|
||||
<td><strong>${s.name}</strong></td>
|
||||
<td>${s.category||'-'}</td>
|
||||
<td>${s.price_type||'-'}</td>
|
||||
<td>${s.price||'-'}</td>
|
||||
<td><span class="badge-status ${s.status===1?'badge-active':'badge-inactive'}">${s.status===1?'上线':'下线'}</span></td>
|
||||
<td>
|
||||
<button class="btn btn-outline btn-sm" onclick="openBizSysModal(${s.id})">编辑</button>
|
||||
<button class="btn btn-danger btn-sm" onclick="deleteBizSys(${s.id})">删除</button>
|
||||
</td>
|
||||
</tr>`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${renderPagination(d,'renderBizSystems')}
|
||||
</div>`;
|
||||
} catch(e) { el.innerHTML = '<div class="loading">加载失败</div>'; }
|
||||
}
|
||||
|
||||
let bizSysData = [];
|
||||
async function openBizSysModal(id) {
|
||||
if (!bizSysData.length) { try { const r=await api('/admin/showcase/systems?page_size=100'); bizSysData=r.data.list||[]; } catch(e){} }
|
||||
const item = id ? bizSysData.find(s=>s.id===id) : null;
|
||||
document.getElementById('modalTitle').textContent = id ? '编辑系统' : '新增系统';
|
||||
document.getElementById('modalBody').innerHTML = `
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>系统名称 *</label><input type="text" id="m_name" value="${item?.name||''}"></div>
|
||||
<div class="form-group"><label>分类</label><input type="text" id="m_category" value="${item?.category||''}" placeholder="进销存/OA/财务/工单/ERP/CRM"></div>
|
||||
</div>
|
||||
<div class="form-group"><label>一句话介绍</label><input type="text" id="m_description" value="${item?.description||''}"></div>
|
||||
<div class="form-group"><label>详细说明</label><textarea id="m_detail" rows="3">${item?.detail||''}</textarea></div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>价格类型</label><select id="m_price_type"><option value="按年" ${item?.price_type==='按年'?'selected':''}>按年</option><option value="按月" ${item?.price_type==='按月'?'selected':''}>按月</option><option value="一次性" ${item?.price_type==='一次性'?'selected':''}>一次性</option><option value="定制" ${item?.price_type==='定制'?'selected':''}>定制开发</option></select></div>
|
||||
<div class="form-group"><label>价格描述</label><input type="text" id="m_price" value="${item?.price||''}" placeholder="如 3000元/年"></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>演示链接</label><input type="text" id="m_demo_url" value="${item?.demo_url||''}"></div>
|
||||
<div class="form-group"><label>排序</label><input type="number" id="m_sort" value="${item?.sort||0}"></div>
|
||||
</div>
|
||||
<div class="form-group"><label>状态</label><select id="m_status"><option value="1" ${item?.status!==0?'selected':''}>上线</option><option value="0" ${item?.status===0?'selected':''}>下线</option></select></div>`;
|
||||
editingId = id;
|
||||
_modalSaveFn = saveBizSys;
|
||||
openModal();
|
||||
}
|
||||
|
||||
async function saveBizSys() {
|
||||
const body = {
|
||||
name: document.getElementById('m_name').value,
|
||||
category: document.getElementById('m_category').value,
|
||||
description: document.getElementById('m_description').value,
|
||||
detail: document.getElementById('m_detail').value,
|
||||
price_type: document.getElementById('m_price_type').value,
|
||||
price: document.getElementById('m_price').value,
|
||||
demo_url: document.getElementById('m_demo_url').value,
|
||||
sort: parseInt(document.getElementById('m_sort').value)||0,
|
||||
status: parseInt(document.getElementById('m_status').value),
|
||||
};
|
||||
try {
|
||||
if (editingId) {
|
||||
await api('/admin/showcase/systems/' + editingId, { method:'PUT', body:JSON.stringify(body) });
|
||||
} else {
|
||||
await api('/admin/showcase/systems', { method:'POST', body:JSON.stringify(body) });
|
||||
}
|
||||
closeModal(); toast('保存成功','success'); renderBizSystems();
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
async function deleteBizSys(id) {
|
||||
if (!confirm('确定删除?')) return;
|
||||
try { await api('/admin/showcase/systems/'+id,{method:'DELETE'}); toast('删除成功','success'); renderBizSystems(); } catch(e){}
|
||||
}
|
||||
|
||||
// ===== 通用工具 =====
|
||||
function formatDate(d) {
|
||||
if (!d) return '-';
|
||||
@@ -1449,7 +1663,12 @@ function renderPagination(d, fnName) {
|
||||
}
|
||||
|
||||
function showModal() { document.getElementById('modalOverlay').classList.add('show'); }
|
||||
function openModal() { showModal(); }
|
||||
function closeModal() { document.getElementById('modalOverlay').classList.remove('show'); editingId = null; }
|
||||
function saveModal() {
|
||||
if (typeof _modalSaveFn === 'function') _modalSaveFn();
|
||||
}
|
||||
let _modalSaveFn = null;
|
||||
|
||||
// ===== 初始化 =====
|
||||
document.getElementById('modalOverlay').addEventListener('click', (e) => {
|
||||
|
||||
149
web/dist/index.html
vendored
149
web/dist/index.html
vendored
@@ -2,8 +2,53 @@
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Gotest · 模板导航</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0">
|
||||
<title>Gotest · 多行业网站模板集合 - 企业官网/电商/外卖/游戏/应用/音乐模板一站式导航</title>
|
||||
<meta name="description" content="Gotest 提供 19 套精心设计的多行业网站模板,覆盖企业官网、跨境电商、外卖平台、游戏、应用商店、音乐平台 6 大行业,支持响应式设计,一键预览,即刻上线。">
|
||||
<meta name="keywords" content="网站模板,企业官网模板,电商模板,响应式网站,网站设计,模板导航,跨境电商模板,外卖平台模板,游戏网站模板,应用商店模板,音乐平台模板,低成本建站">
|
||||
<meta name="author" content="焉耆青梧映星软件开发工作室">
|
||||
<meta name="robots" content="index, follow">
|
||||
<meta name="googlebot" content="index, follow">
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="/">
|
||||
<meta property="og:title" content="Gotest · 多行业网站模板集合">
|
||||
<meta property="og:description" content="19 套精心设计的多行业网站模板,覆盖 6 大行业,响应式设计,一键预览。">
|
||||
<meta property="og:site_name" content="Gotest 模板导航">
|
||||
<!-- Twitter -->
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="Gotest · 多行业网站模板集合">
|
||||
<meta name="twitter:description" content="19 套精心设计的多行业网站模板,覆盖 6 大行业,响应式设计,一键预览。">
|
||||
<!-- GEO -->
|
||||
<meta name="geo.region" content="CN-XJ">
|
||||
<meta name="geo.placename" content="焉耆回族自治县">
|
||||
<meta name="geo.position" content="42.05;86.57">
|
||||
<meta name="ICBM" content="42.05, 86.57">
|
||||
<link rel="canonical" href="/">
|
||||
<!-- Structured Data -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebSite",
|
||||
"name": "Gotest 模板导航",
|
||||
"description": "多行业网站模板集合,覆盖企业官网、跨境电商、外卖、游戏、应用、音乐 6 大行业",
|
||||
"url": "/",
|
||||
"potentialAction": {
|
||||
"@type": "SearchAction",
|
||||
"target": "/?q={search_term_string}",
|
||||
"query-input": "required name=search_term_string"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Organization",
|
||||
"name": "焉耆青梧映星软件开发工作室",
|
||||
"url": "/",
|
||||
"sameAs": []
|
||||
}
|
||||
</script>
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><rect width='100' height='100' rx='22' fill='%23000'/><text x='50' y='70' font-size='58' font-weight='bold' fill='white' text-anchor='middle' font-family='Arial'>G</text></svg)">
|
||||
<script>
|
||||
// Prevent FOUC: apply theme before render
|
||||
@@ -53,6 +98,8 @@ body{font-family:-apple-system,BlinkMacSystemFont,'SF Pro Display','Segoe UI',Ro
|
||||
.g-game .glow{background:linear-gradient(135deg,#0c0a09 0%,#7c3aed 40%,#d946ef 100%)}
|
||||
.g-app .glow{background:linear-gradient(135deg,#0c4a6e 0%,#0ea5e9 40%,#7dd3fc 100%)}
|
||||
.g-music .glow{background:linear-gradient(135deg,#312e81 0%,#7c3aed 40%,#f472b6 100%)}
|
||||
.g-appshow .glow{background:linear-gradient(135deg,#0f766e 0%,#14b8a6 40%,#5eead4 100%)}
|
||||
.g-bizsys .glow{background:linear-gradient(135deg,#1e3a5f 0%,#3b82f6 40%,#93c5fd 100%)}
|
||||
/* Admin */
|
||||
.admin-row{display:flex;justify-content:center;margin-top:56px}
|
||||
.admin-btn{display:inline-flex;align-items:center;gap:8px;padding:14px 32px;border-radius:100px;background:var(--admin-btn-bg);color:var(--text);text-decoration:none;font-size:15px;font-weight:600;transition:all .25s}
|
||||
@@ -68,9 +115,8 @@ body{font-family:-apple-system,BlinkMacSystemFont,'SF Pro Display','Segoe UI',Ro
|
||||
[data-theme="dark"] .theme-toggle .icon-moon{opacity:0;transform:rotate(90deg) scale(.5)}
|
||||
/* Footer */
|
||||
.foot{text-align:center;padding:32px 0 0;color:var(--text-sub);font-size:12px}
|
||||
/* Responsive */
|
||||
/* Responsive - base tablet */
|
||||
@media(max-width:900px){.grid{grid-template-columns:repeat(2,1fr)}}
|
||||
@media(max-width:600px){.grid{grid-template-columns:1fr}.hero h1{font-size:36px}}
|
||||
/* Anim */
|
||||
.item{animation:up .6s ease both}
|
||||
.item:nth-child(1){animation-delay:.05s}.item:nth-child(2){animation-delay:.12s}.item:nth-child(3){animation-delay:.19s}
|
||||
@@ -84,7 +130,7 @@ body{font-family:-apple-system,BlinkMacSystemFont,'SF Pro Display','Segoe UI',Ro
|
||||
.form-row{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:16px}
|
||||
.form-group{margin-bottom:16px}
|
||||
.form-group label{display:block;font-size:14px;font-weight:600;color:var(--text);margin-bottom:6px}
|
||||
.form-group input,.form-group textarea{width:100%;padding:12px 16px;border:2px solid var(--input-border);border-radius:12px;font-size:15px;transition:all .3s;background:var(--input-bg);color:var(--text)}
|
||||
.form-group input,.form-group textarea{width:100%;padding:12px 16px;border:2px solid var(--input-border);border-radius:12px;font-size:15px;transition:all .3s;background:var(--input-bg);color:var(--text);-webkit-appearance:none;appearance:none}
|
||||
.form-group input::placeholder,.form-group textarea::placeholder{color:var(--text-sub)}
|
||||
.form-group input:focus,.form-group textarea:focus{outline:none;border-color:#667eea;background:var(--card);box-shadow:0 0 0 4px rgba(102,126,234,0.1)}
|
||||
.form-group textarea{resize:vertical;min-height:100px}
|
||||
@@ -95,7 +141,40 @@ body{font-family:-apple-system,BlinkMacSystemFont,'SF Pro Display','Segoe UI',Ro
|
||||
.success-icon{width:64px;height:64px;background:linear-gradient(135deg,#10b981 0%,#059669 100%);border-radius:50%;display:flex;align-items:center;justify-content:center;margin:0 auto 16px;font-size:32px;color:#fff}
|
||||
.consult-success h3{font-size:24px;font-weight:700;color:var(--text);margin-bottom:8px}
|
||||
.consult-success p{font-size:16px;color:var(--text-sub)}
|
||||
@media(max-width:600px){.form-row{grid-template-columns:1fr}.consult-section{padding:24px;margin:48px 16px}}
|
||||
|
||||
/* ===== Mobile Responsive ===== */
|
||||
@media(max-width:600px){
|
||||
.wrap{padding:24px 16px 48px}
|
||||
.hero{padding:24px 0 32px}
|
||||
.hero h1{font-size:28px;letter-spacing:-1px}
|
||||
.hero p{font-size:15px}
|
||||
.hero-pills{gap:6px;margin-top:20px}
|
||||
.pill{padding:5px 12px;font-size:12px}
|
||||
.sec{margin-bottom:36px}
|
||||
.sec-head{gap:8px;margin-bottom:16px}
|
||||
.sec-head h2{font-size:20px}
|
||||
.sec-head .sub{font-size:12px}
|
||||
.grid{grid-template-columns:1fr !important;gap:14px}
|
||||
.item-banner{height:120px}
|
||||
.item-body{padding:14px 16px}
|
||||
.item-title{font-size:15px}
|
||||
.item-desc{font-size:12px}
|
||||
.item-go{opacity:1;transform:scale(1)}
|
||||
.theme-toggle{width:40px;height:40px;top:16px;right:16px}
|
||||
.consult-section{margin:36px 0 0;padding:0;border-radius:0;box-shadow:none;background:linear-gradient(135deg,#667eea 0%,#764ba2 100%)}
|
||||
.consult-header{padding:24px 20px 0;margin-bottom:20px}
|
||||
.consult-header h2{font-size:22px}
|
||||
.consult-header p{font-size:14px}
|
||||
.consult-form{padding:20px;border-radius:0;margin:0 16px 16px}
|
||||
.form-row{grid-template-columns:1fr;gap:0}
|
||||
.form-group{margin-bottom:12px}
|
||||
.form-group label{font-size:13px;margin-bottom:4px}
|
||||
.form-group input,.form-group textarea{padding:10px 14px;font-size:14px;border-width:1px}
|
||||
.submit-btn{padding:14px;font-size:16px;margin-top:4px}
|
||||
.admin-row{margin-top:36px}
|
||||
.admin-btn{padding:12px 24px;font-size:14px}
|
||||
.foot{padding:24px 16px 0;font-size:11px}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -176,6 +255,18 @@ body{font-family:-apple-system,BlinkMacSystemFont,'SF Pro Display','Segoe UI',Ro
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- App 候选码 -->
|
||||
<div class="sec" id="sec-apps" style="display:none">
|
||||
<div class="sec-head"><h2 id="s_sec_appshow_title">App 候选码</h2><span class="sub" id="s_sec_appshow_sub">精选应用</span><div class="sec-line"></div></div>
|
||||
<div class="grid" id="appShowGrid"></div>
|
||||
</div>
|
||||
|
||||
<!-- 业务系统 -->
|
||||
<div class="sec" id="sec-systems" style="display:none">
|
||||
<div class="sec-head"><h2 id="s_sec_bizsys_title">业务系统</h2><span class="sub" id="s_sec_bizsys_sub">定制 / 年付</span><div class="sec-line"></div></div>
|
||||
<div class="grid" id="bizSysGrid"></div>
|
||||
</div>
|
||||
|
||||
<!-- Admin -->
|
||||
<div class="admin-row">
|
||||
<a class="admin-btn" href="/admin">
|
||||
@@ -275,6 +366,10 @@ body{font-family:-apple-system,BlinkMacSystemFont,'SF Pro Display','Segoe UI',Ro
|
||||
s_sec_app_sub: 'nav_sec_app_sub',
|
||||
s_sec_music_title: 'nav_sec_music_title',
|
||||
s_sec_music_sub: 'nav_sec_music_sub',
|
||||
s_sec_appshow_title: 'nav_sec_appshow_title',
|
||||
s_sec_appshow_sub: 'nav_sec_appshow_sub',
|
||||
s_sec_bizsys_title: 'nav_sec_bizsys_title',
|
||||
s_sec_bizsys_sub: 'nav_sec_bizsys_sub',
|
||||
};
|
||||
for (var elId in map) {
|
||||
var val = s[map[elId]];
|
||||
@@ -286,6 +381,48 @@ body{font-family:-apple-system,BlinkMacSystemFont,'SF Pro Display','Segoe UI',Ro
|
||||
} catch(e) {}
|
||||
})();
|
||||
|
||||
// 加载 App 候选码
|
||||
(async function(){
|
||||
try {
|
||||
var resp = await fetch('/api/v1/showcase/apps');
|
||||
var result = await resp.json();
|
||||
if (result.code !== 0 || !result.data.list || result.data.list.length === 0) return;
|
||||
var grid = document.getElementById('appShowGrid');
|
||||
var items = result.data.list;
|
||||
var gradients = ['#0f766e,#14b8a6,#5eead4','#7c2d12,#ea580c,#fdba74','#1e3a5f,#2563eb,#60a5fa','#581c87,#9333ea,#d8b4fe','#065f46,#10b981,#6ee7b7'];
|
||||
grid.innerHTML = items.map(function(item, i) {
|
||||
var g = gradients[i % gradients.length].split(',');
|
||||
return '<a class="item" href="' + (item.download_url || '#') + '" target="_blank">' +
|
||||
'<div class="item-banner g-appshow"><div class="glow" style="background:linear-gradient(135deg,' + g[0] + ' 0%,' + g[1] + ' 40%,' + g[2] + ' 100%)"></div><div class="pattern"></div>' +
|
||||
'<span class="item-tag">' + (item.category || 'App') + '</span><div class="item-go">\u2192</div></div>' +
|
||||
'<div class="item-body"><div class="item-title">' + item.name + '</div><div class="item-desc">' + (item.description || '') + '</div></div></a>';
|
||||
}).join('');
|
||||
document.getElementById('sec-apps').style.display = '';
|
||||
} catch(e) {}
|
||||
})();
|
||||
|
||||
// 加载业务系统
|
||||
(async function(){
|
||||
try {
|
||||
var resp = await fetch('/api/v1/showcase/systems');
|
||||
var result = await resp.json();
|
||||
if (result.code !== 0 || !result.data.list || result.data.list.length === 0) return;
|
||||
var grid = document.getElementById('bizSysGrid');
|
||||
var items = result.data.list;
|
||||
var gradients = ['#1e3a5f,#3b82f6,#93c5fd','#0f766e,#14b8a6,#5eead4','#7c2d12,#ea580c,#fdba74','#581c87,#9333ea,#d8b4fe','#065f46,#10b981,#6ee7b7'];
|
||||
grid.innerHTML = items.map(function(item, i) {
|
||||
var g = gradients[i % gradients.length].split(',');
|
||||
var priceLabel = item.price_type === '定制' ? '定制开发' : (item.price || '咨询报价');
|
||||
return '<div class="item">' +
|
||||
'<div class="item-banner g-bizsys"><div class="glow" style="background:linear-gradient(135deg,' + g[0] + ' 0%,' + g[1] + ' 40%,' + g[2] + ' 100%)"></div><div class="pattern"></div>' +
|
||||
'<span class="item-tag">' + (item.category || '系统') + '</span><div class="item-go">\u2192</div></div>' +
|
||||
'<div class="item-body"><div class="item-title">' + item.name + '</div><div class="item-desc">' + (item.description || '') + '</div>' +
|
||||
'<div style="margin-top:8px"><span style="display:inline-block;padding:3px 10px;border-radius:6px;font-size:12px;font-weight:600;background:rgba(59,130,246,0.1);color:#3b82f6">' + priceLabel + '</span></div></div></div>';
|
||||
}).join('');
|
||||
document.getElementById('sec-systems').style.display = '';
|
||||
} catch(e) {}
|
||||
})();
|
||||
|
||||
document.getElementById('consultForm').addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user