فهرست منبع

增加登陆界面

YunaiV 3 سال پیش
والد
کامیت
55ee2fcad7

+ 47 - 0
yudao-vue-ui/common/js/util.js

@@ -0,0 +1,47 @@
+let _debounceTimeout = null,
+	_throttleRunning = false
+	
+/**
+ * 防抖
+ * 参考文章 https://juejin.cn/post/6844903669389885453
+ * 
+ * @param {Function} 执行函数
+ * @param {Number} delay 延时ms   
+ */
+export const debounce = (fn, delay=500) => {
+	clearTimeout(_debounceTimeout);
+	_debounceTimeout = setTimeout(() => {
+		fn();
+	}, delay);
+}
+
+/**
+ * 节流
+ * 参考文章 https://juejin.cn/post/6844903669389885453
+ * 
+ * @param {Function} 执行函数
+ * @param {Number} delay 延时ms  
+ */
+export const throttle = (fn, delay=500) => {
+	if(_throttleRunning){
+		return;
+	}
+	_throttleRunning = true;
+	fn();
+	setTimeout(() => {
+	    _throttleRunning = false;
+	}, delay);
+}
+
+/**
+ * toast
+ */
+export const msg = (title = '', param={}) => {
+	if(!title) return;
+	uni.showToast({
+		title,
+		duration: param.duration || 1500,
+		mask: param.mask || false,
+		icon: param.icon || 'none'
+	});
+}

+ 96 - 0
yudao-vue-ui/common/mixin/mixin.js

@@ -0,0 +1,96 @@
+// import {request} from '@/common/js/request'
+
+export default{
+	data() {
+		return {
+			page: 0, // 页码
+			pageNum: 6, // 每页加载数据量
+			loadingType: 1, // 加载类型。0 加载前;1 加载中;2 没有更多
+			isLoading: false, // 刷新数据
+			loaded: false, // 加载完毕
+		}
+	},
+	methods: {
+		/**
+		 * 打印日志,方便调试
+		 *
+		 * @param {Object} data 数据
+		 */
+		log(data) {
+			console.log(JSON.parse(JSON.stringify(data)))
+		},
+		
+		/**
+		 * navigatorTo 跳转页面
+		 * 
+		 * @param {String} url
+		 * @param {Object} options 可选参数
+		 * @param {Boolean} options.login 是否检测登录  
+		 */
+		navTo(url, options={}) {
+			this.$util.throttle(() => {
+				if (!url) {
+					return;
+				}
+				// 如果需要登陆,并且未登陆,则跳转到登陆界面
+				if ((~url.indexOf('login=1') || options.login) && !this.$store.getters.hasLogin){
+					url = '/pages/auth/login';
+				}
+				// 跳转到指定 url 地址
+				uni.navigateTo({
+					url
+				})
+			}, 300)
+		},
+		
+		/**
+		 * $request云函数请求 TODO 芋艿:需要改成自己的
+		 * @param {String} module
+		 * @param {String} operation
+		 * @param {Boolean} data 请求参数
+		 * @param {Boolean} ext 附加参数
+		 * @param {Boolean} ext.showLoading 是否显示loading状态,默认不显示
+		 * @param {Boolean} ext.hideLoading 是否关闭loading状态,默认关闭
+		 * @param {Boolean} ext.login 未登录拦截
+		 * @param {Boolean} ext.setLoaded 加载完成是设置页面加载完毕
+		 */
+		$request(module, operation, data={}, ext={}){
+			if(ext.login && !this.$util.isLogin()){
+				return;
+			}
+			if(ext.showLoading){
+				this.isLoading = true;
+			}
+			return new Promise((resolve, reject)=> {
+				request(module, operation, data, ext).then(result => {
+					if(ext.hideLoading !== false){
+						this.isLoading = false;
+					}
+					setTimeout(()=>{
+						if(this.setLoaded !== false){
+							this.loaded = true;
+						}
+					}, 100)
+					this.$refs.confirmBtn && this.$refs.confirmBtn.stop();
+					resolve(result);
+				}).catch((err) => {
+					reject(err);
+				})
+			})
+		},
+		imageOnLoad(data, key){ // TODO 芋艿:需要改成自己的
+			setTimeout(()=>{
+				this.$set(data, 'loaded', true);
+			}, 100)
+		},
+		showPopup(key){ //  TODO 芋艿:需要改成自己的
+			this.$util.throttle(()=>{
+				this.$refs[key].open();
+			}, 200)
+		},
+		hidePopup(key){ //  TODO 芋艿:需要改成自己的
+			this.$refs[key].close();
+		},
+		stopPrevent(){}, //  TODO 芋艿:需要改成自己的
+	},
+}

