feat: unify app identity on appKey in platforms
这个提交包含在:
父节点
6eeea6f268
当前提交
775e6c85e8
@ -15,7 +15,7 @@ const router = createRouter({
|
|||||||
{ path: 'statistics', component: () => import('@/views/statistics/StatisticsView.vue') },
|
{ path: 'statistics', component: () => import('@/views/statistics/StatisticsView.vue') },
|
||||||
{ path: 'service-requests', component: () => import('@/views/services/ServiceRequestsView.vue') },
|
{ path: 'service-requests', component: () => import('@/views/services/ServiceRequestsView.vue') },
|
||||||
{ path: 'apps', component: () => import('@/views/apps/AppListView.vue') },
|
{ path: 'apps', component: () => import('@/views/apps/AppListView.vue') },
|
||||||
{ path: 'apps/:id', component: () => import('@/views/apps/AppDetailView.vue') },
|
{ path: 'apps/:appKey', component: () => import('@/views/apps/AppDetailView.vue') },
|
||||||
{ path: 'push', component: () => import('@/views/push/PushDiagnosticsView.vue') },
|
{ path: 'push', component: () => import('@/views/push/PushDiagnosticsView.vue') },
|
||||||
{ path: 'operation-logs', component: () => import('@/views/logs/OperationLogView.vue') },
|
{ path: 'operation-logs', component: () => import('@/views/logs/OperationLogView.vue') },
|
||||||
{ path: 'risk-control', component: () => import('@/views/risk/RiskControlView.vue') },
|
{ path: 'risk-control', component: () => import('@/views/risk/RiskControlView.vue') },
|
||||||
|
|||||||
@ -61,7 +61,7 @@ const route = useRoute()
|
|||||||
const detail = ref<AppDetail | null>(null)
|
const detail = ref<AppDetail | null>(null)
|
||||||
|
|
||||||
async function loadDetail() {
|
async function loadDetail() {
|
||||||
const res = await opsApi.getApp(route.params.id as string)
|
const res = await opsApi.getApp(route.params.appKey as string)
|
||||||
detail.value = res.data.data
|
detail.value = res.data.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="100" fixed="right">
|
<el-table-column label="操作" width="100" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button link type="primary" @click="$router.push(`/apps/${row.id}`)">
|
<el-button link type="primary" @click="$router.push(`/apps/${row.appKey}`)">
|
||||||
详情
|
详情
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-table :data="requests" v-loading="loading">
|
<el-table :data="requests" v-loading="loading">
|
||||||
<el-table-column prop="appKey" label="AppID" width="180" />
|
<el-table-column prop="appKey" label="AppKey" width="180" />
|
||||||
<el-table-column prop="platform" label="平台" width="100">
|
<el-table-column prop="platform" label="平台" width="100">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag size="small">{{ row.platform }}</el-tag>
|
<el-tag size="small">{{ row.platform }}</el-tag>
|
||||||
|
|||||||
@ -52,7 +52,7 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="100">
|
<el-table-column label="操作" width="100">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button link type="primary" @click="$router.push(`/apps/${row.id}`)">详情</el-button>
|
<el-button link type="primary" @click="$router.push(`/apps/${row.appKey}`)">详情</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|||||||
@ -46,7 +46,7 @@ const router = createRouter({
|
|||||||
component: () => import('@/views/logs/OperationLogView.vue'),
|
component: () => import('@/views/logs/OperationLogView.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'apps/:id',
|
path: 'apps/:appKey',
|
||||||
component: () => import('@/views/apps/AppDetailView.vue'),
|
component: () => import('@/views/apps/AppDetailView.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -36,11 +36,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<template v-if="imService">
|
<template v-if="imService">
|
||||||
<div style="margin-top:10px;display:flex;gap:8px;flex-wrap:wrap">
|
<div style="margin-top:10px;display:flex;gap:8px;flex-wrap:wrap">
|
||||||
<!-- IM 管理页按 appKey 作用域查询,不能把租户 app.id 直接传进去。 -->
|
<el-button size="small" @click="$router.push({ path: `/apps/${app.appKey}/im`, query: { appKey: app.appKey } })">
|
||||||
<el-button size="small" @click="$router.push({ path: `/apps/${route.params.id}/im`, query: { appKey: app.appKey } })">
|
|
||||||
即时通讯管理 →
|
即时通讯管理 →
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button size="small" type="primary" plain @click="$router.push(`/apps/${route.params.id}/im-config`)">
|
<el-button size="small" type="primary" plain @click="$router.push(`/apps/${app.appKey}/im-config`)">
|
||||||
服务配置 →
|
服务配置 →
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
@ -80,7 +79,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="service-actions">
|
<div class="service-actions">
|
||||||
<template v-if="isServiceEnabled('PUSH')">
|
<template v-if="isServiceEnabled('PUSH')">
|
||||||
<el-button size="small" type="primary" plain @click="$router.push(`/apps/${route.params.id}/push-config`)">
|
<el-button size="small" type="primary" plain @click="$router.push(`/apps/${app.appKey}/push-config`)">
|
||||||
推送配置 →
|
推送配置 →
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button size="small" @click="$router.push(`/apps/${app.appKey}/push-management`)">
|
<el-button size="small" @click="$router.push(`/apps/${app.appKey}/push-management`)">
|
||||||
@ -118,7 +117,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="service-actions">
|
<div class="service-actions">
|
||||||
<el-button v-if="isServiceEnabled('UPDATE')" size="small" type="primary" plain @click="$router.push(`/apps/${route.params.id}/update`)">
|
<el-button v-if="isServiceEnabled('UPDATE')" size="small" type="primary" plain @click="$router.push(`/apps/${app.appKey}/update`)">
|
||||||
版本管理 →
|
版本管理 →
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button v-else size="small" type="primary" plain @click="openActivationRequest('UPDATE')">
|
<el-button v-else size="small" type="primary" plain @click="openActivationRequest('UPDATE')">
|
||||||
@ -222,9 +221,9 @@ function getServiceRepresentative(svcType: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadData() {
|
async function loadData() {
|
||||||
const id = route.params.id as string
|
const appKey = route.params.appKey as string
|
||||||
const [appRes, svcRes] = await Promise.all([
|
const [appRes, svcRes] = await Promise.all([
|
||||||
appApi.get(id), appApi.getServices(id),
|
appApi.get(appKey), appApi.getServices(appKey),
|
||||||
])
|
])
|
||||||
app.value = appRes.data.data
|
app.value = appRes.data.data
|
||||||
services.value = svcRes.data.data
|
services.value = svcRes.data.data
|
||||||
@ -239,7 +238,7 @@ async function onToggleService(svcType: string, enable: boolean) {
|
|||||||
type: 'warning', confirmButtonText: '确认关闭', cancelButtonText: '取消',
|
type: 'warning', confirmButtonText: '确认关闭', cancelButtonText: '取消',
|
||||||
})
|
})
|
||||||
const platform = getServiceRepresentative(svcType)?.platform ?? 'ANDROID'
|
const platform = getServiceRepresentative(svcType)?.platform ?? 'ANDROID'
|
||||||
await appApi.toggleService(route.params.id as string, platform, svcType, false)
|
await appApi.toggleService(app.value?.appKey ?? (route.params.appKey as string), platform, svcType, false)
|
||||||
ElMessage.success('已关闭')
|
ElMessage.success('已关闭')
|
||||||
loadData()
|
loadData()
|
||||||
}
|
}
|
||||||
@ -256,7 +255,7 @@ async function submitActivationRequest() {
|
|||||||
}
|
}
|
||||||
submittingActivation.value = true
|
submittingActivation.value = true
|
||||||
try {
|
try {
|
||||||
await client.post(`/apps/${route.params.id}/services/request-activation`, null, {
|
await client.post(`/apps/${app.value?.appKey ?? (route.params.appKey as string)}/services/request-activation`, null, {
|
||||||
params: {
|
params: {
|
||||||
platform: activationForm.value.platform,
|
platform: activationForm.value.platform,
|
||||||
serviceType: activationForm.value.serviceType,
|
serviceType: activationForm.value.serviceType,
|
||||||
@ -280,7 +279,7 @@ function openVerifyDialog(purpose: 'REVEAL_SECRET' | 'RESET_SECRET') {
|
|||||||
async function sendVerifyCode() {
|
async function sendVerifyCode() {
|
||||||
sendingCode.value = true
|
sendingCode.value = true
|
||||||
try {
|
try {
|
||||||
await appApi.requestSecretVerify(route.params.id as string, verifyPurpose.value)
|
await appApi.requestSecretVerify(app.value?.appKey ?? (route.params.appKey as string), verifyPurpose.value)
|
||||||
codeSent.value = true
|
codeSent.value = true
|
||||||
ElMessage.success('验证码已发送')
|
ElMessage.success('验证码已发送')
|
||||||
} catch {
|
} catch {
|
||||||
@ -294,13 +293,13 @@ async function submitVerify() {
|
|||||||
if (verifyCode.value.length !== 6) return ElMessage.warning('请输入6位验证码')
|
if (verifyCode.value.length !== 6) return ElMessage.warning('请输入6位验证码')
|
||||||
submittingVerify.value = true
|
submittingVerify.value = true
|
||||||
try {
|
try {
|
||||||
const id = route.params.id as string
|
const appKey = app.value?.appKey ?? (route.params.appKey as string)
|
||||||
if (verifyPurpose.value === 'REVEAL_SECRET') {
|
if (verifyPurpose.value === 'REVEAL_SECRET') {
|
||||||
const res = await appApi.revealSecret(id, verifyCode.value)
|
const res = await appApi.revealSecret(appKey, verifyCode.value)
|
||||||
revealedSecret.value = res.data.data.appSecret
|
revealedSecret.value = res.data.data.appSecret
|
||||||
ElMessage.success('AppSecret 已显示,请妥善保管')
|
ElMessage.success('AppSecret 已显示,请妥善保管')
|
||||||
} else {
|
} else {
|
||||||
const res = await appApi.resetSecret(id, verifyCode.value)
|
const res = await appApi.resetSecret(appKey, verifyCode.value)
|
||||||
revealedSecret.value = res.data.data.appSecret
|
revealedSecret.value = res.data.data.appSecret
|
||||||
ElMessage.success('AppSecret 已重置,旧密钥立即失效')
|
ElMessage.success('AppSecret 已重置,旧密钥立即失效')
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,8 +17,8 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="180">
|
<el-table-column label="操作" width="180">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button link type="primary" @click="$router.push(`/apps/${row.id}`)">详情</el-button>
|
<el-button link type="primary" @click="$router.push(`/apps/${row.appKey}`)">详情</el-button>
|
||||||
<el-button link type="danger" @click="handleDelete(row.id)">删除</el-button>
|
<el-button link type="danger" @click="handleDelete(row.appKey)">删除</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@ -93,9 +93,9 @@ async function handleCreate() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleDelete(id: string) {
|
async function handleDelete(appKey: string) {
|
||||||
await ElMessageBox.confirm('确定删除此应用?删除后不可恢复。', '警告', { type: 'warning' })
|
await ElMessageBox.confirm('确定删除此应用?删除后不可恢复。', '警告', { type: 'warning' })
|
||||||
await appApi.delete(id)
|
await appApi.delete(appKey)
|
||||||
ElMessage.success('已删除')
|
ElMessage.success('已删除')
|
||||||
loadApps()
|
loadApps()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,7 +23,7 @@
|
|||||||
<el-switch :model-value="imEnabled" @change="(val: boolean) => onToggleImService(val)" />
|
<el-switch :model-value="imEnabled" @change="(val: boolean) => onToggleImService(val)" />
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-top:10px;display:flex;gap:8px;flex-wrap:wrap">
|
<div style="margin-top:10px;display:flex;gap:8px;flex-wrap:wrap">
|
||||||
<!-- 服务配置页使用的是租户应用主键 app.id;IM 管理页才带 appKey。 -->
|
<!-- 服务配置页和 IM 管理页现在都按 appKey 作为应用唯一标识。 -->
|
||||||
<el-button size="small" @click="$router.push({ path: `/apps/${route.params.appKey}/im`, query: { appKey: app.appKey } })">
|
<el-button size="small" @click="$router.push({ path: `/apps/${route.params.appKey}/im`, query: { appKey: app.appKey } })">
|
||||||
即时通讯管理 →
|
即时通讯管理 →
|
||||||
</el-button>
|
</el-button>
|
||||||
|
|||||||
@ -59,7 +59,7 @@
|
|||||||
<el-tab-pane label="版本管理" name="UPDATE">
|
<el-tab-pane label="版本管理" name="UPDATE">
|
||||||
<div class="toolbar responsive-toolbar">
|
<div class="toolbar responsive-toolbar">
|
||||||
<el-select
|
<el-select
|
||||||
v-model="updateAppId"
|
v-model="updateAppKey"
|
||||||
placeholder="选择应用"
|
placeholder="选择应用"
|
||||||
style="width: 320px"
|
style="width: 320px"
|
||||||
filterable
|
filterable
|
||||||
@ -67,9 +67,9 @@
|
|||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="app in apps"
|
v-for="app in apps"
|
||||||
:key="app.id"
|
:key="app.appKey"
|
||||||
:label="`${app.name} · ${app.packageName}`"
|
:label="`${app.name} · ${app.packageName}`"
|
||||||
:value="app.id"
|
:value="app.appKey"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
<el-input-number v-model="updateLimit" :min="20" :max="200" :step="10" controls-position="right" />
|
<el-input-number v-model="updateLimit" :min="20" :max="200" :step="10" controls-position="right" />
|
||||||
@ -125,7 +125,7 @@ const updateLoading = ref(false)
|
|||||||
const updateLogs = ref<UpdateOperationLog[]>([])
|
const updateLogs = ref<UpdateOperationLog[]>([])
|
||||||
const updateLimit = ref(100)
|
const updateLimit = ref(100)
|
||||||
const apps = ref<App[]>([])
|
const apps = ref<App[]>([])
|
||||||
const updateAppId = ref('')
|
const updateAppKey = ref('')
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
@ -148,8 +148,8 @@ async function loadApps() {
|
|||||||
try {
|
try {
|
||||||
const res = await appApi.list()
|
const res = await appApi.list()
|
||||||
apps.value = res.data.data
|
apps.value = res.data.data
|
||||||
if (!updateAppId.value && apps.value.length) {
|
if (!updateAppKey.value && apps.value.length) {
|
||||||
updateAppId.value = apps.value[0].id
|
updateAppKey.value = apps.value[0].appKey
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// ignore; empty state will be shown in the selector
|
// ignore; empty state will be shown in the selector
|
||||||
@ -173,13 +173,13 @@ async function loadTenantLogs() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadUpdateLogs() {
|
async function loadUpdateLogs() {
|
||||||
if (!updateAppId.value) {
|
if (!updateAppKey.value) {
|
||||||
updateLogs.value = []
|
updateLogs.value = []
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
updateLoading.value = true
|
updateLoading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await updateAdminApi.listOperationLogs(updateAppId.value, updateLimit.value)
|
const res = await updateAdminApi.listOperationLogs(updateAppKey.value, updateLimit.value)
|
||||||
updateLogs.value = res.data.data ?? []
|
updateLogs.value = res.data.data ?? []
|
||||||
} finally {
|
} finally {
|
||||||
updateLoading.value = false
|
updateLoading.value = false
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户