Initial commit: stock market platform
This commit is contained in:
191
frontend/src/pages/Alerts/AlertsPage.tsx
Normal file
191
frontend/src/pages/Alerts/AlertsPage.tsx
Normal file
@@ -0,0 +1,191 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Table, Button, Card, Typography, Modal, Form, Input, Select, InputNumber,
|
||||
Switch, Popconfirm, message, Tag, Empty,
|
||||
} from "antd";
|
||||
import { PlusOutlined, DeleteOutlined } from "@ant-design/icons";
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { stockService } from "@/services/stocks";
|
||||
import type { Alert } from "@/types";
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
const ALERT_TYPES = [
|
||||
{ value: "price_above", label: "价格高于" },
|
||||
{ value: "price_below", label: "价格低于" },
|
||||
{ value: "change_pct_above", label: "涨幅超过" },
|
||||
{ value: "change_pct_below", label: "跌幅超过" },
|
||||
];
|
||||
|
||||
export default function AlertsPage() {
|
||||
const qc = useQueryClient();
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const { data: alerts = [], isLoading } = useQuery({
|
||||
queryKey: ["alerts"],
|
||||
queryFn: stockService.getAlerts,
|
||||
});
|
||||
|
||||
const createMutation = useMutation({
|
||||
mutationFn: stockService.createAlert,
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ["alerts"] });
|
||||
message.success("预警已创建");
|
||||
setModalOpen(false);
|
||||
form.resetFields();
|
||||
},
|
||||
onError: () => message.error("创建失败"),
|
||||
});
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: stockService.deleteAlert,
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ["alerts"] });
|
||||
message.success("已删除");
|
||||
},
|
||||
});
|
||||
|
||||
const toggleMutation = useMutation({
|
||||
mutationFn: stockService.toggleAlert,
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: ["alerts"] }),
|
||||
});
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: "股票",
|
||||
key: "stock",
|
||||
render: (_: unknown, record: Alert) => (
|
||||
<span>
|
||||
<span style={{ color: "#e6edf3", fontWeight: 600 }}>{record.name}</span>
|
||||
<Tag style={{ marginLeft: 8, fontSize: 11 }} color="default">
|
||||
{record.symbol}
|
||||
</Tag>
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "预警类型",
|
||||
dataIndex: "alert_type",
|
||||
key: "alert_type",
|
||||
render: (v: string) => {
|
||||
const t = ALERT_TYPES.find((t) => t.value === v);
|
||||
return <Tag color="blue">{t?.label || v}</Tag>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "阈值",
|
||||
dataIndex: "threshold",
|
||||
key: "threshold",
|
||||
render: (v: number, record: Alert) => (
|
||||
<span style={{ color: "#e6edf3", fontWeight: 600 }}>
|
||||
{v}
|
||||
{record.alert_type.includes("pct") ? "%" : "元"}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "状态",
|
||||
key: "status",
|
||||
render: (_: unknown, record: Alert) => {
|
||||
if (record.triggered) return <Tag color="warning">已触发</Tag>;
|
||||
return record.is_active ? <Tag color="success">监控中</Tag> : <Tag>已暂停</Tag>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "启用",
|
||||
key: "active",
|
||||
render: (_: unknown, record: Alert) => (
|
||||
<Switch
|
||||
checked={record.is_active}
|
||||
size="small"
|
||||
onChange={() => toggleMutation.mutate(record.id)}
|
||||
disabled={record.triggered}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
key: "action",
|
||||
render: (_: unknown, record: Alert) => (
|
||||
<Popconfirm
|
||||
title="确认删除该预警?"
|
||||
onConfirm={() => deleteMutation.mutate(record.id)}
|
||||
okText="删除"
|
||||
cancelText="取消"
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
icon={<DeleteOutlined />}
|
||||
danger
|
||||
/>
|
||||
</Popconfirm>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ marginBottom: 16, display: "flex", alignItems: "center", gap: 12 }}>
|
||||
<Title level={4} style={{ margin: 0, color: "#e6edf3" }}>
|
||||
价格预警
|
||||
</Title>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => setModalOpen(true)}
|
||||
>
|
||||
新建预警
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Card
|
||||
style={{ background: "#161b22", border: "1px solid #30363d" }}
|
||||
bodyStyle={{ padding: 0 }}
|
||||
>
|
||||
<Table
|
||||
rowKey="id"
|
||||
columns={columns}
|
||||
dataSource={alerts}
|
||||
loading={isLoading}
|
||||
pagination={{ pageSize: 20 }}
|
||||
locale={{ emptyText: <Empty description="暂无预警" /> }}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Modal
|
||||
title="新建价格预警"
|
||||
open={modalOpen}
|
||||
onCancel={() => setModalOpen(false)}
|
||||
onOk={() => form.submit()}
|
||||
confirmLoading={createMutation.isPending}
|
||||
okText="创建"
|
||||
cancelText="取消"
|
||||
styles={{ content: { background: "#161b22" }, header: { background: "#161b22" } }}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onFinish={(values) => createMutation.mutate(values)}
|
||||
>
|
||||
<Form.Item name="symbol" label="股票代码" rules={[{ required: true }]}>
|
||||
<Input placeholder="如:600519" />
|
||||
</Form.Item>
|
||||
<Form.Item name="name" label="股票名称" rules={[{ required: true }]}>
|
||||
<Input placeholder="如:贵州茅台" />
|
||||
</Form.Item>
|
||||
<Form.Item name="alert_type" label="预警类型" rules={[{ required: true }]}>
|
||||
<Select options={ALERT_TYPES} placeholder="选择预警类型" />
|
||||
</Form.Item>
|
||||
<Form.Item name="threshold" label="阈值" rules={[{ required: true }]}>
|
||||
<InputNumber
|
||||
style={{ width: "100%" }}
|
||||
placeholder="输入阈值(价格填元,涨跌幅填%数字)"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user