Skip to content

Commit 5bf02fa

Browse files
committed
feat(ui): 收藏模块功能国际化、布局优化与组件重构
此提交包含收藏模块的国际化支持、布局和交互优化,以及核心组件的重构,以提升可维护性、用户体验和性能。 **主要特性与改进:** 1. **收藏功能国际化 (i18n):** * 实现了收藏模块所有组件的三语言(中、英、繁)国际化支持。 * 国际化了 `CategoryTreeSelect.vue`、`FavoriteCard.vue`、`FavoriteManager.vue` 和 `SaveFavoriteDialog.vue` 中的所有硬编码文本。 * 更新了 `zh-CN.ts`、`en-US.ts` 和 `zh-TW.ts` 翻译资源,新增了必要的翻译键。 * 彻底消除了 UI 层的硬编码中文文本。 2. **收藏管理器布局与交互优化:** * 重构 `FavoriteCard` 为固定高度网格布局,解决了卡片高度不一致问题。 * 新增 `useTooltipTheme` 工具统一 Tooltip 样式,并优化了其智能定位逻辑和空间计算。 * 实现了响应式网格布局 (移动端1列/平板2列/桌面4列),并对 `resize` 事件进行节流和缓存计算结果以优化性能。 * 改进了公开标识的可见性,使用 `NTag` 替代纯图标。 * 将 CSS `v-bind` 改为动态 `style` 绑定提升稳定性。 * `FavoriteManager` 移除了滚动容器,改为固定高度工具栏、可滚动卡片区域与固定底部分页的设计。 * 精简了视图模式,移除了列表视图,仅保留卡片视图,并删除了视图切换按钮组。 * 优化了工具栏布局为单行,左侧包含搜索/筛选/计数,右侧为操作按钮,并支持响应式设计。 * 精简了代码和依赖,删除了多余的组件导入和状态管理,减少了约 180 行代码。 * **修复:** 修复了 `FavoriteManager` 编辑对话框中 `NGrid` 和 `NGridItem` 组件缺失导入导致的运行时警告。 3. **收藏对话框组件重构:** * 新增了 `CategoryTreeSelect` 通用组件,支持树状分类选择、内置分类管理和自动加载数据。 * 改造 `SaveFavoriteDialog` 为通用组件,支持新建/保存模式,动态标题和字段禁用逻辑,并集成了 `CategoryTreeSelect`。 * 简化了 `FavoriteManager` 组件,删除了 120 行重复代码,统一使用 `SaveFavoriteDialog` 和 `CategoryTreeSelect`。 * 遵循 DRY、KISS、YAGNI 原则,消除了约 150 行重复代码,提升了可维护性。 **Breaking Changes:** * 收藏管理器布局从响应式 CSS Grid 改为固定网格 + 分页设计。 * 每页显示数量现在根据屏幕尺寸动态调整 (4-8项)。 * 移除了列表视图 (list view),仅保留卡片视图 (grid view)。
1 parent 609f454 commit 5bf02fa

File tree

9 files changed

+1504
-1141
lines changed

9 files changed

