// VL_VERSION:4.2.13
// @meta component summary:"项目权限（产品主流程）：按 caller 范围列项目→新建/编辑项目档案→导入资源树→按角色配置 allow/deny 与 DataScope→保存草稿→发布策略" keywords:(["项目权限","RBAC","项目档案","资源树导入","发布"])
<Section-ProjectPermissionPage "root"> containerType:col min-height:"0"

# Frontend Public Props
$adminActions([STRING]) = []
$isSuperAdmin(BOOL) = false

# Frontend Public Events

# Frontend Public Methods
METHOD ReloadProjects();RETURN {success:BOOL}
-<ServiceDomain-PermissionCenterService "pcService">.GetManageableProjects("", $env) -> _r
-IF !_r.success
--$projects = []
--$listMessage = _r.message
--RETURN {success:false}
-$projects = _r.projects
-$listMessage = (_r.projects.length == 0 ? "当前账号暂无可管理的项目。可新建项目档案，或联系超级管理员授权。" : "")
-RETURN {success:true}

METHOD ReloadScopeNodes();RETURN {success:BOOL}
-<ServiceDomain-PermissionCenterService "pcService">.ListScopeNodes() -> _r
-_act([{}]) = []
-FOR (_i(INT) = 0; _i < _r.nodes.length; _i++)
--IF _r.nodes[_i].status == "active"
---_act.push(_r.nodes[_i])
-$scopeNodes = _act
-RETURN {success:true}

METHOD RebuildRows();RETURN {success:BOOL}
-_rows([{}]) = []
-FOR (_i(INT) = 0; _i < $nodes.length; _i++)
--_n({}) = $nodes[_i]
--_eff(STRING) = "-"
--_scope(STRING) = ""
--FOR (_j(INT) = 0; _j < $drafts.length; _j++)
---IF $drafts[_j].roleCode == $selectedRole && $drafts[_j].resourceCode == _n.resourceCode
----_eff = $drafts[_j].effect
----_scope = $drafts[_j].dataScopeCode
--_isSel(BOOL) = false
--IF _n.resourceType == "DataScope"
---FOR (_k(INT) = 0; _k < $drafts.length; _k++)
----IF $drafts[_k].roleCode == $selectedRole && $drafts[_k].resourceCode == _n.parentResourceCode && $drafts[_k].dataScopeCode == _n.resourceCode
-----_isSel = true
--_rows.push({resourceCode:_n.resourceCode,parentResourceCode:_n.parentResourceCode,resourceType:_n.resourceType,displayName:_n.displayName,effect:_eff,dataScopeCode:_scope,isSelectedScope:_isSel})
-$rows = _rows
-RETURN {success:true}

METHOD ReloadDrafts();RETURN {success:BOOL}
-<ServiceDomain-PermissionCenterService "pcService">.ListPermissionDrafts($selectedProjectId, $env, "") -> _r
-$drafts = _r.drafts
-RebuildRows() -> _r2
-RETURN {success:true}

METHOD ReloadTree();RETURN {success:BOOL}
-<ServiceDomain-PermissionCenterService "pcService">.GetPermissionTree($selectedProjectId, $env) -> _pt
-IF !_pt.success
--$treeMissing = true
--$nodes = []
--$treeVersion = ""
--RETURN {success:false}
-$treeMissing = false
-$nodes = _pt.nodes
-$treeVersion = _pt.treeVersion
-RETURN {success:true}

METHOD OpenProject(projectId(STRING),projectName(STRING));RETURN {success:BOOL}
-$selectedProjectId = projectId
-$selectedProjectName = projectName
-$selectedRole = ""
-$validationErrors = []
-$publishedText = ""
-$message = ""
-$errorMessage = ""
-$importJson = ""
-$importMessage = ""
-<ServiceDomain-PermissionCenterService "pcService">.GetRoleCatalog("", $env) -> _rc
-$roles = _rc.roles
-ReloadTree() -> _t
-ReloadDrafts() -> _rd
-RETURN {success:true}

