feat: 版本管理支持多入口文件、安装选项(桌面快捷方式/开始菜单/自动运行/默认安装目录)
This commit is contained in:
@@ -227,6 +227,11 @@ type Version struct {
|
|||||||
FileSize int64 `json:"file_size"`
|
FileSize int64 `json:"file_size"`
|
||||||
FileHash string `gorm:"size:64" json:"file_hash"`
|
FileHash string `gorm:"size:64" json:"file_hash"`
|
||||||
EntryFile string `gorm:"size:255" json:"entry_file"`
|
EntryFile string `gorm:"size:255" json:"entry_file"`
|
||||||
|
EntryFiles string `gorm:"type:text" json:"entry_files"` // JSON数组,多入口文件
|
||||||
|
CreateDesktopShortcut bool `gorm:"default:false" json:"create_desktop_shortcut"` // 是否创建桌面快捷方式
|
||||||
|
CreateStartMenu bool `gorm:"default:false" json:"create_start_menu"` // 是否创建开始菜单
|
||||||
|
AutoRun bool `gorm:"default:false" json:"auto_run"` // 是否自动运行
|
||||||
|
DefaultInstallDir string `gorm:"size:500" json:"default_install_dir"` // 默认安装目录
|
||||||
UpdateStrategy string `gorm:"size:20;default:optional" json:"update_strategy"`
|
UpdateStrategy string `gorm:"size:20;default:optional" json:"update_strategy"`
|
||||||
UpdateType string `gorm:"size:20;default:full" json:"update_type"`
|
UpdateType string `gorm:"size:20;default:full" json:"update_type"`
|
||||||
UpdateMethod string `gorm:"size:20;default:auto" json:"update_method"`
|
UpdateMethod string `gorm:"size:20;default:auto" json:"update_method"`
|
||||||
|
|||||||
@@ -260,20 +260,25 @@ func handleGetVersionByID(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CreateVersionRequest struct {
|
type CreateVersionRequest struct {
|
||||||
ApplicationID uint `json:"application_id"`
|
ApplicationID uint `json:"application_id"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
FilePath string `json:"file_path"`
|
FilePath string `json:"file_path"`
|
||||||
FileSize int64 `json:"file_size"`
|
FileSize int64 `json:"file_size"`
|
||||||
FileHash string `json:"file_hash"`
|
FileHash string `json:"file_hash"`
|
||||||
EntryFile string `json:"entry_file"`
|
EntryFile string `json:"entry_file"`
|
||||||
UpdateStrategy string `json:"update_strategy"`
|
EntryFiles string `json:"entry_files"`
|
||||||
UpdateType string `json:"update_type"`
|
CreateDesktopShortcut bool `json:"create_desktop_shortcut"`
|
||||||
UpdateMethod string `json:"update_method"`
|
CreateStartMenu bool `json:"create_start_menu"`
|
||||||
CustomDownloadURL string `json:"custom_download_url"`
|
AutoRun bool `json:"auto_run"`
|
||||||
Description string `json:"description"`
|
DefaultInstallDir string `json:"default_install_dir"`
|
||||||
Changelog string `json:"changelog"`
|
UpdateStrategy string `json:"update_strategy"`
|
||||||
BaseVersionID *uint `json:"base_version_id"`
|
UpdateType string `json:"update_type"`
|
||||||
Files []VersionFileInput `json:"files"`
|
UpdateMethod string `json:"update_method"`
|
||||||
|
CustomDownloadURL string `json:"custom_download_url"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Changelog string `json:"changelog"`
|
||||||
|
BaseVersionID *uint `json:"base_version_id"`
|
||||||
|
Files []VersionFileInput `json:"files"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type VersionFileInput struct {
|
type VersionFileInput struct {
|
||||||
@@ -312,19 +317,24 @@ func handleCreateVersionGlobal(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
version := model.Version{
|
version := model.Version{
|
||||||
ApplicationID: req.ApplicationID,
|
ApplicationID: req.ApplicationID,
|
||||||
Version: req.Version,
|
Version: req.Version,
|
||||||
FilePath: req.FilePath,
|
FilePath: req.FilePath,
|
||||||
FileSize: req.FileSize,
|
FileSize: req.FileSize,
|
||||||
FileHash: req.FileHash,
|
FileHash: req.FileHash,
|
||||||
EntryFile: req.EntryFile,
|
EntryFile: req.EntryFile,
|
||||||
UpdateStrategy: req.UpdateStrategy,
|
EntryFiles: req.EntryFiles,
|
||||||
UpdateType: req.UpdateType,
|
CreateDesktopShortcut: req.CreateDesktopShortcut,
|
||||||
UpdateMethod: req.UpdateMethod,
|
CreateStartMenu: req.CreateStartMenu,
|
||||||
CustomDownloadURL: req.CustomDownloadURL,
|
AutoRun: req.AutoRun,
|
||||||
Description: req.Description,
|
DefaultInstallDir: req.DefaultInstallDir,
|
||||||
Changelog: req.Changelog,
|
UpdateStrategy: req.UpdateStrategy,
|
||||||
PublishStatus: "draft",
|
UpdateType: req.UpdateType,
|
||||||
|
UpdateMethod: req.UpdateMethod,
|
||||||
|
CustomDownloadURL: req.CustomDownloadURL,
|
||||||
|
Description: req.Description,
|
||||||
|
Changelog: req.Changelog,
|
||||||
|
PublishStatus: "draft",
|
||||||
}
|
}
|
||||||
|
|
||||||
if version.UpdateStrategy == "" {
|
if version.UpdateStrategy == "" {
|
||||||
@@ -455,19 +465,24 @@ func handleCreateVersionGlobal(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UpdateVersionRequest struct {
|
type UpdateVersionRequest struct {
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
FilePath string `json:"file_path"`
|
FilePath string `json:"file_path"`
|
||||||
FileSize int64 `json:"file_size"`
|
FileSize int64 `json:"file_size"`
|
||||||
FileHash string `json:"file_hash"`
|
FileHash string `json:"file_hash"`
|
||||||
EntryFile string `json:"entry_file"`
|
EntryFile string `json:"entry_file"`
|
||||||
UpdateStrategy string `json:"update_strategy"`
|
EntryFiles string `json:"entry_files"`
|
||||||
UpdateType string `json:"update_type"`
|
CreateDesktopShortcut bool `json:"create_desktop_shortcut"`
|
||||||
UpdateMethod string `json:"update_method"`
|
CreateStartMenu bool `json:"create_start_menu"`
|
||||||
CustomDownloadURL string `json:"custom_download_url"`
|
AutoRun bool `json:"auto_run"`
|
||||||
Description string `json:"description"`
|
DefaultInstallDir string `json:"default_install_dir"`
|
||||||
Changelog string `json:"changelog"`
|
UpdateStrategy string `json:"update_strategy"`
|
||||||
BaseVersionID *uint `json:"base_version_id"`
|
UpdateType string `json:"update_type"`
|
||||||
Files []VersionFileInput `json:"files"`
|
UpdateMethod string `json:"update_method"`
|
||||||
|
CustomDownloadURL string `json:"custom_download_url"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Changelog string `json:"changelog"`
|
||||||
|
BaseVersionID *uint `json:"base_version_id"`
|
||||||
|
Files []VersionFileInput `json:"files"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleUpdateVersionGlobal(c *gin.Context) {
|
func handleUpdateVersionGlobal(c *gin.Context) {
|
||||||
@@ -525,6 +540,11 @@ func handleUpdateVersionGlobal(c *gin.Context) {
|
|||||||
updates["file_hash"] = req.FileHash
|
updates["file_hash"] = req.FileHash
|
||||||
}
|
}
|
||||||
updates["entry_file"] = req.EntryFile
|
updates["entry_file"] = req.EntryFile
|
||||||
|
updates["entry_files"] = req.EntryFiles
|
||||||
|
updates["create_desktop_shortcut"] = req.CreateDesktopShortcut
|
||||||
|
updates["create_start_menu"] = req.CreateStartMenu
|
||||||
|
updates["auto_run"] = req.AutoRun
|
||||||
|
updates["default_install_dir"] = req.DefaultInstallDir
|
||||||
if req.UpdateStrategy != "" {
|
if req.UpdateStrategy != "" {
|
||||||
updates["update_strategy"] = req.UpdateStrategy
|
updates["update_strategy"] = req.UpdateStrategy
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,17 +114,22 @@ func handleAppCheckUpdate(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
result := gin.H{
|
result := gin.H{
|
||||||
"has_update": true,
|
"has_update": true,
|
||||||
"latest_version": latestVersion.Version,
|
"latest_version": latestVersion.Version,
|
||||||
"download_url": downloadURL,
|
"download_url": downloadURL,
|
||||||
"file_size": downloadSize,
|
"file_size": downloadSize,
|
||||||
"file_hash": downloadHash,
|
"file_hash": downloadHash,
|
||||||
"entry_file": latestVersion.EntryFile,
|
"entry_file": latestVersion.EntryFile,
|
||||||
"update_notes": latestVersion.Description,
|
"entry_files": latestVersion.EntryFiles,
|
||||||
"update_strategy": latestVersion.UpdateStrategy,
|
"create_desktop_shortcut": latestVersion.CreateDesktopShortcut,
|
||||||
"update_type": latestVersion.UpdateType,
|
"create_start_menu": latestVersion.CreateStartMenu,
|
||||||
"update_method": latestVersion.UpdateMethod,
|
"auto_run": latestVersion.AutoRun,
|
||||||
"changelog": latestVersion.Changelog,
|
"default_install_dir": latestVersion.DefaultInstallDir,
|
||||||
|
"update_notes": latestVersion.Description,
|
||||||
|
"update_strategy": latestVersion.UpdateStrategy,
|
||||||
|
"update_type": latestVersion.UpdateType,
|
||||||
|
"update_method": latestVersion.UpdateMethod,
|
||||||
|
"changelog": latestVersion.Changelog,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 同时返回补丁包信息(如果有),供客户端增量更新使用
|
// 同时返回补丁包信息(如果有),供客户端增量更新使用
|
||||||
@@ -250,18 +255,23 @@ func handleAppCheckUpdate(c *gin.Context) {
|
|||||||
|
|
||||||
// 构建返回结果
|
// 构建返回结果
|
||||||
result := map[string]interface{}{
|
result := map[string]interface{}{
|
||||||
"has_update": true,
|
"has_update": true,
|
||||||
"current_version": clientVersion,
|
"current_version": clientVersion,
|
||||||
"latest_version": latestVersion.Version,
|
"latest_version": latestVersion.Version,
|
||||||
"download_url": downloadURL,
|
"download_url": downloadURL,
|
||||||
"file_size": downloadSize,
|
"file_size": downloadSize,
|
||||||
"file_hash": downloadHash,
|
"file_hash": downloadHash,
|
||||||
"entry_file": latestVersion.EntryFile,
|
"entry_file": latestVersion.EntryFile,
|
||||||
"update_notes": latestVersion.Description,
|
"entry_files": latestVersion.EntryFiles,
|
||||||
"update_strategy": updateStrategy,
|
"create_desktop_shortcut": latestVersion.CreateDesktopShortcut,
|
||||||
"update_type": latestVersion.UpdateType,
|
"create_start_menu": latestVersion.CreateStartMenu,
|
||||||
"update_method": latestVersion.UpdateMethod,
|
"auto_run": latestVersion.AutoRun,
|
||||||
"changelog": latestVersion.Changelog,
|
"default_install_dir": latestVersion.DefaultInstallDir,
|
||||||
|
"update_notes": latestVersion.Description,
|
||||||
|
"update_strategy": updateStrategy,
|
||||||
|
"update_type": latestVersion.UpdateType,
|
||||||
|
"update_method": latestVersion.UpdateMethod,
|
||||||
|
"changelog": latestVersion.Changelog,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果是补丁更新,添加补丁信息
|
// 如果是补丁更新,添加补丁信息
|
||||||
|
|||||||
@@ -53,6 +53,11 @@ const formData = ref({
|
|||||||
custom_download_url: '',
|
custom_download_url: '',
|
||||||
changelog: '',
|
changelog: '',
|
||||||
entry_file: '',
|
entry_file: '',
|
||||||
|
entry_files: [] as string[],
|
||||||
|
create_desktop_shortcut: false,
|
||||||
|
create_start_menu: false,
|
||||||
|
auto_run: false,
|
||||||
|
default_install_dir: '',
|
||||||
file: null as File | null,
|
file: null as File | null,
|
||||||
base_version_id: '' as string,
|
base_version_id: '' as string,
|
||||||
})
|
})
|
||||||
@@ -92,6 +97,16 @@ function formatFileSize(size: number): string {
|
|||||||
return `${(size / (1024 * 1024)).toFixed(2)} MB`
|
return `${(size / (1024 * 1024)).toFixed(2)} MB`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleEntryFile(filePath: string) {
|
||||||
|
const index = formData.value.entry_files.indexOf(filePath)
|
||||||
|
if (index >= 0) {
|
||||||
|
formData.value.entry_files.splice(index, 1)
|
||||||
|
} else {
|
||||||
|
formData.value.entry_files.push(filePath)
|
||||||
|
}
|
||||||
|
formData.value.entry_file = formData.value.entry_files.length > 0 ? formData.value.entry_files[0] : ''
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchVersion() {
|
async function fetchVersion() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
@@ -107,6 +122,16 @@ async function fetchVersion() {
|
|||||||
formData.value.changelog = ver.changelog || ''
|
formData.value.changelog = ver.changelog || ''
|
||||||
formData.value.entry_file = ver.entry_file || ''
|
formData.value.entry_file = ver.entry_file || ''
|
||||||
formData.value.base_version_id = ver.base_version_id ? String(ver.base_version_id) : ''
|
formData.value.base_version_id = ver.base_version_id ? String(ver.base_version_id) : ''
|
||||||
|
formData.value.create_desktop_shortcut = ver.create_desktop_shortcut || false
|
||||||
|
formData.value.create_start_menu = ver.create_start_menu || false
|
||||||
|
formData.value.auto_run = ver.auto_run || false
|
||||||
|
formData.value.default_install_dir = ver.default_install_dir || ''
|
||||||
|
// 解析 entry_files JSON
|
||||||
|
try {
|
||||||
|
formData.value.entry_files = ver.entry_files ? JSON.parse(ver.entry_files) : []
|
||||||
|
} catch {
|
||||||
|
formData.value.entry_files = []
|
||||||
|
}
|
||||||
|
|
||||||
// 加载已有文件信息
|
// 加载已有文件信息
|
||||||
if (ver.file_path && ver.file_size) {
|
if (ver.file_path && ver.file_size) {
|
||||||
@@ -275,6 +300,11 @@ async function handleSubmit() {
|
|||||||
custom_download_url: formData.value.custom_download_url,
|
custom_download_url: formData.value.custom_download_url,
|
||||||
changelog: formData.value.changelog,
|
changelog: formData.value.changelog,
|
||||||
entry_file: formData.value.entry_file,
|
entry_file: formData.value.entry_file,
|
||||||
|
entry_files: JSON.stringify(formData.value.entry_files),
|
||||||
|
create_desktop_shortcut: formData.value.create_desktop_shortcut,
|
||||||
|
create_start_menu: formData.value.create_start_menu,
|
||||||
|
auto_run: formData.value.auto_run,
|
||||||
|
default_install_dir: formData.value.default_install_dir,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果是补丁版本,添加基础版本ID
|
// 如果是补丁版本,添加基础版本ID
|
||||||
@@ -385,6 +415,49 @@ onMounted(() => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 更新类型选择(上传前选择) -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<UiLabel>{{ t('admin.versions.createForm.updateType') }} <span class="text-destructive">*</span></UiLabel>
|
||||||
|
<UiSelect v-model="formData.update_type" :disabled="saving">
|
||||||
|
<UiSelectTrigger>
|
||||||
|
<UiSelectValue />
|
||||||
|
</UiSelectTrigger>
|
||||||
|
<UiSelectContent>
|
||||||
|
<UiSelectItem value="full">
|
||||||
|
{{ t('admin.versions.types.full') }}
|
||||||
|
</UiSelectItem>
|
||||||
|
<UiSelectItem value="patch">
|
||||||
|
{{ t('admin.versions.types.patch') }}
|
||||||
|
</UiSelectItem>
|
||||||
|
</UiSelectContent>
|
||||||
|
</UiSelect>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 基础版本选择(仅补丁类型,上传前选择) -->
|
||||||
|
<div v-if="formData.update_type === 'patch'" class="space-y-2">
|
||||||
|
<UiLabel>{{ t('admin.versions.createForm.baseVersion') }} <span class="text-destructive">*</span></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.updateFile') }}</UiLabel>
|
<UiLabel>{{ t('admin.versions.createForm.updateFile') }}</UiLabel>
|
||||||
<div class="border-2 border-dashed rounded-lg p-4">
|
<div class="border-2 border-dashed rounded-lg p-4">
|
||||||
@@ -534,22 +607,68 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 多选入口文件 -->
|
||||||
<div v-if="executableFiles.length > 0" class="space-y-2">
|
<div v-if="executableFiles.length > 0" class="space-y-2">
|
||||||
<UiLabel>{{ t('admin.versions.createForm.entryFile') }}</UiLabel>
|
<UiLabel>{{ t('admin.versions.createForm.entryFiles') }}</UiLabel>
|
||||||
<UiSelect v-model="formData.entry_file" :disabled="saving">
|
<p class="text-sm text-muted-foreground">
|
||||||
<UiSelectTrigger>
|
{{ t('admin.versions.createForm.entryFilesDesc') }}
|
||||||
<UiSelectValue :placeholder="t('admin.versions.createForm.entryFilePlaceholder')" />
|
</p>
|
||||||
</UiSelectTrigger>
|
<div class="space-y-2">
|
||||||
<UiSelectContent>
|
<div
|
||||||
<UiSelectItem
|
v-for="file in executableFiles"
|
||||||
v-for="file in executableFiles"
|
:key="file.file_path"
|
||||||
:key="file.file_path"
|
class="flex items-center gap-3 rounded-lg border p-3"
|
||||||
:value="file.file_path"
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:checked="formData.entry_files.includes(file.file_path)"
|
||||||
|
class="h-4 w-4 rounded border-gray-300"
|
||||||
|
@change="toggleEntryFile(file.file_path)"
|
||||||
>
|
>
|
||||||
{{ file.file_name }} ({{ formatFileSize(file.file_size) }})
|
<div class="flex-1">
|
||||||
</UiSelectItem>
|
<p class="text-sm font-medium">{{ file.file_name }}</p>
|
||||||
</UiSelectContent>
|
<p class="text-xs text-muted-foreground">{{ file.file_path }} ({{ formatFileSize(file.file_size) }})</p>
|
||||||
</UiSelect>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 安装选项 -->
|
||||||
|
<div class="space-y-4 rounded-lg border p-4">
|
||||||
|
<h4 class="font-medium">{{ t('admin.versions.createForm.installOptions') }}</h4>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="space-y-0.5">
|
||||||
|
<UiLabel class="text-sm">{{ t('admin.versions.createForm.createDesktopShortcut') }}</UiLabel>
|
||||||
|
<p class="text-xs text-muted-foreground">{{ t('admin.versions.createForm.createDesktopShortcutDesc') }}</p>
|
||||||
|
</div>
|
||||||
|
<UiSwitch v-model="formData.create_desktop_shortcut" :disabled="saving" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="space-y-0.5">
|
||||||
|
<UiLabel class="text-sm">{{ t('admin.versions.createForm.createStartMenu') }}</UiLabel>
|
||||||
|
<p class="text-xs text-muted-foreground">{{ t('admin.versions.createForm.createStartMenuDesc') }}</p>
|
||||||
|
</div>
|
||||||
|
<UiSwitch v-model="formData.create_start_menu" :disabled="saving" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="space-y-0.5">
|
||||||
|
<UiLabel class="text-sm">{{ t('admin.versions.createForm.autoRun') }}</UiLabel>
|
||||||
|
<p class="text-xs text-muted-foreground">{{ t('admin.versions.createForm.autoRunDesc') }}</p>
|
||||||
|
</div>
|
||||||
|
<UiSwitch v-model="formData.auto_run" :disabled="saving" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
<UiLabel class="text-sm">{{ t('admin.versions.createForm.defaultInstallDir') }}</UiLabel>
|
||||||
|
<UiInput
|
||||||
|
v-model="formData.default_install_dir"
|
||||||
|
:placeholder="t('admin.versions.createForm.defaultInstallDirPlaceholder')"
|
||||||
|
:disabled="saving"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center justify-between rounded-lg border p-4">
|
<div class="flex items-center justify-between rounded-lg border p-4">
|
||||||
@@ -567,48 +686,6 @@ onMounted(() => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
|
||||||
<UiLabel>{{ t('admin.versions.createForm.updateType') }}</UiLabel>
|
|
||||||
<UiSelect v-model="formData.update_type" :disabled="saving">
|
|
||||||
<UiSelectTrigger>
|
|
||||||
<UiSelectValue />
|
|
||||||
</UiSelectTrigger>
|
|
||||||
<UiSelectContent>
|
|
||||||
<UiSelectItem value="full">
|
|
||||||
{{ t('admin.versions.types.full') }}
|
|
||||||
</UiSelectItem>
|
|
||||||
<UiSelectItem value="patch">
|
|
||||||
{{ t('admin.versions.types.patch') }}
|
|
||||||
</UiSelectItem>
|
|
||||||
</UiSelectContent>
|
|
||||||
</UiSelect>
|
|
||||||
</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">
|
||||||
|
|||||||
@@ -52,6 +52,11 @@ const formData = ref({
|
|||||||
custom_download_url: '',
|
custom_download_url: '',
|
||||||
changelog: '',
|
changelog: '',
|
||||||
entry_file: '',
|
entry_file: '',
|
||||||
|
entry_files: [] as string[],
|
||||||
|
create_desktop_shortcut: false,
|
||||||
|
create_start_menu: false,
|
||||||
|
auto_run: false,
|
||||||
|
default_install_dir: '',
|
||||||
file: null as File | null,
|
file: null as File | null,
|
||||||
base_version_id: '' as string,
|
base_version_id: '' as string,
|
||||||
})
|
})
|
||||||
@@ -92,6 +97,17 @@ function formatFileSize(size: number): string {
|
|||||||
return `${(size / (1024 * 1024)).toFixed(2)} MB`
|
return `${(size / (1024 * 1024)).toFixed(2)} MB`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleEntryFile(filePath: string) {
|
||||||
|
const index = formData.value.entry_files.indexOf(filePath)
|
||||||
|
if (index >= 0) {
|
||||||
|
formData.value.entry_files.splice(index, 1)
|
||||||
|
} else {
|
||||||
|
formData.value.entry_files.push(filePath)
|
||||||
|
}
|
||||||
|
// 兼容旧字段:第一个选中的作为主入口
|
||||||
|
formData.value.entry_file = formData.value.entry_files.length > 0 ? formData.value.entry_files[0] : ''
|
||||||
|
}
|
||||||
|
|
||||||
const typeLabel = computed(() => {
|
const typeLabel = computed(() => {
|
||||||
const map: Record<string, string> = {
|
const map: Record<string, string> = {
|
||||||
full: t('admin.versions.types.full'),
|
full: t('admin.versions.types.full'),
|
||||||
@@ -260,6 +276,11 @@ async function handleSubmit() {
|
|||||||
custom_download_url: formData.value.custom_download_url,
|
custom_download_url: formData.value.custom_download_url,
|
||||||
changelog: formData.value.changelog,
|
changelog: formData.value.changelog,
|
||||||
entry_file: formData.value.entry_file,
|
entry_file: formData.value.entry_file,
|
||||||
|
entry_files: JSON.stringify(formData.value.entry_files),
|
||||||
|
create_desktop_shortcut: formData.value.create_desktop_shortcut,
|
||||||
|
create_start_menu: formData.value.create_start_menu,
|
||||||
|
auto_run: formData.value.auto_run,
|
||||||
|
default_install_dir: formData.value.default_install_dir,
|
||||||
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,
|
||||||
@@ -351,6 +372,49 @@ onMounted(() => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 更新类型选择(上传前选择) -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<UiLabel>{{ t('admin.versions.createForm.updateType') }} <span class="text-destructive">*</span></UiLabel>
|
||||||
|
<UiSelect v-model="formData.update_type" :disabled="saving">
|
||||||
|
<UiSelectTrigger>
|
||||||
|
<UiSelectValue />
|
||||||
|
</UiSelectTrigger>
|
||||||
|
<UiSelectContent>
|
||||||
|
<UiSelectItem value="full">
|
||||||
|
{{ t('admin.versions.types.full') }}
|
||||||
|
</UiSelectItem>
|
||||||
|
<UiSelectItem value="patch">
|
||||||
|
{{ t('admin.versions.types.patch') }}
|
||||||
|
</UiSelectItem>
|
||||||
|
</UiSelectContent>
|
||||||
|
</UiSelect>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 基础版本选择(仅补丁类型,上传前选择) -->
|
||||||
|
<div v-if="formData.update_type === 'patch'" class="space-y-2">
|
||||||
|
<UiLabel>{{ t('admin.versions.createForm.baseVersion') }} <span class="text-destructive">*</span></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.updateFile') }}</UiLabel>
|
<UiLabel>{{ t('admin.versions.createForm.updateFile') }}</UiLabel>
|
||||||
<div class="border-2 border-dashed rounded-lg p-4">
|
<div class="border-2 border-dashed rounded-lg p-4">
|
||||||
@@ -473,25 +537,68 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 多选入口文件 -->
|
||||||
<div v-if="executableFiles.length > 0" class="space-y-2">
|
<div v-if="executableFiles.length > 0" class="space-y-2">
|
||||||
<UiLabel>{{ t('admin.versions.createForm.entryFile') }}</UiLabel>
|
<UiLabel>{{ t('admin.versions.createForm.entryFiles') }}</UiLabel>
|
||||||
<p class="text-sm text-muted-foreground">
|
<p class="text-sm text-muted-foreground">
|
||||||
{{ t('admin.versions.createForm.entryFileDesc') }}
|
{{ t('admin.versions.createForm.entryFilesDesc') }}
|
||||||
</p>
|
</p>
|
||||||
<UiSelect v-model="formData.entry_file" :disabled="saving">
|
<div class="space-y-2">
|
||||||
<UiSelectTrigger>
|
<div
|
||||||
<UiSelectValue :placeholder="t('admin.versions.createForm.entryFilePlaceholder')" />
|
v-for="file in executableFiles"
|
||||||
</UiSelectTrigger>
|
:key="file.file_path"
|
||||||
<UiSelectContent>
|
class="flex items-center gap-3 rounded-lg border p-3"
|
||||||
<UiSelectItem
|
>
|
||||||
v-for="file in executableFiles"
|
<input
|
||||||
:key="file.file_path"
|
type="checkbox"
|
||||||
:value="file.file_path"
|
:checked="formData.entry_files.includes(file.file_path)"
|
||||||
|
class="h-4 w-4 rounded border-gray-300"
|
||||||
|
@change="toggleEntryFile(file.file_path)"
|
||||||
>
|
>
|
||||||
{{ file.file_name }} ({{ formatFileSize(file.file_size) }})
|
<div class="flex-1">
|
||||||
</UiSelectItem>
|
<p class="text-sm font-medium">{{ file.file_name }}</p>
|
||||||
</UiSelectContent>
|
<p class="text-xs text-muted-foreground">{{ file.file_path }} ({{ formatFileSize(file.file_size) }})</p>
|
||||||
</UiSelect>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 安装选项 -->
|
||||||
|
<div class="space-y-4 rounded-lg border p-4">
|
||||||
|
<h4 class="font-medium">{{ t('admin.versions.createForm.installOptions') }}</h4>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="space-y-0.5">
|
||||||
|
<UiLabel class="text-sm">{{ t('admin.versions.createForm.createDesktopShortcut') }}</UiLabel>
|
||||||
|
<p class="text-xs text-muted-foreground">{{ t('admin.versions.createForm.createDesktopShortcutDesc') }}</p>
|
||||||
|
</div>
|
||||||
|
<UiSwitch v-model="formData.create_desktop_shortcut" :disabled="saving" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="space-y-0.5">
|
||||||
|
<UiLabel class="text-sm">{{ t('admin.versions.createForm.createStartMenu') }}</UiLabel>
|
||||||
|
<p class="text-xs text-muted-foreground">{{ t('admin.versions.createForm.createStartMenuDesc') }}</p>
|
||||||
|
</div>
|
||||||
|
<UiSwitch v-model="formData.create_start_menu" :disabled="saving" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="space-y-0.5">
|
||||||
|
<UiLabel class="text-sm">{{ t('admin.versions.createForm.autoRun') }}</UiLabel>
|
||||||
|
<p class="text-xs text-muted-foreground">{{ t('admin.versions.createForm.autoRunDesc') }}</p>
|
||||||
|
</div>
|
||||||
|
<UiSwitch v-model="formData.auto_run" :disabled="saving" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
<UiLabel class="text-sm">{{ t('admin.versions.createForm.defaultInstallDir') }}</UiLabel>
|
||||||
|
<UiInput
|
||||||
|
v-model="formData.default_install_dir"
|
||||||
|
:placeholder="t('admin.versions.createForm.defaultInstallDirPlaceholder')"
|
||||||
|
:disabled="saving"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center justify-between rounded-lg border p-4">
|
<div class="flex items-center justify-between rounded-lg border p-4">
|
||||||
@@ -509,48 +616,6 @@ onMounted(() => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
|
||||||
<UiLabel>{{ t('admin.versions.createForm.updateType') }}</UiLabel>
|
|
||||||
<UiSelect v-model="formData.update_type" :disabled="saving">
|
|
||||||
<UiSelectTrigger>
|
|
||||||
<UiSelectValue />
|
|
||||||
</UiSelectTrigger>
|
|
||||||
<UiSelectContent>
|
|
||||||
<UiSelectItem value="full">
|
|
||||||
{{ t('admin.versions.types.full') }}
|
|
||||||
</UiSelectItem>
|
|
||||||
<UiSelectItem value="patch">
|
|
||||||
{{ t('admin.versions.types.patch') }}
|
|
||||||
</UiSelectItem>
|
|
||||||
</UiSelectContent>
|
|
||||||
</UiSelect>
|
|
||||||
</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">
|
||||||
|
|||||||
@@ -1495,6 +1495,17 @@
|
|||||||
"entryFile": "入口文件",
|
"entryFile": "入口文件",
|
||||||
"entryFileDesc": "选择更新完成后要启动的主程序文件(可选)",
|
"entryFileDesc": "选择更新完成后要启动的主程序文件(可选)",
|
||||||
"entryFilePlaceholder": "选择入口文件(可选)",
|
"entryFilePlaceholder": "选择入口文件(可选)",
|
||||||
|
"entryFiles": "入口文件(多选)",
|
||||||
|
"entryFilesDesc": "勾选更新完成后可以启动的程序文件",
|
||||||
|
"installOptions": "安装选项",
|
||||||
|
"createDesktopShortcut": "创建桌面快捷方式",
|
||||||
|
"createDesktopShortcutDesc": "安装时在桌面创建快捷方式",
|
||||||
|
"createStartMenu": "创建开始菜单",
|
||||||
|
"createStartMenuDesc": "安装时在开始菜单创建快捷方式",
|
||||||
|
"autoRun": "安装后自动运行",
|
||||||
|
"autoRunDesc": "安装完成后自动启动主程序",
|
||||||
|
"defaultInstallDir": "默认安装目录",
|
||||||
|
"defaultInstallDirPlaceholder": "例如: C:\\Program Files\\MyApp",
|
||||||
"forcedUpdate": "强制更新",
|
"forcedUpdate": "强制更新",
|
||||||
"forcedUpdateDesc": "启用后用户必须更新到此版本才能继续使用",
|
"forcedUpdateDesc": "启用后用户必须更新到此版本才能继续使用",
|
||||||
"updateType": "更新类型",
|
"updateType": "更新类型",
|
||||||
|
|||||||
Reference in New Issue
Block a user