Files
erp_sb/electron-vue-template/src/renderer/components/common/UpdateDialog.vue
悠山 02858146b3 style(components): format CSS styles in Vue components
- Remove extra spaces in CSS property declarations
- Consolidate multi-line CSS rules into single lines
- Maintain consistent formatting across component styles
- Improve readability by removing unnecessary line breaks
- Ensure uniform styling structure in scoped CSS blocks
2025-11-28 17:14:00 +08:00

355 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div>
<el-dialog v-model="show" width="522px" :close-on-click-modal="false" align-center
:class="['update-dialog', `stage-${stage}`]"
:title="stage === 'downloading' ? `正在更新 ${appName}` : '软件更新'">
<div v-if="stage === 'check'" class="update-content">
<div class="update-layout">
<div class="left-pane">
<img src="/icon/icon1.png" class="app-icon app-icon-large" alt="App Icon"/>
</div>
<div class="right-pane">
<p class="announce">新版本的"{{ appName }}"已经发布</p>
<p class="desc">{{ appName }} {{ info.latestVersion }} 可供安装您现在的版本是 {{
version
}}要现在安装吗</p>
<div class="update-details form">
<h4>更新信息</h4>
<el-input
v-model="info.updateNotes"
type="textarea"
class="notes-box"
:rows="6"
readonly
resize="none"/>
</div>
<div class="update-actions row">
<div class="update-buttons">
<div class="left-actions">
<el-button size="small" @click="skipVersion">跳过这个版本</el-button>
</div>
<div class="right-actions">
<el-button size="small" @click="remindLater">稍后提醒</el-button>
<el-button size="small" type="primary" @click="start">下载更新</el-button>
</div>
</div>
</div>
</div>
</div>
</div>
<div v-else-if="stage === 'downloading'" class="update-content">
<div class="download-main">
<div class="download-icon">
<img src="/icon/icon1.png" class="app-icon" alt="App Icon"/>
</div>
<div class="download-content">
<div class="download-info">
<p>正在下载安装...</p>
</div>
<div class="download-progress">
<el-progress
:percentage="prog.percentage"
:show-text="false"
:stroke-width="6"
color="#409EFF"/>
<div class="progress-details">
<span style="font-weight: 500">{{ prog.current }} / {{ prog.total }}</span>
<el-button size="small" @click="cancelDownload">取消</el-button>
</div>
</div>
</div>
</div>
</div>
<div v-else-if="stage === 'completed'" class="update-content">
<div class="download-main">
<div class="download-icon">
<img src="/icon/icon1.png" class="app-icon" alt="App Icon"/>
</div>
<div class="download-content">
<div class="download-info">
<p>可以开始安装了</p>
</div>
<div class="download-progress">
<el-progress
:percentage="100"
:show-text="false"
:stroke-width="6"
color="#67C23A"/>
<div class="progress-details">
<span style="font-weight: 500" v-if="prog.current !== '0 MB' && prog.total !== '0 MB'">{{ prog.current }} / {{ prog.total }}</span>
<span style="font-weight: 500" v-else>下载完成</span>
<div class="action-buttons">
<el-button size="small" @click="clearDownloadedFiles">清除下载</el-button>
<el-button size="small" type="primary" @click="installUpdate">立即重启</el-button>
</div>
</div>
</div>
</div>
</div>
</div>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import {ref, computed, onMounted, onUnmounted, watch} from 'vue'
import {ElMessage, ElMessageBox} from 'element-plus'
import {updateApi} from '../../api/update'
import {getSettings} from '../../utils/settings'
import {getUsernameFromToken} from '../../utils/token'
const props = defineProps<{ modelValue: boolean }>()
const emit = defineEmits<{ 'update:modelValue': [value: boolean] }>()
defineExpose({ checkForUpdatesNow })
const show = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
})
type Stage = 'check' | 'downloading' | 'completed'
const stage = ref<Stage>('check')
const appName = ref('我了个电商')
const version = ref('')
const prog = ref({percentage: 0, current: '0 MB', total: '0 MB'})
const info = ref({
latestVersion: '',
asarUrl: '',
jarUrl: '',
updateNotes: '',
currentVersion: ''
})
async function checkUpdate(silent = false) {
try {
version.value = await (window as any).electronAPI.getJarVersion()
const result = (await updateApi.checkUpdate(version.value))?.data
info.value = {
currentVersion: result.currentVersion || version.value,
latestVersion: result.latestVersion || version.value,
asarUrl: result.asarUrl || '',
jarUrl: result.jarUrl || '',
updateNotes: result.updateNotes || ''
}
if (!result.needUpdate) {
if (!silent) ElMessage.info('当前已是最新版本')
return
}
if (localStorage.getItem('skipped_version') === result.latestVersion) return
const remindTime = localStorage.getItem('remind_later_time')
if (remindTime && Date.now() < parseInt(remindTime)) return
if (getSettings(getUsernameFromToken()).autoUpdate) {
await downloadUpdate()
return
}
show.value = true
stage.value = 'check'
if (!silent) ElMessage.success('发现新版本')
} catch (error) {
if (!silent) ElMessage.error('检查更新失败')
}
}
async function checkForUpdatesNow() {
if (stage.value === 'downloading' || stage.value === 'completed') {
show.value = true
return
}
await checkUpdate(false)
}
function skipVersion() {
localStorage.setItem('skipped_version', info.value.latestVersion)
show.value = false
}
function remindLater() {
localStorage.setItem('remind_later_time', (Date.now() + 24 * 60 * 60 * 1000).toString())
show.value = false
}
async function start() {
if (stage.value !== 'check') {
show.value = true
return
}
await downloadUpdate(true)
}
async function downloadUpdate(showDialog = false) {
if (!info.value.asarUrl && !info.value.jarUrl) {
if (showDialog) ElMessage.error('下载链接不可用')
return
}
stage.value = 'downloading'
if (showDialog) show.value = true
prog.value = {percentage: 0, current: '0 MB', total: '0 MB'}
;(window as any).electronAPI.onDownloadProgress((progress: any) => {
prog.value = {
percentage: progress.percentage || 0,
current: progress.current || '0 MB',
total: progress.total || '0 MB'
}
})
try {
const response = await (window as any).electronAPI.downloadUpdate({
asarUrl: info.value.asarUrl,
jarUrl: info.value.jarUrl,
latestVersion: info.value.latestVersion
})
if (response.success) {
stage.value = 'completed'
prog.value.percentage = 100
show.value = true
ElMessage.success(showDialog ? '下载完成' : '更新已下载完成,可以安装了')
} else {
stage.value = 'check'
if (showDialog) ElMessage.error('下载失败: ' + (response.error || '未知错误'))
;(window as any).electronAPI.removeDownloadProgressListener()
}
} catch (error) {
stage.value = 'check'
if (showDialog) ElMessage.error('下载失败')
;(window as any).electronAPI.removeDownloadProgressListener()
}
}
async function cancelDownload() {
;(window as any).electronAPI.removeDownloadProgressListener()
await (window as any).electronAPI.cancelDownload().catch(() => {})
stage.value = 'check'
prog.value = {percentage: 0, current: '0 MB', total: '0 MB'}
show.value = false
ElMessage.info('已取消下载')
}
async function installUpdate() {
try {
await ElMessageBox.confirm('安装过程中程序将自动重启,请确保已保存所有工作。确定要立即安装更新吗?', '确认安装', {
confirmButtonText: '立即安装',
cancelButtonText: '取消',
type: 'warning'
})
const response = await (window as any).electronAPI.installUpdate()
if (response.success) {
ElMessage.success('应用即将重启')
setTimeout(() => show.value = false, 1000)
}
} catch (error) {
if (error !== 'cancel') ElMessage.error('安装失败')
}
}
async function clearDownloadedFiles() {
try {
await ElMessageBox.confirm('确定要清除已下载的更新文件吗?清除后需要重新下载。', '确认清除', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
const response = await (window as any).electronAPI.clearUpdateFiles()
if (response.success) {
stage.value = 'check'
prog.value = {percentage: 0, current: '0 MB', total: '0 MB'}
show.value = false
ElMessage.success('已清除下载文件')
} else {
ElMessage.error('清除失败: ' + (response.error || '未知错误'))
}
} catch (error) {
if (error !== 'cancel') ElMessage.error('清除失败')
}
}
onMounted(async () => {
version.value = await (window as any).electronAPI.getJarVersion()
const pendingUpdate = await (window as any).electronAPI.checkPendingUpdate()
if (pendingUpdate?.hasPendingUpdate) {
stage.value = 'completed'
prog.value.percentage = 100
return
}
await checkUpdate(true)
})
onUnmounted(() => {
(window as any).electronAPI.removeDownloadProgressListener()
})
</script>
<style scoped>
:deep(.update-dialog .el-dialog) {border-radius: 16px; box-shadow: 0 24px 48px rgba(0, 0, 0, 0.15);}
/* 通用标题样式 */
:deep(.update-dialog .el-dialog__title) {font-size: 14px; font-weight: 500; margin-left: 8px;}
/* 默认标题样式(第一阶段 - 检查阶段) */
:deep(.update-dialog.stage-check .el-dialog__header) {display: block; text-align: left;}
/* 第二阶段 - 下载中,标题居中 */
:deep(.update-dialog.stage-downloading .el-dialog__header) {display: block; text-align: center;}
:deep(.update-dialog.stage-downloading .el-dialog__title) {margin-left: 20px;}
/* 第三阶段 - 下载完成,标题居中 */
:deep(.update-dialog.stage-completed .el-dialog__header) {display: block; text-align: center;}
:deep(.update-dialog.stage-completed .el-dialog__title) {margin-left: 20px;}
:deep(.update-dialog .el-dialog__body) {padding: 0;}
.update-content {text-align: left;}
.update-layout {display: grid; grid-template-columns: 88px 1fr; align-items: start; margin-bottom: 5px;}
.left-pane {display: flex; flex-direction: column; align-items: flex-start;}
.app-icon-large {width: 70px; height: 70px; border-radius: 12px; margin: 4px 0 0 0;}
.right-pane {min-width: 0;}
.right-pane .announce {font-size: 16px; font-weight: 600; color: #1f2937; margin: 4px 0 6px; word-break: break-word;}
.right-pane .desc {font-size: 13px; color: #6b7280; line-height: 1.6; margin: 0; word-break: break-word;}
.update-header {display: flex; align-items: flex-start; margin-bottom: 24px;}
.update-header.text-center {text-align: center; flex-direction: column; align-items: center;}
.app-icon {width: 70px; height: 70px; border-radius: 12px; margin-right: 16px; flex-shrink: 0;}
.update-header.text-center .app-icon {margin-right: 0; margin-bottom: 16px;}
.update-header h3 {font-size: 20px; font-weight: 600; margin: 16px 0 8px 0; color: #1f2937;}
.update-header p {font-size: 14px; color: #6b7280; margin: 0; line-height: 1.5;}
.update-details {border-radius: 8px; padding: 0; margin: 12px 0 8px 0;}
.update-details.form {max-height: none;}
.notes-box :deep(textarea.el-textarea__inner) {white-space: pre-wrap;}
.update-details h4 {font-size: 14px; font-weight: 600; color: #374151; margin: 0 0 8px 0;}
.update-actions.row {display: flex; flex-direction: column; align-items: stretch; gap: 12px;}
.update-buttons {display: flex; justify-content: space-between; gap: 12px;}
.update-actions.row .update-buttons {justify-content: space-between;}
:deep(.update-actions.row .update-buttons .el-button) {flex: none; min-width: 100px;}
.left-actions {display: flex; gap: 12px;}
.right-actions {display: flex; gap: 8px;}
:deep(.update-buttons .el-button) {flex: 1; height: 32px; font-size: 13px; border-radius: 8px;}
.download-header h3 {font-size: 14px; font-weight: 500; margin: 0; color: #1f2937;}
.download-main {display: grid; grid-template-columns: 80px 1fr; align-items: start;}
.download-icon {display: flex; justify-content: center;}
.download-icon .app-icon {width: 64px; height: 64px; border-radius: 12px;}
.download-content {min-width: 0;}
.download-info {margin-bottom: 12px;}
.download-info p {font-size: 14px; font-weight: 600; color: #6b7280; margin: 0;}
.download-progress {margin: 0;}
.progress-info {display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; font-size: 14px; color: #6b7280;}
.progress-details {margin-top: 12px; display: flex; justify-content: space-between; align-items: center;}
.progress-details span {font-size: 12px; color: #909399;}
.action-buttons {display: flex; gap: 8px;}
:deep(.el-progress-bar__outer) {border-radius: 4px; background-color: #e5e7eb;}
:deep(.el-progress-bar__inner) {border-radius: 4px; transition: width 0.3s ease;}
:deep(.update-buttons .el-button--primary) {background-color: #2563eb; border-color: #2563eb; font-weight: 500;}
:deep(.update-buttons .el-button--primary:hover) {background-color: #1d4ed8; border-color: #1d4ed8;}
:deep(.update-buttons .el-button:not(.el-button--primary)) {background-color: #f3f4f6; border-color: #d1d5db; color: #374151; font-weight: 500;}
:deep(.update-buttons .el-button:not(.el-button--primary):hover) {background-color: #e5e7eb; border-color: #9ca3af;}
</style>