This commit is contained in:
etoai 2026-06-09 10:34:24 +08:00
parent ea6bf18767
commit fc187fe19a
18 changed files with 73783 additions and 42 deletions

File diff suppressed because one or more lines are too long

View File

@ -396,6 +396,23 @@ def _do_fetch_data():
# 当前价无效时实时溢价率也无效 # 当前价无效时实时溢价率也无效
df.loc[~has_current_price, "估算溢价率"] = None df.loc[~has_current_price, "估算溢价率"] = None
# 实时溢价率2 - 按文章第三/四点算法:
# 被动指数QDII-LOF(三): 估算净值 = T-2净值 × (1+指数涨跌幅) × T日汇率/T-1日汇率
# 主动管理LOF(四): 估算净值 = T-1净值 × (1+基准涨跌幅)
# 实现:用涨跌幅作为市场变动代理,调整过时的最新净值
# 调整净值 = 最新净值 × (1 + 涨跌幅/100)
# 溢价率2 = (当前价 - 调整净值) / 调整净值 × 100
df["估算溢价率2"] = None
valid_for2 = has_current_price & df["最新净值/万份收益"].notna() & (df["最新净值/万份收益"] > 0)
if valid_for2.any():
adjusted_nav = (
df.loc[valid_for2, "最新净值/万份收益"]
* (1 + df.loc[valid_for2, "涨跌幅"].fillna(0) / 100)
)
df.loc[valid_for2, "估算溢价率2"] = (
(df.loc[valid_for2, "最新价"] - adjusted_nav) / adjusted_nav * 100
).round(2)
# 5. 按基金规模拆分大基金≥3000万优先返回小基金后台缓存 # 5. 按基金规模拆分大基金≥3000万优先返回小基金后台缓存
LARGE_FUND_THRESHOLD = 30000000 LARGE_FUND_THRESHOLD = 30000000
large_mask = (df["总市值"] >= LARGE_FUND_THRESHOLD) | (df["总市值"].isna()) | (df["总市值"] <= 0) large_mask = (df["总市值"] >= LARGE_FUND_THRESHOLD) | (df["总市值"].isna()) | (df["总市值"] <= 0)
@ -406,16 +423,17 @@ def _do_fetch_data():
part["限额"] = part["日累计限定金额"].apply(format_limit) part["限额"] = part["日累计限定金额"].apply(format_limit)
part["总市值_格式化"] = part["总市值"].apply(format_amount) part["总市值_格式化"] = part["总市值"].apply(format_amount)
part["成交额_格式化"] = part["成交额"].apply(format_amount) part["成交额_格式化"] = part["成交额"].apply(format_amount)
part["赎回状态"] = part["赎回状态"].fillna("")
part = part[[ part = part[[
"代码", "名称", "最新价", "涨跌幅", "代码", "名称", "最新价", "涨跌幅",
"最新净值/万份收益", "估算净值", "溢价率", "估算溢价率", "最新净值/万份收益", "估算净值", "溢价率", "估算溢价率", "估算溢价率2",
"限额", "申购状态", "限额", "申购状态", "赎回状态",
"总市值_格式化", "成交量", "成交额_格式化" "总市值_格式化", "成交量", "成交额_格式化"
]] ]]
part.columns = [ part.columns = [
"fundCode", "fundName", "tradePrice", "increaseRate", "fundCode", "fundName", "tradePrice", "increaseRate",
"netValue", "estimateValue", "premiumRate", "estimatePremiumRate", "netValue", "estimateValue", "premiumRate", "estimatePremiumRate", "estimatePremiumRate2",
"purchaseLimit", "purchaseStatus", "purchaseLimit", "purchaseStatus", "redemptionStatus",
"fundSize", "volume", "turnover" "fundSize", "volume", "turnover"
] ]
part = part.replace({pd.NA: "-"}) part = part.replace({pd.NA: "-"})

View File