# Frontend Global Vars
$env(STRING) = "preview"
$projects([{}]) = []
$listMessage(STRING) = ""
$scopeNodes([{}]) = []
$selectedProjectId(STRING) = ""
$selectedProjectName(STRING) = ""
$roles([{}]) = []
$nodes([{}]) = []
$treeMissing(BOOL) = false
$drafts([{}]) = []
$rows([{}]) = []
$validationErrors([{}]) = []
$selectedRole(STRING) = ""
$treeVersion(STRING) = ""
$publishedText(STRING) = ""
$message(STRING) = ""
$errorMessage(STRING) = ""
$showCreate(BOOL) = false
$newProjectId(STRING) = ""
$newProjectName(STRING) = ""
$newScopeNodeCode(STRING) = ""
$createMessage(STRING) = ""
$showImport(BOOL) = false
$importJson(STRING) = ""
$importMessage(STRING) = ""

# Frontend Tree
<ServiceDomain-PermissionCenterService "pcService">
-<Service-GetManageableProjects> params:(currentAdminUserId(STRING),env(STRING)) returns:(success(BOOL),message(STRING),projects([{}]))
-<Service-ListScopeNodes> params:() returns:(success(BOOL),nodes([{}]),message(STRING))
-<Service-CreateProjectBinding> params:(projectId(STRING),env(STRING),projectName(STRING),scopeNodeCode(STRING)) returns:(success(BOOL),message(STRING))
-<Service-RegisterPermissionTreeSnapshotLocal> params:(projectId(STRING),env(STRING),projectName(STRING),treeVersion(STRING),treeHash(STRING),nodes([{}]),aiResourceNotes([{}])) returns:(success(BOOL),message(STRING),projectId(STRING),env(STRING),treeVersion(STRING),acceptedNodeCount(INT),activeSnapshotId(STRING),warnings([{}]))
-<Service-GetRoleCatalog> params:(userSpaceCode(STRING),env(STRING)) returns:(success(BOOL),message(STRING),roles([{}]))
-<Service-GetPermissionTree> params:(projectId(STRING),env(STRING)) returns:(success(BOOL),message(STRING),treeVersion(STRING),nodes([{}]),aiResourceNotes([{}]))
-<Service-ListPermissionDrafts> params:(projectId(STRING),env(STRING),userSpaceCode(STRING)) returns:(success(BOOL),message(STRING),drafts([{}]))
-<Service-SavePermissionDraft> params:(projectId(STRING),env(STRING),userSpaceCode(STRING),roleCode(STRING),resourceCode(STRING),effect(STRING),dataScopeCode(STRING)) returns:(success(BOOL),message(STRING),draftId(STRING),updatedAt(STRING))
-<Service-PublishPolicy> params:(projectId(STRING),env(STRING),userSpaceCode(STRING),draftId(STRING)) returns:(success(BOOL),policyVersion(STRING),publishedAt(STRING),publishedBy(STRING),validationErrors([{}]))
-<Service-GetPublishedPolicyLocal> params:(projectId(STRING),env(STRING),userSpaceCode(STRING)) returns:(success(BOOL),message(STRING),policyVersion(STRING),rolePermissions([{}]),dataScopeSelections([{}]),resourceNodes([{}]),resourceTreeVersion(STRING),roleCatalogVersion(STRING))
<Col-Root> width:"100%" flex:"1" min-height:"0" gap:"16px" padding:"24px" style:"subtle|passive"
-<If-List> conditions:($selectedProjectId == "")
--<Row-ListHead> width:"100%" gap:"12px" align-items:"center"
---<Col-ListTitle> flex:"1" gap:"2px"
----<Text-Title> value:"项目权限" style:"caption" size:"lg"
----<Text-Hint> value:"仅显示当前管理员可管理的项目；选择项目后配置角色权限并发布策略。" style:"muted" size:"sm"
---<Button-New> value:"+ 新建项目档案" style:"primary|filled|default|md|actionable" min-height:"40px" min-width:"140px" show:($isSuperAdmin == true || $adminActions.includes("editPolicy"))
--<If-Create> conditions:($showCreate == true)
---<Col-CardCreate> width:"100%" gap:"12px" padding:"16px" style:"neutral|soft|passive" radius:"md"
----<Text-CTitle> value:"新建 / 编辑项目档案（projectId 已存在则更新并重挂范围）" style:"caption" size:"md"
----<Row-CForm> width:"100%" gap:"12px" align-items:"center"
-----<Input-NewPid> value:$newProjectId placeholder:"projectId（业务项目标识）" min-height:"40px" min-width:"220px" style:"neutral|outlined|default|md"
-----<Input-NewPName> value:$newProjectName placeholder:"项目显示名" min-height:"40px" min-width:"220px" style:"neutral|outlined|default|md"
----<Col-CScope> width:"100%" gap:"6px"
-----<Text-CScopeTitle> value:("所属管理范围节点（必选）：" + ($newScopeNodeCode != "" ? $newScopeNodeCode : "未选择")) style:"muted" size:"sm"
-----<Row-CScopeList> width:"100%" gap:"8px" align-items:"center" wrap:"wrap"
------<For-ScopePick> sourceArray:$scopeNodes loopVar:[_sn,_snIdx]
-------<Button-PickScopeNode> value:(_sn.name + " (" + _sn.scopeNodeCode + ")") style:"neutral|outlined|default|sm|selectable" min-height:"34px"
----<Row-CActions> width:"100%" gap:"12px" align-items:"center"
-----<Button-CreateSubmit> value:"保存项目档案" style:"primary|filled|default|md|actionable" min-height:"40px" min-width:"130px"
-----<Button-CreateCancel> value:"取消" style:"neutral|outlined|default|md|actionable" min-height:"40px" min-width:"80px"
----<If-CMsg> conditions:($createMessage != "")
-----<Text-CMsg> value:$createMessage style:"hint" size:"sm"
--<If-NoProj> conditions:($projects.length == 0 && $listMessage != "")
---<Col-Empty> width:"100%" align-items:"center" justify-content:"center" padding:"48px" gap:"6px" style:"subtle|passive" radius:"md"
----<Text-EmptyT> value:"暂无可管理的项目" style:"caption" size:"md"
----<Text-EmptyD> value:$listMessage style:"muted" size:"sm"
--<If-HasProj> conditions:($projects.length > 0)
---<Col-Table> width:"100%" border:"1px solid #ebedf1" radius:"md" overflow:"hidden" sk.shadow:"0 1px 2px rgba(17,24,39,0.04)"
----<Row-THead> width:"100%" align-items:"center" padding:"10px 16px" gap:"12px" sk.bg:"#f7f8fa" sk.borderBottom:"1px solid #eef0f3"
-----<Text-ThName> value:"项目" flex:"3" style:"hint" size:"xs"
-----<Text-ThScope> value:"管理范围" flex:"1" style:"hint" size:"xs"
-----<Text-ThTree> value:"资源树" min-width:"88px" style:"hint" size:"xs"
-----<Text-ThStatus> value:"策略状态" min-width:"150px" style:"hint" size:"xs"
-----<Text-ThOps> value:"操作" min-width:"84px" style:"hint" size:"xs"
----<For-Projects> sourceArray:$projects loopVar:[_p,_pIdx]
-----<Row-PItem> width:"100%" gap:"12px" align-items:"center" padding:"10px 16px" min-height:"48px" sk.borderBottom:"1px solid #f1f2f4"
------<Col-PNameCell> flex:"3" gap:"1px" min-width:"0"
-------<Text-PName> value:(_p.projectName != "" ? _p.projectName : "(未命名)") style:"body" size:"sm"
-------<Text-PId> value:("标识 " + _p.projectId) style:"hint" size:"xs"
------<Text-PScope> value:(_p.scopeNodeCode != "" ? _p.scopeNodeCode : "未挂载") flex:"1" style:"muted" size:"sm"
------<Text-PTree> value:(_p.latestTreeVersion != "" ? _p.latestTreeVersion : "—") min-width:"88px" style:"muted" size:"sm"
------<Row-PStatus> min-width:"150px" gap:"8px" align-items:"center"
-------<Row-PStatusTag> sk.bg:(_p.policyStatus == "published" ? "#e6f4ea" : (_p.policyStatus == "draft" ? "#fdf0e3" : "#eef0f3")) padding:"2px 8px" radius:"sm"
--------<Text-PStatusTagT> value:(_p.policyStatus == "published" ? "已发布" : (_p.policyStatus == "draft" ? "草稿" : "未发布")) style:"body" size:"xs" sk.fg:(_p.policyStatus == "published" ? "#1a7f37" : (_p.policyStatus == "draft" ? "#b25e09" : "#5b6472"))
-------<Text-PVer> value:(_p.latestPolicyVersion != "" ? _p.latestPolicyVersion : "") style:"hint" size:"xs"
------<Button-Config> value:"配置" style:"primary|outlined|default|sm|actionable" min-width:"84px"
-<If-Config> conditions:($selectedProjectId != "")
--<Row-CfgHead> width:"100%" gap:"12px" align-items:"center"
---<Button-Back> value:"← 返回" style:"neutral|outlined|default|sm|actionable" min-height:"36px" min-width:"72px"
---<Col-CfgTitleWrap> flex:"1" gap:"2px" min-width:"0"
----<Text-CfgTitle> value:($selectedProjectName != "" ? $selectedProjectName : "(未命名项目)") style:"caption" size:"md"
----<Text-CfgSub> value:("项目标识 " + $selectedProjectId + ($treeVersion != "" ? ("　·　资源树版本 " + $treeVersion) : "　·　尚无资源树")) style:"muted" size:"xs"
---<Button-ToggleImport> value:($showImport == true ? "收起导入" : "导入资源树") style:"neutral|outlined|default|sm|actionable" min-height:"36px" min-width:"110px" show:($isSuperAdmin == true || $adminActions.includes("editPolicy"))
--<If-TreeMissing> conditions:($treeMissing == true)
---<Row-TreeMissing> width:"100%" padding:"10px 12px" align-items:"center" style:"warning|soft|passive" radius:"sm"
----<Text-TreeMissing> value:"该项目还没有资源树，请先导入业务项目导出的资源树 JSON 后再配置权限。" style:"body" size:"sm"
--<If-Import> conditions:($showImport == true || $treeMissing == true)
---<Col-CardImport> width:"100%" gap:"8px" padding:"12px" style:"neutral|soft|passive" radius:"md"
----<Text-ImpTitle> value:"导入项目权限资源树" style:"caption" size:"sm"
----<Text-ImpDesc> value:"粘贴业务项目导出的资源树 JSON（需含 treeVersion 与 nodes）。导入只更新资源树快照，不会自动发布策略。" style:"muted" size:"xs"
----<Textarea-ImportJson> value:$importJson placeholder:"粘贴资源树 JSON" width:"100%" min-height:"140px" style:"neutral|outlined|default|md"
----<Row-ImpActions> width:"100%" gap:"12px" align-items:"center"
-----<Button-ImportSubmit> value:"导入" style:"primary|filled|default|md|actionable" min-height:"40px" min-width:"100px" show:($isSuperAdmin == true || $adminActions.includes("editPolicy"))
----<If-ImpMsg> conditions:($importMessage != "")
-----<Text-ImpMsg> value:$importMessage style:"hint" size:"sm"
--<Row-RoleToolbar> width:"100%" gap:"6px" align-items:"center" wrap:"wrap"
---<Text-RolesTitle> value:"角色" style:"hint" size:"xs" min-width:"32px"
---<Row-RoleSeg> align-items:"center" gap:"2px" padding:"3px" sk.radius:"9999px" sk.bg:"#f4f6f9"
----<For-Roles> sourceArray:$roles loopVar:[_r,_rIdx]
-----<Button-RolePick> value:(_r.name + (_r.status != "active" ? "（停用）" : "")) style:"neutral|ghost|default|sm|selectable" min-height:"30px" sk.radius:"9999px"
------<StateStyle-RoleSel> conditions:(_r.roleCode == $selectedRole) style:"primary|filled|default|sm|selectable"
--<Col-TreePanel> width:"100%" flex:"1" min-height:"0" border:"1px solid #ebedf1" radius:"md" overflow:"auto" sk.shadow:"0 1px 2px rgba(17,24,39,0.04)"
---<Row-TreeHead> width:"100%" align-items:"center" padding:"8px 12px" gap:"12px" sk.bg:"#f7f8fa" sk.borderBottom:"1px solid #eef0f3"
----<Text-ThType> value:"类型" min-width:"84px" style:"hint" size:"xs"
----<Text-ThRes> value:"资源" flex:"1" style:"hint" size:"xs"
----<Text-ThEff> value:"当前权限" min-width:"96px" style:"hint" size:"xs"
----<Text-ThAct> value:"操作" min-width:"172px" style:"hint" size:"xs"
---<If-NoRole> conditions:($selectedRole == "")
----<Col-NoRole> width:"100%" align-items:"center" justify-content:"center" padding:"40px" gap:"4px"
-----<Text-NoRoleT> value:"请先选择角色" style:"caption" size:"sm"
-----<Text-NoRoleD> value:"在上方选择一个角色后，即可配置它对各资源的允许 / 禁止与数据范围。" style:"muted" size:"sm"
---<If-HasRole> conditions:($selectedRole != "")
----<Col-TreeScroll> width:"100%" flex:"1" min-height:"0" overflow:"auto"
-----<For-Rows> sourceArray:$rows loopVar:[_row,_rowIdx]
------<Row-NodeRow> width:"100%" gap:"12px" align-items:"center" padding:"8px 12px" min-height:"46px" sk.borderBottom:"1px solid #f1f2f4"
-------<Row-TypeCell> min-width:"84px" align-items:"center"
--------<Row-TypeTag> sk.bg:(_row.resourceType == "DataScope" ? "#e8eeff" : "#eef0f3") padding:"2px 8px" radius:"sm"
---------<Text-RType> value:(_row.resourceType == "DataScope" ? "数据范围" : _row.resourceType) style:"body" size:"xs" sk.fg:(_row.resourceType == "DataScope" ? "#2549d6" : "#5b6472")
-------<Col-NameCell> flex:"1" gap:"1px" min-width:"0" padding:(_row.resourceType == "DataScope" ? "0 0 0 20px" : "0")
--------<Text-RName> value:(_row.displayName != "" ? _row.displayName : _row.resourceCode) style:"body" size:"sm"
--------<Text-RCode> value:_row.resourceCode style:"hint" size:"xs"
-------<If-NotScope> conditions:(_row.resourceType != "DataScope")
--------<Row-EffCell> min-width:"96px" align-items:"center"
---------<Row-EffTag> sk.bg:(_row.effect == "allow" ? "#e6f4ea" : (_row.effect == "deny" ? "#fdecec" : "#eef0f3")) padding:"2px 8px" radius:"sm"
----------<Text-EffTagT> value:(_row.effect == "allow" ? "允许" : (_row.effect == "deny" ? "禁止" : "未设置")) style:"body" size:"xs" sk.fg:(_row.effect == "allow" ? "#1a7f37" : (_row.effect == "deny" ? "#c1392d" : "#5b6472"))
--------<Row-ActCell> min-width:"172px" gap:"8px" align-items:"center"
---------<Button-Allow> value:"允许" style:"success|outlined|default|sm|actionable" min-width:"56px" show:($isSuperAdmin == true || $adminActions.includes("editPolicy"))
----------<StateStyle-AllowOn> conditions:(_row.effect == "allow") style:"success|filled|default|sm|actionable"
---------<Button-Deny> value:"禁止" style:"danger|outlined|default|sm|actionable" min-width:"56px" show:($isSuperAdmin == true || $adminActions.includes("editPolicy"))
----------<StateStyle-DenyOn> conditions:(_row.effect == "deny") style:"danger|filled|default|sm|actionable"
-------<If-IsScope> conditions:(_row.resourceType == "DataScope")
--------<Row-ScopeEffCell> min-width:"96px" align-items:"center"
---------<Row-ScopeTag> sk.bg:(_row.isSelectedScope == true ? "#e8eeff" : "#eef0f3") padding:"2px 8px" radius:"sm"
----------<Text-ScopeTagT> value:(_row.isSelectedScope == true ? "已选用" : "未选用") style:"body" size:"xs" sk.fg:(_row.isSelectedScope == true ? "#2549d6" : "#5b6472")
--------<Row-ScopeActCell> min-width:"172px" align-items:"center"
---------<Button-PickScope> value:"选用为数据范围" style:"primary|outlined|default|sm|actionable" min-width:"140px" show:($isSuperAdmin == true || $adminActions.includes("editPolicy"))
--<Row-PubBar> width:"100%" gap:"12px" align-items:"center"
---<Button-Publish> value:"发布策略" style:"primary|filled|default|md|actionable" min-height:"40px" min-width:"110px" show:($isSuperAdmin == true || $adminActions.includes("publishPolicy"))
---<Button-ViewPub> value:"查看已发布" style:"neutral|outlined|default|md|actionable" min-height:"40px" min-width:"120px"
---<Col-PubSpacer> flex:"1"
---<If-Pub> conditions:($publishedText != "")
----<Text-Pub> value:$publishedText style:"muted" size:"sm"
--<If-VErr> conditions:($validationErrors.length > 0)
---<Col-VErrList> width:"100%" gap:"4px" padding:"10px 12px" style:"danger|soft|passive" radius:"sm"
----<Text-VErrTitle> value:"发布校验未通过：" style:"body" size:"sm"
----<For-VErr> sourceArray:$validationErrors loopVar:[_e,_eIdx]
-----<Text-VErrItem> value:(_e.message + "（" + _e.roleCode + " / " + _e.resourceCode + (_e.dataScopeCode != "" ? (" / " + _e.dataScopeCode) : "") + "）") style:"hint" size:"sm"
-<If-Msg> conditions:($message != "")
--<Row-MsgBar> width:"100%" padding:"8px 12px" align-items:"center" style:"success|soft|passive" radius:"sm"
---<Text-Msg> value:$message style:"body" size:"sm"
-<If-Err> conditions:($errorMessage != "")
--<Row-ErrBar> width:"100%" padding:"8px 12px" align-items:"center" style:"danger|soft|passive" radius:"sm"
---<Text-Err> value:$errorMessage style:"body" size:"sm"

