四零语境前端代码仓库
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.

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