feat(settings): catalog keys UI and per-tenant program settings editor
Add/remove keys from the Khanegi versions list (form or JSON), edit values per version, and proxy /get_settings for local work. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue'
|
||||
import api from '@/api/http'
|
||||
import { SETTING_TYPE_OPTIONS } from '@/config/khanegiSettingCatalog'
|
||||
|
||||
const props = defineProps<{ canWrite: boolean }>()
|
||||
|
||||
const saving = ref(false)
|
||||
const deleting = ref(false)
|
||||
const activeTab = ref('form')
|
||||
const jsonText = ref('[\n {\n "key": "name_fa",\n "value": "",\n "title_fa": "نام فارسی",\n "description": "",\n "type": "text",\n "can_edit": 0,\n "is_active": 1\n }\n]')
|
||||
const deleteKey = ref('')
|
||||
|
||||
const form = reactive({
|
||||
key: '',
|
||||
title_fa: '',
|
||||
value: '',
|
||||
description: '',
|
||||
type: 'text',
|
||||
type_data: '',
|
||||
can_edit: 0,
|
||||
is_active: 1,
|
||||
})
|
||||
|
||||
const typeOptions = SETTING_TYPE_OPTIONS.map((t) => ({ value: t.key, label: t.value }))
|
||||
|
||||
function parseJsonItems(raw: string): Array<Record<string, unknown>> {
|
||||
const parsed = JSON.parse(raw)
|
||||
if (Array.isArray(parsed)) return parsed
|
||||
if (parsed && typeof parsed === 'object' && Array.isArray(parsed.items)) return parsed.items
|
||||
throw new Error('JSON باید آرایه باشد: [{"key":"name_fa",...}]')
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
if (!props.canWrite) {
|
||||
message.warning('دسترسی نوشتن ندارید')
|
||||
return
|
||||
}
|
||||
let items: Array<Record<string, unknown>> = []
|
||||
try {
|
||||
if (activeTab.value === 'json') {
|
||||
items = parseJsonItems(jsonText.value)
|
||||
} else {
|
||||
if (!form.key.trim()) {
|
||||
message.warning('اسم کلید لازم است (مثلاً name_fa)')
|
||||
return
|
||||
}
|
||||
items = [{ ...form, key: form.key.trim() }]
|
||||
}
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || 'JSON نامعتبر است')
|
||||
return
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
title: 'ثبت کلید روی همهٔ نسخهها؟',
|
||||
content: `${items.length} کلید به قالب اضافه میشود و روی پروژههایی که این کلید را ندارند اعمال میشود.`,
|
||||
okText: 'ثبت',
|
||||
cancelText: 'انصراف',
|
||||
async onOk() {
|
||||
saving.value = true
|
||||
try {
|
||||
const { data } = await api.post('/setting-templates/keys', { items })
|
||||
message.success(`${data.count ?? items.length} کلید ثبت شد`)
|
||||
form.key = ''
|
||||
form.title_fa = ''
|
||||
form.value = ''
|
||||
form.description = ''
|
||||
} catch (e: any) {
|
||||
const msg = e?.response?.data?.message
|
||||
message.error(Array.isArray(msg) ? msg.join('، ') : msg || 'ثبت ناموفق')
|
||||
throw e
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function removeKey() {
|
||||
const key = deleteKey.value.trim()
|
||||
if (!key) {
|
||||
message.warning('اسم کلید را بنویس (مثلاً name_fa)')
|
||||
return
|
||||
}
|
||||
Modal.confirm({
|
||||
title: `حذف کلید «${key}» از همهٔ پروژهها؟`,
|
||||
content: 'از قالب و تنظیمات همهٔ نسخههای خانگی پاک میشود.',
|
||||
okText: 'حذف',
|
||||
okType: 'danger',
|
||||
cancelText: 'انصراف',
|
||||
async onOk() {
|
||||
deleting.value = true
|
||||
try {
|
||||
await api.delete('/setting-templates/keys', { params: { key } })
|
||||
message.success('کلید حذف شد')
|
||||
deleteKey.value = ''
|
||||
} catch (e: any) {
|
||||
const msg = e?.response?.data?.message
|
||||
message.error(Array.isArray(msg) ? msg.join('، ') : msg || 'حذف ناموفق')
|
||||
throw e
|
||||
} finally {
|
||||
deleting.value = false
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="form-section catalog-panel">
|
||||
<h3>کلید تنظیمات (همهٔ نسخهها)</h3>
|
||||
<p class="field-hint">
|
||||
کلید جدید با ثبت هم به قالب نسخههای بعدی میرود هم روی پروژههای فعلی اعمال میشود.
|
||||
اسم کلید مثل <code dir="ltr">name_fa</code> است نه عنوان فارسی.
|
||||
</p>
|
||||
|
||||
<a-tabs v-model:activeKey="activeTab">
|
||||
<a-tab-pane key="form" tab="فرم">
|
||||
<a-form layout="vertical">
|
||||
<div class="catalog-grid">
|
||||
<a-form-item label="کلید (key)" required>
|
||||
<a-input v-model:value="form.key" dir="ltr" placeholder="name_fa" size="large" />
|
||||
</a-form-item>
|
||||
<a-form-item label="عنوان فارسی">
|
||||
<a-input v-model:value="form.title_fa" size="large" />
|
||||
</a-form-item>
|
||||
<a-form-item label="مقدار پیشفرض">
|
||||
<a-input v-model:value="form.value" dir="ltr" size="large" />
|
||||
</a-form-item>
|
||||
<a-form-item label="نوع">
|
||||
<a-select v-model:value="form.type" :options="typeOptions" size="large" />
|
||||
</a-form-item>
|
||||
</div>
|
||||
<a-form-item label="توضیح">
|
||||
<a-textarea v-model:value="form.description" :rows="2" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="json" tab="JSON">
|
||||
<a-textarea v-model:value="jsonText" :rows="10" dir="ltr" class="json-box" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
|
||||
<a-button v-if="canWrite" type="primary" size="large" :loading="saving" @click="submit">
|
||||
<PlusOutlined /> ثبت کلید
|
||||
</a-button>
|
||||
|
||||
<div class="delete-row">
|
||||
<a-input
|
||||
v-model:value="deleteKey"
|
||||
dir="ltr"
|
||||
size="large"
|
||||
placeholder="حذف با اسم کلید — مثلاً name_fa"
|
||||
/>
|
||||
<a-button v-if="canWrite" danger :loading="deleting" @click="removeKey">
|
||||
<DeleteOutlined /> حذف از همه
|
||||
</a-button>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.field-hint {
|
||||
margin: 0 0 12px;
|
||||
color: #64748b;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.catalog-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0 12px;
|
||||
}
|
||||
@media (max-width: 720px) {
|
||||
.catalog-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
.delete-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 16px;
|
||||
align-items: center;
|
||||
}
|
||||
.json-box {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user