Merge branch 'wangyi/feature-20251022162725-启动页登录注册' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into dongqian/feature-20251022181325-deepmate简版
wangyi/feature-20251022162725-启动页登录注册
-
9pages.json
-
292pages/start/Registration/Registration.vue
-
1341pages/start/Registration/list.js
-
263pages/start/login/login.vue
-
69pages/start/login/verification.js
-
985pages/start/recoverPassword/recoverPassword.vue
-
47pages/start/select/select.vue
-
4pages/start/startup/startup.vue
-
BINstatic/icons/To.png
-
BINstatic/icons/back.png
-
BINstatic/icons/look.png
-
BINstatic/icons/start-logo.png
-
BINstatic/icons/unlook.png
-
BINstatic/select.png
-
BINstatic/start-logo.png
-
18uni_modules/uni-popup/changelog.md
-
28uni_modules/uni-popup/components/uni-popup-dialog/uni-popup-dialog.vue
-
9uni_modules/uni-popup/components/uni-popup-share/uni-popup-share.vue
-
31uni_modules/uni-popup/components/uni-popup/uni-popup.vue
-
191uni_modules/uni-popup/package.json
-
7uni_modules/uni-transition/changelog.md
-
520uni_modules/uni-transition/components/uni-transition/uni-transition.vue
-
103uni_modules/uni-transition/package.json
1341
pages/start/Registration/list.js
File diff suppressed because it is too large
View File
@ -0,0 +1,69 @@ |
|||||
|
|
||||
|
function verificationPhone(countryCode,phoneNumber) { |
||||
|
switch (countryCode) { |
||||
|
case '+86': |
||||
|
return verificationChina(phoneNumber); |
||||
|
case '+1': |
||||
|
return verificationAmerica(phoneNumber); |
||||
|
case '+65': |
||||
|
return verificationSingapore(phoneNumber); |
||||
|
case '+60': |
||||
|
return verificationMalaysia(phoneNumber); |
||||
|
case '+66': |
||||
|
return verificationThailand(phoneNumber); |
||||
|
case '+852': |
||||
|
return verificationHongKong(phoneNumber); |
||||
|
case '+84': |
||||
|
return verificationVietnam(phoneNumber); |
||||
|
default: |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
function verificationChina(phoneNumber){ |
||||
|
const phoneRegex = /^1[3-9]\d{9}$/; |
||||
|
return phoneRegex.test(phoneNumber); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
function verificationAmerica(phoneNumber){ |
||||
|
const phoneRegex = /^[2-9]\d{2}[- ]?\d{4}$/; |
||||
|
return phoneRegex.test(phoneNumber); |
||||
|
} |
||||
|
function verificationSingapore(phoneNumber){ |
||||
|
const phoneRegex = /^[89]\d{7}$/; |
||||
|
return phoneRegex.test(phoneNumber); |
||||
|
} |
||||
|
function verificationMalaysia(phoneNumber){ |
||||
|
const phoneRegex = /^01\d{8}$/; |
||||
|
return phoneRegex.test(phoneNumber); |
||||
|
} |
||||
|
|
||||
|
function verificationHongKong(phoneNumber){ |
||||
|
const phoneRegex = /^0[896]\d{8}$/; |
||||
|
return phoneRegex.test(phoneNumber); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
function verificationThailand(phoneNumber){ |
||||
|
const phoneRegex = /^[5-9]\d{7}$/; |
||||
|
return phoneRegex.test(phoneNumber); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
function verificationVietnam(phoneNumber){ |
||||
|
const phoneRegex = /^(0)?[3-9]\d{8}$/; |
||||
|
return phoneRegex.test(phoneNumber); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
function verificationEmail(email) { |
||||
|
const emailRegex = /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/; |
||||
|
return emailRegex.test(email); |
||||
|
} |
||||
|
|
||||
|
export {verificationPhone,verificationEmail} |
||||
@ -0,0 +1,985 @@ |
|||||
|
<template> |
||||
|
<view class="login-registration-container"> |
||||
|
<!-- 自定义导航栏 --> |
||||
|
<view |
||||
|
class="custom-navbar" |
||||
|
:style="{ paddingTop: safeAreaInsets?.top + 'px' }" |
||||
|
> |
||||
|
<view class="nav-left"> |
||||
|
<image |
||||
|
class="icons" |
||||
|
@click="goToBack" |
||||
|
src="../../../static/icons/back.png" |
||||
|
alt="返回首页" |
||||
|
/> |
||||
|
</view> |
||||
|
<view class="nav-right"> |
||||
|
<image |
||||
|
class="icons" |
||||
|
src="../../../static/icons/Headset.png" |
||||
|
alt="联系客服" |
||||
|
/> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- Logo --> |
||||
|
<!-- <image class="logo" src="/static/logo.png" mode="aspectFit"></image> --> |
||||
|
|
||||
|
<!-- 欢迎语 --> |
||||
|
<text class="welcome-text">找回密码</text> |
||||
|
|
||||
|
<!-- 邮箱/手机号切换 --> |
||||
|
<view v-if="!isRecovering" class="switch-container"> |
||||
|
<text |
||||
|
class="switch-item" |
||||
|
:class="{ active: switchType === 'Email' }" |
||||
|
@click="switchEmail" |
||||
|
>邮箱</text |
||||
|
> |
||||
|
<text |
||||
|
class="switch-item" |
||||
|
:class="{ active: switchType === 'Phone' }" |
||||
|
@click="switchPhone" |
||||
|
>手机号</text |
||||
|
> |
||||
|
</view> |
||||
|
<view v-else class="switch-container-occupy"> </view> |
||||
|
<!-- 输入框 --> |
||||
|
<view v-if="isRecovering" class="input-container"> |
||||
|
<view v-if="switchType === 'Email'"> |
||||
|
<!-- 修改邮箱输入框容器,将图标包含在内 --> |
||||
|
<view class="input-with-icon"> |
||||
|
<image |
||||
|
class="input-icon" |
||||
|
src="../../../static/icons/Unlock.png" |
||||
|
alt="" |
||||
|
/> |
||||
|
<input |
||||
|
class="input-field" |
||||
|
:type="newPasswordLookFirst ? 'text' : 'password'" |
||||
|
placeholder="输入新密码" |
||||
|
v-model="newPasswordFirst" |
||||
|
/> |
||||
|
<image |
||||
|
class="input-icon-eye" |
||||
|
@click="newPasswordLookFirst = !newPasswordLookFirst" |
||||
|
:src=" |
||||
|
!newPasswordLookFirst |
||||
|
? '../../../static/icons/unlook.png' |
||||
|
: '../../../static/icons/look.png' |
||||
|
" |
||||
|
alt="" |
||||
|
/> |
||||
|
</view> |
||||
|
<view class="input-with-icon"> |
||||
|
<image |
||||
|
class="input-icon" |
||||
|
src="../../../static/icons/Unlock.png" |
||||
|
alt="" |
||||
|
/> |
||||
|
<input |
||||
|
class="input-field" |
||||
|
:type="newPasswordLookSecond ? 'text' : 'password'" |
||||
|
placeholder="再次确认" |
||||
|
v-model="newPasswordSecond" |
||||
|
/> |
||||
|
<image |
||||
|
class="input-icon-eye" |
||||
|
@click="newPasswordLookSecond = !newPasswordLookSecond" |
||||
|
:src=" |
||||
|
!newPasswordLookSecond |
||||
|
? '../../../static/icons/unlook.png' |
||||
|
: '../../../static/icons/look.png' |
||||
|
" |
||||
|
alt="" |
||||
|
/> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view v-else class="input-container"> |
||||
|
<view v-if="switchType === 'Email'"> |
||||
|
<!-- 修改邮箱输入框容器,将图标包含在内 --> |
||||
|
<view class="input-with-icon"> |
||||
|
<image |
||||
|
class="input-icon" |
||||
|
src="../../../static/icons/Mail.png" |
||||
|
alt="" |
||||
|
/> |
||||
|
<input |
||||
|
class="input-field" |
||||
|
type="text" |
||||
|
placeholder="请输入邮箱" |
||||
|
v-model="email" |
||||
|
/> |
||||
|
<view> |
||||
|
<button |
||||
|
class="send-code-btn-email" |
||||
|
:disabled="isCodeBtnDisabled" |
||||
|
:class="{ 'send-code-btn-disabled': isCodeBtnDisabled }" |
||||
|
@click="sendCode" |
||||
|
> |
||||
|
<text |
||||
|
class="send-code-text" |
||||
|
:class="{ 'send-code-btn-disabled-text': isCodeBtnDisabled }" |
||||
|
>{{ codeBtnText }}</text |
||||
|
> |
||||
|
</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="input-with-icon"> |
||||
|
<image |
||||
|
class="input-icon" |
||||
|
src="../../../static/icons/Text-recognition.png" |
||||
|
alt="" |
||||
|
/> |
||||
|
<input |
||||
|
class="input-field" |
||||
|
type="text" |
||||
|
placeholder="请输入验证码" |
||||
|
v-model="password" |
||||
|
/> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view v-if="switchType === 'Phone'" class="phone-input-container"> |
||||
|
<view class="country-code-selector" @click="showCountryPicker"> |
||||
|
<image |
||||
|
class="country-flag-img" |
||||
|
src="../../../static/icons/Iphone.png" |
||||
|
mode="aspectFit" |
||||
|
></image> |
||||
|
<text class="country-code">{{ selectedCountry.code }}</text> |
||||
|
<!-- <text class="arrow-down">▼</text> --> |
||||
|
</view> |
||||
|
<input |
||||
|
class="input-field phone-input" |
||||
|
type="number" |
||||
|
placeholder="输入手机号" |
||||
|
v-model="phone" |
||||
|
@input="onPhoneInput" |
||||
|
/> |
||||
|
<view> |
||||
|
<button |
||||
|
class="send-code-btn" |
||||
|
:disabled="isCodeBtnDisabled" |
||||
|
:class="{ 'send-code-btn-disabled': isCodeBtnDisabled }" |
||||
|
@click="sendCode" |
||||
|
> |
||||
|
<text |
||||
|
class="send-code-text" |
||||
|
:class="{ 'send-code-btn-disabled-text': isCodeBtnDisabled }" |
||||
|
>{{ codeBtnText }}</text |
||||
|
> |
||||
|
</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view v-if="switchType === 'Phone'" class="input-with-icon"> |
||||
|
<image |
||||
|
class="input-icon" |
||||
|
src="../../../static/icons/Text-recognition.png" |
||||
|
alt="" |
||||
|
/> |
||||
|
<input |
||||
|
class="input-field" |
||||
|
type="text" |
||||
|
placeholder="请输入验证码" |
||||
|
v-model="password" |
||||
|
/> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 用户协议 --> |
||||
|
<view @click="changeCheckbox" class="agreement-container-one"> |
||||
|
<text |
||||
|
class="agreement-text-one" |
||||
|
:style="!isRecovering ? 'visibility: hidden' : 'visibility: visible'" |
||||
|
> |
||||
|
密码最少8位数 |
||||
|
</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 注册按钮 --> |
||||
|
<button class="register-btn" @click="register"> |
||||
|
{{ isRecovering ? "确认" : "下一步" }} |
||||
|
</button> |
||||
|
|
||||
|
<!-- 或者 --> |
||||
|
<text class="or-text-one" @click="goToRegistration" |
||||
|
>如果您还没有账号,点击注册 |
||||
|
<image class="to-icon" src="../../../static/icons/To.png"></image> |
||||
|
</text> |
||||
|
|
||||
|
<!-- 或者 --> |
||||
|
<text class="or-text" @click="goToLogin">已有账号?登录 </text> |
||||
|
|
||||
|
<!-- 同意弹窗 --> |
||||
|
<uniPopup ref="agreementPopup" type="dialog"> |
||||
|
<view class="popup-content"> |
||||
|
<text class="popup-title">同意并继续</text> |
||||
|
<text class="popup-message">请阅读并同意服务协议和隐私权限</text> |
||||
|
<button class="agree-button" @click="handleAgree"> |
||||
|
<text class="agree-text">同意</text> |
||||
|
</button> |
||||
|
</view> |
||||
|
</uniPopup> |
||||
|
<footerBar class="static-footer" :type="type"></footerBar> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script setup> |
||||
|
import { ref } from "vue"; |
||||
|
// 导入完整的国家列表 |
||||
|
import countryList from "../login/list"; |
||||
|
import footerBar from "../../../components/footerBar-cn.vue"; |
||||
|
import uniPopup from "../../../uni_modules/uni-popup/components/uni-popup/uni-popup.vue"; |
||||
|
import { verificationPhone, verificationEmail } from "../login/verification"; |
||||
|
|
||||
|
const type = ref("member"); |
||||
|
const email = ref(""); |
||||
|
const password = ref(""); |
||||
|
const newPasswordFirst = ref(""); |
||||
|
const newPasswordSecond = ref(""); |
||||
|
const phone = ref(""); |
||||
|
const country = ref("+86"); |
||||
|
const agreed = ref(false); |
||||
|
const switchType = ref("Phone"); // 默认是邮箱注册 |
||||
|
const { safeAreaInsets } = uni.getSystemInfoSync(); |
||||
|
const codeBtnText = ref("获取验证码"); |
||||
|
const isCodeBtnDisabled = ref(false); // 添加验证码按钮禁用状态 |
||||
|
const checkboxUrl = ref("../../../static/icons/Check-one-false.png"); |
||||
|
const isRecovering = ref(false); |
||||
|
const newPasswordLookFirst = ref(false); |
||||
|
const newPasswordLookSecond = ref(false); |
||||
|
|
||||
|
// 使用从list.js导入的完整国家列表数据 |
||||
|
const countries = ref( |
||||
|
countryList.list.map((item) => ({ |
||||
|
name: item.name, |
||||
|
code: `+${item.tel}`, |
||||
|
flag: item.flag, |
||||
|
short: item.short, |
||||
|
en: item.en, |
||||
|
})) |
||||
|
); |
||||
|
|
||||
|
// 默认选中的国家(中国) |
||||
|
const selectedCountry = ref( |
||||
|
countries.value.find((country) => country.short === "CN") || |
||||
|
countries.value[0] |
||||
|
); |
||||
|
|
||||
|
// 显示国家选择器 |
||||
|
function showCountryPicker() { |
||||
|
uni.showActionSheet({ |
||||
|
itemList: countries.value.map( |
||||
|
(country) => `${country.name} ${country.code}` |
||||
|
), |
||||
|
success: function (res) { |
||||
|
selectedCountry.value = countries.value[res.tapIndex]; |
||||
|
}, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function goToBack() { |
||||
|
// 返回上一页 |
||||
|
uni.navigateBack(-1); |
||||
|
} |
||||
|
|
||||
|
function switchEmail() { |
||||
|
// 切换到邮箱注册 |
||||
|
switchType.value = "Email"; |
||||
|
} |
||||
|
|
||||
|
function switchPhone() { |
||||
|
// 切换到手机注册 |
||||
|
switchType.value = "Phone"; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
function register() { |
||||
|
if (isRecovering.value) { |
||||
|
|
||||
|
if(!newPasswordFirst.value || !newPasswordSecond.value){ |
||||
|
uni.showToast({ |
||||
|
title: "密码不能为空", |
||||
|
icon: "none", |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (newPasswordFirst.value !== newPasswordSecond.value) { |
||||
|
uni.showToast({ |
||||
|
title: "前后密码不一致", |
||||
|
icon: "none", |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 密码逻辑 |
||||
|
return; |
||||
|
} |
||||
|
if (switchType.value === "Phone") { |
||||
|
// 登录逻辑 |
||||
|
if (!phone.value) { |
||||
|
uni.showToast({ |
||||
|
title: "请输入手机号码", |
||||
|
icon: "none", |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (!password.value) { |
||||
|
uni.showToast({ |
||||
|
title: "请输入验证码", |
||||
|
icon: "none", |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
const phoneAll = `${country.value}${phone.value}`; |
||||
|
console.log("完整手机号" + phoneAll); |
||||
|
if (validatePhoneNumber(country.value, phone.value)) { |
||||
|
console.log("登录成功:", phoneAll); |
||||
|
} |
||||
|
|
||||
|
// 发送登录请求 |
||||
|
// console.log("登录:", phone.value); |
||||
|
} |
||||
|
|
||||
|
if (switchType.value === "Email") { |
||||
|
// 登录逻辑 |
||||
|
if (!email.value) { |
||||
|
uni.showToast({ |
||||
|
title: "请输入邮箱地址", |
||||
|
icon: "none", |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (!password.value) { |
||||
|
uni.showToast({ |
||||
|
title: "请输入验证码", |
||||
|
icon: "none", |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const bool = verificationEmail(email.value); |
||||
|
console.log("验证是否成功", bool); |
||||
|
|
||||
|
// 检查格式是否正确 |
||||
|
if (!bool) { |
||||
|
uni.showToast({ |
||||
|
title: "邮箱格式不正确", |
||||
|
icon: "none", |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 发送登录请求 |
||||
|
console.log("登录:", email.value); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
isRecovering.value = !isRecovering.value; |
||||
|
|
||||
|
// 如果已经同意,则继续登录流程 |
||||
|
// uni.showToast({ |
||||
|
// title: "登录成功", |
||||
|
// icon: "success", |
||||
|
// }); |
||||
|
} |
||||
|
|
||||
|
function goToLogin() { |
||||
|
// 跳转到登录页 |
||||
|
uni.navigateTo({ |
||||
|
url: "/pages/start/login/login", |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function onPhoneInput(e) { |
||||
|
// 确保只允许输入数字 |
||||
|
const value = e.detail.value; |
||||
|
// 使用 isNaN 检查是否为有效数字 |
||||
|
if (isNaN(value)) { |
||||
|
phone.value = ""; |
||||
|
} else { |
||||
|
phone.value = value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// function sendCode() { |
||||
|
// // 如果按钮已禁用,则不执行后续逻辑 |
||||
|
// if (isCodeBtnDisabled.value) return; |
||||
|
|
||||
|
// // 设置按钮为禁用状态 |
||||
|
// isCodeBtnDisabled.value = true; |
||||
|
// codeBtnText.value = "重新发送"; |
||||
|
// let time = 6; |
||||
|
// const timer = setInterval(() => { |
||||
|
// time--; |
||||
|
// codeBtnText.value = "重新发送 " + time + "S"; |
||||
|
// if (time <= 0) { |
||||
|
// clearInterval(timer); |
||||
|
// codeBtnText.value = "重新发送"; |
||||
|
// // 倒计时结束后启用按钮 |
||||
|
// isCodeBtnDisabled.value = false; |
||||
|
// } |
||||
|
// }, 1000); |
||||
|
|
||||
|
// return; |
||||
|
// } |
||||
|
|
||||
|
function sendCode() { |
||||
|
if (switchType.value === "Phone") { |
||||
|
if (!phone.value) { |
||||
|
uni.showToast({ |
||||
|
title: "请输入手机号", |
||||
|
icon: "none", |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const bool = verificationPhone(country.value, phone.value); |
||||
|
console.log("验证是否成功", bool); |
||||
|
|
||||
|
// 检查格式是否正确 |
||||
|
if (!bool) { |
||||
|
uni.showToast({ |
||||
|
title: "手机号格式不正确", |
||||
|
icon: "none", |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (switchType.value === "Email") { |
||||
|
if (!email.value) { |
||||
|
uni.showToast({ |
||||
|
title: "请输入邮箱地址", |
||||
|
icon: "none", |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
const bool = verificationEmail(email.value); |
||||
|
console.log("验证是否成功", bool); |
||||
|
|
||||
|
// 检查格式是否正确 |
||||
|
if (!bool) { |
||||
|
uni.showToast({ |
||||
|
title: "邮箱格式不正确", |
||||
|
icon: "none", |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 如果按钮已禁用,则不执行后续逻辑 |
||||
|
if (isCodeBtnDisabled.value) return; |
||||
|
|
||||
|
// 设置按钮为禁用状态 |
||||
|
isCodeBtnDisabled.value = true; |
||||
|
codeBtnText.value = "重新发送"; |
||||
|
let time = 6; |
||||
|
const timer = setInterval(() => { |
||||
|
time--; |
||||
|
codeBtnText.value = "重新发送 " + time + "s"; |
||||
|
if (time <= 0) { |
||||
|
clearInterval(timer); |
||||
|
codeBtnText.value = "重新发送"; |
||||
|
// 倒计时结束后启用按钮 |
||||
|
isCodeBtnDisabled.value = false; |
||||
|
} |
||||
|
}, 1000); |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
function openAgreement() { |
||||
|
// 打开用户协议 |
||||
|
console.log("打开用户协议"); |
||||
|
uni.navigateTo({ |
||||
|
url: "/pages/start/agreement/agreement", |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function openPrivacy() { |
||||
|
// 打开隐私政策 |
||||
|
console.log("打开隐私政策"); |
||||
|
uni.navigateTo({ |
||||
|
url: "/pages/start/privacy/privacy", |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function changeCheckbox() { |
||||
|
agreed.value = !agreed.value; |
||||
|
checkboxUrl.value = agreed.value |
||||
|
? "../../../static/icons/Check-one-true.png" |
||||
|
: "../../../static/icons/Check-one-false.png"; |
||||
|
} |
||||
|
|
||||
|
// 验证手机号是否正确 |
||||
|
function validatePhoneNumber(countryCode, phoneNumber) { |
||||
|
// 检查是否为空 |
||||
|
if (!phoneNumber || phoneNumber.trim() === "") { |
||||
|
uni.showToast({ |
||||
|
title: "手机号不能为空", |
||||
|
icon: "none", |
||||
|
}); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
const bool = verificationPhone(countryCode, phoneNumber); |
||||
|
console.log("验证是否成功", bool); |
||||
|
|
||||
|
// 检查格式是否正确 |
||||
|
if (!bool) { |
||||
|
uni.showToast({ |
||||
|
title: "手机号格式不正确", |
||||
|
icon: "none", |
||||
|
}); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
// 去掉+号后检查长度(手机号通常在7到15位之间) |
||||
|
const cleanNumber = phoneNumber.replace(/^\+/, ""); |
||||
|
if (cleanNumber.length < 7 || cleanNumber.length > 15) { |
||||
|
uni.showToast({ |
||||
|
title: "手机号长度不正确", |
||||
|
icon: "none", |
||||
|
}); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
// 添加弹窗引用 |
||||
|
const agreementPopup = ref(null); |
||||
|
|
||||
|
// 处理同意按钮点击 |
||||
|
function handleAgree() { |
||||
|
// 关闭弹窗 |
||||
|
agreementPopup.value.close(); |
||||
|
// 设置为已同意 |
||||
|
agreed.value = true; |
||||
|
checkboxUrl.value = "../../../static/icons/Check-one-true.png"; |
||||
|
// 继续登录流程 |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.login-registration-container { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 0 70rpx; |
||||
|
height: 100vh; |
||||
|
background-color: #ffffff; |
||||
|
} |
||||
|
|
||||
|
/* 自定义导航栏样式 */ |
||||
|
.custom-navbar { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
/* z-index: 999; */ |
||||
|
width: 90%; |
||||
|
height: 80rpx; |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
padding: 10rpx 40rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.nav-left, |
||||
|
.nav-right { |
||||
|
flex: 1; |
||||
|
} |
||||
|
|
||||
|
.nav-right { |
||||
|
display: flex; |
||||
|
justify-content: flex-end; |
||||
|
} |
||||
|
.nav-left { |
||||
|
display: flex; |
||||
|
justify-content: flex-start; |
||||
|
} |
||||
|
|
||||
|
.icons { |
||||
|
margin: 20rpx; |
||||
|
width: 40rpx; |
||||
|
height: 40rpx; |
||||
|
/* margin-right: 10rpx; */ |
||||
|
} |
||||
|
|
||||
|
.back-btn, |
||||
|
.headphone-btn { |
||||
|
font-size: 36rpx; |
||||
|
font-weight: bold; |
||||
|
color: #333333; |
||||
|
padding: 10rpx; |
||||
|
} |
||||
|
|
||||
|
.logo { |
||||
|
width: 120rpx; |
||||
|
height: 120rpx; |
||||
|
margin-bottom: 60rpx; |
||||
|
border-radius: 20%; |
||||
|
} |
||||
|
|
||||
|
.welcome-text { |
||||
|
font-size: 48rpx; |
||||
|
font-weight: bold; |
||||
|
color: #333333; |
||||
|
margin-bottom: 60rpx; |
||||
|
/* text-align: left; */ |
||||
|
/* align-self: flex-start; */ |
||||
|
} |
||||
|
|
||||
|
.switch-container { |
||||
|
display: flex; |
||||
|
margin-bottom: 40rpx; |
||||
|
align-self: flex-start; |
||||
|
} |
||||
|
|
||||
|
.switch-item { |
||||
|
font-size: 28rpx; |
||||
|
color: #999999; |
||||
|
padding: 10rpx 20rpx; |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
.switch-item::after { |
||||
|
content: ""; |
||||
|
position: absolute; |
||||
|
bottom: 0; |
||||
|
left: 50%; |
||||
|
transform: translateX(-50%); |
||||
|
width: 60%; |
||||
|
/* 控制边框宽度 */ |
||||
|
height: 2rpx; |
||||
|
background-color: transparent; |
||||
|
} |
||||
|
|
||||
|
.switch-item.active { |
||||
|
color: #333333; |
||||
|
font-weight: 700; |
||||
|
} |
||||
|
|
||||
|
.switch-item.active::after { |
||||
|
content: ""; |
||||
|
position: absolute; |
||||
|
top: 60rpx; |
||||
|
bottom: 0; |
||||
|
left: 50%; |
||||
|
transform: translateX(-50%); |
||||
|
width: 30%; |
||||
|
/* 控制边框宽度 */ |
||||
|
height: 7rpx; |
||||
|
background-color: #333333; |
||||
|
} |
||||
|
|
||||
|
.input-container { |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
/* 添加图标输入框样式 */ |
||||
|
.input-with-icon { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
width: 100%; |
||||
|
height: 80rpx; |
||||
|
border-bottom: 2rpx solid #e5e5e5; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.input-icon { |
||||
|
width: 40rpx; |
||||
|
height: 40rpx; |
||||
|
margin: 0 20rpx; |
||||
|
} |
||||
|
.input-icon-eye { |
||||
|
width: 40rpx; |
||||
|
height: 20rpx; |
||||
|
margin: 0 20rpx; |
||||
|
} |
||||
|
|
||||
|
.input-field { |
||||
|
flex: 1; |
||||
|
height: 80rpx; |
||||
|
padding: 15rpx 0; |
||||
|
font-size: 28rpx; |
||||
|
color: #333333; |
||||
|
border: none; |
||||
|
background-color: transparent; |
||||
|
} |
||||
|
|
||||
|
.phone-input-container { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
width: 95.8%; |
||||
|
height: 80rpx; |
||||
|
/* border-radius: 20rpx; */ |
||||
|
/* border: 2rpx solid #e5e5e5; */ |
||||
|
/* background-color: #f5f5f5; */ |
||||
|
padding: 0 10rpx; |
||||
|
border-bottom: 2rpx solid #e5e5e5; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.country-code-selector { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
padding: 0 10rpx; |
||||
|
padding-bottom: 1rpx; |
||||
|
height: 100%; |
||||
|
/* border-right: 2rpx solid #e5e5e5; */ |
||||
|
/* background-color: #f5f5f5; */ |
||||
|
border-radius: 20rpx 0 0 20rpx; |
||||
|
} |
||||
|
|
||||
|
.country-code { |
||||
|
font-size: 28rpx; |
||||
|
color: #333333; |
||||
|
margin-right: 10rpx; |
||||
|
} |
||||
|
|
||||
|
.country-flag-img { |
||||
|
width: 40rpx; |
||||
|
height: 40rpx; |
||||
|
margin-right: 10rpx; |
||||
|
} |
||||
|
|
||||
|
.arrow-down { |
||||
|
font-size: 20rpx; |
||||
|
color: #999999; |
||||
|
} |
||||
|
|
||||
|
.phone-input { |
||||
|
flex: 1; |
||||
|
width: auto; |
||||
|
height: 100%; |
||||
|
border: none; |
||||
|
background-color: transparent; |
||||
|
padding: 0 0rpx; |
||||
|
} |
||||
|
|
||||
|
.send-code-btn { |
||||
|
width: 200rpx; |
||||
|
height: 60rpx; |
||||
|
display: inline-flex; |
||||
|
padding: 0rpx 10rpx; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
gap: 10px; |
||||
|
border-radius: 4px; |
||||
|
background: #000; |
||||
|
} |
||||
|
.send-code-btn-email { |
||||
|
width: 200rpx; |
||||
|
height: 60rpx; |
||||
|
display: inline-flex; |
||||
|
padding: 0rpx 10rpx; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
gap: 10px; |
||||
|
border-radius: 4px; |
||||
|
background: #000; |
||||
|
margin-right: 15rpx; |
||||
|
} |
||||
|
|
||||
|
.send-code-btn-disabled { |
||||
|
background: #e6e6e6; |
||||
|
/* 禁用状态下的灰色背景 */ |
||||
|
} |
||||
|
|
||||
|
.send-code-btn-disabled-text { |
||||
|
color: #999999 !important; |
||||
|
} |
||||
|
|
||||
|
.send-code-text { |
||||
|
color: #fff; |
||||
|
font-size: 28rpx; |
||||
|
} |
||||
|
|
||||
|
.agreement-container-one { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
align-self: flex-start; |
||||
|
margin-bottom: 80rpx; |
||||
|
} |
||||
|
|
||||
|
.agreement-container { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
margin-bottom: 40rpx; |
||||
|
margin-top: -75.5rpx; |
||||
|
align-self: flex-start; |
||||
|
} |
||||
|
|
||||
|
.checkbox { |
||||
|
width: 30rpx; |
||||
|
height: 30rpx; |
||||
|
margin-left: 20rpx; |
||||
|
/* flex: content; */ |
||||
|
} |
||||
|
|
||||
|
.agreement-text-one { |
||||
|
font-size: 22rpx; |
||||
|
color: #666666; |
||||
|
text-align: center; |
||||
|
margin-left: 10rpx; |
||||
|
} |
||||
|
|
||||
|
.agreement-text { |
||||
|
margin-left: 20rpx; |
||||
|
font-size: 24rpx; |
||||
|
color: #666666; |
||||
|
} |
||||
|
|
||||
|
.link { |
||||
|
color: #333333; |
||||
|
font-weight: bold; |
||||
|
text-decoration: underline; |
||||
|
} |
||||
|
|
||||
|
.register-btn { |
||||
|
width: 100%; |
||||
|
height: 80rpx; |
||||
|
background-color: #000000; |
||||
|
color: white; |
||||
|
font-size: 32rpx; |
||||
|
font-weight: bold; |
||||
|
border-radius: 40rpx; |
||||
|
margin-bottom: 40rpx; |
||||
|
} |
||||
|
|
||||
|
.or-text { |
||||
|
flex-direction: column; |
||||
|
font-size: 24rpx; |
||||
|
color: #999999; |
||||
|
margin-top: 294rpx; |
||||
|
margin-bottom: -22rpx; |
||||
|
} |
||||
|
|
||||
|
.to-icon { |
||||
|
width: 10rpx; |
||||
|
height: 16rpx; |
||||
|
} |
||||
|
|
||||
|
.or-text-one { |
||||
|
flex-direction: column; |
||||
|
font-size: 24rpx; |
||||
|
color: #999999; |
||||
|
} |
||||
|
|
||||
|
.third-party-login { |
||||
|
width: 100%; |
||||
|
margin-bottom: 60rpx; |
||||
|
} |
||||
|
|
||||
|
.third-party-text { |
||||
|
color: #ffffff; |
||||
|
font-weight: bold; |
||||
|
white-space: pre; |
||||
|
} |
||||
|
|
||||
|
.third-party-btn { |
||||
|
width: 100%; |
||||
|
height: 80rpx; |
||||
|
background-color: rgb(0, 0, 0); |
||||
|
border: 2rpx solid #e5e5e5; |
||||
|
border-radius: 40rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
margin-bottom: 20rpx; |
||||
|
font-size: 28rpx; |
||||
|
color: #333333; |
||||
|
} |
||||
|
|
||||
|
.google-icon, |
||||
|
.apple-icon { |
||||
|
width: 60rpx; |
||||
|
height: 60rpx; |
||||
|
margin-right: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.existing-account { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.account-text { |
||||
|
font-size: 24rpx; |
||||
|
color: #666666; |
||||
|
} |
||||
|
|
||||
|
.login-link { |
||||
|
font-size: 24rpx; |
||||
|
font-weight: bold; |
||||
|
color: #333333; |
||||
|
margin-left: 10rpx; |
||||
|
text-decoration: underline; |
||||
|
} |
||||
|
|
||||
|
.static-footer { |
||||
|
position: fixed; |
||||
|
bottom: 0; |
||||
|
} |
||||
|
|
||||
|
/* 弹窗样式 */ |
||||
|
.popup-content { |
||||
|
background-color: #ffffff; |
||||
|
padding: 40rpx; |
||||
|
text-align: center; |
||||
|
border-radius: 10rpx; |
||||
|
width: 550rpx; |
||||
|
} |
||||
|
|
||||
|
.popup-title { |
||||
|
font-size: 36rpx; |
||||
|
font-weight: bold; |
||||
|
color: #333; |
||||
|
margin-bottom: 80rpx; |
||||
|
text-align: center; /* 水平居中 */ |
||||
|
display: flex; /* 使用flex布局 */ |
||||
|
justify-content: center; /* 水平居中 */ |
||||
|
align-items: center; /* 垂直居中 */ |
||||
|
} |
||||
|
|
||||
|
.popup-message { |
||||
|
font-size: 28rpx; |
||||
|
color: #000000; |
||||
|
margin-bottom: 80rpx; |
||||
|
text-align: center; /* 水平居中 */ |
||||
|
display: flex; /* 使用flex布局 */ |
||||
|
justify-content: center; /* 水平居中 */ |
||||
|
align-items: center; /* 垂直居中 */ |
||||
|
} |
||||
|
|
||||
|
.agree-button { |
||||
|
width: 300rpx; |
||||
|
height: 80rpx; |
||||
|
background-color: #000000; |
||||
|
border-radius: 40rpx; |
||||
|
display: flex; /* 添加flex布局 */ |
||||
|
align-items: center; /* 垂直居中 */ |
||||
|
justify-content: center; /* 水平居中 */ |
||||
|
} |
||||
|
.agree-text { |
||||
|
color: #ffffff; |
||||
|
font-size: 34rpx; |
||||
|
/* 添加垂直居中相关样式 */ |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
line-height: 1; /* 确保文字垂直居中 */ |
||||
|
} |
||||
|
|
||||
|
.switch-container-occupy { |
||||
|
margin-top: 100rpx; |
||||
|
} |
||||
|
</style> |
||||
|
After Width: 11 | Height: 18 | Size: 442 B |
|
After Width: 18 | Height: 32 | Size: 576 B |
|
After Width: 40 | Height: 24 | Size: 1.5 KiB |
|
After Width: 326 | Height: 200 | Size: 20 KiB |
|
After Width: 40 | Height: 18 | Size: 1.0 KiB |
|
After Width: 420 | Height: 786 | Size: 156 KiB |
|
After Width: 326 | Height: 200 | Size: 20 KiB |
@ -1,88 +1,107 @@ |
|||||
{ |
{ |
||||
"id": "uni-popup", |
|
||||
"displayName": "uni-popup 弹出层", |
|
||||
"version": "1.9.1", |
|
||||
"description": " Popup 组件,提供常用的弹层", |
|
||||
"keywords": [ |
|
||||
"uni-ui", |
|
||||
"弹出层", |
|
||||
"弹窗", |
|
||||
"popup", |
|
||||
"弹框" |
|
||||
|
"id": "uni-popup", |
||||
|
"displayName": "uni-popup 弹出层", |
||||
|
"version": "1.9.11", |
||||
|
"description": " Popup 组件,提供常用的弹层", |
||||
|
"keywords": [ |
||||
|
"uni-ui", |
||||
|
"弹出层", |
||||
|
"弹窗", |
||||
|
"popup", |
||||
|
"弹框" |
||||
|
], |
||||
|
"repository": "https://github.com/dcloudio/uni-ui", |
||||
|
"engines": { |
||||
|
"HBuilderX": "", |
||||
|
"uni-app": "^4.07", |
||||
|
"uni-app-x": "" |
||||
|
}, |
||||
|
"directories": { |
||||
|
"example": "../../temps/example_temps" |
||||
|
}, |
||||
|
"dcloudext": { |
||||
|
"sale": { |
||||
|
"regular": { |
||||
|
"price": "0.00" |
||||
|
}, |
||||
|
"sourcecode": { |
||||
|
"price": "0.00" |
||||
|
} |
||||
|
}, |
||||
|
"contact": { |
||||
|
"qq": "" |
||||
|
}, |
||||
|
"declaration": { |
||||
|
"ads": "无", |
||||
|
"data": "无", |
||||
|
"permissions": "无" |
||||
|
}, |
||||
|
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", |
||||
|
"type": "component-vue", |
||||
|
"darkmode": "x", |
||||
|
"i18n": "x", |
||||
|
"widescreen": "x" |
||||
|
}, |
||||
|
"uni_modules": { |
||||
|
"dependencies": [ |
||||
|
"uni-scss", |
||||
|
"uni-transition" |
||||
], |
], |
||||
"repository": "https://github.com/dcloudio/uni-ui", |
|
||||
"engines": { |
|
||||
"HBuilderX": "" |
|
||||
}, |
|
||||
"directories": { |
|
||||
"example": "../../temps/example_temps" |
|
||||
}, |
|
||||
"dcloudext": { |
|
||||
"sale": { |
|
||||
"regular": { |
|
||||
"price": "0.00" |
|
||||
}, |
|
||||
"sourcecode": { |
|
||||
"price": "0.00" |
|
||||
} |
|
||||
}, |
|
||||
"contact": { |
|
||||
"qq": "" |
|
||||
}, |
|
||||
"declaration": { |
|
||||
"ads": "无", |
|
||||
"data": "无", |
|
||||
"permissions": "无" |
|
||||
}, |
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", |
|
||||
"type": "component-vue" |
|
||||
}, |
|
||||
"uni_modules": { |
|
||||
"dependencies": [ |
|
||||
"uni-scss", |
|
||||
"uni-transition" |
|
||||
], |
|
||||
"encrypt": [], |
|
||||
"platforms": { |
|
||||
"cloud": { |
|
||||
"tcb": "y", |
|
||||
"aliyun": "y", |
|
||||
"alipay": "n" |
|
||||
}, |
|
||||
"client": { |
|
||||
"App": { |
|
||||
"app-vue": "y", |
|
||||
"app-nvue": "y" |
|
||||
}, |
|
||||
"H5-mobile": { |
|
||||
"Safari": "y", |
|
||||
"Android Browser": "y", |
|
||||
"微信浏览器(Android)": "y", |
|
||||
"QQ浏览器(Android)": "y" |
|
||||
}, |
|
||||
"H5-pc": { |
|
||||
"Chrome": "y", |
|
||||
"IE": "y", |
|
||||
"Edge": "y", |
|
||||
"Firefox": "y", |
|
||||
"Safari": "y" |
|
||||
}, |
|
||||
"小程序": { |
|
||||
"微信": "y", |
|
||||
"阿里": "y", |
|
||||
"百度": "y", |
|
||||
"字节跳动": "y", |
|
||||
"QQ": "y" |
|
||||
}, |
|
||||
"快应用": { |
|
||||
"华为": "u", |
|
||||
"联盟": "u" |
|
||||
}, |
|
||||
"Vue": { |
|
||||
"vue2": "y", |
|
||||
"vue3": "y" |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
"encrypt": [], |
||||
|
"platforms": { |
||||
|
"cloud": { |
||||
|
"tcb": "x", |
||||
|
"aliyun": "x", |
||||
|
"alipay": "x" |
||||
|
}, |
||||
|
"client": { |
||||
|
"uni-app": { |
||||
|
"vue": { |
||||
|
"vue2": "√", |
||||
|
"vue3": "√" |
||||
|
}, |
||||
|
"web": { |
||||
|
"safari": "√", |
||||
|
"chrome": "√" |
||||
|
}, |
||||
|
"app": { |
||||
|
"vue": "√", |
||||
|
"nvue": "√", |
||||
|
"android": "√", |
||||
|
"ios": "√", |
||||
|
"harmony": "√" |
||||
|
}, |
||||
|
"mp": { |
||||
|
"weixin": "√", |
||||
|
"alipay": "√", |
||||
|
"toutiao": "√", |
||||
|
"baidu": "√", |
||||
|
"kuaishou": "-", |
||||
|
"jd": "-", |
||||
|
"harmony": "-", |
||||
|
"qq": "√", |
||||
|
"lark": "-" |
||||
|
}, |
||||
|
"quickapp": { |
||||
|
"huawei": "-", |
||||
|
"union": "-" |
||||
|
} |
||||
|
}, |
||||
|
"uni-app-x": { |
||||
|
"web": { |
||||
|
"safari": "√", |
||||
|
"chrome": "√" |
||||
|
}, |
||||
|
"app": { |
||||
|
"android": "√", |
||||
|
"ios": "√", |
||||
|
"harmony": "√" |
||||
|
}, |
||||
|
"mp": { |
||||
|
"weixin": "√" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,286 +1,292 @@ |
|||||
<template> |
<template> |
||||
<!-- #ifndef APP-NVUE --> |
|
||||
<view v-show="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"><slot></slot></view> |
|
||||
<!-- #endif --> |
|
||||
<!-- #ifdef APP-NVUE --> |
|
||||
<view v-if="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"><slot></slot></view> |
|
||||
<!-- #endif --> |
|
||||
|
<!-- #ifndef APP-NVUE --> |
||||
|
<view v-show="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"> |
||||
|
<slot></slot> |
||||
|
</view> |
||||
|
<!-- #endif --> |
||||
|
<!-- #ifdef APP-NVUE --> |
||||
|
<view v-if="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"> |
||||
|
<slot></slot> |
||||
|
</view> |
||||
|
<!-- #endif --> |
||||
</template> |
</template> |
||||
|
|
||||
<script> |
<script> |
||||
import { createAnimation } from './createAnimation' |
|
||||
|
import { createAnimation } from './createAnimation' |
||||
|
|
||||
/** |
|
||||
* Transition 过渡动画 |
|
||||
* @description 简单过渡动画组件 |
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=985 |
|
||||
* @property {Boolean} show = [false|true] 控制组件显示或隐藏 |
|
||||
* @property {Array|String} modeClass = [fade|slide-top|slide-right|slide-bottom|slide-left|zoom-in|zoom-out] 过渡动画类型 |
|
||||
* @value fade 渐隐渐出过渡 |
|
||||
* @value slide-top 由上至下过渡 |
|
||||
* @value slide-right 由右至左过渡 |
|
||||
* @value slide-bottom 由下至上过渡 |
|
||||
* @value slide-left 由左至右过渡 |
|
||||
* @value zoom-in 由小到大过渡 |
|
||||
* @value zoom-out 由大到小过渡 |
|
||||
* @property {Number} duration 过渡动画持续时间 |
|
||||
* @property {Object} styles 组件样式,同 css 样式,注意带’-‘连接符的属性需要使用小驼峰写法如:`backgroundColor:red` |
|
||||
*/ |
|
||||
export default { |
|
||||
name: 'uniTransition', |
|
||||
emits:['click','change'], |
|
||||
props: { |
|
||||
show: { |
|
||||
type: Boolean, |
|
||||
default: false |
|
||||
}, |
|
||||
modeClass: { |
|
||||
type: [Array, String], |
|
||||
default() { |
|
||||
return 'fade' |
|
||||
} |
|
||||
}, |
|
||||
duration: { |
|
||||
type: Number, |
|
||||
default: 300 |
|
||||
}, |
|
||||
styles: { |
|
||||
type: Object, |
|
||||
default() { |
|
||||
return {} |
|
||||
} |
|
||||
}, |
|
||||
customClass:{ |
|
||||
type: String, |
|
||||
default: '' |
|
||||
}, |
|
||||
onceRender:{ |
|
||||
type:Boolean, |
|
||||
default:false |
|
||||
}, |
|
||||
}, |
|
||||
data() { |
|
||||
return { |
|
||||
isShow: false, |
|
||||
transform: '', |
|
||||
opacity: 1, |
|
||||
animationData: {}, |
|
||||
durationTime: 300, |
|
||||
config: {} |
|
||||
} |
|
||||
}, |
|
||||
watch: { |
|
||||
show: { |
|
||||
handler(newVal) { |
|
||||
if (newVal) { |
|
||||
this.open() |
|
||||
} else { |
|
||||
// 避免上来就执行 close,导致动画错乱 |
|
||||
if (this.isShow) { |
|
||||
this.close() |
|
||||
} |
|
||||
|
/** |
||||
|
* Transition 过渡动画 |
||||
|
* @description 简单过渡动画组件 |
||||
|
* @tutorial https://ext.dcloud.net.cn/plugin?id=985 |
||||
|
* @property {Boolean} show = [false|true] 控制组件显示或隐藏 |
||||
|
* @property {Array|String} modeClass = [fade|slide-top|slide-right|slide-bottom|slide-left|zoom-in|zoom-out] 过渡动画类型 |
||||
|
* @value fade 渐隐渐出过渡 |
||||
|
* @value slide-top 由上至下过渡 |
||||
|
* @value slide-right 由右至左过渡 |
||||
|
* @value slide-bottom 由下至上过渡 |
||||
|
* @value slide-left 由左至右过渡 |
||||
|
* @value zoom-in 由小到大过渡 |
||||
|
* @value zoom-out 由大到小过渡 |
||||
|
* @property {Number} duration 过渡动画持续时间 |
||||
|
* @property {Object} styles 组件样式,同 css 样式,注意带’-‘连接符的属性需要使用小驼峰写法如:`backgroundColor:red` |
||||
|
*/ |
||||
|
export default { |
||||
|
name: 'uniTransition', |
||||
|
emits: ['click', 'change'], |
||||
|
props: { |
||||
|
show: { |
||||
|
type: Boolean, |
||||
|
default: false |
||||
|
}, |
||||
|
modeClass: { |
||||
|
type: [Array, String], |
||||
|
default () { |
||||
|
return 'fade' |
||||
} |
} |
||||
}, |
}, |
||||
immediate: true |
|
||||
} |
|
||||
}, |
|
||||
computed: { |
|
||||
// 生成样式数据 |
|
||||
stylesObject() { |
|
||||
let styles = { |
|
||||
...this.styles, |
|
||||
'transition-duration': this.duration / 1000 + 's' |
|
||||
} |
|
||||
let transform = '' |
|
||||
for (let i in styles) { |
|
||||
let line = this.toLine(i) |
|
||||
transform += line + ':' + styles[i] + ';' |
|
||||
} |
|
||||
return transform |
|
||||
|
duration: { |
||||
|
type: Number, |
||||
|
default: 300 |
||||
|
}, |
||||
|
styles: { |
||||
|
type: Object, |
||||
|
default () { |
||||
|
return {} |
||||
|
} |
||||
|
}, |
||||
|
customClass: { |
||||
|
type: String, |
||||
|
default: '' |
||||
|
}, |
||||
|
onceRender: { |
||||
|
type: Boolean, |
||||
|
default: false |
||||
|
}, |
||||
}, |
}, |
||||
// 初始化动画条件 |
|
||||
transformStyles() { |
|
||||
return 'transform:' + this.transform + ';' + 'opacity:' + this.opacity + ';' + this.stylesObject |
|
||||
} |
|
||||
}, |
|
||||
created() { |
|
||||
// 动画默认配置 |
|
||||
this.config = { |
|
||||
duration: this.duration, |
|
||||
timingFunction: 'ease', |
|
||||
transformOrigin: '50% 50%', |
|
||||
delay: 0 |
|
||||
} |
|
||||
this.durationTime = this.duration |
|
||||
}, |
|
||||
methods: { |
|
||||
/** |
|
||||
* ref 触发 初始化动画 |
|
||||
*/ |
|
||||
init(obj = {}) { |
|
||||
if (obj.duration) { |
|
||||
this.durationTime = obj.duration |
|
||||
|
data() { |
||||
|
return { |
||||
|
isShow: false, |
||||
|
transform: '', |
||||
|
opacity: 0, |
||||
|
animationData: {}, |
||||
|
durationTime: 300, |
||||
|
config: {} |
||||
} |
} |
||||
this.animation = createAnimation(Object.assign(this.config, obj),this) |
|
||||
}, |
}, |
||||
/** |
|
||||
* 点击组件触发回调 |
|
||||
*/ |
|
||||
onClick() { |
|
||||
this.$emit('click', { |
|
||||
detail: this.isShow |
|
||||
}) |
|
||||
}, |
|
||||
/** |
|
||||
* ref 触发 动画分组 |
|
||||
* @param {Object} obj |
|
||||
*/ |
|
||||
step(obj, config = {}) { |
|
||||
if (!this.animation) return |
|
||||
for (let i in obj) { |
|
||||
try { |
|
||||
if(typeof obj[i] === 'object'){ |
|
||||
this.animation[i](...obj[i]) |
|
||||
}else{ |
|
||||
this.animation[i](obj[i]) |
|
||||
|
watch: { |
||||
|
show: { |
||||
|
handler(newVal) { |
||||
|
if (newVal) { |
||||
|
this.open() |
||||
|
} else { |
||||
|
// 避免上来就执行 close,导致动画错乱 |
||||
|
if (this.isShow) { |
||||
|
this.close() |
||||
|
} |
||||
} |
} |
||||
} catch (e) { |
|
||||
console.error(`方法 ${i} 不存在`) |
|
||||
} |
|
||||
|
}, |
||||
|
immediate: true |
||||
} |
} |
||||
this.animation.step(config) |
|
||||
return this |
|
||||
}, |
}, |
||||
/** |
|
||||
* ref 触发 执行动画 |
|
||||
*/ |
|
||||
run(fn) { |
|
||||
if (!this.animation) return |
|
||||
this.animation.run(fn) |
|
||||
}, |
|
||||
// 开始过度动画 |
|
||||
open() { |
|
||||
clearTimeout(this.timer) |
|
||||
this.transform = '' |
|
||||
this.isShow = true |
|
||||
let { opacity, transform } = this.styleInit(false) |
|
||||
if (typeof opacity !== 'undefined') { |
|
||||
this.opacity = opacity |
|
||||
|
computed: { |
||||
|
// 生成样式数据 |
||||
|
stylesObject() { |
||||
|
let styles = { |
||||
|
...this.styles, |
||||
|
'transition-duration': this.duration / 1000 + 's' |
||||
|
} |
||||
|
let transform = '' |
||||
|
for (let i in styles) { |
||||
|
let line = this.toLine(i) |
||||
|
transform += line + ':' + styles[i] + ';' |
||||
|
} |
||||
|
return transform |
||||
|
}, |
||||
|
// 初始化动画条件 |
||||
|
transformStyles() { |
||||
|
return 'transform:' + this.transform + ';' + 'opacity:' + this.opacity + ';' + this.stylesObject |
||||
} |
} |
||||
this.transform = transform |
|
||||
// 确保动态样式已经生效后,执行动画,如果不加 nextTick ,会导致 wx 动画执行异常 |
|
||||
this.$nextTick(() => { |
|
||||
// TODO 定时器保证动画完全执行,目前有些问题,后面会取消定时器 |
|
||||
this.timer = setTimeout(() => { |
|
||||
this.animation = createAnimation(this.config, this) |
|
||||
this.tranfromInit(false).step() |
|
||||
this.animation.run() |
|
||||
this.$emit('change', { |
|
||||
detail: this.isShow |
|
||||
}) |
|
||||
}, 20) |
|
||||
}) |
|
||||
}, |
}, |
||||
// 关闭过度动画 |
|
||||
close(type) { |
|
||||
if (!this.animation) return |
|
||||
this.tranfromInit(true) |
|
||||
.step() |
|
||||
.run(() => { |
|
||||
this.isShow = false |
|
||||
this.animationData = null |
|
||||
this.animation = null |
|
||||
let { opacity, transform } = this.styleInit(false) |
|
||||
this.opacity = opacity || 1 |
|
||||
this.transform = transform |
|
||||
this.$emit('change', { |
|
||||
detail: this.isShow |
|
||||
}) |
|
||||
}) |
|
||||
}, |
|
||||
// 处理动画开始前的默认样式 |
|
||||
styleInit(type) { |
|
||||
let styles = { |
|
||||
transform: '' |
|
||||
|
created() { |
||||
|
// 动画默认配置 |
||||
|
this.config = { |
||||
|
duration: this.duration, |
||||
|
timingFunction: 'ease', |
||||
|
transformOrigin: '50% 50%', |
||||
|
delay: 0 |
||||
} |
} |
||||
let buildStyle = (type, mode) => { |
|
||||
if (mode === 'fade') { |
|
||||
styles.opacity = this.animationType(type)[mode] |
|
||||
} else { |
|
||||
styles.transform += this.animationType(type)[mode] + ' ' |
|
||||
|
this.durationTime = this.duration |
||||
|
}, |
||||
|
methods: { |
||||
|
/** |
||||
|
* ref 触发 初始化动画 |
||||
|
*/ |
||||
|
init(obj = {}) { |
||||
|
if (obj.duration) { |
||||
|
this.durationTime = obj.duration |
||||
} |
} |
||||
} |
|
||||
if (typeof this.modeClass === 'string') { |
|
||||
buildStyle(type, this.modeClass) |
|
||||
} else { |
|
||||
this.modeClass.forEach(mode => { |
|
||||
buildStyle(type, mode) |
|
||||
|
this.animation = createAnimation(Object.assign(this.config, obj), this) |
||||
|
}, |
||||
|
/** |
||||
|
* 点击组件触发回调 |
||||
|
*/ |
||||
|
onClick() { |
||||
|
this.$emit('click', { |
||||
|
detail: this.isShow |
||||
}) |
}) |
||||
} |
|
||||
return styles |
|
||||
}, |
|
||||
// 处理内置组合动画 |
|
||||
tranfromInit(type) { |
|
||||
let buildTranfrom = (type, mode) => { |
|
||||
let aniNum = null |
|
||||
if (mode === 'fade') { |
|
||||
aniNum = type ? 0 : 1 |
|
||||
} else { |
|
||||
aniNum = type ? '-100%' : '0' |
|
||||
if (mode === 'zoom-in') { |
|
||||
aniNum = type ? 0.8 : 1 |
|
||||
} |
|
||||
if (mode === 'zoom-out') { |
|
||||
aniNum = type ? 1.2 : 1 |
|
||||
|
}, |
||||
|
/** |
||||
|
* ref 触发 动画分组 |
||||
|
* @param {Object} obj |
||||
|
*/ |
||||
|
step(obj, config = {}) { |
||||
|
if (!this.animation) return this |
||||
|
Object.keys(obj).forEach(key => { |
||||
|
const value = obj[key] |
||||
|
if (typeof this.animation[key] === 'function') { |
||||
|
Array.isArray(value) ? |
||||
|
this.animation[key](...value) : |
||||
|
this.animation[key](value) |
||||
} |
} |
||||
if (mode === 'slide-right') { |
|
||||
aniNum = type ? '100%' : '0' |
|
||||
|
}) |
||||
|
this.animation.step(config) |
||||
|
return this |
||||
|
}, |
||||
|
/** |
||||
|
* ref 触发 执行动画 |
||||
|
*/ |
||||
|
run(fn) { |
||||
|
if (!this.animation) return |
||||
|
this.animation.run(fn) |
||||
|
}, |
||||
|
// 开始过度动画 |
||||
|
open() { |
||||
|
clearTimeout(this.timer) |
||||
|
this.isShow = true |
||||
|
// 新增初始状态重置逻辑(关键) |
||||
|
this.transform = this.styleInit(false).transform || '' |
||||
|
this.opacity = this.styleInit(false).opacity || 0 |
||||
|
|
||||
|
// 确保动态样式已经生效后,执行动画,如果不加 nextTick ,会导致 wx 动画执行异常 |
||||
|
this.$nextTick(() => { |
||||
|
// TODO 定时器保证动画完全执行,目前有些问题,后面会取消定时器 |
||||
|
this.timer = setTimeout(() => { |
||||
|
this.animation = createAnimation(this.config, this) |
||||
|
this.tranfromInit(false).step() |
||||
|
this.animation.run(() => { |
||||
|
// #ifdef APP-NVUE |
||||
|
this.transform = this.styleInit(false).transform || '' |
||||
|
this.opacity = this.styleInit(false).opacity || 1 |
||||
|
// #endif |
||||
|
// #ifndef APP-NVUE |
||||
|
this.transform = '' |
||||
|
this.opacity = this.styleInit(false).opacity || 1 |
||||
|
// #endif |
||||
|
this.$emit('change', { |
||||
|
detail: this.isShow |
||||
|
}) |
||||
|
}) |
||||
|
}, 80) |
||||
|
}) |
||||
|
}, |
||||
|
// 关闭过度动画 |
||||
|
close(type) { |
||||
|
if (!this.animation) return |
||||
|
this.tranfromInit(true) |
||||
|
.step() |
||||
|
.run(() => { |
||||
|
this.isShow = false |
||||
|
this.animationData = null |
||||
|
this.animation = null |
||||
|
let { opacity, transform } = this.styleInit(false) |
||||
|
this.opacity = opacity || 1 |
||||
|
this.transform = transform |
||||
|
this.$emit('change', { |
||||
|
detail: this.isShow |
||||
|
}) |
||||
|
}) |
||||
|
}, |
||||
|
// 处理动画开始前的默认样式 |
||||
|
styleInit(type) { |
||||
|
let styles = { transform: '', opacity: 1 } |
||||
|
const buildStyle = (type, mode) => { |
||||
|
const value = this.animationType(type)[mode] // 直接使用 type 控制状态 |
||||
|
if (mode.startsWith('fade')) { |
||||
|
styles.opacity = value |
||||
|
} else { |
||||
|
styles.transform += value + ' ' |
||||
} |
} |
||||
if (mode === 'slide-bottom') { |
|
||||
aniNum = type ? '100%' : '0' |
|
||||
|
} |
||||
|
|
||||
|
if (typeof this.modeClass === 'string') { |
||||
|
buildStyle(type, this.modeClass) |
||||
|
} else { |
||||
|
this.modeClass.forEach(mode => buildStyle(type, mode)) |
||||
|
} |
||||
|
return styles |
||||
|
}, |
||||
|
// 处理内置组合动画 |
||||
|
tranfromInit(type) { |
||||
|
let buildTranfrom = (type, mode) => { |
||||
|
let aniNum = null |
||||
|
if (mode === 'fade') { |
||||
|
aniNum = type ? 0 : 1 |
||||
|
} else { |
||||
|
aniNum = type ? '-100%' : '0' |
||||
|
if (mode === 'zoom-in') { |
||||
|
aniNum = type ? 0.8 : 1 |
||||
|
} |
||||
|
if (mode === 'zoom-out') { |
||||
|
aniNum = type ? 1.2 : 1 |
||||
|
} |
||||
|
if (mode === 'slide-right') { |
||||
|
aniNum = type ? '100%' : '0' |
||||
|
} |
||||
|
if (mode === 'slide-bottom') { |
||||
|
aniNum = type ? '100%' : '0' |
||||
|
} |
||||
} |
} |
||||
|
this.animation[this.animationMode()[mode]](aniNum) |
||||
|
} |
||||
|
if (typeof this.modeClass === 'string') { |
||||
|
buildTranfrom(type, this.modeClass) |
||||
|
} else { |
||||
|
this.modeClass.forEach(mode => { |
||||
|
buildTranfrom(type, mode) |
||||
|
}) |
||||
} |
} |
||||
this.animation[this.animationMode()[mode]](aniNum) |
|
||||
} |
|
||||
if (typeof this.modeClass === 'string') { |
|
||||
buildTranfrom(type, this.modeClass) |
|
||||
} else { |
|
||||
this.modeClass.forEach(mode => { |
|
||||
buildTranfrom(type, mode) |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
return this.animation |
|
||||
}, |
|
||||
animationType(type) { |
|
||||
return { |
|
||||
fade: type ? 0 : 1, |
|
||||
'slide-top': `translateY(${type ? '0' : '-100%'})`, |
|
||||
'slide-right': `translateX(${type ? '0' : '100%'})`, |
|
||||
'slide-bottom': `translateY(${type ? '0' : '100%'})`, |
|
||||
'slide-left': `translateX(${type ? '0' : '-100%'})`, |
|
||||
'zoom-in': `scaleX(${type ? 1 : 0.8}) scaleY(${type ? 1 : 0.8})`, |
|
||||
'zoom-out': `scaleX(${type ? 1 : 1.2}) scaleY(${type ? 1 : 1.2})` |
|
||||
} |
|
||||
}, |
|
||||
// 内置动画类型与实际动画对应字典 |
|
||||
animationMode() { |
|
||||
return { |
|
||||
fade: 'opacity', |
|
||||
'slide-top': 'translateY', |
|
||||
'slide-right': 'translateX', |
|
||||
'slide-bottom': 'translateY', |
|
||||
'slide-left': 'translateX', |
|
||||
'zoom-in': 'scale', |
|
||||
'zoom-out': 'scale' |
|
||||
|
return this.animation |
||||
|
}, |
||||
|
animationType(type) { |
||||
|
return { |
||||
|
fade: type ? 1 : 0, |
||||
|
'slide-top': `translateY(${type ? '0' : '-100%'})`, |
||||
|
'slide-right': `translateX(${type ? '0' : '100%'})`, |
||||
|
'slide-bottom': `translateY(${type ? '0' : '100%'})`, |
||||
|
'slide-left': `translateX(${type ? '0' : '-100%'})`, |
||||
|
'zoom-in': `scaleX(${type ? 1 : 0.8}) scaleY(${type ? 1 : 0.8})`, |
||||
|
'zoom-out': `scaleX(${type ? 1 : 1.2}) scaleY(${type ? 1 : 1.2})` |
||||
|
} |
||||
|
}, |
||||
|
// 内置动画类型与实际动画对应字典 |
||||
|
animationMode() { |
||||
|
return { |
||||
|
fade: 'opacity', |
||||
|
'slide-top': 'translateY', |
||||
|
'slide-right': 'translateX', |
||||
|
'slide-bottom': 'translateY', |
||||
|
'slide-left': 'translateX', |
||||
|
'zoom-in': 'scale', |
||||
|
'zoom-out': 'scale' |
||||
|
} |
||||
|
}, |
||||
|
// 驼峰转中横线 |
||||
|
toLine(name) { |
||||
|
return name.replace(/([A-Z])/g, '-$1').toLowerCase() |
||||
} |
} |
||||
}, |
|
||||
// 驼峰转中横线 |
|
||||
toLine(name) { |
|
||||
return name.replace(/([A-Z])/g, '-$1').toLowerCase() |
|
||||
} |
} |
||||
} |
} |
||||
} |
|
||||
</script> |
</script> |
||||
|
|
||||
<style></style> |
<style></style> |
||||