2025 后端自学UNIAPP【项目实战:旅游项目】5、个人中心页面:微信登录,同意授权,获取用户信息

news2025/5/16 9:59:18

一、框架以及准备工作

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秒后自动关闭
	})
}

三、效果

      

     

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2376759.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

蓝桥杯算法题 -蛇形矩阵(方向向量)

&#x1f381;个人主页&#xff1a;工藤新一 &#x1f50d;系列专栏&#xff1a;C面向对象&#xff08;类和对象篇&#xff09; &#x1f31f;心中的天空之城&#xff0c;终会照亮我前方的路 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 文章目录 P…

配置VScodePython环境Python was not found;

Python was not found; run without arguments to install from the Microsoft Store, or disable this shortcut from Settings > Manage App Execution Aliases. 候试试重启电脑。 在卸载重装python后会出现难以解决的局面&#xff0c;系统变量&#xff0c;命令行&#…

ollama 重命名模型

ollama 重命名模型 ollama list# 查看列表 ollama list # 生成原模型的Modelfile文件 ollama show --modelfile qwen3:32b > Modelfile # 从Modelfile文件创建新的模型 ollama create qwen3 -f Modelfile # 删除原模型 ollama rm qwen3:32b

Qt信号槽机制与UI设计完全指南:从基础原理到实战应用

目录 前言一、信号槽1.1 传参1.2 Qt信号与槽的对应关系1.2.1一对多关系1.2.2 多对一关系 二、Designer三、Layout 布局3.1 基础用法3.2 打破布局3.3 贴合窗口3.4 伸展器&#xff08;Spacer&#xff09;3.5 嵌套布局 四、ui指针五、QWidget六、QLabel 标签使用指南总结 前言 本…

XBL6501/02/03在POE设备上的应用方案

前言&#xff1a; 在当今数字化时代&#xff0c;POE&#xff08;Power over Ethernet&#xff09;设备因其能够通过以太网线同时传输数据和电力而被广泛应用。为了满足这些设备日益增长的电源需求&#xff0c;芯伯乐推出了XBL6501/02/03系列DC-DC电源芯片&#xff0c;为POE设备…

编程题 03-树2 List Leaves【PAT】

文章目录 题目输入格式输出格式输入样例输出样例 题解解题思路完整代码 编程练习题目集目录 题目 Given a tree, you are supposed to list all the leaves in the order of top down, and left to right. 输入格式 Each input file contains one test case. For each case, …

生信小白学Rust-03

语句和表达式 举个栗子&#x1f330; fn add_with_extra(x: i32, y: i32) -> i32 {let x x 1; // 语句let y y 5; // 语句x y // 表达式 } // 语句执行操作 // 表达式会返回一个值 怎么区分呢&#xff0c;目前我的理解是只要返回了值&#xff0c;那它就是表达式 fn…

缺乏需求优先级划分时,如何合理分配资源?

当需求优先级不明确时&#xff0c;合理分配资源的关键在于建立统一评估标准、实施敏捷资源管理、提升团队协作效率、加强跨部门沟通机制。尤其是建立统一评估标准至关重要&#xff0c;它能帮助组织快速判断各项需求的重要性与紧迫性&#xff0c;从而实现资源的动态匹配与有效利…

操作系统学习笔记第3章 内存管理(灰灰题库)

1. 单选题 某页式存储管理系统中&#xff0c;主存为 128KB&#xff0c;分成 32 块&#xff0c;块号为 0、1、2、3、…、31。某作业有 5 块&#xff0c;其页号为 0、1、2、3、4&#xff0c;被分别装入主存的 3、8、4、6、9 块中。有一逻辑地址为 [3, 70]&#xff08;其中方括号中…

详细分析python 中的deque 以及和list 的用法区别

dqque :双端队列&#xff0c;可以快速的从另外一侧追加和推出对象,deque是一个双向链表&#xff0c;针对list连续的数据结构插入和删除进行优化。它提供了两端都可以操作的序列&#xff0c;这表示在序列的前后你都可以执行添加或删除操作。 通过上图可以看出&#xff0c;deque …

Stack overflow

