初始化项目模板

This commit is contained in:
yangyao committed 2026-01-04 17:53:40 +08:00
1 parent 80fd6a1d4b
commit b3a69a1f27
69 files changed
+11088

No files matched your search

+855
View File
@@ -0,0 +1,855 @@
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { areaCodeOptions, checkTelPhone, checkEmail, createV, showLoadingToast, closeToast, showToast, isEmpty } from '@/common/common.js';
import { getLoginCode } from '@/common/login.js';
import { login, register, sendCode, policyServer, loginLRInit } from '@/api/user.js';
import { useUserStore } from '@/store/user';
import { useI18n } from 'vue-i18n';
import CommonPicker from './common-picker.vue';
const { t, locale } = useI18n();
const userStore = useUserStore();
const isLogin = ref(true); // 是否登录,默认登录,为false表示注册
import { uniLogin } from '@/common/login';
const isMobile = ref(true); //是否手机号登录
const isPassword = ref(false); //是否密码登录
const isForgetPassword = ref(false); //是否忘记密码
// const isDisable = ref(true); //是否登录注册按钮是否禁用,即是否通过校验
const mobile = ref(''); // 手机号
const areaCode = ref(''); // 地区码
const code = ref(''); // 验证码
const password = ref(''); // 密码
const passwordConfirm = ref(''); // 确认密码
const email = ref(''); // 邮箱
const emit = defineEmits(['close']);
const wxJsCode = ref({}); //wx授权码code
const codeType = ref(''); // 区分code信息类型:=mpLogin(小程序中手机号+验证码登录);=mpMobile(小程序中授权获取手机号登录)
//同意条款相关
const checked = ref(false);
const onChange = (e) => {
checked.value = !checked.value;
};
//地区码弹框相关
const columns = ref([]);
const showCommonPicker = ref(false);
const onConfirmCommonPicker = (data) => {
areaCode.value = data[0].value;
showCommonPicker.value = false;
};
const onCloseCommonPicker = () => {
showCommonPicker.value = false;
};
//导航手机号点击功能
const onMobile = () => {
isMobile.value = true;
password.value = '';
passwordConfirm.value = '';
// isDisable.value = true;
// console.log(isMobile.value);
};
//导航邮箱点击功能
const onEmail = () => {
isMobile.value = false;
isPassword.value = true;
password.value = '';
passwordConfirm.value = '';
// isDisable.value = true;
};
//去登录
const toLogin = () => {
isLogin.value = true;
isMobile.value = true;
isPassword.value = true;
mobile.value = '';
areaCode.value = '852';
code.value = '';
password.value = '';
passwordConfirm.value = '';
email.value = '';
clearInterval(timerId2.value);
};
//去注册
const toRegister = () => {
isLogin.value = false;
isMobile.value = true;
isPassword.value = true;
mobile.value = '';
areaCode.value = '852';
code.value = '';
password.value = '';
passwordConfirm.value = '';
email.value = '';
clearInterval(timerId1.value);
};
/**
* @desc 登录
*/
const onLogin = () => {
if (!checked.value) {
uni.showToast({
title: t('policyTips'),
icon: 'none',
duration: 2000,
});
return;
}
//登录参数
let params = {};
//小程序免验证登录,codeType.value = mpMobile时,表示本次登录为免验证登录,noVerify值为1
if (codeType.value == 'mpMobile') {
params.codeValue = JSON.stringify(wxJsCode.value); //微信端需要传递登录授权code
params.noVerify = 1; // 免验证登录必传, codeType.value = mpMobile 时,为1表示本次登录为免验证登录
} else {
//手机号+验证码登录
params.codeType = userStore.platform == 'h5' ? '' : codeType.value; // 小程序有效,仅在短信验证码登录时有值 mpLogin
params.loginType = userStore.platform == 'h5' ? 'mobile' : ''; // h5环境下短信验证码登录必须要
params.areaCode = isMobile.value ? areaCode.value : ''; // 区号
params.mobile = isMobile.value ? mobile.value : ''; // 手机号
params.verifyCode = code.value; // 验证码
// #ifdef MP-WEIXIN || MP-ALIPAY
params.codeValue = JSON.stringify(wxJsCode.value); //微信端需要传递登录授权code
// #endif
//登录前的验证
if (!LoginCheck()) {
return;
}
}
console.log(params);
try {
uni.showLoading({
mask: true,
});
login(params).then((res) => {
console.log(res);
uni.hideLoading();
if (res.success) {
userStore.userToken = res.data.token;
userStore.isLogin = res.data.isLogin;
userStore.setProfile({
...res.data.userData,
});
emit('close');
mobile.value = '';
password.value = '';
code.value = '';
email.value = '';
} else {
//重新获取code
// #ifdef MP-WEIXIN || MP-ALIPAY
wxGetLoginCode();
// #endif
uni.showToast({
title: res.message,
icon: 'none',
});
}
});
} catch (error) {
//重新获取code
// #ifdef MP-WEIXIN || MP-ALIPAY
wxGetLoginCode();
// #endif
uni.showToast({
title: error.message,
icon: 'none',
});
} finally {
}
};
//登录校验
const LoginCheck = (val) => {
if (isMobile.value) {
//手机号登录
if (!checkTelPhone(areaCode.value, mobile.value)) {
uni.showToast({
title: t('personal.mobileTips'),
icon: 'none',
});
return false;
}
if (isPassword.value) {
//密码登录
if (password.value == '' || password.value == null) {
uni.showToast({
title: '请输入密码',
icon: 'none',
});
return false;
}
} else {
//验证码登录
if (code.value == '' || code.value == null) {
uni.showToast({
title: t('personal.validatePlaceholder'),
icon: 'none',
});
return false;
}
}
} else {
//邮箱登录
if (!checkEmail(email.value)) {
uni.showToast({
title: '请输入正确的电子邮箱',
icon: 'none',
});
return false;
}
if (password.value == '' || password.value == null) {
uni.showToast({
title: '请输入密码',
icon: 'none',
});
return false;
}
}
return true;
};
//注册
const onRegister = () => {
if (!checked.value) {
uni.showToast({
title: t('policyTips'),
icon: 'none',
duration: 2000,
});
return;
}
let params = {
regType: isMobile.value ? '1' : '2', //1手機號 2郵箱
areaCode: isMobile.value ? areaCode.value : '',
mobile: isMobile.value ? mobile.value : '',
password: isPassword.value ? password.value : '',
passwordConfirm: isPassword.value ? passwordConfirm.value : '',
code: isMobile.value ? code.value : '',
email: !isMobile.value ? email.value : '',
// #ifdef MP-WEIXIN || MP-ALIPAY
wxJsCode: wxJsCode.value, //微信端需要传递登录授权code
// #endif
};
console.log(params);
if (!RegisterCheck()) {
return;
}
try {
uni.showLoading({
mask: true,
});
//注册api
register(params).then((res) => {
uni.hideLoading();
if (res.success) {
userStore.userToken = res.data.token;
userStore.setProfile({
...res.data.user,
});
emit('close');
mobile.value = '';
password.value = '';
passwordConfirm.value = '';
code.value = '';
email.value = '';
} else {
//重新获取code
// #ifdef MP-WEIXIN || MP-ALIPAY
wxGetLoginCode();
// #endif
uni.showToast({
title: res.message,
icon: 'none',
});
}
});
} catch (error) {
//重新获取code
// #ifdef MP-WEIXIN || MP-ALIPAY
wxGetLoginCode();
// #endif
uni.showToast({
title: error.message,
icon: 'none',
});
} finally {
}
};
//注册校验
const RegisterCheck = (val) => {
if (isMobile.value) {
//手机号注册
if (!checkTelPhone(areaCode.value, mobile.value)) {
uni.showToast({
title: '请输入正确的地区号和手机号',
icon: 'none',
});
return false;
}
if (code.value == '' || code.value == null) {
uni.showToast({
title: '请输入验证码',
icon: 'none',
});
return false;
}
} else {
//邮箱注册
if (!checkEmail(email.value)) {
uni.showToast({
title: '请输入正确的电子邮箱',
icon: 'none',
});
return false;
}
}
if (password.value == '' || password.value == null) {
uni.showToast({
title: '请输入密码',
icon: 'none',
});
return false;
} else {
if (password.value?.length < 6 || password.value?.length > 20) {
uni.showToast({
title: '密碼長度應為6-20位',
icon: 'none',
});
return false;
}
}
if (passwordConfirm.value == '' || passwordConfirm.value == null) {
uni.showToast({
title: '請再次輸入密碼',
icon: 'none',
});
return false;
} else {
if (password.value != passwordConfirm.value) {
uni.showToast({
title: '兩次密碼不一樣',
icon: 'none',
});
return false;
}
}
return true;
};
const dataV = ref(''); //加密数据
const isCanWxLogin = ref(false); // 允许国内公司快速获取手机号登录
/**
* 初始化,获取data.v
*/
const initMobile = async () => {
try {
await loginLRInit().then((res) => {
if (res.success) {
areaCode.value = res.data.areaCodeList[0].value;
isCanWxLogin.value = res.data.isAllowMpMobile; // 允许国内公司快速获取手机号登录
res.data.areaCodeList.forEach((item, index) => {
columns.value[index] = {
value: item.value,
name: item.label,
};
});
dataV.value = res.data.v;
console.log(dataV.value);
} else {
uni.showToast({
title: res.message,
icon: 'none',
});
}
});
} catch (error) {
uni.showToast({
title: error.message,
icon: 'none',
});
}
};
const timerId1 = ref(null); // 定时器1(登录)
const timerId2 = ref(null); // 定时器2(注册)
const countDown1 = ref(60); // 倒计时1(登录)
const allowSendCode1 = ref(true); // 允许发送验证码1(登录)
const countDown2 = ref(60); // 倒计时2(注册)
const allowSendCode2 = ref(true); // 允许发送验证码2(注册)
//获取验证码
const getValifyCode = () => {
//手机号注册
if (!checkTelPhone(areaCode.value, mobile.value)) {
uni.showToast({
title: t('personal.mobileTips'),
icon: 'none',
});
return false;
}
let account = areaCode.value + ',' + mobile.value;
//加密data.v
let postV;
if (isLogin.value) {
postV = createV(dataV.value, account);
} else {
postV = createV(dataV.value, account);
}
let params = {
// account: isMobile.value ? account : '', //1手機號 2郵箱
// type: isMobile.value ? '1' : '2', //類型:1 手機號 2 郵箱
// purpose: !isLogin.value ? '1' : !isForgetPassword.value ? '2' : '3', //目的:1註冊 2登錄 3忘記密碼
areaCode: areaCode.value,
mobile: mobile.value,
v: postV, //加密数据
};
try {
// console.log(params);
uni.showLoading({
mask: true,
});
sendCode(params).then((res) => {
uni.hideLoading();
if (res.success) {
uni.showToast({
title: t('personal.resendSuccess'),
icon: 'none',
});
// #ifdef MP
codeType.value = 'mpLogin';
// #endif
if (isLogin.value) {
allowSendCode1.value = false;
timerId1.value = setInterval(() => {
countDown1.value--;
if (countDown1.value == 0) {
countDown1.value = 60;
allowSendCode1.value = true;
clearInterval(timerId1.value);
// console.log(timerId1.value);
}
}, 1000);
} else {
allowSendCode2.value = false;
timerId2.value = setInterval(() => {
countDown2.value--;
if (countDown2.value == 0) {
countDown2.value = 60;
allowSendCode2.value = true;
clearInterval(timerId2.value);
}
}, 1000);
}
} else {
uni.showToast({
title: res.message,
icon: 'none',
});
}
});
} catch (error) {
uni.showToast({
title: error.message,
icon: 'none',
});
}
};
//忘记密码
const toForgetPassword = () => {
uni.navigateTo({
url: '/pages/user/forgetPassword',
});
};
// 协议弹框相关
const noticeData = ref({}); //协议数据
const showAgreementPicker = ref(false);
const onShowAgreementPicker = () => {
showAgreementPicker.value = true;
};
const onCancelAgreementPicker = () => {
showAgreementPicker.value = false;
};
const getPolicyServer = async () => {
try {
await policyServer({
type: '',
}).then((res) => {
console.log(res);
if (res.success) {
noticeData.value = res.data?.content;
} else {
uni.showToast({
title: res.message,
icon: 'none',
});
}
});
} catch (error) {
uni.showToast({
title: error.message,
icon: 'none',
});
}
};
/**
* @desc 获取微信登录授权码
*/
const wxGetLoginCode = async () => {
await getLoginCode().then((res) => {
wxJsCode.value = res;
console.log(wxJsCode.value, '新获取的code');
});
};
/**
* @desc 免验证登录
*/
const quickLogin = () => {
getLoginCode().then((res) => {
// #ifdef MP
codeType.value = 'mpMobile';
// #endif
wxJsCode.value = res;
onLogin();
});
};
// 获取用户手机号码
const onGetphonenumber = (ev) => {
uni.$u.debounce(async () => {
if (ev.detail.errMsg.includes('fail')) return;
if (!checked.value) {
uni.showToast({
title: t('policyTips'),
icon: 'none',
duration: 2000,
});
return;
}
try {
uni.showLoading({
title: t('loading'),
mask: true,
});
const flag = await uniLogin(ev);
if (!flag) {
uni.showToast({
title: '登录失败',
icon: 'none',
});
} else {
emit('close');
console.log({
flag,
});
}
} catch (err) {
console.log({
err,
});
} finally {
uni.hideLoading();
}
}, 1000);
};
const handleChecked = () => {
if (!checked.value) {
uni.showToast({
title: t('policyTips'),
icon: 'none',
duration: 2000,
});
return;
}
};
onMounted(async () => {
await initMobile();
// await getPolicyServer();
// #ifdef MP-WEIXIN || MP-ALIPAY
await wxGetLoginCode();
// #endif
});
onUnmounted(() => {
clearInterval(timerId1.value);
clearInterval(timerId2.value);
});
</script>
<template>
<!-- <view class="login-type flex text-[34rpx] text-theme">
<view class="mobile-type flex-1 font-medium border-bottom" @tap="onMobile" :class="{ border-bottom-theme: isMobile }">{{ isLogin ? '手機號碼登入' : '手機號碼註冊' }}</view>
<view class="email-type flex-1 font-medium border-bottom" @tap="onEmail" :class="{ border-bottom-theme: !isMobile }">{{ isLogin ? '電郵登入' : '電郵註冊' }}</view>
</view> -->
<!-- 微信快捷登录 -->
<view v-if="isLogin && isCanWxLogin">
<view class="login-box">
<button
v-if="!checked"
class="up-reset-button bg-theme rounded-full mx-[30rpx] my-[40rpx] flex h-[92rpx] items-center justify-center text-[34rpx] font-medium text-white"
@click="handleChecked"
>
{{ t('phoneQuickLogin') }}
</button>
<button
v-else
class="up-reset-button bg-theme rounded-full mx-[30rpx] my-[40rpx] flex h-[92rpx] items-center justify-center text-[34rpx] font-medium text-white"
open-type="getPhoneNumber"
@getphonenumber="onGetphonenumber"
>
{{ t('phoneQuickLogin') }}
</button>
</view>
</view>
<!-- 登录 -->
<view v-else-if="isLogin">
<view class="mobile" v-if="isMobile">
<view class="label text-[26rpx]">{{ t('personal.mobile') }}</view>
<view class="mobile-text flex items-center border-round-theme">
<view class="areaCode flex items-center">
<view class="text-[30rpx]" @tap="showCommonPicker = true">+{{ areaCode }}</view>
<view class="down-icon">
<up-icon name="arrow-down-fill" size="8" color></up-icon>
</view>
</view>
<!-- #ifndef MP-ALIPAY -->
<up-input :placeholder="t('personal.mobilePlaceholder')" v-model="mobile" type="number" border="none"></up-input>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<input class="flex-1" :placeholder="t('personal.mobilePlaceholder')" v-model="mobile" type="number" border="none" />
<!-- #endif -->
</view>
</view>
<view class="email" v-else>
<view class="label text-[26rpx]">{{ t('personal.email') }}</view>
<view class="email-text flex items-center border-round-theme">
<!-- #ifndef MP-ALIPAY -->
<up-input :placeholder="t('personal.emailPlaceholder')" v-model="email" border="none"></up-input>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<input class="flex-1" :placeholder="t('personal.emailPlaceholder')" v-model="email" border="none" />
<!-- #endif -->
</view>
</view>
<view class="password" v-if="isPassword">
<view class="label text-[26rpx]">{{ t('personal.password') }}</view>
<view class="password-text flex items-center border-round-theme">
<!-- #ifndef MP-ALIPAY -->
<up-input :placeholder="t('personal.passwordPlaceholder')" v-model="password" type="password" border="none"></up-input>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<input class="flex-1" :placeholder="t('personal.passwordPlaceholder')" v-model="password" type="password" border="none" />
<!-- #endif -->
</view>
</view>
<view class="code" v-else>
<view class="label text-[26rpx]">{{ t('personal.validateCode') }}</view>
<view class="code-text flex items-center border-round-theme">
<!-- #ifndef MP-ALIPAY -->
<up-input :placeholder="t('personal.validatePlaceholder')" v-model="code" type="number" border="none"></up-input>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<input class="flex-1" :placeholder="t('personal.validatePlaceholder')" v-model="code" type="number" border="none" />
<!-- #endif -->
<view v-if="allowSendCode1" class="whitespace-nowrap text-[28rpx] font-medium text-theme" @tap="getValifyCode">{{
t('personal.getValidateCode')
}}</view>
<view v-else class="whitespace-nowrap text-[28rpx] font-medium text-[rgb(131,131,131)]"> {{ countDown1 }}s{{ t('personal.resend') }} </view>
</view>
</view>
<!-- 免验证登录 -->
<!-- #ifdef MP -->
<view class="flex flex-row items-center justify-center flex-1" @click="quickLogin">
<text class="text-[15px]">{{ t('quickLogin') }}</text>
</view>
<!-- #endif -->
<!-- <view class="text-right text-[26rpx] mx-[30rpx] text-gray-600" @tap="toForgetPassword">忘記密碼?</view> -->
<view class="bg-theme rounded-full mx-[30rpx] my-[40rpx] flex h-[92rpx] items-center justify-center text-[34rpx] font-medium text-white" @tap="onLogin">{{
t('login')
}}</view>
<!-- <view class="flex mx-[30rpx] items-center justify-between text-[28rpx]">
<view>沒有賬號?點挈 <text class="text-theme" @tap="toRegister">註冊</text></view>
<view v-if="isMobile" class="text-theme" @tap="isPassword = !isPassword">{{ !isPassword ? '密碼登入' : '验证码登入' }}</view>
</view> -->
</view>
<!-- 注册 -->
<view v-else>
<view class="mobile" v-if="isMobile">
<view class="label text-[26rpx]">{{ t('personal.mobile') }}</view>
<view class="mobile-text flex items-center border-round-theme">
<view class="areaCode flex items-center">
<view class="text-[30rpx]" @tap="showCommonPicker = true">+{{ areaCode }}</view>
<view class="down-icon">
<up-icon name="arrow-down-fill" size="8" color></up-icon>
</view>
</view>
<!-- #ifndef MP-ALIPAY -->
<up-input :placeholder="t('personal.mobilePlaceholder')" v-model="mobile" type="number" border="none"></up-input>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<input class="flex-1" :placeholder="t('personal.mobilePlaceholder')" v-model="mobile" type="number" border="none" />
<!-- #endif -->
</view>
</view>
<view class="email" v-else>
<view class="label text-[26rpx]">{{ t('personal.email') }}</view>
<view class="email-text flex items-center border-round-theme">
<!-- #ifndef MP-ALIPAY -->
<up-input :placeholder="t('personal.emailPlaceholder')" v-model="email" border="none"></up-input>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<input class="flex-1" :placeholder="t('personal.emailPlaceholder')" v-model="email" border="none" />
<!-- #endif -->
</view>
</view>
<view class="code" v-if="isMobile">
<view class="label text-[26rpx]">{{ t('personal.validateCode') }}</view>
<view class="code-text flex items-center border-round-theme">
<!-- #ifndef MP-ALIPAY -->
<up-input placeholder="t('personal.validatePlaceholder')" v-model="code" type="number" border="none"></up-input>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<input class="flex-1" placeholder="t('personal.validatePlaceholder')" v-model="code" type="number" border="none" />
<!-- #endif -->
<view v-if="allowSendCode2" class="whitespace-nowrap text-[28rpx] font-medium text-theme" @tap="getValifyCode">{{
t('personal.getValidateCode')
}}</view>
<view v-else class="whitespace-nowrap text-[28rpx] font-medium text-[rgb(131,131,131)]">
{{ `${countDown2}s ${t('personal.resend')}` }}
</view>
</view>
</view>
<view class="password">
<view class="label text-[26rpx]">{{ t('personal.password') }}</view>
<view class="password-text flex items-center border-round-theme">
<!-- #ifndef MP-ALIPAY -->
<up-input :placeholder="t('personal.setPassword')" v-model="password" type="password" border="none"></up-input>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<input class="flex-1" :placeholder="t('personal.setPassword')" v-model="password" type="password" border="none" />
<!-- #endif -->
</view>
</view>
<view class="password">
<view class="label text-[26rpx]">{{ t('personal.confirmPassword') }}</view>
<view class="password-text flex items-center border-round-theme">
<!-- #ifndef MP-ALIPAY -->
<up-input :placeholder="t('personal.passwordAgain')" v-model="passwordConfirm" type="password" border="none"></up-input>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<input class="flex-1" :placeholder="t('personal.passwordAgain')" v-model="passwordConfirm" type="password" border="none" />
<!-- #endif -->
</view>
</view>
<!-- :class="{ disable: isDisable }" -->
<view
class="bg-theme rounded-full mx-[30rpx] mt-[15rpx] my-[40rpx] flex h-[92rpx] items-center justify-center text-[34rpx] font-medium text-white"
@tap="onRegister"
>{{ t('regLogin') }}</view
>
<view class="flex mx-[30rpx] mb-[40rpx] items-center justify-between text-[28rpx]">
<view
>{{ t('hasAccount') }} <text class="text-theme" @tap="toLogin">{{ t('login') }}</text></view
>
</view>
</view>
<view class="text-center my-[40rpx] text-[28rpx]">
<checkbox-group @change="onChange">
<checkbox class="checkbox" :checked="checked" color="#00A88E"> </checkbox>
{{ t('policyNote') }} <text class="text-theme font-medium" @tap="onShowAgreementPicker">{{ t('policy') }}</text>
</checkbox-group>
</view>
<!-- 地区号 -->
<CommonPicker
:title="t('personal.selectAreaCode')"
:show="showCommonPicker"
:columns="columns"
@confirm="onConfirmCommonPicker"
@close="onCloseCommonPicker"
></CommonPicker>
<!-- 用户协议弹框 -->
<up-popup :show="showAgreementPicker" :closeable="true" round="10" mode="bottom" @close="onCancelAgreementPicker">
<view class="h-[70vh] flex flex-col px-[30rpx] pb-[30rpx] text-[28rpx]">
<view class="py-[30rpx] text-center text-[36rpx]">
{{ t('policy') }}
</view>
<view class="flex-1 overflow-y-scroll">
<up-parse :content="noticeData"></up-parse>
</view>
</view>
</up-popup>
</template>
<style lang="scss" scoped>
.border-bottom-theme .login-type {
height: 88rpx;
margin: 30rpx;
.mobile-type,
.email-type {
text-align: center;
line-height: 88rpx;
}
}
.mobile,
.email,
.password,
.code {
margin: 30rpx;
.label {
font-size: 26rpx;
padding-left: 40rpx;
padding-bottom: 20rpx;
}
.mobile-text,
.email-text,
.password-text,
.code-text {
// margin: 0 15px;
height: 100rpx;
padding: 0 40rpx;
border-radius: 100rpx;
.van-field {
background-color: #ffffff !important;
padding: 0 10rpx !important;
}
.areaCode {
margin-right: 17rpx;
.down-icon {
width: 40rpx;
padding-left: 10rpx;
}
border-right: 2rpx solid #eee;
}
}
}
.disable {
opacity: 0.3;
}
</style>