11
This commit is contained in:
parent
f23ffa7610
commit
9c703ad75a
@ -1,10 +1,10 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="zh-CN" class="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>vite-project</title>
|
||||
<title>LOF 基金实时溢价监控</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@ -1,5 +1,23 @@
|
||||
<template>
|
||||
<div class="lof-app">
|
||||
<div class="login-overlay" v-if="!authenticated">
|
||||
<div class="login-card">
|
||||
<div class="login-icon">🔒</div>
|
||||
<el-input
|
||||
v-model="password"
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
show-password
|
||||
@keyup.enter="verifyPassword"
|
||||
size="large"
|
||||
/>
|
||||
<el-button type="primary" size="large" @click="verifyPassword" :loading="checking" class="login-btn">
|
||||
验证
|
||||
</el-button>
|
||||
<p class="login-error" v-if="errorMsg">{{ errorMsg }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="lof-app" v-else>
|
||||
<div class="header">
|
||||
<div class="title">
|
||||
<h2>LOF 基金实时溢价监控</h2>
|
||||
@ -7,7 +25,7 @@
|
||||
</div>
|
||||
<div class="tool-bar">
|
||||
<div class="refresh">
|
||||
<el-button @click="manualRefresh" :loading="loading" type="primary" :icon="Refresh">
|
||||
<el-button @click="manualRefresh" :loading="loading" type="primary" :icon="Refresh" size="default">
|
||||
手动刷新
|
||||
</el-button>
|
||||
</div>
|
||||
@ -24,64 +42,87 @@
|
||||
</div>
|
||||
|
||||
<div class="filter-bar">
|
||||
<el-input v-model="searchCode" placeholder="基金代码" clearable style="width: 160px;" />
|
||||
<el-input v-model="searchName" placeholder="基金名称" clearable style="width: 200px;" />
|
||||
<el-select v-model="filterStatus" placeholder="申购状态" clearable style="width: 140px;">
|
||||
<el-input v-model="searchCode" placeholder="基金代码" clearable />
|
||||
<el-input v-model="searchName" placeholder="基金名称" clearable />
|
||||
<el-select v-model="filterStatus" placeholder="申购状态" clearable>
|
||||
<el-option v-for="s in statusOptions" :key="s" :label="s" :value="s" />
|
||||
</el-select>
|
||||
<el-button @click="resetFilter" :icon="RefreshRight">重置</el-button>
|
||||
<el-button @click="resetFilter" :icon="RefreshRight" size="default">重置</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="displayList" v-loading="loading" height="700" border>
|
||||
<el-table-column prop="fundCode" label="基金代码" align="center" />
|
||||
<el-table-column prop="fundName" label="基金名称" align="center" />
|
||||
<el-table-column prop="tradePrice" label="场内价格" align="center" />
|
||||
<el-table-column prop="netValue" label="场外净值(昨日)" align="center" />
|
||||
<el-table-column prop="estimateValue" label="估算净值(实时)" align="center" />
|
||||
<el-table-column label="涨跌幅" align="center">
|
||||
<div class="table-wrapper">
|
||||
<el-table :data="displayList" v-loading="loading" border>
|
||||
<el-table-column prop="fundCode" label="基金代码" align="center" min-width="100" />
|
||||
<el-table-column prop="fundName" label="基金名称" align="center" min-width="140" />
|
||||
<el-table-column prop="tradePrice" label="场内价格" align="center" min-width="90" />
|
||||
<el-table-column prop="netValue" label="场外净值(昨日)" align="center" min-width="120" />
|
||||
<el-table-column prop="estimateValue" label="估算净值(实时)" align="center" min-width="120" />
|
||||
<el-table-column label="涨跌幅" align="center" min-width="80">
|
||||
<template #default="{ row }">
|
||||
<span :class="getRateClass(row.increaseRate)">
|
||||
{{ row.increaseRate }}%
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="溢价率(昨日)" align="center">
|
||||
<el-table-column label="溢价率(昨日)" align="center" min-width="110">
|
||||
<template #default="{ row }">
|
||||
<span :class="getRateClass(row.premiumRate)">
|
||||
{{ row.premiumRate }}%
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="溢价率(实时)" align="center">
|
||||
<el-table-column label="溢价率(实时)" align="center" min-width="110">
|
||||
<template #default="{ row }">
|
||||
<span :class="getRateClass(row.estimatePremiumRate)">
|
||||
{{ row.estimatePremiumRate }}%
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="purchaseLimit" label="日限额" align="center" />
|
||||
<el-table-column label="申购状态" align="center" width="120">
|
||||
<el-table-column prop="purchaseLimit" label="日限额" align="center" min-width="80" />
|
||||
<el-table-column label="申购状态" align="center" min-width="100">
|
||||
<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" />
|
||||
<el-table-column prop="turnover" label="成交额" align="center" />
|
||||
<el-table-column prop="fundSize" label="基金规模" align="center" min-width="100" />
|
||||
<el-table-column prop="turnover" label="成交额" align="center" min-width="80" />
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { ref, onMounted, computed, watch } from 'vue'
|
||||
import axios from 'axios'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Refresh, RefreshRight } from '@element-plus/icons-vue'
|
||||
|
||||
// 后端接口地址
|
||||
const API_URL = 'http://127.0.0.1:8000/api/lof'
|
||||
|
||||
const authenticated = ref(sessionStorage.getItem('auth') === 'true')
|
||||
const password = ref('')
|
||||
const checking = ref(false)
|
||||
const errorMsg = ref('')
|
||||
|
||||
function verifyPassword() {
|
||||
if (checking.value) return
|
||||
checking.value = true
|
||||
errorMsg.value = ''
|
||||
setTimeout(() => {
|
||||
if (password.value === '88') {
|
||||
authenticated.value = true
|
||||
sessionStorage.setItem('auth', 'true')
|
||||
password.value = ''
|
||||
} else {
|
||||
errorMsg.value = '密码错误,请重试'
|
||||
password.value = ''
|
||||
}
|
||||
checking.value = false
|
||||
}, 300)
|
||||
}
|
||||
|
||||
const fundList = ref([])
|
||||
const loading = ref(false)
|
||||
const sortType = ref('desc')
|
||||
@ -96,13 +137,11 @@ function formatTime(date) {
|
||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
|
||||
}
|
||||
|
||||
// 申购状态选项
|
||||
const statusOptions = computed(() => {
|
||||
const set = new Set(fundList.value.map(i => i.purchaseStatus).filter(Boolean))
|
||||
return Array.from(set).sort()
|
||||
})
|
||||
|
||||
// 筛选 + 排序
|
||||
const displayList = computed(() => {
|
||||
let list = [...fundList.value]
|
||||
if (searchCode.value) {
|
||||
@ -129,7 +168,6 @@ function resetFilter() {
|
||||
filterStatus.value = ''
|
||||
}
|
||||
|
||||
// 获取数据
|
||||
async function fetchData() {
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
@ -148,12 +186,10 @@ async function fetchData() {
|
||||
}
|
||||
}
|
||||
|
||||
// 手动刷新
|
||||
function manualRefresh() {
|
||||
fetchData()
|
||||
}
|
||||
|
||||
// 切换排序
|
||||
function toggleSort() {
|
||||
sortType.value = sortType.value === 'desc' ? 'asc' : 'desc'
|
||||
}
|
||||
@ -162,7 +198,6 @@ function toggleSortField() {
|
||||
sortField.value = sortField.value === 'premiumRate' ? 'estimatePremiumRate' : 'premiumRate'
|
||||
}
|
||||
|
||||
// 颜色样式
|
||||
function getRateClass(rate) {
|
||||
const num = parseFloat(rate) || 0
|
||||
if (num > 0) return 'rate-up'
|
||||
@ -171,67 +206,223 @@ function getRateClass(rate) {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (authenticated.value) {
|
||||
fetchData()
|
||||
}
|
||||
})
|
||||
|
||||
watch(authenticated, (val) => {
|
||||
if (val) {
|
||||
fetchData()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.lof-app {
|
||||
max-width: 100%;
|
||||
/* margin: 20px auto; */
|
||||
padding: 24px;
|
||||
.login-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: #0d1117;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
width: 360px;
|
||||
max-width: 90vw;
|
||||
padding: 40px 32px;
|
||||
background: #161b22;
|
||||
border: 1px solid #30363d;
|
||||
border-radius: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.login-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.login-card h2 {
|
||||
color: #e6edf3;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.login-desc {
|
||||
color: #8b949e;
|
||||
font-size: 13px;
|
||||
margin: 0 0 24px 0;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
width: 100%;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.login-error {
|
||||
color: #f85149;
|
||||
font-size: 13px;
|
||||
margin: 12px 0 0 0;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.login-card {
|
||||
padding: 32px 20px;
|
||||
}
|
||||
|
||||
.login-card h2 {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.lof-app {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 20px 16px;
|
||||
min-height: 100vh;
|
||||
background: #0d1117;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 16px;
|
||||
gap: 16px;
|
||||
}
|
||||
.title {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
|
||||
.title h2 {
|
||||
color: #e6edf3;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
margin: 0 0 4px 0;
|
||||
}
|
||||
|
||||
.update-time {
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
text-align: left;
|
||||
font-size: 12px;
|
||||
color: #8b949e;
|
||||
}
|
||||
|
||||
.tool-bar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
button {
|
||||
padding: 4px 10px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.sort {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.sort-label {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
color: #8b949e;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.filter-bar {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 12px;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.filter-bar .el-input {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.filter-bar .el-select {
|
||||
width: 130px;
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #30363d;
|
||||
}
|
||||
|
||||
.table-wrapper :deep(.el-table) {
|
||||
min-width: 900px;
|
||||
}
|
||||
|
||||
.rate-up {
|
||||
color: #f53f3f;
|
||||
color: #f85149;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.rate-down {
|
||||
color: #009944;
|
||||
color: #3fb950;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.rate-zero {
|
||||
color: #666;
|
||||
color: #8b949e;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.lof-app {
|
||||
padding: 12px 8px;
|
||||
}
|
||||
|
||||
.header {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.title h2 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.tool-bar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.filter-bar .el-input {
|
||||
width: calc(50% - 5px);
|
||||
}
|
||||
|
||||
.filter-bar .el-select {
|
||||
width: calc(50% - 5px);
|
||||
}
|
||||
|
||||
.filter-bar {
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.lof-app {
|
||||
padding: 8px 4px;
|
||||
}
|
||||
|
||||
.title h2 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.update-time {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.filter-bar .el-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.filter-bar .el-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tool-bar {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.sort {
|
||||
gap: 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -3,5 +3,6 @@ import './style.css'
|
||||
import App from './App.vue'
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import 'element-plus/theme-chalk/dark/css-vars.css'
|
||||
|
||||
createApp(App).use(ElementPlus).mount('#app')
|
||||
|
||||
@ -1,53 +1,45 @@
|
||||
:root {
|
||||
--text: #6b6375;
|
||||
--text-h: #08060d;
|
||||
--bg: #fff;
|
||||
--border: #e5e4e7;
|
||||
--code-bg: #f4f3ec;
|
||||
--accent: #aa3bff;
|
||||
--accent-bg: rgba(170, 59, 255, 0.1);
|
||||
--accent-border: rgba(170, 59, 255, 0.5);
|
||||
--social-bg: rgba(244, 243, 236, 0.5);
|
||||
--shadow:
|
||||
rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px;
|
||||
--text: #9ca3af;
|
||||
--text-h: #f3f4f6;
|
||||
--bg: #0d1117;
|
||||
--bg-secondary: #161b22;
|
||||
--border: #30363d;
|
||||
--code-bg: #1f2028;
|
||||
--accent: #58a6ff;
|
||||
--accent-bg: rgba(88, 166, 255, 0.15);
|
||||
--accent-border: rgba(88, 166, 255, 0.5);
|
||||
--social-bg: rgba(47, 48, 58, 0.5);
|
||||
--shadow: rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px;
|
||||
|
||||
--sans: system-ui, 'Segoe UI', Roboto, sans-serif;
|
||||
--heading: system-ui, 'Segoe UI', Roboto, sans-serif;
|
||||
--mono: ui-monospace, Consolas, monospace;
|
||||
|
||||
font: 18px/145% var(--sans);
|
||||
font: 16px/145% var(--sans);
|
||||
letter-spacing: 0.18px;
|
||||
color-scheme: light dark;
|
||||
color-scheme: dark;
|
||||
color: var(--text);
|
||||
background: var(--bg);
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--text: #9ca3af;
|
||||
--text-h: #f3f4f6;
|
||||
--bg: #16171d;
|
||||
--border: #2e303a;
|
||||
--code-bg: #1f2028;
|
||||
--accent: #c084fc;
|
||||
--accent-bg: rgba(192, 132, 252, 0.15);
|
||||
--accent-border: rgba(192, 132, 252, 0.5);
|
||||
--social-bg: rgba(47, 48, 58, 0.5);
|
||||
--shadow:
|
||||
rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px;
|
||||
}
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#social .button-icon {
|
||||
filter: invert(1) brightness(2);
|
||||
}
|
||||
body {
|
||||
min-height: 100vh;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
#app {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
body {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user