feat: 添加登录弹窗组件并优化导航栏返回逻辑

This commit is contained in:
傅光孟 2026-01-30 12:31:24 +08:00
parent 39236b6673
commit 74954007ae
9 changed files with 563 additions and 15 deletions

View File

@ -3,7 +3,7 @@
<!-- 导航栏 start --> <!-- 导航栏 start -->
<view class="custom-bav-bar"> <view class="custom-bav-bar">
<view class="left"> <view class="left">
<u-icon name="arrow-left" color="#fff" size="36rpx" @click="back" /> <u-icon name="arrow-left" color="#fff" size="36rpx" @click="handleBack" />
</view> </view>
<view class="center"> 海外先机 </view> <view class="center"> 海外先机 </view>
</view> </view>
@ -147,6 +147,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { ref } from "vue";
//
const handleBack = () => {
uni.navigateBack({
delta: 1,
});
};
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -3,7 +3,7 @@
<!-- 导航栏 start --> <!-- 导航栏 start -->
<view class="custom-bav-bar"> <view class="custom-bav-bar">
<view class="left"> <view class="left">
<u-icon name="arrow-left" color="#666" size="36rpx" @click="back" /> <u-icon name="arrow-left" color="#666" size="36rpx" @click="handleBack" />
</view> </view>
<view class="center"> 宏观知微 </view> <view class="center"> 宏观知微 </view>
</view> </view>
@ -75,6 +75,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { ref } from "vue";
//
const handleBack = () => {
uni.navigateBack({
delta: 1,
});
};
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@ -148,10 +155,10 @@ import { ref } from "vue";
width: 100%; width: 100%;
margin-bottom: 30rpx; margin-bottom: 30rpx;
padding-bottom: 30rpx; padding-bottom: 30rpx;
border-bottom: 2rpx solid #F3F3F5; border-bottom: 2rpx solid #f3f3f5;
box-sizing: border-box; box-sizing: border-box;
&:last-child{ &:last-child {
border-bottom: none; border-bottom: none;
} }

View File

@ -1,16 +1,34 @@
<template> <template>
<view class="footer"> <view class="footer">
<view class="logout">退出登录</view> <view class="logout" v-if="userStore.isLogin" @click="loginOut">退出登录</view>
</view> </view>
</template> </template>
<script setup lang='ts'> <script setup lang="ts">
import { ref } from 'vue'; import { useUserStore } from "@/stores/user";
import { doLogout } from "@/api";
import { Session } from "@/utils/storage";
const userStore = useUserStore();
// 退
function loginOut() {
uni.showModal({
title: "退出登录?",
content: "",
success: function (res) {
if (res.confirm) {
doLogout({
financialAccount: userStore.getUserInfos().phone,
});
Session.clear();
window.location.reload();
}
},
});
}
</script> </script>
<style scoped lang='scss'> <style scoped lang="scss">
.footer { .footer {
margin: 0 20rpx; margin: 0 20rpx;
padding: 20rpx 0; padding: 20rpx 0;
@ -31,4 +49,4 @@ import { ref } from 'vue';
} }
} }
} }
</style> </style>

View File

