Ver código fonte

新增 车辆类型管理

xu 3 meses atrás
pai
commit
48fd2e54f2

+ 1 - 0
server/api/v1/vehicle/enter.go

@@ -31,4 +31,5 @@ var (
 	vehicleService       = service.ServiceGroupApp.VehicleServiceGroup.VehicleService
 	vehicleRecordService = service.ServiceGroupApp.VehicleServiceGroup.VehicleRecordService
 	feeConfigService     = service.ServiceGroupApp.VehicleServiceGroup.FeeConfigService
+	vehicleTypeService   = service.ServiceGroupApp.VehicleServiceGroup.VehicleTypeService
 )

+ 113 - 0
server/api/v1/vehicle/vehicle_type.go

@@ -0,0 +1,113 @@
+package vehicle
+
+import (
+	"fmt"
+	"server/model/common/response"
+	"server/model/vehicle/request"
+
+	"github.com/gin-gonic/gin"
+)
+
+// CreateVehicleType 创建车辆类型
+func CreateVehicleType(c *gin.Context) {
+	var req request.VehicleTypeCreate
+	if err := c.ShouldBindJSON(&req); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err := vehicleTypeService.CreateVehicleType(req)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithMessage("创建成功", c)
+}
+
+// UpdateVehicleType 更新车辆类型
+func UpdateVehicleType(c *gin.Context) {
+	var req request.VehicleTypeUpdate
+	if err := c.ShouldBindJSON(&req); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	err := vehicleTypeService.UpdateVehicleType(req)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithMessage("更新成功", c)
+}
+
+// GetVehicleTypeByID 根据ID获取车辆类型
+func GetVehicleTypeByID(c *gin.Context) {
+	id := c.Query("id")
+	uintID := uint(0)
+	_, err := fmt.Sscanf(id, "%d", &uintID)
+	if err != nil {
+		response.FailWithMessage("无效的ID", c)
+		return
+	}
+
+	vehicleTypeInfo, err := vehicleTypeService.GetVehicleTypeByID(uintID)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithData(vehicleTypeInfo, c)
+}
+
+// ListVehicleTypes 获取车辆类型列表
+func ListVehicleTypes(c *gin.Context) {
+	var info request.VehicleTypeQuery
+	if err := c.ShouldBindJSON(&info); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	total, list, err := vehicleTypeService.ListVehicleTypes(info)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithDetailed(response.PageResult{
+		List:     list,
+		Total:    total,
+		Page:     info.Page,
+		PageSize: info.PageSize,
+	}, "获取成功", c)
+}
+
+// DeleteVehicleType 删除车辆类型
+func DeleteVehicleType(c *gin.Context) {
+	id := c.Query("id")
+	uintID := uint(0)
+	_, err := fmt.Sscanf(id, "%d", &uintID)
+	if err != nil {
+		response.FailWithMessage("无效的ID", c)
+		return
+	}
+
+	err = vehicleTypeService.DeleteVehicleType(uintID)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithMessage("删除成功", c)
+}
+
+// GetAllVehicleTypes 获取所有车辆类型(用于下拉选择)
+func GetAllVehicleTypes(c *gin.Context) {
+	vehicleTypes, err := vehicleTypeService.GetAllVehicleTypes()
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithData(vehicleTypes, c)
+}

+ 10 - 9
server/dao/fee_config.go

