safety-eval-service-frontend/.cursor/skills/specs/05-页面布局与通用UI规范/03-分类树组件/SKILL.md

197 lines
6.3 KiB
Markdown
Raw Normal View History

2026-08-14 16:54:03 +08:00
---
name: jjb-category-tree
description: 定义分类树组件规范。在使用左侧树形分类导航、分类搜索、右键菜单操作时使用。
---
# 页面布局与通用 UI 规范
## 分类树组件
分类树用于左侧导航区域,提供树形结构的浏览、搜索、右键菜单操作等功能。
### 布局与宽度
- 分类树作为左侧面板,**固定宽度**,通常为 **240px ~ 260px**,高度撑满父容器。
- 树内容区 `flex: 1; overflow: auto`,节点过多时可垂直滚动。
- 结构分区(自上而下):
1. **标题栏**:左侧显示标题文字(如"文件夹"),右侧放置新建图标按钮。
2. **搜索栏**:输入框带搜索前缀图标,`size="small"`,支持 `allowClear`
3. **树主体**`Spin` 包裹 `Tree` 组件,空数据时显示"暂无数据"占位提示。
```less
.category-tree {
display: flex;
flex-direction: column;
height: 100%;
.category-tree-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 12px 8px;
}
.category-tree-search {
padding: 0 12px 8px;
}
.category-tree-body {
flex: 1;
overflow: auto;
padding: 0 8px;
}
}
```
### 树节点渲染
- 使用 `titleRender` 自定义节点内容,组合**高亮文本**与**悬停操作图标**。
- 使用 `fieldNames` 将后端字段映射为 Tree 所需格式(`title` → 显示名、`key` → 唯一标识、`children` → 子节点)。
- 设置 `blockNode` 使整行可点击,提升操作区域。
- 节点标题文本需设置溢出省略:
```less
.tree-node-title {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
```
```jsx
<Tree
treeData={filteredTree}
blockNode
fieldNames={{ title: 'folderName', key: 'id', children: 'children' }}
titleRender={(nodeData) => (
<div className="tree-node">
<span className="tree-node-title">{highlight(nodeData.folderName, searchValue)}</span>
<Space className="tree-node-actions" size={4}>
<FolderAddOutlined onClick={(e) => { e.stopPropagation(); handleCreate(nodeData.id); }} />
<EditOutlined onClick={(e) => { e.stopPropagation(); handleRename(nodeData); }} />
<DeleteOutlined onClick={(e) => { e.stopPropagation(); handleDelete(nodeData); }} style={{ color: '#ff4d4f' }} />
</Space>
</div>
)}
/>
```
> **注意**:操作图标的 `onClick` 必须 `e.stopPropagation()` 阻止冒泡,避免触发树节点选中。
### 选中态与悬停交互
- 选中节点背景色使用浅蓝 `#e6f4ff`
- 悬停时显示行内操作图标,默认隐藏,通过 CSS 控制:
```less
.tree-node-actions {
display: none;
flex-shrink: 0;
}
:global {
.ant-tree-treenode:hover .tree-node-actions {
display: inline-flex;
}
.ant-tree-treenode-selected .ant-tree-node-content-wrapper {
background: #e6f4ff;
}
}
```
- 节点内边距不宜过大,保持紧凑:`.ant-tree-node-content-wrapper { padding: 2px 4px; }`。
### 搜索匹配文字高亮
- 在搜索框中输入关键词后,树节点中**匹配的文字**需高亮显示。
- 高亮颜色使用主题蓝色 `#0064C8`
- 实现方式:将节点标题文本按关键词拆分,匹配部分用 `<span style={{ color: '#0064C8' }}>` 包裹。
- 关键词中的正则特殊字符需转义处理。
```jsx
function highlight(text, keyword) {
if (!keyword) return text;
const escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp(`(${escaped})`, 'gi');
return text.split(regex).map((part, i) =>
regex.test(part) ? <span key={i} style={{ color: '#0064C8' }}>{part}</span> : part
);
}
```
- 搜索结果需 **过滤树**:保留标题匹配的节点,以及其子孙中有匹配的父节点(保持树结构)。
- 搜索有值时,应**自动展开所有匹配节点**,让用户直接看到结果。
### 右键菜单操作
- 使用 `Dropdown` + `menu` + `trigger={['contextMenu']}` 实现右键弹出菜单。
- `Dropdown` 包裹整个 `Tree` 组件,`onRightClick` 记录当前右键节点。
- 菜单项通常包括:**新建子文件夹**、**重命名**、**删除**(删除项标记 `danger: true`)。
- 删除操作需弹出二次确认弹窗(`Modal.confirm`),提示删除后不可恢复。
```jsx
const contextMenuItems = [
{ key: 'create', label: '新建子文件夹' },
{ key: 'rename', label: '重命名' },
{ key: 'delete', label: '删除', danger: true },
];
<Dropdown menu={{ items: contextMenuItems, onClick: handleContextMenuClick }} trigger={['contextMenu']}>
<Tree
treeData={filteredTree}
onRightClick={({ node }) => setRightClickNode(node)}
// ...
/>
</Dropdown>
```
- 右键新建子文件夹时,`parentId` 为右键节点的 id标题栏新建按钮点击时`parentId` 为当前选中节点 id无选中则传 `null` 表示根级新建)。
### 新增/重命名弹窗
- 使用 `Modal` + `Input` 实现新增和重命名,两种操作复用同一弹窗,通过状态区分标题文案。
- 输入框绑定 `onPressEnter` 支持回车确认。
- 名称需 `trim()` 处理,空值不允许提交。
- Modal 的 `confirmLoading` 绑定提交 loading 状态,防止重复点击。
```jsx
<Modal
title={modalType === 'create' ? (parentId != null ? '新建子文件夹' : '新建文件夹') : '重命名'}
open={modalOpen}
onOk={handleModalOk}
onCancel={() => setModalOpen(false)}
confirmLoading={submitLoading}
destroyOnHidden
>
<Input
placeholder="请输入文件夹名称"
value={modalName}
onChange={e => setModalName(e.target.value)}
onPressEnter={handleModalOk}
/>
</Modal>
```
> `destroyOnHidden` 确保每次打开弹窗时输入框状态被重置。
### Loading 状态
- 树数据加载时:使用 `Spin` 组件包裹树主体,绑定树数据的 loading 状态。
- 提交操作时Modal 的 `confirmLoading` 绑定提交 loading防止重复提交。
- 操作完成后需**重新请求树数据**刷新列表。
```jsx
<Spin spinning={treeLoading}>
<div className="category-tree-body">
{filteredTree.length > 0 ? <Tree ... /> : <div className="category-tree-empty">暂无文件夹</div>}
</div>
</Spin>
```
### 空状态
- 树数据为空或搜索无结果时,显示居中占位文字"暂无文件夹"。
- 样式:`text-align: center; color: #999; padding: 24px 0; font-size: 13px;`。