2026-01-31 11:23:56 +08:00

927 lines
35 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>
<el-dialog v-model="showDialog" :title="t('upgrade.title')" width="850px" :close-on-click-modal="false" :close-on-press-escape="false" :before-close="dialogClose">
<template v-if="upgradeContent">
<!-- 检测服务是否到期 -->
<template v-if="step == 1">
<template v-for="(item, index) in upgradeContent.content">
<div class="text-lg">
<template v-if="item.upgrade_version">
<span>【{{ item.app.app_name }}】本次升级将从</span>
<span class="font-bold px-[2px]">{{ item.version }}</span>
<span>升级到</span>
<span class="font-bold px-[2px]">{{ item.upgrade_version }}</span>
<span>版本</span>
</template>
<template v-else>
<template v-if="upgradeContent.content.length > 1">
<span>【{{ item.app.app_name }}】当前版本</span>
<span class="font-bold px-[2px]">{{ item.version }}</span>
</template>
<template v-else>
<span>当前版本</span>
<span class="font-bold px-[2px]">{{ item.version }}</span>
</template>
</template>
</div>
<div class="mt-[10px]" :class="{ 'mb-[10px]' : (index + 1) < upgradeContent.content.length }" v-if="item.upgrade_version != item.last_version">
<el-alert type="info" show-icon :closable="false">
<template #title>
<span>当前最新版本为{{ item.last_version }},您的服务{{ item.expire_time ? `已于${item.expire_time}到期` : '长期有效' }}。</span>
<span>如需升级到最新版可在<a class="text-primary" href="https://www.niucloud.com" target="_blank">niucloud-admin官网</a>购买相关服务后再进行升级</span>
</template>
</el-alert>
</div>
</template>
</template>
<div v-show="step == 2">
<el-steps :active="numberOfSteps" align-center class="number-of-steps" process-status="process">
<el-step :title="t('testDirectoryPermissions')" />
<el-step :title="t('upgrade.option')" />
<el-step :title="t('startUpgrade')" />
<el-step :title="t('upgradeEnd')" />
</el-steps>
<div class="h-[400px]" style="overflow: auto">
<!-- 判断文件权限 -->
<div v-show="active == 'upgrade'">
<div class="flex flex-col" v-if="upgradeCheck && !upgradeTask">
<el-scrollbar>
<div class="bg-[#fff] my-3" v-if="upgradeCheck.dir">
<div class="px-[20px] pt-[10px] text-[14px] el-table">
<el-row class="py-[10px] items table-head-bg pl-[15px] mb-[10px]">
<el-col :span="18">
<span>{{ t("upgrade.path") }}</span>
</el-col>
<el-col :span="3">
<span>{{ t("upgrade.demand") }}</span>
</el-col>
<el-col :span="3">
<span>{{ t("status") }}</span>
</el-col>
</el-row>
<div style="height: calc(300px); overflow: auto">
<el-row class="pb-[10px] items pl-[15px]" v-for="item in upgradeCheck.dir.is_readable">
<el-col :span="18">
<span>{{ item.dir }}</span>
</el-col>
<el-col :span="3">
<span>{{ t("upgrade.readable") }}</span>
</el-col>
<el-col :span="3" >
<span v-if="item.status">
<el-icon color="green">
<Select />
</el-icon>
</span>
<span v-else>
<el-icon color="red">
<CloseBold />
</el-icon>
</span>
</el-col>
</el-row>
<el-row class="pb-[10px] items pl-[15px]" v-for="item in upgradeCheck.dir.is_write">
<el-col :span="18">
<span>{{ item.dir }}</span>
</el-col>
<el-col :span="3">
<span>{{ t("upgrade.write") }}</span>
</el-col>
<el-col :span="3">
<span v-if="item.status">
<el-icon color="green">
<Select />
</el-icon>
</span>
<span v-else>
<el-icon color="red">
<CloseBold />
</el-icon>
</span>
</el-col>
</el-row>
</div>
</div>
</div>
</el-scrollbar>
</div>
<div class="h-[370px] mt-[30px]" v-show="showTerminal && upgradeTask && !errorDialog">
<terminal ref="terminalRef" :name="`upgrade-${terminalId}`" :context="upgradeTask ? upgradeTask.upgrade.app_key : ''" :init-log="null" :show-header="false" :show-log-time="true" @exec-cmd="onExecCmd" />
</div>
</div>
<!-- 是否备份选择 -->
<div class="flex flex-col" v-show="active == 'backup'">
<el-scrollbar>
<div class="bg-[#fff] my-3">
<div class="p-[20px] mt-[50px] mx-[10px] border-[1px] border-[#E6E6E6] rounded-[10px]">
<div class="flex justify-between items-center mt-[-9px]">
<el-checkbox v-model="upgradeOption.is_need_cloudbuild" :label="t('upgrade.isNeedCloudbuild')" :true-value="true" :false-value="false" size="large" ></el-checkbox>
</div>
<div class="text-[14px] text-[#374151] mb-[10px]">{{ t('upgrade.cloudbuildTips') }}</div>
</div>
<div class="p-[20px] mt-[20px] mx-[10px] border-[1px] border-[#E6E6E6] rounded-[10px]" v-if="upgradeContent.last_backup">
<div class="flex justify-between items-center mt-[-9px]">
<el-checkbox v-model="upgradeOption.is_need_backup" :label="t('upgrade.isNeedBackup')" :true-value="true" :false-value="false" size="large" ></el-checkbox>
<el-button link type="primary" class="!text-[#9699B6]" @click="toBackupRecord">{{ t('upgrade.isNeedBackupBtn') }}</el-button>
</div>
<div class="text-[14px] text-[#374151] mb-[10px]">{{ t('upgrade.isNeedBackupTips') }}</div>
<div class="text-[14px] text-[#9699B6]">{{ t('上次备份时间:') }}{{ upgradeContent.last_backup.complete_time }}</div>
</div>
</div>
</el-scrollbar>
</div>
</div>
</div>
<div v-if="step == 3">
<div class="mt-[10px]" v-show="active == 'complete'">
<el-result icon="success" :title="t('upgrade.upgradeSuccess')">
<template #icon>
<img src="@/app/assets/images/success_icon.png" alt="">
</template>
<template #extra>
<div class="text-[16px] text-[#4F516D] mt-[-5px]" v-show="upgradeTask && upgradeTask.executed && !upgradeTask.executed.includes('cloudBuild')">{{ t('upgrade.upgradeCompleteTips') }}</div>
<!-- <div class="text-[16px] text-[#9699B6] mt-[10px]">本次升级用时{{ formatUpgradeDuration }}</div> -->
<div class="w-[750px]" v-if="upgradeTask.cloud_build_error">
<el-alert class="!w-[750px] border-warning !border-[1px] !rounded-[0px] border-solid" type="warning" :closable="false">
<template #title>
<span class="text-error">警告:</span>
<span class="text-black">升级过程中发生云编译错误</span>
</template>
</el-alert>
<div class="text-left mt-[10px] leading-8">
<div class="font-bold">为了保证系统稳定,建议您做以下处理:</div>
<div><span class="w-[6px] h-[6px] rounded-[6px] bg-black inline-block mr-[10px]"></span>如果您是开发者,安装的框架或者插件二开过或者正在开发中,可能是因为您的本地代码不完整导致的云编译失败,需要自己调试并重新进行编译才算升级完成(云编译会把本地插件前端代码上传编译)。</div>
<div><span class="w-[6px] h-[6px] rounded-[6px] bg-black inline-block mr-[10px]"></span>如果您没有二开过任何代码,可能是本地插件存在兼容性问题,请联系插件开发者或者官方客服解决。</div>
<div><span class="w-[6px] h-[6px] rounded-[6px] bg-black inline-block mr-[10px]"></span>如果您的项目已经投入正式运营中,请立即回滚。</div>
</div>
<div class="text-left mt-[10px]">
<div class="font-bold">编译信息错误</div>
<div class="mt-[10px] text-secondary overflow-hidden line-clamp-4">
{{ upgradeTask.cloud_build_error }}
</div>
</div>
<div class="mt-[20px]">
<el-button @click="handleBack()" class="!w-[90px]">更多信息</el-button>
<el-button @click="showDialog=false" type="primary" plain class="!w-[90px]">我已知晓</el-button>
<el-button @click="cloudBuildError('cloud_build_error_rollback')" type="primary" class="!w-[90px]">回滚</el-button>
</div>
</div>
<div class="mt-[20px]" v-else>
<el-button @click="handleBack()" class="!w-[90px]">返回</el-button>
<el-button @click="showDialog=false" type="primary" class="!w-[90px]">完成</el-button>
</div>
</template>
</el-result>
</div>
<div class="mt-[20px] h-[370px]" v-show="active == 'fail'">
<el-result icon="error" :title="t('升级失败')">
<template #icon>
<img src="@/app/assets/images/error_icon.png" alt="" />
</template>
<template #extra>
<el-scrollbar class="max-h-[120px] !overflow-auto text-[15px] text-[#4F516D] mb-[15px] mt-[-15px]">
{{errorMsg}}
</el-scrollbar>
<el-button @click="handleBack()" class="!w-[90px]">错误信息</el-button>
<el-button @click="showDialog=false" type="primary" class="!w-[90px]">完成</el-button>
</template>
</el-result>
</div>
</div>
</template>
<template #footer>
<div class="dialog-footer">
<!-- 查看升级内容 -->
<el-button v-if="step == 1 && upgradeContent.content.length && isAllowUpgrade" @click="step = 2" type="primary">{{ t("upgrade.upgradeButton") }}</el-button>
<el-button type="primary" v-show="step == 2 && showTerminal && upgradeTask && !errorDialog" :loading="timeloading" class="!w-[140px]">已用时 {{ formatUpgradeDuration }}</el-button>
<template v-if="step == 2 && active != 'complete'">
<!-- <el-button v-if="active == 'content'" @click="showDialog = false">{{ t("return") }}</el-button>-->
<el-button type="primary" :disabled="!is_pass" v-if="active == 'upgrade' && !upgradeTask" @click="() => { active = 'backup'; numberOfSteps = 1 }">{{ t("nextStep") }}</el-button>
<el-button v-if="active == 'backup'" @click="() => { active = 'upgrade'; numberOfSteps = 1 } ">{{ t("prev") }}</el-button>
<el-button type="primary" v-if="active == 'backup'" :loading="loading" @click="() => { upgradeAddonFn() }">{{ t("nextStep") }}</el-button>
<el-button v-if="active == 'complete'" @click="showDialog = false">{{ t("complete") }}</el-button>
</template>
</div>
</template>
</el-dialog>
<el-dialog v-model="upgradeTipsShowDialog" :title="t('warning')" width="500px" draggable>
<span v-html="t('upgrade.upgradeTips')"></span>
<template #footer>
<div class="flex justify-end">
<el-button @click="upgradeTipsConfirm(true)" type="primary">{{ t("upgrade.knownToKnow") }}</el-button>
<el-button @click="handleUpgrade()" type="primary" plain :loading="readyLoading">{{ t("upgrade.upgradeButton") }}</el-button>
<el-button @click="upgradeTipsShowDialog = false">{{ t("cancel") }}</el-button>
</div>
</template>
</el-dialog>
<el-dialog v-model="cloudBuildErrorTipsShowDialog" :title="t('warning')" width="500px" draggable :show-close="false">
<span v-html="t('upgrade.cloudBuildErrorTips')"></span>
<template #footer>
<div class="flex justify-end">
<el-button @click="cloudBuildError('local')" type="primary">{{ t("upgrade.localBuild") }}</el-button>
<el-button @click="cloudBuildError('retry')" type="primary">{{ t("upgrade.cloudBuild") }}{{ retrySecond }}S</el-button>
<el-button @click="cloudBuildError('rollback')" type="primary">{{ t("upgrade.rollback") }}</el-button>
</div>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, h, watch, computed } from 'vue'
import { t } from '@/lang'
import { getVersions } from '@/app/api/auth'
import { getFrameworkNewVersion } from '@/app/api/module'
import {
getUpgradeContent,
getUpgradeTask,
upgradeAddon,
executeUpgrade,
preUpgradeCheck,
clearUpgradeTask, upgradeUserOperate
} from '@/app/api/upgrade'
import { Terminal, TerminalFlash } from 'vue-web-terminal'
import 'vue-web-terminal/lib/theme/dark.css'
import { AnyObject } from '@/types/global'
import { ElNotification, ElMessage, ElMessageBox } from 'element-plus'
import Storage from '@/utils/storage'
import { useRouter } from 'vue-router'
const router = useRouter()
const terminalId = ref(Date.now());
const showDialog = ref<boolean>(false)
const upgradeContent = ref<null | AnyObject>(null)
const isAllowUpgrade = ref(true) // 是否允许升级
const upgradeTask = ref<null | AnyObject>(null)
const active = ref('upgrade')
const step = ref(1)
const upgradeCheck = ref<null | AnyObject>(null)
const loading = ref(false)
const terminalRef: any = ref(null)
const emits = defineEmits(['complete', 'cloudbuild'])
const upgradeTipsShowDialog = ref<boolean>(false)
let upgradeLog: any = []
let errorLog: any = []
const cloudBuildErrorTipsShowDialog = ref<boolean>(false)
const retrySecond = ref(30)
let retrySecondInterval: any = null
const upgradeOption = ref({
is_need_backup: true,
is_need_cloudbuild: true
})
// 升级步骤排除backupCode 备份代码backupSql 备份数据库
const excludeSteps: any = ref([
{
name: '备份源码',
code: 'backupCode'
},
{
name: '备份数据库',
code: 'backupSql'
}
])
/**
* 查询升级任务
*/
const timeloading = ref(false)
const showTerminal = ref(false)
const upgradeStartTime = ref<number | null>(null)
const upgradeDuration = ref(0) // 单位:秒
let upgradeTimer: ReturnType<typeof setInterval> | null = null
const errorDialog = ref(false)
const errorMsg = ref('')
const getUpgradeTaskFn = () => {
getUpgradeTask().then(({ data }) => {
if (!data) return
if (!upgradeContent.value) {
upgradeContent.value = data.upgrade_content
if (!data.upgrade_content || !Array.isArray(data.upgrade_content.content)) {
return
}
let upgradeCount = 0
let failUpgradeCount = 0
for (let i = 0; i < upgradeContent.value.content.length; i++) {
if (upgradeContent.value.content[i].version_list.length) {
upgradeCount++
} else {
failUpgradeCount++
}
}
if (upgradeContent.value.content.length == upgradeCount) {
isAllowUpgrade.value = true
} else if (upgradeContent.value.content.length == failUpgradeCount) {
isAllowUpgrade.value = false
}
}
// 检测有没有正在进行中的升级任务
if (!showDialog.value) {
showElNotification()
return
}
if (!upgradeTask.value) {
showTerminal.value = true
terminalRef.value.execute('clear')
terminalRef.value.execute('开始升级')
timeloading.value = true
upgradeStartTime.value = Date.now()
upgradeDuration.value = 0
if (upgradeTimer) clearInterval(upgradeTimer)
upgradeTimer = setInterval(() => {
upgradeDuration.value++
}, 1000)
}
upgradeTask.value = data
data.log.forEach((item) => {
if (!upgradeLog.includes(item)) {
terminalRef.value.pushMessage({ content: `${item}` })
upgradeLog.push(item)
}
})
// 安装失败
if (data.error) {
data.error.forEach((item) => {
if (!errorLog.includes(item)) {
terminalRef.value.pushMessage({ content: item, class: 'error' })
errorLog.push(item)
errorMsg.value = item
}
})
if (upgradeTimer) {
clearInterval(upgradeTimer)
upgradeTimer = null
}
timeloading.value = false
}
// 恢复完毕
if (data.step == 'restoreComplete') {
flashInterval && clearInterval(flashInterval)
step.value = 3
active.value = 'fail'
return
}
// 升级完成
if (data.step == 'upgradeComplete') {
if (data.cloud_build_error) {
terminalRef.value.pushMessage({ content: data.cloud_build_error, class: 'error' })
}
step.value = 3
active.value = 'complete'
numberOfSteps.value = 4
notificationEl && notificationEl.close()
emits('complete')
if (upgradeTimer) {
clearInterval(upgradeTimer)
upgradeTimer = null
}
timeloading.value = false
return
}
numberOfSteps.value = 2
active.value = 'upgrade'
executeUpgradeFn()
}).catch((err) => {
console.log(err)
})
}
getUpgradeTaskFn()
const isBack = ref(false)
const handleBack = () => {
active.value = 'upgrade'
isBack.value = true
showTerminal.value = true
errorDialog.value = false // 隐藏错误弹窗
step.value = 2
}
const formatUpgradeDuration = computed(() => {
const s = upgradeDuration.value
const h = Math.floor(s / 3600)
const m = Math.floor((s % 3600) / 60)
const sec = s % 60
return [
h > 0 ? `${h}小时` : '',
m > 0 ? `${m}分钟` : '',
`${sec}`
].filter(Boolean).join('')
})
const executeUpgradeFn = () => {
executeUpgrade().then(() => {
getUpgradeTaskFn()
}).catch((err) => {
if (err.message.indexOf('队列') != -1) {
retrySecond.value = 30
retrySecondInterval = setInterval(() => {
retrySecond.value--
if (retrySecond.value == 0) {
cloudBuildError('retry')
}
}, 1000)
cloudBuildErrorTipsShowDialog.value = true
} else {
ElMessage({ message: err.message, type: 'error' })
}
})
}
let notificationEl: any = null
/**
* 升级中任务提示
*/
const showElNotification = () => {
notificationEl = ElNotification.success({
title: t('warning'),
dangerouslyUseHTMLString: true,
message: h('div', {}, [t('upgrade.upgradingTips'), h('span', {
class: 'text-primary cursor-pointer',
onClick: elNotificationClick
}, [t('upgrade.clickView')])]),
duration: 0,
showClose: false
})
}
const elNotificationClick = () => {
showDialog.value = true
getUpgradeTaskFn()
step.value = 2
numberOfSteps.value = 3
active.value = 'upgrade'
notificationEl && notificationEl.close()
}
const frameworkVersion = ref('')
getVersions().then((res) => {
frameworkVersion.value = res.data.version.version
})
const newFrameworkVersion = ref('')
getFrameworkNewVersion().then(({ data }) => {
newFrameworkVersion.value = data.last_version
})
/**
* 执行升级
*/
const is_pass = ref(false)
const repeat = ref(false)
const readyLoading = ref(false)
const handleUpgrade = async () => {
if (repeat.value) return
repeat.value = true
readyLoading.value = true
const appKey = upgradeContent.value?.upgrade_apps.join(',') != 'niucloud-admin' ? upgradeContent.value?.upgrade_apps.join(',') : ''
await preUpgradeCheck(appKey).then(async ({ data }) => {
upgradeCheck.value = data
is_pass.value = data.is_pass
active.value = 'upgrade'
!upgradeTask.value ? (numberOfSteps.value = 0) : numberOfSteps.value
upgradeTipsShowDialog.value = false
showDialog.value = true
repeat.value = false
readyLoading.value = false
}).catch(() => {
repeat.value = false
readyLoading.value = false
})
}
const upgradeAddonFn = () => {
if (!is_pass.value) return
if (loading.value) return
loading.value = true
const appKey = upgradeContent.value?.upgrade_apps.join(',') != 'niucloud-admin' ? upgradeContent.value?.upgrade_apps.join(',') : ''
upgradeAddon(appKey, upgradeOption.value).then(() => {
getUpgradeTaskFn()
}).catch(() => {
loading.value = false
})
}
const open = (addonKey: string = '', callback = null) => {
errorDialog.value = false // 隐藏错误弹窗
if (upgradeTask.value) {
ElMessage({ message: '已有正在执行中的升级任务', type: 'error' })
showDialog.value = true
step.value = 2
numberOfSteps.value = 3
active.value = 'upgrade'
if (callback) callback()
} else {
if (addonKey && addonKey.indexOf('niucloud-admin') == -1 && frameworkVersion.value != newFrameworkVersion.value) {
ElMessage({ message: '存在新版本框架,请先升级框架', type: 'error' })
if (callback) callback()
return
}
if (loading.value) return
loading.value = true
getUpgradeContent(addonKey).then(({ data }) => {
loading.value = false
upgradeContent.value = data
let upgradeCount = 0
let failUpgradeCount = 0
for (let i = 0; i < upgradeContent.value.content.length; i++) {
if (upgradeContent.value.content[i].version_list.length) {
upgradeCount++
} else {
failUpgradeCount++
}
}
if (upgradeContent.value.content.length == upgradeCount) {
isAllowUpgrade.value = true
} else if (upgradeContent.value.content.length == failUpgradeCount) {
isAllowUpgrade.value = false
}
if (Storage.get('upgradeTipsLock')) {
handleUpgrade()
} else {
upgradeTipsShowDialog.value = true
}
if (callback) callback()
}).catch(() => {
loading.value = false
if (callback) callback()
})
}
}
/**
* 升级进度动画
*/
let flashInterval: any = null
const terminalFlash = new TerminalFlash()
const onExecCmd = (key, command, success, failed, name) => {
if (command == '开始升级') {
success(terminalFlash)
const frames = makeIterator(['/', '——', '\\', '|'])
flashInterval = setInterval(() => {
terminalFlash.flush('> ' + frames.next().value)
}, 150)
}
}
const makeIterator = (array: string[]) => {
let nextIndex = 0
return {
next () {
if (nextIndex + 1 == array.length) {
nextIndex = 0
}
return { value: array[nextIndex++] }
}
}
}
const dialogClose = (done: () => {}) => {
if (active.value == 'upgrade' && upgradeTask.value && ['upgradeComplete', 'restoreComplete'].includes(upgradeTask.value.step) === false && !isBack.value) {
ElMessageBox.confirm(t('upgrade.showDialogCloseTips'), t('warning'), {
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
}).then(() => {
done()
})
} else {
done()
}
}
watch(
() => showDialog.value,
() => {
if (!showDialog.value) {
clearUpgradeTaskFn()
}
}
)
const clearUpgradeTaskFn = () => {
active.value = 'upgrade'
loading.value = false
upgradeTask.value = null
isBack.value = false
errorDialog.value = false
errorMsg.value = ''
showTerminal.value = false
upgradeLog = []
errorLog = []
numberOfSteps.value = 0
flashInterval && clearInterval(flashInterval)
retrySecondInterval && clearInterval(retrySecondInterval)
upgradeOption.value = {
is_need_backup: true,
is_need_cloudbuild: true
}
step.value = 1
clearUpgradeTask().then(() => {
})
}
/**
* 云编译队列不足操作
* @param event
*/
const cloudBuildError = (event: string) => {
cloudBuildErrorTipsShowDialog.value = false
switch (event) {
case 'local':
upgradeUserOperate(event).then(() => {
getUpgradeTaskFn()
})
break
case 'retry':
executeUpgradeFn()
retrySecondInterval && clearInterval(retrySecondInterval)
break
case 'rollback':
upgradeUserOperate(event).then(() => {
getUpgradeTaskFn()
})
break
case 'cloud_build_error_rollback':
upgradeUserOperate(event).then(() => {
handleBack()
executeUpgradeFn()
})
break
}
}
const timeSplit = (str: string) => {
const [date, time] = str.split(' ')
const [hours, minutes] = time.split(':')
return [date, `${hours}:${minutes}`]
}
const upgradeTipsConfirm = (isLock: boolean = false) => {
isLock && Storage.set({ key: 'upgradeTipsLock', data: isLock })
upgradeTipsShowDialog.value = false
!isLock && (showDialog.value = true)
}
const activeName = ref(0)
const numberOfSteps = ref(0)
const toBackupRecord = () => {
const routeUrl = router.resolve({
path: '/tools/backup_records'
})
window.open(routeUrl.href, '_blank')
}
defineExpose({
open,
loading
})
</script>
<style lang="scss" scoped>
:deep(.el-button){
border-radius: 4px !important;
}
.table-head-bg {
background-color: var(--el-table-header-bg-color);
}
:deep(.el-checkbox__label){
color: var(--el-color-primary);
}
:deep(.terminal .t-log-box span) {
white-space: pre-wrap;
}
:deep(.number-of-steps) {
.el-step__line {
margin: 0 25px;
background: #dddddd;
}
.el-step__head {
margin-top: 10px;
}
.is-success {
color: var(--el-color-primary);
border-color: var(--el-color-primary);
.el-step__icon {
background: var(--el-color-primary);
color: #fff;
// box-shadow: 0 0 0 4px var(--el-color-primary-light-9);
i {
color: #fff;
}
}
.el-step__line {
margin: 0 25px;
background: var(--el-color-primary);
}
}
.is-finish {
color: var(--el-color-primary);
border-color: var(--el-color-primary);
.el-step__icon {
background: var(--el-color-primary)!important;
color: #fff !important;
// box-shadow: 0 0 0 4px var(--el-color-primary-light-9);
i {
color: #fff;
}
}
.el-step__line {
margin: 0 25px;
background: var(--el-color-primary);
}
}
.is-process {
color: var(--el-color-primary);
font-weight: inherit;
// font-size: 18px;
.el-step__icon {
padding: 10px;
border: 1px solid var(--el-color-primary);
background: var(--el-color-primary)!important;
color: #fff !important;
// box-shadow: 0 0 0 4px var(--el-color-primary-light-9);
}
}
.is-wait {
color: #333;
}
}
:deep(.el-dialog__title){
font-size: 20px;
font-weight: bold;
}
:deep(.el-result__title p){
font-size: 25px;
color: #1D1F3A;
font-weight: 500;
}
:deep(.el-result__subtitle p){
font-size: 15px;
color: #4F516D;
font-weight: 500;
word-break: break-all;
text-overflow: ellipsis;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
</style>
<style scoped>
.el-timeline-item {
min-height: 100px;
}
.el-timeline-item >>> .el-timeline-item__node--normal {
left: 117px;
width: 18px;
height: 18px;
background: rgba(25, 103, 249, 0.12) !important;
border-radius: 50%;
/* 创建圆形 */
position: relative;
/* 用于定位伪元素 */
}
.el-timeline-item >>> .el-timeline-item__node--normal::before {
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 8px;
/* 中心圆直径 */
height: 8px;
background-color: var(--el-color-primary);
/* 中心圆颜色 */
border-radius: 50%;
/* 中心圆为圆形 */
transform: translate(-50%, -50%);
/* 居中显示 */
}
.el-timeline-item >>> .el-timeline-item__tail {
left: 125px;
border-left-color: #dddddd;
border-left-style: dashed;
margin: 24px 0 12px;
height: calc(100% - 24px - 12px);
}
.time-dialog-wrap >>> .el-dialog__header {
padding: 10px 20px;
height: 25px;
line-height: 25px;
text-align: left;
background: #fff;
border-bottom: solid 1px #e4e7ed;
}
.time-dialog-wrap >>> .el-card__body {
padding: 8px;
}
.time-dialog-wrap >>> .el-card.is-always-shadow {
box-shadow: none;
}
.time-dialog-wrap >>> .el-dialog__headerbtn .el-dialog__close {
color: #666;
}
.time-dialog-wrap >>> .el-dialog__headerbtn:hover .el-dialog__close {
color: #666;
}
.time-dialog-wrap >>> .el-dialog__headerbtn {
top: 14px;
}
.time-dialog-wrap >>> .el-collapse {
margin-left: 119px;
border: none;
margin-top: -22px;
}
.time-dialog-wrap >>> .el-collapse-item__header {
border: none;
line-height: 25px;
height: 25px;
position: relative;
z-index: 999;
}
.time-dialog-wrap >>> .el-collapse-item__wrap {
border: none;
}
.time-dialog-wrap >>> .el-collapse-item__content {
margin-top: 15px;
padding-bottom: 0 !important;
}
.time-dialog-wrap >>> .el-timeline-item__node--01 {
width: 18px !important;
height: 18px !important;
left: 117px !important;
}
</style>
<style>
.time-dialog-wrap .el-dialog {
margin: 0 auto !important;
max-height: 90%;
overflow: hidden;
top: 5%;
}
.time-dialog-wrap .el-dialog {
margin: 0 auto !important;
height: 65%;
overflow: hidden;
top: 10%;
}
.time-dialog-wrap .el-dialog__body {
position: absolute;
left: 0;
top: 46px;
bottom: 0;
right: 0;
z-index: 1;
overflow: hidden;
overflow-y: auto;
padding: 10px 20px 0 0;
}
.time-dialog-wrap .el-timeline-item__wrapper {
top: -20px !important;
}
</style>