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 {
|
import {
|
||||||
Form,
|
Form,
|
||||||
Table,
|
Table,
|
||||||
|
|
@ -65,6 +65,9 @@ const CHECK_TYPE_OPTIONS = [
|
||||||
{ label: "年度随机抽查", value: "ANNUAL" },
|
{ label: "年度随机抽查", value: "ANNUAL" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/** 随机抽取动画固定时长(毫秒) */
|
||||||
|
const LOTTERY_DURATION = 3000;
|
||||||
|
|
||||||
const getYearOptions = () => {
|
const getYearOptions = () => {
|
||||||
const year = dayjs().year();
|
const year = dayjs().year();
|
||||||
return [0, 1, 2].map((offset) => {
|
return [0, 1, 2].map((offset) => {
|
||||||
|
|
@ -280,6 +283,22 @@ const EvalReportDatabase = (props) => {
|
||||||
const [spotDetailVisible, setSpotDetailVisible] = useState(false);
|
const [spotDetailVisible, setSpotDetailVisible] = useState(false);
|
||||||
const [spotDetailData, setSpotDetailData] = useState(null);
|
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 {
|
const {
|
||||||
regulatorEvalReportPageLoading,
|
regulatorEvalReportPageLoading,
|
||||||
regulatorEvalReportDetailLoading,
|
regulatorEvalReportDetailLoading,
|
||||||
|
|
@ -362,19 +381,90 @@ const EvalReportDatabase = (props) => {
|
||||||
planCheckDate: dayjs(),
|
planCheckDate: dayjs(),
|
||||||
});
|
});
|
||||||
setSpotCheckData([]);
|
setSpotCheckData([]);
|
||||||
|
setLotteryStatus("idle");
|
||||||
|
setLotteryProgress(0);
|
||||||
setSpotCheckVisible(true);
|
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 handleSpotCheck = async () => {
|
||||||
const values = await spotCheckForm.validateFields().catch(() => null);
|
const values = await spotCheckForm.validateFields().catch(() => null);
|
||||||
if (!values) return;
|
if (!values || lotteryStatus === "running") return;
|
||||||
const res = await props.regulatorSpotCheckReport({
|
const count = values.randomNumber;
|
||||||
|
// 先查询该机构已报送报告,无数据则不播放动画
|
||||||
|
const pageRes = await props.regulatorEvalReportPage({
|
||||||
orgId: values.orgId,
|
orgId: values.orgId,
|
||||||
randomNumber: values.randomNumber,
|
current: 1,
|
||||||
|
size: 200,
|
||||||
});
|
});
|
||||||
if (res?.success !== false) {
|
const candidates =
|
||||||
setSpotCheckData(res?.data || []);
|
pageRes?.success !== false ? pageRes?.data || [] : [];
|
||||||
message.success(`已随机抽取 ${res?.data?.length || 0} 份报告`);
|
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(() => {
|
setTimeout(() => {
|
||||||
document
|
document
|
||||||
.querySelector(".redb-modal .micro-temp-modal-body")
|
.querySelector(".redb-modal .micro-temp-modal-body")
|
||||||
|
|
@ -1445,16 +1535,22 @@ const EvalReportDatabase = (props) => {
|
||||||
<Modal
|
<Modal
|
||||||
title="安评报告随机抽检"
|
title="安评报告随机抽检"
|
||||||
open={spotCheckVisible}
|
open={spotCheckVisible}
|
||||||
onCancel={() => setSpotCheckVisible(false)}
|
onCancel={handleCloseSpotCheck}
|
||||||
footer={
|
footer={
|
||||||
<Space>
|
<Space>
|
||||||
<Button onClick={() => setSpotCheckVisible(false)}>关闭</Button>
|
<Button onClick={handleCloseSpotCheck}>关闭</Button>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
loading={regulatorSpotCheckReportLoading}
|
loading={
|
||||||
|
regulatorSpotCheckReportLoading ||
|
||||||
|
regulatorEvalReportPageLoading
|
||||||
|
}
|
||||||
|
disabled={lotteryStatus === "running"}
|
||||||
onClick={handleSpotCheck}
|
onClick={handleSpotCheck}
|
||||||
>
|
>
|
||||||
随机抽取并生成检查单
|
{lotteryStatus === "running"
|
||||||
|
? "正在随机抽取..."
|
||||||
|
: "随机抽取并生成检查单"}
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
|
|
@ -1532,6 +1628,25 @@ const EvalReportDatabase = (props) => {
|
||||||
</Row>
|
</Row>
|
||||||
</Form>
|
</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 && (
|
{spotCheckData.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<div className="redb-modal-section">
|
<div className="redb-modal-section">
|
||||||
|
|
|
||||||
|
|
@ -234,4 +234,115 @@
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
margin-bottom: 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