feat(update): 添加应用更新检查功能支持用户ID参数
- 在UpdateApi接口中新增可选的userId查询参数 - 新增UpdateSDK对象用于统一管理应用更新逻辑 - 实现应用版本检查、下载安装和APK文件处理功能 - 添加下载URL规范化处理逻辑 - 在Flutter SDK中新增update模块实现跨平台更新功能 - 在iOS SDK中新增UpdateSDK类提供应用更新检查接口 - 支持Android和iOS平台的应用商店跳转功能 - 添加React Native SDK的更新检查和插件注册功能 - 实现RN Bundle的检查、下载和缓存机制
这个提交包含在:
父节点
832d180ff3
当前提交
55826db8c4
@ -49,6 +49,25 @@ export interface ServiceRequest {
|
|||||||
reviewNote?: string
|
reviewNote?: string
|
||||||
createdAt: string
|
createdAt: string
|
||||||
reviewedAt?: string
|
reviewedAt?: string
|
||||||
|
tenant?: {
|
||||||
|
id: string
|
||||||
|
username: string
|
||||||
|
nickname: string
|
||||||
|
email: string
|
||||||
|
phone?: string
|
||||||
|
type: 'MAIN' | 'SUB'
|
||||||
|
status: 'ACTIVE' | 'DISABLED' | 'PENDING_EMAIL'
|
||||||
|
} | null
|
||||||
|
app?: {
|
||||||
|
id: string
|
||||||
|
appKey: string
|
||||||
|
name: string
|
||||||
|
packageName: string
|
||||||
|
iosBundleId?: string
|
||||||
|
harmonyBundleName?: string
|
||||||
|
tenantId: string
|
||||||
|
createdAt: string
|
||||||
|
} | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServiceRequestPage {
|
export interface ServiceRequestPage {
|
||||||
|
|||||||
@ -11,6 +11,22 @@
|
|||||||
|
|
||||||
<el-table :data="requests" v-loading="loading">
|
<el-table :data="requests" v-loading="loading">
|
||||||
<el-table-column prop="appKey" label="AppKey" width="180" />
|
<el-table-column prop="appKey" label="AppKey" width="180" />
|
||||||
|
<el-table-column label="租户信息" min-width="220" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div>
|
||||||
|
<div>{{ row.tenant?.nickname || '-' }}</div>
|
||||||
|
<div class="el-text--info" style="font-size:12px">{{ row.tenant?.username || '-' }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="应用信息" min-width="260" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div>
|
||||||
|
<div>{{ row.app?.name || '-' }}</div>
|
||||||
|
<div class="el-text--info" style="font-size:12px">{{ row.app?.packageName || '-' }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<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>
|
||||||
|
|||||||
@ -90,6 +90,7 @@ export interface PublishConfig {
|
|||||||
appKey: string
|
appKey: string
|
||||||
configJson?: string
|
configJson?: string
|
||||||
updatedAt: string
|
updatedAt: string
|
||||||
|
allowAnonymousUpdateCheck?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OperationLog {
|
export interface OperationLog {
|
||||||
|
|||||||
@ -87,7 +87,7 @@
|
|||||||
link type="warning" size="small"
|
link type="warning" size="small"
|
||||||
@click="openPublishDialog(row, 'app')">重新上架</el-button>
|
@click="openPublishDialog(row, 'app')">重新上架</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
v-if="row.publishStatus === 'PUBLISHED'"
|
v-if="row.publishStatus === 'PUBLISHED' && !publishConfigForm.allowAnonymousUpdateCheck"
|
||||||
link type="warning" size="small"
|
link type="warning" size="small"
|
||||||
@click="openGrayDialog(row, 'app')">灰度</el-button>
|
@click="openGrayDialog(row, 'app')">灰度</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
@ -152,7 +152,7 @@
|
|||||||
<template #default="{row}">
|
<template #default="{row}">
|
||||||
<el-button v-if="row.publishStatus === 'DRAFT'" link type="success" size="small" @click="openPublishDialog(row, 'rn')">发布</el-button>
|
<el-button v-if="row.publishStatus === 'DRAFT'" link type="success" size="small" @click="openPublishDialog(row, 'rn')">发布</el-button>
|
||||||
<el-button v-if="row.publishStatus === 'DEPRECATED'" link type="warning" size="small" @click="openPublishDialog(row, 'rn')">重新上架</el-button>
|
<el-button v-if="row.publishStatus === 'DEPRECATED'" link type="warning" size="small" @click="openPublishDialog(row, 'rn')">重新上架</el-button>
|
||||||
<el-button v-if="row.publishStatus === 'PUBLISHED'" link type="warning" size="small" @click="openGrayDialog(row, 'rn')">灰度</el-button>
|
<el-button v-if="row.publishStatus === 'PUBLISHED' && !publishConfigForm.allowAnonymousUpdateCheck" link type="warning" size="small" @click="openGrayDialog(row, 'rn')">灰度</el-button>
|
||||||
<el-button v-if="row.publishStatus === 'PUBLISHED'" link type="danger" size="small" @click="promptUnpublishRn(row.id)">下架</el-button>
|
<el-button v-if="row.publishStatus === 'PUBLISHED'" link type="danger" size="small" @click="promptUnpublishRn(row.id)">下架</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@ -260,36 +260,47 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-form :model="publishConfigForm" label-width="170px" class="release-config-form">
|
<el-form :model="publishConfigForm" label-width="170px" class="release-config-form">
|
||||||
|
<el-form-item label="更新免登录">
|
||||||
|
<el-switch v-model="publishConfigForm.allowAnonymousUpdateCheck" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-alert
|
||||||
|
v-if="publishConfigForm.allowAnonymousUpdateCheck"
|
||||||
|
type="warning"
|
||||||
|
:closable="false"
|
||||||
|
show-icon
|
||||||
|
title="开启后,未登录设备也可以检查更新,但灰度发布相关功能将被禁用。"
|
||||||
|
style="margin-bottom:16px"
|
||||||
|
/>
|
||||||
<el-form-item label="默认灰度模式">
|
<el-form-item label="默认灰度模式">
|
||||||
<el-radio-group v-model="publishConfigForm.grayMode">
|
<el-radio-group v-model="publishConfigForm.grayMode" :disabled="publishConfigForm.allowAnonymousUpdateCheck">
|
||||||
<el-radio-button value="PERCENT">比例</el-radio-button>
|
<el-radio-button value="PERCENT">比例</el-radio-button>
|
||||||
<el-radio-button value="MEMBERS">成员</el-radio-button>
|
<el-radio-button value="MEMBERS">成员</el-radio-button>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="publishConfigForm.grayMode === 'PERCENT'" label="默认灰度比例">
|
<el-form-item v-if="publishConfigForm.grayMode === 'PERCENT'" label="默认灰度比例">
|
||||||
<el-slider v-model="publishConfigForm.defaultGrayPercent" :min="1" :max="100" show-input />
|
<el-slider v-model="publishConfigForm.defaultGrayPercent" :min="1" :max="100" show-input :disabled="publishConfigForm.allowAnonymousUpdateCheck" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<template v-if="publishConfigForm.grayMode === 'MEMBERS'">
|
<template v-if="publishConfigForm.grayMode === 'MEMBERS'">
|
||||||
<el-form-item label="成员选择回调">
|
<el-form-item label="成员选择回调">
|
||||||
<el-input v-model="publishConfigForm.graySelectCallbackUrl" placeholder="选择成员时调用的回调地址" />
|
<el-input v-model="publishConfigForm.graySelectCallbackUrl" placeholder="选择成员时调用的回调地址" :disabled="publishConfigForm.allowAnonymousUpdateCheck" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="成员选择密钥">
|
<el-form-item label="成员选择密钥">
|
||||||
<el-input v-model="publishConfigForm.graySelectCallbackSecret" type="password" show-password placeholder="可选,用于成员选择回调验签" />
|
<el-input v-model="publishConfigForm.graySelectCallbackSecret" type="password" show-password placeholder="可选,用于成员选择回调验签" :disabled="publishConfigForm.allowAnonymousUpdateCheck" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="成员目录同步回调">
|
<el-form-item label="成员目录同步回调">
|
||||||
<el-input v-model="publishConfigForm.grayDirectorySyncCallbackUrl" placeholder="同步所有成员时调用的回调地址" />
|
<el-input v-model="publishConfigForm.grayDirectorySyncCallbackUrl" placeholder="同步所有成员时调用的回调地址" :disabled="publishConfigForm.allowAnonymousUpdateCheck" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="成员目录密钥">
|
<el-form-item label="成员目录密钥">
|
||||||
<el-input v-model="publishConfigForm.grayDirectorySyncCallbackSecret" type="password" show-password placeholder="可选,用于成员同步回调验签" />
|
<el-input v-model="publishConfigForm.grayDirectorySyncCallbackSecret" type="password" show-password placeholder="可选,用于成员同步回调验签" :disabled="publishConfigForm.allowAnonymousUpdateCheck" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="成员选择方式">
|
<el-form-item label="成员选择方式">
|
||||||
<el-radio-group v-model="publishConfigForm.graySelectionSource">
|
<el-radio-group v-model="publishConfigForm.graySelectionSource" :disabled="publishConfigForm.allowAnonymousUpdateCheck">
|
||||||
<el-radio-button value="LOCAL" :disabled="!hasGrayDirectorySyncCallback">同步后本地选择</el-radio-button>
|
<el-radio-button value="LOCAL" :disabled="!hasGrayDirectorySyncCallback">同步后本地选择</el-radio-button>
|
||||||
<el-radio-button value="CALLBACK" :disabled="!hasGraySelectCallback">回调直接返回成员</el-radio-button>
|
<el-radio-button value="CALLBACK" :disabled="!hasGraySelectCallback">回调直接返回成员</el-radio-button>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button @click="syncGrayMembers" :loading="loadingGrayMembers" :disabled="!hasGrayDirectorySyncCallback">同步成员</el-button>
|
<el-button @click="syncGrayMembers" :loading="loadingGrayMembers" :disabled="!hasGrayDirectorySyncCallback || publishConfigForm.allowAnonymousUpdateCheck">同步成员</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</template>
|
</template>
|
||||||
</el-form>
|
</el-form>
|
||||||
@ -700,6 +711,7 @@ const storeConfigs = ref<StoreConfig[]>([])
|
|||||||
const loadingPublishConfig = ref(false)
|
const loadingPublishConfig = ref(false)
|
||||||
const savingPublishConfig = ref(false)
|
const savingPublishConfig = ref(false)
|
||||||
const publishConfigForm = ref({
|
const publishConfigForm = ref({
|
||||||
|
allowAnonymousUpdateCheck: false,
|
||||||
defaultGrayPercent: 0,
|
defaultGrayPercent: 0,
|
||||||
grayMode: 'PERCENT' as GrayMode,
|
grayMode: 'PERCENT' as GrayMode,
|
||||||
graySelectionSource: 'LOCAL' as GraySelectionSource,
|
graySelectionSource: 'LOCAL' as GraySelectionSource,
|
||||||
@ -734,6 +746,7 @@ const loadingOperationLogs = ref(false)
|
|||||||
const hasGraySelectCallback = computed(() => Boolean(publishConfigForm.value.graySelectCallbackUrl.trim()))
|
const hasGraySelectCallback = computed(() => Boolean(publishConfigForm.value.graySelectCallbackUrl.trim()))
|
||||||
const hasGrayDirectorySyncCallback = computed(() => Boolean(publishConfigForm.value.grayDirectorySyncCallbackUrl.trim()))
|
const hasGrayDirectorySyncCallback = computed(() => Boolean(publishConfigForm.value.grayDirectorySyncCallbackUrl.trim()))
|
||||||
const hasAnyGrayCallback = computed(() => hasGraySelectCallback.value || hasGrayDirectorySyncCallback.value)
|
const hasAnyGrayCallback = computed(() => hasGraySelectCallback.value || hasGrayDirectorySyncCallback.value)
|
||||||
|
const allowAnonymousUpdateCheck = computed(() => Boolean(publishConfigForm.value.allowAnonymousUpdateCheck))
|
||||||
|
|
||||||
type FieldDef = { key: string; label: string; type?: 'password' | 'textarea'; placeholder?: string }
|
type FieldDef = { key: string; label: string; type?: 'password' | 'textarea'; placeholder?: string }
|
||||||
type GuideStep = { title: string; description: string }
|
type GuideStep = { title: string; description: string }
|
||||||
@ -1041,6 +1054,7 @@ function normalizePublishConfig(raw: Record<string, unknown> | null | undefined)
|
|||||||
const grayMode = String(raw?.grayMode ?? 'PERCENT') as GrayMode
|
const grayMode = String(raw?.grayMode ?? 'PERCENT') as GrayMode
|
||||||
const graySelectionSource = String(raw?.graySelectionSource ?? 'LOCAL') as GraySelectionSource
|
const graySelectionSource = String(raw?.graySelectionSource ?? 'LOCAL') as GraySelectionSource
|
||||||
return {
|
return {
|
||||||
|
allowAnonymousUpdateCheck: Boolean(raw?.allowAnonymousUpdateCheck ?? raw?.defaultAllowAnonymousUpdateCheck ?? false),
|
||||||
defaultGrayPercent: Number((raw as Record<string, unknown>)?.defaultGrayPercent ?? 0),
|
defaultGrayPercent: Number((raw as Record<string, unknown>)?.defaultGrayPercent ?? 0),
|
||||||
grayMode,
|
grayMode,
|
||||||
graySelectionSource,
|
graySelectionSource,
|
||||||
@ -1067,6 +1081,10 @@ async function loadPublishConfig() {
|
|||||||
try {
|
try {
|
||||||
const res = await updateAdminApi.getPublishConfig(appKey)
|
const res = await updateAdminApi.getPublishConfig(appKey)
|
||||||
publishConfigForm.value = parsePublishConfig(res.data.data.configJson)
|
publishConfigForm.value = parsePublishConfig(res.data.data.configJson)
|
||||||
|
if (allowAnonymousUpdateCheck.value) {
|
||||||
|
publishConfigForm.value.grayMode = 'PERCENT'
|
||||||
|
publishConfigForm.value.graySelectionSource = 'LOCAL'
|
||||||
|
}
|
||||||
if (publishConfigForm.value.grayMode === 'MEMBERS' && !hasAnyGrayCallback.value) {
|
if (publishConfigForm.value.grayMode === 'MEMBERS' && !hasAnyGrayCallback.value) {
|
||||||
publishConfigForm.value.grayMode = 'PERCENT'
|
publishConfigForm.value.grayMode = 'PERCENT'
|
||||||
}
|
}
|
||||||
@ -1090,6 +1108,10 @@ async function savePublishConfig() {
|
|||||||
graySelectCallbackUrl: normalizeCallbackUrl(publishConfigForm.value.graySelectCallbackUrl),
|
graySelectCallbackUrl: normalizeCallbackUrl(publishConfigForm.value.graySelectCallbackUrl),
|
||||||
grayDirectorySyncCallbackUrl: normalizeCallbackUrl(publishConfigForm.value.grayDirectorySyncCallbackUrl),
|
grayDirectorySyncCallbackUrl: normalizeCallbackUrl(publishConfigForm.value.grayDirectorySyncCallbackUrl),
|
||||||
}
|
}
|
||||||
|
if (payload.allowAnonymousUpdateCheck) {
|
||||||
|
payload.grayMode = 'PERCENT'
|
||||||
|
payload.graySelectionSource = 'LOCAL'
|
||||||
|
}
|
||||||
if (payload.grayMode === 'MEMBERS' && !hasAnyGrayCallback.value) {
|
if (payload.grayMode === 'MEMBERS' && !hasAnyGrayCallback.value) {
|
||||||
ElMessage.warning('成员模式至少需要配置一个回调地址')
|
ElMessage.warning('成员模式至少需要配置一个回调地址')
|
||||||
return
|
return
|
||||||
@ -1161,6 +1183,10 @@ const grayForm = ref({
|
|||||||
})
|
})
|
||||||
|
|
||||||
function openGrayDialog(row: { id: string }, type: 'app' | 'rn') {
|
function openGrayDialog(row: { id: string }, type: 'app' | 'rn') {
|
||||||
|
if (allowAnonymousUpdateCheck.value) {
|
||||||
|
ElMessage.warning('当前应用开启了免登录检查更新,灰度发布已禁用')
|
||||||
|
return
|
||||||
|
}
|
||||||
grayTarget.value = { id: row.id, type }
|
grayTarget.value = { id: row.id, type }
|
||||||
grayForm.value = {
|
grayForm.value = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@ -1212,6 +1238,10 @@ async function syncGrayMembers() {
|
|||||||
|
|
||||||
async function submitGray() {
|
async function submitGray() {
|
||||||
if (!grayTarget.value) return
|
if (!grayTarget.value) return
|
||||||
|
if (allowAnonymousUpdateCheck.value) {
|
||||||
|
ElMessage.warning('当前应用开启了免登录检查更新,灰度发布已禁用')
|
||||||
|
return
|
||||||
|
}
|
||||||
if (grayForm.value.enabled && grayForm.value.grayMode === 'MEMBERS' && grayForm.value.selectionSource === 'LOCAL' && !hasGrayDirectorySyncCallback.value) {
|
if (grayForm.value.enabled && grayForm.value.grayMode === 'MEMBERS' && grayForm.value.selectionSource === 'LOCAL' && !hasGrayDirectorySyncCallback.value) {
|
||||||
ElMessage.warning('未配置成员目录同步回调,无法选择本地成员')
|
ElMessage.warning('未配置成员目录同步回调,无法选择本地成员')
|
||||||
return
|
return
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户