<script setup lang="tsx">
|
import { computed, onMounted, reactive, ref } from 'vue'
|
import { useRouter } from 'vue-router'
|
import type { DataTableColumns, FormInst, FormRules, UploadFileInfo } from 'naive-ui'
|
import { NButton } from 'naive-ui'
|
import dayjs from 'dayjs'
|
import { beforeUploadCheck, customUploadRequest } from '@/utils/upload'
|
import { fetchAppVersionPage, releaseAppVersion } from '@/service/api/appVersion'
|
import type { AppVersionItem } from '@/service/api/appVersion'
|
import Pagination from '@/components/common/Pagination.vue'
|
|
const router = useRouter()
|
const listData = ref<AppVersionItem[]>([])
|
const loading = ref(false)
|
const showForm = ref(false)
|
const formMode = ref<'add' | 'view'>('add')
|
const viewingId = ref<number | null>(null)
|
const androidUploading = ref(false)
|
const iosUploading = ref(false)
|
|
const formRef = ref<FormInst | null>(null)
|
const formModel = ref({
|
version: '',
|
androidFiles: [] as UploadFileInfo[],
|
iosFiles: [] as UploadFileInfo[],
|
updateNote: '',
|
isForceUpdate: false,
|
})
|
|
const pagination = reactive({
|
page: 1,
|
pageSize: 10,
|
itemCount: 0,
|
showSizePicker: true,
|
pageSizes: [10, 20, 50],
|
prefix: ({ itemCount }: { itemCount?: number }) => `共 ${itemCount || 0} 条`,
|
onChange: (page: number) => {
|
pagination.page = page
|
getData()
|
},
|
onUpdatePageSize: (pageSize: number) => {
|
pagination.pageSize = pageSize
|
pagination.page = 1
|
getData()
|
},
|
})
|
|
function changePage(page: number, size: number) {
|
pagination.page = page
|
pagination.pageSize = size
|
getData()
|
}
|
|
const rules: FormRules = {
|
version: [
|
{ required: true, message: '请输入版本号', trigger: ['blur', 'input'] },
|
],
|
androidFiles: [
|
{ required: true, type: 'array', message: '请上传安卓安装包', trigger: ['blur', 'change'] },
|
],
|
iosFiles: [
|
{ required: true, type: 'array', message: '请上传 iOS 安装包', trigger: ['blur', 'change'] },
|
],
|
updateNote: [
|
{ required: true, message: '请输入更新说明', trigger: ['blur', 'input'] },
|
],
|
}
|
|
const columns = computed<DataTableColumns<AppVersionItem>>(() => [
|
{
|
title: '序号',
|
key: 'index',
|
width: 80,
|
align: 'left',
|
resizable: true,
|
render: (_row, rowIndex) => rowIndex + 1 + (pagination.page - 1) * pagination.pageSize,
|
},
|
{
|
title: '版本号',
|
key: 'version',
|
width: 168,
|
align: 'left',
|
ellipsis: true,
|
resizable: true,
|
},
|
{
|
title: '更新提示',
|
key: 'updateDescription',
|
align: 'left',
|
ellipsis: { tooltip: true },
|
resizable: true,
|
},
|
{
|
title: '创建时间',
|
key: 'createTime',
|
width: 180,
|
align: 'left',
|
resizable: true,
|
render: row => row.createTime ? dayjs(row.createTime).format('YYYY/MM/DD HH:mm') : '-',
|
},
|
{
|
title: '操作',
|
key: 'actions',
|
width: 80,
|
align: 'left',
|
fixed: 'right',
|
render: (row) => {
|
return (
|
<NButton v-permission="20222" text type="primary" onClick={() => handleView(row)}>
|
查看
|
</NButton>
|
)
|
},
|
},
|
])
|
|
async function getData() {
|
loading.value = true
|
try {
|
const { data, isSuccess } = await fetchAppVersionPage({
|
pageNum: pagination.page,
|
pageSize: pagination.pageSize,
|
})
|
if (isSuccess && data) {
|
listData.value = data.records || []
|
pagination.itemCount = data.total || 0
|
}
|
}
|
finally {
|
loading.value = false
|
}
|
}
|
|
function resetForm() {
|
formModel.value = {
|
version: '',
|
androidFiles: [],
|
iosFiles: [],
|
updateNote: '',
|
isForceUpdate: false,
|
}
|
formRef.value?.restoreValidation()
|
}
|
|
function handleAdd() {
|
formMode.value = 'add'
|
resetForm()
|
showForm.value = true
|
}
|
|
function handleView(row: AppVersionItem) {
|
formMode.value = 'view'
|
viewingId.value = row.id
|
formModel.value = {
|
version: row.version,
|
androidFiles: row.androidFile
|
? [{
|
id: 'android',
|
name: row.androidFile.split('/').pop() || 'android.apk',
|
status: 'finished',
|
url: row.androidFile,
|
}]
|
: [],
|
iosFiles: row.iosFile
|
? [{
|
id: 'ios',
|
name: row.iosFile.split('/').pop() || 'ios.ipa',
|
status: 'finished',
|
url: row.iosFile,
|
}]
|
: [],
|
updateNote: row.updateDescription,
|
isForceUpdate: row.forceUpdateFlag === 1,
|
}
|
showForm.value = true
|
}
|
|
async function handleConfirm() {
|
await formRef.value?.validate(async (errors) => {
|
if (errors) {
|
window.$message?.error('请完善必填信息')
|
return
|
}
|
console.log('androidUploading.value || iosUploading.value', androidUploading.value, iosUploading.value)
|
|
if (androidUploading.value || iosUploading.value) {
|
window.$message?.error('请等待文件上传完成')
|
return
|
}
|
|
const params = {
|
version: formModel.value.version,
|
androidFile: formModel.value.androidFiles[0]?.url || '',
|
iosFile: formModel.value.iosFiles[0]?.url || '',
|
updateDescription: formModel.value.updateNote,
|
forceUpdateFlag: formModel.value.isForceUpdate ? 1 : 0,
|
}
|
console.log('formModel formModel.value', formModel.value)
|
|
if (!params.androidFile || !params.iosFile) {
|
window.$message?.error('请等待文件上传完成')
|
return
|
}
|
|
const { isSuccess } = await releaseAppVersion(params)
|
if (isSuccess) {
|
window.$message?.success('发布成功')
|
showForm.value = false
|
resetForm()
|
getData()
|
}
|
})
|
}
|
|
function handleCancel() {
|
showForm.value = false
|
resetForm()
|
}
|
|
function beforeUploadApk(data: { file: UploadFileInfo, fileList: UploadFileInfo[] }) {
|
return beforeUploadCheck(data, { maxSize: 500, allowedTypes: [] })
|
}
|
|
function beforeUploadIpa(data: { file: UploadFileInfo, fileList: UploadFileInfo[] }) {
|
return beforeUploadCheck(data, { maxSize: 500, allowedTypes: [] })
|
}
|
|
async function customRequestAndroid(options: any) {
|
androidUploading.value = true
|
const originalOnError = options.onError
|
options.onError = () => {
|
formModel.value.androidFiles = []
|
originalOnError?.()
|
}
|
try {
|
let res = await customUploadRequest(options)
|
if (!res) {
|
formModel.value.androidFiles = []
|
}
|
else {
|
formModel.value.androidFiles = [{
|
id: res.id,
|
name: res.name || 'android.apk',
|
status: 'finished',
|
url: res.url || '',
|
}]
|
}
|
}
|
finally {
|
androidUploading.value = false
|
}
|
}
|
|
async function customRequestIos(options: any) {
|
iosUploading.value = true
|
const originalOnError = options.onError
|
options.onError = () => {
|
formModel.value.iosFiles = []
|
originalOnError?.()
|
}
|
try {
|
let res = await customUploadRequest(options)
|
if (!res) {
|
formModel.value.iosFiles = []
|
}
|
else {
|
formModel.value.iosFiles = [{
|
id: res.id,
|
name: res.name || 'ios.ipa',
|
status: 'finished',
|
url: res.url || '',
|
}]
|
}
|
}
|
finally {
|
iosUploading.value = false
|
}
|
}
|
|
onMounted(() => {
|
getData()
|
})
|
</script>
|
|
<template>
|
<div class="roboTaxi">
|
<n-card class="flex-1 robotaxi-table-card">
|
<template #header>
|
<div class="flex items-center justify-between">
|
<div class="flex items-center relative pl-3">
|
<!-- <div class="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-4 bg-[#0068FF] rounded-sm" />
|
<span class="text-base font-bold text-[#1D2129] ml-2">APP版本管理</span> -->
|
</div>
|
<div v-permission="20221">
|
<NButton style="height: 32px; background-color: #E6F4FF; color: #0068FF; border: 1px solid #7ABDFF" @click="handleAdd">
|
新增版本号
|
</NButton>
|
</div>
|
</div>
|
</template>
|
<div class="flex-1 flex flex-col h-full">
|
<n-data-table
|
class="flex-1"
|
:columns="columns"
|
:data="listData"
|
:loading="loading"
|
:bordered="false"
|
flex-height
|
:header-height="18"
|
:row-height="18"
|
/>
|
<Pagination
|
:count="pagination.itemCount"
|
:page="pagination.page"
|
:page-size="pagination.pageSize"
|
class="robotaxi-pagination mt-12px"
|
@change="changePage"
|
/>
|
</div>
|
|
<n-modal
|
:show="showForm"
|
:mask-closable="false"
|
preset="card"
|
:title="formMode === 'add' ? '新增版本号' : '查看版本号'"
|
style="width: 600px"
|
:segmented="{
|
content: true,
|
action: true,
|
}"
|
:content-style="{
|
padding: '16px 24px 24px 24px',
|
}"
|
@close="handleCancel"
|
>
|
<n-form
|
ref="formRef"
|
:model="formModel"
|
:rules="rules"
|
label-placement="left"
|
label-width="auto"
|
:content-style="{
|
padding: '16px 24px 24px 24px',
|
}"
|
require-mark-placement="left"
|
:disabled="formMode === 'view'"
|
inline
|
>
|
<n-grid :cols="2" :x-gap="16" :y-gap="8">
|
<n-gi :span="2">
|
<n-form-item label="版本号" path="version">
|
<n-input v-model:value="formModel.version" placeholder="请输入版本号" />
|
</n-form-item>
|
</n-gi>
|
<n-gi>
|
<n-form-item label="安卓文件" path="androidFiles" required>
|
<div class="flex flex-col gap-2">
|
<n-upload
|
v-model:file-list="formModel.androidFiles"
|
:max="1"
|
accept=".apk"
|
:on-before-upload="beforeUploadApk"
|
:custom-request="customRequestAndroid"
|
:show-file-list="false"
|
:disabled="formMode === 'view'"
|
>
|
<NButton style="width: 100px" :loading="androidUploading" :disabled="androidUploading || formMode === 'view'">
|
{{ formModel.androidFiles.length > 0 ? '重新上传' : '点击上传' }}
|
</NButton>
|
</n-upload>
|
|
<div v-if="formModel.androidFiles.length > 0" class="flex items-center bg-[#F5F6F8] py-2 rounded w-fit gap-3 " style="margin-left: -70px; padding: 12px;">
|
<svg-icons-android-filled class="text-[#00B42A] text-lg" />
|
<span class="text-sm text-[#1D2129] max-w-[180px] truncate" :title="formModel.androidFiles[0].name">{{ formModel.androidFiles[0].name }}</span>
|
<n-spin v-if="androidUploading" size="small" />
|
<icon-park-outline-close v-else-if="formMode !== 'view'" class="text-gray-400 cursor-pointer hover:text-gray-600" @click="formModel.androidFiles = []" />
|
</div>
|
</div>
|
</n-form-item>
|
</n-gi>
|
<n-gi>
|
<n-form-item label="iOS 文件" path="iosFiles" required>
|
<div class="flex flex-col gap-2">
|
<n-upload
|
v-model:file-list="formModel.iosFiles"
|
:max="1"
|
accept=".ipa"
|
:on-before-upload="beforeUploadIpa"
|
:custom-request="customRequestIos"
|
:show-file-list="false"
|
:disabled="formMode === 'view'"
|
>
|
<NButton style="width: 100px" :loading="iosUploading" :disabled="iosUploading || formMode === 'view'">
|
{{ formModel.iosFiles.length > 0 ? '重新上传' : '点击上传' }}
|
</NButton>
|
</n-upload>
|
|
<div v-if="formModel.iosFiles.length > 0" class="flex items-center bg-[#F5F6F8] px-3 py-2 rounded w-fit gap-3" style="margin-left: -70px; padding: 12px;">
|
<ri-apple-fill class="text-black text-lg" />
|
<span class="text-sm text-[#1D2129] max-w-[180px] truncate" :title="formModel.iosFiles[0].name">{{ formModel.iosFiles[0].name }}</span>
|
<n-spin v-if="iosUploading" size="small" />
|
<icon-park-outline-close v-else-if="formMode !== 'view'" class="text-gray-400 cursor-pointer hover:text-gray-600" @click="formModel.iosFiles = []" />
|
</div>
|
</div>
|
</n-form-item>
|
</n-gi>
|
<n-gi :span="2">
|
<n-form-item label="强制更新" path="isForceUpdate">
|
<n-switch v-model:value="formModel.isForceUpdate" />
|
</n-form-item>
|
</n-gi>
|
<n-gi :span="2">
|
<n-form-item label="更新说明" path="updateNote" required label-placement="top">
|
<n-input
|
v-model:value="formModel.updateNote"
|
type="textarea"
|
placeholder="请填写更新内容摘要"
|
:autosize="{ minRows: 4, maxRows: 8 }"
|
/>
|
</n-form-item>
|
</n-gi>
|
</n-grid>
|
</n-form>
|
<template v-if="formMode !== 'view'" #action>
|
<div class="flex justify-end gap-3">
|
<NButton @click="handleCancel">
|
取消
|
</NButton>
|
<NButton v-if="formMode !== 'view'" type="primary" @click="handleConfirm">
|
保存
|
</NButton>
|
</div>
|
</template>
|
</n-modal>
|
</n-card>
|
</div>
|
</template>
|