added setting for users
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
import { useAxios } from '@core/utils/useAxios'
|
||||
import type { TableInput } from '@core/type/baseTable'
|
||||
|
||||
export const getCustomerSpecialPricePackageTable = async (params: TableInput) =>
|
||||
await useAxios.post('/admin/customer_special_price_packages/table', params)
|
||||
|
||||
export const getCustomerSpecialPricePackage = async (id: number) =>
|
||||
await useAxios.get(`/admin/customer_special_price_packages/${id}`)
|
||||
|
||||
export const createCustomerSpecialPricePackage = async (payload: Record<string, unknown>) =>
|
||||
await useAxios.post('/admin/customer_special_price_packages', payload)
|
||||
|
||||
export const updateCustomerSpecialPricePackage = async (id: number, payload: Record<string, unknown>) =>
|
||||
await useAxios.put(`/admin/customer_special_price_packages/${id}`, payload)
|
||||
|
||||
export const deleteCustomerSpecialPricePackage = async (id: number) =>
|
||||
await useAxios.delete(`/admin/customer_special_price_packages/${id}`)
|
||||
|
||||
export const toggleCustomerSpecialPricePackage = async (id: number) =>
|
||||
await useAxios.put(`/admin/customer_special_price_packages/${id}/toggle`)
|
||||
|
||||
export const searchSpecialPriceCustomers = async (q: string) =>
|
||||
await useAxios.get('/admin/customer_special_price_packages/search-customers', { params: { q } })
|
||||
|
||||
export const getProductsByCategories = async (category_names: string[]) =>
|
||||
await useAxios.post('/admin/customer_special_price_packages/products-by-categories', {
|
||||
category_names
|
||||
})
|
||||
@@ -202,7 +202,7 @@ import SettingValueField from './SettingValueField.vue'
|
||||
import SettingCard from './SettingCard.vue'
|
||||
import { refreshAppModeMenu } from '@/router'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
import { applyBannerEnabledFromRows, applyTrackingSystemsEnabledFromRows, applyConditionalOrderEnabledFromRows, applyProductFundsEnabledFromRows, applyPriceTriggerEnabledFromRows, applyAppNotificationEnabledFromRows, applyPriceProviderFromRows, applyPriceChannelAccessFromRows, applySupportChatMenuEnabledFromRows, applyGroupSmsFromRows, setBannerEnabledFromSettings, setConditionalOrderEnabledFromSettings, setProductFundsEnabledFromSettings, setPriceTriggerEnabledFromSettings, setAppNotificationEnabledFromSettings, setPriceProviderFromSettings, setPriceChannelAccessFromSettings, setStoreModeFromSettings, setTrackingSystemsEnabledFromSettings, setGroupSmsEnabledFromSettings } from '@/utils/config'
|
||||
import { applyBannerEnabledFromRows, applyTrackingSystemsEnabledFromRows, applyConditionalOrderEnabledFromRows, applyProductFundsEnabledFromRows, applyPriceTriggerEnabledFromRows, applyAppNotificationEnabledFromRows, applyPriceProviderFromRows, applyPriceChannelAccessFromRows, applySupportChatMenuEnabledFromRows, applyGroupSmsFromRows, applyCustomerSpecialPricingFromRows, setBannerEnabledFromSettings, setConditionalOrderEnabledFromSettings, setProductFundsEnabledFromSettings, setPriceTriggerEnabledFromSettings, setAppNotificationEnabledFromSettings, setPriceProviderFromSettings, setPriceChannelAccessFromSettings, setStoreModeFromSettings, setTrackingSystemsEnabledFromSettings, setGroupSmsEnabledFromSettings, setCustomerSpecialPricingEnabledFromSettings } from '@/utils/config'
|
||||
import {
|
||||
EDITABLE_SETTING_GROUPS,
|
||||
SYSTEM_SETTING_GROUPS,
|
||||
@@ -485,12 +485,14 @@ async function initConfig() {
|
||||
applyPriceChannelAccessFromRows(baseData.value)
|
||||
applySupportChatMenuEnabledFromRows(baseData.value)
|
||||
applyGroupSmsFromRows(baseData.value)
|
||||
applyCustomerSpecialPricingFromRows(baseData.value)
|
||||
// در صفحهٔ عادی price_provider و show_to_users_chat و price_channel_access (can_edit=0) نیستند — از کل تنظیمات بخوان
|
||||
if (
|
||||
!baseData.value.some((row) => row.key === 'price_provider') ||
|
||||
!baseData.value.some((row) => row.key === 'show_to_users_chat') ||
|
||||
!baseData.value.some((row) => row.key === 'price_channel_access') ||
|
||||
!baseData.value.some((row) => row.key === 'group_sms')
|
||||
!baseData.value.some((row) => row.key === 'group_sms') ||
|
||||
!baseData.value.some((row) => row.key === 'customer_special_pricing')
|
||||
) {
|
||||
try {
|
||||
const { getConfiguration } = await import('@/api/configuration')
|
||||
@@ -500,6 +502,7 @@ async function initConfig() {
|
||||
applySupportChatMenuEnabledFromRows(rows)
|
||||
applyPriceChannelAccessFromRows(rows)
|
||||
applyGroupSmsFromRows(rows)
|
||||
applyCustomerSpecialPricingFromRows(rows)
|
||||
} catch {
|
||||
/* کش قبلی میماند */
|
||||
}
|
||||
@@ -572,6 +575,9 @@ function syncMenuFlagsFromSetting(key: string | undefined, value: string) {
|
||||
if (key === 'group_sms') {
|
||||
setGroupSmsEnabledFromSettings(isSwitchOn(value))
|
||||
}
|
||||
if (key === 'customer_special_pricing') {
|
||||
setCustomerSpecialPricingEnabledFromSettings(isSwitchOn(value))
|
||||
}
|
||||
}
|
||||
|
||||
function onValueChange(row: Const, value: string) {
|
||||
|
||||
@@ -179,7 +179,7 @@ export const SYSTEM_SETTING_GROUPS: SettingGroupDef[] = [
|
||||
title: 'دسترسی مشتری به قابلیتها',
|
||||
icon: 'vuesax-linear:people',
|
||||
match: (k) =>
|
||||
/^(enable_conditional_order|enable_product_funds|has_page_price_trigger|has_page_notification|has_page_gift_card|enable_customer_level_profit|banner_enabled|jibit_enabled|customer_direct_sms|has_page_earn_money|group_sms|tracking_systems_enabled|permision__enabled|show_to_users_chat)$/.test(
|
||||
/^(enable_conditional_order|enable_product_funds|has_page_price_trigger|has_page_notification|has_page_gift_card|enable_customer_level_profit|banner_enabled|jibit_enabled|customer_direct_sms|has_page_earn_money|group_sms|customer_special_pricing|tracking_systems_enabled|permision__enabled|show_to_users_chat)$/.test(
|
||||
k
|
||||
)
|
||||
},
|
||||
|
||||
@@ -322,6 +322,7 @@ const KEY_TYPE_FALLBACK: Record<string, SettingFieldType> = {
|
||||
sms_notif_wallet_alert: 'switch',
|
||||
sms_notif_support_admin: 'switch',
|
||||
group_sms: 'switch',
|
||||
customer_special_pricing: 'switch',
|
||||
montzar_closed: 'switch',
|
||||
montzar_open_buy: 'switch',
|
||||
montzar_open_sell: 'switch',
|
||||
@@ -825,6 +826,8 @@ const KEY_HELP_FALLBACK: Record<string, string> = {
|
||||
'روشن: وقتی مشتری اولین پیام خواندهنشده را در چت پشتیبانی میفرستد و ادمین همان گفتگو را باز و visible ندارد، پیامک به ادمینهای دارای «ارسال پیامک» میرود. خاموش: اعلان پیامک چت نمیرود.',
|
||||
group_sms:
|
||||
'روشن: دکمه ارسال پیامک گروهی در پنل ادمین فعال است (ارسال ۱۰تایی). خاموش: ارسال پیامک گروهی غیرفعال است. فقط از تنظیمات پیشرفته.',
|
||||
customer_special_pricing:
|
||||
'روشن: زیرمنوی قیمت ویژه در مشتریان و اعمال سود خرید/فروش پکیج روی مظنه و سفارش برای کاربران انتخابشده. خاموش: غیرفعال. فقط از دیپلوی قابل تغییر است.',
|
||||
profit_gold_api_enabled:
|
||||
'روشن: محاسبه سود طلا از API خارجی فعال است. خاموش: محاسبه سود از API خارجی انجام نمیشود (حالت پیشفرض اکثر نصبها).',
|
||||
main_wallet_products:
|
||||
@@ -986,6 +989,7 @@ export const KEY_LABEL_FALLBACK: Record<string, string> = {
|
||||
open_register: 'ثبتنام باز',
|
||||
sms_panel: 'پنل پیامک (کیف پول)',
|
||||
customer_direct_sms: 'پیامک تکی به مشتری از لیست',
|
||||
customer_special_pricing: 'قیمت ویژه مشتری',
|
||||
charge_notif: 'پیامک شارژ ادمین',
|
||||
sms_notif_order_admin: 'پیامک خرید/فروش به ادمین',
|
||||
sms_notif_order_customer: 'پیامک خرید/فروش به مشتری',
|
||||
@@ -1080,6 +1084,7 @@ export const SETTING_LOCKED_KEYS = new Set([
|
||||
'fee_deduct_from_gold',
|
||||
'referral_bonus_amount',
|
||||
'group_sms',
|
||||
'customer_special_pricing',
|
||||
'customer_direct_sms',
|
||||
'enable_conditional_order',
|
||||
'enable_product_funds',
|
||||
|
||||
@@ -4,23 +4,27 @@
|
||||
<BaseTable v-bind="tblProps" @register="submit" ref="tbl">
|
||||
<template #action="{ record }">
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<a-tooltip placement="bottom">
|
||||
<a-tooltip
|
||||
placement="bottom"
|
||||
class="cursor-pointer"
|
||||
:get-popup-container="tooltipContainer"
|
||||
@click="openAssetFunds(record)"
|
||||
>
|
||||
<template #title><span>قلکهای این دارایی</span></template>
|
||||
<Icon
|
||||
class="cursor-pointer"
|
||||
icon="vuesax-linear:safe-home"
|
||||
color="#7C3AED"
|
||||
@click="openAssetFunds(record)"
|
||||
/>
|
||||
<span class="inline-flex">
|
||||
<Icon icon="vuesax-linear:safe-home" color="#7C3AED" />
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom">
|
||||
<a-tooltip
|
||||
placement="bottom"
|
||||
class="cursor-pointer"
|
||||
:get-popup-container="tooltipContainer"
|
||||
@click="openAssetTx(record)"
|
||||
>
|
||||
<template #title><span>تراکنشهای این دارایی</span></template>
|
||||
<Icon
|
||||
class="cursor-pointer"
|
||||
icon="vuesax-linear:receipt-2"
|
||||
color="#4572E9"
|
||||
@click="openAssetTx(record)"
|
||||
/>
|
||||
<span class="inline-flex">
|
||||
<Icon icon="vuesax-linear:receipt-2" color="#4572E9" />
|
||||
</span>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
@@ -59,6 +63,7 @@ import {
|
||||
} from '@/api/user'
|
||||
import BasicActionSheet from '@/components/BasicActionSheet.vue'
|
||||
import BasicShowContext from '@/components/BasicShowContext.vue'
|
||||
import { toJalali } from '@core/utils/useDataTime'
|
||||
|
||||
const [submit, reload] = useBaseTable()
|
||||
const [assetFundsSubmit] = useBaseTable()
|
||||
@@ -106,7 +111,13 @@ const assetTxCols = [
|
||||
{ title: 'نوع', dataIndex: 'type_str', key: 'type_str', type: 'Text' },
|
||||
{ title: 'طلا', dataIndex: 'gold', key: 'gold', type: 'Number' },
|
||||
{ title: 'ریال', dataIndex: 'cash', key: 'cash', type: 'Number' },
|
||||
{ title: 'تاریخ', dataIndex: 'created_at', key: 'created_at', type: 'Text' }
|
||||
{
|
||||
title: 'تاریخ',
|
||||
dataIndex: 'created_at',
|
||||
key: 'created_at',
|
||||
type: 'DateTime',
|
||||
customRender: (r: any) => (r.record?.created_at ? toJalali(r.record.created_at) : '—')
|
||||
}
|
||||
]
|
||||
|
||||
const assetTxTblProps = computed(() => ({
|
||||
@@ -133,8 +144,17 @@ function openAssetTx(record: any) {
|
||||
assetTxVisible.value = true
|
||||
}
|
||||
|
||||
const tooltipContainer = () => document.body
|
||||
|
||||
const fetch = () => reload()
|
||||
defineExpose({ fetch })
|
||||
|
||||
onMounted(() => {})
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.ant-popover,
|
||||
.ant-tooltip {
|
||||
z-index: 1001 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,14 +4,16 @@
|
||||
<BaseTable v-bind="tblProps" @register="submit" ref="tbl">
|
||||
<template #action="{ record }">
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<a-tooltip placement="bottom">
|
||||
<a-tooltip
|
||||
placement="bottom"
|
||||
class="cursor-pointer"
|
||||
:get-popup-container="tooltipContainer"
|
||||
@click="openDetails(record)"
|
||||
>
|
||||
<template #title><span>مشخصات / تراکنشها</span></template>
|
||||
<Icon
|
||||
class="cursor-pointer"
|
||||
icon="vuesax-linear:info-circle"
|
||||
color="#4572E9"
|
||||
@click="openDetails(record)"
|
||||
/>
|
||||
<span class="inline-flex">
|
||||
<Icon icon="vuesax-linear:info-circle" color="#4572E9" />
|
||||
</span>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
@@ -96,6 +98,15 @@ function openDetails(record: any) {
|
||||
detailsVisible.value = true
|
||||
}
|
||||
|
||||
const tooltipContainer = () => document.body
|
||||
|
||||
const fetch = () => reload()
|
||||
defineExpose({ fetch })
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.ant-popover,
|
||||
.ant-tooltip {
|
||||
z-index: 1001 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -21,14 +21,23 @@ import { computed } from 'vue'
|
||||
import BasicMenu from '@/components/BasicMenu/index.vue'
|
||||
import { useRouterStore } from '@core/stores/router'
|
||||
import { get } from 'lodash'
|
||||
import { customerSpecialPricingEnabledSetting, initWalletSettingsFromApi } from '@/utils/config'
|
||||
|
||||
initWalletSettingsFromApi()
|
||||
|
||||
const routerStore = useRouterStore()
|
||||
const menu = computed(() => {
|
||||
const item = routerStore.routes.find((item) => item.name === 'customers')
|
||||
const children = get(item, 'children[0].children', []) as Array<{ meta?: { order?: number } }>
|
||||
return [...children].sort(
|
||||
(a, b) => (a.meta?.order ?? 99) - (b.meta?.order ?? 99)
|
||||
)
|
||||
const children = get(item, 'children[0].children', []) as Array<{
|
||||
name?: string
|
||||
meta?: { order?: number }
|
||||
}>
|
||||
return [...children]
|
||||
.filter(
|
||||
(child) =>
|
||||
child.name !== 'customerMain_specialPrice' || customerSpecialPricingEnabledSetting.value
|
||||
)
|
||||
.sort((a, b) => (a.meta?.order ?? 99) - (b.meta?.order ?? 99))
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
<template>
|
||||
<a-form :model="form" ref="formRef" layout="vertical" class="space-y-3">
|
||||
<a-form-item label="عنوان پکیج" name="title" :rules="[{ required: true, message: 'عنوان الزامی است' }]">
|
||||
<BasicInput v-model:value="form.title" placeholder="مثلاً مشتریان VIP" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="گروه دستهبندی" name="category_names" :rules="[{ required: true, type: 'array', min: 1, message: 'دسته را انتخاب کنید' }]">
|
||||
<BasicSelect
|
||||
v-model:value="form.category_names"
|
||||
:items="categoryItems"
|
||||
valueName="value"
|
||||
keyName="key"
|
||||
mode="multiple"
|
||||
class="w-full"
|
||||
placeholder="یک یا چند دسته"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="محصول" name="product_ids" :rules="[{ required: true, type: 'array', min: 1, message: 'محصول را انتخاب کنید' }]">
|
||||
<BasicSelect
|
||||
v-model:value="form.product_ids"
|
||||
:items="productItems"
|
||||
valueName="title"
|
||||
keyName="id"
|
||||
mode="multiple"
|
||||
class="w-full"
|
||||
:disabled="!form.category_names?.length"
|
||||
placeholder="ابتدا دسته را انتخاب کنید"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="لیست کاربران زیر مجموعه" name="customer_ids" :rules="[{ required: true, type: 'array', min: 1, message: 'کاربر را انتخاب کنید' }]">
|
||||
<a-select
|
||||
v-model:value="form.customer_ids"
|
||||
mode="multiple"
|
||||
class="w-full"
|
||||
placeholder="جستجو نام یا شماره…"
|
||||
show-search
|
||||
:filter-option="false"
|
||||
:options="customerOptions"
|
||||
@search="onCustomerSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<div class="grid gap-3 md:grid-cols-2">
|
||||
<a-form-item label="سود فروش" name="profit_sell">
|
||||
<BasicInput v-model:value="form.profit_sell" type="price" addon-before="ریال" />
|
||||
</a-form-item>
|
||||
<a-form-item label="سود خرید" name="profit_buy">
|
||||
<BasicInput v-model:value="form.profit_buy" type="price" addon-before="ریال" />
|
||||
</a-form-item>
|
||||
</div>
|
||||
|
||||
<a-form-item label="نمایش محصول">
|
||||
<a-switch v-model:checked="form.show_product">
|
||||
<template #checkedChildren>فعال</template>
|
||||
<template #unCheckedChildren>مخفی</template>
|
||||
</a-switch>
|
||||
<p class="m-0 mt-1 text-xs text-gray-500">
|
||||
خاموش = این محصولات برای کاربران پکیج در اپ مخفی میشوند.
|
||||
</p>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="وضعیت پکیج">
|
||||
<a-switch v-model:checked="form.is_active">
|
||||
<template #checkedChildren>فعال</template>
|
||||
<template #unCheckedChildren>غیرفعال</template>
|
||||
</a-switch>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FormInstance } from 'ant-design-vue'
|
||||
import { debounce } from 'lodash'
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import BasicInput from '@/components/BasicInput.vue'
|
||||
import BasicSelect from '@/components/BasicSelect.vue'
|
||||
import { syncAndLoadCategories, type CategorySelectItem } from '@/utils/categorySelect'
|
||||
import {
|
||||
getProductsByCategories,
|
||||
searchSpecialPriceCustomers
|
||||
} from '@/api/customerSpecialPrice'
|
||||
|
||||
export type SpecialPriceForm = {
|
||||
title: string
|
||||
category_names: string[]
|
||||
product_ids: number[]
|
||||
customer_ids: number[]
|
||||
profit_buy: number | string
|
||||
profit_sell: number | string
|
||||
show_product: boolean
|
||||
is_active: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
form: SpecialPriceForm
|
||||
}>()
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
const categoryItems = ref<CategorySelectItem[]>([])
|
||||
const productItems = ref<Array<{ id: number; title: string }>>([])
|
||||
const customerOptions = ref<Array<{ value: number; label: string }>>([])
|
||||
|
||||
async function loadCategories() {
|
||||
categoryItems.value = await syncAndLoadCategories({ selectOnly: true })
|
||||
}
|
||||
|
||||
async function loadProducts() {
|
||||
const names = props.form.category_names || []
|
||||
if (!names.length) {
|
||||
productItems.value = []
|
||||
props.form.product_ids = []
|
||||
return
|
||||
}
|
||||
const res: any = await getProductsByCategories(names)
|
||||
const rows = res?.products ?? res?.data?.products ?? res ?? []
|
||||
productItems.value = (Array.isArray(rows) ? rows : []).map((p: any) => ({
|
||||
id: Number(p.id),
|
||||
title: `${p.title || '-'} (${p.group_type || '-'})`
|
||||
}))
|
||||
const allowed = new Set(productItems.value.map((p) => p.id))
|
||||
props.form.product_ids = (props.form.product_ids || []).filter((id) => allowed.has(Number(id)))
|
||||
}
|
||||
|
||||
const onCustomerSearch = debounce(async (q: string) => {
|
||||
const res: any = await searchSpecialPriceCustomers(q || '')
|
||||
const rows = res?.customers ?? res?.data?.customers ?? []
|
||||
const mapped = (Array.isArray(rows) ? rows : []).map((c: any) => ({
|
||||
value: Number(c.id),
|
||||
label: `${c.name || '-'} — ${c.mobile || ''} (#${c.id})`
|
||||
}))
|
||||
const selected = new Set((props.form.customer_ids || []).map(Number))
|
||||
for (const item of customerOptions.value) {
|
||||
if (selected.has(item.value) && !mapped.some((m) => m.value === item.value)) {
|
||||
mapped.push(item)
|
||||
}
|
||||
}
|
||||
customerOptions.value = mapped
|
||||
}, 300)
|
||||
|
||||
watch(
|
||||
() => [...(props.form.category_names || [])],
|
||||
() => {
|
||||
void loadProducts()
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(async () => {
|
||||
await loadCategories()
|
||||
await loadProducts()
|
||||
await onCustomerSearch('')
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
validate: () => formRef.value?.validate(),
|
||||
seedCustomers: (rows: Array<{ id: number; name?: string; mobile?: string }>) => {
|
||||
customerOptions.value = rows.map((c) => ({
|
||||
value: Number(c.id),
|
||||
label: `${c.name || '-'} — ${c.mobile || ''} (#${c.id})`
|
||||
}))
|
||||
},
|
||||
seedProducts: (rows: Array<{ id: number; title?: string; group_type?: string }>) => {
|
||||
productItems.value = rows.map((p) => ({
|
||||
id: Number(p.id),
|
||||
title: `${p.title || '-'} (${p.group_type || '-'})`
|
||||
}))
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<div class="h-full min-h-0 min-w-0 max-w-full overflow-hidden">
|
||||
<BasicShowContext title="پکیجهای قیمت ویژه">
|
||||
<template v-if="isMobile" #extra>
|
||||
<BaseButton type="primary" size="small" icon="vuesax-linear:add" @click="emits('add')">
|
||||
افزودن
|
||||
</BaseButton>
|
||||
</template>
|
||||
|
||||
<BaseTable v-bind="tblProps" @register="submit" class="h-full min-h-0">
|
||||
<template #extraHeader>
|
||||
<BaseButton type="primary" size="small" icon="vuesax-linear:add" @click="emits('add')">
|
||||
افزودن پکیج
|
||||
</BaseButton>
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('edit', record)">
|
||||
<template #title><span>ویرایش</span></template>
|
||||
<Icon icon="vuesax-linear:edit-2" color="#4572E9" />
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('toggle', record)">
|
||||
<template #title>
|
||||
<span>{{ record.is_active ? 'غیرفعال کردن' : 'فعال کردن' }}</span>
|
||||
</template>
|
||||
<Icon
|
||||
:icon="record.is_active ? 'vuesax-linear:toggle-on-circle' : 'vuesax-linear:toggle-off-circle'"
|
||||
:color="record.is_active ? '#54AD06' : '#ad0627'"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-popconfirm
|
||||
title="پکیج حذف شود؟"
|
||||
ok-text="تایید"
|
||||
cancel-text="انصراف"
|
||||
@confirm="emits('delete', record)"
|
||||
>
|
||||
<a-tooltip placement="bottom" class="cursor-pointer">
|
||||
<template #title><span>حذف</span></template>
|
||||
<Icon icon="vuesax-linear:trash" color="#C53030" />
|
||||
</a-tooltip>
|
||||
</a-popconfirm>
|
||||
</div>
|
||||
</template>
|
||||
</BaseTable>
|
||||
</BasicShowContext>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { TableInput } from '@core/type/baseTable'
|
||||
import useBaseTable from '@core/components/BaseTable/hooks/useBaseTable'
|
||||
import Icon from '@core/components/BaseIcon/src/Icon.vue'
|
||||
import BasicShowContext from '@/components/BasicShowContext.vue'
|
||||
import { getCustomerSpecialPricePackageTable } from '@/api/customerSpecialPrice'
|
||||
import { getSpecialPricePackageColumns } from '../hooks/specialPriceColumns'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const emits = defineEmits<{
|
||||
(e: 'add'): void
|
||||
(e: 'edit', record: any): void
|
||||
(e: 'toggle', record: any): void
|
||||
(e: 'delete', record: any): void
|
||||
}>()
|
||||
|
||||
const [submit, reload] = useBaseTable()
|
||||
|
||||
const tblProps = ref({
|
||||
columns: getSpecialPricePackageColumns(),
|
||||
api: (params: TableInput) => getCustomerSpecialPricePackageTable(params)
|
||||
})
|
||||
|
||||
defineExpose({ fetch: reload })
|
||||
</script>
|
||||
@@ -0,0 +1,79 @@
|
||||
import type { BaseTableColumn } from '@core/type/baseTable'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import { toJalali } from '@core/utils/useDataTime'
|
||||
|
||||
export function getSpecialPricePackageColumns(): BaseTableColumn[] {
|
||||
return [
|
||||
{
|
||||
title: 'ردیف',
|
||||
dataIndex: 'id',
|
||||
key: 'row',
|
||||
filterable: false,
|
||||
sortable: false,
|
||||
customRender: (record: any) => record.index + 1
|
||||
},
|
||||
{
|
||||
title: 'عنوان پکیج',
|
||||
dataIndex: 'title',
|
||||
key: 'title',
|
||||
type: 'Text'
|
||||
},
|
||||
{
|
||||
title: 'تعداد کاربر',
|
||||
dataIndex: 'customers_count',
|
||||
key: 'customers_count',
|
||||
type: 'Number',
|
||||
filterable: false
|
||||
},
|
||||
{
|
||||
title: 'تعداد محصول',
|
||||
dataIndex: 'products_count',
|
||||
key: 'products_count',
|
||||
type: 'Number',
|
||||
filterable: false
|
||||
},
|
||||
{
|
||||
title: 'سود خرید',
|
||||
dataIndex: 'profit_buy',
|
||||
key: 'profit_buy',
|
||||
type: 'Text',
|
||||
customRender: ({ record }) => usePrice(record.profit_buy ?? 0, true, '')
|
||||
},
|
||||
{
|
||||
title: 'سود فروش',
|
||||
dataIndex: 'profit_sell',
|
||||
key: 'profit_sell',
|
||||
type: 'Text',
|
||||
customRender: ({ record }) => usePrice(record.profit_sell ?? 0, true, '')
|
||||
},
|
||||
{
|
||||
title: 'نمایش محصول',
|
||||
dataIndex: 'show_product',
|
||||
key: 'show_product',
|
||||
type: 'Text',
|
||||
customRender: ({ record }) => (record.show_product ? 'فعال' : 'مخفی')
|
||||
},
|
||||
{
|
||||
title: 'وضعیت',
|
||||
dataIndex: 'is_active',
|
||||
key: 'is_active',
|
||||
type: 'Text',
|
||||
customRender: ({ record }) => (record.is_active ? 'فعال' : 'غیرفعال')
|
||||
},
|
||||
{
|
||||
title: 'تاریخ',
|
||||
dataIndex: 'updated_at',
|
||||
key: 'updated_at',
|
||||
type: 'DateTime',
|
||||
customRender: (r: any) => (r.record?.updated_at ? toJalali(r.record.updated_at) : '—')
|
||||
},
|
||||
{
|
||||
title: 'عملیات',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
slot: 'action',
|
||||
filterable: false,
|
||||
sortable: false
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
<template>
|
||||
<div class="flex h-full w-full flex-col">
|
||||
<tbl
|
||||
ref="tableRef"
|
||||
class="h-full w-full"
|
||||
@add="openAdd"
|
||||
@edit="openEdit"
|
||||
@toggle="onToggle"
|
||||
@delete="onDelete"
|
||||
/>
|
||||
|
||||
<BasicActionSheet
|
||||
v-model:visible="editorVisible"
|
||||
:title="sheetTitle"
|
||||
modal-width="40rem"
|
||||
drawer-height="90vh"
|
||||
:loading="loading"
|
||||
:show-button="true"
|
||||
:show-cancel="true"
|
||||
:show-ok="true"
|
||||
ok-text="ثبت"
|
||||
cancel-text="انصراف"
|
||||
@ok="submitForm"
|
||||
@close="closeEditor"
|
||||
@cancel="closeEditor"
|
||||
>
|
||||
<SpecialPriceEditorForm ref="editorRef" :form="form" />
|
||||
</BasicActionSheet>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import BasicActionSheet from '@/components/BasicActionSheet.vue'
|
||||
import tbl from './components/table.vue'
|
||||
import SpecialPriceEditorForm, {
|
||||
type SpecialPriceForm
|
||||
} from './components/SpecialPriceEditorForm.vue'
|
||||
import {
|
||||
createCustomerSpecialPricePackage,
|
||||
deleteCustomerSpecialPricePackage,
|
||||
getCustomerSpecialPricePackage,
|
||||
toggleCustomerSpecialPricePackage,
|
||||
updateCustomerSpecialPricePackage
|
||||
} from '@/api/customerSpecialPrice'
|
||||
|
||||
const tableRef = ref<null | { fetch: () => void }>(null)
|
||||
const editorRef = ref<null | {
|
||||
validate: () => Promise<void>
|
||||
seedCustomers: (rows: any[]) => void
|
||||
seedProducts: (rows: any[]) => void
|
||||
}>(null)
|
||||
|
||||
const editorVisible = ref(false)
|
||||
const loading = ref(false)
|
||||
const editId = ref<number | null>(null)
|
||||
|
||||
const emptyForm = (): SpecialPriceForm => ({
|
||||
title: '',
|
||||
category_names: [],
|
||||
product_ids: [],
|
||||
customer_ids: [],
|
||||
profit_buy: 0,
|
||||
profit_sell: 0,
|
||||
show_product: true,
|
||||
is_active: true
|
||||
})
|
||||
|
||||
const form = ref<SpecialPriceForm>(emptyForm())
|
||||
|
||||
const sheetTitle = computed(() => (editId.value ? 'ویرایش پکیج قیمت ویژه' : 'افزودن پکیج قیمت ویژه'))
|
||||
|
||||
function openAdd() {
|
||||
editId.value = null
|
||||
form.value = emptyForm()
|
||||
editorVisible.value = true
|
||||
}
|
||||
|
||||
async function openEdit(record: any) {
|
||||
loading.value = true
|
||||
try {
|
||||
const res: any = await getCustomerSpecialPricePackage(Number(record.id))
|
||||
const data = res?.data ?? res
|
||||
editId.value = Number(record.id)
|
||||
form.value = {
|
||||
title: data?.package?.title ?? '',
|
||||
category_names: data?.category_names ?? [],
|
||||
product_ids: (data?.product_ids ?? []).map(Number),
|
||||
customer_ids: (data?.customer_ids ?? []).map(Number),
|
||||
profit_buy: data?.package?.profit_buy ?? 0,
|
||||
profit_sell: data?.package?.profit_sell ?? 0,
|
||||
show_product: Boolean(data?.package?.show_product ?? true),
|
||||
is_active: Boolean(data?.package?.is_active ?? true)
|
||||
}
|
||||
editorVisible.value = true
|
||||
await Promise.resolve()
|
||||
editorRef.value?.seedCustomers(data?.customers ?? [])
|
||||
editorRef.value?.seedProducts(data?.products ?? [])
|
||||
} catch (e: any) {
|
||||
message.error(e?.response?.data?.message ?? 'خطا در دریافت پکیج')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function closeEditor() {
|
||||
editorVisible.value = false
|
||||
editId.value = null
|
||||
form.value = emptyForm()
|
||||
}
|
||||
|
||||
function showWarnings(warnings?: string[]) {
|
||||
if (!warnings?.length) return
|
||||
Modal.warning({
|
||||
title: 'هشدار تداخل پکیج',
|
||||
content: warnings.slice(0, 8).join('\n'),
|
||||
width: 520,
|
||||
okText: 'متوجه شدم'
|
||||
})
|
||||
}
|
||||
|
||||
async function submitForm() {
|
||||
try {
|
||||
await editorRef.value?.validate()
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const payload = {
|
||||
title: form.value.title,
|
||||
category_names: form.value.category_names,
|
||||
product_ids: form.value.product_ids.map(Number),
|
||||
customer_ids: form.value.customer_ids.map(Number),
|
||||
profit_buy: form.value.profit_buy,
|
||||
profit_sell: form.value.profit_sell,
|
||||
show_product: form.value.show_product,
|
||||
is_active: form.value.is_active
|
||||
}
|
||||
let res: any
|
||||
if (editId.value) {
|
||||
res = await updateCustomerSpecialPricePackage(editId.value, payload)
|
||||
message.success(res?.message ?? 'بهروز شد')
|
||||
} else {
|
||||
res = await createCustomerSpecialPricePackage(payload)
|
||||
message.success(res?.message ?? 'ثبت شد')
|
||||
}
|
||||
showWarnings(res?.warnings ?? res?.data?.warnings)
|
||||
closeEditor()
|
||||
tableRef.value?.fetch()
|
||||
} catch (e: any) {
|
||||
message.error(e?.response?.data?.message ?? 'خطا در ذخیره')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function onToggle(record: any) {
|
||||
try {
|
||||
const res: any = await toggleCustomerSpecialPricePackage(Number(record.id))
|
||||
message.success(res?.message ?? 'وضعیت تغییر کرد')
|
||||
tableRef.value?.fetch()
|
||||
} catch (e: any) {
|
||||
message.error(e?.response?.data?.message ?? 'خطا')
|
||||
}
|
||||
}
|
||||
|
||||
async function onDelete(record: any) {
|
||||
try {
|
||||
await deleteCustomerSpecialPricePackage(Number(record.id))
|
||||
message.success('حذف شد')
|
||||
tableRef.value?.fetch()
|
||||
} catch (e: any) {
|
||||
message.error(e?.response?.data?.message ?? 'خطا در حذف')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -70,6 +70,16 @@ const Customers: RouteRecordRaw = {
|
||||
title: 'احراز هویت',
|
||||
order: 3
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'customerMain_specialPrice',
|
||||
path: 'special-price',
|
||||
component: () => import('@/pages/menuPages/customers/specialPrice/index.vue'),
|
||||
meta: {
|
||||
icon: 'vuesax-linear:dollar-circle',
|
||||
title: 'قیمت ویژه',
|
||||
order: 4
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -58,6 +58,9 @@ export const priceTriggerEnabledSetting = ref(false)
|
||||
/** reactive — دکمه ارسال پیامک گروهی در صفحه پیامکها */
|
||||
export const groupSmsEnabledSetting = ref(false)
|
||||
|
||||
/** reactive — زیرمنوی قیمت ویژه مشتری */
|
||||
export const customerSpecialPricingEnabledSetting = ref(false)
|
||||
|
||||
/** reactive — منو و زنگوله اعلان داخل اپ */
|
||||
export const appNotificationEnabledSetting = ref(true)
|
||||
|
||||
@@ -156,6 +159,10 @@ export function setGroupSmsEnabledFromSettings(value: boolean): void {
|
||||
groupSmsEnabledSetting.value = value
|
||||
}
|
||||
|
||||
export function setCustomerSpecialPricingEnabledFromSettings(value: boolean): void {
|
||||
customerSpecialPricingEnabledSetting.value = value
|
||||
}
|
||||
|
||||
export function setConditionalOrderEnabledFromSettings(value: boolean): void {
|
||||
conditionalOrderEnabledSetting.value = value
|
||||
}
|
||||
@@ -262,6 +269,13 @@ export function applyGroupSmsFromRows(rows: Array<{ key?: string; value?: string
|
||||
if (row) setGroupSmsEnabledFromSettings(isSwitchOn(row.value))
|
||||
}
|
||||
|
||||
export function applyCustomerSpecialPricingFromRows(
|
||||
rows: Array<{ key?: string; value?: string | null }>
|
||||
) {
|
||||
const row = rows.find((item) => item.key === 'customer_special_pricing')
|
||||
if (row) setCustomerSpecialPricingEnabledFromSettings(isSwitchOn(row.value))
|
||||
}
|
||||
|
||||
export function applyConditionalOrderEnabledFromRows(
|
||||
rows: Array<{ key?: string; value?: string | null }>
|
||||
) {
|
||||
@@ -330,6 +344,7 @@ async function bootstrapSettingsFromApi(): Promise<void> {
|
||||
applyTrackingSystemsEnabledFromRows(list)
|
||||
applyCustomerDirectSmsFromRows(list)
|
||||
applyGroupSmsFromRows(list)
|
||||
applyCustomerSpecialPricingFromRows(list)
|
||||
applyConditionalOrderEnabledFromRows(list)
|
||||
applyProductFundsEnabledFromRows(list)
|
||||
applyCustomerLevelProfitEnabledFromRows(list)
|
||||
|
||||
Reference in New Issue
Block a user