127 lines
3.8 KiB
Vue
127 lines
3.8 KiB
Vue
<template>
|
|
<AppLayout>
|
|
<div class="card">
|
|
<h1 class="text-2xl font-bold mb-6">{{ t('settings.title') }}</h1>
|
|
<form @submit.prevent="saveSettings" class="space-y-6 max-w-2xl">
|
|
<div v-for="field in meta" :key="field.key" class="border-b border-gray-200 pb-4">
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">
|
|
{{ field.label }}
|
|
<span v-if="field.required" class="text-red-500">*</span>
|
|
</label>
|
|
|
|
<!-- Текстовое поле -->
|
|
<input
|
|
v-if="field.type === 'text' || field.type === 'number'"
|
|
v-model="values[field.key]"
|
|
:type="field.type"
|
|
:required="field.required"
|
|
class="input-field mt-1"
|
|
/>
|
|
|
|
<!-- Текстовая область -->
|
|
<textarea
|
|
v-else-if="field.type === 'textarea'"
|
|
v-model="values[field.key]"
|
|
:rows="field.rows || 3"
|
|
class="input-field mt-1"
|
|
></textarea>
|
|
|
|
<!-- Чекбокс для булевых значений -->
|
|
<div v-else-if="field.type === 'boolean'" class="flex items-center mt-1">
|
|
<input
|
|
type="checkbox"
|
|
:checked="values[field.key] === 'true'"
|
|
@change="values[field.key] = $event.target.checked ? 'true' : 'false'"
|
|
class="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
|
|
/>
|
|
<span class="ml-2 text-sm text-gray-600">Enabled</span>
|
|
</div>
|
|
|
|
<!-- Выпадающий список -->
|
|
<select
|
|
v-else-if="field.type === 'select'"
|
|
v-model="values[field.key]"
|
|
class="input-field mt-1"
|
|
>
|
|
<option v-for="opt in field.options" :key="opt.value" :value="opt.value">
|
|
{{ opt.label }}
|
|
</option>
|
|
</select>
|
|
|
|
<p v-if="field.description" class="mt-1 text-xs text-gray-500">
|
|
{{ field.description }}
|
|
</p>
|
|
</div>
|
|
|
|
<div class="flex justify-end space-x-3 pt-4">
|
|
<button type="button" @click="loadData" class="btn-secondary">{{ t('settings.reset') }}</button>
|
|
<button type="submit" class="btn-primary">{{ t('settings.save') }}</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</AppLayout>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, onMounted } from 'vue';
|
|
import AppLayout from '@/components/Layout/AppLayout.vue';
|
|
import { useI18n } from 'vue-i18n'
|
|
import { useNotification } from '@/composables/useNotification'
|
|
|
|
const { showNotification } = useNotification()
|
|
const { t } = useI18n()
|
|
interface FieldMeta {
|
|
key: string;
|
|
label: string;
|
|
description?: string;
|
|
type: string;
|
|
required?: boolean;
|
|
rows?: number;
|
|
options?: Array<{ value: string; label: string }>;
|
|
}
|
|
|
|
const meta = ref<FieldMeta[]>([]);
|
|
const values = ref<Record<string, string>>({});
|
|
|
|
async function loadMeta() {
|
|
const res = await fetch('/api/admin/settings/meta');
|
|
if (res.ok) {
|
|
meta.value = await res.json();
|
|
} else {
|
|
showNotification('settings.loadMetaError', 'error');
|
|
}
|
|
}
|
|
|
|
async function loadValues() {
|
|
const res = await fetch('/api/admin/settings');
|
|
if (res.ok) {
|
|
values.value = await res.json();
|
|
} else {
|
|
showNotification('settings.loadMetaError', 'error');
|
|
}
|
|
}
|
|
|
|
async function loadData() {
|
|
await Promise.all([loadMeta(), loadValues()]);
|
|
}
|
|
|
|
async function saveSettings() {
|
|
try {
|
|
const res = await fetch('/api/admin/settings', {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(values.value),
|
|
});
|
|
if (res.ok) {
|
|
showNotification('settings.saveSuccess', 'success');
|
|
} else {
|
|
showNotification('settings.saveError', 'error');
|
|
}
|
|
} catch (e) {
|
|
showNotification('common.networkError', 'error');
|
|
}
|
|
}
|
|
|
|
onMounted(loadData);
|
|
</script>
|