[2024-08-06](UPDATE): 创建用户

This commit is contained in:
june 2024-08-06 20:37:34 +08:00
parent 57fdfbf8a8
commit 84a5c80057
40 changed files with 431 additions and 158 deletions

65
corelib/page.go Normal file
View File

@ -0,0 +1,65 @@
package corelib
type PaginationData struct {
LeftPages []int `json:"left_pages"` // 当前页左边显示
RightPages []int `json:"right_pages"` // 当前页右边
CurrentPage int `json:"current_page"` // 当前页
NumPages int `json:"num_pages"` // 总页数
LeftHasMore bool `json:"left_has_more"` // 左边显示更多
RightHasMore bool `json:"right_has_more"` // 右边显示更多
}
// GetPaginationData 分页数据
// numPages 总页数
// currentPage 当前页
// aroundCount 当前页左右两边显示多少个分页按钮
func GetPaginationData(numPages int, currentPage int, aroundCount int) PaginationData {
// 边缘条件判断
if currentPage > numPages {
currentPage = numPages
}
if currentPage < 1 {
currentPage = 1
}
// 当前页向左减去around_count后剩下的页数
leftAroundPages := currentPage - aroundCount
// 当前页向右减去around_count后剩下的页数
rightAroundPages := numPages - currentPage - aroundCount + 1
// 左边还有更多页
leftHasMore := false
// 右边还有更多页
rightHasMore := false
// 当前页左边显示页码
var leftPages []int
// 当前页右边显示页码
var rightPages []int
if leftAroundPages <= 1 || currentPage-aroundCount-1 == 1 {
leftPages = rangeList(1, currentPage-1)
} else {
leftHasMore = true
leftPages = rangeList(leftAroundPages, currentPage-1)
}
if rightAroundPages <= 1 || currentPage+aroundCount+1 == numPages {
rightPages = rangeList(currentPage+1, numPages)
} else {
rightHasMore = true
rightPages = rangeList(currentPage+1, currentPage+aroundCount)
}
return PaginationData{
LeftPages: leftPages, RightPages: rightPages, CurrentPage: currentPage,
NumPages: numPages, LeftHasMore: leftHasMore, RightHasMore: rightHasMore}
}
// LeftPages RightPages
func rangeList(start int, end int) []int {
var list []int
for i := start; i <= end; i++ {
list = append(list, i)
}
if list == nil {
return []int{}
}
return list
}

View File

