feat(command-management): 新增指令层级管理与UI展示
- 【后端】 - `CommandDescriptor` 新增 `parent_group_handler` 和 `sub_commands` 字段,支持指令层级结构定义。 - `list_commands` 函数重构,实现指令的层级收集与构建,将子指令正确挂载到其父指令组下。 - 新增 `_collect_all_descriptors` 和 `_find_parent_group_handler` 辅助函数,用于全面收集指令并定位父指令组。 - `_build_descriptor` 优化指令类型判断逻辑,明确区分普通指令、指令组和子指令。 - `_descriptor_to_dict` 递归处理子指令,确保 API 返回完整的指令层级数据。 - 【前端】 - 指令管理页面 (`CommandPage.vue`) 增加指令类型筛选器,并支持指令组的展开/折叠功能。 - 表格展示优化,为指令组和子指令添加不同的样式和缩进,提升层级结构的视觉可读性。 - 指令详情对话框新增指令类型、所属指令组和子指令列表的展示。 - 更新 `CommandItem` 接口,以适配后端提供的层级数据结构。 - 【i18n】 - 新增指令类型(指令、指令组、子指令)的国际化文本。 - 更新指令管理相关 UI 文本,包括表格头部、详情对话框字段和筛选器选项。
This commit is contained in:
@@ -26,10 +26,11 @@ class CommandDescriptor:
|
||||
plugin_display_name: str | None = None
|
||||
module_path: str = ""
|
||||
description: str = ""
|
||||
command_type: str = "command"
|
||||
command_type: str = "command" # "command" | "group" | "sub_command"
|
||||
raw_command_name: str | None = None
|
||||
current_fragment: str | None = None
|
||||
parent_signature: str = ""
|
||||
parent_group_handler: str = "" # 父指令组的 handler_full_name
|
||||
original_command: str | None = None
|
||||
effective_command: str | None = None
|
||||
aliases: list[str] = field(default_factory=list)
|
||||
@@ -39,6 +40,7 @@ class CommandDescriptor:
|
||||
is_sub_command: bool = False
|
||||
config: CommandConfig | None = None
|
||||
has_conflict: bool = False
|
||||
sub_commands: list[CommandDescriptor] = field(default_factory=list)
|
||||
|
||||
|
||||
async def sync_command_configs() -> None:
|
||||
@@ -127,7 +129,7 @@ async def rename_command(
|
||||
|
||||
|
||||
async def list_commands() -> list[dict[str, Any]]:
|
||||
descriptors = _collect_raw_descriptors()
|
||||
descriptors = _collect_all_descriptors()
|
||||
config_records = await db_helper.get_command_configs()
|
||||
config_map = {cfg.handler_full_name: cfg for cfg in config_records}
|
||||
|
||||
@@ -135,7 +137,7 @@ async def list_commands() -> list[dict[str, Any]]:
|
||||
if cfg := config_map.get(desc.handler_full_name):
|
||||
_bind_descriptor_with_config(desc, cfg)
|
||||
|
||||
# 检测冲突:按 effective_command 分组
|
||||
# 检测冲突:按 effective_command 分组(只检测已启用的指令)
|
||||
conflict_groups: dict[str, list[CommandDescriptor]] = defaultdict(list)
|
||||
for desc in descriptors:
|
||||
if desc.effective_command and desc.enabled:
|
||||
@@ -147,10 +149,36 @@ async def list_commands() -> list[dict[str, Any]]:
|
||||
for desc in group:
|
||||
conflict_handler_names.add(desc.handler_full_name)
|
||||
|
||||
result = []
|
||||
# 构建层级结构:将子指令挂载到父指令组
|
||||
group_map: dict[str, CommandDescriptor] = {}
|
||||
sub_commands: list[CommandDescriptor] = []
|
||||
root_commands: list[CommandDescriptor] = []
|
||||
|
||||
for desc in descriptors:
|
||||
desc.has_conflict = desc.handler_full_name in conflict_handler_names
|
||||
if desc.is_group:
|
||||
group_map[desc.handler_full_name] = desc
|
||||
elif desc.is_sub_command:
|
||||
sub_commands.append(desc)
|
||||
else:
|
||||
root_commands.append(desc)
|
||||
|
||||
# 将子指令挂载到对应的指令组
|
||||
for sub in sub_commands:
|
||||
sub.has_conflict = sub.handler_full_name in conflict_handler_names
|
||||
if sub.parent_group_handler and sub.parent_group_handler in group_map:
|
||||
group_map[sub.parent_group_handler].sub_commands.append(sub)
|
||||
else:
|
||||
# 如果找不到父指令组,作为独立指令处理
|
||||
root_commands.append(sub)
|
||||
|
||||
# 合并结果:指令组(含子指令)+ 普通指令
|
||||
result = []
|
||||
for desc in group_map.values():
|
||||
result.append(_descriptor_to_dict(desc))
|
||||
for desc in root_commands:
|
||||
result.append(_descriptor_to_dict(desc))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@@ -193,6 +221,7 @@ async def list_command_conflicts() -> list[dict[str, Any]]:
|
||||
|
||||
|
||||
def _collect_raw_descriptors() -> list[CommandDescriptor]:
|
||||
"""收集所有根级指令(不含子指令)。"""
|
||||
descriptors: list[CommandDescriptor] = []
|
||||
for handler in star_handlers_registry:
|
||||
desc = _build_descriptor(handler)
|
||||
@@ -202,6 +231,17 @@ def _collect_raw_descriptors() -> list[CommandDescriptor]:
|
||||
return descriptors
|
||||
|
||||
|
||||
def _collect_all_descriptors() -> list[CommandDescriptor]:
|
||||
"""收集所有指令,包括子指令。"""
|
||||
descriptors: list[CommandDescriptor] = []
|
||||
for handler in star_handlers_registry:
|
||||
desc = _build_descriptor(handler)
|
||||
if not desc:
|
||||
continue
|
||||
descriptors.append(desc)
|
||||
return descriptors
|
||||
|
||||
|
||||
def _build_descriptor(handler: StarHandlerMetadata) -> CommandDescriptor | None:
|
||||
filter_ref = _locate_primary_filter(handler)
|
||||
if filter_ref is None:
|
||||
@@ -213,12 +253,20 @@ def _build_descriptor(handler: StarHandlerMetadata) -> CommandDescriptor | None:
|
||||
) or handler.handler_module_path
|
||||
plugin_display = plugin_meta.display_name if plugin_meta else None
|
||||
|
||||
is_sub_command = bool(handler.extras_configs.get("sub_command"))
|
||||
parent_group_handler = ""
|
||||
|
||||
if isinstance(filter_ref, CommandFilter):
|
||||
raw_fragment = getattr(
|
||||
filter_ref, "_original_command_name", filter_ref.command_name
|
||||
)
|
||||
current_fragment = filter_ref.command_name
|
||||
parent_signature = (filter_ref.parent_command_names or [""])[0].strip()
|
||||
# 如果是子指令,尝试找到父指令组的 handler_full_name
|
||||
if is_sub_command and parent_signature:
|
||||
parent_group_handler = _find_parent_group_handler(
|
||||
handler.handler_module_path, parent_signature
|
||||
)
|
||||
else:
|
||||
raw_fragment = getattr(
|
||||
filter_ref, "_original_group_name", filter_ref.group_name
|
||||
@@ -229,6 +277,14 @@ def _build_descriptor(handler: StarHandlerMetadata) -> CommandDescriptor | None:
|
||||
original_command = _compose_command(parent_signature, raw_fragment)
|
||||
effective_command = _compose_command(parent_signature, current_fragment)
|
||||
|
||||
# 确定 command_type
|
||||
if isinstance(filter_ref, CommandGroupFilter):
|
||||
command_type = "group"
|
||||
elif is_sub_command:
|
||||
command_type = "sub_command"
|
||||
else:
|
||||
command_type = "command"
|
||||
|
||||
descriptor = CommandDescriptor(
|
||||
handler=handler,
|
||||
filter_ref=filter_ref,
|
||||
@@ -238,19 +294,18 @@ def _build_descriptor(handler: StarHandlerMetadata) -> CommandDescriptor | None:
|
||||
plugin_display_name=plugin_display,
|
||||
module_path=handler.handler_module_path,
|
||||
description=handler.desc or "",
|
||||
command_type="group"
|
||||
if isinstance(filter_ref, CommandGroupFilter)
|
||||
else "command",
|
||||
command_type=command_type,
|
||||
raw_command_name=raw_fragment,
|
||||
current_fragment=current_fragment,
|
||||
parent_signature=parent_signature,
|
||||
parent_group_handler=parent_group_handler,
|
||||
original_command=original_command,
|
||||
effective_command=effective_command,
|
||||
aliases=sorted(getattr(filter_ref, "alias", set())),
|
||||
permission=_determine_permission(handler),
|
||||
enabled=handler.enabled,
|
||||
is_group=isinstance(filter_ref, CommandGroupFilter),
|
||||
is_sub_command=bool(handler.extras_configs.get("sub_command")),
|
||||
is_sub_command=is_sub_command,
|
||||
)
|
||||
return descriptor
|
||||
|
||||
@@ -291,6 +346,22 @@ def _resolve_group_parent_signature(group_filter: CommandGroupFilter) -> str:
|
||||
return " ".join(reversed(signatures)).strip()
|
||||
|
||||
|
||||
def _find_parent_group_handler(module_path: str, parent_signature: str) -> str:
|
||||
"""根据模块路径和父级签名,找到对应的指令组 handler_full_name。"""
|
||||
parent_sig_normalized = parent_signature.strip()
|
||||
for handler in star_handlers_registry:
|
||||
if handler.handler_module_path != module_path:
|
||||
continue
|
||||
filter_ref = _locate_primary_filter(handler)
|
||||
if not isinstance(filter_ref, CommandGroupFilter):
|
||||
continue
|
||||
# 检查该指令组的完整指令名是否匹配 parent_signature
|
||||
group_names = filter_ref.get_complete_command_names()
|
||||
if parent_sig_normalized in group_names:
|
||||
return handler.handler_full_name
|
||||
return ""
|
||||
|
||||
|
||||
def _compose_command(parent_signature: str, fragment: str | None) -> str:
|
||||
fragment = (fragment or "").strip()
|
||||
parent_signature = parent_signature.strip()
|
||||
@@ -353,7 +424,7 @@ def _is_command_in_use(
|
||||
|
||||
|
||||
def _descriptor_to_dict(desc: CommandDescriptor) -> dict[str, Any]:
|
||||
return {
|
||||
result = {
|
||||
"handler_full_name": desc.handler_full_name,
|
||||
"handler_name": desc.handler_name,
|
||||
"plugin": desc.plugin_name,
|
||||
@@ -362,6 +433,7 @@ def _descriptor_to_dict(desc: CommandDescriptor) -> dict[str, Any]:
|
||||
"description": desc.description,
|
||||
"type": desc.command_type,
|
||||
"parent_signature": desc.parent_signature,
|
||||
"parent_group_handler": desc.parent_group_handler,
|
||||
"original_command": desc.original_command,
|
||||
"current_fragment": desc.current_fragment,
|
||||
"effective_command": desc.effective_command,
|
||||
@@ -371,3 +443,9 @@ def _descriptor_to_dict(desc: CommandDescriptor) -> dict[str, Any]:
|
||||
"is_group": desc.is_group,
|
||||
"has_conflict": desc.has_conflict,
|
||||
}
|
||||
# 如果是指令组,包含子指令列表
|
||||
if desc.is_group and desc.sub_commands:
|
||||
result["sub_commands"] = [_descriptor_to_dict(sub) for sub in desc.sub_commands]
|
||||
else:
|
||||
result["sub_commands"] = []
|
||||
return result
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"table": {
|
||||
"headers": {
|
||||
"command": "Command",
|
||||
"type": "Type",
|
||||
"plugin": "Plugin",
|
||||
"description": "Description",
|
||||
"permission": "Permission",
|
||||
@@ -20,6 +21,12 @@
|
||||
"actions": "Actions"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"command": "Command",
|
||||
"group": "Group",
|
||||
"subCommand": "Sub-command"
|
||||
},
|
||||
"subCommandCount": "sub-commands",
|
||||
"status": {
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled",
|
||||
@@ -44,10 +51,13 @@
|
||||
},
|
||||
"details": {
|
||||
"title": "Command Details",
|
||||
"type": "Command Type",
|
||||
"handler": "Handler",
|
||||
"module": "Module Path",
|
||||
"originalCommand": "Original Command",
|
||||
"effectiveCommand": "Effective Command",
|
||||
"parentGroup": "Parent Group",
|
||||
"subCommands": "Sub-commands",
|
||||
"aliases": "Aliases",
|
||||
"permission": "Permission",
|
||||
"conflictStatus": "Conflict Status"
|
||||
@@ -73,6 +83,7 @@
|
||||
"disabled": "Disabled",
|
||||
"conflict": "Conflict",
|
||||
"byPlugin": "Filter by plugin",
|
||||
"byType": "Filter by type",
|
||||
"byPermission": "Filter by permission",
|
||||
"byStatus": "Filter by status"
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"table": {
|
||||
"headers": {
|
||||
"command": "指令",
|
||||
"type": "类型",
|
||||
"plugin": "所属插件",
|
||||
"description": "描述",
|
||||
"permission": "权限",
|
||||
@@ -20,6 +21,12 @@
|
||||
"actions": "操作"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"command": "指令",
|
||||
"group": "指令组",
|
||||
"subCommand": "子指令"
|
||||
},
|
||||
"subCommandCount": "个子指令",
|
||||
"status": {
|
||||
"enabled": "已启用",
|
||||
"disabled": "已禁用",
|
||||
@@ -44,10 +51,13 @@
|
||||
},
|
||||
"details": {
|
||||
"title": "指令详情",
|
||||
"type": "指令类型",
|
||||
"handler": "处理函数",
|
||||
"module": "模块路径",
|
||||
"originalCommand": "原始指令",
|
||||
"effectiveCommand": "生效指令",
|
||||
"parentGroup": "所属指令组",
|
||||
"subCommands": "子指令列表",
|
||||
"aliases": "别名",
|
||||
"permission": "权限要求",
|
||||
"conflictStatus": "冲突状态"
|
||||
@@ -73,6 +83,7 @@
|
||||
"disabled": "已禁用",
|
||||
"conflict": "有冲突",
|
||||
"byPlugin": "按插件筛选",
|
||||
"byType": "按类型筛选",
|
||||
"byPermission": "按权限筛选",
|
||||
"byStatus": "按状态筛选"
|
||||
}
|
||||
|
||||
@@ -10,8 +10,9 @@ interface CommandItem {
|
||||
plugin_display_name: string | null;
|
||||
module_path: string;
|
||||
description: string;
|
||||
type: string;
|
||||
type: string; // "command" | "group" | "sub_command"
|
||||
parent_signature: string;
|
||||
parent_group_handler: string;
|
||||
original_command: string;
|
||||
current_fragment: string;
|
||||
effective_command: string;
|
||||
@@ -20,6 +21,7 @@ interface CommandItem {
|
||||
enabled: boolean;
|
||||
is_group: boolean;
|
||||
has_conflict: boolean;
|
||||
sub_commands: CommandItem[];
|
||||
}
|
||||
|
||||
interface CommandSummary {
|
||||
@@ -49,6 +51,10 @@ const searchQuery = ref('');
|
||||
const pluginFilter = ref('all');
|
||||
const permissionFilter = ref('all');
|
||||
const statusFilter = ref('all');
|
||||
const typeFilter = ref('all');
|
||||
|
||||
// Track expanded groups
|
||||
const expandedGroups = ref<Set<string>>(new Set());
|
||||
|
||||
// Rename dialog
|
||||
const renameDialog = reactive({
|
||||
@@ -66,12 +72,13 @@ const detailsDialog = reactive({
|
||||
|
||||
// Table headers
|
||||
const commandHeaders = computed(() => [
|
||||
{ title: tm('table.headers.command'), key: 'effective_command', width: '180px' },
|
||||
{ title: tm('table.headers.command'), key: 'effective_command', width: '150px' },
|
||||
{ title: tm('table.headers.type'), key: 'type', sortable: false, width: '110px' },
|
||||
{ title: tm('table.headers.plugin'), key: 'plugin', width: '140px' },
|
||||
{ title: tm('table.headers.description'), key: 'description', sortable: false, maxWidth: '260px' },
|
||||
{ title: tm('table.headers.description'), key: 'description', sortable: false, maxWidth: '240px' },
|
||||
{ title: tm('table.headers.permission'), key: 'permission', sortable: false, width: '100px' },
|
||||
{ title: tm('table.headers.status'), key: 'enabled', sortable: false, width: '120px' },
|
||||
{ title: tm('table.headers.actions'), key: 'actions', sortable: false, width: '160px' }
|
||||
{ title: tm('table.headers.status'), key: 'enabled', sortable: false, width: '100px' },
|
||||
{ title: tm('table.headers.actions'), key: 'actions', sortable: false, width: '140px' }
|
||||
]);
|
||||
|
||||
// Computed: unique plugins for filter
|
||||
@@ -80,66 +87,126 @@ const availablePlugins = computed(() => {
|
||||
return Array.from(plugins).sort();
|
||||
});
|
||||
|
||||
// Computed: filtered commands
|
||||
const filteredCommands = computed(() => {
|
||||
let result = commands.value;
|
||||
|
||||
if (searchQuery.value) {
|
||||
const query = searchQuery.value.toLowerCase();
|
||||
result = result.filter(cmd =>
|
||||
// Helper: check if a command matches filters
|
||||
const matchesFilters = (cmd: CommandItem, query: string): boolean => {
|
||||
// Search filter
|
||||
if (query) {
|
||||
const matchesSearch =
|
||||
cmd.effective_command?.toLowerCase().includes(query) ||
|
||||
cmd.description?.toLowerCase().includes(query) ||
|
||||
cmd.plugin?.toLowerCase().includes(query)
|
||||
);
|
||||
cmd.plugin?.toLowerCase().includes(query);
|
||||
if (!matchesSearch) return false;
|
||||
}
|
||||
|
||||
if (pluginFilter.value !== 'all') {
|
||||
result = result.filter(cmd => cmd.plugin === pluginFilter.value);
|
||||
// Plugin filter
|
||||
if (pluginFilter.value !== 'all' && cmd.plugin !== pluginFilter.value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Permission filter
|
||||
if (permissionFilter.value !== 'all') {
|
||||
if (permissionFilter.value === 'everyone') {
|
||||
// "所有人"筛选:包括 everyone 和 member 权限(当前 member 权限实际作用与 everyone 相同)
|
||||
result = result.filter(cmd => cmd.permission === 'everyone' || cmd.permission === 'member');
|
||||
} else {
|
||||
result = result.filter(cmd => cmd.permission === permissionFilter.value);
|
||||
if (cmd.permission !== 'everyone' && cmd.permission !== 'member') return false;
|
||||
} else if (cmd.permission !== permissionFilter.value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Status filter
|
||||
if (statusFilter.value !== 'all') {
|
||||
if (statusFilter.value === 'enabled') {
|
||||
result = result.filter(cmd => cmd.enabled);
|
||||
} else if (statusFilter.value === 'disabled') {
|
||||
result = result.filter(cmd => !cmd.enabled);
|
||||
} else if (statusFilter.value === 'conflict') {
|
||||
result = result.filter(cmd => cmd.has_conflict);
|
||||
}
|
||||
if (statusFilter.value === 'enabled' && !cmd.enabled) return false;
|
||||
if (statusFilter.value === 'disabled' && cmd.enabled) return false;
|
||||
if (statusFilter.value === 'conflict' && !cmd.has_conflict) return false;
|
||||
}
|
||||
|
||||
// Sort: conflict commands first, grouped by effective_command
|
||||
// Type filter
|
||||
if (typeFilter.value !== 'all') {
|
||||
if (typeFilter.value === 'group' && cmd.type !== 'group') return false;
|
||||
if (typeFilter.value === 'command' && cmd.type !== 'command') return false;
|
||||
if (typeFilter.value === 'sub_command' && cmd.type !== 'sub_command') return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// Computed: filtered commands with hierarchy support
|
||||
const filteredCommands = computed(() => {
|
||||
const query = searchQuery.value.toLowerCase();
|
||||
const result: CommandItem[] = [];
|
||||
const conflictCmds: CommandItem[] = [];
|
||||
const normalCmds: CommandItem[] = [];
|
||||
|
||||
const conflictGroupMap: Map<string, CommandItem[]> = new Map();
|
||||
for (const cmd of result) {
|
||||
if (cmd.has_conflict) {
|
||||
const key = cmd.effective_command || '';
|
||||
if (!conflictGroupMap.has(key)) {
|
||||
conflictGroupMap.set(key, []);
|
||||
|
||||
for (const cmd of commands.value) {
|
||||
// For groups, check if group or any sub-command matches
|
||||
if (cmd.is_group) {
|
||||
const groupMatches = matchesFilters(cmd, query);
|
||||
const matchingSubCmds = (cmd.sub_commands || []).filter(sub => matchesFilters(sub, query));
|
||||
|
||||
// If group matches or has matching sub-commands, include it
|
||||
if (groupMatches || matchingSubCmds.length > 0) {
|
||||
if (cmd.has_conflict) {
|
||||
conflictCmds.push(cmd);
|
||||
} else {
|
||||
normalCmds.push(cmd);
|
||||
}
|
||||
|
||||
// If group is expanded, add matching sub-commands
|
||||
if (expandedGroups.value.has(cmd.handler_full_name)) {
|
||||
const subsToShow = query ? matchingSubCmds : (cmd.sub_commands || []);
|
||||
for (const sub of subsToShow) {
|
||||
if (sub.has_conflict) {
|
||||
conflictCmds.push(sub);
|
||||
} else {
|
||||
normalCmds.push(sub);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (cmd.type !== 'sub_command') {
|
||||
// Regular commands (not sub-commands, they're handled via groups)
|
||||
if (matchesFilters(cmd, query)) {
|
||||
if (cmd.has_conflict) {
|
||||
conflictCmds.push(cmd);
|
||||
} else {
|
||||
normalCmds.push(cmd);
|
||||
}
|
||||
}
|
||||
conflictGroupMap.get(key)!.push(cmd);
|
||||
} else {
|
||||
normalCmds.push(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [_, group] of conflictGroupMap) {
|
||||
conflictCmds.push(...group);
|
||||
}
|
||||
|
||||
// Sort conflicts by effective_command to group them together
|
||||
conflictCmds.sort((a, b) => (a.effective_command || '').localeCompare(b.effective_command || ''));
|
||||
|
||||
return [...conflictCmds, ...normalCmds];
|
||||
});
|
||||
|
||||
// Toggle group expansion
|
||||
const toggleGroupExpand = (cmd: CommandItem) => {
|
||||
if (!cmd.is_group) return;
|
||||
if (expandedGroups.value.has(cmd.handler_full_name)) {
|
||||
expandedGroups.value.delete(cmd.handler_full_name);
|
||||
} else {
|
||||
expandedGroups.value.add(cmd.handler_full_name);
|
||||
}
|
||||
};
|
||||
|
||||
// Check if group is expanded
|
||||
const isGroupExpanded = (cmd: CommandItem): boolean => {
|
||||
return expandedGroups.value.has(cmd.handler_full_name);
|
||||
};
|
||||
|
||||
// Get type display info
|
||||
const getTypeInfo = (type: string) => {
|
||||
switch (type) {
|
||||
case 'group':
|
||||
return { text: tm('type.group'), color: 'info', icon: 'mdi-folder-outline' };
|
||||
case 'sub_command':
|
||||
return { text: tm('type.subCommand'), color: 'secondary', icon: 'mdi-subdirectory-arrow-right' };
|
||||
default:
|
||||
return { text: tm('type.command'), color: 'primary', icon: 'mdi-console-line' };
|
||||
}
|
||||
};
|
||||
|
||||
// Toast helper
|
||||
const toast = (message: string, color: string = 'success') => {
|
||||
snackbar.message = message;
|
||||
@@ -250,12 +317,19 @@ const getStatusInfo = (cmd: CommandItem) => {
|
||||
return { text: tm('status.disabled'), color: 'error', variant: 'outlined' as const };
|
||||
};
|
||||
|
||||
// Get row props for conflict highlighting
|
||||
// Get row props for conflict highlighting and sub-command styling
|
||||
const getRowProps = ({ item }: { item: CommandItem }) => {
|
||||
const classes: string[] = [];
|
||||
if (item.has_conflict) {
|
||||
return { class: 'conflict-row' };
|
||||
classes.push('conflict-row');
|
||||
}
|
||||
return {};
|
||||
if (item.type === 'sub_command') {
|
||||
classes.push('sub-command-row');
|
||||
}
|
||||
if (item.is_group) {
|
||||
classes.push('group-row');
|
||||
}
|
||||
return classes.length > 0 ? { class: classes.join(' ') } : {};
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
@@ -270,7 +344,7 @@ onMounted(async () => {
|
||||
<v-card-text style="padding: 0px 12px;">
|
||||
<!-- Filters Row (Top) -->
|
||||
<v-row class="mb-4" align="center">
|
||||
<v-col cols="12" sm="4" md="3">
|
||||
<v-col cols="12" sm="6" md="3">
|
||||
<v-select
|
||||
v-model="pluginFilter"
|
||||
:items="[{ title: tm('filters.all'), value: 'all' }, ...availablePlugins.map(p => ({ title: p, value: p }))]"
|
||||
@@ -280,7 +354,22 @@ onMounted(async () => {
|
||||
hide-details
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="4" md="3">
|
||||
<v-col cols="12" sm="6" md="2">
|
||||
<v-select
|
||||
v-model="typeFilter"
|
||||
:items="[
|
||||
{ title: tm('filters.all'), value: 'all' },
|
||||
{ title: tm('type.group'), value: 'group' },
|
||||
{ title: tm('type.command'), value: 'command' },
|
||||
{ title: tm('type.subCommand'), value: 'sub_command' }
|
||||
]"
|
||||
:label="tm('filters.byType')"
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="2">
|
||||
<v-select
|
||||
v-model="permissionFilter"
|
||||
:items="[
|
||||
@@ -294,7 +383,7 @@ onMounted(async () => {
|
||||
hide-details
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="4" md="3">
|
||||
<v-col cols="12" sm="6" md="2">
|
||||
<v-select
|
||||
v-model="statusFilter"
|
||||
:items="[
|
||||
@@ -383,14 +472,47 @@ onMounted(async () => {
|
||||
|
||||
<template v-slot:item.effective_command="{ item }">
|
||||
<div class="d-flex align-center py-2">
|
||||
<!-- Expand/collapse button for groups -->
|
||||
<v-btn
|
||||
v-if="item.is_group && item.sub_commands?.length > 0"
|
||||
icon
|
||||
variant="text"
|
||||
size="x-small"
|
||||
class="mr-1"
|
||||
@click.stop="toggleGroupExpand(item)"
|
||||
>
|
||||
<v-icon size="18">{{ isGroupExpanded(item) ? 'mdi-chevron-down' : 'mdi-chevron-right' }}</v-icon>
|
||||
</v-btn>
|
||||
<!-- Indent for sub-commands -->
|
||||
<div v-else-if="item.type === 'sub_command'" class="ml-6"></div>
|
||||
<div>
|
||||
<div class="text-subtitle-1 font-weight-medium">
|
||||
<code>{{ item.effective_command }}</code>
|
||||
<code :class="{ 'sub-command-code': item.type === 'sub_command' }">{{ item.effective_command }}</code>
|
||||
<v-chip
|
||||
v-if="item.is_group && item.sub_commands?.length > 0"
|
||||
size="x-small"
|
||||
color="info"
|
||||
variant="tonal"
|
||||
class="ml-2"
|
||||
>
|
||||
{{ item.sub_commands.length }} {{ tm('subCommandCount') }}
|
||||
</v-chip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-slot:item.type="{ item }">
|
||||
<v-chip
|
||||
:color="getTypeInfo(item.type).color"
|
||||
size="small"
|
||||
variant="tonal"
|
||||
>
|
||||
<v-icon start size="14">{{ getTypeInfo(item.type).icon }}</v-icon>
|
||||
{{ getTypeInfo(item.type).text }}
|
||||
</v-chip>
|
||||
</template>
|
||||
|
||||
<template v-slot:item.plugin="{ item }">
|
||||
<div class="text-body-2">{{ item.plugin_display_name || item.plugin }}</div>
|
||||
</template>
|
||||
@@ -505,6 +627,19 @@ onMounted(async () => {
|
||||
<v-card-title class="text-h5">{{ tm('dialogs.details.title') }}</v-card-title>
|
||||
<v-card-text>
|
||||
<v-list density="compact">
|
||||
<v-list-item>
|
||||
<v-list-item-title class="font-weight-bold">{{ tm('dialogs.details.type') }}</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
<v-chip
|
||||
:color="getTypeInfo(detailsDialog.command.type).color"
|
||||
size="small"
|
||||
variant="tonal"
|
||||
>
|
||||
<v-icon start size="14">{{ getTypeInfo(detailsDialog.command.type).icon }}</v-icon>
|
||||
{{ getTypeInfo(detailsDialog.command.type).text }}
|
||||
</v-chip>
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-list-item-title class="font-weight-bold">{{ tm('dialogs.details.handler') }}</v-list-item-title>
|
||||
<v-list-item-subtitle><code>{{ detailsDialog.command.handler_name }}</code></v-list-item-subtitle>
|
||||
@@ -521,6 +656,10 @@ onMounted(async () => {
|
||||
<v-list-item-title class="font-weight-bold">{{ tm('dialogs.details.effectiveCommand') }}</v-list-item-title>
|
||||
<v-list-item-subtitle><code>{{ detailsDialog.command.effective_command }}</code></v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
<v-list-item v-if="detailsDialog.command.parent_signature">
|
||||
<v-list-item-title class="font-weight-bold">{{ tm('dialogs.details.parentGroup') }}</v-list-item-title>
|
||||
<v-list-item-subtitle><code>{{ detailsDialog.command.parent_signature }}</code></v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
<v-list-item v-if="detailsDialog.command.aliases.length > 0">
|
||||
<v-list-item-title class="font-weight-bold">{{ tm('dialogs.details.aliases') }}</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
@@ -529,6 +668,21 @@ onMounted(async () => {
|
||||
</v-chip>
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
<v-list-item v-if="detailsDialog.command.is_group && detailsDialog.command.sub_commands?.length > 0">
|
||||
<v-list-item-title class="font-weight-bold">{{ tm('dialogs.details.subCommands') }}</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
<div class="d-flex flex-wrap ga-1 mt-1">
|
||||
<v-chip
|
||||
v-for="sub in detailsDialog.command.sub_commands"
|
||||
:key="sub.handler_full_name"
|
||||
size="small"
|
||||
variant="outlined"
|
||||
>
|
||||
{{ sub.current_fragment }}
|
||||
</v-chip>
|
||||
</div>
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-list-item-title class="font-weight-bold">{{ tm('dialogs.details.permission') }}</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
@@ -567,6 +721,11 @@ code {
|
||||
border-radius: 4px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
code.sub-command-code {
|
||||
background-color: rgba(var(--v-theme-secondary), 0.1);
|
||||
color: rgb(var(--v-theme-secondary));
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
@@ -579,4 +738,23 @@ code {
|
||||
.v-data-table .conflict-row:hover {
|
||||
background: linear-gradient(90deg, rgba(var(--v-theme-warning), 0.25) 0%, rgba(var(--v-theme-warning), 0.1) 100%) !important;
|
||||
}
|
||||
|
||||
/* Group row styling */
|
||||
.v-data-table .group-row {
|
||||
background-color: rgba(var(--v-theme-info), 0.03);
|
||||
}
|
||||
|
||||
.v-data-table .group-row:hover {
|
||||
background-color: rgba(var(--v-theme-info), 0.08) !important;
|
||||
}
|
||||
|
||||
/* Sub-command row styling */
|
||||
.v-data-table .sub-command-row {
|
||||
/* background-color: rgba(var(--v-theme-surface-variant), 0.3); */
|
||||
border-left: 2px solid rgba(var(--v-theme-secondary), 0.3);
|
||||
}
|
||||
|
||||
/* .v-data-table .sub-command-row:hover {
|
||||
background-color: rgba(var(--v-theme-surface-variant), 0.5) !important;
|
||||
} */
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user