vue-element 表单校验跟手动兜底

表单校验

  • el-form 负责绑定model和rules
  • el-form-item 负责绑定prop
  • el-input 负责双向绑定具体的表单数据
   <el-card>
      <template #header>****运营后台</template>
      <!-- 登录表单 -->
      <el-form :model="formDate" :rules="formDataRules" ref="formDateRef">
        <el-form-item label="用户名:" prop="username">
          <el-input
            v-model="formDate.username"
            placeholder="请输入账号"
          ></el-input>
        </el-form-item>
        <el-form-item label="密码:" prop="password">
          <el-input
            v-model="formDate.password"
            placeholder="请输入密码"
          ></el-input>
        </el-form-item>
        <el-form-item>
          <el-button @click="doLogin" type="primary">登录</el-button>
          <el-button @click="doReset" type="success">重置</el-button>
        </el-form-item>
      </el-form>
    </el-card>

校验:

  • required:非空
  • trigger:何时进行校验 blur:失焦 change:表单数据发生改变
  • min: 最小长度
  • max: 最大长度
  • message:提示信息
  • validator:自定义表单校验 

  • element官网Form表单

校验规格书写位置:

   

 

自定义表单校验 (validator)

需要自己封装函数  格式为:

 const validCode = (rule, value, callback) =>{
   console.log(value)
}

手动兜底验证:(谨防部分大聪明 跳过校验规则,直接空白提交)

this.$refs.deptForm.validate((valid) =>{
 if(valid){
   //写校验成功的代码
  }

})

