diff --git a/app/api/evaluation_points/reviews.ts b/app/api/evaluation_points/reviews.ts index 32749a4..4c0ef4d 100644 --- a/app/api/evaluation_points/reviews.ts +++ b/app/api/evaluation_points/reviews.ts @@ -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": [ diff --git a/app/api/files/files-upload.ts b/app/api/files/files-upload.ts index bae14e0..c774227 100644 --- a/app/api/files/files-upload.ts +++ b/app/api/files/files-upload.ts @@ -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 响应中提取数据 diff --git a/app/components/layout/Sidebar.tsx b/app/components/layout/Sidebar.tsx index 68a0c8d..c52fe3a 100644 --- a/app/components/layout/Sidebar.tsx +++ b/app/components/layout/Sidebar.tsx @@ -20,13 +20,6 @@ export function Sidebar({ onToggle, collapsed }: SidebarProps) { const [expandedMenus, setExpandedMenus] = useState>({}); const menuItems: MenuItem[] = [ - { - id: 'contract-search', - title: '智能搜索', - path: '/contract-search', - hideBreadcrumb: true, - icon: 'ri-search-line' - }, { id: 'contract-template', title: '合同模板', diff --git a/app/components/reviews/ReviewPointsList.tsx b/app/components/reviews/ReviewPointsList.tsx index f8b684c..92b9d1e 100644 --- a/app/components/reviews/ReviewPointsList.tsx +++ b/app/components/reviews/ReviewPointsList.tsx @@ -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; }>; }; @@ -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( +
+ {tooltip.content} + {/* 添加小三角形指向提示框指向的元素 */} +
+
, + 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(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 ( +
+ + {hasHeader && ( + + + {tableData[0].map((cell, cellIndex) => ( + + ))} + + + )} + + {tableData.slice(1).map((row, rowIndex) => ( + + {row.map((cell, cellIndex) => ( + + ))} + + ))} + +
+ {cell || ' '} +
+ {cell || ' '} +
+
+ ); + } catch (error) { + console.error('表格渲染错误:', error); + return
表格渲染错误
; + } + }; + + // 检测内容是否像表格 + const isTableLike = content.includes('\t') && content.includes('\n'); + + return ( +
+ {showTooltip ? ( + +
+ {content} +
+
+ ) : ( +
+ {content} +
+ )} +
+ ); +}; + 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 ( - - 通过 - + + {title &&
{title}
} + + } + placement="top" + theme="light" + trigger="hover" + showArrow={true} + className="tooltip-custom-offset tooltip-top" + > + + 通过 + +
); } // 当result为false时,根据status决定显示警告还是错误 if (result === false) { - if (status === 'warning') { + if (status === 'warning' || status === 'info') { return ( - - 警告 - + + {title &&
{title}
} + + } + placement="top" + theme="light" + trigger="hover" + showArrow={true} + className="tooltip-custom-offset tooltip-top" + > + + 警告 + +
); } else if (status === 'error') { return ( - - 不通过 - + + {title &&
{title}
} + + } + placement="top" + theme="light" + trigger="hover" + showArrow={true} + className="tooltip-custom-offset tooltip-top" + > + + 不通过 + +
); } } - - // 兼容旧版逻辑,当没有result时,仍按status判断 - switch (status) { - case 'success': - return ( - - 通过 - - ); - case 'warning': - return ( - - 警告 - - ); - case 'error': - return ( - - 不通过 - - ); - case 'processing': - return ( - - 处理中 - - ); - default: - return ( - - 警告 - - ); - } }; /** @@ -482,7 +679,7 @@ export function ReviewPointsList({ const renderHumanReviewBadge = (reviewPoint: ReviewPoint) => { if (reviewPoint.postAction === 'manual') { return ( - + 需人工 ); @@ -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
@@ -636,7 +834,8 @@ export function ReviewPointsList({ /** * 渲染评查点一致性的规则的样式 - * @param singleReviewPoint 评查点 + * @param singleReviewPoint 一个评查点的一致性规则对象 + * @param reviewPoint 评查点 * @returns 评查点一致性的规则的样式 */ const renderConsistencyRule = (singleReviewPoint: Record,reviewPoint: ReviewPoint) => { @@ -907,7 +1106,7 @@ export function ReviewPointsList({ } } if (!hasPage) { - toastService.error('没有找到有效的页码'); + // toastService.error('没有找到有效的页码'); } }} onKeyDown={(e) => { @@ -964,12 +1163,16 @@ export function ReviewPointsList({ >
{/* {item.field}: */} - {item.data.value?.toString() || ''} + {/* {item.data.value?.toString() || ''} {!item.data.page && !item.data.value && ( )} - + */} + + {!item.data.page && !item.data.value && ( + + )}
))} @@ -981,32 +1184,36 @@ export function ReviewPointsList({ ) : ( )} - {/* 悬停提示框 - 横向布局 */} -
-
- {/*
规则检查结果
*/} -
- {chain.map((item, idx) => - idx >= 1 ? ( -
-
- {typeof item.compareMethod === 'object' - ? '' - : getCompareMethodText(item.compareMethod)} + {/* 使用鼠标事件处理悬停提示 */} +
{ + // 获取元素位置信息 + const rect = e.currentTarget.getBoundingClientRect(); + // 创建提示框内容 + const content = ( +
+ {chain.map((item, idx) => + idx >= 1 ? ( +
+
+ {typeof item.compareMethod === 'object' + ? '' + : `${getCompareMethodText(item.compareMethod)}:`} +
+
+ {res ? '通过' : '不通过'} +
-
- {res ? '通过' : '不通过'} -
-
- ) : null - )} -
-
-
-
+ ) : null + )} +
+ ); + // 显示提示框 + showTooltip(content, { top: rect.top + rect.height/2, left: rect.left }); + }} + onMouseLeave={hideTooltip} + />
@@ -1014,7 +1221,6 @@ export function ReviewPointsList({ } // 如果是标准的成对比较(2个元素) - return (
{chain[0].field} {!chain[0].data.page && !chain[0].data.value && ( - + )}
-
{chain[0].data.value?.toString() || ''}
+
@@ -1079,28 +1283,35 @@ export function ReviewPointsList({ ) : ( )} - {/* 悬停提示框 - 横向布局 */} -
-
-
-
-
- {typeof chain[1].compareMethod === 'object' - ? '' - : getCompareMethodText(chain[1].compareMethod)} + {/* 使用鼠标事件处理悬停提示 */} +
{ + // 获取元素位置信息 + const rect = e.currentTarget.getBoundingClientRect(); + // 创建提示框内容 + const content = ( +
+
+
+ {typeof chain[1].compareMethod === 'object' + ? '' + : `${getCompareMethodText(chain[1].compareMethod)}:`} +
+
+ {res ? '通过' : '不通过'} +
-
- {res ? '通过' : '不通过'} -
-
-
-
-
-
+
+ ); + // 显示提示框,稍微向下偏移,便于鼠标移动到tooltip上 + showTooltip(content, { + top: rect.top + rect.height/2, + left: rect.left + }); + }} + onMouseLeave={hideTooltip} + />
); @@ -1143,8 +1354,43 @@ export function ReviewPointsList({ const [, mainTypeValue] = mainTypeEntry; + /** + * 创建提示框内容 + * 这个函数返回一个React节点,用于在提示框中显示 + * 它将为每种规则类型(exists/format/logic/regex)创建一个带有状态标识的项目 + */ + const createTooltipContent = () => { + return ( +
+ {Object.entries(fieldValue?.type || {}).map(([typeKey, typeValue]) => ( +
+
{getRuleTypeText(typeKey)}:
+
+ {typeValue.res ? '通过' : '不通过'} +
+
+ ))} +
+ ); + }; + + /** + * 处理鼠标悬停事件 + * 当鼠标悬停在状态指示器上时,计算提示框应该显示的位置并显示提示框 + * @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 ( - - ))} -
- )} */} - {/* 主要值显示 */} {mainTypeValue.value && ( - + )} -
+ {/* 状态指示器 - 绑定鼠标事件用于显示/隐藏提示框 */} +
{overallResult ? ( ) : ( )} - - {/* 悬停提示框 - 横向布局 */} -
-
- {/*
规则检查结果
*/} -
- {Object.entries(fieldValue?.type || {}).map(([typeKey, typeValue]) => ( -
-
{getRuleTypeText(typeKey)}
-
- {typeValue.res ? '通过' : '不通过'} -
-
- ))} -
-
-
-
); @@ -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, reviewPoint: ReviewPoint) => { - // console.log('aiRule-------', aiRule); + // 从aiRule中提取配置信息 const config = aiRule.config as { model?: string; fields?: Record
- // ); - // 遍历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}内容详情`} > -
+
{/* 字段名称 */}
{key} {/* 没有抽取到目录内容,page和value都为空 */} {!value.page && !value.value && ( - + )} {/* 缺失显示 */} {!res && ( @@ -1336,19 +1534,7 @@ export function ReviewPointsList({ {/* 主要值显示 */} {res && ( - + )}
@@ -1357,38 +1543,45 @@ export function ReviewPointsList({ ) : ( )} - {/* 悬停提示框 - 横向布局 */} -
-
- {/*
规则检查结果
*/} -
-
-
大模型判断
-
- {res ? '通过' : '不通过'} + {/* 使用鼠标事件处理悬停提示 */} +
{ + // 获取元素位置信息 + const rect = e.currentTarget.getBoundingClientRect(); + // 创建提示框内容 + const content = ( +
+
+
大模型判断:
+
+ {res ? '通过' : '不通过'} +
-
-
-
-
+ ); + // 显示提示框,稍微向下偏移,便于鼠标移动到tooltip上 + showTooltip(content, { + top: rect.top + rect.height/2, + left: rect.left + }); + }} + onMouseLeave={hideTooltip} + />
); }); } - // 后处理message + // 渲染AI模型返回的评估消息 if (config.message) { // 检查message是否为对象,如果是则转换为字符串 const messageContent = typeof config.message === 'object' ? JSON.stringify(config.message) : String(config.message); + // 添加模型评估消息区域,使用蓝色背景突出显示 fieldElements.push(
@@ -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 = {}; - // 第一步:按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 && (

该评查点无法找到索引内容,无法自动定位到对应页面。

)} +
{/* {reviewPoint.suggestion && (
@@ -1682,7 +1894,7 @@ export function ReviewPointsList({ {/* 评查点内容显示区域 */} {reviewPoint.content && Object.entries(reviewPoint.content).length > 0 && ( -
+
{/* 修改评查结果的结构之后,显示新的结构 */} {renderContent(reviewPoint, mergedRules)}
@@ -1805,7 +2017,7 @@ export function ReviewPointsList({ {reviewPoint.content !== null && Object.keys(reviewPoint.content).length > 0 && ( <> {/* 内容显示区域 */} -
+
{/* 修改评查结果的结构之后,显示新的结构 */} {renderContent(reviewPoint, mergedRules)} @@ -1969,6 +2181,7 @@ export function ReviewPointsList({ // 组件主渲染函数 return (
+ {/* 面板头部 */}
@@ -1987,14 +2200,15 @@ export function ReviewPointsList({ filteredReviewPoints.map(reviewPoint => (
{ - // console.log('reviewPoint', reviewPoint); + console.log('reviewPoint', reviewPoint); handleReviewPointClick(reviewPoint.id); }} onKeyDown={(e) => { @@ -2006,12 +2220,12 @@ export function ReviewPointsList({ {/* 评查点标题和状态 */} {/* 评查点名称 pointName*/}
-
-
{reviewPoint.pointName}
-
+ {/*
*/} +
{reviewPoint.pointName}
+ {/*
{reviewPoint.title}
- {/* 评查点所属分组 */} - {/*
+ //评查点分组显示 +
- {renderStatusBadge(reviewPoint.status, reviewPoint.result)} +
+
*/} + {/*
*/} +
+ {renderStatusBadge(reviewPoint.status, reviewPoint.result,reviewPoint.title)} {renderHumanReviewBadge(reviewPoint)}
diff --git a/app/components/reviews/ReviewTabs.tsx b/app/components/reviews/ReviewTabs.tsx index 5147a0c..af7fa0b 100644 --- a/app/components/reviews/ReviewTabs.tsx +++ b/app/components/reviews/ReviewTabs.tsx @@ -105,7 +105,9 @@ export function ReviewTabs({ activeTab, onTabChange, children, fileInfo, onConfi > AI智能分析 */} - {fileInfo.type === '1' && ( + {/* {fileInfo.type === '1' && ( */} + {/* 隐藏结构比对 */} + {fileInfo.type === '999999' && ( + + + + + +
+
+ + {/* 不同位置 */} +
+

不同位置

+
+ + + + + + + + + + + + + + + +
+
+ + {/* 不同主题 */} +
+

不同主题

+
+ + + 深色 + + + + + + 浅色 + + + + + + 主题色 + + + + + + 成功 + + + + + + 警告 + + + + + + 错误 + + + + + + 信息 + + +
+
+ + {/* 富文本提示框 */} +
+

富文本提示框

+
+ +
+ CPU使用率: + 32% +
+
+ 内存使用率: + 76% +
+
+ 磁盘空间: + 245GB/500GB +
+
+ } + theme="light" + rich={true} + header="系统性能报告" + footer="更新时间: 2023-10-15 15:30:42" + showArrow={true} + > + + + + +
+ 本月销售额: + ¥258,432 +
+
+ 环比增长: + +15.8% +
+
+ 销售热点: + 电子产品 +
+
+ } + theme="dark" + rich={true} + header="销售数据分析" + footer="数据来源: 销售管理系统" + placement="bottom" + > + + +
+
+ + {/* 条件渲染示例 */} +
+

状态提示示例

+
+ + 95 + + + + 75 + + + + 45 + +
+
+ + {/* 自定义样式示例 */} +
+

自定义样式

+
+ + + 自定义宽度 + + + + + + 无箭头 + + + + + + 自定义类名 + + +
+
+
+
+ ); +} + +export default TooltipExample; \ No newline at end of file diff --git a/app/routes/reviews.tsx b/app/routes/reviews.tsx index 5c051ee..51f7fc2 100644 --- a/app/routes/reviews.tsx +++ b/app/routes/reviews.tsx @@ -548,7 +548,8 @@ export default function ReviewDetails() { )} {reviewData.fileInfo.uploadTime && (
- | 上传时间:{reviewData.fileInfo.uploadTime} | 上传用户:{reviewData.fileInfo.uploadUser} + | 上传时间:{reviewData.fileInfo.uploadTime} + {/* | 上传用户:{reviewData.fileInfo.uploadUser} */}
)}
diff --git a/app/styles/components/TooltipStyles.css b/app/styles/components/TooltipStyles.css new file mode 100644 index 0000000..7b41fda --- /dev/null +++ b/app/styles/components/TooltipStyles.css @@ -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; /* 允许鼠标事件 */ +} \ No newline at end of file diff --git a/app/styles/main.css b/app/styles/main.css index 1762cc3..d9838da 100644 --- a/app/styles/main.css +++ b/app/styles/main.css @@ -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 { diff --git a/app/styles/reviews.css b/app/styles/reviews.css index 136d5e0..477638c 100644 --- a/app/styles/reviews.css +++ b/app/styles/reviews.css @@ -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; } diff --git a/html/renderText(2).html b/html/renderText(2).html new file mode 100644 index 0000000..3612428 --- /dev/null +++ b/html/renderText(2).html @@ -0,0 +1,1693 @@ + + + + + + renderText 增强型文本渲染器 + + + + + + +
+
+ +
+
+ +

renderText 增强型文本渲染器

+
+
+ 文本处理 + 样式增强 + 条件渲染 + 漂浮提示 +
+

renderText 增强型文本渲染器提供了全方位的文本展示解决方案,支持多种文本转换、格式化、条件样式和增强型漂浮提示,满足各种复杂场景的展示需求。

+ +

API参数

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
参数名类型描述是否必填默认值
transformString文本转换类型:uppercase/lowercase/capitalize-
formatString特定格式处理:phone/idcard/bankcard/date/number-
prefixString文本前缀-
suffixString文本后缀-
nullValueString空值显示文本"-"
maxLengthNumber最大显示长度-
ellipsisBoolean是否显示省略号true
tooltipBoolean是否启用悬浮提示false
tooltipPropsObject悬浮提示配置-
+ tooltipProps.theme +
+
+
可选值: dark、light、primary、success、warning、error
+
+
+
+
String提示框主题"dark"
+ tooltipProps.placement +
+
+
可选值: top、bottom、left、right
+
+
+
+
String提示框位置"top"
tooltipProps.contentString提示内容-
tooltipProps.richBoolean是否使用富文本提示false
tooltipProps.headerString富文本提示标题-
tooltipProps.footerString富文本提示页脚-
highlightObject高亮配置-
conditionalArray条件样式规则-
copyableBoolean是否可复制false
linkBoolean/Object转为链接false
+ +

使用示例

+
+
+ 基本使用 - 文本转换 + +
+
+
{
+  "id": "title",
+  "label": "标题",
+  "dataIndex": "title",
+  "render": "renderText",
+  "renderProps": {
+    "transform": "capitalize",
+    "nullValue": "无标题"
+  }
+}
+
+
+ +
+
+ 数据脱敏 + +
+
+
{
+  "id": "phone",
+  "label": "手机号",
+  "dataIndex": "phone",
+  "render": "renderText",
+  "renderProps": {
+    "format": "phone",
+    "copyable": true
+  }
+}
+
+
+ +
+
+ 带前缀后缀 + +
+
+
{
+  "id": "price",
+  "label": "价格",
+  "dataIndex": "price",
+  "render": "renderText",
+  "renderProps": {
+    "prefix": "¥",
+    "suffix": "元",
+    "format": "number"
+  }
+}
+
+
+ +
+
+ 链接转换 + +
+
+
{
+  "id": "website",
+  "label": "网站",
+  "dataIndex": "website",
+  "render": "renderText",
+  "renderProps": {
+    "link": {
+      "target": "_blank",
+      "urlPrefix": "https://"
+    }
+  }
+}
+
+
+ +
+
+ 基础悬浮提示 + +
+
+
{
+  "id": "productDescription",
+  "label": "产品描述",
+  "dataIndex": "productDescription",
+  "render": "renderText",
+  "renderProps": {
+    "maxLength": 50,
+    "ellipsis": true,
+    "tooltip": true,
+    "tooltipProps": {
+      "placement": "top",
+      "theme": "dark",
+      "content": "data.productFullDescription"
+    }
+  }
+}
+
+
+ +
+
+ 增强型悬浮提示框 + +
+
+
{
+  "id": "stockInfo",
+  "label": "库存状态",
+  "dataIndex": "stockStatus",
+  "render": "renderText",
+  "renderProps": {
+    "tooltip": true,
+    "tooltipProps": {
+      "placement": "right",
+      "theme": "primary",
+      "rich": true,
+      "header": "库存详情",
+      "content": "data.stockDetail",
+      "footer": "更新时间: {{data.stockUpdateTime}}"
+    }
+  }
+}
+
+
+ +
+
+ 增强型提示框 - 浅色主题 + +
+
+
{
+  "id": "performance",
+  "label": "性能指标",
+  "dataIndex": "performance",
+  "render": "renderText",
+  "renderProps": {
+    "tooltip": true,
+    "tooltipProps": {
+      "placement": "top",
+      "theme": "light",
+      "rich": true,
+      "header": "系统性能报告",
+      "content": "data.performanceDetail",
+      "footer": "更新时间: {{data.updateTime}}"
+    },
+    "conditional": [
+      { "condition": "data.status === 'normal'", "style": { "color": "#3b82f6", "fontWeight": "medium" } }
+    ]
+  }
+}
+
+
+ +
+
+ 增强型提示框 - 深色主题 + +
+
+
{
+  "id": "salesTrend",
+  "label": "销售趋势",
+  "dataIndex": "salesTrend",
+  "render": "renderText",
+  "renderProps": {
+    "tooltip": true,
+    "tooltipProps": {
+      "placement": "bottom",
+      "theme": "dark",
+      "rich": true,
+      "header": "销售数据分析",
+      "content": "data.salesDetail",
+      "footer": "数据来源: 销售管理系统"
+    },
+    "conditional": [
+      { "condition": "data.growth > 10", "style": { "color": "#22c55e", "fontWeight": "medium" } }
+    ]
+  }
+}
+
+
+ +
+
+ 表格格式悬浮提示 + +
+
+
{
+  "id": "inventoryDetail",
+  "label": "库存详情",
+  "dataIndex": "inventoryStatus",
+  "render": "renderText",
+  "renderProps": {
+    "tooltip": true,
+    "tooltipProps": {
+      "placement": "top",
+      "theme": "light",
+      "rich": true,
+      "header": "卷烟库存明细表",
+      "content": "{{data.inventoryTableMarkdown}}",
+      "footer": "备注:卷烟真假待鉴定"
+    }
+  }
+}
+
+
+ +
+
+ 条件样式 + +
+
+
{
+  "id": "status",
+  "label": "订单状态",
+  "dataIndex": "status",
+  "render": "renderText",
+  "renderProps": {
+    "conditional": [
+      { "condition": "value === '待处理'", "style": { "color": "#f59e0b" } },
+      { "condition": "value === '处理中'", "style": { "color": "#3b82f6" } },
+      { "condition": "value === '已完成'", "style": { "color": "#22c55e", "fontWeight": "bold" } },
+      { "condition": "value === '已取消'", "style": { "color": "#ef4444" } }
+    ]
+  }
+}
+
+
+ +
+
+ 表格格式悬浮提示 - 深色主题 + +
+
+
{
+  "id": "salesStatistics",
+  "label": "销售统计",
+  "dataIndex": "salesTrend",
+  "render": "renderText",
+  "renderProps": {
+    "tooltip": true,
+    "tooltipProps": {
+      "placement": "top",
+      "theme": "dark",
+      "rich": true,
+      "header": "月度销售统计表",
+      "content": "{{data.salesTableMarkdown}}",
+      "footer": "数据更新时间: {{data.updateTime}}"
+    }
+  }
+}
+
+
+ +
+
+ Markdown表格变量示例 + +
+
+
// 数据源中的markdown表格变量示例
+data: {
+  inventoryTableMarkdown: `
+| 品种规格       | 单位 | 数量 | 品种规格       | 单位 | 数量 |
+|----------------|------|------|----------------|------|------|
+| 黄山(印象一品) | 条   | 75   | 以下空白       |      |      |
+| 白沙(硬)     | 条   | 174  |                |      |      |
+| 娇子(蓝时代) | 条   | 63   |                |      |      |
+| 黄山(新一品) | 条   | 220  |                |      |      |
+| 金圣(庐山)   | 条   | 40   |                |      |      |
+| 钻石(硬特醇) | 条   | 342  |                |      |      |
+| 共计:(品种)6个 |      |      | 总计:(数量)914条 |      |      |
+| 备注           |      |      | 卷烟真假待鉴定 |      |      |
+  `,
+  salesTableMarkdown: `
+| 品种规格       | 单位 | 数量 | 品种规格       | 单位 | 数量 |
+|----------------|------|------|----------------|------|------|
+| 黄山(印象一品) | 条   | 75   | 以下空白       |      |      |
+| 白沙(硬)     | 条   | 174  |                |      |      |
+| 娇子(蓝时代) | 条   | 63   |                |      |      |
+| 黄山(新一品) | 条   | 220  |                |      |      |
+| 金圣(庐山)   | 条   | 40   |                |      |      |
+| 钻石(硬特醇) | 条   | 342  |                |      |      |
+| 共计:(品种)6个 |      |      | 总计:(数量)914条 |      |      |
+| 备注           |      |      | 卷烟真假待鉴定 |      |      |
+  `,
+  updateTime: "2023-10-15 16:30:42"
+}
+
+
+ +
+
+ 实际项目中的Markdown渲染实现 + +
+
+
// 方案一:使用Markdown解析库 (如 marked.js)
+import { marked } from 'marked';
+
+function renderTooltipContent(markdownContent) {
+  // 解析markdown为HTML
+  const htmlContent = marked.parse(markdownContent);
+  
+  // 渲染到tooltip容器中
+  const tooltipBody = document.querySelector('.tooltip-body');
+  tooltipBody.innerHTML = htmlContent;
+  
+  // 添加通用样式类
+  const table = tooltipBody.querySelector('table');
+  if (table) {
+    table.classList.add('markdown-rendered-table');
+  }
+}
+
+// 方案二:使用React Markdown组件
+import ReactMarkdown from 'react-markdown';
+
+function TooltipContent({ markdownContent }) {
+  return (
+    <div className="tooltip-body">
+      <ReactMarkdown 
+        components={{
+          table: ({node, ...props}) => 
+            <table className="markdown-rendered-table" {...props} />
+        }}
+      >
+        {markdownContent}
+      </ReactMarkdown>
+    </div>
+  );
+}
+
+// 方案三:使用Vue Markdown组件
+<template>
+  <div class="tooltip-body">
+    <VueMarkdown 
+      :source="markdownContent" 
+      :table-class="'markdown-rendered-table'"
+    />
+  </div>
+</template>
+
+// 通用样式控制方案:通过CSS类和数据属性
+.markdown-rendered-table {
+  /* 基础表格样式 */
+}
+
+.markdown-rendered-table[data-theme="dark"] {
+  /* 深色主题样式 */
+}
+
+.markdown-rendered-table[data-highlight="true"] {
+  /* 高亮样式 */
+}
+
+// 使用示例
+const tooltipProps = {
+  placement: "top",
+  theme: "light", 
+  rich: true,
+  header: "数据详情",
+  content: "{{data.tableMarkdown}}", // 动态markdown内容
+  footer: "更新时间: {{data.updateTime}}"
+};
+
+
+ +

渲染效果

+
+
+
文本转换
+
原始数据:"hello world"
+
渲染结果:Hello World
+
+ +
+
电话脱敏
+
原始数据:"13812345678"
+
渲染结果:138****5678
+
+ +
+
带前缀后缀
+
原始数据:99.99
+
渲染结果:¥99.99元
+
+ +
+
链接转换
+
原始数据:"example.com"
+
渲染结果:example.com
+
+ +
+
基础悬浮提示
+
原始数据:"RapidCore是一个高效的低代码开发框架..."
+
渲染结果: + + RapidCore是一个高效的低代码开发框架... +
+
+
RapidCore是一个高效的低代码开发框架,可以帮助开发者快速构建企业级应用,提供了丰富的组件和灵活的配置选项,支持多种数据源和自定义扩展。
+
+
+
+
+
+
+ +
+
主题色提示框
+
原始数据:"库存紧张"
+
渲染结果: + + 库存紧张 +
+
+
当前库存仅剩5件,低于安全库存(10件),请及时补货
+
+
+
+
+
+
+ +
+
增强型提示框 - 浅色主题
+
原始数据:"性能分析"
+
渲染结果: + + 性能分析 +
+
+
系统性能报告
+
+
+ CPU使用率: + 32% +
+
+ 内存使用率: + 76% +
+
+ 磁盘空间: + 245GB/500GB +
+
+ +
+
+
+
+
+
+ +
+
增强型提示框 - 深色主题
+
原始数据:"销售趋势"
+
渲染结果: + + 销售趋势 +
+
+
销售数据分析
+
+
+ 本月销售额: + ¥258,432 +
+
+ 环比增长: + +15.8% +
+
+ 销售热点: + 电子产品 +
+
+ +
+
+
+
+
+
+ +
+
条件样式
+
+
原始值:"待处理"
+
渲染结果: + 待处理 +
+
+
+
原始值:"处理中"
+
渲染结果: + 处理中 +
+
+
+
原始值:"已完成"
+
渲染结果: + 已完成 +
+
+
+
原始值:"已取消"
+
渲染结果: + 已取消 +
+
+
+ +
+
带条件的悬浮提示
+
原始值:95
+
渲染结果: + + 95 +
+
+
优秀:得分在90分以上
+
+
+
+
+
+
+
原始值:75
+
渲染结果: + + 75 +
+
+
良好:得分在60-90分之间
+
+
+
+
+
+
+
+ + +
+
深色主题 (Dark)
+
配置:tooltipProps: { theme: "dark" }
+
渲染结果: + + 深色主题 +
+
+
这是深色主题提示框
+
+
+
+
+
+
+ + +
+
浅色主题 (Light)
+
配置:tooltipProps: { theme: "light" }
+
渲染结果: + + 浅色主题 +
+
+
这是浅色主题提示框
+
+
+
+
+
+
+ + +
+
主题色 (Primary)
+
配置:tooltipProps: { theme: "primary" }
+
渲染结果: + + 主题色 +
+
+
这是主题色提示框
+
+
+
+
+
+
+ + +
+
成功色 (Success)
+
配置:tooltipProps: { theme: "success" }
+
渲染结果: + + 成功色 +
+
+
这是成功色提示框
+
+
+
+
+
+
+ + +
+
警告色 (Warning)
+
配置:tooltipProps: { theme: "warning" }
+
渲染结果: + + 警告色 +
+
+
这是警告色提示框
+
+
+
+
+
+
+ + +
+
错误色 (Error)
+
配置:tooltipProps: { theme: "error" }
+
渲染结果: + + 错误色 +
+
+
这是错误色提示框
+
+
+
+
+
+
+ + +
+
信息色 (Info)
+
配置:tooltipProps: { theme: "info" }
+
渲染结果: + + 信息色 +
+
+
这是信息色提示框
+
+
+
+
+
+
+ + +
+
表格格式悬浮提示
+
配置:tooltipProps: { placement: "top", theme: "light", rich: true }
+
+
+ Markdown源码: +
+ | 品种规格 | 单位 | 数量 | 品种规格 | 单位 | 数量 |
+ |----------|------|------|----------|------|------|
+ | 黄山(印象一品) | 条 | 75 | 以下空白 | | |
+ | 白沙(硬) | 条 | 174 | | | |
+ | 娇子(蓝时代) | 条 | 63 | | | |
+ | 黄山(新一品) | 条 | 220 | | | |
+ | 金圣(庐山) | 条 | 40 | | | |
+ | 钻石(硬特醇) | 条 | 342 | | | |
+ | 共计:(品种)6个 | | | 总计:(数量)914条 | | |
+ | 备注 | | | 卷烟真假待鉴定 | | | +
+
+
渲染结果: + + 库存详情 +
+
+
卷烟库存明细表
+
+ +
+
+ +
+
+
+
+
+
+
+ + +
+
表格格式悬浮提示 - 深色主题
+
配置:tooltipProps: { placement: "top", theme: "dark", rich: true }
+
+
+ Markdown源码: +
+ | 品种规格 | 单位 | 数量 | 品种规格 | 单位 | 数量 |
+ |----------|------|------|----------|------|------|
+ | 黄山(印象一品) | 条 | 75 | 以下空白 | | |
+ | 白沙(硬) | 条 | 174 | | | |
+ | 娇子(蓝时代) | 条 | 63 | | | |
+ | 黄山(新一品) | 条 | 220 | | | |
+ | 金圣(庐山) | 条 | 40 | | | |
+ | 钻石(硬特醇) | 条 | 342 | | | |
+ | 共计:(品种)6个 | | | 总计:(数量)914条 | | |
+ | 备注 | | | 卷烟真假待鉴定 | | | +
+
+
渲染结果: + + 销售统计 +
+
+
月度销售统计表
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+ + + + \ No newline at end of file