优化:模拟数据显示优化

This commit is contained in:
ivan 2026-03-18 10:36:33 +08:00
parent 748544c883
commit 9d92b4cbfe
9 changed files with 148 additions and 68 deletions

7
.browserslistrc Normal file
View File

@ -0,0 +1,7 @@
# 兼容国产手机、微信内置浏览器、旧版 Android WebView
>= 0.5%
last 2 versions
not dead
Chrome >= 64
Android >= 5
iOS >= 11

1
.env
View File

@ -4,6 +4,7 @@
# VITE_USE_MOCK_DATA=false # 全部关闭 mock
# SSH 部署npm run deploy可选覆盖
VITE_USE_MOCK_WALLET=false # 关闭钱包 mock
# DEPLOY_HOST=38.246.250.238
# DEPLOY_USER=root
# DEPLOY_PATH=/opt/1panel/www/sites/pm.xtrader.vip/index

View File

@ -8,10 +8,10 @@
## 核心能力
- **分类导航**:三层级分类选择(一级 Tab、二级图标、三级 Tab
- **分类导航**:三层级分类选择(一级 Tab、二级文字标签、三级 Tab
- **事件列表**:卡片式展示,支持下拉刷新、触底加载
- **搜索**:可按关键词搜索事件
- **分类筛选**:选中分类后,自动提取所有层级节点的 `tagIds` 进行事件筛选
- **分类筛选**:选中分类后,自动提取所有层级节点的 `tagIds` 进行事件筛选;切换语言时重新请求分类接口并刷新列表
- **Footer**:使用 `<Footer />` 组件,包含品牌、链接、语言选择、免责声明
## 数据流

72
package-lock.json generated
View File

@ -31,6 +31,7 @@
"@vue/eslint-config-typescript": "^14.6.0",
"@vue/test-utils": "^2.4.6",
"@vue/tsconfig": "^0.8.1",
"autoprefixer": "^10.4.27",
"eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-oxlint": "~1.42.0",
@ -40,6 +41,7 @@
"jsdom": "^27.4.0",
"npm-run-all2": "^8.0.4",
"oxlint": "~1.42.0",
"postcss": "^8.5.8",
"prettier": "3.8.1",
"typescript": "~5.9.3",
"vite": "^7.3.1",
@ -3307,6 +3309,43 @@
"url": "https://github.com/sponsors/sxzz"
}
},
"node_modules/autoprefixer": {
"version": "10.4.27",
"resolved": "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.27.tgz",
"integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/autoprefixer"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"browserslist": "^4.28.1",
"caniuse-lite": "^1.0.30001774",
"fraction.js": "^5.3.4",
"picocolors": "^1.1.1",
"postcss-value-parser": "^4.2.0"
},
"bin": {
"autoprefixer": "bin/autoprefixer"
},
"engines": {
"node": "^10 || ^12 || >=14"
},
"peerDependencies": {
"postcss": "^8.1.0"
}
},
"node_modules/available-typed-arrays": {
"version": "1.0.7",
"resolved": "https://registry.npmmirror.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
@ -3715,9 +3754,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001767",
"resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001767.tgz",
"integrity": "sha512-34+zUAMhSH+r+9eKmYG+k2Rpt8XttfE4yXAjoZvkAPs15xcYQhyBYdalJ65BzivAvGRMViEjy6oKr/S91loekQ==",
"version": "1.0.30001780",
"resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001780.tgz",
"integrity": "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==",
"dev": true,
"funding": [
{
@ -5042,6 +5081,20 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/fraction.js": {
"version": "5.3.4",
"resolved": "https://registry.npmmirror.com/fraction.js/-/fraction.js-5.3.4.tgz",
"integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": "*"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/rawify"
}
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz",
@ -6867,9 +6920,9 @@
}
},
"node_modules/postcss": {
"version": "8.5.6",
"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"version": "8.5.8",
"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.8.tgz",
"integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
"funding": [
{
"type": "opencollective",
@ -6908,6 +6961,13 @@
"node": ">=4"
}
},
"node_modules/postcss-value-parser": {
"version": "4.2.0",
"resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"dev": true,
"license": "MIT"
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz",

View File

@ -41,6 +41,7 @@
"@vue/eslint-config-typescript": "^14.6.0",
"@vue/test-utils": "^2.4.6",
"@vue/tsconfig": "^0.8.1",
"autoprefixer": "^10.4.27",
"eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-oxlint": "~1.42.0",
@ -50,6 +51,7 @@
"jsdom": "^27.4.0",
"npm-run-all2": "^8.0.4",
"oxlint": "~1.42.0",
"postcss": "^8.5.8",
"prettier": "3.8.1",
"typescript": "~5.9.3",
"vite": "^7.3.1",

5
postcss.config.js Normal file
View File

@ -0,0 +1,5 @@
export default {
plugins: {
autoprefixer: {},
},
}

View File

@ -39,8 +39,9 @@ export function toLwcData(points: [number, number][]): { time: UTCTimestamp; val
for (const [t, v] of sorted) {
const utcSec = t >= 1e12 ? t / 1000 : t
const time = timeToLocal(utcSec)
if (result.length > 0 && result[result.length - 1].time === time) {
result[result.length - 1].value = v
const last = result[result.length - 1]
if (last && last.time === time) {
last.value = v
} else {
result.push({ time, value: v })
}

View File

@ -116,13 +116,6 @@
:class="{ 'home-category-icon-item--active': layerActiveValues[1] === item.id }"
@click="onCategorySelect(1, item.id)"
>
<v-icon
size="24"
class="home-category-icon"
:color="resolveCategoryIconColor(item) ?? 'grey'"
>
{{ item.icon || DEFAULT_CATEGORY_ICON }}
</v-icon>
<span class="home-category-icon-label">{{ item.label }}</span>
</button>
</div>
@ -243,11 +236,9 @@ import {
type EventCardItem,
} from '../api/event'
import {
DEFAULT_CATEGORY_ICON,
enrichWithIcons,
getPmTagMain,
MOCK_CATEGORY_TREE,
resolveCategoryIconColor,
type CategoryTreeNode,
} from '../api/category'
import { USE_MOCK_CATEGORY } from '../config/mock'
@ -480,13 +471,49 @@ function onCardOpenTrade(
const toastStore = useToastStore()
const localeStore = useLocaleStore()
//
/** 加载分类树(接口或 mock完成后初始化选中并触发事件列表加载 */
async function loadCategory() {
function loadEventListAfterCategoryReady() {
const cached = getEventListCache()
if (cached && cached.list.length > 0) {
eventList.value = cached.list
eventPage.value = cached.page
eventTotal.value = cached.total
eventPageSize.value = cached.pageSize
} else {
loadEvents(1, false)
}
}
if (USE_MOCK_CATEGORY) {
categoryTree.value = enrichWithIcons(MOCK_CATEGORY_TREE)
initCategorySelection()
loadEventListAfterCategoryReady()
} else {
try {
const res = await getPmTagMain()
if (res.code === 0 || res.code === 200) {
const data = res.data
categoryTree.value =
Array.isArray(data) && data.length > 0 ? data : enrichWithIcons(MOCK_CATEGORY_TREE)
} else {
categoryTree.value = enrichWithIcons(MOCK_CATEGORY_TREE)
}
} catch {
categoryTree.value = enrichWithIcons(MOCK_CATEGORY_TREE)
}
initCategorySelection()
loadEventListAfterCategoryReady()
}
}
//
watch(
() => localeStore.currentLocale,
() => {
clearEventListCache()
eventPage.value = 1
loadEvents(1, false, activeSearchKeyword.value)
loadCategory()
},
)
@ -605,41 +632,7 @@ function checkScrollLoad() {
}
onMounted(() => {
/** 分类树就绪后加载列表(确保 activeTagIds 已计算,与下拉刷新参数一致) */
function loadEventListAfterCategoryReady() {
const cached = getEventListCache()
if (cached && cached.list.length > 0) {
eventList.value = cached.list
eventPage.value = cached.page
eventTotal.value = cached.total
eventPageSize.value = cached.pageSize
} else {
loadEvents(1, false)
}
}
if (USE_MOCK_CATEGORY) {
categoryTree.value = enrichWithIcons(MOCK_CATEGORY_TREE)
initCategorySelection()
loadEventListAfterCategoryReady()
} else {
getPmTagMain()
.then((res) => {
if (res.code === 0 || res.code === 200) {
const data = res.data
categoryTree.value = Array.isArray(data) && data.length > 0 ? data : enrichWithIcons(MOCK_CATEGORY_TREE)
} else {
categoryTree.value = enrichWithIcons(MOCK_CATEGORY_TREE)
}
initCategorySelection()
loadEventListAfterCategoryReady()
})
.catch(() => {
categoryTree.value = enrichWithIcons(MOCK_CATEGORY_TREE)
initCategorySelection()
loadEventListAfterCategoryReady()
})
}
loadCategory()
nextTick(() => {
const sentinel = sentinelRef.value
if (sentinel) {
@ -996,15 +989,15 @@ onActivated(() => {
}
.home-category-layer--icon {
padding: 12px 16px;
padding: 6px 16px;
}
.home-category-icon-row {
display: flex;
flex-wrap: nowrap;
gap: 8px;
gap: 6px;
overflow-x: auto;
padding-bottom: 4px;
padding-bottom: 2px;
scrollbar-width: none;
}
@ -1015,13 +1008,12 @@ onActivated(() => {
.home-category-icon-item {
flex-shrink: 0;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
min-width: 56px;
padding: 8px 12px;
justify-content: center;
min-width: 44px;
padding: 4px 10px;
border: none;
border-radius: 8px;
border-radius: 6px;
background: transparent;
color: #64748b;
font-size: 12px;

View File

@ -3,6 +3,16 @@ import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { nodePolyfills } from 'vite-plugin-node-polyfills'
/** 移除 crossorigin 属性,避免部分手机浏览器加载资源异常 */
function removeCrossorigin() {
return {
name: 'remove-crossorigin',
transformIndexHtml(html: string) {
return html.replace(/\s*crossorigin\s*/g, ' ')
},
}
}
export default defineConfig({
resolve: {
alias: {
@ -11,17 +21,19 @@ export default defineConfig({
},
plugins: [
vue(),
// 2. 将此插件添加到插件数组中
nodePolyfills({
// 为了彻底解决SIWE等库的问题建议包含以下选项
protocolImports: true,
}),
removeCrossorigin(),
],
// 3. 可选但推荐:显式定义 process.env 以避免其他潜在错误
define: {
'process.env': {},
},
build: {
target: 'es2020',
cssTarget: 'chrome64',
},
server: {
host: true, // 监听 0.0.0.0,允许局域网内其他设备访问
host: true,
},
})