feat: frontend skeleton done
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
.dashboard {
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
/* Banner */
|
||||
.dashboard-banner {
|
||||
background: #fff;
|
||||
border-radius: var(--radius-card);
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.banner-text h2 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 20px;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.banner-text p {
|
||||
margin: 0;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.banner-image-placeholder {
|
||||
width: 200px;
|
||||
height: 100px;
|
||||
background: var(--color-accent-green);
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--color-primary-green);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* KPI Cards */
|
||||
.kpi-row {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.kpi-card {
|
||||
border-radius: var(--radius-card);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.kpi-icon {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.kpi-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.kpi-value {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.kpi-label {
|
||||
font-size: 12px;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* Content Cards */
|
||||
.cards-row {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.content-card {
|
||||
border-radius: var(--radius-card);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.content-card .ant-card-head-title {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.placeholder-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 32px 16px;
|
||||
text-align: center;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.placeholder-text p {
|
||||
margin: 8px 0 4px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.placeholder-text small {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* Chart Container */
|
||||
.chart-container {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.bars-wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
height: 200px;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.bar-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
max-width: 40px;
|
||||
}
|
||||
|
||||
.bar {
|
||||
width: 24px;
|
||||
border-radius: 4px 4px 0 0;
|
||||
transition: all 0.3s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.bar-label-top {
|
||||
font-size: 9px;
|
||||
color: var(--color-text-secondary);
|
||||
text-align: center;
|
||||
margin-top: 4px;
|
||||
max-width: 40px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.bar-label-bottom {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.bar-label-bottom span {
|
||||
font-size: 9px;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* Chart Legend */
|
||||
.chart-legend {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 11px;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
import { Row, Col, Card, Avatar, Tooltip } from 'antd'
|
||||
import {
|
||||
BarChartOutlined,
|
||||
EyeOutlined,
|
||||
CalendarOutlined,
|
||||
FireOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import './Dashboard.css'
|
||||
|
||||
// 假数据:近 9 期收视(基础目标 0.6,摸高目标 0.9)
|
||||
const fakeEpisodes = [
|
||||
{ id: 1, title: '第 1247 期 现代防空反导大对决', editor: 'simonkoson', share: 0.72 },
|
||||
{ id: 2, title: '第 1246 期 深海暗战', editor: 'simonkoson', share: 0.85 },
|
||||
{ id: 3, title: '第 1245 期 隐形战场', editor: 'simonkoson', share: 0.94 },
|
||||
{ id: 4, title: '第 1244 期 无人机革命', editor: 'simonkoson', share: 0.65 },
|
||||
{ id: 5, title: '第 1243 期 核潜艇传奇', editor: 'simonkoson', share: 0.58 },
|
||||
{ id: 6, title: '第 1242 期 电子战风暴', editor: 'simonkoson', share: 0.88 },
|
||||
{ id: 7, title: '第 1241 期 太空博弈', editor: 'simonkoson', share: 0.91 },
|
||||
{ id: 8, title: '第 1240 期 装甲对决', editor: 'simonkoson', share: 0.67 },
|
||||
{ id: 9, title: '第 1239 期 隐身战机', editor: 'simonkoson', share: 0.55 },
|
||||
]
|
||||
|
||||
const BASE_TARGET = 0.6
|
||||
const STRETCH_TARGET = 0.9
|
||||
|
||||
function getBarColor(share) {
|
||||
if (share > STRETCH_TARGET) return '#cf1322' // 红=优秀
|
||||
if (share >= BASE_TARGET) return '#6b8e6b' // 蓝=达标
|
||||
return '#d4a017' // 绿=待提升
|
||||
}
|
||||
|
||||
function getBarHeight(share) {
|
||||
return Math.round((share / 1.0) * 180)
|
||||
}
|
||||
|
||||
function getShortTitle(title) {
|
||||
return title.length > 12 ? title.slice(0, 12) + '...' : title
|
||||
}
|
||||
|
||||
function Dashboard() {
|
||||
return (
|
||||
<div className="dashboard">
|
||||
{/* 顶部 Banner */}
|
||||
<div className="dashboard-banner">
|
||||
<div className="banner-text">
|
||||
<h2>本月收视最佳</h2>
|
||||
<p>第 1245 期 隐形战场 · 收视份额 0.94</p>
|
||||
</div>
|
||||
<div className="banner-image-placeholder">
|
||||
<span>题图位 · 待 Phase 2 实装</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* KPI Cards */}
|
||||
<Row gutter={16} className="kpi-row">
|
||||
<Col span={8}>
|
||||
<Card className="kpi-card">
|
||||
<div className="kpi-icon" style={{ background: '#e8f5e9' }}>
|
||||
<EyeOutlined style={{ color: '#6b8e6b', fontSize: 24 }} />
|
||||
</div>
|
||||
<div className="kpi-info">
|
||||
<span className="kpi-value">0.82</span>
|
||||
<span className="kpi-label">本期平均收视</span>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Card className="kpi-card">
|
||||
<div className="kpi-icon" style={{ background: '#fff3e0' }}>
|
||||
<FireOutlined style={{ color: '#ff9800', fontSize: 24 }} />
|
||||
</div>
|
||||
<div className="kpi-info">
|
||||
<span className="kpi-value">98.6%</span>
|
||||
<span className="kpi-label">完成率</span>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Card className="kpi-card">
|
||||
<div className="kpi-icon" style={{ background: '#e3f2fd' }}>
|
||||
<CalendarOutlined style={{ color: '#2196f3', fontSize: 24 }} />
|
||||
</div>
|
||||
<div className="kpi-info">
|
||||
<span className="kpi-value">6</span>
|
||||
<span className="kpi-label">未来待排期</span>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{/* 三卡片区域 */}
|
||||
<Row gutter={16} className="cards-row">
|
||||
{/* 热点雷达 */}
|
||||
<Col span={8}>
|
||||
<Card className="content-card" title="热点雷达">
|
||||
<div className="placeholder-text">
|
||||
<BarChartOutlined style={{ fontSize: 32, color: '#ccc', marginBottom: 8 }} />
|
||||
<p>模块 H · Phase 4c 实施</p>
|
||||
<small>热点雷达将在 Phase 4c 开发,作为编导个人首页的核心功能</small>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
{/* 近 9 期收视柱图 */}
|
||||
<Col span={8}>
|
||||
<Card className="content-card" title="近 9 期收视">
|
||||
<div className="chart-container">
|
||||
<div className="bars-wrapper">
|
||||
{fakeEpisodes.map((ep) => (
|
||||
<div key={ep.id} className="bar-item">
|
||||
<Tooltip title={`${ep.title} · ${ep.share}`}>
|
||||
<div
|
||||
className="bar"
|
||||
style={{
|
||||
height: getBarHeight(ep.share),
|
||||
backgroundColor: getBarColor(ep.share),
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
<div className="bar-label-top">{getShortTitle(ep.title)}</div>
|
||||
<div className="bar-label-bottom">
|
||||
<Avatar size={20} style={{ fontSize: 10 }}>{ep.editor[0]}</Avatar>
|
||||
<span>{ep.editor}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="chart-legend">
|
||||
<span className="legend-item"><span className="dot" style={{ background: '#cf1322' }}></span>红=优秀</span>
|
||||
<span className="legend-item"><span className="dot" style={{ background: '#6b8e6b' }}></span>蓝=达标</span>
|
||||
<span className="legend-item"><span className="dot" style={{ background: '#d4a017' }}></span>绿=待提升</span>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
{/* 排播计划 */}
|
||||
<Col span={8}>
|
||||
<Card className="content-card" title="排播计划">
|
||||
<div className="placeholder-text">
|
||||
<CalendarOutlined style={{ fontSize: 32, color: '#ccc', marginBottom: 8 }} />
|
||||
<p>模块 F · Phase 4b 实施</p>
|
||||
<small>甘特图排期将在 Phase 4b 开发,使用 frappe-gantt 实现拖拽排期</small>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Dashboard
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Result } from 'antd'
|
||||
|
||||
function Doco() {
|
||||
return (
|
||||
<Result
|
||||
status="info"
|
||||
title="文稿对齐模块"
|
||||
subTitle="该模块计划在 1.0 主体上线后作为独立 Phase 开发,详见 doco_project_design.md"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default Doco
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Card } from 'antd'
|
||||
import { UserOutlined } from '@ant-design/icons'
|
||||
|
||||
function EditorHome() {
|
||||
return (
|
||||
<Card style={{ borderRadius: 16 }}>
|
||||
<div style={{ textAlign: 'center', padding: '48px 0', color: '#999' }}>
|
||||
<UserOutlined style={{ fontSize: 48, marginBottom: 16 }} />
|
||||
<h3 style={{ color: '#666' }}>个人首页</h3>
|
||||
<p>模块 H · Phase 4c 实施</p>
|
||||
<small>今日热点雷达 · RSS + AI 摘要</small>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditorHome
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Card } from 'antd'
|
||||
import { BookOutlined } from '@ant-design/icons'
|
||||
|
||||
function KnowledgeBase() {
|
||||
return (
|
||||
<Card style={{ borderRadius: 16 }}>
|
||||
<div style={{ textAlign: 'center', padding: '48px 0', color: '#999' }}>
|
||||
<BookOutlined style={{ fontSize: 48, marginBottom: 16 }} />
|
||||
<h3 style={{ color: '#666' }}>知识库</h3>
|
||||
<p>模块 C · Phase 3 实施</p>
|
||||
<small>往期报题单 / 文稿 / 军报语义检索</small>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default KnowledgeBase
|
||||
@@ -0,0 +1,29 @@
|
||||
.login-page {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
background: var(--color-bg-cream);
|
||||
}
|
||||
|
||||
.login-card {
|
||||
width: 400px;
|
||||
border-radius: var(--radius-card);
|
||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.login-header {
|
||||
text-align: center;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.login-header h1 {
|
||||
font-size: 28px;
|
||||
color: var(--color-primary-green);
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.login-header p {
|
||||
color: var(--color-text-secondary);
|
||||
margin: 0;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import { useState } from 'react'
|
||||
import { Form, Input, Button, Card, message } from 'antd'
|
||||
import { UserOutlined, LockOutlined } from '@ant-design/icons'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import useAuthStore from '../../stores/authStore'
|
||||
import './Login.css'
|
||||
|
||||
function Login() {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const navigate = useNavigate()
|
||||
const { login } = useAuthStore()
|
||||
|
||||
const onFinish = async (values) => {
|
||||
setLoading(true)
|
||||
const result = await login(values.username, values.password)
|
||||
setLoading(false)
|
||||
|
||||
if (result.success) {
|
||||
navigate('/dashboard')
|
||||
} else {
|
||||
message.error(result.error || '登录失败')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="login-page">
|
||||
<Card className="login-card" bordered={false}>
|
||||
<div className="login-header">
|
||||
<h1>军事科技</h1>
|
||||
<p>TPS 工作台</p>
|
||||
</div>
|
||||
<Form
|
||||
name="login"
|
||||
onFinish={onFinish}
|
||||
autoComplete="off"
|
||||
layout="vertical"
|
||||
requiredMark={false}
|
||||
>
|
||||
<Form.Item
|
||||
name="username"
|
||||
rules={[{ required: true, message: '请输入用户名' }]}
|
||||
>
|
||||
<Input
|
||||
prefix={<UserOutlined />}
|
||||
placeholder="用户名"
|
||||
size="large"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="password"
|
||||
rules={[{ required: true, message: '请输入密码' }]}
|
||||
>
|
||||
<Input.Password
|
||||
prefix={<LockOutlined />}
|
||||
placeholder="密码"
|
||||
size="large"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
size="large"
|
||||
block
|
||||
loading={loading}
|
||||
>
|
||||
登录
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Login
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Card } from 'antd'
|
||||
import { FileTextOutlined } from '@ant-design/icons'
|
||||
|
||||
function TPS() {
|
||||
return (
|
||||
<Card style={{ borderRadius: 16 }}>
|
||||
<div style={{ textAlign: 'center', padding: '48px 0', color: '#999' }}>
|
||||
<FileTextOutlined style={{ fontSize: 48, marginBottom: 16 }} />
|
||||
<h3 style={{ color: '#666' }}>TPS 选题策划</h3>
|
||||
<p>模块 A / B / C / D / G · Phase 4a 实施</p>
|
||||
<small>查重 + 知识库参考 + 报题单生成</small>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default TPS
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Card } from 'antd'
|
||||
import { TeamOutlined } from '@ant-design/icons'
|
||||
|
||||
function UserManage() {
|
||||
return (
|
||||
<Card style={{ borderRadius: 16 }}>
|
||||
<div style={{ textAlign: 'center', padding: '48px 0', color: '#999' }}>
|
||||
<TeamOutlined style={{ fontSize: 48, marginBottom: 16 }} />
|
||||
<h3 style={{ color: '#666' }}>用户管理</h3>
|
||||
<p>Phase 2 实施</p>
|
||||
<small>管理员创建账号 / 角色分配(仅 zhipianren 可见)</small>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserManage
|
||||
Reference in New Issue
Block a user