hookehuyr

feat(登录): 添加登录提示组件并优化登录流程

引入登录提示弹窗组件,替换原有的直接错误提示,提升用户体验
在公共混入中添加登录状态检查和确保登录的方法
修改登录成功后跳转逻辑,支持重定向到原页面
更新产品详情页的下载和发送邮件功能,使用新的登录提示
调整环境配置中的代理目标地址
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
2 VITE_PORT = 8106 2 VITE_PORT = 8106
3 3
4 # 反向代理服务器地址 4 # 反向代理服务器地址
5 -VITE_PROXY_TARGET = https://www.hager-electric.com/ 5 +VITE_PROXY_TARGET = https://www.hager.cn/
6 # VITE_PROXY_TARGET = https://oa.onwall.cn 6 # VITE_PROXY_TARGET = https://oa.onwall.cn
7 7
8 # API请求前缀 8 # API请求前缀
......
...@@ -33,6 +33,7 @@ declare module 'vue' { ...@@ -33,6 +33,7 @@ declare module 'vue' {
33 HagerMenu: typeof import('./src/components/hagerMenu.vue')['default'] 33 HagerMenu: typeof import('./src/components/hagerMenu.vue')['default']
34 HagerMore: typeof import('./src/components/hagerMore.vue')['default'] 34 HagerMore: typeof import('./src/components/hagerMore.vue')['default']
35 HagerService: typeof import('./src/components/common/hagerService.vue')['default'] 35 HagerService: typeof import('./src/components/common/hagerService.vue')['default']
36 + LoginPrompt: typeof import('./src/components/common/loginPrompt.vue')['default']
36 Navbar: typeof import('./src/components/navbar.vue')['default'] 37 Navbar: typeof import('./src/components/navbar.vue')['default']
37 Privacy: typeof import('./src/components/privacy.vue')['default'] 38 Privacy: typeof import('./src/components/privacy.vue')['default']
38 RouterLink: typeof import('vue-router')['RouterLink'] 39 RouterLink: typeof import('vue-router')['RouterLink']
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
10 <hager-header @onShowMenu="onShowMenu"></hager-header> 10 <hager-header @onShowMenu="onShowMenu"></hager-header>
11 <router-view :class="[!isLoginPage ? 'wrapper' : '', is_xs ? 'xs' : '']"></router-view> 11 <router-view :class="[!isLoginPage ? 'wrapper' : '', is_xs ? 'xs' : '']"></router-view>
12 <hager-footer v-if="!isLoginPage" @send-openid="sendOpenid"></hager-footer> 12 <hager-footer v-if="!isLoginPage" @send-openid="sendOpenid"></hager-footer>
13 + <login-prompt></login-prompt>
13 <div v-if="showMask" class="mask"> 14 <div v-if="showMask" class="mask">
14 </div> 15 </div>
15 </div> 16 </div>
...@@ -18,6 +19,7 @@ ...@@ -18,6 +19,7 @@
18 <script> 19 <script>
19 import hagerHeader from '@/components/common/hagerHeader.vue'; 20 import hagerHeader from '@/components/common/hagerHeader.vue';
20 import hagerFooter from '@/components/common/hagerFooter.vue'; 21 import hagerFooter from '@/components/common/hagerFooter.vue';
22 +import loginPrompt from '@/components/common/loginPrompt.vue';
21 import mixin from '@/common/mixin'; 23 import mixin from '@/common/mixin';
22 import { wxInfo } from '@/utils/tools'; 24 import { wxInfo } from '@/utils/tools';
23 25
...@@ -30,7 +32,7 @@ export default { ...@@ -30,7 +32,7 @@ export default {
30 { name: 'description', content: '海格电气 构建未来电气世界 让人们的生活更安全、更清洁、更愉悦。' }, 32 { name: 'description', content: '海格电气 构建未来电气世界 让人们的生活更安全、更清洁、更愉悦。' },
31 ] 33 ]
32 }, 34 },
33 - components: { hagerHeader, hagerFooter }, 35 + components: { hagerHeader, hagerFooter, loginPrompt },
34 mixins: [mixin.init], 36 mixins: [mixin.init],
35 data () { 37 data () {
36 return { 38 return {
......
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
9 import $ from 'jquery'; 9 import $ from 'jquery';
10 import { throttle } from 'lodash-es'; 10 import { throttle } from 'lodash-es';
11 import axios from '@/utils/axios'; 11 import axios from '@/utils/axios';
12 +import { getUserInfoAPI } from '@/api/hager';
13 +import { openLoginPrompt } from '@/utils/loginPrompt';
12 14
13 export default { 15 export default {
14 // 初始化设置 16 // 初始化设置
...@@ -44,6 +46,41 @@ export default { ...@@ -44,6 +46,41 @@ export default {
44 this.top_img_height = $(window).width()*0.35 + 'px'; 46 this.top_img_height = $(window).width()*0.35 + 'px';
45 } 47 }
46 }, 48 },
49 + async getLoginStatus () {
50 + const res = await getUserInfoAPI();
51 + return Boolean(res && res.code && res.data);
52 + },
53 + goToLoginPage (redirect = this.$route.fullPath) {
54 + const query = redirect ? { redirect } : {};
55 + this.$router.push({
56 + path: '/user/login',
57 + query,
58 + });
59 + },
60 + async ensureLogin (options = {}) {
61 + const isLogin = await this.getLoginStatus();
62 +
63 + if (typeof this.is_login === 'boolean') {
64 + this.is_login = isLogin;
65 + }
66 +
67 + if (isLogin) {
68 + return true;
69 + }
70 +
71 + const shouldLogin = await openLoginPrompt({
72 + title: options.title || '登录后可下载资料',
73 + message: options.message || '当前下载操作需要先登录,是否现在前往登录页面?',
74 + confirmText: options.confirmText || '去登录',
75 + cancelText: options.cancelText || '稍后再说',
76 + });
77 +
78 + if (shouldLogin) {
79 + this.goToLoginPage(options.redirect || this.$route.fullPath);
80 + }
81 +
82 + return false;
83 + },
47 maEvent: throttle(function () { 84 maEvent: throttle(function () {
48 var p = Array.prototype.shift.call(arguments); 85 var p = Array.prototype.shift.call(arguments);
49 return axios.post('/srv/?a=log&p=' + p) 86 return axios.post('/srv/?a=log&p=' + p)
......
1 +<template>
2 + <div>
3 + <el-dialog
4 + v-if="!is_xs"
5 + :visible.sync="dialogVisible"
6 + :close-on-click-modal="false"
7 + :show-close="false"
8 + width="28rem"
9 + custom-class="login-confirm-dialog"
10 + @close="handleCancel">
11 + <div class="login-confirm-content">
12 + <div class="title">{{ prompt.title }}</div>
13 + <div class="message">{{ prompt.message }}</div>
14 + <div class="action-row">
15 + <div class="action-btn cancel" @click="handleCancel">{{ prompt.cancelText }}</div>
16 + <div class="action-btn confirm" @click="handleConfirm">{{ prompt.confirmText }}</div>
17 + </div>
18 + </div>
19 + </el-dialog>
20 +
21 + <transition v-else name="login-mobile-fade">
22 + <div
23 + v-if="drawerVisible"
24 + class="login-confirm-mobile-mask"
25 + @click.self="handleCancel">
26 + <div class="login-confirm-mobile-panel">
27 + <div class="login-confirm-content xs">
28 + <div class="title">{{ prompt.title }}</div>
29 + <div class="message">{{ prompt.message }}</div>
30 + <div class="action-column">
31 + <div class="action-btn confirm" @click="handleConfirm">{{ prompt.confirmText }}</div>
32 + <div class="action-btn cancel" @click="handleCancel">{{ prompt.cancelText }}</div>
33 + </div>
34 + </div>
35 + </div>
36 + </div>
37 + </transition>
38 + </div>
39 +</template>
40 +
41 +<script>
42 +import mixin from '@/common/mixin';
43 +import { closeLoginPrompt, loginPromptState } from '@/utils/loginPrompt';
44 +
45 +export default {
46 + name: 'LoginPrompt',
47 + mixins: [mixin.init],
48 + computed: {
49 + prompt () {
50 + return loginPromptState;
51 + },
52 + dialogVisible: {
53 + get () {
54 + return this.prompt.visible && !this.is_xs;
55 + },
56 + set (visible) {
57 + if (!visible) {
58 + this.handleCancel();
59 + }
60 + }
61 + },
62 + drawerVisible: {
63 + get () {
64 + return this.prompt.visible && this.is_xs;
65 + },
66 + set (visible) {
67 + if (!visible) {
68 + this.handleCancel();
69 + }
70 + }
71 + }
72 + },
73 + methods: {
74 + handleConfirm () {
75 + closeLoginPrompt(true);
76 + },
77 + handleCancel () {
78 + closeLoginPrompt(false);
79 + }
80 + }
81 +}
82 +</script>
83 +
84 +<style lang="less">
85 +.login-confirm-dialog {
86 + border-radius: 0.75rem;
87 +}
88 +
89 +.login-confirm-content {
90 + padding: 0.5rem 0;
91 + text-align: center;
92 +
93 + &.xs {
94 + padding: 1.5rem 1rem 1rem;
95 + }
96 +
97 + .title {
98 + font-size: 1.25rem;
99 + font-weight: bold;
100 + color: @primary-color;
101 + }
102 +
103 + .message {
104 + margin-top: 1rem;
105 + color: @text-color;
106 + line-height: 1.6;
107 + }
108 +
109 + .action-row,
110 + .action-column {
111 + margin-top: 1.5rem;
112 + display: flex;
113 + gap: 0.75rem;
114 + }
115 +
116 + .action-column {
117 + flex-direction: column;
118 + }
119 +
120 + .action-btn {
121 + flex: 1;
122 + padding: 0.75rem 1rem;
123 + border-radius: 0.5rem;
124 + border: 1px solid @primary-color;
125 + color: @primary-color;
126 + box-sizing: border-box;
127 +
128 + &:hover {
129 + cursor: pointer;
130 + }
131 +
132 + &.confirm {
133 + background-color: @primary-color;
134 + color: #fff;
135 + }
136 + }
137 +}
138 +
139 +.login-confirm-mobile-mask {
140 + position: fixed;
141 + inset: 0;
142 + z-index: 2008;
143 + display: flex;
144 + align-items: center;
145 + justify-content: center;
146 + padding: 1rem 0.75rem;
147 + background: rgba(0, 0, 0, 0.45);
148 + box-sizing: border-box;
149 +}
150 +
151 +.login-confirm-mobile-panel {
152 + width: 100%;
153 + max-width: 22rem;
154 + background: #fff;
155 + border-radius: 1rem;
156 + box-shadow: 0 0.75rem 2rem rgba(0, 0, 0, 0.16);
157 +}
158 +
159 +.login-mobile-fade-enter-active,
160 +.login-mobile-fade-leave-active {
161 + transition: opacity 0.24s ease;
162 +}
163 +
164 +.login-mobile-fade-enter-active .login-confirm-mobile-panel,
165 +.login-mobile-fade-leave-active .login-confirm-mobile-panel {
166 + transition: transform 0.24s ease;
167 +}
168 +
169 +.login-mobile-fade-enter,
170 +.login-mobile-fade-leave-to {
171 + opacity: 0;
172 +}
173 +
174 +.login-mobile-fade-enter .login-confirm-mobile-panel,
175 +.login-mobile-fade-leave-to .login-confirm-mobile-panel {
176 + transform: translateY(1rem) scale(0.96);
177 +}
178 +</style>
1 +import Vue from 'vue';
2 +
3 +const defaultOptions = {
4 + title: '登录提示',
5 + message: '当前操作需要先登录,是否前往登录页面?',
6 + confirmText: '去登录',
7 + cancelText: '暂不登录',
8 +};
9 +
10 +export const loginPromptState = Vue.observable({
11 + visible: false,
12 + title: defaultOptions.title,
13 + message: defaultOptions.message,
14 + confirmText: defaultOptions.confirmText,
15 + cancelText: defaultOptions.cancelText,
16 +});
17 +
18 +let resolver = null;
19 +
20 +const resetState = () => {
21 + loginPromptState.visible = false;
22 + loginPromptState.title = defaultOptions.title;
23 + loginPromptState.message = defaultOptions.message;
24 + loginPromptState.confirmText = defaultOptions.confirmText;
25 + loginPromptState.cancelText = defaultOptions.cancelText;
26 + resolver = null;
27 +};
28 +
29 +export const openLoginPrompt = (options = {}) => {
30 + if (resolver) {
31 + resolver(false);
32 + resetState();
33 + }
34 +
35 + loginPromptState.title = options.title || defaultOptions.title;
36 + loginPromptState.message = options.message || defaultOptions.message;
37 + loginPromptState.confirmText = options.confirmText || defaultOptions.confirmText;
38 + loginPromptState.cancelText = options.cancelText || defaultOptions.cancelText;
39 + loginPromptState.visible = true;
40 +
41 + return new Promise(resolve => {
42 + resolver = resolve;
43 + });
44 +};
45 +
46 +export const closeLoginPrompt = (confirmed = false) => {
47 + if (resolver) {
48 + resolver(confirmed);
49 + }
50 + resetState();
51 +};
...@@ -144,8 +144,8 @@ import mixin from 'common/mixin'; ...@@ -144,8 +144,8 @@ import mixin from 'common/mixin';
144 import hagerBox from '@/components/common/hagerBox'; 144 import hagerBox from '@/components/common/hagerBox';
145 import hagerCarousel from '@/components/hagerCarousel'; 145 import hagerCarousel from '@/components/hagerCarousel';
146 import hagerH1 from '@/components/common/hagerH1.vue'; 146 import hagerH1 from '@/components/common/hagerH1.vue';
147 -import { MessageBox, Message, Loading } from 'element-ui'; 147 +import { Message, Loading } from 'element-ui';
148 -import { getProductInfoAPI, getUserInfoAPI, downEmailAPI, downZipAPI } from "@/api/hager.js"; 148 +import { getProductInfoAPI, downEmailAPI, downZipAPI } from "@/api/hager.js";
149 149
150 export default { 150 export default {
151 // TAG:配置页面meta和标题信息 151 // TAG:配置页面meta和标题信息
...@@ -304,21 +304,11 @@ export default { ...@@ -304,21 +304,11 @@ export default {
304 }); 304 });
305 }, 305 },
306 async getUserInfo () { 306 async getUserInfo () {
307 - const { code, data } = await getUserInfoAPI(); 307 + this.is_login = await this.getLoginStatus();
308 - if (code) {
309 - if (data) {
310 - this.is_login = true; // 已登录
311 - } else {
312 - this.is_login = false; // 未登录
313 - }
314 - }
315 }, 308 },
316 async goToDownload () { 309 async goToDownload () {
317 - if (!this.is_login) { 310 + const canDownload = await this.ensureLogin();
318 - Message({ 311 + if (!canDownload) {
319 - type: 'error',
320 - message: '请先登录'
321 - });
322 return; 312 return;
323 } 313 }
324 if (this.is_download_checked) { 314 if (this.is_download_checked) {
...@@ -354,11 +344,8 @@ export default { ...@@ -354,11 +344,8 @@ export default {
354 item.checked_sum = item.list.filter(item => item.checked).length; 344 item.checked_sum = item.list.filter(item => item.checked).length;
355 }, 345 },
356 async sendEmail (item) { 346 async sendEmail (item) {
357 - if (!this.is_login) { 347 + const canDownload = await this.ensureLogin();
358 - Message({ 348 + if (!canDownload) {
359 - type: 'error',
360 - message: '请先登录'
361 - });
362 return; 349 return;
363 } 350 }
364 let loadingInstance = Loading.service({ fullscreen: true, background: 'rgba(0, 0, 0, 0.3)' }); 351 let loadingInstance = Loading.service({ fullscreen: true, background: 'rgba(0, 0, 0, 0.3)' });
......
...@@ -144,8 +144,8 @@ import mixin from 'common/mixin'; ...@@ -144,8 +144,8 @@ import mixin from 'common/mixin';
144 import hagerBox from '@/components/common/hagerBox'; 144 import hagerBox from '@/components/common/hagerBox';
145 import hagerCarousel from '@/components/hagerCarousel'; 145 import hagerCarousel from '@/components/hagerCarousel';
146 import hagerH1 from '@/components/common/hagerH1.vue'; 146 import hagerH1 from '@/components/common/hagerH1.vue';
147 -import { MessageBox, Message, Loading } from 'element-ui'; 147 +import { Message, Loading } from 'element-ui';
148 -import { getProductInfoAPI, getUserInfoAPI, downEmailAPI, downZipAPI } from "@/api/hager.js"; 148 +import { getProductInfoAPI, downEmailAPI, downZipAPI } from "@/api/hager.js";
149 149
150 export default { 150 export default {
151 // TAG:配置页面meta和标题信息 151 // TAG:配置页面meta和标题信息
...@@ -304,21 +304,11 @@ export default { ...@@ -304,21 +304,11 @@ export default {
304 }); 304 });
305 }, 305 },
306 async getUserInfo () { 306 async getUserInfo () {
307 - const { code, data } = await getUserInfoAPI(); 307 + this.is_login = await this.getLoginStatus();
308 - if (code) {
309 - if (data) {
310 - this.is_login = true; // 已登录
311 - } else {
312 - this.is_login = false; // 未登录
313 - }
314 - }
315 }, 308 },
316 async goToDownload () { 309 async goToDownload () {
317 - if (!this.is_login) { 310 + const canDownload = await this.ensureLogin();
318 - Message({ 311 + if (!canDownload) {
319 - type: 'error',
320 - message: '请先登录'
321 - });
322 return; 312 return;
323 } 313 }
324 if (this.is_download_checked) { 314 if (this.is_download_checked) {
...@@ -354,11 +344,8 @@ export default { ...@@ -354,11 +344,8 @@ export default {
354 item.checked_sum = item.list.filter(item => item.checked).length; 344 item.checked_sum = item.list.filter(item => item.checked).length;
355 }, 345 },
356 async sendEmail (item) { 346 async sendEmail (item) {
357 - if (!this.is_login) { 347 + const canDownload = await this.ensureLogin();
358 - Message({ 348 + if (!canDownload) {
359 - type: 'error',
360 - message: '请先登录'
361 - });
362 return; 349 return;
363 } 350 }
364 let loadingInstance = Loading.service({ fullscreen: true, background: 'rgba(0, 0, 0, 0.3)' }); 351 let loadingInstance = Loading.service({ fullscreen: true, background: 'rgba(0, 0, 0, 0.3)' });
......
...@@ -31,7 +31,7 @@ import mixin from 'common/mixin'; ...@@ -31,7 +31,7 @@ import mixin from 'common/mixin';
31 import hagerInput from '@/components/common/hagerInput.vue'; 31 import hagerInput from '@/components/common/hagerInput.vue';
32 import $ from 'jquery'; 32 import $ from 'jquery';
33 import { loginAPI } from '@/api/hager'; 33 import { loginAPI } from '@/api/hager';
34 -import { MessageBox, Message } from 'element-ui'; 34 +import { Message } from 'element-ui';
35 35
36 export default { 36 export default {
37 mixins: [mixin.init], 37 mixins: [mixin.init],
...@@ -77,7 +77,7 @@ export default { ...@@ -77,7 +77,7 @@ export default {
77 type: 'success', 77 type: 'success',
78 message: msg 78 message: msg
79 }); 79 });
80 - this.$router.push('/'); 80 + this.$router.push(this.$route.query.redirect || '/');
81 } 81 }
82 }, 82 },
83 goToRegister () { 83 goToRegister () {
......