Files
Stockbar/StockMonitor/Views/DropdownView.swift
T
vgearenandClaude Opus 4.6 592ad90f61 v1.2.5: Add watchlist (观察仓) feature with virtual portfolio support
- New Watchlist model: named virtual portfolios with per-stock cost/shares
- Watchlists persisted to watchlists.json, active ID in settings
- AppState: effective cost/shares/PnL methods that respect active watchlist
- Settings: create/delete/switch watchlists, edit writes to active watchlist
- ProfitSummaryView shows active watchlist name
- UI alignment fixes for toggle and text field padding

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 11:31:59 +08:00

108 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] {
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] {
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 {
ZStack {
stockListView
.opacity(selectedStock == nil ? 1 : 0)
if let stock = selectedStock {
StockChartView(stock: stock, onClose: { selectedStock = nil })
}
}
}
Divider()
ToolbarView(showSettings: $showSettings, sortByChange: $sortByChange)
}
}
.padding(6)
.frame(width: 320)
}
}