@ -7,6 +7,15 @@ import (
"time" "time"
) )
// SysUserExists 用户是否存在
func SysUserExists(userName string) bool {
if _, err := SysUserSelectByUsername(userName); err != nil {
return false
}
return true
}
// SysUserSelectByUsername 通过用户名查询用户 // SysUserSelectByUsername 通过用户名查询用户
func SysUserSelectByUsername(userName string) (model.SysUser, error) { func SysUserSelectByUsername(userName string) (model.SysUser, error) {
var ( var (
@ -17,7 +26,8 @@ func SysUserSelectByUsername(userName string) (model.SysUser, error) {
corelib.Logger.Infoln("SysUserSelectByUsername 从缓存获取用户信息成功: ", user.Username) corelib.Logger.Infoln("SysUserSelectByUsername 从缓存获取用户信息成功: ", user.Username)
return user, nil return user, nil
} else { } else {
corelib.Logger.Infoln("SysUserSelectByUsername 从缓存获取用户信息失败: ", err) err = nil
corelib.Logger.Infoln("SysUserSelectByUsername 从缓存获取用户信息失败: ", user.Username)
} }
if err = corelib.DbMysql.Model(model.SysUser{Username: userName}).First(&user).Error; err != nil { if err = corelib.DbMysql.Model(model.SysUser{Username: userName}).First(&user).Error; err != nil {
corelib.Logger.Infoln("SysUserSelectByUsername 数据库中查询用户信息失败: ", err) corelib.Logger.Infoln("SysUserSelectByUsername 数据库中查询用户信息失败: ", err)
@ -60,3 +70,8 @@ func SysUserGetCacheByUsername(userName string) (model.SysUser, error) {
} }
return user, nil return user, nil
} }
// SysUserCreate 新建用户
func SysUserCreate(user model.SysUser) error {
return corelib.DbMysql.Create(&user).Error
}

View File

@ -11,7 +11,7 @@ import (
"strings" "strings"
) )
func CaptchaHandler(w http.ResponseWriter, r *http.Request) { func CaptchaApiHandler(w http.ResponseWriter, r *http.Request) {
var ( var (
res []byte res []byte
err error err error

View File

@ -8,7 +8,7 @@ import (
"picgo/model" "picgo/model"
) )
func DomainHandler(w http.ResponseWriter, r *http.Request) { func DomainPageHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" { if r.Method == "GET" {
var ( var (
err error err error
@ -27,5 +27,7 @@ func DomainHandler(w http.ResponseWriter, r *http.Request) {
tmpData["IsSuper"] = user.IsSuper tmpData["IsSuper"] = user.IsSuper
w.Header().Add("X-CSRF-Token", csrf.Token(r)) w.Header().Add("X-CSRF-Token", csrf.Token(r))
corelib.TemplateHandler(w, tmpData, "layout.html", "view/layout.html", "view/domain.html") corelib.TemplateHandler(w, tmpData, "layout.html", "view/layout.html", "view/domain.html")
} else {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
} }
} }

View File

@ -8,7 +8,7 @@ import (
"picgo/model" "picgo/model"
) )
func IndexHandler(w http.ResponseWriter, r *http.Request) { func IndexPageHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" { if r.Method == "GET" {
var ( var (
err error err error
@ -27,5 +27,7 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) {
tmpData["IsSuper"] = user.IsSuper tmpData["IsSuper"] = user.IsSuper
w.Header().Add("X-CSRF-Token", csrf.Token(r)) w.Header().Add("X-CSRF-Token", csrf.Token(r))
corelib.TemplateHandler(w, tmpData, "layout.html", "view/layout.html", "view/index.html") corelib.TemplateHandler(w, tmpData, "layout.html", "view/layout.html", "view/index.html")
} else {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
} }
} }

View File

@ -11,63 +11,61 @@ import (
"picgo/model" "picgo/model"
) )
func LoginHandler(w http.ResponseWriter, r *http.Request) { func LoginPageHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method { switch r.Method {
case http.MethodGet: case http.MethodGet:
tmpData := map[string]interface{}{ tmpData := map[string]interface{}{
csrf.TemplateTag: csrf.TemplateField(r), csrf.TemplateTag: csrf.TemplateField(r),
} }
corelib.TemplateHandler(w, tmpData, "login.html", "view/login.html") corelib.TemplateHandler(w, tmpData, "login.html", "view/login.html")
case http.MethodPost:
loginService(w, r)
default: default:
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
} }
} }
func loginService(w http.ResponseWriter, r *http.Request) { func LoginApiHandler(w http.ResponseWriter, r *http.Request) {
var ( switch r.Method {
res model.LoginRequest case http.MethodPost:
user model.SysUser var (
) err error
res model.LoginRequest
user model.SysUser
)
err := json.NewDecoder(r.Body).Decode(&res) if err = json.NewDecoder(r.Body).Decode(&res); err != nil {
if err != nil { corelib.Logger.Error("LoginApiHandler, 参数获取失败")
http.Error(w, "Bad Request", http.StatusBadRequest) corelib.WriteJsonResponse(w, 400, "参数错误", nil)
return return
}
if (res.Username == "") || (res.Password == "") {
corelib.Logger.Error("LoginApiHandler, 用户名或者密码为空")
corelib.WriteJsonResponse(w, 400, "请输入用户名密码", nil)
return
}
cid := getCaptchaId(r)
if ok := captcha.Verify(cid, res.Captcha); !ok {
corelib.WriteJsonResponse(w, 1040, "验证码错误", nil)
return
}
if user, err = data.SysUserSelectByUsername(res.Username); err != nil {
corelib.WriteJsonResponse(w, 1041, "用户不存在", nil)
return
}
// 验证用户名密码
if !corelib.ComparePasswords(user.Password, res.Password, user.Salt) {
corelib.WriteJsonResponse(w, 1042, "用户名或密码错误", nil)
return
}
session, _ := corelib.SessionStore.Get(r, configs.Settings.Server.SessionName)
session.Values["username"] = user.Username
if err = session.Save(r, w); err != nil {
corelib.Logger.Infoln("session save err:", err)
corelib.WriteJsonResponse(w, 1043, "会话保存失败", nil)
return
}
w.Header().Set("Content-Type", "application/json")
corelib.WriteJsonResponse(w, 200, "登录成功", nil)
default:
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
} }
cid := getCaptchaId(r)
corelib.Logger.Info("LoginRequest: ", res)
corelib.Logger.Info("login cid: ", cid)
if ok := captcha.Verify(cid, res.Captcha); !ok {
corelib.WriteJsonResponse(w, 1040, "验证码错误", nil)
return
}
if user, err = data.SysUserSelectByUsername(res.Username); err != nil {
corelib.WriteJsonResponse(w, 1041, "用户不存在", nil)
return
}
// 验证用户名密码
if !corelib.ComparePasswords(user.Password, res.Password, user.Salt) {
corelib.WriteJsonResponse(w, 1042, "用户名或密码错误", nil)
return
}
session, _ := corelib.SessionStore.Get(r, configs.Settings.Server.SessionName)
session.Values["username"] = user.Username
if err = session.Save(r, w); err != nil {
corelib.Logger.Infoln("session save err:", err)
corelib.WriteJsonResponse(w, 1043, "会话保存失败", nil)
return
}
w.Header().Set("Content-Type", "application/json")
corelib.WriteJsonResponse(w, 200, "登录成功", nil)
} }
//// sysUserSelectDataByUsername 通过用户名查询用户
//func sysUserSelectDataByUsername(userName string) (model.SysUser, error) {
// var user model.SysUser
// if err := corelib.DbMysql.Model(model.SysUser{Username: userName}).First(&user).Error; err != nil {
// return user, err
// }
// return user, nil
//}

