mirror of
https://github.com/skyle1995/NetworkAuth.git
synced 2026-05-25 02:24:05 +08:00
Enhance user authentication and authentication
Fix the modification of personal information Fix the formatted page template
This commit is contained in:
@@ -37,6 +37,12 @@
|
|||||||
"security": {
|
"security": {
|
||||||
"jwt_secret": "your-jwt-secret-key",
|
"jwt_secret": "your-jwt-secret-key",
|
||||||
"encryption_key": "your-encryption-key",
|
"encryption_key": "your-encryption-key",
|
||||||
"jwt_refresh_threshold_hours": 6
|
"jwt_refresh_threshold_hours": 6,
|
||||||
|
"cookie": {
|
||||||
|
"secure": false,
|
||||||
|
"same_site": "Lax",
|
||||||
|
"domain": "",
|
||||||
|
"max_age": 86400
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,8 +26,33 @@ func LoginPageHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取或生成CSRF令牌
|
||||||
|
var token string
|
||||||
|
if existingToken := utils.GetCSRFTokenFromCookie(r); existingToken != "" {
|
||||||
|
// 重用现有的Cookie令牌
|
||||||
|
token = existingToken
|
||||||
|
} else {
|
||||||
|
// 生成新的CSRF令牌并设置到Cookie
|
||||||
|
newToken, err := utils.GenerateCSRFToken()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "生成CSRF令牌失败", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
token = newToken
|
||||||
|
utils.SetCSRFToken(w, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 准备模板数据
|
||||||
|
extraData := map[string]interface{}{
|
||||||
|
"Title": "管理员登录",
|
||||||
|
}
|
||||||
data := utils.GetDefaultTemplateData()
|
data := utils.GetDefaultTemplateData()
|
||||||
data["Title"] = "管理员登录"
|
data["CSRFToken"] = token
|
||||||
|
|
||||||
|
// 合并额外数据
|
||||||
|
for key, value := range extraData {
|
||||||
|
data[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
utils.RenderTemplate(w, "login.html", data)
|
utils.RenderTemplate(w, "login.html", data)
|
||||||
}
|
}
|
||||||
@@ -97,15 +122,8 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置JWT Cookie(HttpOnly,安全)
|
// 设置JWT Cookie(使用安全配置)
|
||||||
cookie := &http.Cookie{
|
cookie := utils.CreateSecureCookie("admin_session", token, utils.GetDefaultCookieMaxAge())
|
||||||
Name: "admin_session",
|
|
||||||
Value: token,
|
|
||||||
Path: "/",
|
|
||||||
HttpOnly: true,
|
|
||||||
Secure: false, // 生产环境应设置为true(HTTPS)
|
|
||||||
MaxAge: 24 * 60 * 60, // 24小时
|
|
||||||
}
|
|
||||||
http.SetCookie(w, cookie)
|
http.SetCookie(w, cookie)
|
||||||
|
|
||||||
utils.JsonResponse(w, http.StatusOK, true, "登录成功", map[string]interface{}{
|
utils.JsonResponse(w, http.StatusOK, true, "登录成功", map[string]interface{}{
|
||||||
@@ -132,15 +150,7 @@ func LogoutHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
// - 统一的Cookie清理函数,确保一致性
|
// - 统一的Cookie清理函数,确保一致性
|
||||||
// - 在JWT校验失败时自动调用,提升安全性和用户体验
|
// - 在JWT校验失败时自动调用,提升安全性和用户体验
|
||||||
func clearInvalidJWTCookie(w http.ResponseWriter) {
|
func clearInvalidJWTCookie(w http.ResponseWriter) {
|
||||||
cookie := &http.Cookie{
|
cookie := utils.CreateExpiredCookie("admin_session")
|
||||||
Name: "admin_session",
|
|
||||||
Value: "",
|
|
||||||
Path: "/",
|
|
||||||
HttpOnly: true,
|
|
||||||
Secure: false, // 生产环境应设置为true
|
|
||||||
MaxAge: -1, // 立即失效
|
|
||||||
Expires: time.Unix(0, 0), // 确保过期
|
|
||||||
}
|
|
||||||
http.SetCookie(w, cookie)
|
http.SetCookie(w, cookie)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -434,15 +444,8 @@ func GetCurrentAdminUserWithRefresh(w http.ResponseWriter, r *http.Request) (*JW
|
|||||||
}
|
}
|
||||||
newToken, err := generateJWTToken(user)
|
newToken, err := generateJWTToken(user)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// 更新Cookie
|
// 更新Cookie(使用安全配置)
|
||||||
newCookie := &http.Cookie{
|
newCookie := utils.CreateSecureCookie("admin_session", newToken, utils.GetDefaultCookieMaxAge())
|
||||||
Name: "admin_session",
|
|
||||||
Value: newToken,
|
|
||||||
Path: "/",
|
|
||||||
HttpOnly: true,
|
|
||||||
Secure: false, // 生产环境应设置为true(HTTPS)
|
|
||||||
MaxAge: 24 * 60 * 60, // 24小时
|
|
||||||
}
|
|
||||||
http.SetCookie(w, newCookie)
|
http.SetCookie(w, newCookie)
|
||||||
refreshed = true
|
refreshed = true
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import (
|
|||||||
"math/big"
|
"math/big"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
"networkDev/utils"
|
||||||
|
|
||||||
"github.com/mojocn/base64Captcha"
|
"github.com/mojocn/base64Captcha"
|
||||||
)
|
)
|
||||||
@@ -62,15 +63,7 @@ func CaptchaHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// 将验证码ID存储到session中(这里简化处理,实际项目中应该使用更安全的方式)
|
// 将验证码ID存储到session中(这里简化处理,实际项目中应该使用更安全的方式)
|
||||||
// 设置cookie来存储验证码ID
|
// 设置cookie来存储验证码ID
|
||||||
cookie := &http.Cookie{
|
cookie := utils.CreateSecureCookie("captcha_id", id, 300) // 5分钟过期
|
||||||
Name: "captcha_id",
|
|
||||||
Value: id,
|
|
||||||
Path: "/",
|
|
||||||
HttpOnly: true,
|
|
||||||
Secure: false, // 生产环境应设置为true
|
|
||||||
MaxAge: 300, // 5分钟过期
|
|
||||||
Expires: time.Now().Add(5 * time.Minute),
|
|
||||||
}
|
|
||||||
http.SetCookie(w, cookie)
|
http.SetCookie(w, cookie)
|
||||||
|
|
||||||
// 解码base64图片数据并返回
|
// 解码base64图片数据并返回
|
||||||
|
|||||||
@@ -26,21 +26,47 @@ func AdminIndexHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
// AdminLayoutHandler 后台布局页渲染
|
// AdminLayoutHandler 后台布局页渲染
|
||||||
// - 渲染 layout.html,包含顶部导航、侧边栏与动态内容容器
|
// - 渲染 layout.html,包含顶部导航、侧边栏与动态内容容器
|
||||||
func AdminLayoutHandler(w http.ResponseWriter, r *http.Request) {
|
func AdminLayoutHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
data := utils.GetDefaultTemplateData()
|
// 获取或生成CSRF令牌
|
||||||
|
var token string
|
||||||
|
if existingToken := utils.GetCSRFTokenFromCookie(r); existingToken != "" {
|
||||||
|
// 重用现有的Cookie令牌
|
||||||
|
token = existingToken
|
||||||
|
} else {
|
||||||
|
// 生成新的CSRF令牌并设置到Cookie
|
||||||
|
newToken, err := utils.GenerateCSRFToken()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "生成CSRF令牌失败", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
token = newToken
|
||||||
|
utils.SetCSRFToken(w, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 准备额外的模板数据
|
||||||
|
extraData := make(map[string]interface{})
|
||||||
|
|
||||||
// 从数据库读取站点标题
|
// 从数据库读取站点标题
|
||||||
db, err := database.GetDB()
|
db, dbErr := database.GetDB()
|
||||||
if err != nil {
|
if dbErr != nil {
|
||||||
data["Title"] = "凌动技术"
|
extraData["Title"] = "凌动技术"
|
||||||
} else {
|
} else {
|
||||||
siteTitle, err := services.FindSettingByName("site_title", db)
|
siteTitle, settingErr := services.FindSettingByName("site_title", db)
|
||||||
if err != nil || siteTitle == nil {
|
if settingErr != nil || siteTitle == nil {
|
||||||
data["Title"] = "凌动技术"
|
extraData["Title"] = "凌动技术"
|
||||||
} else {
|
} else {
|
||||||
data["Title"] = siteTitle.Value
|
extraData["Title"] = siteTitle.Value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 准备模板数据
|
||||||
|
data := utils.GetDefaultTemplateData()
|
||||||
|
data["CSRFToken"] = token
|
||||||
|
|
||||||
|
// 合并额外数据
|
||||||
|
for key, value := range extraData {
|
||||||
|
data[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
utils.RenderTemplate(w, "layout.html", data)
|
utils.RenderTemplate(w, "layout.html", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -160,15 +160,8 @@ func UserPasswordUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新Cookie
|
// 更新Cookie(使用安全配置)
|
||||||
cookie := &http.Cookie{
|
cookie := utils.CreateSecureCookie("admin_session", newToken, utils.GetDefaultCookieMaxAge())
|
||||||
Name: "admin_session",
|
|
||||||
Value: newToken,
|
|
||||||
Path: "/",
|
|
||||||
HttpOnly: true,
|
|
||||||
Secure: false, // 生产环境应设置为true(HTTPS)
|
|
||||||
MaxAge: 24 * 60 * 60, // 24小时
|
|
||||||
}
|
|
||||||
http.SetCookie(w, cookie)
|
http.SetCookie(w, cookie)
|
||||||
|
|
||||||
// 密码修改成功,已重新生成JWT令牌
|
// 密码修改成功,已重新生成JWT令牌
|
||||||
@@ -260,20 +253,14 @@ func UserProfileUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 重新签发JWT并写入Cookie
|
// 重新签发JWT并写入Cookie
|
||||||
newUser := models.User{UUID: claims.UserUUID, Username: username, Role: claims.Role}
|
// 使用完整的用户信息(包含密码)来生成JWT令牌
|
||||||
token, err := generateJWTToken(newUser)
|
user.Username = username // 更新用户名
|
||||||
|
token, err := generateJWTToken(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.JsonResponse(w, http.StatusInternalServerError, false, "生成新令牌失败", nil)
|
utils.JsonResponse(w, http.StatusInternalServerError, false, "生成新令牌失败", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cookie := &http.Cookie{
|
cookie := utils.CreateSecureCookie("admin_session", token, utils.GetDefaultCookieMaxAge())
|
||||||
Name: "admin_session",
|
|
||||||
Value: token,
|
|
||||||
Path: "/",
|
|
||||||
HttpOnly: true,
|
|
||||||
Secure: false,
|
|
||||||
MaxAge: 24 * 60 * 60,
|
|
||||||
}
|
|
||||||
http.SetCookie(w, cookie)
|
http.SetCookie(w, cookie)
|
||||||
|
|
||||||
utils.JsonResponse(w, http.StatusOK, true, "保存成功", map[string]interface{}{
|
utils.JsonResponse(w, http.StatusOK, true, "保存成功", map[string]interface{}{
|
||||||
|
|||||||
5
cookies.txt
Normal file
5
cookies.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Netscape HTTP Cookie File
|
||||||
|
# https://curl.se/docs/http-cookies.html
|
||||||
|
# This file was generated by libcurl! Edit at your own risk.
|
||||||
|
|
||||||
|
#HttpOnly_localhost FALSE / TRUE 1761422606 csrf_token QLYaH1VddKCyAFgijZ80OYxzDht7zVLPbXH-rprEXvM=
|
||||||
@@ -8,21 +8,22 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// SeedDefaultAdmin 初始化默认管理员账号
|
// SeedDefaultAdmin 初始化默认管理员账号
|
||||||
// - 如果用户名为 admin 的用户已存在,则跳过
|
// - 如果已存在任何管理员用户(role=0),则跳过
|
||||||
// - 如不存在,则创建用户名为 admin、密码为 admin123(以 bcrypt 哈希存储)、角色 Role=0 的管理员
|
// - 如不存在,则创建用户名为 admin、密码为 admin123(以 bcrypt 哈希存储)、角色 Role=0 的管理员
|
||||||
// - ID和UUID将自动生成
|
// - 根据需求:默认 admin 用户的 ID 固定为 10000
|
||||||
func SeedDefaultAdmin() error {
|
func SeedDefaultAdmin() error {
|
||||||
db, err := GetDB()
|
db, err := GetDB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否存在用户名为 admin 的用户
|
// 检查是否存在任何管理员用户(role=0)
|
||||||
var count int64
|
var count int64
|
||||||
if dbErr := db.Model(&models.User{}).Where("username = ?", "admin").Count(&count).Error; dbErr != nil {
|
if dbErr := db.Model(&models.User{}).Where("role = ?", 0).Count(&count).Error; dbErr != nil {
|
||||||
return dbErr
|
return dbErr
|
||||||
}
|
}
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
|
logrus.Info("已存在管理员用户,跳过默认管理员创建")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package server
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
adminctl "networkDev/controllers/admin"
|
adminctl "networkDev/controllers/admin"
|
||||||
|
"networkDev/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RegisterAdminRoutes 注册管理员后台相关路由
|
// RegisterAdminRoutes 注册管理员后台相关路由
|
||||||
@@ -23,7 +24,8 @@ func RegisterAdminRoutes(mux *http.ServeMux) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if r.Method == http.MethodPost {
|
if r.Method == http.MethodPost {
|
||||||
adminctl.LoginHandler(w, r)
|
// 应用CSRF保护
|
||||||
|
utils.RequireCSRFToken(adminctl.LoginHandler)(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
@@ -35,6 +37,9 @@ func RegisterAdminRoutes(mux *http.ServeMux) {
|
|||||||
// 验证码生成路由(无需认证)
|
// 验证码生成路由(无需认证)
|
||||||
mux.HandleFunc("/admin/captcha", adminctl.CaptchaHandler)
|
mux.HandleFunc("/admin/captcha", adminctl.CaptchaHandler)
|
||||||
|
|
||||||
|
// CSRF令牌获取API(无需认证,但需要在登录页面等地方获取)
|
||||||
|
mux.HandleFunc("/admin/api/csrf-token", utils.CSRFTokenHandler)
|
||||||
|
|
||||||
// 后台布局页(需要管理员认证)
|
// 后台布局页(需要管理员认证)
|
||||||
mux.HandleFunc("/admin/layout", adminctl.AdminAuthRequired(adminctl.AdminLayoutHandler))
|
mux.HandleFunc("/admin/layout", adminctl.AdminAuthRequired(adminctl.AdminLayoutHandler))
|
||||||
|
|
||||||
@@ -51,44 +56,44 @@ func RegisterAdminRoutes(mux *http.ServeMux) {
|
|||||||
|
|
||||||
// 个人资料API
|
// 个人资料API
|
||||||
mux.HandleFunc("/admin/api/user/profile", adminctl.AdminAuthRequired(adminctl.UserProfileQueryHandler))
|
mux.HandleFunc("/admin/api/user/profile", adminctl.AdminAuthRequired(adminctl.UserProfileQueryHandler))
|
||||||
mux.HandleFunc("/admin/api/user/profile/update", adminctl.AdminAuthRequired(adminctl.UserProfileUpdateHandler))
|
mux.HandleFunc("/admin/api/user/profile/update", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.UserProfileUpdateHandler)))
|
||||||
mux.HandleFunc("/admin/api/user/password", adminctl.AdminAuthRequired(adminctl.UserPasswordUpdateHandler))
|
mux.HandleFunc("/admin/api/user/password", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.UserPasswordUpdateHandler)))
|
||||||
|
|
||||||
// 系统设置API
|
// 系统设置API
|
||||||
mux.HandleFunc("/admin/api/settings", adminctl.AdminAuthRequired(adminctl.SettingsQueryHandler))
|
mux.HandleFunc("/admin/api/settings", adminctl.AdminAuthRequired(adminctl.SettingsQueryHandler))
|
||||||
mux.HandleFunc("/admin/api/settings/update", adminctl.AdminAuthRequired(adminctl.SettingsUpdateHandler))
|
mux.HandleFunc("/admin/api/settings/update", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.SettingsUpdateHandler)))
|
||||||
|
|
||||||
// 应用管理API
|
// 应用管理API
|
||||||
mux.HandleFunc("/admin/api/apps/list", adminctl.AdminAuthRequired(adminctl.AppsListHandler))
|
mux.HandleFunc("/admin/api/apps/list", adminctl.AdminAuthRequired(adminctl.AppsListHandler))
|
||||||
mux.HandleFunc("/admin/api/apps/create", adminctl.AdminAuthRequired(adminctl.AppCreateHandler))
|
mux.HandleFunc("/admin/api/apps/create", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.AppCreateHandler)))
|
||||||
mux.HandleFunc("/admin/api/apps/update", adminctl.AdminAuthRequired(adminctl.AppUpdateHandler))
|
mux.HandleFunc("/admin/api/apps/update", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.AppUpdateHandler)))
|
||||||
mux.HandleFunc("/admin/api/apps/delete", adminctl.AdminAuthRequired(adminctl.AppDeleteHandler))
|
mux.HandleFunc("/admin/api/apps/delete", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.AppDeleteHandler)))
|
||||||
mux.HandleFunc("/admin/api/apps/batch_delete", adminctl.AdminAuthRequired(adminctl.AppsBatchDeleteHandler))
|
mux.HandleFunc("/admin/api/apps/batch_delete", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.AppsBatchDeleteHandler)))
|
||||||
mux.HandleFunc("/admin/api/apps/batch_update_status", adminctl.AdminAuthRequired(adminctl.AppsBatchUpdateStatusHandler))
|
mux.HandleFunc("/admin/api/apps/batch_update_status", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.AppsBatchUpdateStatusHandler)))
|
||||||
mux.HandleFunc("/admin/api/apps/reset_secret", adminctl.AdminAuthRequired(adminctl.AppResetSecretHandler))
|
mux.HandleFunc("/admin/api/apps/reset_secret", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.AppResetSecretHandler)))
|
||||||
mux.HandleFunc("/admin/api/apps/get_app_data", adminctl.AdminAuthRequired(adminctl.AppGetAppDataHandler))
|
mux.HandleFunc("/admin/api/apps/get_app_data", adminctl.AdminAuthRequired(adminctl.AppGetAppDataHandler))
|
||||||
mux.HandleFunc("/admin/api/apps/update_app_data", adminctl.AdminAuthRequired(adminctl.AppUpdateAppDataHandler))
|
mux.HandleFunc("/admin/api/apps/update_app_data", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.AppUpdateAppDataHandler)))
|
||||||
mux.HandleFunc("/admin/api/apps/get_announcement", adminctl.AdminAuthRequired(adminctl.AppGetAnnouncementHandler))
|
mux.HandleFunc("/admin/api/apps/get_announcement", adminctl.AdminAuthRequired(adminctl.AppGetAnnouncementHandler))
|
||||||
mux.HandleFunc("/admin/api/apps/update_announcement", adminctl.AdminAuthRequired(adminctl.AppUpdateAnnouncementHandler))
|
mux.HandleFunc("/admin/api/apps/update_announcement", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.AppUpdateAnnouncementHandler)))
|
||||||
mux.HandleFunc("/admin/api/apps/get_multi_config", adminctl.AdminAuthRequired(adminctl.AppGetMultiConfigHandler))
|
mux.HandleFunc("/admin/api/apps/get_multi_config", adminctl.AdminAuthRequired(adminctl.AppGetMultiConfigHandler))
|
||||||
mux.HandleFunc("/admin/api/apps/update_multi_config", adminctl.AdminAuthRequired(adminctl.AppUpdateMultiConfigHandler))
|
mux.HandleFunc("/admin/api/apps/update_multi_config", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.AppUpdateMultiConfigHandler)))
|
||||||
mux.HandleFunc("/admin/api/apps/get_bind_config", adminctl.AdminAuthRequired(adminctl.AppGetBindConfigHandler))
|
mux.HandleFunc("/admin/api/apps/get_bind_config", adminctl.AdminAuthRequired(adminctl.AppGetBindConfigHandler))
|
||||||
mux.HandleFunc("/admin/api/apps/update_bind_config", adminctl.AdminAuthRequired(adminctl.AppUpdateBindConfigHandler))
|
mux.HandleFunc("/admin/api/apps/update_bind_config", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.AppUpdateBindConfigHandler)))
|
||||||
mux.HandleFunc("/admin/api/apps/get_register_config", adminctl.AdminAuthRequired(adminctl.AppGetRegisterConfigHandler))
|
mux.HandleFunc("/admin/api/apps/get_register_config", adminctl.AdminAuthRequired(adminctl.AppGetRegisterConfigHandler))
|
||||||
mux.HandleFunc("/admin/api/apps/update_register_config", adminctl.AdminAuthRequired(adminctl.AppUpdateRegisterConfigHandler))
|
mux.HandleFunc("/admin/api/apps/update_register_config", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.AppUpdateRegisterConfigHandler)))
|
||||||
|
|
||||||
// API接口管理API
|
// API接口管理API
|
||||||
mux.HandleFunc("/admin/api/apis/list", adminctl.AdminAuthRequired(adminctl.APIListHandler))
|
mux.HandleFunc("/admin/api/apis/list", adminctl.AdminAuthRequired(adminctl.APIListHandler))
|
||||||
mux.HandleFunc("/admin/api/apis/update", adminctl.AdminAuthRequired(adminctl.APIUpdateHandler))
|
mux.HandleFunc("/admin/api/apis/update", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.APIUpdateHandler)))
|
||||||
mux.HandleFunc("/admin/api/apis/apps", adminctl.AdminAuthRequired(adminctl.APIGetAppsHandler))
|
mux.HandleFunc("/admin/api/apis/apps", adminctl.AdminAuthRequired(adminctl.APIGetAppsHandler))
|
||||||
mux.HandleFunc("/admin/api/apis/types", adminctl.AdminAuthRequired(adminctl.APIGetTypesHandler))
|
mux.HandleFunc("/admin/api/apis/types", adminctl.AdminAuthRequired(adminctl.APIGetTypesHandler))
|
||||||
mux.HandleFunc("/admin/api/apis/generate_keys", adminctl.AdminAuthRequired(adminctl.APIGenerateKeysHandler))
|
mux.HandleFunc("/admin/api/apis/generate_keys", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.APIGenerateKeysHandler)))
|
||||||
|
|
||||||
// 变量管理API
|
// 变量管理API
|
||||||
mux.HandleFunc("/admin/variable/list", adminctl.AdminAuthRequired(adminctl.VariableListHandler))
|
mux.HandleFunc("/admin/variable/list", adminctl.AdminAuthRequired(adminctl.VariableListHandler))
|
||||||
mux.HandleFunc("/admin/variable/apps", adminctl.AdminAuthRequired(adminctl.VariableGetAppsHandler))
|
mux.HandleFunc("/admin/variable/apps", adminctl.AdminAuthRequired(adminctl.VariableGetAppsHandler))
|
||||||
mux.HandleFunc("/admin/variable/create", adminctl.AdminAuthRequired(adminctl.VariableCreateHandler))
|
mux.HandleFunc("/admin/variable/create", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.VariableCreateHandler)))
|
||||||
mux.HandleFunc("/admin/variable/update", adminctl.AdminAuthRequired(adminctl.VariableUpdateHandler))
|
mux.HandleFunc("/admin/variable/update", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.VariableUpdateHandler)))
|
||||||
mux.HandleFunc("/admin/variable/delete", adminctl.AdminAuthRequired(adminctl.VariableDeleteHandler))
|
mux.HandleFunc("/admin/variable/delete", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.VariableDeleteHandler)))
|
||||||
mux.HandleFunc("/admin/variable/batch_delete", adminctl.AdminAuthRequired(adminctl.VariablesBatchDeleteHandler))
|
mux.HandleFunc("/admin/variable/batch_delete", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.VariablesBatchDeleteHandler)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,25 @@ func GetDefaultTemplateData() map[string]interface{} {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTemplateDataWithCSRF 获取包含CSRF令牌的模板数据
|
||||||
|
// 合并默认数据和CSRF令牌,用于需要CSRF保护的页面
|
||||||
|
func GetTemplateDataWithCSRF(r *http.Request, additionalData map[string]interface{}) map[string]interface{} {
|
||||||
|
// 获取默认模板数据
|
||||||
|
data := GetDefaultTemplateData()
|
||||||
|
|
||||||
|
// 添加CSRF令牌
|
||||||
|
data["CSRFToken"] = GetCSRFTokenForTemplate(r)
|
||||||
|
|
||||||
|
// 合并额外数据
|
||||||
|
if additionalData != nil {
|
||||||
|
for key, value := range additionalData {
|
||||||
|
data[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
// GetClientIP 获取客户端IP地址
|
// GetClientIP 获取客户端IP地址
|
||||||
// 优先从 X-Forwarded-For 和 X-Real-IP 头部获取,否则使用 RemoteAddr
|
// 优先从 X-Forwarded-For 和 X-Real-IP 头部获取,否则使用 RemoteAddr
|
||||||
func GetClientIP(r *http.Request) string {
|
func GetClientIP(r *http.Request) string {
|
||||||
|
|||||||
77
utils/cookie.go
Normal file
77
utils/cookie.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateSecureCookie 创建安全的Cookie
|
||||||
|
// name: Cookie名称
|
||||||
|
// value: Cookie值
|
||||||
|
// maxAge: 过期时间(秒),0表示会话Cookie,-1表示立即过期
|
||||||
|
func CreateSecureCookie(name, value string, maxAge int) *http.Cookie {
|
||||||
|
cookie := &http.Cookie{
|
||||||
|
Name: name,
|
||||||
|
Value: value,
|
||||||
|
Path: "/",
|
||||||
|
HttpOnly: true,
|
||||||
|
MaxAge: maxAge,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从配置读取安全设置
|
||||||
|
if viper.GetBool("security.cookie.secure") {
|
||||||
|
cookie.Secure = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置SameSite属性
|
||||||
|
sameSite := viper.GetString("security.cookie.same_site")
|
||||||
|
switch sameSite {
|
||||||
|
case "Strict":
|
||||||
|
cookie.SameSite = http.SameSiteStrictMode
|
||||||
|
case "Lax":
|
||||||
|
cookie.SameSite = http.SameSiteLaxMode
|
||||||
|
case "None":
|
||||||
|
cookie.SameSite = http.SameSiteNoneMode
|
||||||
|
// SameSite=None 必须配合 Secure=true 使用
|
||||||
|
cookie.Secure = true
|
||||||
|
default:
|
||||||
|
cookie.SameSite = http.SameSiteStrictMode
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置Domain(如果配置了)
|
||||||
|
domain := viper.GetString("security.cookie.domain")
|
||||||
|
if domain != "" {
|
||||||
|
cookie.Domain = domain
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果maxAge > 0,设置Expires时间
|
||||||
|
if maxAge > 0 {
|
||||||
|
cookie.Expires = time.Now().Add(time.Duration(maxAge) * time.Second)
|
||||||
|
} else if maxAge == -1 {
|
||||||
|
// 立即过期
|
||||||
|
cookie.Expires = time.Unix(0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cookie
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateSessionCookie 创建会话Cookie(浏览器关闭时过期)
|
||||||
|
func CreateSessionCookie(name, value string) *http.Cookie {
|
||||||
|
return CreateSecureCookie(name, value, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateExpiredCookie 创建立即过期的Cookie(用于清理)
|
||||||
|
func CreateExpiredCookie(name string) *http.Cookie {
|
||||||
|
return CreateSecureCookie(name, "", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDefaultCookieMaxAge 获取默认Cookie过期时间
|
||||||
|
func GetDefaultCookieMaxAge() int {
|
||||||
|
maxAge := viper.GetInt("security.cookie.max_age")
|
||||||
|
if maxAge <= 0 {
|
||||||
|
return 86400 // 默认24小时
|
||||||
|
}
|
||||||
|
return maxAge
|
||||||
|
}
|
||||||
@@ -291,7 +291,8 @@ func DecryptStringWithSalt(enc, salt string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HashPasswordWithSalt 使用盐值对密码进行哈希处理
|
// HashPasswordWithSalt 使用盐值对密码进行哈希处理
|
||||||
// 将密码和盐值组合后使用bcrypt进行哈希
|
// 将密码和盐值组合后先用SHA256处理,再使用bcrypt进行哈希
|
||||||
|
// 这样可以避免bcrypt的72字节限制问题
|
||||||
// password: 原始密码
|
// password: 原始密码
|
||||||
// salt: 密码盐值
|
// salt: 密码盐值
|
||||||
// 返回: bcrypt哈希值和错误信息
|
// 返回: bcrypt哈希值和错误信息
|
||||||
@@ -299,8 +300,12 @@ func HashPasswordWithSalt(password, salt string) (string, error) {
|
|||||||
// 将密码和盐值组合
|
// 将密码和盐值组合
|
||||||
combined := password + salt
|
combined := password + salt
|
||||||
|
|
||||||
|
// 先使用SHA256处理组合后的字符串,确保长度固定且不超过bcrypt限制
|
||||||
|
hash := sha256.Sum256([]byte(combined))
|
||||||
|
sha256Hash := fmt.Sprintf("%x", hash) // 64字节的十六进制字符串
|
||||||
|
|
||||||
// 使用bcrypt进行哈希(成本因子10,平衡安全性和性能)
|
// 使用bcrypt进行哈希(成本因子10,平衡安全性和性能)
|
||||||
hashed, err := bcrypt.GenerateFromPassword([]byte(combined), 10)
|
hashed, err := bcrypt.GenerateFromPassword([]byte(sha256Hash), 10)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -317,8 +322,12 @@ func VerifyPasswordWithSalt(password, salt, hashedPassword string) bool {
|
|||||||
// 将密码和盐值组合
|
// 将密码和盐值组合
|
||||||
combined := password + salt
|
combined := password + salt
|
||||||
|
|
||||||
|
// 先使用SHA256处理组合后的字符串,与哈希生成逻辑保持一致
|
||||||
|
hash := sha256.Sum256([]byte(combined))
|
||||||
|
sha256Hash := fmt.Sprintf("%x", hash) // 64字节的十六进制字符串
|
||||||
|
|
||||||
// 使用bcrypt验证
|
// 使用bcrypt验证
|
||||||
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(combined))
|
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(sha256Hash))
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
168
utils/csrf.go
Normal file
168
utils/csrf.go
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/subtle"
|
||||||
|
"encoding/base64"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CSRFTokenLength = 32
|
||||||
|
CSRFCookieName = "csrf_token"
|
||||||
|
CSRFHeaderName = "X-CSRF-Token"
|
||||||
|
CSRFFormField = "csrf_token"
|
||||||
|
)
|
||||||
|
|
||||||
|
// generateRandomBytes 生成指定长度的随机字节
|
||||||
|
func generateRandomBytes(length int) ([]byte, error) {
|
||||||
|
bytes := make([]byte, length)
|
||||||
|
_, err := rand.Read(bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return bytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateCSRFToken 生成CSRF令牌
|
||||||
|
func GenerateCSRFToken() (string, error) {
|
||||||
|
bytes, err := generateRandomBytes(CSRFTokenLength)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return base64.URLEncoding.EncodeToString(bytes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCSRFToken 设置CSRF令牌到Cookie和响应头
|
||||||
|
func SetCSRFToken(w http.ResponseWriter, token string) {
|
||||||
|
// 设置CSRF令牌到Cookie
|
||||||
|
cookie := CreateSecureCookie(CSRFCookieName, token, 3600) // 1小时过期
|
||||||
|
http.SetCookie(w, cookie)
|
||||||
|
|
||||||
|
// 设置CSRF令牌到响应头,方便JavaScript获取
|
||||||
|
w.Header().Set("X-CSRF-Token", token)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCSRFTokenFromRequest 从请求中获取CSRF令牌
|
||||||
|
// 优先级:Header > Form > Cookie
|
||||||
|
func GetCSRFTokenFromRequest(r *http.Request) string {
|
||||||
|
// 1. 从Header获取
|
||||||
|
if token := r.Header.Get(CSRFHeaderName); token != "" {
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 从Form获取
|
||||||
|
if token := r.FormValue(CSRFFormField); token != "" {
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 从Cookie获取(作为备选)
|
||||||
|
if cookie, err := r.Cookie(CSRFCookieName); err == nil {
|
||||||
|
return cookie.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCSRFTokenFromCookie 从Cookie中获取CSRF令牌
|
||||||
|
func GetCSRFTokenFromCookie(r *http.Request) string {
|
||||||
|
cookie, err := r.Cookie(CSRFCookieName)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return cookie.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateCSRFToken 验证CSRF令牌
|
||||||
|
func ValidateCSRFToken(r *http.Request) bool {
|
||||||
|
// 获取Cookie中的令牌(服务器端存储的)
|
||||||
|
cookieToken := GetCSRFTokenFromCookie(r)
|
||||||
|
if cookieToken == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取请求中的令牌(客户端提交的)
|
||||||
|
requestToken := GetCSRFTokenFromRequest(r)
|
||||||
|
if requestToken == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用常量时间比较防止时序攻击
|
||||||
|
return subtle.ConstantTimeCompare([]byte(cookieToken), []byte(requestToken)) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSRFProtection CSRF保护中间件
|
||||||
|
func CSRFProtection(next http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// 对于GET、HEAD、OPTIONS请求,只生成令牌,不验证
|
||||||
|
if r.Method == http.MethodGet || r.Method == http.MethodHead || r.Method == http.MethodOptions {
|
||||||
|
// 生成新的CSRF令牌
|
||||||
|
token, err := GenerateCSRFToken()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
SetCSRFToken(w, token)
|
||||||
|
next(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对于POST、PUT、DELETE等修改性请求,验证CSRF令牌
|
||||||
|
if !ValidateCSRFToken(r) {
|
||||||
|
JsonResponse(w, http.StatusForbidden, false, "CSRF令牌验证失败", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证通过,继续处理请求
|
||||||
|
next(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireCSRFToken 要求CSRF令牌的中间件(用于特定路由)
|
||||||
|
func RequireCSRFToken(next http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !ValidateCSRFToken(r) {
|
||||||
|
JsonResponse(w, http.StatusForbidden, false, "CSRF令牌验证失败", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
next(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCSRFTokenForTemplate 获取用于模板的CSRF令牌
|
||||||
|
func GetCSRFTokenForTemplate(r *http.Request) string {
|
||||||
|
// 尝试从Cookie获取现有令牌
|
||||||
|
if token := GetCSRFTokenFromCookie(r); token != "" {
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有现有令牌,生成新的(但不设置到响应中)
|
||||||
|
token, err := GenerateCSRFToken()
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSRFTokenHandler 专门用于获取CSRF令牌的API端点
|
||||||
|
func CSRFTokenHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodGet {
|
||||||
|
JsonResponse(w, http.StatusMethodNotAllowed, false, "只支持GET请求", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成新的CSRF令牌
|
||||||
|
token, err := GenerateCSRFToken()
|
||||||
|
if err != nil {
|
||||||
|
JsonResponse(w, http.StatusInternalServerError, false, "生成CSRF令牌失败", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置令牌到Cookie和响应头
|
||||||
|
SetCSRFToken(w, token)
|
||||||
|
|
||||||
|
// 返回令牌给前端
|
||||||
|
JsonResponse(w, http.StatusOK, true, "CSRF令牌获取成功", map[string]interface{}{
|
||||||
|
"csrf_token": token,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -6,6 +6,63 @@ const rootPath = (function (src) {
|
|||||||
return src.substring(0, src.lastIndexOf('/') + 1);
|
return src.substring(0, src.lastIndexOf('/') + 1);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
// CSRF令牌管理
|
||||||
|
const CSRFManager = {
|
||||||
|
// 缓存的CSRF令牌
|
||||||
|
token: null,
|
||||||
|
|
||||||
|
// 获取CSRF令牌
|
||||||
|
async getToken() {
|
||||||
|
if (this.token) {
|
||||||
|
return this.token;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/admin/api/csrf-token', {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.code === 0 && data.data && data.data.csrf_token) {
|
||||||
|
this.token = data.data.csrf_token;
|
||||||
|
return this.token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取CSRF令牌失败:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 清除缓存的令牌
|
||||||
|
clearToken() {
|
||||||
|
this.token = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 为fetch请求添加CSRF令牌
|
||||||
|
async addCSRFHeader(headers = {}) {
|
||||||
|
const token = await this.getToken();
|
||||||
|
if (token) {
|
||||||
|
headers['X-CSRF-Token'] = token;
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 增强的fetch函数,自动添加CSRF令牌
|
||||||
|
window.fetchWithCSRF = async function(url, options = {}) {
|
||||||
|
const headers = await CSRFManager.addCSRFHeader(options.headers || {});
|
||||||
|
return fetch(url, {
|
||||||
|
...options,
|
||||||
|
headers
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const app = document.querySelector('#app')
|
const app = document.querySelector('#app')
|
||||||
|
|
||||||
addLink({ href: layuicss }).then(() => {
|
addLink({ href: layuicss }).then(() => {
|
||||||
@@ -150,7 +207,7 @@ loadScript(layuijs, function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 调用登出接口
|
// 调用登出接口
|
||||||
fetch('/admin/logout', {
|
fetchWithCSRF('/admin/logout', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
{{ define "apis.html" }}
|
{{ define "apis.html" }}
|
||||||
<section>
|
<section>
|
||||||
<h2>接口管理</h2>
|
<h2>接口管理</h2>d
|
||||||
|
|
||||||
|
|
||||||
<div class="layui-card" style="margin-top:12px">
|
<div class="layui-card" style="margin-top:12px">
|
||||||
<div class="layui-card-header">筛选</div>
|
<div class="layui-card-header">筛选</div>d
|
||||||
<div class="layui-card-body">
|
<div class="layui-card-body">
|
||||||
<form class="layui-form layui-form-pane" id="apiFilterForm" lay-filter="apiFilterForm">
|
<form class="layui-form layui-form-pane" id="apiFilterForm" lay-filter="apiFilterForm">
|
||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
@@ -12,7 +10,7 @@
|
|||||||
<label class="layui-form-label">应用</label>
|
<label class="layui-form-label">应用</label>
|
||||||
<div class="layui-input-inline">
|
<div class="layui-input-inline">
|
||||||
<select name="app_uuid" lay-filter="appSelect" lay-search="">
|
<select name="app_uuid" lay-filter="appSelect" lay-search="">
|
||||||
<option value="">请选择应用</option>
|
<option value="">请选择应用</option>e
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -73,14 +71,18 @@
|
|||||||
<label class="layui-form-label" style="cursor: pointer;" data-tips="submit-keys">提交密钥</label>
|
<label class="layui-form-label" style="cursor: pointer;" data-tips="submit-keys">提交密钥</label>
|
||||||
<div class="layui-input-block">
|
<div class="layui-input-block">
|
||||||
<div id="submit-rc4" style="display:none">
|
<div id="submit-rc4" style="display:none">
|
||||||
<input type="text" name="submit_private_key" placeholder="RC4密钥(16位十六进制,大写)" autocomplete="off" class="layui-input" readonly />
|
<input type="text" name="submit_private_key" placeholder="RC4密钥(16位十六进制,大写)" autocomplete="off"
|
||||||
|
class="layui-input" readonly />
|
||||||
</div>
|
</div>
|
||||||
<div id="submit-rsa" style="display:none">
|
<div id="submit-rsa" style="display:none">
|
||||||
<textarea name="submit_public_key" placeholder="RSA 公钥(PEM 明文)" class="layui-textarea" rows="4" readonly></textarea>
|
<textarea name="submit_public_key" placeholder="RSA 公钥(PEM 明文)" class="layui-textarea" rows="4"
|
||||||
<textarea name="submit_private_key" placeholder="RSA 私钥(PEM 明文)" class="layui-textarea" rows="6" style="margin-top:8px;" readonly></textarea>
|
readonly></textarea>
|
||||||
|
<textarea name="submit_private_key" placeholder="RSA 私钥(PEM 明文)" class="layui-textarea" rows="6"
|
||||||
|
style="margin-top:8px;" readonly></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div id="submit-easy" style="display:none">
|
<div id="submit-easy" style="display:none">
|
||||||
<input type="text" name="submit_private_key" placeholder="易加密密钥(15-30位整数数组,逗号分隔)" autocomplete="off" class="layui-input" readonly />
|
<input type="text" name="submit_private_key" placeholder="易加密密钥(15-30位整数数组,逗号分隔)" autocomplete="off"
|
||||||
|
class="layui-input" readonly />
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-top:8px;">
|
<div style="margin-top:8px;">
|
||||||
<button type="button" class="layui-btn layui-btn-sm" id="btnGenSubmitKeys">重新生成</button>
|
<button type="button" class="layui-btn layui-btn-sm" id="btnGenSubmitKeys">重新生成</button>
|
||||||
@@ -105,14 +107,18 @@
|
|||||||
<label class="layui-form-label" style="cursor: pointer;" data-tips="return-keys">返回密钥</label>
|
<label class="layui-form-label" style="cursor: pointer;" data-tips="return-keys">返回密钥</label>
|
||||||
<div class="layui-input-block">
|
<div class="layui-input-block">
|
||||||
<div id="return-rc4" style="display:none">
|
<div id="return-rc4" style="display:none">
|
||||||
<input type="text" name="return_private_key" placeholder="RC4密钥(16位十六进制,大写)" autocomplete="off" class="layui-input" readonly />
|
<input type="text" name="return_private_key" placeholder="RC4密钥(16位十六进制,大写)" autocomplete="off"
|
||||||
|
class="layui-input" readonly />
|
||||||
</div>
|
</div>
|
||||||
<div id="return-rsa" style="display:none">
|
<div id="return-rsa" style="display:none">
|
||||||
<textarea name="return_public_key" placeholder="RSA 公钥(PEM 明文)" class="layui-textarea" rows="4" readonly></textarea>
|
<textarea name="return_public_key" placeholder="RSA 公钥(PEM 明文)" class="layui-textarea" rows="4"
|
||||||
<textarea name="return_private_key" placeholder="RSA 私钥(PEM 明文)" class="layui-textarea" rows="6" style="margin-top:8px;" readonly></textarea>
|
readonly></textarea>
|
||||||
|
<textarea name="return_private_key" placeholder="RSA 私钥(PEM 明文)" class="layui-textarea" rows="6"
|
||||||
|
style="margin-top:8px;" readonly></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div id="return-easy" style="display:none">
|
<div id="return-easy" style="display:none">
|
||||||
<input type="text" name="return_private_key" placeholder="易加密密钥(15-30位整数数组,逗号分隔)" autocomplete="off" class="layui-input" readonly />
|
<input type="text" name="return_private_key" placeholder="易加密密钥(15-30位整数数组,逗号分隔)" autocomplete="off"
|
||||||
|
class="layui-input" readonly />
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-top:8px;">
|
<div style="margin-top:8px;">
|
||||||
<button type="button" class="layui-btn layui-btn-sm" id="btnGenReturnKeys">重新生成</button>
|
<button type="button" class="layui-btn layui-btn-sm" id="btnGenReturnKeys">重新生成</button>
|
||||||
@@ -133,7 +139,7 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
layui.use(['table', 'form', 'layer', 'dropdown'], function() {
|
layui.use(['table', 'form', 'layer', 'dropdown'], function () {
|
||||||
const $ = layui.$;
|
const $ = layui.$;
|
||||||
var table = layui.table;
|
var table = layui.table;
|
||||||
var form = layui.form;
|
var form = layui.form;
|
||||||
@@ -149,12 +155,12 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
|
|||||||
var currentAppUUID = '';
|
var currentAppUUID = '';
|
||||||
|
|
||||||
// 复制到剪贴板函数
|
// 复制到剪贴板函数
|
||||||
window.copyToClipboard = function(text) {
|
window.copyToClipboard = function (text) {
|
||||||
if (navigator.clipboard && window.isSecureContext) {
|
if (navigator.clipboard && window.isSecureContext) {
|
||||||
// 使用现代 Clipboard API
|
// 使用现代 Clipboard API
|
||||||
navigator.clipboard.writeText(text).then(function() {
|
navigator.clipboard.writeText(text).then(function () {
|
||||||
layer.msg('接口地址已复制到剪贴板', {icon: 1, time: 2000});
|
layer.msg('接口地址已复制到剪贴板', { icon: 1, time: 2000 });
|
||||||
}).catch(function(err) {
|
}).catch(function (err) {
|
||||||
console.error('复制失败:', err);
|
console.error('复制失败:', err);
|
||||||
fallbackCopyTextToClipboard(text);
|
fallbackCopyTextToClipboard(text);
|
||||||
});
|
});
|
||||||
@@ -177,13 +183,13 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
|
|||||||
try {
|
try {
|
||||||
var successful = document.execCommand('copy');
|
var successful = document.execCommand('copy');
|
||||||
if (successful) {
|
if (successful) {
|
||||||
layer.msg('接口地址已复制到剪贴板', {icon: 1, time: 2000});
|
layer.msg('接口地址已复制到剪贴板', { icon: 1, time: 2000 });
|
||||||
} else {
|
} else {
|
||||||
layer.msg('复制失败,请手动复制', {icon: 2, time: 3000});
|
layer.msg('复制失败,请手动复制', { icon: 2, time: 3000 });
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('复制失败:', err);
|
console.error('复制失败:', err);
|
||||||
layer.msg('复制失败,请手动复制', {icon: 2, time: 3000});
|
layer.msg('复制失败,请手动复制', { icon: 2, time: 3000 });
|
||||||
}
|
}
|
||||||
document.body.removeChild(textArea);
|
document.body.removeChild(textArea);
|
||||||
}
|
}
|
||||||
@@ -192,7 +198,7 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
|
|||||||
var apisTable = table.render({
|
var apisTable = table.render({
|
||||||
elem: '#apisTable',
|
elem: '#apisTable',
|
||||||
url: '/admin/api/apis/list',
|
url: '/admin/api/apis/list',
|
||||||
parseData: function(res) {
|
parseData: function (res) {
|
||||||
return {
|
return {
|
||||||
code: res.success ? 0 : 1,
|
code: res.success ? 0 : 1,
|
||||||
msg: res.message || '',
|
msg: res.message || '',
|
||||||
@@ -209,7 +215,7 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
|
|||||||
limit: 20,
|
limit: 20,
|
||||||
limits: [10, 20, 50, 100],
|
limits: [10, 20, 50, 100],
|
||||||
loading: true,
|
loading: true,
|
||||||
done: function(res, curr, count) {
|
done: function (res, curr, count) {
|
||||||
// 表格渲染完成后的回调
|
// 表格渲染完成后的回调
|
||||||
},
|
},
|
||||||
cols: [[
|
cols: [[
|
||||||
@@ -271,13 +277,13 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
|
|||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/admin/api/apis/apps',
|
url: '/admin/api/apis/apps',
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
success: function(res) {
|
success: function (res) {
|
||||||
if (res.success && res.data) {
|
if (res.success && res.data) {
|
||||||
var filterSelect = $('select[name="app_uuid"]').eq(0);
|
var filterSelect = $('select[name="app_uuid"]').eq(0);
|
||||||
// 清空现有选项(保留默认选项)
|
// 清空现有选项(保留默认选项)
|
||||||
filterSelect.find('option:not(:first)').remove();
|
filterSelect.find('option:not(:first)').remove();
|
||||||
// 添加应用选项(不默认选中,保持“请选择应用”以显示全部接口)
|
// 添加应用选项(不默认选中,保持“请选择应用”以显示全部接口)
|
||||||
res.data.forEach(function(app) {
|
res.data.forEach(function (app) {
|
||||||
var option = '<option value="' + app.uuid + '">' + app.name + '</option>';
|
var option = '<option value="' + app.uuid + '">' + app.name + '</option>';
|
||||||
filterSelect.append(option);
|
filterSelect.append(option);
|
||||||
});
|
});
|
||||||
@@ -285,8 +291,8 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
|
|||||||
form.render('select');
|
form.render('select');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function() {
|
error: function () {
|
||||||
layer.msg('加载应用列表失败', {icon: 2});
|
layer.msg('加载应用列表失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -296,13 +302,13 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
|
|||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/admin/api/apis/types',
|
url: '/admin/api/apis/types',
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
success: function(res) {
|
success: function (res) {
|
||||||
if (res.success && res.data) {
|
if (res.success && res.data) {
|
||||||
var typeSelect = $('select[name="api_type"]').eq(0);
|
var typeSelect = $('select[name="api_type"]').eq(0);
|
||||||
// 清空现有选项(保留默认选项)
|
// 清空现有选项(保留默认选项)
|
||||||
typeSelect.find('option:not(:first)').remove();
|
typeSelect.find('option:not(:first)').remove();
|
||||||
// 添加接口类型选项
|
// 添加接口类型选项
|
||||||
res.data.forEach(function(type) {
|
res.data.forEach(function (type) {
|
||||||
var option = '<option value="' + type.value + '">' + type.name + '</option>';
|
var option = '<option value="' + type.value + '">' + type.name + '</option>';
|
||||||
typeSelect.append(option);
|
typeSelect.append(option);
|
||||||
});
|
});
|
||||||
@@ -310,8 +316,8 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
|
|||||||
form.render('select');
|
form.render('select');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function() {
|
error: function () {
|
||||||
layer.msg('加载接口类型列表失败', {icon: 2});
|
layer.msg('加载接口类型列表失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -321,7 +327,7 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
|
|||||||
loadAPITypes();
|
loadAPITypes();
|
||||||
|
|
||||||
// 监听应用选择变化
|
// 监听应用选择变化
|
||||||
form.on('select(appSelect)', function(data) {
|
form.on('select(appSelect)', function (data) {
|
||||||
currentAppUUID = data.value;
|
currentAppUUID = data.value;
|
||||||
apisTable.reload({
|
apisTable.reload({
|
||||||
where: {
|
where: {
|
||||||
@@ -335,7 +341,7 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 监听接口类型选择变化
|
// 监听接口类型选择变化
|
||||||
form.on('select(apiTypeSelect)', function(data) {
|
form.on('select(apiTypeSelect)', function (data) {
|
||||||
apisTable.reload({
|
apisTable.reload({
|
||||||
where: {
|
where: {
|
||||||
app_uuid: $('select[name="app_uuid"]').val(),
|
app_uuid: $('select[name="app_uuid"]').val(),
|
||||||
@@ -348,7 +354,7 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 重置筛选
|
// 重置筛选
|
||||||
$('#btnResetAPIs').on('click', function() {
|
$('#btnResetAPIs').on('click', function () {
|
||||||
$('#apiFilterForm')[0].reset();
|
$('#apiFilterForm')[0].reset();
|
||||||
form.render();
|
form.render();
|
||||||
apisTable.reload({
|
apisTable.reload({
|
||||||
@@ -446,7 +452,7 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
|
|||||||
type: 'POST',
|
type: 'POST',
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
data: JSON.stringify({ side: side, algorithm: algorithm }),
|
data: JSON.stringify({ side: side, algorithm: algorithm }),
|
||||||
success: function(res){
|
success: function (res) {
|
||||||
if (res.success && res.data) {
|
if (res.success && res.data) {
|
||||||
if (algorithm === 1) { // RC4
|
if (algorithm === 1) { // RC4
|
||||||
$('[name="' + prefix + '_private_key"]').val(res.data.private_key || '');
|
$('[name="' + prefix + '_private_key"]').val(res.data.private_key || '');
|
||||||
@@ -456,31 +462,31 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
|
|||||||
$('[name="' + prefix + '_public_key"]').val(res.data.public_key || '');
|
$('[name="' + prefix + '_public_key"]').val(res.data.public_key || '');
|
||||||
$('[name="' + prefix + '_private_key"]').val(res.data.private_key || '');
|
$('[name="' + prefix + '_private_key"]').val(res.data.private_key || '');
|
||||||
}
|
}
|
||||||
layer.msg('已自动生成' + (side === 'submit' ? '提交' : '返回') + '密钥', {icon: 1, time: 1500});
|
layer.msg('已自动生成' + (side === 'submit' ? '提交' : '返回') + '密钥', { icon: 1, time: 1500 });
|
||||||
} else {
|
} else {
|
||||||
layer.msg(res.message || '自动生成密钥失败', {icon: 2});
|
layer.msg(res.message || '自动生成密钥失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function(){
|
error: function () {
|
||||||
layer.msg('自动生成密钥失败', {icon: 2});
|
layer.msg('自动生成密钥失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
form.on('select(submitAlgorithm)', function(data){
|
form.on('select(submitAlgorithm)', function (data) {
|
||||||
refreshSubmitKeysUI();
|
refreshSubmitKeysUI();
|
||||||
var algo = parseInt(data.value);
|
var algo = parseInt(data.value);
|
||||||
autoGenerateKeys('submit', algo);
|
autoGenerateKeys('submit', algo);
|
||||||
});
|
});
|
||||||
form.on('select(returnAlgorithm)', function(data){
|
form.on('select(returnAlgorithm)', function (data) {
|
||||||
refreshReturnKeysUI();
|
refreshReturnKeysUI();
|
||||||
var algo = parseInt(data.value);
|
var algo = parseInt(data.value);
|
||||||
autoGenerateKeys('return', algo);
|
autoGenerateKeys('return', algo);
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#btnGenSubmitKeys').on('click', function(){
|
$('#btnGenSubmitKeys').on('click', function () {
|
||||||
var algo = parseInt($('select[name="submit_algorithm"]').val());
|
var algo = parseInt($('select[name="submit_algorithm"]').val());
|
||||||
if (algo === 0) { layer.msg('请选择加密算法', {icon: 0}); return; }
|
if (algo === 0) { layer.msg('请选择加密算法', { icon: 0 }); return; }
|
||||||
|
|
||||||
// 先清空所有相关输入框
|
// 先清空所有相关输入框
|
||||||
$('[name="submit_public_key"]').val('');
|
$('[name="submit_public_key"]').val('');
|
||||||
@@ -491,7 +497,7 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
|
|||||||
type: 'POST',
|
type: 'POST',
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
data: JSON.stringify({ side: 'submit', algorithm: algo }),
|
data: JSON.stringify({ side: 'submit', algorithm: algo }),
|
||||||
success: function(res){
|
success: function (res) {
|
||||||
if (res.success && res.data) {
|
if (res.success && res.data) {
|
||||||
if (algo === 1) { // RC4
|
if (algo === 1) { // RC4
|
||||||
$('[name="submit_private_key"]').val(res.data.private_key || '');
|
$('[name="submit_private_key"]').val(res.data.private_key || '');
|
||||||
@@ -502,15 +508,15 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
|
|||||||
$('[name="submit_private_key"]').val(res.data.private_key || '');
|
$('[name="submit_private_key"]').val(res.data.private_key || '');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
layer.msg(res.message || '生成失败', {icon: 2});
|
layer.msg(res.message || '生成失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function(){ layer.msg('生成失败', {icon: 2}); }
|
error: function () { layer.msg('生成失败', { icon: 2 }); }
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
$('#btnGenReturnKeys').on('click', function(){
|
$('#btnGenReturnKeys').on('click', function () {
|
||||||
var algo = parseInt($('select[name="return_algorithm"]').val());
|
var algo = parseInt($('select[name="return_algorithm"]').val());
|
||||||
if (algo === 0) { layer.msg('请选择加密算法', {icon: 0}); return; }
|
if (algo === 0) { layer.msg('请选择加密算法', { icon: 0 }); return; }
|
||||||
|
|
||||||
// 先清空所有相关输入框
|
// 先清空所有相关输入框
|
||||||
$('[name="return_public_key"]').val('');
|
$('[name="return_public_key"]').val('');
|
||||||
@@ -521,7 +527,7 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
|
|||||||
type: 'POST',
|
type: 'POST',
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
data: JSON.stringify({ side: 'return', algorithm: algo }),
|
data: JSON.stringify({ side: 'return', algorithm: algo }),
|
||||||
success: function(res){
|
success: function (res) {
|
||||||
if (res.success && res.data) {
|
if (res.success && res.data) {
|
||||||
if (algo === 1) { // RC4
|
if (algo === 1) { // RC4
|
||||||
$('[name="return_private_key"]').val(res.data.private_key || '');
|
$('[name="return_private_key"]').val(res.data.private_key || '');
|
||||||
@@ -532,17 +538,17 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
|
|||||||
$('[name="return_private_key"]').val(res.data.private_key || '');
|
$('[name="return_private_key"]').val(res.data.private_key || '');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
layer.msg(res.message || '生成失败', {icon: 2});
|
layer.msg(res.message || '生成失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function(){ layer.msg('生成失败', {icon: 2}); }
|
error: function () { layer.msg('生成失败', { icon: 2 }); }
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 监听表格工具条
|
// 监听表格工具条
|
||||||
table.on('tool(apisTableFilter)', function(obj) {
|
table.on('tool(apisTableFilter)', function (obj) {
|
||||||
var data = obj.data;
|
var data = obj.data;
|
||||||
|
|
||||||
if (obj.event === 'edit') {
|
if (obj.event === 'edit') {
|
||||||
@@ -563,10 +569,10 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
|
|||||||
content: $('#apiFormModal'),
|
content: $('#apiFormModal'),
|
||||||
area: ['500px', '520px'],
|
area: ['500px', '520px'],
|
||||||
btn: ['保存', '取消'],
|
btn: ['保存', '取消'],
|
||||||
yes: function(index, layero) {
|
yes: function (index, layero) {
|
||||||
// 手动收集表单数据
|
// 手动收集表单数据
|
||||||
var formData = {};
|
var formData = {};
|
||||||
$('#apiForm').find('input, select, textarea').each(function() {
|
$('#apiForm').find('input, select, textarea').each(function () {
|
||||||
var $this = $(this);
|
var $this = $(this);
|
||||||
var name = $this.attr('name');
|
var name = $this.attr('name');
|
||||||
if (name) {
|
if (name) {
|
||||||
@@ -595,24 +601,24 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
|
|||||||
type: 'POST',
|
type: 'POST',
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
data: JSON.stringify(formData),
|
data: JSON.stringify(formData),
|
||||||
success: function(res) {
|
success: function (res) {
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
layer.msg('接口更新成功', {icon: 1});
|
layer.msg('接口更新成功', { icon: 1 });
|
||||||
layer.close(index);
|
layer.close(index);
|
||||||
apisTable.reload();
|
apisTable.reload();
|
||||||
} else {
|
} else {
|
||||||
layer.msg(res.message || '更新失败', {icon: 2});
|
layer.msg(res.message || '更新失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function() {
|
error: function () {
|
||||||
layer.msg('网络错误,请稍后重试', {icon: 2});
|
layer.msg('网络错误,请稍后重试', { icon: 2 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
btn2: function(index) {
|
btn2: function (index) {
|
||||||
layer.close(index);
|
layer.close(index);
|
||||||
},
|
},
|
||||||
success: function() {
|
success: function () {
|
||||||
form.render();
|
form.render();
|
||||||
},
|
},
|
||||||
shadeClose: false
|
shadeClose: false
|
||||||
@@ -620,7 +626,7 @@ layui.use(['table', 'form', 'layer', 'dropdown'], function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{{ end }}
|
{{ end }}
|
||||||
@@ -3,9 +3,12 @@
|
|||||||
<h2>应用管理</h2>
|
<h2>应用管理</h2>
|
||||||
<div class="layui-btn-container" style="margin:12px 0">
|
<div class="layui-btn-container" style="margin:12px 0">
|
||||||
<button class="layui-btn" id="btnAddApp"><i class="layui-icon layui-icon-add-1"></i> 新增应用</button>
|
<button class="layui-btn" id="btnAddApp"><i class="layui-icon layui-icon-add-1"></i> 新增应用</button>
|
||||||
<button class="layui-btn layui-btn-danger" id="btnBatchDeleteApps"><i class="layui-icon layui-icon-delete"></i> 批量删除</button>
|
<button class="layui-btn layui-btn-danger" id="btnBatchDeleteApps"><i class="layui-icon layui-icon-delete"></i>
|
||||||
<button class="layui-btn layui-btn-normal" id="btnBatchEnableApps"><i class="layui-icon layui-icon-ok-circle"></i> 批量启用</button>
|
批量删除</button>
|
||||||
<button class="layui-btn layui-btn-warm" id="btnBatchDisableApps"><i class="layui-icon layui-icon-close-fill"></i> 批量禁用</button>
|
<button class="layui-btn layui-btn-normal" id="btnBatchEnableApps"><i class="layui-icon layui-icon-ok-circle"></i>
|
||||||
|
批量启用</button>
|
||||||
|
<button class="layui-btn layui-btn-warm" id="btnBatchDisableApps"><i class="layui-icon layui-icon-close-fill"></i>
|
||||||
|
批量禁用</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="layui-card" style="margin-top:12px">
|
<div class="layui-card" style="margin-top:12px">
|
||||||
@@ -58,7 +61,8 @@
|
|||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
<label class="layui-form-label" style="cursor: pointer;" data-tips="app-name">应用名称</label>
|
<label class="layui-form-label" style="cursor: pointer;" data-tips="app-name">应用名称</label>
|
||||||
<div class="layui-input-block">
|
<div class="layui-input-block">
|
||||||
<input type="text" name="name" placeholder="请输入应用名称" autocomplete="off" class="layui-input" lay-verify="required" />
|
<input type="text" name="name" placeholder="请输入应用名称" autocomplete="off" class="layui-input"
|
||||||
|
lay-verify="required" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
@@ -119,7 +123,8 @@
|
|||||||
<div class="layui-inline">
|
<div class="layui-inline">
|
||||||
<label class="layui-form-label" style="cursor: pointer;" data-tips="clean-interval">清理间隔</label>
|
<label class="layui-form-label" style="cursor: pointer;" data-tips="clean-interval">清理间隔</label>
|
||||||
<div class="layui-input-inline">
|
<div class="layui-input-inline">
|
||||||
<input type="number" name="clean_interval" class="layui-input" placeholder="请输入" lay-verify="required|number" min="1">
|
<input type="number" name="clean_interval" class="layui-input" placeholder="请输入"
|
||||||
|
lay-verify="required|number" min="1">
|
||||||
</div>
|
</div>
|
||||||
<div class="layui-form-mid layui-text-em">小时</div>
|
<div class="layui-form-mid layui-text-em">小时</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -128,7 +133,8 @@
|
|||||||
<div class="layui-inline">
|
<div class="layui-inline">
|
||||||
<label class="layui-form-label" style="cursor: pointer;" data-tips="check-interval">校验间隔</label>
|
<label class="layui-form-label" style="cursor: pointer;" data-tips="check-interval">校验间隔</label>
|
||||||
<div class="layui-input-inline">
|
<div class="layui-input-inline">
|
||||||
<input type="number" name="check_interval" class="layui-input" placeholder="请输入" lay-verify="required|number" min="1">
|
<input type="number" name="check_interval" class="layui-input" placeholder="请输入"
|
||||||
|
lay-verify="required|number" min="1">
|
||||||
</div>
|
</div>
|
||||||
<div class="layui-form-mid layui-text-em">分钟</div>
|
<div class="layui-form-mid layui-text-em">分钟</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -136,7 +142,8 @@
|
|||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
<label class="layui-form-label" style="cursor: pointer;" data-tips="multi-open-count">多开数量</label>
|
<label class="layui-form-label" style="cursor: pointer;" data-tips="multi-open-count">多开数量</label>
|
||||||
<div class="layui-input-block">
|
<div class="layui-input-block">
|
||||||
<input type="number" name="multi_open_count" class="layui-input" placeholder="请输入允许的多开数量" lay-verify="required|number" min="1">
|
<input type="number" name="multi_open_count" class="layui-input" placeholder="请输入允许的多开数量"
|
||||||
|
lay-verify="required|number" min="1">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -173,19 +180,22 @@
|
|||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
<label class="layui-form-label" style="cursor: pointer;" data-tips="machine-free-count">免费次数</label>
|
<label class="layui-form-label" style="cursor: pointer;" data-tips="machine-free-count">免费次数</label>
|
||||||
<div class="layui-input-block">
|
<div class="layui-input-block">
|
||||||
<input type="number" name="machine_free_count" class="layui-input" placeholder="请输入" lay-verify="number" min="0">
|
<input type="number" name="machine_free_count" class="layui-input" placeholder="请输入" lay-verify="number"
|
||||||
|
min="0">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
<label class="layui-form-label" style="cursor: pointer;" data-tips="machine-rebind-count">重绑次数</label>
|
<label class="layui-form-label" style="cursor: pointer;" data-tips="machine-rebind-count">重绑次数</label>
|
||||||
<div class="layui-input-block">
|
<div class="layui-input-block">
|
||||||
<input type="number" name="machine_rebind_count" class="layui-input" placeholder="请输入" lay-verify="number" min="0">
|
<input type="number" name="machine_rebind_count" class="layui-input" placeholder="请输入" lay-verify="number"
|
||||||
|
min="0">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
<label class="layui-form-label" style="cursor: pointer;" data-tips="machine-rebind-deduct">重绑扣除</label>
|
<label class="layui-form-label" style="cursor: pointer;" data-tips="machine-rebind-deduct">重绑扣除</label>
|
||||||
<div class="layui-input-block">
|
<div class="layui-input-block">
|
||||||
<input type="number" name="machine_rebind_deduct" class="layui-input" placeholder="请输入重绑扣除时间(分钟)" lay-verify="number" min="0">
|
<input type="number" name="machine_rebind_deduct" class="layui-input" placeholder="请输入重绑扣除时间(分钟)"
|
||||||
|
lay-verify="number" min="0">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -231,7 +241,8 @@
|
|||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
<label class="layui-form-label" style="cursor: pointer;" data-tips="ip-rebind-deduct">重绑扣除</label>
|
<label class="layui-form-label" style="cursor: pointer;" data-tips="ip-rebind-deduct">重绑扣除</label>
|
||||||
<div class="layui-input-block">
|
<div class="layui-input-block">
|
||||||
<input type="number" name="ip_rebind_deduct" class="layui-input" placeholder="请输入重绑扣除时间(分钟)" lay-verify="number" min="0">
|
<input type="number" name="ip_rebind_deduct" class="layui-input" placeholder="请输入重绑扣除时间(分钟)"
|
||||||
|
lay-verify="number" min="0">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -268,7 +279,8 @@
|
|||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
<label class="layui-form-label" style="cursor: pointer;" data-tips="register-count">注册次数</label>
|
<label class="layui-form-label" style="cursor: pointer;" data-tips="register-count">注册次数</label>
|
||||||
<div class="layui-input-block">
|
<div class="layui-input-block">
|
||||||
<input type="number" name="register_count" class="layui-input" placeholder="请输入" lay-verify="required|number" min="1">
|
<input type="number" name="register_count" class="layui-input" placeholder="请输入" lay-verify="required|number"
|
||||||
|
min="1">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -293,7 +305,8 @@
|
|||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
<label class="layui-form-label" style="cursor: pointer;" data-tips="trial-time">试用时间</label>
|
<label class="layui-form-label" style="cursor: pointer;" data-tips="trial-time">试用时间</label>
|
||||||
<div class="layui-input-block">
|
<div class="layui-input-block">
|
||||||
<input type="number" name="trial_duration" class="layui-input" placeholder="请输入试用时间(分钟)" lay-verify="number" min="0">
|
<input type="number" name="trial_duration" class="layui-input" placeholder="请输入试用时间(分钟)" lay-verify="number"
|
||||||
|
min="0">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -309,8 +322,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
waitForLayui(function() {
|
waitForLayui(function () {
|
||||||
layui.use(['table', 'form', 'layer', 'element', 'dropdown', 'util'], function() {
|
layui.use(['table', 'form', 'layer', 'element', 'dropdown', 'util'], function () {
|
||||||
const table = layui.table;
|
const table = layui.table;
|
||||||
const form = layui.form;
|
const form = layui.form;
|
||||||
const layer = layui.layer;
|
const layer = layui.layer;
|
||||||
@@ -329,7 +342,7 @@
|
|||||||
elem: '#appsTable',
|
elem: '#appsTable',
|
||||||
id: 'appsTable',
|
id: 'appsTable',
|
||||||
url: '/admin/api/apps/list',
|
url: '/admin/api/apps/list',
|
||||||
parseData: function(res) {
|
parseData: function (res) {
|
||||||
// 后端返回的数据结构处理
|
// 后端返回的数据结构处理
|
||||||
return {
|
return {
|
||||||
code: res.code,
|
code: res.code,
|
||||||
@@ -347,7 +360,7 @@
|
|||||||
limit: 20,
|
limit: 20,
|
||||||
limits: [10, 20, 50, 100],
|
limits: [10, 20, 50, 100],
|
||||||
loading: true,
|
loading: true,
|
||||||
done: function(res, curr, count) {
|
done: function (res, curr, count) {
|
||||||
// 表格渲染完成后的回调
|
// 表格渲染完成后的回调
|
||||||
},
|
},
|
||||||
cols: [[
|
cols: [[
|
||||||
@@ -382,7 +395,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 搜索功能
|
// 搜索功能
|
||||||
$('#btnSearchApps').on('click', function() {
|
$('#btnSearchApps').on('click', function () {
|
||||||
const search = $('input[name="search"]').val();
|
const search = $('input[name="search"]').val();
|
||||||
appsTable.reload({
|
appsTable.reload({
|
||||||
where: {
|
where: {
|
||||||
@@ -395,7 +408,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 重置搜索
|
// 重置搜索
|
||||||
$('#btnResetApps').on('click', function() {
|
$('#btnResetApps').on('click', function () {
|
||||||
$('#appFilterForm')[0].reset();
|
$('#appFilterForm')[0].reset();
|
||||||
appsTable.reload({
|
appsTable.reload({
|
||||||
where: {},
|
where: {},
|
||||||
@@ -406,7 +419,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 新增应用
|
// 新增应用
|
||||||
$('#btnAddApp').on('click', function() {
|
$('#btnAddApp').on('click', function () {
|
||||||
$('#appForm')[0].reset();
|
$('#appForm')[0].reset();
|
||||||
$('input[name="id"]').val('');
|
$('input[name="id"]').val('');
|
||||||
|
|
||||||
@@ -416,10 +429,10 @@
|
|||||||
content: $('#appFormModal'),
|
content: $('#appFormModal'),
|
||||||
area: ['500px', '460px'],
|
area: ['500px', '460px'],
|
||||||
btn: ['创建', '取消'],
|
btn: ['创建', '取消'],
|
||||||
yes: function(index, layero) {
|
yes: function (index, layero) {
|
||||||
// 手动触发表单提交验证
|
// 手动触发表单提交验证
|
||||||
var formData = {};
|
var formData = {};
|
||||||
$('#appForm').find('input, select, textarea').each(function() {
|
$('#appForm').find('input, select, textarea').each(function () {
|
||||||
var $this = $(this);
|
var $this = $(this);
|
||||||
var name = $this.attr('name');
|
var name = $this.attr('name');
|
||||||
if (name) {
|
if (name) {
|
||||||
@@ -441,7 +454,7 @@
|
|||||||
|
|
||||||
// 验证必填字段
|
// 验证必填字段
|
||||||
if (!formData.name || formData.name.trim() === '') {
|
if (!formData.name || formData.name.trim() === '') {
|
||||||
layer.msg('请输入应用名称', {icon: 2});
|
layer.msg('请输入应用名称', { icon: 2 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,24 +466,24 @@
|
|||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: JSON.stringify(formData),
|
data: JSON.stringify(formData),
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
success: function(res) {
|
success: function (res) {
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
layer.msg(res.msg, {icon: 1});
|
layer.msg(res.msg, { icon: 1 });
|
||||||
layer.close(index);
|
layer.close(index);
|
||||||
appsTable.reload();
|
appsTable.reload();
|
||||||
} else {
|
} else {
|
||||||
layer.msg(res.msg || '操作失败', {icon: 2});
|
layer.msg(res.msg || '操作失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function(xhr) {
|
error: function (xhr) {
|
||||||
layer.msg(xhr.responseText || '操作失败', {icon: 2});
|
layer.msg(xhr.responseText || '操作失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
btn2: function(index) {
|
btn2: function (index) {
|
||||||
layer.close(index);
|
layer.close(index);
|
||||||
},
|
},
|
||||||
success: function() {
|
success: function () {
|
||||||
form.render();
|
form.render();
|
||||||
},
|
},
|
||||||
shadeClose: false
|
shadeClose: false
|
||||||
@@ -478,14 +491,14 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 监听更新方式切换(保留事件监听器以备将来扩展)
|
// 监听更新方式切换(保留事件监听器以备将来扩展)
|
||||||
form.on('radio(downloadTypeChange)', function(data) {
|
form.on('radio(downloadTypeChange)', function (data) {
|
||||||
// 下载地址字段现在始终显示,无需切换显示状态
|
// 下载地址字段现在始终显示,无需切换显示状态
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 表格工具栏事件
|
// 表格工具栏事件
|
||||||
table.on('tool(appsTableFilter)', function(obj) {
|
table.on('tool(appsTableFilter)', function (obj) {
|
||||||
const data = obj.data;
|
const data = obj.data;
|
||||||
|
|
||||||
if (obj.event === 'edit') {
|
if (obj.event === 'edit') {
|
||||||
@@ -508,10 +521,10 @@
|
|||||||
content: $('#appFormModal'),
|
content: $('#appFormModal'),
|
||||||
area: ['500px', '460px'],
|
area: ['500px', '460px'],
|
||||||
btn: ['保存', '取消'],
|
btn: ['保存', '取消'],
|
||||||
yes: function(index, layero) {
|
yes: function (index, layero) {
|
||||||
// 手动触发表单提交验证
|
// 手动触发表单提交验证
|
||||||
var formData = {};
|
var formData = {};
|
||||||
$('#appForm').find('input, select, textarea').each(function() {
|
$('#appForm').find('input, select, textarea').each(function () {
|
||||||
var $this = $(this);
|
var $this = $(this);
|
||||||
var name = $this.attr('name');
|
var name = $this.attr('name');
|
||||||
if (name) {
|
if (name) {
|
||||||
@@ -533,7 +546,7 @@
|
|||||||
|
|
||||||
// 验证必填字段
|
// 验证必填字段
|
||||||
if (!formData.name || formData.name.trim() === '') {
|
if (!formData.name || formData.name.trim() === '') {
|
||||||
layer.msg('请输入应用名称', {icon: 2});
|
layer.msg('请输入应用名称', { icon: 2 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -546,24 +559,24 @@
|
|||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: JSON.stringify(formData),
|
data: JSON.stringify(formData),
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
success: function(res) {
|
success: function (res) {
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
layer.msg(res.msg, {icon: 1});
|
layer.msg(res.msg, { icon: 1 });
|
||||||
layer.close(index);
|
layer.close(index);
|
||||||
appsTable.reload();
|
appsTable.reload();
|
||||||
} else {
|
} else {
|
||||||
layer.msg(res.msg || '操作失败', {icon: 2});
|
layer.msg(res.msg || '操作失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function(xhr) {
|
error: function (xhr) {
|
||||||
layer.msg(xhr.responseText || '操作失败', {icon: 2});
|
layer.msg(xhr.responseText || '操作失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
btn2: function(index) {
|
btn2: function (index) {
|
||||||
layer.close(index);
|
layer.close(index);
|
||||||
},
|
},
|
||||||
success: function() {
|
success: function () {
|
||||||
form.render();
|
form.render();
|
||||||
},
|
},
|
||||||
shadeClose: false
|
shadeClose: false
|
||||||
@@ -571,22 +584,22 @@
|
|||||||
|
|
||||||
} else if (obj.event === 'del') {
|
} else if (obj.event === 'del') {
|
||||||
// 删除
|
// 删除
|
||||||
layer.confirm('确定删除该应用吗?', {icon: 3, title: '提示'}, function(index) {
|
layer.confirm('确定删除该应用吗?', { icon: 3, title: '提示' }, function (index) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/admin/api/apps/delete',
|
url: '/admin/api/apps/delete',
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: JSON.stringify({id: data.id}),
|
data: JSON.stringify({ id: data.id }),
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
success: function(res) {
|
success: function (res) {
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
layer.msg(res.msg, {icon: 1});
|
layer.msg(res.msg, { icon: 1 });
|
||||||
appsTable.reload();
|
appsTable.reload();
|
||||||
} else {
|
} else {
|
||||||
layer.msg(res.msg || '删除失败', {icon: 2});
|
layer.msg(res.msg || '删除失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function(xhr) {
|
error: function (xhr) {
|
||||||
layer.msg(xhr.responseText || '删除失败', {icon: 2});
|
layer.msg(xhr.responseText || '删除失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
layer.close(index);
|
layer.close(index);
|
||||||
@@ -622,14 +635,14 @@
|
|||||||
id: 'reset_secret'
|
id: 'reset_secret'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
click: function(menudata, othis) {
|
click: function (menudata, othis) {
|
||||||
if (menudata.id === 'app_data') {
|
if (menudata.id === 'app_data') {
|
||||||
// 应用数据
|
// 应用数据
|
||||||
// 先获取当前应用数据内容
|
// 先获取当前应用数据内容
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/admin/api/apps/get_app_data?uuid=' + obj.data.uuid,
|
url: '/admin/api/apps/get_app_data?uuid=' + obj.data.uuid,
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
success: function(res) {
|
success: function (res) {
|
||||||
var currentAppData = '';
|
var currentAppData = '';
|
||||||
if (res.code === 0 && res.data && res.data.app_data) {
|
if (res.code === 0 && res.data && res.data.app_data) {
|
||||||
currentAppData = res.data.app_data;
|
currentAppData = res.data.app_data;
|
||||||
@@ -646,7 +659,7 @@
|
|||||||
'</textarea>' +
|
'</textarea>' +
|
||||||
'</div>',
|
'</div>',
|
||||||
btn: ['保存', '取消'],
|
btn: ['保存', '取消'],
|
||||||
yes: function(index, layero) {
|
yes: function (index, layero) {
|
||||||
var appDataContent = $('#appDataEditor').val();
|
var appDataContent = $('#appDataEditor').val();
|
||||||
|
|
||||||
// 发送更新请求
|
// 发送更新请求
|
||||||
@@ -658,7 +671,7 @@
|
|||||||
uuid: obj.data.uuid,
|
uuid: obj.data.uuid,
|
||||||
app_data: appDataContent
|
app_data: appDataContent
|
||||||
}),
|
}),
|
||||||
success: function(res) {
|
success: function (res) {
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
layer.msg('应用数据更新成功!', {
|
layer.msg('应用数据更新成功!', {
|
||||||
icon: 1,
|
icon: 1,
|
||||||
@@ -666,21 +679,21 @@
|
|||||||
});
|
});
|
||||||
layer.close(index);
|
layer.close(index);
|
||||||
} else {
|
} else {
|
||||||
layer.msg(res.msg || '更新应用数据失败', {icon: 2});
|
layer.msg(res.msg || '更新应用数据失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function() {
|
error: function () {
|
||||||
layer.msg('网络错误,请稍后重试', {icon: 2});
|
layer.msg('网络错误,请稍后重试', { icon: 2 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
btn2: function(index) {
|
btn2: function (index) {
|
||||||
layer.close(index);
|
layer.close(index);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
error: function() {
|
error: function () {
|
||||||
layer.msg('获取应用数据失败,请稍后重试', {icon: 2});
|
layer.msg('获取应用数据失败,请稍后重试', { icon: 2 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (menudata.id === 'announcement') {
|
} else if (menudata.id === 'announcement') {
|
||||||
@@ -689,7 +702,7 @@
|
|||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/admin/api/apps/get_announcement?uuid=' + obj.data.uuid,
|
url: '/admin/api/apps/get_announcement?uuid=' + obj.data.uuid,
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
success: function(res) {
|
success: function (res) {
|
||||||
var currentAnnouncement = '';
|
var currentAnnouncement = '';
|
||||||
if (res.code === 0 && res.data && res.data.announcement) {
|
if (res.code === 0 && res.data && res.data.announcement) {
|
||||||
currentAnnouncement = res.data.announcement;
|
currentAnnouncement = res.data.announcement;
|
||||||
@@ -706,7 +719,7 @@
|
|||||||
'</textarea>' +
|
'</textarea>' +
|
||||||
'</div>',
|
'</div>',
|
||||||
btn: ['保存', '取消'],
|
btn: ['保存', '取消'],
|
||||||
yes: function(index, layero) {
|
yes: function (index, layero) {
|
||||||
var announcementContent = $('#announcementEditor').val();
|
var announcementContent = $('#announcementEditor').val();
|
||||||
|
|
||||||
// 发送更新请求
|
// 发送更新请求
|
||||||
@@ -718,7 +731,7 @@
|
|||||||
uuid: obj.data.uuid,
|
uuid: obj.data.uuid,
|
||||||
announcement: announcementContent
|
announcement: announcementContent
|
||||||
}),
|
}),
|
||||||
success: function(res) {
|
success: function (res) {
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
layer.msg('程序公告更新成功!', {
|
layer.msg('程序公告更新成功!', {
|
||||||
icon: 1,
|
icon: 1,
|
||||||
@@ -726,21 +739,21 @@
|
|||||||
});
|
});
|
||||||
layer.close(index);
|
layer.close(index);
|
||||||
} else {
|
} else {
|
||||||
layer.msg(res.msg || '更新程序公告失败', {icon: 2});
|
layer.msg(res.msg || '更新程序公告失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function() {
|
error: function () {
|
||||||
layer.msg('网络错误,请稍后重试', {icon: 2});
|
layer.msg('网络错误,请稍后重试', { icon: 2 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
btn2: function(index) {
|
btn2: function (index) {
|
||||||
layer.close(index);
|
layer.close(index);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
error: function() {
|
error: function () {
|
||||||
layer.msg('获取程序公告失败,请稍后重试', {icon: 2});
|
layer.msg('获取程序公告失败,请稍后重试', { icon: 2 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (menudata.id === 'multi_instance') {
|
} else if (menudata.id === 'multi_instance') {
|
||||||
@@ -748,7 +761,7 @@
|
|||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/admin/api/apps/get_multi_config?uuid=' + obj.data.uuid,
|
url: '/admin/api/apps/get_multi_config?uuid=' + obj.data.uuid,
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
success: function(config) {
|
success: function (config) {
|
||||||
// 填充表单数据
|
// 填充表单数据
|
||||||
$('input[name="login_type"][value="' + config.login_type + '"]').prop('checked', true);
|
$('input[name="login_type"][value="' + config.login_type + '"]').prop('checked', true);
|
||||||
$('input[name="multi_open_scope"][value="' + config.multi_open_scope + '"]').prop('checked', true);
|
$('input[name="multi_open_scope"][value="' + config.multi_open_scope + '"]').prop('checked', true);
|
||||||
@@ -763,7 +776,7 @@
|
|||||||
area: ['550px', '450px'],
|
area: ['550px', '450px'],
|
||||||
content: $('#multiConfigModal'),
|
content: $('#multiConfigModal'),
|
||||||
btn: ['保存', '取消'],
|
btn: ['保存', '取消'],
|
||||||
yes: function(index, layero) {
|
yes: function (index, layero) {
|
||||||
var formData = {
|
var formData = {
|
||||||
uuid: obj.data.uuid,
|
uuid: obj.data.uuid,
|
||||||
login_type: parseInt($('input[name="login_type"]:checked').val()),
|
login_type: parseInt($('input[name="login_type"]:checked').val()),
|
||||||
@@ -775,23 +788,23 @@
|
|||||||
|
|
||||||
// 验证数据
|
// 验证数据
|
||||||
if (isNaN(formData.login_type) || formData.login_type < 0 || formData.login_type > 1) {
|
if (isNaN(formData.login_type) || formData.login_type < 0 || formData.login_type > 1) {
|
||||||
layer.msg('请选择登录方式', {icon: 2});
|
layer.msg('请选择登录方式', { icon: 2 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isNaN(formData.multi_open_scope) || formData.multi_open_scope < 0 || formData.multi_open_scope > 2) {
|
if (isNaN(formData.multi_open_scope) || formData.multi_open_scope < 0 || formData.multi_open_scope > 2) {
|
||||||
layer.msg('请选择多开范围', {icon: 2});
|
layer.msg('请选择多开范围', { icon: 2 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isNaN(formData.clean_interval) || formData.clean_interval < 1) {
|
if (isNaN(formData.clean_interval) || formData.clean_interval < 1) {
|
||||||
layer.msg('清理间隔必须大于0', {icon: 2});
|
layer.msg('清理间隔必须大于0', { icon: 2 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isNaN(formData.check_interval) || formData.check_interval < 1) {
|
if (isNaN(formData.check_interval) || formData.check_interval < 1) {
|
||||||
layer.msg('校验间隔必须大于0', {icon: 2});
|
layer.msg('校验间隔必须大于0', { icon: 2 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isNaN(formData.multi_open_count) || formData.multi_open_count < 1) {
|
if (isNaN(formData.multi_open_count) || formData.multi_open_count < 1) {
|
||||||
layer.msg('多开数量必须大于0', {icon: 2});
|
layer.msg('多开数量必须大于0', { icon: 2 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -801,36 +814,36 @@
|
|||||||
type: 'POST',
|
type: 'POST',
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
data: JSON.stringify(formData),
|
data: JSON.stringify(formData),
|
||||||
success: function(res) {
|
success: function (res) {
|
||||||
if (res.message) {
|
if (res.message) {
|
||||||
layer.msg('多开配置更新成功', {icon: 1});
|
layer.msg('多开配置更新成功', { icon: 1 });
|
||||||
layer.close(index);
|
layer.close(index);
|
||||||
table.reload('appsTable');
|
table.reload('appsTable');
|
||||||
} else {
|
} else {
|
||||||
layer.msg(res.msg || '更新多开配置失败', {icon: 2});
|
layer.msg(res.msg || '更新多开配置失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function() {
|
error: function () {
|
||||||
layer.msg('网络错误,请稍后重试', {icon: 2});
|
layer.msg('网络错误,请稍后重试', { icon: 2 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
btn2: function(index) {
|
btn2: function (index) {
|
||||||
layer.close(index);
|
layer.close(index);
|
||||||
},
|
},
|
||||||
success: function() {
|
success: function () {
|
||||||
// 重新渲染表单
|
// 重新渲染表单
|
||||||
form.render();
|
form.render();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
error: function() {
|
error: function () {
|
||||||
layer.msg('获取多开配置失败,请稍后重试', {icon: 2});
|
layer.msg('获取多开配置失败,请稍后重试', { icon: 2 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (menudata.id === 'reset_secret') {
|
} else if (menudata.id === 'reset_secret') {
|
||||||
// 重置密钥
|
// 重置密钥
|
||||||
layer.confirm('确定重置该应用的密钥吗?重置后原密钥将失效!', {icon: 3, title: '提示'}, function(index) {
|
layer.confirm('确定重置该应用的密钥吗?重置后原密钥将失效!', { icon: 3, title: '提示' }, function (index) {
|
||||||
// 发送重置密钥请求
|
// 发送重置密钥请求
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/admin/api/apps/reset_secret',
|
url: '/admin/api/apps/reset_secret',
|
||||||
@@ -839,7 +852,7 @@
|
|||||||
data: JSON.stringify({
|
data: JSON.stringify({
|
||||||
uuid: obj.data.uuid
|
uuid: obj.data.uuid
|
||||||
}),
|
}),
|
||||||
success: function(res) {
|
success: function (res) {
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
layer.msg('密钥重置成功!', {
|
layer.msg('密钥重置成功!', {
|
||||||
icon: 1,
|
icon: 1,
|
||||||
@@ -848,10 +861,10 @@
|
|||||||
// 刷新表格数据
|
// 刷新表格数据
|
||||||
table.reload('appsTable');
|
table.reload('appsTable');
|
||||||
} else {
|
} else {
|
||||||
layer.msg(res.msg || '重置密钥失败', {icon: 2});
|
layer.msg(res.msg || '重置密钥失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function(xhr) {
|
error: function (xhr) {
|
||||||
let errorMsg = '重置密钥失败';
|
let errorMsg = '重置密钥失败';
|
||||||
if (xhr.responseText) {
|
if (xhr.responseText) {
|
||||||
try {
|
try {
|
||||||
@@ -861,7 +874,7 @@
|
|||||||
errorMsg = xhr.responseText;
|
errorMsg = xhr.responseText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
layer.msg(errorMsg, {icon: 2});
|
layer.msg(errorMsg, { icon: 2 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
layer.close(index);
|
layer.close(index);
|
||||||
@@ -871,7 +884,7 @@
|
|||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/admin/api/apps/get_bind_config?uuid=' + obj.data.uuid,
|
url: '/admin/api/apps/get_bind_config?uuid=' + obj.data.uuid,
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
success: function(config) {
|
success: function (config) {
|
||||||
// 填充表单数据
|
// 填充表单数据
|
||||||
$('#bindConfigModal input[name="machine_verify"][value="' + config.machine_verify + '"]').prop('checked', true);
|
$('#bindConfigModal input[name="machine_verify"][value="' + config.machine_verify + '"]').prop('checked', true);
|
||||||
$('#bindConfigModal input[name="machine_rebind_enabled"][value="' + config.machine_rebind_enabled + '"]').prop('checked', true);
|
$('#bindConfigModal input[name="machine_rebind_enabled"][value="' + config.machine_rebind_enabled + '"]').prop('checked', true);
|
||||||
@@ -893,7 +906,7 @@
|
|||||||
area: ['650px', '600px'],
|
area: ['650px', '600px'],
|
||||||
content: $('#bindConfigModal'),
|
content: $('#bindConfigModal'),
|
||||||
btn: ['保存', '取消'],
|
btn: ['保存', '取消'],
|
||||||
yes: function(index, layero) {
|
yes: function (index, layero) {
|
||||||
var formData = {
|
var formData = {
|
||||||
uuid: obj.data.uuid,
|
uuid: obj.data.uuid,
|
||||||
machine_verify: parseInt($('#bindConfigModal input[name="machine_verify"]:checked').val()),
|
machine_verify: parseInt($('#bindConfigModal input[name="machine_verify"]:checked').val()),
|
||||||
@@ -912,27 +925,27 @@
|
|||||||
|
|
||||||
// 验证数据
|
// 验证数据
|
||||||
if (isNaN(formData.machine_verify) || formData.machine_verify < 0 || formData.machine_verify > 1) {
|
if (isNaN(formData.machine_verify) || formData.machine_verify < 0 || formData.machine_verify > 1) {
|
||||||
layer.msg('请选择机器验证选项', {icon: 2});
|
layer.msg('请选择机器验证选项', { icon: 2 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isNaN(formData.machine_rebind_enabled) || formData.machine_rebind_enabled < 0 || formData.machine_rebind_enabled > 1) {
|
if (isNaN(formData.machine_rebind_enabled) || formData.machine_rebind_enabled < 0 || formData.machine_rebind_enabled > 1) {
|
||||||
layer.msg('请选择机器重绑选项', {icon: 2});
|
layer.msg('请选择机器重绑选项', { icon: 2 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isNaN(formData.machine_rebind_limit) || formData.machine_rebind_limit < 0 || formData.machine_rebind_limit > 1) {
|
if (isNaN(formData.machine_rebind_limit) || formData.machine_rebind_limit < 0 || formData.machine_rebind_limit > 1) {
|
||||||
layer.msg('请选择机器重绑限制', {icon: 2});
|
layer.msg('请选择机器重绑限制', { icon: 2 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isNaN(formData.ip_verify) || formData.ip_verify < 0 || formData.ip_verify > 3) {
|
if (isNaN(formData.ip_verify) || formData.ip_verify < 0 || formData.ip_verify > 3) {
|
||||||
layer.msg('请选择IP地址验证选项', {icon: 2});
|
layer.msg('请选择IP地址验证选项', { icon: 2 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isNaN(formData.ip_rebind_enabled) || formData.ip_rebind_enabled < 0 || formData.ip_rebind_enabled > 1) {
|
if (isNaN(formData.ip_rebind_enabled) || formData.ip_rebind_enabled < 0 || formData.ip_rebind_enabled > 1) {
|
||||||
layer.msg('请选择IP地址重绑选项', {icon: 2});
|
layer.msg('请选择IP地址重绑选项', { icon: 2 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isNaN(formData.ip_rebind_limit) || formData.ip_rebind_limit < 0 || formData.ip_rebind_limit > 1) {
|
if (isNaN(formData.ip_rebind_limit) || formData.ip_rebind_limit < 0 || formData.ip_rebind_limit > 1) {
|
||||||
layer.msg('请选择IP地址重绑限制', {icon: 2});
|
layer.msg('请选择IP地址重绑限制', { icon: 2 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -942,31 +955,31 @@
|
|||||||
type: 'POST',
|
type: 'POST',
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
data: JSON.stringify(formData),
|
data: JSON.stringify(formData),
|
||||||
success: function(res) {
|
success: function (res) {
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
layer.msg('绑定设置更新成功', {icon: 1});
|
layer.msg('绑定设置更新成功', { icon: 1 });
|
||||||
layer.close(index);
|
layer.close(index);
|
||||||
table.reload('appsTable');
|
table.reload('appsTable');
|
||||||
} else {
|
} else {
|
||||||
layer.msg(res.msg || '更新绑定设置失败', {icon: 2});
|
layer.msg(res.msg || '更新绑定设置失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function() {
|
error: function () {
|
||||||
layer.msg('网络错误,请稍后重试', {icon: 2});
|
layer.msg('网络错误,请稍后重试', { icon: 2 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
btn2: function(index) {
|
btn2: function (index) {
|
||||||
layer.close(index);
|
layer.close(index);
|
||||||
},
|
},
|
||||||
success: function() {
|
success: function () {
|
||||||
// 重新渲染表单
|
// 重新渲染表单
|
||||||
form.render();
|
form.render();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
error: function() {
|
error: function () {
|
||||||
layer.msg('获取绑定设置失败,请稍后重试', {icon: 2});
|
layer.msg('获取绑定设置失败,请稍后重试', { icon: 2 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (menudata.id === 'register_settings') {
|
} else if (menudata.id === 'register_settings') {
|
||||||
@@ -974,7 +987,7 @@
|
|||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/admin/api/apps/get_register_config?uuid=' + obj.data.uuid,
|
url: '/admin/api/apps/get_register_config?uuid=' + obj.data.uuid,
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
success: function(config) {
|
success: function (config) {
|
||||||
// 填充表单数据
|
// 填充表单数据
|
||||||
$('#registerConfigModal input[name="register_enabled"][value="' + config.register_enabled + '"]').prop('checked', true);
|
$('#registerConfigModal input[name="register_enabled"][value="' + config.register_enabled + '"]').prop('checked', true);
|
||||||
$('#registerConfigModal input[name="register_limit_enabled"][value="' + config.register_limit_enabled + '"]').prop('checked', true);
|
$('#registerConfigModal input[name="register_limit_enabled"][value="' + config.register_limit_enabled + '"]').prop('checked', true);
|
||||||
@@ -991,7 +1004,7 @@
|
|||||||
area: ['550px', '500px'],
|
area: ['550px', '500px'],
|
||||||
content: $('#registerConfigModal'),
|
content: $('#registerConfigModal'),
|
||||||
btn: ['保存', '取消'],
|
btn: ['保存', '取消'],
|
||||||
yes: function(index, layero) {
|
yes: function (index, layero) {
|
||||||
var formData = {
|
var formData = {
|
||||||
uuid: obj.data.uuid,
|
uuid: obj.data.uuid,
|
||||||
register_enabled: parseInt($('#registerConfigModal input[name="register_enabled"]:checked').val()),
|
register_enabled: parseInt($('#registerConfigModal input[name="register_enabled"]:checked').val()),
|
||||||
@@ -1005,31 +1018,31 @@
|
|||||||
|
|
||||||
// 验证数据
|
// 验证数据
|
||||||
if (isNaN(formData.register_enabled) || formData.register_enabled < 0 || formData.register_enabled > 1) {
|
if (isNaN(formData.register_enabled) || formData.register_enabled < 0 || formData.register_enabled > 1) {
|
||||||
layer.msg('请选择账号注册选项', {icon: 2});
|
layer.msg('请选择账号注册选项', { icon: 2 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isNaN(formData.register_limit_enabled) || formData.register_limit_enabled < 0 || formData.register_limit_enabled > 1) {
|
if (isNaN(formData.register_limit_enabled) || formData.register_limit_enabled < 0 || formData.register_limit_enabled > 1) {
|
||||||
layer.msg('请选择注册限制选项', {icon: 2});
|
layer.msg('请选择注册限制选项', { icon: 2 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isNaN(formData.register_limit_time) || formData.register_limit_time < 0 || formData.register_limit_time > 1) {
|
if (isNaN(formData.register_limit_time) || formData.register_limit_time < 0 || formData.register_limit_time > 1) {
|
||||||
layer.msg('请选择限制时间选项', {icon: 2});
|
layer.msg('请选择限制时间选项', { icon: 2 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isNaN(formData.register_count) || formData.register_count < 1) {
|
if (isNaN(formData.register_count) || formData.register_count < 1) {
|
||||||
layer.msg('注册次数必须大于0', {icon: 2});
|
layer.msg('注册次数必须大于0', { icon: 2 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isNaN(formData.trial_enabled) || formData.trial_enabled < 0 || formData.trial_enabled > 1) {
|
if (isNaN(formData.trial_enabled) || formData.trial_enabled < 0 || formData.trial_enabled > 1) {
|
||||||
layer.msg('请选择领取试用选项', {icon: 2});
|
layer.msg('请选择领取试用选项', { icon: 2 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isNaN(formData.trial_limit_time) || formData.trial_limit_time < 0 || formData.trial_limit_time > 1) {
|
if (isNaN(formData.trial_limit_time) || formData.trial_limit_time < 0 || formData.trial_limit_time > 1) {
|
||||||
layer.msg('请选择试用限制时间选项', {icon: 2});
|
layer.msg('请选择试用限制时间选项', { icon: 2 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isNaN(formData.trial_duration) || formData.trial_duration < 0) {
|
if (isNaN(formData.trial_duration) || formData.trial_duration < 0) {
|
||||||
layer.msg('试用时间不能小于0', {icon: 2});
|
layer.msg('试用时间不能小于0', { icon: 2 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1039,31 +1052,31 @@
|
|||||||
type: 'POST',
|
type: 'POST',
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
data: JSON.stringify(formData),
|
data: JSON.stringify(formData),
|
||||||
success: function(res) {
|
success: function (res) {
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
layer.msg('注册设置更新成功', {icon: 1});
|
layer.msg('注册设置更新成功', { icon: 1 });
|
||||||
layer.close(index);
|
layer.close(index);
|
||||||
table.reload('appsTable');
|
table.reload('appsTable');
|
||||||
} else {
|
} else {
|
||||||
layer.msg(res.msg || '更新注册设置失败', {icon: 2});
|
layer.msg(res.msg || '更新注册设置失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function() {
|
error: function () {
|
||||||
layer.msg('网络错误,请稍后重试', {icon: 2});
|
layer.msg('网络错误,请稍后重试', { icon: 2 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
btn2: function(index) {
|
btn2: function (index) {
|
||||||
layer.close(index);
|
layer.close(index);
|
||||||
},
|
},
|
||||||
success: function() {
|
success: function () {
|
||||||
// 重新渲染表单
|
// 重新渲染表单
|
||||||
form.render();
|
form.render();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
error: function() {
|
error: function () {
|
||||||
layer.msg('获取注册设置失败,请稍后重试', {icon: 2});
|
layer.msg('获取注册设置失败,请稍后重试', { icon: 2 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1075,32 +1088,32 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 批量删除
|
// 批量删除
|
||||||
$('#btnBatchDeleteApps').on('click', function() {
|
$('#btnBatchDeleteApps').on('click', function () {
|
||||||
const checkStatus = table.checkStatus('appsTable');
|
const checkStatus = table.checkStatus('appsTable');
|
||||||
const data = checkStatus.data;
|
const data = checkStatus.data;
|
||||||
|
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
layer.msg('请选择要删除的应用', {icon: 2});
|
layer.msg('请选择要删除的应用', { icon: 2 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
layer.confirm('确定删除选中的 ' + data.length + ' 个应用吗?', {icon: 3, title: '提示'}, function(index) {
|
layer.confirm('确定删除选中的 ' + data.length + ' 个应用吗?', { icon: 3, title: '提示' }, function (index) {
|
||||||
const ids = data.map(item => item.id);
|
const ids = data.map(item => item.id);
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/admin/api/apps/batch_delete',
|
url: '/admin/api/apps/batch_delete',
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: JSON.stringify({ids: ids}),
|
data: JSON.stringify({ ids: ids }),
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
success: function(res) {
|
success: function (res) {
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
layer.msg(res.msg, {icon: 1});
|
layer.msg(res.msg, { icon: 1 });
|
||||||
appsTable.reload();
|
appsTable.reload();
|
||||||
} else {
|
} else {
|
||||||
layer.msg(res.msg || '批量删除失败', {icon: 2});
|
layer.msg(res.msg || '批量删除失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function(xhr) {
|
error: function (xhr) {
|
||||||
layer.msg(xhr.responseText || '批量删除失败', {icon: 2});
|
layer.msg(xhr.responseText || '批量删除失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
layer.close(index);
|
layer.close(index);
|
||||||
@@ -1108,12 +1121,12 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 批量启用
|
// 批量启用
|
||||||
$('#btnBatchEnableApps').on('click', function() {
|
$('#btnBatchEnableApps').on('click', function () {
|
||||||
const checkStatus = table.checkStatus('appsTable');
|
const checkStatus = table.checkStatus('appsTable');
|
||||||
const data = checkStatus.data;
|
const data = checkStatus.data;
|
||||||
|
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
layer.msg('请选择要启用的应用', {icon: 2});
|
layer.msg('请选择要启用的应用', { icon: 2 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1121,29 +1134,29 @@
|
|||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/admin/api/apps/batch_update_status',
|
url: '/admin/api/apps/batch_update_status',
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: JSON.stringify({ids: ids, status: 1}),
|
data: JSON.stringify({ ids: ids, status: 1 }),
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
success: function(res) {
|
success: function (res) {
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
layer.msg(res.msg, {icon: 1});
|
layer.msg(res.msg, { icon: 1 });
|
||||||
appsTable.reload();
|
appsTable.reload();
|
||||||
} else {
|
} else {
|
||||||
layer.msg(res.msg || '批量启用失败', {icon: 2});
|
layer.msg(res.msg || '批量启用失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function(xhr) {
|
error: function (xhr) {
|
||||||
layer.msg(xhr.responseText || '批量启用失败', {icon: 2});
|
layer.msg(xhr.responseText || '批量启用失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// 批量禁用
|
// 批量禁用
|
||||||
$('#btnBatchDisableApps').on('click', function() {
|
$('#btnBatchDisableApps').on('click', function () {
|
||||||
const checkStatus = table.checkStatus('appsTable');
|
const checkStatus = table.checkStatus('appsTable');
|
||||||
const data = checkStatus.data;
|
const data = checkStatus.data;
|
||||||
|
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
layer.msg('请选择要禁用的应用', {icon: 2});
|
layer.msg('请选择要禁用的应用', { icon: 2 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1151,18 +1164,18 @@
|
|||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/admin/api/apps/batch_update_status',
|
url: '/admin/api/apps/batch_update_status',
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: JSON.stringify({ids: ids, status: 0}),
|
data: JSON.stringify({ ids: ids, status: 0 }),
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
success: function(res) {
|
success: function (res) {
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
layer.msg(res.msg, {icon: 1});
|
layer.msg(res.msg, { icon: 1 });
|
||||||
appsTable.reload();
|
appsTable.reload();
|
||||||
} else {
|
} else {
|
||||||
layer.msg(res.msg || '批量禁用失败', {icon: 2});
|
layer.msg(res.msg || '批量禁用失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function(xhr) {
|
error: function (xhr) {
|
||||||
layer.msg(xhr.responseText || '批量禁用失败', {icon: 2});
|
layer.msg(xhr.responseText || '批量禁用失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -38,12 +38,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// 仪表盘统计脚本(采用箭头函数与中文注释)
|
// 仪表盘统计脚本(采用箭头函数与中文注释)
|
||||||
layui.use(['layer', 'util'], function(){
|
layui.use(['layer', 'util'], function () {
|
||||||
const layer = layui.layer;
|
const layer = layui.layer;
|
||||||
const util = layui.util;
|
const util = layui.util;
|
||||||
const $ = layui.$;
|
const $ = layui.$;
|
||||||
@@ -66,8 +63,6 @@ layui.use(['layer', 'util'], function(){
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 函数:刷新基本信息和运行状态
|
// 函数:刷新基本信息和运行状态
|
||||||
// 说明:请求后台获取最新的系统信息并更新页面显示
|
// 说明:请求后台获取最新的系统信息并更新页面显示
|
||||||
const refreshSystemInfo = () => {
|
const refreshSystemInfo = () => {
|
||||||
@@ -76,7 +71,7 @@ layui.use(['layer', 'util'], function(){
|
|||||||
const data = res.data;
|
const data = res.data;
|
||||||
// 更新运行时长
|
// 更新运行时长
|
||||||
if (data.uptime) {
|
if (data.uptime) {
|
||||||
$('.system-info-item').each(function() {
|
$('.system-info-item').each(function () {
|
||||||
const label = $(this).find('.system-info-label').text();
|
const label = $(this).find('.system-info-label').text();
|
||||||
if (label === '运行时长') {
|
if (label === '运行时长') {
|
||||||
$(this).find('.system-info-value').text(data.uptime);
|
$(this).find('.system-info-value').text(data.uptime);
|
||||||
@@ -91,6 +86,6 @@ layui.use(['layer', 'util'], function(){
|
|||||||
|
|
||||||
// 立即刷新一次系统信息
|
// 立即刷新一次系统信息
|
||||||
refreshSystemInfo();
|
refreshSystemInfo();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
|
||||||
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
<title>{{ .Title }} - {{ .SystemName }}</title>
|
<title>{{ .Title }} - {{ .SystemName }}</title>
|
||||||
@@ -9,9 +10,9 @@
|
|||||||
<link rel="shortcut icon" href="/favicon.ico" />
|
<link rel="shortcut icon" href="/favicon.ico" />
|
||||||
<link rel="stylesheet" href="/static/css/admin.css" />
|
<link rel="stylesheet" href="/static/css/admin.css" />
|
||||||
<script type="module" src="./static/lib/include.js"></script>
|
<script type="module" src="./static/lib/include.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="layui-layout layui-layout-admin" id="app">
|
<div class="layui-layout layui-layout-admin" id="app">
|
||||||
<div class="layui-header">
|
<div class="layui-header">
|
||||||
<!-- 头部区域(可配合layui 已有的水平导航) -->
|
<!-- 头部区域(可配合layui 已有的水平导航) -->
|
||||||
@@ -69,5 +70,6 @@
|
|||||||
<div class="layui-footer">{{ .FooterText }}</div>
|
<div class="layui-footer">{{ .FooterText }}</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="module" src="./static/js/admin.js"></script>
|
<script type="module" src="./static/js/admin.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
{{/* 管理员登录页面模板:使用layui构建的登录界面 */}}
|
{{/* 管理员登录页面模板:使用layui构建的登录界面 */}}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||||
@@ -21,6 +22,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.demo-login-container {
|
.demo-login-container {
|
||||||
width: 400px;
|
width: 400px;
|
||||||
margin: 21px auto 0;
|
margin: 21px auto 0;
|
||||||
@@ -29,22 +31,26 @@
|
|||||||
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-header {
|
.login-header {
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
padding: 30px 20px;
|
padding: 30px 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-header h1 {
|
.login-header h1 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-header p {
|
.login-header p {
|
||||||
margin: 8px 0 0;
|
margin: 8px 0 0;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-form {
|
.login-form {
|
||||||
padding: 30px 20px;
|
padding: 30px 20px;
|
||||||
}
|
}
|
||||||
@@ -58,6 +64,7 @@
|
|||||||
.login-form .layui-form-item:last-child {
|
.login-form .layui-form-item:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.demo-login-other .layui-icon {
|
.demo-login-other .layui-icon {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -65,6 +72,7 @@
|
|||||||
top: 2px;
|
top: 2px;
|
||||||
font-size: 26px;
|
font-size: 26px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-footer {
|
.login-footer {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
@@ -80,12 +88,15 @@
|
|||||||
margin: 10px auto;
|
margin: 10px auto;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-header {
|
.login-header {
|
||||||
padding: 25px 15px;
|
padding: 25px 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-header h1 {
|
.login-header h1 {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-form {
|
.login-form {
|
||||||
padding: 25px 15px;
|
padding: 25px 15px;
|
||||||
}
|
}
|
||||||
@@ -105,12 +116,15 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-header {
|
.login-header {
|
||||||
padding: 20px 15px;
|
padding: 20px 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-header h1 {
|
.login-header h1 {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-form {
|
.login-form {
|
||||||
padding: 20px 15px;
|
padding: 20px 15px;
|
||||||
}
|
}
|
||||||
@@ -126,6 +140,7 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<form class="layui-form">
|
<form class="layui-form">
|
||||||
<div class="demo-login-container">
|
<div class="demo-login-container">
|
||||||
@@ -135,12 +150,16 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="login-form">
|
<div class="login-form">
|
||||||
|
<!-- CSRF令牌隐藏字段 -->
|
||||||
|
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}" id="csrf-token">
|
||||||
|
|
||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
<div class="layui-input-wrap">
|
<div class="layui-input-wrap">
|
||||||
<div class="layui-input-prefix">
|
<div class="layui-input-prefix">
|
||||||
<i class="layui-icon layui-icon-username"></i>
|
<i class="layui-icon layui-icon-username"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" name="username" value="" lay-verify="required" placeholder="用户名" lay-reqtext="请填写用户名" autocomplete="off" class="layui-input" lay-affix="clear">
|
<input type="text" name="username" value="" lay-verify="required" placeholder="用户名"
|
||||||
|
lay-reqtext="请填写用户名" autocomplete="off" class="layui-input" lay-affix="clear">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -149,7 +168,8 @@
|
|||||||
<div class="layui-input-prefix">
|
<div class="layui-input-prefix">
|
||||||
<i class="layui-icon layui-icon-password"></i>
|
<i class="layui-icon layui-icon-password"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="password" name="password" value="" lay-verify="required" placeholder="密 码" lay-reqtext="请填写密码" autocomplete="off" class="layui-input" lay-affix="eye">
|
<input type="password" name="password" value="" lay-verify="required" placeholder="密 码"
|
||||||
|
lay-reqtext="请填写密码" autocomplete="off" class="layui-input" lay-affix="eye">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -160,12 +180,16 @@
|
|||||||
<div class="layui-input-prefix">
|
<div class="layui-input-prefix">
|
||||||
<i class="layui-icon layui-icon-vercode"></i>
|
<i class="layui-icon layui-icon-vercode"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" name="captcha" value="" lay-verify="required" placeholder="验证码" lay-reqtext="请填写验证码" autocomplete="off" class="layui-input" lay-affix="clear">
|
<input type="text" name="captcha" value="" lay-verify="required" placeholder="验证码"
|
||||||
|
lay-reqtext="请填写验证码" autocomplete="off" class="layui-input" lay-affix="clear">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layui-col-xs5">
|
<div class="layui-col-xs5">
|
||||||
<div style="margin-left: 5px; text-align: right;">
|
<div style="margin-left: 5px; text-align: right;">
|
||||||
<img id="captcha-img" src="/admin/captcha" onclick="this.src='/admin/captcha?t='+ new Date().getTime();" style="cursor: pointer; height: 38px; border-radius: 4px; width: 100%;" title="点击刷新验证码">
|
<img id="captcha-img" src="/admin/captcha"
|
||||||
|
onclick="this.src='/admin/captcha?t='+ new Date().getTime();"
|
||||||
|
style="cursor: pointer; height: 38px; border-radius: 4px; width: 100%;"
|
||||||
|
title="点击刷新验证码">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -185,21 +209,25 @@
|
|||||||
<!-- 请勿在项目正式环境中引用该 layui.js 地址 -->
|
<!-- 请勿在项目正式环境中引用该 layui.js 地址 -->
|
||||||
<script src="//unpkg.com/layui@2.12.1/dist/layui.js"></script>
|
<script src="//unpkg.com/layui@2.12.1/dist/layui.js"></script>
|
||||||
<script>
|
<script>
|
||||||
layui.use(function(){
|
layui.use(function () {
|
||||||
var form = layui.form;
|
var form = layui.form;
|
||||||
var layer = layui.layer;
|
var layer = layui.layer;
|
||||||
|
|
||||||
// 登录提交回调:向 /admin/login 发送请求,并依据 code===0 判断成功与否
|
// 登录提交回调:向 /admin/login 发送请求,并依据 code===0 判断成功与否
|
||||||
form.on('submit(demo-login)', function(data){
|
form.on('submit(demo-login)', function (data) {
|
||||||
var loadIndex = layer.load(1, {
|
var loadIndex = layer.load(1, {
|
||||||
shade: [0.1, '#fff']
|
shade: [0.1, '#fff']
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 获取CSRF令牌
|
||||||
|
var csrfToken = document.getElementById('csrf-token').value;
|
||||||
|
|
||||||
// 发送登录请求
|
// 发送登录请求
|
||||||
fetch('/admin/login', {
|
fetch('/admin/login', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': csrfToken
|
||||||
},
|
},
|
||||||
body: JSON.stringify(data.field)
|
body: JSON.stringify(data.field)
|
||||||
})
|
})
|
||||||
@@ -213,13 +241,13 @@
|
|||||||
layer.msg('登录成功', {
|
layer.msg('登录成功', {
|
||||||
icon: 1,
|
icon: 1,
|
||||||
time: 1500
|
time: 1500
|
||||||
}, function(){
|
}, function () {
|
||||||
const redirect = (result.data && result.data.redirect) || '/admin';
|
const redirect = (result.data && result.data.redirect) || '/admin';
|
||||||
window.location.href = redirect;
|
window.location.href = redirect;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const msg = (result && (result.msg || result.message)) || '登录失败,请检查用户名和密码';
|
const msg = (result && (result.msg || result.message)) || '登录失败,请检查用户名和密码';
|
||||||
layer.msg(msg, {icon: 2});
|
layer.msg(msg, { icon: 2 });
|
||||||
|
|
||||||
// 登录失败时刷新验证码
|
// 登录失败时刷新验证码
|
||||||
document.getElementById('captcha-img').src = '/admin/captcha?t=' + new Date().getTime();
|
document.getElementById('captcha-img').src = '/admin/captcha?t=' + new Date().getTime();
|
||||||
@@ -228,7 +256,7 @@
|
|||||||
.catch(error => {
|
.catch(error => {
|
||||||
layer.close(loadIndex);
|
layer.close(loadIndex);
|
||||||
console.error('登录错误:', error);
|
console.error('登录错误:', error);
|
||||||
layer.msg('网络错误,请稍后重试', {icon: 2});
|
layer.msg('网络错误,请稍后重试', { icon: 2 });
|
||||||
|
|
||||||
// 网络错误时也刷新验证码
|
// 网络错误时也刷新验证码
|
||||||
document.getElementById('captcha-img').src = '/admin/captcha?t=' + new Date().getTime();
|
document.getElementById('captcha-img').src = '/admin/captcha?t=' + new Date().getTime();
|
||||||
@@ -239,4 +267,5 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
{{ define "settings.html" }}
|
{{ define "settings.html" }}
|
||||||
<section>
|
<section>
|
||||||
<h2>系统设置</h2>
|
<h2>系统设置</h2>
|
||||||
|
|
||||||
<!-- 基本信息设置 -->
|
<!-- 基本信息设置 -->
|
||||||
<div class="layui-card" style="margin-top: 16px;">
|
<div class="layui-card" style="margin-top: 16px;">
|
||||||
<div class="layui-card-header">基本信息设置</div>
|
<div class="layui-card-header">基本信息设置</div>
|
||||||
@@ -61,7 +60,8 @@
|
|||||||
<label class="layui-form-label" style="cursor: pointer;" data-tips="session-timeout">会话超时</label>
|
<label class="layui-form-label" style="cursor: pointer;" data-tips="session-timeout">会话超时</label>
|
||||||
<div class="layui-input-block">
|
<div class="layui-input-block">
|
||||||
<div style="display: flex; align-items: center; gap: 10px;">
|
<div style="display: flex; align-items: center; gap: 10px;">
|
||||||
<input type="number" name="session_timeout" placeholder="3600" min="300" max="86400" class="layui-input" style="width: 120px;" />
|
<input type="number" name="session_timeout" placeholder="3600" min="300" max="86400" class="layui-input"
|
||||||
|
style="width: 120px;" />
|
||||||
<span class="layui-form-mid">秒(300-86400秒)</span>
|
<span class="layui-form-mid">秒(300-86400秒)</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -103,7 +103,8 @@
|
|||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
<label class="layui-form-label" style="cursor: pointer;" data-tips="psb-record-link">备案链接</label>
|
<label class="layui-form-label" style="cursor: pointer;" data-tips="psb-record-link">备案链接</label>
|
||||||
<div class="layui-input-block">
|
<div class="layui-input-block">
|
||||||
<input type="url" name="psb_record_link" placeholder="http://www.beian.gov.cn/portal/registerSystemInfo" class="layui-input" />
|
<input type="url" name="psb_record_link" placeholder="http://www.beian.gov.cn/portal/registerSystemInfo"
|
||||||
|
class="layui-input" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -129,8 +130,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
waitForLayui(function() {
|
waitForLayui(function () {
|
||||||
layui.use(['jquery', 'form', 'layer', 'util'], function() {
|
layui.use(['jquery', 'form', 'layer', 'util'], function () {
|
||||||
const { $, form, layer, util } = layui;
|
const { $, form, layer, util } = layui;
|
||||||
|
|
||||||
// 缓存上次加载的设置值,用于“重置”恢复
|
// 缓存上次加载的设置值,用于“重置”恢复
|
||||||
@@ -198,7 +199,7 @@
|
|||||||
const collectForm = (selector) => {
|
const collectForm = (selector) => {
|
||||||
const obj = {};
|
const obj = {};
|
||||||
const $form = $(selector);
|
const $form = $(selector);
|
||||||
$form.find('input, textarea, select').each(function() {
|
$form.find('input, textarea, select').each(function () {
|
||||||
const $el = $(this);
|
const $el = $(this);
|
||||||
const name = $el.attr('name');
|
const name = $el.attr('name');
|
||||||
if (!name) return; // 无 name 不纳入
|
if (!name) return; // 无 name 不纳入
|
||||||
|
|||||||
@@ -1,145 +1,24 @@
|
|||||||
{{ define "user.html" }}
|
{{ define "user.html" }}
|
||||||
<style>
|
<section>
|
||||||
/* 基础模块样式 */
|
<h2>个人资料</h2>
|
||||||
.user-module {
|
<div class="layui-tab layui-tab-brief" lay-filter="userTabs" style="margin-top: 16px;">
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-module .layui-card-header {
|
|
||||||
font-weight: 600;
|
|
||||||
transition: background-color 0.3s ease, color 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-tabs {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-tabs .layui-tab-title li {
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.readonly-field {
|
|
||||||
cursor: not-allowed !important;
|
|
||||||
transition: background-color 0.3s ease, color 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 浅色模式样式 */
|
|
||||||
:root {
|
|
||||||
--user-card-header-bg: #f8f9fa;
|
|
||||||
--user-card-header-color: #333;
|
|
||||||
--user-readonly-bg: #f5f5f5;
|
|
||||||
--user-readonly-color: #666;
|
|
||||||
--user-card-bg: #ffffff;
|
|
||||||
--user-card-border: #e6e6e6;
|
|
||||||
--user-input-bg: #ffffff;
|
|
||||||
--user-input-border: #d9d9d9;
|
|
||||||
--user-input-color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 深色模式样式 */
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
--user-card-header-bg: #2f2f2f;
|
|
||||||
--user-card-header-color: #e6e6e6;
|
|
||||||
--user-readonly-bg: #3a3a3a;
|
|
||||||
--user-readonly-color: #999;
|
|
||||||
--user-card-bg: #1f1f1f;
|
|
||||||
--user-card-border: #404040;
|
|
||||||
--user-input-bg: #2a2a2a;
|
|
||||||
--user-input-border: #404040;
|
|
||||||
--user-input-color: #e6e6e6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 手动深色模式类 */
|
|
||||||
.dark {
|
|
||||||
--user-card-header-bg: #2f2f2f;
|
|
||||||
--user-card-header-color: #e6e6e6;
|
|
||||||
--user-readonly-bg: #3a3a3a;
|
|
||||||
--user-readonly-color: #999;
|
|
||||||
--user-card-bg: #1f1f1f;
|
|
||||||
--user-card-border: #404040;
|
|
||||||
--user-input-bg: #2a2a2a;
|
|
||||||
--user-input-border: #404040;
|
|
||||||
--user-input-color: #e6e6e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 应用CSS变量到元素 */
|
|
||||||
.user-module .layui-card-header {
|
|
||||||
background-color: var(--user-card-header-bg) !important;
|
|
||||||
color: var(--user-card-header-color) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.readonly-field {
|
|
||||||
background-color: var(--user-readonly-bg) !important;
|
|
||||||
color: var(--user-readonly-color) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-module .layui-card {
|
|
||||||
background-color: var(--user-card-bg);
|
|
||||||
border-color: var(--user-card-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-module .layui-input {
|
|
||||||
background-color: var(--user-input-bg);
|
|
||||||
border-color: var(--user-input-border);
|
|
||||||
color: var(--user-input-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 确保表单元素在深色模式下的可读性 */
|
|
||||||
.user-module .layui-form-label {
|
|
||||||
color: var(--user-card-header-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 按钮在深色模式下的样式调整 */
|
|
||||||
.user-module .layui-btn-primary {
|
|
||||||
background-color: var(--user-input-bg);
|
|
||||||
border-color: var(--user-input-border);
|
|
||||||
color: var(--user-input-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-module .layui-btn-primary:hover {
|
|
||||||
background-color: var(--user-readonly-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 标签页在深色模式下的样式 */
|
|
||||||
.module-tabs .layui-tab-title {
|
|
||||||
border-bottom-color: var(--user-card-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-tabs .layui-tab-title li {
|
|
||||||
color: var(--user-input-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-tabs .layui-tab-title .layui-this {
|
|
||||||
color: var(--lay-color-primary, #1e9fff);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 图标颜色适配 */
|
|
||||||
.user-module .layui-icon {
|
|
||||||
color: var(--user-card-header-color);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="layui-tab layui-tab-brief module-tabs" lay-filter="userTabs">
|
|
||||||
<ul class="layui-tab-title">
|
<ul class="layui-tab-title">
|
||||||
<li class="layui-this">个人资料</li>
|
<li class="layui-this">个人资料</li>
|
||||||
<li>修改密码</li>
|
<li>修改密码</li>
|
||||||
<li>修改用户名</li>
|
<li>修改用户名</li>d
|
||||||
</ul>
|
</ul>
|
||||||
<div class="layui-tab-content">
|
<div class="layui-tab-content">
|
||||||
<!-- 个人资料模块 -->
|
<!-- 个人资料模块 -->
|
||||||
<div class="layui-tab-item layui-show">
|
<div class="layui-tab-item layui-show">
|
||||||
<div class="layui-card user-module">
|
<div class="layui-card" style="margin-top: 16px;">
|
||||||
<div class="layui-card-header">
|
<div class="layui-card-header">个人资料</div>
|
||||||
<i class="layui-icon layui-icon-user"></i> 个人资料
|
|
||||||
</div>
|
|
||||||
<div class="layui-card-body">
|
<div class="layui-card-body">
|
||||||
<form class="layui-form" id="profileForm" lay-filter="profileForm">
|
<form class="layui-form" id="profileForm" lay-filter="profileForm">
|
||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
<label class="layui-form-label">UUID</label>
|
<label class="layui-form-label">UUID</label>
|
||||||
<div class="layui-input-block">
|
<div class="layui-input-block">
|
||||||
<input type="text" name="uuid" disabled readonly class="layui-input readonly-field" style="font-family: monospace; font-size: 12px;" />
|
<input type="text" name="uuid" disabled readonly class="layui-input readonly-field"
|
||||||
|
style="font-family: monospace; font-size: 12px;" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
@@ -167,28 +46,29 @@
|
|||||||
|
|
||||||
<!-- 修改密码模块 -->
|
<!-- 修改密码模块 -->
|
||||||
<div class="layui-tab-item">
|
<div class="layui-tab-item">
|
||||||
<div class="layui-card user-module">
|
<div class="layui-card" style="margin-top: 16px;">
|
||||||
<div class="layui-card-header">
|
<div class="layui-card-header">修改密码</div>
|
||||||
<i class="layui-icon layui-icon-password"></i> 修改密码
|
|
||||||
</div>
|
|
||||||
<div class="layui-card-body">
|
<div class="layui-card-body">
|
||||||
<form class="layui-form" id="passwordForm" lay-filter="passwordForm" onsubmit="return false">
|
<form class="layui-form" id="passwordForm" lay-filter="passwordForm" onsubmit="return false">
|
||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
<label class="layui-form-label">当前密码</label>
|
<label class="layui-form-label">当前密码</label>
|
||||||
<div class="layui-input-block">
|
<div class="layui-input-block">
|
||||||
<input type="password" name="old_password" placeholder="请输入当前密码" autocomplete="off" class="layui-input" lay-verify="required" />
|
<input type="password" name="old_password" placeholder="请输入当前密码" autocomplete="off"
|
||||||
|
class="layui-input" lay-verify="required" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
<label class="layui-form-label">新密码</label>
|
<label class="layui-form-label">新密码</label>
|
||||||
<div class="layui-input-block">
|
<div class="layui-input-block">
|
||||||
<input type="password" name="new_password" placeholder="请输入新密码(至少6位)" autocomplete="off" class="layui-input" lay-verify="required" />
|
<input type="password" name="new_password" placeholder="请输入新密码(至少6位)" autocomplete="off"
|
||||||
|
class="layui-input" lay-verify="required" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
<label class="layui-form-label">确认密码</label>
|
<label class="layui-form-label">确认密码</label>
|
||||||
<div class="layui-input-block">
|
<div class="layui-input-block">
|
||||||
<input type="password" name="confirm_password" placeholder="请再次输入新密码" autocomplete="off" class="layui-input" lay-verify="required" />
|
<input type="password" name="confirm_password" placeholder="请再次输入新密码" autocomplete="off"
|
||||||
|
class="layui-input" lay-verify="required" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
@@ -208,10 +88,8 @@
|
|||||||
|
|
||||||
<!-- 修改用户名模块 -->
|
<!-- 修改用户名模块 -->
|
||||||
<div class="layui-tab-item">
|
<div class="layui-tab-item">
|
||||||
<div class="layui-card user-module">
|
<div class="layui-card" style="margin-top: 16px;">
|
||||||
<div class="layui-card-header">
|
<div class="layui-card-header">修改用户名</div>
|
||||||
<i class="layui-icon layui-icon-edit"></i> 修改用户名
|
|
||||||
</div>
|
|
||||||
<div class="layui-card-body">
|
<div class="layui-card-body">
|
||||||
<form class="layui-form" id="usernameForm" lay-filter="usernameForm" onsubmit="return false">
|
<form class="layui-form" id="usernameForm" lay-filter="usernameForm" onsubmit="return false">
|
||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
@@ -223,13 +101,15 @@
|
|||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
<label class="layui-form-label">新用户名</label>
|
<label class="layui-form-label">新用户名</label>
|
||||||
<div class="layui-input-block">
|
<div class="layui-input-block">
|
||||||
<input type="text" name="new_username" placeholder="请输入新用户名" autocomplete="off" class="layui-input" lay-verify="required" />
|
<input type="text" name="new_username" placeholder="请输入新用户名" autocomplete="off" class="layui-input"
|
||||||
|
lay-verify="required" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
<label class="layui-form-label">当前密码</label>
|
<label class="layui-form-label">当前密码</label>
|
||||||
<div class="layui-input-block">
|
<div class="layui-input-block">
|
||||||
<input type="password" name="password" placeholder="请输入当前密码以确认身份" autocomplete="off" class="layui-input" lay-verify="required" />
|
<input type="password" name="password" placeholder="请输入当前密码以确认身份" autocomplete="off"
|
||||||
|
class="layui-input" lay-verify="required" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
@@ -247,11 +127,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// 使用自执行函数创建局部作用域,避免与其他页面脚本发生全局命名冲突
|
// 使用自执行函数创建局部作用域,避免与其他页面脚本发生全局命名冲突
|
||||||
(() => {
|
(() => {
|
||||||
// 工具方法:将数值角色转为中文标签
|
// 工具方法:将数值角色转为中文标签
|
||||||
const roleToText = (role) => {
|
const roleToText = (role) => {
|
||||||
const r = typeof role === 'string' ? parseInt(role, 10) : role
|
const r = typeof role === 'string' ? parseInt(role, 10) : role
|
||||||
@@ -430,8 +310,12 @@
|
|||||||
// 重新加载个人资料
|
// 重新加载个人资料
|
||||||
await loadProfile()
|
await loadProfile()
|
||||||
|
|
||||||
// 清空表单
|
// 清空表单(不显示重置提示)
|
||||||
UsernameModule.reset()
|
form.val('usernameForm', {
|
||||||
|
new_username: '',
|
||||||
|
password: '',
|
||||||
|
current_username: userProfile?.username || ''
|
||||||
|
})
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
layer.msg(e.message || '修改用户名失败', { icon: 2 })
|
layer.msg(e.message || '修改用户名失败', { icon: 2 })
|
||||||
@@ -467,6 +351,7 @@
|
|||||||
loadProfile()
|
loadProfile()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})()
|
})()
|
||||||
</script>
|
</script>
|
||||||
|
</section>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
@@ -3,7 +3,8 @@
|
|||||||
<h2>变量管理</h2>
|
<h2>变量管理</h2>
|
||||||
<div class="layui-btn-container" style="margin:12px 0">
|
<div class="layui-btn-container" style="margin:12px 0">
|
||||||
<button class="layui-btn" id="btnAddVariable"><i class="layui-icon layui-icon-add-1"></i> 新增变量</button>
|
<button class="layui-btn" id="btnAddVariable"><i class="layui-icon layui-icon-add-1"></i> 新增变量</button>
|
||||||
<button class="layui-btn layui-btn-danger" id="btnBatchDeleteVariables"><i class="layui-icon layui-icon-delete"></i> 批量删除</button>
|
<button class="layui-btn layui-btn-danger" id="btnBatchDeleteVariables"><i class="layui-icon layui-icon-delete"></i>
|
||||||
|
批量删除</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="layui-card" style="margin-top:12px">
|
<div class="layui-card" style="margin-top:12px">
|
||||||
@@ -62,7 +63,8 @@
|
|||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
<label class="layui-form-label">变量别名</label>
|
<label class="layui-form-label">变量别名</label>
|
||||||
<div class="layui-input-block">
|
<div class="layui-input-block">
|
||||||
<input type="text" name="alias" lay-verify="required|alias" placeholder="请输入变量别名(英文开头,只能包含数字和英文字母)" autocomplete="off" class="layui-input" />
|
<input type="text" name="alias" lay-verify="required|alias" placeholder="请输入变量别名(英文开头,只能包含数字和英文字母)"
|
||||||
|
autocomplete="off" class="layui-input" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
@@ -90,8 +92,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
waitForLayui(function() {
|
waitForLayui(function () {
|
||||||
layui.use(['table', 'form', 'layer', 'element'], function() {
|
layui.use(['table', 'form', 'layer', 'element'], function () {
|
||||||
const table = layui.table;
|
const table = layui.table;
|
||||||
const form = layui.form;
|
const form = layui.form;
|
||||||
const layer = layui.layer;
|
const layer = layui.layer;
|
||||||
@@ -99,7 +101,7 @@
|
|||||||
|
|
||||||
// 自定义验证规则
|
// 自定义验证规则
|
||||||
form.verify({
|
form.verify({
|
||||||
alias: function(value) {
|
alias: function (value) {
|
||||||
if (!value) return '别名不能为空';
|
if (!value) return '别名不能为空';
|
||||||
// 检查是否以英文字母开头,且只包含数字和英文字母
|
// 检查是否以英文字母开头,且只包含数字和英文字母
|
||||||
if (!/^[a-zA-Z][a-zA-Z0-9]*$/.test(value)) {
|
if (!/^[a-zA-Z][a-zA-Z0-9]*$/.test(value)) {
|
||||||
@@ -119,18 +121,18 @@
|
|||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/admin/variable/apps',
|
url: '/admin/variable/apps',
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
success: function(res) {
|
success: function (res) {
|
||||||
if (res.code === 0 && res.data) {
|
if (res.code === 0 && res.data) {
|
||||||
let options = '<option value="">请选择应用</option>';
|
let options = '<option value="">请选择应用</option>';
|
||||||
res.data.forEach(function(app) {
|
res.data.forEach(function (app) {
|
||||||
options += '<option value="' + app.uuid + '">' + app.name + '</option>';
|
options += '<option value="' + app.uuid + '">' + app.name + '</option>';
|
||||||
});
|
});
|
||||||
$('select[name="app_uuid"]').html(options);
|
$('select[name="app_uuid"]').html(options);
|
||||||
form.render('select');
|
form.render('select');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function() {
|
error: function () {
|
||||||
layer.msg('加载应用列表失败', {icon: 2});
|
layer.msg('加载应用列表失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -143,7 +145,7 @@
|
|||||||
elem: '#variablesTable',
|
elem: '#variablesTable',
|
||||||
id: 'variablesTable',
|
id: 'variablesTable',
|
||||||
url: '/admin/variable/list',
|
url: '/admin/variable/list',
|
||||||
parseData: function(res) {
|
parseData: function (res) {
|
||||||
return {
|
return {
|
||||||
code: res.code,
|
code: res.code,
|
||||||
msg: res.msg || '',
|
msg: res.msg || '',
|
||||||
@@ -160,20 +162,20 @@
|
|||||||
limit: 20,
|
limit: 20,
|
||||||
limits: [10, 20, 50, 100],
|
limits: [10, 20, 50, 100],
|
||||||
loading: true,
|
loading: true,
|
||||||
done: function(res, curr, count) {
|
done: function (res, curr, count) {
|
||||||
// 表格渲染完成后的回调
|
// 表格渲染完成后的回调
|
||||||
},
|
},
|
||||||
cols: [[
|
cols: [[
|
||||||
{type: 'checkbox', width: 50},
|
{ type: 'checkbox', width: 50 },
|
||||||
{field: 'id', title: 'ID', width: 80, sort: true},
|
{ field: 'id', title: 'ID', width: 80, sort: true },
|
||||||
{field: 'app_name', title: '应用名称', minWidth: 120},
|
{ field: 'app_name', title: '应用名称', minWidth: 120 },
|
||||||
{field: 'number', title: '变量编号', width: 180},
|
{ field: 'number', title: '变量编号', width: 180 },
|
||||||
{field: 'alias', title: '变量别名', minWidth: 150},
|
{ field: 'alias', title: '变量别名', minWidth: 150 },
|
||||||
{
|
{
|
||||||
field: 'data',
|
field: 'data',
|
||||||
title: '变量数据',
|
title: '变量数据',
|
||||||
minWidth: 200,
|
minWidth: 200,
|
||||||
templet: function(d) {
|
templet: function (d) {
|
||||||
// 限制显示长度,避免内容过长影响布局
|
// 限制显示长度,避免内容过长影响布局
|
||||||
if (d.data && d.data.length > 50) {
|
if (d.data && d.data.length > 50) {
|
||||||
return '<span title="' + d.data + '">' + d.data.substring(0, 50) + '...</span>';
|
return '<span title="' + d.data + '">' + d.data.substring(0, 50) + '...</span>';
|
||||||
@@ -185,7 +187,7 @@
|
|||||||
field: 'remark',
|
field: 'remark',
|
||||||
title: '备注',
|
title: '备注',
|
||||||
minWidth: 150,
|
minWidth: 150,
|
||||||
templet: function(d) {
|
templet: function (d) {
|
||||||
// 限制显示长度,避免内容过长影响布局
|
// 限制显示长度,避免内容过长影响布局
|
||||||
if (d.remark && d.remark.length > 30) {
|
if (d.remark && d.remark.length > 30) {
|
||||||
return '<span title="' + d.remark + '">' + d.remark.substring(0, 30) + '...</span>';
|
return '<span title="' + d.remark + '">' + d.remark.substring(0, 30) + '...</span>';
|
||||||
@@ -197,16 +199,16 @@
|
|||||||
field: 'created_at',
|
field: 'created_at',
|
||||||
title: '创建时间',
|
title: '创建时间',
|
||||||
width: 180,
|
width: 180,
|
||||||
templet: function(d) {
|
templet: function (d) {
|
||||||
return formatDateTime(d.created_at);
|
return formatDateTime(d.created_at);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{title: '操作', width: 180, align: 'center', toolbar: '#tpl-variables-ops', fixed: 'right'}
|
{ title: '操作', width: 180, align: 'center', toolbar: '#tpl-variables-ops', fixed: 'right' }
|
||||||
]]
|
]]
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听应用选择变化
|
// 监听应用选择变化
|
||||||
form.on('select(appSelect)', function(data) {
|
form.on('select(appSelect)', function (data) {
|
||||||
variablesTable.reload({
|
variablesTable.reload({
|
||||||
where: {
|
where: {
|
||||||
app_uuid: data.value,
|
app_uuid: data.value,
|
||||||
@@ -219,7 +221,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 搜索功能
|
// 搜索功能
|
||||||
$('#btnSearchVariables').on('click', function() {
|
$('#btnSearchVariables').on('click', function () {
|
||||||
variablesTable.reload({
|
variablesTable.reload({
|
||||||
where: {
|
where: {
|
||||||
app_uuid: $('select[name="app_uuid"]').val(),
|
app_uuid: $('select[name="app_uuid"]').val(),
|
||||||
@@ -232,7 +234,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 重置搜索
|
// 重置搜索
|
||||||
$('#btnResetVariables').on('click', function() {
|
$('#btnResetVariables').on('click', function () {
|
||||||
$('#variableFilterForm')[0].reset();
|
$('#variableFilterForm')[0].reset();
|
||||||
form.render();
|
form.render();
|
||||||
variablesTable.reload({
|
variablesTable.reload({
|
||||||
@@ -244,7 +246,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 新增变量
|
// 新增变量
|
||||||
$('#btnAddVariable').on('click', function() {
|
$('#btnAddVariable').on('click', function () {
|
||||||
console.log('新增变量按钮被点击');
|
console.log('新增变量按钮被点击');
|
||||||
$('#variableForm')[0].reset();
|
$('#variableForm')[0].reset();
|
||||||
$('input[name="id"]').val('');
|
$('input[name="id"]').val('');
|
||||||
@@ -258,10 +260,10 @@
|
|||||||
content: $('#variableFormLayer'),
|
content: $('#variableFormLayer'),
|
||||||
area: ['500px', '460px'],
|
area: ['500px', '460px'],
|
||||||
btn: ['创建', '取消'],
|
btn: ['创建', '取消'],
|
||||||
yes: function(index, layero) {
|
yes: function (index, layero) {
|
||||||
// 手动收集表单数据
|
// 手动收集表单数据
|
||||||
var formData = {};
|
var formData = {};
|
||||||
$('#variableForm').find('input, select, textarea').each(function() {
|
$('#variableForm').find('input, select, textarea').each(function () {
|
||||||
var $this = $(this);
|
var $this = $(this);
|
||||||
var name = $this.attr('name');
|
var name = $this.attr('name');
|
||||||
if (name && name !== 'id') {
|
if (name && name !== 'id') {
|
||||||
@@ -273,15 +275,15 @@
|
|||||||
|
|
||||||
// 验证必填字段
|
// 验证必填字段
|
||||||
if (!formData.app_uuid || formData.app_uuid.trim() === '') {
|
if (!formData.app_uuid || formData.app_uuid.trim() === '') {
|
||||||
layer.msg('应用UUID不能为空', {icon: 2});
|
layer.msg('应用UUID不能为空', { icon: 2 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!formData.alias || formData.alias.trim() === '') {
|
if (!formData.alias || formData.alias.trim() === '') {
|
||||||
layer.msg('请输入变量别名', {icon: 2});
|
layer.msg('请输入变量别名', { icon: 2 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!formData.data || formData.data.trim() === '') {
|
if (!formData.data || formData.data.trim() === '') {
|
||||||
layer.msg('请输入变量数据', {icon: 2});
|
layer.msg('请输入变量数据', { icon: 2 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,24 +294,24 @@
|
|||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: JSON.stringify(formData),
|
data: JSON.stringify(formData),
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
success: function(res) {
|
success: function (res) {
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
layer.msg(res.msg, {icon: 1});
|
layer.msg(res.msg, { icon: 1 });
|
||||||
layer.close(index);
|
layer.close(index);
|
||||||
variablesTable.reload();
|
variablesTable.reload();
|
||||||
} else {
|
} else {
|
||||||
layer.msg(res.msg || '操作失败', {icon: 2});
|
layer.msg(res.msg || '操作失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function(xhr) {
|
error: function (xhr) {
|
||||||
layer.msg(xhr.responseText || '操作失败', {icon: 2});
|
layer.msg(xhr.responseText || '操作失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
btn2: function(index) {
|
btn2: function (index) {
|
||||||
layer.close(index);
|
layer.close(index);
|
||||||
},
|
},
|
||||||
success: function() {
|
success: function () {
|
||||||
form.render();
|
form.render();
|
||||||
},
|
},
|
||||||
shadeClose: false
|
shadeClose: false
|
||||||
@@ -317,32 +319,32 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 批量删除
|
// 批量删除
|
||||||
$('#btnBatchDeleteVariables').on('click', function() {
|
$('#btnBatchDeleteVariables').on('click', function () {
|
||||||
const checkStatus = table.checkStatus('variablesTable');
|
const checkStatus = table.checkStatus('variablesTable');
|
||||||
const data = checkStatus.data;
|
const data = checkStatus.data;
|
||||||
|
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
layer.msg('请选择要删除的变量', {icon: 2});
|
layer.msg('请选择要删除的变量', { icon: 2 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
layer.confirm('确定删除选中的 ' + data.length + ' 个变量吗?', {icon: 3, title: '提示'}, function(index) {
|
layer.confirm('确定删除选中的 ' + data.length + ' 个变量吗?', { icon: 3, title: '提示' }, function (index) {
|
||||||
const ids = data.map(item => item.id);
|
const ids = data.map(item => item.id);
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/admin/variable/batch_delete',
|
url: '/admin/variable/batch_delete',
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: JSON.stringify({ids: ids}),
|
data: JSON.stringify({ ids: ids }),
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
success: function(res) {
|
success: function (res) {
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
layer.msg(res.msg, {icon: 1});
|
layer.msg(res.msg, { icon: 1 });
|
||||||
variablesTable.reload();
|
variablesTable.reload();
|
||||||
} else {
|
} else {
|
||||||
layer.msg(res.msg || '批量删除失败', {icon: 2});
|
layer.msg(res.msg || '批量删除失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function(xhr) {
|
error: function (xhr) {
|
||||||
layer.msg(xhr.responseText || '批量删除失败', {icon: 2});
|
layer.msg(xhr.responseText || '批量删除失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
layer.close(index);
|
layer.close(index);
|
||||||
@@ -350,7 +352,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 表格工具栏事件
|
// 表格工具栏事件
|
||||||
table.on('tool(variablesTableFilter)', function(obj) {
|
table.on('tool(variablesTableFilter)', function (obj) {
|
||||||
const data = obj.data;
|
const data = obj.data;
|
||||||
|
|
||||||
if (obj.event === 'edit') {
|
if (obj.event === 'edit') {
|
||||||
@@ -361,7 +363,7 @@
|
|||||||
|
|
||||||
// 重新加载应用列表,然后设置选中值
|
// 重新加载应用列表,然后设置选中值
|
||||||
loadApps();
|
loadApps();
|
||||||
setTimeout(function() {
|
setTimeout(function () {
|
||||||
$('select[name="app_uuid"]').val(data.app_uuid);
|
$('select[name="app_uuid"]').val(data.app_uuid);
|
||||||
form.render('select');
|
form.render('select');
|
||||||
}, 100);
|
}, 100);
|
||||||
@@ -376,10 +378,10 @@
|
|||||||
content: $('#variableFormLayer'),
|
content: $('#variableFormLayer'),
|
||||||
area: ['500px', '460px'],
|
area: ['500px', '460px'],
|
||||||
btn: ['保存', '取消'],
|
btn: ['保存', '取消'],
|
||||||
yes: function(index, layero) {
|
yes: function (index, layero) {
|
||||||
// 手动收集表单数据
|
// 手动收集表单数据
|
||||||
var formData = {};
|
var formData = {};
|
||||||
$('#variableForm').find('input, select, textarea').each(function() {
|
$('#variableForm').find('input, select, textarea').each(function () {
|
||||||
var $this = $(this);
|
var $this = $(this);
|
||||||
var name = $this.attr('name');
|
var name = $this.attr('name');
|
||||||
if (name && name !== 'id') {
|
if (name && name !== 'id') {
|
||||||
@@ -391,15 +393,15 @@
|
|||||||
|
|
||||||
// 验证必填字段
|
// 验证必填字段
|
||||||
if (!formData.app_uuid || formData.app_uuid.trim() === '') {
|
if (!formData.app_uuid || formData.app_uuid.trim() === '') {
|
||||||
layer.msg('应用UUID不能为空', {icon: 2});
|
layer.msg('应用UUID不能为空', { icon: 2 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!formData.alias || formData.alias.trim() === '') {
|
if (!formData.alias || formData.alias.trim() === '') {
|
||||||
layer.msg('请输入变量别名', {icon: 2});
|
layer.msg('请输入变量别名', { icon: 2 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!formData.data || formData.data.trim() === '') {
|
if (!formData.data || formData.data.trim() === '') {
|
||||||
layer.msg('请输入变量数据', {icon: 2});
|
layer.msg('请输入变量数据', { icon: 2 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -410,24 +412,24 @@
|
|||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: JSON.stringify(formData),
|
data: JSON.stringify(formData),
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
success: function(res) {
|
success: function (res) {
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
layer.msg(res.msg, {icon: 1});
|
layer.msg(res.msg, { icon: 1 });
|
||||||
layer.close(index);
|
layer.close(index);
|
||||||
variablesTable.reload();
|
variablesTable.reload();
|
||||||
} else {
|
} else {
|
||||||
layer.msg(res.msg || '操作失败', {icon: 2});
|
layer.msg(res.msg || '操作失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function(xhr) {
|
error: function (xhr) {
|
||||||
layer.msg(xhr.responseText || '操作失败', {icon: 2});
|
layer.msg(xhr.responseText || '操作失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
btn2: function(index) {
|
btn2: function (index) {
|
||||||
layer.close(index);
|
layer.close(index);
|
||||||
},
|
},
|
||||||
success: function() {
|
success: function () {
|
||||||
form.render();
|
form.render();
|
||||||
},
|
},
|
||||||
shadeClose: false
|
shadeClose: false
|
||||||
@@ -435,22 +437,22 @@
|
|||||||
|
|
||||||
} else if (obj.event === 'del') {
|
} else if (obj.event === 'del') {
|
||||||
// 删除
|
// 删除
|
||||||
layer.confirm('确定删除该变量吗?', {icon: 3, title: '提示'}, function(index) {
|
layer.confirm('确定删除该变量吗?', { icon: 3, title: '提示' }, function (index) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/admin/variable/delete',
|
url: '/admin/variable/delete',
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: JSON.stringify({id: data.id}),
|
data: JSON.stringify({ id: data.id }),
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
success: function(res) {
|
success: function (res) {
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
layer.msg(res.msg, {icon: 1});
|
layer.msg(res.msg, { icon: 1 });
|
||||||
variablesTable.reload();
|
variablesTable.reload();
|
||||||
} else {
|
} else {
|
||||||
layer.msg(res.msg || '删除失败', {icon: 2});
|
layer.msg(res.msg || '删除失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function(xhr) {
|
error: function (xhr) {
|
||||||
layer.msg(xhr.responseText || '删除失败', {icon: 2});
|
layer.msg(xhr.responseText || '删除失败', { icon: 2 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
layer.close(index);
|
layer.close(index);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="zh-cn">
|
<html lang="zh-cn">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>{{.SystemName}} - 生活就像愤怒的小鸟,失败后总有几只猪在笑。</title>
|
<title>{{.SystemName}} - 生活就像愤怒的小鸟,失败后总有几只猪在笑。</title>
|
||||||
<!-- 站 点 协 议 -->
|
<!-- 站 点 协 议 -->
|
||||||
@@ -17,9 +18,10 @@
|
|||||||
<link rel="shortcut icon" href="/favicon.ico" />
|
<link rel="shortcut icon" href="/favicon.ico" />
|
||||||
<link rel="bookmark" href="/favicon.ico" />
|
<link rel="bookmark" href="/favicon.ico" />
|
||||||
<!-- 样 式 文 件 -->
|
<!-- 样 式 文 件 -->
|
||||||
<link rel="stylesheet" href="//lib.baomitu.com/layui/2.8.17/css/layui.css"/>
|
<link rel="stylesheet" href="//lib.baomitu.com/layui/2.8.17/css/layui.css" />
|
||||||
<style>
|
<style>
|
||||||
html, body {
|
html,
|
||||||
|
body {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -69,6 +71,7 @@
|
|||||||
from {
|
from {
|
||||||
text-shadow: 0 0 20px rgba(0, 212, 255, 0.5);
|
text-shadow: 0 0 20px rgba(0, 212, 255, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
to {
|
to {
|
||||||
text-shadow: 0 0 30px rgba(0, 212, 255, 0.8), 0 0 40px rgba(0, 212, 255, 0.6);
|
text-shadow: 0 0 30px rgba(0, 212, 255, 0.8), 0 0 40px rgba(0, 212, 255, 0.6);
|
||||||
}
|
}
|
||||||
@@ -100,8 +103,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes shimmer {
|
@keyframes shimmer {
|
||||||
0% { left: -100%; }
|
0% {
|
||||||
100% { left: 100%; }
|
left: -100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
left: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.box-form .layui-form-item {
|
.box-form .layui-form-item {
|
||||||
@@ -119,8 +127,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes pulse {
|
@keyframes pulse {
|
||||||
0%, 100% { opacity: 1; }
|
|
||||||
50% { opacity: 0.7; }
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-text {
|
.info-text {
|
||||||
@@ -174,9 +189,10 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<!-- 代 码 结 构 -->
|
<!-- 代 码 结 构 -->
|
||||||
<div class="layui-container">
|
<div class="layui-container">
|
||||||
<canvas id="canvas"></canvas>
|
<canvas id="canvas"></canvas>
|
||||||
<div class="body-background body_box">
|
<div class="body-background body_box">
|
||||||
<div class="layui-form box-form body_box">
|
<div class="layui-form box-form body_box">
|
||||||
@@ -192,13 +208,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="body_footer">{{.FooterText}}</div>
|
<div class="body_footer">{{.FooterText}}</div>
|
||||||
{{if or .ICPRecord .PSBRecord}}<div class="body_beian">{{if .ICPRecord}}<a href="{{.ICPRecordLink}}" target="_blank">{{.ICPRecord}}</a>{{end}}{{if and .ICPRecord .PSBRecord}} {{end}}{{if .PSBRecord}}<a href="{{.PSBRecordLink}}" target="_blank">{{.PSBRecord}}</a>{{end}}</div>{{end}}
|
{{if or .ICPRecord .PSBRecord}}<div class="body_beian">{{if .ICPRecord}}<a href="{{.ICPRecordLink}}"
|
||||||
|
target="_blank">{{.ICPRecord}}</a>{{end}}{{if and .ICPRecord .PSBRecord}} {{end}}{{if .PSBRecord}}<a
|
||||||
|
href="{{.PSBRecordLink}}" target="_blank">{{.PSBRecord}}</a>{{end}}</div>{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 资 源 引 入 -->
|
<!-- 资 源 引 入 -->
|
||||||
<script src="//lib.baomitu.com/jquery/3.6.4/jquery.min.js" type="text/javascript"></script>
|
<script src="//lib.baomitu.com/jquery/3.6.4/jquery.min.js" type="text/javascript"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// 获取canvas元素和绘图上下文
|
// 获取canvas元素和绘图上下文
|
||||||
const canvas = document.getElementById('canvas');
|
const canvas = document.getElementById('canvas');
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
@@ -361,6 +379,7 @@
|
|||||||
initParticles();
|
initParticles();
|
||||||
addMouseInteraction();
|
addMouseInteraction();
|
||||||
animate();
|
animate();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user