feat: unify app identity on appKey in platforms

这个提交包含在:
XuqmGroup 2026-05-08 10:09:22 +08:00
父节点 6eeea6f268
当前提交 775e6c85e8
共有 10 个文件被更改,包括 31 次插入32 次删除

查看文件

@ -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.idIM 管理页才带 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