View File

@ -8,7 +8,7 @@ import (
"picgo/model" "picgo/model"
) )
func PictureHandler(w http.ResponseWriter, r *http.Request) { func PicturePageHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" { if r.Method == "GET" {
var ( var (
err error err error
@ -28,5 +28,7 @@ func PictureHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Add("X-CSRF-Token", csrf.Token(r)) w.Header().Add("X-CSRF-Token", csrf.Token(r))
corelib.TemplateHandler(w, tmpData, "layout.html", "view/layout.html", "view/picture.html") corelib.TemplateHandler(w, tmpData, "layout.html", "view/layout.html", "view/picture.html")
} else {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
} }
} }

View File

@ -10,7 +10,7 @@ import (
"picgo/corelib" "picgo/corelib"
) )
func UploadFileHandler(w http.ResponseWriter, r *http.Request) { func UploadFileApiHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" { if r.Method == "POST" {
// 解析表单数据限制最大内存为3MB 1024*1024*3 // 解析表单数据限制最大内存为3MB 1024*1024*3
err := r.ParseMultipartForm(configs.Settings.Server.UploadMaxSize << 20) err := r.ParseMultipartForm(configs.Settings.Server.UploadMaxSize << 20)

View File

@ -1,14 +1,17 @@
package handler package handler
import ( import (
"encoding/json"
"github.com/gorilla/csrf" "github.com/gorilla/csrf"
"math"
"net/http" "net/http"
"picgo/corelib" "picgo/corelib"
"picgo/data" "picgo/data"
"picgo/model" "picgo/model"
"strconv"
) )
func UserHandler(w http.ResponseWriter, r *http.Request) { func UserPageHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" { if r.Method == "GET" {
var ( var (
err error err error
@ -27,6 +30,104 @@ func UserHandler(w http.ResponseWriter, r *http.Request) {
tmpData["IsSuper"] = user.IsSuper tmpData["IsSuper"] = user.IsSuper
w.Header().Add("X-CSRF-Token", csrf.Token(r)) w.Header().Add("X-CSRF-Token", csrf.Token(r))
corelib.TemplateHandler(w, tmpData, "layout.html", "view/layout.html", "view/user.html") corelib.TemplateHandler(w, tmpData, "layout.html", "view/layout.html", "view/user.html")
} else {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
} }
} }
func UserCreateApiHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
var (
err error
res model.UserCreateRequest
user model.SysUser
password string
salt string
)
if err = json.NewDecoder(r.Body).Decode(&res); err != nil {
corelib.Logger.Error("UserCreateApiHandler, 参数获取失败")
corelib.WriteJsonResponse(w, 400, "参数错误", nil)
return
}
if (res.Username == "") || (res.Password == "") {
corelib.Logger.Error("UserCreateApiHandler, 用户名或者密码为空")
corelib.WriteJsonResponse(w, 400, "请输入用户名密码", nil)
return
}
if isExists := data.SysUserExists(res.Username); !isExists {
corelib.Logger.Error("UserCreateApiHandler, 用户已经存在")
corelib.WriteJsonResponse(w, 10050, "用户已经存在", nil)
return
}
salt = corelib.GenerateSalt()
if password, err = corelib.HashPassword(res.Password, salt); err != nil {
corelib.Logger.Error("UserCreateApiHandler, 生成密码失败")
corelib.WriteJsonResponse(w, 10051, "生成密码失败", nil)
return
}
user.Username = res.Username
user.Password = password
user.Salt = salt
user.IsSuper = 0
if err = data.SysUserCreate(user); err != nil {
corelib.Logger.Error("UserCreateApiHandler, 数据保存用户数据失败")
corelib.WriteJsonResponse(w, 10052, "新建用户失败", nil)
return
}
w.Header().Set("Content-Type", "application/json")
corelib.WriteJsonResponse(w, 200, "用户创建成功", nil)
} else {
//http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
corelib.WriteJsonResponse(w, 405, "方法错误", nil)
return
}
}
// UserPageApiHandler 分页查询
func UserPageApiHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
var (
err error
page int
onePageCount int
count int
)
query := r.URL.Query()
// 搜索条件
search := query.Get("search")
// 每页显示多少跳
if onePageCount, err = strconv.Atoi(query.Get("count")); err != nil {
corelib.Logger.Error("UserPageApiHandler, 获取count参数失败")
corelib.WriteJsonResponse(w, 400, "参数错误", nil)
return
}
// 第几页
if page, err = strconv.Atoi(query.Get("page")); err != nil {
corelib.Logger.Error("UserPageApiHandler, 获取page参数失败")
corelib.WriteJsonResponse(w, 400, "参数错误", nil)
return
}
numPage := math.Ceil(float64(count) / float64(onePageCount))
paginationData := corelib.GetPaginationData(int(numPage), page, 2)
if search != "" {
} else {
}
corelib.Logger.Infoln("paginationData: ", paginationData)
} else {
corelib.WriteJsonResponse(w, 405, "方法错误", nil)
return
}
}
func pagination(page, onePageCount int, search string) {
}