+ 26 - 0
yudao-vue-ui/main.js

@@ -1,5 +1,31 @@
 import App from './App'
 
+// 全局 Mixin
+import mixin from './common/mixin/mixin'
+Vue.mixin(mixin) 
+
+// 全局 Util
+import {
+	msg,
+	isLogin,
+	debounce,
+	throttle,
+	prePage,
+	date
+} from '@/common/js/util'
+Vue.prototype.$util = {
+	msg,
+	isLogin,
+	debounce,
+	throttle,
+	prePage,
+	date
+}
+
+// 全局 Store
+import store from './store'
+Vue.prototype.$store = store
+
 // #ifndef VUE3
 import Vue from 'vue'
 Vue.config.productionTip = false

+ 15 - 1
yudao-vue-ui/pages.json

@@ -11,7 +11,21 @@
 				"navigationBarTitleText": "我的",
 				"navigationStyle": "custom"
 			}
-		}
+		}, {
+			"path": "pages/auth/login",
+			"style": {
+				"navigationBarTitleText": "登录",
+				"navigationStyle":"custom",
+				"app-plus": {
+					"animationType": "slide-in-bottom"
+				}
+			}
+		}, {
+            "path" : "pages/set/userInfo",
+            "style" : {
+				"navigationBarTitleText": "个人资料"
+			}
+        }
 	],
 	"globalStyle": {
 		"navigationBarTextStyle": "black",

+ 323 - 0
yudao-vue-ui/pages/auth/login.vue

@@ -0,0 +1,323 @@
+<template>
+	<view class="app">
+		<view class="left-bottom-sign"></view>
+		<view class="back-btn mix-icon icon-guanbi" @click="navBack"></view>
+		<view class="right-top-sign"></view>
+		<view class="agreement center">
+			<text class="mix-icon icon-xuanzhong" :class="{active: agreement}" @click="checkAgreement"></text>
+			<text @click="checkAgreement">请认真阅读并同意</text>
+			<text class="tit" @click="navToAgreementDetail(1)">《用户服务协议》</text>
+			<text class="tit" @click="navToAgreementDetail(2)">《隐私权政策》</text>
+		</view>
+		<view class="wrapper">
+			<view class="left-top-sign">LOGIN</view>
+			<view class="welcome">
+				欢迎回来!
+			</view>
+			<view class="input-content">
+				<view class="input-item">
+					<text class="tit">手机号码</text>
+					<view class="row">
+						<input
+							v-model="username"
+							type="number" 
+							maxlength="11"
+							placeholder="请输入手机号码"
+							placeholder-style="color: #909399"
+						/>
+					</view>
+				</view>
+				<view class="input-item">
+					<text class="tit">验证码</text>
+					<view class="row">
+						<input
+							v-model="code"
+							type="number"
+							maxlength="6"
+							placeholder="请输入手机验证码"
+							placeholder-style="color: #909399"
+						/>
+						<mix-code :mobile="username" templateCode="SMS_194050994"></mix-code>
+					</view>
+				</view>
+			</view>
+			<mix-button ref="confirmBtn" text="登录" marginTop="60rpx" @onConfirm="login"></mix-button>
+			
+			<!-- #ifdef APP-PLUS || MP-WEIXIN -->
+			<view class="other-wrapper">
+				<view class="line center">
+					<text class="tit">快捷登录</text>
+				</view>
+				<view class="list row">
+					<!-- #ifdef MP-WEIXIN -->
+					<view class="item column center" @click="mpWxGetUserInfo">
+						<image class="icon" src="/static/icon/login-wx.png"></image>
+					</view>
+					<!-- #endif -->
+					
+					<!-- #ifdef APP-PLUS -->
+					<view v-if="canUseAppleLogin && false" class="item column center" style="width: 180rpx;" @click="loginByApple">
+						<image class="icon" src="/static/icon/apple.png"></image>
+						<text>Apple登录</text>
+					</view>
+					<view class="item column center" style="width: 180rpx;" @click="loginByWxApp">
+						<image class="icon" src="/static/icon/login-wx.png"></image>
+						<text>微信登录</text>
+					</view>
+					<!-- #endif -->
+				</view>
+			</view>
+			<!-- #endif -->
+		</view>
+		
+		<mix-loading v-if="isLoading"></mix-loading>
+	</view>
+</template>
+
+<script>
+	import {checkStr} from '@/common/js/util'
+	import loginMpWx from './mixin/login-mp-wx.js'
+	import loginAppWx from './mixin/login-app-wx.js'
+	import loginApple from './mixin/login-apple.js'
+	export default{
+		mixins: [loginMpWx, loginAppWx, loginApple],
+		data(){
+			return {
+				canUseAppleLogin: false,
+				agreement: true,
+				username: '',
+				code: '',
+			}
+		},
+		onLoad() {
+			console.log(1);
+		},
+		methods: {
+			loginSuccessCallBack(data){
+				this.$util.msg('登陆成功');
+				this.$store.commit('setToken', data);
+				setTimeout(()=>{
+					uni.navigateBack();
+				}, 1000)
+			},
+			//手机号登录
+			async login(){
+				if(!this.agreement){
+					this.$util.msg('请阅读并同意用户服务及隐私协议');
+					this.$refs.confirmBtn.stop();
+					return;
+				}
+				const {username, code} = this;
+				if(!checkStr(username, 'mobile')){
+					this.$util.msg('请输入正确的手机号码');
+					this.$refs.confirmBtn.stop();
+					return;
+				}
+				if(!checkStr(code, 'mobileCode')){
+					this.$util.msg('验证码错误');
+					this.$refs.confirmBtn.stop();
+					return;
+				}
+				const res = await this.$request('user', 'login', {username,code});
+				this.$refs.confirmBtn.stop();
+				
+				if(res.status === 1){
+					this.loginSuccessCallBack(res.data);
+				}else{
+					this.$util.msg(res.msg);
+				}
+			},
+			navBack(){
+				uni.navigateBack();
+			},
+			//同意协议
+			checkAgreement(){
+				this.agreement = !this.agreement;
+			},
+			//打开协议
+			navToAgreementDetail(type){
+				this.navTo('/pages/public/article?param=' + JSON.stringify({
+					module: 'article',
+					operation: 'getAgreement',
+					data: {
+						type
+					}
+				}))
+			},
+		}
+	}
+</script>
+
+<style>
+	page{
+		background: #fff;
+	}
+</style>
+<style scoped lang='scss'>
+	.app{
+		padding-top: 15vh;
+		position:relative;
+		width: 100vw;
+		height: 100vh;
+		overflow: hidden;
+		background: #fff;
+	}
+	.wrapper{
+		position:relative;
+		z-index: 90;
+		padding-bottom: 40rpx;
+	}
+	.back-btn{
+		position:absolute;
+		left: 20rpx;
+		top: calc(var(--status-bar-height) + 20rpx);
+		z-index: 90;
+		padding: 20rpx;
+		font-size: 32rpx;
+		color: #606266;
+	}
+	.left-top-sign{
+		font-size: 120rpx;
+		color: #f8f8f8;
+		position:relative;
+		left: -12rpx;
+	}
+	.right-top-sign{
+		position:absolute;
+		top: 80rpx;
+		right: -30rpx;
+		z-index: 95;
+		
+		&:before, &:after{
+			display:block;
+			content:"";
+			width: 400rpx;
+			height: 80rpx;
+			background: #b4f3e2;
+		}
+		&:before{
+			transform: rotate(50deg);
+			border-top-right-radius: 50px;
+		}
+		&:after{
+			position: absolute;
+			right: -198rpx;
+			top: 0;
+			transform: rotate(-50deg);
+			border-top-left-radius: 50px;
+		}
+	}
+	.left-bottom-sign{
+		position:absolute;
+		left: -270rpx;
+		bottom: -320rpx;
+		border: 100rpx solid #d0d1fd;
+		border-radius: 50%;
+		padding: 180rpx;
+	}
+	.welcome{
+		position:relative;
+		left: 50rpx;
+		top: -90rpx;
+		font-size: 46rpx;
+		color: #555;
+		text-shadow: 1px 0px 1px rgba(0,0,0,.3);
+	}
+	.input-content{
+		padding: 0 60rpx;
+	}
+	.input-item{
+		display:flex;
+		flex-direction: column;
+		align-items:flex-start;
+		justify-content: center;
+		padding: 0 30rpx;
+		background: #f8f6fc;
+		height: 120rpx;
+		border-radius: 4px;
+		margin-bottom: 50rpx;
+		
+		&:last-child{
+			margin-bottom: 0;
+		}
+		.row{
+			width: 100%;
+		}
+		.tit{
+			height: 50rpx;
+			line-height: 56rpx;
+			font-size: 26rpx;
+			color: #606266;
+		}
+		input{
+			flex: 1;
+			height: 60rpx;
+			font-size: 30rpx;
+			color: #303133;
+			width: 100%;
+		}	
+	}
+	/* 其他登录方式 */
+	.other-wrapper{
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		padding-top: 20rpx;
+		margin-top: 80rpx;
+		
+		.line{
+			margin-bottom: 40rpx;
+			
+			.tit{
+				margin: 0 32rpx;
+				font-size: 24rpx;
+				color: #606266;
+			}
+			&:before, &:after{
+				content: '';
+				width: 160rpx;
+				height: 0;
+				border-top: 1px solid #e0e0e0;
+			}
+		}
+		.item{
+			font-size: 24rpx;
+			color: #606266;
+			background-color: #fff;
+			border: 0;
+			
+			&:after{
+				border: 0;
+			}
+		}
+		.icon{
+			width: 90rpx;
+			height: 90rpx;
+			margin: 0 24rpx 16rpx;
+		}
+	}
+	.agreement{
+		position: absolute;
+		left: 0;
+		bottom: 6vh;
+		z-index: 1;
+		width: 750rpx;
+		height: 90rpx;
+		font-size: 24rpx;
+		color: #999;
+		
+		.mix-icon{
+			font-size: 36rpx;
+			color: #ccc;
+			margin-right: 8rpx;
+			margin-top: 1px;
+			
+			&.active{
+				color: $base-color;
+			}
+		}
+		.tit{
+			color: #40a2ff;
+		}
+	}
+</style>

+ 73 - 0
yudao-vue-ui/pages/auth/mixin/login-app-wx.js

@@ -0,0 +1,73 @@
+export default{
+	// #ifdef APP-PLUS
+	methods: {
+		/**
+		 * 微信App登录
+		 *  "openId": "o0yywwGWxtBCvBuE8vH4Naof0cqU",
+		 *	"nickName": "S .",
+		 *	"gender": 1,
+		 *	"city": "临沂",
+		 *	"province": "山东",
+		 *	"country": "中国",
+		 *	"avatarUrl": "http://thirdwx.qlogo.cn/mmopen/vi_32/xqpCtHRBBmdlf201Fykhtx7P7JcicIbgV3Weic1oOvN6iaR3tEbuu74f2fkKQWXvzK3VDgNTZzgf0g8FqPvq8LCNQ/132",
+		 *	"unionId": "oYqy4wmMcs78x9P-tsyMeM3MQ1PU"
+		 */
+		loginByWxApp(userInfoData){
+			if(!this.agreement){
+				this.$util.msg('请阅读并同意用户服务及隐私协议');
+				return;
+			}
+			this.$util.throttle(async ()=>{
+				let [err, res] = await uni.login({
+					provider: 'weixin'
+				})
+				if(err){
+					console.log(err);
+					return;
+				}
+				uni.getUserInfo({
+					provider: 'weixin',
+					success: async res=>{
+						const response = await this.$request('user', 'loginByWeixin', {
+							userInfo: res.userInfo,
+						}, {
+							showLoading: true
+						});
+						if(response.status === 0){
+							this.$util.msg(response.msg);
+							return;
+						}
+						if(response.hasBindMobile && response.data.token){
+							this.loginSuccessCallBack({
+								token: response.data.token,
+								tokenExpired: response.data.tokenExpired
+							});
+						}else{
+							this.navTo('/pages/auth/bindMobile?data='+JSON.stringify(response.data))
+						}
+						plus.oauth.getServices(oauthRes=>{
+							oauthRes[0].logout(logoutRes => {
+								console.log(logoutRes);
+							}, error => {
+								console.log(error);
+							})
+						})
+					},
+					fail(err) {
+						console.log(err);
+					}
+				})
+			})
+		}
+	}
+	// #endif
+}
+
+
+
+
+
+
+
+
+

+ 83 - 0
yudao-vue-ui/pages/auth/mixin/login-apple.js

@@ -0,0 +1,83 @@
+export default{
+	onLoad() {
+		if(this.systemInfo.platform !== 'ios'){
+			return;
+		}
+		const systemVersion = +this.systemInfo.system.split('.')[0];
+		if(systemVersion >= 13){
+			this.canUseAppleLogin = true;
+		}
+	},
+	methods: {
+		//苹果登录
+		async loginByApple(){
+			/* if(!this.canUseAppleLogin){
+				this.$util.msg('系统版本过低,无法使用苹果登录');
+				return;
+			} */ 
+			if(!this.agreement){
+				this.$util.msg('请阅读并同意用户服务及隐私协议');
+				this.$refs.confirmBtn.stop();
+				return;
+			}
+			uni.login({  
+			    provider: 'apple',  
+			    success: loginRes=> {  
+			        // 登录成功   
+			        uni.getUserInfo({  
+			            provider: 'apple',  
+			            success: async userRes=> {
+							console.log(userRes);
+							const response = await this.$request('user', 'loginByApple', {
+								authorizationCode: userRes.userInfo.authorizationCode,
+								identityToken: userRes.userInfo.identityToken
+							}, {
+								showLoading: true
+							});
+							console.log(response);
+							//注销苹果登录
+							this.appleLogout();
+							console.log(response);
+							if(response.status === 0){
+								this.$util.msg(response.msg);
+								return;
+							}
+							if(response.hasBindMobile && response.data.token){
+								this.loginSuccessCallBack({
+									token: response.data.token,
+									tokenExpired: response.data.tokenExpired
+								});
+							}else{
+								this.navTo('/pages/auth/bindMobile?type=apple&data='+JSON.stringify(response.data))
+							}
+			            }  
+			        })  
+			    },  
+			    fail: err=> {  
+					console.log(err);
+					this.$util.msg('登录失败');
+					this.appleLogout();
+			    }  
+			})
+		},
+		appleLogout(){
+			plus.oauth.getServices(oauthRes=>{
+				const oIndex = oauthRes.findIndex(oItem=> oItem.id === 'apple');
+				oauthRes[oIndex].logout(loRes => {
+					console.log('appleLogout success=> ', loRes);
+				}, loErr => {
+					console.log('appleLogout error=> ', loErr);
+				})
+			})
+		}
+	}
+}
+
+
+
+
+
+
+
+
+

+ 81 - 0
yudao-vue-ui/pages/auth/mixin/login-mp-wx.js

@@ -0,0 +1,81 @@
+export default{
+	// #ifdef MP-WEIXIN
+	data(){
+		return {
+			mpCodeTimer: 0,
+			mpWxCode: '',
+		}
+	},
+	computed: {
+		timerIdent(){
+			return this.$store.state.timerIdent;
+		}
+	},
+	watch: {
+		timerIdent(){
+			this.mpCodeTimer ++;
+			if(this.mpCodeTimer % 30 === 0){
+				this.getMpWxCode();
+			}
+		}
+	},
+	onShow(){
+		this.getMpWxCode();
+	},
+	methods: {
+		//微信小程序登录
+		mpWxGetUserInfo(){
+			if(!this.agreement){
+				this.$util.msg('请阅读并同意用户服务及隐私协议');
+				return;
+			}
+			
+			this.$util.throttle(()=>{
+				uni.getUserProfile({
+					desc: '用于展示您的头像及昵称',
+					success: async profileRes=> {
+						const res = await this.$request('user', 'loginByWeixin', {
+							code: this.mpWxCode,
+							...profileRes.userInfo
+						}, {
+							showLoading: true
+						});
+						if(res.status === 0){
+							this.$util.msg(res.msg);
+							return;
+						}
+						if(res.hasBindMobile && res.data.token){
+							this.loginSuccessCallBack({
+								token: res.data.token,
+								tokenExpired: res.data.tokenExpired
+							});
+						}else{
+							this.navTo('/pages/auth/bindMobile?data='+JSON.stringify(res.data))
+						}
+						console.log(res)
+					}
+				})
+			})
+		},
+		//获取code
+		getMpWxCode(){
+			uni.login({
+				provider: 'weixin',
+				success: res=> {
+					this.mpWxCode = res.code;
+				}
+			})
+		},
+		
+	}
+	// #endif
+}
+
+
+
+
+
+
+
+
+

+ 248 - 0
yudao-vue-ui/pages/set/userInfo.vue

@@ -0,0 +1,248 @@
+<template>
+	<view class="app">
+		<view class="cell">
+			<text class="tit fill">头像</text>
+			<view class="avatar-wrap" @click="chooseImage">
+				<image class="avatar" :src="tempAvatar || userInfo.avatar || '/static/icon/default-avatar.png'" mode="aspectFill"></image>
+				<!-- 进度遮盖 -->
+				<view 
+					class="progress center"
+					:class="{
+						'no-transtion': uploadProgress === 0,
+						show: uploadProgress != 100
+					}"
+					:style="{
+						width: uploadProgress + '%',
+						height: uploadProgress + '%',
+					}"
+				></view>
+			</view>
+		</view>
+		<view class="cell b-b">
+			<text class="tit fill">昵称</text>
+			<input class="input" v-model="userInfo.nickname" type="text" maxlength="8" placeholder="请输入昵称" placeholder-class="placeholder">
+		</view>
+		<view class="cell b-b">
+			<text class="tit fill">性别</text>
+			<view class="checkbox center" @click="changeGender(1)">
+				<text v-if="userInfo.gender == 1" class="mix-icon icon-xuanzhong"></text>
+				<text v-else class="mix-icon icon-yk_yuanquan"></text>
+				<text>男</text>
+			</view>
+			<view class="checkbox center" @click="changeGender(2)">
+				<text v-if="userInfo.gender == 2" class="mix-icon icon-xuanzhong"></text>
+				<text v-else class="mix-icon icon-yk_yuanquan"></text>
+				<text>女</text>
+			</view>
+		</view>
+		
+		<mix-button ref="confirmBtn" text="保存资料" marginTop="80rpx" @onConfirm="confirm"></mix-button>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				uploadProgress: 100, //头像上传进度
+				tempAvatar: '', 
+				userInfo: {},
+			}
+		},
+		computed: {
+			curUserInfo(){
+				return this.$store.state.userInfo
+			}
+		},
+		watch: {
+			curUserInfo(curUserInfo){
+				const {avatar, nickname, gender} = curUserInfo;
+				this.userInfo = {avatar, nickname, gender,};
+			}
+		},
+		onLoad() {
+			const {avatar, nickname, gender, anonymous} = this.curUserInfo;
+			this.userInfo = {avatar, nickname, gender};
+		},
+		methods: {
+			//提交修改
+			async confirm(){
+				const {uploadProgress, userInfo, curUserInfo} = this;
+				let isUpdate = false;
+				for(let key in userInfo){
+					if(userInfo[key] !== curUserInfo[key]){
+						isUpdate = true;
+						break;
+					}
+				}
+				if (isUpdate === false) {
+					this.$util.msg('信息未修改');
+					this.$refs.confirmBtn.stop();
+					return;
+				}
+				if (!userInfo.avatar) {
+					this.$util.msg('请上传头像');
+					this.$refs.confirmBtn.stop();
+					return;
+				}
+				if (uploadProgress !== 100) {
+					this.$util.msg('请等待头像上传完毕');
+					this.$refs.confirmBtn.stop();
+					return;
+				}
+				if (!userInfo.nickname) {
+					this.$util.msg('请输入您的昵称');
+					this.$refs.confirmBtn.stop();
+					return;
+				}
+				if (!userInfo.gender) {
+					this.$util.msg('请选择您的性别');
+					this.$refs.confirmBtn.stop();
+					return;
+				}
+				const res = await this.$request('user', 'update', userInfo);
+				this.$refs.confirmBtn.stop();
+				this.$util.msg(res.msg);
+				if(res.status === 1){
+					this.$store.dispatch('getUserInfo'); //刷新用户信息
+					setTimeout(()=>{
+						uni.navigateBack();
+					}, 1000)
+				}
+			},
+			//选择头像
+			chooseImage(){
+				uni.chooseImage({
+					count: 1,
+					success: res=> {
+						uni.navigateTo({
+							url: `./cutImage/cut?src=${res.tempFilePaths[0]}` 
+						});
+					}
+				});
+			}, 
+			//裁剪回调
+			async setAvatar(filePath){
+				this.tempAvatar = filePath;
+				this.uploadProgress = 0;
+				const result = await uniCloud.uploadFile({
+					filePath: filePath,
+					cloudPath: + new Date() + ('000000' + Math.floor(Math.random() * 999999)).slice(-6) + '.jpg',
+					onUploadProgress: e=> {
+						this.uploadProgress = Math.round(
+							(e.loaded * 100) / e.total
+						);
+					}
+				});
+				if(!result.fileID){
+					this.$util.msg('头像上传失败');
+					return;
+				}
+				if(typeof uniCloud.getTempFileURL === 'undefined'){
+					this.userInfo.avatar = result.fileID;
+				}else{
+					const tempFiles = await uniCloud.getTempFileURL({
+						fileList: [result.fileID]
+					})
+					const tempFile = tempFiles.fileList[0];
+					if(tempFile.download_url || tempFile.fileID){
+						this.userInfo.avatar = tempFile.download_url || tempFile.fileID;
+					}else{
+						this.$util.msg('头像上传失败');
+					}
+				}
+			},
+			//修改性别
+			changeGender(gender){
+				this.$set(this.userInfo, 'gender', gender)
+			}
+		}
+	}
+</script>
+
+<style scoped lang="scss">
+	.app{
+		padding-top: 16rpx;
+	}
+	.cell{
+		display: flex;
+		align-items: center;
+		min-height: 110rpx;
+		padding: 0 40rpx;
+		
+		&:first-child{
+			margin-bottom: 10rpx;
+		}
+		&:after{
+			left: 40rpx;
+			right: 40rpx;
+			border-color: #d8d8d8;
+		}
+		.tit{
+			font-size: 30rpx;
+			color: #333;
+		}
+		.avatar-wrap{
+			width: 120rpx;
+			height: 120rpx;
+			position: relative;
+			border-radius: 100rpx;
+			overflow: hidden;
+			
+			.avatar{
+				width: 100%;
+				height: 100%;
+				border-radius: 100rpx;
+			}
+			.progress{
+				position: absolute;
+				left: 50%;
+				top: 50%;
+				transform: translate(-50%, -50%);
+				width: 100rpx;
+				height: 100rpx;
+				box-shadow: rgba(0,0,0,.6) 0px 0px 0px 2005px;
+				border-radius: 100rpx;
+				transition: .5s;
+				opacity: 0;
+				
+				&.no-transtion{
+					transition: 0s;
+				}
+				&.show{
+					opacity: 1;
+				}
+			}
+		}
+		.input{
+			flex: 1;
+			text-align: right;
+			font-size: 28rpx;
+			color: #333;
+		}
+		switch{
+			margin: 0;
+			transform: scale(0.8) translateX(10rpx);
+			transform-origin: center right;
+		}
+		.tip{
+			margin-left: 20rpx;
+			font-size: 28rpx;
+			color: #999;
+		}
+		.checkbox{
+			padding: 12rpx 0 12rpx 40rpx;
+			font-size: 28rpx;
+			color: #333;
+			
+			.mix-icon{
+				margin-right: 12rpx;
+				font-size: 36rpx;
+				color: #ccc;
+			}
+			.icon-xuanzhong{
+				color: $base-color;
+			}
+		}
+	}
+</style>

