优化了用户体验

This commit is contained in:
Sebastian
2026-02-06 14:08:36 +08:00
parent 77ee9df1d8
commit 0807a4b107
2 changed files with 266 additions and 24 deletions

View File

@@ -148,9 +148,13 @@
<span class="holding-value">{{ holdings[fund.code].cost.toFixed(4) }}</span>
</div>
<div class="holding-row">
<span class="holding-label">持有金额()</span>
<span class="holding-label">持有金额</span>
<span class="holding-value">¥{{ getHoldingAmount(fund).toFixed(2) }}</span>
</div>
<div class="holding-row">
<span class="holding-label">估算金额</span>
<span class="holding-value">¥{{ getHoldingEstimatedAmount(fund).toFixed(2) }}</span>
</div>
<div class="holding-row">
<span class="holding-label">今日收益()</span>
<span class="holding-value" :class="getHoldingProfitTodayClass(fund)">
@@ -188,32 +192,98 @@
<!-- 持仓设置弹窗 -->
<div v-if="holdingModal.open" class="modal-overlay" @click.self="closeHoldingModal">
<div class="modal-box">
<h3>设置持仓</h3>
<div class="modal-tabs">
<button class="modal-tab" :class="{ active: modalTab === 'set' }" @click="modalTab = 'set'">设置持仓</button>
<button class="modal-tab" :class="{ active: modalTab === 'trade' }" @click="modalTab = 'trade'">加减仓</button>
</div>
<div class="fund-modal-info">
<span class="fund-name">{{ holdingModal.fund?.name }}</span>
<span class="fund-code">#{{ holdingModal.fund?.code }}</span>
<div class="fund-nav-info">
<span>上一交易日净值</span>
<span class="nav-value">{{ holdingModal.fund?.dwjz || '-' }}</span>
<span class="nav-date" v-if="holdingModal.fund?.jzrq">{{ holdingModal.fund.jzrq }}</span>
</div>
<div class="fund-holding-summary" v-if="holdingModal.fund && holdings[holdingModal.fund.code]">
<span>当前份额{{ holdings[holdingModal.fund.code].share.toFixed(2) }} </span>
<span class="summary-sep">|</span>
<span>成本价{{ holdings[holdingModal.fund.code].cost.toFixed(4) }}</span>
</div>
</div>
<div class="form-group">
<label>持有金额 ()</label>
<input
v-model.number="holdingForm.amount"
type="number"
step="any"
placeholder="请输入持有金额"
class="modal-input"
/>
<div class="form-hint" v-if="holdingForm.amount && holdingModal.fund?.dwjz">
折算份额{{ calculateShare(holdingForm.amount, holdingModal.fund.dwjz).toFixed(2) }}
<!-- 设置持仓 Tab -->
<div v-if="modalTab === 'set'">
<div class="form-group">
<label>持有金额 ()</label>
<input
v-model.number="holdingForm.amount"
type="number"
step="any"
placeholder="请输入持有金额"
class="modal-input"
/>
<div class="form-hint" v-if="holdingForm.amount && holdingModal.fund?.dwjz">
折算份额{{ calculateShare(holdingForm.amount, holdingModal.fund.dwjz).toFixed(2) }}
</div>
</div>
<div class="modal-actions">
<button class="btn btn-secondary" @click="closeHoldingModal">取消</button>
<button class="btn btn-danger" @click="clearHolding" v-if="holdingModal.fund && holdings[holdingModal.fund.code]">清空</button>
<button class="btn btn-primary" @click="saveHolding" :disabled="!holdingForm.amount">保存</button>
</div>
</div>
<div class="modal-actions">
<button class="btn btn-secondary" @click="closeHoldingModal">取消</button>
<button class="btn btn-danger" @click="clearHolding" v-if="holdingModal.fund && holdings[holdingModal.fund.code]">清空</button>
<button class="btn btn-primary" @click="saveHolding" :disabled="!holdingForm.amount">保存</button>
<!-- 加减仓 Tab -->
<div v-if="modalTab === 'trade'">
<div class="trade-type-toggle">
<button class="trade-type-btn buy" :class="{ active: tradeForm.type === 'buy' }" @click="tradeForm.type = 'buy'">加仓</button>
<button class="trade-type-btn sell" :class="{ active: tradeForm.type === 'sell' }" @click="tradeForm.type = 'sell'">减仓</button>
</div>
<div class="form-group">
<label>交易净值</label>
<input
v-model.number="tradeForm.nav"
type="number"
step="any"
placeholder="默认使用上一交易日净值"
class="modal-input"
/>
<div class="form-hint">
输入交易确认日的单位净值留空则使用上一交易日净值 {{ holdingModal.fund?.dwjz || '' }}
</div>
</div>
<div class="form-group">
<label>{{ tradeForm.type === 'buy' ? '加仓' : '减仓' }}金额 ()</label>
<input
v-model.number="tradeForm.amount"
type="number"
step="any"
:placeholder="'请输入' + (tradeForm.type === 'buy' ? '加仓' : '减仓') + '金额'"
class="modal-input"
/>
<div class="form-hint" v-if="tradeForm.amount && getTradeNav() > 0">
<template v-if="tradeForm.type === 'buy'">
买入份额{{ (tradeForm.amount / getTradeNav()).toFixed(2) }}
交易后总份额{{ getTradeResultShares().toFixed(2) }}
</template>
<template v-else>
卖出份额{{ (tradeForm.amount / getTradeNav()).toFixed(2) }}
交易后总份额{{ getTradeResultShares().toFixed(2) }}
<span v-if="getTradeResultShares() < 0" class="form-error"> 份额不足</span>
</template>
</div>
</div>
<div class="modal-actions">
<button class="btn btn-secondary" @click="closeHoldingModal">取消</button>
<button
class="btn"
:class="tradeForm.type === 'buy' ? 'btn-primary' : 'btn-danger'"
@click="saveTrade"
:disabled="!tradeForm.amount || tradeForm.amount <= 0 || getTradeResultShares() < 0"
>
确认{{ tradeForm.type === 'buy' ? '加仓' : '减仓' }}
</button>
</div>
</div>
</div>
</div>
@@ -247,6 +317,8 @@ export default {
// 持仓弹窗
const holdingModal = ref({ open: false, fund: null })
const holdingForm = ref({ amount: '' })
const modalTab = ref('set')
const tradeForm = ref({ type: 'buy', amount: '', nav: '' })
// ==================== 计算属性 ====================
const isTradingTime = computed(() => {
@@ -279,7 +351,7 @@ export default {
funds.value.forEach(fund => {
const h = holdings.value[fund.code]
if (h && h.share) {
total += getHoldingAmount(fund)
total += getHoldingEstimatedAmount(fund)
}
})
return total
@@ -328,19 +400,29 @@ export default {
return (num >= 0 ? '+' : '') + num.toFixed(2) + '%'
}
// 持有金额 = 上一交易日净值 × 份额(实际金额)
const getHoldingAmount = (fund) => {
const h = holdings.value[fund.code]
if (!h || !h.share) return 0
const nav = parseFloat(fund.dwjz) || 0
return h.share * nav
}
// 估算金额 = 估算净值 × 份额
const getHoldingEstimatedAmount = (fund) => {
const h = holdings.value[fund.code]
if (!h || !h.share) return 0
const nav = parseFloat(fund.gsz) || parseFloat(fund.dwjz) || 0
return h.share * nav
}
// 今日收益 = 份额 × (估算净值 - 上一交易日净值)
const getHoldingProfitToday = (fund) => {
const h = holdings.value[fund.code]
if (!h || !h.share) return 0
const amount = getHoldingAmount(fund)
const rate = typeof fund.gszzl === 'number' ? fund.gszzl : parseFloat(fund.gszzl) || 0
return amount - (amount / (1 + rate / 100))
const gsz = parseFloat(fund.gsz) || parseFloat(fund.dwjz) || 0
const dwjz = parseFloat(fund.dwjz) || 0
return h.share * (gsz - dwjz)
}
const getHoldingProfitTotal = (fund) => {
@@ -675,6 +757,9 @@ export default {
} else {
holdingForm.value = { amount: '' }
}
// 重置加减仓表单
modalTab.value = h && h.share ? 'trade' : 'set'
tradeForm.value = { type: 'buy', amount: '', nav: '' }
}
const closeHoldingModal = () => {
@@ -717,6 +802,64 @@ export default {
closeHoldingModal()
}
// 获取交易净值(优先使用用户输入,否则使用上一交易日净值)
const getTradeNav = () => {
const inputNav = parseFloat(tradeForm.value.nav)
if (inputNav > 0) return inputNav
return parseFloat(holdingModal.value.fund?.dwjz) || 0
}
// 计算交易后总份额
const getTradeResultShares = () => {
const nav = getTradeNav()
const amount = parseFloat(tradeForm.value.amount) || 0
if (nav <= 0 || amount <= 0) return holdings.value[holdingModal.value.fund?.code]?.share || 0
const tradeShares = amount / nav
const currentShares = holdings.value[holdingModal.value.fund?.code]?.share || 0
return tradeForm.value.type === 'buy' ? currentShares + tradeShares : currentShares - tradeShares
}
// 保存加减仓交易
const saveTrade = () => {
const fund = holdingModal.value.fund
if (!fund) return
const amount = parseFloat(tradeForm.value.amount)
const nav = getTradeNav()
if (!amount || amount <= 0 || nav <= 0) return
const tradeShares = amount / nav
const h = holdings.value[fund.code] || { share: 0, cost: 0 }
const newHoldings = { ...holdings.value }
if (tradeForm.value.type === 'buy') {
// 加仓:计算新的总份额和加权平均成本
const newTotalShares = h.share + tradeShares
const newAvgCost = h.share > 0
? (h.share * h.cost + amount) / newTotalShares
: nav
newHoldings[fund.code] = {
share: newTotalShares,
cost: newAvgCost
}
} else {
// 减仓:份额减少,成本价不变
const newTotalShares = h.share - tradeShares
if (newTotalShares <= 0.01) {
delete newHoldings[fund.code]
} else {
newHoldings[fund.code] = {
share: newTotalShares,
cost: h.cost
}
}
}
holdings.value = newHoldings
localStorage.setItem('realtime_holdings', JSON.stringify(newHoldings))
closeHoldingModal()
}
const saveRefreshMs = () => {
localStorage.setItem('realtime_refresh_ms', refreshMs.value.toString())
// 重启定时器
@@ -788,6 +931,8 @@ export default {
dropdownRef,
holdingModal,
holdingForm,
modalTab,
tradeForm,
isTradingTime,
sortedFunds,
hasHoldings,
@@ -800,6 +945,7 @@ export default {
formatGsz,
formatChange,
getHoldingAmount,
getHoldingEstimatedAmount,
getHoldingProfitToday,
getHoldingProfitTotal,
getHoldingProfitTodayClass,
@@ -816,7 +962,10 @@ export default {
saveHolding,
clearHolding,
saveRefreshMs,
calculateShare
calculateShare,
getTradeNav,
getTradeResultShares,
saveTrade
}
}
}
@@ -1375,6 +1524,99 @@ export default {
font-size: 1.1rem;
}
/* 弹窗 Tab */
.modal-tabs {
display: flex;
gap: 0;
margin-bottom: 16px;
border-bottom: 2px solid var(--border-color);
}
.modal-tab {
flex: 1;
padding: 10px 0;
border: none;
background: transparent;
font-size: 14px;
font-weight: 500;
color: var(--text-secondary);
cursor: pointer;
border-bottom: 2px solid transparent;
margin-bottom: -2px;
transition: all 0.2s;
}
.modal-tab.active {
color: var(--primary-color);
border-bottom-color: var(--primary-color);
}
.modal-tab:hover:not(.active) {
color: var(--text-primary);
}
/* 持仓摘要 */
.fund-holding-summary {
margin-top: 8px;
padding-top: 8px;
border-top: 1px dashed var(--border-color);
font-size: 12px;
color: var(--text-secondary);
display: flex;
align-items: center;
gap: 6px;
}
.summary-sep {
color: var(--border-color);
}
.nav-date {
font-size: 11px;
color: var(--text-tertiary);
}
/* 加减仓切换 */
.trade-type-toggle {
display: flex;
gap: 0;
margin-bottom: 16px;
border-radius: 8px;
overflow: hidden;
border: 1px solid var(--border-color);
}
.trade-type-btn {
flex: 1;
padding: 10px 0;
border: none;
background: var(--bg-primary);
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
color: var(--text-secondary);
}
.trade-type-btn.buy.active {
background: rgba(22, 119, 255, 0.1);
color: var(--primary-color);
}
.trade-type-btn.sell.active {
background: rgba(255, 77, 79, 0.1);
color: var(--danger-color);
}
.trade-type-btn:hover:not(.active) {
background: var(--bg-card);
}
.form-error {
color: var(--danger-color);
font-weight: 600;
}
.fund-modal-info {
margin-bottom: 16px;
padding: 12px;

View File

@@ -547,8 +547,8 @@ export default {
}
}
onMounted(() => {
loadWatchlist()
onMounted(async () => {
await loadWatchlist()
// 启动估值自动刷新
startEstimateRefreshTimer()
})