@ -4,7 +4,7 @@
<text class="date"> 数据更新时间{{ date }} </text> <text class="date"> 数据更新时间{{ date }} </text>
</view> </view>
<u-grid class="menu" :col="5" :border="false" align="center"> <u-grid class="menu" :col="5" :border="false" align="center">
<u-grid-item v-for="menu in menus"> <u-grid-item v-for="menu in menus" @click="goto(menu.path, menu.auth)">
<image :src="menu.icon" class="menu-icon"></image> <image :src="menu.icon" class="menu-icon"></image>
<text class="menu-text">{{ menu.name }}</text> <text class="menu-text">{{ menu.name }}</text>
</u-grid-item> </u-grid-item>
@ -20,30 +20,58 @@ import MENUICON2 from "@/assets/images/page/icon_2@2x.png";
import MENUICON3 from "@/assets/images/page/icon_3@2x.png"; import MENUICON3 from "@/assets/images/page/icon_3@2x.png";
import MENUICON4 from "@/assets/images/page/icon_4@2x.png"; import MENUICON4 from "@/assets/images/page/icon_4@2x.png";
import MENUICON5 from "@/assets/images/page/icon_5@2x.png"; import MENUICON5 from "@/assets/images/page/icon_5@2x.png";
import { useUserStore } from "@/stores/user";
const menus = reactive([ const menus = reactive([
{ {
name: "海外先机", name: "海外先机",
icon: MENUICON1, icon: MENUICON1,
auth: "menu1",
path: "/pages/foreign/index",
}, },
{ {
name: "编辑精选", name: "编辑精选",
icon: MENUICON2, icon: MENUICON2,
auth: "menu2",
path: "/pages/recommend/index",
}, },
{ {
name: "宏观知微", name: "宏观知微",
icon: MENUICON3, icon: MENUICON3,
auth: "menu3",
path: "/pages/macroscopic/index",
}, },
{ {
name: "热门行业", name: "热门行业",
icon: MENUICON4, icon: MENUICON4,
auth: "menu4",
path: "/pages/foreign/index",
}, },
{ {
name: "风口概念", name: "风口概念",
icon: MENUICON5, icon: MENUICON5,
auth: "menu5",
path: "/pages/foreign/index",
}, },
]); ]);
const date = computed(() => dayjs().format("YYYY-MM-DD")); const date = computed(() => dayjs().format("YYYY-MM-DD"));
const userStore = useUserStore();
const userInfos = computed(() => {
return userStore.getUserInfos();
});
const goto = (path: string, auth: string) => {
if (userInfos.value.auth.includes(auth)) {
uni.navigateTo({
url: path,
});
} else {
uni.showToast({
title: "无权限访问",
icon: "none",
});
}
};
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -0,0 +1,310 @@
<template>
<!-- <view class="loginPopup"> -->
<u-popup
class="loginPopup"
:show="props.show"
closeable
:mode="props.mode"
round="12"
:closeOnClickOverlay="false"
@close="handlePopupClose"
>
<view class="loginPopupContent">
<view class="loginTitle">登录后掌握更多优质财经内容</view>
<!-- 登录表单 -->
<view class="loginForm">
<u--form :model="state.loginForm" ref="uForm">
<u-form-item
style="padding: 0"
class="loginFormItem"
prop="userInfo.name"
borderBottom
ref="item1"
>
<u--input
class="loginFormInput"
placeholder="请输入您的手机号"
v-model="state.loginForm.phone"
border="none"
></u--input>
</u-form-item>
<u-form-item
class="loginFormItem"
prop="userInfo.name"
borderBottom
ref="item1"
>
<u--input
class="loginFormInput"
placeholder="请输入短信验证码"
v-model="state.loginForm.code"
border="none"
></u--input>
<view class="getCode" @click="captcha">{{ codeText }}</view>
</u-form-item>
</u--form>
<u-button class="loginFormBtn" text="登录" @click="Login"></u-button>
</view>
<!-- 用户协议 -->
<view class="tips"
>登录即代表已经阅读并同意
<view class="userAgreement">用户协议</view>
</view>
</view>
</u-popup>
<!-- </view> -->
</template>
<script setup lang="ts">
import { reactive, ref } from "vue";
import { getCaptcha, login } from "@/api";
import { validatePhoneNumber } from "@/utils/util";
import { Session } from "@/utils/storage";
import { useUserStore } from "@/stores/user";
import { storeToRefs } from "pinia";
const userStore = useUserStore();
const { userInfos } = storeToRefs(userStore);
const emit = defineEmits(["onSuccess", "onError", "onCancel"]);
const props = defineProps({
//
show: {
type: Boolean,
default: () => false,
},
mode: {
type: String,
default: () => "bottom",
},
});
const codeText = ref("获取验证码");
const isDisabled = ref(false);
const state = reactive({
loginForm: {
phone: "",
code: "",
},
});
//
const captcha = async () => {
if (!validatePhoneNumber(state.loginForm.phone)) {
return uni.showToast({
title: "请输入正确的手机号",
icon: "none",
});
} else {
const res = await getCaptcha({
phone: state.loginForm.phone,
});
if (res.code == 200) {
let countdown = 60;
if (!isDisabled.value) {
isDisabled.value = true;
codeText.value = `${countdown}秒后重新获取`;
const intervalId = setInterval(() => {
countdown--;
codeText.value = `${countdown}秒后重新获取`;
if (countdown <= 0) {
clearInterval(intervalId);
isDisabled.value = false;
codeText.value = "获取验证码";
}
}, 1000);
} else {
uni.showToast({
title: "请等待 60s 后重试",
icon: "error",
});
}
}
}
};
//
const handlePopupSuccessCallback = () => {
emit("handlePopupSuccessCallback");
};
//
const handlePopupErrorCallback = () => {
emit("handlePopupErrorCallback");
};
const { aplus_queue } = window;
//
const Login = async () => {
if (!validatePhoneNumber(state.loginForm.phone)) {
return uni.showToast({
title: "请输入正确的手机号",
icon: "none",
});
} else if (state.loginForm.code.length !== 6) {
return uni.showToast({
title: "请输入正确的验证码",
icon: "none",
});
}
userStore.onLogin(
{
...state.loginForm,
},
(result: any) => {
if (result.code === 200) {
emit("onSuccess");
} else {
emit("onError");
}
},
);
// console.log(code, "<=== code");
// if (code === 200) {
// aplus_queue.push({
// action: 'aplus.record',
// arguments: ['login', 'CLK', {
// phone: data.phone,
// }]
// });
// uni.hideLoading();
// Session.set("token", data.token);
// Session.set("userPhone", data.phone);
// uni.showToast({
// title: "",
// icon: "success",
// });
// handlePopupSuccessCallback();
// } else {
// console.log(msg, "<=== msg");
// handlePopupErrorCallback();
// uni.hideLoading();
// uni.showToast({
// title: msg,
// icon: "error",
// });
// }
// emit("handlePopupClose");
};
//
const handlePopupClose = () => {
emit("onCancel");
};
</script>
<style lang="scss" scoped>
.loginPopup {
::v-deep {
.u-slide-up-enter-active {
width: 100%;
height: 786rpx;
}
.u-popup__content__close {
margin-top: 30rpx;
}
}
.loginPopupContent {
box-sizing: border-box;
padding: 30rpx;
position: relative;
.loginTitle {
// font-size: 32rpx;
font-size: var(--h1-font-size);
font-weight: bold;
text-align: center;
box-sizing: border-box;
padding-top: 30rpx;
}
.loginForm {
width: 600rpx;
// border: 1px solid;
margin: 60rpx auto;
.loginFormItem {
width: 600rpx;
height: 100rpx;
border-radius: 20rpx;
background-color: #fff;
border: 2rpx solid #b9b9b9;
// padding: 0 !important;
&:nth-child(2) {
margin: 32rpx 0;
}
::v-deep {
.u-form-item__body {
padding: 0;
}
}
.loginFormInput {
width: 100%;
height: 100rpx !important;
box-sizing: border-box;
padding: 0 20px !important;
}
.getCode {
height: 100rpx;
box-sizing: border-box;
padding: 20px;
display: flex;
align-items: center;
font-size: var(--h1-font-size);
color: rgba(51, 51, 51, 0.6);
position: relative;
&::before {
content: "";
display: block;
width: 2rpx;
height: 52rpx;
background-color: rgba(51, 51, 51, 0.1);
position: absolute;
left: 0;
}
}
}
.loginFormBtn {
width: 600rpx;
height: 100rpx;
border-radius: 20rpx;
border: none;
background-color: #e7303f;
color: #fff;
::v-deep {
.u-button__text {
font-size: var(--h1-font-size) !important;
}
}
}
}
.tips {
// font-size: 24rpx;
font-size: var(--h4-font-size);
display: flex;
justify-content: center;
color: #717171;
.userAgreement {
color: #333;
}
}
}
}
</style>

