GTong 8 сар өмнө
parent
commit
126eedf15b

+ 3 - 1
TODO.text

@@ -20,4 +20,6 @@
 
    发送license到邮箱,那个license名称是没有标识的吧? 如果一个项目生成N个license,后续项目上怎么去对应license呢?
 
-   提供下载license按钮
+   提供下载license按钮
+
+   已经分发过的用户,再oa上的申请单修改后,再次分发给用户

+ 4 - 4
config.toml

@@ -1,12 +1,12 @@
 [database]
-oadb_ip = "10.28.20.150"
-oadb_port = "5199"
+oadb_ip = "10.28.20.233"
+oadb_port = "5138"
 oadb_db = "OAdb"
 oadb_user = "SYSDBA"
 oadb_password = "SYSDBA"
 
-licdb_ip = "10.28.20.150"
-licdb_port = "5199"
+licdb_ip = "10.28.20.233"
+licdb_port = "5138"
 licdb_db = "license"
 licdb_user = "SYSDBA"
 licdb_password = "SYSDBA"

+ 6 - 4
internal/api/license_api.go

@@ -1,7 +1,8 @@
 package api
 
 type AllLicenseInfoResponse struct {
-	UniqueID               string `json:"UniqueID"`                 // 每行数据的唯一值ID,理论上有oa的REQUESTID就可以了
+	UniqueID               string `json:"UniqueID"` // 每行数据的唯一值ID,理论上有oa的REQUESTID就可以了
+	OAId                   int64  `json:"oa_id"`
 	OARequestID            int64  `json:"oa_request_id"`            // oa里的申请单请求ID,假设唯一标识
 	OARequestName          string `json:"Creator"`                  // 请求名称
 	OARequestNameNew       string `json:"oa_request_name_new"`      // 新请求名称
@@ -58,7 +59,8 @@ type DistributeLicenseRequest struct {
 }
 
 type GetDistributeHistoryRequest struct {
-	UniqueID string `json:"uniqueID"`
+	OaRequestId int    `json:"oa_request_id"`
+	UniqueID    string `json:"uniqueID"`
 }
 
 type UpdateLicenseInfoRequest struct {
@@ -67,9 +69,9 @@ type UpdateLicenseInfoRequest struct {
 	ApplicationDate   string `json:"ApplicationDate" `
 	AssociatedProject string `json:"AssociatedProject" `
 	SalesPerson       string `json:"SalesPerson" `
-	SalesEmail        string `json:"SalesEmail" binding:"required,email"`
+	SalesEmail        string `json:"SalesEmail"`
 	SupportPerson     string `json:"SupportPerson" `
-	SupportEmail      string `json:"SupportEmail" binding:"required,email"`
+	SupportEmail      string `json:"SupportEmail" `
 	TotalNodes        int64  `json:"TotalNodes" `
 	Company           string `json:"Company" `
 	ProductName       string `json:"ProductName" `

+ 54 - 334
internal/controllers/OALicenseInfo_controllers.go

@@ -1,7 +1,6 @@
 package controllers
 
 import (
-	"database/sql"
 	"fmt"
 	"net/http"
 	"sort"
@@ -12,7 +11,7 @@ import (
 	middlewares "xugu_license/internal/middleware"
 	"xugu_license/internal/models"
 	"xugu_license/internal/module/capture"
-	"xugu_license/internal/module/email"
+	"xugu_license/internal/services"
 	"xugu_license/internal/utils"
 
 	"github.com/gin-gonic/gin"
@@ -167,6 +166,7 @@ func GetAllLicenseInfoController(c *gin.Context) {
 
 			ApiLicenseInfo := api.AllLicenseInfoResponse{
 				UniqueID:               utils.ToString(data.LicInfo.UniqueID),
+				OAId:                   utils.ToInt64(data.LicInfo.OAId),
 				OARequestID:            utils.ToInt64(data.LicInfo.OARequestID),
 				OARequestName:          utils.ToString(data.LicInfo.OARequestName),
 				OARequestNameNew:       utils.ToString(data.LicInfo.OARequestNameNew),
@@ -316,13 +316,27 @@ func GetAllLicenseInfoController(c *gin.Context) {
 		datas = append(datas, apiLicenseInfoTemp)
 	}
 	// 对 datas 的第一维进行排序
+	// sort.Slice(datas, func(i, j int) bool {
+	// 	// 假设你需要根据每个数组第一个元素的 OACreationDate 和 OACreationTime 来排序
+	// 	if datas[i][0].OACreationDate == datas[j][0].OACreationDate {
+
+	// 		return datas[i][0].OACreationTime > datas[j][0].OACreationTime
+	// 	}
+	// 	return datas[i][0].OACreationDate > datas[j][0].OACreationDate
+	// })
+	// 对 datas 的第一维进行排序
 	sort.Slice(datas, func(i, j int) bool {
-		// 假设你需要根据每个数组第一个元素的 OACreationDate 和 OACreationTime 来排序
+		// 先按 OACreationDate 和 OACreationTime 排序
 		if datas[i][0].OACreationDate == datas[j][0].OACreationDate {
+			if datas[i][0].OACreationTime == datas[j][0].OACreationTime {
+				// 如果 OACreationDate 和 OACreationTime 相同,则按 OARequestID 从大到小排序
+				return datas[i][0].OARequestID > datas[j][0].OARequestID
+			}
 			return datas[i][0].OACreationTime > datas[j][0].OACreationTime
 		}
 		return datas[i][0].OACreationDate > datas[j][0].OACreationDate
 	})
+
 	c.JSON(http.StatusOK, gin.H{
 		"data":     datas,
 		"page":     page,
@@ -341,50 +355,17 @@ func GenerateOALicenseStrController(c *gin.Context) {
 		})
 		return
 	}
-	//生成单独的license
-	if request.UniqueID != "" {
-
-		if err := generateLicenseStrSub(request.UniqueID); err != nil {
-			global.Logger.Errorf("请联系管理员, LicenseInfo%v生成失败:  %v\n", request.UniqueID, err.Error())
-			c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("请联系管理员, LicenseInfo%v生成失败: %v", request.UniqueID, err.Error())})
-			return
-		}
-	} else if request.OARequestId != "" {
-		//一个OARequestId会有多条数据UniqueID
-		OALics, _, err := models.GetOALicenseInfo(0, 0, "", "", "", request.OARequestId)
-		if err != nil || OALics == nil {
-			global.Logger.Errorln("LicenseInfo数据查询失败:  ", err.Error())
-			c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("数据查询失败: ", err.Error())})
-			return
-		}
-		//生成license,已生成的不管
-		for _, v := range *OALics {
-			if v.GenrateInfo.LicenseFlage.String == "未生成" {
-				err := generateLicenseStrSub(utils.ToString(v.LicInfo.UniqueID))
-				if err != nil {
-					global.Logger.Errorf("请联系管理员, LicenseInfo%v生成失败:  %v\n", v.LicInfo.UniqueID, err.Error())
-					c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("请联系管理员, LicenseInfo%v生成失败: %v", v.LicInfo.UniqueID, err.Error())})
-					return
-				}
-			}
-		}
-
-	} else {
-		global.Logger.Errorln("未指定明确License行 ")
-		c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("未指定明确License行 ")})
+	err := services.GenerateOALicenseStrServices(request)
+	if err != nil {
+		c.JSON(http.StatusOK, gin.H{
+			"error": fmt.Sprintf("生成错误: ", err.Error()),
+		})
 		return
 	}
-
-	//刷新缓存
-	// layout := "2006-01-02 15:04:05"
-	// startTime, _ := time.Parse(layout, "2023-07-30 14:00:00")
-	// endTime, _ := time.Parse(layout, "2023-08-26 16:00:00")
-	// if err := global.LicCache.RefreshCache(global.XuguDB, startTime, endTime, 0, 20); err != nil {
-	// 	global.Logger.Errorln("LicenseInfo数据刷新缓存失败:  ", err.Error())
-	// 	c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("数据刷新缓存失败: ", err.Error())})
-	// 	return
-	// }
-	// fmt.Printf("LicenseInfo数据刷新缓存成功 %#v\n", global.LicCache.GetData())
+	c.JSON(http.StatusOK, gin.H{
+		"success": true,
+		"message": "License生成成功!",
+	})
 
 	// applications, _, err := models.GetOALicenseInfo(0, 1, "", request.UniqueID, "")
 	// if err != nil {
@@ -463,117 +444,9 @@ func GenerateOALicenseStrController(c *gin.Context) {
 	// 	}
 	// }
 
-	c.JSON(http.StatusOK, gin.H{
-		"success": true,
-		"message": "License生成成功!",
-	})
 	//xlsx.ExcelToMail(lic, licStr, licStr2)
 }
 
-func generateLicenseStrSub(UniqueID string) error {
-	var applications *[]models.OALicenseInfo
-	var err error
-	if UniqueID != "" {
-
-		applications, _, err = models.GetOALicenseInfo(0, 1, "", UniqueID, "", "")
-		if err != nil {
-			global.Logger.Errorln("LicenseInfo数据查询失败:  ", err.Error())
-			//	c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("数据查询失败: ", err.Error())})
-			return errors.Errorf("LicenseInfo数据查询失败:: %v", err.Error())
-		}
-	}
-	// (*applications)[0].LicInfo.UniqueID.String = ""
-	// (*applications)[0].LicInfo.UniqueID.Valid = false
-	// (*applications)[0].GenrateInfo.LicenseUniqueID.String = ""
-	// (*applications)[0].GenrateInfo.LicenseUniqueID.Valid = false
-	fmt.Printf("applications%#v\n", applications)
-	//检测该数据是否与oa库数据库一致
-	isCheck, err := models.CheckLicenseInfoInOADB(&(*applications)[0].LicInfo)
-	if err != nil {
-		global.Logger.Errorln("LicenseInfo数据对比oa库 查询失败:  ", err.Error())
-		//	c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("数据查询失败: ", err.Error())})
-		return errors.Errorf("LicenseInfo数据对比oa库 查询失败 %v", err.Error())
-	}
-	if !isCheck {
-		global.Logger.Errorln("LicenseInfo数据对比oa库 不一致")
-		return errors.Errorf("LicenseInfo数据对比oa库 数据不一致 \n")
-	}
-
-	//checkTemp := CompareOALicenseInfo(&(*applications)[0], OALicenseInfo)
-	//如果不一致,则修改源数据
-	// if checkTemp != true {
-	// 	global.Logger.Errorln("LicenseInfo数据对比oa库 不一致")
-	// 	//	c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("数据查询失败: ", err.Error())})
-	// 	OALicenseInfo.LicInfo.UniqueID = (*applications)[0].LicInfo.UniqueID
-	// 	if err := models.UpdatelicenseInfoRow(*OALicenseInfo); err != nil {
-	// 		global.Logger.Errorln("LicenseInfo数据对比oa库 不一致,更新数据库失败:  ", err.Error())
-	// 		return errors.Errorf("LicenseInfo数据对比oa库 不一致,更新数据库失败:%v", err.Error())
-	// 	}
-
-	// //刷新缓存
-	// layout := "2006-01-02 15:04:05"
-	// startTime, _ := time.Parse(layout, "2023-07-30 14:00:00")
-	// endTime, _ := time.Parse(layout, "2023-08-26 16:00:00")
-	// if err := global.LicCache.RefreshCache(global.XuguDB, startTime, endTime, 0, 20); err != nil {
-	// 	global.Logger.Errorln("LicenseInfo数据刷新缓存失败:  ", err.Error())
-
-	// 	return errors.Errorf("LicenseInfo数据刷新缓存失败:")
-	// }
-
-	// return errors.Errorf("LicenseInfo数据对比oa库 不一致,清刷新页面")
-	//}
-	// pj := license.ProjectInfo{
-	// 	ProjectName:  utils.ToString((*applications)[0].LicInfo.OAProductName),
-	// 	UserName:     utils.ToString((*applications)[0].LicInfo.OAProductVersion),
-	// 	UserAddr:     "未填写",
-	// 	SerialNumber: "未填写",
-	// }
-	// ei := license.EnvironmentInfo{
-	// 	CpuSN:       "未填写",
-	// 	BaseboardSN: "未填写",
-	// 	MacAddr:     utils.ToString((*applications)[0].LicInfo.OAMainMAC),
-	// 	DiskID:      "未填写",
-	// 	IPAddr:      "未填写",
-	// }
-	// LicType := utils.SwitchLicenseType(utils.ToString((*applications)[0].LicInfo.OAProductName))
-	// lI := license.LicenseInfo{
-	// 	GenDate:        "2024-07-15",
-	// 	ExpireDate:     "9999-12-31",
-	// 	LicenseType:    LicType,
-	// 	LicenseVersion: 1,
-	// 	HardType:       3,
-	// }
-
-	//生成副主节点license
-	if applications == nil {
-		global.Logger.Errorln("LicenseInfo数据生成失败 ,数据查询为空  ")
-		//c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("LicenseInfo数据生成失败 ,数据查询为空: ", err.Error())})
-		return errors.Errorf("LicenseInfo数据生成失败 ,数据查询为空 ")
-	}
-
-	if (*applications)[0].LicInfo.OASecondMAC.String != "" {
-		// var licStr2 []byte
-		// ei.MacAddr = applications[0].SecondaryMasterMacAddress
-		// licStr2 = license.GenerateLicense(pj, ei, lI)
-		err = models.UpdateOALicenseStr(utils.ToString((*applications)[0].LicInfo.UniqueID), []byte("b4j6z4rE2IfG1av0wIPT7YnvyGZFHxwIBikMGjgCLQILR0xsT1NHiuzoi+Dqq+bmiNDEiuPyitDVgdvlRmYbFAk+MAAGASlPTkdMbE9"), []byte("b4j6z4rE2IfG1av0wIPT7YnvyGZFHxwIBikMGjgCLQILR0xsT1NHiuzoi+Dqq+bmiNDEiuPyitDVgdvlRmYbFAk+MAAGASlPTkdMbE9"))
-		if err != nil {
-			global.Logger.Errorln("LicenseInfo生成插入失败:  ", err.Error())
-			//	c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("数据查询失败: ", err.Error())})
-			return errors.Errorf("LicenseInfo生成插入失败:: ", err.Error())
-		}
-	} else {
-		//fmt.Println("licStr licStr2", licStr, licStr2)
-		//插入到数据库
-		err = models.UpdateOALicenseStr(utils.ToString((*applications)[0].LicInfo.UniqueID), []byte("b4j6z4rE2IfG1av0wIPT7YnvyGZFHxwIBikMGjgCLQILR0xsT1NHiuzoi+Dqq+bmiNDEiuPyitDVgdvlRmYbFAk+MAAGASlPTkdMbE9"), nil)
-		if err != nil {
-			global.Logger.Errorln("LicenseInfo2生成插入失败:  ", err.Error())
-			//	c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("数据查询失败: ", err.Error())})
-			return errors.Errorf("LicenseInfo2生成插入失败:: ", err.Error())
-		}
-	}
-	return nil
-}
-
 // func CheckDistributeOALicenseToOaDbController(c *gin.Context) {
 // 	var CheckDisLicense struct {
 // 		LicUniqueID string `json:"lic_unique_id"`
@@ -616,138 +489,23 @@ func DistributeOALicenseController(c *gin.Context) {
 		return
 	}
 
-	var licToUser []string
-	if request.OaRequestId != "" {
-		//获取该license申请单的所有子信息
-		datas, _, err := models.GetOALicenseInfo(0, 0, "", "", "", request.OaRequestId)
-		if err != nil {
-			global.Logger.Errorln("license分发请求查询license申请单的所有子信息失败  %s", err.Error())
-			c.JSON(400, gin.H{
-				"error": fmt.Sprintf("license分发请求解析失败  %s", err.Error()),
-			})
-			return
-		}
-
-		for _, oaLicInfo := range *datas {
-			users, err := checkDistributeToUser(&oaLicInfo, request.UserUniqueIDs, request.UserNames)
-			if err != nil {
-				global.Logger.Errorln("license分发请求检测是否已发放给用户失败  %s", err.Error())
-				c.JSON(400, gin.H{
-					"error": fmt.Sprintf("license分发请求解析失败  %s", err.Error()),
-				})
-				return
-			}
-
-			if len(users) != 0 {
-				c.JSON(400, gin.H{
-					"success": false,
-					"error":   fmt.Sprintf("该license已经分发给了该用户: %s\n", licToUser),
-				})
-				return
-			}
-
-		}
-
-	} else if request.LicenseUniqueID != "" {
-		//查询该license是否已经分发给了该用户
-		for i, v := range request.UserUniqueIDs {
-			fmt.Println("request.UserUniqueIDs", request.LicenseUniqueID, v)
-			if isTurn, err := models.CheckLicenseToUser(request.LicenseUniqueID, v); err != nil {
-				global.Logger.Errorln("该license查询是否分发给用户失败 ", v, err.Error())
-				c.JSON(400, gin.H{
-					"success": false,
-					"error":   fmt.Sprintf("该license查询是否分发给用户失败: %s", v, err.Error()),
-				})
-				return
-			} else if isTurn {
-				global.Logger.Info("该license已经分发给了该用户 ", v)
-				licToUser = append(licToUser, request.UserNames[i])
-			}
-
-		}
-
-	}
-
-	applications, _, err := models.GetOALicenseInfo(1, 1, "", request.LicenseUniqueID, "", "")
+	EmailArray := strings.Split(request.Emails, ",")
+	err, existUsers := services.DistributeOALicenseServices(request.OperatorUniqueID, request.OaRequestId, request.UserUniqueIDs, request.UserAccounts, request.UserNames, EmailArray)
 	if err != nil {
-		c.JSON(http.StatusBadRequest, gin.H{"success": false,
-			"error": err.Error()})
+		global.Logger.Errorln("license分发失败  %s", err.Error())
+		c.JSON(400, gin.H{
+			"error": fmt.Sprintf("license分发失败  %s", err.Error()),
+		})
 		return
 	}
