index.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760
  1. <template>
  2. <view class="page-profile">
  3. <!-- ========== Region 1: User Card ========== -->
  4. <view class="user-card">
  5. <!-- Login prompt for unauthenticated users -->
  6. <view v-if="!userStore.isLoggedIn" class="user-card__login" @click="goLogin">
  7. <view class="avatar avatar--empty">
  8. <text class="avatar__icon">+</text>
  9. </view>
  10. <text class="user-card__nickname">未登录</text>
  11. <text class="user-card__login-hint">点击登录 / 注册</text>
  12. </view>
  13. <!-- Authenticated user card -->
  14. <view v-else class="user-card__logged">
  15. <view class="avatar">
  16. <image :src="userStore.avatarUrl || '/static/default-avatar.png'" mode="aspectFill" />
  17. <view class="avatar__edit" @click="goEditProfile">
  18. <text class="avatar__edit-icon">✏</text>
  19. </view>
  20. </view>
  21. <text class="user-card__nickname">{{ userStore.nickname }}</text>
  22. <!-- Role tag -->
  23. <view
  24. class="user-card__tag"
  25. :class="{
  26. 'user-card__tag--practitioner': userStore.vipType === 'practitioner',
  27. 'user-card__tag--annual': userStore.vipType === 'annual',
  28. 'user-card__tag--normal': !userStore.vipType
  29. }"
  30. >
  31. <text v-if="!userStore.vipType">普通用户</text>
  32. <text v-else-if="userStore.vipType === 'annual'">C端会员</text>
  33. <text v-else>能量师 🌟</text>
  34. </view>
  35. <!-- Social summary (optional) -->
  36. <text v-if="userStore.bio || userStore.city" class="user-card__summary">
  37. {{ userStore.city || '' }}{{ userStore.city && userStore.bio ? ' · ' : '' }}{{ userStore.bio || '' }}
  38. </text>
  39. </view>
  40. </view>
  41. <!-- ========== Region 2: Membership Status Cards ========== -->
  42. <!-- US-5.1 §13: Profile incomplete banner -->
  43. <view v-if="userStore.isLoggedIn && userStore.profileIncomplete" class="banner" @click="goEditProfile">
  44. <text class="banner__text">📝 请完善个人资料,开启能量匹配</text>
  45. <text class="banner__arrow">›</text>
  46. </view>
  47. <!-- US-4.3: Renewal reminder (7 days before expiry) -->
  48. <view v-if="isExpiringSoon" class="banner banner--warning" @click="goRenew">
  49. <text class="banner__text">⏰ 您的会员即将到期,请及时续费</text>
  50. <text class="banner__arrow">›</text>
  51. </view>
  52. <!-- Seed-price badge -->
  53. <view v-if="userStore.isSeedPriceUser" class="seed-badge">
  54. <text class="seed-badge__text">🌱 种子价专属会员</text>
  55. </view>
  56. <!-- VIP status card — role-based -->
  57. <!-- Case 1: Energy master (practitioner, in valid period) -->
  58. <view
  59. v-if="userStore.vipType === 'practitioner' && userStore.isVip && !isExpired"
  60. class="vip-card vip-card--practitioner"
  61. >
  62. <view class="vip-card__body">
  63. <text class="vip-card__title">能量师会员 🌟</text>
  64. <text class="vip-card__desc">
  65. 有效期至 {{ formatDate(userStore.vipEndTime) }}
  66. </text>
  67. </view>
  68. <text class="vip-card__action" @click="goRenew">续费</text>
  69. </view>
  70. <!-- Case 2: C-end annual subscriber (in valid period) -->
  71. <view
  72. v-else-if="userStore.vipType === 'annual' && userStore.isVip && !isExpired"
  73. class="vip-card vip-card--annual"
  74. >
  75. <view class="vip-card__body">
  76. <text class="vip-card__title">C端会员</text>
  77. <text class="vip-card__desc">
  78. 有效期至 {{ formatDate(userStore.vipEndTime) }}
  79. </text>
  80. </view>
  81. <text class="vip-card__action" @click="goUpgradePractitioner">升级能量师</text>
  82. </view>
  83. <!-- Case 3: Expired member (vipEndTime < now) -->
  84. <view v-else-if="userStore.vipType && isExpired" class="vip-card vip-card--expired">
  85. <view class="vip-card__body">
  86. <text class="vip-card__title">会员已过期</text>
  87. <text class="vip-card__desc">
  88. 过期时间 {{ formatDate(userStore.vipEndTime) }}
  89. </text>
  90. </view>
  91. <text class="vip-card__action" @click="goPayment">重新开通</text>
  92. </view>
  93. <!-- Case 4: Free user — upsell card -->
  94. <view v-else class="vip-card vip-card--upsell">
  95. <view class="vip-card__body">
  96. <text class="vip-card__title">开通VIP · 解锁完整分析</text>
  97. <view class="vip-card__benefits">
  98. <text class="vip-card__benefit">✅ AI 完整五区解读</text>
  99. <text class="vip-card__benefit">✅ 不限次 AI 追问互动</text>
  100. <text class="vip-card__benefit">✅ 导出 PDF 报告</text>
  101. <text class="vip-card__benefit">✅ 分销推广赚佣金</text>
  102. </view>
  103. </view>
  104. <text class="vip-card__cta" @click="goPayment">立即开通 ¥131/年</text>
  105. </view>
  106. <!-- Daily quota (non-VIP only) -->
  107. <view v-if="!userStore.isVip" class="quota-bar">
  108. <view class="quota-bar__item">
  109. <text class="quota-bar__label">今日分析</text>
  110. <text class="quota-bar__value">{{ userStore.usedCharts }}/{{ userStore.maxCharts }}</text>
  111. </view>
  112. <view class="quota-bar__divider"></view>
  113. <view class="quota-bar__item">
  114. <text class="quota-bar__label">今日解读</text>
  115. <text class="quota-bar__value">{{ userStore.usedChats }}/{{ userStore.maxChats }}</text>
  116. </view>
  117. </view>
  118. <!-- ========== Region 3: Feature Menu ========== -->
  119. <!-- Group 1: My Services -->
  120. <view class="menu-section">
  121. <view class="menu-section__header">我的服务</view>
  122. <!-- Analysis history (all logged-in) -->
  123. <view v-if="userStore.isLoggedIn" class="menu-item" @click="goRecords">
  124. <text class="menu-item__label">📋 我的分析</text>
  125. <text class="menu-item__arrow">›</text>
  126. </view>
  127. <!-- Academic orientation (all users, NEW badge) — EPIC 9 -->
  128. <view v-if="userStore.isLoggedIn" class="menu-item" @click="goAcademicOrientation">
  129. <text class="menu-item__label">📖 学业方向</text>
  130. <view class="menu-item__badge menu-item__badge--new">NEW</view>
  131. <text class="menu-item__arrow">›</text>
  132. </view>
  133. <!-- Distribution panel (paid users only) -->
  134. <view v-if="userStore.isLoggedIn && userStore.isVip" class="menu-item" @click="goCommission">
  135. <text class="menu-item__label">💰 我的推广</text>
  136. <text class="menu-item__arrow">›</text>
  137. </view>
  138. <!-- Practitioner workbench (practitioners only) — EPIC 9 -->
  139. <view v-if="userStore.vipType === 'practitioner' && userStore.isVip" class="menu-item" @click="goWorkbench">
  140. <text class="menu-item__label">🔧 工作台</text>
  141. <view class="menu-item__badge menu-item__badge--new">NEW</view>
  142. <text class="menu-item__arrow">›</text>
  143. </view>
  144. </view>
  145. <!-- Group 2: Promotion Tools (paid users only) -->
  146. <view v-if="userStore.isLoggedIn && userStore.isVip && userStore.referralCode" class="menu-section">
  147. <view class="menu-section__header">推广工具</view>
  148. <view class="promo-card">
  149. <view class="promo-card__code-area">
  150. <text class="promo-card__prefix">推广码</text>
  151. <text class="promo-card__code">{{ userStore.referralCode }}</text>
  152. </view>
  153. <view class="promo-card__actions">
  154. <text class="promo-card__btn" @click="copyReferral">复制推广码</text>
  155. <text class="promo-card__divider">|</text>
  156. <text class="promo-card__btn" @click="generatePoster">生成推广海报</text>
  157. </view>
  158. </view>
  159. </view>
  160. <!-- Group 3: More -->
  161. <view class="menu-section">
  162. <view class="menu-section__header">更多</view>
  163. <!-- My tags (practitioner only, P2) -->
  164. <view v-if="userStore.vipType === 'practitioner'" class="menu-item" @click="goTags">
  165. <text class="menu-item__label">🏷 我的标签</text>
  166. <text class="menu-item__arrow">›</text>
  167. </view>
  168. <!-- About us (all) -->
  169. <view class="menu-item" @click="goAbout">
  170. <text class="menu-item__label">ℹ️ 关于我们</text>
  171. <text class="menu-item__arrow">›</text>
  172. </view>
  173. </view>
  174. <!-- ========== Region 4: Logout ========== -->
  175. <view v-if="userStore.isLoggedIn" class="menu-section">
  176. <view class="menu-item menu-item--danger" @click="handleLogout">
  177. <text class="menu-item__label menu-item__label--danger">退出登录</text>
  178. </view>
  179. </view>
  180. </view>
  181. </template>
  182. <script setup>
  183. import { computed } from 'vue'
  184. import { onShow } from '@dcloudio/uni-app'
  185. import { useUserStore } from '@/stores/user'
  186. const userStore = useUserStore()
  187. // US-4.3: Renewal reminder if expiry within 7 days
  188. const isExpiringSoon = computed(() => {
  189. if (!userStore.vipEndTime) return false
  190. const expiry = new Date(userStore.vipEndTime)
  191. const now = new Date()
  192. const diffHours = (expiry - now) / (1000 * 60 * 60)
  193. return diffHours > 0 && diffHours <= 7 * 24
  194. })
  195. // US-4.3: Expired check
  196. const isExpired = computed(() => {
  197. if (!userStore.vipEndTime) return false
  198. return new Date(userStore.vipEndTime) < new Date()
  199. })
  200. function formatDate(dateStr) {
  201. if (!dateStr) return ''
  202. return dateStr.slice(0, 10)
  203. }
  204. onShow(() => {
  205. if (userStore.isLoggedIn) {
  206. userStore.fetchProfile()
  207. userStore.fetchQuota()
  208. userStore.fetchCommissionSummary()
  209. }
  210. })
  211. function goLogin() {
  212. uni.navigateTo({ url: '/pages/login/index' })
  213. }
  214. function goEditProfile() {
  215. if (!userStore.isLoggedIn) {
  216. uni.navigateTo({ url: '/pages/login/index' })
  217. return
  218. }
  219. uni.navigateTo({ url: '/pages/profile/edit' })
  220. }
  221. function goPayment() {
  222. uni.navigateTo({ url: '/pages/payment/index?product=annual' })
  223. }
  224. function goRenew() {
  225. const product = userStore.vipType === 'practitioner' ? 'practitioner' : 'annual'
  226. uni.navigateTo({ url: `/pages/payment/index?product=${product}` })
  227. }
  228. function goUpgradePractitioner() {
  229. uni.navigateTo({ url: '/pages/payment/index?product=practitioner' })
  230. }
  231. function goRecords() {
  232. uni.switchTab({ url: '/pages/records/index' })
  233. }
  234. function goAcademicOrientation() {
  235. // EPIC 9: Navigate to academic orientation history
  236. uni.navigateTo({ url: '/pages/academic/index' })
  237. }
  238. function goCommission() {
  239. if (!userStore.isLoggedIn) {
  240. uni.navigateTo({ url: '/pages/login/index' })
  241. return
  242. }
  243. uni.navigateTo({ url: '/pages/commission/index' })
  244. }
  245. // EPIC 9: Navigate to practitioner workbench
  246. function goWorkbench() {
  247. uni.navigateTo({ url: '/pages/plan/workbench' })
  248. }
  249. function copyReferral() {
  250. if (userStore.referralCode) {
  251. uni.setClipboardData({
  252. data: userStore.referralCode,
  253. success: () => uni.showToast({ title: '已复制推广码', icon: 'success' })
  254. })
  255. }
  256. }
  257. function generatePoster() {
  258. uni.showToast({ title: '海报生成中…', icon: 'none' })
  259. // US-6.5: Backend generates poster with mini-program code
  260. }
  261. function goTags() {
  262. uni.navigateTo({ url: '/pages/tags/index' })
  263. }
  264. function goAbout() {
  265. uni.navigateTo({ url: '/pages/about/index' })
  266. }
  267. function handleLogout() {
  268. uni.showModal({
  269. title: '确认退出',
  270. content: '确定要退出登录吗?',
  271. success: (res) => {
  272. if (res.confirm) {
  273. userStore.logout()
  274. uni.showToast({ title: '已退出', icon: 'success' })
  275. }
  276. }
  277. })
  278. }
  279. </script>
  280. <style scoped lang="scss">
  281. // Brand colors (aligned with style-guide.md)
  282. $blue-dark: #1E3A5F;
  283. $gold: #C9A84C;
  284. $gold-light: #F8F0D8;
  285. $warm-white: #F8F6F1;
  286. $white: #FFFFFF;
  287. $text-primary: #1F2937;
  288. $text-secondary: #6B7280;
  289. $text-muted: #9CA3AF;
  290. $danger: #EF4444;
  291. .page-profile {
  292. padding: 16px;
  293. min-height: 100vh;
  294. background: $warm-white;
  295. }
  296. // ========== Region 1: User Card ==========
  297. .user-card {
  298. background: linear-gradient(135deg, $blue-dark, darken($blue-dark, 6%));
  299. border-radius: 16px;
  300. padding: 32px 20px 28px;
  301. margin-bottom: 16px;
  302. text-align: center;
  303. &__login {
  304. cursor: pointer;
  305. }
  306. &__logged {
  307. display: flex;
  308. flex-direction: column;
  309. align-items: center;
  310. }
  311. &__nickname {
  312. font-size: 18px;
  313. font-weight: 600;
  314. color: $white;
  315. display: block;
  316. margin-top: 12px;
  317. }
  318. &__login-hint {
  319. font-size: 12px;
  320. color: rgba(255, 255, 255, 0.7);
  321. display: block;
  322. margin-top: 4px;
  323. }
  324. &__tag {
  325. display: inline-flex;
  326. padding: 3px 14px;
  327. border-radius: 20px;
  328. font-size: 12px;
  329. margin-top: 8px;
  330. &--practitioner {
  331. background: $gold;
  332. color: $blue-dark;
  333. font-weight: 600;
  334. }
  335. &--annual {
  336. background: rgba($gold, 0.25);
  337. color: $gold;
  338. }
  339. &--normal {
  340. background: rgba(255, 255, 255, 0.15);
  341. color: rgba(255, 255, 255, 0.8);
  342. }
  343. }
  344. &__summary {
  345. font-size: 12px;
  346. color: rgba(255, 255, 255, 0.55);
  347. margin-top: 6px;
  348. }
  349. }
  350. // Avatar
  351. .avatar {
  352. width: 68px;
  353. height: 68px;
  354. border-radius: 50%;
  355. overflow: visible;
  356. position: relative;
  357. margin: 0 auto;
  358. image {
  359. width: 100%;
  360. height: 100%;
  361. border-radius: 50%;
  362. border: 2px solid rgba(255, 255, 255, 0.3);
  363. }
  364. &--empty {
  365. background: rgba(255, 255, 255, 0.12);
  366. display: flex;
  367. align-items: center;
  368. justify-content: center;
  369. border: 2px dashed rgba(255, 255, 255, 0.25);
  370. }
  371. &__icon {
  372. font-size: 28px;
  373. color: rgba(255, 255, 255, 0.5);
  374. }
  375. &__edit {
  376. position: absolute;
  377. right: -2px;
  378. bottom: -2px;
  379. width: 24px;
  380. height: 24px;
  381. border-radius: 50%;
  382. background: $gold;
  383. display: flex;
  384. align-items: center;
  385. justify-content: center;
  386. z-index: 1;
  387. cursor: pointer;
  388. &-icon {
  389. font-size: 12px;
  390. color: $blue-dark;
  391. font-weight: 700;
  392. }
  393. }
  394. }
  395. // ========== Region 2: Membership Status ==========
  396. // Banners
  397. .banner {
  398. display: flex;
  399. align-items: center;
  400. justify-content: space-between;
  401. padding: 10px 14px;
  402. border-radius: 10px;
  403. margin-bottom: 12px;
  404. background: #FEF3C7;
  405. border: 1px solid #FDE68A;
  406. &--warning {
  407. background: #FEF3C7;
  408. border-color: #FDE68A;
  409. }
  410. &__text {
  411. font-size: 13px;
  412. color: #92400E;
  413. flex: 1;
  414. }
  415. &__arrow {
  416. font-size: 18px;
  417. color: #92400E;
  418. margin-left: 8px;
  419. }
  420. }
  421. // Seed-price badge
  422. .seed-badge {
  423. display: flex;
  424. align-items: center;
  425. justify-content: center;
  426. padding: 6px 14px;
  427. border-radius: 8px;
  428. margin-bottom: 12px;
  429. background: $gold-light;
  430. border: 1px solid $gold;
  431. &__text {
  432. font-size: 12px;
  433. color: $blue-dark;
  434. font-weight: 500;
  435. }
  436. }
  437. // VIP status card
  438. .vip-card {
  439. border-radius: 12px;
  440. padding: 16px;
  441. display: flex;
  442. justify-content: space-between;
  443. align-items: center;
  444. margin-bottom: 16px;
  445. &__body {
  446. flex: 1;
  447. }
  448. &__title {
  449. font-weight: 600;
  450. font-size: 16px;
  451. display: block;
  452. }
  453. &__desc {
  454. font-size: 12px;
  455. opacity: 0.8;
  456. display: block;
  457. margin-top: 4px;
  458. }
  459. &__action {
  460. font-size: 14px;
  461. font-weight: 500;
  462. padding: 6px 14px;
  463. border-radius: 8px;
  464. white-space: nowrap;
  465. cursor: pointer;
  466. }
  467. // Practitioner
  468. &--practitioner {
  469. background: linear-gradient(135deg, #7c3aed, #6d28d9);
  470. .vip-card__title, .vip-card__desc { color: #fff; }
  471. .vip-card__action {
  472. color: #7c3aed;
  473. background: rgba(255, 255, 255, 0.95);
  474. }
  475. }
  476. // Annual
  477. &--annual {
  478. background: linear-gradient(135deg, #f59e0b, #d97706);
  479. .vip-card__title, .vip-card__desc { color: #fff; }
  480. .vip-card__action {
  481. color: #d97706;
  482. background: rgba(255, 255, 255, 0.95);
  483. }
  484. }
  485. // Expired
  486. &--expired {
  487. background: #E5E7EB;
  488. .vip-card__title { color: $text-secondary; }
  489. .vip-card__desc { color: $text-muted; }
  490. .vip-card__action {
  491. color: $text-secondary;
  492. background: $white;
  493. }
  494. }
  495. // Upsell (free user)
  496. &--upsell {
  497. background: linear-gradient(135deg, $blue-dark, darken($blue-dark, 4%));
  498. flex-direction: column;
  499. align-items: flex-start;
  500. gap: 14px;
  501. .vip-card__title {
  502. color: $gold;
  503. font-size: 16px;
  504. }
  505. & .vip-card__benefits {
  506. display: flex;
  507. flex-direction: column;
  508. gap: 4px;
  509. margin-top: 8px;
  510. }
  511. .vip-card__benefit {
  512. color: rgba(255, 255, 255, 0.8);
  513. font-size: 13px;
  514. }
  515. .vip-card__cta {
  516. align-self: stretch;
  517. text-align: center;
  518. background: $gold;
  519. color: $blue-dark;
  520. font-weight: 600;
  521. font-size: 15px;
  522. padding: 12px;
  523. border-radius: 10px;
  524. cursor: pointer;
  525. }
  526. }
  527. }
  528. // Daily quota
  529. .quota-bar {
  530. background: $white;
  531. border-radius: 12px;
  532. padding: 16px;
  533. margin-bottom: 16px;
  534. display: flex;
  535. align-items: center;
  536. justify-content: space-around;
  537. &__item {
  538. text-align: center;
  539. }
  540. &__label {
  541. font-size: 12px;
  542. color: $text-secondary;
  543. display: block;
  544. margin-bottom: 4px;
  545. }
  546. &__value {
  547. font-size: 20px;
  548. font-weight: 700;
  549. color: $text-primary;
  550. }
  551. &__divider {
  552. width: 1px;
  553. height: 36px;
  554. background: #E5E7EB;
  555. }
  556. }
  557. // ========== Region 3: Feature Menu ==========
  558. .menu-section {
  559. background: $white;
  560. border-radius: 12px;
  561. margin-bottom: 16px;
  562. overflow: hidden;
  563. &__header {
  564. font-size: 12px;
  565. font-weight: 600;
  566. color: $text-muted;
  567. text-transform: uppercase;
  568. letter-spacing: 0.5px;
  569. padding: 14px 16px 6px;
  570. }
  571. }
  572. .menu-item {
  573. display: flex;
  574. align-items: center;
  575. gap: 8px;
  576. padding: 14px 16px;
  577. border-bottom: 1px solid #F3F4F6;
  578. cursor: pointer;
  579. transition: background 0.15s;
  580. &:active {
  581. background: #F9FAFB;
  582. }
  583. &:last-child {
  584. border-bottom: none;
  585. }
  586. &__label {
  587. flex: 1;
  588. font-size: 15px;
  589. color: $text-primary;
  590. &--danger {
  591. color: $danger;
  592. }
  593. }
  594. &__badge {
  595. font-size: 10px;
  596. font-weight: 700;
  597. padding: 2px 6px;
  598. border-radius: 4px;
  599. &--new {
  600. background: #FEF3C7;
  601. color: #D97706;
  602. }
  603. }
  604. &__arrow {
  605. color: $text-muted;
  606. font-size: 18px;
  607. }
  608. &--danger {
  609. justify-content: center;
  610. border-bottom: none;
  611. }
  612. }
  613. // Promotion tools card
  614. .promo-card {
  615. margin: 0 16px 14px;
  616. background: #FFFBEB;
  617. border: 1px solid #FDE68A;
  618. border-radius: 10px;
  619. overflow: hidden;
  620. &__code-area {
  621. display: flex;
  622. align-items: center;
  623. justify-content: center;
  624. gap: 10px;
  625. padding: 14px 16px 8px;
  626. }
  627. &__prefix {
  628. font-size: 13px;
  629. color: $text-secondary;
  630. }
  631. &__code {
  632. font-size: 22px;
  633. font-weight: 700;
  634. color: $blue-dark;
  635. letter-spacing: 3px;
  636. font-family: monospace;
  637. }
  638. &__actions {
  639. display: flex;
  640. align-items: center;
  641. justify-content: center;
  642. gap: 10px;
  643. padding: 6px 16px 14px;
  644. }
  645. &__btn {
  646. font-size: 12px;
  647. color: $blue-dark;
  648. font-weight: 500;
  649. cursor: pointer;
  650. }
  651. &__divider {
  652. color: #D1D5DB;
  653. font-size: 12px;
  654. }
  655. }
  656. </style>