@@ -4,15 +4,16 @@ import "server/global"
 
 type FeeConfig struct {
 	global.GVA_MODEL
-	VehicleType string  `gorm:"size:20;not null;uniqueIndex" json:"vehicle_type"` // 车辆类型
-	StartTime   int     `json:"start_time"`                                       // 起始时间(分钟)
-	StartFee    float64 `json:"start_fee"`                                        // 起始费用
-	UnitTime    int     `json:"unit_time"`                                        // 单位时间(分钟)
-	UnitFee     float64 `json:"unit_fee"`                                         // 单位费用
-	DailyMaxFee float64 `json:"daily_max_fee"`                                    // 每日最高费用
-	IsVIPFree   bool    `gorm:"default:false" json:"is_vip_free"`                 // VIP是否免费
-	VIPDiscount float64 `gorm:"default:1.0" json:"vip_discount"`                  // VIP折扣
-	Description string  `gorm:"size:200" json:"description"`                      // 描述
+	VehicleTypeID uint         `gorm:"not null;uniqueIndex" json:"vehicle_type_id"`  // 车辆类型ID
+	VehicleType   *VehicleType `gorm:"foreignKey:VehicleTypeID" json:"vehicle_type"` // 车辆类型关联
+	StartTime     int          `json:"start_time"`                                   // 起始时间(分钟)
+	StartFee      float64      `json:"start_fee"`                                    // 起始费用
+	UnitTime      int          `json:"unit_time"`                                    // 单位时间(分钟)
+	UnitFee       float64      `json:"unit_fee"`                                     // 单位费用
+	DailyMaxFee   float64      `json:"daily_max_fee"`                                // 每日最高费用
+	IsVIPFree     bool         `gorm:"default:false" json:"is_vip_free"`             // VIP是否免费
+	VIPDiscount   float64      `gorm:"default:1.0" json:"vip_discount"`              // VIP折扣
+	Description   string       `gorm:"size:200" json:"description"`                  // 描述
 }
 
 func (FeeConfig) TableName() string {

+ 12 - 11
server/dao/vehicle.go

@@ -7,17 +7,18 @@ import (
 
 type Vehicle struct {
 	global.GVA_MODEL
-	PlateNumber   string     `gorm:"size:20;not null;uniqueIndex" json:"plate_number"` // 车牌号
-	RFIDTag       string     `gorm:"size:20;" json:"rfid_tag"`                         // 电子标签
-	VehicleType   string     `gorm:"size:20;not null" json:"vehicle_type"`             // 车辆类型
-	VehicleBrand  string     `gorm:"size:50" json:"vehicle_brand"`                     // 车辆品牌
-	VehicleColor  string     `gorm:"size:20" json:"vehicle_color"`                     // 车辆颜色
-	OwnerId       *uint      `json:"owner_id"`                                         // 车主id,可为空(临时车辆)
-	Owner         *Owner     `gorm:"foreignKey:OwnerId" json:"owner"`
-	LastEntryTime *time.Time `json:"last_entry_time" gorm:"default:null"` // 最近入场时间
-	LastExitTime  *time.Time `json:"last_exit_time"  gorm:"default:null"` // 最近出场时间
-	TotalStayTime int64      `json:"total_stay_time" gorm:"default:null"` // 总停留时间
-	TotalFee      float64    `json:"total_fee"`                           // 总费用
+	PlateNumber   string       `gorm:"size:20;not null;uniqueIndex" json:"plate_number"` // 车牌号
+	RFIDTag       string       `gorm:"size:20;" json:"rfid_tag"`                         // 电子标签
+	VehicleTypeID uint         `gorm:"not null" json:"vehicle_type_id"`                  // 车辆类型ID
+	VehicleType   *VehicleType `gorm:"foreignKey:VehicleTypeID" json:"vehicle_type"`     // 车辆类型关联
+	VehicleBrand  string       `gorm:"size:50" json:"vehicle_brand"`                     // 车辆品牌
+	VehicleColor  string       `gorm:"size:20" json:"vehicle_color"`                     // 车辆颜色
+	OwnerId       *uint        `json:"owner_id"`                                         // 车主id,可为空(临时车辆)
+	Owner         *Owner       `gorm:"foreignKey:OwnerId" json:"owner"`
+	LastEntryTime *time.Time   `json:"last_entry_time" gorm:"default:null"` // 最近入场时间
+	LastExitTime  *time.Time   `json:"last_exit_time"  gorm:"default:null"` // 最近出场时间
+	TotalStayTime int64        `json:"total_stay_time" gorm:"default:null"` // 总停留时间
+	TotalFee      float64      `json:"total_fee"`                           // 总费用
 }
 
 func (Vehicle) TableName() string {

+ 13 - 0
server/dao/vehicle_type.go

@@ -0,0 +1,13 @@
+package dao
+
+import "server/global"
+
+type VehicleType struct {
+	global.GVA_MODEL
+	Name    string `form:"name" json:"name" gorm:"comment:名称"`
+	Remarks string `form:"remarks" json:"remarks" gorm:"comment:备注"`
+}
+
+func (VehicleType) TableName() string {
+	return "vehicle_type"
+}

+ 1 - 0
server/initialize/gorm.go

@@ -48,6 +48,7 @@ func RegisterTables() {
 		dao.ExaFileUploadAndDownload{},
 
 		dao.Vehicle{},
+		dao.VehicleType{},
 		dao.Owner{},
 		dao.FeeConfig{},
 		dao.Booth{},

+ 22 - 22
server/model/vehicle/request/fee_config.go

@@ -1,32 +1,32 @@
 package request
 
 type FeeConfigCreate struct {
-	VehicleType string  `json:"vehicle_type" `
-	StartTime   int     `json:"start_time"`
-	StartFee    float64 `json:"start_fee"`
-	UnitTime    int     `json:"unit_time"`
-	UnitFee     float64 `json:"unit_fee"`
-	DailyMaxFee float64 `json:"daily_max_fee"`
-	IsVIPFree   bool    `json:"is_vip_free"`
-	VIPDiscount float64 `json:"vip_discount"`
-	Description string  `json:"description"`
+	VehicleTypeID uint    `json:"vehicle_type_id"`
+	StartTime     int     `json:"start_time"`
+	StartFee      float64 `json:"start_fee"`
+	UnitTime      int     `json:"unit_time"`
+	UnitFee       float64 `json:"unit_fee"`
+	DailyMaxFee   float64 `json:"daily_max_fee"`
+	IsVIPFree     bool    `json:"is_vip_free"`
+	VIPDiscount   float64 `json:"vip_discount"`
+	Description   string  `json:"description"`
 }
 
 type FeeConfigUpdate struct {
-	ID          uint    `json:"id" `
-	VehicleType string  `json:"vehicle_type" `
-	StartTime   int     `json:"start_time"`
-	StartFee    float64 `json:"start_fee"`
-	UnitTime    int     `json:"unit_time"`
-	UnitFee     float64 `json:"unit_fee"`
-	DailyMaxFee float64 `json:"daily_max_fee"`
-	IsVIPFree   bool    `json:"is_vip_free"`
-	VIPDiscount float64 `json:"vip_discount"`
-	Description string  `json:"description"`
+	ID            uint    `json:"id"`
+	VehicleTypeID uint    `json:"vehicle_type_id"`
+	StartTime     int     `json:"start_time"`
+	StartFee      float64 `json:"start_fee"`
+	UnitTime      int     `json:"unit_time"`
+	UnitFee       float64 `json:"unit_fee"`
+	DailyMaxFee   float64 `json:"daily_max_fee"`
+	IsVIPFree     bool    `json:"is_vip_free"`
+	VIPDiscount   float64 `json:"vip_discount"`
+	Description   string  `json:"description"`
 }
 
 type FeeConfigQuery struct {
-	VehicleType string `form:"vehicle_type"`
-	Page        int    `form:"page,default=1"`
-	PageSize    int    `form:"page_size,default=10"`
+	VehicleTypeID uint `form:"vehicle_type_id"`
+	Page          int  `form:"page,default=1"`
+	PageSize      int  `form:"page_size,default=10"`
 }

+ 35 - 18
server/model/vehicle/request/vehicle.go

@@ -1,30 +1,47 @@
 package request
 
 type VehicleCreate struct {
-	PlateNumber  string `json:"plate_number"`
-	VehicleType  string `json:"vehicle_type"`
-	VehicleBrand string `json:"vehicle_brand"`
-	VehicleColor string `json:"vehicle_color"`
-	RFIDTag      string `json:"rfid_tag"`
-	OwnerId      uint   `json:"owner_id"`
+	PlateNumber   string `json:"plate_number"`
+	VehicleTypeID uint   `json:"vehicle_type_id"`
+	VehicleBrand  string `json:"vehicle_brand"`
+	VehicleColor  string `json:"vehicle_color"`
+	RFIDTag       string `json:"rfid_tag"`
+	OwnerId       uint   `json:"owner_id"`
 }
 
 type VehicleUpdate struct {
-	ID           uint   `json:"id"`
-	PlateNumber  string `json:"plate_number"`
-	VehicleType  string `json:"vehicle_type"`
-	VehicleBrand string `json:"vehicle_brand"`
-	VehicleColor string `json:"vehicle_color"`
-	RFIDTag      string `json:"rfid_tag"`
-	OwnerId      uint   `json:"owner_id"`
+	ID            uint   `json:"id"`
+	PlateNumber   string `json:"plate_number"`
+	VehicleTypeID uint   `json:"vehicle_type_id"`
+	VehicleBrand  string `json:"vehicle_brand"`
+	VehicleColor  string `json:"vehicle_color"`
+	RFIDTag       string `json:"rfid_tag"`
+	OwnerId       uint   `json:"owner_id"`
 }
 
 type VehicleQuery struct {
-	PlateNumber string `form:"plate_number"`
-	VehicleType string `form:"vehicle_type"`
-	OwnerId     uint   `form:"owner_id"`
-	Page        int    `form:"page,default=1"`
-	PageSize    int    `form:"page_size,default=10"`
+	PlateNumber   string `form:"plate_number"`
+	VehicleTypeID uint   `form:"vehicle_type_id"`
+	OwnerId       uint   `form:"owner_id"`
+	Page          int    `form:"page,default=1"`
+	PageSize      int    `form:"page_size,default=10"`
+}
+
+type VehicleTypeCreate struct {
+	Name    string `json:"name"`
+	Remarks string `json:"remarks"`
+}
+
+type VehicleTypeUpdate struct {
+	ID      uint   `json:"id"`
+	Name    string `json:"name"`
+	Remarks string `json:"remarks"`
+}
+
+type VehicleTypeQuery struct {
+	Name     string `json:"name" form:"name"`
+	Page     int    `json:"page,default=1" form:"page"`
+	PageSize int    `json:"pageSize,default=1" form:"pageSize"`
 }
 
 type VehicleEntry struct {

+ 8 - 0
server/router/vehicle/vehicle.go

@@ -10,6 +10,14 @@ import (
 func SetupVehicleRouter(router *gin.RouterGroup) {
 	vehicleGroup := router.Group("/vehicle")
 	{
+		// 车辆类型管理
+		vehicleGroup.POST("/type/create", vehicle.CreateVehicleType)
+		vehicleGroup.PUT("/type/update", vehicle.UpdateVehicleType)
+		vehicleGroup.GET("/type/get", vehicle.GetVehicleTypeByID)
+		vehicleGroup.POST("/type/list", vehicle.ListVehicleTypes)
+		vehicleGroup.DELETE("/type/delete", vehicle.DeleteVehicleType)
+		vehicleGroup.GET("/type/all", vehicle.GetAllVehicleTypes)
+
 		// 车辆管理
 		vehicleGroup.POST("/create", vehicle.CreateVehicle)
 		vehicleGroup.PUT("/update", vehicle.UpdateVehicle)

+ 1 - 1
server/service/uhf/reader.go

@@ -273,7 +273,7 @@ func handleReportData(report *ReportData) {
 	if err != nil || vehicle.ID == 0 {
 		isTempVehicle = true
 	} else {
-		isTempVehicle = (vehicle.VehicleType == "临时车")
+		isTempVehicle = (vehicle.VehicleType.Name == "临时车")
 	}
 
 	// ===================== 2. 查询黑白名单 =====================

+ 1 - 0
server/service/vehicle/enter.go

@@ -5,6 +5,7 @@ type ServiceGroup struct {
 	VehicleService
 	VehicleRecordService
 	FeeConfigService
+	VehicleTypeService
 }
 
 //var (

+ 33 - 19
server/service/vehicle/fee_config.go

@@ -11,23 +11,30 @@ type FeeConfigService struct{}
 
 // CreateFeeConfig 创建收费配置
 func (s *FeeConfigService) CreateFeeConfig(req request.FeeConfigCreate) error {
-	// 检查车辆类型是否已存在
+	// 检查车辆类型是否存在
+	var vehicleType dao.VehicleType
+	result := global.GVA_DB.First(&vehicleType, req.VehicleTypeID)
+	if result.RowsAffected == 0 {
+		return errors.New("车辆类型不存在")
+	}
+
+	// 检查车辆类型是否已存在收费配置
 	var existingConfig dao.FeeConfig
-	result := global.GVA_DB.Where("vehicle_type = ?", req.VehicleType).First(&existingConfig)
+	result = global.GVA_DB.Where("vehicle_type_id = ?", req.VehicleTypeID).First(&existingConfig)
 	if result.RowsAffected > 0 {
 		return errors.New("该车辆类型的收费配置已存在")
 	}
 
 	config := dao.FeeConfig{
-		VehicleType: req.VehicleType,
-		StartTime:   req.StartTime,
-		StartFee:    req.StartFee,
-		UnitTime:    req.UnitTime,
-		UnitFee:     req.UnitFee,
-		DailyMaxFee: req.DailyMaxFee,
-		IsVIPFree:   req.IsVIPFree,
-		VIPDiscount: req.VIPDiscount,
-		Description: req.Description,
+		VehicleTypeID: req.VehicleTypeID,
+		StartTime:     req.StartTime,
+		StartFee:      req.StartFee,
+		UnitTime:      req.UnitTime,
+		UnitFee:       req.UnitFee,
+		DailyMaxFee:   req.DailyMaxFee,
+		IsVIPFree:     req.IsVIPFree,
+		VIPDiscount:   req.VIPDiscount,
+		Description:   req.Description,
 	}
 
 	return global.GVA_DB.Create(&config).Error
@@ -41,14 +48,21 @@ func (s *FeeConfigService) UpdateFeeConfig(req request.FeeConfigUpdate) error {
 		return errors.New("收费配置不存在")
 	}
 
+	// 检查车辆类型是否存在
+	var vehicleType dao.VehicleType
+	result = global.GVA_DB.First(&vehicleType, req.VehicleTypeID)
+	if result.RowsAffected == 0 {
+		return errors.New("车辆类型不存在")
+	}
+
 	// 检查车辆类型是否被其他配置使用
 	var existingConfig dao.FeeConfig
-	result = global.GVA_DB.Where("vehicle_type = ? AND id != ?", req.VehicleType, req.ID).First(&existingConfig)
+	result = global.GVA_DB.Where("vehicle_type_id = ? AND id != ?", req.VehicleTypeID, req.ID).First(&existingConfig)
 	if result.RowsAffected > 0 {
 		return errors.New("该车辆类型的收费配置已存在")
 	}
 
-	config.VehicleType = req.VehicleType
+	config.VehicleTypeID = req.VehicleTypeID
 	config.StartTime = req.StartTime
 	config.StartFee = req.StartFee
 	config.UnitTime = req.UnitTime
@@ -71,10 +85,10 @@ func (s *FeeConfigService) GetFeeConfigByID(id uint) (dao.FeeConfig, error) {
 	return config, nil
 }
 
-// GetFeeConfigByVehicleType 根据车辆类型获取收费配置
-func (s *FeeConfigService) GetFeeConfigByVehicleType(vehicleType string) (dao.FeeConfig, error) {
+// GetFeeConfigByVehicleType 根据车辆类型ID获取收费配置
+func (s *FeeConfigService) GetFeeConfigByVehicleType(vehicleTypeID uint) (dao.FeeConfig, error) {
 	var config dao.FeeConfig
-	result := global.GVA_DB.Where("vehicle_type = ?", vehicleType).First(&config)
+	result := global.GVA_DB.Where("vehicle_type_id = ?", vehicleTypeID).First(&config)
 	if result.RowsAffected == 0 {
 		return dao.FeeConfig{}, errors.New("收费配置不存在")
 	}
@@ -88,8 +102,8 @@ func (s *FeeConfigService) ListFeeConfigs(req request.FeeConfigQuery) (int64, []
 
 	query := global.GVA_DB.Model(&dao.FeeConfig{})
 
-	if req.VehicleType != "" {
-		query = query.Where("vehicle_type LIKE ?", "%"+req.VehicleType+"%")
+	if req.VehicleTypeID > 0 {
+		query = query.Where("vehicle_type_id = ?", req.VehicleTypeID)
 	}
 
 	// 计算总数
@@ -97,7 +111,7 @@ func (s *FeeConfigService) ListFeeConfigs(req request.FeeConfigQuery) (int64, []
 
 	// 分页查询
 	offset := (req.Page - 1) * req.PageSize
-	result := query.Offset(offset).Limit(req.PageSize).Find(&configs)
+	result := query.Offset(offset).Limit(req.PageSize).Preload("VehicleType").Find(&configs)
 	if result.Error != nil {
 		return 0, nil, result.Error
 	}

+ 33 - 20
server/service/vehicle/vehicle.go

@@ -19,17 +19,23 @@ func (s *VehicleService) CreateVehicle(req request.VehicleCreate) error {
 	if result.RowsAffected > 0 {
 		return errors.New("车牌号已存在")
 	}
+	// 检查车辆类型是否存在
+	var vehicleType dao.VehicleType
+	result = global.GVA_DB.First(&vehicleType, req.VehicleTypeID)
+	if result.RowsAffected == 0 {
+		return errors.New("车辆类型不存在")
+	}
 	// 处理临时车辆的情况
 	var ownerId *uint
 	if req.OwnerId > 0 {
 		ownerId = &req.OwnerId
 	}
 	vehicle := dao.Vehicle{
-		PlateNumber:  req.PlateNumber,
-		VehicleType:  req.VehicleType,
-		VehicleBrand: req.VehicleBrand,
-		VehicleColor: req.VehicleColor,
-		OwnerId:      ownerId,
+		PlateNumber:   req.PlateNumber,
+		VehicleTypeID: req.VehicleTypeID,
+		VehicleBrand:  req.VehicleBrand,
+		VehicleColor:  req.VehicleColor,
+		OwnerId:       ownerId,
 	}
 
 	return global.GVA_DB.Create(&vehicle).Error
@@ -51,6 +57,13 @@ func (s *VehicleService) UpdateVehicle(req request.VehicleUpdate) error {
 		return errors.New("车牌号已被其他车辆使用")
 	}
 
+	// 检查车辆类型是否存在
+	var vehicleType dao.VehicleType
+	result = global.GVA_DB.First(&vehicleType, req.VehicleTypeID)
+	if result.RowsAffected == 0 {
+		return errors.New("车辆类型不存在")
+	}
+
 	// 处理临时车辆的情况
 	var ownerId *uint
 	if req.OwnerId > 0 {
@@ -59,12 +72,12 @@ func (s *VehicleService) UpdateVehicle(req request.VehicleUpdate) error {
 		ownerId = nil
 	}
 	err := global.GVA_DB.Model(&vehicle).Updates(map[string]interface{}{
-		"plate_number":  req.PlateNumber,
-		"rfid_tag":      req.RFIDTag,
-		"vehicle_type":  req.VehicleType,
-		"vehicle_brand": req.VehicleBrand,
-		"vehicle_color": req.VehicleColor,
-		"owner_id":      ownerId,
+		"plate_number":    req.PlateNumber,
+		"rfid_tag":        req.RFIDTag,
+		"vehicle_type_id": req.VehicleTypeID,
+		"vehicle_brand":   req.VehicleBrand,
+		"vehicle_color":   req.VehicleColor,
+		"owner_id":        ownerId,
 	}).Error
 
 	return err
@@ -83,7 +96,7 @@ func (s *VehicleService) GetVehicleByID(id uint) (dao.Vehicle, error) {
 // GetVehicleByPlateNumber 根据车牌号获取车辆信息
 func (s *VehicleService) GetVehicleByPlateNumber(plateNumber, RFIDTag string) (dao.Vehicle, error) {
 	var vehicle dao.Vehicle
-	result := global.GVA_DB.Where("plate_number = ? or rfid_tag = ?", plateNumber, RFIDTag).First(&vehicle)
+	result := global.GVA_DB.Where("plate_number = ? or rfid_tag = ?", plateNumber, RFIDTag).Preload("VehicleType").First(&vehicle)
 	if result.RowsAffected == 0 {
 		return dao.Vehicle{}, errors.New("车辆不存在")
 	}
@@ -110,8 +123,8 @@ func (s *VehicleService) ListVehicles(req request.VehicleQuery) (int64, []dao.Ve
 		query = query.Where("plate_number LIKE ?", "%"+req.PlateNumber+"%")
 	}
 
-	if req.VehicleType != "" {
-		query = query.Where("vehicle_type = ?", req.VehicleType)
+	if req.VehicleTypeID > 0 {
+		query = query.Where("vehicle_type_id = ?", req.VehicleTypeID)
 	}
 
 	if req.OwnerId > 0 {
@@ -123,7 +136,7 @@ func (s *VehicleService) ListVehicles(req request.VehicleQuery) (int64, []dao.Ve
 
 	// 分页查询
 	offset := (req.Page - 1) * req.PageSize
-	result := query.Offset(offset).Limit(req.PageSize).Preload("Owner").Find(&vehicles)
+	result := query.Offset(offset).Limit(req.PageSize).Preload("Owner").Preload("VehicleType").Find(&vehicles)
 	if result.Error != nil {
 		return 0, nil, result.Error
 	}
@@ -145,11 +158,11 @@ func (s *VehicleService) VehicleEntry(req request.VehicleEntry) (request.Vehicle
 	// 检查车辆是否存在
 	vehicle, err := s.GetVehicleByPlateNumber(req.PlateNumber, req.RFIDTag)
 	if err != nil {
-		// 如果车辆不存在,创建新车辆
+		// 如果车辆不存在,创建新车辆,默认使用ID为1的车辆类型(临时车)
 		createReq := request.VehicleCreate{
-			PlateNumber: req.PlateNumber,
-			VehicleType: "临时车", // 默认类型
-			OwnerId:     0,     // 默认无车主
+			PlateNumber:   req.PlateNumber,
+			VehicleTypeID: 1, // 默认类型ID为1(临时车)
+			OwnerId:       0, // 默认无车主
 		}
 		err = s.CreateVehicle(createReq)
 		if err != nil {
@@ -254,7 +267,7 @@ func (s *VehicleService) VehicleExit(req request.VehicleExit) (request.VehicleEx
 func (s *VehicleService) calculateFee(vehicle dao.Vehicle, stayTime int64) float64 {
 	// 获取收费配置
 	var feeConfig dao.FeeConfig
-	result := global.GVA_DB.Where("vehicle_type = ?", vehicle.VehicleType).First(&feeConfig)
+	result := global.GVA_DB.Where("vehicle_type_id = ?", vehicle.VehicleTypeID).First(&feeConfig)
 	if result.RowsAffected == 0 {
 		// 使用默认收费标准
 		return float64(stayTime) * 0.1 // 默认0.1元/分钟

+ 113 - 0
server/service/vehicle/vehicle_type.go

@@ -0,0 +1,113 @@
+package vehicle
+
+import (
+	"errors"
+	"server/dao"
+	"server/global"
+	"server/model/vehicle/request"
+)
+
+type VehicleTypeService struct{}
+
+// CreateVehicleType 创建车辆类型
+func (s *VehicleTypeService) CreateVehicleType(req request.VehicleTypeCreate) error {
+	var existingType dao.VehicleType
+	result := global.GVA_DB.Where("name = ?", req.Name).First(&existingType)
+	if result.RowsAffected > 0 {
+		return errors.New("车辆类型名称已存在")
+	}
+
+	vehicleType := dao.VehicleType{
+		Name:    req.Name,
+		Remarks: req.Remarks,
+	}
+
+	return global.GVA_DB.Create(&vehicleType).Error
+}
+
+// UpdateVehicleType 更新车辆类型
+func (s *VehicleTypeService) UpdateVehicleType(req request.VehicleTypeUpdate) error {
+	var vehicleType dao.VehicleType
+	result := global.GVA_DB.First(&vehicleType, req.ID)
+	if result.RowsAffected == 0 {
+		return errors.New("车辆类型不存在")
+	}
+
+	var existingType dao.VehicleType
+	result = global.GVA_DB.Where("name = ? AND id != ?", req.Name, req.ID).First(&existingType)
+	if result.RowsAffected > 0 {
+		return errors.New("车辆类型名称已被其他类型使用")
+	}
+
+	err := global.GVA_DB.Model(&vehicleType).Updates(map[string]interface{}{
+		"name":    req.Name,
+		"remarks": req.Remarks,
+	}).Error
+
+	return err
+}
+
+// GetVehicleTypeByID 根据ID获取车辆类型
+func (s *VehicleTypeService) GetVehicleTypeByID(id uint) (dao.VehicleType, error) {
+	var vehicleType dao.VehicleType
+	result := global.GVA_DB.First(&vehicleType, id)
+	if result.RowsAffected == 0 {
+		return dao.VehicleType{}, errors.New("车辆类型不存在")
+	}
+	return vehicleType, nil
+}
+
+// ListVehicleTypes 获取车辆类型列表
+func (s *VehicleTypeService) ListVehicleTypes(req request.VehicleTypeQuery) (int64, []dao.VehicleType, error) {
+	var vehicleTypes []dao.VehicleType
+	var total int64
+
+	query := global.GVA_DB.Model(&dao.VehicleType{})
+
+	if req.Name != "" {
+		query = query.Where("name LIKE ?", "%"+req.Name+"%")
+	}
+
+	query.Count(&total)
+
+	offset := (req.Page - 1) * req.PageSize
+	result := query.Offset(offset).Limit(req.PageSize).Find(&vehicleTypes)
+	if result.Error != nil {
+		return 0, nil, result.Error
+	}
+
+	return total, vehicleTypes, nil
+}
+
+// DeleteVehicleType 删除车辆类型
+func (s *VehicleTypeService) DeleteVehicleType(id uint) error {
+	var vehicleType dao.VehicleType
+	result := global.GVA_DB.First(&vehicleType, id)
+	if result.RowsAffected == 0 {
+		return errors.New("车辆类型不存在")
+	}
+
+	var vehicleCount int64
+	global.GVA_DB.Model(&dao.Vehicle{}).Where("vehicle_type_id = ?", id).Count(&vehicleCount)
+	if vehicleCount > 0 {
+		return errors.New("该车辆类型下存在车辆,无法删除")
+	}
+
+	var feeConfigCount int64
+	global.GVA_DB.Model(&dao.FeeConfig{}).Where("vehicle_type_id = ?", id).Count(&feeConfigCount)
+	if feeConfigCount > 0 {
+		return errors.New("该车辆类型下存在收费配置,无法删除")
+	}
+
+	return global.GVA_DB.Delete(&vehicleType).Error
+}
+
+// GetAllVehicleTypes 获取所有车辆类型(用于下拉选择)
+func (s *VehicleTypeService) GetAllVehicleTypes() ([]dao.VehicleType, error) {
+	var vehicleTypes []dao.VehicleType
+	err := global.GVA_DB.Find(&vehicleTypes).Error
+	if err != nil {
+		return nil, err
+	}
+	return vehicleTypes, nil
+}

+ 51 - 0
web/src/api/vehicle.js

@@ -118,3 +118,54 @@ export const queryVehicleRecordList = (data) => {
     data
   })
 }
+
+// 创建车辆类型
+export const createVehicleType = (data) => {
+  return service({
+    url: '/vehicle/type/create',
+    method: 'post',
+    data
+  })
+}
+
+// 更新车辆类型
+export const editVehicleType = (data) => {
+  return service({
+    url: '/vehicle/type/update',
+    method: 'put',
+    data
+  })
+}
+
+// 根据ID获取车辆类型
+export const obtainVehicleTypeId = (id) => {
+  return service({
+    url: '/vehicle/type/get?id=' + id,
+    method: 'get'
+  })
+}
+
+// 获取车辆类型列表
+export const obtainVehicleTypeList = (data) => {
+  return service({
+    url: '/vehicle/type/list',
+    method: 'post',
+    data
+  })
+}
+
+// 删除车辆类型
+export const removeVehicleType = (id) => {
+  return service({
+    url: '/vehicle/type/delete?id=' + id,
+    method: 'delete'
+  })
+}
+
+// 获取所有车辆类型(下拉选择)
+export const getAllVehicleTypes = () => {
+  return service({
+    url: '/vehicle/type/all',
+    method: 'get'
+  })
+}

+ 213 - 39
web/src/view/parking/carInfo.vue

@@ -11,17 +11,16 @@
       </el-form-item>
       <el-form-item label="车辆类型">
         <el-select
-          v-model="queryForm.vehicle_type"
+          v-model="queryForm.vehicle_type_id"
           placeholder="全部"
           clearable
+          style="width:160px"
         >
           <el-option
-            label="月租车"
-            value="月租车"
-          />
-          <el-option
-            label="临时车"
-            value="临时车"
+            v-for="item in typeList"
+            :key="item.ID"
+            :label="item.name"
+            :value="item.ID"
           />
         </el-select>
       </el-form-item>
@@ -58,6 +57,10 @@
           type="primary"
           @click="openDialog('新增车辆', null)"
         >新增</el-button>
+        <el-button
+          type="warning"
+          @click="openTypeDrawer"
+        >管理车辆类型</el-button>
       </el-form-item>
     </el-form>
 
@@ -86,11 +89,10 @@
       <el-table-column
         label="车辆类型"
         align="center"
-        prop="vehicle_type"
       >
         <template #default="scope">
-          <el-tag :type="scope.row.vehicle_type === '月租车' ? 'primary' : 'success'">
-            {{ scope.row.vehicle_type }}
+          <el-tag type="primary">
+            {{ scope.row.vehicle_type?.name || "未知" }}
           </el-tag>
         </template>
       </el-table-column>
@@ -112,7 +114,6 @@
           {{ scope.row.owner?.owner_name || "临时车辆" }}
         </template>
       </el-table-column>
-
       <el-table-column
         label="操作"
         align="center"
@@ -172,16 +173,15 @@
         </el-form-item>
         <el-form-item label="车辆类型:">
           <el-select
-            v-model="formData.vehicle_type"
+            v-model="formData.vehicle_type_id"
             placeholder="请选择"
+            style="width:100%"
           >
             <el-option
-              label="月租车"
-              value="月租车"
-            />
-            <el-option
-              label="临时车"
-              value="临时车"
+              v-for="item in typeList"
+              :key="item.ID"
+              :label="item.name"
+              :value="item.ID"
             />
           </el-select>
         </el-form-item>
@@ -213,7 +213,6 @@
           </el-select>
         </el-form-item>
       </el-form>
-
       <template #footer>
         <el-button @click="dialogVisible = false">取消</el-button>
         <el-button
@@ -222,6 +221,102 @@
         >确认保存</el-button>
       </template>
     </el-dialog>
+
+    <!-- ===================== 车辆类型抽屉(Drawer)===================== -->
+    <el-drawer
+      v-model="typeDrawerVisible"
+      title="车辆类型管理"
+      size="700px"
+    >
+      <div style="display:flex; justify-content:space-between; margin-bottom:10px">
+        <el-input
+          v-model="typeQuery.name"
+          placeholder="搜索类型名称"
+          clearable
+          style="width:200px"
+          @input="getTypeList"
+        />
+        <el-button
+          type="primary"
+          icon="Plus"
+          @click="openTypeForm('新增')"
+        >新增类型</el-button>
+      </div>
+
+      <el-table
+        :data="typeList"
+        border
+      >
+        <el-table-column
+          label="ID"
+          prop="ID"
+        />
+        <el-table-column
+          label="类型名称"
+          prop="name"
+        />
+        <el-table-column
+          label="备注"
+          prop="remarks"
+        />
+        <el-table-column label="操作">
+          <template #default="scope">
+            <el-button
+              text
+              type="primary"
+              @click="openTypeForm('编辑', scope.row)"
+            >编辑</el-button>
+            <el-button
+              text
+              type="danger"
+              @click="deleteType(scope.row.ID)"
+            >删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <el-pagination
+        v-model:current-page="typeQuery.page"
+        v-model:page-size="typeQuery.pageSize"
+        :total="typeTotal"
+        layout="total, prev, pager, next"
+        style="margin-top:10px; text-align:right"
+        @current-change="getTypeList"
+      />
+    </el-drawer>
+
+    <!-- 车辆类型 新增/编辑 弹窗 -->
+    <el-dialog
+      v-model="typeDialogVisible"
+      :title="typeTitle"
+      width="450px"
+    >
+      <el-form
+        label-width="80px"
+        :model="typeForm"
+      >
+        <el-form-item label="类型名称">
+          <el-input
+            v-model="typeForm.name"
+            placeholder="如:月租车、VIP车"
+          />
+        </el-form-item>
+        <el-form-item label="备注">
+          <el-input
+            v-model="typeForm.remarks"
+            type="textarea"
+            rows="3"
+          />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="typeDialogVisible = false">取消</el-button>
+        <el-button
+          type="primary"
+          @click="saveType"
+        >保存</el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
@@ -235,38 +330,42 @@ import {
   editVehicle,
   removeVehicleList
 } from '@/api/vehicle'
-// 车主接口
 import { obtainCarOwnerList } from '@/api/carOwner'
+// 车辆类型接口
+import {
+  obtainVehicleTypeList,
+  createVehicleType,
+  editVehicleType,
+  removeVehicleType,
+  getAllVehicleTypes
+} from '@/api/vehicle'
 
-// 搜索参数
+// ==================== 车辆管理原有逻辑 ====================
 const queryForm = reactive({
   plate_number: '',
-  vehicle_type: '',
+  vehicle_type_id: null,
   owner_id: null,
   page: 1,
   pageSize: 10
 })
 
-// 车辆列表
 const vehicleList = ref([])
 const total = ref(0)
-// 车主列表
 const ownerList = ref([])
+const typeList = ref([])
 
-// 弹窗
 const dialogVisible = ref(false)
 const dialogTitle = ref('')
 const formData = reactive({
   ID: 0,
   plate_number: '',
   rfid_tag: '',
-  vehicle_type: '',
+  vehicle_type_id: null,
   vehicle_brand: '',
   vehicle_color: '',
-  owner_id: null // 支持为空(临时车)
+  owner_id: null
 })
 
-// ============== 车辆列表 ==============
 const getVehicleList = async() => {
   try {
     const res = await obtainVehicleList(queryForm)
@@ -279,33 +378,28 @@ const getVehicleList = async() => {
   }
 }
 
-// 重置搜索
 const resetQuery = () => {
   queryForm.plate_number = ''
-  queryForm.vehicle_type = ''
+  queryForm.vehicle_type_id = null
   queryForm.owner_id = null
   queryForm.page = 1
   getVehicleList()
 }
 
-// ============== 弹窗操作 ==============
 const openDialog = (title, row) => {
   dialogTitle.value = title
   dialogVisible.value = true
-  // 重置表单
   if (!row) {
     Object.assign(formData, {
       ID: 0, plate_number: '', rfid_tag: '',
-      vehicle_type: '', vehicle_brand: '',
+      vehicle_type_id: null, vehicle_brand: '',
       vehicle_color: '', owner_id: null
     })
     return
   }
-  // 编辑回显
   Object.assign(formData, row)
 }
 
-// 提交保存
 const submitForm = async() => {
   if (!formData.plate_number) {
     ElMessage.warning('请输入车牌号')
@@ -326,7 +420,6 @@ const submitForm = async() => {
   }
 }
 
-// ============== 删除 ==============
 const deleteVehicle = async(id) => {
   await ElMessageBox.confirm('确定删除该车辆?', '提示', { type: 'warning' })
   try {
@@ -338,7 +431,6 @@ const deleteVehicle = async(id) => {
   }
 }
 
-// ============== 分页 ==============
 const handlePageChange = (page) => {
   queryForm.page = page
   getVehicleList()
@@ -349,13 +441,95 @@ const handleSizeChange = (size) => {
   getVehicleList()
 }
 
-// ============== 初始化 ==============
+// ==================== 车辆类型 抽屉逻辑 ====================
+const typeDrawerVisible = ref(false)
+const typeDialogVisible = ref(false)
+const typeTitle = ref('')
+
+const typeQuery = reactive({
+  name: '',
+  page: 1,
+  pageSize: 10
+})
+const typeTotal = ref(0)
+
+const typeForm = reactive({
+  ID: 0,
+  name: '',
+  remarks: ''
+})
+
+// 打开类型抽屉
+const openTypeDrawer = () => {
+  typeDrawerVisible.value = true
+  getTypeList()
+}
+
+// 获取类型列表
+const getTypeList = async() => {
+  const res = await obtainVehicleTypeList(typeQuery)
+  if (res.code === 0) {
+    typeList.value = res.data.list
+    typeTotal.value = res.data.total
+  }
+}
+
+// 打开新增/编辑
+const openTypeForm = (title, row = null) => {
+  typeTitle.value = title
+  typeDialogVisible.value = true
+  if (!row) {
+    Object.assign(typeForm, { ID: 0, name: '', remarks: '' })
+    return
+  }
+  Object.assign(typeForm, row)
+}
+
+// 保存
+const saveType = async() => {
+  if (!typeForm.name) {
+    ElMessage.warning('请输入类型名称')
+    return
+  }
+  try {
+    if (typeForm.ID > 0) {
+      await editVehicleType(typeForm)
+      ElMessage.success('编辑成功')
+    } else {
+      await createVehicleType(typeForm)
+      ElMessage.success('新增成功')
+    }
+    typeDialogVisible.value = false
+    getTypeList()
+    loadAllTypes()
+  } catch (e) {
+    ElMessage.error('保存失败')
+  }
+}
+
+// 删除
+const deleteType = async(id) => {
+  await ElMessageBox.confirm('确定删除?')
+  await removeVehicleType(id)
+  ElMessage.success('删除成功')
+  getTypeList()
+  loadAllTypes()
+}
+
+// 加载所有类型(下拉框)
+const loadAllTypes = async() => {
+  const res = await getAllVehicleTypes()
+  if (res.code === 0) typeList.value = res.data
+}
+
+// ==================== 初始化 ====================
 const getOwnerList = async() => {
   const res = await obtainCarOwnerList()
   if (res.code === 0) ownerList.value = res.data.list
 }
 
-onMounted(() => {
+onMounted(async() => {
+  await loadAllTypes()
   getVehicleList()
   getOwnerList()
 })