218 lines
7.8 KiB
Vue
218 lines
7.8 KiB
Vue
<script setup lang="ts">
|
|||
|
|
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||
|
|
import { useRoute, useRouter } from 'vue-router'
|
||
|
|
import { message } from 'ant-design-vue'
|
||
|
|
import { ArrowRightOutlined } from '@ant-design/icons-vue'
|
||
|
|
import api from '@/api/http'
|
||
|
|
|
||
|
|
const route = useRoute()
|
||
|
|
const router = useRouter()
|
||
|
|
const loading = ref(false)
|
||
|
|
const saving = ref(false)
|
||
|
|
|
||
|
|
const isEdit = computed(() => Boolean(route.params.id))
|
||
|
|
|
||
|
|
const form = reactive({
|
||
|
|
product: 'hamkar' as 'hamkar' | 'khanegi',
|
||
|
|
slug: '',
|
||
|
|
nameFa: '',
|
||
|
|
nameEn: '',
|
||
|
|
domain: '',
|
||
|
|
avatarUrl: '',
|
||
|
|
primaryColor: '#1d4ed8',
|
||
|
|
secondaryColor: '#0f172a',
|
||
|
|
backgroundColor: '#ffffff',
|
||
|
|
nodeServer: '',
|
||
|
|
nodeWs: '',
|
||
|
|
appUrl: '',
|
||
|
|
adminUrl: '',
|
||
|
|
remoteApiToken: '',
|
||
|
|
isActive: true,
|
||
|
|
notes: '',
|
||
|
|
})
|
||
|
|
|
||
|
|
const productLabel = computed(() => (form.product === 'hamkar' ? 'همکار' : 'خانگی'))
|
||
|
|
|
||
|
|
function backTarget() {
|
||
|
|
return {
|
||
|
|
name: 'product-versions' as const,
|
||
|
|
params: { product: form.product },
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async function load() {
|
||
|
|
if (!isEdit.value) {
|
||
|
|
const q = String(route.query.product || '')
|
||
|
|
form.product = q === 'khanegi' ? 'khanegi' : 'hamkar'
|
||
|
|
if (form.product === 'khanegi') {
|
||
|
|
form.primaryColor = '#b45309'
|
||
|
|
}
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
loading.value = true
|
||
|
|
try {
|
||
|
|
const { data } = await api.get(`/tenants/${route.params.id}`)
|
||
|
|
Object.assign(form, {
|
||
|
|
product: data.product,
|
||
|
|
slug: data.slug,
|
||
|
|
nameFa: data.nameFa,
|
||
|
|
nameEn: data.nameEn || '',
|
||
|
|
domain: data.domain,
|
||
|
|
avatarUrl: data.avatarUrl || '',
|
||
|
|
primaryColor: data.primaryColor || '#1d4ed8',
|
||
|
|
secondaryColor: data.secondaryColor || '#0f172a',
|
||
|
|
backgroundColor: data.backgroundColor || '#ffffff',
|
||
|
|
nodeServer: data.nodeServer,
|
||
|
|
nodeWs: data.nodeWs || '',
|
||
|
|
appUrl: data.appUrl || '',
|
||
|
|
adminUrl: data.adminUrl || '',
|
||
|
|
remoteApiToken: '',
|
||
|
|
isActive: data.isActive,
|
||
|
|
notes: data.notes || '',
|
||
|
|
})
|
||
|
|
} catch (e: any) {
|
||
|
|
message.error(e?.response?.data?.message || 'خطا در بارگذاری')
|
||
|
|
} finally {
|
||
|
|
loading.value = false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async function save() {
|
||
|
|
saving.value = true
|
||
|
|
try {
|
||
|
|
const payload = { ...form }
|
||
|
|
if (!payload.remoteApiToken) {
|
||
|
|
delete (payload as any).remoteApiToken
|
||
|
|
}
|
||
|
|
// node_ws فقط برای همکار حیاتی است؛ برای خانگی اختیاری (SSE)
|
||
|
|
if (isEdit.value) {
|
||
|
|
await api.patch(`/tenants/${route.params.id}`, payload)
|
||
|
|
message.success('بهروزرسانی شد')
|
||
|
|
} else {
|
||
|
|
await api.post('/tenants', payload)
|
||
|
|
message.success('نسخه ایجاد شد')
|
||
|
|
}
|
||
|
|
router.push(backTarget())
|
||
|
|
} catch (e: any) {
|
||
|
|
const msg = e?.response?.data?.message
|
||
|
|
message.error(Array.isArray(msg) ? msg.join('، ') : msg || 'ذخیره ناموفق')
|
||
|
|
} finally {
|
||
|
|
saving.value = false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
watch(() => route.fullPath, load)
|
||
|
|
onMounted(load)
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<template>
|
||
|
|
<div class="page-shell" v-if="!loading">
|
||
|
|
<div style="display: flex; align-items: flex-start; gap: 8px; margin-bottom: 12px">
|
||
|
|
<a-button type="text" @click="router.push(backTarget())">
|
||
|
|
<ArrowRightOutlined />
|
||
|
|
</a-button>
|
||
|
|
<div style="flex: 1">
|
||
|
|
<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: 'version-ui', params: { id: route.params.id } })"
|
||
|
|
>
|
||
|
|
ظاهر اپ
|
||
|
|
</a-button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<a-form :model="form" layout="vertical" @finish="save">
|
||
|
|
<section class="form-section">
|
||
|
|
<h3>هویت نسخه</h3>
|
||
|
|
<a-form-item label="محصول" name="product">
|
||
|
|
<a-select v-model:value="form.product" :disabled="isEdit">
|
||
|
|
<a-select-option value="hamkar">همکار</a-select-option>
|
||
|
|
<a-select-option value="khanegi">خانگی</a-select-option>
|
||
|
|
</a-select>
|
||
|
|
</a-form-item>
|
||
|
|
<a-form-item label="نام فارسی" name="nameFa" :rules="[{ required: true, message: 'لازم است' }]">
|
||
|
|
<a-input v-model:value="form.nameFa" size="large" placeholder="مثلاً باختر گلد" />
|
||
|
|
</a-form-item>
|
||
|
|
<a-form-item label="slug لاتین" name="slug" :rules="[{ required: true, message: 'لازم است' }]">
|
||
|
|
<a-input v-model:value="form.slug" size="large" placeholder="bakhtargold" dir="ltr" />
|
||
|
|
</a-form-item>
|
||
|
|
<a-form-item label="نام لاتین (اختیاری)" name="nameEn">
|
||
|
|
<a-input v-model:value="form.nameEn" size="large" dir="ltr" />
|
||
|
|
</a-form-item>
|
||
|
|
<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>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<section class="form-section">
|
||
|
|
<h3>برند و ظاهر</h3>
|
||
|
|
<a-form-item label="لوگو (URL)" name="avatarUrl">
|
||
|
|
<a-input v-model:value="form.avatarUrl" size="large" dir="ltr" placeholder="https://..." />
|
||
|
|
</a-form-item>
|
||
|
|
<a-row :gutter="12">
|
||
|
|
<a-col :span="8">
|
||
|
|
<a-form-item label="اصلی">
|
||
|
|
<a-input v-model:value="form.primaryColor" type="color" />
|
||
|
|
</a-form-item>
|
||
|
|
</a-col>
|
||
|
|
<a-col :span="8">
|
||
|
|
<a-form-item label="ثانویه">
|
||
|
|
<a-input v-model:value="form.secondaryColor" type="color" />
|
||
|
|
</a-form-item>
|
||
|
|
</a-col>
|
||
|
|
<a-col :span="8">
|
||
|
|
<a-form-item label="پسزمینه">
|
||
|
|
<a-input v-model:value="form.backgroundColor" type="color" />
|
||
|
|
</a-form-item>
|
||
|
|
</a-col>
|
||
|
|
</a-row>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<section class="form-section">
|
||
|
|
<h3>اتصال سرور این نسخه</h3>
|
||
|
|
<a-form-item label="آدرس API (node_server)" name="nodeServer" :rules="[{ required: true, message: 'لازم است' }]">
|
||
|
|
<a-input v-model:value="form.nodeServer" size="large" dir="ltr" placeholder="https://example.com/server" />
|
||
|
|
</a-form-item>
|
||
|
|
<a-form-item
|
||
|
|
:label="form.product === 'hamkar' ? 'WebSocket (node_ws)' : 'WebSocket اختیاری (خانگی بیشتر SSE است)'"
|
||
|
|
name="nodeWs"
|
||
|
|
>
|
||
|
|
<a-input v-model:value="form.nodeWs" size="large" dir="ltr" />
|
||
|
|
</a-form-item>
|
||
|
|
<a-form-item label="app_url" name="appUrl">
|
||
|
|
<a-input v-model:value="form.appUrl" size="large" dir="ltr" />
|
||
|
|
</a-form-item>
|
||
|
|
<a-form-item label="admin_url" name="adminUrl">
|
||
|
|
<a-input v-model:value="form.adminUrl" size="large" dir="ltr" />
|
||
|
|
</a-form-item>
|
||
|
|
<a-form-item label="توکن ریموت سوپر (برای تنظیمات داخل سرور نسخه)" name="remoteApiToken">
|
||
|
|
<a-input-password v-model:value="form.remoteApiToken" size="large" />
|
||
|
|
</a-form-item>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<section class="form-section">
|
||
|
|
<h3>وضعیت</h3>
|
||
|
|
<a-form-item label="فعال در رجیستری">
|
||
|
|
<a-switch v-model:checked="form.isActive" />
|
||
|
|
</a-form-item>
|
||
|
|
<a-form-item label="یادداشت داخلی">
|
||
|
|
<a-textarea v-model:value="form.notes" :rows="3" />
|
||
|
|
</a-form-item>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<div class="sticky-actions">
|
||
|
|
<a-button type="primary" html-type="submit" size="large" block :loading="saving">ذخیره</a-button>
|
||
|
|
<a-button size="large" @click="router.push(backTarget())">انصراف</a-button>
|
||
|
|
</div>
|
||
|
|
</a-form>
|
||
|
|
</div>
|
||
|
|
<div v-else class="page-shell" style="display: grid; place-items: center; min-height: 200px">
|
||
|
|
<a-spin />
|
||
|
|
</div>
|
||
|
|
</template>
|