22
middleware/csrf.go Normal file
View File

@ -0,0 +1,22 @@
package middleware
import (
"github.com/gorilla/csrf"
"picgo/configs"
)
func getSecure() (secure bool) {
if configs.Settings.Server.Environment == "dev" {
secure = false
} else {
secure = true
}
return
}
// CsrfMiddleware 设置CSRF保护
var CsrfMiddleware = csrf.Protect(
[]byte(configs.Settings.Server.SessionsKey),
csrf.Secure(getSecure()), // 在开发环境中禁用HTTPS
csrf.RequestHeader("X-CSRF-Token"),
)

25
middleware/log.go Normal file
View File

@ -0,0 +1,25 @@
package middleware
import (
"net/http"
"picgo/corelib"
"time"
)
// LoggingMiddleware 是一个中间件函数,用于记录 HTTP 请求日志
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 获取请求的详细信息
start := time.Now()
method := r.Method
path := r.URL.Path
remoteAddr := r.RemoteAddr
referer := r.Referer()
// 调用下一个处理程序
next.ServeHTTP(w, r)
// 记录日志
corelib.Logger.Infof("[%s] %s %s %s %s", time.Since(start), method, path, remoteAddr, referer)
})
}

6
model/user_request.go Normal file
View File

@ -0,0 +1,6 @@
package model
type UserCreateRequest struct {
Username string `json:"username"`
Password string `json:"password"`
}

View File

