hldy_app_mini/pages/login/ceshi.nvue

199 lines
4.2 KiB
Plaintext
Raw Normal View History

2026-03-10 17:13:50 +08:00
<template>
<div class="page">
<div class="controls">
<div class="row">
<button class="btn" @click="generate(2000)">生成 2000 项</button>
<button class="btn" @click="generate(5000)">生成 5000 项</button>
<button class="btn" @click="clearList">清空</button>
</div>
<div class="row">
<text class="stat">FPS: {{fpsDisplay}}</text>
<text class="stat">平均帧(ms): {{avgFrameMsDisplay}}</text>
<text class="stat">items: {{items.length}}</text>
</div>
<div class="hint">说明:向下快速滚动列表以测试渲染/滚动性能nvue 将使用原生渲染。</div>
</div>
<scroll-view class="list" :scroll-y="true" show-scrollbar="true" @scroll="onScroll">
<div class="item" v-for="item in items" :key="item.id">
<div class="thumb" :style="{backgroundColor: item.color}"></div>
<div class="meta">
<text class="title">Item #{{item.id}}</text>
<text class="desc">这是第 {{item.id}} 个条目,用于增加每个 DOM 的复杂度以测试渲染成本。多行文本强制布局多次换行,模拟真实列表。</text>
</div>
</div>
</scroll-view>
</div>
</template>
<script>
export default {
data() {
return {
items: [],
// FPS 测量
measuring: false,
fpsDisplay: 0,
avgFrameMsDisplay: 0,
// 内部用
_frames: [],
_rafId: null,
_idleTO: null
}
},
methods: {
generate(n) {
// 生成带颜色的占位项(避免网络请求)
const list = []
for (let i = 0; i < n; i++) {
list.push({
id: i + 1,
color: this._randColor()
})
}
this.items = list
// 清除测量结果
this.fpsDisplay = 0
this.avgFrameMsDisplay = 0
},
clearList() {
this.items = []
this.fpsDisplay = 0
this.avgFrameMsDisplay = 0
},
_randColor() {
const h = Math.floor(Math.random() * 360)
const s = 60 + Math.floor(Math.random() * 20)
const l = 55 + Math.floor(Math.random() * 10)
return `hsl(${h} ${s}% ${l}%)`
},
// 滚动回调(来自原生 nvue scroll-view
onScroll(e) {
// 每次 scroll 事件触发时重置空闲计时器
// 开始/继续测量 FPS
if (!this.measuring) this._startMeasure()
clearTimeout(this._idleTO)
this._idleTO = setTimeout(() => {
this._stopMeasure()
}, 300)
},
_startMeasure() {
this.measuring = true
this._frames = []
const pushFrame = (t) => {
this._frames.push(t)
// 保持最近 120 帧
if (this._frames.length > 120) this._frames.shift()
this._rafId = requestAnimationFrame(pushFrame)
}
this._rafId = requestAnimationFrame(pushFrame)
},
_stopMeasure() {
this.measuring = false
if (this._rafId) {
cancelAnimationFrame(this._rafId)
this._rafId = null
}
// 计算 FPS 与平均帧时长
if (this._frames.length < 2) {
this.fpsDisplay = 0
this.avgFrameMsDisplay = 0
return
}
const intervals = []
for (let i = 1; i < this._frames.length; i++) {
intervals.push(this._frames[i] - this._frames[i - 1])
}
const sum = intervals.reduce((a, b) => a + b, 0)
const avg = sum / intervals.length
const fps = 1000 / avg
this.avgFrameMsDisplay = avg.toFixed(2)
this.fpsDisplay = Math.round(fps)
}
}
}
</script>
<style>
.page {
flex: 1;
background-color: #f5f6fa;
}
.controls {
padding: 20px;
background: #fff;
}
.row {
flex-direction: row;
align-items: center;
margin-bottom: 10px;
}
.btn {
padding: 10px 14px;
border-radius: 6px;
background: #2d8cf0;
color: #fff;
margin-right: 10px;
}
.stat {
margin-right: 14px;
font-size: 28px;
}
.hint {
color: #888;
font-size: 24px;
margin-top: 6px;
}
.list {
flex: 1;
}
.item {
height: 160px;
padding: 12px;
flex-direction: row;
align-items: center;
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
background: #fff;
}
.thumb {
width: 120px;
height: 120px;
border-radius: 8px;
margin-right: 12px;
}
.meta {
flex: 1;
}
.title {
font-size: 30px;
font-weight: bold;
margin-bottom: 8px;
}
.desc {
font-size: 24px;
color: #666;
line-height: 32px;
}
/* ensure full-height page in nvue */
page,
body,
.page {
height: 100%;
}
</style>