View File

@ -26,8 +26,16 @@
<!-- 今日资讯 end --> <!-- 今日资讯 end -->
<!-- 尾部 start --> <!-- 尾部 start -->
<FooterView /> <FooterView />
<!-- 尾部 end --> <!-- 尾部 end -->
<!-- 登录弹窗 start -->
<LoginDialog
:show="LoginShow"
@onSuccess="handleLoginSuccess"
@onCancel="handleLoginCancel"
/>
<!-- 登录弹窗 end -->
</view> </view>
</template> </template>
@ -40,6 +48,47 @@ import HotNewsView from "./components/HotNewsView/index.vue";
import ConceptNewsView from "./components/ConceptNewsView/index.vue"; import ConceptNewsView from "./components/ConceptNewsView/index.vue";
import TodayNewsView from "./components/TodayNewsView/index.vue"; import TodayNewsView from "./components/TodayNewsView/index.vue";
import FooterView from "./components/FooterView/index.vue"; import FooterView from "./components/FooterView/index.vue";
import LoginDialog from "./components/LoginDialog/index.vue";
import { Session } from "@/utils/storage";
import { doLogout } from "@/api";
//
const LoginShow = ref(false);
const isLoginStatus = ref();
//
const handleLoginCancel = () => {
LoginShow.value = false;
};
//
const handleLoginSuccess = () => {
LoginShow.value = false;
};
//
const handlePopupErrorCallback = () => {
console.log("登录失败");
};
function loginOut() {
doLogout({
financialAccount: Session.get("userPhone"),
});
Session.clear();
window.location.reload();
}
onMounted(async () => {
if (!Session.get("token")) {
LoginShow.value = true;
}
const { aplus_queue } = window;
aplus_queue.push({
action: "aplus.sendPV",
arguments: [{ is_auto: false }], //
});
});
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -3,7 +3,7 @@
<!-- 导航栏 start --> <!-- 导航栏 start -->
<view class="custom-bav-bar"> <view class="custom-bav-bar">
<view class="left"> <view class="left">
<u-icon name="arrow-left" color="#fff" size="36rpx" @click="back" /> <u-icon name="arrow-left" color="#fff" size="36rpx" @click="handleBack" />
</view> </view>
<view class="center"> 编辑精选 </view> <view class="center"> 编辑精选 </view>
</view> </view>
@ -184,6 +184,13 @@ import { ref } from "vue";
const input = ref(""); const input = ref("");
const a = ref(true); const a = ref(true);
//
const handleBack = () => {
uni.navigateBack({
delta: 1,
});
};
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

