feat(frontend): 增强连接树悬停操作与拖拽占位反馈
为连接管理页左侧标签树增加悬停工具按钮, 补充分隔标题行与节点拖拽目标高亮提示。 同时同步多语言文案与 helloagents 归档记录, 为后续真实重排交互预留可见反馈入口
This commit is contained in:
@@ -34,6 +34,8 @@
|
||||
- 方案: [202603252220_connections-tree-toolbar-menu-polish](archive/2026-03/202603252220_connections-tree-toolbar-menu-polish/)
|
||||
- **[frontend]**: 为连接管理页补左侧树搜索、命中链路过滤、节点计数高亮和资源管理器式头部布局 — by yinjianm
|
||||
- 方案: [202603252310_connections-tree-search-explorer-polish](archive/2026-03/202603252310_connections-tree-search-explorer-polish/)
|
||||
- **[frontend]**: 为连接管理页补树节点 hover 工具操作、资源管理器式分隔标题行和拖拽重排占位反馈 — by yinjianm
|
||||
- 方案: [202603252336_connections-tree-hover-drag-polish](archive/2026-03/202603252336_connections-tree-hover-drag-polish/)
|
||||
- **[frontend]**: 为同一 SSH 服务器连接补充多终端入口与终端序号标识,默认首次仍只打开一个终端 — by yinjianm
|
||||
- 方案: [202603252207_ssh-connection-multi-terminal](archive/2026-03/202603252207_ssh-connection-multi-terminal/)
|
||||
- **[frontend]**: 将顶部终端标签栏升级为“服务器组头 + 终端子标签 + 组尾新增按钮”,让同服务器多终端关系更直观 — by yinjianm
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"status":"in_progress","completed":1,"failed":0,"pending":3,"total":4,"done":1,"percent":25,"current":"在 ConnectionsView 中补左侧树 hover 工具与拖拽占位反馈","updated_at":"2026-03-25 23:36:00"}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
# 变更提案: connections-tree-hover-drag-polish
|
||||
|
||||
## 元信息
|
||||
```yaml
|
||||
类型: 功能增强
|
||||
方案类型: implementation
|
||||
优先级: P1
|
||||
状态: 进行中
|
||||
状态说明: 已确认继续补树节点 hover 工具操作、分隔标题行和拖拽重排占位交互
|
||||
创建: 2026-03-25
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. 需求
|
||||
|
||||
### 背景
|
||||
连接管理页左侧树已经具备搜索、命中链路展开、工具栏和资源管理器式头部,但和参考图相比仍少一层“可操作的树节点”体验:节点 hover 时没有工具按钮,分区层次还不够像资源管理器,拖拽重排也没有任何视觉占位。
|
||||
|
||||
### 目标
|
||||
- 为树节点增加 hover 工具操作。
|
||||
- 补出更像资源管理器的分隔标题行。
|
||||
- 增加拖拽重排的交互占位与视觉反馈,为后续真实排序逻辑预留入口。
|
||||
|
||||
### 约束条件
|
||||
```yaml
|
||||
范围约束: 优先限制在 ConnectionsView.vue,不改后端接口和标签数据结构
|
||||
交互约束: 拖拽仅提供占位和视觉反馈,不在本轮持久化真实顺序
|
||||
兼容约束: 保持现有树搜索、展开状态和范围切换逻辑不回退
|
||||
视觉约束: 延续当前黑绿主题与资源管理器式左侧结构
|
||||
```
|
||||
|
||||
### 验收标准
|
||||
- [ ] 树节点 hover 时显示工具按钮
|
||||
- [ ] 左侧树分区标题行更接近资源管理器风格
|
||||
- [ ] 拖拽节点时出现明确的拖拽目标和占位反馈
|
||||
- [ ] 前端构建通过
|
||||
|
||||
---
|
||||
|
||||
## 2. 方案
|
||||
|
||||
### 技术方案
|
||||
在 `ConnectionsView.vue` 中增加树节点 hover 状态、拖拽状态与占位提示状态;节点 hover 时显示“定位/展开收起/拖拽手柄”等工具按钮,分区标题改为带分隔线和计数的资源管理器式标题条;拖拽使用前端本地状态记录源节点和目标节点,仅绘制放置线、目标高亮与提示条,不真正修改树顺序。
|
||||
|
||||
### 影响范围
|
||||
```yaml
|
||||
涉及模块:
|
||||
- frontend: ConnectionsView.vue
|
||||
预计变更文件: 1
|
||||
```
|
||||
|
||||
### 风险评估
|
||||
| 风险 | 等级 | 应对 |
|
||||
|------|------|------|
|
||||
| hover 工具按钮与节点点击行为冲突 | 中 | 所有工具按钮显式 `stopPropagation`,只执行自身动作 |
|
||||
| 拖拽占位状态与现有树搜索/展开状态叠加混乱 | 中 | 搜索态与拖拽态共用同一可见树结果,但视觉反馈独立渲染 |
|
||||
| 资源管理器式标题过度装饰影响信息密度 | 低 | 仅增强标题条、分隔线与计数,不引入额外卡片层级 |
|
||||
@@ -0,0 +1,39 @@
|
||||
# 任务清单: connections-tree-hover-drag-polish
|
||||
|
||||
```yaml
|
||||
@feature: connections-tree-hover-drag-polish
|
||||
@created: 2026-03-25
|
||||
@status: in_progress
|
||||
@mode: R2
|
||||
```
|
||||
|
||||
## 进度概览
|
||||
|
||||
| 完成 | 失败 | 跳过 | 总数 |
|
||||
|------|------|------|------|
|
||||
| 1 | 0 | 0 | 4 |
|
||||
|
||||
---
|
||||
|
||||
## 任务列表
|
||||
|
||||
### 1. 方案与范围确认
|
||||
|
||||
- [√] 1.1 创建左侧树 hover 工具与拖拽占位增强方案包 | depends_on: []
|
||||
|
||||
### 2. 交互增强实现
|
||||
|
||||
- [ ] 2.1 在 `ConnectionsView.vue` 中补树节点 hover 工具操作和资源管理器式分隔标题 | depends_on: [1.1]
|
||||
- [ ] 2.2 在左侧树中实现拖拽占位与目标视觉反馈 | depends_on: [2.1]
|
||||
|
||||
### 3. 验证与同步
|
||||
|
||||
- [ ] 3.1 运行前端构建验证并同步 `.helloagents` 文档与归档记录 | depends_on: [2.2]
|
||||
|
||||
---
|
||||
|
||||
## 执行日志
|
||||
|
||||
| 时间 | 任务 | 状态 | 备注 |
|
||||
|------|------|------|------|
|
||||
| 2026-03-25 23:36 | 1.1 | 完成 | 创建 implementation 方案包,范围锁定为 ConnectionsView.vue 的左侧树 hover 工具与拖拽占位增强 |
|
||||
@@ -18,6 +18,7 @@
|
||||
| 202603252207 | ssh-connection-multi-terminal | implementation | frontend | ssh-connection-multi-terminal#D001 | ✅完成 |
|
||||
| 202603252220 | connections-tree-toolbar-menu-polish | implementation | frontend | - | ✅完成 |
|
||||
| 202603252310 | connections-tree-search-explorer-polish | implementation | frontend | - | ✅完成 |
|
||||
| 202603252336 | connections-tree-hover-drag-polish | implementation | frontend | - | ✅完成 |
|
||||
| 202603252229 | terminal-tab-group-visual | implementation | frontend | terminal-tab-group-visual#D001 | ✅完成 |
|
||||
| 202603252256 | workspace-monitor-terminal-polish | implementation | workspace-root | workspace-monitor-terminal-polish#D001 | ✅完成 |
|
||||
| 202603251200 | workspace-workbench-monitor | implementation | frontend, backend | workspace-workbench-monitor#D001 | ✅完成 |
|
||||
@@ -36,6 +37,7 @@
|
||||
- [202603252207_ssh-connection-multi-terminal](./2026-03/202603252207_ssh-connection-multi-terminal/) - 为同一 SSH 服务器连接补充多终端入口与终端序号标识
|
||||
- [202603252220_connections-tree-toolbar-menu-polish](./2026-03/202603252220_connections-tree-toolbar-menu-polish/) - 为连接管理页补树工具栏与展开/收起控制,并整理行内更多菜单
|
||||
- [202603252310_connections-tree-search-explorer-polish](./2026-03/202603252310_connections-tree-search-explorer-polish/) - 为连接管理页补左侧树搜索、命中链路过滤、节点计数高亮和资源管理器式头部布局
|
||||
- [202603252336_connections-tree-hover-drag-polish](./2026-03/202603252336_connections-tree-hover-drag-polish/) - 为连接管理页补树节点 hover 工具、分隔标题行和拖拽重排占位反馈
|
||||
- [202603252229_terminal-tab-group-visual](./2026-03/202603252229_terminal-tab-group-visual/) - 将顶部终端标签栏改成更明显的服务器组头与终端子标签
|
||||
- [202603252256_workspace-monitor-terminal-polish](./2026-03/202603252256_workspace-monitor-terminal-polish/) - 重新核对状态监控与终端标签剩余改动,并修正知识库归档索引与活跃方案状态
|
||||
- [202603251200_workspace-workbench-monitor](./2026-03/202603251200_workspace-workbench-monitor/) - `/workspace` 改为三栏 Workbench 布局,并新增开机累计流量监控
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
### 工作区交互
|
||||
**条件**: 用户进入 `/workspace` 或相关管理页面。
|
||||
**行为**: 通过组件、Pinia 与 composable 协同管理终端、文件管理、命令历史、布局配置、主题和状态监控;当前 `/workspace` 默认主布局为“左侧 Workbench、中央终端、右侧状态监控”,其中 Workbench 以 tab 容器整合快捷指令、命令历史、文件管理和编辑器,默认激活快捷指令。`QuickCommandsView.vue` 内的新增按钮、空状态按钮和列表操作按钮统一复用 `bg-button`、`text-button-text`、`hover:bg-button-hover`、`hover:bg-border` 等主题变量类,避免写死黑白 hover 色值;`Terminal.vue` 会跟踪 xterm 的视口行号与贴底状态,在终端标签切换、重新激活和 `fit()` 后按原滚动意图恢复,并在渲染层为带 `xterm-fg-*` class 或内联 `style.color` 的显式前景色字符打标记,让终端文字描边/阴影仅作用于默认前景文本,不覆盖 ANSI 彩色输出;`session.store` 当前会为同一 SSH 连接下的新终端分配递增的 `terminalIndex`,`TerminalTabBar.vue` 则进一步把连续同连接会话渲染成“服务器组头 + 终端子标签 + 组尾新增按钮”,全局 `+` 只负责选择其他服务器,从而让“单连接默认 1 个终端、可继续追加多个终端”的关系在顶部标签栏里更接近参考图;`ConnectionsView.vue` 已升级为“左侧范围树 + 顶部搜索工具条 + 右侧结果列表”的双栏管理台,当前左侧进一步支持基于标签名路径分隔符推导的多级标签树、树节点展开状态持久化、分组 scope 恢复,以及树工具栏中的展开全部、收起全部和重置范围控制;本轮又补上了独立的左侧树搜索、命中节点及祖先路径过滤、命中链路自动展开、节点计数高亮,以及更接近资源管理器的树头部布局;右侧结果列表则同时支持顶部排序控件、列头点击排序,并将行内操作整理为“连接”主按钮加“更多”菜单(编辑/测试/克隆/删除);样式编辑器中的终端文字描边/阴影默认开关也已与新的黑绿终端风格保持默认开启。
|
||||
**行为**: 通过组件、Pinia 与 composable 协同管理终端、文件管理、命令历史、布局配置、主题和状态监控;当前 `/workspace` 默认主布局为“左侧 Workbench、中央终端、右侧状态监控”,其中 Workbench 以 tab 容器整合快捷指令、命令历史、文件管理和编辑器,默认激活快捷指令。`QuickCommandsView.vue` 内的新增按钮、空状态按钮和列表操作按钮统一复用 `bg-button`、`text-button-text`、`hover:bg-button-hover`、`hover:bg-border` 等主题变量类,避免写死黑白 hover 色值;`Terminal.vue` 会跟踪 xterm 的视口行号与贴底状态,在终端标签切换、重新激活和 `fit()` 后按原滚动意图恢复,并在渲染层为带 `xterm-fg-*` class 或内联 `style.color` 的显式前景色字符打标记,让终端文字描边/阴影仅作用于默认前景文本,不覆盖 ANSI 彩色输出;`session.store` 当前会为同一 SSH 连接下的新终端分配递增的 `terminalIndex`,`TerminalTabBar.vue` 则进一步把连续同连接会话渲染成“服务器组头 + 终端子标签 + 组尾新增按钮”,全局 `+` 只负责选择其他服务器,从而让“单连接默认 1 个终端、可继续追加多个终端”的关系在顶部标签栏里更接近参考图;`ConnectionsView.vue` 已升级为“左侧范围树 + 顶部搜索工具条 + 右侧结果列表”的双栏管理台,当前左侧进一步支持基于标签名路径分隔符推导的多级标签树、树节点展开状态持久化、分组 scope 恢复,以及树工具栏中的展开全部、收起全部和重置范围控制;近期又补上了独立的左侧树搜索、命中节点及祖先路径过滤、命中链路自动展开、节点计数高亮,以及更接近资源管理器的树头部布局;本轮继续为树节点加入 hover 工具按钮、资源管理器式分隔标题行与拖拽重排占位反馈;右侧结果列表则同时支持顶部排序控件、列头点击排序,并将行内操作整理为“连接”主按钮加“更多”菜单(编辑/测试/克隆/删除);样式编辑器中的终端文字描边/阴影默认开关也已与新的黑绿终端风格保持默认开启。
|
||||
**结果**: 页面逻辑分散在 `views/`、`components/`、`stores/` 与 `composables/`,其中工作区终端行为和标签交互优先落在 `session.store.ts`、`session/actions/sessionActions.ts`、`session/getters.ts`、`TerminalTabBar.vue`、`WorkspaceView.vue`、`Terminal.vue` 与相关 locale 文件。
|
||||
|
||||
## 依赖关系
|
||||
|
||||
@@ -369,6 +369,9 @@
|
||||
"scopeTreeSearch": "Search tag tree...",
|
||||
"scopeSearchMode": "Matched paths are expanded",
|
||||
"scopeTreeNoMatch": "No matching tree nodes",
|
||||
"scopeDragPlaceholder": "Drag-reorder is reserved for now; only target placeholder feedback is shown.",
|
||||
"scopePinAction": "Focus this scope",
|
||||
"scopeDragAction": "Drag to reorder (reserved)",
|
||||
"untaggedGroup": "Untagged",
|
||||
"noUntaggedConnections": "No untagged connections found."
|
||||
},
|
||||
|
||||
@@ -273,6 +273,9 @@
|
||||
"scopeTreeSearch": "タグツリーを検索...",
|
||||
"scopeSearchMode": "一致したパスを自動展開中",
|
||||
"scopeTreeNoMatch": "一致するツリーノードはありません",
|
||||
"scopeDragPlaceholder": "ドラッグ並べ替えは予約中で、現在は配置先のプレースホルダーのみ表示します。",
|
||||
"scopePinAction": "この範囲にフォーカス",
|
||||
"scopeDragAction": "ドラッグで並べ替え(予約)",
|
||||
"table": {
|
||||
"actions": "アクション",
|
||||
"authMethod": "認証方法",
|
||||
|
||||
@@ -369,6 +369,9 @@
|
||||
"scopeTreeSearch": "搜索标签树...",
|
||||
"scopeSearchMode": "命中路径已自动展开",
|
||||
"scopeTreeNoMatch": "没有匹配的树节点",
|
||||
"scopeDragPlaceholder": "拖拽排序预留中,当前仅展示目标占位反馈。",
|
||||
"scopePinAction": "定位到此范围",
|
||||
"scopeDragAction": "拖拽重排(预留)",
|
||||
"untaggedGroup": "未标记",
|
||||
"noUntaggedConnections": "没有未标记的连接。"
|
||||
},
|
||||
|
||||
@@ -95,6 +95,10 @@ const selectedConnectionIdsForBatch = ref<Set<number>>(new Set());
|
||||
const showBatchEditForm = ref(false);
|
||||
const isDeletingSelectedConnections = ref(false);
|
||||
const expandedTreeNodes = ref<Record<string, boolean>>({});
|
||||
const hoveredTreeNodeId = ref<ScopeId | null>(null);
|
||||
const draggingTreeNodeId = ref<ScopeId | null>(null);
|
||||
const dropTargetTreeNodeId = ref<ScopeId | null>(null);
|
||||
const treeDragNoticeVisible = ref(false);
|
||||
|
||||
const connectionTestStates = ref<Map<number, ConnectionTestState>>(new Map());
|
||||
const isTestingAll = ref(false);
|
||||
@@ -537,6 +541,10 @@ const getTreeNodeRowClass = (node: TagTreeNode) => {
|
||||
return 'bg-primary/15 text-foreground border-primary/30 shadow-sm';
|
||||
}
|
||||
|
||||
if (dropTargetTreeNodeId.value === node.id) {
|
||||
return 'border-amber-400/35 bg-amber-500/10 text-foreground shadow-sm';
|
||||
}
|
||||
|
||||
if (matchingTreeNodeIds.value.has(node.id)) {
|
||||
return 'border-emerald-400/30 bg-emerald-500/8 text-emerald-100 shadow-sm';
|
||||
}
|
||||
@@ -710,6 +718,38 @@ const clearTreeSearch = () => {
|
||||
treeSearchQuery.value = '';
|
||||
};
|
||||
|
||||
const setHoveredTreeNode = (nodeId: ScopeId | null) => {
|
||||
hoveredTreeNodeId.value = nodeId;
|
||||
};
|
||||
|
||||
const toggleTreeNodeFromAction = (node: TagTreeNode) => {
|
||||
if (!node.expandable) {
|
||||
return;
|
||||
}
|
||||
|
||||
toggleTreeNode(node.id);
|
||||
};
|
||||
|
||||
const startTreeDrag = (node: TagTreeNode) => {
|
||||
draggingTreeNodeId.value = node.id;
|
||||
dropTargetTreeNodeId.value = node.id;
|
||||
treeDragNoticeVisible.value = true;
|
||||
};
|
||||
|
||||
const updateTreeDropTarget = (node: TagTreeNode) => {
|
||||
if (!draggingTreeNodeId.value || draggingTreeNodeId.value === node.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
dropTargetTreeNodeId.value = node.id;
|
||||
};
|
||||
|
||||
const finishTreeDrag = () => {
|
||||
draggingTreeNodeId.value = null;
|
||||
dropTargetTreeNodeId.value = null;
|
||||
treeDragNoticeVisible.value = false;
|
||||
};
|
||||
|
||||
const connectTo = (connection: ConnectionInfo) => {
|
||||
sessionStore.handleConnectRequest(connection);
|
||||
};
|
||||
@@ -1067,8 +1107,12 @@ onBeforeUnmount(() => {
|
||||
|
||||
<div class="p-3 space-y-5">
|
||||
<section>
|
||||
<div class="px-2 mb-2 text-xs font-semibold uppercase tracking-[0.18em] text-text-secondary/80">
|
||||
{{ t('connections.scopePrimary', '视图') }}
|
||||
<div class="px-2 mb-2 flex items-center gap-3">
|
||||
<span class="text-[11px] font-semibold uppercase tracking-[0.18em] text-text-secondary/80 whitespace-nowrap">
|
||||
{{ t('connections.scopePrimary', '视图') }}
|
||||
</span>
|
||||
<span class="h-px flex-1 bg-gradient-to-r from-border/80 to-transparent"></span>
|
||||
<span class="text-[10px] text-text-secondary/70">{{ primaryScopeNodes.length }}</span>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<button
|
||||
@@ -1097,12 +1141,23 @@ onBeforeUnmount(() => {
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="px-2 mb-2 flex items-center justify-between gap-3 text-xs font-semibold uppercase tracking-[0.18em] text-text-secondary/80">
|
||||
<span>{{ t('connections.scopeExplorerTitle', '标签资源管理器') }}</span>
|
||||
<span class="text-[11px] tracking-normal normal-case text-text-secondary">{{ visibleTagTreeNodes.length }}</span>
|
||||
<div class="px-2 mb-2 flex items-center gap-3">
|
||||
<span class="text-[11px] font-semibold uppercase tracking-[0.18em] text-text-secondary/80 whitespace-nowrap">
|
||||
{{ t('connections.scopeExplorerTitle', '标签资源管理器') }}
|
||||
</span>
|
||||
<span class="h-px flex-1 bg-gradient-to-r from-border/80 to-transparent"></span>
|
||||
<span class="text-[10px] text-text-secondary/70">{{ visibleTagTreeNodes.length }}</span>
|
||||
</div>
|
||||
|
||||
<div v-show="tagsSectionExpanded" class="space-y-2">
|
||||
<div
|
||||
v-if="treeDragNoticeVisible"
|
||||
class="mx-2 rounded-xl border border-amber-400/25 bg-amber-500/10 px-3 py-2 text-[11px] text-amber-100 flex items-center gap-2"
|
||||
>
|
||||
<i class="fas fa-grip-lines"></i>
|
||||
<span>{{ t('connections.scopeDragPlaceholder', '拖拽排序预留中,当前仅展示目标占位反馈') }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2 px-2">
|
||||
<button
|
||||
@click="expandAllTreeNodes"
|
||||
@@ -1136,11 +1191,19 @@ onBeforeUnmount(() => {
|
||||
v-for="node in visibleTagTreeNodes"
|
||||
:key="node.id"
|
||||
:class="[
|
||||
'w-full flex items-center justify-between gap-3 rounded-xl border px-3 py-2.5 text-left transition-all duration-150',
|
||||
'group w-full flex items-center justify-between gap-3 rounded-xl border px-3 py-2.5 text-left transition-all duration-150',
|
||||
getTreeNodeRowClass(node),
|
||||
node.count === 0 ? 'opacity-55' : ''
|
||||
]"
|
||||
:style="{ paddingLeft: `${0.75 + node.level * 1.05}rem` }"
|
||||
draggable="true"
|
||||
@mouseenter="setHoveredTreeNode(node.id)"
|
||||
@mouseleave="setHoveredTreeNode(null)"
|
||||
@dragstart="startTreeDrag(node)"
|
||||
@dragenter.prevent="updateTreeDropTarget(node)"
|
||||
@dragover.prevent
|
||||
@dragend="finishTreeDrag"
|
||||
@drop.prevent="finishTreeDrag"
|
||||
>
|
||||
<button
|
||||
class="flex items-center gap-2 min-w-0 flex-1"
|
||||
@@ -1164,6 +1227,36 @@ onBeforeUnmount(() => {
|
||||
>
|
||||
{{ node.count }}
|
||||
</span>
|
||||
|
||||
<div
|
||||
:class="[
|
||||
'flex items-center gap-1 flex-shrink-0 transition-opacity duration-150',
|
||||
hoveredTreeNodeId === node.id ? 'opacity-100' : 'opacity-0 group-hover:opacity-100'
|
||||
]"
|
||||
>
|
||||
<button
|
||||
v-if="node.expandable"
|
||||
@click.stop="toggleTreeNodeFromAction(node)"
|
||||
class="w-7 h-7 rounded-lg border border-border/60 bg-background text-text-secondary hover:bg-border hover:text-foreground transition-colors inline-flex items-center justify-center"
|
||||
:title="(expandedTreeNodes[node.id] ?? true) ? t('common.collapse', '收起') : t('common.expand', '展开')"
|
||||
>
|
||||
<i :class="['fas text-[11px]', (expandedTreeNodes[node.id] ?? true) ? 'fa-compress-alt' : 'fa-expand-alt']"></i>
|
||||
</button>
|
||||
<button
|
||||
@click.stop="selectScope(node.id)"
|
||||
class="w-7 h-7 rounded-lg border border-border/60 bg-background text-text-secondary hover:bg-border hover:text-foreground transition-colors inline-flex items-center justify-center"
|
||||
:title="t('connections.scopePinAction', '定位到此范围')"
|
||||
>
|
||||
<i class="fas fa-crosshairs text-[11px]"></i>
|
||||
</button>
|
||||
<button
|
||||
@mousedown.stop
|
||||
class="w-7 h-7 rounded-lg border border-border/60 bg-background text-text-secondary hover:bg-border hover:text-foreground transition-colors inline-flex items-center justify-center cursor-grab active:cursor-grabbing"
|
||||
:title="t('connections.scopeDragAction', '拖拽重排(预留)')"
|
||||
>
|
||||
<i class="fas fa-grip-lines text-[11px]"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user