This commit is contained in:
Baobhan Sith
2025-04-19 12:28:30 +08:00
parent 651113baf0
commit 9d1bfe45d9
3 changed files with 164 additions and 160 deletions
@@ -37,7 +37,6 @@ const themeParseError = ref<string | null>(null); // 用于显示 JSON 解析错
// 终端主题管理相关状态 // 终端主题管理相关状态
const isEditingTheme = ref(false); // 是否正在编辑某个主题 const isEditingTheme = ref(false); // 是否正在编辑某个主题
const themeSearchTerm = ref(''); // 主题搜索词 const themeSearchTerm = ref(''); // 主题搜索词
const themeSortOrder = ref<'nameAsc' | 'nameDesc'>('nameAsc'); // 主题排序方式
// 使用 reactive 确保嵌套对象 themeData 的响应性 // 使用 reactive 确保嵌套对象 themeData 的响应性
// 修正:editingTheme 应该是一个 ref 包含 TerminalTheme 或 null // 修正:editingTheme 应该是一个 ref 包含 TerminalTheme 或 null
const editingTheme = ref<TerminalTheme | null>(null); // 正在编辑的主题数据副本 (完整结构) const editingTheme = ref<TerminalTheme | null>(null); // 正在编辑的主题数据副本 (完整结构)
@@ -131,16 +130,13 @@ watch([
const emit = defineEmits(['close']); const emit = defineEmits(['close']);
const closeCustomizer = () => { const closeCustomizer = () => {
// 如果正在编辑主题,提示用户是否放弃更改 // 如果正在编辑主题,直接关闭并重置状态
if (isEditingTheme.value) { if (isEditingTheme.value) {
if (confirm(t('styleCustomizer.confirmCloseEditing'))) { isEditingTheme.value = false; // 退出编辑状态
isEditingTheme.value = false; // 退出编辑状态 editingTheme.value = null;
editingTheme.value = null; saveThemeError.value = null; // 同时清除可能存在的保存错误
emit('close');
}
} else {
emit('close');
} }
emit('close'); // 总是触发关闭事件
}; };
// 当前活动的标签页 // 当前活动的标签页
@@ -161,15 +157,13 @@ const handleSaveUiTheme = async () => {
// 重置 UI 主题 // 重置 UI 主题
const handleResetUiTheme = async () => { const handleResetUiTheme = async () => {
if (confirm(t('styleCustomizer.confirmResetUi'))) { try {
try { await appearanceStore.resetCustomUiTheme();
await appearanceStore.resetCustomUiTheme(); // watch 会自动更新 editableUiTheme.value
// watch 会自动更新 editableUiTheme.value alert(t('styleCustomizer.uiThemeReset'));
alert(t('styleCustomizer.uiThemeReset')); } catch (error: any) {
} catch (error: any) { console.error("重置 UI 主题失败:", error);
console.error("重置 UI 主题失败:", error); alert(t('styleCustomizer.uiThemeResetFailed', { message: error.message }));
alert(t('styleCustomizer.uiThemeResetFailed', { message: error.message }));
}
} }
}; };
@@ -353,7 +347,7 @@ const handleApplyTheme = async (theme: TerminalTheme) => {
try { try {
await appearanceStore.setActiveTerminalTheme(themeIdToApply); await appearanceStore.setActiveTerminalTheme(themeIdToApply);
// 成功后 activeTerminalThemeId 会自动更新 // 成功后 activeTerminalThemeId 会自动更新
alert(t('styleCustomizer.themeAppliedSuccess', { name: theme.name })); // 需要添加翻译 // alert(`Theme '${theme.name}' applied successfully`); // 移除成功提示
} catch (error: any) { } catch (error: any) {
console.error("应用终端主题失败:", error); console.error("应用终端主题失败:", error);
alert(t('styleCustomizer.setActiveThemeFailed', { message: error.message })); alert(t('styleCustomizer.setActiveThemeFailed', { message: error.message }));
@@ -382,7 +376,7 @@ const handleEditTheme = (theme: TerminalTheme) => {
// 基于预设创建副本 // 基于预设创建副本
const themeCopy = JSON.parse(JSON.stringify(theme)); const themeCopy = JSON.parse(JSON.stringify(theme));
themeCopy._id = undefined; // 清除 ID,表示是新建 themeCopy._id = undefined; // 清除 ID,表示是新建
themeCopy.name = `${theme.name} (${t('styleCustomizer.copySuffix', '副本')})`; // 添加后缀,需要添加翻译 '副本' themeCopy.name = `${theme.name} (Copy)`;
themeCopy.isPreset = false; // 副本不再是预设 themeCopy.isPreset = false; // 副本不再是预设
editingTheme.value = themeCopy; editingTheme.value = themeCopy;
console.log('创建预设主题副本进行编辑:', editingTheme.value); console.log('创建预设主题副本进行编辑:', editingTheme.value);
@@ -438,14 +432,12 @@ const handleCancelEditingTheme = () => {
// 删除主题 // 删除主题
const handleDeleteTheme = async (theme: TerminalTheme) => { const handleDeleteTheme = async (theme: TerminalTheme) => {
if (theme.isPreset) return; if (theme.isPreset) return;
if (confirm(t('styleCustomizer.confirmDeleteTheme', { name: theme.name }))) { try {
try { await appearanceStore.deleteTerminalTheme(theme._id!);
await appearanceStore.deleteTerminalTheme(theme._id!); alert(t('styleCustomizer.themeDeletedSuccess'));
alert(t('styleCustomizer.themeDeletedSuccess')); } catch (error: any) {
} catch (error: any) { console.error("删除终端主题失败:", error);
console.error("删除终端主题失败:", error); alert(t('styleCustomizer.themeDeleteFailed', { message: error.message }));
alert(t('styleCustomizer.themeDeleteFailed', { message: error.message }));
}
} }
}; };
@@ -531,26 +523,22 @@ const handleTerminalBgUpload = async (event: Event) => {
}; };
const handleRemovePageBg = async () => { const handleRemovePageBg = async () => {
if (confirm(t('styleCustomizer.confirmRemovePageBg'))) { try {
try { await appearanceStore.removePageBackground();
await appearanceStore.removePageBackground(); alert(t('styleCustomizer.pageBgRemoved'));
alert(t('styleCustomizer.pageBgRemoved')); } catch (error: any) {
} catch (error: any) { console.error("移除页面背景失败:", error);
console.error("移除页面背景失败:", error); alert(t('styleCustomizer.removeBgFailed', { message: error.message }));
alert(t('styleCustomizer.removeBgFailed', { message: error.message }));
}
} }
}; };
const handleRemoveTerminalBg = async () => { const handleRemoveTerminalBg = async () => {
if (confirm(t('styleCustomizer.confirmRemoveTerminalBg'))) { try {
try { await appearanceStore.removeTerminalBackground();
await appearanceStore.removeTerminalBackground(); alert(t('styleCustomizer.terminalBgRemoved'));
alert(t('styleCustomizer.terminalBgRemoved')); } catch (error: any) {
} catch (error: any) { console.error("移除终端背景失败:", error);
console.error("移除终端背景失败:", error); alert(t('styleCustomizer.removeBgFailed', { message: error.message }));
alert(t('styleCustomizer.removeBgFailed', { message: error.message }));
}
} }
}; };
@@ -579,7 +567,7 @@ const formatXtermLabel = (key: keyof ITheme): string => {
const activeThemeName = computed(() => { const activeThemeName = computed(() => {
const theme = availableTerminalThemes.value.find(t => t._id === activeTerminalThemeId.value); const theme = availableTerminalThemes.value.find(t => t._id === activeTerminalThemeId.value);
// 如果找不到主题 (例如 ID 无效或列表为空),则显示提示 // 如果找不到主题 (例如 ID 无效或列表为空),则显示提示
return theme ? theme.name : t('styleCustomizer.noActiveThemeSelected', '无激活主题'); // 需要添加翻译 return theme ? theme.name : 'No active theme selected';
}); });
// 过滤和排序主题列表 // 过滤和排序主题列表
@@ -592,25 +580,14 @@ const filteredAndSortedThemes = computed(() => {
themes = themes.filter(theme => theme.name.toLowerCase().includes(searchTerm)); themes = themes.filter(theme => theme.name.toLowerCase().includes(searchTerm));
} }
// 排序 // 排序逻辑已移除
themes.sort((a, b) => { // 默认按名称升序排序
const nameA = a.name.toLowerCase(); themes.sort((a, b) => a.name.localeCompare(b.name));
const nameB = b.name.toLowerCase();
if (themeSortOrder.value === 'nameAsc') {
return nameA.localeCompare(nameB);
} else { // nameDesc
return nameB.localeCompare(nameA);
}
// 未来可扩展日期排序,需要后端提供 createdAt/updatedAt
});
return themes; return themes;
}); });
// 切换排序顺序 // 排序切换函数已移除
const handleSortThemes = () => {
themeSortOrder.value = themeSortOrder.value === 'nameAsc' ? 'nameDesc' : 'nameAsc';
};
</script> </script>
@@ -699,60 +676,59 @@ const handleSortThemes = () => {
<!-- 终端主题选择与管理 --> <!-- 终端主题选择与管理 -->
<h4>{{ t('styleCustomizer.terminalThemeSelection') }}</h4> <h4>{{ t('styleCustomizer.terminalThemeSelection') }}</h4>
<!-- 显示当前激活主题 --> <!-- 显示当前激活主题 -->
<div class="active-theme-display"> <div class="active-theme-display">
<span>{{ t('styleCustomizer.activeTheme') }}:</span> <span>{{ t('styleCustomizer.activeTheme') }}:</span>
<strong>{{ activeThemeName }}</strong> <strong>{{ activeThemeName }}</strong>
<button @click="handleExportActiveTheme" :disabled="!activeTerminalThemeId" class="button-inline button-small" :title="t('styleCustomizer.exportActiveThemeTooltip', '导出当前激活的主题为 JSON 文件')">{{ t('styleCustomizer.exportActiveTheme', '导出当前主题') }}</button> <!-- 添加 tooltip --> <!-- 导出按钮移到管理按钮区域 -->
</div> </div>
<!-- 主题管理按钮 --> <!-- 主题管理按钮 -->
<div class="theme-management-buttons"> <div class="theme-management-buttons">
<button @click="handleAddNewTheme">{{ t('styleCustomizer.addNewTheme') }}</button> <button @click="handleAddNewTheme">{{ t('styleCustomizer.addNewTheme') }}</button>
<button @click="handleTriggerImport">{{ t('styleCustomizer.importTheme') }}</button> <button @click="handleTriggerImport">{{ t('styleCustomizer.importTheme') }}</button>
<input type="file" ref="themeImportInput" @change="handleImportThemeFile" accept=".json" style="display: none;" /> <button @click="handleExportActiveTheme" :disabled="!activeTerminalThemeId" class="button-inline button-small" :title="t('styleCustomizer.exportActiveThemeTooltip', 'Export the currently active theme as a JSON file')">{{ t('styleCustomizer.exportActiveTheme', 'Export Active Theme') }}</button>
<input type="file" ref="themeImportInput" @change="handleImportThemeFile" accept=".json" style="display: none;" />
<p v-if="importError" class="error-message">{{ importError }}</p> <p v-if="importError" class="error-message">{{ importError }}</p>
</div> </div>
<!-- 搜索和排序 -->
<div class="theme-filter-sort">
<input
type="text"
v-model="themeSearchTerm"
:placeholder="t('styleCustomizer.searchThemePlaceholder', '搜索主题名称...')"
class="text-input"
/>
<button @click="handleSortThemes" class="button-inline button-small">
{{ t('styleCustomizer.sortBy', '排序:') }} {{ themeSortOrder === 'nameAsc' ? t('styleCustomizer.sortAsc', '名称升序') : t('styleCustomizer.sortDesc', '名称降序') }} <!-- 需要添加翻译 -->
</button>
</div>
<!-- 主题列表 --> <!-- 搜索框移到列表上方 -->
<ul class="theme-list"> <div class="theme-search-bar">
<!-- 添加空状态提示 --> <input
<li v-if="filteredAndSortedThemes.length === 0" class="empty-list-item"> type="text"
{{ t('styleCustomizer.noThemesFound', '未找到匹配的主题') }} <!-- 需要添加翻译 --> v-model="themeSearchTerm"
</li> :placeholder="t('styleCustomizer.searchThemePlaceholder', '搜索主题名称...')"
<li v-else v-for="theme in filteredAndSortedThemes" :key="theme._id" :class="{ 'preset-theme': theme.isPreset, 'active': theme._id === activeTerminalThemeId }"> class="text-input"
<button />
@click="handleApplyTheme(theme)" </div>
class="button-apply"
:disabled="theme._id === activeTerminalThemeId"
:title="t('styleCustomizer.applyThemeTooltip', '应用此主题')"
>
{{ t('styleCustomizer.applyButton', '应用') }} <!-- 需要添加翻译 -->
</button>
<span class="theme-name" :title="theme.name">{{ theme.name }}</span> <!-- 添加 title 属性显示完整名称 -->
<div class="theme-actions">
<button @click="handleEditTheme(theme)" :title="theme.isPreset ? t('styleCustomizer.editAsCopyTooltip', '编辑此预设主题的副本') : t('common.edit')">{{ t('common.edit') }}</button> <!-- 添加 tooltip -->
<button @click="handleDeleteTheme(theme)" :disabled="theme.isPreset" class="button-danger" :title="theme.isPreset ? t('styleCustomizer.cannotDeletePresetTooltip', '无法删除预设主题') : t('common.delete')">{{ t('common.delete') }}</button> <!-- 添加 tooltip -->
</div>
</li>
</ul>
</section> <!-- 主题列表 -->
<ul class="theme-list">
<!-- 添加空状态提示 -->
<li v-if="filteredAndSortedThemes.length === 0" class="empty-list-item">
{{ t('styleCustomizer.noThemesFound', 'No matching themes found') }}
</li>
<li v-else v-for="theme in filteredAndSortedThemes" :key="theme._id" :class="{ 'preset-theme': theme.isPreset, 'active': theme._id === activeTerminalThemeId }">
<!-- 应用按钮移到 theme-actions -->
<span class="theme-name" :title="theme.name">{{ theme.name }}</span>
<div class="theme-actions">
<button
@click="handleApplyTheme(theme)"
:disabled="theme._id === activeTerminalThemeId"
:title="t('styleCustomizer.applyThemeTooltip', 'Apply this theme')"
>
{{ t('styleCustomizer.applyButton', 'Apply') }}
</button>
<button @click="handleEditTheme(theme)" :title="theme.isPreset ? t('styleCustomizer.editAsCopy', 'Edit as Copy') : t('common.edit')">{{ t('common.edit') }}</button>
<button @click="handleDeleteTheme(theme)" :disabled="theme.isPreset" class="button-danger" :title="theme.isPreset ? t('styleCustomizer.cannotDeletePreset', 'Cannot delete preset theme') : t('common.delete')">{{ t('common.delete') }}</button>
</div>
</li>
</ul>
</section>
<!-- 主题编辑器 --> <!-- 主题编辑器 -->
<section v-if="isEditingTheme && editingTheme"> <section v-if="isEditingTheme && editingTheme">
<h3>{{ editingTheme._id ? t('styleCustomizer.editThemeTitle') : t('styleCustomizer.newThemeTitle') }}</h3> <h3>{{ editingTheme._id ? t('styleCustomizer.editThemeTitle') : t('styleCustomizer.newThemeTitle') }}</h3>
<p v-if="saveThemeError" class="error-message">{{ saveThemeError }}</p> <p v-if="saveThemeError" class="error-message">{{ saveThemeError }}</p>
<div class="form-group"> <div class="form-group">
@@ -1165,6 +1141,7 @@ hr {
display: flex; display: flex;
gap: var(--base-margin); gap: var(--base-margin);
flex-wrap: wrap; flex-wrap: wrap;
align-items: center; /* 垂直居中按钮和错误消息 */
padding-bottom: var(--base-padding); padding-bottom: var(--base-padding);
border-bottom: 1px dashed var(--border-color); border-bottom: 1px dashed var(--border-color);
} }
@@ -1188,14 +1165,19 @@ hr {
opacity: 0.6; opacity: 0.6;
cursor: not-allowed; cursor: not-allowed;
} }
.theme-management-buttons .error-message {
margin: 0; /* 移除错误消息的默认外边距 */
flex-grow: 1; /* 让错误消息填充剩余空间 */
text-align: left; /* 错误消息左对齐 */
}
.theme-list { .theme-list {
list-style: none; list-style: none;
padding: 0; padding: 0;
margin-top: 0; margin-top: var(--base-padding); /* 列表与上方元素间距 */
max-height: 280px; max-height: 280px; /* 限制高度 */
overflow-y: auto; overflow-y: auto; /* 允许滚动 */
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
border-radius: 4px; border-radius: 4px;
background-color: var(--app-bg-color); background-color: var(--app-bg-color);
@@ -1203,8 +1185,8 @@ hr {
.theme-list li { .theme-list li {
display: grid; /* 使用 Grid 布局列表项 */ display: grid; /* 使用 Grid 布局列表项 */
/* 新增列: 应用按钮 | 名称 | 操作按钮 */ /* 列: 名称 | 操作按钮 */
grid-template-columns: auto 1fr auto; grid-template-columns: 1fr auto; /* 名称占据剩余空间,按钮组自适应 */
align-items: center; align-items: center;
padding: 0.6rem 0.8rem; /* 调整内边距 */ padding: 0.6rem 0.8rem; /* 调整内边距 */
border-bottom: 1px solid var(--border-color); border-bottom: 1px solid var(--border-color);
@@ -1223,14 +1205,26 @@ hr {
font-weight: bold; font-weight: bold;
color: var(--button-text-color); /* 确保激活项文本颜色正确 */ color: var(--button-text-color); /* 确保激活项文本颜色正确 */
} }
.theme-list li.active .button-apply { /* 激活项内按钮样式调整 */
/* 可以选择隐藏或禁用已激活项的应用按钮 */ .theme-list li.active .theme-actions button {
opacity: 0.5;
cursor: default;
background-color: transparent; /* 激活项的应用按钮背景透明 */
border-color: transparent;
color: var(--button-text-color); /* 继承激活项文本颜色 */ color: var(--button-text-color); /* 继承激活项文本颜色 */
border-color: rgba(255, 255, 255, 0.3); /* 边框稍微可见 */
background-color: rgba(255, 255, 255, 0.1); /* 轻微背景 */
} }
.theme-list li.active .theme-actions button:hover:not(:disabled) {
background-color: rgba(255, 255, 255, 0.2);
border-color: rgba(255, 255, 255, 0.5);
}
/* 激活项的应用按钮禁用样式 - 注意: :disabled 选择器优先级可能需要调整 */
.theme-list li.active .theme-actions button:disabled {
opacity: 0.5 !important; /* 增加优先级确保生效 */
cursor: default !important;
background-color: transparent !important;
border-color: transparent !important;
color: var(--button-text-color) !important; /* 确保禁用时颜色也正确 */
}
.theme-list li:last-child { .theme-list li:last-child {
border-bottom: none; border-bottom: none;
} }
@@ -1243,44 +1237,24 @@ hr {
font-style: italic; font-style: italic;
} }
.button-apply { /* 应用按钮样式 */ /* 移除独立的 .button-apply 样式 */
grid-column: 1 / 2;
padding: 0.3rem 0.6rem;
font-size: 0.85rem;
border: 1px solid var(--border-color);
border-radius: 4px;
background-color: var(--header-bg-color);
color: var(--text-color);
cursor: pointer;
transition: background-color 0.2s ease, border-color 0.2s ease;
white-space: nowrap;
margin-right: var(--base-margin); /* 与名称间距 */
}
.button-apply:hover:not(:disabled) {
background-color: var(--border-color);
border-color: var(--text-color-secondary);
}
.button-apply:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.theme-name { /* 主题名称样式 */ .theme-name { /* 主题名称样式 */
grid-column: 2 / 3; grid-column: 1 / 2; /* 名称在第一列 */
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
color: var(--text-color); /* 确保非激活项文本颜色 */ color: var(--text-color); /* 确保非激活项文本颜色 */
} }
.theme-list li.preset-theme .theme-name { .theme-list li.preset-theme .theme-name {
/* font-style: italic; */ /* 移除斜体,通过按钮区分 */ /* 可以添加特定样式 */
/* color: var(--text-color-secondary); */
} }
.theme-actions { .theme-actions {
grid-column: 3 / 4; /* 按钮组在第列 */ grid-column: 2 / 3; /* 按钮组在第列 */
flex-shrink: 0; flex-shrink: 0;
display: flex; display: flex;
gap: 0.5rem; gap: 0.5rem; /* 按钮间距 */
justify-content: flex-end; /* 按钮靠右对齐 */
} }
/* 统一操作按钮样式 */ /* 统一操作按钮样式 */
.theme-actions button { .theme-actions button {
@@ -1401,30 +1375,42 @@ hr {
cursor: not-allowed; cursor: not-allowed;
} }
/* 搜索和排序区域 */ /* 搜索框样式 */
.theme-filter-sort { .theme-search-bar {
display: flex; margin-bottom: var(--base-padding); /* 与下方列表的间距 */
gap: var(--base-margin);
margin-bottom: var(--base-padding);
align-items: center;
} }
.theme-filter-sort input.text-input { .theme-search-bar input.text-input {
flex-grow: 1; /* 让搜索框占据更多空间 */ /* flex-grow: 1; /* 不再需要 flex */
grid-column: auto; /* 重置 grid 影响 */ /* 移除 grid-column 相关的样式,因为它不再是 grid item */
/* grid-column: auto; */
/* 确保输入框样式正确 */
border: 1px solid var(--border-color);
padding: 0.5rem 0.7rem;
border-radius: 4px;
font-size: 0.9rem;
background-color: var(--app-bg-color);
color: var(--text-color);
/* width: auto; /* 覆盖 form-group 的 width: 100% */
width: 100%; /* 让搜索框占满宽度 */
box-sizing: border-box;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
} }
.theme-filter-sort button { .theme-search-bar input.text-input:focus {
flex-shrink: 0; /* 防止按钮被压缩 */ border-color: var(--link-active-color);
outline: 0;
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.15);
} }
/* 排序按钮样式已移除 */
/* 当前激活主题显示 */ /* 当前激活主题显示 */
.active-theme-display { .active-theme-display {
margin-bottom: var(--base-padding); margin-bottom: var(--base-padding); /* 与下方搜索框的间距 */
padding: 0.6rem 0; padding: 0.6rem 0;
font-size: 0.95rem; font-size: 0.95rem;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.8rem; gap: 0.8rem;
border-bottom: 1px solid var(--border-color); /* border-bottom: 1px solid var(--border-color); */ /* 移除底部边框 */
} }
.active-theme-display span { .active-theme-display span {
color: var(--text-color-secondary); color: var(--text-color-secondary);
+10 -1
View File
@@ -88,7 +88,16 @@
"errorInvalidJsonObject": "Invalid input. Please provide a valid JSON object.", "errorInvalidJsonObject": "Invalid input. Please provide a valid JSON object.",
"errorInvalidJsonConfig": "Invalid JSON configuration", "errorInvalidJsonConfig": "Invalid JSON configuration",
"editAsCopy": "Edit as Copy", "editAsCopy": "Edit as Copy",
"copySuffix": "Copy" "copySuffix": "Copy",
"cannotDeletePreset": "Cannot delete preset theme",
"applyThemeTooltip": "Apply this theme",
"applyButton": "Apply",
"searchThemePlaceholder": "Search theme name...",
"sortBy": "Sort by:",
"sortAsc": "Name Asc",
"sortDesc": "Name Desc",
"exportActiveThemeTooltip": "Export the currently active theme as a JSON file",
"exportActiveTheme": "Export Active Theme"
}, },
"login": { "login": {
"title": "User Login", "title": "User Login",
+10 -1
View File
@@ -88,7 +88,16 @@
"errorInvalidJsonObject": "输入无效。请输入一个有效的 JSON 对象。", "errorInvalidJsonObject": "输入无效。请输入一个有效的 JSON 对象。",
"errorInvalidJsonConfig": "无效的 JSON 配置", "errorInvalidJsonConfig": "无效的 JSON 配置",
"editAsCopy": "编辑副本", "editAsCopy": "编辑副本",
"copySuffix": "副本" "copySuffix": "副本",
"cannotDeletePreset": "无法删除预设主题",
"applyThemeTooltip": "应用此主题",
"applyButton": "应用",
"searchThemePlaceholder": "搜索主题名称...",
"sortBy": "排序:",
"sortAsc": "名称升序",
"sortDesc": "名称降序",
"exportActiveThemeTooltip": "将当前激活的主题导出为 JSON 文件",
"exportActiveTheme": "导出当前主题"
}, },
"login": { "login": {
"title": "用户登录", "title": "用户登录",