feat
parent
f9c7339ee1
commit
a2456fa2c3
|
|
@ -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) => {
|
|||
<Modal
|
||||
title="安评报告随机抽检"
|
||||
open={spotCheckVisible}
|
||||
onCancel={() => setSpotCheckVisible(false)}
|
||||
onCancel={handleCloseSpotCheck}
|
||||
footer={
|
||||
<Space>
|
||||
<Button onClick={() => setSpotCheckVisible(false)}>关闭</Button>
|
||||
<Button onClick={handleCloseSpotCheck}>关闭</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
loading={regulatorSpotCheckReportLoading}
|
||||
loading={
|
||||
regulatorSpotCheckReportLoading ||
|
||||
regulatorEvalReportPageLoading
|
||||
}
|
||||
disabled={lotteryStatus === "running"}
|
||||
onClick={handleSpotCheck}
|
||||
>
|
||||
随机抽取并生成检查单
|
||||
{lotteryStatus === "running"
|
||||
? "正在随机抽取..."
|
||||
: "随机抽取并生成检查单"}
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
|
|
@ -1532,6 +1628,25 @@ const EvalReportDatabase = (props) => {
|
|||
</Row>
|
||||
</Form>
|
||||
|
||||
{lotteryStatus !== "idle" && (
|
||||
<div
|
||||
className={`redb-lottery${
|
||||
lotteryStatus === "finished" ? " redb-lottery-finished" : ""
|
||||
}`}
|
||||
>
|
||||
<div className="redb-lottery-head">
|
||||
<strong>{lotteryTitle}</strong>
|
||||
<span>{lotteryCount}</span>
|
||||
</div>
|
||||
<div className="redb-lottery-window">
|
||||
<div className="redb-lottery-name">{lotteryName}</div>
|
||||
</div>
|
||||
<div className="redb-lottery-progress">
|
||||
<i style={{ width: `${lotteryProgress}%` }} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{spotCheckData.length > 0 && (
|
||||
<>
|
||||
<div className="redb-modal-section">
|
||||
|
|
|
|||
|
|
@ -235,3 +235,114 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue