添加登录逻辑

This commit is contained in:
bobobobo
2025-12-24 02:01:34 +08:00
parent 8271e4e0bb
commit 6f418fae8a
35 changed files with 928 additions and 94 deletions

View File

@@ -0,0 +1,62 @@
<script setup>
const isShow = defineModel({
type: Boolean,
default: false
})
</script>
<template>
<view class="agreement-checkbox">
<view
v-show="!isShow"
class="checkbox-box"
@click="isShow = true"
></view>
<image
v-show="isShow"
src="/static/images/public/check-to-confirm.png"
mode="aspectFit"
class="left-icon"
@click="isShow = false"
></image>
<text class="text">我已阅读并同意</text>
<text class="on">用户权益</text>
<text class="text"></text>
<text class="on">隐私政策</text>
</view>
</template>
<style lang="scss" scoped>
.agreement-checkbox {
margin-top: 48px;
display: flex;
.checkbox-box,
.left-icon {
margin-right: 10rpx;
}
.checkbox-box {
width: 32rpx;
height: 32rpx;
border-radius: 48rpx;
border: 2rpx solid #999999;
}
.left-icon {
width: 36.25rpx;
height: 36.25rpx;
}
.text,
.on {
font-family: PingFang SC, PingFang SC;
font-weight: 500;
font-size: 24rpx;
color: #999999;
font-style: normal;
text-transform: none;
}
.on {
color: #00d9c5;
}
}
</style>

View File