@ -22,18 +22,23 @@ func InitRouter() *mux.Router {
csrf.Secure(secure), // 在开发环境中禁用HTTPS csrf.Secure(secure), // 在开发环境中禁用HTTPS
csrf.RequestHeader("X-CSRF-Token"), csrf.RequestHeader("X-CSRF-Token"),
) )
// 创建新的路由器 // 创建新的路由器
r := mux.NewRouter() r := mux.NewRouter()
// 不需要鉴权路由 // 不需要鉴权路由
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", handler.StaticHandler())) r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", handler.StaticHandler())) // 静态资源路由
r.HandleFunc("/login", handler.LoginHandler).Methods(http.MethodGet, http.MethodPost) // 登录 r.HandleFunc("/login", handler.LoginPageHandler).Methods(http.MethodGet) // 登录页面
r.HandleFunc("/captcha", handler.CaptchaHandler).Methods(http.MethodGet) // 验证码接口 r.HandleFunc("/api/v1/login", handler.LoginApiHandler).Methods(http.MethodPost) // 登录接口
// 需要鉴权路由 r.HandleFunc("/captcha", handler.CaptchaApiHandler).Methods(http.MethodGet) // 验证码接口
r.Handle("/", middleware.LoginMiddleware(http.HandlerFunc(handler.IndexHandler))).Methods(http.MethodGet) // 后台首页 // 需要鉴权页面路由
r.Handle("/domain", middleware.LoginMiddleware(http.HandlerFunc(handler.DomainHandler))).Methods(http.MethodGet) // 域名管理 r.Handle("/", middleware.LoginMiddleware(http.HandlerFunc(handler.IndexPageHandler))).Methods(http.MethodGet) // 后台首页
r.Handle("/user", middleware.LoginMiddleware(http.HandlerFunc(handler.UserHandler))).Methods(http.MethodGet) // 用户管理 r.Handle("/domain", middleware.LoginMiddleware(http.HandlerFunc(handler.DomainPageHandler))).Methods(http.MethodGet) // 域名管理
r.Handle("/picture", middleware.LoginMiddleware(http.HandlerFunc(handler.PictureHandler))).Methods(http.MethodGet) // 图片管理 r.Handle("/user", middleware.LoginMiddleware(http.HandlerFunc(handler.UserPageHandler))).Methods(http.MethodGet) // 用户管理
r.Handle("/api/v1/upload", middleware.LoginMiddleware(http.HandlerFunc(handler.UploadFileHandler))).Methods(http.MethodPost) // 图片上传接口 r.Handle("/picture", middleware.LoginMiddleware(http.HandlerFunc(handler.PicturePageHandler))).Methods(http.MethodGet) // 图片管理
// 需要鉴权接口路由
r.Handle("/api/v1/upload", middleware.LoginMiddleware(http.HandlerFunc(handler.UploadFileApiHandler))).Methods(http.MethodPost) // 图片上传接口
r.Handle("/api/v1/user/create", middleware.LoginMiddleware(http.HandlerFunc(handler.UserCreateApiHandler))).Methods(http.MethodPost) // 新增用户
// 应用 CORS CSRF 中间件 // 应用 CORS CSRF 中间件
http.Handle("/", middleware.CorsMiddleware(CSRF(r))) http.Handle("/", middleware.CorsMiddleware(CSRF(r)))

File diff suppressed because one or more lines are too long

View File

@ -7,8 +7,8 @@
@font-face { @font-face {
font-display: block; font-display: block;
font-family: "bootstrap-icons"; font-family: "bootstrap-icons";
src: url("./fonts/bootstrap-icons.woff2?dd67030699838ea613ee6dbda90effa6") format("woff2"), src: url("fonts/bootstrap-icons.woff2?dd67030699838ea613ee6dbda90effa6") format("woff2"),
url("./fonts/bootstrap-icons.woff?dd67030699838ea613ee6dbda90effa6") format("woff"); url("fonts/bootstrap-icons.woff?dd67030699838ea613ee6dbda90effa6") format("woff");
} }
.bi::before, .bi::before,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -56,7 +56,7 @@ Domain.prototype.domainPageInit = function () {
// 表单验证 // 表单验证
Domain.prototype.verifyDomain = function (domain) { Domain.prototype.verifyDomain = function (domain) {
let patt = /(https?|ftp|file):\/\/[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]/i let patt = /(https|http):\/\/[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]/i
return patt.test(domain) return patt.test(domain)
} }

View File

@ -50,6 +50,6 @@ Common.prototype.AlertSuccessToast = function (msg) {
this.AlertToast(msg, 'success') this.AlertToast(msg, 'success')
} }
var Alert = new Common() var Alert = new Common()

View File

