feat: 添加补丁版本基础版本选择功能
- 添加基础版本下拉选择器,仅在补丁更新类型时显示 - 添加应用和更新类型的 watch 监听,自动获取全量版本列表 - 提交时传递 base_version_id 参数 - 添加调试日志便于排查问题 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -272,6 +272,7 @@ type CreateVersionRequest struct {
|
|||||||
CustomDownloadURL string `json:"custom_download_url"`
|
CustomDownloadURL string `json:"custom_download_url"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Changelog string `json:"changelog"`
|
Changelog string `json:"changelog"`
|
||||||
|
BaseVersionID *uint `json:"base_version_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleCreateVersionGlobal(c *gin.Context) {
|
func handleCreateVersionGlobal(c *gin.Context) {
|
||||||
@@ -360,15 +361,30 @@ func handleCreateVersionGlobal(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
} else if version.UpdateType == "patch" {
|
} else if version.UpdateType == "patch" {
|
||||||
// 补丁包:需要找到基础全量包并合并
|
// 补丁包:需要找到基础全量包并合并
|
||||||
var lastFullVersion model.Version
|
var baseVersion model.Version
|
||||||
|
|
||||||
|
// 优先使用传入的 base_version_id
|
||||||
|
if req.BaseVersionID != nil {
|
||||||
|
if err := database.DB.Where("id = ? AND application_id = ? AND update_type = ?",
|
||||||
|
*req.BaseVersionID, req.ApplicationID, "full").First(&baseVersion).Error; err != nil {
|
||||||
|
response.Error(c, 400, "指定的基础版本不存在或不是全量版本")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 未指定基础版本,使用最新的全量版本
|
||||||
if err := database.DB.Where("application_id = ? AND update_type = ?", req.ApplicationID, "full").
|
if err := database.DB.Where("application_id = ? AND update_type = ?", req.ApplicationID, "full").
|
||||||
Order("id DESC").First(&lastFullVersion).Error; err == nil {
|
Order("id DESC").First(&baseVersion).Error; err != nil {
|
||||||
version.BaseVersionID = &lastFullVersion.ID
|
response.Error(c, 400, "找不到基础全量包,无法创建补丁版本")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
version.BaseVersionID = &baseVersion.ID
|
||||||
|
|
||||||
// 获取基础版本的全量包路径,如果没有则使用 FilePath
|
// 获取基础版本的全量包路径,如果没有则使用 FilePath
|
||||||
baseFullPackagePath := lastFullVersion.FullPackagePath
|
baseFullPackagePath := baseVersion.FullPackagePath
|
||||||
if baseFullPackagePath == "" {
|
if baseFullPackagePath == "" {
|
||||||
baseFullPackagePath = lastFullVersion.FilePath
|
baseFullPackagePath = baseVersion.FilePath
|
||||||
}
|
}
|
||||||
|
|
||||||
// 合并生成新的全量包
|
// 合并生成新的全量包
|
||||||
@@ -386,10 +402,6 @@ func handleCreateVersionGlobal(c *gin.Context) {
|
|||||||
version.FullPackagePath = fullPkgPath
|
version.FullPackagePath = fullPkgPath
|
||||||
version.FullPackageSize = fullPkgSize
|
version.FullPackageSize = fullPkgSize
|
||||||
version.FullPackageHash = fullPkgHash
|
version.FullPackageHash = fullPkgHash
|
||||||
} else {
|
|
||||||
response.Error(c, 400, "找不到基础全量包,无法创建补丁版本")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := database.DB.Create(&version).Error; err != nil {
|
if err := database.DB.Create(&version).Error; err != nil {
|
||||||
@@ -425,6 +437,7 @@ type UpdateVersionRequest struct {
|
|||||||
CustomDownloadURL string `json:"custom_download_url"`
|
CustomDownloadURL string `json:"custom_download_url"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Changelog string `json:"changelog"`
|
Changelog string `json:"changelog"`
|
||||||
|
BaseVersionID *uint `json:"base_version_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleUpdateVersionGlobal(c *gin.Context) {
|
func handleUpdateVersionGlobal(c *gin.Context) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { CheckCircle, Eye, FileArchive, FilePlus, GitBranch, Loader2, Rocket, Upload, UploadCloud, X } from 'lucide-vue-next'
|
import { CheckCircle, Eye, FileArchive, FilePlus, GitBranch, Loader2, Rocket, Upload, UploadCloud, X } from 'lucide-vue-next'
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref, watch } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { toast } from 'vue-sonner'
|
import { toast } from 'vue-sonner'
|
||||||
@@ -39,6 +39,8 @@ const uploading = ref(false)
|
|||||||
const uploadProgress = ref(0)
|
const uploadProgress = ref(0)
|
||||||
const uploadSpeed = ref('')
|
const uploadSpeed = ref('')
|
||||||
const applications = ref<Application[]>([])
|
const applications = ref<Application[]>([])
|
||||||
|
const baseVersions = ref<{ id: number, version: string }[]>([])
|
||||||
|
const loadingBaseVersions = ref(false)
|
||||||
|
|
||||||
const formData = ref({
|
const formData = ref({
|
||||||
application_id: '',
|
application_id: '',
|
||||||
@@ -51,6 +53,7 @@ const formData = ref({
|
|||||||
changelog: '',
|
changelog: '',
|
||||||
entry_file: '',
|
entry_file: '',
|
||||||
file: null as File | null,
|
file: null as File | null,
|
||||||
|
base_version_id: '' as string,
|
||||||
})
|
})
|
||||||
|
|
||||||
const uploadedData = ref<UploadResponse | null>(null)
|
const uploadedData = ref<UploadResponse | null>(null)
|
||||||
@@ -123,6 +126,62 @@ async function fetchApplications() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchBaseVersions(appId: string) {
|
||||||
|
if (!appId) {
|
||||||
|
baseVersions.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loadingBaseVersions.value = true
|
||||||
|
try {
|
||||||
|
const data = await api.get<{ versions: { id: number, version: string, update_type: string }[] }>(
|
||||||
|
`/dev/versions?application_id=${appId}&update_type=full&page_size=100`
|
||||||
|
)
|
||||||
|
console.log('[DEBUG] fetchBaseVersions response:', data)
|
||||||
|
// 只筛选全量版本
|
||||||
|
const versions = data?.versions || []
|
||||||
|
baseVersions.value = versions.filter(v => v.update_type === 'full')
|
||||||
|
console.log('[DEBUG] baseVersions after filter:', baseVersions.value)
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('获取基础版本列表失败:', error)
|
||||||
|
baseVersions.value = []
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
loadingBaseVersions.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听应用变化
|
||||||
|
watch(
|
||||||
|
() => formData.value.application_id,
|
||||||
|
(newAppId) => {
|
||||||
|
console.log('[DEBUG] application_id changed:', newAppId, 'update_type:', formData.value.update_type)
|
||||||
|
if (newAppId && formData.value.update_type === 'patch') {
|
||||||
|
fetchBaseVersions(newAppId)
|
||||||
|
} else if (!newAppId) {
|
||||||
|
// 只有当应用被清空时才清空基础版本
|
||||||
|
baseVersions.value = []
|
||||||
|
formData.value.base_version_id = ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
// 监听更新类型变化
|
||||||
|
watch(
|
||||||
|
() => formData.value.update_type,
|
||||||
|
(newType) => {
|
||||||
|
console.log('[DEBUG] update_type changed:', newType, 'application_id:', formData.value.application_id)
|
||||||
|
if (newType === 'patch' && formData.value.application_id) {
|
||||||
|
fetchBaseVersions(formData.value.application_id)
|
||||||
|
} else if (newType !== 'patch') {
|
||||||
|
baseVersions.value = []
|
||||||
|
formData.value.base_version_id = ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
function handleFileSelect(event: Event) {
|
function handleFileSelect(event: Event) {
|
||||||
const target = event.target as HTMLInputElement
|
const target = event.target as HTMLInputElement
|
||||||
if (target.files && target.files.length > 0) {
|
if (target.files && target.files.length > 0) {
|
||||||
@@ -195,7 +254,7 @@ async function handleSubmit() {
|
|||||||
|
|
||||||
saving.value = true
|
saving.value = true
|
||||||
try {
|
try {
|
||||||
await api.post('/dev/versions', {
|
const payload: Record<string, any> = {
|
||||||
application_id: Number(formData.value.application_id),
|
application_id: Number(formData.value.application_id),
|
||||||
version: formData.value.version,
|
version: formData.value.version,
|
||||||
description: formData.value.description,
|
description: formData.value.description,
|
||||||
@@ -208,7 +267,12 @@ async function handleSubmit() {
|
|||||||
file_path: uploadedData.value.file_path,
|
file_path: uploadedData.value.file_path,
|
||||||
file_size: uploadedData.value.file_size,
|
file_size: uploadedData.value.file_size,
|
||||||
file_hash: uploadedData.value.file_hash,
|
file_hash: uploadedData.value.file_hash,
|
||||||
})
|
}
|
||||||
|
// 如果是补丁版本,添加基础版本ID
|
||||||
|
if (formData.value.update_type === 'patch' && formData.value.base_version_id) {
|
||||||
|
payload.base_version_id = Number(formData.value.base_version_id)
|
||||||
|
}
|
||||||
|
await api.post('/dev/versions', payload)
|
||||||
toast.success(t('admin.versions.createForm.createSuccess'))
|
toast.success(t('admin.versions.createForm.createSuccess'))
|
||||||
router.push('/admin/versions')
|
router.push('/admin/versions')
|
||||||
}
|
}
|
||||||
@@ -465,6 +529,31 @@ onMounted(() => {
|
|||||||
</UiSelect>
|
</UiSelect>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 基础版本选择(仅补丁类型) -->
|
||||||
|
<div v-if="formData.update_type === 'patch'" class="space-y-2">
|
||||||
|
<UiLabel>{{ t('admin.versions.createForm.baseVersion') }}</UiLabel>
|
||||||
|
<p class="text-sm text-muted-foreground">
|
||||||
|
{{ t('admin.versions.createForm.baseVersionDesc') }}
|
||||||
|
</p>
|
||||||
|
<UiSelect v-model="formData.base_version_id" :disabled="saving || loadingBaseVersions">
|
||||||
|
<UiSelectTrigger>
|
||||||
|
<UiSelectValue :placeholder="t('admin.versions.createForm.baseVersionPlaceholder')" />
|
||||||
|
</UiSelectTrigger>
|
||||||
|
<UiSelectContent>
|
||||||
|
<UiSelectItem
|
||||||
|
v-for="v in baseVersions"
|
||||||
|
:key="v.id"
|
||||||
|
:value="String(v.id)"
|
||||||
|
>
|
||||||
|
{{ v.version }}
|
||||||
|
</UiSelectItem>
|
||||||
|
</UiSelectContent>
|
||||||
|
</UiSelect>
|
||||||
|
<p v-if="baseVersions.length === 0 && !loadingBaseVersions" class="text-sm text-destructive">
|
||||||
|
{{ t('admin.versions.createForm.noBaseVersion') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<UiLabel>{{ t('admin.versions.createForm.updateMethod') }}</UiLabel>
|
<UiLabel>{{ t('admin.versions.createForm.updateMethod') }}</UiLabel>
|
||||||
<UiSelect v-model="formData.update_method" :disabled="saving">
|
<UiSelect v-model="formData.update_method" :disabled="saving">
|
||||||
|
|||||||
@@ -1502,8 +1502,8 @@
|
|||||||
"updateMethod": "Update Method",
|
"updateMethod": "Update Method",
|
||||||
"baseVersion": "Base Version",
|
"baseVersion": "Base Version",
|
||||||
"baseVersionPlaceholder": "Select base version (patch mode)",
|
"baseVersionPlaceholder": "Select base version (patch mode)",
|
||||||
"baseVersionDesc": "Select a base version to only upload changed files for incremental patch update",
|
"baseVersionDesc": "Select a full version as the base for this patch. The patch will be generated based on this version",
|
||||||
"noBaseVersion": "None (Full version)",
|
"noBaseVersion": "No full version available. Please create a full version first",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"autoUpdate": "Auto Update",
|
"autoUpdate": "Auto Update",
|
||||||
"autoUpdateDesc": "When enabled, the application will automatically download and install updates",
|
"autoUpdateDesc": "When enabled, the application will automatically download and install updates",
|
||||||
|
|||||||
@@ -1480,8 +1480,8 @@
|
|||||||
"updateMethod": "更新方式",
|
"updateMethod": "更新方式",
|
||||||
"baseVersion": "基础版本",
|
"baseVersion": "基础版本",
|
||||||
"baseVersionPlaceholder": "选择基础版本(补丁模式)",
|
"baseVersionPlaceholder": "选择基础版本(补丁模式)",
|
||||||
"baseVersionDesc": "选择基础版本后,只会上传变更的文件,用于增量补丁更新",
|
"baseVersionDesc": "选择一个全量版本作为此补丁包的基础,补丁将基于此版本生成",
|
||||||
"noBaseVersion": "无(完整版本)",
|
"noBaseVersion": "没有可用的全量版本,请先创建全量版本",
|
||||||
"status": "状态",
|
"status": "状态",
|
||||||
"autoUpdate": "自动更新",
|
"autoUpdate": "自动更新",
|
||||||
"autoUpdateDesc": "启用后应用将自动下载并安装更新",
|
"autoUpdateDesc": "启用后应用将自动下载并安装更新",
|
||||||
|
|||||||
Reference in New Issue
Block a user