login.vue 20 KB

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