@ -4,7 +4,7 @@ function Modal(modalTitle, fields, onSubmit) {
this.onSubmit = onSubmit this.onSubmit = onSubmit
} }
Modal.prototype.GenericModalForm = function () { Modal.prototype.RenderModalForm = function () {
let self = this let self = this
// 设置表单标题 // 设置表单标题
$('#genericFormModalLabel').text(self.modalTitle) $('#genericFormModalLabel').text(self.modalTitle)

View File

@ -33,7 +33,7 @@ Table.prototype.RenderTable = function () {
if (self.actions.length > 0) { if (self.actions.length > 0) {
let $actionTd = $('<td>'); let $actionTd = $('<td>');
self.actions.forEach(function(action) { self.actions.forEach(function(action) {
let $button = $('<button class="btn btn-' + action.class + ' btn-sm">' + action.label + '</button>'); let $button = $('<button class="btn btn-' + action.class + ' btn-sm ml-2">' + action.label + '</button>');
$button.on('click', function() { $button.on('click', function() {
action.onClick(row); action.onClick(row);
}); });

View File

@ -25,7 +25,7 @@ Login.prototype.LoginClick = function () {
let captcha = $("#login-captcha").val() let captcha = $("#login-captcha").val()
$.ajax({ $.ajax({
url: '/login', url: '/api/v1/login',
method: 'POST', method: 'POST',
data: JSON.stringify({ data: JSON.stringify({
username: username, username: username,
@ -48,7 +48,7 @@ Login.prototype.LoginClick = function () {
Alert.AlertError("服务器内部错误") Alert.AlertError("服务器内部错误")
console.log('请求失败:', error); console.log('请求失败:', error);
} }
}); })
}) })
} }

View File

@ -1,14 +0,0 @@
function PageHead(fields, actions,elementId) {
this.fields = fields
this.actions = actions
this.element = $(elementId)
}
PageHead.prototype.RenderPageHead = function () {
}

88
static/js/user.js Normal file
View File

@ -0,0 +1,88 @@
function User() {
}
User.prototype.CreateUser = function () {
// 示例字段配置
let fields = [
{ label: '用户名:', name: 'username', type: 'text' },
{ label: '密码:', name: 'password', type: 'password' },
{ label: '确认密码:', name: 'confirm', type: 'password' }
]
// 初始化表单并显示
$('#add-user').on('click', function() {
let title = '添加用户'
let modal = new Modal(title, fields,function(data) {
console.log('表单提交数据:', data)
let csrfToken = document.getElementsByName("gorilla.csrf.Token")[0].value
console.log('表单提交数据csrfToken', csrfToken)
// 表单
if (!data.username) {
Alert.AlertError("请输入用户名!")
return
}
if (!data.password || !data.confirm) {
Alert.AlertError("请输入密码!")
return
}
if (data.password !== data.confirm) {
Alert.AlertError("请确认密码!")
return
}
Alert.AlertSuccessToast("创建用户成功!")
$.ajax({
url: '/api/v1/user/create',
method: 'POST',
data: JSON.stringify({
username: data.username,
password: data.password
}),
headers: {
'Content-Type': 'application/json',
"X-CSRF-Token": csrfToken
},
success: function (result) {
if (result.status === 200) {
Alert.AlertSuccessToast(result.message)
location.reload();
} else {
Alert.AlertError(result.message)
}
},
error: function (xhr, status, error) {
Alert.AlertError("服务器内部错误!")
console.log(" 状态: ", status, ' 请求失败:', error);
}
})
})
modal.RenderModalForm()
})
}
User.prototype.GetUserData = function () {
}
User.prototype.run = function () {
this.CreateUser()
}
// 构造执行入口
$(function () {
// feather.replace()
// 模板过滤方法
// if (window.template) {
// template.defaults.imports.domainSubstring = function (dateValue) {
// if (dateValue.length > 40 ) {
// return dateValue.substring(0, 37) + "..."
// } else {
// return dateValue
// }
// }
// }
let user = new User()
user.run()
})

View File

@ -4,13 +4,14 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="shortcut icon" href="/static/img/favicon.ico"> <link rel="shortcut icon" href="/static/img/favicon.ico">
<link href="/static/css/bootstrap.min.css" rel="stylesheet"> <link href="/static/css/lib/bootstrap.min.css" rel="stylesheet">
<link href="/static/css/sweetalert.css" rel="stylesheet"> <link href="/static/css/lib/bootstrap-icons.css" rel="stylesheet">
<script src="/static/js/jquery-3.3.1.min.js"></script> <link href="/static/css/lib/sweetalert.css" rel="stylesheet">
<script src="/static/js/popper.min.js"></script> <script src="/static/js/lib/jquery-3.3.1.min.js"></script>
<script src="/static/js/bootstrap.min.js"></script> <script src="/static/js/lib/popper.min.js"></script>
<script src="/static/js/sweetalert.min.js"></script> <script src="/static/js/lib/bootstrap.min.js"></script>
<script src="/static/js/common.js"></script> <script src="/static/js/lib/sweetalert.min.js"></script>
<script src="/static/js/lib/common.js"></script>
{{template "style" .}} {{template "style" .}}
<title>{{.Title}}</title> <title>{{.Title}}</title>
</head> </head>

View File

@ -6,14 +6,13 @@
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="shortcut icon" href="/static/img/favicon.ico"> <link rel="shortcut icon" href="/static/img/favicon.ico">
<title>后台登录</title> <title>后台登录</title>
<link href="/static/css/bootstrap.min.css" rel="stylesheet"> <link href="/static/css/lib/bootstrap.min.css" rel="stylesheet">
<link href="/static/css/bootstrap-icons.css" rel="stylesheet"> <link href="/static/css/lib/bootstrap-icons.css" rel="stylesheet">
<link href="/static/css/sweetalert.css" rel="stylesheet"> <link href="/static/css/lib/sweetalert.css" rel="stylesheet">
<script src="/static/js/jquery-3.3.1.min.js"></script> <script src="/static/js/lib/jquery-3.3.1.min.js"></script>
<script src="/static/js/bootstrap.min.js"></script> <script src="/static/js/lib/bootstrap.min.js"></script>
<!-- <script src="/static/js/feather-icons.js"></script>--> <script src="/static/js/lib/sweetalert.min.js"></script>
<script src="/static/js/sweetalert.min.js"></script> <script src="/static/js/lib/common.js"></script>
<script src="/static/js/common.js"></script>
<style> <style>
.login-container {min-height: 100vh;display: flex;align-items: center;justify-content: center;} .login-container {min-height: 100vh;display: flex;align-items: center;justify-content: center;}
.login-form {width: 100%;max-width: 420px;padding: 15px;margin: auto;border-radius: 12px;} .login-form {width: 100%;max-width: 420px;padding: 15px;margin: auto;border-radius: 12px;}

View File

@ -8,14 +8,14 @@
{{end}} {{end}}
{{define "script"}} {{define "script"}}
<script src="/static/js/modal.js"></script> <script src="/static/js/lib/modal.js"></script>
<script> <script>
$(document).ready(function() { $(document).ready(function() {
// 示例字段配置 // 示例字段配置
let fields = [ let fields = [
{ label: '用户名', name: 'username', type: 'text' }, { label: '用户名:', name: 'username', type: 'text' },
{ label: '邮箱', name: 'email', type: 'email' }, { label: '密码:', name: 'password', type: 'password' },
{ label: '密码', name: 'password', type: 'password' } { label: '确认密码:', name: 'password', type: 'password' }
] ]
// 初始化表单并显示 // 初始化表单并显示
@ -25,7 +25,7 @@
console.log('表单提交数据:', data) console.log('表单提交数据:', data)
// 在此处处理表单提交,例如通过 AJAX 发送到服务器 // 在此处处理表单提交,例如通过 AJAX 发送到服务器
}) })
modal.GenericModalForm() modal.RenderModalForm()
}) })
}) })
</script> </script>

