index.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923
  1. <template>
  2. <view class="login-page">
  3. <!-- 状态栏占位 -->
  4. <view class="status-bar"></view>
  5. <!-- 水墨背景 -->
  6. <view class="ink-bg">
  7. <view class="ink-wash ink-1"></view>
  8. <view class="ink-wash ink-2"></view>
  9. <view class="ink-wash ink-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"
  22. src="https://ndtk.tos-cn-guangzhou.volces.com/uploads/156adea827104e38ae0e25ec4701ecfe.jpg"
  23. mode="aspectFit"></image>
  24. </view>
  25. <view class="logo-shadow"></view>
  26. </view>
  27. <view class="title-section">
  28. <text class="title">宏匠</text>
  29. <view class="title-brush"></view>
  30. </view>
  31. <text class="subtitle">唐卡世界,一键开启</text>
  32. </view>
  33. <!-- 登录按钮 -->
  34. <view class="login-section">
  35. <view class="btn-container">
  36. <button class="wechat-btn" @tap="handleWxLogin" :class="{ 'btn-loading': isLoading }">
  37. <view class="btn-ink"></view>
  38. <text class="font32">一键快捷登录</text>
  39. </button>
  40. </view>
  41. <view class="zen-divider">
  42. <view class="zen-dot"></view>
  43. <view class="zen-line"></view>
  44. <view class="zen-dot"></view>
  45. </view>
  46. <view class="btn-container">
  47. <button class="browse-btn" @tap="handleBrowse">
  48. <text>暂不登录,先去逛逛</text>
  49. </button>
  50. </view>
  51. <view class="agreement">
  52. <view class="checkbox-wrapper" @tap="toggleAgreement">
  53. <view class="zen-checkbox" :class="{ 'checked': isAgree }">
  54. <view class="zen-circle-inner" v-if="isAgree"></view>
  55. </view>
  56. </view>
  57. <text class="agreement-text">
  58. 登录即代表您已同意
  59. <text class="link" @tap="goToPrivacy">《隐私政策》</text>
  60. <text class="link" @tap="goToService">《服务协议》</text>
  61. </text>
  62. </view>
  63. </view>
  64. <!-- 底部装饰 -->
  65. <view class="bottom-decoration">
  66. <view class="zen-mountain">
  67. <view class="mountain-peak"></view>
  68. <view class="mountain-base"></view>
  69. </view>
  70. </view>
  71. <!-- 首次登录设置弹框 -->
  72. <u-popup :show="showProfileModal">
  73. <view class="profile-modal">
  74. <view class="modal-header">
  75. <text class="modal-title">完善个人信息</text>
  76. <text class="modal-subtitle">请设置您的账号和头像</text>
  77. </view>
  78. <view class="modal-content">
  79. <!-- 头像选择 -->
  80. <view class="avatar-section">
  81. <text class="section-label">头像</text>
  82. <button class="avatar-button" open-type="chooseAvatar" @chooseavatar="onChooseAvatar"
  83. :disabled="isUploadingAvatar">
  84. <view class="avatar-container">
  85. <image class="avatar-preview" :src="tempAvatar || '/static/images/avatar.png'"
  86. mode="aspectFill"></image>
  87. <view class="avatar-overlay">
  88. <u-icon v-if="!isUploadingAvatar" name="camera" size="24" color="#fff"></u-icon>
  89. <u-loading-icon v-else mode="spinner" size="24" color="#fff"></u-loading-icon>
  90. </view>
  91. </view>
  92. </button>
  93. </view>
  94. <!-- 账号输入 -->
  95. <view class="account-section">
  96. <text class="section-label">用户名</text>
  97. <u-input v-model="tempAccount" placeholder="请输入用户名" :maxlength="20" :clearable="true"
  98. border="surround"></u-input>
  99. </view>
  100. </view>
  101. <view class="modal-actions">
  102. <button class="confirm-btn" @tap="confirmProfile"
  103. :disabled="!tempAccount.trim() || isUploadingAvatar">
  104. <text v-if="!isUploadingAvatar">确认</text>
  105. <text v-else>头像上传中...</text>
  106. </button>
  107. </view>
  108. </view>
  109. </u-popup>
  110. </view>
  111. </template>
  112. <script>
  113. import {
  114. wxLogin,
  115. updateProfile
  116. } from '@/config/api.js';
  117. import {
  118. UPLOAD_URL
  119. } from '@/common/config.js';
  120. export default {
  121. data() {
  122. return {
  123. isAgree: false,
  124. isLoading: false,
  125. redirectUrl: '', // 登录成功后的重定向地址
  126. showProfileModal: false, // 显示个人信息设置弹框
  127. tempAccount: '', // 临时账号
  128. tempAvatar: '', // 临时头像
  129. loginData: null, // 临时保存登录数据
  130. isUploadingAvatar: false // 头像上传状态
  131. }
  132. },
  133. onLoad(options) {
  134. // 获取重定向地址
  135. if (options.redirect) {
  136. this.redirectUrl = decodeURIComponent(options.redirect);
  137. }
  138. },
  139. methods: {
  140. // 处理微信登录
  141. async handleWxLogin() {
  142. if (!this.isAgree) {
  143. let that = this
  144. // 弹出确认弹框询问用户是否同意协议
  145. uni.showModal({
  146. title: '提示',
  147. content: '登录需要您同意《隐私政策》和《服务协议》,是否同意?',
  148. success: function(res) {
  149. if (res.confirm) {
  150. // 用户点击同意,自动勾选协议并执行登录
  151. that.isAgree = true;
  152. that.executeLogin();
  153. } else if (res.cancel) {
  154. console.log('用户点击取消');
  155. }
  156. }
  157. });
  158. return;
  159. }
  160. this.executeLogin();
  161. },
  162. // 执行登录逻辑
  163. async executeLogin() {
  164. uni.showLoading({
  165. mask: true
  166. })
  167. this.isLoading = true;
  168. uni.getUserProfile({
  169. desc: '用于完善会员资料',
  170. success: (profileRes) => {
  171. uni.login({
  172. provider: 'weixin',
  173. success: async (loginRes) => {
  174. try {
  175. const loginData = await wxLogin({
  176. code: loginRes.code,
  177. account: profileRes.userInfo.nickName,
  178. avatarUrl: profileRes.userInfo.avatarUrl,
  179. grant_type: 'wechat'
  180. });
  181. if (loginData.code === 200) {
  182. // 检查是否为首次登录
  183. if (loginData.isFirstLogin) {
  184. // 首次登录,显示个人信息设置弹框
  185. this.loginData = loginData;
  186. this.tempAccount = loginData.account || '';
  187. this.tempAvatar = loginData.avatar || '';
  188. this.showProfileModal = true;
  189. this.isLoading = false;
  190. uni.setStorageSync('access_token', loginData
  191. .access_token);
  192. uni.setStorageSync('refresh_token', loginData
  193. .refresh_token);
  194. uni.setStorageSync('user', loginData);
  195. this.$store.commit('isLogin', true);
  196. this.$store.commit('refresh_token', loginData
  197. .refresh_token);
  198. uni.hideLoading();
  199. } else {
  200. // 非首次登录,直接保存并跳转
  201. this.saveLoginDataAndRedirect(loginData);
  202. }
  203. } else {
  204. uni.showToast({
  205. title: loginData.msg || '登录失败',
  206. icon: 'none'
  207. });
  208. }
  209. } catch (e) {
  210. console.error('登录错误:', e);
  211. uni.showToast({
  212. title: '登录失败,请重试',
  213. icon: 'none'
  214. });
  215. } finally {
  216. this.isLoading = false;
  217. }
  218. },
  219. fail: () => {
  220. this.isLoading = false;
  221. }
  222. });
  223. },
  224. fail: (err) => {
  225. this.isLoading = false;
  226. if (err.errMsg.includes('cancel')) {
  227. return; // 用户取消,不显示提示
  228. }
  229. uni.showToast({
  230. title: '获取用户信息失败',
  231. icon: 'none'
  232. });
  233. }
  234. });
  235. },
  236. // 切换协议同意状态
  237. toggleAgreement() {
  238. this.isAgree = !this.isAgree;
  239. },
  240. // 返回上一页
  241. goBack() {
  242. uni.navigateBack();
  243. },
  244. // 跳转到隐私政策
  245. goToPrivacy() {
  246. uni.navigateTo({
  247. url: '/packageUser/pages/richtext/index'
  248. });
  249. },
  250. // 跳转到服务协议
  251. goToService() {
  252. uni.navigateTo({
  253. url: '/packageUser/pages/richtext/index'
  254. });
  255. },
  256. handleBrowse() {
  257. // 直接返回首页
  258. uni.switchTab({
  259. url: '/pages/index/indexNew'
  260. });
  261. },
  262. // 保存登录数据并跳转
  263. saveLoginDataAndRedirect(loginData) {
  264. uni.setStorageSync('access_token', loginData.access_token);
  265. uni.setStorageSync('refresh_token', loginData.refresh_token);
  266. uni.setStorageSync('user', loginData);
  267. this.$store.commit('isLogin', true);
  268. this.$store.commit('refresh_token', loginData.refresh_token);
  269. uni.showToast({
  270. title: '登录成功',
  271. icon: 'success',
  272. duration: 1000
  273. });
  274. // 登录成功后的跳转
  275. setTimeout(() => {
  276. if (this.redirectUrl) {
  277. uni.redirectTo({
  278. url: this.redirectUrl
  279. });
  280. } else {
  281. // 检查是否有待处理的邀请,如果有则跳转到首页触发邀请弹框
  282. const pendingInvitation = uni.getStorageSync('pendingInvitation');
  283. if (pendingInvitation && pendingInvitation.inviterId) {
  284. console.log('登录成功,发现待处理邀请,跳转到首页');
  285. // 使用 reLaunch 确保首页能重新加载并检查邀请
  286. uni.reLaunch({
  287. url: '/pages/index/indexNew'
  288. });
  289. } else {
  290. uni.switchTab({
  291. url: '/pages/index/indexNew'
  292. });
  293. }
  294. }
  295. }, 1000);
  296. },
  297. // 微信小程序头像选择回调
  298. async onChooseAvatar(e) {
  299. console.log('选择头像:', e.detail.avatarUrl);
  300. // 显示上传状态
  301. this.isUploadingAvatar = true;
  302. uni.showLoading({
  303. title: '上传头像中...',
  304. mask: true
  305. });
  306. try {
  307. // 上传头像到服务器
  308. const uploadResult = await this.uploadFilePromise(e.detail.avatarUrl);
  309. if (uploadResult && uploadResult.data && uploadResult.data.link) {
  310. this.tempAvatar = uploadResult.data.link;
  311. uni.showToast({
  312. title: '头像上传成功',
  313. icon: 'success'
  314. });
  315. } else {
  316. throw new Error('上传失败');
  317. }
  318. } catch (error) {
  319. console.error('头像上传失败:', error);
  320. uni.showToast({
  321. title: '头像上传失败,请重试',
  322. icon: 'none'
  323. });
  324. } finally {
  325. this.isUploadingAvatar = false;
  326. uni.hideLoading();
  327. }
  328. },
  329. // 上传文件到服务器
  330. uploadFilePromise(filePath) {
  331. return new Promise((resolve, reject) => {
  332. // 优先使用登录数据中的token,如果没有则使用本地存储的token
  333. const token = (this.loginData && this.loginData.access_token) || uni.getStorageSync(
  334. 'access_token');
  335. if (!token) {
  336. reject(new Error('未找到访问令牌'));
  337. return;
  338. }
  339. uni.uploadFile({
  340. url: UPLOAD_URL,
  341. filePath: filePath,
  342. header: {
  343. "Blade-Auth": token
  344. },
  345. name: 'file',
  346. formData: {
  347. user: 'avatar'
  348. },
  349. success: (res) => {
  350. try {
  351. const result = JSON.parse(res.data);
  352. if (result.success || result.code === 200) {
  353. resolve(result);
  354. } else {
  355. reject(new Error(result.msg || '上传失败'));
  356. }
  357. } catch (e) {
  358. reject(new Error('解析响应失败'));
  359. }
  360. },
  361. fail: (error) => {
  362. reject(error);
  363. }
  364. });
  365. });
  366. },
  367. // 确认个人信息设置
  368. async confirmProfile() {
  369. if (!this.tempAccount.trim()) {
  370. uni.showToast({
  371. title: '请输入账号',
  372. icon: 'none'
  373. });
  374. return;
  375. }
  376. uni.showLoading({
  377. title: '保存中...',
  378. mask: true
  379. });
  380. try {
  381. // 调用更新用户信息接口
  382. const updateRes = await updateProfile({
  383. account: this.tempAccount.trim(),
  384. avatar: this.tempAvatar
  385. });
  386. if (updateRes.code == 200) {
  387. // 更新登录数据中的用户信息
  388. this.loginData.account = this.tempAccount.trim();
  389. if (this.tempAvatar) {
  390. this.loginData.avatar = this.tempAvatar;
  391. this.loginData.avatarUrl = this.tempAvatar; // 同时更新 avatarUrl 字段
  392. }
  393. // 更新本地存储的用户信息
  394. uni.setStorageSync('user', this.loginData);
  395. // 保存登录数据并跳转
  396. this.saveLoginDataAndRedirect(this.loginData);
  397. this.showProfileModal = false;
  398. } else {
  399. uni.showToast({
  400. title: updateRes.msg || '保存失败,请重试',
  401. icon: 'none'
  402. });
  403. }
  404. } catch (error) {
  405. console.error('更新用户信息错误:', error);
  406. uni.showToast({
  407. title: '保存失败,请重试',
  408. icon: 'none'
  409. });
  410. } finally {
  411. uni.hideLoading();
  412. }
  413. }
  414. }
  415. }
  416. </script>
  417. <style lang="scss" scoped>
  418. .login-page {
  419. min-height: 100vh;
  420. background: linear-gradient(135deg, #f8f6f0 0%, #e8e4d8 50%, #d4cfc0 100%);
  421. position: relative;
  422. overflow: hidden;
  423. }
  424. .status-bar {
  425. height: var(--status-bar-height);
  426. width: 100%;
  427. }
  428. // 水墨背景
  429. .ink-bg {
  430. position: absolute;
  431. top: 0;
  432. left: 0;
  433. width: 100%;
  434. height: 100%;
  435. pointer-events: none;
  436. z-index: 1;
  437. .ink-wash {
  438. position: absolute;
  439. border-radius: 50%;
  440. background: radial-gradient(circle, rgba(0, 0, 0, 0.03) 0%, transparent 70%);
  441. &.ink-1 {
  442. width: 400rpx;
  443. height: 400rpx;
  444. top: 10%;
  445. right: -100rpx;
  446. transform: rotate(15deg);
  447. }
  448. &.ink-2 {
  449. width: 300rpx;
  450. height: 300rpx;
  451. top: 40%;
  452. left: -50rpx;
  453. transform: rotate(-20deg);
  454. }
  455. &.ink-3 {
  456. width: 200rpx;
  457. height: 200rpx;
  458. bottom: 20%;
  459. right: 20%;
  460. transform: rotate(45deg);
  461. }
  462. }
  463. }
  464. .nav-back {
  465. position: fixed;
  466. left: 30rpx;
  467. top: calc(var(--status-bar-height) + 20rpx);
  468. z-index: 100;
  469. .back-btn {
  470. width: 80rpx;
  471. height: 80rpx;
  472. background: rgba(255, 255, 255, 0.8);
  473. border: 1rpx solid rgba(0, 0, 0, 0.1);
  474. border-radius: 50%;
  475. display: flex;
  476. align-items: center;
  477. justify-content: center;
  478. transition: all 0.3s ease;
  479. box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
  480. &:active {
  481. transform: scale(0.95);
  482. box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.15);
  483. }
  484. }
  485. .back-icon {
  486. font-family: "iconfont";
  487. font-size: 36rpx;
  488. color: #333;
  489. }
  490. }
  491. .header {
  492. display: flex;
  493. flex-direction: column;
  494. align-items: center;
  495. padding-top: 120rpx;
  496. position: relative;
  497. z-index: 2;
  498. .logo-container {
  499. position: relative;
  500. margin-bottom: 60rpx;
  501. .logo-circle {
  502. width: 200rpx;
  503. height: 200rpx;
  504. border-radius: 50%;
  505. background: linear-gradient(135deg, #fff 0%, #f5f5f5 100%);
  506. display: flex;
  507. align-items: center;
  508. justify-content: center;
  509. box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.1);
  510. border: 2rpx solid rgba(0, 0, 0, 0.05);
  511. .logo {
  512. width: 160rpx;
  513. height: 160rpx;
  514. border-radius: 50%;
  515. }
  516. }
  517. .logo-shadow {
  518. position: absolute;
  519. bottom: -20rpx;
  520. left: 50%;
  521. transform: translateX(-50%);
  522. width: 160rpx;
  523. height: 20rpx;
  524. background: radial-gradient(ellipse, rgba(0, 0, 0, 0.1) 0%, transparent 70%);
  525. border-radius: 50%;
  526. }
  527. }
  528. .title-section {
  529. text-align: center;
  530. margin-bottom: 20rpx;
  531. position: relative;
  532. .title {
  533. font-size: 64rpx;
  534. font-weight: normal;
  535. color: #2c3e50;
  536. letter-spacing: 12rpx;
  537. font-family: 'KaiTi', '楷体', serif;
  538. text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
  539. }
  540. .title-brush {
  541. position: absolute;
  542. bottom: -10rpx;
  543. left: 50%;
  544. transform: translateX(-50%);
  545. width: 120rpx;
  546. height: 4rpx;
  547. background: linear-gradient(90deg, transparent, #2c3e50, transparent);
  548. border-radius: 2rpx;
  549. }
  550. }
  551. .subtitle {
  552. font-size: 28rpx;
  553. color: rgba(44, 62, 80, 0.7);
  554. font-weight: 300;
  555. letter-spacing: 4rpx;
  556. font-style: italic;
  557. font-family: inherit;
  558. }
  559. }
  560. .login-section {
  561. padding: 80rpx 60rpx;
  562. position: relative;
  563. z-index: 2;
  564. .btn-container {
  565. margin-bottom: 40rpx;
  566. }
  567. .wechat-btn {
  568. position: relative;
  569. display: flex;
  570. align-items: center;
  571. justify-content: center;
  572. width: 100%;
  573. height: 96rpx;
  574. background: linear-gradient(135deg, #fff 0%, #f8f9fa 100%);
  575. border: 2rpx solid rgba(0, 0, 0, 0.08);
  576. border-radius: 48rpx;
  577. color: #2c3e50;
  578. font-size: 32rpx;
  579. font-weight: normal;
  580. transition: all 0.3s ease;
  581. box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
  582. overflow: hidden;
  583. &:active {
  584. transform: translateY(2rpx);
  585. box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.12);
  586. }
  587. &.btn-loading {
  588. background: linear-gradient(135deg, #f5f5f5 0%, #e8e8e8 100%);
  589. color: rgba(44, 62, 80, 0.5);
  590. }
  591. .btn-ink {
  592. position: absolute;
  593. top: 0;
  594. left: 0;
  595. width: 100%;
  596. height: 100%;
  597. background: linear-gradient(135deg, rgba(0, 0, 0, 0.02) 0%, transparent 50%);
  598. pointer-events: none;
  599. }
  600. .wechat-icon {
  601. width: 44rpx;
  602. height: 44rpx;
  603. margin-right: 16rpx;
  604. }
  605. }
  606. .zen-divider {
  607. display: flex;
  608. align-items: center;
  609. justify-content: center;
  610. margin: 50rpx 0;
  611. .zen-dot {
  612. width: 8rpx;
  613. height: 8rpx;
  614. border-radius: 50%;
  615. background: rgba(44, 62, 80, 0.3);
  616. }
  617. .zen-line {
  618. width: 100rpx;
  619. height: 1rpx;
  620. background: linear-gradient(90deg, transparent, rgba(44, 62, 80, 0.2), transparent);
  621. margin: 0 20rpx;
  622. }
  623. }
  624. .browse-btn {
  625. display: flex;
  626. align-items: center;
  627. justify-content: center;
  628. width: 100%;
  629. height: 88rpx;
  630. background: transparent;
  631. border: 1rpx solid rgba(44, 62, 80, 0.15);
  632. border-radius: 44rpx;
  633. color: rgba(44, 62, 80, 0.7);
  634. font-size: 28rpx;
  635. transition: all 0.3s ease;
  636. &:active {
  637. background: rgba(44, 62, 80, 0.05);
  638. transform: translateY(1rpx);
  639. }
  640. }
  641. .agreement {
  642. display: flex;
  643. align-items: center;
  644. justify-content: center;
  645. margin-top: 60rpx;
  646. .checkbox-wrapper {
  647. margin-right: 16rpx;
  648. }
  649. .zen-checkbox {
  650. width: 36rpx;
  651. height: 36rpx;
  652. border: 2rpx solid rgba(44, 62, 80, 0.3);
  653. border-radius: 50%;
  654. display: flex;
  655. align-items: center;
  656. justify-content: center;
  657. transition: all 0.3s ease;
  658. background: rgba(255, 255, 255, 0.8);
  659. &.checked {
  660. background: #2c3e50;
  661. border-color: #2c3e50;
  662. }
  663. .zen-circle-inner {
  664. width: 16rpx;
  665. height: 16rpx;
  666. border-radius: 50%;
  667. background: #fff;
  668. }
  669. }
  670. .agreement-text {
  671. font-size: 24rpx;
  672. color: rgba(44, 62, 80, 0.6);
  673. line-height: 1.5;
  674. .link {
  675. color: #2c3e50;
  676. text-decoration: underline;
  677. }
  678. }
  679. }
  680. }
  681. // 底部装饰
  682. .bottom-decoration {
  683. position: absolute;
  684. bottom: 40rpx;
  685. left: 50%;
  686. transform: translateX(-50%);
  687. z-index: 1;
  688. .zen-mountain {
  689. position: relative;
  690. width: 200rpx;
  691. height: 120rpx;
  692. .mountain-peak {
  693. position: absolute;
  694. top: 0;
  695. left: 50%;
  696. transform: translateX(-50%);
  697. width: 0;
  698. height: 0;
  699. border-left: 60rpx solid transparent;
  700. border-right: 60rpx solid transparent;
  701. border-bottom: 80rpx solid rgba(44, 62, 80, 0.1);
  702. }
  703. .mountain-base {
  704. position: absolute;
  705. bottom: 0;
  706. left: 0;
  707. width: 100%;
  708. height: 40rpx;
  709. background: linear-gradient(90deg, transparent, rgba(44, 62, 80, 0.05), transparent);
  710. border-radius: 50%;
  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, #f8f6f0 0%, #e8e4d8 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 #e8e4d8;
  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, #2c3e50 0%, #34495e 100%);
  803. color: #fff;
  804. border-radius: 40rpx;
  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(44, 62, 80, 0.3);
  816. }
  817. }
  818. }
  819. }
  820. </style>