refactor(login): 使用 wd-form 官方表单校验替换手动校验,优化错误提示展示方式
- 使用 wd-form + wd-form-item 配合 formSchema 实现表单校验 - 校验错误改为内联提示(error-type=message),不再弹窗 - 修复 wd-form-item 水平布局导致错误提示与输入框挤在一起的样式问题 - 为 wd-form-item 添加 layout=vertical 并将错误提示强制显示在输入框下方 - 移除 manifest.json 中不兼容小程序编译的 darkMode/themeLocation 配置 - 移除 pages.json 中小程序不支持的 animationType/animationDuration/customize/overlay 字段
This commit is contained in:
@@ -48,9 +48,7 @@
|
|||||||
"ignoreDevUnusedFiles": false,
|
"ignoreDevUnusedFiles": false,
|
||||||
"ignoreUploadUnusedFiles": false
|
"ignoreUploadUnusedFiles": false
|
||||||
},
|
},
|
||||||
"usingComponents": true,
|
"usingComponents": true
|
||||||
"darkMode": true,
|
|
||||||
"themeLocation": "theme.json"
|
|
||||||
},
|
},
|
||||||
"mp-alipay": {
|
"mp-alipay": {
|
||||||
"usingComponents": true
|
"usingComponents": true
|
||||||
@@ -65,8 +63,5 @@
|
|||||||
"enable": false
|
"enable": false
|
||||||
},
|
},
|
||||||
"vueVersion": "3",
|
"vueVersion": "3",
|
||||||
"h5": {
|
"h5": {}
|
||||||
"darkmode": true,
|
|
||||||
"themeLocation": "theme.json"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,10 @@
|
|||||||
"backgroundTextStyle": "@bgTxtStyle",
|
"backgroundTextStyle": "@bgTxtStyle",
|
||||||
"backgroundColorTop": "@bgColorTop",
|
"backgroundColorTop": "@bgColorTop",
|
||||||
"backgroundColorBottom": "@bgColorBottom",
|
"backgroundColorBottom": "@bgColorBottom",
|
||||||
"enablePullDownRefresh": false,
|
"enablePullDownRefresh": false
|
||||||
"onReachBottomDistance": 50,
|
|
||||||
"animationType": "pop-in",
|
|
||||||
"animationDuration": 300
|
|
||||||
},
|
},
|
||||||
"pages": [
|
"pages": [
|
||||||
// GENERATED BY UNI-PAGES, PLATFORM: H5
|
// GENERATED BY UNI-PAGES, PLATFORM: H5 || MP-WEIXIN
|
||||||
{
|
{
|
||||||
"path": "pages/index/index",
|
"path": "pages/index/index",
|
||||||
"type": "home",
|
"type": "home",
|
||||||
@@ -215,15 +212,13 @@
|
|||||||
"subPackages": [],
|
"subPackages": [],
|
||||||
"tabBar": {
|
"tabBar": {
|
||||||
"custom": true,
|
"custom": true,
|
||||||
"customize": true,
|
|
||||||
"overlay": true,
|
|
||||||
"height": "0",
|
"height": "0",
|
||||||
"color": "@tabColor",
|
"color": "@tabColor",
|
||||||
"selectedColor": "@tabSelectedColor",
|
"selectedColor": "@tabSelectedColor",
|
||||||
"backgroundColor": "@tabBgColor",
|
"backgroundColor": "@tabBgColor",
|
||||||
"borderStyle": "@tabBorderStyle",
|
"borderStyle": "@tabBorderStyle",
|
||||||
"list": [
|
"list": [
|
||||||
// GENERATED BY UNI-PAGES, PLATFORM: H5
|
// GENERATED BY UNI-PAGES, PLATFORM: H5 || MP-WEIXIN
|
||||||
{
|
{
|
||||||
"pagePath": "pages/index/index"
|
"pagePath": "pages/index/index"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -34,69 +34,84 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 表单区域 -->
|
<!-- 表单区域 -->
|
||||||
<view v-if="loginMode !== 'WECHAT'" class="login__form">
|
<wd-form
|
||||||
|
v-if="loginMode !== 'WECHAT'"
|
||||||
|
ref="loginFormRef"
|
||||||
|
:model="formData"
|
||||||
|
:schema="formSchema"
|
||||||
|
error-type="message"
|
||||||
|
hide-asterisk
|
||||||
|
>
|
||||||
<!-- 用户名/手机号 -->
|
<!-- 用户名/手机号 -->
|
||||||
<view class="login__form-item login__field">
|
<wd-form-item prop="username" layout="vertical">
|
||||||
<wd-icon name="user" size="20" color="var(--color-text-placeholder)" />
|
<view class="login__field">
|
||||||
<input
|
<wd-icon name="user" size="20" color="var(--color-text-placeholder)" />
|
||||||
v-model="formData.username"
|
<input
|
||||||
class="login__field-input"
|
v-model="formData.username"
|
||||||
:placeholder="loginMode === 'PASSWORD' ? '请输入用户名' : '请输入手机号'"
|
class="login__field-input"
|
||||||
:maxlength="loginMode === 'PASSWORD' ? 50 : 11"
|
:placeholder="loginMode === 'PASSWORD' ? '请输入用户名' : '请输入手机号'"
|
||||||
/>
|
:maxlength="loginMode === 'PASSWORD' ? 50 : 11"
|
||||||
</view>
|
/>
|
||||||
|
</view>
|
||||||
|
</wd-form-item>
|
||||||
|
|
||||||
<!-- 密码 -->
|
<!-- 密码 -->
|
||||||
<view v-if="loginMode === 'PASSWORD'" class="login__form-item login__field">
|
<wd-form-item v-if="loginMode === 'PASSWORD'" prop="password" layout="vertical">
|
||||||
<wd-icon name="lock" size="20" color="var(--color-text-placeholder)" />
|
<view class="login__field">
|
||||||
<input
|
<wd-icon name="lock" size="20" color="var(--color-text-placeholder)" />
|
||||||
v-model="formData.password"
|
<input
|
||||||
class="login__field-input"
|
v-model="formData.password"
|
||||||
placeholder="请输入密码"
|
class="login__field-input"
|
||||||
:maxlength="50"
|
placeholder="请输入密码"
|
||||||
password
|
:maxlength="50"
|
||||||
@confirm="handleLogin"
|
password
|
||||||
/>
|
@confirm="handleLogin"
|
||||||
</view>
|
/>
|
||||||
|
</view>
|
||||||
|
</wd-form-item>
|
||||||
|
|
||||||
<!-- 图形验证码(密码登录时显示) -->
|
<!-- 图形验证码(密码登录时显示) -->
|
||||||
<view v-if="loginMode === 'PASSWORD'" class="login__form-item login__field">
|
<wd-form-item v-if="loginMode === 'PASSWORD'" prop="captchaCode" layout="vertical">
|
||||||
<wd-icon name="code-square" size="20" color="var(--color-text-placeholder)" />
|
<view class="login__field">
|
||||||
<input
|
<wd-icon name="code-square" size="20" color="var(--color-text-placeholder)" />
|
||||||
v-model="formData.captchaCode"
|
<input
|
||||||
class="login__field-input"
|
v-model="formData.captchaCode"
|
||||||
placeholder="请输入验证码"
|
class="login__field-input"
|
||||||
:maxlength="6"
|
placeholder="请输入验证码"
|
||||||
@confirm="handleLogin"
|
:maxlength="6"
|
||||||
/>
|
@confirm="handleLogin"
|
||||||
<image
|
/>
|
||||||
v-if="captchaBase64"
|
<image
|
||||||
class="login__captcha-img"
|
v-if="captchaBase64"
|
||||||
:src="captchaBase64"
|
class="login__captcha-img"
|
||||||
mode="aspectFit"
|
:src="captchaBase64"
|
||||||
@click="fetchCaptcha"
|
mode="aspectFit"
|
||||||
/>
|
@click="fetchCaptcha"
|
||||||
</view>
|
/>
|
||||||
|
</view>
|
||||||
|
</wd-form-item>
|
||||||
|
|
||||||
<!-- 短信验证码 -->
|
<!-- 短信验证码 -->
|
||||||
<view v-else class="login__form-item login__field">
|
<wd-form-item v-if="loginMode === 'SMS'" prop="code" layout="vertical">
|
||||||
<wd-icon name="lock" size="20" color="var(--color-text-placeholder)" />
|
<view class="login__field">
|
||||||
<input
|
<wd-icon name="lock" size="20" color="var(--color-text-placeholder)" />
|
||||||
v-model="formData.code"
|
<input
|
||||||
class="login__field-input"
|
v-model="formData.code"
|
||||||
placeholder="请输入验证码"
|
class="login__field-input"
|
||||||
type="number"
|
placeholder="请输入验证码"
|
||||||
:maxlength="6"
|
type="number"
|
||||||
@confirm="handleLogin"
|
:maxlength="6"
|
||||||
/>
|
@confirm="handleLogin"
|
||||||
<view
|
/>
|
||||||
class="login__code-btn"
|
<view
|
||||||
:class="smsCountdown > 0 ? 'login__code-btn--disabled' : 'login__code-btn--active'"
|
class="login__code-btn"
|
||||||
@click="handleSendCode"
|
:class="smsCountdown > 0 ? 'login__code-btn--disabled' : 'login__code-btn--active'"
|
||||||
>
|
@click="handleSendCode"
|
||||||
{{ smsCountdown > 0 ? `${smsCountdown}s` : "获取验证码" }}
|
>
|
||||||
|
{{ smsCountdown > 0 ? `${smsCountdown}s` : "获取验证码" }}
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</wd-form-item>
|
||||||
|
|
||||||
<!-- 演示环境提示 -->
|
<!-- 演示环境提示 -->
|
||||||
<view v-if="loginMode === 'SMS'" class="login__form-item login__demo-hint">
|
<view v-if="loginMode === 'SMS'" class="login__form-item login__demo-hint">
|
||||||
@@ -119,7 +134,7 @@
|
|||||||
{{ loginMode === "PASSWORD" ? "验证码登录" : "密码登录" }}
|
{{ loginMode === "PASSWORD" ? "验证码登录" : "密码登录" }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</wd-form>
|
||||||
|
|
||||||
<!-- #ifdef MP-WEIXIN -->
|
<!-- #ifdef MP-WEIXIN -->
|
||||||
<!-- 微信登录区域 -->
|
<!-- 微信登录区域 -->
|
||||||
@@ -250,6 +265,7 @@ definePage({
|
|||||||
|
|
||||||
import { onLoad, onShow } from "@dcloudio/uni-app";
|
import { onLoad, onShow } from "@dcloudio/uni-app";
|
||||||
import { useToast, useDialog } from "@wot-ui/ui";
|
import { useToast, useDialog } from "@wot-ui/ui";
|
||||||
|
import type { FormSchema } from "@wot-ui/ui/components/wd-form/types";
|
||||||
|
|
||||||
import { useUserStore } from "@/store/modules/user";
|
import { useUserStore } from "@/store/modules/user";
|
||||||
import { useCountdown } from "@/composables/useCountdown";
|
import { useCountdown } from "@/composables/useCountdown";
|
||||||
@@ -258,6 +274,7 @@ import AuthAPI from "@/api/auth";
|
|||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const dialog = useDialog("policy-box");
|
const dialog = useDialog("policy-box");
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
const loginFormRef = ref();
|
||||||
|
|
||||||
// 导航栏尺寸
|
// 导航栏尺寸
|
||||||
const statusBarHeight = ref(20);
|
const statusBarHeight = ref(20);
|
||||||
@@ -309,15 +326,33 @@ const loginModeDesc = computed(() => {
|
|||||||
|
|
||||||
const isValidMobile = (mobile: string) => /^1\d{10}$/.test((mobile || "").trim());
|
const isValidMobile = (mobile: string) => /^1\d{10}$/.test((mobile || "").trim());
|
||||||
|
|
||||||
const canSubmit = computed(() => {
|
// 表单校验规则
|
||||||
if (loginMode.value === "PASSWORD") {
|
const formSchema = computed<FormSchema>(() => ({
|
||||||
return (formData.value.username || "").trim() && (formData.value.password || "").trim();
|
validate(model) {
|
||||||
}
|
const issues: { path: string[]; message: string }[] = [];
|
||||||
if (loginMode.value === "SMS") {
|
if (loginMode.value === "PASSWORD") {
|
||||||
return isValidMobile(formData.value.username) && (formData.value.code || "").trim();
|
if (!(model.username || "").trim()) {
|
||||||
}
|
issues.push({ path: ["username"], message: "请输入用户名" });
|
||||||
return false;
|
}
|
||||||
});
|
if (!(model.password || "").trim()) {
|
||||||
|
issues.push({ path: ["password"], message: "请输入密码" });
|
||||||
|
}
|
||||||
|
if (!(model.captchaCode || "").trim()) {
|
||||||
|
issues.push({ path: ["captchaCode"], message: "请输入验证码" });
|
||||||
|
}
|
||||||
|
} else if (loginMode.value === "SMS") {
|
||||||
|
if (!(model.username || "").trim()) {
|
||||||
|
issues.push({ path: ["username"], message: "请输入手机号" });
|
||||||
|
} else if (!isValidMobile(model.username)) {
|
||||||
|
issues.push({ path: ["username"], message: "请输入正确的手机号" });
|
||||||
|
}
|
||||||
|
if (!(model.code || "").trim()) {
|
||||||
|
issues.push({ path: ["code"], message: "请输入验证码" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return issues;
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
// 图形验证码
|
// 图形验证码
|
||||||
const fetchCaptcha = async () => {
|
const fetchCaptcha = async () => {
|
||||||
@@ -378,7 +413,6 @@ const openPolicyDialog = (action: "FORM" | "WECHAT_PHONE", phoneCode = "") => {
|
|||||||
|
|
||||||
// 表单登录
|
// 表单登录
|
||||||
async function doFormLogin() {
|
async function doFormLogin() {
|
||||||
if (isLoading.value) return;
|
|
||||||
if (isLoading.value) return;
|
if (isLoading.value) return;
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
try {
|
try {
|
||||||
@@ -407,13 +441,9 @@ async function doFormLogin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleLogin = async () => {
|
const handleLogin = async () => {
|
||||||
// 先校验表单必填项
|
// 先校验表单必填项(内联提示,不再弹窗)
|
||||||
if (!canSubmit.value) {
|
const { valid } = await loginFormRef.value?.validate();
|
||||||
toast.error(
|
if (!valid) return;
|
||||||
loginMode.value === "PASSWORD" ? "请输入用户名和密码" : "请输入正确的手机号和验证码"
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 再校验隐私协议
|
// 再校验隐私协议
|
||||||
if (!isAgreePolicy.value) {
|
if (!isAgreePolicy.value) {
|
||||||
openPolicyDialog("FORM");
|
openPolicyDialog("FORM");
|
||||||
@@ -748,6 +778,40 @@ onShow(() => uni.setNavigationBarTitle({ title: "" }));
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 覆盖 wd-form-item 默认样式,仅用于校验和错误提示
|
||||||
|
:deep(.wd-form-item) {
|
||||||
|
padding: 0 !important;
|
||||||
|
margin-top: 32rpx;
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 垂直布局下隐藏左侧区域(无标题时)
|
||||||
|
:deep(.wd-form-item .wd-cell__left) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.wd-form-item .wd-cell__wrapper) {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.wd-form-item .wd-cell__right) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 错误提示:独占一行,显示在输入框下方
|
||||||
|
:deep(.wd-form-item__error-message) {
|
||||||
|
display: block !important;
|
||||||
|
width: 100% !important;
|
||||||
|
padding: 8rpx 0 0 !important;
|
||||||
|
font-size: 24rpx !important;
|
||||||
|
line-height: 1.5 !important;
|
||||||
|
color: var(--color-danger) !important;
|
||||||
|
}
|
||||||
|
|
||||||
// 输入框行
|
// 输入框行
|
||||||
.login__field {
|
.login__field {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
Reference in New Issue
Block a user