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:
2026-08-17 20:14:17 +03:30
co-authored by Cursor
parent 0e7f97d938
commit b879d085d3
14 changed files with 3103 additions and 41 deletions
+69 -14
View File
@@ -23,6 +23,7 @@ const form = reactive({
nameFa: '',
nameEn: '',
domain: '',
domains: [] as string[],
avatarUrl: '',
appVersion: '1',
uiVariant: 'modular' as 'modular' | 'newTheme',
@@ -51,6 +52,7 @@ async function load() {
if (!isEdit.value) {
const q = String(route.query.product || '')
form.product = q === 'khanegi' ? 'khanegi' : 'hamkar'
form.domains = []
createdAt.value = null
return
}
@@ -65,6 +67,7 @@ async function load() {
nameFa: data.nameFa,
nameEn: data.nameEn || '',
domain: data.domain,
domains: Array.isArray(data.domains) ? data.domains.map(String) : [],
avatarUrl: data.avatarUrl || '',
appVersion: data.appVersion || '1',
uiVariant: data.uiVariant === 'newTheme' ? 'newTheme' : 'modular',
@@ -119,7 +122,10 @@ async function save() {
}
saving.value = true
try {
const payload = { ...form }
const payload = {
...form,
domains: form.domains.map((item) => item.trim()).filter(Boolean),
}
if (!payload.remoteApiToken) {
delete (payload as any).remoteApiToken
}
@@ -153,19 +159,30 @@ onMounted(load)
<h1 class="page-title">{{ isEdit ? 'مدیریت نسخه' : 'نسخه فروشی جدید' }}</h1>
<p class="page-sub">محصول: {{ productLabel }} · تنظیمات رجیستری برند (/get_user)</p>
</div>
<a-button
v-if="isEdit && form.product === 'khanegi'"
type="primary"
ghost
@click="
router.push({
name: form.uiVariant === 'newTheme' ? 'version-ui' : 'version-ui-modular',
params: { id: route.params.id },
})
"
>
ظاهر اپ
</a-button>
<a-space v-if="isEdit && form.product === 'khanegi'" wrap>
<a-button
@click="
router.push({
name: 'version-program-settings',
params: { id: route.params.id },
})
"
>
تنظیمات برنامه
</a-button>
<a-button
type="primary"
ghost
@click="
router.push({
name: form.uiVariant === 'newTheme' ? 'version-ui' : 'version-ui-modular',
params: { id: route.params.id },
})
"
>
ظاهر اپ
</a-button>
</a-space>
</div>
<a-form :model="form" layout="vertical" @finish="save">
@@ -189,6 +206,29 @@ onMounted(load)
<a-form-item label="دامنه فروشگاه" name="domain" :rules="[{ required: true, message: 'لازم است' }]">
<a-input v-model:value="form.domain" size="large" placeholder="example.com" dir="ltr" />
</a-form-item>
<a-form-item label="دامنه‌ها و IPهای اضافی">
<p class="field-hint">
برای /get_user. داخل همان محصول (خانگی یا همکار) هر دامنه/IP فقط روی یک نسخه می‌ماند؛
اگر از قبل روی نسخه دیگری از همین محصول باشد، از آنجا برداشته و به این نسخه اضافه می‌شود.
خانگی و همکار روی هم اثر ندارند.
</p>
<div class="alias-list">
<div v-for="(_, index) in form.domains" :key="index" class="alias-row">
<a-input
v-model:value="form.domains[index]"
size="large"
dir="ltr"
placeholder="مثلاً 192.168.1.10 یا shop.example.com"
/>
<a-button danger @click="form.domains.splice(index, 1)">
<DeleteOutlined />
</a-button>
</div>
<a-button @click="form.domains.push('')">
<PlusOutlined /> افزودن دامنه / IP
</a-button>
</div>
</a-form-item>
<a-form-item v-if="isEdit" label="تاریخ ایجاد">
<a-input :value="formatIranDateTime(createdAt)" size="large" disabled />
</a-form-item>
@@ -322,4 +362,19 @@ onMounted(load)
max-height: 100%;
object-fit: contain;
}
.alias-list {
display: grid;
gap: 8px;
}
.alias-row {
display: flex;
gap: 8px;
align-items: center;
}
.alias-row :deep(.ant-input) {
flex: 1;
}
</style>