feat(round-15): 跨境多币种发票税额/线索自动化阶段跃升/名片私密访问锁/芯片激光标刻排版 (P1/P2功能达成)

这个提交包含在:
wikiboss
2026-09-21 02:15:08 +08:00
父节点 45f5f74805
当前提交 e6d06f762c
共有 8 个文件被更改,包括 928 次插入 和 0 次删除

查看文件

@@ -0,0 +1,156 @@
import { Provide } from '@midwayjs/core';
import { BaseService } from '@cool-midway/core';
export interface LaserEngravingOptions {
uid: string;
holderName?: string;
company?: string;
cardMaterial?: 'MATTE_BLACK_METAL' | 'GOLD_METAL' | 'PVC_ECO' | string;
}
export interface LaserEngravingElement {
type: 'UID_TEXT' | 'HOLDER_NAME' | 'COMPANY' | 'SECURITY_SERIAL';
text: string;
xMm: number;
yMm: number;
fontHeightMm: number;
}
export interface LaserEngravingVectorSpec {
uid: string;
cardMaterial: string;
laserSettings: {
powerPercent: number;
speedMmPerSec: number;
frequencyKhz: number;
};
elements: LaserEngravingElement[];
}
export interface BatchEngravingJobResult {
jobId: string;
cardCount: number;
specs: LaserEngravingVectorSpec[];
generatedAt: number;
}
/**
* 将 14 位 HEX UID 格式化为大写带冒号分隔 (例如 04:A1:B2:C3:D4:E5:F6)
*/
export function formatUidForLaserEngrave(uid?: string): string {
if (!uid || typeof uid !== 'string') {
throw new Error('UID 不能为空');
}
const clean = uid.replace(/[^0-9a-fA-F]/g, '').toUpperCase();
if (clean.length !== 14) {
throw new Error(`UID 长度必须为 14 位十六进制字符,当前: ${clean.length}`);
}
const parts: string[] = [];
for (let i = 0; i < 14; i += 2) {
parts.push(clean.slice(i, i + 2));
}
return parts.join(':');
}
/**
* 基于 ISO 7064 模 97-10 算法计算 2 位十进制校验码
*/
export function calculateIso7064CheckDigit(uid?: string): string {
if (!uid || typeof uid !== 'string') {
throw new Error('UID 不能为空');
}
const clean = uid.replace(/[^0-9a-fA-F]/g, '').toUpperCase();
if (clean.length !== 14) {
throw new Error('UID 长度必须为 14 位十六进制字符');
}
// 累加 HEX 数值权重模 97
let checksum = 0;
for (let i = 0; i < clean.length; i++) {
const val = parseInt(clean.charAt(i), 16);
checksum = (checksum * 16 + val) % 97;
}
const checkDigit = (98 - ((checksum * 100) % 97)) % 97;
return checkDigit < 10 ? `0${checkDigit}` : String(checkDigit);
}
/**
* 生成激光标刻矢量参数与元素坐标
*/
export function generateLaserEngravingVectorSpec(
options: LaserEngravingOptions
): LaserEngravingVectorSpec {
const formattedUid = formatUidForLaserEngrave(options.uid);
const checkDigit = calculateIso7064CheckDigit(options.uid);
const material = options.cardMaterial || 'MATTE_BLACK_METAL';
// 针对金属与非金属设置差异化激光功率
const isMetal = material.includes('METAL');
const laserSettings = {
powerPercent: isMetal ? 35 : 20,
speedMmPerSec: isMetal ? 1200 : 1800,
frequencyKhz: isMetal ? 30 : 50,
};
const elements: LaserEngravingElement[] = [
{
type: 'UID_TEXT',
text: formattedUid,
xMm: 12.0,
yMm: 45.0,
fontHeightMm: 2.2,
},
{
type: 'SECURITY_SERIAL',
text: `SN-${options.uid.slice(-6).toUpperCase()}-${checkDigit}`,
xMm: 12.0,
yMm: 48.5,
fontHeightMm: 1.8,
},
];
if (options.holderName) {
elements.push({
type: 'HOLDER_NAME',
text: options.holderName,
xMm: 12.0,
yMm: 38.0,
fontHeightMm: 3.5,
});
}
if (options.company) {
elements.push({
type: 'COMPANY',
text: options.company,
xMm: 12.0,
yMm: 41.8,
fontHeightMm: 2.4,
});
}
return {
uid: options.uid,
cardMaterial: material,
laserSettings,
elements,
};
}
/**
* 实体卡芯片激光雕刻排版规范服务
*/
@Provide()
export class NfcEngravingSpecService extends BaseService {
async generateBatchEngravingJob(
items: LaserEngravingOptions[]
): Promise<BatchEngravingJobResult> {
const specs = items.map(generateLaserEngravingVectorSpec);
return {
jobId: `job_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
cardCount: items.length,
specs,
generatedAt: Date.now(),
};
}
}