diff --git a/src/api/system/batchEmail.ts b/src/api/system/batchEmail.ts index e8e1f45..9150063 100644 --- a/src/api/system/batchEmail.ts +++ b/src/api/system/batchEmail.ts @@ -167,8 +167,37 @@ export async function sendBatchEmailOne(body: BatchEmailSendOneBody) { assertBusinessOk(res.data) } +export interface BatchEmailDuplicatePair { + to: string + subject: string +} + +export interface BatchEmailDuplicateHit { + toEmail: string + subject: string + sendTime: string + sender: string +} + +function normalizeDuplicateHit(raw: unknown): BatchEmailDuplicateHit | null { + if (raw == null || typeof raw !== 'object') return null + const row = raw as Record + const toEmail = pickStr(row, 'toEmail', 'ToEmail') + if (!toEmail) return null + return { + toEmail, + subject: pickStr(row, 'subject', 'Subject'), + sendTime: pickStr(row, 'sendTime', 'SendTime'), + sender: pickStr(row, 'sender', 'Sender'), + } +} + /** GET /system/batch-email/records/list */ -export async function listBatchEmailRecords(params?: { page?: number; pageSize?: number }) { +export async function listBatchEmailRecords(params?: { + page?: number + pageSize?: number + keyword?: string +}) { const res = await axiosInstance.get(omsUrl('/system/batch-email/records/list'), { params }) const page = unwrapPageResult(res.data) return { @@ -178,3 +207,15 @@ export async function listBatchEmailRecords(params?: { page?: number; pageSize?: total: page.total, } } + +/** POST /system/batch-email/check-duplicates — 近 N 天同邮箱同主题预检 */ +export async function checkBatchEmailDuplicates(body: { + pairs: BatchEmailDuplicatePair[] + withinDays?: number +}): Promise { + const res = await axiosInstance.post(omsUrl('/system/batch-email/check-duplicates'), body) + const list = unwrapList(res.data) + return list + .map(normalizeDuplicateHit) + .filter((item): item is BatchEmailDuplicateHit => item != null) +} diff --git a/src/locale/en-US.ts b/src/locale/en-US.ts index d5510eb..513f5c0 100644 --- a/src/locale/en-US.ts +++ b/src/locale/en-US.ts @@ -4399,11 +4399,24 @@ export default { 'system.batchEmail.errLoadTemplates': 'Failed to load templates', 'system.batchEmail.errLoadRecords': 'Failed to load email records', 'system.batchEmail.recordList': 'Email records', + 'system.batchEmail.recordSearch': 'Search', + 'system.batchEmail.recordSearchPlaceholder': 'Email / subject / body / sender', 'system.batchEmail.record.toEmail': 'Email', 'system.batchEmail.record.subject': 'Subject', 'system.batchEmail.record.content': 'Content', 'system.batchEmail.record.sendTime': 'Sent at', 'system.batchEmail.record.sender': 'Sender', + 'system.batchEmail.errCheckDup': 'Duplicate check failed. Please try again.', + 'system.batchEmail.dupConfirmTitle': 'Duplicates in the last 30 days', + 'system.batchEmail.dupConfirmBody': '{count} item(s) share the same email + subject within {days} days (including in-batch duplicates):', + 'system.batchEmail.dupConfirmMore': '{n} more not shown', + 'system.batchEmail.dupConfirmBatchOnly': '{n} more are duplicates within this batch', + 'system.batchEmail.dupBatchOnlyTip': '{n} duplicate group(s) in this batch — only the first of each email+subject will be sent', + 'system.batchEmail.dupConfirmHint': 'Duplicates are skipped by default. Click “Send anyway” to include them.', + 'system.batchEmail.dupConfirmForce': 'Send anyway', + 'system.batchEmail.dupConfirmSkip': 'Skip duplicates', + 'system.batchEmail.failDupRecent': 'Already sent with the same subject within {days} days', + 'system.batchEmail.failDupInBatch': 'Duplicate in this batch, skipped', 'system.batchEmail.warnEmptyContent': 'Template body is required', 'system.batchEmail.warnEmptyName': 'Template name is required', 'system.batchEmail.confirmOverwriteTitle': 'Overwrite?', diff --git a/src/locale/zh-CN.ts b/src/locale/zh-CN.ts index eb9cb10..9002781 100644 --- a/src/locale/zh-CN.ts +++ b/src/locale/zh-CN.ts @@ -4423,11 +4423,24 @@ export default { 'system.batchEmail.errLoadTemplates': '加载模板失败', 'system.batchEmail.errLoadRecords': '加载邮件记录失败', 'system.batchEmail.recordList': '邮件记录', + 'system.batchEmail.recordSearch': '查询', + 'system.batchEmail.recordSearchPlaceholder': '邮箱 / 主题 / 内容 / 发送人', 'system.batchEmail.record.toEmail': '邮箱', 'system.batchEmail.record.subject': '主题', 'system.batchEmail.record.content': '内容', 'system.batchEmail.record.sendTime': '发送时间', 'system.batchEmail.record.sender': '发送人', + 'system.batchEmail.errCheckDup': '查重失败,请稍后重试', + 'system.batchEmail.dupConfirmTitle': '发现近 30 天重复发送', + 'system.batchEmail.dupConfirmBody': '有 {count} 条「同邮箱 + 同主题」在近 {days} 天内已发送过(含本批重复):', + 'system.batchEmail.dupConfirmMore': '另有 {n} 条未展示', + 'system.batchEmail.dupConfirmBatchOnly': '另有 {n} 条为本批内重复收件', + 'system.batchEmail.dupBatchOnlyTip': '本批有 {n} 组重复收件,同邮箱同主题仅发送首条', + 'system.batchEmail.dupConfirmHint': '默认跳过重复项;若仍要发送,请点「仍要发送」。', + 'system.batchEmail.dupConfirmForce': '仍要发送', + 'system.batchEmail.dupConfirmSkip': '仅发送未重复', + 'system.batchEmail.failDupRecent': '近 {days} 天已发送过同主题', + 'system.batchEmail.failDupInBatch': '本批内重复,已跳过', 'system.batchEmail.warnEmptyContent': '模板内容不能为空', 'system.batchEmail.warnEmptyName': '模板名称不能为空', 'system.batchEmail.confirmOverwriteTitle': '确认覆盖', diff --git a/src/views/site/GradientWaves.vue b/src/views/site/GradientWaves.vue index b68d073..643bcf2 100644 --- a/src/views/site/GradientWaves.vue +++ b/src/views/site/GradientWaves.vue @@ -325,6 +325,8 @@ void main() { canvas.style.height = '100%'; canvas.style.display = 'block'; canvas.style.pointerEvents = 'none'; + canvas.style.opacity = '0'; + canvas.style.transition = 'opacity 0.55s ease'; container.appendChild(canvas); const geometry = new Triangle(gl); @@ -457,6 +459,11 @@ void main() { tryStart(); syncUniforms(); + // 先画一帧再淡入,盖在静态海天底上,避免灰/白闪 + requestAnimationFrame(() => { + paint(performance.now()); + canvas.style.opacity = '1'; + }); teardown = () => { tryStop(); @@ -494,5 +501,13 @@ void main() { display: block; width: 100% !important; height: 100% !important; + will-change: opacity; + } + + @media (prefers-reduced-motion: reduce) { + .gradient-waves :deep(canvas) { + transition: none !important; + opacity: 1 !important; + } } diff --git a/src/views/site/SiteAdvisorShell.vue b/src/views/site/SiteAdvisorShell.vue index c8e5cda..8c20198 100644 --- a/src/views/site/SiteAdvisorShell.vue +++ b/src/views/site/SiteAdvisorShell.vue @@ -40,7 +40,8 @@ as="span" class-name="ai-welcome-title__type" :text="shortTitle ? copy.titleRestShort : copy.titleRest" - :typing-speed="36" + :typing-speed="48" + :initial-delay="280" :pause-duration="900" :show-cursor="false" :loop="false" diff --git a/src/views/site/index.vue b/src/views/site/index.vue index 2467413..de091ca 100644 --- a/src/views/site/index.vue +++ b/src/views/site/index.vue @@ -133,7 +133,7 @@