login.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994
  1. <template>
  2. <view class="login-page">
  3. <!-- 状态栏占位 -->
  4. <view class="status-bar"></view>
  5. <!-- 现代化背景 -->
  6. <view class="modern-bg">
  7. <view class="bg-shape shape-1"></view>
  8. <view class="bg-shape shape-2"></view>
  9. <view class="bg-shape shape-3"></view>
  10. </view>
  11. <!-- 返回按钮 -->
  12. <view class="nav-back pad-t-20" @click="goBack">
  13. <view class="back-btn">
  14. <u-icon size="30" name="arrow-left"></u-icon>
  15. </view>
  16. </view>
  17. <!-- Logo和标题 -->
  18. <view class="header">
  19. <view class="logo-container">
  20. <view class="logo-circle">
  21. <image class="logo" src="/static/images/logo.png" mode="aspectFit"></image>
  22. </view>
  23. <view class="logo-shadow"></view>
  24. </view>
  25. <view class="title-section">
  26. <text class="title">知己</text>
  27. <view class="title-underline"></view>
  28. </view>
  29. <text class="subtitle">访客记录软件</text>
  30. </view>
  31. <!-- 登录按钮 -->
  32. <view class="login-section">
  33. <view class="btn-container">
  34. <button class="wechat-btn" @tap="handleWxLogin" :class="{ 'btn-loading': isLoading }">
  35. <u-icon name="weixin-fill" size="20" color="#fff" style="margin-right: 12rpx;"></u-icon>
  36. <text>授权快捷登录</text>
  37. </button>
  38. </view>
  39. <view class="divider">
  40. <view class="divider-line"></view>
  41. <text class="divider-text">或</text>
  42. <view class="divider-line"></view>
  43. </view>
  44. <view class="btn-container">
  45. <button class="phone-btn" @tap="handlePhoneLogin">
  46. <u-icon name="phone" size="20" color="#e28669" style="margin-right: 12rpx;"></u-icon>
  47. <text>手机号登录</text>
  48. </button>
  49. </view>
  50. <view class="btn-container">
  51. <button class="browse-btn" @tap="handleBrowse">
  52. <text>暂不登录,先去看看</text>
  53. </button>
  54. </view>
  55. <view class="agreement">
  56. <view class="checkbox-wrapper" @tap="toggleAgreement">
  57. <view class="zen-checkbox" :class="{ 'checked': isAgree }">
  58. <view class="zen-circle-inner" v-if="isAgree"></view>
  59. </view>
  60. </view>
  61. <text class="agreement-text">
  62. 登录即代表您已同意
  63. <text class="link" @tap="goToPrivacy">《隐私政策》</text>
  64. <text class="link" @tap="goToService">《服务协议》</text>
  65. </text>
  66. </view>
  67. </view>
  68. <!-- 底部装饰 -->
  69. <view class="bottom-decoration">
  70. <view class="company-info">
  71. <text class="company-name">知己访客记录系统</text>
  72. <text class="version">v1.0.0</text>
  73. </view>
  74. </view>
  75. <!-- 首次登录设置弹框 -->
  76. <u-popup :show="showProfileModal">
  77. <view class="profile-modal">
  78. <view class="modal-header">
  79. <text class="modal-title">完善个人信息</text>
  80. <text class="modal-subtitle">请设置您的账号和头像</text>
  81. </view>
  82. <view class="modal-content">
  83. <!-- 头像选择 -->
  84. <view class="avatar-section">
  85. <text class="section-label">头像</text>
  86. <button class="avatar-button" open-type="chooseAvatar" @chooseavatar="onChooseAvatar"
  87. :disabled="isUploadingAvatar">
  88. <view class="avatar-container">
  89. <image class="avatar-preview" :src="tempAvatar || '/static/images/avatar.png'"
  90. mode="aspectFill"></image>
  91. <view class="avatar-overlay">
  92. <u-icon v-if="!isUploadingAvatar" name="camera" size="24" color="#fff"></u-icon>
  93. <u-loading-icon v-else mode="spinner" size="24" color="#fff"></u-loading-icon>
  94. </view>
  95. </view>
  96. </button>
  97. </view>
  98. <!-- 账号输入 -->
  99. <view class="account-section">
  100. <text class="section-label">用户名</text>
  101. <u-input v-model="tempAccount" placeholder="请输入用户名" :maxlength="20" :clearable="true"
  102. border="surround"></u-input>
  103. </view>
  104. </view>
  105. <view class="modal-actions">
  106. <button class="confirm-btn" @tap="confirmProfile"
  107. :disabled="!tempAccount.trim() || isUploadingAvatar">
  108. <text v-if="!isUploadingAvatar">确认</text>
  109. <text v-else>头像上传中...</text>
  110. </button>
  111. </view>
  112. </view>
  113. </u-popup>
  114. </view>
  115. </template>
  116. <script>
  117. import {
  118. wxLogin,
  119. updateProfile
  120. } from '@/config/api.js';
  121. import {
  122. UPLOAD_URL
  123. } from '@/common/config.js';
  124. export default {
  125. data() {
  126. return {
  127. isAgree: false,
  128. isLoading: false,
  129. redirectUrl: '', // 登录成功后的重定向地址
  130. showProfileModal: false, // 显示个人信息设置弹框
  131. tempAccount: '', // 临时账号
  132. tempAvatar: '', // 临时头像
  133. loginData: null, // 临时保存登录数据
  134. isUploadingAvatar: false, // 头像上传状态
  135. // 默认用户信息
  136. defaultUser: {
  137. createBy: "admin",
  138. createTime: "2025-06-04 16:48:04",
  139. updateBy: null,
  140. updateTime: null,
  141. remark: "管理员",
  142. params: {
  143. "@type": "java.util.HashMap"
  144. },
  145. userId: 1,
  146. deptId: 100,
  147. userName: "admin",
  148. nickName: "知己集团",
  149. email: "ry@163.com",
  150. phonenumber: "15888888888",
  151. sex: "1",
  152. avatar: "/profile/avatar/2025/06/16/微信图片_202506101640343_20250616153504A012.jpg",
  153. password: "$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2",
  154. status: "0",
  155. delFlag: "0",
  156. loginIp: "107.182.190.58",
  157. loginDate: "2025-10-27T19:46:44.000+08:00",
  158. pwdUpdateDate: "2025-06-04T16:48:04.000+08:00",
  159. wxid: null,
  160. dept: {
  161. createBy: null,
  162. createTime: null,
  163. updateBy: null,
  164. updateTime: null,
  165. remark: null,
  166. params: {
  167. "@type": "java.util.HashMap"
  168. },
  169. deptId: 100,
  170. parentId: 0,
  171. ancestors: "0",
  172. deptName: "知己集团",
  173. orderNum: 0,
  174. leader: "贲智群",
  175. phone: null,
  176. email: null,
  177. status: "0",
  178. delFlag: null,
  179. parentName: null,
  180. children: []
  181. }
  182. }
  183. }
  184. },
  185. onLoad(options) {
  186. // 获取重定向地址
  187. if (options.redirect) {
  188. this.redirectUrl = decodeURIComponent(options.redirect);
  189. }
  190. },
  191. methods: {
  192. // 处理微信登录
  193. async handleWxLogin() {
  194. if (!this.isAgree) {
  195. let that = this
  196. // 弹出确认弹框询问用户是否同意协议
  197. uni.showModal({
  198. title: '提示',
  199. content: '登录需要您同意《隐私政策》和《服务协议》,是否同意?',
  200. success: function(res) {
  201. if (res.confirm) {
  202. // 用户点击同意,自动勾选协议并执行登录
  203. that.isAgree = true;
  204. that.executeLogin();
  205. } else if (res.cancel) {
  206. console.log('用户点击取消');
  207. }
  208. }
  209. });
  210. return;
  211. }
  212. this.executeLogin();
  213. },
  214. // 执行登录逻辑
  215. async executeLogin() {
  216. uni.showLoading({
  217. mask: true
  218. })
  219. this.isLoading = true;
  220. uni.getUserProfile({
  221. desc: '用于完善会员资料',
  222. success: (profileRes) => {
  223. uni.login({
  224. provider: 'weixin',
  225. success: async (loginRes) => {
  226. console.log(loginRes.code, "code")
  227. try {
  228. const loginData = await wxLogin({
  229. code: loginRes.code
  230. });
  231. console.log('登录响应:', loginData);
  232. // 检查登录是否成功:有 openid 或者 code === 200 都认为成功
  233. if (loginData.openid || loginData.code === 200 || loginData.message === "登录成功") {
  234. this.isLoading = false;
  235. // 如果 user 为 null,使用默认用户信息
  236. if (!loginData.user || loginData.user === null) {
  237. console.log('用户信息为空,使用默认用户信息');
  238. loginData.user = this.defaultUser;
  239. }
  240. // 保存 token(支持 token 和 access_token 两种字段)
  241. const token = loginData.token || loginData.access_token;
  242. if (token) {
  243. uni.setStorageSync('access_token', token);
  244. uni.setStorageSync('token', token);
  245. }
  246. // 保存 refresh_token
  247. if (loginData.refresh_token) {
  248. uni.setStorageSync('refresh_token', loginData.refresh_token);
  249. }
  250. // 保存完整的登录数据
  251. uni.setStorageSync('loginData', loginData);
  252. // 保存用户信息
  253. uni.setStorageSync('user', loginData.user);
  254. // 保存 openid
  255. if (loginData.openid) {
  256. uni.setStorageSync('openid', loginData.openid);
  257. }
  258. // 更新 store 状态
  259. this.$store.commit('isLogin', true);
  260. if (loginData.refresh_token) {
  261. this.$store.commit('refresh_token', loginData.refresh_token);
  262. }
  263. uni.hideLoading();
  264. // 显示登录成功提示
  265. uni.showToast({
  266. title: loginData.message || '登录成功',
  267. icon: 'success',
  268. duration: 1500
  269. });
  270. // 延迟跳转,让用户看到成功提示
  271. setTimeout(() => {
  272. uni.switchTab({
  273. url: '/pages/index/index'
  274. });
  275. }, 1500);
  276. } else {
  277. uni.showToast({
  278. title: loginData.msg || loginData.message || '登录失败',
  279. icon: 'none'
  280. });
  281. this.isLoading = false;
  282. uni.hideLoading();
  283. }
  284. } catch (error) {
  285. console.error('登录错误:', error);
  286. uni.showToast({
  287. title: error.message || '登录失败,请重试',
  288. icon: 'none'
  289. });
  290. this.isLoading = false;
  291. uni.hideLoading();
  292. }
  293. },
  294. fail: () => {
  295. this.isLoading = false;
  296. uni.hideLoading();
  297. }
  298. });
  299. },
  300. fail: (err) => {
  301. this.isLoading = false;
  302. if (err.errMsg.includes('cancel')) {
  303. return; // 用户取消,不显示提示
  304. }
  305. uni.showToast({
  306. title: '获取用户信息失败',
  307. icon: 'none'
  308. });
  309. }
  310. });
  311. },
  312. // 切换协议同意状态
  313. toggleAgreement() {
  314. this.isAgree = !this.isAgree;
  315. },
  316. // 返回上一页
  317. goBack() {
  318. uni.navigateBack();
  319. },
  320. // 跳转到隐私政策
  321. goToPrivacy() {
  322. uni.navigateTo({
  323. url: '/pagesA/public/richtext'
  324. });
  325. },
  326. // 跳转到服务协议
  327. goToService() {
  328. uni.navigateTo({
  329. url: '/pagesA/public/richtext'
  330. });
  331. },
  332. handleBrowse() {
  333. // 直接返回首页
  334. uni.switchTab({
  335. url: '/pages/index/index'
  336. });
  337. },
  338. // 跳转到手机登录页面
  339. handlePhoneLogin() {
  340. uni.navigateTo({
  341. url: '/pagesA/public/phone-login'
  342. });
  343. },
  344. // 保存登录数据并跳转
  345. saveLoginDataAndRedirect(loginData) {
  346. uni.setStorageSync('access_token', loginData.access_token);
  347. uni.setStorageSync('refresh_token', loginData.refresh_token);
  348. uni.setStorageSync('user', loginData);
  349. this.$store.commit('isLogin', true);
  350. this.$store.commit('refresh_token', loginData.refresh_token);
  351. uni.showToast({
  352. title: '登录成功',
  353. icon: 'success',
  354. duration: 1000
  355. });
  356. // 登录成功后的跳转
  357. setTimeout(() => {
  358. uni.switchTab({
  359. url: '/pages/index/index'
  360. });
  361. }, 1000);
  362. },
  363. // 微信小程序头像选择回调
  364. async onChooseAvatar(e) {
  365. console.log('选择头像:', e.detail.avatarUrl);
  366. // 显示上传状态
  367. this.isUploadingAvatar = true;
  368. uni.showLoading({
  369. title: '上传头像中...',
  370. mask: true
  371. });
  372. try {
  373. // 上传头像到服务器
  374. const uploadResult = await this.uploadFilePromise(e.detail.avatarUrl);
  375. if (uploadResult && uploadResult.data && uploadResult.data.link) {
  376. this.tempAvatar = uploadResult.data.link;
  377. uni.showToast({
  378. title: '头像上传成功',
  379. icon: 'success'
  380. });
  381. } else {
  382. throw new Error('上传失败');
  383. }
  384. } catch (error) {
  385. console.error('头像上传失败:', error);
  386. uni.showToast({
  387. title: '头像上传失败,请重试',
  388. icon: 'none'
  389. });
  390. } finally {
  391. this.isUploadingAvatar = false;
  392. uni.hideLoading();
  393. }
  394. },
  395. // 上传文件到服务器
  396. uploadFilePromise(filePath) {
  397. return new Promise((resolve, reject) => {
  398. // 优先使用登录数据中的token,如果没有则使用本地存储的token
  399. const token = (this.loginData && this.loginData.access_token) || uni.getStorageSync(
  400. 'access_token');
  401. if (!token) {
  402. reject(new Error('未找到访问令牌'));
  403. return;
  404. }
  405. uni.uploadFile({
  406. url: UPLOAD_URL,
  407. filePath: filePath,
  408. header: {
  409. "Blade-Auth": token
  410. },
  411. name: 'file',
  412. formData: {
  413. user: 'avatar'
  414. },
  415. success: (res) => {
  416. try {
  417. const result = JSON.parse(res.data);
  418. if (result.success || result.code === 200) {
  419. resolve(result);
  420. } else {
  421. reject(new Error(result.msg || '上传失败'));
  422. }
  423. } catch (e) {
  424. reject(new Error('解析响应失败'));
  425. }
  426. },
  427. fail: (error) => {
  428. reject(error);
  429. }
  430. });
  431. });
  432. },
  433. // 确认个人信息设置
  434. async confirmProfile() {
  435. if (!this.tempAccount.trim()) {
  436. uni.showToast({
  437. title: '请输入账号',
  438. icon: 'none'
  439. });
  440. return;
  441. }
  442. uni.showLoading({
  443. title: '保存中...',
  444. mask: true
  445. });
  446. try {
  447. // 调用更新用户信息接口
  448. const updateRes = await updateProfile({
  449. account: this.tempAccount.trim(),
  450. avatar: this.tempAvatar
  451. });
  452. if (updateRes.code == 200) {
  453. // 更新登录数据中的用户信息
  454. this.loginData.account = this.tempAccount.trim();
  455. if (this.tempAvatar) {
  456. this.loginData.avatar = this.tempAvatar;
  457. this.loginData.avatarUrl = this.tempAvatar; // 同时更新 avatarUrl 字段
  458. }
  459. // 更新本地存储的用户信息
  460. uni.setStorageSync('user', this.loginData);
  461. // 保存登录数据并跳转
  462. this.saveLoginDataAndRedirect(this.loginData);
  463. this.showProfileModal = false;
  464. } else {
  465. uni.showToast({
  466. title: updateRes.msg || '保存失败,请重试',
  467. icon: 'none'
  468. });
  469. }
  470. } catch (error) {
  471. console.error('更新用户信息错误:', error);
  472. uni.showToast({
  473. title: '保存失败,请重试',
  474. icon: 'none'
  475. });
  476. } finally {
  477. uni.hideLoading();
  478. }
  479. }
  480. }
  481. }
  482. </script>
  483. <style lang="scss" scoped>
  484. .login-page {
  485. min-height: 100vh;
  486. background: #F5F5F5;
  487. position: relative;
  488. overflow: hidden;
  489. }
  490. .status-bar {
  491. height: var(--status-bar-height);
  492. width: 100%;
  493. }
  494. // 现代化背景
  495. .modern-bg {
  496. position: absolute;
  497. top: 0;
  498. left: 0;
  499. width: 100%;
  500. height: 100%;
  501. pointer-events: none;
  502. z-index: 1;
  503. .bg-shape {
  504. position: absolute;
  505. border-radius: 50%;
  506. &.shape-1 {
  507. width: 300rpx;
  508. height: 300rpx;
  509. top: 15%;
  510. right: -80rpx;
  511. background: linear-gradient(135deg, rgba(255, 91, 5, 0.1) 0%, transparent 70%);
  512. }
  513. &.shape-2 {
  514. width: 200rpx;
  515. height: 200rpx;
  516. top: 50%;
  517. left: -60rpx;
  518. background: linear-gradient(135deg, rgba(41, 44, 53, 0.08) 0%, transparent 70%);
  519. }
  520. &.shape-3 {
  521. width: 150rpx;
  522. height: 150rpx;
  523. bottom: 25%;
  524. right: 15%;
  525. background: linear-gradient(135deg, rgba(255, 91, 5, 0.06) 0%, transparent 70%);
  526. }
  527. }
  528. }
  529. .nav-back {
  530. position: fixed;
  531. left: 30rpx;
  532. top: calc(var(--status-bar-height) + 20rpx);
  533. z-index: 100;
  534. .back-btn {
  535. width: 80rpx;
  536. height: 80rpx;
  537. background: #FFFFFF;
  538. border: 1rpx solid rgba(255, 91, 5, 0.2);
  539. border-radius: 50%;
  540. display: flex;
  541. align-items: center;
  542. justify-content: center;
  543. transition: all 0.3s ease;
  544. box-shadow: 0 4rpx 16rpx rgba(41, 44, 53, 0.1);
  545. &:active {
  546. transform: scale(0.95);
  547. background: rgba(255, 91, 5, 0.1);
  548. }
  549. }
  550. .back-icon {
  551. font-family: "iconfont";
  552. font-size: 36rpx;
  553. color: #292C35;
  554. }
  555. }
  556. .header {
  557. display: flex;
  558. flex-direction: column;
  559. align-items: center;
  560. padding-top: 120rpx;
  561. position: relative;
  562. z-index: 2;
  563. .logo-container {
  564. position: relative;
  565. margin-bottom: 60rpx;
  566. .logo-circle {
  567. width: 200rpx;
  568. height: 200rpx;
  569. border-radius: 20rpx;
  570. background: #FFFFFF;
  571. display: flex;
  572. align-items: center;
  573. justify-content: center;
  574. box-shadow: 0 8rpx 24rpx rgba(41, 44, 53, 0.1);
  575. border: 2rpx solid rgba(255, 91, 5, 0.2);
  576. .logo {
  577. width: 160rpx;
  578. height: 160rpx;
  579. border-radius: 16rpx;
  580. }
  581. }
  582. .logo-shadow {
  583. position: absolute;
  584. bottom: -20rpx;
  585. left: 50%;
  586. transform: translateX(-50%);
  587. width: 160rpx;
  588. height: 20rpx;
  589. background: radial-gradient(ellipse, rgba(255, 91, 5, 0.1) 0%, transparent 70%);
  590. border-radius: 50%;
  591. }
  592. }
  593. .title-section {
  594. text-align: center;
  595. margin-bottom: 20rpx;
  596. position: relative;
  597. .title {
  598. font-size: 64rpx;
  599. font-weight: 600;
  600. color: #292C35;
  601. letter-spacing: 8rpx;
  602. font-family: 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;
  603. text-shadow: 0 2rpx 8rpx rgba(41, 44, 53, 0.1);
  604. }
  605. .title-underline {
  606. position: absolute;
  607. bottom: -10rpx;
  608. left: 50%;
  609. transform: translateX(-50%);
  610. width: 80rpx;
  611. height: 4rpx;
  612. background: linear-gradient(90deg, transparent, #FF5B05, transparent);
  613. border-radius: 2rpx;
  614. }
  615. }
  616. .subtitle {
  617. font-size: 28rpx;
  618. color: rgba(41, 44, 53, 0.7);
  619. font-weight: 300;
  620. letter-spacing: 4rpx;
  621. font-family: inherit;
  622. }
  623. }
  624. .login-section {
  625. padding: 80rpx 60rpx;
  626. position: relative;
  627. z-index: 2;
  628. .btn-container {
  629. margin-bottom: 40rpx;
  630. }
  631. .wechat-btn {
  632. position: relative;
  633. display: flex;
  634. align-items: center;
  635. justify-content: center;
  636. width: 100%;
  637. height: 96rpx;
  638. background: linear-gradient(135deg, #FF5B05 0%, #FF8E3C 100%);
  639. border: none;
  640. border-radius: 48rpx;
  641. color: #fff;
  642. font-size: 32rpx;
  643. font-weight: 500;
  644. transition: all 0.3s ease;
  645. box-shadow: 0 4rpx 16rpx rgba(255, 91, 5, 0.3);
  646. &:active {
  647. transform: translateY(2rpx);
  648. box-shadow: 0 2rpx 8rpx rgba(255, 91, 5, 0.4);
  649. }
  650. &.btn-loading {
  651. background: linear-gradient(135deg, #FFB088 0%, #FFA573 100%);
  652. color: rgba(255, 255, 255, 0.8);
  653. }
  654. }
  655. .phone-btn {
  656. display: flex;
  657. align-items: center;
  658. justify-content: center;
  659. width: 100%;
  660. height: 96rpx;
  661. background: #FFFFFF;
  662. border: 2rpx solid #FF5B05;
  663. border-radius: 48rpx;
  664. color: #FF5B05;
  665. font-size: 32rpx;
  666. font-weight: 500;
  667. transition: all 0.3s ease;
  668. box-shadow: 0 4rpx 16rpx rgba(41, 44, 53, 0.1);
  669. &:active {
  670. transform: translateY(2rpx);
  671. background: rgba(255, 91, 5, 0.05);
  672. }
  673. }
  674. .divider {
  675. display: flex;
  676. align-items: center;
  677. justify-content: center;
  678. margin: 40rpx 0;
  679. .divider-line {
  680. flex: 1;
  681. height: 1rpx;
  682. background: linear-gradient(90deg, transparent, rgba(41, 44, 53, 0.1), transparent);
  683. }
  684. .divider-text {
  685. margin: 0 30rpx;
  686. font-size: 26rpx;
  687. color: rgba(41, 44, 53, 0.5);
  688. }
  689. }
  690. .browse-btn {
  691. display: flex;
  692. align-items: center;
  693. justify-content: center;
  694. width: 100%;
  695. height: 88rpx;
  696. background: transparent;
  697. border: 1rpx solid rgba(41, 44, 53, 0.2);
  698. border-radius: 44rpx;
  699. color: #292C35;
  700. font-size: 28rpx;
  701. transition: all 0.3s ease;
  702. &:active {
  703. background: rgba(41, 44, 53, 0.05);
  704. transform: translateY(1rpx);
  705. }
  706. }
  707. .agreement {
  708. display: flex;
  709. align-items: center;
  710. justify-content: center;
  711. margin-top: 60rpx;
  712. .checkbox-wrapper {
  713. margin-right: 16rpx;
  714. }
  715. .zen-checkbox {
  716. width: 36rpx;
  717. height: 36rpx;
  718. border: 2rpx solid rgba(255, 91, 5, 0.4);
  719. border-radius: 18rpx;
  720. display: flex;
  721. align-items: center;
  722. justify-content: center;
  723. transition: all 0.3s ease;
  724. background: rgba(255, 255, 255, 0.9);
  725. &.checked {
  726. background: #FF5B05;
  727. border-color: #FF5B05;
  728. }
  729. .zen-circle-inner {
  730. width: 16rpx;
  731. height: 16rpx;
  732. border-radius: 50%;
  733. background: #fff;
  734. }
  735. }
  736. .agreement-text {
  737. font-size: 24rpx;
  738. color: rgba(41, 44, 53, 0.6);
  739. line-height: 1.5;
  740. .link {
  741. color: #FF5B05;
  742. text-decoration: none;
  743. }
  744. }
  745. }
  746. }
  747. // 底部装饰
  748. .bottom-decoration {
  749. position: absolute;
  750. bottom: 40rpx;
  751. left: 50%;
  752. transform: translateX(-50%);
  753. z-index: 1;
  754. .company-info {
  755. text-align: center;
  756. .company-name {
  757. display: block;
  758. font-size: 24rpx;
  759. color: rgba(41, 44, 53, 0.6);
  760. margin-bottom: 8rpx;
  761. font-weight: 400;
  762. }
  763. .version {
  764. font-size: 20rpx;
  765. color: rgba(41, 44, 53, 0.4);
  766. font-weight: 300;
  767. }
  768. }
  769. }
  770. // 个人信息设置弹框样式
  771. .profile-modal {
  772. width: 750rpx;
  773. background: #fff;
  774. border-radius: 20rpx;
  775. overflow: hidden;
  776. .modal-header {
  777. text-align: center;
  778. padding: 60rpx 40rpx 40rpx;
  779. background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
  780. .modal-title {
  781. display: block;
  782. font-size: 36rpx;
  783. font-weight: 600;
  784. color: #2c3e50;
  785. margin-bottom: 12rpx;
  786. }
  787. .modal-subtitle {
  788. font-size: 26rpx;
  789. color: rgba(44, 62, 80, 0.7);
  790. }
  791. }
  792. .modal-content {
  793. padding: 40rpx;
  794. .avatar-section {
  795. margin-bottom: 40rpx;
  796. text-align: center;
  797. .section-label {
  798. display: block;
  799. font-size: 28rpx;
  800. color: #2c3e50;
  801. margin-bottom: 20rpx;
  802. font-weight: 500;
  803. }
  804. .avatar-button {
  805. background: none;
  806. border: none;
  807. padding: 0;
  808. margin: 0 auto;
  809. display: block;
  810. width: 160rpx;
  811. height: 160rpx;
  812. &::after {
  813. border: none;
  814. }
  815. &:disabled {
  816. opacity: 0.7;
  817. }
  818. }
  819. .avatar-container {
  820. position: relative;
  821. width: 160rpx;
  822. height: 160rpx;
  823. border-radius: 50%;
  824. overflow: hidden;
  825. border: 3rpx solid #4A90E2;
  826. .avatar-preview {
  827. width: 100%;
  828. height: 100%;
  829. }
  830. .avatar-overlay {
  831. position: absolute;
  832. bottom: 0;
  833. left: 0;
  834. right: 0;
  835. height: 50rpx;
  836. background: rgba(0, 0, 0, 0.5);
  837. display: flex;
  838. align-items: center;
  839. justify-content: center;
  840. }
  841. }
  842. }
  843. .account-section {
  844. .section-label {
  845. display: block;
  846. font-size: 28rpx;
  847. color: #2c3e50;
  848. margin-bottom: 20rpx;
  849. font-weight: 500;
  850. }
  851. }
  852. }
  853. .modal-actions {
  854. padding: 0 40rpx 40rpx;
  855. .confirm-btn {
  856. width: 100%;
  857. height: 80rpx;
  858. background: linear-gradient(135deg, #4A90E2 0%, #357ABD 100%);
  859. color: #fff;
  860. border-radius: 12rpx;
  861. font-size: 32rpx;
  862. font-weight: 500;
  863. border: none;
  864. transition: all 0.3s ease;
  865. &:disabled {
  866. background: #bdc3c7;
  867. color: rgba(255, 255, 255, 0.7);
  868. }
  869. &:not(:disabled):active {
  870. transform: translateY(2rpx);
  871. box-shadow: 0 2rpx 8rpx rgba(74, 144, 226, 0.3);
  872. }
  873. }
  874. }
  875. }
  876. </style>