118
src/stores/user/index.ts Normal file
View File

@ -0,0 +1,118 @@
import { login } from "@/api";
import { Session } from "@/utils/storage";
import { defineStore } from "pinia";
import { computed, ref } from "vue";
type IUserInfos = {
id?: string;
phone: string;
name?: string;
auth: string[];
role: string[];
};
const storeSetup = () => {
const token = ref(Session.get("token"));
const userInfos = ref<IUserInfos>({
phone: "",
name: "",
auth: [],
role: [],
});
const setUserInfos = (payload: Partial<IUserInfos>) => {
userInfos.value = { ...userInfos.value, ...payload };
Session.set("userInfos", userInfos.value);
};
const getUserInfos = () => {
if (Session.get("userInfos")) {
userInfos.value = Session.get("userInfos");
}
return userInfos.value;
};
// 设置 token
const setToken = (payload: string) => {
token.value = payload;
Session.set("token", payload);
};
// 获取 token
const getToken = () => {
return token.value;
};
// 登录状态
const isLogin = computed(() => {
return !!token.value;
});
// 登录
const onLogin = async (data: any, callback: Function) => {
uni.showLoading({
title: "登录中",
mask: true,
});
try {
const params = {
phone: data.phone,
smsCode: data.code,
};
// const result = await login(params);
// mock
const result = await new Promise((resolve) => {
setTimeout(() => {
resolve({
code: 200,
data: {
token: "123456",
phone: "123456",
auth: ["menu1", "menu2", "menu3"],
role: ["role1"],
},
});
}, 1000);
});
console.log("output >>>>> result", result.code);
uni.hideLoading();
if (result.code === 200) {
window.aplus_queue.push({
action: "aplus.record",
arguments: [
"login",
"CLK",
{
phone: data.phone,
},
],
});
setToken(result.data.token);
setUserInfos({ ...result.data });
uni.showToast({
title: "登录成功",
icon: "success",
});
} else {
uni.showToast({
title: result.msg,
icon: "error",
});
}
callback && callback(result);
} catch (error) {
uni.hideLoading();
console.log(error, "<=== error");
}
};
return { isLogin, userInfos, getToken, onLogin, getUserInfos };
};
export const useUserStore = defineStore("user", storeSetup);

4
src/types/global.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
declare interface Window {
aplus_queue: any
}