Files
vgearen 059aef3609 fix(chart): fix top whitespace and KRW number wrap in intraday view
Two bugs in StockChartView triggered by opening a Korean stock's chart:

1. Top whitespace: DropdownView used ZStack to overlay stockListView
   (opacity 0 when chart shown) and StockChartView. ZStack sized to
   max(list, chart) — list at ~720 tall pushed the ~500-tall chart to
   vertical-center, leaving ~200 of blank above the chart. Replace
   ZStack with plain if-let so list and chart are mutually exclusive
   in the layout tree.

2. Stat-row line wrap: fmt used %.3f always, producing 'KRW 284000.000'
   which overflowed the narrow stat columns and wrapped to two lines
   ('284000.0' / '00'). Scale precision by magnitude: ≥1000 → integer,
   ≥10 → 2 decimals, else 3 decimals.
2026-05-14 11:53:11 +08:00

106 lines
4.3 KiB
Swift

import SwiftUI
struct DropdownView: View {
@EnvironmentObject var appState: AppState
@State private var showSettings = false
@State private var sortByChange = false
@State private var selectedStock: Stock? = nil
/// 分组数据:(market, stocks, customTitle?)
private var groupedStocks: [(Market, [Stock], String?)] {
let rule = appState.config.sortRule
let usMode = appState.config.usPriceMode
let doSort = sortByChange
func sorted(_ stocks: [Stock], market: Market) -> [Stock] {
guard doSort else { return stocks }
return stocks.sorted { a, b in
let va = sortValue(quote: appState.quotes[a.id], market: market, mode: usMode)
let vb = sortValue(quote: appState.quotes[b.id], market: market, mode: usMode)
return rule == .changeDesc ? va > vb : va < vb
}
}
var result: [(Market, [Stock], String?)] = []
// 持仓股单独分组
if appState.config.groupHoldings {
let holdings = appState.stocks.filter { appState.effectiveShares(for: $0) != nil }
if !holdings.isEmpty {
// 持仓组内按第一只股票的市场类型排序(混合市场)
let sortedHoldings = doSort ? holdings.sorted { a, b in
let va = sortValue(quote: appState.quotes[a.id], market: a.market, mode: usMode)
let vb = sortValue(quote: appState.quotes[b.id], market: b.market, mode: usMode)
return rule == .changeDesc ? va > vb : va < vb
} : holdings
result.append((.aStock, sortedHoldings, "持仓"))
}
let holdingIds = Set(holdings.map(\.id))
for market in [Market.aStock, .hkStock, .usStock, .krStock] {
let s = appState.stocks.filter { $0.market == market && !holdingIds.contains($0.id) }
if !s.isEmpty { result.append((market, sorted(s, market: market), nil)) }
}
} else {
for market in [Market.aStock, .hkStock, .usStock, .krStock] {
let s = appState.stocks.filter { $0.market == market }
if !s.isEmpty { result.append((market, sorted(s, market: market), nil)) }
}
}
return result
}
private var stockListView: some View {
ScrollView {
VStack(alignment: .leading, spacing: 6) {
ForEach(Array(groupedStocks.enumerated()), id: \.offset) { _, group in
StockGroupView(market: group.0, stocks: group.1,
title: group.2,
onSelect: { selectedStock = $0 })
}
}
.padding(.horizontal, 6)
}
.scrollIndicators(.never)
.frame(maxHeight: 520)
}
private func sortValue(quote: Quote?, market: Market, mode: USPriceMode) -> Double {
guard let q = quote else { return -Double.infinity }
if market == .usStock && mode == .sessionPrice, let ext = q.extendedPrice {
// 用时段价格算涨跌幅:(盘外价 - 正盘价) / 正盘价
return q.price > 0 ? (ext - q.price) / q.price * 100 : q.changePercent
}
return q.changePercent
}
var body: some View {
VStack(alignment: .leading, spacing: 4) {
if showSettings {
SettingsView(showSettings: $showSettings)
} else {
ProfitSummaryView()
if appState.stocks.isEmpty {
Text("暂无股票,点击设置添加")
.font(.system(size: 12))
.foregroundColor(.secondary)
.frame(maxWidth: .infinity)
.padding()
} else if let stock = selectedStock {
StockChartView(stock: stock, onClose: { selectedStock = nil })
} else {
stockListView
}
Divider()
ToolbarView(showSettings: $showSettings, sortByChange: $sortByChange)
}
}
.padding(6)
.frame(width: 320)
.fixedSize(horizontal: false, vertical: true)
.background(Color(nsColor: .windowBackgroundColor))
}
}