Files
stock/frontend/src/pages/Alerts/AlertsPage.tsx
2026-06-11 01:41:47 +08:00

192 lines
5.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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>
);
}