feat:卡片打印机器初步对接

This commit is contained in:
dzc committed 2026-09-20 15:18:06 +08:00
1 parent 035ae7fbd7
commit b1b67b4dc7
7 files changed
+430

No files matched your search

+48
View File
@@ -0,0 +1,48 @@
-- 饭卡卡面打印:卡面模板表 + 默认种子模板。
-- 本脚本只新增表和种子数据,不修改既有表结构,可安全重跑。
-- 菜单与按钮权限见同目录 20260920_饭卡卡面模板菜单权限.txt,由管理员手工添加。
-- 模板 JSON 由前端可视化设计器生成与消费:background(背景) + elements(元素数组),
-- 元素坐标基于 1014×642 @300dpi 卡面(HiTi CS-2xx 标准),文本 content 支持 {{field}} 动态字段插值。
CREATE TABLE IF NOT EXISTS `pjcloudx_wallet`.`wallet_card_template`
(
`id` bigint NOT NULL COMMENT '主键',
`name` varchar(64) NOT NULL COMMENT '模板名称',
`identity_type` varchar(16) DEFAULT NULL COMMENT '适用身份:STUDENT学生/TEACHER教师,空为通用',
`sides` char(1) NOT NULL DEFAULT '1' COMMENT '打印面数:1单面 2双面',
`front_json` mediumtext COMMENT '正面设计JSON(背景+元素数组)',
`back_json` mediumtext COMMENT '背面设计JSON(背景+元素数组),单面模板为空',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
`create_by` varchar(64) DEFAULT NULL COMMENT '创建人',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT NULL COMMENT '修改人',
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
`del_flag` char(1) DEFAULT '0' COMMENT '删除标记',
`tenant_id` bigint DEFAULT NULL COMMENT '租户ID',
PRIMARY KEY (`id`),
KEY `idx_wallet_card_template_tenant` (`tenant_id`, `identity_type`, `del_flag`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci
COMMENT = '饭卡卡面打印模板';
-- 种子模板:为所有已初始化字典(即已开通)的租户各插一套学生/教师默认模板,按租户+名称幂等。
INSERT INTO `pjcloudx_wallet`.`wallet_card_template`
(`id`, `name`, `identity_type`, `sides`, `front_json`, `back_json`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `tenant_id`)
SELECT (SELECT GREATEST(COALESCE(MAX(`id`), 0), 2026092000000000000) FROM `pjcloudx_wallet`.`wallet_card_template`) + ROW_NUMBER() OVER (ORDER BY tenant.`tenant_id`, item.`sort`),
item.`name`, item.`identity_type`, item.`sides`, item.`front_json`, item.`back_json`, item.`remark`,
'system', NOW(), 'system', NOW(), '0', tenant.`tenant_id`
FROM (SELECT DISTINCT `tenant_id` FROM `pjcloudx`.`sys_dict` WHERE `del_flag` = '0') tenant
JOIN (
SELECT '学生饭卡' AS `name`, 'STUDENT' AS `identity_type`, '2' AS `sides`, 1 AS `sort`,
'学生卡默认模板:姓名/学号/班级/头像' AS `remark`,
'{"background":{"type":"gradient","color":"#4e6ef2","color2":"#2d3a8c","angle":135},"elements":[{"id":"s1","type":"text","x":72,"y":64,"w":560,"h":96,"content":"{{name}}","fontSize":76,"bold":true,"color":"#ffffff","fontFamily":"PingFang SC","align":"left","lineHeight":1.2,"opacity":1,"rotate":0,"resin":false},{"id":"s2","type":"text","x":72,"y":208,"w":560,"h":56,"content":"学号:{{studentNo}}","fontSize":40,"bold":false,"color":"#ffffff","fontFamily":"PingFang SC","align":"left","lineHeight":1.2,"opacity":1,"rotate":0,"resin":false},{"id":"s3","type":"text","x":72,"y":284,"w":560,"h":56,"content":"班级:{{className}}","fontSize":40,"bold":false,"color":"#ffffff","fontFamily":"PingFang SC","align":"left","lineHeight":1.2,"opacity":1,"rotate":0,"resin":false},{"id":"s4","type":"image","x":724,"y":84,"w":216,"h":288,"src":"{{avatar}}","fit":"cover","radius":12,"opacity":1,"rotate":0},{"id":"s5","type":"rect","x":0,"y":492,"w":1014,"h":150,"fill":"rgba(255,255,255,0.14)","radius":0,"opacity":1,"rotate":0},{"id":"s6","type":"text","x":72,"y":545,"w":600,"h":48,"content":"智慧食堂 · 饭卡","fontSize":34,"bold":false,"color":"#ffffff","fontFamily":"PingFang SC","align":"left","lineHeight":1.2,"opacity":1,"rotate":0,"resin":false}]}' AS `front_json`,
'{"background":{"type":"gradient","color":"#2d3a8c","color2":"#4e6ef2","angle":135},"elements":[{"id":"b1","type":"text","x":72,"y":72,"w":400,"h":64,"content":"使用须知","fontSize":48,"bold":true,"color":"#ffffff","fontFamily":"PingFang SC","align":"left","lineHeight":1.2,"opacity":1,"rotate":0,"resin":false},{"id":"b2","type":"text","x":72,"y":170,"w":870,"h":250,"content":"1. 本卡仅限本人使用,请勿转借;\n2. 卡片遗失请及时到管理中心挂失补办;\n3. 请保持卡面清洁,避免弯折、靠近高温。","fontSize":30,"bold":false,"color":"rgba(255,255,255,0.92)","fontFamily":"PingFang SC","align":"left","lineHeight":1.5,"opacity":1,"rotate":0,"resin":false},{"id":"b3","type":"rect","x":0,"y":492,"w":1014,"h":150,"fill":"rgba(255,255,255,0.14)","radius":0,"opacity":1,"rotate":0},{"id":"b4","type":"text","x":72,"y":545,"w":600,"h":48,"content":"卡号:{{cardNumber}}","fontSize":34,"bold":false,"color":"#ffffff","fontFamily":"PingFang SC","align":"left","lineHeight":1.2,"opacity":1,"rotate":0,"resin":false}]}' AS `back_json`
UNION ALL
SELECT '教师饭卡', 'TEACHER', '1', 2,
'教师卡默认模板:校徽/标语/姓名/工号',
'{"background":{"type":"gradient","color":"#0f766e","color2":"#134e4a","angle":135},"elements":[{"id":"t1","type":"image","x":72,"y":64,"w":120,"h":120,"src":"","fit":"contain","radius":0,"opacity":1,"rotate":0},{"id":"t2","type":"text","x":216,"y":92,"w":640,"h":72,"content":"智慧食堂 · 教师卡","fontSize":52,"bold":true,"color":"#ffffff","fontFamily":"PingFang SC","align":"left","lineHeight":1.2,"opacity":1,"rotate":0,"resin":false},{"id":"t3","type":"text","x":72,"y":248,"w":560,"h":64,"content":"{{name}}","fontSize":48,"bold":true,"color":"#ffffff","fontFamily":"PingFang SC","align":"left","lineHeight":1.2,"opacity":1,"rotate":0,"resin":false},{"id":"t4","type":"text","x":72,"y":336,"w":560,"h":56,"content":"工号:{{teacherNo}}","fontSize":40,"bold":false,"color":"#ffffff","fontFamily":"PingFang SC","align":"left","lineHeight":1.2,"opacity":1,"rotate":0,"resin":false},{"id":"t5","type":"rect","x":0,"y":492,"w":1014,"h":150,"fill":"rgba(255,255,255,0.14)","radius":0,"opacity":1,"rotate":0},{"id":"t6","type":"text","x":72,"y":545,"w":640,"h":48,"content":"一餐一饭 皆是心意","fontSize":34,"bold":false,"color":"#ffffff","fontFamily":"PingFang SC","align":"left","lineHeight":1.2,"opacity":1,"rotate":0,"resin":false}]}' AS `front_json`,
NULL AS `back_json`
) item ON item.`name` IS NOT NULL
WHERE NOT EXISTS (
SELECT 1 FROM `pjcloudx_wallet`.`wallet_card_template` t
WHERE t.`tenant_id` = tenant.`tenant_id` AND t.`name` = item.`name` AND t.`del_flag` = '0'
);
@@ -0,0 +1,92 @@
========================================================================
饭卡卡面打印 - 菜单/权限手工添加清单 (2026-09-20)
========================================================================
说明:以下内容需要管理员在对应环境手工添加(UPMS 管理端「菜单管理」或直接执行 SQL)。
SQL 按租户幂等,风格与 db/20260918_hard_coffee_dict_menu.txt 一致。
菜单 parent_id 依赖各环境实际菜单主键:模板页与「钱包开卡」菜单同级
(参照 permission='wallet_walletActive_view' 的菜单取父级),打印按钮挂在「钱包开卡」页面菜单下。
前端页面:
卡面模板管理 src/views/wallet/card-template/index.vue(内嵌可视化设计器)
打印入口 src/views/wallet/wallet-active/index.vue 操作列/工具栏(本机打印助手配合)
------------------------------------------------------------------------
一、页面菜单:卡面模板(与钱包开卡同级)
------------------------------------------------------------------------
INSERT INTO `pjcloudx`.`sys_menu`
(`menu_id`, `name`, `permission`, `path`, `component`, `parent_id`, `icon`, `visible`, `sort_order`, `keep_alive`, `embedded`, `menu_type`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `tenant_id`)
SELECT (SELECT GREATEST(COALESCE(MAX(`menu_id`), 0), 2026092000000000000) FROM `pjcloudx`.`sys_menu`) + ROW_NUMBER() OVER (ORDER BY sibling.`tenant_id`, sibling.`menu_id`),
'卡面模板', 'wallet_cardTemplate_view', '/wallet/card-template', 'wallet/card-template/index',
sibling.`parent_id`, NULL, '1', 50, '0', '0', '0',
'system', NOW(), 'system', NOW(), '0', sibling.`tenant_id`
FROM `pjcloudx`.`sys_menu` sibling
WHERE sibling.`permission` = 'wallet_walletActive_view' AND sibling.`del_flag` = '0'
AND NOT EXISTS (
SELECT 1 FROM `pjcloudx`.`sys_menu` existing
WHERE existing.`tenant_id` = sibling.`tenant_id`
AND existing.`permission` = 'wallet_cardTemplate_view' AND existing.`del_flag` = '0'
);
------------------------------------------------------------------------
二、按钮权限:卡面模板 新增/编辑/删除(挂在卡面模板页面菜单下)
------------------------------------------------------------------------
INSERT INTO `pjcloudx`.`sys_menu`
(`menu_id`, `name`, `permission`, `parent_id`, `visible`, `sort_order`, `keep_alive`, `embedded`, `menu_type`, `create_by`, `create_time`, `del_flag`, `tenant_id`)
SELECT (SELECT GREATEST(COALESCE(MAX(`menu_id`), 0), 2026092000000000000) FROM `pjcloudx`.`sys_menu`) + ROW_NUMBER() OVER (ORDER BY page_menu.`tenant_id`, page_menu.`menu_id`, item.`sort`),
item.`name`, item.`permission`, page_menu.`menu_id`,
'1', item.`sort`, '0', '0', '2', 'system', NOW(), '0', page_menu.`tenant_id`
FROM `pjcloudx`.`sys_menu` page_menu
JOIN (
SELECT '新增模板' AS `name`, 'wallet_cardTemplate_add' AS `permission`, 1 AS `sort`
UNION ALL SELECT '编辑模板', 'wallet_cardTemplate_edit', 2
UNION ALL SELECT '删除模板', 'wallet_cardTemplate_del', 3
) item ON item.`name` IS NOT NULL
WHERE page_menu.`permission` = 'wallet_cardTemplate_view' AND page_menu.`menu_type` = '0' AND page_menu.`del_flag` = '0'
AND NOT EXISTS (
SELECT 1 FROM `pjcloudx`.`sys_menu` existing
WHERE existing.`tenant_id` = page_menu.`tenant_id`
AND existing.`permission` = item.`permission` AND existing.`del_flag` = '0'
);
------------------------------------------------------------------------
三、按钮权限:钱包开卡页「打印卡面」(挂在钱包开卡页面菜单下)
------------------------------------------------------------------------
INSERT INTO `pjcloudx`.`sys_menu`
(`menu_id`, `name`, `permission`, `parent_id`, `visible`, `sort_order`, `keep_alive`, `embedded`, `menu_type`, `create_by`, `create_time`, `del_flag`, `tenant_id`)
SELECT (SELECT GREATEST(COALESCE(MAX(`menu_id`), 0), 2026092000000000000) FROM `pjcloudx`.`sys_menu`) + ROW_NUMBER() OVER (ORDER BY page_menu.`tenant_id`, page_menu.`menu_id`),
'打印卡面', 'wallet_walletActive_print', page_menu.`menu_id`,
'1', 30, '0', '0', '2', 'system', NOW(), '0', page_menu.`tenant_id`
FROM `pjcloudx`.`sys_menu` page_menu
WHERE page_menu.`permission` = 'wallet_walletActive_view' AND page_menu.`menu_type` = '0' AND page_menu.`del_flag` = '0'
AND NOT EXISTS (
SELECT 1 FROM `pjcloudx`.`sys_menu` existing
WHERE existing.`tenant_id` = page_menu.`tenant_id`
AND existing.`permission` = 'wallet_walletActive_print' AND existing.`del_flag` = '0'
);
------------------------------------------------------------------------
四、后端权限字符串与接口对应关系(前端页面取用)
------------------------------------------------------------------------
wallet_cardTemplate_view:
GET /wallet/card-template/page 卡面模板分页(管理页)
wallet_cardTemplate_add:
POST /wallet/card-template 新增卡面模板
wallet_cardTemplate_edit:
PUT /wallet/card-template 修改卡面模板
wallet_cardTemplate_del:
DELETE /wallet/card-template 删除卡面模板
登录即可(无权限注解,打印抽屉取模板用):
GET /wallet/card-template/list 卡面模板全量列表
GET /wallet/card-template/{id} 卡面模板详情
wallet_walletActive_print:
前端按钮权限(v-auth),不对应后端接口;打印数据直接复用开卡列表行数据。
@@ -0,0 +1,113 @@
package com.bsd.pjcloud.wallet.api.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import com.bsd.pjcloud.common.core.util.TenantTable;
import java.time.LocalDateTime;
/**
* 饭卡卡面打印模板
*
* <p>模板为前端可视化设计器产出的结构化 JSON(元素坐标基于 1014×642 @300dpi 卡面),
* 打印时由前端按开卡记录填充动态字段后渲染成图,后端只负责存取。</p>
*
* @author dzc
* @date 2026-09-20
*/
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
@TenantTable
@TableName("wallet_card_template")
@EqualsAndHashCode(callSuper = true)
@Schema(description = "饭卡卡面打印模板")
public class WalletCardTemplateEntity extends Model<WalletCardTemplateEntity> {
/**
* 主键
*/
@TableId(type = IdType.ASSIGN_ID)
@Schema(description = "主键")
private Long id;
/**
* 模板名称
*/
@Schema(description = "模板名称")
private String name;
/**
* 适用身份:STUDENT 学生 / TEACHER 教师,空表示通用
*/
@Schema(description = "适用身份:STUDENT学生/TEACHER教师,空为通用")
private String identityType;
/**
* 打印面数:1 单面 2 双面
*/
@Schema(description = "打印面数:1单面 2双面")
private String sides;
/**
* 正面设计 JSON(背景 + 元素数组)
*/
@Schema(description = "正面设计JSON")
private String frontJson;
/**
* 背面设计 JSON,双面模板才有
*/
@Schema(description = "背面设计JSON")
private String backJson;
/**
* 备注
*/
@Schema(description = "备注")
private String remark;
/**
* 创建人
*/
@TableField(fill = FieldFill.INSERT)
@Schema(description = "创建人")
private String createBy;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
@Schema(description = "创建时间")
private LocalDateTime createTime;
/**
* 修改人
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
@Schema(description = "修改人")
private String updateBy;
/**
* 修改时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
@Schema(description = "修改时间")
private LocalDateTime updateTime;
/**
* 删除标记
*/
@TableLogic
@TableField(fill = FieldFill.INSERT)
@Schema(description = "删除标记")
private String delFlag;
/**
* 租户ID
*/
@Schema(description = "租户ID")
private Long tenantId;
}
@@ -0,0 +1,111 @@
package com.bsd.pjcloud.wallet.controller;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.bsd.pjcloud.common.core.util.R;
import com.bsd.pjcloud.common.log.annotation.SysLog;
import com.bsd.pjcloud.wallet.api.entity.WalletCardTemplateEntity;
import com.bsd.pjcloud.wallet.service.WalletCardTemplateService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.http.HttpHeaders;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 饭卡卡面打印模板
*
* <p>模板 JSON 由前端可视化设计器生成与消费;list/detail 登录即可访问,
* 供开卡打印抽屉在无模板管理权限时也能取模板。</p>
*
* @author dzc
* @date 2026-09-20
*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/card-template")
@Tag(description = "card-template", name = "饭卡卡面打印模板")
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
@Validated
public class WalletCardTemplateController {
private final WalletCardTemplateService walletCardTemplateService;
/**
* 模板全量列表(按更新时间倒序),打印抽屉与设计器下拉使用,登录即可访问。
*/
@GetMapping("/list")
@Operation(summary = "卡面模板列表", description = "登录即可访问,供打印抽屉选择模板")
public R<List<WalletCardTemplateEntity>> list() {
return R.ok(walletCardTemplateService.list(Wrappers.<WalletCardTemplateEntity>lambdaQuery()
.orderByDesc(WalletCardTemplateEntity::getUpdateTime)));
}
/**
* 模板分页,管理页使用。
*/
@GetMapping("/page")
@Operation(summary = "卡面模板分页")
@PreAuthorize("@pms.hasPermission('wallet_cardTemplate_view')")
public R<Page<WalletCardTemplateEntity>> page(@ParameterObject Page page, @ParameterObject WalletCardTemplateEntity entity) {
LambdaQueryWrapper<WalletCardTemplateEntity> wrapper = Wrappers.lambdaQuery();
if (ObjectUtil.isNotEmpty(entity)) {
wrapper.like(ObjectUtil.isNotEmpty(entity.getName()), WalletCardTemplateEntity::getName, entity.getName())
.eq(ObjectUtil.isNotEmpty(entity.getIdentityType()), WalletCardTemplateEntity::getIdentityType,
entity.getIdentityType());
}
wrapper.orderByDesc(WalletCardTemplateEntity::getUpdateTime);
return R.ok(walletCardTemplateService.page(page, wrapper));
}
/**
* 模板详情,设计器编辑回显,登录即可访问。
*/
@GetMapping("/{id}")
@Operation(summary = "卡面模板详情")
public R<WalletCardTemplateEntity> getById(@PathVariable Long id) {
return R.ok(walletCardTemplateService.getById(id));
}
/**
* 新增模板
*/
@PostMapping
@Operation(summary = "新增卡面模板")
@SysLog("新增饭卡卡面模板")
@PreAuthorize("@pms.hasPermission('wallet_cardTemplate_add')")
public R<Boolean> save(@Valid @RequestBody WalletCardTemplateEntity entity) {
return R.ok(walletCardTemplateService.save(entity));
}
/**
* 修改模板
*/
@PutMapping
@Operation(summary = "修改卡面模板")
@SysLog("修改饭卡卡面模板")
@PreAuthorize("@pms.hasPermission('wallet_cardTemplate_edit')")
public R<Boolean> update(@Valid @RequestBody WalletCardTemplateEntity entity) {
return R.ok(walletCardTemplateService.updateById(entity));
}
/**
* 删除模板
*/
@DeleteMapping
@Operation(summary = "删除卡面模板")
@SysLog("删除饭卡卡面模板")
@PreAuthorize("@pms.hasPermission('wallet_cardTemplate_del')")
public R<Boolean> remove(@RequestBody List<Long> ids) {
return R.ok(walletCardTemplateService.removeTemplateByIds(ids));
}
}
@@ -0,0 +1,10 @@
package com.bsd.pjcloud.wallet.mapper;
import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper;
import com.bsd.pjcloud.wallet.api.entity.WalletCardTemplateEntity;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface WalletCardTemplateMapper extends PjcloudBaseMapper<WalletCardTemplateEntity> {
}
@@ -0,0 +1,21 @@
package com.bsd.pjcloud.wallet.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bsd.pjcloud.wallet.api.entity.WalletCardTemplateEntity;
/**
* 饭卡卡面打印模板
*
* @author dzc
* @date 2026-09-20
*/
public interface WalletCardTemplateService extends IService<WalletCardTemplateEntity> {
/**
* 删除前校验模板是否被引用,目前无引用关系,仅保留扩展点。
*
* @param ids 待删除的模板 ID 集合
* @return 是否删除成功
*/
Boolean removeTemplateByIds(java.util.List<Long> ids);
}
@@ -0,0 +1,35 @@
package com.bsd.pjcloud.wallet.service.impl;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bsd.pjcloud.wallet.api.entity.WalletCardTemplateEntity;
import com.bsd.pjcloud.wallet.mapper.WalletCardTemplateMapper;
import com.bsd.pjcloud.wallet.service.WalletCardTemplateService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.List;
/**
* 饭卡卡面打印模板
*
* @author dzc
* @date 2026-09-20
*/
@Validated
@Slf4j
@RequiredArgsConstructor
@Service
public class WalletCardTemplateServiceImpl extends ServiceImpl<WalletCardTemplateMapper, WalletCardTemplateEntity>
implements WalletCardTemplateService {
@Override
public Boolean removeTemplateByIds(List<Long> ids) {
if (CollUtil.isEmpty(ids)) {
return Boolean.FALSE;
}
return removeByIds(ids);
}
}