Expand SuperAdmin web for SMS, servers, plugins, admins, and UI config.
Align SMS tenant panel with Hamkar, add Jalali dates, and support hamkar/khanegi appearance editing. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+104
-31
@@ -2,13 +2,14 @@
|
||||
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 { ArrowRightOutlined, PlusOutlined, DeleteOutlined } 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 uploading = ref(false)
|
||||
|
||||
const isEdit = computed(() => Boolean(route.params.id))
|
||||
|
||||
@@ -19,9 +20,7 @@ const form = reactive({
|
||||
nameEn: '',
|
||||
domain: '',
|
||||
avatarUrl: '',
|
||||
primaryColor: '#1d4ed8',
|
||||
secondaryColor: '#0f172a',
|
||||
backgroundColor: '#ffffff',
|
||||
appVersion: '1',
|
||||
nodeServer: '',
|
||||
nodeWs: '',
|
||||
appUrl: '',
|
||||
@@ -44,9 +43,6 @@ 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
|
||||
}
|
||||
|
||||
@@ -60,9 +56,7 @@ async function load() {
|
||||
nameEn: data.nameEn || '',
|
||||
domain: data.domain,
|
||||
avatarUrl: data.avatarUrl || '',
|
||||
primaryColor: data.primaryColor || '#1d4ed8',
|
||||
secondaryColor: data.secondaryColor || '#0f172a',
|
||||
backgroundColor: data.backgroundColor || '#ffffff',
|
||||
appVersion: data.appVersion || '1',
|
||||
nodeServer: data.nodeServer,
|
||||
nodeWs: data.nodeWs || '',
|
||||
appUrl: data.appUrl || '',
|
||||
@@ -78,6 +72,35 @@ async function load() {
|
||||
}
|
||||
}
|
||||
|
||||
async function uploadLogo(options: {
|
||||
file: string | Blob | File
|
||||
onSuccess?: (body?: unknown) => void
|
||||
onError?: (e: Error) => void
|
||||
}) {
|
||||
const file = options.file
|
||||
if (!(file instanceof File) && !(file instanceof Blob)) return
|
||||
|
||||
uploading.value = true
|
||||
try {
|
||||
const body = new FormData()
|
||||
body.append('file', file)
|
||||
const { data } = await api.post<{ url: string }>('/tenants/upload-avatar', body)
|
||||
form.avatarUrl = data.url
|
||||
options.onSuccess?.(data)
|
||||
message.success('لوگو آپلود شد')
|
||||
} catch (e: any) {
|
||||
const msg = e?.response?.data?.message
|
||||
options.onError?.(e instanceof Error ? e : new Error(String(msg || 'upload failed')))
|
||||
message.error(Array.isArray(msg) ? msg.join('، ') : msg || 'آپلود ناموفق')
|
||||
} finally {
|
||||
uploading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function clearLogo() {
|
||||
form.avatarUrl = ''
|
||||
}
|
||||
|
||||
async function save() {
|
||||
saving.value = true
|
||||
try {
|
||||
@@ -85,7 +108,6 @@ async function save() {
|
||||
if (!payload.remoteApiToken) {
|
||||
delete (payload as any).remoteApiToken
|
||||
}
|
||||
// node_ws فقط برای همکار حیاتی است؛ برای خانگی اختیاری (SSE)
|
||||
if (isEdit.value) {
|
||||
await api.patch(`/tenants/${route.params.id}`, payload)
|
||||
message.success('بهروزرسانی شد')
|
||||
@@ -150,27 +172,34 @@ onMounted(load)
|
||||
</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://..." />
|
||||
<h3>لوگو</h3>
|
||||
<p class="field-hint">
|
||||
از طریق /get_user بهعنوان <code dir="ltr">avatar</code> به نسخهها فرستاده میشود.
|
||||
<template v-if="form.product === 'khanegi'"> رنگ و تم از صفحه «ظاهر اپ» تنظیم میشود.</template>
|
||||
</p>
|
||||
<a-form-item name="avatarUrl">
|
||||
<div class="logo-upload-row">
|
||||
<a-upload
|
||||
name="file"
|
||||
list-type="picture-card"
|
||||
:show-upload-list="false"
|
||||
accept="image/jpeg,image/png,image/gif,image/webp,image/svg+xml"
|
||||
:custom-request="uploadLogo"
|
||||
:disabled="uploading"
|
||||
>
|
||||
<div v-if="form.avatarUrl" class="logo-preview">
|
||||
<img :src="form.avatarUrl" alt="لوگو" />
|
||||
</div>
|
||||
<div v-else class="logo-placeholder">
|
||||
<PlusOutlined />
|
||||
<div>{{ uploading ? 'در حال آپلود…' : 'آپلود لوگو' }}</div>
|
||||
</div>
|
||||
</a-upload>
|
||||
<a-button v-if="form.avatarUrl" danger @click="clearLogo">
|
||||
<DeleteOutlined /> حذف لوگو
|
||||
</a-button>
|
||||
</div>
|
||||
</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">
|
||||
@@ -200,6 +229,17 @@ onMounted(load)
|
||||
<a-form-item label="فعال در رجیستری">
|
||||
<a-switch v-model:checked="form.isActive" />
|
||||
</a-form-item>
|
||||
<a-form-item label="ورژن اپ" name="appVersion">
|
||||
<a-input
|
||||
v-model:value="form.appVersion"
|
||||
size="large"
|
||||
dir="ltr"
|
||||
placeholder="مثلاً 2.0.1"
|
||||
/>
|
||||
<template #extra>
|
||||
از طریق <code dir="ltr">/get_version</code> به اپ فرستاده میشود. با تغییرش، کلاینت کش کانفیگ را رفرش و صفحه را reload میکند.
|
||||
</template>
|
||||
</a-form-item>
|
||||
<a-form-item label="یادداشت داخلی">
|
||||
<a-textarea v-model:value="form.notes" :rows="3" />
|
||||
</a-form-item>
|
||||
@@ -215,3 +255,36 @@ onMounted(load)
|
||||
<a-spin />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.field-hint {
|
||||
margin: 0 0 12px;
|
||||
color: #64748b;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.logo-upload-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.logo-preview,
|
||||
.logo-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.logo-preview img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user