This commit is contained in:
2026-09-03 17:55:42 +08:00
parent 7c38e84e30
commit c80a3d4a2d
35 changed files with 6795 additions and 597 deletions
+157 -17
View File
@@ -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 学院(仅大学/职业学校有)