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-text>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<el-card style="margin-bottom:16px">
|
<el-card
|
||||||
|
v-for="vendor in filteredVendorDefs"
|
||||||
|
:key="vendor.key"
|
||||||
|
style="margin-bottom:16px"
|
||||||
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div style="display:flex;justify-content:space-between;align-items:center">
|
<div style="display:flex;justify-content:space-between;align-items:center">
|
||||||
<span>厂商凭据</span>
|
<span>{{ vendor.label }}</span>
|
||||||
<el-button type="primary" size="small" :loading="savingVendors" @click="saveVendors">保存凭据</el-button>
|
<el-button type="primary" size="small" :loading="!!savingMap[vendor.key]" @click="saveVendorConfig(vendor.key)">保存</el-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</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>
|
<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-form-item v-for="field in vendor.fields" :key="field.key" :label="field.label">
|
||||||
<el-input
|
<el-input
|
||||||
v-if="field.type === 'textarea'"
|
v-if="field.type === 'textarea'"
|
||||||
@ -64,144 +66,94 @@
|
|||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</el-card>
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
|
|
||||||
<el-card>
|
<el-divider content-position="left" style="margin:16px 0 12px">
|
||||||
<template #header>
|
<span style="font-size:13px;color:#606266">通知渠道 Profiles</span>
|
||||||
<div class="profiles-header">
|
<el-button text type="primary" size="small" style="margin-left:8px" @click="addProfile(vendor.key)">+ 添加</el-button>
|
||||||
<span>平台 Profiles</span>
|
</el-divider>
|
||||||
<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-alert
|
<div v-if="vendorProfilesWithIndex(vendor.key).length === 0" style="padding:4px 0 8px;color:#909399;font-size:13px">
|
||||||
description="routeType 为空时表示默认兜底 profile。你可以先建 3 条,后续随时补充剩余的 2 条,或者删除不再使用的 profile。"
|
暂无 Profiles,点击「+ 添加」创建
|
||||||
type="info"
|
</div>
|
||||||
:closable="false"
|
<el-table
|
||||||
:show-icon="false"
|
v-else
|
||||||
style="margin-bottom:12px"
|
:data="vendorProfilesWithIndex(vendor.key)"
|
||||||
/>
|
border
|
||||||
|
size="small"
|
||||||
<el-table :data="pushConfig.profiles" border style="margin-bottom:16px" class="profiles-table">
|
style="margin-bottom:0"
|
||||||
<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"
|
|
||||||
>
|
>
|
||||||
<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>
|
</el-select>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="Profile Key" min-width="180">
|
<el-table-column label="Channel ID" min-width="150">
|
||||||
<template #default="{ row }">
|
<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>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="Channel ID" min-width="180">
|
<el-table-column label="Category" min-width="130">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-input v-model="row.channelId" placeholder="厂商 Channel ID / 通知通道 ID" />
|
<el-input v-model="row.profile.category" size="small" placeholder="消息分类" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="Category" min-width="160">
|
<el-table-column label="重要性" width="105">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-input v-model="row.category" placeholder="MESSAGE / SYSTEM / ..." />
|
<el-select v-model="row.profile.importance" size="small">
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="重要性" width="120">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-select v-model="row.importance">
|
|
||||||
<el-option v-for="item in importanceOptions" :key="item.value" :label="item.label" :value="item.value" />
|
<el-option v-for="item in importanceOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="优先级" width="120">
|
<el-table-column label="角标" width="58" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-select v-model="row.priority">
|
<el-switch v-model="row.profile.badge" size="small" />
|
||||||
<el-option v-for="item in priorityOptions" :key="item.value" :label="item.label" :value="item.value" />
|
|
||||||
</el-select>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="角标" width="78" align="center">
|
<el-table-column label="声音" width="58" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-switch v-model="row.badge" />
|
<el-switch v-model="row.profile.sound" size="small" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="声音" width="78" align="center">
|
<el-table-column label="振动" width="58" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-switch v-model="row.sound" />
|
<el-switch v-model="row.profile.vibration" size="small" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</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 }">
|
<template #default="{ row }">
|
||||||
<el-switch v-model="row.vibration" />
|
<el-input v-model="row.profile.threadIdentifier" size="small" placeholder="iOS thread-id" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</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 }">
|
<template #default="{ row }">
|
||||||
<el-input v-model="row.threadIdentifier" placeholder="iOS thread-id" />
|
<el-select v-model="row.profile.interruptionLevel" size="small" clearable placeholder="默认">
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="中断级别" min-width="150">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-select v-model="row.interruptionLevel" clearable placeholder="默认">
|
|
||||||
<el-option v-for="item in interruptionOptions" :key="item.value" :label="item.label" :value="item.value" />
|
<el-option v-for="item in interruptionOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="NotifyType" width="120">
|
<el-table-column label="备注" min-width="130">
|
||||||
<template #default="{ row }">
|
<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>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="版本" width="92">
|
<el-table-column label="" width="56" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-input-number v-model="row.version" :min="1" controls-position="right" style="width:74px" />
|
<el-button link type="danger" size="small" @click="removeProfile(row.index)">删除</el-button>
|
||||||
</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>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</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>
|
</el-card>
|
||||||
|
|
||||||
|
<div style="margin-bottom:24px">
|
||||||
|
<el-button :loading="loading" @click="reloadConfig">刷新配置</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -266,8 +218,7 @@ const route = useRoute()
|
|||||||
const app = ref<App | null>(null)
|
const app = ref<App | null>(null)
|
||||||
const services = ref<FeatureService[]>([])
|
const services = ref<FeatureService[]>([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const savingVendors = ref(false)
|
const savingMap = reactive<Record<string, boolean>>({})
|
||||||
const savingProfiles = ref(false)
|
|
||||||
const isMobile = ref(window.innerWidth < 768)
|
const isMobile = ref(window.innerWidth < 768)
|
||||||
const selectedPlatform = ref<'ANDROID' | 'IOS' | 'HARMONY'>('ANDROID')
|
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
|
if (!app.value) return
|
||||||
savingVendors.value = true
|
savingMap[vendorKey] = true
|
||||||
try {
|
try {
|
||||||
await appApi.updateServiceConfig(app.value.appKey, selectedPlatform.value, 'PUSH', { pushConfig: buildPayload() })
|
await appApi.updateServiceConfig(app.value.appKey, selectedPlatform.value, 'PUSH', { pushConfig: buildPayload() })
|
||||||
ElMessage.success('厂商凭据已保存')
|
ElMessage.success('配置已保存')
|
||||||
await loadData()
|
await loadData()
|
||||||
} catch {
|
} catch {
|
||||||
ElMessage.error('保存失败')
|
ElMessage.error('保存失败')
|
||||||
} finally {
|
} finally {
|
||||||
savingVendors.value = false
|
savingMap[vendorKey] = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveProfiles() {
|
function vendorProfilesWithIndex(vendorKey: PushVendorKey) {
|
||||||
if (!app.value) return
|
return pushConfig.profiles
|
||||||
savingProfiles.value = true
|
.map((profile, index) => ({ profile, index }))
|
||||||
try {
|
.filter(({ profile }) => profile.vendor === vendorKey)
|
||||||
await appApi.updateServiceConfig(app.value.appKey, selectedPlatform.value, 'PUSH', { pushConfig: buildPayload() })
|
|
||||||
ElMessage.success('Profiles 已保存')
|
|
||||||
await loadData()
|
|
||||||
} catch {
|
|
||||||
ElMessage.error('保存失败')
|
|
||||||
} finally {
|
|
||||||
savingProfiles.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function addProfile(vendor: PushVendorKey = 'xiaomi') {
|
function addProfile(vendor: PushVendorKey = 'xiaomi') {
|
||||||
@ -627,16 +570,6 @@ onBeforeUnmount(() => window.removeEventListener('resize', updateViewport))
|
|||||||
overflow-x: auto;
|
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 {
|
.vendor-hint {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@ -644,44 +577,14 @@ onBeforeUnmount(() => window.removeEventListener('resize', updateViewport))
|
|||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profiles-header {
|
/* Profile 嵌入表格内的 select / input 撑满列宽 */
|
||||||
display: flex;
|
:deep(.el-table .el-select),
|
||||||
align-items: center;
|
:deep(.el-table .el-input) {
|
||||||
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) {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
@media (max-width: 767px) {
|
||||||
.vendor-grid {
|
:deep(.el-form-item__label) {
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolbar :deep(.el-button),
|
|
||||||
.profiles-actions :deep(.el-button) {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vendor-card :deep(.el-form-item__label) {
|
|
||||||
padding-bottom: 4px;
|
padding-bottom: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户