@@ -1,5 +1,5 @@
import { type LoaderFunctionArgs , type MetaFunction } from "@remix-run/node" ;
import { useLoaderData , useSearchParams } from "@remix-run/react" ;
import { useLoaderData , useNavigate , useSearchParams } from "@remix-run/react" ;
import { Fragment , useEffect , useMemo , useRef , useState } from "react" ;
import axios from "axios" ;
@@ -15,6 +15,7 @@ import {
import { Button } from "~/components/ui/Button" ;
import { Card } from "~/components/ui/Card" ;
import { API_BASE_URL } from "~/config/api-config" ;
import { usePermission } from "~/hooks/usePermission" ;
import { parseRuleSummariesFromYaml , type RuleSummary } from "~/utils/rule-yaml-parser" ;
export function links() {
@@ -71,6 +72,32 @@ interface LoaderData {
frontendJWT? : string | null ;
}
interface RuleTemplatePayload {
groupId? : number ;
groupName? : string ;
parentGroupName? : string ;
documentTypeName? : string ;
entryModuleName? : string ;
ruleType? : string ;
ruleName? : string ;
yamlTemplate? : string ;
yamlText? : string ;
ossPreviewKey? : string ;
}
interface RuleDraftCreateResult {
packId? : number | null ;
groupId? : number | null ;
groupName? : string | null ;
ruleSetId? : number | null ;
ruleSetName? : string | null ;
ruleName? : string | null ;
ruleType? : string | null ;
versionId? : number | null ;
versionNo? : string | null ;
bindingId? : number | null ;
}
interface GroupFormState {
id? : number ;
mode : "create" | "edit" ;
@@ -119,22 +146,32 @@ async function fetchGroupTree(token?: string | null): Promise<RuleGroupNode[]> {
export async function loader ( { request } : LoaderFunctionArgs ) {
const { getUserSession } = await import ( "~/api/login/auth.server" ) ;
const { frontendJWT } = await getUserSession ( request ) ;
const { frontendJWT , userInfo } = await getUserSession ( request ) ;
const { requireRoutePermission } = await import ( "~/api/auth/check-route-permission.server" ) ;
const [ groups , docTypesRes , entryModulesRes , ruleSetsRes ] = await Promise . all ( [
fetchGroupTree ( frontendJWT ) ,
getDocumentTypes ( { page : 1 , pageSize : 500 } , frontendJWT ) ,
getEntryModules ( frontendJWT ) ,
getRuleSets ( frontendJWT ) ,
] ) ;
await requireRoutePermission ( "/rule-groups" , userInfo ? . role || "" , frontendJWT || undefined ) ;
return Response . json ( {
groups ,
docTypes : docTypesRes.data?.types || [ ] ,
entryModules : entryModulesRes.data || [ ] ,
ruleSets : ruleSetsRes.data || [ ] ,
frontendJWT ,
} satisfies LoaderData ) ;
try {
const [ groups , docTypesRes , entryModulesRes , ruleSetsRes ] = await Promise . all ( [
fetchGroupTree ( frontendJWT ) ,
getDocumentTypes ( { page : 1 , pageSize : 500 } , frontendJWT ) ,
getEntryModules ( frontendJWT ) ,
getRuleSets ( frontendJWT ) ,
] ) ;
return Response . json ( {
groups ,
docTypes : docTypesRes.data?.types || [ ] ,
entryModules : entryModulesRes.data || [ ] ,
ruleSets : ruleSetsRes.data || [ ] ,
frontendJWT ,
} satisfies LoaderData ) ;
} catch ( error ) {
if ( axios . isAxiosError ( error ) && error . response ? . status === 403 ) {
throw new Response ( "无权访问评查点分组页面" , { status : 403 } ) ;
}
throw error ;
}
}
function formatVersionLabel ( binding : BindingItem ) : string {
@@ -278,6 +315,75 @@ type RulePreviewState = {
rules : RuleSummary [ ] ;
} ;
type RuleDraftFormState = {
groupId : number ;
yamlText : string ;
changeNote : string ;
template : RuleTemplatePayload | null ;
loading : boolean ;
saving : boolean ;
error : string | null ;
success : RuleDraftCreateResult | null ;
} ;
function unwrapApiData < T > ( payload : any ) : T {
return ( payload ? . data ? ? payload ) as T ;
}
function normalizeRuleTemplatePayload ( payload : any ) : RuleTemplatePayload {
const item = unwrapApiData < any > ( payload ) || { } ;
const context = item . context || { } ;
return {
groupId : item.groupId ? ? item . group_id ? ? context . groupId ? ? context . group_id ,
groupName : item.groupName ? ? item . group_name ? ? context . groupName ? ? context . group_name ,
parentGroupName : item.parentGroupName ? ? item . root_group_name ? ? context . parentGroupName ? ? context . parent_group_name ,
documentTypeName : item.documentTypeName ? ? item . document_type_name ? ? context . documentTypeName ? ? context . document_type_name ,
entryModuleName : item.entryModuleName ? ? item . entry_module_name ? ? context . entryModuleName ? ? context . entry_module_name ,
ruleType : item.ruleType ? ? item . rule_type ,
ruleName : item.ruleName ? ? item . rule_name ,
yamlTemplate : item.yamlTemplate ? ? item . yaml_template ,
yamlText : item.yamlText ? ? item . yaml_text ,
ossPreviewKey : item.ossPreviewKey ? ? item . oss_preview_key ,
} ;
}
function normalizeRuleDraftCreateResult ( payload : any ) : RuleDraftCreateResult {
const item = unwrapApiData < any > ( payload ) || { } ;
const createdVersion = item . createdVersion ? ? item . created_version ? ? { } ;
const binding = item . binding ? ? { } ;
return {
packId : item.packId ? ? item . pack_id ? ? item . groupId ? ? item . group_id ,
groupId : item.groupId ? ? item . group_id ,
groupName : item.groupName ? ? item . group_name ,
ruleSetId : item.ruleSetId ? ? item . rule_set_id ? ? createdVersion . ruleSetId ? ? createdVersion . rule_set_id ,
ruleSetName : item.ruleSetName ? ? item . rule_set_name ? ? binding . rule_name ,
ruleName : item.ruleName ? ? item . rule_name ? ? binding . rule_name ,
ruleType : item.ruleType ? ? item . rule_type ? ? binding . rule_type ,
versionId : item.versionId ? ? item . version_id ? ? createdVersion . id ,
versionNo : item.versionNo ? ? item . version_no ? ? createdVersion . versionNo ? ? createdVersion . version_no ,
bindingId : item.bindingId ? ? item . binding_id ? ? binding . id ,
} ;
}
function findGroupNode ( groups : RuleGroupNode [ ] , groupId : number ) : RuleGroupNode | null {
for ( const root of groups ) {
if ( root . id === groupId ) return root ;
const child = ( root . children || [ ] ) . find ( ( item ) = > item . id === groupId ) ;
if ( child ) return child ;
}
return null ;
}
function buildRuleDraftJumpUrl ( group : RuleGroupNode | null , success : RuleDraftCreateResult | null , template : RuleTemplatePayload | null ) : string {
if ( success ? . packId ) {
return ` /rulesTest/detail?packId= ${ encodeURIComponent ( String ( success . packId ) ) } ` ;
}
const params = new URLSearchParams ( ) ;
const keyword = success ? . ruleType || template ? . ruleType || group ? . code || group ? . name || "" ;
if ( keyword ) params . set ( "keyword" , keyword ) ;
return ` /rulesTest/list ${ params . toString ( ) ? ` ? ${ params . toString ( ) } ` : "" } ` ;
}
function GroupModal ( {
visible ,
form ,
@@ -506,10 +612,125 @@ function BindingModal({
) ;
}
function RuleDraftModal ( {
visible ,
group ,
form ,
onClose ,
onChange ,
onSubmit ,
onOpenRules ,
} : {
visible : boolean ;
group : RuleGroupNode | null ;
form : RuleDraftFormState ;
onClose : ( ) = > void ;
onChange : ( patch : Partial < RuleDraftFormState > ) = > void ;
onSubmit : ( ) = > void ;
onOpenRules : ( ) = > void ;
} ) {
if ( ! visible || ! group ) return null ;
const template = form . template ;
return (
< div className = "rg-modal-backdrop" >
< div className = "rg-modal large" >
< div className = "rg-modal-header" >
< h3 > 从 二 级 分 组 新 建 规 则 集 / YAML < / h3 >
< button type = "button" className = "icon-button" onClick = { onClose } >
< i className = "ri-close-line" > < / i >
< / button >
< / div >
< div className = "rg-modal-body grid-form" >
< div className = "form-item form-item-span-2" >
< div className = "modal-tip" >
< strong > { getSubtypeGroupDisplayName ( group ) } < / strong >
< span >
所 属 一 级 : { template ? . parentGroupName || "-" } · 文 档 类 型 : { group . document_type_name || template ? . documentTypeName || "-" }
{ ` · 入口模块: ${ group . entry_module_name || template ? . entryModuleName || "-" } ` }
< / span >
< / div >
< / div >
< div className = "form-item form-item-span-2" >
< div className = "detail-alert info compact" >
< strong > 当 前 入 口 只 负 责 生 成 完 整 YAML 草 稿 < / strong >
< span > 模 板 来 自 当 前 二 级 分 组 , 保 存 后 会 创 建 规 则 集 版 本 , 并 尽 量 同 步 回 当 前 分 组 绑 定 。 < / span >
< / div >
< / div >
< div className = "form-item" >
< label > 规 则 类 型 < / label >
< input value = { template ? . ruleType || "" } readOnly placeholder = { form . loading ? "模板加载中..." : "等待后端返回" } / >
< / div >
< div className = "form-item" >
< label > 规 则 集 名 称 < / label >
< input value = { template ? . ruleName || "" } readOnly placeholder = { form . loading ? "模板加载中..." : "等待后端返回" } / >
< / div >
< div className = "form-item form-item-span-2" >
< label > OSS 预 览 路 径 < / label >
< input value = { template ? . ossPreviewKey || "" } readOnly placeholder = { form . loading ? "模板加载中..." : "等待后端返回" } / >
< p className = "field-tip" > 后 端 正 式 保 存 时 会 按 当 前 版 本 号 写 入 实 际 规 则 文 件 路 径 。 < / p >
< / div >
< div className = "form-item form-item-span-2" >
< label > 变 更 说 明 < / label >
< input
value = { form . changeNote }
onChange = { ( event ) = > onChange ( { changeNote : event.target.value , success : null } ) }
placeholder = "例如:初始化建设工程合同规则草稿"
/ >
< / div >
< div className = "form-item form-item-span-2" >
< label > 完 整 YAML < / label >
< textarea
rows = { 24 }
value = { form . yamlText }
onChange = { ( event ) = > onChange ( { yamlText : event.target.value , error : null , success : null } ) }
placeholder = { form . loading ? "模板加载中..." : "这里显示后端生成的完整 YAML 模板" }
/ >
< p className = "field-tip" > 这 里 直 接 提 交 完 整 YAML 文 本 , 不 再 由 前 端 重 组 规 则 结 构 。 < / p >
< / div >
{ form . error ? (
< div className = "form-item form-item-span-2" >
< div className = "detail-alert danger compact" >
< strong > 保 存 失 败 < / strong >
< span > { form . error } < / span >
< / div >
< / div >
) : null }
{ form . success ? (
< div className = "form-item form-item-span-2" >
< div className = "detail-alert success compact" >
< strong > 规 则 草 稿 已 保 存 < / strong >
< span >
{ form . success . ruleName || form . success . ruleSetName || template ? . ruleName || "规则集" } ·
{ ` 版本 ${ form . success . versionNo || form . success . versionId || "已创建" } ` }
{ form . success . bindingId ? ` · 已同步绑定 # ${ form . success . bindingId } ` : "· 绑定结果以后端返回为准" }
< / span >
< / div >
< / div >
) : null }
< / div >
< div className = "rg-modal-footer" >
< Button type = "default" onClick = { onClose } > 关 闭 < / Button >
{ form . success ? (
< Button type = "primary" onClick = { onOpenRules } > 继 续 进 入 规 则 页 < / Button >
) : (
< Button type = "primary" onClick = { onSubmit } disabled = { form . loading || form . saving } >
{ form . loading ? "模板加载中..." : form . saving ? "保存中..." : "保存规则草稿" }
< / Button >
) }
< / div >
< / div >
< / div >
) ;
}
export default function RuleGroupsIndex() {
const { groups , docTypes , entryModules , ruleSets , frontendJWT } = useLoaderData < LoaderData > ( ) ;
const navigate = useNavigate ( ) ;
const { hasAnyPermission } = usePermission ( ) ;
const [ searchParams , setSearchParams ] = useSearchParams ( ) ;
const topGroups = useMemo ( ( ) = > toTopGroups ( groups ) , [ groups ] ) ;
const [ groupTree , setGroupTree ] = useState < RuleGroupNode [ ] > ( groups ) ;
const topGroups = useMemo ( ( ) = > toTopGroups ( groupTree ) , [ groupTree ] ) ;
const nameValue = searchParams . get ( "name" ) || "" ;
const codeValue = searchParams . get ( "code" ) || "" ;
const statusValue = searchParams . get ( "status" ) || "" ;
@@ -521,6 +742,7 @@ export default function RuleGroupsIndex() {
const [ rulePreviewMap , setRulePreviewMap ] = useState < Record < number , RulePreviewState > > ( { } ) ;
const [ groupModalOpen , setGroupModalOpen ] = useState ( false ) ;
const [ bindingModalOpen , setBindingModalOpen ] = useState ( false ) ;
const [ draftModalOpen , setDraftModalOpen ] = useState ( false ) ;
const [ saving , setSaving ] = useState ( false ) ;
const [ groupForm , setGroupForm ] = useState < GroupFormState > ( {
mode : "create" ,
@@ -542,8 +764,22 @@ export default function RuleGroupsIndex() {
isActive : true ,
} ) ;
const [ activeBindingGroup , setActiveBindingGroup ] = useState < RuleGroupNode | null > ( null ) ;
const [ activeDraftGroup , setActiveDraftGroup ] = useState < RuleGroupNode | null > ( null ) ;
const [ draftForm , setDraftForm ] = useState < RuleDraftFormState > ( {
groupId : 0 ,
yamlText : "" ,
changeNote : "" ,
template : null ,
loading : false ,
saving : false ,
error : null ,
success : null ,
} ) ;
const mountedRef = useRef ( false ) ;
const childGroupStats = useMemo ( ( ) = > buildChildGroupStats ( topGroups ) , [ topGroups ] ) ;
const canManageGroups = hasAnyPermission ( [ "evaluation_group:create:write" , "evaluation_group:update:write" ] ) ;
const canManageBindings = hasAnyPermission ( [ "evaluation_group:update:write" ] ) ;
const canCreateRuleDraft = hasAnyPermission ( [ "evaluation_group:update:write" , "rules:create:write" ] ) ;
const filteredGroups = useMemo ( ( ) = > {
return topGroups
@@ -605,8 +841,12 @@ export default function RuleGroupsIndex() {
const resetFilters = ( ) = > setSearchParams ( new URLSearchParams ( ) ) ;
const reloadPage = ( ) = > {
if ( typeof window !== "undefined" ) window . location . reload ( ) ;
const reloadPage = async ( ) : Promise < RuleGroupNode [ ] > = > {
const nextGroups = await fetchGroupTree ( frontendJWT ) ;
setGroupTree ( nextGroups ) ;
setActiveBindingGroup ( ( current ) = > ( current ? findGroupNode ( nextGroups , current . id ) : null ) ) ;
setActiveDraftGroup ( ( current ) = > ( current ? findGroupNode ( nextGroups , current . id ) : null ) ) ;
return nextGroups ;
} ;
const handleApiError = ( error : any ) = > {
@@ -656,6 +896,39 @@ export default function RuleGroupsIndex() {
setBindingModalOpen ( true ) ;
} ;
const openCreateRuleDraft = async ( group : RuleGroupNode ) = > {
setActiveDraftGroup ( group ) ;
setDraftForm ( {
groupId : group.id ,
yamlText : "" ,
changeNote : "" ,
template : null ,
loading : true ,
saving : false ,
error : null ,
success : null ,
} ) ;
setDraftModalOpen ( true ) ;
try {
const response = await axios . get ( ` ${ API_BASE_URL } /api/v3/evaluation-point-groups/ ${ group . id } /rule-template ` , {
headers : authHeaders ( frontendJWT ) ,
} ) ;
const template = normalizeRuleTemplatePayload ( response ? . data ) ;
const yamlText = String ( template ? . yamlTemplate || template ? . yamlText || "" ) ;
setDraftForm ( ( prev ) = > ( {
. . . prev ,
loading : false ,
template ,
yamlText ,
changeNote : prev.changeNote || ` 初始化 ${ getSubtypeGroupDisplayName ( group ) } 规则草稿 ` ,
error : yamlText.trim ( ) ? null : "后端未返回可编辑的 YAML 模板" ,
} ) ) ;
} catch ( error : any ) {
const message = error ? . response ? . data ? . msg || error ? . response ? . data ? . detail || error ? . message || "模板加载失败" ;
setDraftForm ( ( prev ) = > ( { . . . prev , loading : false , error : String ( message ) } ) ) ;
}
} ;
const openEditBinding = ( group : RuleGroupNode , binding : BindingItem ) = > {
setActiveBindingGroup ( group ) ;
setBindingForm ( {
@@ -691,7 +964,8 @@ export default function RuleGroupsIndex() {
} else {
await axios . put ( ` ${ API_BASE_URL } /api/v3/evaluation-point-groups/ ${ groupForm . id } ` , payload , { headers : { . . . authHeaders ( frontendJWT ) } } ) ;
}
reloadPage ( ) ;
await reloadPage ( ) ;
setGroupModalOpen ( false ) ;
} catch ( error ) {
handleApiError ( error ) ;
} finally {
@@ -714,7 +988,8 @@ export default function RuleGroupsIndex() {
} else {
await axios . put ( ` ${ API_BASE_URL } /api/v3/evaluation-point-groups/bindings/ ${ bindingForm . bindingId } ` , payload , { headers : { . . . authHeaders ( frontendJWT ) } } ) ;
}
reloadPage ( ) ;
await reloadPage ( ) ;
setBindingModalOpen ( false ) ;
} catch ( error ) {
handleApiError ( error ) ;
} finally {
@@ -729,7 +1004,7 @@ export default function RuleGroupsIndex() {
const response = await axios . delete ( ` ${ API_BASE_URL } /api/v3/evaluation-point-groups/ ${ group . id } ` , { headers : authHeaders ( frontendJWT ) } ) ;
const payload = response ? . data ? . data ? ? response ? . data ;
if ( payload ? . success === false ) return window . alert ( payload ? . message || "删除失败" ) ;
reloadPage ( ) ;
await reloadPage ( ) ;
} catch ( error ) {
handleApiError ( error ) ;
}
@@ -739,12 +1014,45 @@ export default function RuleGroupsIndex() {
if ( ! window . confirm ( ` 确认解除规则集「 ${ binding . rule_name || binding . rule_type || binding . rule_set_id } 」吗? ` ) ) return ;
try {
await axios . delete ( ` ${ API_BASE_URL } /api/v3/evaluation-point-groups/bindings/ ${ binding . id } ` , { headers : authHeaders ( frontendJWT ) } ) ;
reloadPage ( ) ;
await reloadPage ( ) ;
} catch ( error ) {
handleApiError ( error ) ;
}
} ;
const submitRuleDraft = async ( ) = > {
if ( ! activeDraftGroup ) return ;
if ( ! draftForm . yamlText . trim ( ) ) {
setDraftForm ( ( prev ) = > ( { . . . prev , error : "请先确认 YAML 内容后再保存" } ) ) ;
return ;
}
setDraftForm ( ( prev ) = > ( { . . . prev , saving : true , error : null } ) ) ;
try {
const payload = {
yaml_text : draftForm.yamlText ,
change_note : draftForm.changeNote.trim ( ) || null ,
} ;
const response = await axios . post (
` ${ API_BASE_URL } /api/v3/evaluation-point-groups/ ${ activeDraftGroup . id } /rule-drafts ` ,
payload ,
{ headers : authHeaders ( frontendJWT ) } ,
) ;
const result = normalizeRuleDraftCreateResult ( response ? . data ) ;
const nextGroups = await reloadPage ( ) ;
const refreshedGroup = findGroupNode ( nextGroups , activeDraftGroup . id ) || activeDraftGroup ;
setActiveDraftGroup ( refreshedGroup ) ;
setDraftForm ( ( prev ) = > ( { . . . prev , saving : false , success : result } ) ) ;
} catch ( error : any ) {
const message = error ? . response ? . data ? . msg || error ? . response ? . data ? . detail || error ? . message || "规则草稿保存失败" ;
setDraftForm ( ( prev ) = > ( { . . . prev , saving : false , error : String ( message ) } ) ) ;
}
} ;
const openRuleDraftResult = ( ) = > {
const target = buildRuleDraftJumpUrl ( activeDraftGroup , draftForm . success , draftForm . template ) ;
navigate ( target ) ;
} ;
const toggleRoot = ( groupId : number ) = > {
setExpandedGroupIds ( ( prev ) = > {
const next = new Set ( prev ) ;
@@ -812,7 +1120,9 @@ export default function RuleGroupsIndex() {
< div className = "page-actions" >
< Button type = "default" icon = "ri-expand-up-down-line" onClick = { ( ) = > setExpandedGroupIds ( new Set ( filteredGroups . map ( ( item ) = > item . id ) ) ) } > 展 开 全 部 < / Button >
< Button type = "default" icon = "ri-collapse-diagonal-line" onClick = { ( ) = > { setExpandedGroupIds ( new Set ( ) ) ; setExpandedBindingIds ( new Set ( ) ) ; } } > 收 起 全 部 < / Button >
< Button type = "primary" icon = "ri-add-line" onClick = { openCreateRoot } > 新 增 一 级 分 组 < / Button >
{ canManageGroups ? (
< Button type = "primary" icon = "ri-add-line" onClick = { openCreateRoot } > 新 增 一 级 分 组 < / Button >
) : null }
< / div >
< / div >
@@ -959,9 +1269,15 @@ export default function RuleGroupsIndex() {
< td > { formatDateTime ( root . updated_at || root . created_at ) } < / td >
< td >
< div className = "action-links" >
< button type = "button" className = "action-link button-link" onClick = { ( ) = > openCreateChild ( root ) } > 新 增 二 级 分 组 < / button >
< button type = "button" className = "action-link button-link" onClick = { ( ) = > openEditGroup ( root ) } > 编 辑 < / button >
< button type = "button" className = "action-link danger button-link" onClick = { ( ) = > deleteGroup ( root ) } > 删 除 < / button >
{ canManageGroups ? (
< button type = "button" className = "action-link button-link" onClick = { ( ) = > openCreateChild ( root ) } > 新 增 二 级 分 组 < / button >
) : null }
{ canManageGroups ? (
< button type = "button" className = "action-link button-link" onClick = { ( ) = > openEditGroup ( root ) } > 编 辑 < / button >
) : null }
{ canManageGroups ? (
< button type = "button" className = "action-link danger button-link" onClick = { ( ) = > deleteGroup ( root ) } > 删 除 < / button >
) : null }
< / div >
< / td >
< / tr >
@@ -1026,9 +1342,18 @@ export default function RuleGroupsIndex() {
>
{ child . bindings . length ? ( child . bindings . every ( ( item ) = > expandedBindingIds . has ( item . id ) ) ? "收起规则集" : "查看规则集" ) : "暂无规则集" }
< / button >
< button type = "button" className = "action-link button-link" onClick = { ( ) = > openCreateBinding ( child ) } > 绑 定 规 则 集 < / button >
< button type = "button" className = "action-link button-link" onClick = { ( ) = > openEditGroup ( child ) } > 编 辑 < / button >
< button type = "button" className = "action-link danger button-link" onClick = { ( ) = > deleteGroup ( child ) } > 删 除 < / button >
{ canCreateRuleDraft ? (
< button type = "button" className = "action-link button-link" onClick = { ( ) = > openCreateRuleDraft ( child ) } > 新 建 规 则 集 / YAML < / button >
) : null }
{ canManageBindings ? (
< button type = "button" className = "action-link button-link" onClick = { ( ) = > openCreateBinding ( child ) } > 绑 定 规 则 集 < / button >
) : null }
{ canManageGroups ? (
< button type = "button" className = "action-link button-link" onClick = { ( ) = > openEditGroup ( child ) } > 编 辑 < / button >
) : null }
{ canManageGroups ? (
< button type = "button" className = "action-link danger button-link" onClick = { ( ) = > deleteGroup ( child ) } > 删 除 < / button >
) : null }
< / div >
< / td >
< / tr >
@@ -1071,8 +1396,12 @@ export default function RuleGroupsIndex() {
< button type = "button" className = "action-link button-link" onClick = { ( ) = > toggleBindingPreview ( binding ) } >
{ expandedBinding ? "收起规则明细" : "查看组内规则" }
< / button >
< button type = "button" className = "action-link button-link" onClick = { ( ) = > openEditBinding ( child , binding ) } > 编 辑 绑 定 < / button >
< button type = "button" className = "action-link danger button-link" onClick = { ( ) = > deleteBinding ( binding ) } > 解 除 绑 定 < / button >
{ canManageBindings ? (
< button type = "button" className = "action-link button-link" onClick = { ( ) = > openEditBinding ( child , binding ) } > 编 辑 绑 定 < / button >
) : null }
{ canManageBindings ? (
< button type = "button" className = "action-link danger button-link" onClick = { ( ) = > deleteBinding ( binding ) } > 解 除 绑 定 < / button >
) : null }
< / div >
< / td >
< / tr >
@@ -1157,6 +1486,15 @@ export default function RuleGroupsIndex() {
onSubmit = { submitBinding }
saving = { saving }
/ >
< RuleDraftModal
visible = { draftModalOpen }
group = { activeDraftGroup }
form = { draftForm }
onClose = { ( ) = > setDraftModalOpen ( false ) }
onChange = { ( patch ) = > setDraftForm ( ( prev ) = > ( { . . . prev , . . . patch } ) ) }
onSubmit = { submitRuleDraft }
onOpenRules = { openRuleDraftResult }
/ >
< / div >
) ;
}