Ver código fonte

前后端init

chengqian 5 meses atrás
pai
commit
569ea475c8
47 arquivos alterados com 1959 adições e 34 exclusões
  1. 4 4
      lc_basic_framework.sql
  2. 11 0
      server/api/v1/devices/enter.go
  3. 116 0
      server/api/v1/devices/screens.go
  4. 4 0
      server/api/v1/enter.go
  5. 11 0
      server/api/v1/project/enter.go
  6. 143 0
      server/api/v1/project/project.go
  7. 4 3
      server/config.yaml
  8. 2 1
      server/config/config.go
  9. 0 1
      server/core/server.go
  10. 77 0
      server/dao/dev_screens.go
  11. 91 0
      server/dao/pro_project.go
  12. 3 1
      server/dao/sys_user.go
  13. 6 3
      server/initialize/gorm.go
  14. 5 0
      server/initialize/router.go
  15. 0 1
      server/main.go
  16. 5 0
      server/model/common/request/common.go
  17. 17 0
      server/model/devices/common.go
  18. 33 0
      server/model/project/common.go
  19. 0 1
      server/plugin/plugin-tool/utils/check.go
  20. 5 0
      server/router/devices/enter.go
  21. 26 0
      server/router/devices/screens.go
  22. 4 0
      server/router/enter.go
  23. 5 0
      server/router/project/enter.go
  24. 28 0
      server/router/project/project.go
  25. 5 0
      server/service/devices/enter.go
  26. 27 0
      server/service/devices/screens.go
  27. 4 0
      server/service/enter.go
  28. 5 0
      server/service/project/enter.go
  29. 76 0
      server/service/project/project.go
  30. 1 2
      server/service/system/jwt_black_list.go
  31. 1 2
      server/service/system/sys_base_menu.go
  32. 1 2
      server/service/system/sys_dictionary.go
  33. 1 2
      server/service/system/sys_menu.go
  34. 4 0
      server/service/system/sys_user.go
  35. 229 0
      server/service/tcp/deviceMgr.go
  36. 108 0
      server/service/tcp/tcp.go
  37. 1 1
      web/.env.development
  38. 33 0
      web/src/api/project.js
  39. 40 0
      web/src/api/screens.js
  40. 17 0
      web/src/api/user.js
  41. 2 2
      web/src/core/config.js
  42. 23 0
      web/src/view/devicesAdmin/index.vue
  43. 402 0
      web/src/view/devicesAdmin/screens/screens.vue
  44. 1 1
      web/src/view/layout/index.vue
  45. 4 3
      web/src/view/login/index.vue
  46. 321 0
      web/src/view/project/index.vue
  47. 53 4
      web/src/view/superAdmin/user/user.vue

Diferenças do arquivo suprimidas por serem muito extensas
+ 4 - 4
lc_basic_framework.sql


+ 11 - 0
server/api/v1/devices/enter.go

@@ -0,0 +1,11 @@
+package devices
+
+import "server/service"
+
+type ApiGroup struct {
+	ScreensApi
+}
+
+var (
+	ScreensService = service.ServiceGroupApp.DevicesServiceGroup.ScreensService
+)

+ 116 - 0
server/api/v1/devices/screens.go

