From 20051550f01227ecebbe0bf064e188dd242f58f2 Mon Sep 17 00:00:00 2001
From: liujie <1793218484@qq.com>
Date: 星期一, 21 九月 2026 11:55:02 +0800
Subject: [PATCH] update
---
ruoyi-admin/src/main/resources/application-test.yml | 2
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysCardMapper.java | 41 +
ruoyi-ui/src/views/visitor-admin/card/index.vue | 249 ++++++++++
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysEmployeeServiceImpl.java | 65 ++
ruoyi-system/src/main/resources/mapper/system/SysCardMapper.xml | 176 +++++++
ruoyi-ui/dist.zip | 0
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysCard.java | 23
ruoyi-system/src/main/java/com/ruoyi/system/dto/SysCardDetailRequest.java | 14
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysCardServiceImpl.java | 340 ++++++++++++++
ruoyi-system/src/main/java/com/ruoyi/system/dto/SysCardSaveRequest.java | 23
ruoyi-ui/.env.production | 2
doc/sql/20260918_visitor_card_menu.sql | 44 +
ruoyi-system/src/main/resources/mapper/system/SysEmployeeMapper.xml | 16
ruoyi-ui/src/router/index.js | 6
doc/sql/20260918_t_card.sql | 27 +
ruoyi-system/src/main/java/com/ruoyi/system/dto/SysCardEmployeeRequest.java | 14
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysCardService.java | 32 +
ruoyi-ui/src/api/visitor-admin/card.js | 69 ++
ruoyi-admin/src/main/resources/application.yml | 2
ruoyi-system/src/main/java/com/ruoyi/system/vo/SysCardVO.java | 32 +
ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/SysCardController.java | 186 +++++++
ruoyi-admin/src/main/resources/application-prod.yml | 2
ruoyi-system/src/main/java/com/ruoyi/system/dto/SysCardDeleteRequest.java | 16
ruoyi-system/src/main/java/com/ruoyi/system/dto/SysCardPageRequest.java | 23
ruoyi-system/src/main/resources/mapper/system/SysVisitorConfigMapper.xml | 3
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysEmployeeMapper.java | 5
26 files changed, 1,403 insertions(+), 9 deletions(-)
diff --git a/doc/sql/20260918_t_card.sql b/doc/sql/20260918_t_card.sql
new file mode 100644
index 0000000..b6596eb
--- /dev/null
+++ b/doc/sql/20260918_t_card.sql
@@ -0,0 +1,27 @@
+-- 卡片管理:卡片独立保存,绑定关系同步到 t_employee.card_no。
+CREATE TABLE IF NOT EXISTS `t_card` (
+ `card_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '卡片主键',
+ `card_no` varchar(64) NOT NULL COMMENT '卡号',
+ `employee_id` bigint(20) DEFAULT NULL COMMENT '绑定员工主键',
+ `bind_time` datetime DEFAULT NULL COMMENT '绑定时间',
+ `del_flag` char(1) NOT NULL DEFAULT '0' COMMENT '删除标识:0正常 1删除',
+ `create_by` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+ `update_by` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime DEFAULT NULL COMMENT '更新时间',
+ PRIMARY KEY (`card_id`),
+ UNIQUE KEY `uk_card_no` (`card_no`, `del_flag`),
+ KEY `idx_card_employee` (`employee_id`, `del_flag`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='卡片管理表';
+
+-- 兼容已有员工卡号,首次执行迁移时自动建立卡片记录。
+INSERT INTO `t_card` (`card_no`, `employee_id`, `bind_time`, `del_flag`, `create_by`, `create_time`)
+SELECT e.`card_no`, e.`employee_id`, e.`update_time`, '0', 'system', NOW()
+FROM `t_employee` e
+WHERE e.`card_no` IS NOT NULL
+ AND e.`card_no` <> ''
+ AND e.`del_flag` = '0'
+ AND NOT EXISTS (
+ SELECT 1 FROM `t_card` c
+ WHERE c.`card_no` = e.`card_no` AND c.`del_flag` = '0'
+ );
diff --git a/doc/sql/20260918_visitor_card_menu.sql b/doc/sql/20260918_visitor_card_menu.sql
new file mode 100644
index 0000000..b438859
--- /dev/null
+++ b/doc/sql/20260918_visitor_card_menu.sql
@@ -0,0 +1,44 @@
+-- 卡片管理菜单及权限,独立于卡片表结构迁移,且不受中台版本控制。
+INSERT INTO `sys_menu` (
+ `menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`,
+ `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`,
+ `create_by`, `create_time`, `remark`
+) VALUES
+ (3013, '卡片管理', 3010, 3, 'card', 'visitor-admin/card/index', NULL, '1', '1', 'C', '0', '0', 'visitor:card:view', 'bank-card', 'admin', NOW(), '访客后台菜单'),
+ (3100, '卡片新增', 3013, 1, '#', NULL, NULL, '1', '1', 'F', '0', '0', 'visitor:card:add', '#', 'admin', NOW(), '访客后台按钮权限'),
+ (3101, '卡片编辑', 3013, 2, '#', NULL, NULL, '1', '1', 'F', '0', '0', 'visitor:card:edit', '#', 'admin', NOW(), '访客后台按钮权限'),
+ (3102, '卡片删除', 3013, 3, '#', NULL, NULL, '1', '1', 'F', '0', '0', 'visitor:card:remove', '#', 'admin', NOW(), '访客后台按钮权限'),
+ (3103, '卡片导入', 3013, 4, '#', NULL, NULL, '1', '1', 'F', '0', '0', 'visitor:card:import', '#', 'admin', NOW(), '访客后台按钮权限'),
+ (3104, '卡片导出', 3013, 5, '#', NULL, NULL, '1', '1', 'F', '0', '0', 'visitor:card:export', '#', 'admin', NOW(), '访客后台按钮权限'),
+ (3105, '卡片绑定', 3013, 6, '#', NULL, NULL, '1', '1', 'F', '0', '0', 'visitor:card:bind', '#', 'admin', NOW(), '访客后台按钮权限'),
+ (3106, '卡片解绑', 3013, 7, '#', NULL, NULL, '1', '1', 'F', '0', '0', 'visitor:card:unbind', '#', 'admin', NOW(), '访客后台按钮权限')
+ON DUPLICATE KEY UPDATE
+ `menu_name` = VALUES(`menu_name`),
+ `parent_id` = VALUES(`parent_id`),
+ `order_num` = VALUES(`order_num`),
+ `path` = VALUES(`path`),
+ `component` = VALUES(`component`),
+ `query` = VALUES(`query`),
+ `is_frame` = VALUES(`is_frame`),
+ `is_cache` = VALUES(`is_cache`),
+ `menu_type` = VALUES(`menu_type`),
+ `visible` = VALUES(`visible`),
+ `status` = VALUES(`status`),
+ `perms` = VALUES(`perms`),
+ `icon` = VALUES(`icon`),
+ `remark` = VALUES(`remark`),
+ `update_by` = 'admin',
+ `update_time` = NOW();
+
+-- 默认授权给若依管理员角色,普通角色可在角色管理中按需勾选。
+INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
+SELECT r.`role_id`, m.`menu_id`
+FROM `sys_role` r
+JOIN `sys_menu` m ON m.`menu_id` IN (3013, 3100, 3101, 3102, 3103, 3104, 3105, 3106)
+WHERE r.`role_id` = 1
+ AND NOT EXISTS (
+ SELECT 1
+ FROM `sys_role_menu` rm
+ WHERE rm.`role_id` = r.`role_id`
+ AND rm.`menu_id` = m.`menu_id`
+ );
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/SysCardController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/SysCardController.java
new file mode 100644
index 0000000..bee3173
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/SysCardController.java
@@ -0,0 +1,186 @@
+package com.ruoyi.web.controller.api;
+
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.system.dto.SysCardDeleteRequest;
+import com.ruoyi.system.dto.SysCardDetailRequest;
+import com.ruoyi.system.dto.SysCardEmployeeRequest;
+import com.ruoyi.system.dto.SysCardPageRequest;
+import com.ruoyi.system.dto.SysCardSaveRequest;
+import com.ruoyi.system.service.ISysCardService;
+import com.ruoyi.system.vo.SysCardVO;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.text.SimpleDateFormat;
+import java.util.List;
+
+/**
+ * 卡片管理接口。卡片属于本地设备侧数据,不受中台版本管控。
+ */
+@RestController
+@RequestMapping("/system/card")
+public class SysCardController extends BaseController
+{
+ @Autowired
+ private ISysCardService cardService;
+
+ @Log(title = "卡片管理", businessType = BusinessType.SELECT)
+ @PreAuthorize("@ss.hasPermi('visitor:card:view')")
+ @PostMapping("/page")
+ public AjaxResult page(@RequestBody(required = false) SysCardPageRequest request)
+ {
+ return success(cardService.pageList(request));
+ }
+
+ @Log(title = "卡片管理", businessType = BusinessType.SELECT)
+ @PreAuthorize("@ss.hasPermi('visitor:card:view')")
+ @PostMapping("/detail")
+ public AjaxResult detail(@RequestBody SysCardDetailRequest request)
+ {
+ return success(cardService.detail(request));
+ }
+
+ @Log(title = "卡片管理", businessType = BusinessType.SELECT)
+ @PreAuthorize("@ss.hasPermi('visitor:card:view')")
+ @PostMapping("/employee")
+ public AjaxResult employee(@RequestBody SysCardEmployeeRequest request)
+ {
+ return success(cardService.findEmployee(request));
+ }
+
+ @Log(title = "卡片管理", businessType = BusinessType.INSERT)
+ @PreAuthorize("@ss.hasPermi('visitor:card:add')")
+ @PostMapping("/add")
+ public AjaxResult add(@RequestBody SysCardSaveRequest request)
+ {
+ return toAjax(cardService.save(request, getUsername()));
+ }
+
+ @Log(title = "卡片管理", businessType = BusinessType.UPDATE)
+ @PreAuthorize("@ss.hasPermi('visitor:card:edit')")
+ @PostMapping("/edit")
+ public AjaxResult edit(@RequestBody SysCardSaveRequest request)
+ {
+ return toAjax(cardService.save(request, getUsername()));
+ }
+
+ @Log(title = "卡片管理", businessType = BusinessType.UPDATE)
+ @PreAuthorize("@ss.hasPermi('visitor:card:bind')")
+ @PostMapping("/bind")
+ public AjaxResult bind(@RequestBody SysCardSaveRequest request)
+ {
+ return toAjax(cardService.save(request, getUsername()));
+ }
+
+ @Log(title = "卡片管理", businessType = BusinessType.UPDATE)
+ @PreAuthorize("@ss.hasPermi('visitor:card:unbind')")
+ @PostMapping("/unbind")
+ public AjaxResult unbind(@RequestBody SysCardSaveRequest request)
+ {
+ request.setEmployeeId(null);
+ request.setEmployeeName(null);
+ return toAjax(cardService.save(request, getUsername()));
+ }
+
+ @Log(title = "卡片管理", businessType = BusinessType.DELETE)
+ @PreAuthorize("@ss.hasPermi('visitor:card:remove')")
+ @PostMapping("/delete")
+ public AjaxResult delete(@RequestBody SysCardDeleteRequest request)
+ {
+ return toAjax(cardService.delete(request, getUsername()));
+ }
+
+ @Log(title = "卡片管理", businessType = BusinessType.IMPORT)
+ @PreAuthorize("@ss.hasPermi('visitor:card:import')")
+ @PostMapping("/importData")
+ public AjaxResult importData(@RequestParam("file") MultipartFile file) throws IOException
+ {
+ if (file == null || file.isEmpty())
+ {
+ return error("请选择导入文件");
+ }
+ return success(cardService.importData(file.getInputStream(), getUsername()));
+ }
+
+ @Log(title = "卡片管理", businessType = BusinessType.EXPORT)
+ @PreAuthorize("@ss.hasPermi('visitor:card:export')")
+ @PostMapping("/export")
+ public void export(@RequestBody(required = false) SysCardPageRequest request, HttpServletResponse response) throws IOException
+ {
+ response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+ response.setCharacterEncoding("utf-8");
+ String fileName = URLEncoder.encode("卡片列表", "UTF-8").replaceAll("\\+", "%20");
+ response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
+ List<SysCardVO> cards = cardService.list(request);
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ try (Workbook workbook = new XSSFWorkbook())
+ {
+ Sheet sheet = workbook.createSheet("卡片列表");
+ String[] headers = {"卡号", "员工姓名", "联系方式", "所属部门", "绑定时间", "卡片状态"};
+ Row headerRow = sheet.createRow(0);
+ for (int index = 0; index < headers.length; index++)
+ {
+ Cell cell = headerRow.createCell(index);
+ cell.setCellValue(headers[index]);
+ sheet.setColumnWidth(index, 22 * 256);
+ }
+ for (int index = 0; index < cards.size(); index++)
+ {
+ SysCardVO card = cards.get(index);
+ Row row = sheet.createRow(index + 1);
+ row.createCell(0).setCellValue(value(card.getCardNo()));
+ row.createCell(1).setCellValue(value(card.getEmployeeName()));
+ row.createCell(2).setCellValue(value(card.getContactPhone()));
+ row.createCell(3).setCellValue(value(card.getDeptName()));
+ row.createCell(4).setCellValue(card.getBindTime() == null ? "" : dateFormat.format(card.getBindTime()));
+ row.createCell(5).setCellValue(Boolean.TRUE.equals(card.getBound()) ? "已绑定" : "未绑定");
+ }
+ workbook.write(response.getOutputStream());
+ }
+ }
+
+ @Log(title = "卡片管理", businessType = BusinessType.EXPORT)
+ @PreAuthorize("@ss.hasPermi('visitor:card:import')")
+ @PostMapping("/importTemplate")
+ public void importTemplate(HttpServletResponse response) throws IOException
+ {
+ response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+ response.setCharacterEncoding("utf-8");
+ String fileName = URLEncoder.encode("卡片导入模板", "UTF-8").replaceAll("\\+", "%20");
+ response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
+ try (Workbook workbook = new XSSFWorkbook())
+ {
+ Sheet sheet = workbook.createSheet("卡片导入");
+ String[] headers = {"卡号", "员工姓名"};
+ Row headerRow = sheet.createRow(0);
+ for (int index = 0; index < headers.length; index++)
+ {
+ headerRow.createCell(index).setCellValue(headers[index]);
+ sheet.setColumnWidth(index, 24 * 256);
+ }
+ workbook.write(response.getOutputStream());
+ }
+ }
+
+ private String value(String value)
+ {
+ return value == null ? "" : value;
+ }
+}
diff --git a/ruoyi-admin/src/main/resources/application-prod.yml b/ruoyi-admin/src/main/resources/application-prod.yml
index 1c00496..d6cd895 100644
--- a/ruoyi-admin/src/main/resources/application-prod.yml
+++ b/ruoyi-admin/src/main/resources/application-prod.yml
@@ -17,7 +17,7 @@
# 部署模式:standalone单机版 cluster集群版
deploymentMode: cluster
# 集群版是否对接中台:true对接 false不对接
- middlePlatformEnabled: false
+ middlePlatformEnabled: true
# 开发环境配置
server:
# 服务器的HTTP端口,默认为8080
diff --git a/ruoyi-admin/src/main/resources/application-test.yml b/ruoyi-admin/src/main/resources/application-test.yml
index eccc799..1c90ae0 100644
--- a/ruoyi-admin/src/main/resources/application-test.yml
+++ b/ruoyi-admin/src/main/resources/application-test.yml
@@ -17,7 +17,7 @@
# 部署模式:standalone单机版 cluster集群版
deploymentMode: cluster
# 集群版是否对接中台:true对接 false不对接
- middlePlatformEnabled: false
+ middlePlatformEnabled: true
# 开发环境配置
server:
# 服务器的HTTP端口,默认为8080
diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml
index e673e36..dcc106d 100644
--- a/ruoyi-admin/src/main/resources/application.yml
+++ b/ruoyi-admin/src/main/resources/application.yml
@@ -1,4 +1,4 @@
# 项目相关配置
spring:
profiles:
- active: prod
+ active: test
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysCard.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysCard.java
new file mode 100644
index 0000000..e971a07
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysCard.java
@@ -0,0 +1,23 @@
+package com.ruoyi.system.domain;
+
+import com.ruoyi.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 卡片管理实体,对应 t_card 表。
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class SysCard extends BaseEntity
+{
+ private static final long serialVersionUID = 1L;
+
+ private Long cardId;
+
+ private String cardNo;
+
+ private Long employeeId;
+
+ private String delFlag;
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/dto/SysCardDeleteRequest.java b/ruoyi-system/src/main/java/com/ruoyi/system/dto/SysCardDeleteRequest.java
new file mode 100644
index 0000000..21ec92c
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/dto/SysCardDeleteRequest.java
@@ -0,0 +1,16 @@
+package com.ruoyi.system.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 卡片删除请求。
+ */
+@Data
+public class SysCardDeleteRequest
+{
+ @ApiModelProperty("卡片主键集合")
+ private List<Long> cardIds;
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/dto/SysCardDetailRequest.java b/ruoyi-system/src/main/java/com/ruoyi/system/dto/SysCardDetailRequest.java
new file mode 100644
index 0000000..e500b24
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/dto/SysCardDetailRequest.java
@@ -0,0 +1,14 @@
+package com.ruoyi.system.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 卡片详情请求。
+ */
+@Data
+public class SysCardDetailRequest
+{
+ @ApiModelProperty("卡片主键")
+ private Long cardId;
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/dto/SysCardEmployeeRequest.java b/ruoyi-system/src/main/java/com/ruoyi/system/dto/SysCardEmployeeRequest.java
new file mode 100644
index 0000000..4f75671
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/dto/SysCardEmployeeRequest.java
@@ -0,0 +1,14 @@
+package com.ruoyi.system.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 按员工姓名查询绑定信息请求。
+ */
+@Data
+public class SysCardEmployeeRequest
+{
+ @ApiModelProperty("员工姓名")
+ private String employeeName;
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/dto/SysCardPageRequest.java b/ruoyi-system/src/main/java/com/ruoyi/system/dto/SysCardPageRequest.java
new file mode 100644
index 0000000..18c1d22
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/dto/SysCardPageRequest.java
@@ -0,0 +1,23 @@
+package com.ruoyi.system.dto;
+
+import com.ruoyi.common.core.domain.BasePage;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 卡片分页查询请求。
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class SysCardPageRequest extends BasePage
+{
+ @ApiModelProperty("卡号")
+ private String cardNo;
+
+ @ApiModelProperty("员工姓名")
+ private String employeeName;
+
+ @ApiModelProperty("卡片是否已绑定")
+ private Boolean bound;
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/dto/SysCardSaveRequest.java b/ruoyi-system/src/main/java/com/ruoyi/system/dto/SysCardSaveRequest.java
new file mode 100644
index 0000000..c02041b
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/dto/SysCardSaveRequest.java
@@ -0,0 +1,23 @@
+package com.ruoyi.system.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 卡片新增或编辑请求。
+ */
+@Data
+public class SysCardSaveRequest
+{
+ @ApiModelProperty("卡片主键,编辑时必填")
+ private Long cardId;
+
+ @ApiModelProperty("卡号")
+ private String cardNo;
+
+ @ApiModelProperty("员工主键,绑定时必填")
+ private Long employeeId;
+
+ @ApiModelProperty("员工姓名,用于导入时校验")
+ private String employeeName;
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysCardMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysCardMapper.java
new file mode 100644
index 0000000..2904a4f
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysCardMapper.java
@@ -0,0 +1,41 @@
+package com.ruoyi.system.mapper;
+
+import com.ruoyi.common.basic.PageInfo;
+import com.ruoyi.system.domain.SysCard;
+import com.ruoyi.system.dto.SysCardPageRequest;
+import com.ruoyi.system.vo.SysCardVO;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 卡片管理数据层。
+ */
+@Mapper
+public interface SysCardMapper
+{
+ List<SysCardVO> pageList(@Param("query") SysCardPageRequest query, @Param("pageInfo") PageInfo<SysCardVO> pageInfo);
+
+ List<SysCardVO> selectList(@Param("query") SysCardPageRequest query);
+
+ SysCardVO selectVoById(Long cardId);
+
+ SysCard selectById(Long cardId);
+
+ SysCard selectByCardNo(@Param("cardNo") String cardNo);
+
+ SysCard selectByEmployeeId(@Param("employeeId") Long employeeId);
+
+ int insertCard(SysCard card);
+
+ int updateCard(SysCard card);
+
+ int deleteByIds(@Param("cardIds") List<Long> cardIds, @Param("updateBy") String updateBy);
+
+ int bindCard(@Param("cardId") Long cardId, @Param("employeeId") Long employeeId, @Param("updateBy") String updateBy);
+
+ int unbindByEmployeeId(@Param("employeeId") Long employeeId, @Param("updateBy") String updateBy);
+
+ int unbindByCardNo(@Param("cardNo") String cardNo, @Param("updateBy") String updateBy);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysEmployeeMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysEmployeeMapper.java
index 5e28f0b..4a9b2ce 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysEmployeeMapper.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysEmployeeMapper.java
@@ -23,6 +23,8 @@
public SysEmployeeVO selectVoById(Long employeeId);
+ public List<SysEmployeeVO> selectVoByName(@Param("employeeName") String employeeName);
+
public SysEmployee selectById(Long employeeId);
public SysEmployee checkEmployeeNoUnique(@Param("employeeNo") String employeeNo,
@@ -43,6 +45,9 @@
public int clearCard(@Param("employeeIds") List<Long> employeeIds, @Param("updateBy") String updateBy);
+ public int updateCardNo(@Param("employeeId") Long employeeId, @Param("cardNo") String cardNo,
+ @Param("updateBy") String updateBy);
+
public int clearFace(@Param("employeeIds") List<Long> employeeIds, @Param("updateBy") String updateBy);
public int deleteByIds(@Param("employeeIds") List<Long> employeeIds, @Param("updateBy") String updateBy);
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysCardService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysCardService.java
new file mode 100644
index 0000000..ea449cb
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysCardService.java
@@ -0,0 +1,32 @@
+package com.ruoyi.system.service;
+
+import com.ruoyi.common.basic.PageInfo;
+import com.ruoyi.system.dto.SysCardDeleteRequest;
+import com.ruoyi.system.dto.SysCardDetailRequest;
+import com.ruoyi.system.dto.SysCardEmployeeRequest;
+import com.ruoyi.system.dto.SysCardPageRequest;
+import com.ruoyi.system.dto.SysCardSaveRequest;
+import com.ruoyi.system.vo.SysCardVO;
+
+import java.io.InputStream;
+import java.util.List;
+
+/**
+ * 卡片管理服务。
+ */
+public interface ISysCardService
+{
+ PageInfo<SysCardVO> pageList(SysCardPageRequest request);
+
+ List<SysCardVO> list(SysCardPageRequest request);
+
+ SysCardVO detail(SysCardDetailRequest request);
+
+ SysCardVO findEmployee(SysCardEmployeeRequest request);
+
+ int save(SysCardSaveRequest request, String username);
+
+ int delete(SysCardDeleteRequest request, String username);
+
+ String importData(InputStream inputStream, String username);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysCardServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysCardServiceImpl.java
new file mode 100644
index 0000000..67de457
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysCardServiceImpl.java
@@ -0,0 +1,340 @@
+package com.ruoyi.system.service.impl;
+
+import com.ruoyi.common.basic.PageInfo;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.system.domain.SysCard;
+import com.ruoyi.system.domain.SysEmployee;
+import com.ruoyi.system.dto.SysCardDeleteRequest;
+import com.ruoyi.system.dto.SysCardDetailRequest;
+import com.ruoyi.system.dto.SysCardEmployeeRequest;
+import com.ruoyi.system.dto.SysCardPageRequest;
+import com.ruoyi.system.dto.SysCardSaveRequest;
+import com.ruoyi.system.mapper.SysCardMapper;
+import com.ruoyi.system.mapper.SysEmployeeMapper;
+import com.ruoyi.system.service.ISysCardService;
+import com.ruoyi.system.utils.EmployeePhoneCrypto;
+import com.ruoyi.system.vo.SysCardVO;
+import com.ruoyi.system.vo.SysEmployeeVO;
+import org.apache.poi.ss.usermodel.DataFormatter;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.usermodel.WorkbookFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 卡片管理服务实现。
+ */
+@Service
+public class SysCardServiceImpl implements ISysCardService
+{
+ @Autowired
+ private SysCardMapper cardMapper;
+
+ @Autowired
+ private SysEmployeeMapper employeeMapper;
+
+ @Override
+ public PageInfo<SysCardVO> pageList(SysCardPageRequest request)
+ {
+ SysCardPageRequest query = normalizeQuery(request);
+ PageInfo<SysCardVO> pageInfo = new PageInfo<SysCardVO>(query.getPageNum(), query.getPageSize());
+ List<SysCardVO> rows = cardMapper.pageList(query, pageInfo);
+ fillDisplayFields(rows, false);
+ pageInfo.setRecords(rows);
+ return pageInfo;
+ }
+
+ @Override
+ public List<SysCardVO> list(SysCardPageRequest request)
+ {
+ List<SysCardVO> rows = cardMapper.selectList(normalizeQuery(request));
+ fillDisplayFields(rows, true);
+ return rows;
+ }
+
+ @Override
+ public SysCardVO detail(SysCardDetailRequest request)
+ {
+ Long cardId = requiredId(request == null ? null : request.getCardId(), "卡片主键不能为空");
+ SysCardVO card = cardMapper.selectVoById(cardId);
+ if (card == null)
+ {
+ throw new ServiceException("卡片不存在");
+ }
+ fillDisplayFields(Collections.singletonList(card), true);
+ return card;
+ }
+
+ @Override
+ public SysCardVO findEmployee(SysCardEmployeeRequest request)
+ {
+ String employeeName = requiredText(request == null ? null : request.getEmployeeName(), "请输入员工姓名");
+ List<SysEmployeeVO> employees = employeeMapper.selectVoByName(employeeName);
+ if (employees == null || employees.isEmpty())
+ {
+ throw new ServiceException("该员工不存在,请到员工管理中添加");
+ }
+ if (employees.size() > 1)
+ {
+ throw new ServiceException("员工姓名重复,请使用员工管理中的唯一姓名");
+ }
+ SysEmployeeVO employee = employees.get(0);
+ employee.setContactPhone(EmployeePhoneCrypto.decrypt(employee.getContactPhone()));
+ SysCardVO result = new SysCardVO();
+ result.setEmployeeId(employee.getEmployeeId());
+ result.setEmployeeName(employee.getEmployeeName());
+ result.setContactPhone(employee.getContactPhone());
+ result.setDeptId(employee.getDeptId());
+ result.setDeptName(employee.getDeptName());
+ return result;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public int save(SysCardSaveRequest request, String username)
+ {
+ if (request == null)
+ {
+ throw new ServiceException("卡片内容不能为空");
+ }
+ String cardNo = requiredText(request.getCardNo(), "卡号不能为空");
+ SysCard existing = request.getCardId() == null ? null : cardMapper.selectById(request.getCardId());
+ if (request.getCardId() != null && existing == null)
+ {
+ throw new ServiceException("卡片不存在");
+ }
+
+ Long employeeId = resolveEmployeeId(request);
+ SysCard duplicated = cardMapper.selectByCardNo(cardNo);
+ if (duplicated != null && (existing == null || !duplicated.getCardId().equals(existing.getCardId())))
+ {
+ throw new ServiceException("卡号已存在");
+ }
+
+ SysEmployee employee = employeeId == null ? null : employeeMapper.selectById(employeeId);
+ if (employeeId != null && employee == null)
+ {
+ throw new ServiceException("该员工不存在,请到员工管理中添加");
+ }
+ if (employeeId != null)
+ {
+ if (StringUtils.isNotEmpty(StringUtils.trim(request.getEmployeeName()))
+ && !StringUtils.trim(request.getEmployeeName()).equals(employee.getEmployeeName()))
+ {
+ throw new ServiceException("员工信息已变更,请重新校验员工姓名");
+ }
+ boolean editingEmployeeCard = existing != null && employeeId.equals(existing.getEmployeeId());
+ if (StringUtils.isNotEmpty(employee.getCardNo())
+ && !cardNo.equals(employee.getCardNo())
+ && !editingEmployeeCard)
+ {
+ throw new ServiceException("该员工已绑定其他卡片");
+ }
+ SysCard employeeCard = cardMapper.selectByEmployeeId(employeeId);
+ if (employeeCard != null && (existing == null || !employeeCard.getCardId().equals(existing.getCardId())))
+ {
+ throw new ServiceException("该员工已绑定其他卡片");
+ }
+ SysEmployee sameCardEmployee = employeeMapper.checkCardNoUnique(cardNo, employeeId);
+ if (sameCardEmployee != null)
+ {
+ throw new ServiceException("卡号已绑定其他员工");
+ }
+ }
+
+ if (existing == null)
+ {
+ SysCard card = new SysCard();
+ card.setCardNo(cardNo);
+ card.setEmployeeId(employeeId);
+ card.setCreateBy(username);
+ int rows = cardMapper.insertCard(card);
+ if (employeeId != null)
+ {
+ employeeMapper.updateCardNo(employeeId, cardNo, username);
+ }
+ return rows;
+ }
+
+ String oldCardNo = existing.getCardNo();
+ Long oldEmployeeId = existing.getEmployeeId();
+ if (oldEmployeeId != null && (!oldEmployeeId.equals(employeeId) || !oldCardNo.equals(cardNo)))
+ {
+ clearEmployeeCard(oldEmployeeId, oldCardNo, username);
+ cardMapper.unbindByEmployeeId(oldEmployeeId, username);
+ }
+ existing.setCardNo(cardNo);
+ existing.setUpdateBy(username);
+ int rows = cardMapper.updateCard(existing);
+ if (employeeId == null)
+ {
+ cardMapper.unbindByCardNo(cardNo, username);
+ }
+ else
+ {
+ cardMapper.bindCard(existing.getCardId(), employeeId, username);
+ employeeMapper.updateCardNo(employeeId, cardNo, username);
+ }
+ return rows;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public int delete(SysCardDeleteRequest request, String username)
+ {
+ List<Long> cardIds = request == null ? null : request.getCardIds();
+ if (cardIds == null || cardIds.isEmpty())
+ {
+ throw new ServiceException("请选择卡片");
+ }
+ for (Long cardId : cardIds)
+ {
+ SysCard card = cardMapper.selectById(requiredId(cardId, "卡片主键不能为空"));
+ if (card != null && card.getEmployeeId() != null)
+ {
+ clearEmployeeCard(card.getEmployeeId(), card.getCardNo(), username);
+ }
+ }
+ return cardMapper.deleteByIds(cardIds, username);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public String importData(InputStream inputStream, String username)
+ {
+ int successCount = 0;
+ List<String> errors = new ArrayList<String>();
+ DataFormatter formatter = new DataFormatter();
+ try (Workbook workbook = WorkbookFactory.create(inputStream))
+ {
+ Sheet sheet = workbook.getSheetAt(0);
+ if (sheet == null || sheet.getLastRowNum() < 1)
+ {
+ throw new ServiceException("导入文件没有卡片数据");
+ }
+ for (int rowIndex = 1; rowIndex <= sheet.getLastRowNum(); rowIndex++)
+ {
+ Row row = sheet.getRow(rowIndex);
+ String cardNo = getCell(row, 0, formatter);
+ String employeeName = getCell(row, 1, formatter);
+ if (StringUtils.isEmpty(cardNo) && StringUtils.isEmpty(employeeName))
+ {
+ continue;
+ }
+ try
+ {
+ SysCardSaveRequest request = new SysCardSaveRequest();
+ request.setCardNo(cardNo);
+ request.setEmployeeName(employeeName);
+ save(request, username);
+ successCount++;
+ }
+ catch (Exception e)
+ {
+ errors.add("第" + (rowIndex + 1) + "行:" + e.getMessage());
+ }
+ }
+ }
+ catch (ServiceException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new ServiceException("卡片导入失败:" + e.getMessage());
+ }
+ if (successCount == 0 && errors.isEmpty())
+ {
+ throw new ServiceException("导入文件没有有效卡片数据");
+ }
+ if (errors.isEmpty())
+ {
+ return "导入成功,共导入" + successCount + "条卡片数据";
+ }
+ return "导入完成,成功" + successCount + "条,失败" + errors.size() + "条;" + String.join(";", errors);
+ }
+
+ private Long resolveEmployeeId(SysCardSaveRequest request)
+ {
+ if (request.getEmployeeId() != null)
+ {
+ return request.getEmployeeId();
+ }
+ if (StringUtils.isEmpty(StringUtils.trim(request.getEmployeeName())))
+ {
+ return null;
+ }
+ SysCardEmployeeRequest employeeRequest = new SysCardEmployeeRequest();
+ employeeRequest.setEmployeeName(request.getEmployeeName());
+ return findEmployee(employeeRequest).getEmployeeId();
+ }
+
+ private void clearEmployeeCard(Long employeeId, String cardNo, String username)
+ {
+ SysEmployee employee = employeeMapper.selectById(employeeId);
+ if (employee != null && cardNo != null && cardNo.equals(employee.getCardNo()))
+ {
+ employeeMapper.updateCardNo(employeeId, null, username);
+ }
+ }
+
+ private SysCardPageRequest normalizeQuery(SysCardPageRequest request)
+ {
+ SysCardPageRequest query = request == null ? new SysCardPageRequest() : request;
+ query.setCardNo(StringUtils.trim(query.getCardNo()));
+ query.setEmployeeName(StringUtils.trim(query.getEmployeeName()));
+ return query;
+ }
+
+ private void fillDisplayFields(List<SysCardVO> cards, boolean fullPhone)
+ {
+ for (SysCardVO card : cards)
+ {
+ String phone = EmployeePhoneCrypto.decrypt(card.getContactPhone());
+ card.setContactPhone(fullPhone ? phone : maskPhone(phone));
+ card.setBound(card.getEmployeeId() != null);
+ }
+ }
+
+ private String maskPhone(String phone)
+ {
+ if (phone == null || phone.length() < 7)
+ {
+ return "";
+ }
+ return phone.substring(0, 3) + "****" + phone.substring(phone.length() - 4);
+ }
+
+ private String requiredText(String value, String message)
+ {
+ String result = StringUtils.trim(value);
+ if (StringUtils.isEmpty(result))
+ {
+ throw new ServiceException(message);
+ }
+ return result;
+ }
+
+ private Long requiredId(Long value, String message)
+ {
+ if (value == null || value <= 0)
+ {
+ throw new ServiceException(message);
+ }
+ return value;
+ }
+
+ private String getCell(Row row, int index, DataFormatter formatter)
+ {
+ return row == null ? "" : StringUtils.trim(formatter.formatCellValue(row.getCell(index)));
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysEmployeeServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysEmployeeServiceImpl.java
index 6e39eea..cf2f15d 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysEmployeeServiceImpl.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysEmployeeServiceImpl.java
@@ -5,6 +5,7 @@
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.SysEmployee;
+import com.ruoyi.system.domain.SysCard;
import com.ruoyi.system.dto.SysEmployeeAddRequest;
import com.ruoyi.system.dto.SysEmployeeDeleteRequest;
import com.ruoyi.system.dto.SysEmployeeDetailRequest;
@@ -14,6 +15,7 @@
import com.ruoyi.system.dto.SysEmployeeUnbindRequest;
import com.ruoyi.system.mapper.SysDeptMapper;
import com.ruoyi.system.mapper.SysEmployeeMapper;
+import com.ruoyi.system.mapper.SysCardMapper;
import com.ruoyi.system.service.ISysEmployeeService;
import com.ruoyi.system.utils.EmployeePhoneCrypto;
import com.ruoyi.system.vo.SysEmployeeVO;
@@ -24,6 +26,7 @@
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.io.InputStream;
@@ -45,6 +48,9 @@
@Autowired
private SysEmployeeMapper employeeMapper;
+
+ @Autowired
+ private SysCardMapper cardMapper;
@Autowired
private SysDeptMapper deptMapper;
@@ -83,6 +89,7 @@
}
@Override
+ @Transactional(rollbackFor = Exception.class)
public int add(SysEmployeeAddRequest request, String username)
{
if (request == null)
@@ -93,10 +100,13 @@
request.getContactPhone(), request.getPosition(), request.getCardNo(), request.getFaceImage(),
request.getEnabled(), null);
employee.setCreateBy(username);
- return employeeMapper.insertEmployee(employee);
+ int rows = employeeMapper.insertEmployee(employee);
+ syncCard(employee.getEmployeeId(), null, employee.getCardNo(), username);
+ return rows;
}
@Override
+ @Transactional(rollbackFor = Exception.class)
public int edit(SysEmployeeEditRequest request, String username)
{
if (request == null)
@@ -108,12 +118,15 @@
{
throw new ServiceException("员工不存在");
}
+ SysEmployee oldEmployee = employeeMapper.selectById(employeeId);
SysEmployee employee = buildEmployee(request.getDeptId(), request.getEmployeeName(), request.getEmployeeNo(),
request.getContactPhone(), request.getPosition(), request.getCardNo(), request.getFaceImage(),
request.getEnabled(), employeeId);
employee.setEmployeeId(employeeId);
employee.setUpdateBy(username);
- return employeeMapper.updateEmployee(employee);
+ int rows = employeeMapper.updateEmployee(employee);
+ syncCard(employeeId, oldEmployee.getCardNo(), employee.getCardNo(), username);
+ return rows;
}
@Override
@@ -128,9 +141,16 @@
}
@Override
+ @Transactional(rollbackFor = Exception.class)
public int unbindCard(SysEmployeeUnbindRequest request, String username)
{
- return employeeMapper.clearCard(getRequiredEmployeeIds(request == null ? null : request.getEmployeeIds()), username);
+ List<Long> employeeIds = getRequiredEmployeeIds(request == null ? null : request.getEmployeeIds());
+ int rows = employeeMapper.clearCard(employeeIds, username);
+ for (Long employeeId : employeeIds)
+ {
+ cardMapper.unbindByEmployeeId(employeeId, username);
+ }
+ return rows;
}
@Override
@@ -140,9 +160,16 @@
}
@Override
+ @Transactional(rollbackFor = Exception.class)
public int delete(SysEmployeeDeleteRequest request, String username)
{
- return employeeMapper.deleteByIds(getRequiredEmployeeIds(request == null ? null : request.getEmployeeIds()), username);
+ List<Long> employeeIds = getRequiredEmployeeIds(request == null ? null : request.getEmployeeIds());
+ int rows = employeeMapper.deleteByIds(employeeIds, username);
+ for (Long employeeId : employeeIds)
+ {
+ cardMapper.unbindByEmployeeId(employeeId, username);
+ }
+ return rows;
}
@Override
@@ -369,6 +396,36 @@
return StringUtils.trim(value);
}
+ /**
+ * 员工模块直接维护卡号时同步卡片关系,保证两个管理入口的数据一致。
+ */
+ private void syncCard(Long employeeId, String oldCardNo, String newCardNo, String username)
+ {
+ if (oldCardNo != null && !oldCardNo.equals(newCardNo))
+ {
+ cardMapper.unbindByEmployeeId(employeeId, username);
+ }
+ if (newCardNo == null || newCardNo.length() == 0)
+ {
+ return;
+ }
+ SysCard card = cardMapper.selectByCardNo(newCardNo);
+ if (card == null)
+ {
+ card = new SysCard();
+ card.setCardNo(newCardNo);
+ card.setEmployeeId(employeeId);
+ card.setCreateBy(username);
+ cardMapper.insertCard(card);
+ return;
+ }
+ if (card.getEmployeeId() != null && !employeeId.equals(card.getEmployeeId()))
+ {
+ throw new ServiceException("卡号已绑定其他员工");
+ }
+ cardMapper.bindCard(card.getCardId(), employeeId, username);
+ }
+
private void fillDisplayFields(SysEmployeeVO employee, boolean fullPhone)
{
String phone = EmployeePhoneCrypto.decrypt(employee.getContactPhone());
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/vo/SysCardVO.java b/ruoyi-system/src/main/java/com/ruoyi/system/vo/SysCardVO.java
new file mode 100644
index 0000000..c9dda25
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/vo/SysCardVO.java
@@ -0,0 +1,32 @@
+package com.ruoyi.system.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 卡片管理返回对象。
+ */
+@Data
+public class SysCardVO
+{
+ private Long cardId;
+
+ private String cardNo;
+
+ private Long employeeId;
+
+ private String employeeName;
+
+ private String contactPhone;
+
+ private Long deptId;
+
+ private String deptName;
+
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date bindTime;
+
+ private Boolean bound;
+}
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysCardMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysCardMapper.xml
new file mode 100644
index 0000000..92db048
--- /dev/null
+++ b/ruoyi-system/src/main/resources/mapper/system/SysCardMapper.xml
@@ -0,0 +1,176 @@
+<?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.system.mapper.SysCardMapper">
+
+ <resultMap type="SysCard" id="SysCardResult">
+ <id property="cardId" column="card_id" />
+ <result property="cardNo" column="card_no" />
+ <result property="employeeId" column="employee_id" />
+ <result property="delFlag" column="del_flag" />
+ <result property="createBy" column="create_by" />
+ <result property="createTime" column="create_time" />
+ <result property="updateBy" column="update_by" />
+ <result property="updateTime" column="update_time" />
+ </resultMap>
+
+ <resultMap type="SysCardVO" id="SysCardVOResult">
+ <id property="cardId" column="card_id" />
+ <result property="cardNo" column="card_no" />
+ <result property="employeeId" column="employee_id" />
+ <result property="employeeName" column="employee_name" />
+ <result property="contactPhone" column="contact_phone_cipher" />
+ <result property="deptId" column="dept_id" />
+ <result property="deptName" column="dept_name" />
+ <result property="bindTime" column="bind_time" />
+ <result property="bound" column="bound" />
+ </resultMap>
+
+ <sql id="selectCardVo">
+ select c.card_id,
+ c.card_no,
+ c.employee_id,
+ e.employee_name,
+ e.contact_phone_cipher,
+ e.dept_id,
+ d.dept_name,
+ c.bind_time,
+ case when c.employee_id is not null then 1 else 0 end as bound
+ from t_card c
+ left join t_employee e on e.employee_id = c.employee_id and e.del_flag = '0'
+ left join sys_dept d on d.dept_id = e.dept_id and d.del_flag = '0'
+ </sql>
+
+ <select id="pageList" resultMap="SysCardVOResult">
+ <include refid="selectCardVo" />
+ <where>
+ c.del_flag = '0'
+ <if test="query.cardNo != null and query.cardNo != ''">
+ and c.card_no like concat('%', #{query.cardNo}, '%')
+ </if>
+ <if test="query.employeeName != null and query.employeeName != ''">
+ and e.employee_name like concat('%', #{query.employeeName}, '%')
+ </if>
+ <if test="query.bound != null and query.bound">
+ and c.employee_id is not null
+ </if>
+ <if test="query.bound != null and !query.bound">
+ and c.employee_id is null
+ </if>
+ </where>
+ order by c.bind_time desc, c.card_id desc
+ </select>
+
+ <select id="selectList" resultMap="SysCardVOResult">
+ <include refid="selectCardVo" />
+ <where>
+ c.del_flag = '0'
+ <if test="query.cardNo != null and query.cardNo != ''">
+ and c.card_no like concat('%', #{query.cardNo}, '%')
+ </if>
+ <if test="query.employeeName != null and query.employeeName != ''">
+ and e.employee_name like concat('%', #{query.employeeName}, '%')
+ </if>
+ <if test="query.bound != null and query.bound">
+ and c.employee_id is not null
+ </if>
+ <if test="query.bound != null and !query.bound">
+ and c.employee_id is null
+ </if>
+ </where>
+ order by c.bind_time desc, c.card_id desc
+ </select>
+
+ <select id="selectVoById" parameterType="Long" resultMap="SysCardVOResult">
+ <include refid="selectCardVo" />
+ where c.card_id = #{cardId}
+ and c.del_flag = '0'
+ </select>
+
+ <select id="selectById" parameterType="Long" resultMap="SysCardResult">
+ select card_id, card_no, employee_id, del_flag, create_by, create_time, update_by, update_time
+ from t_card
+ where card_id = #{cardId}
+ and del_flag = '0'
+ </select>
+
+ <select id="selectByCardNo" resultMap="SysCardResult">
+ select card_id, card_no, employee_id, del_flag, create_by, create_time, update_by, update_time
+ from t_card
+ where card_no = #{cardNo}
+ and del_flag = '0'
+ limit 1
+ </select>
+
+ <select id="selectByEmployeeId" resultMap="SysCardResult">
+ select card_id, card_no, employee_id, del_flag, create_by, create_time, update_by, update_time
+ from t_card
+ where employee_id = #{employeeId}
+ and del_flag = '0'
+ limit 1
+ </select>
+
+ <insert id="insertCard" parameterType="SysCard" useGeneratedKeys="true" keyProperty="cardId">
+ insert into t_card (card_no, employee_id, bind_time, create_by, create_time)
+ values (#{cardNo}, #{employeeId},
+ <choose>
+ <when test="employeeId != null">sysdate()</when>
+ <otherwise>null</otherwise>
+ </choose>,
+ #{createBy}, sysdate())
+ </insert>
+
+ <update id="updateCard" parameterType="SysCard">
+ update t_card
+ set card_no = #{cardNo},
+ update_by = #{updateBy},
+ update_time = sysdate()
+ where card_id = #{cardId}
+ and del_flag = '0'
+ </update>
+
+ <update id="bindCard">
+ update t_card
+ set employee_id = #{employeeId},
+ bind_time = sysdate(),
+ update_by = #{updateBy},
+ update_time = sysdate()
+ where card_id = #{cardId}
+ and del_flag = '0'
+ </update>
+
+ <update id="unbindByEmployeeId">
+ update t_card
+ set employee_id = null,
+ bind_time = null,
+ update_by = #{updateBy},
+ update_time = sysdate()
+ where employee_id = #{employeeId}
+ and del_flag = '0'
+ </update>
+
+ <update id="unbindByCardNo">
+ update t_card
+ set employee_id = null,
+ bind_time = null,
+ update_by = #{updateBy},
+ update_time = sysdate()
+ where card_no = #{cardNo}
+ and del_flag = '0'
+ </update>
+
+ <update id="deleteByIds">
+ update t_card
+ set employee_id = null,
+ bind_time = null,
+ del_flag = '1',
+ update_by = #{updateBy},
+ update_time = sysdate()
+ where del_flag = '0'
+ and card_id in
+ <foreach collection="cardIds" item="cardId" open="(" separator="," close=")">
+ #{cardId}
+ </foreach>
+ </update>
+</mapper>
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysEmployeeMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysEmployeeMapper.xml
index 5c5b802..9c31955 100644
--- a/ruoyi-system/src/main/resources/mapper/system/SysEmployeeMapper.xml
+++ b/ruoyi-system/src/main/resources/mapper/system/SysEmployeeMapper.xml
@@ -108,6 +108,13 @@
and e.del_flag = '0'
</select>
+ <select id="selectVoByName" resultMap="SysEmployeeVOResult">
+ <include refid="selectEmployeeVo" />
+ where e.employee_name = #{employeeName}
+ and e.del_flag = '0'
+ order by e.employee_id
+ </select>
+
<select id="selectById" parameterType="Long" resultMap="SysEmployeeResult">
<include refid="selectEmployeeDomain" />
where employee_id = #{employeeId}
@@ -213,6 +220,15 @@
</foreach>
</update>
+ <update id="updateCardNo">
+ update t_employee
+ set card_no = #{cardNo},
+ update_by = #{updateBy},
+ update_time = sysdate()
+ where employee_id = #{employeeId}
+ and del_flag = '0'
+ </update>
+
<update id="clearFace">
update t_employee
set face_image = null,
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysVisitorConfigMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysVisitorConfigMapper.xml
index 08aedde..d288ed7 100644
--- a/ruoyi-system/src/main/resources/mapper/system/SysVisitorConfigMapper.xml
+++ b/ruoyi-system/src/main/resources/mapper/system/SysVisitorConfigMapper.xml
@@ -122,7 +122,8 @@
visitor_machine_entry_enabled = #{visitorMachineEntryEnabled},
update_by = #{updateBy},
update_time = sysdate(),
- remark = #{remark}
+ remark = #{remark},
+ center_path = #{centerPath}
where config_id = #{configId}
</update>
</mapper>
diff --git a/ruoyi-ui/.env.production b/ruoyi-ui/.env.production
index d819a67..e0b403b 100644
--- a/ruoyi-ui/.env.production
+++ b/ruoyi-ui/.env.production
@@ -11,6 +11,6 @@
VUE_APP_PUBLIC_PATH = './'
VUE_APP_VISITOR_FRONTEND_MODE = 'cluster'
-VUE_APP_VISITOR_MIDDLE_PLATFORM_ENABLED = 'false'
+VUE_APP_VISITOR_MIDDLE_PLATFORM_ENABLED = 'true'
# VUE_APP_VISITOR_LOGIN_MIDDLE_PLATFORM_ENABLED 未配置时默认跟随 VUE_APP_VISITOR_MIDDLE_PLATFORM_ENABLED
VUE_APP_VISITOR_API_BASE_URL = '/prod-api'
diff --git a/ruoyi-ui/dist.zip b/ruoyi-ui/dist.zip
new file mode 100644
index 0000000..40eccb5
--- /dev/null
+++ b/ruoyi-ui/dist.zip
Binary files differ
diff --git a/ruoyi-ui/src/api/visitor-admin/card.js b/ruoyi-ui/src/api/visitor-admin/card.js
new file mode 100644
index 0000000..32bbbbd
--- /dev/null
+++ b/ruoyi-ui/src/api/visitor-admin/card.js
@@ -0,0 +1,69 @@
+import request from '@/utils/request'
+
+export function listCard(data) {
+ return request({ url: '/system/card/page', method: 'post', data })
+}
+
+export function getCard(cardId) {
+ return request({
+ url: '/system/card/detail',
+ method: 'post',
+ data: {
+ cardId: cardId
+ }
+ })
+}
+
+export function findCardEmployee(employeeName) {
+ return request({
+ url: '/system/card/employee',
+ method: 'post',
+ data: {
+ employeeName: employeeName
+ },
+ silentError: true
+ })
+}
+
+export function addCard(data) {
+ return request({ url: '/system/card/add', method: 'post', data })
+}
+
+export function updateCard(data) {
+ return request({ url: '/system/card/edit', method: 'post', data })
+}
+
+export function bindCard(data) {
+ return request({ url: '/system/card/bind', method: 'post', data })
+}
+
+export function unbindCard(data) {
+ return request({ url: '/system/card/unbind', method: 'post', data })
+}
+
+export function delCard(cardIds) {
+ return request({
+ url: '/system/card/delete',
+ method: 'post',
+ data: {
+ cardIds: cardIds
+ }
+ })
+}
+
+export function importCardData(data) {
+ return request({
+ url: '/system/card/importData',
+ method: 'post',
+ data,
+ headers: { 'Content-Type': 'multipart/form-data' }
+ })
+}
+
+export function exportCard(data) {
+ return request({ url: '/system/card/export', method: 'post', data, responseType: 'blob' })
+}
+
+export function downloadCardTemplate() {
+ return request({ url: '/system/card/importTemplate', method: 'post', responseType: 'blob' })
+}
diff --git a/ruoyi-ui/src/router/index.js b/ruoyi-ui/src/router/index.js
index a1da171..3cb81ec 100644
--- a/ruoyi-ui/src/router/index.js
+++ b/ruoyi-ui/src/router/index.js
@@ -148,6 +148,12 @@
component: () => import('@/views/visitor-admin/device/index'),
name: 'VisitorDoorDevice',
meta: { title: '门禁管理', icon: 'server', deviceType: 'door' }
+ },
+ {
+ path: 'card',
+ component: () => import('@/views/visitor-admin/card/index'),
+ name: 'VisitorCard',
+ meta: { title: '卡片管理', icon: 'bank-card' }
}
]
},
diff --git a/ruoyi-ui/src/views/visitor-admin/card/index.vue b/ruoyi-ui/src/views/visitor-admin/card/index.vue
new file mode 100644
index 0000000..2a53421
--- /dev/null
+++ b/ruoyi-ui/src/views/visitor-admin/card/index.vue
@@ -0,0 +1,249 @@
+<template>
+ <main class="visitor-page card-page">
+ <section class="visitor-card">
+ <header class="visitor-header">
+ <h1>卡片管理</h1>
+ </header>
+
+ <el-form ref="queryForm" :model="queryParams" size="small" :inline="true" class="visitor-query">
+ <el-form-item label="卡号" prop="cardNo">
+ <el-input v-model="queryParams.cardNo" placeholder="请输入" clearable @keyup.enter.native="handleQuery" />
+ </el-form-item>
+ <el-form-item label="姓名" prop="employeeName">
+ <el-input v-model="queryParams.employeeName" placeholder="请输入" clearable @keyup.enter.native="handleQuery" />
+ </el-form-item>
+ <el-form-item label="卡片状态" prop="bound">
+ <el-select v-model="queryParams.bound" placeholder="全部" clearable>
+ <el-option label="已绑定" :value="true" />
+ <el-option label="未绑定" :value="false" />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button @click="resetQuery">重置</el-button>
+ <el-button type="primary" @click="handleQuery">搜索</el-button>
+ </el-form-item>
+ </el-form>
+
+ <div class="card-toolbar">
+ <div>
+ <el-button v-hasPermi="['visitor:card:import']" type="primary" plain icon="el-icon-upload2" size="small" @click="handleImport">批量导入</el-button>
+ <el-button v-hasPermi="['visitor:card:import']" type="text" icon="el-icon-download" size="small" @click="handleDownloadTemplate">下载模板</el-button>
+ <el-button v-hasPermi="['visitor:card:export']" type="primary" plain icon="el-icon-document" size="small" @click="handleExport">导出</el-button>
+ <input ref="cardImportFile" class="card-import-file" type="file" accept=".xlsx,.xls" @change="handleImportFileChange">
+ </div>
+ <el-button v-hasPermi="['visitor:card:add']" type="primary" icon="el-icon-plus" size="small" @click="handleAdd">绑定卡片</el-button>
+ </div>
+
+ <el-table v-loading="loading" class="visitor-table" :data="cardList">
+ <el-table-column label="序号" width="64" align="center">
+ <template slot-scope="scope">{{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}</template>
+ </el-table-column>
+ <el-table-column label="卡号" prop="cardNo" min-width="150" show-overflow-tooltip />
+ <el-table-column label="员工姓名" prop="employeeName" min-width="110" show-overflow-tooltip />
+ <el-table-column label="联系方式" prop="contactPhone" min-width="130" />
+ <el-table-column label="所属部门" prop="deptName" min-width="140" show-overflow-tooltip />
+ <el-table-column label="绑定时间" prop="bindTime" min-width="165" />
+ <el-table-column label="卡片状态" align="center" width="105">
+ <template slot-scope="scope">
+ <el-tag :type="scope.row.bound ? 'success' : 'warning'">{{ scope.row.bound ? '已绑定' : '未绑定' }}</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column v-if="$auth.hasPermi('visitor:card:edit') || $auth.hasPermi('visitor:card:bind') || $auth.hasPermi('visitor:card:unbind') || $auth.hasPermi('visitor:card:remove')" label="操作" align="center" width="180" fixed="right">
+ <template slot-scope="scope">
+ <el-button v-hasPermi="['visitor:card:edit']" type="text" @click="handleUpdate(scope.row)">编辑</el-button>
+ <el-button v-if="scope.row.bound" v-hasPermi="['visitor:card:unbind']" type="text" @click="handleUnbind(scope.row)">解绑</el-button>
+ <el-button v-else v-hasPermi="['visitor:card:bind']" type="text" @click="handleUpdate(scope.row)">绑定</el-button>
+ <el-button v-hasPermi="['visitor:card:remove']" type="text" class="danger-text" @click="handleDelete(scope.row)">删除</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
+ </section>
+
+ <el-dialog :title="title" :visible.sync="open" width="560px" append-to-body custom-class="visitor-card-dialog">
+ <el-form ref="form" :model="form" :rules="rules" label-position="top">
+ <el-form-item label="卡号" prop="cardNo">
+ <el-input v-model="form.cardNo" placeholder="请输入卡号" />
+ </el-form-item>
+ <el-form-item label="员工姓名" prop="employeeName">
+ <el-input v-model="form.employeeName" placeholder="请输入员工姓名" @blur="handleEmployeeBlur" />
+ <div v-if="employeeError" class="card-form-error">{{ employeeError }}</div>
+ </el-form-item>
+ <el-form-item label="所属部门">
+ <el-input v-model="form.deptName" disabled placeholder="员工校验后自动带出" />
+ </el-form-item>
+ <el-form-item label="联系方式">
+ <el-input v-model="form.contactPhone" disabled placeholder="员工校验后自动带出" />
+ </el-form-item>
+ </el-form>
+ <div slot="footer" class="dialog-footer">
+ <el-button @click="open = false">取消</el-button>
+ <el-button type="primary" @click="submitForm">保存</el-button>
+ </div>
+ </el-dialog>
+ </main>
+</template>
+
+<script>
+import { saveAs } from 'file-saver'
+import { listCard, getCard, findCardEmployee, addCard, updateCard, bindCard, unbindCard, delCard, importCardData, exportCard, downloadCardTemplate } from '@/api/visitor-admin/card'
+import { getPageRows, getPageTotal, getResponseData } from '@/utils/visitor-admin'
+
+export default {
+ name: 'VisitorCard',
+ data() {
+ return {
+ loading: false,
+ open: false,
+ title: '',
+ employeeError: '',
+ total: 0,
+ cardList: [],
+ queryParams: {
+ pageNum: 1,
+ pageSize: 10,
+ cardNo: '',
+ employeeName: '',
+ bound: undefined
+ },
+ form: {},
+ rules: {
+ cardNo: [{ required: true, message: '请输入卡号', trigger: 'blur' }],
+ employeeName: [{ required: true, message: '请输入员工姓名', trigger: 'blur' }]
+ }
+ }
+ },
+ created() {
+ this.getList()
+ },
+ methods: {
+ getList() {
+ this.loading = true
+ listCard(this.queryParams).then(res => {
+ this.cardList = getPageRows(res)
+ this.total = getPageTotal(res)
+ }).finally(() => {
+ this.loading = false
+ })
+ },
+ handleQuery() {
+ this.queryParams.pageNum = 1
+ this.getList()
+ },
+ resetQuery() {
+ this.resetForm('queryForm')
+ this.queryParams.bound = undefined
+ this.handleQuery()
+ },
+ handleAdd() {
+ this.title = '绑定卡片'
+ this.form = { cardNo: '', employeeName: '', employeeId: null, deptName: '', contactPhone: '' }
+ this.employeeError = ''
+ this.open = true
+ },
+ handleUpdate(row) {
+ getCard(row.cardId).then(res => {
+ this.title = row.bound ? '编辑卡片' : '绑定卡片'
+ this.form = getResponseData(res) || {}
+ this.employeeError = ''
+ this.open = true
+ })
+ },
+ handleEmployeeBlur() {
+ const employeeName = (this.form.employeeName || '').trim()
+ this.employeeError = ''
+ this.form.employeeId = null
+ this.form.deptName = ''
+ this.form.contactPhone = ''
+ if (!employeeName) {
+ return
+ }
+ findCardEmployee(employeeName).then(res => {
+ const employee = getResponseData(res) || {}
+ this.form.employeeId = employee.employeeId
+ this.form.employeeName = employee.employeeName
+ this.form.deptName = employee.deptName
+ this.form.contactPhone = employee.contactPhone
+ }).catch(error => {
+ this.employeeError = (error && error.message) || '该员工不存在,请到员工管理中添加'
+ })
+ },
+ submitForm() {
+ this.$refs.form.validate(valid => {
+ if (!valid) {
+ return
+ }
+ if (!this.form.employeeId) {
+ this.employeeError = '该员工不存在,请先到员工管理中添加'
+ return
+ }
+ const request = this.form.cardId ? (this.form.bound ? updateCard : bindCard) : addCard
+ request({ cardId: this.form.cardId, cardNo: this.form.cardNo, employeeId: this.form.employeeId, employeeName: this.form.employeeName }).then(() => {
+ this.$modal.msgSuccess('保存成功')
+ this.open = false
+ this.getList()
+ })
+ })
+ },
+ handleUnbind(row) {
+ this.$modal.confirm('解绑后卡片将变为未绑定状态,确认解绑吗?').then(() => unbindCard({ cardId: row.cardId, cardNo: row.cardNo })).then(() => {
+ this.$modal.msgSuccess('解绑成功')
+ this.getList()
+ })
+ },
+ handleDelete(row) {
+ this.$modal.confirm('是否确认删除该卡片?').then(() => delCard([row.cardId])).then(() => {
+ this.$modal.msgSuccess('删除成功')
+ this.getList()
+ })
+ },
+ handleImport() {
+ this.$refs.cardImportFile && this.$refs.cardImportFile.click()
+ },
+ handleImportFileChange(event) {
+ const file = event.target.files && event.target.files[0]
+ if (!file) {
+ return
+ }
+ const formData = new FormData()
+ formData.append('file', file)
+ importCardData(formData).then(res => {
+ this.$modal.msgSuccess(getResponseData(res) || '导入成功')
+ this.getList()
+ }).finally(() => {
+ event.target.value = ''
+ })
+ },
+ handleDownloadTemplate() {
+ downloadCardTemplate().then(data => saveAs(new Blob([data]), '卡片导入模板.xlsx'))
+ },
+ handleExport() {
+ exportCard(this.queryParams).then(data => saveAs(new Blob([data]), '卡片列表.xlsx'))
+ }
+ }
+}
+</script>
+
+<style scoped>
+.card-toolbar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 12px;
+}
+
+.card-import-file {
+ display: none;
+}
+
+.card-form-error {
+ color: #f56c6c;
+ line-height: 20px;
+ margin-top: 4px;
+}
+
+.danger-text {
+ color: #f56c6c;
+}
+</style>
--
Gitblit v1.7.1