diff --git a/yudao-vue-ui/common/js/util.js b/yudao-vue-ui/common/js/util.js
new file mode 100644
index 000000000..13ab716a9
--- /dev/null
+++ b/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'
+ });
+}
\ No newline at end of file
diff --git a/yudao-vue-ui/common/mixin/mixin.js b/yudao-vue-ui/common/mixin/mixin.js
new file mode 100644
index 000000000..701d70222
--- /dev/null
+++ b/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 芋艿:需要改成自己的
+ },
+}
\ No newline at end of file
diff --git a/yudao-vue-ui/main.js b/yudao-vue-ui/main.js
index afc6b0895..565d893ff 100644
--- a/yudao-vue-ui/main.js
+++ b/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
diff --git a/yudao-vue-ui/pages.json b/yudao-vue-ui/pages.json
index 1f30ba509..4e14ef627 100644
--- a/yudao-vue-ui/pages.json
+++ b/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",
diff --git a/yudao-vue-ui/pages/auth/login.vue b/yudao-vue-ui/pages/auth/login.vue
new file mode 100644
index 000000000..8667c0da1
--- /dev/null
+++ b/yudao-vue-ui/pages/auth/login.vue
@@ -0,0 +1,323 @@
+
+
+
+
+
+
+
+ 请认真阅读并同意
+ 《用户服务协议》
+ 《隐私权政策》
+
+
+ LOGIN
+
+ 欢迎回来!
+
+
+
+ 手机号码
+
+
+
+
+
+ 验证码
+
+
+
+
+
+
+
+
+
+
+
+ 快捷登录
+
+
+
+
+
+
+
+
+
+
+
+ Apple登录
+
+
+
+ 微信登录
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/yudao-vue-ui/pages/auth/mixin/login-app-wx.js b/yudao-vue-ui/pages/auth/mixin/login-app-wx.js
new file mode 100644
index 000000000..834ff87ac
--- /dev/null
+++ b/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
+}
+
+
+
+
+
+
+
+
+
diff --git a/yudao-vue-ui/pages/auth/mixin/login-apple.js b/yudao-vue-ui/pages/auth/mixin/login-apple.js
new file mode 100644
index 000000000..c0c98d345
--- /dev/null
+++ b/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);
+ })
+ })
+ }
+ }
+}
+
+
+
+
+
+
+
+
+
diff --git a/yudao-vue-ui/pages/auth/mixin/login-mp-wx.js b/yudao-vue-ui/pages/auth/mixin/login-mp-wx.js
new file mode 100644
index 000000000..1f4438a86
--- /dev/null
+++ b/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
+}
+
+
+
+
+
+
+
+
+
diff --git a/yudao-vue-ui/pages/set/userInfo.vue b/yudao-vue-ui/pages/set/userInfo.vue
new file mode 100644
index 000000000..89dcd33a3
--- /dev/null
+++ b/yudao-vue-ui/pages/set/userInfo.vue
@@ -0,0 +1,248 @@
+
+
+
+ 头像
+
+
+
+
+
+
+
+ 昵称
+
+
+
+ 性别
+
+
+
+ 男
+
+
+
+
+ 女
+
+
+
+
+
+
+
+
+
+
diff --git a/yudao-vue-ui/pages/tabbar/user.vue b/yudao-vue-ui/pages/tabbar/user.vue
index db0973537..8e72213bc 100644
--- a/yudao-vue-ui/pages/tabbar/user.vue
+++ b/yudao-vue-ui/pages/tabbar/user.vue
@@ -59,7 +59,7 @@
-
+
diff --git a/yudao-vue-ui/store/index.js b/yudao-vue-ui/store/index.js
new file mode 100644
index 000000000..afcddc646
--- /dev/null
+++ b/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