527 lines
14 KiB
Vue
527 lines
14 KiB
Vue
<template>
|
|
<div class="banners-page">
|
|
<div class="banners-page__body">
|
|
<a-collapse
|
|
v-if="!showSettingsProfileBanners"
|
|
v-model:activeKey="sliderActiveKey"
|
|
:bordered="false"
|
|
class="banners-page__collapse"
|
|
>
|
|
<a-collapse-panel key="slider">
|
|
<template #header>
|
|
<div class="banners-page__collapse-header">
|
|
<BaseIcon icon="vuesax-linear:slider-horizontal-1" size="18" class="banners-page__collapse-icon" />
|
|
<div class="banners-page__collapse-text">
|
|
<span class="banners-page__collapse-title">بنرهای اسلایدر</span>
|
|
<span class="banners-page__collapse-desc">تصاویر بالای صفحهٔ اصلی اپ مشتری — هر ردیف یک اسلاید</span>
|
|
</div>
|
|
<AdminFieldHelp :text="bannersPageIntro" size="sm" />
|
|
<a-badge
|
|
:count="state.slider.length"
|
|
:number-style="{ backgroundColor: '#d4af37', color: '#fff', fontSize: '11px' }"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<div class="banners-page__panel-actions">
|
|
<BaseButton size="small" type="primary" @click="addRow">
|
|
<span>افزودن اسلاید</span>
|
|
<BaseIcon icon="vuesax-linear:add" size="17" />
|
|
</BaseButton>
|
|
<BaseButton type="primary" :loading="saving" @click="handleSubmit">
|
|
ذخیره تغییرات
|
|
</BaseButton>
|
|
</div>
|
|
|
|
<div v-if="!state.slider.length" class="banners-page__empty">
|
|
<div class="banners-page__empty-inner">
|
|
<BaseIcon icon="vuesax-linear:image" size="48" class="banners-page__empty-icon" />
|
|
<p class="banners-page__empty-title">هنوز بنری اضافه نشده</p>
|
|
<p class="banners-page__empty-text">اولین اسلاید را بسازید تا در اپ مشتری نمایش داده شود</p>
|
|
<BaseButton type="primary" @click="addRow">
|
|
<span>افزودن اسلاید</span>
|
|
<BaseIcon icon="vuesax-linear:add" size="17" />
|
|
</BaseButton>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-else class="banners-page__list">
|
|
<article
|
|
v-for="(item, index) in state.slider"
|
|
:key="index"
|
|
class="banner-card"
|
|
:class="{ 'banner-card--hidden': !item.show }"
|
|
>
|
|
<header class="banner-card__head">
|
|
<div class="banner-card__head-start">
|
|
<span class="banner-card__index">{{ index + 1 }}</span>
|
|
<div>
|
|
<div class="banner-card__title">اسلاید {{ index + 1 }}</div>
|
|
<div class="banner-card__meta">
|
|
<span class="banner-card__pill" :class="{ 'banner-card__pill--off': !item.show }">
|
|
{{ item.show ? 'فعال در اپ' : 'مخفی' }}
|
|
</span>
|
|
<span class="banner-card__ratio">۱۶:۹</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="banner-card__head-end">
|
|
<label class="banner-card__switch">
|
|
<span>نمایش</span>
|
|
<a-switch v-model:checked="item.show" size="small" />
|
|
<AdminFieldHelp :text="bannersShowHelp" />
|
|
</label>
|
|
<button type="button" class="banner-card__remove" title="حذف اسلاید" @click="removeRow(index)">
|
|
<BaseIcon icon="vuesax-linear:trash" size="18" />
|
|
</button>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="banner-card__content">
|
|
<div class="banner-card__preview-wrap">
|
|
<AdminLabelWithHelp label="پیشنمایش تصویر" :help="bannersImageSectionHelp" />
|
|
<div class="banner-card__preview">
|
|
<BannerImageUpload
|
|
:image-url="item.icon"
|
|
@upload="item.icon = $event"
|
|
@remove="item.icon = null"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="banner-card__fields" :class="{ 'banner-card__fields--stack': item.typeClick === 'modal' }">
|
|
<div class="banner-card__field">
|
|
<AdminLabelWithHelp label="بعد از کلیک مشتری" :help="clickEventHelp(item.typeClick)" />
|
|
<BasicSelect
|
|
v-model:value="item.typeClick"
|
|
:items="typeList"
|
|
value-name="lable"
|
|
key-name="value"
|
|
/>
|
|
</div>
|
|
|
|
<div v-if="item.typeClick === 'link'" class="banner-card__field banner-card__field--wide">
|
|
<AdminLabelWithHelp label="آدرس لینک" :help="bannersLinkHelp" />
|
|
<BasicInput v-model:value="item.link" placeholder="https://example.com" />
|
|
</div>
|
|
|
|
<div v-if="item.typeClick === 'modal'" class="banner-card__field banner-card__field--wide">
|
|
<AdminLabelWithHelp label="متن پیام" :help="bannersModalHelp" />
|
|
<a-textarea
|
|
:value="htmlToPlain(item.modalText)"
|
|
:rows="7"
|
|
class="banner-card__message"
|
|
placeholder="متن پیامی که مشتری در پنجرهٔ بازشو میبیند..."
|
|
@update:value="item.modalText = plainToHtml($event)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
</div>
|
|
</a-collapse-panel>
|
|
</a-collapse>
|
|
|
|
<SettingsProfileBanners v-if="showSettingsProfileBanners" />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import BannerImageUpload from './components/BannerImageUpload.vue'
|
|
import SettingsProfileBanners from './components/SettingsProfileBanners.vue'
|
|
import AdminFieldHelp from '@/components/AdminFieldHelp.vue'
|
|
import AdminLabelWithHelp from '@/components/AdminLabelWithHelp.vue'
|
|
import { useAppBannersAdmin } from '@/composables/useAppBannersAdmin'
|
|
import { hasAppUiConfig } from '@/utils/config'
|
|
import {
|
|
bannersEventHelp,
|
|
bannersImageSectionHelp,
|
|
bannersLinkHelp,
|
|
bannersModalHelp,
|
|
bannersNoEventHelp,
|
|
bannersPageIntro,
|
|
bannersShareHelp,
|
|
bannersShowHelp,
|
|
} from '@/utils/adminHelpCopy'
|
|
|
|
const { state, saving, save } = useAppBannersAdmin()
|
|
/** پیشفرض کالپس بنرهای اسلایدر بسته است */
|
|
const sliderActiveKey = ref<(string | number)[]>([])
|
|
const showSettingsProfileBanners = computed(() => hasAppUiConfig())
|
|
|
|
const typeList = ref([
|
|
{ lable: 'بدون رویداد', value: null },
|
|
{ lable: 'باز شدن لینک', value: 'link' },
|
|
{ lable: 'پنجره بازشو', value: 'modal' },
|
|
{ lable: 'اشتراکگذاری', value: 'share' },
|
|
])
|
|
|
|
function clickEventHelp(typeClick: string | null) {
|
|
if (typeClick === 'link') return `${bannersEventHelp}\n\n${bannersLinkHelp}`
|
|
if (typeClick === 'modal') return `${bannersEventHelp}\n\n${bannersModalHelp}`
|
|
if (typeClick === 'share') return `${bannersEventHelp}\n\n${bannersShareHelp}`
|
|
return `${bannersEventHelp}\n\n${bannersNoEventHelp}`
|
|
}
|
|
|
|
function htmlToPlain(html: string | null | undefined): string {
|
|
if (!html) return ''
|
|
if (typeof document === 'undefined') {
|
|
return html.replace(/<br\s*\/?>/gi, '\n').replace(/<[^>]+>/g, '').trim()
|
|
}
|
|
const el = document.createElement('div')
|
|
el.innerHTML = html
|
|
return (el.textContent ?? el.innerText ?? '').trim()
|
|
}
|
|
|
|
function plainToHtml(text: string | null | undefined): string | null {
|
|
const trimmed = (text ?? '').trim()
|
|
if (!trimmed) return null
|
|
const escaped = trimmed
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
return `<p>${escaped.replace(/\n/g, '<br>')}</p>`
|
|
}
|
|
|
|
function addRow() {
|
|
if (!Array.isArray(state.slider)) {
|
|
state.slider = []
|
|
}
|
|
state.slider.push({
|
|
icon: null,
|
|
show: true,
|
|
caption: null,
|
|
link: null,
|
|
typeClick: null,
|
|
modalText: null,
|
|
})
|
|
}
|
|
|
|
function removeRow(index: number) {
|
|
state.slider.splice(index, 1)
|
|
}
|
|
|
|
async function handleSubmit() {
|
|
await save('اطلاعات با موفقیت ذخیره شد')
|
|
}
|
|
</script>
|
|
|
|
<style scoped lang="less">
|
|
.banners-page {
|
|
display: flex;
|
|
flex-direction: column;
|
|
width: 100%;
|
|
min-width: 0;
|
|
height: 100%;
|
|
min-height: 0;
|
|
}
|
|
|
|
.banners-page__body {
|
|
flex: 1;
|
|
min-height: 0;
|
|
overflow-y: auto;
|
|
padding: 1rem 1.25rem 1.5rem;
|
|
}
|
|
|
|
.banners-page__collapse {
|
|
background: transparent;
|
|
}
|
|
|
|
.banners-page__collapse :deep(.ant-collapse-item) {
|
|
margin-bottom: 0;
|
|
overflow: hidden;
|
|
border: 1px solid var(--admin-border);
|
|
border-radius: 12px;
|
|
background: var(--admin-bg-elevated);
|
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
}
|
|
|
|
.banners-page__collapse :deep(.ant-collapse-header) {
|
|
padding: 0.875rem 1rem !important;
|
|
align-items: center !important;
|
|
background: color-mix(in srgb, var(--admin-bg-muted) 70%, transparent);
|
|
color: var(--admin-text);
|
|
}
|
|
|
|
.banners-page__collapse :deep(.ant-collapse-content) {
|
|
border-top: 1px solid var(--admin-border);
|
|
background: var(--admin-bg-elevated);
|
|
}
|
|
|
|
.banners-page__collapse :deep(.ant-collapse-content-box) {
|
|
padding: 1rem !important;
|
|
}
|
|
|
|
.banners-page__collapse-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
width: 100%;
|
|
padding-inline-end: 0.5rem;
|
|
}
|
|
|
|
.banners-page__collapse-icon {
|
|
flex-shrink: 0;
|
|
color: #d4af37;
|
|
}
|
|
|
|
.banners-page__collapse-text {
|
|
display: flex;
|
|
min-width: 0;
|
|
flex: 1;
|
|
flex-direction: column;
|
|
gap: 0.15rem;
|
|
}
|
|
|
|
.banners-page__collapse-title {
|
|
font-size: 0.9375rem;
|
|
font-weight: 700;
|
|
color: var(--admin-text);
|
|
}
|
|
|
|
.banners-page__collapse-desc {
|
|
font-size: 0.75rem;
|
|
color: var(--admin-text-secondary);
|
|
}
|
|
|
|
.banners-page__panel-actions {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
justify-content: flex-end;
|
|
gap: 0.625rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.banners-page__empty {
|
|
display: flex;
|
|
width: 100%;
|
|
min-height: 14rem;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.banners-page__empty-inner {
|
|
display: flex;
|
|
width: 100%;
|
|
max-width: 28rem;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 0.625rem;
|
|
padding: 3rem 1.5rem;
|
|
border: 1px dashed var(--admin-border);
|
|
border-radius: 16px;
|
|
background: var(--admin-bg-elevated);
|
|
text-align: center;
|
|
}
|
|
|
|
.banners-page__empty-icon {
|
|
color: var(--admin-text-secondary);
|
|
opacity: 0.65;
|
|
}
|
|
|
|
.banners-page__empty-title {
|
|
margin: 0.25rem 0 0;
|
|
font-size: 1rem;
|
|
font-weight: 600;
|
|
color: var(--admin-text);
|
|
}
|
|
|
|
.banners-page__empty-text {
|
|
margin: 0 0 0.75rem;
|
|
font-size: 0.875rem;
|
|
color: var(--admin-text-secondary);
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.banners-page__list {
|
|
display: flex;
|
|
width: 100%;
|
|
flex-direction: column;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.banner-card {
|
|
width: 100%;
|
|
overflow: hidden;
|
|
border: 1px solid var(--admin-border);
|
|
border-radius: 14px;
|
|
background: var(--admin-bg-elevated);
|
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
|
|
|
|
&--hidden {
|
|
opacity: 0.88;
|
|
}
|
|
}
|
|
|
|
.banner-card__head {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 0.75rem;
|
|
padding: 0.875rem 1rem;
|
|
border-bottom: 1px solid var(--admin-border);
|
|
background: color-mix(in srgb, var(--admin-bg-muted) 70%, transparent);
|
|
}
|
|
|
|
.banner-card__head-start {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
min-width: 0;
|
|
}
|
|
|
|
.banner-card__index {
|
|
display: inline-flex;
|
|
width: 2.25rem;
|
|
height: 2.25rem;
|
|
flex-shrink: 0;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border-radius: 10px;
|
|
background: var(--admin-primary, #f5b800);
|
|
color: #1a1a1a;
|
|
font-size: 0.9375rem;
|
|
font-weight: 800;
|
|
}
|
|
|
|
.banner-card__title {
|
|
font-size: 0.9375rem;
|
|
font-weight: 700;
|
|
color: var(--admin-text);
|
|
}
|
|
|
|
.banner-card__meta {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
margin-top: 0.2rem;
|
|
}
|
|
|
|
.banner-card__pill {
|
|
font-size: 0.75rem;
|
|
color: #52c41a;
|
|
|
|
&--off {
|
|
color: var(--admin-text-secondary);
|
|
}
|
|
}
|
|
|
|
.banner-card__ratio {
|
|
font-size: 0.6875rem;
|
|
color: var(--admin-text-secondary);
|
|
}
|
|
|
|
.banner-card__head-end {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.banner-card__switch {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.4rem;
|
|
margin: 0;
|
|
font-size: 0.8125rem;
|
|
color: var(--admin-text-secondary);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.banner-card__remove {
|
|
display: inline-flex;
|
|
width: 2.25rem;
|
|
height: 2.25rem;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border: 1px solid rgba(255, 77, 79, 0.35);
|
|
border-radius: 10px;
|
|
background: rgba(255, 77, 79, 0.08);
|
|
color: #ff4d4f;
|
|
cursor: pointer;
|
|
transition: background 0.15s ease;
|
|
|
|
&:hover {
|
|
background: rgba(255, 77, 79, 0.16);
|
|
}
|
|
}
|
|
|
|
.banner-card__content {
|
|
display: grid;
|
|
grid-template-columns: 1fr;
|
|
gap: 1.25rem;
|
|
padding: 1rem;
|
|
|
|
@media (min-width: 900px) {
|
|
grid-template-columns: minmax(280px, 42%) minmax(0, 1fr);
|
|
padding: 1.125rem;
|
|
}
|
|
}
|
|
|
|
.banner-card__preview-wrap {
|
|
display: flex;
|
|
min-width: 0;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.banner-card__preview {
|
|
aspect-ratio: 16 / 9;
|
|
min-height: 11rem;
|
|
}
|
|
|
|
.banner-card__fields {
|
|
display: grid;
|
|
grid-template-columns: 1fr;
|
|
gap: 1rem;
|
|
min-width: 0;
|
|
align-content: start;
|
|
|
|
&--stack {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
.banner-card__field {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
min-width: 0;
|
|
|
|
&--wide {
|
|
grid-column: 1 / -1;
|
|
}
|
|
}
|
|
|
|
.banner-card__message {
|
|
width: 100%;
|
|
min-height: 10.5rem;
|
|
padding: 0.75rem 0.875rem;
|
|
border: 1px solid var(--admin-border) !important;
|
|
border-radius: 10px !important;
|
|
background: var(--admin-bg-muted) !important;
|
|
color: var(--admin-text) !important;
|
|
font-size: 0.875rem;
|
|
line-height: 1.7;
|
|
resize: vertical;
|
|
|
|
&::placeholder {
|
|
color: var(--admin-text-secondary);
|
|
}
|
|
|
|
&:hover {
|
|
border-color: color-mix(in srgb, var(--admin-primary, #f5b800) 35%, var(--admin-border)) !important;
|
|
}
|
|
|
|
&:focus {
|
|
border-color: var(--admin-primary, #f5b800) !important;
|
|
box-shadow: 0 0 0 2px color-mix(in srgb, var(--admin-primary, #f5b800) 18%, transparent);
|
|
outline: none;
|
|
}
|
|
}
|
|
</style>
|