1047 lines
28 KiB
Vue
1047 lines
28 KiB
Vue
<script setup lang="ts">
|
|
import { ref, watch } from 'vue';
|
|
import { useI18n } from 'vue-i18n';
|
|
import { Icon } from '@iconify/vue';
|
|
import { settings } from '../utils/settings';
|
|
import TagManager from './TagManager.vue';
|
|
import { categoryService, type CategoryItem } from '../utils/categoryService';
|
|
import { metaService } from '../utils/metaService';
|
|
import { useDataUpdateActions } from '../composables/useDataUpdateActions';
|
|
|
|
const props = defineProps({
|
|
visible: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
});
|
|
|
|
const emit = defineEmits(['close']);
|
|
const { t, locale } = useI18n();
|
|
|
|
const handleClose = () => {
|
|
emit('close');
|
|
};
|
|
|
|
// Sync settings.language with i18n locale
|
|
watch(() => settings.language, (newLang) => {
|
|
locale.value = newLang;
|
|
});
|
|
|
|
// Categories for split view
|
|
const categories = [
|
|
{ id: 'textFormat', icon: 'mdi:format-text' },
|
|
{ id: 'autocomplete', icon: 'mdi:magnify' },
|
|
{ id: 'editing', icon: 'mdi:pencil' },
|
|
{ id: 'interface', icon: 'mdi:translate' },
|
|
{ id: 'data', icon: 'mdi:database' },
|
|
{ id: 'categoryManager', icon: 'mdi:palette' },
|
|
{ id: 'metaManager', icon: 'mdi:format-list-bulleted-type' },
|
|
{ id: 'tagManager', icon: 'mdi:tag-multiple' }
|
|
];
|
|
|
|
const activeCategory = ref('textFormat');
|
|
|
|
// Category Management Logic
|
|
const newCatName = ref('');
|
|
const newCatColor = ref('#aabbcc');
|
|
|
|
const handleAddCategory = () => {
|
|
if (!newCatName.value) return;
|
|
|
|
const cats = categoryService.categories.value;
|
|
const maxId = cats.length > 0 ? Math.max(...cats.map(c => c.id)) : 5;
|
|
let nextId = maxId + 1;
|
|
if (nextId < 6) nextId = 6;
|
|
|
|
while (cats.some(c => c.id === nextId)) {
|
|
nextId++;
|
|
}
|
|
|
|
const newCat: CategoryItem = {
|
|
id: nextId,
|
|
name: newCatName.value,
|
|
color: newCatColor.value
|
|
};
|
|
|
|
const knownDefaultIds = [0, 1, 3, 4, 5, 6, 7];
|
|
const customs = [...cats.filter(c => !knownDefaultIds.includes(c.id)), newCat];
|
|
|
|
categoryService.saveCustomCategories(customs).then(() => {
|
|
newCatName.value = '';
|
|
});
|
|
};
|
|
|
|
const handleDeleteCategory = (id: number) => {
|
|
if (!confirm(t('common.confirm') || 'Are you sure?')) return;
|
|
const cats = categoryService.categories.value;
|
|
const knownDefaultIds = [0, 1, 3, 4, 5, 6, 7];
|
|
if (knownDefaultIds.includes(id)) {
|
|
alert(t('category.cannotDeleteDefault'));
|
|
return;
|
|
}
|
|
|
|
const customs = cats.filter(c => !knownDefaultIds.includes(c.id) && c.id !== id);
|
|
categoryService.saveCustomCategories(customs);
|
|
};
|
|
|
|
// Meta Preset Management Logic
|
|
const presetName = ref('');
|
|
const presetTags = ref(''); // string, comma separated
|
|
const editingPresetId = ref<string | null>(null);
|
|
|
|
watch(activeCategory, (newVal) => {
|
|
if (newVal === 'categoryManager') {
|
|
categoryService.fetchCategories();
|
|
} else if (newVal === 'metaManager') {
|
|
metaService.fetchPresets();
|
|
}
|
|
});
|
|
|
|
const handleSavePreset = () => {
|
|
if (!presetName.value || !presetTags.value) {
|
|
alert(t('category.nameRequired'));
|
|
return;
|
|
}
|
|
|
|
// Parse tags: comma/newline
|
|
const tags = presetTags.value.split(/[,\n]/).map(s => s.trim()).filter(Boolean);
|
|
|
|
if (editingPresetId.value) {
|
|
// Update existing
|
|
const index = metaService.customPresets.value.findIndex(p => p.id === editingPresetId.value);
|
|
if (index !== -1) {
|
|
metaService.customPresets.value[index] = {
|
|
...metaService.customPresets.value[index],
|
|
name: presetName.value,
|
|
tags: tags
|
|
};
|
|
}
|
|
} else {
|
|
// Create new
|
|
const newPreset = {
|
|
id: 'custom_' + Date.now(),
|
|
name: presetName.value,
|
|
tags: tags
|
|
};
|
|
metaService.customPresets.value.push(newPreset);
|
|
}
|
|
|
|
metaService.saveCustomPresets(metaService.customPresets.value).then(() => {
|
|
resetPresetForm();
|
|
});
|
|
};
|
|
|
|
const handleEditPreset = (preset: any) => {
|
|
editingPresetId.value = preset.id;
|
|
presetName.value = preset.name;
|
|
presetTags.value = preset.tags.join(', ');
|
|
};
|
|
|
|
const handleDeletePreset = (id: string) => {
|
|
if (!confirm(t('common.confirm') || 'Are you sure?')) return;
|
|
const newCustoms = metaService.customPresets.value.filter(p => p.id !== id);
|
|
metaService.customPresets.value = newCustoms;
|
|
metaService.saveCustomPresets(newCustoms);
|
|
};
|
|
|
|
const resetPresetForm = () => {
|
|
presetName.value = '';
|
|
presetTags.value = '';
|
|
editingPresetId.value = null;
|
|
};
|
|
|
|
const { updateStatus, isUpdating, isChecking, latestVersion, resetUpdateStatus, handleDataUpdate } = useDataUpdateActions(t);
|
|
|
|
watch(() => props.visible, (newVal) => {
|
|
if (newVal) {
|
|
resetUpdateStatus();
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<Transition name="fade">
|
|
<div v-if="visible" class="settings-overlay" @click.self="handleClose">
|
|
<div class="settings-container">
|
|
<!-- Header -->
|
|
<div class="settings-header">
|
|
<div class="settings-title">
|
|
<Icon icon="mdi:cog" class="settings-icon" />
|
|
<span>{{ t('settings.title') }}</span>
|
|
</div>
|
|
<button class="btn-close" @click="handleClose" :title="t('common.close')">
|
|
<Icon icon="mdi:close" />
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Body -->
|
|
<div class="settings-body">
|
|
<!-- Sidebar -->
|
|
<div class="settings-sidebar">
|
|
<div
|
|
v-for="cat in categories"
|
|
:key="cat.id"
|
|
class="sidebar-item"
|
|
:class="{ active: activeCategory === cat.id }"
|
|
@click="activeCategory = cat.id"
|
|
>
|
|
<Icon :icon="cat.icon" class="item-icon" />
|
|
<span>{{ t(`settings.sections.${cat.id}`) }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Content Area -->
|
|
<div class="settings-content">
|
|
<!-- Section 1: Text Formatting -->
|
|
<div v-if="activeCategory === 'textFormat'" class="settings-section">
|
|
<h3 class="section-title">
|
|
<Icon icon="mdi:format-text" />
|
|
{{ t('settings.sections.textFormat') }}
|
|
</h3>
|
|
|
|
<div class="setting-item">
|
|
<div class="setting-info">
|
|
<label class="setting-label">{{ t('settings.items.convertUnderscore') }}</label>
|
|
<p class="setting-desc">{{ t('settings.items.convertUnderscoreDesc') }}</p>
|
|
</div>
|
|
<label class="switch">
|
|
<input type="checkbox" v-model="settings.convertUnderscoreToSpace">
|
|
<span class="slider"></span>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="setting-item">
|
|
<div class="setting-info">
|
|
<label class="setting-label">{{ t('settings.items.escapeBrackets') }}</label>
|
|
<p class="setting-desc">{{ t('settings.items.escapeBracketsDesc') }}</p>
|
|
</div>
|
|
<label class="switch">
|
|
<input type="checkbox" v-model="settings.escapeBrackets">
|
|
<span class="slider"></span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Section 2: Autocomplete & Search -->
|
|
<div v-if="activeCategory === 'autocomplete'" class="settings-section">
|
|
<h3 class="section-title">
|
|
<Icon icon="mdi:magnify" />
|
|
{{ t('settings.sections.autocomplete') }}
|
|
</h3>
|
|
|
|
<div class="setting-item">
|
|
<div class="setting-info">
|
|
<label class="setting-label">{{ t('settings.items.useAliasSearch') }}</label>
|
|
<p class="setting-desc">{{ t('settings.items.useAliasSearchDesc') }}</p>
|
|
</div>
|
|
<label class="switch">
|
|
<input type="checkbox" v-model="settings.useAliasesInSearch">
|
|
<span class="slider"></span>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="setting-item">
|
|
<div class="setting-info">
|
|
<label class="setting-label">{{ t('settings.items.useAliasAutocomplete') }}</label>
|
|
<p class="setting-desc">{{ t('settings.items.useAliasAutocompleteDesc') }}</p>
|
|
</div>
|
|
<label class="switch">
|
|
<input type="checkbox" v-model="settings.useAliasesInAutocomplete">
|
|
<span class="slider"></span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Section 3: Editing Behavior -->
|
|
<div v-if="activeCategory === 'editing'" class="settings-section">
|
|
<h3 class="section-title">
|
|
<Icon icon="mdi:pencil" />
|
|
{{ t('settings.sections.editing') }}
|
|
</h3>
|
|
|
|
<div class="setting-item">
|
|
<div class="setting-info">
|
|
<label class="setting-label">{{ t('settings.items.smartBackspace') }}</label>
|
|
<p class="setting-desc">{{ t('settings.items.smartBackspaceDesc') }}</p>
|
|
</div>
|
|
<label class="switch">
|
|
<input type="checkbox" v-model="settings.smartBackspace">
|
|
<span class="slider"></span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Section 4: Interface -->
|
|
<div v-if="activeCategory === 'interface'" class="settings-section">
|
|
<h3 class="section-title">
|
|
<Icon icon="mdi:translate" />
|
|
{{ t('settings.sections.interface') }}
|
|
</h3>
|
|
|
|
<div class="setting-item">
|
|
<div class="setting-info">
|
|
<label class="setting-label">{{ t('settings.items.language') }}</label>
|
|
<p class="setting-desc">{{ t('settings.items.languageDesc') }}</p>
|
|
</div>
|
|
<select v-model="settings.language" class="language-select">
|
|
<option value="en">English</option>
|
|
<option value="zh-CN">简体中文</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="setting-item">
|
|
<div class="setting-info">
|
|
<label class="setting-label">{{ t('settings.items.tagMaxLength') }}</label>
|
|
<p class="setting-desc">{{ t('settings.items.tagMaxLengthDesc') }}</p>
|
|
</div>
|
|
<input
|
|
type="number"
|
|
v-model.number="settings.tagMaxLength"
|
|
class="input-number"
|
|
min="0"
|
|
step="5"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Section 5: Data Management -->
|
|
<div v-if="activeCategory === 'data'" class="settings-section">
|
|
<h3 class="section-title">
|
|
<Icon icon="mdi:database" />
|
|
{{ t('settings.sections.data') }}
|
|
</h3>
|
|
|
|
<div class="setting-item">
|
|
<div class="setting-info">
|
|
<label class="setting-label">{{ t('settings.items.allowEditDefaultTags') || 'Allow Editing Default Tags' }}</label>
|
|
<p class="setting-desc">{{ t('settings.items.allowEditDefaultTagsDesc') || 'Enable editing and deleting of default tags (Use with caution).' }}</p>
|
|
</div>
|
|
<label class="switch">
|
|
<input type="checkbox" v-model="settings.allowEditDefaultTags">
|
|
<span class="slider"></span>
|
|
</label>
|
|
</div>
|
|
|
|
<!-- Update from GitHub -->
|
|
<div class="setting-item data-update-item">
|
|
<div class="setting-info">
|
|
<label class="setting-label">{{ t('settings.items.updateTags') }}</label>
|
|
<p class="setting-desc">{{ t('settings.items.updateTagsDesc') }}</p>
|
|
</div>
|
|
<div class="update-actions">
|
|
<button
|
|
class="btn-update primary"
|
|
:disabled="isUpdating || isChecking"
|
|
@click="handleDataUpdate('update_github')"
|
|
>
|
|
<Icon v-if="isUpdating && activeCategory === 'data'" icon="mdi:loading" class="spin" />
|
|
<span>{{ t('settings.items.updateNow') }}</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- NEW: Update Liked Tags Data -->
|
|
<div class="setting-item data-update-item">
|
|
<div class="setting-info">
|
|
<label class="setting-label">{{ t('settings.items.updateLikedTags') || 'Update Liked Tags' }}</label>
|
|
<p class="setting-desc">{{ t('settings.items.updateLikedTagsDesc') || 'Sync liked tags data with main database.' }}</p>
|
|
</div>
|
|
<div class="update-actions">
|
|
<button
|
|
class="btn-update secondary"
|
|
:disabled="isUpdating"
|
|
@click="handleDataUpdate('update_liked')"
|
|
>
|
|
<Icon icon="mdi:heart" />
|
|
<span>{{ t('common.update') || 'Update' }}</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- NEW: Update User Tags Data -->
|
|
<div class="setting-item data-update-item">
|
|
<div class="setting-info">
|
|
<label class="setting-label">{{ t('settings.items.updateUserTags') || 'Update User Tags' }}</label>
|
|
<p class="setting-desc">{{ t('settings.items.updateUserTagsDesc') || 'Sync custom tags data with main database.' }}</p>
|
|
</div>
|
|
<div class="update-actions">
|
|
<button
|
|
class="btn-update secondary"
|
|
:disabled="isUpdating"
|
|
@click="handleDataUpdate('update_user')"
|
|
>
|
|
<Icon icon="mdi:account-tag" />
|
|
<span>{{ t('common.update') || 'Update' }}</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="updateStatus" class="update-status-box" :class="{ error: updateStatus.includes('failed') || updateStatus.toLowerCase().includes('error'), success: updateStatus.includes('success') || updateStatus.includes('upToDate') || updateStatus.includes('updated') }">
|
|
<Icon :icon="updateStatus.includes('success') || updateStatus.includes('upToDate') || updateStatus.includes('updated') ? 'mdi:check-circle' : 'mdi:alert-circle'" />
|
|
<span>{{ updateStatus }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<!-- Section: Category Manager -->
|
|
<div v-if="activeCategory === 'categoryManager'" class="settings-section full-width">
|
|
<h3 class="section-title">
|
|
<Icon icon="mdi:palette" />
|
|
{{ t('settings.sections.categoryManager') || 'Category Management' }}
|
|
</h3>
|
|
|
|
<div class="category-manager">
|
|
<div class="add-cat-form">
|
|
<input v-model="newCatName" :placeholder="t('category.namePlaceholder')" class="input-text" />
|
|
<input v-model="newCatColor" type="color" class="input-color" />
|
|
<button class="btn-primary" @click="handleAddCategory">{{ t('category.addButton') }}</button>
|
|
</div>
|
|
|
|
<div class="cat-list">
|
|
<div v-for="cat in categoryService.categories.value" :key="cat.id" class="cat-item">
|
|
<div class="cat-dot" :style="{ backgroundColor: cat.color }"></div>
|
|
<span class="cat-name">{{ cat.name }}</span>
|
|
<span class="cat-id">#{{ cat.id }}</span>
|
|
<!-- Only allow delete for customs -->
|
|
<button v-if="![0,1,3,4,5,6,7].includes(cat.id)" class="btn-icon" @click="handleDeleteCategory(cat.id)">
|
|
<Icon icon="mdi:delete" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Section: Meta Manager (Presets) -->
|
|
<div v-if="activeCategory === 'metaManager'" class="settings-section full-width">
|
|
<h3 class="section-title">
|
|
<Icon icon="mdi:format-list-bulleted-type" />
|
|
{{ t('settings.sections.metaManager') || 'Meta Presets Management' }}
|
|
</h3>
|
|
|
|
<div class="meta-manager">
|
|
<div class="preset-form">
|
|
<h4>{{ editingPresetId ? t('settings.meta.editPreset') : t('settings.meta.newPreset') }}</h4>
|
|
<div class="form-row">
|
|
<input v-model="presetName" :placeholder="t('settings.meta.presetNamePlaceholder')" class="input-text" />
|
|
</div>
|
|
<div class="form-row">
|
|
<textarea
|
|
v-model="presetTags"
|
|
:placeholder="t('settings.meta.presetTagsPlaceholder')"
|
|
class="input-textarea"
|
|
></textarea>
|
|
</div>
|
|
<div class="form-actions">
|
|
<button class="btn-cancel" @click="resetPresetForm" v-if="presetName || presetTags">{{ t('common.cancel') }}</button>
|
|
<button class="btn-primary" @click="handleSavePreset">{{ editingPresetId ? t('common.update') : t('category.addButton') }}</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="preset-list">
|
|
<h4>{{ t('settings.meta.customPresetsHeading') }}</h4>
|
|
<div v-for="p in metaService.customPresets.value" :key="p.id" class="preset-item">
|
|
<div class="preset-info">
|
|
<span class="preset-name">{{ p.name }}</span>
|
|
<span class="preset-tags">{{ p.tags.join(', ') }}</span>
|
|
</div>
|
|
<div class="preset-actions">
|
|
<button class="btn-icon" @click="handleEditPreset(p)">
|
|
<Icon icon="mdi:pencil" />
|
|
</button>
|
|
<button class="btn-icon" @click="handleDeletePreset(p.id)">
|
|
<Icon icon="mdi:delete" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div v-if="metaService.customPresets.value.length === 0" class="empty-msg">{{ t('settings.meta.noCustomPresets') }}</div>
|
|
</div>
|
|
|
|
<div class="preset-list" style="margin-top: 20px;">
|
|
<h4 style="color: #888;">{{ t('settings.meta.defaultPresetsHeading') }}</h4>
|
|
<div v-for="p in metaService.defaultPresets.value" :key="p.id" class="preset-item default">
|
|
<div class="preset-info">
|
|
<span class="preset-name">{{ p.name }}</span>
|
|
<span class="preset-tags">{{ p.tags.join(', ') }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Section: Tag Manager -->
|
|
<div v-if="activeCategory === 'tagManager'" class="settings-section full-width">
|
|
<h3 class="section-title">
|
|
<Icon icon="mdi:tag-multiple" />
|
|
{{ t('settings.sections.tagManager') || 'Tag Manager' }}
|
|
</h3>
|
|
<TagManager />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<div class="settings-footer">
|
|
<div class="footer-info">
|
|
<Icon icon="mdi:information-outline" />
|
|
{{ t('settings.autoSave') }}
|
|
</div>
|
|
<button class="btn-primary" @click="handleClose">{{ t('common.done') }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Transition>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.settings-overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100vw;
|
|
height: 100vh;
|
|
background-color: rgba(0, 0, 0, 0.7);
|
|
backdrop-filter: blur(3px);
|
|
z-index: 10000;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-family: 'Segoe UI', Inter, system-ui, sans-serif;
|
|
color: #e0e0e0;
|
|
}
|
|
|
|
.settings-container {
|
|
width: 90%;
|
|
max-width: 800px;
|
|
height: 85vh;
|
|
background-color: #1e1e1e;
|
|
border-radius: 8px;
|
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.6);
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
border: 1px solid #333;
|
|
}
|
|
|
|
/* Header */
|
|
.settings-header {
|
|
height: 56px;
|
|
background-color: #252526;
|
|
border-bottom: 1px solid #333;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 0 20px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.settings-title {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.settings-icon {
|
|
font-size: 24px;
|
|
color: #0075db;
|
|
}
|
|
|
|
.btn-close {
|
|
background: none;
|
|
border: none;
|
|
color: #aaa;
|
|
cursor: pointer;
|
|
padding: 6px;
|
|
border-radius: 4px;
|
|
font-size: 20px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.btn-close:hover {
|
|
background-color: #c42b1c;
|
|
color: white;
|
|
}
|
|
|
|
/* Body Split Layout */
|
|
.settings-body {
|
|
flex: 1;
|
|
display: flex;
|
|
overflow: hidden;
|
|
background-color: #1e1e1e;
|
|
}
|
|
|
|
/* Sidebar */
|
|
.settings-sidebar {
|
|
width: 220px;
|
|
background-color: #252526;
|
|
border-right: 1px solid #333;
|
|
padding: 12px 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.sidebar-item {
|
|
padding: 12px 20px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
font-size: 14px;
|
|
color: #aaa;
|
|
position: relative;
|
|
}
|
|
|
|
.sidebar-item:hover {
|
|
background-color: #2a2d2e;
|
|
color: #e0e0e0;
|
|
}
|
|
|
|
.sidebar-item.active {
|
|
background-color: #37373d;
|
|
color: white;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.sidebar-item.active::before {
|
|
content: "";
|
|
position: absolute;
|
|
left: 0;
|
|
top: 8px;
|
|
bottom: 8px;
|
|
width: 3px;
|
|
background-color: #0075db;
|
|
}
|
|
|
|
.item-icon {
|
|
font-size: 18px;
|
|
}
|
|
|
|
/* Content Area */
|
|
.settings-content {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 24px 30px;
|
|
}
|
|
|
|
.settings-section {
|
|
max-width: 500px;
|
|
}
|
|
|
|
.settings-section.full-width {
|
|
max-width: 100%;
|
|
}
|
|
|
|
.section-title {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: #0075db;
|
|
margin-bottom: 24px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding-bottom: 12px;
|
|
border-bottom: 1px solid #333;
|
|
}
|
|
|
|
.setting-item {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 18px 0;
|
|
border-bottom: 1px solid #2a2a2a;
|
|
}
|
|
|
|
.setting-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.setting-info {
|
|
flex: 1;
|
|
margin-right: 20px;
|
|
}
|
|
|
|
.setting-label {
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
color: #e0e0e0;
|
|
display: block;
|
|
margin-bottom: 6px;
|
|
cursor: default;
|
|
}
|
|
|
|
.setting-desc {
|
|
font-size: 12px;
|
|
color: #888;
|
|
margin: 0;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
/* Toggle Switch */
|
|
.switch {
|
|
position: relative;
|
|
display: inline-block;
|
|
width: 44px;
|
|
height: 22px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.switch input {
|
|
opacity: 0;
|
|
width: 0;
|
|
height: 0;
|
|
}
|
|
|
|
.slider {
|
|
position: absolute;
|
|
cursor: pointer;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background-color: #444;
|
|
transition: 0.3s;
|
|
border-radius: 22px;
|
|
}
|
|
|
|
.slider:before {
|
|
position: absolute;
|
|
content: "";
|
|
height: 16px;
|
|
width: 16px;
|
|
left: 3px;
|
|
bottom: 3px;
|
|
background-color: white;
|
|
transition: 0.3s;
|
|
border-radius: 50%;
|
|
}
|
|
|
|
input:checked + .slider {
|
|
background-color: #0075db;
|
|
}
|
|
|
|
input:checked + .slider:before {
|
|
transform: translateX(22px);
|
|
}
|
|
|
|
/* Update Button */
|
|
.update-actions {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.input-number {
|
|
background: #2a2a2a;
|
|
border: 1px solid #444;
|
|
color: #e0e0e0;
|
|
padding: 6px 10px;
|
|
border-radius: 4px;
|
|
width: 80px;
|
|
text-align: center;
|
|
}
|
|
|
|
.input-number:focus {
|
|
border-color: #0075db;
|
|
outline: none;
|
|
}
|
|
|
|
.btn-update {
|
|
padding: 8px 16px;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 13px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
transition: all 0.2s;
|
|
background: transparent;
|
|
border: 1px solid #555;
|
|
color: #e0e0e0;
|
|
}
|
|
|
|
.btn-update.primary {
|
|
background-color: #0075db;
|
|
border-color: #0075db;
|
|
color: white;
|
|
}
|
|
|
|
.btn-update.primary:hover:not(:disabled) {
|
|
background-color: #0060b5;
|
|
border-color: #0060b5;
|
|
}
|
|
|
|
.btn-update.secondary:hover:not(:disabled) {
|
|
background-color: #333;
|
|
border-color: #777;
|
|
color: white;
|
|
}
|
|
|
|
.btn-update:disabled {
|
|
opacity: 0.6;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.spin {
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
|
|
@keyframes spin {
|
|
from { transform: rotate(0deg); }
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
|
|
.update-status-box {
|
|
margin-top: 20px;
|
|
padding: 12px 16px;
|
|
background-color: #252526;
|
|
border-radius: 6px;
|
|
border-left: 4px solid #0075db;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.update-status-box.error {
|
|
border-left-color: #f44336;
|
|
}
|
|
|
|
.update-status-box.success {
|
|
border-left-color: #4CAF50;
|
|
}
|
|
|
|
/* Language Select */
|
|
.language-select {
|
|
padding: 6px 12px;
|
|
background-color: #333;
|
|
color: #e0e0e0;
|
|
border: 1px solid #444;
|
|
border-radius: 4px;
|
|
font-size: 14px;
|
|
cursor: pointer;
|
|
outline: none;
|
|
min-width: 120px;
|
|
}
|
|
|
|
/* Footer */
|
|
.settings-footer {
|
|
height: 60px;
|
|
background-color: #252526;
|
|
border-top: 1px solid #333;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 0 20px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.footer-info {
|
|
font-size: 12px;
|
|
color: #888;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
.btn-primary {
|
|
padding: 8px 24px;
|
|
background-color: #0075db;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
transition: background-color 0.2s;
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
background-color: #0060b5;
|
|
}
|
|
|
|
/* Scrollbar */
|
|
.settings-content::-webkit-scrollbar {
|
|
width: 10px;
|
|
}
|
|
|
|
.settings-content::-webkit-scrollbar-track {
|
|
background: #1e1e1e;
|
|
}
|
|
|
|
.settings-content::-webkit-scrollbar-thumb {
|
|
background: #333;
|
|
border-radius: 5px;
|
|
}
|
|
|
|
/* Transition */
|
|
.fade-enter-active,
|
|
.fade-leave-active {
|
|
transition: opacity 0.2s ease;
|
|
}
|
|
|
|
.fade-enter-from,
|
|
.fade-leave-to {
|
|
opacity: 0;
|
|
}
|
|
|
|
/* Category Manager Styles */
|
|
.category-manager {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 16px;
|
|
}
|
|
|
|
.add-cat-form {
|
|
display: flex;
|
|
gap: 10px;
|
|
align-items: center;
|
|
background: #252526;
|
|
padding: 10px;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.input-text {
|
|
flex: 1;
|
|
background: #333;
|
|
border: 1px solid #444;
|
|
color: white;
|
|
padding: 6px 10px;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.input-color {
|
|
background: none;
|
|
border: none;
|
|
width: 40px;
|
|
height: 30px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.cat-list {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 8px;
|
|
}
|
|
|
|
.cat-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
background: #252526;
|
|
padding: 6px 10px;
|
|
border-radius: 4px;
|
|
border: 1px solid #333;
|
|
}
|
|
|
|
.cat-dot {
|
|
width: 12px;
|
|
height: 12px;
|
|
border-radius: 50%;
|
|
}
|
|
|
|
.cat-name {
|
|
font-weight: 500;
|
|
}
|
|
|
|
.cat-id {
|
|
color: #666;
|
|
font-size: 11px;
|
|
}
|
|
|
|
.btn-icon {
|
|
background: none;
|
|
border: none;
|
|
color: #888;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.btn-icon:hover {
|
|
color: #f44336;
|
|
}
|
|
|
|
.meta-manager {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 20px;
|
|
}
|
|
|
|
.preset-form {
|
|
background: #252526;
|
|
padding: 16px;
|
|
border-radius: 6px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
|
|
.form-row {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.input-textarea {
|
|
background: #333;
|
|
border: 1px solid #444;
|
|
color: white;
|
|
padding: 8px;
|
|
border-radius: 4px;
|
|
min-height: 60px;
|
|
resize: vertical;
|
|
}
|
|
|
|
.form-actions {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 10px;
|
|
}
|
|
|
|
.preset-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
|
|
.preset-item {
|
|
background: #252526;
|
|
padding: 12px;
|
|
border-radius: 6px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
border: 1px solid #333;
|
|
}
|
|
|
|
.preset-info {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
}
|
|
|
|
.preset-name {
|
|
font-weight: 600;
|
|
color: #e0e0e0;
|
|
}
|
|
|
|
.preset-tags {
|
|
font-size: 12px;
|
|
color: #888;
|
|
}
|
|
|
|
.preset-actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
.empty-msg {
|
|
color: #666;
|
|
font-style: italic;
|
|
}
|
|
</style>
|