@ -117,14 +117,15 @@ def fetch_purchase_data():
temp_df = pd.DataFrame(data_json["datas"]) temp_df = pd.DataFrame(data_json["datas"])
# datas 列顺序0基金代码 1基金简称 2基金类型 3最新净值 4净值时间 5申购状态 6赎回状态 # datas 列顺序0基金代码 1基金简称 2基金类型 3最新净值 4净值时间 5申购状态 6赎回状态
# 7下一开放日 8购买起点 9日累计限定金额 10- 11- 12手续费 # 7下一开放日 8购买起点 9日累计限定金额 10- 11- 12手续费
result = temp_df.iloc[:, [0, 3, 9, 5]].copy() result = temp_df.iloc[:, [0, 3, 9, 5, 6]].copy()
result.columns = ["基金代码", "最新净值/万份收益", "日累计限定金额", "申购状态"] result.columns = ["基金代码", "最新净值/万份收益", "日累计限定金额", "申购状态", "赎回状态"]
result["最新净值/万份收益"] = pd.to_numeric(result["最新净值/万份收益"], errors="coerce") result["最新净值/万份收益"] = pd.to_numeric(result["最新净值/万份收益"], errors="coerce")
result["日累计限定金额"] = pd.to_numeric(result["日累计限定金额"], errors="coerce") result["日累计限定金额"] = pd.to_numeric(result["日累计限定金额"], errors="coerce")
except Exception as e: except Exception as e:
logger.warning("直接解析净值/限额数据失败:%s,回退到 akshare", e) logger.warning("直接解析净值/限额数据失败:%s,回退到 akshare", e)
df = ak.fund_purchase_em() df = ak.fund_purchase_em()
result = df[["基金代码", "最新净值/万份收益", "日累计限定金额", "申购状态"]] result = df[["基金代码", "最新净值/万份收益", "日累计限定金额", "申购状态"]]
result["赎回状态"] = ""
# 更新缓存 # 更新缓存
update_purchase_cache(result) update_purchase_cache(result)

View File