+ 1 - 1
yudao-vue-ui/pages/tabbar/user.vue

@@ -59,7 +59,7 @@
 			<!-- <mix-list-cell icon="icon-shoucang_xuanzhongzhuangtai" iconColor="#54b4ef" title="我的收藏" @onClick="navTo('/pages/favorite/favorite', {login: true})"></mix-list-cell> -->
 			<!-- <mix-list-cell icon="icon-pinglun-copy" iconColor="#ee883b" title="意见反馈" @onClick="navTo('/pages/feedback/feedback', {login: true})"></mix-list-cell> -->
 			<!-- <mix-list-cell icon="icon-shezhi1" iconColor="#37b0fb" title="设置" border="" @onClick="navTo('/pages/set/set', {login: true})"></mix-list-cell> -->
-			<mix-list-cell icon="icon-bianji" iconColor="#5fcda2" title="个人信息" border="" @onClick="navTo('/pages/set/set', {login: true})"></mix-list-cell>
+			<mix-list-cell icon="icon-bianji" iconColor="#5fcda2" title="个人信息" border="" @onClick="navTo('/pages/set/userInfo', {login: true})"></mix-list-cell>
 			<mix-list-cell icon="icon-shezhi1" iconColor="#37b0fb" title="账号安全" border="" @onClick="navTo('/pages/set/set', {login: true})"></mix-list-cell>
 		</view>
 	</view>

