feat(round-11): 弱网离线队列/跨国会议一键预约/团队多语言名片批量向导/NFC硬件出厂封印 (83套件984测试全绿)

这个提交包含在:
wikiboss
2026-09-21 02:03:13 +08:00
父节点 f46b24a177
当前提交 76f0dc17c0
共有 22 个文件被更改,包括 1488 次插入 和 12 次删除

查看文件

@@ -0,0 +1,212 @@
import { Init, Provide } from '@midwayjs/core';
import { BaseService } from '@cool-midway/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { CardLocaleEntity } from '../entity/locale';
import { CardLocaleFields } from './locale';
export interface BatchLocaleMemberCard {
cardId: number;
baseFields: CardLocaleFields;
avatar?: string;
}
export interface BatchLocaleGenerateParams {
members: BatchLocaleMemberCard[];
targetLocales: string[];
localeTransformer?: (
locale: string,
base: CardLocaleFields
) => CardLocaleFields | Promise<CardLocaleFields>;
}
export interface BatchLocaleResultItem {
cardId: number;
locale: string;
success: boolean;
error?: string;
}
export interface BatchLocaleReport {
totalTasks: number;
successTasks: number;
failedTasks: number;
completionRate: number;
errors: Array<{ cardId: number; locale: string; message: string }>;
results: BatchLocaleResultItem[];
}
export interface LocaleCoverageItem {
locale: string;
coveredCount: number;
coverageRate: number;
missingCardIds: number[];
}
export interface TeamLocaleCoverageReport {
teamMemberCount: number;
locales: Record<string, LocaleCoverageItem>;
}
/**
* 校验多语言文案字段规范
*/
export function validateLocaleFields(fields: CardLocaleFields): {
valid: boolean;
errors: string[];
} {
const errors: string[] = [];
const name = fields?.name != null ? String(fields.name).trim() : '';
if (!name) {
errors.push('姓名不能为空');
} else if (name.length > 64) {
errors.push('姓名长度不能超过64字符');
}
if (fields?.title && String(fields.title).length > 128) {
errors.push('职位长度不能超过128字符');
}
if (fields?.company && String(fields.company).length > 128) {
errors.push('公司名称长度不能超过128字符');
}
if (fields?.slogan && String(fields.slogan).length > 255) {
errors.push('个人标语长度不能超过255字符');
}
return {
valid: errors.length === 0,
errors,
};
}
/**
* 出海团队多语言名片批量生成向导与多语种字段镜像校验服务
*/
@Provide()
export class CardBatchLocaleService extends BaseService {
@InjectEntityModel(CardLocaleEntity)
cardLocaleEntity: Repository<CardLocaleEntity>;
@Init()
async init() {
await super.init();
if (this.cardLocaleEntity) {
this.setEntity(this.cardLocaleEntity);
}
}
/**
* 批量为团队成员生成目标语言分支
*/
async batchGenerateMemberLocales(
params: BatchLocaleGenerateParams
): Promise<BatchLocaleReport> {
const { members, targetLocales, localeTransformer } = params;
const totalTasks = members.length * targetLocales.length;
let successTasks = 0;
let failedTasks = 0;
const errors: Array<{ cardId: number; locale: string; message: string }> = [];
const results: BatchLocaleResultItem[] = [];
for (const member of members) {
for (const locale of targetLocales) {
try {
const transformed = localeTransformer
? await localeTransformer(locale, member.baseFields)
: member.baseFields;
const validation = validateLocaleFields(transformed);
if (!validation.valid) {
throw new Error(validation.errors.join('; '));
}
let existing = await this.cardLocaleEntity.findOneBy({
cardId: member.cardId,
locale,
});
if (existing) {
existing.name = transformed.name;
existing.title = transformed.title;
existing.company = transformed.company;
existing.slogan = transformed.slogan;
await this.cardLocaleEntity.save(existing);
} else {
const created = new CardLocaleEntity();
created.cardId = member.cardId;
created.locale = locale;
created.name = transformed.name;
created.title = transformed.title;
created.company = transformed.company;
created.slogan = transformed.slogan;
await this.cardLocaleEntity.save(created);
}
successTasks++;
results.push({ cardId: member.cardId, locale, success: true });
} catch (err: any) {
failedTasks++;
const msg = err?.message || '生成失败';
errors.push({ cardId: member.cardId, locale, message: msg });
results.push({ cardId: member.cardId, locale, success: false, error: msg });
}
}
}
const completionRate = totalTasks > 0 ? Number((successTasks / totalTasks).toFixed(2)) : 1;
return {
totalTasks,
successTasks,
failedTasks,
completionRate,
errors,
results,
};
}
/**
* 诊断团队名片在各语言下的覆盖率与就绪度
*/
async inspectTeamLocaleCoverage(
cardIds: number[],
targetLocales: string[]
): Promise<TeamLocaleCoverageReport> {
const teamMemberCount = cardIds.length;
const locales: Record<string, LocaleCoverageItem> = {};
for (const loc of targetLocales) {
const missingCardIds: number[] = [];
let coveredCount = 0;
for (const cid of cardIds) {
const found = await this.cardLocaleEntity.findOneBy({
cardId: cid,
locale: loc,
});
if (found) {
coveredCount++;
} else {
missingCardIds.push(cid);
}
}
const rate = teamMemberCount > 0 ? Number((coveredCount / teamMemberCount).toFixed(2)) : 0;
locales[loc] = {
locale: loc,
coveredCount,
coverageRate: rate,
missingCardIds,
};
}
return {
teamMemberCount,
locales,
};
}
}

