feat(hamkar-ui): mobile preview and brandManaged for chrome colors
Save sets brandManaged so header+menu share one color and body stays separate, without changing live apps until save. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -31,6 +31,12 @@ export type HamkarUiConfig = {
|
||||
* Laravel settings stay source of truth (prevents visual jumps).
|
||||
*/
|
||||
settingsManaged: boolean
|
||||
/**
|
||||
* When true, Hamkar applies brand colors to mobile chrome (header + bottom menu)
|
||||
* and page body — including theme-two. Default false so live apps stay untouched
|
||||
* until colors are saved from SuperAdmin.
|
||||
*/
|
||||
brandManaged: boolean
|
||||
/** Named CSS mode from live registry (Montpar, limangold, …) or dynamicMode */
|
||||
registryMode: string
|
||||
brand: HamkarBrandColors
|
||||
@@ -103,6 +109,7 @@ export function createDefaultHamkarUiConfig(): HamkarUiConfig {
|
||||
return {
|
||||
kind: 'hamkar',
|
||||
settingsManaged: false,
|
||||
brandManaged: false,
|
||||
registryMode: 'dynamicMode',
|
||||
brand: {
|
||||
primaryColor: '',
|
||||
@@ -151,6 +158,7 @@ export function normalizeHamkarUiConfig(raw: unknown): HamkarUiConfig {
|
||||
return {
|
||||
kind: 'hamkar',
|
||||
settingsManaged: Boolean(input.settingsManaged),
|
||||
brandManaged: Boolean(input.brandManaged),
|
||||
registryMode,
|
||||
brand,
|
||||
settings,
|
||||
@@ -177,6 +185,7 @@ export function hamkarUiToApiPayload(config: HamkarUiConfig) {
|
||||
return {
|
||||
kind: 'hamkar' as const,
|
||||
settingsManaged: normalized.settingsManaged,
|
||||
brandManaged: normalized.brandManaged,
|
||||
registryMode: normalized.registryMode,
|
||||
brand: {
|
||||
primaryColor: emptyToNull(normalized.brand.primaryColor),
|
||||
|
||||
@@ -75,6 +75,6 @@ export function resolveHamkarEffectiveTheme(input: {
|
||||
bg2: panel,
|
||||
colorPrimary: accent,
|
||||
},
|
||||
note: 'حالت dynamicMode: color → هدر (--bg2)، secondary_color → اکسنت (--colorPrimary)، background_color → پسزمینه (--bg).',
|
||||
note: 'حالت dynamicMode: color → هدر و منوی موبایل (--bg2 / --theme-two-header)، secondary_color → اکسنت (--colorPrimary)، background_color → بدنه صفحه (--bg / --theme-two-background).',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,8 @@ const palette = computed(() => ({
|
||||
const previewChrome = computed(() => {
|
||||
const p = palette.value
|
||||
const onHeader = textOn(p.header)
|
||||
const headerEdge =
|
||||
onHeader === '#ffffff' ? '1px solid rgba(255,255,255,0.12)' : '1px solid #e2e8f0'
|
||||
return {
|
||||
shell: {
|
||||
background: p.page,
|
||||
@@ -90,7 +92,12 @@ const previewChrome = computed(() => {
|
||||
header: {
|
||||
background: p.header,
|
||||
color: onHeader,
|
||||
borderBottom: onHeader === '#ffffff' ? '1px solid rgba(255,255,255,0.12)' : '1px solid #e2e8f0',
|
||||
borderBottom: headerEdge,
|
||||
},
|
||||
footer: {
|
||||
background: p.header,
|
||||
color: onHeader,
|
||||
borderTop: headerEdge,
|
||||
},
|
||||
accent: p.accent,
|
||||
muted: onHeader === '#ffffff' ? 'rgba(255,255,255,0.72)' : '#616161',
|
||||
@@ -221,6 +228,8 @@ async function save() {
|
||||
saving.value = true
|
||||
try {
|
||||
config.registryMode = 'dynamicMode'
|
||||
// Opt-in: live apps keep current chrome until colors are saved here.
|
||||
config.brandManaged = true
|
||||
if (!config.defaultSnapshot) {
|
||||
ensureDefaultSnapshot(config, {
|
||||
notes: null,
|
||||
@@ -259,6 +268,8 @@ function resetToDefault() {
|
||||
config.brand.secondaryColor = snap.brand.secondaryColor
|
||||
config.brand.backgroundColor = snap.brand.backgroundColor
|
||||
config.settings = { ...snap.settings }
|
||||
// Named factory modes must not keep brand chrome overrides.
|
||||
config.brandManaged = snap.registryMode === 'dynamicMode'
|
||||
await persist(hamkarUiToApiPayload(config))
|
||||
seedEditableColors(config)
|
||||
message.success('ظاهر اولیه بازیابی شد')
|
||||
@@ -294,9 +305,25 @@ onMounted(load)
|
||||
<a-spin :spinning="loading">
|
||||
<div class="layout">
|
||||
<section class="form-section preview">
|
||||
<h3>پیشنمایش</h3>
|
||||
<h3>پیشنمایش موبایل</h3>
|
||||
<p class="hint">هدر و منوی پایین یک رنگاند؛ بدنه صفحه جداست.</p>
|
||||
<div class="app-preview" :style="previewChrome.shell">
|
||||
<div class="app-preview-header" :style="previewChrome.header">
|
||||
<span class="app-preview-user">علی منتظر</span>
|
||||
<span class="app-preview-logo" :style="{ background: previewChrome.accent }" />
|
||||
</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 class="app-preview-footer" :style="previewChrome.footer">
|
||||
<div
|
||||
v-for="(item, i) in NAV_ITEMS"
|
||||
:key="item"
|
||||
@@ -310,17 +337,6 @@ onMounted(load)
|
||||
{{ 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>
|
||||
|
||||
@@ -329,9 +345,12 @@ onMounted(load)
|
||||
<p v-if="hasDefaultSnapshot" class="hint">
|
||||
ظاهر پیشفرض ثبتشده: <strong>{{ defaultModeLabel }}</strong>
|
||||
</p>
|
||||
<p class="hint">
|
||||
تا وقتی «ذخیره ظاهر همکار» نزنید، اپهای آنلاین عوض نمیشوند.
|
||||
</p>
|
||||
<div class="palette-grid">
|
||||
<label class="palette-card">
|
||||
<span class="palette-title">هدر</span>
|
||||
<span class="palette-title">هدر و منو</span>
|
||||
<span class="palette-swatch" :style="{ background: palette.header }">
|
||||
<input
|
||||
:value="palette.header"
|
||||
@@ -365,7 +384,7 @@ onMounted(load)
|
||||
</label>
|
||||
|
||||
<label class="palette-card">
|
||||
<span class="palette-title">پسزمینه</span>
|
||||
<span class="palette-title">بدنه صفحه</span>
|
||||
<span class="palette-swatch" :style="{ background: palette.page }">
|
||||
<input
|
||||
:value="palette.page"
|
||||
@@ -469,12 +488,33 @@ onMounted(load)
|
||||
}
|
||||
|
||||
.app-preview-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 14px;
|
||||
min-height: 52px;
|
||||
}
|
||||
|
||||
.app-preview-user {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.app-preview-logo {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.app-preview-footer {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 10px 8px;
|
||||
min-height: 56px;
|
||||
padding: 8px 6px 10px;
|
||||
min-height: 58px;
|
||||
}
|
||||
|
||||
.app-preview-nav {
|
||||
@@ -482,13 +522,13 @@ onMounted(load)
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 11px;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.app-preview-dot {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 6px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
@@ -497,6 +537,7 @@ onMounted(load)
|
||||
padding: 14px;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
min-height: 160px;
|
||||
}
|
||||
|
||||
.app-preview-card {
|
||||
|
||||
Reference in New Issue
Block a user