Files
web/src/components/SettingsCatalogPanel.vue
T
mohmdsdgandCursor 6dbb15c6e1 feat(versions): shared identity/UI/settings tabs and denser desktop form
Keep the same pill nav on all three Khanegi version pages, tighten the identity form on desktop, and split catalog add/delete into separate modals.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-17 21:39:50 +03:30

206 lines
6.0 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { reactive, ref } from 'vue'
import { message } 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 addOpen = ref(false)
const deleteOpen = ref(false)
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",...}]')
}
function errText(e: any) {
const msg = e?.response?.data?.message
return Array.isArray(msg) ? msg.join('، ') : msg || 'خطا'
}
async function submitAdd() {
if (!props.canWrite) {
message.warning('دسترسی نوشتن ندارید')
return Promise.reject()
}
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 Promise.reject()
}
items = [{ ...form, key: form.key.trim() }]
}
} catch (e: any) {
message.error(e?.message || 'JSON نامعتبر است')
return Promise.reject()
}
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 = ''
addOpen.value = false
} catch (e: any) {
message.error(errText(e) || 'ثبت ناموفق')
return Promise.reject()
} finally {
saving.value = false
}
}
async function submitDelete() {
const key = deleteKey.value.trim()
if (!key) {
message.warning('اسم کلید را بنویس (مثلاً name_fa)')
return Promise.reject()
}
deleting.value = true
try {
await api.delete('/setting-templates/keys', { params: { key } })
message.success('کلید حذف شد')
deleteKey.value = ''
deleteOpen.value = false
} catch (e: any) {
message.error(errText(e) || 'حذف ناموفق')
return Promise.reject()
} finally {
deleting.value = false
}
}
</script>
<template>
<div class="catalog-actions">
<a-button v-if="canWrite" @click="addOpen = true">
<PlusOutlined /> افزودن کلید
</a-button>
<a-button v-if="canWrite" danger ghost @click="deleteOpen = true">
<DeleteOutlined /> حذف کلید
</a-button>
</div>
<a-modal
v-model:open="addOpen"
title="افزودن کلید تنظیمات"
ok-text="ثبت کلید"
cancel-text="انصراف"
:confirm-loading="saving"
:width="720"
destroy-on-close
@ok="submitAdd"
>
<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-modal>
<a-modal
v-model:open="deleteOpen"
title="حذف کلید از همهٔ نسخه‌ها"
ok-text="حذف از همه"
ok-type="danger"
cancel-text="انصراف"
:confirm-loading="deleting"
:width="520"
destroy-on-close
@ok="submitDelete"
>
<p class="field-hint">
این کلید از قالب و تنظیمات همهٔ پروژه‌های خانگی پاک می‌شود. اسم کلید را بنویس، نه عنوان فارسی.
</p>
<a-input
v-model:value="deleteKey"
dir="ltr"
size="large"
placeholder="مثلاً name_fa"
/>
</a-modal>
</template>
<style scoped>
.catalog-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.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;
}
}
.json-box {
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 13px;
}
</style>