The development mode supports hot reloading of templates

This commit is contained in:
2025-10-26 22:28:50 +08:00
parent a20368a39b
commit edff1bb839
13 changed files with 387 additions and 80 deletions

View File

@@ -105,6 +105,9 @@ func createHTTPServer(addr string) *http.Server {
// 添加日志中间件 // 添加日志中间件
router.Use(middleware.WrapHandler()) router.Use(middleware.WrapHandler())
// 添加开发模式中间件(统一管理开发模式功能)
router.Use(middleware.DevModeMiddleware(router))
// 加载模板 // 加载模板
if err := loadTemplates(router); err != nil { if err := loadTemplates(router); err != nil {
logrus.WithError(err).Fatal("模板加载失败") logrus.WithError(err).Fatal("模板加载失败")

View File

@@ -274,6 +274,55 @@ func APIGetTypesHandler(c *gin.Context) {
apiBaseController.HandleSuccess(c, "获取接口类型列表成功", apiTypes) apiBaseController.HandleSuccess(c, "获取接口类型列表成功", apiTypes)
} }
// APIUpdateStatusHandler 更新单个接口状态处理器
func APIUpdateStatusHandler(c *gin.Context) {
var req struct {
ID uint `json:"id"`
Status int `json:"status"`
}
if !apiBaseController.BindJSON(c, &req) {
return
}
if req.ID == 0 {
apiBaseController.HandleValidationError(c, "接口ID不能为空")
return
}
if req.Status != 0 && req.Status != 1 {
apiBaseController.HandleValidationError(c, "状态值无效")
return
}
// 获取数据库连接
db, ok := apiBaseController.GetDB(c)
if !ok {
return
}
// 检查接口是否存在
var api models.API
if err := db.Where("id = ?", req.ID).First(&api).Error; err != nil {
apiBaseController.HandleValidationError(c, "接口不存在")
return
}
// 更新状态
if err := db.Model(&api).Update("status", req.Status).Error; err != nil {
logrus.WithError(err).Error("Failed to update API status")
apiBaseController.HandleInternalError(c, "更新状态失败", err)
return
}
statusText := "禁用"
if req.Status == 1 {
statusText = "启用"
}
apiBaseController.HandleSuccess(c, "接口"+statusText+"成功", nil)
}
func APIGenerateKeysHandler(c *gin.Context) { func APIGenerateKeysHandler(c *gin.Context) {
var req struct { var req struct {
Side string `json:"side"` // submit | return Side string `json:"side"` // submit | return

View File

@@ -339,25 +339,25 @@ func AppCreateHandler(c *gin.Context) {
// 为应用创建所有默认接口 // 为应用创建所有默认接口
defaultAPITypes := []int{ defaultAPITypes := []int{
models.APITypeGetBulletin, // 获取程序公告 models.APITypeGetBulletin, // 获取程序公告
models.APITypeGetUpdateUrl, // 获取更新地址 models.APITypeGetUpdateUrl, // 获取更新地址
models.APITypeCheckAppVersion, // 检测最新版本 models.APITypeCheckAppVersion, // 检测最新版本
models.APITypeGetCardInfo, // 获取卡密信息 models.APITypeGetCardInfo, // 获取卡密信息
models.APITypeSingleLogin, // 卡密登录 models.APITypeSingleLogin, // 卡密登录
models.APITypeUserLogin, // 用户登录 models.APITypeUserLogin, // 用户登录
models.APITypeUserRegin, // 用户注册 models.APITypeUserRegin, // 用户注册
models.APITypeUserRecharge, // 用户充值 models.APITypeUserRecharge, // 用户充值
models.APITypeCardRegin, // 卡密注册 models.APITypeCardRegin, // 卡密注册
models.APITypeLogOut, // 退出登录 models.APITypeLogOut, // 退出登录
models.APITypeGetExpired, // 获取到期时间 models.APITypeGetExpired, // 获取到期时间
models.APITypeCheckUserStatus, // 检测账号状态 models.APITypeCheckUserStatus, // 检测账号状态
models.APITypeGetAppData, // 获取程序数据 models.APITypeGetAppData, // 获取程序数据
models.APITypeGetVariable, // 获取变量数据 models.APITypeGetVariable, // 获取变量数据
models.APITypeUpdatePwd, // 修改账号密码 models.APITypeUpdatePwd, // 修改账号密码
models.APITypeMacChangeBind, // 机器码转绑 models.APITypeMacChangeBind, // 机器码转绑
models.APITypeIPChangeBind, // IP转绑 models.APITypeIPChangeBind, // IP转绑
models.APITypeDisableUser, // 封停用户 models.APITypeDisableUser, // 封停用户
models.APITypeBlackUser, // 添加黑名单 models.APITypeBlackUser, // 添加黑名单
models.APITypeUserDeductedTime, // 扣除时间 models.APITypeUserDeductedTime, // 扣除时间
} }
@@ -366,7 +366,7 @@ func AppCreateHandler(c *gin.Context) {
api := models.API{ api := models.API{
APIType: apiType, APIType: apiType,
AppUUID: app.UUID, AppUUID: app.UUID,
Status: 1, // 默认 Status: 0, // 默认
SubmitAlgorithm: models.AlgorithmNone, // 默认不加密 SubmitAlgorithm: models.AlgorithmNone, // 默认不加密
ReturnAlgorithm: models.AlgorithmNone, // 默认不加密 ReturnAlgorithm: models.AlgorithmNone, // 默认不加密
} }
@@ -928,18 +928,18 @@ func AppGetBindConfigHandler(c *gin.Context) {
"code": 0, "code": 0,
"msg": "获取绑定配置成功", "msg": "获取绑定配置成功",
"data": gin.H{ "data": gin.H{
"machine_verify": app.MachineVerify, "machine_verify": app.MachineVerify,
"machine_rebind_enabled": app.MachineRebindEnabled, "machine_rebind_enabled": app.MachineRebindEnabled,
"machine_rebind_limit": app.MachineRebindLimit, "machine_rebind_limit": app.MachineRebindLimit,
"machine_free_count": app.MachineFreeCount, "machine_free_count": app.MachineFreeCount,
"machine_rebind_count": app.MachineRebindCount, "machine_rebind_count": app.MachineRebindCount,
"machine_rebind_deduct": app.MachineRebindDeduct, "machine_rebind_deduct": app.MachineRebindDeduct,
"ip_verify": app.IPVerify, "ip_verify": app.IPVerify,
"ip_rebind_enabled": app.IPRebindEnabled, "ip_rebind_enabled": app.IPRebindEnabled,
"ip_rebind_limit": app.IPRebindLimit, "ip_rebind_limit": app.IPRebindLimit,
"ip_free_count": app.IPFreeCount, "ip_free_count": app.IPFreeCount,
"ip_rebind_count": app.IPRebindCount, "ip_rebind_count": app.IPRebindCount,
"ip_rebind_deduct": app.IPRebindDeduct, "ip_rebind_deduct": app.IPRebindDeduct,
}, },
}) })
} }
@@ -948,19 +948,19 @@ func AppGetBindConfigHandler(c *gin.Context) {
func AppUpdateBindConfigHandler(c *gin.Context) { func AppUpdateBindConfigHandler(c *gin.Context) {
// 解析请求体 // 解析请求体
var req struct { var req struct {
UUID string `json:"uuid"` UUID string `json:"uuid"`
MachineVerify int `json:"machine_verify"` MachineVerify int `json:"machine_verify"`
MachineRebindEnabled int `json:"machine_rebind_enabled"` MachineRebindEnabled int `json:"machine_rebind_enabled"`
MachineRebindLimit int `json:"machine_rebind_limit"` MachineRebindLimit int `json:"machine_rebind_limit"`
MachineFreeCount int `json:"machine_free_count"` MachineFreeCount int `json:"machine_free_count"`
MachineRebindCount int `json:"machine_rebind_count"` MachineRebindCount int `json:"machine_rebind_count"`
MachineRebindDeduct int `json:"machine_rebind_deduct"` MachineRebindDeduct int `json:"machine_rebind_deduct"`
IPVerify int `json:"ip_verify"` IPVerify int `json:"ip_verify"`
IPRebindEnabled int `json:"ip_rebind_enabled"` IPRebindEnabled int `json:"ip_rebind_enabled"`
IPRebindLimit int `json:"ip_rebind_limit"` IPRebindLimit int `json:"ip_rebind_limit"`
IPFreeCount int `json:"ip_free_count"` IPFreeCount int `json:"ip_free_count"`
IPRebindCount int `json:"ip_rebind_count"` IPRebindCount int `json:"ip_rebind_count"`
IPRebindDeduct int `json:"ip_rebind_deduct"` IPRebindDeduct int `json:"ip_rebind_deduct"`
} }
if !appBaseController.BindJSON(c, &req) { if !appBaseController.BindJSON(c, &req) {
@@ -1005,18 +1005,18 @@ func AppUpdateBindConfigHandler(c *gin.Context) {
// 更新绑定配置 // 更新绑定配置
updates := map[string]interface{}{ updates := map[string]interface{}{
"machine_verify": req.MachineVerify, "machine_verify": req.MachineVerify,
"machine_rebind_enabled": req.MachineRebindEnabled, "machine_rebind_enabled": req.MachineRebindEnabled,
"machine_rebind_limit": req.MachineRebindLimit, "machine_rebind_limit": req.MachineRebindLimit,
"machine_free_count": req.MachineFreeCount, "machine_free_count": req.MachineFreeCount,
"machine_rebind_count": req.MachineRebindCount, "machine_rebind_count": req.MachineRebindCount,
"machine_rebind_deduct": req.MachineRebindDeduct, "machine_rebind_deduct": req.MachineRebindDeduct,
"ip_verify": req.IPVerify, "ip_verify": req.IPVerify,
"ip_rebind_enabled": req.IPRebindEnabled, "ip_rebind_enabled": req.IPRebindEnabled,
"ip_rebind_limit": req.IPRebindLimit, "ip_rebind_limit": req.IPRebindLimit,
"ip_free_count": req.IPFreeCount, "ip_free_count": req.IPFreeCount,
"ip_rebind_count": req.IPRebindCount, "ip_rebind_count": req.IPRebindCount,
"ip_rebind_deduct": req.IPRebindDeduct, "ip_rebind_deduct": req.IPRebindDeduct,
} }
if err := db.Model(&app).Updates(updates).Error; err != nil { if err := db.Model(&app).Updates(updates).Error; err != nil {
@@ -1334,3 +1334,67 @@ func AppsBatchUpdateStatusHandler(c *gin.Context) {
"msg": "批量" + statusText + "成功", "msg": "批量" + statusText + "成功",
}) })
} }
// AppUpdateStatusHandler 更新单个应用状态处理器
func AppUpdateStatusHandler(c *gin.Context) {
var req struct {
ID uint `json:"id"`
Status int `json:"status"`
}
if !appBaseController.BindJSON(c, &req) {
return
}
if req.ID == 0 {
c.JSON(http.StatusBadRequest, gin.H{
"code": 1,
"msg": "应用ID不能为空",
})
return
}
if req.Status != 0 && req.Status != 1 {
c.JSON(http.StatusBadRequest, gin.H{
"code": 1,
"msg": "状态值无效",
})
return
}
// 获取数据库连接
db, ok := appBaseController.GetDB(c)
if !ok {
return
}
// 检查应用是否存在
var app models.App
if err := db.Where("id = ?", req.ID).First(&app).Error; err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 1,
"msg": "应用不存在",
})
return
}
// 更新状态
if err := db.Model(&app).Update("status", req.Status).Error; err != nil {
logrus.WithError(err).Error("Failed to update app status")
c.JSON(http.StatusInternalServerError, gin.H{
"code": 1,
"msg": "更新状态失败",
})
return
}
statusText := "禁用"
if req.Status == 1 {
statusText = "启用"
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"msg": "应用" + statusText + "成功",
})
}

View File

@@ -9,10 +9,10 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"networkDev/controllers" "networkDev/controllers"
"networkDev/middleware"
"networkDev/utils" "networkDev/utils"
"github.com/mojocn/base64Captcha" "github.com/mojocn/base64Captcha"
"github.com/spf13/viper"
) )
// 创建基础控制器实例 // 创建基础控制器实例
@@ -94,7 +94,7 @@ func CaptchaHandler(c *gin.Context) {
// 支持大小写不敏感匹配 // 支持大小写不敏感匹配
func VerifyCaptcha(c *gin.Context, captchaValue string) bool { func VerifyCaptcha(c *gin.Context, captchaValue string) bool {
// 检查是否为开发模式,如果是则跳过验证码验证 // 检查是否为开发模式,如果是则跳过验证码验证
if viper.GetBool("server.dev_mode") { if middleware.ShouldSkipCaptcha(c) {
return true return true
} }

View File

@@ -4,6 +4,7 @@ import (
"net/http" "net/http"
"networkDev/constants" "networkDev/constants"
"networkDev/controllers" "networkDev/controllers"
"networkDev/middleware"
"networkDev/models" "networkDev/models"
"networkDev/services" "networkDev/services"
"networkDev/utils" "networkDev/utils"
@@ -97,7 +98,7 @@ func AdminLayoutHandler(c *gin.Context) {
// - 展示系统信息:版本、开发模式、数据库类型、启动时长 // - 展示系统信息:版本、开发模式、数据库类型、启动时长
func DashboardFragmentHandler(c *gin.Context) { func DashboardFragmentHandler(c *gin.Context) {
version := constants.AppVersion version := constants.AppVersion
mode := viper.GetBool("server.dev_mode") mode := middleware.IsDevModeFromContext(c)
dbType := viper.GetString("database.type") dbType := viper.GetString("database.type")
if dbType == "" { if dbType == "" {
dbType = "sqlite" dbType = "sqlite"
@@ -118,7 +119,7 @@ func DashboardFragmentHandler(c *gin.Context) {
// - 返回系统运行状态的JSON数据用于前端定时刷新 // - 返回系统运行状态的JSON数据用于前端定时刷新
func SystemInfoHandler(c *gin.Context) { func SystemInfoHandler(c *gin.Context) {
version := constants.AppVersion version := constants.AppVersion
mode := viper.GetBool("server.dev_mode") mode := middleware.IsDevModeFromContext(c)
dbType := viper.GetString("database.type") dbType := viper.GetString("database.type")
if dbType == "" { if dbType == "" {
dbType = "sqlite" dbType = "sqlite"

100
middleware/devmode.go Normal file
View File

@@ -0,0 +1,100 @@
package middleware
import (
"networkDev/web"
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
)
// DevModeConfig 开发模式配置
type DevModeConfig struct {
// 是否启用模板热重载
EnableTemplateReload bool
// 是否跳过验证码验证
SkipCaptcha bool
// 是否显示详细错误信息
ShowDetailedErrors bool
// 是否启用调试日志
EnableDebugLog bool
}
// DevModeMiddleware 开发模式中间件
// 统一管理所有开发模式相关的功能
func DevModeMiddleware(engine *gin.Engine) gin.HandlerFunc {
return func(c *gin.Context) {
// 检查是否为开发模式
if IsDevMode() {
// 设置开发模式标识到上下文
c.Set("dev_mode", true)
c.Set("dev_config", GetDevModeConfig())
// 如果启用了模板热重载,则重新加载模板
config := GetDevModeConfig()
if config.EnableTemplateReload {
reloadTemplates(engine)
}
// 设置开发模式相关的响应头
c.Header("X-Dev-Mode", "true")
} else {
c.Set("dev_mode", false)
}
c.Next()
}
}
// IsDevMode 检查是否为开发模式
func IsDevMode() bool {
return viper.GetBool("server.dev_mode")
}
// GetDevModeConfig 获取开发模式配置
func GetDevModeConfig() DevModeConfig {
if !IsDevMode() {
return DevModeConfig{}
}
return DevModeConfig{
EnableTemplateReload: true, // 开发模式下默认启用模板热重载
SkipCaptcha: true, // 开发模式下默认跳过验证码
ShowDetailedErrors: true, // 开发模式下显示详细错误
EnableDebugLog: true, // 开发模式下启用调试日志
}
}
// IsDevModeFromContext 从上下文中检查是否为开发模式
func IsDevModeFromContext(c *gin.Context) bool {
if devMode, exists := c.Get("dev_mode"); exists {
if isDevMode, ok := devMode.(bool); ok {
return isDevMode
}
}
// 回退到配置检查
return IsDevMode()
}
// GetDevModeConfigFromContext 从上下文中获取开发模式配置
func GetDevModeConfigFromContext(c *gin.Context) DevModeConfig {
if config, exists := c.Get("dev_config"); exists {
if devConfig, ok := config.(DevModeConfig); ok {
return devConfig
}
}
// 回退到默认配置
return GetDevModeConfig()
}
// ShouldSkipCaptcha 检查是否应该跳过验证码验证
func ShouldSkipCaptcha(c *gin.Context) bool {
config := GetDevModeConfigFromContext(c)
return config.SkipCaptcha
}
// reloadTemplates 重新加载模板(内部函数)
func reloadTemplates(engine *gin.Engine) {
if tmpl, err := web.ParseTemplates(); err == nil {
engine.SetHTMLTemplate(tmpl)
}
}

View File

@@ -27,7 +27,7 @@ type API struct {
AppUUID string `gorm:"size:36;not null;index;comment:关联的应用UUID" json:"app_uuid"` AppUUID string `gorm:"size:36;not null;index;comment:关联的应用UUID" json:"app_uuid"`
// 接口状态1=启用0=禁用) // 接口状态1=启用0=禁用)
Status int `gorm:"default:1;not null;comment:接口状态1=启用0=禁用" json:"status"` Status int `gorm:"default:0;not null;comment:接口状态1=启用0=禁用" json:"status"`
// 接口提交算法 // 接口提交算法
// 支持的算法0=不加密1=RC42=RSA3=RSA动态4=易加密 // 支持的算法0=不加密1=RC42=RSA3=RSA动态4=易加密

View File

@@ -25,7 +25,7 @@ type App struct {
// UUID应用唯一标识符自动生成 // UUID应用唯一标识符自动生成
UUID string `gorm:"uniqueIndex;size:36;not null;comment:应用UUID唯一标识符" json:"uuid"` UUID string `gorm:"uniqueIndex;size:36;not null;comment:应用UUID唯一标识符" json:"uuid"`
// Status状态1=启用0=禁用json 名称与前端一致 // Status状态1=启用0=禁用json 名称与前端一致
Status int `gorm:"default:1;not null;comment:应用状态1=启用0=禁用" json:"status"` Status int `gorm:"default:0;not null;comment:应用状态1=启用0=禁用" json:"status"`
// Name应用名称json 名称与前端一致 // Name应用名称json 名称与前端一致
Name string `gorm:"size:100;not null;comment:应用名称" json:"name"` Name string `gorm:"size:100;not null;comment:应用名称" json:"name"`
// Secret应用密钥用于API认证 // Secret应用密钥用于API认证

View File

@@ -81,6 +81,7 @@ func RegisterAdminRoutes(router *gin.Engine) {
router.POST("/admin/api/apps/delete", adminctl.AdminAuthRequired(), adminctl.AppDeleteHandler) router.POST("/admin/api/apps/delete", adminctl.AdminAuthRequired(), adminctl.AppDeleteHandler)
router.POST("/admin/api/apps/batch_delete", adminctl.AdminAuthRequired(), adminctl.AppsBatchDeleteHandler) router.POST("/admin/api/apps/batch_delete", adminctl.AdminAuthRequired(), adminctl.AppsBatchDeleteHandler)
router.POST("/admin/api/apps/batch_update_status", adminctl.AdminAuthRequired(), adminctl.AppsBatchUpdateStatusHandler) router.POST("/admin/api/apps/batch_update_status", adminctl.AdminAuthRequired(), adminctl.AppsBatchUpdateStatusHandler)
router.POST("/admin/api/apps/update_status", adminctl.AdminAuthRequired(), adminctl.AppUpdateStatusHandler)
router.POST("/admin/api/apps/reset_secret", adminctl.AdminAuthRequired(), adminctl.AppResetSecretHandler) router.POST("/admin/api/apps/reset_secret", adminctl.AdminAuthRequired(), adminctl.AppResetSecretHandler)
router.GET("/admin/api/apps/get_app_data", adminctl.AdminAuthRequired(), adminctl.AppGetAppDataHandler) router.GET("/admin/api/apps/get_app_data", adminctl.AdminAuthRequired(), adminctl.AppGetAppDataHandler)
router.POST("/admin/api/apps/update_app_data", adminctl.AdminAuthRequired(), adminctl.AppUpdateAppDataHandler) router.POST("/admin/api/apps/update_app_data", adminctl.AdminAuthRequired(), adminctl.AppUpdateAppDataHandler)
@@ -96,6 +97,7 @@ func RegisterAdminRoutes(router *gin.Engine) {
// API接口管理API // API接口管理API
router.GET("/admin/api/apis/list", adminctl.AdminAuthRequired(), adminctl.APIListHandler) router.GET("/admin/api/apis/list", adminctl.AdminAuthRequired(), adminctl.APIListHandler)
router.POST("/admin/api/apis/update", adminctl.AdminAuthRequired(), adminctl.APIUpdateHandler) router.POST("/admin/api/apis/update", adminctl.AdminAuthRequired(), adminctl.APIUpdateHandler)
router.POST("/admin/api/apis/update_status", adminctl.AdminAuthRequired(), adminctl.APIUpdateStatusHandler)
router.GET("/admin/api/apis/apps", adminctl.AdminAuthRequired(), adminctl.APIGetAppsHandler) router.GET("/admin/api/apis/apps", adminctl.AdminAuthRequired(), adminctl.APIGetAppsHandler)
router.GET("/admin/api/apis/types", adminctl.AdminAuthRequired(), adminctl.APIGetTypesHandler) router.GET("/admin/api/apis/types", adminctl.AdminAuthRequired(), adminctl.APIGetTypesHandler)
router.POST("/admin/api/apis/generate_keys", adminctl.AdminAuthRequired(), adminctl.APIGenerateKeysHandler) router.POST("/admin/api/apis/generate_keys", adminctl.AdminAuthRequired(), adminctl.APIGenerateKeysHandler)

View File

@@ -65,3 +65,9 @@ func GetStaticFS() (fs.FS, error) { // Go 顶级函数不支持箭头写法
} }
return staticFS, nil return staticFS, nil
} }
// IsDevMode 检查是否为开发模式
// 注意:这个函数保留用于向后兼容,建议使用 middleware.IsDevMode()
func IsDevMode() bool {
return viper.GetBool("server.dev_mode")
}

View File

@@ -226,10 +226,10 @@
{ {
field: 'status_name', field: 'status_name',
title: '状态', title: '状态',
width: 80, width: 100,
templet: (d) => { templet: (d) => {
if (d.status === 1) return '<span style="color: #5FB878;">启用</span>'; const checked = d.status === 1 ? 'checked' : '';
return '<span style="color: #FF5722;">禁用</span>'; return `<input type="checkbox" ${checked} lay-skin="switch" lay-text="启用|禁用" lay-filter="api-status-switch" data-id="${d.id}">`;
} }
}, },
{ {
@@ -626,6 +626,35 @@
} }
}); });
// 接口状态switch开关事件监听
form.on('switch(api-status-switch)', function(data) {
const apiId = data.elem.getAttribute('data-id');
const status = data.elem.checked ? 1 : 0;
$.ajax({
url: '/admin/api/apis/update_status',
type: 'POST',
data: JSON.stringify({ id: parseInt(apiId), status: status }),
contentType: 'application/json',
success: function (res) {
if (res.code === 0) {
layer.msg(res.msg || '状态更新成功', { icon: 1 });
} else {
layer.msg(res.msg || '状态更新失败', { icon: 2 });
// 如果更新失败,恢复开关状态
data.elem.checked = !data.elem.checked;
form.render('checkbox');
}
},
error: function (xhr) {
layer.msg(xhr.responseText || '状态更新失败', { icon: 2 });
// 如果更新失败,恢复开关状态
data.elem.checked = !data.elem.checked;
form.render('checkbox');
}
});
});
}); });
</script> </script>

View File

@@ -374,8 +374,8 @@
title: '应用状态', title: '应用状态',
width: 100, width: 100,
templet: (d) => { templet: (d) => {
if (d.status === 1) return '<span style="color: #5FB878;">启用</span>'; const checked = d.status === 1 ? 'checked' : '';
return '<span style="color: #FF5722;">禁用</span>'; return `<input type="checkbox" name="app-status-${d.id}" lay-skin="switch" lay-text="启用|禁用" ${checked} lay-filter="app-status-switch" data-id="${d.id}">`;
} }
}, },
{ {
@@ -1195,6 +1195,35 @@
}); });
}); });
// 应用状态switch开关事件监听
form.on('switch(app-status-switch)', function(data) {
const appId = data.elem.getAttribute('data-id');
const status = data.elem.checked ? 1 : 0;
$.ajax({
url: '/admin/api/apps/update_status',
type: 'POST',
data: JSON.stringify({ id: parseInt(appId), status: status }),
contentType: 'application/json',
success: function (res) {
if (res.code === 0) {
layer.msg(res.msg || '状态更新成功', { icon: 1 });
} else {
layer.msg(res.msg || '状态更新失败', { icon: 2 });
// 如果更新失败,恢复开关状态
data.elem.checked = !data.elem.checked;
form.render('checkbox');
}
},
error: function (xhr) {
layer.msg(xhr.responseText || '状态更新失败', { icon: 2 });
// 如果更新失败,恢复开关状态
data.elem.checked = !data.elem.checked;
form.render('checkbox');
}
});
});
// Tips提示功能已移至admin.js统一管理 // Tips提示功能已移至admin.js统一管理
}); });
}); });

