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>
This commit is contained in:
@@ -1,16 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { message, Modal } from 'ant-design-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 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({
|
||||
@@ -33,10 +37,15 @@ function parseJsonItems(raw: string): Array<Record<string, unknown>> {
|
||||
throw new Error('JSON باید آرایه باشد: [{"key":"name_fa",...}]')
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
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
|
||||
return Promise.reject()
|
||||
}
|
||||
let items: Array<Record<string, unknown>> = []
|
||||
try {
|
||||
@@ -45,78 +54,77 @@ async function submit() {
|
||||
} else {
|
||||
if (!form.key.trim()) {
|
||||
message.warning('اسم کلید لازم است (مثلاً name_fa)')
|
||||
return
|
||||
return Promise.reject()
|
||||
}
|
||||
items = [{ ...form, key: form.key.trim() }]
|
||||
}
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || 'JSON نامعتبر است')
|
||||
return
|
||||
return Promise.reject()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
},
|
||||
})
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
function removeKey() {
|
||||
async function submitDelete() {
|
||||
const key = deleteKey.value.trim()
|
||||
if (!key) {
|
||||
message.warning('اسم کلید را بنویس (مثلاً name_fa)')
|
||||
return
|
||||
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
|
||||
}
|
||||
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>
|
||||
<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">
|
||||
@@ -143,26 +151,37 @@ function removeKey() {
|
||||
<a-textarea v-model:value="jsonText" :rows="10" dir="ltr" class="json-box" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-modal>
|
||||
|
||||
<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>
|
||||
<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;
|
||||
@@ -175,13 +194,9 @@ function removeKey() {
|
||||
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;
|
||||
.catalog-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
.json-box {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
|
||||
Reference in New Issue
Block a user