feat(登录)

This commit is contained in:
unknown 2024-05-23 01:38:19 +08:00
parent c6ba320a80
commit 4b1de8bdac
5 changed files with 701 additions and 443 deletions

View File

@ -1,83 +1,119 @@
<template> <template>
<pb-layout :leftIcon="''" navbar="inner" :bgStyle="{ backgroundColor: '#fff' }"> <pb-layout :leftIcon="''" navbar="inner" :bgStyle="{ backgroundColor: '#fff' }">
<view class="login-module"> <view class="login-module ss-p-x-30">
<view> <view class="login-title ss-font-56 ss-m-b-72">
{{ title }} {{ title }}
</view> </view>
<uni-forms ref="smsLoginRef" v-model="state.model" :rules="state.rules" validateTrigger="bind"> <uni-forms ref="smsLoginRef" v-model="state.model" :rules="state.rules" validateTrigger="bind">
<uni-forms-item name="mobile"> <uni-forms-item name="mobile">
<uni-easyinput <uni-easyinput :styles="state.inputStyle" trim="all" placeholder="请输入手机号" v-model="state.model.mobile"
placeholder="请输入手机号" :inputBorder="false" type="number" />
v-model="state.model.mobile"
:inputBorder="false"
type="number"
/>
</uni-forms-item> </uni-forms-item>
<uni-forms-item name="code"> <uni-forms-item name="code">
<uni-easyinput <uni-easyinput :styles="state.inputStyle" trim="all" placeholder="验证码" v-model="state.model.code"
placeholder="验证码" :inputBorder="false" type="number" maxlength="4">
v-model="state.model.code"
:inputBorder="false"
type="number"
maxlength="4"
>
<template #right> <template #right>
<button class="ss-reset-button code-btn code-btn-start" @tap="smsLoginSubmit"> <button :disabled="state.isMobileEnd" :class="{ 'code-btn-end': state.isMobileEnd }"
获取验证码 class="ss-reset-button code-btn code-btn-start" @tap="getSmsCode('smsLogin', state.model.mobile)">
{{ getSmsTimer('smsLogin') }}
</button> </button>
</template> </template>
</uni-easyinput> </uni-easyinput>
</uni-forms-item> </uni-forms-item>
</uni-forms> </uni-forms>
<button class="ss-reset-button login-btn-start" @tap="smsLoginSubmit">登录</button> <button class="ss-reset-button login-btn-start" @tap="smsLoginSubmit">登录</button>
<view class="agreement-box ss-flex ss-row-center" :class="{ shake: state.isShaking }">
<label class="radio ss-flex" @tap="onChange">
<radio :checked="state.agreeStatus" color="var(--ui-BG-Main)" style="transform: scale(0.8)"
@tap.stop="onChange" />
<view class="agreement-text ss-flex ss-m-l-8">
我已阅读并遵守
<view class="tcp-text" @tap.stop="onProtocol('商家入驻协议')"> 商家入驻协议 </view>
</view>
</label>
</view>
</view> </view>
</pb-layout> </pb-layout>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue' import { ref } from 'vue'
import { code, mobile } from '@/peach/validate/form';
import { showAuthModal, closeAuthModal, getSmsCode, getSmsTimer } from '@/peach/hooks/useModal';
const title = ref('欢迎登录') const title = ref('欢迎登录')
const smsLoginRef = ref(null)
const state = ref({ const state = ref({
isMobileEnd: false,
agreeStatus: false,
isShaking: false,
model: { model: {
mobile: '', mobile: '',
code: '', code: '',
}, },
rules: { rules: {
mobile: { mobile,
rules: [ code
{
required: true,
errorMessage: '手机号不能为空',
},
{
pattern: /^1[3-9]\d{9}$/,
errorMessage: '手机号格式错误',
},
],
},
code: {
rules: [
{
required: true,
errorMessage: '验证码不能为空',
},
{
pattern: /^\d{4}$/,
errorMessage: '验证码格式错误',
},
],
},
}, },
inputStyle: {
backgroundColor: '#ECECEC'
}
}) })
function onChange() {
state.value.agreeStatus = !state.value.agreeStatus
}
async function smsLoginSubmit() {
const validate = await smsLoginRef.value.validate().catch(err => {
console.log('err', err)
})
if (!validate) return
if (!state.agreeStatus) {
state.value.isShaking = true
setTimeout(() => {
state.value.isShaking = false
}, 1000)
peach.$helper.toast('请勾选同意')
return
}
const { code } = await AuthUtil.smsLogin(state.value.model)
if (code === 0) {
}
}
</script> </script>
<style lang="scss">
.login-module {
.uni-easyinput {
::v-deep .uni-easyinput__content {
border-radius: 41rpx;
padding: 0 10rpx;
}
}
}
</style>
<style lang="scss" scoped> <style lang="scss" scoped>
.login-module { .login-module {
margin-top: 50%;
overflow: hidden;
.login-title {
color: '#1818d';
font-weight: 600;
}
.login-btn-start { .login-btn-start {
width: 158rpx; height: 82rpx;
height: 56rpx;
line-height: normal; line-height: normal;
background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient)); background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
border-radius: 28rpx; border-radius: 28rpx;
@ -85,16 +121,53 @@ const state = ref({
font-weight: 500; font-weight: 500;
color: #fff; color: #fff;
} }
.code-btn-start { .code-btn-start {
width: 160rpx; width: 160rpx;
height: 56rpx; height: 56rpx;
line-height: normal; line-height: normal;
border: 2rpx solid var(--ui-BG-Main);
border-radius: 28rpx; border-radius: 28rpx;
font-size: 26rpx; font-size: 26rpx;
font-weight: 400; font-weight: 400;
color: var(--ui-BG-Main); color: var(--ui-BG-Main);
opacity: 1; opacity: 1;
} }
.agreement-box {
margin: -10px auto 10px;
position: absolute;
bottom: 30rpx;
left: 50%;
transform: translateX(-50%);
width: 100%;
.protocol-check {
transform: scale(0.7);
}
.agreement-text {
font-size: 26rpx;
font-weight: 500;
color: #999999;
.tcp-text {
color: var(--ui-BG-Main);
}
}
}
.shake {
animation: shake 0.05s linear 4 alternate;
}
@keyframes shake {
from {
transform: translateX(-10rpx);
}
to {
transform: translateX(10rpx);
}
}
} }
</style> </style>

