login.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926
  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. },
  137. onLoad(options) {
  138. // 获取重定向地址
  139. if (options.redirect) {
  140. this.redirectUrl = decodeURIComponent(options.redirect);
  141. }
  142. },
  143. methods: {
  144. // 处理微信登录
  145. async handleWxLogin() {
  146. if (!this.isAgree) {
  147. let that = this
  148. // 弹出确认弹框询问用户是否同意协议
  149. uni.showModal({
  150. title: '提示',
  151. content: '登录需要您同意《隐私政策》和《服务协议》,是否同意?',
  152. success: function(res) {
  153. if (res.confirm) {
  154. // 用户点击同意,自动勾选协议并执行登录
  155. that.isAgree = true;
  156. that.executeLogin();
  157. } else if (res.cancel) {
  158. console.log('用户点击取消');
  159. }
  160. }
  161. });
  162. return;
  163. }
  164. this.executeLogin();
  165. },
  166. // 执行登录逻辑
  167. async executeLogin() {
  168. uni.showLoading({
  169. mask: true
  170. })
  171. this.isLoading = true;
  172. uni.getUserProfile({
  173. desc: '用于完善会员资料',
  174. success: (profileRes) => {
  175. uni.login({
  176. provider: 'weixin',
  177. success: async (loginRes) => {
  178. try {
  179. const loginData = await wxLogin({
  180. code: loginRes.code,
  181. account: profileRes.userInfo.nickName,
  182. avatarUrl: profileRes.userInfo.avatarUrl,
  183. grant_type: 'wechat'
  184. });
  185. if (loginData.code === 200) {
  186. // 检查是否为首次登录
  187. if (loginData.isFirstLogin) {
  188. // 首次登录,显示个人信息设置弹框
  189. this.loginData = loginData;
  190. this.tempAccount = loginData.account || '';
  191. this.tempAvatar = loginData.avatar || '';
  192. this.showProfileModal = true;
  193. this.isLoading = false;
  194. uni.setStorageSync('access_token', loginData
  195. .access_token);
  196. uni.setStorageSync('refresh_token', loginData
  197. .refresh_token);
  198. uni.setStorageSync('user', loginData);
  199. this.$store.commit('isLogin', true);
  200. this.$store.commit('refresh_token', loginData
  201. .refresh_token);
  202. uni.hideLoading();
  203. } else {
  204. // 非首次登录,直接保存并跳转
  205. this.saveLoginDataAndRedirect(loginData);
  206. }
  207. } else {
  208. uni.showToast({
  209. title: loginData.msg || '登录失败',
  210. icon: 'none'
  211. });
  212. }
  213. } catch (e) {
  214. console.error('登录错误:', e);
  215. uni.showToast({
  216. title: '登录失败,请重试',
  217. icon: 'none'
  218. });
  219. } finally {
  220. this.isLoading = false;
  221. }
  222. },
  223. fail: () => {
  224. this.isLoading = false;
  225. }
  226. });
  227. },
  228. fail: (err) => {
  229. this.isLoading = false;
  230. if (err.errMsg.includes('cancel')) {
  231. return; // 用户取消,不显示提示
  232. }
  233. uni.showToast({
  234. title: '获取用户信息失败',
  235. icon: 'none'
  236. });
  237. }
  238. });
  239. },
  240. // 切换协议同意状态
  241. toggleAgreement() {
  242. this.isAgree = !this.isAgree;
  243. },
  244. // 返回上一页
  245. goBack() {
  246. uni.navigateBack();
  247. },
  248. // 跳转到隐私政策
  249. goToPrivacy() {
  250. uni.navigateTo({
  251. url: '/pagesA/public/richtext'
  252. });
  253. },
  254. // 跳转到服务协议
  255. goToService() {
  256. uni.navigateTo({
  257. url: '/pagesA/public/richtext'
  258. });
  259. },
  260. handleBrowse() {
  261. // 直接返回首页
  262. uni.switchTab({
  263. url: '/pages/index/index'
  264. });
  265. },
  266. // 跳转到手机登录页面
  267. handlePhoneLogin() {
  268. uni.navigateTo({
  269. url: '/pagesA/public/phone-login'
  270. });
  271. },
  272. // 保存登录数据并跳转
  273. saveLoginDataAndRedirect(loginData) {
  274. uni.setStorageSync('access_token', loginData.access_token);
  275. uni.setStorageSync('refresh_token', loginData.refresh_token);
  276. uni.setStorageSync('user', loginData);
  277. this.$store.commit('isLogin', true);
  278. this.$store.commit('refresh_token', loginData.refresh_token);
  279. uni.showToast({
  280. title: '登录成功',
  281. icon: 'success',
  282. duration: 1000
  283. });
  284. // 登录成功后的跳转
  285. setTimeout(() => {
  286. if (this.redirectUrl) {
  287. uni.redirectTo({
  288. url: this.redirectUrl
  289. });
  290. } else {
  291. // 检查是否有待处理的邀请,如果有则跳转到首页触发邀请弹框
  292. const pendingInvitation = uni.getStorageSync('pendingInvitation');
  293. if (pendingInvitation && pendingInvitation.inviterId) {
  294. console.log('登录成功,发现待处理邀请,跳转到首页');
  295. // 使用 reLaunch 确保首页能重新加载并检查邀请
  296. uni.reLaunch({
  297. url: '/pages/index/index'
  298. });
  299. } else {
  300. uni.switchTab({
  301. url: '/pages/index/index'
  302. });
  303. }
  304. }
  305. }, 1000);
  306. },
  307. // 微信小程序头像选择回调
  308. async onChooseAvatar(e) {
  309. console.log('选择头像:', e.detail.avatarUrl);
  310. // 显示上传状态
  311. this.isUploadingAvatar = true;
  312. uni.showLoading({
  313. title: '上传头像中...',
  314. mask: true
  315. });
  316. try {
  317. // 上传头像到服务器
  318. const uploadResult = await this.uploadFilePromise(e.detail.avatarUrl);
  319. if (uploadResult && uploadResult.data && uploadResult.data.link) {
  320. this.tempAvatar = uploadResult.data.link;
  321. uni.showToast({
  322. title: '头像上传成功',
  323. icon: 'success'
  324. });
  325. } else {
  326. throw new Error('上传失败');
  327. }
  328. } catch (error) {
  329. console.error('头像上传失败:', error);
  330. uni.showToast({
  331. title: '头像上传失败,请重试',
  332. icon: 'none'
  333. });
  334. } finally {
  335. this.isUploadingAvatar = false;
  336. uni.hideLoading();
  337. }
  338. },
  339. // 上传文件到服务器
  340. uploadFilePromise(filePath) {
  341. return new Promise((resolve, reject) => {
  342. // 优先使用登录数据中的token,如果没有则使用本地存储的token
  343. const token = (this.loginData && this.loginData.access_token) || uni.getStorageSync(
  344. 'access_token');
  345. if (!token) {
  346. reject(new Error('未找到访问令牌'));
  347. return;
  348. }
  349. uni.uploadFile({
  350. url: UPLOAD_URL,
  351. filePath: filePath,
  352. header: {
  353. "Blade-Auth": token
  354. },
  355. name: 'file',
  356. formData: {
  357. user: 'avatar'
  358. },
  359. success: (res) => {
  360. try {
  361. const result = JSON.parse(res.data);
  362. if (result.success || result.code === 200) {
  363. resolve(result);
  364. } else {
  365. reject(new Error(result.msg || '上传失败'));
  366. }
  367. } catch (e) {
  368. reject(new Error('解析响应失败'));
  369. }
  370. },
  371. fail: (error) => {
  372. reject(error);
  373. }
  374. });
  375. });
  376. },
  377. // 确认个人信息设置
  378. async confirmProfile() {
  379. if (!this.tempAccount.trim()) {
  380. uni.showToast({
  381. title: '请输入账号',
  382. icon: 'none'
  383. });
  384. return;
  385. }
  386. uni.showLoading({
  387. title: '保存中...',
  388. mask: true
  389. });
  390. try {
  391. // 调用更新用户信息接口
  392. const updateRes = await updateProfile({
  393. account: this.tempAccount.trim(),
  394. avatar: this.tempAvatar
  395. });
  396. if (updateRes.code == 200) {
  397. // 更新登录数据中的用户信息
  398. this.loginData.account = this.tempAccount.trim();
  399. if (this.tempAvatar) {
  400. this.loginData.avatar = this.tempAvatar;
  401. this.loginData.avatarUrl = this.tempAvatar; // 同时更新 avatarUrl 字段
  402. }
  403. // 更新本地存储的用户信息
  404. uni.setStorageSync('user', this.loginData);
  405. // 保存登录数据并跳转
  406. this.saveLoginDataAndRedirect(this.loginData);
  407. this.showProfileModal = false;
  408. } else {
  409. uni.showToast({
  410. title: updateRes.msg || '保存失败,请重试',
  411. icon: 'none'
  412. });
  413. }
  414. } catch (error) {
  415. console.error('更新用户信息错误:', error);
  416. uni.showToast({
  417. title: '保存失败,请重试',
  418. icon: 'none'
  419. });
  420. } finally {
  421. uni.hideLoading();
  422. }
  423. }
  424. }
  425. }
  426. </script>
  427. <style lang="scss" scoped>
  428. .login-page {
  429. min-height: 100vh;
  430. background: #F5F5F5;
  431. position: relative;
  432. overflow: hidden;
  433. }
  434. .status-bar {
  435. height: var(--status-bar-height);
  436. width: 100%;
  437. }
  438. // 现代化背景
  439. .modern-bg {
  440. position: absolute;
  441. top: 0;
  442. left: 0;
  443. width: 100%;
  444. height: 100%;
  445. pointer-events: none;
  446. z-index: 1;
  447. .bg-shape {
  448. position: absolute;
  449. border-radius: 50%;
  450. &.shape-1 {
  451. width: 300rpx;
  452. height: 300rpx;
  453. top: 15%;
  454. right: -80rpx;
  455. background: linear-gradient(135deg, rgba(255, 91, 5, 0.1) 0%, transparent 70%);
  456. }
  457. &.shape-2 {
  458. width: 200rpx;
  459. height: 200rpx;
  460. top: 50%;
  461. left: -60rpx;
  462. background: linear-gradient(135deg, rgba(41, 44, 53, 0.08) 0%, transparent 70%);
  463. }
  464. &.shape-3 {
  465. width: 150rpx;
  466. height: 150rpx;
  467. bottom: 25%;
  468. right: 15%;
  469. background: linear-gradient(135deg, rgba(255, 91, 5, 0.06) 0%, transparent 70%);
  470. }
  471. }
  472. }
  473. .nav-back {
  474. position: fixed;
  475. left: 30rpx;
  476. top: calc(var(--status-bar-height) + 20rpx);
  477. z-index: 100;
  478. .back-btn {
  479. width: 80rpx;
  480. height: 80rpx;
  481. background: #FFFFFF;
  482. border: 1rpx solid rgba(255, 91, 5, 0.2);
  483. border-radius: 50%;
  484. display: flex;
  485. align-items: center;
  486. justify-content: center;
  487. transition: all 0.3s ease;
  488. box-shadow: 0 4rpx 16rpx rgba(41, 44, 53, 0.1);
  489. &:active {
  490. transform: scale(0.95);
  491. background: rgba(255, 91, 5, 0.1);
  492. }
  493. }
  494. .back-icon {
  495. font-family: "iconfont";
  496. font-size: 36rpx;
  497. color: #292C35;
  498. }
  499. }
  500. .header {
  501. display: flex;
  502. flex-direction: column;
  503. align-items: center;
  504. padding-top: 120rpx;
  505. position: relative;
  506. z-index: 2;
  507. .logo-container {
  508. position: relative;
  509. margin-bottom: 60rpx;
  510. .logo-circle {
  511. width: 200rpx;
  512. height: 200rpx;
  513. border-radius: 20rpx;
  514. background: #FFFFFF;
  515. display: flex;
  516. align-items: center;
  517. justify-content: center;
  518. box-shadow: 0 8rpx 24rpx rgba(41, 44, 53, 0.1);
  519. border: 2rpx solid rgba(255, 91, 5, 0.2);
  520. .logo {
  521. width: 160rpx;
  522. height: 160rpx;
  523. border-radius: 16rpx;
  524. }
  525. }
  526. .logo-shadow {
  527. position: absolute;
  528. bottom: -20rpx;
  529. left: 50%;
  530. transform: translateX(-50%);
  531. width: 160rpx;
  532. height: 20rpx;
  533. background: radial-gradient(ellipse, rgba(255, 91, 5, 0.1) 0%, transparent 70%);
  534. border-radius: 50%;
  535. }
  536. }
  537. .title-section {
  538. text-align: center;
  539. margin-bottom: 20rpx;
  540. position: relative;
  541. .title {
  542. font-size: 64rpx;
  543. font-weight: 600;
  544. color: #292C35;
  545. letter-spacing: 8rpx;
  546. font-family: 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;
  547. text-shadow: 0 2rpx 8rpx rgba(41, 44, 53, 0.1);
  548. }
  549. .title-underline {
  550. position: absolute;
  551. bottom: -10rpx;
  552. left: 50%;
  553. transform: translateX(-50%);
  554. width: 80rpx;
  555. height: 4rpx;
  556. background: linear-gradient(90deg, transparent, #FF5B05, transparent);
  557. border-radius: 2rpx;
  558. }
  559. }
  560. .subtitle {
  561. font-size: 28rpx;
  562. color: rgba(41, 44, 53, 0.7);
  563. font-weight: 300;
  564. letter-spacing: 4rpx;
  565. font-family: inherit;
  566. }
  567. }
  568. .login-section {
  569. padding: 80rpx 60rpx;
  570. position: relative;
  571. z-index: 2;
  572. .btn-container {
  573. margin-bottom: 40rpx;
  574. }
  575. .wechat-btn {
  576. position: relative;
  577. display: flex;
  578. align-items: center;
  579. justify-content: center;
  580. width: 100%;
  581. height: 96rpx;
  582. background: linear-gradient(135deg, #FF5B05 0%, #FF8E3C 100%);
  583. border: none;
  584. border-radius: 48rpx;
  585. color: #fff;
  586. font-size: 32rpx;
  587. font-weight: 500;
  588. transition: all 0.3s ease;
  589. box-shadow: 0 4rpx 16rpx rgba(255, 91, 5, 0.3);
  590. &:active {
  591. transform: translateY(2rpx);
  592. box-shadow: 0 2rpx 8rpx rgba(255, 91, 5, 0.4);
  593. }
  594. &.btn-loading {
  595. background: linear-gradient(135deg, #FFB088 0%, #FFA573 100%);
  596. color: rgba(255, 255, 255, 0.8);
  597. }
  598. }
  599. .phone-btn {
  600. display: flex;
  601. align-items: center;
  602. justify-content: center;
  603. width: 100%;
  604. height: 96rpx;
  605. background: #FFFFFF;
  606. border: 2rpx solid #FF5B05;
  607. border-radius: 48rpx;
  608. color: #FF5B05;
  609. font-size: 32rpx;
  610. font-weight: 500;
  611. transition: all 0.3s ease;
  612. box-shadow: 0 4rpx 16rpx rgba(41, 44, 53, 0.1);
  613. &:active {
  614. transform: translateY(2rpx);
  615. background: rgba(255, 91, 5, 0.05);
  616. }
  617. }
  618. .divider {
  619. display: flex;
  620. align-items: center;
  621. justify-content: center;
  622. margin: 40rpx 0;
  623. .divider-line {
  624. flex: 1;
  625. height: 1rpx;
  626. background: linear-gradient(90deg, transparent, rgba(41, 44, 53, 0.1), transparent);
  627. }
  628. .divider-text {
  629. margin: 0 30rpx;
  630. font-size: 26rpx;
  631. color: rgba(41, 44, 53, 0.5);
  632. }
  633. }
  634. .browse-btn {
  635. display: flex;
  636. align-items: center;
  637. justify-content: center;
  638. width: 100%;
  639. height: 88rpx;
  640. background: transparent;
  641. border: 1rpx solid rgba(41, 44, 53, 0.2);
  642. border-radius: 44rpx;
  643. color: #292C35;
  644. font-size: 28rpx;
  645. transition: all 0.3s ease;
  646. &:active {
  647. background: rgba(41, 44, 53, 0.05);
  648. transform: translateY(1rpx);
  649. }
  650. }
  651. .agreement {
  652. display: flex;
  653. align-items: center;
  654. justify-content: center;
  655. margin-top: 60rpx;
  656. .checkbox-wrapper {
  657. margin-right: 16rpx;
  658. }
  659. .zen-checkbox {
  660. width: 36rpx;
  661. height: 36rpx;
  662. border: 2rpx solid rgba(255, 91, 5, 0.4);
  663. border-radius: 18rpx;
  664. display: flex;
  665. align-items: center;
  666. justify-content: center;
  667. transition: all 0.3s ease;
  668. background: rgba(255, 255, 255, 0.9);
  669. &.checked {
  670. background: #FF5B05;
  671. border-color: #FF5B05;
  672. }
  673. .zen-circle-inner {
  674. width: 16rpx;
  675. height: 16rpx;
  676. border-radius: 50%;
  677. background: #fff;
  678. }
  679. }
  680. .agreement-text {
  681. font-size: 24rpx;
  682. color: rgba(41, 44, 53, 0.6);
  683. line-height: 1.5;
  684. .link {
  685. color: #FF5B05;
  686. text-decoration: none;
  687. }
  688. }
  689. }
  690. }
  691. // 底部装饰
  692. .bottom-decoration {
  693. position: absolute;
  694. bottom: 40rpx;
  695. left: 50%;
  696. transform: translateX(-50%);
  697. z-index: 1;
  698. .company-info {
  699. text-align: center;
  700. .company-name {
  701. display: block;
  702. font-size: 24rpx;
  703. color: rgba(41, 44, 53, 0.6);
  704. margin-bottom: 8rpx;
  705. font-weight: 400;
  706. }
  707. .version {
  708. font-size: 20rpx;
  709. color: rgba(41, 44, 53, 0.4);
  710. font-weight: 300;
  711. }
  712. }
  713. }
  714. // 个人信息设置弹框样式
  715. .profile-modal {
  716. width: 750rpx;
  717. background: #fff;
  718. border-radius: 20rpx;
  719. overflow: hidden;
  720. .modal-header {
  721. text-align: center;
  722. padding: 60rpx 40rpx 40rpx;
  723. background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
  724. .modal-title {
  725. display: block;
  726. font-size: 36rpx;
  727. font-weight: 600;
  728. color: #2c3e50;
  729. margin-bottom: 12rpx;
  730. }
  731. .modal-subtitle {
  732. font-size: 26rpx;
  733. color: rgba(44, 62, 80, 0.7);
  734. }
  735. }
  736. .modal-content {
  737. padding: 40rpx;
  738. .avatar-section {
  739. margin-bottom: 40rpx;
  740. text-align: center;
  741. .section-label {
  742. display: block;
  743. font-size: 28rpx;
  744. color: #2c3e50;
  745. margin-bottom: 20rpx;
  746. font-weight: 500;
  747. }
  748. .avatar-button {
  749. background: none;
  750. border: none;
  751. padding: 0;
  752. margin: 0 auto;
  753. display: block;
  754. width: 160rpx;
  755. height: 160rpx;
  756. &::after {
  757. border: none;
  758. }
  759. &:disabled {
  760. opacity: 0.7;
  761. }
  762. }
  763. .avatar-container {
  764. position: relative;
  765. width: 160rpx;
  766. height: 160rpx;
  767. border-radius: 50%;
  768. overflow: hidden;
  769. border: 3rpx solid #4A90E2;
  770. .avatar-preview {
  771. width: 100%;
  772. height: 100%;
  773. }
  774. .avatar-overlay {
  775. position: absolute;
  776. bottom: 0;
  777. left: 0;
  778. right: 0;
  779. height: 50rpx;
  780. background: rgba(0, 0, 0, 0.5);
  781. display: flex;
  782. align-items: center;
  783. justify-content: center;
  784. }
  785. }
  786. }
  787. .account-section {
  788. .section-label {
  789. display: block;
  790. font-size: 28rpx;
  791. color: #2c3e50;
  792. margin-bottom: 20rpx;
  793. font-weight: 500;
  794. }
  795. }
  796. }
  797. .modal-actions {
  798. padding: 0 40rpx 40rpx;
  799. .confirm-btn {
  800. width: 100%;
  801. height: 80rpx;
  802. background: linear-gradient(135deg, #4A90E2 0%, #357ABD 100%);
  803. color: #fff;
  804. border-radius: 12rpx;
  805. font-size: 32rpx;
  806. font-weight: 500;
  807. border: none;
  808. transition: all 0.3s ease;
  809. &:disabled {
  810. background: #bdc3c7;
  811. color: rgba(255, 255, 255, 0.7);
  812. }
  813. &:not(:disabled):active {
  814. transform: translateY(2rpx);
  815. box-shadow: 0 2rpx 8rpx rgba(74, 144, 226, 0.3);
  816. }
  817. }
  818. }
  819. }
  820. </style>