2 Commits c78094f7ee ... 868a2c9322

Auteur SHA1 Bericht Datum
  zoie 868a2c9322 add:文件管理 1 maand geleden
  zoie e013763e82 add:添加电子签名 1 maand geleden

+ 1 - 2
ColdVerify_server.go

@@ -1,7 +1,6 @@
 package main
 
 import (
-	"ColdVerify_server/TimeTask"
 	"ColdVerify_server/conf"
 	_ "ColdVerify_server/routers"
 	"fmt"
@@ -49,7 +48,7 @@ func main() {
 	beego.BConfig.RunMode = "dev"                           //  应用的运行模式
 	beego.BConfig.Listen.HTTPPort = HTTPPort_int            //监听端口  本地:8518  线上:8528
 
-	go TimeTask.Init() // 定时任务
+	//go TimeTask.Init() // 定时任务
 
 	beego.Run()
 

+ 183 - 1
controllers/DeviceClass.go

@@ -3,6 +3,7 @@ package controllers
 import (
 	"ColdVerify_server/conf"
 	"ColdVerify_server/lib"
+	"ColdVerify_server/logs"
 	"ColdVerify_server/models/Account"
 	"ColdVerify_server/models/Certificate"
 	"ColdVerify_server/models/Device"
@@ -10,10 +11,11 @@ import (
 	"ColdVerify_server/models/Task"
 	"ColdVerify_server/models/VerifyTemplate"
 	"fmt"
-	beego "github.com/beego/beego/v2/server/web"
 	"math"
 	"strconv"
 	"strings"
+
+	beego "github.com/beego/beego/v2/server/web"
 )
 
 type DeviceClassController struct {
@@ -442,6 +444,9 @@ func (c *DeviceClassController) List_Add() {
 	succesId := []string{}
 	for _, sn_id := range list {
 		T_sn := strings.Split(sn_id, ",")[0]
+		if strings.HasPrefix(T_sn, "03") && strings.HasSuffix(T_sn, "000001") {
+			T_sn = strings.TrimSuffix(strings.TrimPrefix(T_sn, "03"), "000001")
+		}
 		T_id := strings.Split(sn_id, ",")[1]
 		if len(T_sn) == 0 || len(T_id) == 0 {
 			errList = append(errList, sn_id)
@@ -592,6 +597,18 @@ func (c *DeviceClassController) List_Copy() {
 
 	}
 
+	// 自动填写备注
+	err := AutoFillDeviceClassRemark(T_paste_task_id)
+	if err != nil {
+		logs.Error("自动填写设备备注失败", err)
+	}
+
+	// 自动填写布点
+	err = AutoFillDeploy(T_paste_task_id, 0)
+	if err != nil {
+		logs.Error("自动填写布点失败", err)
+	}
+
 	if len(errList) == 0 {
 		c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: cnt}
 		c.ServeJSON()
@@ -610,6 +627,171 @@ func (c *DeviceClassController) List_Copy() {
 	return
 }
 
+func (c *DeviceClassController) List_Add_batch() {
+	// 验证登录 User_is, User_r
+	User_r, User_is := Account.Verification_Admin(c.Ctx.GetCookie("User_tokey"), c.GetString("User_tokey"))
+	if !User_is {
+		c.Data["json"] = lib.JSONS{Code: 201, Msg: "请重新登录!"}
+		c.ServeJSON()
+		return
+	}
+
+	T_class, _ := c.GetInt("T_class")
+
+	// 删除之前的设备列表
+	pasteList, _ := Device.Read_DeviceClassList_OrderList(T_class, "", "", "", 0, 9999)
+	for _, ds := range pasteList {
+		Device.Delete_DeviceClassList_(ds)
+	}
+
+	T_sn_id_list := c.GetString("T_sn_id_list")
+
+	// 根据 | 分割数据
+	items := strings.Split(T_sn_id_list, "|")
+
+	// 创建一个 map 来去重
+	uniqueItems := make(map[string]struct{})
+
+	for _, item := range items {
+		// 如果不为空,则添加到 map 中
+		if item != "" {
+			uniqueItems[item] = struct{}{}
+		}
+	}
+
+	// 将唯一项拼接成字符串
+	result := ""
+
+	for item := range uniqueItems {
+		result += item + "|"
+	}
+
+	// 移除最后的 | 字符
+	if len(result) > 0 {
+		result = result[:len(result)-1]
+	}
+	T_sn_id_list = result
+
+	list := strings.Split(strings.TrimRight(T_sn_id_list, "|"), "|")
+	T_remark := c.GetString("T_remark")
+
+	_, is := Device.Read_DeviceClass_ById(T_class)
+	if !is {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "T_class 错误"}
+		c.ServeJSON()
+		return
+	}
+
+	var successNum int // 成功数量
+	errList := []string{}
+	errList2 := []string{}
+	succesId := []string{}
+	for _, sn_id := range list {
+		T_sn := strings.Split(sn_id, ",")[0]
+		if strings.HasPrefix(T_sn, "03") && strings.HasSuffix(T_sn, "000001") {
+			T_sn = strings.TrimSuffix(strings.TrimPrefix(T_sn, "03"), "000001")
+		}
+		T_id := strings.Split(sn_id, ",")[1]
+		if len(T_sn) == 0 || len(T_id) == 0 {
+			errList = append(errList, sn_id)
+			continue
+		}
+		// 判断是否已存在sn
+		dc, is := Device.Read_DeviceClassList_T_class_T_sn(T_class, T_sn)
+		// 添加的id和数据库已存在id相同
+		if is && dc.T_id == T_id {
+			successNum += 1
+			continue
+		}
+		// 添加的id和数据库已存在id不同
+		if is && dc.T_id != T_id {
+			_, is = Device.Read_DeviceClassList_T_class_T_id(T_class, T_id)
+			if is {
+				errList2 = append(errList2, sn_id)
+				continue
+			}
+			dc.T_id = T_id
+			dc.T_remark = T_remark
+			if !Device.Update_DeviceClassList(dc, "T_id", "T_remark") {
+				errList = append(errList, sn_id)
+			} else {
+				successNum += 1
+			}
+			continue
+		}
+
+		if !is {
+			_, is = Device.Read_DeviceClassList_T_class_T_id(T_class, T_id)
+			if is {
+				errList2 = append(errList2, sn_id)
+				continue
+			}
+		}
+
+		var pdf Certificate.CertificatePdf
+		//pdfList, _ := Certificate.Read_CertificatePdf_Newest(T_sn)
+		pdfList, _ := Certificate.Read_CertificatePdf_T_layout_no(T_id, "")
+		if len(pdfList) > 0 {
+			pdf = pdfList[0]
+		}
+
+		var_ := Device.DeviceClassList{
+			T_class:          T_class,
+			T_id:             T_id,
+			T_sn:             T_sn,
+			T_failure_time:   pdf.T_failure_time,
+			T_pdf:            pdf.T_pdf,
+			T_Certificate_sn: pdf.T_Certificate_sn,
+			T_remark:         T_remark,
+			T_State:          1,
+		}
+
+		_, is = Device.Add_DeviceClassList(var_)
+		if !is {
+			errList = append(errList, sn_id)
+			continue
+		}
+		successNum += 1
+		succesId = append(succesId, sn_id)
+		System.Add_UserLogs_T(User_r.T_uuid, "分类设备管理", "添加", var_)
+	}
+	// 通过 T_class 查找任务 id
+	task, is := Task.Read_Task_ByT_class(T_class)
+
+	if len(errList) == 0 && len(errList2) == 0 {
+		if is && len(task.T_task_id) > 0 {
+			// 自动填写备注
+			err := AutoFillDeviceClassRemark(task.T_task_id)
+			if err != nil {
+				logs.Error("自动填写设备备注失败", err)
+			}
+
+			// 自动填写布点
+			err = AutoFillDeploy(task.T_task_id, 0)
+			if err != nil {
+				logs.Error("自动填写布点失败", err)
+			}
+		}
+		c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: successNum}
+		c.ServeJSON()
+		return
+	}
+	var errStr string
+	if len(errList2) > 0 {
+		errStr += strings.Join(errList2, ",") + "编号已存在"
+	}
+	if len(errList) > 0 {
+		if len(errStr) > 0 {
+			errStr += ","
+		}
+		errStr += strings.Join(errList, ",") + "添加失败"
+	}
+
+	c.Data["json"] = lib.JSONS{Code: 210, Msg: errStr, Data: successNum}
+	c.ServeJSON()
+	return
+}
+
 // 修改-
 func (c *DeviceClassController) List_Up() {
 	// 验证登录 User_is, User_r

+ 207 - 0
controllers/FileManagement.go

@@ -0,0 +1,207 @@
+package controllers
+
+import (
+	"ColdVerify_server/conf"
+	"ColdVerify_server/lib"
+	"ColdVerify_server/models/Account"
+	FileManagementModel "ColdVerify_server/models/FileManagement"
+	"ColdVerify_server/models/System"
+	"math"
+	"strings"
+
+	beego "github.com/beego/beego/v2/server/web"
+)
+
+type FileManagementController struct {
+	beego.Controller
+}
+
+func (c *FileManagementController) List() {
+	User_r, User_is := Account.Verification_Admin(c.Ctx.GetCookie("User_tokey"), c.GetString("User_tokey"))
+	if !User_is {
+		c.Data["json"] = lib.JSONS{Code: 201, Msg: "请重新登录!"}
+		c.ServeJSON()
+		return
+	}
+
+	page, _ := c.GetInt("page")
+	if page < 1 {
+		page = 1
+	}
+	pageSize, _ := c.GetInt("page_z")
+	if pageSize < 1 {
+		pageSize = conf.Page_size
+	}
+
+	fileName := strings.TrimSpace(c.GetString("file_name"))
+
+	list, cnt := FileManagementModel.ReadFileManagementList(fileName, page, pageSize)
+	pageCount := math.Ceil(float64(cnt) / float64(pageSize))
+
+	var response lib.R_JSONS
+	response.List = list
+	response.Page = page
+	response.Page_size = int(pageCount)
+	response.Pages = lib.Func_page(int64(page), int64(pageCount))
+	response.Num = int(cnt)
+
+	System.Add_UserLogs_T(User_r.T_uuid, "文件管理", "查询", map[string]interface{}{
+		"file_name": fileName,
+		"page":      page,
+		"page_z":    pageSize,
+	})
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: response}
+	c.ServeJSON()
+}
+
+func (c *FileManagementController) Get() {
+	User_r, User_is := Account.Verification_Admin(c.Ctx.GetCookie("User_tokey"), c.GetString("User_tokey"))
+	if !User_is {
+		c.Data["json"] = lib.JSONS{Code: 201, Msg: "请重新登录!"}
+		c.ServeJSON()
+		return
+	}
+
+	id, err := c.GetInt("Id")
+	if err != nil || id <= 0 {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "Id 错误!"}
+		c.ServeJSON()
+		return
+	}
+
+	file, ok := FileManagementModel.ReadFileManagementById(id)
+	if !ok {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "未找到文件信息!"}
+		c.ServeJSON()
+		return
+	}
+
+	System.Add_UserLogs_T(User_r.T_uuid, "文件管理", "详情", map[string]interface{}{
+		"id": id,
+	})
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: FileManagementModel.ToFileManagementR(file)}
+	c.ServeJSON()
+}
+
+func (c *FileManagementController) Add() {
+	User_r, User_is := Account.Verification_Admin(c.Ctx.GetCookie("User_tokey"), c.GetString("User_tokey"))
+	if !User_is {
+		c.Data["json"] = lib.JSONS{Code: 201, Msg: "请重新登录!"}
+		c.ServeJSON()
+		return
+	}
+
+	fileName := strings.TrimSpace(c.GetString("file_name"))
+	filePath := strings.TrimSpace(c.GetString("file_path"))
+	if len(fileName) == 0 || len(filePath) == 0 {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "文件名称和文件路径不能为空!"}
+		c.ServeJSON()
+		return
+	}
+
+	file := FileManagementModel.FileManagement{
+		FileName: fileName,
+		FilePath: filePath,
+	}
+
+	id, ok := FileManagementModel.AddFileManagement(file)
+	if !ok {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "添加失败!"}
+		c.ServeJSON()
+		return
+	}
+
+	System.Add_UserLogs_T(User_r.T_uuid, "文件管理", "添加", file)
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: id}
+	c.ServeJSON()
+}
+
+func (c *FileManagementController) Up() {
+	User_r, User_is := Account.Verification_Admin(c.Ctx.GetCookie("User_tokey"), c.GetString("User_tokey"))
+	if !User_is {
+		c.Data["json"] = lib.JSONS{Code: 201, Msg: "请重新登录!"}
+		c.ServeJSON()
+		return
+	}
+
+	id, err := c.GetInt("Id")
+	if err != nil || id <= 0 {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "Id 错误!"}
+		c.ServeJSON()
+		return
+	}
+
+	file, ok := FileManagementModel.ReadFileManagementById(id)
+	if !ok {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "未找到文件信息!"}
+		c.ServeJSON()
+		return
+	}
+
+	fileName := strings.TrimSpace(c.GetString("file_name"))
+	filePath := strings.TrimSpace(c.GetString("file_path"))
+	updateCols := make([]string, 0, 2)
+
+	if len(fileName) > 0 {
+		file.FileName = fileName
+		updateCols = append(updateCols, "FileName")
+	}
+	if len(filePath) > 0 {
+		file.FilePath = filePath
+		updateCols = append(updateCols, "FilePath")
+	}
+
+	if len(updateCols) == 0 {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "没有可更新的内容!"}
+		c.ServeJSON()
+		return
+	}
+
+	if !FileManagementModel.UpdateFileManagement(file, updateCols...) {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "修改失败!"}
+		c.ServeJSON()
+		return
+	}
+
+	System.Add_UserLogs_T(User_r.T_uuid, "文件管理", "修改", file)
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!"}
+	c.ServeJSON()
+}
+
+func (c *FileManagementController) Del() {
+	User_r, User_is := Account.Verification_Admin(c.Ctx.GetCookie("User_tokey"), c.GetString("User_tokey"))
+	if !User_is {
+		c.Data["json"] = lib.JSONS{Code: 201, Msg: "请重新登录!"}
+		c.ServeJSON()
+		return
+	}
+
+	id, err := c.GetInt("Id")
+	if err != nil || id <= 0 {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "Id 错误!"}
+		c.ServeJSON()
+		return
+	}
+
+	file, ok := FileManagementModel.ReadFileManagementById(id)
+	if !ok {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "未找到文件信息!"}
+		c.ServeJSON()
+		return
+	}
+
+	if !FileManagementModel.DeleteFileManagement(file) {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "删除失败!"}
+		c.ServeJSON()
+		return
+	}
+
+	System.Add_UserLogs_T(User_r.T_uuid, "文件管理", "删除", file)
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!"}
+	c.ServeJSON()
+}

