四零语境前端代码仓库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

396 lines
9.6 KiB

1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
  1. <template>
  2. <view class="login-container">
  3. <!-- 背景图 -->
  4. <!-- <image class="bg-image" src="@/subPages/static/登录_背景图.png" mode="aspectFill"></image> -->
  5. <!-- 主要内容 -->
  6. <view class="content">
  7. <!-- Logo和标题区域 -->
  8. <view class="logo-section">
  9. <image class="logo" :src="configParamContent('login_logo')" mode="aspectFit"></image>
  10. <text class="title-text">{{ configParamContent('app_name') }}</text>
  11. </view>
  12. <!-- 登录按钮区域 -->
  13. <view class="login-section">
  14. <button class="login-btn" @click="handleLogin">
  15. 登录
  16. </button>
  17. <button class="guest-btn" @click="handleGuestLogin">
  18. 取消登录
  19. </button>
  20. <!-- 协议文本 -->
  21. <view class="agreement-text">
  22. <view class="agreement-content">
  23. <view class="checkbox-container" @click="toggleAgreement">
  24. <view class="checkbox" :class="{ 'checked': isAgreed }">
  25. <view class="checkbox-inner" v-if="isAgreed"></view>
  26. </view>
  27. </view>
  28. <text >我已阅读并同意
  29. <text class="link-text" @click="showServiceAgreement">服务协议</text>
  30. <text></text>
  31. <text class="link-text" @click="showPrivacyPolicy">隐私政策</text>
  32. </text>
  33. </view>
  34. </view>
  35. </view>
  36. </view>
  37. <!-- 用户协议和隐私政策弹窗 -->
  38. <uv-modal ref="serviceModal" title="《服务协议与隐私条款》" :show-cancel-button="false" confirm-text="我知道了" confirm-color="#06DADC" @confirm="isAgreed = true">
  39. <view class="privacy-content">
  40. <!-- 如果是富文本 -->
  41. <uv-parse :content="configParamContent('privacy_policy')"></uv-parse>
  42. </view>
  43. </uv-modal>
  44. <!-- 用户协议和隐私政策弹窗 -->
  45. <uv-modal ref="guideModal" title="《个人信息保护指引》" :show-cancel-button="false" confirm-text="我知道了" confirm-color="#06DADC" @confirm="isAgreed = true">
  46. <view class="privacy-content">
  47. <!-- 如果是富文本 -->
  48. <uv-parse :content="configParamContent('user_agreement')"></uv-parse>
  49. </view>
  50. </uv-modal>
  51. </view>
  52. </template>
  53. <script>
  54. export default {
  55. name: 'Login',
  56. data() {
  57. return {
  58. isAgreed: false
  59. }
  60. },
  61. mounted() {
  62. // #ifdef H5
  63. // H5环境下检查URL参数,处理微信授权回调
  64. this.checkWechatAuthCallback();
  65. // #endif
  66. },
  67. methods: {
  68. // 微信登录统一入口
  69. handleLogin() {
  70. if (!this.isAgreed) {
  71. this.$refs.serviceModal.open();
  72. this.$refs.guideModal.open();
  73. return
  74. }
  75. // #ifdef MP-WEIXIN
  76. // 小程序环境
  77. this.miniProgramLogin();
  78. // #endif
  79. // #ifdef H5
  80. // H5环境 - 微信公众号授权
  81. this.wechatOfficialLogin();
  82. // #endif
  83. },
  84. // 小程序登录
  85. miniProgramLogin() {
  86. uni.login({
  87. provider: 'weixin',
  88. success: async (res) => {
  89. console.log('小程序登录成功', res);
  90. await this.processLogin(res.code);
  91. },
  92. fail: (err) => {
  93. console.log('小程序登录失败', err);
  94. uni.showToast({
  95. title: '登录失败',
  96. icon: 'none'
  97. });
  98. }
  99. })
  100. },
  101. // 微信公众号授权登录
  102. wechatOfficialLogin() {
  103. console.log('开始微信公众号授权登录');
  104. // 构建微信授权URL
  105. const appId = this.configParamContent('official_appid') || 'your_wechat_appid'; // 从配置中获取AppID
  106. const redirectUri = encodeURIComponent(window.location.origin + window.location.pathname + '#/subPages/login/login');
  107. console.log('redirectUri:', window.location.origin + window.location.pathname + '#/subPages/login/login');
  108. const state = Date.now().toString();
  109. // 保存当前邀请人信息
  110. if (uni.getStorageSync('inviter')) {
  111. sessionStorage.setItem('temp_inviter', uni.getStorageSync('inviter'));
  112. }
  113. const authUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${redirectUri}&response_type=code&scope=snsapi_userinfo&state=${state}#wechat_redirect`;
  114. console.log('跳转到微信授权页面:', authUrl);
  115. window.location.href = authUrl;
  116. },
  117. // 检查微信授权回调
  118. checkWechatAuthCallback() {
  119. const code = this.getUrlParameter('code');
  120. const state = this.getUrlParameter('state');
  121. if (code && state) {
  122. console.log('检测到微信授权回调, code:', code, 'state:', state);
  123. // 恢复邀请人信息
  124. const tempInviter = sessionStorage.getItem('temp_inviter');
  125. if (tempInviter) {
  126. uni.setStorageSync('inviter', tempInviter);
  127. sessionStorage.removeItem('temp_inviter');
  128. }
  129. // 处理登录
  130. this.processLogin(code, 'official');
  131. }
  132. },
  133. // 统一登录处理
  134. async processLogin(code, type = 'miniprogram') {
  135. try {
  136. const loginParams = {
  137. code: code
  138. };
  139. // 添加邀请人信息
  140. if (uni.getStorageSync('inviter')) {
  141. loginParams.inviter = uni.getStorageSync('inviter');
  142. }
  143. // 添加登录类型参数
  144. if (type === 'official') {
  145. loginParams.type = 'official';
  146. }
  147. console.log('登录参数:', loginParams);
  148. const { result: loginRes } = await this.$api.login.login(loginParams);
  149. uni.setStorageSync('token', loginRes.token);
  150. const userInfo = loginRes.userInfo;
  151. // 存储用户信息到store
  152. this.$store.dispatch('updateUserInfo', userInfo);
  153. if (!userInfo.avatar || !userInfo.name || !userInfo.phone) {
  154. uni.navigateTo({
  155. url: '/subPages/login/userInfo'
  156. });
  157. return;
  158. } else {
  159. uni.showToast({
  160. title: '登录成功',
  161. icon: 'success'
  162. });
  163. uni.switchTab({
  164. url: '/pages/index/home'
  165. });
  166. }
  167. } catch (error) {
  168. console.error('登录失败:', error);
  169. uni.showToast({
  170. title: '登录失败,请重试',
  171. icon: 'none'
  172. });
  173. // #ifdef H5
  174. // H5环境下如果登录失败,清除URL参数并重新加载页面
  175. if (this.getUrlParameter('code')) {
  176. const cleanUrl = window.location.origin + window.location.pathname + window.location.hash.split('?')[0];
  177. window.history.replaceState({}, document.title, cleanUrl);
  178. }
  179. // #endif
  180. }
  181. },
  182. // 获取URL参数
  183. getUrlParameter(name) {
  184. // #ifdef H5
  185. const url = window.location.href;
  186. try {
  187. const urlParams = new URLSearchParams(window.location.search);
  188. return urlParams.get(name) || '';
  189. } catch (e) {
  190. // 兼容性处理
  191. const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)');
  192. const results = regex.exec(url);
  193. if (!results) return '';
  194. if (!results[2]) return '';
  195. return decodeURIComponent(results[2].replace(/\+/g, ' '));
  196. }
  197. // #endif
  198. // #ifndef H5
  199. return '';
  200. // #endif
  201. },
  202. // 游客登录
  203. handleGuestLogin() {
  204. console.log('暂不登录');
  205. // 跳转到主页
  206. uni.switchTab({
  207. url: '/pages/index/home'
  208. });
  209. },
  210. // 显示服务协议
  211. showServiceAgreement() {
  212. this.$refs.serviceModal.open();
  213. console.log('查看服务协议');
  214. // 这里可以跳转到协议页面或显示弹窗
  215. },
  216. // 显示隐私政策
  217. showPrivacyPolicy() {
  218. this.$refs.guideModal.open();
  219. console.log('查看隐私条款');
  220. // 这里可以跳转到隐私政策页面或显示弹窗
  221. },
  222. // 切换协议同意状态
  223. toggleAgreement() {
  224. this.isAgreed = !this.isAgreed;
  225. }
  226. }
  227. }
  228. </script>
  229. <style lang="scss" scoped>
  230. .login-container {
  231. position: relative;
  232. width: 100vw;
  233. height: 100vh;
  234. overflow: hidden;
  235. background: #E8FBFB;
  236. .bg-image {
  237. position: absolute;
  238. top: 0;
  239. left: 0;
  240. width: 100%;
  241. height: 100%;
  242. z-index: 1;
  243. }
  244. .content {
  245. position: relative;
  246. z-index: 2;
  247. height: 100%;
  248. display: flex;
  249. flex-direction: column;
  250. align-items: center;
  251. justify-content: center;
  252. padding: 0 60rpx;
  253. .logo-section {
  254. display: flex;
  255. flex-direction: column;
  256. align-items: center;
  257. margin-bottom: 120rpx;
  258. .logo {
  259. width: 120rpx;
  260. height: 120rpx;
  261. margin-bottom: 40rpx;
  262. }
  263. .title-text {
  264. font-size: 36rpx;
  265. font-weight: 600;
  266. color: $primary-text-color;
  267. text-align: center;
  268. }
  269. }
  270. .login-section {
  271. width: 100%;
  272. display: flex;
  273. flex-direction: column;
  274. align-items: center;
  275. .login-btn {
  276. width: 630rpx;
  277. height: 88rpx;
  278. margin-bottom: 30rpx;
  279. background-color: $primary-color;
  280. border: none;
  281. border-radius: 44rpx;
  282. color: white;
  283. font-size: 32rpx;
  284. font-weight: 500;
  285. display: flex;
  286. align-items: center;
  287. justify-content: center;
  288. }
  289. .guest-btn {
  290. width: 630rpx;
  291. height: 88rpx;
  292. margin-bottom: 60rpx;
  293. border: 2rpx solid $primary-color;
  294. border-radius: 44rpx;
  295. color: $primary-color;
  296. font-size: 32rpx;
  297. font-weight: 400;
  298. background-color: transparent;
  299. display: flex;
  300. align-items: center;
  301. justify-content: center;
  302. }
  303. .agreement-text {
  304. display: flex;
  305. align-items: center;
  306. justify-content: center;
  307. font-size: 24rpx;
  308. color: $secondary-text-color;
  309. line-height: 1.5;
  310. .checkbox-container {
  311. margin-right: 12rpx;
  312. cursor: pointer;
  313. .checkbox {
  314. width: 29rpx;
  315. height: 29rpx;
  316. border: 1rpx solid $secondary-text-color;
  317. border-radius: 50%;
  318. display: flex;
  319. align-items: center;
  320. justify-content: center;
  321. transition: all 0.3s ease;
  322. &.checked {
  323. border-color: $primary-color;
  324. background-color: $primary-color;
  325. }
  326. .checkbox-inner {
  327. width: 16rpx;
  328. height: 16rpx;
  329. background-color: white;
  330. border-radius: 50%;
  331. }
  332. }
  333. }
  334. .agreement-content {
  335. flex: 1;
  336. text-align: left;
  337. display: flex;
  338. .link-text {
  339. color: $primary-color;
  340. // text-decoration: underline;
  341. }
  342. }
  343. }
  344. }
  345. }
  346. }
  347. </style>