puzhibing
2026-09-01 4292699a7addaf35d25c4ce00dc626c3d63a9476
feat: add card statistics and vin management
16个文件已添加
5个文件已修改
696 ■■■■■ 已修改文件
doc/耗时统计.md 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/VinManagementExcelDTO.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/VinManagementQueryDTO.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/VinManagementSaveDTO.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/TVinManagement.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/query/CardBalanceDetailQuery.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/vo/CardBalanceDetailVO.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TVinManagementController.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/mapper/TVinManagementMapper.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/TVinManagementService.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TVinManagementServiceImpl.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/resources/mapper/account/TVinManagementMapper.xml 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TCardChargingOrderController.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/export/CardBalanceDetailExport.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/mapper/TCardChargingOrderMapper.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/TCardChargingOrderService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TCardChargingOrderServiceImpl.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TCardChargingOrderMapper.xml 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/20260901_card_balance_detail_menu.sql 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/20260901_card_order_statistics_menu.sql 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/20260901_vin_management.sql 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/耗时统计.md
New file
@@ -0,0 +1,11 @@
# 任务耗时统计
> 自 2026-08-05 起,项目所有任务耗时统一记录到此 Markdown 表格(原 `doc/开发任务耗时统计.xlsx` 仅作历史备份)。
> 缺失精确时间戳时标记为「估算」或「未记录」。
| 开始时间 | 结束时间 | 耗时(秒) | 用户原始任务描述 | 生成修改方案 | 任务内容 | 修改文件 | 测试结果 | 备注 |
|---|---|---|---|---|---|---|---|---|
| 2026-08-31(估算) | 2026-08-31(估算) | 约 600(估算) | 复制一个相同功能的接口只用于内部调用;giveVip1 删除 @RequiresPermissions,不需要加 @InnerAuth | 删除 giveVip1 上的 @RequiresPermissions,不加 @InnerAuth;giveVip 保持权限校验;网关白名单外无 token 请求 401;提示持有合法 token 的外部请求可直接调用该接口的风险 | 删除 giveVip1(/user/give/vip1)的权限注解,使其可供内部 Feign 调用(微信/支付宝会员回调链路放行) | ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TAppUserController.java | mvn -pl ruoyi-service/ruoyi-account -am compile 通过;lint 0 错误 | 待部署后实测微信购买会员 vip_end_time 更新;管理后台 giveVip 不受影响 |
| 2026-08-31 17:04:00(估算) | 2026-08-31 17:05:00(估算) | 约 60(估算) | 推送服务端代码 | 先检查改动,编译订单服务,通过后提交并推送;排除未跟踪的 `doc/` 目录 | 提交并推送刷卡充电订单查询接口服务端改动,提交号 `ec2cbf74`,推送至 `origin/2.3` | `ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/query/CardChargingOrderQuery.java`;`ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TCardChargingOrderController.java`;`ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/mapper/TCardChargingOrderMapper.java`;`ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/TCardChargingOrderService.java`;`ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TCardChargingOrderServiceImpl.java`;`ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TCardChargingOrderMapper.xml` | `mvn -pl ruoyi-service/ruoyi-order -am compile -DskipTests` 通过;`git diff --check` 通过;远程分支校验通过 | 工作区仍有未跟踪 `doc/` 目录,未纳入本次提交 |
| 2026-08-31 17:20:00(估算) | 2026-08-31 17:21:00(估算) | 约 60(估算) | 修复刷卡订单汇总 SQL 语法错误 | 将代理层无法正确解析的 `TIMESTAMPDIFF(SECOND, ...)` 替换为 `UNIX_TIMESTAMP` 时间戳差值,保持 `time` 字段秒数语义不变 | 修复 `TCardChargingOrderMapper.xml` 汇总查询中的充电时长统计 SQL | `ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TCardChargingOrderMapper.xml` | `mvn -pl ruoyi-service/ruoyi-order -am compile -DskipTests` 通过;`git diff --check` 通过;运行时 Mapper 资源已确认使用 `UNIX_TIMESTAMP`;IDE 诊断 0 条 | 未执行真实数据库接口验证;本次未提交未推送 |
| 2026-09-01 10:33:38 | 2026-09-01(结束时间待推送后补记) | 未记录 | 推送代码 | 是:回归验证通过后分别提交并普通推送服务端 `2.3` 与管理后台 `dev_2.3`,不使用强制推送 | 提交并推送当前两个仓库的卡片收支明细、刷卡订单统计、VIN 管理及相关路由权限改动 | 服务端 20 个业务/SQL 文件;管理后台 12 个页面、路由和权限文件 | 服务端 Maven clean test、前端 `tsc`、Jest 11 套件/42 用例、生产构建、UTF-8 检查及两仓库 `git diff --cached --check` 已通过;推送结果待完成 | 当前服务端 `doc/` 仍未纳入暂存,待补记最终耗时后提交;构建仅有既存警告 |
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/VinManagementExcelDTO.java
New file
@@ -0,0 +1,16 @@
package com.ruoyi.account.api.dto;
import com.ruoyi.common.core.annotation.Excel;
import lombok.Data;
import java.io.Serializable;
@Data
public class VinManagementExcelDTO implements Serializable {
    private static final long serialVersionUID = 1L;
    @Excel(name = "VIN码", width = 24, prompt = "必填,最多64个字符") private String vin;
    @Excel(name = "车牌号", width = 16) private String plateNumber;
    @Excel(name = "手机号", width = 16) private String phone;
    @Excel(name = "所属公司", width = 24) private String companyName;
    @Excel(name = "备注", width = 30) private String remark;
    @Excel(name = "启用状态", width = 14, readConverterExp = "1=启用,2=禁用", combo = {"启用", "禁用"}) private String status;
}
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/VinManagementQueryDTO.java
New file
@@ -0,0 +1,14 @@
package com.ruoyi.account.api.dto;
import com.ruoyi.common.core.web.page.BasePage;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public class VinManagementQueryDTO extends BasePage {
    private String vin;
    private String plateNumber;
    private String phone;
    private Integer status;
}
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/VinManagementSaveDTO.java
New file
@@ -0,0 +1,16 @@
package com.ruoyi.account.api.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public class VinManagementSaveDTO implements Serializable {
    private static final long serialVersionUID = 1L;
    private Long id;
    private String vin;
    private String plateNumber;
    private String phone;
    private String companyName;
    private String remark;
    private Integer status;
}
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/TVinManagement.java
New file
@@ -0,0 +1,28 @@
package com.ruoyi.account.api.model;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.core.annotation.Excel;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
@TableName("t_vin_management")
public class TVinManagement implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.AUTO) private Long id;
    @Excel(name = "VIN码", width = 24) private String vin;
    @Excel(name = "车牌号", width = 16) private String plateNumber;
    @Excel(name = "手机号", width = 16) private String phone;
    @Excel(name = "所属公司", width = 24) private String companyName;
    @Excel(name = "备注", width = 30) private String remark;
    @Excel(name = "启用状态", readConverterExp = "1=启用,2=禁用") private Integer status;
    @TableLogic(value = "0", delval = "2") private Integer delFlag;
    private LocalDateTime createTime;
    private Long createBy;
    private LocalDateTime updateTime;
    private Long updateBy;
}
ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/query/CardBalanceDetailQuery.java
New file
@@ -0,0 +1,36 @@
package com.ruoyi.order.api.query;
import com.ruoyi.common.core.web.page.BasePage;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
@ApiModel(value = "CardBalanceDetailQuery卡片收支明细查询对象")
public class CardBalanceDetailQuery extends BasePage {
    @ApiModelProperty("卡号")
    private String cardNo;
    @ApiModelProperty("订单编号")
    private String code;
    @ApiModelProperty("收支类型:1收入,2支出")
    private Integer incomeType;
    @ApiModelProperty("操作内容:1充值,2充电扣款,3退款")
    private Integer operationType;
    @ApiModelProperty("操作时间范围的开始时间")
    private String operationTime1;
    @ApiModelProperty("操作时间范围的结束时间")
    private String operationTime2;
    @ApiModelProperty("站点ids(权限过滤,前端忽略)")
    private List<Integer> siteIds;
}
ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/vo/CardBalanceDetailVO.java
New file
@@ -0,0 +1,45 @@
package com.ruoyi.order.api.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@ApiModel(value = "CardBalanceDetailVO卡片收支明细")
public class CardBalanceDetailVO {
    @ApiModelProperty("流水唯一标识")
    private String id;
    @ApiModelProperty("刷卡订单ID")
    private Long orderId;
    @ApiModelProperty("卡号")
    private String cardNo;
    @ApiModelProperty("订单关联手机号")
    private String phone;
    @ApiModelProperty("订单编号")
    private String code;
    @ApiModelProperty("收支类型:1收入,2支出")
    private Integer incomeType;
    @ApiModelProperty("操作内容:1充值,2充电扣款,3退款")
    private Integer operationType;
    @ApiModelProperty("金额")
    private BigDecimal amount;
    @ApiModelProperty("备注")
    private String remark;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @ApiModelProperty("操作时间")
    private LocalDateTime operationTime;
}
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TVinManagementController.java
New file
@@ -0,0 +1,39 @@
package com.ruoyi.account.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.ruoyi.account.api.dto.VinManagementExcelDTO;
import com.ruoyi.account.api.dto.VinManagementQueryDTO;
import com.ruoyi.account.api.dto.VinManagementSaveDTO;
import com.ruoyi.account.service.TVinManagementService;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.utils.poi.ExcelUtil;
import com.ruoyi.common.core.web.controller.BaseController;
import com.ruoyi.common.security.annotation.Logical;
import com.ruoyi.common.security.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
@RestController
@RequestMapping("/t-vin-management")
public class TVinManagementController extends BaseController {
    @Autowired private TVinManagementService service;
    @RequiresPermissions(value = "/vinManagement/list", logical = Logical.OR)
    @PostMapping("/page") public R<IPage<?>> page(@RequestBody VinManagementQueryDTO query) { return R.ok(service.selectPageList(query)); }
    @RequiresPermissions(value = "/vinManagement/add", logical = Logical.OR)
    @PostMapping public R<Boolean> add(@RequestBody VinManagementSaveDTO dto) { return R.ok(service.saveVin(dto)); }
    @RequiresPermissions(value = "/vinManagement/edit", logical = Logical.OR)
    @PutMapping public R<Boolean> edit(@RequestBody VinManagementSaveDTO dto) { return R.ok(service.updateVin(dto)); }
    @RequiresPermissions(value = "/vinManagement/delete", logical = Logical.OR)
    @DeleteMapping public R<Boolean> delete(@RequestBody List<Long> ids) { return R.ok(service.deleteVins(ids)); }
    @RequiresPermissions(value = "/vinManagement/status", logical = Logical.OR)
    @PutMapping("/status") public R<Boolean> status(@RequestBody VinManagementSaveDTO dto) { return R.ok(service.updateStatus(dto.getId(), dto.getStatus())); }
    @RequiresPermissions(value = "/vinManagement/export", logical = Logical.OR)
    @PutMapping("/export") public void export(HttpServletResponse response, @RequestBody VinManagementQueryDTO query) { new ExcelUtil<>(VinManagementExcelDTO.class).exportExcel(response, service.selectExportList(query), "VIN码数据"); }
    @RequiresPermissions(value = "/vinManagement/import", logical = Logical.OR)
    @GetMapping("/import-template") public void template(HttpServletResponse response) { new ExcelUtil<>(VinManagementExcelDTO.class).importTemplateExcel(response, "VIN码导入模板"); }
    @RequiresPermissions(value = "/vinManagement/import", logical = Logical.OR)
    @PostMapping("/import") public R<String> importData(@RequestPart("file") MultipartFile file) throws Exception { List<VinManagementExcelDTO> items = new ExcelUtil<>(VinManagementExcelDTO.class).importExcel(file.getInputStream()); return R.ok(service.importVins(items)); }
}
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/mapper/TVinManagementMapper.java
New file
@@ -0,0 +1,16 @@
package com.ruoyi.account.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.api.dto.VinManagementExcelDTO;
import com.ruoyi.account.api.dto.VinManagementQueryDTO;
import com.ruoyi.account.api.model.TVinManagement;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface TVinManagementMapper extends BaseMapper<TVinManagement> {
    IPage<TVinManagement> selectPageList(Page<TVinManagement> page, @Param("query") VinManagementQueryDTO query);
    List<VinManagementExcelDTO> selectExportList(@Param("query") VinManagementQueryDTO query);
    int countByVin(@Param("vin") String vin, @Param("excludeId") Long excludeId);
}
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/TVinManagementService.java
New file
@@ -0,0 +1,19 @@
package com.ruoyi.account.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.account.api.dto.VinManagementExcelDTO;
import com.ruoyi.account.api.dto.VinManagementQueryDTO;
import com.ruoyi.account.api.dto.VinManagementSaveDTO;
import com.ruoyi.account.api.model.TVinManagement;
import java.util.List;
public interface TVinManagementService extends IService<TVinManagement> {
    IPage<TVinManagement> selectPageList(VinManagementQueryDTO query);
    boolean saveVin(VinManagementSaveDTO dto);
    boolean updateVin(VinManagementSaveDTO dto);
    boolean deleteVins(List<Long> ids);
    boolean updateStatus(Long id, Integer status);
    List<VinManagementExcelDTO> selectExportList(VinManagementQueryDTO query);
    String importVins(List<VinManagementExcelDTO> items);
}
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TVinManagementServiceImpl.java
New file
@@ -0,0 +1,45 @@
package com.ruoyi.account.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.account.api.dto.VinManagementExcelDTO;
import com.ruoyi.account.api.dto.VinManagementQueryDTO;
import com.ruoyi.account.api.dto.VinManagementSaveDTO;
import com.ruoyi.account.api.model.TVinManagement;
import com.ruoyi.account.mapper.TVinManagementMapper;
import com.ruoyi.account.service.TVinManagementService;
import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.security.utils.SecurityUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Service
public class TVinManagementServiceImpl extends ServiceImpl<TVinManagementMapper, TVinManagement> implements TVinManagementService {
    private static final int ENABLED = 1;
    private static final int DISABLED = 2;
    @Override public IPage<TVinManagement> selectPageList(VinManagementQueryDTO query) {
        VinManagementQueryDTO safe = query == null ? new VinManagementQueryDTO() : query;
        long page = safe.getPageCurr() == null || safe.getPageCurr() < 1 ? 1 : safe.getPageCurr();
        long size = safe.getPageSize() == null || safe.getPageSize() < 1 ? 10 : safe.getPageSize();
        return baseMapper.selectPageList(new Page<>(page, size), safe);
    }
    @Override public boolean saveVin(VinManagementSaveDTO dto) { validate(dto, false); TVinManagement item = from(dto); item.setDelFlag(0); item.setCreateTime(LocalDateTime.now()); item.setCreateBy(SecurityUtils.getUserId()); return save(item); }
    @Override public boolean updateVin(VinManagementSaveDTO dto) { validate(dto, true); if (getById(dto.getId()) == null) throw new ServiceException("VIN码不存在或已删除"); TVinManagement item = from(dto); item.setId(dto.getId()); item.setUpdateTime(LocalDateTime.now()); item.setUpdateBy(SecurityUtils.getUserId()); return updateById(item); }
    @Override public boolean deleteVins(List<Long> ids) { if (ids == null || ids.isEmpty()) throw new ServiceException("请选择需要删除的VIN码"); List<Long> distinct = new ArrayList<>(new HashSet<>(ids)); if (listByIds(distinct).size() != distinct.size()) throw new ServiceException("部分VIN码不存在或已删除"); return removeByIds(distinct); }
    @Override public boolean updateStatus(Long id, Integer status) { if (id == null || !validStatus(status)) throw new ServiceException("VIN码状态参数错误"); if (getById(id) == null) throw new ServiceException("VIN码不存在或已删除"); TVinManagement item = new TVinManagement(); item.setId(id); item.setStatus(status); item.setUpdateTime(LocalDateTime.now()); item.setUpdateBy(SecurityUtils.getUserId()); return updateById(item); }
    @Override public List<VinManagementExcelDTO> selectExportList(VinManagementQueryDTO query) { return baseMapper.selectExportList(query == null ? new VinManagementQueryDTO() : query); }
    @Override @Transactional(rollbackFor = Exception.class) public String importVins(List<VinManagementExcelDTO> items) { if (items == null || items.isEmpty()) throw new ServiceException("导入数据不能为空"); Set<String> vins = new HashSet<>(); int row = 1; for (VinManagementExcelDTO item : items) { row++; if (item == null || StringUtils.isEmpty(item.getVin())) throw new ServiceException("第" + row + "行VIN码不能为空"); String vin = item.getVin().trim(); if (vin.length() > 64) throw new ServiceException("第" + row + "行VIN码不能超过64个字符"); if (!vins.add(vin) || baseMapper.countByVin(vin, null) > 0) throw new ServiceException("第" + row + "行VIN码已存在或重复"); parseStatus(item.getStatus(), row); } Long userId = SecurityUtils.getUserId(); LocalDateTime now = LocalDateTime.now(); for (VinManagementExcelDTO item : items) { TVinManagement v = new TVinManagement(); v.setVin(item.getVin().trim()); v.setPlateNumber(trim(item.getPlateNumber())); v.setPhone(trim(item.getPhone())); v.setCompanyName(trim(item.getCompanyName())); v.setRemark(trim(item.getRemark())); v.setStatus(parseStatus(item.getStatus(), 0)); v.setDelFlag(0); v.setCreateBy(userId); v.setCreateTime(now); save(v); } return "成功导入" + items.size() + "条VIN码数据"; }
    private void validate(VinManagementSaveDTO dto, boolean update) { if (dto == null || StringUtils.isEmpty(dto.getVin())) throw new ServiceException("VIN码不能为空"); if (update && dto.getId() == null) throw new ServiceException("VIN码ID不能为空"); String vin = dto.getVin().trim(); if (vin.length() > 64) throw new ServiceException("VIN码不能超过64个字符"); if (!validStatus(dto.getStatus() == null ? ENABLED : dto.getStatus())) throw new ServiceException("VIN码状态参数错误"); if (baseMapper.countByVin(vin, dto.getId()) > 0) throw new ServiceException("VIN码已存在"); }
    private TVinManagement from(VinManagementSaveDTO d) { TVinManagement v = new TVinManagement(); v.setVin(d.getVin().trim()); v.setPlateNumber(trim(d.getPlateNumber())); v.setPhone(trim(d.getPhone())); v.setCompanyName(trim(d.getCompanyName())); v.setRemark(trim(d.getRemark())); v.setStatus(d.getStatus() == null ? ENABLED : d.getStatus()); return v; }
    private String trim(String value) { return value == null ? null : value.trim(); }
    private boolean validStatus(Integer status) { return status != null && (status == ENABLED || status == DISABLED); }
    private Integer parseStatus(String value, int row) { if (StringUtils.isEmpty(value) || "1".equals(value.trim()) || "启用".equals(value.trim())) return ENABLED; if ("2".equals(value.trim()) || "禁用".equals(value.trim())) return DISABLED; throw new ServiceException((row > 0 ? "第" + row + "行" : "") + "启用状态错误"); }
}
ruoyi-service/ruoyi-account/src/main/resources/mapper/account/TVinManagementMapper.xml
New file
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.account.mapper.TVinManagementMapper">
    <select id="selectPageList" resultType="com.ruoyi.account.api.model.TVinManagement">
        SELECT id, vin, plate_number, phone, company_name, remark, status, del_flag, create_time, create_by, update_time, update_by
        FROM t_vin_management WHERE del_flag = 0
        <if test="query != null and query.vin != null and query.vin != ''">AND vin LIKE CONCAT('%', #{query.vin}, '%')</if>
        <if test="query != null and query.plateNumber != null and query.plateNumber != ''">AND plate_number LIKE CONCAT('%', #{query.plateNumber}, '%')</if>
        <if test="query != null and query.phone != null and query.phone != ''">AND phone LIKE CONCAT('%', #{query.phone}, '%')</if>
        <if test="query != null and query.status != null">AND status = #{query.status}</if>
        ORDER BY create_time DESC, id DESC
    </select>
    <select id="selectExportList" resultType="com.ruoyi.account.api.dto.VinManagementExcelDTO">
        SELECT vin, plate_number AS plateNumber, phone, company_name AS companyName, remark, status
        FROM t_vin_management WHERE del_flag = 0
        <if test="query != null and query.vin != null and query.vin != ''">AND vin LIKE CONCAT('%', #{query.vin}, '%')</if>
        <if test="query != null and query.plateNumber != null and query.plateNumber != ''">AND plate_number LIKE CONCAT('%', #{query.plateNumber}, '%')</if>
        <if test="query != null and query.phone != null and query.phone != ''">AND phone LIKE CONCAT('%', #{query.phone}, '%')</if>
        <if test="query != null and query.status != null">AND status = #{query.status}</if>
        ORDER BY create_time DESC, id DESC
    </select>
    <select id="countByVin" resultType="int">SELECT COUNT(1) FROM t_vin_management WHERE vin = #{vin}<if test="excludeId != null"> AND id != #{excludeId}</if></select>
</mapper>
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TCardChargingOrderController.java
@@ -10,8 +10,11 @@
import com.ruoyi.common.log.enums.BusinessType;
import com.ruoyi.common.security.annotation.Logical;
import com.ruoyi.common.security.annotation.RequiresPermissions;
import com.ruoyi.order.api.query.CardBalanceDetailQuery;
import com.ruoyi.order.api.query.CardChargingOrderQuery;
import com.ruoyi.order.api.vo.CardBalanceDetailVO;
import com.ruoyi.order.api.vo.ChargingOrderVO;
import com.ruoyi.order.export.CardBalanceDetailExport;
import com.ruoyi.order.export.CardChargingOrderExport;
import com.ruoyi.order.service.TCardChargingOrderService;
import io.swagger.annotations.Api;
@@ -117,4 +120,37 @@
        List<Map<String, Object>> list = cardChargingOrderService.accountingStrategyList(chargingOrderId);
        return R.ok(Collections.singletonMap("list", list));
    }
    @RequiresPermissions("/chargingCardBalanceDetail/list")
    @GetMapping("/balance-detail/list")
    @ApiOperation("卡片收支明细列表")
    public R<PageInfo<CardBalanceDetailVO>> balanceDetailList(CardBalanceDetailQuery query) {
        return R.ok(cardChargingOrderService.balanceDetailPage(query));
    }
    @RequiresPermissions("/chargingCardBalanceDetail/list")
    @GetMapping("/balance-detail/summary")
    @ApiOperation("卡片收支明细汇总")
    public R<Map<String, Object>> balanceDetailSummary(CardBalanceDetailQuery query) {
        return R.ok(cardChargingOrderService.balanceDetailSummary(query));
    }
    @RequiresPermissions("/chargingCardBalanceDetail/export")
    @PutMapping("/balance-detail/export")
    @ApiOperation("卡片收支明细导出")
    @Log(title = "卡片收支明细导出", businessType = BusinessType.EXPORT)
    public void balanceDetailExport(@RequestBody CardBalanceDetailQuery query) {
        List<CardBalanceDetailVO> list = cardChargingOrderService.balanceDetailExport(query);
        List<CardBalanceDetailExport> exports = new ArrayList<>();
        for (int i = 0; i < list.size(); i++) {
            CardBalanceDetailVO detail = list.get(i);
            CardBalanceDetailExport export = new CardBalanceDetailExport();
            BeanUtils.copyProperties(detail, export);
            export.setSequence(i + 1);
            export.setOperationTime(formatTime(detail.getOperationTime()));
            exports.add(export);
        }
        Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), CardBalanceDetailExport.class, exports);
        writeWorkbook(workbook, "卡片收支明细导出.xls");
    }
}
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/export/CardBalanceDetailExport.java
New file
@@ -0,0 +1,40 @@
package com.ruoyi.order.export;
import cn.afterturn.easypoi.excel.annotation.Excel;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
@Data
public class CardBalanceDetailExport implements Serializable {
    private static final long serialVersionUID = 1L;
    @Excel(name = "序号", width = 10)
    private Integer sequence;
    @Excel(name = "卡号", width = 22)
    private String cardNo;
    @Excel(name = "关联手机号", width = 18)
    private String phone;
    @Excel(name = "收支类型", width = 12, replace = {"收入_1", "支出_2"})
    private Integer incomeType;
    @Excel(name = "操作内容", width = 16, replace = {"充值_1", "充电扣款_2", "退款_3"})
    private Integer operationType;
    @Excel(name = "金额", width = 14, numFormat = "0.00")
    private BigDecimal amount;
    @Excel(name = "订单编号", width = 28)
    private String code;
    @Excel(name = "备注", width = 30)
    private String remark;
    @Excel(name = "操作时间", width = 22)
    private String operationTime;
}
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/mapper/TCardChargingOrderMapper.java
@@ -2,7 +2,9 @@
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.order.api.model.TCardChargingOrder;
import com.ruoyi.order.api.query.CardBalanceDetailQuery;
import com.ruoyi.order.api.query.CardChargingOrderQuery;
import com.ruoyi.order.api.vo.CardBalanceDetailVO;
import com.ruoyi.order.api.vo.ChargingOrderVO;
import org.apache.ibatis.annotations.Param;
@@ -35,6 +37,14 @@
    List<Map<String, Object>> accountingStrategyList(@Param("chargingOrderId") Long chargingOrderId);
    List<CardBalanceDetailVO> balanceDetailList(@Param("query") CardBalanceDetailQuery query,
                                                @Param("offset") long offset,
                                                @Param("pageSize") long pageSize);
    long balanceDetailCount(@Param("query") CardBalanceDetailQuery query);
    Map<String, Object> balanceDetailSummary(@Param("query") CardBalanceDetailQuery query);
    List<Map<String, Object>> shareIncomeByOrderIds(@Param("orderIds") List<Long> orderIds);
    Map<String, Object> shareIncomeSummaryByOrderIds(@Param("orderIds") List<Long> orderIds);
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/TCardChargingOrderService.java
@@ -3,7 +3,9 @@
import com.ruoyi.common.core.web.page.PageInfo;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.order.api.model.TCardChargingOrder;
import com.ruoyi.order.api.query.CardBalanceDetailQuery;
import com.ruoyi.order.api.query.CardChargingOrderQuery;
import com.ruoyi.order.api.vo.CardBalanceDetailVO;
import com.ruoyi.order.api.vo.ChargingOrderVO;
import java.util.List;
@@ -23,4 +25,10 @@
    List<Map<String, Object>> balanceIncomeDetail(CardChargingOrderQuery query);
    List<Map<String, Object>> accountingStrategyList(Long chargingOrderId);
    PageInfo<CardBalanceDetailVO> balanceDetailPage(CardBalanceDetailQuery query);
    Map<String, Object> balanceDetailSummary(CardBalanceDetailQuery query);
    List<CardBalanceDetailVO> balanceDetailExport(CardBalanceDetailQuery query);
}
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TCardChargingOrderServiceImpl.java
@@ -9,7 +9,9 @@
import com.ruoyi.common.core.web.page.PageInfo;
import com.ruoyi.common.security.service.TokenService;
import com.ruoyi.order.api.model.TCardChargingOrder;
import com.ruoyi.order.api.query.CardBalanceDetailQuery;
import com.ruoyi.order.api.query.CardChargingOrderQuery;
import com.ruoyi.order.api.vo.CardBalanceDetailVO;
import com.ruoyi.order.api.vo.ChargingOrderVO;
import com.ruoyi.order.mapper.TCardChargingOrderMapper;
import com.ruoyi.order.service.TCardChargingOrderService;
@@ -106,6 +108,56 @@
        return baseMapper.accountingStrategyList(chargingOrderId);
    }
    @Override
    public PageInfo<CardBalanceDetailVO> balanceDetailPage(CardBalanceDetailQuery query) {
        int pageCurr = query.getPageCurr() == null || query.getPageCurr() < 1 ? 1 : query.getPageCurr();
        int pageSize = query.getPageSize() == null || query.getPageSize() < 1 ? 50 : query.getPageSize();
        setAuthSiteIds(query);
        PageInfo<CardBalanceDetailVO> pageInfo = new PageInfo<>(pageCurr, pageSize);
        long total = baseMapper.balanceDetailCount(query);
        pageInfo.setTotal(total);
        if (total == 0) {
            pageInfo.setRecords(Collections.emptyList());
            return pageInfo;
        }
        long offset = (long) (pageCurr - 1) * pageSize;
        List<CardBalanceDetailVO> list = baseMapper.balanceDetailList(query, offset, pageSize);
        pageInfo.setRecords(list);
        return pageInfo;
    }
    @Override
    public Map<String, Object> balanceDetailSummary(CardBalanceDetailQuery query) {
        setAuthSiteIds(query);
        Map<String, Object> result = baseMapper.balanceDetailSummary(query);
        if (result == null) {
            result = new HashMap<>();
        }
        result.putIfAbsent("orderCount", 0);
        result.putIfAbsent("incomeAmount", BigDecimal.ZERO);
        result.putIfAbsent("expenseAmount", BigDecimal.ZERO);
        return result;
    }
    @Override
    public List<CardBalanceDetailVO> balanceDetailExport(CardBalanceDetailQuery query) {
        setAuthSiteIds(query);
        long total = baseMapper.balanceDetailCount(query);
        if (total == 0) {
            return new ArrayList<>();
        }
        List<CardBalanceDetailVO> result = new ArrayList<>();
        int batchSize = 5000;
        for (long offset = 0; offset < total; offset += batchSize) {
            List<CardBalanceDetailVO> batch = baseMapper.balanceDetailList(query, offset, batchSize);
            if (batch == null || batch.isEmpty()) {
                break;
            }
            result.addAll(batch);
        }
        return result;
    }
    private void setAuthSiteIds(CardChargingOrderQuery query) {
        List<Integer> siteIds = new ArrayList<>();
        if (query.getSiteId() != null) {
@@ -123,6 +175,19 @@
        query.setSiteIds(siteIds);
    }
    private void setAuthSiteIds(CardBalanceDetailQuery query) {
        List<Integer> siteIds = new ArrayList<>();
        Long userId = tokenService.getLoginUser().getUserid();
        if (userId != null) {
            siteIds = Optional.ofNullable(siteClient.getSiteListByUserId(userId).getData()).orElse(Collections.emptyList())
                    .stream().map(GetSiteListDTO::getId).collect(Collectors.toList());
        }
        if (siteIds.isEmpty()) {
            siteIds.add(-1);
        }
        query.setSiteIds(siteIds);
    }
    private void fillPageExtra(List<ChargingOrderVO> list) {
        if (list == null || list.isEmpty()) {
            return;
ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TCardChargingOrderMapper.xml
@@ -84,6 +84,108 @@
        ORDER BY balancePayAmount DESC
    </select>
    <sql id="balanceDetailUnion">
        SELECT CONCAT('R-', o.id) AS id,
               o.id AS orderId,
               o.phone AS phone,
               o.card_no AS cardNo,
               o.code AS code,
               1 AS incomeType,
               1 AS operationType,
               o.recharge_amount AS amount,
               '刷卡订单充值' AS remark,
               COALESCE(o.pay_time, o.create_time) AS operationTime
        FROM t_card_charging_order o
        WHERE o.del_flag = 0
          AND o.recharge_payment_status = 2
          AND IFNULL(o.recharge_amount, 0) &gt; 0
        UNION ALL
        SELECT CONCAT('P-', o.id) AS id,
               o.id AS orderId,
               o.phone AS phone,
               o.card_no AS cardNo,
               o.code AS code,
               2 AS incomeType,
               2 AS operationType,
               o.balance_pay_amount AS amount,
               '充电余额扣款' AS remark,
               COALESCE(o.pay_time, o.end_time, o.create_time) AS operationTime
        FROM t_card_charging_order o
        WHERE o.del_flag = 0
          AND IFNULL(o.balance_pay_amount, 0) &gt; 0
        UNION ALL
        SELECT CONCAT('F-', r.id) AS id,
               o.id AS orderId,
               o.phone AS phone,
               o.card_no AS cardNo,
               o.code AS code,
               1 AS incomeType,
               3 AS operationType,
               r.balance_refund_amount AS amount,
               COALESCE(r.refund_remark, r.refund_reason, r.refund_content, '余额退款') AS remark,
               COALESCE(r.refund_time, r.create_time) AS operationTime
        FROM t_card_charging_order_refund r
        INNER JOIN t_card_charging_order o ON o.id = r.charging_order_id
        WHERE r.del_flag = 0
          AND o.del_flag = 0
          AND r.refund_status = 2
          AND IFNULL(r.balance_refund_amount, 0) &gt; 0
    </sql>
    <sql id="balanceDetailCondition">
        <where>
            <if test="query.siteIds != null and query.siteIds.size() > 0">
                AND o.site_id IN
                <foreach collection="query.siteIds" item="siteId" open="(" separator="," close=")">
                    #{siteId}
                </foreach>
            </if>
            <if test="query.cardNo != null and query.cardNo != ''">
                AND d.cardNo LIKE CONCAT('%', #{query.cardNo}, '%')
            </if>
            <if test="query.code != null and query.code != ''">
                AND d.code LIKE CONCAT('%', #{query.code}, '%')
            </if>
            <if test="query.incomeType != null">
                AND d.incomeType = #{query.incomeType}
            </if>
            <if test="query.operationType != null">
                AND d.operationType = #{query.operationType}
            </if>
            <if test="query.operationTime1 != null and query.operationTime1 != ''">
                AND d.operationTime &gt;= #{query.operationTime1}
            </if>
            <if test="query.operationTime2 != null and query.operationTime2 != ''">
                AND d.operationTime &lt;= #{query.operationTime2}
            </if>
        </where>
    </sql>
    <select id="balanceDetailList" resultType="com.ruoyi.order.api.vo.CardBalanceDetailVO">
        SELECT d.*
        FROM (<include refid="balanceDetailUnion"/>) d
        INNER JOIN t_card_charging_order o ON o.id = d.orderId
        <include refid="balanceDetailCondition"/>
        ORDER BY d.operationTime DESC, d.id DESC
        LIMIT #{offset}, #{pageSize}
    </select>
    <select id="balanceDetailCount" resultType="long">
        SELECT COUNT(1)
        FROM (<include refid="balanceDetailUnion"/>) d
        INNER JOIN t_card_charging_order o ON o.id = d.orderId
        <include refid="balanceDetailCondition"/>
    </select>
    <select id="balanceDetailSummary" resultType="java.util.LinkedHashMap">
        SELECT COUNT(DISTINCT d.orderId) AS orderCount,
               IFNULL(SUM(CASE WHEN d.incomeType = 1 THEN d.amount ELSE 0 END), 0) AS incomeAmount,
               IFNULL(SUM(CASE WHEN d.incomeType = 2 THEN d.amount ELSE 0 END), 0) AS expenseAmount
        FROM (<include refid="balanceDetailUnion"/>) d
        INNER JOIN t_card_charging_order o ON o.id = d.orderId
        <include refid="balanceDetailCondition"/>
    </select>
    <select id="accountingStrategyList" resultType="java.util.LinkedHashMap">
        SELECT id,
               charging_order_id AS chargingOrderId,
sql/20260901_card_balance_detail_menu.sql
New file
@@ -0,0 +1,66 @@
-- Card balance detail menu and button permissions.
-- Chinese text is encoded as UTF-8 hex literals to avoid PowerShell/mysql.exe encoding issues.
SET @card_parent_id := (
    SELECT menu_id
    FROM sys_menu
    WHERE perms = '/chargingCard/list' AND parent_id = 0
    ORDER BY menu_id
    LIMIT 1
);
INSERT INTO sys_menu (
    menu_name, parent_id, order_num, path, component, query, is_frame, is_cache,
    menu_type, visible, status, perms, icon, create_by, create_time, remark
)
SELECT
    CONVERT(0xE58DA1E78987E694B6E694AFE6988EE7BB86 USING utf8mb4),
    @card_parent_id, 4, 'balanceDetail', 'card/balanceDetail', NULL, 1, 0,
    'C', '0', 0, '/chargingCardBalanceDetail/list', '#', 'admin', NOW(),
    CONVERT(0xE58DA1E78987E694B6E694AFE6988EE7BB86E88F9CE58D95 USING utf8mb4)
WHERE @card_parent_id IS NOT NULL
  AND NOT EXISTS (
      SELECT 1 FROM sys_menu WHERE perms = '/chargingCardBalanceDetail/list'
  );
SET @balance_detail_menu_id := (
    SELECT menu_id
    FROM sys_menu
    WHERE perms = '/chargingCardBalanceDetail/list'
    ORDER BY menu_id
    LIMIT 1
);
INSERT INTO sys_menu (
    menu_name, parent_id, order_num, path, component, query, is_frame, is_cache,
    menu_type, visible, status, perms, icon, create_by, create_time, remark
)
SELECT
    CONVERT(0xE5AFBCE587BA USING utf8mb4),
    @balance_detail_menu_id, 1, '#', NULL, NULL, 1, 0,
    'F', '0', 0, '/chargingCardBalanceDetail/export', '#', 'admin', NOW(),
    CONVERT(0xE58DA1E78987E694B6E694AFE6988EE7BB86E5AFBCE587BAE68C89E992AE USING utf8mb4)
WHERE @balance_detail_menu_id IS NOT NULL
  AND NOT EXISTS (
      SELECT 1 FROM sys_menu WHERE perms = '/chargingCardBalanceDetail/export'
  );
INSERT IGNORE INTO sys_role_menu (role_id, menu_id)
SELECT DISTINCT role_id, @balance_detail_menu_id
FROM sys_role_menu
WHERE menu_id = @card_parent_id
  AND @balance_detail_menu_id IS NOT NULL;
SET @balance_detail_export_id := (
    SELECT menu_id
    FROM sys_menu
    WHERE perms = '/chargingCardBalanceDetail/export'
    ORDER BY menu_id
    LIMIT 1
);
INSERT IGNORE INTO sys_role_menu (role_id, menu_id)
SELECT DISTINCT role_id, @balance_detail_export_id
FROM sys_role_menu
WHERE menu_id = @card_parent_id
  AND @balance_detail_export_id IS NOT NULL;
sql/20260901_card_order_statistics_menu.sql
New file
@@ -0,0 +1,16 @@
-- 卡片管理-刷卡订单统计菜单及超级管理员授权(幂等)
USE charging_pile_other;
INSERT INTO sys_menu
(menu_name, parent_id, order_num, path, component, query, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
SELECT CONVERT(0xE588B7E58DA1E8AEA2E7BB9FE8AEA1 USING utf8mb4), parent.menu_id, 3, 'cardOrderStatistics', 'order/cardOrderStatistics/index', '', 1, 0, 'C', '0', '0', '/cardOrderStatistics', 'transaction', 'admin', NOW(), '', NULL, '刷卡订单统计'
FROM sys_menu parent
WHERE parent.path = 'card'
  AND parent.menu_type = 'M'
  AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = '/cardOrderStatistics');
INSERT INTO sys_role_menu (role_id, menu_id)
SELECT 1, menu.menu_id
FROM sys_menu menu
WHERE menu.perms = '/cardOrderStatistics'
  AND NOT EXISTS (SELECT 1 FROM sys_role_menu role_menu WHERE role_menu.role_id = 1 AND role_menu.menu_id = menu.menu_id);
sql/20260901_vin_management.sql
New file
@@ -0,0 +1,45 @@
-- VIN码管理业务表及菜单权限,使用 UTF-8 执行。
USE charging_pile_account;
CREATE TABLE IF NOT EXISTS t_vin_management (
  id BIGINT NOT NULL AUTO_INCREMENT,
  vin VARCHAR(64) NOT NULL,
  plate_number VARCHAR(32) DEFAULT NULL,
  phone VARCHAR(32) DEFAULT NULL,
  company_name VARCHAR(128) DEFAULT NULL,
  remark VARCHAR(255) DEFAULT NULL,
  status TINYINT NOT NULL DEFAULT 1,
  del_flag TINYINT NOT NULL DEFAULT 0,
  create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  create_by BIGINT DEFAULT NULL,
  update_time DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  update_by BIGINT DEFAULT NULL,
  PRIMARY KEY (id), UNIQUE KEY uk_vin (vin), KEY idx_vin_status_del (status, del_flag)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='VIN码管理';
USE charging_pile_other;
INSERT INTO sys_menu (menu_name,parent_id,order_num,path,component,menu_type,visible,status,perms,icon,create_by,create_time,remark)
SELECT 'VIN码管理',0,11,'/vinManagement',NULL,'M','0',0,'/vinManagement','car','admin',NOW(),'VIN码管理父级菜单'
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE parent_id=0 AND path='/vinManagement');
SET @vin_parent := (SELECT menu_id FROM sys_menu WHERE parent_id=0 AND path='/vinManagement' ORDER BY menu_id LIMIT 1);
INSERT INTO sys_menu (menu_name,parent_id,order_num,path,component,menu_type,visible,status,perms,icon,create_by,create_time,remark)
SELECT 'VIN码管理',@vin_parent,1,'list','vin','C','0',0,'/vinManagement/list','car','admin',NOW(),'VIN码管理列表'
WHERE @vin_parent IS NOT NULL AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE parent_id=@vin_parent AND path='list');
SET @vin_menu := (SELECT menu_id FROM sys_menu WHERE parent_id=@vin_parent AND path='list' ORDER BY menu_id LIMIT 1);
INSERT INTO sys_menu (menu_name,parent_id,order_num,path,component,menu_type,visible,status,perms,icon,create_by,create_time,remark)
SELECT '新增',@vin_menu,1,'#',NULL,'F','0',0,'/vinManagement/add','', 'admin',NOW(),'VIN新增'
WHERE @vin_menu IS NOT NULL AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE parent_id=@vin_menu AND perms='/vinManagement/add');
INSERT INTO sys_menu (menu_name,parent_id,order_num,path,component,menu_type,visible,status,perms,icon,create_by,create_time,remark)
SELECT '编辑',@vin_menu,2,'#',NULL,'F','0',0,'/vinManagement/edit','', 'admin',NOW(),'VIN编辑'
WHERE @vin_menu IS NOT NULL AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE parent_id=@vin_menu AND perms='/vinManagement/edit');
INSERT INTO sys_menu (menu_name,parent_id,order_num,path,component,menu_type,visible,status,perms,icon,create_by,create_time,remark)
SELECT '删除',@vin_menu,3,'#',NULL,'F','0',0,'/vinManagement/delete','', 'admin',NOW(),'VIN删除'
WHERE @vin_menu IS NOT NULL AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE parent_id=@vin_menu AND perms='/vinManagement/delete');
INSERT INTO sys_menu (menu_name,parent_id,order_num,path,component,menu_type,visible,status,perms,icon,create_by,create_time,remark)
SELECT '启停',@vin_menu,4,'#',NULL,'F','0',0,'/vinManagement/status','', 'admin',NOW(),'VIN启停'
WHERE @vin_menu IS NOT NULL AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE parent_id=@vin_menu AND perms='/vinManagement/status');
INSERT INTO sys_menu (menu_name,parent_id,order_num,path,component,menu_type,visible,status,perms,icon,create_by,create_time,remark)
SELECT '导入',@vin_menu,5,'#',NULL,'F','0',0,'/vinManagement/import','', 'admin',NOW(),'VIN导入'
WHERE @vin_menu IS NOT NULL AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE parent_id=@vin_menu AND perms='/vinManagement/import');
INSERT INTO sys_menu (menu_name,parent_id,order_num,path,component,menu_type,visible,status,perms,icon,create_by,create_time,remark)
SELECT '导出',@vin_menu,6,'#',NULL,'F','0',0,'/vinManagement/export','', 'admin',NOW(),'VIN导出'
WHERE @vin_menu IS NOT NULL AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE parent_id=@vin_menu AND perms='/vinManagement/export');
INSERT IGNORE INTO sys_role_menu (role_id,menu_id) SELECT 1,menu_id FROM sys_menu WHERE menu_id IN (@vin_parent,@vin_menu) OR parent_id=@vin_menu;