# Frontend Event Handlers
<Section-ProjectPermissionPage "root">.@init()
-ReloadProjects() -> _r
-ReloadScopeNodes() -> _r2

<Button-New>.@click()
-$showCreate = !$showCreate
-$createMessage = ""

<Input-NewPid>.@change(value)
-$newProjectId = value

<Input-NewPName>.@change(value)
-$newProjectName = value

<Button-PickScopeNode>.@click()
-$newScopeNodeCode = _sn.scopeNodeCode

<Button-CreateSubmit>.@click()
-$createMessage = ""
-IF $newProjectId == ""
--$createMessage = "请填写 projectId"
--RETURN
-IF $newScopeNodeCode == ""
--$createMessage = "请选择所属管理范围节点"
--RETURN
-<ServiceDomain-PermissionCenterService "pcService">.CreateProjectBinding($newProjectId, $env, $newProjectName, $newScopeNodeCode) -> _r
-IF !_r.success
--$createMessage = "保存失败:" + _r.message
--RETURN
-$createMessage = ""
-$showCreate = false
-$newProjectId = ""
-$newProjectName = ""
-$newScopeNodeCode = ""
-ReloadProjects() -> _r2

<Button-CreateCancel>.@click()
-$showCreate = false
-$createMessage = ""

<Button-Config>.@click()
-OpenProject(("" + _p.projectId), ("" + _p.projectName)) -> _r