本文来源 &#xff1a;腾讯元宝 Stack Overflow - Where Developers Learn, Share, & Build Careers 开发者学习&#xff0c;分享 通过学习、工作和经验积累等方式&#xff0c;逐步建立和发展自己的职业生涯。 Find answers to your technical questions and help othe…

和为target问题汇总

文章目录 习题377.组合总和 IV494.目标和 和为target的问题&#xff0c;可以有很多种问题的形式的考察&#xff0c;当然&#xff0c;及时的总结与回顾有利于我们熟练掌握这些知识&#xff01; 习题 377.组合总和 IV 377.组合总和 IV 思路分析&#xff1a;通过观察&#xff0…

STM32单片机内存分配详细讲解

单片机的内存无非就两种&#xff0c;内部FLASH和SRAM&#xff0c;最多再加上一个外部的FLASH拓展。在这里我以STM32F103C8T6为例子讲解FLASH和SRAM。 STM32F103C8T6具有64KB的闪存和20KB的SRAM。 一. Flash 1.1 定义 非易失性存储器&#xff0c;即使在断电后&#xff0c;其所…

Ubuntu 编译SRS和ZLMediaKit用于视频推拉流

SRS实现视频的rtmp webrtc推流 ZLMediaKit编译生成MediaServer实现rtsp推流 SRS指定某个固定网卡&#xff0c;修改程序后重新编译 打开SRS-4.0.0/trunk/src/app/srs_app_rtc_server.cpp&#xff0c;在 232 行后面添加&#xff1a; ZLMediaKit编译后文件存放在ZLMediakit/rele…

Intellij报错:the file size(3.47M) exceeds configured limit (2.56MB)

今天在部署一个教学平台的时候&#xff0c;当执行数据库脚本出现了以上问题。 自己把解决的方案分享给大家&#xff1a; 于IntelliJ IDEA或PyCharm&#xff0c;可以通过编辑idea.properties文件来增加文件大小限制。 打开idea.properties文件&#xff0c;通常位于IDE的安装目录…

Unity动画与生命周期函数

一、Animator动画组件 Animator组件是Unity中用于管理和控制动画的主要工具&#xff0c;它可以处理复杂的动画状态机和动画片段之间的过 1.动画状态机 Animator组件的核心是动画状态机&#xff0c;它由多个动画状态和状态之间的过渡组成。可以通过Unity的动画窗口来创建和编辑…

解决ubuntu20中tracker占用过多cpu,引起的风扇狂转

track是linux中的文件索引工具&#xff0c;ubuntu18之前是默认不安装的&#xff0c;所以在升级到20后会默认安装&#xff0c;它是和桌面程序gnome绑定的&#xff0c;甚至还有很多依赖项&#xff0c;导致无法删除&#xff0c;一旦删除很多依赖项都不能运行&#xff0c;禁用也很难…

在线文档管理系统 spring boot➕vue|源码+数据库+部署教程

&#x1f4cc; 一、项目简介 本系统采用Spring Boot Vue ElementUI技术栈&#xff0c;支持管理员和员工两类角色&#xff0c;涵盖文档上传、分类管理、公告发布、员工资料维护、部门岗位管理等核心功能。 系统目标是打造一个简洁高效的内部文档管理平台&#xff0c;便于员工…

在UI 原型设计中,交互规则有哪些核心要素?

在UI 原型设计中&#xff0c;交互规则主要有三个核心要素&#xff0c;分别为重要性、原则与实践&#xff0c;具体表现在&#xff1a; 一、交互规则在 UI 原型设计中的重要性 明确交互逻辑&#xff1a;设计阶段制定交互规则&#xff0c;清晰定义界面元素操作响应。 如社交应用…

OpenResty Manager 介绍与部署(Docker部署)

概述 OpenResty-Manager 是一个基于 OpenResty 构建的开源 Web 管理平台。OpenResty 是一个高性能的 Web 平台&#xff0c;集成了 Nginx 和 LuaJIT&#xff0c;支持强大的脚本功能。OpenResty-Manager 由 Safe3 开发&#xff0c;提供了一个用户友好的界面&#xff0c;用于管理…