View File

@ -173,7 +173,9 @@
border-bottom-right-radius: $i + rpx; border-bottom-right-radius: $i + rpx;
} }
@each $short, $long in tl 'top-left', tr 'top-right', bl 'bottom-right', br 'bottom-right' { @each $short, $long in tl "top-left", tr "top-right", bl "bottom-right",
br "bottom-right"
{
// 定义外边距 // 定义外边距
.ss-r-#{$short}-#{$i} { .ss-r-#{$short}-#{$i} {
border-#{$long}-radius: $i + rpx; border-#{$long}-radius: $i + rpx;
@ -247,7 +249,7 @@
==================== */ ==================== */
@for $i from 20 through 50 { @for $i from 20 through 60 {
.ss-font-#{$i} { .ss-font-#{$i} {
font-size: $i + rpx; font-size: $i + rpx;
} }

View File

@ -1,12 +1,12 @@
import { ref } from 'vue' import { ref } from "vue";
import { defineStore } from 'pinia' import { defineStore } from "pinia";
import $platform from '@/peach/platform' import $platform from "@/peach/platform";
import $router from '@/peach/router' import $router from "@/peach/router";
import user from './user' import user from "./user";
import sys from './sys' import useSysStore from "./sys";
const useAppStore = defineStore( const useAppStore = defineStore(
'app', "app",
() => { () => {
/** /**
* @description 应用信息 * @description 应用信息
@ -19,14 +19,14 @@ const useAppStore = defineStore(
* @param string filesystem 文件系统 * @param string filesystem 文件系统
*/ */
const info = ref({ const info = ref({
name: '', name: "",
logo: '', logo: "",
version: '', version: "",
copyright: '', copyright: "",
copytime: '', copytime: "",
cdnurl: '', cdnurl: "",
filesystem: '', filesystem: "",
}) });
/** /**
* @description 平台信息 * @description 平台信息
@ -41,12 +41,12 @@ const useAppStore = defineStore(
methods: [], methods: [],
forwardInfo: {}, forwardInfo: {},
posterInfo: {}, posterInfo: {},
linkAddress: '', linkAddress: "",
}, },
bindMobile: 0, bindMobile: 0,
}) });
const chat = ref({}) const chat = ref({});
/** /**
* @description 模板信息 * @description 模板信息
@ -58,46 +58,50 @@ const useAppStore = defineStore(
tabbar: { tabbar: {
items: [ items: [
{ {
activeIconUrl: 'http://mall.yudao.iocoder.cn/static/images/1-002.png', activeIconUrl:
iconUrl: 'http://mall.yudao.iocoder.cn/static/images/1-001.png', "http://mall.yudao.iocoder.cn/static/images/1-002.png",
text: '首页', iconUrl: "http://mall.yudao.iocoder.cn/static/images/1-001.png",
url: '/pages/index/index', text: "首页",
url: "/pages/index/index",
}, },
{ {
activeIconUrl: 'http://mall.yudao.iocoder.cn/static/images/2-002.png', activeIconUrl:
iconUrl: 'http://mall.yudao.iocoder.cn/static/images/2-001.png', "http://mall.yudao.iocoder.cn/static/images/2-002.png",
text: '分类', iconUrl: "http://mall.yudao.iocoder.cn/static/images/2-001.png",
url: '/pages/index/category?id=3', text: "分类",
url: "/pages/index/category?id=3",
}, },
{ {
activeIconUrl: 'http://mall.yudao.iocoder.cn/static/images/3-002.png', activeIconUrl:
iconUrl: 'http://mall.yudao.iocoder.cn/static/images/3-001.png', "http://mall.yudao.iocoder.cn/static/images/3-002.png",
text: '购物车', iconUrl: "http://mall.yudao.iocoder.cn/static/images/3-001.png",
url: '/pages/index/icons', text: "购物车",
url: "/pages/index/icons",
}, },
{ {
activeIconUrl: 'http://mall.yudao.iocoder.cn/static/images/4-002.png', activeIconUrl:
iconUrl: 'http://mall.yudao.iocoder.cn/static/images/4-001.png', "http://mall.yudao.iocoder.cn/static/images/4-002.png",
text: '我的', iconUrl: "http://mall.yudao.iocoder.cn/static/images/4-001.png",
url: '/pages/index/user', text: "我的",
url: "/pages/index/user",
}, },
], ],
style: { style: {
activeColor: '#fc4141', activeColor: "#fc4141",
bgColor: '#fff', bgColor: "#fff",
bgType: 'color', bgType: "color",
color: '#282828', color: "#282828",
}, },
theme: 'red', theme: "red",
}, },
}, },
}) });
// 全局分享信息 // 全局分享信息
const shareInfo = ref({}) const shareInfo = ref({});
// 小程序发货信息管理 0: 没有 1 // 小程序发货信息管理 0: 没有 1
const hasWechatTradeManaged = ref(0) const hasWechatTradeManaged = ref(0);
/** /**
* @author Ankkaya * @author Ankkaya
@ -107,52 +111,52 @@ const useAppStore = defineStore(
*/ */
async function init() { async function init() {
// 检查网络 // 检查网络
const networkStatus = await $platform.checkNetwork() const networkStatus = await $platform.checkNetwork();
if (!networkStatus) { if (!networkStatus) {
$router.error('NetworkError') $router.error("NetworkError");
} }
if (true) { if (true) {
this.info = { this.info = {
name: '🍑商城', name: "🍑商城",
logo: 'https://static.iocoder.cn/ruoyi-vue-pro-logo.png', logo: "https://static.iocoder.cn/ruoyi-vue-pro-logo.png",
version: '1.0.0', version: "1.0.0",
copyright: '全部开源,个人与企业可 100% 免费使用', copyright: "全部开源,个人与企业可 100% 免费使用",
copytime: 'Copyright© 2018-2024', copytime: "Copyright© 2018-2024",
cdnurl: 'https://file.sheepjs.com', // 云存储域名 cdnurl: "https://file.sheepjs.com", // 云存储域名
filesystem: 'qcloud', // 云存储平台 filesystem: "qcloud", // 云存储平台
} };
this.platform = { this.platform = {
share: { share: {
methods: ['poster', 'link'], methods: ["poster", "link"],
linkAddress: 'https://shopro.sheepjs.com/#/', linkAddress: "https://shopro.sheepjs.com/#/",
posterInfo: { posterInfo: {
user_bg: '/static/img/shop/config/user-poster-bg.png', user_bg: "/static/img/shop/config/user-poster-bg.png",
goods_bg: '/static/img/shop/config/goods-poster-bg.png', goods_bg: "/static/img/shop/config/goods-poster-bg.png",
groupon_bg: '/static/img/shop/config/groupon-poster-bg.png', groupon_bg: "/static/img/shop/config/groupon-poster-bg.png",
}, },
}, },
bind_mobile: 0, bind_mobile: 0,
} };
this.chat = { this.chat = {
chat_domain: 'https://api.shopro.sheepjs.com/chat', chat_domain: "https://api.shopro.sheepjs.com/chat",
room_id: 'admin', room_id: "admin",
} };
this.has_wechat_trade_managed = 0 this.has_wechat_trade_managed = 0;
// 加载主题 // 加载主题
const sysStore = sys() const sysStore = useSysStore();
sysStore.setTheme() sysStore.setTheme();
// 模拟用户登录 // 模拟用户登录
const userStore = user() const userStore = user();
if (userStore.isLogin) { if (userStore.isLogin) {
userStore.loginAfter() userStore.loginAfter();
} }
return Promise.resolve(true) return Promise.resolve(true);
} else { } else {
$router.error('InitError', res.msg || '加载失败') $router.error("InitError", res.msg || "加载失败");
} }
} }
@ -164,18 +168,18 @@ const useAppStore = defineStore(
shareInfo, shareInfo,
hasWechatTradeManaged, hasWechatTradeManaged,
init, init,
} };
}, },
{ {
persist: { persist: {
enabled: true, enabled: true,
strategies: [ strategies: [
{ {
key: 'app-store', key: "app-store",
}, },
], ],
}, },
} }
) );
export default useAppStore export default useAppStore;

View File

@ -1,20 +1,17 @@
import { ref } from 'vue' import { ref } from "vue";
import { defineStore } from 'pinia' import { defineStore } from "pinia";
const useSysStore = defineStore( const useSysStore = defineStore(
'sys', "sys",
() => { () => {
const theme = ref('') const theme = ref("");
const mode = ref('light') const mode = ref("light");
const modeAuto = ref(false) const modeAuto = ref(false);
const fontSize = ref(1) const fontSize = ref(1);
function setTheme(stheme = '') { function setTheme(stheme = "") {
if (theme === '') { console.log("setTheme", stheme);
theme.value = 'orange' theme.value = stheme ? stheme : "orange";
} else {
theme.value = stheme
}
} }
return { return {
@ -23,14 +20,14 @@ const useSysStore = defineStore(
modeAuto, modeAuto,
fontSize, fontSize,
setTheme, setTheme,
} };
}, },
{ {
persist: { persist: {
enabled: true, enabled: true,
strategies: [{ key: 'sys-store' }], strategies: [{ key: "sys-store" }],
}, },
} }
) );
export default useSysStore export default useSysStore;

182
peach/validate/form.js Normal file
View File

@ -0,0 +1,182 @@
/**
* Validate v1.0.0 通用验证
* @description 项目中用到的表单验证规则
*/
import test from "@/peach/helper/test.js";
// 身份证号
export const idCard = {
rules: [
{
required: true,
errorMessage: "请输入身份证号",
},
{
validateFunction: function (rule, value, data, callback) {
if (!test.idCard(value)) {
callback("身份证号格式不正确");
}
return true;
},
},
],
};
// 手机号
export const mobile = {
rules: [
{
required: true,
errorMessage: "请输入手机号",
},
{
validateFunction: function (rule, value, data, callback) {
if (!test.mobile(value)) {
callback("手机号码格式不正确");
}
return true;
},
},
],
};
// 密码
export const password = {
rules: [
{
required: true,
errorMessage: "请输入密码",
},
{
validateFunction: function (rule, value, data, callback) {
if (!/^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]+\S{5,12}$/.test(value)) {
callback("需包含字母和数字,长度在6-12之间");
}
return true;
},
},
],
};
// 短信验证码
export const code = {
rules: [
{
required: true,
errorMessage: "请输入验证码",
},
],
};
// 真实姓名
export const realName = {
rules: [
{
required: true,
errorMessage: "请输入姓名",
},
{
validateFunction: function (rule, value, data, callback) {
if (!test.chinese(value)) {
callback("请输入汉字");
}
return true;
},
},
],
};
export const taxName = {
rules: [
{
required: true,
errorMessage: "请输入发票抬头名称",
},
{
validateFunction: function (rule, value, data, callback) {
if (!test.chinese(value)) {
callback("请输入汉字");
}
return true;
},
},
],
};
// 税号
export const taxNo = {
rules: [
{
required: true,
errorMessage: "请输入税号",
},
],
};
// 开户行
export const bankName = {
rules: [
{
required: true,
errorMessage: "请输入银行名称",
},
{
validateFunction: function (rule, value, data, callback) {
if (!test.chinese(value)) {
callback("请输入汉字");
}
return true;
},
},
],
};
// 银行卡号
export const bankCode = {
rules: [
{
required: true,
errorMessage: "请输入银行卡号",
},
{
validateFunction: function (rule, value, data, callback) {
if (!test.bankCode(value)) {
callback("请输入正确银行卡号");
}
return true;
},
},
],
};
// 支付宝账号
export const alipayAccount = {
rules: [
{
required: true,
errorMessage: "请输入支付宝账号",
},
{
validateFunction: function (rule, value, data, callback) {
let isEmail = test.email(value);
let isMobile = test.mobile(value);
if (!isEmail && !isMobile) {
callback("请输入正确账号");
}
return true;
},
},
],
};
export default {
mobile,
alipayAccount,
bankCode,
bankName,
realName,
password,
code,
taxNo,
taxName,
};