This commit is contained in:
2026-08-25 16:52:55 +08:00
parent d3ba1c7e48
commit e8812a0a73
34 changed files with 1072 additions and 45 deletions
+5
View File
@@ -3,6 +3,7 @@ import { useRoute, useRouter } from 'vue-router'
import {
List as ListIcon,
Key as KeyIcon,
Refresh as RefreshIcon,
SwitchButton as SwitchIcon,
} from '@element-plus/icons-vue'
@@ -31,6 +32,10 @@ function logout() {
<el-icon><KeyIcon /></el-icon>
<span>授权管理</span>
</el-menu-item>
<el-menu-item index="/app-versions">
<el-icon><RefreshIcon /></el-icon>
<span>版本管理</span>
</el-menu-item>
</el-menu>
</el-aside>
<el-container>
+2
View File
@@ -2,6 +2,7 @@ import { createRouter, createWebHistory } from 'vue-router'
import Login from '../views/Login.vue'
import Orders from '../views/Orders.vue'
import Licenses from '../views/Licenses.vue'
import AppVersions from '../views/AppVersions.vue'
// 生产部署于 /admin/ 前缀(后端静态托管),history 路由由后端 SPA fallback 兜底
const router = createRouter({
@@ -11,6 +12,7 @@ const router = createRouter({
{ path: '/', redirect: '/orders' },
{ path: '/orders', name: 'orders', component: Orders, meta: { title: '订单管理' } },
{ path: '/licenses', name: 'licenses', component: Licenses, meta: { title: '授权管理' } },
{ path: '/app-versions', name: 'appVersions', component: AppVersions, meta: { title: '版本管理' } },
],
})
+164
View File
@@ -0,0 +1,164 @@
<script setup>
import { onMounted, reactive, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { UploadFilled } from '@element-plus/icons-vue'
import request from '../api/request'
const downloadUrl = `${location.origin}/download/observer-latest.apk`
const loading = ref(false)
const list = ref([])
const total = ref(0)
const page = ref(1)
const size = ref(20)
const addVisible = ref(false)
const addForm = reactive({ version: '', notes: '', file: null })
const adding = ref(false)
function load() {
loading.value = true
request
.get('/app-versions', { params: { page: page.value, size: size.value } })
.then((data) => {
list.value = data.list || []
total.value = data.total || 0
})
.finally(() => {
loading.value = false
})
}
function openAdd() {
addForm.version = ''
addForm.notes = ''
addForm.file = null
fileList.value = []
addVisible.value = true
}
function onFileChange(uploadFile) {
addForm.file = uploadFile.raw || null
}
function submitAdd() {
if (!addForm.file) {
ElMessage.warning('请选择 APK 文件')
return
}
adding.value = true
const fd = new FormData()
fd.append('version', addForm.version.trim())
fd.append('notes', addForm.notes.trim())
fd.append('file', addForm.file)
request
.post('/app-versions', fd, { timeout: 300000 })
.then(() => {
ElMessage.success(`已下发版本 ${addForm.version},覆盖服务器最新 APK`)
addVisible.value = false
page.value = 1
load()
})
.finally(() => {
adding.value = false
})
}
const fileList = ref([])
onMounted(load)
</script>
<template>
<el-card shadow="never">
<div class="toolbar">
<span class="tip"> Android检测到服务器版本高于手机已装版本即强制更新不可跳过上传的 APK 覆盖保存为固定文件服务器永远只保留最新一个下载地址 {{ downloadUrl }}</span>
<el-button type="primary" @click="openAdd">下发新版本</el-button>
</div>
<el-table v-loading="loading" :data="list" border stripe>
<el-table-column prop="version" label="版本号" width="140" />
<el-table-column prop="notes" label="更新说明" min-width="260" show-overflow-tooltip>
<template #default="{ row }">{{ row.notes || '-' }}</template>
</el-table-column>
<el-table-column prop="createdAt" label="下发时间" width="180">
<template #default="{ row }">{{ row.createdAt || '-' }}</template>
</el-table-column>
</el-table>
<el-pagination
class="pager"
v-model:current-page="page"
v-model:page-size="size"
:total="total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next"
@change="load"
/>
<el-dialog v-model="addVisible" title="下发新版本(Android" width="480px">
<el-form label-width="90px">
<el-form-item label="版本号">
<el-input v-model="addForm.version" placeholder="如 1.1.0x.y.z,语义化比较,不可重复)" />
</el-form-item>
<el-form-item label="更新说明">
<el-input
v-model="addForm.notes"
type="textarea"
:rows="3"
maxlength="500"
show-word-limit
placeholder="客户端弹窗展示的更新说明(选填)"
/>
</el-form-item>
<el-form-item label="APK 文件">
<el-upload
v-model:file-list="fileList"
:auto-upload="false"
:limit="1"
accept=".apk"
drag
:on-change="onFileChange"
:on-remove="() => (addForm.file = null)"
:on-exceed="() => ElMessage.warning('只能选择一个 APK 文件')"
>
<div class="upload-hint">
<el-icon class="upload-icon"><UploadFilled /></el-icon>
<div>拖拽或点击选择 .apk 文件</div>
</div>
</el-upload>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="addVisible = false">取消</el-button>
<el-button type="primary" :loading="adding" @click="submitAdd">确认下发</el-button>
</template>
</el-dialog>
</el-card>
</template>
<style scoped>
.toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 14px;
}
.tip {
color: #909399;
font-size: 13px;
}
.pager {
margin-top: 14px;
justify-content: flex-end;
}
.upload-hint {
padding: 8px 0;
color: #909399;
}
.upload-icon {
font-size: 40px;
color: #c0c4cc;
margin-bottom: 6px;
}
</style>