388 lines
9.2 KiB
Vue
388 lines
9.2 KiB
Vue
<template>
|
||
<view class="machine">
|
||
<view class="stage">
|
||
<!-- 顶部横幅 -->
|
||
<view class="top">
|
||
<image class="banner" src="https://resource.leapy.cn/retired/top.png" mode="aspectFill" />
|
||
</view>
|
||
|
||
<!-- 中间:背景 + 标题 + 表格 -->
|
||
<view class="middle">
|
||
<view class="title">
|
||
<text class="text1">中国银行外汇牌价</text>
|
||
<text class="text2">BOC Exchange Rate</text>
|
||
</view>
|
||
|
||
<view class="table-area">
|
||
<swiper class="table-swiper" :indicator-dots="false" :autoplay="true" circular :interval="60000"
|
||
:duration="1000">
|
||
<swiper-item v-for="(list,idx) in tablePages" :key="idx">
|
||
<view class="table">
|
||
<!-- 表头(可换行) -->
|
||
<view class="table-header table-grid">
|
||
<text class="cell">Currency Name</text>
|
||
<text class="cell">Buying Rate</text>
|
||
<text class="cell">Cash Buying Rate</text>
|
||
<text class="cell">Selling Rate</text>
|
||
<text class="cell">Cash Selling Rate</text>
|
||
<text class="cell">Middle Rate</text>
|
||
<text class="cell">Pub Time</text>
|
||
</view>
|
||
|
||
<!-- 表体(不换行) -->
|
||
<view class="table-body">
|
||
<view class="table-row table-grid" v-for="(item,i) in list" :key="i">
|
||
<text class="cell">{{ item.currency_name }}</text>
|
||
<text class="cell num">{{ item.buying_rate }}</text>
|
||
<text class="cell num">{{ item.cash_buying_rate }}</text>
|
||
<text class="cell num">{{ item.selling_rate }}</text>
|
||
<text class="cell num">{{ item.cash_selling_rate }}</text>
|
||
<text class="cell num">{{ item.middle_rate }}</text>
|
||
<text class="cell time">{{ item.pub_time }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</swiper-item>
|
||
</swiper>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部广告(全宽铺满) -->
|
||
<view class="ad-area">
|
||
<swiper class="ad-swiper" :indicator-dots="false" :autoplay="true" circular :interval="20000"
|
||
:duration="1000">
|
||
<swiper-item v-for="(item,index) in swiperList" :key="index">
|
||
<image class="ad-image" :src="item.img" mode="aspectFill" />
|
||
</swiper-item>
|
||
</swiper>
|
||
</view>
|
||
|
||
<!-- 底部装饰 -->
|
||
<view class="bottom">
|
||
<image class="footer" src="https://resource.leapy.cn/retired/bottom.png" mode="aspectFill" />
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
data() {
|
||
return {
|
||
tableData: [],
|
||
tablePages: [],
|
||
refreshTimer: 0,
|
||
headerPx: null,
|
||
swiperList: [{
|
||
img: 'https://resource.leapy.cn/retired/swiper1.png'
|
||
},
|
||
{
|
||
img: 'https://resource.leapy.cn/retired/swiper2.png'
|
||
},
|
||
{
|
||
img: 'https://resource.leapy.cn/retired/swiper3.png'
|
||
}
|
||
],
|
||
vhConf: {
|
||
top: 9,
|
||
title: 7.2,
|
||
ad: 13.5,
|
||
bottom: 7.5,
|
||
row: 2.15,
|
||
theadFallback: 5.2,
|
||
gridLinePx: 1
|
||
},
|
||
tableAreaPadTopVH: 1.0,
|
||
tableAreaPadBottomVH: 1.2,
|
||
tablePadVH: 1.2,
|
||
tableBorderPx: 1,
|
||
safeRowOffset: 1
|
||
}
|
||
},
|
||
onLoad() {
|
||
this.makeRequest()
|
||
this.refreshTimer = setInterval(this.makeRequest, 5 * 60 * 1000)
|
||
this.repaginate()
|
||
// 监听尺寸变化(H5/App 都能触发)
|
||
try {
|
||
// #ifdef H5
|
||
window.addEventListener('resize', this.repaginate)
|
||
// #endif
|
||
// #ifndef H5
|
||
plus.screen && plus.screen.addEventListener('change', this.repaginate)
|
||
// #endif
|
||
} catch (e) {}
|
||
},
|
||
onUnload() {
|
||
clearInterval(this.refreshTimer)
|
||
try {
|
||
// #ifdef H5
|
||
window.removeEventListener('resize', this.repaginate)
|
||
// #endif
|
||
// #ifndef H5
|
||
plus.screen && plus.screen.removeEventListener('change', this.repaginate)
|
||
// #endif
|
||
} catch (e) {}
|
||
},
|
||
watch: {
|
||
tableData() {
|
||
this.repaginate()
|
||
}
|
||
},
|
||
methods: {
|
||
makeRequest() {
|
||
uni.request({
|
||
url: 'http://br.leapy.cn/', // 强烈建议换成 https
|
||
method: 'GET',
|
||
success: (res) => {
|
||
// 打个日志确认拿到数组
|
||
console.log('api ok, length=', Array.isArray(res.data) ? res.data.length : 'non-array')
|
||
this.tableData = Array.isArray(res.data) ? res.data : []
|
||
},
|
||
fail: (err) => {
|
||
console.error('request fail:', err)
|
||
uni.showToast({
|
||
title: '网络错误',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
})
|
||
},
|
||
// 统一拿“可用高度”(App 端不要用 window.innerHeight)
|
||
_viewportHeightPx() {
|
||
// H5 优先用 window.innerHeight
|
||
if (typeof window !== 'undefined' && window.innerHeight) return window.innerHeight
|
||
// App 端:优先 safeArea.height,其次 windowHeight/screenHeight
|
||
const info = uni.getSystemInfoSync()
|
||
if (info.safeArea && info.safeArea.height) return info.safeArea.height
|
||
return info.windowHeight || info.screenHeight || 0
|
||
},
|
||
// 测量表头真实高度(App/H5 都测)
|
||
measureHeader() {
|
||
return new Promise((resolve) => {
|
||
this.$nextTick(() => {
|
||
uni.createSelectorQuery()
|
||
.in(this)
|
||
.select('.table-header')
|
||
.boundingClientRect(rect => {
|
||
if (rect && rect.height) this.headerPx = rect.height
|
||
resolve()
|
||
})
|
||
.exec()
|
||
})
|
||
})
|
||
},
|
||
calcRowsPerPage() {
|
||
const v = this.vhConf
|
||
const viewport = this._viewportHeightPx() || 0
|
||
const vh = (n) => viewport * n / 100
|
||
|
||
const midUsablePx = vh(100 - (v.top + v.ad + v.bottom))
|
||
const titlePx = vh(v.title)
|
||
const headPx = this.headerPx || vh(v.theadFallback)
|
||
const areaPadPx = vh(this.tableAreaPadTopVH + this.tableAreaPadBottomVH)
|
||
const tableChromePx = vh(this.tablePadVH * 2) + this.tableBorderPx * 2
|
||
const tableBodyPx = midUsablePx - titlePx - headPx - areaPadPx - tableChromePx
|
||
|
||
const rowPx = vh(v.row) + v.gridLinePx
|
||
|
||
let rows = Math.max(1, Math.floor((tableBodyPx - v.gridLinePx) / rowPx) - this.safeRowOffset)
|
||
|
||
const leftover = tableBodyPx - rows * rowPx - v.gridLinePx
|
||
if (leftover >= rowPx * 0.8) rows += 1
|
||
|
||
// 防御:若 viewport 拿不到,至少给 10 行,避免空白
|
||
if (!viewport || !isFinite(rows)) rows = Math.min(20, Math.max(8, Math.floor((this.tableData.length ||
|
||
12) / 2)))
|
||
|
||
console.log('rows/page =', rows, ' viewport=', viewport)
|
||
return rows
|
||
},
|
||
async repaginate() {
|
||
await this.measureHeader()
|
||
const r = this.calcRowsPerPage()
|
||
const pages = []
|
||
for (let i = 0; i < this.tableData.length; i += r) {
|
||
pages.push(this.tableData.slice(i, i + r))
|
||
}
|
||
this.tablePages = pages
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
/* 整屏铺满,彻底去掉左右留白 */
|
||
.machine {
|
||
position: fixed;
|
||
inset: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: #fff;
|
||
}
|
||
|
||
.stage {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: grid;
|
||
grid-template-rows: 9vh 1fr 13.5vh 7.5vh;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* 顶/底/广告:贴边全铺 */
|
||
.top,
|
||
.ad-area,
|
||
.bottom {
|
||
padding: 0;
|
||
}
|
||
|
||
.banner,
|
||
.footer,
|
||
.ad-image {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: block;
|
||
}
|
||
|
||
/* 中间内容(背景图) */
|
||
.middle {
|
||
background-image: url('https://resource.leapy.cn/retired/back.png');
|
||
background-size: cover;
|
||
background-position: center;
|
||
background-repeat: no-repeat;
|
||
display: grid;
|
||
grid-template-rows: 7.2vh 1fr;
|
||
padding: 1vh 2vw 0;
|
||
/* 左右用 vw,更稳 */
|
||
}
|
||
|
||
/* 标题 */
|
||
.title {
|
||
color: #3E3A39;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.title .text1 {
|
||
font-size: 3.0vh;
|
||
letter-spacing: .45vh;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.title .text2 {
|
||
font-size: 1.85vh;
|
||
margin-top: .6vh;
|
||
opacity: .95;
|
||
}
|
||
|
||
/* 表格 */
|
||
.table-area {
|
||
overflow: hidden;
|
||
padding-top: 1.0vh;
|
||
padding-bottom: 1.2vh;
|
||
}
|
||
|
||
.table-swiper {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.table {
|
||
width: 100%;
|
||
height: 100%;
|
||
background: #fff;
|
||
padding: 1.2vh;
|
||
box-sizing: border-box;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
border: 1px solid #cacdcf;
|
||
border-radius: .5vh;
|
||
}
|
||
|
||
.table {
|
||
--c: 0.97fr;
|
||
--pub: 1.60fr;
|
||
}
|
||
|
||
.table-grid {
|
||
display: grid;
|
||
grid-template-columns: var(--c) var(--c) var(--c) var(--c) var(--c) var(--c) minmax(16ch, var(--pub));
|
||
}
|
||
|
||
.table-header {
|
||
background: #d8f3ef;
|
||
align-items: stretch;
|
||
border-bottom: 1px solid #cacdcf;
|
||
padding: .25vh 0;
|
||
}
|
||
|
||
.table-header .cell {
|
||
border-right: 1px solid #cacdcf;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
text-align: center;
|
||
white-space: normal;
|
||
overflow-wrap: anywhere;
|
||
line-height: 1.15;
|
||
font-weight: 700;
|
||
font-size: 1.25vh;
|
||
padding: 0 .6vh;
|
||
}
|
||
|
||
.table-header .cell:last-child {
|
||
border-right: none;
|
||
}
|
||
|
||
.table-body {
|
||
flex: 1;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.table-row {
|
||
height: 2.15vh;
|
||
align-items: center;
|
||
border-bottom: 1px solid #cacdcf;
|
||
}
|
||
|
||
.table-row:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.table-row .cell {
|
||
border-right: 1px solid #cacdcf;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 100%;
|
||
font-size: 1.1vh;
|
||
color: #333;
|
||
padding: 0 .6vh;
|
||
box-sizing: border-box;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
font-variant-numeric: tabular-nums;
|
||
font-feature-settings: "tnum";
|
||
}
|
||
|
||
.table-row .cell:last-child {
|
||
border-right: none;
|
||
}
|
||
|
||
.table-row .cell.time {
|
||
white-space: nowrap;
|
||
font-size: 1.1vh;
|
||
}
|
||
|
||
/* 广告位(全宽铺满) */
|
||
.ad-swiper {
|
||
width: 100%;
|
||
height: 100%;
|
||
border-radius: .6vh;
|
||
overflow: hidden;
|
||
}
|
||
</style> |