<Button-Back>.@click()
-$selectedProjectId = ""
-$selectedProjectName = ""
-$message = ""
-$errorMessage = ""
-$showImport = false
-ReloadProjects() -> _r

<Button-ToggleImport>.@click()
-$showImport = !$showImport
-$importMessage = ""

<Textarea-ImportJson>.@change(value)
-$importJson = value

<Button-ImportSubmit>.@click()
-$importMessage = ""
-_raw(STRING) = $importJson.trim()
-IF _raw == ""
--$importMessage = "请先粘贴资源树 JSON"
--RETURN
-// 前置强校验：拦截空串/截断粘贴/贴错内容等常见错误（VL 无 try/catch，深层格式错误由系统默认错误提示兜底）
-IF !_raw.startsWith("{") || !_raw.endsWith("}")
--$importMessage = "JSON 格式不正确：应为以 { 开头、} 结尾的完整对象（含 treeVersion 与 nodes）"
--RETURN
-IF !_raw.includes("\"nodes\"")
--$importMessage = "JSON 缺少 \"nodes\" 字段：请粘贴项目导出的资源树 JSON"
--RETURN
-IF !_raw.includes("\"treeVersion\"")
--$importMessage = "JSON 缺少 \"treeVersion\" 字段：请粘贴项目导出的资源树 JSON"
--RETURN
-_payload({}) = JSON.parse(_raw)
-IF _payload == NULL || _payload.nodes == NULL || _payload.nodes.length == 0
--$importMessage = "JSON 缺少非空 nodes 数组"
--RETURN
-_tv(STRING) = (_payload.treeVersion != NULL && _payload.treeVersion != "") ? ("" + _payload.treeVersion) : ""
-IF _tv == ""
--$importMessage = "JSON 缺少 treeVersion"
--RETURN
-_notes([{}]) = (_payload.aiResourceNotes != NULL) ? _payload.aiResourceNotes : []
-_hash(STRING) = (_payload.treeHash != NULL && _payload.treeHash != "") ? ("" + _payload.treeHash) : ("hash-" + _tv)
-<ServiceDomain-PermissionCenterService "pcService">.RegisterPermissionTreeSnapshotLocal($selectedProjectId, $env, $selectedProjectName, _tv, _hash, _payload.nodes, _notes) -> _r
-IF !_r.success
--$importMessage = "导入失败:" + _r.message
--RETURN
-$importMessage = ("成功导入 " + _r.acceptedNodeCount + " 个资源节点；导入只更新资源树，未自动发布策略。")
-$showImport = false
-ReloadTree() -> _t
-ReloadDrafts() -> _rd

