重构收件人录入与邮箱配置弹窗,修复账号接口解析;支持脱敏密码复用后直接测试与发送。 Co-authored-by: Cursor <cursoragent@cursor.com>
181 lines
6.0 KiB
TypeScript
181 lines
6.0 KiB
TypeScript
import { axiosInstance } from '@/api/rbac'
|
|
import { assertBusinessOk, omsUrl, unwrapData } from '@/api/oms/common'
|
|
|
|
/** 与桌面版 emailAuto.py 一致:阿里企业邮等 */
|
|
export type BatchEmailServerType = 'qiye' | 'qiye_ssl' | 'aliyun' | 'aliyun_ssl' | 'mxhichina'
|
|
|
|
/** 与后端 BatchEmailSmtpHelper.SecretMask 一致,用于线上已保存密码占位 */
|
|
export const SMTP_SECRET_MASK = '******'
|
|
|
|
export function isSmtpSecretMask(value?: string | null): boolean {
|
|
return String(value ?? '').trim() === SMTP_SECRET_MASK
|
|
}
|
|
|
|
export interface BatchEmailTemplate {
|
|
name: string
|
|
subject: string
|
|
content: string
|
|
}
|
|
|
|
export interface BatchEmailAccount {
|
|
smtpUsername: string
|
|
smtpPassword: string
|
|
serverType: BatchEmailServerType
|
|
fromName?: string
|
|
}
|
|
|
|
export interface BatchEmailSendOneBody {
|
|
smtpUsername: string
|
|
smtpPassword: string
|
|
serverType?: BatchEmailServerType
|
|
to: string
|
|
subject: string
|
|
body: string
|
|
bodyType?: 'plain' | 'html'
|
|
fromName?: string
|
|
}
|
|
|
|
function pickStr(row: Record<string, unknown>, ...keys: string[]) {
|
|
for (const key of keys) {
|
|
const val = row[key]
|
|
if (val != null && String(val).trim() !== '') return String(val).trim()
|
|
}
|
|
return ''
|
|
}
|
|
|
|
function normalizeTemplate(raw: unknown): BatchEmailTemplate | null {
|
|
if (raw == null || typeof raw !== 'object') return null
|
|
const row = raw as Record<string, unknown>
|
|
const name = pickStr(row, 'name', 'Name')
|
|
if (!name) return null
|
|
return {
|
|
name,
|
|
subject: pickStr(row, 'subject', 'Subject') || name,
|
|
content: pickStr(row, 'content', 'Content'),
|
|
}
|
|
}
|
|
|
|
function normalizeAccount(raw: unknown): BatchEmailAccount | null {
|
|
if (raw == null || typeof raw !== 'object') return null
|
|
const row = raw as Record<string, unknown>
|
|
const smtpUsername = pickStr(row, 'smtpUsername', 'SmtpUsername')
|
|
if (!smtpUsername) return null
|
|
const serverType = (pickStr(row, 'serverType', 'ServerType') || 'qiye') as BatchEmailServerType
|
|
return {
|
|
smtpUsername,
|
|
smtpPassword: pickStr(row, 'smtpPassword', 'SmtpPassword'),
|
|
serverType,
|
|
fromName: pickStr(row, 'fromName', 'FromName') || undefined,
|
|
}
|
|
}
|
|
|
|
export interface BatchEmailSendRecord {
|
|
id: number
|
|
toEmail: string
|
|
subject: string
|
|
content: string
|
|
sendTime: string
|
|
sender: string
|
|
}
|
|
|
|
function normalizeRecord(raw: unknown): BatchEmailSendRecord | null {
|
|
if (raw == null || typeof raw !== 'object') return null
|
|
const row = raw as Record<string, unknown>
|
|
const id = Number(row.id ?? row.Id)
|
|
if (!Number.isFinite(id)) return null
|
|
return {
|
|
id,
|
|
toEmail: pickStr(row, 'toEmail', 'ToEmail'),
|
|
subject: pickStr(row, 'subject', 'Subject'),
|
|
content: pickStr(row, 'content', 'Content'),
|
|
sendTime: pickStr(row, 'sendTime', 'SendTime'),
|
|
sender: pickStr(row, 'sender', 'Sender'),
|
|
}
|
|
}
|
|
|
|
function unwrapPageResult(resBody: unknown): { items: unknown[]; total: number } {
|
|
assertBusinessOk(resBody)
|
|
const root = (resBody as Record<string, unknown>) ?? {}
|
|
const inner = (root.data ?? root.Data ?? root) as Record<string, unknown>
|
|
const items = inner.items ?? inner.Items ?? inner.list ?? inner.List ?? []
|
|
const total = Number(inner.total ?? inner.Total ?? inner.totalCount ?? inner.TotalCount ?? 0)
|
|
return {
|
|
items: Array.isArray(items) ? items : [],
|
|
total: Number.isFinite(total) ? total : 0,
|
|
}
|
|
}
|
|
|
|
function unwrapList(resBody: unknown): unknown[] {
|
|
assertBusinessOk(resBody)
|
|
const root = (resBody as Record<string, unknown>) ?? {}
|
|
const inner = root.data ?? root.Data ?? root
|
|
if (Array.isArray(inner)) return inner
|
|
return unwrapPageResult(resBody).items
|
|
}
|
|
|
|
/** GET /system/batch-email/account */
|
|
export async function getBatchEmailAccount(): Promise<BatchEmailAccount | null> {
|
|
const res = await axiosInstance.get(omsUrl('/system/batch-email/account'))
|
|
const raw = unwrapData<Record<string, unknown> | null>(res.data)
|
|
return raw ? normalizeAccount(raw) : null
|
|
}
|
|
|
|
/** GET /system/batch-email/templates/list */
|
|
export async function listBatchEmailTemplates(): Promise<BatchEmailTemplate[]> {
|
|
const res = await axiosInstance.get(omsUrl('/system/batch-email/templates/list'))
|
|
return unwrapList(res.data)
|
|
.map(normalizeTemplate)
|
|
.filter((item): item is BatchEmailTemplate => item != null)
|
|
}
|
|
|
|
/** POST /system/batch-email/templates/save */
|
|
export async function saveBatchEmailTemplate(body: {
|
|
name: string
|
|
subject: string
|
|
content: string
|
|
}) {
|
|
const res = await axiosInstance.post(omsUrl('/system/batch-email/templates/save'), body)
|
|
assertBusinessOk(res.data)
|
|
}
|
|
|
|
/** POST /system/batch-email/templates/delete */
|
|
export async function deleteBatchEmailTemplate(body: { name: string }) {
|
|
const res = await axiosInstance.post(omsUrl('/system/batch-email/templates/delete'), body)
|
|
assertBusinessOk(res.data)
|
|
}
|
|
|
|
/** POST /system/batch-email/account/save */
|
|
export async function saveBatchEmailAccount(body: BatchEmailAccount) {
|
|
const res = await axiosInstance.post(omsUrl('/system/batch-email/account/save'), body)
|
|
assertBusinessOk(res.data)
|
|
}
|
|
|
|
/** POST /system/batch-email/test-connection — 验证 SMTP 账号 */
|
|
export async function testBatchEmailConnection(body: {
|
|
smtpUsername: string
|
|
smtpPassword: string
|
|
serverType?: BatchEmailServerType
|
|
fromName?: string
|
|
}) {
|
|
const res = await axiosInstance.post(omsUrl('/system/batch-email/test-connection'), body)
|
|
assertBusinessOk(res.data)
|
|
}
|
|
|
|
/** POST /system/batch-email/send-one — 发送单封(批量时由前端循环调用,便于逐条写日志) */
|
|
export async function sendBatchEmailOne(body: BatchEmailSendOneBody) {
|
|
const res = await axiosInstance.post(omsUrl('/system/batch-email/send-one'), body)
|
|
assertBusinessOk(res.data)
|
|
}
|
|
|
|
/** GET /system/batch-email/records/list */
|
|
export async function listBatchEmailRecords(params?: { page?: number; pageSize?: number }) {
|
|
const res = await axiosInstance.get(omsUrl('/system/batch-email/records/list'), { params })
|
|
const page = unwrapPageResult(res.data)
|
|
return {
|
|
items: page.items
|
|
.map(normalizeRecord)
|
|
.filter((item): item is BatchEmailSendRecord => item != null),
|
|
total: page.total,
|
|
}
|
|
}
|