0904
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
// Copyright 2026 tamakyi. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/tamakyi/TamaBox/internal/dbutil"
|
||||
)
|
||||
|
||||
var VoteFormFields VoteFormFieldsStore
|
||||
|
||||
// CustomFieldMarker 参与端"其他/自定义选项"的保留标记值(radio/select/checkbox 字段)。
|
||||
// 选中该值时,后端从 field_{id}_custom 读取参与者填写的文本;预设选项不允许使用该保留值。
|
||||
const CustomFieldMarker = "__tb_custom_option__"
|
||||
|
||||
// 表单字段类型(type=form 时使用)
|
||||
const (
|
||||
VoteFormFieldTypeText = "text" // 单行文本
|
||||
VoteFormFieldTypeTextarea = "textarea" // 多行文本
|
||||
VoteFormFieldTypeSelect = "select" // 下拉单选
|
||||
VoteFormFieldTypeRadio = "radio" // 单选
|
||||
VoteFormFieldTypeCheckbox = "checkbox" // 多选
|
||||
VoteFormFieldTypeNumber = "number" // 数字
|
||||
VoteFormFieldTypeRating = "rating" // 评分
|
||||
VoteFormFieldTypeRank = "rank" // 排序(对预置选项排 1..N)
|
||||
)
|
||||
|
||||
type VoteFormFieldsStore interface {
|
||||
Create(ctx context.Context, opts CreateVoteFormFieldOptions) (*VoteFormField, error)
|
||||
BulkCreate(ctx context.Context, voteID uint, fields []CreateVoteFormFieldOptions) ([]*VoteFormField, error)
|
||||
ListByVoteID(ctx context.Context, voteID uint) ([]*VoteFormField, error)
|
||||
ReplaceByVoteID(ctx context.Context, voteID uint, fields []CreateVoteFormFieldOptions) error
|
||||
CountByVoteID(ctx context.Context, voteID uint) (int64, error)
|
||||
DeleteByVoteID(ctx context.Context, voteID uint) error
|
||||
DeleteByID(ctx context.Context, id uint) error
|
||||
}
|
||||
|
||||
func NewVoteFormFieldsStore(db *gorm.DB) VoteFormFieldsStore {
|
||||
return &voteFormFields{db}
|
||||
}
|
||||
|
||||
type voteFormFields struct {
|
||||
*gorm.DB
|
||||
}
|
||||
|
||||
// VoteFormField 表单字段定义表:结构化表单(type=form)的字段配置。
|
||||
type VoteFormField struct {
|
||||
dbutil.Model
|
||||
VoteID uint `gorm:"index:idx_form_field_vote" json:"vote_id"`
|
||||
FieldType string `gorm:"size:20" json:"field_type"`
|
||||
Label string `gorm:"size:100" json:"label"`
|
||||
Required bool `gorm:"default:false" json:"required"`
|
||||
MaxLength int `gorm:"default:0" json:"max_length"` // 0 = 不限制(字数限制)
|
||||
Options string `gorm:"type:text" json:"options"` // JSON 数组(select/radio/checkbox 用)
|
||||
SortOrder int `gorm:"default:0" json:"sort_order"`
|
||||
AllowCustom bool `gorm:"default:false" json:"allow_custom"` // 允许参与者补充自定义选项(radio/select/checkbox)
|
||||
}
|
||||
|
||||
type CreateVoteFormFieldOptions struct {
|
||||
VoteID uint
|
||||
FieldType string
|
||||
Label string
|
||||
Required bool
|
||||
MaxLength int
|
||||
Options string
|
||||
SortOrder int
|
||||
AllowCustom bool
|
||||
}
|
||||
|
||||
func (db *voteFormFields) Create(ctx context.Context, opts CreateVoteFormFieldOptions) (*VoteFormField, error) {
|
||||
field := &VoteFormField{
|
||||
VoteID: opts.VoteID,
|
||||
FieldType: opts.FieldType,
|
||||
Label: opts.Label,
|
||||
Required: opts.Required,
|
||||
MaxLength: opts.MaxLength,
|
||||
Options: opts.Options,
|
||||
SortOrder: opts.SortOrder,
|
||||
AllowCustom: opts.AllowCustom,
|
||||
}
|
||||
if err := db.WithContext(ctx).Create(field).Error; err != nil {
|
||||
return nil, errors.Wrap(err, "create vote form field")
|
||||
}
|
||||
return field, nil
|
||||
}
|
||||
|
||||
// BulkCreate 批量创建表单字段(箱主一次提交全部字段配置)。
|
||||
func (db *voteFormFields) BulkCreate(ctx context.Context, voteID uint, fields []CreateVoteFormFieldOptions) ([]*VoteFormField, error) {
|
||||
formFields := make([]*VoteFormField, 0, len(fields))
|
||||
for _, f := range fields {
|
||||
formFields = append(formFields, &VoteFormField{
|
||||
VoteID: voteID,
|
||||
FieldType: f.FieldType,
|
||||
Label: f.Label,
|
||||
Required: f.Required,
|
||||
MaxLength: f.MaxLength,
|
||||
Options: f.Options,
|
||||
SortOrder: f.SortOrder,
|
||||
AllowCustom: f.AllowCustom,
|
||||
})
|
||||
}
|
||||
if len(formFields) == 0 {
|
||||
return formFields, nil
|
||||
}
|
||||
if err := db.WithContext(ctx).Create(&formFields).Error; err != nil {
|
||||
return nil, errors.Wrap(err, "bulk create vote form fields")
|
||||
}
|
||||
return formFields, nil
|
||||
}
|
||||
|
||||
// ListByVoteID 获取某投票的全部表单字段(按 SortOrder 升序)。
|
||||
func (db *voteFormFields) ListByVoteID(ctx context.Context, voteID uint) ([]*VoteFormField, error) {
|
||||
var fields []*VoteFormField
|
||||
if err := db.WithContext(ctx).
|
||||
Where("vote_id = ?", voteID).
|
||||
Order("sort_order ASC, id ASC").
|
||||
Find(&fields).Error; err != nil {
|
||||
return nil, errors.Wrap(err, "list vote form fields by vote id")
|
||||
}
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
// CountByVoteID 统计某投票的表单字段数量(发布前完整性校验用)。
|
||||
func (db *voteFormFields) CountByVoteID(ctx context.Context, voteID uint) (int64, error) {
|
||||
var count int64
|
||||
if err := db.WithContext(ctx).Model(&VoteFormField{}).Where("vote_id = ?", voteID).Count(&count).Error; err != nil {
|
||||
return 0, errors.Wrap(err, "count vote form fields by vote id")
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// ReplaceByVoteID 在单个事务内替换投票的全部表单字段(先删后建),保证原子性。
|
||||
// 仅在草稿状态编辑时调用。
|
||||
func (db *voteFormFields) ReplaceByVoteID(ctx context.Context, voteID uint, fields []CreateVoteFormFieldOptions) error {
|
||||
return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("vote_id = ?", voteID).Delete(&VoteFormField{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if len(fields) == 0 {
|
||||
return nil
|
||||
}
|
||||
formFields := make([]*VoteFormField, 0, len(fields))
|
||||
for _, f := range fields {
|
||||
formFields = append(formFields, &VoteFormField{
|
||||
VoteID: voteID,
|
||||
FieldType: f.FieldType,
|
||||
Label: f.Label,
|
||||
Required: f.Required,
|
||||
MaxLength: f.MaxLength,
|
||||
Options: f.Options,
|
||||
SortOrder: f.SortOrder,
|
||||
AllowCustom: f.AllowCustom,
|
||||
})
|
||||
}
|
||||
return tx.Create(&formFields).Error
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteByVoteID 删除投票时级联清理全部表单字段。
|
||||
func (db *voteFormFields) DeleteByVoteID(ctx context.Context, voteID uint) error {
|
||||
if err := db.WithContext(ctx).Where("vote_id = ?", voteID).Delete(&VoteFormField{}).Error; err != nil {
|
||||
return errors.Wrap(err, "delete vote form fields by vote id")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *voteFormFields) DeleteByID(ctx context.Context, id uint) error {
|
||||
res := db.WithContext(ctx).Delete(&VoteFormField{}, id)
|
||||
if res.Error != nil {
|
||||
return errors.Wrap(res.Error, "delete vote form field")
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return ErrVoteFormFieldNotExist
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var ErrVoteFormFieldNotExist = errors.New("投票表单字段不存在")
|
||||
Reference in New Issue
Block a user