2026-09-04 08:56:15 +08:00

411 lines
9.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<!-- #ifdef APP -->
<scroll-view style="flex:1">
<!-- #endif -->
<view class="page">
<view class="login-tabs">
<!-- 左:密码登录 -->
<view
class="login-tab-item"
@click="switchTab(1)"
>
<text class="login-tab-text" :class="currentTab == 1 ? 'active-text' : '' ">Password login</text>
<view class="login-tab-underline" v-if="currentTab == 1"></view>
</view>
<!-- 右:短信登录 -->
<view
class="login-tab-item"
@click="switchTab(0)"
>
<text class="login-tab-text" :class="currentTab == 0 ? 'active-text' : '' ">SMS login</text>
<view class="login-tab-underline" v-if="currentTab == 0"></view>
</view>
</view>
<view class="page-box">
<!-- SMS login 表单 -->
<view v-if="currentTab == 0">
<view class="input-wrapper">
<uni-icons type="person" size="20" color="#3A6FA1" class="input-icon" />
<input
class="inputType"
type="text"
v-model="phoneNumber"
prefixIcon="person"
placeholder="mobile phone number"
/>
</view>
<view class="input-wrapper">
<uni-icons type="locked" size="20" color="#3A6FA1" class="input-icon" />
<input
class="inputType"
v-model="verificationCode"
prefixIcon="locked"
placeholder="verification code"
/>
<view class="code-btn" @click="onGetCode"> get code </view>
</view>
</view>
<!-- Password login 表单 -->
<view v-else>
<view class="input-wrapper">
<uni-icons type="person" size="20" color="#3A6FA1" class="input-icon" />
<input
class="inputType"
type="text"
v-model="account"
prefixIcon="person"
placeholder="account"
/>
</view>
<view class="input-wrapper">
<uni-icons type="locked" size="20" color="#3A6FA1" class="input-icon" />
<input
class="inputType"
type="text"
:password="!showPwd"
v-model="password"
placeholder="password"
/>
<!-- 右侧“眼睛”按钮 -->
<view class="toggle" @tap="showPwd = !showPwd">
<uni-icons :type="showPwd ? 'eye' : 'eye-filled'" size="20" color="#3A6FA1" />
</view>
</view>
<!-- 记住密码 -->
<view class="remember-row" @tap="rememberPwd = !rememberPwd">
<uni-icons :type="rememberPwd ? 'checkbox-filled' : 'circle'" size="18" :color="rememberPwd ? '#3A6FA1' : '#999999'" />
<text class="remember-text">remember password</text>
</view>
<!-- 历史账号 -->
<view class="history-row" v-if="historyList.length > 0" @tap="historyOpen = !historyOpen">
<uni-icons :type="historyOpen ? 'up' : 'down'" size="16" color="#3A6FA1" />
<text class="history-text">saved accounts ({{ historyList.length }})</text>
</view>
<view class="history-list" v-if="historyOpen && historyList.length > 0">
<view class="history-item" v-for="(item, idx) in historyList" :key="idx" @tap="onPickHistory(item)">
<uni-icons type="person" size="16" color="#3A6FA1" />
<text class="history-account">{{ item.account }}</text>
<view class="history-del" @tap.stop="onRemoveHistory(item)">
<uni-icons type="closeempty" size="16" color="#999999" />
</view>
</view>
</view>
</view>
</view>
<view class="buttonType">
<button class="buttonSize" @click="LoginRequest">login</button>
<text class="registerText" @click="onRegister">Don't have an account yet? Quick registration</text>
</view>
</view>
<!-- #ifdef APP -->
</scroll-view>
<!-- #endif -->
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { LoginReqApi } from '../../api/requestApi'
import { setToken, setUserName, setSavedAccount, getSavedAccount, getSavedPassword, clearSavedAccount, AccountRecord, getAccountHistory, addAccountHistory, removeAccountHistory } from '../../common/auth'
const account = ref("")
const password = ref("")
const currentTab = ref(1)
const phoneNumber = ref("")
const verificationCode = ref("")
const showPwd = ref(false)
const rememberPwd = ref(false)
const historyList = ref<AccountRecord[]>([])
const historyOpen = ref(false)
// 页面加载时回填已保存的账号密码
onLoad(() => {
const savedPassword = getSavedPassword()
if (savedPassword.length > 0) {
rememberPwd.value = true
account.value = getSavedAccount()
password.value = savedPassword
}
historyList.value = getAccountHistory()
})
// 选择历史账号回填
function onPickHistory(item: AccountRecord) {
account.value = item.account
password.value = item.password
rememberPwd.value = true
historyOpen.value = false
}
// 删除一条历史账号
function onRemoveHistory(item: AccountRecord) {
removeAccountHistory(item.account)
historyList.value = getAccountHistory()
}
function switchTab(index: number) {
if (currentTab.value !== index) {
currentTab.value = index
}
}
function onGetCode() {
// TODO: 这里写请求验证码的逻辑
uni.showToast({
title: "Send verification code",
icon: "none"
})
}
async function GetSmsLoginReq(){
uni.showToast({
title: "in development",
icon: "error"
})
}
async function onRegister() {
uni.redirectTo({
url: "/pages/register/register"
})
}
async function GetPasswordLoginReq(){
try {
const res = await LoginReqApi({
username: account.value,
password: password.value
})
// console.log("res = ", JSON.stringify((res)))
const statusCode = res["statusCode"] as number
if(statusCode == 200) {
const data = res["data"] as Record<string, any>
const token = data["token"] as string | null
const userName = data["username"] as string | null
if(token != null && token.length > 0){
setToken(token)
}
if(userName != null && userName.length > 0) {
setUserName(userName)
}
// 勾选了记住密码则保存账号密码,否则清除
if(rememberPwd.value) {
setSavedAccount(account.value, password.value)
} else {
clearSavedAccount()
}
// 保存到历史账号,方便下次直接选择
addAccountHistory(account.value, password.value)
uni.switchTab({
url: "/pages/tabbar/my"
})
} else {
const data = res["data"] as Record<string, any>
const msg = (data["result"] as string | null) ?? "login fail"
uni.showToast({
title: msg,
icon: 'error',
duration: 2000
})
}
} catch (e: any) {
console.log('login error = ', e)
uni.showToast({
title: "login error",
icon: "error",
duration: 2000,
})
} finally {
}
}
async function LoginRequest() {
if(currentTab.value == 0) {
GetSmsLoginReq()
}
else if(currentTab.value == 1) {
GetPasswordLoginReq()
}
}
</script>
<style>
.page{
flex: 1;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.buttonType {
width: 100%;
padding: 0 40rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 80%;
}
.buttonSize {
width: 100%;
border-radius: 10rpx;
margin: 40rpx auto;
background-color: #6d91b3;
}
.input-wrapper {
width: 600rpx;
margin: 20rpx auto;
border-bottom: 10rpx;
display: flex;
flex-direction: row;
align-items: center;
border-bottom: 2rpx solid #6d91b3;
box-sizing: border-box;
margin-top: 30rpx;
}
.inputType {
flex: 1;
border: none;
background-color: transparent;
font-size: 32rpx;
padding-left: 70rpx;
padding-right: 20rpx;
}
.input-icon {
position: absolute;
left: 10rpx;
width: 40rpx;
top: 50%;
transform: translateY(-50%);
}
.toggle {
position: absolute;
right: 20rpx;
top: 50%;
transform: translateY(-50%);
}
.page-box {
flex: 1;
margin-top: 15rpx;
}
.login-tabs {
margin-top: 40rpx;
padding: 0 60rpx;
display: flex;
flex-direction: row;
justify-content: center;
}
.login-tab-item {
margin-right: 100rpx;
display: flex;
flex-direction: column;
align-items: flex-start; /* 让文字和下划线左对齐 */
}
.login-tab-text {
font-size: 30rpx;
color: #6d91b3; /* 未选中浅色 */
}
/* 选中时文字变深、加粗 */
.active-text {
color: #3c2c14;
}
/* 下划线小块 */
.login-tab-underline {
margin-top: 6rpx;
width: 100rpx;
height: 4rpx;
background-color: #3A6FA1;
border-radius: 2rpx;
}
.code-btn {
margin-left: 16rpx;
padding: 8rpx 16rpx;
border-radius: 24rpx;
border-width: 1rpx;
border-style: solid;
}
.registerText {
margin-top: 30rpx;
font-weight: 700;
color: #3A6FA1;
text-align: center;
}
/* 记住密码 */
.remember-row {
width: 600rpx;
margin: 20rpx auto;
display: flex;
flex-direction: row;
align-items: center;
}
.remember-text {
font-size: 26rpx;
color: #666666;
margin-left: 10rpx;
}
/* 历史账号 */
.history-row {
width: 600rpx;
margin: 10rpx auto;
display: flex;
flex-direction: row;
align-items: center;
}
.history-text {
font-size: 26rpx;
color: #3A6FA1;
margin-left: 10rpx;
}
.history-list {
width: 600rpx;
margin: 0 auto;
border: 2rpx solid #e6e6e6;
border-radius: 16rpx;
background-color: #ffffff;
}
.history-item {
display: flex;
flex-direction: row;
align-items: center;
padding: 16rpx 20rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.history-account {
flex: 1;
min-width: 0;
font-size: 28rpx;
color: #333333;
margin-left: 12rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.history-del {
padding: 6rpx;
}
</style>