一、框架以及准备工作
1、前端项目文件结构展示 2、后端项目文件结构展示
3、登录微信公众平台,注册一个个人的程序,获取大appid(前端后端都需要)和密钥(后端需要)
微信公众平台微信公众平台,给个人、企业和组织提供业务服务与用户管理能力的全新服务平台。https://mp.weixin.qq.com/
4、后端代码配置上自己的appid和密钥以及相关文件
我的是 application.properties,
后端代码逻辑可以从这里获取,自己编写到自己的java后端业务代码中2025 Java 微信小程序根据code获取openid,二次code获取手机号【工具类】拿来就用-CSDN博客文章浏览阅读4次。Spring Boot实现微信小程序登录,含session_key获取和手机号解密功能。https://blog.csdn.net/m0_47484034/article/details/147993148?spm=1001.2014.3001.5501
#wechat
wx.open.applet.app_id=自己的
wx.open.applet.secret=自己的
5、前端配置上自己的appid
二、前端页面源码
1、页面源码personal_center.vue
微信登录重点是:弹出框的相关逻辑
<template>
<view class="container">
<!-- 个人信息 -->
<view class="info-box">
<!-- 签到、设置、聊天一行 -->
<view class="info-header">
<!-- 签到 -->
<view class="sign-in">
<up-icon name="calendar" color="#fff" size="30"></up-icon>
<view class="sign-text">签到</view>
</view>
<!-- 设置和聊天 -->
<view class="action-icons">
<up-icon name="chat" color="#fff" size="30"></up-icon>
<up-icon name="setting" color="#fff" size="30" style="margin-left: 20rpx;"></up-icon>
</view>
</view>
<!-- 个人信息卡片 -->
<view class="profile-card" @click="toGetUserInfo">
<!-- 个人信息卡片 信息主体 -->
<view class="profile-main">
<!-- 如果未登录/未注册显示默认头像并提示注册/登录 -->
<template v-if="!userInfo.nickName">
<!-- 默认头像 -->
<image class="avatar" src="../../static/logo.png" mode="aspectFill"></image>
<!-- 去注册/登录 -->
<view class="nikename">注册 / 登录</view>
</template>
<!-- 如果登录后,显示出头像和昵称 -->
<template v-else>
<!-- 获取头像 -->
<image class="avatar" image :src="userInfo.avatar" mode="aspectFill"></image>
<!-- 昵称 -->
<view class="nikename">{{userInfo.nickName}}</view>
</template>
</view>
<view class="profile-other">
<view class="profile-other-item favourite">
<view class="profile-other-item-num favourite—num">5</view>
<view class="profile-other-item-text favourite-text">最爱</view>
</view>
<view class="profile-other-item browse">
<view class="profile-other-item-num browse-num">100</view>
<view class="profile-other-item-text browse-text">浏览</view>
</view>
</view>
</view>
</view>
<!-- 功能列表 -->
<view class="list-box">
<view class="list">
<uni-list>
<uni-list-item title="个人信息" showArrow thumb="/static/user-menu/个人信息.png" thumb-size="sm"
clickable />
<uni-list-item title="我的购物车" showArrow thumb="/static/user-menu/我的购物车.png" thumb-size="sm"
clickable />
<uni-list-item title="用户反馈" showArrow thumb="/static/user-menu/用户反馈.png" thumb-size="sm"
clickable />
<uni-list-item title="我的邮件" showArrow thumb="/static/user-menu/我的邮件.png" thumb-size="sm"
clickable />
<uni-list-item title="分享有礼" showArrow thumb="/static/user-menu/分享有礼.png" thumb-size="sm"
clickable />
</uni-list>
</view>
</view>
<!-- 弹出层 -->
<up-popup :show="show" @close="close">
<view class="popup">
<view class="popup-title">获取您的昵称、头像</view>
<!-- 头像选择 -->
<view class="popup-child-box">
<view class="label">获取用户头像</view>
<button class="avatar-btn" open-type="chooseAvatar" @chooseavatar="onChooseAvatar">
<image class="avatar-img" :src="userInfo.avatar || '/static/default-avatar.png'"></image>
</button>
</view>
<!-- 昵称输入 -->
<view class="popup-child-box">
<view class="label">获取用户昵称</view>
<input class="nickname-input" type="nickname" @input="changeName" />
</view>
<button size="default" type="primary" @click="userSubmit">确认</button>
</view>
</up-popup>
</view>
</template>
<script setup>
import {
ref,
reactive
} from 'vue'
// 生命周期,进来就加载
import {
onLoad
} from '@dcloudio/uni-app'
// 生命周期,进来就加载
import {
login
} from '../../api/my_api'
import avatar from '../../uni_modules/uview-plus/components/u-avatar/avatar';
onLoad(() => {
// 尝试从本地存储获取用户信息
const storedUserInfo = uni.getStorageSync('userInfo');
if (storedUserInfo) {
userInfo.nickName = storedUserInfo.nickName;
userInfo.avatar = storedUserInfo.avatar;
}
})
const userInfo = reactive({
nickName: "",
avatar: ""
})
// 控制弹出层的显示
const show = ref(false)
//关闭弹出层的显示
const close = () => {
show.value = false
}
const onChooseAvatar = (e) => {
userInfo.avatar = e.detail.avatarUrl
}
const changeName = (e) => {
userInfo.nickName = e.detail.value
}
// 用户提交信息
const userSubmit = async () => {
if (!userInfo.nickName || !userInfo.avatar) {
uni.showToast({
title: '请填写完整信息',
icon: 'none'
});
return;
}
try {
// 保存用户信息到本地存储
uni.setStorageSync('userInfo', {
nickName: userInfo.nickName,
avatar: userInfo.avatar
});
uni.showToast({
title: '信息保存成功',
icon: 'success'
});
show.value = false
} catch (e) {
uni.showToast({
title: '保存失败,请重试',
icon: 'none'
});
}
}
//微信授权登录,获取微信用户相关信息
const toGetUserInfo = () => {
// 如果已经有用户信息,直接显示弹窗
if (userInfo.nickName && userInfo.avatar) {
show.value = true;
return;
}
uni.showModal({
title: "温馨提示",
content: "授权登录才可以正常使用小程序",
success(res) {
if (res.confirm) {
uni.login({
success: async (data) => {
uni.getUserInfo({
provider: 'weixin',
success: async function(infoRes) {
const user = await login(data.code, infoRes
.userInfo.avatarUrl)
userInfo.nickName = infoRes.userInfo.nickName
userInfo.avatar = infoRes.userInfo.avatarUrl
// 保存用户信息到本地存储
uni.setStorageSync('userInfo', {
nickName: userInfo.nickName,
avatar: userInfo.avatar
});
show.value = true
}
})
}
})
}
}
})
}
</script>
<style lang="scss" scoped>
.container {
height: 100vh;
background-color: #f5f5f5;
// 个人信息区域
.info-box {
width: 100%;
position: relative;
z-index: 1;
overflow: hidden;
padding: 40rpx 30rpx;
box-sizing: border-box;
&::after {
content: "";
width: 140%;
height: 400rpx;
z-index: -1;
position: absolute;
top: 0;
left: -20%;
background-color: #6799FF;
border-radius: 0 0 50% 50%;
}
// 顶部图标行
.info-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 40rpx;
.sign-in {
display: flex;
align-items: center;
.sign-text {
color: white;
font-size: 28rpx;
margin-left: 10rpx;
}
}
.action-icons {
display: flex;
align-items: center;
}
}
// 个人信息卡片
.profile-card {
background-color: #fff;
border-radius: 20rpx;
padding: 30rpx;
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.08);
// 个人信息卡片 信息主体
.profile-main {
display: flex;
align-items: center;
// 用户头像
.avatar {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
margin-right: 30rpx;
}
// 用户昵称
.nikename {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
}
.profile-other {
display: flex;
justify-content: space-around;
align-items: center;
.profile-other-item {
text-align: center;
margin-top: 40rpx;
.profile-other-item-num {
color: black;
font-weight: 700;
font-size: 25rpx;
}
.profile-other-item-text {
color: #757575;
margin-top: 10rpx;
font-size: 20rpx;
}
}
}
}
}
// 功能列表
.list-box {
height: 200rpx;
margin: -10rpx auto 0;
padding: 20rpx;
box-sizing: border-box;
border-radius: 12rpx;
}
/* 弹出层样式 */
.popup {
padding: 40rpx;
border-radius: 20rpx;
background: #fff;
/* 标题 */
.popup-title {
margin-bottom: 40rpx;
font-size: 36rpx;
font-weight: bold;
text-align: center;
color: #333;
}
}
/* 每个选项容器 */
.popup-child-box {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx 0;
border-bottom: 1rpx solid #f0f0f0;
/* 标签文字 */
.label {
font-size: 32rpx;
color: #666;
width: 200rpx;
}
/* 头像按钮 */
.avatar-btn {
margin: 0;
padding: 0;
border: none;
background: transparent;
line-height: 1;
.avatar-img {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
background: #f5f5f5;
}
}
/* 昵称输入框 */
.nickname-input {
flex: 1;
text-align: right;
font-size: 32rpx;
color: #333;
}
}
}
</style>
2、调用的后端各个接口的js文件:my_api.js
// 引入公共的请求封装
import http from './my_http.js'
// 登录接口
export const login = async (code, avatar) => {
const res = await http('/login/getWXSessionKey', {code,avatar});
};
// 获取bannner列表
export const getBannerList = () => {
return http('/banner/list')
}
// 获取景点类型列表(支持传入typeId参数)
export const getTypeList = () => {
return http('/type/list')
}
// 获取景点列表
export const getAttractionList = (typeId) => {
// 如果有typeId就拼接到URL,没有就不加
const url = typeId ? `/attraction/list?typeId=${typeId}` : '/attraction/list'
return http(url)
}
// 获取景点详情
export const getAttractionInfo = (attractionId) => {
return http(`/attraction/getInfo/${attractionId}`)
}
3、公共http请求封装的js文件:my_http.js
/**
* 基础API请求地址(常量,全大写命名规范)
* @type {string}
* @constant
*/
let BASE_URL="http://localhost:9001/travel"
/**
* 封装的HTTP请求核心函数
* @param {string} url - 请求的接口路径(不需要包含基础接口URL)
* @param {Object} [data={}] - 请求参数,默认为空对象
* @param {string} [method='GET'] - HTTP方法,默认GET,支持GET/POST/DELETE/PUT等
* @returns {Promise} - 返回Promise便于链式调用
*
*/
export default function http(url, data = {}, method = 'GET') {
// 返回一个Promise对象,支持外部链式调用
return new Promise((resolve, reject) => {
// 调用uni-app的底层请求API
uni.request({
// 拼接完整请求地址(基础接口URL + 请求的接口路径)
url: BASE_URL + url,
// 请求参数(GET请求时会自动转为query string)
data: data,
// 请求方法(转换为大写保证兼容性)
method: method.toUpperCase(),
// 请求头配置
header: {
// 从本地存储获取token,没有就位空
'token': uni.getStorageSync('token') || '',
// 默认JSON格式
'Content-Type': 'application/json'
},
// 请求成功回调(注意:只要收到服务器响应就会触发,无论HTTP状态码)
success: (res) => {
/* HTTP层状态码处理(4xx/5xx等也会进入success回调) */
if (res.statusCode !== 200) {
const errMsg = `[${res.statusCode}]${res.errMsg || '请求失败'}`
showErrorToast(errMsg)
// 使用Error对象传递更多错误信息
reject(errMsg)
}
/* 业务层状态码处理(假设1表示成功) */
if (res.data.code === "200") {
// 提取业务数据(约定data字段为有效载荷)
resolve(res.data.data)
} else {
// 业务错误处理
const errMsg = res.data.msg || `业务错误[${res.data.code}]`
showErrorToast(errMsg)
reject(res.data.msg)
}
},
// 请求失败回调(网络错误、超时等)
fail: (err) => {
const errMsg = `网络连接失败: ${err.errMsg || '未知错误'}`
showErrorToast(errMsg)
reject(new Error(errMsg))
},
})
})
}
/**
* 显示统一格式的错误提示(私有工具方法)
* @param {string} message - 需要显示的错误信息
* @private
*/
function showErrorToast(message) {
uni.showToast({
title: message, // 提示内容
icon: 'none', // 不显示图标
duration: 3000 // 3秒后自动关闭
})
}
三、效果