fix some bugs 4
This commit is contained in:
@@ -0,0 +1,55 @@
|
|||||||
|
import type { Customer } from '@/model/customer'
|
||||||
|
import useAxios from '@core/utils/useAxios'
|
||||||
|
|
||||||
|
export const sendCustomerDirectSms = async (payload: {
|
||||||
|
customer_id: number
|
||||||
|
message: string
|
||||||
|
}) => useAxios.post<{ ok: boolean }>('/admin/messages/send_customer', payload)
|
||||||
|
|
||||||
|
export type DirectSmsSnippet = {
|
||||||
|
key: string
|
||||||
|
label: string
|
||||||
|
insert: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** دکمههای آماده برای درج در متن پیامک */
|
||||||
|
export function buildDirectSmsSnippets(customer?: Customer | null, appName = '', appUrl = ''): DirectSmsSnippet[] {
|
||||||
|
const name = (customer?.name ?? '').trim() || 'مشتری'
|
||||||
|
return [
|
||||||
|
{ key: 'name', label: 'نام مشتری', insert: name },
|
||||||
|
{ key: 'app_name', label: 'نام اپ', insert: appName || 'اپ' },
|
||||||
|
{ key: 'app_url', label: 'لینک اپ', insert: appUrl || '' },
|
||||||
|
{ key: 'name_ph', label: '%نام%', insert: '%نام%' },
|
||||||
|
{ key: 'app_name_ph', label: '%نام_اپ%', insert: '%نام_اپ%' },
|
||||||
|
{ key: 'app_url_ph', label: '%لینک_اپ%', insert: '%لینک_اپ%' }
|
||||||
|
].filter((s) => s.insert !== '')
|
||||||
|
}
|
||||||
|
|
||||||
|
const BLOCKED = [
|
||||||
|
'fuck', 'shit', 'bitch', 'asshole', 'bastard', 'dick', 'pussy', 'cunt',
|
||||||
|
'motherfucker', 'nigger', 'faggot', 'slut', 'whore',
|
||||||
|
'کیر', 'کس', 'کص', 'جنده', 'گایید', 'گاییدن', 'گایدم', 'بیناموس',
|
||||||
|
'بیناموس', 'بی ناموس', 'حرومزاده', 'حرامزاده', 'مادرجنده', 'ننه جنده',
|
||||||
|
'کونی', 'کون', 'لاشی', 'عن', 'اشغال', 'دیوث', 'قرمساق'
|
||||||
|
]
|
||||||
|
|
||||||
|
function normalize(text: string): string {
|
||||||
|
return text
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[ي]/g, 'ی')
|
||||||
|
.replace(/[ك]/g, 'ک')
|
||||||
|
.replace(/[\u200c]/g, '')
|
||||||
|
.replace(/[^\p{L}\p{N}\s]+/gu, ' ')
|
||||||
|
.replace(/\s+/g, ' ')
|
||||||
|
.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findProfanity(message: string): string | null {
|
||||||
|
const normalized = normalize(message)
|
||||||
|
if (!normalized) return null
|
||||||
|
for (const word of BLOCKED) {
|
||||||
|
const needle = normalize(word)
|
||||||
|
if (needle && normalized.includes(needle)) return word
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
@@ -92,7 +92,11 @@ export const EDITABLE_SETTING_GROUPS: SettingGroupDef[] = [
|
|||||||
id: 'notifications',
|
id: 'notifications',
|
||||||
title: 'پیامک و اعلان',
|
title: 'پیامک و اعلان',
|
||||||
icon: 'vuesax-linear:notification',
|
icon: 'vuesax-linear:notification',
|
||||||
match: (k) => /^charge_notif/.test(k)
|
match: (k) =>
|
||||||
|
k === 'charge_notif' ||
|
||||||
|
k === 'group_sms' ||
|
||||||
|
k === 'register_referral_sms_to_referrer' ||
|
||||||
|
/^sms_notif_/.test(k)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'features',
|
id: 'features',
|
||||||
@@ -115,7 +119,7 @@ export const SYSTEM_SETTING_GROUPS: SettingGroupDef[] = [
|
|||||||
title: 'عمومی و برند',
|
title: 'عمومی و برند',
|
||||||
icon: 'vuesax-linear:home-2',
|
icon: 'vuesax-linear:home-2',
|
||||||
match: (k) =>
|
match: (k) =>
|
||||||
/^(name_fa|name_en|app_url|admin_url|apk_download_link|banner_enabled|permision__enabled|bace_url|server_url|logo_|title_pwa|title_fav|metal_type|main_metal|unit$|open_register|auto_active_customer|store_status|key$|is_store|group_sms|has_page_earn_money|sms_panel)/.test(
|
/^(name_fa|name_en|app_url|admin_url|apk_download_link|banner_enabled|permision__enabled|bace_url|server_url|logo_|title_pwa|title_fav|metal_type|main_metal|unit$|open_register|auto_active_customer|store_status|key$|is_store|group_sms|customer_direct_sms|has_page_earn_money|sms_panel)/.test(
|
||||||
k
|
k
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -259,7 +259,23 @@ const KEY_TYPE_FALLBACK: Record<string, SettingFieldType> = {
|
|||||||
payping_round_toman: 'switch',
|
payping_round_toman: 'switch',
|
||||||
payping_strict_failed_withdraw: 'switch',
|
payping_strict_failed_withdraw: 'switch',
|
||||||
has_page_earn_money: 'switch',
|
has_page_earn_money: 'switch',
|
||||||
|
customer_direct_sms: 'switch',
|
||||||
charge_notif: 'switch',
|
charge_notif: 'switch',
|
||||||
|
sms_notif_order_admin: 'switch',
|
||||||
|
sms_notif_order_customer: 'switch',
|
||||||
|
sms_notif_withdraw_admin: 'switch',
|
||||||
|
sms_notif_withdraw_otp: 'switch',
|
||||||
|
sms_notif_login_otp: 'switch',
|
||||||
|
sms_notif_gateway_charge_admin: 'switch',
|
||||||
|
sms_notif_register_customer: 'switch',
|
||||||
|
sms_notif_register_admin: 'switch',
|
||||||
|
sms_notif_jibit_admin: 'switch',
|
||||||
|
sms_notif_bank_card_admin: 'switch',
|
||||||
|
sms_notif_bank_card_customer: 'switch',
|
||||||
|
sms_notif_fund_withdraw_admin: 'switch',
|
||||||
|
sms_notif_fund_withdraw_customer: 'switch',
|
||||||
|
sms_notif_price_trigger: 'switch',
|
||||||
|
sms_notif_wallet_alert: 'switch',
|
||||||
group_sms: 'switch',
|
group_sms: 'switch',
|
||||||
montzar_closed: 'switch',
|
montzar_closed: 'switch',
|
||||||
montzar_open_buy: 'switch',
|
montzar_open_buy: 'switch',
|
||||||
@@ -505,8 +521,10 @@ const KEY_HELP_FALLBACK: Record<string, string> = {
|
|||||||
'روشن: کاربران جدید میتوانند ثبتنام کنند. خاموش: فقط ورود کاربران قبلی مجاز است.',
|
'روشن: کاربران جدید میتوانند ثبتنام کنند. خاموش: فقط ورود کاربران قبلی مجاز است.',
|
||||||
sms_panel:
|
sms_panel:
|
||||||
'روشن: پنل پیامک و کیف پول billing فعال است. خاموش: پیامکها مثل قبل ارسال میشوند بدون billing.',
|
'روشن: پنل پیامک و کیف پول billing فعال است. خاموش: پیامکها مثل قبل ارسال میشوند بدون billing.',
|
||||||
|
customer_direct_sms:
|
||||||
|
'روشن: در لیست مشتریان دکمه ارسال پیامک تکی نمایش داده میشود و ادمین میتواند برای هر مشتری متن دلخواه بفرستد. خاموش: دکمه مخفی است. فقط از صفحه دیپلوی تغییر میکند.',
|
||||||
auto_active_customer:
|
auto_active_customer:
|
||||||
'روشن: حساب بلافاصله پس از ثبتنام فعال میشود. خاموش: نیاز به تأیید دستی شما در پنل دارد.',
|
'روشن: بعد از ثبتنام، کاربر میتواند وارد اپ شود (بدون تأیید دستی ادمین). خاموش: ورود فقط بعد از فعالسازی دستی شما در پنل. این سوییچ اجازهٔ خرید/فروش نمیدهد — معامله فقط بعد از احراز هویت (is_legal) ممکن است.',
|
||||||
close_price:
|
close_price:
|
||||||
'فیلتر اصلی ساعت کاری فروشگاه. روشن: خرید، فروش و نمایش قیمت فقط بین «ساعت شروع» و «ساعت پایان» انجام میشود و خارج از این بازه به مشتری «فروشگاه بسته است» نشان داده میشود. خاموش: محدودیت ساعتی سراسری اعمال نمیشود (مگر برای محصولی ساعت اختصاصی تعریف کرده باشید).',
|
'فیلتر اصلی ساعت کاری فروشگاه. روشن: خرید، فروش و نمایش قیمت فقط بین «ساعت شروع» و «ساعت پایان» انجام میشود و خارج از این بازه به مشتری «فروشگاه بسته است» نشان داده میشود. خاموش: محدودیت ساعتی سراسری اعمال نمیشود (مگر برای محصولی ساعت اختصاصی تعریف کرده باشید).',
|
||||||
startDay:
|
startDay:
|
||||||
@@ -610,7 +628,37 @@ const KEY_HELP_FALLBACK: Record<string, string> = {
|
|||||||
deposit_sms_support_number:
|
deposit_sms_support_number:
|
||||||
'شماره تماس پشتیبانی برای راهنمای واریز و دکمه پیامک داخل مودال شارژ. برای نمایش در منوی شناور، باید «تماس تلفنی» یا «پیامک» را جداگانه در لیست پیامرسانهای پشتیبانی اضافه کنید.',
|
'شماره تماس پشتیبانی برای راهنمای واریز و دکمه پیامک داخل مودال شارژ. برای نمایش در منوی شناور، باید «تماس تلفنی» یا «پیامک» را جداگانه در لیست پیامرسانهای پشتیبانی اضافه کنید.',
|
||||||
charge_notif:
|
charge_notif:
|
||||||
'روشن: پس از شارژ کیف توسط ادمین، پیامک به مشتری ارسال میشود. خاموش: پیامکی پس از شارژ ارسال نمیشود.',
|
'روشن: بعد از شارژ یا کسر کیف توسط ادمین، پیامک به مشتری و ادمینهای دارای «ارسال پیامک» میرود. خاموش: هیچکدام ارسال نمیشود.',
|
||||||
|
sms_notif_order_admin:
|
||||||
|
'روشن: بعد از خرید یا فروش موفق، پیامک خلاصه معامله به ادمینهای دارای «ارسال پیامک» میرود. خاموش: ادمین پیامک معامله نمیگیرد.',
|
||||||
|
sms_notif_order_customer:
|
||||||
|
'روشن: بعد از خرید یا فروش موفق، پیامک رسید (مقدار، مبلغ، موجودی) به مشتری میرود. خاموش: مشتری پیامک معامله نمیگیرد.',
|
||||||
|
sms_notif_withdraw_admin:
|
||||||
|
'روشن: وقتی مشتری درخواست برداشت ریالی میدهد، پیامک به ادمینهای دارای «ارسال پیامک» میرود. خاموش: اعلان برداشت به ادمین نمیرود.',
|
||||||
|
sms_notif_withdraw_otp:
|
||||||
|
'روشن: رمز یکبارمصرف برداشت برای مشتری پیامک میشود. خاموش: رمز پیامک نمیشود و برداشت با OTP عملاً کار نمیکند. خاموش کردن توصیه نمیشود.',
|
||||||
|
sms_notif_login_otp:
|
||||||
|
'روشن: رمز یکبارمصرف ورود پیامک میشود. خاموش: ورود با OTP ممکن نیست. خاموش کردن توصیه نمیشود.',
|
||||||
|
sms_notif_gateway_charge_admin:
|
||||||
|
'روشن: بعد از شارژ موفق از درگاه پرداخت، پیامک به ادمینهای دارای «ارسال پیامک» میرود. خاموش: اعلان شارژ درگاه نمیرود.',
|
||||||
|
sms_notif_register_customer:
|
||||||
|
'روشن: بعد از ثبتنام، پیامک خوشآمد (نام کاربری و لینک ورود) به مشتری میرود. خاموش: پیامک خوشآمد ارسال نمیشود.',
|
||||||
|
sms_notif_register_admin:
|
||||||
|
'روشن: وقتی کسی با کد معرف ثبتنام میکند و پیامک به معرف خاموش است، اعلان به ادمین میرود. خاموش: این اعلان ادمین نمیرود.',
|
||||||
|
sms_notif_jibit_admin:
|
||||||
|
'روشن: نتیجه احراز هویت جیبیت در ثبتنام برای ادمین پیامک میشود. خاموش: اعلان جیبیت نمیرود.',
|
||||||
|
sms_notif_bank_card_admin:
|
||||||
|
'روشن: ثبت کارت بانکی جدید یا شکست احراز کارت برای ادمین پیامک میشود. خاموش: اعلان کارت به ادمین نمیرود.',
|
||||||
|
sms_notif_bank_card_customer:
|
||||||
|
'روشن: بعد از تایید کارت بانکی توسط ادمین، پیامک تایید به مشتری میرود. خاموش: مشتری پیامک تایید کارت نمیگیرد.',
|
||||||
|
sms_notif_fund_withdraw_admin:
|
||||||
|
'روشن: برداشت از قلک برای ادمین پیامک میشود. خاموش: اعلان برداشت قلک به ادمین نمیرود.',
|
||||||
|
sms_notif_fund_withdraw_customer:
|
||||||
|
'روشن: بعد از برداشت از قلک، پیامک رسید به مشتری میرود. خاموش: مشتری پیامک برداشت قلک نمیگیرد.',
|
||||||
|
sms_notif_price_trigger:
|
||||||
|
'روشن: وقتی هشدار قیمت مشتری (اقدام=پیامک) فعال شود، پیامک به مشتری میرود. خاموش: هشدار قیمت پیامک نمیفرستد.',
|
||||||
|
sms_notif_wallet_alert:
|
||||||
|
'روشن: وقتی اعتبار پنل پیامک صفر یا منفی شود، هشدار به سوپرادمین و ادمینها میرود. خاموش: این هشدار نمیرود.',
|
||||||
group_sms:
|
group_sms:
|
||||||
'روشن: امکان ارسال پیامک گروهی از پنل ادمین فعال است. خاموش: ارسال پیامک گروهی غیرفعال است.',
|
'روشن: امکان ارسال پیامک گروهی از پنل ادمین فعال است. خاموش: ارسال پیامک گروهی غیرفعال است.',
|
||||||
profit_gold_api_enabled:
|
profit_gold_api_enabled:
|
||||||
@@ -729,6 +777,23 @@ export const KEY_LABEL_FALLBACK: Record<string, string> = {
|
|||||||
referral_gift_on_first_order: 'پاداش معرف پس از اولین سفارش',
|
referral_gift_on_first_order: 'پاداش معرف پس از اولین سفارش',
|
||||||
open_register: 'ثبتنام باز',
|
open_register: 'ثبتنام باز',
|
||||||
sms_panel: 'پنل پیامک (کیف پول)',
|
sms_panel: 'پنل پیامک (کیف پول)',
|
||||||
|
customer_direct_sms: 'پیامک تکی به مشتری از لیست',
|
||||||
|
charge_notif: 'پیامک شارژ ادمین',
|
||||||
|
sms_notif_order_admin: 'پیامک خرید/فروش به ادمین',
|
||||||
|
sms_notif_order_customer: 'پیامک خرید/فروش به مشتری',
|
||||||
|
sms_notif_withdraw_admin: 'پیامک درخواست برداشت به ادمین',
|
||||||
|
sms_notif_withdraw_otp: 'پیامک رمز برداشت',
|
||||||
|
sms_notif_login_otp: 'پیامک رمز ورود',
|
||||||
|
sms_notif_gateway_charge_admin: 'پیامک شارژ درگاه به ادمین',
|
||||||
|
sms_notif_register_customer: 'پیامک خوشآمد ثبتنام',
|
||||||
|
sms_notif_register_admin: 'پیامک ثبتنام به ادمین',
|
||||||
|
sms_notif_jibit_admin: 'پیامک احراز هویت به ادمین',
|
||||||
|
sms_notif_bank_card_admin: 'پیامک کارت بانکی به ادمین',
|
||||||
|
sms_notif_bank_card_customer: 'پیامک تایید کارت به مشتری',
|
||||||
|
sms_notif_fund_withdraw_admin: 'پیامک برداشت قلک به ادمین',
|
||||||
|
sms_notif_fund_withdraw_customer: 'پیامک برداشت قلک به مشتری',
|
||||||
|
sms_notif_price_trigger: 'پیامک هشدار قیمت',
|
||||||
|
sms_notif_wallet_alert: 'پیامک هشدار اعتبار پنل پیامک',
|
||||||
auto_active_customer: 'فعالسازی خودکار پس از ثبتنام',
|
auto_active_customer: 'فعالسازی خودکار پس از ثبتنام',
|
||||||
balance_report_exclude_customer_id: 'حذف مشتری از گزارش تراز',
|
balance_report_exclude_customer_id: 'حذف مشتری از گزارش تراز',
|
||||||
chart_use_local_data: 'نمودار از داده داخلی',
|
chart_use_local_data: 'نمودار از داده داخلی',
|
||||||
@@ -777,6 +842,7 @@ export const SETTING_LOCKED_KEYS = new Set([
|
|||||||
'external_order_api_enabled',
|
'external_order_api_enabled',
|
||||||
'fee_deduct_from_gold',
|
'fee_deduct_from_gold',
|
||||||
'group_sms',
|
'group_sms',
|
||||||
|
'customer_direct_sms',
|
||||||
'has_page_earn_money',
|
'has_page_earn_money',
|
||||||
'is_store',
|
'is_store',
|
||||||
'sms_panel',
|
'sms_panel',
|
||||||
@@ -811,6 +877,12 @@ export const SETTING_EDITABLE_KEYS = new Set([
|
|||||||
'name_fa',
|
'name_fa',
|
||||||
'auto_active_customer', 'balance_report_exclude_customer_id',
|
'auto_active_customer', 'balance_report_exclude_customer_id',
|
||||||
'buy_fee', 'charge_notif', 'close_price',
|
'buy_fee', 'charge_notif', 'close_price',
|
||||||
|
'sms_notif_order_admin', 'sms_notif_order_customer',
|
||||||
|
'sms_notif_withdraw_admin', 'sms_notif_withdraw_otp', 'sms_notif_login_otp',
|
||||||
|
'sms_notif_gateway_charge_admin', 'sms_notif_register_customer', 'sms_notif_register_admin',
|
||||||
|
'sms_notif_jibit_admin', 'sms_notif_bank_card_admin', 'sms_notif_bank_card_customer',
|
||||||
|
'sms_notif_fund_withdraw_admin', 'sms_notif_fund_withdraw_customer',
|
||||||
|
'sms_notif_price_trigger', 'sms_notif_wallet_alert',
|
||||||
'deposit_sms_support_number', 'deposit_support_message_text',
|
'deposit_sms_support_number', 'deposit_support_message_text',
|
||||||
'deposit_account_holder', 'deposit_card_number', 'deposit_sheba',
|
'deposit_account_holder', 'deposit_card_number', 'deposit_sheba',
|
||||||
'endDay',
|
'endDay',
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
<template>
|
||||||
|
<BasicModal
|
||||||
|
v-model:visible="visibleProxy"
|
||||||
|
:width="isMobile ? '94vw' : '36rem'"
|
||||||
|
:title="modalTitle"
|
||||||
|
ok-text="ارسال پیامک"
|
||||||
|
:loading="loading"
|
||||||
|
:ok-button-props="{ disabled: !canSubmit }"
|
||||||
|
@ok="submit"
|
||||||
|
@close="close"
|
||||||
|
>
|
||||||
|
<div class="customer-sms-modal flex flex-col gap-3">
|
||||||
|
<p class="m-0 text-xs" style="color: var(--admin-text-secondary)">
|
||||||
|
گیرنده:
|
||||||
|
<strong dir="ltr" class="text-primary">{{ customer?.mobile || '—' }}</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<a-button
|
||||||
|
v-for="snippet in snippets"
|
||||||
|
:key="snippet.key"
|
||||||
|
size="small"
|
||||||
|
@click="appendSnippet(snippet.insert)"
|
||||||
|
>
|
||||||
|
{{ snippet.label }}
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-textarea
|
||||||
|
v-model:value="message"
|
||||||
|
:rows="6"
|
||||||
|
:maxlength="700"
|
||||||
|
show-count
|
||||||
|
placeholder="متن پیامک را بنویسید…"
|
||||||
|
class="customer-sms-modal__textarea"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<p v-if="profanityHit" class="m-0 text-xs text-red-500">
|
||||||
|
متن حاوی عبارت نامناسب است — قبل از ارسال اصلاح کنید.
|
||||||
|
</p>
|
||||||
|
<p class="m-0 text-xs" style="color: var(--admin-text-secondary)">
|
||||||
|
متغیرها در سرور جایگزین میشوند: %نام% · %نام_اپ% · %لینک_اپ%
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</BasicModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref, watch } from 'vue'
|
||||||
|
import { message as antMessage } from 'ant-design-vue'
|
||||||
|
import type { Customer } from '@/model/customer'
|
||||||
|
import { isMobile } from '@core/utils/is'
|
||||||
|
import BasicModal from '@/components/BasicModal.vue'
|
||||||
|
import {
|
||||||
|
buildDirectSmsSnippets,
|
||||||
|
findProfanity,
|
||||||
|
sendCustomerDirectSms
|
||||||
|
} from '@/api/customerDirectSms'
|
||||||
|
import { getConfiguration } from '@/api/configuration'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
visible: boolean
|
||||||
|
customer: Customer | null
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:visible', value: boolean): void
|
||||||
|
(e: 'sent'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const visibleProxy = computed({
|
||||||
|
get: () => props.visible,
|
||||||
|
set: (v: boolean) => emit('update:visible', v)
|
||||||
|
})
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const message = ref('')
|
||||||
|
const appName = ref('')
|
||||||
|
const appUrl = ref('')
|
||||||
|
|
||||||
|
const modalTitle = computed(() =>
|
||||||
|
props.customer?.name ? `پیامک به ${props.customer.name}` : 'ارسال پیامک به مشتری'
|
||||||
|
)
|
||||||
|
|
||||||
|
const snippets = computed(() =>
|
||||||
|
buildDirectSmsSnippets(props.customer, appName.value, appUrl.value)
|
||||||
|
)
|
||||||
|
|
||||||
|
const profanityHit = computed(() => findProfanity(message.value))
|
||||||
|
|
||||||
|
const canSubmit = computed(
|
||||||
|
() => message.value.trim().length > 0 && !profanityHit.value && !loading.value
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.visible,
|
||||||
|
async (open) => {
|
||||||
|
if (!open) return
|
||||||
|
message.value = ''
|
||||||
|
try {
|
||||||
|
const rows = await getConfiguration()
|
||||||
|
const list = Array.isArray(rows) ? rows : []
|
||||||
|
appName.value = String(list.find((r) => r.key === 'name_fa')?.value ?? '').trim()
|
||||||
|
appUrl.value = String(list.find((r) => r.key === 'app_url')?.value ?? '').trim()
|
||||||
|
} catch {
|
||||||
|
appName.value = ''
|
||||||
|
appUrl.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function appendSnippet(text: string) {
|
||||||
|
if (!text) return
|
||||||
|
const current = message.value
|
||||||
|
const needsSpace = current.length > 0 && !/\s$/.test(current)
|
||||||
|
message.value = current + (needsSpace ? ' ' : '') + text
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
visibleProxy.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submit() {
|
||||||
|
if (!props.customer?.id || !canSubmit.value) return
|
||||||
|
const hit = findProfanity(message.value)
|
||||||
|
if (hit) {
|
||||||
|
antMessage.error('متن حاوی محتوای نامناسب است')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
loading.value = true
|
||||||
|
await sendCustomerDirectSms({
|
||||||
|
customer_id: Number(props.customer.id),
|
||||||
|
message: message.value.trim()
|
||||||
|
})
|
||||||
|
antMessage.success('پیامک ارسال شد')
|
||||||
|
emit('sent')
|
||||||
|
close()
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
@chargeWallet="forwardAndClose('chargeWallet', $event)"
|
@chargeWallet="forwardAndClose('chargeWallet', $event)"
|
||||||
@toggleTrade="forward('toggleTrade', $event)"
|
@toggleTrade="forward('toggleTrade', $event)"
|
||||||
@confirmUser="forwardAndClose('confirmUser', $event)"
|
@confirmUser="forwardAndClose('confirmUser', $event)"
|
||||||
|
@sendSms="forwardAndClose('sendSms', $event)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -57,6 +58,7 @@ const emit = defineEmits<{
|
|||||||
(e: 'chargeWallet', item: Customer): void
|
(e: 'chargeWallet', item: Customer): void
|
||||||
(e: 'toggleTrade', item: Customer): void
|
(e: 'toggleTrade', item: Customer): void
|
||||||
(e: 'confirmUser', item: Customer): void
|
(e: 'confirmUser', item: Customer): void
|
||||||
|
(e: 'sendSms', item: Customer): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { sheetVisible, selectedItem, openSheet, syncSelectedItem } = useMobileDetailSheet<Customer>()
|
const { sheetVisible, selectedItem, openSheet, syncSelectedItem } = useMobileDetailSheet<Customer>()
|
||||||
@@ -69,6 +71,7 @@ type SheetAction =
|
|||||||
| 'chargeWallet'
|
| 'chargeWallet'
|
||||||
| 'toggleTrade'
|
| 'toggleTrade'
|
||||||
| 'confirmUser'
|
| 'confirmUser'
|
||||||
|
| 'sendSms'
|
||||||
|
|
||||||
function forward(action: SheetAction, item: Customer) {
|
function forward(action: SheetAction, item: Customer) {
|
||||||
emit(action, item)
|
emit(action, item)
|
||||||
|
|||||||
@@ -46,6 +46,15 @@
|
|||||||
<BaseButton size="small" type="default" icon="vuesax-linear:archive-tick" @click="closeAndEmit('archive', row)">
|
<BaseButton size="small" type="default" icon="vuesax-linear:archive-tick" @click="closeAndEmit('archive', row)">
|
||||||
بایگانی
|
بایگانی
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
|
<BaseButton
|
||||||
|
v-if="customerDirectSmsEnabled"
|
||||||
|
size="small"
|
||||||
|
type="default"
|
||||||
|
icon="vuesax-linear:sms"
|
||||||
|
@click="closeAndEmit('sendSms', row)"
|
||||||
|
>
|
||||||
|
پیامک
|
||||||
|
</BaseButton>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
v-if="!row.is_legal"
|
v-if="!row.is_legal"
|
||||||
size="small"
|
size="small"
|
||||||
@@ -96,6 +105,7 @@ import MobileDetailSheet from '@/components/mobile/MobileDetailSheet.vue'
|
|||||||
import MobileInfoGrid from '@/components/mobile/MobileInfoGrid.vue'
|
import MobileInfoGrid from '@/components/mobile/MobileInfoGrid.vue'
|
||||||
import CustomerWalletBalancePanel from '@/components/customer/CustomerWalletBalancePanel.vue'
|
import CustomerWalletBalancePanel from '@/components/customer/CustomerWalletBalancePanel.vue'
|
||||||
import { toJalali } from '@core/utils/useDataTime'
|
import { toJalali } from '@core/utils/useDataTime'
|
||||||
|
import { customerDirectSmsEnabledSetting } from '@/utils/config'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
visible: boolean
|
visible: boolean
|
||||||
@@ -111,9 +121,11 @@ const emit = defineEmits<{
|
|||||||
(e: 'chargeWallet', item: Customer): void
|
(e: 'chargeWallet', item: Customer): void
|
||||||
(e: 'toggleTrade', item: Customer): void
|
(e: 'toggleTrade', item: Customer): void
|
||||||
(e: 'confirmUser', item: Customer): void
|
(e: 'confirmUser', item: Customer): void
|
||||||
|
(e: 'sendSms', item: Customer): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const chargeTargets = ref<ChargeTarget[]>([])
|
const chargeTargets = ref<ChargeTarget[]>([])
|
||||||
|
const customerDirectSmsEnabled = customerDirectSmsEnabledSetting
|
||||||
|
|
||||||
const visibleProxy = computed({
|
const visibleProxy = computed({
|
||||||
get: () => props.visible,
|
get: () => props.visible,
|
||||||
@@ -122,7 +134,7 @@ const visibleProxy = computed({
|
|||||||
|
|
||||||
const sheetTitle = computed(() => props.item?.name?.trim() || 'مشتری')
|
const sheetTitle = computed(() => props.item?.name?.trim() || 'مشتری')
|
||||||
|
|
||||||
type SheetEmit = 'archive' | 'chargeWallet' | 'info' | 'confirmUser' | 'edit'
|
type SheetEmit = 'archive' | 'chargeWallet' | 'info' | 'confirmUser' | 'edit' | 'sendSms'
|
||||||
|
|
||||||
function closeAndEmit(event: SheetEmit, row: Customer) {
|
function closeAndEmit(event: SheetEmit, row: Customer) {
|
||||||
visibleProxy.value = false
|
visibleProxy.value = false
|
||||||
|
|||||||
@@ -93,6 +93,15 @@
|
|||||||
:color="record.trade_active === 1 ? '#54AD06' : '#ad0627'"
|
:color="record.trade_active === 1 ? '#54AD06' : '#ad0627'"
|
||||||
/>
|
/>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
<a-tooltip
|
||||||
|
v-if="customerDirectSmsEnabled"
|
||||||
|
placement="bottom"
|
||||||
|
class="cursor-pointer"
|
||||||
|
@click="openDirectSms(record)"
|
||||||
|
>
|
||||||
|
<template #title><span>ارسال پیامک</span></template>
|
||||||
|
<Icon icon="vuesax-linear:sms" color="#0ea5e9" />
|
||||||
|
</a-tooltip>
|
||||||
<a-tooltip
|
<a-tooltip
|
||||||
v-if="!record.is_legal"
|
v-if="!record.is_legal"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
@@ -117,9 +126,12 @@
|
|||||||
@chargeWallet="emits('chargeWallet', $event)"
|
@chargeWallet="emits('chargeWallet', $event)"
|
||||||
@toggleTrade="(item) => changeCustomerOrderActivationFunction(item.id)"
|
@toggleTrade="(item) => changeCustomerOrderActivationFunction(item.id)"
|
||||||
@confirmUser="openConfirmUser"
|
@confirmUser="openConfirmUser"
|
||||||
|
@sendSms="openDirectSms"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<CustomerDirectSmsModal v-model:visible="directSmsVisible" :customer="directSmsCustomer" />
|
||||||
|
|
||||||
<BasicModal
|
<BasicModal
|
||||||
v-model:visible="confirmVisible"
|
v-model:visible="confirmVisible"
|
||||||
:width="isMobile ? '92vw' : '50vw'"
|
:width="isMobile ? '92vw' : '50vw'"
|
||||||
@@ -176,6 +188,7 @@ import {
|
|||||||
} from '@/api/customer'
|
} from '@/api/customer'
|
||||||
import { buildCustomerColumns } from '@/pages/menuPages/customers/base/hooks/customerColumns'
|
import { buildCustomerColumns } from '@/pages/menuPages/customers/base/hooks/customerColumns'
|
||||||
import CustomerMobileCardList from '@/pages/menuPages/customers/base/components/CustomerMobileCardList.vue'
|
import CustomerMobileCardList from '@/pages/menuPages/customers/base/components/CustomerMobileCardList.vue'
|
||||||
|
import CustomerDirectSmsModal from '@/pages/menuPages/customers/base/components/CustomerDirectSmsModal.vue'
|
||||||
import Icon from '@core/components/BaseIcon/src/Icon.vue'
|
import Icon from '@core/components/BaseIcon/src/Icon.vue'
|
||||||
import useBaseTable from '@core/components/BaseTable/hooks/useBaseTable'
|
import useBaseTable from '@core/components/BaseTable/hooks/useBaseTable'
|
||||||
import type { TableInput } from '@core/type/baseTable'
|
import type { TableInput } from '@core/type/baseTable'
|
||||||
@@ -185,7 +198,13 @@ import { MODAL_ABOVE_ACTION_SHEET_Z_INDEX } from '@/constants/overlayZIndex'
|
|||||||
import { isMobile } from '@core/utils/is'
|
import { isMobile } from '@core/utils/is'
|
||||||
import TahesabRetryModal from '@/components/TahesabRetryModal.vue'
|
import TahesabRetryModal from '@/components/TahesabRetryModal.vue'
|
||||||
import PargarRetryModal from '@/components/PargarRetryModal.vue'
|
import PargarRetryModal from '@/components/PargarRetryModal.vue'
|
||||||
import { tahesabEnabledSetting, pargarEnabledSetting, initAccountingSettingsFromApi } from '@/utils/config'
|
import {
|
||||||
|
tahesabEnabledSetting,
|
||||||
|
pargarEnabledSetting,
|
||||||
|
customerDirectSmsEnabledSetting,
|
||||||
|
initAccountingSettingsFromApi,
|
||||||
|
initWalletSettingsFromApi
|
||||||
|
} from '@/utils/config'
|
||||||
|
|
||||||
const [submit, reload] = useBaseTable()
|
const [submit, reload] = useBaseTable()
|
||||||
const emits = defineEmits(['delete', 'edit', 'add', 'info', 'chargeWallet'])
|
const emits = defineEmits(['delete', 'edit', 'add', 'info', 'chargeWallet'])
|
||||||
@@ -199,6 +218,9 @@ const tahesabEnabled = tahesabEnabledSetting
|
|||||||
const pargarRegisterVisible = ref(false)
|
const pargarRegisterVisible = ref(false)
|
||||||
const pargarCustomer = ref<Customer>()
|
const pargarCustomer = ref<Customer>()
|
||||||
const pargarEnabled = pargarEnabledSetting
|
const pargarEnabled = pargarEnabledSetting
|
||||||
|
const customerDirectSmsEnabled = customerDirectSmsEnabledSetting
|
||||||
|
const directSmsVisible = ref(false)
|
||||||
|
const directSmsCustomer = ref<Customer | null>(null)
|
||||||
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
|
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
|
||||||
|
|
||||||
const tblProps = ref<any>({
|
const tblProps = ref<any>({
|
||||||
@@ -219,8 +241,14 @@ defineExpose({ fetch })
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
void initAccountingSettingsFromApi()
|
void initAccountingSettingsFromApi()
|
||||||
|
void initWalletSettingsFromApi()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function openDirectSms(user: Customer) {
|
||||||
|
directSmsCustomer.value = user
|
||||||
|
directSmsVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
async function changeCustomerActivationFunction(id: number) {
|
async function changeCustomerActivationFunction(id: number) {
|
||||||
try {
|
try {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|||||||
+18
-2
@@ -28,16 +28,20 @@ let settingsBootstrapped = false
|
|||||||
/** reactive — برای فیلتر منوی «بنرها» در پیکربندی */
|
/** reactive — برای فیلتر منوی «بنرها» در پیکربندی */
|
||||||
export const bannerEnabledSetting = ref(false)
|
export const bannerEnabledSetting = ref(false)
|
||||||
|
|
||||||
/** reactive — نمایش UI تهحsاب در حسابداری و ... */
|
/** reactive — نمایش UI تهحساب در حسابداری و ... */
|
||||||
export const tahesabEnabledSetting = ref(false)
|
export const tahesabEnabledSetting = ref(false)
|
||||||
|
|
||||||
/** reactive — نمایش UI پرگar در حسابداری و ... */
|
/** reactive — نمایش UI پرگار در حسابداری و ... */
|
||||||
export const pargarEnabledSetting = ref(false)
|
export const pargarEnabledSetting = ref(false)
|
||||||
|
|
||||||
|
/** reactive — دکمه پیامک تکی در لیست مشتریان */
|
||||||
|
export const customerDirectSmsEnabledSetting = ref(false)
|
||||||
|
|
||||||
export const accountingProviderSetting = ref<AccountingProvider>('none')
|
export const accountingProviderSetting = ref<AccountingProvider>('none')
|
||||||
|
|
||||||
let tahesabEnabledFromSettings: boolean | null = null
|
let tahesabEnabledFromSettings: boolean | null = null
|
||||||
let pargarEnabledFromSettings: boolean | null = null
|
let pargarEnabledFromSettings: boolean | null = null
|
||||||
|
let customerDirectSmsFromSettings: boolean | null = null
|
||||||
let accountingProviderFromSettings: AccountingProvider | null = null
|
let accountingProviderFromSettings: AccountingProvider | null = null
|
||||||
|
|
||||||
function isSwitchOn(value: string | undefined | null): boolean {
|
function isSwitchOn(value: string | undefined | null): boolean {
|
||||||
@@ -96,11 +100,21 @@ export function setPargarEnabledFromSettings(value: boolean): void {
|
|||||||
pargarEnabledSetting.value = value
|
pargarEnabledSetting.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setCustomerDirectSmsEnabledFromSettings(value: boolean): void {
|
||||||
|
customerDirectSmsFromSettings = value
|
||||||
|
customerDirectSmsEnabledSetting.value = value
|
||||||
|
}
|
||||||
|
|
||||||
export function applyBannerEnabledFromRows(rows: Array<{ key?: string; value?: string | null }>) {
|
export function applyBannerEnabledFromRows(rows: Array<{ key?: string; value?: string | null }>) {
|
||||||
const row = rows.find((item) => item.key === 'banner_enabled')
|
const row = rows.find((item) => item.key === 'banner_enabled')
|
||||||
setBannerEnabledFromSettings(row ? isSwitchOn(row.value) : false)
|
setBannerEnabledFromSettings(row ? isSwitchOn(row.value) : false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function applyCustomerDirectSmsFromRows(rows: Array<{ key?: string; value?: string | null }>) {
|
||||||
|
const row = rows.find((item) => item.key === 'customer_direct_sms')
|
||||||
|
setCustomerDirectSmsEnabledFromSettings(row ? isSwitchOn(row.value) : false)
|
||||||
|
}
|
||||||
|
|
||||||
async function bootstrapSettingsFromApi(): Promise<void> {
|
async function bootstrapSettingsFromApi(): Promise<void> {
|
||||||
if (settingsBootstrapped) return
|
if (settingsBootstrapped) return
|
||||||
|
|
||||||
@@ -115,12 +129,14 @@ async function bootstrapSettingsFromApi(): Promise<void> {
|
|||||||
storeModeFromSettings = isStoreRow ? isSwitchOn(isStoreRow.value) : false
|
storeModeFromSettings = isStoreRow ? isSwitchOn(isStoreRow.value) : false
|
||||||
applyAccountingProviderFromRows(list)
|
applyAccountingProviderFromRows(list)
|
||||||
applyBannerEnabledFromRows(list)
|
applyBannerEnabledFromRows(list)
|
||||||
|
applyCustomerDirectSmsFromRows(list)
|
||||||
setCachedMainMetal(mainMetalRow?.value)
|
setCachedMainMetal(mainMetalRow?.value)
|
||||||
const { setCachedMainWalletProductIds } = await import('@/components/mobile/utils/wallet')
|
const { setCachedMainWalletProductIds } = await import('@/components/mobile/utils/wallet')
|
||||||
setCachedMainWalletProductIds(parseMainWalletProductIds(mainWalletRow?.value))
|
setCachedMainWalletProductIds(parseMainWalletProductIds(mainWalletRow?.value))
|
||||||
} catch {
|
} catch {
|
||||||
storeModeFromSettings = false
|
storeModeFromSettings = false
|
||||||
setBannerEnabledFromSettings(false)
|
setBannerEnabledFromSettings(false)
|
||||||
|
setCustomerDirectSmsEnabledFromSettings(false)
|
||||||
applyAccountingProviderFromRows([])
|
applyAccountingProviderFromRows([])
|
||||||
setCachedMainMetal(null)
|
setCachedMainMetal(null)
|
||||||
const { setCachedMainWalletProductIds } = await import('@/components/mobile/utils/wallet')
|
const { setCachedMainWalletProductIds } = await import('@/components/mobile/utils/wallet')
|
||||||
|
|||||||
Reference in New Issue
Block a user