@ -0,0 +1 @@
.history-app[data-v-93aff09e]{box-sizing:border-box;width:100%;max-width:1600px;margin:0 auto;padding:24px;overflow-x:auto}.history-header[data-v-93aff09e]{background:var(--bg-elevated);border:1px solid var(--border-color);border-radius:8px;justify-content:space-between;align-items:center;gap:12px;margin-bottom:16px;padding:16px 24px;display:flex}.history-header h2[data-v-93aff09e]{color:var(--text-primary);margin:0;font-size:20px}.back-link[data-v-93aff09e]{color:var(--accent);cursor:pointer;white-space:nowrap;flex-shrink:0;font-size:14px;text-decoration:none;transition:color .2s}.back-link[data-v-93aff09e]:hover{color:#66b1ff}.rate-up[data-v-93aff09e]{color:var(--rate-up);font-weight:700}.rate-down[data-v-93aff09e]{color:var(--rate-down);font-weight:700}.rate-zero[data-v-93aff09e]{color:var(--text-muted)}@media (width<=768px){.history-app[data-v-93aff09e]{padding:12px}.history-header[data-v-93aff09e]{flex-direction:column;align-items:flex-start}.history-header h2[data-v-93aff09e]{word-break:break-all;font-size:16px}.back-link[data-v-93aff09e]{align-self:flex-end}}

View File

@ -1 +0,0 @@
.history-app[data-v-18b3266c]{box-sizing:border-box;width:100%;max-width:1600px;margin:0 auto;padding:24px;overflow-x:auto}.history-header[data-v-18b3266c]{background:var(--bg-elevated);border:1px solid var(--border-color);border-radius:8px;justify-content:space-between;align-items:center;gap:12px;margin-bottom:16px;padding:16px 24px;display:flex}.history-header h2[data-v-18b3266c]{color:var(--text-primary);margin:0;font-size:20px}.back-link[data-v-18b3266c]{color:var(--accent);cursor:pointer;white-space:nowrap;flex-shrink:0;font-size:14px;text-decoration:none;transition:color .2s}.back-link[data-v-18b3266c]:hover{color:#66b1ff}.rate-up[data-v-18b3266c]{color:var(--rate-up);font-weight:700}.rate-down[data-v-18b3266c]{color:var(--rate-down);font-weight:700}.rate-zero[data-v-18b3266c]{color:var(--text-muted)}@media (width<=768px){.history-app[data-v-18b3266c]{padding:12px}.history-header[data-v-18b3266c]{flex-direction:column;align-items:flex-start}.history-header h2[data-v-18b3266c]{word-break:break-all;font-size:16px}.back-link[data-v-18b3266c]{align-self:flex-end}}

View File

@ -1 +1 @@
import{C as e,E as t,S as n,T as r,_ as i,b as a,d as o,f as s,g as c,h as l,i as u,n as d,p as f,r as p,t as m,x as h,y as g}from"./index-CWE7ZHKi.js";import{t as _}from"./axios-C2kjobOc.js";var v={class:`history-app`},y={class:`history-header`},b={class:`header-left`},x=`/api/lof/history`,S=m({__name:`HistoryDetail`,setup(m){let S=d(),C=p(),w=e(S.query.fundCode||``),T=e(S.query.fundName||``),E=e([]),D=e(!1);function O(){C.push(`/`)}async function k(){if(!w.value){u.error(`缺少基金代码参数`);return}D.value=!0;try{let e=await _.get(x,{params:{fund_code:w.value,fund_name:T.value}});e.data.code===200?(E.value=e.data.data,T.value=e.data.fundName||T.value):u.error(e.data.msg||`获取历史数据失败`)}catch{u.error(`请求失败:请确认 Python 后端已启动`)}finally{D.value=!1}}function A(e){if(e==null)return`-`;let t=parseFloat(e);return isNaN(t)?`-`:(t>0?`+`:``)+t+`%`}function j(e){if(e==null)return`-`;let t=parseFloat(e);return isNaN(t)?`-`:(t>0?`+`:``)+t.toFixed(3)+`%`}function M(e){if(e==null)return`-`;let t=parseFloat(e);return isNaN(t)?`-`:(t>0?`+`:``)+t.toFixed(2)}function N(e){let t=parseFloat(e)||0;return t>0?`rate-up`:t<0?`rate-down`:`rate-zero`}return c(()=>{k()}),(e,c)=>{let u=g(`el-table-column`),d=g(`el-table`),p=a(`loading`);return i(),f(`div`,v,[o(`div`,y,[o(`div`,b,[o(`h2`,null,t(T.value)+` (`+t(w.value)+`) 历史数据`,1)]),o(`a`,{class:`back-link`,onClick:O},`← 返回看板`)]),n((i(),s(d,{data:E.value,border:``,height:`calc(100vh - 200px)`,style:{width:`100%`}},{default:h(()=>[l(u,{prop:`date`,label:`价格日期`,align:`center`,"min-width":`110`}),l(u,{prop:`price`,label:`收盘价`,align:`center`,"min-width":`90`}),l(u,{prop:`navDate`,label:`净值日期`,align:`center`,"min-width":`110`}),l(u,{prop:`nav`,label:`净值`,align:`center`,"min-width":`90`}),l(u,{label:`溢价率`,align:`center`,"min-width":`100`},{default:h(({row:e})=>[o(`span`,{class:r(N(e.premiumRate))},t(A(e.premiumRate)),3)]),_:1}),l(u,{prop:`turnover`,label:`成交额(万元)`,align:`center`,"min-width":`120`}),l(u,{prop:`shareVolume`,label:`场内份额(万份)`,align:`center`,"min-width":`130`}),l(u,{label:`场内新增(万份)`,align:`center`,"min-width":`120`},{default:h(({row:e})=>[o(`span`,{class:r(N(e.changeAmount))},t(e.changeAmount==null?`-`:M(e.changeAmount)),3)]),_:1}),l(u,{label:`份额涨幅`,align:`center`,"min-width":`100`},{default:h(({row:e})=>[o(`span`,{class:r(N(e.changePct))},t(e.changePct==null?`-`:j(e.changePct)),3)]),_:1})]),_:1},8,[`data`])),[[p,D.value]])])}}},[[`__scopeId`,`data-v-18b3266c`]]);export{S as default}; import{C as e,E as t,S as n,T as r,_ as i,b as a,d as o,f as s,g as c,h as l,i as u,n as d,p as f,r as p,t as m,x as h,y as g}from"./index-B7jxak6r.js";import{t as _}from"./axios-CMXECuIF.js";var v={class:`history-app`},y={class:`history-header`},b={class:`header-left`},x=`/api/lof/history`,S=m({__name:`HistoryDetail`,setup(m){let S=d(),C=p(),w=e(S.query.fundCode||``),T=e(S.query.fundName||``),E=e([]),D=e(!1);function O(){C.push(`/`)}async function k(){if(!w.value){u.error(`缺少基金代码参数`);return}D.value=!0;try{let e=await _.get(x,{params:{fund_code:w.value,fund_name:T.value}});e.data.code===200?(E.value=e.data.data,T.value=e.data.fundName||T.value):u.error(e.data.msg||`获取历史数据失败`)}catch{u.error(`请求失败:请确认 Python 后端已启动`)}finally{D.value=!1}}function A(e){if(e==null)return`-`;let t=parseFloat(e);return isNaN(t)?`-`:(t>0?`+`:``)+t+`%`}function j(e){if(e==null)return`-`;let t=parseFloat(e);return isNaN(t)?`-`:(t>0?`+`:``)+t.toFixed(3)+`%`}function M(e){if(e==null)return`-`;let t=parseFloat(e);return isNaN(t)?`-`:(t>0?`+`:``)+t.toFixed(2)}function N(e){let t=parseFloat(e)||0;return t>0?`rate-up`:t<0?`rate-down`:`rate-zero`}return c(()=>{k()}),(e,c)=>{let u=g(`el-table-column`),d=g(`el-table`),p=a(`loading`);return i(),f(`div`,v,[o(`div`,y,[o(`div`,b,[o(`h2`,null,t(T.value)+` (`+t(w.value)+`) 历史数据`,1)]),o(`a`,{class:`back-link`,onClick:O},`← 返回看板`)]),n((i(),s(d,{data:E.value,border:``,height:`calc(100vh - 200px)`,style:{width:`100%`}},{default:h(()=>[l(u,{prop:`date`,label:`价格日期`,align:`center`,"min-width":`110`}),l(u,{prop:`price`,label:`收盘价`,align:`center`,"min-width":`90`}),l(u,{prop:`navDate`,label:`净值日期`,align:`center`,"min-width":`110`}),l(u,{prop:`nav`,label:`净值`,align:`center`,"min-width":`90`}),l(u,{label:`溢价率`,align:`center`,"min-width":`100`},{default:h(({row:e})=>[o(`span`,{class:r(N(e.premiumRate))},t(A(e.premiumRate)),3)]),_:1}),l(u,{prop:`turnover`,label:`成交额(万元)`,align:`center`,"min-width":`120`}),l(u,{prop:`shareVolume`,label:`场内份额(万份)`,align:`center`,"min-width":`130`}),l(u,{label:`场内新增(万份)`,align:`center`,"min-width":`120`},{default:h(({row:e})=>[o(`span`,{class:r(N(e.changeAmount))},t(e.changeAmount==null?`-`:M(e.changeAmount)),3)]),_:1}),l(u,{label:`份额涨幅`,align:`center`,"min-width":`100`},{default:h(({row:e})=>[o(`span`,{class:r(N(e.changePct))},t(e.changePct==null?`-`:j(e.changePct)),3)]),_:1})]),_:1},8,[`data`])),[[p,D.value]])])}}},[[`__scopeId`,`data-v-93aff09e`]]);export{S as default};

View File

@ -1 +0,0 @@
.lof-app[data-v-f13f868e]{box-sizing:border-box;width:100%;max-width:1600px;margin:0 auto;padding:24px;overflow-x:auto}.header[data-v-f13f868e]{justify-content:space-between;align-items:center;gap:12px;margin-bottom:12px;display:flex}.title[data-v-f13f868e]{flex-direction:column;flex-shrink:0;gap:4px;display:flex}.update-time[data-v-f13f868e]{color:var(--text-muted);text-align:left;white-space:nowrap;font-size:13px}.tool-bar[data-v-f13f868e]{flex-shrink:0;gap:12px;display:flex}.filter-bar[data-v-f13f868e]{flex-wrap:wrap;align-items:center;gap:10px;margin-bottom:12px;display:flex}.filter-input[data-v-f13f868e]{width:200px}.filter-input-sm[data-v-f13f868e]{width:160px}.filter-select[data-v-f13f868e]{width:140px}.filter-toggle-btn[data-v-f13f868e]{display:none}.rate-up[data-v-f13f868e]{color:var(--rate-up);font-weight:700}.rate-down[data-v-f13f868e]{color:var(--rate-down);font-weight:700}.rate-zero[data-v-f13f868e]{color:var(--text-muted)}.fund-code-link[data-v-f13f868e]{color:var(--accent);cursor:pointer;text-decoration:none}.fund-code-link[data-v-f13f868e]:hover{text-decoration:underline}.lof-app .el-table .cell[data-v-f13f868e]{padding:2px 3px}.lof-app .el-table th.el-table__cell .cell[data-v-f13f868e]{padding:3px}@media (width<=768px){.lof-app[data-v-f13f868e]{padding:8px}.header[data-v-f13f868e]{flex-direction:column;align-items:flex-start}.tool-bar[data-v-f13f868e]{gap:8px;width:100%;display:flex}.tool-bar .el-button[data-v-f13f868e]{flex:1}.filter-toggle-btn[data-v-f13f868e]{display:inline-flex}.filter-bar[data-v-f13f868e]{flex-direction:column;align-items:stretch}.filter-input[data-v-f13f868e],.filter-input-sm[data-v-f13f868e],.filter-select[data-v-f13f868e]{width:100%}.filter-checkbox[data-v-f13f868e]{justify-content:center;display:flex}.filter-radio[data-v-f13f868e]{width:100%;display:flex}.filter-radio .el-radio-button[data-v-f13f868e]{flex:1}.filter-radio .el-radio-button__inner[data-v-f13f868e]{justify-content:center;width:100%}.filter-reset-btn[data-v-f13f868e]{width:100%}.update-time[data-v-f13f868e]{white-space:normal}.lof-app .el-table[data-v-f13f868e],.lof-app .fund-code-link[data-v-f13f868e]{font-size:12px}.lof-app .el-tag[data-v-f13f868e]{height:22px;padding:0 4px;font-size:11px;line-height:20px}.lof-app .el-switch[data-v-f13f868e]{--el-switch-height:16px;--el-switch-button-size:12px;--el-switch-font-size:11px}}

View File

@ -0,0 +1 @@
.lof-app[data-v-fa10363e]{box-sizing:border-box;width:100%;max-width:1600px;margin:0 auto;padding:24px;overflow-x:auto}.header[data-v-fa10363e]{justify-content:space-between;align-items:center;gap:12px;margin-bottom:12px;display:flex}.title[data-v-fa10363e]{flex-direction:column;flex-shrink:0;gap:4px;display:flex}.update-time[data-v-fa10363e]{color:var(--text-muted);text-align:left;white-space:nowrap;font-size:13px}.tool-bar[data-v-fa10363e]{flex-shrink:0;gap:12px;display:flex}.filter-bar[data-v-fa10363e]{flex-wrap:wrap;align-items:center;gap:10px;margin-bottom:12px;display:flex}.filter-input[data-v-fa10363e]{width:200px}.filter-input-sm[data-v-fa10363e]{width:160px}.filter-select[data-v-fa10363e]{width:140px}.filter-toggle-btn[data-v-fa10363e]{display:none}.rate-up[data-v-fa10363e]{color:var(--rate-up);font-weight:700}.rate-down[data-v-fa10363e]{color:var(--rate-down);font-weight:700}.rate-zero[data-v-fa10363e]{color:var(--text-muted)}.fund-code-link[data-v-fa10363e]{color:var(--accent);cursor:pointer;text-decoration:none}.fund-code-link[data-v-fa10363e]:hover{text-decoration:underline}.lof-app .el-table .cell[data-v-fa10363e]{padding:2px 3px}.lof-app .el-table th.el-table__cell .cell[data-v-fa10363e]{padding:3px}@media (width<=768px){.lof-app[data-v-fa10363e]{padding:8px}.header[data-v-fa10363e]{flex-direction:column;align-items:flex-start}.tool-bar[data-v-fa10363e]{gap:8px;width:100%;display:flex}.tool-bar .el-button[data-v-fa10363e]{flex:1}.filter-toggle-btn[data-v-fa10363e]{display:inline-flex}.filter-bar[data-v-fa10363e]{flex-direction:column;align-items:stretch}.filter-input[data-v-fa10363e],.filter-input-sm[data-v-fa10363e],.filter-select[data-v-fa10363e]{width:100%}.filter-checkbox[data-v-fa10363e]{justify-content:center;display:flex}.filter-radio[data-v-fa10363e]{width:100%;display:flex}.filter-radio .el-radio-button[data-v-fa10363e]{flex:1}.filter-radio .el-radio-button__inner[data-v-fa10363e]{justify-content:center;width:100%}.filter-reset-btn[data-v-fa10363e]{width:100%}.update-time[data-v-fa10363e]{white-space:normal}.lof-app .el-table[data-v-fa10363e],.lof-app .fund-code-link[data-v-fa10363e]{font-size:12px}.lof-app .el-tag[data-v-fa10363e]{height:22px;padding:0 4px;font-size:11px;line-height:20px}.lof-app .el-switch[data-v-fa10363e]{--el-switch-height:16px;--el-switch-button-size:12px;--el-switch-font-size:11px}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -6,8 +6,8 @@
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>LOF 基金监控</title> <title>LOF 基金监控</title>
<script type="module" crossorigin src="/assets/index-CWE7ZHKi.js"></script> <script type="module" crossorigin src="/assets/index-B7jxak6r.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Ci_MFdXL.css"> <link rel="stylesheet" crossorigin href="/assets/index-DS7e8T_6.css">
</head> </head>
<body> <body>

View File

@ -35,16 +35,34 @@ import { ref } from 'vue'
const ACCESS_PASSWORD = '88' const ACCESS_PASSWORD = '88'
const STORAGE_KEY = 'lof_access_granted' const STORAGE_KEY = 'lof_access_granted'
const EXPIRY_HOURS = 24
const password = ref('') const password = ref('')
const error = ref(false) const error = ref(false)
const isAuthenticated = ref(sessionStorage.getItem(STORAGE_KEY) === 'true')
function isAuthValid() {
try {
const raw = localStorage.getItem(STORAGE_KEY)
if (!raw) return false
const data = JSON.parse(raw)
if (!data || !data.granted) return false
const elapsed = Date.now() - data.time
return elapsed < EXPIRY_HOURS * 3600 * 1000
} catch {
return false
}
}
const isAuthenticated = ref(isAuthValid())
function verifyPassword() { function verifyPassword() {
if (password.value === ACCESS_PASSWORD) { if (password.value === ACCESS_PASSWORD) {
isAuthenticated.value = true isAuthenticated.value = true
error.value = false error.value = false
sessionStorage.setItem(STORAGE_KEY, 'true') localStorage.setItem(STORAGE_KEY, JSON.stringify({
granted: true,
time: Date.now()
}))
} else { } else {
error.value = true error.value = true
password.value = '' password.value = ''

View File

@ -20,6 +20,9 @@
<el-select v-model="filterStatus" placeholder="申购状态" clearable class="filter-select"> <el-select v-model="filterStatus" placeholder="申购状态" clearable class="filter-select">
<el-option v-for="s in statusOptions" :key="s" :label="s" :value="s" /> <el-option v-for="s in statusOptions" :key="s" :label="s" :value="s" />
</el-select> </el-select>
<el-select v-model="filterRedeemStatus" placeholder="赎回状态" clearable class="filter-select">
<el-option v-for="s in redeemStatusOptions" :key="s" :label="s" :value="s" />
</el-select>
<el-checkbox v-model="filterByScale" class="filter-checkbox">规模3000万</el-checkbox> <el-checkbox v-model="filterByScale" class="filter-checkbox">规模3000万</el-checkbox>
<el-radio-group v-model="showOnlyFavorites" size="small" class="filter-radio"> <el-radio-group v-model="showOnlyFavorites" size="small" class="filter-radio">
<el-radio-button :label="false">全部基金</el-radio-button> <el-radio-button :label="false">全部基金</el-radio-button>
@ -49,6 +52,30 @@
</span> </span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="申购状态" align="center" width="85">
<template #default="{ row }">
<el-tag :type="row.purchaseStatus === '暂停申购' ? 'danger' : row.purchaseStatus === '开放申购' ? 'success' : 'info'" size="small" style="white-space: nowrap;">
{{ row.purchaseStatus }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="赎回状态" align="center" width="85">
<template #default="{ row }">
<el-tag :type="row.redemptionStatus === '暂停赎回' ? 'danger' : row.redemptionStatus === '开放赎回' ? 'success' : 'info'" size="small" style="white-space: nowrap;">
{{ row.redemptionStatus || '-' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="purchaseLimit" label="日限额" align="center" min-width="85" sortable :sort-method="(a, b) => numericSort(a, b, 'purchaseLimit')" />
<!-- 溢价率(实时2) - 已隐藏保留计算逻辑
<el-table-column prop="estimatePremiumRate2" label="溢价率(实时2)" align="center" min-width="95" sortable :sort-method="(a, b) => numericSort(a, b, 'estimatePremiumRate2')">
<template #default="{ row }">
<span :class="getRateClass(row.estimatePremiumRate2)">
{{ row.estimatePremiumRate2 }}%
</span>
</template>
</el-table-column>
-->
<el-table-column prop="tradePrice" label="场内价格" align="center" min-width="80" sortable :sort-method="(a, b) => numericSort(a, b, 'tradePrice')" /> <el-table-column prop="tradePrice" label="场内价格" align="center" min-width="80" sortable :sort-method="(a, b) => numericSort(a, b, 'tradePrice')" />
<el-table-column prop="netValue" label="场外净值(昨日)" align="center" min-width="105" sortable :sort-method="(a, b) => numericSort(a, b, 'netValue')" /> <el-table-column prop="netValue" label="场外净值(昨日)" align="center" min-width="105" sortable :sort-method="(a, b) => numericSort(a, b, 'netValue')" />
<el-table-column prop="estimateValue" label="估算净值(实时)" align="center" min-width="105" sortable :sort-method="(a, b) => numericSort(a, b, 'estimateValue')" /> <el-table-column prop="estimateValue" label="估算净值(实时)" align="center" min-width="105" sortable :sort-method="(a, b) => numericSort(a, b, 'estimateValue')" />
@ -59,14 +86,6 @@
</span> </span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="purchaseLimit" label="日限额" align="center" min-width="85" sortable :sort-method="(a, b) => numericSort(a, b, 'purchaseLimit')" />
<el-table-column label="申购状态" align="center" width="85">
<template #default="{ row }">
<el-tag :type="row.purchaseStatus === '暂停申购' ? 'danger' : row.purchaseStatus === '开放申购' ? 'success' : 'info'" size="small" style="white-space: nowrap;">
{{ row.purchaseStatus }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="fundSize" label="基金规模" align="center" min-width="85" sortable :sort-method="(a, b) => numericSort(a, b, 'fundSize')" /> <el-table-column prop="fundSize" label="基金规模" align="center" min-width="85" sortable :sort-method="(a, b) => numericSort(a, b, 'fundSize')" />
<el-table-column prop="turnover" label="成交额" align="center" min-width="85" sortable :sort-method="(a, b) => numericSort(a, b, 'turnover')" /> <el-table-column prop="turnover" label="成交额" align="center" min-width="85" sortable :sort-method="(a, b) => numericSort(a, b, 'turnover')" />
<el-table-column label="关注" align="center" width="55"> <el-table-column label="关注" align="center" width="55">
@ -149,11 +168,12 @@ const loading = ref(false)
const searchCode = ref('') const searchCode = ref('')
const searchName = ref('') const searchName = ref('')
const filterStatus = ref('') const filterStatus = ref('')
const filterRedeemStatus = ref('')
const lastUpdateTime = ref('') const lastUpdateTime = ref('')
const favorites = ref(new Set()) const favorites = ref(new Set())
const showOnlyFavorites = ref(false) const showOnlyFavorites = ref(false)
const filterByScale = ref(true) const filterByScale = ref(true)
const showFilters = ref(false) const showFilters = ref(window.innerWidth > 768)
const router = useRouter() const router = useRouter()
function parseFundSize(sizeStr) { function parseFundSize(sizeStr) {
@ -182,6 +202,12 @@ const statusOptions = computed(() => {
return Array.from(set).sort() return Array.from(set).sort()
}) })
//
const redeemStatusOptions = computed(() => {
const set = new Set(fundList.value.map(i => i.redemptionStatus).filter(Boolean))
return Array.from(set).sort()
})
// //
const displayList = computed(() => { const displayList = computed(() => {
let list = [...fundList.value] let list = [...fundList.value]
@ -197,6 +223,9 @@ const displayList = computed(() => {
if (filterStatus.value) { if (filterStatus.value) {
list = list.filter(i => i.purchaseStatus === filterStatus.value) list = list.filter(i => i.purchaseStatus === filterStatus.value)
} }
if (filterRedeemStatus.value) {
list = list.filter(i => i.redemptionStatus === filterRedeemStatus.value)
}
if (filterByScale.value) { if (filterByScale.value) {
list = list.filter(i => parseFundSize(i.fundSize) >= 30000000) list = list.filter(i => parseFundSize(i.fundSize) >= 30000000)
} }
@ -207,6 +236,7 @@ function resetFilter() {
searchCode.value = '' searchCode.value = ''
searchName.value = '' searchName.value = ''
filterStatus.value = '' filterStatus.value = ''
filterRedeemStatus.value = ''
showOnlyFavorites.value = false showOnlyFavorites.value = false
filterByScale.value = true filterByScale.value = true
} }

View File

@ -0,0 +1,14 @@
{
"folders": [
{
"path": "."
},
{
"path": "../lof_project-main参考"
},
{
"path": "../lof-premium-calculator-main"
}
],
"settings": {}
}