added function for trigger in admin
This commit is contained in:
Vendored
+5
@@ -21,6 +21,8 @@ declare module '@vue/runtime-core' {
|
||||
ACollapsePanel: typeof import('ant-design-vue/es')['CollapsePanel']
|
||||
AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider']
|
||||
ADivider: typeof import('ant-design-vue/es')['Divider']
|
||||
AdminFieldHelp: typeof import('./src/components/AdminFieldHelp.vue')['default']
|
||||
AdminLabelWithHelp: typeof import('./src/components/AdminLabelWithHelp.vue')['default']
|
||||
ADrawer: typeof import('ant-design-vue/es')['Drawer']
|
||||
ADropdown: typeof import('ant-design-vue/es')['Dropdown']
|
||||
AEmpty: typeof import('ant-design-vue/es')['Empty']
|
||||
@@ -50,6 +52,9 @@ declare module '@vue/runtime-core' {
|
||||
ASubMenu: typeof import('ant-design-vue/es')['SubMenu']
|
||||
ASwitch: typeof import('ant-design-vue/es')['Switch']
|
||||
ATable: typeof import('ant-design-vue/es')['Table']
|
||||
ATabPane: typeof import('ant-design-vue/es')['TabPane']
|
||||
ATabs: typeof import('ant-design-vue/es')['Tabs']
|
||||
ATag: typeof import('ant-design-vue/es')['Tag']
|
||||
ATextarea: typeof import('ant-design-vue/es')['Textarea']
|
||||
ATimePicker: typeof import('ant-design-vue/es')['TimePicker']
|
||||
ATooltip: typeof import('ant-design-vue/es')['Tooltip']
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
import type {TableInput} from "@core/type/baseTable";
|
||||
import useAxios from "@core/utils/useAxios";
|
||||
import type {PriceTriggers} from "@/model/priceTriggers";
|
||||
import type { TableInput } from '@core/type/baseTable'
|
||||
import useAxios from '@core/utils/useAxios'
|
||||
import type { PriceTriggers } from '@/model/priceTriggers'
|
||||
|
||||
export const getPriceTriggersTable = async (params: TableInput) =>
|
||||
await useAxios.post<Array<PriceTriggers>>('/admin/price_triggers/table', params)
|
||||
await useAxios.post<Array<PriceTriggers>>('/admin/price_triggers/table', params)
|
||||
|
||||
export const createPriceTrigger = async (payload: Partial<PriceTriggers>) =>
|
||||
await useAxios.post<PriceTriggers>('/admin/price_triggers', payload)
|
||||
|
||||
export const updatePriceTrigger = async (id: number, payload: Partial<PriceTriggers>) =>
|
||||
await useAxios.put(`/admin/price_triggers/${id}`, payload)
|
||||
|
||||
export const togglePriceTriggerActive = async (id: number) =>
|
||||
await useAxios.put<{ is_active: boolean }>(`/admin/price_triggers/${id}/toggle`)
|
||||
|
||||
export const deletePriceTrigger = async (id: number) =>
|
||||
await useAxios.delete(`/admin/price_triggers/${id}`)
|
||||
|
||||
@@ -1,25 +1,60 @@
|
||||
<template>
|
||||
<p v-if="text" class="admin-field-help" :class="rootClass">{{ text }}</p>
|
||||
<a-tooltip
|
||||
v-if="text"
|
||||
placement="top"
|
||||
:title="text"
|
||||
overlay-class-name="admin-field-help-tooltip"
|
||||
:overlay-style="{ zIndex: TOOLTIP_ABOVE_MODAL_Z_INDEX }"
|
||||
:overlay-inner-style="{
|
||||
maxWidth: '22rem',
|
||||
direction: 'rtl',
|
||||
textAlign: 'right',
|
||||
whiteSpace: 'pre-line',
|
||||
}"
|
||||
:mouse-enter-delay="0.15"
|
||||
>
|
||||
<span
|
||||
class="admin-field-help"
|
||||
:class="rootClass"
|
||||
role="img"
|
||||
:aria-label="text"
|
||||
tabindex="0"
|
||||
>
|
||||
<IosInfoBadge :size="size" />
|
||||
</span>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import IosInfoBadge from '@/pages/dashboard/components/IosInfoBadge.vue'
|
||||
import { TOOLTIP_ABOVE_MODAL_Z_INDEX } from '@/constants/overlayZIndex'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
text?: string
|
||||
rootClass?: string
|
||||
size?: 'xs' | 'sm' | 'md'
|
||||
}>(),
|
||||
{
|
||||
text: '',
|
||||
rootClass: '',
|
||||
size: 'xs',
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.admin-field-help {
|
||||
margin: 0;
|
||||
font-size: 11px;
|
||||
color: var(--admin-text-secondary);
|
||||
line-height: 1.5;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
line-height: 0;
|
||||
cursor: help;
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.ant-tooltip.admin-field-help-tooltip {
|
||||
z-index: 1250 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<span
|
||||
class="admin-label-with-help inline-flex max-w-full items-center gap-1.5"
|
||||
:class="[textClass, rootClass]"
|
||||
>
|
||||
<span :class="labelClass">{{ label }}</span>
|
||||
<AdminFieldHelp v-if="help" :text="help" :size="size" />
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import AdminFieldHelp from '@/components/AdminFieldHelp.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
label: string
|
||||
help?: string
|
||||
size?: 'xs' | 'sm' | 'md'
|
||||
textClass?: string
|
||||
labelClass?: string
|
||||
rootClass?: string
|
||||
}>(),
|
||||
{
|
||||
help: '',
|
||||
size: 'xs',
|
||||
textClass: 'text-sm',
|
||||
labelClass: '',
|
||||
rootClass: '',
|
||||
}
|
||||
)
|
||||
</script>
|
||||
@@ -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>
|
||||
|
||||
@@ -1,46 +1,57 @@
|
||||
<template>
|
||||
<a-form :model="form" ref="formRef" autocomplete="off">
|
||||
<div class="space-y-3">
|
||||
<a-form-item label="شماره موبایل مشتری" name="mobile" :rules="inputLenRules(11)">
|
||||
<a-form-item name="mobile" :rules="inputLenRules(11)">
|
||||
<template #label>
|
||||
<AdminLabelWithHelp label="شماره موبایل مشتری" :help="giftCardMobileHelp" />
|
||||
</template>
|
||||
<BasicInput v-model:value="form.mobile" type="tel" placeholder="09123456789" />
|
||||
<AdminFieldHelp :text="giftCardMobileHelp" root-class="mt-1" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="نام مشتری" name="name" :rules="inputRules()">
|
||||
<a-form-item name="name" :rules="inputRules()">
|
||||
<template #label>
|
||||
<AdminLabelWithHelp label="نام مشتری" :help="giftCardNameHelp" />
|
||||
</template>
|
||||
<BasicInput v-model:value="form.name" placeholder="نام و نام خانوادگی مشتری" />
|
||||
<AdminFieldHelp :text="giftCardNameHelp" root-class="mt-1" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="نوع کارت هدیه" name="unit" :rules="inputRules()">
|
||||
<a-form-item name="unit" :rules="inputRules()">
|
||||
<template #label>
|
||||
<AdminLabelWithHelp label="نوع کارت هدیه" :help="giftCardUnitHelp" />
|
||||
</template>
|
||||
<a-radio-group v-model:value="form.unit" button-style="solid">
|
||||
<a-radio-button value="1">ریالی</a-radio-button>
|
||||
<a-radio-button value="2">{{ metalLabel.goldWalletShort() }}</a-radio-button>
|
||||
</a-radio-group>
|
||||
<AdminFieldHelp :text="giftCardUnitHelp" root-class="mt-1" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="تعداد کدها" name="count" :rules="inputRulesNumber()">
|
||||
<a-form-item name="count" :rules="inputRulesNumber()">
|
||||
<template #label>
|
||||
<AdminLabelWithHelp label="تعداد کدها" :help="giftCardCountHelp" />
|
||||
</template>
|
||||
<BasicInput v-model:value="form.count" type="tel" placeholder="تعداد کدهای کارت هدیه" />
|
||||
<AdminFieldHelp :text="giftCardCountHelp" root-class="mt-1" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:label="`مقدار ${form.unit === '2' ? '(گرم)' : '(ریال)'}`"
|
||||
name="amount"
|
||||
:rules="inputRulesNumber()"
|
||||
>
|
||||
<a-form-item name="amount" :rules="inputRulesNumber()">
|
||||
<template #label>
|
||||
<AdminLabelWithHelp
|
||||
:label="`مقدار ${form.unit === '2' ? '(گرم)' : '(ریال)'}`"
|
||||
:help="giftCardAmountHelp"
|
||||
/>
|
||||
</template>
|
||||
<BasicInput
|
||||
v-model:value="form.amount"
|
||||
:addon-before="form.unit === '2' ? 'گرم' : 'ریال'"
|
||||
type="tel"
|
||||
placeholder="مقدار کارت هدیه"
|
||||
/>
|
||||
<AdminFieldHelp :text="giftCardAmountHelp" root-class="mt-1" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="تاریخ انقضا" name="expiry_date" :rules="inputRules()">
|
||||
<a-form-item name="expiry_date" :rules="inputRules()">
|
||||
<template #label>
|
||||
<AdminLabelWithHelp label="تاریخ انقضا" :help="giftCardExpiryHelp" />
|
||||
</template>
|
||||
<BaseDatePicker v-model:value="form.expiry_date" format="YYYY-MM-DD" />
|
||||
<AdminFieldHelp :text="giftCardExpiryHelp" root-class="mt-1" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="توضیحات" name="description" class="mb-0">
|
||||
@@ -53,7 +64,7 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormInstance } from 'ant-design-vue'
|
||||
import type { GiftcardInput } from '@/model/giftCard'
|
||||
import AdminFieldHelp from '@/components/AdminFieldHelp.vue'
|
||||
import AdminLabelWithHelp from '@/components/AdminLabelWithHelp.vue'
|
||||
import {
|
||||
giftCardAmountHelp,
|
||||
giftCardCountHelp,
|
||||
|
||||
@@ -34,21 +34,29 @@
|
||||
>
|
||||
<div v-if="isMobile" class="mb-3 text-base font-700">{{ discountSheetTitle }}</div>
|
||||
<a-form :model="discountForm" ref="discountFormRef" autocomplete="off" layout="vertical">
|
||||
<a-form-item label="نام پویش" name="name" :rules="inputRules()">
|
||||
<a-form-item name="name" :rules="inputRules()">
|
||||
<template #label>
|
||||
<AdminLabelWithHelp label="نام پویش" :help="discountNameHelp" />
|
||||
</template>
|
||||
<BasicInput v-model:value="discountForm.name" placeholder="نام پویش" />
|
||||
<AdminFieldHelp :text="discountNameHelp" root-class="mt-1" />
|
||||
</a-form-item>
|
||||
<a-form-item label="درصد تخفیف" name="amount" :rules="inputRulesNumber()">
|
||||
<a-form-item name="amount" :rules="inputRulesNumber()">
|
||||
<template #label>
|
||||
<AdminLabelWithHelp label="درصد تخفیف" :help="discountPercentHelp" />
|
||||
</template>
|
||||
<BasicInput v-model:value="discountForm.amount" type="tel" placeholder="مثلاً 10" addon-before="%" />
|
||||
<AdminFieldHelp :text="discountPercentHelp" root-class="mt-1" />
|
||||
</a-form-item>
|
||||
<a-form-item label="تعداد کدها" name="count" :rules="inputRulesNumber()">
|
||||
<a-form-item name="count" :rules="inputRulesNumber()">
|
||||
<template #label>
|
||||
<AdminLabelWithHelp label="تعداد کدها" :help="discountCountHelp" />
|
||||
</template>
|
||||
<BasicInput v-model:value="discountForm.count" type="tel" />
|
||||
<AdminFieldHelp :text="discountCountHelp" root-class="mt-1" />
|
||||
</a-form-item>
|
||||
<a-form-item label="تاریخ انقضا" name="expiry_date" :rules="inputRules()">
|
||||
<a-form-item name="expiry_date" :rules="inputRules()">
|
||||
<template #label>
|
||||
<AdminLabelWithHelp label="تاریخ انقضا" :help="discountExpiryHelp" />
|
||||
</template>
|
||||
<BaseDatePicker v-model:value="discountForm.expiry_date" format="YYYY-MM-DD" />
|
||||
<AdminFieldHelp :text="discountExpiryHelp" root-class="mt-1" />
|
||||
</a-form-item>
|
||||
<a-form-item label="توضیحات" name="description">
|
||||
<BasicTextArea v-model:value="discountForm.description" place-holder="توضیحات اختیاری — فقط برای یادداشت شما" />
|
||||
@@ -115,7 +123,7 @@ import { isStoreApp } from '@/utils/config'
|
||||
import { inputRules, inputRulesNumber } from '@/utils/inputRules'
|
||||
import BasicInput from '@/components/BasicInput.vue'
|
||||
import BasicTextArea from '@/components/BasicTextArea.vue'
|
||||
import AdminFieldHelp from '@/components/AdminFieldHelp.vue'
|
||||
import AdminLabelWithHelp from '@/components/AdminLabelWithHelp.vue'
|
||||
import {
|
||||
discountCountHelp,
|
||||
discountExpiryHelp,
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<a-form :model="form" ref="formRef" layout="vertical" autocomplete="off">
|
||||
<a-form-item label="مشتری" name="customer_id" :rules="inputRules()">
|
||||
<BasicSelect
|
||||
v-model:value="form.customer_id"
|
||||
:items="customerItems"
|
||||
key-name="id"
|
||||
value-name="label"
|
||||
placeholder="انتخاب مشتری"
|
||||
:disabled="Boolean(editId)"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="شرط قیمت" name="price_condition" :rules="inputRules()">
|
||||
<BasicSelect
|
||||
v-model:value="form.price_condition"
|
||||
:items="conditionItems"
|
||||
key-name="key"
|
||||
value-name="value"
|
||||
placeholder="بزرگتر یا کوچکتر"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="قیمت هدف" name="target_price" :rules="inputRulesNumber()">
|
||||
<BasicInput v-model:value="form.target_price" type="price" addon-before="ریال" placeholder="مثلاً ۱۷۸۵۰۰۰۰۰" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="روش اطلاعرسانی" name="action" :rules="inputRules()">
|
||||
<a-radio-group v-model:value="form.action" button-style="solid">
|
||||
<a-radio-button value="sms">پیامک</a-radio-button>
|
||||
<a-radio-button value="email">ایمیل</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="وضعیت" name="is_active">
|
||||
<a-switch v-model:checked="form.is_active" checked-children="فعال" un-checked-children="غیرفعال" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import type { FormInstance } from 'ant-design-vue'
|
||||
import type { Customer } from '@/model/customer'
|
||||
import type { PriceTriggers } from '@/model/priceTriggers'
|
||||
import { inputRules, inputRulesNumber } from '@/utils/inputRules'
|
||||
|
||||
defineProps<{
|
||||
form: Partial<PriceTriggers>
|
||||
customerItems: Array<{ id: number; label: string }>
|
||||
editId?: number
|
||||
}>()
|
||||
|
||||
const conditionItems = [
|
||||
{ value: 'بزرگتر از', key: '>' },
|
||||
{ value: 'کوچکتر از', key: '<' },
|
||||
]
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
defineExpose({
|
||||
validateFields: () => formRef.value?.validateFields(),
|
||||
resetFields: () => formRef.value?.resetFields(),
|
||||
})
|
||||
</script>
|
||||
@@ -1,27 +1,52 @@
|
||||
<template>
|
||||
<div>
|
||||
<BasicShowContext title="هشدار قیمت">
|
||||
<AdminFieldHelp :text="priceTriggersPageIntro" root-class="mb-3 px-1" />
|
||||
<BaseTable v-bind="tblProps" @register="submit" ref="tbl">
|
||||
<!-- <template #extraHeader>
|
||||
<BaseButton type="primary" size="small" class="flex" @click="emits('add')">
|
||||
<div>افزودن مدیر</div>
|
||||
<BaseIcon icon="vuesax-linear:user-add" Size="16px" />
|
||||
<template #extra>
|
||||
<div class="flex items-center gap-2">
|
||||
<AdminFieldHelp :text="priceTriggersPageIntro" />
|
||||
<BaseButton type="primary" size="small" @click="emits('add')">
|
||||
<span>افزودن هشدار</span>
|
||||
<BaseIcon icon="vuesax-linear:add" size="16" />
|
||||
</BaseButton>
|
||||
</template>-->
|
||||
<template #action="{ column, record }">
|
||||
<div class="gap-2 flex justify-center">
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('info', record)">
|
||||
<template #title>
|
||||
<span>مشخصات</span>
|
||||
</template>
|
||||
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<BaseTable v-bind="tblProps" @register="submit" ref="tbl">
|
||||
<template #extraHeader>
|
||||
<BaseButton type="primary" size="small" class="hidden md:inline-flex" @click="emits('add')">
|
||||
<span>افزودن هشدار</span>
|
||||
<BaseIcon icon="vuesax-linear:add" size="16" />
|
||||
</BaseButton>
|
||||
</template>
|
||||
|
||||
<template #action="{ record }">
|
||||
<div class="flex justify-center gap-2">
|
||||
<a-tooltip placement="bottom">
|
||||
<template #title>ویرایش</template>
|
||||
<Icon
|
||||
icon="vuesax-linear:edit"
|
||||
color="#4572E9"
|
||||
class="cursor-pointer"
|
||||
@click="emits('edit', record)"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('edit', record)">
|
||||
<template #title>
|
||||
<span>ویرایش</span>
|
||||
</template>
|
||||
<Icon icon="vuesax-linear:edit" color="#4572E9" />
|
||||
<a-tooltip placement="bottom">
|
||||
<template #title>{{ record.is_active ? 'غیرفعال کردن' : 'فعال کردن' }}</template>
|
||||
<Icon
|
||||
:icon="record.is_active ? 'vuesax-linear:eye-slash' : 'vuesax-linear:eye'"
|
||||
:color="record.is_active ? '#C53030' : '#25855A'"
|
||||
class="cursor-pointer"
|
||||
@click="emits('toggle', record)"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom">
|
||||
<template #title>حذف</template>
|
||||
<Icon
|
||||
icon="vuesax-linear:trash"
|
||||
color="#C53030"
|
||||
class="cursor-pointer"
|
||||
@click="emits('delete', record.id)"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
@@ -33,67 +58,29 @@
|
||||
<script lang="tsx" setup>
|
||||
import type { TableInput } from '@core/type/baseTable'
|
||||
import useBaseTable from '@core/components/BaseTableOld/hooks/useBaseTable'
|
||||
import { toJalali, toJalaliNoTime } from '@core/utils/useDataTime'
|
||||
import { message, Tag } from 'ant-design-vue'
|
||||
import CustomTag from '@/components/CustomTag.vue'
|
||||
import Icon from '@core/components/BaseIcon/src/Icon.vue'
|
||||
import { changeUserActivation, getUserTable } from '@/api/user'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
import { cols } from '../hooks/priceTriggersColumns'
|
||||
import { changeCustomerActivation } from '@/api/customer'
|
||||
import { getPriceTriggersTable } from '@/api/priceTriggers'
|
||||
import AdminFieldHelp from '@/components/AdminFieldHelp.vue'
|
||||
import { priceTriggersPageIntro } from '@/utils/adminHelpCopy'
|
||||
|
||||
const [submit, reload] = useBaseTable()
|
||||
|
||||
//emits
|
||||
const emits = defineEmits(['delete', 'edit', 'add', 'info'])
|
||||
const emits = defineEmits<{
|
||||
add: []
|
||||
edit: [record: Record<string, unknown>]
|
||||
toggle: [record: Record<string, unknown>]
|
||||
delete: [id: number]
|
||||
}>()
|
||||
|
||||
//define table
|
||||
const table = ref(null)
|
||||
|
||||
//permission
|
||||
function checkPermission(temp: string) {
|
||||
const store = useUserStore()
|
||||
return store.canAccess(temp)
|
||||
}
|
||||
|
||||
const loading = ref<boolean>(false)
|
||||
|
||||
async function changeUserActivationFunction(id: number) {
|
||||
try {
|
||||
loading.value = true
|
||||
await changeUserActivation(id)
|
||||
reload()
|
||||
message.success('تغییر وضعیت با موفقیت انجام شد')
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const tblProps = ref<any>({
|
||||
const tblProps = {
|
||||
columns: cols,
|
||||
api: (params: TableInput) => {
|
||||
return getPriceTriggersTable(params)
|
||||
}
|
||||
//bordered: true
|
||||
})
|
||||
api: (params: TableInput) => getPriceTriggersTable(params),
|
||||
}
|
||||
|
||||
const fetch = () => {
|
||||
// console.log('sss')
|
||||
function fetch() {
|
||||
reload()
|
||||
}
|
||||
// const spinTable = (val: boolean) => {
|
||||
// // console.log('sss')
|
||||
// table?.value?.loading.value = val
|
||||
// }
|
||||
defineExpose({
|
||||
fetch
|
||||
// spinTable
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
defineExpose({ fetch })
|
||||
</script>
|
||||
|
||||
@@ -29,6 +29,16 @@ export const cols: BaseTableColumn[] = [
|
||||
return record.index + 1
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'مشتری',
|
||||
dataIndex: 'customer',
|
||||
key: 'customer',
|
||||
type: 'Text',
|
||||
customRender: ({ record }) => {
|
||||
const name = record.customer?.name?.trim()
|
||||
return name ? `${name} (${record.customer_id})` : String(record.customer_id ?? '-')
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'کد مشتری',
|
||||
dataIndex: 'customer_id',
|
||||
@@ -203,6 +213,15 @@ export const cols: BaseTableColumn[] = [
|
||||
{
|
||||
title: 'روش ارسال',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
}
|
||||
key: 'notify_action',
|
||||
customRender: ({ record }) => (record.action === 'email' ? 'ایمیل' : 'پیامک'),
|
||||
},
|
||||
{
|
||||
title: 'عملیات',
|
||||
dataIndex: 'row_action',
|
||||
key: 'row_action',
|
||||
slot: 'action',
|
||||
fixed: 'right',
|
||||
filterable: false,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,248 +1,163 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- <BasicMainCard>-->
|
||||
<div class="flex w-full h-full">
|
||||
<tbl ref="table" class="w-full h-full" @info="info" @edit="edit" @add="add" />
|
||||
<div class="flex h-full w-full">
|
||||
<tbl ref="tableRef" class="h-full w-full" @add="openAdd" @edit="openEdit" @toggle="toggleActive" @delete="deleteItem" />
|
||||
|
||||
<!-- <BasicMenu :menu="menu" width="13rem" @onChange="tabSelected" />
|
||||
<component :is="selectedComponent" class="mr-5" />-->
|
||||
<!-- <BasicShowContext title="پروژه ها"> <Search /> </BasicShowContext>-->
|
||||
</div>
|
||||
<!-- </BasicMainCard>-->
|
||||
<!-- <BasicModal
|
||||
v-model:visible="addUserVisible"
|
||||
width="40rem"
|
||||
:title="`${editId ? 'ویرایش' : 'افزودن'} کاربر`"
|
||||
@ok="ok"
|
||||
<BasicActionSheet
|
||||
v-model:visible="editorVisible"
|
||||
:title="sheetTitle"
|
||||
modal-width="32rem"
|
||||
drawer-height="85vh"
|
||||
:loading="loading"
|
||||
@close="addUserVisible = false"
|
||||
>-->
|
||||
<!-- <a-form :model="form" ref="formRef" autocomplete="off">
|
||||
<div class="grid md:grid-cols-2 gap-4">
|
||||
<a-form-item label="نام" name="fname" :rules="inputRules()">
|
||||
<BasicInput v-model:value="form.fname" />
|
||||
</a-form-item>
|
||||
<a-form-item label="نام خانوادگی" name="lname" :rules="inputRules()">
|
||||
<BasicInput v-model:value="form.lname" />
|
||||
</a-form-item>
|
||||
<a-form-item label="شماره همراه" name="mobile" :rules="inputLenRules(11)">
|
||||
<BasicInput v-model:value="form.mobile" type="number" />
|
||||
</a-form-item>
|
||||
<a-form-item v-if="editId" label="رمز عبور" name="password">
|
||||
<BasicInput v-model:value="form.password" is-password type="number" />
|
||||
</a-form-item>
|
||||
<a-form-item v-if="editId" label="تکرار رمز عبور">
|
||||
<BasicInput v-model:value="form.repassword" is-password type="number" />
|
||||
</a-form-item>
|
||||
</div>
|
||||
</a-form>-->
|
||||
<!-- <BasicInfoModal
|
||||
v-model:visible="userInfoVisible"
|
||||
@close="userInfoVisible = false"
|
||||
:title="infoParams.title"
|
||||
:date="infoParams.date"
|
||||
:time="infoParams.time"
|
||||
:type="infoParams.type"
|
||||
itemsTitle="اطلاعات فردی"
|
||||
:name="infoParams.name"
|
||||
:items="infoParams.items"
|
||||
/>-->
|
||||
:show-button="true"
|
||||
:show-cancel="true"
|
||||
:show-ok="true"
|
||||
ok-text="ذخیره"
|
||||
cancel-text="انصراف"
|
||||
@ok="submitForm"
|
||||
@close="closeEditor"
|
||||
@cancel="closeEditor"
|
||||
>
|
||||
<div v-if="isMobile" class="mb-3 text-base font-bold">{{ sheetTitle }}</div>
|
||||
<PriceTriggerEditorFormBody
|
||||
ref="editorRef"
|
||||
:form="form"
|
||||
:customer-items="customerItems"
|
||||
:edit-id="editId"
|
||||
/>
|
||||
</BasicActionSheet>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// import BasicInfoModal from '@/components/BasicInfoModal'
|
||||
import { inputLenRules, inputRules } from '@/utils/inputRules'
|
||||
import { useStaticApiListsStore } from '@/store/staticApisStore'
|
||||
import type { Const } from '@/model/const'
|
||||
import type { User } from '@/model/user'
|
||||
import { createUser, deleteUser, updateUser } from '@/api/user'
|
||||
import { ref } from 'vue'
|
||||
import tbl from './components/table.vue'
|
||||
import type { FormInstance } from 'ant-design-vue'
|
||||
import BasicInfoModal from '@/components/BasicInfoModal/index.vue'
|
||||
import { message } from 'ant-design-vue/es'
|
||||
import type { Info } from '@/components/BasicInfoModal/model/Info'
|
||||
import { toJalaliNoTime, toTime } from '@core/utils/useDataTime'
|
||||
import { confirmDeleteItem } from '@/utils/confirmModal'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import type { Item } from '@/model/Item'
|
||||
import tbl from './components/table.vue'
|
||||
import BasicActionSheet from '@/components/BasicActionSheet.vue'
|
||||
import PriceTriggerEditorFormBody from './components/PriceTriggerEditorFormBody.vue'
|
||||
import type { PriceTriggers } from '@/model/priceTriggers'
|
||||
import type { Customer } from '@/model/customer'
|
||||
import { getCustomersList } from '@/api/customer'
|
||||
import {
|
||||
createPriceTrigger,
|
||||
deletePriceTrigger,
|
||||
togglePriceTriggerActive,
|
||||
updatePriceTrigger,
|
||||
} from '@/api/priceTriggers'
|
||||
import { confirmDeleteItem } from '@/utils/confirmModal'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
|
||||
//loading
|
||||
const loading = ref<boolean>(false)
|
||||
const tableRef = ref<null | { fetch: () => void }>(null)
|
||||
const editorRef = ref<{
|
||||
validateFields?: () => Promise<unknown>
|
||||
resetFields?: () => void
|
||||
}>()
|
||||
|
||||
//define store
|
||||
const store = useStaticApiListsStore()
|
||||
const loading = ref(false)
|
||||
const editorVisible = ref(false)
|
||||
const editId = ref<number | undefined>()
|
||||
const customers = ref<Customer[]>([])
|
||||
|
||||
//define tbl
|
||||
const table = ref(null)
|
||||
const refreshTable = () => table?.value?.fetch()
|
||||
|
||||
let updateKey = ref<number>(0)
|
||||
|
||||
//role confirm items
|
||||
const confirmItems: Array<Item> = [
|
||||
{
|
||||
value: 'بله',
|
||||
key: true
|
||||
},
|
||||
{
|
||||
value: 'خیر',
|
||||
key: false
|
||||
}
|
||||
]
|
||||
|
||||
//form
|
||||
const form = ref<User>({
|
||||
active: true
|
||||
const defaultForm = (): Partial<PriceTriggers> => ({
|
||||
customer_id: undefined,
|
||||
target_price: undefined,
|
||||
price_condition: '>',
|
||||
action: 'sms',
|
||||
is_active: true,
|
||||
})
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
//info
|
||||
let infoParams = reactive<Info>({})
|
||||
const form = ref<Partial<PriceTriggers>>(defaultForm())
|
||||
|
||||
function info(val: User) {
|
||||
infoParams.name = val.name
|
||||
infoParams.type = val.mobile
|
||||
infoParams.date = toJalaliNoTime(val.created_at)
|
||||
infoParams.time = toTime(val.created_at)
|
||||
infoParams.itemsTitle = 'اطلاعات کاربری'
|
||||
infoParams.items = [
|
||||
{
|
||||
key: 'تلفن همراه',
|
||||
value: val.mobile ?? '-'
|
||||
},
|
||||
{
|
||||
key: 'کد کاربری',
|
||||
value: val.id ?? '-'
|
||||
}
|
||||
]
|
||||
userInfoVisible.value = true
|
||||
const sheetTitle = computed(() => (editId.value ? 'ویرایش هشدار قیمت' : 'افزودن هشدار قیمت'))
|
||||
|
||||
const customerItems = computed(() =>
|
||||
customers.value.map((customer) => ({
|
||||
id: customer.id,
|
||||
label: `${customer.name || 'بدون نام'} — ${customer.mobile || customer.id}`,
|
||||
}))
|
||||
)
|
||||
|
||||
function refreshTable() {
|
||||
tableRef.value?.fetch()
|
||||
}
|
||||
|
||||
//edit
|
||||
let editId: number | undefined = undefined
|
||||
|
||||
async function edit(record: User) {
|
||||
addUserVisible.value = true
|
||||
editId = record.id
|
||||
form.value = cloneDeep(record)
|
||||
function closeEditor() {
|
||||
editorVisible.value = false
|
||||
}
|
||||
|
||||
//add
|
||||
async function add() {
|
||||
loading.value = true
|
||||
editId = undefined
|
||||
// await store.getUsersList()
|
||||
addUserVisible.value = true
|
||||
updateKey.value++
|
||||
resetData()
|
||||
loading.value = false
|
||||
function openAdd() {
|
||||
editId.value = undefined
|
||||
form.value = defaultForm()
|
||||
editorRef.value?.resetFields?.()
|
||||
editorVisible.value = true
|
||||
}
|
||||
|
||||
//reset data
|
||||
const resetData = () => {
|
||||
formRef.value?.resetFields()
|
||||
form.value = {
|
||||
fname: '',
|
||||
lname: '',
|
||||
active: false,
|
||||
avatar_id: undefined,
|
||||
sts: undefined,
|
||||
id: undefined,
|
||||
mobile: undefined
|
||||
}
|
||||
function openEdit(record: PriceTriggers) {
|
||||
editId.value = record.id
|
||||
form.value = cloneDeep({
|
||||
customer_id: record.customer_id,
|
||||
target_price: record.target_price,
|
||||
price_condition: record.price_condition,
|
||||
action: record.action,
|
||||
is_active: record.is_active,
|
||||
})
|
||||
editorVisible.value = true
|
||||
}
|
||||
|
||||
//delete
|
||||
const deleteItem = (id: number) => confirmDeleteItem('کاربر', id, deleteUser, refreshTable)
|
||||
|
||||
/* Modal.confirm({
|
||||
title: 'آیا از حذف کاربر اطمینان دارید؟',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
okText: 'تایید',
|
||||
okType: 'danger',
|
||||
centered: true,
|
||||
cancelText: 'انصراف',
|
||||
async onOk() {
|
||||
// console.log('OK');
|
||||
await deleteItem(id)
|
||||
},
|
||||
onCancel() {
|
||||
console.log('Cancel')
|
||||
}
|
||||
})*/
|
||||
|
||||
/*async function deleteItem(id: number) {
|
||||
async function submitForm() {
|
||||
try {
|
||||
await editorRef.value?.validateFields?.()
|
||||
loading.value = true
|
||||
// table?.value?.spinTable(true)
|
||||
await deleteUser(id)
|
||||
message.success('با موفقیت حذف شد')
|
||||
table?.value?.fetch()
|
||||
|
||||
const payload = {
|
||||
customer_id: Number(form.value.customer_id),
|
||||
target_price: Number(form.value.target_price),
|
||||
price_condition: form.value.price_condition,
|
||||
action: form.value.action,
|
||||
is_active: Boolean(form.value.is_active),
|
||||
}
|
||||
|
||||
if (editId.value) {
|
||||
await updatePriceTrigger(editId.value, payload)
|
||||
} else {
|
||||
await createPriceTrigger(payload)
|
||||
}
|
||||
|
||||
message.success('هشدار با موفقیت ذخیره شد')
|
||||
editorVisible.value = false
|
||||
refreshTable()
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
message.error('خطا در حذف')
|
||||
console.error(e)
|
||||
} finally {
|
||||
loading.value = false
|
||||
// table?.value?.spinTable(false)
|
||||
}
|
||||
}*/
|
||||
|
||||
//define selects
|
||||
onMounted(async () => {
|
||||
loading.value = true
|
||||
// await getResponsible()
|
||||
// await getQC()
|
||||
// await getManager()
|
||||
loading.value = false
|
||||
})
|
||||
|
||||
interface selectsItem {
|
||||
responsibleItems: Array<User> | undefined
|
||||
users: Array<Const> | undefined
|
||||
}
|
||||
|
||||
//add project modal
|
||||
const addUserVisible = ref<boolean>(false)
|
||||
const userInfoVisible = ref<boolean>(false)
|
||||
async function toggleActive(record: PriceTriggers) {
|
||||
if (!record.id) return
|
||||
|
||||
//submit user
|
||||
async function ok() {
|
||||
formRef.value?.validateFields().then(async () => {
|
||||
// console.log('salam', form.value)
|
||||
// return
|
||||
try {
|
||||
loading.value = true
|
||||
try {
|
||||
if (editId) {
|
||||
if (form.value.password) {
|
||||
if (form.value.password === form.value.repassword) {
|
||||
await updateUser(editId, form.value)
|
||||
} else {
|
||||
message.error('پسورد وارد شده با تکرار پسورد وارد شده یکسان نیست')
|
||||
return
|
||||
}
|
||||
} else {
|
||||
await updateUser(editId, form.value)
|
||||
}
|
||||
} else {
|
||||
await createUser(form.value)
|
||||
}
|
||||
addUserVisible.value = false
|
||||
refreshTable()
|
||||
resetData()
|
||||
message.success('عملیات با موفقیت انجام شد')
|
||||
// await getResponsible()
|
||||
// await getQC()
|
||||
// await getManager()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
// message.error('خطا در بارگذاری اطلاعات')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
await togglePriceTriggerActive(record.id)
|
||||
message.success(record.is_active ? 'هشدار غیرفعال شد' : 'هشدار فعال شد')
|
||||
refreshTable()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
message.error('خطا در تغییر وضعیت')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
function deleteItem(id: number) {
|
||||
confirmDeleteItem('هشدار قیمت', id, deletePriceTrigger, refreshTable)
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
customers.value = (await getCustomersList()) ?? []
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
<div class="h-full min-h-0 min-w-0 max-w-full overflow-hidden">
|
||||
<BasicShowContext title="تاریخچه قیمت">
|
||||
<template #extra>
|
||||
<BaseButton
|
||||
<div class="flex items-center gap-2">
|
||||
<AdminFieldHelp :text="pricingPageIntro" />
|
||||
<BaseButton
|
||||
v-if="isMobile"
|
||||
type="primary"
|
||||
size="small"
|
||||
@@ -12,14 +14,15 @@
|
||||
>
|
||||
بهروزرسانی قیمت
|
||||
</BaseButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<AdminFieldHelp :text="pricingPageIntro" root-class="mb-3 px-1" />
|
||||
|
||||
<div class="flex h-full min-h-0 flex-col">
|
||||
<BaseTable v-if="!isMobile" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl">
|
||||
<template #extraHeader>
|
||||
<BaseButton
|
||||
<div class="flex items-center gap-2">
|
||||
<AdminFieldHelp :text="pricingPageIntro" />
|
||||
<BaseButton
|
||||
type="primary"
|
||||
size="small"
|
||||
icon="vuesax-linear:refresh"
|
||||
@@ -28,6 +31,7 @@
|
||||
>
|
||||
بهروزرسانی قیمت
|
||||
</BaseButton>
|
||||
</div>
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<div class="flex justify-center gap-2">
|
||||
|
||||
@@ -10,7 +10,10 @@ export const bannersAspectHelp =
|
||||
'عکس باید افقی و با نسبت ۱۶:۹ باشد (مثل تصویر تلویزیون). اگر بلند و باریک یا مربع باشد، در اپ کشیده یا بریده میشود.'
|
||||
|
||||
export const bannersImageHelp =
|
||||
'روی «افزودن عکس» بزنید و یک تصویر با کیفیت مناسب انتخاب کنید. آیکون سطل زباله کل این بنر را از لیست حذف میکند.'
|
||||
'روی «افزودن عکس» بزنید. آیکون سطل زباله فقط تصویر را پاک میکند؛ برای حذف کل اسلاید از دکمهٔ «حذف اسلاید» استفاده کنید.'
|
||||
|
||||
export const bannersImageSectionHelp =
|
||||
'عکس افقی با نسبت ۱۶:۹ (مثل تلویزیون) بهترین نتیجه را میدهد. تصویر نامناسب در اپ کشیده یا بریده میشود.'
|
||||
|
||||
export const bannersEventHelp =
|
||||
'وقتی مشتری روی بنر ضربه میزند چه اتفاقی بیفتد؟ اگر مطمئن نیستید «بدون رویداد» را بگذارید.'
|
||||
@@ -61,4 +64,4 @@ export const discountExpiryHelp =
|
||||
'بعد از این تاریخ کدهای این پویش دیگر کار نمیکنند.'
|
||||
|
||||
export const priceTriggersPageIntro =
|
||||
'لیست درخواستهای «هشدار قیمت» مشتریان. وقتی قیمت به عددی که مشتری گفته برسد، به او اطلاع داده میشود. از آیکون مداد میتوانید وضعیت را ببینید یا ویرایش کنید.'
|
||||
'لیست هشدارهای قیمت مشتریان. میتوانید هشدار جدید بسازید، ویرایش کنید، فعال/غیرفعال کنید یا حذف کنید. وقتی قیمت به هدف برسد، به مشتری پیامک یا ایمیل میرود و هشدار خودکار غیرفعال میشود.'
|
||||
|
||||
Reference in New Issue
Block a user