<template>
|
<el-drawer v-model="drawerVisible" :destroy-on-close="true" size="450px" :title="`${drawerProps.title}`">
|
<!-- Progress Bar Overlay -->
|
<transition name="fade">
|
<div v-if="showProgress" class="progress-overlay">
|
<div class="progress-modal">
|
<div class="progress-header">
|
<h3>{{ t("company.initializingData") }}</h3>
|
<p class="progress-description">{{ t("company.pleaseWait") }}</p>
|
</div>
|
<el-progress
|
:percentage="progressPercent"
|
:status="progressStatus"
|
:stroke-width="20"
|
:text-inside="true"
|
class="progress-bar"
|
/>
|
<div class="progress-steps">
|
<div
|
v-for="(step, index) in progressSteps"
|
:key="index"
|
:class="[
|
'step-item',
|
{
|
active: index <= currentStep - 1,
|
completed: index < currentStep - 1
|
}
|
]"
|
>
|
<div class="step-icon">{{ index + 1 }}</div>
|
<div class="step-label">{{ t(`company.progressSteps.${step.key}`) }}</div>
|
</div>
|
</div>
|
<div class="progress-percent">{{ progressPercent.toFixed(0) }}%</div>
|
</div>
|
</div>
|
</transition>
|
|
<el-form
|
ref="ruleFormRef"
|
label-width="100px"
|
label-suffix=" :"
|
:rules="rules"
|
:disabled="drawerProps.isView || showProgress"
|
:model="drawerProps.row"
|
:hide-required-asterisk="drawerProps.isView"
|
:class="{ 'form-blur': showProgress }"
|
>
|
<el-form-item :label="t('company.companyLogo')" prop="logo">
|
<UploadImg v-model:image-url="drawerProps.row!.logo" width="135px" height="135px" :file-size="1">
|
<template #empty>
|
<el-icon><Memo /></el-icon>
|
<span>{{ t("company.uploadLogo") }}</span>
|
</template>
|
<template #tip> {{ t("company.logoSizeTip") }} </template>
|
</UploadImg>
|
</el-form-item>
|
<el-form-item :label="t('User.companyName')" prop="companyname" v-if="NoUpdate">
|
<el-input v-model="drawerProps.row!.companyname" :placeholder="t('company.inputCompanyName')" clearable></el-input>
|
</el-form-item>
|
<el-form-item
|
:label="t('company.systemName')"
|
prop="systemname"
|
:rules="[
|
{ required: true, message: t('company.validation.systemNameRequired'), trigger: 'blur' },
|
{ max: 10, message: t('company.validation.systemNameMaxLength'), trigger: 'blur' }
|
]"
|
>
|
<el-input v-model="drawerProps.row!.systemname" :placeholder="t('company.inputSystemName')" clearable></el-input>
|
</el-form-item>
|
</el-form>
|
|
<template #footer>
|
<el-button @click="drawerVisible = false" :disabled="showProgress">{{ t("Config.cancel") }}</el-button>
|
<el-button
|
v-show="!drawerProps.isView"
|
type="primary"
|
@click="handleSubmit"
|
:loading="isSubmitting"
|
:disabled="showProgress"
|
>
|
{{ t("Config.sure") }}
|
</el-button>
|
</template>
|
</el-drawer>
|
</template>
|
|
<script setup lang="ts" name="UserDrawer">
|
import { ref, reactive, onUnmounted } from "vue";
|
import { useI18n } from "vue-i18n";
|
import { ElMessage, FormInstance } from "element-plus";
|
import { User } from "@/api/interface";
|
import UploadImg from "@/components/Upload/user/companyLogo.vue";
|
|
const { t } = useI18n();
|
const ruleFormRef = ref<FormInstance>();
|
const NoUpdate = ref(true);
|
const rules = reactive({
|
logo: [{ required: true, message: t("company.validation.logoRequired") }],
|
companyname: [{ required: true, message: t("company.validation.companyNameRequired") }]
|
});
|
|
interface DrawerProps {
|
title: string;
|
isView: boolean;
|
initParam: Partial<User.ResUserList>;
|
row: Partial<User.ResUserList>;
|
api?: (params: any) => Promise<any>;
|
getTableList?: () => void;
|
}
|
|
const drawerVisible = ref(false);
|
const drawerProps = ref<DrawerProps>({
|
isView: false,
|
initParam: {},
|
title: "",
|
row: {}
|
});
|
|
// 进度条相关状态
|
const showProgress = ref(false);
|
const isSubmitting = ref(false);
|
const progressPercent = ref(0);
|
const progressStatus = ref("");
|
const currentStep = ref(0);
|
const progressSteps = ref([
|
{ key: "initBasicInfo" },
|
{ key: "config3dMap" },
|
{ key: "configDefaultRole" },
|
{ key: "configDepartment" },
|
{ key: "configDefaultIcon" },
|
{ key: "completeInit" }
|
]);
|
const progressInterval = ref<NodeJS.Timeout>();
|
|
// 接收父组件传过来的参数
|
const acceptParams = (params: DrawerProps) => {
|
drawerProps.value = params;
|
if (params.title == t("Config.update")) {
|
NoUpdate.value = false;
|
} else {
|
NoUpdate.value = true;
|
}
|
drawerVisible.value = true;
|
};
|
|
// 平滑随机进度函数
|
const startSmoothProgress = () => {
|
showProgress.value = true;
|
progressPercent.value = 0;
|
currentStep.value = 0;
|
progressStatus.value = "";
|
|
const minStep = 3;
|
const maxStep = 15;
|
const interval = 800;
|
|
const easeOutQuad = (t: number) => t * (2 - t);
|
|
progressInterval.value = setInterval(() => {
|
const remaining = 100 - progressPercent.value;
|
let increment = Math.min(Math.random() * (maxStep - minStep) + minStep, remaining);
|
|
if (progressPercent.value > 50) {
|
const progressRatio = (progressPercent.value - 50) / 50;
|
increment *= 1 - easeOutQuad(progressRatio);
|
}
|
|
increment = Math.max(increment, 1);
|
progressPercent.value = parseFloat(Math.min(progressPercent.value + increment, 100).toFixed(1));
|
|
// 新的步骤计算逻辑
|
const stepSize = 100 / progressSteps.value.length;
|
currentStep.value = Math.min(Math.floor(progressPercent.value / stepSize) + 1, progressSteps.value.length);
|
|
if (progressPercent.value >= 100) {
|
clearInterval(progressInterval.value);
|
progressStatus.value = "success";
|
currentStep.value = progressSteps.value.length;
|
}
|
}, interval);
|
};
|
|
const handleSubmit = async () => {
|
try {
|
const valid = await ruleFormRef.value!.validate();
|
if (!valid) return;
|
|
isSubmitting.value = true;
|
|
// 显示进度条
|
showProgress.value = true;
|
startSmoothProgress();
|
|
// 执行API调用
|
const apiPromise = drawerProps.value.api!(drawerProps.value.row);
|
|
// 最小显示时间(毫秒)
|
const minDisplayTime = 8000;
|
|
// 等待API和最小显示时间
|
const [res] = await Promise.all([apiPromise, new Promise(resolve => setTimeout(resolve, minDisplayTime))]);
|
|
// 确保进度完成
|
if (progressPercent.value < 100) {
|
clearInterval(progressInterval.value);
|
const remaining = 100 - progressPercent.value;
|
const steps = Math.ceil(remaining / 5);
|
const stepInterval = 300;
|
|
for (let i = 1; i <= steps; i++) {
|
await new Promise(resolve => setTimeout(resolve, stepInterval));
|
progressPercent.value = parseFloat(Math.min(progressPercent.value + remaining / steps, 100).toFixed(1));
|
}
|
progressStatus.value = "success";
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
}
|
|
ElMessage.success({ message: `${res.msg}` });
|
drawerProps.value.getTableList!();
|
drawerVisible.value = false;
|
} catch (error) {
|
console.log(error);
|
clearInterval(progressInterval.value);
|
progressStatus.value = "exception";
|
ElMessage.error(t("company.initFailed"));
|
} finally {
|
isSubmitting.value = false;
|
setTimeout(() => {
|
showProgress.value = false;
|
}, 1000);
|
}
|
};
|
onUnmounted(() => {
|
if (progressInterval.value) {
|
clearInterval(progressInterval.value);
|
}
|
});
|
|
defineExpose({
|
acceptParams
|
});
|
</script>
|
|
<style scoped lang="scss">
|
.fade-enter-active,
|
.fade-leave-active {
|
transition: opacity 0.5s ease;
|
}
|
.fade-enter-from,
|
.fade-leave-to {
|
opacity: 0;
|
}
|
.progress-overlay {
|
position: fixed;
|
inset: 0;
|
z-index: 2000;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
background-color: rgb(255 255 255 / 80%);
|
backdrop-filter: blur(2px);
|
}
|
.progress-modal {
|
width: 600px;
|
padding: 24px;
|
background: white;
|
border-radius: 8px;
|
box-shadow: 0 4px 12px rgb(0 0 0 / 15%);
|
}
|
.progress-header {
|
margin-bottom: 20px;
|
text-align: center;
|
h3 {
|
margin: 0;
|
font-size: 18px;
|
color: #303133;
|
}
|
.progress-description {
|
margin: 8px 0 0;
|
font-size: 14px;
|
color: #909399;
|
}
|
}
|
.progress-bar {
|
margin: 20px 0;
|
}
|
.progress-steps {
|
position: relative;
|
display: flex;
|
justify-content: space-between;
|
margin: 30px 0 20px;
|
&::before {
|
position: absolute;
|
top: 15px;
|
right: 0;
|
left: 0;
|
z-index: 1;
|
height: 2px;
|
content: "";
|
background: #ebeef5;
|
}
|
}
|
.step-item {
|
position: relative;
|
z-index: 2;
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
&.active .step-icon {
|
color: white;
|
background: #409eff;
|
border-color: #409eff;
|
}
|
&.completed .step-icon {
|
color: white;
|
background: #67c23a;
|
border-color: #67c23a;
|
}
|
}
|
.step-icon {
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
width: 30px;
|
height: 30px;
|
margin-bottom: 8px;
|
font-size: 14px;
|
background: white;
|
border: 2px solid #dcdfe6;
|
border-radius: 50%;
|
transition: all 0.3s ease;
|
}
|
.step-label {
|
max-width: 80px;
|
font-size: 12px;
|
color: #c0c4cc;
|
text-align: center;
|
word-break: break-word;
|
}
|
.step-item.active .step-label,
|
.step-item.completed .step-label {
|
font-weight: 500;
|
color: #409eff;
|
}
|
.progress-percent {
|
margin-top: 10px;
|
font-size: 24px;
|
font-weight: bold;
|
color: #409eff;
|
text-align: center;
|
}
|
.form-blur {
|
pointer-events: none;
|
filter: blur(2px);
|
}
|
</style>
|