Merge branch fix/app-version-batch

This commit is contained in:
Zhou Weidong authored and trae-solo-agent committed 2026-10-23 16:46:29 +08:00
commit 18234ebd5b
2 files changed
+253 -58

No files matched your search

+4
View File
@@ -49,6 +49,10 @@ export const createAppVersionApi = (data: AppVersionSaveData) => {
return request.post<string>({ url: '/admin/app-versions', data }) return request.post<string>({ url: '/admin/app-versions', data })
} }
export const batchCreateAppVersionsApi = (data: AppVersionSaveData[]) => {
return request.post<string[]>({ url: '/admin/app-versions/batch', data })
}
export const updateAppVersionApi = (id: string | number, data: AppVersionSaveData) => { export const updateAppVersionApi = (id: string | number, data: AppVersionSaveData) => {
return request.post<void>({ url: `/admin/app-versions/${id}`, data }) return request.post<void>({ url: `/admin/app-versions/${id}`, data })
} }
+249 -58
View File
@@ -8,6 +8,7 @@ import PageHeading from '@/views/components/PageHeading.vue'
import { AppIcons } from '@/constants/icons' import { AppIcons } from '@/constants/icons'
import { dateCell } from '@/utils/format' import { dateCell } from '@/utils/format'
import { import {
batchCreateAppVersionsApi,
createAppVersionApi, createAppVersionApi,
deleteAppVersionApi, deleteAppVersionApi,
listAppVersionsApi, listAppVersionsApi,
@@ -43,16 +44,23 @@ const formLoading = ref(false)
const editingId = ref<string | null>(null) const editingId = ref<string | null>(null)
const formRef = ref<FormInstance>() const formRef = ref<FormInstance>()
const emptyForm: AppVersionSaveData = { /** 新建时两个平台一起配(各自可启停),编辑时用单份。 */
platform: 'ANDROID', interface PlatformBlock {
enabled: boolean
data: AppVersionSaveData
}
const emptyPlatform = (platform: 'ANDROID' | 'IOS'): AppVersionSaveData => ({
platform,
versionCode: 0, versionCode: 0,
versionName: '', versionName: '',
downloadUrl: '', downloadUrl: '',
updateContent: '', updateContent: '',
forceUpdate: 0, forceUpdate: 0,
minSupportedCode: 0 minSupportedCode: 0
} })
const form = ref<AppVersionSaveData>({ ...emptyForm }) const androidBlock = ref<PlatformBlock>({ enabled: true, data: emptyPlatform('ANDROID') })
const iosBlock = ref<PlatformBlock>({ enabled: true, data: emptyPlatform('IOS') })
const editForm = ref<AppVersionSaveData>(emptyPlatform('ANDROID'))
const rules: FormRules = { const rules: FormRules = {
platform: [{ required: true, message: '请选择平台', trigger: 'change' }], platform: [{ required: true, message: '请选择平台', trigger: 'change' }],
@@ -91,13 +99,14 @@ async function fetchList() {
function openCreate() { function openCreate() {
editingId.value = null editingId.value = null
form.value = { ...emptyForm } androidBlock.value = { enabled: true, data: emptyPlatform('ANDROID') }
iosBlock.value = { enabled: true, data: emptyPlatform('IOS') }
dialogOpen.value = true dialogOpen.value = true
} }
function openEdit(row: AppVersionItem) { function openEdit(row: AppVersionItem) {
editingId.value = row.id editingId.value = row.id
form.value = { editForm.value = {
platform: row.platform, platform: row.platform,
versionCode: row.versionCode, versionCode: row.versionCode,
versionName: row.versionName, versionName: row.versionName,
@@ -110,26 +119,29 @@ function openEdit(row: AppVersionItem) {
} }
async function submit() { async function submit() {
if (!formRef.value) return formLoading.value = true
await formRef.value.validate(async (valid) => { try {
if (!valid) return if (editingId.value) {
formLoading.value = true await updateAppVersionApi(editingId.value, editForm.value)
try { ElMessage.success('已更新')
if (editingId.value) { } else {
await updateAppVersionApi(editingId.value, form.value) const toCreate: AppVersionSaveData[] = []
ElMessage.success('已更新') if (androidBlock.value.enabled) toCreate.push(androidBlock.value.data)
} else { if (iosBlock.value.enabled) toCreate.push(iosBlock.value.data)
await createAppVersionApi(form.value) if (toCreate.length === 0) {
ElMessage.success('已创建') ElMessage.warning('至少启用一个平台')
return
} }
dialogOpen.value = false await batchCreateAppVersionsApi(toCreate)
await fetchList() ElMessage.success(`已创建 ${toCreate.length} 条版本`)
} catch {
/* axios interceptor 已弹消息 */
} finally {
formLoading.value = false
} }
}) dialogOpen.value = false
await fetchList()
} catch {
/* axios interceptor 已弹消息 */
} finally {
formLoading.value = false
}
} }
async function toggleStatus(row: AppVersionItem) { async function toggleStatus(row: AppVersionItem) {
@@ -241,23 +253,23 @@ const forceText = computed(() => (v: number) => (v === 1 ? '强制' : '可选升
<el-dialog <el-dialog
v-model="dialogOpen" v-model="dialogOpen"
:title="editingId ? '编辑版本' : '新建版本'" :title="editingId ? '编辑版本' : '新建版本'"
width="640px" width="820px"
@close="() => formRef?.resetFields()" @close="() => formRef?.resetFields()"
> >
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px" v-loading="formLoading"> <!-- 编辑模式:单平台 -->
<el-form
v-if="editingId"
ref="formRef"
:model="editForm"
:rules="rules"
label-width="120px"
v-loading="formLoading"
>
<el-row :gutter="12"> <el-row :gutter="12">
<el-col :span="12"> <el-col :span="8">
<el-form-item label="平台" prop="platform">
<el-radio-group v-model="form.platform">
<el-radio-button value="ANDROID">Android</el-radio-button>
<el-radio-button value="IOS">iOS</el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="整数版本" prop="versionCode"> <el-form-item label="整数版本" prop="versionCode">
<el-input-number <el-input-number
v-model="form.versionCode" v-model="editForm.versionCode"
:min="1" :min="1"
:max="999999" :max="999999"
controls-position="right" controls-position="right"
@@ -265,39 +277,196 @@ const forceText = computed(() => (v: number) => (v === 1 ? '强制' : '可选升
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="16">
<el-form-item label="展示版本" prop="versionName"> <el-form-item label="展示版本" prop="versionName">
<el-input v-model="form.versionName" placeholder="如 1.2.0" /> <el-input v-model="editForm.versionName" placeholder="如 1.2.0" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-form-item label="下载地址" prop="downloadUrl"> <el-form-item label="下载地址" prop="downloadUrl">
<el-input v-model="form.downloadUrl" placeholder="Android apk / iOS App Store 链接" /> <el-input v-model="editForm.downloadUrl" placeholder="Android apk / iOS App Store 链接" />
</el-form-item>
<el-form-item label="强制升级">
<el-switch
v-model="form.forceUpdate"
:active-value="1"
:inactive-value="0"
active-text="强制"
inactive-text="可选"
/>
</el-form-item>
<el-form-item label="兜底最低版本">
<el-input-number v-model="form.minSupportedCode" :min="0" controls-position="right" />
<span class="hint">低于此版本的客户端一律强制升级,0=不开启</span>
</el-form-item> </el-form-item>
<el-row :gutter="12">
<el-col :span="12">
<el-form-item label="强制升级">
<el-switch
v-model="editForm.forceUpdate"
:active-value="1"
:inactive-value="0"
active-text="强制"
inactive-text="可选"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="兜底最低版本">
<el-input-number
v-model="editForm.minSupportedCode"
:min="0"
controls-position="right"
/>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="更新说明"> <el-form-item label="更新说明">
<el-input <el-input
v-model="form.updateContent" v-model="editForm.updateContent"
type="textarea" type="textarea"
:rows="4" :rows="3"
placeholder="换行分隔多条更新内容" placeholder="换行分隔多条更新内容"
maxlength="2000" maxlength="2000"
show-word-limit show-word-limit
/> />
</el-form-item> </el-form-item>
</el-form> </el-form>
<!-- 新建模式:Android + iOS 两块 -->
<div v-else class="dual-blocks" v-loading="formLoading">
<div class="platform-block" :class="{ disabled: !androidBlock.enabled }">
<header>
<span class="platform-title">Android</span>
<el-switch
v-model="androidBlock.enabled"
active-text="启用"
inactive-text="跳过"
inline-prompt
/>
</header>
<el-form
:model="androidBlock.data"
:rules="rules"
label-width="120px"
:disabled="!androidBlock.enabled"
>
<el-row :gutter="12">
<el-col :span="8">
<el-form-item label="整数版本" prop="versionCode">
<el-input-number
v-model="androidBlock.data.versionCode"
:min="1"
:max="999999"
controls-position="right"
class="w-full"
/>
</el-form-item>
</el-col>
<el-col :span="16">
<el-form-item label="展示版本" prop="versionName">
<el-input v-model="androidBlock.data.versionName" placeholder="如 1.2.0" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="下载地址" prop="downloadUrl">
<el-input v-model="androidBlock.data.downloadUrl" placeholder="apk 下载链接" />
</el-form-item>
<el-row :gutter="12">
<el-col :span="12">
<el-form-item label="强制升级">
<el-switch
v-model="androidBlock.data.forceUpdate"
:active-value="1"
:inactive-value="0"
active-text="强制"
inactive-text="可选"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="兜底最低">
<el-input-number
v-model="androidBlock.data.minSupportedCode"
:min="0"
controls-position="right"
/>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="更新说明">
<el-input
v-model="androidBlock.data.updateContent"
type="textarea"
:rows="3"
placeholder="换行分隔多条"
maxlength="2000"
show-word-limit
/>
</el-form-item>
</el-form>
</div>
<div class="platform-block" :class="{ disabled: !iosBlock.enabled }">
<header>
<span class="platform-title">iOS</span>
<el-switch
v-model="iosBlock.enabled"
active-text="启用"
inactive-text="跳过"
inline-prompt
/>
</header>
<el-form
:model="iosBlock.data"
:rules="rules"
label-width="120px"
:disabled="!iosBlock.enabled"
>
<el-row :gutter="12">
<el-col :span="8">
<el-form-item label="整数版本" prop="versionCode">
<el-input-number
v-model="iosBlock.data.versionCode"
:min="1"
:max="999999"
controls-position="right"
class="w-full"
/>
</el-form-item>
</el-col>
<el-col :span="16">
<el-form-item label="展示版本" prop="versionName">
<el-input v-model="iosBlock.data.versionName" placeholder="如 1.2.0" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="下载地址" prop="downloadUrl">
<el-input v-model="iosBlock.data.downloadUrl" placeholder="App Store 链接" />
</el-form-item>
<el-row :gutter="12">
<el-col :span="12">
<el-form-item label="强制升级">
<el-switch
v-model="iosBlock.data.forceUpdate"
:active-value="1"
:inactive-value="0"
active-text="强制"
inactive-text="可选"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="兜底最低">
<el-input-number
v-model="iosBlock.data.minSupportedCode"
:min="0"
controls-position="right"
/>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="更新说明">
<el-input
v-model="iosBlock.data.updateContent"
type="textarea"
:rows="3"
placeholder="换行分隔多条"
maxlength="2000"
show-word-limit
/>
</el-form-item>
</el-form>
</div>
</div>
<template #footer> <template #footer>
<el-button @click="dialogOpen = false">取消</el-button> <el-button @click="dialogOpen = false">取消</el-button>
<el-button type="primary" :loading="formLoading" @click="submit"> <el-button type="primary" :loading="formLoading" @click="submit">
@@ -315,9 +484,31 @@ const forceText = computed(() => (v: number) => (v === 1 ? '强制' : '可选升
margin-top: 12px; margin-top: 12px;
justify-content: flex-end; justify-content: flex-end;
} }
.hint { .dual-blocks {
margin-left: 8px; display: flex;
font-size: 12px; flex-direction: column;
color: var(--el-text-color-secondary); gap: 16px;
}
.platform-block {
border: 1px solid var(--el-border-color-lighter);
border-radius: 8px;
padding: 14px 16px 4px;
background: var(--el-fill-color-blank);
header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 6px;
padding-bottom: 8px;
border-bottom: 1px solid var(--el-border-color-lighter);
}
.platform-title {
font-weight: 600;
font-size: 15px;
}
&.disabled {
opacity: 0.5;
border-style: dashed;
}
} }
</style> </style>