+ 561 - 8
controllers/Task.go

@@ -7,6 +7,7 @@ import (
 	"ColdVerify_server/lib/wx"
 	"ColdVerify_server/logs"
 	"ColdVerify_server/models/Account"
+	"ColdVerify_server/models/Certificate"
 	"ColdVerify_server/models/Device"
 	"ColdVerify_server/models/InfoCollection"
 	"ColdVerify_server/models/System"
@@ -15,13 +16,6 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	beego "github.com/beego/beego/v2/server/web"
-	"github.com/google/uuid"
-	"github.com/xuri/excelize/v2"
-	"gonum.org/v1/plot"
-	"gonum.org/v1/plot/plotter"
-	"gonum.org/v1/plot/vg"
-	"gonum.org/v1/plot/vg/draw"
 	"image/color"
 	"math"
 	"os"
@@ -29,12 +23,357 @@ import (
 	"strings"
 	"sync"
 	"time"
+
+	beego "github.com/beego/beego/v2/server/web"
+	"github.com/google/uuid"
+	"github.com/xuri/excelize/v2"
+	"gonum.org/v1/plot"
+	"gonum.org/v1/plot/plotter"
+	"gonum.org/v1/plot/vg"
+	"gonum.org/v1/plot/vg/draw"
 )
 
 type TaskController struct {
 	beego.Controller
 }
 
+// 导入Excel创建任务并写入模版数据
+func (c *TaskController) Import_Tasks() {
+	file, _, err := c.GetFile("file")
+	if err != nil {
+		c.Data["json"] = lib.JSONS{Code: 203, Msg: "读取上传文件失败!"}
+		c.ServeJSON()
+		return
+	}
+	defer file.Close()
+
+	xlsx, err := excelize.OpenReader(file)
+	if err != nil {
+		c.Data["json"] = lib.JSONS{Code: 203, Msg: "Excel 文件解析失败!"}
+		c.ServeJSON()
+		return
+	}
+	defer func() {
+		_ = xlsx.Close()
+	}()
+
+	sheetName := c.GetString("sheet")
+	if len(sheetName) == 0 {
+		sheets := xlsx.GetSheetList()
+		if len(sheets) == 0 {
+			c.Data["json"] = lib.JSONS{Code: 203, Msg: "Excel 文件为空!"}
+			c.ServeJSON()
+			return
+		}
+		sheetName = sheets[0]
+	}
+
+	rows, err := xlsx.GetRows(sheetName)
+	if err != nil {
+		c.Data["json"] = lib.JSONS{Code: 203, Msg: "读取 Excel 内容失败!"}
+		c.ServeJSON()
+		return
+	}
+	if len(rows) < 2 {
+		c.Data["json"] = lib.JSONS{Code: 203, Msg: "Excel 中缺少数据行!"}
+		c.ServeJSON()
+		return
+	}
+
+	headers := rows[0]
+	successIDs := make([]string, 0)
+	skippedRows := make([]int, 0)
+	failedRows := make([]string, 0)
+
+	for idx := 1; idx < len(rows); idx++ {
+		rowCells := rows[idx]
+		rowData := make(map[string]string, len(headers))
+		nonEmpty := false
+		for colIdx, header := range headers {
+			header = strings.TrimSpace(header)
+			if len(header) == 0 {
+				continue
+			}
+			value := ""
+			if colIdx < len(rowCells) {
+				value = strings.TrimSpace(rowCells[colIdx])
+			}
+			if len(value) > 0 {
+				nonEmpty = true
+			}
+			rowData[header] = value
+		}
+		if !nonEmpty {
+			continue
+		}
+		companyName := strings.TrimSpace(rowData["公司名称"])
+		taskName := strings.TrimSpace(rowData["任务名称"])
+		if len(companyName) == 0 || len(taskName) == 0 {
+			skippedRows = append(skippedRows, idx+1)
+			continue
+		}
+		if rowData["布点图片"] == "" {
+			skippedRows = append(skippedRows, idx+1)
+			continue
+		}
+
+		taskID, err := c.importTaskRow(rowData)
+		if err != nil {
+			failedRows = append(failedRows, fmt.Sprintf("第%d行: %v", idx+1, err))
+			continue
+		}
+		successIDs = append(successIDs, taskID)
+	}
+
+	msg := fmt.Sprintf("导入完成, 成功 %d 条, 跳过 %d 条, 失败 %d 条", len(successIDs), len(skippedRows), len(failedRows))
+	respData := map[string]interface{}{
+		"success_ids":  successIDs,
+		"skipped_rows": skippedRows,
+	}
+	if len(failedRows) > 0 {
+		respData["failed_rows"] = failedRows
+	}
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: msg, Data: respData}
+	c.ServeJSON()
+}
+
+func (c *TaskController) importTaskRow(row map[string]string) (string, error) {
+	templateName := strings.TrimSpace(row["模版名称"])
+	lastOpenBracket := strings.LastIndex(templateName, "[")
+	lastCloseBracket := strings.LastIndex(templateName, "]")
+	if lastOpenBracket == -1 || lastCloseBracket == -1 || lastOpenBracket >= lastCloseBracket {
+		return "", fmt.Errorf("模版名称中未找到模版ID")
+	}
+	templateID := templateName[lastOpenBracket+1 : lastCloseBracket]
+	if len(templateID) == 0 {
+		return "", fmt.Errorf("模版ID为空")
+	}
+
+	vt, ok := VerifyTemplate.Read_VerifyTemplate(templateID)
+	if !ok {
+		return "", fmt.Errorf("模版不存在: %s", templateID)
+	}
+
+	taskUID := strings.TrimSpace(row["唯一标识"])
+	if len(taskUID) == 0 {
+		return "", fmt.Errorf("唯一标识缺失")
+	}
+	existingTask, taskExists := Task.Read_Task_ByUid(taskUID)
+
+	companyName := strings.TrimSpace(row["公司名称"])
+	if len(companyName) == 0 {
+		return "", fmt.Errorf("公司名称缺失")
+	}
+
+	cachedUser, found, err := Account.GetCompanyFromCache(companyName)
+	if err != nil {
+		return "", fmt.Errorf("公司数据读取失败: %w", err)
+	}
+	var user Account.User
+	if !found {
+		newUser := Account.User{
+			T_name:    companyName,
+			T_pass:    Account.DefaultCompanyPasswordHash,
+			T_passstr: Account.DefaultCompanyPasswordPlain,
+			T_Show:    1,
+			T_State:   1,
+			T_pid:     0,
+		}
+		newID, ok := Account.Add_User(newUser)
+		if !ok {
+			return "", fmt.Errorf("自动创建公司失败")
+		}
+		if err := Account.UpdateUserPath(int(newID)); err != nil {
+			logs.Error("更新新公司路径失败:", err)
+		}
+		createdUser, is := Account.Read_User_ById(int(newID))
+		if !is {
+			return "", fmt.Errorf("获取新公司信息失败")
+		}
+		user = createdUser
+		Account.UpsertCompanyCache(user)
+	} else {
+		user = cachedUser
+	}
+
+	resolveAdminUUID := func(fieldName, fallback string, strict bool) (string, error) {
+		value, ok := row[fieldName]
+		if !ok || len(strings.TrimSpace(value)) == 0 {
+			return fallback, nil
+		}
+		admin, err := Account.EnsureAdminByName(strings.TrimSpace(value), "")
+		if err != nil {
+			if strict {
+				return "", err
+			}
+			logs.Error(fieldName+" 负责人处理失败:", err)
+			return fallback, nil
+		}
+		return admin.T_uuid, nil
+	}
+
+	taskName := strings.TrimSpace(row["任务名称"])
+	if len(taskName) == 0 {
+		return "", fmt.Errorf("任务名称缺失")
+	}
+	snValue := strings.TrimSpace(row["SN"])
+
+	classPath := ""
+	if vt.T_class > 0 {
+		if cls, err := VerifyTemplate.Read_VerifyTemplateClass_ById(vt.T_class); err == nil {
+			p := cls.T_path
+			p = strings.Trim(p, "/")
+			p = strings.TrimPrefix(p, "0/")
+			classPath = p
+		}
+	}
+
+	schemeFallback := ""
+	collectionFallback := ""
+	reportingFallback := ""
+	deliveryFallback := ""
+	projectFallback := ""
+	categoryFallback := ""
+	deviceTypeFallback := ""
+	verifyTypeFallback := ""
+	if taskExists {
+		schemeFallback = existingTask.T_scheme
+		collectionFallback = existingTask.T_collection
+		reportingFallback = existingTask.T_reporting
+		deliveryFallback = existingTask.T_delivery
+		projectFallback = existingTask.T_project
+		categoryFallback = existingTask.T_category
+		deviceTypeFallback = existingTask.T_device_type
+		verifyTypeFallback = existingTask.T_verify_type
+	}
+
+	schemeUUID, err := resolveAdminUUID("实施方案", schemeFallback, false)
+	if err != nil {
+		return "", fmt.Errorf("实施方案负责人创建失败: %w", err)
+	}
+	collectionUUID, err := resolveAdminUUID("数据采集", collectionFallback, true)
+	if err != nil {
+		return "", fmt.Errorf("数据采集负责人创建失败: %w", err)
+	}
+	reportingUUID, err := resolveAdminUUID("报告编辑", reportingFallback, true)
+	if err != nil {
+		return "", fmt.Errorf("报告编辑负责人创建失败: %w", err)
+	}
+	deliveryUUID, err := resolveAdminUUID("交付审核", deliveryFallback, false)
+	if err != nil {
+		return "", fmt.Errorf("交付审核负责人创建失败: %w", err)
+	}
+	projectUUID, _ := resolveAdminUUID("项目负责人", projectFallback, false)
+
+	categoryValue := strings.TrimSpace(row["类别"])
+	if len(categoryValue) == 0 {
+		categoryValue = categoryFallback
+	}
+
+	deviceTypeValue := strings.TrimSpace(row["设备类型"])
+	if len(deviceTypeValue) == 0 {
+		deviceTypeValue = deviceTypeFallback
+	} else {
+		if mapped, ok := Task.DeviceTypeMap[deviceTypeValue]; ok {
+			deviceTypeValue = mapped
+		} else {
+			deviceTypeValue = deviceTypeValue
+		}
+	}
+
+	verifyTypeValue := strings.TrimSpace(row["验证类型"])
+	if len(verifyTypeValue) == 0 {
+		verifyTypeValue = verifyTypeFallback
+	}
+
+	taskRecord := Task.Task{
+		T_Distributor_id:       user.T_Distributor_id,
+		T_uuid:                 user.T_uuid,
+		T_name:                 taskName,
+		T_VerifyTemplate_id:    templateID,
+		T_VerifyTemplate_class: classPath,
+		T_sn:                   snValue,
+		T_uid:                  taskUID,
+		T_scheme:               schemeUUID,
+		T_collection:           collectionUUID,
+		T_reporting:            reportingUUID,
+		T_delivery:             deliveryUUID,
+		T_project:              projectUUID,
+		T_category:             categoryValue,
+		T_device_type:          deviceTypeValue,
+		T_verify_type:          verifyTypeValue,
+	}
+
+	var taskID string
+	if taskExists {
+		taskRecord.Id = existingTask.Id
+		taskRecord.T_task_id = existingTask.T_task_id
+		taskRecord.T_State = existingTask.T_State
+		if ok := Task.Update_Task(taskRecord, "T_Distributor_id", "T_uuid", "T_name", "T_VerifyTemplate_id", "T_VerifyTemplate_class", "T_sn", "T_uid", "T_scheme", "T_collection", "T_reporting", "T_delivery", "T_project", "T_category", "T_device_type", "T_verify_type"); !ok {
+			return "", fmt.Errorf("更新任务失败")
+		}
+		taskID = existingTask.T_task_id
+	} else {
+		dc := Device.DeviceClass{
+			T_uuid:  user.T_uuid,
+			T_State: 1,
+		}
+		classID, ok := Device.Add_DeviceClass(dc)
+		if !ok {
+			return "", fmt.Errorf("创建分类失败")
+		}
+		taskRecord.T_class = int(classID)
+		taskRecord.T_State = 1
+		taskRecord.T_deadline = time.Now().AddDate(0, 2, 0).Format("2006-01-02")
+		if len(strings.TrimSpace(taskRecord.T_device_type)) > 0 {
+			number, err := Task.GenerateNextT_report_number(taskRecord.T_device_type)
+			if err != nil {
+				return "", fmt.Errorf("生成报告编号失败: %w", err)
+			}
+			taskRecord.T_report_number = number
+		}
+
+		if newID, ok := Task.Add_Task(taskRecord); !ok {
+			return "", fmt.Errorf("创建任务失败")
+		} else {
+			taskID = newID
+		}
+	}
+
+	mapList, _ := VerifyTemplate.Read_VerifyTemplateMap_List(templateID, 0, 0)
+	labelMap := make(map[string]VerifyTemplate.VerifyTemplateMap_R, len(mapList))
+	for _, m := range mapList {
+		labelMap[m.T_name] = m
+	}
+
+	dataList := make([]VerifyTemplate.VerifyTemplateMapData, 0, len(row))
+	for key, val := range row {
+		if vtm, exists := labelMap[key]; exists {
+			dataList = append(dataList, VerifyTemplate.VerifyTemplateMapData{
+				T_source:               vtm.T_source,
+				T_task_id:              taskID,
+				T_VerifyTemplate_id:    templateID,
+				T_VerifyTemplateMap_id: vtm.T_id,
+				T_value:                val,
+				T_Required:             vtm.T_Required,
+				T_Construction:         vtm.T_Construction,
+				T_flow_sort:            vtm.T_flow_sort,
+				T_max_time:             vtm.T_max_time,
+				T_min_time:             vtm.T_min_time,
+				T_start_time:           int64(vtm.T_start_time),
+			})
+		}
+	}
+	if len(dataList) > 0 {
+		if _, ok := VerifyTemplate.AddOrUpdate_VerifyTemplateMapData(dataList); !ok {
+			return "", fmt.Errorf("写入模版数据失败")
+		}
+	}
+
+	return taskID, nil
+}
+
 // 列表 -
 func (c *TaskController) List() {
 	// 验证登录 User_is, User_r
@@ -1134,6 +1473,18 @@ func (c *TaskController) Up() {
 		clos = append(clos, "T_VerifyTemplate_class")
 	}
 	if len(T_VerifyTemplate_id) > 0 {
+		// 保存旧的 T_VerifyTemplate_id
+		old_T_VerifyTemplate_id := r.T_VerifyTemplate_id
+		// 判断旧的模版id与新模版id是否一致,如果不一致则复制旧模板数据到新模板
+		if old_T_VerifyTemplate_id != T_VerifyTemplate_id && len(old_T_VerifyTemplate_id) > 0 {
+			// 创建新任务对象用于复制数据
+			new_task := r
+			new_task.T_VerifyTemplate_id = T_VerifyTemplate_id
+			_, err := CopyMapData(r, new_task, 0)
+			if err != nil {
+				logs.Error("复制旧模板数据到新模板失败", err)
+			}
+		}
 		r.T_VerifyTemplate_id = T_VerifyTemplate_id
 		clos = append(clos, "T_VerifyTemplate_id")
 	}
@@ -1496,6 +1847,59 @@ func (c *TaskController) Up() {
 	return
 }
 
+// 保存电子签名pdf
+func (c *TaskController) SaveElectronicSignaturePDF() {
+	// 验证登录 User_is, User_r
+	User_r, User_is := Account.Verification_Admin(c.Ctx.GetCookie("User_tokey"), c.GetString("User_tokey"))
+	if !User_is {
+		c.Data["json"] = lib.JSONS{Code: 201, Msg: "请重新登录!"}
+		c.ServeJSON()
+		return
+	}
+
+	T_pdf1 := c.GetString("T_pdf1") // 方案
+	T_pdf2 := c.GetString("T_pdf2") // 证书
+
+	T_task_id := c.GetString("T_task_id")
+	r, is := Task.Read_Task(T_task_id)
+	if !is {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "Id 错误!"}
+		c.ServeJSON()
+		return
+	}
+
+	// .......
+	clos := make([]string, 0)
+	// 验证报告内容T_pdf1,上传后将 当前任务 验证方案 标志 为 5 (0未完成 1已完成(客户通过) 2已退回(客户) 3已通过(报告负责人) 4已退回(报告负责人) 5已提交)
+	// 上传后设置方案结束时间,计算所需时间和超时时间
+	if len(T_pdf1) > 0 {
+		r.T_pdf1_elec_signature = T_pdf1
+		clos = append(clos, "T_pdf1")
+		go GetWatermarkPdf(r, T_pdf1, false, "T_pdf1")
+	}
+
+	// 验证报告内容T_pdf2 ,上传后将 当前任务 报告编写 标志 为 1
+	// 上传后设置报告结束时间,计算所需时间和超时时间
+	if len(T_pdf2) > 0 {
+		r.T_pdf1_elec_signature = T_pdf2
+		clos = append(clos, "T_pdf2")
+		go GetWatermarkPdf(r, T_pdf2, false, "T_pdf2")
+
+	}
+
+	if !Task.Update_Task(r, clos...) {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "修改失败!"}
+		c.ServeJSON()
+		return
+	}
+	// 添加任务操作日志
+	Task.Add_TaskLogs_T(User_r.T_uuid, T_task_id, "任务管理", "保存电子签名方案/报告", r)
+	System.Add_UserLogs_T(User_r.T_uuid, "任务管理", "保存电子签名方案/报告", r)
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!"}
+	c.ServeJSON()
+	return
+}
+
 // 修改验证方案状态
 func (c *TaskController) UpSchemeState() {
 	Admin_r, Admin_is := Account.Verification_Admin(c.Ctx.GetCookie("User_tokey"), c.GetString("User_tokey"))
@@ -3044,7 +3448,7 @@ func (c *TaskController) Copy() {
 		T_name:                 T_name,
 		T_VerifyTemplate_class: r.T_VerifyTemplate_class,
 		T_VerifyTemplate_id:    r.T_VerifyTemplate_id,
-		T_deadline:             r.T_deadline,
+		T_deadline:             time.Now().AddDate(0, 2, 0).Format("2006-01-02"),
 		T_scheme:               r.T_scheme,
 		T_collection:           r.T_collection,
 		T_reporting:            r.T_reporting,
@@ -3076,6 +3480,24 @@ func (c *TaskController) Copy() {
 		c.ServeJSON()
 		return
 	}
+
+	// 复制 DeviceClassList
+	err := CopyDeviceClassList(r, var_)
+	if err != nil {
+		logs.Error("复制设备列表失败", err)
+	}
+	// 自动填写备注
+	err = AutoFillDeviceClassRemark(T_paste_task_id)
+	if err != nil {
+		logs.Error("自动填写设备备注失败", err)
+	}
+
+	// 自动填写布点
+	err = AutoFillDeploy(T_paste_task_id, 0)
+	if err != nil {
+		logs.Error("自动填写布点失败", err)
+	}
+
 	NatsServer.Create_Local_Table(T_paste_task_id)
 	Task.Redis_Task_T_report_number_DelK(var_.T_report_number) // 删除redis内的任务编号
 
@@ -3403,3 +3825,134 @@ func (c *TaskController) SyncPDFWatermark() {
 	c.ServeJSON()
 	return
 }
+
+// 复制设备列表
+func CopyDeviceClassList(copy_task, paste_task Task.Task) error {
+	// 读取源任务的设备列表
+	copyList, _ := Device.Read_DeviceClassList_OrderList(copy_task.T_class, "", "", "", 0, 9999)
+
+	// 遍历源任务的设备列表,为每个设备创建新的 DeviceClassList 并添加到目标任务
+	for _, v := range copyList {
+		// 读取证书信息
+		var pdf Certificate.CertificatePdf
+		pdfList, _ := Certificate.Read_CertificatePdf_T_layout_no(v.T_id, "")
+		if len(pdfList) > 0 {
+			pdf = pdfList[0]
+		}
+
+		var_ := Device.DeviceClassList{
+			T_class:          paste_task.T_class,
+			T_id:             v.T_id,
+			T_sn:             v.T_sn,
+			T_failure_time:   pdf.T_failure_time,
+			T_pdf:            pdf.T_pdf,
+			T_Certificate_sn: pdf.T_Certificate_sn,
+			T_remark:         v.T_remark,
+			T_terminal:       v.T_terminal,
+			T_State:          1,
+		}
+
+		_, is := Device.Add_DeviceClassList(var_)
+		if !is {
+			return fmt.Errorf("添加设备列表失败: %s", v.T_id)
+		}
+	}
+	return nil
+}
+
+// 自动填写设备备注
+func AutoFillDeviceClassRemark(T_task_id string) error {
+	task, is := Task.Read_Task(T_task_id)
+	if !is {
+		return fmt.Errorf("读取任务失败: %s", T_task_id)
+	}
+
+	verifyTemplate, is := VerifyTemplate.Read_VerifyTemplate(task.T_VerifyTemplate_id)
+	if !is {
+		return fmt.Errorf("读取验证模板失败: %s", task.T_VerifyTemplate_id)
+	}
+	verifyTemplate_R := VerifyTemplate.VerifyTemplateToVerifyTemplate_R(verifyTemplate)
+	T_deploy_list := verifyTemplate_R.T_deploy_list
+
+	// 循环查询布点
+	deviceClassRemarkMap := make(map[int][]string)
+	deviceClassList := Device.Read_DeviceClassList_List_id_By_Terminal(task.T_class, false)
+	for _, deploy := range T_deploy_list {
+		if len(deploy.T_scope) > 0 {
+			dcl := FilterByRange(deviceClassList, deploy.T_scope)
+			for _, dc := range dcl {
+				deviceClassRemarkMap[dc.Id] = append(deviceClassRemarkMap[dc.Id], deploy.T_name)
+			}
+		}
+	}
+
+	for _, deviceClass := range deviceClassList {
+		if remark, ok := deviceClassRemarkMap[deviceClass.Id]; ok {
+			deviceClass.T_remark = strings.Join(remark, "|")
+			if !Device.Update_DeviceClassList(deviceClass, "T_remark") {
+				return fmt.Errorf("修改备注失败: %d", deviceClass.Id)
+			}
+		}
+	}
+
+	return nil
+}
+
+// 自动填写布点
+func AutoFillDeploy(T_task_id string, T_source int) error {
+	task, is := Task.Read_Task(T_task_id)
+	if !is {
+		return fmt.Errorf("读取任务失败: %s", T_task_id)
+	}
+
+	verifyTemplate, is := VerifyTemplate.Read_VerifyTemplate(task.T_VerifyTemplate_id)
+	if !is {
+		return fmt.Errorf("读取验证模板失败: %s", task.T_VerifyTemplate_id)
+	}
+	verifyTemplate_R := VerifyTemplate.VerifyTemplateToVerifyTemplate_R(verifyTemplate)
+
+	Map_List := VerifyTemplate.Read_VerifyTemplateMap_List_For_Data(task.T_VerifyTemplate_id, T_source, 0)
+	MapData := VerifyTemplate.Read_VerifyTemplateMapData_List(T_source, T_task_id, task.T_VerifyTemplate_id, Map_List)
+	T_deploy_list := verifyTemplate_R.T_deploy_list
+
+	// 循环查询布点
+	deployMap := make(map[string]string)
+	for _, deploy := range T_deploy_list {
+		deviceClassList := Device.Read_DeviceClassList_List_ByT_remark(task.T_class, deploy.T_name)
+		var snList []string
+		for _, v := range deviceClassList {
+			snList = append(snList, v.T_sn)
+		}
+		deployMap[deploy.T_name] = strings.Join(snList, "|")
+	}
+	MapDataList := make([]VerifyTemplate.VerifyTemplateMapData, 0)
+
+	for _, v := range MapData {
+		if snList, ok := deployMap[v.T_name]; ok {
+			val := VerifyTemplate.VerifyTemplateMapData{
+				T_source:               v.T_source,
+				T_task_id:              task.T_task_id,
+				T_VerifyTemplate_id:    task.T_VerifyTemplate_id,
+				T_VerifyTemplateMap_id: v.T_VerifyTemplateMap_id,
+				T_Required:             v.T_Required,
+				T_Construction:         v.T_Construction,
+				T_flow_sort:            v.T_flow_sort,
+				T_max_time:             v.T_max_time,
+				T_min_time:             v.T_min_time,
+
+				T_value:      snList,
+				T_start_time: v.T_start_time,
+			}
+			MapDataList = append(MapDataList, val)
+		}
+	}
+
+	if len(MapDataList) > 0 {
+		_, is := VerifyTemplate.AddOrUpdate_VerifyTemplateMapData_ADD_History(MapDataList, T_source, "", 0, 0, 0)
+		if !is {
+			return fmt.Errorf("保存布点数据失败")
+		}
+	}
+
+	return nil
+}

+ 30 - 9
controllers/User.go

@@ -315,12 +315,13 @@ func (c *UserController) Signature_List() {
 	}
 
 	T_name := c.GetString("T_name")
+	T_type, _ := c.GetInt("T_type")
 
 	var cnt int64
 	var distributorMap map[string]string
 	//distributorList := Distributor.Read_Distributor_List_ALL("")
 	//distributorMap := Distributor.DistributorListToMap(distributorList)
-	List, cnt := Account.Read_UserSignature_List(user_r.T_uuid, T_name, page, page_z, distributorMap)
+	List, cnt := Account.Read_UserSignature_List(user_r.T_uuid, T_name, T_type, page, page_z, distributorMap)
 	page_size := math.Ceil(float64(cnt) / float64(page_z))
 	r_jsons.List = List
 	r_jsons.Page = page
@@ -345,13 +346,15 @@ func (c *UserController) Signature_Add() {
 
 	T_name := c.GetString("T_name")
 	T_signature := c.GetString("T_signature")
+	T_type, _ := c.GetInt("T_type")
+	var is_seal bool
+	if T_type == 2 {
+		is_seal = true
+	}
 
-	var_ := Account.UserSignature{
-		T_name:           T_name,
-		T_signature:      T_signature,
-		T_State:          1,
-		T_uuid:           user_r.T_uuid,           // 用户uuid
-		T_Distributor_id: user_r.T_Distributor_id, // 经销商id
+	signature_pdf, err := lib.ProcessImage(T_signature, is_seal)
+	if err != nil {
+		signature_pdf = T_signature
 	}
 
 	if _, is := Account.Read_UserSignatureByT_name(user_r.T_uuid, T_name); is {
@@ -360,6 +363,14 @@ func (c *UserController) Signature_Add() {
 		return
 	}
 
+	var_ := Account.UserSignature{
+		T_name:           T_name,
+		T_signature:      signature_pdf,
+		T_State:          1,
+		T_uuid:           user_r.T_uuid,
+		T_Distributor_id: user_r.T_Distributor_id,
+		T_type:           T_type,
+	}
 	Id, is := Account.Add_UserSignature(var_)
 	if !is {
 		c.Data["json"] = lib.JSONS{Code: 202, Msg: "添加失败!"}
@@ -386,6 +397,16 @@ func (c *UserController) Signature_Up() {
 	Id, _ := c.GetInt("Id")
 	T_name := c.GetString("T_name")
 	T_signature := c.GetString("T_signature")
+	T_type, _ := c.GetInt("T_type")
+	var is_seal bool
+	if T_type == 2 {
+		is_seal = true
+	}
+
+	signature_pdf, err := lib.ProcessImage(T_signature, is_seal)
+	if err != nil {
+		signature_pdf = T_signature
+	}
 
 	r, is := Account.Read_UserSignature_ById(Id)
 	if !is {
@@ -406,8 +427,8 @@ func (c *UserController) Signature_Up() {
 	if len(T_name) > 0 {
 		r.T_name = T_name
 	}
-	if len(T_signature) > 0 {
-		r.T_signature = T_signature
+	if len(signature_pdf) > 0 {
+		r.T_signature = signature_pdf
 	}
 
 	// .......

+ 261 - 1
controllers/VerifyTemplate.go

@@ -12,11 +12,14 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	beego "github.com/beego/beego/v2/server/web"
 	"math"
+	"os"
 	"strconv"
 	"strings"
 	"time"
+
+	beego "github.com/beego/beego/v2/server/web"
+	"github.com/xuri/excelize/v2"
 )
 
 type VerifyTemplateController struct {
@@ -1404,3 +1407,260 @@ func CopyMapData(copy_task, paste_task Task.Task, T_source int) (ids []int64, er
 	}
 	return nil, err
 }
+
+// 导出所有模板的标签名称 - 每个模板一个Sheet
+func (c *VerifyTemplateController) Export_Labels() {
+	// 验证登录
+	_, User_is := Account.Verification_Admin(c.Ctx.GetCookie("User_tokey"), c.GetString("User_tokey"))
+	if !User_is {
+		c.Data["json"] = lib.JSONS{Code: 201, Msg: "请重新登录!"}
+		c.ServeJSON()
+		return
+	}
+
+	// 读取所有模板分类
+	classList := VerifyTemplate.Read_VerifyTemplateClass_List()
+
+	// 收集所有模板
+	type tpl struct {
+		Id         string
+		Name       string
+		TemplateId string
+		ClassName  string
+	}
+	var tplList []tpl
+	var collectTpl func(list []VerifyTemplate.VerifyTemplateClass)
+	collectTpl = func(list []VerifyTemplate.VerifyTemplateClass) {
+		for _, cls := range list {
+			// 当前分类下的模板
+			vlist, _ := VerifyTemplate.Read_VerifyTemplate_List(cls.Id, "", 0, 9999)
+			for _, v := range vlist {
+				tplList = append(tplList, tpl{Id: v.T_VerifyTemplate_id, Name: v.T_name, TemplateId: v.T_VerifyTemplate_id, ClassName: cls.T_name})
+			}
+			// 递归子分类
+			if len(cls.Children) > 0 {
+				collectTpl(cls.Children)
+			}
+		}
+	}
+	collectTpl(classList)
+
+	// 创建excel
+	f := excelize.NewFile()
+	sheetName := "Sheet1"
+	idx, _ := f.GetSheetIndex(sheetName)
+
+	f.SetActiveSheet(idx)
+
+	// 表头
+	f.SetCellValue(sheetName, "A1", "分类名称")
+	f.SetCellValue(sheetName, "B1", "模版名称")
+	f.SetCellValue(sheetName, "C1", "标签名称")
+
+	// 设置列宽
+	f.SetColWidth(sheetName, "A", "A", 30)
+	f.SetColWidth(sheetName, "B", "B", 40)
+	f.SetColWidth(sheetName, "C", "C", 40)
+
+	line := 1
+
+	// 写入模板与对应标签
+	for _, t := range tplList {
+		mapList, _ := VerifyTemplate.Read_VerifyTemplateMap_List(t.Id, 0, 0)
+		if len(mapList) == 0 {
+			line++
+			row := line
+			f.SetCellValue(sheetName, fmt.Sprintf("A%d", row), t.ClassName)
+			f.SetCellValue(sheetName, fmt.Sprintf("B%d", row), t.Name+fmt.Sprintf("[%s]", t.TemplateId))
+			continue
+		}
+
+		startRow := line + 1
+		for idxLabel, m := range mapList {
+			line++
+			row := line
+			if idxLabel == 0 {
+				f.SetCellValue(sheetName, fmt.Sprintf("A%d", row), t.ClassName)
+				f.SetCellValue(sheetName, fmt.Sprintf("B%d", row), t.Name)
+			}
+			f.SetCellValue(sheetName, fmt.Sprintf("C%d", row), m.T_name)
+		}
+		if len(mapList) > 1 {
+			f.MergeCell(sheetName, fmt.Sprintf("A%d", startRow), fmt.Sprintf("A%d", line))
+			f.MergeCell(sheetName, fmt.Sprintf("B%d", startRow), fmt.Sprintf("B%d", line))
+		}
+	}
+
+	// 设置居中样式
+	styleCenter, styleErr := f.NewStyle(&excelize.Style{
+		Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center"},
+	})
+	if styleErr != nil {
+		logs.Error("NewStyle Err:", styleErr)
+	} else {
+		f.SetCellStyle(sheetName, "A1", fmt.Sprintf("C%d", line), styleCenter)
+	}
+
+	// 生成文件
+	lib.Create_Dir("./ofile")
+	timeStr := time.Now().Format("20060102150405")
+	filePath := "ofile/" + timeStr + ".xlsx"
+	if err := f.SaveAs(filePath); err != nil {
+		logs.Error(lib.FuncName(), err)
+		c.Data["json"] = lib.JSONS{Code: 203, Msg: "文件保存失败!"}
+		c.ServeJSON()
+		return
+	}
+
+	// 上传到七牛
+	if !lib.Pload_qiniu(filePath, filePath) {
+		c.Data["json"] = lib.JSONS{Code: 203, Msg: "oss!"}
+		c.ServeJSON()
+		return
+	}
+
+	// 删除本地文件
+	err := os.Remove(filePath)
+	if err != nil {
+		logs.Error(lib.FuncName(), err)
+	}
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: "https://bzdcoldverifyoss.baozhida.cn/" + filePath}
+	c.ServeJSON()
+	return
+}
+
+// 导出分类和模版名称(带T_VerifyTemplate_id)
+func (c *VerifyTemplateController) Export_Class_Template() {
+	// 验证登录
+	_, User_is := Account.Verification_Admin(c.Ctx.GetCookie("User_tokey"), c.GetString("User_tokey"))
+	if !User_is {
+		c.Data["json"] = lib.JSONS{Code: 201, Msg: "请重新登录!"}
+		c.ServeJSON()
+		return
+	}
+
+	// 读取所有模板分类
+	classList := VerifyTemplate.Read_VerifyTemplateClass_List()
+
+	// 按分类收集模板,使用map存储:分类名称 -> 模板列表
+	type tpl struct {
+		Name       string
+		TemplateId string
+	}
+	classTemplates := make(map[string][]tpl)
+
+	var collectTpl func(list []VerifyTemplate.VerifyTemplateClass)
+	collectTpl = func(list []VerifyTemplate.VerifyTemplateClass) {
+		for _, cls := range list {
+			// 当前分类下的模板
+			vlist, _ := VerifyTemplate.Read_VerifyTemplate_List(cls.Id, "", 0, 9999)
+			if len(vlist) > 0 {
+				var templates []tpl
+				for _, v := range vlist {
+					templates = append(templates, tpl{
+						Name:       v.T_name,
+						TemplateId: v.T_VerifyTemplate_id,
+					})
+				}
+				classTemplates[cls.T_name] = templates
+			}
+			// 递归子分类
+			if len(cls.Children) > 0 {
+				collectTpl(cls.Children)
+			}
+		}
+	}
+	collectTpl(classList)
+
+	// 创建excel
+	f := excelize.NewFile()
+	sheetName := "Sheet1"
+	idx, _ := f.GetSheetIndex(sheetName)
+
+	f.SetActiveSheet(idx)
+
+	// 找到所有分类中模板数量最多的,作为最大行数
+	maxRows := 1 // 至少有一行表头
+	classNames := make([]string, 0, len(classTemplates))
+	for className, templates := range classTemplates {
+		classNames = append(classNames, className)
+		if len(templates)+1 > maxRows {
+			maxRows = len(templates) + 1
+		}
+	}
+
+	// Excel列名转换函数:1->A, 2->B, ..., 26->Z, 27->AA, 28->AB, ...
+	toExcelColumn := func(n int) string {
+		result := ""
+		for n > 0 {
+			n--
+			result = string(rune('A'+n%26)) + result
+			n /= 26
+		}
+		return result
+	}
+
+	// 第一行写入分类名称
+	for colIndex, className := range classNames {
+		colName := toExcelColumn(colIndex + 1)
+		cell := fmt.Sprintf("%s1", colName)
+		f.SetCellValue(sheetName, cell, className)
+		// 设置列宽
+		f.SetColWidth(sheetName, colName, colName, 40)
+	}
+
+	// 从第二行开始,按列写入每个分类下的模版
+	for row := 2; row <= maxRows; row++ {
+		for colIndex, className := range classNames {
+			templates := classTemplates[className]
+			colName := toExcelColumn(colIndex + 1)
+			cell := fmt.Sprintf("%s%d", colName, row)
+			// 如果该分类还有模版,则写入
+			templateIndex := row - 2
+			if templateIndex < len(templates) {
+				t := templates[templateIndex]
+				f.SetCellValue(sheetName, cell, t.Name+fmt.Sprintf("[%s]", t.TemplateId))
+			}
+		}
+	}
+
+	// 设置居中样式
+	styleCenter, styleErr := f.NewStyle(&excelize.Style{
+		Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center"},
+	})
+	if styleErr != nil {
+		logs.Error("NewStyle Err:", styleErr)
+	} else {
+		lastCol := toExcelColumn(len(classNames))
+		f.SetCellStyle(sheetName, "A1", fmt.Sprintf("%s%d", lastCol, maxRows), styleCenter)
+	}
+
+	// 生成文件
+	lib.Create_Dir("./ofile")
+	timeStr := time.Now().Format("20060102150405")
+	filePath := "ofile/" + timeStr + "_class_template.xlsx"
+	if err := f.SaveAs(filePath); err != nil {
+		logs.Error(lib.FuncName(), err)
+		c.Data["json"] = lib.JSONS{Code: 203, Msg: "文件保存失败!"}
+		c.ServeJSON()
+		return
+	}
+
+	// 上传到七牛
+	if !lib.Pload_qiniu(filePath, filePath) {
+		c.Data["json"] = lib.JSONS{Code: 203, Msg: "oss!"}
+		c.ServeJSON()
+		return
+	}
+
+	// 删除本地文件
+	err := os.Remove(filePath)
+	if err != nil {
+		logs.Error(lib.FuncName(), err)
+	}
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: "https://bzdcoldverifyoss.baozhida.cn/" + filePath}
+	c.ServeJSON()
+	return
+}

+ 61 - 3
lib/lib.go

@@ -4,9 +4,6 @@ import (
 	"ColdVerify_server/conf"
 	"encoding/json"
 	"fmt"
-	"github.com/beego/beego/v2/core/logs"
-	"github.com/nats-io/nats.go"
-	"github.com/signintech/gopdf"
 	"io"
 	"math/rand"
 	"net/http"
@@ -17,6 +14,10 @@ import (
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/beego/beego/v2/core/logs"
+	"github.com/nats-io/nats.go"
+	"github.com/signintech/gopdf"
 )
 
 var Nats *nats.Conn
@@ -556,3 +557,60 @@ func GetSignaturePdf(pdfURL string) (string, error) {
 	}
 	return result.SignaturePdfURL, nil
 }
+
+// ProcessImage 处理图片,传入图片链接和是否加盖印章,返回处理后的图片链接
+func ProcessImage(imageURL string, isSeal bool) (string, error) {
+	url := conf.PdfProcessingHost + "/process_image"
+	method := "POST"
+
+	// 构建请求体
+	requestBody := struct {
+		ImageURL string `json:"image_url"`
+		IsSeal   bool   `json:"is_seal"`
+	}{
+		ImageURL: imageURL,
+		IsSeal:   isSeal,
+	}
+
+	jsonData, err := json.Marshal(requestBody)
+	if err != nil {
+		return "", fmt.Errorf("构建请求参数失败: %v", err)
+	}
+
+	payload := strings.NewReader(string(jsonData))
+
+	client := &http.Client{}
+	req, err := http.NewRequest(method, url, payload)
+	if err != nil {
+		return "", fmt.Errorf("创建请求失败: %v", err)
+	}
+	req.Header.Add("Content-Type", "application/json")
+
+	res, err := client.Do(req)
+	if err != nil {
+		return "", fmt.Errorf("请求失败: %v", err)
+	}
+	defer res.Body.Close()
+
+	body, err := io.ReadAll(res.Body)
+	if err != nil {
+		return "", fmt.Errorf("读取响应失败: %v", err)
+	}
+
+	// 解析JSON响应
+	var result struct {
+		Success  bool   `json:"success"`
+		Message  string `json:"message"`
+		ImageURL string `json:"image_url"`
+	}
+
+	if err = json.Unmarshal(body, &result); err != nil {
+		return "", fmt.Errorf("解析响应失败: %v", err)
+	}
+
+	if !result.Success {
+		return "", fmt.Errorf("图片处理失败: %s", result.Message)
+	}
+
+	return result.ImageURL, nil
+}

+ 114 - 1
models/Account/Admin.go

@@ -3,9 +3,15 @@ package Account
 import (
 	"ColdVerify_server/lib"
 	"ColdVerify_server/logs"
+	"fmt"
+	"strings"
+	"sync"
+	"time"
+
 	"github.com/beego/beego/v2/adapter/orm"
 	orm2 "github.com/beego/beego/v2/client/orm"
-	"time"
+	_ "github.com/go-sql-driver/mysql"
+	uuid "github.com/satori/go.uuid"
 )
 
 type Admin struct {
@@ -44,6 +50,103 @@ func init() {
 	orm.RegisterModel(new(Admin))
 
 }
+
+var adminCache = struct {
+	sync.RWMutex
+	data map[string]Admin
+}{}
+
+const (
+	DefaultAdminPassword = "12345678"
+)
+
+func GetAdminFromCache(name string) (Admin, bool, error) {
+	name = strings.TrimSpace(name)
+	if len(name) == 0 {
+		return Admin{}, false, nil
+	}
+
+	adminCache.RLock()
+	cached := adminCache.data
+	adminCache.RUnlock()
+
+	if cached == nil {
+		if err := RefreshAdminCache(); err != nil {
+			return Admin{}, false, err
+		}
+		adminCache.RLock()
+		cached = adminCache.data
+		adminCache.RUnlock()
+	}
+
+	admin, ok := cached[name]
+	return admin, ok, nil
+}
+
+func RefreshAdminCache() error {
+	admins, err := Read_AllActiveAdmins()
+	if err != nil {
+		return err
+	}
+	cache := make(map[string]Admin, len(admins))
+	for _, a := range admins {
+		cache[a.T_name] = a
+	}
+	adminCache.Lock()
+	adminCache.data = cache
+	adminCache.Unlock()
+	return nil
+}
+
+func UpsertAdminCache(admin Admin) {
+	if len(admin.T_name) == 0 {
+		return
+	}
+	adminCache.Lock()
+	if adminCache.data == nil {
+		adminCache.data = make(map[string]Admin)
+	}
+	adminCache.data[admin.T_name] = admin
+	adminCache.Unlock()
+}
+
+func EnsureAdminByName(name, distributorID string) (Admin, error) {
+	name = strings.TrimSpace(name)
+	if len(name) == 0 {
+		return Admin{}, fmt.Errorf("管理员名称为空")
+	}
+
+	if admin, ok, err := GetAdminFromCache(name); err != nil {
+		return Admin{}, err
+	} else if ok {
+		return admin, nil
+	}
+
+	newUUID := uuid.NewV4().String()
+	username := fmt.Sprintf("auto_%s", strings.ReplaceAll(newUUID, "-", "")[:8])
+
+	newAdmin := Admin{
+		T_uuid:           newUUID,
+		T_power:          6,
+		T_name:           name,
+		T_user:           username,
+		T_pass:           DefaultAdminPassword,
+		T_wxname:         name,
+		T_State:          1,
+		T_Distributor_id: distributorID,
+	}
+
+	if _, err := Add_Admin(newAdmin); err != nil {
+		return Admin{}, err
+	}
+
+	if err, persisted := Read_Admin_ByT_uuid(newUUID); err != nil {
+		return Admin{}, err
+	} else {
+		UpsertAdminCache(persisted)
+		return persisted, nil
+	}
+}
 func AdminToAdmin_R(T Admin, powerMap map[int]string) (T_r Admin_R) {
 	T_r.T_uuid = T.T_uuid
 	T_r.T_power = T.T_power
@@ -173,6 +276,16 @@ func Read_Admin_List_ALL() (maps []Admin) {
 	return maps
 }
 
+func Read_AllActiveAdmins() ([]Admin, error) {
+	o := orm.NewOrm()
+	var admins []Admin
+	_, err := o.QueryTable(new(Admin)).Filter("T_State", 1).All(&admins)
+	if err != nil {
+		return nil, err
+	}
+	return admins, nil
+}
+
 // 获取全部列表-包括软删除数据
 func Read_Admin_List_ALL_1() (maps []Admin) {
 

+ 71 - 0
models/Account/User.go

@@ -7,6 +7,7 @@ import (
 	"log"
 	"strconv"
 	"strings"
+	"sync"
 	"time"
 
 	"github.com/beego/beego/v2/adapter/orm"
@@ -55,6 +56,65 @@ func init() {
 
 }
 
+var companyCache = struct {
+	sync.RWMutex
+	data map[string]User
+}{}
+
+const (
+	DefaultCompanyPasswordHash  = "25d55ad283aa400af464c76d713c07ad"
+	DefaultCompanyPasswordPlain = "12345678"
+)
+
+func GetCompanyFromCache(name string) (User, bool, error) {
+	if len(strings.TrimSpace(name)) == 0 {
+		return User{}, false, nil
+	}
+
+	companyCache.RLock()
+	cached := companyCache.data
+	companyCache.RUnlock()
+
+	if cached == nil {
+		if err := RefreshCompanyCache(); err != nil {
+			return User{}, false, err
+		}
+		companyCache.RLock()
+		cached = companyCache.data
+		companyCache.RUnlock()
+	}
+
+	user, ok := cached[name]
+	return user, ok, nil
+}
+
+func RefreshCompanyCache() error {
+	users, err := Read_AllActiveUsers()
+	if err != nil {
+		return err
+	}
+	cache := make(map[string]User, len(users))
+	for _, u := range users {
+		cache[u.T_name] = u
+	}
+	companyCache.Lock()
+	companyCache.data = cache
+	companyCache.Unlock()
+	return nil
+}
+
+func UpsertCompanyCache(user User) {
+	if len(user.T_name) == 0 {
+		return
+	}
+	companyCache.Lock()
+	if companyCache.data == nil {
+		companyCache.data = make(map[string]User)
+	}
+	companyCache.data[user.T_name] = user
+	companyCache.Unlock()
+}
+
 // -------------------------------------------------------------
 func UserToUser_R(T User, distributorMap map[string]string) (T_r User_R) {
 	T_r.Id = T.Id
@@ -276,6 +336,17 @@ func Read_User_List_ALL() (maps []User) {
 	return maps
 }
 
+// 获取全部启用公司列表
+func Read_AllActiveUsers() ([]User, error) {
+	o := orm.NewOrm()
+	var users []User
+	_, err := o.QueryTable(new(User)).Filter("T_State", 1).All(&users)
+	if err != nil {
+		return nil, err
+	}
+	return users, nil
+}
+
 // 获取全部列表-包括软删除数据
 func Read_User_List_ALL_1() (maps []User) {
 

+ 10 - 3
models/Account/UserSignature.go

@@ -3,11 +3,12 @@ package Account
 import (
 	"ColdVerify_server/lib"
 	"ColdVerify_server/logs"
+	"log"
+	"time"
+
 	"github.com/beego/beego/v2/adapter/orm"
 	orm2 "github.com/beego/beego/v2/client/orm"
 	_ "github.com/go-sql-driver/mysql"
-	"log"
-	"time"
 )
 
 type UserSignature struct {
@@ -15,6 +16,7 @@ type UserSignature struct {
 	T_Distributor_id string    `orm:"size(256);null"`                                        // 分销商id
 	T_uuid           string    `orm:"size(256);null"`                                        // 公司uuid
 	T_name           string    `orm:"size(256);null"`                                        // 姓名
+	T_type           int       `orm:"size(256);default(1)"`                                  // 1-签名 2-公章
 	T_signature      string    `orm:"size(256);null"`                                        // 签名
 	T_State          int       `orm:"size(200);default(1)"`                                  // 0删除  1正常
 	CreateTime       time.Time `orm:"column(create_time);type(timestamp);null;auto_now_add"` //auto_now 每次 model 保存时都会对时间自动更新
@@ -27,6 +29,7 @@ type UserSignature_R struct {
 	T_uuid             string
 	T_name             string
 	T_signature        string
+	T_type             int
 }
 
 func (t *UserSignature) TableName() string {
@@ -51,6 +54,7 @@ func UserSignatureToUserSignature_R(T UserSignature, distributorMap map[string]s
 	T_r.T_Distributor_name = T_Distributor_name
 	T_r.T_name = T.T_name
 	T_r.T_signature = T.T_signature
+	T_r.T_type = T.T_type
 
 	return T_r
 }
@@ -117,7 +121,7 @@ func Update_UserSignature(m UserSignature, cols ...string) bool {
 }
 
 // 获取列表
-func Read_UserSignature_List(T_uuid string, T_name string, page int, page_z int, distributorMap map[string]string) ([]UserSignature_R, int64) {
+func Read_UserSignature_List(T_uuid string, T_name string, T_type, page int, page_z int, distributorMap map[string]string) ([]UserSignature_R, int64) {
 	o := orm.NewOrm()
 
 	// 也可以直接使用 Model 结构体作为表名
@@ -131,6 +135,9 @@ func Read_UserSignature_List(T_uuid string, T_name string, page int, page_z int,
 	}
 	cond := orm.NewCondition()
 	cond1 := cond.And("T_State", 1).And("T_uuid", T_uuid).AndCond(cond.Or("T_name__icontains", T_name))
+	if T_type > 0 {
+		cond1 = cond1.And("T_type", T_type)
+	}
 	qs.Limit(page_z, offset).SetCond((*orm2.Condition)(cond1)).OrderBy("-Id").All(&r)
 	cnt, _ := qs.SetCond((*orm2.Condition)(cond1)).Count()
 

+ 109 - 0
models/FileManagement/FileManagement.go

@@ -0,0 +1,109 @@
+package FileManagement
+
+import (
+	"ColdVerify_server/lib"
+	"ColdVerify_server/logs"
+	"time"
+
+	"github.com/beego/beego/v2/adapter/orm"
+)
+
+type FileManagement struct {
+	Id         int       `orm:"column(ID);size(11);auto;pk"`
+	FileName   string    `orm:"column(file_name);size(256);null"`
+	FilePath   string    `orm:"column(file_path);size(512);null"`
+	CreateTime time.Time `orm:"column(create_time);type(timestamp);null;auto_now_add"`
+	UpdateTime time.Time `orm:"column(update_time);type(timestamp);null;auto_now"`
+}
+
+type FileManagementR struct {
+	Id         int
+	FileName   string
+	FilePath   string
+	CreateTime string
+	UpdateTime string
+}
+
+func (t *FileManagement) TableName() string {
+	return "file_management"
+}
+
+func init() {
+	orm.RegisterModel(new(FileManagement))
+}
+
+func ToFileManagementR(f FileManagement) FileManagementR {
+	return FileManagementR{
+		Id:         f.Id,
+		FileName:   f.FileName,
+		FilePath:   f.FilePath,
+		CreateTime: f.CreateTime.Format("2006-01-02 15:04:05"),
+		UpdateTime: f.UpdateTime.Format("2006-01-02 15:04:05"),
+	}
+}
+
+func AddFileManagement(f FileManagement) (int64, bool) {
+	o := orm.NewOrm()
+	id, err := o.Insert(&f)
+	if err != nil {
+		logs.Error(lib.FuncName(), err)
+		return 0, false
+	}
+	return id, true
+}
+
+func UpdateFileManagement(f FileManagement, cols ...string) bool {
+	o := orm.NewOrm()
+	if num, err := o.Update(&f, cols...); err == nil {
+		logs.Println("Number of records updated in database:", num)
+		return true
+	}
+	return false
+}
+
+func DeleteFileManagement(f FileManagement) bool {
+	o := orm.NewOrm()
+	if num, err := o.Delete(&f); err == nil {
+		logs.Println("Number of records deleted in database:", num)
+		return true
+	}
+	return false
+}
+
+func ReadFileManagementById(id int) (FileManagement, bool) {
+	o := orm.NewOrm()
+	f := FileManagement{Id: id}
+	if err := o.Read(&f); err != nil {
+		logs.Error(lib.FuncName(), err)
+		return FileManagement{}, false
+	}
+	return f, true
+}
+
+func ReadFileManagementList(fileName string, page, pageSize int) ([]FileManagementR, int64) {
+	o := orm.NewOrm()
+	var list []FileManagement
+	qs := o.QueryTable(new(FileManagement))
+
+	if page <= 0 {
+		page = 1
+	}
+	if pageSize <= 0 {
+		pageSize = 10
+	}
+	offset := (page - 1) * pageSize
+
+	if len(fileName) > 0 {
+		qs = qs.Filter("file_name__icontains", fileName)
+	}
+
+	qs.Limit(pageSize, offset).OrderBy("-Id").All(&list)
+	cnt, _ := qs.Count()
+
+	result := make([]FileManagementR, 0, len(list))
+	for _, f := range list {
+		result = append(result, ToFileManagementR(f))
+	}
+
+	return result, cnt
+}

+ 103 - 28
models/Task/Task.go

@@ -149,6 +149,18 @@ var (
 	}
 
 	TaskCollectionTimeLimit float64 = 7 * 24 * 60
+
+	DeviceTypeMap = map[string]string{
+		"箱":  "X",
+		"柜":  "G",
+		"车":  "C",
+		"库":  "K",
+		"系统": "XT",
+		"位置": "WZ",
+		"培训": "PX",
+		"巡检": "XJ",
+		"其他": "QT",
+	}
 )
 
 type AuditRecord struct {
@@ -165,6 +177,7 @@ type AuditRecord struct {
 // 模版
 type Task struct {
 	Id                  int    `orm:"column(ID);size(11);auto;pk"`
+	T_uid               string `orm:"size(256);null"`       // 文件导入任务的唯一标识
 	T_Distributor_id    string `orm:"size(256);null"`       // 分销商id
 	T_class             int    `orm:"size(200);default(0)"` // 分类id
 	T_InfoCollection_id string `orm:"size(256);null"`       // 信息采集ID
@@ -197,20 +210,22 @@ type Task struct {
 	T_BindDeviceDataStartTime   string `orm:"size(256);null"` // 绑定设备数据开始时间
 	T_BindDeviceDataEndTime     string `orm:"size(256);null"` // 绑定设备数据结束时间
 
-	T_doc1           string `orm:"type(text);null"` // 封面
-	T_pdf1           string `orm:"type(text);null"` // 验证方案
-	T_pdf1_watermark string `orm:"type(text);null"` // 验证方案 带水印
-	T_pdf1_signature string `orm:"type(text);null"` // 验证方案 带公章
-	T_doc2           string `orm:"type(text);null"` // 报告
-	T_pdf2           string `orm:"type(text);null"` // 验证报告
-	T_pdf2_watermark string `orm:"type(text);null"` // 验证报告 带水印
-	T_pdf2_signature string `orm:"type(text);null"` // 验证报告 带公章
-	T_doc3           string `orm:"type(text);null"` // 证书
-	T_pdf3           string `orm:"type(text);null"` // 证书
-	T_pdf4           string `orm:"type(text);null"` // 验证标识
-	T_pdf5           string `orm:"type(text);null"` // 检测报告
-	T_pdf5_watermark string `orm:"type(text);null"` // 检测报告 带水印
-	T_pdf6           string `orm:"type(text);null"` // 原始记录
+	T_doc1                string `orm:"type(text);null"` // 封面
+	T_pdf1                string `orm:"type(text);null"` // 验证方案
+	T_pdf1_watermark      string `orm:"type(text);null"` // 验证方案 带水印
+	T_pdf1_signature      string `orm:"type(text);null"` // 验证方案 带公章
+	T_pdf1_elec_signature string `orm:"type(text);null"` // 验证方案 带电子签名及公章
+	T_doc2                string `orm:"type(text);null"` // 报告
+	T_pdf2                string `orm:"type(text);null"` // 验证报告
+	T_pdf2_watermark      string `orm:"type(text);null"` // 验证报告 带水印
+	T_pdf2_signature      string `orm:"type(text);null"` // 验证报告 带公章
+	T_pdf2_elec_signature string `orm:"type(text);null"` // 验证报告 带电子签名及公章
+	T_doc3                string `orm:"type(text);null"` // 证书
+	T_pdf3                string `orm:"type(text);null"` // 证书
+	T_pdf4                string `orm:"type(text);null"` // 验证标识
+	T_pdf5                string `orm:"type(text);null"` // 检测报告
+	T_pdf5_watermark      string `orm:"type(text);null"` // 检测报告 带水印
+	T_pdf6                string `orm:"type(text);null"` // 原始记录
 
 	T_Show  int `orm:"size(2);default(1)"`   // 0 隐藏   1 公开
 	T_Visit int `orm:"size(200);default(0)"` // 浏览量
@@ -339,8 +354,10 @@ type Task_ struct {
 	T_BindDeviceDataTime         [2]string // 绑定设备数据开始-结束时间
 	T_doc1                       string    // 封面
 	T_pdf1                       string    // 验证方案
+	T_pdf1_signature             string    // 验证方案 带公章
 	T_doc2                       string    // 报告
 	T_pdf2                       string    // 报告
+	T_pdf2_signature             string    // 报告 带公章
 	T_doc3                       string    // 证书
 	T_pdf3                       string    // 证书
 	T_pdf4                       string    // 验证标识
@@ -513,6 +530,35 @@ func init() {
 
 // -------------------------------------------------------------
 
+// selectPdf 根据条件选择PDF文件
+// 返回选中的PDF和signature PDF
+func selectPdf(watermark, original, signature, elecSignature string, isReturnedMoney bool, hasInfoCollection bool) (selectedPdf, selectedSignature string) {
+	// 默认使用watermark版本
+	selectedPdf = watermark
+
+	// 如果满足以下任一条件,使用原始版本:退款状态、无InfoCollection、watermark为空
+	if isReturnedMoney || !hasInfoCollection || len(watermark) == 0 {
+		selectedPdf = original
+
+		// 如果是退款状态,优先选择签名版本(电子签名优先于普通签名)
+		if isReturnedMoney {
+			if len(elecSignature) > 0 {
+				selectedPdf = elecSignature
+			} else if len(signature) > 0 {
+				selectedPdf = signature
+			}
+		}
+	}
+
+	// 设置signature,如果为空则使用原始版本
+	selectedSignature = signature
+	if len(selectedSignature) == 0 {
+		selectedSignature = original
+	}
+
+	return selectedPdf, selectedSignature
+}
+
 func TaskToTask_(T Task, userMap, adminMap map[string]string) (T_ Task_) {
 	T_.Id = T.Id
 	T_.T_Distributor_id = T.T_Distributor_id
@@ -559,20 +605,27 @@ func TaskToTask_(T Task, userMap, adminMap map[string]string) (T_ Task_) {
 	T_.T_doc2 = T.T_doc2
 	T_.T_doc3 = T.T_doc3
 
-	T_.T_pdf1 = T.T_pdf1_watermark
-	if T_.InfoCollection.T_status == InfoCollection.InfoCollectionStatusReturnedMoney || len(T.T_InfoCollection_id) == 0 || len(T.T_pdf1_watermark) == 0 {
-		T_.T_pdf1 = T.T_pdf1
-		if len(T.T_pdf1_signature) > 0 {
-			T_.T_pdf1 = T.T_pdf1_signature
-		}
-	}
-	T_.T_pdf2 = T.T_pdf2_watermark
-	if T_.InfoCollection.T_status == InfoCollection.InfoCollectionStatusReturnedMoney || len(T.T_InfoCollection_id) == 0 || len(T.T_pdf2_watermark) == 0 {
-		T_.T_pdf2 = T.T_pdf2
-		if len(T.T_pdf1_signature) > 0 {
-			T_.T_pdf2 = T.T_pdf2_signature
-		}
-	}
+	// 处理PDF1和PDF2的选择逻辑
+	isReturnedMoney := T_.InfoCollection.T_status == InfoCollection.InfoCollectionStatusReturnedMoney
+	hasInfoCollection := len(T.T_InfoCollection_id) > 0
+
+	T_.T_pdf1, T_.T_pdf1_signature = selectPdf(
+		T.T_pdf1_watermark,
+		T.T_pdf1,
+		T.T_pdf1_signature,
+		T.T_pdf1_elec_signature,
+		isReturnedMoney,
+		hasInfoCollection,
+	)
+
+	T_.T_pdf2, T_.T_pdf2_signature = selectPdf(
+		T.T_pdf2_watermark,
+		T.T_pdf2,
+		T.T_pdf2_signature,
+		T.T_pdf2_elec_signature,
+		isReturnedMoney,
+		hasInfoCollection,
+	)
 	T_.T_pdf3 = T.T_pdf3
 	T_.T_pdf4 = T.T_pdf4
 	T_.T_pdf5 = T.T_pdf5_watermark
@@ -816,6 +869,28 @@ func Read_Task(T_task_id string) (r Task, is bool) {
 	return r, true
 }
 
+// 获取 By T_uid
+func Read_Task_ByUid(T_uid string) (r Task, is bool) {
+	o := orm.NewOrm()
+	qs := o.QueryTable(new(Task))
+	err := qs.Filter("T_uid", T_uid).Filter("T_State", 1).One(&r)
+	if err != nil {
+		return r, false
+	}
+	return r, true
+}
+
+// 获取 By T_class
+func Read_Task_ByT_class(T_class int) (r Task, is bool) {
+	o := orm.NewOrm()
+	qs := o.QueryTable(new(Task))
+	err := qs.Filter("T_class", T_class).Filter("T_State", 1).One(&r)
+	if err != nil {
+		return r, false
+	}
+	return r, true
+}
+
 // 添加
 func Add_Task(r Task) (string, bool) {
 	o := orm.NewOrm()

+ 1 - 0
routers/Device.go

@@ -19,6 +19,7 @@ func init() {
 	beego.Router("/DeviceClassList/List", &controllers.DeviceClassController{}, "*:List_List")                                        // 设备分类列表
 	beego.Router("/DeviceClassList/Maximum", &controllers.DeviceClassController{}, "*:List_Maximum")                                  // 设备分类列表
 	beego.Router("/DeviceClassList/Add", &controllers.DeviceClassController{}, "*:List_Add")                                          // 添加设备分类列表
+	beego.Router("/DeviceClassList/Add_batch", &controllers.DeviceClassController{}, "*:List_Add_batch")                              // 添加设备分类列表
 	beego.Router("/DeviceClassList/Up", &controllers.DeviceClassController{}, "*:List_Up")                                            // 编辑设备分类列表
 	beego.Router("/DeviceClassList/Up_terminal", &controllers.DeviceClassController{}, "*:List_Up_terminal")                          // 编辑设备分类列表
 	beego.Router("/DeviceClassList/Del", &controllers.DeviceClassController{}, "*:List_Del")                                          // 删除设备分类列表

+ 15 - 0
routers/FileManagement.go

@@ -0,0 +1,15 @@
+package routers
+
+import (
+	"ColdVerify_server/controllers"
+
+	beego "github.com/beego/beego/v2/server/web"
+)
+
+func init() {
+	beego.Router("/FileManagement/List", &controllers.FileManagementController{}, "*:List")
+	beego.Router("/FileManagement/Get", &controllers.FileManagementController{}, "*:Get")
+	beego.Router("/FileManagement/Add", &controllers.FileManagementController{}, "*:Add")
+	beego.Router("/FileManagement/Up", &controllers.FileManagementController{}, "*:Up")
+	beego.Router("/FileManagement/Del", &controllers.FileManagementController{}, "*:Del")
+}

+ 27 - 24
routers/Task.go

@@ -2,6 +2,7 @@ package routers
 
 import (
 	"ColdVerify_server/controllers"
+
 	beego "github.com/beego/beego/v2/server/web"
 )
 
@@ -9,30 +10,32 @@ func init() {
 
 	beego.Router("/UpFileToken", &controllers.UpFileController{}, "*:ConfigUpFileToken") // 上传文件
 
-	beego.Router("/Task/List", &controllers.TaskController{}, "*:List")                                     // 任务列表
-	beego.Router("/Task/Get", &controllers.TaskController{}, "*:Get")                                       // 获取任务
-	beego.Router("/Task/Add", &controllers.TaskController{}, "*:Add")                                       // 添加任务
-	beego.Router("/Task/Up", &controllers.TaskController{}, "*:Up")                                         // 编辑任务
-	beego.Router("/Task/Del", &controllers.TaskController{}, "*:Del")                                       // 删除任务
-	beego.Router("/Task/Copy", &controllers.TaskController{}, "*:Copy")                                     // 复制任务
-	beego.Router("/Task/UpCollectionState", &controllers.TaskController{}, "*:UpCollectionState")           // 修改任务状态
-	beego.Router("/Task/UpDeliveryState", &controllers.TaskController{}, "*:UpDeliveryState")               // 修改交付审核状态
-	beego.Router("/Task/UpExaminingReportState", &controllers.TaskController{}, "*:UpExaminingReportState") // 修改交付审核状态
-	beego.Router("/Task/UpOriginalRecordState", &controllers.TaskController{}, "*:UpOriginalRecordState")   // 修改交付审核状态
-	beego.Router("/Task/AddData_Tool", &controllers.TaskController{}, "*:AddData_Tool")                     // 1.0 添加任务数据
-	beego.Router("/Task/ReceiptInfoCollection", &controllers.TaskController{}, "*:ReceiptInfoCollection")   // 接收信息采集
-	beego.Router("/Task/SyncInfoCollection", &controllers.TaskController{}, "*:SyncInfoCollection")         // 同步信息采集
-	beego.Router("/Task/UpSchemeState", &controllers.TaskController{}, "*:UpSchemeState")                   // 修改验证方案状态
-	beego.Router("/Task/EnterArea", &controllers.TaskController{}, "*:EnterArea")                           // 进场
-	beego.Router("/Task/StartVerify", &controllers.TaskController{}, "*:StartVerify")                       // 开始验证-实施开始时间
-	beego.Router("/Task/UpReportingState", &controllers.TaskController{}, "*:UpReportingState")             // 修改验证报告状态
-	beego.Router("/Task/AuditRecordList", &controllers.TaskController{}, "*:AuditRecordList")               // 审核记录
-	beego.Router("/Task/GetTaskUserList", &controllers.TaskController{}, "*:GetTaskUserList")               // 获取任务负责人列表
-	beego.Router("/Task/Stat", &controllers.TaskController{}, "*:Stat")                                     // 报告统计
-	beego.Router("/Task/Stat_Excel", &controllers.TaskController{}, "*:Stat_Excel")                         // 报告统计-excel
-	beego.Router("/Task/StatisticalRanking", &controllers.TaskController{}, "*:StatisticalRanking")         // 报告统计-excel
-	beego.Router("/Task/GenT_report_number", &controllers.TaskController{}, "*:GenT_report_number")         // 生成报告编号
-	beego.Router("/Task/SyncPDFWatermark", &controllers.TaskController{}, "*:SyncPDFWatermark")             // 生成报告编号
+	beego.Router("/Task/List", &controllers.TaskController{}, "*:List")                                             // 任务列表
+	beego.Router("/Task/Get", &controllers.TaskController{}, "*:Get")                                               // 获取任务
+	beego.Router("/Task/Add", &controllers.TaskController{}, "*:Add")                                               // 添加任务
+	beego.Router("/Task/Up", &controllers.TaskController{}, "*:Up")                                                 // 编辑任务
+	beego.Router("/Task/Del", &controllers.TaskController{}, "*:Del")                                               // 删除任务
+	beego.Router("/Task/Copy", &controllers.TaskController{}, "*:Copy")                                             // 复制任务
+	beego.Router("/Task/UpCollectionState", &controllers.TaskController{}, "*:UpCollectionState")                   // 修改任务状态
+	beego.Router("/Task/UpDeliveryState", &controllers.TaskController{}, "*:UpDeliveryState")                       // 修改交付审核状态
+	beego.Router("/Task/UpExaminingReportState", &controllers.TaskController{}, "*:UpExaminingReportState")         // 修改交付审核状态
+	beego.Router("/Task/UpOriginalRecordState", &controllers.TaskController{}, "*:UpOriginalRecordState")           // 修改交付审核状态
+	beego.Router("/Task/AddData_Tool", &controllers.TaskController{}, "*:AddData_Tool")                             // 1.0 添加任务数据
+	beego.Router("/Task/ReceiptInfoCollection", &controllers.TaskController{}, "*:ReceiptInfoCollection")           // 接收信息采集
+	beego.Router("/Task/SyncInfoCollection", &controllers.TaskController{}, "*:SyncInfoCollection")                 // 同步信息采集
+	beego.Router("/Task/UpSchemeState", &controllers.TaskController{}, "*:UpSchemeState")                           // 修改验证方案状态
+	beego.Router("/Task/EnterArea", &controllers.TaskController{}, "*:EnterArea")                                   // 进场
+	beego.Router("/Task/StartVerify", &controllers.TaskController{}, "*:StartVerify")                               // 开始验证-实施开始时间
+	beego.Router("/Task/UpReportingState", &controllers.TaskController{}, "*:UpReportingState")                     // 修改验证报告状态
+	beego.Router("/Task/AuditRecordList", &controllers.TaskController{}, "*:AuditRecordList")                       // 审核记录
+	beego.Router("/Task/GetTaskUserList", &controllers.TaskController{}, "*:GetTaskUserList")                       // 获取任务负责人列表
+	beego.Router("/Task/Stat", &controllers.TaskController{}, "*:Stat")                                             // 报告统计
+	beego.Router("/Task/Stat_Excel", &controllers.TaskController{}, "*:Stat_Excel")                                 // 报告统计-excel
+	beego.Router("/Task/StatisticalRanking", &controllers.TaskController{}, "*:StatisticalRanking")                 // 报告统计-excel
+	beego.Router("/Task/GenT_report_number", &controllers.TaskController{}, "*:GenT_report_number")                 // 生成报告编号
+	beego.Router("/Task/SyncPDFWatermark", &controllers.TaskController{}, "*:SyncPDFWatermark")                     // 生成报告编号
+	beego.Router("/Task/SaveElectronicSignaturePDF", &controllers.TaskController{}, "*:SaveElectronicSignaturePDF") // 生成报告编号
+	beego.Router("/Task/Import_Tasks", &controllers.TaskController{}, "*:Import_Tasks")                             // 导入任务
 
 	// 日志
 	beego.Router("/TaskLogs/List", &controllers.TaskController{}, "*:Logs_List")

+ 7 - 4
routers/VerifyTemplate.go

@@ -2,6 +2,7 @@ package routers
 
 import (
 	"ColdVerify_server/controllers"
+
 	beego "github.com/beego/beego/v2/server/web"
 )
 
@@ -28,10 +29,12 @@ func init() {
 	beego.Router("/VerifyTemplate/Get", &controllers.VerifyTemplateController{}, "*:Get")   // 模版修改
 	beego.Router("/VerifyTemplate/Copy", &controllers.VerifyTemplateController{}, "*:Copy") // 模版复制
 
-	beego.Router("/VerifyTemplate/Map_List", &controllers.VerifyTemplateController{}, "*:Map_List") // 标签列表
-	beego.Router("/VerifyTemplate/Map_Add", &controllers.VerifyTemplateController{}, "*:Map_Add")   // 标签添加
-	beego.Router("/VerifyTemplate/Map_Up", &controllers.VerifyTemplateController{}, "*:Map_Up")     // 标签修改
-	beego.Router("/VerifyTemplate/Map_Del", &controllers.VerifyTemplateController{}, "*:Map_Del")   // 标签删除
+	beego.Router("/VerifyTemplate/Map_List", &controllers.VerifyTemplateController{}, "*:Map_List")                           // 标签列表
+	beego.Router("/VerifyTemplate/Map_Add", &controllers.VerifyTemplateController{}, "*:Map_Add")                             // 标签添加
+	beego.Router("/VerifyTemplate/Map_Up", &controllers.VerifyTemplateController{}, "*:Map_Up")                               // 标签修改
+	beego.Router("/VerifyTemplate/Map_Del", &controllers.VerifyTemplateController{}, "*:Map_Del")                             // 标签删除
+	beego.Router("/VerifyTemplate/Export_Labels", &controllers.VerifyTemplateController{}, "*:Export_Labels")                 // 导出标签名称
+	beego.Router("/VerifyTemplate/Export_Class_Template", &controllers.VerifyTemplateController{}, "*:Export_Class_Template") // 导出分类和模版名称
 
 	beego.Router("/VerifyTemplateMapData/List", &controllers.VerifyTemplateController{}, "*:Map_Data_List")                           // 标签数据列表
 	beego.Router("/VerifyTemplateMapData/Pu", &controllers.VerifyTemplateController{}, "*:Map_Data_Pu")                               // 添加标签数据