+1504
-1141
lines changed
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
<template>
2+
<NTreeSelect
3+
v-model:value="internalValue"
4+
:options="treeOptions"
5+
:placeholder="placeholder || t('favorites.dialog.categoryPlaceholder')"
6+
:clearable="clearable"
7+
:consistent-menu-width="consistentMenuWidth"
8+
:style="computedStyle"
9+
@update:value="handleValueChange"
10+
>
11+
<template v-if="showManageButton" #action>
12+
<NButton
13+
text
14+
block
15+
@click="handleOpenManager"
16+
style="justify-content: flex-start; padding: 8px 12px;"
17+
>
18+
<template #icon>
19+
<NIcon><Folder /></NIcon>
20+
</template>
21+
{{ t('favorites.manager.categoryManager.title') }}
22+
</NButton>
23+
</template>
24+
</NTreeSelect>
25+
26+
<!-- 分类管理对话框 -->
27+
<NModal
28+
v-if="showManageButton"
29+
v-model:show="managerVisible"
30+
preset="card"
31+
:title="t('favorites.manager.categoryManager.title')"
32+
:mask-closable="true"
33+
:style="{ width: 'min(800px, 90vw)', height: 'min(600px, 80vh)' }"
34+
>
35+
<CategoryManager @category-updated="handleCategoryUpdated" />
36+
</NModal>
37+
</template>
38+
39+
<script setup lang="ts">
40+
import { ref, computed, watch, inject, type Ref } from 'vue';
41+
import { NTreeSelect, NButton, NIcon, NModal, type TreeSelectOption } from 'naive-ui';
42+
import { Folder } from '@vicons/tabler';
43+
import { useI18n } from 'vue-i18n';
44+
import CategoryManager from './CategoryManager.vue';
45+
import type { FavoriteCategory } from '@prompt-optimizer/core';
46+
import type { AppServices } from '../types/services';
47+
48+
const { t } = useI18n();
49+
50+
interface Props {
51+
/** 当前选中的分类ID */
52+
modelValue?: string;
53+
/** 占位符文本 */
54+
placeholder?: string;
55+
/** 是否可清除 */
56+
clearable?: boolean;
57+
/** 是否显示"全部分类"选项(用于筛选场景) */
58+
showAllOption?: boolean;
59+
/** 是否显示管理按钮 */
60+
showManageButton?: boolean;
61+
/** 自定义样式 */
62+
style?: string;
63+
/** 是否保持菜单宽度一致 */
64+
consistentMenuWidth?: boolean;
65+
}
66+
67+
const props = withDefaults(defineProps<Props>(), {
68+
modelValue: '',
69+
placeholder: '',
70+
clearable: true,
71+
showAllOption: false,
72+
showManageButton: true,
73+
style: 'min-width: 180px; max-width: 250px;',
74+
consistentMenuWidth: true
75+
});
76+
77+
const emit = defineEmits<{
78+
'update:modelValue': [value: string];
79+
'change': [value: string];
80+
'category-updated': [];
81+
}>();
82+
83+
const services = inject<Ref<AppServices | null> | null>('services', null);
84+
85+
// 内部状态
86+
const internalValue = ref(props.modelValue);
87+
const categories = ref<FavoriteCategory[]>([]);
88+
const managerVisible = ref(false);
89+
90+
// 计算树状分类选项
91+
const treeOptions = computed<TreeSelectOption[]>(() => {
92+
const buildTree = (parentId?: string): TreeSelectOption[] => {
93+
return categories.value
94+
.filter(cat => cat.parentId === parentId)
95+
.map(cat => ({
96+
label: cat.name,
97+
key: cat.id,
98+
children: buildTree(cat.id)
99+
}));
100+
};
101+
102+
const tree = buildTree(undefined);
103+
104+
// 如果是筛选模式,添加"全部分类"选项
105+
if (props.showAllOption) {
106+
return [
107+
{ label: t('favorites.manager.allCategories'), key: '' },
108+
...tree
109+
];
110+
}
111+
112+
return tree;
113+
});
114+
115+
// 计算样式
116+
const computedStyle = computed(() => props.style);
117+
118+
// 加载分类数据
119+
const loadCategories = async () => {
120+
const servicesValue = services?.value;
121+
if (!servicesValue?.favoriteManager) {
122+
console.warn('收藏管理器未初始化,跳过分类加载');
123+
return;
124+
}
125+
126+
try {
127+
categories.value = await servicesValue.favoriteManager.getCategories();
128+
} catch (error: any) {
129+
console.error('加载分类失败:', error);
130+
}
131+
};
132+
133+
// 处理值变化
134+
const handleValueChange = (value: string) => {
135+
internalValue.value = value;
136+
emit('update:modelValue', value);
137+
emit('change', value);
138+
};
139+
140+
// 打开分类管理器
141+
const handleOpenManager = () => {
142+
managerVisible.value = true;
143+
};
144+
145+
// 分类更新后刷新数据
146+
const handleCategoryUpdated = async () => {
147+
await loadCategories();
148+
emit('category-updated');
149+
};
150+
151+
// 监听外部值变化
152+
watch(() => props.modelValue, (newValue) => {
153+
if (newValue !== internalValue.value) {
154+
internalValue.value = newValue;
155+
}
156+
});
157+
158+
// 监听服务初始化
159+
watch(() => services?.value?.favoriteManager, (favoriteManager) => {
160+
if (favoriteManager) {
161+
loadCategories();
162+
}
163+
}, { immediate: true });
164+
165+
// 暴露方法
166+
defineExpose({
167+
reloadCategories: loadCategories
168+
});
169+
</script>

0 commit comments

Comments
 (0)