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:
Ray.Hao
2026-05-19 18:14:51 +08:00
parent 1c9e6f0054
commit 3fe8d018fc
3 changed files with 142 additions and 88 deletions

View File

@@ -48,9 +48,7 @@
"ignoreDevUnusedFiles": false,
"ignoreUploadUnusedFiles": false
},
"usingComponents": true,
"darkMode": true,
"themeLocation": "theme.json"
"usingComponents": true
},
"mp-alipay": {
"usingComponents": true
@@ -65,8 +63,5 @@
"enable": false
},
"vueVersion": "3",
"h5": {
"darkmode": true,
"themeLocation": "theme.json"
}
"h5": {}
}

View File

@@ -7,13 +7,10 @@
"backgroundTextStyle": "@bgTxtStyle",
"backgroundColorTop": "@bgColorTop",
"backgroundColorBottom": "@bgColorBottom",
"enablePullDownRefresh": false,
"onReachBottomDistance": 50,
"animationType": "pop-in",
"animationDuration": 300
"enablePullDownRefresh": false
},
"pages": [
// GENERATED BY UNI-PAGES, PLATFORM: H5
// GENERATED BY UNI-PAGES, PLATFORM: H5 || MP-WEIXIN
{
"path": "pages/index/index",
"type": "home",
@@ -215,15 +212,13 @@
"subPackages": [],
"tabBar": {
"custom": true,
"customize": true,
"overlay": true,
"height": "0",
"color": "@tabColor",
"selectedColor": "@tabSelectedColor",
"backgroundColor": "@tabBgColor",
"borderStyle": "@tabBorderStyle",
"list": [
// GENERATED BY UNI-PAGES, PLATFORM: H5
// GENERATED BY UNI-PAGES, PLATFORM: H5 || MP-WEIXIN
{
"pagePath": "pages/index/index"
},

View File

@@ -34,69 +34,84 @@
</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-icon name="user" size="20" color="var(--color-text-placeholder)" />
<input
v-model="formData.username"
class="login__field-input"
:placeholder="loginMode === 'PASSWORD' ? '请输入用户名' : '请输入手机号'"
:maxlength="loginMode === 'PASSWORD' ? 50 : 11"
/>
</view>
<wd-form-item prop="username" layout="vertical">
<view class="login__field">
<wd-icon name="user" size="20" color="var(--color-text-placeholder)" />
<input
v-model="formData.username"
class="login__field-input"
:placeholder="loginMode === 'PASSWORD' ? '请输入用户名' : '请输入手机号'"
:maxlength="loginMode === 'PASSWORD' ? 50 : 11"
/>
</view>
</wd-form-item>
<!-- 密码 -->
<view v-if="loginMode === 'PASSWORD'" class="login__form-item login__field">
<wd-icon name="lock" size="20" color="var(--color-text-placeholder)" />
<input
v-model="formData.password"
class="login__field-input"
placeholder="请输入密码"
:maxlength="50"
password
@confirm="handleLogin"
/>
</view>
<wd-form-item v-if="loginMode === 'PASSWORD'" prop="password" layout="vertical">
<view class="login__field">
<wd-icon name="lock" size="20" color="var(--color-text-placeholder)" />
<input
v-model="formData.password"
class="login__field-input"
placeholder="请输入密码"
:maxlength="50"
password
@confirm="handleLogin"
/>
</view>
</wd-form-item>
<!-- 图形验证码密码登录时显示 -->
<view v-if="loginMode === 'PASSWORD'" class="login__form-item login__field">
<wd-icon name="code-square" size="20" color="var(--color-text-placeholder)" />
<input
v-model="formData.captchaCode"
class="login__field-input"
placeholder="请输入验证码"
:maxlength="6"
@confirm="handleLogin"
/>
<image
v-if="captchaBase64"
class="login__captcha-img"
:src="captchaBase64"
mode="aspectFit"
@click="fetchCaptcha"
/>
</view>
<wd-form-item v-if="loginMode === 'PASSWORD'" prop="captchaCode" layout="vertical">
<view class="login__field">
<wd-icon name="code-square" size="20" color="var(--color-text-placeholder)" />
<input
v-model="formData.captchaCode"
class="login__field-input"
placeholder="请输入验证码"
:maxlength="6"
@confirm="handleLogin"
/>
<image
v-if="captchaBase64"
class="login__captcha-img"
:src="captchaBase64"
mode="aspectFit"
@click="fetchCaptcha"
/>
</view>
</wd-form-item>
<!-- 短信验证码 -->
<view v-else class="login__form-item login__field">
<wd-icon name="lock" size="20" color="var(--color-text-placeholder)" />
<input
v-model="formData.code"
class="login__field-input"
placeholder="请输入验证码"
type="number"
:maxlength="6"
@confirm="handleLogin"
/>
<view
class="login__code-btn"
:class="smsCountdown > 0 ? 'login__code-btn--disabled' : 'login__code-btn--active'"
@click="handleSendCode"
>
{{ smsCountdown > 0 ? `${smsCountdown}s` : "获取验证码" }}
<wd-form-item v-if="loginMode === 'SMS'" prop="code" layout="vertical">
<view class="login__field">
<wd-icon name="lock" size="20" color="var(--color-text-placeholder)" />
<input
v-model="formData.code"
class="login__field-input"
placeholder="请输入验证码"
type="number"
:maxlength="6"
@confirm="handleLogin"
/>
<view
class="login__code-btn"
:class="smsCountdown > 0 ? 'login__code-btn--disabled' : 'login__code-btn--active'"
@click="handleSendCode"
>
{{ smsCountdown > 0 ? `${smsCountdown}s` : "获取验证码" }}
</view>
</view>
</view>
</wd-form-item>
<!-- 演示环境提示 -->
<view v-if="loginMode === 'SMS'" class="login__form-item login__demo-hint">
@@ -119,7 +134,7 @@
{{ loginMode === "PASSWORD" ? "验证码登录" : "密码登录" }}
</text>
</view>
</view>
</wd-form>
<!-- #ifdef MP-WEIXIN -->
<!-- 微信登录区域 -->
@@ -250,6 +265,7 @@ definePage({
import { onLoad, onShow } from "@dcloudio/uni-app";
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 { useCountdown } from "@/composables/useCountdown";
@@ -258,6 +274,7 @@ import AuthAPI from "@/api/auth";
const toast = useToast();
const dialog = useDialog("policy-box");
const userStore = useUserStore();
const loginFormRef = ref();
// 导航栏尺寸
const statusBarHeight = ref(20);
@@ -309,15 +326,33 @@ const loginModeDesc = computed(() => {
const isValidMobile = (mobile: string) => /^1\d{10}$/.test((mobile || "").trim());
const canSubmit = computed(() => {
if (loginMode.value === "PASSWORD") {
return (formData.value.username || "").trim() && (formData.value.password || "").trim();
}
if (loginMode.value === "SMS") {
return isValidMobile(formData.value.username) && (formData.value.code || "").trim();
}
return false;
});
// 表单校验规则
const formSchema = computed<FormSchema>(() => ({
validate(model) {
const issues: { path: string[]; message: string }[] = [];
if (loginMode.value === "PASSWORD") {
if (!(model.username || "").trim()) {
issues.push({ path: ["username"], message: "请输入用户名" });
}
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 () => {
@@ -378,7 +413,6 @@ const openPolicyDialog = (action: "FORM" | "WECHAT_PHONE", phoneCode = "") => {
// 表单登录
async function doFormLogin() {
if (isLoading.value) return;
if (isLoading.value) return;
isLoading.value = true;
try {
@@ -407,13 +441,9 @@ async function doFormLogin() {
}
const handleLogin = async () => {
// 先校验表单必填项
if (!canSubmit.value) {
toast.error(
loginMode.value === "PASSWORD" ? "请输入用户名和密码" : "请输入正确的手机号和验证码"
);
return;
}
// 先校验表单必填项(内联提示,不再弹窗)
const { valid } = await loginFormRef.value?.validate();
if (!valid) return;
// 再校验隐私协议
if (!isAgreePolicy.value) {
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 {
display: flex;