应用市场详情页点击安装实现,点击安装增加弹窗显示进度

This commit is contained in:
jingrow 2025-10-27 02:05:09 +08:00
parent 4504c3e8bf
commit 6a90f5ec39
2 changed files with 185 additions and 12 deletions

View File

@ -113,13 +113,36 @@
</div> </div>
</div> </div>
<!-- 安装进度弹窗 -->
<n-modal v-model:show="showProgressModal" preset="card" style="width: 500px">
<template #header>
<h3>{{ t('Installing App') }}</h3>
</template>
<div class="progress-content">
<n-progress
:percentage="installProgress"
:status="installStatus"
:show-indicator="true"
/>
<n-text class="progress-text">{{ installMessage }}</n-text>
</div>
<template #action>
<n-button @click="showProgressModal = false" :disabled="installing">
{{ installing ? t('Installing...') : t('Close') }}
</n-button>
</template>
</n-modal>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, computed } from 'vue' import { ref, onMounted, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { NButton, NIcon, NSpin, NEmpty, useMessage } from 'naive-ui' import { get_session_api_headers } from '@/shared/api/auth'
import { NButton, NIcon, NSpin, NEmpty, useMessage, NModal, NProgress, NText } from 'naive-ui'
import { Icon } from '@iconify/vue' import { Icon } from '@iconify/vue'
import axios from 'axios' import axios from 'axios'
import { t } from '@/shared/i18n' import { t } from '@/shared/i18n'
@ -132,6 +155,13 @@ const loading = ref(true)
const error = ref('') const error = ref('')
const app = ref<any>(null) const app = ref<any>(null)
//
const installing = ref(false)
const installProgress = ref(0)
const installMessage = ref('')
const installStatus = ref<'success' | 'error' | 'info'>('info')
const showProgressModal = ref(false)
const appName = computed(() => route.params.name as string) const appName = computed(() => route.params.name as string)
async function loadAppDetail() { async function loadAppDetail() {
@ -173,9 +203,66 @@ function goBack() {
router.push('/app-marketplace') router.push('/app-marketplace')
} }
function installApp() { async function installApp() {
// TODO: if (!app.value?.file_url) {
message.info(t('Install feature coming soon')) message.error(t('应用文件URL不存在'))
return
}
try {
installing.value = true
installProgress.value = 0
installMessage.value = t('正在下载应用包...')
installStatus.value = 'info'
showProgressModal.value = true
//
setTimeout(() => {
installProgress.value = 30
installMessage.value = t('正在安装应用...')
}, 500)
const response = await axios.post('/jingrow/install-from-url', new URLSearchParams({
url: app.value.file_url
}), {
headers: {
...get_session_api_headers(),
'Content-Type': 'application/x-www-form-urlencoded'
}
})
setTimeout(() => {
installProgress.value = 80
installMessage.value = t('正在同步数据库...')
}, 1000)
setTimeout(() => {
installProgress.value = 100
}, 1500)
if (response.data.success) {
installStatus.value = 'success'
installMessage.value = t('应用安装成功!')
message.success(t('应用安装成功'))
setTimeout(() => {
showProgressModal.value = false
}, 2000)
} else {
throw new Error(response.data.error || t('安装失败'))
}
} catch (error: any) {
console.error('Install app error:', error)
installStatus.value = 'error'
installMessage.value = error.response?.data?.detail || error.message || t('安装失败')
message.error(error.response?.data?.detail || t('安装失败'))
setTimeout(() => {
showProgressModal.value = false
}, 3000)
} finally {
installing.value = false
}
} }
onMounted(() => { onMounted(() => {
@ -405,6 +492,18 @@ onMounted(() => {
font-style: italic; font-style: italic;
} }
.progress-content {
display: flex;
flex-direction: column;
gap: 16px;
padding: 20px 0;
}
.progress-text {
text-align: center;
color: #666;
}
@media (max-width: 768px) { @media (max-width: 768px) {
.app-card { .app-card {
padding: 20px; padding: 20px;

View File

@ -130,13 +130,35 @@
</n-empty> </n-empty>
</div> </div>
</div> </div>
<!-- 安装进度弹窗 -->
<n-modal v-model:show="showProgressModal" preset="card" style="width: 500px">
<template #header>
<h3>{{ t('Installing App') }}</h3>
</template>
<div class="progress-content">
<n-progress
:percentage="installProgress"
:status="installStatus"
:show-indicator="true"
/>
<n-text class="progress-text">{{ installMessage }}</n-text>
</div>
<template #action>
<n-button @click="showProgressModal = false" :disabled="installing">
{{ installing ? t('Installing...') : t('Close') }}
</n-button>
</template>
</n-modal>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, computed, watch } from 'vue' import { ref, onMounted, computed, watch } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { NInput, NButton, NIcon, NSpin, NEmpty, NSelect, NPagination, useMessage } from 'naive-ui' import { NInput, NButton, NIcon, NSpin, NEmpty, NSelect, NPagination, useMessage, NModal, NProgress, NText } from 'naive-ui'
import { Icon } from '@iconify/vue' import { Icon } from '@iconify/vue'
import axios from 'axios' import axios from 'axios'
import { t } from '@/shared/i18n' import { t } from '@/shared/i18n'
@ -153,6 +175,13 @@ const page = ref(1)
const pageSize = ref(parseInt(localStorage.getItem('itemsPerPage') || '20')) const pageSize = ref(parseInt(localStorage.getItem('itemsPerPage') || '20'))
const sortBy = ref('creation desc') const sortBy = ref('creation desc')
//
const installing = ref(false)
const installProgress = ref(0)
const installMessage = ref('')
const installStatus = ref<'success' | 'error' | 'info'>('info')
const showProgressModal = ref(false)
// //
const sortOptions = computed(() => [ const sortOptions = computed(() => [
{ label: t('Latest'), value: 'creation desc' }, { label: t('Latest'), value: 'creation desc' },
@ -214,11 +243,23 @@ function viewAppDetail(app: any) {
} }
async function installApp(app: any) { async function installApp(app: any) {
if (!app.file_url) {
message.error(t('应用文件URL不存在'))
return
}
try { try {
if (!app.file_url) { installing.value = true
message.error(t('应用文件URL不存在')) installProgress.value = 0
return installMessage.value = t('正在下载应用包...')
} installStatus.value = 'info'
showProgressModal.value = true
//
setTimeout(() => {
installProgress.value = 30
installMessage.value = t('正在安装应用...')
}, 500)
const response = await axios.post('/jingrow/install-from-url', new URLSearchParams({ const response = await axios.post('/jingrow/install-from-url', new URLSearchParams({
url: app.file_url url: app.file_url
@ -229,16 +270,37 @@ async function installApp(app: any) {
} }
}) })
setTimeout(() => {
installProgress.value = 80
installMessage.value = t('正在同步数据库...')
}, 1000)
setTimeout(() => {
installProgress.value = 100
}, 1500)
if (response.data.success) { if (response.data.success) {
installStatus.value = 'success'
installMessage.value = t('应用安装成功!')
message.success(t('应用安装成功')) message.success(t('应用安装成功'))
//
router.push('/installed-apps') setTimeout(() => {
showProgressModal.value = false
}, 2000)
} else { } else {
message.error(response.data.error || t('安装失败')) throw new Error(response.data.error || t('安装失败'))
} }
} catch (error: any) { } catch (error: any) {
console.error('Install app error:', error) console.error('Install app error:', error)
installStatus.value = 'error'
installMessage.value = error.response?.data?.detail || error.message || t('安装失败')
message.error(error.response?.data?.detail || t('安装失败')) message.error(error.response?.data?.detail || t('安装失败'))
setTimeout(() => {
showProgressModal.value = false
}, 3000)
} finally {
installing.value = false
} }
} }
@ -591,4 +653,16 @@ watch(() => localStorage.getItem('itemsPerPage'), (newValue) => {
height: 180px; height: 180px;
} }
} }
.progress-content {
display: flex;
flex-direction: column;
gap: 16px;
padding: 20px 0;
}
.progress-text {
text-align: center;
color: #666;
}
</style> </style>