@@ -0,0 +1,116 @@
+package devices
+
+import (
+	"github.com/gin-gonic/gin"
+	"go.uber.org/zap"
+	"server/dao"
+	"server/global"
+	"server/model/common/request"
+	"server/model/common/response"
+	"server/model/devices"
+	"server/utils"
+)
+
+type ScreensApi struct{}
+
+func (a ScreensApi) AddScreens(c *gin.Context) {
+	var ReqScr devices.ReqScreens
+	err := c.ShouldBindJSON(&ReqScr)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	screens := dao.Screens{
+		ScreensName: ReqScr.ScreensName,
+		Sn:          ReqScr.Sn,
+		ProjectId:   ReqScr.ProjectId,
+		IPAddress:   ReqScr.IpAddress,
+		Remark:      ReqScr.Remark}
+
+	err = ScreensService.AddScreens(screens)
+	if err != nil {
+		global.GVA_LOG.Error(err.Error(), zap.Error(err))
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	response.OkWithMessage("添加成功", c)
+}
+
+func (a ScreensApi) DelScreens(c *gin.Context) {
+	var reqId request.GetById
+	err := c.ShouldBindJSON(&reqId)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(reqId, utils.IdVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	err = ScreensService.DelScreens(reqId.ID)
+	if err != nil {
+		global.GVA_LOG.Error("删除失败!", zap.Error(err))
+		response.FailWithMessage("删除失败", c)
+		return
+	}
+	response.OkWithMessage("删除成功", c)
+}
+
+func (a ScreensApi) UpdateScreens(c *gin.Context) {
+	var ReqScr devices.ReqScreens
+	err := c.ShouldBindJSON(&ReqScr)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(ReqScr, utils.IdVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	screens := dao.Screens{
+		ID:          ReqScr.ID,
+		ScreensName: ReqScr.ScreensName,
+		Sn:          ReqScr.Sn,
+		ProjectId:   ReqScr.ProjectId,
+		IPAddress:   ReqScr.IpAddress,
+		Remark:      ReqScr.Remark}
+	err = ScreensService.UpdateScreens(screens)
+	if err != nil {
+		global.GVA_LOG.Error("设置失败!", zap.Error(err))
+		response.FailWithMessage("设置失败", c)
+		return
+	}
+	response.OkWithMessage("设置成功", c)
+}
+
+func (a ScreensApi) GetScreensList(c *gin.Context) {
+	var pageInfo devices.SearchInfo
+	err := c.ShouldBindJSON(&pageInfo)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(pageInfo, utils.PageInfoVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	userID := utils.GetUserID(c)
+	list, total, err := ScreensService.GetScreensList(pageInfo, userID)
+	if err != nil {
+		global.GVA_LOG.Error("获取失败!", zap.Error(err))
+		response.FailWithMessage("获取失败", c)
+		return
+	}
+	response.OkWithDetailed(response.PageResult{
+		List:     list,
+		Total:    total,
+		Page:     pageInfo.Page,
+		PageSize: pageInfo.PageSize,
+	}, "获取成功", c)
+}

+ 4 - 0
server/api/v1/enter.go

@@ -1,13 +1,17 @@
 package v1
 
 import (
+	"server/api/v1/devices"
 	"server/api/v1/example"
+	"server/api/v1/project"
 	"server/api/v1/system"
 )
 
 type ApiGroup struct {
 	SystemApiGroup  system.ApiGroup
 	ExampleApiGroup example.ApiGroup
+	ProjectApiGroup project.ApiGroup
+	DevicesApiGroup devices.ApiGroup
 }
 
 var ApiGroupApp = new(ApiGroup)

+ 11 - 0
server/api/v1/project/enter.go

@@ -0,0 +1,11 @@
+package project
+
+import "server/service"
+
+type ApiGroup struct {
+	ProjectApi
+}
+
+var (
+	ProjectService = service.ServiceGroupApp.ProjectServiceGroup.ProjectService
+)

+ 143 - 0
server/api/v1/project/project.go

@@ -0,0 +1,143 @@
+package project
+
+import (
+	"github.com/gin-gonic/gin"
+	"go.uber.org/zap"
+	"server/dao"
+	"server/global"
+	"server/model/common/request"
+	"server/model/common/response"
+	"server/model/project"
+	"server/utils"
+)
+
+type ProjectApi struct{}
+
+func (a ProjectApi) AddProject(c *gin.Context) {
+	var ReqPro project.ReqProject
+	err := c.ShouldBindJSON(&ReqPro)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	project := dao.Project{ProjectName: ReqPro.ProjectName, StartTime: ReqPro.StartTime, Remark: ReqPro.Remark}
+
+	err = ProjectService.AddProject(project)
+	if err != nil {
+		global.GVA_LOG.Error(err.Error(), zap.Error(err))
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	response.OkWithMessage("添加成功", c)
+}
+
+func (a ProjectApi) DelProject(c *gin.Context) {
+	var reqId request.GetById
+	err := c.ShouldBindJSON(&reqId)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(reqId, utils.IdVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	err = ProjectService.DelProject(reqId.ID)
+	if err != nil {
+		global.GVA_LOG.Error("删除失败!", zap.Error(err))
+		response.FailWithMessage("删除失败", c)
+		return
+	}
+	response.OkWithMessage("删除成功", c)
+}
+
+func (a ProjectApi) UpdateProject(c *gin.Context) {
+	var ReqPro project.ReqProject
+	err := c.ShouldBindJSON(&ReqPro)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(ReqPro, utils.IdVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	project := dao.Project{GVA_MODEL: global.GVA_MODEL{
+		ID: ReqPro.ID,
+	}, ProjectName: ReqPro.ProjectName, StartTime: ReqPro.StartTime, Remark: ReqPro.Remark}
+	err = ProjectService.UpdateProject(project)
+	if err != nil {
+		global.GVA_LOG.Error("设置失败!", zap.Error(err))
+		response.FailWithMessage("设置失败", c)
+		return
+	}
+	response.OkWithMessage("设置成功", c)
+}
+
+func (a ProjectApi) GetProjectAndDetails(c *gin.Context) {
+	var pageInfo request.PageInfo
+	err := c.ShouldBindJSON(&pageInfo)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(pageInfo, utils.PageInfoVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	list, total, err := ProjectService.GetProjectAndDetails(pageInfo)
+	if err != nil {
+		global.GVA_LOG.Error("获取失败!", zap.Error(err))
+		response.FailWithMessage("获取失败", c)
+		return
+	}
+	response.OkWithDetailed(response.PageResult{
+		List:     list,
+		Total:    total,
+		Page:     pageInfo.Page,
+		PageSize: pageInfo.PageSize,
+	}, "获取成功", c)
+}
+
+func (a ProjectApi) GetProjectList(c *gin.Context) {
+	list, err := ProjectService.GetProjectList()
+	if err != nil {
+		global.GVA_LOG.Error("获取失败!", zap.Error(err))
+		response.FailWithMessage("获取失败", c)
+		return
+	}
+	response.OkWithDetailed(list, "获取成功", c)
+}
+
+func (a ProjectApi) ChangeProjects(c *gin.Context) {
+	var reqChange project.ReqChangeProjects
+	err := c.ShouldBindJSON(&reqChange)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	err = ProjectService.ChangeProjects(reqChange)
+	if err != nil {
+		global.GVA_LOG.Error("设置失败!", zap.Error(err))
+		response.FailWithMessage("设置失败", c)
+		return
+	}
+	response.OkWithMessage("设置成功", c)
+}
+
+func (a ProjectApi) GetProjectListByUserID(c *gin.Context) {
+	userID := utils.GetUserID(c)
+	list, err := ProjectService.GetProjectListByUserID(userID)
+	if err != nil {
+		global.GVA_LOG.Error("获取失败!", zap.Error(err))
+		response.FailWithMessage("获取失败", c)
+		return
+	}
+	response.OkWithDetailed(list, "获取成功", c)
+}

+ 4 - 3
server/config.yaml

@@ -123,9 +123,9 @@ mysql:
     prefix: ""
     port: "3306"
     config: charset=utf8mb4&parseTime=True&loc=Local
-    db-name: lc_finance
+    db-name: smart_intersection2.0
     username: root
-    password: root
+    password: 123456
     path: 127.0.0.1
     engine: ""
     log-mode: error
@@ -196,7 +196,7 @@ system:
     db-type: mysql
     oss-type: local
     router-prefix: ""
-    addr: 8888
+    addr: 8889
     iplimit-count: 15000
     iplimit-time: 3600
     use-multipoint: false
@@ -219,3 +219,4 @@ zap:
     max-age: 0
     show-line: true
     log-in-console: true
+

+ 2 - 1
server/config/config.go

@@ -23,7 +23,8 @@ type Server struct {
 	TencentCOS TencentCOS `mapstructure:"tencent-cos" json:"tencent-cos" yaml:"tencent-cos"`
 	AwsS3      AwsS3      `mapstructure:"aws-s3" json:"aws-s3" yaml:"aws-s3"`
 
-	Excel Excel `mapstructure:"excel" json:"excel" yaml:"excel"`
+	Excel  Excel  `mapstructure:"excel" json:"excel" yaml:"excel"`
+	Logrus Logrus `mapstructure:"logger" json:"logger" yaml:"logger"`
 
 	// 跨域配置
 	Cors CORS `mapstructure:"cors" json:"cors" yaml:"cors"`

+ 0 - 1
server/core/server.go

@@ -35,6 +35,5 @@ func RunWindowsServer() {
 	s := initServer(address, Router)
 
 	global.GVA_LOG.Info("server run success on ", zap.String("address", address))
-	fmt.Println("龙弛")
 	global.GVA_LOG.Error(s.ListenAndServe().Error())
 }

+ 77 - 0
server/dao/dev_screens.go

@@ -0,0 +1,77 @@
+package dao
+
+import (
+	"gorm.io/gorm"
+	"server/global"
+	"server/model/devices"
+)
+
+type Screens struct {
+	ID          uint           `gorm:"primarykey" json:"ID"`                //主键ID
+	ScreensName string         `gorm:"type:varchar(64)" json:"screensName"` //设备名称
+	Sn          string         `gorm:"type:varchar(64)" json:"sn"`          //设备sn
+	ProjectId   uint           `gorm:"type:int" json:"projectId"`           //所属项目
+	IPAddress   string         `gorm:"type:varchar(40)" json:"ipAddress"`   //IP地址
+	Remark      string         `gorm:"type:varchar(60)" json:"Remark"`      //备注
+	IsDeleted   gorm.DeletedAt `gorm:"default:null" json:"isDeleted"`       //是否删除
+	Status      int            `gorm:"type:int;default:0" json:"status"`    //在线状态 0=离线,1=在线
+
+	Project Project `gorm:"foreignkey:ProjectId"`
+}
+
+func (Screens) TableName() string {
+	return "dev_screens"
+}
+
+func (s Screens) AddScreens() error {
+	return global.GVA_DB.Model(&Screens{}).Create(&s).Error
+}
+
+func (s Screens) DelScreens(id int) error {
+	err := global.GVA_DB.Model(&s).Where("id = ?", id).Delete(&s).Error
+	return err
+}
+
+func (s Screens) GetScreensList(info devices.SearchInfo, uid uint) (screensList []Screens, total int64, err error) {
+	limit := info.PageSize
+	offset := info.PageSize * (info.Page - 1)
+	db := global.GVA_DB.Model(&Screens{})
+
+	if info.Sn != "" {
+		db.Where("sn like ?", "%"+info.Sn+"%")
+	}
+
+	if info.ProjectId != 0 {
+		db.Where("project_id = ?", info.ProjectId)
+	} else {
+		var projectIds []uint
+		list, _ := GetProjectListByUserIDNoPage(uid)
+		for _, project := range list {
+			projectIds = append(projectIds, project.ID)
+		}
+		db.Where("project_id in (?)", projectIds).Order("id DESC ")
+	}
+
+	err = db.Count(&total).Error
+	if err != nil {
+		return
+	}
+	err = db.Limit(limit).Offset(offset).Preload("Project").Find(&screensList).Error
+	return
+}
+
+func (s Screens) UpdateScreens() error {
+	return global.GVA_DB.Model(&Screens{}).Where("id = ?", s.ID).Updates(map[string]interface{}{
+		"screens_name": s.ScreensName,
+		"sn":           s.Sn,
+		"project_id":   s.ProjectId,
+		"ip_address":   s.IPAddress,
+		"remark":       s.Remark,
+	}).Error
+}
+
+func QueryScreensToDetail(id uint) (screensList []Screens, err error) {
+	db := global.GVA_DB.Model(&Screens{})
+	err = db.Where("project_id = ?", id).Find(&screensList).Error
+	return
+}

+ 91 - 0
server/dao/pro_project.go

@@ -0,0 +1,91 @@
+package dao
+
+import (
+	"server/global"
+	"server/model/common/request"
+	"time"
+)
+
+type Project struct {
+	global.GVA_MODEL
+	ProjectName string    `gorm:"type:varchar(64)" json:"projectName"`           //项目名称
+	StartTime   time.Time `gorm:"type:date" json:"startTime" time_format:"date"` //开始时间
+	Remark      string    `gorm:"type:varchar(64);default:''" json:"remark"`     //备注
+
+	Users []SysUser `json:"users" gorm:"many2many:user_project;"`
+}
+
+func (Project) TableName() string {
+	return "project"
+}
+
+func (p Project) AddProject() error {
+	return global.GVA_DB.Model(&Project{}).Create(&p).Error
+}
+
+func (p Project) DelProject(id int) error {
+	err := global.GVA_DB.Model(&p).Where("id = ?", id).Delete(&p).Error
+	return err
+}
+
+func (p Project) UpdateProject() error {
+	return global.GVA_DB.Model(&Project{}).Debug().Where("id = ?", p.GVA_MODEL.ID).Updates(map[string]interface{}{
+		"project_name": p.ProjectName,
+		"start_time":   p.StartTime,
+		"remark":       p.Remark,
+	}).Error
+}
+
+// 根据用户ID查询对应的项目
+func (p Project) GetProjectListByUserID(info request.PageInfo, userid uint) (projectList []Project, total int64, err error) {
+	limit := info.PageSize
+	offset := info.PageSize * (info.Page - 1)
+	db := global.GVA_DB.Model(&Project{}).
+		Joins("JOIN user_project ON project.id = user_project.project_id").
+		Where("user_project.sys_user_id = ?", userid)
+
+	err = db.Count(&total).Error
+
+	err = db.Limit(limit).Offset(offset).Scan(&projectList).Error
+	return
+}
+
+// 查询项目列表
+func (p Project) GetProjectAndDetails(info request.PageInfo) (projectList []Project, total int64, err error) {
+	limit := info.PageSize
+	offset := info.PageSize * (info.Page - 1)
+	db := global.GVA_DB.Model(&Project{}).Find(&projectList)
+
+	err = db.Count(&total).Error
+
+	err = db.Limit(limit).Offset(offset).Scan(&projectList).Error
+	return
+}
+
+// 增加user_project记录
+func AddUserProjectRelation(userID, projectID uint) error {
+	return global.GVA_DB.Table("user_project").Exec("INSERT INTO user_project (sys_user_id, project_id) VALUES (?, ?)", userID, projectID).Error
+}
+
+// 获取所有项目列表
+func GetProjectList() (projectList []Project, err error) {
+	err = global.GVA_DB.Model(&Project{}).Find(&projectList).Error
+	return projectList, err
+}
+
+func GetProjectListByUserIDNoPage(userid uint) (projectList []Project, err error) {
+	db := global.GVA_DB.Model(&Project{}).
+		Joins("JOIN user_project ON project.id = user_project.project_id").
+		Where("user_project.sys_user_id = ?", userid).Scan(&projectList).Error
+	return projectList, db
+}
+
+func DelOldProjectsByUserId(userId uint) error {
+	db := global.GVA_DB.Table("user_project").Exec("DELETE FROM user_project WHERE sys_user_id = ?", userId).Error
+	return db
+}
+
+// 批量新增新的项目id
+func ChangeProjects(up []request.UserProject) error {
+	return global.GVA_DB.Table("user_project").Create(&up).Error
+}

+ 3 - 1
server/dao/sys_user.go

@@ -22,6 +22,8 @@ type SysUser struct {
 	Phone       string       `json:"phone"  gorm:"comment:用户手机号"`                     // 用户手机号
 	Email       string       `json:"email"  gorm:"comment:用户邮箱"`                      // 用户邮箱
 	Enable      int          `json:"enable" gorm:"default:1;comment:用户是否被冻结 1正常 2冻结"` //用户是否被冻结 1正常 2冻结
+
+	Projects []Project `json:"projects" gorm:"many2many:user_project;"`
 }
 
 func (SysUser) TableName() string {
@@ -129,7 +131,7 @@ func SetUserAuthorities(id uint, authorityId uint) (err error) {
 
 // SetUserInfo 修改用户信息
 func (u SysUser) SetUserInfo() error {
-	return global.GVA_DB.Model(&SysUser{}).Updates(&u).Error
+	return global.GVA_DB.Model(&SysUser{}).Where("id = ?", u.ID).Updates(&u).Error
 }
 
 // SetUserInfoById 修改用户信息 按id

+ 6 - 3
server/initialize/gorm.go

@@ -1,11 +1,10 @@
 package initialize
 
 import (
-	"os"
-	"server/dao"
-
 	"go.uber.org/zap"
 	"gorm.io/gorm"
+	"os"
+	"server/dao"
 	"server/global"
 	"server/model/example"
 )
@@ -46,6 +45,10 @@ func RegisterTables() {
 		example.ExaFile{},
 		example.ExaFileChunk{},
 		dao.ExaFileUploadAndDownload{},
+
+		dao.Project{},
+
+		dao.Screens{},
 	)
 	if err != nil {
 		global.GVA_LOG.Error("register table failed", zap.Error(err))

+ 5 - 0
server/initialize/router.go

@@ -42,6 +42,8 @@ func Routers() *gin.Engine {
 	InstallPlugin(Router) // 安装插件
 	systemRouter := router.RouterGroupApp.System
 	exampleRouter := router.RouterGroupApp.Example
+	projectRouter := router.RouterGroupApp.Project
+	devicesRouter := router.RouterGroupApp.Devices
 	// 如果想要不使用nginx代理前端网页,可以修改 web/.env.production 下的
 	// VUE_APP_BASE_API = /
 	// VUE_APP_BASE_PATH = http://localhost
@@ -85,6 +87,9 @@ func Routers() *gin.Engine {
 		systemRouter.InitAuthorityBtnRouterRouter(PrivateGroup)  // 字典详情管理
 
 		exampleRouter.InitFileUploadAndDownloadRouter(PrivateGroup)
+
+		projectRouter.InitProjectRouter(PrivateGroup) //注册项目管理路由
+		devicesRouter.InitScreensRouter(PrivateGroup) //注册设备管理路由
 	}
 
 	global.GVA_LOG.Info("router register success")

+ 0 - 1
server/main.go

@@ -3,7 +3,6 @@ package main
 import (
 	_ "go.uber.org/automaxprocs"
 	"go.uber.org/zap"
-
 	"server/core"
 	"server/global"
 	"server/initialize"

+ 5 - 0
server/model/common/request/common.go

@@ -26,3 +26,8 @@ type GetAuthorityId struct {
 }
 
 type Empty struct{}
+
+type UserProject struct {
+	SysUserId uint `json:"sys_user_id"`
+	ProjectId uint `json:"project_id"`
+}

+ 17 - 0
server/model/devices/common.go

@@ -0,0 +1,17 @@
+package devices
+
+type SearchInfo struct {
+	Page      int    `json:"page" form:"page"`           // 页码
+	PageSize  int    `json:"pageSize" form:"pageSize"`   // 每页大小
+	ProjectId uint   `json:"projectId" form:"projectId"` //关键字1
+	Sn        string `json:"sn" form:"sn"`               //关键字2
+}
+
+type ReqScreens struct {
+	ID          uint   `json:"id"`          //ID
+	ScreensName string `json:"screensName"` //名称
+	Sn          string `json:"sn"`          //设备sn
+	ProjectId   uint   `json:"projectId"`   //归属项目id
+	IpAddress   string `json:"ipAddress"`   //ip地址
+	Remark      string `json:"remark"`      //备注
+}

+ 33 - 0
server/model/project/common.go

@@ -0,0 +1,33 @@
+package project
+
+import (
+	"server/dao"
+	"time"
+)
+
+type ReqProject struct {
+	ID          uint      `json:"id"`          //ID
+	ProjectName string    `json:"projectName"` //名称
+	StartTime   time.Time `json:"startTime"`   //开始时间
+	Remark      string    `json:"remark"`      //备注
+}
+
+type ProjectDetails struct {
+	Project dao.Project `json:"project"`
+
+	RelationDev RelationDev `json:"relationDev"`
+}
+
+type RelationDev struct {
+	Screens []dao.Screens `json:"screensList"`
+}
+
+type ReqChangeProjects struct {
+	ID     uint   `json:"id"`
+	ProIds []uint `json:"proIds"`
+}
+
+type UserProject struct {
+	SysUserId uint `json:"sys_user_id"`
+	ProjectId uint `json:"project_id"`
+}

+ 0 - 1
server/plugin/plugin-tool/utils/check.go

@@ -3,7 +3,6 @@ package utils
 import (
 	"fmt"
 	"server/dao"
-
 	"server/global"
 )
 

+ 5 - 0
server/router/devices/enter.go

@@ -0,0 +1,5 @@
+package devices
+
+type RouterGroup struct {
+	ScreensRouter
+}

+ 26 - 0
server/router/devices/screens.go

@@ -0,0 +1,26 @@
+package devices
+
+import (
+	"github.com/gin-gonic/gin"
+	v1 "server/api/v1"
+	"server/middleware"
+)
+
+type ScreensRouter struct{}
+
+func (s *ScreensRouter) InitScreensRouter(Router *gin.RouterGroup) {
+	screensRouter := Router.Group("screens").Use(middleware.OperationRecord())
+	screensRouterWithoutRecord := Router.Group("screens")
+
+	baseApi := v1.ApiGroupApp.DevicesApiGroup.ScreensApi
+
+	{
+		screensRouter.POST("addScreens", baseApi.AddScreens)      // 增加LED
+		screensRouter.PUT("updateScreens", baseApi.UpdateScreens) // 编辑
+		screensRouter.DELETE("delScreens", baseApi.DelScreens)    // 删除
+	}
+
+	{
+		screensRouterWithoutRecord.POST("getScreensList", baseApi.GetScreensList) //获取显示屏列表
+	}
+}

+ 4 - 0
server/router/enter.go

@@ -1,13 +1,17 @@
 package router
 
 import (
+	"server/router/devices"
 	"server/router/example"
+	"server/router/project"
 	"server/router/system"
 )
 
 type RouterGroup struct {
 	System  system.RouterGroup
 	Example example.RouterGroup
+	Project project.RouterGroup
+	Devices devices.RouterGroup
 }
 
 var RouterGroupApp = new(RouterGroup)

+ 5 - 0
server/router/project/enter.go

@@ -0,0 +1,5 @@
+package project
+
+type RouterGroup struct {
+	ProjectRouter
+}

+ 28 - 0
server/router/project/project.go

@@ -0,0 +1,28 @@
+package project
+
+import (
+	"github.com/gin-gonic/gin"
+	v1 "server/api/v1"
+	"server/middleware"
+)
+
+type ProjectRouter struct{}
+
+func (p *ProjectRouter) InitProjectRouter(Router *gin.RouterGroup) {
+	projectRouter := Router.Group("project").Use(middleware.OperationRecord())
+	projectRouterWithoutRecord := Router.Group("project")
+
+	baseApi := v1.ApiGroupApp.ProjectApiGroup.ProjectApi
+	{
+		projectRouter.POST("addProject", baseApi.AddProject)      // 增加项目
+		projectRouter.PUT("updateProject", baseApi.UpdateProject) // 编辑项目
+		projectRouter.DELETE("delProject", baseApi.DelProject)    // 删除项目
+
+		projectRouter.PUT("changeProjects", baseApi.ChangeProjects) // 修改用户归属项目
+	}
+	{
+		projectRouterWithoutRecord.POST("getProjectAndDetails", baseApi.GetProjectAndDetails)    //获取项目列表和设备详情
+		projectRouterWithoutRecord.GET("getProjectList", baseApi.GetProjectList)                 //获取项目列表
+		projectRouterWithoutRecord.GET("getProjectListByUserID", baseApi.GetProjectListByUserID) //获取项目列表根据用户id
+	}
+}

+ 5 - 0
server/service/devices/enter.go

@@ -0,0 +1,5 @@
+package devices
+
+type ServiceGroup struct {
+	ScreensService
+}

+ 27 - 0
server/service/devices/screens.go

@@ -0,0 +1,27 @@
+package devices
+
+import (
+	"server/dao"
+	"server/model/devices"
+)
+
+type ScreensService struct{}
+
+func (s ScreensService) AddScreens(screens dao.Screens) error {
+	err := screens.AddScreens()
+	return err
+}
+
+func (s ScreensService) DelScreens(id int) error {
+	err := dao.Screens{}.DelScreens(id)
+	return err
+}
+
+func (s ScreensService) UpdateScreens(screens dao.Screens) error {
+	err := screens.UpdateScreens()
+	return err
+}
+func (s ScreensService) GetScreensList(info devices.SearchInfo, userId uint) (list interface{}, total int64, err error) {
+	screensList, t, err := dao.Screens{}.GetScreensList(info, userId)
+	return screensList, t, err
+}

+ 4 - 0
server/service/enter.go

@@ -1,13 +1,17 @@
 package service
 
 import (
+	"server/service/devices"
 	"server/service/example"
+	"server/service/project"
 	"server/service/system"
 )
 
 type ServiceGroup struct {
 	SystemServiceGroup  system.ServiceGroup
 	ExampleServiceGroup example.ServiceGroup
+	ProjectServiceGroup project.ServiceGroup
+	DevicesServiceGroup devices.ServiceGroup
 }
 
 var ServiceGroupApp = new(ServiceGroup)

+ 5 - 0
server/service/project/enter.go

@@ -0,0 +1,5 @@
+package project
+
+type ServiceGroup struct {
+	ProjectService
+}

+ 76 - 0
server/service/project/project.go

@@ -0,0 +1,76 @@
+package project
+
+import (
+	"server/dao"
+	"server/model/common/request"
+	"server/model/project"
+)
+
+type ProjectService struct{}
+
+func (s ProjectService) AddProject(project dao.Project) error {
+	err := project.AddProject()
+	return err
+}
+
+func (s ProjectService) DelProject(id int) error {
+	err := dao.Project{}.DelProject(id)
+	return err
+}
+
+func (s ProjectService) UpdateProject(project dao.Project) error {
+	err := project.UpdateProject()
+	return err
+}
+
+func (s ProjectService) GetProjectAndDetails(info request.PageInfo) (list interface{}, total int64, err error) {
+	projectList, total, err := dao.Project{}.GetProjectAndDetails(info)
+
+	var details []project.ProjectDetails
+
+	for _, Pjc := range projectList {
+		detail := project.ProjectDetails{Project: Pjc}
+		screensList, _ := dao.QueryScreensToDetail(Pjc.ID)
+
+		detail.RelationDev.Screens = screensList
+
+		details = append(details, detail)
+	}
+	return details, total, nil
+}
+
+func (s ProjectService) GetProjectList() (list interface{}, err error) {
+	list, err = dao.GetProjectList()
+	return list, err
+}
+
+func (s ProjectService) ChangeProjects(change project.ReqChangeProjects) error {
+	//先删除原来记录
+	err := dao.DelOldProjectsByUserId(change.ID)
+	if err != nil {
+		return err
+	}
+
+	if len(change.ProIds) != 0 {
+		//组装对象批量新增
+		var UserProjects []request.UserProject
+
+		for _, pid := range change.ProIds {
+			UserProjects = append(UserProjects, request.UserProject{
+				SysUserId: change.ID,
+				ProjectId: pid,
+			})
+		}
+		err = dao.ChangeProjects(UserProjects)
+		if err != nil {
+			return err
+		}
+		return nil
+	}
+	return nil
+}
+
+func (s ProjectService) GetProjectListByUserID(id uint) (interface{}, error) {
+	list, err := dao.GetProjectListByUserIDNoPage(id)
+	return list, err
+}

+ 1 - 2
server/service/system/jwt_black_list.go

@@ -2,9 +2,8 @@ package system
 
 import (
 	"context"
-	"server/dao"
-
 	"go.uber.org/zap"
+	"server/dao"
 
 	"server/global"
 	"server/utils"

+ 1 - 2
server/service/system/sys_base_menu.go

@@ -2,9 +2,8 @@ package system
 
 import (
 	"errors"
-	"server/dao"
-
 	"gorm.io/gorm"
+	"server/dao"
 	"server/global"
 )
 

+ 1 - 2
server/service/system/sys_dictionary.go

@@ -2,9 +2,8 @@ package system
 
 import (
 	"errors"
-	"server/dao"
-
 	"gorm.io/gorm"
+	"server/dao"
 	"server/global"
 )
 

+ 1 - 2
server/service/system/sys_menu.go

@@ -2,9 +2,8 @@ package system
 
 import (
 	"errors"
-	"server/dao"
-
 	"gorm.io/gorm"
+	"server/dao"
 	"server/model/common/request"
 )
 

+ 4 - 0
server/service/system/sys_user.go

@@ -88,6 +88,10 @@ func (userService *UserService) GetUserInfoList(info request.PageInfo) (list int
 
 	userList, total, err := dao.QueryUserInfoList(limit, offset)
 
+	for i, user := range userList {
+		projectList, _ := dao.GetProjectListByUserIDNoPage(user.ID)
+		userList[i].Projects = projectList
+	}
 	return userList, total, err
 }
 

+ 229 - 0
server/service/tcp/deviceMgr.go

@@ -0,0 +1,229 @@
+package tcp
+
+import (
+	"encoding/hex"
+	"fmt"
+	"net"
+	"server/dao"
+	"time"
+)
+
+var (
+	Devices = make(map[string]*Device) // 存储连接的全局 map
+)
+
+type Device struct {
+	Info     dao.Screens
+	Conn     net.Conn
+	LastTime time.Time
+}
+
+// 实例所有设备
+//func InitDevices() {
+//	// 将数据库中的设备全部离线
+//	devs, _ := deviceDao.QueryAllScreens()
+//	for _, screen := range devs {
+//		if screen.Status != 0 {
+//			screen.Status = 0
+//			now := time.Now()
+//			screen.LastOfflineTime = &now
+//		}
+//		Devices[screen.Sn] = &Device{Info: screen}
+//	}
+//}
+
+func (s *Device) Start(conn net.Conn) {
+	s.Conn = conn
+	go s.Process()
+}
+
+func (s *Device) Process() {
+	// 函数执行完之后关闭连接
+	defer s.Conn.Close()
+	for {
+		buf := make([]byte, 256)
+		// 将tcp连接读取到的数据读取到byte数组中, 返回读取到的byte的数目
+		n, err := s.Conn.Read(buf)
+		if err != nil {
+			// 从客户端读取数据的过程中发生错误 这里如果没读到可以视为设备离线了
+			//logger.Error.Errorf("read from client failed [离线了], err:%v", err)
+
+			break
+		}
+		data := hex.EncodeToString(buf[:n])
+		fmt.Println("读取:", data)
+		//if !strings.Contains(strings.ToLower(data), "fe5c4b89") {
+		//	continue
+		//}
+		//switch data[16:18] {
+		//case "61":
+		//	// fe5c4b89 2a000000 62 00000000 17000000 31 23 32303233(年) 3035(月) 3135(日) 3031(星期) 3038(时) 3334(分) 3135(秒) 23 303630(心跳包时间) 23 ffff
+		//	//目前登录只做查找设备的操作 (假登录)
+		//	buffer := protocol.AuthDataPack{}.AuthLogin()
+		//	// 通过sn 查设备是否存在 不存在return 存在就保存con 和 info
+		//	_, err := deviceDao.QueryScreenBySn(data[34:50])
+		//	if errors.Is(err, gorm.ErrRecordNotFound) {
+		//		logger.Logger.Errorf("Process[case '61'] SN not found \n")
+		//		break
+		//	}
+		//	s.Conn.Write(buffer.Bytes())
+		//case "91":
+		//	dev := Devices[data[34:50]]
+		//	if dev.Info.Status == 0 {
+		//		state := 1
+		//		//上线 则修改数据库上线时间
+		//		err := Dev.UpdateScreensStatusAndOnline(dev.Info.Sn, state)
+		//		if err != nil {
+		//			logger.Logger.Error("Process[case '91'] UpdateScreensStatusAndOnline err", zap.Error(err))
+		//			continue
+		//		}
+		//		dev.Info.Status = 1
+		//		now := time.Now()
+		//		dev.Info.LastOnlineTime = &now
+		//		logger.Logger.Debugf("设备 [%v] 上线", dev.Info.ScreensName) // 登录
+		//	}
+		//	dev.Conn = s.Conn
+		//	dev.LastTime = time.Now()
+		//	logger.Logger.Debugf("设备 [%v] 心跳", dev.Info.ScreensName)
+		//default:
+		//	fmt.Println("读取:", data)
+		//}
+	}
+}
+
+//func IsOnline() {
+//	t := time.NewTicker(1 * time.Minute) //每分钟
+//	for {
+//		select {
+//		case <-t.C:
+//			for _, device := range Devices {
+//				//符合条件
+//				if (time.Now().Add(-5*time.Minute).After(device.LastTime) || device.LastTime.IsZero()) && device.Info.Status != 0 {
+//					//修改数据库和内存
+//					//离线
+//					state := 0
+//					err := Dev.UpdateScreensStatusAndOffline(device.Info.Sn, state)
+//					if err != nil {
+//						logger.Logger.Error("[Handle] UpdateScreensStatus err", zap.Error(err))
+//						continue
+//					}
+//					device.Info.Status = 0
+//					now := time.Now()
+//					device.Info.LastOfflineTime = &now
+//					logger.Logger.Debugf("设备 [%v] 离线了...", device.Info.ScreensName)
+//				}
+//			}
+//		}
+//	}
+//}
+
+//func (s *Device) SwitchScreen(onOff int) error {
+//	if s.Conn == nil {
+//		return errors.New("connection is nil")
+//	}
+//	pack := protocol.SwitchDataPack{Type: 0x52} //熄屏
+//	if onOff == 1 {
+//		pack = protocol.SwitchDataPack{Type: 0x51} //亮屏
+//	}
+//	buf := pack.SwitchScreens() //获取开关屏指令
+//	_, err := s.Conn.Write(buf.Bytes())
+//	if err != nil {
+//		logger.Logger.Errorf("SwitchScreen write failed, err:%v", err)
+//		return err
+//	}
+//	return nil
+//}
+//
+//func (s *Device) SendInternalCode(content []promodel.InternalCodeContent) error {
+//	if s.Conn == nil {
+//		return errors.New("connection is nil")
+//	}
+//	pack := protocol.InternalCodeDataPack{}
+//	//获取要写入连接的字节数组
+//	buf := pack.SendInternalCode(content)
+//	_, err := s.Conn.Write(buf.Bytes())
+//	if err != nil {
+//		logger.Logger.Errorf("SendInternalCode write failed, err:%v", err)
+//		return err
+//	}
+//
+//	return nil
+//}
+//
+//func (s *Device) VoiceBroad(broad string) error {
+//	if s.Conn == nil {
+//		return errors.New("connection is nil")
+//	}
+//	pack := protocol.VoiceBroadDataPack{}
+//	buf := pack.VoiceBroad(broad)
+//	_, err := s.Conn.Write(buf.Bytes())
+//	if err != nil {
+//		logger.Logger.Errorf("VoiceBroad write failed, err:%v", err)
+//		return err
+//	}
+//	return nil
+//}
+//
+//func (s *Device) SetBrightness(bright byte) error {
+//	if s.Conn == nil {
+//		return errors.New("connection is nil")
+//	}
+//	pack := protocol.SetBrightnessDataPack{}
+//	buf := pack.SetBrightness(bright)
+//	_, err := s.Conn.Write(buf.Bytes())
+//	if err != nil {
+//		logger.Logger.Errorf("SetBrightness write failed, err:%v", err)
+//		return err
+//	}
+//	return nil
+//}
+//
+//func SwitchScreen(sn string, onOff int) error {
+//	device := Devices[sn]
+//	err := device.SwitchScreen(onOff)
+//	if err != nil {
+//		return err
+//	}
+//	return nil
+//}
+//
+//func SendInternalCode(sn string, content []promodel.InternalCodeContent) error {
+//	device := Devices[sn]
+//	err := device.SendInternalCode(content)
+//	if err != nil {
+//		return err
+//	}
+//	return nil
+//}
+//
+//func VoiceBroad(sn string, broad string) error {
+//	device := Devices[sn]
+//	err := device.VoiceBroad(broad)
+//	if err != nil {
+//		return err
+//	}
+//	return nil
+//}
+//
+//func SetBrightness(sn string, bright int) error {
+//	device := Devices[sn]
+//	err := device.SetBrightness(brightnessMap[bright])
+//	if err != nil {
+//		return err
+//	}
+//	return nil
+//}
+
+var brightnessMap = map[int]byte{
+	0:   0x19,
+	10:  0x18,
+	20:  0x16,
+	30:  0x14,
+	40:  0x12,
+	50:  0x10,
+	60:  0x08,
+	70:  0x06,
+	80:  0x04,
+	90:  0x02,
+	100: 0x00,
+}

+ 108 - 0
server/service/tcp/tcp.go

@@ -0,0 +1,108 @@
+package tcp
+
+import (
+	"errors"
+	"go.uber.org/zap"
+	"net"
+	"server/global"
+	"strings"
+	"sync"
+	"time"
+)
+
+func ListenTcp() {
+	var listen net.Listener
+	var err error
+	// 监听当前的tcp连接
+	for {
+		listen, err = net.Listen("tcp", "0.0.0.0:9200")
+		if err != nil {
+			//logger.Logger.Errorf("Listen failed, err: %v. Retrying in 5 seconds...", err)
+			global.GVA_LOG.Error("Listen failed, Retrying in 5 seconds...", zap.Error(err))
+			time.Sleep(5 * time.Second) // 休眠一段时间后重试
+			continue
+		}
+		break // 成功监听后退出循环
+	}
+
+	tracker := NewConnectionTracker() //创建连接检测器
+
+	for {
+		conn, err := listen.Accept()
+		if err != nil {
+			//logger.Logger.Errorf("Accept failed, err:%v", err)
+			global.GVA_LOG.Error("Accept failed,", zap.Error(err))
+			continue
+		}
+		err = CheckConn(conn, tracker)
+		if err != nil {
+			conn.Close() // 如果是恶意连接,则关闭连接
+			continue
+		}
+	}
+}
+
+func CheckConn(conn net.Conn, tracker *ConnectionTracker) error {
+	//logger.Logger.Debugf("StartDevice addr:%s", conn.RemoteAddr().String())
+	global.GVA_LOG.Debug("StartDevice addr:", zap.Any("data", conn.RemoteAddr().String()))
+	arr := strings.Split(conn.RemoteAddr().String(), ":")
+	ip := arr[0]
+	// 记录连接
+	tracker.recordConnection(ip)
+
+	// 检查是否为恶意连接
+	if tracker.isMalicious(ip) {
+		//logger.Logger.Debugf("恶意连接检测到 ip: %s\n", ip)
+		global.GVA_LOG.Debug("")
+		return errors.New("connection is Malicious")
+	}
+	device := Device{}
+	device.Start(conn)
+	return nil
+}
+
+type ConnectionTracker struct {
+	mu          sync.Mutex
+	connections map[string][]time.Time // 存储每个 IP 的连接时间戳
+}
+
+func NewConnectionTracker() *ConnectionTracker {
+	return &ConnectionTracker{
+		connections: make(map[string][]time.Time),
+	}
+}
+
+func (ct *ConnectionTracker) recordConnection(ip string) {
+	ct.mu.Lock()
+	defer ct.mu.Unlock()
+
+	now := time.Now()
+	ct.connections[ip] = append(ct.connections[ip], now)
+
+	// 清理过期的连接记录
+	ct.cleanUpExpired(ip, now)
+}
+
+func (ct *ConnectionTracker) cleanUpExpired(ip string, now time.Time) {
+	threshold := now.Add(-3 * time.Minute)
+	if timestamps, exists := ct.connections[ip]; exists {
+		var filtered []time.Time
+		for _, t := range timestamps {
+			if t.After(threshold) { // 检查时间戳是否在三分钟内
+				filtered = append(filtered, t) // 如果在范围内,保存到 filtered 列表
+			}
+		}
+		ct.connections[ip] = filtered
+	}
+}
+
+// 判断是否是恶意连接
+func (ct *ConnectionTracker) isMalicious(ip string) bool {
+	ct.mu.Lock()
+	defer ct.mu.Unlock()
+
+	if timestamps, exists := ct.connections[ip]; exists {
+		return len(timestamps) >= 10 // 定义恶意连接的阈值
+	}
+	return false
+}

+ 1 - 1
web/.env.development

@@ -1,6 +1,6 @@
 ENV = 'development'
 VITE_CLI_PORT = 8080
-VITE_SERVER_PORT = 8888
+VITE_SERVER_PORT = 8889
 VITE_BASE_API = /api
 VITE_FILE_API = /api
 VITE_BASE_PATH = http://127.0.0.1

+ 33 - 0
web/src/api/project.js

@@ -0,0 +1,33 @@
+import service from '@/utils/request'
+
+export const getProjectList = (data) => {
+  return service({
+    url: '/project/getProjectAndDetails',
+    method: 'post',
+    data: data
+  })
+}
+
+export const addProject = (data) => {
+  return service({
+    url: '/project/addProject',
+    method: 'post',
+    data: data
+  })
+}
+
+export const updateProject = (data) => {
+  return service({
+    url: '/project/updateProject',
+    method: 'put',
+    data: data
+  })
+}
+
+export const delProject = (data) => {
+  return service({
+    url: '/project/delProject',
+    method: 'delete',
+    data: data
+  })
+}

+ 40 - 0
web/src/api/screens.js

@@ -0,0 +1,40 @@
+import service from '@/utils/request'
+
+export const getProjectList = () => {
+  return service({
+    url: '/project/getProjectListByUserID',
+    method: 'get'
+  })
+}
+
+export const getScreensList = (data) => {
+  return service({
+    url: '/screens/getScreensList',
+    method: 'post',
+    data: data
+  })
+}
+
+export const addScreens = (data) => {
+  return service({
+    url: '/screens/addScreens',
+    method: 'post',
+    data: data
+  })
+}
+
+export const updateScreens = (data) => {
+  return service({
+    url: '/screens/updateScreens',
+    method: 'put',
+    data: data
+  })
+}
+
+export const delScreens = (data) => {
+  return service({
+    url: '/screens/delScreens',
+    method: 'delete',
+    data: data
+  })
+}

+ 17 - 0
web/src/api/user.js

@@ -172,3 +172,20 @@ export const routerPublicKey = () => {
     method: 'get'
   })
 }
+
+//获取所有项目列表
+export const getProjectList = () => {
+  return service({
+    url: '/project/getProjectList',
+    method: 'get'
+  })
+}
+
+export const changeProjects = (data) => {
+  return service({
+    url: '/project/changeProjects',
+    method: 'put',
+    data: data
+  })
+}
+

+ 2 - 2
web/src/core/config.js

@@ -3,8 +3,8 @@
  */
 
 const config = {
-  appName: '龙弛财务',
-  appLogo: '../../assets/logo.png',
+  appName: '龙弛智慧路口',
+  appLogo: '../../assets/lc_logo.jpg',
   showViteLogo: true,
   logs: [],
 }

+ 23 - 0
web/src/view/devicesAdmin/index.vue

@@ -0,0 +1,23 @@
+<template>
+  <div>
+    <router-view v-slot="{ Component }">
+      <transition
+        mode="out-in"
+        name="el-fade-in-linear"
+      >
+        <keep-alive :include="routerStore.keepAliveRouters">
+          <component :is="Component" />
+        </keep-alive>
+      </transition>
+    </router-view>
+  </div>
+</template>
+
+<script setup>
+import { useRouterStore } from '@/pinia/modules/router'
+const routerStore = useRouterStore()
+
+defineOptions({
+  name: 'DevicesAdmin'
+})
+</script>

+ 402 - 0
web/src/view/devicesAdmin/screens/screens.vue

@@ -0,0 +1,402 @@
+<template>
+  <div>
+    <div class="gva-search-box">
+      <el-form
+        ref="searchForm"
+        :inline="true"
+        :model="searchInfo"
+      >
+        <el-form-item label="项目名称:">
+          <el-select
+            v-model="searchInfo.projectId"
+            filterable
+            collapse-tags
+            placeholder="选择项目"
+            clearable
+            @change="getTableData()"
+          >
+            <el-option
+              v-for="item in projects"
+              :key="item.ID"
+              :label="item.projectName"
+              :value="item.ID"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="设备SN:">
+          <el-input
+            v-model="searchInfo.sn"
+            placeholder="搜索sn"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button
+            type="primary"
+            icon="search"
+            @click="getTableData"
+          >查询</el-button>
+          <el-button
+            icon="refresh"
+            @click="onReset"
+          >重置</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <div class="gva-table-box">
+      <div class="gva-btn-list">
+        <el-button
+          type="primary"
+          icon="plus"
+          @click="add()"
+        >添加显示屏</el-button>
+      </div>
+      <el-table
+        :data="tableData"
+        style="width: 100%; margin-bottom: 20px"
+        row-key="id"
+        border
+        :table-layout="'fixed'"
+        :row-class-name="tableRowClassName"
+      >
+        <el-table-column
+          prop="ID"
+          label="编号"
+          :width="100"
+        />
+        <el-table-column
+          label="名称"
+          :width="200"
+          prop="screensName"
+        />
+        <el-table-column
+          label="sn"
+          :width="150"
+          prop="sn"
+        />
+        <el-table-column
+          label="ip地址"
+          :width="200"
+          prop="ipAddress"
+        />
+        <el-table-column
+          label="所属项目"
+          :width="180"
+          prop="Project.projectName"
+        />
+        <el-table-column
+          prop="Remark"
+          label="备注"
+        />
+        <el-table-column
+          prop="status"
+          label="在线状态"
+          :width="100"
+        >
+          <template #default="scope">
+            <div
+              class="onlinebox"
+              :style="{'background': scope.row.status===0 ? 'red':'green' }"
+            />
+            <span :style="{ color: scope.row.status === 0 ? 'red' : 'green' }">{{ scope.row.status === 0 ? '离线' : '在线' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column
+          align="left"
+          fixed="right"
+          label="操作"
+          :width="300"
+        >
+          <template #default="scope">
+            <el-button
+              type="primary"
+              link
+              @click="openSet(scope.row)"
+            >操作</el-button>
+            <el-button
+              type="primary"
+              link
+              icon="edit"
+              @click="editScreens(scope.row)"
+            >编辑</el-button>
+            <el-button
+              type="primary"
+              link
+              icon="delete"
+              @click="deleteScreens(scope.row)"
+            >删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <div class="gva-pagination">
+        <el-pagination
+          :current-page="page"
+          :page-size="pageSize"
+          :page-sizes="[10, 30, 50, 100]"
+          :total="total"
+          layout="total, sizes, prev, pager, next, jumper"
+          @current-change="handleCurrentChange"
+          @size-change="handleSizeChange"
+        />
+      </div>
+
+      <el-dialog
+        v-model="addScreensDialog"
+        title="LED屏"
+        width="600"
+      >
+        <el-form
+          ref="screensForm"
+          :rules="rules"
+          :model="screensInfo"
+          label-width="100px"
+          style="padding: 15px"
+        >
+          <el-row>
+            <el-col :span="24">
+              <el-form-item
+                label="LED名称:"
+                :inline="false"
+                prop="screensName"
+              >
+                <el-input v-model="screensInfo.screensName" />
+              </el-form-item>
+              <el-form-item
+                label="设备Sn:"
+                :inline="false"
+                prop="Sn"
+              >
+                <el-input v-model="screensInfo.sn" />
+              </el-form-item>
+              <el-form-item
+                label="所属项目:"
+                prop="projectId"
+              >
+                <el-select
+                  v-model="screensInfo.projectId"
+                  filterable
+                  collapse-tags
+                  placeholder="选择项目"
+                >
+                  <el-option
+                    v-for="item in projects"
+                    :key="item.ID"
+                    :label="item.projectName"
+                    :value="item.ID"
+                  />
+                </el-select>
+              </el-form-item>
+              <el-form-item
+                label="ip地址:"
+                :inline="false"
+              >
+                <el-input v-model="screensInfo.ipAddress" />
+              </el-form-item>
+              <el-form-item
+                label="备注:"
+                :inline="false"
+              >
+                <el-input
+                  v-model="screensInfo.Remark"
+                  type="textarea"
+                />
+              </el-form-item>
+            </el-col>
+          </el-row>
+        </el-form>
+        <template #footer>
+          <div class="dialog-footer">
+            <el-button @click="closeAddScreensDialog">取消</el-button>
+            <el-button
+              type="primary"
+              @click="enterAddScreensDialog"
+            >
+              确定
+            </el-button>
+          </div>
+        </template>
+      </el-dialog>
+
+      <el-dialog
+        v-model="showSettingDialog"
+        title="操作"
+        width="800"
+      >
+        <span>This is a message</span>
+        <template #footer>
+          <div class="dialog-footer">
+            <el-button @click="showSettingDialog = false">取消</el-button>
+            <el-button
+              type="primary"
+              @click="showSettingDialog = false"
+            >
+              确定
+            </el-button>
+          </div>
+        </template>
+      </el-dialog>
+
+    </div>
+  </div>
+</template>
+<script setup>
+import { addScreens, delScreens, getProjectList, getScreensList, updateScreens } from '@/api/screens'
+import { ref, onMounted } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+const page = ref(1)
+const total = ref(0)
+const pageSize = ref(10)
+const tableData = ref([])
+const screensInfo = ref({})
+const searchInfo = ref({})
+const screensForm = ref(null)
+const dialogFlag = ref('add')
+const isFlag = ref('')
+const addScreensDialog = ref(false)
+
+const rules = ref({
+  screensName: [
+    { required: true, message: '请输入名称', trigger: 'blur' },
+    // { min: 5, message: '最低5位字符', trigger: 'blur' }
+  ],
+  projectId: [
+    { required: true, message: '请选择归属项目', trigger: 'blur' },
+    // { min: 5, message: '最低5位字符', trigger: 'blur' }
+  ]
+})
+
+// eslint-disable-next-line no-unused-vars
+const getNowDay = () => {
+  const timestamp = Date.now() // 获取当前时间戳,单位是毫秒
+  screensInfo.value.startTime = new Date(timestamp)
+}
+
+const tableRowClassName = ({ row, rowIndex }) => {
+  if ((rowIndex + 1) % 2 === 0) {
+    return 'success-row'
+  }
+  return ''
+}
+const getTableData = async() => {
+  const table = await getScreensList({ page: page.value, pageSize: pageSize.value, ...searchInfo.value })
+  if (table.code === 0) {
+    tableData.value = table.data.list
+    total.value = table.data.total
+    page.value = table.data.page
+    pageSize.value = table.data.pageSize
+  }
+}
+
+const onReset = () => {
+  searchInfo.value = {}
+  getTableData()
+}
+
+const projects = ref([])
+
+onMounted(async() => {
+  getTableData()
+  const res = await getProjectList()
+  projects.value = res.data
+})
+
+const handleSizeChange = (val) => {
+  pageSize.value = val
+  getTableData()
+}
+
+const handleCurrentChange = (val) => {
+  page.value = val
+  getTableData()
+}
+
+const add = () => {
+  screensInfo.value = {}
+  dialogFlag.value = 'add'
+  getNowDay()
+  addScreensDialog.value = true
+}
+
+const closeAddScreensDialog = () => {
+  screensForm.value.resetFields()
+  addScreensDialog.value = false
+  isFlag.value = ''
+}
+
+const enterAddScreensDialog = async() => {
+  screensForm.value.validate(async valid => {
+    if (valid) {
+      const req = {
+        ...screensInfo.value
+      }
+      if (dialogFlag.value === 'add') {
+        const res = await addScreens(req)
+        if (res.code === 0) {
+          ElMessage({ type: 'success', message: '创建成功' })
+          await getTableData()
+          closeAddScreensDialog()
+        }
+      }
+      if (dialogFlag.value === 'edit') {
+        const res = await updateScreens(req)
+        if (res.code === 0) {
+          ElMessage({ type: 'success', message: '编辑成功' })
+          await getTableData()
+          closeAddScreensDialog()
+        }
+      }
+    }
+  })
+}
+
+const editScreens = (row) => {
+  dialogFlag.value = 'edit'
+  screensInfo.value = JSON.parse(JSON.stringify(row))
+  addScreensDialog.value = true
+}
+const deleteScreens = (obj) => {
+  ElMessageBox.confirm('您确定要删除吗?', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+  })
+    .then(async() => {
+      const res = await delScreens({ ID: obj.ID })
+      if (res.code === 0) {
+        ElMessage({
+          type: 'success',
+          message: '删除成功!',
+        })
+        getTableData()
+      }
+    })
+    .catch(() => {
+      ElMessage({
+        type: 'info',
+        message: '已取消删除',
+      })
+    })
+}
+const showSettingDialog = ref(false)
+
+const openSet = () => {
+  showSettingDialog.value = true
+}
+
+</script>
+<style>
+.el-table .success-row {
+  background: #f3fdef;
+}
+
+.onlinebox{
+  width: 10px; height: 10px;border-radius: 50%; display: inline-block; margin-right: 4px; position: relative; top: 1px;
+}
+
+.el-dialog__header{
+  border-bottom: 1px solid #e8eaec;
+}
+.el-dialog__footer {
+  border-top: 1px solid #e8eaec;
+}
+</style>

+ 1 - 1
web/src/view/layout/index.vue

@@ -16,7 +16,7 @@
           <img
             alt
             class="w-9 h-9 p-1 bg-white rounded-full"
-            src="../../assets/logo.png"
+            src="../../assets/lc_logo.jpg"
           >
           <div
             v-if="isSider"

+ 4 - 3
web/src/view/login/index.vue

@@ -14,13 +14,14 @@
             <div class="flex items-center justify-center">
 
               <img
-                class="w-24"
-                src="../../assets/logo.png"
+                class="w-3/6"
+                src="../../assets/lc_logo.jpg"
                 alt
               >
             </div>
             <div class="mb-9">
-              <p class="text-center text-4xl font-bold">龙弛财务</p>
+              <p class="text-center text-4xl font-bold">龙弛-智慧路口</p>
+              <p class="text-center text-2xl font-bold">SMART INTERSECTION</p>
             </div>
             <el-form
               ref="loginForm"

+ 321 - 0
web/src/view/project/index.vue

@@ -0,0 +1,321 @@
+<template>
+  <div>
+    <div class="gva-table-box">
+      <div class="gva-btn-list">
+        <el-button
+          type="primary"
+          icon="plus"
+          @click="add()"
+        >新增项目</el-button>
+      </div>
+      <el-table
+        :data="tableData"
+        style="width: 100%; margin-bottom: 20px"
+        row-key="id"
+        border
+        :table-layout="'fixed'"
+        :row-class-name="tableRowClassName"
+      >
+        <el-table-column
+          prop="project.ID"
+          label="编号"
+          :width="200"
+        />
+        <el-table-column
+          prop="project.projectName"
+          label="名称"
+        >
+          <template #default="scope">
+            <el-link
+              link
+              type="primary"
+              size="large"
+              @click="view"
+            >
+              {{ scope.row.project.projectName }}
+            </el-link>
+          </template>
+
+        </el-table-column>
+        <el-table-column
+          prop="project.startTime"
+          label="开始日期"
+          :formatter="(row, column, cellValue) => cellValue?.split('T')[0] || ''"
+        />
+        <el-table-column
+          prop="project.remark"
+          label="备注"
+        />
+        <el-table-column
+          align="left"
+          fixed="right"
+          label="操作"
+          :width="300"
+        >
+          <template #default="scope">
+            <el-button
+              type="primary"
+              link
+              icon="edit"
+              @click="editProject(scope.row.project)"
+            >编辑</el-button>
+            <el-button
+              type="primary"
+              link
+              icon="delete"
+              @click="deleteProject(scope.row)"
+            >删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <div class="gva-pagination">
+        <el-pagination
+          :current-page="page"
+          :page-size="pageSize"
+          :page-sizes="[10, 30, 50, 100]"
+          :total="total"
+          layout="total, sizes, prev, pager, next, jumper"
+          @current-change="handleCurrentChange"
+          @size-change="handleSizeChange"
+        />
+      </div>
+
+      <el-dialog
+        v-model="addProjectDialog"
+        title="路口项目"
+        width="600"
+      >
+        <el-form
+          ref="projectForm"
+          :rules="rules"
+          :model="projectInfo"
+          label-width="100px"
+          style="padding: 15px"
+        >
+          <el-row>
+            <el-col :span="24">
+              <el-form-item
+                label="项目名称:"
+                :inline="false"
+                prop="projectName"
+              >
+                <el-input v-model="projectInfo.projectName" />
+              </el-form-item>
+              <el-form-item
+                label="开始时间:"
+                :inline="false"
+                prop="startTime"
+              >
+                <el-date-picker
+                  v-model="projectInfo.startTime"
+                  type="date"
+                  placeholder="Pick a day"
+                  size="default"
+                />
+              </el-form-item>
+              <el-form-item
+                label="备注:"
+                :inline="false"
+              >
+                <el-input
+                  v-model="projectInfo.remark"
+                  type="textarea"
+                />
+              </el-form-item>
+            </el-col>
+          </el-row>
+        </el-form>
+        <template #footer>
+          <div class="dialog-footer">
+            <el-button @click="closeAddProjectDialog">取消</el-button>
+            <el-button
+              type="primary"
+              @click="enterAddProjectDialog"
+            >
+              确定
+            </el-button>
+          </div>
+        </template>
+      </el-dialog>
+
+      <!--      抽屉-->
+      <el-drawer
+        v-model="viewProjectDialog"
+        :show-close="false"
+      >
+        <template #header="{ close, titleId, titleClass }">
+          <h4
+            :id="titleId"
+            :class="titleClass"
+          >关联设备:</h4>
+
+        </template>
+        This is drawer content.
+      </el-drawer>
+    </div>
+  </div>
+</template>
+<script setup>
+import { delProject, getProjectList, updateProject, addProject } from '@/api/project'
+import { ref, onMounted } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+const page = ref(1)
+const total = ref(0)
+const pageSize = ref(10)
+const tableData = ref([])
+const projectInfo = ref({})
+const projectForm = ref(null)
+const dialogFlag = ref('add')
+const isFlag = ref('')
+const addProjectDialog = ref(false)
+const viewProjectDialog = ref(false)
+
+const rules = ref({
+  projectName: [
+    { required: true, message: '请输入名称', trigger: 'blur' },
+    // { min: 5, message: '最低5位字符', trigger: 'blur' }
+  ],
+  startTime: [
+    { required: true, message: '请选择时间', trigger: 'blur' },
+    // { min: 5, message: '最低5位字符', trigger: 'blur' }
+  ]
+})
+
+// eslint-disable-next-line no-unused-vars
+const getNowDay = () => {
+  const timestamp = Date.now() // 获取当前时间戳,单位是毫秒
+  projectInfo.value.startTime = new Date(timestamp)
+}
+
+const tableRowClassName = ({ row, rowIndex }) => {
+  if ((rowIndex + 1) % 2 === 0) {
+    return 'success-row'
+  }
+  return ''
+}
+const getTableData = async() => {
+  const table = await getProjectList({ page: page.value, pageSize: pageSize.value })
+  if (table.code === 0) {
+    tableData.value = table.data.list
+    total.value = table.data.total
+    page.value = table.data.page
+    pageSize.value = table.data.pageSize
+  }
+}
+
+onMounted(() => {
+  getTableData()
+})
+
+const view = (row) => {
+  viewProjectDialog.value = true
+  // projectInfo.value = JSON.parse(JSON.stringify(row))
+  // projectInfo.value.installTime = new Date(projectInfo.value.installTime).toLocaleDateString()
+}
+
+const handleSizeChange = (val) => {
+  pageSize.value = val
+  getTableData()
+}
+
+const handleCurrentChange = (val) => {
+  page.value = val
+  getTableData()
+}
+
+const add = () => {
+  projectInfo.value = {}
+  dialogFlag.value = 'add'
+  getNowDay()
+  addProjectDialog.value = true
+}
+
+const closeAddProjectDialog = () => {
+  projectForm.value.resetFields()
+  addProjectDialog.value = false
+  isFlag.value = ''
+}
+
+const enterAddProjectDialog = async() => {
+  projectForm.value.validate(async valid => {
+    if (valid) {
+      const req = {
+        ...projectInfo.value
+      }
+      if (dialogFlag.value === 'add') {
+        const res = await addProject(req)
+        if (res.code === 0) {
+          ElMessage({ type: 'success', message: '创建成功' })
+          await getTableData()
+          closeAddProjectDialog()
+        }
+      }
+      if (dialogFlag.value === 'edit') {
+        const res = await updateProject(req)
+        if (res.code === 0) {
+          ElMessage({ type: 'success', message: '编辑成功' })
+          await getTableData()
+          closeAddProjectDialog()
+        }
+      }
+    }
+  })
+}
+
+const editProject = (row) => {
+  dialogFlag.value = 'edit'
+  projectInfo.value = JSON.parse(JSON.stringify(row))
+  addProjectDialog.value = true
+}
+const deleteProject = (obj) => {
+  ElMessageBox.confirm('您确定要删除吗?', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+  })
+    .then(async() => {
+      for (const prop in obj.relationDev) {
+        if (Array.isArray(obj.relationDev[prop])) {
+          if (obj.relationDev[prop].length !== 0) {
+            ElMessage({
+              type: 'warning',
+              message: '删除失败,请先解绑该项目下的设备!',
+            })
+            return
+          }
+        }
+      }
+      const res = await delProject({ ID: obj.project.ID })
+      if (res.code === 0) {
+        ElMessage({
+          type: 'success',
+          message: '删除成功!',
+        })
+        getTableData()
+      }
+    })
+    .catch(() => {
+      ElMessage({
+        type: 'info',
+        message: '已取消删除',
+      })
+    })
+}
+
+</script>
+<style>
+.el-table .success-row {
+  background: #f3fdef;
+}
+
+.onlinebox{
+  width: 12px; height: 12px;border-radius: 50%; display: inline-block; margin-right: 4px; position: relative; top: 1px;
+}
+
+.el-dialog__header{
+  border-bottom: 1px solid #e8eaec;
+}
+.el-dialog__footer {
+  border-top: 1px solid #e8eaec;
+}
+</style>

+ 53 - 4
web/src/view/superAdmin/user/user.vue

@@ -12,6 +12,7 @@
       <el-table
         :data="tableData"
         row-key="ID"
+        style="width: 100%"
       >
         <el-table-column
           align="left"
@@ -40,13 +41,13 @@
         <el-table-column
           align="left"
           label="昵称"
-          min-width="150"
+          min-width="120"
           prop="nickName"
         />
         <el-table-column
           align="left"
           label="手机号"
-          min-width="180"
+          min-width="120"
           prop="phone"
         />
         <el-table-column
@@ -72,10 +73,35 @@
             />
           </template>
         </el-table-column>
+        <el-table-column
+          align="left"
+          label="项目分配"
+          min-width="260"
+          prop="project"
+        >
+          <template #default="scope">
+            <el-select
+              v-model="scope.row.projectIds"
+              multiple
+              filterable
+              collapse-tags
+              placeholder="分配项目"
+              style="width: 240px"
+              @change="changeProjectId(scope.row.ID,scope.row.projectIds)"
+            >
+              <el-option
+                v-for="item in projects"
+                :key="item.ID"
+                :label="item.projectName"
+                :value="item.ID"
+              />
+            </el-select>
+          </template>
+        </el-table-column>
         <el-table-column
           align="left"
           label="启用"
-          min-width="150"
+          min-width="80"
         >
           <template #default="scope">
             <el-switch
@@ -235,7 +261,7 @@ import {
 import { getAuthorityList } from '@/api/authority'
 import CustomPic from '@/components/customPic/index.vue'
 import WarningBar from '@/components/warningBar/warningBar.vue'
-import { setUserInfo, resetPassword } from '@/api/user.js'
+import { setUserInfo, resetPassword, getProjectList, changeProjects } from '@/api/user.js'
 
 import { nextTick, ref } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
@@ -292,13 +318,36 @@ const getTableData = async() => {
     page.value = table.data.page
     pageSize.value = table.data.pageSize
   }
+  tableData.value && tableData.value.forEach((user) => {
+    user.projectIds = user.projects && user.projects.map(i => {
+      return i.ID
+    })
+  })
 }
 
+const projects = ref()
+const changeProjectId = async(userId, arr) => {
+  var res = await changeProjects({ Id: userId, ProIds: arr })
+  if (res.code === 0) {
+    ElMessage({
+      type: 'success',
+      message: res.msg,
+    })
+  } else {
+    ElMessage({
+      type: 'error',
+      message: res.msg,
+    })
+  }
+}
 
 const initPage = async() => {
   getTableData()
   const res = await getAuthorityList({ page: 1, pageSize: 999 })
   setOptions(res.data.list)
+
+  const response = await getProjectList()
+  projects.value = response.data
 }
 
 initPage()