From a2456fa2c364e2568368ae39097c39109002543b Mon Sep 17 00:00:00 2001 From: tangjie <122778500@qq.com> Date: Wed, 19 Aug 2026 15:34:47 +0800 Subject: [PATCH] feat --- .../Supervision/EvalReportDatabase/index.js | 137 ++++++++++++++++-- .../Supervision/EvalReportDatabase/index.less | 111 ++++++++++++++ 2 files changed, 237 insertions(+), 11 deletions(-) diff --git a/src/pages/Container/Supervision/EvalReportDatabase/index.js b/src/pages/Container/Supervision/EvalReportDatabase/index.js index f1cba98..afc328d 100644 --- a/src/pages/Container/Supervision/EvalReportDatabase/index.js +++ b/src/pages/Container/Supervision/EvalReportDatabase/index.js @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from "react"; +import React, { useEffect, useMemo, useRef, useState } from "react"; import { Form, Table, @@ -65,6 +65,9 @@ const CHECK_TYPE_OPTIONS = [ { label: "年度随机抽查", value: "ANNUAL" }, ]; +/** 随机抽取动画固定时长(毫秒) */ +const LOTTERY_DURATION = 3000; + const getYearOptions = () => { const year = dayjs().year(); return [0, 1, 2].map((offset) => { @@ -280,6 +283,22 @@ const EvalReportDatabase = (props) => { const [spotDetailVisible, setSpotDetailVisible] = useState(false); const [spotDetailData, setSpotDetailData] = useState(null); + // 随机抽取动画(对齐原型 sampling-lottery) + const [lotteryStatus, setLotteryStatus] = useState("idle"); // idle | running | finished + const [lotteryTitle, setLotteryTitle] = useState("正在随机抽取报告"); + const [lotteryCount, setLotteryCount] = useState("准备抽取"); + const [lotteryName, setLotteryName] = useState("等待开始"); + const [lotteryProgress, setLotteryProgress] = useState(0); + const lotteryTimerRef = useRef(null); + const lotteryRollRef = useRef(null); + + useEffect(() => { + return () => { + if (lotteryTimerRef.current) clearTimeout(lotteryTimerRef.current); + if (lotteryRollRef.current) clearInterval(lotteryRollRef.current); + }; + }, []); + const { regulatorEvalReportPageLoading, regulatorEvalReportDetailLoading, @@ -362,19 +381,90 @@ const EvalReportDatabase = (props) => { planCheckDate: dayjs(), }); setSpotCheckData([]); + setLotteryStatus("idle"); + setLotteryProgress(0); setSpotCheckVisible(true); }; + /** 动画滚动:固定 3 秒,滚动展示候选报告数据,完成后 resolve */ + const playLottery = (count, candidates) => + new Promise((resolve) => { + if (lotteryRollRef.current) clearInterval(lotteryRollRef.current); + if (lotteryTimerRef.current) clearTimeout(lotteryTimerRef.current); + setLotteryStatus("running"); + setLotteryTitle("系统正在随机抽取报告"); + setLotteryCount(`目标 ${count} 份`); + setLotteryProgress(0); + const startTime = Date.now(); + const rollName = () => { + const item = + candidates[Math.floor(Math.random() * candidates.length)]; + setLotteryName( + [item.reportNo || "", item.reportName || ""] + .filter(Boolean) + .join(" · "), + ); + }; + rollName(); + lotteryRollRef.current = setInterval(() => { + rollName(); + setLotteryProgress( + Math.min( + 100, + Math.round(((Date.now() - startTime) / LOTTERY_DURATION) * 100), + ), + ); + }, 90); + lotteryTimerRef.current = setTimeout(() => { + clearInterval(lotteryRollRef.current); + lotteryRollRef.current = null; + setLotteryProgress(100); + setLotteryTitle("随机抽取完成"); + setLotteryName("抽取结果已锁定,正在生成监督检查单"); + setLotteryStatus("finished"); + resolve(); + }, LOTTERY_DURATION); + }); + + const handleCloseSpotCheck = () => { + if (lotteryTimerRef.current) clearTimeout(lotteryTimerRef.current); + if (lotteryRollRef.current) clearInterval(lotteryRollRef.current); + setLotteryStatus("idle"); + setSpotCheckVisible(false); + }; + const handleSpotCheck = async () => { const values = await spotCheckForm.validateFields().catch(() => null); - if (!values) return; - const res = await props.regulatorSpotCheckReport({ + if (!values || lotteryStatus === "running") return; + const count = values.randomNumber; + // 先查询该机构已报送报告,无数据则不播放动画 + const pageRes = await props.regulatorEvalReportPage({ orgId: values.orgId, - randomNumber: values.randomNumber, + current: 1, + size: 200, }); - if (res?.success !== false) { - setSpotCheckData(res?.data || []); - message.success(`已随机抽取 ${res?.data?.length || 0} 份报告`); + const candidates = + pageRes?.success !== false ? pageRes?.data || [] : []; + if (!candidates.length) { + message.warning("暂无报告"); + return; + } + setSpotCheckData([]); + // 动画与抽取接口并行,两者都完成后再展示抽取结果 + const animationDone = playLottery(count, candidates); + const request = props + .regulatorSpotCheckReport({ + orgId: values.orgId, + randomNumber: count, + }) + .then((res) => (res?.success !== false ? res?.data || [] : [])) + .catch(() => []); + document.querySelector(".redb-modal .micro-temp-modal-body")?.scrollTo({ top: 999999, behavior: "smooth" }); + const [data] = await Promise.all([request, animationDone]); + setSpotCheckData(data); + setLotteryCount(`已抽取 ${data.length} 份报告`); + if (data.length > 0) { + message.success(`已随机抽取 ${data.length} 份报告`); setTimeout(() => { document .querySelector(".redb-modal .micro-temp-modal-body") @@ -1445,16 +1535,22 @@ const EvalReportDatabase = (props) => { setSpotCheckVisible(false)} + onCancel={handleCloseSpotCheck} footer={ - + } @@ -1532,6 +1628,25 @@ const EvalReportDatabase = (props) => { + {lotteryStatus !== "idle" && ( +
+
+ {lotteryTitle} + {lotteryCount} +
+
+
{lotteryName}
+
+
+ +
+
+ )} + {spotCheckData.length > 0 && ( <>
diff --git a/src/pages/Container/Supervision/EvalReportDatabase/index.less b/src/pages/Container/Supervision/EvalReportDatabase/index.less index 49da41c..1193c82 100644 --- a/src/pages/Container/Supervision/EvalReportDatabase/index.less +++ b/src/pages/Container/Supervision/EvalReportDatabase/index.less @@ -234,4 +234,115 @@ justify-content: space-between; gap: 12px; margin-bottom: 12px; +} + +/* 随机抽取动画 — 原型 .sampling-lottery */ +.redb-lottery { + margin-top: 16px; + padding: 16px; + border: 1px solid #bfdbfe; + border-radius: 6px; + background: #f8fbff; + overflow: hidden; +} + +.redb-lottery-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + margin-bottom: 12px; +} + +.redb-lottery-head strong { + font-size: 14px; + color: #1e3a5f; +} + +.redb-lottery-head span { + color: #64748b; +} + +.redb-lottery-window { + position: relative; + min-height: 118px; + display: flex; + align-items: center; + justify-content: center; + padding: 16px 48px; + border: 1px solid #dbeafe; + border-radius: 5px; + background: #fff; + text-align: center; +} + +.redb-lottery-window::before, +.redb-lottery-window::after { + content: ""; + position: absolute; + left: 0; + right: 0; + height: 24px; + z-index: 1; + pointer-events: none; +} + +.redb-lottery-window::before { + top: 0; + background: linear-gradient(#fff, rgba(255, 255, 255, 0)); +} + +.redb-lottery-window::after { + bottom: 0; + background: linear-gradient(rgba(255, 255, 255, 0), #fff); +} + +.redb-lottery-name { + font-size: 16px; + font-weight: 700; + color: #1d4ed8; + line-height: 1.55; +} + +.redb-lottery:not(.redb-lottery-finished) .redb-lottery-name { + animation: redb-sampling-pulse 0.18s ease-in-out infinite alternate; +} + +.redb-lottery-finished .redb-lottery-window { + border-color: #86efac; + background: #f0fdf4; +} + +.redb-lottery-finished .redb-lottery-name { + color: #15803d; + animation: none; +} + +.redb-lottery-progress { + height: 5px; + margin-top: 12px; + border-radius: 9px; + background: #dbeafe; + overflow: hidden; +} + +.redb-lottery-progress i { + display: block; + width: 0; + height: 100%; + border-radius: 9px; + background: #2563eb; + transition: width 0.2s ease; +} + +@keyframes redb-sampling-pulse { + from { + transform: translateY(-2px); + opacity: 0.72; + } + + to { + transform: translateY(2px); + opacity: 1; + } } \ No newline at end of file