This commit is contained in:
xds
2026-03-22 13:26:18 +03:00
parent 28a5d51389
commit f98c57a433
20 changed files with 2949 additions and 0 deletions

View File

@@ -0,0 +1,161 @@
<template>
<div>
<h1 class="mb-6 text-2xl font-bold text-gray-900">Настройки</h1>
<div v-if="loading" class="text-gray-500">Загрузка...</div>
<div v-else class="space-y-6">
<!-- Settings groups -->
<div v-for="group in settingsGroups" :key="group.title" class="rounded-xl border border-gray-200 bg-white">
<div class="border-b border-gray-200 px-5 py-3">
<h2 class="text-sm font-bold uppercase text-gray-500">{{ group.title }}</h2>
</div>
<div class="divide-y divide-gray-100">
<div v-for="item in group.items" :key="item.key" class="flex items-center justify-between px-5 py-4">
<div>
<p class="text-sm font-medium text-gray-900">{{ item.label }}</p>
<p class="text-xs text-gray-400">{{ item.key }}</p>
</div>
<div class="flex items-center gap-2">
<input
v-model="settingsValues[item.key]"
class="input-field !w-64 !py-1.5 text-sm text-right"
:placeholder="item.placeholder || ''"
@keydown.enter="saveKey(item.key)"
/>
<button
@click="saveKey(item.key)"
:disabled="savingKey === item.key"
class="rounded-lg bg-primary-600 px-3 py-1.5 text-xs font-medium text-white hover:bg-primary-700 disabled:opacity-50"
>
{{ savingKey === item.key ? '...' : 'Сохранить' }}
</button>
</div>
</div>
</div>
</div>
<!-- Custom settings -->
<div class="rounded-xl border border-gray-200 bg-white">
<div class="border-b border-gray-200 px-5 py-3 flex items-center justify-between">
<h2 class="text-sm font-bold uppercase text-gray-500">Все настройки</h2>
<button @click="showAddModal = true" class="text-xs font-medium text-primary-600 hover:text-primary-700">
+ Добавить
</button>
</div>
<div v-if="allSettings.length === 0" class="px-5 py-8 text-center text-sm text-gray-400">
Настроек пока нет
</div>
<div v-else class="divide-y divide-gray-100">
<div v-for="s in allSettings" :key="s.key" class="flex items-center justify-between px-5 py-3">
<div class="flex-1 min-w-0 mr-4">
<p class="text-sm font-mono text-gray-700 truncate">{{ s.key }}</p>
</div>
<div class="flex items-center gap-2">
<span class="text-sm text-gray-900 max-w-xs truncate">{{ s.value }}</span>
<span class="text-xs text-gray-400">{{ formatDate(s.updated_at) }}</span>
</div>
</div>
</div>
</div>
</div>
<!-- Add setting modal -->
<Teleport to="body">
<div v-if="showAddModal" class="fixed inset-0 z-50 flex items-center justify-center bg-black/30" @click.self="showAddModal = false">
<div class="w-full max-w-sm rounded-xl bg-white p-6 shadow-2xl">
<h3 class="text-lg font-bold text-gray-900 mb-4">Новая настройка</h3>
<form @submit.prevent="addSetting" class="space-y-3">
<div>
<label class="mb-1 block text-xs font-semibold uppercase text-gray-500">Ключ</label>
<input v-model="newKey" required class="input-field" placeholder="time_rate_per_hour" />
</div>
<div>
<label class="mb-1 block text-xs font-semibold uppercase text-gray-500">Значение</label>
<input v-model="newValue" required class="input-field" placeholder="200" />
</div>
<div class="flex justify-end gap-2 pt-2">
<button type="button" @click="showAddModal = false" class="rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50">
Отмена
</button>
<button type="submit" class="btn-primary text-sm">Создать</button>
</div>
</form>
</div>
</div>
</Teleport>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import api from '../../api/client'
const loading = ref(true)
const allSettings = ref([])
const settingsValues = ref({})
const savingKey = ref(null)
const showAddModal = ref(false)
const newKey = ref('')
const newValue = ref('')
const settingsGroups = [
{
title: 'Расчёт стоимости',
items: [
{ key: 'time_rate_per_hour', label: 'Ставка за час печати (руб)', placeholder: '200' },
{ key: 'sanding_cost', label: 'Стоимость шлифовки (руб/шт)', placeholder: '300' },
{ key: 'painting_cost', label: 'Стоимость покраски (руб/шт)', placeholder: '500' },
{ key: 'threading_cost', label: 'Стоимость резьбы (руб/шт)', placeholder: '200' },
{ key: 'acetone_smoothing_cost', label: 'Ацетоновая обработка (руб/шт)', placeholder: '400' },
],
},
{
title: 'Уведомления',
items: [
{ key: 'telegram_enabled', label: 'Telegram уведомления (true/false)', placeholder: 'true' },
{ key: 'company_name', label: 'Название компании', placeholder: 'Filam3D' },
{ key: 'company_phone', label: 'Телефон', placeholder: '+7 (999) 123-45-67' },
{ key: 'company_email', label: 'Email', placeholder: 'info@filam3d.ru' },
],
},
]
onMounted(() => loadSettings())
async function loadSettings() {
loading.value = true
try {
const { data } = await api.get('/admin/settings')
allSettings.value = data
const map = {}
data.forEach((s) => { map[s.key] = s.value })
settingsValues.value = map
} finally {
loading.value = false
}
}
async function saveKey(key) {
savingKey.value = key
try {
await api.put(`/admin/settings/${key}`, { value: settingsValues.value[key] || '' })
await loadSettings()
} finally {
savingKey.value = null
}
}
async function addSetting() {
await api.put(`/admin/settings/${newKey.value}`, { value: newValue.value })
showAddModal.value = false
newKey.value = ''
newValue.value = ''
await loadSettings()
}
function formatDate(iso) {
if (!iso) return ''
return new Date(iso).toLocaleString('ru-RU', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' })
}
</script>