更新
This commit is contained in:
@@ -27,6 +27,7 @@ type Class struct {
|
||||
GraduateYear int `gorm:"index:idx_class_unique,priority:5" json:"graduate_year"` // 毕业年份
|
||||
AdminUserID uint `gorm:"index;not null" json:"admin_user_id"`
|
||||
GraduationPhoto string `gorm:"size:512" json:"graduation_photo"` // 毕业照URL(每个班级唯一一张)
|
||||
RowCount int `gorm:"default:1" json:"row_count"` // 毕业照排数
|
||||
StudentNamesJSON string `gorm:"type:text" json:"-"` // 班级所有同学名字 JSON 数组
|
||||
TeacherNamesJSON string `gorm:"type:text" json:"-"` // 老师名字 JSON 数组
|
||||
Status ClassStatus `gorm:"size:16;default:pending;index" json:"status"`
|
||||
@@ -114,3 +115,18 @@ type Invitation struct {
|
||||
Status string `gorm:"size:16;default:active" json:"status"` // active / disabled
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// ClassRoster 班级毕业照排位花名册(预录入人员,每个同学/老师单独一行存储,支持后续注册后绑定具体账户)
|
||||
type ClassRoster struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
ClassID uint `gorm:"index:idx_class_roster;not null" json:"class_id"`
|
||||
RowNo int `gorm:"index:idx_class_roster;not null" json:"row_no"` // 排号(从1开始,第1排为前排)
|
||||
ColNo int `gorm:"not null" json:"col_no"` // 排内序号(从1开始,从左往右)
|
||||
Name string `gorm:"size:64;not null;index" json:"name"` // 姓名
|
||||
Role string `gorm:"size:16;default:student;index" json:"role"` // 身份:student(同学) / teacher(老师)
|
||||
UserID uint `gorm:"index;default:0" json:"user_id"` // 绑定的系统用户ID(0表示未注册/未绑定账户)
|
||||
IsClaimed bool `gorm:"default:false;index" json:"is_claimed"` // 是否已被具体账户绑定认领
|
||||
ClaimedAt *time.Time `json:"claimed_at,omitempty"` // 绑定认领时间
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
+157
-17
@@ -1,17 +1,23 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RegionLevel 地区层级
|
||||
type RegionLevel string
|
||||
|
||||
const (
|
||||
LevelCountry RegionLevel = "country"
|
||||
LevelProvince RegionLevel = "province" // 省/州/直辖市
|
||||
LevelCounty RegionLevel = "county" // 县/区(最低级)
|
||||
LevelProvince RegionLevel = "province" // 省/州/直辖市/自治区
|
||||
LevelCity RegionLevel = "city" // 市/地区/自治州/盟
|
||||
LevelCounty RegionLevel = "county" // 县/区/旗/县级市
|
||||
)
|
||||
|
||||
// Region 地区表,自关联三级:国家 -> 省/州 -> 县/区
|
||||
// Region 地区表,自关联多级:国家 -> 省/州 -> 市 -> 县/区
|
||||
type Region struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"size:128;not null;index" json:"name"`
|
||||
@@ -23,19 +29,53 @@ type Region struct {
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// RegionTree 地区树节点
|
||||
type RegionTree struct {
|
||||
Region
|
||||
Children []*RegionTree `json:"children,omitempty"`
|
||||
}
|
||||
|
||||
// SchoolStatus 学校审核状态
|
||||
type SchoolStatus string
|
||||
|
||||
const (
|
||||
SchoolStatusPending SchoolStatus = "pending" // 待审核
|
||||
SchoolStatusActive SchoolStatus = "active" // 正常
|
||||
SchoolStatusRejected SchoolStatus = "rejected" // 已打回
|
||||
)
|
||||
|
||||
// SchoolType 学校类型
|
||||
type SchoolType string
|
||||
|
||||
const (
|
||||
SchoolUniversity SchoolType = "university" // 大学(含学院层级)
|
||||
SchoolJuniorHigh SchoolType = "junior_high" // 初中
|
||||
SchoolSeniorHigh SchoolType = "senior_high" // 高中
|
||||
SchoolPrimary SchoolType = "primary" // 小学
|
||||
SchoolKindergarten SchoolType = "kindergarten" // 幼儿园
|
||||
SchoolVocational SchoolType = "vocational" // 职业学校/中专(也可设学院)
|
||||
SchoolUniversity SchoolType = "university" // 大学(含学院层级)
|
||||
SchoolJuniorHigh SchoolType = "junior_high" // 初中
|
||||
SchoolSeniorHigh SchoolType = "senior_high" // 高中
|
||||
SchoolPrimary SchoolType = "primary" // 小学
|
||||
SchoolKindergarten SchoolType = "kindergarten" // 幼儿园
|
||||
SchoolVocational SchoolType = "vocational" // 职业学校/中专(也可设学院)
|
||||
SchoolOther SchoolType = "other"
|
||||
)
|
||||
|
||||
// SchoolTypeOrder 学校类型的展示与主类型优先级(数值越小优先级越高)
|
||||
// 一所学校可同时涵盖多个学段(如完全中学 = 初中 + 高中),
|
||||
// 主类型 school_type 取其中优先级最高的一个,用于兼容旧逻辑与统计。
|
||||
var SchoolTypeOrder = map[SchoolType]int{
|
||||
SchoolUniversity: 0,
|
||||
SchoolVocational: 1,
|
||||
SchoolSeniorHigh: 2,
|
||||
SchoolJuniorHigh: 3,
|
||||
SchoolPrimary: 4,
|
||||
SchoolKindergarten: 5,
|
||||
SchoolOther: 6,
|
||||
}
|
||||
|
||||
// IsValid 是否为已定义的学校类型
|
||||
func (s SchoolType) IsValid() bool {
|
||||
_, ok := SchoolTypeOrder[s]
|
||||
return ok
|
||||
}
|
||||
|
||||
// HasCollege 该类型学校是否有学院层级
|
||||
func (s SchoolType) HasCollege() bool {
|
||||
return s == SchoolUniversity || s == SchoolVocational
|
||||
@@ -43,14 +83,114 @@ func (s SchoolType) HasCollege() bool {
|
||||
|
||||
// School 学校
|
||||
type School struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
RegionID uint `gorm:"index;not null" json:"region_id"` // 必须挂在 county 级地区下
|
||||
Name string `gorm:"size:128;not null;index" json:"name"`
|
||||
NameEn string `gorm:"size:128" json:"name_en"`
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
RegionID uint `gorm:"index;not null" json:"region_id"` // 挂在 county 或 city 级地区下
|
||||
Name string `gorm:"size:128;not null;index" json:"name"`
|
||||
NameEn string `gorm:"size:128" json:"name_en"`
|
||||
// SchoolType 主类型:SchoolTypes 中优先级最高的一个,保留用于兼容旧数据与统计
|
||||
SchoolType SchoolType `gorm:"size:24;index;not null" json:"school_type"`
|
||||
Address string `gorm:"size:255" json:"address"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
// SchoolTypes 全部涵盖的学段,英文逗号分隔且无空格(如 "junior_high,senior_high"),
|
||||
// 便于使用 MySQL FIND_IN_SET 检索
|
||||
SchoolTypes string `gorm:"size:128;index" json:"school_types_raw"`
|
||||
Address string `gorm:"size:255" json:"address"`
|
||||
Status SchoolStatus `gorm:"size:16;default:active;index" json:"status"` // pending, active, rejected
|
||||
RejectReason string `gorm:"size:255" json:"reject_reason"`
|
||||
CreatedBy uint `gorm:"default:0;index" json:"created_by"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
// 关联字段(非数据库持久化)
|
||||
RegionPath string `gorm:"-" json:"region_path,omitempty"`
|
||||
}
|
||||
|
||||
// TypeList 返回学校涵盖的全部学段;旧数据未写 school_types 时回退到主类型
|
||||
func (s *School) TypeList() []SchoolType {
|
||||
list := ParseSchoolTypes(s.SchoolTypes)
|
||||
if len(list) == 0 && s.SchoolType != "" {
|
||||
return []SchoolType{s.SchoolType}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// HasType 学校是否涵盖指定学段
|
||||
func (s *School) HasType(t SchoolType) bool {
|
||||
for _, item := range s.TypeList() {
|
||||
if item == t {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HasCollegeType 学校是否存在支持学院层级的学段(大学/职业院校)
|
||||
func (s *School) HasCollegeType() bool {
|
||||
for _, item := range s.TypeList() {
|
||||
if item.HasCollege() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RequiresCollege 建班时是否必须选择学院:仅当所有学段都支持学院层级
|
||||
// (例如"大学 + 高中"的学校,走高中学段建班时无需选学院)
|
||||
func (s *School) RequiresCollege() bool {
|
||||
list := s.TypeList()
|
||||
if len(list) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, item := range list {
|
||||
if !item.HasCollege() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// MarshalJSON 输出时附带 school_types 数组,前端可直接使用
|
||||
func (s School) MarshalJSON() ([]byte, error) {
|
||||
type schoolAlias School
|
||||
list := (&s).TypeList()
|
||||
strs := make([]string, 0, len(list))
|
||||
for _, item := range list {
|
||||
strs = append(strs, string(item))
|
||||
}
|
||||
return json.Marshal(struct {
|
||||
schoolAlias
|
||||
SchoolTypeList []string `json:"school_types"`
|
||||
}{schoolAlias(s), strs})
|
||||
}
|
||||
|
||||
// ParseSchoolTypes 解析逗号分隔的学校类型,去重并按优先级排序
|
||||
func ParseSchoolTypes(raw string) []SchoolType {
|
||||
return NormalizeSchoolTypes(strings.Split(raw, ","))
|
||||
}
|
||||
|
||||
// NormalizeSchoolTypes 校验、去重并按优先级排序学校类型集合
|
||||
func NormalizeSchoolTypes(items []string) []SchoolType {
|
||||
seen := map[SchoolType]bool{}
|
||||
res := make([]SchoolType, 0, len(items))
|
||||
for _, item := range items {
|
||||
t := SchoolType(strings.TrimSpace(item))
|
||||
if t == "" || !t.IsValid() || seen[t] {
|
||||
continue
|
||||
}
|
||||
seen[t] = true
|
||||
res = append(res, t)
|
||||
}
|
||||
sort.SliceStable(res, func(i, j int) bool {
|
||||
return SchoolTypeOrder[res[i]] < SchoolTypeOrder[res[j]]
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
// JoinSchoolTypes 将类型集合拼为可供 FIND_IN_SET 使用的字符串
|
||||
func JoinSchoolTypes(items []SchoolType) string {
|
||||
strs := make([]string, 0, len(items))
|
||||
for _, item := range items {
|
||||
strs = append(strs, string(item))
|
||||
}
|
||||
return strings.Join(strs, ",")
|
||||
}
|
||||
|
||||
// College 学院(仅大学/职业学校有)
|
||||
|
||||
+21
-14
@@ -6,20 +6,27 @@ import (
|
||||
|
||||
// User 用户表
|
||||
type User struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Username string `gorm:"size:64;uniqueIndex;not null" json:"username"`
|
||||
PasswordHash string `gorm:"size:32;not null" json:"-"` // MD5 32字符
|
||||
Salt string `gorm:"size:32;not null" json:"-"` // 密码盐
|
||||
Email string `gorm:"size:128;index" json:"email"` // 用于找回密码
|
||||
Nickname string `gorm:"size:64" json:"nickname"`
|
||||
Avatar string `gorm:"size:512" json:"avatar"`
|
||||
Bio string `gorm:"size:512" json:"bio"`
|
||||
Role string `gorm:"size:16;default:user;index" json:"role"` // user / admin
|
||||
Status string `gorm:"size:16;default:active;index" json:"status"` // active / banned
|
||||
BanReason string `gorm:"size:255" json:"ban_reason"`
|
||||
BannedAt *time.Time `json:"banned_at,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Username string `gorm:"size:64;uniqueIndex;not null" json:"username"`
|
||||
PasswordHash string `gorm:"size:32;not null" json:"-"` // MD5 32字符
|
||||
Salt string `gorm:"size:32;not null" json:"-"` // 密码盐
|
||||
Email string `gorm:"size:128;index" json:"email"` // 用于找回密码
|
||||
Phone string `gorm:"size:32" json:"phone"` // 联系电话
|
||||
RealName string `gorm:"size:64" json:"real_name"` // 真实姓名
|
||||
Gender string `gorm:"size:16" json:"gender"` // 性别:男 / 女
|
||||
Nation string `gorm:"size:32" json:"nation"` // 民族
|
||||
PoliticalStatus string `gorm:"size:32" json:"political_status"` // 政治身份/政治面貌
|
||||
IdCard string `gorm:"size:32" json:"id_card"` // 身份证号
|
||||
Address string `gorm:"size:255" json:"address"` // 家庭住址
|
||||
Nickname string `gorm:"size:64" json:"nickname"`
|
||||
Avatar string `gorm:"size:512" json:"avatar"`
|
||||
Bio string `gorm:"size:512" json:"bio"`
|
||||
Role string `gorm:"size:16;default:user;index" json:"role"` // user / admin
|
||||
Status string `gorm:"size:16;default:active;index" json:"status"` // active / banned
|
||||
BanReason string `gorm:"size:255" json:"ban_reason"`
|
||||
BannedAt *time.Time `json:"banned_at,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// ContactType 联系方式类型
|
||||
|
||||
Reference in New Issue
Block a user