From 56da25dc81c682c54c9947a663709fbd818b1d9d Mon Sep 17 00:00:00 2001 From: GoCo Date: Thu, 24 Jul 2025 21:53:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20=E5=89=8D=E5=8F=B0?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=99=BB=E5=BD=95=E6=B3=A8=E5=86=8C=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/launch.json | 15 ++ server/api/admin/users/users.go | 67 +++++ server/api/api/users/users.go | 16 ++ server/api/api/users/v1/users.go | 24 ++ server/internal/consts/business_error_code.go | 13 + server/internal/controller/admin/sys/users.go | 73 ++++++ server/internal/controller/api/users/users.go | 5 + .../controller/api/users/users_new.go | 15 ++ .../controller/api/users/users_v1_login.go | 43 ++++ .../controller/api/users/users_v1_register.go | 78 ++++++ server/internal/dao/internal/users.go | 117 +++++++++ server/internal/dao/users.go | 22 ++ server/internal/logic/sys/users.go | 157 ++++++++++++ server/internal/model/do/users.go | 35 +++ server/internal/model/entity/users.go | 33 +++ server/internal/model/input/sysin/users.go | 174 +++++++++++++ server/internal/router/api.go | 7 + server/internal/router/genrouter/users.go | 13 + server/internal/service/sys.go | 28 +++ server/storage/data/generate/users_menu.sql | 65 +++++ server/utility/jwt/jwt.go | 50 ++++ web/src/api/users/index.ts | 51 ++++ web/src/views/users/edit.vue | 181 ++++++++++++++ web/src/views/users/index.vue | 225 +++++++++++++++++ web/src/views/users/model.ts | 228 ++++++++++++++++++ web/src/views/users/view.vue | 132 ++++++++++ 26 files changed, 1867 insertions(+) create mode 100644 .vscode/launch.json create mode 100644 server/api/admin/users/users.go create mode 100644 server/api/api/users/users.go create mode 100644 server/api/api/users/v1/users.go create mode 100644 server/internal/consts/business_error_code.go create mode 100644 server/internal/controller/admin/sys/users.go create mode 100644 server/internal/controller/api/users/users.go create mode 100644 server/internal/controller/api/users/users_new.go create mode 100644 server/internal/controller/api/users/users_v1_login.go create mode 100644 server/internal/controller/api/users/users_v1_register.go create mode 100755 server/internal/dao/internal/users.go create mode 100755 server/internal/dao/users.go create mode 100644 server/internal/logic/sys/users.go create mode 100755 server/internal/model/do/users.go create mode 100755 server/internal/model/entity/users.go create mode 100644 server/internal/model/input/sysin/users.go create mode 100644 server/internal/router/genrouter/users.go create mode 100644 server/storage/data/generate/users_menu.sql create mode 100644 server/utility/jwt/jwt.go create mode 100644 web/src/api/users/index.ts create mode 100644 web/src/views/users/edit.vue create mode 100644 web/src/views/users/index.vue create mode 100644 web/src/views/users/model.ts create mode 100644 web/src/views/users/view.vue diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..06d94a6 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch file", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceFolder}/server/main.go" + } + ] +} \ No newline at end of file diff --git a/server/api/admin/users/users.go b/server/api/admin/users/users.go new file mode 100644 index 0000000..1352536 --- /dev/null +++ b/server/api/admin/users/users.go @@ -0,0 +1,67 @@ +// Package users +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2025 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +// @AutoGenerate Version 2.17.8 +package users + +import ( + "hotgo/internal/model/input/form" + "hotgo/internal/model/input/sysin" + + "github.com/gogf/gf/v2/frame/g" +) + +// ListReq 查询前台用户表列表 +type ListReq struct { + g.Meta `path:"/users/list" method:"get" tags:"前台用户表" summary:"获取前台用户表列表"` + sysin.UsersListInp +} + +type ListRes struct { + form.PageRes + List []*sysin.UsersListModel `json:"list" dc:"数据列表"` +} + +// ExportReq 导出前台用户表列表 +type ExportReq struct { + g.Meta `path:"/users/export" method:"get" tags:"前台用户表" summary:"导出前台用户表列表"` + sysin.UsersListInp +} + +type ExportRes struct{} + +// ViewReq 获取前台用户表指定信息 +type ViewReq struct { + g.Meta `path:"/users/view" method:"get" tags:"前台用户表" summary:"获取前台用户表指定信息"` + sysin.UsersViewInp +} + +type ViewRes struct { + *sysin.UsersViewModel +} + +// EditReq 修改/新增前台用户表 +type EditReq struct { + g.Meta `path:"/users/edit" method:"post" tags:"前台用户表" summary:"修改/新增前台用户表"` + sysin.UsersEditInp +} + +type EditRes struct{} + +// DeleteReq 删除前台用户表 +type DeleteReq struct { + g.Meta `path:"/users/delete" method:"post" tags:"前台用户表" summary:"删除前台用户表"` + sysin.UsersDeleteInp +} + +type DeleteRes struct{} + +// StatusReq 更新前台用户表状态 +type StatusReq struct { + g.Meta `path:"/users/status" method:"post" tags:"前台用户表" summary:"更新前台用户表状态"` + sysin.UsersStatusInp +} + +type StatusRes struct{} \ No newline at end of file diff --git a/server/api/api/users/users.go b/server/api/api/users/users.go new file mode 100644 index 0000000..9ed755f --- /dev/null +++ b/server/api/api/users/users.go @@ -0,0 +1,16 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package users + +import ( + "context" + + "hotgo/api/api/users/v1" +) + +type IUsersV1 interface { + Login(ctx context.Context, req *v1.LoginReq) (res *v1.LoginRes, err error) + Register(ctx context.Context, req *v1.RegisterReq) (res *v1.RegisterRes, err error) +} diff --git a/server/api/api/users/v1/users.go b/server/api/api/users/v1/users.go new file mode 100644 index 0000000..c2f1786 --- /dev/null +++ b/server/api/api/users/v1/users.go @@ -0,0 +1,24 @@ +package v1 + +import "github.com/gogf/gf/v2/frame/g" + +type LoginReq struct { + g.Meta `path:"/users/login" method:"post" tags:"前台用户登录" summary:"前台用户登录"` + Phone string `json:"phone"` + Password string `json:"password"` + ConfirmPassword string `json:"confirmPassword"` +} + +type LoginRes struct { + Token string `json:"token"` +} + +type RegisterReq struct { + g.Meta `path:"/users/register" method:"post" tags:"Users" summary:"注册"` + Phone string `v:"required" dc:"user phone"` + Password string `v:"required" dc:"user password"` + ConfirmPassword string `v:"required" dc:"确认密码"` +} +type RegisterRes struct { + Token string `json:"token" dc:"user token"` +} diff --git a/server/internal/consts/business_error_code.go b/server/internal/consts/business_error_code.go new file mode 100644 index 0000000..de2946c --- /dev/null +++ b/server/internal/consts/business_error_code.go @@ -0,0 +1,13 @@ +package consts + +import "github.com/gogf/gf/v2/errors/gcode" + +var ( + CodeOK = gcode.New(0, "成功", nil) + CodeInvalidParam = gcode.New(10001, "参数错误", nil) + CodeUnauthorized = gcode.New(10002, "未授权访问", nil) + CodeInternalError = gcode.New(10003, "服务器内部错误", nil) + CodeUserNotFound = gcode.New(20001, "用户不存在", nil) + CodePasswordInvalid = gcode.New(20002, "密码错误", nil) + CodeUserAlreadyExists = gcode.New(20003, "用户已存在", nil) +) diff --git a/server/internal/controller/admin/sys/users.go b/server/internal/controller/admin/sys/users.go new file mode 100644 index 0000000..b9c0b96 --- /dev/null +++ b/server/internal/controller/admin/sys/users.go @@ -0,0 +1,73 @@ +// Package sys +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2025 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +// @AutoGenerate Version 2.17.8 +package sys + +import ( + "context" + "hotgo/api/admin/users" + "hotgo/internal/model/input/sysin" + "hotgo/internal/service" +) + +var ( + Users = cUsers{} +) + +type cUsers struct{} + +// List 查看前台用户表列表 +func (c *cUsers) List(ctx context.Context, req *users.ListReq) (res *users.ListRes, err error) { + list, totalCount, err := service.SysUsers().List(ctx, &req.UsersListInp) + if err != nil { + return + } + + if list == nil { + list = []*sysin.UsersListModel{} + } + + res = new(users.ListRes) + res.List = list + res.PageRes.Pack(req, totalCount) + return +} + +// Export 导出前台用户表列表 +func (c *cUsers) Export(ctx context.Context, req *users.ExportReq) (res *users.ExportRes, err error) { + err = service.SysUsers().Export(ctx, &req.UsersListInp) + return +} + +// Edit 更新前台用户表 +func (c *cUsers) Edit(ctx context.Context, req *users.EditReq) (res *users.EditRes, err error) { + err = service.SysUsers().Edit(ctx, &req.UsersEditInp) + return +} + +// View 获取指定前台用户表信息 +func (c *cUsers) View(ctx context.Context, req *users.ViewReq) (res *users.ViewRes, err error) { + data, err := service.SysUsers().View(ctx, &req.UsersViewInp) + if err != nil { + return + } + + res = new(users.ViewRes) + res.UsersViewModel = data + return +} + +// Delete 删除前台用户表 +func (c *cUsers) Delete(ctx context.Context, req *users.DeleteReq) (res *users.DeleteRes, err error) { + err = service.SysUsers().Delete(ctx, &req.UsersDeleteInp) + return +} + +// Status 更新前台用户表状态 +func (c *cUsers) Status(ctx context.Context, req *users.StatusReq) (res *users.StatusRes, err error) { + err = service.SysUsers().Status(ctx, &req.UsersStatusInp) + return +} \ No newline at end of file diff --git a/server/internal/controller/api/users/users.go b/server/internal/controller/api/users/users.go new file mode 100644 index 0000000..50454d8 --- /dev/null +++ b/server/internal/controller/api/users/users.go @@ -0,0 +1,5 @@ +// ================================================================================= +// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. +// ================================================================================= + +package users diff --git a/server/internal/controller/api/users/users_new.go b/server/internal/controller/api/users/users_new.go new file mode 100644 index 0000000..ff205d9 --- /dev/null +++ b/server/internal/controller/api/users/users_new.go @@ -0,0 +1,15 @@ +// ================================================================================= +// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. +// ================================================================================= + +package users + +import ( + "hotgo/api/api/users" +) + +type ControllerV1 struct{} + +func NewV1() users.IUsersV1 { + return &ControllerV1{} +} diff --git a/server/internal/controller/api/users/users_v1_login.go b/server/internal/controller/api/users/users_v1_login.go new file mode 100644 index 0000000..8f7a4fc --- /dev/null +++ b/server/internal/controller/api/users/users_v1_login.go @@ -0,0 +1,43 @@ +package users + +import ( + "context" + + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" + + v1 "hotgo/api/api/users/v1" + "hotgo/internal/consts" + "hotgo/internal/dao" + "hotgo/internal/model/entity" + utility "hotgo/utility/jwt" +) + +func (c *ControllerV1) Login(ctx context.Context, req *v1.LoginReq) (res *v1.LoginRes, err error) { + // 1. 查找用户 + user := &entity.Users{} + err = dao.Users.Ctx(ctx).Where("phone = ?", req.Phone).Scan(user) + if err != nil { + return nil, gerror.NewCode(gcode.CodeDbOperationError, "数据库错误") + } + if user.Id == 0 { + return nil, gerror.NewCode(consts.CodeUserNotFound, "用户不存在") + } + + // 2. 校验密码(sha256+salt) + hashedPwd := utility.HashPassword(req.Password, user.Salt) + if user.Password != hashedPwd { + return nil, gerror.NewCode(consts.CodePasswordInvalid, "密码错误") + } + + // 3. 生成JWT Token + token, err := utility.GenerateToken(user.Id) + if err != nil { + return nil, gerror.NewCode(consts.CodeInternalError, "生成Token失败") + } + + res = &v1.LoginRes{ + Token: token, + } + return +} diff --git a/server/internal/controller/api/users/users_v1_register.go b/server/internal/controller/api/users/users_v1_register.go new file mode 100644 index 0000000..175a584 --- /dev/null +++ b/server/internal/controller/api/users/users_v1_register.go @@ -0,0 +1,78 @@ +package users + +import ( + "context" + "crypto/rand" + "encoding/hex" + + v1 "hotgo/api/api/users/v1" + "hotgo/internal/consts" + "hotgo/internal/dao" + "hotgo/internal/model/entity" + utility "hotgo/utility/jwt" + + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/os/gtime" +) + +// 生成随机 salt +func generateSalt(n int) (string, error) { + b := make([]byte, n) + _, err := rand.Read(b) + if err != nil { + return "", err + } + return hex.EncodeToString(b), nil +} + +func (c *ControllerV1) Register(ctx context.Context, req *v1.RegisterReq) (res *v1.RegisterRes, err error) { + // 0. 检查两次密码是否一致 + if req.Password != req.ConfirmPassword { + return nil, gerror.NewCode(gcode.CodeInvalidParameter, "两次密码输入不一致") + } + // 1. 检查手机号是否已注册 + count, err := dao.Users.Ctx(ctx).Where("phone = ?", req.Phone).Count() + if err != nil { + return nil, gerror.NewCode(gcode.CodeDbOperationError, "数据库错误") + } + if count > 0 { + return nil, gerror.NewCode(consts.CodeUserAlreadyExists, "手机号已注册") + } + + // 2. 生成 salt 并加密密码 + salt, err := generateSalt(8) + if err != nil { + return nil, gerror.NewCode(gcode.CodeInternalError, "生成salt失败") + } + hashedPwd := utility.HashPassword(req.Password, salt) + + // 3. 写入用户表 + user := entity.Users{ + Phone: req.Phone, + Password: hashedPwd, + Salt: salt, + Nickname: "用户" + req.Phone, + Avatar: "https://cdn.jsdelivr.net/gh/hotgo-io/hotgo-cdn@main/avatar/default.png", + Status: 1, + Introduce: "这个人很懒,什么都没有留下", + Gender: 0, + LoginCount: 0, + LastLogin: gtime.Now(), + } + + userId, err := dao.Users.Ctx(ctx).Data(user).InsertAndGetId() + if err != nil { + return nil, gerror.NewCode(consts.CodeInternalError, "注册失败") + } + + // 4. 返回 JWT Token + token, err := utility.GenerateToken(userId) + if err != nil { + return nil, gerror.NewCode(consts.CodeInternalError, "生成Token失败") + } + res = &v1.RegisterRes{ + Token: token, + } + return +} diff --git a/server/internal/dao/internal/users.go b/server/internal/dao/internal/users.go new file mode 100755 index 0000000..cd366a3 --- /dev/null +++ b/server/internal/dao/internal/users.go @@ -0,0 +1,117 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// UsersDao is the data access object for the table hg_users. +type UsersDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns UsersColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// UsersColumns defines and stores column names for the table hg_users. +type UsersColumns struct { + Id string // id + Phone string // 手机号 + Nickname string // 昵称 + Email string // 邮箱 + Avatar string // 头像URL + Introduce string // 个人简介 + Password string // 密码 + Salt string // 密码盐 + Realname string // 真实姓名 + Gender string // 性别 + Birthday string // 生日 + School string // 学校 + Grade string // 学历 + Major string // 专业 + Status string // 用户状态 + VerificationToken string // 邮箱验证令牌 + EmailVerified string // 邮箱验证状态 + CreatedAt string // 注册时间 + LastLogin string // 最后登录时间 + LoginCount string // 累计登录次数 +} + +// usersColumns holds the columns for the table hg_users. +var usersColumns = UsersColumns{ + Id: "id", + Phone: "phone", + Nickname: "nickname", + Email: "email", + Avatar: "avatar", + Introduce: "introduce", + Password: "password", + Salt: "salt", + Realname: "realname", + Gender: "gender", + Birthday: "birthday", + School: "school", + Grade: "grade", + Major: "major", + Status: "status", + VerificationToken: "verification_token", + EmailVerified: "email_verified", + CreatedAt: "created_at", + LastLogin: "last_login", + LoginCount: "login_count", +} + +// NewUsersDao creates and returns a new DAO object for table data access. +func NewUsersDao(handlers ...gdb.ModelHandler) *UsersDao { + return &UsersDao{ + group: "default", + table: "hg_users", + columns: usersColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *UsersDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *UsersDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *UsersDao) Columns() UsersColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *UsersDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *UsersDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *UsersDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/users.go b/server/internal/dao/users.go new file mode 100755 index 0000000..4e0765a --- /dev/null +++ b/server/internal/dao/users.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// usersDao is the data access object for the table hg_users. +// You can define custom methods on it to extend its functionality as needed. +type usersDao struct { + *internal.UsersDao +} + +var ( + // Users is a globally accessible object for table hg_users operations. + Users = usersDao{internal.NewUsersDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/logic/sys/users.go b/server/internal/logic/sys/users.go new file mode 100644 index 0000000..4cfa74d --- /dev/null +++ b/server/internal/logic/sys/users.go @@ -0,0 +1,157 @@ +// Package sys +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2025 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +// @AutoGenerate Version 2.17.8 +package sys + +import ( + "context" + "fmt" + "hotgo/internal/dao" + "hotgo/internal/library/hgorm/handler" + "hotgo/internal/model/input/form" + "hotgo/internal/model/input/sysin" + "hotgo/internal/service" + "hotgo/utility/convert" + "hotgo/utility/excel" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/util/gconv" +) + +type sSysUsers struct{} + +func NewSysUsers() *sSysUsers { + return &sSysUsers{} +} + +func init() { + service.RegisterSysUsers(NewSysUsers()) +} + +// Model 前台用户表ORM模型 +func (s *sSysUsers) Model(ctx context.Context, option ...*handler.Option) *gdb.Model { + return handler.Model(dao.Users.Ctx(ctx), option...) +} + +// List 获取前台用户表列表 +func (s *sSysUsers) List(ctx context.Context, in *sysin.UsersListInp) (list []*sysin.UsersListModel, totalCount int, err error) { + mod := s.Model(ctx) + + // 字段过滤 + mod = mod.Fields(sysin.UsersListModel{}) + + // 查询id + if in.Id > 0 { + mod = mod.Where(dao.Users.Columns().Id, in.Id) + } + + // 查询用户状态 + if in.Status > 0 { + mod = mod.Where(dao.Users.Columns().Status, in.Status) + } + + // 查询注册时间 + if len(in.CreatedAt) == 2 { + mod = mod.WhereBetween(dao.Users.Columns().CreatedAt, in.CreatedAt[0], in.CreatedAt[1]) + } + + // 分页 + mod = mod.Page(in.Page, in.PerPage) + + // 排序 + mod = mod.OrderDesc(dao.Users.Columns().Id) + + // 查询数据 + if err = mod.ScanAndCount(&list, &totalCount, false); err != nil { + err = gerror.Wrap(err, "获取前台用户表列表失败,请稍后重试!") + return + } + return +} + +// Export 导出前台用户表 +func (s *sSysUsers) Export(ctx context.Context, in *sysin.UsersListInp) (err error) { + list, totalCount, err := s.List(ctx, in) + if err != nil { + return + } + + // 字段的排序是依据tags的字段顺序,如果你不想使用默认的排序方式,可以直接定义 tags = []string{"字段名称", "字段名称2", ...} + tags, err := convert.GetEntityDescTags(sysin.UsersExportModel{}) + if err != nil { + return + } + + var ( + fileName = "导出前台用户表-" + gctx.CtxId(ctx) + sheetName = fmt.Sprintf("索引条件共%v行,共%v页,当前导出是第%v页,本页共%v行", totalCount, form.CalPageCount(totalCount, in.PerPage), in.Page, len(list)) + exports []sysin.UsersExportModel + ) + + if err = gconv.Scan(list, &exports); err != nil { + return + } + + err = excel.ExportByStructs(ctx, tags, exports, fileName, sheetName) + return +} + +// Edit 修改/新增前台用户表 +func (s *sSysUsers) Edit(ctx context.Context, in *sysin.UsersEditInp) (err error) { + return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) { + + // 修改 + if in.Id > 0 { + if _, err = s.Model(ctx). + Fields(sysin.UsersUpdateFields{}). + WherePri(in.Id).Data(in).Update(); err != nil { + err = gerror.Wrap(err, "修改前台用户表失败,请稍后重试!") + } + return + } + + // 新增 + if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}). + Fields(sysin.UsersInsertFields{}). + Data(in).OmitEmptyData().Insert(); err != nil { + err = gerror.Wrap(err, "新增前台用户表失败,请稍后重试!") + } + return + }) +} + +// Delete 删除前台用户表 +func (s *sSysUsers) Delete(ctx context.Context, in *sysin.UsersDeleteInp) (err error) { + + if _, err = s.Model(ctx).WherePri(in.Id).Unscoped().Delete(); err != nil { + err = gerror.Wrap(err, "删除前台用户表失败,请稍后重试!") + return + } + return +} + +// View 获取前台用户表指定信息 +func (s *sSysUsers) View(ctx context.Context, in *sysin.UsersViewInp) (res *sysin.UsersViewModel, err error) { + if err = s.Model(ctx).WherePri(in.Id).Scan(&res); err != nil { + err = gerror.Wrap(err, "获取前台用户表信息,请稍后重试!") + return + } + return +} + +// Status 更新前台用户表状态 +func (s *sSysUsers) Status(ctx context.Context, in *sysin.UsersStatusInp) (err error) { + if _, err = s.Model(ctx).WherePri(in.Id).Data(g.Map{ + dao.Users.Columns().Status: in.Status, + }).Update(); err != nil { + err = gerror.Wrap(err, "更新前台用户表状态失败,请稍后重试!") + return + } + return +} \ No newline at end of file diff --git a/server/internal/model/do/users.go b/server/internal/model/do/users.go new file mode 100755 index 0000000..54f166c --- /dev/null +++ b/server/internal/model/do/users.go @@ -0,0 +1,35 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package do + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" +) + +// Users is the golang structure of table hg_users for DAO operations like Where/Data. +type Users struct { + g.Meta `orm:"table:hg_users, do:true"` + Id interface{} // id + Phone interface{} // 手机号 + Nickname interface{} // 昵称 + Email interface{} // 邮箱 + Avatar interface{} // 头像URL + Introduce interface{} // 个人简介 + Password interface{} // 密码 + Salt interface{} // 密码盐 + Realname interface{} // 真实姓名 + Gender interface{} // 性别 + Birthday interface{} // 生日 + School interface{} // 学校 + Grade interface{} // 学历 + Major interface{} // 专业 + Status interface{} // 用户状态 + VerificationToken interface{} // 邮箱验证令牌 + EmailVerified interface{} // 邮箱验证状态 + CreatedAt *gtime.Time // 注册时间 + LastLogin *gtime.Time // 最后登录时间 + LoginCount interface{} // 累计登录次数 +} diff --git a/server/internal/model/entity/users.go b/server/internal/model/entity/users.go new file mode 100755 index 0000000..e8f2a75 --- /dev/null +++ b/server/internal/model/entity/users.go @@ -0,0 +1,33 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package entity + +import ( + "github.com/gogf/gf/v2/os/gtime" +) + +// Users is the golang structure for table users. +type Users struct { + Id int64 `json:"id" orm:"id" description:"id"` + Phone string `json:"phone" orm:"phone" description:"手机号"` + Nickname string `json:"nickname" orm:"nickname" description:"昵称"` + Email string `json:"email" orm:"email" description:"邮箱"` + Avatar string `json:"avatar" orm:"avatar" description:"头像URL"` + Introduce string `json:"introduce" orm:"introduce" description:"个人简介"` + Password string `json:"password" orm:"password" description:"密码"` + Salt string `json:"salt" orm:"salt" description:"密码盐"` + Realname string `json:"realname" orm:"realname" description:"真实姓名"` + Gender int64 `json:"gender" orm:"gender" description:"性别"` + Birthday string `json:"birthday" orm:"birthday" description:"生日"` + School string `json:"school" orm:"school" description:"学校"` + Grade string `json:"grade" orm:"grade" description:"学历"` + Major string `json:"major" orm:"major" description:"专业"` + Status int64 `json:"status" orm:"status" description:"用户状态"` + VerificationToken string `json:"verificationToken" orm:"verification_token" description:"邮箱验证令牌"` + EmailVerified int64 `json:"emailVerified" orm:"email_verified" description:"邮箱验证状态"` + CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"注册时间"` + LastLogin *gtime.Time `json:"lastLogin" orm:"last_login" description:"最后登录时间"` + LoginCount int64 `json:"loginCount" orm:"login_count" description:"累计登录次数"` +} diff --git a/server/internal/model/input/sysin/users.go b/server/internal/model/input/sysin/users.go new file mode 100644 index 0000000..80aeb1a --- /dev/null +++ b/server/internal/model/input/sysin/users.go @@ -0,0 +1,174 @@ +// Package sysin +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2025 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +// @AutoGenerate Version 2.17.8 +package sysin + +import ( + "context" + "hotgo/internal/consts" + "hotgo/internal/model/entity" + "hotgo/internal/model/input/form" + "hotgo/utility/validate" + + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" +) + +// UsersUpdateFields 修改前台用户表字段过滤 +type UsersUpdateFields struct { + Phone string `json:"phone" dc:"手机号"` + Nickname string `json:"nickname" dc:"昵称"` + Email string `json:"email" dc:"邮箱"` + Avatar string `json:"avatar" dc:"头像URL"` + Introduce string `json:"introduce" dc:"个人简介"` + Realname string `json:"realname" dc:"真实姓名"` + Gender int `json:"gender" dc:"性别"` + Birthday string `json:"birthday" dc:"生日"` + School string `json:"school" dc:"学校"` + Grade string `json:"grade" dc:"学历"` + Major string `json:"major" dc:"专业"` + Status int `json:"status" dc:"用户状态"` +} + +// UsersInsertFields 新增前台用户表字段过滤 +type UsersInsertFields struct { + Phone string `json:"phone" dc:"手机号"` + Nickname string `json:"nickname" dc:"昵称"` + Email string `json:"email" dc:"邮箱"` + Avatar string `json:"avatar" dc:"头像URL"` + Introduce string `json:"introduce" dc:"个人简介"` + Realname string `json:"realname" dc:"真实姓名"` + Gender int `json:"gender" dc:"性别"` + Birthday string `json:"birthday" dc:"生日"` + School string `json:"school" dc:"学校"` + Grade string `json:"grade" dc:"学历"` + Major string `json:"major" dc:"专业"` + Status int `json:"status" dc:"用户状态"` +} + +// UsersEditInp 修改/新增前台用户表 +type UsersEditInp struct { + entity.Users +} + +func (in *UsersEditInp) Filter(ctx context.Context) (err error) { + // 验证邮箱 + if err := g.Validator().Rules("email").Data(in.Email).Messages("邮箱不是邮箱地址").Run(ctx); err != nil { + return err.Current() + } + + return +} + +type UsersEditModel struct{} + +// UsersDeleteInp 删除前台用户表 +type UsersDeleteInp struct { + Id interface{} `json:"id" v:"required#id不能为空" dc:"id"` +} + +func (in *UsersDeleteInp) Filter(ctx context.Context) (err error) { + return +} + +type UsersDeleteModel struct{} + +// UsersViewInp 获取指定前台用户表信息 +type UsersViewInp struct { + Id int `json:"id" v:"required#id不能为空" dc:"id"` +} + +func (in *UsersViewInp) Filter(ctx context.Context) (err error) { + return +} + +type UsersViewModel struct { + entity.Users +} + +// UsersListInp 获取前台用户表列表 +type UsersListInp struct { + form.PageReq + Id int `json:"id" dc:"id"` + Status int `json:"status" dc:"用户状态"` + CreatedAt []*gtime.Time `json:"createdAt" dc:"注册时间"` +} + +func (in *UsersListInp) Filter(ctx context.Context) (err error) { + return +} + +type UsersListModel struct { + Id int `json:"id" dc:"id"` + Phone string `json:"phone" dc:"手机号"` + Nickname string `json:"nickname" dc:"昵称"` + Email string `json:"email" dc:"邮箱"` + Avatar string `json:"avatar" dc:"头像URL"` + Password string `json:"password" dc:"密码"` + Salt string `json:"salt" dc:"密码盐"` + Realname string `json:"realname" dc:"真实姓名"` + Gender int `json:"gender" dc:"性别"` + Birthday string `json:"birthday" dc:"生日"` + School string `json:"school" dc:"学校"` + Grade string `json:"grade" dc:"学历"` + Major string `json:"major" dc:"专业"` + Status int `json:"status" dc:"用户状态"` + VerificationToken string `json:"verificationToken" dc:"邮箱验证令牌"` + EmailVerified int `json:"emailVerified" dc:"邮箱验证状态"` + CreatedAt *gtime.Time `json:"createdAt" dc:"注册时间"` + LastLogin *gtime.Time `json:"lastLogin" dc:"最后登录时间"` + LoginCount int `json:"loginCount" dc:"累计登录次数"` +} + +// UsersExportModel 导出前台用户表 +type UsersExportModel struct { + Id int `json:"id" dc:"id"` + Phone string `json:"phone" dc:"手机号"` + Nickname string `json:"nickname" dc:"昵称"` + Email string `json:"email" dc:"邮箱"` + Avatar string `json:"avatar" dc:"头像URL"` + Password string `json:"password" dc:"密码"` + Salt string `json:"salt" dc:"密码盐"` + Realname string `json:"realname" dc:"真实姓名"` + Gender int `json:"gender" dc:"性别"` + Birthday string `json:"birthday" dc:"生日"` + School string `json:"school" dc:"学校"` + Grade string `json:"grade" dc:"学历"` + Major string `json:"major" dc:"专业"` + Status int `json:"status" dc:"用户状态"` + VerificationToken string `json:"verificationToken" dc:"邮箱验证令牌"` + EmailVerified int `json:"emailVerified" dc:"邮箱验证状态"` + CreatedAt *gtime.Time `json:"createdAt" dc:"注册时间"` + LastLogin *gtime.Time `json:"lastLogin" dc:"最后登录时间"` + LoginCount int `json:"loginCount" dc:"累计登录次数"` +} + +// UsersStatusInp 更新前台用户表状态 +type UsersStatusInp struct { + Id int `json:"id" v:"required#id不能为空" dc:"id"` + Status int `json:"status" dc:"状态"` +} + +func (in *UsersStatusInp) Filter(ctx context.Context) (err error) { + if in.Id <= 0 { + err = gerror.New("id不能为空") + return + } + + if in.Status <= 0 { + err = gerror.New("状态不能为空") + return + } + + if !validate.InSlice(consts.StatusSlice, in.Status) { + err = gerror.New("状态不正确") + return + } + return +} + +type UsersStatusModel struct{} \ No newline at end of file diff --git a/server/internal/router/api.go b/server/internal/router/api.go index 27b2980..f01c304 100644 --- a/server/internal/router/api.go +++ b/server/internal/router/api.go @@ -10,6 +10,7 @@ import ( "hotgo/internal/consts" "hotgo/internal/controller/api/member" "hotgo/internal/controller/api/pay" + "hotgo/internal/controller/api/users" "hotgo/internal/service" "hotgo/utility/simple" @@ -27,4 +28,10 @@ func Api(ctx context.Context, group *ghttp.RouterGroup) { member.NewV1(), // 管理员 ) }) + + group.Group(simple.RouterPrefix(ctx, consts.AppApi), func(group *ghttp.RouterGroup) { + group.Bind( + users.NewV1(), // 前台用户 + ) + }) } diff --git a/server/internal/router/genrouter/users.go b/server/internal/router/genrouter/users.go new file mode 100644 index 0000000..140c733 --- /dev/null +++ b/server/internal/router/genrouter/users.go @@ -0,0 +1,13 @@ +// Package genrouter +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2025 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +// @AutoGenerate Version 2.17.8 +package genrouter + +import "hotgo/internal/controller/admin/sys" + +func init() { + LoginRequiredRouter = append(LoginRequiredRouter, sys.Users) // 前台用户表 +} \ No newline at end of file diff --git a/server/internal/service/sys.go b/server/internal/service/sys.go index 3a362d9..55da94a 100644 --- a/server/internal/service/sys.go +++ b/server/internal/service/sys.go @@ -433,6 +433,22 @@ type ( // Option 获取测试分类选项 Option(ctx context.Context) (opts []*model.Option, err error) } + ISysUsers interface { + // Model 前台用户表ORM模型 + Model(ctx context.Context, option ...*handler.Option) *gdb.Model + // List 获取前台用户表列表 + List(ctx context.Context, in *sysin.UsersListInp) (list []*sysin.UsersListModel, totalCount int, err error) + // Export 导出前台用户表 + Export(ctx context.Context, in *sysin.UsersListInp) (err error) + // Edit 修改/新增前台用户表 + Edit(ctx context.Context, in *sysin.UsersEditInp) (err error) + // Delete 删除前台用户表 + Delete(ctx context.Context, in *sysin.UsersDeleteInp) (err error) + // View 获取前台用户表指定信息 + View(ctx context.Context, in *sysin.UsersViewInp) (res *sysin.UsersViewModel, err error) + // Status 更新前台用户表状态 + Status(ctx context.Context, in *sysin.UsersStatusInp) (err error) + } ) var ( @@ -459,6 +475,7 @@ var ( localSysServeLog ISysServeLog localSysSmsLog ISysSmsLog localSysTestCategory ISysTestCategory + localSysUsers ISysUsers ) func SysAddons() ISysAddons { @@ -713,3 +730,14 @@ func SysTestCategory() ISysTestCategory { func RegisterSysTestCategory(i ISysTestCategory) { localSysTestCategory = i } + +func SysUsers() ISysUsers { + if localSysUsers == nil { + panic("implement not found for interface ISysUsers, forgot register?") + } + return localSysUsers +} + +func RegisterSysUsers(i ISysUsers) { + localSysUsers = i +} diff --git a/server/storage/data/generate/users_menu.sql b/server/storage/data/generate/users_menu.sql new file mode 100644 index 0000000..949cbbd --- /dev/null +++ b/server/storage/data/generate/users_menu.sql @@ -0,0 +1,65 @@ +-- hotgo自动生成菜单权限SQL 通常情况下只在首次生成代码时自动执行一次 +-- 如需再次执行请先手动删除生成的菜单权限和SQL文件:/Users/guochen/Documents/projects/g031/hotgo/server/storage/data/generate/users_menu.sql +-- Version: 2.17.8 +-- Date: 2025-07-24 20:39:59 +-- Link https://github.com/bufanyun/hotgo + +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +SET AUTOCOMMIT = 0; +START TRANSACTION; + +-- +-- 数据库: `hotgo` +-- + +-- -------------------------------------------------------- + +-- +-- 插入表中的数据 `hg_admin_menu` +-- + + +SET @now := now(); + + +-- 菜单目录 +INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`) VALUES (NULL, '0', '前台用户表', 'users', '/users', 'MenuOutlined', '1', '/users/index', '', '', 'LAYOUT', '1', '', '0', '0', '', '0', '0', '0', '1', '', '0', '', '1', @now, @now); + + +SET @dirId = LAST_INSERT_ID(); + + +-- 菜单页面 +-- 列表 +INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`) VALUES (NULL, @dirId, '前台用户表列表', 'usersIndex', 'index', '', '2', '', '/users/list', '', '/users/index', '1', 'users', '0', '0', '', '0', '1', '0', '2', CONCAT('tr_', @dirId,' '), '10', '', '1', @now, @now); + + +SET @listId = LAST_INSERT_ID(); + +-- 详情 +INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`) VALUES (NULL, @listId, '前台用户表详情', 'usersView', '', '', '3', '', '/users/view', '', '', '1', '', '0', '0', '', '0', '1', '0', '3', CONCAT('tr_', @dirId, ' tr_', @listId,' '), '10', '', '1', @now, @now); + + +-- 菜单按钮 + +-- 编辑 +INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`) VALUES (NULL, @listId, '编辑/新增前台用户表', 'usersEdit', '', '', '3', '', '/users/edit', '', '', '1', '', '0', '0', '', '0', '1', '0', '3', CONCAT('tr_', @dirId, ' tr_', @listId,' '), '20', '', '1', @now, @now); + + +SET @editId = LAST_INSERT_ID(); + + +-- 删除 +INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`) VALUES (NULL, @listId, '删除前台用户表', 'usersDelete', '', '', '3', '', '/users/delete', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', @dirId, ' tr_', @listId,' '), '40', '', '1', @now, @now); + + +-- 更新状态 +INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`) VALUES (NULL, @listId, '修改前台用户表状态', 'usersStatus', '', '', '3', '', '/users/status', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', @dirId, ' tr_', @listId,' '), '50', '', '1', @now, @now); + + + +-- 导出 +INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`) VALUES (NULL, @listId, '导出前台用户表', 'usersExport', '', '', '3', '', '/users/export', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', @dirId, ' tr_', @listId,' '), '70', '', '1', @now, @now); + + +COMMIT; \ No newline at end of file diff --git a/server/utility/jwt/jwt.go b/server/utility/jwt/jwt.go new file mode 100644 index 0000000..37c7e68 --- /dev/null +++ b/server/utility/jwt/jwt.go @@ -0,0 +1,50 @@ +package utility + +import ( + "crypto/sha256" + "encoding/hex" + "time" + + "github.com/golang-jwt/jwt/v5" +) + +var jwtSecret = []byte("your_secret_key") // 建议放到配置文件 + +type Claims struct { + UserID int64 `json:"user_id"` + jwt.RegisteredClaims +} + +// GenerateToken 生成 JWT Token +func GenerateToken(userID int64) (string, error) { + claims := Claims{ + UserID: userID, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), + IssuedAt: jwt.NewNumericDate(time.Now()), + }, + } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + return token.SignedString(jwtSecret) +} + +// ParseToken 解析 JWT Token +func ParseToken(tokenString string) (*Claims, error) { + token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { + return jwtSecret, nil + }) + if err != nil { + return nil, err + } + if claims, ok := token.Claims.(*Claims); ok && token.Valid { + return claims, nil + } + return nil, err +} + +// HashPassword 对密码+salt做sha256加密 +func HashPassword(password, salt string) string { + h := sha256.New() + h.Write([]byte(password + salt)) + return hex.EncodeToString(h.Sum(nil)) +} diff --git a/web/src/api/users/index.ts b/web/src/api/users/index.ts new file mode 100644 index 0000000..f8df107 --- /dev/null +++ b/web/src/api/users/index.ts @@ -0,0 +1,51 @@ +import { http, jumpExport } from '@/utils/http/axios'; + +// 获取前台用户表列表 +export function List(params) { + return http.request({ + url: '/users/list', + method: 'get', + params, + }); +} + +// 删除/批量删除前台用户表 +export function Delete(params) { + return http.request({ + url: '/users/delete', + method: 'POST', + params, + }); +} + +// 添加/编辑前台用户表 +export function Edit(params) { + return http.request({ + url: '/users/edit', + method: 'POST', + params, + }); +} + +// 修改前台用户表状态 +export function Status(params) { + return http.request({ + url: '/users/status', + method: 'POST', + params, + }); +} + +// 获取前台用户表指定详情 +export function View(params) { + return http.request({ + url: '/users/view', + method: 'GET', + params, + }); +} + +// 导出前台用户表 +export function Export(params) { + jumpExport('/users/export', params); +} \ No newline at end of file diff --git a/web/src/views/users/edit.vue b/web/src/views/users/edit.vue new file mode 100644 index 0000000..5367fe0 --- /dev/null +++ b/web/src/views/users/edit.vue @@ -0,0 +1,181 @@ + + + + + \ No newline at end of file diff --git a/web/src/views/users/index.vue b/web/src/views/users/index.vue new file mode 100644 index 0000000..7bd9cbd --- /dev/null +++ b/web/src/views/users/index.vue @@ -0,0 +1,225 @@ + + + + + \ No newline at end of file diff --git a/web/src/views/users/model.ts b/web/src/views/users/model.ts new file mode 100644 index 0000000..0f03310 --- /dev/null +++ b/web/src/views/users/model.ts @@ -0,0 +1,228 @@ +import { h, ref } from 'vue'; +import { cloneDeep } from 'lodash-es'; +import { FormSchema } from '@/components/Form'; +import { defRangeShortcuts } from '@/utils/dateUtil'; +import { validate } from '@/utils/validateUtil'; +import { renderImage, renderOptionTag } from '@/utils'; +import { useDictStore } from '@/store/modules/dict'; + +const dict = useDictStore(); + +export class State { + public id = 0; // id + public phone = ''; // 手机号 + public nickname = ''; // 昵称 + public email = ''; // 邮箱 + public avatar = ''; // 头像URL + public introduce = ''; // 个人简介 + public password = ''; // 密码 + public salt = ''; // 密码盐 + public realname = ''; // 真实姓名 + public gender = 0; // 性别 + public birthday = ''; // 生日 + public school = ''; // 学校 + public grade = ''; // 学历 + public major = ''; // 专业 + public status = 1; // 用户状态 + public verificationToken = ''; // 邮箱验证令牌 + public emailVerified = 0; // 邮箱验证状态 + public createdAt = ''; // 注册时间 + public lastLogin = ''; // 最后登录时间 + public loginCount = 0; // 累计登录次数 + + constructor(state?: Partial) { + if (state) { + Object.assign(this, state); + } + } +} + +export function newState(state: State | Record | null): State { + if (state !== null) { + if (state instanceof State) { + return cloneDeep(state); + } + return new State(state); + } + return new State(); +} + +// 表单验证规则 +export const rules = { + email: { + required: false, + trigger: ['blur', 'input'], + type: 'string', + validator: validate.email, + }, +}; + +// 表格搜索表单 +export const schemas = ref([ + { + field: 'id', + component: 'NInputNumber', + label: 'id', + componentProps: { + placeholder: '请输入id', + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'status', + component: 'NSelect', + label: '用户状态', + defaultValue: null, + componentProps: { + placeholder: '请选择用户状态', + options: dict.getOption('sys_normal_disable'), + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'createdAt', + component: 'NDatePicker', + label: '注册时间', + componentProps: { + type: 'datetimerange', + clearable: true, + shortcuts: defRangeShortcuts(), + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, +]); + +// 表格列 +export const columns = [ + { + title: 'id', + key: 'id', + align: 'left', + width: -1, + }, + { + title: '手机号', + key: 'phone', + align: 'left', + width: -1, + }, + { + title: '昵称', + key: 'nickname', + align: 'left', + width: -1, + }, + { + title: '邮箱', + key: 'email', + align: 'left', + width: -1, + }, + { + title: '头像URL', + key: 'avatar', + align: 'left', + width: -1, + render(row: State) { + return renderImage(row.avatar); + }, + }, + { + title: '密码', + key: 'password', + align: 'left', + width: -1, + }, + { + title: '密码盐', + key: 'salt', + align: 'left', + width: -1, + }, + { + title: '真实姓名', + key: 'realname', + align: 'left', + width: -1, + }, + { + title: '性别', + key: 'gender', + align: 'left', + width: -1, + }, + { + title: '生日', + key: 'birthday', + align: 'left', + width: -1, + }, + { + title: '学校', + key: 'school', + align: 'left', + width: -1, + }, + { + title: '学历', + key: 'grade', + align: 'left', + width: -1, + }, + { + title: '专业', + key: 'major', + align: 'left', + width: -1, + }, + { + title: '用户状态', + key: 'status', + align: 'left', + width: -1, + render(row: State) { + return renderOptionTag('sys_normal_disable', row.status); + }, + }, + { + title: '邮箱验证令牌', + key: 'verificationToken', + align: 'left', + width: -1, + }, + { + title: '邮箱验证状态', + key: 'emailVerified', + align: 'left', + width: -1, + }, + { + title: '注册时间', + key: 'createdAt', + align: 'left', + width: -1, + }, + { + title: '最后登录时间', + key: 'lastLogin', + align: 'left', + width: -1, + }, + { + title: '累计登录次数', + key: 'loginCount', + align: 'left', + width: -1, + }, +]; + +// 加载字典数据选项 +export function loadOptions() { + dict.loadOptions(['sys_normal_disable']); +} \ No newline at end of file diff --git a/web/src/views/users/view.vue b/web/src/views/users/view.vue new file mode 100644 index 0000000..2788854 --- /dev/null +++ b/web/src/views/users/view.vue @@ -0,0 +1,132 @@ + + + + + \ No newline at end of file