feat(push): 添加推送服务功能支持

- 新增推送相关的类型定义,包括消息类型、聊天类型、推送配置等接口
- 实现 HarmonyOS 推送 SDK,集成 HarmonyOS NEXT Push Kit 服务
- 实现 iOS 推送 SDK,支持 APNS 推送注册和消息接收
- 添加服务器端 APNS 推送提供商,支持 JWT 认证和推送消息发送
- 添加服务器端 HarmonyOS 推送提供商基础框架
- 集成推送配置加载和路由功能,支持多渠道推送分类管理
这个提交包含在:
XuqmGroup 2026-05-05 22:26:32 +08:00
父节点 edfb3bac2e
当前提交 0f57fe3b71
共有 3 个文件被更改,包括 53 次插入3 次删除

查看文件

@ -46,6 +46,7 @@ declare module 'vue' {
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRow: typeof import('element-plus/es')['ElRow']
ElSegmented: typeof import('element-plus/es')['ElSegmented']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSlider: typeof import('element-plus/es')['ElSlider']
ElSpace: typeof import('element-plus/es')['ElSpace']

查看文件

@ -77,6 +77,7 @@ export interface PushServiceConfig {
oppo?: PushVendorConfig
vivo?: PushVendorConfig
honor?: PushVendorConfig
harmony?: PushVendorConfig
apns?: PushVendorConfig
fcm?: PushVendorConfig
channels?: PushNotificationChannelConfig[]

查看文件

@ -13,6 +13,9 @@
<el-descriptions-item label="服务状态">
<el-tag :type="pushEnabled ? 'success' : 'info'">{{ pushEnabled ? '已开通' : '未开通' }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="配置平台">
<el-segmented v-model="selectedPlatform" :options="platformOptions" @change="applySelectedPlatformConfig" />
</el-descriptions-item>
</el-descriptions>
<div class="push-switch-row">
<el-switch :model-value="pushEnabled" @change="(val: boolean) => onTogglePushService(val)" />
@ -176,6 +179,12 @@ const services = ref<FeatureService[]>([])
const loading = ref(false)
const saving = ref(false)
const isMobile = ref(window.innerWidth < 768)
const selectedPlatform = ref<'ANDROID' | 'IOS' | 'HARMONY'>('ANDROID')
const platformOptions = [
{ label: 'Android', value: 'ANDROID' },
{ label: 'iOS', value: 'IOS' },
{ label: '鸿蒙', value: 'HARMONY' },
]
function updateViewport() {
isMobile.value = window.innerWidth < 768
@ -187,6 +196,7 @@ const pushConfig = reactive<Required<PushServiceConfig>>({
oppo: { appId: '', appKey: '', masterSecret: '' },
vivo: { appId: '', appKey: '', appSecret: '' },
honor: { appId: '', clientId: '', clientSecret: '' },
harmony: { appId: '', appSecret: '' },
apns: { teamId: '', keyId: '', bundleId: '', keyPath: '', sandbox: false },
fcm: { serviceAccountJson: '' },
channels: defaultChannels(),
@ -263,6 +273,15 @@ const vendorDefs: VendorDef[] = [
{ key: 'clientSecret', label: 'ClientSecret' },
],
},
{
key: 'harmony',
label: '鸿蒙 Push Kit',
hint: '填写 HarmonyOS Push Kit AppId / AppSecret。',
fields: [
{ key: 'appId', label: 'AppId' },
{ key: 'appSecret', label: 'AppSecret' },
],
},
{
key: 'apns',
label: 'APNsiOS',
@ -277,8 +296,10 @@ const vendorDefs: VendorDef[] = [
},
]
const pushEnabled = computed(() => services.value.some(s => s.serviceType === 'PUSH' && s.enabled))
const servicePlatform = computed(() => services.value.find(s => s.serviceType === 'PUSH')?.platform ?? 'ANDROID')
const pushEnabled = computed(() => services.value.some(
s => s.serviceType === 'PUSH' && s.platform === selectedPlatform.value && s.enabled,
))
const servicePlatform = computed(() => selectedPlatform.value)
async function loadData() {
const id = route.params.appId as string
@ -288,16 +309,28 @@ async function loadData() {
])
app.value = appRes.data.data
services.value = svcRes.data.data
applyConfig(services.value.find(s => s.serviceType === 'PUSH')?.config)
const firstPushService = services.value.find(s => s.serviceType === 'PUSH')
if (firstPushService && !services.value.some(s => s.serviceType === 'PUSH' && s.platform === selectedPlatform.value)) {
selectedPlatform.value = firstPushService.platform
}
applySelectedPlatformConfig()
}
function applySelectedPlatformConfig() {
applyConfig(services.value.find(
s => s.serviceType === 'PUSH' && s.platform === selectedPlatform.value,
)?.config)
}
function applyConfig(raw?: string | null) {
resetPushConfig()
const parsed = parseConfig(raw)
pushConfig.huawei = { ...pushConfig.huawei, ...parsed.huawei }
pushConfig.xiaomi = { ...pushConfig.xiaomi, ...parsed.xiaomi }
pushConfig.oppo = { ...pushConfig.oppo, ...parsed.oppo }
pushConfig.vivo = { ...pushConfig.vivo, ...parsed.vivo }
pushConfig.honor = { ...pushConfig.honor, ...parsed.honor }
pushConfig.harmony = { ...pushConfig.harmony, ...parsed.harmony }
pushConfig.apns = { ...pushConfig.apns, ...parsed.apns }
pushConfig.fcm = { ...pushConfig.fcm, ...parsed.fcm }
pushConfig.channels = normalizeChannels(parsed.channels)
@ -305,6 +338,19 @@ function applyConfig(raw?: string | null) {
originalChannels.value = pushConfig.channels.map(channel => ({ ...channel }))
}
function resetPushConfig() {
pushConfig.huawei = { appId: '', appSecret: '' }
pushConfig.xiaomi = { appId: '', appKey: '', appSecret: '' }
pushConfig.oppo = { appId: '', appKey: '', masterSecret: '' }
pushConfig.vivo = { appId: '', appKey: '', appSecret: '' }
pushConfig.honor = { appId: '', clientId: '', clientSecret: '' }
pushConfig.harmony = { appId: '', appSecret: '' }
pushConfig.apns = { teamId: '', keyId: '', bundleId: '', keyPath: '', sandbox: false }
pushConfig.fcm = { serviceAccountJson: '' }
pushConfig.channels = defaultChannels()
pushConfig.routing = defaultRouting()
}
function parseConfig(raw?: string | null): PushServiceConfig {
if (!raw) return {}
try {
@ -345,6 +391,8 @@ function toPushConfigRequest(): Record<string, unknown> {
honorAppId: pushConfig.honor.appId ?? '',
honorClientId: pushConfig.honor.clientId ?? '',
honorClientSecret: pushConfig.honor.clientSecret ?? '',
harmonyAppId: pushConfig.harmony.appId ?? '',
harmonyAppSecret: pushConfig.harmony.appSecret ?? '',
apnsTeamId: pushConfig.apns.teamId ?? '',
apnsKeyId: pushConfig.apns.keyId ?? '',
apnsBundleId: pushConfig.apns.bundleId ?? '',