添加登录功能

This commit is contained in:
kron
2025-05-26 16:28:13 +08:00
parent 11171f66ec
commit e9070438f2
10 changed files with 205 additions and 105 deletions

View File

@@ -58,3 +58,33 @@ export const getProvinceData = () => {
}); });
}); });
}; };
// 获取省份及下属城市列表
export const loginAPI = (nickName, avatarUrl, code) => {
return new Promise((resolve, reject) => {
uni.request({
url: `${BASE_URL}/index/code`,
method: "POST",
data: {
appName: "shoot",
appId: "wxa8f5989dcd45cc23",
nickName,
avatarUrl,
code,
},
success: (res) => {
const { code, data } = res.data;
if (code === 0) {
resolve(data);
}
},
fail: (err) => {
reject(err);
uni.showToast({
title: "获取数据失败",
icon: "none",
});
},
});
});
};

View File

@@ -17,7 +17,7 @@ const props = defineProps({
<template> <template>
<view <view
class="container" class="sbtn"
:style="{ width: width, borderRadius: rounded + 'px' }" :style="{ width: width, borderRadius: rounded + 'px' }"
@click="onClick" @click="onClick"
> >
@@ -26,7 +26,7 @@ const props = defineProps({
</template> </template>
<style scoped> <style scoped>
.container { .sbtn {
margin: 0 auto; margin: 0 auto;
height: 40px; height: 40px;
line-height: 40px; line-height: 40px;
@@ -34,5 +34,8 @@ const props = defineProps({
background-color: #fed847; background-color: #fed847;
font-size: 15px; font-size: 15px;
text-align: center; text-align: center;
display: flex;
justify-content: center;
align-items: center;
} }
</style> </style>

View File

@@ -1,16 +1,20 @@
<script setup> <script setup>
import { ref, computed } from "vue"; import { computed } from "vue";
const props = defineProps({ const props = defineProps({
showRank: { showRank: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
}); user: {
const userInfo = ref({ type: Object,
name: "打酱油·路过", default: () => ({
level: "L100", nickName:'',
rank: 1928, lvl:0,
rankTotal: 1320, points:0,
rankLvl:0,
lvlPoints:0,
}),
},
}); });
const containerWidth = computed(() => (props.showRank ? "72vw" : "100vw")); const containerWidth = computed(() => (props.showRank ? "72vw" : "100vw"));
const toUserPage = () => { const toUserPage = () => {
@@ -19,7 +23,7 @@ const toUserPage = () => {
const currentPage = pages[pages.length - 1]; const currentPage = pages[pages.length - 1];
// 如果当前不是用户页面才进行跳转 // 如果当前不是用户页面才进行跳转
if (currentPage.route !== 'pages/user') { if (currentPage.route !== "pages/user") {
uni.navigateTo({ uni.navigateTo({
url: "/pages/user", url: "/pages/user",
}); });
@@ -35,15 +39,15 @@ const toUserPage = () => {
</view> </view>
<view class="user-details"> <view class="user-details">
<view class="user-name"> <view class="user-name">
<text>{{ userInfo.name }}</text> <text>{{ user.nickName }}</text>
<image src="../static/vip1.png" mode="widthFix" /> <image src="../static/vip1.png" mode="widthFix" />
</view> </view>
<view class="user-stats"> <view class="user-stats">
<text class="level-tag">钻石1级</text> <text class="level-tag">钻石1级</text>
<text class="level-tag">{{ userInfo.level }}</text> <text class="level-tag">{{ user.lvl }}</text>
<view class="rank-tag"> <view class="rank-tag">
<view :style="{ width: '40%' }" /> <view :style="{ width: '40%' }" />
<text>158/1928</text> <text>{{ user.lvl }}/{{ user.lvlPoints }}</text>
</view> </view>
</view> </view>
</view> </view>
@@ -52,7 +56,7 @@ const toUserPage = () => {
<text>本赛季全国</text> <text>本赛季全国</text>
<text class="rank-number" <text class="rank-number"
><text :style="{ color: '#ffd700' }" ><text :style="{ color: '#ffd700' }"
>{{ userInfo.rank }}/{{ userInfo.rankTotal }}</text >{{ user.points }}/{{ user.rankLvl }}</text
></text ></text
> >
</view> </view>

View File

@@ -6,6 +6,12 @@
"navigationBarTitleText": "首页" "navigationBarTitleText": "首页"
} }
}, },
{
"path": "pages/login",
"style": {
"navigationBarTitleText": "登录"
}
},
{ {
"path": "pages/user", "path": "pages/user",
"style": { "style": {

View File

@@ -1,85 +1,22 @@
<script setup> <script setup>
import { ref, onMounted } from "vue"; import { onMounted } from "vue";
import AppFooter from "@/components/AppFooter.vue"; import AppFooter from "@/components/AppFooter.vue";
import AppBackground from "@/components/AppBackground.vue"; import AppBackground from "@/components/AppBackground.vue";
import UserHeader from "@/components/UserHeader.vue"; import UserHeader from "@/components/UserHeader.vue";
import { getAppConfig } from "@/apis"; import { getAppConfig } from "@/apis";
import useStore from "@/store";
// import useStore from "@/store";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
// const store = useStore(); const store = useStore();
// 使用actions方法
// const { updateUsername } = store;
// 使用storeToRefs用于UI里显示保持响应性 // 使用storeToRefs用于UI里显示保持响应性
// const { user } = storeToRefs(store); const { user } = storeToRefs(store);
// 添加登录状态和用户信息 const toLoginPage = () => {
const isLogin = ref(true); uni.navigateTo({
const userInfo = ref({ url: "/pages/login",
name: "",
avatarUrl: "",
level: "L1",
rank: 0,
rankTotal: 0,
});
// 检查登录状态
const checkLogin = () => {
const storedUserInfo = uni.getStorageSync("userInfo");
if (storedUserInfo) {
userInfo.value = JSON.parse(storedUserInfo);
isLogin.value = true;
}
};
// 处理微信登录
const handleLogin = () => {
// console.log("getUsername", store.getUsername);
// updateUsername("we0f9we9f08");
uni.getUserProfile({
desc: "用于完善用户资料",
success: (res) => {
const { userInfo: wxUserInfo } = res;
console.log("wxUserInfo", wxUserInfo);
// 获取登录凭证
uni.login({
provider: "weixin",
success: async (loginRes) => {
const { code } = loginRes;
console.log("loginRes", loginRes);
// 这里可以把 code 和用户信息发送到后端
// const result = await loginAPI(code, wxUserInfo);
// 暂时直接使用微信返回的用户信息
userInfo.value = {
name: wxUserInfo.nickName,
avatarUrl: wxUserInfo.avatarUrl,
level: "L1",
rank: 0,
rankTotal: 0,
};
isLogin.value = true;
// 保存到本地存储
uni.setStorageSync("userInfo", JSON.stringify(userInfo.value));
},
fail: (err) => {
uni.showToast({
title: "登录失败",
icon: "none",
});
console.error("登录失败:", err);
},
});
},
fail: (err) => {
console.log("获取用户信息失败:", err);
},
}); });
}; };
const toFristTryPage = () => { const toFristTryPage = () => {
console.log("username", username);
uni.navigateTo({ uni.navigateTo({
url: "/pages/first-try", url: "/pages/first-try",
}); });
@@ -128,11 +65,11 @@ onMounted(() => {
<view class="root-container"> <view class="root-container">
<AppBackground /> <AppBackground />
<!-- 根据登录状态显示用户信息或登录按钮 --> <!-- 根据登录状态显示用户信息或登录按钮 -->
<block v-if="isLogin"> <block v-if="user.id">
<UserHeader :userInfo="userInfo" showRank /> <UserHeader :user="user" showRank />
</block> </block>
<block v-else> <block v-else>
<view class="login-btn" @click="handleLogin"> <view @click="toLoginPage">
<text>微信登录</text> <text>微信登录</text>
</view> </view>
</block> </block>
@@ -418,16 +355,4 @@ onMounted(() => {
line-height: 20px; line-height: 20px;
margin-bottom: 5px; margin-bottom: 5px;
} }
.login-btn {
margin: 20px;
padding: 10px 20px;
background-color: #07c160;
border-radius: 4px;
text-align: center;
}
.login-btn text {
color: #ffffff;
font-size: 16px;
}
</style> </style>

123
src/pages/login.vue Normal file
View File

@@ -0,0 +1,123 @@
<script setup>
import { ref } from "vue";
import Header from "@/components/Header.vue";
import SButton from "@/components/SButton.vue";
import useStore from "@/store";
import { loginAPI } from "@/apis";
const store = useStore();
// 使用actions方法
const { updateUser, updateToken } = store;
const agree = ref(false);
const handleAgree = () => {
agree.value = !agree.value;
};
const handleLogin = () => {
if (!agree.value) {
return uni.showToast({
title: "请先同意协议",
icon: "none",
});
}
// console.log("getUsername", store.getUsername);
uni.getUserProfile({
desc: "用于完善用户资料",
success: (res) => {
const { nickName, avatarUrl } = res.userInfo;
// 获取登录凭证
uni.login({
provider: "weixin",
success: async (loginRes) => {
const { code } = loginRes;
const result = await loginAPI(nickName, avatarUrl, code);
updateUser(result.user);
updateToken(result.token, result.expires);
uni.navigateBack();
},
fail: (err) => {
uni.showToast({
title: "登录失败",
icon: "none",
});
console.error("登录失败:", err);
},
});
},
fail: (err) => {
console.log("获取用户信息失败:", err);
},
});
};
</script>
<template>
<view class="container">
<image src="../static/login-bg.png" mode="widthFix" />
<Header />
<view>
<SButton :rounded="20" width="80vw" :onClick="handleLogin">
<image
src="../static/wechat-icon.png"
mode="widthFix"
class="wechat-icon"
/>
<text>登录/注册</text>
</SButton>
<view class="protocol" @click="handleAgree">
<view v-if="!agree" />
<image v-if="agree" src="../static/checked.png" mode="widthFix" />
<text
>已同意并阅读<text :style="{ color: '#fff' }">用户协议</text
><text :style="{ color: '#fff' }">隐私协议</text>内容</text
>
</view>
</view>
</view>
</template>
<style scoped>
.container {
position: relative;
}
.container > image:first-child {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100vh;
z-index: -1;
}
.container > view:last-child {
width: 100%;
height: calc(100vh - 96px);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.protocol {
display: flex;
justify-content: center;
align-items: center;
font-size: 13px;
margin-top: 40px;
color: #8a8a8a;
}
.protocol > image {
width: 18px;
height: 18px;
margin-right: 10px;
}
.protocol > view:first-child {
width: 16px;
height: 16px;
border-radius: 50%;
margin-right: 10px;
border: 1px solid #fff;
}
.wechat-icon {
width: 24px;
height: 24px;
margin-right: 20px;
}
</style>

BIN
src/static/checked.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
src/static/login-bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 570 KiB

BIN
src/static/wechat-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -5,7 +5,12 @@ export default defineStore("store", {
// 状态 // 状态
state: () => ({ state: () => ({
user: { user: {
username: "游客", id: "",
nickName: "游客",
},
request: {
token: "",
tokenExpire: 0,
}, },
}), }),
@@ -18,8 +23,12 @@ export default defineStore("store", {
// 方法 // 方法
actions: { actions: {
updateUsername(newUsername) { updateUser(user) {
this.user.username = newUsername; this.user = user;
},
updateToken(token, expire) {
this.request.token = token;
this.request.tokenExpire = Date.now() + expire;
}, },
}, },
@@ -29,7 +38,7 @@ export default defineStore("store", {
strategies: [ strategies: [
{ {
storage: uni.getStorageSync, storage: uni.getStorageSync,
paths: ["user"], // 只持久化用户信息 paths: ["user", "request"], // 只持久化用户信息
}, },
], ],
}, },