@@ -0,0 +1,46 @@
<script setup>
const props = defineProps({
disabled: {
type: Boolean,
default: false
}
})
// 抛出点击事件
const emits = defineEmits(['click'])
</script>
<template>
<view class="cb-button">
<button
:disabled="props.disabled"
@click="emits('click')"
class="cb-button"
>
<slot></slot>
</button>
</view>
</template>
<style lang="scss" scoped>
.cb-button {
button {
height: 96rpx;
line-height: 96rpx;
border-radius: 96rpx;
background: linear-gradient(180deg, #00d993 0%, #00d9c5 100%);
font-family: PingFang SC, PingFang SC;
font-weight: 500;
font-size: 32rpx;
color: #ffffff;
font-style: normal;
text-transform: none;
&::after {
border: none;
}
&[disabled] {
background: #d9d9d9;
}
}
}
</style>

View File

@@ -0,0 +1,196 @@
<script setup>
import { ref, computed, watch } from 'vue'
import account from '@/static/images/login/account.png'
import password from '@/static/images/login/password.png'
import phone from '@/static/images/login/phone.png'
import invitation from '@/static/images/login/invitation.png'
import email from '@/static/images/login/email.png'
import codeIcon from '@/static/images/login/code.png'
import viewPassword from '@/static/images/login/view.png'
import concealPassword from '@/static/images/login/conceal.png'
const name = defineModel({
type: String,
default: ''
})
const isCode = defineModel('code', {
type: Boolean,
default: false
})
/** 倒计时 */
const countdown = ref(0)
/** 启动倒计时 */
const startCountdown = () => {
countdown.value = 60
const timer = setInterval(() => {
if (countdown.value > 0) {
countdown.value--
} else {
isCode.value = false
clearInterval(timer)
}
}, 1000)
}
watch(
() => isCode.value,
v => {
if (v) {
startCountdown()
} else {
countdown.value = 0
}
}
)
const props = defineProps({
/**
* 输入框状态类型
* text: 文本
* password: 密码
* number: 数字
* tel: 手机号
* email: 邮箱
*/
type: {
type: String,
default: 'text'
},
/**
* 输入框图标
* 1: 手机号/邮箱
* 2: 密码
* 3: 手机号
* 4: 邀请码
* 5: 邮箱
* 6: 验证码
*/
icon: {
type: String,
default: '1'
},
placeholder: {
type: String,
default: '请输入'
}
})
const emits = defineEmits(['onGetCode'])
/** 切换查看密码状态 */
const showPassword = ref(false)
const placeholderStyle = `font-family: PingFang SC, PingFang SC; font-weight: 500; color: #D9D9D9; font-size: 28rpx; font-style: normal; text-transform: none;`
const leftIcon = computed(() => {
switch (props.icon) {
case '1':
return account
case '2':
return password
case '3':
return phone
case '4':
return invitation
case '5':
return email
case '6':
return codeIcon
default:
return account
}
})
</script>
<template>
<view class="cb-input">
<image :src="leftIcon" mode="heightFix" class="left-icon"></image>
<input
v-if="props.type === 'password'"
v-model="name"
:password="!showPassword"
:placeholder-style="placeholderStyle"
:placeholder="props.placeholder"
class="input-box"
/>
<input
v-else
v-model="name"
:type="props.type"
:placeholder-style="placeholderStyle"
:placeholder="props.placeholder"
class="input-box"
/>
<text
v-if="props.icon === '6'"
:class="{ 'text-decoration': isCode }"
class="right-text"
@click="!isCode && emits('onGetCode')"
>
{{ isCode ? `${countdown}秒后重新获取` : '获取验证码' }}
</text>
<image
v-if="props.type === 'password'"
:src="showPassword ? viewPassword : concealPassword"
mode="heightFix"
class="right-icon"
@click="showPassword = !showPassword"
></image>
</view>
</template>
<style lang="scss" scoped>
.cb-input + .cb-input {
margin-top: 48rpx;
}
.cb-input {
width: calc(100% - 64rpx);
height: 96rpx;
background: #f9f9f9;
border-radius: 128rpx;
padding: 0 32rpx;
display: flex;
align-items: center;
.left-icon,
.right-icon {
flex-shrink: 0;
height: 48rpx;
}
.left-icon {
margin-right: 16rpx;
}
.input-box {
width: 100%;
font-family: PingFang SC, PingFang SC;
font-weight: 500;
font-size: 28rpx;
color: #333333;
font-style: normal;
text-transform: none;
}
.right-icon {
margin-left: 16rpx;
}
.right-text {
flex-shrink: 0;
margin-left: 16rpx;
width: 140rpx;
color: #00d9c5;
font-family: PingFang SC, PingFang SC;
font-weight: 500;
font-size: 28rpx;
font-style: normal;
text-transform: none;
}
.text-decoration {
width: 202rpx;
text-align: right;
color: #d9d9d9;
}
}
</style>

View File

@@ -0,0 +1,44 @@
<script setup>
import { navigateBack } from '@/utils/router'
const props = defineProps({
})
const onBack = () => {
navigateBack()
}
</script>
<template>
<view class="nav-bar">
<view class="status_bar">
<!-- 这里是状态栏 -->
</view>
<view class="nav-bar-box">
<view @click="onBack">
<!-- 返回图标插槽 -->
<slot name="back"></slot>
</view>
</view>
</view>
</template>
<style lang="scss" scoped>
.nav-bar {
position: fixed;
top: 0;
width: 100%;
}
.status_bar {
height: var(--status-bar-height);
width: 100%;
}
.nav-bar-box {
padding: 0 36rpx;
height: 58rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
</style>

View File

@@ -0,0 +1,119 @@
<script setup>
import { reactive, computed } from 'vue'
import { reLaunch } from '@/utils/router'
const props = defineProps({
/**
* 注册方式
* phone: 手机号
* email: 邮箱
*/
type: {
type: String,
default: 'phone'
}
})
const isPhone = computed(() => props.type === 'phone')
const formData = reactive({
// 手机号
name: '',
// 验证码
code: '',
// 密码
password: '',
// 确认密码
confirmPassword: '',
// 邀请码
inviteCode: '',
agreement: false
})
const isBtn = computed(() => {
return (
formData.name &&
formData.code &&
formData.password &&
formData.confirmPassword &&
formData.inviteCode &&
!formData.agreement
)
})
const onRegister = () => {
console.log('注册')
}
const onLogin = () => {
reLaunch('/pages/login/login')
}
const onTopRight = () => {
console.log('切换注册方式', isPhone.value)
const url = isPhone.value
? '/pages/login/email-register/email-register'
: '/pages/login/phone-register/phone-register'
reLaunch(url)
}
</script>
<template>
<view class="register-app">
<view class="top-register-nav">
<text class="title-left">{{ isPhone ? '手机' : '邮箱' }}注册</text>
<text class="title-right" @click="onTopRight">
{{ isPhone ? '邮箱' : '手机号' }}注册
</text>
</view>
<div class="input-wrapper">
<cb-input
v-model="formData.name"
:type="isPhone ? 'number' : 'email'"
icon="3"
:placeholder="`请输入${isPhone ? '手机号' : '邮箱'}`"
></cb-input>
<cb-input
v-model="formData.code"
type="number"
icon="6"
placeholder="请输入验证码"
></cb-input>
<cb-input
v-model="formData.password"
type="password"
icon="2"
placeholder="请输入密码"
></cb-input>
<cb-input
v-model="formData.confirmPassword"
type="password"
icon="2"
placeholder="请输入确认密码"
></cb-input>
<cb-input
v-model="formData.inviteCode"
type="number"
icon="4"
placeholder="请输入邀请码"
></cb-input>
<agreement-checkbox v-model="formData.agreement" />
<cb-button class="bottom-btn" :disabled="isBtn" @click="onRegister">
注册
</cb-button>
<view class="bottom-text">
<text class="text">已有账号</text>
<text class="text" @click="onLogin">去登录</text>
</view>
</div>
</view>
</template>
<style lang="scss" scoped>
@import '/styles/login.scss';
.register-app {
.bottom-btn {
margin: 100rpx 0 64rpx;
}
}
</style>