-
-	//如果lic1为空则报错
-	if (*applications)[0].GenrateInfo.Lic1.String == "" {
-		global.Logger.Errorln("license未生成 ")
-		c.JSON(http.StatusBadRequest, gin.H{"success": false,
-			"error": "license未生成"})
+	if existUsers != nil {
+		global.Logger.Errorf("license分发失败,以下用户已经分发过  %s")
+		c.JSON(400, gin.H{
+			"error": fmt.Sprintf("以下用户已经分发过  %s", existUsers),
+		})
 		return
 	}
-
-	//邮箱不空则发送
-	var EmailArray []string
-	if request.Emails != "" {
-		fmt.Println("request.Emails", request.Emails)
-		EmailArray = strings.Split(request.Emails, ",")
-		fmt.Println("EmailArray", EmailArray)
-
-		em, err := email.BuildEmail(&(*applications)[0], EmailArray, (*applications)[0].GenrateInfo.Lic1.String, (*applications)[0].GenrateInfo.Lic2.String)
-		if err != nil {
-			global.Logger.Errorln("邮件生成失败", err.Error())
-
-			c.JSON(400, gin.H{
-				"success": false,
-				"error":   fmt.Sprintf("邮件生成失败: ", err.Error()),
-			})
-			return
-		}
-
-		err = email.SendEmail(em)
-		if err != nil {
-			global.Logger.Errorln("邮件发送失败", err.Error())
-			c.JSON(400, gin.H{
-				"success": false,
-				"error":   fmt.Sprintf("邮件发送失败: ", err.Error()),
-			})
-			return
-		}
-	}
-
-	for i, v := range request.UserUniqueIDs {
-		err = models.InsertlicenseRecordByUserRow((*applications)[0].LicInfo.UniqueID.String, v, request.UserAccounts[i], request.OperatorUniqueID)
-		if err != nil {
-			global.Logger.Errorln("数据库插入失败: ", err.Error())
-			c.JSON(400, gin.H{
-				"success": false,
-				"error":   fmt.Sprintf("插入失败: ", err.Error()),
-			})
-			return
-		}
-	}
-
-	if len(EmailArray) != 0 {
-
-		for _, v := range EmailArray {
-			err = models.InsertlicenseRecordByEmailRow((*applications)[0].LicInfo.UniqueID.String, v, request.OperatorUniqueID)
-			if err != nil {
-				global.Logger.Errorln("数据库插入失败: ", err.Error())
-				c.JSON(400, gin.H{
-					"error": fmt.Sprintf("插入失败: ", err.Error()),
-				})
-				return
-			}
-		}
-	}
-
-	//刷新缓存
-	// layout := "2006-01-02 15:04:05"
-	// startTime, _ := time.Parse(layout, "2023-07-30 14:00:00")
-	// endTime, _ := time.Parse(layout, "2023-08-26 16:00:00")
-	// if err := global.LicCache.RefreshCache(global.XuguDB, startTime, endTime, 0, 20); err != nil {
-	// 	global.Logger.Errorln("LicenseInfo数据刷新缓存失败:  ", err.Error())
-	// 	c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("数据刷新缓存失败: ", err.Error())})
-	// 	return
-	// }
-
+	
 	c.JSON(http.StatusOK, gin.H{
 		"success": true,
 		"data":    "分发成功!",
@@ -771,7 +529,7 @@ func GetlicenseRecordInfo(c *gin.Context) {
 		return
 	}
 
-	LicUers, err := models.GetlicenseRecordByUser(request.UniqueID)
+	LicUers, err := models.GetlicenseRecordByUser(request.OaRequestId, request.UniqueID)
 	if err != nil {
 		global.Logger.Errorln("数据查询失败 ", err.Error())
 
@@ -780,7 +538,7 @@ func GetlicenseRecordInfo(c *gin.Context) {
 		})
 		return
 	}
-	LicEmails, err := models.GetlicenseRecordByEmail(request.UniqueID)
+	LicEmails, err := models.GetlicenseRecordByEmail(request.OaRequestId, request.UniqueID)
 	if err != nil {
 		global.Logger.Errorln("数据查询失败 ", err.Error())
 
@@ -815,60 +573,22 @@ func UpdateLicense(c *gin.Context) {
 	//	fmt.Printf(" license.ApplicationDate: %#v ", license)
 	err := models.UpdatelicenseInfoRow(models.OALicenseInfo{
 		LicInfo: models.TargetOALicenseInfo{
-			UniqueID: sql.NullString{
-				String: license.UniqueID,
-				Valid:  true, // 因为你有一个有效的字符串,所以设置为 true
-			},
-			OARequestName: sql.NullString{
-				String: license.Creator,
-				Valid:  true, // 因为你有一个有效的字符串,所以设置为 true
-			},
-			OACreationDate: sql.NullString{
-				String: license.ApplicationDate,
-				Valid:  true, // 因为你有一个有效的字符串,所以设置为 true
-			},
-			OAGLXMName: sql.NullString{
-				String: license.AssociatedProject,
-				Valid:  true, // 因为你有一个有效的字符串,所以设置为 true
-			},
-			OANodeCount: sql.NullInt64{
-				Int64: license.NodeCount,
-				Valid: true, // 因为你有一个有效的字符串,所以设置为 true
-			},
-			OASalespersonName: sql.NullString{
-				String: license.SalesPerson,
-				Valid:  true, // 因为你有一个有效的字符串,所以设置为 true
-			},
-			OAOperationsPersonName: sql.NullString{
-				String: license.SupportPerson,
-				Valid:  true, // 因为你有一个有效的字符串,所以设置为 true
-			},
-			OAXSJSYX: sql.NullString{
-				String: license.SalesEmail,
-				Valid:  true, // 因为你有一个有效的字符串,所以设置为 true
-			},
-			OAJFJSYX: sql.NullString{
-				String: license.SupportEmail,
-				Valid:  true, // 因为你有一个有效的字符串,所以设置为 true
-			},
-			OAJDS: sql.NullInt64{
-				Int64: license.TotalNodes,
-				Valid: true, // 因为你有一个有效的字符串,所以设置为 true
-			},
-			OAProductName: sql.NullString{
-				String: license.ProductName,
-				Valid:  true, // 因为你有一个有效的字符串,所以设置为 true
-			},
-			OAProductVersion: sql.NullString{
-				String: license.Version,
-				Valid:  true, // 因为你有一个有效的字符串,所以设置为 true
-			},
-			OASYDW: sql.NullString{
-				String: license.Company,
-				Valid:  true, // 因为你有一个有效的字符串,所以设置为 true
-			},
+			UniqueID:               utils.StringToNullString(license.UniqueID),
+			OARequestName:          utils.StringToNullString(license.Creator),
+			OACreationDate:         utils.StringToNullString(license.ApplicationDate),
+			OAGLXMName:             utils.StringToNullString(license.AssociatedProject),
+			OANodeCount:            utils.IntToNullInt64(license.NodeCount),
+			OASalespersonName:      utils.StringToNullString(license.SalesPerson),
+			OAOperationsPersonName: utils.StringToNullString(license.SupportPerson),
+			OAXSJSYX:               utils.StringToNullString(license.SalesEmail),
+			OAJFJSYX:               utils.StringToNullString(license.SupportEmail),
+			OAJDS:                  utils.IntToNullInt64(license.TotalNodes),
+			OAProductName:          utils.StringToNullString(license.ProductName),
+			OAProductVersion:       utils.StringToNullString(license.Version),
+			OASYDW:                 utils.StringToNullString(license.Company),
 		},
 	})
+
 	if err != nil {
 		global.Logger.Errorln("数据插入失败 ", err.Error())
 		c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": fmt.Sprintf("数据插入失败: ", err.Error())})

+ 12 - 0
internal/controllers/user_controllers.go

@@ -6,6 +6,7 @@ import (
 	"regexp"
 
 	"xugu_license/internal/global"
+	middlewares "xugu_license/internal/middleware"
 	"xugu_license/internal/models"
 	"xugu_license/internal/utils"
 	tokens "xugu_license/internal/utils/token"
@@ -207,6 +208,13 @@ func GetAllUserInfo(c *gin.Context) {
 }
 
 func UpdateUserInfo(c *gin.Context) {
+	//获取用户信息和权限
+	userInfo, err := getLoginInfo(c)
+	if err != nil {
+		c.JSON(http.StatusUnauthorized, gin.H{"error": "用户信息不存在"})
+		c.Abort()
+	}
+
 	var req models.UserInfo
 	if err := c.ShouldBindBodyWithJSON(&req); err != nil {
 		c.JSON(http.StatusBadRequest, gin.H{
@@ -214,6 +222,10 @@ func UpdateUserInfo(c *gin.Context) {
 		})
 		return
 	}
+	if userInfo.Permission[middlewares.UpdateRole] != middlewares.UpdateRole {
+		req.Role = userInfo.Role
+	}
+
 	fmt.Println("req", req)
 	if err := models.UpdateUserInfo(req); err != nil {
 		c.JSON(http.StatusBadRequest, gin.H{

+ 3 - 0
internal/global/global.go

@@ -44,6 +44,9 @@ func InitDb() {
 	if err != nil {
 		log.Fatal("OaDB err ", err)
 	}
+	if err := OaDB.Ping(); err != nil {
+		log.Fatal("OaDB ping err ", err)
+	}
 	OaDB.SetConnMaxLifetime(time.Hour) // 设置连接的最大生命周期
 
 	// 启动定时数据同步服务

+ 140 - 34
internal/models/license_oa_models.go

@@ -15,7 +15,7 @@ type OALicenseInfo struct {
 
 type TargetOALicenseInfo struct {
 	ID                     sql.NullInt32  `json:"id"` // 自增主键,从1开始,每次递增1
-	OAId                   sql.NullInt32  `json:"OA_Id"`
+	OAId                   sql.NullInt64  `json:"OA_Id"`
 	UniqueID               sql.NullString `json:"unique_id"`                 // 每行数据的唯一值ID,理论上有oa的REQUESTID就可以了
 	OARequestID            sql.NullInt64  `json:"oa_request_id"`             // oa里的申请单请求ID,假设唯一标识
 	OARequestName          sql.NullString `json:"oa_request_name"`           // 请求名称
@@ -66,6 +66,7 @@ type LicenseRecord struct {
 
 type LicenseRecordToUser struct {
 	ID               int       `json:"id"`
+	OARequestId      int       `json:"oa_request_id"`
 	LicenseUniqueID  string    `json:"license_unique_id"`
 	UserUniqueID     string    `json:"user_unique_id"`
 	UserAccount      string    `json:"user_account"`
@@ -76,6 +77,7 @@ type LicenseRecordToUser struct {
 
 type LicenseRecordToEmails struct {
 	ID               int       `json:"id"`
+	OARequestId      int       `json:"oa_request_id"`
 	LicenseUniqueID  string    `json:"license_unique_id"`
 	Emails           string    `json:"emails"`
 	OperatorUniqueID string    `json:"operator_unique_id"`
@@ -256,7 +258,7 @@ func getSingleLicInfoByUniqueID(unique_ID string) (*OALicenseInfo, error) {
 
 func getLicInfoByUserUniqueID(userUniqueID string, limit int, offset int) (*[]OALicenseInfo, error) {
 	sql := `
-SELECT     la.ID, la.Unique_ID, la.OA_REQUESTID, la.OA_REQUESTNAME, la.OA_REQUESTNAMENEW,
+	 SELECT     la.ID, la.Unique_ID, la.OA_REQUESTID, la.OA_REQUESTNAME, la.OA_REQUESTNAMENEW,
             la.OA_REQUESTNAMEHTMLNEW,
 			la.OA_GLXMID ,la.OA_GLXMNAME,
 			la.OA_SQSJ, la.OA_SALESPERSONNAME, la.OA_XSJSYX,
@@ -274,17 +276,19 @@ INNER JOIN
 INNER JOIN 
 			LICENSERECORDTOUSER LU 
 		ON
-			la.UNIQUE_ID = lu.LICENSE_UNIQUEID
+			la.UNIQUE_ID = lu.LICENSE_UNIQUEID 
+				AND lu.USER_UNIQUEID = ?
+				
 		WHERE OA_REQUESTID IN(
 			SELECT OA_REQUESTID FROM (
-		SELECT OA_REQUESTID ,rownum AS a FROM (SELECT DISTINCT OA_REQUESTID FROM TARGET_OA_LICENSE) a) s
+		SELECT OA_REQUESTID ,rownum AS a FROM (SELECT DISTINCT OA_REQUESTID FROM LICENSERECORDTOUSER) a) s
 								WHERE a BETWEEN ? AND ?) 
 								
 		AND la.del_Time IS NULL
-		AND lu.USER_UNIQUEID = ?
+	
 
 `
-	rows, err := global.XuguDB.Query(sql, limit, offset, userUniqueID)
+	rows, err := global.XuguDB.Query(sql, userUniqueID, limit, offset)
 	if err != nil {
 		global.Logger.Errorln(" getAllLicInfo rows not found ", err.Error())
 		return nil, fmt.Errorf("getAllLicInfo query error: %v", err)
@@ -447,7 +451,7 @@ func UpdatelicenseInfoRow(license OALicenseInfo) error {
 }
 
 // 插入分发记录用户表
-func InsertlicenseRecordByUserRow(LicenseUniqueID string, userUniqueID string, UserAccount string, operatorUniqueID string) error {
+func InsertlicenseRecordByUserRow(oaRequestID string, LicenseUniqueID string, userUniqueID string, UserAccount string, operatorUniqueID string) error {
 
 	tx, err := global.XuguDB.Begin()
 	if err != nil {
@@ -463,10 +467,9 @@ func InsertlicenseRecordByUserRow(LicenseUniqueID string, userUniqueID string, U
 		}
 	}()
 	_, err = tx.Exec(`
-	Insert into  licenseRecordToUser(License_UniqueID ,user_UniqueID,User_Account,up_Time,operator_UniqueID) values(?,?,?,?,?)`,
-		LicenseUniqueID, userUniqueID, UserAccount, time.Now(), operatorUniqueID)
+	Insert into  licenseRecordToUser(OA_REQUESTID,License_UniqueID ,user_UniqueID,User_Account,up_Time,operator_UniqueID) values(?,?,?,?,?,?)`,
+		oaRequestID, LicenseUniqueID, userUniqueID, UserAccount, time.Now(), operatorUniqueID)
 	if err != nil {
-
 		global.Logger.Errorln("插入分发记录用户表失败: ", err.Error())
 		return err
 	}
@@ -475,7 +478,7 @@ func InsertlicenseRecordByUserRow(LicenseUniqueID string, userUniqueID string, U
 }
 
 // 插入分发记录邮箱表
-func InsertlicenseRecordByEmailRow(LicenseUniqueID string, emails string, operatorUniqueID string) error {
+func InsertlicenseRecordByEmailRow(oaRequestID, LicenseUniqueID string, emails string, operatorUniqueID string) error {
 
 	tx, err := global.XuguDB.Begin()
 	if err != nil {
@@ -491,8 +494,8 @@ func InsertlicenseRecordByEmailRow(LicenseUniqueID string, emails string, operat
 		}
 	}()
 	_, err = tx.Exec(`
-	Insert into  licenseRecordToEmails(License_UniqueID ,emails,operator_UniqueID,up_Time) values(?,?,?,?)`,
-		LicenseUniqueID, emails, operatorUniqueID, time.Now())
+	Insert into  licenseRecordToEmails(OA_REQUESTID,License_UniqueID ,emails,operator_UniqueID,up_Time) values(?,?,?,?,?)`,
+		oaRequestID, LicenseUniqueID, emails, operatorUniqueID, time.Now())
 	if err != nil {
 
 		global.Logger.Errorln("插入分发记录邮箱表失败: ", err.Error())
@@ -503,15 +506,18 @@ func InsertlicenseRecordByEmailRow(LicenseUniqueID string, emails string, operat
 }
 
 // 搜索分发记录用户表
-func GetlicenseRecordByUser(uniqueID string) ([]LicenseRecordToUser, error) {
-	fmt.Println("搜索分发记录表 uniqueID", uniqueID)
+func GetlicenseRecordByUser(OA_REQUESTID int, License_UniqueID string) ([]LicenseRecordToUser, error) {
+	fmt.Println("搜索分发记录表 OA_REQUESTID", OA_REQUESTID)
 
-	if uniqueID == "" {
+	if License_UniqueID == "" {
 		return nil, fmt.Errorf("uniqueID is empty")
 
 	}
-	sqlLicUser := `select License_UniqueID,user_UNIQUEID,User_Account,operator_UniqueID,up_Time from licenseRecordToUser where License_UniqueID  = ? AND  del_time IS NULL`
-	rows, err := global.XuguDB.Query(sqlLicUser, uniqueID)
+	if OA_REQUESTID <= 0 {
+		return nil, fmt.Errorf("OA_REQUESTID is empty or error")
+	}
+	sqlLicUser := `select OA_REQUESTID,License_UniqueID,user_UNIQUEID,User_Account,operator_UniqueID,up_Time from licenseRecordToUser where License_UniqueID  = ? AND  del_time IS NULL`
+	rows, err := global.XuguDB.Query(sqlLicUser, License_UniqueID)
 	if err != nil {
 		global.Logger.Errorln("query data: ", err.Error())
 		return nil, fmt.Errorf("query error: %v", err)
@@ -523,6 +529,7 @@ func GetlicenseRecordByUser(uniqueID string) ([]LicenseRecordToUser, error) {
 		var lRU LicenseRecordToUser
 
 		err = rows.Scan(
+			&lRU.OARequestId,
 			&lRU.LicenseUniqueID,
 			&lRU.UserUniqueID,
 			&lRU.UserAccount,
@@ -544,14 +551,18 @@ func GetlicenseRecordByUser(uniqueID string) ([]LicenseRecordToUser, error) {
 }
 
 // 搜索分发记录邮箱表
-func GetlicenseRecordByEmail(uniqueID string) ([]LicenseRecordToEmails, error) {
-	if uniqueID == "" {
+func GetlicenseRecordByEmail(OA_REQUESTID int, License_UniqueID string) ([]LicenseRecordToEmails, error) {
+
+	if License_UniqueID == "" {
 		return nil, fmt.Errorf("uniqueID is empty")
 
 	}
+	if OA_REQUESTID <= 0 {
+		return nil, fmt.Errorf("OA_REQUESTID is empty or error")
+	}
 
-	sqlLicEmail := `select License_UniqueID,emails,operator_UniqueID ,up_Time from licenseRecordToEmails  where License_UniqueID  = ? AND  del_time IS NULL;`
-	rows, err := global.XuguDB.Query(sqlLicEmail, uniqueID)
+	sqlLicEmail := `select OA_REQUESTID, License_UniqueID,emails,operator_UniqueID ,up_Time from licenseRecordToEmails  where License_UniqueID  = ? AND  del_time IS NULL;`
+	rows, err := global.XuguDB.Query(sqlLicEmail, License_UniqueID)
 	if err != nil {
 		global.Logger.Errorln("query data: ", err.Error())
 		return nil, fmt.Errorf("query error: %v", err)
@@ -562,6 +573,7 @@ func GetlicenseRecordByEmail(uniqueID string) ([]LicenseRecordToEmails, error) {
 		var lRU LicenseRecordToEmails
 
 		err = rows.Scan(
+			&lRU.OARequestId,
 			&lRU.LicenseUniqueID,
 			&lRU.Emails,
 			&lRU.OperatorUniqueID,
@@ -634,9 +646,9 @@ type SQLResult struct {
 	OAXMXXMS               sql.NullString `json:"oa_xmxxms"`                 // 项目详细描述 (fm.XMXXMS)
 	OAJDS                  sql.NullInt64  `json:"oa_jds"`                    // 节点数 (fm.JDS)
 	OANodeCount            sql.NullInt64  `json:"oa_node_count"`             // 总节点数 (fmd.JDS)
-	OAProductCode          sql.NullString `json:"oa_product_code"`           // 产品编号 (ws1.SELECTNAME)
+	OAProductName          sql.NullString `json:"oa_product_name"`           // 产品编号 (ws1.SELECTNAME)
 	OAProductVersion       sql.NullString `json:"oa_product_version"`        // 产品版本 (ws2.SELECTNAME)
-	CLQ                    sql.NullString `json:"clq"`                       // 车辆情况 (fmd.CLQ)
+	CLQ                    sql.NullString `json:"clq"`                       // cpu
 	CZXT                   sql.NullString `json:"czxt"`                      // 操作系统 (fmd.CZXT)
 	IP                     sql.NullString `json:"ip"`                        // IP 地址 (fmd.IP)
 	MAC                    sql.NullString `json:"mac"`                       // MAC 地址 (fmd.MAC)
@@ -656,8 +668,7 @@ func CheckLicenseInfoInOADB(licInfo *TargetOALicenseInfo) (bool, error) {
 
 	sql := `	
 		SELECT
-		wr.REQUESTNAME, 
-		wr.REQUESTNAMENEW, wr.REQUESTNAMEHTMLNEW, 
+		wr.REQUESTNAME, wr.REQUESTNAMENEW, wr.REQUESTNAMEHTMLNEW, 
 		fm.glxm,PP.NAME,fm.SQSJ, hrm1.LASTNAME, 
 		hrm2.LASTNAME, fm.XSJSYX, fm.JFJSYX, 
 		fm.SYDW, fm.XMXXMS, fm.JDS, 
@@ -678,7 +689,7 @@ where fmd.id  = ?
 `
 	var record SQLResult
 	//Wvar sqsj string
-	err := global.OaDB.QueryRow(sql, licInfo.OAId.Int32).Scan(
+	err := global.OaDB.QueryRow(sql, licInfo.OAId.Int64).Scan(
 		&record.OARequestName,
 		&record.OARequestNameNew,
 		&record.OARequestNameHTMLNew,
@@ -693,7 +704,7 @@ where fmd.id  = ?
 		&record.OAXMXXMS,
 		&record.OAJDS,
 		&record.OANodeCount,
-		&record.OAProductCode,
+		&record.OAProductName,
 		&record.OAProductVersion,
 		&record.CLQ,
 		&record.CZXT,
@@ -712,16 +723,17 @@ where fmd.id  = ?
 	// 将字符串转换为 time.Time 对象
 	//record.OASQSJ.Time, err = time.Parse("2006-01-02", sqsj)
 
-	if err != nil {
-		//fmt.Println("Error parsing date:", err)
-		return false, fmt.Errorf("CheckLicenseInfoInOADB  将字符串转换为 time.Time  Error : %v", err)
-	}
+	// if err != nil {
+	// 	//fmt.Println("Error parsing date:", err)
+	// 	return false, fmt.Errorf("CheckLicenseInfoInOADB  将字符串转换为 time.Time  Error : %v", err)
+	// }
 
 	if isEqual := compareAndCopy(record, licInfo); !isEqual {
-		return false, fmt.Errorf("与oa数据不一致")
+
+		return true, nil
 	}
 
-	return true, nil
+	return false, nil
 }
 
 func SearchLicInfoToDb(userInput string) ([]TargetOALicenseInfo, error) {
@@ -794,3 +806,97 @@ func SearchLicInfoToDb(userInput string) ([]TargetOALicenseInfo, error) {
 	}
 	return licenses, nil
 }
+
+// 检测该单个license是否分配给用户
+func CheckLicenseToUser(LicenseUniqueID string, userUNIQUEID string) (bool, error) {
+	if LicenseUniqueID == "" || userUNIQUEID == "" {
+		global.Logger.Errorln("LicenseUniqueID 或 userUNIQUEID 为空")
+		return false, fmt.Errorf("LicenseUniqueID 或 userUNIQUEID 为空")
+	}
+
+	type checkLicens struct {
+		check int
+	}
+
+	sql := `SELECT ID FROM licenseRecordToUser WHERE  License_UniqueID = ? AND user_UNIQUEID = ?`
+
+	rows, err := global.XuguDB.Query(sql, LicenseUniqueID, userUNIQUEID)
+	if err != nil {
+		global.Logger.Errorln("query data: ", err.Error())
+		return false, fmt.Errorf("query error: %v", err)
+	}
+
+	var cl checkLicens
+	for rows.Next() {
+
+		err = rows.Scan(
+			&cl.check,
+		)
+
+		if err != nil {
+
+			global.Logger.Errorln("scan row: ", err.Error())
+
+			return false, fmt.Errorf("scan row error: %v", err)
+		}
+
+	}
+
+	if err = rows.Err(); err != nil {
+		global.Logger.Errorln("rows error: ", err.Error())
+		return false, fmt.Errorf("rows error: %v", err)
+	}
+
+	fmt.Println("check: ", cl)
+	if cl.check == 0 {
+		return false, nil
+	}
+	return true, nil
+}
+
+// 按oa的申请单号来检测是否分发给用户
+func CheckOaLicRequest(oaRequestID string, userUNIQUEID string) (bool, error) {
+	if oaRequestID == "" || userUNIQUEID == "" {
+		global.Logger.Errorln("CheckOaLicRequest : LicenseUniqueID 或 userUNIQUEID 为空")
+		return false, fmt.Errorf("oaRequestID 或 userUNIQUEID 为空")
+	}
+
+	type checkLicens struct {
+		check int
+	}
+
+	sql := `SELECT ID FROM licenseRecordToUser WHERE  OA_REQUESTID = ? AND user_UNIQUEID = ?`
+
+	rows, err := global.XuguDB.Query(sql, oaRequestID, userUNIQUEID)
+	if err != nil {
+		global.Logger.Errorln("CheckOaLicRequest : query data: ", err.Error())
+		return false, fmt.Errorf("CheckOaLicRequest : query error: %v", err)
+	}
+
+	var cl checkLicens
+	for rows.Next() {
+
+		err = rows.Scan(
+			&cl.check,
+		)
+
+		if err != nil {
+
+			global.Logger.Errorln("CheckOaLicRequest : scan row: ", err.Error())
+
+			return false, fmt.Errorf("CheckOaLicRequest : scan row error: %v", err)
+		}
+
+	}
+
+	if err = rows.Err(); err != nil {
+		global.Logger.Errorln("CheckOaLicRequest : rows error: ", err.Error())
+		return false, fmt.Errorf("CheckOaLicRequest : rows error: %v", err)
+	}
+
+	if cl.check == 0 {
+		return false, nil
+	}
+
+	return true, nil
+}

+ 0 - 44
internal/models/license_upfile_models.go

@@ -578,48 +578,4 @@ UPDATE LicenseInfo SET LicenseFlage = ? ,lic1 = ?,lic2 = ?  WHERE LicenseApplica
 // 	return nil
 // }
 
-func CheckLicenseToUser(LicenseUniqueID string, userUNIQUEID string) (bool, error) {
-	if LicenseUniqueID == "" || userUNIQUEID == "" {
-		global.Logger.Errorln("LicenseUniqueID 或 userUNIQUEID 为空")
-		return false, fmt.Errorf("LicenseUniqueID 或 userUNIQUEID 为空")
-	}
-
-	type checkLicens struct {
-		check int
-	}
-
-	sql := `SELECT ID FROM licenseRecordToUser WHERE  License_UniqueID = ? AND user_UNIQUEID = ?`
-
-	rows, err := global.XuguDB.Query(sql, LicenseUniqueID, userUNIQUEID)
-	if err != nil {
-		global.Logger.Errorln("query data: ", err.Error())
-		return false, fmt.Errorf("query error: %v", err)
-	}
-
-	var cl checkLicens
-	for rows.Next() {
-
-		err = rows.Scan(
-			&cl.check,
-		)
-
-		if err != nil {
-
-			global.Logger.Errorln("scan row: ", err.Error())
-
-			return false, fmt.Errorf("scan row error: %v", err)
-		}
-
-	}
 
-	if err = rows.Err(); err != nil {
-		global.Logger.Errorln("rows error: ", err.Error())
-		return false, fmt.Errorf("rows error: %v", err)
-	}
-
-	fmt.Println("check: ", cl)
-	if cl.check == 0 {
-		return false, nil
-	}
-	return true, nil
-}

+ 48 - 49
internal/models/models_util.go

@@ -1,150 +1,149 @@
 package models
 
+import "database/sql"
+
 func compareAndCopy(sqlResult SQLResult, target *TargetOALicenseInfo) bool {
 	// 标志位,初始为 true,表示默认所有值相等
 	isEqual := true
 
+	// 辅助函数,用于处理 sql.NullString 类型的比较
+	compareAndAssignNullString := func(sqlValue sql.NullString, targetValue *sql.NullString) bool {
+		if sqlValue.Valid && (!targetValue.Valid || sqlValue.String != targetValue.String) {
+			*targetValue = sqlValue
+			return false
+		}
+		return true
+	}
+
+	// 辅助函数,用于处理 sql.NullInt64 类型的比较
+	compareAndAssignNullInt64 := func(sqlValue sql.NullInt64, targetValue *sql.NullInt64) bool {
+		if sqlValue.Valid && (!targetValue.Valid || sqlValue.Int64 != targetValue.Int64) {
+			*targetValue = sqlValue
+			return false
+		}
+		return true
+	}
+
 	// 比较 OARequestName
-	if sqlResult.OARequestName != target.OARequestName {
-		target.OARequestName = sqlResult.OARequestName
+	if !compareAndAssignNullString(sqlResult.OARequestName, &target.OARequestName) {
 		isEqual = false
 	}
 
 	// 比较 OARequestNameNew
-	if sqlResult.OARequestNameNew != target.OARequestNameNew {
-		target.OARequestNameNew = sqlResult.OARequestNameNew
+	if !compareAndAssignNullString(sqlResult.OARequestNameNew, &target.OARequestNameNew) {
 		isEqual = false
 	}
 
 	// 比较 OARequestNameHTMLNew
-	if sqlResult.OARequestNameHTMLNew != target.OARequestNameHTMLNew {
-		target.OARequestNameHTMLNew = sqlResult.OARequestNameHTMLNew
+	if !compareAndAssignNullString(sqlResult.OARequestNameHTMLNew, &target.OARequestNameHTMLNew) {
 		isEqual = false
 	}
 
-	// 比较 OAGLXMID
-	if sqlResult.OAGLXMID != target.OAGLXMID {
-		target.OAGLXMID = sqlResult.OAGLXMID
+	// 比较 OAGLXMID(处理 sql.NullInt64)
+	if !compareAndAssignNullInt64(sqlResult.OAGLXMID, &target.OAGLXMID) {
 		isEqual = false
 	}
 
 	// 比较 OAGLXMName
-	if sqlResult.OAGLXMName != target.OAGLXMName {
-		target.OAGLXMName = sqlResult.OAGLXMName
+	if !compareAndAssignNullString(sqlResult.OAGLXMName, &target.OAGLXMName) {
 		isEqual = false
 	}
 
 	// 比较 OASQSJ
-	if sqlResult.OASQSJ != target.OASQSJ {
-		target.OASQSJ = sqlResult.OASQSJ
+	if !compareAndAssignNullString(sqlResult.OASQSJ, &target.OASQSJ) {
 		isEqual = false
 	}
 
 	// 比较 OASalespersonName
-	if sqlResult.OASalespersonName != target.OASalespersonName {
-		target.OASalespersonName = sqlResult.OASalespersonName
+	if !compareAndAssignNullString(sqlResult.OASalespersonName, &target.OASalespersonName) {
 		isEqual = false
 	}
 
 	// 比较 OAOperationsPersonName
-	if sqlResult.OAOperationsPersonName != target.OAOperationsPersonName {
-		target.OAOperationsPersonName = sqlResult.OAOperationsPersonName
+	if !compareAndAssignNullString(sqlResult.OAOperationsPersonName, &target.OAOperationsPersonName) {
 		isEqual = false
 	}
 
 	// 比较 OAXSJSYX
-	if sqlResult.OAXSJSYX != target.OAXSJSYX {
-		target.OAXSJSYX = sqlResult.OAXSJSYX
+	if !compareAndAssignNullString(sqlResult.OAXSJSYX, &target.OAXSJSYX) {
 		isEqual = false
 	}
 
 	// 比较 OAJFJSYX
-	if sqlResult.OAJFJSYX != target.OAJFJSYX {
-		target.OAJFJSYX = sqlResult.OAJFJSYX
+	if !compareAndAssignNullString(sqlResult.OAJFJSYX, &target.OAJFJSYX) {
 		isEqual = false
 	}
 
 	// 比较 OASYDW
-	if sqlResult.OASYDW != target.OASYDW {
-		target.OASYDW = sqlResult.OASYDW
+	if !compareAndAssignNullString(sqlResult.OASYDW, &target.OASYDW) {
 		isEqual = false
 	}
 
 	// 比较 OAXMXXMS
-	if sqlResult.OAXMXXMS != target.OAXMXXMS {
-		target.OAXMXXMS = sqlResult.OAXMXXMS
+	if !compareAndAssignNullString(sqlResult.OAXMXXMS, &target.OAXMXXMS) {
 		isEqual = false
 	}
 
-	// 比较 OAJDS
+	// 比较 OAJDS(处理 int 类型)
 	if sqlResult.OAJDS != target.OAJDS {
 		target.OAJDS = sqlResult.OAJDS
 		isEqual = false
 	}
 
-	// 比较 OANodeCount
+	// 比较 OANodeCount(处理 int 类型)
 	if sqlResult.OANodeCount != target.OANodeCount {
 		target.OANodeCount = sqlResult.OANodeCount
 		isEqual = false
 	}
 
-	// 比较 OAProductCode
-	// if sqlResult.OAProductCode != target.OAProductCode {
-	// 	target.OAProductCode = sqlResult.OAProductCode
-	// 	isEqual = false
-	// }
+	// 比较 OAProductName
+	if !compareAndAssignNullString(sqlResult.OAProductName, &target.OAProductName) {
+		isEqual = false
+	}
 
 	// 比较 OAProductVersion
-	if sqlResult.OAProductVersion != target.OAProductVersion {
-		target.OAProductVersion = sqlResult.OAProductVersion
+	if !compareAndAssignNullString(sqlResult.OAProductVersion, &target.OAProductVersion) {
 		isEqual = false
 	}
 
-	// 比较 CLQ
+	// 比较 CLQ(处理 int 类型)
 	if sqlResult.CLQ != target.OACPU {
 		target.OACPU = sqlResult.CLQ
 		isEqual = false
 	}
 
 	// 比较 CZXT
-	if sqlResult.CZXT != target.OAOperatingSystem {
-		target.OAOperatingSystem = sqlResult.CZXT
+	if !compareAndAssignNullString(sqlResult.CZXT, &target.OAOperatingSystem) {
 		isEqual = false
 	}
 
 	// 比较 IP
-	if sqlResult.IP != target.OAMainMAC {
-		target.OAMainMAC = sqlResult.IP
+	if !compareAndAssignNullString(sqlResult.IP, &target.OAMainMAC) {
 		isEqual = false
 	}
 
 	// 比较 MAC
-	if sqlResult.MAC != target.OASecondMAC {
-		target.OASecondMAC = sqlResult.MAC
+	if !compareAndAssignNullString(sqlResult.MAC, &target.OASecondMAC) {
 		isEqual = false
 	}
 
 	// 比较 OACreationDate
-	if sqlResult.OACreationDate != target.OACreationDate {
-		target.OACreationDate = sqlResult.OACreationDate
+	if !compareAndAssignNullString(sqlResult.OACreationDate, &target.OACreationDate) {
 		isEqual = false
 	}
 
 	// 比较 OACreationTime
-	if sqlResult.OACreationTime != target.OACreationTime {
-		target.OACreationTime = sqlResult.OACreationTime
+	if !compareAndAssignNullString(sqlResult.OACreationTime, &target.OACreationTime) {
 		isEqual = false
 	}
 
 	// 比较 OALastOperateDate
-	if sqlResult.OALastOperateDate != target.OALastOperateDate {
-		target.OALastOperateDate = sqlResult.OALastOperateDate
+	if !compareAndAssignNullString(sqlResult.OALastOperateDate, &target.OALastOperateDate) {
 		isEqual = false
 	}
 
 	// 比较 OALastOperateTime
-	if sqlResult.OALastOperateTime != target.OALastOperateTime {
-		target.OALastOperateTime = sqlResult.OALastOperateTime
+	if !compareAndAssignNullString(sqlResult.OALastOperateTime, &target.OALastOperateTime) {
 		isEqual = false
 	}
 

+ 27 - 24
internal/module/capture/capture.go

@@ -105,33 +105,36 @@ func insertLicToUser(count int, targetDB *sql.DB) error {
 			continue
 		}
 
-		//将获取到的字段,和用户表关联对比
-		var userUNIQUEID, userACCOUNT string
-		queryLIC_USER := `SELECT UNIQUEID, ACCOUNT  FROM LIC_USER where 
-						(USERNAME = ?  OR USERNAME = ? )
-						and (role = 'ADMIN'  OR  role = 'supportRole' OR  role = 'salesRole');`
-		err = targetDB.QueryRow(queryLIC_USER, salesPersonName, operationsPersonName).Scan(&userUNIQUEID, &userACCOUNT)
-		if err == sql.ErrNoRows {
-			Logger.Warn("No rows were returned")
-			continue
-		}
-		if err != nil {
-			Logger.Error("find user name Failed to scan row :", err)
-			continue
-		}
+		if salesPersonName.Valid || operationsPersonName.Valid != false {
+			//将获取到的字段,和用户表关联对比
+			var userUNIQUEID, userACCOUNT sql.NullString
+			queryLIC_USER := `SELECT UNIQUEID, ACCOUNT  FROM LIC_USER where 
+				(USERNAME = ?  OR USERNAME = ? )
+				and (role = 'ADMIN'  OR  role = 'supportRole' OR  role = 'salesRole');`
+			err = targetDB.QueryRow(queryLIC_USER, salesPersonName, operationsPersonName).Scan(&userUNIQUEID, &userACCOUNT)
+			if err == sql.ErrNoRows {
+				Logger.Warn("No rows were returned")
+				continue
+			}
+			if err != nil {
+				Logger.Error("find user name Failed to scan row :", err)
+				continue
+			}
 
-		//fmt.Println("关联到用户 ", userUNIQUEID, userACCOUNT)
-		insertQuery := "INSERT  INTO LICENSERECORDTOUSER(LICENSE_UNIQUEID,USER_UNIQUEID,USER_ACCOUNT,OPERATOR_UNIQUEID,UP_TIME) VALUES(?,?,?,'SYSTEM_AUTO',?)"
-		_, err = targetDB.Exec(insertQuery,
-			licUniqueID,
-			userUNIQUEID,
-			userACCOUNT,
-			time.Now(),
-		)
+			//fmt.Println("关联到用户 ", userUNIQUEID, userACCOUNT)
+			insertQuery := "INSERT  INTO LICENSERECORDTOUSER(LICENSE_UNIQUEID,USER_UNIQUEID,USER_ACCOUNT,OPERATOR_UNIQUEID,UP_TIME) VALUES(?,?,?,'SYSTEM_AUTO',?)"
+			_, err = targetDB.Exec(insertQuery,
+				licUniqueID,
+				userUNIQUEID,
+				userACCOUNT,
+				time.Now(),
+			)
 
-		if err != nil {
-			Logger.Error("lic关联到目标库用户错误 Failed to sql2 insert record into target DB:", err)
+			if err != nil {
+				Logger.Error("lic关联到目标库用户错误 Failed to sql2 insert record into target DB:", err)
+			}
 		}
+
 	}
 
 	return nil

+ 4 - 2
internal/module/email/email.go

@@ -52,11 +52,13 @@ func BuildEmail(lic *models.OALicenseInfo, emils []string, licStr string, licStr
 		return nil, err
 	}
 	if licStr2 == "" {
-		body := fmt.Sprintf(" 来自%s 使用%s ,该项目销售:%s, 技术支持:%s", lic.LicInfo.OAXMXXMS.String, lic.LicInfo.OAGLXMName.String, lic.LicInfo.OASalespersonName.String, lic.LicInfo.OAOperationsPersonName.String)
+		body := fmt.Sprintf(" 来自%s项目, 该项目信息:%s\n 使用%s ,该项目销售:%s, 技术支持:%s", lic.LicInfo.OAGLXMName.String, lic.LicInfo.OAXMXXMS.String,
+			lic.LicInfo.OAProductName.String, lic.LicInfo.OASalespersonName.String, lic.LicInfo.OAOperationsPersonName.String)
 		em = NewEmail("gt@xugudb.com", "zI7cKadNHv7XedV5", emils, "smtp.sparkspace.huaweicloud.com",
 			"465", "来自license消息分发", body, []byte(licStr), nil)
 	} else {
-		body := fmt.Sprintf(" 来自%s 使用%s ,该项目销售:%s, 技术支持:%s", lic.LicInfo.OAXMXXMS.String, lic.LicInfo.OAGLXMName.String, lic.LicInfo.OASalespersonName.String, lic.LicInfo.OAOperationsPersonName.String)
+		body := fmt.Sprintf(" 来自%s项目, 该项目信息:%s\n 使用%s ,该项目销售:%s, 技术支持:%s", lic.LicInfo.OAGLXMName.String, lic.LicInfo.OAXMXXMS.String,
+			lic.LicInfo.OAProductName.String, lic.LicInfo.OASalespersonName.String, lic.LicInfo.OAOperationsPersonName.String)
 		em = NewEmail("gt@xugudb.com", "zI7cKadNHv7XedV5", emils, "smtp.sparkspace.huaweicloud.com",
 			"465", "来自license消息分发", body, []byte(licStr), []byte(licStr2))
 	}

+ 196 - 0
internal/services/OALicenseInfo_services.go

@@ -0,0 +1,196 @@
+package services
+
+import (
+	"fmt"
+	"strings"
+	"xugu_license/internal/api"
+	"xugu_license/internal/global"
+	"xugu_license/internal/models"
+	"xugu_license/internal/module/email"
+	"xugu_license/internal/utils"
+
+	"github.com/pkg/errors"
+)
+
+func DistributeOALicenseServices(OperatorUniqueID, OaRequestId string, UserUniqueIDs, UserAccounts, UserNames, EmailArray []string) (error, []string) {
+	//检测OARequest是否以及分发过给用户
+	existUsers, err := checkDistributeToUser(OaRequestId, UserUniqueIDs, UserNames)
+	if err != nil {
+		global.Logger.Errorln("license分发请求检测是否已发放给用户失败  %s", err.Error())
+		return err, nil
+	}
+	//返回以及分发过的用户列表
+	if len(existUsers) != 0 {
+
+		return nil, existUsers
+	}
+	fmt.Println("DistributeOALicenseServices : ")
+	//获取一个申请单的多个license信息
+	LicInfos, _, err := models.GetOALicenseInfo(1, 1, "", "", "", OaRequestId)
+	if err != nil {
+		return err, nil
+	}
+
+	//将分发的lic信息插入lic分发用户表中
+	for _, licInfo := range *LicInfos {
+
+		for userIdx, userUID := range UserUniqueIDs {
+
+			//如果lic1为空则报错
+			if licInfo.GenrateInfo.Lic1.String == "" {
+				global.Logger.Errorln("DistributeOALicenseServices : license生成信息为空 ")
+				return errors.New("license生成信息为空"), nil
+			}
+
+			err = models.InsertlicenseRecordByUserRow(OaRequestId, licInfo.LicInfo.UniqueID.String, userUID, UserAccounts[userIdx], OperatorUniqueID)
+			if err != nil {
+				global.Logger.Errorln("DistributeOALicenseServices: 数据库插入失败: ", err.Error())
+				return err, nil
+			}
+		}
+	}
+	//发送邮件
+	//邮箱不空则发送
+	//将一个申请单的多个license信息用邮箱发送
+	fmt.Println("len(EmailArray) == ")
+	if len(EmailArray) == 0 {
+		return nil, nil
+	}
+
+	fmt.Println("EmailArray : ", EmailArray)
+	emails := strings.Join(EmailArray, ",")
+	if !strings.Contains(emails, "@") {
+		return nil, nil
+	}
+	for _, lic := range *LicInfos {
+
+		if lic.GenrateInfo.Lic1.String == "" {
+			global.Logger.Errorln("DistributeOALicenseServices : license生成信息为空 ")
+			return errors.New("license生成信息为空"), nil
+		}
+
+		//组装邮件
+		em, err := email.BuildEmail(&lic, EmailArray, lic.GenrateInfo.Lic1.String, lic.GenrateInfo.Lic2.String)
+		if err != nil {
+			global.Logger.Errorln("邮件生成失败", err.Error())
+			return err, nil
+		}
+		//发送邮件
+		err = email.SendEmail(em)
+		if err != nil {
+			global.Logger.Errorln("邮件发送失败", err.Error())
+
+			return err, nil
+		}
+		//将分发的lic信息插入lic分发邮箱表中
+		err = models.InsertlicenseRecordByEmailRow(OaRequestId, lic.LicInfo.UniqueID.String, emails, OperatorUniqueID)
+		if err != nil {
+			global.Logger.Errorln("数据库插入失败: ", err.Error())
+			return err, nil
+		}
+
+	}
+
+	return nil, nil
+}
+
+func GenerateOALicenseStrServices(request api.GenerateLicenseStrRequest) error {
+	//生成单独的license
+	if request.UniqueID != "" {
+		if err, isCheck := generateLicenseStrSub(request.UniqueID); err != nil {
+			global.Logger.Errorf("请联系管理员, LicenseInfo%v生成失败:  %v\n", request.UniqueID, err.Error())
+			return errors.Errorf("请联系管理员, LicenseInfo%v生成失败: %v", err.Error())
+		} else if isCheck {
+			return errors.Errorf("该申请信息与OA库不一致,请刷新页面")
+		}
+
+	} else if request.OARequestId != "" {
+		//一个OARequestId会有多条数据UniqueID
+		OALics, _, err := models.GetOALicenseInfo(0, 0, "", "", "", request.OARequestId)
+		if err != nil || OALics == nil {
+			global.Logger.Errorln("LicenseInfo数据查询失败:  ", err.Error())
+			return errors.Errorf("LicenseInfo数据查询失败 : %v", err.Error())
+		}
+		//生成license,已生成的不管
+		for _, v := range *OALics {
+			if v.GenrateInfo.LicenseFlage.String == "未生成" {
+				err, isCheck := generateLicenseStrSub(utils.ToString(v.LicInfo.UniqueID))
+				if err != nil {
+					global.Logger.Errorf("请联系管理员, LicenseInfo%v生成失败:  %v\n", v.LicInfo.UniqueID, err.Error())
+					return errors.Errorf("请联系管理员, LicenseInfo%v生成失败:  %v\n", v.LicInfo.UniqueID, err.Error())
+				} else if isCheck {
+					return errors.Errorf("该申请信息与OA库不一致,请刷新页面")
+				}
+			}
+		}
+	} else {
+		global.Logger.Errorln("未指定明确License行生成 ")
+		return errors.Errorf("未指定明确License行生成\n")
+	}
+
+	return nil
+}
+
+func generateLicenseStrSub(UniqueID string) (error, bool) {
+	var applications *[]models.OALicenseInfo
+	var err error
+	if UniqueID != "" {
+		applications, _, err = models.GetOALicenseInfo(0, 1, "", UniqueID, "", "")
+		if err != nil {
+			global.Logger.Errorln("LicenseInfo数据查询失败:  ", err.Error())
+			//	c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("数据查询失败: ", err.Error())})
+			return errors.Errorf("LicenseInfo数据查询失败: %v", err.Error()), false
+		}
+	}
+	// (*applications)[0].LicInfo.UniqueID.String = ""
+	// (*applications)[0].LicInfo.UniqueID.Valid = false
+	// (*applications)[0].GenrateInfo.LicenseUniqueID.String = ""
+	// (*applications)[0].GenrateInfo.LicenseUniqueID.Valid = false
+	fmt.Printf("applications%#v\n", applications)
+	//检测该数据是否与oa库数据库一致 //todo
+	// isCheck, err := models.CheckLicenseInfoInOADB(&(*applications)[0].LicInfo)
+	// if err != nil {
+	// 	global.Logger.Errorln("LicenseInfo数据对比oa库 查询失败:  ", err.Error())
+	// 	//	c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("数据查询失败: ", err.Error())})
+	// 	return errors.Errorf("LicenseInfo数据对比oa库 查询失败 %v", err.Error()), false
+	// }
+	// if isCheck {
+	// 	global.Logger.Errorln("LicenseInfo数据对比oa库 不一致")
+	// 	err := models.UpdatelicenseInfoRow(models.OALicenseInfo{
+	// 		LicInfo: (*applications)[0].LicInfo,
+	// 	})
+	// 	if err != nil {
+	// 		return err, false
+	// 	}
+	// 	return nil, true
+	// }
+
+	//生成副主节点license
+	if applications == nil {
+		global.Logger.Errorln("LicenseInfo数据生成失败 ,数据查询为空  ")
+		//c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("LicenseInfo数据生成失败 ,数据查询为空: ", err.Error())})
+		return errors.Errorf("LicenseInfo数据生成失败 ,数据查询为空 "), false
+	}
+
+	if (*applications)[0].LicInfo.OASecondMAC.String != "" {
+		// var licStr2 []byte
+		// ei.MacAddr = applications[0].SecondaryMasterMacAddress
+		// licStr2 = license.GenerateLicense(pj, ei, lI)
+		err = models.UpdateOALicenseStr(utils.ToString((*applications)[0].LicInfo.UniqueID), []byte("b4j6z4rE2IfG1av0wIPT7YnvyGZFHxwIBikMGjgCLQILR0xsT1NHiuzoi+Dqq+bmiNDEiuPyitDVgdvlRmYbFAk+MAAGASlPTkdMbE9"), []byte("b4j6z4rE2IfG1av0wIPT7YnvyGZFHxwIBikMGjgCLQILR0xsT1NHiuzoi+Dqq+bmiNDEiuPyitDVgdvlRmYbFAk+MAAGASlPTkdMbE9"))
+		if err != nil {
+			global.Logger.Errorln("LicenseInfo生成插入失败:  ", err.Error())
+			//	c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("数据查询失败: ", err.Error())})
+			return errors.Errorf("LicenseInfo生成插入失败:: ", err.Error()), false
+		}
+	} else {
+		//fmt.Println("licStr licStr2", licStr, licStr2)
+		//插入到数据库
+		err = models.UpdateOALicenseStr(utils.ToString((*applications)[0].LicInfo.UniqueID), []byte("b4j6z4rE2IfG1av0wIPT7YnvyGZFHxwIBikMGjgCLQILR0xsT1NHiuzoi+Dqq+bmiNDEiuPyitDVgdvlRmYbFAk+MAAGASlPTkdMbE9"), nil)
+		if err != nil {
+			global.Logger.Errorln("LicenseInfo2生成插入失败:  ", err.Error())
+			//	c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("数据查询失败: ", err.Error())})
+			return errors.Errorf("LicenseInfo2生成插入失败:: ", err.Error()), false
+		}
+	}
+	return nil, false
+}

+ 23 - 0
internal/services/OALicenseInfo_services_sub.go

@@ -0,0 +1,23 @@
+package services
+
+import (
+	"xugu_license/internal/global"
+	"xugu_license/internal/models"
+)
+
+func checkDistributeToUser(OaLicRequest string, UserUniqueIDs []string, UserNames []string) (licToUser []string, err error) {
+
+	for i, UserUID := range UserUniqueIDs {
+
+		//查询该license是否已经分发给了该用户
+		if isTurn, err := models.CheckOaLicRequest(OaLicRequest, UserUID); err != nil {
+			global.Logger.Errorln("该license查询是否分发给用户失败 ", UserUID, err.Error())
+			return nil, err
+		} else if isTurn {
+			global.Logger.Info("该license已经分发给了该用户 ", UserUID, UserNames[i])
+			licToUser = append(licToUser, UserNames[i])
+		}
+
+	}
+	return licToUser, nil
+}

+ 22 - 0
internal/utils/utils.go

@@ -117,3 +117,25 @@ func ToTimeString(nt sql.NullTime) string {
 	}
 	return ""
 }
+
+// StringToNullString 将字符串转换为 sql.NullString
+func StringToNullString(s string) sql.NullString {
+	if s == "" {
+		return sql.NullString{
+			String: "",
+			Valid:  false,
+		}
+	}
+	return sql.NullString{
+		String: s,
+		Valid:  true,
+	}
+}
+
+// IntToNullInt64 将 int64 转换为 sql.NullInt64
+func IntToNullInt64(i int64) sql.NullInt64 {
+	return sql.NullInt64{
+		Int64: i,
+		Valid: true,
+	}
+}

+ 0 - 92
pkg/xugu/xugu/xugu_buffer.go

@@ -1,92 +0,0 @@
-package xugu
-
-import (
-	"errors"
-	"net"
-	"time"
-)
-
-var (
-	ErrBusyBuffer = errors.New("busy buffer")
-)
-
-type buffer struct {
-	buf     []byte // buf 是一个字节缓冲区,长度和容量相等。
-	conn    net.Conn
-	idx     int64
-	length  int
-	timeout time.Duration
-}
-
-// newBuffer 分配并返回一个新的缓冲区。
-func newBuffer(nc net.Conn) buffer {
-	return buffer{
-		buf:  make([]byte, 2048),
-		conn: nc,
-	}
-}
-
-func (b *buffer) peekChar() byte {
-	if b.idx >= int64(len(b.buf)) {
-		b.readNext(1, false)
-		b.idx-- //peekchar 只查看当前字符,不移动指针,但是readNext会移动指针,所以需要-1
-	}
-	ret := b.buf[b.idx]
-	return ret
-}
-func (b *buffer) reset() {
-	b.idx = 0
-	b.length = 0
-	b.buf = make([]byte, 2048)
-}
-
-func (b *buffer) readNext(need int, reverse bool) ([]byte, error) {
-	if need == 0 {
-		return nil, nil
-	}
-	//长度不够返回
-	if len(b.buf[b.idx:b.length]) < need {
-		buffer := make([]byte, need+1)
-		b.buf = append(b.buf[:b.length], buffer...)
-		n, err := b.conn.Read(b.buf[b.length:])
-		if err != nil {
-			// if err == io.EOF {
-
-			// }
-			return nil, err
-		}
-
-		b.length += n
-
-		for b.length-int(b.idx) < need {
-
-			n, err := b.conn.Read(b.buf[b.length:])
-			if err != nil {
-				// if err == io.EOF {
-				// 	fmt.Println("End of data")
-
-				// }
-
-				return nil, err
-			}
-			//nTmp += n
-			b.length += n
-		}
-
-	}
-
-	offset := b.idx
-	b.idx += int64(need)
-	if GlobalIsBig {
-		reverse = false
-	}
-	if reverse {
-		tmp := reverseBytes(b.buf[offset:b.idx])
-
-		return tmp, nil
-	} else {
-
-		return b.buf[offset:b.idx], nil
-	}
-
-}

+ 0 - 388
pkg/xugu/xugu/xugu_conn.go

@@ -1,388 +0,0 @@
-package xugu
-
-import (
-	"bytes"
-	"context"
-	"database/sql/driver"
-	"encoding/binary"
-	"errors"
-	"fmt"
-	"net"
-	"sync"
-)
-
-type xuguConn struct {
-	dsnConfig
-	conn        net.Conn
-	mu          sync.Mutex
-	useSSL      bool // 是否使用加密
-	havePrepare int  //default 0
-
-	prepareNo   int
-	prepareName string
-	//presPrepareCata *Result
-
-	errStr []byte
-
-	sendBuff bytes.Buffer
-	readBuff buffer
-}
-
-type dsnConfig struct {
-	IP             string
-	Port           string
-	Database       string
-	User           string
-	Password       string
-	Encryptor      string //加密库的解密口令
-	CharSet        string //客户端使用的字符集名
-	TimeZone       string
-	IsoLevel       string //事务隔离级别
-	LockTimeout    string //加锁超时
-	AutoCommit     string
-	StrictCommit   string
-	Result         string
-	ReturnSchema   string
-	ReturnCursorID string
-	LobRet         string
-	ReturnRowid    string
-	Version        string
-}
-
-func (xgConn *xuguConn) Begin() (driver.Tx, error) {
-
-	_, err := xgConn.exec("Begin;", nil)
-	if err != nil {
-		return nil, err
-	}
-	return &xuguTx{tconn: xgConn}, nil
-
-}
-
-func (xgConn *xuguConn) Close() error {
-
-	xgConn.mu.Lock()
-	defer xgConn.mu.Unlock()
-	err := xgConn.conn.Close()
-	if err != nil {
-		xgConn.mu.Unlock()
-		return err
-	}
-
-	return nil
-}
-
-func (xgConn *xuguConn) Query(sql string,
-	args []driver.Value) (driver.Rows, error) {
-
-	xgConn.mu.Lock()
-	defer xgConn.mu.Unlock()
-
-	// 检测sql语句不是查询则报错
-	if switchSQLType(sql) != SQL_SELECT {
-		return nil, errors.New("The executed SQL statement is not a SELECT")
-	}
-
-	// 有传进来的参数
-	if len(args) != 0 {
-		values := []xuguValue{}
-		//判断类型
-		for _, param := range args {
-			err := assertParamType(param, &values)
-			if err != nil {
-				return nil, err
-			}
-		}
-		//send msg
-		if err := sockSendPutStatement(xgConn, []byte(sql), &values, len(args)); err != nil {
-			return nil, err
-		}
-
-		if err := sockSendExecute(xgConn); err != nil {
-			return nil, err
-		}
-
-	} else {
-
-		//send msg
-		if err := sockSendPutStatement(xgConn, []byte(sql), nil, len(args)); err != nil {
-			return nil, err
-		}
-
-		if err := sockSendExecute(xgConn); err != nil {
-			return nil, err
-		}
-
-	}
-	//recv msg
-	aR, err := xuguSockRecvMsg(xgConn)
-	if err != nil {
-		return nil, err
-	}
-
-	switch aR.rt {
-	case selectResult:
-
-		rows := &xuguRows{
-			rows_conn: xgConn,
-			results:   aR.s,
-			colIdx:    0,
-			prepared:  false,
-		}
-
-		return rows, nil
-	case errInfo:
-
-		return nil, errors.New(string(aR.e.ErrStr))
-	case warnInfo:
-
-		return nil, errors.New(string(aR.w.WarnStr))
-	default:
-	}
-
-	return nil, errors.New("xugu Query error")
-}
-
-func (xgConn *xuguConn) Ping(ctx context.Context) error {
-
-	//send
-	xgConn.mu.Lock()
-	defer xgConn.mu.Unlock()
-	sockSendPutStatement(xgConn, []byte("select count(*) from dual;"), nil, 0)
-	sockSendExecute(xgConn)
-
-	_, err := xuguSockRecvMsg(xgConn)
-	if err != nil {
-		return err
-	}
-	xgConn.readBuff.reset()
-
-	return nil
-}
-
-func (xgConn *xuguConn) Prepare(sql string) (driver.Stmt, error) {
-
-	xgConn.mu.Lock()
-	defer xgConn.mu.Unlock()
-
-	//判断sql类型
-	switch switchSQLType(sql) {
-	case SQL_PROCEDURE:
-		return nil, errors.New("Prepare does not support stored procedures")
-	case SQL_UNKNOWN:
-		return nil, errors.New("Unknown SQL statement type")
-	case SQL_CREATE:
-		return nil, errors.New("Prepare does not support DDL.")
-	}
-	//发送创建prepare
-	prepareName := "GTONG"
-
-	err := xuguPrepare(xgConn, sql, prepareName)
-	if err != nil {
-		return nil, err
-	}
-	count := assertParamCount(sql)
-	stmt := &xuguStmt{
-		stmt_conn:  xgConn,
-		prepared:   true,
-		prename:    make([]byte, 128),
-		curopend:   false,
-		curname:    make([]byte, 128),
-		paramCount: count,
-		mysql:      sql,
-	}
-	stmt.prename = []byte(fmt.Sprintf("? %s", xgConn.prepareName))
-
-	return stmt, nil
-}
-
-func xuguPrepare(pConn *xuguConn, cmd_sql string, prepareName string) error {
-
-	prepareName = fmt.Sprintf("%s%d", prepareName, pConn.prepareNo)
-	sqlRet := fmt.Sprintf("PREPARE %s AS %s", prepareName, cmd_sql)
-	pConn.prepareName = prepareName
-	pConn.prepareNo++
-	//send msg
-	sockSendPutStatement(pConn, []byte(sqlRet), nil, 0)
-	sockSendExecute(pConn)
-	//recv msg
-	aR, err := xuguSockRecvMsg(pConn)
-	switch aR.rt {
-
-	case errInfo:
-
-		return errors.New(string(aR.e.ErrStr))
-	case warnInfo:
-
-		return errors.New(string(aR.w.WarnStr))
-	default:
-	}
-
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func xuguUnPrepare(pConn *xuguConn, prepareName string) error {
-
-	sqlRet := fmt.Sprintf("DEALLOCATE %s ", prepareName)
-
-	//send msg
-	sockSendPutStatement(pConn, []byte(sqlRet), nil, 0)
-	sockSendExecute(pConn)
-	//recv msg
-	aR, err := xuguSockRecvMsg(pConn)
-	switch aR.rt {
-
-	case 'K':
-		return nil
-	case errInfo:
-
-		return errors.New(string(aR.e.ErrStr))
-	case warnInfo:
-
-		return errors.New(string(aR.w.WarnStr))
-	default:
-	}
-
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func (xgConn *xuguConn) Exec(sql string,
-	args []driver.Value) (driver.Result, error) {
-
-	xgConn.mu.Lock()
-	defer xgConn.mu.Unlock()
-
-	// 检测sql语句是查询则报错
-	if switchSQLType(sql) == SQL_SELECT {
-		return nil, errors.New("The executed SQL statement is  a SELECT")
-	}
-
-	// 有传进来的参数
-	if len(args) != 0 {
-		values := []xuguValue{}
-		//判断类型
-		for _, param := range args {
-			err := assertParamType(param, &values)
-			if err != nil {
-				return nil, err
-			}
-		}
-		//send msg
-		if err := sockSendPutStatement(xgConn, []byte(sql), &values, len(args)); err != nil {
-			return nil, err
-		}
-
-		if err := sockSendExecute(xgConn); err != nil {
-			return nil, err
-		}
-
-	} else {
-
-		//send msg
-		if err := sockSendPutStatement(xgConn, []byte(sql), nil, len(args)); err != nil {
-			return nil, err
-		}
-
-		if err := sockSendExecute(xgConn); err != nil {
-			return nil, err
-		}
-
-	}
-	//recv msg
-	aR, err := xuguSockRecvMsg(xgConn)
-	if err != nil {
-		return nil, err
-	}
-
-	switch aR.rt {
-	case selectResult:
-
-		return nil, errors.New("exec is Query error")
-	case errInfo:
-
-		return nil, errors.New(string(aR.e.ErrStr))
-	case warnInfo:
-
-		return nil, errors.New(string(aR.w.WarnStr))
-	case updateResult:
-		return &xuguResult{affectedRows: int64(aR.u.UpdateNum), insertId: int64(0)}, nil
-	case insertResult:
-		return &xuguResult{affectedRows: int64(0), insertId: int64(aR.i.RowidLen)}, nil
-	default:
-		return &xuguResult{
-			affectedRows: int64(0),
-			insertId:     int64(0),
-		}, nil
-	}
-
-}
-
-func (xgConn *xuguConn) exec(sql string, args []driver.Value) (driver.Result, error) {
-	// 有传进来的参数
-	if len(args) != 0 {
-		values := []xuguValue{}
-		//判断类型
-		for _, param := range args {
-			err := assertParamType(param, &values)
-			if err != nil {
-				return nil, err
-			}
-		}
-		//send msg
-		if err := sockSendPutStatement(xgConn, []byte(sql), &values, len(args)); err != nil {
-			return nil, err
-		}
-
-		if err := sockSendExecute(xgConn); err != nil {
-			return nil, err
-		}
-
-	} else {
-
-		//send msg
-		if err := sockSendPutStatement(xgConn, []byte(sql), nil, len(args)); err != nil {
-			return nil, err
-		}
-
-		if err := sockSendExecute(xgConn); err != nil {
-			return nil, err
-		}
-
-	}
-
-	//recv msg
-	aR, err := xuguSockRecvMsg(xgConn)
-	if err != nil {
-		return nil, err
-	}
-	switch aR.rt {
-	case selectResult:
-
-		return nil, errors.New("exec is Query error")
-	case updateResult:
-		return &xuguResult{affectedRows: int64(aR.u.UpdateNum), insertId: int64(0)}, nil
-	case insertResult:
-
-		return &xuguResult{affectedRows: int64(0), insertId: int64(binary.LittleEndian.Uint64(aR.i.RowidData))}, nil
-	case errInfo:
-
-		return nil, errors.New(string(aR.e.ErrStr))
-	case warnInfo:
-
-		return nil, errors.New(string(aR.w.WarnStr))
-	default:
-		return &xuguResult{
-			affectedRows: int64(0),
-			insertId:     int64(0),
-		}, nil
-	}
-
-}

+ 0 - 64
pkg/xugu/xugu/xugu_connector.go

@@ -1,64 +0,0 @@
-package xugu
-
-import (
-	"context"
-	"database/sql/driver"
-	"fmt"
-	"net"
-	"time"
-)
-
-type connector struct {
-	dsn string
-}
-
-// Driver implements driver.Connector interface.
-// Driver returns &XuguDriver{}
-func (conntor *connector) Driver() driver.Driver {
-
-	return &XuguDriver{}
-}
-
-// Connect implements driver.Connector interface.
-// Connect returns a connection to the database.
-/*
-dsn解析
-创建连接
-设置为 tcp 长连接(
-创建连接缓冲区
-设置连接超时配置
-接收来自服务端的握手请求
-*/
-func (conntor *connector) Connect(ctx context.Context) (driver.Conn, error) {
-
-	GlobalIsBig = CheckEndian()
-
-	dsnConfig := parseDSN(conntor.dsn)
-
-	xgConn := &xuguConn{conn: nil}
-	xgConn.dsnConfig = dsnConfig
-
-	nd := net.Dialer{Timeout: 10 * time.Millisecond}
-	netConn, err := nd.DialContext(ctx, "tcp", fmt.Sprintf("%s:%s", xgConn.IP, xgConn.Port))
-	if err != nil {
-		return nil, err
-	}
-
-	// 启用 TCP 保活
-	if tc, ok := netConn.(*net.TCPConn); ok {
-		if err := tc.SetKeepAlive(true); err != nil {
-			//c.cfg.Logger.Print(err) // 如果设置保活失败,记录错误但不终止
-			return nil, err
-		}
-	}
-
-	xgConn.conn = netConn
-	xgConn.mu.Lock()
-	xgConn.readBuff = newBuffer(xgConn.conn)
-	err = xgSockOpenConn(ctx, xgConn)
-	if err != nil {
-		return nil, err
-	}
-	xgConn.mu.Unlock()
-	return xgConn, nil
-}

+ 0 - 32
pkg/xugu/xugu/xugu_define.go

@@ -1,32 +0,0 @@
-package xugu
-
-var GlobalIsBig bool
-
-// 协议消息类型
-type msgType byte
-
-const (
-	selectResult msgType = iota + 0x01
-	insertResult
-	updateResult
-	deleteResult
-	procRet
-	outParamRet
-	errInfo
-	warnInfo
-	message
-	formArgDescri
-)
-
-// SQL类型常量
-const (
-	SQL_UNKNOWN = iota
-	SQL_SELECT
-	SQL_INSERT
-	SQL_UPDATE
-	SQL_DELETE
-	SQL_CREATE
-	SQL_ALTER
-	SQL_PROCEDURE
-	SQL_OTHER
-)

+ 0 - 50
pkg/xugu/xugu/xugu_driver.go

@@ -1,50 +0,0 @@
-package xugu
-
-import (
-	"context"
-	"database/sql"
-	"database/sql/driver"
-	"time"
-)
-
-// XuguDriver is exported to make the driver directly accessible
-type XuguDriver struct{}
-
-/* Register Driver */
-func init() {
-
-	/* Register makes a database driver available by the provided name.
-	 * If Register is called twice with the same name or if driver is nil,
-	 * it panics.
-	 */
-	sql.Register("xugusql", &XuguDriver{})
-	timezone, _ := time.LoadLocation("Asia/Shanghai")
-	time.Local = timezone
-}
-
-// Open opens a database specified by its database driver name and a
-// driver-specific data source name, usually consisting of at least a
-// database name and connection information.
-//
-// Most users will open a database via a driver-specific connection
-// helper function that returns a *DB. No database drivers are included
-// in the Go standard library. See https://golang.org/s/sqldrivers for
-// a list of third-party drivers.
-//
-// Open may just validate its arguments without creating a connection
-// to the database. To verify that the data source name is valid, call
-// Ping.
-// The returned DB is safe for concurrent use by multiple goroutines
-// and maintains its own pool of idle connections. Thus, the Open
-// function should be called just once. It is rarely necessary to
-// close a DB.
-func (db XuguDriver) Open(dsn string) (driver.Conn, error) {
-
-	conn := &connector{dsn: dsn}
-	return conn.Connect(context.Background())
-}
-
-func (db XuguDriver) OpenConnector(dsn string) (driver.Connector, error) {
-
-	return &connector{dsn: dsn}, nil
-}

+ 0 - 229
pkg/xugu/xugu/xugu_fields.go

@@ -1,229 +0,0 @@
-package xugu
-
-import (
-	"database/sql"
-	"reflect"
-	"time"
-)
-
-type FieldDescri struct {
-	FieldNameLen    int
-	FieldName       string
-	FieldType       fieldType
-	FieldPreciScale fieldPreciScaleInfo
-	FieldFlag       uint32
-}
-
-type fieldPreciScaleInfo struct {
-	scale    uint16
-	accuracy uint16
-}
-
-type fieldType uint16
-
-const (
-	fieldType_EMPTY fieldType = iota + 0x00
-	fieldType_NULL
-	fieldType_BOOL
-	fieldType_I1
-	fieldType_I2
-	fieldType_I4
-	fieldType_I8
-	fieldType_NUM
-	fieldType_R4
-	fieldType_R8
-
-	fieldType_DATE
-	fieldType_TIME
-	fieldType_TIME_TZ
-	fieldType_DATETIME
-	fieldType_DATETIME_TZ
-
-	fieldType_INTERVAL_Y
-	fieldType_INTERVAL_Y2M
-	fieldType_INTERVAL_M
-
-	fieldType_INTERVAL_D
-	fieldType_INTERVAL_D2H
-	fieldType_INTERVAL_H
-	fieldType_INTERVAL_D2M
-	fieldType_INTERVAL_H2M
-	fieldType_INTERVAL_MI
-	fieldType_INTERVAL_D2S
-	fieldType_INTERVAL_H2S
-	fieldType_INTERVAL_M2S
-	fieldType_INTERVAL_S
-
-	fieldType_ROWVER
-	fieldType_GUID
-	fieldType_CHAR
-	fieldType_NCHAR
-	fieldType_CLOB
-
-	fieldType_BINARY
-	fieldType_BLOB
-
-	fieldType_GEOM
-	fieldType_POINT
-	fieldType_BOX
-	fieldType_POLYLINE
-	fieldType_POLYGON
-
-	fieldType_BLOB_I
-	fieldType_BLOB_S
-	fieldType_BLOB_M
-	fieldType_BLOB_OM
-	fieldType_STREAM
-	fieldType_ROWID
-	fieldType_SIBLING
-	fieldType_MAX_SYS fieldType = 47
-
-	fieldType_BLADE_BEGIN fieldType = 101
-	fieldType_BLADE_END   fieldType = 1000
-
-	fieldType_OBJECT fieldType = 1001 // object type
-	fieldType_REFROW
-	fieldType_RECORD  // record type
-	fieldType_VARRAY  // array type
-	fieldType_TABLE   // table type
-	fieldType_ITABLE  // Idxby table
-	fieldType_CURSOR  // involved ref-record type (cannot change)
-	fieldType_REFCUR  // REF_CURSOR type
-	fieldType_ROWTYPE // ref row type
-	fieldType_COLTYPE // ref column type
-	fieldType_CUR_REC
-	fieldType_PARAM
-)
-
-/* {{ */
-func (self *FieldDescri) typeDatabaseName() string {
-	switch self.FieldType {
-	case fieldType_BOOL:
-		return "BOOLEAN"
-	case fieldType_CHAR, fieldType_NCHAR:
-		return "CHAR"
-	case fieldType_I1:
-		return "TINYINT"
-	case fieldType_I2:
-		return "SHORT"
-	case fieldType_I4:
-		return "INTEGER"
-	case fieldType_I8:
-		return "BIGINT"
-	case fieldType_R4:
-		return "FLOAT"
-	case fieldType_R8:
-		return "DOUBLE"
-	case fieldType_NUM:
-		return "NUMERIC"
-	case fieldType_DATE:
-		return "DATE"
-	case fieldType_TIME:
-		return "TIME"
-	case fieldType_TIME_TZ:
-		return "TIMEZONE"
-	case fieldType_DATETIME:
-		return "DATETIME"
-	case fieldType_DATETIME_TZ:
-		return "DATETIME TIMEZONE"
-	case fieldType_BINARY:
-		return "BINARY"
-	case fieldType_INTERVAL_Y:
-		return "INTERVAL YEAR"
-	case fieldType_INTERVAL_Y2M:
-		return "INTERVAL YEAR TO MONTH"
-	case fieldType_INTERVAL_D2S:
-		return "INTERVAL DAY TO SECOND"
-	case fieldType_CLOB:
-		return "CLOB"
-	case fieldType_BLOB:
-		return "BLOB"
-	default:
-		return ""
-	}
-}
-
-/* {{ */
-func (self *FieldDescri) scanType() reflect.Type {
-
-	switch self.FieldType {
-
-	case fieldType_BOOL:
-		return scanTypeBool
-	case fieldType_I1:
-		return scanTypeInt8
-	case fieldType_I2:
-		return scanTypeInt16
-	case fieldType_I4:
-		return scanTypeInt32
-	case fieldType_I8:
-		return scanTypeInt64
-	case fieldType_R4:
-		return scanTypeFloat32
-	case fieldType_R8:
-		return scanTypeFloat64
-	case fieldType_DATE,
-		fieldType_TIME,
-		fieldType_DATETIME:
-		return scanTypeNullTime
-	case fieldType_TIME_TZ,
-		fieldType_DATETIME_TZ,
-		fieldType_CHAR,
-		fieldType_NCHAR,
-		fieldType_BINARY,
-		//fieldTypeInterval,
-		fieldType_NUM,
-		fieldType_INTERVAL_Y2M,
-		fieldType_INTERVAL_D2S,
-		//fieldTypeLob,
-		fieldType_CLOB,
-		fieldType_BLOB:
-		return scanTypeRawBytes
-	default:
-		return scanTypeUnknown
-
-	}
-}
-
-var (
-	scanTypeFloat32   = reflect.TypeOf(float32(0))
-	scanTypeFloat64   = reflect.TypeOf(float64(0))
-	scanTypeNullFloat = reflect.TypeOf(sql.NullFloat64{})
-	scanTypeNullInt   = reflect.TypeOf(sql.NullInt64{})
-	scanTypeNullTime  = reflect.TypeOf(time.Time{})
-	scanTypeInt8      = reflect.TypeOf(int8(0))
-	scanTypeInt16     = reflect.TypeOf(int16(0))
-	scanTypeInt32     = reflect.TypeOf(int32(0))
-	scanTypeInt64     = reflect.TypeOf(int64(0))
-	scanTypeUnknown   = reflect.TypeOf(new(interface{}))
-	scanTypeRawBytes  = reflect.TypeOf(sql.RawBytes{})
-	scanTypeUint8     = reflect.TypeOf(uint8(0))
-	scanTypeUint16    = reflect.TypeOf(uint16(0))
-	scanTypeUint32    = reflect.TypeOf(uint32(0))
-	scanTypeUint64    = reflect.TypeOf(uint64(0))
-	scanTypeBool      = reflect.TypeOf(bool(false))
-)
-
-// func processValue(value driver.Value) {
-// 	switch v := value.(type) {
-// 	case nil:
-// 		fmt.Println("Value is nil")
-// 	case int64:
-// 		fmt.Printf("Value is int64: %d\n", v)
-// 	case float64:
-// 		fmt.Printf("Value is float64: %f\n", v)
-// 	case bool:
-// 		fmt.Printf("Value is bool: %t\n", v)
-// 	case []byte:
-// 		fmt.Printf("Value is []byte: %x\n", v)
-// 	case string:
-// 		fmt.Printf("Value is string: %s\n", v)
-// 	case time.Time:
-// 		fmt.Printf("Value is time.Time: %s\n", v)
-// 	case driver.Rows:
-// 		fmt.Println("Value is driver.Rows")
-// 		// You can further process the rows here
-// 	default:
-// 		fmt.Println("Unknown type")
-// 	}
-// }

+ 0 - 82
pkg/xugu/xugu/xugu_model.go

@@ -1,82 +0,0 @@
-package xugu
-
-type SelectResult struct {
-	Field_Num uint32
-	Fields    []FieldDescri
-	Values    [][]FieldValue //[字段][字段所有值]
-	rowIdx    int
-	fad       *FormArgDescri
-	next      *SelectResult
-}
-type FormArgDescri struct {
-	ArgNum uint32
-	Args   []ArgDescri
-}
-
-type FieldValue struct {
-	Col_len  uint32
-	Col_Data []byte
-}
-
-type InsertResult struct {
-	RowidLen  uint32
-	RowidData []byte
-}
-
-type UpdateResult struct {
-	UpdateNum uint32
-}
-
-type DeleteResult struct {
-	DeleteNum uint32
-}
-
-type ProcRet struct {
-	RetDType   uint32
-	RetDataLen uint32
-	RetData    []byte
-}
-
-type OutParamRet struct {
-	OutParamNo    uint32
-	OutParamDType uint32
-	OutParamLen   uint32
-	OutParamData  []byte
-}
-
-type ErrInfo struct {
-	ErrStrLen uint32
-	ErrStr    []byte
-}
-
-type WarnInfo struct {
-	WarnStrLen uint32
-	WarnStr    []byte
-}
-
-type Message struct {
-	MsgStrLen uint32
-	MsgStr    []byte
-}
-
-type ArgDescri struct {
-	ArgNameLen    uint32
-	ArgName       []byte
-	ArgNo         uint32
-	ArgDType      uint32
-	ArgPreciScale uint32
-}
-
-type allResult struct {
-	rt msgType
-	s  *SelectResult
-	i  *InsertResult
-	u  *UpdateResult
-	d  *DeleteResult
-	p  *ProcRet
-	o  *OutParamRet
-	e  *ErrInfo
-	w  *WarnInfo
-	m  *Message
-	f  *FormArgDescri
-}

+ 0 - 512
pkg/xugu/xugu/xugu_parse.go

@@ -1,512 +0,0 @@
-package xugu
-
-import (
-	"database/sql/driver"
-	"encoding/binary"
-	"errors"
-	"fmt"
-	"strconv"
-	"strings"
-	"time"
-)
-
-type xuguValue struct {
-	// 布尔值,如果值为 true,表示当前字段的数据类型是大对象数据类型
-	islob bool
-	//字段名
-	paramName []byte
-	//paramNameLength [2]byte
-	// 字段的实际值
-	value []byte
-	// 值的长度
-	valueLength int
-	// 字段的类型
-	types fieldType
-}
-
-// 判断参数个数
-func assertParamCount(query string) int {
-
-	paramCount := strings.Count(query, "?")
-
-	return paramCount
-}
-func assertParamType(dV driver.Value, values *[]xuguValue) error {
-	var dest xuguValue
-	switch srcv := dV.(type) {
-
-	case int64:
-		buf := make([]byte, 8)
-		binary.BigEndian.PutUint64(buf, uint64(srcv))
-		dest.value = buf
-		dest.valueLength = 8
-		dest.islob = false
-		dest.types = fieldType_I8
-
-	case float64:
-		S := strconv.FormatFloat(srcv, 'f', 15, 64)
-		dest.value = []byte(S)
-		dest.valueLength = len(S)
-		dest.islob = false
-		dest.types = fieldType_CHAR
-
-	case bool:
-		//S := strconv.FormatBool(srcv)
-		var tmp []byte
-		if srcv {
-			tmp = []byte{1}
-		} else {
-			tmp = []byte{0}
-		}
-
-		dest.value = []byte(tmp)
-		dest.valueLength = 1
-		dest.islob = false
-		dest.types = fieldType_BOOL
-
-	case string:
-		dest.value = []byte(srcv)
-		dest.valueLength = len(srcv)
-		dest.islob = false
-		dest.types = fieldType_CHAR
-		if dest.valueLength == 0 {
-			dest.valueLength = 1
-			dest.value = []byte{0}
-		}
-
-	case time.Time:
-		tm := fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d",
-			srcv.Year(), int(srcv.Month()), srcv.Day(),
-			srcv.Hour(), srcv.Minute(), srcv.Second())
-		dest.value = []byte(tm)
-		//	dest.valueLength = strings.Count(tm, "") - 1
-		dest.valueLength = len(tm)
-		dest.islob = false
-		//dest.types = fieldType_TIME
-		dest.types = fieldType_CHAR
-	case []byte:
-		dest.value = srcv
-		dest.valueLength = len(srcv)
-		dest.islob = true
-		dest.types = fieldType_BLOB
-		if dest.valueLength == 0 {
-			dest.valueLength = 1
-			dest.value = []byte{0}
-		}
-
-	case nil:
-		dest.value = nil
-		dest.valueLength = 0
-		dest.islob = false
-		dest.types = fieldType_NULL
-
-	default:
-		return errors.New("unknown data type")
-	}
-
-	*values = append(*values, dest)
-	return nil
-}
-
-func parseMsg(readBuf *buffer, pConn *xuguConn) (*allResult, error) {
-	var err error
-	aR := allResult{}
-	for {
-		char := readBuf.peekChar()
-		switch char {
-
-		case 'K':
-			readBuf.reset()
-			return &aR, nil
-
-		case '$':
-			readBuf.idx++
-			if aR.f, err = parseFormArgDescri(readBuf); err != nil {
-				return nil, err
-			}
-			aR.rt = formArgDescri
-			return &aR, err
-		case 'A':
-			readBuf.idx++
-			if aR.s, err = parseSelectResult(readBuf); err != nil {
-				return nil, err
-			}
-			aR.rt = selectResult
-			return &aR, err
-
-		case 'I':
-			readBuf.idx++
-			if aR.i, err = parseInsertResult(readBuf); err != nil {
-				return nil, err
-			}
-			aR.rt = insertResult
-			return &aR, err
-
-		case 'U':
-			readBuf.idx++
-			if aR.u, err = parseUpdateResult(readBuf); err != nil {
-				return nil, err
-			}
-			aR.rt = updateResult
-			return &aR, err
-
-		case 'D':
-			readBuf.idx++
-			readBuf.idx++
-			if aR.d, err = parseDeleteResult(readBuf); err != nil {
-				return nil, err
-			}
-			aR.rt = deleteResult
-			return &aR, err
-
-		case 'E':
-			readBuf.idx++
-
-			if aR.e, err = parseErrInfo(readBuf); err != nil {
-				return nil, err
-			}
-			pConn.errStr = aR.e.ErrStr
-			aR.rt = errInfo
-			return &aR, err
-
-		case 'W':
-			readBuf.idx++
-			if aR.w, err = parseWarnInfo(readBuf); err != nil {
-				return nil, err
-			}
-			aR.rt = warnInfo
-			return &aR, err
-
-		case 'M':
-			readBuf.idx++
-			if aR.m, err = parseMessage(readBuf); err != nil {
-				return nil, err
-			}
-			aR.rt = message
-			return &aR, err
-
-		default:
-			return nil, errors.New("parseMsg: unknown message type")
-		}
-
-	}
-}
-func parseSelectResult(readBuf *buffer) (*SelectResult, error) {
-	data := &SelectResult{}
-
-	char := readBuf.peekChar()
-
-	//Field_Num
-	fn, err := readBuf.readNext(4, true)
-	if err != nil {
-		return nil, err
-	}
-
-	Field_Num := binary.LittleEndian.Uint32(fn)
-	data.Field_Num = Field_Num
-	data.rowIdx = 0
-
-	//获取字段信息
-	for i := 0; i < int(Field_Num); i++ {
-
-		field := FieldDescri{}
-
-		//Field_Name_Len
-		Field_Name_Len, err := readBuf.readNext(4, true)
-		if err != nil {
-			return nil, err
-		}
-		field.FieldNameLen = int(binary.LittleEndian.Uint32(Field_Name_Len))
-
-		//Field_Name:
-		Field_Name, err := readBuf.readNext(field.FieldNameLen, false)
-		if err != nil {
-			return nil, err
-		}
-		field.FieldName = string(Field_Name)
-
-		//Field_DType:
-		Field_DType, err := readBuf.readNext(4, true)
-		if err != nil {
-			return nil, err
-		}
-		field.FieldType = fieldType(binary.LittleEndian.Uint32(Field_DType))
-
-		//Field_Preci_Scale:
-		Field_Preci_Scale, err := readBuf.readNext(4, true)
-		if err != nil {
-			return nil, err
-		}
-
-		fieldPreciScale := binary.LittleEndian.Uint32(Field_Preci_Scale)
-		if int32(fieldPreciScale) <= 0 {
-			field.FieldPreciScale = fieldPreciScaleInfo{
-				scale:    0,
-				accuracy: 0,
-			}
-		} else {
-			field.FieldPreciScale = fieldPreciScaleInfo{
-				scale:    uint16(fieldPreciScale >> 16),
-				accuracy: uint16(fieldPreciScale & 0xFFFF),
-			}
-		}
-
-		//Field_Flag:
-		Field_Flag, err := readBuf.readNext(4, true)
-		if err != nil {
-			return nil, err
-		}
-		field.FieldFlag = binary.LittleEndian.Uint32(Field_Flag)
-		data.Fields = append(data.Fields, field)
-	}
-
-	data.Values = make([][]FieldValue, data.Field_Num)
-
-	//获取字段的行值,并判断类型
-	// 使用 Peek 方法检查下一个字节是否为'R'或'K'
-
-	char = readBuf.peekChar()
-	readBuf.idx++
-	if char == 'K' {
-		return data, nil
-	} else if char == 'R' {
-		colIdx := 0
-		//typeIdx := 0
-
-		for {
-			col := FieldValue{}
-			//获取数据的大小
-			Col_len, err := readBuf.readNext(4, true)
-			if err != nil {
-				return nil, err
-			}
-			col.Col_len = binary.LittleEndian.Uint32(Col_len)
-
-			//获取数据的值
-			col.Col_Data, err = readBuf.readNext(int(col.Col_len), false)
-			if err != nil {
-				return nil, err
-			}
-			
-			data.Values[colIdx] = append(data.Values[colIdx], col)
-			colIdx++
-		
-			char := readBuf.peekChar()
-
-			//既不是R 也不是K 代表该行还有其他字段内容没有读取完成
-			if char == 'R' {
-				readBuf.idx++
-				colIdx = 0
-				continue
-			} else if char == 'K' {
-				return data, nil
-				//break
-			}
-
-		} //for end
-
-	} else if char == '$' {
-		fad, err := parseFormArgDescri(readBuf)
-		if err != nil {
-			return nil, err
-		}
-
-		char := readBuf.peekChar()
-		//既不是R 也不是K 代表该行还有其他字段内容没有读取完成
-		if char == 'K' {
-			data.fad = fad
-			return data, nil
-			//break
-		}
-
-		return nil, errors.New("select to $ 解析失败")
-	} else {
-		return nil, errors.New("解析失败")
-	}
-
-}
-
-func parseInsertResult(readBuf *buffer) (*InsertResult, error) {
-
-	//Rowid_Len
-	Rowid_L, err := readBuf.readNext(4, true)
-	if err != nil {
-		return nil, err
-	}
-	Rowid_Len := binary.LittleEndian.Uint32(Rowid_L)
-	//Rowid_Data
-	encoded, err := readBuf.readNext(int(Rowid_Len), false)
-	if err != nil {
-		return nil, err
-	}
-
-	//检测是否结束
-	char := readBuf.peekChar()
-
-	if char == 'K' {
-		return &InsertResult{
-			RowidLen:  Rowid_Len,
-			RowidData: encoded,
-		}, nil
-	}
-	return nil, errors.New("parseInsertResult error")
-}
-
-func parseUpdateResult(readBuf *buffer) (*UpdateResult, error) {
-	updatas, err := readBuf.readNext(4, true)
-	if err != nil {
-		return nil, err
-	}
-	updateNum := binary.LittleEndian.Uint32(updatas)
-
-	return &UpdateResult{UpdateNum: updateNum}, nil
-}
-
-func parseDeleteResult(readBuf *buffer) (*DeleteResult, error) {
-	deletes, err := readBuf.readNext(4, true)
-	if err != nil {
-		return nil, err
-	}
-	deleteNum := binary.LittleEndian.Uint32(deletes)
-
-	return &DeleteResult{DeleteNum: deleteNum}, nil
-}
-
-func parseProcRet(readBuf *buffer) (*ProcRet, error) {
-	retDypes, err := readBuf.readNext(4, true)
-	if err != nil {
-		return nil, err
-	}
-	retDType := binary.LittleEndian.Uint32(retDypes)
-	retDataLens, err := readBuf.readNext(4, true)
-	if err != nil {
-		return nil, err
-	}
-	retDataLen := binary.LittleEndian.Uint32(retDataLens)
-	retData, err := readBuf.readNext(int(retDataLen), false)
-	if err != nil {
-		return nil, err
-	}
-
-	return &ProcRet{RetDType: retDType, RetDataLen: retDataLen, RetData: retData}, nil
-}
-
-func parseOutParamRet(readBuf *buffer) (*OutParamRet, error) {
-	outParamNos, err := readBuf.readNext(4, true)
-	if err != nil {
-		return nil, err
-	}
-	outParamNo := binary.LittleEndian.Uint32(outParamNos)
-
-	outParamDTypes, err := readBuf.readNext(4, true)
-	if err != nil {
-		return nil, err
-	}
-	outParamDType := binary.LittleEndian.Uint32(outParamDTypes)
-
-	outParamLens, err := readBuf.readNext(4, true)
-	if err != nil {
-		return nil, err
-	}
-	outParamLen := binary.LittleEndian.Uint32(outParamLens)
-
-	outParamData, err := readBuf.readNext(int(outParamLen), false)
-	if err != nil {
-		return nil, err
-	}
-
-	return &OutParamRet{
-		OutParamNo:    outParamNo,
-		OutParamDType: outParamDType,
-		OutParamLen:   outParamLen,
-		OutParamData:  outParamData,
-	}, nil
-}
-
-func parseErrInfo(readBuf *buffer) (*ErrInfo, error) {
-	errStrLens, err := readBuf.readNext(4, true)
-	if err != nil {
-		return nil, err
-	}
-	errStrLen := binary.LittleEndian.Uint32(errStrLens)
-
-	errStr, err := readBuf.readNext(int(errStrLen), false)
-	if err != nil {
-		return nil, err
-	}
-	return &ErrInfo{ErrStrLen: errStrLen, ErrStr: errStr}, nil
-
-}
-
-func parseWarnInfo(readBuf *buffer) (*WarnInfo, error) {
-	warnStrLens, err := readBuf.readNext(4, true)
-	if err != nil {
-		return nil, err
-	}
-	warnStrLen := binary.LittleEndian.Uint32(warnStrLens)
-
-	warnStr, err := readBuf.readNext(int(warnStrLen), false)
-	if err != nil {
-		return nil, err
-	}
-	return &WarnInfo{WarnStrLen: warnStrLen, WarnStr: warnStr}, nil
-}
-
-func parseMessage(readBuf *buffer) (*Message, error) {
-	msgStrLens, err := readBuf.readNext(4, true)
-	if err != nil {
-		return nil, err
-	}
-	msgStrLen := binary.LittleEndian.Uint32(msgStrLens)
-
-	msgStr, err := readBuf.readNext(int(msgStrLen), false)
-	if err != nil {
-		return nil, err
-	}
-
-	return &Message{MsgStrLen: msgStrLen, MsgStr: msgStr}, nil
-}
-
-func parseFormArgDescri(readBuf *buffer) (*FormArgDescri, error) {
-	//	FormArgDescri:   '$' Arg_Num { Arg_Name_Len Arg_Name Arg_No Arg_DType Arg_Preci_Scale }+
-	Arg_Nums, err := readBuf.readNext(4, true)
-	if err != nil {
-		return nil, err
-	}
-	Arg_Num := binary.LittleEndian.Uint32(Arg_Nums)
-	formArgDescri := &FormArgDescri{ArgNum: Arg_Num}
-	for i := 0; i < int(Arg_Num); i++ {
-		arg := ArgDescri{}
-		//Arg_Name_Len
-		ArgNameLen, err := readBuf.readNext(4, true)
-		if err != nil {
-			return nil, err
-		}
-		arg.ArgNameLen = binary.LittleEndian.Uint32(ArgNameLen)
-		//Arg_Name
-		arg.ArgName, err = readBuf.readNext(int(arg.ArgNameLen), false)
-		if err != nil {
-			return nil, err
-		}
-		//Arg_No
-		ArgNo, err := readBuf.readNext(4, true)
-		if err != nil {
-			return nil, err
-		}
-		arg.ArgNo = binary.LittleEndian.Uint32(ArgNo)
-		//Argg_DType
-		ArgDType, err := readBuf.readNext(4, true)
-		if err != nil {
-			return nil, err
-		}
-		arg.ArgDType = binary.LittleEndian.Uint32(ArgDType)
-		//Arg_Preci_Scale
-		ArgPreciScale, err := readBuf.readNext(4, true)
-		if err != nil {
-			return nil, err
-		}
-		arg.ArgPreciScale = binary.LittleEndian.Uint32(ArgPreciScale)
-		formArgDescri.Args = append(formArgDescri.Args, arg)
-	}
-	return formArgDescri, nil
-}

+ 0 - 156
pkg/xugu/xugu/xugu_parse_time.go

@@ -1,156 +0,0 @@
-package xugu
-
-type TIMESTAMP struct {
-	year     int
-	month    int
-	day      int
-	hour     int
-	minute   int
-	second   int
-	fraction int // 毫秒
-}
-
-// 是否是闰年
-func IsLeapYear(year int) bool {
-	return (year%4 == 0 && year%100 != 0) || (year%400 == 0)
-}
-
-// 月份到天数的映射
-var mtod = [2][13]int{
-	{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}, // 非闰年
-	{0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}, // 闰年
-}
-
-func dt2dtm(t int64) TIMESTAMP {
-	var (
-		y, m, d, s int
-		mm, nn     int
-		wday       int
-		ms         int
-		rn_num     int
-	)
-
-	if t >= 0 { // 1970年以后
-		ms = int(t % 1000)
-		t /= 1000
-		s = int(t % 86400)
-		d = int(t / 86400)
-		wday = (d + 4) % 7
-		mm = d / 146097
-		nn = d % 146097
-		y = 1970 + 400*mm
-		mm = nn / 36524
-		nn = nn % 36524
-		y += 100 * mm
-		mm = nn / 1461
-		nn = nn % 1461
-		y += 4 * mm
-		if nn > 1096 {
-			y += 3
-		}
-		if nn > 730 && nn <= 1096 {
-			y += 2
-		}
-		if nn > 365 && nn <= 730 {
-			y++
-		}
-		if nn == 0 {
-			y--
-		}
-		rn_num = (y-1)/4 - (y-1)/100 + (y-1)/400
-		rn_num -= 477
-		d = d - 365*(y-1970) - rn_num
-	} else { // 1970年以前
-		ms = int(t % 1000)
-		t /= 1000
-		if ms != 0 {
-			ms += 1000
-			t--
-		}
-		s = int(t % 86400)
-		d = int(t / 86400)
-		if s != 0 {
-			s += 86400
-			d--
-		}
-		wday = (d + 4) % 7
-		if wday < 0 {
-			wday += 7
-		}
-		mm = d / 146097
-		nn = d % 146097
-		y = 1969 + 400*mm
-		mm = nn / 36524
-		nn = nn % 36524
-		y += 100 * mm
-		mm = nn / 1461
-		nn = nn % 1461
-		y += 4 * mm
-		if nn < -1096 {
-			y -= 3
-		}
-		if nn < -731 && nn >= -1096 {
-			y -= 2
-		}
-		if nn < -365 && nn >= -731 {
-			y--
-		}
-		if nn == 0 {
-			y++
-		}
-		rn_num = y/4 - y/100 + y/400
-		rn_num -= 477
-		d = d - 365*(y+1-1970) - rn_num
-		if IsLeapYear(y) {
-			d += 366
-		} else {
-			d += 365
-		}
-	}
-
-	if d < 0 {
-		y--
-		if IsLeapYear(y) {
-			d += 366
-		} else {
-			d += 365
-		}
-	}
-
-	d++
-	if IsLeapYear(y) {
-		if d > 366 {
-			d -= 366
-			y++
-		}
-	} else if d > 365 {
-		d -= 365
-		y++
-	}
-
-	if IsLeapYear(y) {
-		for m = 0; m <= 11; m++ {
-			if d > mtod[1][m] && d <= mtod[1][m+1] {
-				d -= mtod[1][m]
-				break
-			}
-		}
-	} else {
-		for m = 0; m <= 11; m++ {
-			if d > mtod[0][m] && d <= mtod[0][m+1] {
-				d -= mtod[0][m]
-				break
-			}
-		}
-	}
-
-	return TIMESTAMP{
-		year:     y,
-		month:    m + 1,
-		day:      d,
-		hour:     s / 3600,
-		minute:   (s % 3600) / 60,
-		second:   s % 60,
-		fraction: ms,
-	}
-}

+ 0 - 28
pkg/xugu/xugu/xugu_result.go

@@ -1,28 +0,0 @@
-package xugu
-
-type xuguResult struct {
-
-	// Returns the number of rows affected
-	// by update, delete and other related operations
-	affectedRows int64
-
-	// Returns the GUID number of
-	// the insert operation (not supported)
-	insertId int64
-}
-
-// LastInsertId returns the integer generated by the database
-// in response to a command. Typically this will be from an
-// "auto increment" column when inserting a new row. Not all
-// databases support this feature, and the syntax of such
-// statements varies.
-func (self *xuguResult) LastInsertId() (int64, error) {
-	return self.insertId, nil
-}
-
-// RowsAffected returns the number of rows affected by an
-// update, insert, or delete. Not every database or database
-// driver may support this.
-func (self *xuguResult) RowsAffected() (int64, error) {
-	return self.affectedRows, nil
-}

+ 0 - 166
pkg/xugu/xugu/xugu_rows.go

@@ -1,166 +0,0 @@
-package xugu
-
-import (
-	"database/sql/driver"
-	"encoding/binary"
-	"fmt"
-	"io"
-	"math"
-	"reflect"
-	"time"
-)
-
-type xuguRows struct {
-	rows_conn *xuguConn
-
-	results  *SelectResult
-	colIdx   int
-	prepared bool
-}
-
-func (row *xuguRows) Next(dest []driver.Value) error {
-
-	if row.results.rowIdx >= len(row.results.Values[0]) {
-		//return errors.New("The result set has been released")
-		return io.EOF
-	}
-	//	fmt.Println(" -- int(row.results.Field_Num)", int(row.results.Field_Num))
-
-	for j := 0; j < int(row.results.Field_Num); j++ {
-
-		coluType := row.results.Fields[j].FieldType
-		//fmt.Println("  --coluType: ", coluType, row.results.Fields[j].typeDatabaseName())
-		if len(row.results.Values[j][row.results.rowIdx].Col_Data) == 0 {
-			dest[j] = nil
-		} else {
-			switch coluType {
-
-			case fieldType_BINARY,
-				fieldType_CLOB,
-				fieldType_BLOB:
-				fmt.Println(row.results.Values[j][row.results.rowIdx].Col_Data)
-				dest[j] = row.results.Values[j][row.results.rowIdx].Col_Data
-
-			case fieldType_INTERVAL_Y, fieldType_INTERVAL_M, fieldType_INTERVAL_D,
-				fieldType_INTERVAL_H, fieldType_INTERVAL_S,
-				fieldType_INTERVAL_MI:
-				dest[j] = binary.BigEndian.Uint32(row.results.Values[j][row.results.rowIdx].Col_Data)
-
-			case fieldType_TIME,
-				fieldType_TIME_TZ:
-				timeTmp := int32(binary.BigEndian.Uint32(row.results.Values[j][row.results.rowIdx].Col_Data))
-				//tv, _ := time.Parse("2006-01-02 15:04:05", string(reverseBytes(row.results.Values[j][row.results.rowIdx].Col_Data)))
-				tv := time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC).Add((time.Millisecond * time.Duration(timeTmp)))
-				utcTime := tv.UTC()
-				dest[j] = utcTime
-			case fieldType_DATE:
-
-				timeTmp := int32(binary.BigEndian.Uint32(row.results.Values[j][row.results.rowIdx].Col_Data))
-				//tv, _ := time.Parse("2006-01-02 15:04:05", string(reverseBytes(row.results.Values[j][row.results.rowIdx].Col_Data)))
-				tv := time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC).Add((time.Hour * 24 * time.Duration(timeTmp)))
-				utcTime := tv.UTC()
-				dest[j] = utcTime
-				// fmt.Println(string(row.results.Values[j][row.results.rowIdx].Col_Data))
-				// tv, _ := time.Parse("15:04:05", string(row.results.Values[j][row.results.rowIdx].Col_Data))
-				// dest[j] = tv
-
-			case fieldType_DATETIME,
-				fieldType_DATETIME_TZ:
-
-				if len(row.results.Values[j][row.results.rowIdx].Col_Data) == 0 {
-					dest[j] = nil
-				} else {
-					//timeTmp := binary.LittleEndian.Uint64(reverseBytes(row.results.Values[j][row.results.rowIdx].Col_Data)
-					timeTmp := binary.BigEndian.Uint64(row.results.Values[j][row.results.rowIdx].Col_Data)
-					//tv, _ := time.Parse("2006-01-02 15:04:05", string(reverseBytes(row.results.Values[j][row.results.rowIdx].Col_Data)))
-					tv := time.Unix(0, int64(timeTmp)*1000000)
-					utcTime := tv.UTC()
-					dest[j] = utcTime
-				}
-
-			case fieldType_R4:
-				if len(row.results.Values[j][row.results.rowIdx].Col_Data) == 0 {
-					dest[j] = nil
-				} else {
-					dest[j] = math.Float32frombits(binary.BigEndian.Uint32(row.results.Values[j][row.results.rowIdx].Col_Data))
-				}
-
-			case fieldType_R8:
-				if len(row.results.Values[j][row.results.rowIdx].Col_Data) == 0 {
-					dest[j] = nil
-				} else {
-					dest[j] = math.Float64frombits(binary.BigEndian.Uint64(row.results.Values[j][row.results.rowIdx].Col_Data))
-				}
-			case fieldType_NUM:
-				dest[j] = row.results.Values[j][row.results.rowIdx].Col_Data
-			case fieldType_I1:
-				if len(row.results.Values[j][row.results.rowIdx].Col_Data) == 0 {
-					dest[j] = nil
-				} else {
-					dest[j] = int8(row.results.Values[j][row.results.rowIdx].Col_Data[0])
-				}
-			case fieldType_I2:
-				if len(row.results.Values[j][row.results.rowIdx].Col_Data) == 0 {
-					dest[j] = nil
-				} else {
-					dest[j] = int16(binary.BigEndian.Uint16(row.results.Values[j][row.results.rowIdx].Col_Data))
-				}
-			case fieldType_I4:
-				if len(row.results.Values[j][row.results.rowIdx].Col_Data) == 0 {
-					dest[j] = nil
-				} else {
-					dest[j] = int32(binary.BigEndian.Uint32(row.results.Values[j][row.results.rowIdx].Col_Data))
-				}
-			case fieldType_I8:
-				if len(row.results.Values[j][row.results.rowIdx].Col_Data) == 0 {
-					dest[j] = nil
-				} else {
-					dest[j] = int64(binary.BigEndian.Uint64(row.results.Values[j][row.results.rowIdx].Col_Data))
-					//dest[j] = row.results.Values[j][row.results.rowIdx].Col_Data
-				}
-			case fieldType_CHAR, fieldType_NCHAR:
-				if row.results.Values[j][row.results.rowIdx].Col_Data == nil {
-					dest[j] = string("")
-				} else if row.results.Values[j][row.results.rowIdx].Col_Data[0] == 0x00 {
-					dest[j] = string("")
-				} else {
-					dest[j] = string(row.results.Values[j][row.results.rowIdx].Col_Data)
-				}
-			default:
-
-				//填入一行的数据
-				//TODO这里长度改为一行长度
-				dest[j] = make([]byte, len(row.results.Values))
-				// Values[字段][0]
-				dest[j] = row.results.Values[j][row.results.rowIdx].Col_Data
-
-			}
-		}
-	}
-	row.results.rowIdx++
-
-	return nil
-}
-
-// Columns返回列的名字集,它的个数是从slice的长度中推断出来的。
-// 如果不知道特定的列名,应该为该条目返回一个空的字符串
-func (row *xuguRows) Columns() []string {
-
-	var columns []string
-
-	for _, v := range row.results.Fields {
-		columns = append(columns, v.FieldName)
-	}
-
-	return columns
-}
-
-func (row *xuguRows) ColumnTypeScanType(index int) reflect.Type {
-	fmt.Println(">>>>>ColumnTypeScanType ")
-
-	return row.results.Fields[index].scanType()
-}
-
-func (self *xuguRows) Close() error {
-	return nil
-}

+ 0 - 128
pkg/xugu/xugu/xugu_sock.go

@@ -1,128 +0,0 @@
-package xugu
-
-import (
-	"bytes"
-	"context"
-	"encoding/binary"
-	"errors"
-)
-
-func xgSockOpenConn(ctx context.Context, pConn *xuguConn) error {
-	//发送
-	//fmt.Printf("login   database = '%s' user = '%s'  password = '%s' version='201' ", pConn.Database, pConn.User, pConn.Password)
-	//	message := "login   database = 'SYSTEM' user = 'SYSDBA'  password = 'SYSDBA' version='201' "
-	dsnMessage := generateLoginString(pConn.dsnConfig)
-	_, err := pConn.conn.Write([]byte(dsnMessage))
-	if err != nil {
-		return errors.New("向数据库发起连接失败")
-	}
-
-	buffer := make([]byte, 1)
-	n, err := pConn.conn.Read(buffer)
-	if err != nil {
-		return errors.New("接收数据库连接失败:")
-	}
-
-	if !bytes.Equal(buffer[:n], []byte("K")) {
-		return errors.New("数据库连接失败")
-	} else {
-		return nil
-	}
-
-}
-
-func sockSendPutStatement(pConn *xuguConn, sql []byte, values *[]xuguValue, paramCount int) error {
-	if pConn.sendBuff.Len() > 0 {
-		//将缓冲区重置为空
-		pConn.sendBuff.Reset()
-	}
-	// ?
-	pConn.sendBuff.Write([]byte("?"))
-	// Comand_Len
-	sqlLength := uint32(len(sql))
-	var networkBytes [4]byte
-	binary.BigEndian.PutUint32(networkBytes[:], sqlLength)
-	pConn.sendBuff.Write(networkBytes[:])
-	//  Comand_str
-	pConn.sendBuff.Write(sql)
-	//'0' end
-	binary.BigEndian.PutUint32(networkBytes[:], 0)
-	pConn.sendBuff.Write([]byte{0})
-	// Param_num
-
-	var Param_num [4]byte
-	binary.BigEndian.PutUint32(Param_num[:], uint32(paramCount))
-	pConn.sendBuff.Write(Param_num[:])
-	if values != nil {
-		//当缓冲区大于8190字节时,直接发送
-		// if pConn.sendBuff.Len() > 8190 {
-		// 	_, err := pConn.conn.Write(pConn.sendBuff.Bytes())
-		// 	if err != nil {
-		// 		fmt.Println("sockSend Write failed: ", err)
-		// 		return err
-		// 	}
-		// }
-
-		//发送后续参数
-		//	Param_num   { Param_name_len Param_name Param_INOUT Param_DType Param_Data_Len Param_Data }
-		for _, value := range *values {
-			//Param_name_len
-			if value.paramName == nil {
-				var Param_name_len [2]byte
-				pConn.sendBuff.Write(Param_name_len[:])
-				//Param_name
-				// var Param_name []byte
-				// pConn.sendBuff.Write(Param_name)
-
-			} else {
-				var Param_name_len [2]byte
-
-				binary.BigEndian.PutUint16(Param_name_len[:], uint16(len(value.paramName)))
-				pConn.sendBuff.Write(Param_name_len[:])
-
-				//Param_name
-				pConn.sendBuff.Write(value.paramName[:])
-
-			}
-
-			//Param_INOUT
-			Param_INOUT := [2]byte{0x1}
-			pConn.sendBuff.Write(reverseBytes(Param_INOUT[:]))
-
-			//Param_DType
-			var Param_DType [2]byte
-			binary.BigEndian.PutUint16(Param_DType[:], uint16(value.types))
-			pConn.sendBuff.Write(Param_DType[:])
-			//Param_Data_Len 根据DType 修改长度
-			Param_Data_Len := make([]byte, 4)
-			binary.BigEndian.PutUint32(Param_Data_Len[:], uint32(value.valueLength))
-			pConn.sendBuff.Write(Param_Data_Len[:])
-			//Param_Data 根据DType 修改长度
-			//Param_Data := make([]byte, value.valueLength)
-			pConn.sendBuff.Write([]byte(value.value))
-		}
-
-	}
-
-	return nil
-}
-
-func sockSendExecute(pConn *xuguConn) error {
-	//	fmt.Println("SockSendExecute msg: ", pConn.sendBuff.String())
-	_, err := pConn.conn.Write(pConn.sendBuff.Bytes())
-	if err != nil {
-		return err
-	}
-	return nil
-}
-
-func xuguSockRecvMsg(pConn *xuguConn) (*allResult, error) {
-	n, _ := pConn.conn.Read(pConn.readBuff.buf)
-	pConn.readBuff.length += n
-	rs, err := parseMsg(&pConn.readBuff, pConn)
-	if err != nil {
-		return nil, err
-	}
-	pConn.readBuff.reset()
-	return rs, nil
-}

+ 0 - 143
pkg/xugu/xugu/xugu_stmt.go

@@ -1,143 +0,0 @@
-package xugu
-
-import (
-	"database/sql/driver"
-	"errors"
-)
-
-type xuguStmt struct {
-
-	// 上下文连接句柄指针
-	stmt_conn *xuguConn
-
-	// 布尔值,用于标识执行的 SQL 语句是否已准备好
-	prepared bool
-
-	// 接受准备好的 SQL 语句的代码
-	prename []byte
-
-	// 布尔值,用于标识游标是否启用
-	curopend bool
-
-	// 游标名称
-	curname []byte
-
-	// 执行的 SQL 语句中的参数数量
-	paramCount int
-	mysql      string
-
-	//需替换的参数字段信息
-	//parser *xuguParse
-}
-
-func (stmt *xuguStmt) Close() error {
-	//关闭 prepare
-	err := xuguUnPrepare(stmt.stmt_conn, stmt.stmt_conn.prepareName)
-	//释放资源
-
-	return err
-}
-
-func (stmt *xuguStmt) NumInput() int {
-
-	return assertParamCount(stmt.mysql)
-	//return 0
-}
-
-func (stmt *xuguStmt) Exec(args []driver.Value) (driver.Result, error) {
-	
-
-	stmt.stmt_conn.mu.Lock()
-	defer stmt.stmt_conn.mu.Unlock()
-	//send msg
-	//如果有参数
-	if stmt.paramCount > 0 && len(args) > 0 {
-		values := []xuguValue{}
-		for _, param := range args {
-			assertParamType(param, &values)
-		}
-		sockSendPutStatement(stmt.stmt_conn, stmt.prename, &values, stmt.paramCount)
-		sockSendExecute(stmt.stmt_conn)
-		//没有参数
-	} else {
-		sockSendPutStatement(stmt.stmt_conn, []byte(stmt.mysql), nil, 0)
-		sockSendExecute(stmt.stmt_conn)
-	}
-
-	//recv msg
-	aR, err := xuguSockRecvMsg(stmt.stmt_conn)
-	if err != nil {
-		return nil, err
-	}
-	switch aR.rt {
-	case selectResult:
-
-		return nil, errors.New("exec is Query error")
-	case updateResult:
-		return &xuguResult{affectedRows: int64(aR.u.UpdateNum), insertId: int64(0)}, nil
-	case insertResult:
-		return &xuguResult{affectedRows: int64(0), insertId: int64(aR.i.RowidLen)}, nil
-	case errInfo:
-
-		return nil, errors.New(string(aR.e.ErrStr))
-	case warnInfo:
-
-		return nil, errors.New(string(aR.w.WarnStr))
-	default:
-		return &xuguResult{
-			affectedRows: int64(0),
-			insertId:     int64(0),
-		}, nil
-	}
-
-}
-
-func (stmt *xuguStmt) Query(args []driver.Value) (driver.Rows, error) {
-
-	
-	stmt.stmt_conn.mu.Lock()
-	defer stmt.stmt_conn.mu.Unlock()
-
-	//send msg
-	//如果有参数
-	if stmt.paramCount > 0 && len(args) > 0 {
-		values := []xuguValue{}
-		for _, param := range args {
-			assertParamType(param, &values)
-		}
-		sockSendPutStatement(stmt.stmt_conn, stmt.prename, &values, stmt.paramCount)
-		sockSendExecute(stmt.stmt_conn)
-		//没有参数
-	} else {
-		sockSendPutStatement(stmt.stmt_conn, []byte(stmt.mysql), nil, 0)
-		sockSendExecute(stmt.stmt_conn)
-	}
-
-	//recv msg
-	aR, err := xuguSockRecvMsg(stmt.stmt_conn)
-	if err != nil {
-		return nil, err
-	}
-
-	switch aR.rt {
-	case selectResult:
-
-		rows := &xuguRows{
-			rows_conn: stmt.stmt_conn,
-			results:   aR.s,
-			colIdx:    0,
-			prepared:  false,
-		}
-
-		return rows, nil
-	case errInfo:
-
-		return nil, errors.New(string(aR.e.ErrStr))
-	case warnInfo:
-
-		return nil, errors.New(string(aR.w.WarnStr))
-	default:
-	}
-
-	return nil, errors.New("xugu Query error")
-}

+ 0 - 36
pkg/xugu/xugu/xugu_tranx.go

@@ -1,36 +0,0 @@
-package xugu
-
-import "errors"
-
-type xuguTx struct {
-	tconn *xuguConn
-}
-
-func (tx *xuguTx) Commit() error {
-	tx.tconn.mu.Lock()
-	defer tx.tconn.mu.Unlock()
-
-	if tx.tconn == nil {
-		return errors.New("invalid connection")
-	}
-	_, err := tx.tconn.exec("commit;", nil)
-	if err != nil {
-		return err
-	}
-	return nil
-}
-
-func (tx *xuguTx) Rollback() error {
-	tx.tconn.mu.Lock()
-	defer tx.tconn.mu.Unlock()
-
-	if tx.tconn == nil {
-		return errors.New("invalid connection")
-	}
-	_, err := tx.tconn.exec("rollback;", nil)
-	if err != nil {
-		return err
-	}
-
-	return err
-}

+ 0 - 189
pkg/xugu/xugu/xugu_utils.go

@@ -1,189 +0,0 @@
-package xugu
-
-import (
-	"encoding/binary"
-	"fmt"
-	"strings"
-)
-
-func parseDSN(dsn string) dsnConfig {
-	// Initialize a dsnConfig struct
-	var config dsnConfig
-
-	// Split the string by semicolons
-	pairs := strings.Split(dsn, ";")
-
-	// Iterate over the pairs and map them to the struct fields
-	for _, pair := range pairs {
-		// Split each pair by the equals sign
-		kv := strings.SplitN(pair, "=", 2)
-		if len(kv) != 2 {
-			continue
-		}
-		key, value := strings.TrimSpace(kv[0]), strings.Trim(strings.TrimSpace(kv[1]), "'")
-		keyL := strings.ToLower(key)
-
-		// Map the key to the appropriate struct field
-		switch keyL {
-		case "ip":
-			config.IP = value
-		case "port":
-			config.Port = value
-		case "db":
-			config.Database = value
-		case "user":
-			config.User = value
-		case "pwd":
-			config.Password = value
-		case "encryptor":
-			config.Encryptor = value
-		case "char_set":
-			config.CharSet = value
-		case "time_zone":
-			config.TimeZone = value
-		case "iso_level":
-			config.IsoLevel = value
-		case "lock_timeout":
-			config.LockTimeout = value
-		case "auto_commit":
-			config.AutoCommit = value
-		case "strict_commit":
-			config.StrictCommit = value
-		case "result":
-			config.Result = value
-		case "return_schema":
-			config.ReturnSchema = value
-		case "return_cursor_id":
-			config.ReturnCursorID = value
-		case "lob_ret":
-			config.LobRet = value
-		case "return_rowid":
-			config.ReturnRowid = value
-		case "version":
-			config.Version = value
-		}
-	}
-
-	return config
-}
-
-func generateLoginString(config dsnConfig) string {
-	baseString := "login   database = '%s' user = '%s'  password = '%s' "
-	additionalParams := ""
-
-	if config.Encryptor != "" {
-		additionalParams += fmt.Sprintf(" encryptor='%s'", config.Encryptor)
-	}
-	if config.CharSet != "" {
-		additionalParams += fmt.Sprintf(" char_set='%s'", config.CharSet)
-	}
-	if config.TimeZone != "" {
-		additionalParams += fmt.Sprintf(" time_zone='%s'", config.TimeZone)
-	}
-	if config.IsoLevel != "" {
-		additionalParams += fmt.Sprintf(" iso_level='%s'", config.IsoLevel)
-	}
-	if config.LockTimeout != "" {
-		additionalParams += fmt.Sprintf(" lock_timeout='%s'", config.LockTimeout)
-	}
-	if config.AutoCommit != "" {
-		additionalParams += fmt.Sprintf(" auto_commit='%s'", config.AutoCommit)
-	}
-	if config.StrictCommit != "" {
-		additionalParams += fmt.Sprintf(" strict_commit='%s'", config.StrictCommit)
-	}
-	if config.Result != "" {
-		additionalParams += fmt.Sprintf(" result='%s'", config.Result)
-	}
-	if config.ReturnSchema != "" {
-		additionalParams += fmt.Sprintf(" return_schema='%s'", config.ReturnSchema)
-	}
-	if config.ReturnCursorID != "" {
-		additionalParams += fmt.Sprintf(" return_cursor_id='%s'", config.ReturnCursorID)
-	}
-	if config.LobRet != "" {
-		additionalParams += fmt.Sprintf(" lob_ret='%s'", config.LobRet)
-	}
-	if config.ReturnRowid != "" {
-		additionalParams += fmt.Sprintf(" return_rowid='%s'", config.ReturnRowid)
-	}
-	if config.Version != "" {
-		additionalParams += fmt.Sprintf(" version='%s'", config.Version)
-	} else {
-		additionalParams += " version='201'"
-	}
-
-	finalString := fmt.Sprintf(baseString, config.Database, config.User, config.Password)
-	if additionalParams != "" {
-		finalString += additionalParams
-	}
-	//finalString += " version='201'"
-	return finalString
-}
-
-// reverseBytes 反转 byte slice 的顺序
-func reverseBytes(b []byte) []byte {
-	reversed := make([]byte, len(b))
-	for i := range b {
-		reversed[i] = b[len(b)-1-i]
-	}
-	return reversed
-}
-
-func gt(name string) {
-	fmt.Printf("\n=============%s================\n", name)
-}
-
-func switchSQLType(sql string) int {
-	// 去掉首尾的空格、换行符和回车符
-	sql = strings.TrimSpace(sql)
-	if len(sql) < 6 {
-		return SQL_OTHER
-	}
-
-	// 取前6个字符并转为大写
-	kstr := strings.ToUpper(sql[:6])
-
-	// 根据SQL语句前缀判断类型
-	switch {
-	case strings.HasPrefix(kstr, "SELECT"):
-		if strings.Contains(sql, ";") && len(sql[strings.Index(sql, ";"):]) > 5 {
-			return SQL_OTHER // 多结果集
-		}
-		return SQL_SELECT
-	case strings.HasPrefix(kstr, "INSERT"):
-		return SQL_INSERT
-	case strings.HasPrefix(kstr, "UPDATE"):
-		return SQL_UPDATE
-	case strings.HasPrefix(kstr, "DELETE"):
-		return SQL_DELETE
-	case strings.HasPrefix(kstr, "CREATE"):
-		return SQL_CREATE
-	case strings.HasPrefix(kstr, "ALTER "):
-		return SQL_ALTER
-	case strings.HasPrefix(kstr, "EXEC "):
-		return SQL_PROCEDURE
-	case strings.HasPrefix(kstr, "EXECUT"):
-		return SQL_PROCEDURE
-	case strings.HasPrefix(kstr, "STC"):
-		return SQL_SELECT
-	default:
-		return SQL_OTHER
-	}
-}
-
-// CheckEndian 判断机器的字节序
-func CheckEndian() bool {
-	var i int32 = 0x01020304
-	b := [4]byte{}
-
-	// 将整数值写入字节切片
-	binary.BigEndian.PutUint32(b[:], uint32(i))
-
-	// 判断字节序
-	if b[0] == 0x01 {
-		return false // 大端字节序
-	} else {
-		return true // 小端字节序
-	}
-}

+ 3904 - 0
static/license_info/bakup/license_info copy 主行分页展示版本.html

@@ -0,0 +1,3904 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>License 分发管理</title>
+    <style>
+        /* 保持样式不变 */
+        body {
+            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+            background-color: #f8f8f8;
+            margin: 0;
+            padding: 0;
+            display: flex;
+        }
+header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 20px;
+    background-color: #1169ff;
+    color: white;
+    width: 100%;
+    margin: 0; /* 移除上下左右的空隙 */
+    box-sizing: border-box; /* 确保padding不会影响元素的宽度 */
+    border: 4px solid #1169ff; /* 确保红框颜色填满 */
+    border-width: 4px 0 4px 0; /* 只显示上下的红框颜色 */
+    border-radius: 10px; /* 设置圆角 */
+  
+}
+
+h1 {
+            font-size: 24px;
+            font-weight: 600;
+            margin: 0;
+        }
+
+    .CaptureLicenseOnce-button {
+   background-color: white;
+    color: #007aff;
+    border: 2px solid #007aff;
+    padding: 10px 20px;
+    border-radius: 10px;
+    cursor: pointer;
+    font-size: 16px;
+    font-weight: 500;
+    box-shadow: none;
+    transition: background-color 0.3s, color 0.3s;
+    float: right; /* 将按钮移动到左侧 */
+}
+
+.CaptureLicenseOnce-button:hover {
+    background-color: #0eb3ff;
+    color: white;
+}
+        
+      
+/* 扁平风格的选择框 */
+.flat-select {
+    background-color: transparent; /* 背景色透明 */
+    border: 1px solid #007aff; /* 边框颜色与按钮一致 */
+    border-radius: 15px; /* 圆角边框 */
+    padding: 5px 10px; /* 内边距 */
+    font-size: 16px; /* 字体大小 */
+    color: #007aff; /* 文字颜色 */
+    outline: none; /* 移除默认的焦点样式 */
+    appearance: none; /* 移除默认的下拉箭头样式 */
+    -webkit-appearance: none; /* 对部分浏览器的兼容性处理 */
+    -moz-appearance: none; /* 对部分浏览器的兼容性处理 */
+    cursor: pointer; /* 鼠标指针样式 */
+    margin-left: 10px; /* 添加左边距,使选择框与前面的元素有些距离 */
+    box-shadow: none; /* 移除阴影效果 */
+    transition: background-color 0.3s, color 0.3s; /* 添加过渡效果 */
+}
+
+/* 选择框在悬停时的效果 */
+.flat-select:hover {
+    background-color: #007aff; /* 背景色变为蓝色 */
+    color: white; /* 文字颜色变为白色 */
+}
+
+/* 选择框在聚焦时的效果 */
+.flat-select:focus {
+    background-color: #e0f7ff; /* 聚焦时的背景颜色 */
+    border-color: #0056b3; /* 聚焦时的边框颜色 */
+}
+
+/* 添加下拉箭头的样式 */
+.flat-select::after {
+    content: '▼'; /* 使用字符作为下拉箭头 */
+    position: absolute;
+    right: 10px;
+    pointer-events: none; /* 确保下拉箭头不会遮挡选择框 */
+}
+
+
+/* 扁平圆角风格的按钮样式 */
+.flat-rounded-button {
+    background-color: white;
+    color: #007aff;
+    border: 2px solid #007aff;
+    padding: 10px 20px;
+    border-radius: 15px;
+    cursor: pointer;
+    font-size: 16px;
+    font-weight: 500;
+    box-shadow: none; /* 移除阴影效果 */
+    transition: background-color 0.3s, color 0.3s;
+    margin: 5px; /* 添加按钮之间的间距 */
+}
+
+/* 悬停时的效果 */
+.flat-rounded-button:hover {
+    background-color: #0eb3ff;
+    color: white;
+}
+
+/*lic 展示页 主行展开后变化*/
+.expanded-row {
+    background-color: #0eb3ff; /* 展开时的背景颜色 */
+    border-color: #04aa9c; /* 展开时的边框颜色 */
+    border-width: 4px; /* 边框宽度 */
+    border-radius: 35px; /* 边框圆角 */
+    transition: all 0.2s ease; /* 添加过渡效果 */
+}
+
+/*苹果风格模态框*/
+.apple-modal-content {
+    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+    padding: 20px;
+    border-radius: 12px;
+    background-color: white;
+    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
+    margin-bottom: 20px;
+    min-width: 250px; /* 添加最小宽度 */
+}
+
+.apple-modal-title {
+    font-size: 24px;
+    font-weight: 600;
+    color: #333;
+    margin-bottom: 10px;
+}
+
+.apple-modal-subtitle {
+    font-size: 20px;
+    font-weight: 500;
+    color: #555;
+    margin-top: 20px;
+    margin-bottom: 10px;
+}
+/* 苹果风格的文字样式 */
+.apple-modal-text {
+    font-size: 16px;
+    color: #555;
+    line-height: 1.6;
+    margin-bottom: 10px;
+    
+}
+
+/* 强调部分 */
+.apple-modal-text strong {
+    color: #333;
+}
+
+/* 苹果风格的模态框关闭按钮 */
+
+.apple-close {
+    color: #aaa;
+    font-size: 24px; /* 调整字体大小 */
+    font-weight: bold;
+    cursor: pointer;
+    transition: color 0.3s;
+}
+
+.apple-close:hover {
+    color: black;
+}
+
+/* 苹果风格的退出按钮 */
+.apple-logout-button {
+    background-color: #007aff; /* 苹果风格的蓝色 */
+    color: white;
+    border: none;
+    padding: 8px 16px; /* 减小内边距,使按钮更小 */
+    border-radius: 10px; /* 圆角稍微减小 */
+    font-size: 14px; /* 调整字体大小 */
+    font-weight: 500;
+    cursor: pointer;
+    transition: background-color 0.3s, transform 0.3s;
+}
+
+.apple-logout-button:hover {
+    background-color: #005bb5; /* 悬停时按钮颜色变深 */
+    transform: scale(1.05); /* 悬停时按钮略微放大 */
+}
+
+.apple-logout-button:active {
+    background-color: #003e7e; /* 点击时按钮颜色更深 */
+    transform: scale(0.98); /* 点击时按钮略微缩小 */
+}
+
+
+.upload-button, .user-button {
+            background-color: white;
+            color: #007aff;
+            border: none;
+            padding: 10px 20px;
+            border-radius: 10px;
+            cursor: pointer;
+            font-size: 16px;
+            font-weight: 500;
+            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+            transition: background-color 0.3s;
+        }
+ .upload-button:hover, .user-button:hover {
+            background-color: #f0f0f0;
+        }
+        .sidebar {
+            width: 200px;
+            background-color: #f1f1f1;
+            padding: 20px;
+            box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
+            height: 100vh;
+            transition: transform 0.3s ease;
+        }
+        .sidebar.hidden {
+            transform: translateX(-100%);
+        }
+        .sidebar button {
+            width: 100%;
+            padding: 10px;
+            margin-bottom: 10px;
+            background-color: white;
+            color: #007aff;
+            border: none;
+            border-radius: 10px;
+            cursor: pointer;
+            font-size: 16px;
+            font-weight: 500;
+            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+            transition: background-color 0.3s;
+        }
+        .sidebar button:hover {
+            background-color: #e0e0e0;
+        }
+        .content {
+            flex-grow: 1;
+            padding: 20px;
+            transition: margin-left 0.3s ease;
+        }
+        .content.expanded {
+            margin-left: -200px;
+        }
+        .toggle-sidebar {
+    position: fixed;
+    top: 50%; /* 垂直居中 */
+    transform: translateY(-50%); /* 调整为居中对齐 */
+    left: 0; /* 靠左对齐 */
+    background-color: #007aff;
+    color: white;
+    border: none;
+    padding: 10px;
+    cursor: pointer;
+    border-radius: 0 10px 10px 0; /* 右侧圆角,左侧保持平直 */
+    width: 30px; /* 长条形箭头的宽度 */
+    height: 60px; /* 长条形箭头的高度 */
+    z-index: 1000;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    transition: all 0.3s ease; /* 添加动效 */
+}
+
+.sidebar.hidden + .content .toggle-sidebar {
+            left: 0;
+            transform: translateX(0) translateY(-50%);
+        }
+
+
+/* 按钮容器的样式 */
+.button-container {
+    display: inline-flex; /* 确保按钮在同一行 */
+    align-items: center; /* 垂直居中对齐 */
+    gap: 10px; /* 设置按钮之间的间距为10px,可以根据需要调整 */
+}
+
+/* 统一按钮的样式 */
+
+
+
+ /* 通用按钮样式 */
+.license-status-btn {
+    display: inline-block;
+    padding: 5px 15px;
+    font-size: 14px;
+    font-weight: bold;
+    color: white;
+    text-align: center;
+    border-radius: 15px; /* 圆角样式 */
+    cursor: pointer;
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* 阴影效果 */
+    transition: background-color 0.3s;
+    border: none; /* 移除按钮边框 */
+}
+
+/* 查看详情按钮的特定样式 */
+.license-status-btn.view-details {
+    background-color: #add8e6; /* 浅蓝色背景 */
+    color: white;
+}
+
+/* 下载lic按钮的特定样式 */
+.license-status-btn.download-lic {
+    background-color: #4CAF50; /* 绿色背景 */
+    color: white;
+}
+
+
+
+/* 分发按钮样式 */
+.license-status-btn.distribute {
+    background-color: #007bff; /* 蓝色背景 */
+}
+
+.license-status-btn.distribute:hover {
+    background-color: #0056b3; /* 悬停时的颜色 */
+}
+
+/* 生成按钮样式 */
+.license-status-btn.generate {
+    background-color: #ffc107; /* 黄色背景 */
+}
+
+.license-status-btn.generate:hover {
+    background-color: #e0a800; /* 悬停时的颜色 */
+}
+
+/* 默认状态下的箭头图标 */
+.arrow-icon {
+    width: 10px;
+    height: 10px;
+    border: solid #007aff;
+    border-width: 0 3px 3px 0;
+    display: inline-block;
+    padding: 3px;
+    transform: rotate(45deg); /* 初始方向向下 */
+    transition: transform 0.3s ease; /* 添加旋转过渡效果 */
+    cursor: pointer;
+}
+
+/* 当行展开时旋转箭头图标 */
+.rotate-arrow {
+    transform: rotate(-135deg); /* 旋转方向向上 */
+}
+
+
+
+/*分发历史样式*/
+#distributionHistoryModal .centered-modal-content {
+    background-color: #ffffff;
+    border: none;
+    border-radius: 15px;
+    padding: 20px;
+    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+    text-align: center;
+    width: 30%;  /* 设置宽度为屏幕的80% */
+    max-width: 90%;  /* 最大宽度设置为90%屏幕宽度 */
+    height: auto;  /* 高度自适应内容 */
+    max-height: 80vh;  /* 设置最大高度为屏幕的80% */
+    overflow-y: auto;  /* 如果内容超出则添加垂直滚动条 */
+    overflow-x: hidden;  /* 水平不出现滚动条 */
+    margin: 0 auto;  /* 居中显示 */
+}
+
+/* lic查看详细按钮样式 */
+.license-status-btn.view-details {
+    background-color: #add8e6; /* 浅蓝色背景 */
+    color: white; /* 白色文字 */
+    border: none; /* 移除按钮边框 */
+    padding: 8px 16px; /* 调整内边距,与“生成”按钮保持一致 */
+    border-radius: 20px; /* 调整圆角,确保形状一致 */
+    cursor: pointer;
+    font-size: 14px; /* 调整字体大小,确保一致 */
+    font-weight: 500; /* 字体权重,确保一致 */
+    margin-left: 10px; /* 与其他按钮的间隔 */
+    transition: background-color 0.3s, color 0.3s; /* 添加过渡效果 */
+}
+
+.license-status-btn.view-details:hover {
+    background-color: #87ceeb; /* 悬停时颜色变深 */
+}
+
+
+
+
+/* 表格样式 */
+/* 表格容器 */
+/* 表格容器 */
+
+table {
+    width: 100%;
+    border-collapse: separate;
+    border-spacing: 0 4px; /* 设置行间距为4px */
+   /*  background-color: transparent; 使表格背景透明,突出行间距效果 */
+    margin: 20px 0;
+}
+
+th, td {
+    padding: 15px 20px; /* 设置单元格内边距 */
+    text-align: left;
+    font-size: 16px;
+  /*   background-color: white; 单元格背景色 */
+   
+}
+
+
+/* 鼠标悬停时改变边框颜色 */
+tr:hover {
+    background-color: #e6f7ff; /* 设置悬停时的背景颜色 */
+    border-color: #1169ff; /* 设置悬停时的边框颜色 */
+    border-width: 4px; /* 设置悬停时的边框大小 */
+    border-radius: 15px; /* 设置悬停时的圆角 */
+}
+th {
+    background-color: #f8f8f8; /* 表头背景色 */
+    font-weight: bold;
+    color: #333;
+}
+
+td {
+    color: #555;
+}
+
+/* 表格行样式 */
+tr {
+    background-color: white; /* 每行背景色 */
+    box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1); /* 设置阴影效果 */
+    border-radius: 10px; /* 设置每行的圆角 */
+    overflow: hidden; /* 确保圆角效果生效 */
+    border: 2px solid transparent; /* 默认边框为透明 */
+    transition: all 0.3s ease; /* 添加过渡效果 */
+    
+}
+/* 确保单元格也适应行的圆角 */
+tr:first-child td:first-child {
+    border-top-left-radius: 10px;
+}
+tr:first-child td:last-child {
+    border-top-right-radius: 10px;
+}
+tr:last-child td:first-child {
+    border-bottom-left-radius: 10px;
+}
+tr:last-child td:last-child {
+    border-bottom-right-radius: 10px;
+}
+
+
+
+
+
+/*添加用户样式*/
+.flat-rounded-input {
+    width: calc(100% - 40px); /* 与其他输入框一致的宽度 */
+    padding: 15px 20px;  /* 更大的内边距 */
+    font-size: 18px;  /* 更大的字体 */
+    border: 2px solid #e0e0e0;  /* 边框颜色 */
+    border-radius: 25px;  /* 圆角边框 */
+    box-sizing: border-box;
+    margin-bottom: 20px;
+    background-color: #f8f8f8;  /* 背景色 */
+    transition: border-color 0.3s, box-shadow 0.3s;  /* 添加动画效果 */
+}
+
+.flat-rounded-input:focus {
+    border-color: #007aff;  /* 聚焦时的边框颜色 */
+    box-shadow: 0 0 8px rgba(0, 122, 255, 0.2);  /* 聚焦时的阴影效果 */
+    outline: none;  /* 去掉默认的聚焦样式 */
+}
+
+.flat-rounded-button {
+    background-color: #007aff;
+    color: white;
+    border: none;
+    padding: 15px 20px;
+    border-radius: 25px;
+    cursor: pointer;
+    font-size: 18px;
+    font-weight: bold;
+    box-shadow: none; /* 移除阴影效果 */
+    transition: background-color 0.3s, color 0.3s;
+}
+
+.flat-rounded-button:hover {
+    background-color: #005bb5;
+}
+.form-group {
+    display: flex;
+    align-items: center;
+    margin-bottom: 15px;
+}
+
+.form-group label {
+    flex: 0 0 80px; /* 固定标签宽度 */
+    text-align: right;
+    margin-right: 10px; /* 标签与输入框之间的间距 */
+}
+
+.form-group input {
+    flex: 1; /* 输入框占据剩余空间 */
+}
+
+
+
+/* 分页按钮样式 */
+.pagination button {
+    padding: 10px 20px;
+    margin: 0 5px;
+    cursor: pointer;
+    border: none;
+    border-radius: 15px;
+    background-color: #007aff;
+    color: white;
+    font-size: 16px;
+    font-weight: 500;
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+    transition: background-color 0.3s;
+}
+
+.pagination button:disabled {
+    background-color: #b0c4de;
+    cursor: not-allowed;
+}
+
+.pagination button:not(:disabled):hover {
+    background-color: #005bb5;
+}
+
+
+
+
+        .modal {
+            display: none;
+            position: fixed;
+            z-index: 1;
+            left: 0;
+            top: 0;
+            width: 100%;
+            height: 100%;
+            overflow: auto;
+            background-color: rgba(0, 0, 0, 0.4);
+        }
+.modal-content {
+            position: fixed;
+            top: 50%;
+            left: 50%;
+            transform: translate(-50%, -50%);
+            background-color: #fefefe;
+            padding: 20px;
+            border: 1px solid #888;
+            border-radius: 10px;
+            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+            max-width: 100%;
+            max-height: 90%;
+
+            overflow-y: auto;
+        }
+
+
+/*修改license信息的样式*/
+.modify-license-modal-content {
+    position: fixed;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    background-color: #fefefe;
+    padding: 50px;
+    border: 1px solid #888;
+    border-radius: 10px;
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+    width: 400px;
+    height: 80%; /* 设置为你希望的高度 */
+    overflow-y: auto;
+    text-align: center;
+}
+
+.modify-license-modal-content input[type="number"],
+.modify-license-modal-content input[type="date"],
+.modify-license-modal-content input[type="text"],
+.modify-license-modal-content input[type="email"],
+.modify-license-modal-content input[type="password"],
+.modify-license-modal-content textarea,
+.modify-license-modal-content select {
+    width: calc(100% - 40px);
+    padding: 15px 20px;  /* 更大内边距 */
+    font-size: 18px;  /* 更大字体 */
+    border: 2px solid #e0e0e0;  /* 边框颜色 */
+    border-radius: 25px;  /* 圆形边框 */
+    box-sizing: border-box;
+    margin-bottom: 20px;
+    background-color: #f8f8f8;  /* 背景色更浅 */
+    transition: border-color 0.3s, box-shadow 0.3s;  /* 添加动画效果 */
+}
+
+.modify-license-modal-content input[type="number"]:focus,
+.modify-license-modal-content input[type="date"]:focus,
+.modify-license-modal-content input[type="text"]:focus,
+.modify-license-modal-content input[type="email"]:focus,
+.modify-license-modal-content input[type="password"]:focus,
+.modify-license-modal-content textarea:focus,
+.modify-license-modal-content select:focus {
+    border-color: #007aff;  /* 聚焦时的边框颜色 */
+    box-shadow: 0 0 8px rgba(0, 122, 255, 0.2);  /* 聚焦时的阴影效果 */
+    outline: none;  /* 去掉默认的聚焦样式 */
+}
+
+.modify-license-modal-content button {
+    width: 100%;
+    padding: 15px 20px;
+    font-size: 18px;
+    font-weight: bold;
+    color: white;
+    background-color: #007aff;
+    border: none;
+    border-radius: 25px;
+    cursor: pointer;
+    transition: background-color 0.3s;
+}
+
+.modify-license-modal-content button:hover {
+    background-color: #005bb5;  /* 鼠标悬停时的颜色变化 */
+}
+
+.modify-license-modal-content .close {
+    color: #aaa;
+    float: right;
+    font-size: 30px;
+    font-weight: bold;
+}
+
+.modify-license-modal-content .close:hover,
+.modify-license-modal-content .close:focus {
+    color: black;
+    text-decoration: none;
+    cursor: pointer;
+}
+
+.modify-license-modal-content .extra-info {
+    padding: 20px;
+    background-color: white;
+    border: 1px solid #ddd;
+    border-radius: 10px;
+    margin-top: 10px;
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+    line-height: 1.5; /* 增加行高,保证字段间距 */
+}
+
+
+
+.modal-content input[type="number"] {
+    width: calc(100% - 40px);
+    padding: 15px 20px;  /* 与其他输入框的内边距一致 */
+    font-size: 18px;  /* 与其他输入框的字体大小一致 */
+    border: 2px solid #e0e0e0;  /* 与其他输入框的边框颜色一致 */
+    border-radius: 25px;  /* 与其他输入框的圆角一致 */
+    box-sizing: border-box;
+    margin-bottom: 20px;
+    background-color: #f8f8f8;  /* 与其他输入框的背景色一致 */
+    transition: border-color 0.3s, box-shadow 0.3s;  /* 与其他输入框的过渡效果一致 */
+}
+
+.modal-content input[type="number"]:focus {
+    border-color: #007aff;  /* 与其他输入框获得焦点时的边框颜色一致 */
+    box-shadow: 0 0 8px rgba(0, 122, 255, 0.2);  /* 与其他输入框获得焦点时的阴影效果一致 */
+    outline: none;  /* 移除默认的焦点轮廓 */
+}
+
+
+.modal-content input[type="date"] {
+    width: calc(100% - 40px);
+    padding: 15px 20px;  /* 与其他输入框的内边距一致 */
+    font-size: 18px;  /* 与其他输入框的字体大小一致 */
+    border: 2px solid #e0e0e0;  /* 与其他输入框的边框颜色一致 */
+    border-radius: 25px;  /* 与其他输入框的圆角一致 */
+    box-sizing: border-box;
+    margin-bottom: 20px;
+    background-color: #f8f8f8;  /* 与其他输入框的背景色一致 */
+    transition: border-color 0.3s, box-shadow 0.3s;  /* 与其他输入框的过渡效果一致 */
+}
+
+.modal-content input[type="date"]:focus {
+    border-color: #007aff;  /* 与其他输入框获得焦点时的边框颜色一致 */
+    box-shadow: 0 0 8px rgba(0, 122, 255, 0.2);  /* 与其他输入框获得焦点时的阴影效果一致 */
+    outline: none;  /* 移除默认的焦点轮廓 */
+}
+
+
+        /* 输入框样式 */
+.modal-content input[type="text"],
+.modal-content input[type="email"],
+.modal-content input[type="password"],
+.modal-content textarea,
+.modal-content select {
+    width: calc(100% - 40px);
+    padding: 15px 20px;  /* 更大内边距 */
+    font-size: 18px;  /* 更大字体 */
+    border: 2px solid #e0e0e0;  /* 边框颜色 */
+    border-radius: 25px;  /* 圆形边框 */
+    box-sizing: border-box;
+    margin-bottom: 20px;
+    background-color: #f8f8f8;  /* 背景色更浅 */
+    transition: border-color 0.3s, box-shadow 0.3s;  /* 添加动画效果 */
+}
+
+/* 输入框聚焦时的样式 */
+.modal-content input[type="text"]:focus,
+.modal-content input[type="email"]:focus,
+.modal-content input[type="password"]:focus,
+.modal-content textarea:focus,
+.modal-content select:focus {
+    border-color: #007aff;  /* 聚焦时的边框颜色 */
+    box-shadow: 0 0 8px rgba(0, 122, 255, 0.2);  /* 聚焦时的阴影效果 */
+    outline: none;  /* 去掉默认的聚焦样式 */
+}
+
+
+
+/* 按钮样式 */
+.modal-content button {
+    width: 100%;
+    padding: 15px 20px;
+    font-size: 18px;
+    font-weight: bold;
+    color: white;
+    background-color: #007aff;
+    border: none;
+    border-radius: 25px;
+    cursor: pointer;
+    transition: background-color 0.3s;
+}
+
+.modal-content button:hover {
+    background-color: #005bb5;  /* 鼠标悬停时的颜色变化 */
+}
+
+        .close {
+            color: #aaa;
+            float: right;
+            font-size: 30px;
+            font-weight: bold;
+            text-shadow: 2px 2px 5px rgba(0, 0, 0, 0.3);
+        }
+        .close:hover {
+    color: #000; /* 悬停时颜色变为黑色 */
+    transform: scale(1.1); /* 悬停时稍微放大 */
+}
+
+        .close:focus {
+            color: black;
+            text-decoration: none;
+            cursor: pointer;
+        }
+        .extra-info {
+    padding: 20px;
+    background-color: white;
+    border: 1px solid #ddd;
+    border-radius: 10px;
+    margin-top: 10px;
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+    line-height: 1.5; /* 增加行高,保证字段间距 */
+}
+.extra-info p {
+    margin: 10px 0; /* 每个字段的间距 */
+    word-wrap: break-word; /* 允许在单词内断行 */
+    word-break: break-all; /* 允许在任何字符处断行 */
+    white-space: pre-wrap; /* 保持空格和换行符 */
+    border-bottom: 1px solid #f0f0f0; /* 每个字段下方加上细线 */
+    padding-bottom: 8px; /* 加大下方内边距 */
+}
+
+/* 修改运维邮箱和销售邮箱的勾选框样式 */
+#emailInputs {
+    display: flex;
+    flex-direction: column; /* 确保每一行都垂直排列 */
+    gap: 10px; /* 增加每行之间的间距 */
+}
+
+#emailInputs div {
+    display: flex;
+    align-items: center; /* 垂直居中 */
+}
+
+#emailInputs input[type="checkbox"] {
+    appearance: none;
+    background-color: #fff;
+    margin: 0;
+    font: inherit;
+    color: #007aff;
+    width: 1.15em;
+    height: 1.15em;
+    border: 0.15em solid #007aff;
+    border-radius: 0.15em;
+    transform: translateY(-0.075em);
+    display: grid;
+    place-content: center;
+}
+
+#emailInputs input[type="checkbox"]::before {
+    content: "";
+    width: 0.65em;
+    height: 0.65em;
+    clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 22%, 80% 0%, 43% 62%);
+    transform: scale(0);
+    transform-origin: bottom left;
+    transition: transform 0.15s ease-in-out;
+    box-shadow: inset 1em 1em #007aff;
+    /* 使用淡蓝色与主题颜色匹配 */
+    background-color: CanvasText;
+}
+
+#emailInputs input[type="checkbox"]:checked::before {
+    transform: scale(1);
+}
+
+
+        .user-info {
+            position: absolute;
+            width: 300px;
+            background-color: white;
+            padding: 20px;
+            border: 1px solid #ddd;
+            border-radius: 10px;
+            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+            display: none;
+        }
+        .user-info .close {
+            position: absolute;
+            top: 10px;
+            right: 10px;
+            color: #aaa;
+            font-size: 24px;
+            font-weight: bold;
+            cursor: pointer;
+        }
+        .user-info .close:hover,
+        .user-info .close:focus {
+            color: black;
+            text-decoration: none;
+        }
+        .centered-modal-content {
+            position: fixed;
+            top: 50%;
+            left: 50%;
+            transform: translate(-50%, -50%);
+            background-color: #fefefe;
+            padding: 50px;
+            border: 1px solid #888;
+            border-radius: 10px;
+            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+            width: 400px;
+            height: 400px;
+            overflow-y: auto;
+            text-align: center;
+        }
+        .ios-button {
+            width: 100%;
+            padding: 10px;
+            margin: 0;
+            border: none;
+            border-radius: 0;
+            font-size: 16px;
+            font-weight: 600;
+            color: #007aff;
+            background-color: transparent;
+            text-align: center;
+            cursor: pointer;
+            transition: background-color 0.3s;
+        }
+        .ios-button:active {
+            background-color: rgba(0, 122, 255, 0.1);
+        }
+        .email-modal-label {
+            font-size: 18px;
+            margin-bottom: 10px;
+            display: block;
+        }
+        .email-modal-input {
+            width: calc(100% - 20px);
+            padding: 10px;
+            font-size: 16px;
+            border: 1px solid #ccc;
+            border-radius: 5px;
+            margin-bottom: 10px;
+        }
+        .email-modal .modal-content {
+    width: 90%; /* 修改宽度 */
+    max-width: 600px; /* 最大宽度 */
+    height: auto; /* 自适应高度 */
+    max-height: 80%; /* 最大高度,防止内容溢出屏幕 */
+    overflow-y: auto; /* 添加垂直滚动条,如果内容超出 */
+}
+
+
+        .add-email-button {
+            display: block;
+            width: 100%;
+            padding: 10px 20px;
+            margin-bottom: 10px;
+            background-color: #007aff;
+            color: white;
+            border: none;
+            text-align: center;
+            font-size: 16px;
+            cursor: pointer;
+            border-radius: 5px;
+            transition: background-color 0.3s;
+        }
+        .add-email-button:hover {
+            background-color: #005bb5;
+        }
+        .action-modal-content {
+            display: flex;
+            flex-direction: column;
+            align-items: flex-start;
+        }
+        .action-modal-content button {
+            width: auto;
+            padding: 10px 20px;
+            margin: 5px 0;
+            background-color: white;
+            color: black;
+            border: none;
+            text-align: left;
+            font-size: 16px;
+            cursor: pointer;
+            border-radius: 5px;
+            transition: background-color 0.3s;
+        }
+        .action-modal-content button:hover {
+            background-color: #f0f0f0;
+        }
+        .sort-button {
+            background-color: transparent;
+            border: none;
+            color: #007aff;
+            font-size: 16px;
+            cursor: pointer;
+            padding: 5px 10px;
+            border-radius: 15px;
+            transition: background-color 0.3s;
+        }
+        .sort-button:hover {
+            background-color: rgba(0, 122, 255, 0.1);
+        }
+        #confirmGenerateModal .centered-modal-content {
+            background-color: #ffffff;
+            border: none;
+            border-radius: 15px;
+            padding: 20px;
+            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+            text-align: center;
+        }
+        #confirmGenerateModal .centered-modal-content button {
+            background-color: #007aff;
+            color: white;
+            border: none;
+            border-radius: 10px;
+            padding: 10px 20px;
+            cursor: pointer;
+            font-size: 16px;
+            margin: 10px;
+            transition: all 0.3s;
+        }
+        #confirmGenerateModal .centered-modal-content button:hover {
+            background-color: #005bb5;
+            border: 1px solid #007aff;
+        }
+
+        #role-management {
+    padding: 20px;
+    background-color: white;
+    border-radius: 10px;
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+/* 角色项的默认样式 */
+.role-item {
+    background-color: white; /* 默认背景色 */
+    box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1); /* 阴影效果 */
+    border-radius: 10px; /* 圆角效果 */
+    overflow: hidden; /* 确保圆角效果生效 */
+    border: 2px solid transparent; /* 默认边框为透明 */
+    transition: all 0.3s ease; /* 过渡效果 */
+    margin-bottom: 20px; /* 每个角色之间的间距 */
+    padding: 10px; /* 内边距 */
+}
+
+/* 置顶角色的样式 */
+.role-item.top-role {
+    background-color: #d3d3d3; /* 灰色背景 */
+}
+
+/* 鼠标悬停时的样式 */
+.role-item:hover {
+    background-color: #e6f7ff; /* 悬停时背景色 */
+    border-color: #1169ff; /* 悬停时边框颜色 */
+    border-width: 4px; /* 悬停时边框大小 */
+    border-radius: 15px; /* 悬停时的圆角 */
+}
+
+/* 置顶角色在悬停时的样式 */
+.role-item.top-role:hover {
+    background-color: #d3d3d3; /* 保持灰色背景不变 */
+    border-color: #1169ff; /* 悬停时边框颜色 */
+    border-width: 4px; /* 悬停时边框大小 */
+    border-radius: 15px; /* 悬停时的圆角 */
+}
+
+
+.role-item h3 {
+    margin-top: 0;
+}
+
+.role-item ul {
+    padding-left: 20px;
+}
+
+.create-role-button {
+    background-color: #007aff;
+    color: white;
+    border: none;
+    padding: 10px 20px;
+    border-radius: 10px;
+    cursor: pointer;
+    font-size: 16px;
+    font-weight: 500;
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+    transition: background-color 0.3s;
+    margin-left: 20px; /* 与标题之间的间距 */
+}
+.create-role-button:hover {
+    background-color: #005bb5;
+}
+
+    </style>
+</head>
+<body>
+    <div class="sidebar" id="sidebar">
+        <button onclick="showTable()">License 管理</button>
+        <button onclick="showUserManagement()">用户管理</button>
+        <button onclick="showRoleManagement()">角色管理</button>
+    </div>
+    <div class="content" id="content">
+        <button class="toggle-sidebar" onclick="toggleSidebar()">←</button>
+        <header>
+            <h1>XUGU License 管理平台</h1>
+            <div>
+             <!--   <button class="upload-button" onclick="window.location.href='/static/upload_xlsx/index.html'" style="display: none;">Upload File</button> --> <!-- 默认隐藏 -->
+                <button class="user-button" onclick="toggleUserInfo()" id="username">User</button>
+             
+            </div>
+            
+        </header>
+        
+        <div id="table-container">
+            <table id="applications-table">
+                <thead>
+                    <tr>
+                        <th>创建人</th>
+                        <th>申请日期 <button class="sort-button" onclick="sortTableByDate()">排序</button></th>
+                        <th>关联项目</th>
+                        <th>销售人员</th>
+                        <th>技服人员</th>
+                        <th>总节点数</th>
+                        <th>使用单位</th>
+                        <th>产品名称</th>
+                        <th>版本</th>
+                        <th>节点数</th>
+                        <th>License状态</th>
+                    </tr>
+                </thead>
+                <tbody>
+                </tbody>
+            </table>
+            <div class="pagination">
+                <button id="prev-page">上一页</button>
+                <button id="next-page">下一页</button>
+                <select id="page-selector" class="flat-select"></select>
+                <span id="pagination-info">第 1 页,共 0 页</span> <!-- 新增信息显示区域 -->
+                <div id="capture-license-button-container">
+                    <!-- 按钮将在这里插入 -->
+                </div>
+             <!--   <button class="CaptureLicenseOnce-button" onclick="CaptureLicenseOncefunc()">获取最新license</button>   动态插入按钮的容器 -->
+            </div>
+        </div>
+<!-- License 详细信息的模态框 -->
+<div id="detailsModal" class="modal">
+    <div class="modal-content">
+        <span class="close" onclick="closeModal('detailsModal')">&times;</span>
+        <h3>License 详细信息</h3>
+        <div id="detailsContent"></div>
+        <button class="close-button" onclick="closeModal('detailsModal')">关闭</button>
+    </div>
+</div>
+
+
+ <!-- 用户管理的添加用户 -->
+<div id="user-management" style="display: none;">
+    <div style="display: flex; align-items: center; justify-content: space-between;">
+        <h2>用户管理</h2>
+        <button id="addUserButton" class="create-role-button" style="display: none;">添加用户</button> <!-- 默认隐藏 -->
+    </div>
+    <table id="users-table">
+        <thead>
+        
+            <tr>
+                <th>用户名</th>
+                <th>电话</th>
+                <th>邮箱</th>
+                <th>权限</th>
+                <th>账号</th>
+            </tr>
+        </thead>
+        <tbody>
+        </tbody>
+    </table>
+</div>
+
+        
+            <!-- 新增的角色管理容器 -->
+    <div id="role-management" style="display: none;">
+        <div style="display: flex; align-items: center; justify-content: space-between;">
+            <h2>角色管理</h2>
+            <button id="createRoleButton" class="create-role-button">创建角色</button>
+        </div>
+        <div id="roles-container"></div>
+    </div>
+    </div>
+
+    <!-- License 管理的弹出菜单 -->
+    <div id="licenseActionModal" class="modal">
+        <div class="modal-content action-modal-content" id="licenseActionModalContent">
+            <span class="close" onclick="closeModal('licenseActionModal')">&times;</span>
+            <!-- <button onclick="showExtraInfo()">查看额外信息</button>
+            <button onclick="modifyLicenseInfo()">修改信息</button> 新增的修改信息按钮 -->
+            <!-- <button onclick="showDistributionHistory()">分发历史</button>
+            <button onclick="confirmDelete()">删除</button> -->
+        </div>
+    </div>
+
+<!-- 修改信息的表单框 -->
+<div id="modifyLicenseModal" class="modal">
+    <div class="modify-license-modal-content">
+        <span class="close" onclick="closeModal('modifyLicenseModal')">&times;</span>
+        <h3>修改License信息</h3>
+        <form id="modifyLicenseForm">
+            <label for="creator">创建人:</label>
+            <input type="text" id="creator" name="creator" required><br><br>
+
+            <label for="applicationDate">申请日期:</label>
+            <input type="date" id="applicationDate" name="applicationDate" class="form-control" required><br><br>
+            
+
+            <label for="associatedProject">关联项目:</label>
+            <input type="text" id="associatedProject" name="associatedProject" required><br><br>
+
+            <label for="salesPerson">销售人员:</label>
+            <input type="text" id="salesPerson" name="salesPerson" required><br><br>
+
+            <label for="salesEmail">销售邮箱:</label>
+            <input type="email" id="salesEmail" name="salesEmail" required><br><br>
+
+            <label for="supportPerson">技服人员:</label>
+            <input type="text" id="supportPerson" name="supportPerson" required><br><br>
+
+            <label for="supportEmail">技服邮箱:</label>
+            <input type="email" id="supportEmail" name="supportEmail" required><br><br>
+
+            <label for="totalNodes">总节点数:</label>
+            <input type="number" id="totalNodes" name="totalNodes" class="form-control" required><br><br>
+
+            <label for="company">使用单位:</label>
+            <input type="text" id="company" name="company" required><br><br>
+
+            <label for="productName">产品名称:</label>
+            <input type="text" id="productName" name="productName" required><br><br>
+
+            <label for="version"> 数据库版本: </label>
+            <input type="text" id="version" name="version" required><br><br>
+
+            <label for="nodeCount">节点数:</label>
+            <input type="number" id="nodeCount" name="nodeCount" class="form-control" required><br><br>
+
+            <button type="button" onclick="saveLicenseChanges()">保存</button>
+        </form>
+    </div>
+</div>
+
+
+
+    <!-- 用户管理的弹出菜单 -->
+    <div id="userActionModal" class="modal">
+        <div class="modal-content action-modal-content" id="userActionModalContent">
+            <span class="close" onclick="closeModal('userActionModal')">&times;</span>
+            <button onclick="showMoreUserInfo()">更多用户信息</button>
+            <button onclick="showUserLicenseHistory()">查看license分发记录</button>
+            <button onclick="modifyUser()">修改用户</button>
+        </div>
+    </div>
+
+<!-- 用户管理的添加用户 -->
+<div id="addUserModal" class="modal">
+    <div class="centered-modal-content" style="height: 50vh; overflow-y: auto;">
+        <span class="close" onclick="closeModal('addUserModal')">&times;</span>
+        <h3>添加用户</h3>
+        <form id="addUserForm">
+            <div class="form-group">
+                <label for="addUsername">用户名:</label>
+                <input type="text" id="addUsername" name="username" class="flat-rounded-input" required>
+            </div>
+
+            <div class="form-group">
+                <label for="addPassword">密码:</label>
+                <input type="password" id="addPassword" name="password" class="flat-rounded-input" required>
+            </div>
+
+            <div class="form-group">
+                <label for="addAccount">账号:</label>
+                <input type="text" id="addAccount" name="account" class="flat-rounded-input" required>
+            </div>
+
+            <div class="form-group">
+                <label for="addTelephone">电话:</label>
+                <input type="text" id="addTelephone" name="telephone" class="flat-rounded-input" required>
+            </div>
+
+            <div class="form-group">
+                <label for="addEmail">邮箱:</label>
+                <input type="email" id="addEmail" name="email" class="flat-rounded-input" required>
+            </div>
+
+            <button type="button" onclick="saveNewUser()" class="flat-rounded-button">保存</button>
+        </form>
+    </div>
+</div>
+
+
+    
+
+    <!-- 邮箱和用户选择弹出框 -->
+    <div id="emailModal" class="modal">
+        <div class="modal-content centered-modal-content" style="height: 45vh; overflow-y: auto;">
+            <span class="close" onclick="closeModal('emailModal')">&times;</span>
+    
+            <!-- 邮箱输入部分 -->
+            <label for="emailInput1" class="email-modal-label">选中要发送的邮箱:</label>
+            <div id="emailInputs">
+                <input type="text" id="emailInput1" class="email-modal-input">
+            </div>
+            <br>
+            <button onclick="addEmailInput()" class="add-email-button">添加邮箱</button>
+    
+            <!-- 用户选择部分 -->
+            <label for="userSelect1" class="email-modal-label">选择要分发的用户:</label>
+            <div id="userInputs"></div>
+            <button onclick="addUserInput()" class="add-email-button">添加用户</button>
+    
+            <!-- 发送按钮 -->
+            <button onclick="sendEmail()" class="ios-button">发送</button>
+        </div>
+    </div>
+    
+    
+    <!-- 其他模态框不变 -->
+    <div id="extraInfoModal" class="modal">
+        <div class="modal-content">
+            <span class="close" onclick="closeModal('extraInfoModal')">&times;</span>
+            <div id="extraInfoContent" class="extra-info"></div>
+        </div>
+    </div>
+
+    <div id="userModal" class="user-info">
+        <span class="close apple-close" onclick="closeUserInfo()">&times;</span> <!-- 关闭按钮 -->
+        <div id="userInfoContent"></div>
+        <br>
+        <button class="apple-logout-button" onclick="logout()">退出</button>
+    </div>
+    
+
+        <!-- license删除模态框-->
+    <div id="confirmDeleteModal" class="modal">
+        <div class="centered-modal-content">
+            <span class="close" onclick="closeModal('confirmDeleteModal')">&times;</span>
+            
+        </div>
+    </div>
+    
+    
+
+    <div id="confirmGenerateModal" class="modal">
+        <div class="centered-modal-content" style="height: 15vh; overflow-y: auto;">
+            <span class="close" onclick="closeModal('confirmGenerateModal')">&times;</span>
+            <p>确认生成License吗?</p>
+            <button id="confirm-generate-button">确认</button>
+            <button onclick="closeModal('confirmGenerateModal')">取消</button>
+        </div>
+    </div>
+
+    <div id="distributionHistoryModal" class="modal">
+        <div class="centered-modal-content">
+            <span class="close" onclick="closeModal('distributionHistoryModal')">&times;</span>
+            <h3>分发历史</h3>
+            <div id="distributionHistoryContent" class="extra-info"></div>
+        </div>
+    </div>
+
+<script>
+    //翻页有关,还需优化
+        let currentPage = 1;
+        let pageSize = 10;
+        let LisOffset = 1;
+        let totalPages = 0; // 存储总页数
+        let selectedPage =1;
+        let nextPage =1;
+        let prevPage = 10;
+//用户token
+        const token = localStorage.getItem('Authorization');
+//选中行的数据
+        let selectedRowData = null;//选中行的数据
+        let isAscending = true;
+        let userMap = {}; // 用于存储用户名和用户ID的映射
+        let currentUserRole = ''; // 新增变量存储当前用户的权限
+        let currentUserName = ''; // 新增变量存储当前用户的名字
+        let userPermissions = [];// 全局变量,用于存储用户权限
+        let LicApplicationData = []; // 全局变量存储数据
+        let currentRolePermissions = []; // 这是您获取的当前角色的权限列表
+
+        
+function checkPermission(permission) {
+            console.log(`检查权限: ${permission} - ${hasPermission ? '有权限' : '无权限'}`);
+    return userPermissions.includes(permission);
+}
+
+async function initialize() {
+    await fetchUsername(); // 确保 fetchUsername 完成
+    fetchApplications(1, 10); // 然后再调用 fetchApplications
+
+
+}
+  
+//获取license展示信息
+function fetchApplications(page, size) {
+    
+    fetch(`http://127.0.0.1:8080/api/admin/GetAllLicenseInfo?page=${page}&pageSize=${size}`, {
+        method: 'GET',
+        headers: {
+            'Authorization': `Bearer ${token}`,
+            'Content-Type': 'application/json'
+        }
+    })
+    .then(response => response.json())
+    .then(data => {
+      //selectedRowData = data.data;
+        LicApplicationData = data.data; // 将接收到的数据存储到全局变量
+        console.log("lic数据",data); // 输出获取的数据结构
+        console.log("z该死的权限数组",userPermissions); 
+        const hasGeneratePermission = userPermissions.includes("generate_license");
+        const hasDistributePermission = userPermissions.includes("dispat_license");
+
+    
+ // 更新总页数
+ //totalPages = Math.ceil(data.total / 10);
+        // 在控制台打印权限结果
+        console.log(`hasGeneratePermission: ${hasGeneratePermission}, hasDistributePermission: ${hasDistributePermission}`);
+        totalPages = Math.ceil(data.total / 10);
+        const tableBody = document.querySelector('#applications-table tbody');
+        const tableHead = document.querySelector('#applications-table thead tr');
+
+        tableBody.innerHTML = '';
+        tableHead.innerHTML = `
+            <th></th> <!-- 添加一个空的表头用于放置下拉按钮 -->
+            <th>创建人</th>
+            <th>申请日期 </th> 
+               <!--   <th>申请日期 <button class="sort-button" onclick="sortTableByDate()">排序</button></th>-->
+            <th>关联项目</th>
+            <th>销售人员</th>
+            <th>技服人员</th>
+            <th>总节点数</th>
+            <th>使用单位</th>
+            <th>操作</th> <!-- 新增的表头用于显示操作按钮 -->
+        `;
+
+        data.data.forEach(applicationArray => {
+            const firstApplication = applicationArray[0];
+            let showGenerateButton = false;
+
+            // Check if any child row has LicenseFlage as "未生成"
+            applicationArray.forEach(detail => {
+                if (detail.LicenseFlage === "未生成") {
+                    showGenerateButton = true;
+                }
+            });
+
+            // 创建主行
+            const row = document.createElement('tr');
+            const dropdownButton = document.createElement('button');
+        // 使用CSS创建一个箭头图标
+            dropdownButton.innerHTML = '';
+            dropdownButton.classList.add('arrow-icon');
+            dropdownButton.style.cursor = 'pointer';
+
+            row.innerHTML = `
+                <td></td> <!-- 空的单元格用于放置下拉按钮 -->
+                <td>${createEllipsisText(firstApplication.Creator)}</td>
+                <td>${createEllipsisText(firstApplication.ApplicationDate)} ${firstApplication.ApplicationTime}</td>
+                <td>${createEllipsisText(firstApplication.GlxmName)}</td>
+                <td>${createEllipsisText(firstApplication.SalesPerson)}</td>
+                <td>${createEllipsisText(firstApplication.SupportPerson)}</td>
+                <td>${firstApplication.TotalNodes}</td>
+                <td>${createEllipsisText(firstApplication.Company)}</td>
+            `;
+            row.querySelector('td').appendChild(dropdownButton);
+
+    // 添加 License 状态按钮 (父级行)
+    if (hasGeneratePermission || hasDistributePermission) {
+        const licenseStatusCell = document.createElement('td');
+
+        if (hasGeneratePermission && showGenerateButton) {
+            licenseStatusCell.innerHTML += `<button class="license-status-btn generate" onclick="confirmGenerateLicense('${firstApplication.oa_request_id}', true)">生成</button>`;
+        } else if (hasDistributePermission && firstApplication.LicenseFlage === "已生成") {
+            licenseStatusCell.innerHTML += `<button class="license-status-btn distribute" onclick="showEmailModal('${firstApplication.SupportEmail}', '${firstApplication.SalesEmail}','','${firstApplication.oa_request_id}')">分发</button>`;
+        }
+
+        // 添加一个分隔符
+        licenseStatusCell.innerHTML += `<span style="margin: 0 10px; display: inline-block; width: 1px; height: 20px; background-color: #ccc;"></span>`;
+
+        // 在这里添加“查看详情”按钮
+        licenseStatusCell.innerHTML += `<button class="license-status-btn view-details" onclick="showDetailsModal('${firstApplication.UniqueID}', false)">查看详情</button>`;
+
+
+        row.appendChild(licenseStatusCell);
+}
+
+
+            tableBody.appendChild(row);
+
+            // 多行内容及表头 (子行)
+            const detailRow = document.createElement('tr');
+            const detailCell = document.createElement('td');
+            detailCell.colSpan = 11; // 占满所有列
+            detailCell.style.display = 'none';
+            const detailTable = document.createElement('table');
+
+            // 添加表头
+            const detailTableHeader = document.createElement('tr');
+            detailTableHeader.innerHTML = `
+                <th>产品名称</th>
+                <th>版本</th>
+                <th>节点数</th>
+                <th>处理器</th>
+                <th>操作系统</th>
+                <th>主MAC地址</th>
+                <th>副MAC地址</th>
+                <th>   查看</th>
+            `;
+            detailTable.appendChild(detailTableHeader);
+
+            // 添加详细信息行 (子行)
+            applicationArray.forEach(detail => {
+                const detailInnerRow = document.createElement('tr');
+                detailInnerRow.innerHTML = `
+                    <td>${createEllipsisText(detail.ProductName)}</td>
+                    <td>${createEllipsisText(detail.ProductVersion)}</td>
+                    <td>${detail.NodeCount}</td>
+                    <td>${createEllipsisText(detail.oa_cpu)}</td>
+                    <td>${createEllipsisText(detail.oa_operating_system)}</td>
+                    <td>${createEllipsisText(detail.oa_main_mac)}</td>
+                    <td>${createEllipsisText(detail.oa_second_mac)}</td>
+                `;
+//保留这里的代码,涉及到子行的单独申请
+                // 添加 License 状态按钮 (子行) 
+                // if (hasGeneratePermission || hasDistributePermission) {
+                //     const licenseStatusDetailCell = document.createElement('td');
+
+                //     if (hasGeneratePermission && detail.LicenseFlage === "未生成") {
+                //         // 子行点击生成按钮时,传递 UniqueID 和 false 表示这是子行
+                //         licenseStatusDetailCell.innerHTML = `<button class="license-status-btn generate" onclick="confirmGenerateLicense('${detail.UniqueID}', false)">生成</button>`;
+                //     } else if (hasDistributePermission && detail.LicenseFlage === "已生成") {
+                //         // 子行点击分发按钮时,传递 UniqueID 用于分发操作
+                //         licenseStatusDetailCell.innerHTML = `<button class="license-status-btn distribute" onclick="showEmailModal('${detail.SupportEmail}', '${detail.SalesEmail}', '${detail.UniqueID}','')">分发</button>`;
+                //     }
+
+                //     detailInnerRow.appendChild(licenseStatusDetailCell);
+                // }
+
+                detailTable.appendChild(detailInnerRow);
+        // 添加“查看详情”按钮 (子行)
+        // const viewDetailsCell = document.createElement('td');
+        // viewDetailsCell.innerHTML = `<button class="license-status-btn view-details" onclick="showDetailsModal('${detail.UniqueID}', true)">查看详情</button>`;
+        // detailInnerRow.appendChild(viewDetailsCell);
+        // detailTable.appendChild(detailInnerRow);
+
+            // 仅当主行状态为“分发”时添加“下载lic”按钮
+// 创建一个新的单元格用于放置按钮
+const actionCell = document.createElement('td');
+
+// 创建按钮容器
+const buttonContainer = document.createElement('div');
+buttonContainer.classList.add('button-container');
+
+// 创建“查看详情”按钮 (子行)
+const viewDetailsButton = document.createElement('button');
+viewDetailsButton.classList.add('license-status-btn', 'view-details');
+viewDetailsButton.textContent = '查看详情';
+viewDetailsButton.onclick = () => showDetailsModal(detail.UniqueID, true);
+
+// 将“查看详情”按钮添加到按钮容器
+buttonContainer.appendChild(viewDetailsButton);
+
+// 仅当主行状态为“已生成”时添加“下载lic”按钮
+if (firstApplication.LicenseFlage === "已生成") {
+    const downloadLicButton = document.createElement('button');
+    downloadLicButton.classList.add('license-status-btn', 'download-lic');
+    downloadLicButton.textContent = '下载lic';
+    
+    // 绑定当前行的 lic1 和 lic2 数据
+    downloadLicButton.onclick = () => downloadLicenseFiles(detail.lic1, detail.lic2);
+    
+    // 将“下载lic”按钮添加到按钮容器
+    buttonContainer.appendChild(downloadLicButton);
+}
+
+// 将按钮容器添加到actionCell
+actionCell.appendChild(buttonContainer);
+
+// 将actionCell添加到当前子行
+detailInnerRow.appendChild(actionCell);
+
+// 将子行添加到详细信息表格
+detailTable.appendChild(detailInnerRow);
+
+
+
+   
+
+    // 为每个详细信息行添加点击事件 (子行)
+                detailInnerRow.addEventListener('click', (event) => {
+                    if (!event.target.closest('button')) {
+                        openActionModal(event, detail, 'licenseActionModal');
+                    }
+                });
+
+                detailInnerRow.addEventListener('contextmenu', (event) => {
+                    event.preventDefault();
+                    if (!event.target.closest('button')) {
+                        openActionModal(event, detail, 'licenseActionModal');
+                    }
+                });
+            });
+
+            detailCell.appendChild(detailTable);
+            detailRow.appendChild(detailCell);
+            tableBody.appendChild(detailRow);
+
+            // 主行的点击事件,用于切换下拉菜单的显示隐藏
+            // row.addEventListener('click', (event) => {
+            //     if (!event.target.closest('button')) { // 如果点击的不是按钮
+            //         detailCell.style.display = detailCell.style.display === 'none' ? 'table-cell' : 'none';
+            //     }
+            // });
+
+            // // 点击下拉按钮切换显示隐藏
+            // dropdownButton.addEventListener('click', (event) => {
+            //     event.stopPropagation(); // 阻止事件冒泡到行点击事件
+            //     detailCell.style.display = detailCell.style.display === 'none' ? 'table-cell' : 'none';
+            // });
+            row.addEventListener('click', (event) => {
+    if (!event.target.closest('button')) { // 如果点击的不是按钮
+        // const isExpanded = detailCell.style.display === 'table-cell';
+        // detailCell.style.display = isExpanded ? 'none' : 'table-cell';
+        // row.classList.toggle('expanded-row', !isExpanded);
+        // dropdownButton.classList.toggle('rotate-arrow', !isExpanded); // 旋转箭头图标
+
+        if (!event.target.closest('button')) {
+        const isExpanded = detailCell.style.display === 'table-cell';
+        
+        // 收回其他行
+        closeAllExpandedRows();
+
+        // 展开/收起当前行
+        detailCell.style.display = isExpanded ? 'none' : 'table-cell';
+        row.classList.toggle('expanded-row', !isExpanded);
+        dropdownButton.classList.toggle('rotate-arrow', !isExpanded);
+    }
+    }
+
+    
+});
+
+
+
+
+
+
+dropdownButton.addEventListener('click', (event) => {
+    // event.stopPropagation(); // 阻止事件冒泡到行点击事件
+    // const isExpanded = detailCell.style.display === 'table-cell';
+    // detailCell.style.display = isExpanded ? 'none' : 'table-cell';
+    // row.classList.toggle('expanded-row', !isExpanded);
+    // dropdownButton.classList.toggle('rotate-arrow', !isExpanded); // 旋转箭头图标
+
+    event.stopPropagation(); // 阻止事件冒泡到行点击事件
+    const isExpanded = detailCell.style.display === 'table-cell';
+    
+    // 收回其他行
+    closeAllExpandedRows();
+
+    // 展开/收起当前行
+    detailCell.style.display = isExpanded ? 'none' : 'table-cell';
+    row.classList.toggle('expanded-row', !isExpanded);
+    dropdownButton.classList.toggle('rotate-arrow', !isExpanded);
+});
+
+        });
+
+        // 分页和其他控制逻辑
+  // 更新分页信息
+      //  totalPages = Math.ceil(data.total / pageSize);
+        document.getElementById('pagination-info').textContent = `第 ${currentPage} 页,共 ${totalPages} 页`;
+        document.getElementById('prev-page').disabled = currentPage <= 1;
+        document.getElementById('next-page').disabled = currentPage >= totalPages;
+        const pageSelector = document.getElementById('page-selector');
+        pageSelector.innerHTML = '';
+        for (let i = 1; i <= totalPages; i++) {
+            const option = document.createElement('option');
+            option.value = i;
+            option.text = i;
+            if (i === selectedPage) {
+                option.selected = true;
+                document.getElementById('pagination-info').textContent = `第 ${selectedPage} 页,共 ${totalPages} 页`;
+                // LisOffset = selectedPage
+                // pageSize = LisOffset + 10;
+                // document.getElementById('prev-page').disabled = currentPage <= 1;
+                // document.getElementById('next-page').disabled = currentPage >= totalPages;
+            }
+            pageSelector.appendChild(option);
+        }
+        
+        
+    });
+}
+
+function checkCaptureLicensePermission() {
+  
+
+    // 获取插入按钮的容器元素
+    const container = document.getElementById('capture-license-button-container');
+
+    if (container) {  // 确保容器元素存在
+        if (userPermissions.includes('capture_license_once_to_db')) {
+            // 如果有权限,插入按钮
+            const CaptureLicensebuttonHtml = `<button class="CaptureLicenseOnce-button" onclick="CaptureLicenseOncefunc()">获取最新license</button>`;
+            container.innerHTML = CaptureLicensebuttonHtml;
+        } else {
+            // 如果没有权限,确保不显示按钮
+            container.innerHTML = ''; // 清空容器
+        }
+    } else {
+        console.error('找不到 capture-license-button-container 元素');
+    }
+}
+
+
+
+let currentOffset = 1;
+const itemsPerPage = 10;
+
+// 添加页数选择框的事件监听器
+document.getElementById('page-selector').addEventListener('change', (event) => {
+     selectedPage = parseInt(event.target.value, 10);
+     
+    const startRecord = (selectedPage - 1) * 10 + 1;
+    const endRecord = startRecord + 10;
+    LisOffset = selectedPage
+    pageSize = LisOffset* 10; 
+    currentPage = selectedPage 
+    currentOffset = startRecord
+    endOffset = endRecord
+    fetchApplications(startRecord, endRecord);
+
+});
+
+// 分页按钮事件监听器
+document.getElementById('prev-page').addEventListener('click', () => {
+    if (currentPage > 1) {
+        currentPage--;
+        selectedPage--
+    
+        currentOffset -= itemsPerPage;
+        const startOffset = currentOffset;
+        const endOffset = currentOffset + itemsPerPage - 1;
+        fetchApplications(startOffset, endOffset);
+    }
+});
+
+
+document.getElementById('next-page').addEventListener('click', () => {
+    if (currentPage < totalPages) {
+        currentPage++
+        selectedPage++
+        LisOffset += pageSize-LisOffset;
+        pageSize += 10; // 增加页数时,增加 pageSize,规则可自定义
+
+  
+        currentOffset += itemsPerPage;
+        const startOffset = currentOffset;
+        const endOffset = currentOffset + itemsPerPage - 1;
+        fetchApplications(startOffset, endOffset);
+    }
+});
+
+// 页面加载时默认调用第一页的数据
+//fetchApplications(1);
+// 页面初始化时调用 fetchUsername 函数
+
+//fetchApplications(LisOffset,pageSize);
+
+//排序
+// function sortTableByDate() {
+//     applicationData.sort((a, b) => {
+//         const dateTimeA = `${a[0].ApplicationDate} ${a[0].ApplicationTime}`;
+//         const dateTimeB = `${b[0].ApplicationDate} ${b[0].ApplicationTime}`;
+//         return isAscending ? dateTimeA.localeCompare(dateTimeB) : dateTimeB.localeCompare(dateTimeA);
+//     });
+
+//     renderTable(applicationData); // 排序后重新渲染表格
+//     isAscending = !isAscending; // 切换排序顺序
+// }
+
+
+// 关闭所有已经展开的行
+function closeAllExpandedRows() {
+    const allExpandedRows = document.querySelectorAll('.expanded-row');
+    allExpandedRows.forEach(expandedRow => {
+        const detailCell = expandedRow.nextElementSibling.querySelector('td');
+        if (detailCell && detailCell.style.display === 'table-cell') {
+            detailCell.style.display = 'none';
+            expandedRow.classList.remove('expanded-row');
+            const dropdownButton = expandedRow.querySelector('.arrow-icon');
+            if (dropdownButton) {
+                dropdownButton.classList.remove('rotate-arrow');
+            }
+        }
+    });
+}
+
+
+
+// 创建带省略号的文本,并在鼠标悬停时显示完整内容
+function createEllipsisText(text) {
+    const maxLength = 20; // 最大显示字符数
+    if (text == null || typeof text !== 'string') {
+        return ''; // 如果 text 是 null、undefined 或不是字符串,返回空字符串
+    }
+    if (text.length > maxLength) {
+        const span = document.createElement('span');
+        span.textContent = text.substring(0, maxLength) + '...';
+        span.title = text; // 鼠标悬停时显示完整内容
+        return span.outerHTML;
+    }
+    return text;
+}
+
+//生成licensestr
+function confirmGenerateLicense(id, isParentRow) {
+    const confirmGenerateModal = document.getElementById('confirmGenerateModal');
+    confirmGenerateModal.style.display = 'block';
+    document.getElementById('confirm-generate-button').onclick = function() {
+        generateLicense(id, isParentRow);
+    };
+}
+
+function generateLicense(id, isParentRow) {
+    const payload = isParentRow ? { oa_request_id: id } : { uniqueID: id };
+    
+    fetch('http://127.0.0.1:8080/api/admin/GenerateLicense', {
+        method: 'POST',
+        headers: {
+            'Authorization': `Bearer ${token}`,
+            'Content-Type': 'application/json'
+        },
+        body: JSON.stringify(payload)
+    })
+    .then(response => response.json())
+    .then(data => {
+        if (data.success) {
+            alert('License生成成功!');
+            const startOffset = currentOffset;  // 当前的起始位置
+    const endOffset = startOffset + itemsPerPage - 1;  // 计算结束位置
+    fetchApplications(startOffset, endOffset);  // 传入计算好的起始和结束位置
+        //    fetchApplications(LisOffset, pageSize);
+        } else {
+            alert('License生成失败:' + data.error);
+        }
+        closeModal('confirmGenerateModal');
+    });
+}
+
+
+//主动抓取一次Licen数据
+function CaptureLicenseOncefunc() {
+    // 显示加载模态框
+    const loadingModal = document.createElement('div');
+    loadingModal.classList.add('modal-content', 'apple-modal-content');
+    loadingModal.innerHTML = `
+        <h3>正在获取 License 信息...</h3>
+        <div class="progress-bar" style="width: 100%; height: 20px; background-color: #e0e0e0;">
+            <div class="progress" style="width: 50%; height: 100%; background-color: #007aff;"></div>
+        </div>
+    `;
+    document.body.appendChild(loadingModal);
+
+    // 定义超时函数
+    const timeoutPromise = new Promise((_, reject) => {
+        setTimeout(() => {
+            reject(new Error('获取超时'));
+        }, 10000); // 10秒超时
+    });
+
+    // 发起 GET 请求的 Promise
+    const fetchPromise = fetch('http://127.0.0.1:8080/api/admin/GetCaptureLicenseOnce', {
+        method: 'GET',
+        headers: {
+            'Authorization': `Bearer ${token}`,
+            'Content-Type': 'application/json'
+        }
+    }).then(response => response.json());
+
+    // 使用 Promise.race 来竞争两个 Promise,哪个先返回就使用哪个
+    Promise.race([fetchPromise, timeoutPromise])
+    .then(data => {
+        // 先关闭进度条
+        document.body.removeChild(loadingModal);
+
+        // 显示成功或失败提示
+        if (data.success) {
+            alert('License 获取成功!');
+        } else {
+            alert('获取失败:' + data.error);
+        }
+    })
+    .catch(error => {
+        // 先关闭进度条
+        document.body.removeChild(loadingModal);
+        
+        // 显示错误提示
+        alert(error.message); // 如果超时或请求失败
+    });
+}
+
+
+function downloadLicenseFiles(lic1, lic2) {
+    // 下载license.dat文件
+    if (lic1) {
+        const blob1 = new Blob([lic1], { type: 'text/plain' });
+        const link1 = document.createElement('a');
+        link1.href = URL.createObjectURL(blob1);
+        link1.download = 'license.dat';
+        link1.click();
+    }
+
+    // 如果lic2不为空,下载license2.dat文件
+    if (lic2) {
+        const blob2 = new Blob([lic2], { type: 'text/plain' });
+        const link2 = document.createElement('a');
+        link2.href = URL.createObjectURL(blob2);
+        link2.download = 'license2.dat';
+        link2.click();
+    }
+}
+
+
+
+//查看lic所有信息
+// 查看lic所有信息
+function showDetailsModal(uniqueID, isChildRow = false, page = 1) {
+    console.log("当前 uniqueID:", uniqueID);
+    console.log("当前 isChildRow:", isChildRow);
+    console.log("当前页码:", page);
+
+    let selectedData = null;
+    const itemsPerPage = 1; // 每页只显示一个子行数据
+    let currentPage = page; // 当前页码
+
+    // 查找主行和子行数据
+    console.log("查找主行和子行数据");
+    let selectedApplicationArray = null;
+    for (let applicationArray of LicApplicationData) {
+        if (applicationArray.some(data => data.UniqueID === uniqueID)) {
+            selectedApplicationArray = applicationArray;
+            break;
+        }
+    }
+
+    if (!selectedApplicationArray) {
+        console.error('未找到对应的记录,UniqueID:', uniqueID);
+        alert('未找到对应的记录,请检查数据');
+        return;
+    }
+
+    let detailsHtml = `<div class="apple-modal-content">`;
+
+    // 如果是子行,直接显示该子行的信息,不进行分页
+    if (isChildRow) {
+    
+        // 查找唯一的子行数据
+        const childData = selectedApplicationArray.find(data => data.UniqueID === uniqueID);
+        if (childData) {
+            detailsHtml += `
+                
+                <p class="apple-modal-text"><strong>产品名称:</strong> ${childData.ProductName}</p>
+                <p class="apple-modal-text"><strong>版本:</strong> ${childData.ProductVersion}</p>
+                <p class="apple-modal-text"><strong>节点数:</strong> ${childData.NodeCount}</p>
+                <p class="apple-modal-text"><strong>处理器:</strong> ${childData.oa_cpu}</p>
+                <p class="apple-modal-text"><strong>操作系统:</strong> ${childData.oa_operating_system}</p>
+                <p class="apple-modal-text"><strong>主MAC地址:</strong> ${childData.oa_main_mac}</p>
+                <p class="apple-modal-text"><strong>副MAC地址:</strong> ${childData.oa_second_mac}</p>
+            `;
+        } else {
+            detailsHtml += `<p>未找到对应的子行记录。</p>`;
+        }
+    } else {
+        // 如果是主行,使用分页显示主行和子行信息
+    
+
+        // 计算总页数
+        const totalItems = selectedApplicationArray.length;
+        const totalPages = totalItems + 1; // 主行为第一页,后续为子行
+        console.log("totalItems: ", totalItems);
+        console.log("totalPages: ", totalPages);
+
+        if (currentPage === 1) {
+            // 显示主行内容(第一页)
+  
+            detailsHtml += `<h3 class="apple-modal-title">项目信息</h3>`;
+            const firstData = selectedApplicationArray[0];
+            detailsHtml += `
+                <p class="apple-modal-text"><strong>创建人:</strong> ${firstData.Creator}</p>
+                <p class="apple-modal-text"><strong>申请日期:</strong> ${firstData.ApplicationDate}</p>
+                <p class="apple-modal-text"><strong>关联项目:</strong> ${firstData.GlxmName}</p>
+                <p class="apple-modal-text"><strong>销售人员:</strong> ${firstData.SalesPerson}</p>
+                <p class="apple-modal-text"><strong>技服人员:</strong> ${firstData.SupportPerson}</p>
+                <p class="apple-modal-text"><strong>总节点数:</strong> ${firstData.TotalNodes}</p>
+                <p class="apple-modal-text"><strong>使用单位:</strong> ${firstData.Company}</p>
+            `;
+        } else {
+            // 显示子行内容(从第二页开始)
+
+            const dataIndex = currentPage - 2; // 当前页对应的子行数据索引,主行占用第一页
+            const data = selectedApplicationArray[dataIndex];
+            detailsHtml += `
+                <h4 class="apple-modal-subtitle">集群 ${currentPage - 1}</h4>
+                <p class="apple-modal-text"><strong>产品名称:</strong> ${data.ProductName}</p>
+                <p class="apple-modal-text"><strong>版本:</strong> ${data.ProductVersion}</p>
+                <p class="apple-modal-text"><strong>节点数:</strong> ${data.NodeCount}</p>
+                <p class="apple-modal-text"><strong>处理器:</strong> ${data.oa_cpu}</p>
+                <p class="apple-modal-text"><strong>操作系统:</strong> ${data.oa_operating_system}</p>
+                <p class="apple-modal-text"><strong>主MAC地址:</strong> ${data.oa_main_mac}</p>
+                <p class="apple-modal-text"><strong>副MAC地址:</strong> ${data.oa_second_mac}</p>
+            `;
+        }
+
+        // 添加分页按钮
+        detailsHtml += `<div class="pagination-controls" style="text-align: center;">`;
+        if (currentPage > 1) {
+            detailsHtml += `<button style="font-size: 12px; padding: 5px 10px; width: 80px;" onclick="showDetailsModal('${uniqueID}', ${isChildRow}, ${currentPage - 1})">上一页</button>`;
+        }
+        detailsHtml += `<hr style="margin: 10px 0; border-top: 1px solid #ccc;">`; // 分隔符
+        if (currentPage < totalPages) {
+            detailsHtml += `<button style="font-size: 12px; padding: 5px 10px; width: 80px;" onclick="showDetailsModal('${uniqueID}', ${isChildRow}, ${currentPage + 1})">下一页</button>`;
+        }
+        detailsHtml += `</div>`;
+    }
+
+    detailsHtml += `</div>`;
+
+    console.log("detailsHtml: ", detailsHtml);
+
+    document.getElementById('detailsContent').innerHTML = detailsHtml;
+    document.getElementById('detailsModal').style.display = 'block';
+}
+
+
+// 用户管理菜单栏
+function openUserActionModal(event, data, modalId) {
+    selectedRowData = data;
+    let modal = document.getElementById(modalId);
+    const modalContent = modal.querySelector('.modal-content');
+
+    // 清空之前的内容
+    modalContent.innerHTML = `
+        <span class="close" onclick="closeModal('${modalId}')">&times;</span>
+    `;
+
+    // 动态生成更多用户信息按钮
+    if (userPermissions.includes('read_user')) {
+        modalContent.innerHTML += `<button onclick="showMoreUserInfo(); closeModal('${modalId}');">更多用户信息</button>`;
+    }
+
+    // 检查是否有 `update_user` 权限,决定是否显示修改用户按钮
+    if (userPermissions.includes('update_user')) {
+        modalContent.innerHTML += `<button onclick="modifyUser(); closeModal('${modalId}');">修改用户</button>`;
+    }
+
+    // 检查是否有 `delete_user` 权限,决定是否显示删除按钮
+    if (userPermissions.includes('delete_user')) {
+        modalContent.innerHTML += `<button onclick="confirmDeleteUser('${data.UniqueID}'); closeModal('${modalId}');">删除用户</button>`;
+    }
+
+    // 显示菜单位置
+    modalContent.style.top = `${event.clientY}px`;
+    modalContent.style.left = `${event.clientX}px`;
+    modal.style.display = 'block';
+}
+
+
+
+
+// 为用户管理界面的每一行添加点击和右键事件监听器
+function fetchUsers() {
+    fetch('http://127.0.0.1:8080/api/admin/userInfoAll', {
+        method: 'GET',
+        headers: {
+            'Authorization': `Bearer ${token}`,
+            'Content-Type': 'application/json'
+        }
+    })
+    .then(response => response.json())
+    .then(data => {
+        const tableBody = document.querySelector('#users-table tbody');
+        tableBody.innerHTML = '';
+        data.data.forEach(user => {
+            // 如果 ACCOUNT 字段为 'admin',则跳过这一行
+            if (user.Account === 'admin') {
+                return;
+            }
+
+            const row = document.createElement('tr');
+            row.innerHTML = `
+                <td>${user.Username}</td>
+                <td>${user.Telephone}</td>
+                <td>${user.Email}</td>
+                <td>${user.Role}</td>
+                <td>${user.Account}</td>
+            `;
+
+            row.addEventListener('click', (event) => {
+                if (!event.target.classList.contains('ios-button')) {
+                    openUserActionModal(event, user, 'userActionModal');
+                }
+            });
+            row.addEventListener('contextmenu', (event) => {
+                event.preventDefault();
+                if (!event.target.classList.contains('ios-button')) {
+                    openUserActionModal(event, user, 'userActionModal');
+                }
+            });
+
+            tableBody.appendChild(row);
+        });
+    });
+}
+
+//查看用户lic历史
+function showUserLicenseHistory() {
+    fetch(`http://127.0.0.1:8080/api/admin/GetlicenseRecordInfoByUser`, {
+        method: 'POST',
+        headers: {
+            'Authorization': `Bearer ${token}`,
+            'Content-Type': 'application/json'
+        },
+        body: JSON.stringify({
+            Id: selectedRowData.Id,
+            UserName: selectedRowData.Username,
+            Page: 1,
+            PageSize: 10
+        })
+    })
+    .then(response => response.json())
+    .then(data => {
+        const extraInfoContent = document.getElementById('extraInfoContent');
+        extraInfoContent.innerHTML = `<h3>License 分发记录</h3>`;
+        
+        if (data.data && data.data.length > 0) {
+            const table = document.createElement('table');
+            table.style.width = '100%';
+            table.style.borderCollapse = 'collapse';
+            table.style.marginTop = '10px';
+
+            const thead = document.createElement('thead');
+            const headerRow = document.createElement('tr');
+            const headers = ['分发日期', '关联项目', '产品名称', '版本', '节点数', 'Company'];
+
+            headers.forEach(headerText => {
+                const th = document.createElement('th');
+                th.textContent = headerText;
+                th.style.border = '1px solid #ddd';
+                th.style.padding = '8px';
+                th.style.backgroundColor = '#f1f1f1';
+                headerRow.appendChild(th);
+            });
+
+            thead.appendChild(headerRow);
+            table.appendChild(thead);
+
+            const tbody = document.createElement('tbody');
+            data.data.forEach(record => {
+                const row = document.createElement('tr');
+
+                const cells = [
+                    record.up_time.Time,
+                    record.AssociatedProject,
+                    record.ProductName,
+                    record.Version,
+                    record.NodeCount,
+                    record.Company
+                ];
+
+                cells.forEach(cellText => {
+                    const td = document.createElement('td');
+                    td.textContent = cellText;
+                    td.style.border = '1px solid #ddd';
+                    td.style.padding = '8px';
+                    row.appendChild(td);
+                });
+
+                tbody.appendChild(row);
+            });
+
+            table.appendChild(tbody);
+            extraInfoContent.appendChild(table);
+        } else {
+            extraInfoContent.innerHTML += `<p>没有找到分发记录。</p>`;
+        }
+
+        document.getElementById('extraInfoModal').style.display = 'block';
+    });
+}
+
+
+
+
+
+
+//用户菜单栏
+function openActionModal(event, data, modalId) {
+    selectedRowData = data;
+    let modal = document.getElementById(modalId);
+    const modalContent = modal.querySelector('.modal-content');
+
+    // 清空之前的内容
+    modalContent.innerHTML = `
+        <span class="close" onclick="closeModal('${modalId}')">&times;</span>
+    `;
+
+
+
+    // 动态生成查看分发记录
+    if (userPermissions.includes('read_license_record')) {
+        modalContent.innerHTML += `
+            <button onclick="showDistributionHistory(); closeModal('${modalId}');">查看分发记录</button>
+        `;
+    }
+     // 动态生成查看额外信息
+     if (userPermissions.includes('read_license')) {
+        modalContent.innerHTML += `
+            <button onclick="showExtraInfo(); closeModal('${modalId}');">查看额外信息</button>
+        `;
+    }
+
+    // 检查是否有 `update_license` 权限,决定是否显示修改信息按钮
+    if (userPermissions.includes('update_license')) {
+        modalContent.innerHTML += `<button onclick="modifyLicenseInfo(); closeModal('${modalId}');">修改信息</button>`;
+    }
+
+    // 检查是否有 `delete_license` 权限,决定是否显示删除按钮
+    if (userPermissions.includes('delete_license')) {
+        modalContent.innerHTML += `<button onclick="confirmDelete(); closeModal('${modalId}');">删除</button>`;
+    }
+
+    // 显示菜单位置
+    modalContent.style.top = `${event.clientY}px`;
+    modalContent.style.left = `${event.clientX}px`;
+    modal.style.display = 'block';
+}
+
+
+
+
+
+function closeModal(modalId) {
+            document.getElementById(modalId).style.display = 'none';
+        }
+
+
+// function closeUserInfo() {
+//             document.getElementById('userModal').style.display = 'none';
+//         }
+
+//删除一行lic信息
+function confirmDelete() {
+    const confirmDeleteModal = document.getElementById('confirmDeleteModal');
+    confirmDeleteModal.innerHTML = `
+ <div class="centered-modal-content">
+    <span class="close" onclick="closeModal('confirmDeleteModal')" style="font-size: 24px; font-weight: bold; color: #555; cursor: pointer;">&times;</span>
+    <div style="margin-bottom: 15px; border: 1px solid #ddd; padding: 10px; border-radius: 10px;">
+        <p><strong>申请日期:</strong> ${selectedRowData.ApplicationDate}</p>
+        <p><strong>关联项目:</strong> ${selectedRowData.AssociatedProject}</p>
+        <p><strong>使用单位:</strong> ${selectedRowData.Company}</p>
+    </div>
+    <p>确认删除此行数据吗?</p>
+    <button class="flat-rounded-button" onclick="deleteRow()">确认</button>
+    <button class="flat-rounded-button" onclick="closeModal('confirmDeleteModal')">取消</button>
+</div>
+
+    `;
+    confirmDeleteModal.style.display = 'block';
+}
+
+function deleteRow() {
+            fetch(`http://127.0.0.1:8080/api/admin/deleteLicRow`, {
+                method: 'POST',
+                headers: {
+                    'Authorization': `Bearer ${token}`,
+                    'Content-Type': 'application/json'
+                },
+                body: JSON.stringify({ uniqueID: selectedRowData.UniqueID })
+            }).then(response => {
+                if (response.ok) {
+                    alert('删除成功');
+                    closeModal('confirmDeleteModal');
+                  //  fetchApplications(LisOffset, pageSize);
+                  const startOffset = currentOffset;  // 当前的起始位置
+    const endOffset = startOffset + itemsPerPage - 1;  // 计算结束位置
+    fetchApplications(startOffset, endOffset);  // 传入计算好的起始和结束位置
+                } else {
+                    alert('删除失败');
+                }
+            });
+        }
+
+
+
+//添加用户
+document.getElementById('addUserButton').addEventListener('click', function() {
+    document.getElementById('addUserModal').style.display = 'block';
+});
+
+//添加用户
+function saveNewUser() {
+    const username = document.getElementById('addUsername').value.trim();
+    const password = document.getElementById('addPassword').value.trim();
+    const account = document.getElementById('addAccount').value.trim();
+    const telephone = document.getElementById('addTelephone').value.trim();
+    const email = document.getElementById('addEmail').value.trim();
+
+    // 验证每个字段都不能为空
+    if (!username || !password || !account || !telephone || !email) {
+        alert('有空选项未填写');
+        return;
+    }
+
+    // 验证电话是否为11位数字
+    const phonePattern = /^\d{11}$/;
+    if (!phonePattern.test(telephone)) {
+        alert('电话必须是11位数字');
+        return;
+    }
+
+    // 验证密码长度是否至少为6位
+    if (password.length < 6) {
+        alert('密码长度必须至少为6位');
+        return;
+    }
+
+    // 验证邮箱是否包含@符号
+    if (!email.includes('@')) {
+        alert('邮箱必须包含@符号');
+        return;
+    }
+
+    // 验证账号长度是否至少为3位
+    if (account.length < 3) {
+        alert('账号长度必须至少为3位');
+        return;
+    }
+
+    const newUser = {
+        Username: username,
+        Password: password,
+        Account: account,
+        Telephone: telephone,
+        Email: email
+    };
+
+    fetch('http://127.0.0.1:8080/api/register', {  // 修改为实际的API路径
+        method: 'POST',
+        headers: {
+            'Authorization': `Bearer ${token}`,
+            'Content-Type': 'application/json'
+        },
+        body: JSON.stringify(newUser)
+    })
+    .then(response => response.json())
+    .then(data => {
+        if (data.success) {
+            alert('用户创建成功!');
+            fetchUsers(); // 重新加载用户列表
+            closeModal('addUserModal'); // 关闭模态框
+        } else {
+            alert('用户创建失败:' + data.error);
+        }
+    });
+}
+
+
+
+
+
+
+function deleteUser(uniqueID) {
+    fetch('http://127.0.0.1:8080/api/admin/deleteUser', {
+        method: 'POST',
+        headers: {
+            'Authorization': `Bearer ${token}`,
+            'Content-Type': 'application/json'
+        },
+        body: JSON.stringify({ UniqueID: uniqueID })
+    })
+    .then(response => response.json())
+    .then(data => {
+        if (data.success) {
+            alert('用户删除成功!');
+            fetchUsers(); // 重新加载用户列表
+        } else {
+            alert('用户删除失败:' + data.error);
+        }
+    })
+    .catch(error => {
+        console.error('Error deleting user:', error);
+        alert('删除用户时发生错误,请稍后再试。');
+    });
+}
+function confirmDeleteUser(uniqueID) {
+    const confirmed = confirm("确认删除此用户吗?");
+    if (confirmed) {
+        deleteUser(uniqueID);
+    }
+}
+
+
+// 分发模态框
+function showEmailModal(supportEmail, salesEmail, uniqueID,oa_request_id) {
+    // 清空并设置默认值
+    document.getElementById('emailInputs').innerHTML = `
+        <div>
+            <input type="checkbox" id="supportEmailCheckbox" value="${supportEmail}" checked>
+            <label for="supportEmailCheckbox">  运维邮箱: ${supportEmail}</label>
+        </div>
+        <div>
+            <input type="checkbox" id="salesEmailCheckbox" value="${salesEmail}" checked>
+            <label for="salesEmailCheckbox">  销售邮箱: ${salesEmail}</label>
+        </div>
+ 
+    `;
+    document.getElementById('userInputs').innerHTML = ''; // 清空用户输入
+
+    // 获取用户信息并填充第一个用户输入框
+    fetch('http://127.0.0.1:8080/api/admin/distributeLicenseByUserInfo', {
+        method: 'GET',
+        headers: {
+            'Authorization': `Bearer ${token}`,
+            'Content-Type': 'application/json'
+        }
+    })
+    .then(response => response.json())
+    .then(data => {
+        const userSelect = document.createElement('select');
+        userSelect.id = 'userSelect1';
+        userSelect.className = 'email-modal-input';
+        
+        data.data.forEach(user => {
+            userMap[user.Username] = {// 存储用户名和用户ID的映射
+            Account: user.Account,
+            UniqueID: user.UniqueID,
+            Username: user.Username,
+            Email: user.Email,
+            Role: user.Role,
+            Telephone:user.Telephone
+        };
+            const option = document.createElement('option');
+            option.value = user.Username;
+            option.textContent = user.Username;
+            userSelect.appendChild(option);
+        });
+
+        //document.getElementById('userInputs').appendChild(userSelect);
+    });
+
+    document.getElementById('emailModal').style.display = 'block';
+    selectedRowData = { ...selectedRowData, UniqueID: uniqueID ,Oa_request_id: oa_request_id};
+}
+
+function addEmailInput() {
+    const emailInputs = document.getElementById('emailInputs');
+    const newInputDiv = document.createElement('div');
+    newInputDiv.style.display = 'flex';
+    newInputDiv.style.alignItems = 'center';
+    newInputDiv.style.marginBottom = '10px';
+
+    const newInput = document.createElement('input');
+    newInput.type = 'text';
+    newInput.className = 'email-modal-input';
+    newInput.style.flexGrow = '1';
+    newInputDiv.appendChild(newInput);
+
+    const deleteButton = document.createElement('button');
+    deleteButton.textContent = '✕';
+    deleteButton.style.background = 'none';
+    deleteButton.style.border = 'none';
+    deleteButton.style.color = '#007aff';
+    deleteButton.style.cursor = 'pointer';
+    deleteButton.style.fontSize = '12px'; /* 再次缩小字体大小 */
+    deleteButton.style.padding = '0'; /* 移除内边距 */
+    deleteButton.style.marginLeft = '3px'; /* 减少与输入框的间距 */
+    deleteButton.style.width = '20px'; /* 限制按钮宽度 */
+    deleteButton.style.height = '20px'; /* 限制按钮高度 */
+    deleteButton.onclick = () => {
+        newInputDiv.remove();
+    };
+
+    newInputDiv.appendChild(deleteButton);
+    emailInputs.appendChild(newInputDiv);
+}
+
+function addUserInput() {
+    const userInputs = document.getElementById('userInputs');
+    const newUserDiv = document.createElement('div');
+    newUserDiv.className = 'user-select-container';
+    newUserDiv.style.display = 'flex';
+    newUserDiv.style.alignItems = 'center';
+    newUserDiv.style.marginBottom = '10px';
+
+    const newUserSelect = document.createElement('select');
+    newUserSelect.className = 'email-modal-input';
+    newUserSelect.style.flexGrow = '1';
+    newUserSelect.style.marginRight = '5px';
+
+    // 添加一个默认的空白选项
+    const defaultOption = document.createElement('option');
+    defaultOption.value = '';
+    defaultOption.textContent = '请选择用户';
+    newUserSelect.appendChild(defaultOption);
+
+    // 获取当前已选择的用户
+    const selectedValues = Array.from(document.querySelectorAll('.user-select-container select'))
+        .map(select => select.value);
+
+    // 获取用户列表并填充到选择框中
+    fetch('http://127.0.0.1:8080/api/admin/distributeLicenseByUserInfo', {
+        method: 'GET',
+        headers: {
+            'Authorization': `Bearer ${token}`,
+            'Content-Type': 'application/json'
+        }
+    })
+    .then(response => response.json())
+    .then(data => {
+        data.data.forEach(user => {
+            // 仅显示未被选中的用户,并且过滤掉admin用户
+            if (!selectedValues.includes(user.Username) && user.Username !== 'admin') {
+                const option = document.createElement('option');
+                option.value = user.Username;
+                option.textContent = user.Username;
+                newUserSelect.appendChild(option);
+            }
+        });
+    });
+
+    const deleteButton = document.createElement('button');
+    deleteButton.textContent = '✕';
+    deleteButton.style.background = 'none';
+    deleteButton.style.border = 'none';
+    deleteButton.style.color = '#007aff';
+    deleteButton.style.cursor = 'pointer';
+    deleteButton.style.fontSize = '12px';
+    deleteButton.style.padding = '0';
+    deleteButton.style.marginLeft = '3px';
+    deleteButton.style.width = '20px';
+    deleteButton.style.height = '20px';
+    deleteButton.onclick = () => {
+        newUserDiv.remove();
+        updateUserOptions(); // 更新其他下拉框中的选项
+    };
+
+    newUserSelect.addEventListener('change', updateUserOptions);
+
+    newUserDiv.appendChild(newUserSelect);
+    newUserDiv.appendChild(deleteButton);
+    userInputs.appendChild(newUserDiv);
+        // 设置空白选项为默认选项
+    newUserSelect.selectedIndex = 0;
+}
+
+function updateUserOptions() {
+    const selectedValues = Array.from(document.querySelectorAll('.user-select-container select'))
+        .map(select => select.value);
+
+    document.querySelectorAll('.user-select-container select').forEach(select => {
+        const currentValue = select.value;
+        select.innerHTML = ''; // 清空现有选项
+
+        // 添加一个默认的空白选项
+        const defaultOption = document.createElement('option');
+        defaultOption.value = '';
+        defaultOption.textContent = '请选择用户';
+        select.appendChild(defaultOption);
+
+        fetch('http://127.0.0.1:8080/api/admin/userInfoAll', {
+            method: 'GET',
+            headers: {
+                'Authorization': `Bearer ${token}`,
+                'Content-Type': 'application/json'
+            }
+        })
+        .then(response => response.json())
+        .then(data => {
+            data.data.forEach(user => {
+                if (!selectedValues.includes(user.Username) || user.Username === currentValue) {
+                    if (user.Username !== 'admin') { // 过滤掉admin用户
+                        const option = document.createElement('option');
+                        option.value = user.Username;
+                        option.textContent = user.Username;
+                        select.appendChild(option);
+                    }
+                }
+            });
+
+            select.value = currentValue;
+        });
+    });
+}
+
+
+
+// 打开修改信息表单框
+function modifyLicenseInfo() {
+    const form = document.getElementById('modifyLicenseForm');
+    
+    // 将选中的行数据填充到表单中
+    form.creator.value = selectedRowData.Creator;
+    form.applicationDate.value = selectedRowData.ApplicationDate;
+    form.associatedProject.value = selectedRowData.GlxmName;
+    form.salesPerson.value = selectedRowData.SalesPerson;
+    form.salesEmail.value = selectedRowData.SalesEmail;
+    form.supportPerson.value = selectedRowData.SupportPerson;
+    form.supportEmail.value = selectedRowData.SupportEmail;
+    form.totalNodes.value = selectedRowData.TotalNodes;
+    form.company.value = selectedRowData.Company;
+    form.productName.value = selectedRowData.ProductName;
+    form.version.value = selectedRowData.ProductVersion;
+    form.nodeCount.value = selectedRowData.NodeCount;
+    
+    document.getElementById('modifyLicenseModal').style.display = 'block';
+}
+
+// 保存修改后的数据
+function saveLicenseChanges() {
+    const form = document.getElementById('modifyLicenseForm');
+    const updatedData = {
+        UniqueID: selectedRowData.UniqueID,
+        Creator: form.creator.value,
+        ApplicationDate: form.applicationDate.value,
+        AssociatedProject: form.associatedProject.value,
+        SalesPerson: form.salesPerson.value,
+        SalesEmail: form.salesEmail.value,
+        SupportPerson: form.supportPerson.value,
+        SupportEmail: form.supportEmail.value,
+        TotalNodes: parseInt(form.totalNodes.value, 10), // 将 TotalNodes 转换为整数
+        Company: form.company.value,
+        ProductName: form.productName.value,
+        Version: form.version.value,
+        NodeCount: parseInt(form.nodeCount.value, 10), // 将 NodeCount 转换为整数
+    };
+    
+    fetch(`http://127.0.0.1:8080/api/admin/UpdateLicense`, {
+        method: 'POST',
+        headers: {
+            'Authorization': `Bearer ${token}`,
+            'Content-Type': 'application/json'
+        },
+        body: JSON.stringify(updatedData)
+    })
+    .then(response => response.json())
+    .then(data => {
+        if (data.success) {
+            alert('License信息更新成功!');
+          //  fetchApplications(LisOffset, pageSize);
+          const startOffset = currentOffset;  // 当前的起始位置
+    const endOffset = startOffset + itemsPerPage - 1;  // 计算结束位置
+    fetchApplications(startOffset, endOffset);  // 传入计算好的起始和结束位置
+            closeModal('modifyLicenseModal');
+        } else {
+            alert('License信息更新失败:' + data.error);
+        }
+    });
+}
+
+
+
+
+function updateUserOptions() {
+    const selectedValues = Array.from(document.querySelectorAll('.user-select-container select'))
+        .map(select => select.value);
+
+    document.querySelectorAll('.user-select-container select').forEach(select => {
+        const currentValue = select.value;  // 保留当前选择值
+        select.innerHTML = ''; // 清空现有选项
+        
+        fetch('http://127.0.0.1:8080/api/admin/distributeLicenseByUserInfo', {
+            method: 'GET',
+            headers: {
+                'Authorization': `Bearer ${token}`,
+                'Content-Type': 'application/json'
+            }
+        })
+        .then(response => response.json())
+        .then(data => {
+            data.data.forEach(user => {
+                // 保留当前值,并排除已经在其他下拉框中选中的值
+                if (!selectedValues.includes(user.Username) || user.Username === currentValue) {
+                    const option = document.createElement('option');
+                    option.value = user.Username;
+                    option.textContent = user.Username;
+                    if (user.Username === currentValue) {
+                        option.selected = true;  // 保持当前选择
+                    }
+                    select.appendChild(option);
+                }
+            });
+        });
+    });
+}
+
+//lic分发给用户 与邮件
+function sendEmail() {
+    console.log('Send button clicked');
+
+    const emailInputs = document.querySelectorAll('.email-modal-input[type="text"]');
+    let emails = Array.from(emailInputs)
+        .map(input => input.value.trim())
+        .filter(email => email && email.includes('@'))
+        .join(',');
+
+    const supportEmailChecked = document.getElementById('supportEmailCheckbox').checked;
+    const salesEmailChecked = document.getElementById('salesEmailCheckbox').checked;
+
+    const emailArray = emails ? [emails] : [];
+
+    if (supportEmailChecked) {
+        emailArray.push(document.getElementById('supportEmailCheckbox').value);
+    }
+    if (salesEmailChecked) {
+        emailArray.push(document.getElementById('salesEmailCheckbox').value);
+    }
+
+    emails = emailArray.join(',');
+
+    // if (!emails) {
+    //     alert('请至少选择一个邮箱地址。');
+    //     return;
+    // }
+
+    console.log('Emails to send:', emails);
+
+    const userInputs = document.querySelectorAll('select.email-modal-input');
+    console.log('userMap:', userMap);
+    
+    const users = Array.from(userInputs)
+        .map(select => userMap[select.value].Account)
+        .join(',');
+
+    const userIds = Array.from(userInputs)
+        .map(select => userMap[select.value].UniqueID)
+        .join(',');
+
+    const userNames = Array.from(userInputs)
+        .map(select => userMap[select.value].Username);
+
+
+
+    const operatorUniqueID = userMap[currentUserName].UniqueID;
+    console.log('User IDs:', userIds);
+    console.log('Users:', users);
+    console.log(' selectedRowData.UniqueID:',  selectedRowData.UniqueID);
+
+    if (!userIds) {
+        alert('请至少选择一个用户。');
+        return;
+    }
+
+    const requestData = {
+        LicenseUniqueID: selectedRowData.UniqueID,
+        Oa_request_id: selectedRowData.Oa_request_id,
+        UserUniqueIDs: userIds.split(','), 
+        UserAccounts: users.split(','), 
+        Emails: emails,
+        UserNames: userNames,
+        OperatorUniqueID: operatorUniqueID
+    };
+    console.log('requestData:', requestData);
+
+    fetch('http://127.0.0.1:8080/api/admin/distributeLicense', {
+        method: 'POST',
+        headers: {
+            'Authorization': `Bearer ${token}`,
+            'Content-Type': 'application/json'
+        },
+        body: JSON.stringify(requestData)
+    })
+    .then(response => {
+        console.log('Response received');
+        return response.json();
+    })
+    .then(data => {
+        if (data.success) {
+            alert('分发成功!');
+            closeModal('emailModal');
+        } else {
+            // 检查是否有 error 字段,并提示用户详细的错误信息
+            const errorMessage = data.error || '分发失败。';
+            alert(`分发失败: ${errorMessage}`);
+        }
+   
+    })
+    .catch(error => {
+        console.error('Error occurred during email distribution:', error);
+        alert('分发失败,请检查网络或后端服务。');
+    });
+}
+
+
+
+
+
+
+function showDistributionHistory() {
+    fetch('http://127.0.0.1:8080/api/admin/GetlicenseRecord', {
+        method: 'POST',
+        headers: {
+            'Authorization': `Bearer ${token}`,
+            'Content-Type': 'application/json'
+        },
+        body: JSON.stringify({ uniqueID: selectedRowData.UniqueID, oa_request_id: selectedRowData.oa_request_id })
+    })
+    .then(response => response.json())
+    .then(data => {
+        const distributionHistoryContent = document.getElementById('distributionHistoryContent');
+        const { license_record_to_user, license_record_to_emails } = data.data;
+
+        // 确保 license_record_to_user 和 license_record_to_emails 存在并且是数组
+        const userRecords = Array.isArray(license_record_to_user) ? license_record_to_user : [];
+        const emailRecords = Array.isArray(license_record_to_emails) ? license_record_to_emails : [];
+
+        if (userRecords.length > 0 || emailRecords.length > 0) {
+            const userHistoryHtml = userRecords.map(record => `
+                <div style="margin-bottom: 20px; padding: 10px; border: 1px solid #ddd; border-radius: 10px; background-color: #f9f9f9;">
+                    <p><strong>已转发给用户:</strong> ${record.user_account}</p>
+                    <p><strong>分发时间:</strong> ${new Date(record.up_time).toLocaleString()}</p>
+                </div>
+            `).join('');
+
+            const emailHistoryHtml = emailRecords.map(record => `
+                <div style="margin-bottom: 20px; padding: 10px; border: 1px solid #ddd; border-radius: 10px; background-color: #f9f9f9;">
+                    <p><strong>已发给邮箱:</strong> ${record.emails}</p>
+                    <p><strong>分发时间:</strong> ${new Date(record.up_time).toLocaleString()}</p>
+                </div>
+            `).join('');
+
+            distributionHistoryContent.innerHTML = userHistoryHtml + emailHistoryHtml;
+        } else {
+            distributionHistoryContent.innerHTML = `<p>没有分发历史记录。</p>`;
+        }
+
+        // 显示模态框
+        const distributionHistoryModal = document.getElementById('distributionHistoryModal');
+        distributionHistoryModal.style.display = 'block';
+    });
+}
+
+
+
+
+
+ function showMoreUserInfo() {
+            const extraInfoContent = document.getElementById('extraInfoContent');
+            extraInfoContent.innerHTML = `
+                <p><strong>用户名:</strong> ${selectedRowData.Username}</p>
+                <p><strong>电话:</strong> ${selectedRowData.Telephone}</p>
+                <p><strong>邮箱:</strong> ${selectedRowData.Email}</p>
+                <p><strong>权限:</strong> ${selectedRowData.Role}</p>
+            `;
+            document.getElementById('extraInfoModal').style.display = 'block';
+        }
+
+function modifyUser() {
+            const userInfoContent = document.getElementById('extraInfoContent');
+            userInfoContent.innerHTML = `
+                <p><strong>用户名:</strong> ${selectedRowData.Username}</p>
+                <p><strong>电话:</strong> ${selectedRowData.Telephone}</p>
+                <p><strong>邮箱:</strong> ${selectedRowData.Email}</p>
+                <p><strong>权限:</strong> ${selectedRowData.Role}</p>
+                <button onclick="showEditUserForm()">编辑用户信息</button>
+            `;
+            document.getElementById('extraInfoModal').style.display = 'block';
+        }
+
+//查看额外信息
+function showExtraInfo() {
+    const extraInfoContent = document.getElementById('extraInfoContent');
+    
+    // 清空之前的内容
+    extraInfoContent.innerHTML = '';
+
+    // 使用预定义的字段名和中文翻译
+    const fieldTranslations = {
+        id: "ID",
+        user_id: "用户ID",
+        up_user: "上传用户",
+        up_time: "上传时间",
+        del_time: "删除时间",
+        License1: "License 1",
+        License2: "License 2",
+        LicenseFlage: "License 状态",
+        UniqueID: "唯一标识符",
+        Creator: "创建人",
+        ApplicationDate: "申请日期",
+        AssociatedProject: "关联项目",
+        SalesPerson: "销售人员",
+        SalesEmail: "销售邮箱",
+        SupportPerson: "支持人员",
+        SupportEmail: "支持邮箱",
+        TotalNodes: "总节点数",
+        Company: "公司",
+        ProductName: "产品名称",
+        Version: "版本",
+        NodeCount: "节点数",
+        Processor: "处理器",
+        OperatingSystem: "操作系统",
+        MasterMacAddress: "主MAC地址",
+        SecondaryMasterMacAddress: "备用主MAC地址"
+    };
+
+    // 使用指定的字段进行展示
+    const fieldsToShow = [
+        "id", "user_id", "up_user", "up_time", "del_time", "License1", "License2", 
+        "LicenseFlage", "UniqueID", "Creator", "ApplicationDate", "AssociatedProject", 
+        "SalesPerson", "SalesEmail", "SupportPerson", "SupportEmail", "TotalNodes", 
+        "Company", "ProductName", "Version", "NodeCount", "Processor", "OperatingSystem", 
+        "MasterMacAddress", "SecondaryMasterMacAddress"
+    ];
+
+    fieldsToShow.forEach(key => {
+        if (selectedRowData.hasOwnProperty(key)) {
+            let value = selectedRowData[key];
+            
+            // 特殊处理 up_time 和 del_time
+            if (key === 'up_time' || key === 'del_time') {
+                if (value.Valid) {
+                    value = new Date(value.Time).toLocaleString(); // 格式化日期时间
+                } else {
+                    value = '无';
+                }
+            }
+
+            // 处理 License1 和 License2,提取字符串字段
+            if (key === 'License1' || key === 'License2') {
+                // 假设 License 对象包含一个名为 'Details' 的字符串字段
+                value = value.String || '[无数据]';
+            }
+
+            // 处理 MasterMacAddress 和 SecondaryMasterMacAddress,提取字符串字段
+            // if (key === 'MasterMacAddress' || key === 'SecondaryMasterMacAddress') {
+            //     // 假设 License 对象包含一个名为 'Details' 的字符串字段
+            //     value = value.String || '[无数据]';
+            // }
+
+            const infoElement = document.createElement('p');
+            infoElement.innerHTML = `<strong>${fieldTranslations[key]}:</strong> ${value}`;
+            extraInfoContent.appendChild(infoElement);
+        }
+    });
+
+    // 显示 modal
+    document.getElementById('extraInfoModal').style.display = 'block';
+}
+
+
+function showRoleManagement() {
+    document.getElementById('table-container').style.display = 'none';
+    document.getElementById('user-management').style.display = 'none'; // 隐藏用户管理页面
+    document.getElementById('role-management').style.display = 'block';;
+
+    fetchRoles();
+}
+
+function fetchRoles() {
+    fetch('http://127.0.0.1:8080/api/admin/GetRoles', {
+        method: 'POST',
+        headers: {
+            'Authorization': `Bearer ${token}`,
+            'Content-Type': 'application/json'
+        },
+        body: JSON.stringify({ Name: " " })  // 发送空的 Name 参数
+    })
+    .then(response => response.json())
+    .then(data => {
+        const roleContainer = document.getElementById('roles-container');
+        roleContainer.innerHTML = ''; // 清空之前的内容
+
+        const permissionMap = {
+            'generate_license': '生成许可证',
+            'upload_license': '上传许可证',
+            'read_license': '读取许可证',
+            'read_license_record': '读取许可证分发记录',
+            'update_license': '修改许可证',
+            'delete_license': '删除许可证',
+            'dispat_license': '分发许可证',
+            'create_user': '创建用户',
+            'read_user': '读取用户',
+            'update_user': '更新用户',
+            'delete_user': '删除用户',
+            'create_role': '创建角色',
+            'delete_role': '删除角色',
+            'update_role': '更新角色',
+            'get_role': '获取角色'
+        };
+
+        // 检查用户是否有 create_role 权限
+        const hasCreateRolePermission = userPermissions.includes('create_role');
+
+        // 如果有权限则显示“创建角色”按钮
+        if (hasCreateRolePermission) {
+            document.getElementById('createRoleButton').style.display = 'block';
+        } else {
+            document.getElementById('createRoleButton').style.display = 'none';
+        }
+
+        // 处理置顶角色和其他角色的渲染(代码不变)
+        const topRoles = ['admin', 'guest', 'supportRole'];
+
+        topRoles.forEach(role => {
+            if (data.data[role]) {
+                renderRole(roleContainer, data.data[role], permissionMap, true);
+            }
+        });
+
+        Object.keys(data.data).forEach(role => {
+            if (!topRoles.includes(role)) {
+                renderRole(roleContainer, data.data[role], permissionMap, false);
+            }
+        });
+    });
+}
+
+
+function renderRole(container, roleInfo, permissionMap, isTopRole) {
+    const roleDiv = document.createElement('div');
+    roleDiv.className = 'role-item';
+    roleDiv.style.position = 'relative';  // 确保弹出菜单能够正确定位
+
+    if (isTopRole) {
+        roleDiv.classList.add('top-role'); // 应用置顶角色的样式
+    }
+
+    let permissions = roleInfo.Permissions.map(permission => permissionMap[permission]);
+    let permissionsHtml = permissions.join(',');  // 使用逗号分隔并合并为一行
+
+    roleDiv.innerHTML = `
+        <h3>${roleInfo.Name}</h3>
+        <p>${permissionsHtml}</p>
+    `;
+    container.appendChild(roleDiv);
+
+    // 添加事件监听器,处理点击和右键点击弹出菜单
+    roleDiv.addEventListener('click', function(event) {
+        event.stopPropagation();  // 阻止事件冒泡
+        event.preventDefault();   // 阻止默认行为
+        showRoleActionMenu(event, roleInfo.Name);
+    });
+
+    roleDiv.addEventListener('contextmenu', function(event) {
+        event.preventDefault();
+        showRoleActionMenu(event, roleInfo.Name);
+    });
+}
+
+function showRoleActionMenu(event, roleName) {
+    const existingMenu = document.getElementById('role-action-menu');
+    if (existingMenu) {
+        existingMenu.remove();
+    }
+
+    const menu = document.createElement('div');
+    menu.id = 'role-action-menu';
+    menu.style.position = 'absolute';
+    menu.style.top = `${event.clientY}px`;
+    menu.style.left = `${event.clientX}px`;
+    menu.style.backgroundColor = '#fff';
+    menu.style.border = '1px solid #ccc';
+    menu.style.borderRadius = '5px';
+    menu.style.boxShadow = '0 2px 10px rgba(0, 0, 0, 0.2)';
+    menu.style.padding = '10px';
+    menu.style.zIndex = '1000';
+
+    const topRoles = ['admin', 'guest', 'support'];
+
+    if (topRoles.includes(roleName)) {
+        // 为 admin, guest, supportRole 设置灰色和不可点击样式
+        const disabledButton = document.createElement('button');
+        disabledButton.textContent = '不可操作';
+        disabledButton.style.display = 'block';
+        disabledButton.style.width = '100%';
+        disabledButton.style.padding = '10px 20px';
+        disabledButton.style.border = 'none';
+        disabledButton.style.borderRadius = '15px';
+        disabledButton.style.cursor = 'not-allowed';
+        disabledButton.style.backgroundColor = '#d3d3d3';  // 灰色背景
+        disabledButton.style.color = '#999';  // 灰色文字
+
+        menu.appendChild(disabledButton);
+    } else {
+        // 检查用户是否有 update_role 和 delete_role 权限
+        const hasUpdateRolePermission = userPermissions.includes('update_role');
+        const hasDeleteRolePermission = userPermissions.includes('delete_role');
+
+        // 创建和添加修改角色按钮
+        if (hasUpdateRolePermission) {
+            const modifyButton = document.createElement('button');
+            modifyButton.textContent = '修改角色';
+            modifyButton.style.display = 'block';
+            modifyButton.style.marginBottom = '10px';
+            modifyButton.style.width = '100%';
+            modifyButton.style.padding = '10px 20px';
+            modifyButton.style.border = 'none';
+            modifyButton.style.borderRadius = '15px';
+            modifyButton.style.cursor = 'pointer';
+            modifyButton.style.transition = 'background-color 0.3s, color 0.3s';
+            modifyButton.style.boxShadow = '0 2px 4px rgba(0, 0, 0, 0.1)';
+            modifyButton.style.backgroundColor = '#007aff';
+            modifyButton.style.color = '#fff';
+
+            modifyButton.addEventListener('mouseenter', function () {
+                modifyButton.style.backgroundColor = '#005bb5';
+            });
+
+            modifyButton.addEventListener('mouseleave', function () {
+                modifyButton.style.backgroundColor = '#007aff';
+            });
+
+            modifyButton.addEventListener('click', function () {
+                modifyRole(roleName);
+                menu.remove(); // 点击按钮后,关闭菜单栏
+            });
+
+            menu.appendChild(modifyButton);
+        }
+
+        // 创建和添加删除角色按钮
+        if (hasDeleteRolePermission) {
+            const deleteButton = document.createElement('button');
+            deleteButton.textContent = '删除角色';
+            deleteButton.style.display = 'block';
+            deleteButton.style.width = '100%';
+            deleteButton.style.padding = '10px 20px';
+            deleteButton.style.border = 'none';
+            deleteButton.style.borderRadius = '15px';
+            deleteButton.style.cursor = 'pointer';
+            deleteButton.style.transition = 'background-color 0.3s, color 0.3s';
+            deleteButton.style.boxShadow = '0 2px 4px rgba(0, 0, 0, 0.1)';
+            deleteButton.style.backgroundColor = '#ff3b30';
+            deleteButton.style.color = '#fff';
+
+            deleteButton.addEventListener('mouseenter', function () {
+                deleteButton.style.backgroundColor = '#c23321';
+            });
+
+            deleteButton.addEventListener('mouseleave', function () {
+                deleteButton.style.backgroundColor = '#ff3b30';
+            });
+
+            deleteButton.addEventListener('click', function () {
+                deleteRole(roleName);
+                menu.remove(); // 点击按钮后,关闭菜单栏
+            });
+
+            menu.appendChild(deleteButton);
+        }
+
+        // 如果用户没有修改或删除权限,则显示没有选项的提示
+        if (!hasUpdateRolePermission && !hasDeleteRolePermission) {
+            const noOption = document.createElement('div');
+            noOption.textContent = '没有可用的操作';
+            noOption.style.textAlign = 'center';
+            noOption.style.color = '#999';
+            menu.appendChild(noOption);
+        }
+    }
+
+    document.body.appendChild(menu);
+
+    document.addEventListener('click', function closeMenu(event) {
+        if (!menu.contains(event.target)) {
+            menu.remove();
+            document.removeEventListener('click', closeMenu);
+        }
+    });
+}
+
+
+//修改角色信息
+function modifyRole(roleName) {
+    console.log(`modifyRole called with roleName: ${roleName}`);
+    
+    fetchRoleInfo(roleName, function(roleInfo) {
+        console.log(`fetchRoleInfo returned:`, roleInfo);
+
+        if (!roleInfo) {
+            console.error('Role info is undefined or null');
+            return;
+        }
+
+        const extraInfoContent = document.getElementById('extraInfoContent');
+        if (!extraInfoContent) {
+            console.error('Element with id "extraInfoContent" not found');
+            return;
+        }
+
+        extraInfoContent.innerHTML = `
+            <h3>修改角色: ${roleInfo.Name}</h3>
+            <label for="roleName">角色名称:</label>
+            <input type="text" id="roleName" value="${roleInfo.Name}" required><br><br>
+            <label for="permissions">权限:</label><br>
+            <div id="permissions">
+                  <div>
+            <input type="checkbox" id="license" name="permissions" value="license" onchange="toggleSubPermissions('license', this)">
+            <label for="license">许可证</label>
+            <div id="licenseSubPermissions" style="margin-left: 20px;">
+                <input type="checkbox" id="generate_license" name="permissions" value="generate_license">
+                <label for="generate_license">生成许可证</label><br>
+                <input type="checkbox" id="upload_license" name="permissions" value="upload_license">
+                <label for="upload_license">上传许可证</label><br>
+                <input type="checkbox" id="read_license" name="permissions" value="read_license">
+                <label for="read_license">读取许可证</label><br>
+                <input type="checkbox" id="update_license" name="permissions" value="update_license">
+                <label for="update_license">更新许可证</label><br>
+                <input type="checkbox" id="delete_license" name="permissions" value="delete_license">
+                <label for="delete_license">删除许可证</label><br>
+            </div>
+        </div>
+        <div>
+            <input type="checkbox" id="user" name="permissions" value="user" onchange="toggleSubPermissions('user', this)">
+            <label for="user">用户</label>
+            <div id="userSubPermissions" style="margin-left: 20px;">
+                <input type="checkbox" id="create_user" name="permissions" value="create_user">
+                <label for="create_user">创建用户</label><br>
+                <input type="checkbox" id="read_user" name="permissions" value="read_user">
+                <label for="read_user">读取用户</label><br>
+                <input type="checkbox" id="update_user" name="permissions" value="update_user">
+                <label for="update_user">更新用户</label><br>
+                <input type="checkbox" id="delete_user" name="permissions" value="delete_user">
+                <label for="delete_user">删除用户</label><br>
+            </div>
+        </div>
+        <div>
+            <input type="checkbox" id="role" name="permissions" value="role" onchange="toggleSubPermissions('role', this)">
+            <label for="role">角色</label>
+            <div id="roleSubPermissions" style="margin-left: 20px;">
+                <input type="checkbox" id="create_role" name="permissions" value="create_role">
+                <label for="create_role">创建角色</label><br>
+                <input type="checkbox" id="delete_role" name="permissions" value="delete_role">
+                <label for="delete_role">删除角色</label><br>
+                <input type="checkbox" id="update_role" name="permissions" value="update_role">
+                <label for="update_role">更新角色</label><br>
+                <input type="checkbox" id="get_role" name="permissions" value="get_role">
+                <label for="get_role">获取角色</label><br>
+            </div>
+        </div>
+            </div><br><br>
+            <button onclick="saveRoleChanges('${roleInfo.Id}')">保存</button>
+        `;
+
+        console.log('Form content added to extraInfoContent');
+
+        const checkboxes = document.querySelectorAll('#permissions input[type="checkbox"]');
+
+// 遍历所有复选框
+checkboxes.forEach(checkbox => {
+    if (roleInfo.Permissions.includes(checkbox.value)) {
+        checkbox.checked = true; // 如果当前权限在roleInfo.Permissions中,则勾选
+    }
+});
+
+        const extraInfoModal = document.getElementById('extraInfoModal');
+        if (!extraInfoModal) {
+            console.error('Element with id "extraInfoModal" not found');
+            return;
+        }
+
+        extraInfoModal.style.display = 'block';  // 强制显示模态框
+        console.log('Modal should be visible now');
+    });
+}
+
+function getCheckboxOptions(selectedPermissions) {
+    const permissionMap = {
+        'generate_license': '生成许可证',
+        'upload_license': '上传许可证',
+        'read_license': '读取许可证',
+        'update_license': '修改许可证',
+        'delete_license': '删除许可证',
+        'dispat_license': '分发许可证',
+        'read_license_record': '读取许可证分发记录',
+      //  'get_user_info': '获取用户信息',
+        'create_user': '创建用户',
+        'read_user': '读取用户',
+        'update_user': '更新用户',
+        'delete_user': '删除用户',
+        'create_role': '创建角色',
+        'delete_role': '删除角色',
+        'update_role': '更新角色',
+        'get_role': '获取角色'
+    };
+
+    return Object.keys(permissionMap).map(permission => `
+        <div>
+            <input type="checkbox" id="${permission}" name="permissions" value="${permission}" ${selectedPermissions.includes(permission) ? 'checked' : ''}>
+            <label for="${permission}">${permissionMap[permission]}</label>
+        </div>
+    `).join('');
+}
+
+
+
+
+
+function fetchRoleInfo(roleName, callback) {
+    console.log(`fetchRoleInfo called with roleName: ${roleName}`);
+    
+    fetch('http://127.0.0.1:8080/api/admin/GetRoles', {
+        method: 'POST',
+        headers: {
+            'Authorization': `Bearer ${token}`,
+            'Content-Type': 'application/json'
+        },
+        body: JSON.stringify({ Name: roleName })
+    })
+    .then(response => {
+        console.log('fetchRoleInfo response received', response);
+        return response.json();
+    })
+    .then(data => {
+        console.log('fetchRoleInfo data received', data);
+
+        // 直接从 data.data 获取角色信息
+        if (data && data.data) {
+            console.log('Role info found:', data.data);
+            callback(data.data);  // 直接返回 data.data 而不是 data.data[roleName]
+        } else {
+            console.error('Role not found or unexpected data structure', data);
+            callback(null);  // 如果角色信息未找到,返回 null
+        }
+    })
+    .catch(error => {
+        console.error('Error in fetchRoleInfo:', error);
+        callback(null);  // 捕获异常情况
+    });
+}
+
+function getPermissionsOptions(selectedPermissions) {
+    const permissionMap = {
+        'generate_license': '生成许可证',
+        'upload_license': '上传许可证',
+        'read_license': '读取许可证',
+        'update_license': '修改许可证',
+        'delete_license': '删除许可证',
+        'dispat_license': '分发许可证',
+        'read_license_record': '读取许可证分发记录',
+        //'get_user_info': '获取用户信息',
+        'create_user': '创建用户',
+        'read_user': '读取用户',
+        'update_user': '更新用户',
+        'delete_user': '删除用户',
+        'create_role': '创建角色',
+        'delete_role': '删除角色',
+        'update_role': '更新角色',
+        'get_role': '获取角色'
+    };
+    
+    return Object.keys(permissionMap).map(permission => `
+        <option value="${permission}" ${selectedPermissions.includes(permission) ? 'selected' : ''}>
+            ${permissionMap[permission]}
+        </option>
+    `).join('');
+}
+
+function saveRoleChanges(roleId) {
+    const updatedRoleName = document.getElementById('roleName').value.trim();
+    const selectedPermissions = Array.from(document.querySelectorAll('#permissions input[type="checkbox"]:checked'))
+                                    .map(checkbox => checkbox.value)
+                                    .filter(value => !['license', 'user', 'role'].includes(value)); // 过滤掉一级分类
+
+    if (!updatedRoleName) {
+        alert('角色名称不能为空!');
+        return;
+    }
+
+    const updateRole = {
+        Id: parseInt(roleId, 10),  // 确保 roleId 被传递并转换为整数
+        Name: updatedRoleName,
+        Permissions: selectedPermissions
+    };
+
+    fetch('http://127.0.0.1:8080/api/admin/UpdateRole', {
+        method: 'POST',
+        headers: {
+            'Authorization': `Bearer ${token}`,
+            'Content-Type': 'application/json'
+        },
+        body: JSON.stringify(updateRole)
+    })
+    .then(response => response.json())
+    .then(data => {
+        if (data.success) {
+            alert('角色更新成功!');
+            fetchRoles(); // 刷新角色列表
+            closeModal('extraInfoModal'); // 关闭信息栏
+        } else {
+            alert('角色更新失败:' + data.error);
+        }
+    });
+}
+
+//删除角色
+function deleteRole(roleName) {
+    console.log(`deleteRole called for role: ${roleName}`);
+    
+    const confirmed = confirm(`确认删除角色: ${roleName} 吗?`);
+    if (!confirmed) {
+        return;
+    }
+
+    fetch('http://127.0.0.1:8080/api/admin/DeleteRole', {
+        method: 'POST',
+        headers: {
+            'Authorization': `Bearer ${token}`,
+            'Content-Type': 'application/json'
+        },
+        body: JSON.stringify({
+            name: roleName  // 将角色名称传递给后端
+        })
+    })
+    .then(response => response.json())
+    .then(data => {
+        if (data.success) {
+            alert('角色删除成功!');
+            fetchRoles();  // 刷新角色列表
+        } else {
+            alert('角色删除失败:' + data.error);
+        }
+    })
+    .catch(error => {
+        console.error('Error deleting role:', error);
+        alert('删除角色时发生错误,请稍后再试。');
+    });
+}
+
+document.getElementById('createRoleButton').addEventListener('click', function() {
+    const extraInfoContent = document.getElementById('extraInfoContent');
+    extraInfoContent.innerHTML = `
+        <h3>创建新角色</h3>
+        <label for="newRoleName">角色名称:</label>
+        <input type="text" id="newRoleName" required><br><br>
+          <label for="newPermissions">权限:</label><br>
+ <div id="newPermissions">
+        <div>
+            <input type="checkbox" id="license" name="permissions" value="license" onchange="toggleSubPermissions('license', this)">
+            <label for="license">许可证</label>
+            <div id="licenseSubPermissions" style="margin-left: 20px;">
+                <input type="checkbox" id="generate_license" name="permissions" value="generate_license">
+                <label for="generate_license">生成许可证</label><br>
+                <input type="checkbox" id="upload_license" name="permissions" value="upload_license">
+                <label for="upload_license">上传许可证</label><br>
+                <input type="checkbox" id="read_license" name="permissions" value="read_license">
+                <label for="read_license">读取许可证</label><br>
+                <input type="checkbox" id="update_license" name="permissions" value="update_license">
+                <label for="update_license">更新许可证</label><br>
+                <input type="checkbox" id="delete_license" name="permissions" value="delete_license">
+                <label for="delete_license">删除许可证</label><br>
+            </div>
+        </div>
+        <div>
+            <input type="checkbox" id="user" name="permissions" value="user" onchange="toggleSubPermissions('user', this)">
+            <label for="user">用户</label>
+            <div id="userSubPermissions" style="margin-left: 20px;">
+                <input type="checkbox" id="create_user" name="permissions" value="create_user">
+                <label for="create_user">创建用户</label><br>
+                <input type="checkbox" id="read_user" name="permissions" value="read_user">
+                <label for="read_user">读取用户</label><br>
+                <input type="checkbox" id="update_user" name="permissions" value="update_user">
+                <label for="update_user">更新用户</label><br>
+                <input type="checkbox" id="delete_user" name="permissions" value="delete_user">
+                <label for="delete_user">删除用户</label><br>
+            </div>
+        </div>
+        <div>
+            <input type="checkbox" id="role" name="permissions" value="role" onchange="toggleSubPermissions('role', this)">
+            <label for="role">角色</label>
+            <div id="roleSubPermissions" style="margin-left: 20px;">
+                <input type="checkbox" id="create_role" name="permissions" value="create_role">
+                <label for="create_role">创建角色</label><br>
+                <input type="checkbox" id="delete_role" name="permissions" value="delete_role">
+                <label for="delete_role">删除角色</label><br>
+                <input type="checkbox" id="update_role" name="permissions" value="update_role">
+                <label for="update_role">更新角色</label><br>
+                <input type="checkbox" id="get_role" name="permissions" value="get_role">
+                <label for="get_role">获取角色</label><br>
+            </div>
+        </div>
+    </div><br><br>
+    <button onclick="createNewRole()">创建</button>
+</div>
+
+     
+    `;
+    document.getElementById('extraInfoModal').style.display = 'block';
+});
+
+
+function toggleSubPermissions(category, checkbox) {
+    const subPermissions = document.querySelectorAll(`#${category}SubPermissions input[type="checkbox"]`);
+    subPermissions.forEach(subCheckbox => {
+        subCheckbox.checked = checkbox.checked;
+    });
+}
+
+function createNewRole() {
+    const newRoleName = document.getElementById('newRoleName').value.trim();
+    const selectedPermissions = Array.from(document.querySelectorAll('#newPermissions input[type="checkbox"]:checked'))
+                                    .map(checkbox => checkbox.value)
+                                    .filter(value => !['license', 'user', 'role'].includes(value)); // 过滤掉一级分类
+
+    if (!newRoleName) {
+        alert('角色名称不能为空!');
+        return;
+    }
+
+    const newRole = {
+        Name: newRoleName,
+        Permissions: selectedPermissions
+    };
+
+    fetch('http://127.0.0.1:8080/api/admin/CreateRole', {
+        method: 'POST',
+        headers: {
+            'Authorization': `Bearer ${token}`,
+            'Content-Type': 'application/json'
+        },
+        body: JSON.stringify(newRole)
+    })
+    .then(response => response.json())
+    .then(data => {
+        if (data.success) {
+            alert('角色创建成功!');
+            fetchRoles(); // 刷新角色列表
+            closeModal('extraInfoModal'); // 关闭信息栏
+        } else {
+            alert('角色创建失败:' + data.error);
+        }
+    });
+}
+
+//显示用户信息
+function showEditUserForm() {
+    const extraInfoContent = document.getElementById('extraInfoContent');
+
+    // 清空之前的内容
+    extraInfoContent.innerHTML = '';
+
+    // 创建表单
+    const form = document.createElement('form');
+    form.id = 'editUserForm';
+    form.style.width = '400px'; // 增加表单宽度
+
+    form.innerHTML = `
+        <div style="display: flex; align-items: center; margin-bottom: 15px;">
+            <label for="username" style="flex: 0 0 80px;">用户名:</label>
+            <input type="text" id="username" name="username" value="${selectedRowData.Username}" style="flex: 1;" required>
+        </div>
+        
+        <div style="display: flex; align-items: center; margin-bottom: 15px;">
+            <label for="telephone" style="flex: 0 0 80px;">电话:</label>
+            <input type="text" id="telephone" name="telephone" value="${selectedRowData.Telephone}" style="flex: 1;" required>
+        </div>
+        
+        <div style="display: flex; align-items: center; margin-bottom: 15px;">
+            <label for="email" style="flex: 0 0 80px;">邮箱:</label>
+            <input type="email" id="email" name="email" value="${selectedRowData.Email}" style="flex: 1;" required>
+        </div>
+    `;
+
+    // 检查用户是否有 'update_role' 权限,如果有,则显示权限选择框
+    if (userPermissions.includes('update_role')) {
+        const roleSelectDiv = document.createElement('div');
+        roleSelectDiv.style = 'display: flex; align-items: center; margin-bottom: 15px;';
+        roleSelectDiv.innerHTML = `
+            <label for="role" style="flex: 0 0 80px;">权限:</label>
+            <select id="role" name="role" style="flex: 1;" required></select>
+        `;
+        form.appendChild(roleSelectDiv);
+
+        // 获取角色列表并填充到选择框中
+        fetch('http://127.0.0.1:8080/api/admin/GetRoleNames', {
+            method: 'GET',
+            headers: {
+                'Authorization': `Bearer ${token}`,
+                'Content-Type': 'application/json'
+            }
+        })
+        .then(response => response.json())
+        .then(data => {
+            const roleSelect = document.getElementById('role');
+            data.roles.forEach(role => {
+                const option = document.createElement('option');
+                option.value = role;
+                option.textContent = role;
+                if (selectedRowData.Role === role) {
+                    option.selected = true;
+                }
+                roleSelect.appendChild(option);
+            });
+        })
+        .catch(error => console.error('获取角色列表时出错:', error));
+    }
+
+    // 将表单添加到弹出框内容中
+    extraInfoContent.appendChild(form);
+
+    // 添加保存按钮
+    const saveButton = document.createElement('button');
+    saveButton.type = 'button';
+    saveButton.onclick = saveUserChanges;
+    saveButton.style = 'width: 100%; background-color: #007bff; color: white; border: none; padding: 10px; border-radius: 5px;';
+    saveButton.textContent = '保存';
+    form.appendChild(saveButton);
+}
+
+
+function saveUserChanges() {
+            const form = document.getElementById('editUserForm');
+            const formData = new FormData(form);
+            const updatedUser = {
+                Username: formData.get('username'),
+                Telephone: formData.get('telephone'),
+                Email: formData.get('email'),
+                Role: formData.get('role')
+            };
+
+            fetch(`http://127.0.0.1:8080/api/admin/updateUser`, {
+                method: 'POST',
+                headers: {
+                    'Authorization': `Bearer ${token}`,
+                    'Content-Type': 'application/json'
+                },
+                body: JSON.stringify({ ...updatedUser, Id: selectedRowData.Id })
+            })
+            .then(response => response.json())
+            .then(data => {
+                if (data.success) {
+                    alert('用户信息更新成功!');
+                    fetchUsers();
+                    closeModal('extraInfoModal');
+                } else {
+                    alert('用户信息更新失败:' + data.error);
+                }
+            });
+        }
+
+//用户个人信息
+function toggleUserInfo() {
+    const userModal = document.getElementById('userModal');
+    const userButton = document.getElementById('username');
+    const rect = userButton.getBoundingClientRect();
+    const modalWidth = 300; // 信息栏的宽度
+    const windowWidth = window.innerWidth; // 浏览器窗口的宽度
+
+    if (userModal.style.display === 'none' || userModal.style.display === '') {
+        fetch(`http://127.0.0.1:8080/api/admin/userInfo`, {
+            method: 'GET',
+            headers: {
+                'Authorization': `Bearer ${token}`,
+                'Content-Type': 'application/json'
+            }
+        })
+        .then(response => response.json())
+        .then(data => {
+            const userInfoContent = document.getElementById('userInfoContent');
+            let power;
+            switch (data.data.Role) {
+                case 'admin':
+                    power = '至高无上的主';
+                    break;
+                case 'supportRole':
+                    power = '运维';
+                    break;
+                case 'guest':
+                    power = '访客';
+                    break;
+                default:
+                    power = data.data.Role;
+            }
+
+            userInfoContent.innerHTML = `
+               <h2> 个人信息</h2>
+               <div class="apple-modal-content">
+                   <p class="apple-modal-text"><strong>用户名:</strong> ${data.data.Username}</p>
+                   <p class="apple-modal-text"><strong>账号:</strong> ${data.data.Account}</p>
+                   <p class="apple-modal-text"><strong>邮箱:</strong> ${data.data.Email}</p>
+                   <p class="apple-modal-text"><strong>电话:</strong> ${data.data.Telephone}</p>
+                   <p class="apple-modal-text"><strong>权限:</strong> ${power}</p>
+               </div>
+            `;
+
+            // 设置模态框位置
+            userModal.style.top = `${rect.bottom + window.scrollY + 10}px`;
+            const leftPosition = rect.right + window.scrollX + 20; // 右侧位置
+            const rightEdge = leftPosition + modalWidth;
+
+            if (rightEdge > windowWidth) {
+                const leftEdge = rect.left + window.scrollX - modalWidth - 20;
+                userModal.style.left = `${Math.max(20, leftEdge)}px`;
+            } else {
+                userModal.style.left = `${leftPosition}px`;
+            }
+
+            userModal.style.display = 'block';
+            userModal.style.opacity = '0'; // 先设置透明度为0
+            setTimeout(() => {
+                userModal.style.opacity = '1'; // 然后慢慢显示
+            }, 0); // 延迟一帧显示
+
+            userModal.style.transition = 'opacity 0.3s ease, top 0.3s ease, left 0.3s ease';
+
+            // 添加全局点击监听器
+            document.addEventListener('click', handleClickOutsideModal);
+        });
+
+    } else {
+        closeUserInfo();
+    }
+}
+
+function closeUserInfo() {
+    const userModal = document.getElementById('userModal');
+    userModal.style.opacity = '0'; // 先设置透明度为0
+    setTimeout(() => {
+        userModal.style.display = 'none'; // 然后隐藏
+    }, 300); // 延迟0.3秒,等待过渡结束
+
+    // 移除全局点击监听器
+    document.removeEventListener('click', handleClickOutsideModal);
+}
+
+
+
+function closeUserInfo() {
+    const userModal = document.getElementById('userModal');
+    userModal.style.opacity = '0'; // 先设置透明度为0
+    setTimeout(() => {
+        userModal.style.display = 'none'; // 然后隐藏
+    }, 300); // 延迟0.3秒,等待过渡结束
+
+    // 移除全局点击监听器
+    document.removeEventListener('click', handleClickOutsideModal);
+}
+
+function handleClickOutsideModal(event) {
+    const userModal = document.getElementById('userModal');
+    if (!userModal.contains(event.target) && event.target.id !== 'username') {
+        closeUserInfo();
+    }
+}
+
+
+//用户退出
+function logout() {
+            localStorage.removeItem('Authorization');
+            window.location.href = '/';
+        }
+
+
+function showUserManagement() {
+    document.getElementById('table-container').style.display = 'none';
+    document.getElementById('role-management').style.display = 'none'; // 隐藏角色管理页面
+    document.getElementById('user-management').style.display = 'block';
+    
+    // 检查用户是否有 create_user 权限
+    const hasCreateUserPermission = userPermissions.includes('create_user');
+
+    // 获取添加用户按钮元素
+    const addUserButton = document.getElementById('addUserButton');
+
+    // 根据权限显示或隐藏添加用户按钮
+    if (hasCreateUserPermission) {
+        addUserButton.style.display = 'block';
+    } else {
+        addUserButton.style.display = 'none';
+    }
+
+    // 加载用户列表
+    fetchUsers();
+        }
+
+function showTable() {
+    document.getElementById('user-management').style.display = 'none';
+    document.getElementById('role-management').style.display = 'none'; // 隐藏角色管理页面
+    document.getElementById('table-container').style.display = 'block';
+        }
+
+function toggleSidebar() {
+    const sidebar = document.getElementById('sidebar');
+    const content = document.getElementById('content');
+    sidebar.classList.toggle('hidden');
+    content.classList.toggle('expanded');
+
+    const toggleButton = document.querySelector('.toggle-sidebar');
+    if (sidebar.classList.contains('hidden')) {
+        toggleButton.innerHTML = '>';  // 当侧边栏隐藏时,显示右箭头
+    } else {
+        toggleButton.innerHTML = '<';  // 当侧边栏显示时,显示左箭头
+    }
+}
+
+
+
+
+
+
+
+ function fetchUsername() {
+    fetch(`http://127.0.0.1:8080/api/admin/userInfo`, {
+        method: 'GET',
+        headers: {
+            'Authorization': `Bearer ${token}`,
+            'Content-Type': 'application/json'
+        }
+    })
+    .then(response => response.json())
+    .then(data => {
+        document.getElementById('username').textContent = data.data.Username;
+        currentUserRole = data.data.Role; // 存储当前用户的角色
+        currentUserName = data.data.Username
+        // 使用获取到的角色,调用获取权限的接口
+        fetchPermissionsByRole(currentUserRole)
+            .then(() => {
+                //fetchApplications(LisOffset, pageSize); // 确保在权限获取之后才调用 fetchApplications
+                const startOffset = currentOffset;  // 当前的起始位置
+    const endOffset = startOffset + itemsPerPage - 1;  // 计算结束位置
+    fetchApplications(startOffset, endOffset);  // 传入计算好的起始和结束位置
+                // 检查用户是否有 upload_license 权限
+                const hasUploadLicensePermission = userPermissions.includes('upload_license');
+
+                // 获取上传按钮元素
+                const uploadButton = document.querySelector('.upload-button');
+
+                // 根据权限显示或隐藏上传按钮
+                // if (hasUploadLicensePermission) {
+                //     uploadButton.style.display = 'inline-block'; // 显示按钮
+                // } else {
+                //     uploadButton.style.display = 'none'; // 隐藏按钮
+                // }
+// 是否生成主动抓取lic按钮
+    checkCaptureLicensePermission();
+ // 检查是否有 capture_license_once_to_db 权限
+
+            });
+    });
+}
+
+function fetchPermissionsByRole(role) {
+    return fetch(`http://127.0.0.1:8080/api/admin/GetSelfRoles`, {
+        method: 'POST',
+        headers: {
+            'Authorization': `Bearer ${token}`,
+            'Content-Type': 'application/json'
+        },
+        body: JSON.stringify({ name: role })
+    })
+    .then(response => response.json())
+    .then(data => {
+        userPermissions = data.data.Permissions; // 获取用户的权限数组
+        console.log('userPermissions:', userPermissions);
+        
+        // 定义权限类别
+        const licensePermissions = ['upload_license', 'read_license'];
+        const userPermissionsCheck = [ 'create_user', 'read_user', 'update_user', 'delete_user'];
+        const rolePermissions = ['create_role', 'delete_role', 'update_role', 'get_role'];
+
+        const hasLicenseAccess = licensePermissions.some(permission => userPermissions.includes(permission));
+        const hasUserManagementAccess = userPermissionsCheck.some(permission => userPermissions.includes(permission));
+        const hasRoleManagementAccess = rolePermissions.some(permission => userPermissions.includes(permission));
+
+        // 根据权限渲染菜单并显示初始页面
+        renderMenuAndInitialPage(hasLicenseAccess, hasUserManagementAccess, hasRoleManagementAccess);
+    });
+}
+
+
+
+
+function renderMenuAndInitialPage(hasLicenseAccess, hasUserManagementAccess, hasRoleManagementAccess) {
+    const sidebar = document.querySelector('.sidebar');
+
+    // 清空现有菜单项
+    sidebar.innerHTML = '';
+
+    let initialPageShown = false;
+
+    // 动态添加菜单项并确定初始显示的页面
+    if (hasLicenseAccess) {
+        const licenseButton = document.createElement('button');
+        licenseButton.textContent = 'License 管理';
+        licenseButton.onclick = showTable;
+        sidebar.appendChild(licenseButton);
+
+        if (!initialPageShown) {
+            showTable(); // 如果有License权限,先显示License页面
+            initialPageShown = true;
+        }
+    }
+
+    if (hasUserManagementAccess) {
+        const userManagementButton = document.createElement('button');
+        userManagementButton.textContent = '用户管理';
+        userManagementButton.onclick = showUserManagement;
+        sidebar.appendChild(userManagementButton);
+
+        if (!initialPageShown) {
+            showUserManagement(); // 如果没有License权限,但有用户管理权限,显示用户管理页面
+            initialPageShown = true;
+        }
+    }
+
+    if (hasRoleManagementAccess) {
+        const roleManagementButton = document.createElement('button');
+        roleManagementButton.textContent = '角色管理';
+        roleManagementButton.onclick = showRoleManagement;
+        sidebar.appendChild(roleManagementButton);
+
+        if (!initialPageShown) {
+            showRoleManagement(); // 如果没有License和用户管理权限,但有角色管理权限,显示角色管理页面
+            initialPageShown = true;
+        }
+    }
+
+    if (!initialPageShown && sidebar.firstChild) {
+        sidebar.firstChild.click(); // 如果没有优先的权限页面,则显示菜单中的第一个页面
+    }
+}
+
+// 添加到每个模态框的 JavaScript 部分
+document.addEventListener('DOMContentLoaded', function() {
+  
+    // 获取所有模态框
+    const modals = document.querySelectorAll('.modal');
+    
+    // 为每个模态框添加点击事件监听器
+    modals.forEach(modal => {
+        modal.addEventListener('click', function(event) {
+            // 如果点击的是模态框背景而不是模态框内容,关闭模态框
+            if (event.target === modal) {
+                modal.style.display = 'none';
+            }
+        });
+    });
+});
+
+        // 页面初始化时调用 fetchUsername 函数
+        fetchUsername();
+        //fetchApplications(currentPage, pageSize);
+    </script>
+</body>
+</html>

+ 185 - 102
static/license_info/license_info.html

@@ -132,7 +132,7 @@ h1 {
     background-color: white;
     box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
     margin-bottom: 20px;
-    min-width: 300px; /* 添加最小宽度 */
+    min-width: 250px; /* 添加最小宽度 */
 }
 
 .apple-modal-title {
@@ -278,6 +278,17 @@ h1 {
         }
 
 
+/* 按钮容器的样式 */
+.button-container {
+    display: inline-flex; /* 确保按钮在同一行 */
+    align-items: center; /* 垂直居中对齐 */
+    gap: 10px; /* 设置按钮之间的间距为10px,可以根据需要调整 */
+}
+
+/* 统一按钮的样式 */
+
+
+
  /* 通用按钮样式 */
 .license-status-btn {
     display: inline-block;
@@ -293,6 +304,20 @@ h1 {
     border: none; /* 移除按钮边框 */
 }
 
+/* 查看详情按钮的特定样式 */
+.license-status-btn.view-details {
+    background-color: #add8e6; /* 浅蓝色背景 */
+    color: white;
+}
+
+/* 下载lic按钮的特定样式 */
+.license-status-btn.download-lic {
+    background-color: #4CAF50; /* 绿色背景 */
+    color: white;
+}
+
+
+
 /* 分发按钮样式 */
 .license-status-btn.distribute {
     background-color: #007bff; /* 蓝色背景 */
@@ -1388,7 +1413,7 @@ function fetchApplications(page, size) {
             row.innerHTML = `
                 <td></td> <!-- 空的单元格用于放置下拉按钮 -->
                 <td>${createEllipsisText(firstApplication.Creator)}</td>
-                <td>${createEllipsisText(firstApplication.ApplicationDate)}</td>
+                <td>${createEllipsisText(firstApplication.ApplicationDate)} ${firstApplication.ApplicationTime}</td>
                 <td>${createEllipsisText(firstApplication.GlxmName)}</td>
                 <td>${createEllipsisText(firstApplication.SalesPerson)}</td>
                 <td>${createEllipsisText(firstApplication.SupportPerson)}</td>
@@ -1413,6 +1438,7 @@ function fetchApplications(page, size) {
         // 在这里添加“查看详情”按钮
         licenseStatusCell.innerHTML += `<button class="license-status-btn view-details" onclick="showDetailsModal('${firstApplication.UniqueID}', false)">查看详情</button>`;
 
+
         row.appendChild(licenseStatusCell);
 }
 
@@ -1441,7 +1467,7 @@ function fetchApplications(page, size) {
             detailTable.appendChild(detailTableHeader);
 
             // 添加详细信息行 (子行)
-            applicationArray.forEach(detail => {
+applicationArray.forEach(detail => {
                 const detailInnerRow = document.createElement('tr');
                 detailInnerRow.innerHTML = `
                     <td>${createEllipsisText(detail.ProductName)}</td>
@@ -1470,14 +1496,55 @@ function fetchApplications(page, size) {
 
                 detailTable.appendChild(detailInnerRow);
         // 添加“查看详情”按钮 (子行)
-        const viewDetailsCell = document.createElement('td');
-        viewDetailsCell.innerHTML = `<button class="license-status-btn view-details" onclick="showDetailsModal('${detail.UniqueID}', true)">查看详情</button>`;
-        detailInnerRow.appendChild(viewDetailsCell);
+        // const viewDetailsCell = document.createElement('td');
+        // viewDetailsCell.innerHTML = `<button class="license-status-btn view-details" onclick="showDetailsModal('${detail.UniqueID}', true)">查看详情</button>`;
+        // detailInnerRow.appendChild(viewDetailsCell);
+        // detailTable.appendChild(detailInnerRow);
+
+            // 仅当主行状态为“分发”时添加“下载lic”按钮
+// 创建一个新的单元格用于放置按钮
+const actionCell = document.createElement('td');
+
+// 创建按钮容器
+const buttonContainer = document.createElement('div');
+buttonContainer.classList.add('button-container');
+
+// 创建“查看详情”按钮 (子行)
+const viewDetailsButton = document.createElement('button');
+viewDetailsButton.classList.add('license-status-btn', 'view-details');
+viewDetailsButton.textContent = '查看详情';
+viewDetailsButton.onclick = () => showDetailsModal(detail.UniqueID, true);
+
+// 将“查看详情”按钮添加到按钮容器
+buttonContainer.appendChild(viewDetailsButton);
+
+// 仅当主行状态为“已生成”时添加“下载lic”按钮
+if (firstApplication.LicenseFlage === "已生成") {
+    const downloadLicButton = document.createElement('button');
+    downloadLicButton.classList.add('license-status-btn', 'download-lic');
+    downloadLicButton.textContent = '下载lic';
+    
+    // 绑定当前行的 lic1 和 lic2 数据
+    downloadLicButton.onclick = () => downloadLicenseFiles(detail.lic1, detail.lic2);
+    
+    // 将“下载lic”按钮添加到按钮容器
+    buttonContainer.appendChild(downloadLicButton);
+}
+
+// 将按钮容器添加到actionCell
+actionCell.appendChild(buttonContainer);
+
+// 将actionCell添加到当前子行
+detailInnerRow.appendChild(actionCell);
+
+// 将子行添加到详细信息表格
+detailTable.appendChild(detailInnerRow);
 
-    detailTable.appendChild(detailInnerRow);
 
 
-                // 为每个详细信息行添加点击事件 (子行)
+   
+
+    // 为每个详细信息行添加点击事件 (子行)
                 detailInnerRow.addEventListener('click', (event) => {
                     if (!event.target.closest('button')) {
                         openActionModal(event, detail, 'licenseActionModal');
@@ -1793,16 +1860,35 @@ function CaptureLicenseOncefunc() {
 }
 
 
+function downloadLicenseFiles(lic1, lic2) {
+    // 下载license.dat文件
+    if (lic1) {
+        const blob1 = new Blob([lic1], { type: 'text/plain' });
+        const link1 = document.createElement('a');
+        link1.href = URL.createObjectURL(blob1);
+        link1.download = 'license.dat';
+        link1.click();
+    }
+
+    // 如果lic2不为空,下载license2.dat文件
+    if (lic2) {
+        const blob2 = new Blob([lic2], { type: 'text/plain' });
+        const link2 = document.createElement('a');
+        link2.href = URL.createObjectURL(blob2);
+        link2.download = 'license2.dat';
+        link2.click();
+    }
+}
+
+
+
 //查看lic所有信息
 // 查看lic所有信息
-function showDetailsModal(uniqueID, isChildRow = false, page = 1) {
+function showDetailsModal(uniqueID, isChildRow = false) {
     console.log("当前 uniqueID:", uniqueID);
     console.log("当前 isChildRow:", isChildRow);
-    console.log("当前页码:", page);
 
     let selectedData = null;
-    const itemsPerPage = 1; // 每页只显示一个子行数据
-    let currentPage = page; // 当前页码
 
     // 查找主行和子行数据
     console.log("查找主行和子行数据");
@@ -1820,84 +1906,64 @@ function showDetailsModal(uniqueID, isChildRow = false, page = 1) {
         return;
     }
 
-    let detailsHtml = `<div class="apple-modal-content">`;
+    let detailsHtml = `<table class="details-table" style="width:100%;">`;
 
-    // 如果是子行,直接显示该子行的信息,不进行分页
+    // 如果是子行,直接显示该子行的信息
     if (isChildRow) {
-    
-        // 查找唯一的子行数据
         const childData = selectedApplicationArray.find(data => data.UniqueID === uniqueID);
         if (childData) {
             detailsHtml += `
-                
-                <p class="apple-modal-text"><strong>产品名称:</strong> ${childData.ProductName}</p>
-                <p class="apple-modal-text"><strong>版本:</strong> ${childData.ProductVersion}</p>
-                <p class="apple-modal-text"><strong>节点数:</strong> ${childData.NodeCount}</p>
-                <p class="apple-modal-text"><strong>处理器:</strong> ${childData.oa_cpu}</p>
-                <p class="apple-modal-text"><strong>操作系统:</strong> ${childData.oa_operating_system}</p>
-                <p class="apple-modal-text"><strong>主MAC地址:</strong> ${childData.oa_main_mac}</p>
-                <p class="apple-modal-text"><strong>副MAC地址:</strong> ${childData.oa_second_mac}</p>
+                <tr>
+                    <th style="width: 50px;">序号</th><th>产品名称</th><th>版本</th><th>节点数</th><th>处理器</th>
+                    <th>操作系统</th><th>主MAC地址</th><th>副MAC地址</th><th>License状态</th> <!-- 新增 License 状态列 -->
+                </tr>
+                <tr>
+                    <td style="width: 50px;">${childData.oa_id}</td><td>${childData.ProductName}</td><td>${childData.ProductVersion}</td><td>${childData.NodeCount}</td>
+                    <td>${childData.oa_cpu}</td><td>${childData.oa_operating_system}</td><td>${childData.oa_main_mac}</td><td>${childData.oa_second_mac}</td>
+                    <td>${childData.LicenseFlage}</td> <!-- 显示 License 状态 -->
+                </tr>
             `;
         } else {
-            detailsHtml += `<p>未找到对应的子行记录。</p>`;
+            detailsHtml += `<tr><td colspan="9">未找到对应的子行记录。</td></tr>`;
         }
     } else {
-        // 如果是主行,使用分页显示主行和子行信息
-    
+        // 如果是主行,显示主行和所有子行信息
+        const firstData = selectedApplicationArray[0];
+        detailsHtml += `
+            <tr>
+                <th>创建人</th><th>申请日期</th><th>关联项目</th><th>销售人员</th>
+                <th>技服人员</th><th>总节点数</th><th>使用单位</th>
+            </tr>
+            <tr>
+                <td>${firstData.Creator}</td><td>${firstData.ApplicationDate}</td><td>${firstData.GlxmName}</td>
+                <td>${firstData.SalesPerson}</td><td>${firstData.SupportPerson}</td><td>${firstData.TotalNodes}</td>
+                <td>${firstData.Company}</td>
+            </tr>
+        `;
 
-        // 计算总页数
-        const totalItems = selectedApplicationArray.length;
-        const totalPages = totalItems + 1; // 主行为第一页,后续为子行
-        console.log("totalItems: ", totalItems);
-        console.log("totalPages: ", totalPages);
+        // 按 oa_id 对子行数据进行排序(升序)
+        const sortedApplicationArray = selectedApplicationArray.slice(1).sort((a, b) => a.oa_id - b.oa_id);
 
-        if (currentPage === 1) {
-            // 显示主行内容(第一页)
-  
-            detailsHtml += `<h3 class="apple-modal-title">项目信息</h3>`;
-            const firstData = selectedApplicationArray[0];
-            detailsHtml += `
-                <p class="apple-modal-text"><strong>创建人:</strong> ${firstData.Creator}</p>
-                <p class="apple-modal-text"><strong>申请日期:</strong> ${firstData.ApplicationDate}</p>
-                <p class="apple-modal-text"><strong>关联项目:</strong> ${firstData.GlxmName}</p>
-                <p class="apple-modal-text"><strong>销售人员:</strong> ${firstData.SalesPerson}</p>
-                <p class="apple-modal-text"><strong>技服人员:</strong> ${firstData.SupportPerson}</p>
-                <p class="apple-modal-text"><strong>总节点数:</strong> ${firstData.TotalNodes}</p>
-                <p class="apple-modal-text"><strong>使用单位:</strong> ${firstData.Company}</p>
-            `;
-        } else {
-            // 显示子行内容(从第二页开始)
+        // 显示子行信息
+        detailsHtml += `
+            <tr>
+                <th style="width: 50px;">序号</th><th>产品名称</th><th>版本</th><th>节点数</th><th>处理器</th>
+                <th>操作系统</th><th>主MAC地址</th><th>副MAC地址</th><th>License状态</th> <!-- 新增 License 状态列 -->
+            </tr>
+        `;
 
-            const dataIndex = currentPage - 2; // 当前页对应的子行数据索引,主行占用第一页
-            const data = selectedApplicationArray[dataIndex];
+        sortedApplicationArray.forEach((data, index) => {
             detailsHtml += `
-                <h4 class="apple-modal-subtitle">集群 ${currentPage - 1}</h4>
-                <p class="apple-modal-text"><strong>产品名称:</strong> ${data.ProductName}</p>
-                <p class="apple-modal-text"><strong>版本:</strong> ${data.ProductVersion}</p>
-                <p class="apple-modal-text"><strong>节点数:</strong> ${data.NodeCount}</p>
-                <p class="apple-modal-text"><strong>处理器:</strong> ${data.oa_cpu}</p>
-                <p class="apple-modal-text"><strong>操作系统:</strong> ${data.oa_operating_system}</p>
-                <p class="apple-modal-text"><strong>主MAC地址:</strong> ${data.oa_main_mac}</p>
-                <p class="apple-modal-text"><strong>副MAC地址:</strong> ${data.oa_second_mac}</p>
+                <tr>
+                    <td style="width: 50px;">${index + 1}</td><td>${data.ProductName}</td><td>${data.ProductVersion}</td><td>${data.NodeCount}</td>
+                    <td>${data.oa_cpu}</td><td>${data.oa_operating_system}</td><td>${data.oa_main_mac}</td><td>${data.oa_second_mac}</td>
+                    <td>${data.LicenseFlage}</td> <!-- 显示 License 状态 -->
+                </tr>
             `;
-        }
-
-        // 添加分页按钮
-        detailsHtml += `<div class="pagination-controls" style="text-align: center;">`;
-        if (currentPage > 1) {
-            detailsHtml += `<button style="font-size: 12px; padding: 5px 10px; width: 80px;" onclick="showDetailsModal('${uniqueID}', ${isChildRow}, ${currentPage - 1})">上一页</button>`;
-        }
-        detailsHtml += `<hr style="margin: 10px 0; border-top: 1px solid #ccc;">`; // 分隔符
-        if (currentPage < totalPages) {
-            detailsHtml += `<button style="font-size: 12px; padding: 5px 10px; width: 80px;" onclick="showDetailsModal('${uniqueID}', ${isChildRow}, ${currentPage + 1})">下一页</button>`;
-        }
-        detailsHtml += `</div>`;
+        });
     }
 
-    detailsHtml += `</div>`;
-
-    console.log("detailsHtml: ", detailsHtml);
-
+    detailsHtml += `</table>`;
     document.getElementById('detailsContent').innerHTML = detailsHtml;
     document.getElementById('detailsModal').style.display = 'block';
 }
@@ -1937,6 +2003,7 @@ function openUserActionModal(event, data, modalId) {
 
 
 
+
 // 为用户管理界面的每一行添加点击和右键事件监听器
 function fetchUsers() {
     fetch('http://127.0.0.1:8080/api/admin/userInfoAll', {
@@ -2059,6 +2126,10 @@ function showUserLicenseHistory() {
 }
 
 
+
+
+
+
 //用户菜单栏
 function openActionModal(event, data, modalId) {
     selectedRowData = data;
@@ -2668,7 +2739,7 @@ function showDistributionHistory() {
             'Authorization': `Bearer ${token}`,
             'Content-Type': 'application/json'
         },
-        body: JSON.stringify({ uniqueID: selectedRowData.UniqueID })
+        body: JSON.stringify({ uniqueID: selectedRowData.UniqueID, oa_request_id: selectedRowData.oa_request_id })
     })
     .then(response => response.json())
     .then(data => {
@@ -3424,40 +3495,52 @@ function showEditUserForm() {
             <label for="email" style="flex: 0 0 80px;">邮箱:</label>
             <input type="email" id="email" name="email" value="${selectedRowData.Email}" style="flex: 1;" required>
         </div>
-        
-        <div style="display: flex; align-items: center; margin-bottom: 15px;">
+    `;
+
+    // 检查用户是否有 'update_role' 权限,如果有,则显示权限选择框
+    if (userPermissions.includes('update_role')) {
+        const roleSelectDiv = document.createElement('div');
+        roleSelectDiv.style = 'display: flex; align-items: center; margin-bottom: 15px;';
+        roleSelectDiv.innerHTML = `
             <label for="role" style="flex: 0 0 80px;">权限:</label>
             <select id="role" name="role" style="flex: 1;" required></select>
-        </div>
+        `;
+        form.appendChild(roleSelectDiv);
 
-        <button type="button" onclick="saveUserChanges()" style="width: 100%; background-color: #007bff; color: white; border: none; padding: 10px; border-radius: 5px;">保存</button>
-    `;
+        // 获取角色列表并填充到选择框中
+        fetch('http://127.0.0.1:8080/api/admin/GetRoleNames', {
+            method: 'GET',
+            headers: {
+                'Authorization': `Bearer ${token}`,
+                'Content-Type': 'application/json'
+            }
+        })
+        .then(response => response.json())
+        .then(data => {
+            const roleSelect = document.getElementById('role');
+            data.roles.forEach(role => {
+                const option = document.createElement('option');
+                option.value = role;
+                option.textContent = role;
+                if (selectedRowData.Role === role) {
+                    option.selected = true;
+                }
+                roleSelect.appendChild(option);
+            });
+        })
+        .catch(error => console.error('获取角色列表时出错:', error));
+    }
 
     // 将表单添加到弹出框内容中
     extraInfoContent.appendChild(form);
 
-    // 获取角色列表并填充到选择框中
-    fetch('http://127.0.0.1:8080/api/admin/GetRoleNames', {
-        method: 'GET',
-        headers: {
-            'Authorization': `Bearer ${token}`,
-            'Content-Type': 'application/json'
-        }
-    })
-    .then(response => response.json())
-    .then(data => {
-        const roleSelect = document.getElementById('role');
-        data.roles.forEach(role => {
-            const option = document.createElement('option');
-            option.value = role;
-            option.textContent = role;
-            if (selectedRowData.Role === role) {
-                option.selected = true;
-            }
-            roleSelect.appendChild(option);
-        });
-    })
-    .catch(error => console.error('Error fetching roles:', error));
+    // 添加保存按钮
+    const saveButton = document.createElement('button');
+    saveButton.type = 'button';
+    saveButton.onclick = saveUserChanges;
+    saveButton.style = 'width: 100%; background-color: #007bff; color: white; border: none; padding: 10px; border-radius: 5px;';
+    saveButton.textContent = '保存';
+    form.appendChild(saveButton);
 }
 
 

+ 4 - 4
支持库建表语句.sql

@@ -74,7 +74,7 @@ CREATE TABLE target_OA_license (
     del_Time  DATETIME COMMENT '该表的行删除时间,非oa表中的删除',
     LAST_OPERATE_TIME   DATETIME COMMENT '该表的行最后操作时间,非oa表中的最后操作时间'
 );
--- 创建 LicenseInfo 表
+-- 创建 LicenseGenerateInfo 表
 CREATE TABLE License_generate_Info (
     ID INT IDENTITY(1,1),
     OA_ID INTEGER COMMENT 'oa 一行的ID',
@@ -90,8 +90,8 @@ CREATE TABLE License_generate_Info (
 --创建license分发记录表
 CREATE TABLE licenseRecordToUser (
     ID INT IDENTITY(1,1) PRIMARY KEY,
-    License_UniqueID VARCHAR(36),
-    OA_REQUESTID INTEGER COMMENT 'oa里的申请单请求ID',
+    OA_REQUESTID VARCHAR(36) COMMENT 'oa里的申请单请求ID',
+    License_UniqueID VARCHAR(36)  COMMENT '申请单里一条licUid,支撑库里的唯一值ID',
     user_UNIQUEID VARCHAR(255),
     User_Account VARCHAR(255),
     operator_UniqueID VARCHAR(255),
@@ -104,8 +104,8 @@ CREATE TABLE licenseRecordToUser (
 --创建license分发记录表
 CREATE TABLE licenseRecordToEmails (
     ID INT IDENTITY(1,1) PRIMARY KEY,
-    License_UniqueID VARCHAR(36),
     OA_REQUESTID INTEGER COMMENT 'oa里的申请单请求ID',
+    License_UniqueID VARCHAR(36),
     emails VARCHAR(255),
     operator_UniqueID VARCHAR(255),
     up_Time DATETIME,

+ 7 - 0
模拟oa库环境.sql

@@ -1,3 +1,10 @@
+DROP  TABLE XUGU.FORMTABLE_MAIN_146;
+DROP  TABLE  XUGU.FORMTABLE_MAIN_146_DT1;
+DROP  TABLE XUGU.HRMRESOURCE ;
+DROP  TABLE  XUGU.PRJ_PROJECTINFO;
+DROP  TABLE  XUGU.WORKFLOW_REQUESTBASE;
+DROP  TABLE  XUGU.WORKFLOW_SELECTITEM;
+
 create  database oadb;
 use oadb;
 CREATE USER xugu LOGIN xugu IDENTIFIED BY '123QWEasd!@';