View File

@@ -11,25 +11,31 @@
<tbody> <tbody>
<tr> <tr>
<td style="width: 120px; font-weight: bold;">程序版本</td> <td style="width: 120px; font-weight: bold;">程序版本</td>
<td><span style="font-size: 18px; font-weight: bold; color: var(--lay-color-normal);">{{ .Version }}</span></td> <td style="height: 20px; vertical-align: middle;">
<span class="layui-badge layui-bg-blue" style="font-size: 14px; padding: 2px 8px; line-height: 1.2;">v{{ .Version }}</span>
</td>
</tr> </tr>
<tr> <tr>
<td style="font-weight: bold;">存储方案</td> <td style="font-weight: bold;">存储方案</td>
<td><span style="font-size: 18px; font-weight: bold; color: var(--lay-color-info);">{{ .DBType }}</span></td> <td style="height: 20px; vertical-align: middle;">
<span class="layui-badge layui-bg-cyan" style="font-size: 14px; padding: 2px 8px; line-height: 1.2;">{{ .DBType }}</span>
</td>
</tr> </tr>
<tr> <tr>
<td style="font-weight: bold;">开发模式</td> <td style="font-weight: bold;">开发模式</td>
<td> <td style="height: 20px; vertical-align: middle;">
{{ if .Mode }} {{ if .Mode }}
<span style="font-size: 18px; font-weight: bold; color: var(--lay-color-danger);">开启</span> <span class="layui-badge layui-bg-orange" style="font-size: 14px; padding: 2px 8px; line-height: 1.2;">开启</span>
{{ else }} {{ else }}
<span style="font-size: 18px; font-weight: bold; color: var(--lay-color-success);">关闭</span> <span class="layui-badge layui-bg-green" style="font-size: 14px; padding: 2px 8px; line-height: 1.2;">关闭</span>
{{ end }} {{ end }}
</td> </td>
</tr> </tr>
<tr> <tr>
<td style="font-weight: bold;">运行时长</td> <td style="font-weight: bold;">运行时长</td>
<td><span id="uptime-display" style="font-size: 18px; font-weight: bold; color: var(--lay-color-normal);">{{ .Uptime }}</span></td> <td style="height: 20px; vertical-align: middle;">
<span id="uptime-display" class="layui-badge layui-bg-gray" style="font-size: 14px; padding: 2px 8px; line-height: 1.2;">{{ .Uptime }}</span>
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -46,19 +52,27 @@
<tbody> <tbody>
<tr> <tr>
<td style="width: 120px; font-weight: bold;">全部应用</td> <td style="width: 120px; font-weight: bold;">全部应用</td>
<td><span id="total-apps" style="font-size: 18px; font-weight: bold;">0</span></td> <td style="height: 20px; vertical-align: middle;">
<span id="total-apps" class="layui-badge" style="font-size: 14px; padding: 2px 8px; min-width: 30px; text-align: center; line-height: 1.2;">0</span>
</td>
</tr> </tr>
<tr> <tr>
<td style="font-weight: bold;">启用应用</td> <td style="font-weight: bold;">启用应用</td>
<td><span id="enabled-apps" style="font-size: 18px; font-weight: bold; color: var(--lay-color-success);">0</span></td> <td style="height: 20px; vertical-align: middle;">
<span id="enabled-apps" class="layui-badge layui-bg-green" style="font-size: 14px; padding: 2px 8px; min-width: 30px; text-align: center; line-height: 1.2;">0</span>
</td>
</tr> </tr>
<tr> <tr>
<td style="font-weight: bold;">禁用应用</td> <td style="font-weight: bold;">禁用应用</td>
<td><span id="disabled-apps" style="font-size: 18px; font-weight: bold; color: var(--lay-color-danger);">0</span></td> <td style="height: 20px; vertical-align: middle;">
<span id="disabled-apps" class="layui-badge layui-bg-orange" style="font-size: 14px; padding: 2px 8px; min-width: 30px; text-align: center; line-height: 1.2;">0</span>
</td>
</tr> </tr>
<tr> <tr>
<td style="font-weight: bold;">变量数量</td> <td style="font-weight: bold;">变量数量</td>
<td><span id="total-variables" style="font-size: 18px; font-weight: bold; color: var(--lay-color-info);">0</span></td> <td style="height: 20px; vertical-align: middle;">
<span id="total-variables" class="layui-badge layui-bg-blue" style="font-size: 14px; padding: 2px 8px; min-width: 30px; text-align: center; line-height: 1.2;">0</span>
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -98,9 +112,19 @@
$.get('/admin/api/system/info', (res) => { $.get('/admin/api/system/info', (res) => {
if (res && res.code === 0 && res.data) { if (res && res.code === 0 && res.data) {
const data = res.data; const data = res.data;
// 更新运行时长 // 更新运行时长,保持徽章样式
if (data.uptime) { if (data.uptime) {
$('#uptime-display').text(data.uptime); const uptimeElement = $('#uptime-display');
uptimeElement.text(data.uptime);
// 确保徽章样式保持一致
if (!uptimeElement.hasClass('layui-badge')) {
uptimeElement.addClass('layui-badge layui-bg-gray');
uptimeElement.css({
'font-size': '14px',
'padding': '2px 8px',
'line-height': '1.2'
});
}
} }
} }
}).fail(() => { }).fail(() => {