All checks were successful
Build and Deploy Front / deploy (push) Successful in 6m0s
页面背景默认改为默认灰并同步启动屏;顶栏/登录头可回官网;线路卡片与话题面板顾问入口改为图标。 Co-authored-by: Cursor <cursoragent@cursor.com>
1532 lines
40 KiB
Vue
1532 lines
40 KiB
Vue
<script lang="ts" setup>
|
||
import { ref, computed, watch, useSlots } from 'vue';
|
||
import {
|
||
IconSearch,
|
||
IconDelete,
|
||
IconLeft,
|
||
IconRight,
|
||
IconRefresh,
|
||
IconPlus,
|
||
} from '@arco-design/web-vue/es/icon';
|
||
import type { CustomLeftMenuTreeNode } from './types';
|
||
import {
|
||
buildPaginatedTree,
|
||
collectPaginateEntries,
|
||
getPaginationMeta,
|
||
} from './pagination';
|
||
|
||
const props = withDefaults(
|
||
defineProps<{
|
||
treeData: CustomLeftMenuTreeNode[];
|
||
selectedKeys?: string[];
|
||
expandedKeys?: string[];
|
||
loading?: boolean;
|
||
searchKeyword?: string;
|
||
searchPlaceholder?: string;
|
||
showSearch?: boolean;
|
||
showRefresh?: boolean;
|
||
showAdd?: boolean;
|
||
refreshTitle?: string;
|
||
addTitle?: string;
|
||
loadingText?: string;
|
||
showNodeIndex?: boolean;
|
||
showMethod?: boolean;
|
||
methodLabel?: string;
|
||
showMapCount?: boolean;
|
||
showNodeActions?: boolean;
|
||
/** 非叶子节点(如分组)是否显示操作下拉 */
|
||
showBranchNodeActions?: boolean;
|
||
showNodeCode?: boolean;
|
||
actionsTitle?: string;
|
||
deleteLabel?: string;
|
||
width?: string | number;
|
||
showPagination?: boolean;
|
||
pageSize?: number;
|
||
currentPage?: number;
|
||
/** 树形层级展示(保留缩进/展开箭头,适用于组织等多级树) */
|
||
hierarchical?: boolean;
|
||
/** 列表为空时的提示文案 */
|
||
emptyText?: string;
|
||
}>(),
|
||
{
|
||
selectedKeys: () => [],
|
||
expandedKeys: () => [],
|
||
loading: false,
|
||
searchKeyword: '',
|
||
searchPlaceholder: '请输入',
|
||
showSearch: true,
|
||
showRefresh: true,
|
||
showAdd: true,
|
||
refreshTitle: '刷新列表',
|
||
addTitle: '新增',
|
||
loadingText: '正在加载...',
|
||
showNodeIndex: true,
|
||
showMethod: true,
|
||
methodLabel: 'POST',
|
||
showMapCount: true,
|
||
showNodeActions: true,
|
||
showBranchNodeActions: false,
|
||
showNodeCode: true,
|
||
actionsTitle: '操作',
|
||
deleteLabel: '删除',
|
||
width: '280px',
|
||
showPagination: true,
|
||
pageSize: 15,
|
||
currentPage: 1,
|
||
hierarchical: false,
|
||
emptyText: '暂无数据',
|
||
}
|
||
);
|
||
|
||
const emit = defineEmits<{
|
||
(e: 'update:selectedKeys', value: string[]): void;
|
||
(e: 'update:expandedKeys', value: string[]): void;
|
||
(e: 'update:searchKeyword', value: string): void;
|
||
(e: 'update:currentPage', value: number): void;
|
||
(e: 'search', keyword: string): void;
|
||
(e: 'refresh'): void;
|
||
(e: 'add'): void;
|
||
(e: 'select', keys: string[], info: any): void;
|
||
(e: 'expand', keys: string[]): void;
|
||
(e: 'actionSelect', value: string, option: any): void;
|
||
(e: 'deleteNode', nodeData: any): void;
|
||
(e: 'nodeClick', key: string, nodeData: any): void;
|
||
(e: 'pageChange', page: number): void;
|
||
}>();
|
||
|
||
const slots = useSlots();
|
||
|
||
const innerSelectedKeys = ref<string[]>(props.selectedKeys?.length ? [...props.selectedKeys] : []);
|
||
const innerExpandedKeys = ref<string[]>(props.expandedKeys?.length ? [...props.expandedKeys] : []);
|
||
const innerSearchKeyword = ref(props.searchKeyword ?? '');
|
||
const innerCurrentPage = ref(props.currentPage ?? 1);
|
||
|
||
const paginateEntries = computed(() => collectPaginateEntries(props.treeData));
|
||
const paginationMeta = computed(() => getPaginationMeta(
|
||
paginateEntries.value.length,
|
||
props.pageSize,
|
||
innerCurrentPage.value,
|
||
));
|
||
|
||
/** key → node,避免模板内反复树遍历 */
|
||
const nodeByKey = computed(() => {
|
||
const map = new Map<string, CustomLeftMenuTreeNode>();
|
||
const walk = (nodes: CustomLeftMenuTreeNode[]) => {
|
||
for (const node of nodes) {
|
||
map.set(node.key, node);
|
||
if (node.children?.length) walk(node.children);
|
||
}
|
||
};
|
||
walk(props.treeData || []);
|
||
return map;
|
||
});
|
||
|
||
const paginatedTreeData = computed(() => {
|
||
if (!props.showPagination) return props.treeData;
|
||
const { start, end } = paginationMeta.value;
|
||
const slice = paginateEntries.value.slice(start, end);
|
||
return buildPaginatedTree(slice);
|
||
});
|
||
|
||
const isTreeEmpty = computed(
|
||
() => !props.loading && (!paginatedTreeData.value || paginatedTreeData.value.length === 0),
|
||
);
|
||
|
||
const visiblePageNumbers = computed(() => {
|
||
const { totalPages, currentPage } = paginationMeta.value;
|
||
if (totalPages <= 5) {
|
||
return Array.from({ length: totalPages }, (_, i) => i + 1);
|
||
}
|
||
const pages = new Set<number>([1, totalPages, currentPage]);
|
||
if (currentPage > 1) pages.add(currentPage - 1);
|
||
if (currentPage < totalPages) pages.add(currentPage + 1);
|
||
return Array.from(pages).sort((a, b) => a - b);
|
||
});
|
||
|
||
const currentPageModel = computed({
|
||
get: () => paginationMeta.value.currentPage,
|
||
set: (page: number) => {
|
||
setCurrentPage(page);
|
||
},
|
||
});
|
||
|
||
function setCurrentPage(page: number) {
|
||
const nextPage = Math.min(
|
||
Math.max(1, page),
|
||
paginationMeta.value.totalPages,
|
||
);
|
||
innerCurrentPage.value = nextPage;
|
||
emit('update:currentPage', nextPage);
|
||
emit('pageChange', nextPage);
|
||
}
|
||
|
||
function goPrevPage() {
|
||
setCurrentPage(currentPageModel.value - 1);
|
||
}
|
||
|
||
function goNextPage() {
|
||
setCurrentPage(currentPageModel.value + 1);
|
||
}
|
||
|
||
const selectedKeysModel = computed({
|
||
get: () => (props.selectedKeys && props.selectedKeys.length >= 0 ? props.selectedKeys : innerSelectedKeys.value),
|
||
set: (v: string[]) => {
|
||
innerSelectedKeys.value = v;
|
||
emit('update:selectedKeys', v);
|
||
},
|
||
});
|
||
|
||
const expandedKeysModel = computed({
|
||
get: () => (props.expandedKeys && props.expandedKeys.length >= 0 ? props.expandedKeys : innerExpandedKeys.value),
|
||
set: (v: string[]) => {
|
||
innerExpandedKeys.value = v;
|
||
emit('update:expandedKeys', v);
|
||
},
|
||
});
|
||
|
||
const searchKeywordModel = computed({
|
||
get: () => props.searchKeyword !== undefined ? props.searchKeyword : innerSearchKeyword.value,
|
||
set: (v: string) => {
|
||
innerSearchKeyword.value = v;
|
||
emit('update:searchKeyword', v);
|
||
},
|
||
});
|
||
|
||
watch(
|
||
() => props.selectedKeys,
|
||
(val) => {
|
||
if (val && val.length >= 0) innerSelectedKeys.value = [...val];
|
||
},
|
||
{ deep: true }
|
||
);
|
||
watch(
|
||
() => props.expandedKeys,
|
||
(val) => {
|
||
if (val && val.length >= 0) innerExpandedKeys.value = [...val];
|
||
},
|
||
{ deep: true }
|
||
);
|
||
watch(
|
||
() => props.searchKeyword,
|
||
(val) => {
|
||
if (val !== undefined) innerSearchKeyword.value = val;
|
||
}
|
||
);
|
||
watch(
|
||
() => props.currentPage,
|
||
(val) => {
|
||
if (val !== undefined && val !== innerCurrentPage.value) {
|
||
innerCurrentPage.value = val;
|
||
}
|
||
},
|
||
);
|
||
watch(
|
||
() => props.searchKeyword,
|
||
() => {
|
||
setCurrentPage(1);
|
||
},
|
||
);
|
||
watch(
|
||
() => props.pageSize,
|
||
() => {
|
||
setCurrentPage(1);
|
||
},
|
||
);
|
||
/** 条目数变化(搜索过滤、增删类型)时才回到第一页;节点字段更新(如 mapCount)不重置 */
|
||
watch(
|
||
() => paginateEntries.value.length,
|
||
(len, prevLen) => {
|
||
if (prevLen !== undefined && len !== prevLen) {
|
||
setCurrentPage(1);
|
||
}
|
||
},
|
||
);
|
||
watch(
|
||
() => paginationMeta.value.totalPages,
|
||
(totalPages) => {
|
||
if (innerCurrentPage.value > totalPages) {
|
||
setCurrentPage(totalPages);
|
||
}
|
||
},
|
||
);
|
||
|
||
function getNodeData(key: string): CustomLeftMenuTreeNode | null {
|
||
if (!key) return null;
|
||
return nodeByKey.value.get(key) ?? null;
|
||
}
|
||
|
||
/** 二级菜单序号:按父级分组,每组从 1 重新开始 */
|
||
function getNodeIndex(key: string): number | '' {
|
||
if (!key || !props.treeData?.length) return '';
|
||
for (const category of props.treeData) {
|
||
const children = category.children || [];
|
||
for (let i = 0; i < children.length; i++) {
|
||
if (children[i].key === key) return i + 1;
|
||
}
|
||
}
|
||
// 扁平叶子列表(无分组)时按顶层叶子序号
|
||
const topLeaves = props.treeData.filter((n) => n.isLeaf);
|
||
const idx = topLeaves.findIndex((n) => n.key === key);
|
||
return idx >= 0 ? idx + 1 : '';
|
||
}
|
||
|
||
function handleSearch() {
|
||
emit('search', searchKeywordModel.value);
|
||
}
|
||
|
||
function handleRefresh() {
|
||
emit('refresh');
|
||
}
|
||
|
||
function handleAdd() {
|
||
emit('add');
|
||
}
|
||
|
||
function handleTreeSelect(keys: string[], info: any) {
|
||
emit('select', keys, info);
|
||
}
|
||
|
||
function handleTreeExpand(keys: string[]) {
|
||
expandedKeysModel.value = keys;
|
||
emit('expand', keys);
|
||
}
|
||
|
||
function handleActionSelect(value: string, option: any) {
|
||
emit('actionSelect', value, option);
|
||
}
|
||
|
||
function handleDeleteNode(nodeData: any) {
|
||
emit('deleteNode', nodeData);
|
||
}
|
||
|
||
function selectTreeNode(key: string) {
|
||
if (!key) return;
|
||
selectedKeysModel.value = [key];
|
||
const nodeData = getNodeData(key);
|
||
emit('select', [key], { node: nodeData });
|
||
if (nodeData) emit('nodeClick', key, nodeData);
|
||
}
|
||
|
||
defineExpose({
|
||
getNodeData,
|
||
getNodeIndex,
|
||
});
|
||
</script>
|
||
|
||
<template>
|
||
<div
|
||
class="custom-left-menu"
|
||
:style="{ width: typeof width === 'number' ? `${width}px` : width }"
|
||
>
|
||
<div class="sidebar-header">
|
||
<div class="search-box">
|
||
<a-input
|
||
v-if="showSearch"
|
||
v-model="searchKeywordModel"
|
||
:placeholder="searchPlaceholder"
|
||
allow-clear
|
||
size="small"
|
||
class="search-input"
|
||
@change="handleSearch"
|
||
@clear="handleSearch"
|
||
>
|
||
<template #prefix>
|
||
<icon-search />
|
||
</template>
|
||
</a-input>
|
||
<div class="search-actions">
|
||
<a-button
|
||
v-if="showRefresh"
|
||
type="text"
|
||
size="small"
|
||
class="action-icon-btn refresh-btn"
|
||
:title="refreshTitle"
|
||
@click="handleRefresh"
|
||
>
|
||
<icon-refresh :size="18" />
|
||
</a-button>
|
||
<a-button
|
||
v-if="showAdd"
|
||
type="text"
|
||
size="small"
|
||
class="action-icon-btn add-btn"
|
||
:title="addTitle"
|
||
@click="handleAdd"
|
||
>
|
||
<icon-plus :size="18" />
|
||
</a-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div
|
||
class="interface-tree"
|
||
:class="{ 'interface-tree--hierarchical': hierarchical }"
|
||
>
|
||
<div v-if="loading" class="loading-state">
|
||
<a-spin />
|
||
<p>{{ loadingText }}</p>
|
||
</div>
|
||
<div v-else-if="isTreeEmpty" class="empty-state">
|
||
<p>{{ emptyText }}</p>
|
||
</div>
|
||
<a-tree
|
||
v-else
|
||
:data="paginatedTreeData"
|
||
:selected-keys="selectedKeysModel"
|
||
:expanded-keys="expandedKeysModel"
|
||
show-line
|
||
block-node
|
||
@select="handleTreeSelect"
|
||
@expand="handleTreeExpand"
|
||
>
|
||
<template #title="{ key, title }">
|
||
<div
|
||
class="tree-node"
|
||
:class="{
|
||
selected: selectedKeysModel.includes(key),
|
||
'tree-node--hierarchical': hierarchical,
|
||
'tree-node--branch': getNodeData(key)?.isLeaf !== true,
|
||
'tree-node--leaf': getNodeData(key)?.isLeaf === true,
|
||
'tree-node--with-code': getNodeData(key)?.isLeaf && showNodeCode,
|
||
[`tree-node--depth-${getNodeData(key)?.depth || (getNodeData(key)?.isLeaf ? 2 : 1)}`]: true,
|
||
}"
|
||
@click="hierarchical && selectTreeNode(key)"
|
||
>
|
||
<table class="node-table">
|
||
<tbody>
|
||
<tr>
|
||
<td class="node-content-cell">
|
||
<div class="node-main-row">
|
||
<span v-if="getNodeData(key)?.isLeaf && showNodeIndex && !hierarchical" class="node-number">{{ getNodeIndex(key) }}</span>
|
||
<span
|
||
v-if="getNodeData(key)?.isLeaf && showMethod"
|
||
class="post-method"
|
||
:class="`post-method--${String(getNodeData(key)?.method || methodLabel || 'POST').toLowerCase()}`"
|
||
>{{ getNodeData(key)?.method || methodLabel }}</span>
|
||
<span class="node-title" :title="title">{{ title }}</span>
|
||
<span v-if="getNodeData(key)?.isLeaf && showMapCount" class="map-count">
|
||
{{ getNodeData(key)?.mapCount ?? 0 }}
|
||
</span>
|
||
</div>
|
||
</td>
|
||
<td
|
||
v-if="getNodeData(key)?.isLeaf && showNodeActions"
|
||
class="node-actions-cell"
|
||
>
|
||
<a-dropdown
|
||
trigger="click"
|
||
:popup-max-height="false"
|
||
@select="handleActionSelect"
|
||
>
|
||
<a-button
|
||
type="text"
|
||
size="small"
|
||
class="action-btn vertical-dots"
|
||
:title="actionsTitle"
|
||
@click.stop
|
||
>
|
||
<div class="vertical-dots-icon">
|
||
<span></span>
|
||
<span></span>
|
||
<span></span>
|
||
</div>
|
||
</a-button>
|
||
<template #content>
|
||
<slot name="node-menu" :node-data="getNodeData(key)" :node-key="key">
|
||
<a-doption
|
||
value="delete"
|
||
:data="getNodeData(key)"
|
||
@click="handleDeleteNode(getNodeData(key))"
|
||
>
|
||
<icon-delete />
|
||
{{ deleteLabel }}
|
||
</a-doption>
|
||
</slot>
|
||
</template>
|
||
</a-dropdown>
|
||
</td>
|
||
<td
|
||
v-else-if="showBranchNodeActions && getNodeData(key)?.isLeaf !== true"
|
||
class="node-actions-cell branch-node-actions-cell"
|
||
>
|
||
<div class="branch-node-actions-row">
|
||
<span
|
||
v-if="slots['branch-node-inline-before']"
|
||
class="branch-node-inline-before"
|
||
@click.stop
|
||
>
|
||
<slot name="branch-node-inline-before" :node-data="getNodeData(key)" :node-key="key"></slot>
|
||
</span>
|
||
<a-dropdown
|
||
trigger="click"
|
||
:popup-max-height="false"
|
||
@select="handleActionSelect"
|
||
>
|
||
<a-button
|
||
type="text"
|
||
size="small"
|
||
class="action-btn vertical-dots"
|
||
:title="actionsTitle"
|
||
@click.stop
|
||
>
|
||
<div class="vertical-dots-icon">
|
||
<span></span>
|
||
<span></span>
|
||
<span></span>
|
||
</div>
|
||
</a-button>
|
||
<template #content>
|
||
<slot name="branch-node-menu" :node-data="getNodeData(key)" :node-key="key"></slot>
|
||
</template>
|
||
</a-dropdown>
|
||
</div>
|
||
</td>
|
||
<td
|
||
v-else-if="getNodeData(key)?.isLeaf !== true && slots['branch-node-extra']"
|
||
class="node-actions-cell branch-node-extra-cell"
|
||
>
|
||
<span class="branch-node-extra" @click.stop>
|
||
<slot name="branch-node-extra" :node-data="getNodeData(key)" :node-key="key"></slot>
|
||
</span>
|
||
</td>
|
||
</tr>
|
||
<!-- 第二行始终占位,保证单行/双行卡片高度一致(以两行为准) -->
|
||
<tr class="node-second-row">
|
||
<template
|
||
v-if="
|
||
(hierarchical || getNodeData(key)?.isLeaf !== true) &&
|
||
(getNodeData(key)?.subtitle || $slots['branch-node-subtitle'])
|
||
"
|
||
>
|
||
<td colspan="2" class="node-subtitle-cell">
|
||
<slot name="branch-node-subtitle" :node-data="getNodeData(key)" :node-key="key">
|
||
<span v-if="getNodeData(key)?.subtitle" class="node-subtitle">
|
||
{{ getNodeData(key)?.subtitle }}
|
||
</span>
|
||
</slot>
|
||
</td>
|
||
</template>
|
||
<template
|
||
v-else-if="
|
||
getNodeData(key)?.isLeaf &&
|
||
((showNodeCode && getNodeData(key)?.code) || !!$slots['node-second-row-extra'])
|
||
"
|
||
>
|
||
<td v-if="showNodeCode && getNodeData(key)?.code" class="node-code-cell">
|
||
<span class="interface-code">{{ getNodeData(key)?.code }}</span>
|
||
</td>
|
||
<td
|
||
:class="['node-actions-cell', 'node-second-row-extra-cell']"
|
||
:colspan="(showNodeCode && getNodeData(key)?.code) ? 1 : 2"
|
||
>
|
||
<span class="node-second-row-extra-slot">
|
||
<slot name="node-second-row-extra" :node-data="getNodeData(key)" :node-key="key"></slot>
|
||
</span>
|
||
</td>
|
||
</template>
|
||
<template v-else>
|
||
<td colspan="2" class="node-second-row-spacer-cell" aria-hidden="true" />
|
||
</template>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</template>
|
||
</a-tree>
|
||
</div>
|
||
|
||
<div
|
||
v-if="showPagination && paginationMeta.showPagination && !loading"
|
||
class="menu-pagination"
|
||
>
|
||
<div class="menu-pagination-capsule">
|
||
<button
|
||
type="button"
|
||
class="page-nav-btn"
|
||
:disabled="currentPageModel <= 1"
|
||
aria-label="上一页"
|
||
@click="goPrevPage"
|
||
>
|
||
<icon-left />
|
||
</button>
|
||
|
||
<div class="page-number-group">
|
||
<template v-for="(page, index) in visiblePageNumbers" :key="page">
|
||
<span
|
||
v-if="index > 0 && page - visiblePageNumbers[index - 1] > 1"
|
||
class="page-ellipsis"
|
||
>…</span>
|
||
<button
|
||
type="button"
|
||
class="page-number-btn"
|
||
:class="{ active: page === currentPageModel }"
|
||
@click="setCurrentPage(page)"
|
||
>
|
||
{{ page }}
|
||
</button>
|
||
</template>
|
||
</div>
|
||
|
||
<button
|
||
type="button"
|
||
class="page-nav-btn"
|
||
:disabled="currentPageModel >= paginationMeta.totalPages"
|
||
aria-label="下一页"
|
||
@click="goNextPage"
|
||
>
|
||
<icon-right />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped lang="less">
|
||
/*
|
||
* Linear 风格资源侧栏(浅色面板)
|
||
* 白底、圆角选中块、弱边框、次要信息降噪
|
||
*/
|
||
.custom-left-menu {
|
||
--clm-bg: #ffffff;
|
||
--clm-text: #1a1c20;
|
||
--clm-muted: #8a8f98;
|
||
--clm-faint: #b0b4ba;
|
||
--clm-hover: rgba(15, 16, 17, 0.04);
|
||
--clm-selected: var(--app-primary-color-light);
|
||
--clm-selected-text: var(--app-primary-color);
|
||
--clm-selected-border: color-mix(in srgb, var(--app-primary-color) 32%, transparent);
|
||
--clm-border: rgba(15, 16, 17, 0.08);
|
||
--clm-radius: 8px;
|
||
/* 列表卡片统一高度 */
|
||
--clm-node-title-line-height: 18px;
|
||
--clm-node-second-line-height: 16px;
|
||
--clm-node-padding-y: 5px;
|
||
--clm-node-height: 60px;
|
||
/* 菜单项垂直间距 */
|
||
--clm-node-gap: 8px;
|
||
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
flex-shrink: 0;
|
||
min-height: 0;
|
||
height: 100%;
|
||
padding: 10px 8px;
|
||
overflow: hidden;
|
||
color: var(--clm-text);
|
||
background: var(--clm-bg);
|
||
border: 1px solid var(--clm-border);
|
||
border-radius: 12px;
|
||
box-shadow: none;
|
||
}
|
||
|
||
.sidebar-header {
|
||
padding: 2px 4px 10px;
|
||
border-bottom: 1px solid var(--clm-border);
|
||
min-height: 40px;
|
||
display: flex;
|
||
align-items: center;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.search-box {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
width: 100%;
|
||
}
|
||
|
||
.search-input {
|
||
flex: 1;
|
||
min-width: 0;
|
||
|
||
:deep(.arco-input-wrapper) {
|
||
background: var(--color-fill-2) !important;
|
||
border-color: transparent !important;
|
||
border-radius: 7px;
|
||
box-shadow: 0 0 0 1px var(--clm-border);
|
||
transition: box-shadow 0.15s ease, background-color 0.15s ease;
|
||
|
||
&:hover {
|
||
background: var(--color-fill-3) !important;
|
||
box-shadow: 0 0 0 1px var(--color-border-3);
|
||
}
|
||
|
||
&.arco-input-focus {
|
||
background: var(--app-card-bg) !important;
|
||
border-color: transparent !important;
|
||
box-shadow: 0 0 0 1.5px var(--app-primary-color) !important;
|
||
}
|
||
}
|
||
|
||
:deep(.arco-input) {
|
||
color: var(--clm-text) !important;
|
||
font-size: 13px;
|
||
|
||
&::placeholder {
|
||
color: var(--clm-faint);
|
||
}
|
||
}
|
||
|
||
:deep(.arco-input-prefix),
|
||
:deep(.arco-input-clear-btn) {
|
||
color: var(--clm-muted);
|
||
}
|
||
}
|
||
|
||
.search-actions {
|
||
display: flex;
|
||
gap: 2px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.action-icon-btn {
|
||
width: 28px;
|
||
height: 28px;
|
||
padding: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: var(--clm-muted);
|
||
border: none;
|
||
border-radius: 7px;
|
||
font-size: 14px;
|
||
font-weight: 510;
|
||
transition: background-color 0.12s ease, color 0.12s ease;
|
||
|
||
:deep(.arco-icon) {
|
||
font-size: 14px;
|
||
}
|
||
|
||
&:hover {
|
||
color: var(--clm-text);
|
||
background-color: var(--clm-hover);
|
||
}
|
||
|
||
&.add-btn,
|
||
&.refresh-btn {
|
||
color: var(--clm-muted);
|
||
|
||
:deep(.arco-icon),
|
||
:deep(svg) {
|
||
font-size: 18px !important;
|
||
width: 18px !important;
|
||
height: 18px !important;
|
||
min-width: 18px !important;
|
||
min-height: 18px !important;
|
||
}
|
||
|
||
&:hover {
|
||
color: var(--clm-text);
|
||
background-color: var(--clm-hover);
|
||
}
|
||
}
|
||
}
|
||
|
||
.interface-tree {
|
||
flex: 1;
|
||
min-height: 0;
|
||
padding: 6px 2px;
|
||
overflow-x: hidden;
|
||
overflow-y: auto;
|
||
background: transparent;
|
||
|
||
&:not(.interface-tree--hierarchical) {
|
||
:deep(.arco-tree),
|
||
:deep(.arco-tree-show-line),
|
||
:deep(.arco-tree-size-medium),
|
||
:deep(.arco-tree-node),
|
||
:deep(.arco-tree-node-content),
|
||
:deep(.arco-tree-node-content-wrapper) {
|
||
display: contents !important;
|
||
padding: 0 !important;
|
||
margin: 0 !important;
|
||
background: transparent !important;
|
||
border: none !important;
|
||
box-shadow: none !important;
|
||
}
|
||
|
||
:deep(.arco-tree-node-title),
|
||
:deep(.arco-tree-node-title-text) {
|
||
display: block !important;
|
||
width: 100%;
|
||
padding: 0 !important;
|
||
margin: 0 !important;
|
||
line-height: 1.25;
|
||
min-height: 0;
|
||
background: transparent !important;
|
||
border: none !important;
|
||
box-shadow: none !important;
|
||
}
|
||
|
||
:deep(.arco-tree-node-icon),
|
||
:deep(.arco-tree-node-indent),
|
||
:deep(.arco-tree-node-switcher),
|
||
:deep(.arco-icon),
|
||
:deep(.arco-tree-node-title .arco-icon),
|
||
:deep(.arco-tree-node-line),
|
||
:deep(.arco-tree-node-line-top),
|
||
:deep(.arco-tree-node-line-bottom) {
|
||
display: none !important;
|
||
}
|
||
|
||
:deep(.branch-node-extra .arco-icon) {
|
||
display: inline-block !important;
|
||
width: 14px !important;
|
||
height: 14px !important;
|
||
min-width: 14px !important;
|
||
flex: none !important;
|
||
font-size: 14px;
|
||
vertical-align: middle;
|
||
}
|
||
|
||
:deep(.arco-tree-node-wrapper),
|
||
:deep(.arco-tree-node-selected),
|
||
:deep(.arco-tree-node-selected > .arco-tree-node-title) {
|
||
display: contents !important;
|
||
padding: 0 !important;
|
||
margin: 0 !important;
|
||
background: transparent !important;
|
||
border: none !important;
|
||
}
|
||
|
||
:deep(.arco-tree-node-content) {
|
||
display: block !important;
|
||
}
|
||
|
||
:deep(.arco-tree-list),
|
||
:deep(.arco-tree-list-holder) {
|
||
display: contents !important;
|
||
padding: 0 !important;
|
||
margin: 0 !important;
|
||
}
|
||
}
|
||
|
||
&.interface-tree--hierarchical {
|
||
padding-right: 2px;
|
||
|
||
:deep(.arco-tree-node) {
|
||
align-items: flex-start;
|
||
}
|
||
|
||
:deep(.arco-tree-node-switcher) {
|
||
display: inline-flex !important;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 28px;
|
||
color: var(--clm-muted);
|
||
margin-top: 4px;
|
||
}
|
||
|
||
:deep(.arco-tree-node-indent-block) {
|
||
display: inline-block !important;
|
||
}
|
||
|
||
:deep(.arco-tree-node-title),
|
||
:deep(.arco-tree-node-title-text),
|
||
:deep(.arco-tree-node-content),
|
||
:deep(.arco-tree-node-content-wrapper) {
|
||
flex: 1;
|
||
min-width: 0;
|
||
width: 100%;
|
||
padding: 0 !important;
|
||
background: transparent !important;
|
||
}
|
||
|
||
:deep(.arco-tree-node-title) {
|
||
cursor: pointer;
|
||
padding: 0 !important;
|
||
line-height: 1.25;
|
||
min-height: 0;
|
||
}
|
||
|
||
:deep(.arco-tree-node-title-text) {
|
||
display: block;
|
||
width: 100%;
|
||
line-height: 1.25;
|
||
min-height: 0;
|
||
}
|
||
|
||
:deep(.arco-tree-node-selected .arco-tree-node-title),
|
||
:deep(.arco-tree-node-selected .arco-tree-node-content) {
|
||
background: transparent !important;
|
||
}
|
||
|
||
:deep(.arco-tree-node-line),
|
||
:deep(.arco-tree-node-line-top),
|
||
:deep(.arco-tree-node-line-bottom) {
|
||
border-color: rgba(15, 16, 17, 0.08) !important;
|
||
}
|
||
|
||
.tree-node {
|
||
margin: calc(var(--clm-node-gap) / 2) 0;
|
||
padding: var(--clm-node-padding-y) 8px;
|
||
border-radius: var(--clm-radius);
|
||
border: 1px solid rgba(15, 16, 17, 0.06);
|
||
background: transparent;
|
||
box-sizing: border-box;
|
||
height: var(--clm-node-height);
|
||
min-height: var(--clm-node-height);
|
||
max-height: var(--clm-node-height);
|
||
overflow: hidden;
|
||
display: flex;
|
||
align-items: stretch;
|
||
|
||
&.tree-node--with-code {
|
||
min-height: var(--clm-node-height);
|
||
}
|
||
|
||
&.tree-node--branch {
|
||
height: auto;
|
||
min-height: 34px;
|
||
max-height: none;
|
||
margin: calc(var(--clm-node-gap) + 4px) 0 calc(var(--clm-node-gap) / 2);
|
||
padding: 6px 8px;
|
||
border-color: transparent;
|
||
background: transparent;
|
||
overflow: visible;
|
||
|
||
.node-second-row-spacer-cell {
|
||
display: none;
|
||
}
|
||
}
|
||
|
||
&.tree-node--leaf {
|
||
background: var(--color-fill-1);
|
||
}
|
||
|
||
&:hover {
|
||
background: var(--clm-hover);
|
||
border-color: rgba(15, 16, 17, 0.1);
|
||
}
|
||
|
||
&.tree-node--branch:hover {
|
||
background: transparent;
|
||
border-color: transparent;
|
||
}
|
||
|
||
&.selected {
|
||
background: var(--clm-selected) !important;
|
||
border-color: var(--clm-selected-border) !important;
|
||
color: var(--clm-text) !important;
|
||
box-shadow: none;
|
||
|
||
.node-title {
|
||
color: var(--clm-selected-text) !important;
|
||
font-weight: 560;
|
||
}
|
||
|
||
.node-subtitle {
|
||
color: var(--clm-muted) !important;
|
||
}
|
||
|
||
&.tree-node--branch {
|
||
background: transparent !important;
|
||
border-color: transparent !important;
|
||
|
||
.node-title {
|
||
color: var(--clm-muted) !important;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.node-title::before {
|
||
background: var(--app-primary-color);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.tree-node--depth-1.tree-node--branch .node-title {
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
letter-spacing: 0.02em;
|
||
color: var(--clm-muted);
|
||
}
|
||
|
||
.tree-node--depth-2 .node-title {
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.tree-node--depth-3,
|
||
.tree-node--depth-4,
|
||
.tree-node--depth-5,
|
||
.tree-node--depth-6 {
|
||
.node-title {
|
||
font-size: 11px;
|
||
font-weight: 450;
|
||
}
|
||
}
|
||
|
||
.tree-node--branch .node-title::before {
|
||
content: '';
|
||
display: inline-block;
|
||
width: 3px;
|
||
height: 12px;
|
||
margin-right: 8px;
|
||
border-radius: 2px;
|
||
background: color-mix(in srgb, var(--app-primary-color) 55%, var(--clm-faint));
|
||
vertical-align: -2px;
|
||
}
|
||
|
||
.node-subtitle {
|
||
color: var(--clm-muted);
|
||
font-size: 10px;
|
||
}
|
||
}
|
||
}
|
||
|
||
.interface-tree::-webkit-scrollbar {
|
||
width: 4px;
|
||
}
|
||
|
||
.interface-tree::-webkit-scrollbar-thumb {
|
||
background: rgba(15, 16, 17, 0.12);
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.interface-tree::-webkit-scrollbar-track {
|
||
background: transparent;
|
||
}
|
||
|
||
.menu-pagination {
|
||
flex-shrink: 0;
|
||
padding: 8px 4px 2px;
|
||
display: flex;
|
||
justify-content: center;
|
||
border-top: 1px solid var(--clm-border);
|
||
}
|
||
|
||
.menu-pagination-capsule {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 2px;
|
||
padding: 2px;
|
||
border-radius: var(--clm-radius);
|
||
background: transparent;
|
||
border: none;
|
||
box-shadow: none;
|
||
}
|
||
|
||
.page-nav-btn,
|
||
.page-number-btn {
|
||
border: none;
|
||
background: transparent;
|
||
color: var(--clm-muted);
|
||
cursor: pointer;
|
||
transition: background-color 0.12s ease, color 0.12s ease;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 12px;
|
||
line-height: 1;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.page-nav-btn {
|
||
width: 26px;
|
||
height: 26px;
|
||
border-radius: 6px;
|
||
|
||
&:hover:not(:disabled) {
|
||
background: var(--clm-hover);
|
||
color: var(--clm-text);
|
||
}
|
||
|
||
&:disabled {
|
||
opacity: 0.35;
|
||
cursor: not-allowed;
|
||
}
|
||
}
|
||
|
||
.page-number-group {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 2px;
|
||
padding: 0 2px;
|
||
}
|
||
|
||
.page-number-btn {
|
||
min-width: 26px;
|
||
height: 26px;
|
||
padding: 0 6px;
|
||
border-radius: 6px;
|
||
font-weight: 500;
|
||
|
||
&:hover {
|
||
background: var(--clm-hover);
|
||
color: var(--clm-text);
|
||
}
|
||
|
||
&.active {
|
||
background: var(--clm-selected);
|
||
color: var(--clm-selected-text);
|
||
font-weight: 600;
|
||
box-shadow: none;
|
||
}
|
||
}
|
||
|
||
.page-ellipsis {
|
||
min-width: 16px;
|
||
text-align: center;
|
||
color: var(--clm-faint);
|
||
font-size: 12px;
|
||
user-select: none;
|
||
}
|
||
|
||
.loading-state,
|
||
.empty-state {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 120px;
|
||
color: var(--clm-muted);
|
||
|
||
p {
|
||
margin: 8px 0 0;
|
||
font-size: 12px;
|
||
font-weight: 450;
|
||
}
|
||
}
|
||
|
||
.empty-state p {
|
||
margin-top: 0;
|
||
}
|
||
|
||
.tree-node {
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
border: 1px solid rgba(15, 16, 17, 0.06);
|
||
padding: var(--clm-node-padding-y) 8px;
|
||
margin: calc(var(--clm-node-gap) / 2) 0;
|
||
border-radius: var(--clm-radius);
|
||
transition: background-color 0.12s ease, border-color 0.12s ease;
|
||
cursor: pointer;
|
||
background: transparent;
|
||
position: relative;
|
||
height: var(--clm-node-height);
|
||
min-height: var(--clm-node-height);
|
||
max-height: var(--clm-node-height);
|
||
overflow: hidden;
|
||
display: flex;
|
||
align-items: stretch;
|
||
|
||
&.tree-node--with-code {
|
||
min-height: var(--clm-node-height);
|
||
}
|
||
|
||
/* 分组头:轻量、矮一些,和可点击实体卡片拉开层级 */
|
||
&.tree-node--branch {
|
||
height: auto;
|
||
min-height: 34px;
|
||
max-height: none;
|
||
margin: calc(var(--clm-node-gap) + 4px) 0 calc(var(--clm-node-gap) / 2);
|
||
padding: 6px 8px;
|
||
border-color: transparent;
|
||
background: transparent;
|
||
cursor: default;
|
||
overflow: visible;
|
||
|
||
.node-title {
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: var(--clm-muted);
|
||
letter-spacing: 0.02em;
|
||
}
|
||
|
||
.node-title::before {
|
||
content: '';
|
||
display: inline-block;
|
||
width: 3px;
|
||
height: 12px;
|
||
margin-right: 8px;
|
||
border-radius: 2px;
|
||
background: color-mix(in srgb, var(--app-primary-color) 55%, var(--clm-faint));
|
||
vertical-align: -2px;
|
||
}
|
||
|
||
.node-subtitle {
|
||
color: var(--clm-faint);
|
||
font-size: 11px;
|
||
}
|
||
|
||
.node-second-row-spacer-cell {
|
||
display: none;
|
||
}
|
||
|
||
.node-subtitle-cell {
|
||
padding-top: 2px;
|
||
padding-left: 11px;
|
||
}
|
||
|
||
&:hover {
|
||
background: transparent;
|
||
border-color: transparent;
|
||
}
|
||
|
||
&.selected {
|
||
background: transparent !important;
|
||
border-color: transparent !important;
|
||
box-shadow: none !important;
|
||
|
||
.node-title {
|
||
color: var(--clm-muted) !important;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.node-subtitle {
|
||
color: var(--clm-faint) !important;
|
||
}
|
||
|
||
.node-title::before {
|
||
background: var(--app-primary-color);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 实体叶子:固定高度,内容块垂直居中避免贴底 */
|
||
&.tree-node--leaf {
|
||
background: var(--color-fill-1);
|
||
border-color: rgba(15, 16, 17, 0.07);
|
||
align-items: center;
|
||
}
|
||
|
||
&:hover {
|
||
background: var(--clm-hover);
|
||
border-color: rgba(15, 16, 17, 0.1);
|
||
transform: none;
|
||
box-shadow: none;
|
||
|
||
.action-btn,
|
||
.vertical-dots-icon {
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
&.tree-node--leaf:hover {
|
||
background: var(--clm-hover);
|
||
}
|
||
}
|
||
|
||
.tree-node.selected {
|
||
background: var(--clm-selected);
|
||
border-color: var(--clm-selected-border);
|
||
box-shadow: none;
|
||
color: var(--clm-text);
|
||
|
||
.node-number {
|
||
color: var(--clm-muted);
|
||
}
|
||
|
||
.node-title {
|
||
color: var(--clm-selected-text);
|
||
font-weight: 560;
|
||
}
|
||
|
||
.interface-code {
|
||
color: var(--clm-muted);
|
||
}
|
||
|
||
.map-count {
|
||
color: var(--clm-muted);
|
||
background: rgba(15, 16, 17, 0.04);
|
||
}
|
||
|
||
.node-second-row-extra-slot {
|
||
color: var(--clm-muted) !important;
|
||
|
||
.create-time,
|
||
.update-time {
|
||
color: var(--clm-muted) !important;
|
||
}
|
||
}
|
||
|
||
.action-btn,
|
||
.vertical-dots-icon {
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
.node-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
table-layout: fixed;
|
||
height: auto;
|
||
}
|
||
|
||
.node-second-row > td {
|
||
padding-top: 2px;
|
||
}
|
||
|
||
.node-content-cell {
|
||
text-align: left;
|
||
padding: 0;
|
||
width: auto;
|
||
vertical-align: middle;
|
||
min-width: 0;
|
||
}
|
||
|
||
.node-main-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
min-width: 0;
|
||
width: 100%;
|
||
height: var(--clm-node-title-line-height);
|
||
min-height: var(--clm-node-title-line-height);
|
||
}
|
||
|
||
.node-code-cell {
|
||
text-align: left;
|
||
padding: 2px 0 0;
|
||
width: auto;
|
||
padding-left: 20px;
|
||
height: var(--clm-node-second-line-height);
|
||
vertical-align: middle;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.node-actions-cell {
|
||
text-align: right;
|
||
padding: 0;
|
||
width: 28px;
|
||
vertical-align: middle;
|
||
}
|
||
|
||
.branch-node-actions-cell {
|
||
width: auto;
|
||
min-width: 52px;
|
||
white-space: nowrap;
|
||
|
||
.branch-node-actions-row {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
gap: 2px;
|
||
}
|
||
|
||
.branch-node-inline-before {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
}
|
||
}
|
||
|
||
.branch-node-extra-cell {
|
||
width: 28px;
|
||
|
||
.branch-node-extra {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
|
||
:deep(.arco-btn) {
|
||
width: 22px !important;
|
||
min-width: 22px !important;
|
||
height: 22px !important;
|
||
min-height: 22px !important;
|
||
padding: 0 !important;
|
||
color: var(--clm-muted);
|
||
border-radius: 5px;
|
||
|
||
&:hover {
|
||
color: var(--clm-text);
|
||
background: var(--clm-hover);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.node-second-row-extra-cell {
|
||
text-align: right;
|
||
padding: 2px 0 0;
|
||
min-width: 50px;
|
||
width: auto;
|
||
height: var(--clm-node-second-line-height);
|
||
vertical-align: middle;
|
||
}
|
||
|
||
.node-second-row-spacer-cell {
|
||
height: var(--clm-node-second-line-height);
|
||
padding: 2px 0 0;
|
||
line-height: var(--clm-node-second-line-height);
|
||
border: none;
|
||
}
|
||
|
||
.node-second-row-extra-slot {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
color: var(--clm-muted);
|
||
font-size: 11px;
|
||
}
|
||
|
||
.node-title {
|
||
flex: 1;
|
||
min-width: 0;
|
||
font-size: 12px;
|
||
color: var(--clm-text);
|
||
font-weight: 510;
|
||
letter-spacing: -0.01em;
|
||
line-height: var(--clm-node-title-line-height);
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.map-count {
|
||
flex-shrink: 0;
|
||
font-size: 10px;
|
||
font-weight: 500;
|
||
color: var(--clm-muted);
|
||
min-width: 16px;
|
||
height: 16px;
|
||
padding: 0 5px;
|
||
border-radius: 999px;
|
||
background: rgba(15, 16, 17, 0.05);
|
||
text-align: center;
|
||
line-height: 16px;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
.node-number {
|
||
color: var(--clm-faint);
|
||
font-size: 11px;
|
||
font-weight: 500;
|
||
margin-right: 0;
|
||
display: inline-block;
|
||
min-width: 14px;
|
||
text-align: center;
|
||
font-variant-numeric: tabular-nums;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.post-method {
|
||
font-weight: 600;
|
||
font-size: 10px;
|
||
margin-right: 0;
|
||
display: inline-block;
|
||
padding: 0 4px;
|
||
border-radius: 3px;
|
||
border: none;
|
||
line-height: 14px;
|
||
letter-spacing: 0.02em;
|
||
flex-shrink: 0;
|
||
color: #d97706;
|
||
background: rgba(217, 119, 6, 0.12);
|
||
|
||
&--get {
|
||
color: #059669;
|
||
background: rgba(5, 150, 105, 0.12);
|
||
}
|
||
|
||
&--post {
|
||
color: #d97706;
|
||
background: rgba(217, 119, 6, 0.12);
|
||
}
|
||
|
||
&--put {
|
||
color: #2563eb;
|
||
background: rgba(37, 99, 235, 0.12);
|
||
}
|
||
|
||
&--patch {
|
||
color: #7c3aed;
|
||
background: rgba(124, 58, 237, 0.12);
|
||
}
|
||
|
||
&--delete {
|
||
color: #dc2626;
|
||
background: rgba(220, 38, 38, 0.12);
|
||
}
|
||
}
|
||
|
||
.interface-code {
|
||
color: var(--clm-muted);
|
||
font-size: 11px;
|
||
font-weight: 450;
|
||
display: inline-block;
|
||
line-height: var(--clm-node-second-line-height);
|
||
letter-spacing: -0.01em;
|
||
max-width: 100%;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.node-subtitle-cell {
|
||
text-align: left;
|
||
padding: 1px 0 0;
|
||
height: var(--clm-node-second-line-height);
|
||
vertical-align: middle;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.node-subtitle {
|
||
display: block;
|
||
font-size: 10px;
|
||
font-weight: 450;
|
||
color: var(--clm-muted);
|
||
line-height: var(--clm-node-second-line-height);
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.tree-node.selected .node-subtitle {
|
||
color: var(--clm-muted);
|
||
}
|
||
|
||
.action-btn {
|
||
padding: 0;
|
||
min-width: 20px;
|
||
height: 20px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
opacity: 0;
|
||
transition: opacity 0.12s ease;
|
||
border-radius: 5px;
|
||
|
||
&:hover {
|
||
background: rgba(15, 16, 17, 0.06);
|
||
}
|
||
|
||
:deep(.arco-icon) {
|
||
font-size: 12px;
|
||
}
|
||
}
|
||
|
||
.vertical-dots-icon {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 2px;
|
||
width: 6px;
|
||
height: 14px;
|
||
opacity: 0;
|
||
transition: opacity 0.12s ease;
|
||
}
|
||
|
||
.tree-node:hover .vertical-dots-icon,
|
||
.tree-node.selected .vertical-dots-icon,
|
||
.tree-node:hover .action-btn,
|
||
.tree-node.selected .action-btn {
|
||
opacity: 1;
|
||
}
|
||
|
||
.vertical-dots-icon span {
|
||
width: 3px;
|
||
height: 3px;
|
||
background-color: var(--clm-muted);
|
||
border-radius: 50%;
|
||
display: block;
|
||
}
|
||
|
||
.tree-node.selected .vertical-dots-icon span {
|
||
background-color: var(--clm-muted);
|
||
}
|
||
</style>
|