Replace hour-only select with HH:mm picker, show last run and send logs in Jalali, and display scheduled times as HH:mm in the table. Co-authored-by: Cursor <cursoragent@cursor.com>
755 lines
24 KiB
Vue
755 lines
24 KiB
Vue
<template>
|
||
<a-form
|
||
ref="formRef"
|
||
:model="form"
|
||
layout="vertical"
|
||
autocomplete="off"
|
||
class="auto-editor"
|
||
>
|
||
<div class="auto-editor__grid">
|
||
<div class="auto-editor__col">
|
||
<a-form-item>
|
||
<template #label>
|
||
<AdminLabelWithHelp label="مخاطب" :help="automationAudienceHelp" />
|
||
</template>
|
||
<a-radio-group
|
||
:value="audienceMode"
|
||
option-type="button"
|
||
button-style="solid"
|
||
@update:value="setAudienceMode"
|
||
>
|
||
<a-radio-button value="all">همه کاربران</a-radio-button>
|
||
<a-radio-button value="filter">با شرط</a-radio-button>
|
||
</a-radio-group>
|
||
</a-form-item>
|
||
|
||
<template v-if="audienceMode === 'filter'">
|
||
<div class="auto-editor__conds">
|
||
<div
|
||
v-for="(row, idx) in form.conditions"
|
||
:key="idx"
|
||
class="auto-editor__cond"
|
||
>
|
||
<div v-if="idx > 0" class="auto-editor__join">
|
||
<a-radio-group
|
||
:value="row.cr || 'and'"
|
||
option-type="button"
|
||
button-style="solid"
|
||
size="small"
|
||
@update:value="(v) => (row.cr = v)"
|
||
>
|
||
<a-radio-button value="and">و</a-radio-button>
|
||
<a-radio-button value="or">یا</a-radio-button>
|
||
</a-radio-group>
|
||
</div>
|
||
|
||
<div class="auto-editor__cond-row">
|
||
<BasicSelect
|
||
class="auto-editor__cond-select"
|
||
:value="row.field"
|
||
:items="fieldItems"
|
||
key-name="key"
|
||
value-name="name"
|
||
placeholder="نوع شرط"
|
||
:has-tooltip="false"
|
||
:dropdown-match-select-width="false"
|
||
:dropdown-style="wideDropdownStyle"
|
||
@update:value="(v) => onFieldChange(row, v)"
|
||
/>
|
||
<template v-for="inp in inputsOf(row.field)" :key="inp.key">
|
||
<BasicSelect
|
||
v-if="inp.type === 'operator'"
|
||
class="auto-editor__cond-select"
|
||
:value="row.op || 'eq'"
|
||
:items="catalog?.operators || []"
|
||
key-name="key"
|
||
value-name="name"
|
||
:placeholder="inp.label"
|
||
:has-tooltip="false"
|
||
@update:value="(v) => (row.op = v)"
|
||
/>
|
||
<BasicSelect
|
||
v-else-if="inp.type === 'select'"
|
||
class="auto-editor__cond-select"
|
||
:value="row.value"
|
||
:items="inp.options || []"
|
||
key-name="key"
|
||
value-name="name"
|
||
:placeholder="inp.label"
|
||
:has-tooltip="false"
|
||
@update:value="(v) => (row.value = v)"
|
||
/>
|
||
<BasicInput
|
||
v-else
|
||
class="auto-editor__cond-input"
|
||
:value="(row.value as any)"
|
||
type="number"
|
||
:place-holder="inp.label"
|
||
@update:value="(v) => (row.value = v)"
|
||
/>
|
||
</template>
|
||
<BaseButton danger size="small" @click="removeCondition(idx)">حذف</BaseButton>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<BaseButton size="small" type="dashed" @click="addCondition">افزودن شرط</BaseButton>
|
||
</template>
|
||
|
||
<div class="auto-editor__hint">مخاطب تقریبی: {{ previewText }}</div>
|
||
</div>
|
||
|
||
<div class="auto-editor__col">
|
||
<div class="auto-editor__pair">
|
||
<a-form-item name="name" :rules="inputRules()">
|
||
<template #label>
|
||
<AdminLabelWithHelp label="نام" :help="automationNameHelp" />
|
||
</template>
|
||
<BasicInput v-model:value="form.name" placeholder="نام اتوماسیون" />
|
||
</a-form-item>
|
||
<a-form-item name="title" :rules="inputRules()">
|
||
<template #label>
|
||
<AdminLabelWithHelp label="عنوان پیام" :help="automationTitleHelp" />
|
||
</template>
|
||
<BasicInput v-model:value="form.title" placeholder="عنوان پیامک / اعلان" />
|
||
</a-form-item>
|
||
</div>
|
||
<a-form-item name="body" :rules="inputRules()" class="auto-editor__body">
|
||
<template #label>
|
||
<AdminLabelWithHelp label="متن" :help="automationBodyHelp" />
|
||
</template>
|
||
<a-textarea
|
||
ref="bodyAreaRef"
|
||
v-model:value="form.body"
|
||
class="auto-editor__textarea"
|
||
:rows="4"
|
||
placeholder="متن پیام را بنویسید…"
|
||
@blur="rememberBodyCursor"
|
||
@click="rememberBodyCursor"
|
||
@keyup="rememberBodyCursor"
|
||
@select="rememberBodyCursor"
|
||
/>
|
||
<div class="auto-editor__vars">
|
||
<button
|
||
v-for="v in bodyVars"
|
||
:key="v.token"
|
||
type="button"
|
||
class="auto-editor__var-chip"
|
||
:title="v.help"
|
||
@mousedown.prevent="insertBodyToken(v.token)"
|
||
>
|
||
<span class="auto-editor__var-token">%{{ v.token }}</span>
|
||
<span>{{ v.label }}</span>
|
||
</button>
|
||
</div>
|
||
</a-form-item>
|
||
|
||
<a-form-item name="trigger_type" :rules="inputRules()">
|
||
<template #label>
|
||
<AdminLabelWithHelp label="نوع اجرا" :help="automationTriggerTypeHelp" />
|
||
</template>
|
||
<a-radio-group
|
||
v-model:value="form.trigger_type"
|
||
option-type="button"
|
||
button-style="solid"
|
||
>
|
||
<a-radio-button
|
||
v-for="item in catalog?.triggers || []"
|
||
:key="String(item.key)"
|
||
:value="item.key"
|
||
>
|
||
{{ item.name }}
|
||
</a-radio-button>
|
||
</a-radio-group>
|
||
</a-form-item>
|
||
|
||
<a-form-item v-if="form.trigger_type === 'schedule'">
|
||
<template #label>
|
||
<AdminLabelWithHelp label="هر چند روز یکبار" :help="automationEveryDaysHelp" />
|
||
</template>
|
||
<BasicInput
|
||
v-model:value="form.trigger_config.every_days"
|
||
type="number"
|
||
placeholder="مثلاً ۲۰"
|
||
/>
|
||
</a-form-item>
|
||
|
||
<template v-if="form.trigger_type === 'event'">
|
||
<div class="auto-editor__pair">
|
||
<a-form-item>
|
||
<template #label>
|
||
<AdminLabelWithHelp label="رویداد" :help="automationEventHelp" />
|
||
</template>
|
||
<BasicSelect
|
||
v-model:value="form.trigger_config.event"
|
||
:items="catalog?.events || []"
|
||
key-name="key"
|
||
value-name="name"
|
||
placeholder="انتخاب رویداد"
|
||
:has-tooltip="false"
|
||
/>
|
||
</a-form-item>
|
||
<a-form-item>
|
||
<template #label>
|
||
<AdminLabelWithHelp label="زمان ارسال" :help="automationEventTimingHelp" />
|
||
</template>
|
||
<a-radio-group
|
||
:value="eventTiming"
|
||
option-type="button"
|
||
button-style="solid"
|
||
@update:value="setEventTiming"
|
||
>
|
||
<a-radio-button value="immediate">سر ضرب</a-radio-button>
|
||
<a-radio-button value="delayed">با تأخیر</a-radio-button>
|
||
</a-radio-group>
|
||
</a-form-item>
|
||
</div>
|
||
<a-form-item v-if="eventTiming === 'delayed'">
|
||
<template #label>
|
||
<AdminLabelWithHelp label="تأخیر (ساعت)" :help="automationDelayHoursHelp" />
|
||
</template>
|
||
<BasicInput
|
||
v-model:value="form.trigger_config.delay_hours"
|
||
type="number"
|
||
placeholder="۲۴"
|
||
/>
|
||
</a-form-item>
|
||
</template>
|
||
|
||
<template v-if="form.trigger_type === 'price'">
|
||
<div class="auto-editor__pair">
|
||
<a-form-item>
|
||
<template #label>
|
||
<AdminLabelWithHelp label="بازه" :help="automationPriceWindowHelp" />
|
||
</template>
|
||
<a-radio-group
|
||
v-model:value="form.trigger_config.price_window"
|
||
option-type="button"
|
||
button-style="solid"
|
||
>
|
||
<a-radio-button value="calendar_day">در یک روز</a-radio-button>
|
||
<a-radio-button value="from_last">از آخرین ارسال</a-radio-button>
|
||
</a-radio-group>
|
||
</a-form-item>
|
||
<a-form-item>
|
||
<template #label>
|
||
<AdminLabelWithHelp label="جهت" :help="automationPriceDirectionHelp" />
|
||
</template>
|
||
<a-radio-group
|
||
v-model:value="form.trigger_config.direction"
|
||
option-type="button"
|
||
button-style="solid"
|
||
>
|
||
<a-radio-button value="up">بالا</a-radio-button>
|
||
<a-radio-button value="down">پایین</a-radio-button>
|
||
<a-radio-button value="both">هر دو</a-radio-button>
|
||
</a-radio-group>
|
||
</a-form-item>
|
||
</div>
|
||
<div class="auto-editor__pair">
|
||
<a-form-item>
|
||
<template #label>
|
||
<AdminLabelWithHelp label="رقم تغییر (تومان)" :help="automationPriceStepHelp" />
|
||
</template>
|
||
<BasicInput
|
||
v-model:value="form.trigger_config.step_toman"
|
||
type="price"
|
||
addon-before="تومان"
|
||
placeholder="۱۰۰۰۰۰"
|
||
/>
|
||
</a-form-item>
|
||
<a-form-item>
|
||
<template #label>
|
||
<AdminLabelWithHelp label="محصول" :help="automationPriceProductHelp" />
|
||
</template>
|
||
<BasicSelect
|
||
v-model:value="form.trigger_config.product_id"
|
||
:items="productItems"
|
||
key-name="id"
|
||
value-name="name"
|
||
placeholder="خالی = قیمت تابلو"
|
||
allow-clear
|
||
:has-tooltip="false"
|
||
/>
|
||
</a-form-item>
|
||
</div>
|
||
</template>
|
||
|
||
<div class="auto-editor__pair">
|
||
<a-form-item v-if="form.trigger_type !== 'schedule'">
|
||
<template #label>
|
||
<AdminLabelWithHelp
|
||
label="فاصلهٔ تکرار برای هر نفر (ساعت)"
|
||
:help="automationCooldownHelp"
|
||
/>
|
||
</template>
|
||
<BasicInput v-model:value="form.cooldown_hours" type="number" placeholder="۲۴" />
|
||
</a-form-item>
|
||
<a-form-item>
|
||
<template #label>
|
||
<AdminLabelWithHelp label="حداکثر ارسال به هر نفر" :help="automationMaxSendsHelp" />
|
||
</template>
|
||
<BasicInput v-model:value="form.max_sends" type="number" placeholder="خالی = نامحدود" />
|
||
</a-form-item>
|
||
</div>
|
||
|
||
<div class="auto-editor__pair">
|
||
<a-form-item>
|
||
<template #label>
|
||
<AdminLabelWithHelp label="پیامک" :help="automationSmsHelp" />
|
||
</template>
|
||
<div class="auto-editor__channel">
|
||
<a-switch :checked="hasSms" @change="(v) => toggleChannel('sms', !!v)" />
|
||
<a-time-picker
|
||
v-if="hasSms && showChannelHours"
|
||
class="auto-editor__hour-picker"
|
||
:value="smsHourValue"
|
||
format="HH:mm"
|
||
:minute-step="1"
|
||
:show-second="false"
|
||
placeholder="ساعت"
|
||
allow-clear
|
||
@update:value="setSmsTime"
|
||
/>
|
||
</div>
|
||
</a-form-item>
|
||
<a-form-item>
|
||
<template #label>
|
||
<AdminLabelWithHelp label="اعلان داخل اپ" :help="automationInAppHelp" />
|
||
</template>
|
||
<div class="auto-editor__channel">
|
||
<a-switch :checked="hasInApp" @change="(v) => toggleChannel('in_app', !!v)" />
|
||
<a-time-picker
|
||
v-if="hasInApp && showChannelHours"
|
||
class="auto-editor__hour-picker"
|
||
:value="inAppHourValue"
|
||
format="HH:mm"
|
||
:minute-step="1"
|
||
:show-second="false"
|
||
placeholder="ساعت"
|
||
allow-clear
|
||
@update:value="setInAppTime"
|
||
/>
|
||
</div>
|
||
</a-form-item>
|
||
</div>
|
||
|
||
<div class="auto-editor__pair">
|
||
<a-form-item>
|
||
<template #label>
|
||
<AdminLabelWithHelp label="شروع از" :help="automationStartsAtHelp" />
|
||
</template>
|
||
<BaseDatePicker
|
||
v-model:value="form.starts_at"
|
||
show-time
|
||
format="YYYY-MM-DD HH:mm:ss"
|
||
class="w-full"
|
||
/>
|
||
</a-form-item>
|
||
<a-form-item>
|
||
<template #label>
|
||
<AdminLabelWithHelp label="پایان در" :help="automationEndsAtHelp" />
|
||
</template>
|
||
<BaseDatePicker
|
||
v-model:value="form.ends_at"
|
||
show-time
|
||
format="YYYY-MM-DD HH:mm:ss"
|
||
class="w-full"
|
||
/>
|
||
</a-form-item>
|
||
</div>
|
||
<a-form-item>
|
||
<template #label>
|
||
<AdminLabelWithHelp label="وضعیت" :help="automationStatusHelp" />
|
||
</template>
|
||
<a-switch
|
||
v-model:checked="form.is_active"
|
||
checked-children="فعال"
|
||
un-checked-children="غیرفعال"
|
||
/>
|
||
</a-form-item>
|
||
</div>
|
||
</div>
|
||
</a-form>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
||
import type { FormInstance } from 'ant-design-vue'
|
||
import dayjs, { type Dayjs } from 'dayjs'
|
||
import customParseFormat from 'dayjs/plugin/customParseFormat'
|
||
import { cloneDeep, debounce, isEmpty } from 'lodash'
|
||
|
||
dayjs.extend(customParseFormat)
|
||
import { inputRules } from '@/utils/inputRules'
|
||
import { getAutomationCatalog, previewAutomationAudience } from '@/api/automations'
|
||
import BaseDatePicker from '@core/components/BaseDatePicker'
|
||
import AdminLabelWithHelp from '@/components/AdminLabelWithHelp.vue'
|
||
import {
|
||
automationAudienceHelp,
|
||
automationBodyHelp,
|
||
automationCooldownHelp,
|
||
automationDelayHoursHelp,
|
||
automationEndsAtHelp,
|
||
automationEventHelp,
|
||
automationEventTimingHelp,
|
||
automationEveryDaysHelp,
|
||
automationInAppHelp,
|
||
automationMaxSendsHelp,
|
||
automationNameHelp,
|
||
automationPriceDirectionHelp,
|
||
automationPriceProductHelp,
|
||
automationPriceStepHelp,
|
||
automationPriceWindowHelp,
|
||
automationSmsHelp,
|
||
automationStartsAtHelp,
|
||
automationStatusHelp,
|
||
automationTitleHelp,
|
||
automationTriggerTypeHelp,
|
||
} from '@/utils/adminHelpCopy'
|
||
import type {
|
||
Automation,
|
||
AutomationCatalog,
|
||
AutomationChannel,
|
||
AutomationCondition,
|
||
AutomationField,
|
||
} from '@/model/automations'
|
||
|
||
const props = defineProps<{
|
||
form: Automation
|
||
}>()
|
||
|
||
const formRef = ref<FormInstance>()
|
||
const catalog = ref<AutomationCatalog | null>(null)
|
||
const previewCount = ref<number | null>(null)
|
||
const bodyAreaRef = ref<{ $el?: HTMLElement } | null>(null)
|
||
let bodyCursorStart = 0
|
||
let bodyCursorEnd = 0
|
||
|
||
const bodyVars = [
|
||
{ token: 'نام', label: 'نام کاربر', help: 'نام کامل مشتری' },
|
||
{ token: 'نام_کوچک', label: 'نام کوچک', help: 'نام کوچک مشتری' },
|
||
{ token: 'موبایل', label: 'موبایل', help: 'شماره موبایل مشتری' },
|
||
]
|
||
|
||
function getBodyTextarea(): HTMLTextAreaElement | null {
|
||
const root = bodyAreaRef.value?.$el
|
||
if (!root) return null
|
||
if (root instanceof HTMLTextAreaElement) return root
|
||
return root.querySelector?.('textarea') ?? null
|
||
}
|
||
|
||
function rememberBodyCursor() {
|
||
const ta = getBodyTextarea()
|
||
if (!ta) return
|
||
bodyCursorStart = ta.selectionStart ?? 0
|
||
bodyCursorEnd = ta.selectionEnd ?? bodyCursorStart
|
||
}
|
||
|
||
function insertBodyToken(token: string) {
|
||
rememberBodyCursor()
|
||
const insert = `%${token}`
|
||
const text = props.form.body ?? ''
|
||
const start = bodyCursorStart
|
||
const end = bodyCursorEnd
|
||
props.form.body = text.slice(0, start) + insert + text.slice(end)
|
||
const nextPos = start + insert.length
|
||
bodyCursorStart = nextPos
|
||
bodyCursorEnd = nextPos
|
||
nextTick(() => {
|
||
const ta = getBodyTextarea()
|
||
if (!ta) return
|
||
ta.focus()
|
||
ta.setSelectionRange(nextPos, nextPos)
|
||
})
|
||
}
|
||
|
||
const fieldItems = computed(() =>
|
||
(catalog.value?.fields || []).map((f) => ({
|
||
key: f.key,
|
||
name: `${f.group} › ${f.name}`,
|
||
}))
|
||
)
|
||
|
||
const productItems = computed(() => catalog.value?.products || [])
|
||
|
||
const wideDropdownStyle = {
|
||
minWidth: '22rem',
|
||
maxWidth: 'min(40rem, 94vw)',
|
||
}
|
||
|
||
const eventTiming = computed<'immediate' | 'delayed'>(() =>
|
||
Number(props.form.trigger_config?.delay_hours ?? 0) > 0 ? 'delayed' : 'immediate'
|
||
)
|
||
|
||
const audienceMode = computed<'all' | 'filter'>(() =>
|
||
props.form.audience_mode === 'filter' || (props.form.conditions || []).some((r) => r.field)
|
||
? 'filter'
|
||
: 'all'
|
||
)
|
||
|
||
const showChannelHours = computed(() => {
|
||
if (props.form.trigger_type === 'event') return eventTiming.value === 'delayed'
|
||
if (props.form.trigger_type === 'price') {
|
||
return (props.form.trigger_config?.price_window || 'calendar_day') === 'from_last'
|
||
}
|
||
return true
|
||
})
|
||
|
||
const previewText = computed(() => {
|
||
if (previewCount.value == null) return '—'
|
||
return `${previewCount.value.toLocaleString('fa-IR')} نفر`
|
||
})
|
||
|
||
const hasSms = computed(() => (props.form.channels || []).includes('sms'))
|
||
const hasInApp = computed(() => (props.form.channels || []).includes('in_app'))
|
||
|
||
function timeToPicker(hour?: number | null, minute?: number | null): Dayjs | undefined {
|
||
if (hour == null || Number.isNaN(Number(hour))) return undefined
|
||
return dayjs()
|
||
.hour(Number(hour))
|
||
.minute(Number(minute ?? 0))
|
||
.second(0)
|
||
.millisecond(0)
|
||
}
|
||
|
||
function pickerToTime(val: Dayjs | string | null | undefined): { hour: number | null; minute: number | null } {
|
||
if (val == null || val === '') return { hour: null, minute: null }
|
||
const parsed = dayjs.isDayjs(val) ? val : dayjs(String(val), 'HH:mm', true)
|
||
if (!parsed.isValid()) return { hour: null, minute: null }
|
||
return { hour: parsed.hour(), minute: parsed.minute() }
|
||
}
|
||
|
||
const smsHourValue = computed(() => timeToPicker(props.form.sms_send_hour, props.form.sms_send_minute))
|
||
const inAppHourValue = computed(() => timeToPicker(props.form.in_app_send_hour, props.form.in_app_send_minute))
|
||
|
||
function setSmsTime(val: Dayjs | string | null) {
|
||
const time = pickerToTime(val)
|
||
props.form.sms_send_hour = time.hour
|
||
props.form.sms_send_minute = time.hour == null ? null : time.minute
|
||
}
|
||
|
||
function setInAppTime(val: Dayjs | string | null) {
|
||
const time = pickerToTime(val)
|
||
props.form.in_app_send_hour = time.hour
|
||
props.form.in_app_send_minute = time.hour == null ? null : time.minute
|
||
}
|
||
|
||
function ensureTriggerConfig() {
|
||
if (!props.form.trigger_config) {
|
||
props.form.trigger_config = {
|
||
delay_hours: 0,
|
||
every_days: 1,
|
||
direction: 'both',
|
||
price_window: 'calendar_day',
|
||
}
|
||
}
|
||
}
|
||
|
||
function setAudienceMode(mode: 'all' | 'filter') {
|
||
props.form.audience_mode = mode
|
||
if (mode === 'all') {
|
||
props.form.conditions = []
|
||
} else if (!props.form.conditions?.length) {
|
||
addCondition()
|
||
}
|
||
}
|
||
|
||
function setEventTiming(mode: 'immediate' | 'delayed') {
|
||
ensureTriggerConfig()
|
||
if (mode === 'immediate') {
|
||
props.form.trigger_config!.delay_hours = 0
|
||
} else if (!(Number(props.form.trigger_config!.delay_hours) > 0)) {
|
||
props.form.trigger_config!.delay_hours = 24
|
||
}
|
||
}
|
||
|
||
function fieldByKey(key?: string): AutomationField | undefined {
|
||
return (catalog.value?.fields || []).find((f) => f.key === key)
|
||
}
|
||
|
||
function inputsOf(field?: string) {
|
||
return fieldByKey(field)?.inputs || []
|
||
}
|
||
|
||
function onFieldChange(row: AutomationCondition, key: string) {
|
||
row.field = key
|
||
row.op = inputsOf(key).some((i) => i.type === 'operator') ? 'eq' : null
|
||
row.value = null
|
||
}
|
||
|
||
function addCondition() {
|
||
if (!props.form.conditions) props.form.conditions = []
|
||
props.form.audience_mode = 'filter'
|
||
props.form.conditions.push({
|
||
cr: 'and',
|
||
field: '',
|
||
op: null,
|
||
value: null,
|
||
})
|
||
}
|
||
|
||
function removeCondition(idx: number) {
|
||
props.form.conditions?.splice(idx, 1)
|
||
if (!props.form.conditions?.length) {
|
||
props.form.audience_mode = 'all'
|
||
}
|
||
}
|
||
|
||
function toggleChannel(ch: AutomationChannel, on: boolean) {
|
||
const cur = props.form.channels || []
|
||
if (on) {
|
||
if (!cur.includes(ch)) props.form.channels = [...cur, ch]
|
||
} else {
|
||
props.form.channels = cur.filter((c) => c !== ch)
|
||
}
|
||
}
|
||
|
||
const runPreview = debounce(async () => {
|
||
try {
|
||
const cond =
|
||
audienceMode.value === 'all'
|
||
? []
|
||
: (props.form.conditions || []).filter((r) => !isEmpty(r.field))
|
||
const data = await previewAutomationAudience(cloneDeep(cond))
|
||
previewCount.value = data?.count ?? 0
|
||
} catch {
|
||
previewCount.value = null
|
||
}
|
||
}, 400)
|
||
|
||
watch(
|
||
() => cloneDeep(props.form.conditions),
|
||
() => runPreview(),
|
||
{ deep: true }
|
||
)
|
||
|
||
watch(audienceMode, () => runPreview())
|
||
|
||
onMounted(async () => {
|
||
catalog.value = await getAutomationCatalog()
|
||
ensureTriggerConfig()
|
||
if (props.form.trigger_config!.every_days == null) {
|
||
props.form.trigger_config!.every_days = 1
|
||
}
|
||
if (!props.form.trigger_config!.price_window) {
|
||
props.form.trigger_config!.price_window = 'calendar_day'
|
||
}
|
||
if (!props.form.audience_mode) {
|
||
props.form.audience_mode = (props.form.conditions || []).some((r) => r.field)
|
||
? 'filter'
|
||
: 'all'
|
||
}
|
||
runPreview()
|
||
})
|
||
|
||
defineExpose({
|
||
validateFields: () => formRef.value?.validate(),
|
||
resetFields: () => formRef.value?.resetFields(),
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.auto-editor {
|
||
@apply flex flex-col;
|
||
}
|
||
|
||
.auto-editor__grid {
|
||
direction: ltr;
|
||
@apply grid grid-cols-1 items-start gap-x-8 gap-y-6 lg:grid-cols-2;
|
||
}
|
||
|
||
.auto-editor__col {
|
||
direction: rtl;
|
||
@apply flex min-w-0 flex-col gap-6;
|
||
}
|
||
|
||
.auto-editor__pair {
|
||
@apply grid grid-cols-1 gap-x-4 gap-y-6 sm:grid-cols-2;
|
||
}
|
||
|
||
.auto-editor__body :deep(textarea),
|
||
.auto-editor__textarea {
|
||
min-height: 4.5rem;
|
||
max-height: 7rem;
|
||
background: var(--admin-bg-elevated) !important;
|
||
border-color: var(--admin-border) !important;
|
||
color: var(--admin-text) !important;
|
||
}
|
||
|
||
.auto-editor__vars {
|
||
@apply mt-2 flex flex-wrap gap-2;
|
||
}
|
||
|
||
.auto-editor__var-chip {
|
||
@apply inline-flex items-center gap-1.5 rounded-lg border border-solid px-2.5 py-1 text-xs;
|
||
border-color: var(--admin-border);
|
||
background: var(--admin-bg-elevated);
|
||
color: var(--admin-text);
|
||
cursor: pointer;
|
||
}
|
||
|
||
.auto-editor__var-chip:hover {
|
||
border-color: var(--admin-border-strong, var(--admin-border));
|
||
}
|
||
|
||
.auto-editor__var-token {
|
||
@apply font-bold;
|
||
color: var(--admin-text-secondary);
|
||
}
|
||
|
||
.auto-editor :deep(.ant-form-item) {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.auto-editor :deep(.ant-form-item-label) {
|
||
padding-bottom: 6px;
|
||
}
|
||
|
||
.auto-editor :deep(.ant-form-item-label > label) {
|
||
color: var(--admin-text);
|
||
height: auto;
|
||
}
|
||
|
||
.auto-editor__conds {
|
||
@apply mb-3 flex flex-col gap-3;
|
||
}
|
||
|
||
.auto-editor__cond {
|
||
@apply flex flex-col gap-2;
|
||
}
|
||
|
||
.auto-editor__join {
|
||
@apply flex;
|
||
}
|
||
|
||
.auto-editor__cond-row {
|
||
@apply flex flex-wrap items-center gap-2;
|
||
}
|
||
|
||
.auto-editor__cond-select,
|
||
.auto-editor__cond-input {
|
||
width: min(100%, 22rem);
|
||
min-width: 12rem;
|
||
flex: 0 1 22rem;
|
||
}
|
||
|
||
.auto-editor__channel {
|
||
@apply flex items-center gap-3;
|
||
}
|
||
|
||
.auto-editor__hour-picker {
|
||
min-width: 7rem;
|
||
flex: 1;
|
||
}
|
||
|
||
.auto-editor__hour-picker :deep(.ant-picker) {
|
||
width: 100%;
|
||
border-radius: 10px;
|
||
}
|
||
|
||
.auto-editor__hint {
|
||
@apply mt-3 text-xs;
|
||
color: var(--admin-text-secondary);
|
||
}
|
||
</style>
|