feat: 1. 完善全局路由的访问权限的验证。 2. 完善接口返回的树形路由结构 3.优化评查点列表的查询,改用表连接的方式,废弃使用数据库的rpc函数,同时进行地区隔离和权限隔离。
4. 删除冗余的评查文件列表。 5.完善上传文档 页面初始化查询数据的时候 查询文件类型(改成动态指定) 6. 添加获取入口模块的查询接口。 7.完善服务端中判断token的有效性,失效则跳转到登录页。 8. 重构layout和sidebar的页面,改成由动态权限路由来渲染对应的菜单栏。 9.重构入口页面,通过动态查询根据不同地区的人返回不同的入口。
This commit is contained in:
@@ -96,9 +96,11 @@ export function Breadcrumb({ items = [], className = '' }: BreadcrumbProps) {
|
||||
{index === breadcrumbs.length - 1 ? (
|
||||
<span className="text-gray-900 font-medium">{item.title}</span>
|
||||
) : (
|
||||
<Link
|
||||
to={item.to || '#'}
|
||||
<Link
|
||||
to={item.to || '#'}
|
||||
className="hover:text-primary-600"
|
||||
prefetch="intent"
|
||||
preventScrollReset={false}
|
||||
>
|
||||
{item.title}
|
||||
</Link>
|
||||
|
||||
@@ -5,16 +5,6 @@ import { Breadcrumb } from './Breadcrumb';
|
||||
import { useMatches, useLocation } from '@remix-run/react';
|
||||
import type { UserRole } from '~/root';
|
||||
|
||||
// 定义应用模块类型
|
||||
type AppModule = 'contract' | 'record' | 'model' | '';
|
||||
|
||||
// 应用模块与reviewType的映射
|
||||
const REVIEW_TYPE_TO_APP: Record<string, AppModule> = {
|
||||
'contract': 'contract', // 合同管理
|
||||
'record': 'record', // 案卷智能评查
|
||||
'model': 'model' // 智慧法务大模型
|
||||
};
|
||||
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode;
|
||||
userRole?: UserRole;
|
||||
@@ -35,7 +25,6 @@ interface Match {
|
||||
|
||||
export function Layout({ children, userRole = 'developer' as UserRole, frontendJWT = '' }: LayoutProps) {
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||
const [selectedApp, setSelectedApp] = useState<AppModule>('');
|
||||
const [effectiveUserRole, setEffectiveUserRole] = useState<UserRole>(userRole);
|
||||
const [effectiveFrontendJWT, setEffectiveFrontendJWT] = useState<string>(frontendJWT);
|
||||
const matches = useMatches() as Match[];
|
||||
@@ -83,7 +72,7 @@ export function Layout({ children, userRole = 'developer' as UserRole, frontendJ
|
||||
}
|
||||
}, [userRole, frontendJWT]);
|
||||
|
||||
// 从sessionStorage中获取侧边栏状态和reviewType
|
||||
// 从localStorage中获取侧边栏状态
|
||||
useEffect(() => {
|
||||
// 检查是否为移动端
|
||||
const isMobile = window.innerWidth <= 768;
|
||||
@@ -97,35 +86,8 @@ export function Layout({ children, userRole = 'developer' as UserRole, frontendJ
|
||||
} else if (savedState) {
|
||||
setSidebarCollapsed(savedState === 'true');
|
||||
}
|
||||
|
||||
// 从sessionStorage获取reviewType并设置对应的应用模块
|
||||
if (typeof window !== 'undefined') {
|
||||
try {
|
||||
const reviewType = sessionStorage.getItem('reviewType');
|
||||
if (reviewType && REVIEW_TYPE_TO_APP[reviewType]) {
|
||||
setSelectedApp(REVIEW_TYPE_TO_APP[reviewType]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取reviewType失败:', error);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 路由变化时,检查并更新应用模块
|
||||
useEffect(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
try {
|
||||
const reviewType = sessionStorage.getItem('reviewType');
|
||||
// console.log('Layout 路由变化, reviewType:', reviewType, '路径:', location.pathname);
|
||||
if (reviewType && REVIEW_TYPE_TO_APP[reviewType]) {
|
||||
setSelectedApp(REVIEW_TYPE_TO_APP[reviewType]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('路由变化时获取reviewType失败:', error);
|
||||
}
|
||||
}
|
||||
}, [location.pathname]);
|
||||
|
||||
const toggleSidebar = () => {
|
||||
const newState = !sidebarCollapsed;
|
||||
setSidebarCollapsed(newState);
|
||||
@@ -150,7 +112,6 @@ export function Layout({ children, userRole = 'developer' as UserRole, frontendJ
|
||||
collapsed={sidebarCollapsed}
|
||||
onToggle={toggleSidebar}
|
||||
userRole={effectiveUserRole}
|
||||
selectedApp={selectedApp}
|
||||
frontendJWT={effectiveFrontendJWT}
|
||||
/>
|
||||
|
||||
|
||||
@@ -8,36 +8,15 @@ interface SidebarProps {
|
||||
collapsed: boolean;
|
||||
userRole: UserRole;
|
||||
frontendJWT?: string;
|
||||
selectedApp?: string; // 添加所选应用模块参数
|
||||
}
|
||||
|
||||
// 已移除 APP_MENU_MAP:路由的显示/隐藏由后端 is_hidden 字段控制
|
||||
// 只保留特殊规则:
|
||||
// - /chat-with-llm 只在 model 模块中显示
|
||||
// - /contract-template 只在 contract 模块中显示
|
||||
|
||||
// 应用模块名称映射
|
||||
const APP_NAME_MAP: Record<string, string> = {
|
||||
'contract': '合同管理',
|
||||
'record': '案卷智能评查',
|
||||
'model': '智慧法务大模型'
|
||||
};
|
||||
|
||||
// 应用模块图标映射
|
||||
const APP_ICON_MAP: Record<string, string> = {
|
||||
'contract': '/images/icon_hetong.png',
|
||||
'record': '/images/icon_anjuan.png',
|
||||
'model': '/images/icon_assistant.png'
|
||||
};
|
||||
|
||||
export function Sidebar({ onToggle, collapsed, userRole, frontendJWT = '', selectedApp = '' }: SidebarProps) {
|
||||
export function Sidebar({ onToggle, collapsed, userRole, frontendJWT = '' }: SidebarProps) {
|
||||
const location = useLocation();
|
||||
const [expandedMenus, setExpandedMenus] = useState<Record<string, boolean>>({});
|
||||
const [currentApp, setCurrentApp] = useState<string>(''); // 初始设置为空字符串而不是selectedApp
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true); // 添加加载状态
|
||||
const [menuItems, setMenuItems] = useState<MenuItem[]>([]); // 动态菜单项
|
||||
const [isLoadingRoutes, setIsLoadingRoutes] = useState<boolean>(true); // 路由加载状态
|
||||
const [isMobile, setIsMobile] = useState<boolean>(false); // 移动端检测
|
||||
const [selectedModuleName, setSelectedModuleName] = useState<string>(''); // 当前选中的模块名称
|
||||
const navigate = useNavigate();
|
||||
|
||||
// 移动端检测
|
||||
@@ -57,6 +36,8 @@ export function Sidebar({ onToggle, collapsed, userRole, frontendJWT = '', selec
|
||||
|
||||
// 获取用户路由权限
|
||||
useEffect(() => {
|
||||
console.log('🔍 [Sidebar] useEffect 触发,开始获取路由权限');
|
||||
|
||||
const fetchUserRoutes = async () => {
|
||||
setIsLoadingRoutes(true);
|
||||
try {
|
||||
@@ -69,19 +50,19 @@ export function Sidebar({ onToggle, collapsed, userRole, frontendJWT = '', selec
|
||||
}
|
||||
|
||||
if (!jwt) {
|
||||
console.error('❌ [Sidebar] JWT token 未找到');
|
||||
console.error('❌ [Sidebar] JWT token 未找到,props.frontendJWT:', frontendJWT, 'localStorage.access_token:', localStorage.getItem('access_token'));
|
||||
setMenuItems([]);
|
||||
setIsLoadingRoutes(false);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('🔍 [Sidebar] 当前用户角色:', userRole);
|
||||
const roleKey = mapUserRoleToRoleKey(userRole);
|
||||
const result = await getUserRoutesByRole(roleKey, jwt);
|
||||
// console.log('🔍 [Sidebar] 当前用户角色:', userRole, 'JWT前20字符:', jwt.substring(0, 20));
|
||||
// console.log('🔍 [Sidebar] 映射后的角色key:', roleKey);
|
||||
const result = await getUserRoutesByRole(userRole, jwt);
|
||||
|
||||
if (result.success && result.data) {
|
||||
setMenuItems(result.data);
|
||||
console.log('✅ [Sidebar] 用户路由权限加载成功:', result.data);
|
||||
// console.log('✅ [Sidebar] 用户路由权限加载成功:', result.data);
|
||||
} else {
|
||||
console.error('❌ [Sidebar] 获取用户路由权限失败:', result.error);
|
||||
|
||||
@@ -108,93 +89,20 @@ export function Sidebar({ onToggle, collapsed, userRole, frontendJWT = '', selec
|
||||
fetchUserRoutes();
|
||||
}, [userRole, frontendJWT, navigate]);
|
||||
|
||||
// 组件挂载后从 sessionStorage 读取初始 reviewType
|
||||
// 从 sessionStorage 读取当前选中的模块名称
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
setIsLoading(true); // 设置加载状态
|
||||
|
||||
try {
|
||||
const reviewType = sessionStorage.getItem('reviewType');
|
||||
// console.log('初始 reviewType:', reviewType);
|
||||
if (reviewType && mounted) {
|
||||
setCurrentApp(reviewType);
|
||||
} else if (selectedApp && mounted) {
|
||||
// 如果没有reviewType,但有selectedApp,使用selectedApp
|
||||
setCurrentApp(selectedApp);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('读取 reviewType 失败:', error);
|
||||
} finally {
|
||||
// 使用setTimeout确保状态在DOM更新后再变化,避免闪烁
|
||||
setTimeout(() => {
|
||||
if (mounted) {
|
||||
setIsLoading(false); // 数据加载完成
|
||||
if (typeof window !== 'undefined') {
|
||||
try {
|
||||
const moduleName = sessionStorage.getItem('selectedModuleName');
|
||||
if (moduleName) {
|
||||
setSelectedModuleName(moduleName);
|
||||
console.log('📌 [Sidebar] 当前选中模块:', moduleName);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
return () => {
|
||||
mounted = false;
|
||||
};
|
||||
}, [selectedApp]);
|
||||
|
||||
// 从 sessionStorage 获取 reviewType 并设置当前应用模块
|
||||
useEffect(() => {
|
||||
// 监听 sessionStorage 变化(主要用于多标签页情况)
|
||||
const handleStorageChange = (e: StorageEvent) => {
|
||||
if (e.key === 'reviewType' && e.newValue) {
|
||||
setCurrentApp(e.newValue);
|
||||
}
|
||||
};
|
||||
|
||||
// 添加事件监听器
|
||||
window.addEventListener('storage', handleStorageChange);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('storage', handleStorageChange);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 监听路由变化,重新检查 reviewType
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
|
||||
try {
|
||||
const reviewType = sessionStorage.getItem('reviewType');
|
||||
// 只有当reviewType变化时才设置加载状态和更新currentApp
|
||||
if (reviewType && reviewType !== currentApp && mounted) {
|
||||
setIsLoading(true); // 路由变化时设置加载状态
|
||||
setCurrentApp(reviewType);
|
||||
// 使用setTimeout确保状态在DOM更新后再变化,避免闪烁
|
||||
setTimeout(() => {
|
||||
if (mounted) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('路由变化时读取 reviewType 失败:', error);
|
||||
if (mounted) {
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
console.error('❌ [Sidebar] 读取 selectedModuleName 失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
mounted = false;
|
||||
};
|
||||
}, [location.pathname, currentApp]);
|
||||
|
||||
// 监听 selectedApp 属性变化
|
||||
useEffect(() => {
|
||||
if (selectedApp && selectedApp !== currentApp) {
|
||||
setIsLoading(true); // 设置加载状态
|
||||
setCurrentApp(selectedApp);
|
||||
// 使用setTimeout确保状态在DOM更新后再变化,避免闪烁
|
||||
setTimeout(() => {
|
||||
setIsLoading(false); // 数据加载完成
|
||||
}, 0);
|
||||
}
|
||||
}, [selectedApp, currentApp]);
|
||||
}, [location.pathname]); // 路由变化时重新读取
|
||||
|
||||
// 初始化展开状态,默认全部展开
|
||||
useEffect(() => {
|
||||
@@ -238,73 +146,72 @@ export function Sidebar({ onToggle, collapsed, userRole, frontendJWT = '', selec
|
||||
// console.log('子菜单点击:', child.title, '路径:', child.path);
|
||||
};
|
||||
|
||||
// 检查是否通过51707端口访问(省局)
|
||||
const isPort51707 = typeof window !== 'undefined' && window.location.port === '51707';
|
||||
const isPort51707 = typeof window !== 'undefined' && window.location.port === '51707'
|
||||
|
||||
// 根据当前应用模式过滤菜单项
|
||||
const filteredMenuItems = menuItems
|
||||
.filter(item => {
|
||||
// 如果是51707端口,只显示交叉评查相关菜单
|
||||
if (isPort51707) {
|
||||
// 如果当前应用是智慧法务大模型,只显示AI对话菜单
|
||||
if (currentApp === 'model') {
|
||||
return item.path && item.path.startsWith('/chat-with-llm');
|
||||
} else {
|
||||
return item.path && item.path.startsWith('/cross-checking');
|
||||
}
|
||||
// 处理菜单项:清理子菜单结构
|
||||
const processedMenuItems: MenuItem[] = menuItems.filter(item =>{
|
||||
// 如果是省局访问
|
||||
if(isPort51707){
|
||||
if (selectedModuleName === '智慧法务大模型'){
|
||||
return item.path && item.path.startsWith('/chat-with-llm')
|
||||
}
|
||||
return item.path && item.path.startsWith('/cross-checking')
|
||||
}
|
||||
|
||||
// 特殊规则1:/chat-with-llm 只在 model 模块中显示
|
||||
// 🔑 如果选择了"智慧法务大模型",只显示 /chat-with-llm 相关菜单
|
||||
if (selectedModuleName === '智慧法务大模型') {
|
||||
return item.path === '/chat-with-llm' || item.path?.startsWith('/chat-with-llm/');
|
||||
}
|
||||
|
||||
// 🔑 如果选择了包含"合同"的模块
|
||||
if (selectedModuleName.includes('合同')) {
|
||||
// 排除智慧法务大模型专属菜单
|
||||
if (item.path === '/chat-with-llm' || item.path?.startsWith('/chat-with-llm/')) {
|
||||
return currentApp === 'model';
|
||||
return false;
|
||||
}
|
||||
|
||||
// 特殊规则2:/contract-template 只在 contract 模块中显示
|
||||
if (item.path === '/contract-template' || item.path?.startsWith('/contract-template/')) {
|
||||
return currentApp === 'contract';
|
||||
}
|
||||
|
||||
// 其他路由:后端已通过 is_hidden 控制显示/隐藏,这里全部保留
|
||||
// 保留其他所有菜单(包括 /contract-template)
|
||||
return true;
|
||||
})
|
||||
.map(item => {
|
||||
// 处理子菜单:过滤隐藏的子菜单
|
||||
if (item.children && item.children.length > 0) {
|
||||
// 过滤掉 hideBreadcrumb=true 的子菜单(这些通常是隐藏菜单)
|
||||
const visibleChildren = item.children.filter(child => !child.hideBreadcrumb);
|
||||
}
|
||||
|
||||
// 如果过滤后没有可见的子菜单,返回不带子菜单的父级(变成可点击的单级菜单)
|
||||
if (visibleChildren.length === 0) {
|
||||
const { children, ...itemWithoutChildren } = item;
|
||||
return itemWithoutChildren;
|
||||
}
|
||||
// 🔑 其他模块:排除特殊菜单
|
||||
// 排除智慧法务大模型专属菜单
|
||||
if (item.path === '/chat-with-llm' || item.path?.startsWith('/chat-with-llm/')) {
|
||||
return false;
|
||||
}
|
||||
// 排除合同专属菜单
|
||||
if (item.path === '/contract-template' || item.path?.startsWith('/contract-template/')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果还有可见的子菜单,返回带过滤后子菜单的项
|
||||
return { ...item, children: visibleChildren };
|
||||
}
|
||||
// 保留其他菜单
|
||||
return true;
|
||||
|
||||
// 处理空 children 数组或 undefined 的情况
|
||||
if (item.children !== undefined) {
|
||||
// 如果 children 存在但为空数组,移除它(让父级变成可点击的单级菜单)
|
||||
}).map((item): MenuItem => {
|
||||
// 处理子菜单:过滤隐藏的子菜单
|
||||
if (item.children && item.children.length > 0) {
|
||||
// 过滤掉 hideBreadcrumb=true 的子菜单(这些通常是隐藏菜单)
|
||||
const visibleChildren = item.children.filter(child => !child.hideBreadcrumb);
|
||||
|
||||
// 如果过滤后没有可见的子菜单,返回不带子菜单的父级(变成可点击的单级菜单)
|
||||
if (visibleChildren.length === 0) {
|
||||
const { children, ...itemWithoutChildren } = item;
|
||||
return itemWithoutChildren;
|
||||
return itemWithoutChildren as MenuItem;
|
||||
}
|
||||
|
||||
// 没有子菜单的项直接返回
|
||||
return item;
|
||||
});
|
||||
// 如果还有可见的子菜单,返回带过滤后子菜单的项
|
||||
return { ...item, children: visibleChildren };
|
||||
}
|
||||
|
||||
// filteredMenuItems = filteredMenuItems.map(item => {
|
||||
// if(item.children && item.children.length > 0){
|
||||
// const children = item.children.filter(child => {
|
||||
// const isUploadByPath = child.path === '/files/upload' || child.path?.startsWith('/files/upload')
|
||||
// const isUploadByTitle = child.title === '文件上传'
|
||||
// return !(isUploadByPath || isUploadByTitle)
|
||||
// })
|
||||
// return { ...item, children}
|
||||
// }
|
||||
// return item
|
||||
// })
|
||||
// 处理空 children 数组或 undefined 的情况
|
||||
if (item.children !== undefined) {
|
||||
// 如果 children 存在但为空数组,移除它(让父级变成可点击的单级菜单)
|
||||
const { children, ...itemWithoutChildren } = item;
|
||||
return itemWithoutChildren as MenuItem;
|
||||
}
|
||||
|
||||
// 没有子菜单的项直接返回
|
||||
return item;
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -353,27 +260,8 @@ export function Sidebar({ onToggle, collapsed, userRole, frontendJWT = '', selec
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{!collapsed && (
|
||||
<div className="px-4 py-3 border-b border-gray-100">
|
||||
<div className="flex items-center text-green-700">
|
||||
{isLoading ? (
|
||||
// 加载中状态,只显示加载图标,保留布局
|
||||
<div className="flex items-center">
|
||||
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-green-700 mr-2"></div>
|
||||
<span className="font-medium text-gray-500">加载中...</span>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<img src={APP_ICON_MAP[currentApp] || ''} alt={APP_NAME_MAP[currentApp] || ''} className="w-6 h-6 mr-2" />
|
||||
<span className="font-medium">{APP_NAME_MAP[currentApp] || ''}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="py-4 px-[10px] flex-1 overflow-y-auto sidebar-scroll-area">
|
||||
{isLoading || isLoadingRoutes ? (
|
||||
{isLoadingRoutes ? (
|
||||
// 加载中状态显示,保留菜单布局结构
|
||||
<div className="py-2">
|
||||
{Array(5).fill(0).map((_, index) => (
|
||||
@@ -387,7 +275,7 @@ export function Sidebar({ onToggle, collapsed, userRole, frontendJWT = '', selec
|
||||
</div>
|
||||
) : (
|
||||
// 数据加载完成后显示菜单
|
||||
filteredMenuItems.map((item) => (
|
||||
processedMenuItems.map((item) => (
|
||||
<div key={item.id} className={`${collapsed ? 'px-0' : ''}`}>
|
||||
{!item.children ? (
|
||||
<Link
|
||||
|
||||
@@ -24,11 +24,11 @@ export function BasicInfo({ onChange, initialData, evaluationPointGroups = [], r
|
||||
// 找到当前评查点类型对应的code
|
||||
const getCheckpointTypeCode = () => {
|
||||
if (!formData.evaluation_point_groups_pid) return "";
|
||||
|
||||
|
||||
const typeGroup = evaluationPointGroups.find(
|
||||
group => group.id === formData.evaluation_point_groups_pid && group.pid === 0
|
||||
group => group.id === formData.evaluation_point_groups_pid && (!group.pid || group.pid === 0) // 🆕 NULL或0都表示顶级分组
|
||||
);
|
||||
|
||||
|
||||
return typeGroup?.code || "";
|
||||
};
|
||||
|
||||
@@ -45,7 +45,7 @@ export function BasicInfo({ onChange, initialData, evaluationPointGroups = [], r
|
||||
group.is_enabled
|
||||
);
|
||||
|
||||
// 获取评查点类型选项(pid=0的数据)
|
||||
// 🆕 获取评查点类型选项(pid为NULL或0的数据)
|
||||
const getCheckpointTypeOptions = () => {
|
||||
if (!evaluationPointGroups || evaluationPointGroups.length === 0) {
|
||||
return (
|
||||
@@ -54,8 +54,8 @@ export function BasicInfo({ onChange, initialData, evaluationPointGroups = [], r
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const typeGroups = evaluationPointGroups.filter(group => group.pid === 0 && group.is_enabled);
|
||||
|
||||
const typeGroups = evaluationPointGroups.filter(group => (!group.pid || group.pid === 0) && group.is_enabled);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -113,8 +113,8 @@ export function BasicInfo({ onChange, initialData, evaluationPointGroups = [], r
|
||||
case 'checkpoint-type':
|
||||
// 处理评查点类型选择
|
||||
if (value) {
|
||||
// 找到选中的类型组
|
||||
const selectedType = evaluationPointGroups.find(group => group.code === value && group.pid === 0);
|
||||
// 🆕 找到选中的类型组(pid为NULL或0表示顶级分组)
|
||||
const selectedType = evaluationPointGroups.find(group => group.code === value && (!group.pid || group.pid === 0));
|
||||
if (selectedType) {
|
||||
newData.evaluation_point_groups_pid = selectedType.id;
|
||||
}
|
||||
|
||||
@@ -1029,51 +1029,55 @@ export function ExtractionSettings({
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<label
|
||||
className="form-label mb-1"
|
||||
htmlFor="regex-template-container"
|
||||
>
|
||||
常用正则模板
|
||||
</label>
|
||||
<div
|
||||
className="flex flex-wrap gap-1 mt-1"
|
||||
id="regex-template-container"
|
||||
>
|
||||
{[
|
||||
{
|
||||
label: "日期格式:yyyy-mm-dd",
|
||||
regex:
|
||||
"\\d{4}[-/年](0?[1-9]|1[0-2])[-/月](0?[1-9]|[12][0-9]|3[01])[日]?",
|
||||
},
|
||||
{ label: "合同编号格式", regex: "[A-Z]{2,5}-\\d{4,10}" },
|
||||
{
|
||||
label: "金额格式",
|
||||
regex:
|
||||
"(人民币|RMB)?\\s?(\\d{1,3}(,\\d{3})*(\\.\\d{2})?)\\s?[万元]?",
|
||||
},
|
||||
{
|
||||
label: "座机号码格式",
|
||||
regex: "\\d{3}-\\d{8}|\\d{4}-\\d{7,8}",
|
||||
},
|
||||
{ label: "手机号码格式", regex: "1[3-9]\\d{9}" },
|
||||
].map(({ label, regex }) => (
|
||||
<div
|
||||
key={label}
|
||||
className="chip cursor-pointer regex-template"
|
||||
onClick={() => applyRegexTemplate(regex)}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ")
|
||||
applyRegexTemplate(regex);
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* 🔑 只有在添加字段后或本来就有字段时才显示常用正则模板 */}
|
||||
{regexFields.length > 0 && (
|
||||
<div className="mt-2">
|
||||
<label
|
||||
className="form-label mb-1"
|
||||
htmlFor="regex-template-container"
|
||||
>
|
||||
常用正则模板
|
||||
</label>
|
||||
<div
|
||||
className="flex flex-wrap gap-1 mt-1"
|
||||
id="regex-template-container"
|
||||
>
|
||||
{[
|
||||
{
|
||||
label: "日期格式:yyyy-mm-dd",
|
||||
regex:
|
||||
"\\d{4}[-/年](0?[1-9]|1[0-2])[-/月](0?[1-9]|[12][0-9]|3[01])[日]?",
|
||||
},
|
||||
{ label: "合同编号格式", regex: "[A-Z]{2,5}-\\d{4,10}" },
|
||||
{
|
||||
label: "金额格式",
|
||||
regex:
|
||||
"(人民币|RMB)?\\s?(\\d{1,3}(,\\d{3})*(\\.\\d{2})?)\\s?[万元]?",
|
||||
},
|
||||
{
|
||||
label: "座机号码格式",
|
||||
regex: "\\d{3}-\\d{8}|\\d{4}-\\d{7,8}",
|
||||
},
|
||||
{ label: "手机号码格式", regex: "1[3-9]\\d{9}" },
|
||||
].map(({ label, regex }) => (
|
||||
<div
|
||||
key={label}
|
||||
className="chip cursor-pointer regex-template"
|
||||
onClick={() => applyRegexTemplate(regex)}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ")
|
||||
applyRegexTemplate(regex);
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user