子组件
diolag.vue
<template>
<el-dialog
v-model="dialogVisible"
title="设置登录密码"
width="400px"
height="400px"
align-center
:close-on-click-modal="true"
:close-on-press-escape="true"
:show-close="true"
style="border-radius: 8px"
>
<template #header="{ close, titleId, titleClass }">
<div class="my-header">
<div class="title">设置登录密码</div>
</div>
</template>
<div class="password-setup-dialog">
<div class="description">为了您的账户安全,请设置登录密码</div>
<el-form ref="formRef" :model="formData" :rules="rules" label-position="top" size="large" class="password-form">
<el-form-item prop="password">
<el-input v-model="formData.password" :type="showPassword ? 'text' : 'password'" placeholder="请输入新密码" clearable>
<template #prefix>
<div class="iconfont icon-mima" style="font-size: 14px"></div>
</template>
<template #suffix>
<el-icon class="password-icon" @click="showPassword = !showPassword">
<View v-if="showPassword" />
<Hide v-else />
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item prop="confirmPassword">
<el-input v-model="formData.confirmPassword" :type="showConfirmPassword ? 'text' : 'password'" placeholder="请再次输入密码" clearable>
<template #prefix>
<div class="iconfont icon-mima" style="font-size: 14px"></div>
</template>
<template #suffix>
<el-icon class="password-icon" @click="showConfirmPassword = !showConfirmPassword">
<View v-if="showConfirmPassword" />
<Hide v-else />
</el-icon>
</template>
</el-input>
</el-form-item>
<!-- <div class="password-requirements">
<div class="requirement-title">密码要求:</div>
<ul>
<li :class="{ 'met': passwordLength }">长度至少8位</li>
<li :class="{ 'met': hasUpperCase }">包含大写字母</li>
<li :class="{ 'met': hasLowerCase }">包含小写字母</li>
<li :class="{ 'met': hasNumber }">包含数字</li>
<li :class="{ 'met': hasSpecialChar }">包含特殊字符</li>
</ul>
</div> -->
</el-form>
<div class="dialog-footer">
<el-button type="primary" size="large" class="confirm-btn" :loading="loading" @click="handleConfirm"> 确认设置 </el-button>
</div>
</div>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, computed, watch } from 'vue';
import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
import { View, Hide } from '@element-plus/icons-vue';
import { useLoginApi } from '../../../api/login/index';
import { storeToRefs } from 'pinia';
import { useRoute, useRouter } from 'vue-router';
import { Session } from '/@/utils/storage';
import { NextLoading } from '/@/utils/loading';
import { useI18n } from 'vue-i18n';
import Cookies from 'js-cookie';
import { useThemeConfig } from '/@/stores/themeConfig';
import { initFrontEndControlRoutes } from '/@/router/frontEnd';
import { initBackEndControlRoutes } from '/@/router/backEnd';
import { formatAxis } from '/@/utils/formatTime';
// 定义变量内容
const { t } = useI18n();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const route = useRoute();
const router = useRouter();
// 使用登录API
const { setPassword } = useLoginApi();
// 定义props和emits
interface Props {
visible: boolean;
}
interface Emits {
(e: 'update:visible', value: boolean): void;
(e: 'confirm', password: string): void;
}
const props = defineProps<Props>();
const emit = defineEmits<Emits>();
// 响应式数据
const dialogVisible = computed({
get: () => props.visible,
set: (value) => emit('update:visible', value),
});
const formRef = ref<FormInstance>();
const loading = ref(false);
const showPassword = ref(false);
const showConfirmPassword = ref(false);
const formData = reactive({
password: '',
confirmPassword: '',
});
// 密码强度验证
const passwordLength = computed(() => formData.password.length >= 8);
const hasUpperCase = computed(() => /[A-Z]/.test(formData.password));
const hasLowerCase = computed(() => /[a-z]/.test(formData.password));
const hasNumber = computed(() => /[0-9]/.test(formData.password));
const hasSpecialChar = computed(() => /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(formData.password));
const isPasswordValid = computed(() => passwordLength.value && hasUpperCase.value && hasLowerCase.value && hasNumber.value && hasSpecialChar.value);
// 表单验证规则
const validatePassword = (rule: any, value: string, callback: any) => {
if (!value) {
callback(new Error('请输入密码'));
} else if (!isPasswordValid.value) {
callback(new Error('密码不符合要求'));
} else {
callback();
}
};
const validateConfirmPassword = (rule: any, value: string, callback: any) => {
if (!value) {
callback(new Error('请确认密码'));
} else if (value !== formData.password) {
callback(new Error('两次输入的密码不一致'));
} else {
callback();
}
};
const rules: FormRules = {
password: [{ required: true, validator: validatePassword, trigger: 'blur' }],
confirmPassword: [{ required: true, validator: validateConfirmPassword, trigger: 'blur' }],
};
// 监听密码变化,实时验证
watch(
() => formData.password,
() => {
if (formRef.value) {
formRef.value.validateField('password');
}
}
);
// 时间获取
const currentTime = computed(() => {
return formatAxis(new Date());
});
// 方法
const handleConfirm = async () => {
if (!formRef.value) return;
try {
const valid = await formRef.value.validate();
if (valid) {
loading.value = true;
const loginRes = await setPassword({
password: formData.password,
});
if (loginRes.code == 200) {
signInSuccess();
// if (!themeConfig.value.isRequestRoutes) {
// // 前端控制路由,2、请注意执行顺序
// const isNoPower = await initFrontEndControlRoutes();
// signInSuccess(isNoPower);
// } else {
// // 模拟后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
// // 添加完动态路由,再进行 router 跳转,否则可能报错 No match found for location with path "/"
// const isNoPower = await initBackEndControlRoutes();
// // 执行完 initBackEndControlRoutes,再执行 signInSuccess
// signInSuccess(isNoPower);
// }
} else {
ElMessage.success(loginRes.message);
}
// 模拟API调用
// setTimeout(() => {
// ElMessage.success('密码设置成功');
// emit('confirm', formData.password);
// resetForm();
// loading.value = false;
// }, 1000);
}
} catch (error) {
console.log('表单验证失败', error);
}
};
// 登录成功后的跳转
// isNoPower: boolean | undefined
const signInSuccess = () => {
// 初始化登录成功时间问候语
let currentTimeInfo = currentTime.value;
// 登录成功,跳到转首页
// 如果是复制粘贴的路径,非首页/登录页,那么登录成功后重定向到对应的路径中
if (route.query?.redirect) {
router.push({
path: <string>route.query?.redirect,
query: Object.keys(<string>route.query?.params).length > 0 ? JSON.parse(<string>route.query?.params) : '',
});
} else {
router.push('/');
}
// 登录成功提示
const signInText = t('message.signInText');
ElMessage.success(`${currentTimeInfo},${signInText}`);
// 添加 loading,防止第一次进入界面时出现短暂空白
NextLoading.start();
};
const resetForm = () => {
formData.password = '';
formData.confirmPassword = '';
showPassword.value = false;
showConfirmPassword.value = false;
if (formRef.value) {
formRef.value.clearValidate();
}
};
</script>
<style scoped lang="scss">
:deep(.el-dialog__header) {
color: #4c62d1 !important;
}
:deep(.el-input__wrapper) {
border-radius: 8px;
}
.my-header {
color: #4c62d1;
display: flex;
justify-content: flex-start;
font-weight: 700;
font-size: 14px;
padding: 0 20px;
}
.password-setup-dialog {
padding: 0 10px;
.description {
text-align: left;
color: #999;
margin-bottom: 10px;
font-size: 14px;
}
.password-form {
margin-bottom: 20px;
:deep(.el-form-item__label) {
font-weight: 500;
margin-bottom: 8px;
}
.password-icon {
cursor: pointer;
color: #c0c4cc;
&:hover {
color: #909399;
}
}
}
.password-requirements {
background-color: #f8f9fa;
border-radius: 4px;
padding: 12px 16px;
margin-bottom: 20px;
.requirement-title {
font-size: 13px;
color: #666;
margin-bottom: 8px;
}
ul {
list-style: none;
padding: 0;
margin: 0;
li {
font-size: 12px;
color: #f56c6c;
margin-bottom: 4px;
transition: color 0.3s;
&.met {
color: #67c23a;
}
&::before {
content: '•';
margin-right: 6px;
}
}
}
}
.dialog-footer {
text-align: center;
margin-top: 20px;
.confirm-btn {
width: 100%;
height: 40px;
font-size: 16px;
}
}
}
</style>
父组件 使用
<PasswordUpdateDiolag v-model:visible="showDialog" @confirm="handlePasswordUpdated" />
const showDialog = ref(false); //
这样 就是最标准的 v-model 双向绑定 可以有多个v-model
