Files
filam3d/frontend/src/views/admin/AdminSettings.vue
2026-03-23 12:48:44 +03:00

171 lines
6.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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: 'AI-ассистент',
items: [
{ key: 'ai_use_proxy', label: 'Использовать AI-прокси (true/false)', placeholder: 'true' },
{ key: 'ai_proxy_url', label: 'URL прокси', placeholder: 'http://82.22.174.14:8001' },
{ key: 'ai_proxy_salt', label: 'Секретная соль прокси', placeholder: '' },
{ key: 'ai_direct_api_key', label: 'Qwen API Key (DashScope)', placeholder: '' },
],
},
{
title: 'Уведомления',
items: [
{ key: 'telegram_enabled', label: 'Telegram уведомления (true/false)', placeholder: 'true' },
{ key: 'company_name', label: 'Название компании', placeholder: 'Bambu Russia' },
{ key: 'company_phone', label: 'Телефон', placeholder: '+7 (999) 123-45-67' },
{ key: 'company_email', label: 'Email', placeholder: 'info@bamburussia.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>