machine/pages/index/index.vue

388 lines
9.2 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>