diff --git a/backend/internal/model/models.go b/backend/internal/model/models.go index 69fb165..0c843d0 100644 --- a/backend/internal/model/models.go +++ b/backend/internal/model/models.go @@ -225,10 +225,9 @@ type Version struct { FileSize int64 `json:"file_size"` FileHash string `gorm:"size:64" json:"file_hash"` EntryFile string `gorm:"size:255" json:"entry_file"` - ForceUpdate bool `gorm:"default:false" json:"force_update"` UpdateStrategy string `gorm:"size:20;default:optional" json:"update_strategy"` - UpdateMethod string `gorm:"size:20;default:manual" json:"update_method"` - MinVersion string `gorm:"size:50" json:"min_version"` + UpdateType string `gorm:"size:20;default:full" json:"update_type"` + UpdateMethod string `gorm:"size:20;default:auto" json:"update_method"` Description string `gorm:"type:text" json:"description"` Changelog string `gorm:"type:text" json:"changelog"` Status string `gorm:"size:20;default:active" json:"status"` @@ -236,6 +235,8 @@ type Version struct { CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + ForceUpdate bool `gorm:"default:false" json:"force_update,omitempty"` + MinVersion string `gorm:"size:50" json:"-"` Application Application `gorm:"foreignKey:ApplicationID" json:"application,omitempty"` Files []VersionFile `gorm:"foreignKey:VersionID" json:"files,omitempty"` diff --git a/backend/internal/router/admin/applications.go b/backend/internal/router/admin/applications.go index 8ef9b11..010467a 100644 --- a/backend/internal/router/admin/applications.go +++ b/backend/internal/router/admin/applications.go @@ -871,15 +871,14 @@ func handleCreateVersion(c *gin.Context) { Version string `json:"version"` Description string `json:"description"` FilePath string `json:"file_path"` - ForceUpdate bool `json:"force_update"` } if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, 400, "参数错误") return } - fmt.Printf("接收到的完整请求: Version=%s, Description=%s, FilePath=%s, ForceUpdate=%v\n", - req.Version, req.Description, req.FilePath, req.ForceUpdate) + fmt.Printf("接收到的完整请求: Version=%s, Description=%s, FilePath=%s\n", + req.Version, req.Description, req.FilePath) finalFilePath := req.FilePath fileSize := int64(0) @@ -904,7 +903,6 @@ func handleCreateVersion(c *gin.Context) { Version: req.Version, Description: req.Description, FilePath: filePath, - ForceUpdate: req.ForceUpdate, Status: "active", FileSize: fileSize, } @@ -954,7 +952,6 @@ func handleUpdateVersion(c *gin.Context) { Version string `json:"version"` Description string `json:"description"` FilePath string `json:"file_path"` - ForceUpdate bool `json:"force_update"` Status string `json:"status"` } if err := c.ShouldBindJSON(&req); err != nil { @@ -971,7 +968,6 @@ func handleUpdateVersion(c *gin.Context) { version.Version = req.Version version.Description = req.Description version.FilePath = req.FilePath - version.ForceUpdate = req.ForceUpdate version.Status = req.Status if err := database.DB.Save(&version).Error; err != nil { diff --git a/backend/internal/router/admin/versions.go b/backend/internal/router/admin/versions.go index 18db600..110ed83 100644 --- a/backend/internal/router/admin/versions.go +++ b/backend/internal/router/admin/versions.go @@ -42,6 +42,7 @@ func handleGetAllVersions(c *gin.Context) { pageSize := c.DefaultQuery("page_size", "20") applicationIDFilter := c.Query("application_id") updateStrategyFilter := c.Query("update_strategy") + updateTypeFilter := c.Query("update_type") updateMethodFilter := c.Query("update_method") searchFilter := c.Query("search") @@ -82,6 +83,9 @@ func handleGetAllVersions(c *gin.Context) { if updateStrategyFilter != "" { countQuery = countQuery.Where("update_strategy = ?", updateStrategyFilter) } + if updateTypeFilter != "" { + countQuery = countQuery.Where("update_type = ?", updateTypeFilter) + } if updateMethodFilter != "" { countQuery = countQuery.Where("update_method = ?", updateMethodFilter) } @@ -98,6 +102,9 @@ func handleGetAllVersions(c *gin.Context) { if updateStrategyFilter != "" { query = query.Where("update_strategy = ?", updateStrategyFilter) } + if updateTypeFilter != "" { + query = query.Where("update_type = ?", updateTypeFilter) + } if updateMethodFilter != "" { query = query.Where("update_method = ?", updateMethodFilter) } @@ -223,8 +230,8 @@ func handleGetVersionByID(c *gin.Context) { "file_path": version.FilePath, "file_size": version.FileSize, "file_hash": version.FileHash, - "force_update": version.ForceUpdate, "update_strategy": version.UpdateStrategy, + "update_type": version.UpdateType, "update_method": version.UpdateMethod, "changelog": version.Changelog, "status": computedStatus, @@ -242,8 +249,8 @@ type CreateVersionRequest struct { FileSize int64 `json:"file_size"` FileHash string `json:"file_hash"` EntryFile string `json:"entry_file"` - ForceUpdate bool `json:"force_update"` UpdateStrategy string `json:"update_strategy"` + UpdateType string `json:"update_type"` UpdateMethod string `json:"update_method"` Description string `json:"description"` Changelog string `json:"changelog"` @@ -282,8 +289,8 @@ func handleCreateVersionGlobal(c *gin.Context) { FileSize: req.FileSize, FileHash: req.FileHash, EntryFile: req.EntryFile, - ForceUpdate: req.ForceUpdate, UpdateStrategy: req.UpdateStrategy, + UpdateType: req.UpdateType, UpdateMethod: req.UpdateMethod, Description: req.Description, Changelog: req.Changelog, @@ -293,17 +300,16 @@ func handleCreateVersionGlobal(c *gin.Context) { if version.UpdateStrategy == "" { version.UpdateStrategy = "optional" } + if version.UpdateType == "" { + version.UpdateType = "full" + } if version.UpdateMethod == "" { - version.UpdateMethod = "manual" + version.UpdateMethod = "auto" } - if version.UpdateStrategy == "forced" { - version.ForceUpdate = true - } - - if version.UpdateMethod == "hot" { + if version.UpdateType == "patch" { var lastFullVersion model.Version - if err := database.DB.Where("application_id = ? AND update_method = ?", req.ApplicationID, "full"). + if err := database.DB.Where("application_id = ? AND update_type = ?", req.ApplicationID, "full"). Order("id DESC").First(&lastFullVersion).Error; err == nil { version.BaseVersionID = &lastFullVersion.ID } @@ -333,6 +339,7 @@ func handleCreateVersionGlobal(c *gin.Context) { type UpdateVersionRequest struct { Version string `json:"version"` UpdateStrategy string `json:"update_strategy"` + UpdateType string `json:"update_type"` UpdateMethod string `json:"update_method"` Description string `json:"description"` Changelog string `json:"changelog"` @@ -376,8 +383,8 @@ func handleUpdateVersionGlobal(c *gin.Context) { } } - log.Printf("[DEBUG] handleUpdateVersionGlobal versionID=%s, req: version=%s, update_strategy=%s, update_method=%s, description=%s\n", - versionID, req.Version, req.UpdateStrategy, req.UpdateMethod, req.Description) + log.Printf("[DEBUG] handleUpdateVersionGlobal versionID=%s, req: version=%s, update_strategy=%s, update_type=%s, update_method=%s, description=%s\n", + versionID, req.Version, req.UpdateStrategy, req.UpdateType, req.UpdateMethod, req.Description) updates := map[string]interface{}{} if req.Version != "" { @@ -385,11 +392,9 @@ func handleUpdateVersionGlobal(c *gin.Context) { } if req.UpdateStrategy != "" { updates["update_strategy"] = req.UpdateStrategy - if req.UpdateStrategy == "forced" { - updates["force_update"] = true - } else { - updates["force_update"] = false - } + } + if req.UpdateType != "" { + updates["update_type"] = req.UpdateType } if req.UpdateMethod != "" { updates["update_method"] = req.UpdateMethod diff --git a/backend/internal/router/app/info.go b/backend/internal/router/app/info.go index 8492636..63ad420 100644 --- a/backend/internal/router/app/info.go +++ b/backend/internal/router/app/info.go @@ -103,6 +103,7 @@ func handleAppCheckUpdate(c *gin.Context) { "entry_file": latestVersion.EntryFile, "update_notes": latestVersion.Description, "update_strategy": latestVersion.UpdateStrategy, + "update_type": latestVersion.UpdateType, "update_method": latestVersion.UpdateMethod, "changelog": latestVersion.Changelog, "files": files, diff --git a/backend/scripts/update_api_docs.go b/backend/scripts/update_api_docs.go index 353433a..ca31a6e 100644 --- a/backend/scripts/update_api_docs.go +++ b/backend/scripts/update_api_docs.go @@ -1,4 +1,4 @@ -package main +package main import ( "log" @@ -122,10 +122,12 @@ GET /api/v1/app/{appKey}/check-update?version=1.0.0 "message": "success", "data": { "has_update": true, - "force_update": false, "latest_version": "2.0.0", "download_url": "http://example.com/download/app-v2.0.0.zip", "file_size": 10240000, + "update_strategy": "optional", + "update_type": "full", + "update_method": "auto", "description": "新版本修复了若干bug" } } diff --git a/frontend/src/pages/admin/versions/[id].vue b/frontend/src/pages/admin/versions/[id].vue index 15154f7..39c516b 100644 --- a/frontend/src/pages/admin/versions/[id].vue +++ b/frontend/src/pages/admin/versions/[id].vue @@ -44,7 +44,8 @@ const formData = ref({ version: '', description: '', update_strategy: 'optional' as 'optional' | 'forced', - update_method: 'manual' as 'manual' | 'hot' | 'full', + update_type: 'full' as 'full' | 'patch', + update_method: 'auto' as 'auto' | 'manual', changelog: '', entry_file: '', file: null as File | null, @@ -94,7 +95,8 @@ async function fetchVersion() { formData.value.version = ver.version || '' formData.value.description = ver.description || '' formData.value.update_strategy = ver.update_strategy || 'optional' - formData.value.update_method = ver.update_method || 'manual' + formData.value.update_type = ver.update_type || 'full' + formData.value.update_method = ver.update_method || 'auto' formData.value.changelog = ver.changelog || '' formData.value.entry_file = ver.entry_file || '' } @@ -180,6 +182,7 @@ async function handleSubmit() { version: formData.value.version, description: formData.value.description, update_strategy: formData.value.update_strategy, + update_type: formData.value.update_type, update_method: formData.value.update_method, changelog: formData.value.changelog, entry_file: formData.value.entry_file, @@ -423,6 +426,23 @@ onMounted(() => { /> +
+ {{ t('admin.versions.createForm.updateType') }} + + + + + + + {{ t('admin.versions.types.full') }} + + + {{ t('admin.versions.types.patch') }} + + + +
+
{{ t('admin.versions.createForm.updateMethod') }} @@ -430,11 +450,8 @@ onMounted(() => { - - {{ t('admin.versions.methods.full') }} - - - {{ t('admin.versions.methods.hot') }} + + {{ t('admin.versions.methods.auto') }} {{ t('admin.versions.methods.manual') }} @@ -485,11 +502,17 @@ onMounted(() => { {{ t('admin.versions.createForm.previewForcedUpdate') }} {{ formData.update_strategy === 'forced' ? t('common.yes') : t('common.no') }}
+
+ {{ t('admin.versions.createForm.previewUpdateType') }} + {{ + formData.update_type === 'full' ? t('admin.versions.types.full') + : t('admin.versions.types.patch') + }} +
{{ t('admin.versions.createForm.previewUpdateMethod') }} {{ - formData.update_method === 'full' ? t('admin.versions.methods.full') - : formData.update_method === 'hot' ? t('admin.versions.methods.hot') + formData.update_method === 'auto' ? t('admin.versions.methods.auto') : t('admin.versions.methods.manual') }}
diff --git a/frontend/src/pages/admin/versions/components/columns.ts b/frontend/src/pages/admin/versions/components/columns.ts index 228f64b..d4c526b 100644 --- a/frontend/src/pages/admin/versions/components/columns.ts +++ b/frontend/src/pages/admin/versions/components/columns.ts @@ -69,21 +69,34 @@ export function getColumns(options: ColumnOptions, t: (key: string) => string): return h(Badge, { class: strategyClasses[row.original.update_strategy] }, () => strategyLabels[row.original.update_strategy]) }, }, + { + accessorKey: 'update_type', + header: () => t('admin.versions.columns.type'), + cell: ({ row }) => { + const typeClasses: Record = { + full: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400', + patch: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400', + } + const typeLabels: Record = { + full: t('admin.versions.types.full'), + patch: t('admin.versions.types.patch'), + } + return h(Badge, { class: typeClasses[row.original.update_type] || typeClasses.full }, () => typeLabels[row.original.update_type] || row.original.update_type) + }, + }, { accessorKey: 'update_method', header: () => t('admin.versions.columns.method'), cell: ({ row }) => { const methodClasses: Record = { + auto: 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400', manual: 'bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-400', - hot: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400', - full: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400', } const methodLabels: Record = { + auto: t('admin.versions.methods.auto'), manual: t('admin.versions.methods.manual'), - hot: t('admin.versions.methods.hot'), - full: t('admin.versions.methods.full'), } - return h(Badge, { class: methodClasses[row.original.update_method] }, () => methodLabels[row.original.update_method]) + return h(Badge, { class: methodClasses[row.original.update_method] || methodClasses.manual }, () => methodLabels[row.original.update_method] || row.original.update_method) }, }, { diff --git a/frontend/src/pages/admin/versions/create.vue b/frontend/src/pages/admin/versions/create.vue index e2bd20d..11e7c43 100644 --- a/frontend/src/pages/admin/versions/create.vue +++ b/frontend/src/pages/admin/versions/create.vue @@ -43,7 +43,8 @@ const formData = ref({ version: '', description: '', update_strategy: 'optional' as 'optional' | 'forced', - update_method: 'manual' as 'manual' | 'hot' | 'full', + update_type: 'full' as 'full' | 'patch', + update_method: 'auto' as 'auto' | 'manual', changelog: '', entry_file: '', file: null as File | null, @@ -85,10 +86,17 @@ function formatFileSize(size: number): string { return `${(size / (1024 * 1024)).toFixed(2)} MB` } +const typeLabel = computed(() => { + const map: Record = { + full: t('admin.versions.types.full'), + patch: t('admin.versions.types.patch'), + } + return map[formData.value.update_type] || formData.value.update_type +}) + const methodLabel = computed(() => { const map: Record = { - full: t('admin.versions.methods.full'), - hot: t('admin.versions.methods.hot'), + auto: t('admin.versions.methods.auto'), manual: t('admin.versions.methods.manual'), } return map[formData.value.update_method] || formData.value.update_method @@ -178,6 +186,7 @@ async function handleSubmit() { version: formData.value.version, description: formData.value.description, update_strategy: formData.value.update_strategy, + update_type: formData.value.update_type, update_method: formData.value.update_method, changelog: formData.value.changelog, entry_file: formData.value.entry_file, @@ -411,6 +420,23 @@ onMounted(() => { /> +
+ {{ t('admin.versions.createForm.updateType') }} + + + + + + + {{ t('admin.versions.types.full') }} + + + {{ t('admin.versions.types.patch') }} + + + +
+
{{ t('admin.versions.createForm.updateMethod') }} @@ -418,11 +444,8 @@ onMounted(() => { - - {{ t('admin.versions.methods.full') }} - - - {{ t('admin.versions.methods.hot') }} + + {{ t('admin.versions.methods.auto') }} {{ t('admin.versions.methods.manual') }} @@ -485,6 +508,10 @@ onMounted(() => { {{ t('admin.versions.createForm.previewForcedUpdate') }} {{ formData.update_strategy === 'forced' ? t('common.yes') : t('common.no') }}
+
+ {{ t('admin.versions.createForm.previewUpdateType') }} + {{ typeLabel }} +
{{ t('admin.versions.createForm.previewUpdateMethod') }} {{ methodLabel }} diff --git a/frontend/src/pages/admin/versions/data/schema.ts b/frontend/src/pages/admin/versions/data/schema.ts index 549b764..3a0b8de 100644 --- a/frontend/src/pages/admin/versions/data/schema.ts +++ b/frontend/src/pages/admin/versions/data/schema.ts @@ -1,7 +1,8 @@ import { z } from 'zod' export const updateStrategySchema = z.enum(['optional', 'forced']) -export const updateMethodSchema = z.enum(['manual', 'hot', 'full']) +export const updateTypeSchema = z.enum(['full', 'patch']) +export const updateMethodSchema = z.enum(['auto', 'manual']) export const versionStatusSchema = z.enum(['active', 'superseded']) export const versionSchema = z.object({ @@ -12,8 +13,8 @@ export const versionSchema = z.object({ description: z.string(), file_path: z.string(), file_size: z.number(), - force_update: z.boolean(), update_strategy: updateStrategySchema, + update_type: updateTypeSchema, update_method: updateMethodSchema, status: versionStatusSchema, changelog: z.string(), diff --git a/frontend/src/pages/admin/versions/index.vue b/frontend/src/pages/admin/versions/index.vue index 34d4bd0..e31ad0c 100644 --- a/frontend/src/pages/admin/versions/index.vue +++ b/frontend/src/pages/admin/versions/index.vue @@ -30,6 +30,7 @@ const tableRef = ref() const appFilter = ref('') const strategyFilter = ref('') +const typeFilter = ref('') const methodFilter = ref('') const searchFilter = ref('') const currentPage = ref(1) @@ -56,10 +57,14 @@ const strategyOptions = computed(() => [ { label: t('admin.versions.strategies.forced'), value: 'forced' }, ]) +const typeOptions = computed(() => [ + { label: t('admin.versions.types.full'), value: 'full' }, + { label: t('admin.versions.types.patch'), value: 'patch' }, +]) + const methodOptions = computed(() => [ + { label: t('admin.versions.methods.auto'), value: 'auto' }, { label: t('admin.versions.methods.manual'), value: 'manual' }, - { label: t('admin.versions.methods.hot'), value: 'hot' }, - { label: t('admin.versions.methods.full'), value: 'full' }, ]) const totalSize = computed(() => { @@ -113,6 +118,9 @@ async function fetchVersions() { if (strategyFilter.value) { params.append('update_strategy', strategyFilter.value) } + if (typeFilter.value) { + params.append('update_type', typeFilter.value) + } if (methodFilter.value) { params.append('update_method', methodFilter.value) } @@ -179,6 +187,8 @@ function handleExport() { params.append('application_id', appFilter.value) if (strategyFilter.value) params.append('update_strategy', strategyFilter.value) + if (typeFilter.value) + params.append('update_type', typeFilter.value) if (methodFilter.value) params.append('update_method', methodFilter.value) @@ -230,7 +240,7 @@ watch([currentPage, pageSize], () => { fetchVersions() }) -watch([appFilter, strategyFilter, methodFilter, searchFilter], () => { +watch([appFilter, strategyFilter, typeFilter, methodFilter, searchFilter], () => { currentPage.value = 1 fetchVersions() }) @@ -336,6 +346,11 @@ watch([appFilter, strategyFilter, methodFilter, searchFilter], () => { :title="t('admin.versions.strategy')" :options="strategyOptions" /> +