feat: Dashboard数据关联修复 + 通知模板优化 + 工单创建页布局

1. Dashboard: stats新增queryStats查询统计, trend API真实工单趋势数据
2. Layout铃铛: 显示真实待审批工单数量
3. 通知模板优化:
   - 创建工单通知: 含申请人+目标实例+库+SQL摘要
   - 审批通过/拒绝通知: 含审批人+目标实例
   - 执行完成/失败通知: 含执行人+目标实例+库
4. 飞书卡片颜色: 创建=turquoise, 审批=blue, 成功=green, 失败=red
5. 钉钉消息: 添加状态emoji标签(🆕✅❌⚠️)
6. TicketCreatePage: 底部操作栏(SQL检测+提交审批)
7. ticket_executor: 移除aiomysql不支持的read_timeout/write_timeout

Tests: 1057 passed, 17 pre-existing failures (notification 35/35 passed)
This commit is contained in:
logan committed 2026-05-25 01:29:24 +08:00
1 parent 7109031f71
commit 880dbfefa4
9 files changed
+423 -209

No files matched your search

+26 -21
View File
@@ -43,24 +43,9 @@ const priorityStyle: Record<PriorityKey, { border: string; bg: string; icon: Rea
const priorityLabel: Record<PriorityKey, string> = { urgent: '紧急', high: '重要', normal: '通知', low: '一般' };
const trendDatasets: Record<TrendRange, TrendDataset> = {
7: {
xAxis: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
submitted: [120, 132, 101, 134, 90, 230, 210],
success: [110, 125, 95, 128, 85, 215, 200],
failed: [5, 3, 4, 2, 3, 8, 5],
},
30: {
xAxis: Array.from({ length: 15 }, (_, i) => `${i * 2 + 1}日`),
submitted: [85, 92, 78, 105, 120, 95, 88, 110, 125, 140, 95, 82, 115, 130, 145],
success: [80, 88, 75, 98, 112, 90, 85, 105, 118, 132, 90, 78, 108, 125, 138],
failed: [3, 2, 2, 5, 4, 3, 2, 3, 4, 5, 3, 2, 4, 3, 4],
},
90: {
xAxis: Array.from({ length: 12 }, (_, i) => `${i * 7 + 1}日`),
submitted: [520, 480, 610, 590, 720, 680, 750, 800, 720, 650, 580, 620],
success: [490, 455, 580, 560, 685, 650, 720, 770, 695, 625, 555, 598],
failed: [18, 15, 22, 20, 25, 18, 20, 22, 18, 16, 14, 15],
},
7: { xAxis: [], submitted: [], success: [], failed: [] },
30: { xAxis: [], submitted: [], success: [], failed: [] },
90: { xAxis: [], submitted: [], success: [], failed: [] },
};
/* ------------------------------------------------------------------ */
@@ -140,6 +125,7 @@ export default function Dashboard() {
const [dbInstances, setDbInstances] = useState<any[]>([]);
const [tickets, setTickets] = useState<any[]>([]);
const [queryStats, setQueryStats] = useState<any>(null);
const [trendData, setTrendData] = useState<TrendDataset>({ xAxis: [], submitted: [], success: [], failed: [] });
useEffect(() => {
import('@/api/request').then(m => m.default.get('/announcements')).then(res => {
@@ -160,11 +146,30 @@ export default function Dashboard() {
/* ---- Data Fetch ---- */
useEffect(() => {
dashboardApi.getStats()
.then(data => setStats(data))
.then(data => {
setStats(data);
// 关联查询统计
if (data.query_stats) setQueryStats(data.query_stats);
})
.catch(() => setStats(null))
.finally(() => setLoading(false));
}, []);
/* ---- Trend Data ---- */
useEffect(() => {
import('@/api/request').then(m => m.default.get('/dashboard/trend', { params: { days: trendRange } }))
.then((res: any) => {
const items = res?.items || res?.data?.items || [];
setTrendData({
xAxis: items.map((p: any) => p.date),
submitted: items.map((p: any) => p.submitted),
success: items.map((p: any) => p.success),
failed: items.map((p: any) => p.failed),
});
})
.catch(() => setTrendData({ xAxis: [], submitted: [], success: [], failed: [] }));
}, [trendRange]);
/* ---- Instances & Tickets ---- */
useEffect(() => {
import('@/api/request').then(m => {
@@ -225,7 +230,7 @@ export default function Dashboard() {
? { bg: 'rgba(255,255,255,0.95)', border: '#e2e5eb', text: '#1a1d23', muted: '#5c6370', grid: '#f0f1f5', blue: '#2563eb', green: '#16a34a', red: '#dc2626' }
: { bg: 'rgba(22, 27, 34, 0.95)', border: '#30363d', text: '#f0f6fc', muted: '#8b949e', grid: '#21262d', blue: '#58a6ff', green: '#3fb950', red: '#f85149' };
const dataset = trendDatasets[trendRange];
const dataset = trendData;
const option: echarts.EChartsOption = {
backgroundColor: 'transparent',
@@ -297,7 +302,7 @@ export default function Dashboard() {
const handleResize = () => chart.resize();
window.addEventListener('resize', handleResize);
return () => { window.removeEventListener('resize', handleResize); };
}, [theme, trendRange]);
}, [theme, trendRange, trendData]);
useEffect(() => {
return () => { chartInstanceRef.current?.dispose(); };