优化评查结果的显示效果
This commit is contained in:
@@ -345,8 +345,25 @@ export async function getReviewPoints(fileId: string) {
|
||||
// evaluatedPointResultsLog: {
|
||||
// rules:[
|
||||
// {
|
||||
// "id": "0",
|
||||
// "type": "consistency",
|
||||
// "res": true,
|
||||
// "config": {
|
||||
// "logic": "all",
|
||||
// "pairs": [
|
||||
// {
|
||||
// "sourceField": {"证据先行登记保存批准书-负责人意见并签名-时间": {page: 1,value: ''}},
|
||||
// "targetField": {"证据先行登记保存批准书-负责人意见并签名-签名": {page: 2,value: '有无判断类型'}},
|
||||
// "compareMethod": "exact",
|
||||
// "res": true
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// "id": "1",
|
||||
// "type": "consistency",
|
||||
// "res": false,
|
||||
// "config": {
|
||||
// "logic": "and",
|
||||
// "pairs": [
|
||||
|
||||
@@ -1,22 +1,7 @@
|
||||
import { postgrestGet, postgrestPut, postgrestPost, type PostgrestParams } from '../postgrest-client';
|
||||
import { postgrestGet, type PostgrestParams } from '../postgrest-client';
|
||||
import dayjs from 'dayjs';
|
||||
// import { API_BASE_URL } from '../client';
|
||||
|
||||
/**
|
||||
* 格式化日期
|
||||
* @param dateString 日期字符串
|
||||
* @returns 格式化后的日期字符串
|
||||
*/
|
||||
function formatDate(dateString: string): string {
|
||||
if (!dateString) return '';
|
||||
try {
|
||||
return dayjs(dateString).format('YYYY-MM-DD HH:mm:ss');
|
||||
} catch (error) {
|
||||
console.error('日期格式化失败:', error);
|
||||
return dateString;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 从不同格式的 API 响应中提取数据
|
||||
|
||||
@@ -20,13 +20,6 @@ export function Sidebar({ onToggle, collapsed }: SidebarProps) {
|
||||
const [expandedMenus, setExpandedMenus] = useState<Record<string, boolean>>({});
|
||||
|
||||
const menuItems: MenuItem[] = [
|
||||
{
|
||||
id: 'contract-search',
|
||||
title: '智能搜索',
|
||||
path: '/contract-search',
|
||||
hideBreadcrumb: true,
|
||||
icon: 'ri-search-line'
|
||||
},
|
||||
{
|
||||
id: 'contract-template',
|
||||
title: '合同模板',
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
* - 建议修改区域: 显示建议的修改内容
|
||||
* - 操作按钮: 提供一键替换和人工审核功能
|
||||
*/
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { toastService } from '../ui/Toast';
|
||||
// import { toastService } from '../ui/Toast';
|
||||
import { createPortal } from 'react-dom'; // 导入React Portal API,用于将组件渲染到DOM树的不同位置
|
||||
import { Tooltip } from '../ui/Tooltip';
|
||||
// import '../../styles/components/TooltipStyles.css';
|
||||
|
||||
/**
|
||||
* 比较方法映射
|
||||
@@ -114,6 +116,7 @@ export interface ReviewPoint {
|
||||
rules: Array<{
|
||||
id: string;
|
||||
type: string;
|
||||
res?: boolean;
|
||||
config: Record<string, unknown>;
|
||||
}>;
|
||||
};
|
||||
@@ -143,6 +146,194 @@ interface ReviewPointsListProps {
|
||||
onStatusChange: (id: string, editAuditStatusId: string | number, status: string, message: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局状态对象,存储当前活动的提示框信息
|
||||
* 这种方式避免了复杂的状态提升或Context API的使用
|
||||
*/
|
||||
let activeTooltip = {
|
||||
show: false, // 控制提示框是否显示
|
||||
content: null as React.ReactNode, // 提示框内容(React节点)
|
||||
position: { top: 0, left: 0 } // 提示框在屏幕上的位置
|
||||
};
|
||||
|
||||
/**
|
||||
* 提示框Portal组件
|
||||
*
|
||||
* 使用React Portal将提示框渲染到document.body下,
|
||||
* 这样可以确保提示框不受任何父元素overflow或z-index限制
|
||||
*/
|
||||
function TooltipPortal() {
|
||||
// 使用本地状态保存提示框信息的副本
|
||||
const [tooltip, setTooltip] = useState(activeTooltip);
|
||||
|
||||
useEffect(() => {
|
||||
// 通过自定义事件机制监听全局tooltip状态更新
|
||||
const updateTooltip = () => {
|
||||
// 使用扩展运算符创建对象副本,确保状态更新被React检测到
|
||||
setTooltip({...activeTooltip});
|
||||
};
|
||||
|
||||
// 添加事件监听器
|
||||
window.addEventListener('tooltip-update', updateTooltip);
|
||||
|
||||
// 组件卸载时清理事件监听器
|
||||
return () => {
|
||||
window.removeEventListener('tooltip-update', updateTooltip);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 如果不显示或没有内容,则不渲染任何东西
|
||||
if (!tooltip.show || !tooltip.content) return null;
|
||||
|
||||
// 使用createPortal将提示框内容渲染到document.body
|
||||
return createPortal(
|
||||
<div
|
||||
className="fixed bg-white shadow-lg rounded-md p-1 border border-gray-200 z-[9999]"
|
||||
style={{
|
||||
top: `${tooltip.position.top}px`,
|
||||
left: `${tooltip.position.left}px`,
|
||||
transform: 'translate(-100%, -50%)' // 调整位置,使提示框在指针左侧居中显示
|
||||
}}
|
||||
>
|
||||
{tooltip.content}
|
||||
{/* 添加小三角形指向提示框指向的元素 */}
|
||||
<div className="absolute top-1/2 right-0 transform translate-x-1/2 -translate-y-1/2 rotate-45 w-2 h-2 bg-white border-t border-r border-gray-200"></div>
|
||||
</div>,
|
||||
document.body // 将内容挂载到body元素,完全脱离原组件DOM结构
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示提示框的辅助函数
|
||||
* @param content 要显示的React节点内容
|
||||
* @param position 显示位置坐标
|
||||
*/
|
||||
function showTooltip(content: React.ReactNode, position: { top: number; left: number }): void {
|
||||
// 更新全局状态对象
|
||||
activeTooltip = {
|
||||
show: true,
|
||||
content,
|
||||
position
|
||||
};
|
||||
// 触发自定义事件,通知TooltipPortal组件更新状态
|
||||
window.dispatchEvent(new Event('tooltip-update'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏提示框的辅助函数
|
||||
*/
|
||||
function hideTooltip(): void {
|
||||
// 设置为不显示状态
|
||||
activeTooltip.show = false;
|
||||
// 触发自定义事件,通知TooltipPortal组件更新状态
|
||||
window.dispatchEvent(new Event('tooltip-update'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* React组件表格Tooltip
|
||||
* 将文本数据解析为表格并使用React组件渲染
|
||||
* 条件性Tooltip组件
|
||||
* 只有当内容超过2行时才显示tooltip
|
||||
*/
|
||||
const ReactTableTooltip = ({ content }: { content: string }) => {
|
||||
const [showTooltip, setShowTooltip] = useState(false);
|
||||
const textRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const checkTextOverflow = () => {
|
||||
const element = textRef.current;
|
||||
if (element) {
|
||||
setShowTooltip(element.scrollHeight > element.clientHeight);
|
||||
}
|
||||
};
|
||||
|
||||
setTimeout(checkTextOverflow, 0);
|
||||
window.addEventListener('resize', checkTextOverflow);
|
||||
return () => {
|
||||
window.removeEventListener('resize', checkTextOverflow);
|
||||
};
|
||||
}, [content]);
|
||||
|
||||
// 解析表格数据
|
||||
const parseTableData = (text: string) => {
|
||||
const rows = text.split('\n').map(row => row.split('\t'));
|
||||
return rows;
|
||||
};
|
||||
|
||||
// 渲染React表格
|
||||
const renderReactTable = (text: string) => {
|
||||
try {
|
||||
const tableData = parseTableData(text);
|
||||
const hasHeader = tableData.length > 0;
|
||||
|
||||
return (
|
||||
<div className="overflow-auto max-h-[400px]">
|
||||
<table className="min-w-full border-collapse border border-gray-300">
|
||||
{hasHeader && (
|
||||
<thead>
|
||||
<tr>
|
||||
{tableData[0].map((cell, cellIndex) => (
|
||||
<th
|
||||
key={`header-${cellIndex}`}
|
||||
className="px-2 py-1 border border-gray-300 bg-gray-100 font-medium text-xs text-left"
|
||||
>
|
||||
{cell || ' '}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
)}
|
||||
<tbody>
|
||||
{tableData.slice(1).map((row, rowIndex) => (
|
||||
<tr key={`row-${rowIndex}`}>
|
||||
{row.map((cell, cellIndex) => (
|
||||
<td
|
||||
key={`cell-${rowIndex}-${cellIndex}`}
|
||||
className="px-2 py-1 border border-gray-300 text-xs text-left"
|
||||
>
|
||||
{cell || ' '}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('表格渲染错误:', error);
|
||||
return <div className="text-red-500">表格渲染错误</div>;
|
||||
}
|
||||
};
|
||||
|
||||
// 检测内容是否像表格
|
||||
const isTableLike = content.includes('\t') && content.includes('\n');
|
||||
|
||||
return (
|
||||
<div className="text-xs p-1 rounded cursor-text w-full text-left">
|
||||
{showTooltip ? (
|
||||
<Tooltip
|
||||
content={isTableLike ? renderReactTable(content) : content}
|
||||
placement="top"
|
||||
theme="light"
|
||||
trigger="hover"
|
||||
showArrow={true}
|
||||
className="tooltip-custom-offset"
|
||||
>
|
||||
<div className="text-gray-800 break-all overflow-hidden line-clamp-2" ref={textRef}>
|
||||
{content}
|
||||
</div>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<div className="text-gray-800 break-all overflow-hidden line-clamp-2" ref={textRef}>
|
||||
{content}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export function ReviewPointsList({
|
||||
reviewPoints,
|
||||
statistics,
|
||||
@@ -410,68 +601,74 @@ export function ReviewPointsList({
|
||||
* 渲染评查点状态标签
|
||||
* @param status 状态文本
|
||||
* @param result 评查结果
|
||||
* @param title 标签提示内容
|
||||
* @returns 状态标签组件
|
||||
*/
|
||||
const renderStatusBadge = (status: string, result?: boolean) => {
|
||||
const renderStatusBadge = (status: string, result?: boolean, title?: string) => {
|
||||
// 优先根据result判断是否通过
|
||||
if (result === true) {
|
||||
return (
|
||||
<span className="status-badge status-success">
|
||||
<Tooltip
|
||||
content={
|
||||
<div className="p-1 gap-1 flex flex-col max-w-xs">
|
||||
{title && <div className="text-xs text-gray-600">{title}</div>}
|
||||
</div>
|
||||
}
|
||||
placement="top"
|
||||
theme="light"
|
||||
trigger="hover"
|
||||
showArrow={true}
|
||||
className="tooltip-custom-offset tooltip-top"
|
||||
>
|
||||
<span className="status-badge status-success text-xs m-1">
|
||||
<i className="ri-checkbox-circle-line mr-1"></i>通过
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
// 当result为false时,根据status决定显示警告还是错误
|
||||
if (result === false) {
|
||||
if (status === 'warning') {
|
||||
if (status === 'warning' || status === 'info') {
|
||||
return (
|
||||
<span className="status-badge status-warning">
|
||||
<Tooltip
|
||||
content={
|
||||
<div className="p-1 flex flex-col gap-1 max-w-xs">
|
||||
{title && <div className="text-xs text-gray-600">{title}</div>}
|
||||
</div>
|
||||
}
|
||||
placement="top"
|
||||
theme="light"
|
||||
trigger="hover"
|
||||
showArrow={true}
|
||||
className="tooltip-custom-offset tooltip-top"
|
||||
>
|
||||
<span className="status-badge status-warning text-xs m-1">
|
||||
<i className="ri-alert-line mr-1"></i>警告
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
} else if (status === 'error') {
|
||||
return (
|
||||
<span className="status-badge status-error">
|
||||
<Tooltip
|
||||
content={
|
||||
<div className="p-1 flex flex-col gap-1 max-w-xs">
|
||||
{title && <div className="text-xs text-gray-600">{title}</div>}
|
||||
</div>
|
||||
}
|
||||
placement="top"
|
||||
theme="light"
|
||||
trigger="hover"
|
||||
showArrow={true}
|
||||
className="tooltip-custom-offset tooltip-top"
|
||||
>
|
||||
<span className="status-badge status-error text-xs m-1">
|
||||
<i className="ri-close-circle-line mr-1"></i>不通过
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 兼容旧版逻辑,当没有result时,仍按status判断
|
||||
switch (status) {
|
||||
case 'success':
|
||||
return (
|
||||
<span className="status-badge status-success">
|
||||
<i className="ri-checkbox-circle-line mr-1"></i>通过
|
||||
</span>
|
||||
);
|
||||
case 'warning':
|
||||
return (
|
||||
<span className="status-badge status-warning">
|
||||
<i className="ri-alert-line mr-1"></i>警告
|
||||
</span>
|
||||
);
|
||||
case 'error':
|
||||
return (
|
||||
<span className="status-badge status-error">
|
||||
<i className="ri-close-circle-line mr-1"></i>不通过
|
||||
</span>
|
||||
);
|
||||
case 'processing':
|
||||
return (
|
||||
<span className="status-badge status-processing">
|
||||
<i className="ri-time-line mr-1"></i>处理中
|
||||
</span>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<span className="status-badge status-warning">
|
||||
<i className="ri-alert-line mr-1"></i>警告
|
||||
</span>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -482,7 +679,7 @@ export function ReviewPointsList({
|
||||
const renderHumanReviewBadge = (reviewPoint: ReviewPoint) => {
|
||||
if (reviewPoint.postAction === 'manual') {
|
||||
return (
|
||||
<span className="status-badge status-waiting mt-1 text-xs">
|
||||
<span className=" bg-[#f9f0ff] text-[#722ed1] text-xs rounded-sm p-0.5 my-auto">
|
||||
<i className="ri-user-line mr-1"></i>需人工
|
||||
</span>
|
||||
);
|
||||
@@ -529,6 +726,7 @@ export function ReviewPointsList({
|
||||
{/* 渲染各个一致性的规则分组 */}
|
||||
{reviewPoint.evaluatedPointResultsLog?.rules?.map((rule, index) => {
|
||||
// console.log('rule-------', rule);
|
||||
// if (rule.type === 'consistency' && rule.res === true) {
|
||||
if (rule.type === 'consistency') {
|
||||
// console.log('rule-------', rule);
|
||||
return <div key={`rule-${index}`}>
|
||||
@@ -636,7 +834,8 @@ export function ReviewPointsList({
|
||||
|
||||
/**
|
||||
* 渲染评查点一致性的规则的样式
|
||||
* @param singleReviewPoint 评查点
|
||||
* @param singleReviewPoint 一个评查点的一致性规则对象
|
||||
* @param reviewPoint 评查点
|
||||
* @returns 评查点一致性的规则的样式
|
||||
*/
|
||||
const renderConsistencyRule = (singleReviewPoint: Record<string, unknown>,reviewPoint: ReviewPoint) => {
|
||||
@@ -907,7 +1106,7 @@ export function ReviewPointsList({
|
||||
}
|
||||
}
|
||||
if (!hasPage) {
|
||||
toastService.error('没有找到有效的页码');
|
||||
// toastService.error('没有找到有效的页码');
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
@@ -964,12 +1163,16 @@ export function ReviewPointsList({
|
||||
>
|
||||
<div className="flex justify-between w-full">
|
||||
{/* <span className="font-medium">{item.field}:</span> */}
|
||||
<span className="w-full overflow-hidden line-clamp-2 hover:line-clamp-none hover:shadow-[0_0_10px_rgba(0,0,0,0.1)]
|
||||
hover: z-10 hover:overflow-auto rounded transition-all duration-300 ease-in-out max-h-96">{item.data.value?.toString() || ''}
|
||||
{/* <span className="w-full overflow-hidden line-clamp-2 hover:line-clamp-none
|
||||
hover: z-10 hover:overflow-auto rounded max-h-96">{item.data.value?.toString() || ''}
|
||||
{!item.data.page && !item.data.value && (
|
||||
<i className="ri-information-line text-red-500 text-xs"></i>
|
||||
)}
|
||||
</span>
|
||||
</span> */}
|
||||
<ReactTableTooltip content={item.data.value?.toString() || ''} />
|
||||
{!item.data.page && !item.data.value && (
|
||||
<i className="ri-information-line text-red-500 text-xs" title="没有找到对应的文书内容"></i>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
@@ -981,32 +1184,36 @@ export function ReviewPointsList({
|
||||
) : (
|
||||
<i className="ri-alert-line text-warning text-base" ></i>
|
||||
)}
|
||||
{/* 悬停提示框 - 横向布局 */}
|
||||
<div className="w-auto absolute hidden group-hover:block right-full top-1/2 transform -translate-y-1/2 mr-2 z-100">
|
||||
<div className="bg-white shadow-md rounded-md p-1 border border-gray-200">
|
||||
{/* <div className="text-xs font-medium text-gray-700 w-auto">规则检查结果</div> */}
|
||||
{/* 使用鼠标事件处理悬停提示 */}
|
||||
<div
|
||||
className="w-full h-full absolute top-0 left-0"
|
||||
onMouseEnter={(e) => {
|
||||
// 获取元素位置信息
|
||||
const rect = e.currentTarget.getBoundingClientRect();
|
||||
// 创建提示框内容
|
||||
const content = (
|
||||
<div className="flex flex-row gap-2 overflow-x-auto">
|
||||
{chain.map((item, idx) =>
|
||||
idx >= 1 ? (
|
||||
<div key={idx} className={`rounded-md flex flex-row items-center
|
||||
${
|
||||
res ? 'bg-green-100 text-green-600': 'bg-yellow-100 text-yellow-600'
|
||||
}`}>
|
||||
<div key={idx} className={`rounded-md flex flex-row items-center`}>
|
||||
<div className="text-xs text-gray-600 whitespace-nowrap pl-2">
|
||||
{typeof item.compareMethod === 'object'
|
||||
? ''
|
||||
: getCompareMethodText(item.compareMethod)}
|
||||
: `${getCompareMethodText(item.compareMethod)}:`}
|
||||
</div>
|
||||
<div className={`p-1 text-xs font-medium rounded-full min-w-[50px] text-center`}>
|
||||
<div className={`p-1 text-xs rounded-full min-w-[50px] text-center`}>
|
||||
{res ? '通过' : '不通过'}
|
||||
</div>
|
||||
</div>
|
||||
) : null
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute top-1/2 right-0 transform translate-x-1/2 -translate-y-1/2 rotate-45 w-2 h-2 bg-white border-t border-r border-gray-200"></div>
|
||||
</div>
|
||||
);
|
||||
// 显示提示框
|
||||
showTooltip(content, { top: rect.top + rect.height/2, left: rect.left });
|
||||
}}
|
||||
onMouseLeave={hideTooltip}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1014,7 +1221,6 @@ export function ReviewPointsList({
|
||||
}
|
||||
|
||||
// 如果是标准的成对比较(2个元素)
|
||||
|
||||
return (
|
||||
<div
|
||||
key={`pair_${chainIndex}`}
|
||||
@@ -1043,11 +1249,10 @@ export function ReviewPointsList({
|
||||
>
|
||||
<div className="value-source text-xs text-gray-500 mb-1">{chain[0].field}
|
||||
{!chain[0].data.page && !chain[0].data.value && (
|
||||
<i className="ri-information-line text-red-500 text-xs ml-1"></i>
|
||||
<i className="ri-information-line text-red-500 text-xs ml-1" title="没有找到对应的文书内容"></i>
|
||||
)}
|
||||
</div>
|
||||
<div className="value-content text-xs overflow-hidden line-clamp-2 hover:line-clamp-none hover:shadow-[0_0_10px_rgba(0,0,0,0.1)]
|
||||
hover:z-10 hover:overflow-auto rounded transition-all duration-300 ease-in-out cursor-text max-h-96">{chain[0].data.value?.toString() || ''}</div>
|
||||
<ReactTableTooltip content={chain[0].data.value?.toString() || ''} />
|
||||
</button>
|
||||
<button
|
||||
className={`value-box flex flex-col flex-1 p-2 text-left ${res ? 'hover:bg-[rgba(0,128,0,0.1)]' : 'hover:bg-[rgba(255,255,0,0.1)]'} transition-colors hover:shadow-[0_0_10px_rgba(0,0,0,0.1)]`}
|
||||
@@ -1066,11 +1271,10 @@ export function ReviewPointsList({
|
||||
>
|
||||
<div className="value-source text-xs text-gray-500 mb-1">{chain[1].field}
|
||||
{!chain[1].data.page && !chain[1].data.value && (
|
||||
<i className="ri-information-line text-red-500 text-xs ml-1"></i>
|
||||
<i className="ri-information-line text-red-500 text-xs ml-1" title="没有找到对应的文书内容"></i>
|
||||
)}
|
||||
</div>
|
||||
<div className="value-content text-xs overflow-hidden line-clamp-2 hover:line-clamp-none hover:shadow-[0_0_10px_rgba(0,0,0,0.1)]
|
||||
hover:z-10 hover:overflow-auto rounded transition-all duration-300 ease-in-out cursor-text max-h-96">{chain[1].data.value?.toString() || ''}</div>
|
||||
<ReactTableTooltip content={chain[1].data.value?.toString() || ''} />
|
||||
</button>
|
||||
</div>
|
||||
<div className="status-indicator tooltip w-8 flex items-center justify-center group relative">
|
||||
@@ -1079,28 +1283,35 @@ export function ReviewPointsList({
|
||||
) : (
|
||||
<i className="ri-alert-line text-warning text-base"></i>
|
||||
)}
|
||||
{/* 悬停提示框 - 横向布局 */}
|
||||
<div className="w-auto absolute hidden group-hover:block right-full top-1/2 transform -translate-y-1/2 mr-2 z-100">
|
||||
<div className="bg-white shadow-md rounded-md p-1 border border-gray-200">
|
||||
{/* 使用鼠标事件处理悬停提示 */}
|
||||
<div
|
||||
className="w-full h-full absolute top-0 left-0"
|
||||
onMouseEnter={(e) => {
|
||||
// 获取元素位置信息
|
||||
const rect = e.currentTarget.getBoundingClientRect();
|
||||
// 创建提示框内容
|
||||
const content = (
|
||||
<div className="flex flex-row gap-2 overflow-x-auto">
|
||||
<div key={chain[1].compareMethod} className={`rounded-md flex flex-row items-center ${
|
||||
res
|
||||
? 'bg-green-100 text-green-600'
|
||||
: 'bg-yellow-100 text-yellow-600'
|
||||
}`}>
|
||||
<div key={chain[1].compareMethod} className={`rounded-md flex flex-row items-center`}>
|
||||
<div className="text-xs text-gray-600 whitespace-nowrap pl-1">
|
||||
{typeof chain[1].compareMethod === 'object'
|
||||
? ''
|
||||
: getCompareMethodText(chain[1].compareMethod)}
|
||||
: `${getCompareMethodText(chain[1].compareMethod)}:`}
|
||||
</div>
|
||||
<div className={`p-1 text-xs font-medium rounded-full min-w-[50px] text-center`}>
|
||||
<div className={`p-1 text-xs rounded-full min-w-[50px] text-center`}>
|
||||
{res ? '通过' : '不通过'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute top-1/2 right-0 transform translate-x-1/2 -translate-y-1/2 rotate-45 w-2 h-2 bg-white border-t border-r border-gray-200"></div>
|
||||
</div>
|
||||
);
|
||||
// 显示提示框,稍微向下偏移,便于鼠标移动到tooltip上
|
||||
showTooltip(content, {
|
||||
top: rect.top + rect.height/2,
|
||||
left: rect.left
|
||||
});
|
||||
}}
|
||||
onMouseLeave={hideTooltip}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -1143,6 +1354,41 @@ export function ReviewPointsList({
|
||||
|
||||
const [, mainTypeValue] = mainTypeEntry;
|
||||
|
||||
/**
|
||||
* 创建提示框内容
|
||||
* 这个函数返回一个React节点,用于在提示框中显示
|
||||
* 它将为每种规则类型(exists/format/logic/regex)创建一个带有状态标识的项目
|
||||
*/
|
||||
const createTooltipContent = () => {
|
||||
return (
|
||||
<div className="flex flex-row gap-2 overflow-x-auto max-h-[300px]">
|
||||
{Object.entries(fieldValue?.type || {}).map(([typeKey, typeValue]) => (
|
||||
<div key={typeKey} className={`rounded-md flex flex-row items-center`}>
|
||||
<div className="text-xs text-gray-600 pl-1 whitespace-nowrap">{getRuleTypeText(typeKey)}:</div>
|
||||
<div className={`p-1 text-xs rounded-full min-w-[50px] text-center`}>
|
||||
{typeValue.res ? '通过' : '不通过'}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理鼠标悬停事件
|
||||
* 当鼠标悬停在状态指示器上时,计算提示框应该显示的位置并显示提示框
|
||||
* @param e 鼠标事件对象
|
||||
*/
|
||||
const handleMouseEnter = (e: React.MouseEvent): void => {
|
||||
// 获取触发元素的位置信息
|
||||
const rect = e.currentTarget.getBoundingClientRect();
|
||||
// 调用全局函数显示提示框,传递内容和位置信息
|
||||
showTooltip(
|
||||
createTooltipContent(),
|
||||
{ top: rect.top + rect.height/2, left: rect.left }
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
className={`border border-gray rounded-md overflow-hidden mb-2 ${overallResult ? 'bg-[rgba(246,255,237,1)]' : 'bg-[rgba(255,251,230,1)]'} flex w-full text-left
|
||||
@@ -1173,7 +1419,7 @@ export function ReviewPointsList({
|
||||
<div className="text-xs text-gray-500 mb-1">
|
||||
{fieldKey}
|
||||
{!mainTypeValue.page && !mainTypeValue.value && (
|
||||
<i className="ri-information-line text-red-500 text-xs ml-1"></i>
|
||||
<i className="ri-information-line text-red-500 text-xs ml-1" title="没有找到对应的文书内容"></i>
|
||||
)}
|
||||
{/* 缺失显示 */}
|
||||
{mainTypeValue.res === false && !mainTypeValue.value && (
|
||||
@@ -1183,74 +1429,22 @@ export function ReviewPointsList({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 规则类型标签组 - 只在有多个类型时显示,且只显示非主要类型 */}
|
||||
{/* {Object.keys(fieldValue?.type || {}).length > 1 && (
|
||||
<div className="flex flex-wrap gap-1 mb-2">
|
||||
{Object.entries(fieldValue?.type || {})
|
||||
.filter(([typeKey]) => typeKey !== mainTypeKey) // 排除主要类型
|
||||
.map(([typeKey, typeValue]) => (
|
||||
<button
|
||||
key={typeKey}
|
||||
className={`text-xs px-2 py-0.5 rounded-full ${typeValue.res ? 'bg-green-100 text-green-800 hover:bg-green-300' : 'bg-yellow-100 text-yellow-800'}`}
|
||||
onClick={() => {
|
||||
if (typeValue.page && typeof onReviewPointSelect === 'function') {
|
||||
onReviewPointSelect(reviewPoint.id, Number(typeValue.page));
|
||||
}
|
||||
}}
|
||||
title={`${typeKey}: ${typeValue.res ? '通过' : '不通过'}`}
|
||||
>
|
||||
{typeKey}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)} */}
|
||||
|
||||
{/* 主要值显示 */}
|
||||
{mainTypeValue.value && (
|
||||
<button
|
||||
className="text-xs p-1 rounded cursor-text w-full text-left"
|
||||
// onClick={(e) => {
|
||||
// if (mainTypeValue.page && typeof onReviewPointSelect === 'function') {
|
||||
// e.stopPropagation();
|
||||
// onReviewPointSelect(reviewPoint.id, Number(mainTypeValue.page));
|
||||
// }
|
||||
// }}
|
||||
>
|
||||
<div className="text-gray-800 break-all overflow-hidden line-clamp-2 hover:line-clamp-none hover:shadow-[0_0_10px_rgba(0,0,0,0.1)]
|
||||
hover: z-10 hover:overflow-auto rounded transition-all duration-300 ease-in-out cursor-text max-h-96">
|
||||
{mainTypeValue.value}
|
||||
</div>
|
||||
</button>
|
||||
<ReactTableTooltip content={mainTypeValue.value} />
|
||||
)}
|
||||
</div>
|
||||
<div className={`w-8 flex items-center justify-center rounded-r-md group relative`}>
|
||||
{/* 状态指示器 - 绑定鼠标事件用于显示/隐藏提示框 */}
|
||||
<div
|
||||
className="w-8 flex items-center justify-center rounded-r-md"
|
||||
onMouseEnter={handleMouseEnter} // 鼠标进入时显示提示框
|
||||
onMouseLeave={hideTooltip} // 鼠标离开时隐藏提示框
|
||||
>
|
||||
{overallResult ? (
|
||||
<i className="ri-check-line text-success text-base hover:text-green-800" ></i>
|
||||
) : (
|
||||
<i className="ri-alert-line text-warning text-base hover:text-yellow-800" ></i>
|
||||
)}
|
||||
|
||||
{/* 悬停提示框 - 横向布局 */}
|
||||
<div className="w-auto absolute hidden group-hover:block right-full top-1/2 transform -translate-y-1/2 mr-2 z-100">
|
||||
<div className="bg-white shadow-md rounded-md p-1 border border-gray-200">
|
||||
{/* <div className="text-xs font-medium text-gray-700 w-auto">规则检查结果</div> */}
|
||||
<div className="flex flex-row gap-2 overflow-x-auto">
|
||||
{Object.entries(fieldValue?.type || {}).map(([typeKey, typeValue]) => (
|
||||
<div key={typeKey} className={`rounded-md flex flex-row items-center ${
|
||||
typeValue.res
|
||||
? 'bg-green-100 text-green-600'
|
||||
: 'bg-yellow-100 text-yellow-600'
|
||||
}`}>
|
||||
<div className="text-xs text-gray-600 pl-1 whitespace-nowrap">{getRuleTypeText(typeKey)}</div>
|
||||
<div className={`p-1 text-xs font-medium rounded-full min-w-[50px] text-center`}>
|
||||
{typeValue.res ? '通过' : '不通过'}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute top-1/2 right-0 transform translate-x-1/2 -translate-y-1/2 rotate-45 w-2 h-2 bg-white border-t border-r border-gray-200"></div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
@@ -1260,12 +1454,19 @@ export function ReviewPointsList({
|
||||
|
||||
/**
|
||||
* 渲染评查点大模型判断的规则的样式
|
||||
* @param aiRule 评查点大模型判断的规则
|
||||
* @param reviewPoint 关联的评查点
|
||||
* @returns 评查点大模型判断的规则的样式
|
||||
*
|
||||
* 该函数处理AI模型评估的结果展示,包括:
|
||||
* 1. 从规则配置中提取字段和评估结果
|
||||
* 2. 为每个字段创建可点击的UI元素,显示内容和评估状态
|
||||
* 3. 展示模型的评估消息
|
||||
* 4. 处理字段点击导航到相应页面的逻辑
|
||||
*
|
||||
* @param aiRule 评查点大模型判断的规则对象
|
||||
* @param reviewPoint 关联的评查点对象
|
||||
* @returns React组件,用于显示AI模型评估结果
|
||||
*/
|
||||
const renderModelRule = (aiRule: Record<string, unknown>, reviewPoint: ReviewPoint) => {
|
||||
// console.log('aiRule-------', aiRule);
|
||||
// 从aiRule中提取配置信息
|
||||
const config = aiRule.config as {
|
||||
model?: string;
|
||||
fields?: Record<string, {
|
||||
@@ -1276,16 +1477,12 @@ export function ReviewPointsList({
|
||||
res?: boolean;
|
||||
} | undefined;
|
||||
|
||||
// 如果配置不存在,不渲染任何内容
|
||||
if (!config) return null;
|
||||
|
||||
// 创建一个数组来存储需要渲染的JSX元素
|
||||
const fieldElements: JSX.Element[] = [];
|
||||
|
||||
// 先存放一条粗的横线
|
||||
// fieldElements.push(
|
||||
// <div key="line" className=" bg-gray-50 rounded border border-gray-200 text-xs mb-3"></div>
|
||||
// );
|
||||
|
||||
// 遍历fields,获取每个字段的值并生成对应的JSX元素
|
||||
if (config.fields) {
|
||||
Object.entries(config.fields).forEach(([key, value], index) => {
|
||||
@@ -1305,6 +1502,7 @@ export function ReviewPointsList({
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
// 键盘导航支持
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
if (value.page && typeof onReviewPointSelect === 'function') {
|
||||
@@ -1318,13 +1516,13 @@ export function ReviewPointsList({
|
||||
type="button"
|
||||
aria-label={`查看${key}内容详情`}
|
||||
>
|
||||
<div className="p-1 flex-1">
|
||||
<div className="p-1 flex-1 text-left">
|
||||
{/* 字段名称 */}
|
||||
<div className="text-xs text-left text-gray-500 mb-1">
|
||||
{key}
|
||||
{/* 没有抽取到目录内容,page和value都为空 */}
|
||||
{!value.page && !value.value && (
|
||||
<i className="ri-information-line text-red-500 text-xs ml-1"></i>
|
||||
<i className="ri-information-line text-red-500 text-xs ml-1" title="没有找到对应的文书内容"></i>
|
||||
)}
|
||||
{/* 缺失显示 */}
|
||||
{!res && (
|
||||
@@ -1336,19 +1534,7 @@ export function ReviewPointsList({
|
||||
|
||||
{/* 主要值显示 */}
|
||||
{res && (
|
||||
<button
|
||||
className="text-xs p-1 rounded cursor-text w-full text-left"
|
||||
// onClick={() => {
|
||||
// if (value.page && typeof onReviewPointSelect === 'function') {
|
||||
// onReviewPointSelect(reviewPoint.id, Number(value.page));
|
||||
// }
|
||||
// }}
|
||||
>
|
||||
<div className="text-gray-800 break-all overflow-hidden line-clamp-2 hover:line-clamp-none hover:shadow-[0_0_10px_rgba(0,0,0,0.1)]
|
||||
hover: z-10 hover:overflow-auto rounded transition-all duration-300 ease-in-out cursor-text max-h-96">
|
||||
{value.value}
|
||||
</div>
|
||||
</button>
|
||||
<ReactTableTooltip content={value.value} />
|
||||
)}
|
||||
</div>
|
||||
<div className={`w-8 flex items-center justify-center rounded-r-md group relative`}>
|
||||
@@ -1357,38 +1543,45 @@ export function ReviewPointsList({
|
||||
) : (
|
||||
<i className="ri-alert-line text-warning text-base hover:text-yellow-800" ></i>
|
||||
)}
|
||||
{/* 悬停提示框 - 横向布局 */}
|
||||
<div className="w-auto absolute hidden group-hover:block right-full top-1/2 transform -translate-y-1/2 mr-2 z-100">
|
||||
<div className="bg-white shadow-md rounded-md p-1 border border-gray-200">
|
||||
{/* <div className="text-xs font-medium text-gray-700 w-auto">规则检查结果</div> */}
|
||||
{/* 使用鼠标事件处理悬停提示 */}
|
||||
<div
|
||||
className="w-full h-full absolute top-0 left-0"
|
||||
onMouseEnter={(e) => {
|
||||
// 获取元素位置信息
|
||||
const rect = e.currentTarget.getBoundingClientRect();
|
||||
// 创建提示框内容
|
||||
const content = (
|
||||
<div className="flex flex-row gap-2 overflow-x-auto">
|
||||
<div className={`rounded-md flex flex-row items-center ${
|
||||
res
|
||||
? 'bg-green-100 text-green-600'
|
||||
: 'bg-yellow-100 text-yellow-600'
|
||||
}`}>
|
||||
<div className="text-xs text-gray-600 pl-1 whitespace-nowrap">大模型判断</div>
|
||||
<div className={`p-1 text-xs font-medium rounded-full min-w-[50px] text-center`}>
|
||||
<div className={`rounded-md flex flex-row items-center`}>
|
||||
<div className="text-xs text-gray-600 pl-1 whitespace-nowrap">大模型判断:</div>
|
||||
<div className={`p-1 text-xs rounded-full min-w-[50px] text-center`}>
|
||||
{res ? '通过' : '不通过'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute top-1/2 right-0 transform translate-x-1/2 -translate-y-1/2 rotate-45 w-2 h-2 bg-white border-t border-r border-gray-200"></div>
|
||||
</div>
|
||||
);
|
||||
// 显示提示框,稍微向下偏移,便于鼠标移动到tooltip上
|
||||
showTooltip(content, {
|
||||
top: rect.top + rect.height/2,
|
||||
left: rect.left
|
||||
});
|
||||
}}
|
||||
onMouseLeave={hideTooltip}
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// 后处理message
|
||||
// 渲染AI模型返回的评估消息
|
||||
if (config.message) {
|
||||
// 检查message是否为对象,如果是则转换为字符串
|
||||
const messageContent = typeof config.message === 'object'
|
||||
? JSON.stringify(config.message)
|
||||
: String(config.message);
|
||||
|
||||
// 添加模型评估消息区域,使用蓝色背景突出显示
|
||||
fieldElements.push(
|
||||
<div key="message" className="p-2 bg-blue-50 rounded border border-blue-200 text-xs mb-3 select-text">
|
||||
<div className="flex flex-row items-center">
|
||||
@@ -1406,12 +1599,24 @@ export function ReviewPointsList({
|
||||
|
||||
|
||||
/**
|
||||
* 过滤评查点中的规则,把type是exist、format、logic、regex的规则中重复的进行去重
|
||||
* @param reviewPoint 评查点
|
||||
* @returns 合并后的规则数组
|
||||
* 过滤评查点中的规则,把type是exists、format、logic、regex的规则中重复的进行去重和合并
|
||||
*
|
||||
* 该函数的主要作用:
|
||||
* 1. 从评查点的evaluatedPointResultsLog中提取特定类型的规则
|
||||
* 2. 将相同字段(fieldKey)的不同规则类型结果合并到一起
|
||||
* 3. 为UI渲染准备统一结构的数据
|
||||
*
|
||||
* 支持的规则类型:
|
||||
* - exists: 有无判断规则
|
||||
* - format: 格式判断规则
|
||||
* - logic: 逻辑判断规则
|
||||
* - regex: 正则表达式规则
|
||||
*
|
||||
* @param reviewPoint 评查点对象
|
||||
* @returns 合并后的规则数组,每个元素包含字段名和各类规则的评估结果
|
||||
*/
|
||||
const filterOtherRule = (reviewPoint: ReviewPoint) => {
|
||||
// 遍历这些评查点中的规则evaluatedPointResultsLog,把type是exist、format、logic、regex的规则添加到allRule中
|
||||
// 定义接口描述规则字段值的结构
|
||||
interface RuleFieldValue {
|
||||
page?: number | string;
|
||||
value?: string;
|
||||
@@ -1424,6 +1629,7 @@ export function ReviewPointsList({
|
||||
}> = [];
|
||||
|
||||
for (const rule of reviewPoint.evaluatedPointResultsLog?.rules || []) {
|
||||
// 处理"有无判断"类型的规则
|
||||
if (rule.type === 'exists') {
|
||||
// 使用类型断言获取config对象的具体结构
|
||||
const config = rule.config as {
|
||||
@@ -1469,6 +1675,8 @@ export function ReviewPointsList({
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 处理"格式判断"类型的规则
|
||||
if (rule.type === 'format') {
|
||||
// 使用类型断言获取config对象的具体结构
|
||||
const config = rule.config as {
|
||||
@@ -1491,7 +1699,7 @@ export function ReviewPointsList({
|
||||
fieldKey: key,
|
||||
fieldValue: {
|
||||
...fieldValue,
|
||||
type: { format: config.res }
|
||||
type: { format: config.res } // 标记为format类型,结果为config.res
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1499,6 +1707,8 @@ export function ReviewPointsList({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理"逻辑判断"类型的规则
|
||||
if (rule.type === 'logic') {
|
||||
// 使用类型断言获取config对象的具体结构
|
||||
const config = rule.config as {
|
||||
@@ -1533,6 +1743,8 @@ export function ReviewPointsList({
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 处理"正则表达式"类型的规则
|
||||
if (rule.type === 'regex') {
|
||||
// 使用类型断言获取config对象的具体结构
|
||||
const config = rule.config as {
|
||||
@@ -1576,7 +1788,7 @@ export function ReviewPointsList({
|
||||
};
|
||||
}> = [];
|
||||
|
||||
// 使用对象存储相同fieldKey的项
|
||||
// 使用对象存储相同fieldKey的项,便于快速查找和合并
|
||||
const fieldKeyMap: Record<string, {
|
||||
fieldKey: string;
|
||||
fieldValue: {
|
||||
@@ -1588,7 +1800,7 @@ export function ReviewPointsList({
|
||||
};
|
||||
}> = {};
|
||||
|
||||
// 第一步:按fieldKey分组
|
||||
// 第一步:按fieldKey分组并合并不同类型的规则结果
|
||||
allRule.forEach(item => {
|
||||
const fieldKey = item.fieldKey;
|
||||
const fieldValue = item.fieldValue;
|
||||
@@ -1599,7 +1811,7 @@ export function ReviewPointsList({
|
||||
const page = fieldValue.page;
|
||||
const value = fieldValue.value;
|
||||
|
||||
// 如果是第一次遇到这个fieldKey
|
||||
// 如果是第一次遇到这个fieldKey,创建新条目
|
||||
if (!fieldKeyMap[fieldKey]) {
|
||||
// 创建新的结构
|
||||
fieldKeyMap[fieldKey] = {
|
||||
@@ -1610,7 +1822,7 @@ export function ReviewPointsList({
|
||||
};
|
||||
}
|
||||
|
||||
// 将类型信息添加到type对象中
|
||||
// 将类型信息添加到type对象中,允许一个字段有多种规则类型的结果
|
||||
fieldKeyMap[fieldKey].fieldValue.type[typeKey] = {
|
||||
res: typeValue,
|
||||
page,
|
||||
@@ -1623,8 +1835,7 @@ export function ReviewPointsList({
|
||||
mergedRules.push(fieldKeyMap[key]);
|
||||
}
|
||||
|
||||
// console.log('合并后的规则:', mergedRules);
|
||||
// setOtherRule(mergedRules);
|
||||
// 返回合并后的规则数组
|
||||
return mergedRules;
|
||||
};
|
||||
|
||||
@@ -1670,6 +1881,7 @@ export function ReviewPointsList({
|
||||
{checkContentPage(reviewPoint).pageIndex === 0 && (
|
||||
<p className="text-xs text-red-500 select-text text-left mb-1">该评查点无法找到索引内容,无法自动定位到对应页面。</p>
|
||||
)}
|
||||
|
||||
<div className="mt-2">
|
||||
{/* {reviewPoint.suggestion && (
|
||||
<div className="p-2 bg-blue-50 rounded border border-blue-200 text-xs mb-3 select-text">
|
||||
@@ -1682,7 +1894,7 @@ export function ReviewPointsList({
|
||||
|
||||
{/* 评查点内容显示区域 */}
|
||||
{reviewPoint.content && Object.entries(reviewPoint.content).length > 0 && (
|
||||
<div className="p-2 bg-white rounded border border-gray-200 text-xs mb-3 select-text">
|
||||
<div className="bg-white rounded border-gray-200 text-xs mb-3 select-text">
|
||||
{/* 修改评查结果的结构之后,显示新的结构 */}
|
||||
{renderContent(reviewPoint, mergedRules)}
|
||||
</div>
|
||||
@@ -1805,7 +2017,7 @@ export function ReviewPointsList({
|
||||
{reviewPoint.content !== null && Object.keys(reviewPoint.content).length > 0 && (
|
||||
<>
|
||||
{/* 内容显示区域 */}
|
||||
<div className="p-2 bg-white rounded border border-gray-200 text-xs mb-3 select-text">
|
||||
<div className="bg-white rounded border-gray-200 text-xs mb-3 select-text">
|
||||
<div>
|
||||
{/* 修改评查结果的结构之后,显示新的结构 */}
|
||||
{renderContent(reviewPoint, mergedRules)}
|
||||
@@ -1969,6 +2181,7 @@ export function ReviewPointsList({
|
||||
// 组件主渲染函数
|
||||
return (
|
||||
<div className="review-points-panel select-text">
|
||||
<TooltipPortal />
|
||||
{/* 面板头部 */}
|
||||
<div className="review-panel-header py-2 px-4 flex items-center">
|
||||
<i className="ri-file-list-check-line text-primary mr-2"></i>
|
||||
@@ -1987,14 +2200,15 @@ export function ReviewPointsList({
|
||||
filteredReviewPoints.map(reviewPoint => (
|
||||
<div
|
||||
key={reviewPoint.id}
|
||||
className={`rounded-md review-point-item ${activeReviewPointResultId === reviewPoint.id ? 'active border-l-4 border-l-[#00684a] shadow-md' : 'border-l-4 border-l-transparent'}
|
||||
transition-all duration-300 ease-in-out
|
||||
hover:shadow-lg hover:-translate-x-0.5 hover:bg-[rgba(0,0,0,0.02)] my-2`}
|
||||
className={`rounded-md review-point-item ${activeReviewPointResultId === reviewPoint.id ? 'active border-l-4 !border-l-[rgba(0,104,74,1)] shadow-md' : 'border-l-4 border-l-transparent'}
|
||||
transition-all duration-300 ease-in-out shadow-sm
|
||||
hover:shadow-lg hover:border-l-4 hover:border-l-[rgba(0,104,74,0.3)]
|
||||
hover:bg-[rgba(0,0,0,0.02)] my-2`}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
style={{ userSelect: 'text' }}
|
||||
onClick={() => {
|
||||
// console.log('reviewPoint', reviewPoint);
|
||||
console.log('reviewPoint', reviewPoint);
|
||||
handleReviewPointClick(reviewPoint.id);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
@@ -2006,12 +2220,12 @@ export function ReviewPointsList({
|
||||
{/* 评查点标题和状态 */}
|
||||
{/* 评查点名称 pointName*/}
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<div className='flex flex-col'>
|
||||
<div className="review-point-title flex-1 text-left text-blue-500">{reviewPoint.pointName}</div>
|
||||
<div className="review-point-header flex justify-between items-start">
|
||||
{/* <div className='flex flex-col'> */}
|
||||
<div className="review-point-title text-left text-blue-500 max-w-[75%] break-all">{reviewPoint.pointName}</div>
|
||||
{/* <div className="review-point-header flex justify-between items-start">
|
||||
<div className="flex-1 text-left min-w-[25%] font-medium text-[13px]">{reviewPoint.title}</div>
|
||||
{/* 评查点所属分组 */}
|
||||
{/* <div className="review-point-location max-w-[40%] flex items-center">
|
||||
//评查点分组显示
|
||||
<div className="review-point-location max-w-[40%] flex items-center">
|
||||
<i className="ri-file-list-line mr-1 flex-shrink-0"></i>
|
||||
<span
|
||||
className="truncate block whitespace-nowrap overflow-hidden
|
||||
@@ -2021,11 +2235,11 @@ export function ReviewPointsList({
|
||||
>
|
||||
{reviewPoint.groupName}
|
||||
</span>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-center ml-2 flex-shrink-0 max-w-[15%]">
|
||||
{renderStatusBadge(reviewPoint.status, reviewPoint.result)}
|
||||
{/* </div> */}
|
||||
<div className="flex ml-2 flex-shrink-0 min-w-[15%]">
|
||||
{renderStatusBadge(reviewPoint.status, reviewPoint.result,reviewPoint.title)}
|
||||
{renderHumanReviewBadge(reviewPoint)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -105,7 +105,9 @@ export function ReviewTabs({ activeTab, onTabChange, children, fileInfo, onConfi
|
||||
>
|
||||
<i className="ri-lightbulb-line"></i> AI智能分析
|
||||
</button> */}
|
||||
{fileInfo.type === '1' && (
|
||||
{/* {fileInfo.type === '1' && ( */}
|
||||
{/* 隐藏结构比对 */}
|
||||
{fileInfo.type === '999999' && (
|
||||
<button
|
||||
className={`tab-nav-item ${activeTab === 'filecompare' ? 'active' : ''}`}
|
||||
onClick={() => onTabChange('filecompare')}
|
||||
|
||||
@@ -0,0 +1,295 @@
|
||||
import { useState, useEffect, useRef, ReactNode } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import '../../styles/components/TooltipStyles.css';
|
||||
|
||||
// 提示框主题
|
||||
export type TooltipTheme = 'dark' | 'light' | 'primary' | 'success' | 'warning' | 'error' | 'info';
|
||||
|
||||
// 提示框位置
|
||||
export type TooltipPlacement = 'top' | 'bottom' | 'left' | 'right';
|
||||
|
||||
// 提示框属性
|
||||
export interface TooltipProps {
|
||||
children: ReactNode; // 触发元素
|
||||
content: ReactNode; // 提示内容
|
||||
theme?: TooltipTheme; // 主题样式
|
||||
placement?: TooltipPlacement; // 显示位置
|
||||
rich?: boolean; // 是否为富文本提示
|
||||
header?: ReactNode; // 富文本提示标题
|
||||
footer?: ReactNode; // 富文本提示页脚
|
||||
trigger?: 'hover' | 'click'; // 触发方式
|
||||
visible?: boolean; // 是否显示(受控模式)
|
||||
showArrow?: boolean; // 是否显示箭头
|
||||
maxWidth?: number | string; // 最大宽度
|
||||
className?: string; // 自定义类名
|
||||
onVisibleChange?: (visible: boolean) => void; // 显示状态变化回调
|
||||
}
|
||||
|
||||
/**
|
||||
* Tooltip 组件
|
||||
*
|
||||
* 提供增强的悬浮提示功能,支持多种主题、位置和富文本模式
|
||||
*/
|
||||
export function Tooltip({
|
||||
children,
|
||||
content,
|
||||
theme = 'dark',
|
||||
placement = 'top',
|
||||
rich = false,
|
||||
header,
|
||||
footer,
|
||||
trigger = 'hover',
|
||||
visible: controlledVisible,
|
||||
showArrow = true,
|
||||
maxWidth = 320,
|
||||
className = '',
|
||||
onVisibleChange
|
||||
}: TooltipProps) {
|
||||
// 使用内部状态管理提示框显示状态(非受控模式)
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
// 判断是否为受控组件
|
||||
const isControlled = controlledVisible !== undefined;
|
||||
|
||||
// 获取实际显示状态
|
||||
const isVisible = isControlled ? controlledVisible : visible;
|
||||
|
||||
// 引用DOM元素
|
||||
const triggerRef = useRef<HTMLDivElement>(null);
|
||||
const tooltipRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// 处理显示状态变化
|
||||
const handleVisibleChange = (newVisible: boolean) => {
|
||||
if (!isControlled) {
|
||||
setVisible(newVisible);
|
||||
}
|
||||
onVisibleChange?.(newVisible);
|
||||
};
|
||||
|
||||
// 触发器事件处理
|
||||
const triggerEvents = trigger === 'hover'
|
||||
? {
|
||||
onMouseEnter: () => handleVisibleChange(true),
|
||||
onMouseLeave: () => handleVisibleChange(false),
|
||||
}
|
||||
: {
|
||||
onClick: () => handleVisibleChange(!isVisible),
|
||||
};
|
||||
|
||||
// 更新提示框位置的函数
|
||||
const updateTooltipPosition = () => {
|
||||
if (!isVisible || !triggerRef.current || !tooltipRef.current) return;
|
||||
|
||||
// 获取触发元素位置
|
||||
const triggerRect = triggerRef.current.getBoundingClientRect();
|
||||
const tooltipRect = tooltipRef.current.getBoundingClientRect();
|
||||
|
||||
// 设置最大宽度
|
||||
tooltipRef.current.style.maxWidth = typeof maxWidth === 'number'
|
||||
? `${maxWidth}px`
|
||||
: String(maxWidth);
|
||||
|
||||
// 计算各个方向的位置
|
||||
let top = 0, left = 0;
|
||||
let arrowTop = 0, arrowLeft = 0;
|
||||
const arrowSize = 8; // 箭头大小
|
||||
const gap = 10; // 提示框与触发元素的间距
|
||||
|
||||
switch (placement) {
|
||||
case 'top':
|
||||
left = triggerRect.left + (triggerRect.width / 2) - (tooltipRect.width / 2);
|
||||
top = triggerRect.top - tooltipRect.height - arrowSize;
|
||||
arrowLeft = tooltipRect.width / 2 - arrowSize;
|
||||
arrowTop = tooltipRect.height;
|
||||
break;
|
||||
case 'bottom':
|
||||
left = triggerRect.left + (triggerRect.width / 2) - (tooltipRect.width / 2);
|
||||
top = triggerRect.bottom + arrowSize;
|
||||
arrowLeft = tooltipRect.width / 2 - arrowSize;
|
||||
arrowTop = -arrowSize;
|
||||
break;
|
||||
case 'left':
|
||||
left = triggerRect.left - tooltipRect.width - arrowSize;
|
||||
top = triggerRect.top + (triggerRect.height / 2) - (tooltipRect.height / 2);
|
||||
arrowLeft = tooltipRect.width;
|
||||
arrowTop = tooltipRect.height / 2 - arrowSize;
|
||||
break;
|
||||
case 'right':
|
||||
left = triggerRect.right + arrowSize;
|
||||
top = triggerRect.top + (triggerRect.height / 2) - (tooltipRect.height / 2);
|
||||
arrowLeft = -arrowSize;
|
||||
arrowTop = tooltipRect.height / 2 - arrowSize;
|
||||
break;
|
||||
}
|
||||
|
||||
// 确保提示框不超出视口
|
||||
const viewportWidth = window.innerWidth;
|
||||
const viewportHeight = window.innerHeight;
|
||||
|
||||
// 调整位置,避免超出视口
|
||||
if (left < 0) {
|
||||
left = gap;
|
||||
arrowLeft = Math.max(triggerRect.left + (triggerRect.width / 2) - left - arrowSize, arrowSize * 2);
|
||||
} else if (left + tooltipRect.width > viewportWidth) {
|
||||
left = viewportWidth - tooltipRect.width - gap;
|
||||
arrowLeft = Math.min(triggerRect.left + (triggerRect.width / 2) - left - arrowSize, tooltipRect.width - arrowSize * 2);
|
||||
}
|
||||
|
||||
if (top < 0) {
|
||||
if (placement === 'top') {
|
||||
// 如果上方放不下,切换到下方
|
||||
top = triggerRect.bottom + arrowSize;
|
||||
arrowTop = -arrowSize;
|
||||
tooltipRef.current.classList.remove('tooltip-top');
|
||||
tooltipRef.current.classList.add('tooltip-bottom');
|
||||
} else {
|
||||
top = gap;
|
||||
arrowTop = Math.max(triggerRect.top + (triggerRect.height / 2) - top - arrowSize, arrowSize * 2);
|
||||
}
|
||||
} else if (top + tooltipRect.height > viewportHeight) {
|
||||
if (placement === 'bottom') {
|
||||
// 如果下方放不下,切换到上方
|
||||
top = triggerRect.top - tooltipRect.height - arrowSize;
|
||||
arrowTop = tooltipRect.height;
|
||||
tooltipRef.current.classList.remove('tooltip-bottom');
|
||||
tooltipRef.current.classList.add('tooltip-top');
|
||||
} else {
|
||||
top = viewportHeight - tooltipRect.height - gap;
|
||||
arrowTop = Math.min(triggerRect.top + (triggerRect.height / 2) - top - arrowSize, tooltipRect.height - arrowSize * 2);
|
||||
}
|
||||
}
|
||||
|
||||
// 应用位置样式
|
||||
tooltipRef.current.style.position = 'fixed';
|
||||
tooltipRef.current.style.top = `${top}px`;
|
||||
tooltipRef.current.style.left = `${left}px`;
|
||||
tooltipRef.current.style.transform = 'none'; // 重置任何变换
|
||||
|
||||
// 定位箭头
|
||||
const arrow = tooltipRef.current.querySelector('.tooltip-arrow') as HTMLElement;
|
||||
if (arrow && showArrow) {
|
||||
switch (placement) {
|
||||
case 'top':
|
||||
arrow.style.bottom = `-${arrowSize}px`;
|
||||
arrow.style.left = `${arrowLeft}px`;
|
||||
arrow.style.top = 'auto';
|
||||
arrow.style.right = 'auto';
|
||||
break;
|
||||
case 'bottom':
|
||||
arrow.style.top = `-${arrowSize}px`;
|
||||
arrow.style.left = `${arrowLeft}px`;
|
||||
arrow.style.bottom = 'auto';
|
||||
arrow.style.right = 'auto';
|
||||
break;
|
||||
case 'left':
|
||||
arrow.style.right = `-${arrowSize}px`;
|
||||
arrow.style.top = `${arrowTop}px`;
|
||||
arrow.style.bottom = 'auto';
|
||||
arrow.style.left = 'auto';
|
||||
break;
|
||||
case 'right':
|
||||
arrow.style.left = `-${arrowSize}px`;
|
||||
arrow.style.top = `${arrowTop}px`;
|
||||
arrow.style.bottom = 'auto';
|
||||
arrow.style.right = 'auto';
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 计算提示框位置
|
||||
useEffect(() => {
|
||||
if (!isVisible) return;
|
||||
|
||||
// 初始化位置
|
||||
updateTooltipPosition();
|
||||
|
||||
// 添加滚动和调整大小事件监听器
|
||||
window.addEventListener('scroll', updateTooltipPosition, true);
|
||||
window.addEventListener('resize', updateTooltipPosition);
|
||||
|
||||
// 定期更新位置(处理内容动态变化的情况)
|
||||
const intervalId = setInterval(updateTooltipPosition, 300);
|
||||
|
||||
// 清理函数
|
||||
return () => {
|
||||
window.removeEventListener('scroll', updateTooltipPosition, true);
|
||||
window.removeEventListener('resize', updateTooltipPosition);
|
||||
clearInterval(intervalId);
|
||||
};
|
||||
}, [isVisible, placement, maxWidth, showArrow]);
|
||||
|
||||
// 处理点击外部关闭
|
||||
useEffect(() => {
|
||||
if (trigger !== 'click' || !isVisible) return;
|
||||
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
triggerRef.current &&
|
||||
!triggerRef.current.contains(event.target as Node) &&
|
||||
tooltipRef.current &&
|
||||
!tooltipRef.current.contains(event.target as Node)
|
||||
) {
|
||||
handleVisibleChange(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, [trigger, isVisible]);
|
||||
|
||||
// 渲染提示框内容
|
||||
const renderTooltipContent = () => {
|
||||
if (rich) {
|
||||
return (
|
||||
<div className="tooltip-content">
|
||||
{header && <div className="tooltip-header">{header}</div>}
|
||||
<div className="tooltip-body">{content}</div>
|
||||
{footer && <div className="tooltip-footer">{footer}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="tooltip-content">
|
||||
<div>{content}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 生成提示框类名
|
||||
const tooltipClassNames = [
|
||||
'tooltip-container',
|
||||
`tooltip-${placement}`,
|
||||
`tooltip-${theme}`,
|
||||
rich ? 'tooltip-rich' : '',
|
||||
isVisible ? 'tooltip-visible' : '',
|
||||
className
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
// 使用Portal渲染提示框
|
||||
const tooltipPortal = isVisible && typeof document !== 'undefined'
|
||||
? createPortal(
|
||||
<div
|
||||
ref={tooltipRef}
|
||||
className={tooltipClassNames}
|
||||
style={{ maxWidth }}
|
||||
>
|
||||
{renderTooltipContent()}
|
||||
{showArrow && <div className="tooltip-arrow"></div>}
|
||||
</div>,
|
||||
document.body
|
||||
)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className="tooltip-trigger" ref={triggerRef} {...triggerEvents}>
|
||||
{children}
|
||||
{tooltipPortal}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Tooltip;
|
||||
@@ -0,0 +1,35 @@
|
||||
// 导出所有UI组件,方便统一引入
|
||||
|
||||
// 核心组件
|
||||
export { Button } from './Button';
|
||||
export { Card } from './Card';
|
||||
export { Modal } from './Modal';
|
||||
export { toastService } from './Toast';
|
||||
export { Tooltip } from './Tooltip';
|
||||
|
||||
// 数据展示组件
|
||||
export { Table } from './Table';
|
||||
export { Pagination } from './Pagination';
|
||||
export { Tag } from './Tag';
|
||||
export { FileTag } from './FileTag';
|
||||
export { FileTypeTag } from './FileTypeTag';
|
||||
export { StatusBadge } from './StatusBadge';
|
||||
export { StatusDot } from './StatusDot';
|
||||
export { FileIcon } from './FileIcon';
|
||||
|
||||
// 数据输入组件
|
||||
export { SearchBox } from './SearchBox';
|
||||
export { DateRangePicker } from './DateRangePicker';
|
||||
export { FilterPanel } from './FilterPanel';
|
||||
export { UploadArea } from './UploadArea';
|
||||
|
||||
// 反馈组件
|
||||
export { Alert } from './Alert';
|
||||
export { MessageModal } from './MessageModal';
|
||||
export { LoadingBar } from './LoadingBar';
|
||||
export { RouteChangeLoader } from './RouteChangeLoader';
|
||||
export { FileProgress } from './FileProgress';
|
||||
export { ProcessingSteps } from './ProcessingSteps';
|
||||
|
||||
// 示例组件(开发环境使用)
|
||||
export { TooltipExample } from '../../routes/examples/TooltipExample';
|
||||
@@ -0,0 +1,236 @@
|
||||
import { Tooltip } from '../../components/ui/Tooltip';
|
||||
|
||||
/**
|
||||
* Tooltip 组件示例
|
||||
* 展示不同主题、位置和风格的提示框
|
||||
*/
|
||||
export function TooltipExample() {
|
||||
return (
|
||||
<div className="tooltip-examples p-4">
|
||||
<h2 className="text-lg font-bold mb-4">Tooltip 组件示例</h2>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
{/* 基础提示框 */}
|
||||
<div className="example-section">
|
||||
<h3 className="text-base font-semibold mb-2">基础提示框</h3>
|
||||
<div className="flex space-x-4 mb-4">
|
||||
<Tooltip content="这是一个基础提示框">
|
||||
<button className="px-3 py-1 bg-gray-200 rounded hover:bg-gray-300">
|
||||
鼠标悬停
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip content="点击显示的提示框" trigger="click">
|
||||
<button className="px-3 py-1 bg-gray-200 rounded hover:bg-gray-300">
|
||||
点击查看
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 不同位置 */}
|
||||
<div className="example-section">
|
||||
<h3 className="text-base font-semibold mb-2">不同位置</h3>
|
||||
<div className="flex space-x-4 mb-4">
|
||||
<Tooltip content="顶部提示" placement="top">
|
||||
<button className="px-3 py-1 bg-gray-200 rounded hover:bg-gray-300">
|
||||
顶部
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip content="底部提示" placement="bottom">
|
||||
<button className="px-3 py-1 bg-gray-200 rounded hover:bg-gray-300">
|
||||
底部
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip content="左侧提示" placement="left">
|
||||
<button className="px-3 py-1 bg-gray-200 rounded hover:bg-gray-300">
|
||||
左侧
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip content="右侧提示" placement="right">
|
||||
<button className="px-3 py-1 bg-gray-200 rounded hover:bg-gray-300">
|
||||
右侧
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 不同主题 */}
|
||||
<div className="example-section">
|
||||
<h3 className="text-base font-semibold mb-2">不同主题</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Tooltip content="深色主题" theme="dark">
|
||||
<span className="px-3 py-1 border border-gray-300 rounded cursor-default">
|
||||
深色
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip content="浅色主题" theme="light">
|
||||
<span className="px-3 py-1 border border-gray-300 rounded cursor-default">
|
||||
浅色
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip content="主题色" theme="primary">
|
||||
<span className="px-3 py-1 border border-gray-300 rounded cursor-default">
|
||||
主题色
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip content="成功提示" theme="success">
|
||||
<span className="px-3 py-1 border border-gray-300 rounded cursor-default">
|
||||
成功
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip content="警告提示" theme="warning">
|
||||
<span className="px-3 py-1 border border-gray-300 rounded cursor-default">
|
||||
警告
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip content="错误提示" theme="error">
|
||||
<span className="px-3 py-1 border border-gray-300 rounded cursor-default">
|
||||
错误
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip content="信息提示" theme="info">
|
||||
<span className="px-3 py-1 border border-gray-300 rounded cursor-default">
|
||||
信息
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 富文本提示框 */}
|
||||
<div className="example-section">
|
||||
<h3 className="text-base font-semibold mb-2">富文本提示框</h3>
|
||||
<div className="flex space-x-4">
|
||||
<Tooltip
|
||||
content={
|
||||
<div>
|
||||
<div className="flex items-center mb-1">
|
||||
<span className="w-24 text-gray-500">CPU使用率:</span>
|
||||
<span className="text-green-500 font-medium">32%</span>
|
||||
</div>
|
||||
<div className="flex items-center mb-1">
|
||||
<span className="w-24 text-gray-500">内存使用率:</span>
|
||||
<span className="text-yellow-500 font-medium">76%</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<span className="w-24 text-gray-500">磁盘空间:</span>
|
||||
<span className="text-blue-500 font-medium">245GB/500GB</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
theme="light"
|
||||
rich={true}
|
||||
header="系统性能报告"
|
||||
footer="更新时间: 2023-10-15 15:30:42"
|
||||
showArrow={true}
|
||||
>
|
||||
<button className="px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600">
|
||||
性能分析
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip
|
||||
content={
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<span>本月销售额:</span>
|
||||
<span className="text-green-400 font-medium">¥258,432</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<span>环比增长:</span>
|
||||
<span className="text-green-400 font-medium">+15.8%</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span>销售热点:</span>
|
||||
<span className="text-yellow-400 font-medium">电子产品</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
theme="dark"
|
||||
rich={true}
|
||||
header="销售数据分析"
|
||||
footer="数据来源: 销售管理系统"
|
||||
placement="bottom"
|
||||
>
|
||||
<button className="px-3 py-1 bg-green-500 text-white rounded hover:bg-green-600">
|
||||
销售趋势
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 条件渲染示例 */}
|
||||
<div className="example-section">
|
||||
<h3 className="text-base font-semibold mb-2">状态提示示例</h3>
|
||||
<div className="flex space-x-4">
|
||||
<Tooltip
|
||||
content="优秀:得分在90分以上"
|
||||
theme="success"
|
||||
>
|
||||
<span className="text-green-500 font-bold px-2">95</span>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip
|
||||
content="良好:得分在60-90分之间"
|
||||
theme="warning"
|
||||
>
|
||||
<span className="text-yellow-500 px-2">75</span>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip
|
||||
content="不及格:得分低于60分"
|
||||
theme="error"
|
||||
>
|
||||
<span className="text-red-500 px-2">45</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 自定义样式示例 */}
|
||||
<div className="example-section">
|
||||
<h3 className="text-base font-semibold mb-2">自定义样式</h3>
|
||||
<div className="flex space-x-4">
|
||||
<Tooltip
|
||||
content="带有自定义宽度的提示框"
|
||||
maxWidth={150}
|
||||
>
|
||||
<span className="border-b border-dotted border-gray-500 cursor-help">
|
||||
自定义宽度
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip
|
||||
content="没有箭头的提示框"
|
||||
showArrow={false}
|
||||
theme="primary"
|
||||
>
|
||||
<span className="border-b border-dotted border-blue-500 cursor-help">
|
||||
无箭头
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip
|
||||
content="自定义类名"
|
||||
className="custom-tooltip"
|
||||
>
|
||||
<span className="border-b border-dotted border-purple-500 cursor-help text-purple-500">
|
||||
自定义类名
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default TooltipExample;
|
||||
@@ -548,7 +548,8 @@ export default function ReviewDetails() {
|
||||
)}
|
||||
{reviewData.fileInfo.uploadTime && (
|
||||
<div className="text-xs text-gray-500">
|
||||
| 上传时间:{reviewData.fileInfo.uploadTime} | 上传用户:{reviewData.fileInfo.uploadUser}
|
||||
| 上传时间:{reviewData.fileInfo.uploadTime}
|
||||
{/* | 上传用户:{reviewData.fileInfo.uploadUser} */}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
/* Tooltip 基础样式 */
|
||||
.tooltip-trigger {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.tooltip-container {
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
z-index: 9999;
|
||||
width: max-content;
|
||||
max-width: 320px;
|
||||
transition: opacity 0.3s ease, visibility 0.3s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tooltip-visible {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.tooltip-content {
|
||||
position: relative;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.tooltip-arrow {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* Tooltip 主题 */
|
||||
.tooltip-dark .tooltip-content {
|
||||
background-color: #1e293b;
|
||||
color: #f1f5f9;
|
||||
}
|
||||
|
||||
.tooltip-dark .tooltip-arrow {
|
||||
border-color: #1e293b;
|
||||
}
|
||||
|
||||
.tooltip-light .tooltip-content {
|
||||
background-color: #ffffff;
|
||||
color: #334155;
|
||||
border: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.tooltip-light .tooltip-arrow {
|
||||
border-color: #ffffff;
|
||||
}
|
||||
|
||||
.tooltip-primary .tooltip-content {
|
||||
background-color: #2563eb;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.tooltip-primary .tooltip-arrow {
|
||||
border-color: #2563eb;
|
||||
}
|
||||
|
||||
.tooltip-success .tooltip-content {
|
||||
background-color: #22c55e;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.tooltip-success .tooltip-arrow {
|
||||
border-color: #22c55e;
|
||||
}
|
||||
|
||||
.tooltip-warning .tooltip-content {
|
||||
background-color: #f59e0b;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.tooltip-warning .tooltip-arrow {
|
||||
border-color: #f59e0b;
|
||||
}
|
||||
|
||||
.tooltip-error .tooltip-content {
|
||||
background-color: #ef4444;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.tooltip-error .tooltip-arrow {
|
||||
border-color: #ef4444;
|
||||
}
|
||||
|
||||
.tooltip-info .tooltip-content {
|
||||
background-color: #3b82f6;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.tooltip-info .tooltip-arrow {
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
|
||||
/* Tooltip 位置基本样式 */
|
||||
.tooltip-top .tooltip-arrow {
|
||||
border-width: 8px 8px 0 8px;
|
||||
border-color: inherit transparent transparent transparent;
|
||||
}
|
||||
|
||||
.tooltip-bottom .tooltip-arrow {
|
||||
border-width: 0 8px 8px 8px;
|
||||
border-color: transparent transparent inherit transparent;
|
||||
}
|
||||
|
||||
.tooltip-left .tooltip-arrow {
|
||||
border-width: 8px 0 8px 8px;
|
||||
border-color: transparent transparent transparent inherit;
|
||||
}
|
||||
|
||||
.tooltip-right .tooltip-arrow {
|
||||
border-width: 8px 8px 8px 0;
|
||||
border-color: transparent inherit transparent transparent;
|
||||
}
|
||||
|
||||
/* 富文本提示框样式 */
|
||||
.tooltip-rich .tooltip-content {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tooltip-header {
|
||||
padding: 0.5rem 1rem;
|
||||
font-weight: 600;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.tooltip-body {
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
.tooltip-footer {
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
font-size: 0.75rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* 深色主题特定样式调整 */
|
||||
.tooltip-dark .tooltip-header {
|
||||
border-bottom-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.tooltip-dark .tooltip-footer {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
border-top-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* 浅色主题特定样式调整 */
|
||||
.tooltip-light .tooltip-header {
|
||||
border-bottom-color: #e2e8f0;
|
||||
}
|
||||
|
||||
.tooltip-light .tooltip-footer {
|
||||
background-color: #f8fafc;
|
||||
border-top-color: #e2e8f0;
|
||||
}
|
||||
|
||||
/* 浅色主题下的箭头样式修正 */
|
||||
.tooltip-light.tooltip-top .tooltip-arrow {
|
||||
border-color: #ffffff transparent transparent transparent;
|
||||
}
|
||||
|
||||
.tooltip-light.tooltip-bottom .tooltip-arrow {
|
||||
border-color: transparent transparent #ffffff transparent;
|
||||
}
|
||||
|
||||
.tooltip-light.tooltip-left .tooltip-arrow {
|
||||
border-color: transparent transparent transparent #ffffff;
|
||||
}
|
||||
|
||||
.tooltip-light.tooltip-right .tooltip-arrow {
|
||||
border-color: transparent #ffffff transparent transparent;
|
||||
}
|
||||
|
||||
/* 表格样式 */
|
||||
.tooltip-content table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.tooltip-content table th,
|
||||
.tooltip-content table td {
|
||||
padding: 0.25rem 0.5rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.tooltip-content table th {
|
||||
font-weight: 600;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.tooltip-content table tr:nth-child(even) {
|
||||
background-color: rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
/* 深色主题下的表格样式 */
|
||||
.tooltip-dark .tooltip-content table th,
|
||||
.tooltip-dark .tooltip-content table td {
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.tooltip-dark .tooltip-content table th {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.tooltip-dark .tooltip-content table tr:nth-child(even) {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
/* 动画效果 */
|
||||
@keyframes tooltip-fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.tooltip-visible {
|
||||
animation: tooltip-fade-in 0.2s ease-out;
|
||||
}
|
||||
|
||||
/* 自定义偏移类 */
|
||||
.tooltip-custom-offset.tooltip-top {
|
||||
margin-top: 8px; /* 增加顶部边距 */
|
||||
pointer-events: auto; /* 允许鼠标事件 */
|
||||
}
|
||||
|
||||
/* 延长鼠标移入tooltip的路径 */
|
||||
.tooltip-custom-offset .tooltip-arrow {
|
||||
height: 12px; /* 增加箭头高度 */
|
||||
pointer-events: auto; /* 允许鼠标事件 */
|
||||
}
|
||||
+3
-2
@@ -26,6 +26,7 @@
|
||||
@import './components/file-tag.css';
|
||||
@import './components/message-modal.css';
|
||||
@import './components/toast.css';
|
||||
@import './components/TooltipStyles.css';
|
||||
|
||||
/* @import './components/modal.css'; */
|
||||
|
||||
@@ -132,7 +133,7 @@
|
||||
|
||||
/* === 侧边栏 === */
|
||||
.sidebar {
|
||||
@apply w-[280px] h-screen bg-white border-r border-gray-100 flex flex-col
|
||||
@apply w-[240px] h-screen bg-white border-r border-gray-100 flex flex-col
|
||||
transition-all duration-300 fixed left-0 top-0 z-[100] overflow-y-auto
|
||||
shadow-[0_0_15px_rgba(0,0,0,0.05)];
|
||||
}
|
||||
@@ -199,7 +200,7 @@
|
||||
|
||||
/* === 主内容区域 === */
|
||||
.main-content {
|
||||
@apply flex-1 ml-[280px] transition-all duration-300 min-h-screen flex flex-col;
|
||||
@apply flex-1 ml-[240px] transition-all duration-300 min-h-screen flex flex-col;
|
||||
}
|
||||
|
||||
.main-content.sidebar-collapsed {
|
||||
|
||||
@@ -225,7 +225,7 @@
|
||||
.review-point-item:hover {
|
||||
/* background-color: #f5f5f5; */
|
||||
/* box-shadow: 10px 10px 10px 3px rgba(250, 173, 20, 0.6); */
|
||||
transform: translateX(-2px);
|
||||
/* transform: translateX(-2px); */
|
||||
box-shadow: 1px 4px 10px rgba(0, 0, 0, 0.08);
|
||||
|
||||
}
|
||||
@@ -294,8 +294,10 @@
|
||||
}
|
||||
|
||||
.status-success {
|
||||
background-color: #f6ffed;
|
||||
/* background-color: #f6ffed; */
|
||||
background-color: #e7ffcd;
|
||||
color: #52c41a;
|
||||
/* color: #143503; */
|
||||
}
|
||||
|
||||
.status-error {
|
||||
@@ -304,7 +306,7 @@
|
||||
}
|
||||
|
||||
.status-warning {
|
||||
background-color: #fffbe6;
|
||||
background-color: #fff2aec7;
|
||||
color: #faad14;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user