shish 2 недель назад
Родитель
Сommit
464c831d1b

+ 6 - 0
ghsPad/src/api/common/index.js

@@ -50,5 +50,11 @@ export const initSocket = async clientId => {
 //app登陆
 export const appLogin = data => https.get("/auth/app-login", data);
 
+// 发送登录验证码(与 ghsApp 同一套接口)
+export const sendLoginSms = data => https.post("/auth/send-login-sms", data);
+
+// 短信验证码登录(与 ghsApp 同一套接口)
+export const smsLogin = data => https.post("/auth/sms-login", data);
+
 //app重新获取登陆信息
 export const hasTokenAutoLogin = data => https.get("/admin/has-token-auto-login", data);

+ 233 - 60
ghsPad/src/pages/home/login.vue

@@ -1,20 +1,56 @@
+<!--
+  供货商收银机登录界面:支持密码登录与短信验证码登录
+  短信接口与 ghsApp admin/home/login 共用 /auth/send-login-sms、/auth/sms-login
+-->
 <template>
 	<view>
 		<view class="login-model">
 			<view class="experFrame">
 				<view>
 					<view class="logo-area">
-						<image :src="`${constant.imgUrl}/logo3.png`" class="logo" mode="widthFix"></image> 
+						<image :src="`${constant.imgUrl}/logo3.png`" class="logo" mode="widthFix"></image>
 					</view>
-					<view style="color:#666666;text-align:center;margin-bottom:10upx;font-size:20upx;">销花宝</view>
-					<view>
-						<input @focus="mobile=''" v-model="mobile" class="experInput" placeholder="手机号" type="number" />
-					</view>
-					<view>
-						<input @focus="password=''" v-model="password" class="experInput" placeholder="密码" type="password" />
-					</view>
-					<view style="margin-top:10upx;">
-						<button class="admin-button-com small blue buttonText" @click="loginFun">登陆</button>
+					<view class="brand-name">销花宝</view>
+
+					<block v-if="!isSmsLogin">
+						<view>
+							<input @focus="mobile=''" v-model="mobile" class="experInput" placeholder="手机号" type="number" />
+						</view>
+						<view>
+							<input @focus="password=''" v-model="password" class="experInput" placeholder="密码" type="password" />
+						</view>
+						<view class="login-btn-wrap">
+							<button class="admin-button-com small blue buttonText" @click="loginFun">登陆</button>
+						</view>
+					</block>
+
+					<block v-else>
+						<view>
+							<input @focus="mobile=''" v-model="mobile" class="experInput" placeholder="手机号" type="number" />
+						</view>
+						<view class="sms-code-row">
+							<input
+								@focus="smsCode=''"
+								v-model="smsCode"
+								class="experInput experInput--code"
+								placeholder="验证码"
+								type="number"
+							/>
+							<button
+								class="sms-code-btn"
+								:disabled="smsCountdown > 0"
+								@click="getSmsCode"
+							>
+								{{ smsCountdown > 0 ? smsCountdown + 's' : '获取验证码' }}
+							</button>
+						</view>
+						<view class="login-btn-wrap">
+							<button class="admin-button-com small blue buttonText" @click="smsLoginFun">登陆</button>
+						</view>
+					</block>
+
+					<view class="login-mode-switch" @click="toggleLoginMode">
+						{{ isSmsLogin ? '密码登录' : '短信登录' }}
 					</view>
 				</view>
 			</view>
@@ -23,67 +59,150 @@
 </template>
 <script>
 import { mapActions } from "vuex";
-import { getUser} from "@/utils/auth";
-import {appLogin} from '@/api/common'
-import { getDictionaries } from '@/api/mini'
+import { getUser } from "@/utils/auth";
+import { appLogin, sendLoginSms, smsLogin } from "@/api/common";
+import { getDictionaries } from "@/api/mini";
+
 export default {
 	name: "login",
-	components: {},
-	mixins: [],
 	data() {
 		return {
-			mobile:'',
-			password: '',
-			hasMobileNet:false
-		}
-	},
-	computed: {
+			mobile: "",
+			password: "",
+			smsCode: "",
+			isSmsLogin: false,
+			smsCountdown: 0,
+			smsTimer: null,
+			hasMobileNet: false
+		};
 	},
-	onLoad(){
+	onLoad() {
+		this.initSmsCountdown();
 	},
-	onUnload(){
+	onUnload() {
+		if (this.smsTimer) {
+			clearInterval(this.smsTimer);
+			this.smsTimer = null;
+		}
 	},
 	methods: {
-		...mapActions(['setUserShopAll']),
-		loginFun(){
-			let that = this
-			let mobile = this.mobile
-			let password = this.password
-			if (that.$util.isEmpty(mobile)) {
-				this.$msg('请输入手机号')
-				return false
+		...mapActions(["setUserShopAll"]),
+		/** 切换密码 / 短信登录方式 */
+		toggleLoginMode() {
+			this.isSmsLogin = !this.isSmsLogin;
+			this.password = "";
+			this.smsCode = "";
+		},
+		/** 恢复短信验证码倒计时,防止刷新绕过 60 秒限制 */
+		initSmsCountdown() {
+			const lastSentTime = uni.getStorageSync("last_sms_sent_time_login");
+			if (lastSentTime) {
+				const timePassed = Math.floor((Date.now() - lastSentTime) / 1000);
+				if (timePassed < 60) {
+					this.startCountdown(60 - timePassed);
+				}
 			}
-			if(this.$util.checkMobile(mobile) == false){
-				this.$msg('请输入正确手机号')
-				return false
+		},
+		/** 启动验证码按钮倒计时 */
+		startCountdown(seconds) {
+			this.smsCountdown = seconds;
+			if (this.smsTimer) {
+				clearInterval(this.smsTimer);
 			}
-			if (that.$util.isEmpty(password)) {
-				this.$msg('请输入密码')
-				return false
+			this.smsTimer = setInterval(() => {
+				this.smsCountdown--;
+				if (this.smsCountdown <= 0) {
+					clearInterval(this.smsTimer);
+					this.smsTimer = null;
+				}
+			}, 1000);
+		},
+		/** 校验手机号 */
+		validateMobile() {
+			if (this.$util.isEmpty(this.mobile)) {
+				this.$msg("请输入手机号");
+				return false;
+			}
+			if (this.$util.checkMobile(this.mobile) == false) {
+				this.$msg("请输入正确手机号");
+				return false;
+			}
+			return true;
+		},
+		/** 获取短信验证码 */
+		getSmsCode() {
+			if (!this.validateMobile()) {
+				return false;
 			}
-			uni.showLoading({mask:true})
-			//有几个地方涉及登陆,需要统一方法,请搜索关键词 uni_login
-			appLogin({mobile,password}).then(subRes=>{
-				uni.hideLoading()
-				if(subRes.code == 1){
-					if (that.$util.isEmpty(subRes)) {
-						return false
-					}
-					uni.setStorageSync('token', subRes.data.token)
-					uni.setStorageSync('shopId', subRes.data.admin.currentShopId)
-					getDictionaries({ token: subRes.data.token }).then(res => {
-						if (that.$util.isEmpty(res)) {
-							return false
-						}
-						that.$store.commit('setDictionariesInfo', { ...res.data.dict, ...res.data })
-						that.$store.commit('setMyShopInfo', { ...res.data.shop, ...res.data })
-					})
-					that.$store.commit('setLoginInfo', { ...subRes.data.admin, ...subRes.data })
-					getUser(true).then(res => { uni.setNavigationBarTitle({ title: res.name }) })
-					that.setUserShopAll()
+			uni.showLoading({ mask: true });
+			sendLoginSms({ mobile: this.mobile }).then(res => {
+				uni.hideLoading();
+				if (res.code == 1) {
+					this.$msg("验证码已发送");
+					uni.setStorageSync("last_sms_sent_time_login", Date.now());
+					this.startCountdown(60);
 				}
-			})
-
+			}).catch(() => {
+				uni.hideLoading();
+			});
+		},
+		/** 登录成功后写入 token 并初始化店铺信息 */
+		handleLoginSuccess(subRes) {
+			const that = this;
+			if (that.$util.isEmpty(subRes)) {
+				return false;
+			}
+			uni.setStorageSync("token", subRes.data.token);
+			uni.setStorageSync("shopId", subRes.data.admin.currentShopId);
+			getDictionaries({ token: subRes.data.token }).then(res => {
+				if (that.$util.isEmpty(res)) {
+					return false;
+				}
+				that.$store.commit("setDictionariesInfo", { ...res.data.dict, ...res.data });
+				that.$store.commit("setMyShopInfo", { ...res.data.shop, ...res.data });
+			});
+			that.$store.commit("setLoginInfo", { ...subRes.data.admin, ...subRes.data });
+			getUser(true).then(res => {
+				uni.setNavigationBarTitle({ title: res.name });
+			});
+			that.setUserShopAll();
+		},
+		loginFun() {
+			if (!this.validateMobile()) {
+				return false;
+			}
+			if (this.$util.isEmpty(this.password)) {
+				this.$msg("请输入密码");
+				return false;
+			}
+			uni.showLoading({ mask: true });
+			appLogin({ mobile: this.mobile, password: this.password }).then(subRes => {
+				uni.hideLoading();
+				if (subRes.code == 1) {
+					this.handleLoginSuccess(subRes);
+				}
+			});
+		},
+		/** 短信验证码登录 */
+		smsLoginFun() {
+			if (!this.validateMobile()) {
+				return false;
+			}
+			if (this.$util.isEmpty(this.smsCode)) {
+				this.$msg("请输入验证码");
+				return false;
+			}
+			uni.showLoading({ mask: true });
+			smsLogin({
+				mobile: this.mobile,
+				code: this.smsCode,
+				deviceId: uni.getStorageSync("deviceId")
+			}).then(subRes => {
+				uni.hideLoading();
+				if (subRes.code == 1) {
+					this.handleLoginSuccess(subRes);
+				}
+			});
 		}
 	}
 };
@@ -143,6 +262,10 @@ export default {
         box-sizing: border-box;
         text-align: left;
       }
+      .experInput--code {
+        width: 120upx;
+        margin-bottom: 0;
+      }
       .buttonText{
         display: inline-block;
         line-height:40upx;
@@ -166,4 +289,54 @@ export default {
 	  }
   }
 }
-</style>
+
+.brand-name {
+  color: #666666;
+  text-align: center;
+  margin-bottom: 10upx;
+  font-size: 20upx;
+}
+
+.login-btn-wrap {
+  margin-top: 10upx;
+}
+
+.sms-code-row {
+  width: 230upx;
+  margin: 0 auto 14upx;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.sms-code-btn {
+  flex-shrink: 0;
+  margin-left: 8upx;
+  min-width: 98upx;
+  height: 35upx;
+  line-height: 35upx;
+  padding: 0 8upx;
+  font-size: 18upx;
+  color: #ffffff;
+  background: #3385ff;
+  border: none;
+  border-radius: 4upx;
+}
+
+.sms-code-btn[disabled] {
+  color: #999999;
+  background: #e8e8e8;
+}
+
+.sms-code-btn::after {
+  border: none;
+}
+
+.login-mode-switch {
+  margin-top: 12upx;
+  font-size: 20upx;
+  color: #3385ff;
+  text-align: center;
+}
+</style>

+ 6 - 0
hdPad/src/api/common/index.js

@@ -50,5 +50,11 @@ export const initSocket = async clientId => {
 //app登陆
 export const appLogin = data => https.get("/auth/app-login", data);
 
+// 发送登录验证码(与 hdApp 同一套接口)
+export const sendLoginSms = data => https.post("/auth/send-login-sms", data);
+
+// 短信验证码登录(与 hdApp 同一套接口)
+export const smsLogin = data => https.post("/auth/sms-login", data);
+
 //app重新获取登陆信息
 export const hasTokenAutoLogin = data => https.get("/admin/has-token-auto-login", data);

+ 233 - 60
hdPad/src/pages/home/login.vue

@@ -1,20 +1,56 @@
+<!--
+  花店收银机登录界面:支持密码登录与短信验证码登录
+  短信接口与 hdApp admin/home/login 共用 /auth/send-login-sms、/auth/sms-login
+-->
 <template>
 	<view>
 		<view class="login-model">
 			<view class="experFrame">
 				<view>
 					<view class="logo-area">
-						<image :src="`${constant.imgUrl}/logo4.png`" class="logo" mode="widthFix"></image> 
+						<image :src="`${constant.imgUrl}/logo4.png`" class="logo" mode="widthFix"></image>
 					</view>
-					<view style="color:#666666;text-align:center;margin-bottom:10upx;font-size:20upx;">花掌柜</view>
-					<view>
-						<input @focus="mobile=''" v-model="mobile" class="experInput" placeholder="手机号" type="number" />
-					</view>
-					<view>
-						<input @focus="password=''" v-model="password" class="experInput" placeholder="密码" type="password" />
-					</view>
-					<view style="margin-top:10upx;">
-						<button class="admin-button-com small blue buttonText" @click="loginFun">登陆</button>
+					<view class="brand-name">花掌柜</view>
+
+					<block v-if="!isSmsLogin">
+						<view>
+							<input @focus="mobile=''" v-model="mobile" class="experInput" placeholder="手机号" type="number" />
+						</view>
+						<view>
+							<input @focus="password=''" v-model="password" class="experInput" placeholder="密码" type="password" />
+						</view>
+						<view class="login-btn-wrap">
+							<button class="admin-button-com small blue buttonText" @click="loginFun">登录</button>
+						</view>
+					</block>
+
+					<block v-else>
+						<view>
+							<input @focus="mobile=''" v-model="mobile" class="experInput" placeholder="手机号" type="number" />
+						</view>
+						<view class="sms-code-row">
+							<input
+								@focus="smsCode=''"
+								v-model="smsCode"
+								class="experInput experInput--code"
+								placeholder="验证码"
+								type="number"
+							/>
+							<button
+								class="sms-code-btn"
+								:disabled="smsCountdown > 0"
+								@click="getSmsCode"
+							>
+								{{ smsCountdown > 0 ? smsCountdown + 's' : '获取验证码' }}
+							</button>
+						</view>
+						<view class="login-btn-wrap">
+							<button class="admin-button-com small blue buttonText" @click="smsLoginFun">登录</button>
+						</view>
+					</block>
+
+					<view class="login-mode-switch" @click="toggleLoginMode">
+						{{ isSmsLogin ? '密码登录' : '短信登录' }}
 					</view>
 				</view>
 			</view>
@@ -23,67 +59,150 @@
 </template>
 <script>
 import { mapActions } from "vuex";
-import { getUser} from "@/utils/auth";
-import {appLogin} from '@/api/common'
-import { getDictionaries } from '@/api/mini'
+import { getUser } from "@/utils/auth";
+import { appLogin, sendLoginSms, smsLogin } from "@/api/common";
+import { getDictionaries } from "@/api/mini";
+
 export default {
 	name: "login",
-	components: {},
-	mixins: [],
 	data() {
 		return {
-			mobile:'',
-			password: '',
-			hasMobileNet:false
-		}
-	},
-	computed: {
+			mobile: "",
+			password: "",
+			smsCode: "",
+			isSmsLogin: false,
+			smsCountdown: 0,
+			smsTimer: null,
+			hasMobileNet: false
+		};
 	},
-	onLoad(){
+	onLoad() {
+		this.initSmsCountdown();
 	},
-	onUnload(){
+	onUnload() {
+		if (this.smsTimer) {
+			clearInterval(this.smsTimer);
+			this.smsTimer = null;
+		}
 	},
 	methods: {
-		...mapActions(['setUserShopAll']),
-		loginFun(){
-			let that = this
-			let mobile = this.mobile
-			let password = this.password
-			if (that.$util.isEmpty(mobile)) {
-				this.$msg('请输入手机号')
-				return false
+		...mapActions(["setUserShopAll"]),
+		/** 切换密码 / 短信登录方式 */
+		toggleLoginMode() {
+			this.isSmsLogin = !this.isSmsLogin;
+			this.password = "";
+			this.smsCode = "";
+		},
+		/** 恢复短信验证码倒计时,防止刷新绕过 60 秒限制 */
+		initSmsCountdown() {
+			const lastSentTime = uni.getStorageSync("last_sms_sent_time_login_hd");
+			if (lastSentTime) {
+				const timePassed = Math.floor((Date.now() - lastSentTime) / 1000);
+				if (timePassed < 60) {
+					this.startCountdown(60 - timePassed);
+				}
 			}
-			if(this.$util.checkMobile(mobile) == false){
-				this.$msg('请输入正确手机号')
-				return false
+		},
+		/** 启动验证码按钮倒计时 */
+		startCountdown(seconds) {
+			this.smsCountdown = seconds;
+			if (this.smsTimer) {
+				clearInterval(this.smsTimer);
 			}
-			if (that.$util.isEmpty(password)) {
-				this.$msg('请输入密码')
-				return false
+			this.smsTimer = setInterval(() => {
+				this.smsCountdown--;
+				if (this.smsCountdown <= 0) {
+					clearInterval(this.smsTimer);
+					this.smsTimer = null;
+				}
+			}, 1000);
+		},
+		/** 校验手机号 */
+		validateMobile() {
+			if (this.$util.isEmpty(this.mobile)) {
+				this.$msg("请输入手机号");
+				return false;
+			}
+			if (this.$util.checkMobile(this.mobile) == false) {
+				this.$msg("请输入正确手机号");
+				return false;
+			}
+			return true;
+		},
+		/** 获取短信验证码 */
+		getSmsCode() {
+			if (!this.validateMobile()) {
+				return false;
 			}
-			uni.showLoading({mask:true})
-			//有几个地方涉及登陆,需要统一方法,请搜索关键词 uni_login
-			appLogin({mobile,password}).then(subRes=>{
-				uni.hideLoading()
-				if(subRes.code == 1){
-					if (that.$util.isEmpty(subRes)) {
-						return false
-					}
-					uni.setStorageSync('token', subRes.data.token)
-					uni.setStorageSync('shopId', subRes.data.admin.currentShopId)
-					getDictionaries({ token: subRes.data.token }).then(res => {
-						if (that.$util.isEmpty(res)) {
-							return false
-						}
-						that.$store.commit('setDictionariesInfo', { ...res.data.dict, ...res.data })
-						that.$store.commit('setMyShopInfo', { ...res.data }) //...res.data.shop,
-					})
-					that.$store.commit('setLoginInfo', { ...subRes.data.admin, ...subRes.data })
-					getUser(true).then(res => { uni.setNavigationBarTitle({ title: res.name }) })
-					that.setUserShopAll()
+			uni.showLoading({ mask: true });
+			sendLoginSms({ mobile: this.mobile }).then(res => {
+				uni.hideLoading();
+				if (res.code == 1) {
+					this.$msg("验证码已发送");
+					uni.setStorageSync("last_sms_sent_time_login_hd", Date.now());
+					this.startCountdown(60);
 				}
-			})
-
+			}).catch(() => {
+				uni.hideLoading();
+			});
+		},
+		/** 登录成功后写入 token 并初始化店铺信息 */
+		handleLoginSuccess(subRes) {
+			const that = this;
+			if (that.$util.isEmpty(subRes)) {
+				return false;
+			}
+			uni.setStorageSync("token", subRes.data.token);
+			uni.setStorageSync("shopId", subRes.data.admin.currentShopId);
+			getDictionaries({ token: subRes.data.token }).then(res => {
+				if (that.$util.isEmpty(res)) {
+					return false;
+				}
+				that.$store.commit("setDictionariesInfo", { ...res.data.dict, ...res.data });
+				that.$store.commit("setMyShopInfo", { ...res.data });
+			});
+			that.$store.commit("setLoginInfo", { ...subRes.data.admin, ...subRes.data });
+			getUser(true).then(res => {
+				uni.setNavigationBarTitle({ title: res.name });
+			});
+			that.setUserShopAll();
+		},
+		loginFun() {
+			if (!this.validateMobile()) {
+				return false;
+			}
+			if (this.$util.isEmpty(this.password)) {
+				this.$msg("请输入密码");
+				return false;
+			}
+			uni.showLoading({ mask: true });
+			appLogin({ mobile: this.mobile, password: this.password }).then(subRes => {
+				uni.hideLoading();
+				if (subRes.code == 1) {
+					this.handleLoginSuccess(subRes);
+				}
+			});
+		},
+		/** 短信验证码登录 */
+		smsLoginFun() {
+			if (!this.validateMobile()) {
+				return false;
+			}
+			if (this.$util.isEmpty(this.smsCode)) {
+				this.$msg("请输入验证码");
+				return false;
+			}
+			uni.showLoading({ mask: true });
+			smsLogin({
+				mobile: this.mobile,
+				code: this.smsCode,
+				deviceId: uni.getStorageSync("deviceId")
+			}).then(subRes => {
+				uni.hideLoading();
+				if (subRes.code == 1) {
+					this.handleLoginSuccess(subRes);
+				}
+			});
 		}
 	}
 };
@@ -143,6 +262,10 @@ export default {
         box-sizing: border-box;
         text-align: left;
       }
+      .experInput--code {
+        width: 120upx;
+        margin-bottom: 0;
+      }
       .buttonText{
         display: inline-block;
         line-height:40upx;
@@ -166,4 +289,54 @@ export default {
 	  }
   }
 }
-</style>
+
+.brand-name {
+  color: #666666;
+  text-align: center;
+  margin-bottom: 10upx;
+  font-size: 20upx;
+}
+
+.login-btn-wrap {
+  margin-top: 10upx;
+}
+
+.sms-code-row {
+  width: 230upx;
+  margin: 0 auto 14upx;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.sms-code-btn {
+  flex-shrink: 0;
+  margin-left: 8upx;
+  min-width: 98upx;
+  height: 35upx;
+  line-height: 35upx;
+  padding: 0 8upx;
+  font-size: 18upx;
+  color: #ffffff;
+  background: #3385ff;
+  border: none;
+  border-radius: 4upx;
+}
+
+.sms-code-btn[disabled] {
+  color: #999999;
+  background: #e8e8e8;
+}
+
+.sms-code-btn::after {
+  border: none;
+}
+
+.login-mode-switch {
+  margin-top: 12upx;
+  font-size: 20upx;
+  color: #3385ff;
+  text-align: center;
+}
+</style>