Merge branch 'Wren' into shiy-login
This commit is contained in:
@@ -1,15 +1,26 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import type { EvaluationPoint } from '~/models/evaluation_points';
|
||||
import type { EvaluationPointGroup } from '~/models/evaluation_point_groups';
|
||||
import { getRulesList } from '~/api/evaluation_points/rules';
|
||||
|
||||
interface BasicInfoProps {
|
||||
onChange?: (data: Record<string, unknown>) => void;
|
||||
initialData?: EvaluationPoint;
|
||||
evaluationPointGroups?: EvaluationPointGroup[];
|
||||
riskOptions?: Array<{value: string, label: string}>;
|
||||
frontendJWT?: string;
|
||||
evaluationPointId?: number | string;
|
||||
}
|
||||
|
||||
// 评查点基本信息组件
|
||||
export function BasicInfo({ onChange, initialData, evaluationPointGroups = [], riskOptions = [] }: BasicInfoProps) {
|
||||
export function BasicInfo({
|
||||
onChange,
|
||||
initialData,
|
||||
evaluationPointGroups = [],
|
||||
riskOptions = [],
|
||||
frontendJWT,
|
||||
evaluationPointId
|
||||
}: BasicInfoProps) {
|
||||
const [formData, setFormData] = useState<EvaluationPoint>({
|
||||
risk: 'medium', // 风险等级 默认中风险
|
||||
is_enabled: true, // 是否启用 默认启用
|
||||
@@ -21,6 +32,47 @@ export function BasicInfo({ onChange, initialData, evaluationPointGroups = [], r
|
||||
...(initialData || {}) // 合并初始数据
|
||||
});
|
||||
|
||||
// 编码验证状态
|
||||
const [codeValidating, setCodeValidating] = useState(false);
|
||||
const [codeError, setCodeError] = useState('');
|
||||
const [codeValidationTimer, setCodeValidationTimer] = useState<NodeJS.Timeout | null>(null);
|
||||
|
||||
// 异步验证编码唯一性
|
||||
const validateCodeUnique = async (code: string): Promise<string> => {
|
||||
if (!code.trim()) {
|
||||
return ''; // 空值不验证
|
||||
}
|
||||
|
||||
setCodeValidating(true);
|
||||
setCodeError('');
|
||||
|
||||
try {
|
||||
const response = await getRulesList({
|
||||
keyword: code.trim(),
|
||||
pageSize: 10,
|
||||
token: frontendJWT
|
||||
});
|
||||
|
||||
if (response.data && response.data.rules && response.data.rules.length > 0) {
|
||||
// 检查是否有完全匹配的编码(排除当前编辑的评查点)
|
||||
const isDuplicate = response.data.rules.some(rule =>
|
||||
rule.code === code.trim() && String(rule.id) !== String(evaluationPointId)
|
||||
);
|
||||
|
||||
if (isDuplicate) {
|
||||
return '该编码已被使用,请使用其他编码';
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
} catch (error) {
|
||||
console.error('验证编码唯一性失败:', error);
|
||||
return ''; // 验证失败不阻止用户输入
|
||||
} finally {
|
||||
setCodeValidating(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 找到当前评查点类型对应的code
|
||||
const getCheckpointTypeCode = () => {
|
||||
if (!formData.evaluation_point_groups_pid) return "";
|
||||
@@ -80,11 +132,23 @@ export function BasicInfo({ onChange, initialData, evaluationPointGroups = [], r
|
||||
const newData = { ...formData };
|
||||
// 映射id到表单字段名
|
||||
switch(id) {
|
||||
case 'rule-name':
|
||||
case 'rule-name':
|
||||
newData.name = value;
|
||||
break;
|
||||
case 'rule-code':
|
||||
case 'rule-code':
|
||||
newData.code = value;
|
||||
// 清除之前的验证定时器
|
||||
if (codeValidationTimer) {
|
||||
clearTimeout(codeValidationTimer);
|
||||
}
|
||||
// 清除错误信息
|
||||
setCodeError('');
|
||||
// 设置新的验证定时器(500ms后触发验证)
|
||||
const timer = setTimeout(async () => {
|
||||
const error = await validateCodeUnique(value);
|
||||
setCodeError(error);
|
||||
}, 500);
|
||||
setCodeValidationTimer(timer);
|
||||
break;
|
||||
case 'risk-level':
|
||||
newData.risk = value;
|
||||
@@ -197,6 +261,15 @@ export function BasicInfo({ onChange, initialData, evaluationPointGroups = [], r
|
||||
// }
|
||||
// }, [filteredRuleGroups, onChange]);
|
||||
|
||||
// 清理验证定时器
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (codeValidationTimer) {
|
||||
clearTimeout(codeValidationTimer);
|
||||
}
|
||||
};
|
||||
}, [codeValidationTimer]);
|
||||
|
||||
return (
|
||||
<div className="ant-card">
|
||||
<div className="ant-card-header">
|
||||
@@ -221,16 +294,21 @@ export function BasicInfo({ onChange, initialData, evaluationPointGroups = [], r
|
||||
<div>
|
||||
<label className="form-label" htmlFor="rule-code">
|
||||
评查点编码 <span className="required-mark">*</span>
|
||||
{codeValidating && <span className="ml-2 text-sm text-gray-500">验证中...</span>}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="rule-code"
|
||||
className="form-input"
|
||||
className={`form-input ${codeError ? 'border-red-500' : ''}`}
|
||||
placeholder="请输入评查点编码"
|
||||
value={formData.code}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<div className="form-tip">用于系统标识的唯一编码</div>
|
||||
{codeError ? (
|
||||
<div className="form-tip text-red-500">{codeError}</div>
|
||||
) : (
|
||||
<div className="form-tip">用于系统标识的唯一编码</div>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<label className="form-label" htmlFor="risk-level">
|
||||
|
||||
@@ -40,6 +40,8 @@ interface MessageModalProps {
|
||||
customIcon?: React.ReactNode;
|
||||
// 自定义内容
|
||||
children?: React.ReactNode;
|
||||
// 确认按钮延迟时间(秒)- 用于危险操作(如删除)
|
||||
confirmDelay?: number;
|
||||
}
|
||||
|
||||
// 默认自动关闭延迟
|
||||
@@ -63,10 +65,12 @@ export function MessageModal({
|
||||
cancelText = '取消',
|
||||
showCloseButton = true,
|
||||
customIcon,
|
||||
children
|
||||
children,
|
||||
confirmDelay = 0
|
||||
}: MessageModalProps) {
|
||||
const [isClosing, setIsClosing] = useState(false);
|
||||
const [portalElement, setPortalElement] = useState<HTMLElement | null>(null);
|
||||
const [remainingSeconds, setRemainingSeconds] = useState(confirmDelay);
|
||||
|
||||
// 在客户端渲染时获取 portal 容器
|
||||
useEffect(() => {
|
||||
@@ -108,6 +112,23 @@ export function MessageModal({
|
||||
}
|
||||
}, [isOpen, autoClose, autoCloseDelay, handleClose]);
|
||||
|
||||
// 确认延迟倒计时
|
||||
useEffect(() => {
|
||||
if (isOpen && confirmDelay > 0) {
|
||||
setRemainingSeconds(confirmDelay);
|
||||
const timer = setInterval(() => {
|
||||
setRemainingSeconds((prev) => {
|
||||
if (prev <= 1) {
|
||||
clearInterval(timer);
|
||||
return 0;
|
||||
}
|
||||
return prev - 1;
|
||||
});
|
||||
}, 1000);
|
||||
return () => clearInterval(timer);
|
||||
}
|
||||
}, [isOpen, confirmDelay]);
|
||||
|
||||
// 关闭按钮键盘交互
|
||||
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
@@ -194,15 +215,20 @@ export function MessageModal({
|
||||
<div className="message-modal-actions">
|
||||
{onConfirm && (
|
||||
<>
|
||||
<button
|
||||
className="message-modal-button primary"
|
||||
<button
|
||||
className="message-modal-button primary"
|
||||
onClick={handleConfirm}
|
||||
disabled={remainingSeconds > 0}
|
||||
style={{
|
||||
opacity: remainingSeconds > 0 ? 0.5 : 1,
|
||||
cursor: remainingSeconds > 0 ? 'not-allowed' : 'pointer'
|
||||
}}
|
||||
>
|
||||
{confirmText}
|
||||
{remainingSeconds > 0 ? `${confirmText} (${remainingSeconds}s)` : confirmText}
|
||||
</button>
|
||||
{cancelText && (
|
||||
<button
|
||||
className="message-modal-button"
|
||||
<button
|
||||
className="message-modal-button"
|
||||
onClick={handleClose}
|
||||
>
|
||||
{cancelText}
|
||||
@@ -210,10 +236,10 @@ export function MessageModal({
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
{!onConfirm && (
|
||||
<button
|
||||
className="message-modal-button primary"
|
||||
<button
|
||||
className="message-modal-button primary"
|
||||
onClick={handleClose}
|
||||
>
|
||||
{confirmText}
|
||||
|
||||
@@ -25,7 +25,7 @@ export { UploadArea } from './UploadArea';
|
||||
|
||||
// 反馈组件
|
||||
export { Alert } from './Alert';
|
||||
export { MessageModal } from './MessageModal';
|
||||
export { MessageModal, messageService } from './MessageModal';
|
||||
export { LoadingBar } from './LoadingBar';
|
||||
export { RouteChangeLoader } from './RouteChangeLoader';
|
||||
export { FileProgress } from './FileProgress';
|
||||
|
||||
Reference in New Issue
Block a user