feat(settings): allow adding/removing dynamic variables in SMS templates
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
<!-- /src/views/settings/SettingsView.vue -->
|
<!-- /src/views/settings/SettingsView.vue -->
|
||||||
<template>
|
<template>
|
||||||
<div class="settings-view w-full max-w-3xl mx-auto">
|
<div class="settings-view w-full max-w-4xl mx-auto">
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="تنظیمات سیستم"
|
title="تنظیمات سیستم"
|
||||||
subtitle="قالبهای پیامک، راهاندازی پایگاه داده و وارد کردن دادهها"
|
subtitle="قالبهای پیامک، راهاندازی پایگاه داده و وارد کردن دادهها"
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
<div class="flex-grow-1">
|
<div class="flex-grow-1">
|
||||||
<h2 class="text-lg font-bold text-color m-0 mb-1">قالبهای پیامک</h2>
|
<h2 class="text-lg font-bold text-color m-0 mb-1">قالبهای پیامک</h2>
|
||||||
<p class="text-sm text-muted m-0 line-height-3">
|
<p class="text-sm text-muted m-0 line-height-3">
|
||||||
شناسه قالب و نام متغیرهای sms.ir را وارد کنید. نام متغیر باید دقیقاً با قالب پنل یکی باشد. اگر قالبی در پایگاه داده نباشد، با ذخیره ایجاد میشود.
|
شناسه قالب و تعداد و نام متغیرهای sms.ir را بر اساس قالب تعریف شده در پنل پیامک تنظیم کنید. میتوانید هر تعداد متغیر که میخواهید تعریف یا حذف نمایید.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -28,50 +28,107 @@
|
|||||||
<div
|
<div
|
||||||
v-for="template in smsTemplates"
|
v-for="template in smsTemplates"
|
||||||
:key="template.key"
|
:key="template.key"
|
||||||
class="p-3 border-round surface-ground border-1 border-color flex flex-column gap-3"
|
class="p-4 border-round-lg surface-ground border-1 border-color flex flex-column gap-3"
|
||||||
>
|
>
|
||||||
<template v-if="templateForm[template.key]">
|
<template v-if="templateForm[template.key]">
|
||||||
<label class="font-semibold text-sm text-color" :for="`sms-template-${template.key}`">
|
<div class="flex flex-column sm:flex-row sm:align-items-center justify-content-between gap-2 border-bottom-1 border-color pb-2">
|
||||||
{{ template.label }}
|
<div class="flex align-items-center gap-2">
|
||||||
</label>
|
<i class="pi pi-bookmark text-primary"></i>
|
||||||
<InputText
|
<label class="font-bold text-base text-color" :for="`sms-template-${template.key}`">
|
||||||
:id="`sms-template-${template.key}`"
|
{{ template.label }}
|
||||||
v-model.trim="templateForm[template.key].templateId"
|
</label>
|
||||||
class="w-full text-sm"
|
</div>
|
||||||
dir="ltr"
|
<span class="text-xs text-muted font-mono">{{ template.key }}</span>
|
||||||
inputmode="numeric"
|
</div>
|
||||||
placeholder="شناسه قالب sms.ir"
|
|
||||||
/>
|
<div class="flex flex-column sm:flex-row sm:align-items-center gap-2">
|
||||||
<div class="flex flex-column gap-2">
|
|
||||||
<span class="text-xs text-muted">نام متغیرها در قالب sms.ir</span>
|
|
||||||
<div
|
|
||||||
v-for="variable in template.variables"
|
|
||||||
:key="variable.slot"
|
|
||||||
class="flex flex-column sm:flex-row sm:align-items-center gap-2"
|
|
||||||
>
|
|
||||||
<label
|
<label
|
||||||
class="text-sm text-color sm:w-10rem flex-shrink-0"
|
class="text-sm font-semibold text-color sm:w-10rem flex-shrink-0"
|
||||||
:for="`sms-var-${template.key}-${variable.slot}`"
|
:for="`sms-template-${template.key}`"
|
||||||
>
|
>
|
||||||
{{ variable.label }}
|
شناسه قالب sms.ir:
|
||||||
</label>
|
</label>
|
||||||
<div class="flex-grow-1 flex align-items-center gap-2">
|
<div class="flex-grow-1">
|
||||||
<InputText
|
<InputText
|
||||||
:id="`sms-var-${template.key}-${variable.slot}`"
|
:id="`sms-template-${template.key}`"
|
||||||
v-model.trim="templateForm[template.key].variables[variable.slot]"
|
v-model.trim="templateForm[template.key].templateId"
|
||||||
class="w-full text-sm"
|
class="w-full text-sm"
|
||||||
dir="ltr"
|
dir="ltr"
|
||||||
:placeholder="variable.defaultName"
|
inputmode="numeric"
|
||||||
autocomplete="off"
|
placeholder="مثلاً: 123456"
|
||||||
/>
|
|
||||||
<Tag
|
|
||||||
:value="`#${templateForm[template.key].variables[variable.slot] || variable.defaultName}#`"
|
|
||||||
severity="secondary"
|
|
||||||
class="text-xs flex-shrink-0"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<div class="flex flex-column gap-3 pt-2">
|
||||||
|
<div class="flex align-items-center justify-content-between">
|
||||||
|
<div>
|
||||||
|
<span class="text-sm font-semibold text-color">متغیرهای ارسالی در قالب</span>
|
||||||
|
<span class="text-xs text-muted block mt-1">متغیرهایی که در پنل sms.ir در متن قالب قرار دادهاید</span>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
label="افزودن متغیر"
|
||||||
|
icon="pi pi-plus"
|
||||||
|
size="small"
|
||||||
|
outlined
|
||||||
|
class="text-xs font-semibold"
|
||||||
|
@click="addVariable(template.key)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="!templateForm[template.key].variables || templateForm[template.key].variables.length === 0"
|
||||||
|
class="p-3 text-center border-1 border-dashed border-round surface-card text-muted text-xs line-height-3"
|
||||||
|
>
|
||||||
|
هیچ متغیری برای این قالب تعریف نشده است. پیامک بدون متغیر ارسال خواهد شد.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="flex flex-column gap-2">
|
||||||
|
<div
|
||||||
|
v-for="(variable, vIdx) in templateForm[template.key].variables"
|
||||||
|
:key="vIdx"
|
||||||
|
class="flex flex-column sm:flex-row sm:align-items-center gap-2 p-2 border-round surface-card border-1 border-color"
|
||||||
|
>
|
||||||
|
<div class="flex align-items-center gap-2 sm:w-14rem flex-shrink-0">
|
||||||
|
<span class="text-xs text-muted font-bold ml-1">{{ vIdx + 1 }}.</span>
|
||||||
|
<Select
|
||||||
|
v-model="variable.slot"
|
||||||
|
:options="template.availableSlots || []"
|
||||||
|
optionLabel="label"
|
||||||
|
optionValue="slot"
|
||||||
|
placeholder="انتخاب مقدار داده"
|
||||||
|
class="w-full text-sm"
|
||||||
|
@change="onSlotChange(template.key, vIdx)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-grow-1 flex align-items-center gap-2">
|
||||||
|
<InputText
|
||||||
|
v-model.trim="variable.name"
|
||||||
|
class="w-full text-sm"
|
||||||
|
dir="ltr"
|
||||||
|
:placeholder="getSlotDefaultName(template, variable.slot) || 'نام متغیر در sms.ir'"
|
||||||
|
autocomplete="off"
|
||||||
|
/>
|
||||||
|
<Tag
|
||||||
|
:value="`#${variable.name || getSlotDefaultName(template, variable.slot) || '...'}#`"
|
||||||
|
severity="secondary"
|
||||||
|
class="text-xs flex-shrink-0 font-mono"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
icon="pi pi-trash"
|
||||||
|
severity="danger"
|
||||||
|
text
|
||||||
|
rounded
|
||||||
|
size="small"
|
||||||
|
class="p-button-sm flex-shrink-0"
|
||||||
|
title="حذف متغیر"
|
||||||
|
@click="removeVariable(template.key, vIdx)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -195,6 +252,7 @@ import { computed, onMounted, reactive, ref } from 'vue';
|
|||||||
import { useConfirm } from 'primevue/useconfirm';
|
import { useConfirm } from 'primevue/useconfirm';
|
||||||
import Button from 'primevue/button';
|
import Button from 'primevue/button';
|
||||||
import InputText from 'primevue/inputtext';
|
import InputText from 'primevue/inputtext';
|
||||||
|
import Select from 'primevue/select';
|
||||||
import Tag from 'primevue/tag';
|
import Tag from 'primevue/tag';
|
||||||
import PageHeader from '@/components/common/PageHeader.vue';
|
import PageHeader from '@/components/common/PageHeader.vue';
|
||||||
import { seedApi } from '@/api/seedApi';
|
import { seedApi } from '@/api/seedApi';
|
||||||
@@ -227,6 +285,44 @@ const lockedAtLabel = computed(() => {
|
|||||||
return `قفل شده در ${date.toLocaleString('fa-IR')}`;
|
return `قفل شده در ${date.toLocaleString('fa-IR')}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getSlotDefaultName = (template, slotKey) => {
|
||||||
|
const slot = (template?.availableSlots || []).find((s) => s.slot === slotKey);
|
||||||
|
return slot?.defaultName || '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSlotChange = (templateKey, vIdx) => {
|
||||||
|
const form = templateForm[templateKey];
|
||||||
|
if (!form || !form.variables[vIdx]) return;
|
||||||
|
const template = smsTemplates.value.find((t) => t.key === templateKey);
|
||||||
|
const currentVar = form.variables[vIdx];
|
||||||
|
if (!currentVar.name) {
|
||||||
|
currentVar.name = getSlotDefaultName(template, currentVar.slot);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const addVariable = (templateKey) => {
|
||||||
|
const form = templateForm[templateKey];
|
||||||
|
if (!form) return;
|
||||||
|
const template = smsTemplates.value.find((t) => t.key === templateKey);
|
||||||
|
const availableSlots = template?.availableSlots || [];
|
||||||
|
|
||||||
|
const existingSlots = new Set((form.variables || []).map((v) => v.slot));
|
||||||
|
const unusedSlot = availableSlots.find((s) => !existingSlots.has(s.slot));
|
||||||
|
const selectedSlot = unusedSlot || availableSlots[0] || { slot: 'param', defaultName: 'param' };
|
||||||
|
|
||||||
|
form.variables.push({
|
||||||
|
slot: selectedSlot.slot,
|
||||||
|
name: selectedSlot.defaultName || ''
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeVariable = (templateKey, index) => {
|
||||||
|
const form = templateForm[templateKey];
|
||||||
|
if (form && form.variables) {
|
||||||
|
form.variables.splice(index, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const applyStatus = (payload) => {
|
const applyStatus = (payload) => {
|
||||||
const status = payload?.data || payload || {};
|
const status = payload?.data || payload || {};
|
||||||
isLocked.value = Boolean(status.locked);
|
isLocked.value = Boolean(status.locked);
|
||||||
@@ -313,12 +409,12 @@ const confirmImport = () => {
|
|||||||
|
|
||||||
const emptyFormEntry = (template) => ({
|
const emptyFormEntry = (template) => ({
|
||||||
templateId: template?.templateId || '',
|
templateId: template?.templateId || '',
|
||||||
variables: Object.fromEntries(
|
variables: Array.isArray(template?.variables)
|
||||||
(template?.variables || []).map((variable) => [
|
? template.variables.map((variable) => ({
|
||||||
variable.slot,
|
slot: variable.slot,
|
||||||
variable.name || variable.defaultName || ''
|
name: variable.name || ''
|
||||||
])
|
}))
|
||||||
)
|
: []
|
||||||
});
|
});
|
||||||
|
|
||||||
const applySmsTemplates = (payload) => {
|
const applySmsTemplates = (payload) => {
|
||||||
@@ -353,7 +449,12 @@ const saveSmsTemplates = async () => {
|
|||||||
const entry = templateForm[template.key] || emptyFormEntry(template);
|
const entry = templateForm[template.key] || emptyFormEntry(template);
|
||||||
smsTemplatesPayload[template.key] = {
|
smsTemplatesPayload[template.key] = {
|
||||||
templateId: entry.templateId || '',
|
templateId: entry.templateId || '',
|
||||||
variables: { ...(entry.variables || {}) }
|
variables: (entry.variables || [])
|
||||||
|
.filter((v) => v.slot)
|
||||||
|
.map((v) => ({
|
||||||
|
slot: v.slot,
|
||||||
|
name: v.name || getSlotDefaultName(template, v.slot) || v.slot
|
||||||
|
}))
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const response = await settingsApi.save({ smsTemplates: smsTemplatesPayload });
|
const response = await settingsApi.save({ smsTemplates: smsTemplatesPayload });
|
||||||
|
|||||||
Reference in New Issue
Block a user