2026-04-24 16:16:54 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<el-page-header @back="$router.back()" :content="`版本管理 — ${appId}`" style="margin-bottom:20px" />
|
|
|
|
|
|
|
|
|
|
|
|
<el-card>
|
2026-04-28 21:05:07 +08:00
|
|
|
|
<div class="toolbar" style="margin-bottom: 16px">
|
|
|
|
|
|
<el-button type="primary" @click="showUnifiedUpload = true">一键上传</el-button>
|
|
|
|
|
|
</div>
|
2026-04-24 16:16:54 +08:00
|
|
|
|
<el-tabs v-model="activeTab">
|
|
|
|
|
|
<!-- App Versions -->
|
|
|
|
|
|
<el-tab-pane label="App 整包版本" name="app">
|
|
|
|
|
|
<div class="toolbar">
|
|
|
|
|
|
<el-radio-group v-model="appPlatform" @change="loadAppVersions" style="margin-right:12px">
|
|
|
|
|
|
<el-radio-button value="ANDROID">Android</el-radio-button>
|
|
|
|
|
|
<el-radio-button value="IOS">iOS</el-radio-button>
|
|
|
|
|
|
</el-radio-group>
|
|
|
|
|
|
<el-button type="primary" @click="showUploadApp = true">上传新版本</el-button>
|
|
|
|
|
|
<el-button @click="loadAppVersions" :loading="loadingApp">刷新</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<el-table :data="appVersions" v-loading="loadingApp" border stripe>
|
|
|
|
|
|
<el-table-column prop="versionName" label="版本名" width="120" />
|
|
|
|
|
|
<el-table-column prop="versionCode" label="版本码" width="100" />
|
|
|
|
|
|
<el-table-column label="状态" width="120">
|
|
|
|
|
|
<template #default="{row}">
|
|
|
|
|
|
<el-tag :type="statusTagType(row)" size="small">{{ statusLabel(row) }}</el-tag>
|
|
|
|
|
|
<el-tag v-if="row.grayEnabled" type="warning" size="small" style="margin-left:4px">
|
|
|
|
|
|
灰度 {{ row.grayPercent }}%
|
|
|
|
|
|
</el-tag>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="forceUpdate" label="强制更新" width="100">
|
|
|
|
|
|
<template #default="{row}">
|
|
|
|
|
|
<el-tag :type="row.forceUpdate ? 'danger' : 'info'" size="small">
|
|
|
|
|
|
{{ row.forceUpdate ? '是' : '否' }}
|
|
|
|
|
|
</el-tag>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="changeLog" label="更新说明" show-overflow-tooltip />
|
|
|
|
|
|
<el-table-column prop="createdAt" label="上传时间" width="180">
|
|
|
|
|
|
<template #default="{row}">{{ formatTime(row.createdAt) }}</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column label="操作" width="200" fixed="right">
|
|
|
|
|
|
<template #default="{row}">
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
v-if="row.publishStatus === 'DRAFT'"
|
|
|
|
|
|
link type="success" size="small"
|
|
|
|
|
|
@click="publishApp(row.id)">发布</el-button>
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
v-if="row.publishStatus === 'PUBLISHED'"
|
|
|
|
|
|
link type="warning" size="small"
|
|
|
|
|
|
@click="openGrayDialog(row, 'app')">灰度</el-button>
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
v-if="row.publishStatus === 'PUBLISHED'"
|
|
|
|
|
|
link type="danger" size="small"
|
|
|
|
|
|
@click="unpublishApp(row.id)">下架</el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
</el-table>
|
|
|
|
|
|
</el-tab-pane>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- RN Bundles -->
|
|
|
|
|
|
<el-tab-pane label="RN Bundle 热更新" name="rn">
|
|
|
|
|
|
<div class="toolbar">
|
|
|
|
|
|
<el-input
|
|
|
|
|
|
v-model="rnModuleFilter"
|
|
|
|
|
|
placeholder="模块ID(可选)"
|
|
|
|
|
|
clearable
|
|
|
|
|
|
style="width:180px;margin-right:8px"
|
|
|
|
|
|
@change="loadRnBundles"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<el-radio-group v-model="rnPlatform" @change="loadRnBundles" style="margin-right:12px">
|
|
|
|
|
|
<el-radio-button value="">全平台</el-radio-button>
|
|
|
|
|
|
<el-radio-button value="ANDROID">Android</el-radio-button>
|
|
|
|
|
|
<el-radio-button value="IOS">iOS</el-radio-button>
|
|
|
|
|
|
</el-radio-group>
|
|
|
|
|
|
<el-button type="primary" @click="showUploadRn = true">上传 Bundle</el-button>
|
|
|
|
|
|
<el-button @click="loadRnBundles" :loading="loadingRn">刷新</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<el-table :data="rnBundles" v-loading="loadingRn" border stripe>
|
|
|
|
|
|
<el-table-column prop="moduleId" label="模块ID" width="140" />
|
|
|
|
|
|
<el-table-column prop="version" label="版本" width="100" />
|
|
|
|
|
|
<el-table-column prop="platform" label="平台" width="90" />
|
|
|
|
|
|
<el-table-column label="状态" width="140">
|
|
|
|
|
|
<template #default="{row}">
|
|
|
|
|
|
<el-tag :type="statusTagType(row)" size="small">{{ statusLabel(row) }}</el-tag>
|
|
|
|
|
|
<el-tag v-if="row.grayEnabled" type="warning" size="small" style="margin-left:4px">
|
|
|
|
|
|
灰度 {{ row.grayPercent }}%
|
|
|
|
|
|
</el-tag>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="minCommonVersion" label="最低 Common 版本" width="160" />
|
|
|
|
|
|
<el-table-column prop="note" label="说明" show-overflow-tooltip />
|
|
|
|
|
|
<el-table-column prop="createdAt" label="上传时间" width="180">
|
|
|
|
|
|
<template #default="{row}">{{ formatTime(row.createdAt) }}</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column label="操作" width="200" fixed="right">
|
|
|
|
|
|
<template #default="{row}">
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
v-if="row.publishStatus === 'DRAFT'"
|
|
|
|
|
|
link type="success" size="small"
|
|
|
|
|
|
@click="publishRn(row.id)">发布</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'"
|
|
|
|
|
|
link type="danger" size="small"
|
|
|
|
|
|
@click="unpublishRn(row.id)">下架</el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
</el-table>
|
|
|
|
|
|
</el-tab-pane>
|
|
|
|
|
|
</el-tabs>
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Gray Release Dialog -->
|
|
|
|
|
|
<el-dialog v-model="showGray" title="灰度发布配置" width="400px">
|
|
|
|
|
|
<el-form label-width="90px">
|
|
|
|
|
|
<el-form-item label="开启灰度">
|
|
|
|
|
|
<el-switch v-model="grayForm.enabled" />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="灰度比例" v-if="grayForm.enabled">
|
|
|
|
|
|
<el-slider v-model="grayForm.percent" :min="1" :max="100" show-input />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
|
<el-button @click="showGray = false">取消</el-button>
|
|
|
|
|
|
<el-button type="primary" @click="submitGray" :loading="submittingGray">保存</el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Upload App Version Dialog -->
|
|
|
|
|
|
<el-dialog v-model="showUploadApp" title="上传 App 版本" width="480px">
|
|
|
|
|
|
<el-form :model="appUploadForm" label-width="100px">
|
|
|
|
|
|
<el-form-item label="平台">
|
|
|
|
|
|
<el-select v-model="appUploadForm.platform">
|
|
|
|
|
|
<el-option value="ANDROID" label="Android" />
|
|
|
|
|
|
<el-option value="IOS" label="iOS" />
|
|
|
|
|
|
</el-select>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="版本名称"><el-input v-model="appUploadForm.versionName" /></el-form-item>
|
|
|
|
|
|
<el-form-item label="版本码"><el-input-number v-model="appUploadForm.versionCode" :min="1" /></el-form-item>
|
|
|
|
|
|
<el-form-item label="强制更新"><el-switch v-model="appUploadForm.forceUpdate" /></el-form-item>
|
|
|
|
|
|
<el-form-item label="更新说明"><el-input v-model="appUploadForm.changeLog" type="textarea" :rows="3" /></el-form-item>
|
|
|
|
|
|
<el-form-item label="APK 文件">
|
|
|
|
|
|
<el-upload
|
|
|
|
|
|
:auto-upload="false"
|
|
|
|
|
|
:limit="1"
|
|
|
|
|
|
:on-change="f => appUploadForm.file = f.raw ?? null"
|
|
|
|
|
|
accept=".apk,.ipa">
|
|
|
|
|
|
<el-button>选择文件</el-button>
|
|
|
|
|
|
</el-upload>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
|
<el-button @click="showUploadApp = false">取消</el-button>
|
|
|
|
|
|
<el-button type="primary" @click="submitAppUpload" :loading="uploadingApp">上传</el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Upload RN Bundle Dialog -->
|
|
|
|
|
|
<el-dialog v-model="showUploadRn" title="上传 RN Bundle" width="480px">
|
|
|
|
|
|
<el-form :model="rnUploadForm" label-width="100px">
|
|
|
|
|
|
<el-form-item label="模块ID"><el-input v-model="rnUploadForm.moduleId" /></el-form-item>
|
|
|
|
|
|
<el-form-item label="平台">
|
|
|
|
|
|
<el-select v-model="rnUploadForm.platform">
|
|
|
|
|
|
<el-option value="ANDROID" label="Android" />
|
|
|
|
|
|
<el-option value="IOS" label="iOS" />
|
|
|
|
|
|
</el-select>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="版本"><el-input v-model="rnUploadForm.version" placeholder="如 1.0.1" /></el-form-item>
|
|
|
|
|
|
<el-form-item label="最低 Common 版本"><el-input v-model="rnUploadForm.minCommonVersion" placeholder="如 1.0.0" /></el-form-item>
|
|
|
|
|
|
<el-form-item label="说明"><el-input v-model="rnUploadForm.note" type="textarea" :rows="2" /></el-form-item>
|
|
|
|
|
|
<el-form-item label="Bundle 文件">
|
|
|
|
|
|
<el-upload
|
|
|
|
|
|
:auto-upload="false"
|
|
|
|
|
|
:limit="1"
|
|
|
|
|
|
:on-change="f => rnUploadForm.file = f.raw ?? null"
|
|
|
|
|
|
accept=".bundle,.js">
|
|
|
|
|
|
<el-button>选择文件</el-button>
|
|
|
|
|
|
</el-upload>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
|
<el-button @click="showUploadRn = false">取消</el-button>
|
|
|
|
|
|
<el-button type="primary" @click="submitRnUpload" :loading="uploadingRn">上传</el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-dialog>
|
2026-04-28 21:05:07 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- Unified Release Dialog -->
|
|
|
|
|
|
<el-dialog v-model="showUnifiedUpload" title="一键上传" width="920px">
|
|
|
|
|
|
<el-form label-width="110px">
|
|
|
|
|
|
<el-divider content-position="left">Android / iOS 整包</el-divider>
|
|
|
|
|
|
<div class="unified-grid">
|
|
|
|
|
|
<div v-for="item in unifiedAppForms" :key="item.platform" class="unified-block">
|
|
|
|
|
|
<div class="unified-block-title">{{ item.platform === 'ANDROID' ? 'Android' : 'iOS' }}</div>
|
|
|
|
|
|
<el-form-item label="启用">
|
|
|
|
|
|
<el-switch v-model="item.enabled" />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<template v-if="item.enabled">
|
|
|
|
|
|
<el-form-item label="版本名称"><el-input v-model="item.versionName" /></el-form-item>
|
|
|
|
|
|
<el-form-item label="版本码"><el-input-number v-model="item.versionCode" :min="1" /></el-form-item>
|
|
|
|
|
|
<el-form-item label="强制更新"><el-switch v-model="item.forceUpdate" /></el-form-item>
|
|
|
|
|
|
<el-form-item label="更新说明"><el-input v-model="item.changeLog" type="textarea" :rows="2" /></el-form-item>
|
|
|
|
|
|
<el-form-item label="包文件">
|
|
|
|
|
|
<el-upload
|
|
|
|
|
|
:auto-upload="false"
|
|
|
|
|
|
:limit="1"
|
|
|
|
|
|
:on-change="f => item.file = f.raw ?? null"
|
|
|
|
|
|
:accept="item.platform === 'ANDROID' ? '.apk' : '.ipa'">
|
|
|
|
|
|
<el-button>选择文件</el-button>
|
|
|
|
|
|
</el-upload>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<el-divider content-position="left">
|
|
|
|
|
|
Bundle 插件上传
|
|
|
|
|
|
<el-button link type="primary" @click="addUnifiedBundle" style="margin-left:8px">新增插件</el-button>
|
|
|
|
|
|
</el-divider>
|
|
|
|
|
|
<div v-for="(item, index) in unifiedBundleForms" :key="item.key" class="unified-bundle-row">
|
|
|
|
|
|
<div class="unified-bundle-head">
|
|
|
|
|
|
<div>插件 {{ index + 1 }}</div>
|
|
|
|
|
|
<el-button link type="danger" @click="removeUnifiedBundle(index)" :disabled="unifiedBundleForms.length === 1">删除</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="unified-grid">
|
|
|
|
|
|
<el-form-item label="模块ID"><el-input v-model="item.moduleId" /></el-form-item>
|
|
|
|
|
|
<el-form-item label="平台">
|
|
|
|
|
|
<el-select v-model="item.platform">
|
|
|
|
|
|
<el-option value="ANDROID" label="Android" />
|
|
|
|
|
|
<el-option value="IOS" label="iOS" />
|
|
|
|
|
|
</el-select>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="版本"><el-input v-model="item.version" /></el-form-item>
|
|
|
|
|
|
<el-form-item label="最低 Common 版本"><el-input v-model="item.minCommonVersion" /></el-form-item>
|
|
|
|
|
|
<el-form-item label="说明"><el-input v-model="item.note" type="textarea" :rows="2" /></el-form-item>
|
|
|
|
|
|
<el-form-item label="Bundle 文件">
|
|
|
|
|
|
<el-upload
|
|
|
|
|
|
:auto-upload="false"
|
|
|
|
|
|
:limit="1"
|
|
|
|
|
|
:on-change="f => item.file = f.raw ?? null"
|
|
|
|
|
|
accept=".bundle,.js">
|
|
|
|
|
|
<el-button>选择文件</el-button>
|
|
|
|
|
|
</el-upload>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
|
<el-button @click="showUnifiedUpload = false">取消</el-button>
|
|
|
|
|
|
<el-button type="primary" @click="submitUnifiedUpload" :loading="uploadingUnified">上传</el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
|
2026-04-24 16:16:54 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
import { ref, onMounted } from 'vue'
|
|
|
|
|
|
import { useRoute } from 'vue-router'
|
|
|
|
|
|
import { ElMessage } from 'element-plus'
|
|
|
|
|
|
import { updateAdminApi, type AppVersion, type RnBundle } from '@/api/update'
|
|
|
|
|
|
|
|
|
|
|
|
const route = useRoute()
|
|
|
|
|
|
const appId = route.params.appId as string
|
|
|
|
|
|
|
|
|
|
|
|
const activeTab = ref('app')
|
|
|
|
|
|
const appPlatform = ref<'ANDROID' | 'IOS'>('ANDROID')
|
2026-04-28 21:05:07 +08:00
|
|
|
|
const rnPlatform = ref<'ANDROID' | 'IOS' | ''>('')
|
2026-04-24 16:16:54 +08:00
|
|
|
|
const rnModuleFilter = ref('')
|
|
|
|
|
|
|
|
|
|
|
|
const appVersions = ref<AppVersion[]>([])
|
|
|
|
|
|
const loadingApp = ref(false)
|
|
|
|
|
|
const rnBundles = ref<RnBundle[]>([])
|
|
|
|
|
|
const loadingRn = ref(false)
|
|
|
|
|
|
|
|
|
|
|
|
const showGray = ref(false)
|
|
|
|
|
|
const submittingGray = ref(false)
|
|
|
|
|
|
const grayTarget = ref<{ id: string; type: 'app' | 'rn' } | null>(null)
|
|
|
|
|
|
const grayForm = ref({ enabled: true, percent: 10 })
|
|
|
|
|
|
|
|
|
|
|
|
const showUploadApp = ref(false)
|
|
|
|
|
|
const uploadingApp = ref(false)
|
|
|
|
|
|
const appUploadForm = ref({
|
|
|
|
|
|
platform: 'ANDROID' as 'ANDROID' | 'IOS',
|
|
|
|
|
|
versionName: '',
|
|
|
|
|
|
versionCode: 1,
|
|
|
|
|
|
forceUpdate: false,
|
|
|
|
|
|
changeLog: '',
|
|
|
|
|
|
file: null as File | null,
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const showUploadRn = ref(false)
|
|
|
|
|
|
const uploadingRn = ref(false)
|
|
|
|
|
|
const rnUploadForm = ref({
|
|
|
|
|
|
moduleId: '',
|
|
|
|
|
|
platform: 'ANDROID' as 'ANDROID' | 'IOS',
|
|
|
|
|
|
version: '',
|
|
|
|
|
|
minCommonVersion: '',
|
|
|
|
|
|
note: '',
|
|
|
|
|
|
file: null as File | null,
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-04-28 21:05:07 +08:00
|
|
|
|
const showUnifiedUpload = ref(false)
|
|
|
|
|
|
const uploadingUnified = ref(false)
|
|
|
|
|
|
const unifiedAppForms = ref([
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'ANDROID',
|
|
|
|
|
|
enabled: true,
|
|
|
|
|
|
platform: 'ANDROID' as const,
|
|
|
|
|
|
versionName: '',
|
|
|
|
|
|
versionCode: 1,
|
|
|
|
|
|
forceUpdate: false,
|
|
|
|
|
|
changeLog: '',
|
|
|
|
|
|
file: null as File | null,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'IOS',
|
|
|
|
|
|
enabled: true,
|
|
|
|
|
|
platform: 'IOS' as const,
|
|
|
|
|
|
versionName: '',
|
|
|
|
|
|
versionCode: 1,
|
|
|
|
|
|
forceUpdate: false,
|
|
|
|
|
|
changeLog: '',
|
|
|
|
|
|
file: null as File | null,
|
|
|
|
|
|
},
|
|
|
|
|
|
])
|
|
|
|
|
|
const unifiedBundleForms = ref([
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'bundle-0',
|
|
|
|
|
|
moduleId: '',
|
|
|
|
|
|
platform: 'ANDROID' as 'ANDROID' | 'IOS',
|
|
|
|
|
|
version: '',
|
|
|
|
|
|
minCommonVersion: '',
|
|
|
|
|
|
note: '',
|
|
|
|
|
|
file: null as File | null,
|
|
|
|
|
|
},
|
|
|
|
|
|
])
|
|
|
|
|
|
|
2026-04-24 16:16:54 +08:00
|
|
|
|
function formatTime(t: string) {
|
|
|
|
|
|
return t ? new Date(t).toLocaleString('zh-CN') : '-'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function statusLabel(row: { publishStatus: string }) {
|
|
|
|
|
|
return { DRAFT: '草稿', PUBLISHED: '已发布', DEPRECATED: '已下架' }[row.publishStatus] ?? row.publishStatus
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function statusTagType(row: { publishStatus: string }) {
|
|
|
|
|
|
return { DRAFT: '', PUBLISHED: 'success', DEPRECATED: 'info' }[row.publishStatus] ?? ''
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function loadAppVersions() {
|
|
|
|
|
|
loadingApp.value = true
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await updateAdminApi.listAppVersions(appId, appPlatform.value)
|
|
|
|
|
|
appVersions.value = res.data.data
|
|
|
|
|
|
} catch { ElMessage.error('加载失败') }
|
|
|
|
|
|
finally { loadingApp.value = false }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function loadRnBundles() {
|
|
|
|
|
|
loadingRn.value = true
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await updateAdminApi.listRnBundles(
|
|
|
|
|
|
appId,
|
|
|
|
|
|
rnModuleFilter.value || undefined,
|
|
|
|
|
|
rnPlatform.value || undefined,
|
|
|
|
|
|
)
|
|
|
|
|
|
rnBundles.value = res.data.data
|
|
|
|
|
|
} catch { ElMessage.error('加载失败') }
|
|
|
|
|
|
finally { loadingRn.value = false }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function publishApp(id: string) {
|
|
|
|
|
|
await updateAdminApi.publishAppVersion(id)
|
|
|
|
|
|
ElMessage.success('已发布')
|
|
|
|
|
|
loadAppVersions()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function unpublishApp(id: string) {
|
|
|
|
|
|
await updateAdminApi.unpublishAppVersion(id)
|
|
|
|
|
|
ElMessage.success('已下架')
|
|
|
|
|
|
loadAppVersions()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function publishRn(id: string) {
|
|
|
|
|
|
await updateAdminApi.publishRnBundle(id)
|
|
|
|
|
|
ElMessage.success('已发布')
|
|
|
|
|
|
loadRnBundles()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function unpublishRn(id: string) {
|
|
|
|
|
|
await updateAdminApi.unpublishRnBundle(id)
|
|
|
|
|
|
ElMessage.success('已下架')
|
|
|
|
|
|
loadRnBundles()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function openGrayDialog(row: { id: string }, type: 'app' | 'rn') {
|
|
|
|
|
|
grayTarget.value = { id: row.id, type }
|
|
|
|
|
|
grayForm.value = { enabled: true, percent: 10 }
|
|
|
|
|
|
showGray.value = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function submitGray() {
|
|
|
|
|
|
if (!grayTarget.value) return
|
|
|
|
|
|
submittingGray.value = true
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { id, type } = grayTarget.value
|
|
|
|
|
|
if (type === 'app') {
|
|
|
|
|
|
await updateAdminApi.grayAppVersion(id, grayForm.value.enabled, grayForm.value.percent)
|
|
|
|
|
|
loadAppVersions()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
await updateAdminApi.grayRnBundle(id, grayForm.value.enabled, grayForm.value.percent)
|
|
|
|
|
|
loadRnBundles()
|
|
|
|
|
|
}
|
|
|
|
|
|
ElMessage.success('灰度配置已保存')
|
|
|
|
|
|
showGray.value = false
|
|
|
|
|
|
} finally { submittingGray.value = false }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function submitAppUpload() {
|
|
|
|
|
|
const f = appUploadForm.value
|
|
|
|
|
|
if (!f.versionName || !f.versionCode) return ElMessage.warning('请填写版本信息')
|
|
|
|
|
|
uploadingApp.value = true
|
|
|
|
|
|
try {
|
|
|
|
|
|
const fd = new FormData()
|
|
|
|
|
|
fd.append('appId', appId)
|
|
|
|
|
|
fd.append('platform', f.platform)
|
|
|
|
|
|
fd.append('versionName', f.versionName)
|
|
|
|
|
|
fd.append('versionCode', String(f.versionCode))
|
|
|
|
|
|
fd.append('forceUpdate', String(f.forceUpdate))
|
|
|
|
|
|
if (f.changeLog) fd.append('changeLog', f.changeLog)
|
|
|
|
|
|
if (f.file) fd.append('apkFile', f.file)
|
|
|
|
|
|
await updateAdminApi.uploadAppVersion(fd)
|
|
|
|
|
|
ElMessage.success('上传成功,版本已创建为草稿')
|
|
|
|
|
|
showUploadApp.value = false
|
|
|
|
|
|
loadAppVersions()
|
|
|
|
|
|
} finally { uploadingApp.value = false }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function submitRnUpload() {
|
|
|
|
|
|
const f = rnUploadForm.value
|
|
|
|
|
|
if (!f.moduleId || !f.version || !f.file) return ElMessage.warning('请填写模块ID、版本和 Bundle 文件')
|
|
|
|
|
|
uploadingRn.value = true
|
|
|
|
|
|
try {
|
|
|
|
|
|
const fd = new FormData()
|
|
|
|
|
|
fd.append('appId', appId)
|
|
|
|
|
|
fd.append('moduleId', f.moduleId)
|
|
|
|
|
|
fd.append('platform', f.platform)
|
|
|
|
|
|
fd.append('version', f.version)
|
|
|
|
|
|
if (f.minCommonVersion) fd.append('minCommonVersion', f.minCommonVersion)
|
|
|
|
|
|
if (f.note) fd.append('note', f.note)
|
|
|
|
|
|
fd.append('bundle', f.file)
|
|
|
|
|
|
await updateAdminApi.uploadRnBundle(fd)
|
|
|
|
|
|
ElMessage.success('上传成功,Bundle 已创建为草稿')
|
|
|
|
|
|
showUploadRn.value = false
|
|
|
|
|
|
loadRnBundles()
|
|
|
|
|
|
} finally { uploadingRn.value = false }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 21:05:07 +08:00
|
|
|
|
function addUnifiedBundle() {
|
|
|
|
|
|
unifiedBundleForms.value.push({
|
|
|
|
|
|
key: `bundle-${Date.now()}-${unifiedBundleForms.value.length}`,
|
|
|
|
|
|
moduleId: '',
|
|
|
|
|
|
platform: 'ANDROID',
|
|
|
|
|
|
version: '',
|
|
|
|
|
|
minCommonVersion: '',
|
|
|
|
|
|
note: '',
|
|
|
|
|
|
file: null,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function removeUnifiedBundle(index: number) {
|
|
|
|
|
|
unifiedBundleForms.value.splice(index, 1)
|
|
|
|
|
|
if (unifiedBundleForms.value.length === 0) {
|
|
|
|
|
|
addUnifiedBundle()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function submitUnifiedUpload() {
|
|
|
|
|
|
const appItems = unifiedAppForms.value
|
|
|
|
|
|
.filter(item => item.enabled && item.file)
|
|
|
|
|
|
.map(item => ({
|
|
|
|
|
|
fileKey: item.key,
|
|
|
|
|
|
platform: item.platform,
|
|
|
|
|
|
versionName: item.versionName,
|
|
|
|
|
|
versionCode: item.versionCode,
|
|
|
|
|
|
changeLog: item.changeLog || undefined,
|
|
|
|
|
|
forceUpdate: item.forceUpdate,
|
|
|
|
|
|
}))
|
|
|
|
|
|
const bundleItems = unifiedBundleForms.value
|
|
|
|
|
|
.filter(item => item.file)
|
|
|
|
|
|
.map(item => ({
|
|
|
|
|
|
fileKey: item.key,
|
|
|
|
|
|
moduleId: item.moduleId,
|
|
|
|
|
|
platform: item.platform,
|
|
|
|
|
|
version: item.version,
|
|
|
|
|
|
minCommonVersion: item.minCommonVersion || undefined,
|
|
|
|
|
|
note: item.note || undefined,
|
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
|
|
if (!appItems.length && !bundleItems.length) {
|
|
|
|
|
|
ElMessage.warning('请至少选择一个 App 包或 Bundle 文件')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (const item of unifiedAppForms.value) {
|
|
|
|
|
|
if (item.enabled && item.file && (!item.versionName || !item.versionCode)) {
|
|
|
|
|
|
ElMessage.warning('请填写整包版本信息')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
for (const item of unifiedBundleForms.value) {
|
|
|
|
|
|
if (item.file && (!item.moduleId || !item.version)) {
|
|
|
|
|
|
ElMessage.warning('请填写 Bundle 版本信息')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uploadingUnified.value = true
|
|
|
|
|
|
try {
|
|
|
|
|
|
const fd = new FormData()
|
|
|
|
|
|
fd.append('appId', appId)
|
|
|
|
|
|
fd.append('manifest', JSON.stringify({ appVersions: appItems, rnBundles: bundleItems }))
|
|
|
|
|
|
for (const item of unifiedAppForms.value) {
|
|
|
|
|
|
if (item.enabled && item.file) fd.append(item.key, item.file)
|
|
|
|
|
|
}
|
|
|
|
|
|
for (const item of unifiedBundleForms.value) {
|
|
|
|
|
|
if (item.file) fd.append(item.key, item.file)
|
|
|
|
|
|
}
|
|
|
|
|
|
await updateAdminApi.uploadUnifiedRelease(fd)
|
|
|
|
|
|
ElMessage.success('一键上传成功')
|
|
|
|
|
|
showUnifiedUpload.value = false
|
|
|
|
|
|
loadAppVersions()
|
|
|
|
|
|
loadRnBundles()
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
uploadingUnified.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-24 16:16:54 +08:00
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
loadAppVersions()
|
|
|
|
|
|
loadRnBundles()
|
|
|
|
|
|
})
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.toolbar { display: flex; align-items: center; gap: 8px; margin-bottom: 16px; flex-wrap: wrap; }
|
2026-04-28 21:05:07 +08:00
|
|
|
|
.unified-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.unified-block,
|
|
|
|
|
|
.unified-bundle-row {
|
|
|
|
|
|
border: 1px solid var(--el-border-color-lighter);
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
padding: 12px;
|
|
|
|
|
|
background: var(--el-bg-color-page);
|
|
|
|
|
|
}
|
|
|
|
|
|
.unified-block-title,
|
|
|
|
|
|
.unified-bundle-head {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
.unified-bundle-row + .unified-bundle-row {
|
|
|
|
|
|
margin-top: 12px;
|
|
|
|
|
|
}
|
2026-04-24 16:16:54 +08:00
|
|
|
|
</style>
|