查看文件

@@ -239,6 +239,94 @@ export function parseIcsEvent(ics: string): Partial<MeetingEventOptions> {
return result;
}
/**
* 日历预约集合契约
*/
export interface CalendarInviteBundle {
googleUrl: string;
outlookUrl: string;
office365Url: string;
icsContent: string;
}
/**
* 生成 Google Calendar 一键预约链接
*/
export function generateGoogleCalendarUrl(meeting: MeetingEventOptions): string {
const summary = meeting.summary || meeting.title || '';
const dtStart = formatIcsDate(meeting.startTime || meeting.start || new Date());
const dtEnd = formatIcsDate(meeting.endTime || meeting.end || new Date());
let details = meeting.description || '';
if (meeting.organizer) {
const orgEmail = typeof meeting.organizer === 'string' ? meeting.organizer : meeting.organizer.email;
if (orgEmail) details += `\nOrganizer: ${orgEmail}`;
}
if (meeting.attendees && meeting.attendees.length > 0) {
details += `\nAttendees: ` + meeting.attendees.map(a => a.email).join(', ');
}
const params = new URLSearchParams({
action: 'TEMPLATE',
text: summary,
dates: `${dtStart}/${dtEnd}`,
details: details.trim(),
location: meeting.location || '',
});
return `https://calendar.google.com/calendar/render?${params.toString()}`;
}
/**
* 格式化 ISO 8601 日期字符串用于 Outlook
*/
function toIsoDate(date: Date | string | number): string {
const d = date instanceof Date ? date : new Date(date);
return d.toISOString();
}
/**
* 生成 Outlook Live (个人版) 一键预约链接
*/
export function generateOutlookCalendarUrl(meeting: MeetingEventOptions): string {
const summary = meeting.summary || meeting.title || '';
const startDt = toIsoDate(meeting.startTime || meeting.start || new Date());
const endDt = toIsoDate(meeting.endTime || meeting.end || new Date());
const params = new URLSearchParams({
path: '/calendar/action/compose',
rru: 'addevent',
subject: summary,
startdt: startDt,
enddt: endDt,
body: meeting.description || '',
location: meeting.location || '',
});
return `https://outlook.live.com/calendar/0/deeplink/compose?${params.toString()}`;
}
/**
* 生成 Office 365 (商业企业版) 一键预约链接
*/
export function generateOffice365CalendarUrl(meeting: MeetingEventOptions): string {
const summary = meeting.summary || meeting.title || '';
const startDt = toIsoDate(meeting.startTime || meeting.start || new Date());
const endDt = toIsoDate(meeting.endTime || meeting.end || new Date());
const params = new URLSearchParams({
path: '/calendar/action/compose',
rru: 'addevent',
subject: summary,
startdt: startDt,
enddt: endDt,
body: meeting.description || '',
location: meeting.location || '',
});
return `https://outlook.office.com/calendar/0/deeplink/compose?${params.toString()}`;
}
/**
* OmniCard 国际商务日程预约与 ICS 日历订阅服务
*/
@@ -285,4 +373,16 @@ export class CardCalendarService extends BaseService {
parseIcsEvent(ics: string): Partial<MeetingEventOptions> {
return parseIcsEvent(ics);
}
/**
* 综合生成 Google, Outlook, Office 365 链接与 RFC 5545 ICS 字符串包
*/
generateCalendarInviteLinks(meeting: MeetingEventOptions): CalendarInviteBundle {
return {
googleUrl: generateGoogleCalendarUrl(meeting),
outlookUrl: generateOutlookCalendarUrl(meeting),
office365Url: generateOffice365CalendarUrl(meeting),
icsContent: generateIcsEvent(meeting),
};
}
}