<template> <div> <h3 text-center m-0 mb-20px>{{ t("login.login") }}</h3> <el-form ref="loginFormRef" :model="loginFormData" :rules="loginRules" size="large" :validate-on-rule-change="false"> <!-- 用户名 --> <el-form-item prop="username"> <el-input v-model.trim="loginFormData.username" :placeholder="t('login.username')"> <template #prefix> <el-icon> <User /> </el-icon> </template> </el-input> </el-form-item> <!-- 密码 --> <el-tooltip :visible="isCapsLock" :content="t('login.capsLock')" placement="right"> <el-form-item prop="password"> <el-input v-model.trim="loginFormData.password" :placeholder="t('login.password')" type="password" show-password @keyup="checkCapsLock" @keyup.enter="handleLoginSubmit"> <template #prefix> <el-icon> <Lock /> </el-icon> </template> </el-input> </el-form-item> </el-tooltip> <div class="flex-x-between w-full"> <el-checkbox v-model="loginFormData.rememberMe">{{ t("login.rememberMe") }}</el-checkbox> <el-link type="primary" underline="never" @click="toOtherForm('resetPwd')"> {{ t("login.forgetPassword") }} </el-link> </div> <!-- 登录按钮 --> <el-form-item> <el-button :loading="loading" type="primary" class="w-full" @click="handleLoginSubmit"> {{ t("login.login") }} </el-button> </el-form-item> </el-form> <div flex-center gap-10px> <el-text size="default">{{ t("login.noAccount") }}</el-text> <el-link type="primary" underline="never" @click="toOtherForm('register')"> {{ t("login.reg") }} </el-link> </div> <!-- 第三方登录 --> <div class="third-party-login"> <div class="divider-container"> <div class="divider-line"></div> <span class="divider-text">{{ t("login.otherLoginMethods") }}</span> <div class="divider-line"></div> </div> <div class="flex-center gap-x-5 w-full text-[var(--el-text-color-secondary)]"> <CommonWrapper> <div text-20px class="i-svg:wechat" /> </CommonWrapper> <CommonWrapper> <div text-20px cursor-pointer class="i-svg:qq" /> </CommonWrapper> <CommonWrapper> <div text-20px cursor-pointer class="i-svg:github" /> </CommonWrapper> <CommonWrapper> <div text-20px cursor-pointer class="i-svg:gitee" /> </CommonWrapper> </div> </div> </div> </template> <script setup lang="ts"> import type { FormInstance } from "element-plus"; import { User, Lock } from "@element-plus/icons-vue"; import { useI18n } from "vue-i18n"; import { useRoute, useRouter } from "vue-router"; import { ElMessage } from "element-plus"; // 引入核心依赖(移除 permissionStore 相关) import AuthAPI, { type LoginFormData } from "@/api/auth-api"; import { useUserStore } from "@/store"; // 仅保留用户Store import CommonWrapper from "@/components/CommonWrapper/index.vue"; import { AuthStorage } from "@/utils/auth"; import axios from "axios"; // 初始化工具实例(移除 permissionStore) const { t } = useI18n(); const userStore = useUserStore(); const route = useRoute(); const router = useRouter(); // 确保是全局正确的路由实例 // 表单核心状态(不变) const loginFormRef = ref<FormInstance>(); const loading = ref(false); const isCapsLock = ref(false); // 登录表单数据(不变) const loginFormData = ref<Omit<LoginFormData, "captchaKey" | "captchaCode">>({ username: "李四", password: "123", rememberMe: false }); // 表单验证规则(不变) const loginRules = computed(() => ({ username: [ { required: true, trigger: "blur", message: t("login.message.username.required") } ], password: [ { required: true, trigger: "blur", message: t("login.message.password.required") } ] })); /** * 登录提交(核心简化:移除动态路由逻辑,直接跳转) */ async function handleLoginSubmit() { try { const valid = await loginFormRef.value?.validate(); if (!valid) return; loading.value = true; // 1. 执行登录(存储 Token) await userStore.login(loginFormData.value); console.log("登录成功,表单数据:", loginFormData.value); // 2. 获取 Token 并校验(确保登录状态有效) const token = AuthStorage.getAccessToken(); if (!token) { throw new Error("Token 存储失败,请重试"); } console.log("当前 Token:", token); // 3. (可选)获取用户信息(仅存信息,不依赖其菜单) const infoRes = await axios({ url: "http://10.170.1.104:8978/admin/admin/info", method: "get", headers: { "Authorization": token } }); if (infoRes.data.code !== 200) { throw new Error(`获取用户信息失败:${infoRes.data.msg || "未知错误"}`); } console.log("用户信息:", infoRes.data.data); // 仅打印,不用于路由 // 4. 确定跳转路径(简化:redirect参数优先,无则跳首页) let redirectPath = "/"; // 兜底跳首页(静态路由,确保存在) // 如果有 redirect 参数(如之前访问 /dashboard 被拦截),则跳该路径 if (route.query.redirect && typeof route.query.redirect === "string") { const decodedRedirect = decodeURIComponent(route.query.redirect); // 仅允许跳静态路由中的路径(避免无效路径) const validStaticPaths = ["/", "/dashboard", "/profile"]; // 替换为你的静态路由路径 if (validStaticPaths.includes(decodedRedirect)) { redirectPath = decodedRedirect; } console.log("使用 redirect 参数跳转:", redirectPath); } console.log("最终跳转路径:", redirectPath); // // 5. 直接跳转(移除所有动态路由相关干扰) // await router.replace(redirectPath); // ElMessage.success("登录成功,已进入系统"); // 5. 直接跳转(添加详细日志) console.log("准备执行跳转,路由实例:", router); console.log("router.replace 方法是否存在:", typeof router.replace === "function"); console.log("跳转路径是否为字符串:", typeof redirectPath === "string"); try { console.log("开始执行 router.replace,路径:", redirectPath); const result = await router.replace(redirectPath); console.log("router.replace 执行成功,返回结果:", result); ElMessage.success("登录成功,已进入系统"); } catch (error) { console.error("router.replace 执行失败,错误详情:", error); // console.error("错误名称:", error.name); // console.error("错误消息:", error.message); // console.error("错误堆栈:", jumpError.stack); // ElMessage.error("跳转失败:" + jumpError.message); } } catch (error: any) { // 错误详情打印(便于排查) console.error("登录跳转失败详情:", { message: error.message, statusCode: error.response?.status, responseData: error.response?.data, requestUrl: error.response?.config?.url }); // 用户友好提示 const errorMsg = error.response?.data?.msg || error.message || "登录失败,请联系管理员"; ElMessage.error(errorMsg); } finally { loading.value = false; } } // 大写锁定检查(不变) function checkCapsLock(event: KeyboardEvent) { isCapsLock.value = event.getModifierState("CapsLock"); } // 切换表单(不变) const emit = defineEmits(["update:modelValue"]); function toOtherForm(type: "register" | "resetPwd") { emit("update:modelValue", type); } </script> <style lang="scss" scoped> .third-party-login { .divider-container { display: flex; align-items: center; margin: 40px 0; .divider-line { flex: 1; height: 1px; background: linear-gradient(to right, transparent, var(--el-border-color-light), transparent); } .divider-text { padding: 0 16px; font-size: 12px; color: var(--el-text-color-regular); white-space: nowrap; } } } </style> import { createApp } from "vue"; import App from "./App.vue"; import setupPlugins from "@/plugins"; // 导入router import router from "@/router"; // 暗黑主题样式 import "element-plus/theme-chalk/dark/css-vars.css"; import "vxe-table/lib/style.css"; // 暗黑模式自定义变量 import "@/styles/dark/css-vars.css"; import "@/styles/index.scss"; import "uno.css"; // 过渡动画 import "animate.css"; // 自动为某些默认事件(如 touchstart、wheel 等)添加 { passive: true },提升滚动性能并消除控制台的非被动事件监听警告 import "default-passive-events"; const app = createApp(App); // 注册插件 app.use(setupPlugins); app.use(router); app.mount("#app"); // src/router/index.ts import type { App } from "vue"; // ✅ 从 vue 导入 App 类型 import type { RouteRecordRaw } from "vue-router"; // ✅ RouteRecordRaw 仍从 vue-router 导入 import { createRouter, createWebHashHistory } from "vue-router"; // 1. 导入静态路由(根据你的项目实际路由调整,若没有单独文件可直接定义) const constantRoutes: RouteRecordRaw[] = [ { path: "/login", component: () => import("@/views/login/index.vue"), meta: { hidden: true }, }, { path: "/404", component: () => import("@/views/error/404.vue"), meta: { hidden: true }, }, { path: "/test-jump", name: "TestJump", component: () => import("../views/test/TestJump.vue"), // 新建空白组件 meta: { hidden: true }, }, { path: "/", component: () => import("@/layouts/index.vue"), // 你的布局组件路径 redirect: "/dashboard", children: [ { path: "dashboard", component: () => import("@/views/dashboard/index.vue"), // 你的首页路径 meta: { title: "首页", icon: "home" }, }, ], }, ]; // 2. 创建路由实例(这里用 hash 模式,避免后端配置,适合开发) const router = createRouter({ history: createWebHashHistory(), // 若用 history 模式,需改 createWebHistory() routes: constantRoutes, scrollBehavior: () => ({ left: 0, top: 0 }), // 跳转后滚动到顶部 }); // 3. 全局注册路由的方法(供 main.ts 使用) export function setupRouter(app: App<Element>) { app.use(router); } // 4. 关键:添加默认导出!!!(解决“不提供 default 导出”的核心) export default router; // 可选:保留命名导出(方便需要显式导入的场景,非必需) export { router, constantRoutes }; 为什么不能执行const result = await router.replace(redirectPath);,进行安全跳转
10-17
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值