rank.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792
  1. <template>
  2. <view class="page-rank">
  3. <scroll-view class="scroll-view" scroll-y>
  4. <view class="content-wrapper" :class="{ 'blur-content': !isLoggedIn }">
  5. <!-- 上证指数卡片 -->
  6. <view class="index-card">
  7. <view class="index-left">
  8. <view class="index-price-row">
  9. <text :class="['index-price', getIndexChangeClass(indexData.changePercent)]">
  10. {{ formatIndexPrice(indexData.currentPrice) }}
  11. </text>
  12. <text :class="['index-change', getIndexChangeClass(indexData.changePercent)]">
  13. {{ indexData.priceChange || '--' }}
  14. </text>
  15. </view>
  16. <view class="index-name-row">
  17. <text class="index-name">{{ indexData.stockName || '上证指数' }}</text>
  18. <text :class="['index-percent', getIndexChangeClass(indexData.changePercent)]">
  19. {{ indexData.changePercent || '--' }}
  20. </text>
  21. </view>
  22. </view>
  23. </view>
  24. <!-- 分段控制器 -->
  25. <view class="segment-control">
  26. <view class="segment-slider" :class="{ 'slider-right': viewMode === 'table' }"></view>
  27. <view
  28. class="segment-item"
  29. :class="{ 'segment-active': viewMode === 'list' }"
  30. @click="setViewMode('list')"
  31. >
  32. <text class="segment-text">热力图</text>
  33. </view>
  34. <view
  35. class="segment-item"
  36. :class="{ 'segment-active': viewMode === 'table' }"
  37. @click="setViewMode('table')"
  38. >
  39. <text class="segment-text">详情</text>
  40. </view>
  41. </view>
  42. <!-- 列表视图 -->
  43. <view v-show="viewMode === 'list'" class="stock-list" :class="{ 'hidden-list': myStocks.length === 0 }">
  44. <stock-list-item
  45. v-for="(stock, index) in myStocks"
  46. :key="stock.code"
  47. :stock="stock"
  48. :show-delete="true"
  49. @delete="removeStock(index)"
  50. @click="handleStockClick(stock, index)"
  51. />
  52. </view>
  53. <!-- 表格视图 -->
  54. <view v-show="viewMode === 'table'" class="stock-table" :class="{ 'hidden-list': myStocks.length === 0 }">
  55. <!-- 表头 -->
  56. <view class="table-header">
  57. <text class="th-name">股票</text>
  58. <text class="th-date">自选日</text>
  59. <text class="th-price">自选价</text>
  60. <text class="th-profit">自选收益</text>
  61. </view>
  62. <!-- 表格内容 -->
  63. <view
  64. v-for="(stock, index) in myStocks"
  65. :key="stock.code"
  66. class="table-row"
  67. @click="handleStockClick(stock, index)"
  68. >
  69. <view class="td-name">
  70. <text class="stock-name">{{ stock.name }}</text>
  71. <view class="stock-code-row">
  72. <text :class="['stock-tag', getMarketClass(stock.code)]">{{ getMarketTag(stock.code) }}</text>
  73. <text class="stock-code">{{ stock.code }}</text>
  74. </view>
  75. </view>
  76. <text class="td-date">{{ stock.addDate || '--' }}</text>
  77. <text class="td-price">{{ formatPrice(stock.addPrice) }}</text>
  78. <text :class="['td-profit', getProfitClass(stock.profitPercent)]">
  79. {{ stock.profitPercent || '--' }}
  80. </text>
  81. </view>
  82. </view>
  83. <!-- 空状态 -->
  84. <view v-if="myStocks.length === 0" class="empty-content">
  85. <view class="empty-icon">📊</view>
  86. <text class="empty-text">暂无收藏股票</text>
  87. <text class="empty-desc">在强势池中点击"+"按钮添加股票</text>
  88. </view>
  89. <!-- 底部安全区域 -->
  90. <view class="bottom-safe-area"></view>
  91. </view>
  92. </scroll-view>
  93. <!-- 未登录遮罩层 -->
  94. <view v-if="!isLoggedIn" class="login-mask">
  95. <view class="login-prompt">
  96. <view class="lock-icon">🔒</view>
  97. <text class="prompt-title">登录后查看我的股票</text>
  98. <text class="prompt-desc">使用微信授权快速登录</text>
  99. <button class="login-button-native" @click="goToLogin">
  100. <text>登录</text>
  101. </button>
  102. </view>
  103. </view>
  104. </view>
  105. </template>
  106. <script setup>
  107. import { ref } from 'vue'
  108. import { onLoad, onShow, onHide, onUnload } from '@dcloudio/uni-app'
  109. import { isLoggedIn as checkLoginStatus } from '../../utils/auth.js'
  110. import { getStockQuotes, getIndexQuote, getUserStocks, deleteUserStock } from '../../utils/api.js'
  111. import StockListItem from '../../components/StockListItem.vue'
  112. const isLoggedIn = ref(false)
  113. const myStocks = ref([])
  114. const viewMode = ref('list') // 'list' 或 'table'
  115. // 切换视图模式
  116. const toggleViewMode = () => {
  117. viewMode.value = viewMode.value === 'list' ? 'table' : 'list'
  118. }
  119. // 设置视图模式(只切换显示,不重新加载数据)
  120. const setViewMode = (mode) => {
  121. viewMode.value = mode
  122. }
  123. const indexData = ref({
  124. stockCode: '000001',
  125. stockName: '上证指数',
  126. currentPrice: null,
  127. priceChange: null,
  128. changePercent: null
  129. })
  130. let refreshTimer = null
  131. // 获取上证指数数据
  132. const fetchIndexData = async () => {
  133. try {
  134. const res = await getIndexQuote('000001')
  135. if (res.code === 200 && res.data) {
  136. indexData.value = { ...indexData.value, ...res.data }
  137. }
  138. } catch (e) {
  139. console.error('[上证指数] 获取失败:', e.message)
  140. }
  141. }
  142. // 格式化指数价格
  143. const formatIndexPrice = (price) => {
  144. if (!price) return '--'
  145. return parseFloat(price).toFixed(2)
  146. }
  147. // 格式化价格
  148. const formatPrice = (price) => {
  149. if (!price) return '--'
  150. return parseFloat(price).toFixed(2)
  151. }
  152. // 获取指数涨跌样式类
  153. const getIndexChangeClass = (changePercent) => {
  154. if (!changePercent) return ''
  155. const str = String(changePercent).replace('%', '').replace('+', '')
  156. const value = parseFloat(str)
  157. if (value > 0) return 'index-up'
  158. if (value < 0) return 'index-down'
  159. return ''
  160. }
  161. // 获取收益样式类
  162. const getProfitClass = (profitPercent) => {
  163. if (!profitPercent) return ''
  164. const str = String(profitPercent).replace('%', '').replace('+', '')
  165. const value = parseFloat(str)
  166. if (value > 0) return 'profit-up'
  167. if (value < 0) return 'profit-down'
  168. return ''
  169. }
  170. // 获取市场标签
  171. const getMarketTag = (code) => {
  172. if (code.startsWith('6')) return '沪'
  173. if (code.startsWith('0')) return '深'
  174. if (code.startsWith('3')) return '创'
  175. return '沪'
  176. }
  177. const getMarketClass = (code) => {
  178. if (code.startsWith('6')) return 'market-sh'
  179. if (code.startsWith('0')) return 'market-sz'
  180. if (code.startsWith('3')) return 'market-cy'
  181. return 'market-sh'
  182. }
  183. // 加载我的股票列表(从服务器数据库查询)
  184. const loadMyStocks = async () => {
  185. console.log('[我的股票] loadMyStocks 开始执行, isLoggedIn=', isLoggedIn.value)
  186. if (!isLoggedIn.value) {
  187. myStocks.value = []
  188. stopAutoRefresh()
  189. return
  190. }
  191. try {
  192. // 显示加载提示
  193. uni.showLoading({ title: '加载中...' })
  194. // 从服务器获取用户自选股票(每次都从数据库查询)
  195. console.log('[我的股票] 调用 getUserStocks 接口')
  196. const res = await getUserStocks()
  197. console.log('[我的股票] 服务器返回:', JSON.stringify(res))
  198. uni.hideLoading()
  199. if (res.code === 200 && res.data) {
  200. // 转换数据格式
  201. myStocks.value = res.data.map(item => ({
  202. code: item.stockCode,
  203. name: item.stockName,
  204. addPrice: item.addPrice,
  205. addDate: item.addDate,
  206. currentPrice: item.currentPrice,
  207. profitPercent: item.profitPercent,
  208. priceChange: item.priceChange,
  209. changePercent: item.changePercent,
  210. trendData: item.trendData
  211. }))
  212. console.log('[我的股票] 加载完成, 股票数量:', myStocks.value.length)
  213. } else {
  214. myStocks.value = []
  215. console.log('[我的股票] 返回数据为空')
  216. }
  217. // 获取上证指数
  218. await fetchIndexData()
  219. // 如果有股票数据,刷新行情
  220. if (myStocks.value.length > 0) {
  221. await refreshAllQuotes()
  222. }
  223. // 登录后启动定时刷新
  224. startAutoRefresh()
  225. } catch (e) {
  226. uni.hideLoading()
  227. console.error('[我的股票] 加载失败:', e)
  228. myStocks.value = []
  229. startAutoRefresh()
  230. }
  231. }
  232. // 批量刷新所有股票行情
  233. const refreshAllQuotes = async () => {
  234. if (myStocks.value.length === 0) return
  235. try {
  236. const codes = myStocks.value.map(stock => stock.code).join(',')
  237. const quoteRes = await getStockQuotes(codes)
  238. if (quoteRes.code === 200 && quoteRes.data && quoteRes.data.length > 0) {
  239. quoteRes.data.forEach(quoteData => {
  240. const index = myStocks.value.findIndex(stock => stock.code === quoteData.stockCode)
  241. if (index !== -1) {
  242. const stock = myStocks.value[index]
  243. stock.priceChange = quoteData.priceChange
  244. stock.changePercent = quoteData.changePercent
  245. stock.currentPrice = quoteData.currentPrice
  246. stock.name = quoteData.stockName || stock.name
  247. stock.trendData = quoteData.trendData || null
  248. // 计算自选收益(当前价格相对于加入价格的涨跌幅)
  249. if (stock.addPrice && quoteData.currentPrice) {
  250. const addPrice = parseFloat(stock.addPrice)
  251. const currentPrice = parseFloat(quoteData.currentPrice)
  252. if (addPrice > 0) {
  253. const profit = ((currentPrice - addPrice) / addPrice * 100).toFixed(2)
  254. stock.profitPercent = profit >= 0 ? `+${profit}%` : `${profit}%`
  255. }
  256. }
  257. }
  258. })
  259. }
  260. } catch (e) {
  261. console.error('[我的股票] 刷新异常:', e.message)
  262. }
  263. }
  264. // 启动定时刷新
  265. const startAutoRefresh = () => {
  266. if (!isLoggedIn.value) return
  267. stopAutoRefresh()
  268. const scheduleNextRefresh = () => {
  269. const delay = 3000 + Math.random() * 1000
  270. refreshTimer = setTimeout(async () => {
  271. await fetchIndexData()
  272. if (myStocks.value.length > 0) {
  273. await refreshAllQuotes()
  274. }
  275. scheduleNextRefresh()
  276. }, delay)
  277. }
  278. scheduleNextRefresh()
  279. }
  280. // 停止定时刷新
  281. const stopAutoRefresh = () => {
  282. if (refreshTimer) {
  283. clearTimeout(refreshTimer)
  284. refreshTimer = null
  285. }
  286. }
  287. // 跳转到登录页
  288. const goToLogin = () => {
  289. uni.navigateTo({ url: '/pages/login/login' })
  290. }
  291. // 点击股票项
  292. const handleStockClick = async (stockItem, idx) => {
  293. console.log('点击股票:', stockItem.name, idx)
  294. // 刷新该股票的最新行情
  295. try {
  296. const quoteRes = await getStockQuotes(stockItem.code)
  297. if (quoteRes.code === 200 && quoteRes.data && quoteRes.data.length > 0) {
  298. const quoteData = quoteRes.data[0]
  299. const stock = myStocks.value[idx]
  300. if (stock) {
  301. stock.priceChange = quoteData.priceChange
  302. stock.changePercent = quoteData.changePercent
  303. stock.currentPrice = quoteData.currentPrice
  304. stock.name = quoteData.stockName || stock.name
  305. stock.trendData = quoteData.trendData || null
  306. // 计算自选收益
  307. if (stock.addPrice && quoteData.currentPrice) {
  308. const addPrice = parseFloat(stock.addPrice)
  309. const currentPrice = parseFloat(quoteData.currentPrice)
  310. if (addPrice > 0) {
  311. const profit = ((currentPrice - addPrice) / addPrice * 100).toFixed(2)
  312. stock.profitPercent = profit >= 0 ? `+${profit}%` : `${profit}%`
  313. }
  314. }
  315. }
  316. }
  317. } catch (e) {
  318. console.error('刷新股票行情失败:', e)
  319. }
  320. uni.showToast({ title: '股票详情功能开发中', icon: 'none' })
  321. }
  322. // 删除股票
  323. const removeStock = async (idx) => {
  324. const stock = myStocks.value[idx]
  325. uni.showModal({
  326. title: '确认删除',
  327. content: `确定要删除 ${stock.name} 吗?`,
  328. confirmText: '删除',
  329. cancelText: '取消',
  330. success: async (res) => {
  331. if (res.confirm) {
  332. try {
  333. // 调用服务器删除接口
  334. await deleteUserStock(stock.code)
  335. // 从本地列表删除
  336. myStocks.value.splice(idx, 1)
  337. uni.showToast({ title: '删除成功', icon: 'success' })
  338. if (myStocks.value.length === 0) {
  339. stopAutoRefresh()
  340. }
  341. } catch (e) {
  342. console.error('删除失败:', e)
  343. uni.showToast({ title: '删除失败', icon: 'none' })
  344. }
  345. }
  346. }
  347. })
  348. }
  349. onLoad(() => {
  350. console.log('[我的股票] onLoad 触发')
  351. isLoggedIn.value = checkLoginStatus()
  352. loadMyStocks()
  353. })
  354. onShow(() => {
  355. console.log('[我的股票] onShow 触发')
  356. isLoggedIn.value = checkLoginStatus()
  357. // 每次页面显示时都从数据库重新加载
  358. loadMyStocks()
  359. uni.setNavigationBarTitle({ title: '量化交易大师' })
  360. })
  361. onHide(() => {
  362. stopAutoRefresh()
  363. })
  364. onUnload(() => {
  365. stopAutoRefresh()
  366. })
  367. </script>
  368. <style>
  369. .page-rank {
  370. display: flex;
  371. flex-direction: column;
  372. background: #f5f6fb;
  373. height: 100vh;
  374. }
  375. .scroll-view {
  376. flex: 1;
  377. height: 0;
  378. }
  379. .content-wrapper {
  380. padding: 32rpx;
  381. min-height: 100%;
  382. }
  383. /* 上证指数卡片 */
  384. .index-card {
  385. display: flex;
  386. align-items: center;
  387. justify-content: space-between;
  388. background: #ffffff;
  389. border-radius: 24rpx;
  390. padding: 32rpx;
  391. margin-bottom: 24rpx;
  392. box-shadow: 0 8rpx 24rpx rgba(37, 52, 94, 0.08);
  393. }
  394. .index-left {
  395. display: flex;
  396. flex-direction: column;
  397. }
  398. .index-price-row {
  399. display: flex;
  400. align-items: baseline;
  401. margin-bottom: 8rpx;
  402. }
  403. .index-price {
  404. font-size: 48rpx;
  405. font-weight: 700;
  406. margin-right: 16rpx;
  407. }
  408. .index-change {
  409. font-size: 28rpx;
  410. font-weight: 600;
  411. }
  412. .index-name-row {
  413. display: flex;
  414. align-items: center;
  415. }
  416. .index-name {
  417. font-size: 26rpx;
  418. color: #666666;
  419. margin-right: 12rpx;
  420. }
  421. .index-percent {
  422. font-size: 26rpx;
  423. font-weight: 600;
  424. }
  425. .index-up {
  426. color: #FF3B30;
  427. }
  428. .index-down {
  429. color: #34C759;
  430. }
  431. /* 分段控制器 */
  432. .segment-control {
  433. display: flex;
  434. align-items: center;
  435. background: #F5F7FA;
  436. border-radius: 32rpx;
  437. padding: 6rpx;
  438. margin-bottom: 24rpx;
  439. position: relative;
  440. height: 64rpx;
  441. box-sizing: border-box;
  442. }
  443. .segment-slider {
  444. position: absolute;
  445. left: 6rpx;
  446. top: 6rpx;
  447. width: calc(50% - 6rpx);
  448. height: calc(100% - 12rpx);
  449. background: #ffffff;
  450. border-radius: 26rpx;
  451. box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.05);
  452. transition: transform 0.3s ease;
  453. z-index: 0;
  454. }
  455. .segment-slider.slider-right {
  456. transform: translateX(100%);
  457. }
  458. .segment-item {
  459. flex: 1;
  460. display: flex;
  461. align-items: center;
  462. justify-content: center;
  463. height: 100%;
  464. z-index: 1;
  465. transition: color 0.3s ease;
  466. }
  467. .segment-icon {
  468. font-size: 24rpx;
  469. margin-right: 8rpx;
  470. color: #999999;
  471. transition: color 0.3s ease;
  472. }
  473. .segment-text {
  474. font-size: 26rpx;
  475. color: #999999;
  476. font-weight: 500;
  477. transition: color 0.3s ease;
  478. }
  479. .segment-active .segment-icon,
  480. .segment-active .segment-text {
  481. color: #E53935;
  482. }
  483. /* 隐藏空列表 */
  484. .hidden-list {
  485. display: none !important;
  486. }
  487. /* 股票列表 */
  488. .stock-list {
  489. display: flex;
  490. flex-direction: column;
  491. background: #ffffff;
  492. border-radius: 24rpx;
  493. padding: 0 32rpx;
  494. box-shadow: 0 8rpx 24rpx rgba(37, 52, 94, 0.08);
  495. }
  496. /* 表格视图 */
  497. .stock-table {
  498. background: #ffffff;
  499. border-radius: 24rpx;
  500. padding: 0 24rpx;
  501. box-shadow: 0 8rpx 24rpx rgba(37, 52, 94, 0.08);
  502. }
  503. .table-header {
  504. display: flex;
  505. align-items: center;
  506. padding: 24rpx 0;
  507. border-bottom: 1rpx solid #f1f2f6;
  508. }
  509. .table-header text {
  510. font-size: 24rpx;
  511. color: #999999;
  512. font-weight: 500;
  513. }
  514. .th-name {
  515. flex: 1;
  516. }
  517. .th-date {
  518. width: 160rpx;
  519. text-align: center;
  520. }
  521. .th-price {
  522. width: 140rpx;
  523. text-align: center;
  524. }
  525. .th-profit {
  526. width: 140rpx;
  527. text-align: right;
  528. }
  529. .table-row {
  530. display: flex;
  531. align-items: center;
  532. padding: 24rpx 0;
  533. border-bottom: 1rpx solid #f1f2f6;
  534. }
  535. .table-row:last-child {
  536. border-bottom: none;
  537. }
  538. .td-name {
  539. flex: 1;
  540. display: flex;
  541. flex-direction: column;
  542. }
  543. .td-name .stock-name {
  544. font-size: 28rpx;
  545. font-weight: 600;
  546. color: #222222;
  547. margin-bottom: 4rpx;
  548. }
  549. .stock-code-row {
  550. display: flex;
  551. align-items: center;
  552. }
  553. .stock-tag {
  554. font-size: 18rpx;
  555. padding: 2rpx 6rpx;
  556. border-radius: 4rpx;
  557. color: #ffffff;
  558. font-weight: 500;
  559. margin-right: 8rpx;
  560. }
  561. .market-sh {
  562. background: #FF3B30;
  563. }
  564. .market-sz {
  565. background: #34C759;
  566. }
  567. .market-cy {
  568. background: #FF9500;
  569. }
  570. .stock-code {
  571. font-size: 22rpx;
  572. color: #9ca2b5;
  573. }
  574. .td-date {
  575. width: 160rpx;
  576. text-align: center;
  577. font-size: 24rpx;
  578. color: #666666;
  579. }
  580. .td-price {
  581. width: 140rpx;
  582. text-align: center;
  583. font-size: 26rpx;
  584. color: #333333;
  585. }
  586. .td-profit {
  587. width: 140rpx;
  588. text-align: right;
  589. font-size: 26rpx;
  590. font-weight: 600;
  591. }
  592. .profit-up {
  593. color: #FF3B30;
  594. }
  595. .profit-down {
  596. color: #34C759;
  597. }
  598. /* 空状态 */
  599. .empty-content {
  600. display: flex;
  601. flex-direction: column;
  602. align-items: center;
  603. justify-content: center;
  604. padding: 200rpx 60rpx;
  605. }
  606. .empty-icon {
  607. font-size: 120rpx;
  608. margin-bottom: 40rpx;
  609. }
  610. .empty-text {
  611. font-size: 32rpx;
  612. font-weight: 600;
  613. color: #333333;
  614. margin-bottom: 16rpx;
  615. }
  616. .empty-desc {
  617. font-size: 26rpx;
  618. color: #999999;
  619. text-align: center;
  620. line-height: 1.6;
  621. }
  622. .bottom-safe-area {
  623. height: 80rpx;
  624. }
  625. /* 模糊效果 */
  626. .blur-content {
  627. filter: blur(8rpx);
  628. pointer-events: none;
  629. }
  630. /* 登录遮罩层 */
  631. .login-mask {
  632. position: fixed;
  633. top: 0;
  634. left: 0;
  635. right: 0;
  636. bottom: 0;
  637. background: rgba(0, 0, 0, 0.4);
  638. display: flex;
  639. align-items: center;
  640. justify-content: center;
  641. z-index: 999;
  642. }
  643. .login-prompt {
  644. width: 560rpx;
  645. max-width: 560rpx;
  646. background: #ffffff;
  647. border-radius: 24rpx;
  648. padding: 50rpx 40rpx;
  649. margin: 0 auto;
  650. text-align: center;
  651. box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.3);
  652. box-sizing: border-box;
  653. }
  654. .lock-icon {
  655. font-size: 72rpx;
  656. margin-bottom: 20rpx;
  657. }
  658. .prompt-title {
  659. display: block;
  660. font-size: 32rpx;
  661. font-weight: 600;
  662. color: #222222;
  663. margin-bottom: 12rpx;
  664. }
  665. .prompt-desc {
  666. display: block;
  667. font-size: 24rpx;
  668. color: #999999;
  669. line-height: 1.5;
  670. margin-bottom: 32rpx;
  671. }
  672. .login-button-native {
  673. width: 100%;
  674. height: 80rpx;
  675. background: linear-gradient(135deg, #4CAF50, #66BB6A);
  676. color: #ffffff;
  677. border-radius: 40rpx;
  678. font-size: 30rpx;
  679. font-weight: 600;
  680. box-shadow: 0 8rpx 24rpx rgba(76, 175, 80, 0.4);
  681. display: flex;
  682. align-items: center;
  683. justify-content: center;
  684. border: none;
  685. padding: 0;
  686. line-height: 80rpx;
  687. }
  688. .login-button-native::after {
  689. border: none;
  690. }
  691. </style>