This commit is contained in:
weizhong committed 2025-08-19 19:59:48 +08:00
commit ea739d328b
19 files changed
+3510 -39

No files matched your search

@@ -7,6 +7,7 @@ import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaIgnore;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
@@ -22,6 +23,8 @@ import org.dromara.zsjy.domain.vo.ZsjyEnrollmentInfoVo;
import org.dromara.zsjy.domain.bo.ZsjyEnrollmentInfoBo;
import org.dromara.zsjy.service.IZsjyEnrollmentInfoService;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.xg.service.IXgSysDeptService;
import org.dromara.xg.domain.vo.XgSysDeptVo;
/**
* 招生报名信息Controller
@@ -36,6 +39,7 @@ import org.dromara.common.mybatis.core.page.TableDataInfo;
public class ZsjyEnrollmentInfoController extends BaseController {
private final IZsjyEnrollmentInfoService enrollmentInfoService;
private final IXgSysDeptService xgSysDeptService;
/**
* 查询招生报名信息列表
@@ -101,6 +105,7 @@ public class ZsjyEnrollmentInfoController extends BaseController {
/**
* 根据身份证号和准考证号查询报名信息
*/
@SaIgnore
@GetMapping("/query")
public R<ZsjyEnrollmentInfoVo> queryByIdCardAndExamNumber(
@RequestParam(required = false) String idCard,
@@ -112,7 +117,7 @@ public class ZsjyEnrollmentInfoController extends BaseController {
/**
* 提交报名信息(供学生端使用)
*/
@SaCheckPermission("zsjy:mobile:enrollment:apply")
@SaIgnore
@Log(title = "学生报名", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping("/submit")
@@ -123,7 +128,7 @@ public class ZsjyEnrollmentInfoController extends BaseController {
/**
* 查询我的报名状态(供学生端使用)
*/
@SaCheckPermission("zsjy:mobile:enrollment:status")
@SaIgnore
@GetMapping("/myStatus")
public R<ZsjyEnrollmentInfoVo> getMyEnrollmentStatus(
@RequestParam String idCard,
@@ -153,6 +158,36 @@ public class ZsjyEnrollmentInfoController extends BaseController {
return R.fail("请提供要更新的报名信息ID");
}
}
/**
* 获取学院列表(供移动端公开访问)
*/
@SaIgnore
@GetMapping("/colleges")
public R<List<XgSysDeptVo>> getColleges() {
try {
// 调用学院部门服务获取学院列表
List<XgSysDeptVo> colleges = xgSysDeptService.selectCollegeList();
return R.ok(colleges);
} catch (Exception e) {
return R.fail("获取学院列表异常: " + e.getMessage());
}
}
/**
* 根据学院ID获取专业列表(供移动端公开访问)
*/
@SaIgnore
@GetMapping("/majors/{collegeId}")
public R<List<XgSysDeptVo>> getMajorsByCollegeId(@PathVariable Long collegeId) {
try {
// 调用学院部门服务获取专业列表
List<XgSysDeptVo> majors = xgSysDeptService.selectMajorListByCollegeId(collegeId);
return R.ok(majors);
} catch (Exception e) {
return R.fail("获取专业列表异常: " + e.getMessage());
}
}
}
/**
@@ -0,0 +1,161 @@
package org.dromara.zsjy.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.zsjy.domain.bo.ZsjyQrCodeBo;
import org.dromara.zsjy.domain.vo.ZsjyQrCodeVo;
import org.dromara.zsjy.service.IZsjyQrCodeService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import java.util.Arrays;
import java.util.List;
/**
* 二维码管理Controller
*
* @author Cursor AI
* @date 2025-01-27
*/
@Slf4j
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/zsjy/qrcode")
@Tag(name = "二维码管理", description = "招生二维码管理功能")
public class ZsjyQrCodeController extends BaseController {
private final IZsjyQrCodeService qrCodeService;
/**
* 查询二维码信息列表
*/
@Operation(summary = "查询二维码列表", description = "分页查询二维码信息列表")
@SaCheckPermission("zsjy:qrcode:list")
@GetMapping("/list")
public TableDataInfo<ZsjyQrCodeVo> list(@Validated ZsjyQrCodeBo bo, PageQuery pageQuery) {
return qrCodeService.queryPageList(bo, pageQuery);
}
/**
* 导出二维码信息列表
*/
@Operation(summary = "导出二维码列表", description = "导出二维码信息到Excel")
@SaCheckPermission("zsjy:qrcode:export")
@Log(title = "二维码管理", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(@Validated ZsjyQrCodeBo bo, HttpServletResponse response) {
List<ZsjyQrCodeVo> list = qrCodeService.queryList(bo);
ExcelUtil.exportExcel(list, "二维码信息", ZsjyQrCodeVo.class, response);
}
/**
* 获取二维码信息详细信息
*/
@Operation(summary = "获取二维码详情", description = "根据ID获取二维码详细信息")
@SaCheckPermission("zsjy:qrcode:query")
@GetMapping("/{id}")
public R<ZsjyQrCodeVo> getInfo(
@Parameter(description = "二维码ID", required = true)
@PathVariable @NotNull(message = "主键不能为空") Long id) {
return R.ok(qrCodeService.queryById(id));
}
/**
* 新增二维码信息
*/
@Operation(summary = "新增二维码", description = "创建新的二维码")
@SaCheckPermission("zsjy:qrcode:add")
@Log(title = "二维码管理", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody ZsjyQrCodeBo bo) {
return toAjax(qrCodeService.insertByBo(bo));
}
/**
* 修改二维码信息
*/
@Operation(summary = "修改二维码", description = "修改二维码信息")
@SaCheckPermission("zsjy:qrcode:edit")
@Log(title = "二维码管理", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody ZsjyQrCodeBo bo) {
return toAjax(qrCodeService.updateByBo(bo));
}
/**
* 删除二维码信息
*/
@Operation(summary = "删除二维码", description = "批量删除二维码信息")
@SaCheckPermission("zsjy:qrcode:remove")
@Log(title = "二维码管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(
@Parameter(description = "二维码ID数组", required = true)
@PathVariable @NotEmpty(message = "主键不能为空") Long[] ids) {
return toAjax(qrCodeService.deleteWithValidByIds(Arrays.asList(ids), true));
}
/**
* 验证二维码Token有效性
*/
@Operation(summary = "验证Token", description = "验证二维码访问令牌是否有效")
@GetMapping("/validate/{token}")
public R<Boolean> validateToken(
@Parameter(description = "访问令牌", required = true)
@PathVariable String token) {
return R.ok(qrCodeService.validateToken(token));
}
/**
* 根据Token获取二维码信息
*/
@Operation(summary = "根据Token获取二维码", description = "通过访问令牌获取二维码信息")
@GetMapping("/token/{token}")
public R<ZsjyQrCodeVo> getByToken(
@Parameter(description = "访问令牌", required = true)
@PathVariable String token) {
return R.ok(qrCodeService.queryByAccessToken(token));
}
/**
* 增加二维码使用次数
*/
@Operation(summary = "增加使用次数", description = "增加二维码使用次数")
@PostMapping("/increment/{id}")
public R<Void> incrementUsage(
@Parameter(description = "二维码ID", required = true)
@PathVariable @NotNull(message = "主键不能为空") Long id) {
return toAjax(qrCodeService.incrementUsage(id));
}
/**
* 生成新的访问令牌
*/
@Operation(summary = "生成Token", description = "生成新的访问令牌")
@SaCheckPermission("zsjy:qrcode:token")
@PostMapping("/generate-token")
public R<String> generateToken() {
return R.ok(qrCodeService.generateAccessToken());
}
}
@@ -0,0 +1,79 @@
package org.dromara.zsjy.domain;
import org.dromara.common.tenant.core.TenantEntity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
import java.util.Date;
/**
* 二维码管理对象 zsjy_qr_code
*
* @author Cursor AI
* @date 2025-01-27
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("zsjy_qr_code")
public class ZsjyQrCode extends TenantEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id")
private Long id;
/**
* 二维码名称
*/
private String codeName;
/**
* 二维码类型(1-招生报名)
*/
private Integer codeType;
/**
* 二维码内容(URL)
*/
private String qrContent;
/**
* 访问令牌
*/
private String accessToken;
/**
* 过期时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date expireTime;
/**
* 最大使用次数(0-无限制)
*/
private Integer maxUsage;
/**
* 当前使用次数
*/
private Integer currentUsage;
/**
* 状态(0-禁用 1-启用)
*/
private Integer status;
/**
* 删除标志(0代表存在 1代表删除)
*/
@TableLogic
private Integer deleted;
}
@@ -0,0 +1,85 @@
package org.dromara.zsjy.domain.bo;
import org.dromara.zsjy.domain.ZsjyQrCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
import java.util.Date;
/**
* 二维码管理业务对象 zsjy_qr_code
*
* @author Cursor AI
* @date 2025-01-27
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = ZsjyQrCode.class, reverseConvertGenerate = false)
public class ZsjyQrCodeBo extends BaseEntity {
/**
* 主键ID
*/
private Long id;
/**
* 二维码名称
*/
@NotBlank(message = "二维码名称不能为空", groups = { AddGroup.class, EditGroup.class })
@Size(max = 100, message = "二维码名称长度不能超过100个字符", groups = { AddGroup.class, EditGroup.class })
private String codeName;
/**
* 二维码类型(1-招生报名)
*/
@NotNull(message = "二维码类型不能为空", groups = { AddGroup.class, EditGroup.class })
private Integer codeType;
/**
* 二维码内容(URL)
*/
@NotBlank(message = "二维码内容不能为空", groups = { AddGroup.class, EditGroup.class })
private String qrContent;
/**
* 访问令牌
*/
@NotBlank(message = "访问令牌不能为空", groups = { AddGroup.class, EditGroup.class })
@Size(max = 255, message = "访问令牌长度不能超过255个字符", groups = { AddGroup.class, EditGroup.class })
private String accessToken;
/**
* 过期时间
*/
private Date expireTime;
/**
* 最大使用次数(0-无限制)
*/
@NotNull(message = "最大使用次数不能为空", groups = { AddGroup.class, EditGroup.class })
@Min(value = 0, message = "最大使用次数不能小于0", groups = { AddGroup.class, EditGroup.class })
private Integer maxUsage;
/**
* 当前使用次数
*/
private Integer currentUsage;
/**
* 状态(0-禁用 1-启用)
*/
@NotNull(message = "状态不能为空", groups = { AddGroup.class, EditGroup.class })
private Integer status;
// VO类基础字段(强制性规范)
private Long createBy;
private Date createTime;
private Long updateBy;
private Date updateTime;
}
@@ -0,0 +1,91 @@
package org.dromara.zsjy.domain.vo;
import org.dromara.zsjy.domain.ZsjyQrCode;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 二维码管理视图对象 zsjy_qr_code
*
* @author Cursor AI
* @date 2025-01-27
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = ZsjyQrCode.class)
public class ZsjyQrCodeVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@ExcelProperty(value = "主键ID")
private Long id;
/**
* 二维码名称
*/
@ExcelProperty(value = "二维码名称")
private String codeName;
/**
* 二维码类型(1-招生报名)
*/
@ExcelProperty(value = "二维码类型")
private Integer codeType;
/**
* 二维码内容(URL)
*/
@ExcelProperty(value = "二维码内容")
private String qrContent;
/**
* 访问令牌
*/
@ExcelProperty(value = "访问令牌")
private String accessToken;
/**
* 过期时间
*/
@ExcelProperty(value = "过期时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date expireTime;
/**
* 最大使用次数(0-无限制)
*/
@ExcelProperty(value = "最大使用次数")
private Integer maxUsage;
/**
* 当前使用次数
*/
@ExcelProperty(value = "当前使用次数")
private Integer currentUsage;
/**
* 状态(0-禁用 1-启用)
*/
@ExcelProperty(value = "状态")
private Integer status;
// VO类基础字段(强制性规范)
private Long createBy;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
private Long updateBy;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}
@@ -0,0 +1,54 @@
package org.dromara.zsjy.mapper;
import org.dromara.zsjy.domain.ZsjyQrCode;
import org.dromara.zsjy.domain.vo.ZsjyQrCodeVo;
import org.dromara.zsjy.domain.bo.ZsjyQrCodeBo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 二维码管理 Mapper 接口
*
* @author Cursor AI
* @date 2025-01-27
*/
public interface ZsjyQrCodeMapper extends BaseMapperPlus<ZsjyQrCode, ZsjyQrCodeVo> {
/**
* 分页查询二维码列表
*
* @param page 分页参数
* @param bo 查询条件
* @return 二维码列表
*/
IPage<ZsjyQrCodeVo> selectQrCodePageList(@Param("page") Page<?> page, @Param("bo") ZsjyQrCodeBo bo);
/**
* 查询二维码列表(非分页)
*
* @param bo 查询条件
* @return 二维码列表
*/
List<ZsjyQrCodeVo> selectQrCodeVoList(ZsjyQrCodeBo bo);
/**
* 根据访问令牌查询二维码信息
*
* @param accessToken 访问令牌
* @return 二维码信息
*/
ZsjyQrCodeVo selectByAccessToken(String accessToken);
/**
* 更新二维码使用次数
*
* @param id 二维码ID
* @return 更新结果
*/
int updateUsageCount(@Param("id") Long id);
}
@@ -0,0 +1,100 @@
package org.dromara.zsjy.service;
import org.dromara.zsjy.domain.bo.ZsjyQrCodeBo;
import org.dromara.zsjy.domain.vo.ZsjyQrCodeVo;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import java.util.Collection;
import java.util.List;
/**
* 二维码管理Service接口
*
* @author Cursor AI
* @date 2025-01-27
*/
public interface IZsjyQrCodeService {
/**
* 查询二维码信息
*
* @param id 二维码信息主键
* @return 二维码信息
*/
ZsjyQrCodeVo queryById(Long id);
/**
* 查询二维码信息列表
*
* @param bo 二维码信息
* @param pageQuery 分页参数
* @return 二维码信息集合
*/
TableDataInfo<ZsjyQrCodeVo> queryPageList(ZsjyQrCodeBo bo, PageQuery pageQuery);
/**
* 查询二维码信息列表
*
* @param bo 二维码信息
* @return 二维码信息集合
*/
List<ZsjyQrCodeVo> queryList(ZsjyQrCodeBo bo);
/**
* 新增二维码信息
*
* @param bo 二维码信息
* @return 结果
*/
Boolean insertByBo(ZsjyQrCodeBo bo);
/**
* 修改二维码信息
*
* @param bo 二维码信息
* @return 结果
*/
Boolean updateByBo(ZsjyQrCodeBo bo);
/**
* 校验并批量删除二维码信息
*
* @param ids 需要删除的二维码信息主键集合
* @param isValid 是否校验,true-删除前校验,false-不校验
* @return 结果
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 根据访问令牌查询二维码信息
*
* @param accessToken 访问令牌
* @return 二维码信息
*/
ZsjyQrCodeVo queryByAccessToken(String accessToken);
/**
* 验证二维码Token有效性
*
* @param accessToken 访问令牌
* @return 是否有效
*/
Boolean validateToken(String accessToken);
/**
* 增加二维码使用次数
*
* @param id 二维码ID
* @return 结果
*/
Boolean incrementUsage(Long id);
/**
* 生成唯一的访问令牌
*
* @return 访问令牌
*/
String generateAccessToken();
}
@@ -0,0 +1,242 @@
package org.dromara.zsjy.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.zsjy.domain.ZsjyQrCode;
import org.dromara.zsjy.domain.bo.ZsjyQrCodeBo;
import org.dromara.zsjy.domain.vo.ZsjyQrCodeVo;
import org.dromara.zsjy.mapper.ZsjyQrCodeMapper;
import org.dromara.zsjy.service.IZsjyQrCodeService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.util.Date;
import java.util.List;
/**
* 二维码管理Service业务层处理
*
* @author Cursor AI
* @date 2025-01-27
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class ZsjyQrCodeServiceImpl implements IZsjyQrCodeService {
private final ZsjyQrCodeMapper baseMapper;
/**
* 查询二维码信息
*/
@Override
public ZsjyQrCodeVo queryById(Long id) {
return baseMapper.selectVoById(id);
}
/**
* 分页查询二维码信息列表
*/
@Override
public TableDataInfo<ZsjyQrCodeVo> queryPageList(ZsjyQrCodeBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<ZsjyQrCode> lqw = buildQueryWrapper(bo);
Page<ZsjyQrCodeVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 查询二维码信息列表
*/
@Override
public List<ZsjyQrCodeVo> queryList(ZsjyQrCodeBo bo) {
LambdaQueryWrapper<ZsjyQrCode> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<ZsjyQrCode> buildQueryWrapper(ZsjyQrCodeBo bo) {
LambdaQueryWrapper<ZsjyQrCode> lqw = Wrappers.lambdaQuery();
lqw.like(StringUtils.isNotBlank(bo.getCodeName()), ZsjyQrCode::getCodeName, bo.getCodeName());
lqw.eq(ObjectUtil.isNotNull(bo.getCodeType()), ZsjyQrCode::getCodeType, bo.getCodeType());
lqw.like(StringUtils.isNotBlank(bo.getQrContent()), ZsjyQrCode::getQrContent, bo.getQrContent());
lqw.eq(StringUtils.isNotBlank(bo.getAccessToken()), ZsjyQrCode::getAccessToken, bo.getAccessToken());
lqw.eq(ObjectUtil.isNotNull(bo.getExpireTime()), ZsjyQrCode::getExpireTime, bo.getExpireTime());
lqw.eq(ObjectUtil.isNotNull(bo.getMaxUsage()), ZsjyQrCode::getMaxUsage, bo.getMaxUsage());
lqw.eq(ObjectUtil.isNotNull(bo.getCurrentUsage()), ZsjyQrCode::getCurrentUsage, bo.getCurrentUsage());
lqw.eq(ObjectUtil.isNotNull(bo.getStatus()), ZsjyQrCode::getStatus, bo.getStatus());
return lqw;
}
/**
* 新增二维码信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean insertByBo(ZsjyQrCodeBo bo) {
ZsjyQrCode add = BeanUtil.toBean(bo, ZsjyQrCode.class);
validEntityBeforeSave(add);
// 如果没有提供访问令牌,则自动生成
if (StrUtil.isBlank(add.getAccessToken())) {
add.setAccessToken(generateAccessToken());
}
// 初始化当前使用次数
if (add.getCurrentUsage() == null) {
add.setCurrentUsage(0);
}
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());
}
return flag;
}
/**
* 修改二维码信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateByBo(ZsjyQrCodeBo bo) {
ZsjyQrCode update = BeanUtil.toBean(bo, ZsjyQrCode.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(ZsjyQrCode entity) {
// 校验二维码名称唯一性
if (StringUtils.isNotBlank(entity.getCodeName())) {
LambdaQueryWrapper<ZsjyQrCode> lqw = Wrappers.lambdaQuery();
lqw.eq(ZsjyQrCode::getCodeName, entity.getCodeName());
if (entity.getId() != null) {
lqw.ne(ZsjyQrCode::getId, entity.getId());
}
boolean exists = baseMapper.exists(lqw);
if (exists) {
throw new RuntimeException("二维码名称已存在");
}
}
// 校验访问令牌唯一性
if (StringUtils.isNotBlank(entity.getAccessToken())) {
LambdaQueryWrapper<ZsjyQrCode> lqw = Wrappers.lambdaQuery();
lqw.eq(ZsjyQrCode::getAccessToken, entity.getAccessToken());
if (entity.getId() != null) {
lqw.ne(ZsjyQrCode::getId, entity.getId());
}
boolean exists = baseMapper.exists(lqw);
if (exists) {
throw new RuntimeException("访问令牌已存在");
}
}
}
/**
* 批量删除二维码信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if (isValid) {
// 删除前校验逻辑(如果需要的话)
}
return baseMapper.deleteBatchIds(ids) > 0;
}
/**
* 根据访问令牌查询二维码信息
*/
@Override
public ZsjyQrCodeVo queryByAccessToken(String accessToken) {
if (StrUtil.isBlank(accessToken)) {
return null;
}
LambdaQueryWrapper<ZsjyQrCode> lqw = Wrappers.lambdaQuery();
lqw.eq(ZsjyQrCode::getAccessToken, accessToken);
return baseMapper.selectVoOne(lqw);
}
/**
* 验证二维码Token有效性
*/
@Override
public Boolean validateToken(String accessToken) {
ZsjyQrCodeVo qrCode = queryByAccessToken(accessToken);
if (qrCode == null) {
return false;
}
// 检查状态
if (qrCode.getStatus() == null || qrCode.getStatus() != 1) {
return false;
}
// 检查过期时间
if (qrCode.getExpireTime() != null && qrCode.getExpireTime().before(new Date())) {
return false;
}
// 检查使用次数限制
if (qrCode.getMaxUsage() != null && qrCode.getMaxUsage() > 0) {
if (qrCode.getCurrentUsage() != null && qrCode.getCurrentUsage() >= qrCode.getMaxUsage()) {
return false;
}
}
return true;
}
/**
* 增加二维码使用次数
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean incrementUsage(Long id) {
return baseMapper.updateUsageCount(id) > 0;
}
/**
* 生成唯一的访问令牌
*/
@Override
public String generateAccessToken() {
String token;
int maxAttempts = 10;
int attempts = 0;
do {
token = "qr_" + IdUtil.fastSimpleUUID();
attempts++;
// 检查令牌是否已存在
LambdaQueryWrapper<ZsjyQrCode> lqw = Wrappers.lambdaQuery();
lqw.eq(ZsjyQrCode::getAccessToken, token);
boolean exists = baseMapper.exists(lqw);
if (!exists) {
return token;
}
} while (attempts < maxAttempts);
throw new RuntimeException("生成访问令牌失败,请重试");
}
}
@@ -0,0 +1,110 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.zsjy.mapper.ZsjyQrCodeMapper">
<resultMap type="org.dromara.zsjy.domain.vo.ZsjyQrCodeVo" id="ZsjyQrCodeVoResult">
<result property="id" column="id"/>
<result property="codeName" column="code_name"/>
<result property="codeType" column="code_type"/>
<result property="qrContent" column="qr_content"/>
<result property="accessToken" column="access_token"/>
<result property="expireTime" column="expire_time"/>
<result property="maxUsage" column="max_usage"/>
<result property="currentUsage" column="current_usage"/>
<result property="status" column="status"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
</resultMap>
<sql id="selectZsjyQrCodeVo">
select id, code_name, code_type, qr_content, access_token, expire_time,
max_usage, current_usage, status, create_by, create_time,
update_by, update_time
from zsjy_qr_code
</sql>
<!-- 分页查询二维码列表 -->
<select id="selectQrCodePageList" parameterType="org.dromara.zsjy.domain.bo.ZsjyQrCodeBo" resultMap="ZsjyQrCodeVoResult">
<include refid="selectZsjyQrCodeVo"/>
<where>
deleted = 0
<if test="bo.codeName != null and bo.codeName != ''">
AND code_name like concat('%', #{bo.codeName}, '%')
</if>
<if test="bo.codeType != null">
AND code_type = #{bo.codeType}
</if>
<if test="bo.qrContent != null and bo.qrContent != ''">
AND qr_content like concat('%', #{bo.qrContent}, '%')
</if>
<if test="bo.accessToken != null and bo.accessToken != ''">
AND access_token = #{bo.accessToken}
</if>
<if test="bo.expireTime != null">
AND expire_time = #{bo.expireTime}
</if>
<if test="bo.maxUsage != null">
AND max_usage = #{bo.maxUsage}
</if>
<if test="bo.currentUsage != null">
AND current_usage = #{bo.currentUsage}
</if>
<if test="bo.status != null">
AND status = #{bo.status}
</if>
</where>
ORDER BY create_time DESC
</select>
<!-- 查询二维码列表(非分页) -->
<select id="selectQrCodeVoList" parameterType="org.dromara.zsjy.domain.bo.ZsjyQrCodeBo" resultMap="ZsjyQrCodeVoResult">
<include refid="selectZsjyQrCodeVo"/>
<where>
deleted = 0
<if test="codeName != null and codeName != ''">
AND code_name like concat('%', #{codeName}, '%')
</if>
<if test="codeType != null">
AND code_type = #{codeType}
</if>
<if test="qrContent != null and qrContent != ''">
AND qr_content like concat('%', #{qrContent}, '%')
</if>
<if test="accessToken != null and accessToken != ''">
AND access_token = #{accessToken}
</if>
<if test="expireTime != null">
AND expire_time = #{expireTime}
</if>
<if test="maxUsage != null">
AND max_usage = #{maxUsage}
</if>
<if test="currentUsage != null">
AND current_usage = #{currentUsage}
</if>
<if test="status != null">
AND status = #{status}
</if>
</where>
ORDER BY create_time DESC
</select>
<!-- 根据访问令牌查询二维码信息 -->
<select id="selectByAccessToken" parameterType="String" resultMap="ZsjyQrCodeVoResult">
<include refid="selectZsjyQrCodeVo"/>
WHERE access_token = #{accessToken} AND deleted = 0
</select>
<!-- 更新二维码使用次数 -->
<update id="updateUsageCount" parameterType="Long">
UPDATE zsjy_qr_code
SET current_usage = IFNULL(current_usage, 0) + 1,
update_time = NOW()
WHERE id = #{id} AND deleted = 0
</update>
</mapper>
+125
View File
@@ -0,0 +1,125 @@
// 移动端新生报名相关API接口
import { request } from '@/common/request.js'
/**
* 新生报名相关API
*/
export const enrollmentApi = {
/**
* 提交报名信息(供学生端使用)
* @param {Object} data 报名表单数据
*/
submitApplication: (data) => {
return request({
url: '/zsjy/enrollment/info/submit',
method: 'POST',
data: data,
header: {
isToken: false // 不需要登录token,支持对外公开访问
}
})
},
/**
* 查询我的报名状态(供学生端使用)
* @param {Object} params 查询参数 { idCard?, examNumber? }
*/
queryMyStatus: (params) => {
return request({
url: '/zsjy/enrollment/info/myStatus',
method: 'GET',
params: params,
header: {
isToken: false // 不需要登录token,支持对外公开访问
}
})
},
/**
* 根据身份证号和准考证号查询报名信息
* @param {string} idCard 身份证号
* @param {string} examNumber 准考证号
*/
queryByInfo: (idCard, examNumber) => {
return request({
url: '/zsjy/enrollment/info/query',
method: 'GET',
params: {
idCard,
examNumber
},
header: {
isToken: false // 不需要登录token
}
})
},
/**
* 获取学院列表
*/
getCollegeList: () => {
return request({
url: '/zsjy/enrollment/info/colleges',
method: 'GET',
header: {
isToken: false // 不需要登录token,支持对外公开访问
}
})
},
/**
* 根据学院ID获取专业列表
* @param {string|number} collegeId 学院ID
*/
getMajorsByCollegeId: (collegeId) => {
return request({
url: `/zsjy/enrollment/info/majors/${collegeId}`,
method: 'GET',
header: {
isToken: false // 不需要登录token,支持对外公开访问
}
})
}
}
/**
* 便捷方法 - 提交报名申请
* @param {Object} formData 报名表单数据
*/
export const submitEnrollmentApplication = (formData) => {
return enrollmentApi.submitApplication(formData)
}
/**
* 便捷方法 - 查询报名状态
* @param {Object} params 查询参数
*/
export const queryMyEnrollmentStatus = (params) => {
return enrollmentApi.queryMyStatus(params)
}
/**
* 便捷方法 - 根据证件信息查询
* @param {string} idCard 身份证号
* @param {string} examNumber 准考证号
*/
export const queryEnrollmentByInfo = (idCard, examNumber) => {
return enrollmentApi.queryByInfo(idCard, examNumber)
}
/**
* 便捷方法 - 获取学院列表
*/
export const getCollegeList = () => {
return enrollmentApi.getCollegeList()
}
/**
* 便捷方法 - 根据学院ID获取专业列表
* @param {string|number} collegeId 学院ID
*/
export const getMajorsByCollegeId = (collegeId) => {
return enrollmentApi.getMajorsByCollegeId(collegeId)
}
export default enrollmentApi
+7 -3
View File
@@ -1,5 +1,5 @@
// 移动端字典数据获取公共方法 - 适配Vue 2.6.14 + uni-app 2.x
import { get } from './request.js';
import { request } from './request.js';
/**
* 字典数据缓存管理
@@ -76,8 +76,12 @@ const dictCache = {
* @returns {Promise} 字典数据Promise
*/
export const getDicts = (dictType) => {
return get(`/system/dict/data/type/${dictType}`, null, {
isToken: false, // 字典数据不需要token认证
return request({
url: `/system/dict/data/type/${dictType}`,
method: 'GET',
header: {
isToken: false // 字典数据不需要token认证
}
});
};
+11 -5
View File
@@ -34,6 +34,17 @@ function getToken() {
function handleUnauthorized(message = '登录已失效,请重新登录') {
console.log('🔒 处理401未授权响应:', message);
// 获取当前页面路径
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
const currentPath = '/' + currentPage.route;
// 如果是新生报名页面,不跳转到登录页,只是静默处理
if (currentPath === '/pages/enrollment/index') {
console.log('📋 新生报名页面401错误,使用兜底数据,不跳转登录页');
return;
}
// 清除登录信息
try {
uni.removeStorageSync('access_token');
@@ -53,11 +64,6 @@ function handleUnauthorized(message = '登录已失效,请重新登录') {
// 延迟跳转,确保Toast显示
setTimeout(() => {
// 获取当前页面路径
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
const currentPath = '/' + currentPage.route;
// 如果当前不是登录页,则跳转到登录页
if (currentPath !== '/pages/login/index') {
console.log('🔄 从', currentPath, '跳转到登录页');
+7
View File
@@ -215,6 +215,13 @@
"navigationBarBackgroundColor": "#0051d9",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/enrollment/index",
"style": {
"navigationBarTitleText": "新生报名登记",
"navigationStyle": "custom"
}
}
],
"globalStyle": {
File diff suppressed because it is too large. Load diff
+2
View File
@@ -27,6 +27,7 @@
"@fullcalendar/timegrid": "^6.1.18",
"@fullcalendar/vue3": "^6.1.18",
"@highlightjs/vue-plugin": "2.1.0",
"@types/qrcode": "^1.5.5",
"@vueup/vue-quill": "1.2.0",
"@vueuse/core": "13.1.0",
"animate.css": "4.1.1",
@@ -45,6 +46,7 @@
"jspdf-font": "^1.0.7",
"nprogress": "0.2.0",
"pinia": "3.0.2",
"qrcode": "^1.5.4",
"screenfull": "6.0.2",
"vue": "3.5.13",
"vue-cropper": "1.1.1",
+278
View File
@@ -0,0 +1,278 @@
import request from '@/utils/request'
/**
* 二维码管理 VO
*/
export interface QrCodeVO {
/**
* 主键ID
*/
id?: string | number;
/**
* 二维码名称
*/
codeName?: string;
/**
* 二维码类型(1-招生报名)
*/
codeType?: number;
/**
* 二维码内容(URL)
*/
qrContent?: string;
/**
* 访问令牌
*/
accessToken?: string;
/**
* 过期时间
*/
expireTime?: string;
/**
* 最大使用次数(0-无限制)
*/
maxUsage?: number;
/**
* 当前使用次数
*/
currentUsage?: number;
/**
* 状态(0-禁用 1-启用)
*/
status?: number;
/**
* 创建人
*/
createBy?: string | number;
/**
* 创建时间
*/
createTime?: string;
/**
* 更新人
*/
updateBy?: string | number;
/**
* 更新时间
*/
updateTime?: string;
}
/**
* 二维码管理 BO
*/
export interface QrCodeBO {
/**
* 主键ID
*/
id?: string | number;
/**
* 二维码名称
*/
codeName?: string;
/**
* 二维码类型(1-招生报名)
*/
codeType?: number;
/**
* 二维码内容(URL)
*/
qrContent?: string;
/**
* 访问令牌
*/
accessToken?: string;
/**
* 过期时间
*/
expireTime?: string;
/**
* 最大使用次数(0-无限制)
*/
maxUsage?: number;
/**
* 当前使用次数
*/
currentUsage?: number;
/**
* 状态(0-禁用 1-启用)
*/
status?: number;
}
/**
* 二维码查询参数
*/
export interface QrCodeQuery {
/**
* 二维码名称
*/
codeName?: string;
/**
* 二维码类型
*/
codeType?: number;
/**
* 状态
*/
status?: number;
/**
* 创建时间范围
*/
params?: {
beginTime?: string;
endTime?: string;
};
}
/**
* 查询二维码列表
*/
export function listQrCode(query?: QrCodeQuery) {
return request({
url: '/zsjy/qrcode/list',
method: 'get',
params: query
})
}
/**
* 查询二维码详细
*/
export function getQrCode(id: string | number) {
return request({
url: '/zsjy/qrcode/' + id,
method: 'get'
})
}
/**
* 新增二维码
*/
export function addQrCode(data: QrCodeBO) {
return request({
url: '/zsjy/qrcode',
method: 'post',
data: data
})
}
/**
* 修改二维码
*/
export function updateQrCode(data: QrCodeBO) {
return request({
url: '/zsjy/qrcode',
method: 'put',
data: data
})
}
/**
* 删除二维码
*/
export function delQrCode(ids: string | number | Array<string | number>) {
return request({
url: '/zsjy/qrcode/' + ids,
method: 'delete'
})
}
/**
* 导出二维码
*/
export function exportQrCode(query?: QrCodeQuery) {
return request({
url: '/zsjy/qrcode/export',
method: 'post',
data: query,
responseType: 'blob'
})
}
/**
* 验证二维码Token有效性
*/
export function validateToken(token: string) {
return request({
url: '/zsjy/qrcode/validate/' + token,
method: 'get'
})
}
/**
* 根据Token获取二维码信息
*/
export function getQrCodeByToken(token: string) {
return request({
url: '/zsjy/qrcode/token/' + token,
method: 'get'
})
}
/**
* 增加二维码使用次数
*/
export function incrementUsage(id: string | number) {
return request({
url: '/zsjy/qrcode/increment/' + id,
method: 'post'
})
}
/**
* 生成新的访问令牌
*/
export function generateToken() {
return request({
url: '/zsjy/qrcode/generate-token',
method: 'post'
})
}
/**
* 二维码类型选项
*/
export const qrCodeTypeOptions = [
{
label: '招生报名',
value: 1
}
]
/**
* 状态选项
*/
export const statusOptions = [
{
label: '禁用',
value: 0
},
{
label: '启用',
value: 1
}
]
+91 -7
View File
@@ -159,6 +159,20 @@
<el-icon><Star /></el-icon>
报考信息
</h3>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="所属学院" prop="collegeName" required>
<el-select v-model="collegeSelectValue" placeholder="请选择所属学院" style="width: 100%" @change="onCollegeChange">
<el-option v-for="college in collegeOptions" :key="college.deptId" :label="college.deptName" :value="college.deptId" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学院代码" prop="collegeId">
<el-input v-model="applyForm.collegeId" placeholder="学院代码将自动填入" readonly />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="报考专业" prop="appliedMajor" required>
@@ -271,17 +285,20 @@
import { reactive, ref, toRefs, getCurrentInstance, ComponentInternalInstance, onMounted, nextTick } from 'vue';
import { ElForm } from 'element-plus';
import { submitEnrollmentApplication, queryMyEnrollmentStatus } from '@/api/zsjy/enrollment';
import { EnrollmentInfoForm } from '@/api/zsjy/enrollment/types';
import type { EnrollmentInfoForm } from '@/api/zsjy/enrollment/types';
import { listColleges, listMajorsByCollegeId } from '@/api/xg/xsjcxx/classMsg/college';
import { User, School, Star, Document, Check, Refresh } from '@element-plus/icons-vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { zsjy_gender, sys_yes_no, zsjy_major, zsjy_uniform_size } = toRefs<any>(
proxy?.useDict('zsjy_gender', 'sys_yes_no', 'zsjy_major', 'zsjy_uniform_size')
(proxy as any)?.useDict('zsjy_gender', 'sys_yes_no', 'zsjy_major', 'zsjy_uniform_size')
);
const applyFormRef = ref<InstanceType<typeof ElForm>>();
const submitLoading = ref(false);
const queryLoading = ref(false);
const collegeOptions = ref<Array<{ deptId: number; deptName: string; deptCategory: string }>>([]);
const collegeSelectValue = ref<number | undefined>(undefined);
const majorOptions = ref<Array<{ value: string; label: string; majorCode: string }>>([]);
const statusDialog = reactive({
@@ -311,6 +328,8 @@ const applyForm = reactive<EnrollmentInfoForm>({
scienceScore: undefined,
socialScore: undefined,
peScore: undefined,
collegeName: undefined,
collegeId: undefined,
appliedMajor: undefined,
majorCode: undefined,
uniformSize: undefined,
@@ -340,6 +359,7 @@ const rules = {
{ pattern: /^1[3-9]\d{9}$/, message: '手机号码格式不正确', trigger: 'blur' }
],
phone2: [{ pattern: /^1[3-9]\d{9}$/, message: '手机号码格式不正确', trigger: 'blur' }],
collegeName: [{ required: true, message: '所属学院不能为空', trigger: 'change' }],
appliedMajor: [{ required: true, message: '报考专业不能为空', trigger: 'change' }],
totalScoreFull: [{ type: 'number', min: 100, max: 1000, message: '中考满分应在100-1000之间', trigger: 'blur' }],
totalScore: [{ type: 'number', min: 0, message: '中考成绩不能小于0', trigger: 'blur' }],
@@ -383,6 +403,55 @@ const onIdCardChange = () => {
}
};
/** 学院选择变化处理 */
const onCollegeChange = async (selectedCollegeId: number) => {
if (selectedCollegeId) {
// 根据选择的学院ID找到学院对象
const college = collegeOptions.value.find((item) => item.deptId === selectedCollegeId);
if (college) {
// 设置学院信息
applyForm.collegeId = college.deptCategory || college.deptId.toString();
applyForm.collegeName = college.deptName;
// 根据学院加载专业选项
await loadMajorsByCollegeId(selectedCollegeId);
// 清空专业选择
applyForm.appliedMajor = undefined;
applyForm.majorCode = undefined;
console.log('学院选择变化:', {
collegeId: applyForm.collegeId,
collegeName: applyForm.collegeName
});
}
} else {
// 清空学院和专业相关字段
applyForm.collegeName = undefined;
applyForm.collegeId = undefined;
applyForm.appliedMajor = undefined;
applyForm.majorCode = undefined;
majorOptions.value = [];
}
};
/** 根据学院ID加载专业 */
const loadMajorsByCollegeId = async (collegeId: number) => {
try {
const response = await listMajorsByCollegeId(collegeId);
if (response.data) {
majorOptions.value = response.data.map((major: any) => ({
value: major.deptId.toString(),
label: major.deptName,
majorCode: major.deptCategory || major.deptId.toString()
}));
}
} catch (error) {
console.error('加载专业数据失败:', error);
majorOptions.value = [];
}
};
/** 专业选择变化处理 */
const onMajorChange = (selectedMajor: string) => {
const major = majorOptions.value.find((item) => item.label === selectedMajor);
@@ -448,10 +517,10 @@ const submitApplication = () => {
}
await submitEnrollmentApplication(applyForm);
proxy?.$modal.msgSuccess('报名申请提交成功!');
(proxy as any)?.$modal.msgSuccess('报名申请提交成功!');
resetForm();
} catch (error) {
proxy?.$modal.msgError('提交失败,请稍后重试');
(proxy as any)?.$modal.msgError('提交失败,请稍后重试');
} finally {
submitLoading.value = false;
}
@@ -470,7 +539,7 @@ const resetForm = () => {
/** 查询报名状态 */
const queryStatus = async () => {
if (!queryForm.idCard && !queryForm.examNumber) {
proxy?.$modal.msgWarning('请输入身份证号或准考证号');
(proxy as any)?.$modal.msgWarning('请输入身份证号或准考证号');
return;
}
@@ -485,16 +554,31 @@ const queryStatus = async () => {
statusInfo.value = response.data;
statusDialog.visible = true;
} else {
proxy?.$modal.msgWarning('未找到相关报名信息');
(proxy as any)?.$modal.msgWarning('未找到相关报名信息');
}
} catch (error) {
proxy?.$modal.msgError('查询失败,请稍后重试');
(proxy as any)?.$modal.msgError('查询失败,请稍后重试');
} finally {
queryLoading.value = false;
}
};
/** 获取学院选项 */
const getCollegeOptions = async () => {
try {
const response = await listColleges();
if (response.data) {
collegeOptions.value = response.data;
console.log('学院数据加载成功:', collegeOptions.value);
}
} catch (error) {
console.error('学院数据加载失败:', error);
collegeOptions.value = [];
}
};
onMounted(() => {
getCollegeOptions();
getMajorOptions();
});
</script>
+4 -22
View File
@@ -365,15 +365,7 @@
<el-col :span="12">
<el-form-item label="校服尺码" prop="uniformSize">
<el-select v-model="form.uniformSize" placeholder="请选择校服尺码" style="width: 100%">
<el-option value="150/84A" label="150/84A(身高150cm)" />
<el-option value="155/86A" label="155/86A(身高155cm)" />
<el-option value="160/88A" label="160/88A(身高160cm)" />
<el-option value="165/90A" label="165/90A(身高165cm)" />
<el-option value="170/92A" label="170/92A(身高170cm)" />
<el-option value="175/94A" label="175/94A(身高175cm)" />
<el-option value="180/96A" label="180/96A(身高180cm)" />
<el-option value="185/98A" label="185/98A(身高185cm)" />
<el-option value="190/100A" label="190/100A(身高190cm)" />
<el-option v-for="dict in zsjy_uniform_size" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
</el-col>
@@ -1214,18 +1206,7 @@ const onSearchCollegeChange = (selectedCollegeId: string) => {
// 专业选项由学院选择后动态生成
// 校服尺码数据,全局常量
const UNIFORM_SIZES = [
{ value: '150/84A', label: '150/84A(身高150cm)', height: '150cm' },
{ value: '155/86A', label: '155/86A(身高155cm)', height: '155cm' },
{ value: '160/88A', label: '160/88A(身高160cm)', height: '160cm' },
{ value: '165/90A', label: '165/90A(身高165cm)', height: '165cm' },
{ value: '170/92A', label: '170/92A(身高170cm)', height: '170cm' },
{ value: '175/94A', label: '175/94A(身高175cm)', height: '175cm' },
{ value: '180/96A', label: '180/96A(身高180cm)', height: '180cm' },
{ value: '185/98A', label: '185/98A(身高185cm)', height: '185cm' },
{ value: '190/100A', label: '190/100A(身高190cm)', height: '190cm' }
];
// 校服尺码数据使用字典 zsjy_uniform_size
/** 根据学院代码查找学院名称 */
const getCollegeName = (code: string | undefined) => {
@@ -1281,7 +1262,8 @@ const getCollegeNameEnhanced = (student: any) => {
/** 获取校服尺码显示名称 */
const getUniformSizeLabel = (value: string | undefined) => {
if (!value) return '-';
const size = UNIFORM_SIZES.find((item) => item.value === value);
// 使用字典数据查找校服尺码
const size = zsjy_uniform_size.value?.find((item: any) => item.value === value);
return size ? size.label : value;
};
+332
View File
@@ -0,0 +1,332 @@
<template>
<div class="p-2 page-container" v-hasPermi="['zsjy:qrcode:view']">
<!-- 二维码展示卡片 -->
<el-card shadow="never" class="qrcode-card">
<div class="qrcode-content">
<!-- 页面标题 -->
<div class="qrcode-header">
<h2 class="qrcode-title">新生报名二维码</h2>
<p class="qrcode-description">请扫码进行新生报名信息填写</p>
</div>
<!-- 二维码区域 -->
<div class="qrcode-wrapper">
<div class="qrcode-container">
<canvas ref="qrcodeCanvas" class="qrcode-canvas"></canvas>
</div>
<!-- 二维码URL显示 -->
<div class="qrcode-url">
<el-input
v-model="qrcodeUrl"
placeholder="请输入二维码链接地址"
clearable
@change="generateQrcode"
>
<template #prepend>链接地址</template>
</el-input>
</div>
</div>
<!-- 操作按钮 -->
<div class="qrcode-actions">
<el-button
type="primary"
icon="Refresh"
@click="generateQrcode"
:loading="generating"
v-hasPermi="['zsjy:qrcode:view']"
>
重新生成
</el-button>
<el-button
type="success"
icon="Download"
@click="downloadQrcode"
:disabled="!qrcodeGenerated"
v-hasPermi="['zsjy:qrcode:view']"
>
下载二维码
</el-button>
<el-button
type="info"
icon="Printer"
@click="printQrcode"
:disabled="!qrcodeGenerated"
v-hasPermi="['zsjy:qrcode:view']"
>
打印二维码
</el-button>
</div>
</div>
</el-card>
</div>
</template>
<script setup lang="ts" name="QrCode">
import { ref, onMounted, nextTick, getCurrentInstance } from 'vue'
import QRCode from 'qrcode'
import { ElMessage } from 'element-plus'
const { proxy } = getCurrentInstance() as any
// 响应式数据
const qrcodeCanvas = ref<HTMLCanvasElement>()
const qrcodeUrl = ref<string>('') // 二维码链接地址,可手动配置
const generating = ref<boolean>(false)
const qrcodeGenerated = ref<boolean>(false)
// 默认二维码URL(后续可手动配置)
const getDefaultUrl = () => {
// 使用 window.location 来判断环境
const isProduction = window.location.hostname !== 'localhost'
return isProduction
? 'https://domain.com/mobile/#/pages/enrollment/form'
: 'http://localhost:8081/#/pages/enrollment/form'
}
/** 生成二维码 */
const generateQrcode = async () => {
if (!qrcodeUrl.value.trim()) {
ElMessage.warning('请输入有效的链接地址')
return
}
generating.value = true
try {
await nextTick()
if (qrcodeCanvas.value) {
// 生成二维码到canvas
await QRCode.toCanvas(qrcodeCanvas.value, qrcodeUrl.value, {
width: 200,
margin: 2,
color: {
dark: '#000000',
light: '#FFFFFF'
},
errorCorrectionLevel: 'M'
})
qrcodeGenerated.value = true
ElMessage.success('二维码生成成功')
}
} catch (error) {
console.error('生成二维码失败:', error)
ElMessage.error('生成二维码失败,请检查链接地址是否正确')
} finally {
generating.value = false
}
}
/** 下载二维码 */
const downloadQrcode = () => {
if (!qrcodeCanvas.value || !qrcodeGenerated.value) {
ElMessage.warning('请先生成二维码')
return
}
try {
// 创建下载链接
const link = document.createElement('a')
link.download = `新生报到二维码_${new Date().getTime()}.png`
link.href = qrcodeCanvas.value.toDataURL('image/png')
// 触发下载
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
ElMessage.success('二维码下载成功')
} catch (error) {
console.error('下载二维码失败:', error)
ElMessage.error('下载失败')
}
}
/** 打印二维码 */
const printQrcode = () => {
if (!qrcodeCanvas.value || !qrcodeGenerated.value) {
ElMessage.warning('请先生成二维码')
return
}
try {
// 创建打印窗口
const printWindow = window.open('', '_blank')
if (!printWindow) {
ElMessage.error('无法打开打印窗口,请检查浏览器设置')
return
}
// 获取二维码图片数据
const qrcodeDataUrl = qrcodeCanvas.value.toDataURL('image/png')
// 生成打印页面HTML
const scriptTag = 'script'
const printContent = [
'<!DOCTYPE html>',
'<html>',
'<head>',
'<meta charset="utf-8">',
'<title>新生报到二维码</title>',
'<style>',
'body { margin: 0; padding: 20px; text-align: center; font-family: Arial, sans-serif; }',
'.print-header { margin-bottom: 30px; }',
'.print-title { font-size: 24px; font-weight: bold; color: #333; margin-bottom: 10px; }',
'.print-description { font-size: 16px; color: #666; margin-bottom: 20px; }',
'.print-qrcode { margin: 30px 0; }',
'.print-qrcode img { border: 1px solid #ddd; padding: 10px; background: white; }',
'@media print { body { margin: 0; } }',
'</style>',
'</head>',
'<body>',
'<div class="print-header">',
'<h1 class="print-title">新生报到二维码</h1>',
'<p class="print-description">请扫码进行新生报名信息填写</p>',
'</div>',
'<div class="print-qrcode">',
`<img src="${qrcodeDataUrl}" alt="新生报到二维码" />`,
'</div>',
`<${scriptTag}>`,
'window.onload = function() {',
'window.print();',
'window.onafterprint = function() { window.close(); }',
'}',
`</${scriptTag}>`,
'</body>',
'</html>'
].join('')
// 写入打印内容
printWindow.document.write(printContent)
printWindow.document.close()
} catch (error) {
console.error('打印二维码失败:', error)
ElMessage.error('打印失败')
}
}
// 组件挂载后初始化
onMounted(() => {
// 设置默认URL
qrcodeUrl.value = getDefaultUrl()
// 自动生成二维码
nextTick(() => {
generateQrcode()
})
})
</script>
<style lang="scss" scoped>
.page-container {
.qrcode-card {
max-width: 600px;
margin: 0 auto;
.qrcode-content {
text-align: center;
padding: 20px;
}
.qrcode-header {
margin-bottom: 40px;
.qrcode-title {
font-size: 28px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
margin-top: 0;
}
.qrcode-description {
font-size: 16px;
color: #606266;
margin-bottom: 0;
margin-top: 0;
}
}
.qrcode-wrapper {
margin-bottom: 40px;
.qrcode-container {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 20px;
padding: 20px;
background-color: #f8f9fa;
border-radius: 8px;
border: 2px dashed #e4e7ed;
transition: all 0.3s ease;
&:hover {
border-color: #409eff;
background-color: #f0f9ff;
}
.qrcode-canvas {
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
}
.qrcode-url {
max-width: 400px;
margin: 0 auto;
}
}
.qrcode-actions {
display: flex;
justify-content: center;
gap: 16px;
flex-wrap: wrap;
.el-button {
min-width: 120px;
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
}
}
}
}
// 响应式设计
@media (max-width: 768px) {
.page-container {
.qrcode-card {
margin: 0;
.qrcode-content {
padding: 15px;
}
.qrcode-header {
margin-bottom: 30px;
.qrcode-title {
font-size: 24px;
}
.qrcode-description {
font-size: 14px;
}
}
.qrcode-actions {
.el-button {
min-width: 100px;
font-size: 14px;
}
}
}
}
}
</style>