Merge branch 'master' into electron_dev
| | |
| | | `device_id` varchar(100) NOT NULL COMMENT '设备ID', |
| | | `enabled` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否启用', |
| | | `online_status` varchar(32) NOT NULL DEFAULT 'UNACTIVATED' COMMENT '在线状态:UNACTIVATED未激活 ONLINE在线 OFFLINE离线', |
| | | `fingerprint` varchar(255) DEFAULT NULL COMMENT '百度SDK指纹', |
| | | `del_flag` char(1) NOT NULL DEFAULT '0' COMMENT '删除标识,0正常 1删除', |
| | | `create_by` varchar(64) DEFAULT '' COMMENT '创建者', |
| | | `create_time` datetime DEFAULT NULL COMMENT '创建时间', |
| | |
| | | @RequestMapping("/system/visitor/record") |
| | | public class SysVisitorRecordController extends BaseController |
| | | { |
| | | private static final int EXPORT_PAGE_SIZE = 10000; |
| | | |
| | | @Autowired |
| | | private ISysVisitorRecordService visitorRecordService; |
| | | |
| | |
| | | } |
| | | |
| | | /** |
| | | * 导出访客登记记录 |
| | | */ |
| | | @Log(title = "访客登记记录", businessType = BusinessType.EXPORT) |
| | | @PreAuthorize("@ss.hasPermi('visitor:record:export')") |
| | | @PostMapping("/export") |
| | | public AjaxResult export(@RequestBody(required = false) SysVisitorRecordPageRequest request) |
| | | { |
| | | SysVisitorRecordPageRequest query = request == null ? new SysVisitorRecordPageRequest() : request; |
| | | query.setPageNum(1); |
| | | query.setPageSize(EXPORT_PAGE_SIZE); |
| | | return success(visitorRecordService.pageList(query).getRecords()); |
| | | } |
| | | |
| | | /** |
| | | * 查看访客登记详情 |
| | | */ |
| | | @Log(title = "访客登记记录", businessType = BusinessType.SELECT) |
| | |
| | | # 部署模式:standalone单机版 cluster集群版 |
| | | deploymentMode: cluster |
| | | # 集群版是否对接中台:true对接 false不对接 |
| | | middlePlatformEnabled: true |
| | | middlePlatformEnabled: false |
| | | # 开发环境配置 |
| | | server: |
| | | # 服务器的HTTP端口,默认为8080 |
| | |
| | | public class RuoYiAppletApplication |
| | | { |
| | | public static void main(String[] args) { |
| | | |
| | | |
| | | ConfigurableApplicationContext application = SpringApplication.run(RuoYiAppletApplication.class, args); |
| | | try { |
| | | Environment env = application.getEnvironment(); |
| | |
| | | import com.ruoyi.system.mapper.SysVisitorMachineMapper; |
| | | import com.ruoyi.system.service.EmployeeVerifyService; |
| | | import com.ruoyi.system.service.ISysVisitorMachineService; |
| | | import com.ruoyi.system.utils.EmployeePhoneCrypto; |
| | | import com.ruoyi.system.vo.SysVisitorMachineVO; |
| | | import com.ruoyi.web.controller.tool.ImageUtil; |
| | | import io.swagger.annotations.Api; |
| | |
| | | * 刷卡验证:现场读卡器读取卡号后调用本接口,与接待人绑定的卡号比对。 |
| | | */ |
| | | @PostMapping("/allEmployee") |
| | | @ApiOperation(value = "查询所有员工接口", notes = "必传设备ID") |
| | | public AjaxResult getAllEmployee(@RequestBody @Valid ApiVisitorMachineRequest request) { |
| | | SysVisitorMachine detail = visitorMachineMapper.checkDeviceIdUnique(request.getMachineId(),null); |
| | | if (detail == null || !detail.getEnabled()) { |
| | | throw new ServiceException("未找到有效设备"); |
| | | } |
| | | @ApiOperation(value = "查询所有员工接口") |
| | | public AjaxResult getAllEmployee() { |
| | | // SysVisitorMachine detail = visitorMachineMapper.checkDeviceIdUnique(request.getMachineId(),null); |
| | | // if (detail == null || !detail.getEnabled()) { |
| | | // throw new ServiceException("未找到有效设备"); |
| | | // } |
| | | |
| | | List<Employee> employees = employeeMapper.selectAllEnabled(); |
| | | if (employees == null || employees.isEmpty()) { |
| | |
| | | dto.setFaceImage(base64); |
| | | } |
| | | |
| | | if (StringUtils.isNotEmpty(employee.getContactPhoneCipher())) { |
| | | String phone = EmployeePhoneCrypto.decrypt(employee.getContactPhoneCipher()); |
| | | dto.setPhone(phone); |
| | | } |
| | | dto.setContactPhoneCipher(null); |
| | | |
| | | return dto; |
| | | }) |
| | | .collect(Collectors.toList()); |
| | |
| | | import com.ruoyi.common.core.controller.BaseController; |
| | | import com.ruoyi.common.core.domain.AjaxResult; |
| | | import com.ruoyi.common.enums.BusinessType; |
| | | import com.ruoyi.common.exception.ServiceException; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.system.dto.*; |
| | | import com.ruoyi.system.service.ISysVisitorMachineService; |
| | | import io.swagger.annotations.Api; |
| | |
| | | @PostMapping("/activate") |
| | | @ApiOperation(value = "激活访客机设备") |
| | | public AjaxResult activate(@Valid @RequestBody ApiVisitorMachineRequest request) { |
| | | int result = visitorMachineService.activateDevice(request.getMachineId()); |
| | | if (StringUtils.isEmpty(request.getFingerprint())) { |
| | | throw new ServiceException("缺少参数"); |
| | | } |
| | | |
| | | int result = visitorMachineService.activateDevice(request.getMachineId(), request.getFingerprint()); |
| | | return result > 0 ? success("设备激活成功") : error("设备激活失败"); |
| | | } |
| | | |
| New file |
| | |
| | | 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.SysProtocolConfigDetailRequest; |
| | | import com.ruoyi.system.dto.SysProtocolConfigSaveRequest; |
| | | import com.ruoyi.system.service.ISysProtocolConfigService; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import io.swagger.annotations.ApiResponse; |
| | | import io.swagger.annotations.ApiResponses; |
| | | 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.RestController; |
| | | |
| | | /** |
| | | * 入园承诺 |
| | | * |
| | | * @author ruoyi |
| | | */ |
| | | @RestController |
| | | @RequestMapping("/api/admission") |
| | | @Api(tags = "入园承诺" , value = "入园承诺") |
| | | public class SysApiProtocolConfigController extends BaseController |
| | | { |
| | | @Autowired |
| | | private ISysProtocolConfigService protocolConfigService; |
| | | |
| | | /** |
| | | * 入园承诺 |
| | | */ |
| | | @Log(title = "入园承诺", businessType = BusinessType.SELECT) |
| | | @PostMapping("/detail") |
| | | @ApiOperation(value = "入园承诺") |
| | | @ApiResponses({ |
| | | @ApiResponse(code = 200, message = "操作成功", response = SysProtocolConfigDetailRequest.class), |
| | | @ApiResponse(code = 500, message = "操作失败") |
| | | }) |
| | | public AjaxResult<SysProtocolConfigDetailRequest> detail() |
| | | { |
| | | SysProtocolConfigDetailRequest request = new SysProtocolConfigDetailRequest(); |
| | | request.setProtocolType("ENTRY_COMMITMENT");//入园承诺 |
| | | return success(protocolConfigService.selectConfig(request)); |
| | | } |
| | | |
| | | } |
| New file |
| | |
| | | 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.SysVisitorConfigSaveRequest; |
| | | import com.ruoyi.system.service.ISysVisitorConfigService; |
| | | import com.ruoyi.system.vo.SysVisitorConfigVO; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import io.swagger.annotations.ApiResponse; |
| | | import io.swagger.annotations.ApiResponses; |
| | | 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.RestController; |
| | | |
| | | /** |
| | | * 访客系统配置接口 |
| | | * |
| | | * @author ruoyi |
| | | */ |
| | | @RestController |
| | | @RequestMapping("/api/visitorconfig") |
| | | @Api(tags = "访客系统配置接口") |
| | | public class SysApiVisitorConfigController extends BaseController |
| | | { |
| | | @Autowired |
| | | private ISysVisitorConfigService visitorConfigService; |
| | | |
| | | /** |
| | | * 查询当前配置 |
| | | */ |
| | | @Log(title = "访客系统配置", businessType = BusinessType.SELECT) |
| | | @PostMapping("/detail") |
| | | @ApiOperation( |
| | | value = "查询访客系统配置", |
| | | notes = "获取当前访客系统的配置信息,包括访客登记、审批流程等设置", |
| | | response = SysVisitorConfigVO.class |
| | | ) |
| | | @ApiResponses({ |
| | | @ApiResponse(code = 200, message = "查询成功", response = SysVisitorConfigVO.class), |
| | | @ApiResponse(code = 500, message = "查询失败") |
| | | }) |
| | | public AjaxResult<SysVisitorConfigVO> detail() |
| | | { |
| | | SysVisitorConfigVO data = visitorConfigService.selectConfig(); |
| | | return new AjaxResult<>(200, "操作成功", data); |
| | | } |
| | | } |
| | |
| | | package com.ruoyi.web.controller.api; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; |
| | | import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
| | | import com.ruoyi.common.core.controller.BaseController; |
| | | import com.ruoyi.common.core.domain.AjaxResult; |
| | |
| | | import com.ruoyi.system.domain.Employee; |
| | | import com.ruoyi.system.domain.SysVisitArea; |
| | | import com.ruoyi.system.domain.SysVisitorRecord; |
| | | import com.ruoyi.system.domain.vo.AreasList; |
| | | import com.ruoyi.system.dto.SysVisitAreaTreeRequest; |
| | | import com.ruoyi.system.dto.SysVisitorIdCardOutRequest; |
| | | import com.ruoyi.system.dto.SysVisitorRecordAddRequest; |
| | | import com.ruoyi.system.mapper.EmployeeMapper; |
| | |
| | | import com.ruoyi.system.mapper.SysVisitAreaMapper; |
| | | import com.ruoyi.system.mapper.SysVisitorRecordMapper; |
| | | import com.ruoyi.system.service.ISysVisitAreaService; |
| | | import com.ruoyi.system.utils.EmployeePhoneCrypto; |
| | | import com.ruoyi.system.vo.SysVisitAreaVO; |
| | | import com.ruoyi.web.controller.service.SysVisitorRecordService; |
| | | import com.ruoyi.web.controller.service.VisitorRegisterSyncService; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import io.swagger.annotations.ApiParam; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | 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.RestController; |
| | | |
| | | import java.util.Arrays; |
| | | import java.util.Calendar; |
| | | import java.util.Date; |
| | | import java.util.Objects; |
| | | import java.util.*; |
| | | import java.util.stream.Collectors; |
| | | |
| | | @RestController |
| | | @RequestMapping("/api/visitorRecord") |
| | | @Api(tags = "现场访客登记-签离") |
| | | public class SysApiVisitorRecordController extends BaseController { |
| | | @Autowired |
| | | private VisitorRegisterSyncService visitorRegisterSyncService; |
| | | @Autowired |
| | | private SysVisitorRecordService sysVisitorRecordService; |
| | | @Autowired |
| | |
| | | |
| | | @PostMapping("/add") |
| | | @ApiOperation(value = "访客登记", notes = "现场访客登记") |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public AjaxResult add( |
| | | @RequestBody |
| | | @ApiParam(value = "访客登记信息", required = true) |
| | |
| | | |
| | | if (StringUtils.isBlank(request.getIntervieweeName())) { |
| | | return AjaxResult.error("被访人不能为空"); |
| | | } |
| | | |
| | | List<Employee> employeeISList = employeeMapper.selectEnabledByEmployeeName(request.getIntervieweeName()); |
| | | if (employeeISList.isEmpty()) { |
| | | throw new ServiceException("被访人不存在,请联系工作人员"); |
| | | }else { |
| | | //重名 |
| | | if (StringUtils.isNotEmpty(request.getBeVisitorPhone())) { |
| | | String phoneHash = StringUtils.isEmpty(request.getBeVisitorPhone()) ? null : EmployeePhoneCrypto.hash(request.getBeVisitorPhone()); |
| | | |
| | | Employee employee = employeeMapper.selectOne(new LambdaQueryWrapper<Employee>().eq(Employee::getContactPhoneHash, phoneHash)); |
| | | if (employee == null) { |
| | | throw new ServiceException("被访人不存在,请联系工作人员"); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 签离的当天不能再次进入 签到 |
| | |
| | | BeanUtils.copyProperties(request, record); |
| | | |
| | | //查询地区 |
| | | if (StringUtils.isNotEmpty(record.getVisitAreaIds())) { |
| | | String[] visitAreaIds = record.getVisitAreaIds().split(","); |
| | | List<AreasList> areasList = null; |
| | | |
| | | //查询地区 |
| | | if (StringUtils.isNotEmpty(request.getVisitAreaIds())) { |
| | | String[] visitAreaIds = request.getVisitAreaIds().split(","); |
| | | String visitArea = Arrays.stream(visitAreaIds) |
| | | .map(id -> { |
| | | SysVisitArea area = sysVisitAreaMapper.selectById(Long.parseLong(id.trim())); |
| | | if (area == null || "1".equals(area.getDelFlag())) { |
| | | throw new ServiceException("访问区域不存在,请联系工作人员"); |
| | | } |
| | | return area != null ? area.getAreaName() : null; |
| | | }) |
| | | .filter(Objects::nonNull) |
| | | .collect(Collectors.joining(",")); |
| | | |
| | | // ====== 新增:构建 areas 数组 ====== |
| | | areasList = Arrays.stream(visitAreaIds) |
| | | .map(String::trim) |
| | | .filter(StringUtils::isNotEmpty) |
| | | .map(id -> { |
| | | SysVisitArea area = sysVisitAreaMapper.selectById(Long.parseLong(id)); |
| | | if (area == null) { |
| | | throw new ServiceException("访问区域不存在,请联系工作人员"); |
| | | } |
| | | AreasList areas = new AreasList(); |
| | | areas.setAreaCode(area.getAreaCode()); |
| | | areas.setAreaName(area.getAreaName()); |
| | | return areas; |
| | | }) |
| | | .filter(Objects::nonNull) |
| | | .collect(Collectors.toList()); |
| | | |
| | | if (!areasList.isEmpty()) { |
| | | record.setAreasList(areasList); |
| | | } |
| | | |
| | | // 使用 visitArea |
| | | if (StringUtils.isNotEmpty(visitArea)) { |
| | |
| | | } |
| | | } |
| | | |
| | | // 如果 visitAreaIds 为空,但 visitAreaNames 不为空,根据名称查询填充 visitAreaIds |
| | | else if (StringUtils.isNotEmpty(request.getVisitAreaNames())) { |
| | | String[] visitAreaNames = request.getVisitAreaNames().split(","); |
| | | |
| | | // 根据区域名称查询对应的ID,构建ID字符串 |
| | | String visitAreaIdsStr = Arrays.stream(visitAreaNames) |
| | | .map(String::trim) |
| | | .filter(StringUtils::isNotEmpty) |
| | | .map(name -> { |
| | | // 根据区域名称查询区域信息(这里假设您有根据名称查询的方法) |
| | | SysVisitAreaTreeRequest query = new SysVisitAreaTreeRequest(); |
| | | query.setAreaName(name); |
| | | List<SysVisitAreaVO> area = sysVisitAreaMapper.selectAreaTreeList(query); |
| | | return area != null ? String.valueOf(area.get(0).getAreaId()) : null; |
| | | }) |
| | | .filter(Objects::nonNull) |
| | | .collect(Collectors.joining(",")); |
| | | |
| | | if (StringUtils.isNotEmpty(visitAreaIdsStr)) { |
| | | record.setVisitAreaIds(visitAreaIdsStr); |
| | | } |
| | | |
| | | // 同时构建 areas 数组 |
| | | areasList = Arrays.stream(visitAreaNames) |
| | | .map(String::trim) |
| | | .filter(StringUtils::isNotEmpty) |
| | | .map(name -> { |
| | | SysVisitAreaTreeRequest query = new SysVisitAreaTreeRequest(); |
| | | query.setAreaName(name); |
| | | List<SysVisitAreaVO> area = sysVisitAreaMapper.selectAreaTreeList(query); |
| | | if (area == null || area.isEmpty()) { |
| | | return null; |
| | | } |
| | | AreasList areas = new AreasList(); |
| | | areas.setAreaCode(area.get(0).getAreaCode()); |
| | | areas.setAreaName(area.get(0).getAreaName()); |
| | | return areas; |
| | | }) |
| | | .filter(Objects::nonNull) |
| | | .collect(Collectors.toList()); |
| | | |
| | | if (!areasList.isEmpty()) { |
| | | record.setAreasList(areasList); |
| | | record.setRemark("来访中,允许访问区域:" + record.getVisitAreaNames()); |
| | | } |
| | | } |
| | | |
| | | //查询接待人的部门 |
| | | if (StringUtils.isNotEmpty(record.getIntervieweeName())) { |
| | | Employee employee = employeeMapper.selectEnabledByEmployeeName(record.getIntervieweeName()); |
| | | if (employee != null && employee.getDeptId() != null) { |
| | | SysDept sysDept = sysDeptMapper.selectDeptById(employee.getDeptId()); |
| | | if (sysDept != null) { |
| | | record.setIntervieweeDept(sysDept.getDeptName()); |
| | | List<Employee> employee = employeeMapper.selectEnabledByEmployeeName(record.getIntervieweeName()); |
| | | if (!employee.isEmpty()) { |
| | | Employee employeeNo1 = employee.get(0); |
| | | if (employeeNo1.getDeptId() != null) { |
| | | SysDept sysDept = sysDeptMapper.selectDeptById(employeeNo1.getDeptId()); |
| | | if (sysDept != null) { |
| | | record.setIntervieweeDept(sysDept.getDeptName()); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | record.setRegisterNo(generateRegisterNo());//登记编号 |
| | | record.setRegisterType("ONSITE");//现场登记 |
| | | // record.setRegisterType("ONSITE");//现场登记 |
| | | record.setRegisterSource("访客机"); |
| | | record.setStatus("VISITING");//来访中 |
| | | record.setRegisterTime(new Date()); |
| | | record.setCreateTime(new Date()); |
| | | record.setDelFlag("0"); |
| | | |
| | | // ================= 同步到对端服务器 ================= |
| | | try { |
| | | AjaxResult syncResult = visitorRegisterSyncService.syncVisitorRegister(record); |
| | | |
| | | if (syncResult.isSuccess()) { |
| | | logger.info("访客登记数据已同步到对端,远程ID:{}", syncResult.get("data")); |
| | | // 可将远程ID保存到本地记录 |
| | | // record.setRemoteId(syncResult.getRemoteId()); |
| | | UpdateWrapper<SysVisitorRecord> wrapper = new UpdateWrapper<>(); |
| | | wrapper.eq("record_id", record.getRecordId()); // 指定ID条件 |
| | | visitorRecordMapper.update(record, wrapper); |
| | | } else { |
| | | String msg = (String) syncResult.get("msg"); |
| | | logger.error("访客登记同步失败:{}", msg); |
| | | // 可选择是否允许本地登记成功但同步失败 |
| | | // 如果要保证数据一致性,这里可以抛出异常回滚事务 |
| | | throw new ServiceException(msg); |
| | | } |
| | | } catch (Exception e) { |
| | | logger.error("访客登记同步异常", e); |
| | | throw new ServiceException("服务器登记失败"); |
| | | } |
| | | |
| | | int rows = visitorRecordMapper.insert(record); |
| | | |
| | | sysVisitorRecordService.registerVisitor(record); |
| | | |
| | | if (rows > 0) { |
| | | return AjaxResult.success(record); |
| | | } |
| | | |
| | | return AjaxResult.error("登记失败"); |
| | | } |
| | | |
| | | |
| | | @PostMapping("/idCardSingOut") |
| | | @ApiOperation(value = "访客身份证签离", notes = "访客身份证签离") |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public AjaxResult idCardSingOut( |
| | | @RequestBody |
| | | @ApiParam(value = "访客身份证签离", required = true) |
| | |
| | | } else { |
| | | record.setStatus("SIGNED_OUT"); |
| | | } |
| | | record.setSignVerifyMethod("MANUAL"); |
| | | |
| | | SysVisitorRecord visitorRecord = new SysVisitorRecord(); |
| | | BeanUtils.copyBeanProp(visitorRecord, request); |
| | | visitorRecord.setSignVerifyMethod(record.getSignVerifyMethod()); |
| | | visitorRecord.setSignVerifyMethod(request.getSignVerifyMethod()); |
| | | visitorRecord.setStatus(record.getStatus()); |
| | | visitorRecord.setRecordId(record.getRecordId()); |
| | | visitorRecord.setSignTime(new Date()); |
| | | visitorRecordMapper.updateById(visitorRecord); |
| | | |
| | | UpdateWrapper<SysVisitorRecord> wrapperUpdate = new UpdateWrapper<>(); |
| | | wrapperUpdate.eq("record_id", visitorRecord.getRecordId()); // 指定ID条件 |
| | | visitorRecordMapper.update(visitorRecord, wrapperUpdate); |
| | | |
| | | return success(); |
| | | } |
| | |
| | | import com.ruoyi.common.core.domain.AjaxResult; |
| | | import com.ruoyi.system.dto.SysVisitorRecordDetailRequest; |
| | | import com.ruoyi.system.dto.SysVisitorRecordPageRequest; |
| | | import com.ruoyi.system.service.ISysVersionUpdateService; |
| | | import com.ruoyi.system.service.ISysVisitorRecordService; |
| | | import com.ruoyi.system.vo.SysVersionUpdateVO; |
| | | import com.ruoyi.system.vo.SysVisitorRecordDetailVO; |
| | | import com.ruoyi.system.vo.SysVisitorRecordVO; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import io.swagger.annotations.ApiResponse; |
| | | import io.swagger.annotations.ApiResponses; |
| | | import org.apache.poi.ss.usermodel.Cell; |
| | | import org.apache.poi.ss.usermodel.CellStyle; |
| | | import org.apache.poi.ss.usermodel.Font; |
| | | import org.apache.poi.ss.usermodel.HorizontalAlignment; |
| | | import org.apache.poi.ss.usermodel.Row; |
| | | import org.apache.poi.ss.usermodel.Sheet; |
| | | import org.apache.poi.xssf.streaming.SXSSFWorkbook; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | 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.RestController; |
| | | |
| | | import javax.servlet.http.HttpServletResponse; |
| | | import java.io.IOException; |
| | | import java.net.URLEncoder; |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * 管理侧访客记录接口 |
| | |
| | | { |
| | | @Autowired |
| | | private ISysVisitorRecordService visitorRecordService; |
| | | |
| | | @Autowired |
| | | private ISysVersionUpdateService versionUpdateService; |
| | | |
| | | /** |
| | | * 分页查询访客记录 |
| | |
| | | } |
| | | |
| | | /** |
| | | * 导出访客记录 |
| | | */ |
| | | @ApiOperation( |
| | | value = "导出访客记录", |
| | | notes = "按分页查询接口的筛选条件查询全部访客记录并导出 Excel,不进行分页" |
| | | ) |
| | | @PostMapping("/export") |
| | | public void export(HttpServletResponse response, |
| | | @RequestBody(required = false) SysVisitorRecordPageRequest request) throws IOException |
| | | { |
| | | List<SysVisitorRecordVO> records = visitorRecordService.list(request); |
| | | response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); |
| | | response.setCharacterEncoding("utf-8"); |
| | | String fileName = URLEncoder.encode("访客记录", "UTF-8").replace("+", "%20"); |
| | | response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); |
| | | |
| | | SXSSFWorkbook workbook = new SXSSFWorkbook(500); |
| | | try |
| | | { |
| | | writeVisitorRecordSheet(workbook, records); |
| | | workbook.write(response.getOutputStream()); |
| | | response.flushBuffer(); |
| | | } |
| | | finally |
| | | { |
| | | workbook.dispose(); |
| | | workbook.close(); |
| | | } |
| | | } |
| | | |
| | | private void writeVisitorRecordSheet(SXSSFWorkbook workbook, List<SysVisitorRecordVO> records) |
| | | { |
| | | String[] headers = { |
| | | "登记编号", "访客姓名", "证件号码", "登记类型", "被访人", "被访部门", "访问区域", |
| | | "车牌号", "来访事由", "有效期开始时间", "有效期结束时间", "状态", "登记时间" |
| | | }; |
| | | int[] columnWidths = {20, 16, 24, 16, 16, 20, 24, 16, 24, 22, 22, 20, 22}; |
| | | Sheet sheet = workbook.createSheet("访客记录"); |
| | | sheet.createFreezePane(0, 1); |
| | | CellStyle headerStyle = createHeaderStyle(workbook); |
| | | Row headerRow = sheet.createRow(0); |
| | | for (int index = 0; index < headers.length; index++) |
| | | { |
| | | Cell cell = headerRow.createCell(index); |
| | | cell.setCellValue(headers[index]); |
| | | cell.setCellStyle(headerStyle); |
| | | sheet.setColumnWidth(index, columnWidths[index] * 256); |
| | | } |
| | | |
| | | SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); |
| | | for (int index = 0; index < records.size(); index++) |
| | | { |
| | | SysVisitorRecordVO record = records.get(index); |
| | | Row row = sheet.createRow(index + 1); |
| | | setCellValue(row, 0, record.getRegisterNo()); |
| | | setCellValue(row, 1, record.getVisitorName()); |
| | | setCellValue(row, 2, record.getIdCard()); |
| | | setCellValue(row, 3, registerTypeName(record.getRegisterType())); |
| | | setCellValue(row, 4, record.getIntervieweeName()); |
| | | setCellValue(row, 5, record.getIntervieweeDept()); |
| | | setCellValue(row, 6, record.getVisitAreaNames()); |
| | | setCellValue(row, 7, record.getPlateNo()); |
| | | setCellValue(row, 8, record.getVisitReason()); |
| | | setCellValue(row, 9, formatDate(dateFormat, record.getValidStartTime())); |
| | | setCellValue(row, 10, formatDate(dateFormat, record.getValidEndTime())); |
| | | setCellValue(row, 11, statusName(record.getStatus())); |
| | | setCellValue(row, 12, formatDate(dateFormat, record.getRegisterTime())); |
| | | } |
| | | } |
| | | |
| | | private CellStyle createHeaderStyle(SXSSFWorkbook workbook) |
| | | { |
| | | Font font = workbook.createFont(); |
| | | font.setBold(true); |
| | | CellStyle style = workbook.createCellStyle(); |
| | | style.setFont(font); |
| | | style.setAlignment(HorizontalAlignment.CENTER); |
| | | return style; |
| | | } |
| | | |
| | | private void setCellValue(Row row, int columnIndex, String value) |
| | | { |
| | | row.createCell(columnIndex).setCellValue(value == null ? "" : value); |
| | | } |
| | | |
| | | private String formatDate(SimpleDateFormat dateFormat, java.util.Date date) |
| | | { |
| | | return date == null ? "" : dateFormat.format(date); |
| | | } |
| | | |
| | | private String registerTypeName(String registerType) |
| | | { |
| | | if ("APPOINTMENT".equalsIgnoreCase(registerType)) |
| | | { |
| | | return "预约登记"; |
| | | } |
| | | if ("ONSITE".equalsIgnoreCase(registerType)) |
| | | { |
| | | return "现场登记"; |
| | | } |
| | | return registerType; |
| | | } |
| | | |
| | | private String statusName(String status) |
| | | { |
| | | if ("VISITING".equalsIgnoreCase(status)) |
| | | { |
| | | return "来访中"; |
| | | } |
| | | if ("SIGNED_OUT".equalsIgnoreCase(status)) |
| | | { |
| | | return "已签离"; |
| | | } |
| | | if ("PENDING_CONFIRM".equalsIgnoreCase(status)) |
| | | { |
| | | return "逾期滞留"; |
| | | } |
| | | if ("OVERDUE_AUTO_SIGNED_OUT".equalsIgnoreCase(status)) |
| | | { |
| | | return "超期自动签离"; |
| | | } |
| | | if ("CANCELLED".equalsIgnoreCase(status)) |
| | | { |
| | | return "已取消"; |
| | | } |
| | | return status; |
| | | } |
| | | |
| | | /** |
| | | * 查询访客记录详情 |
| | | */ |
| | | @ApiOperation( |
| | |
| | | { |
| | | return success(visitorRecordService.detail(request)); |
| | | } |
| | | |
| | | /** |
| | | * 获取最新版本信息 |
| | | */ |
| | | @ApiOperation( |
| | | value = "获取最新版本信息", |
| | | notes = "返回最新一条未删除的版本更新记录", |
| | | response = SysVersionUpdateVO.class |
| | | ) |
| | | @ApiResponses({ |
| | | @ApiResponse( |
| | | code = 200, |
| | | message = "成功", |
| | | response = AjaxResult.class, |
| | | responseContainer = "Data" |
| | | ) |
| | | }) |
| | | @PostMapping("/latestVersion") |
| | | public AjaxResult<SysVersionUpdateVO> latestVersion() |
| | | { |
| | | return success(versionUpdateService.latest()); |
| | | } |
| | | } |
| | |
| | | |
| | | // 1. 参数校验 |
| | | if (!StringUtils.hasText(request.getIdCard())) { |
| | | log.warn("身份证号为空,已跳过,设备编号: {}", request.getDeviceNo()); |
| | | log.warn("身份证号为空,已跳过,人员姓名: {}", request.getIntervieweeName()); |
| | | errorCount++; |
| | | continue; |
| | | } |
| | |
| | | request.setSignImageUrl(url); |
| | | } |
| | | |
| | | // 2. 防重校验(基于设备编号+身份证号+签离时间) |
| | | // 2. 防重校验(身份证号+签离时间) |
| | | String uniqueKey = generateUniqueKey(request); |
| | | String redisKey = "offline:signout:" + uniqueKey; |
| | | if (Boolean.TRUE.equals(redisTemplate.hasKey(redisKey))) { |
| | | log.warn("签离数据重复,已跳过, 设备编号: {}, 身份证号: {}, 签离时间: {}", |
| | | request.getDeviceNo(), request.getIdCard(), request.getSignTime()); |
| | | log.warn("签离数据重复,已跳过, 身份证号: {}, 签离时间: {}", |
| | | request.getIdCard(), request.getSignTime()); |
| | | skipCount++; |
| | | continue; |
| | | } |
| | |
| | | } |
| | | |
| | | } catch (Exception e) { |
| | | log.error("处理签离数据失败, 身份证号: {}, 设备编号: {}", |
| | | request.getIdCard(), request.getDeviceNo(), e); |
| | | log.error("处理签离数据失败, 身份证号: {}", |
| | | request.getIdCard(), e); |
| | | errorCount++; |
| | | } |
| | | } |
| | |
| | | */ |
| | | private String generateUniqueKey(ApiVisitorRecodeSingoutRequest request) { |
| | | StringBuilder sb = new StringBuilder(); |
| | | sb.append(request.getDeviceNo()).append(":"); |
| | | // sb.append(request.getDeviceNo()).append(":"); |
| | | sb.append(request.getIdCard()).append(":"); |
| | | sb.append(request.getSignTime() != null ? |
| | | request.getSignTime().getTime() : System.currentTimeMillis()); |
| | |
| | | |
| | | // 1. 参数校验 |
| | | if (!StringUtils.hasText(request.getIdCard())) { |
| | | log.warn("身份证号为空,已跳过,设备编号: {}", request.getDeviceNo()); |
| | | log.warn("身份证号为空,已跳过,访问人姓名: {}", request.getVisitorName()); |
| | | errorCount++; |
| | | continue; |
| | | } |
| | |
| | | String uniqueKey = generateUniqueKey(request); |
| | | String redisKey = "offline:signin:" + uniqueKey; |
| | | if (Boolean.TRUE.equals(redisTemplate.hasKey(redisKey))) { |
| | | log.warn("签到数据重复,已跳过, 设备编号: {}, 身份证号: {}, 登记时间: {}", |
| | | request.getDeviceNo(), request.getIdCard(), request.getRegisterTime()); |
| | | log.warn("签到数据重复,已跳过, 身份证号: {}, 登记时间: {}", |
| | | request.getIdCard(), request.getRegisterTime()); |
| | | skipCount++; |
| | | continue; |
| | | } |
| | |
| | | BeanUtils.copyProperties(request, record); |
| | | record.setRegisterTime(registerTime); |
| | | record.setRegisterSource("访客机"); |
| | | record.setRegisterType("ONSITE"); |
| | | // record.setRegisterType("ONSITE"); |
| | | record.setCreateTime(new Date()); |
| | | record.setDelFlag("0"); |
| | | record.setRegisterNo(generateRegisterNo()); |
| | |
| | | // 4. 如果有未关联的签离数据,直接关联 |
| | | if (signoutTemp != null) { |
| | | // 设置签离信息 |
| | | |
| | | |
| | | |
| | | record.setSignTime(signoutTemp.getSignTime()); |
| | | record.setSignReceiverName(signoutTemp.getSignReceiverName()); |
| | |
| | | */ |
| | | private String generateUniqueKey(ApiVisitorRecordSinginRequest request) { |
| | | StringBuilder sb = new StringBuilder(); |
| | | sb.append(request.getDeviceNo()).append(":"); |
| | | // sb.append(request.getDeviceNo()).append(":"); |
| | | sb.append(request.getIdCard()).append(":"); |
| | | sb.append(request.getRegisterTime() != null ? |
| | | request.getRegisterTime().getTime() : System.currentTimeMillis()); |
| New file |
| | |
| | | package com.ruoyi.web.controller.service; |
| | | |
| | | import cn.hutool.http.HttpRequest; |
| | | import cn.hutool.http.HttpResponse; |
| | | import cn.hutool.http.ContentType; |
| | | import com.fasterxml.jackson.databind.ObjectMapper; |
| | | import com.fasterxml.jackson.databind.JsonNode; |
| | | import com.ruoyi.common.core.domain.AjaxResult; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.system.domain.SysVisitorRecord; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Data; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.time.ZoneId; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.Date; |
| | | import java.util.HashMap; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * 访客登记同步服务 |
| | | * 将本地访客登记数据同步到对端服务器 |
| | | */ |
| | | @Slf4j |
| | | @Service |
| | | public class VisitorRegisterSyncService { |
| | | |
| | | @Value("${visitor.sync.url:}") |
| | | private String syncUrl; |
| | | |
| | | @Value("${visitor.sync.enabled:false}") |
| | | private Boolean syncEnabled; |
| | | |
| | | private final ObjectMapper objectMapper = new ObjectMapper(); |
| | | |
| | | private static final DateTimeFormatter DATE_FORMATTER = |
| | | DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); |
| | | |
| | | /** |
| | | * 同步访客登记信息到对端服务器 |
| | | * |
| | | * @param record 本地访客登记记录 |
| | | * @return 同步结果 |
| | | */ |
| | | public AjaxResult syncVisitorRegister(SysVisitorRecord record) { |
| | | // 检查同步开关 |
| | | if (!syncEnabled) { |
| | | log.info("访客登记同步已关闭,跳过同步"); |
| | | return AjaxResult.success("同步功能未开启"); |
| | | } |
| | | |
| | | if (StringUtils.isBlank(syncUrl)) { |
| | | log.warn("同步URL未配置,跳过同步"); |
| | | return AjaxResult.warn("同步URL未配置"); |
| | | } |
| | | |
| | | try { |
| | | // 1. 构建同步请求参数 |
| | | Map<String, Object> requestBody = buildSyncRequest(record); |
| | | |
| | | String jsonBody = objectMapper.writeValueAsString(requestBody); |
| | | log.info("同步访客登记数据:{}", jsonBody); |
| | | |
| | | // 2. 发送HTTP请求 |
| | | HttpResponse response = HttpRequest.post(syncUrl) |
| | | .header("Content-Type", ContentType.JSON.getValue()) |
| | | .body(jsonBody) |
| | | .timeout(30000) |
| | | .execute(); |
| | | |
| | | // 3. 解析响应 |
| | | String responseBody = response.body(); |
| | | log.info("同步响应:{}", responseBody); |
| | | |
| | | JsonNode jsonNode = objectMapper.readTree(responseBody); |
| | | boolean success = jsonNode.has("success") && jsonNode.get("success").asBoolean(); |
| | | |
| | | if (success) { |
| | | String remoteId = jsonNode.has("data") && jsonNode.get("data").has("id") |
| | | ? jsonNode.get("data").get("id").asText() : null; |
| | | log.info("访客登记同步成功,远程ID:{}", remoteId); |
| | | return AjaxResult.success(remoteId); |
| | | } else { |
| | | String errorMsg = jsonNode.has("description") |
| | | ? jsonNode.get("description").asText() : "未知错误"; |
| | | log.error("访客登记同步失败:{}", errorMsg); |
| | | return AjaxResult.error(errorMsg); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("访客登记同步异常", e); |
| | | return AjaxResult.error("同步异常:" + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 构建同步请求参数 |
| | | */ |
| | | private Map<String, Object> buildSyncRequest(SysVisitorRecord record) { |
| | | Map<String, Object> params = new HashMap<>(); |
| | | |
| | | // 必填字段 |
| | | params.put("name", record.getVisitorName()); |
| | | params.put("certType", "111"); |
| | | params.put("certNo", record.getIdCard()); |
| | | // params.put("qrCode", generateQrCode(record)); |
| | | params.put("person", record.getIntervieweeName()); |
| | | params.put("reason", StringUtils.isNotBlank(record.getVisitReason()) |
| | | ? record.getVisitReason() : "来访"); |
| | | params.put("estimatedTimeStart", formatTime(record.getValidStartTime())); |
| | | params.put("estimatedTimeEnd", formatTime(record.getValidEndTime())); |
| | | |
| | | if (record.getAreasList() != null && !record.getAreasList().isEmpty()) { |
| | | params.put("areas", record.getAreasList()); |
| | | } |
| | | |
| | | // 可选字段 |
| | | // params.put("gender", convertGender(record.getGender())); |
| | | // params.put("phone", record.getPhone()); |
| | | params.put("licensePlateNumber", record.getPlateNo()); |
| | | // params.put("visitorDept", record.getVisitorCompany()); |
| | | params.put("dept", record.getIntervieweeDept()); |
| | | params.put("areaName", record.getVisitAreaNames()); |
| | | params.put("registerGate", record.getRegisterSource()); |
| | | params.put("registerMachineCode", record.getDeviceId()); |
| | | params.put("status", "1"); // 1-已登记 |
| | | |
| | | // 如果有证件照片或现场照片,可以传递 |
| | | // params.put("idPhoto", record.getIdCardPhoto()); |
| | | params.put("pic", record.getFaceImageUrl()); |
| | | |
| | | return params; |
| | | } |
| | | |
| | | /** |
| | | * 转换证件类型:本地字典 → 对方字典值 |
| | | */ |
| | | private String convertCertType(String localCertType) { |
| | | if (StringUtils.isBlank(localCertType)) { |
| | | return "111"; // 默认身份证 |
| | | } |
| | | // 根据实际情况映射 |
| | | switch (localCertType) { |
| | | case "ID_CARD": return "111"; // 居民身份证 |
| | | case "PASSPORT": return "414"; // 护照 |
| | | case "DRIVER_LICENSE": return "335"; // 驾驶证 |
| | | case "HK_MACAU_PASS": return "337"; // 港澳通行证 |
| | | default: return "111"; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 转换性别:本地字典 → 对方字典值 |
| | | */ |
| | | private String convertGender(String localGender) { |
| | | if (StringUtils.isBlank(localGender)) { |
| | | return null; |
| | | } |
| | | switch (localGender) { |
| | | case "MALE": return "1"; |
| | | case "FEMALE": return "2"; |
| | | default: return null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 格式化时间 |
| | | */ |
| | | private String formatTime(Date date) { |
| | | if (date == null) { |
| | | return null; |
| | | } |
| | | LocalDateTime localDateTime = date.toInstant() |
| | | .atZone(ZoneId.systemDefault()) |
| | | .toLocalDateTime(); |
| | | return localDateTime.format(DATE_FORMATTER); |
| | | } |
| | | |
| | | /** |
| | | * 生成二维码内容 |
| | | * 格式:vms:visitor:{日期}:{登记编号} |
| | | */ |
| | | private String generateQrCode(SysVisitorRecord record) { |
| | | String dateStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")); |
| | | String registerNo = record.getRegisterNo(); |
| | | return String.format("vms:visitor:%s:%s", dateStr, registerNo); |
| | | } |
| | | } |
| | |
| | | package com.ruoyi.web.controller.tool; |
| | | |
| | | import com.ruoyi.common.config.RuoYiConfig; |
| | | import com.ruoyi.common.utils.file.FileUtils; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.commons.codec.binary.Base64; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import java.io.ByteArrayOutputStream; |
| | | import java.io.File; |
| | | import java.io.FileInputStream; |
| | | import java.io.InputStream; |
| | | import java.net.HttpURLConnection; |
| | | import java.net.URL; |
| | | import java.nio.file.Files; |
| | | |
| | | @Slf4j |
| | | @Component |
| | |
| | | return null; |
| | | } |
| | | |
| | | String imagePath = RuoYiConfig.getUploadPath() + imageUrl.replace("/profile/upload", ""); |
| | | |
| | | InputStream inputStream = null; |
| | | ByteArrayOutputStream outputStream = null; |
| | | |
| | | try { |
| | | // 1. 创建URL连接 |
| | | URL url = new URL(imageUrl); |
| | | HttpURLConnection connection = (HttpURLConnection) url.openConnection(); |
| | | connection.setRequestMethod("GET"); |
| | | connection.setConnectTimeout(5000); |
| | | connection.setReadTimeout(10000); |
| | | connection.setDoInput(true); |
| | | // 1. 获取本地文件 |
| | | File imageFile = new File(imagePath); |
| | | if (!imageFile.exists()) { |
| | | log.error("图片文件不存在:{}", imagePath); |
| | | return null; |
| | | } |
| | | |
| | | // 2. 获取图片数据 |
| | | inputStream = connection.getInputStream(); |
| | | // 2. 读取图片数据 |
| | | inputStream = Files.newInputStream(imageFile.toPath()); |
| | | outputStream = new ByteArrayOutputStream(); |
| | | |
| | | byte[] buffer = new byte[8192]; |
| | |
| | | byte[] imageBytes = outputStream.toByteArray(); |
| | | String base64 = Base64.encodeBase64String(imageBytes); |
| | | |
| | | // 4. 根据图片类型添加前缀 |
| | | String contentType = connection.getContentType(); |
| | | if (contentType != null && contentType.startsWith("image/")) { |
| | | return "data:" + contentType + ";base64," + base64; |
| | | } |
| | | |
| | | return "data:image/jpeg;base64," + base64; |
| | | // 4. 根据文件扩展名添加前缀 |
| | | String contentType = getContentType(imagePath); |
| | | return "data:" + contentType + ";base64," + base64; |
| | | |
| | | } catch (Exception e) { |
| | | log.error("图片转Base64失败,URL:{}", imageUrl, e); |
| | | log.error("本地图片转Base64失败,路径:{}", imagePath, e); |
| | | return null; |
| | | } finally { |
| | | try { |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 根据文件扩展名获取Content-Type |
| | | */ |
| | | private static String getContentType(String imagePath) { |
| | | if (StringUtils.isEmpty(imagePath)) { |
| | | return "image/jpeg"; |
| | | } |
| | | String ext = imagePath.substring(imagePath.lastIndexOf(".") + 1).toLowerCase(); |
| | | switch (ext) { |
| | | case "png": |
| | | return "image/png"; |
| | | case "gif": |
| | | return "image/gif"; |
| | | case "bmp": |
| | | return "image/bmp"; |
| | | case "webp": |
| | | return "image/webp"; |
| | | case "svg": |
| | | return "image/svg+xml"; |
| | | case "jpg": |
| | | case "jpeg": |
| | | default: |
| | | return "image/jpeg"; |
| | | } |
| | | } |
| | | } |
| | |
| | | # Tomcat启动初始化的线程数,默认值10 |
| | | min-spare: 100 |
| | | |
| | | # 访客登记同步配置 |
| | | visitor: |
| | | sync: |
| | | enabled: false # 是否启用同步 |
| | | url: http://对方服务器IP:端口/event-center/security-protection/visitor-register |
| | | |
| | | # 日志配置 |
| | | logging: |
| | | level: |
| | |
| | | private String remark; |
| | | |
| | | @ApiModelProperty(value = "禁用备注") |
| | | @TableField("disable_remark") |
| | | @TableField(value = "disable_remark" ,exist = false) |
| | | private String disableRemark; |
| | | |
| | | @ApiModelProperty(value = "操作时间") |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | @TableField("operating_time") |
| | | @TableField(value = "operating_time" ,exist = false) |
| | | private LocalDateTime operatingTime; |
| | | |
| | | @ApiModelProperty(value = "操作人 登录名(登录账号)") |
| | | @TableField("operating_person") |
| | | @TableField(value = "operating_person",exist = false) |
| | | private String operatingPerson; |
| | | |
| | | public String getDisableRemark() { |
| | |
| | | |
| | | /** 请求参数 */ |
| | | @JsonInclude(JsonInclude.Include.NON_EMPTY) |
| | | @TableField(exist = false) |
| | | private Map<String, Object> params; |
| | | |
| | | public String getSearchValue() |
| | |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import io.swagger.annotations.Api; |
| | | import lombok.Data; |
| | | |
| | |
| | | * 员工实体(对应 t_employee 表) |
| | | */ |
| | | @Data |
| | | @TableName("t_employee") |
| | | public class Employee { |
| | | |
| | | private Long employeeId; |
| | |
| | | package com.ruoyi.system.domain; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import com.ruoyi.common.core.domain.BaseEntity; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | |
| | | */ |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @TableName("t_visit_area") |
| | | public class SysVisitArea extends BaseEntity |
| | | { |
| | | private static final long serialVersionUID = 1L; |
| | |
| | | |
| | | /** 删除标识,0正常 1删除 */ |
| | | private String delFlag; |
| | | |
| | | /** 百度SDK指纹 */ |
| | | private String fingerprint; |
| | | } |
| | |
| | | package com.ruoyi.system.domain; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.TableField; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import com.ruoyi.common.core.domain.BaseEntity; |
| | | import com.ruoyi.system.domain.vo.AreasList; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * 访客登记记录 t_visitor_record |
| | |
| | | */ |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @TableName("t_visitor_record") |
| | | public class SysVisitorRecord extends BaseEntity |
| | | { |
| | | private static final long serialVersionUID = 1L; |
| | |
| | | |
| | | /** 删除标识:0正常 1删除 */ |
| | | private String delFlag; |
| | | |
| | | @TableField(exist = false) |
| | | private List<AreasList> areasList; |
| | | @TableField(exist = false) |
| | | private String deviceId; |
| | | } |
| | |
| | | package com.ruoyi.system.domain; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.*; |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | |
| | |
| | | * 签离时间 |
| | | */ |
| | | @TableField("sign_time") |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private Date signTime; |
| | | |
| | | /** |
| | |
| | | * 创建时间 |
| | | */ |
| | | @TableField(value = "create_time", fill = FieldFill.INSERT) |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private Date createTime; |
| | | |
| | | /** |
| | | * 更新时间 |
| | | */ |
| | | @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE) |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private Date updateTime; |
| | | |
| | | /** |
| New file |
| | |
| | | package com.ruoyi.system.domain.api; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.TableField; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import com.ruoyi.common.core.domain.BaseEntity; |
| | | import com.ruoyi.system.domain.vo.AreasList; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * 访客登记记录 t_visitor_record |
| | | * |
| | | * @author ruoyi |
| | | */ |
| | | @Data |
| | | @TableName("t_visitor_record") |
| | | public class ApiSysVisitorRecord |
| | | { |
| | | private static final long serialVersionUID = 1L; |
| | | |
| | | /** 访客登记记录主键 */ |
| | | private Long recordId; |
| | | |
| | | /** 登记编号 */ |
| | | private String registerNo; |
| | | |
| | | /** 访客姓名 */ |
| | | private String visitorName; |
| | | |
| | | /** 访客身份证号 */ |
| | | private String idCard; |
| | | |
| | | /** 登记类型:APPOINTMENT预约登记 ONSITE现场登记 */ |
| | | private String registerType; |
| | | |
| | | /** 被访人 */ |
| | | private String intervieweeName; |
| | | |
| | | /** 被访人部门 */ |
| | | private String intervieweeDept; |
| | | |
| | | /** 登记接待人 */ |
| | | private String receiverName; |
| | | |
| | | /** 接待人验证方式 */ |
| | | private String receiverVerifyMethod; |
| | | |
| | | /** 访问区域ID,英文逗号分隔 */ |
| | | private String visitAreaIds; |
| | | |
| | | /** 访问区域名称,英文逗号分隔 */ |
| | | private String visitAreaNames; |
| | | |
| | | /** 车牌号 */ |
| | | private String plateNo; |
| | | |
| | | /** 来访事由 */ |
| | | private String visitReason; |
| | | |
| | | /** 有效期开始时间 */ |
| | | private Date validStartTime; |
| | | |
| | | /** 有效期结束时间 */ |
| | | private Date validEndTime; |
| | | |
| | | /** 登记状态 */ |
| | | private String status; |
| | | |
| | | /** 登记时间 */ |
| | | private Date registerTime; |
| | | |
| | | /** 登记来源 */ |
| | | private String registerSource; |
| | | |
| | | /** 访客人像地址 */ |
| | | private String faceImageUrl; |
| | | |
| | | /** 签离时间 */ |
| | | private Date signTime; |
| | | |
| | | /** 签离接待人 */ |
| | | private String signReceiverName; |
| | | |
| | | /** 签离验证方式 */ |
| | | private String signVerifyMethod; |
| | | |
| | | /** 签离人像地址 */ |
| | | private String signImageUrl; |
| | | |
| | | /** 删除标识:0正常 1删除 */ |
| | | private String delFlag; |
| | | |
| | | @TableField(exist = false) |
| | | private List<AreasList> areasList; |
| | | @TableField(exist = false) |
| | | private String deviceId; |
| | | private String remark; |
| | | private Date createTime; |
| | | } |
| New file |
| | |
| | | package com.ruoyi.system.domain.vo; |
| | | |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | public class AreasList { |
| | | private String areaCode; |
| | | private String areaName; |
| | | } |
| | |
| | | @ApiModelProperty("设备ID") |
| | | @NotNull(message = "未找到该设备") |
| | | private String machineId; |
| | | |
| | | @ApiModelProperty("百度指纹") |
| | | private String fingerprint; |
| | | } |
| | |
| | | package com.ruoyi.system.dto; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Data; |
| | |
| | | @ApiModel(value = "ApiVisitorRecodeSingoutRequest", description = "离线上传的访客身份证签离") |
| | | public class ApiVisitorRecodeSingoutRequest |
| | | { |
| | | @NotBlank(message = "设备编号不能为空") |
| | | @ApiModelProperty(value = "设备编号") |
| | | // @NotBlank(message = "设备编号不能为空") |
| | | @ApiModelProperty(value = "设备编号,非必填") |
| | | private String deviceNo; |
| | | |
| | | /** |
| | |
| | | |
| | | /** 签离时间 */ |
| | | @ApiModelProperty(value = "签离时间") |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private Date signTime; |
| | | |
| | | // @NotBlank(message = "登记类型不能为空") |
| | | // @ApiModelProperty(value = "登记类型:APPOINTMENT预约登记 ONSITE现场登记") |
| | | // private String registerType; |
| | | |
| | | /** 签离接待人 */ |
| | | @ApiModelProperty(value = "签离接待人") |
| | | private String signReceiverName; |
| | |
| | | @ApiModel(value = "ApiVisitorRecordSinginRequest", description = "离线模式签到数据") |
| | | public class ApiVisitorRecordSinginRequest |
| | | { |
| | | @NotBlank(message = "设备编号不能为空") |
| | | @ApiModelProperty(value = "设备编号") |
| | | // @NotBlank(message = "设备编号不能为空") |
| | | @ApiModelProperty(value = "设备编号,非必填") |
| | | private String deviceNo; |
| | | |
| | | /** |
| | |
| | | @ApiModelProperty(value = "接待人验证方式") |
| | | private String receiverVerifyMethod; |
| | | |
| | | @NotBlank(message = "登记类型不能为空") |
| | | @ApiModelProperty(value = "登记类型:APPOINTMENT预约登记 ONSITE现场登记") |
| | | private String registerType; |
| | | |
| | | /** |
| | | * 访问区域ID,英文逗号分隔 |
| | | */ |
| | |
| | | * 有效期开始时间 |
| | | */ |
| | | @ApiModelProperty(value = "有效期开始时间") |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
| | | @JsonFormat(pattern = "yyyyMMddHHmmss") |
| | | private Date validStartTime; |
| | | |
| | | /** |
| | | * 有效期结束时间 |
| | | */ |
| | | @ApiModelProperty(value = "有效期结束时间") |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
| | | @JsonFormat(pattern = "yyyyMMddHHmmss") |
| | | private Date validEndTime; |
| | | |
| | | /** |
| | | * 登记时间 |
| | | */ |
| | | @ApiModelProperty(value = "登记时间") |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
| | | @JsonFormat(pattern = "yyyyMMddHHmmss") |
| | | private Date registerTime; |
| | | |
| | | /** |
| | |
| | | |
| | | @ApiModelProperty("人像图片") |
| | | private String faceImage; |
| | | |
| | | @ApiModelProperty("手机号") |
| | | private String phone; |
| | | |
| | | // 手机号加密的 |
| | | private String contactPhoneCipher; |
| | | } |
| | |
| | | @ApiModelProperty(value = "访客身份证号") |
| | | private String idCard; |
| | | |
| | | /** |
| | | * 被访人 |
| | | */ |
| | | @NotBlank(message = "被访人不能为空") |
| | | @ApiModelProperty(value = "被访人") |
| | | private String intervieweeName; |
| | | |
| | | /** |
| | | * 接待人验证方式 |
| | | */ |
| | | @ApiModelProperty(value = "接待人验证方式") |
| | | private String receiverVerifyMethod; |
| | | // /** |
| | | // * 被访人 |
| | | // */ |
| | | // @NotBlank(message = "被访人不能为空") |
| | | // @ApiModelProperty(value = "被访人") |
| | | // private String intervieweeName; |
| | | // |
| | | // /** |
| | | // * 接待人验证方式 |
| | | // */ |
| | | // @ApiModelProperty(value = "接待人验证方式") |
| | | // private String receiverVerifyMethod; |
| | | |
| | | /** 签离接待人 */ |
| | | @ApiModelProperty(value = "签离接待人") |
| | |
| | | public class SysVisitorRecordAddRequest |
| | | { |
| | | /** |
| | | * 设备编号 |
| | | */ |
| | | @ApiModelProperty(value = "设备编号,非必传") |
| | | private String deviceId; |
| | | |
| | | /** |
| | | * 访客姓名 |
| | | */ |
| | | @NotBlank(message = "访客姓名不能为空") |
| | | @ApiModelProperty(value = "访客姓名") |
| | | private String visitorName; |
| | | |
| | | @NotBlank(message = "登记类型不能为空") |
| | | @ApiModelProperty(value = "登记类型:APPOINTMENT预约登记 ONSITE现场登记") |
| | | private String registerType; |
| | | |
| | | /** |
| | | * 访客身份证号 |
| | |
| | | @ApiModelProperty(value = "被访人") |
| | | private String intervieweeName; |
| | | |
| | | @ApiModelProperty(value = "被访人电话") |
| | | private String beVisitorPhone; |
| | | |
| | | /** |
| | | * 接待人验证方式 |
| | | */ |
| | | @ApiModelProperty(value = "接待人验证方式") |
| | | private String receiverVerifyMethod; |
| | | |
| | | @ApiModelProperty(value = "登记接待人") |
| | | private String receiverName; |
| | | |
| | | /** |
| | | * 访问区域ID,英文逗号分隔 |
| | | */ |
| | | @ApiModelProperty(value = "访问区域ID,英文逗号分隔") |
| | | private String visitAreaIds; |
| | | |
| | | @ApiModelProperty(value = "访问区域名称,英文逗号分隔") |
| | | private String visitAreaNames; |
| | | |
| | | /** |
| | | * 车牌号 |
| | | */ |
| | |
| | | package com.ruoyi.system.mapper; |
| | | |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.ruoyi.system.domain.Employee; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | import org.apache.ibatis.annotations.Param; |
| | |
| | | import java.util.List; |
| | | |
| | | @Mapper |
| | | public interface EmployeeMapper { |
| | | public interface EmployeeMapper extends BaseMapper<Employee> { |
| | | |
| | | /** |
| | | * 按主键查询启用且未删除的接待人。 |
| | |
| | | /** |
| | | * 根据员工姓名精准查询启用的员工 |
| | | */ |
| | | Employee selectEnabledByEmployeeName(@Param("employeeName") String employeeName); |
| | | List<Employee> selectEnabledByEmployeeName(@Param("employeeName") String employeeName); |
| | | |
| | | List<Employee> selectAllEnabled(); |
| | | } |
| | |
| | | public List<SysVersionUpdateVO> pageList(@Param("query") SysVersionUpdatePageRequest query, |
| | | @Param("pageInfo") PageInfo<SysVersionUpdateVO> pageInfo); |
| | | |
| | | public SysVersionUpdateVO selectLatest(); |
| | | |
| | | public SysVersionUpdateVO selectById(Long updateId); |
| | | |
| | | public int insertVersionUpdate(SysVersionUpdate versionUpdate); |
| | |
| | | public int updateChildrenAncestors(@Param("areaId") Long areaId, @Param("oldAncestors") String oldAncestors, |
| | | @Param("newAncestors") String newAncestors); |
| | | |
| | | public int countChildrenByParentIds(@Param("parentIds") List<Long> parentIds); |
| | | |
| | | public int deleteByIds(@Param("areaIds") List<Long> areaIds, @Param("updateBy") String updateBy); |
| | | } |
| | |
| | | @Mapper |
| | | public interface SysVisitorRecordMapper extends BaseMapper<SysVisitorRecord> |
| | | { |
| | | // 显式覆盖 updateById 方法 |
| | | @Override |
| | | int updateById(@Param("et") SysVisitorRecord entity); |
| | | |
| | | public List<SysVisitorRecordVO> pageList(@Param("query") SysVisitorRecordPageRequest query, |
| | | @Param("pageInfo") PageInfo<SysVisitorRecordVO> pageInfo); |
| | | |
| | | public List<SysVisitorRecordVO> list(@Param("query") SysVisitorRecordPageRequest query); |
| | | |
| | | public SysVisitorRecordDetailVO selectDetailById(Long recordId); |
| | | |
| | | public List<SysVisitorPassRecordVO> selectPassRecordsByRecordId(Long recordId); |
| New file |
| | |
| | | package com.ruoyi.system.mapper.api; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.ruoyi.common.basic.PageInfo; |
| | | import com.ruoyi.system.domain.SysVisitorRecord; |
| | | import com.ruoyi.system.domain.api.ApiSysVisitorRecord; |
| | | import com.ruoyi.system.dto.SysVisitorRecordPageRequest; |
| | | import com.ruoyi.system.vo.SysVisitorPassRecordVO; |
| | | import com.ruoyi.system.vo.SysVisitorRecordDetailVO; |
| | | import com.ruoyi.system.vo.SysVisitorRecordVO; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | import org.apache.ibatis.annotations.Param; |
| | | import org.springframework.stereotype.Repository; |
| | | |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * 访客登记记录数据层 |
| | | * |
| | | * @author ruoyi |
| | | */ |
| | | @Repository |
| | | public interface SysApiVisitorRecordMapper extends BaseMapper<ApiSysVisitorRecord> |
| | | { |
| | | |
| | | } |
| | |
| | | { |
| | | public PageInfo<SysVersionUpdateVO> pageList(SysVersionUpdatePageRequest request); |
| | | |
| | | public SysVersionUpdateVO latest(); |
| | | |
| | | public SysVersionUpdateVO detail(SysVersionUpdateDetailRequest request); |
| | | |
| | | public int add(SysVersionUpdateAddRequest request, String username); |
| | |
| | | * @param deviceId 设备ID |
| | | * @return 影响行数 |
| | | */ |
| | | int activateDevice(String deviceId); |
| | | int activateDevice(String deviceId, String fingerprint); |
| | | |
| | | /** |
| | | * 注销设备 |
| | |
| | | import com.ruoyi.system.vo.SysVisitorRecordDetailVO; |
| | | import com.ruoyi.system.vo.SysVisitorRecordVO; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * 访客登记记录服务 |
| | | * |
| | |
| | | { |
| | | public PageInfo<SysVisitorRecordVO> pageList(SysVisitorRecordPageRequest request); |
| | | |
| | | public List<SysVisitorRecordVO> list(SysVisitorRecordPageRequest request); |
| | | |
| | | public SysVisitorRecordDetailVO detail(SysVisitorRecordDetailRequest request); |
| | | |
| | | public int refreshVisitorStatus(); |
| | |
| | | } |
| | | |
| | | @Override |
| | | public SysVersionUpdateVO latest() |
| | | { |
| | | return versionUpdateMapper.selectLatest(); |
| | | } |
| | | |
| | | @Override |
| | | public SysVersionUpdateVO detail(SysVersionUpdateDetailRequest request) |
| | | { |
| | | if (request == null || request.getUpdateId() == null) |
| | |
| | | { |
| | | getRequiredAreaId(areaId); |
| | | } |
| | | if (visitAreaMapper.countChildrenByParentIds(request.getAreaIds()) > 0) |
| | | { |
| | | throw new ServiceException("存在下级区域"); |
| | | } |
| | | return visitAreaMapper.deleteByIds(request.getAreaIds(), username); |
| | | } |
| | | |
| | |
| | | labels.put("plateNo", "车牌号"); |
| | | labels.put("visitReason", "来访事由"); |
| | | labels.put("visitArea", "访问区域"); |
| | | labels.put("receiverName", "接待人"); |
| | | labels.put("registerTime", "登记时间"); |
| | | PRINT_FIELD_LABELS = Collections.unmodifiableMap(labels); |
| | | } |
| | | |
| | |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public int activateDevice(String deviceId) { |
| | | public int activateDevice(String deviceId, String fingerprint) { |
| | | // 1. 根据deviceId查询设备 |
| | | SysVisitorMachine device = visitorMachineMapper.checkDeviceIdUnique(deviceId, null); |
| | | if (device == null || device.getDelFlag().equals("1")) { |
| | |
| | | update.setMachineId(device.getMachineId()); |
| | | update.setOnlineStatus("ONLINE"); // 或 "ACTIVATED",根据你的枚举定义 |
| | | update.setUpdateTime(new Date()); |
| | | |
| | | update.setFingerprint(fingerprint); |
| | | return visitorMachineMapper.updateMachine(update); |
| | | } |
| | | |
| | |
| | | import com.ruoyi.system.dto.SysVisitorRecordDeleteRequest; |
| | | import com.ruoyi.system.dto.SysVisitorRecordDetailRequest; |
| | | import com.ruoyi.system.dto.SysVisitorRecordPageRequest; |
| | | import com.ruoyi.system.domain.SysVisitArea; |
| | | import com.ruoyi.system.mapper.SysVisitAreaMapper; |
| | | import com.ruoyi.system.mapper.SysVisitorRecordMapper; |
| | | import com.ruoyi.system.service.ISysVisitorRecordService; |
| | | import com.ruoyi.system.vo.SysVisitorRecordDetailVO; |
| | |
| | | @Autowired |
| | | private SysVisitorRecordMapper visitorRecordMapper; |
| | | |
| | | @Autowired |
| | | private SysVisitAreaMapper visitAreaMapper; |
| | | |
| | | @Override |
| | | public PageInfo<SysVisitorRecordVO> pageList(SysVisitorRecordPageRequest request) |
| | | { |
| | | refreshVisitorStatus(); |
| | | SysVisitorRecordPageRequest query = request == null ? new SysVisitorRecordPageRequest() : request; |
| | | query.setIntervieweeName(StringUtils.trim(query.getIntervieweeName())); |
| | | query.setIntervieweeKeyword(StringUtils.trim(query.getIntervieweeKeyword())); |
| | | query.setIntervieweeDept(StringUtils.trim(query.getIntervieweeDept())); |
| | | query.setVisitAreaKeyword(StringUtils.trim(query.getVisitAreaKeyword())); |
| | | query.setStatus(StringUtils.trim(query.getStatus())); |
| | | query.setVisitorName(StringUtils.trim(query.getVisitorName())); |
| | | query.setVisitorKeyword(StringUtils.trim(query.getVisitorKeyword())); |
| | | query.setVisitReason(StringUtils.trim(query.getVisitReason())); |
| | | query.setRegisterType(StringUtils.trim(query.getRegisterType())); |
| | | SysVisitorRecordPageRequest query = normalizeQuery(request); |
| | | PageInfo<SysVisitorRecordVO> pageInfo = new PageInfo<SysVisitorRecordVO>(query.getPageNum(), query.getPageSize()); |
| | | List<SysVisitorRecordVO> list = visitorRecordMapper.pageList(query, pageInfo); |
| | | pageInfo.setRecords(list); |
| | | return pageInfo; |
| | | } |
| | | |
| | | @Override |
| | | public List<SysVisitorRecordVO> list(SysVisitorRecordPageRequest request) |
| | | { |
| | | refreshVisitorStatus(); |
| | | return visitorRecordMapper.list(normalizeQuery(request)); |
| | | } |
| | | |
| | | @Override |
| | |
| | | |
| | | private void fillDetailDisplayFields(SysVisitorRecordDetailVO detail) |
| | | { |
| | | List<String> visitAreaNames = resolveVisitAreaNames(detail); |
| | | detail.setIdCardMask(maskIdCard(detail.getIdCard())); |
| | | detail.setRegisterTypeName(registerTypeName(detail.getRegisterType())); |
| | | detail.setStatusName(statusName(detail.getStatus())); |
| | | detail.setReceiverVerifyMethodName(verifyMethodName(detail.getReceiverVerifyMethod())); |
| | | detail.setSignVerifyMethodName(verifyMethodName(detail.getSignVerifyMethod())); |
| | | detail.setVisitAreaNameList(splitNames(detail.getVisitAreaNames())); |
| | | detail.setVisitAreaNameList(visitAreaNames); |
| | | detail.setVisitAreaNames(String.join(",", visitAreaNames)); |
| | | detail.setValidTimeText(validTimeText(detail.getValidStartTime(), detail.getValidEndTime())); |
| | | detail.setRegisterCollectTime(detail.getRegisterTime()); |
| | | detail.setSignCollectTime(detail.getSignTime()); |
| | | } |
| | | |
| | | private List<String> resolveVisitAreaNames(SysVisitorRecordDetailVO detail) |
| | | { |
| | | List<String> names = splitNames(detail.getVisitAreaNames()); |
| | | if (!names.isEmpty() || StringUtils.isBlank(detail.getVisitAreaIds())) |
| | | { |
| | | return names; |
| | | } |
| | | for (String areaIdText : detail.getVisitAreaIds().split(",")) |
| | | { |
| | | try |
| | | { |
| | | SysVisitArea area = visitAreaMapper.selectById(Long.valueOf(StringUtils.trim(areaIdText))); |
| | | if (area != null && StringUtils.isNotBlank(area.getAreaName())) |
| | | { |
| | | names.add(area.getAreaName()); |
| | | } |
| | | } |
| | | catch (NumberFormatException ignored) |
| | | { |
| | | continue; |
| | | } |
| | | } |
| | | return names; |
| | | } |
| | | |
| | | private String maskIdCard(String idCard) |
| | |
| | | } |
| | | return recordId; |
| | | } |
| | | |
| | | private SysVisitorRecordPageRequest normalizeQuery(SysVisitorRecordPageRequest request) |
| | | { |
| | | SysVisitorRecordPageRequest query = request == null ? new SysVisitorRecordPageRequest() : request; |
| | | query.setIntervieweeName(StringUtils.trim(query.getIntervieweeName())); |
| | | query.setIntervieweeKeyword(StringUtils.trim(query.getIntervieweeKeyword())); |
| | | query.setIntervieweeDept(StringUtils.trim(query.getIntervieweeDept())); |
| | | query.setVisitAreaKeyword(StringUtils.trim(query.getVisitAreaKeyword())); |
| | | query.setStatus(StringUtils.trim(query.getStatus())); |
| | | query.setVisitorName(StringUtils.trim(query.getVisitorName())); |
| | | query.setVisitorKeyword(StringUtils.trim(query.getVisitorKeyword())); |
| | | query.setVisitReason(StringUtils.trim(query.getVisitReason())); |
| | | query.setRegisterType(StringUtils.trim(query.getRegisterType())); |
| | | return query; |
| | | } |
| | | } |
| | |
| | | |
| | | private String deviceId; |
| | | |
| | | private String fingerprint; |
| | | |
| | | private Boolean enabled; |
| | | |
| | | private String onlineStatus; |
| | |
| | | |
| | | <!-- 人脸图片不参与本接口逻辑,且 longtext 体积较大,列表中排除,减少不必要的数据传输 --> |
| | | <sql id="Base_Column_List"> |
| | | employee_id, dept_id, employee_name, employee_no, contact_phone_cipher, |
| | | employee_id, dept_id, employee_name, face_image, employee_no, contact_phone_cipher, |
| | | contact_phone_hash, `position`, card_no, enabled, del_flag, |
| | | create_by, create_time, update_by, update_time |
| | | </sql> |
| | |
| | | SELECT |
| | | <include refid="Base_Column_List"/> |
| | | FROM t_employee |
| | | AND enabled = 1 |
| | | where enabled = 1 |
| | | AND del_flag = '0' |
| | | </select> |
| | | |
| | |
| | | and del_flag = '0' |
| | | </select> |
| | | |
| | | <select id="selectLatest" resultMap="SysVersionUpdateVOResult"> |
| | | <include refid="selectVersionUpdateVo" /> |
| | | where del_flag = '0' |
| | | order by create_time desc, update_id desc |
| | | limit 1 |
| | | </select> |
| | | |
| | | <insert id="insertVersionUpdate" parameterType="SysVersionUpdate" useGeneratedKeys="true" keyProperty="updateId"> |
| | | insert into t_version_update ( |
| | | version_no, |
| | |
| | | and find_in_set(#{areaId}, ancestors) |
| | | </update> |
| | | |
| | | <select id="countChildrenByParentIds" resultType="int"> |
| | | select count(1) |
| | | from t_visit_area |
| | | where del_flag = '0' |
| | | and parent_id in |
| | | <foreach collection="parentIds" item="parentId" open="(" separator="," close=")"> |
| | | #{parentId} |
| | | </foreach> |
| | | </select> |
| | | |
| | | <update id="deleteByIds"> |
| | | update t_visit_area |
| | | set del_flag = '1', |
| | |
| | | <result property="deviceId" column="device_id" /> |
| | | <result property="enabled" column="enabled" /> |
| | | <result property="onlineStatus" column="online_status" /> |
| | | <result property="fingerprint" column="fingerprint" /> |
| | | <result property="delFlag" column="del_flag" /> |
| | | <result property="remark" column="remark" /> |
| | | <result property="createBy" column="create_by" /> |
| | |
| | | <result property="deviceName" column="device_name" /> |
| | | <result property="brand" column="brand" /> |
| | | <result property="deviceId" column="device_id" /> |
| | | <result property="fingerprint" column="fingerprint" /> |
| | | <result property="enabled" column="enabled" /> |
| | | <result property="onlineStatus" column="online_status" /> |
| | | <result property="remark" column="remark" /> |
| | |
| | | device_id, |
| | | enabled, |
| | | online_status, |
| | | fingerprint, |
| | | del_flag, |
| | | remark, |
| | | create_by, |
| | |
| | | m.device_name, |
| | | m.brand, |
| | | m.device_id, |
| | | m.fingerprint, |
| | | m.enabled, |
| | | m.online_status, |
| | | m.remark, |
| | |
| | | |
| | | <update id="updateMachine" parameterType="SysVisitorMachine"> |
| | | update t_visitor_machine |
| | | set area_id = #{areaId}, |
| | | device_name = #{deviceName}, |
| | | brand = #{brand}, |
| | | device_id = #{deviceId}, |
| | | enabled = #{enabled}, |
| | | remark = #{remark}, |
| | | <set> |
| | | <if test="areaId != null and areaId != ''"> |
| | | area_id = #{areaId}, |
| | | </if> |
| | | <if test="deviceName != null and deviceName != ''"> |
| | | device_name = #{deviceName}, |
| | | </if> |
| | | <if test="brand != null and brand != ''"> |
| | | brand = #{brand}, |
| | | </if> |
| | | <if test="deviceId != null and deviceId != ''"> |
| | | device_id = #{deviceId}, |
| | | </if> |
| | | <if test="enabled != null"> |
| | | enabled = #{enabled}, <!-- 注意:enabled 可能是 Boolean 或 Integer,若允许 false 或 0,则只判 null --> |
| | | </if> |
| | | <if test="onlineStatus != null and onlineStatus != ''"> |
| | | online_status = #{onlineStatus}, |
| | | </if> |
| | | <if test="fingerprint != null and fingerprint != ''"> |
| | | fingerprint = #{fingerprint}, |
| | | </if> |
| | | <if test="remark != null and remark != ''"> |
| | | remark = #{remark}, |
| | | </if> |
| | | <!-- 以下两个字段始终更新,不加 if --> |
| | | update_by = #{updateBy}, |
| | | update_time = sysdate() |
| | | </set> |
| | | where machine_id = #{machineId} |
| | | and del_flag = '0' |
| | | </update> |
| | |
| | | from t_visitor_record |
| | | </sql> |
| | | |
| | | <select id="pageList" resultMap="SysVisitorRecordVOResult"> |
| | | <sql id="selectVisitorRecordList"> |
| | | <include refid="selectVisitorRecordBase" /> |
| | | <where> |
| | | and del_flag = '0' |
| | |
| | | ) |
| | | </if> |
| | | <if test="query.visitReason != null and query.visitReason != ''"> |
| | | and visit_reason = #{query.visitReason} |
| | | and visit_reason like concat('%', #{query.visitReason}, '%') |
| | | </if> |
| | | <if test="query.registerType != null and query.registerType != ''"> |
| | | and register_type = #{query.registerType} |
| | | </if> |
| | | </where> |
| | | order by register_time desc, record_id desc |
| | | </sql> |
| | | |
| | | <select id="pageList" resultMap="SysVisitorRecordVOResult"> |
| | | <include refid="selectVisitorRecordList" /> |
| | | </select> |
| | | |
| | | <select id="list" resultMap="SysVisitorRecordVOResult"> |
| | | <include refid="selectVisitorRecordList" /> |
| | | </select> |
| | | |
| | | <select id="selectDetailById" parameterType="Long" resultMap="SysVisitorRecordDetailVOResult"> |
| | |
| | | # 页面标题 |
| | | VUE_APP_TITLE = 运营活动管理平台 |
| | | VUE_APP_TITLE = 访客机自助登记管理平台 |
| | | |
| | | # 开发环境配置 |
| | | ENV = 'development' |
| | |
| | | |
| | | # 访客后台前端部署模式:standalone 单机版需要输入IP,cluster 集群版不输入IP |
| | | VUE_APP_VISITOR_FRONTEND_MODE = 'cluster' |
| | | VUE_APP_VISITOR_MIDDLE_PLATFORM_ENABLED = 'true' |
| | | VUE_APP_VISITOR_MIDDLE_PLATFORM_ENABLED = 'false' |
| | | # VUE_APP_VISITOR_LOGIN_MIDDLE_PLATFORM_ENABLED 未配置时默认跟随 VUE_APP_VISITOR_MIDDLE_PLATFORM_ENABLED |
| | | |
| | | # 开发环境通过 /dev-api 代理到本地 9098 |
| | |
| | | # 页面标题 |
| | | VUE_APP_TITLE = 运营活动管理平台 |
| | | VUE_APP_TITLE = 访客机自助登记管理平台 |
| | | |
| | | # 生产环境配置 |
| | | ENV = 'production' |
| | |
| | | } |
| | | |
| | | export function getVisitorRecord(recordId) { |
| | | return request({ url: '/system/visitor/record/detail', method: 'post', data: { recordId } }) |
| | | return request({ url: '/system/visitor/record/detail', method: 'post', data: { recordId }}) |
| | | } |
| | | |
| | | export function delVisitorRecord(recordIds) { |
| | | return request({ url: '/system/visitor/record/delete', method: 'post', data: { recordIds } }) |
| | | return request({ url: '/system/visitor/record/delete', method: 'post', data: { recordIds }}) |
| | | } |
| | | |
| | | export function exportVisitorRecord(data) { |
| | | return request({ url: '/system/visitor/record/export', method: 'post', data }) |
| | | } |
| | | |
| | | export function listOperationLog(data) { |
| | |
| | | export function exportOperationLog(data) { |
| | | return request({ url: '/system/operation/log/export', method: 'post', data }) |
| | | } |
| | | |
| | |
| | | min-width: 0 !important; |
| | | height: 36px !important; |
| | | line-height: 36px !important; |
| | | margin: 0; |
| | | margin: 0 0 4px; |
| | | padding: 0 0 0 48px !important; |
| | | border-radius: 0; |
| | | border-radius: 13px; |
| | | color: rgba(255, 255, 255, 0.92) !important; |
| | | background: transparent !important; |
| | | font-size: 12px; |
| | |
| | | background-color: rgba(255, 255, 255, 0.14) !important; |
| | | } |
| | | |
| | | .theme-dark .el-submenu .el-menu-item:hover, |
| | | .theme-dark .el-submenu .el-menu-item.is-active, |
| | | .el-submenu .el-menu-item:hover, |
| | | .el-submenu .el-menu-item.is-active { |
| | | color: #fff !important; |
| | | background: transparent !important; |
| | | background-color: transparent !important; |
| | | background: rgba(255, 255, 255, 0.14) !important; |
| | | background-color: rgba(255, 255, 255, 0.14) !important; |
| | | } |
| | | |
| | | > .el-scrollbar > .el-scrollbar__wrap > .el-scrollbar__view > .el-menu > .el-menu-item.is-active, |
| | |
| | | text-overflow: ellipsis; |
| | | white-space: nowrap; |
| | | vertical-align: middle; |
| | | } |
| | | |
| | | .content-preview { |
| | | position: absolute; |
| | | left: -16px; |
| | | top: 38px; |
| | | z-index: 10; |
| | | width: 204px; |
| | | min-height: 102px; |
| | | display: none; |
| | | padding: 0 10px 12px; |
| | | border: 1px solid #f0bc2d; |
| | | border-radius: 2px; |
| | | background: #fff; |
| | | box-shadow: 0 4px 12px rgba(15, 35, 70, 0.12); |
| | | } |
| | | |
| | | .version-content-cell:hover .content-preview { |
| | | display: block; |
| | | } |
| | | |
| | | .preview-index { |
| | | height: 16px; |
| | | margin: 0 -10px 12px; |
| | | padding-left: 9px; |
| | | color: #111827; |
| | | background: #ffd753; |
| | | font-weight: 700; |
| | | line-height: 16px; |
| | | } |
| | | |
| | | .preview-content { |
| | | min-height: 42px; |
| | | color: #111827; |
| | | font-weight: 700; |
| | | line-height: 18px; |
| | | word-break: break-word; |
| | | } |
| | | |
| | | .preview-foot { |
| | | margin-top: 26px; |
| | | color: #8a9ab0; |
| | | font-size: 11px; |
| | | } |
| | | |
| | | .table-actions { |
| | |
| | | isShowTip: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 是否在上传前压缩图片 |
| | | compress: { |
| | | type: Boolean, |
| | | default: false |
| | | } |
| | | }, |
| | | data() { |
| | |
| | | this.$modal.msgError(`文件格式不正确, 请上传${this.fileType.join("/")}图片格式文件!`); |
| | | return false; |
| | | } |
| | | if (this.fileSize) { |
| | | const isLt = file.size / 1024 / 1024 < this.fileSize; |
| | | if (!isLt) { |
| | | this.$modal.msgError(`上传头像图片大小不能超过 ${this.fileSize} MB!`); |
| | | return false; |
| | | const compressedFile = this.compress ? this.compressImage(file) : Promise.resolve(file); |
| | | return compressedFile.then(uploadFile => { |
| | | if (this.fileSize) { |
| | | const isLt = uploadFile.size / 1024 / 1024 < this.fileSize; |
| | | if (!isLt) { |
| | | throw new Error("FILE_SIZE_EXCEEDED"); |
| | | } |
| | | } |
| | | } |
| | | this.$modal.loading("正在上传图片,请稍候..."); |
| | | this.number++; |
| | | this.$modal.loading("正在上传图片,请稍候..."); |
| | | this.number++; |
| | | return uploadFile; |
| | | }).catch(error => { |
| | | const message = error && error.message === "FILE_SIZE_EXCEEDED" |
| | | ? `上传头像图片大小不能超过 ${this.fileSize} MB!` |
| | | : "图片压缩失败,请重试"; |
| | | this.$modal.msgError(message); |
| | | return Promise.reject(error); |
| | | }); |
| | | }, |
| | | // 将人脸图片限制在合理尺寸并转为 JPEG,减少上传和设备同步数据量 |
| | | compressImage(file) { |
| | | return new Promise((resolve, reject) => { |
| | | const reader = new FileReader(); |
| | | reader.onload = event => { |
| | | const image = new Image(); |
| | | image.onload = () => { |
| | | const maxWidth = 1280; |
| | | const maxHeight = 1280; |
| | | const scale = Math.min(1, maxWidth / image.width, maxHeight / image.height); |
| | | const resized = scale < 1; |
| | | const canvas = document.createElement("canvas"); |
| | | canvas.width = Math.max(1, Math.round(image.width * scale)); |
| | | canvas.height = Math.max(1, Math.round(image.height * scale)); |
| | | const context = canvas.getContext("2d"); |
| | | if (!context) { |
| | | resolve(file); |
| | | return; |
| | | } |
| | | context.drawImage(image, 0, 0, canvas.width, canvas.height); |
| | | canvas.toBlob(blob => { |
| | | if (!blob || (!resized && blob.size >= file.size)) { |
| | | resolve(file); |
| | | return; |
| | | } |
| | | const fileName = file.name.replace(/\.[^.]+$/, "") + ".jpg"; |
| | | resolve(new File([blob], fileName, { |
| | | type: "image/jpeg", |
| | | lastModified: Date.now() |
| | | })); |
| | | }, "image/jpeg", 0.82); |
| | | }; |
| | | image.onerror = reject; |
| | | image.src = event.target.result; |
| | | }; |
| | | reader.onerror = reject; |
| | | reader.readAsDataURL(file); |
| | | }); |
| | | }, |
| | | // 文件个数超出 |
| | | handleExceed() { |
| | |
| | | }, |
| | | passwordRules: { |
| | | oldPassword: [{ required: true, message: '请输入原密码', trigger: 'blur' }], |
| | | newPassword: [{ validator: strongPassword, trigger: 'blur' }], |
| | | newPassword: [{ required: true, validator: strongPassword, trigger: 'blur' }], |
| | | confirmPassword: [{ required: true, message: '请确认新密码', trigger: 'blur' }, { validator: confirmPassword, trigger: 'blur' }] |
| | | } |
| | | } |
| | |
| | | import { getToken } from '@/utils/auth' |
| | | import errorCode from '@/utils/errorCode' |
| | | import { tansParams, blobValidate } from "@/utils/ruoyi"; |
| | | import cache from '@/plugins/cache' |
| | | import { saveAs } from 'file-saver' |
| | | import visitorAdminConfig from '@/config/visitor-admin' |
| | | |
| | |
| | | config.baseURL = getVisitorApiBaseUrl() |
| | | // 是否需要设置 token |
| | | const isToken = (config.headers || {}).isToken === false |
| | | // 是否需要防止数据重复提交 |
| | | const isRepeatSubmit = (config.headers || {}).repeatSubmit === false |
| | | if (getToken() && !isToken) { |
| | | config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 |
| | | } |
| | |
| | | url = url.slice(0, -1); |
| | | config.params = {}; |
| | | config.url = url; |
| | | } |
| | | if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) { |
| | | const requestObj = { |
| | | url: config.url, |
| | | data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data, |
| | | time: new Date().getTime() |
| | | } |
| | | const requestSize = Object.keys(JSON.stringify(requestObj)).length; // 请求数据大小 |
| | | const limitSize = 5 * 1024 * 1024; // 限制存放数据5M |
| | | if (requestSize >= limitSize) { |
| | | console.warn(`[${config.url}]: ` + '请求数据大小超出允许的5M限制,无法进行防重复提交验证。') |
| | | return config; |
| | | } |
| | | const sessionObj = cache.session.getJSON('sessionObj') |
| | | if (sessionObj === undefined || sessionObj === null || sessionObj === '') { |
| | | cache.session.setJSON('sessionObj', requestObj) |
| | | } else { |
| | | const s_url = sessionObj.url; // 请求地址 |
| | | const s_data = sessionObj.data; // 请求数据 |
| | | const s_time = sessionObj.time; // 请求时间 |
| | | const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交 |
| | | if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) { |
| | | const message = '数据正在处理,请勿重复提交'; |
| | | console.warn(`[${s_url}]: ` + message) |
| | | return Promise.reject(new Error(message)) |
| | | } else { |
| | | cache.session.setJSON('sessionObj', requestObj) |
| | | } |
| | | } |
| | | } |
| | | return config |
| | | }, error => { |
| | |
| | | <el-table-column label="设备名称" prop="deviceName" min-width="120" show-overflow-tooltip /> |
| | | <el-table-column label="品牌" prop="brand" min-width="100" show-overflow-tooltip /> |
| | | <el-table-column label="设备ID" prop="deviceId" min-width="150" show-overflow-tooltip /> |
| | | <el-table-column v-if="deviceType === 'machine'" label="百度SDK指纹" prop="fingerprint" min-width="160" show-overflow-tooltip /> |
| | | <el-table-column label="所属区域" prop="areaName" min-width="130" show-overflow-tooltip /> |
| | | <el-table-column label="启用状态" align="center" width="100"> |
| | | <template slot-scope="scope"> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="人脸图片" prop="faceImage"> |
| | | <el-input v-model="form.faceImage" placeholder="请输入人脸图片地址" /> |
| | | <el-form-item label="人脸图片" prop="faceImage" class="employee-face-upload"> |
| | | <image-upload v-model="form.faceImage" :limit="1" :file-size="5" :is-show-tip="false" :compress="true" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div slot="footer" class="dialog-footer"> |
| | |
| | | this.title = '新增员工' |
| | | this.form = { |
| | | deptId: this.queryParams.deptId, |
| | | enabled: true |
| | | enabled: true, |
| | | faceImage: '' |
| | | } |
| | | this.open = true |
| | | }, |
| | |
| | | .employee-add-col .el-button { |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .employee-face-upload ::v-deep .el-upload--picture-card, |
| | | .employee-face-upload ::v-deep .el-upload-list--picture-card .el-upload-list__item { |
| | | width: 96px; |
| | | height: 96px; |
| | | line-height: 96px; |
| | | } |
| | | |
| | | .employee-face-upload ::v-deep .el-upload--picture-card { |
| | | border-radius: 8px; |
| | | } |
| | | </style> |
| | |
| | | computed: { |
| | | // 目的:把详情访问区域拆成标签数组,适配中英文逗号和数组格式。 |
| | | visitAreaTags() { |
| | | const value = this.detail.visitAreaNames |
| | | const value = this.detail.visitAreaNameList && this.detail.visitAreaNameList.length |
| | | ? this.detail.visitAreaNameList |
| | | : this.detail.visitAreaNames |
| | | if (Array.isArray(value)) { |
| | | return value.map(item => String(item || '').trim()).filter(Boolean) |
| | | } |
| | |
| | | <header class="visitor-header"> |
| | | <h1>访客登记记录</h1> |
| | | </header> |
| | | <el-form v-show="showSearch" ref="queryForm" :model="queryParams" size="small" :inline="true" class="visitor-query"> |
| | | <el-form ref="queryForm" :model="queryParams" size="small" :inline="true" class="visitor-query"> |
| | | <el-form-item label="访客姓名" prop="visitorName"> |
| | | <el-input v-model="queryParams.visitorName" placeholder="请输入访客姓名" clearable @keyup.enter.native="handleQuery" /> |
| | | </el-form-item> |
| | |
| | | </el-form-item> |
| | | <el-form-item label="被访部门" prop="intervieweeDept"> |
| | | <el-input v-model="queryParams.intervieweeDept" placeholder="请输入被访部门" clearable @keyup.enter.native="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="来访事由" prop="visitReason"> |
| | | <el-input v-model="queryParams.visitReason" placeholder="请输入来访事由" clearable @keyup.enter.native="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="访问区域" prop="areaId"> |
| | | <treeselect v-model="queryParams.areaId" :options="areaOptions" placeholder="请选择区域" clearable class="query-tree" /> |
| | |
| | | </el-form> |
| | | |
| | | <el-row :gutter="10" class="mb8"> |
| | | <el-col v-if="!isMiddlePlatformReadonly" v-hasPermi="['visitor:record:remove']" :span="1.5"> |
| | | <el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete()">删除</el-button> |
| | | <el-col v-hasPermi="['visitor:record:export']" :span="1.5"> |
| | | <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport">导出</el-button> |
| | | </el-col> |
| | | <right-toolbar :show-search.sync="showSearch" @queryTable="getList" /> |
| | | <el-col v-if="!isMiddlePlatformReadonly" v-hasPermi="['visitor:record:remove']" :span="1.5"> |
| | | <el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete">批量删除</el-button> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-table v-loading="loading" class="visitor-table" :data="recordList" @selection-change="handleSelectionChange"> |
| | | <el-table-column v-if="!isMiddlePlatformReadonly" type="selection" width="50" align="center" /> |
| | | <el-table-column label="登记编号" prop="registerNo" min-width="150" show-overflow-tooltip /> |
| | | <el-table-column label="访客姓名" prop="visitorName" min-width="150" /> |
| | | <el-table-column label="身份证" min-width="150" show-overflow-tooltip> |
| | | <template slot-scope="scope">{{ maskIdCard(scope.row.idCard) }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="登记类型" align="center" min-width="150"> |
| | | <template slot-scope="scope">{{ optionLabel(registerTypes, scope.row.registerType) }}</template> |
| | | </el-table-column> |
| | |
| | | <el-table-column label="被访部门" prop="intervieweeDept" min-width="150" show-overflow-tooltip /> |
| | | <el-table-column label="访问区域" prop="visitAreaNames" min-width="150" show-overflow-tooltip /> |
| | | <el-table-column label="车牌号" prop="plateNo" min-width="150" /> |
| | | <el-table-column label="来访事由" prop="visitReason" min-width="150" show-overflow-tooltip /> |
| | | <el-table-column label="有效期" min-width="260" show-overflow-tooltip> |
| | | <template slot-scope="scope">{{ validTimeText(scope.row.validStartTime, scope.row.validEndTime) }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="状态" align="center" min-width="150"> |
| | | <template slot-scope="scope"> |
| | | <span class="record-status-pill" :class="statusClass(scope.row.status)">{{ optionLabel(statusOptions, scope.row.status) }}</span> |
| | |
| | | <el-table-column label="登记时间" align="center" prop="registerTime" min-width="150"> |
| | | <template slot-scope="scope">{{ parseTime(scope.row.registerTime) }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" align="center" width="150" fixed="right" class-name="small-padding fixed-width"> |
| | | <el-table-column label="操作" align="center" width="90" fixed="right" class-name="small-padding fixed-width"> |
| | | <template slot-scope="scope"> |
| | | <el-button size="mini" type="text" icon="el-icon-view" @click="handleDetail(scope.row)">详情</el-button> |
| | | <el-button v-if="!isMiddlePlatformReadonly" v-hasPermi="['visitor:record:remove']" size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)">删除</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | |
| | | </template> |
| | | |
| | | <script> |
| | | import { saveAs } from 'file-saver' |
| | | import Treeselect from '@riophae/vue-treeselect' |
| | | import '@riophae/vue-treeselect/dist/vue-treeselect.css' |
| | | import { treeArea } from '@/api/visitor-admin/area' |
| | | import { listVisitorRecord, delVisitorRecord } from '@/api/visitor-admin/record' |
| | | import { listVisitorRecord, delVisitorRecord, exportVisitorRecord } from '@/api/visitor-admin/record' |
| | | import { getResponseData, getPageRows, getPageTotal, normalizeAreaOptions, toIdArray } from '@/utils/visitor-admin' |
| | | |
| | | export default { |
| | |
| | | data() { |
| | | return { |
| | | loading: false, |
| | | showSearch: true, |
| | | multiple: true, |
| | | ids: [], |
| | | total: 0, |
| | |
| | | } |
| | | return 'is-neutral' |
| | | }, |
| | | // 目的:列表只展示身份证前四位和后四位,中间内容统一脱敏。 |
| | | maskIdCard(value) { |
| | | const text = String(value || '') |
| | | if (!text) { |
| | | return '-' |
| | | } |
| | | if (text.length <= 8) { |
| | | return text |
| | | } |
| | | return text.slice(0, 4) + '*'.repeat(text.length - 8) + text.slice(-4) |
| | | }, |
| | | // 目的:把有效期开始和结束时间合并成列表展示文本。 |
| | | validTimeText(start, end) { |
| | | if (!start && !end) { |
| | | return '-' |
| | | } |
| | | return (this.parseTime(start) || '-') + ' 至 ' + (this.parseTime(end) || '-') |
| | | }, |
| | | // 目的:从第一页重新执行访客记录查询。 |
| | | handleQuery() { |
| | | this.queryParams.pageNum = 1 |
| | |
| | | this.resetForm('queryForm') |
| | | this.handleQuery() |
| | | }, |
| | | // 目的:导出当前筛选条件下的访客登记记录。 |
| | | handleExport() { |
| | | exportVisitorRecord(this.buildQuery()).then(res => { |
| | | const rows = Array.isArray(getResponseData(res)) ? getResponseData(res) : [] |
| | | const blob = new Blob([this.buildExcelXml(rows)], { type: 'application/vnd.ms-excel;charset=utf-8' }) |
| | | saveAs(blob, `访客登记记录_${this.parseTime(new Date(), '{y}{m}{d}{h}{i}{s}')}.xls`) |
| | | }) |
| | | }, |
| | | // 目的:把访客登记记录转换成 Excel 2003 XML,保持与操作日志导出方式一致。 |
| | | buildExcelXml(rows) { |
| | | const columns = [ |
| | | { title: '登记编号', key: 'registerNo', width: 150 }, |
| | | { title: '访客姓名', key: 'visitorName', width: 100 }, |
| | | { title: '证件号码', key: 'idCard', width: 170 }, |
| | | { title: '登记类型', value: row => this.optionLabel(this.registerTypes, row.registerType), width: 90 }, |
| | | { title: '被访人', key: 'intervieweeName', width: 100 }, |
| | | { title: '被访部门', key: 'intervieweeDept', width: 130 }, |
| | | { title: '访问区域', key: 'visitAreaNames', width: 150 }, |
| | | { title: '车牌号', key: 'plateNo', width: 100 }, |
| | | { title: '来访事由', key: 'visitReason', width: 180 }, |
| | | { title: '有效期开始时间', value: row => this.parseTime(row.validStartTime) || '', width: 150 }, |
| | | { title: '有效期结束时间', value: row => this.parseTime(row.validEndTime) || '', width: 150 }, |
| | | { title: '状态', value: row => this.optionLabel(this.statusOptions, row.status), width: 100 }, |
| | | { title: '登记时间', value: row => this.parseTime(row.registerTime) || '', width: 150 } |
| | | ] |
| | | const columnXml = columns.map(column => `<Column ss:Width="${column.width}"/>`).join('') |
| | | const headerXml = `<Row>${columns.map(column => this.excelCell(column.title, 'Header')).join('')}</Row>` |
| | | const bodyXml = rows.map(row => `<Row>${columns.map(column => { |
| | | const value = column.value ? column.value(row) : row[column.key] |
| | | return this.excelCell(value) |
| | | }).join('')}</Row>`).join('') |
| | | return `<?xml version="1.0" encoding="UTF-8"?> |
| | | <?mso-application progid="Excel.Sheet"?> |
| | | <Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" |
| | | xmlns:o="urn:schemas-microsoft-com:office:office" |
| | | xmlns:x="urn:schemas-microsoft-com:office:excel" |
| | | xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"> |
| | | <Styles> |
| | | <Style ss:ID="Header"><Font ss:Bold="1"/><Interior ss:Color="#EAF1F9" ss:Pattern="Solid"/></Style> |
| | | </Styles> |
| | | <Worksheet ss:Name="访客登记记录"> |
| | | <Table>${columnXml}${headerXml}${bodyXml}</Table> |
| | | </Worksheet> |
| | | </Workbook>` |
| | | }, |
| | | // 目的:生成 Excel 文本单元格并按需应用表头样式。 |
| | | excelCell(value, styleId) { |
| | | const style = styleId ? ` ss:StyleID="${styleId}"` : '' |
| | | return `<Cell${style}><Data ss:Type="String">${this.escapeXml(value)}</Data></Cell>` |
| | | }, |
| | | // 目的:转义 Excel XML 特殊字符,避免访客数据破坏文件结构。 |
| | | escapeXml(value) { |
| | | return String(value == null ? '' : value).replace(/[<>&'"]/g, char => ({ |
| | | '<': '<', |
| | | '>': '>', |
| | | '&': '&', |
| | | "'": ''', |
| | | '"': '"' |
| | | }[char])) |
| | | }, |
| | | // 目的:同步表格勾选状态,控制批量删除按钮可用性。 |
| | | handleSelectionChange(selection) { |
| | | this.ids = selection.map(item => item.recordId) |
| | |
| | | handleDetail(row) { |
| | | this.$router.push(`/visitor/record/detail/${row.recordId}`) |
| | | }, |
| | | // 目的:删除访客记录;中台只读模式下禁止本地删除。 |
| | | handleDelete(row) { |
| | | // 目的:批量删除已勾选的访客记录;中台只读模式下禁止本地删除。 |
| | | handleDelete() { |
| | | if (this.isMiddlePlatformReadonly) { |
| | | this.denyMiddlePlatformWrite() |
| | | return |
| | | } |
| | | const ids = row ? [row.recordId] : toIdArray(this.ids) |
| | | this.$modal.confirm('是否确认删除选中的访客登记记录?').then(() => delVisitorRecord(ids)).then(() => { |
| | | const ids = toIdArray(this.ids) |
| | | this.$modal.confirm(`是否确认批量删除选中的 ${ids.length} 条访客登记记录?`).then(() => delVisitorRecord(ids)).then(() => { |
| | | this.$modal.msgSuccess('删除成功') |
| | | this.getList() |
| | | }) |
| | |
| | | { fieldCode: 'visitAreaNames', fieldName: '访问区域' }, |
| | | { fieldCode: 'validTime', fieldName: '有效期' }, |
| | | { fieldCode: 'plateNo', fieldName: '车牌号' }, |
| | | { fieldCode: 'visitReason', fieldName: '来访事由' } |
| | | { fieldCode: 'visitReason', fieldName: '来访事由' }, |
| | | { fieldCode: 'receiverName', fieldName: '接待人' }, |
| | | { fieldCode: 'registerTime', fieldName: '登记时间' } |
| | | ] |
| | | } |
| | | }, |
| | |
| | | visitAreaNames: 'A区 / 办公楼', |
| | | validTime: '2026-07-07 09:00~18:00', |
| | | plateNo: '苏A12345', |
| | | visitReason: '商务拜访' |
| | | visitReason: '商务拜访', |
| | | receiverName: '王五', |
| | | registerTime: '2026-07-23 09:00' |
| | | } |
| | | return this.printFieldOptions |
| | | .filter(item => this.form.printFields.indexOf(item.fieldCode) !== -1) |
| | |
| | | <td>v{{ row.versionNo }}</td> |
| | | <td class="version-content-cell"> |
| | | <span class="version-content-text">{{ row.updateContent || '-' }}</span> |
| | | <div class="content-preview"> |
| | | <div class="preview-index">{{ index + 1 }}</div> |
| | | <div class="preview-content">{{ row.updateContent || '暂无更新内容' }}</div> |
| | | <div class="preview-foot">{{ parseTime(row.createTime) || '-' }}</div> |
| | | </div> |
| | | </td> |
| | | <td>{{ parseTime(row.createTime) }}</td> |
| | | <td v-if="!isMiddlePlatformReadonly && $auth.hasPermi('visitor:version:remove')"> |
| | |
| | | proxy: { |
| | | // detail: https://cli.vuejs.org/config/#devserver-proxy |
| | | [process.env.VUE_APP_BASE_API]: { |
| | | target: `http://localhost:9098`, |
| | | target: `http://221.182.45.100:9799`, |
| | | changeOrigin: true, |
| | | pathRewrite: { |
| | | ['^' + process.env.VUE_APP_BASE_API]: '' |
| | | } |
| | | }, |
| | | '/profile': { |
| | | target: `http://localhost:9098`, |
| | | target: `http://221.182.45.100:9799`, |
| | | changeOrigin: true |
| | | } |
| | | }, |
| | |
| | | vertical-align: middle; |
| | | } |
| | | |
| | | .content-preview { |
| | | position: absolute; |
| | | left: -62px; |
| | | top: 38px; |
| | | z-index: 10; |
| | | width: 204px; |
| | | min-height: 102px; |
| | | display: none; |
| | | padding: 0 10px 12px; |
| | | border: 1px solid #f0bc2d; |
| | | border-radius: 2px; |
| | | background: #fff; |
| | | box-shadow: 0 4px 12px rgba(15, 35, 70, 0.12); |
| | | } |
| | | |
| | | .content-preview::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 46px; |
| | | top: -188px; |
| | | width: 1px; |
| | | height: 188px; |
| | | background: #dca51f; |
| | | } |
| | | |
| | | .content-preview::after { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 42px; |
| | | top: -191px; |
| | | width: 8px; |
| | | height: 8px; |
| | | border-radius: 50%; |
| | | background: #dca51f; |
| | | } |
| | | |
| | | .version-content-cell:hover .content-preview { |
| | | display: block; |
| | | } |
| | | |
| | | .preview-index { |
| | | height: 16px; |
| | | margin: 0 -10px 12px; |
| | | padding-left: 9px; |
| | | color: #111827; |
| | | background: #ffd753; |
| | | font-weight: 700; |
| | | line-height: 16px; |
| | | } |
| | | |
| | | .preview-content { |
| | | min-height: 42px; |
| | | color: #111827; |
| | | font-weight: 700; |
| | | line-height: 18px; |
| | | word-break: break-word; |
| | | } |
| | | |
| | | .preview-foot { |
| | | margin-top: 26px; |
| | | color: #8a9ab0; |
| | | font-size: 11px; |
| | | } |
| | | |
| | | .table-actions { |
| | | display: flex; |
| | | align-items: center; |
| | |
| | | .machine-table th:nth-child(8), |
| | | .machine-table td:nth-child(8), |
| | | .machine-table th:nth-child(9), |
| | | .machine-table td:nth-child(9) { |
| | | width: 11.111%; |
| | | .machine-table td:nth-child(9), |
| | | .machine-table th:nth-child(10), |
| | | .machine-table td:nth-child(10) { |
| | | width: 10%; |
| | | } |
| | |
| | | } else if (state.mode === "detail") { |
| | | title = "角色详情"; |
| | | } |
| | | document.title = title + " - 运营活动管理平台"; |
| | | document.title = title + " - 访客机自助登记管理平台"; |
| | | document.getElementById("pageTitle").textContent = title; |
| | | document.getElementById("pageTab").textContent = title; |
| | | document.getElementById("formActions").hidden = state.mode === "detail"; |
| | |
| | | ' <td>' + escapeHtml(item.versionNo || "-") + '</td>', |
| | | ' <td class="version-content-cell">', |
| | | ' <span class="version-content-text">' + escapeHtml(item.updateContent || "-") + '</span>', |
| | | ' <div class="content-preview">', |
| | | ' <div class="preview-index">' + rowIndex + '</div>', |
| | | ' <div class="preview-content">' + escapeHtml(item.updateContent || "-") + '</div>', |
| | | ' <div class="preview-foot">' + escapeHtml(item.packageName || "安装包") + ' ' + escapeHtml(shortDate(item.createTime)) + '</div>', |
| | | ' </div>', |
| | | ' </td>', |
| | | ' <td>' + escapeHtml(item.createTime || "-") + '</td>', |
| | | ' <td><div class="table-actions">', |
| | |
| | | ' <td>' + escapeHtml(item.deviceName || "-") + '</td>', |
| | | ' <td>' + escapeHtml(item.brand || "-") + '</td>', |
| | | ' <td>' + escapeHtml(item.deviceId || "-") + '</td>', |
| | | ' <td title="' + escapeHtml(item.fingerprint || "") + '">' + escapeHtml(item.fingerprint || "-") + '</td>', |
| | | ' <td>' + escapeHtml(item.areaName || "-") + '</td>', |
| | | ' <td>' + renderEnabledStatus(item.enabled) + '</td>', |
| | | ' <td>' + renderOnlineStatus(item.onlineStatus) + '</td>', |
| | |
| | | if (text.length < 8) { |
| | | return text || "-"; |
| | | } |
| | | return text.slice(0, 6) + "********" + text.slice(-4); |
| | | return text.slice(0, 4) + repeatString("*", text.length - 8) + text.slice(-4); |
| | | } |
| | | |
| | | function repeatString(value, count) { |
| | | var result = ""; |
| | | for (var index = 0; index < count; index++) { |
| | | result += value; |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | function toServerDateTime(value) { |
| | |
| | | <head> |
| | | <meta charset="UTF-8"> |
| | | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | | <title>首页 - 运营活动管理平台</title> |
| | | <title>首页 - 访客机自助登记管理平台</title> |
| | | <link rel="stylesheet" href="../css/dashboard.css"> |
| | | <link rel="stylesheet" href="../css/components.css"> |
| | | </head> |
| | |
| | | <aside class="sidebar"> |
| | | <div class="brand"> |
| | | <img src="../assets/login-avatar.svg" alt=""> |
| | | <strong>运营活动管理平台</strong> |
| | | <strong>访客机自助登记管理平台</strong> |
| | | </div> |
| | | |
| | | <nav class="menu" aria-label="主菜单"> |
| | |
| | | <head> |
| | | <meta charset="UTF-8"> |
| | | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | | <title>部门管理 - 运营活动管理平台</title> |
| | | <title>部门管理 - 访客机自助登记管理平台</title> |
| | | <link rel="stylesheet" href="../css/dashboard.css"> |
| | | <link rel="stylesheet" href="../css/components.css"> |
| | | <link rel="stylesheet" href="../css/department-management.css"> |
| | |
| | | <aside class="sidebar"> |
| | | <div class="brand"> |
| | | <img src="../assets/login-avatar.svg" alt=""> |
| | | <strong>运营活动管理平台</strong> |
| | | <strong>访客机自助登记管理平台</strong> |
| | | </div> |
| | | |
| | | <nav class="menu" aria-label="主菜单"> |
| | |
| | | <head> |
| | | <meta charset="UTF-8"> |
| | | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | | <title>门禁设备管理 - 运营活动管理平台</title> |
| | | <title>门禁设备管理 - 访客机自助登记管理平台</title> |
| | | <link rel="stylesheet" href="../css/dashboard.css"> |
| | | <link rel="stylesheet" href="../css/components.css"> |
| | | <link rel="stylesheet" href="../css/door-management.css"> |
| | |
| | | <aside class="sidebar"> |
| | | <div class="brand"> |
| | | <img src="../assets/login-avatar.svg" alt=""> |
| | | <strong>运营活动管理平台</strong> |
| | | <strong>访客机自助登记管理平台</strong> |
| | | </div> |
| | | |
| | | <nav class="menu" aria-label="主菜单"> |
| | |
| | | <head> |
| | | <meta charset="UTF-8"> |
| | | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | | <title>员工管理 - 运营活动管理平台</title> |
| | | <title>员工管理 - 访客机自助登记管理平台</title> |
| | | <link rel="stylesheet" href="../css/dashboard.css"> |
| | | <link rel="stylesheet" href="../css/components.css"> |
| | | <link rel="stylesheet" href="../css/employee-management.css"> |
| | |
| | | <aside class="sidebar"> |
| | | <div class="brand"> |
| | | <img src="../assets/login-avatar.svg" alt=""> |
| | | <strong>运营活动管理平台</strong> |
| | | <strong>访客机自助登记管理平台</strong> |
| | | </div> |
| | | |
| | | <nav class="menu" aria-label="主菜单"> |
| | |
| | | <head> |
| | | <meta charset="UTF-8"> |
| | | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | | <title>平台管理员 - 运营活动管理平台</title> |
| | | <title>平台管理员 - 访客机自助登记管理平台</title> |
| | | <link rel="stylesheet" href="../css/dashboard.css"> |
| | | <link rel="stylesheet" href="../css/components.css"> |
| | | <link rel="stylesheet" href="../css/platform-admin.css"> |
| | |
| | | <aside class="sidebar"> |
| | | <div class="brand"> |
| | | <img src="../assets/login-avatar.svg" alt=""> |
| | | <strong>运营活动管理平台</strong> |
| | | <strong>访客机自助登记管理平台</strong> |
| | | </div> |
| | | <nav class="menu" aria-label="主菜单"> |
| | | <a class="menu-item" href="./dashboard.html"><span class="menu-icon home"></span><span>首页</span></a> |
| | |
| | | <head> |
| | | <meta charset="UTF-8"> |
| | | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | | <title>入园承诺 - 运营活动管理平台</title> |
| | | <title>入园承诺 - 访客机自助登记管理平台</title> |
| | | <link rel="stylesheet" href="../css/dashboard.css"> |
| | | <link rel="stylesheet" href="../css/components.css"> |
| | | <link rel="stylesheet" href="../css/protocol-config.css"> |
| | |
| | | <aside class="sidebar"> |
| | | <div class="brand"> |
| | | <img src="../assets/login-avatar.svg" alt=""> |
| | | <strong>运营活动管理平台</strong> |
| | | <strong>访客机自助登记管理平台</strong> |
| | | </div> |
| | | |
| | | <nav class="menu" aria-label="主菜单"> |
| | |
| | | <head> |
| | | <meta charset="UTF-8"> |
| | | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | | <title>角色 - 运营活动管理平台</title> |
| | | <title>角色 - 访客机自助登记管理平台</title> |
| | | <link rel="stylesheet" href="../css/dashboard.css"> |
| | | <link rel="stylesheet" href="../css/components.css"> |
| | | <link rel="stylesheet" href="../css/role-management.css"> |
| | |
| | | <aside class="sidebar"> |
| | | <div class="brand"> |
| | | <img src="../assets/login-avatar.svg" alt=""> |
| | | <strong>运营活动管理平台</strong> |
| | | <strong>访客机自助登记管理平台</strong> |
| | | </div> |
| | | <nav class="menu" aria-label="主菜单"> |
| | | <a class="menu-item" href="./dashboard.html"><span class="menu-icon home"></span><span>首页</span></a> |
| | |
| | | <head> |
| | | <meta charset="UTF-8"> |
| | | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | | <title>角色管理 - 运营活动管理平台</title> |
| | | <title>角色管理 - 访客机自助登记管理平台</title> |
| | | <link rel="stylesheet" href="../css/dashboard.css"> |
| | | <link rel="stylesheet" href="../css/components.css"> |
| | | <link rel="stylesheet" href="../css/role-management.css"> |
| | |
| | | <aside class="sidebar"> |
| | | <div class="brand"> |
| | | <img src="../assets/login-avatar.svg" alt=""> |
| | | <strong>运营活动管理平台</strong> |
| | | <strong>访客机自助登记管理平台</strong> |
| | | </div> |
| | | <nav class="menu" aria-label="主菜单"> |
| | | <a class="menu-item" href="./dashboard.html"><span class="menu-icon home"></span><span>首页</span></a> |
| | |
| | | <head> |
| | | <meta charset="UTF-8"> |
| | | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | | <title>信息配置 - 运营活动管理平台</title> |
| | | <title>信息配置 - 访客机自助登记管理平台</title> |
| | | <link rel="stylesheet" href="../css/dashboard.css"> |
| | | <link rel="stylesheet" href="../css/components.css"> |
| | | <link rel="stylesheet" href="../css/system-config.css"> |
| | |
| | | <aside class="sidebar"> |
| | | <div class="brand"> |
| | | <img src="../assets/login-avatar.svg" alt=""> |
| | | <strong>运营活动管理平台</strong> |
| | | <strong>访客机自助登记管理平台</strong> |
| | | </div> |
| | | |
| | | <nav class="menu" aria-label="主菜单"> |
| | |
| | | <head> |
| | | <meta charset="UTF-8"> |
| | | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | | <title>访客机版本更新 - 运营活动管理平台</title> |
| | | <title>访客机版本更新 - 访客机自助登记管理平台</title> |
| | | <link rel="stylesheet" href="../css/dashboard.css"> |
| | | <link rel="stylesheet" href="../css/components.css"> |
| | | <link rel="stylesheet" href="../css/version-update.css"> |
| | |
| | | <aside class="sidebar"> |
| | | <div class="brand"> |
| | | <img src="../assets/login-avatar.svg" alt=""> |
| | | <strong>运营活动管理平台</strong> |
| | | <strong>访客机自助登记管理平台</strong> |
| | | </div> |
| | | |
| | | <nav class="menu" aria-label="主菜单"> |
| | |
| | | <head> |
| | | <meta charset="UTF-8"> |
| | | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | | <title>访客机管理 - 运营活动管理平台</title> |
| | | <title>访客机管理 - 访客机自助登记管理平台</title> |
| | | <link rel="stylesheet" href="../css/dashboard.css"> |
| | | <link rel="stylesheet" href="../css/components.css"> |
| | | <link rel="stylesheet" href="../css/visitor-machine.css"> |
| | |
| | | <aside class="sidebar"> |
| | | <div class="brand"> |
| | | <img src="../assets/login-avatar.svg" alt=""> |
| | | <strong>运营活动管理平台</strong> |
| | | <strong>访客机自助登记管理平台</strong> |
| | | </div> |
| | | |
| | | <nav class="menu" aria-label="主菜单"> |
| | |
| | | <th>设备名称</th> |
| | | <th>品牌</th> |
| | | <th>设备ID</th> |
| | | <th>百度SDK指纹</th> |
| | | <th>所属区域</th> |
| | | <th>启用状态</th> |
| | | <th>在线状态</th> |
| | |
| | | <head> |
| | | <meta charset="UTF-8"> |
| | | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | | <title>预约详情 - 运营活动管理平台</title> |
| | | <title>预约详情 - 访客机自助登记管理平台</title> |
| | | <link rel="stylesheet" href="../css/dashboard.css"> |
| | | <link rel="stylesheet" href="../css/components.css"> |
| | | <link rel="stylesheet" href="../css/visitor-record.css"> |
| | |
| | | <body> |
| | | <div class="app-shell"> |
| | | <aside class="sidebar"> |
| | | <div class="brand"><img src="../assets/login-avatar.svg" alt=""><strong>运营活动管理平台</strong></div> |
| | | <div class="brand"><img src="../assets/login-avatar.svg" alt=""><strong>访客机自助登记管理平台</strong></div> |
| | | <nav class="menu" aria-label="主菜单"> |
| | | <a class="menu-item" href="./dashboard.html"><span class="menu-icon home"></span><span>首页</span></a> |
| | | <section class="menu-group open"> |
| | |
| | | <head> |
| | | <meta charset="UTF-8"> |
| | | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | | <title>访客登记记录 - 运营活动管理平台</title> |
| | | <title>访客登记记录 - 访客机自助登记管理平台</title> |
| | | <link rel="stylesheet" href="../css/dashboard.css"> |
| | | <link rel="stylesheet" href="../css/components.css"> |
| | | <link rel="stylesheet" href="../css/visitor-record.css"> |
| | |
| | | <aside class="sidebar"> |
| | | <div class="brand"> |
| | | <img src="../assets/login-avatar.svg" alt=""> |
| | | <strong>运营活动管理平台</strong> |
| | | <strong>访客机自助登记管理平台</strong> |
| | | </div> |
| | | <nav class="menu" aria-label="主菜单"> |
| | | <a class="menu-item" href="./dashboard.html"><span class="menu-icon home"></span><span>首页</span></a> |