SongZihuan 4 місяців тому
батько
коміт
fb2a9acfc4

+ 577 - 0
src/excelio/readerfile.go

@@ -0,0 +1,577 @@
+package excelio
+
+import (
+	"database/sql"
+	"fmt"
+	"github.com/SuperH-0630/hdangan/src/model"
+	"github.com/SuperH-0630/hdangan/src/runtime"
+	"github.com/xuri/excelize/v2"
+	"io"
+	"strconv"
+	"strings"
+	"time"
+)
+
+var InputTitle = map[model.FileSetType][]string{
+	model.QianRu:               {"档案ID", "姓名", "曾用名", "身份证", "性别", "出生日期", "是否同上", "备注", "类型", "旧地址", "新地址", "办理时间", "备考", "材料页数", "材料"},
+	model.ChuSheng:             {"档案ID", "姓名", "曾用名", "身份证", "性别", "出生日期", "是否同上", "备注", "类型", "地址", "办理时间", "备考", "材料页数", "材料"},
+	model.QianChu:              {"档案ID", "姓名", "曾用名", "身份证", "性别", "出生日期", "是否同上", "备注", "类型", "新地址", "办理时间", "备考", "材料页数", "材料"},
+	model.SiWang:               {"档案ID", "姓名", "曾用名", "身份证", "性别", "出生日期", "是否同上", "备注", "类型", "地址", "办理时间", "备考", "材料页数", "材料"},
+	model.BianGeng:             {"档案ID", "姓名", "曾用名", "身份证", "性别", "出生日期", "是否同上", "备注", "类型", "地址", "办理时间", "备考", "材料页数", "材料"},
+	model.SuoNeiYiJu:           {"档案ID", "姓名", "曾用名", "身份证", "性别", "出生日期", "是否同上", "备注", "类型", "地址", "办理时间", "备考", "材料页数", "材料"},
+	model.SuoJianYiJu:          {"档案ID", "姓名", "曾用名", "身份证", "性别", "出生日期", "是否同上", "备注", "类型", "地址", "办理时间", "备考", "材料页数", "材料"},
+	model.NongZiZhuanFei:       {"档案ID", "姓名", "曾用名", "身份证", "性别", "出生日期", "是否同上", "备注", "类型", "地址", "办理时间", "备考", "材料页数", "材料"},
+	model.YiZhanShiQianYiZheng: {"档案ID", "姓名", "曾用名", "身份证", "性别", "出生日期", "是否同上", "备注", "类型", "地址", "办理时间", "备考", "材料页数", "材料"},
+}
+
+var Header = []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S"}
+
+var BadTitle = fmt.Errorf("表格首行的标题错误")
+
+const (
+	successAdd = iota
+	successUpdate
+	fail
+)
+
+func ReadFile(rt runtime.RunTime, fst model.FileSetType, reader io.ReadCloser) (int64, int64, int64, error) {
+	f, err := excelize.OpenReader(reader)
+	if err != nil {
+		return 0, 0, 0, err
+	}
+
+	tit, ok := InputTitle[fst]
+	if !ok {
+		return 0, 0, 0, err
+	}
+
+	defer func() {
+		_ = f.Close()
+	}()
+
+	slst := f.GetSheetList()
+	if len(slst) != 1 {
+		return 0, 0, 0, fmt.Errorf("sheet 过多")
+	}
+
+	sheet := slst[0]
+
+	timeStyleID, err := f.NewStyle(&excelize.Style{})
+	if err != nil {
+		return 0, 0, 0, err
+	}
+
+	for i, t := range tit {
+		if strings.Contains(t, "日期") || strings.Contains(t, "时间") {
+			err = f.SetColStyle(sheet, Header[i], timeStyleID)
+			if err != nil {
+				return 0, 0, 0, err
+			}
+		}
+	}
+
+	rows, err := f.GetRows(sheet)
+	if err != nil {
+		return 0, 0, 0, err
+	}
+
+	var sa, su, fu int64
+
+	for i, j := range rows {
+		if i == 0 {
+			if !checkTitle(j, tit) {
+				return 0, 0, 0, BadTitle
+			}
+		} else {
+			t := make([]string, 0, len(tit))
+			t = append(t, j...)
+			if len(t) < len(tit) {
+				t = append(t, make([]string, len(tit)-len(t))...)
+			}
+
+			s := makeFile(rt, fst, t, tit)
+			if s == successAdd {
+				sa += 1
+			} else if s == successUpdate {
+				su += 1
+			} else {
+				fu += 1
+			}
+		}
+	}
+
+	return sa, su, fu, nil
+}
+
+func checkTitle(t []string, tit []string) bool {
+	for i, s := range t {
+		if tit[i] != s {
+			return false
+		}
+	}
+	return true
+}
+
+func makeFile(rt runtime.RunTime, fst model.FileSetType, t []string, tit []string) int {
+	if t[0] == "" {
+		file, err := makeNewFile(rt, fst, t, tit)
+		if err != nil {
+			return fail
+		}
+
+		err = model.CreateFile(rt, file.GetFile().FileSetType, file)
+		if err != nil {
+			return fail
+		}
+		return successAdd
+	} else {
+		fileID, err := strconv.ParseInt(t[0], 10, 64)
+		if err != nil {
+			return fail
+		}
+
+		file, err := makeUpdateFile(rt, fileID, fst, t, tit)
+		if err != nil {
+			return fail
+		}
+
+		err = model.SaveFile(rt, file)
+		if err != nil {
+			return fail
+		}
+		return successUpdate
+	}
+}
+
+func makeNewFile(rt runtime.RunTime, fst model.FileSetType, t []string, tit []string) (model.File, error) {
+	name := t[1]
+	if len(name) == 0 {
+		return nil, fmt.Errorf("must has name")
+	}
+
+	oldName := sql.NullString{
+		Valid:  len(t[2]) != 0,
+		String: t[2],
+	}
+
+	idcard := sql.NullString{
+		Valid:  len(t[3]) != 0,
+		String: t[3],
+	}
+
+	sex := t[4] == "女性" || t[4] == "女" || t[4] == "W" || t[4] == "WOMAN"
+
+	birthday := time.Now()
+	if len(t[5]) != 0 {
+		var err error
+		birthday, err = timeReader(time.Now(), t[5])
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	sameAbove := t[6] == "是" || t[6] == "是的" || t[6] == "同上" || t[6] == "T" || t[6] == "True"
+
+	comment := sql.NullString{
+		Valid:  len(t[7]) != 0,
+		String: t[7],
+	}
+
+	fileTimeIndex := 0
+	for i, j := range tit {
+		if j == "办理时间" {
+			fileTimeIndex = i
+			break
+		}
+	}
+
+	fileTime := time.Now()
+	if len(t[fileTimeIndex]) != 0 {
+		var err error
+		fileTime, err = timeReader(time.Now(), t[fileTimeIndex])
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	beiKao := sql.NullString{
+		Valid:  len(t[fileTimeIndex+1]) != 0,
+		String: t[fileTimeIndex+1],
+	}
+
+	page, err := strconv.ParseInt(t[fileTimeIndex+2], 10, 64)
+	if err != nil {
+		return nil, err
+	}
+
+	material := sql.NullString{
+		Valid:  len(t[fileTimeIndex+3]) != 0,
+		String: t[fileTimeIndex+3],
+	}
+
+	var res model.File
+
+	abs := model.FileAbs{
+		Name:     name,
+		OldName:  oldName,
+		IDCard:   idcard,
+		IsMan:    sex,
+		Birthday: birthday,
+		Comment:  comment,
+
+		SameAsAbove: sameAbove,
+		PeopleCount: 1,
+
+		Time: fileTime,
+
+		PageCount: page,
+
+		BeiKao:   beiKao,
+		Material: material,
+	}
+
+	switch fst {
+	case model.QianRu:
+		fileType := t[8]
+		if len(fileType) == 0 {
+			return nil, fmt.Errorf("must has file type")
+		}
+
+		oldLoc := t[9]
+		if len(oldLoc) == 0 {
+			return nil, fmt.Errorf("must has old location")
+		}
+
+		newLoc := t[10]
+		if len(newLoc) == 0 {
+			return nil, fmt.Errorf("must has new location")
+		}
+
+		res = &model.FileQianRu{
+			FileAbs:     abs,
+			Type:        fileType,
+			OldLocation: oldLoc,
+			NewLocation: newLoc,
+		}
+	case model.ChuSheng:
+		fileType := t[8]
+		if len(fileType) == 0 {
+			return nil, fmt.Errorf("must has file type")
+		}
+
+		loc := t[9]
+		if len(loc) == 0 {
+			return nil, fmt.Errorf("must has location")
+		}
+
+		res = &model.FileChuSheng{
+			FileAbs:     abs,
+			Type:        fileType,
+			NewLocation: loc,
+		}
+	case model.SiWang:
+		fileType := t[8]
+		if len(fileType) == 0 {
+			return nil, fmt.Errorf("must has file type")
+		}
+
+		loc := t[9]
+		if len(loc) == 0 {
+			return nil, fmt.Errorf("must has new location")
+		}
+
+		res = &model.FileSiWang{
+			FileAbs:  abs,
+			Type:     fileType,
+			Location: loc,
+		}
+	case model.BianGeng:
+		fileType := t[8]
+		if len(fileType) == 0 {
+			return nil, fmt.Errorf("must has file type")
+		}
+
+		loc := t[9]
+		if len(loc) == 0 {
+			return nil, fmt.Errorf("must has location")
+		}
+
+		res = &model.FileBianGeng{
+			FileAbs:  abs,
+			Type:     fileType,
+			Location: loc,
+		}
+	case model.SuoNeiYiJu:
+		fileType := t[8]
+		if len(fileType) == 0 {
+			return nil, fmt.Errorf("must has file type")
+		}
+
+		loc := t[9]
+		if len(loc) == 0 {
+			return nil, fmt.Errorf("must has location")
+		}
+
+		res = &model.FileSuoNeiYiJu{
+			FileAbs:  abs,
+			Type:     fileType,
+			Location: loc,
+		}
+	case model.SuoJianYiJu:
+		fileType := t[8]
+		if len(fileType) == 0 {
+			return nil, fmt.Errorf("must has file type")
+		}
+
+		loc := t[9]
+		if len(loc) == 0 {
+			return nil, fmt.Errorf("must has new location")
+		}
+
+		res = &model.FileSuoJianYiJu{
+			FileAbs:  abs,
+			Type:     fileType,
+			Location: loc,
+		}
+	case model.NongZiZhuanFei:
+		fileType := t[8]
+		if len(fileType) == 0 {
+			return nil, fmt.Errorf("must has file type")
+		}
+
+		loc := t[9]
+		if len(loc) == 0 {
+			return nil, fmt.Errorf("must has location")
+		}
+
+		res = &model.FileNongZiZhuanFei{
+			FileAbs:  abs,
+			Type:     fileType,
+			Location: loc,
+		}
+	case model.YiZhanShiQianYiZheng:
+		fileType := t[8]
+		if len(fileType) == 0 {
+			return nil, fmt.Errorf("must has file type")
+		}
+
+		loc := t[9]
+		if len(loc) == 0 {
+			return nil, fmt.Errorf("must has location")
+		}
+
+		res = &model.FileYiZhanShiQianYiZheng{
+			FileAbs:  abs,
+			Type:     fileType,
+			Location: loc,
+		}
+	default:
+		return nil, fmt.Errorf("file set type error")
+	}
+
+	return res, nil
+}
+
+func makeUpdateFile(rt runtime.RunTime, fileID int64, fst model.FileSetType, t []string, tit []string) (model.File, error) {
+	maker, ok := model.FileSetTypeMaker[fst]
+	if !ok {
+		return nil, fmt.Errorf("bad file set type")
+	}
+
+	f := maker()
+	err := model.FindFile(rt, fileID, f) // f 已经是指针
+	if err != nil {
+		return nil, err
+	}
+
+	file := f.GetFile()
+	if file.FileSetType != fst {
+		return nil, fmt.Errorf("bad file set type")
+	}
+
+	name := t[1]
+	if len(name) != 0 {
+		file.Name = name
+	}
+
+	oldName := t[2]
+	if len(oldName) != 0 {
+		file.OldName = sql.NullString{
+			Valid:  len(oldName) != 0,
+			String: oldName,
+		}
+	}
+
+	idcard := t[3]
+	if len(idcard) != 0 {
+		file.IDCard = sql.NullString{
+			Valid:  len(idcard) != 0,
+			String: idcard,
+		}
+	}
+
+	if len(t[4]) != 0 {
+		file.IsMan = t[4] == "女性" || t[4] == "女" || t[4] == "W" || t[4] == "WOMAN"
+	}
+
+	if len(t[5]) != 0 {
+		var err error
+		file.Birthday, err = timeReader(time.Now(), t[5])
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	if len(t[7]) != 0 {
+		file.Comment = sql.NullString{
+			Valid:  true,
+			String: t[7],
+		}
+	}
+
+	fileTimeIndex := 0
+	for i, j := range tit {
+		if j == "办理时间" {
+			fileTimeIndex = i
+			break
+		}
+	}
+
+	if len(t[fileTimeIndex]) != 0 {
+		var err error
+		file.Time, err = timeReader(time.Now(), t[fileTimeIndex])
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	if len(t[fileTimeIndex+1]) != 0 {
+		file.BeiKao = sql.NullString{
+			Valid:  true,
+			String: t[fileTimeIndex+1],
+		}
+	}
+
+	if len(t[fileTimeIndex+2]) != 0 {
+		file.Material = sql.NullString{
+			Valid:  true,
+			String: t[fileTimeIndex+2],
+		}
+	}
+
+	switch ff := f.(type) {
+	case *model.FileQianRu:
+		fileType := t[8]
+		if len(fileType) != 0 {
+			ff.Type = fileType
+		}
+
+		oldLoc := t[9]
+		if len(oldLoc) != 0 {
+			ff.OldLocation = oldLoc
+		}
+
+		newLoc := t[10]
+		if len(newLoc) != 0 {
+			ff.NewLocation = newLoc
+		}
+	case *model.FileChuSheng:
+		fileType := t[8]
+		if len(fileType) != 0 {
+			ff.Type = fileType
+		}
+
+		loc := t[9]
+		if len(loc) != 0 {
+			ff.NewLocation = loc
+		}
+	case *model.FileSiWang:
+		fileType := t[8]
+		if len(fileType) != 0 {
+			ff.Type = fileType
+		}
+
+		loc := t[9]
+		if len(loc) != 0 {
+			ff.Location = loc
+		}
+	case *model.FileBianGeng:
+		fileType := t[8]
+		if len(fileType) != 0 {
+			ff.Type = fileType
+		}
+
+		loc := t[9]
+		if len(loc) != 0 {
+			ff.Location = loc
+		}
+	case *model.FileSuoNeiYiJu:
+		fileType := t[8]
+		if len(fileType) != 0 {
+			ff.Type = fileType
+		}
+
+		loc := t[9]
+		if len(loc) != 0 {
+			ff.Location = loc
+		}
+	case *model.FileSuoJianYiJu:
+		fileType := t[8]
+		if len(fileType) != 0 {
+			ff.Type = fileType
+		}
+
+		loc := t[9]
+		if len(loc) != 0 {
+			ff.Location = loc
+		}
+	case *model.FileNongZiZhuanFei:
+		fileType := t[8]
+		if len(fileType) != 0 {
+			ff.Type = fileType
+		}
+
+		loc := t[9]
+		if len(loc) != 0 {
+			ff.Location = loc
+		}
+	case *model.FileYiZhanShiQianYiZheng:
+		fileType := t[8]
+		if len(fileType) != 0 {
+			ff.Type = fileType
+		}
+
+		loc := t[9]
+		if len(loc) != 0 {
+			ff.Location = loc
+		}
+	default:
+		return nil, fmt.Errorf("file set type error")
+	}
+
+	return f, nil
+}
+
+func timeReader(baseTime time.Time, data string) (time.Time, error) {
+	if len(data) == 0 {
+		return baseTime, nil
+	}
+
+	timeFloat, err := strconv.ParseFloat(data, 64)
+	if err != nil {
+		//err 说明无法转化成float64 那么有可能本身是字符串时间进行返回
+		timeTime, err := time.Parse("2006-01-02 15:04:05", data)
+		if err != nil {
+			return time.Time{}, fmt.Errorf("未知的时间类型")
+		} else {
+			return timeTime, nil
+		}
+	} else {
+		timeTime, err := excelize.ExcelDateToTime(timeFloat, false)
+		if err != nil {
+			return time.Time{}, fmt.Errorf("未知的时间类型")
+		} else {
+			return timeTime, nil
+		}
+	}
+}

+ 235 - 0
src/excelio/readerrecord.go

@@ -0,0 +1,235 @@
+package excelio
+
+import (
+	"database/sql"
+	"fmt"
+	"github.com/SuperH-0630/hdangan/src/model"
+	"github.com/SuperH-0630/hdangan/src/runtime"
+	"github.com/xuri/excelize/v2"
+	"io"
+	"strconv"
+	"strings"
+	"time"
+)
+
+var RecordInputTitle = []string{
+	"档案ID", "出借记录ID", "操作目的", "状态", "借出时间", "借出人", "借出单位", "借入人", "借入单位", "备注",
+}
+
+func ReadRecord(rt runtime.RunTime, fst model.FileSetType, reader io.ReadCloser) (int64, int64, int64, error) {
+	f, err := excelize.OpenReader(reader)
+	if err != nil {
+		return 0, 0, 0, err
+	}
+
+	tit := RecordInputTitle
+
+	defer func() {
+		_ = f.Close()
+	}()
+
+	slst := f.GetSheetList()
+	if len(slst) != 1 {
+		return 0, 0, 0, fmt.Errorf("sheet 过多")
+	}
+
+	sheet := slst[0]
+
+	timeStyleID, err := f.NewStyle(&excelize.Style{})
+	if err != nil {
+		return 0, 0, 0, err
+	}
+
+	for i, t := range tit {
+		if strings.Contains(t, "日期") || strings.Contains(t, "时间") {
+			err = f.SetColStyle(sheet, Header[i], timeStyleID)
+			if err != nil {
+				return 0, 0, 0, err
+			}
+		}
+	}
+
+	rows, err := f.GetRows(sheet)
+	if err != nil {
+		return 0, 0, 0, err
+	}
+
+	var sa, su, fu int64
+
+	for i, j := range rows {
+		if i == 0 {
+			if !checkTitle(j, tit) {
+				return 0, 0, 0, BadTitle
+			}
+		} else {
+			t := make([]string, 0, len(tit))
+			t = append(t, j...)
+			if len(t) < len(tit) {
+				t = append(t, make([]string, len(tit)-len(t))...)
+			}
+
+			s := makeRecord(rt, fst, t, tit)
+			if s == successAdd {
+				sa += 1
+			} else if s == successUpdate {
+				su += 1
+			} else {
+				fu += 1
+			}
+		}
+	}
+
+	return sa, su, fu, nil
+}
+
+func makeRecord(rt runtime.RunTime, fst model.FileSetType, t []string, tit []string) int {
+	if t[1] != "更新" {
+		var f model.File
+
+		if len(t[0]) != 0 {
+			fileID, err := strconv.ParseInt(t[0], 10, 64)
+			if err != nil {
+				return fail
+			}
+
+			maker, ok := model.FileSetTypeMaker[fst]
+			if !ok {
+				return fail
+			}
+
+			f = maker()
+			err = model.FindFile(rt, fileID, f) // f 已经是指针
+			if err != nil {
+				return fail
+			}
+		} else {
+			return fail
+		}
+
+		file, record, err := makeNewRecord(rt, f, fst, t, tit)
+		if err != nil {
+			return fail
+		}
+
+		err = model.CreateFileRecord(rt, file, record)
+		if err != nil {
+			return fail
+		}
+		return successAdd
+	} else {
+		var r *model.FileMoveRecord
+
+		if len(t[1]) != 0 {
+			recordID, err := strconv.ParseInt(t[1], 10, 64)
+			if err != nil {
+				return fail
+			}
+
+			r, err = model.FindRecord(rt, recordID)
+			if err != nil {
+				return fail
+			}
+		} else {
+			return fail
+		}
+
+		r, err := makeUpdateRecord(rt, r, t, tit)
+		if err != nil {
+			return fail
+		}
+
+		err = model.SaveRecord(rt, r)
+		if err != nil {
+			return fail
+		}
+		return successUpdate
+	}
+}
+
+func makeNewRecord(rt runtime.RunTime, f model.File, fst model.FileSetType, t []string, tit []string) (model.File, *model.FileMoveRecord, error) {
+	file := f.GetFile()
+
+	moveStatus := t[2]
+	if len(moveStatus) == 0 {
+		return nil, nil, fmt.Errorf("move status should be given")
+	}
+
+	moveTime := time.Now()
+	if len(t[3]) != 0 {
+		var err error
+		moveTime, err = timeReader(time.Now(), t[3])
+		if err != nil {
+			return nil, nil, err
+		}
+	}
+
+	moveOutPeople := sql.NullString{Valid: len(t[4]) != 0, String: t[4]}
+	moveOutUnit := sql.NullString{Valid: len(t[5]) != 0, String: t[5]}
+	moveInPeople := sql.NullString{Valid: len(t[6]) != 0, String: t[6]}
+	moveInUnit := sql.NullString{Valid: len(t[7]) != 0, String: t[7]}
+	moveComment := sql.NullString{Valid: len(t[8]) != 0, String: t[8]}
+
+	res := &model.FileMoveRecord{
+		MoveStatus:        moveStatus,
+		MoveTime:          moveTime,
+		MoveOutPeopleName: moveOutPeople,
+		MoveOutPeopleUnit: moveOutUnit,
+		MoveInPeopleName:  moveInPeople,
+		MoveInPeopleUnit:  moveInUnit,
+		MoveComment:       moveComment,
+	}
+
+	return file, res, nil
+}
+
+func makeUpdateRecord(rt runtime.RunTime, record *model.FileMoveRecord, t []string, tit []string) (*model.FileMoveRecord, error) {
+	moveStatus := t[2]
+	if len(moveStatus) != 0 {
+		record.MoveStatus = moveStatus
+	}
+
+	if len(t[3]) != 0 {
+		var err error
+		record.MoveTime, err = timeReader(time.Now(), t[3])
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	if len(t[4]) != 0 {
+		record.MoveOutPeopleName = sql.NullString{
+			Valid:  true,
+			String: t[4],
+		}
+	}
+
+	if len(t[5]) != 0 {
+		record.MoveOutPeopleUnit = sql.NullString{
+			Valid:  true,
+			String: t[5],
+		}
+	}
+
+	if len(t[6]) != 0 {
+		record.MoveInPeopleName = sql.NullString{
+			Valid:  true,
+			String: t[6],
+		}
+	}
+
+	if len(t[7]) != 0 {
+		record.MoveInPeopleUnit = sql.NullString{
+			Valid:  true,
+			String: t[7],
+		}
+	}
+
+	if len(t[8]) != 0 {
+		record.MoveComment = sql.NullString{
+			Valid:  true,
+			String: t[8],
+		}
+	}
+
+	return record, nil
+}

+ 105 - 0
src/excelio/writerecord.go

@@ -0,0 +1,105 @@
+package excelio
+
+import (
+	"fmt"
+	"github.com/SuperH-0630/hdangan/src/model"
+	"github.com/SuperH-0630/hdangan/src/runtime"
+	"github.com/xuri/excelize/v2"
+	"strings"
+)
+
+var OutputRecordTitle = []string{
+	"出借记录ID", "联合档案ID", "状态", "借出时间", "借出人", "借出单位", "借入人", "借入单位", "备注",
+}
+
+func OutputFileRecord(rt runtime.RunTime, savepath string, file model.File, record []model.FileMoveRecord, s *model.SearchRecord) error {
+	var err error
+
+	f := excelize.NewFile()
+	defer func() {
+		_ = f.Close()
+	}()
+
+	tit := OutputRecordTitle
+
+	sheetIndex := 0
+	sheetName := "Sheet1"
+	slts := f.GetSheetList()
+	if len(slts) == 0 {
+		sheetIndex, err = f.NewSheet(sheetName)
+		if err != nil {
+			return err
+		}
+	} else {
+		sheetName = slts[0]
+	}
+
+	f.SetActiveSheet(sheetIndex)
+
+	timeStyleID, err := f.NewStyle(&excelize.Style{})
+	if err != nil {
+		return err
+	}
+
+	for i, t := range tit {
+		if strings.Contains(t, "日期") || strings.Contains(t, "时间") {
+			err = f.SetColStyle(sheetName, Header[i], timeStyleID)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	for i, k := range tit {
+		err = f.SetCellStr(sheetName, fmt.Sprintf("%s1", Header[i]), k)
+		if err != nil {
+			return err
+		}
+	}
+
+	if record == nil || len(record) == 0 {
+		record, err = model.GetAllRecord(rt, file, s)
+		if err != nil {
+			return err
+		}
+	}
+
+	for i, r := range record {
+		res, err := recordToStringLst(rt, r)
+		if err != nil {
+			continue
+		}
+		for d, v := range res {
+			err = f.SetCellStr(sheetName, fmt.Sprintf("%s%d", Header[d], i+2), v)
+			if err != nil {
+				continue
+			}
+		}
+	}
+
+	err = f.SaveAs(savepath)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func recordToStringLst(rt runtime.RunTime, record model.FileMoveRecord) ([]string, error) {
+	res := make([]string, 0, 15)
+
+	res = append(res, fmt.Sprintf("%d", record.ID))
+	res = append(res, fmt.Sprintf("%d", record.FileUnionID))
+	res = append(res, fmt.Sprintf("%s", record.MoveStatus))
+	res = append(res, fmt.Sprintf("%s", record.MoveTime.Format("2006-01-02 15:04:05")))
+
+	res = append(res, fmt.Sprintf("%s", record.MoveOutPeopleName.String))
+	res = append(res, fmt.Sprintf("%s", record.MoveOutPeopleUnit.String))
+
+	res = append(res, fmt.Sprintf("%s", record.MoveInPeopleName.String))
+	res = append(res, fmt.Sprintf("%s", record.MoveInPeopleUnit.String))
+
+	res = append(res, fmt.Sprintf("%s", record.MoveComment.String))
+
+	return res, nil
+}

+ 200 - 0
src/excelio/writerfile.go

@@ -0,0 +1,200 @@
+package excelio
+
+import (
+	"fmt"
+	"github.com/SuperH-0630/hdangan/src/model"
+	"github.com/SuperH-0630/hdangan/src/runtime"
+	"github.com/xuri/excelize/v2"
+	"reflect"
+	"strings"
+)
+
+var OutputTitle = map[model.FileSetType][]string{
+	model.QianRu:               {"卷宗ID", "联合档案ID", "档案ID", "组内ID", "姓名", "曾用名", "身份证", "性别", "出生日期", "是否同上", "备注", "类型", "旧地址", "新地址", "办理时间", "备考", "材料页数", "人数", "材料"},
+	model.ChuSheng:             {"卷宗ID", "联合档案ID", "档案ID", "组内ID", "姓名", "曾用名", "身份证", "性别", "出生日期", "是否同上", "备注", "类型", "地址", "办理时间", "备考", "材料页数", "人数", "材料"},
+	model.QianChu:              {"卷宗ID", "联合档案ID", "档案ID", "组内ID", "姓名", "曾用名", "身份证", "性别", "出生日期", "是否同上", "备注", "类型", "新地址", "办理时间", "备考", "材料页数", "人数", "材料"},
+	model.SiWang:               {"卷宗ID", "联合档案ID", "档案ID", "组内ID", "姓名", "曾用名", "身份证", "性别", "出生日期", "是否同上", "备注", "类型", "地址", "办理时间", "备考", "材料页数", "人数", "材料"},
+	model.BianGeng:             {"卷宗ID", "联合档案ID", "档案ID", "组内ID", "姓名", "曾用名", "身份证", "性别", "出生日期", "是否同上", "备注", "类型", "地址", "办理时间", "备考", "材料页数", "人数", "材料"},
+	model.SuoNeiYiJu:           {"卷宗ID", "联合档案ID", "档案ID", "组内ID", "姓名", "曾用名", "身份证", "性别", "出生日期", "是否同上", "备注", "类型", "地址", "办理时间", "备考", "材料页数", "人数", "材料"},
+	model.SuoJianYiJu:          {"卷宗ID", "联合档案ID", "档案ID", "组内ID", "姓名", "曾用名", "身份证", "性别", "出生日期", "是否同上", "备注", "类型", "地址", "办理时间", "备考", "材料页数", "人数", "材料"},
+	model.NongZiZhuanFei:       {"卷宗ID", "联合档案ID", "档案ID", "组内ID", "姓名", "曾用名", "身份证", "性别", "出生日期", "是否同上", "备注", "类型", "地址", "办理时间", "备考", "材料页数", "人数", "材料"},
+	model.YiZhanShiQianYiZheng: {"卷宗ID", "联合档案ID", "档案ID", "组内ID", "姓名", "曾用名", "身份证", "性别", "出生日期", "是否同上", "备注", "类型", "地址", "办理时间", "备考", "材料页数", "人数", "材料"},
+}
+
+func OutputFile(rt runtime.RunTime, fst model.FileSetType, savepath string, files []model.File, s *model.SearchWhere) error {
+	var err error
+
+	f := excelize.NewFile()
+	defer func() {
+		_ = f.Close()
+	}()
+
+	tit, ok := InputTitle[fst]
+	if !ok {
+		return err
+	}
+
+	sheetIndex := 0
+	sheetName := "Sheet1"
+	slts := f.GetSheetList()
+	if len(slts) == 0 {
+		sheetIndex, err = f.NewSheet(sheetName)
+		if err != nil {
+			return err
+		}
+	} else {
+		sheetName = slts[0]
+	}
+
+	f.SetActiveSheet(sheetIndex)
+
+	timeStyleID, err := f.NewStyle(&excelize.Style{})
+	if err != nil {
+		return err
+	}
+
+	for i, t := range tit {
+		if strings.Contains(t, "日期") || strings.Contains(t, "时间") {
+			err = f.SetColStyle(sheetName, Header[i], timeStyleID)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	for i, k := range tit {
+		err = f.SetCellStr(sheetName, fmt.Sprintf("%s1", Header[i]), k)
+		if err != nil {
+			return err
+		}
+	}
+
+	if files == nil || len(files) == 0 {
+		maker, ok := model.FileSetTypeMaker[fst]
+		if !ok {
+			return fmt.Errorf("file set type not found")
+		}
+
+		tptr := reflect.TypeOf(maker())
+		if tptr.Kind() != reflect.Ptr {
+			return fmt.Errorf("file set type not found")
+		}
+
+		t := tptr.Elem()
+		if t.Kind() != reflect.Struct {
+			return fmt.Errorf("file set type not found")
+		}
+
+		if strings.HasPrefix(t.Name(), "File") {
+			return fmt.Errorf("file set type not found")
+		}
+
+		resValue := reflect.MakeSlice(reflect.SliceOf(t), 0, 20)
+		res := resValue.Interface()
+
+		err = model.GetAllFile(rt, fst, s, res)
+		if err != nil {
+			return err
+		}
+
+		for i := 0; i < resValue.Len(); i++ {
+			file := resValue.Index(i).Interface().(model.File)
+			res, err := fileToStringLst(rt, file)
+			if err != nil {
+				continue
+			}
+			for d, v := range res {
+				err = f.SetCellStr(sheetName, fmt.Sprintf("%s%d", Header[d], i+2), v)
+				if err != nil {
+					continue
+				}
+			}
+		}
+	} else {
+		for i, file := range files {
+			res, err := fileToStringLst(rt, file)
+			if err != nil {
+				continue
+			}
+			for d, v := range res {
+				err = f.SetCellStr(sheetName, fmt.Sprintf("%s%d", Header[d], i+2), v)
+				if err != nil {
+					continue
+				}
+			}
+		}
+	}
+
+	err = f.SaveAs(savepath)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func fileToStringLst(rt runtime.RunTime, file model.File) ([]string, error) {
+	f := file.GetFile()
+
+	res := make([]string, 0, 15)
+
+	res = append(res, fmt.Sprintf("%d", f.FileSetID))
+	res = append(res, fmt.Sprintf("%d", f.FileUnionID))
+	res = append(res, fmt.Sprintf("%d", f.FileID))
+	res = append(res, fmt.Sprintf("%d", f.FileGroupID))
+
+	res = append(res, fmt.Sprintf("%s", f.Name))
+	res = append(res, fmt.Sprintf("%s", f.OldName.String))
+	res = append(res, fmt.Sprintf("%s", f.IDCard.String))
+
+	if f.IsMan {
+		res = append(res, "男性")
+	} else {
+		res = append(res, "女性")
+	}
+
+	res = append(res, fmt.Sprintf("%s", f.Birthday.Format("2006-01-02")))
+	if f.SameAsAbove {
+		res = append(res, "同上")
+	} else {
+		res = append(res, "非同上")
+	}
+
+	switch ff := file.(type) {
+	case *model.FileQianRu:
+		res = append(res, model.FileSetTypeName[ff.FileSetType])
+		res = append(res, ff.OldLocation)
+		res = append(res, ff.NewLocation)
+	case *model.FileChuSheng:
+		res = append(res, model.FileSetTypeName[ff.FileSetType])
+		res = append(res, ff.NewLocation)
+	case *model.FileSiWang:
+		res = append(res, model.FileSetTypeName[ff.FileSetType])
+		res = append(res, ff.Location)
+	case *model.FileBianGeng:
+		res = append(res, model.FileSetTypeName[ff.FileSetType])
+		res = append(res, ff.Location)
+	case *model.FileSuoNeiYiJu:
+		res = append(res, model.FileSetTypeName[ff.FileSetType])
+		res = append(res, ff.Location)
+	case *model.FileSuoJianYiJu:
+		res = append(res, model.FileSetTypeName[ff.FileSetType])
+		res = append(res, ff.Location)
+	case *model.FileNongZiZhuanFei:
+		res = append(res, model.FileSetTypeName[ff.FileSetType])
+		res = append(res, ff.Location)
+	case *model.FileYiZhanShiQianYiZheng:
+		res = append(res, model.FileSetTypeName[ff.FileSetType])
+		res = append(res, ff.Location)
+	default:
+		return nil, fmt.Errorf("file set type error")
+	}
+
+	res = append(res, fmt.Sprintf("%s", f.Time.Format("2006-01-02 15:04:05")))
+	res = append(res, fmt.Sprintf("%s", f.BeiKao.String))
+	res = append(res, fmt.Sprintf("%d", f.PageCount))
+	res = append(res, fmt.Sprintf("%d", f.PeopleCount))
+	res = append(res, fmt.Sprintf("%s", f.Material.String))
+
+	return res, nil
+}

+ 15 - 0
src/excelio/writetemplate.go

@@ -0,0 +1,15 @@
+package excelio
+
+import (
+	"github.com/SuperH-0630/hdangan/src/assest"
+	"github.com/SuperH-0630/hdangan/src/runtime"
+	"io"
+)
+
+func CreateTemplate(rt runtime.RunTime, writer io.Writer) error {
+	_, err := writer.Write(assest.TemplateXlsx.Content())
+	if err != nil {
+		return err
+	}
+	return nil
+}

+ 0 - 709
src/excelreader/reader.go

@@ -1,709 +0,0 @@
-package excelreader
-
-import (
-	"database/sql"
-	"fmt"
-	"github.com/SuperH-0630/hdangan/src/assest"
-	"github.com/SuperH-0630/hdangan/src/model"
-	"github.com/SuperH-0630/hdangan/src/runtime"
-	"github.com/SuperH-0630/hdangan/src/systeminit"
-	"github.com/xuri/excelize/v2"
-	"io"
-	"strconv"
-	"time"
-)
-
-var Title = []string{
-	"档案号", "姓名", "身份证", "户籍地", "卷宗标题", "卷宗类型", "卷宗备注", "第一次迁移时间", "最后一次迁移时间", "迁移状态", "迁移人", "迁移单位", "迁移备注",
-}
-
-var RecordTitle = []string{
-	"档案号", "姓名", "身份证", "户籍地", "卷宗标题", "卷宗类型", "卷宗备注", "迁入迁出状态", "时间", "迁出单位", "迁出备注",
-}
-
-var Header = []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N"}
-
-func indexToDataRecord(f *model.FileMoveRecord, i int) string {
-	if f == nil {
-		return ""
-	}
-
-	switch i {
-	case 0:
-		return fmt.Sprintf("%d", f.File.FileID)
-	case 1:
-		return fmt.Sprintf("%s", f.File.Name)
-	case 2:
-		return fmt.Sprintf("%s", f.File.IDCard)
-	case 3:
-		return fmt.Sprintf("%s", f.File.Location)
-
-	case 4:
-		return fmt.Sprintf("%s", f.File.FileType)
-	case 5:
-		return fmt.Sprintf("%s", f.File.FileType)
-	case 6:
-		return fmt.Sprintf("%s", f.File.FileComment.String)
-
-	case 7:
-		return fmt.Sprintf("%s", f.MoveStatus)
-	case 8:
-		return fmt.Sprintf("%s", f.MoveTime.Format("2006-01-02 15:04:05"))
-
-	case 9:
-		return fmt.Sprintf("%s", getString(f.MoveOutPeopleName))
-	case 10:
-		return fmt.Sprintf("%s", getString(f.MoveOutPeopleUnit))
-	case 11:
-		return fmt.Sprintf("%s", getString(f.MoveComment))
-	}
-
-	return ""
-}
-
-func indexToData(f *model.File, i int) string {
-	if f == nil {
-		return ""
-	}
-
-	switch i {
-	case 0:
-		return fmt.Sprintf("%d", f.FileID)
-	case 1:
-		return fmt.Sprintf("%s", f.Name)
-	case 2:
-		return fmt.Sprintf("%s", f.IDCard)
-	case 3:
-		return fmt.Sprintf("%s", f.Location)
-
-	case 4:
-		return fmt.Sprintf("%s", f.FileTitle)
-	case 5:
-		return fmt.Sprintf("%s", f.FileType)
-	case 6:
-		return fmt.Sprintf("%s", f.FileComment.String)
-	case 7:
-		return fmt.Sprintf("%s", f.FirstMoveIn)
-	case 8:
-		return fmt.Sprintf("%s", f.LastMoveIn)
-	case 9:
-		return fmt.Sprintf("%s", f.MoveStatus)
-	case 10:
-		return fmt.Sprintf("%s", getString(f.MoveOutPeopleName))
-	case 11:
-		return fmt.Sprintf("%s", getString(f.MoveOutPeopleUnit))
-	case 12:
-		return fmt.Sprintf("%s", getString(f.MoveComment))
-	}
-
-	return ""
-}
-
-func getString(str sql.NullString) string {
-	if str.Valid {
-		return str.String
-	}
-	return ""
-}
-
-func getTime(t sql.NullTime) string {
-	if t.Valid {
-		return t.Time.Format("2006-01-02 15:04:05")
-	}
-	return ""
-}
-
-var BadTitle = fmt.Errorf("表格首行的标题错误")
-
-const (
-	successAdd = iota
-	successUpdate
-	fail
-)
-
-func CreateTemplate(rt runtime.RunTime, writer io.Writer) error {
-	_, err := writer.Write(assest.TemplateXlsx.Content())
-	if err != nil {
-		return err
-	}
-	return nil
-}
-
-func OutputFile(rt runtime.RunTime, savepath string, files []model.File, s *model.SearchWhere) error {
-	var err error
-
-	f := excelize.NewFile()
-	defer func() {
-		_ = f.Close()
-	}()
-
-	sheetIndex := 0
-	sheetName := "Sheet1"
-	slts := f.GetSheetList()
-	if len(slts) == 0 {
-		sheetIndex, err = f.NewSheet(sheetName)
-		if err != nil {
-			return err
-		}
-	} else {
-		sheetName = slts[0]
-	}
-
-	f.SetActiveSheet(sheetIndex)
-
-	styleId, err := f.NewStyle(&excelize.Style{})
-	if err != nil {
-		return err
-	}
-
-	_ = f.SetColStyle(sheetName, Header[7], styleId)
-	_ = f.SetColStyle(sheetName, Header[8], styleId)
-	_ = f.SetColStyle(sheetName, Header[10], styleId)
-
-	if files == nil || len(files) == 0 {
-		files, err = model.GetAllFile(rt, s)
-		if err != nil {
-			return err
-		}
-	}
-
-	for i, k := range Title {
-		err = f.SetCellStr(sheetName, fmt.Sprintf("%s1", Header[i]), k)
-		if err != nil {
-			return err
-		}
-	}
-
-	for h, _ := range Title {
-		for j, y := range files {
-			err = f.SetCellStr(sheetName, fmt.Sprintf("%s%d", Header[h], j+2), indexToData(&y, h))
-			if err != nil {
-				return err
-			}
-		}
-	}
-
-	err = f.SaveAs(savepath)
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func OutputFileRecord(rt runtime.RunTime, savepath string, file *model.File, record []model.FileMoveRecord, s *model.SearchRecord) error {
-	var err error
-
-	f := excelize.NewFile()
-	defer func() {
-		_ = f.Close()
-	}()
-
-	sheetIndex := 0
-	sheetName := "Sheet1"
-	slts := f.GetSheetList()
-	if len(slts) == 0 {
-		sheetIndex, err = f.NewSheet(sheetName)
-		if err != nil {
-			return err
-		}
-	} else {
-		sheetName = slts[0]
-	}
-
-	f.SetActiveSheet(sheetIndex)
-
-	styleId, err := f.NewStyle(&excelize.Style{})
-	if err != nil {
-		return err
-	}
-
-	_ = f.SetColStyle(sheetName, Header[7], styleId)
-	_ = f.SetColStyle(sheetName, Header[8], styleId)
-
-	if record == nil || len(record) == 0 {
-		record, err = model.GetAllFileRecord(rt, file, s)
-		if err != nil {
-			return err
-		}
-	}
-
-	for i, k := range RecordTitle {
-		err = f.SetCellStr(sheetName, fmt.Sprintf("%s1", Header[i]), k)
-		if err != nil {
-			return err
-		}
-	}
-
-	for h, _ := range RecordTitle {
-		for j, y := range record {
-			err = f.SetCellStr(sheetName, fmt.Sprintf("%s%d", Header[h], j+2), indexToDataRecord(&y, h))
-			if err != nil {
-				return err
-			}
-		}
-	}
-
-	err = f.SaveAs(savepath)
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func ReadFile(rt runtime.RunTime, reader io.ReadCloser) (int64, int64, int64, error) {
-	f, err := excelize.OpenReader(reader)
-	if err != nil {
-		return 0, 0, 0, err
-	}
-
-	defer func() {
-		_ = f.Close()
-	}()
-
-	slst := f.GetSheetList()
-	if len(slst) != 1 {
-		return 0, 0, 0, fmt.Errorf("sheet 过多")
-	}
-
-	sheet := slst[0]
-
-	styleId, err := f.NewStyle(&excelize.Style{})
-	if err != nil {
-		return 0, 0, 0, err
-	}
-
-	err = f.SetColStyle(sheet, Header[7], styleId)
-	if err != nil {
-		return 0, 0, 0, err
-	}
-
-	err = f.SetColStyle(sheet, Header[8], styleId)
-	if err != nil {
-		return 0, 0, 0, err
-	}
-
-	err = f.SetColStyle(sheet, Header[10], styleId)
-	if err != nil {
-		return 0, 0, 0, err
-	}
-
-	rows, err := f.GetRows(sheet)
-	if err != nil {
-		return 0, 0, 0, err
-	}
-
-	var sa, su, fu int64
-
-	for i, j := range rows {
-		if i == 0 {
-			if !checkTitle(j) {
-				return 0, 0, 0, BadTitle
-			}
-		} else {
-			t := make([]string, 0, len(Title))
-			t = append(t, j...)
-			t = append(t, make([]string, len(Title)-len(t))...)
-			s := makeFile(rt, t)
-			if s == successAdd {
-				sa += 1
-			} else if s == successUpdate {
-				su += 1
-			} else {
-				fu += 1
-			}
-		}
-	}
-
-	return sa, su, fu, nil
-}
-
-func checkTitle(t []string) bool {
-	for i, s := range t {
-		if Title[i] != s {
-			return false
-		}
-	}
-	return true
-}
-
-func makeFile(rt runtime.RunTime, t []string) int {
-	file, record, oldRecord, err := _makeFile(rt, t)
-
-	if err == nil && file != nil && record != nil {
-		isNew := file.ID == 0
-		err := model.SaveFileRecord(file, record, oldRecord)
-		if err == nil {
-			if isNew {
-				return successAdd
-			} else {
-				return successUpdate
-			}
-		}
-	}
-	return fail
-}
-
-func _makeFile(rt runtime.RunTime, t []string) (*model.File, *model.FileMoveRecord, *model.FileMoveRecord, error) {
-	if t[0] == "" {
-		fileID, err := model.GetNewFileID(rt)
-		if err != nil {
-			return nil, nil, nil, err
-		}
-		return makeNewFile(rt, fileID, t)
-	} else {
-		tmp, err := strconv.ParseInt(t[0], 10, 64)
-		if err != nil {
-		}
-
-		if err != nil || tmp <= 0 {
-			fileID, err := model.GetNewFileID(rt)
-			if err != nil {
-				return nil, nil, nil, err
-			}
-			return makeNewFile(rt, fileID, t)
-		} else {
-			f := model.FindFile(tmp)
-			if f == nil {
-				return makeNewFile(rt, tmp, t)
-			} else {
-				return makeUpdateFile(rt, f, t)
-			}
-		}
-	}
-}
-
-func makeNewFile(rt runtime.RunTime, fileID int64, t []string) (*model.File, *model.FileMoveRecord, *model.FileMoveRecord, error) {
-	c, err := systeminit.GetInit()
-	if err != nil {
-		return nil, nil, nil, err
-	}
-
-	name := t[1]
-	if len(name) == 0 {
-		return nil, nil, nil, fmt.Errorf("must has name")
-	}
-
-	idcard := t[2]
-	if len(idcard) == 0 {
-		return nil, nil, nil, fmt.Errorf("must has id")
-	} else if idcard[0] == '\'' {
-		idcard = idcard[1:]
-	}
-
-	loc := t[3]
-	if len(loc) == 0 {
-		return nil, nil, nil, fmt.Errorf("must has loc")
-	}
-
-	fileTitle := t[4]
-	if len(fileTitle) == 0 {
-		return nil, nil, nil, fmt.Errorf("must has title")
-	}
-
-	fileType := t[5]
-	if len(fileType) == 0 {
-		return nil, nil, nil, fmt.Errorf("must has filetype")
-	}
-
-	fileComment := t[6]
-
-	firstMoveInTime := time.Now()
-	if len(t[7]) != 0 {
-		firstMoveInTime, err = timeReader(time.Now(), t[7])
-		if err != nil {
-			return nil, nil, nil, err
-		}
-	}
-
-	lastMoveInTime := firstMoveInTime // t[8]忽略
-
-	moveStatus := t[9]
-	isMoveIn := moveStatus == c.Yaml.Move.MoveInStatus
-	if len(moveStatus) == 0 {
-		return nil, nil, nil, fmt.Errorf("must has move status")
-	}
-
-	lastMoveTime := sql.NullTime{
-		Valid: false,
-	}
-
-	lasttMove := t[8]
-	if isMoveIn {
-		if len(lasttMove) != 0 {
-			tmp, err := timeReader(firstMoveInTime, lasttMove)
-			if err == nil {
-				lastMoveTime.Valid = true
-				lastMoveTime.Time = tmp
-			} else {
-				return nil, nil, nil, err
-			}
-		} else {
-			lastMoveTime.Valid = true
-			lastMoveTime.Time = firstMoveInTime
-		}
-	} else {
-		if len(lasttMove) != 0 {
-			tmp, err := timeReader(time.Now(), lasttMove)
-			if err == nil {
-				lastMoveTime.Valid = true
-				lastMoveTime.Time = tmp
-			} else {
-				return nil, nil, nil, err
-			}
-		} else {
-			lastMoveTime.Valid = true
-			lastMoveTime.Time = time.Now()
-		}
-	}
-
-	movePeople := t[10]
-	if isMoveIn {
-		movePeople = ""
-	} else {
-		if len(movePeople) == 0 {
-			return nil, nil, nil, fmt.Errorf("must has move people")
-		}
-	}
-
-	moveUnit := t[11]
-	if isMoveIn {
-		moveUnit = ""
-	} else {
-		if len(moveUnit) == 0 {
-			return nil, nil, nil, fmt.Errorf("must has move unit")
-		}
-	}
-
-	moveComment := t[12]
-	if isMoveIn && len(moveComment) != 0 {
-		moveComment = ""
-	}
-
-	file := model.File{
-		FileID:    fileID,
-		Name:      name,
-		IDCard:    idcard,
-		Location:  loc,
-		FileTitle: fileTitle,
-		FileType:  fileType,
-		FileComment: sql.NullString{
-			Valid:  len(fileComment) != 0,
-			String: fileComment,
-		},
-
-		FirstMoveIn: firstMoveInTime,
-		LastMoveIn:  lastMoveInTime,
-
-		MoveStatus: moveStatus,
-		MoveOutPeopleName: sql.NullString{
-			Valid:  !isMoveIn,
-			String: movePeople,
-		},
-		MoveOutPeopleUnit: sql.NullString{
-			Valid:  !isMoveIn,
-			String: moveUnit,
-		},
-		MoveComment: sql.NullString{
-			Valid:  len(moveComment) != 0 && !isMoveIn,
-			String: moveComment,
-		},
-	}
-
-	record := model.FileMoveRecord{
-		// 不设置File参数
-		MoveStatus: moveStatus,
-		MoveTime:   file.LastMoveIn,
-		MoveOutPeopleName: sql.NullString{
-			Valid:  !isMoveIn,
-			String: movePeople,
-		},
-		MoveOutPeopleUnit: sql.NullString{
-			Valid:  !isMoveIn,
-			String: moveUnit,
-		},
-		MoveComment: sql.NullString{
-			Valid:  len(moveComment) != 0 && !isMoveIn,
-			String: moveComment,
-		},
-	}
-
-	return &file, &record, nil, nil
-}
-
-func makeUpdateFile(rt runtime.RunTime, file *model.File, t []string) (*model.File, *model.FileMoveRecord, *model.FileMoveRecord, error) {
-	c, err := systeminit.GetInit()
-	if err != nil {
-		return nil, nil, nil, err
-	}
-
-	name := t[1]
-	if len(name) != 0 {
-		file.Name = name
-	}
-
-	idcard := t[2]
-	if len(idcard) > 0 && idcard[0] == '\'' {
-		idcard = idcard[1:]
-	}
-	if len(idcard) != 0 {
-		file.IDCard = idcard
-	}
-
-	loc := t[3]
-	if len(loc) != 0 {
-		file.Location = loc
-	}
-
-	fileTitle := t[4]
-	if len(fileTitle) != 0 {
-		file.FileTitle = fileTitle
-	}
-
-	fileType := t[5]
-	if len(fileType) != 0 {
-		file.FileType = fileType
-	}
-
-	fileComment := t[6]
-	if len(fileComment) != 0 {
-		file.FileComment = sql.NullString{
-			Valid:  true,
-			String: fileComment,
-		}
-	}
-
-	firstMoveInTime := time.Now()
-	if len(t[7]) != 0 {
-		firstMoveInTime, err = timeReader(time.Now(), t[7])
-		if err != nil {
-			return nil, nil, nil, err
-		}
-	}
-
-	file.FirstMoveIn = firstMoveInTime
-
-	lastMoveTime := time.Time{}
-
-	moveStatus := t[9]
-	isMoveIn := moveStatus == c.Yaml.Move.MoveInStatus
-	if len(moveStatus) != 0 {
-		file.MoveStatus = moveStatus
-	}
-
-	lastMove := t[8]
-	if isMoveIn {
-		if len(lastMove) != 0 {
-			tmp, err := timeReader(file.LastMoveIn, lastMove)
-			if err == nil {
-				lastMoveTime = tmp
-			} else {
-				return nil, nil, nil, err
-			}
-		} else {
-			lastMoveTime = file.LastMoveIn
-		}
-	} else {
-		if len(lastMove) != 0 {
-			tmp, err := timeReader(time.Now(), lastMove)
-			if err == nil {
-				lastMoveTime = tmp
-			} else {
-				return nil, nil, nil, err
-			}
-		} else {
-			lastMoveTime = time.Now()
-		}
-	}
-
-	file.LastMoveIn = lastMoveTime
-
-	movePeople := t[10]
-	file.MoveOutPeopleName = sql.NullString{
-		Valid:  true,
-		String: movePeople,
-	}
-
-	moveUnit := t[11]
-	file.MoveOutPeopleUnit = sql.NullString{
-		Valid:  true,
-		String: moveUnit,
-	}
-
-	moveComment := t[12]
-	file.MoveComment = sql.NullString{
-		Valid:  len(moveComment) != 0,
-		String: moveComment,
-	}
-
-	if moveStatus == c.Yaml.Move.MoveInStatus {
-		file.MoveOutPeopleName.Valid = false
-		file.MoveOutPeopleUnit.Valid = false
-		file.MoveComment.Valid = false
-	} else {
-		if !file.MoveOutPeopleName.Valid || !file.MoveOutPeopleUnit.Valid {
-			return nil, nil, nil, fmt.Errorf("迁出状态需要填写迁出人和单位")
-		}
-	}
-
-	if moveStatus == c.Yaml.Move.MoveInStatus {
-		file.MoveOutPeopleName.Valid = false
-		file.MoveOutPeopleUnit.Valid = false
-		file.MoveComment.Valid = false
-
-		record := &model.FileMoveRecord{
-			// FileID在Save时会自动增加
-			MoveStatus:        file.MoveStatus,
-			MoveTime:          file.LastMoveIn,
-			MoveOutPeopleName: file.MoveOutPeopleName,
-			MoveOutPeopleUnit: file.MoveOutPeopleUnit,
-			MoveComment:       file.MoveComment,
-		}
-
-		oldRecord, _ := model.CheckFileMoveOut(file)
-
-		return file, record, oldRecord, nil
-	} else {
-		if !file.MoveOutPeopleName.Valid || !file.MoveOutPeopleUnit.Valid {
-			return nil, nil, nil, fmt.Errorf("迁出状态需要填写迁出人和单位")
-		} else {
-			record := &model.FileMoveRecord{
-				// FileID在Save时会自动增加
-				MoveStatus:        file.MoveStatus,
-				MoveTime:          file.LastMoveIn,
-				MoveOutPeopleName: file.MoveOutPeopleName,
-				MoveOutPeopleUnit: file.MoveOutPeopleUnit,
-				MoveComment:       file.MoveComment,
-			}
-
-			oldRecord, _ := model.CheckFileMoveOut(file)
-
-			return file, record, oldRecord, nil
-		}
-	}
-}
-
-func timeReader(baseTime time.Time, data string) (time.Time, error) {
-	if len(data) == 0 {
-		return baseTime, nil
-	}
-
-	timeFloat, err := strconv.ParseFloat(data, 64)
-	if err != nil {
-		//err 说明无法转化成float64 那么有可能本身是字符串时间进行返回
-		timeTime, err := time.Parse("2006-01-02 15:04:05", data)
-		if err != nil {
-			return time.Time{}, fmt.Errorf("未知的时间类型")
-		} else {
-			return timeTime, nil
-		}
-	} else {
-		timeTime, err := excelize.ExcelDateToTime(timeFloat, false)
-		if err != nil {
-			return time.Time{}, fmt.Errorf("未知的时间类型")
-		} else {
-			return timeTime, nil
-		}
-	}
-}

+ 1 - 0
src/model/action.go

@@ -1,6 +1,7 @@
 package model
 
 const DefaultPageItemCount = 20
+const MaxLimit = 10000
 
 type PageSizeResult struct {
 	AllCount int64 `gorm:"column:ps"`

+ 123 - 109
src/model/action_file.go

@@ -1,133 +1,132 @@
 package model
 
 import (
-	"database/sql"
 	"errors"
 	"fmt"
 	"github.com/SuperH-0630/hdangan/src/runtime"
 	"gorm.io/gorm"
 )
 
+const FileOrder = "filesetid desc, fileunionid desc, fileid desc, filegroupid asc, id desc"
+
 var FileMoveRecordNotFound = fmt.Errorf("file move record not found")
 
 func likeValue(t string) string {
 	return fmt.Sprintf("%%%s%%", t)
 }
 
-func SetWhere(tx *gorm.DB, s *SearchWhere) *gorm.DB {
+func SetWhere(tx *gorm.DB, s *SearchWhere, fst FileSetType) *gorm.DB {
 	if s == nil {
 		return tx
 	}
 
+	tx = tx.Where("filesettype = ?", fst)
+
 	if s.Name != "" {
 		tx = tx.Where("name LIKE ?", likeValue(s.Name))
 	}
 
-	if s.IDCard != "" {
-		tx = tx.Where("idcard LIKE ?", likeValue(s.IDCard))
-	}
-
-	if s.Location != "" {
-		tx = tx.Where("loc LIKE ?", likeValue(s.Location))
+	if s.OldName != "" {
+		tx = tx.Where("oldname LIKE ?", likeValue(s.OldName))
 	}
 
-	if s.FileID != 0 {
-		tx = tx.Where("fileid = ?", s.FileID)
-	}
-
-	if s.FileTitle != "" {
-		tx = tx.Where("filetitle LIKE ?", likeValue(s.FileTitle))
-	}
-
-	if s.FileType != "" {
-		tx = tx.Where("filetype LIKE ?", likeValue(s.FileType))
-	}
-
-	if s.FirstMoveInStart.Valid {
-		tx = tx.Where("firstmovein >= ?", s.FirstMoveInStart)
+	if s.IDCard != "" {
+		tx = tx.Where("idcard LIKE ?", likeValue(s.IDCard))
 	}
 
-	if s.FirstMoveInEnd.Valid {
-		tx = tx.Where("firstmovein <= ?", s.FirstMoveInEnd)
+	if s.IsMan == "男性" {
+		tx = tx.Where("isman == 1")
+	} else if s.IsMan == "女性" {
+		tx = tx.Where("isman == 0")
 	}
 
-	if s.LastMoveInStart.Valid {
-		tx = tx.Where("lastmovein >= ?", s.LastMoveInStart)
+	if s.BirthdayStart.Valid {
+		tx = tx.Where("birthday >= ?", s.BirthdayStart.Time)
 	}
 
-	if s.LastMoveInStart.Valid {
-		tx = tx.Where("lastmovein <= ?", s.LastMoveInStart)
+	if s.BirthdayEnd.Valid {
+		tx = tx.Where("birthday <= ?", s.BirthdayEnd.Time)
 	}
 
-	if s.LastMoveOutStart.Valid {
-		tx = tx.Where("lastmoveout >= ?", s.LastMoveOutStart)
+	if s.Comment != "" {
+		tx = tx.Where("comment LIKE ?", likeValue(s.Comment))
 	}
 
-	if s.LastMoveOutStart.Valid {
-		tx = tx.Where("lastmoveout <= ?", s.LastMoveOutStart)
+	if s.FileSetID != 0 {
+		tx = tx.Where("filesetid = ?", s.FileSetID)
 	}
 
-	if s.MoveStatus != "" {
-		tx = tx.Where("movestatus LIKE ?", likeValue(s.MoveStatus))
+	if s.FileUnionID != 0 {
+		tx = tx.Where("fileunionid = ?", s.FileUnionID)
 	}
 
-	if s.MoveOutPeopleName != "" {
-		tx = tx.Where("moveoutpeoplename LIKE ?", likeValue(s.MoveOutPeopleName))
+	if s.FileID != 0 {
+		tx = tx.Where("fileid = ?", s.FileID)
 	}
 
-	if s.MoveOutPeopleUnit != "" {
-		tx = tx.Where("moveoutpeopleunit LIKE ?", likeValue(s.MoveOutPeopleUnit))
+	if s.FileGroupID != 0 {
+		tx = tx.Where("filegroupid = ?", s.FileGroupID)
 	}
 
 	return tx
 }
 
-func GetAllFile(rt runtime.RunTime, s *SearchWhere) ([]File, error) {
+func GetAllFile(rt runtime.RunTime, fst FileSetType, s *SearchWhere, res interface{}) error {
 	db, err := GetDB(rt)
 	if err != nil {
-		return nil, err
+		return err
 	}
 
-	var res []File
-	err = SetWhere(db.Model(&File{}), s).Order("fileid desc, id desc").Find(&res).Error
+	err = SetWhere(db.Model(&FileAbs{}), s, fst).Order(FileOrder).Find(&res).Error
 	if errors.Is(err, gorm.ErrRecordNotFound) {
-		return []File{}, nil
+		return nil
 	} else if err != nil {
-		return nil, err
+		return err
 	}
 
-	return res, nil
+	return nil
 }
 
-func GetNewFileID(rt runtime.RunTime) (int64, error) {
+func GetNewFileID(rt runtime.RunTime, fst FileSetType, sameAbove bool) (fs *FileSet, lst File, unionID int64, fileID int64, groupID int64, err error) {
 	db, err := GetDB(rt)
 	if err != nil {
-		return 0, err
+		return nil, nil, 0, 0, 0, err
 	}
 
-	var res FileIDMax
-	err = db.Model(&File{}).Select("MAX(fileid) AS fid").First(&res).Error
+	fs, err = GetFileSet(rt, fst, sameAbove)
+	if err != nil {
+		return nil, nil, 0, 0, 0, err
+	}
+
+	var last FileAbs
+	err = db.Model(&FileAbs{}).Where("filesettype = ?", fst).Order("fileid desc, fileunionid desc, filegroupid desc").First(&last).Error
 	if errors.Is(err, gorm.ErrRecordNotFound) {
-		res.FileID = 1
+		return nil, nil, 0, 0, 0, fmt.Errorf("not found")
 	} else if err != nil {
-		return 0, err
+		return nil, nil, 0, 0, 0, err
 	}
 
-	if res.FileID < 0 {
-		res.FileID = 0
+	if sameAbove {
+		unionID = last.FileUnionID
+		groupID = last.FileGroupID + 1
+		fileID = last.FileID + 1
+	} else {
+		unionID = last.FileUnionID + 1
+		groupID = 1
+		fileID = last.FileID + 1
 	}
 
-	return res.FileID + 1, err
+	return fs, &last, unionID, fileID, groupID, nil
 }
 
-func CountAllFile(rt runtime.RunTime, s *SearchWhere) (int64, error) {
+func CountAllFile(rt runtime.RunTime, fst FileSetType, s *SearchWhere) (int64, error) {
 	db, err := GetDB(rt)
 	if err != nil {
 		return 0, err
 	}
 
 	var res PageSizeResult
-	err = SetWhere(db.Model(&File{}), s).Select("COUNT(*) AS ps").First(&res).Error
+	err = SetWhere(db.Model(&FileAbs{}), s, fst).Select("COUNT(*) AS ps").First(&res).Error
 	if errors.Is(err, gorm.ErrRecordNotFound) {
 		res.AllCount = 0
 	} else if err != nil {
@@ -137,8 +136,8 @@ func CountAllFile(rt runtime.RunTime, s *SearchWhere) (int64, error) {
 	return res.AllCount, err
 }
 
-func GetPageMax(rt runtime.RunTime, pageItemCount int, s *SearchWhere) (int64, error) {
-	allCount, err := CountAllFile(rt, s)
+func GetPageMax(rt runtime.RunTime, fileSetType FileSetType, pageItemCount int, s *SearchWhere) (int64, error) {
+	allCount, err := CountAllFile(rt, fileSetType, s)
 	if err != nil {
 		return 0, err
 	}
@@ -150,8 +149,8 @@ func GetPageMax(rt runtime.RunTime, pageItemCount int, s *SearchWhere) (int64, e
 	return allCount / int64(pageItemCount), nil
 }
 
-func PageChoiceOffset(rt runtime.RunTime, pageItemCount int, page int64, s *SearchWhere) (int64, int, int64, error) {
-	pageMax, err := GetPageMax(rt, pageItemCount, s)
+func PageChoiceOffset(rt runtime.RunTime, fileSetType FileSetType, pageItemCount int, page int64, s *SearchWhere) (int64, int, int64, error) {
+	pageMax, err := GetPageMax(rt, fileSetType, pageItemCount, s)
 	if err != nil {
 		return 0, 0, 0, err
 	}
@@ -165,57 +164,89 @@ func PageChoiceOffset(rt runtime.RunTime, pageItemCount int, page int64, s *Sear
 	return (page - 1) * int64(pageItemCount), pageItemCount, pageMax, nil
 }
 
-func GetPageData(rt runtime.RunTime, pageItemCount int, page int64, s *SearchWhere) ([]File, int64, error) {
+func GetPageData(rt runtime.RunTime, fst FileSetType, pageItemCount int, page int64, s *SearchWhere, res interface{}) (int64, error) {
 	if pageItemCount <= 0 {
 		pageItemCount = DefaultPageItemCount
 	}
 
 	db, err := GetDB(rt)
 	if err != nil {
-		return []File{}, 0, err
+		return 0, err
 	}
 
-	offset, limit, pageMax, err := PageChoiceOffset(rt, pageItemCount, page, s)
-	res := make([]File, 0, pageItemCount)
-
-	err = SetWhere(db.Model(File{}), s).Limit(limit).Offset(int(offset)).Order("fileid desc, id desc").Find(&res).Error
+	offset, limit, pageMax, err := PageChoiceOffset(rt, fst, pageItemCount, page, s)
+	err = SetWhere(db.Model(FileAbs{}), s, fst).Limit(limit).Offset(int(offset)).Order(FileOrder).Find(res).Error
 	if errors.Is(err, gorm.ErrRecordNotFound) {
-		return make([]File, 0, pageItemCount), 0, nil
+		return 0, nil
 	} else if err != nil {
-		return nil, 0, err
+		return 0, err
 	}
 
-	return res, pageMax, nil
+	return pageMax, nil
 }
 
-func DeleteFile(f *File) error {
-	return db.Delete(f).Error
-}
-
-func CreateFile(f *File) error {
-	return db.Create(f).Error
-}
+func DeleteFile(rt runtime.RunTime, f File) error {
+	db, err := GetDB(rt)
+	if err != nil {
+		return err
+	}
 
-func SaveFile(f *File) error {
-	return db.Save(f).Error
+	return db.Delete(f).Error
 }
 
-func SaveNewMoveOut(f *File, record *FileMoveRecord) error {
-	err := db.Create(record).Error
+func CreateFile(rt runtime.RunTime, fst FileSetType, fc File) error {
+	db, err := GetDB(rt)
 	if err != nil {
 		return err
 	}
 
-	f.LastMoveRecordID = sql.NullInt64{
-		Valid: true,
-		Int64: int64(record.ID),
-	}
+	return db.Transaction(func(tx *gorm.DB) error {
+		fs, lastf, unionid, fildid, groupid, err := GetNewFileID(rt, fst, fc.GetFile().SameAsAbove)
+		if err != nil {
+			return err
+		}
+
+		nf := fc.GetFile()
+		lf := lastf.GetFile()
+		nf.FileUnionID = unionid
+		nf.FileID = fildid
+		nf.FileGroupID = groupid
+		nf.FileSetID = fs.FileSetID
+		nf.FileSetType = fs.FileSetType
+		nf.FileSetSQLID = int64(fs.ID)
+		nf.PageStart = fs.PageCount + 1
+		nf.PageEnd = fs.PageCount + nf.PageCount
+
+		fs.PageCount += nf.PageEnd
+
+		if nf.SameAsAbove {
+			lf.PeopleCount += 1
+			nf.PeopleCount = lf.PeopleCount
+		} else {
+			nf.PeopleCount = 1
+		}
+
+		err = tx.Create(nf).Error
+		if err != nil {
+			return err
+		}
+
+		err = tx.Update("peoplecount", lf.PeopleCount).Error
+		if err != nil {
+			return err
+		}
+
+		err = tx.Save(fs).Error
+		if err != nil {
+			return err
+		}
 
-	return db.Save(f).Error
+		return nil
+	})
 }
 
-func UpdateRecord(f *File, record *FileMoveRecord) error {
-	err := db.Save(record).Error
+func SaveFile(rt runtime.RunTime, f File) error {
+	db, err := GetDB(rt)
 	if err != nil {
 		return err
 	}
@@ -223,28 +254,11 @@ func UpdateRecord(f *File, record *FileMoveRecord) error {
 	return db.Save(f).Error
 }
 
-func FindMoveRecord(f *File) (*FileMoveRecord, error) {
-	if !f.LastMoveRecordID.Valid {
-		return nil, FileMoveRecordNotFound
-	}
-
-	var res FileMoveRecord
-
-	err := db.Model(&FileMoveRecord{}).Where("id = ?", f.LastMoveRecordID.Int64).First(&res).Error
-	if errors.Is(err, gorm.ErrRecordNotFound) {
-		return nil, FileMoveRecordNotFound
-	} else if err != nil {
-		return nil, err
-	}
-
-	return &res, nil
-}
-
-func FindFile(fileID int64) *File {
-	var res File
-	err := db.Model(File{}).Where("fileid = ?", fileID).Order("fileid desc, id desc").First(&res).Error
+func FindFile(rt runtime.RunTime, fileID int64, res interface{}) error {
+	db, err := GetDB(rt)
 	if err != nil {
-		return nil
+		return err
 	}
-	return &res
+
+	return db.Where("fileid = ?", fileID).Order("fileid desc, id desc").First(res).Error
 }

+ 129 - 16
src/model/action_file_record.go

@@ -1,7 +1,9 @@
 package model
 
 import (
+	"database/sql"
 	"errors"
+	"fmt"
 	"github.com/SuperH-0630/hdangan/src/runtime"
 	"gorm.io/gorm"
 )
@@ -31,6 +33,14 @@ func SetRecord(tx *gorm.DB, s *SearchRecord) *gorm.DB {
 		tx = tx.Where("moveoutpeopleunit LIKE ?", likeValue(s.MoveOutPeopleUnit))
 	}
 
+	if s.MoveInPeopleName != "" {
+		tx = tx.Where("moveinpeoplename LIKE ?", likeValue(s.MoveInPeopleName))
+	}
+
+	if s.MoveInPeopleUnit != "" {
+		tx = tx.Where("moveinpeopleunit LIKE ?", likeValue(s.MoveInPeopleUnit))
+	}
+
 	return tx
 }
 
@@ -79,11 +89,13 @@ func PageChoiceOffsetRecord(rt runtime.RunTime, fid int64, pageItemCount int, pa
 	return (page - 1) * int64(pageItemCount), pageItemCount, pageMax, nil
 }
 
-func GetPageDataRecord(rt runtime.RunTime, file *File, pageItemCount int, page int64, s *SearchRecord) ([]FileMoveRecord, int64, error) {
+func GetPageDataRecord(rt runtime.RunTime, f File, pageItemCount int, page int64, s *SearchRecord) ([]FileMoveRecord, int64, error) {
 	if pageItemCount <= 0 {
 		pageItemCount = DefaultPageItemCount
 	}
 
+	file := f.GetFile()
+
 	db, err := GetDB(rt)
 	if err != nil {
 		return []FileMoveRecord{}, 0, err
@@ -102,25 +114,16 @@ func GetPageDataRecord(rt runtime.RunTime, file *File, pageItemCount int, page i
 	return res, pageMax, nil
 }
 
-func FindFileRecord(recordID int64) *FileMoveRecord {
-	var res FileMoveRecord
-	err := db.Model(FileMoveRecord{}).Preload("File").Select("id = ?", recordID).Order("movetime desc, id desc").First(&res).Error
-	if err != nil {
-		return nil
-	}
-	return &res
-}
-
-func GetAllFileRecord(rt runtime.RunTime, f *File, s *SearchRecord) ([]FileMoveRecord, error) {
+func GetAllRecord(rt runtime.RunTime, fc File, s *SearchRecord) ([]FileMoveRecord, error) {
 	db, err := GetDB(rt)
 	if err != nil {
 		return nil, err
 	}
 
 	var res []FileMoveRecord
-	tx := SetRecord(db.Model(&FileMoveRecord{}), s).Preload("File")
-	if f != nil {
-		tx = tx.Where("filesqlid = ?", f.ID)
+	tx := SetRecord(db.Model(&FileMoveRecord{}), s)
+	if fc != nil {
+		tx = tx.Where("fileunionid = ?", fc.GetFile().FileUnionID)
 	}
 	err = tx.Order("movetime desc, id desc").Find(&res).Error
 	if errors.Is(err, gorm.ErrRecordNotFound) {
@@ -132,15 +135,125 @@ func GetAllFileRecord(rt runtime.RunTime, f *File, s *SearchRecord) ([]FileMoveR
 	return res, nil
 }
 
-func CheckFileMoveOut(f *File) (*FileMoveRecord, bool) {
+func CheckFileMoveOut(rt runtime.RunTime, fc File) (*FileMoveRecord, bool) {
+	f := fc.GetFile()
+
 	if !f.LastMoveRecordID.Valid {
 		return nil, false
 	}
 
-	record, err := FindMoveRecord(f)
+	record, err := FindMoveRecord(rt, fc)
 	if err != nil {
 		return nil, false
 	}
 
 	return record, true
 }
+
+func FindMoveRecord(rt runtime.RunTime, fc File) (*FileMoveRecord, error) {
+	db, err := GetDB(rt)
+	if err != nil {
+		return nil, err
+	}
+
+	f := fc.GetFile()
+
+	if !f.LastMoveRecordID.Valid || f.LastMoveRecordID.Int64 <= 0 {
+		return nil, FileMoveRecordNotFound
+	}
+
+	var res FileMoveRecord
+	err = db.Model(&FileMoveRecord{}).Where("id = ?", f.LastMoveRecordID).First(&res).Error
+	if errors.Is(err, gorm.ErrRecordNotFound) {
+		return nil, FileMoveRecordNotFound
+	} else if err != nil {
+		return nil, err
+	}
+
+	return &res, nil
+}
+
+func FindRecord(rt runtime.RunTime, recordID int64) (*FileMoveRecord, error) {
+	db, err := GetDB(rt)
+	if err != nil {
+		return nil, err
+	}
+
+	var res FileMoveRecord
+	err = db.Model(&FileMoveRecord{}).Where("id = ?", recordID).First(&res).Error
+	if errors.Is(err, gorm.ErrRecordNotFound) {
+		return nil, FileMoveRecordNotFound
+	} else if err != nil {
+		return nil, err
+	}
+
+	return &res, nil
+}
+
+func SaveRecord(rt runtime.RunTime, r *FileMoveRecord) error {
+	db, err := GetDB(rt)
+	if err != nil {
+		return err
+	}
+
+	return db.Save(r).Error
+}
+
+func CreateFileRecord(rt runtime.RunTime, fc File, record *FileMoveRecord) error {
+	db, err := GetDB(rt)
+	if err != nil {
+		return err
+	}
+
+	f := fc.GetFile()
+	if f.ID <= 0 {
+		return fmt.Errorf("file not save")
+	}
+
+	return db.Transaction(func(tx *gorm.DB) error {
+		var file1 FileAbs
+		var oldRecord *FileMoveRecord
+
+		oldRecord, err := FindMoveRecord(rt, fc)
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			oldRecord = nil
+		} else {
+			return err
+		}
+
+		err = tx.Where("fileunionid = ?", f.FileUnionID).Order("filegroupid asc").First(&file1).Error
+		err = tx.Save(record).Error
+		if err != nil {
+			return err
+		}
+
+		record.FileSetSQLID = file1.FileSetSQLID
+		record.FileSetID = file1.FileSetID
+		record.FileSetType = file1.FileSetType
+
+		record.FileSQLID = int64(file1.ID)
+		record.FileUnionID = int64(file1.ID)
+
+		if oldRecord != nil {
+			record.UpRecord = sql.NullInt64{Valid: true, Int64: int64(oldRecord.ID)}
+		}
+		err = tx.Save(record).Error
+		if err != nil {
+			return err
+		}
+
+		err = tx.Update("lastmoverecordid", sql.NullInt64{Valid: true, Int64: int64(record.ID)}).Where("fileunionid = ?", file1.FileUnionID).Error
+		if err != nil {
+			return err
+		}
+
+		if oldRecord != nil {
+			oldRecord.NextRecord = sql.NullInt64{Valid: true, Int64: int64(record.ID)}
+			err = tx.Save(oldRecord).Error
+			if err != nil {
+				return err
+			}
+		}
+		return nil
+	})
+}

+ 68 - 0
src/model/action_file_set.go

@@ -0,0 +1,68 @@
+package model
+
+import (
+	"errors"
+	"fmt"
+	"github.com/SuperH-0630/hdangan/src/runtime"
+	"github.com/SuperH-0630/hdangan/src/systeminit"
+	"gorm.io/gorm"
+)
+
+func GetFileSet(rt runtime.RunTime, setType FileSetType, sameAbove bool) (*FileSet, error) {
+	db, err := GetDB(rt)
+	if err != nil {
+		return nil, err
+	}
+
+	return GetFileSetTx(rt, db, setType, sameAbove)
+}
+
+func GetFileSetTx(rt runtime.RunTime, db *gorm.DB, setType FileSetType, sameAbove bool) (*FileSet, error) {
+	c, err := systeminit.GetInit()
+	if err != nil {
+		return nil, err
+	}
+
+	db, err = GetDB(rt)
+	if err != nil {
+		return nil, err
+	}
+
+	var res FileSet
+	err = db.Model(&FileSet{}).Where("filesettype = ?", setType).Order("filesetid desc").First(&res).Error
+	if errors.Is(err, gorm.ErrRecordNotFound) {
+		fs := newFileSet(rt, setType, 1)
+		err := db.Create(fs).Error
+		if err != nil {
+			return nil, err
+		}
+		return fs, nil
+	} else if err != nil {
+		return nil, err
+	}
+
+	if res.FileSetID <= 0 {
+		return nil, fmt.Errorf("file set error")
+	} else if !sameAbove && res.PageCount >= c.Yaml.FileSet.MaxFilePage {
+		fs := newFileSet(rt, setType, res.FileSetID+1)
+		err := db.Create(fs).Error
+		if err != nil {
+			return nil, err
+		}
+		return fs, nil
+	}
+
+	return &res, err
+}
+
+func newFileSet(rt runtime.RunTime, setType FileSetType, fileSetID int64) *FileSet {
+	if fileSetID <= 0 {
+		fileSetID = 1
+	}
+
+	return &FileSet{
+		FileSetID:   fileSetID,
+		FileSetType: setType,
+		PageCount:   0,
+	}
+}

+ 0 - 48
src/model/actionex.go

@@ -1,48 +0,0 @@
-package model
-
-import (
-	"database/sql"
-	"fmt"
-	"gorm.io/gorm"
-)
-
-func SaveFileRecord(file *File, record *FileMoveRecord, oldRecord *FileMoveRecord) error {
-	return db.Transaction(func(tx *gorm.DB) error {
-		if file != nil {
-			err := tx.Save(file).Error
-			if err != nil {
-				return err
-			}
-		} else {
-			return fmt.Errorf("not file")
-		}
-
-		if record != nil {
-			record.FileSQLID = int64(file.ID)
-			if oldRecord != nil {
-				record.UpRecord = sql.NullInt64{Valid: true, Int64: int64(oldRecord.ID)}
-			}
-			err := tx.Save(record).Error
-			if err != nil {
-				return err
-			}
-		} else {
-			return fmt.Errorf("not human")
-		}
-
-		file.LastMoveRecordID = sql.NullInt64{Valid: true, Int64: int64(record.ID)}
-		err := tx.Save(file).Error
-		if err != nil {
-			return err
-		}
-
-		if oldRecord != nil {
-			oldRecord.NextRecord = sql.NullInt64{Valid: true, Int64: int64(record.ID)}
-			err = tx.Save(oldRecord).Error
-			if err != nil {
-				return err
-			}
-		}
-		return nil
-	})
-}

+ 2 - 19
src/model/actiontongji.go

@@ -15,14 +15,14 @@ type CountAllWithFile struct {
 	Res  int64  `gorm:"column:ps"`
 }
 
-func CountFile(rt runtime.RunTime) (int64, error) {
+func CountFile(rt runtime.RunTime, model interface{}) (int64, error) {
 	db, err := GetDB(rt)
 	if err != nil {
 		return 0, err
 	}
 
 	var res CountAll
-	err = db.Model(&File{}).Select("COUNT(*) AS ps").First(&res).Error
+	err = db.Model(model).Select("COUNT(*) AS ps").First(&res).Error
 	if errors.Is(err, gorm.ErrRecordNotFound) {
 		return 0, nil
 	} else if err != nil {
@@ -31,20 +31,3 @@ func CountFile(rt runtime.RunTime) (int64, error) {
 
 	return res.Res, err
 }
-
-func CountDifferentFile(rt runtime.RunTime) ([]CountAllWithFile, error) {
-	db, err := GetDB(rt)
-	if err != nil {
-		return nil, err
-	}
-
-	var res []CountAllWithFile
-	err = db.Model(&File{}).Select("COUNT(*) AS ps, filetype").Group("filetype").Find(&res).Error
-	if errors.Is(err, gorm.ErrRecordNotFound) {
-		return []CountAllWithFile{}, nil
-	} else if err != nil {
-		return nil, err
-	}
-
-	return res, err
-}

+ 3 - 1
src/model/auto.go

@@ -10,5 +10,7 @@ func AutoCreateModel(rt runtime.RunTime) error {
 		return err
 	}
 
-	return db.AutoMigrate(&File{}, &FileMoveRecord{})
+	return db.AutoMigrate(&FileSet{}, &FileQianRu{}, &FileChuSheng{}, &FileQianChu{}, &FileSiWang{},
+		&FileBianGeng{}, &FileSuoNeiYiJu{}, &FileSuoJianYiJu{}, &FileNongZiZhuanFei{},
+		&FileYiZhanShiQianYiZheng{}, &FileMoveRecord{})
 }

+ 5 - 10
src/model/driver.go

@@ -10,15 +10,15 @@ import (
 	"path"
 )
 
-var db *gorm.DB
+var _db *gorm.DB
 
 var gormConfig = &gorm.Config{}
 
 func startDriver(rt runtime.RunTime) (*gorm.DB, error) {
 	var err error
 
-	if db != nil {
-		return db, nil
+	if _db != nil {
+		return _db, nil
 	}
 
 	initConfig, err := systeminit.GetInit()
@@ -32,20 +32,15 @@ func startDriver(rt runtime.RunTime) (*gorm.DB, error) {
 
 	dbPath := path.Join(initConfig.HomeDir, "data.db")
 
-	db, err = gorm.Open(sqlite.Open(dbPath), gormConfig)
+	_db, err = gorm.Open(sqlite.Open(dbPath), gormConfig)
 	if err != nil {
 		rt.DBConnectError(fmt.Errorf("配置文件错误,请检查配置文件状态。"))
 		return nil, fmt.Errorf("数据库链接错误: %s", err.Error())
 	}
 
-	return db, nil
+	return _db, nil
 }
 
 func GetDB(rt runtime.RunTime) (*gorm.DB, error) {
 	return startDriver(rt)
 }
-
-func StopDB(rt runtime.RunTime) {
-	if db != nil {
-	}
-}

+ 189 - 22
src/model/model.go

@@ -6,45 +6,212 @@ import (
 	"time"
 )
 
-// File 档案
-type File struct {
+type FileSetType int64
+
+const (
+	QianRu FileSetType = iota
+	ChuSheng
+	QianChu
+	SiWang
+	BianGeng
+	SuoNeiYiJu
+	SuoJianYiJu
+	NongZiZhuanFei
+	YiZhanShiQianYiZheng
+)
+
+var FileSetTypeList = []FileSetType{QianRu, ChuSheng, QianChu, SiWang, BianGeng, SuoNeiYiJu, SuoJianYiJu, NongZiZhuanFei, YiZhanShiQianYiZheng}
+var FileSetTypeName = map[FileSetType]string{
+	QianRu:               "迁入",
+	ChuSheng:             "出生",
+	QianChu:              "迁出",
+	SiWang:               "死亡",
+	BianGeng:             "变更",
+	SuoNeiYiJu:           "所内移居",
+	SuoJianYiJu:          "所间移居",
+	NongZiZhuanFei:       "农(自)转非",
+	YiZhanShiQianYiZheng: "一站式迁移证",
+}
+var FileSetTypeID = map[string]FileSetType{
+	"迁入":     QianRu,
+	"出生":     ChuSheng,
+	"迁出":     QianChu,
+	"死亡":     SiWang,
+	"变更":     BianGeng,
+	"所内移居":   SuoNeiYiJu,
+	"所间移居":   SuoJianYiJu,
+	"农(自)转非": NongZiZhuanFei,
+	"一站式迁移证": YiZhanShiQianYiZheng,
+}
+var FileSetTypeMaker = map[FileSetType]func() File{
+	QianRu: func() File {
+		return &FileQianRu{}
+	},
+	ChuSheng: func() File {
+		return &FileChuSheng{}
+	},
+	QianChu: func() File {
+		return &FileQianChu{}
+	},
+	SiWang: func() File {
+		return &FileSiWang{}
+	},
+	BianGeng: func() File {
+		return &FileBianGeng{}
+	},
+	SuoNeiYiJu: func() File {
+		return &FileSuoNeiYiJu{}
+	},
+	SuoJianYiJu: func() File {
+		return &FileSuoJianYiJu{}
+	},
+	NongZiZhuanFei: func() File {
+		return &FileNongZiZhuanFei{}
+	},
+	YiZhanShiQianYiZheng: func() File {
+		return &FileYiZhanShiQianYiZheng{}
+	},
+}
+
+type FileSet struct {
+	gorm.Model
+	FileSetID   int64       `gorm:"column:filesetid;type:uint;not null;index:fileset"`
+	FileSetType FileSetType `gorm:"column:filesettype;type:uint;not null;index:fileset"`
+	PageCount   int64       `gorm:"column:pagecount;type:uint;not null"`
+
+	File []FileAbs `gorm:"foreignKey:FileSetSQLID;references:ID"`
+}
+
+type File interface {
+	GetFile() *FileAbs
+}
+
+// FileAbs 档案
+type FileAbs struct {
 	gorm.Model
 
+	FileSetSQLID int64       `gorm:"column:filesetsqlid;type:uint;not null"`
+	FileSetID    int64       `gorm:"column:filesetid;type:uint;not null"`
+	FileSetType  FileSetType `gorm:"column:filesettype;type:uint;not null"`
+	FileSet      FileSet     `gorm:"foreignKey:FileSetSQLID;references:ID"`
+
+	FileUnionID int64 `gorm:"column:fileunionid;type:uint;not null"` // 联合ID
+	FileGroupID int64 `gorm:"column:filegroupid;type:uint;not null"` // 组内id
+	FileID      int64 `gorm:"column:fileid;type:uint;not null"`
+
 	// 档案人基本信息
-	Name     string `gorm:"column:name;type:VARCHAR(50);not null;index:people"`
-	IDCard   string `gorm:"column:idcard;type:VARCHAR(20);not null;index:people"`
-	Location string `gorm:"column:loc;type:VARCHAR(150);not null;index:people"`
+	Name     string         `gorm:"column:name;type:VARCHAR(50);not null"`
+	OldName  sql.NullString `gorm:"column:oldname;type:VARCHAR(50);not null"`
+	IDCard   sql.NullString `gorm:"column:idcard;type:VARCHAR(20)"`
+	IsMan    bool           `gorm:"column:isman;type:BOOL;not null"`
+	Birthday time.Time      `gorm:"column:birthday;type:DATE;not null"`
+	Comment  sql.NullString `gorm:"column:comment;type:TEXT"`
 
-	// 档案基本inxi
-	FileID      int64          `gorm:"column:fileid;type:uint;not null;index:file"`
-	FileTitle   string         `gorm:"column:filetitle;type:VARCHAR(50);not null;index:file"`
-	FileType    string         `gorm:"column:filetype;type:VARCHAR(20);not null;index:file"`
-	FileComment sql.NullString `gorm:"column:filecomment;type:TEXT"`
+	// 档案联合
+	SameAsAbove bool  `gorm:"column:sameabove;type:BOOL;not null"`
+	PeopleCount int64 `gorm:"column:peoplecount;type:uint;not null"`
 
-	// 迁入迁出
-	FirstMoveIn time.Time `gorm:"column:firstmovein;type:DATETIME;not null"`
-	LastMoveIn  time.Time `gorm:"column:lastmovein;type:DATETIME;not null"`
+	// 档案信息
+	Time time.Time `gorm:"column:time;type:DATETIME;not null"`
 
-	// 最新一次迁出记录
-	LastMoveRecordID  sql.NullInt64    `gorm:"column:lastmoverecordid;type:uint"`
-	MoveStatus        string           `gorm:"column:movestatus;type:VARCHAR(20);not null"`
-	MoveOutPeopleName sql.NullString   `gorm:"column:moveoutpeoplename;type:VARCHAR(10)"`
-	MoveOutPeopleUnit sql.NullString   `gorm:"column:moveoutpeopleunit;type:VARCHAR(50)"`
-	MoveComment       sql.NullString   `gorm:"column:movecomment;type:TEXT"`
-	MoveRecord        []FileMoveRecord `gorm:"foreignKey:FileSQLID;references:ID"`
+	PageStart int64 `gorm:"column:pagestart;type:uint;not null"`
+	PageEnd   int64 `gorm:"column:pageend;type:uint;not null"`
+	PageCount int64 `gorm:"column:pagecount;type:uint;not null"`
+
+	BeiKao   sql.NullString `gorm:"column:beikao;type:VARCHAR(50);"`
+	Material sql.NullString `gorm:"column:material;type:VARCHAR(500);"`
+
+	// 出入库
+	LastMoveRecordID sql.NullInt64 `gorm:"column:lastmoverecordid;type:uint;"`
+}
+
+func (f *FileAbs) GetFile() *FileAbs {
+	return f
+}
+
+type FileQianRu struct {
+	// 迁入
+	FileAbs
+	Type        string `gorm:"column:type;type:VARCHAR(20);not null"`
+	OldLocation string `gorm:"column:oldloc;type:VARCHAR(50);not null"`
+	NewLocation string `gorm:"column:newloc;type:VARCHAR(50);not null"`
+}
+
+type FileChuSheng struct {
+	// 出生
+	FileAbs
+	Type        string `gorm:"column:type;type:VARCHAR(20);not null"`
+	NewLocation string `gorm:"column:newloc;type:VARCHAR(50);not null"`
+}
+
+type FileQianChu struct {
+	// 迁出
+	FileAbs
+	Type        string `gorm:"column:type;type:VARCHAR(20);not null"`
+	NewLocation string `gorm:"column:newloc;type:VARCHAR(50);not null"`
+}
+
+type FileSiWang struct {
+	// 死亡
+	FileAbs
+	Type     string `gorm:"column:type;type:VARCHAR(20);not null"`
+	Location string `gorm:"column:loc;type:VARCHAR(50);not null"`
+}
+
+type FileBianGeng struct {
+	// 变更
+	FileAbs
+	Type     string `gorm:"column:type;type:VARCHAR(20);not null"`
+	Location string `gorm:"column:loc;type:VARCHAR(50);not null"`
+}
+
+type FileSuoNeiYiJu struct {
+	// 所内移居
+	FileAbs
+	Type     string `gorm:"column:type;type:VARCHAR(20);not null"`
+	Location string `gorm:"column:loc;type:VARCHAR(50);not null"`
+}
+
+type FileSuoJianYiJu struct {
+	// 所间移居
+	FileAbs
+	Type     string `gorm:"column:type;type:VARCHAR(20);not null"`
+	Location string `gorm:"column:loc;type:VARCHAR(50);not null"`
+}
+
+type FileNongZiZhuanFei struct {
+	// 农自转非
+	FileAbs
+	Type     string `gorm:"column:type;type:VARCHAR(20);not null"`
+	Location string `gorm:"column:loc;type:VARCHAR(50);not null"`
+}
+
+type FileYiZhanShiQianYiZheng struct {
+	// 一站式迁移证
+	FileAbs
+	Type     string `gorm:"column:type;type:VARCHAR(20);not null"`
+	Location string `gorm:"column:loc;type:VARCHAR(50);not null"`
 }
 
 type FileMoveRecord struct {
 	gorm.Model
 
-	FileSQLID  int64         `gorm:"column:filesqlid;type:uint;not null"`
+	FileSetSQLID int64       `gorm:"column:filesetsqlid;type:uint;not null"`
+	FileSetID    int64       `gorm:"column:filesetid;type:uint;not null"`
+	FileSetType  FileSetType `gorm:"column:filesettype;type:uint;not null"`
+
+	FileSQLID   int64 `gorm:"column:filesqlid;type:uint;not null"`
+	FileUnionID int64 `gorm:"column:fileunionid;type:uint;not null"`
+
 	UpRecord   sql.NullInt64 `gorm:"column:uprecord;type:uint"`
 	NextRecord sql.NullInt64 `gorm:"column:nextrecord;type:uint"`
-	File       File          `gorm:"foreignKey:FileSQLID;references:ID"`
+	File       FileAbs       `gorm:"foreignKey:FileSQLID;references:ID"`
 
 	MoveStatus        string         `gorm:"column:movestatus;type:VARCHAR(20);not null"`
 	MoveTime          time.Time      `gorm:"column:movetime;type:DATETIME;not null"`
 	MoveOutPeopleName sql.NullString `gorm:"column:moveoutpeoplename;type:VARCHAR(10)"`
 	MoveOutPeopleUnit sql.NullString `gorm:"column:moveoutpeopleunit;type:VARCHAR(50)"`
+	MoveInPeopleName  sql.NullString `gorm:"column:moveinpeoplename;type:VARCHAR(10)"`
+	MoveInPeopleUnit  sql.NullString `gorm:"column:moveinpeopleunit;type:VARCHAR(50)"`
 	MoveComment       sql.NullString `gorm:"column:movecomment;type:TEXT"`
 }

+ 14 - 20
src/model/where.go

@@ -5,26 +5,18 @@ import (
 )
 
 type SearchWhere struct {
-	Name      string
-	IDCard    string
-	Location  string
-	FileID    int64
-	FileTitle string
-	FileType  string
-
-	FirstMoveInStart sql.NullTime
-	FirstMoveInEnd   sql.NullTime
-
-	LastMoveInStart sql.NullTime
-	LastMoveInEnd   sql.NullTime
-
-	LastMoveOutStart sql.NullTime
-	LastMoveOutEnd   sql.NullTime
-
-	MoveStatus string
-
-	MoveOutPeopleName string
-	MoveOutPeopleUnit string
+	Name          string
+	OldName       string
+	IDCard        string
+	IsMan         string
+	BirthdayStart sql.NullTime
+	BirthdayEnd   sql.NullTime
+	Comment       string
+
+	FileSetID   int64
+	FileUnionID int64
+	FileID      int64
+	FileGroupID int64
 }
 
 type SearchRecord struct {
@@ -34,4 +26,6 @@ type SearchRecord struct {
 	MoveStatus        string
 	MoveOutPeopleName string
 	MoveOutPeopleUnit string
+	MoveInPeopleName  string
+	MoveInPeopleUnit  string
 }

+ 45 - 10
src/systeminit/init.go

@@ -70,21 +70,52 @@ func (m *Move) init() error {
 	return nil
 }
 
+type BeiKao struct {
+	Name     string   `yaml:"name"`
+	Material []string `yaml:"material"`
+}
+
+func (f *BeiKao) init() error {
+	if len(f.Name) == 0 {
+		return fmt.Errorf("not name")
+	}
+	return nil
+}
+
 type File struct {
-	FileType []string `yaml:"fileType"`
+	FileType map[string][]string `yaml:"fileType"`
+	BeiKao   map[string][]BeiKao `yaml:"beiKao"`
 }
 
 func (f *File) init() error {
-	if len(f.FileType) == 0 {
-		f.FileType = append(f.FileType, "常规")
+	for _, s := range f.BeiKao {
+		for _, i := range s {
+			err := i.init()
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	return nil
+}
+
+type FileSet struct {
+	MaxFilePage int64 `yaml:"maxFilePage"`
+}
+
+func (f *FileSet) init() error {
+	if f.MaxFilePage <= 0 {
+		f.MaxFilePage = 75
 	}
 	return nil
 }
 
 type Init struct {
-	Report Report `yaml:"report"`
-	Move   Move   `yaml:"move"`
-	File   File   `yaml:"file"`
+	Report  Report  `yaml:"report"`
+	Move    Move    `yaml:"move"`
+	File    File    `yaml:"file"`
+	FileSet FileSet `yaml:"fileSet"`
 }
 
 func (i *Init) init() (err error) {
@@ -103,6 +134,11 @@ func (i *Init) init() (err error) {
 		return err
 	}
 
+	err = i.FileSet.init()
+	if err != nil {
+		return err
+	}
+
 	return nil
 }
 
@@ -200,10 +236,9 @@ func GetInit() (InitConfig, error) {
 						"三中队",
 					},
 				},
-				File: File{
-					FileType: []string{
-						"常规",
-					},
+				File: File{},
+				FileSet: FileSet{
+					MaxFilePage: 75,
 				},
 			},
 			HomeDir:   homeDir,

+ 18 - 17
src/v1main/ctrl.go

@@ -18,7 +18,7 @@ type CtrlWindow struct {
 
 var ctrlWindow *CtrlWindow
 
-func newWindow(rt runtime.RunTime, title string, killSecond time.Duration) *CtrlWindow {
+func newCtrlWindow(rt runtime.RunTime, title string, killSecond time.Duration) *CtrlWindow {
 	ks := killSecond * time.Second
 	cw := &CtrlWindow{
 		window:   rt.App().NewWindow(title),
@@ -27,16 +27,17 @@ func newWindow(rt runtime.RunTime, title string, killSecond time.Duration) *Ctrl
 		rt:       rt,
 	}
 
-	cw.window.SetOnClosed(func() {
-		ctrlWindow = nil
-		ShowHelloWindow(rt)
+	rt.SetDBConnectErrorWindow(cw.window)
+	rt.SetAction(func() {
+		cw.Action()
 	})
 
-	cw.window.SetCloseIntercept(func() {
-		WinClose(ctrlWindow.window)
-		ctrlWindow = nil
-		ShowHelloWindow(rt)
+	cw.menu = getMainMenu(rt, cw, func(rt runtime.RunTime) {
+		cw.table.UpdateTable(rt, cw.table.fileSetType, 0, cw.menu.NowPage)
 	})
+	cw.window.SetMainMenu(cw.menu.Main)
+
+	cw.table = CreateInfoTable(rt, cw)
 
 	return cw
 }
@@ -86,18 +87,18 @@ func createCtrlWindow(rt runtime.RunTime) error {
 		return nil
 	}
 
-	ctrlWindow = newWindow(rt, "桓档案-控制中心", 15*60)
-	rt.SetDBConnectErrorWindow(ctrlWindow.window)
-	rt.SetAction(func() {
-		ctrlWindow.Action()
-	})
+	ctrlWindow = newCtrlWindow(rt, "桓档案-控制中心", 15*60)
 
-	ctrlWindow.menu = getMainMenu(rt, ctrlWindow, func(rt runtime.RunTime) {
-		ctrlWindow.table.UpdateTable(rt, 0, ctrlWindow.menu.NowPage)
+	ctrlWindow.window.SetOnClosed(func() {
+		ctrlWindow = nil
+		ShowHelloWindow(rt)
 	})
-	ctrlWindow.window.SetMainMenu(ctrlWindow.menu.Main)
 
-	ctrlWindow.table = CreateInfoTable(rt, ctrlWindow)
+	ctrlWindow.window.SetCloseIntercept(func() {
+		WinClose(ctrlWindow.window)
+		ctrlWindow = nil
+		ShowHelloWindow(rt)
+	})
 
 	bg := NewBg(fmax(ctrlWindow.table.fileTable.MinSize().Width, ctrlWindow.table.fileTable.Size().Width, 800),
 		fmax(ctrlWindow.table.fileTable.MinSize().Height, ctrlWindow.table.fileTable.Size().Height, 500))

+ 56 - 13
src/v1main/ctrlmainmenu.go

@@ -5,7 +5,7 @@ import (
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2/dialog"
 	"github.com/SuperH-0630/hdangan/src/aboutme"
-	"github.com/SuperH-0630/hdangan/src/excelreader"
+	"github.com/SuperH-0630/hdangan/src/excelio"
 	"github.com/SuperH-0630/hdangan/src/model"
 	"github.com/SuperH-0630/hdangan/src/runtime"
 	"os"
@@ -16,6 +16,7 @@ type MainMenu struct {
 	Window  *CtrlWindow
 	Main    *fyne.MainMenu
 	Page    *fyne.Menu
+	FileSet *fyne.Menu
 	NowPage int64
 }
 
@@ -73,12 +74,20 @@ func getMainMenu(rt runtime.RunTime, w *CtrlWindow, refresh func(rt runtime.RunT
 
 	newFile := fyne.NewMenuItem("新建档案", func() {
 		rt.Action()
-		ShowNew(rt, refresh)
+		ShowNew(rt, w, refresh)
 	})
 
 	exclFile := fyne.NewMenuItem("模板导入", func() {
 		rt.Action()
-		err := AddFromFile(rt, w.window, refresh)
+		err := AddFromFile(rt, w, refresh)
+		if err != nil {
+			dialog.ShowError(fmt.Errorf("导入失败:%s", err.Error()), w.window)
+		}
+	})
+
+	exclRecordFile := fyne.NewMenuItem("模板导入(借出记录)", func() {
+		rt.Action()
+		err := AddRecordFromFile(rt, w, refresh)
 		if err != nil {
 			dialog.ShowError(fmt.Errorf("导入失败:%s", err.Error()), w.window)
 		}
@@ -102,7 +111,7 @@ func getMainMenu(rt runtime.RunTime, w *CtrlWindow, refresh func(rt runtime.RunT
 				dialog.ShowError(fmt.Errorf("文件名必须以.xlsx结尾"), w.window)
 			}
 
-			err = excelreader.CreateTemplate(rt, writer)
+			err = excelio.CreateTemplate(rt, writer)
 			if err != nil {
 				dialog.ShowError(fmt.Errorf("模板保存失败:%s", err), w.window)
 			}
@@ -114,7 +123,7 @@ func getMainMenu(rt runtime.RunTime, w *CtrlWindow, refresh func(rt runtime.RunT
 
 	tj := fyne.NewMenuItem("数据统计", func() {
 		rt.Action()
-		TongJi(rt, w.window)
+		TongJi(rt, w)
 	})
 
 	outputAll := fyne.NewMenuItem("导出全部档案", func() {
@@ -137,7 +146,7 @@ func getMainMenu(rt runtime.RunTime, w *CtrlWindow, refresh func(rt runtime.RunT
 				dialog.ShowError(fmt.Errorf("文件名必须以.xlsx结尾"), w.window)
 			}
 
-			err = excelreader.OutputFile(rt, savepath, []model.File{}, nil)
+			err = excelio.OutputFile(rt, w.table.fileSetType, savepath, nil, nil)
 			if err != nil {
 				dialog.ShowError(fmt.Errorf("生成数据库遇到错误:%s", err), w.window)
 			} else {
@@ -169,7 +178,7 @@ func getMainMenu(rt runtime.RunTime, w *CtrlWindow, refresh func(rt runtime.RunT
 				dialog.ShowError(fmt.Errorf("文件名必须以.xlsx结尾"), w.window)
 			}
 
-			err = excelreader.OutputFile(rt, savepath, []model.File{}, &wm.Window.table.whereInfo)
+			err = excelio.OutputFile(rt, w.table.fileSetType, savepath, nil, &wm.Window.table.whereInfo)
 			if err != nil {
 				dialog.ShowError(fmt.Errorf("生成数据库遇到错误:%s", err), w.window)
 			} else {
@@ -201,7 +210,7 @@ func getMainMenu(rt runtime.RunTime, w *CtrlWindow, refresh func(rt runtime.RunT
 				dialog.ShowError(fmt.Errorf("文件名必须以.xlsx结尾"), w.window)
 			}
 
-			err = excelreader.OutputFile(rt, savepath, wm.Window.table.InfoFile, nil)
+			err = excelio.OutputFile(rt, w.table.fileSetType, savepath, wm.Window.table.InfoFile, nil)
 			if err != nil {
 				dialog.ShowError(fmt.Errorf("生成数据库遇到错误:%s", err), w.window)
 			} else {
@@ -233,7 +242,7 @@ func getMainMenu(rt runtime.RunTime, w *CtrlWindow, refresh func(rt runtime.RunT
 				dialog.ShowError(fmt.Errorf("文件名必须以.xlsx结尾"), w.window)
 			}
 
-			err = excelreader.OutputFileRecord(rt, savepath, nil, nil, nil)
+			err = excelio.OutputFileRecord(rt, savepath, nil, nil, nil)
 			if err != nil {
 				dialog.ShowError(fmt.Errorf("生成数据库遇到错误:%s", err), w.window)
 			} else {
@@ -266,7 +275,7 @@ func getMainMenu(rt runtime.RunTime, w *CtrlWindow, refresh func(rt runtime.RunT
 					dialog.ShowError(fmt.Errorf("文件名必须以.xlsx结尾"), w.window)
 				}
 
-				err = excelreader.OutputFileRecord(rt, savepath, nil, nil, &s.SearchRecord)
+				err = excelio.OutputFileRecord(rt, savepath, nil, nil, &s.SearchRecord)
 				if err != nil {
 					dialog.ShowError(fmt.Errorf("生成数据库遇到错误:%s", err), w.window)
 				} else {
@@ -280,7 +289,8 @@ func getMainMenu(rt runtime.RunTime, w *CtrlWindow, refresh func(rt runtime.RunT
 		ww.Show(rt)
 	})
 
-	xitong := fyne.NewMenu("系统", newFile, exclFile, template, aboutMe, quit)
+	xitong := fyne.NewMenu("系统", newFile, exclFile, exclRecordFile, template, aboutMe, quit)
+	wm.FileSet = fyne.NewMenu("档案类型")
 	peizhi := fyne.NewMenu("配置", initFile, openFile, saveFile, copyFile)
 	sousuo := fyne.NewMenu("搜索", search)
 	wm.Page = fyne.NewMenu("分页")
@@ -291,6 +301,39 @@ func getMainMenu(rt runtime.RunTime, w *CtrlWindow, refresh func(rt runtime.RunT
 	return wm
 }
 
+func (m *MainMenu) ChangeFileSetModelItem(rt runtime.RunTime, pageItemCount int, message string) {
+	setFileList := make([]*fyne.MenuItem, 0, len(model.FileSetTypeList))
+
+	for _, t := range model.FileSetTypeList {
+		name, ok := model.FileSetTypeName[t]
+		if !ok {
+			continue
+		}
+
+		if t == m.Window.table.fileSetType {
+			name += "(当前)"
+			setFileList = append(setFileList, fyne.NewMenuItem(name, func() {
+				dialog.ShowConfirm("是否需要重载?", message, func(b bool) {
+					rt.Action()
+					if !b {
+						return
+					}
+					m.Window.table.UpdateTable(rt, t, pageItemCount, m.NowPage)
+				}, m.Window.window)
+			}))
+		} else {
+			setFileList = append(setFileList, fyne.NewMenuItem(name, func() {
+				rt.Action()
+				m.Window.table.UpdateTable(rt, t, pageItemCount, 1)
+			}))
+		}
+	}
+
+	m.FileSet.Items = setFileList
+	m.FileSet.Refresh()
+	m.Main.Refresh()
+}
+
 func (m *MainMenu) ChangePageMenuItem(rt runtime.RunTime, pageItemCount int, p int64, pageMax int64, message string) {
 	pageList := make([]*fyne.MenuItem, 0, pageMax)
 	if pageMax <= 0 {
@@ -313,14 +356,14 @@ func (m *MainMenu) ChangePageMenuItem(rt runtime.RunTime, pageItemCount int, p i
 						if !b {
 							return
 						}
-						m.Window.table.UpdateTable(rt, pageItemCount, i)
+						m.Window.table.UpdateTable(rt, m.Window.table.fileSetType, pageItemCount, i)
 					}, m.Window.window)
 				})
 				pageList = append(pageList, m)
 			} else {
 				pageList = append(pageList, fyne.NewMenuItem(fmt.Sprintf("第%d页", i), func() {
 					rt.Action()
-					m.Window.table.UpdateTable(rt, pageItemCount, i)
+					m.Window.table.UpdateTable(rt, m.Window.table.fileSetType, pageItemCount, i)
 				}))
 			}
 		}

+ 276 - 61
src/v1main/edit.go

@@ -2,7 +2,6 @@ package v1main
 
 import (
 	"database/sql"
-	"errors"
 	"fmt"
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2/container"
@@ -12,18 +11,14 @@ import (
 	"github.com/SuperH-0630/hdangan/src/model"
 	"github.com/SuperH-0630/hdangan/src/runtime"
 	"github.com/SuperH-0630/hdangan/src/systeminit"
+	datepicker "github.com/sdassow/fyne-datepicker"
 	"strconv"
+	"strings"
+	"time"
 )
 
-func ShowEdit(rt runtime.RunTime, f *model.File, refresh func(rt runtime.RunTime)) {
-	config, err := systeminit.GetInit()
-	if errors.Is(err, systeminit.LuckyError) {
-		rt.DBConnectError(err)
-		return
-	} else if err != nil {
-		rt.DBConnectError(fmt.Errorf("配置文件错误,请检查配置文件状态。"))
-		return
-	}
+func ShowEdit(rt runtime.RunTime, fc model.File, refresh func(rt runtime.RunTime)) {
+	f := fc.GetFile()
 
 	infoWindow := rt.App().NewWindow(fmt.Sprintf("编辑信息-%s-%d", f.Name, f.FileID))
 
@@ -37,38 +32,150 @@ func ShowEdit(rt runtime.RunTime, f *model.File, refresh func(rt runtime.RunTime
 		infoWindow = nil
 	})
 
-	fileComment := widget.NewMultiLineEntry()
-	fileComment.Text = strToStr(f.FileComment, "")
-	fileComment.Wrapping = fyne.TextWrapWord
-	fileComment.OnChanged = func(text string) {
-		f.FileComment = sql.NullString{String: text, Valid: len(text) != 0}
+	comment := widget.NewMultiLineEntry()
+	comment.Text = strToStr(f.Comment, "")
+	comment.Wrapping = fyne.TextWrapWord
+	comment.OnChanged = func(s string) {
+		rt.Action()
+		comment.Text = strToStr(f.Comment, "")
+	}
+
+	material := widget.NewMultiLineEntry()
+	material.Text = strToStr(f.Comment, "")
+	material.Wrapping = fyne.TextWrapWord
+	material.OnChanged = func(s string) {
+		rt.Action()
+		material.Text = strToStr(f.Comment, "")
+	}
+
+	if !f.OldName.Valid {
+		f.OldName.String = ""
+	}
+
+	if !f.IDCard.Valid {
+		f.IDCard.String = ""
+	}
+
+	if !f.Comment.Valid {
+		f.Comment.String = ""
 	}
 
 	leftLayout := layout.NewFormLayout()
 	left := container.New(leftLayout,
-		widget.NewLabel("卷宗号:"),
-		newFileIDEntry(f.FileID, &f.FileID),
-
 		widget.NewLabel("姓名:"),
 		newEntry(fmt.Sprintf("%s", f.Name), &f.Name),
 
-		widget.NewLabel("身份证号:"),
-		newEntry(fmt.Sprintf("%s", f.IDCard), &f.IDCard),
+		widget.NewLabel("曾用名:"),
+		newEntryValid(fmt.Sprintf("%s", f.OldName.String), &f.OldName),
 
-		widget.NewLabel("户籍地:"),
-		newEntry(fmt.Sprintf("%s", f.Location), &f.Location),
+		widget.NewLabel("身份证号:"),
+		newEntryValid(fmt.Sprintf("%s", f.IDCard.String), &f.IDCard),
 
-		widget.NewLabel("卷宗标题:"),
-		newEntry(fmt.Sprintf("%s", f.FileTitle), &f.FileTitle),
+		widget.NewLabel("性别:"),
+		newSexSelect(f.IsMan, &f.IsMan),
 
-		widget.NewLabel("卷宗类型:"),
-		newFileTypeSelect(fmt.Sprintf("%s", f.FileType), config.Yaml.File.FileType, &f.FileType),
+		widget.NewLabel("出生日期:"),
+		newDatePicker(f.Birthday, &f.Birthday, infoWindow),
 
 		widget.NewLabel("卷宗备注:"),
-		fileComment,
+		comment,
 	)
 
-	upBox := container.NewHBox(left)
+	rightDigit := make([]fyne.CanvasObject, 0, 10)
+
+	switch ff := fc.(type) {
+	case *model.FileQianRu:
+		rightDigit = append(rightDigit,
+			widget.NewLabel("类型:"),
+			newFileTypeSelect(ff.Type, ff.FileSetType, &ff.Type),
+
+			widget.NewLabel("旧地址:"),
+			newEntry(ff.OldLocation, &ff.OldLocation),
+
+			widget.NewLabel("新地址:"),
+			newEntry(ff.NewLocation, &ff.NewLocation),
+		)
+	case *model.FileChuSheng:
+		rightDigit = append(rightDigit,
+			widget.NewLabel("类型:"),
+			newFileTypeSelect(ff.Type, ff.FileSetType, &ff.Type),
+
+			widget.NewLabel("地址:"),
+			newEntry(ff.NewLocation, &ff.NewLocation),
+		)
+	case *model.FileQianChu:
+		rightDigit = append(rightDigit,
+			widget.NewLabel("类型:"),
+			newFileTypeSelect(ff.Type, ff.FileSetType, &ff.Type),
+
+			widget.NewLabel("新地址:"),
+			newEntry(ff.NewLocation, &ff.NewLocation),
+		)
+	case *model.FileSiWang:
+		rightDigit = append(rightDigit,
+			widget.NewLabel("类型:"),
+			newFileTypeSelect(ff.Type, ff.FileSetType, &ff.Type),
+
+			widget.NewLabel("地址:"),
+			newEntry(ff.Location, &ff.Location),
+		)
+	case *model.FileBianGeng:
+		rightDigit = append(rightDigit,
+			widget.NewLabel("类型:"),
+			newFileTypeSelect(ff.Type, ff.FileSetType, &ff.Type),
+
+			widget.NewLabel("地址:"),
+			newEntry(ff.Location, &ff.Location),
+		)
+	case *model.FileSuoNeiYiJu:
+		rightDigit = append(rightDigit,
+			widget.NewLabel("类型:"),
+			newFileTypeSelect(ff.Type, ff.FileSetType, &ff.Type),
+
+			widget.NewLabel("地址:"),
+			newEntry(ff.Location, &ff.Location),
+		)
+	case *model.FileSuoJianYiJu:
+		rightDigit = append(rightDigit,
+			widget.NewLabel("类型:"),
+			newFileTypeSelect(ff.Type, ff.FileSetType, &ff.Type),
+
+			widget.NewLabel("地址:"),
+			newEntry(ff.Location, &ff.Location),
+		)
+	case *model.FileNongZiZhuanFei:
+		rightDigit = append(rightDigit,
+			widget.NewLabel("类型:"),
+			newFileTypeSelect(ff.Type, ff.FileSetType, &ff.Type),
+
+			widget.NewLabel("地址:"),
+			newEntry(ff.Location, &ff.Location),
+		)
+	case *model.FileYiZhanShiQianYiZheng:
+		rightDigit = append(rightDigit,
+			widget.NewLabel("类型:"),
+			newFileTypeSelect(ff.Type, ff.FileSetType, &ff.Type),
+
+			widget.NewLabel("地址:"),
+			newEntry(ff.Location, &ff.Location),
+		)
+	}
+
+	rightDigit = append(rightDigit,
+		widget.NewLabel("办理时间:"),
+		newDatePicker(f.Time, &f.Time, infoWindow),
+
+		widget.NewLabel("备考:"),
+		newFileBeiKaoSelect(f.BeiKao.String, f.FileSetType, &f.BeiKao, &f.Material, infoWindow),
+
+		widget.NewLabel("材料:"),
+		material,
+	)
+
+	rightLayout := layout.NewFormLayout()
+	right := container.New(rightLayout, rightDigit...)
+
+	upBox := container.NewHBox(left, right)
 
 	save := widget.NewButton("保存", func() {
 		rt.Action()
@@ -80,7 +187,7 @@ func ShowEdit(rt runtime.RunTime, f *model.File, refresh func(rt runtime.RunTime
 		dialog.ShowConfirm("更新?", "你确定要更新嘛?", func(b bool) {
 			rt.Action()
 			if b {
-				err := model.SaveFile(f)
+				err := model.SaveFile(rt, f)
 				if err != nil {
 					dialog.ShowError(fmt.Errorf("数据库错误: %s", err.Error()), infoWindow)
 				}
@@ -136,45 +243,42 @@ func newEntry(data string, input *string) *widget.Entry {
 	}
 
 	entryList = append(entryList, entry)
+
 	return entry
 }
 
-func newFileIDEntry(data int64, input *int64) *widget.Entry {
+func newEntryNumber(data int64, input *int64) *widget.Entry {
 	entry := widget.NewEntry()
-	if data <= 0 {
-		entry.Text = ""
-	} else {
-		entry.Text = fmt.Sprintf("%d", data)
-	}
+	entry.Text = fmt.Sprintf("%d", data)
 
 	entry.Validator = func(s string) error {
-		if len(s) == 0 {
-			return nil
-		}
+		_, err := strconv.ParseInt(s, 10, 64)
+		return err
+	}
 
-		n, err := strconv.ParseInt(s, 10, 64)
-		if err != nil {
-			return err
+	entry.OnChanged = func(s string) {
+		if entry.Validate() == nil {
+			n, err := strconv.ParseInt(s, 10, 64)
+			if err == nil {
+				*input = n
+			}
 		}
+	}
 
-		if n <= 0 {
-			return fmt.Errorf("must bigger than zero")
-		}
+	entryList = append(entryList, entry)
+	return entry
+}
 
-		return nil
-	}
+func newEntryValid(data string, input *sql.NullString) *widget.Entry {
+	entry := widget.NewEntry()
+	entry.Text = data
 
 	entry.OnChanged = func(s string) {
 		if entry.Validate() == nil {
 			if len(s) == 0 {
-				*input = 0
+				*input = sql.NullString{Valid: false}
 			} else {
-				n, err := strconv.ParseInt(s, 10, 64)
-				if err == nil {
-					*input = n
-				} else {
-					*input = 0
-				}
+				*input = sql.NullString{Valid: true, String: s}
 			}
 		}
 	}
@@ -183,17 +287,34 @@ func newFileIDEntry(data int64, input *int64) *widget.Entry {
 	return entry
 }
 
-func newFileTypeSelect(data string, options []string, input *string) *widget.Select {
-	func() {
-		for _, option := range options {
-			if option == data {
-				return
+func newFileTypeSelect(data string, fst model.FileSetType, input *string) fyne.CanvasObject {
+	c, err := systeminit.GetInit()
+	if err != nil {
+		return newEntry(data, input)
+	}
+
+	fstName, ok := model.FileSetTypeName[fst]
+	if !ok {
+		return newEntry(data, input)
+	}
+
+	fileType, ok := c.Yaml.File.FileType[fstName]
+	if !ok {
+		return newEntry(data, input)
+	}
+
+	if !func() bool {
+		for _, t := range fileType {
+			if t == data {
+				return true
 			}
 		}
-		options = append(options, data)
-	}()
+		return false
+	}() {
+		return newEntry(data, input)
+	}
 
-	sel := widget.NewSelect(options, func(s string) {
+	sel := widget.NewSelect(fileType, func(s string) {
 		*input = s
 	})
 
@@ -201,6 +322,100 @@ func newFileTypeSelect(data string, options []string, input *string) *widget.Sel
 	return sel
 }
 
+func newFileBeiKaoSelect(data string, fst model.FileSetType, input *sql.NullString, materialInput *sql.NullString, w fyne.Window) fyne.CanvasObject {
+	c, err := systeminit.GetInit()
+	if err != nil {
+		return newEntryValid(data, input)
+	}
+
+	fstName, ok := model.FileSetTypeName[fst]
+	if !ok {
+		return newEntryValid(data, input)
+	}
+
+	beiKao, ok := c.Yaml.File.BeiKao[fstName]
+	if !ok {
+		return newEntryValid(data, input)
+	}
+
+	if !func() bool {
+		for _, t := range beiKao {
+			if t.Name == data {
+				return true
+			}
+		}
+		return false
+	}() {
+		return newEntryValid(data, input)
+	}
+
+	for _, i := range beiKao {
+		for _, j := range beiKao {
+			if i.Name == j.Name {
+				return newEntryValid(data, input)
+			}
+		}
+	}
+
+	beiKaoLst := make([]string, 0, len(beiKao))
+	for _, i := range beiKao {
+		beiKaoLst = append(beiKaoLst, i.Name)
+	}
+
+	sel := widget.NewSelect(beiKaoLst, func(s string) {
+		input.Valid = len(s) != 0
+		input.String = s
+
+		for _, i := range beiKao {
+			if s == i.Name && len(i.Material) != 0 {
+				dialog.ShowConfirm("确认", "此”备考“有预先录制的材料列表,是否立即使用?", func(b bool) {
+					if !b {
+						return
+					}
+
+					materialInput.Valid = true
+					materialInput.String = strings.Join(i.Material, "、") + "。"
+				}, w)
+			}
+		}
+
+	})
+
+	sel.Selected = data
+	return sel
+}
+
+func newSexSelect(data bool, isMan *bool) *widget.Select {
+	sel := widget.NewSelect([]string{"男性", "女性"}, func(s string) {
+		*isMan = s == "男性"
+	})
+
+	if data {
+		sel.Selected = "男性"
+	} else {
+		sel.Selected = "女性"
+	}
+
+	return sel
+}
+
+func newDatePicker(data time.Time, input *time.Time, w fyne.Window) *widget.Button {
+	btn := widget.NewButton("选择时间", func() {})
+
+	d := datepicker.NewDatePicker(data, time.Monday, func(t time.Time, b bool) {
+		if b {
+			*input = t
+			btn.SetText(t.Format("2006-01-02"))
+		}
+	})
+
+	btn.OnTapped = func() {
+		dialog.ShowCustomConfirm("选择时间", "确认", "放弃", d, d.OnActioned, w)
+	}
+
+	return btn
+}
+
 func checkAllInputRight() error {
 	for _, e := range entryList {
 		err := e.Validate()

+ 45 - 10
src/v1main/fromfile.go

@@ -5,17 +5,17 @@ import (
 	"fmt"
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2/dialog"
-	"github.com/SuperH-0630/hdangan/src/excelreader"
+	"github.com/SuperH-0630/hdangan/src/excelio"
 	"github.com/SuperH-0630/hdangan/src/runtime"
 	"os"
 )
 
-func AddFromFile(rt runtime.RunTime, w fyne.Window, refresh func(rt runtime.RunTime)) error {
+func AddFromFile(rt runtime.RunTime, w *CtrlWindow, refresh func(rt runtime.RunTime)) error {
 	dialog.ShowFileOpen(func(reader fyne.URIReadCloser, err error) {
 		if reader == nil {
 			return
 		} else if err != nil {
-			dialog.ShowError(fmt.Errorf("选择框遇到错误:%s", err.Error()), w)
+			dialog.ShowError(fmt.Errorf("选择框遇到错误:%s", err.Error()), w.window)
 			return
 		}
 
@@ -25,22 +25,57 @@ func AddFromFile(rt runtime.RunTime, w fyne.Window, refresh func(rt runtime.RunT
 
 		filename := reader.URI().Path()
 		if !IsFileExists(filename) {
-			dialog.ShowError(fmt.Errorf("文件不存在:%s", filename), w)
+			dialog.ShowError(fmt.Errorf("文件不存在:%s", filename), w.window)
 			return
 		}
 
-		sa, su, fu, err := excelreader.ReadFile(rt, reader)
-		if errors.Is(err, excelreader.BadTitle) {
-			dialog.ShowError(fmt.Errorf("表格首行(表头)对应错误"), w)
+		sa, su, fu, err := excelio.ReadFile(rt, w.table.fileSetType, reader)
+		if errors.Is(err, excelio.BadTitle) {
+			dialog.ShowError(fmt.Errorf("表格首行(表头)对应错误"), w.window)
 			return
 		} else if err != nil {
-			dialog.ShowError(fmt.Errorf("导入出错:%s", err), w)
+			dialog.ShowError(fmt.Errorf("导入出错:%s", err), w.window)
 			return
 		}
 
-		dialog.ShowInformation("完成", fmt.Sprintf("恭喜你!共新增%d条数据,共升级%d条数据,但是%d条数据未成功识别。。", sa, su, fu), w)
+		dialog.ShowInformation("完成", fmt.Sprintf("恭喜你!共新增%d条数据,共升级%d条数据,但是%d条数据未成功识别。。", sa, su, fu), w.window)
 		refresh(rt)
-	}, w)
+	}, w.window)
+
+	return nil
+}
+
+func AddRecordFromFile(rt runtime.RunTime, w *CtrlWindow, refresh func(rt runtime.RunTime)) error {
+	dialog.ShowFileOpen(func(reader fyne.URIReadCloser, err error) {
+		if reader == nil {
+			return
+		} else if err != nil {
+			dialog.ShowError(fmt.Errorf("选择框遇到错误:%s", err.Error()), w.window)
+			return
+		}
+
+		defer func() {
+			_ = reader.Close()
+		}()
+
+		filename := reader.URI().Path()
+		if !IsFileExists(filename) {
+			dialog.ShowError(fmt.Errorf("文件不存在:%s", filename), w.window)
+			return
+		}
+
+		sa, su, fu, err := excelio.ReadRecord(rt, w.table.fileSetType, reader)
+		if errors.Is(err, excelio.BadTitle) {
+			dialog.ShowError(fmt.Errorf("表格首行(表头)对应错误"), w.window)
+			return
+		} else if err != nil {
+			dialog.ShowError(fmt.Errorf("导入出错:%s", err), w.window)
+			return
+		}
+
+		dialog.ShowInformation("完成", fmt.Sprintf("恭喜你!共新增%d条数据,共升级%d条数据,但是%d条数据未成功识别。。", sa, su, fu), w.window)
+		refresh(rt)
+	}, w.window)
 
 	return nil
 }

+ 2 - 0
src/v1main/hello.go

@@ -27,6 +27,7 @@ func startBtnClick(rt runtime.RunTime) error {
 }
 
 func createHelloWindow(rt runtime.RunTime) {
+	fmt.Println("TAG A")
 	if helloWindow != nil {
 		return
 	}
@@ -83,6 +84,7 @@ func createHelloWindow(rt runtime.RunTime) {
 		helloWindow.Close()
 		rt.App().Quit()
 	})
+	fmt.Println("TAG B")
 }
 
 func ShowHelloWindow(rt runtime.RunTime) {

+ 149 - 38
src/v1main/info.go

@@ -11,21 +11,40 @@ import (
 	"github.com/SuperH-0630/hdangan/src/runtime"
 )
 
-func readData(rt runtime.RunTime, f *model.File) (*fyne.Container, *fyne.Container) {
-	fileComment := widget.NewMultiLineEntry()
-	fileComment.Text = strToStr(f.FileComment, "")
-	fileComment.Wrapping = fyne.TextWrapWord
-	fileComment.OnChanged = func(s string) {
+func readData(rt runtime.RunTime, fc model.File) (*fyne.Container, *fyne.Container) {
+	f := fc.GetFile()
+
+	comment := widget.NewMultiLineEntry()
+	comment.Text = strToStr(f.Comment, "")
+	comment.Wrapping = fyne.TextWrapWord
+	comment.OnChanged = func(s string) {
 		rt.Action()
-		fileComment.Text = strToStr(f.FileComment, "")
+		comment.Text = strToStr(f.Comment, "")
 	}
 
-	moveComment := widget.NewMultiLineEntry()
-	moveComment.Text = strToStr(f.MoveComment, "")
-	moveComment.Wrapping = fyne.TextWrapWord
-	moveComment.OnChanged = func(s string) {
+	material := widget.NewMultiLineEntry()
+	material.Text = strToStr(f.Material, "")
+	material.Wrapping = fyne.TextWrapWord
+	material.OnChanged = func(s string) {
 		rt.Action()
-		moveComment.Text = strToStr(f.MoveComment, "")
+		material.Text = strToStr(f.Material, "")
+	}
+
+	if !f.OldName.Valid {
+		f.OldName.String = ""
+	}
+
+	if !f.IDCard.Valid {
+		f.IDCard.String = ""
+	}
+
+	if !f.Comment.Valid {
+		f.Comment.String = ""
+	}
+
+	sex := "男性"
+	if !f.IsMan {
+		sex = "女性"
 	}
 
 	leftLayout := layout.NewFormLayout()
@@ -33,51 +52,143 @@ func readData(rt runtime.RunTime, f *model.File) (*fyne.Container, *fyne.Contain
 		widget.NewLabel("卷宗号:"),
 		widget.NewLabel(fmt.Sprintf("%d", f.FileID)),
 
+		widget.NewLabel("卷宗类型:"),
+		widget.NewLabel(fmt.Sprintf("%s", model.FileSetTypeName[f.FileSetType])),
+
+		widget.NewLabel("文件联合编号:"),
+		widget.NewLabel(fmt.Sprintf("%d", f.FileUnionID)),
+
+		widget.NewLabel("文件编号:"),
+		widget.NewLabel(fmt.Sprintf("%d", f.FileID)),
+
+		widget.NewLabel("文件组内编号:"),
+		widget.NewLabel(fmt.Sprintf("%d", f.FileGroupID)),
+
 		widget.NewLabel("姓名:"),
 		widget.NewLabel(fmt.Sprintf("%s", f.Name)),
 
+		widget.NewLabel("曾用名:"),
+		widget.NewLabel(fmt.Sprintf("%s", f.OldName)),
+
 		widget.NewLabel("身份证号:"),
 		widget.NewLabel(fmt.Sprintf("%s", f.IDCard)),
 
-		widget.NewLabel("户籍地:"),
-		widget.NewLabel(fmt.Sprintf("%s", f.Location)),
+		widget.NewLabel("性别:"),
+		widget.NewLabel(fmt.Sprintf("%s", sex)),
 
-		widget.NewLabel("卷宗标题:"),
-		widget.NewLabel(fmt.Sprintf("%s", f.FileTitle)),
-
-		widget.NewLabel("卷宗类型:"),
-		widget.NewLabel(fmt.Sprintf("%s", f.FileType)),
+		widget.NewLabel("出生日期:"),
+		widget.NewLabel(fmt.Sprintf("%s", f.Birthday.Format("2006-01-02"))),
 
 		widget.NewLabel("卷宗备注:"),
-		fileComment,
+		comment,
 	)
 
-	rightLayout := layout.NewFormLayout()
-	right := container.New(rightLayout,
-		widget.NewLabel("最早迁入时间:"),
-		widget.NewLabel(fmt.Sprintf("%s", f.FirstMoveIn.Format("2006-01-02 15:04:05"))),
+	rightDigit := make([]fyne.CanvasObject, 0, 10)
+
+	switch ff := fc.(type) {
+	case *model.FileQianRu:
+		rightDigit = append(rightDigit,
+			widget.NewLabel("类型:"),
+			widget.NewLabel(fmt.Sprintf("%s", ff.Type)),
+
+			widget.NewLabel("旧地址:"),
+			widget.NewLabel(fmt.Sprintf("%s", ff.OldLocation)),
+
+			widget.NewLabel("新地址:"),
+			widget.NewLabel(fmt.Sprintf("%s", ff.NewLocation)),
+		)
+	case *model.FileChuSheng:
+		rightDigit = append(rightDigit,
+			widget.NewLabel("类型:"),
+			widget.NewLabel(fmt.Sprintf("%s", ff.Type)),
+
+			widget.NewLabel("地址:"),
+			widget.NewLabel(fmt.Sprintf("%s", ff.NewLocation)),
+		)
+	case *model.FileQianChu:
+		rightDigit = append(rightDigit,
+			widget.NewLabel("类型:"),
+			widget.NewLabel(fmt.Sprintf("%s", ff.Type)),
+
+			widget.NewLabel("新地址:"),
+			widget.NewLabel(fmt.Sprintf("%s", ff.NewLocation)),
+		)
+	case *model.FileSiWang:
+		rightDigit = append(rightDigit,
+			widget.NewLabel("类型:"),
+			widget.NewLabel(fmt.Sprintf("%s", ff.Type)),
+
+			widget.NewLabel("地址:"),
+			widget.NewLabel(fmt.Sprintf("%s", ff.Location)),
+		)
+	case *model.FileBianGeng:
+		rightDigit = append(rightDigit,
+			widget.NewLabel("类型:"),
+			widget.NewLabel(fmt.Sprintf("%s", ff.Type)),
+
+			widget.NewLabel("地址:"),
+			widget.NewLabel(fmt.Sprintf("%s", ff.Location)),
+		)
+	case *model.FileSuoNeiYiJu:
+		rightDigit = append(rightDigit,
+			widget.NewLabel("类型:"),
+			widget.NewLabel(fmt.Sprintf("%s", ff.Type)),
+
+			widget.NewLabel("地址:"),
+			widget.NewLabel(fmt.Sprintf("%s", ff.Location)),
+		)
+	case *model.FileSuoJianYiJu:
+		rightDigit = append(rightDigit,
+			widget.NewLabel("类型:"),
+			widget.NewLabel(fmt.Sprintf("%s", ff.Type)),
+
+			widget.NewLabel("地址:"),
+			widget.NewLabel(fmt.Sprintf("%s", ff.Location)),
+		)
+	case *model.FileNongZiZhuanFei:
+		rightDigit = append(rightDigit,
+			widget.NewLabel("类型:"),
+			widget.NewLabel(fmt.Sprintf("%s", ff.Type)),
+
+			widget.NewLabel("地址:"),
+			widget.NewLabel(fmt.Sprintf("%s", ff.Location)),
+		)
+	case *model.FileYiZhanShiQianYiZheng:
+		rightDigit = append(rightDigit,
+			widget.NewLabel("类型:"),
+			widget.NewLabel(fmt.Sprintf("%s", ff.Type)),
+
+			widget.NewLabel("旧地址:"),
+			widget.NewLabel(fmt.Sprintf("%s", ff.Location)),
+		)
+	}
 
-		widget.NewLabel("最后迁入时间:"),
-		widget.NewLabel(fmt.Sprintf("%s", f.LastMoveIn.Format("2006-01-02 15:04:05"))),
+	rightDigit = append(rightDigit,
+		widget.NewLabel("人数:"),
+		widget.NewLabel(fmt.Sprintf("%d", f.PeopleCount)),
 
-		widget.NewLabel("最后迁入迁出时间:"),
-		widget.NewLabel(fmt.Sprintf("%s", f.MoveStatus)),
+		widget.NewLabel("办理时间:"),
+		widget.NewLabel(fmt.Sprintf("%s", f.Time.Format("2006-01-02 15:04:05"))),
 
-		widget.NewLabel("最后迁出人:"),
-		widget.NewLabel(fmt.Sprintf("%s", strToStr(f.MoveOutPeopleName, "暂无"))),
+		widget.NewLabel("页码:"),
+		widget.NewLabel(fmt.Sprintf("%d-%d(共%d页)", f.PageStart, f.PageEnd, f.PageCount)),
 
-		widget.NewLabel("最后迁出单位:"),
-		widget.NewLabel(fmt.Sprintf("%s", strToStr(f.MoveOutPeopleUnit, "暂无"))),
+		widget.NewLabel("备考:"),
+		widget.NewLabel(fmt.Sprintf("%s", f.BeiKao.String)),
 
-		widget.NewLabel("最后迁出备注:"),
-		moveComment,
+		widget.NewLabel("材料:"),
+		material,
 	)
 
+	rightLayout := layout.NewFormLayout()
+	right := container.New(rightLayout, rightDigit...)
+
 	return left, right
 }
 
-func ShowInfo(rt runtime.RunTime, f *model.File, refresh func(rt runtime.RunTime)) {
-	infoWindow := rt.App().NewWindow(fmt.Sprintf("详细信息-%s-%d", f.Name, f.FileID))
+func ShowInfo(rt runtime.RunTime, f model.File, refresh func(rt runtime.RunTime)) {
+	fc := f.GetFile()
+	infoWindow := rt.App().NewWindow(fmt.Sprintf("详细信息-%s-%d", fc.Name, fc.FileID))
 
 	infoWindow.SetOnClosed(func() {
 		rt.Action()
@@ -107,9 +218,9 @@ func ShowInfo(rt runtime.RunTime, f *model.File, refresh func(rt runtime.RunTime
 		ShowEdit(rt, f, warpRefresh)
 	})
 
-	move := widget.NewButton("迁入迁出", func() {
+	move := widget.NewButton("出借档案", func() {
 		rt.Action()
-		ShowMove(rt, f, warpRefresh)
+		ShowNewMove(rt, f, warpRefresh)
 	})
 
 	record := widget.NewButton("查看迁入迁出记录", func() {
@@ -123,7 +234,7 @@ func ShowInfo(rt runtime.RunTime, f *model.File, refresh func(rt runtime.RunTime
 		rt.Action()
 		dialog.ShowConfirm("删除提示", "请问是否删除此条档案记录,删除后恢复可能较为苦难。", func(b bool) {
 			if b {
-				err := model.DeleteFile(f)
+				err := model.DeleteFile(rt, fc)
 				if err != nil {
 					dialog.ShowError(fmt.Errorf("数据库错误: %s", err.Error()), infoWindow)
 				}

+ 83 - 30
src/v1main/infotable.go

@@ -8,9 +8,11 @@ import (
 	"fyne.io/fyne/v2/widget"
 	"github.com/SuperH-0630/hdangan/src/model"
 	"github.com/SuperH-0630/hdangan/src/runtime"
+	"reflect"
+	"strings"
 )
 
-var TopHeaderData = []string{"卷宗号", "姓名", "身份证", "户籍地", "卷宗标题", "卷宗类型", "最早迁入时间", "最后迁入(归还)时间", "迁入迁出状态", "迁出人姓名", "迁出人工作单位", "详情"}
+var TopHeaderData = []string{"卷宗号", "卷宗类型", "文件联合编号", "文件编号", "文件组内编号", "姓名", "曾用名", "身份证", "性别", "出生日期", "备注", "详情"}
 var xiangQingIndex = -1
 
 const defaultItemCount = model.DefaultPageItemCount
@@ -26,11 +28,12 @@ func init() {
 }
 
 type MainTable struct {
-	fileTable *widget.Table
-	window    *CtrlWindow
-	InfoFile  []model.File
-	InfoData  [][]string
-	whereInfo model.SearchWhere
+	fileTable   *widget.Table
+	window      *CtrlWindow
+	InfoFile    []model.File
+	InfoData    [][]string
+	whereInfo   model.SearchWhere
+	fileSetType model.FileSetType
 }
 
 func CreateInfoTable(rt runtime.RunTime, window *CtrlWindow) *MainTable {
@@ -48,8 +51,9 @@ func CreateInfoTable(rt runtime.RunTime, window *CtrlWindow) *MainTable {
 		})
 
 	m := &MainTable{
-		fileTable: fileTable,
-		window:    ctrlWindow,
+		fileTable:   fileTable,
+		window:      ctrlWindow,
+		fileSetType: model.QianRu,
 	}
 
 	fileTable.Length = func() (rows int, cols int) {
@@ -90,8 +94,8 @@ func CreateInfoTable(rt runtime.RunTime, window *CtrlWindow) *MainTable {
 		if id.Col == xiangQingIndex {
 			if id.Row >= 0 && id.Row < len(m.InfoData) {
 				file := m.InfoFile[id.Row]
-				ShowInfo(rt, &file, func(rt runtime.RunTime) {
-					m.UpdateTable(rt, 0, window.menu.NowPage)
+				ShowInfo(rt, file, func(rt runtime.RunTime) {
+					m.UpdateTable(rt, m.fileSetType, 0, window.menu.NowPage)
 				})
 			}
 		}
@@ -102,48 +106,97 @@ func CreateInfoTable(rt runtime.RunTime, window *CtrlWindow) *MainTable {
 		rt.Action()
 	}
 
-	m.UpdateTable(rt, 0, 1)
+	m.UpdateTable(rt, m.fileSetType, 0, window.menu.NowPage)
 	return m
 }
 
-func (m *MainTable) UpdateTableInfo(rt runtime.RunTime, files []model.File) {
-	res := make([][]string, len(files))
+func (m *MainTable) UpdateTableInfo(rt runtime.RunTime) {
+	res := make([][]string, len(m.InfoFile))
 
-	for i, f := range files {
+	for i, j := range m.InfoFile {
 		res[i] = make([]string, len(TopHeaderData))
+		f := j.GetFile()
 
-		res[i][0] = fmt.Sprintf("%03d", f.FileID)
-		res[i][1] = f.Name
-		res[i][2] = f.IDCard
-		res[i][3] = f.Location
-		res[i][4] = f.FileTitle
-		res[i][5] = f.FileType
-		res[i][6] = f.FirstMoveIn.Format("2006-01-02 15:04:05")
-		res[i][7] = f.LastMoveIn.Format("2006-01-02 15:04:05")
-		res[i][8] = f.MoveStatus
-		res[i][9] = strToStr(f.MoveOutPeopleName)
-		res[i][10] = strToStr(f.MoveOutPeopleUnit)
+		if !f.OldName.Valid {
+			f.OldName.String = ""
+		}
+
+		if !f.IDCard.Valid {
+			f.IDCard.String = ""
+		}
+
+		if !f.Comment.Valid {
+			f.Comment.String = ""
+		}
+
+		sex := "男性"
+		if !f.IsMan {
+			sex = "女性"
+		}
+
+		res[i][0] = fmt.Sprintf("%03d", f.FileSetID)
+		res[i][1] = fmt.Sprintf("%s", model.FileSetTypeName[m.fileSetType])
+		res[i][2] = fmt.Sprintf("%03d", f.FileUnionID)
+		res[i][3] = fmt.Sprintf("%03d", f.FileID)
+		res[i][4] = fmt.Sprintf("%03d", f.FileGroupID)
+		res[i][5] = f.Name
+		res[i][6] = f.OldName.String
+		res[i][7] = f.IDCard.String
+		res[i][8] = sex
+		res[i][9] = f.Birthday.Format("2006-01-02")
+		res[i][10] = f.Comment.String
 		res[i][11] = "点击查看"
 	}
 
 	m.InfoData = res
 }
 
-func (m *MainTable) UpdateTable(rt runtime.RunTime, pageItemCount int, p int64) {
+func (m *MainTable) UpdateTable(rt runtime.RunTime, fileSetType model.FileSetType, pageItemCount int, p int64) {
 	if pageItemCount <= 0 {
 		pageItemCount = defaultItemCount
 	}
 
-	files, pageMax, err := model.GetPageData(rt, pageItemCount, p, &m.whereInfo)
+	maker, ok := model.FileSetTypeMaker[fileSetType]
+	if !ok {
+		return
+	}
+
+	tptr := reflect.TypeOf(maker())
+	if tptr.Kind() != reflect.Ptr {
+		return
+	}
+
+	t := tptr.Elem()
+	if t.Kind() != reflect.Struct {
+		return
+	}
+
+	if strings.HasPrefix(t.Name(), "File") {
+		return
+	}
+
+	resValue := reflect.MakeSlice(reflect.SliceOf(t), 0, pageItemCount)
+	res := resValue.Interface()
+	pageMax, err := model.GetPageData(rt, fileSetType, pageItemCount, p, &m.whereInfo, &res)
 	if err != nil {
 		dialog.ShowError(fmt.Errorf("获取数据库档案信息错误。"), m.window.window)
 		return
 	}
 
-	m.InfoFile = files
+	m.InfoFile = make([]model.File, 0, pageItemCount)
+	for i := 0; i < resValue.Len(); i++ {
+		elem := resValue.Index(i)
+		etype := elem.Type()
+		if !etype.Implements(reflect.TypeOf((*model.File)(nil)).Elem()) {
+			continue
+		}
+
+		m.InfoFile = append(m.InfoFile, elem.Interface().(model.File))
+	}
 
-	m.UpdateTableInfo(rt, files)
-	m.window.menu.ChangePageMenuItem(rt, pageItemCount, p, pageMax, fmt.Sprintf("本页共显示数据:%d条。", len(files)))
+	m.UpdateTableInfo(rt)
+	m.window.menu.ChangePageMenuItem(rt, pageItemCount, p, pageMax, fmt.Sprintf("本页共显示数据:%d条。", len(m.InfoFile)))
+	m.window.menu.ChangeFileSetModelItem(rt, pageItemCount, fmt.Sprintf("本页共显示数据:%d条。", len(m.InfoFile)))
 	m.fileTable.Refresh()
 }
 

+ 7 - 1
src/v1main/main.go

@@ -15,13 +15,17 @@ import (
 
 func Main() {
 	start()
+	fmt.Println("TAG A")
 
 	a := happ.NewApp()
+	fmt.Println("TAG B")
 
 	rt := runtime.NewRunTime(a)
+	fmt.Println("TAG C")
 
 	err := model.AutoCreateModel(rt)
 	if err != nil {
+		fmt.Println("TAG D")
 		dbFail(rt, fmt.Sprintf("数据库构建失败: %s。", err.Error()), 1)
 		return
 	}
@@ -65,10 +69,12 @@ func dbFail(rt runtime.RunTime, res string, exitCode int) {
 		msg = fmt.Sprintf("I am sorry, that we has miss some wrong.\n%s", res)
 	}
 
+	fmt.Println(msg)
 	dialog.ShowError(fmt.Errorf("%s", msg), w1)
 
 	w1.SetContent(widget.NewLabel(""))
 	w1.CenterOnScreen()
 	w1.SetFixedSize(true)
-	w1.Show()
+	w1.ShowAndRun()
+	exit()
 }

+ 334 - 101
src/v1main/new.go

@@ -1,7 +1,7 @@
 package v1main
 
 import (
-	"errors"
+	"database/sql"
 	"fmt"
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2/container"
@@ -11,21 +11,14 @@ import (
 	"github.com/SuperH-0630/hdangan/src/model"
 	"github.com/SuperH-0630/hdangan/src/runtime"
 	"github.com/SuperH-0630/hdangan/src/systeminit"
+	datepicker "github.com/sdassow/fyne-datepicker"
 	"strconv"
+	"strings"
 	"time"
 )
 
-func ShowNew(rt runtime.RunTime, refresh func(rt runtime.RunTime)) {
-	config, err := systeminit.GetInit()
-	if errors.Is(err, systeminit.LuckyError) {
-		rt.DBConnectError(err)
-		return
-	} else if err != nil {
-		rt.DBConnectError(fmt.Errorf("配置文件错误,请检查配置文件状态。"))
-		return
-	}
-
-	newWindow := rt.App().NewWindow("创建新卷宗")
+func ShowNew(rt runtime.RunTime, w *CtrlWindow, refresh func(rt runtime.RunTime)) {
+	newWindow := rt.App().NewWindow("创建记录")
 
 	newWindow.SetOnClosed(func() {
 		rt.Action()
@@ -37,49 +30,169 @@ func ShowNew(rt runtime.RunTime, refresh func(rt runtime.RunTime)) {
 		newWindow = nil
 	})
 
-	fid, err := model.GetNewFileID(rt)
-	if err != nil {
-		dialog.ShowError(fmt.Errorf("获取新的档案ID出错:%s", err.Error()), newWindow)
+	maker, ok := model.FileSetTypeMaker[w.table.fileSetType]
+	if !ok {
+		rt.DBConnectError(fmt.Errorf("配置文件错误,请检查配置文件状态。"))
+		return
+	}
+
+	fm := maker()
+	f := fm.GetFile()
+
+	f.PeopleCount = 1
+
+	comment := widget.NewMultiLineEntry()
+	comment.Text = strToStr(f.Comment, "")
+	comment.Wrapping = fyne.TextWrapWord
+	comment.OnChanged = func(s string) {
+		rt.Action()
+		comment.Text = strToStr(f.Comment, "")
 	}
 
-	now := time.Now()
+	material := widget.NewMultiLineEntry()
+	material.Text = strToStr(f.Comment, "")
+	material.Wrapping = fyne.TextWrapWord
+	material.OnChanged = func(s string) {
+		rt.Action()
+		material.Text = strToStr(f.Comment, "")
+	}
+
+	if !f.OldName.Valid {
+		f.OldName.String = ""
+	}
 
-	f := model.File{
-		FileID:      fid,
-		FirstMoveIn: now,
-		LastMoveIn:  now,
-		MoveStatus:  config.Yaml.Move.MoveInStatus,
+	if !f.IDCard.Valid {
+		f.IDCard.String = ""
 	}
 
-	fileComment := widget.NewMultiLineEntry()
-	fileComment.Text = strToStr(f.FileComment, "")
-	fileComment.Wrapping = fyne.TextWrapWord
+	if !f.Comment.Valid {
+		f.Comment.String = ""
+	}
 
-	formLayout := layout.NewFormLayout()
-	form := container.New(formLayout,
-		widget.NewLabel("卷宗号:"),
-		newFileIDEntry4(fmt.Sprintf("%d", f.FileID), &f.FileID),
+	sameAboveDisableList := make([]fyne.Disableable, 0, 10)
 
+	leftLayout := layout.NewFormLayout()
+	left := container.New(leftLayout,
 		widget.NewLabel("姓名:"),
-		newEntry4(fmt.Sprintf("%s", f.Name), &f.Name),
+		newEntry4(fmt.Sprintf("%s", f.Name), &f.Name, nil),
+
+		widget.NewLabel("曾用名:"),
+		newEntryValid4(fmt.Sprintf("%s", f.OldName.String), &f.OldName, nil),
 
 		widget.NewLabel("身份证号:"),
-		newEntry4(fmt.Sprintf("%s", f.IDCard), &f.IDCard),
+		newEntryValid4(fmt.Sprintf("%s", f.IDCard.String), &f.IDCard, nil),
 
-		widget.NewLabel("户籍地:"),
-		newEntry4(fmt.Sprintf("%s", f.Location), &f.Location),
+		widget.NewLabel("性别:"),
+		newSexSelect4(f.IsMan, &f.IsMan),
 
-		widget.NewLabel("卷宗标题:"),
-		newEntry4(fmt.Sprintf("%s", f.FileTitle), &f.FileTitle),
+		widget.NewLabel("出生日期:"),
+		newDatePicker4(f.Birthday, &f.Birthday, newWindow, nil),
 
-		widget.NewLabel("卷宗类型:"),
-		newFileTypeSelect4(fmt.Sprintf("%s", f.FileType), config.Yaml.File.FileType, &f.FileType),
+		widget.NewLabel("同上:"),
+		newSameAboveCheck4("", &f.SameAsAbove, &sameAboveDisableList),
 
-		widget.NewLabel("卷宗备注:"),
-		fileComment,
+		widget.NewLabel("备注:"),
+		comment,
 	)
 
-	upBox := container.NewHBox(form)
+	rightDigit := make([]fyne.CanvasObject, 0, 10)
+
+	switch ff := fm.(type) {
+	case *model.FileQianRu:
+		rightDigit = append(rightDigit,
+			widget.NewLabel("类型:"),
+			newFileTypeSelect4(ff.Type, ff.FileSetType, &ff.Type, &sameAboveDisableList),
+
+			widget.NewLabel("旧地址:"),
+			newEntry4(ff.OldLocation, &ff.OldLocation, &sameAboveDisableList),
+
+			widget.NewLabel("新地址:"),
+			newEntry4(ff.NewLocation, &ff.NewLocation, &sameAboveDisableList),
+		)
+	case *model.FileChuSheng:
+		rightDigit = append(rightDigit,
+			widget.NewLabel("类型:"),
+			newFileTypeSelect4(ff.Type, ff.FileSetType, &ff.Type, &sameAboveDisableList),
+
+			widget.NewLabel("地址:"),
+			newEntry4(ff.NewLocation, &ff.NewLocation, &sameAboveDisableList),
+		)
+	case *model.FileQianChu:
+		rightDigit = append(rightDigit,
+			widget.NewLabel("类型:"),
+			newFileTypeSelect4(ff.Type, ff.FileSetType, &ff.Type, &sameAboveDisableList),
+
+			widget.NewLabel("新地址:"),
+			newEntry4(ff.NewLocation, &ff.NewLocation, &sameAboveDisableList),
+		)
+	case *model.FileSiWang:
+		rightDigit = append(rightDigit,
+			widget.NewLabel("类型:"),
+			newFileTypeSelect4(ff.Type, ff.FileSetType, &ff.Type, &sameAboveDisableList),
+
+			widget.NewLabel("地址:"),
+			newEntry4(ff.Location, &ff.Location, &sameAboveDisableList),
+		)
+	case *model.FileBianGeng:
+		rightDigit = append(rightDigit,
+			widget.NewLabel("类型:"),
+			newFileTypeSelect4(ff.Type, ff.FileSetType, &ff.Type, &sameAboveDisableList),
+
+			widget.NewLabel("地址:"),
+			newEntry4(ff.Location, &ff.Location, &sameAboveDisableList),
+		)
+	case *model.FileSuoNeiYiJu:
+		rightDigit = append(rightDigit,
+			widget.NewLabel("类型:"),
+			newFileTypeSelect4(ff.Type, ff.FileSetType, &ff.Type, &sameAboveDisableList),
+
+			widget.NewLabel("地址:"),
+			newEntry4(ff.Location, &ff.Location, &sameAboveDisableList),
+		)
+	case *model.FileSuoJianYiJu:
+		rightDigit = append(rightDigit,
+			widget.NewLabel("类型:"),
+			newFileTypeSelect4(ff.Type, ff.FileSetType, &ff.Type, &sameAboveDisableList),
+
+			widget.NewLabel("地址:"),
+			newEntry4(ff.Location, &ff.Location, &sameAboveDisableList),
+		)
+	case *model.FileNongZiZhuanFei:
+		rightDigit = append(rightDigit,
+			widget.NewLabel("类型:"),
+			newFileTypeSelect4(ff.Type, ff.FileSetType, &ff.Type, &sameAboveDisableList),
+
+			widget.NewLabel("地址:"),
+			newEntry4(ff.Location, &ff.Location, &sameAboveDisableList),
+		)
+	case *model.FileYiZhanShiQianYiZheng:
+		rightDigit = append(rightDigit,
+			widget.NewLabel("类型:"),
+			newFileTypeSelect4(ff.Type, ff.FileSetType, &ff.Type, &sameAboveDisableList),
+
+			widget.NewLabel("地址:"),
+			newEntry4(ff.Location, &ff.Location, &sameAboveDisableList),
+		)
+	}
+
+	rightDigit = append(rightDigit,
+		widget.NewLabel("办理时间:"),
+		newDatePicker4(f.Time, &f.Time, newWindow, &sameAboveDisableList),
+
+		widget.NewLabel("备考:"),
+		newFileBeiKaoSelect4(f.BeiKao.String, f.FileSetType, &f.BeiKao, &f.Material, newWindow, nil),
+
+		widget.NewLabel("材料页数:"),
+		newEntryPage4(f.PageCount, &f.PageCount, &sameAboveDisableList),
+
+		widget.NewLabel("材料:"),
+		material,
+	)
+
+	rightLayout := layout.NewFormLayout()
+	right := container.New(rightLayout, rightDigit...)
+
+	upBox := container.NewHBox(left, right)
 
 	save := widget.NewButton("保存", func() {
 		rt.Action()
@@ -89,16 +202,10 @@ func ShowNew(rt runtime.RunTime, refresh func(rt runtime.RunTime)) {
 			return
 		}
 
-		err = checkNewFile(&f)
-		if err != nil {
-			dialog.ShowError(fmt.Errorf("请检查错误:%s", err.Error()), newWindow)
-			return
-		}
-
-		dialog.ShowConfirm("创建?", "你确定要新增卷宗嘛?", func(b bool) {
+		dialog.ShowConfirm("创建?", "你确定要新增档案嘛?", func(b bool) {
 			rt.Action()
 			if b {
-				err := model.CreateFile(&f)
+				err := model.CreateFile(rt, w.table.fileSetType, fm)
 				if err != nil {
 					dialog.ShowError(fmt.Errorf("数据库错误: %s", err.Error()), newWindow)
 				}
@@ -140,49 +247,9 @@ func ShowNew(rt runtime.RunTime, refresh func(rt runtime.RunTime)) {
 	newWindow.SetFixedSize(true)
 }
 
-func checkNewFile(f *model.File) error {
-	if f.ID != 0 {
-		return fmt.Errorf("系统错误")
-	}
-
-	if f.FileID <= 0 {
-		return fmt.Errorf("卷宗号必须大于0")
-	}
-
-	if len(f.Name) <= 0 || len(f.Name) >= 45 {
-		return fmt.Errorf("姓名必填,最大45字符")
-	}
-
-	if len(f.IDCard) <= 0 || len(f.IDCard) >= 20 {
-		return fmt.Errorf("身份证必填,最大20字符")
-	}
-
-	if len(f.Location) <= 0 || len(f.Location) >= 20 {
-		return fmt.Errorf("户籍地必填,最大145字符")
-	}
-
-	if len(f.FileTitle) <= 0 || len(f.FileTitle) >= 45 {
-		return fmt.Errorf("卷宗标题必填,最大45字符")
-	}
-
-	if len(f.FileType) <= 0 || len(f.FileType) >= 45 {
-		return fmt.Errorf("卷宗类型必填,最大15字符")
-	}
-
-	if f.FileComment.Valid {
-		if len(f.FileComment.String) == 0 {
-			f.FileComment.Valid = false
-		}
-	} else {
-		f.FileComment.Valid = true
-	}
-
-	return nil
-}
-
 var entryList4 []*widget.Entry
 
-func newEntry4(data string, input *string) *widget.Entry {
+func newEntry4(data string, input *string, disableLst *[]fyne.Disableable) *widget.Entry {
 	entry := widget.NewEntry()
 	entry.Text = data
 
@@ -193,12 +260,18 @@ func newEntry4(data string, input *string) *widget.Entry {
 	}
 
 	entryList4 = append(entryList4, entry)
+
+	if disableLst != nil {
+		*disableLst = append(*disableLst, entry)
+	}
+
 	return entry
 }
 
-func newFileIDEntry4(data string, input *int64) *widget.Entry {
+func newEntryPage4(data int64, input *int64, disableLst *[]fyne.Disableable) *widget.Entry {
 	entry := widget.NewEntry()
-	entry.Text = data
+	entry.Text = fmt.Sprintf("%d", data)
+
 	entry.Validator = func(s string) error {
 		n, err := strconv.ParseInt(s, 10, 64)
 		if err != nil {
@@ -206,7 +279,7 @@ func newFileIDEntry4(data string, input *int64) *widget.Entry {
 		}
 
 		if n <= 0 {
-			return fmt.Errorf("must bigger than zero")
+			return fmt.Errorf("page musr bigger than zero")
 		}
 
 		return nil
@@ -221,30 +294,190 @@ func newFileIDEntry4(data string, input *int64) *widget.Entry {
 		}
 	}
 
+	if disableLst != nil {
+		*disableLst = append(*disableLst, entry)
+	}
+
 	entryList4 = append(entryList4, entry)
 	return entry
 }
 
-func newFileTypeSelect4(data string, options []string, input *string) *widget.Select {
-	if data != "" {
-		func() {
-			for _, option := range options {
-				if option == data {
-					return
-				}
+func newEntryValid4(data string, input *sql.NullString, disableLst *[]fyne.Disableable) *widget.Entry {
+	entry := widget.NewEntry()
+	entry.Text = data
+
+	entry.OnChanged = func(s string) {
+		if entry.Validate() == nil {
+			if len(s) == 0 {
+				*input = sql.NullString{Valid: false}
+			} else {
+				*input = sql.NullString{Valid: true, String: s}
 			}
-			options = append(options, data)
-		}()
+		}
+	}
+
+	if disableLst != nil {
+		*disableLst = append(*disableLst, entry)
 	}
 
-	sel := widget.NewSelect(options, func(s string) {
+	entryList4 = append(entryList4, entry)
+	return entry
+}
+
+func newFileTypeSelect4(data string, fst model.FileSetType, input *string, disableLst *[]fyne.Disableable) fyne.CanvasObject {
+	c, err := systeminit.GetInit()
+	if err != nil {
+		return newEntry4(data, input, disableLst)
+	}
+
+	fstName, ok := model.FileSetTypeName[fst]
+	if !ok {
+		return newEntry4(data, input, disableLst)
+	}
+
+	fileType, ok := c.Yaml.File.FileType[fstName]
+	if !ok {
+		return newEntry4(data, input, disableLst)
+	}
+
+	if !func() bool {
+		for _, t := range fileType {
+			if t == data {
+				return true
+			}
+		}
+		return false
+	}() {
+		return newEntry4(data, input, disableLst)
+	}
+
+	sel := widget.NewSelect(fileType, func(s string) {
 		*input = s
 	})
 
+	if disableLst != nil {
+		*disableLst = append(*disableLst, sel)
+	}
+
 	sel.Selected = data
 	return sel
 }
 
+func newFileBeiKaoSelect4(data string, fst model.FileSetType, input *sql.NullString, materialInput *sql.NullString, w fyne.Window, disableLst *[]fyne.Disableable) fyne.CanvasObject {
+	c, err := systeminit.GetInit()
+	if err != nil {
+		return newEntryValid4(data, input, disableLst)
+	}
+
+	fstName, ok := model.FileSetTypeName[fst]
+	if !ok {
+		return newEntryValid4(data, input, disableLst)
+	}
+
+	beiKao, ok := c.Yaml.File.BeiKao[fstName]
+	if !ok {
+		return newEntryValid4(data, input, disableLst)
+	}
+
+	if !func() bool {
+		for _, t := range beiKao {
+			if t.Name == data {
+				return true
+			}
+		}
+		return false
+	}() {
+		return newEntryValid4(data, input, disableLst)
+	}
+
+	for _, i := range beiKao {
+		for _, j := range beiKao {
+			if i.Name == j.Name {
+				return newEntryValid4(data, input, disableLst)
+			}
+		}
+	}
+
+	beiKaoLst := make([]string, 0, len(beiKao))
+	for _, i := range beiKao {
+		beiKaoLst = append(beiKaoLst, i.Name)
+	}
+
+	sel := widget.NewSelect(beiKaoLst, func(s string) {
+		input.Valid = len(s) != 0
+		input.String = s
+
+		for _, i := range beiKao {
+			if s == i.Name && len(i.Material) != 0 {
+				dialog.ShowConfirm("确认", "此”备考“有预先录制的材料列表,是否立即使用?", func(b bool) {
+					if !b {
+						return
+					}
+
+					materialInput.Valid = true
+					materialInput.String = strings.Join(i.Material, "、") + "。"
+				}, w)
+			}
+		}
+
+	})
+
+	if disableLst != nil {
+		*disableLst = append(*disableLst, sel)
+	}
+
+	sel.Selected = data
+	return sel
+}
+
+func newSexSelect4(data bool, isMan *bool) *widget.Select {
+	sel := widget.NewSelect([]string{"男性", "女性"}, func(s string) {
+		*isMan = s == "男性"
+	})
+
+	if data {
+		sel.Selected = "男性"
+	} else {
+		sel.Selected = "女性"
+	}
+
+	return sel
+}
+
+func newDatePicker4(data time.Time, input *time.Time, w fyne.Window, disableLst *[]fyne.Disableable) *widget.Button {
+	btn := widget.NewButton("选择时间", func() {})
+
+	d := datepicker.NewDatePicker(data, time.Monday, func(t time.Time, b bool) {
+		if b {
+			*input = t
+			btn.SetText(t.Format("2006-01-02"))
+		}
+	})
+
+	btn.OnTapped = func() {
+		dialog.ShowCustomConfirm("选择时间", "确认", "放弃", d, d.OnActioned, w)
+	}
+
+	if disableLst != nil {
+		*disableLst = append(*disableLst, btn)
+	}
+	return btn
+}
+
+func newSameAboveCheck4(data string, input *bool, disableLst *[]fyne.Disableable) *widget.Check {
+	btn := widget.NewCheck(data, func(b bool) {
+		*input = b
+		for _, d := range *disableLst {
+			if b {
+				d.Disable()
+			} else {
+				d.Enable()
+			}
+		}
+	})
+	return btn
+}
+
 func checkAllInputRight4() error {
 	for _, e := range entryList4 {
 		err := e.Validate()

+ 5 - 3
src/v1main/record.go

@@ -13,15 +13,17 @@ type RecordWindow struct {
 	Menu           *Controller
 	Table          *RecordTable
 	FileWindow     fyne.Window
-	File           *model.File
+	File           model.File
 	NowPage        int64
 	InfoRecord     []model.FileMoveRecord
 	InfoDataRecord [][]string
 	SearchRecord   model.SearchRecord
 }
 
-func CreateRecordWindow(rt runtime.RunTime, f *model.File, fileWindow fyne.Window) *RecordWindow {
-	recordWindow := rt.App().NewWindow(fmt.Sprintf("档案迁移记录-%d", f.FileID))
+func CreateRecordWindow(rt runtime.RunTime, fc model.File, fileWindow fyne.Window) *RecordWindow {
+	f := fc.GetFile()
+
+	recordWindow := rt.App().NewWindow(fmt.Sprintf("档案出借记录-%d", f.FileID))
 	w := &RecordWindow{
 		Window: recordWindow,
 

+ 43 - 71
src/v1main/move.go → src/v1main/recordedit.go

@@ -12,10 +12,10 @@ import (
 	"github.com/SuperH-0630/hdangan/src/model"
 	"github.com/SuperH-0630/hdangan/src/runtime"
 	"github.com/SuperH-0630/hdangan/src/systeminit"
-	"time"
+	"gorm.io/gorm"
 )
 
-func ShowMove(rt runtime.RunTime, f *model.File, refresh func(rt runtime.RunTime)) {
+func ShowMoveEdit(rt runtime.RunTime, fc model.File, refresh func(rt runtime.RunTime)) {
 	config, err := systeminit.GetInit()
 	if errors.Is(err, systeminit.LuckyError) {
 		rt.DBConnectError(err)
@@ -25,7 +25,9 @@ func ShowMove(rt runtime.RunTime, f *model.File, refresh func(rt runtime.RunTime
 		return
 	}
 
-	infoWindow := rt.App().NewWindow(fmt.Sprintf("迁入迁出-%s-%d", f.Name, f.FileID))
+	f := fc.GetFile()
+
+	infoWindow := rt.App().NewWindow(fmt.Sprintf("出借记录-%s-%d", f.Name, f.FileID))
 
 	infoWindow.SetOnClosed(func() {
 		rt.Action()
@@ -37,23 +39,50 @@ func ShowMove(rt runtime.RunTime, f *model.File, refresh func(rt runtime.RunTime
 		infoWindow = nil
 	})
 
+	record, err := model.FindMoveRecord(rt, fc)
+	if errors.Is(err, gorm.ErrRecordNotFound) {
+		infoWindow.Resize(fyne.NewSize(300, 200))
+		d := dialog.NewInformation("提示", "当前档案不存在出借记录", infoWindow)
+		d.SetOnClosed(func() {
+			infoWindow.Close()
+		})
+		d.Show()
+		infoWindow.Show()
+		return
+	} else if err != nil {
+		infoWindow.Resize(fyne.NewSize(300, 200))
+		d := dialog.NewError(fmt.Errorf("数据错误: %s", err.Error()), infoWindow)
+		d.SetOnClosed(func() {
+			infoWindow.Close()
+		})
+		d.Show()
+		infoWindow.Show()
+		return
+	}
+
 	moveComment := widget.NewMultiLineEntry()
-	moveComment.Text = strToStr(f.MoveComment, "")
+	moveComment.Text = strToStr(record.MoveComment, "")
 	moveComment.Wrapping = fyne.TextWrapWord
 	moveComment.OnChanged = func(text string) {
-		f.MoveComment = sql.NullString{String: text, Valid: len(text) != 0}
+		record.MoveComment = sql.NullString{String: text, Valid: len(text) != 0}
 	}
 
 	leftLayout := layout.NewFormLayout()
 	left := container.New(leftLayout,
-		widget.NewLabel("迁入迁出状态:"),
-		newMoveStatusSelect2(fmt.Sprintf("%s", f.MoveStatus), config.Yaml.Move.MoveStatus, &f.MoveStatus),
+		widget.NewLabel("出借状态:"),
+		newMoveStatusSelect2(fmt.Sprintf("%s", record.MoveStatus), config.Yaml.Move.MoveStatus, &record.MoveStatus),
+
+		widget.NewLabel("借入人:"),
+		newEntryWithNULL2(fmt.Sprintf("%s", strToStr(record.MoveInPeopleName, "")), &record.MoveInPeopleName),
 
-		widget.NewLabel("最后迁出人:"),
-		newEntryWithNULL2(fmt.Sprintf("%s", strToStr(f.MoveOutPeopleName, "")), &f.MoveOutPeopleName),
+		widget.NewLabel("借入单位:"),
+		newMoveUnitSelectWithNULL2(fmt.Sprintf("%s", strToStr(record.MoveInPeopleUnit, "")), config.Yaml.Move.MoveUnit, &record.MoveInPeopleUnit),
 
-		widget.NewLabel("最后迁出单位:"),
-		newMoveUnitSelectWithNULL2(fmt.Sprintf("%s", strToStr(f.MoveOutPeopleUnit, "")), config.Yaml.Move.MoveUnit, &f.MoveOutPeopleUnit),
+		widget.NewLabel("借出人:"),
+		newEntryWithNULL2(fmt.Sprintf("%s", strToStr(record.MoveOutPeopleName, "")), &record.MoveOutPeopleName),
+
+		widget.NewLabel("借出单位:"),
+		newMoveUnitSelectWithNULL2(fmt.Sprintf("%s", strToStr(record.MoveOutPeopleUnit, "")), config.Yaml.Move.MoveUnit, &record.MoveOutPeopleUnit),
 
 		widget.NewLabel("最后迁出备注:"),
 		moveComment,
@@ -69,53 +98,10 @@ func ShowMove(rt runtime.RunTime, f *model.File, refresh func(rt runtime.RunTime
 			return
 		}
 
-		if f.MoveStatus == config.Yaml.Move.MoveInStatus {
-			// 正常非迁入之间更改
-			if f.MoveComment.Valid || f.MoveOutPeopleName.Valid || f.MoveOutPeopleUnit.Valid {
-				// 非借出状态,确有借出数据
-				dialog.ShowError(fmt.Errorf("非迁出状态却有迁出人等数据"), infoWindow)
-			} else {
-				oldRecord, _ := model.CheckFileMoveOut(f)
-
-				record := &model.FileMoveRecord{
-					// FileID会在执行SaveFile时绑定
-					MoveStatus:        f.MoveStatus,
-					MoveTime:          time.Now(),
-					MoveOutPeopleName: f.MoveOutPeopleName,
-					MoveOutPeopleUnit: f.MoveOutPeopleUnit,
-					MoveComment:       f.MoveComment,
-				}
-
-				f.LastMoveIn = record.MoveTime
-
-				err := model.SaveFileRecord(f, record, oldRecord)
-				if err != nil {
-					dialog.ShowError(fmt.Errorf("数据库错误: %s", err.Error()), infoWindow)
-				}
-			}
-		} else {
-			if !f.MoveOutPeopleName.Valid || !f.MoveOutPeopleUnit.Valid {
-				dialog.ShowError(fmt.Errorf("迁出状态需要填写迁出人和单位"), infoWindow)
-			} else {
-				oldRecord, _ := model.CheckFileMoveOut(f)
-
-				record := &model.FileMoveRecord{
-					MoveStatus:        f.MoveStatus,
-					MoveTime:          time.Now(),
-					MoveOutPeopleName: f.MoveOutPeopleName,
-					MoveOutPeopleUnit: f.MoveOutPeopleUnit,
-					MoveComment:       f.MoveComment,
-				}
-
-				f.LastMoveIn = record.MoveTime
-
-				err := model.SaveFileRecord(f, record, oldRecord)
-				if err != nil {
-					dialog.ShowError(fmt.Errorf("数据库错误: %s", err.Error()), infoWindow)
-				}
-			}
+		err = model.SaveRecord(rt, record)
+		if err != nil {
+			dialog.ShowError(fmt.Errorf("数据库错误: %s", err.Error()), infoWindow)
 		}
-
 		WinClose(infoWindow)
 		infoWindow = nil
 		refresh(rt)
@@ -153,20 +139,6 @@ func ShowMove(rt runtime.RunTime, f *model.File, refresh func(rt runtime.RunTime
 
 var entryList2 []*widget.Entry
 
-func newEntry2(data string, input *string) *widget.Entry {
-	entry := widget.NewEntry()
-	entry.Text = data
-
-	entry.OnChanged = func(s string) {
-		if entry.Validate() == nil {
-			*input = s
-		}
-	}
-
-	entryList2 = append(entryList2, entry)
-	return entry
-}
-
 func newEntryWithNULL2(data string, input *sql.NullString) *widget.Entry {
 	entry := widget.NewEntry()
 	entry.Text = data

+ 49 - 31
src/v1main/recordinfo.go

@@ -11,14 +11,8 @@ import (
 	"github.com/SuperH-0630/hdangan/src/runtime"
 )
 
-func readRecordData(rt runtime.RunTime, f *model.File, r *model.FileMoveRecord) (*fyne.Container, *fyne.Container) {
-	fileComment := widget.NewMultiLineEntry()
-	fileComment.Text = strToStr(f.FileComment, "")
-	fileComment.Wrapping = fyne.TextWrapWord
-	fileComment.OnChanged = func(s string) {
-		rt.Action()
-		fileComment.Text = strToStr(f.FileComment, "")
-	}
+func readRecordData(rt runtime.RunTime, fc model.File, r *model.FileMoveRecord) (*fyne.Container, *fyne.Container) {
+	f := fc.GetFile()
 
 	moveComment := widget.NewMultiLineEntry()
 	moveComment.Text = strToStr(r.MoveComment, "")
@@ -31,39 +25,42 @@ func readRecordData(rt runtime.RunTime, f *model.File, r *model.FileMoveRecord)
 	leftLayout := layout.NewFormLayout()
 	left := container.New(leftLayout,
 		widget.NewLabel("卷宗号:"),
-		widget.NewLabel(fmt.Sprintf("%d", f.FileID)),
+		widget.NewLabel(fmt.Sprintf("%d", f.FileSetID)),
 
-		widget.NewLabel("姓名:"),
-		widget.NewLabel(fmt.Sprintf("%s", f.Name)),
+		widget.NewLabel("卷宗类型:"),
+		widget.NewLabel(fmt.Sprintf("%s", model.FileSetTypeName[f.FileSetType])),
 
-		widget.NewLabel("身份证号:"),
-		widget.NewLabel(fmt.Sprintf("%s", f.IDCard)),
+		widget.NewLabel("联合文件编号::"),
+		widget.NewLabel(fmt.Sprintf("%d", f.FileUnionID)),
 
-		widget.NewLabel("户籍地:"),
-		widget.NewLabel(fmt.Sprintf("%s", f.Location)),
+		widget.NewLabel("出借记录号:"),
+		widget.NewLabel(fmt.Sprintf("%d", r.ID)),
 
-		widget.NewLabel("卷宗标题:"),
-		widget.NewLabel(fmt.Sprintf("%s", f.FileTitle)),
+		widget.NewLabel("档案类型:"),
+		widget.NewLabel(fmt.Sprintf("%s", model.FileSetTypeName[f.FileSetType])),
 
-		widget.NewLabel("卷宗类型:"),
-		widget.NewLabel(fmt.Sprintf("%s", f.FileType)),
+		widget.NewLabel("姓名:"),
+		widget.NewLabel(fmt.Sprintf("%s", f.Name)),
 
-		widget.NewLabel("卷宗备注:"),
-		fileComment,
+		widget.NewLabel("借出状态:"),
+		widget.NewLabel(fmt.Sprintf("%s", r.MoveStatus)),
 	)
 
 	rightLayout := layout.NewFormLayout()
 	right := container.New(rightLayout,
-		widget.NewLabel("迁入迁出状态:"),
-		widget.NewLabel(fmt.Sprintf("%s", r.MoveStatus)),
-
-		widget.NewLabel("动作发生时间:"),
+		widget.NewLabel("借出时间:"),
 		widget.NewLabel(fmt.Sprintf("%s", r.MoveTime.Format("2006-01-02 15:04:05"))),
 
-		widget.NewLabel("最后迁出人:"),
+		widget.NewLabel("借入人:"),
+		widget.NewLabel(fmt.Sprintf("%s", strToStr(r.MoveOutPeopleName, "暂无"))),
+
+		widget.NewLabel("借入单位:"),
+		widget.NewLabel(fmt.Sprintf("%s", strToStr(r.MoveOutPeopleUnit, "暂无"))),
+
+		widget.NewLabel("借出人:"),
 		widget.NewLabel(fmt.Sprintf("%s", strToStr(r.MoveOutPeopleName, "暂无"))),
 
-		widget.NewLabel("最后迁出单位:"),
+		widget.NewLabel("出单位:"),
 		widget.NewLabel(fmt.Sprintf("%s", strToStr(r.MoveOutPeopleUnit, "暂无"))),
 
 		widget.NewLabel("最后迁出备注:"),
@@ -73,8 +70,10 @@ func readRecordData(rt runtime.RunTime, f *model.File, r *model.FileMoveRecord)
 	return left, right
 }
 
-func ShowRecordInfo(rt runtime.RunTime, recordTableWindow fyne.Window, fileWindow fyne.Window, f *model.File, r *model.FileMoveRecord, refresh func(rt runtime.RunTime)) {
-	infoWindow := rt.App().NewWindow(fmt.Sprintf("迁入迁出详细信息-%s-%d", f.Name, f.FileID))
+func ShowRecordInfo(rt runtime.RunTime, recordTableWindow fyne.Window, fileWindow fyne.Window, fc model.File, r *model.FileMoveRecord, refresh func(rt runtime.RunTime)) {
+	f := fc.GetFile()
+
+	infoWindow := rt.App().NewWindow(fmt.Sprintf("档案借出信息-%s-%d", f.Name, f.FileID))
 
 	infoWindow.SetOnClosed(func() {
 		rt.Action()
@@ -90,7 +89,26 @@ func ShowRecordInfo(rt runtime.RunTime, recordTableWindow fyne.Window, fileWindo
 
 	upBox := container.NewHBox(left, right)
 
-	change := widget.NewButton("同档案的其他迁入迁出记录", func() {
+	warpRefresh := func(rt runtime.RunTime) {
+		left, right := readData(rt, f)
+		upBox.RemoveAll()
+		upBox.Add(left)
+		upBox.Add(right)
+		upBox.Refresh()
+		refresh(rt)
+	}
+
+	change := widget.NewButton("编辑本条记录", func() {
+		rt.Action()
+		ShowMoveEdit(rt, fc, warpRefresh)
+	})
+
+	new_ := widget.NewButton("新建记录", func() {
+		rt.Action()
+		ShowNewMove(rt, fc, warpRefresh)
+	})
+
+	other := widget.NewButton("同档案的其他迁入迁出记录", func() {
 		rt.Action()
 		recordTableWindow.Show()
 		recordTableWindow.CenterOnScreen()
@@ -107,7 +125,7 @@ func ShowRecordInfo(rt runtime.RunTime, recordTableWindow fyne.Window, fileWindo
 		dialog.ShowInformation("你的操作很危险", "删除档案迁移记录是被视作危险的行为,将不被允许操作。", infoWindow)
 	})
 
-	downBox := container.NewHBox(change, move, del)
+	downBox := container.NewHBox(change, new_, other, move, del)
 	downCenterBox := container.NewCenter(downBox)
 
 	gg := NewBg(5, 20)

+ 252 - 0
src/v1main/recordnew.go

@@ -0,0 +1,252 @@
+package v1main
+
+import (
+	"database/sql"
+	"errors"
+	"fmt"
+	"fyne.io/fyne/v2"
+	"fyne.io/fyne/v2/container"
+	"fyne.io/fyne/v2/dialog"
+	"fyne.io/fyne/v2/layout"
+	"fyne.io/fyne/v2/widget"
+	"github.com/SuperH-0630/hdangan/src/model"
+	"github.com/SuperH-0630/hdangan/src/runtime"
+	"github.com/SuperH-0630/hdangan/src/systeminit"
+	datepicker "github.com/sdassow/fyne-datepicker"
+	"gorm.io/gorm"
+	"time"
+)
+
+func ShowNewMove(rt runtime.RunTime, fc model.File, refresh func(rt runtime.RunTime)) {
+	config, err := systeminit.GetInit()
+	if errors.Is(err, systeminit.LuckyError) {
+		rt.DBConnectError(err)
+		return
+	} else if err != nil {
+		rt.DBConnectError(fmt.Errorf("配置文件错误,请检查配置文件状态。"))
+		return
+	}
+
+	f := fc.GetFile()
+
+	infoWindow := rt.App().NewWindow(fmt.Sprintf("新增出借记录-%s-%d", f.Name, f.FileID))
+
+	infoWindow.SetOnClosed(func() {
+		rt.Action()
+		infoWindow = nil
+	})
+	infoWindow.SetCloseIntercept(func() {
+		rt.Action()
+		WinClose(infoWindow)
+		infoWindow = nil
+	})
+
+	oldRecord, err := model.FindMoveRecord(rt, fc)
+	if errors.Is(err, gorm.ErrRecordNotFound) {
+		infoWindow.Resize(fyne.NewSize(300, 200))
+		d := dialog.NewInformation("提示", "当前档案不存在出借记录", infoWindow)
+		d.SetOnClosed(func() {
+			infoWindow.Close()
+		})
+		d.Show()
+		infoWindow.Show()
+		return
+	} else if err != nil {
+		infoWindow.Resize(fyne.NewSize(300, 200))
+		d := dialog.NewError(fmt.Errorf("数据错误: %s", err.Error()), infoWindow)
+		d.SetOnClosed(func() {
+			infoWindow.Close()
+		})
+		d.Show()
+		infoWindow.Show()
+		return
+	}
+
+	var newRecord model.FileMoveRecord
+
+	moveComment := widget.NewMultiLineEntry()
+	moveComment.Text = ""
+	moveComment.Wrapping = fyne.TextWrapWord
+	moveComment.OnChanged = func(text string) {
+		newRecord.MoveComment = sql.NullString{String: text, Valid: len(text) != 0}
+	}
+
+	leftLayout := layout.NewFormLayout()
+	left := container.New(leftLayout,
+		widget.NewLabel("出借状态:"),
+		newMoveStatusSelect7(config.Yaml.Move.MoveStatus, &newRecord.MoveStatus),
+
+		widget.NewLabel("借入人:"),
+		newEntryWithNULL7(&newRecord.MoveInPeopleName),
+
+		widget.NewLabel("借入单位:"),
+		newMoveUnitSelectWithNULL7(config.Yaml.Move.MoveUnit, &newRecord.MoveInPeopleUnit),
+
+		widget.NewLabel("借出人:"),
+		newEntryWithNULL7(&newRecord.MoveOutPeopleName),
+
+		widget.NewLabel("借出单位:"),
+		newMoveUnitSelectWithNULL7(config.Yaml.Move.MoveUnit, &newRecord.MoveOutPeopleUnit),
+
+		widget.NewLabel("借出单位:"),
+		newTimePicker7(time.Now(), &newRecord.MoveTime, infoWindow),
+
+		widget.NewLabel("最后迁出备注:"),
+		moveComment,
+	)
+
+	upBox := container.NewHBox(left)
+
+	save := widget.NewButton("保存", func() {
+		rt.Action()
+		err := checkAllInputRight7()
+		if err != nil {
+			dialog.ShowError(fmt.Errorf("请检查错误:%s", err.Error()), infoWindow)
+			return
+		}
+
+		if len(newRecord.MoveStatus) == 0 {
+			dialog.ShowError(fmt.Errorf("必须设置状态"), infoWindow)
+			return
+		}
+
+		if newRecord.MoveTime.Before(oldRecord.MoveTime) {
+			dialog.ShowError(fmt.Errorf("借出时间不得早于上次借出的事件"), infoWindow)
+			return
+		}
+
+		if newRecord.MoveTime.After(time.Now().Add(24 * time.Hour)) {
+			dialog.ShowError(fmt.Errorf("借出时间不得晚于当下的24小时之后"), infoWindow)
+			return
+		}
+
+		err = model.CreateFileRecord(rt, f, &newRecord)
+		if err != nil {
+			dialog.ShowError(fmt.Errorf("数据库错误:%s", err.Error()), infoWindow)
+			return
+		}
+
+		WinClose(infoWindow)
+		infoWindow = nil
+		refresh(rt)
+	})
+
+	cancle := widget.NewButton("丢弃", func() {
+		rt.Action()
+		dialog.ShowConfirm("放弃?", "你确定要放弃你的操作码?", func(b bool) {
+			rt.Action()
+			if b {
+				WinClose(infoWindow)
+				infoWindow = nil
+				refresh(rt)
+			}
+		}, infoWindow)
+	})
+
+	downBox := container.NewHBox(save, cancle)
+	downCenterBox := container.NewCenter(downBox)
+
+	gg := NewBg(5, 20)
+
+	box := container.NewVBox(upBox, gg, downCenterBox)
+	cbox := container.NewCenter(box)
+
+	bg := NewBg(fmax(cbox.MinSize().Width, cbox.Size().Width, 220),
+		fmax(cbox.MinSize().Height, cbox.Size().Height, 280))
+
+	lastContainer := container.NewStack(bg, cbox)
+	infoWindow.SetContent(lastContainer)
+
+	infoWindow.Show()
+	infoWindow.CenterOnScreen()
+}
+
+var entryList7 []*widget.Entry
+
+func newEntryWithNULL7(input *sql.NullString) *widget.Entry {
+	entry := widget.NewEntry()
+
+	entry.OnChanged = func(s string) {
+		if entry.Validate() == nil {
+			if len(s) == 0 {
+				*input = sql.NullString{
+					Valid:  false,
+					String: "",
+				}
+			} else {
+				*input = sql.NullString{
+					Valid:  true,
+					String: s,
+				}
+			}
+		}
+	}
+
+	entryList7 = append(entryList7, entry)
+	return entry
+}
+
+func newMoveStatusSelect7(options []string, input *string) *widget.Select {
+	sel := widget.NewSelect(options, func(s string) {
+		*input = s
+	})
+	return sel
+}
+
+func newMoveUnitSelectWithNULL7(firstOptions []string, input *sql.NullString) *widget.Select {
+	const emptySelectItem = "暂无"
+
+	options := make([]string, 0, len(firstOptions)+1)
+	options = append(options, emptySelectItem)
+
+	for _, fo := range firstOptions {
+		if fo != emptySelectItem && fo != "" {
+			options = append(options, fo)
+		}
+	}
+
+	sel := widget.NewSelect(options, func(s string) {
+		if len(s) == 0 || s == emptySelectItem {
+			*input = sql.NullString{
+				Valid:  false,
+				String: "",
+			}
+		} else {
+			*input = sql.NullString{
+				Valid:  true,
+				String: s,
+			}
+		}
+	})
+
+	sel.PlaceHolder = ""
+	return sel
+}
+
+func newTimePicker7(t time.Time, input *time.Time, w fyne.Window) *widget.Button {
+	btn := widget.NewButton("选择时间", func() {})
+
+	d := datepicker.NewDateTimePicker(t, time.Monday, func(t time.Time, b bool) {
+		if b {
+			*input = t
+			btn.SetText(t.Format("2006-01-02 15:04:05"))
+		}
+	})
+
+	btn.OnTapped = func() {
+		dialog.ShowCustomConfirm("选择时间", "确认", "放弃", d, d.OnActioned, w)
+	}
+
+	return btn
+}
+
+func checkAllInputRight7() error {
+	for _, e := range entryList7 {
+		err := e.Validate()
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}

+ 6 - 6
src/v1main/recordnmenu.go

@@ -4,7 +4,7 @@ import (
 	"fmt"
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2/dialog"
-	"github.com/SuperH-0630/hdangan/src/excelreader"
+	"github.com/SuperH-0630/hdangan/src/excelio"
 	"github.com/SuperH-0630/hdangan/src/model"
 	"github.com/SuperH-0630/hdangan/src/runtime"
 	"strings"
@@ -51,7 +51,7 @@ func GetMainMenuRecord(rt runtime.RunTime, w *RecordWindow, refresh func(rt runt
 				dialog.ShowError(fmt.Errorf("文件名必须以.xlsx结尾"), w.Window)
 			}
 
-			err = excelreader.OutputFileRecord(rt, savepath, w.File, []model.FileMoveRecord{}, nil)
+			err = excelio.OutputFileRecord(rt, savepath, w.File, []model.FileMoveRecord{}, nil)
 			if err != nil {
 				dialog.ShowError(fmt.Errorf("生成数据库遇到错误:%s", err), w.Window)
 			} else {
@@ -83,7 +83,7 @@ func GetMainMenuRecord(rt runtime.RunTime, w *RecordWindow, refresh func(rt runt
 				dialog.ShowError(fmt.Errorf("文件名必须以.xlsx结尾"), w.Window)
 			}
 
-			err = excelreader.OutputFileRecord(rt, savepath, w.File, []model.FileMoveRecord{}, &w.SearchRecord)
+			err = excelio.OutputFileRecord(rt, savepath, w.File, []model.FileMoveRecord{}, &w.SearchRecord)
 			if err != nil {
 				dialog.ShowError(fmt.Errorf("生成数据库遇到错误:%s", err), w.Window)
 			} else {
@@ -115,7 +115,7 @@ func GetMainMenuRecord(rt runtime.RunTime, w *RecordWindow, refresh func(rt runt
 				dialog.ShowError(fmt.Errorf("文件名必须以.xlsx结尾"), w.Window)
 			}
 
-			err = excelreader.OutputFileRecord(rt, savepath, nil, []model.FileMoveRecord{}, nil)
+			err = excelio.OutputFileRecord(rt, savepath, nil, []model.FileMoveRecord{}, nil)
 			if err != nil {
 				dialog.ShowError(fmt.Errorf("生成数据库遇到错误:%s", err), w.Window)
 			} else {
@@ -147,7 +147,7 @@ func GetMainMenuRecord(rt runtime.RunTime, w *RecordWindow, refresh func(rt runt
 				dialog.ShowError(fmt.Errorf("文件名必须以.xlsx结尾"), w.Window)
 			}
 
-			err = excelreader.OutputFileRecord(rt, savepath, nil, []model.FileMoveRecord{}, &w.SearchRecord)
+			err = excelio.OutputFileRecord(rt, savepath, nil, []model.FileMoveRecord{}, &w.SearchRecord)
 			if err != nil {
 				dialog.ShowError(fmt.Errorf("生成数据库遇到错误:%s", err), w.Window)
 			} else {
@@ -179,7 +179,7 @@ func GetMainMenuRecord(rt runtime.RunTime, w *RecordWindow, refresh func(rt runt
 				dialog.ShowError(fmt.Errorf("文件名必须以.xlsx结尾"), w.Window)
 			}
 
-			err = excelreader.OutputFileRecord(rt, savepath, w.File, w.InfoRecord, nil)
+			err = excelio.OutputFileRecord(rt, savepath, w.File, w.InfoRecord, nil)
 			if err != nil {
 				dialog.ShowError(fmt.Errorf("生成数据库遇到错误:%s", err), w.Window)
 			} else {

+ 6 - 4
src/v1main/recordtable.go

@@ -9,7 +9,7 @@ import (
 	"github.com/SuperH-0630/hdangan/src/runtime"
 )
 
-var TopHeaderDataRecord = []string{"发起时间", "迁入迁出状态", "迁出人", "迁出单位", "详情"}
+var TopHeaderDataRecord = []string{"发起时间", "迁入迁出状态", "借出人", "借出单位", "借入人", "借入单位", "详情"}
 var xiangQingIndexRecord = -1
 
 const defaultItemCountRecord = model.DefaultPageItemCount
@@ -108,9 +108,11 @@ func (c *RecordTable) UpdateTableInfoRecord(rt runtime.RunTime, record []model.F
 
 		res[i][0] = f.MoveTime.Format("2006-01-02 15:04:05")
 		res[i][1] = f.MoveStatus
-		res[i][2] = strToStr(f.MoveOutPeopleName, "暂无")
-		res[i][3] = strToStr(f.MoveOutPeopleUnit, "暂无")
-		res[i][4] = "点击查看"
+		res[i][2] = strToStr(f.MoveInPeopleName, "暂无")
+		res[i][3] = strToStr(f.MoveInPeopleUnit, "暂无")
+		res[i][4] = strToStr(f.MoveOutPeopleName, "暂无")
+		res[i][5] = strToStr(f.MoveOutPeopleUnit, "暂无")
+		res[i][6] = "点击查看"
 	}
 
 	c.Record.InfoRecord = record

+ 32 - 17
src/v1main/tongji.go

@@ -2,33 +2,48 @@ package v1main
 
 import (
 	"fmt"
-	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2/dialog"
 	"github.com/SuperH-0630/hdangan/src/model"
 	"github.com/SuperH-0630/hdangan/src/runtime"
 )
 
-func TongJi(rt runtime.RunTime, w fyne.Window) {
-	ca, err := model.CountFile(rt)
-	if err != nil {
-		dialog.ShowError(fmt.Errorf("数据库错误:%s", err.Error()), w)
-		return
-	}
+func TongJi(rt runtime.RunTime, w *CtrlWindow) {
+	fca := make([]int64, len(model.FileSetTypeList))
+	sum := int64(0)
+
+	for _, k := range model.FileSetTypeList {
+		var err error
+		maker, ok := model.FileSetTypeMaker[k]
+		if !ok {
+			fca[k] = 0
+			continue
+		}
 
-	fca, err := model.CountDifferentFile(rt)
-	if err != nil {
-		dialog.ShowError(fmt.Errorf("数据库错误:%s", err.Error()), w)
-		return
+		fca[k], err = model.CountFile(rt, maker())
+		if err != nil {
+			fca[k] = 0
+			continue
+		}
+
+		sum += fca[k]
 	}
 
-	m1 := fmt.Sprintf("数据库共记录档案:%d件。", ca)
-	m2 := "其中"
-	for _, i := range fca {
-		m2 += fmt.Sprintf(",%s共%d件", i.File, i.Res)
+	m1 := fmt.Sprintf("数据库共记录档案:%d件。", sum)
+	m2 := ""
+	if sum != 0 {
+		m2 = "其中"
+		for _, i := range model.FileSetTypeList {
+			ca := fca[i]
+			if ca <= 0 {
+				continue
+			}
+
+			m2 += fmt.Sprintf(",%s共%d件", model.FileSetTypeName[i], ca)
+		}
+		m2 += "。"
 	}
-	m2 += "。"
 
 	m := fmt.Sprintf("%s\n%s", m1, m2)
 
-	dialog.ShowInformation("数据统计", m, w)
+	dialog.ShowInformation("数据统计", m, w.window)
 }

+ 58 - 45
src/v1main/where.go

@@ -2,7 +2,6 @@ package v1main
 
 import (
 	"database/sql"
-	"errors"
 	"fmt"
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2/container"
@@ -11,7 +10,6 @@ import (
 	"fyne.io/fyne/v2/widget"
 	"github.com/SuperH-0630/hdangan/src/model"
 	"github.com/SuperH-0630/hdangan/src/runtime"
-	"github.com/SuperH-0630/hdangan/src/systeminit"
 	datepicker "github.com/sdassow/fyne-datepicker"
 	"strconv"
 	"time"
@@ -20,15 +18,6 @@ import (
 var whereWindow fyne.Window
 
 func createWindow(rt runtime.RunTime, target *model.SearchWhere, refresh func(rt runtime.RunTime)) {
-	config, err := systeminit.GetInit()
-	if errors.Is(err, systeminit.LuckyError) {
-		rt.DBConnectError(err)
-		return
-	} else if err != nil {
-		rt.DBConnectError(fmt.Errorf("配置文件错误,请检查配置文件状态。"))
-		return
-	}
-
 	var s = *target
 	whereWindow = rt.App().NewWindow(fmt.Sprintf("搜索筛选"))
 
@@ -50,53 +39,41 @@ func createWindow(rt runtime.RunTime, target *model.SearchWhere, refresh func(rt
 		widget.NewLabel("姓名:"),
 		newEntry3(&s.Name),
 
+		widget.NewLabel("曾用名:"),
+		newEntry3(&s.Name),
+
 		widget.NewLabel("身份证号:"),
 		newEntry3(&s.IDCard),
 
-		widget.NewLabel("户籍地:"),
-		newEntry3(&s.Location),
-
-		widget.NewLabel("卷宗标题:"),
-		newEntry3(&s.FileTitle),
+		widget.NewLabel("性别:"),
+		newSexSelect2(&s.IsMan),
 
-		widget.NewLabel("卷宗类型:"),
-		newSelect3(config.Yaml.File.FileType, &s.FileType),
-	)
+		widget.NewLabel("生日(始,含)"),
+		newDatePicker3(&s.BirthdayStart, whereWindow),
 
-	centerLayout := layout.NewFormLayout()
-	center := container.New(centerLayout,
-		widget.NewLabel("最早第一次迁入时间(含):"),
-		newTimePicker3(&s.FirstMoveInStart, whereWindow),
+		widget.NewLabel("生日(终,含)"),
+		newDatePicker3(&s.BirthdayEnd, whereWindow),
 
-		widget.NewLabel("最晚第一次迁入时间(含):"),
-		newTimePicker3(&s.FirstMoveInStart, whereWindow),
-
-		widget.NewLabel("最早最后一次迁入(归还)时间(含):"),
-		newTimePicker3(&s.LastMoveInStart, whereWindow),
-
-		widget.NewLabel("最晚最后一次迁入(归还)时间(含):"),
-		newTimePicker3(&s.LastMoveInEnd, whereWindow),
-
-		widget.NewLabel("最早最后一次迁出时间(含):"),
-		newTimePicker3(&s.LastMoveOutStart, whereWindow),
-
-		widget.NewLabel("最晚最后一次迁出时间(含):"),
-		newTimePicker3(&s.LastMoveOutEnd, whereWindow),
+		widget.NewLabel("备注:"),
+		newEntry3(&s.Comment),
 	)
 
 	rightLayout := layout.NewFormLayout()
 	right := container.New(rightLayout,
-		widget.NewLabel("迁入迁出状态:"),
-		newSelect3(config.Yaml.Move.MoveStatus, &s.MoveStatus),
+		widget.NewLabel("卷宗ID:"),
+		newFileIDEntry3(&s.FileSetID),
+
+		widget.NewLabel("文件联合ID:"),
+		newFileIDEntry3(&s.FileUnionID),
 
-		widget.NewLabel("最后迁出人:"),
-		newEntry3(&s.MoveOutPeopleName),
+		widget.NewLabel("文件ID:"),
+		newFileIDEntry3(&s.FileID),
 
-		widget.NewLabel("最后迁出单位:"),
-		newSelect3(config.Yaml.Move.MoveUnit, &s.MoveOutPeopleUnit),
+		widget.NewLabel("组ID:"),
+		newFileIDEntry3(&s.FileGroupID),
 	)
 
-	upBox := container.NewHBox(left, center, right)
+	upBox := container.NewHBox(left, right)
 
 	save := widget.NewButton("保存条件", func() {
 		rt.Action()
@@ -215,7 +192,7 @@ func newFileIDEntry3(input *int64) *widget.Entry {
 		}
 	}
 
-	entryList = append(entryList, entry)
+	entryList3 = append(entryList3, entry)
 	return entry
 }
 
@@ -265,6 +242,16 @@ func checkAllInputRight3() error {
 
 	return nil
 }
+
+func newSexSelect2(isMan *string) *widget.Select {
+	sel := widget.NewSelect([]string{"男性", "女性", "均可"}, func(s string) {
+		*isMan = s
+	})
+
+	sel.Selected = "均可"
+	return sel
+}
+
 func newTimePicker3(input *sql.NullTime, w fyne.Window) *widget.Button {
 	btn := widget.NewButton("选择时间", func() {})
 
@@ -290,3 +277,29 @@ func newTimePicker3(input *sql.NullTime, w fyne.Window) *widget.Button {
 
 	return btn
 }
+
+func newDatePicker3(input *sql.NullTime, w fyne.Window) *widget.Button {
+	btn := widget.NewButton("选择时间", func() {})
+
+	t := time.Now()
+	if input.Valid {
+		t = input.Time
+	}
+
+	d := datepicker.NewDatePicker(t, time.Monday, func(t time.Time, b bool) {
+		if b {
+			input.Valid = true
+			input.Time = t
+			btn.SetText(t.Format("2006-01-02 15:04:05"))
+		} else {
+			input.Valid = false
+			btn.SetText("选择时间")
+		}
+	})
+
+	btn.OnTapped = func() {
+		dialog.ShowCustomConfirm("选择时间", "确认", "放弃", d, d.OnActioned, w)
+	}
+
+	return btn
+}

+ 13 - 7
src/v1main/whererecord.go

@@ -57,22 +57,28 @@ func (w *WhereWindow) create(rt runtime.RunTime) {
 
 	leftLayout := layout.NewFormLayout()
 	left := container.New(leftLayout,
-		widget.NewLabel("最早迁出时间(含):"),
+		widget.NewLabel("借出状态:"),
+		newSelect5(config.Yaml.Move.MoveStatus, &s.MoveStatus),
+
+		widget.NewLabel("出借时间(始,含):"),
 		newTimePicker5(&s.MoveOutStart, w.Window),
 
-		widget.NewLabel("最晚迁出时间(含):"),
+		widget.NewLabel("出时间(终,含):"),
 		newTimePicker5(&s.MoveOutEnd, w.Window),
 	)
 
 	rightLayout := layout.NewFormLayout()
 	right := container.New(rightLayout,
-		widget.NewLabel("迁入迁出状态:"),
-		newSelect5(config.Yaml.Move.MoveStatus, &s.MoveStatus),
+		widget.NewLabel("借出人:"),
+		newEntry5(&s.MoveOutPeopleName),
+
+		widget.NewLabel("借出单位:"),
+		newSelect5(config.Yaml.Move.MoveUnit, &s.MoveOutPeopleUnit),
 
-		widget.NewLabel("最后迁出人:"),
+		widget.NewLabel("借入人:"),
 		newEntry5(&s.MoveOutPeopleName),
 
-		widget.NewLabel("最后迁出单位:"),
+		widget.NewLabel("借入单位:"),
 		newSelect5(config.Yaml.Move.MoveUnit, &s.MoveOutPeopleUnit),
 	)
 
@@ -185,7 +191,7 @@ func newFileIDEntry5(input *int64) *widget.Entry {
 
 	entry.OnChanged = func(s string) {
 		if entry.Validate() == nil {
-			n, err := strconv.ParseInt(s, 64, 10)
+			n, err := strconv.ParseInt(s, 10, 64)
 			if err == nil {
 				*input = n
 			}