455 lines
10 KiB
Vue
455 lines
10 KiB
Vue
<template>
|
|
<view class="login-page">
|
|
<view class="bg-top"></view>
|
|
<view class="content">
|
|
<view class="brand">
|
|
<view class="brand-outer">
|
|
<view class="brand-inner">U</view>
|
|
</view>
|
|
<text class="brand-title">{{ text.brandTitle }}</text>
|
|
<text class="brand-subtitle">{{ text.brandSubtitle }}</text>
|
|
</view>
|
|
|
|
<view class="btn-group" v-if="!showAccount">
|
|
<!-- #ifdef MP-WEIXIN -->
|
|
<button
|
|
class="btn btn-primary"
|
|
:disabled="!agree"
|
|
:class="{ 'btn-disabled': !agree }"
|
|
open-type="getPhoneNumber"
|
|
@getphonenumber="onGetPhoneNumber"
|
|
@click="handlePhoneLoginClick"
|
|
>
|
|
微信手机号快速登录
|
|
</button>
|
|
<button class="btn btn-secondary" @click="toggleAccount(true)">
|
|
密码登录
|
|
</button>
|
|
<!-- #endif -->
|
|
|
|
<!-- #ifndef MP-WEIXIN -->
|
|
<button class="btn btn-primary" @click="toggleAccount(true)">
|
|
密码登录
|
|
</button>
|
|
<!-- #endif -->
|
|
</view>
|
|
|
|
<view v-if="showAccount" class="account-panel">
|
|
<view class="field">
|
|
<text class="field-label">手机号</text>
|
|
<input
|
|
v-model.trim="form.phone"
|
|
class="field-input"
|
|
type="number"
|
|
placeholder="请输入手机号"
|
|
maxlength="11"
|
|
/>
|
|
</view>
|
|
<view class="field">
|
|
<text class="field-label">密码</text>
|
|
<input
|
|
v-model="form.password"
|
|
class="field-input"
|
|
type="text"
|
|
password
|
|
placeholder="请输入密码"
|
|
/>
|
|
</view>
|
|
<button class="btn btn-primary" @click="handlePasswordLogin">
|
|
登录
|
|
</button>
|
|
<button class="btn btn-secondary" @click="toggleAccount(false)">
|
|
取消
|
|
</button>
|
|
</view>
|
|
|
|
<view class="agreement">
|
|
<checkbox-group style="width: 10%" @change="onAgreeChange">
|
|
<checkbox class="checkbox" value="agree" :checked="agree" />
|
|
</checkbox-group>
|
|
<view class="agreement-text">
|
|
<text>我已阅读并同意</text>
|
|
<text class="link" @click="goService">《用户服务协议》</text>
|
|
<text>、</text>
|
|
<text class="link" @click="goPrivacy">《隐私保护指引》</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import { post } from "@/common/request";
|
|
import { STORAGE_KEYS, ENV } from "@/common/env";
|
|
import { API } from "@/common/api";
|
|
const app = getApp();
|
|
export default {
|
|
data() {
|
|
return {
|
|
text: {
|
|
srandTitle: "XXX小程序",
|
|
brandSubtitle: "连接精彩生活,发现无限可能",
|
|
},
|
|
agree: false,
|
|
showAccount: false,
|
|
form: {
|
|
phone: "",
|
|
password: "",
|
|
},
|
|
};
|
|
},
|
|
onShow() {
|
|
this.text.brandTitle = app.globalData.ENV.APP_NAME;
|
|
this.text.brandSubtitle = app.globalData.ENV.APP_SUBTITLE;
|
|
},
|
|
methods: {
|
|
onAgreeChange(e) {
|
|
const values = (e.detail && e.detail.value) || [];
|
|
this.agree = values.includes("agree");
|
|
},
|
|
ensureAgree() {
|
|
if (this.agree) return true;
|
|
uni.showModal({
|
|
title: "提示",
|
|
content: "请先阅读并同意隐私保护指引与用户服务协议",
|
|
confirmText: "去阅读",
|
|
cancelText: "暂不",
|
|
success: (res) => {
|
|
if (res.confirm) {
|
|
this.goPrivacy();
|
|
}
|
|
},
|
|
});
|
|
return false;
|
|
},
|
|
handlePhoneLoginClick() {
|
|
if (!this.agree) {
|
|
this.ensureAgree();
|
|
}
|
|
},
|
|
toggleAccount(nextState) {
|
|
this.showAccount =
|
|
typeof nextState === "boolean" ? nextState : !this.showAccount;
|
|
if (!this.showAccount) {
|
|
this.form.phone = "";
|
|
this.form.password = "";
|
|
}
|
|
},
|
|
async onGetPhoneNumber(e) {
|
|
if (!this.ensureAgree()) return;
|
|
|
|
const detail = e && e.detail ? e.detail : {};
|
|
if (detail.errMsg && detail.errMsg.indexOf("ok") === -1) {
|
|
uni.showToast({
|
|
title: "已取消授权",
|
|
icon: "none",
|
|
});
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const loginRes = await uni.login({
|
|
provider: "weixin",
|
|
});
|
|
if (!loginRes || !loginRes.code) {
|
|
uni.showToast({
|
|
title: "获取登录凭证失败",
|
|
icon: "none",
|
|
});
|
|
return;
|
|
}
|
|
|
|
const payload = {
|
|
code: loginRes.code,
|
|
phoneCode: detail.code || "",
|
|
encryptedData: detail.encryptedData || "",
|
|
iv: detail.iv || "",
|
|
};
|
|
|
|
const res = await post(API.WX_MINI_LOGIN, payload, {
|
|
allowGuest: true,
|
|
});
|
|
const loginData = this.normalizeLoginData(res);
|
|
if (!loginData.token) {
|
|
uni.showToast({
|
|
title: loginData.message || "登录失败",
|
|
icon: "none",
|
|
});
|
|
return;
|
|
}
|
|
|
|
uni.setStorageSync(STORAGE_KEYS.TOKEN, loginData.token);
|
|
uni.setStorageSync(STORAGE_KEYS.USER_INFO, loginData.userInfo || {});
|
|
uni.showToast({
|
|
title: "登录成功",
|
|
icon: "success",
|
|
});
|
|
uni.switchTab({
|
|
url: "/pages/index/index",
|
|
});
|
|
} catch (err) {
|
|
uni.showToast({
|
|
title: err.message || "登录失败",
|
|
icon: "none",
|
|
});
|
|
}
|
|
},
|
|
async handlePasswordLogin() {
|
|
if (!this.ensureAgree()) return;
|
|
|
|
const phone = (this.form.phone || "").trim();
|
|
const password = this.form.password || "";
|
|
if (!phone || phone.length !== 11) {
|
|
uni.showToast({
|
|
title: "请输入正确手机号",
|
|
icon: "none",
|
|
});
|
|
return;
|
|
}
|
|
if (!password) {
|
|
uni.showToast({
|
|
title: "请输入密码",
|
|
icon: "none",
|
|
});
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const res = await post(
|
|
API.USER_PASSWORD_LOGIN,
|
|
{
|
|
phone,
|
|
password,
|
|
},
|
|
{
|
|
allowGuest: true,
|
|
},
|
|
);
|
|
const data = res && res.data ? res.data : res;
|
|
const token = data && data.token ? data.token : "";
|
|
const userInfo = data && data.user ? data.user : {};
|
|
if (!token) {
|
|
uni.showToast({
|
|
title: res.message || res.msg || "登录失败",
|
|
icon: "none",
|
|
});
|
|
return;
|
|
}
|
|
|
|
uni.setStorageSync(STORAGE_KEYS.TOKEN, token);
|
|
uni.setStorageSync(STORAGE_KEYS.USER_INFO, userInfo);
|
|
uni.showToast({
|
|
title: "登录成功",
|
|
icon: "success",
|
|
});
|
|
uni.switchTab({
|
|
url: "/pages/index/index",
|
|
});
|
|
} catch (err) {
|
|
uni.showToast({
|
|
title: err.message || "登录失败",
|
|
icon: "none",
|
|
});
|
|
}
|
|
},
|
|
normalizeLoginData(res) {
|
|
if (!res)
|
|
return {
|
|
token: "",
|
|
userInfo: {},
|
|
message: "",
|
|
};
|
|
if (typeof res.code !== "undefined" && ![0, 200].includes(res.code)) {
|
|
return {
|
|
token: "",
|
|
userInfo: {},
|
|
message: res.message || res.msg || "",
|
|
};
|
|
}
|
|
const data = res.data || res.result || res;
|
|
return {
|
|
token: data.token || data.access_token || data.accessToken || "",
|
|
userInfo: data.userInfo ||
|
|
data.user || {
|
|
id: data.userId,
|
|
platformUserId: data.platformUserId,
|
|
openid: data.openid,
|
|
unionid: data.unionid,
|
|
phone: data.phone,
|
|
},
|
|
message: res.message || res.msg || "",
|
|
};
|
|
},
|
|
goService() {
|
|
uni.navigateTo({
|
|
url: "/pages/protocol/service",
|
|
});
|
|
},
|
|
goPrivacy() {
|
|
uni.navigateTo({
|
|
url: "/pages/protocol/privacy",
|
|
});
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.login-page {
|
|
min-height: 100vh;
|
|
background: #f8f6f6;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.bg-top {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 360rpx;
|
|
background: linear-gradient(
|
|
180deg,
|
|
rgba(236, 91, 19, 0.08),
|
|
rgba(248, 246, 246, 0)
|
|
);
|
|
}
|
|
|
|
.content {
|
|
position: relative;
|
|
z-index: 1;
|
|
padding: 120rpx 48rpx 80rpx;
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 85vh;
|
|
}
|
|
|
|
.brand {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 16rpx;
|
|
}
|
|
|
|
.brand-outer {
|
|
width: 160rpx;
|
|
height: 160rpx;
|
|
border-radius: 80rpx;
|
|
background: rgba(236, 91, 19, 0.12);
|
|
border: 2rpx solid rgba(236, 91, 19, 0.25);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.brand-inner {
|
|
width: 112rpx;
|
|
height: 112rpx;
|
|
border-radius: 28rpx;
|
|
background: #ec5b13;
|
|
color: #fff;
|
|
font-size: 52rpx;
|
|
font-weight: 700;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
box-shadow: 0 12rpx 24rpx rgba(236, 91, 19, 0.35);
|
|
}
|
|
|
|
.brand-title {
|
|
font-size: 44rpx;
|
|
font-weight: 700;
|
|
color: #1f2d3d;
|
|
}
|
|
|
|
.brand-subtitle {
|
|
font-size: 26rpx;
|
|
color: #7a8699;
|
|
}
|
|
|
|
.btn-group {
|
|
margin-top: 64rpx;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 24rpx;
|
|
}
|
|
|
|
.btn {
|
|
width: 100%;
|
|
height: 96rpx;
|
|
border-radius: 24rpx;
|
|
font-size: 32rpx;
|
|
font-weight: 600;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.btn-primary {
|
|
background: #07c160;
|
|
color: #fff;
|
|
}
|
|
|
|
.btn-secondary {
|
|
background: #f0f2f6;
|
|
color: #3a4556;
|
|
}
|
|
|
|
.btn-disabled {
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.account-panel {
|
|
margin-top: 36rpx;
|
|
padding: 28rpx;
|
|
background: #ffffff;
|
|
border-radius: 20rpx;
|
|
box-shadow: 0 12rpx 24rpx rgba(15, 33, 66, 0.06);
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 20rpx;
|
|
}
|
|
|
|
.field-label {
|
|
font-size: 24rpx;
|
|
color: #7a8699;
|
|
}
|
|
|
|
.field-input {
|
|
margin-top: 12rpx;
|
|
padding: 20rpx;
|
|
border-radius: 16rpx;
|
|
background: #f5f6fa;
|
|
font-size: 28rpx;
|
|
color: #1f2d3d;
|
|
}
|
|
|
|
.link {
|
|
color: #ec5b13;
|
|
font-size: 26rpx;
|
|
}
|
|
|
|
.agreement {
|
|
margin-top: auto;
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 20rpx;
|
|
padding-top: 40rpx;
|
|
}
|
|
|
|
.checkbox {
|
|
transform: scale(0.9);
|
|
}
|
|
|
|
.agreement-text {
|
|
font-size: 22rpx;
|
|
color: #7a8699;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.agreement-text .link {
|
|
font-size: 22rpx;
|
|
font-weight: 600;
|
|
}
|
|
</style>
|