added table

This commit is contained in:
2026-08-16 20:01:54 +03:30
parent 27f3a6212a
commit 609f6b2fa3
17 changed files with 1227 additions and 39 deletions
@@ -0,0 +1,28 @@
<template>
<div class="min-w-0 w-full max-w-full overflow-x-hidden">
<BasicShowContext title="تراکنش همکار">
<div class="min-w-0 w-full max-w-full overflow-x-auto">
<BaseTable v-bind="tblProps" @register="submit" />
</div>
</BasicShowContext>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { getColleagueOrderTable } from '@/api/order'
import { getColleagueOrderColumns } from '@/pages/menuPages/colleagueOrders/hooks/colleagueOrderColumns'
import useBaseTable from '@core/components/BaseTable/hooks/useBaseTable'
import type { TableInput } from '@core/type/baseTable'
const [submit, reload] = useBaseTable()
const tblProps = ref({
columns: getColleagueOrderColumns(),
api: (params: TableInput) => getColleagueOrderTable(params)
})
defineExpose({
fetch: reload
})
</script>
@@ -0,0 +1,134 @@
import type { BaseTableColumn } from '@core/type/baseTable'
import { toJalali } from '@core/utils/useDataTime'
import CustomTag from '@/components/CustomTag.vue'
import { usePrice } from '@core/utils/usePrice'
export function getColleagueOrderColumns(): BaseTableColumn[] {
return [
{
title: 'ردیف',
dataIndex: 'id',
key: 'row',
filterable: false,
sortable: false,
customRender: (record: any) => record.index + 1
},
{
title: 'کد سفارش',
dataIndex: 'id',
key: 'id',
type: 'Number'
},
{
title: 'کد همکار',
dataIndex: 'provider_id',
key: 'provider_id',
type: 'Number',
customRender: ({ record }) => record.provider_id ?? '-'
},
{
title: 'نوع',
dataIndex: 'direction',
key: 'direction',
filterable: false,
customRender: ({ record }) => {
if (record.direction === 2) {
return (
<div class="flex justify-center">
<CustomTag textColor="#C53030" bgColor="#FECACA">
فروش
</CustomTag>
</div>
)
}
return (
<div class="flex justify-center">
<CustomTag textColor="#25855A" bgColor="#E3F4E9">
خرید
</CustomTag>
</div>
)
}
},
{
title: 'مشتری',
dataIndex: 'customer.name',
key: 'customer.name',
type: 'Text'
},
{
title: 'مقدار مشتری (گرم)',
dataIndex: 'customer_amount',
key: 'customer_amount',
type: 'Number',
filterable: false,
customRender: ({ record }) => record.customer_amount ?? record.amount ?? '-'
},
{
title: 'مقدار نزد همکار (گرم)',
dataIndex: 'colleague_amount',
key: 'colleague_amount',
type: 'Number',
filterable: false,
customRender: ({ record }) => record.colleague_amount ?? record.amount ?? '-'
},
{
title: 'کارمزد',
dataIndex: 'service_charge',
key: 'service_charge',
type: 'Number',
customRender: ({ record }) => {
if (record.fee_asset === 'rial') {
return usePrice(record.service_charge_price ?? 0, true, '')
}
const gold = record.service_charge ?? 0
return `${gold} گرم`
}
},
{
title: 'قیمت مشتری (ریال)',
dataIndex: 'price',
key: 'price',
type: 'Text',
customRender: ({ record }) => usePrice(record.price ?? 0, true, '')
},
{
title: 'مبلغ مشتری (ریال)',
dataIndex: 'total_price',
key: 'total_price',
type: 'Text',
customRender: ({ record }) => usePrice(record.total_price ?? 0, true, '')
},
{
title: 'نرخ همکار (ریال/گرم)',
dataIndex: 'colleague_unit_price',
key: 'colleague_unit_price',
type: 'Text',
filterable: false,
customRender: ({ record }) =>
usePrice(record.colleague_unit_price ?? record.total_price_montzar ?? 0, true, '')
},
{
title: 'مبلغ نزد همکار (ریال)',
dataIndex: 'colleague_total_price',
key: 'colleague_total_price',
type: 'Text',
filterable: false,
customRender: ({ record }) => usePrice(record.colleague_total_price ?? 0, true, '')
},
{
title: 'سود کل (ریال)',
dataIndex: 'total_fee',
key: 'total_fee',
type: 'Text',
customRender: ({ record }) => usePrice(record.total_fee ?? 0, true, '')
},
{
title: 'تاریخ',
dataIndex: 'created_at',
key: 'created_at',
type: 'DateTime',
customRender: (record: any) => toJalali(record.record.created_at)
}
]
}
@@ -0,0 +1,16 @@
<template>
<div>
<BasicMainCard>
<div class="flex h-full w-full min-w-0 max-w-full flex-col overflow-hidden">
<tbl ref="table" class="min-h-0 flex-1" />
</div>
</BasicMainCard>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import tbl from './components/table.vue'
const table = ref<InstanceType<typeof tbl> | null>(null)
</script>
+8 -2
View File
@@ -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, setBannerEnabledFromSettings, setConditionalOrderEnabledFromSettings, setProductFundsEnabledFromSettings, setPriceTriggerEnabledFromSettings, setAppNotificationEnabledFromSettings, setPriceProviderFromSettings, setPriceChannelAccessFromSettings, setStoreModeFromSettings, setTrackingSystemsEnabledFromSettings } from '@/utils/config'
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 {
EDITABLE_SETTING_GROUPS,
SYSTEM_SETTING_GROUPS,
@@ -484,11 +484,13 @@ async function initConfig() {
applyPriceProviderFromRows(baseData.value)
applyPriceChannelAccessFromRows(baseData.value)
applySupportChatMenuEnabledFromRows(baseData.value)
applyGroupSmsFromRows(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 === 'price_channel_access') ||
!baseData.value.some((row) => row.key === 'group_sms')
) {
try {
const { getConfiguration } = await import('@/api/configuration')
@@ -497,6 +499,7 @@ async function initConfig() {
applyPriceProviderFromRows(rows)
applySupportChatMenuEnabledFromRows(rows)
applyPriceChannelAccessFromRows(rows)
applyGroupSmsFromRows(rows)
} catch {
/* کش قبلی می‌ماند */
}
@@ -566,6 +569,9 @@ function syncMenuFlagsFromSetting(key: string | undefined, value: string) {
if (key === 'price_channel_access') {
setPriceChannelAccessFromSettings(isSwitchOn(value))
}
if (key === 'group_sms') {
setGroupSmsEnabledFromSettings(isSwitchOn(value))
}
}
function onValueChange(row: Const, value: string) {
@@ -155,7 +155,6 @@ export const EDITABLE_SETTING_GROUPS: SettingGroupDef[] = [
icon: 'vuesax-linear:notification',
match: (k) =>
k === 'charge_notif' ||
k === 'group_sms' ||
k === 'register_referral_sms_to_referrer' ||
/^sms_notif_/.test(k)
},
@@ -180,10 +179,16 @@ 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|tracking_systems_enabled|permision__enabled|show_to_users_chat)$/.test(
k
)
},
{
id: 'limits',
title: 'محدودیت‌ها',
icon: 'vuesax-linear:lock',
match: (k) => k === 'group_sms'
},
{
id: 'support_chat',
title: 'چت پشتیبانی',
@@ -824,7 +824,7 @@ const KEY_HELP_FALLBACK: Record<string, string> = {
sms_notif_support_admin:
'روشن: وقتی مشتری اولین پیام خوانده‌نشده را در چت پشتیبانی می‌فرستد و ادمین همان گفتگو را باز و visible ندارد، پیامک به ادمین‌های دارای «ارسال پیامک» می‌رود. خاموش: اعلان پیامک چت نمی‌رود.',
group_sms:
'روشن: امکان ارسال پیامک گروهی از پنل ادمین فعال است. خاموش: ارسال پیامک گروهی غیرفعال است.',
'روشن: دکمه ارسال پیامک گروهی در پنل ادمین فعال است (ارسال ۱۰تایی). خاموش: ارسال پیامک گروهی غیرفعال است. فقط از تنظیمات پیشرفته.',
profit_gold_api_enabled:
'روشن: محاسبه سود طلا از API خارجی فعال است. خاموش: محاسبه سود از API خارجی انجام نمی‌شود (حالت پیش‌فرض اکثر نصب‌ها).',
main_wallet_products:
+7 -1
View File
@@ -24,7 +24,12 @@ import BasicMenu from '@/components/BasicMenu/index.vue'
import { useRouterStore } from '@core/stores/router'
import { get } from 'lodash'
import type { RouteRecordRaw } from 'vue-router'
import { conditionalOrderEnabledSetting, initWalletSettingsFromApi, priceTriggerEnabledSetting } from '@/utils/config'
import {
conditionalOrderEnabledSetting,
initWalletSettingsFromApi,
priceProviderSetting,
priceTriggerEnabledSetting
} from '@/utils/config'
const routerStore = useRouterStore()
const route = useRoute()
@@ -40,6 +45,7 @@ const menu = computed(() => {
.filter((child) => !child.meta?.hideMenu)
.filter((child) => child.name !== 'conditionalOrders' || conditionalOrderEnabledSetting.value)
.filter((child) => child.name !== 'priceTriggers' || priceTriggerEnabledSetting.value)
.filter((child) => child.name !== 'colleagueOrdersMain' || priceProviderSetting.value === 'montzar')
.sort((a, b) => (Number(a.meta?.order) || 99) - (Number(b.meta?.order) || 99))
})
</script>
@@ -42,13 +42,13 @@
rowKey="mobile"
:row-selection="{ selectedRowKeys: form.numbers, onChange: onSelectChange }"
:columns="columns"
:scroll="{ y: '20vh', x: 500 }"
:scroll="{ y: 280, x: 500 }"
:pagination="false"
:data-source="groupNumbers"
/>
<div
class="md:hidden max-h-48 overflow-y-auto rounded-xl border border-gray-200 bg-gray-50 p-2 space-y-1"
class="md:hidden max-h-64 overflow-y-auto rounded-xl border border-gray-200 bg-gray-50 p-2 space-y-1"
>
<div v-if="customersLoading" class="flex justify-center py-6">
<a-spin size="small" />
+18 -12
View File
@@ -1,21 +1,21 @@
<template>
<div class="h-full min-h-0 min-w-0 max-w-full overflow-hidden">
<BasicShowContext title="لیست پیامک ها">
<!-- <template v-if="!mobile && isMobileScreen" #extra>
<template v-if="groupSmsEnabled && !mobile && isMobileScreen" #extra>
<BaseButton type="primary" size="small" icon="vuesax-linear:sms" @click="emits('add')">
ارسال پیامک
</BaseButton>
</template>-->
</template>
<div class="flex h-full min-h-0 flex-col">
<BaseTable v-if="!isMobileScreen" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl">
<!-- <template #extraHeader>
<BaseButton v-if="!mobile" type="primary" size="small" icon="vuesax-linear:sms" @click="emits('add')">
ارسال پیامک
</BaseButton>
</template>-->
<template #action="{ column, record }">
<!-- <div class="gap-2 flex justify-center">
<template v-if="groupSmsEnabled" #extraHeader>
<BaseButton v-if="!mobile" type="primary" size="small" icon="vuesax-linear:sms" @click="emits('add')">
ارسال پیامک
</BaseButton>
</template>
<template #action="{ column, record }">
<!-- <div class="gap-2 flex justify-center">
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('info', record)">
<template #title>
<span>مشخصات</span>
@@ -23,10 +23,10 @@
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
</a-tooltip>
</div>-->
</template>
</BaseTable>
</template>
</BaseTable>
<SmsMobileCardList v-else ref="mobileListRef" class="min-h-0 flex-1" :mobile="mobile" />
<SmsMobileCardList v-else ref="mobileListRef" class="min-h-0 flex-1" :mobile="mobile" />
</div>
</BasicShowContext>
</div>
@@ -39,6 +39,7 @@ import { cols } from '../hooks/smsColumns'
import { getSMSTable } from '@/api/sms'
import SmsMobileCardList from './SmsMobileCardList.vue'
import { isMobile as isMobileScreen } from '@core/utils/is'
import { groupSmsEnabledSetting, initWalletSettingsFromApi } from '@/utils/config'
interface Prop {
mobile?: number | string
@@ -49,6 +50,11 @@ const emits = defineEmits(['delete', 'edit', 'add', 'info'])
const [submit, reload] = useBaseTable()
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
const groupSmsEnabled = groupSmsEnabledSetting
onMounted(() => {
void initWalletSettingsFromApi()
})
const tblProps = ref<any>({
columns: cols,
+66 -18
View File
@@ -15,29 +15,39 @@
:showButton="true"
:showCancel="true"
:showOk="true"
ok-text="ارسال"
ok-text="شروع ارسال"
cancel-text="انصراف"
@ok="ok"
@close="closeSmsSheet"
@cancel="closeSmsSheet"
>
<div v-if="isMobile" class="mb-3 text-base font-700">ارسال پیامک</div>
<SmsEditorFormBody ref="smsEditorRef" :form="form" :group-items="groupItems" :loading="loading" />
<a-alert
class="mb-3"
type="warning"
show-icon
message="ارسال ۱۰تایی انجام می‌شود. تا پایان کار پنل را نبندید و صفحه را رفرش نکنید."
/>
<div class="sms-editor-scroll">
<SmsEditorFormBody ref="smsEditorRef" :form="form" :group-items="groupItems" :loading="loading" />
</div>
</BasicActionSheet>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ref, watch } from 'vue'
import tbl from './components/table.vue'
import type { FormInstance } from 'ant-design-vue'
import BasicActionSheet from '@/components/BasicActionSheet.vue'
import SmsEditorFormBody from './components/SmsEditorFormBody.vue'
import { message } from 'ant-design-vue/es'
import type { FunctionItem } from '@/model/Item'
import { createSMS, getGroupsList } from '@/api/sms'
import { getGroupsList } from '@/api/sms'
import type { Sms } from '@/model/sms'
import { isMobile } from '@core/utils/is'
import { groupSmsEnabledSetting, initWalletSettingsFromApi } from '@/utils/config'
import { useGroupSmsSendStore } from '@/store/groupSmsSendStore'
const loading = ref<boolean>(false)
const groupItems = ref<FunctionItem[]>()
@@ -46,6 +56,7 @@ const smsEditorRef = ref<{
validateFields?: () => ReturnType<NonNullable<FormInstance['validateFields']>>
resetAll?: () => void
}>()
const sendStore = useGroupSmsSendStore()
const form = ref<Sms>({
message: '',
@@ -55,7 +66,17 @@ const form = ref<Sms>({
const addSMSVisible = ref<boolean>(false)
onMounted(() => getGroups())
onMounted(() => {
void initWalletSettingsFromApi()
getGroups()
})
watch(
() => sendStore.finished,
(finished) => {
if (finished) refreshTable()
}
)
async function getGroups() {
try {
@@ -73,10 +94,20 @@ function refreshTable() {
}
function closeSmsSheet() {
if (loading.value) return
addSMSVisible.value = false
}
async function add() {
if (!groupSmsEnabledSetting.value) {
message.warning('ارسال پیامک گروهی غیرفعال است')
return
}
if (sendStore.active) {
message.info('یک ارسال گروهی در حال انجام است')
sendStore.expand()
return
}
addSMSVisible.value = true
resetData()
}
@@ -90,22 +121,39 @@ function resetData() {
smsEditorRef.value?.resetAll?.()
}
function uniqueNumbers(list: string[]): string[] {
return Array.from(new Set(list.map((n) => String(n).trim()).filter(Boolean)))
}
function ok() {
if (sendStore.active) {
message.warning('ارسال قبلی هنوز تمام نشده است')
return
}
smsEditorRef.value?.validateFields?.().then(async () => {
try {
loading.value = true
if (!form.value.numbers) form.value.numbers = []
form.value.numbers.push(...(form.value.single ?? []))
await createSMS(form.value)
resetData()
addSMSVisible.value = false
refreshTable()
message.success('با موفقیت ارسال شد')
} catch (e) {
console.error(e)
} finally {
loading.value = false
const numbers = uniqueNumbers([...(form.value.numbers ?? []), ...(form.value.single ?? [])])
if (!numbers.length) {
message.warning('حداقل یک شماره انتخاب کنید')
return
}
if (!form.value.message?.trim()) {
message.warning('متن پیامک خالی است')
return
}
const text = form.value.message
addSMSVisible.value = false
resetData()
await sendStore.start(numbers, text)
})
}
</script>
<style scoped>
.sms-editor-scroll {
max-height: min(62vh, 560px);
overflow-y: auto;
padding-inline-end: 4px;
}
</style>