fix(market-ranking): 对齐 Vue 卡片布局

This commit is contained in:
clz
2026-07-22 16:09:46 +08:00
parent 2e9bd0c275
commit 7c807fcd30
2 changed files with 178 additions and 70 deletions
@@ -13,6 +13,20 @@ export struct MarketRanking {
@State primaryIdx: number = 0;
@State secondaryIdx: number = 0;
@State unfolded: boolean = false;
@State tabViewportWidth: number = 0;
@State tabContentWidth: number = 0;
@State tabScrollOffset: number = 0;
private showTabGradient(): boolean {
const maxOffset = this.tabContentWidth - this.tabViewportWidth;
return maxOffset > 0 && this.tabScrollOffset < maxOffset;
}
private emptyHeight(): number {
const rowHeight = 45;
const headerHeight = 24;
return (this.unfolded ? MAX_SIZE : NORMAL_SIZE) * rowHeight + headerHeight;
}
// 第三列标题随 Tab 变化
private thirdColumnTitle(): string {
@@ -73,40 +87,69 @@ export struct MarketRanking {
.borderRadius(11)
.backgroundColor($r('app.color.pill_inactive_bg'))
.textAlign(TextAlign.Center)
.margin({ left: 16 })
.margin({ left: 8 })
.onClick(() => {
this.unfolded = !this.unfolded;
})
}
.width('100%')
.padding({ bottom: 16 })
.height(48)
.alignItems(VerticalAlign.Center)
// 一级 Tab(胶囊按钮,可横向滚动)
Scroll() {
Row({ space: 8 }) {
ForEach(TAB_METRICS, (metric: TabMetric, index: number) => {
Text(metric.label)
.fontSize(14)
.fontWeight(this.primaryIdx === index ? FontWeight.Bold : FontWeight.Normal)
.fontColor(this.primaryIdx === index ? $r('app.color.text_blue') : $r('app.color.text_secondary'))
.constraintSize({ minWidth: 63 })
.textAlign(TextAlign.Center)
.padding({ top: 6, bottom: 6, left: 12, right: 12 })
.borderRadius(4)
.backgroundColor(this.primaryIdx === index
? $r('app.color.pill_active_bg')
: $r('app.color.pill_inactive_bg'))
.onClick(() => {
this.primaryIdx = index;
this.secondaryIdx = 0;
})
}, (metric: TabMetric) => metric.id)
Stack({ alignContent: Alignment.End }) {
Scroll() {
Row({ space: 8 }) {
ForEach(TAB_METRICS, (metric: TabMetric, index: number) => {
Text(metric.label)
.fontSize(14)
.fontWeight(this.primaryIdx === index ? FontWeight.Bold : FontWeight.Normal)
.fontColor(this.primaryIdx === index
? $r('app.color.text_blue')
: $r('app.color.text_secondary'))
.constraintSize({ minWidth: 63 })
.textAlign(TextAlign.Center)
.padding({ top: 6, bottom: 6, left: 12, right: 12 })
.borderRadius(4)
.backgroundColor(this.primaryIdx === index
? $r('app.color.pill_active_bg')
: $r('app.color.pill_inactive_bg'))
.onClick(() => {
this.primaryIdx = index;
this.secondaryIdx = 0;
})
}, (metric: TabMetric) => metric.id)
}
.onAreaChange((oldValue: Area, newValue: Area) => {
this.tabContentWidth = Number(newValue.width);
})
}
.width('100%')
.height(30)
.padding({ right: 10 })
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.align(Alignment.Start)
.onScroll((xOffset: number, _yOffset: number) => {
this.tabScrollOffset = xOffset;
})
if (this.showTabGradient()) {
Row()
.width(24)
.height(30)
.linearGradient({
angle: 90,
colors: [['#00FFFFFF', 0], [$r('app.color.card_bg'), 1]],
})
.hitTestBehavior(HitTestMode.None)
}
}
.width('100%')
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.align(Alignment.Start)
.height(30)
.onAreaChange((oldValue: Area, newValue: Area) => {
this.tabViewportWidth = Number(newValue.width);
})
// 二级 Tab(仅涨速/跌速出现,右对齐文字 + 分隔线)
if (TAB_METRICS[this.primaryIdx].hasChildren) {
@@ -133,55 +176,100 @@ export struct MarketRanking {
}
.width('100%')
.justifyContent(FlexAlign.End)
.margin({ top: 8 })
.padding({ left: 10, right: 10 })
.margin({ top: 8, left: -10, right: -10 })
}
// 表头
Row() {
Text('合约名称').fontSize(12).fontColor($r('app.color.text_tertiary')).width('35%')
Text('最新价').fontSize(12).fontColor($r('app.color.text_tertiary')).layoutWeight(1).textAlign(TextAlign.End)
Text(this.thirdColumnTitle())
.fontSize(12)
.fontColor($r('app.color.text_tertiary'))
.layoutWeight(1)
.textAlign(TextAlign.End)
}
.width('100%')
.margin({ top: 10 })
.padding({ bottom: 2 })
.border({ width: { bottom: 0.5 }, color: $r('app.color.divider_color') })
if (this.vm.currentList(this.primaryIdx, this.secondaryIdx).length === 0) {
Column() {
Text('暂无数据')
.fontSize(14)
.fontColor($r('app.color.text_grey'))
}
.width('100%')
.height(this.emptyHeight())
.margin({ top: 10 })
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
} else {
// 表头
Row() {
Text('合约名称')
.fontSize(12)
.lineHeight(21)
.fontColor($r('app.color.text_tertiary'))
.width('35%')
Text('最新价')
.fontSize(12)
.lineHeight(21)
.fontColor($r('app.color.text_tertiary'))
.layoutWeight(1)
.margin({ left: 8 })
.textAlign(TextAlign.End)
Text(this.thirdColumnTitle())
.fontSize(12)
.lineHeight(21)
.fontColor($r('app.color.text_tertiary'))
.layoutWeight(1)
.margin({ left: 8 })
.textAlign(TextAlign.End)
}
.width('100%')
.height(23)
.margin({ top: 10 })
.padding({ bottom: 2 })
.alignItems(VerticalAlign.Center)
.border({ width: { bottom: 0.5 }, color: $r('app.color.divider_color') })
// 表格列表
ForEach(
this.vm.currentList(this.primaryIdx, this.secondaryIdx).slice(0, this.unfolded ? MAX_SIZE : NORMAL_SIZE),
(item: RankItem, index: number) => {
Row() {
Text(item.name).fontSize(16).fontColor($r('app.color.text_primary')).width('35%')
Text(`${item.price}`)
.fontSize(16)
.fontColor(riseFallColor(item.price_chg))
.layoutWeight(1)
.textAlign(TextAlign.End)
Text(this.thirdColumnValue(item))
.fontSize(16)
.fontColor(this.thirdColumnColor(item))
.layoutWeight(1)
.textAlign(TextAlign.End)
}
.width('100%')
.padding({ top: 12, bottom: 12 })
.border(index === Math.min(
this.unfolded ? MAX_SIZE : NORMAL_SIZE,
this.vm.currentList(this.primaryIdx, this.secondaryIdx).length,
) - 1
? undefined
: { width: { bottom: 0.5 }, color: $r('app.color.divider_color') })
.onClick(() => {
this.vm.jumpToDetail(item);
})
},
(item: RankItem) => item.code,
)
// 表格列表
ForEach(
this.vm.currentList(this.primaryIdx, this.secondaryIdx).slice(0, this.unfolded ? MAX_SIZE : NORMAL_SIZE),
(item: RankItem, index: number) => {
Row() {
Text(item.name)
.fontSize(16)
.lineHeight(21)
.fontColor($r('app.color.text_primary'))
.width('35%')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(`${item.price}`)
.fontSize(16)
.lineHeight(21)
.fontFamily('monospace')
.fontColor(riseFallColor(item.price_chg))
.layoutWeight(1)
.margin({ left: 8 })
.textAlign(TextAlign.End)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(this.thirdColumnValue(item))
.fontSize(16)
.lineHeight(21)
.fontFamily('monospace')
.fontColor(this.thirdColumnColor(item))
.layoutWeight(1)
.margin({ left: 8 })
.textAlign(TextAlign.End)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
.width('100%')
.height(45)
.alignItems(VerticalAlign.Center)
.border(index === Math.min(
this.unfolded ? MAX_SIZE : NORMAL_SIZE,
this.vm.currentList(this.primaryIdx, this.secondaryIdx).length,
) - 1
? undefined
: { width: { bottom: 0.5 }, color: $r('app.color.divider_color') })
.onClick(() => {
this.vm.jumpToDetail(item);
})
},
(item: RankItem) => item.code,
)
}
}
.width('100%')
}
@@ -26,12 +26,16 @@ export const MOCK_RANK_LISTS: RankItem[][] = [
{ code: 'RB2505', market: '70', name: '螺纹钢2505', price: 3452, price_chg: 2.15, turnover: 1286000 },
{ code: 'M2505', market: '70', name: '豆粕2505', price: 2986, price_chg: 1.87, turnover: 963000 },
{ code: 'I2505', market: '70', name: '铁矿石2505', price: 812, price_chg: -2.86, turnover: 841000 },
{ code: 'AU2506', market: '70', name: '沪金2506', price: 598.4, price_chg: 1.52, turnover: 765000 },
{ code: 'P2505', market: '70', name: '棕榈油2505', price: 7654, price_chg: -2.13, turnover: 628000 },
],
// increPosit 日增仓(原始数值,未换算单位)
[
{ code: 'CU2501', market: '70', name: '沪铜2501', price: 68520, price_chg: 3.21, incre_posit: 12000 },
{ code: 'AU2506', market: '70', name: '沪金2506', price: 598.4, price_chg: 1.52, incre_posit: 9000 },
{ code: 'P2505', market: '70', name: '棕榈油2505', price: 7654, price_chg: -2.13, incre_posit: -6000 },
{ code: 'RB2505', market: '70', name: '螺纹钢2505', price: 3452, price_chg: 2.15, incre_posit: 5200 },
{ code: 'M2505', market: '70', name: '豆粕2505', price: 2986, price_chg: 1.87, incre_posit: 3800 },
],
];
@@ -45,24 +49,32 @@ export const MOCK_SPEED_LISTS: RankItem[][][] = [
{ code: 'CU2501', market: '70', name: '沪铜2501', price: 68520, price_chg: 3.21, chg_speed: 0.85 },
{ code: 'AU2506', market: '70', name: '沪金2506', price: 598.4, price_chg: 1.52, chg_speed: 0.62 },
{ code: 'SR2505', market: '70', name: '白糖2505', price: 6234, price_chg: 1.1, chg_speed: 0.41 },
{ code: 'RB2505', market: '70', name: '螺纹钢2505', price: 3452, price_chg: 2.15, chg_speed: 0.36 },
{ code: 'M2505', market: '70', name: '豆粕2505', price: 2986, price_chg: 1.87, chg_speed: 0.29 },
],
// 5分钟
[
{ code: 'AU2506', market: '70', name: '沪金2506', price: 598.4, price_chg: 1.52, chg_speed: 1.34 },
{ code: 'CU2501', market: '70', name: '沪铜2501', price: 68520, price_chg: 3.21, chg_speed: 1.02 },
{ code: 'M2505', market: '70', name: '豆粕2505', price: 2986, price_chg: 1.87, chg_speed: 0.78 },
{ code: 'RB2505', market: '70', name: '螺纹钢2505', price: 3452, price_chg: 2.15, chg_speed: 0.63 },
{ code: 'SR2505', market: '70', name: '白糖2505', price: 6234, price_chg: 1.1, chg_speed: 0.51 },
],
// 10分钟
[
{ code: 'M2505', market: '70', name: '豆粕2505', price: 2986, price_chg: 1.87, chg_speed: 2.15 },
{ code: 'SR2505', market: '70', name: '白糖2505', price: 6234, price_chg: 1.1, chg_speed: 1.76 },
{ code: 'RB2505', market: '70', name: '螺纹钢2505', price: 3452, price_chg: 2.15, chg_speed: 1.3 },
{ code: 'CU2501', market: '70', name: '沪铜2501', price: 68520, price_chg: 3.21, chg_speed: 1.16 },
{ code: 'AU2506', market: '70', name: '沪金2506', price: 598.4, price_chg: 1.52, chg_speed: 0.92 },
],
// 15分钟
[
{ code: 'RB2505', market: '70', name: '螺纹钢2505', price: 3452, price_chg: 2.15, chg_speed: 2.68 },
{ code: 'CU2501', market: '70', name: '沪铜2501', price: 68520, price_chg: 3.21, chg_speed: 2.21 },
{ code: 'AU2506', market: '70', name: '沪金2506', price: 598.4, price_chg: 1.52, chg_speed: 1.85 },
{ code: 'M2505', market: '70', name: '豆粕2505', price: 2986, price_chg: 1.87, chg_speed: 1.61 },
{ code: 'SR2505', market: '70', name: '白糖2505', price: 6234, price_chg: 1.1, chg_speed: 1.42 },
],
],
// fallSpeed 跌速
@@ -72,24 +84,32 @@ export const MOCK_SPEED_LISTS: RankItem[][][] = [
{ code: 'I2505', market: '70', name: '铁矿石2505', price: 812, price_chg: -2.86, chg_speed: -0.73 },
{ code: 'FG2505', market: '70', name: '玻璃2505', price: 1289, price_chg: -1.34, chg_speed: -0.55 },
{ code: 'SM2505', market: '70', name: '锰硅2505', price: 5876, price_chg: -0.92, chg_speed: -0.38 },
{ code: 'JD2505', market: '70', name: '鸡蛋2505', price: 3568, price_chg: -1.75, chg_speed: -0.31 },
{ code: 'P2505', market: '70', name: '棕榈油2505', price: 7654, price_chg: -2.13, chg_speed: -0.25 },
],
// 5分钟
[
{ code: 'FG2505', market: '70', name: '玻璃2505', price: 1289, price_chg: -1.34, chg_speed: -1.12 },
{ code: 'SM2505', market: '70', name: '锰硅2505', price: 5876, price_chg: -0.92, chg_speed: -0.89 },
{ code: 'I2505', market: '70', name: '铁矿石2505', price: 812, price_chg: -2.86, chg_speed: -0.64 },
{ code: 'JD2505', market: '70', name: '鸡蛋2505', price: 3568, price_chg: -1.75, chg_speed: -0.52 },
{ code: 'P2505', market: '70', name: '棕榈油2505', price: 7654, price_chg: -2.13, chg_speed: -0.43 },
],
// 10分钟
[
{ code: 'SM2505', market: '70', name: '锰硅2505', price: 5876, price_chg: -0.92, chg_speed: -1.87 },
{ code: 'JD2505', market: '70', name: '鸡蛋2505', price: 3568, price_chg: -1.75, chg_speed: -1.45 },
{ code: 'FG2505', market: '70', name: '玻璃2505', price: 1289, price_chg: -1.34, chg_speed: -1.06 },
{ code: 'I2505', market: '70', name: '铁矿石2505', price: 812, price_chg: -2.86, chg_speed: -0.91 },
{ code: 'P2505', market: '70', name: '棕榈油2505', price: 7654, price_chg: -2.13, chg_speed: -0.76 },
],
// 15分钟
[
{ code: 'JD2505', market: '70', name: '鸡蛋2505', price: 3568, price_chg: -1.75, chg_speed: -2.34 },
{ code: 'I2505', market: '70', name: '铁矿石2505', price: 812, price_chg: -2.86, chg_speed: -1.98 },
{ code: 'SM2505', market: '70', name: '锰硅2505', price: 5876, price_chg: -0.92, chg_speed: -1.52 },
{ code: 'FG2505', market: '70', name: '玻璃2505', price: 1289, price_chg: -1.34, chg_speed: -1.31 },
{ code: 'P2505', market: '70', name: '棕榈油2505', price: 7654, price_chg: -2.13, chg_speed: -1.17 },
],
],
];