mirror of
https://github.com/VGEAREN/Stockbar.git
synced 2026-10-07 09:43:58 +08:00
- 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>
83 lines
3.7 KiB
Swift
83 lines
3.7 KiB
Swift
import SwiftUI
|
|
|
|
struct StockRowView: View {
|
|
let stock: Stock
|
|
let quote: Quote?
|
|
var onTap: (() -> Void)? = nil
|
|
@EnvironmentObject var appState: AppState
|
|
|
|
var body: some View {
|
|
HStack(alignment: .center, spacing: 0) {
|
|
// 左侧:名称 + 代码
|
|
VStack(alignment: .leading, spacing: 1) {
|
|
Text(stock.name)
|
|
.font(.system(size: 12, weight: .medium))
|
|
.lineLimit(1)
|
|
Text(stock.id)
|
|
.font(.system(size: 9))
|
|
.foregroundColor(.secondary)
|
|
}
|
|
Spacer(minLength: 4)
|
|
// 右侧:行情 + 盈亏
|
|
if let q = quote {
|
|
VStack(alignment: .trailing, spacing: 1) {
|
|
HStack(spacing: 4) {
|
|
Text(q.formattedPrice)
|
|
.font(.system(size: 12, weight: .medium))
|
|
.foregroundColor(appState.quoteColor(for: q))
|
|
Text(q.formattedPercent)
|
|
.font(.system(size: 11))
|
|
.foregroundColor(appState.quoteColor(for: q))
|
|
}
|
|
if stock.market == .usStock,
|
|
let session = AppState.usMarketSession(),
|
|
let extPrice = q.formattedExtendedPrice,
|
|
let extPct = q.formattedExtendedPercent {
|
|
Text("\(session) \(extPrice) \(extPct)")
|
|
.font(.system(size: 9))
|
|
.foregroundColor(
|
|
(q.extendedChangePercent ?? 0) >= 0
|
|
? Color(appState.config.upColorName)
|
|
: Color(appState.config.downColorName)
|
|
)
|
|
}
|
|
if appState.effectiveShares(for: stock) == nil {
|
|
Text("未持仓")
|
|
.font(.system(size: 9))
|
|
.foregroundColor(.secondary)
|
|
} else {
|
|
HStack(spacing: 4) {
|
|
if let daily = appState.effectiveDailyPnl(stock: stock, quote: q) {
|
|
Text(daily >= 0
|
|
? "日+\(String(format: "%.2f", daily))"
|
|
: "日\(String(format: "%.2f", daily))")
|
|
.font(.system(size: 10))
|
|
.foregroundColor(appState.pnlColor(daily))
|
|
}
|
|
if let pnl = appState.effectivePnl(stock: stock, quote: q) {
|
|
let pct = appState.effectivePnlPercent(stock: stock, quote: q)
|
|
let pctStr = pct.map { String(format: " %.2f%%", $0) } ?? ""
|
|
Text(pnl >= 0
|
|
? "浮+\(String(format: "%.2f", pnl))\(pctStr)"
|
|
: "浮\(String(format: "%.2f", pnl))\(pctStr)")
|
|
.font(.system(size: 10))
|
|
.foregroundColor(appState.pnlColor(pnl))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
Text("--")
|
|
.foregroundColor(.secondary)
|
|
.font(.system(size: 12))
|
|
}
|
|
}
|
|
.padding(.horizontal, 6)
|
|
.padding(.vertical, 3)
|
|
.background(Color.primary.opacity(0.04))
|
|
.cornerRadius(4)
|
|
.contentShape(Rectangle())
|
|
.onTapGesture { onTap?() }
|
|
}
|
|
}
|