View File

@ -3,45 +3,14 @@
{{define "content"}} {{define "content"}}
<div id="user-inquire"> <div id="user-inquire">
<div class="alert alert-primary form-inline" role="alert"> <div class="alert alert-primary" role="alert">
<button type="button" id="add-domain" class="btn btn-primary" data-toggle="modal" data-target="#DomainModal"><i class="bi bi-plus-lg"></i> 添加域名</button> <button type="button" class="btn btn-primary" id="add-user"><i class="bi bi-plus-square"></i> 创建用户</button>
</div> </div>
</div> </div>
<div id="user-table"></div> <div id="user-table"></div>
{{end}} {{end}}
{{define "script"}} {{define "script"}}
<script src="/static/js/table.js"></script> <script src="/static/js/lib/modal.js"></script>
<script> <script src="/static/js/user.js"></script>
$(document).ready(function() {
// 示例数据
let columns = ['ID', '名称', '年龄'];
let data = [
[1, '张三', 25],
[2, '李四', 30],
[3, '王五', 28]
];
// 操作按钮配置
let actions = [
{
label: '编辑',
class: 'primary',
onClick: function(row) {
alert('编辑:' + row[1]);
}
},
{
label: '删除',
class: 'danger',
onClick: function(row) {
alert('删除:' + row[1]);
}
}
];
let table = new Table(columns, data, actions, "#user-table")
table.RenderTable()
})
</script>
{{end}} {{end}}