feat(push-config): 每个厂商卡片内嵌 Profiles,独立保存按钮
- 移除全局"厂商凭据"包裹卡和底部"平台 Profiles"卡 - 每个厂商独立一张卡:凭据 + 通知渠道 Profiles + 保存按钮 - Profiles 按厂商过滤展示,支持在对应厂商卡内添加/删除 - iOS APNs 卡额外显示 Thread ID 和中断级别列 - savingMap 替代独立的 saving ref,各厂商 loading 状态独立 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
父节点
eadd024fc0
当前提交
088e922ec9
@ -33,18 +33,20 @@
|
||||
</el-text>
|
||||
</el-card>
|
||||
|
||||
<el-card style="margin-bottom:16px">
|
||||
<el-card
|
||||
v-for="vendor in filteredVendorDefs"
|
||||
:key="vendor.key"
|
||||
style="margin-bottom:16px"
|
||||
>
|
||||
<template #header>
|
||||
<div style="display:flex;justify-content:space-between;align-items:center">
|
||||
<span>厂商凭据</span>
|
||||
<el-button type="primary" size="small" :loading="savingVendors" @click="saveVendors">保存凭据</el-button>
|
||||
<span>{{ vendor.label }}</span>
|
||||
<el-button type="primary" size="small" :loading="!!savingMap[vendor.key]" @click="saveVendorConfig(vendor.key)">保存</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<div class="vendor-grid">
|
||||
<el-card v-for="vendor in filteredVendorDefs" :key="vendor.key" shadow="never" class="vendor-card">
|
||||
<template #header>{{ vendor.label }}</template>
|
||||
|
||||
<div class="vendor-hint">{{ vendor.hint }}</div>
|
||||
<el-form :label-position="isMobile ? 'top' : 'right'" label-width="110px">
|
||||
<el-form :label-position="isMobile ? 'top' : 'right'" label-width="110px" style="max-width:640px">
|
||||
<el-form-item v-for="field in vendor.fields" :key="field.key" :label="field.label">
|
||||
<el-input
|
||||
v-if="field.type === 'textarea'"
|
||||
@ -64,144 +66,94 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="profiles-header">
|
||||
<span>平台 Profiles</span>
|
||||
<div class="profiles-actions">
|
||||
<el-button size="small" @click="addProfile()">新增通用 profile</el-button>
|
||||
<el-button size="small" @click="addProfile('xiaomi')">新增小米</el-button>
|
||||
<el-button size="small" @click="addProfile('huawei')">新增华为</el-button>
|
||||
<el-button size="small" @click="addProfile('honor')">新增荣耀</el-button>
|
||||
<el-button size="small" @click="addProfile('oppo')">新增 OPPO</el-button>
|
||||
<el-button size="small" @click="addProfile('vivo')">新增 vivo</el-button>
|
||||
<el-button size="small" @click="addProfile('harmony')">新增鸿蒙</el-button>
|
||||
<el-button size="small" @click="addProfile('apns')">新增 iOS</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-divider content-position="left" style="margin:16px 0 12px">
|
||||
<span style="font-size:13px;color:#606266">通知渠道 Profiles</span>
|
||||
<el-button text type="primary" size="small" style="margin-left:8px" @click="addProfile(vendor.key)">+ 添加</el-button>
|
||||
</el-divider>
|
||||
|
||||
<el-alert
|
||||
description="routeType 为空时表示默认兜底 profile。你可以先建 3 条,后续随时补充剩余的 2 条,或者删除不再使用的 profile。"
|
||||
type="info"
|
||||
:closable="false"
|
||||
:show-icon="false"
|
||||
style="margin-bottom:12px"
|
||||
/>
|
||||
|
||||
<el-table :data="pushConfig.profiles" border style="margin-bottom:16px" class="profiles-table">
|
||||
<el-table-column label="启用" width="78" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-switch v-model="row.enabled" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="厂商" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.vendor" filterable>
|
||||
<el-option v-for="vendor in vendorOptions" :key="vendor.value" :label="vendor.label" :value="vendor.value" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="场景 RouteType" min-width="180">
|
||||
<template #default="{ row }">
|
||||
<el-select
|
||||
v-model="row.routeType"
|
||||
filterable
|
||||
allow-create
|
||||
default-first-option
|
||||
placeholder="例如 IM_MESSAGE / SYSTEM_NOTICE"
|
||||
<div v-if="vendorProfilesWithIndex(vendor.key).length === 0" style="padding:4px 0 8px;color:#909399;font-size:13px">
|
||||
暂无 Profiles,点击「+ 添加」创建
|
||||
</div>
|
||||
<el-table
|
||||
v-else
|
||||
:data="vendorProfilesWithIndex(vendor.key)"
|
||||
border
|
||||
size="small"
|
||||
style="margin-bottom:0"
|
||||
>
|
||||
<el-option v-for="route in routeTypeOptions" :key="route" :label="route" :value="route" />
|
||||
<el-table-column label="启用" width="64" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-switch v-model="row.profile.enabled" size="small" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="场景" min-width="170">
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.profile.routeType" filterable allow-create default-first-option size="small" placeholder="IM_MESSAGE / SYSTEM_NOTICE…">
|
||||
<el-option v-for="rt in routeTypeOptions" :key="rt" :label="rt" :value="rt" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Profile Key" min-width="180">
|
||||
<el-table-column label="Channel ID" min-width="150">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.profileKey" placeholder="例如 xiaomi_im_message_v1" />
|
||||
<el-input v-model="row.profile.channelId" size="small" placeholder="通知通道 ID" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Channel ID" min-width="180">
|
||||
<el-table-column label="Category" min-width="130">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.channelId" placeholder="厂商 Channel ID / 通知通道 ID" />
|
||||
<el-input v-model="row.profile.category" size="small" placeholder="消息分类" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Category" min-width="160">
|
||||
<el-table-column label="重要性" width="105">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.category" placeholder="MESSAGE / SYSTEM / ..." />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="重要性" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.importance">
|
||||
<el-select v-model="row.profile.importance" size="small">
|
||||
<el-option v-for="item in importanceOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="优先级" width="120">
|
||||
<el-table-column label="角标" width="58" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.priority">
|
||||
<el-option v-for="item in priorityOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
<el-switch v-model="row.profile.badge" size="small" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="角标" width="78" align="center">
|
||||
<el-table-column label="声音" width="58" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-switch v-model="row.badge" />
|
||||
<el-switch v-model="row.profile.sound" size="small" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="声音" width="78" align="center">
|
||||
<el-table-column label="振动" width="58" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-switch v-model="row.sound" />
|
||||
<el-switch v-model="row.profile.vibration" size="small" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="振动" width="78" align="center">
|
||||
<el-table-column v-if="vendor.key === 'apns'" label="Thread ID" min-width="130">
|
||||
<template #default="{ row }">
|
||||
<el-switch v-model="row.vibration" />
|
||||
<el-input v-model="row.profile.threadIdentifier" size="small" placeholder="iOS thread-id" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Thread ID" min-width="150">
|
||||
<el-table-column v-if="vendor.key === 'apns'" label="中断级别" width="130">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.threadIdentifier" placeholder="iOS thread-id" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="中断级别" min-width="150">
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.interruptionLevel" clearable placeholder="默认">
|
||||
<el-select v-model="row.profile.interruptionLevel" size="small" clearable placeholder="默认">
|
||||
<el-option v-for="item in interruptionOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="NotifyType" width="120">
|
||||
<el-table-column label="备注" min-width="130">
|
||||
<template #default="{ row }">
|
||||
<el-input-number v-model="row.notifyType" :min="0" :max="10" controls-position="right" style="width:100%" />
|
||||
<el-input v-model="row.profile.remark" size="small" placeholder="可选说明" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="版本" width="92">
|
||||
<el-table-column label="" width="56" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-input-number v-model="row.version" :min="1" controls-position="right" style="width:74px" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="可选说明" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="84" fixed="right">
|
||||
<template #default="{ $index }">
|
||||
<el-button link type="danger" @click="removeProfile($index)">删除</el-button>
|
||||
<el-button link type="danger" size="small" @click="removeProfile(row.index)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="toolbar">
|
||||
<el-button @click="reloadConfig" :loading="loading">刷新</el-button>
|
||||
<el-button type="primary" @click="saveProfiles" :loading="savingProfiles">保存 Profiles</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<div style="margin-bottom:24px">
|
||||
<el-button :loading="loading" @click="reloadConfig">刷新配置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -266,8 +218,7 @@ const route = useRoute()
|
||||
const app = ref<App | null>(null)
|
||||
const services = ref<FeatureService[]>([])
|
||||
const loading = ref(false)
|
||||
const savingVendors = ref(false)
|
||||
const savingProfiles = ref(false)
|
||||
const savingMap = reactive<Record<string, boolean>>({})
|
||||
const isMobile = ref(window.innerWidth < 768)
|
||||
const selectedPlatform = ref<'ANDROID' | 'IOS' | 'HARMONY'>('ANDROID')
|
||||
|
||||
@ -562,32 +513,24 @@ function buildPayload(): PushServiceConfig {
|
||||
}
|
||||
}
|
||||
|
||||
async function saveVendors() {
|
||||
async function saveVendorConfig(vendorKey: PushVendorKey) {
|
||||
if (!app.value) return
|
||||
savingVendors.value = true
|
||||
savingMap[vendorKey] = true
|
||||
try {
|
||||
await appApi.updateServiceConfig(app.value.appKey, selectedPlatform.value, 'PUSH', { pushConfig: buildPayload() })
|
||||
ElMessage.success('厂商凭据已保存')
|
||||
ElMessage.success('配置已保存')
|
||||
await loadData()
|
||||
} catch {
|
||||
ElMessage.error('保存失败')
|
||||
} finally {
|
||||
savingVendors.value = false
|
||||
savingMap[vendorKey] = false
|
||||
}
|
||||
}
|
||||
|
||||
async function saveProfiles() {
|
||||
if (!app.value) return
|
||||
savingProfiles.value = true
|
||||
try {
|
||||
await appApi.updateServiceConfig(app.value.appKey, selectedPlatform.value, 'PUSH', { pushConfig: buildPayload() })
|
||||
ElMessage.success('Profiles 已保存')
|
||||
await loadData()
|
||||
} catch {
|
||||
ElMessage.error('保存失败')
|
||||
} finally {
|
||||
savingProfiles.value = false
|
||||
}
|
||||
function vendorProfilesWithIndex(vendorKey: PushVendorKey) {
|
||||
return pushConfig.profiles
|
||||
.map((profile, index) => ({ profile, index }))
|
||||
.filter(({ profile }) => profile.vendor === vendorKey)
|
||||
}
|
||||
|
||||
function addProfile(vendor: PushVendorKey = 'xiaomi') {
|
||||
@ -627,16 +570,6 @@ onBeforeUnmount(() => window.removeEventListener('resize', updateViewport))
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.vendor-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.vendor-card {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.vendor-hint {
|
||||
margin-bottom: 12px;
|
||||
font-size: 12px;
|
||||
@ -644,44 +577,14 @@ onBeforeUnmount(() => window.removeEventListener('resize', updateViewport))
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.profiles-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.profiles-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.profiles-table :deep(.el-select),
|
||||
.profiles-table :deep(.el-input),
|
||||
.profiles-table :deep(.el-input-number) {
|
||||
/* Profile 嵌入表格内的 select / input 撑满列宽 */
|
||||
:deep(.el-table .el-select),
|
||||
:deep(.el-table .el-input) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.vendor-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.toolbar :deep(.el-button),
|
||||
.profiles-actions :deep(.el-button) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.vendor-card :deep(.el-form-item__label) {
|
||||
:deep(.el-form-item__label) {
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
正在加载...
在新工单中引用
屏蔽一个用户