added function for trigger in admin
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
<template>
|
||||
<div class="banner-image-upload">
|
||||
<a-upload
|
||||
v-if="!imageUrl"
|
||||
class="banner-image-upload__zone"
|
||||
name="file"
|
||||
accept="image/*"
|
||||
:show-upload-list="false"
|
||||
:action="uploadURL"
|
||||
:headers="fileHeader"
|
||||
@change="onChange"
|
||||
>
|
||||
<div class="banner-image-upload__empty">
|
||||
<div v-if="loading" class="banner-image-upload__loading">
|
||||
<LoadingOutlined />
|
||||
<span>در حال آپلود...</span>
|
||||
</div>
|
||||
<template v-else>
|
||||
<BaseIcon icon="vuesax-linear:gallery-add" size="40" class="banner-image-upload__icon" />
|
||||
<span class="banner-image-upload__cta">افزودن تصویر بنر</span>
|
||||
<span class="banner-image-upload__hint">کلیک کنید — تصویر افقی با نسبت ۱۶:۹</span>
|
||||
</template>
|
||||
</div>
|
||||
</a-upload>
|
||||
|
||||
<div v-else class="banner-image-upload__filled">
|
||||
<img :src="imageUrl" alt="" class="banner-image-upload__img" />
|
||||
<div class="banner-image-upload__bar">
|
||||
<a-upload
|
||||
name="file"
|
||||
accept="image/*"
|
||||
:show-upload-list="false"
|
||||
:action="uploadURL"
|
||||
:headers="fileHeader"
|
||||
@change="onChange"
|
||||
>
|
||||
<button type="button" class="banner-image-upload__btn banner-image-upload__btn--primary">
|
||||
تغییر تصویر
|
||||
</button>
|
||||
</a-upload>
|
||||
<button
|
||||
type="button"
|
||||
class="banner-image-upload__btn banner-image-upload__btn--danger"
|
||||
@click="emit('remove')"
|
||||
>
|
||||
حذف تصویر
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { LoadingOutlined } from '@ant-design/icons-vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { uploadURL } from '@root/config'
|
||||
import { getToken } from '@/utils'
|
||||
import { normalizeUploadFileResponse } from '@/utils/file'
|
||||
|
||||
defineProps<{
|
||||
imageUrl?: string | null
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
upload: [url: string]
|
||||
remove: []
|
||||
}>()
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const fileHeader = computed(() => ({
|
||||
Authorization: `Bearer ${getToken()}`,
|
||||
}))
|
||||
|
||||
function onChange({ file }: { file: { status?: string; response?: unknown } }) {
|
||||
if (file.status === 'uploading') {
|
||||
loading.value = true
|
||||
return
|
||||
}
|
||||
|
||||
if (file.status === 'error') {
|
||||
loading.value = false
|
||||
message.error('خطا در آپلود تصویر')
|
||||
return
|
||||
}
|
||||
|
||||
if (file.status === 'done') {
|
||||
loading.value = false
|
||||
const fileResource = normalizeUploadFileResponse(file.response)
|
||||
if (!fileResource?.full_src) {
|
||||
message.error('خطا در ساخت آدرس تصویر')
|
||||
return
|
||||
}
|
||||
emit('upload', fileResource.full_src)
|
||||
message.success('تصویر با موفقیت آپلود شد')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.banner-image-upload {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.banner-image-upload__zone {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
:deep(.ant-upload) {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:deep(.ant-upload-select) {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
margin: 0 !important;
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
}
|
||||
|
||||
.banner-image-upload__empty {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 10rem;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
padding: 1.25rem;
|
||||
border: 2px dashed color-mix(in srgb, var(--admin-primary, #f5b800) 45%, var(--admin-border));
|
||||
border-radius: 12px;
|
||||
background: color-mix(in srgb, var(--admin-bg-muted) 85%, transparent);
|
||||
color: var(--admin-text-secondary);
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.15s ease, background 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--admin-primary, #f5b800);
|
||||
background: color-mix(in srgb, var(--admin-primary, #f5b800) 8%, var(--admin-bg-muted));
|
||||
}
|
||||
}
|
||||
|
||||
.banner-image-upload__icon {
|
||||
color: var(--admin-primary, #f5b800);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.banner-image-upload__cta {
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 600;
|
||||
color: var(--admin-text);
|
||||
}
|
||||
|
||||
.banner-image-upload__hint {
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.banner-image-upload__loading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.banner-image-upload__filled {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 10rem;
|
||||
overflow: hidden;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--admin-border);
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.banner-image-upload__img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.banner-image-upload__bar {
|
||||
position: absolute;
|
||||
inset: auto 0 0 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem;
|
||||
background: linear-gradient(to top, rgba(0, 0, 0, 0.78), transparent);
|
||||
}
|
||||
|
||||
.banner-image-upload__btn {
|
||||
padding: 0.4rem 0.85rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.35);
|
||||
border-radius: 8px;
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
color: #fff;
|
||||
font-size: 0.8125rem;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s ease;
|
||||
|
||||
&--primary:hover {
|
||||
background: rgba(0, 0, 0, 0.65);
|
||||
}
|
||||
|
||||
&--danger {
|
||||
border-color: rgba(255, 77, 79, 0.55);
|
||||
color: #ffb3b3;
|
||||
}
|
||||
|
||||
&--danger:hover {
|
||||
background: rgba(255, 77, 79, 0.35);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,104 +1,126 @@
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<AdminFieldHelp :text="bannersPageIntro" root-class="mb-4 text-xs md:text-sm" />
|
||||
|
||||
<BaseButton size="small" type="primary" @click="addRow">
|
||||
<span>افزودن بنر</span>
|
||||
<BaseIcon icon="vuesax-linear:add" size="17" />
|
||||
</BaseButton>
|
||||
<AdminFieldHelp
|
||||
text="هر بار «افزودن بنر» یک اسلاید جدید به انتهای لیست اضافه میکند."
|
||||
root-class="mt-2"
|
||||
/>
|
||||
|
||||
<div class="my-4 flex flex-col gap-2">
|
||||
<div
|
||||
class="grid max-h-52vh grid-cols-1 gap-4 overflow-y-scroll md:max-h-75vh md:grid-cols-3"
|
||||
>
|
||||
<Card v-for="(item, index) in benefitsList" :key="index">
|
||||
<div class="mt-3 grid grid-cols-1 gap-4">
|
||||
<div class="my-2 flex items-center justify-between gap-2">
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<div class="text-sm font-medium">نسبت تصویر ۱۶:۹</div>
|
||||
<AdminFieldHelp :text="bannersAspectHelp" />
|
||||
</div>
|
||||
<div class="flex shrink-0 flex-col items-end gap-0.5">
|
||||
<div class="flex items-center gap-2">
|
||||
<span>نمایش:</span>
|
||||
<a-switch v-model:checked="item.show" />
|
||||
</div>
|
||||
<AdminFieldHelp :text="bannersShowHelp" root-class="text-left" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<AdminFieldHelp :text="bannersImageHelp" root-class="mb-2 text-center" />
|
||||
<UploadPic
|
||||
:compressed="true"
|
||||
@upload="handleUpload($event, index, 'icon')"
|
||||
@remove="handelRemoveFile(index, 'icon')"
|
||||
:imageUrl="item.icon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 flex flex-col gap-1">
|
||||
<div class="text-sm font-medium">انتخاب رویداد (پس از کلیک):</div>
|
||||
<AdminFieldHelp :text="bannersEventHelp" />
|
||||
<BasicSelect
|
||||
v-model:value="item.typeClick"
|
||||
:items="typeList"
|
||||
value-name="lable"
|
||||
key-name="value"
|
||||
/>
|
||||
<AdminFieldHelp v-if="item.typeClick === 'link'" :text="bannersLinkHelp" />
|
||||
<AdminFieldHelp v-else-if="item.typeClick === 'modal'" :text="bannersModalHelp" />
|
||||
<AdminFieldHelp v-else-if="item.typeClick === 'share'" :text="bannersShareHelp" />
|
||||
<AdminFieldHelp v-else :text="bannersNoEventHelp" />
|
||||
</div>
|
||||
|
||||
<div v-if="item.typeClick == 'link'" class="mt-2 flex flex-col gap-1">
|
||||
<div class="text-sm font-medium">آدرس لینک</div>
|
||||
<BasicInput v-model:value="item.link" placeholder="https://..." />
|
||||
</div>
|
||||
|
||||
<div v-if="item.typeClick == 'modal'" class="mt-2 flex flex-col gap-1">
|
||||
<div class="text-sm font-medium">متن پیام</div>
|
||||
<QuillEditor
|
||||
placeholder="متن پیام"
|
||||
v-model:content="item.modalText"
|
||||
toolbar="full"
|
||||
theme="snow"
|
||||
content-type="html"
|
||||
class="editor-box2"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<div class="banners-page">
|
||||
<header class="banners-page__header">
|
||||
<div class="banners-page__header-text">
|
||||
<div class="banners-page__title-row">
|
||||
<h1 class="banners-page__title">بنرهای اسلایدر</h1>
|
||||
<AdminFieldHelp :text="bannersPageIntro" size="sm" />
|
||||
</div>
|
||||
<p class="banners-page__subtitle">تصاویر بالای صفحهٔ اصلی اپ مشتری — هر ردیف یک اسلاید</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="banners-page__header-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>
|
||||
</header>
|
||||
|
||||
<div class="mt-5 text-left md:mt-0">
|
||||
<BaseButton type="primary" @click="handleSubmit">
|
||||
<span>ذخیره تغییرات</span>
|
||||
</BaseButton>
|
||||
<AdminFieldHelp
|
||||
text="بدون زدن این دکمه، هیچکدام از تغییرات بالا در اپ مشتری دیده نمیشود."
|
||||
root-class="mt-2"
|
||||
/>
|
||||
<div class="banners-page__body">
|
||||
<div v-if="!benefitsList.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 benefitsList"
|
||||
: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>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Card, message } from 'ant-design-vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import type { IBenefits } from '@/model/banner'
|
||||
import UploadPic from '@/components/UploadPic.vue'
|
||||
import BannerImageUpload from './components/BannerImageUpload.vue'
|
||||
import AdminFieldHelp from '@/components/AdminFieldHelp.vue'
|
||||
import AdminLabelWithHelp from '@/components/AdminLabelWithHelp.vue'
|
||||
import { updateBannerConst, getHeaderBannerConst } from '@/api/consts'
|
||||
import {
|
||||
bannersAspectHelp,
|
||||
bannersEventHelp,
|
||||
bannersImageHelp,
|
||||
bannersImageSectionHelp,
|
||||
bannersLinkHelp,
|
||||
bannersModalHelp,
|
||||
bannersNoEventHelp,
|
||||
@@ -106,19 +128,45 @@ import {
|
||||
bannersShareHelp,
|
||||
bannersShowHelp,
|
||||
} from '@/utils/adminHelpCopy'
|
||||
import '@vueup/vue-quill/dist/vue-quill.snow.css'
|
||||
import { QuillEditor } from '@vueup/vue-quill'
|
||||
import { normalizeUploadFileResponse } from '@/utils/file'
|
||||
|
||||
const benefitsList = ref<IBenefits[]>([])
|
||||
const saving = ref(false)
|
||||
let id: number | null = null
|
||||
|
||||
const typeList = ref([
|
||||
{ lable: 'بدون رویداد', value: null },
|
||||
{ lable: 'باز شدن لینک', value: 'link' },
|
||||
{ lable: 'پنجره بازشو', value: 'modal' },
|
||||
{ lable: 'اشتراک گذاری', value: 'share' },
|
||||
{ lable: 'بدون رویداد', value: null },
|
||||
{ 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 parseBannerList(value: string | null | undefined): IBenefits[] {
|
||||
if (!value) return []
|
||||
try {
|
||||
@@ -143,19 +191,8 @@ function addRow() {
|
||||
})
|
||||
}
|
||||
|
||||
function handleUpload(response: unknown, index: number, field: keyof IBenefits) {
|
||||
const fileResource = normalizeUploadFileResponse(response)
|
||||
|
||||
if (!fileResource?.full_src) {
|
||||
message.error('خطا در ساخت آدرس تصویر')
|
||||
return
|
||||
}
|
||||
|
||||
benefitsList.value[index][field] = fileResource.full_src
|
||||
}
|
||||
|
||||
function handelRemoveFile(index: number, field: keyof IBenefits) {
|
||||
benefitsList.value[index][field] = null
|
||||
function removeRow(index: number) {
|
||||
benefitsList.value.splice(index, 1)
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
@@ -165,6 +202,7 @@ async function handleSubmit() {
|
||||
}
|
||||
|
||||
try {
|
||||
saving.value = true
|
||||
await updateBannerConst({
|
||||
id,
|
||||
value: JSON.stringify(benefitsList.value),
|
||||
@@ -173,6 +211,8 @@ async function handleSubmit() {
|
||||
message.success('اطلاعات با موفقیت ذخیره شد')
|
||||
} catch (error) {
|
||||
message.error('خطا در ذخیره اطلاعات')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,18 +231,286 @@ onMounted(async () => {
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.editor-box2 {
|
||||
max-height: 40vh;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
<style scoped lang="less">
|
||||
.banners-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.editor-box2 .ql-editor {
|
||||
min-height: 10vh;
|
||||
.banners-page__header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
padding: 1rem 1.25rem;
|
||||
border-bottom: 1px solid var(--admin-border);
|
||||
background: var(--admin-bg-elevated);
|
||||
}
|
||||
|
||||
.banners-page__title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.banners-page__title {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
color: var(--admin-text);
|
||||
}
|
||||
|
||||
.banners-page__subtitle {
|
||||
margin: 0.35rem 0 0;
|
||||
font-size: 0.8125rem;
|
||||
color: var(--admin-text-secondary);
|
||||
}
|
||||
|
||||
.banners-page__header-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.625rem;
|
||||
}
|
||||
|
||||
.banners-page__body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
padding: 1rem 1.25rem 1.5rem;
|
||||
}
|
||||
|
||||
.banners-page__empty {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: calc(100vh - 12rem);
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user