+ 93 - 0
yudao-vue-ui/store/index.js

@@ -0,0 +1,93 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+// import {request} from '@/common/js/request'
+
+Vue.use(Vuex)
+
+const store = new Vuex.Store({
+	state: {
+		openExamine: false, // 是否开启审核状态。用于小程序、App 等审核时,关闭部分功能。TODO 芋艿:暂时没找到刷新的地方
+		token: '', // 用户身份 Token
+		userInfo: {}, // 用户基本信息
+		timerIdent: false, // 全局 1s 定时器,只在全局开启一个,所有需要定时执行的任务监听该值即可,无需额外开启 TODO 芋艿:需要看看
+		orderCount: {}, // 订单数量
+	},
+	getters: {
+		hasLogin(state){
+			return !!state.token;
+		}
+	},
+	mutations: {
+		//更新state数据
+		setStateAttr(state, param){
+			if(param instanceof Array){
+				for(let item of param){
+					state[item.key] = item.val;
+				}
+			}else{
+				state[param.key] = param.val;
+			}
+		},
+		//更新token
+		setToken(state, data){
+			const {token, tokenExpired} = data;
+			state.token = token;
+			uni.setStorageSync('uniIdToken', token);
+			uni.setStorageSync('tokenExpired', tokenExpired);
+			this.dispatch('getUserInfo'); //更新用户信息
+			this.dispatch('getCartCount');//更新购物车数量
+			uni.$emit('refreshCart');//刷新购物车
+			this.dispatch('getOrderCount'); //更新订单数量
+		},
+		// 退出登录
+		logout(state) {
+			state.token = '';
+			uni.removeStorageSync('uniIdToken');
+			this.dispatch('getCartCount');//更新购物车数量
+			uni.$emit('refreshCart');//刷新购物车
+			this.dispatch('getOrderCount'); //更新订单数量
+			setTimeout(()=>{
+				state.userInfo = {};
+			}, 1100)
+		},
+	},
+	actions: {
+		//更新用户信息
+		async getUserInfo({state, commit}){
+			const res = await request('user', 'get', {}, {
+				checkAuthInvalid: false
+			});
+			if(res.status === 1){
+				const userInfo = res.data;
+				commit('setStateAttr', {
+					key: 'userInfo',
+					val: userInfo
+				})
+			}
+		},
+		//更新用户订单数量
+		async getOrderCount({state, commit}){
+			let data = {
+				c0: 0,
+				c1: 0,
+				c2: 0,
+				c3: 0
+			}
+			if(state.token){
+				try {
+					const res = await request('order', 'getOrderCount');
+					data = res;
+				}catch (err){
+					console.error('更新用户订单数量 => ', err);
+				}
+			}
+			commit('setStateAttr', {
+				key: 'orderCount',
+				val: data
+			})
+		}
+	}
+}) 
+
+
+export default store