<Button-RolePick>.@click()
-$selectedRole = _r.roleCode
-RebuildRows() -> _r2

<Button-Allow>.@click()
-IF $selectedRole == ""
--$errorMessage = "请先选择角色"
--RETURN
-<ServiceDomain-PermissionCenterService "pcService">.SavePermissionDraft($selectedProjectId, $env, "", $selectedRole, _row.resourceCode, "allow", "") -> _r
-IF !_r.success
--$errorMessage = _r.message
--RETURN
-$errorMessage = ""
-ReloadDrafts() -> _r2

<Button-Deny>.@click()
-IF $selectedRole == ""
--$errorMessage = "请先选择角色"
--RETURN
-<ServiceDomain-PermissionCenterService "pcService">.SavePermissionDraft($selectedProjectId, $env, "", $selectedRole, _row.resourceCode, "deny", "") -> _r
-IF !_r.success
--$errorMessage = _r.message
--RETURN
-$errorMessage = ""
-ReloadDrafts() -> _r2

<Button-PickScope>.@click()
-IF $selectedRole == ""
--$errorMessage = "请先选择角色"
--RETURN
-<ServiceDomain-PermissionCenterService "pcService">.SavePermissionDraft($selectedProjectId, $env, "", $selectedRole, _row.parentResourceCode, "allow", _row.resourceCode) -> _r
-IF !_r.success
--$errorMessage = _r.message
--RETURN
-$errorMessage = ""
-ReloadDrafts() -> _r2

<Button-Publish>.@click()
-<SysUI>.showModal("发布策略", "发布后该项目的权限策略将立即对业务项目运行时生效，确定发布？") -> _cfm
-IF !_cfm.confirm
--RETURN
-$message = ""
-$errorMessage = ""
-$validationErrors = []
-<ServiceDomain-PermissionCenterService "pcService">.PublishPolicy($selectedProjectId, $env, "", "") -> _r
-IF _r.success
--$message = "发布成功，权限策略已对业务项目运行时生效。"
--RETURN
-$validationErrors = _r.validationErrors
-$errorMessage = "发布未通过校验"

<Button-ViewPub>.@click()
-$message = ""
-$errorMessage = ""
-<ServiceDomain-PermissionCenterService "pcService">.GetPublishedPolicyLocal($selectedProjectId, $env, "") -> _r
-IF !_r.success
--$errorMessage = _r.message
--RETURN
-$publishedText = ("当前已发布策略：角色权限 " + _r.rolePermissions.length + " 条，数据范围 " + _r.dataScopeSelections.length + " 条，资源节点 " + _r.resourceNodes.length + " 个。")

# Frontend Internal Methods

# Frontend Pipeline Funcs

</Section-ProjectPermissionPage>
