Add Khanegi modular appearance editor and variant routing.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,371 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted, reactive, ref } 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(true)
|
||||||
|
const saving = ref(false)
|
||||||
|
const switching = ref(false)
|
||||||
|
const tenantName = ref('')
|
||||||
|
const domain = ref('')
|
||||||
|
const uiVariant = ref<'modular' | 'newTheme'>('modular')
|
||||||
|
|
||||||
|
const brand = reactive({
|
||||||
|
primaryColor: '#ffffff',
|
||||||
|
secondaryColor: '#2563EB',
|
||||||
|
backgroundColor: '#fafafa',
|
||||||
|
})
|
||||||
|
|
||||||
|
const NAV_ITEMS = ['خانه', 'خرید و فروش', 'کیف پول', 'تراکنشها', 'پروفایل']
|
||||||
|
|
||||||
|
function swatchHex(value: string, fallback = '#cbd5e1') {
|
||||||
|
return /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(String(value || '').trim())
|
||||||
|
? String(value).trim()
|
||||||
|
: fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
function textOn(bg: string) {
|
||||||
|
const hex = swatchHex(bg, '#ffffff').replace('#', '')
|
||||||
|
const full =
|
||||||
|
hex.length === 3
|
||||||
|
? hex
|
||||||
|
.split('')
|
||||||
|
.map((c) => c + c)
|
||||||
|
.join('')
|
||||||
|
: hex
|
||||||
|
const n = parseInt(full, 16)
|
||||||
|
const r = (n >> 16) & 255
|
||||||
|
const g = (n >> 8) & 255
|
||||||
|
const b = n & 255
|
||||||
|
return (r * 299 + g * 587 + b * 114) / 1000 > 128 ? '#0f172a' : '#ffffff'
|
||||||
|
}
|
||||||
|
|
||||||
|
const palette = computed(() => ({
|
||||||
|
header: swatchHex(brand.primaryColor, '#ffffff'),
|
||||||
|
accent: swatchHex(brand.secondaryColor, '#2563EB'),
|
||||||
|
page: swatchHex(brand.backgroundColor, '#fafafa'),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const previewChrome = computed(() => {
|
||||||
|
const p = palette.value
|
||||||
|
const onHeader = textOn(p.header)
|
||||||
|
return {
|
||||||
|
shell: {
|
||||||
|
background: p.page,
|
||||||
|
borderRadius: '18px',
|
||||||
|
border: '1px solid #e2e8f0',
|
||||||
|
overflow: 'hidden' as const,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
background: p.header,
|
||||||
|
color: onHeader,
|
||||||
|
borderBottom:
|
||||||
|
onHeader === '#ffffff' ? '1px solid rgba(255,255,255,0.12)' : '1px solid #e2e8f0',
|
||||||
|
},
|
||||||
|
accent: p.accent,
|
||||||
|
muted: onHeader === '#ffffff' ? 'rgba(255,255,255,0.72)' : '#616161',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
async function load() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const { data } = await api.get(`/tenants/${route.params.id}`)
|
||||||
|
if (data.product !== 'khanegi') {
|
||||||
|
if (data.product === 'hamkar') {
|
||||||
|
router.replace({ name: 'version-ui-hamkar', params: { id: String(route.params.id) } })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
message.warning('ظاهر اپ برای این محصول فعال نیست')
|
||||||
|
router.replace({ name: 'version-edit', params: { id: String(route.params.id) } })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const variant = data.uiVariant === 'newTheme' ? 'newTheme' : 'modular'
|
||||||
|
uiVariant.value = variant
|
||||||
|
if (variant === 'newTheme') {
|
||||||
|
router.replace({ name: 'version-ui', params: { id: String(route.params.id) } })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tenantName.value = data.nameFa || ''
|
||||||
|
domain.value = data.domain || ''
|
||||||
|
brand.primaryColor = String(data.primaryColor || brand.primaryColor)
|
||||||
|
brand.secondaryColor = String(data.secondaryColor || brand.secondaryColor)
|
||||||
|
brand.backgroundColor = String(data.backgroundColor || brand.backgroundColor)
|
||||||
|
} catch (e: any) {
|
||||||
|
message.error(e?.response?.data?.message || 'خطا در بارگذاری ظاهر ماژولار')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
saving.value = true
|
||||||
|
try {
|
||||||
|
await api.patch(`/tenants/${route.params.id}`, {
|
||||||
|
uiVariant: 'modular',
|
||||||
|
primaryColor: brand.primaryColor,
|
||||||
|
secondaryColor: brand.secondaryColor,
|
||||||
|
backgroundColor: brand.backgroundColor,
|
||||||
|
})
|
||||||
|
message.success('ظاهر ماژولار ذخیره شد')
|
||||||
|
} catch (e: any) {
|
||||||
|
const msg = e?.response?.data?.message
|
||||||
|
message.error(Array.isArray(msg) ? msg.join('، ') : msg || 'ذخیره ناموفق')
|
||||||
|
} finally {
|
||||||
|
saving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function switchToNewTheme() {
|
||||||
|
switching.value = true
|
||||||
|
try {
|
||||||
|
await api.patch(`/tenants/${route.params.id}`, {
|
||||||
|
uiVariant: 'newTheme',
|
||||||
|
primaryColor: brand.primaryColor,
|
||||||
|
secondaryColor: brand.secondaryColor,
|
||||||
|
backgroundColor: brand.backgroundColor,
|
||||||
|
})
|
||||||
|
message.success('حالت ظاهر به تم جدید تغییر کرد')
|
||||||
|
router.replace({ name: 'version-ui', params: { id: String(route.params.id) } })
|
||||||
|
} catch (e: any) {
|
||||||
|
const msg = e?.response?.data?.message
|
||||||
|
message.error(Array.isArray(msg) ? msg.join('، ') : msg || 'تغییر حالت ناموفق')
|
||||||
|
} finally {
|
||||||
|
switching.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(load)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="page-shell">
|
||||||
|
<div class="head-row">
|
||||||
|
<a-button type="text" @click="router.push({ name: 'product-versions', params: { product: 'khanegi' } })">
|
||||||
|
<ArrowRightOutlined />
|
||||||
|
</a-button>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<h1 class="page-title">ظاهر اپ خانگی — ماژولار</h1>
|
||||||
|
<p class="page-sub">
|
||||||
|
{{ tenantName || 'نسخه' }}
|
||||||
|
<template v-if="domain"> · {{ domain }}</template>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<a-space>
|
||||||
|
<a-select
|
||||||
|
:value="uiVariant"
|
||||||
|
style="width: 180px"
|
||||||
|
:options="[
|
||||||
|
{ value: 'modular', label: 'Modular' },
|
||||||
|
{ value: 'newTheme', label: 'New theme' },
|
||||||
|
]"
|
||||||
|
:disabled="switching || saving || loading"
|
||||||
|
@change="(v: string) => v === 'newTheme' && switchToNewTheme()"
|
||||||
|
/>
|
||||||
|
<a-button type="primary" size="large" :loading="saving" @click="save">ذخیره</a-button>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-spin :spinning="loading || switching">
|
||||||
|
<p class="hint">
|
||||||
|
لوگو را از صفحه مدیریت نسخه تغییر دهید. اینجا فقط سه رنگ اصلی مثل همکار تنظیم میشود.
|
||||||
|
</p>
|
||||||
|
<div class="layout">
|
||||||
|
<section class="form-section preview">
|
||||||
|
<h3>پیشنمایش</h3>
|
||||||
|
<div class="app-preview" :style="previewChrome.shell">
|
||||||
|
<div class="app-preview-header" :style="previewChrome.header">
|
||||||
|
<div
|
||||||
|
v-for="(item, i) in NAV_ITEMS"
|
||||||
|
:key="item"
|
||||||
|
class="app-preview-nav"
|
||||||
|
:style="{ color: i === 0 ? previewChrome.accent : previewChrome.muted }"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="app-preview-dot"
|
||||||
|
:style="{ background: i === 0 ? previewChrome.accent : previewChrome.muted }"
|
||||||
|
/>
|
||||||
|
{{ item }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="app-preview-body">
|
||||||
|
<div class="app-preview-card">
|
||||||
|
<div class="app-preview-card-title">خوش آمدگویی</div>
|
||||||
|
<div class="app-preview-card-line" />
|
||||||
|
<div class="app-preview-card-line short" />
|
||||||
|
</div>
|
||||||
|
<div class="app-preview-price" :style="{ borderColor: palette.accent }">
|
||||||
|
<span>نقد فردا</span>
|
||||||
|
<strong :style="{ color: palette.accent }">۸۱٬۴۵۰٬۰۰۰</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="form-section">
|
||||||
|
<h3>رنگها</h3>
|
||||||
|
<div class="palette-grid">
|
||||||
|
<label class="palette-card">
|
||||||
|
<span class="palette-title">هدر</span>
|
||||||
|
<span class="palette-swatch" :style="{ background: palette.header }">
|
||||||
|
<input
|
||||||
|
:value="palette.header"
|
||||||
|
type="color"
|
||||||
|
@input="brand.primaryColor = ($event.target as HTMLInputElement).value"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<a-input v-model:value="brand.primaryColor" dir="ltr" size="large" class="palette-hex" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="palette-card">
|
||||||
|
<span class="palette-title">اکسنت</span>
|
||||||
|
<span class="palette-swatch" :style="{ background: palette.accent }">
|
||||||
|
<input
|
||||||
|
:value="palette.accent"
|
||||||
|
type="color"
|
||||||
|
@input="brand.secondaryColor = ($event.target as HTMLInputElement).value"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<a-input v-model:value="brand.secondaryColor" dir="ltr" size="large" class="palette-hex" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="palette-card">
|
||||||
|
<span class="palette-title">پسزمینه</span>
|
||||||
|
<span class="palette-swatch" :style="{ background: palette.page }">
|
||||||
|
<input
|
||||||
|
:value="palette.page"
|
||||||
|
type="color"
|
||||||
|
@input="brand.backgroundColor = ($event.target as HTMLInputElement).value"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<a-input v-model:value="brand.backgroundColor" dir="ltr" size="large" class="palette-hex" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.head-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.hint {
|
||||||
|
color: #64748b;
|
||||||
|
margin: 0 0 16px;
|
||||||
|
}
|
||||||
|
.layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(280px, 360px) 1fr;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
.form-section {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
.form-section h3 {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
}
|
||||||
|
.palette-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.palette-card {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 72px 56px 1fr;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.palette-title {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.palette-swatch {
|
||||||
|
width: 56px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.palette-swatch input[type='color'] {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
opacity: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.palette-hex {
|
||||||
|
font-family: ui-monospace, monospace;
|
||||||
|
}
|
||||||
|
.app-preview-header {
|
||||||
|
display: flex;
|
||||||
|
gap: 14px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.app-preview-nav {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.app-preview-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
.app-preview-body {
|
||||||
|
padding: 16px;
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.app-preview-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 14px;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
.app-preview-card-title {
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.app-preview-card-line {
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 99px;
|
||||||
|
background: #e2e8f0;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.app-preview-card-line.short {
|
||||||
|
width: 55%;
|
||||||
|
}
|
||||||
|
.app-preview-price {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 14px;
|
||||||
|
border: 1px solid;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.layout {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
.head-row {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -18,6 +18,7 @@ type Tenant = {
|
|||||||
nodeServer: string
|
nodeServer: string
|
||||||
notes?: string | null
|
notes?: string | null
|
||||||
hasAppUiConfig?: boolean
|
hasAppUiConfig?: boolean
|
||||||
|
uiVariant?: 'modular' | 'newTheme'
|
||||||
}
|
}
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@@ -144,6 +145,9 @@ onMounted(load)
|
|||||||
بررسی محصول
|
بررسی محصول
|
||||||
</a-tag>
|
</a-tag>
|
||||||
<a-tag v-if="item.hasAppUiConfig" color="purple">ظاهر اپ</a-tag>
|
<a-tag v-if="item.hasAppUiConfig" color="purple">ظاهر اپ</a-tag>
|
||||||
|
<a-tag v-if="product === 'khanegi'" :color="item.uiVariant === 'newTheme' ? 'geekblue' : 'cyan'">
|
||||||
|
{{ item.uiVariant === 'newTheme' ? 'New theme' : 'Modular' }}
|
||||||
|
</a-tag>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -157,7 +161,12 @@ onMounted(load)
|
|||||||
style="flex: 1"
|
style="flex: 1"
|
||||||
@click="
|
@click="
|
||||||
router.push({
|
router.push({
|
||||||
name: product === 'hamkar' ? 'version-ui-hamkar' : 'version-ui',
|
name:
|
||||||
|
product === 'hamkar'
|
||||||
|
? 'version-ui-hamkar'
|
||||||
|
: item.uiVariant === 'newTheme'
|
||||||
|
? 'version-ui'
|
||||||
|
: 'version-ui-modular',
|
||||||
params: { id: item.id },
|
params: { id: item.id },
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const form = reactive({
|
|||||||
domain: '',
|
domain: '',
|
||||||
avatarUrl: '',
|
avatarUrl: '',
|
||||||
appVersion: '1',
|
appVersion: '1',
|
||||||
|
uiVariant: 'modular' as 'modular' | 'newTheme',
|
||||||
nodeServer: '',
|
nodeServer: '',
|
||||||
nodeWs: '',
|
nodeWs: '',
|
||||||
appUrl: '',
|
appUrl: '',
|
||||||
@@ -57,6 +58,7 @@ async function load() {
|
|||||||
domain: data.domain,
|
domain: data.domain,
|
||||||
avatarUrl: data.avatarUrl || '',
|
avatarUrl: data.avatarUrl || '',
|
||||||
appVersion: data.appVersion || '1',
|
appVersion: data.appVersion || '1',
|
||||||
|
uiVariant: data.uiVariant === 'newTheme' ? 'newTheme' : 'modular',
|
||||||
nodeServer: data.nodeServer,
|
nodeServer: data.nodeServer,
|
||||||
nodeWs: data.nodeWs || '',
|
nodeWs: data.nodeWs || '',
|
||||||
appUrl: data.appUrl || '',
|
appUrl: data.appUrl || '',
|
||||||
@@ -142,7 +144,12 @@ onMounted(load)
|
|||||||
v-if="isEdit && form.product === 'khanegi'"
|
v-if="isEdit && form.product === 'khanegi'"
|
||||||
type="primary"
|
type="primary"
|
||||||
ghost
|
ghost
|
||||||
@click="router.push({ name: 'version-ui', params: { id: route.params.id } })"
|
@click="
|
||||||
|
router.push({
|
||||||
|
name: form.uiVariant === 'newTheme' ? 'version-ui' : 'version-ui-modular',
|
||||||
|
params: { id: route.params.id },
|
||||||
|
})
|
||||||
|
"
|
||||||
>
|
>
|
||||||
ظاهر اپ
|
ظاهر اپ
|
||||||
</a-button>
|
</a-button>
|
||||||
@@ -175,7 +182,10 @@ onMounted(load)
|
|||||||
<h3>لوگو</h3>
|
<h3>لوگو</h3>
|
||||||
<p class="field-hint">
|
<p class="field-hint">
|
||||||
از طریق /get_user بهعنوان <code dir="ltr">avatar</code> به نسخهها فرستاده میشود.
|
از طریق /get_user بهعنوان <code dir="ltr">avatar</code> به نسخهها فرستاده میشود.
|
||||||
<template v-if="form.product === 'khanegi'"> رنگ و تم از صفحه «ظاهر اپ» تنظیم میشود.</template>
|
<template v-if="form.product === 'khanegi'">
|
||||||
|
رنگ و تم از صفحه «ظاهر اپ» تنظیم میشود
|
||||||
|
({{ form.uiVariant === 'newTheme' ? 'New theme' : 'Modular' }}).
|
||||||
|
</template>
|
||||||
</p>
|
</p>
|
||||||
<a-form-item name="avatarUrl">
|
<a-form-item name="avatarUrl">
|
||||||
<div class="logo-upload-row">
|
<div class="logo-upload-row">
|
||||||
|
|||||||
@@ -23,8 +23,10 @@ const router = useRouter()
|
|||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
const resetting = ref(false)
|
const resetting = ref(false)
|
||||||
|
const switching = ref(false)
|
||||||
const tenantName = ref('')
|
const tenantName = ref('')
|
||||||
const product = ref<'hamkar' | 'khanegi'>('khanegi')
|
const product = ref<'hamkar' | 'khanegi'>('khanegi')
|
||||||
|
const uiVariant = ref<'modular' | 'newTheme'>('newTheme')
|
||||||
const activeTab = ref('theme')
|
const activeTab = ref('theme')
|
||||||
const advancedJson = ref('')
|
const advancedJson = ref('')
|
||||||
const jsonError = ref('')
|
const jsonError = ref('')
|
||||||
@@ -158,6 +160,14 @@ async function load() {
|
|||||||
router.replace({ name: 'version-edit', params: { id: String(route.params.id) } })
|
router.replace({ name: 'version-edit', params: { id: String(route.params.id) } })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const variant = data.uiVariant === 'newTheme' ? 'newTheme' : 'modular'
|
||||||
|
uiVariant.value = variant
|
||||||
|
if (variant === 'modular') {
|
||||||
|
router.replace({ name: 'version-ui-modular', params: { id: String(route.params.id) } })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const hadSnapshot = Boolean(data.appUiConfig?.defaultSnapshot)
|
const hadSnapshot = Boolean(data.appUiConfig?.defaultSnapshot)
|
||||||
if (data.appUiConfig && typeof data.appUiConfig === 'object') {
|
if (data.appUiConfig && typeof data.appUiConfig === 'object') {
|
||||||
Object.assign(config, createDefaultKhanegiUiConfig(), data.appUiConfig)
|
Object.assign(config, createDefaultKhanegiUiConfig(), data.appUiConfig)
|
||||||
@@ -204,6 +214,7 @@ async function persistConfig() {
|
|||||||
const colors = payload.app.theme.colors
|
const colors = payload.app.theme.colors
|
||||||
await api.patch(`/tenants/${route.params.id}`, {
|
await api.patch(`/tenants/${route.params.id}`, {
|
||||||
appUiConfig: payload,
|
appUiConfig: payload,
|
||||||
|
uiVariant: 'newTheme',
|
||||||
primaryColor: colors.primary,
|
primaryColor: colors.primary,
|
||||||
secondaryColor: colors.secondary,
|
secondaryColor: colors.secondary,
|
||||||
backgroundColor: colors.background,
|
backgroundColor: colors.background,
|
||||||
@@ -214,6 +225,26 @@ async function persistConfig() {
|
|||||||
syncAdvancedJson()
|
syncAdvancedJson()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function switchToModular() {
|
||||||
|
switching.value = true
|
||||||
|
try {
|
||||||
|
const colors = config.app.theme.colors
|
||||||
|
await api.patch(`/tenants/${route.params.id}`, {
|
||||||
|
uiVariant: 'modular',
|
||||||
|
primaryColor: colors.primary,
|
||||||
|
secondaryColor: colors.secondary,
|
||||||
|
backgroundColor: colors.background,
|
||||||
|
})
|
||||||
|
message.success('حالت ظاهر به ماژولار تغییر کرد')
|
||||||
|
router.replace({ name: 'version-ui-modular', params: { id: String(route.params.id) } })
|
||||||
|
} catch (e: any) {
|
||||||
|
const msg = e?.response?.data?.message
|
||||||
|
message.error(Array.isArray(msg) ? msg.join('، ') : msg || 'تغییر حالت ناموفق')
|
||||||
|
} finally {
|
||||||
|
switching.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
saving.value = true
|
saving.value = true
|
||||||
try {
|
try {
|
||||||
@@ -277,10 +308,22 @@ onMounted(load)
|
|||||||
<ArrowRightOutlined />
|
<ArrowRightOutlined />
|
||||||
</a-button>
|
</a-button>
|
||||||
<div style="flex: 1">
|
<div style="flex: 1">
|
||||||
<h1 class="page-title">ظاهر اپ خانگی</h1>
|
<h1 class="page-title">ظاهر اپ خانگی — تم جدید</h1>
|
||||||
<p class="page-sub">{{ tenantName }} · ۳ تم آماده + سفارشی</p>
|
<p class="page-sub">{{ tenantName }} · ۳ تم آماده + سفارشی</p>
|
||||||
</div>
|
</div>
|
||||||
<a-button type="primary" size="large" :loading="saving" @click="save">ذخیره ظاهر</a-button>
|
<a-space>
|
||||||
|
<a-select
|
||||||
|
:value="uiVariant"
|
||||||
|
style="width: 180px"
|
||||||
|
:options="[
|
||||||
|
{ value: 'modular', label: 'Modular' },
|
||||||
|
{ value: 'newTheme', label: 'New theme' },
|
||||||
|
]"
|
||||||
|
:disabled="switching || saving || loading"
|
||||||
|
@change="(v: string) => v === 'modular' && switchToModular()"
|
||||||
|
/>
|
||||||
|
<a-button type="primary" size="large" :loading="saving" @click="save">ذخیره ظاهر</a-button>
|
||||||
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section class="form-section preset-section">
|
<section class="form-section preset-section">
|
||||||
|
|||||||
@@ -84,6 +84,11 @@ const router = createRouter({
|
|||||||
name: 'version-ui',
|
name: 'version-ui',
|
||||||
component: () => import('@/pages/UiConfigPage.vue'),
|
component: () => import('@/pages/UiConfigPage.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'versions/:id/ui-modular',
|
||||||
|
name: 'version-ui-modular',
|
||||||
|
component: () => import('@/pages/KhanegiModularUiConfigPage.vue'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'versions/:id/ui-hamkar',
|
path: 'versions/:id/ui-hamkar',
|
||||||
name: 'version-ui-hamkar',
|
name: 'version-ui-hamkar',
|
||||||
|
|||||||
Reference in New Issue
Block a user