change responsive]

This commit is contained in:
2026-05-31 11:51:52 +03:30
parent 06568c5858
commit 023b8ee5be
124 changed files with 6296 additions and 4233 deletions
@@ -0,0 +1,51 @@
<template>
<MobileCardList
v-model:search="search"
:loading="loading"
:has-more="hasMore"
:show-empty="!loading && items.length === 0"
empty-text="رکوردی یافت نشد"
@search="reload"
@load-more="loadMore"
>
<AccountingMobileListItem
v-for="item in items"
:key="item.id"
:item="item"
@open="openSheet"
/>
</MobileCardList>
<AccountingMobileDetailSheet
v-model:visible="sheetVisible"
:item="selectedItem"
@info="$emit('info', $event)"
@orders="$emit('orders', $event)"
/>
</template>
<script setup lang="ts">
import { getTotalAccountingTable } from '@/api/accounting'
import type { TotalAccounting } from '@/model/accounting'
import MobileCardList from '@/components/mobile/MobileCardList.vue'
import { useMobileDetailSheet } from '@/components/mobile/composables/useMobileDetailSheet'
import { useMobileTableList } from '@/components/mobile/composables/useMobileTableList'
import AccountingMobileDetailSheet from './AccountingMobileDetailSheet.vue'
import AccountingMobileListItem from './AccountingMobileListItem.vue'
defineEmits<{
(e: 'info', item: TotalAccounting): void
(e: 'orders', item: TotalAccounting): void
}>()
const { sheetVisible, selectedItem, openSheet, syncSelectedItem } = useMobileDetailSheet<TotalAccounting>()
const { loading, search, items, hasMore, reload, loadMore, fetch } = useMobileTableList<TotalAccounting>({
fetchApi: getTotalAccountingTable,
onFetched: (rows) => syncSelectedItem(rows)
})
onMounted(() => reload())
defineExpose({ fetch, openSheet })
</script>
@@ -0,0 +1,65 @@
<template>
<MobileDetailSheet v-model:visible="visibleProxy" :item="item" :title="sheetTitle">
<template #default="{ item: row }">
<MobileSheetHeader :title="sheetTitle" :show-title="isMobile">
<template #actions>
<MobileIconButton plain icon="vuesax-linear:stickynote" color="#10913A" :size="22" @click="$emit('info', row)" />
<MobileIconButton plain icon="vuesax-linear:receipt-item" color="#10913A" :size="22" @click="$emit('orders', row)" />
</template>
</MobileSheetHeader>
<div class="space-y-2">
<MobileStatCard bordered variant="success" label="موجودی ریالی" :value="usePrice(row.balance_cash ?? 0, true, '')">
<template #prefix>
<BaseIcon icon="vuesax-linear:empty-wallet" size="18" color="#15803d" />
</template>
</MobileStatCard>
<MobileStatCard bordered variant="success" label="موجودی طلایی" :value="`${row.balance_gold ?? 0} گرم`">
<template #prefix>
<BaseIcon icon="vuesax-linear:coin" size="18" color="#15803d" />
</template>
</MobileStatCard>
</div>
<MobileInfoGrid>
<MobileDetailRow label="نام مشتری" :value="row.customer_to?.name || '-'" />
<MobileDetailRow label="نام مشتری دعوت شده" :value="row.customer?.name || '-'" />
<MobileDetailRow label="زمان ثبت" :value="row.created_at ? toJalali(row.created_at) : '-'" />
<MobileDetailRow label="نقد (ریال)" :value="usePrice(row.cash ?? 0, true, '')" />
<MobileDetailRow label="طلا (گرم)" :value="`${row.gold ?? 0}`" />
<MobileDetailRow label="توضیحات" full-width :value="row.note || '-'" />
</MobileInfoGrid>
</template>
</MobileDetailSheet>
</template>
<script setup lang="ts">
import type { TotalAccounting } from '@/model/accounting'
import MobileDetailRow from '@/components/mobile/MobileDetailRow.vue'
import MobileStatCard from '@/components/mobile/MobileStatCard.vue'
import MobileDetailSheet from '@/components/mobile/MobileDetailSheet.vue'
import MobileIconButton from '@/components/mobile/MobileIconButton.vue'
import MobileInfoGrid from '@/components/mobile/MobileInfoGrid.vue'
import MobileSheetHeader from '@/components/mobile/MobileSheetHeader.vue'
import { isMobile } from '@core/utils/is'
import { toJalali } from '@core/utils/useDataTime'
import { usePrice } from '@core/utils/usePrice'
const props = defineProps<{
visible: boolean
item: TotalAccounting | null
}>()
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
(e: 'info', item: TotalAccounting): void
(e: 'orders', item: TotalAccounting): void
}>()
const visibleProxy = computed({
get: () => props.visible,
set: (value: boolean) => emit('update:visible', value)
})
const sheetTitle = computed(() => props.item?.customer_to?.name || props.item?.customer?.name || 'حسابداری')
</script>
@@ -0,0 +1,37 @@
<template>
<MobileCard clickable @click="$emit('open', item)">
<div class="space-y-1">
<div>
<div class="flex justify-between items-center gap-x-3 gap-y-1 text-xs">
<div class="truncate text-sm font-700 text-gray-900">{{ customerToName }}</div>
<div class="text-xs text-gray-400">{{ createdAtLabel }}</div>
</div>
<span class="text-xs text-gray-600">نقد: <span class="font-600 text-gray-900">{{ cashLabel }}</span></span>
<span class="text-xs text-gray-600">طلا: <span class="font-600 text-gray-900">{{ goldLabel }}</span></span>
</div>
</div>
</MobileCard>
</template>
<script setup lang="ts">
import type { TotalAccounting } from '@/model/accounting'
import MobileCard from '@/components/mobile/MobileCard.vue'
import { toJalali } from '@core/utils/useDataTime'
import { usePrice } from '@core/utils/usePrice'
const props = defineProps<{
item: TotalAccounting
}>()
defineEmits<{
(e: 'open', item: TotalAccounting): void
}>()
const customerToName = computed(() => props.item.customer_to?.name || '-')
const createdAtLabel = computed(() => (props.item.created_at ? toJalali(props.item.created_at) : '-'))
const cashLabel = computed(() => usePrice(props.item.cash ?? 0, true, ''))
const goldLabel = computed(() => `${props.item.gold ?? 0} گرم`)
</script>
@@ -1,141 +1,67 @@
<template>
<div>
<div class="min-w-0 w-full max-w-full overflow-x-hidden">
<BasicShowContext title="حسابداری">
<BaseTable v-bind="tblProps" @register="submit" ref="tbl">
<template #extraHeader>
<!-- <BaseButton v-if="!customerId" type="primary" size="small" class="flex" @click="emits('add')">-->
<!-- <div>افزودن سفارش</div>-->
<!-- <BaseIcon icon="vuesax-linear:receipt-add" Size="16px" />-->
<!-- </BaseButton>-->
</template>
<template #action="{ column, record }">
<div class="gap-2 flex justify-center">
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('', record)">
<template #title>
<span>برداشت و واریز</span>
</template>
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
</a-tooltip>
<a-tooltip placement="bottom" class="cursor-pointer" @click="handleOrderModal(record)">
<template #title>
<span>سفارشات</span>
</template>
<Icon icon="vuesax-linear:receipt-item" color="#10913A" />
</a-tooltip>
<div v-if="!isMobile" class="min-w-0 w-full max-w-full overflow-x-auto">
<BaseTable v-bind="tblProps" @register="submit" ref="tbl">
<template #action="{ record }">
<div class="flex justify-center gap-2">
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('info', record)">
<template #title><span>برداشت و واریز</span></template>
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
</a-tooltip>
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('orders', record)">
<template #title><span>سفارشات</span></template>
<Icon icon="vuesax-linear:receipt-item" color="#10913A" />
</a-tooltip>
</div>
</template>
</BaseTable>
</div>
</div>
</template>
</BaseTable>
<AccountingMobileCardList
v-else
ref="mobileListRef"
@info="emits('info', $event)"
@orders="emits('orders', $event)"
/>
</BasicShowContext>
</div>
</template>
<script lang="tsx" setup>
import type { TableInput } from '@core/type/baseTable'
import useBaseTable from '@core/components/BaseTableOld/hooks/useBaseTable'
import Icon from '@core/components/BaseIcon/src/Icon.vue'
import { changeCustomerActivation, getCustomerTable } from '@/api/customer'
import { cols } from '../hooks/accountingColumns'
import { useUserStore } from '@/store/userStore'
import { usePrefix } from '@core/composable/usePrefix'
import { message } from 'ant-design-vue'
<script setup lang="ts">
import { ref } from 'vue'
import { getTotalAccountingTable } from '@/api/accounting'
import {ref} from "vue";
import {cloneDeep} from "lodash";
import { cols } from '@/pages/menuPages/accounting/hooks/accountingColumns'
import AccountingMobileCardList from '@/pages/menuPages/accounting/components/AccountingMobileCardList.vue'
import Icon from '@core/components/BaseIcon/src/Icon.vue'
import useBaseTable from '@core/components/BaseTable/hooks/useBaseTable'
import type { TableInput } from '@core/type/baseTable'
import { isMobile } from '@core/utils/is'
const showOrderModal = ref<boolean>(false)
function handleOrderModal(record) {
showOrderModal.value = true
}
defineProps<{
customerId?: number
}>()
const emits = defineEmits<{
(e: 'info', record: unknown): void
(e: 'orders', record: unknown): void
}>()
const [submit, reload] = useBaseTable()
/*const select = (val) => {
val.sts === 1 ? return ['1'] : ['2']
}*/
//emits
interface Prop {
customerId: number
}
const emits = defineEmits(['delete', 'edit', 'add', 'info'])
const props = defineProps<Prop>()
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
const { prefixCls } = usePrefix('pophoverstatus')
//permission
function checkPermission(temp: string) {
const store = useUserStore()
return store.canAccess(temp)
}
//define table
const table = ref(null)
const tblProps = ref<any>({
const tblProps = ref({
columns: cols,
api: (params: TableInput) => {
if (props.customerId) {
// params.filters = []
// params.filters?.push({
// field: "customer_id",
// type: "text",
// value: props.customerId,
// cr: "and",
// op: "eq",
// hidden: false
// })
}
return getTotalAccountingTable(params)
}
//bordered: true
api: (params: TableInput) => getTotalAccountingTable(params)
})
const loading = ref<boolean>(false)
async function changeCustomerActivationFunction(id: number) {
try {
loading.value = true
await changeCustomerActivation(id)
reload()
message.success('تغییر وضعیت با موفقیت انجام شد')
} catch (e) {
console.error(e)
} finally {
loading.value = false
async function fetch() {
if (isMobile.value) {
await mobileListRef.value?.fetch?.()
return
}
await reload()
}
const fetch = () => {
// console.log('sss')
reload()
}
// const spinTable = (val: boolean) => {
// // console.log('sss')
// table?.value?.loading.value = val
// }
defineExpose({
fetch
// spinTable
})
defineExpose({ fetch })
</script>
<style lang="less">
@prefix: ~'@{prefixCls}-pophoverstatus';
.@{prefix} {
.ant-menu-rtl.ant-menu-inline,
.ant-menu-rtl.ant-menu-vertical {
border-left: none;
}
.ant-menu-item-only-child {
border-radius: @border-radius-base;
}
//bor
/* .ant-input-group-addon:first-child {
!* border: #d9d9d9 1px solid;
border-radius: 0 6px 6px 0;*!
}*/
}
</style>
@@ -79,7 +79,6 @@ export const cols: BaseTableColumn[] = [
dataIndex: 'action',
key: 'action',
slot: 'action',
fixed: 'right',
filterable: false
}
]
+11 -9
View File
@@ -1,15 +1,8 @@
<template>
<div>
<BasicMainCard>
<div class="flex w-full h-full">
<tbl
ref="table"
class="w-full h-full"
@info="info"
@edit="edit"
@delete="deleteItem"
@add="add"
/>
<div class="flex h-full w-full min-w-0 max-w-full overflow-x-hidden">
<tbl ref="table" class="h-full w-full min-w-0" @info="onAccountingWithdraw" @orders="onAccountingOrders" />
<!-- <BasicMenu :menu="menu" width="13rem" @onChange="tabSelected" />
<component :is="selectedComponent" class="mr-5" />-->
@@ -143,6 +136,7 @@ import { cloneDeep } from 'lodash'
import type { Item } from '@/model/Item'
import type { Customer } from '@/model/customer'
import { createCustomer, deleteCustomer, getCustomersList, updateCustomer } from '@/api/customer'
import type { TotalAccounting } from '@/model/accounting'
import type { Order } from '@/model/order'
import { usePrice } from '@core/utils/usePrice'
import { getLastPrice } from '@/api/price'
@@ -234,6 +228,14 @@ function stopGetPrice() {
//info
let infoParams = reactive<Info>({})
function onAccountingWithdraw(_record: TotalAccounting) {
message.info('برداشت و واریز به‌زودی در دسترس است')
}
function onAccountingOrders(_record: TotalAccounting) {
message.info('لیست سفارشات به‌زودی در دسترس است')
}
function info(val: Order) {
infoParams.name = val.customer.name
infoParams.type = val.customer.mobile
@@ -0,0 +1,66 @@
<template>
<MobileCardList
v-model:search="search"
:loading="loading"
:has-more="hasMore"
:show-empty="!loading && items.length === 0"
empty-text="حسابی یافت نشد"
search-placeholder="جستجو حساب"
show-refresh
@search="reload"
@refresh="reload"
@load-more="loadMore"
>
<AccountMobileListItem
v-for="item in items"
:key="item.id"
:item="item"
@open="openSheet"
@toggle-sts="confirmToggleSts"
/>
</MobileCardList>
<AccountMobileDetailSheet v-model:visible="sheetVisible" :item="selectedItem" />
</template>
<script setup lang="ts">
import { Modal } from 'ant-design-vue'
import { getAccountTable } from '@/api/card'
import type { Card } from '@/model/card'
import MobileCardList from '@/components/mobile/MobileCardList.vue'
import { useMobileDetailSheet } from '@/components/mobile/composables/useMobileDetailSheet'
import { useMobileTableList } from '@/components/mobile/composables/useMobileTableList'
import AccountMobileDetailSheet from './AccountMobileDetailSheet.vue'
import AccountMobileListItem from './AccountMobileListItem.vue'
type AccountRow = Card & { sts_str?: string; created_at?: string; customer_id?: number }
const emit = defineEmits<{
(e: 'toggleSts', item: AccountRow): void
}>()
const { sheetVisible, selectedItem, openSheet, syncSelectedItem } = useMobileDetailSheet<AccountRow>()
function confirmToggleSts(item: AccountRow) {
const isApproved = item.sts === 2
Modal.confirm({
title: isApproved ? 'غیر فعال سازی حساب' : 'فعال سازی حساب',
content: isApproved
? 'آیا از غیرفعال‌سازی این حساب اطمینان دارید؟'
: 'آیا از فعال‌سازی این حساب اطمینان دارید؟',
okText: 'تایید',
cancelText: 'انصراف',
centered: true,
onOk: () => emit('toggleSts', item)
})
}
const { loading, search, items, hasMore, reload, loadMore, fetch } = useMobileTableList<AccountRow>({
fetchApi: getAccountTable,
onFetched: (rows) => syncSelectedItem(rows)
})
onMounted(() => reload())
defineExpose({ fetch, openSheet })
</script>
@@ -0,0 +1,73 @@
<template>
<MobileDetailSheet v-model:visible="visibleProxy" :item="item" :title="sheetTitle">
<template #default="{ item: row }">
<MobileSheetHeader :title="sheetTitle" :subtitle="dateLabel(row)" :show-title="isMobile" />
<span class="inline-flex rounded-lg border px-2 py-0.5 text-xs font-600" :class="statusClass(row)">
{{ row.sts_str || '-' }}
</span>
<div class="space-y-2">
<MobileStatCard bordered label="نام بانک" :value="row.bank_name || '-'">
<template #prefix>
<BaseIcon icon="vuesax-linear:bank" size="18" color="#2563eb" />
</template>
</MobileStatCard>
</div>
<MobileInfoGrid>
<MobileDetailRow label="کد حساب" :value="row.id ?? '-'" />
<MobileDetailRow label="کد کاربر" :value="row.customer_id ?? '-'" />
<MobileDetailRow label="نام مشتری" full-width :value="row.customer?.name || '-'" />
<MobileDetailRow label="شماره حساب" full-width :value="row.account_number || '-'" />
<MobileDetailRow label="شماره کارت" full-width :value="row.cart_number || '-'" />
<MobileDetailRow label="شماره شبا" full-width :value="row.shaba_number || '-'" />
<MobileDetailRow label="تاریخ ثبت" :value="dateLabel(row)" />
</MobileInfoGrid>
</template>
</MobileDetailSheet>
</template>
<script setup lang="ts">
import type { Card } from '@/model/card'
import MobileDetailRow from '@/components/mobile/MobileDetailRow.vue'
import MobileDetailSheet from '@/components/mobile/MobileDetailSheet.vue'
import MobileInfoGrid from '@/components/mobile/MobileInfoGrid.vue'
import MobileSheetHeader from '@/components/mobile/MobileSheetHeader.vue'
import MobileStatCard from '@/components/mobile/MobileStatCard.vue'
import { isMobile } from '@core/utils/is'
import { toJalaliNoTime } from '@core/utils/useDataTime'
type AccountRow = Card & { sts_str?: string; created_at?: string; customer_id?: number }
const props = defineProps<{
visible: boolean
item: AccountRow | null
}>()
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
}>()
const visibleProxy = computed({
get: () => props.visible,
set: (value: boolean) => emit('update:visible', value)
})
const sheetTitle = computed(() => props.item?.customer?.name || `حساب #${props.item?.id ?? '-'}`)
function statusClass(row: AccountRow) {
switch (row.sts) {
case 1:
return 'border-amber-300 bg-amber-50 text-amber-700'
case 2:
return 'border-green-300 bg-green-50 text-green-700'
default:
return 'border-red-300 bg-red-50 text-red-600'
}
}
function dateLabel(row: AccountRow) {
return row.created_at ? toJalaliNoTime(row.created_at) : '-'
}
</script>
@@ -0,0 +1,56 @@
<template>
<MobileCard clickable @click="$emit('open', item)">
<div class="flex items-center gap-2">
<div class="min-w-0 flex-1 space-y-1">
<div class="flex items-center justify-between gap-2">
<span class="truncate text-sm font-700 text-gray-900">{{ customerName }}</span>
<span class="shrink-0 rounded-md border px-1.5 py-0.5 text-xs font-600" :class="statusClass">
{{ statusLabel }}
</span>
</div>
<div class="flex items-center justify-between gap-2 text-xs text-gray-600">
<span class="truncate">{{ item.bank_name || '-' }}</span>
<span class="shrink-0 text-gray-400">#{{ item.id ?? '-' }}</span>
</div>
</div>
<MobileIconButton
:icon="item.sts === 2 ? 'vuesax-linear:card-tick' : 'vuesax-linear:card-remove'"
:color="item.sts === 2 ? '#16A34A' : '#DC2626'"
:size="20"
@click="$emit('toggleSts', item)"
/>
</div>
</MobileCard>
</template>
<script setup lang="ts">
import type { Card } from '@/model/card'
import MobileCard from '@/components/mobile/MobileCard.vue'
import MobileIconButton from '@/components/mobile/MobileIconButton.vue'
type AccountRow = Card & { sts_str?: string; created_at?: string; customer_id?: number }
const props = defineProps<{
item: AccountRow
}>()
defineEmits<{
(e: 'open', item: AccountRow): void
(e: 'toggleSts', item: AccountRow): void
}>()
const customerName = computed(() => props.item.customer?.name || '-')
const statusLabel = computed(() => props.item.sts_str || '-')
const statusClass = computed(() => {
switch (props.item.sts) {
case 1:
return 'border-amber-300 bg-amber-50 text-amber-700'
case 2:
return 'border-green-300 bg-green-50 text-green-700'
default:
return 'border-red-300 bg-red-50 text-red-600'
}
})
</script>
+18 -6
View File
@@ -1,7 +1,8 @@
<template>
<div>
<div class="h-full min-h-0 min-w-0 max-w-full overflow-hidden">
<BasicShowContext title="لیست حساب ها">
<BaseTable v-bind="tblProps" @register="submit" ref="tbl">
<div class="flex h-full min-h-0 flex-col">
<BaseTable v-if="!isMobile" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl">
<template #extraHeader>
<!-- <BaseButton type="primary" size="small" class="flex" @click="emits('add')">
<div>افزودن مشتری عمده</div>
@@ -27,6 +28,14 @@
</div>
</template>
</BaseTable>
<AccountMobileCardList
v-else
ref="mobileListRef"
class="min-h-0 flex-1"
@toggle-sts="changeAccountStsFunction"
/>
</div>
</BasicShowContext>
</div>
</template>
@@ -44,7 +53,11 @@ import { getOrderTable } from '@/api/order'
import { getWithdrawTable } from '@/api/wallet'
import { changeAccountSts, getAccountTable } from '@/api/card'
import type { Card } from '@/model/card'
import AccountMobileCardList from './AccountMobileCardList.vue'
import { isMobile } from '@core/utils/is'
const [submit, reload] = useBaseTable()
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
/*const select = (val) => {
val.sts === 1 ? return ['1'] : ['2']
}*/
@@ -64,7 +77,7 @@ async function changeAccountStsFunction(val: Card) {
loading.value = true
await changeAccountSts({ id: val.id, sts: val.sts !== 2 ? 2 : 3 })
message.success(val.sts !== 2 ? 'با موفقیت تایید شد' : 'با موفقیت رد شد')
reload()
await Promise.allSettled([reload(), mobileListRef.value?.fetch?.() as Promise<void>])
} catch (e) {
console.log(e)
} finally {
@@ -83,9 +96,8 @@ const tblProps = ref<any>({
})
const loading = ref<boolean>(false)
const fetch = () => {
// console.log('sss')
reload()
async function fetch() {
await Promise.allSettled([reload(), mobileListRef.value?.fetch?.() as Promise<void>])
}
// const spinTable = (val: boolean) => {
// // console.log('sss')
@@ -0,0 +1,48 @@
<template>
<MobileCardList
v-model:search="search"
:loading="loading"
:has-more="hasMore"
:show-empty="!loading && items.length === 0"
empty-text="مشتری بایگانی‌شده‌ای یافت نشد"
@search="reload"
@load-more="loadMore"
>
<CustomerArchiveMobileListItem
v-for="item in items"
:key="item.id"
:item="item"
@open="openSheet"
@info="$emit('info', $event)"
@unarchive="$emit('unarchive', $event)"
/>
</MobileCardList>
<CustomerArchiveMobileDetailSheet v-model:visible="sheetVisible" :item="selectedItem" />
</template>
<script setup lang="ts">
import { getCustomerArchiveTable } from '@/api/customer'
import type { Customer } from '@/model/customer'
import MobileCardList from '@/components/mobile/MobileCardList.vue'
import { useMobileDetailSheet } from '@/components/mobile/composables/useMobileDetailSheet'
import { useMobileTableList } from '@/components/mobile/composables/useMobileTableList'
import CustomerArchiveMobileDetailSheet from './CustomerArchiveMobileDetailSheet.vue'
import CustomerArchiveMobileListItem from './CustomerArchiveMobileListItem.vue'
defineEmits<{
(e: 'info', item: Customer): void
(e: 'unarchive', item: Customer): void
}>()
const { sheetVisible, selectedItem, openSheet, syncSelectedItem } = useMobileDetailSheet<Customer>()
const { loading, search, items, hasMore, reload, loadMore, fetch } = useMobileTableList<Customer>({
fetchApi: getCustomerArchiveTable,
onFetched: (rows) => syncSelectedItem(rows)
})
onMounted(() => reload())
defineExpose({ fetch })
</script>
@@ -0,0 +1,45 @@
<template>
<MobileDetailSheet v-model:visible="visibleProxy" :item="item" :title="item?.name || ''">
<template #default="{ item: row }">
<MobileSheetHeader :title="row.name || '-'" :show-title="isMobile" />
<MobileInfoGrid>
<MobileDetailRow label="کد کاربری" :value="row.id ?? '-'" />
<MobileDetailRow label="شماره موبایل" :value="row.mobile || '-'" />
<MobileDetailRow label="وضعیت کاربر" full-width :value-class="row.active ? 'text-green-700' : 'text-red-600'">
{{ row.active ? 'فعال' : 'غیرفعال' }}
</MobileDetailRow>
<MobileDetailRow
label="وضعیت سفارش دهی"
full-width
:value-class="row.trade_active ? 'text-green-700' : 'text-red-600'"
>
{{ row.trade_active ? 'مجاز' : 'غیر مجاز' }}
</MobileDetailRow>
</MobileInfoGrid>
</template>
</MobileDetailSheet>
</template>
<script setup lang="ts">
import type { Customer } from '@/model/customer'
import MobileDetailRow from '@/components/mobile/MobileDetailRow.vue'
import MobileDetailSheet from '@/components/mobile/MobileDetailSheet.vue'
import MobileInfoGrid from '@/components/mobile/MobileInfoGrid.vue'
import MobileSheetHeader from '@/components/mobile/MobileSheetHeader.vue'
import { isMobile } from '@core/utils/is'
const props = defineProps<{
visible: boolean
item: Customer | null
}>()
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
}>()
const visibleProxy = computed({
get: () => props.visible,
set: (value: boolean) => emit('update:visible', value)
})
</script>
@@ -0,0 +1,53 @@
<template>
<MobileCard clickable @click="$emit('open', item)">
<div class="flex items-center gap-3">
<div class="flex min-w-0 flex-1 items-center gap-2">
<BaseIcon icon="vuesax-linear:user" size="20" color="#6b7280" />
<button
type="button"
class="truncate border-none bg-transparent p-0 text-right text-sm font-700 text-gray-900 cursor-pointer"
@click.stop="$emit('info', item)"
>
{{ item.name || '-' }}
</button>
</div>
<div class="shrink-0 text-sm text-gray-600">{{ item.mobile || '-' }}</div>
<div class="flex shrink-0 items-center gap-2">
<span class="whitespace-nowrap rounded-lg border px-2 py-0.5 text-xs font-600" :class="typeTagClass">
{{ item.type_str || 'کاربر' }}
</span>
<MobileIconButton
icon="vuesax-linear:archive-slash"
color="#e3bf00"
:size="20"
@click="$emit('unarchive', item)"
/>
<span class="inline-block h-2.5 w-2.5 rounded-full" :class="item.active ? 'bg-green-500' : 'bg-red-500'" />
</div>
</div>
</MobileCard>
</template>
<script setup lang="ts">
import type { Customer } from '@/model/customer'
import MobileCard from '@/components/mobile/MobileCard.vue'
import MobileIconButton from '@/components/mobile/MobileIconButton.vue'
const props = defineProps<{
item: Customer
}>()
defineEmits<{
(e: 'open', item: Customer): void
(e: 'info', item: Customer): void
(e: 'unarchive', item: Customer): void
}>()
const typeTagClass = computed(() => {
const type = props.item.type_str || ''
if (type.includes('نماینده')) return 'border-blue-400 bg-blue-50 text-blue-600'
return 'border-gray-300 bg-white text-gray-600'
})
</script>
@@ -1,128 +1,87 @@
<template>
<div>
<div class="h-full min-h-0 min-w-0 max-w-full overflow-hidden">
<BasicShowContext title="لیست مشتریان بایگانی شده">
<BaseTable v-bind="tblProps" @register="submit" ref="tbl">
<!-- <template #extraHeader>
<BaseButton type="primary" size="small" class="flex" @click="emits('add')">
<div>افزودن مشتری عمده</div>
<BaseIcon icon="vuesax-linear:user-add" Size="16px" />
</BaseButton>
</template>-->
<template #action="{ column, record }">
<div class="gap-2 flex justify-center">
<a-tooltip :key="record.is_archive" placement="bottom" class="cursor-pointer">
<template #title>
<span>{{ record.is_archive ? 'بیرون بردن از بایگانی' : 'بایگانی کردن' }}</span>
</template>
<a-popconfirm
title="آیا مشتری را از بایگانی خارج می کنید؟"
ok-text="تایید"
cancel-text="رد"
placement="topLeft"
@confirm="archiveFunction(record.id)"
@cancel=""
>
<!-- <template #icon><question-circle-outlined style="color: red" /></template>-->
<Icon class="cursor-pointer" icon="vuesax-linear:archive-slash" color="#e3bf00" />
</a-popconfirm>
</a-tooltip>
<div class="flex h-full min-h-0 flex-col">
<BaseTable v-if="!isMobile" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl">
<template #action="{ record }">
<div class="flex justify-center gap-2">
<a-tooltip :key="record.is_archive" placement="bottom" class="cursor-pointer">
<template #title>
<span>{{ record.is_archive ? 'بیرون بردن از بایگانی' : 'بایگانی کردن' }}</span>
</template>
<a-popconfirm
title="آیا مشتری را از بایگانی خارج می کنید؟"
ok-text="تایید"
cancel-text="رد"
placement="topLeft"
@confirm="archiveFunction(record.id)"
>
<Icon class="cursor-pointer" icon="vuesax-linear:archive-slash" color="#e3bf00" />
</a-popconfirm>
</a-tooltip>
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('info', record)">
<template #title>
<span>مشخصات</span>
</template>
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
</a-tooltip>
<!-- <a-tooltip
placement="bottom"
class="cursor-pointer"
@click="emits('delete', record.id)"
>
<template #title>
<span>حذف</span>
</template>
<Icon icon="vuesax-linear:trash" color="#EB3535" />
</a-tooltip>-->
</div>
</template>
</BaseTable>
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('info', record)">
<template #title><span>مشخصات</span></template>
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
</a-tooltip>
</div>
</template>
</BaseTable>
<CustomerArchiveMobileCardList
v-else
ref="mobileListRef"
class="min-h-0 flex-1"
@info="emits('info', $event)"
@unarchive="handleUnarchive"
/>
</div>
</BasicShowContext>
</div>
</template>
<script lang="tsx" setup>
import type { TableInput } from '@core/type/baseTable'
import useBaseTable from '@core/components/BaseTableOld/hooks/useBaseTable'
<script setup lang="ts">
import { message, Modal } from 'ant-design-vue'
import { ref } from 'vue'
import { changeCustomerArchive, getCustomerArchiveTable } from '@/api/customer'
import type { Customer } from '@/model/customer'
import CustomerArchiveMobileCardList from '@/pages/menuPages/customers/archive/components/CustomerArchiveMobileCardList.vue'
import { cols } from '@/pages/menuPages/customers/archive/hooks/archiveColumns'
import Icon from '@core/components/BaseIcon/src/Icon.vue'
import {
changeCustomerActivation,
changeCustomerArchive,
changeCustomerOrderActivation,
getCustomerArchiveTable,
getCustomerTable
} from '@/api/customer'
import { cols } from '../hooks/archiveColumns'
import { useUserStore } from '@/store/userStore'
import { usePrefix } from '@core/composable/usePrefix'
import { message } from 'ant-design-vue'
import useBaseTable from '@core/components/BaseTable/hooks/useBaseTable'
import type { TableInput } from '@core/type/baseTable'
import { isMobile } from '@core/utils/is'
const [submit, reload] = useBaseTable()
/*const select = (val) => {
val.sts === 1 ? return ['1'] : ['2']
}*/
//emits
const emits = defineEmits(['delete', 'edit', 'add', 'info', 'charge'])
const { prefixCls } = usePrefix('pophoverstatus')
const loading = ref(false)
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
//permission
function checkPermission(temp: string) {
const store = useUserStore()
return store.canAccess(temp)
}
//define table
const table = ref(null)
const tblProps = ref<any>({
const tblProps = ref({
columns: cols,
api: (params: TableInput) => {
return getCustomerArchiveTable(params)
}
//bordered: true
api: (params: TableInput) => getCustomerArchiveTable(params)
})
const loading = ref<boolean>(false)
async function changeCustomerActivationFunction(id: number) {
try {
loading.value = true
await changeCustomerActivation(id)
reload()
message.success('تغییر وضعیت با موفقیت انجام شد')
} catch (e) {
console.error(e)
} finally {
loading.value = false
}
async function fetch() {
await Promise.allSettled([reload(), mobileListRef.value?.fetch?.() as Promise<void>])
}
async function changeCustomerOrderActivationFunction(id: number) {
try {
loading.value = true
await changeCustomerOrderActivation(id)
reload()
message.success('تغییر وضعیت سفارش دهی با موفقیت انجام شد')
} catch (e) {
console.error(e)
} finally {
loading.value = false
}
function handleUnarchive(customer: Customer) {
Modal.confirm({
title: 'آیا مشتری را از بایگانی خارج می کنید؟',
okText: 'تایید',
cancelText: 'رد',
centered: true,
onOk: () => archiveFunction(customer.id)
})
}
async function archiveFunction(id: number) {
try {
loading.value = true
await changeCustomerArchive(id)
reload()
await fetch()
message.success('مشتری با موفقیت از بایگانی خارج شد.')
} catch (e) {
console.error(e)
@@ -130,37 +89,6 @@ async function archiveFunction(id: number) {
loading.value = false
}
}
const fetch = () => {
// console.log('sss')
reload()
}
// const spinTable = (val: boolean) => {
// // console.log('sss')
// table?.value?.loading.value = val
// }
defineExpose({
fetch
// spinTable
})
defineExpose({ fetch })
</script>
<style lang="less">
@prefix: ~'@{prefixCls}-pophoverstatus';
.@{prefix} {
.ant-menu-rtl.ant-menu-inline,
.ant-menu-rtl.ant-menu-vertical {
border-left: none;
}
.ant-menu-item-only-child {
border-radius: @border-radius-base;
}
//bor
/* .ant-input-group-addon:first-child {
!* border: #d9d9d9 1px solid;
border-radius: 0 6px 6px 0;*!
}*/
}
</style>
@@ -157,7 +157,6 @@ export const cols: BaseTableColumn[] = [
dataIndex: 'action',
key: 'action',
slot: 'action',
fixed: 'right',
filterable: false
}
]
+45 -38
View File
@@ -1,8 +1,8 @@
<template>
<div>
<div class="h-full min-h-0">
<!-- <BasicMainCard>-->
<div class="flex w-full h-full">
<tbl ref="table" class="w-full h-full" @info="info" @edit="edit" @charge="walletCharge" />
<div class="flex h-full min-h-0 w-full min-w-0 flex-col overflow-hidden">
<tbl ref="table" class="h-full min-h-0 w-full" @info="info" @edit="edit" @charge="walletCharge" />
<!-- <BasicMenu :menu="menu" width="13rem" @onChange="tabSelected" />
<component :is="selectedComponent" class="mr-5" />-->
@@ -55,15 +55,17 @@
</div>
</a-form>
</BasicModal>
<BasicModal
<BasicActionSheet
v-model:visible="userInfoVisible"
width="40vw"
title="اطلاعات کاربر"
custom-footer
modal-width="100vw"
:loading="loading"
:showButton="false"
@close="userInfoVisible = false"
>
<div class="grid grid-cols-4 gap-2">
<div class="col-span-4 bg-gray-100 w-full rounded-lg flex p-3 justify-between">
<div v-if="isMobile" class="mb-3 text-base font-700">اطلاعات کاربر</div>
<div class="space-y-2">
<div class="rounded-lg bg-gray-100 w-full flex flex-col gap-3 p-3 md:flex-row md:justify-between">
<div>
<div class="font-bold">{{ userInfo.name }}</div>
<div class="mt-1">{{ userInfo?.mobile }}</div>
@@ -82,35 +84,38 @@
</div>
</div>
<BasicInfoItem title="کد کاربری" :value="userInfo?.id" />
<BasicInfoItem title="نوع کاربر" :value="userInfo?.type_str" />
<BasicInfoItem
title="تاریخ تولد"
:value="userInfo?.birth_date ? toJalaliNoTime(userInfo?.birth_date) : '_'"
/>
<BasicInfoItem title="شماره ملی" :value="userInfo?.n_code ?? '_'" />
<BasicInfoItem title="نام استان" :value="userInfo?.province ?? '_'" />
<BasicInfoItem title="نام شهر" :value="userInfo?.city ?? '_'" />
<BasicInfoItem title="سقف اعتبار" :value="userInfo?.gram ? `${userInfo?.gram} گرم` : '_'" />
<BasicInfoItem title="کد معرف" :value="userInfo?.referral_id" />
<BasicInfoItem
title="کد معرف کاربر"
:value="userInfo?.referral ? userInfo?.referral?.id : '_'"
/>
<BasicInfoItem
title="نام معرف کاربر"
:value="userInfo?.referral ? userInfo?.referral?.name : '_'"
/>
<BasicInfoItem title="وضعیت سفارش دهی">
<CustomTag
:textColor="userInfo?.trade_active === 1 ? '#25855A' : '#C53030'"
:bgColor="userInfo?.trade_active === 1 ? '#E3F4E9' : '#FECACA'"
class="font-bold px-6"
>
{{ userInfo?.trade_active === 1 ? 'مجاز' : 'غیر مجاز' }}
</CustomTag>
</BasicInfoItem>
<div class="col-span-4 grid grid-cols-2 gap-3">
<div class="grid grid-cols-2 gap-2 md:grid-cols-4">
<BasicInfoItem title="کد کاربری" :value="userInfo?.id" />
<BasicInfoItem title="نوع کاربر" :value="userInfo?.type_str" />
<BasicInfoItem
title="تاریخ تولد"
:value="userInfo?.birth_date ? toJalaliNoTime(userInfo?.birth_date) : '_'"
/>
<BasicInfoItem title="شماره ملی" :value="userInfo?.n_code ?? '_'" />
<BasicInfoItem title="نام استان" :value="userInfo?.province ?? '_'" />
<BasicInfoItem title="نام شهر" :value="userInfo?.city ?? '_'" />
<BasicInfoItem title="سقف اعتبار" :value="userInfo?.gram ? `${userInfo?.gram} گرم` : '_'" />
<BasicInfoItem title="کد معرف" :value="userInfo?.referral_id" />
<BasicInfoItem
title="کد معرف کاربر"
:value="userInfo?.referral ? userInfo?.referral?.id : '_'"
/>
<BasicInfoItem
title="نام معرف کاربر"
:value="userInfo?.referral ? userInfo?.referral?.name : '_'"
/>
<BasicInfoItem title="وضعیت سفارش دهی" class="col-span-2 md:col-span-1">
<CustomTag
:textColor="userInfo?.trade_active === 1 ? '#25855A' : '#C53030'"
:bgColor="userInfo?.trade_active === 1 ? '#E3F4E9' : '#FECACA'"
class="font-bold px-6"
>
{{ userInfo?.trade_active === 1 ? 'مجاز' : 'غیر مجاز' }}
</CustomTag>
</BasicInfoItem>
</div>
<div class="grid grid-cols-1 gap-3 md:grid-cols-2">
<div
v-for="(item, index) in userInfo.wallet"
:key="index"
@@ -124,7 +129,7 @@
</div>
</div>
</div>
</BasicModal>
</BasicActionSheet>
<BasicModal
v-model:visible="showWalletCharge"
@@ -150,7 +155,9 @@ import { useStaticApiListsStore } from '@/store/staticApisStore'
import { ref } from 'vue'
import tbl from './components/table.vue'
import type { FormInstance } from 'ant-design-vue'
import BasicActionSheet from '@/components/BasicActionSheet.vue'
import BasicInfoModal from '@/components/BasicInfoModal/index.vue'
import { isMobile } from '@core/utils/is'
import { message } from 'ant-design-vue/es'
import type { Info } from '@/components/BasicInfoModal/model/Info'
import { toJalaliNoTime, toTime } from '@core/utils/useDataTime'
@@ -0,0 +1,64 @@
<template>
<MobileCardList
v-model:search="search"
:loading="loading"
:has-more="hasMore"
:show-empty="!loading && items.length === 0"
empty-text="مشتری‌ای یافت نشد"
@search="reload"
@load-more="loadMore"
>
<CustomerMobileListItem
v-for="item in items"
:key="item.id"
:item="item"
@open="openSheet"
@info="$emit('info', $event)"
@edit="$emit('edit', $event)"
/>
</MobileCardList>
<CustomerMobileDetailSheet
v-model:visible="sheetVisible"
:item="selectedItem"
@info="$emit('info', $event)"
@archive="$emit('archive', $event)"
@toggleActive="$emit('toggleActive', $event)"
@chargeMoney="$emit('chargeMoney', $event)"
@chargeGold="$emit('chargeGold', $event)"
@toggleTrade="$emit('toggleTrade', $event)"
@confirmUser="$emit('confirmUser', $event)"
/>
</template>
<script setup lang="ts">
import { getCustomerTable } from '@/api/customer'
import type { Customer } from '@/model/customer'
import MobileCardList from '@/components/mobile/MobileCardList.vue'
import { useMobileDetailSheet } from '@/components/mobile/composables/useMobileDetailSheet'
import { useMobileTableList } from '@/components/mobile/composables/useMobileTableList'
import CustomerMobileListItem from './CustomerMobileListItem.vue'
import CustomerMobileDetailSheet from './CustomerMobileDetailSheet.vue'
defineEmits<{
(e: 'info', item: Customer): void
(e: 'edit', item: Customer): void
(e: 'archive', item: Customer): void
(e: 'toggleActive', item: Customer): void
(e: 'chargeMoney', item: Customer): void
(e: 'chargeGold', item: Customer): void
(e: 'toggleTrade', item: Customer): void
(e: 'confirmUser', item: Customer): void
}>()
const { sheetVisible, selectedItem, openSheet, syncSelectedItem } = useMobileDetailSheet<Customer>()
const { loading, search, items, hasMore, reload, loadMore, fetch } = useMobileTableList<Customer>({
fetchApi: getCustomerTable,
onFetched: (rows) => syncSelectedItem(rows)
})
onMounted(() => reload())
defineExpose({ fetch })
</script>
@@ -0,0 +1,119 @@
<template>
<MobileDetailSheet v-model:visible="visibleProxy" :item="item" :title="item?.name || ''">
<template #default="{ item: row }">
<MobileSheetHeader :title="row.name || '-'" :show-title="isMobile">
<template #actions>
<MobileIconButton plain icon="vuesax-linear:archive-tick" color="#e3bf00" :size="22" @click="$emit('archive', row)" />
<MobileIconButton
plain
:icon="row.active ? 'vuesax-linear:user-tick' : 'vuesax-linear:user-remove'"
:color="row.active ? '#54AD06' : '#ad0627'"
:size="22"
@click="$emit('toggleActive', row)"
/>
<MobileIconButton plain icon="vuesax-linear:wallet-add" color="#9333ea" :size="22" @click="$emit('chargeMoney', row)" />
<MobileIconButton plain icon="vuesax-linear:wallet-money" color="#ea580c" :size="22" @click="$emit('chargeGold', row)" />
<MobileIconButton
plain
:icon="row.trade_active ? 'vuesax-linear:box-tick' : 'vuesax-linear:box-remove'"
:color="row.trade_active ? '#54AD06' : '#ad0627'"
:size="22"
@click="$emit('toggleTrade', row)"
/>
<MobileIconButton
v-if="!(row as any).is_legal"
plain
icon="vuesax-linear:user-search"
color="#0d9488"
:size="22"
@click="$emit('confirmUser', row)"
/>
</template>
</MobileSheetHeader>
<div class="space-y-2">
<MobileStatCard bordered variant="success" label="موجودی طلایی" :value="`${goldBalance(row)} گرم`">
<template #prefix>
<BaseIcon icon="vuesax-linear:coin" size="18" color="#15803d" />
</template>
</MobileStatCard>
<MobileStatCard
bordered
variant="success"
label="موجودی ریالی"
:value="usePrice(cashBalance(row), true, '')"
>
<template #prefix>
<BaseIcon icon="vuesax-linear:empty-wallet" size="18" color="#15803d" />
</template>
</MobileStatCard>
</div>
<MobileInfoGrid>
<MobileDetailRow label="وضعیت کاربر" full-width :value-class="row.active ? 'text-green-700' : 'text-red-600'">
{{ row.active ? 'فعال' : 'غیرفعال' }}
</MobileDetailRow>
<MobileDetailRow
label="وضعیت سفارش دهی"
full-width
:value-class="row.trade_active ? 'text-green-700' : 'text-red-600'"
>
{{ row.trade_active ? 'مجاز' : 'غیر مجاز' }}
</MobileDetailRow>
<MobileDetailRow label="کد کاربری" :value="row.id ?? '-'" />
<MobileDetailRow label="نوع کاربر" :value="row.type_str || '-'" />
<MobileDetailRow label="کد معرف" :value="row.referral_id ?? '-'" />
<MobileDetailRow label="نام معرف کاربر" :value="row.referral?.name ?? '-'" />
<MobileDetailRow label="گنج (گرم)" :value="row.gold ?? '-'" />
<MobileDetailRow label="آخرین بازدید" :value="lastSeen(row)" value-class="text-xs" />
</MobileInfoGrid>
</template>
</MobileDetailSheet>
</template>
<script setup lang="ts">
import type { Customer } from '@/model/customer'
import MobileDetailRow from '@/components/mobile/MobileDetailRow.vue'
import MobileStatCard from '@/components/mobile/MobileStatCard.vue'
import MobileDetailSheet from '@/components/mobile/MobileDetailSheet.vue'
import MobileIconButton from '@/components/mobile/MobileIconButton.vue'
import MobileInfoGrid from '@/components/mobile/MobileInfoGrid.vue'
import MobileSheetHeader from '@/components/mobile/MobileSheetHeader.vue'
import { getCashBalance, getGoldBalance } from '@/components/mobile/utils/wallet'
import { isMobile } from '@core/utils/is'
import { toJalali } from '@core/utils/useDataTime'
import { usePrice } from '@core/utils/usePrice'
const props = defineProps<{
visible: boolean
item: Customer | null
}>()
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
(e: 'info', item: Customer): void
(e: 'archive', item: Customer): void
(e: 'toggleActive', item: Customer): void
(e: 'chargeMoney', item: Customer): void
(e: 'chargeGold', item: Customer): void
(e: 'toggleTrade', item: Customer): void
(e: 'confirmUser', item: Customer): void
}>()
const visibleProxy = computed({
get: () => props.visible,
set: (value: boolean) => emit('update:visible', value)
})
function cashBalance(row: Customer) {
return getCashBalance(row.wallet)
}
function goldBalance(row: Customer) {
return getGoldBalance(row.wallet)
}
function lastSeen(row: Customer) {
return row.last_seen_at ? toJalali(row.last_seen_at) : '-'
}
</script>
@@ -0,0 +1,48 @@
<template>
<MobileCard clickable @click="$emit('open', item)">
<div class="flex items-center gap-3">
<div class="flex min-w-0 flex-1 items-center gap-2">
<BaseIcon icon="vuesax-linear:user" size="20" color="#6b7280" />
<button
type="button"
class="truncate border-none bg-transparent p-0 text-right text-sm font-700 text-gray-900 cursor-pointer"
@click.stop="$emit('info', item)"
>
{{ item.name || '-' }}
</button>
</div>
<div class="shrink-0 text-sm text-gray-600">{{ item.mobile || '-' }}</div>
<div class="flex shrink-0 items-center gap-2">
<span class="whitespace-nowrap rounded-lg border px-2 py-0.5 text-xs font-600" :class="typeTagClass">
{{ item.type_str || 'کاربر' }}
</span>
<MobileIconButton icon="vuesax-linear:edit" color="#4572E9" :size="20" @click="$emit('edit', item)" />
<span class="inline-block h-2.5 w-2.5 rounded-full" :class="item.active ? 'bg-green-500' : 'bg-red-500'" />
</div>
</div>
</MobileCard>
</template>
<script setup lang="ts">
import type { Customer } from '@/model/customer'
import MobileCard from '@/components/mobile/MobileCard.vue'
import MobileIconButton from '@/components/mobile/MobileIconButton.vue'
const props = defineProps<{
item: Customer
}>()
defineEmits<{
(e: 'open', item: Customer): void
(e: 'info', item: Customer): void
(e: 'edit', item: Customer): void
}>()
const typeTagClass = computed(() => {
const type = props.item.type_str || ''
if (type.includes('نماینده')) return 'border-blue-400 bg-blue-50 text-blue-600'
return 'border-gray-300 bg-white text-gray-600'
})
</script>
@@ -1,138 +1,21 @@
<template>
<div>
<BasicShowContext title=" لیست مشتریان">
<BaseTable v-bind="tblProps" @register="submit" ref="tbl">
<div class="h-full min-h-0 min-w-0 max-w-full overflow-hidden">
<BasicShowContext title="لیست مشتریان">
<template #extra>
<BaseButton type="primary" size="small" class="flex shrink-0 md:hidden" @click="emits('add')">
<div>افزودن</div>
<BaseIcon icon="vuesax-linear:user-add" Size="16px" />
</BaseButton>
</template>
<div class="flex h-full min-h-0 flex-col">
<BaseTable v-if="!isMobile" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl">
<template #extraHeader>
<BaseButton type="primary" size="small" class="flex" @click="emits('add')">
<div>افزودن مشتری</div>
<BaseIcon icon="vuesax-linear:user-add" Size="16px" />
</BaseButton>
</template>
<template #action="{ column, record }">
<div class="md:hidden">
<a-popover title="" placement="right" @cancel="">
<template #content>
<div class="flex flex-col gap-3">
<a-tooltip :key="record.is_archive" placement="bottom" class="cursor-pointer">
<template #title>
<span>{{
record.is_archive ? 'بیرون بردن از بایگانی' : 'بایگانی کردن'
}}</span>
</template>
<a-popconfirm
title="آیا مشتری را بایگانی می کنید؟"
ok-text="تایید"
cancel-text="رد"
placement="topLeft"
@confirm="archiveFunction(record.id)"
@cancel=""
>
<!-- <template #icon><question-circle-outlined style="color: red" /></template>-->
<Icon
class="cursor-pointer"
icon="vuesax-linear:archive-tick"
color="#e3bf00"
/>
</a-popconfirm>
</a-tooltip>
<a-tooltip
:key="record.active"
placement="bottom"
class="cursor-pointer"
@click="changeCustomerActivationFunction(record.id)"
>
<template #title>
<span>
{{ record.active ? 'غیرفعال کردن حساب کاربری' : 'فعال کردن حساب کاربری' }}
</span>
</template>
<Icon
:icon="
record.active ? 'vuesax-linear:user-tick' : 'vuesax-linear:user-remove'
"
:color="record.active ? '#54AD06' : '#ad0627'"
/>
</a-tooltip>
<a-tooltip
placement="bottom"
class="cursor-pointer"
@click="emits('chargeMoney', record)"
>
<template #title>
<span>شارژ کیف پول ریالی</span>
</template>
<Icon icon="vuesax-linear:wallet-add" color="#9333ea" />
</a-tooltip>
<a-tooltip
placement="bottom"
class="cursor-pointer"
@click="emits('chargeGold', record)"
>
<template #title>
<span>شارژ کیف پول طلایی</span>
</template>
<Icon icon="vuesax-linear:wallet-money" color="#ea580c" />
</a-tooltip>
<a-tooltip
placement="bottom"
class="cursor-pointer"
@click="emits('info', record)"
>
<template #title>
<span>مشخصات</span>
</template>
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
</a-tooltip>
<a-tooltip
placement="bottom"
class="cursor-pointer"
@click="emits('edit', record)"
>
<template #title>
<span>ویرایش</span>
</template>
<Icon icon="vuesax-linear:edit" color="#4572E9" />
</a-tooltip>
<a-tooltip
:key="record.trade_active"
placement="bottom"
class="cursor-pointer"
@click="changeCustomerOrderActivationFunction(record.id)"
>
<template #title>
<span>
{{
record.trade_active ? 'غیر مجاز کردن سفارش دهی' : 'مجاز کردن سفارش دهی'
}}
</span>
</template>
<Icon
:icon="
record.trade_active === 1
? 'vuesax-linear:box-tick'
: 'vuesax-linear:box-remove'
"
:color="record.trade_active === 1 ? '#54AD06' : '#ad0627'"
/>
</a-tooltip>
<a-tooltip
v-if="!record.is_legal"
:key="record.trade_active"
placement="bottom"
class="cursor-pointer"
@click="openConfirmUser(record)"
>
<template #title>
<span>تایید کاربر</span>
</template>
<Icon icon="vuesax-linear:user-search" color="#0d9488" />
</a-tooltip>
</div>
</template>
<!-- <template #icon><question-circle-outlined style="color: red" /></template>-->
<Icon class="cursor-pointer" icon="vuesax-linear:setting" color="#000" />
</a-popover>
</div>
<template #action="{ record }">
<div class="gap-2 hidden md:flex justify-center">
<a-tooltip :key="record.is_archive" placement="bottom" class="cursor-pointer">
<template #title>
@@ -144,9 +27,7 @@
cancel-text="رد"
placement="topLeft"
@confirm="archiveFunction(record.id)"
@cancel=""
>
<!-- <template #icon><question-circle-outlined style="color: red" /></template>-->
<Icon class="cursor-pointer" icon="vuesax-linear:archive-tick" color="#e3bf00" />
</a-popconfirm>
</a-tooltip>
@@ -157,45 +38,27 @@
@click="changeCustomerActivationFunction(record.id)"
>
<template #title>
<span>{{
record.active ? 'غیرفعال کردن حساب کاربری' : 'فعال کردن حساب کاربری'
}}</span>
<span>{{ record.active ? 'غیرفعال کردن حساب کاربری' : 'فعال کردن حساب کاربری' }}</span>
</template>
<Icon
:icon="record.active ? 'vuesax-linear:user-tick' : 'vuesax-linear:user-remove'"
:color="record.active ? '#54AD06' : '#ad0627'"
/>
</a-tooltip>
<a-tooltip
placement="bottom"
class="cursor-pointer"
@click="emits('chargeMoney', record)"
>
<template #title>
<span>شارژ کیف پول ریالی</span>
</template>
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('chargeMoney', record)">
<template #title><span>شارژ کیف پول ریالی</span></template>
<Icon icon="vuesax-linear:wallet-add" color="#9333ea" />
</a-tooltip>
<a-tooltip
placement="bottom"
class="cursor-pointer"
@click="emits('chargeGold', record)"
>
<template #title>
<span>شارژ کیف پول طلایی</span>
</template>
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('chargeGold', record)">
<template #title><span>شارژ کیف پول طلایی</span></template>
<Icon icon="vuesax-linear:wallet-money" color="#ea580c" />
</a-tooltip>
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('info', record)">
<template #title>
<span>مشخصات</span>
</template>
<template #title><span>مشخصات</span></template>
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
</a-tooltip>
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('edit', record)">
<template #title>
<span>ویرایش</span>
</template>
<template #title><span>ویرایش</span></template>
<Icon icon="vuesax-linear:edit" color="#4572E9" />
</a-tooltip>
<a-tooltip
@@ -205,42 +68,41 @@
@click="changeCustomerOrderActivationFunction(record.id)"
>
<template #title>
<span>{{
record.trade_active ? 'غیر مجاز کردن سفارش دهی' : 'مجاز کردن سفارش دهی'
}}</span>
<span>{{ record.trade_active ? 'غیر مجاز کردن سفارش دهی' : 'مجاز کردن سفارش دهی' }}</span>
</template>
<Icon
:icon="
record.trade_active === 1 ? 'vuesax-linear:box-tick' : 'vuesax-linear:box-remove'
"
:icon="record.trade_active === 1 ? 'vuesax-linear:box-tick' : 'vuesax-linear:box-remove'"
:color="record.trade_active === 1 ? '#54AD06' : '#ad0627'"
/>
</a-tooltip>
<a-tooltip
v-if="!record.is_legal"
:key="record.trade_active"
placement="bottom"
class="cursor-pointer"
@click="openConfirmUser(record)"
>
<template #title>
<span>تایید کاربر</span>
</template>
<template #title><span>تایید کاربر</span></template>
<Icon icon="vuesax-linear:user-search" color="#0d9488" />
</a-tooltip>
<!-- <a-tooltip
placement="bottom"
class="cursor-pointer"
@click="emits('delete', record.id)"
>
<template #title>
<span>حذف</span>
</template>
<Icon icon="vuesax-linear:trash" color="#EB3535" />
</a-tooltip>-->
</div>
</template>
</BaseTable>
<CustomerMobileCardList
v-else
ref="mobileListRef"
class="min-h-0 flex-1"
@info="emits('info', $event)"
@edit="emits('edit', $event)"
@archive="handleArchive"
@toggleActive="(item) => changeCustomerActivationFunction(item.id)"
@chargeMoney="emits('chargeMoney', $event)"
@chargeGold="emits('chargeGold', $event)"
@toggleTrade="(item) => changeCustomerOrderActivationFunction(item.id)"
@confirmUser="openConfirmUser"
/>
</div>
<BasicModal
v-model:visible="confirmVisible"
width="50vw"
@@ -268,10 +130,9 @@
</div>
</template>
<script lang="tsx" setup>
import type { TableInput } from '@core/type/baseTable'
import useBaseTable from '@core/components/BaseTable/hooks/useBaseTable'
import Icon from '@core/components/BaseIcon/src/Icon.vue'
<script setup lang="ts">
import { message, Modal } from 'ant-design-vue'
import { ref } from 'vue'
import {
changeCustomerActivation,
changeCustomerArchive,
@@ -280,59 +141,38 @@ import {
getCustomerTable
} from '@/api/customer'
import { cols } from '@/pages/menuPages/customers/base/hooks/customerColumns'
import { useUserStore } from '@/store/userStore'
import { usePrefix } from '@core/composable/usePrefix'
import { message } from 'ant-design-vue'
import { inputRules } from '@/utils/inputRules'
import type { User } from '@/model/user'
import CustomerMobileCardList from '@/pages/menuPages/customers/base/components/CustomerMobileCardList.vue'
import Icon from '@core/components/BaseIcon/src/Icon.vue'
import useBaseTable from '@core/components/BaseTable/hooks/useBaseTable'
import type { TableInput } from '@core/type/baseTable'
import { toJalaliNoTime } from '@core/utils/useDataTime'
import type { Customer } from '@/model/customer'
import { onMounted } from 'vue'
import { isMobile } from '@core/utils/is'
const [submit, reload] = useBaseTable()
/*const select = (val) => {
val.sts === 1 ? return ['1'] : ['2']
}*/
//emits
const emits = defineEmits(['delete', 'edit', 'add', 'info', 'chargeMoney', 'chargeGold'])
const { prefixCls } = usePrefix('pophoverstatus')
//permission
function checkPermission(temp: string) {
const store = useUserStore()
return store.canAccess(temp)
}
//define table
const table = ref(null)
const confirmVisible = ref<boolean>(false)
const loading = ref(false)
const confirmVisible = ref(false)
const confirmInfo = ref<Customer>()
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
const tblProps = ref<any>({
columns: cols,
api: (params: TableInput) => {
return getCustomerTable(params)
}
//bordered: true
api: (params: TableInput) => getCustomerTable(params)
})
const refPop = ref()
// function edit(recordId){
// emits('edit', recordId);
// console.log( recordId);
// console.log( visibleAction.value[parseInt(recordId)]);
// visibleAction.value[recordId]=false
// console.log('ddddd')
// }
const loading = ref<boolean>(false)
const fetch = async () => {
await Promise.allSettled([reload(), mobileListRef.value?.fetch?.() as Promise<void>])
}
defineExpose({ fetch })
async function changeCustomerActivationFunction(id: number) {
try {
loading.value = true
await changeCustomerActivation(id)
reload()
await fetch()
message.success('تغییر وضعیت با موفقیت انجام شد')
} catch (e) {
console.error(e)
@@ -346,11 +186,21 @@ function openConfirmUser(user: Customer) {
confirmVisible.value = true
}
function handleArchive(customer: Customer) {
Modal.confirm({
title: 'آیا مشتری را بایگانی می کنید؟',
okText: 'تایید',
cancelText: 'رد',
centered: true,
onOk: () => archiveFunction(customer.id)
})
}
async function archiveFunction(id: number) {
try {
loading.value = true
await changeCustomerArchive(id)
reload()
await fetch()
message.success('مشتری با موفقیت بایگانی شد.')
} catch (e) {
console.error(e)
@@ -363,7 +213,7 @@ async function confirmUserFunction() {
try {
loading.value = true
await confirmUser(confirmInfo.value?.id)
reload()
await fetch()
confirmVisible.value = false
} catch (e) {
console.log(e)
@@ -376,7 +226,7 @@ async function changeCustomerOrderActivationFunction(id: number) {
try {
loading.value = true
await changeCustomerOrderActivation(id)
reload()
await fetch()
message.success('تغییر وضعیت سفارش دهی با موفقیت انجام شد')
} catch (e) {
console.error(e)
@@ -384,42 +234,11 @@ async function changeCustomerOrderActivationFunction(id: number) {
loading.value = false
}
}
const fetch = () => {
// console.log('sss')
reload()
}
// const spinTable = (val: boolean) => {
// // console.log('sss')
// table?.value?.loading.value = val
// }
defineExpose({
fetch
// spinTable
})
</script>
<style lang="less">
@prefix: ~'@{prefixCls}-pophoverstatus';
.ant-popover,
.ant-tooltip {
z-index: 1001 !important;
}
.@{prefix} {
.ant-menu-rtl.ant-menu-inline,
.ant-menu-rtl.ant-menu-vertical {
border-left: none;
}
.ant-menu-item-only-child {
border-radius: @border-radius-base;
}
//bor
/* .ant-input-group-addon:first-child {
!* border: #d9d9d9 1px solid;
border-radius: 0 6px 6px 0;*!
}*/
}
</style>
@@ -208,7 +208,6 @@ export const cols: BaseTableColumn[] = [
dataIndex: 'action',
key: 'action',
slot: 'action',
fixed: 'right',
filterable: false
}
]
+69 -59
View File
@@ -1,10 +1,8 @@
<template>
<div>
<!-- <BasicMainCard>-->
<div class="flex w-full h-full">
<tbl
ref="table"
class="w-full h-full"
<div class="h-full min-h-0 min-w-0 overflow-hidden">
<tbl
ref="table"
class="h-full min-h-0 w-full"
@info="info"
@edit="edit"
@add="add"
@@ -15,16 +13,21 @@
<!-- <BasicMenu :menu="menu" width="13rem" @onChange="tabSelected" />
<component :is="selectedComponent" class="mr-5" />-->
<!-- <BasicShowContext title="پروژه ها"> <Search /> </BasicShowContext>-->
</div>
<!-- </BasicMainCard>-->
<BasicModal
<BasicActionSheet
v-model:visible="addUserVisible"
width="40rem"
:title="`${editId ? 'ویرایش' : 'افزودن'} کاربر`"
@ok="ok"
modal-width="40rem"
:loading="loading"
:showButton="true"
:showCancel="true"
:showOk="true"
ok-text="ثبت"
cancel-text="انصراف"
@ok="ok"
@close="addUserVisible = false"
>
<div v-if="isMobile" class="mb-3 text-base font-700">{{ `${editId ? 'ویرایش' : 'افزودن'} کاربر` }}</div>
<a-form :model="form" ref="formRef" autocomplete="off">
<div class="grid md:grid-cols-2 gap-4">
<a-form-item label="نام" name="fname" :rules="inputRules()">
@@ -88,19 +91,20 @@
</a-form-item>-->
</div>
</a-form>
</BasicModal>
<BasicModal
</BasicActionSheet>
<BasicActionSheet
v-model:visible="userInfoVisible"
width="100vw"
title="اطلاعات کاربر"
modal-width="100vw"
:loading="loading"
custom-footer
:showButton="false"
@close="userInfoVisible = false"
>
<a-tabs v-model:activeKey="activeKey">
<div v-if="isMobile" class="mb-3 text-base font-700">اطلاعات کاربر</div>
<a-tabs v-if="userInfo" v-model:activeKey="activeKey">
<a-tab-pane key="اطلاعات جامع" tab="اطلاعات جامع">
<div class="grid grid-cols-4 gap-2">
<div class="col-span-4 bg-gray-100 w-full rounded-lg flex p-3 justify-between">
<div class="space-y-2">
<div class="rounded-lg bg-gray-100 w-full flex flex-col gap-3 p-3 md:flex-row md:justify-between">
<div>
<div class="font-bold">{{ userInfo.name }}</div>
<div class="mt-1">{{ userInfo?.mobile }}</div>
@@ -135,47 +139,51 @@
</div>
</div>
</div>
<BasicInfoItem title="کد کاربری" :value="userInfo?.id" />
<BasicInfoItem title="نوع کاربر" :value="userInfo?.type_str" />
<BasicInfoItem
title="تاریخ تولد"
:value="userInfo?.birth_date ? toJalaliNoTime(userInfo?.birth_date) : '_'"
/>
<BasicInfoItem title="شماره ملی" :value="userInfo?.n_code ?? '_'" />
<BasicInfoItem title="نام استان" :value="userInfo?.province ?? '_'" />
<BasicInfoItem title="نام شهر" :value="userInfo?.city ?? '_'" />
<BasicInfoItem
title="سقف اعتبار"
:value="userInfo?.gram ? `${userInfo?.gram} گرم` : '_'"
/>
<BasicInfoItem title="کد معرف" :value="userInfo?.referral_id" />
<BasicInfoItem
title="کد معرف کاربر"
:value="userInfo?.referral ? userInfo?.referral?.id : '_'"
/>
<BasicInfoItem
title="نام معرف کاربر"
:value="userInfo?.referral ? userInfo?.referral?.name : '_'"
/>
<BasicInfoItem
title="میزان سود معرفی"
:value="userInfo?.profit_amount ? `${userInfo?.profit_amount} گرم` : '_'"
/>
<BasicInfoItem
title="حداکثر معرفی"
:value="userInfo?.add_user_limit ? `${userInfo?.add_user_limit} نفر` : '_'"
/>
<BasicInfoItem title="آخرین بازدید" :value="toJalali(userInfo?.last_seen_at)" />
<BasicInfoItem title="وضعیت سفارش دهی">
<CustomTag
:textColor="userInfo?.trade_active === 1 ? '#25855A' : '#C53030'"
:bgColor="userInfo?.trade_active === 1 ? '#E3F4E9' : '#FECACA'"
class="font-bold px-6"
>
{{ userInfo?.trade_active === 1 ? 'مجاز' : 'غیر مجاز' }}
</CustomTag>
</BasicInfoItem>
<div class="col-span-4 grid grid-cols-2 gap-3">
<div class="grid grid-cols-2 gap-2 md:grid-cols-4">
<BasicInfoItem title="کد کاربری" :value="userInfo?.id" />
<BasicInfoItem title="نوع کاربر" :value="userInfo?.type_str" />
<BasicInfoItem
title="تاریخ تولد"
:value="userInfo?.birth_date ? toJalaliNoTime(userInfo?.birth_date) : '_'"
/>
<BasicInfoItem title="شماره ملی" :value="userInfo?.n_code ?? '_'" />
<BasicInfoItem title="نام استان" :value="userInfo?.province ?? '_'" />
<BasicInfoItem title="نام شهر" :value="userInfo?.city ?? '_'" />
<BasicInfoItem
title="سقف اعتبار"
:value="userInfo?.gram ? `${userInfo?.gram} گرم` : '_'"
/>
<BasicInfoItem title="کد معرف" :value="userInfo?.referral_id" />
<BasicInfoItem
title="کد معرف کاربر"
:value="userInfo?.referral ? userInfo?.referral?.id : '_'"
/>
<BasicInfoItem
title="نام معرف کاربر"
:value="userInfo?.referral ? userInfo?.referral?.name : '_'"
/>
<BasicInfoItem
title="میزان سود معرفی"
:value="userInfo?.profit_amount ? `${userInfo?.profit_amount} گرم` : '_'"
/>
<BasicInfoItem
title="حداکثر معرفی"
:value="userInfo?.add_user_limit ? `${userInfo?.add_user_limit} نفر` : '_'"
/>
<BasicInfoItem title="آخرین بازدید" :value="toJalali(userInfo?.last_seen_at)" />
<BasicInfoItem title="وضعیت سفارش دهی" class="col-span-2 md:col-span-1">
<CustomTag
:textColor="userInfo?.trade_active === 1 ? '#25855A' : '#C53030'"
:bgColor="userInfo?.trade_active === 1 ? '#E3F4E9' : '#FECACA'"
class="font-bold px-6"
>
{{ userInfo?.trade_active === 1 ? 'مجاز' : 'غیر مجاز' }}
</CustomTag>
</BasicInfoItem>
</div>
<div class="grid grid-cols-1 gap-3 md:grid-cols-2">
<div
v-for="(item, index) in userInfo.wallet"
:key="index"
@@ -206,7 +214,7 @@
<FundsTable ref="fundTable" :customerId="userInfo?.id" />
</a-tab-pane>
</a-tabs>
</BasicModal>
</BasicActionSheet>
<BasicModal
v-model:visible="showMoneyWalletCharge"
width="20rem"
@@ -275,6 +283,7 @@ import { ref, watch } from 'vue'
import tbl from './components/table.vue'
import type { FormInstance } from 'ant-design-vue'
import BasicInfoModal from '@/components/BasicInfoModal/index.vue'
import BasicActionSheet from '@/components/BasicActionSheet.vue'
import { message } from 'ant-design-vue/es'
import type { Info } from '@/components/BasicInfoModal/model/Info'
import { toJalali, toJalaliNoTime, toTime } from '@core/utils/useDataTime'
@@ -295,6 +304,7 @@ import FundsTable from '@/pages/menuPages/customers/base/components/fundsTable.v
import ReferralsTable from '@/pages/menuPages/customers/base/components/referralsTable.vue'
import type { Order } from '@/model/order'
import Icon from '@core/components/BaseIcon/src/Icon.vue'
import { isMobile } from '@core/utils/is'
//tabs
const activeKey = ref('اطلاعات جامع')
+5 -5
View File
@@ -1,16 +1,16 @@
<template>
<div>
<div class="h-full min-h-0">
<BasicMainCard>
<div class="flex flex-col md:flex-row md:h-full">
<div class="flex h-full min-h-0 min-w-0 flex-col md:flex-row">
<!-- {{ menu }}-->
<BasicMenu width="11rem" :menu="menu" mode="horizontal" class="md:hidden overflow-auto" />
<BasicMenu width="11rem" :menu="menu" mode="horizontal" class="shrink-0 overflow-auto md:hidden" />
<BasicMenu
width="13rem"
:menu="menu"
class="hidden md:inline overflow-auto min-w-[13.5rem]"
class="hidden min-w-[13.5rem] shrink-0 overflow-auto md:inline"
/>
<!-- <component :is="selectedComponent" class="mr-5" />-->
<RouterView class="md:mr-5 md:mt-0 mt-5 w-full"></RouterView>
<RouterView class="mt-5 min-h-0 min-w-0 w-full flex-1 overflow-hidden md:mr-5 md:mt-0" />
</div>
</BasicMainCard>
</div>
@@ -0,0 +1,101 @@
<template>
<MobileDetailSheet v-model:visible="visibleProxy" :item="item" :title="sheetTitle">
<template #default="{ item: row }">
<MobileSheetHeader :title="sheetTitle" :show-title="isMobile" />
<div class="flex items-center gap-2">
<span class="rounded-lg border px-2 py-0.5 text-xs font-600" :class="unitTagClass(row)">
{{ unitLabel(row) }}
</span>
<span class="rounded-lg border px-2 py-0.5 text-xs font-600" :class="statusClass(row)">
{{ statusLabel(row) }}
</span>
</div>
<MobileStatCard bordered label="مقدار" :value="amountLabel(row)">
<template #prefix>
<BaseIcon
:icon="row.unit === 2 ? 'vuesax-linear:coin' : 'vuesax-linear:empty-wallet'"
size="18"
color="#2563eb"
/>
</template>
</MobileStatCard>
<MobileInfoGrid>
<MobileDetailRow label="کد" full-width :value="row.code || '-'" />
<MobileDetailRow label="تاریخ انقضا" full-width :value="dateLabel(row.expiry_date)" />
<MobileDetailRow label="تاریخ استفاده" full-width :value="dateLabel(row.used_at)" />
<MobileDetailRow label="نام کاربر" full-width :value="row.assigned_to?.name || '-'" />
<MobileDetailRow label="موبایل کاربر" full-width :value="row.assigned_to?.mobile || '-'" />
</MobileInfoGrid>
</template>
</MobileDetailSheet>
</template>
<script setup lang="ts">
import type { Giftcardcode } from '@/model/giftCard'
import MobileDetailRow from '@/components/mobile/MobileDetailRow.vue'
import MobileDetailSheet from '@/components/mobile/MobileDetailSheet.vue'
import MobileInfoGrid from '@/components/mobile/MobileInfoGrid.vue'
import MobileSheetHeader from '@/components/mobile/MobileSheetHeader.vue'
import MobileStatCard from '@/components/mobile/MobileStatCard.vue'
import { isMobile } from '@core/utils/is'
import { toJalali } from '@core/utils/useDataTime'
const props = defineProps<{
visible: boolean
item: Giftcardcode | null
}>()
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
}>()
const visibleProxy = computed({
get: () => props.visible,
set: (value: boolean) => emit('update:visible', value)
})
const sheetTitle = computed(() => props.item?.code || 'کد کارت هدیه')
function statusLabel(row: Giftcardcode) {
switch (row.sts) {
case 1:
return 'فعال'
case 2:
return 'استفاده شده'
default:
return 'غیرفعال'
}
}
function statusClass(row: Giftcardcode) {
switch (row.sts) {
case 1:
return 'border-amber-300 bg-amber-50 text-amber-700'
case 2:
return 'border-green-300 bg-green-50 text-green-700'
default:
return 'border-red-300 bg-red-50 text-red-600'
}
}
function unitLabel(row: Giftcardcode) {
return row.unit === 2 ? 'طلایی' : 'ریالی'
}
function unitTagClass(row: Giftcardcode) {
return row.unit === 2
? 'border-amber-300 bg-amber-50 text-amber-700'
: 'border-green-300 bg-green-50 text-green-700'
}
function amountLabel(row: Giftcardcode) {
return `${row.amount ?? 0} ${row.unit === 2 ? 'گرم' : 'ریال'}`
}
function dateLabel(value?: string) {
return value ? toJalali(value) : '-'
}
</script>
@@ -0,0 +1,61 @@
<template>
<MobileCard clickable @click="$emit('open', item)">
<div class="space-y-1">
<div class="flex items-center justify-between gap-2">
<span class="truncate font-mono text-sm font-700 text-gray-900">{{ item.code || '-' }}</span>
<span class="shrink-0 rounded-md border px-1.5 py-0.5 text-xs font-600" :class="statusClass">
{{ statusLabel }}
</span>
</div>
<div class="flex items-center justify-between gap-2 text-xs text-gray-600">
<span class="rounded-md border px-1.5 py-0.5 font-600" :class="unitTagClass">{{ unitLabel }}</span>
<span class="font-600 text-gray-900">{{ amountLabel }}</span>
</div>
</div>
</MobileCard>
</template>
<script setup lang="ts">
import type { Giftcardcode } from '@/model/giftCard'
import MobileCard from '@/components/mobile/MobileCard.vue'
const props = defineProps<{
item: Giftcardcode
}>()
defineEmits<{
(e: 'open', item: Giftcardcode): void
}>()
const statusLabel = computed(() => {
switch (props.item.sts) {
case 1:
return 'فعال'
case 2:
return 'استفاده شده'
default:
return 'غیرفعال'
}
})
const statusClass = computed(() => {
switch (props.item.sts) {
case 1:
return 'border-amber-300 bg-amber-50 text-amber-700'
case 2:
return 'border-green-300 bg-green-50 text-green-700'
default:
return 'border-red-300 bg-red-50 text-red-600'
}
})
const unitLabel = computed(() => (props.item.unit === 2 ? 'طلایی' : 'ریالی'))
const unitTagClass = computed(() =>
props.item.unit === 2
? 'border-amber-300 bg-amber-50 text-amber-700'
: 'border-green-300 bg-green-50 text-green-700'
)
const amountLabel = computed(() => `${props.item.amount ?? 0} ${props.item.unit === 2 ? 'گرم' : 'ریال'}`)
</script>
@@ -0,0 +1,106 @@
<template>
<BasicActionSheet
v-model:visible="visibleProxy"
title="لیست کدهای کارت هدیه"
modal-width="80%"
drawer-height="90vh"
:showButton="true"
:showCancel="true"
:showOk="false"
cancel-text="بستن"
@cancel="close"
@close="close"
>
<div v-if="isMobile" class="mb-3 text-base font-700">لیست کدهای کارت هدیه</div>
<a-table
class="hidden md:block"
:columns="columns"
:data-source="codes"
:pagination="{ pageSize: 10 }"
row-key="id"
>
<template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
<template v-else-if="column.key === 'sts'">
<CustomTag v-if="record.sts === 1" textColor="#ca8a04" bgColor="#fef3c7">فعال</CustomTag>
<CustomTag v-else-if="record.sts === 2" textColor="#25855A" bgColor="#E3F4E9">استفاده شده</CustomTag>
<CustomTag v-else textColor="#C53030" bgColor="#FECACA">غیرفعال</CustomTag>
</template>
<template v-else-if="column.key === 'unit'">
<CustomTag v-if="record.unit === 2" textColor="#C53030" bgColor="#FECACA">طلایی</CustomTag>
<CustomTag v-else textColor="#25855A" bgColor="#E3F4E9">ریالی</CustomTag>
</template>
<template v-else-if="column.key === 'amount'">
{{ record.amount }} {{ record.unit === 2 ? 'گرم' : 'ریال' }}
</template>
<template v-else-if="column.key === 'expiry_date'">
{{ record.expiry_date ? toJalali(record.expiry_date) : '-' }}
</template>
<template v-else-if="column.key === 'used_at'">
{{ record.used_at ? toJalali(record.used_at) : '-' }}
</template>
<template v-else-if="column.key === 'assigned_to_name'">
{{ record.assigned_to?.name ?? '-' }}
</template>
<template v-else-if="column.key === 'assigned_to_mobile'">
{{ record.assigned_to?.mobile ?? '-' }}
</template>
</template>
</a-table>
<div class="space-y-2 md:hidden">
<GiftCardCodeMobileListItem
v-for="item in codes"
:key="item.id"
:item="item"
@open="openSheet"
/>
</div>
<GiftCardCodeMobileDetailSheet v-model:visible="sheetVisible" :item="selectedItem" />
</BasicActionSheet>
</template>
<script setup lang="ts">
import type { Giftcardcode } from '@/model/giftCard'
import BasicActionSheet from '@/components/BasicActionSheet.vue'
import CustomTag from '@/components/CustomTag.vue'
import { useMobileDetailSheet } from '@/components/mobile/composables/useMobileDetailSheet'
import { isMobile } from '@core/utils/is'
import { toJalali } from '@core/utils/useDataTime'
import GiftCardCodeMobileDetailSheet from './GiftCardCodeMobileDetailSheet.vue'
import GiftCardCodeMobileListItem from './GiftCardCodeMobileListItem.vue'
const props = defineProps<{
visible: boolean
codes: Giftcardcode[]
}>()
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
}>()
const visibleProxy = computed({
get: () => props.visible,
set: (value: boolean) => emit('update:visible', value)
})
const { sheetVisible, selectedItem, openSheet } = useMobileDetailSheet<Giftcardcode>()
const columns = [
{ title: 'ردیف', key: 'index', dataIndex: 'index' },
{ title: 'کد', dataIndex: 'code', key: 'code' },
{ title: 'نوع', dataIndex: 'unit', key: 'unit' },
{ title: 'مقدار', dataIndex: 'amount', key: 'amount' },
{ title: 'وضعیت', dataIndex: 'sts', key: 'sts' },
{ title: 'تاریخ انقضا', dataIndex: 'expiry_date', key: 'expiry_date' },
{ title: 'تاریخ استفاده', dataIndex: 'used_at', key: 'used_at' },
{ title: 'نام کاربر اختصاص داده شده', dataIndex: 'assigned_to.name', key: 'assigned_to_name' },
{ title: 'شماره موبایل کاربر اختصاص داده شده', dataIndex: 'assigned_to.mobile', key: 'assigned_to_mobile' }
]
function close() {
visibleProxy.value = false
}
</script>
@@ -0,0 +1,62 @@
<template>
<a-form :model="form" ref="formRef" autocomplete="off">
<div class="space-y-3">
<a-form-item label="شماره موبایل مشتری" name="mobile" :rules="inputLenRules(11)">
<BasicInput v-model:value="form.mobile" type="tel" placeholder="09123456789" />
</a-form-item>
<a-form-item label="نام مشتری" name="name" :rules="inputRules()">
<BasicInput v-model:value="form.name" placeholder="نام و نام خانوادگی مشتری" />
</a-form-item>
<a-form-item label="نوع کارت هدیه" name="unit" :rules="inputRules()">
<a-radio-group v-model:value="form.unit" button-style="solid">
<a-radio-button value="1">ریالی</a-radio-button>
<a-radio-button value="2">طلایی</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item label="تعداد کدها" name="count" :rules="inputRulesNumber()">
<BasicInput v-model:value="form.count" type="tel" placeholder="تعداد کدهای کارت هدیه" />
</a-form-item>
<a-form-item
:label="`مقدار ${form.unit === '2' ? '(گرم)' : '(ریال)'}`"
name="amount"
:rules="inputRulesNumber()"
>
<BasicInput
v-model:value="form.amount"
:addon-before="form.unit === '2' ? 'گرم' : 'ریال'"
type="tel"
placeholder="مقدار کارت هدیه"
/>
</a-form-item>
<a-form-item label="تاریخ انقضا" name="expiry_date" :rules="inputRules()">
<BaseDatePicker v-model:value="form.expiry_date" format="YYYY-MM-DD" />
</a-form-item>
<a-form-item label="توضیحات" name="description" class="mb-0">
<BasicTextArea v-model:value="form.description" place-holder="توضیحات اختیاری" />
</a-form-item>
</div>
</a-form>
</template>
<script setup lang="ts">
import type { FormInstance } from 'ant-design-vue'
import type { GiftcardInput } from '@/model/giftCard'
import { inputLenRules, inputRules, inputRulesNumber } from '@/utils/inputRules'
defineProps<{
form: GiftcardInput
}>()
const formRef = ref<FormInstance>()
defineExpose({
validateFields: () => formRef.value?.validateFields(),
resetFields: () => formRef.value?.resetFields()
})
</script>
@@ -0,0 +1,72 @@
<template>
<MobileCardList
v-model:search="search"
:loading="loading"
:has-more="hasMore"
:show-empty="!loading && items.length === 0"
empty-text="کارت هدیه‌ای یافت نشد"
search-placeholder="جستجو کارت هدیه"
show-refresh
@search="reload"
@refresh="reload"
@load-more="loadMore"
>
<GiftCardMobileListItem
v-for="item in items"
:key="item.id"
:item="item"
:hide-customer="!!customerId"
@open="openSheet"
@gift-card-codes="$emit('giftCardCodes', $event)"
/>
</MobileCardList>
<GiftCardMobileDetailSheet v-model:visible="sheetVisible" :item="selectedItem" />
</template>
<script setup lang="ts">
import { getGiftCardTable } from '@/api/giftCard'
import type { Giftcard } from '@/model/giftCard'
import MobileCardList from '@/components/mobile/MobileCardList.vue'
import { useMobileDetailSheet } from '@/components/mobile/composables/useMobileDetailSheet'
import { useMobileTableList } from '@/components/mobile/composables/useMobileTableList'
import type { TableInput } from '@core/type/baseTable'
import GiftCardMobileDetailSheet from './GiftCardMobileDetailSheet.vue'
import GiftCardMobileListItem from './GiftCardMobileListItem.vue'
type GiftCardRow = Giftcard & { customer?: { name?: string; mobile?: string } }
const props = defineProps<{
customerId?: number
}>()
defineEmits<{
(e: 'giftCardCodes', item: GiftCardRow): void
}>()
const { sheetVisible, selectedItem, openSheet, syncSelectedItem } = useMobileDetailSheet<GiftCardRow>()
function fetchGiftCards(params: TableInput) {
if (props.customerId) {
params.filters = params.filters ?? []
params.filters.push({
field: 'customer_id',
type: 'text',
value: props.customerId,
cr: 'and',
op: 'eq',
hidden: false
})
}
return getGiftCardTable(params)
}
const { loading, search, items, hasMore, reload, loadMore, fetch } = useMobileTableList<GiftCardRow>({
fetchApi: fetchGiftCards,
onFetched: (rows) => syncSelectedItem(rows)
})
onMounted(() => reload())
defineExpose({ fetch, openSheet })
</script>
@@ -0,0 +1,122 @@
<template>
<MobileDetailSheet v-model:visible="visibleProxy" :item="item" :title="sheetTitle">
<template #default="{ item: row }">
<MobileSheetHeader :title="sheetTitle" :subtitle="createdAtLabel(row)" :show-title="isMobile" />
<div class="flex items-center gap-2">
<span class="rounded-lg border px-2 py-0.5 text-xs font-600" :class="unitTagClass(row)">
{{ unitLabel(row) }}
</span>
<span
v-if="row.sts_str"
class="rounded-lg border px-2 py-0.5 text-xs font-600"
:class="statusClass(row)"
>
{{ row.sts_str }}
</span>
</div>
<div class="space-y-2">
<MobileStatCard bordered variant="success" :label="amountLabel(row)" :value="amountValue(row)">
<template #prefix>
<BaseIcon
:icon="row.unit === 2 ? 'vuesax-linear:coin' : 'vuesax-linear:empty-wallet'"
size="18"
color="#15803d"
/>
</template>
</MobileStatCard>
<MobileStatCard
v-if="row.unit !== 2"
bordered
label="مبلغ کل (ریال)"
:value="usePrice(row.total ?? 0, true, '')"
>
<template #prefix>
<BaseIcon icon="vuesax-linear:money-send" size="18" color="#2563eb" />
</template>
</MobileStatCard>
</div>
<MobileInfoGrid>
<MobileDetailRow label="کد" :value="row.id ?? '-'" />
<MobileDetailRow label="تعداد کدها" :value="row.count ?? '-'" />
<MobileDetailRow label="تعداد استفاده شده" full-width :value="row.used_count ?? '-'" />
<MobileDetailRow label="کد یکتا" full-width :value="row.unique_code || '-'" />
<MobileDetailRow label="نام مشتری" full-width :value="row.customer?.name || '-'" />
<MobileDetailRow label="موبایل مشتری" full-width :value="row.customer?.mobile ?? '-'" />
<MobileDetailRow label="تاریخ ثبت" full-width :value="createdAtLabel(row)" />
<MobileDetailRow label="تاریخ انقضا" full-width :value="expiryLabel(row)" />
<MobileDetailRow label="توضیحات" full-width :value="row.description || '-'" />
</MobileInfoGrid>
</template>
</MobileDetailSheet>
</template>
<script setup lang="ts">
import type { Giftcard } from '@/model/giftCard'
import MobileDetailRow from '@/components/mobile/MobileDetailRow.vue'
import MobileDetailSheet from '@/components/mobile/MobileDetailSheet.vue'
import MobileInfoGrid from '@/components/mobile/MobileInfoGrid.vue'
import MobileSheetHeader from '@/components/mobile/MobileSheetHeader.vue'
import MobileStatCard from '@/components/mobile/MobileStatCard.vue'
import { isMobile } from '@core/utils/is'
import { toJalali, toJalaliNoTime } from '@core/utils/useDataTime'
import { usePrice } from '@core/utils/usePrice'
type GiftCardRow = Giftcard & { customer?: { name?: string; mobile?: string } }
const props = defineProps<{
visible: boolean
item: GiftCardRow | null
}>()
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
}>()
const visibleProxy = computed({
get: () => props.visible,
set: (value: boolean) => emit('update:visible', value)
})
const sheetTitle = computed(() => props.item?.unique_code || `کارت هدیه #${props.item?.id ?? '-'}`)
function unitLabel(row: GiftCardRow) {
return row.unit === 2 ? 'طلایی' : 'ریالی'
}
function unitTagClass(row: GiftCardRow) {
return row.unit === 2
? 'border-amber-300 bg-amber-50 text-amber-700'
: 'border-green-300 bg-green-50 text-green-700'
}
function statusClass(row: GiftCardRow) {
switch (row.sts) {
case 1:
return 'border-amber-300 bg-amber-50 text-amber-700'
case 2:
return 'border-green-300 bg-green-50 text-green-700'
default:
return 'border-red-300 bg-red-50 text-red-600'
}
}
function amountLabel(row: GiftCardRow) {
return row.unit === 2 ? 'مقدار (گرم)' : 'مقدار (ریال)'
}
function amountValue(row: GiftCardRow) {
if (row.unit === 2) return `${row.amount ?? 0} گرم`
return usePrice(row.amount ?? 0, true, '')
}
function createdAtLabel(row: GiftCardRow) {
return row.created_at ? toJalali(row.created_at) : '-'
}
function expiryLabel(row: GiftCardRow) {
return row.expiry_date ? toJalali(row.expiry_date) : '-'
}
</script>
@@ -0,0 +1,68 @@
<template>
<MobileCard clickable @click="$emit('open', item)">
<div class="flex items-center gap-2">
<div class="min-w-0 flex-1 space-y-1">
<div class="flex items-center justify-between gap-2">
<div class="flex min-w-0 items-center gap-2">
<span class="shrink-0 rounded-md border px-1.5 py-0.5 text-xs font-600" :class="unitTagClass">
{{ unitLabel }}
</span>
<span class="truncate text-sm font-700 text-gray-900">{{ uniqueCodeLabel }}</span>
</div>
<span class="shrink-0 rounded-md border px-1.5 py-0.5 text-xs font-600" :class="statusClass">
{{ statusLabel }}
</span>
</div>
<div v-if="!hideCustomer" class="truncate text-xs text-gray-600">{{ customerName }}</div>
</div>
<MobileIconButton
icon="vuesax-linear:document-text"
color="#1890ff"
:size="20"
@click="$emit('giftCardCodes', item)"
/>
</div>
</MobileCard>
</template>
<script setup lang="ts">
import type { Giftcard } from '@/model/giftCard'
import MobileCard from '@/components/mobile/MobileCard.vue'
import MobileIconButton from '@/components/mobile/MobileIconButton.vue'
type GiftCardRow = Giftcard & { customer?: { name?: string; mobile?: string } }
const props = defineProps<{
item: GiftCardRow
hideCustomer?: boolean
}>()
defineEmits<{
(e: 'open', item: GiftCardRow): void
(e: 'giftCardCodes', item: GiftCardRow): void
}>()
const unitLabel = computed(() => (props.item.unit === 2 ? 'طلایی' : 'ریالی'))
const unitTagClass = computed(() =>
props.item.unit === 2
? 'border-amber-300 bg-amber-50 text-amber-700'
: 'border-green-300 bg-green-50 text-green-700'
)
const uniqueCodeLabel = computed(() => props.item.unique_code || `#${props.item.id ?? '-'}`)
const customerName = computed(() => props.item.customer?.name || '-')
const statusLabel = computed(() => props.item.sts_str || '-')
const statusClass = computed(() => {
switch (props.item.sts) {
case 1:
return 'border-amber-300 bg-amber-50 text-amber-700'
case 2:
return 'border-green-300 bg-green-50 text-green-700'
default:
return 'border-red-300 bg-red-50 text-red-600'
}
})
</script>
@@ -1,7 +1,15 @@
<template>
<div>
<div class="h-full min-h-0 min-w-0 max-w-full overflow-hidden">
<BasicShowContext title="لیست کارت های هدیه">
<BaseTable v-bind="tblProps" @register="submit" ref="tbl">
<template v-if="!customerId" #extra>
<BaseButton type="primary" size="small" class="flex shrink-0 md:hidden" @click="emits('add')">
<div>افزودن</div>
<BaseIcon icon="vuesax-linear:receipt-add" Size="16px" />
</BaseButton>
</template>
<div class="flex h-full min-h-0 flex-col">
<BaseTable v-if="!isMobile" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl">
<template #extraHeader>
<BaseButton v-if="!customerId" type="primary" size="small" class="flex" @click="emits('add')">
<div>افزودن کارت هدیه</div>
@@ -25,6 +33,15 @@
</div>
</template>
</BaseTable>
<GiftCardMobileCardList
v-else
ref="mobileListRef"
class="min-h-0 flex-1"
:customer-id="customerId"
@gift-card-codes="emits('giftcardcodes', $event)"
/>
</div>
</BasicShowContext>
</div>
</template>
@@ -33,81 +50,47 @@
import type { TableInput } from '@core/type/baseTable'
import useBaseTable from '@core/components/BaseTableOld/hooks/useBaseTable'
import Icon from '@core/components/BaseIcon/src/Icon.vue'
import { changeCustomerActivation, getCustomerTable } from '@/api/customer'
import { cols } from '../hooks/orderColumns'
import { useUserStore } from '@/store/userStore'
import { usePrefix } from '@core/composable/usePrefix'
import { message } from 'ant-design-vue'
import { getOrderTable } from '@/api/order'
import {getGiftCardTable} from "@/api/giftCard";
import { getGiftCardTable } from '@/api/giftCard'
import GiftCardMobileCardList from './GiftCardMobileCardList.vue'
import { isMobile } from '@core/utils/is'
interface Prop {
customerId?: number
}
const props = defineProps<Prop>()
const emits = defineEmits(['delete', 'edit', 'add', 'info', 'giftcardcodes'])
const [submit, reload] = useBaseTable()
/*const select = (val) => {
val.sts === 1 ? return ['1'] : ['2']
}*/
//emits
interface Prop {
customerId: number
}
const emits = defineEmits(['delete', 'edit', 'add', 'info', 'giftcardcodes'])
const props = defineProps<Prop>()
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
const { prefixCls } = usePrefix('pophoverstatus')
//permission
function checkPermission(temp: string) {
const store = useUserStore()
return store.canAccess(temp)
}
//define table
const table = ref(null)
const tblProps = ref<any>({
columns: cols,
api: (params: TableInput) => {
if (props.customerId) {
params.filters = []
params.filters?.push({
field: "customer_id",
type: "text",
field: 'customer_id',
type: 'text',
value: props.customerId,
cr: "and",
op: "eq",
cr: 'and',
op: 'eq',
hidden: false
})
}
return getGiftCardTable(params)
}
//bordered: true
})
const loading = ref<boolean>(false)
async function changeCustomerActivationFunction(id: number) {
try {
loading.value = true
await changeCustomerActivation(id)
reload()
message.success('تغییر وضعیت با موفقیت انجام شد')
} catch (e) {
console.error(e)
} finally {
loading.value = false
}
async function fetch() {
await Promise.allSettled([reload(), mobileListRef.value?.fetch?.() as Promise<void>])
}
const fetch = () => {
// console.log('sss')
reload()
}
// const spinTable = (val: boolean) => {
// // console.log('sss')
// table?.value?.loading.value = val
// }
defineExpose({
fetch
// spinTable
})
defineExpose({ fetch })
</script>
<style lang="less">
@@ -122,11 +105,5 @@ defineExpose({
.ant-menu-item-only-child {
border-radius: @border-radius-base;
}
//bor
/* .ant-input-group-addon:first-child {
!* border: #d9d9d9 1px solid;
border-radius: 0 6px 6px 0;*!
}*/
}
</style>
+33 -191
View File
@@ -17,69 +17,24 @@
<!-- <BasicShowContext title="پروژه ها"> <Search /> </BasicShowContext>-->
</div>
</BasicMainCard>
<BasicModal
<BasicActionSheet
v-model:visible="addGiftCardVisible"
width="30rem"
:title="`${editId ? 'ویرایش' : 'افزودن'} کارت هدیه`"
@ok="ok"
:title="giftCardSheetTitle"
modal-width="30rem"
drawer-height="90vh"
:loading="loading"
@close="addGiftCardVisible = false"
:showButton="true"
:showCancel="true"
:showOk="true"
ok-text="ثبت"
cancel-text="انصراف"
@ok="ok"
@close="closeGiftCardSheet"
@cancel="closeGiftCardSheet"
>
<a-form :model="form" ref="formRef" autocomplete="off">
<div class="grid md:grid-cols-1 gap-4">
<a-form-item label="شماره موبایل مشتری" name="mobile" :rules="inputLenRules(11)">
<BasicInput
v-model:value="form.mobile"
type="tel"
placeholder="09123456789"
/>
</a-form-item>
<a-form-item label="نام مشتری" name="name" :rules="inputRules()">
<BasicInput
v-model:value="form.name"
placeholder="نام و نام خانوادگی مشتری"
/>
</a-form-item>
<a-form-item label="نوع کارت هدیه" name="unit" :rules="inputRules()">
<a-radio-group
v-model:value="form.unit"
button-style="solid"
>
<a-radio-button value="1">ریالی</a-radio-button>
<a-radio-button value="2">طلایی</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item label="تعداد کدها" name="count" :rules="inputRulesNumber()">
<BasicInput
v-model:value="form.count"
type="tel"
placeholder="تعداد کدهای کارت هدیه"
/>
</a-form-item>
<a-form-item
:label="`مقدار ${form.unit === '2' ? '(گرم)' : '(ریال)'}`"
name="amount"
:rules="inputRulesNumber()"
>
<BasicInput
v-model:value="form.amount"
:addon-before="form.unit === '2' ? 'گرم' : 'ریال'"
type="tel"
placeholder="مقدار کارت هدیه"
/>
</a-form-item>
<a-form-item label="تاریخ انقضا" name="expiry_date" :rules="inputRules()">
<BaseDatePicker v-model:value="form.expiry_date" format="YYYY-MM-DD" />
</a-form-item>
<a-form-item label="توضیحات" name="description">
<BasicTextArea
v-model:value="form.description"
place-holder="توضیحات اختیاری"
/>
</a-form-item>
</div>
</a-form>
</BasicModal>
<div v-if="isMobile" class="mb-3 text-base font-700">{{ giftCardSheetTitle }}</div>
<GiftCardEditorFormBody ref="giftCardEditorRef" :form="form" />
</BasicActionSheet>
<BasicInfoModal
v-model:visible="userInfoVisible"
@close="userInfoVisible = false"
@@ -90,94 +45,20 @@
:name="infoParams.name"
:items="infoParams.items"
/>
<BasicModal
v-model:visible="giftCardCodesVisible"
width="80%"
title="لیست کدهای کارت هدیه"
custom-footer
@close="giftCardCodesVisible = false"
>
<template #footer>
<a-button @click="giftCardCodesVisible = false">بستن</a-button>
</template>
<a-table
:columns="giftCardCodesColumns"
:data-source="selectedGiftCardCodes"
:pagination="{ pageSize: 10 }"
row-key="id"
>
<template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'index'">
{{ index + 1 }}
</template>
<template v-else-if="column.key === 'sts'">
<CustomTag
v-if="record.sts === 1"
textColor="#ca8a04"
bgColor="#fef3c7"
>
فعال
</CustomTag>
<CustomTag
v-else-if="record.sts === 2"
textColor="#25855A"
bgColor="#E3F4E9"
>
استفاده شده
</CustomTag>
<CustomTag
v-else
textColor="#C53030"
bgColor="#FECACA"
>
غیرفعال
</CustomTag>
</template>
<template v-else-if="column.key === 'unit'">
<CustomTag
v-if="record.unit === 2"
textColor="#C53030"
bgColor="#FECACA"
>
طلایی
</CustomTag>
<CustomTag
v-else
textColor="#25855A"
bgColor="#E3F4E9"
>
ریالی
</CustomTag>
</template>
<template v-else-if="column.key === 'amount'">
{{ record.amount }} {{ record.unit === 2 ? 'گرم' : 'ریال' }}
</template>
<template v-else-if="column.key === 'expiry_date'">
{{ record.expiry_date ? toJalali(record.expiry_date) : '-' }}
</template>
<template v-else-if="column.key === 'used_at'">
{{ record.used_at ? toJalali(record.used_at) : '-' }}
</template>
<template v-else-if="column.key === 'assigned_to_name'">
{{ record.assigned_to?.name ?? '-' }}
</template>
<template v-else-if="column.key === 'assigned_to_mobile'">
{{ record.assigned_to?.mobile ?? '-' }}
</template>
</template>
</a-table>
</BasicModal>
<GiftCardCodesSheet v-model:visible="giftCardCodesVisible" :codes="selectedGiftCardCodes" />
</div>
</template>
<script setup lang="ts">
// import BasicInfoModal from '@/components/BasicInfoModal'
import { inputLenRules, inputRules, inputRulesNumber } from '@/utils/inputRules'
import { useStaticApiListsStore } from '@/store/staticApisStore'
import { ref } from 'vue'
import tbl from './components/table.vue'
import type { FormInstance } from 'ant-design-vue'
import BasicInfoModal from '@/components/BasicInfoModal/index.vue'
import BasicActionSheet from '@/components/BasicActionSheet.vue'
import GiftCardEditorFormBody from './components/GiftCardEditorFormBody.vue'
import GiftCardCodesSheet from './components/GiftCardCodesSheet.vue'
import { message } from 'ant-design-vue/es'
import type { Info } from '@/components/BasicInfoModal/model/Info'
import { toJalaliNoTime, toTime } from '@core/utils/useDataTime'
@@ -188,10 +69,9 @@ import type { Customer } from '@/model/customer'
import { createCustomer, deleteCustomer, getCustomersList, updateCustomer } from '@/api/customer'
import { usePrice } from '@core/utils/usePrice'
import type { Giftcard, Giftcardcode, GiftcardInput } from '@/model/giftCard'
import CustomTag from '@/components/CustomTag.vue'
import { toJalali } from '@core/utils/useDataTime'
import { createGiftCard } from '@/api/giftCard'
import dayjs from 'dayjs'
import { isMobile } from '@core/utils/is'
//loading
const loading = ref<boolean>(false)
@@ -203,6 +83,17 @@ const store = useStaticApiListsStore()
const table = ref(null)
const refreshTable = () => table?.value?.fetch()
const giftCardEditorRef = ref<{
validateFields?: () => ReturnType<NonNullable<FormInstance['validateFields']>>
resetFields?: () => void
}>()
const giftCardSheetTitle = computed(() => `${editId ? 'ویرایش' : 'افزودن'} کارت هدیه`)
function closeGiftCardSheet() {
addGiftCardVisible.value = false
}
//form
const form = ref<GiftcardInput>({
mobile: '',
@@ -213,61 +104,12 @@ const form = ref<GiftcardInput>({
expiry_date: '',
description: ''
})
const formRef = ref<FormInstance>()
//info
let infoParams = reactive<Info>({})
const giftCardCodesVisible = ref<boolean>(false)
const selectedGiftCardCodes = ref<Giftcardcode[]>([])
const giftCardCodesColumns = [
{
title: 'ردیف',
key: 'index',
dataIndex: 'index'
},
{
title: 'کد',
dataIndex: 'code',
key: 'code'
},
{
title: 'نوع',
dataIndex: 'unit',
key: 'unit'
},
{
title: 'مقدار',
dataIndex: 'amount',
key: 'amount'
},
{
title: 'وضعیت',
dataIndex: 'sts',
key: 'sts'
},
{
title: 'تاریخ انقضا',
dataIndex: 'expiry_date',
key: 'expiry_date'
},
{
title: 'تاریخ استفاده',
dataIndex: 'used_at',
key: 'used_at'
},
{
title: 'نام کاربر اختصاص داده شده',
dataIndex: 'assigned_to.name',
key: 'assigned_to_name'
},
{
title: 'شماره موبایل کاربر اختصاص داده شده',
dataIndex: 'assigned_to.mobile',
key: 'assigned_to_mobile'
}
]
function info(val: Giftcard) {
// نمایش اطلاعات اصلی در BasicInfoModal
infoParams.title = 'اطلاعات کارت هدیه'
@@ -361,7 +203,7 @@ async function add() {
//reset data
const resetData = () => {
formRef.value?.resetFields()
giftCardEditorRef.value?.resetFields?.()
form.value = {
mobile: '',
name: '',
@@ -382,7 +224,7 @@ const userInfoVisible = ref<boolean>(false)
//submit gift card
async function ok() {
formRef.value?.validateFields().then(async () => {
await giftCardEditorRef.value?.validateFields?.().then(async () => {
loading.value = true
try {
// تنظیم تاریخ انقضا به آخر همان روز (23:59:59) و تبدیل به میلادی
@@ -0,0 +1,118 @@
<template>
<a-form :model="form" ref="formRef" autocomplete="off">
<div class="space-y-3">
<div class="flex flex-col items-center gap-1 rounded-xl bg-gray-50 px-4 py-3 sm:flex-row sm:justify-center sm:gap-4">
<span class="text-sm text-gray-600">قیمت {{ form.direction === 1 ? 'خرید' : 'فروش' }} طلا:</span>
<span class="text-sm font-700 text-gray-900">{{ marketPriceLabel }}</span>
</div>
<a-form-item label="نوع تراکنش" name="direction" :rules="inputRules()">
<a-radio-group v-model:value="form.direction" button-style="solid" @change="$emit('changePrice')">
<a-radio-button :value="1">خرید</a-radio-button>
<a-radio-button :value="2">فروش</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item
:label="`قیمت ${form.direction === 1 ? 'خرید' : 'فروش'} هر گرم`"
name="price"
:rules="inputRules()"
>
<BasicInput
v-model:value="form.price"
addon-before="ریال"
type="price"
@change="$emit('changePriceValue')"
/>
</a-form-item>
<a-form-item label="تاریخ سفارش" name="pay_at" :rules="inputRules()">
<BaseDatePicker v-model:value="form.pay_at" format="YYYY-MM-DD HH:mm:ss" />
</a-form-item>
<a-form-item label="سود" name="difference" :rules="inputRules()">
<BasicInput v-model:value="form.difference" addon-before="ریال" type="price" />
</a-form-item>
<a-form-item label="کل قیمت لیمان" name="total_price_liman">
<BasicInput v-model:value="form.total_price_liman" addon-before="ریال" type="price" />
</a-form-item>
<div class="space-y-2 rounded-xl bg-orange-50 p-3">
<a-form-item label="انتخاب کاربر" name="customer_id" :rules="inputRules()" class="mb-0">
<BasicSelect
v-model:value="form.customer_id"
:items="userItems"
valueName="name"
keyName="id"
@change="$emit('changeUser', $event)"
/>
</a-form-item>
<BasicInfoItem is-line title="کیف پول طلا" :value="selectedCustomer?.wallet?.[0]?.gold ?? '_'" />
<BasicInfoItem is-line title="کیف پول ریالی" :value="selectedCustomer?.wallet?.[0]?.cash ?? '_'" />
</div>
<div class="space-y-2 rounded-xl bg-blue-50 p-3">
<div class="text-sm font-600 text-gray-700">مقدار {{ form.direction === 1 ? 'خرید' : 'فروش' }}:</div>
<a-form-item name="goldCash" :rules="inputRulesNumber()" class="mb-0">
<BasicInput
v-model:value="form.goldCash"
addon-before="ریال"
addon-bg-color="#0A1B37"
addon-text-color="white"
place-holder="ارزش کل"
type="price"
@onchange="$emit('calculateGold', $event)"
/>
</a-form-item>
<a-form-item name="goldValue" :rules="inputRulesNumber()" class="mb-0">
<BasicInput
v-model:value="form.goldValue"
addon-before="گرم"
addon-bg-color="#0A1B37"
addon-text-color="white"
place-holder="مقدار طلا"
type="tel"
@onchange="$emit('calculateCash', $event)"
/>
</a-form-item>
</div>
</div>
</a-form>
</template>
<script setup lang="ts">
import type { FormInstance } from 'ant-design-vue'
import type { Customer } from '@/model/customer'
import type { Order } from '@/model/order'
import type { last } from '@/model/price'
import { inputRules, inputRulesNumber } from '@/utils/inputRules'
import { usePrice } from '@core/utils/usePrice'
const props = defineProps<{
form: Order
dataLast?: last
userItems?: Customer[]
selectedCustomer?: Customer
}>()
defineEmits<{
(e: 'changePrice'): void
(e: 'changePriceValue'): void
(e: 'changeUser', value: Customer): void
(e: 'calculateGold', value: number): void
(e: 'calculateCash', value: number): void
}>()
const formRef = ref<FormInstance>()
const marketPriceLabel = computed(() => {
const price = props.form.direction === 1 ? props.dataLast?.buy ?? 0 : props.dataLast?.sell ?? 0
return usePrice(price)
})
defineExpose({
validateFields: () => formRef.value?.validateFields(),
resetFields: () => formRef.value?.resetFields()
})
</script>
@@ -0,0 +1,73 @@
<template>
<MobileCardList
v-model:search="search"
:loading="loading"
:has-more="hasMore"
:show-empty="!loading && items.length === 0"
empty-text="سفارشی یافت نشد"
search-placeholder="جستجو سفارش"
show-refresh
@search="reload"
@refresh="reload"
@load-more="loadMore"
>
<OrderMobileListItem
v-for="item in items"
:key="item.id"
:item="item"
@open="openSheet"
/>
</MobileCardList>
<OrderMobileDetailSheet
v-model:visible="sheetVisible"
:item="selectedItem"
/>
</template>
<script setup lang="ts">
import { getOrderTable } from '@/api/order'
import type { Order } from '@/model/order'
import MobileCardList from '@/components/mobile/MobileCardList.vue'
import { useMobileDetailSheet } from '@/components/mobile/composables/useMobileDetailSheet'
import { useMobileTableList } from '@/components/mobile/composables/useMobileTableList'
import type { TableInput } from '@core/type/baseTable'
import OrderMobileDetailSheet from './OrderMobileDetailSheet.vue'
import OrderMobileListItem from './OrderMobileListItem.vue'
type OrderRow = Order & { sts_str?: string; service_charge?: number }
const props = defineProps<{
customerId?: number
}>()
defineEmits<{
(e: 'info', item: OrderRow): void
}>()
const { sheetVisible, selectedItem, openSheet, syncSelectedItem } = useMobileDetailSheet<OrderRow>()
function fetchOrders(params: TableInput) {
if (props.customerId) {
params.filters = params.filters ?? []
params.filters.push({
field: 'customer_id',
type: 'text',
value: props.customerId,
cr: 'and',
op: 'eq',
hidden: false
})
}
return getOrderTable(params)
}
const { loading, search, items, hasMore, reload, loadMore, fetch } = useMobileTableList<OrderRow>({
fetchApi: fetchOrders,
onFetched: (rows) => syncSelectedItem(rows)
})
onMounted(() => reload())
defineExpose({ fetch, openSheet })
</script>
@@ -0,0 +1,122 @@
<template>
<MobileDetailSheet v-model:visible="visibleProxy" :item="item" :title="sheetTitle">
<template #default="{ item: row }">
<MobileSheetHeader :subtitle="createdAtLabel(row)" :show-title="isMobile">
<template #title>
<span class="min-w-0 truncate">{{ sheetTitle }}</span>
</template>
</MobileSheetHeader>
<div class="flex items-center gap-2">
<span class="rounded-lg border px-2 py-0.5 text-xs font-600" :class="directionClass(row)">
{{ directionLabel(row) }}
</span>
<span
v-if="row.sts_str"
class="rounded-lg border px-2 py-0.5 text-xs font-600"
:class="statusClass(row)"
>
{{ row.sts_str }}
</span>
</div>
<div class="space-y-2">
<MobileStatCard bordered variant="success" label="قیمت کل برای مشتری (ریال)" :value="usePrice(row.total_price ?? 0, true, '')">
<template #prefix>
<BaseIcon icon="vuesax-linear:empty-wallet" size="18" color="#15803d" />
</template>
</MobileStatCard>
<MobileStatCard bordered variant="warning" label="مقدار (گرم)" :value="`${row.amount ?? 0} گرم`">
<template #prefix>
<BaseIcon icon="vuesax-linear:coin" size="18" color="#b45309" />
</template>
</MobileStatCard>
</div>
<div class="space-y-2">
<MobileStatCard bordered label="قیمت هر گرم طلا برای مشتری (ریال)" :value="usePrice(row.price ?? 0, true, '')" />
<MobileStatCard bordered label="قیمت هر گرم طلا بدون سود (ریال)" :value="usePrice(row.price_original ?? 0, true, '')" />
<MobileStatCard bordered label="قیمت کل لیمان (ریال)" :value="usePrice(row.total_price_liman ?? 0, true, '')" />
<MobileStatCard bordered label="قیمت کل بدون سود (ریال)" :value="usePrice(row.total_price_original ?? 0, true, '')" />
<MobileStatCard bordered label="سود هر گرم (ریال)" :value="usePrice(row.fee ?? 0, true, '')" />
<MobileStatCard bordered label="کارمزد طلایی" :value="serviceChargeLabel(row)" />
</div>
<MobileInfoGrid>
<MobileDetailRow label="کد سفارش" :value="row.id ?? '-'" />
<MobileDetailRow label="شماره موبایل" :value="row.customer?.mobile || '-'" />
<MobileDetailRow label="سود کل (ریال)" :value="usePrice(row.total_fee ?? 0, true, '')" />
<MobileDetailRow label="کد کاربری" :value="row.customer?.id ?? '-'" />
<MobileDetailRow label="شماره ملی" :value="row.customer?.n_code || '-'" />
<MobileDetailRow label="شماره تلفن" :value="row.customer?.phone_number || '-'" />
<MobileDetailRow label="نام استان" :value="row.customer?.province || '-'" />
<MobileDetailRow label="نام شهر" :value="row.customer?.city || '-'" />
</MobileInfoGrid>
</template>
</MobileDetailSheet>
</template>
<script setup lang="ts">
import type { Order } from '@/model/order'
import MobileDetailRow from '@/components/mobile/MobileDetailRow.vue'
import MobileDetailSheet from '@/components/mobile/MobileDetailSheet.vue'
import MobileInfoGrid from '@/components/mobile/MobileInfoGrid.vue'
import MobileSheetHeader from '@/components/mobile/MobileSheetHeader.vue'
import MobileStatCard from '@/components/mobile/MobileStatCard.vue'
import { isMobile } from '@core/utils/is'
import { toJalali } from '@core/utils/useDataTime'
import { usePrice } from '@core/utils/usePrice'
type OrderRow = Order & { sts_str?: string; service_charge?: number | string }
const props = defineProps<{
visible: boolean
item: OrderRow | null
}>()
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
}>()
const visibleProxy = computed({
get: () => props.visible,
set: (value: boolean) => emit('update:visible', value)
})
const sheetTitle = computed(() => {
if (!props.item) return 'سفارش'
return props.item.customer?.name ? `سفارش ${props.item.customer.name}` : `سفارش #${props.item.id ?? '-'}`
})
function directionLabel(row: OrderRow) {
return row.direction === 2 ? 'فروش' : 'خرید'
}
function directionClass(row: OrderRow) {
return row.direction === 2
? 'border-red-300 bg-red-50 text-red-600'
: 'border-green-300 bg-green-50 text-green-700'
}
function statusClass(row: OrderRow) {
switch (row.sts) {
case 1:
return 'border-amber-300 bg-amber-50 text-amber-700'
case 2:
return 'border-green-300 bg-green-50 text-green-700'
default:
return 'border-red-300 bg-red-50 text-red-600'
}
}
function createdAtLabel(row: OrderRow) {
return row.created_at ? toJalali(row.created_at) : '-'
}
function serviceChargeLabel(row: OrderRow) {
if (row.service_charge == null || row.service_charge === '') return '-'
const value = Number(row.service_charge)
if (Number.isNaN(value)) return `${row.service_charge} گرم`
return `${parseFloat(value.toFixed(5))} گرم`
}
</script>
@@ -0,0 +1,64 @@
<template>
<MobileCard clickable @click="$emit('open', item)">
<div class="space-y-1">
<div class="flex items-center justify-between gap-2">
<div class="flex min-w-0 items-center gap-2">
<span class="shrink-0 rounded-md border px-1.5 py-0.5 text-xs font-600" :class="directionClass">
{{ directionLabel }}
</span>
<span class="truncate text-sm font-700 text-gray-900">{{ customerName }}</span>
</div>
<span class="shrink-0 text-xs font-600" :class="statusValueClass">{{ statusLabel }}</span>
</div>
<div class="flex items-center justify-between gap-2 text-xs text-gray-600">
<span>
<span class="text-gray-500">طلا:</span>
<span class="font-600 text-gray-900">{{ amountLabel }}</span>
</span>
<span class="font-700 text-gray-900">{{ totalPriceLabel }}</span>
</div>
</div>
</MobileCard>
</template>
<script setup lang="ts">
import type { Order } from '@/model/order'
import MobileCard from '@/components/mobile/MobileCard.vue'
import { usePrice } from '@core/utils/usePrice'
const props = defineProps<{
item: Order & { sts_str?: string; service_charge?: number }
}>()
defineEmits<{
(e: 'open', item: Order): void
}>()
const directionLabel = computed(() => (props.item.direction === 2 ? 'فروش' : 'خرید'))
const directionClass = computed(() =>
props.item.direction === 2
? 'border-red-300 bg-red-50 text-red-600'
: 'border-green-300 bg-green-50 text-green-700'
)
const customerName = computed(() => props.item.customer?.name || '-')
const amountLabel = computed(() => {
const amount = props.item.amount
if (amount == null) return '-'
return `${Number(amount.toFixed(3))} گرم`
})
const totalPriceLabel = computed(() => usePrice(props.item.total_price ?? 0, true, ''))
const statusLabel = computed(() => props.item.sts_str || '-')
const statusValueClass = computed(() => {
switch (props.item.sts) {
case 1:
return 'text-amber-700'
case 2:
return 'text-green-700'
default:
return 'text-red-600'
}
})
</script>
+41 -23
View File
@@ -1,24 +1,40 @@
<template>
<div>
<div class="h-full min-h-0 min-w-0 max-w-full overflow-hidden">
<BasicShowContext title="لیست سفارشات">
<BaseTable v-bind="tblProps" @register="submit" ref="tbl">
<template #extraHeader>
<BaseButton v-if="!customerId" type="primary" size="small" class="flex" @click="emits('add')">
<div>افزودن سفارش</div>
<BaseIcon icon="vuesax-linear:receipt-add" Size="16px" />
</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>
</template>
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
</a-tooltip>
</div>
</template>
</BaseTable>
<template v-if="!customerId" #extra>
<BaseButton type="primary" size="small" class="flex shrink-0 md:hidden" @click="emits('add')">
<div>افزودن</div>
<BaseIcon icon="vuesax-linear:receipt-add" Size="16px" />
</BaseButton>
</template>
<div class="flex h-full min-h-0 flex-col">
<BaseTable v-if="!isMobile" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl">
<template #extraHeader>
<BaseButton v-if="!customerId" type="primary" size="small" class="flex" @click="emits('add')">
<div>افزودن سفارش</div>
<BaseIcon icon="vuesax-linear:receipt-add" Size="16px" />
</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>
</template>
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
</a-tooltip>
</div>
</template>
</BaseTable>
<OrderMobileCardList
v-else
ref="mobileListRef"
class="min-h-0 flex-1"
:customer-id="customerId"
@info="emits('info', $event)"
/>
</div>
</BasicShowContext>
</div>
</template>
@@ -33,14 +49,17 @@ import { useUserStore } from '@/store/userStore'
import { usePrefix } from '@core/composable/usePrefix'
import { message } from 'ant-design-vue'
import { getOrderTable } from '@/api/order'
import OrderMobileCardList from './OrderMobileCardList.vue'
import { isMobile } from '@core/utils/is'
const [submit, reload] = useBaseTable()
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
/*const select = (val) => {
val.sts === 1 ? return ['1'] : ['2']
}*/
//emits
interface Prop {
customerId: number
customerId?: number
}
const emits = defineEmits(['delete', 'edit', 'add', 'info'])
const props = defineProps<Prop>()
@@ -89,9 +108,8 @@ async function changeCustomerActivationFunction(id: number) {
}
}
const fetch = () => {
// console.log('sss')
reload()
async function fetch() {
await Promise.allSettled([reload(), mobileListRef.value?.fetch?.() as Promise<void>])
}
// const spinTable = (val: boolean) => {
// // console.log('sss')
+49 -117
View File
@@ -1,10 +1,10 @@
<template>
<div>
<div class="h-full min-h-0">
<BasicMainCard>
<div class="flex w-full h-full">
<div class="flex h-full min-h-0 w-full min-w-0 flex-col overflow-hidden">
<tbl
ref="table"
class="w-full h-full"
class="h-full min-h-0 w-full"
@info="info"
@edit="edit"
@delete="deleteItem"
@@ -16,107 +16,35 @@
<!-- <BasicShowContext title="پروژه ها"> <Search /> </BasicShowContext>-->
</div>
</BasicMainCard>
<BasicModal
<BasicActionSheet
v-model:visible="addOrderVisible"
width="25rem"
:title="`${editId ? 'ویرایش' : 'افزودن'} سفارش`"
@ok="ok"
:title="orderSheetTitle"
modal-width="25rem"
drawer-height="90vh"
:loading="loading"
@close="stopGetPrice"
:showButton="true"
:showCancel="true"
:showOk="true"
ok-text="ثبت"
cancel-text="انصراف"
@ok="ok"
@close="closeAddOrder"
@cancel="closeAddOrder"
>
<a-form :model="form" ref="formRef" autocomplete="off">
<div class="grid md:grid-cols-1 gap-4">
<div class="bg-gray-50 px-10 py-3 flex justify-center gap-4">
<div>قیمت {{ form.direction === 1 ? 'خرید' : 'فروش' }} طلا:</div>
<div class="font-bold">
{{ usePrice(form.direction === 1 ? dataLast?.buy ?? 0 : dataLast?.sell ?? 0) }}
</div>
</div>
<a-form-item label="نوع تراکنش" name="direction" :rules="inputRules()">
<a-radio-group
v-model:value="form.direction"
button-style="solid"
@change="changePrice"
>
<a-radio-button :value="1">خرید</a-radio-button>
<a-radio-button :value="2">فروش</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item
:label="`قیمت ${form.direction === 1 ? 'خرید' : 'فروش'} هر گرم`"
name="price"
:rules="inputRules()"
>
<BasicInput
v-model:value="form.price"
addon-before="ریال"
type="price"
@change="changePriceValue"
/>
</a-form-item>
<a-form-item label="تاریخ سفارش" name="pay_at" :rules="inputRules()">
<BaseDatePicker v-model:value="form.pay_at" format="YYYY-MM-DD HH:mm:ss" />
</a-form-item>
<a-form-item label="سود" name="difference" :rules="inputRules()">
<BasicInput v-model:value="form.difference" addon-before="ریال" type="price" />
</a-form-item>
<a-form-item label="کل قیمت لیمان" name="total_price_liman">
<BasicInput v-model:value="form.total_price_liman" addon-before="ریال" type="price" />
</a-form-item>
<div class="p-2 bg-orange-50 rounded-lg">
<a-form-item label="انتخاب کاربر" name="customer_id" :rules="inputRules()">
<BasicSelect
v-model:value="form.customer_id"
:items="userItems"
valueName="name"
keyName="id"
@change="changeUser"
/>
</a-form-item>
<BasicInfoItem
is-line
title="کیف پول طلا"
class="mt-2"
:value="selectedCustomer?.wallet[0]?.gold ?? '_'"
/>
<BasicInfoItem
is-line
title="کیف پول ریالی"
class="mt-1"
:value="selectedCustomer?.wallet[0]?.cash ?? '_'"
/>
</div>
<div class="flex flex-col gap-2 bg-blue-50 p-2 rounded-lg">
<div>مقدار {{ form.direction === 1 ? 'خرید' : 'فروش' }}:</div>
<a-form-item name="goldCash" :rules="inputRulesNumber()">
<BasicInput
v-model:value="form.goldCash"
addon-before="ریال"
addon-bg-color="#0A1B37"
addon-text-color="white"
place-holder="ارزش کل"
type="price"
@onchange="calculateGold"
/>
</a-form-item>
<a-form-item name="goldValue" :rules="inputRulesNumber()">
<BasicInput
v-model:value="form.goldValue"
addon-before="گرم"
addon-bg-color="#0A1B37"
addon-text-color="white"
place-holder="مقدار طلا"
type="tel"
@onchange="calculateCash"
/>
</a-form-item>
</div>
<!-- <a-form-item label="نام چاپی" name="print_name" :rules="inputRules()">
<BasicInput v-model:value="form.print_name" />
</a-form-item>-->
</div>
</a-form>
</BasicModal>
<div v-if="isMobile" class="mb-3 text-base font-700">{{ orderSheetTitle }}</div>
<OrderEditorFormBody
ref="orderEditorRef"
:form="form"
:data-last="dataLast"
:user-items="userItems"
:selected-customer="selectedCustomer"
@change-price="changePrice"
@change-price-value="changePriceValue"
@change-user="changeUser"
@calculate-gold="calculateGold"
@calculate-cash="calculateCash"
/>
</BasicActionSheet>
<BasicInfoModal
v-model:visible="userInfoVisible"
@close="userInfoVisible = false"
@@ -132,25 +60,26 @@
<script setup lang="ts">
// import BasicInfoModal from '@/components/BasicInfoModal'
import { inputLenRules, inputRules, inputRulesNumber } from '@/utils/inputRules'
import { useStaticApiListsStore } from '@/store/staticApisStore'
import { ref } from 'vue'
import tbl from './components/table.vue'
import type { FormInstance } from 'ant-design-vue'
import BasicInfoModal from '@/components/BasicInfoModal/index.vue'
import BasicActionSheet from '@/components/BasicActionSheet.vue'
import OrderEditorFormBody from './components/OrderEditorFormBody.vue'
import { message } from 'ant-design-vue/es'
import type { Info } from '@/components/BasicInfoModal/model/Info'
import { toJalaliNoTime, toTime } from '@core/utils/useDataTime'
import { confirmDeleteItem } from '@/utils/confirmModal'
import { cloneDeep } from 'lodash'
import type { Item } from '@/model/Item'
import type { Customer } from '@/model/customer'
import { createCustomer, deleteCustomer, getCustomersList, updateCustomer } from '@/api/customer'
import { deleteCustomer, getCustomersList } from '@/api/customer'
import type { Order } from '@/model/order'
import { usePrice } from '@core/utils/usePrice'
import { getLastPrice } from '@/api/price'
import type { last } from '@/model/price'
import { createOrder } from '@/api/order'
import { isMobile } from '@core/utils/is'
//loading
const loading = ref<boolean>(false)
@@ -191,9 +120,18 @@ const form = ref<Order>({
goldCash: undefined,
goldValue: undefined
})
const formRef = ref<FormInstance>()
const orderEditorRef = ref<{
validateFields?: () => ReturnType<NonNullable<FormInstance['validateFields']>>
resetFields?: () => void
}>()
const dataLast = ref<last>()
let interval: any
let interval: ReturnType<typeof setInterval> | undefined
const orderSheetTitle = computed(() => `${editId ? 'ویرایش' : 'افزودن'} سفارش`)
function closeAddOrder() {
stopGetPrice()
}
async function setIntervalGetPrice() {
try {
@@ -226,12 +164,11 @@ async function getPrices() {
async function changeUser(val: Customer) {
selectedCustomer.value = cloneDeep(val)
console.log(selectedCustomer.value.wallet[0])
}
function stopGetPrice() {
clearInterval(interval)
console.log('stop')
if (interval) clearInterval(interval)
interval = undefined
}
//info
@@ -384,7 +321,7 @@ async function add() {
//reset data
const resetData = () => {
formRef.value?.resetFields()
orderEditorRef.value?.resetFields?.()
form.value = {
direction: 1,
price: 0,
@@ -415,9 +352,7 @@ const userInfoVisible = ref<boolean>(false)
//submit user
async function ok() {
formRef.value?.validateFields().then(async () => {
// console.log('salam', form.value)
// return
await orderEditorRef.value?.validateFields?.().then(async () => {
loading.value = true
try {
form.value.amount = form.value.goldValue
@@ -427,14 +362,11 @@ async function ok() {
await createOrder(form.value)
message.success('سفارش با موفقیت ثبت شد')
addOrderVisible.value = false
closeAddOrder()
resetData()
refreshTable()
// await getResponsible()
// await getQC()
// await getManager()
} catch (e) {
console.error(e)
// message.error('خطا در بارگذاری اطلاعات')
} finally {
loading.value = false
}
@@ -0,0 +1,39 @@
<template>
<MobileCardList
v-model:search="search"
:loading="loading"
:has-more="hasMore"
:show-empty="!loading && items.length === 0"
empty-text="قیمتی یافت نشد"
search-placeholder="جستجو تاریخچه قیمت"
show-refresh
@search="reload"
@refresh="reload"
@load-more="loadMore"
>
<PriceMobileListItem v-for="item in items" :key="item.id" :item="item" @open="openSheet" />
</MobileCardList>
<PriceMobileDetailSheet v-model:visible="sheetVisible" :item="selectedItem" />
</template>
<script setup lang="ts">
import { getPriceTable } from '@/api/price'
import type { Price } from '@/model/price'
import MobileCardList from '@/components/mobile/MobileCardList.vue'
import { useMobileDetailSheet } from '@/components/mobile/composables/useMobileDetailSheet'
import { useMobileTableList } from '@/components/mobile/composables/useMobileTableList'
import PriceMobileDetailSheet from './PriceMobileDetailSheet.vue'
import PriceMobileListItem from './PriceMobileListItem.vue'
const { sheetVisible, selectedItem, openSheet, syncSelectedItem } = useMobileDetailSheet<Price>()
const { loading, search, items, hasMore, reload, loadMore, fetch } = useMobileTableList<Price>({
fetchApi: getPriceTable,
onFetched: (rows) => syncSelectedItem(rows)
})
onMounted(() => reload())
defineExpose({ fetch, openSheet })
</script>
@@ -0,0 +1,63 @@
<template>
<MobileDetailSheet v-model:visible="visibleProxy" :item="item" :title="sheetTitle">
<template #default="{ item: row }">
<MobileSheetHeader :title="sheetTitle" :subtitle="dateTimeLabel(row)" :show-title="isMobile" />
<div class="space-y-2">
<MobileStatCard bordered variant="success" label="قیمت هر گرم طلا برای مشتری (ریال)" :value="usePrice(row.price ?? 0, true, '')">
<template #prefix>
<BaseIcon icon="vuesax-linear:empty-wallet" size="18" color="#15803d" />
</template>
</MobileStatCard>
</div>
<MobileInfoGrid>
<MobileDetailRow label="کد قیمت" :value="row.id ?? '-'" />
<MobileDetailRow label="کد کاربری" :value="row.user_id ?? '-'" />
<MobileDetailRow label="نام و نام خانوادگی" full-width :value="row.user?.name || '-'" />
<MobileDetailRow label="تاریخ ثبت" :value="dateLabel(row)" />
<MobileDetailRow label="زمان ثبت" :value="row.time_at || '-'" />
</MobileInfoGrid>
</template>
</MobileDetailSheet>
</template>
<script setup lang="ts">
import type { Price } from '@/model/price'
import MobileDetailRow from '@/components/mobile/MobileDetailRow.vue'
import MobileDetailSheet from '@/components/mobile/MobileDetailSheet.vue'
import MobileInfoGrid from '@/components/mobile/MobileInfoGrid.vue'
import MobileSheetHeader from '@/components/mobile/MobileSheetHeader.vue'
import MobileStatCard from '@/components/mobile/MobileStatCard.vue'
import { isMobile } from '@core/utils/is'
import { toJalaliNoTime } from '@core/utils/useDataTime'
import { usePrice } from '@core/utils/usePrice'
const props = defineProps<{
visible: boolean
item: Price | null
}>()
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
}>()
const visibleProxy = computed({
get: () => props.visible,
set: (value: boolean) => emit('update:visible', value)
})
const sheetTitle = computed(() => props.item?.user?.name || `قیمت #${props.item?.id ?? '-'}`)
function dateLabel(row: Price) {
return row.created_at ? toJalaliNoTime(row.created_at) : '-'
}
function dateTimeLabel(row: Price) {
const date = dateLabel(row)
const time = row.time_at
if (date === '-' && !time) return '-'
if (!time) return date
return `${date} · ${time}`
}
</script>
@@ -0,0 +1,34 @@
<template>
<MobileCard clickable @click="$emit('open', item)">
<div class="space-y-1">
<div class="flex items-center justify-between gap-2">
<span class="truncate text-sm font-700 text-gray-900">{{ userName }}</span>
<span class="shrink-0 text-xs text-gray-400">{{ dateLabel }}</span>
</div>
<div class="flex items-center justify-between gap-2 text-xs">
<span class="truncate text-gray-600">#{{ item.id ?? '-' }}</span>
<span class="shrink-0 font-700 text-green-700">{{ priceLabel }}</span>
</div>
</div>
</MobileCard>
</template>
<script setup lang="ts">
import type { Price } from '@/model/price'
import MobileCard from '@/components/mobile/MobileCard.vue'
import { toJalaliNoTime } from '@core/utils/useDataTime'
import { usePrice } from '@core/utils/usePrice'
const props = defineProps<{
item: Price
}>()
defineEmits<{
(e: 'open', item: Price): void
}>()
const userName = computed(() => props.item.user?.name || '-')
const dateLabel = computed(() => (props.item.created_at ? toJalaliNoTime(props.item.created_at) : '-'))
const priceLabel = computed(() => usePrice(props.item.price ?? 0, true, ''))
</script>
@@ -1,7 +1,15 @@
<template>
<div>
<div class="h-full min-h-0 min-w-0 max-w-full overflow-hidden">
<BasicShowContext title="تاریخچه قیمت">
<BaseTable v-bind="tblProps" @register="submit" ref="tbl">
<template #extra>
<BaseButton type="primary" size="small" class="flex shrink-0 md:hidden" @click="emits('add')">
<div>قیمت گذاری</div>
<BaseIcon icon="vuesax-linear:dollar-circle" Size="16px" />
</BaseButton>
</template>
<div class="flex h-full min-h-0 flex-col">
<BaseTable v-if="!isMobile" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl">
<template #extraHeader>
<BaseButton type="primary" size="small" class="flex" @click="emits('add')">
<div>قیمت گذاری</div>
@@ -19,6 +27,9 @@
</div>
</template>
</BaseTable>
<PriceMobileCardList v-else ref="mobileListRef" class="min-h-0 flex-1" />
</div>
</BasicShowContext>
</div>
</template>
@@ -27,63 +38,32 @@
import type { TableInput } from '@core/type/baseTable'
import useBaseTable from '@core/components/BaseTableOld/hooks/useBaseTable'
import Icon from '@core/components/BaseIcon/src/Icon.vue'
import { changeCustomerActivation, getCustomerTable } from '@/api/customer'
import { cols } from '../hooks/priceColumns'
import { useUserStore } from '@/store/userStore'
import { usePrefix } from '@core/composable/usePrefix'
import { message } from 'ant-design-vue'
import { getOrderTable } from '@/api/order'
import { getPriceTable } from '@/api/price'
import PriceMobileCardList from './PriceMobileCardList.vue'
import { isMobile } from '@core/utils/is'
const [submit, reload] = useBaseTable()
/*const select = (val) => {
val.sts === 1 ? return ['1'] : ['2']
}*/
//emits
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
const emits = defineEmits(['delete', 'edit', 'add', 'info'])
const { prefixCls } = usePrefix('pophoverstatus')
//permission
function checkPermission(temp: string) {
const store = useUserStore()
return store.canAccess(temp)
}
//define table
const table = ref(null)
const tblProps = ref<any>({
columns: cols,
api: (params: TableInput) => {
return getPriceTable(params)
}
//bordered: true
})
const loading = ref<boolean>(false)
async function changeCustomerActivationFunction(id: number) {
try {
loading.value = true
await changeCustomerActivation(id)
reload()
message.success('تغییر وضعیت با موفقیت انجام شد')
} catch (e) {
console.error(e)
} finally {
loading.value = false
}
async function fetch() {
await Promise.allSettled([reload(), mobileListRef.value?.fetch?.() as Promise<void>])
}
const fetch = () => {
// console.log('sss')
reload()
}
// const spinTable = (val: boolean) => {
// // console.log('sss')
// table?.value?.loading.value = val
// }
defineExpose({
fetch
// spinTable
})
</script>
@@ -98,10 +78,5 @@ defineExpose({
.ant-menu-item-only-child {
border-radius: @border-radius-base;
}
//bor
/* .ant-input-group-addon:first-child {
!* border: #d9d9d9 1px solid;
border-radius: 0 6px 6px 0;*!
}*/
}
</style>
@@ -0,0 +1,43 @@
<template>
<MobileCardList
v-model:search="search"
:loading="loading"
:has-more="hasMore"
:show-empty="!loading && items.length === 0"
empty-text="محصولی یافت نشد"
search-placeholder="جستجو محصول"
show-refresh
@search="reload"
@refresh="reload"
@load-more="loadMore"
>
<ArchiveMobileListItem
v-for="item in items"
:key="item.id"
:item="item"
@info="$emit('info', $event)"
@unarchive="$emit('unarchive', $event)"
/>
</MobileCardList>
</template>
<script setup lang="ts">
import { getProductArchiveTable } from '@/api/product'
import type { Product } from '@/model/product'
import MobileCardList from '@/components/mobile/MobileCardList.vue'
import { useMobileTableList } from '@/components/mobile/composables/useMobileTableList'
import ArchiveMobileListItem from './ArchiveMobileListItem.vue'
defineEmits<{
(e: 'info', item: Product): void
(e: 'unarchive', item: Product): void
}>()
const { loading, search, items, hasMore, reload, loadMore, fetch } = useMobileTableList<Product>({
fetchApi: getProductArchiveTable
})
onMounted(() => reload())
defineExpose({ fetch })
</script>
@@ -0,0 +1,44 @@
<template>
<MobileCard>
<div class="flex items-center justify-between gap-2">
<div class="flex min-w-0 items-center gap-2">
<span class="inline-block h-2.5 w-2.5 rounded-full" :class="item.active ? 'bg-green-500' : 'bg-red-400'" />
<span class="truncate text-sm font-700 text-gray-900">{{ item.title || '-' }}</span>
</div>
<div class="flex items-center gap-2">
<MobileIconButton icon="vuesax-linear:archive-slash" color="#e3bf00" @click="$emit('unarchive', item)" />
<MobileIconButton icon="vuesax-linear:stickynote" color="#10913A" @click="$emit('info', item)" />
</div>
</div>
<div class="mt-3 flex items-center justify-between gap-2">
<span class="truncate text-xs text-gray-500">قیمت: {{ usePrice(item.price || 0) }}</span>
<span class="text-xs font-700" :class="item.open_buy ? 'text-green-700' : 'text-red-600'">
{{ item.open_buy ? 'خرید: باز' : 'خرید: بسته' }}
</span>
</div>
<div class="mt-2 flex items-center justify-between">
<span class="text-xs text-gray-500">کد محصول: {{ item.id ?? '-' }}</span>
<span class="text-xs font-700" :class="item.open_sell ? 'text-green-700' : 'text-red-600'">
{{ item.open_sell ? 'فروش: باز' : 'فروش: بسته' }}
</span>
</div>
</MobileCard>
</template>
<script setup lang="ts">
import type { Product } from '@/model/product'
import MobileCard from '@/components/mobile/MobileCard.vue'
import MobileIconButton from '@/components/mobile/MobileIconButton.vue'
import { usePrice } from '@core/utils/usePrice'
defineProps<{
item: Product
}>()
defineEmits<{
(e: 'info', item: Product): void
(e: 'unarchive', item: Product): void
}>()
</script>
@@ -1,7 +1,8 @@
<template>
<div>
<div class="h-full min-h-0 min-w-0 max-w-full overflow-hidden">
<BasicShowContext title="لیست محصولات بایگانی شده">
<BaseTable v-bind="tblProps" @register="submit" ref="tbl">
<div class="flex h-full min-h-0 flex-col">
<BaseTable v-if="!isMobile" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl">
<!-- <template #extraHeader>
<BaseButton type="primary" size="small" class="flex" @click="emits('add')">
<div>افزودن مشتری عمده</div>
@@ -46,6 +47,14 @@
</div>
</template>
</BaseTable>
<ArchiveMobileCardList
v-else
ref="mobileListRef"
class="min-h-0 flex-1"
@info="emits('info', $event)"
@unarchive="archiveFunction($event.id)"
/>
</div>
</BasicShowContext>
</div>
</template>
@@ -65,6 +74,9 @@ import { cols } from '../hooks/archiveColumns'
import { useUserStore } from '@/store/userStore'
import { usePrefix } from '@core/composable/usePrefix'
import { message } from 'ant-design-vue'
import ArchiveMobileCardList from './ArchiveMobileCardList.vue'
import { getProductArchiveTable } from '@/api/product'
import { isMobile } from '@core/utils/is'
const [submit, reload] = useBaseTable()
/*const select = (val) => {
@@ -87,7 +99,7 @@ const table = ref(null)
const tblProps = ref<any>({
columns: cols,
api: (params: TableInput) => {
return getCustomerArchiveTable(params)
return getProductArchiveTable(params)
}
//bordered: true
})
@@ -122,7 +134,7 @@ async function archiveFunction(id: number) {
try {
loading.value = true
await changeCustomerArchive(id)
reload()
await Promise.allSettled([reload(), mobileListRef.value?.fetch?.() as Promise<void>])
message.success('مشتری با موفقیت از بایگانی خارج شد.')
} catch (e) {
console.error(e)
@@ -130,9 +142,10 @@ async function archiveFunction(id: number) {
loading.value = false
}
}
const fetch = () => {
// console.log('sss')
reload()
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
const fetch = async () => {
await Promise.allSettled([reload(), mobileListRef.value?.fetch?.() as Promise<void>])
}
// const spinTable = (val: boolean) => {
// // console.log('sss')
@@ -1,71 +0,0 @@
<template>
<CollapsePanel :key="activeKey">
<template #header>
<div class="w-full">
<div class="flex justify-between items-center mb-3">
<span>{{ props.title }}</span>
<BaseIcon :class="[{ rotate: props.actives.includes(props.activeKey) }]" icon="vuesax-linear:arrow-left-1" size="17" />
</div>
<div :class="[{ open: props.actives.includes(props.activeKey) }]"></div>
</div>
</template>
<slot name="content"></slot>
</CollapsePanel>
</template>
<script setup lang="ts">
import {CollapsePanel} from "ant-design-vue";
interface Props {
title: string,
activeKey: string | number,
actives: string[] | number[]
}
const props = withDefaults(defineProps<Props>(), {});
</script>
<style lang="less">
.open {
width: 100%;
height: 1px;
background-color: #E5E7EB;
}
.rotate {
transform: rotate(-90deg);
transition: all 0.3s;
}
.ant-collapse > .ant-collapse-item > .ant-collapse-header {
direction: rtl !important;
justify-content: space-between !important;
background-color: #fff !important;
font-weight: bold;
}
.ant-collapse{
border: none !important;
}
.ant-collapse > .ant-collapse-item {
border-bottom: none !important;
}
.ant-collapse-content {
border-top: none;
}
.ant-collapse > .ant-collapse-item > .ant-collapse-header .ant-collapse-arrow {
display: none !important;
}
.ant-collapse > .ant-collapse-item > .ant-collapse-header {
padding: 12px 16px 0 !important;
}
</style>
@@ -0,0 +1,137 @@
<template>
<Collapse class="grid grid-cols-1 gap-2" :activeKey="activeKey" @update:activeKey="$emit('update:activeKey', $event)">
<BaseCollapse title="اطلاعات محصول" active-key="0" :actives="activeKey">
<template #content>
<div
v-if="form.group_type === '' || form.group_type !== 'آبشده'"
class="md:flex justify-between border-1 border-solid rounded-lg border-gray-200 p-2"
>
<div class="items-center">
<span class="ml-4">دریافت قیمت از :</span>
<a-radio-group :value="methodOption" name="radioGroup" @update:value="$emit('update:methodOption', $event)">
<a-radio class="items-center" value="1">دستی</a-radio>
<a-radio value="2">اتوماتیک</a-radio>
</a-radio-group>
</div>
<div>
<div v-if="methodOption === '1'" class="md:flex items-center justify-between md:w-[25rem]">
<div>قیمت وسط :</div>
<BasicInput class="mt-1 md:mt-unset" addon-before="ریال" type="price" v-model:value="form.price" />
<AButton class="border-green text-green mt-1 md:mt-unset rounded-full aspect-square" size="small" shape="circle" @click="$emit('addPrice')">
<template #icon><Icon icon="vuesax-linear:add" size="20" /></template>
</AButton>
<AButton class="border-red text-red mr-1 md:mr-unset rounded-full" size="small" shape="circle" @click="$emit('minusPrice')">
<template #icon><Icon icon="vuesax-linear:minus" size="20" /></template>
</AButton>
</div>
<div v-if="methodOption === '2'" class="md:flex justify-between items-center">
<div class="ml-4">قیمت :</div>
<BasicSelect class="w-11rem method_style" allowClear :items="pricesList" valueName="name" keyName="id" v-model:value="form.reading_method" />
</div>
</div>
</div>
<div
v-if="form.group_type === '' || form.group_type !== 'آبشده'"
class="md:flex justify-between p-2 border-1 border-solid rounded-lg border-gray-200 mt-2"
>
<div>
<span class="ml-4">وزن :</span>
<a-radio-group :value="weightOption" name="radioGroup" @update:value="$emit('update:weightOption', $event)">
<a-radio value="1">دارد</a-radio>
<a-radio value="2">ندارد</a-radio>
</a-radio-group>
</div>
<div v-if="weightOption === '1'" class="flex items-center">
<span class="ml-4 ">وزن به گرم :</span>
<BasicInput v-model:value="form.amount" type="number" />
<div class="mt-1 mr-4" v-if="form.amount && form.amount < 1 && 0 < form.amount">معادل {{ form.amount * 1000 }} سوت</div>
</div>
</div>
<a-form :model="form" ref="formRef" autocomplete="off">
<div class="grid md:grid-cols-2 gap-4">
<a-form-item label="عنوان" name="title" :rules="inputRules()"><BasicInput v-model:value="form.title" /></a-form-item>
<a-form-item v-if="getConfigApp('name') === 'garoosi'" label="دسته بندی" name="group_type" :rules="inputRules()">
<BasicSelect v-model:value="form.group_type" :items="garoosiCategoryItems" valueName="value" keyName="value" />
</a-form-item>
<a-form-item v-else label="دسته بندی" name="group_type" :rules="inputRules()">
<BasicSelect v-model:value="form.group_type" :items="categoryItems" valueName="value" keyName="value" />
</a-form-item>
<a-form-item label="جنس فلز" name="metal_id" :rules="inputRules()">
<BasicSelect v-model:value="form.metal_id" :items="metalItems" valueName="value" keyName="key" />
</a-form-item>
<a-form-item label="نوع قیمت" name="direct_price" :rules="inputRules()">
<BasicSelect v-model:value="form.direct_price" :items="priceItems" valueName="value" keyName="key" />
</a-form-item>
<a-form-item label="واحد" name="unit" :rules="inputRules()">
<BasicSelect v-model:value="form.unit" :items="unitItems" valueName="value" keyName="value" />
</a-form-item>
<a-form-item v-if="form.group_type === '' || form.group_type !== 'آبشده'" label="هزینه نقدی موقع خرید" name="cash_fee_buy" :rules="inputRules()">
<BasicInput v-model:value="form.cash_fee_buy" type="number" />
</a-form-item>
<a-form-item v-if="form.group_type === '' || form.group_type !== 'آبشده'" label="هزینه نقدی موقع فروش" name="cash_fee_sell" :rules="inputRules()">
<BasicInput v-model:value="form.cash_fee_sell" type="number" />
</a-form-item>
<a-form-item label="زمان شروع فعال سازی" name="auto_open_at">
<TimePicker v-model:value="form.auto_open_at" :locale="customLocale" placeholder="انتخاب زمان" :defaultValue="dayjs('00:00:00', 'HH:mm:ss')" />
</a-form-item>
<a-form-item label="زمان پایان فعال سازی" name="auto_close_at">
<TimePicker v-model:value="form.auto_close_at" :locale="customLocale" placeholder="انتخاب زمان" :defaultValue="dayjs('00:00:00', 'HH:mm:ss')" />
</a-form-item>
</div>
</a-form>
</template>
</BaseCollapse>
<BaseCollapse title="تصاویر محصول" active-key="1" :actives="activeKey">
<template #content>
<IndexImageUploader
:key="editId ?? 'new-product'"
:file-list="productIndexImage"
@update:file-list="$emit('update:productIndexImage', $event)"
@ok="(id, res) => $emit('uploadedIndexFile', id, res)"
@delete="$emit('deleteIndexFile')"
/>
</template>
</BaseCollapse>
</Collapse>
</template>
<script setup lang="ts">
import { Collapse, TimePicker } from 'ant-design-vue'
import { inputRules } from '@/utils/inputRules'
import Icon from '@core/components/BaseIcon/src/Icon.vue'
import BaseCollapse from '@/components/BaseCollapse.vue'
import IndexImageUploader from '@/components/ProductUploader/IndexImageUploader.vue'
import { getConfigApp } from '@/utils/config'
import dayjs from 'dayjs'
defineProps<{
form: any
formRef: any
methodOption: string
weightOption: string
activeKey: string[]
pricesList: Array<{ id: string; name: string }>
garoosiCategoryItems: any[]
categoryItems: any[]
metalItems: any[]
priceItems: any[]
unitItems: any[]
customLocale: any
productIndexImage: any[]
editId?: number
}>()
defineEmits<{
(e: 'addPrice'): void
(e: 'minusPrice'): void
(e: 'update:methodOption', value: string): void
(e: 'update:weightOption', value: string): void
(e: 'update:activeKey', value: string[]): void
(e: 'update:productIndexImage', value: any[]): void
(e: 'uploadedIndexFile', id: number, response?: Record<string, any>): void
(e: 'deleteIndexFile'): void
}>()
</script>
@@ -0,0 +1,57 @@
<template>
<MobileCardList
v-model:search="search"
:loading="loading"
:has-more="hasMore"
search-placeholder="جستجو محصول"
show-refresh
@search="reload"
@refresh="reload"
@load-more="loadMore"
>
<ProductMobileListItem
v-for="item in items"
:key="item.id"
:item="item"
@open="openSheet"
@edit="$emit('edit', $event)"
/>
</MobileCardList>
<ProductMobileDetailSheet
v-model:visible="sheetVisible"
:item="selectedItem"
@toggleBuy="$emit('toggleBuy', $event)"
@toggleSell="$emit('toggleSell', $event)"
@toggleActive="$emit('toggleActive', $event)"
@edit="$emit('edit', $event)"
/>
</template>
<script setup lang="ts">
import { getCustomerTable } from '@/api/product'
import type { Product } from '@/model/product'
import MobileCardList from '@/components/mobile/MobileCardList.vue'
import { useMobileDetailSheet } from '@/components/mobile/composables/useMobileDetailSheet'
import { useMobileTableList } from '@/components/mobile/composables/useMobileTableList'
import ProductMobileListItem from './ProductMobileListItem.vue'
import ProductMobileDetailSheet from './ProductMobileDetailSheet.vue'
defineEmits<{
(e: 'edit', item: Product): void
(e: 'toggleBuy', item: Product): void
(e: 'toggleSell', item: Product): void
(e: 'toggleActive', item: Product): void
}>()
const { sheetVisible, selectedItem, openSheet, syncSelectedItem } = useMobileDetailSheet<Product>()
const { loading, search, items, hasMore, reload, loadMore, fetch } = useMobileTableList<Product>({
fetchApi: getCustomerTable,
onFetched: (rows) => syncSelectedItem(rows)
})
onMounted(() => reload())
defineExpose({ fetch })
</script>
@@ -0,0 +1,109 @@
<template>
<MobileDetailSheet v-model:visible="visibleProxy" :item="item" :title="item?.title || ''">
<template #default="{ item: row }">
<MobileSheetHeader :subtitle="updatedAtLabel(row)" :show-title="isMobile">
<template #title>
<div class="flex w-full items-center justify-between gap-3">
<span class="min-w-0 truncate">{{ row.title || '-' }}</span>
<div class="flex shrink-0 items-center gap-2">
<MobileIconButton
plain
:key="`buy-${row.id}-${row.open_buy}`"
icon="vuesax-linear:money-receive"
:color="row.open_buy ? '#16A34A' : '#DC2626'"
:size="22"
@click="$emit('toggleBuy', row)"
/>
<MobileIconButton
plain
:key="`sell-${row.id}-${row.open_sell}`"
icon="vuesax-linear:money-send"
:color="row.open_sell ? '#16A34A' : '#DC2626'"
:size="22"
@click="$emit('toggleSell', row)"
/>
<MobileIconButton
plain
:key="`active-${row.id}-${row.active}`"
:icon="row.active ? 'vuesax-linear:tick-circle' : 'vuesax-linear:close-circle'"
:color="row.active ? '#16A34A' : '#DC2626'"
:size="22"
@click="$emit('toggleActive', row)"
/>
</div>
</div>
</template>
</MobileSheetHeader>
<MobileStatCard variant="success" bordered label="قیمت محصول" :value="usePrice(row.price || 0)">
<template #prefix>
<BaseIcon icon="vuesax-linear:money-send" size="18" color="#15803d" />
</template>
</MobileStatCard>
<MobileStatCard variant="danger" bordered label="کارمزد نقدی" :value="cashFeeLabel(row)">
<template #prefix>
<BaseIcon icon="vuesax-linear:empty-wallet" size="18" color="#dc2626" />
</template>
</MobileStatCard>
<div class="flex items-center justify-between rounded-xl border border-gray-200 bg-white px-4 py-3">
<div class="flex items-center">
<BaseIcon icon="vuesax-linear:calendar" size="18" color="#2563eb" />
<span class="mr-2 font-700 text-primary">درصد کارمزد</span>
</div>
<div class="text-2xl font-800">
<span class="text-gray-900">{{ row.percent_fee_buy || 0 }}</span>
<span class="mx-1 text-green-600">=</span>
<span class="text-green-600">{{ row.percent_fee_sell || 0 }}</span>
</div>
</div>
<MobileInfoGrid>
<MobileDetailRow label="کد محصول" :value="row.id ?? '-'" />
<MobileDetailRow label="دسته بندی محصول" :value="row.group_type || '-'" />
<MobileDetailRow label="واحد" :value="row.unit || '-'" />
<MobileDetailRow label="وزن" :value="row.amount ?? '-'" />
</MobileInfoGrid>
</template>
</MobileDetailSheet>
</template>
<script setup lang="ts">
import type { Product } from '@/model/product'
import MobileDetailRow from '@/components/mobile/MobileDetailRow.vue'
import MobileDetailSheet from '@/components/mobile/MobileDetailSheet.vue'
import MobileIconButton from '@/components/mobile/MobileIconButton.vue'
import MobileInfoGrid from '@/components/mobile/MobileInfoGrid.vue'
import MobileSheetHeader from '@/components/mobile/MobileSheetHeader.vue'
import MobileStatCard from '@/components/mobile/MobileStatCard.vue'
import { isMobile } from '@core/utils/is'
import { toJalali } from '@core/utils/useDataTime'
import { usePrice } from '@core/utils/usePrice'
const props = defineProps<{
visible: boolean
item: Product | null
}>()
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
(e: 'toggleBuy', item: Product): void
(e: 'toggleSell', item: Product): void
(e: 'toggleActive', item: Product): void
(e: 'edit', item: Product): void
}>()
const visibleProxy = computed({
get: () => props.visible,
set: (value: boolean) => emit('update:visible', value)
})
function updatedAtLabel(row: Product) {
return row.created_at ? `آخرین بروزرسانی: ${toJalali(row.created_at)}` : '-'
}
function cashFeeLabel(row: Product) {
return usePrice((row.cash_fee_buy || 0) + (row.cash_fee_sell || 0))
}
</script>
@@ -0,0 +1,41 @@
<template>
<MobileCard>
<div class="flex items-center justify-between gap-2">
<div class="flex min-w-0 items-center gap-2">
<span class="inline-block h-2.5 w-2.5 shrink-0 rounded-full" :class="statusDotClass" />
<button
type="button"
class="truncate border-none bg-transparent p-0 text-right text-sm font-700 text-gray-900 cursor-pointer"
@click="$emit('open', item)"
>
{{ item.title || '-' }}
</button>
</div>
<MobileIconButton icon="vuesax-linear:edit" color="#2563eb" :size="20" @click="$emit('edit', item)" />
</div>
<div class="mt-3 flex items-center justify-between gap-2">
<span class="text-xs text-gray-500">کد محصول: {{ item.id ?? '-' }}</span>
<span class="text-sm font-600 text-gray-700">{{ priceLabel }}</span>
</div>
</MobileCard>
</template>
<script setup lang="ts">
import type { Product } from '@/model/product'
import MobileCard from '@/components/mobile/MobileCard.vue'
import MobileIconButton from '@/components/mobile/MobileIconButton.vue'
import { usePrice } from '@core/utils/usePrice'
const props = defineProps<{
item: Product
}>()
defineEmits<{
(e: 'open', item: Product): void
(e: 'edit', item: Product): void
}>()
const statusDotClass = computed(() => (props.item.active ? 'bg-green-500' : 'bg-red-400'))
const priceLabel = computed(() => (props.item.price ? usePrice(props.item.price) : '-'))
</script>
@@ -1,103 +0,0 @@
<template>
<div>
<BasicShowContext title="لیست قلک ها">
<BaseTable v-bind="tblProps" @register="submit" ref="tbl">
</BaseTable>
</BasicShowContext>
</div>
</template>
<script lang="tsx" setup>
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 {
changeCustomerActivation,
changeCustomerArchive,
changeCustomerOrderActivation,
confirmUser,
getCustomerTable
} from '@/api/customer'
import {cols} from '@/pages/menuPages/customers/base/hooks/fundsColumns'
import {useUserStore} from '@/store/userStore'
import {usePrefix} from '@core/composable/usePrefix'
import {message} from 'ant-design-vue'
import {inputRules} from '@/utils/inputRules'
import type {User} from '@/model/user'
import {toJalaliNoTime} from '@core/utils/useDataTime'
import type {Customer} from '@/model/customer'
import {onMounted} from 'vue'
import {getFundsTable} from "@/api/user";
const [submit, reload] = useBaseTable()
interface Prop {
customerId?: number
}
const emits = defineEmits(['delete', 'edit', 'add', 'info', 'chargeMoney', 'chargeGold'])
const props = defineProps<Prop>()
const {prefixCls} = usePrefix('pophoverstatus')
//permission
function checkPermission(temp: string) {
const store = useUserStore()
return store.canAccess(temp)
}
//define table
const table = ref(null)
const confirmVisible = ref<boolean>(false)
const confirmInfo = ref<Customer>()
const tblProps = ref<any>({
columns: cols,
api: (params: TableInput) => {
return getFundsTable(params, props.customerId)
}
//bordered: true
})
const refPop = ref()
const loading = ref<boolean>(false)
const fetch = () => {
// console.log('sss')
reload()
}
// const spinTable = (val: boolean) => {
// // console.log('sss')
// table?.value?.loading.value = val
// }
defineExpose({
fetch
// spinTable
})
</script>
<style lang="less">
@prefix: ~'@{prefixCls}-pophoverstatus';
.ant-popover,
.ant-tooltip {
z-index: 1001 !important;
}
.@{prefix} {
.ant-menu-rtl.ant-menu-inline,
.ant-menu-rtl.ant-menu-vertical {
border-left: none;
}
.ant-menu-item-only-child {
border-radius: @border-radius-base;
}
//bor
/* .ant-input-group-addon:first-child {
!* border: #d9d9d9 1px solid;
border-radius: 0 6px 6px 0;*!
}*/
}
</style>
@@ -1,104 +0,0 @@
<template>
<div>
<BasicShowContext title="لیست معرفی کرده ها">
<BaseTable v-bind="tblProps" @register="submit" ref="tbl">
</BaseTable>
</BasicShowContext>
</div>
</template>
<script lang="tsx" setup>
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 {
changeCustomerActivation,
changeCustomerArchive,
changeCustomerOrderActivation,
confirmUser,
getCustomerTable
} from '@/api/customer'
import {cols} from '@/pages/menuPages/customers/base/hooks/referralColumns'
import {useUserStore} from '@/store/userStore'
import {usePrefix} from '@core/composable/usePrefix'
import {message} from 'ant-design-vue'
import {inputRules} from '@/utils/inputRules'
import type {User} from '@/model/user'
import {toJalaliNoTime} from '@core/utils/useDataTime'
import type {Customer} from '@/model/customer'
import {onMounted} from 'vue'
import {getReferralsTable} from "@/api/user";
const [submit, reload] = useBaseTable()
interface Prop {
customerId?: number
}
const emits = defineEmits(['delete', 'edit', 'add', 'info', 'chargeMoney', 'chargeGold'])
const props = defineProps<Prop>()
const {prefixCls} = usePrefix('pophoverstatus')
//permission
function checkPermission(temp: string) {
const store = useUserStore()
return store.canAccess(temp)
}
//define table
const table = ref(null)
const confirmVisible = ref<boolean>(false)
const confirmInfo = ref<Customer>()
const tblProps = ref<any>({
columns: cols,
api: (params: TableInput) => {
return getReferralsTable(params, props.customerId)
}
//bordered: true
})
const refPop = ref()
const loading = ref<boolean>(false)
const fetch = () => {
// console.log('sss')
reload()
}
// const spinTable = (val: boolean) => {
// // console.log('sss')
// table?.value?.loading.value = val
// }
defineExpose({
fetch
// spinTable
})
</script>
<style lang="less">
@prefix: ~'@{prefixCls}-pophoverstatus';
.ant-popover,
.ant-tooltip {
z-index: 1001 !important;
}
.@{prefix} {
.ant-menu-rtl.ant-menu-inline,
.ant-menu-rtl.ant-menu-vertical {
border-left: none;
}
.ant-menu-item-only-child {
border-radius: @border-radius-base;
}
//bor
/* .ant-input-group-addon:first-child {
!* border: #d9d9d9 1px solid;
border-radius: 0 6px 6px 0;*!
}*/
}
</style>
@@ -1,8 +1,8 @@
<template>
<div>
<div class="md:flex pr-3">
<div class="flex h-full min-h-0 min-w-0 max-w-full flex-col overflow-hidden">
<div class="shrink-0 pr-3 md:flex">
<div class="content-center">فعال سازی محصولات:</div>
<div class="ml-2 content-center mt-1 md:mt-unset md:mr-4">زمان شروع </div>
<div class="ml-2 content-center mt-1 md:mt-unset md:mr-4">زمان شروع</div>
<div>
<TimePicker
v-model:value="startTime"
@@ -11,7 +11,7 @@
:defaultValue="dayjs('00:00:00', 'HH:mm:ss')"
/>
</div>
<div class="md:mr-4 ml-2 mt-2 md:mt-unset content-center">زمان پایان </div>
<div class="md:mr-4 ml-2 mt-2 md:mt-unset content-center">زمان پایان</div>
<div>
<TimePicker
v-model:value="endTime"
@@ -20,592 +20,179 @@
:defaultValue="dayjs('00:00:00', 'HH:mm:ss')"
/>
</div>
<BaseButton @click="setTimeActive(startTime , endTime)" class="mt-2 md:mt-unset md:mr-2">ثبت</BaseButton>
<BaseButton class="mt-2 md:mt-unset md:mr-2" @click="setTimeActive(startTime, endTime)">ثبت</BaseButton>
</div>
<BasicShowContext title=" لیست محصولات">
<BaseTable v-bind="tblProps" @register="submit" ref="tbl">
<template #extraHeader>
<BaseButton type="primary" size="small" class="flex" @click="emits('add')">
<div>افزودن محصول</div>
<BaseIcon icon="vuesax-linear:box" Size="16px" />
</BaseButton>
</template>
<template #action="{ column, record }">
<div class="gap-2 flex justify-center">
<a-tooltip class="cursor-pointer" :key="record.open_buy">
<template v-if="!record.open_buy" #title><span>فعال کردن خرید</span></template>
<template v-else #title><span>غیر فعال کردن خرید</span></template>
<BaseIcon
<BasicShowContext title="لیست محصولات" class="min-h-0 flex-1">
<template #extra>
<BaseButton type="primary" size="small" class="flex shrink-0 md:hidden" @click="emits('add')">
<div>افزودن</div>
<BaseIcon icon="vuesax-linear:box" Size="16px" />
</BaseButton>
</template>
<div class="flex h-full min-h-0 flex-col">
<BaseTable v-if="!isMobile" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl">
<template #extraHeader>
<BaseButton type="primary" size="small" class="flex" @click="emits('add')">
<div>افزودن محصول</div>
<BaseIcon icon="vuesax-linear:box" Size="16px" />
</BaseButton>
</template>
<template #action="{ record }">
<div class="gap-2 flex justify-center">
<a-tooltip class="cursor-pointer" :key="`buy-${record.id}-${record.open_buy}`">
<template v-if="!record.open_buy" #title><span>فعال کردن خرید</span></template>
<template v-else #title><span>غیر فعال کردن خرید</span></template>
<BaseIcon
icon="vuesax-linear:money-receive"
:color="record.open_buy ? '#16A34A' : '#DC2626'"
@click="activeBuy(record)"
/>
</a-tooltip>
<a-tooltip class="cursor-pointer" :key="record.open_sell">
<template v-if="!record.open_sell" #title><span>فعال کردن فروش</span></template>
<template v-else #title><span>غیر فعال کردن فروش</span></template>
<BaseIcon
/>
</a-tooltip>
<a-tooltip class="cursor-pointer" :key="`sell-${record.id}-${record.open_sell}`">
<template v-if="!record.open_sell" #title><span>فعال کردن فروش</span></template>
<template v-else #title><span>غیر فعال کردن فروش</span></template>
<BaseIcon
icon="vuesax-linear:money-send"
:color="record.open_sell ? '#16A34A' : '#DC2626'"
@click="activeSell(record)"
/>
</a-tooltip>
<a-tooltip class="cursor-pointer" :key="record.active">
<template v-if="!record.active" #title><span>فعال کردن محصول</span></template>
<template v-else #title><span>غیر فعال کردن محصول</span></template>
<BaseIcon
v-if="!record.active"
icon="vuesax-linear:close-circle"
color="#DC2626"
@click="activeProduct(record)"
/>
<BaseIcon
v-else
icon="vuesax-linear:tick-circle"
color="#16A34A"
@click="activeProduct(record)"
/>
</a-tooltip>
<!-- <a-tooltip class="cursor-pointer" :key="record.show">
<template v-if="!record.show" #title><span>نشان دادن محصول</span></template>
<template v-else #title><span>مخفی کردن محصول</span></template>
<BaseIcon
v-if="!record.show"
icon="vuesax-linear:chart-fail"
color="#DC2626"
@click="visibilityProduct(record)"
/>
<BaseIcon
v-else
icon="vuesax-linear:chart-success"
color="#16A34A"
@click="visibilityProduct(record)"
/>
</a-tooltip>-->
<a-tooltip
placement="bottom"
class="cursor-pointer"
@click="emits('edit', record)"
>
<template #title>
<span>ویرایش</span>
</template>
<BaseIcon icon="vuesax-linear:edit" color="#4572E9" />
</a-tooltip>
</div>
<!-- <div class="md:hidden">-->
<!-- <a-popover title="" placement="right" @cancel="">
<template #content>
<div class="flex flex-col gap-3">
<a-tooltip :key="record.is_archive" placement="bottom" class="cursor-pointer">
<template #title>
<span>{{
record.is_archive ? 'بیرون بردن از بایگانی' : 'بایگانی کردن'
}}</span>
</template>
<a-popconfirm
title="آیا مشتری را بایگانی می کنید؟"
ok-text="تایید"
cancel-text="رد"
placement="topLeft"
@confirm="archiveFunction(record.id)"
@cancel=""
>
&lt;!&ndash; <template #icon><question-circle-outlined style="color: red" /></template>&ndash;&gt;
<Icon
class="cursor-pointer"
icon="vuesax-linear:archive-tick"
color="#e3bf00"
/>
</a-popconfirm>
</a-tooltip>
<a-tooltip
:key="record.active"
placement="bottom"
class="cursor-pointer"
@click="changeCustomerActivationFunction(record.id)"
>
<template #title>
<span>
{{ record.active ? 'غیرفعال کردن حساب کاربری' : 'فعال کردن حساب کاربری' }}
</span>
</template>
<Icon
:icon="
record.active ? 'vuesax-linear:user-tick' : 'vuesax-linear:user-remove'
"
:color="record.active ? '#54AD06' : '#ad0627'"
/>
</a-tooltip>
<a-tooltip
placement="bottom"
class="cursor-pointer"
@click="emits('chargeMoney', record)"
>
<template #title>
<span>شارژ کیف پول ریالی</span>
</template>
<Icon icon="vuesax-linear:wallet-add" color="#9333ea" />
</a-tooltip>
<a-tooltip
placement="bottom"
class="cursor-pointer"
@click="emits('chargeGold', record)"
>
<template #title>
<span>شارژ کیف پول طلایی</span>
</template>
<Icon icon="vuesax-linear:wallet-money" color="#ea580c" />
</a-tooltip>
<a-tooltip
placement="bottom"
class="cursor-pointer"
@click="emits('info', record)"
>
<template #title>
<span>مشخصات</span>
</template>
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
</a-tooltip>
<a-tooltip
placement="bottom"
class="cursor-pointer"
@click="emits('edit', record)"
>
<template #title>
<span>ویرایش</span>
</template>
<Icon icon="vuesax-linear:edit" color="#4572E9" />
</a-tooltip>
<a-tooltip
:key="record.trade_active"
placement="bottom"
class="cursor-pointer"
@click="changeCustomerOrderActivationFunction(record.id)"
>
<template #title>
<span>
{{
record.trade_active ? 'غیر مجاز کردن سفارش دهی' : 'مجاز کردن سفارش دهی'
}}
</span>
</template>
<Icon
:icon="
record.trade_active === 1
? 'vuesax-linear:box-tick'
: 'vuesax-linear:box-remove'
"
:color="record.trade_active === 1 ? '#54AD06' : '#ad0627'"
/>
</a-tooltip>
<a-tooltip
v-if="!record.is_legal"
:key="record.trade_active"
placement="bottom"
class="cursor-pointer"
@click="openConfirmUser(record)"
>
<template #title>
<span>تایید کاربر</span>
</template>
<Icon icon="vuesax-linear:user-search" color="#0d9488" />
</a-tooltip>
</div>
</template>
&lt;!&ndash; <template #icon><question-circle-outlined style="color: red" /></template>&ndash;&gt;
<Icon class="cursor-pointer" icon="vuesax-linear:setting" color="#000" />
</a-popover>
</div>
<div class="gap-2 hidden md:flex justify-center">
<a-tooltip :key="record.is_archive" placement="bottom" class="cursor-pointer">
<template #title>
<span>{{ record.is_archive ? 'بیرون بردن از بایگانی' : 'بایگانی کردن' }}</span>
</template>
<a-popconfirm
title="آیا مشتری را بایگانی می کنید؟"
ok-text="تایید"
cancel-text="رد"
placement="topLeft"
@confirm="archiveFunction(record.id)"
@cancel=""
>
&lt;!&ndash; <template #icon><question-circle-outlined style="color: red" /></template>&ndash;&gt;
<Icon class="cursor-pointer" icon="vuesax-linear:archive-tick" color="#e3bf00" />
</a-popconfirm>
</a-tooltip>
<a-tooltip
:key="record.active"
placement="bottom"
class="cursor-pointer"
@click="changeCustomerActivationFunction(record.id)"
>
<template #title>
<span>{{
record.active ? 'غیرفعال کردن حساب کاربری' : 'فعال کردن حساب کاربری'
}}</span>
</template>
<Icon
:icon="record.active ? 'vuesax-linear:user-tick' : 'vuesax-linear:user-remove'"
:color="record.active ? '#54AD06' : '#ad0627'"
/>
</a-tooltip>
<a-tooltip
placement="bottom"
class="cursor-pointer"
@click="emits('chargeMoney', record)"
>
<template #title>
<span>شارژ کیف پول ریالی</span>
</template>
<Icon icon="vuesax-linear:wallet-add" color="#9333ea" />
</a-tooltip>
<a-tooltip
placement="bottom"
class="cursor-pointer"
@click="emits('chargeGold', record)"
>
<template #title>
<span>شارژ کیف پول طلایی</span>
</template>
<Icon icon="vuesax-linear:wallet-money" color="#ea580c" />
</a-tooltip>
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('info', record)">
<template #title>
<span>مشخصات</span>
</template>
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
</a-tooltip>
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('edit', record)">
<template #title>
<span>ویرایش</span>
</template>
<Icon icon="vuesax-linear:edit" color="#4572E9" />
</a-tooltip>
<a-tooltip
:key="record.trade_active"
placement="bottom"
class="cursor-pointer"
@click="changeCustomerOrderActivationFunction(record.id)"
>
<template #title>
<span>{{
record.trade_active ? 'غیر مجاز کردن سفارش دهی' : 'مجاز کردن سفارش دهی'
}}</span>
</template>
<Icon
:icon="
record.trade_active === 1 ? 'vuesax-linear:box-tick' : 'vuesax-linear:box-remove'
"
:color="record.trade_active === 1 ? '#54AD06' : '#ad0627'"
/>
</a-tooltip>
<a-tooltip
v-if="!record.is_legal"
:key="record.trade_active"
placement="bottom"
class="cursor-pointer"
@click="openConfirmUser(record)"
>
<template #title>
<span>تایید کاربر</span>
</template>
<Icon icon="vuesax-linear:user-search" color="#0d9488" />
</a-tooltip>
&lt;!&ndash; <a-tooltip
placement="bottom"
class="cursor-pointer"
@click="emits('delete', record.id)"
>
<template #title>
<span>حذف</span>
</template>
<Icon icon="vuesax-linear:trash" color="#EB3535" />
</a-tooltip>&ndash;&gt;
</div>-->
</template>
</BaseTable>
<BasicModal
v-model:visible="confirmVisible"
width="50vw"
title="تایید مشتری"
@ok="confirmUserFunction"
ok-text="تایید"
:loading="loading"
@close="confirmVisible = false"
>
<div class="mt-[-10px] mb-10 font-bold">آیا از اطلاعات وارد شده اطمینان دارید؟</div>
<div class="grid grid-cols-4 gap-2">
<BasicInfoItem title="نام و نام خانوادگی" :value="confirmInfo?.name" />
<BasicInfoItem title="شماره همراه" :value="confirmInfo?.mobile" />
<BasicInfoItem
title="تاریخ تولد"
:value="confirmInfo?.birth_date ? toJalaliNoTime(confirmInfo?.birth_date) : '_'"
/>
<BasicInfoItem title="شماره تلفن" :value="confirmInfo?.phone_number ?? '_'" />
<BasicInfoItem title="نام استان" :value="confirmInfo?.province" />
<BasicInfoItem title="نام شهر" :value="confirmInfo?.city" />
<BasicInfoItem title="شماره ملی" :value="confirmInfo?.n_code" />
</div>
</BasicModal>
/>
</a-tooltip>
<a-tooltip class="cursor-pointer" :key="`active-${record.id}-${record.active}`">
<template v-if="!record.active" #title><span>فعال کردن محصول</span></template>
<template v-else #title><span>غیر فعال کردن محصول</span></template>
<BaseIcon
v-if="!record.active"
icon="vuesax-linear:close-circle"
color="#DC2626"
@click="activeProduct(record)"
/>
<BaseIcon
v-else
icon="vuesax-linear:tick-circle"
color="#16A34A"
@click="activeProduct(record)"
/>
</a-tooltip>
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('edit', record)">
<template #title><span>ویرایش</span></template>
<BaseIcon icon="vuesax-linear:edit" color="#4572E9" />
</a-tooltip>
</div>
</template>
</BaseTable>
<ProductMobileCardList
v-else
ref="mobileListRef"
class="min-h-0 flex-1"
@edit="emits('edit', $event)"
@toggleBuy="activeBuy"
@toggleSell="activeSell"
@toggleActive="activeProduct"
/>
</div>
</BasicShowContext>
</div>
</template>
<script lang="tsx" setup>
import type { TableInput } from '@core/type/baseTable'
import useBaseTable from '@core/components/BaseTable/hooks/useBaseTable'
import Icon from '@core/components/BaseIcon/src/Icon.vue'
<script setup lang="ts">
import { message, Modal, TimePicker } from 'ant-design-vue'
import locale from 'ant-design-vue/es/date-picker/locale/fa_IR'
import dayjs from 'dayjs'
import customParseFormat from 'dayjs/plugin/customParseFormat'
import { ref } from 'vue'
import {
changeActiveProduct,
changeBuyActiveProduct,
changeCustomerArchive,
changeCustomerOrderActivation,
changeProductActivation, changeSellActiveProduct, changeVisibilityProduct,
confirmUser,
getCustomerTable, setActiveTime
} from "@/api/product";
import { cols } from '@/pages/menuPages/products/base/hooks/customerColumns'
import { useUserStore } from '@/store/userStore'
import { usePrefix } from '@core/composable/usePrefix'
import { message, type TimePickerProps } from "ant-design-vue";
import { inputRules } from '@/utils/inputRules'
import type { User } from '@/model/user'
import { toJalaliNoTime } from '@core/utils/useDataTime'
import type { Customer } from '@/model/customer'
import { onMounted } from 'vue'
import { Modal } from 'ant-design-vue'
import type {Product} from "@/model/product";
import {resolve} from "path";
changeSellActiveProduct,
getCustomerTable,
setActiveTime
} from '@/api/product'
import { cols } from '@/pages/menuPages/products/base/hooks/productColumns'
import ProductMobileCardList from '@/pages/menuPages/products/base/components/ProductMobileCardList.vue'
import useBaseTable from '@core/components/BaseTable/hooks/useBaseTable'
import type { TableInput } from '@core/type/baseTable'
import type { Product } from '@/model/product'
import { isMobile } from '@core/utils/is'
dayjs.extend(customParseFormat)
const [submit, reload] = useBaseTable()
/*const select = (val) => {
val.sts === 1 ? return ['1'] : ['2']
}*/
//emits
const emits = defineEmits(['delete', 'edit', 'add', 'info', 'chargeMoney', 'chargeGold'])
const emits = defineEmits(['edit', 'add'])
const { prefixCls } = usePrefix('pophoverstatus')
//permission
function checkPermission(temp: string) {
const store = useUserStore()
return store.canAccess(temp)
}
//define table
const table = ref(null)
const data = ref()
const confirmVisible = ref<boolean>(false)
const confirmInfo = ref<Customer>()
const tblProps = ref<any>({
columns: cols,
api: async (params: TableInput) => {
const data = await getCustomerTable(params)
startTime.value = dayjs(data?.auto_open_at, 'HH:mm:ss')
endTime.value = dayjs(data?.auto_close_at, 'HH:mm:ss')
return data
},
//bordered: true
})
const refPop = ref()
// function edit(recordId){
// emits('edit', recordId);
// console.log( recordId);
// console.log( visibleAction.value[parseInt(recordId)]);
// visibleAction.value[recordId]=false
// console.log('ddddd')
// }
const loading = ref<boolean>(false)
async function changeCustomerActivationFunction(id: number) {
try {
loading.value = true
// await changeCustomerActivation(id)
reload()
message.success('تغییر وضعیت با موفقیت انجام شد')
} catch (e) {
console.error(e)
} finally {
loading.value = false
}
}
function openConfirmUser(user: Customer) {
confirmInfo.value = user
confirmVisible.value = true
}
async function archiveFunction(id: number) {
try {
loading.value = true
await changeCustomerArchive(id)
reload()
message.success('مشتری با موفقیت بایگانی شد.')
} catch (e) {
console.error(e)
} finally {
loading.value = false
}
}
async function confirmUserFunction() {
try {
loading.value = true
await confirmUser(confirmInfo.value?.id)
reload()
confirmVisible.value = false
} catch (e) {
console.log(e)
} finally {
loading.value = false
}
}
async function changeCustomerOrderActivationFunction(id: number) {
try {
loading.value = true
await changeCustomerOrderActivation(id)
reload()
message.success('تغییر وضعیت سفارش دهی با موفقیت انجام شد')
} catch (e) {
console.error(e)
} finally {
loading.value = false
}
}
const fetch = () => {
// console.log('sss')
reload()
}
// const spinTable = (val: boolean) => {
// // console.log('sss')
// table?.value?.loading.value = val
// }
defineExpose({
fetch
// spinTable
})
const activeSell = async (product: Product) => {
Modal.confirm({
title: 'وضعیت فروش محصول تغییر خواهد یافت.آیا مطمئن هستید؟',
okText: 'بله',
cancelText: 'انصراف',
centered: true,
onOk: async () => {
await changeSellActiveProduct(product.id)
reload()
},
onCancel() {
console.log('Cancel')
}
})
}
const activeBuy = async (product: Product) => {
Modal.confirm({
title: 'وضعیت خرید محصول تغییر خواهد یافت.آیا مطمئن هستید؟',
okText: 'بله',
cancelText: 'انصراف',
centered: true,
onOk: async () => {
await changeBuyActiveProduct(product.id)
reload()
},
onCancel() {
console.log('Cancel')
}
})
}
const activeProduct = async (product: Product) => {
Modal.confirm({
title: 'آیا مطمئن به انجام این عملیات هستید؟',
okText: 'بله',
cancelText: 'انصراف',
centered: true,
onOk: async () => {
await changeActiveProduct(product.id)
reload()
},
onCancel() {
console.log('Cancel')
}
})
}
const visibilityProduct = async (product: Product) => {
Modal.confirm({
title: 'آیا مطمئن به انجام این عملیات هستید؟',
okText: 'بله',
cancelText: 'انصراف',
centered: true,
onOk: async () => {
await changeVisibilityProduct(product.id)
reload()
},
onCancel() {
console.log('Cancel')
}
})
}
import { ref } from 'vue'
import { TimePicker } from 'ant-design-vue'
import dayjs from 'dayjs';
import customParseFormat from 'dayjs/plugin/customParseFormat'
import locale from "ant-design-vue/es/date-picker/locale/fa_IR"
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
const startTime = ref(dayjs('00:00:00', 'HH:mm:ss'))
const endTime = ref(dayjs('00:00:00', 'HH:mm:ss'))
dayjs.extend(customParseFormat)
const customLocale = ref({
...locale,
lang: {
...locale.lang,
now: "اکنون",
ok: "ثبت",
},
})
async function setTimeActive(start: string, end: string) {
try {
const startTimeFormatted = dayjs(start, 'HH:mm:ss').format('HH:mm:ss')
const endTimeFormatted = dayjs(end, 'HH:mm:ss').format('HH:mm:ss')
await setActiveTime(startTimeFormatted, endTimeFormatted)
message.success('با موفقیت ثبت شد.')
} catch (e) {
message.error('زمان را وارد کنید.')
// console.log(e)
} finally {
loading.value = false
now: 'اکنون',
ok: 'ثبت'
}
})
const tblProps = ref<any>({
columns: cols,
api: async (params: TableInput) => {
const data = (await getCustomerTable(params)) as any
startTime.value = dayjs(data?.auto_open_at, 'HH:mm:ss')
endTime.value = dayjs(data?.auto_close_at, 'HH:mm:ss')
return data
}
})
const fetch = async () => {
await Promise.allSettled([reload(), mobileListRef.value?.fetch?.() as Promise<void>])
}
defineExpose({ fetch })
function confirmToggle(title: string, onOk: () => Promise<void>) {
Modal.confirm({
title,
okText: 'بله',
cancelText: 'انصراف',
centered: true,
onOk
})
}
const activeSell = (product: Product) =>
confirmToggle('وضعیت فروش محصول تغییر خواهد یافت.آیا مطمئن هستید؟', async () => {
await changeSellActiveProduct(product.id)
await fetch()
})
const activeBuy = (product: Product) =>
confirmToggle('وضعیت خرید محصول تغییر خواهد یافت.آیا مطمئن هستید؟', async () => {
await changeBuyActiveProduct(product.id)
await fetch()
})
const activeProduct = (product: Product) =>
confirmToggle('آیا مطمئن به انجام این عملیات هستید؟', async () => {
await changeActiveProduct(product.id)
await fetch()
})
async function setTimeActive(start: dayjs.Dayjs | string, end: dayjs.Dayjs | string) {
try {
const startTimeFormatted = dayjs(start).format('HH:mm:ss')
const endTimeFormatted = dayjs(end).format('HH:mm:ss')
await setActiveTime(startTimeFormatted, endTimeFormatted)
message.success('با موفقیت ثبت شد.')
} catch {
message.error('زمان را وارد کنید.')
}
}
</script>
<style lang="less">
@prefix: ~'@{prefixCls}-pophoverstatus';
.ant-popover,
.ant-tooltip {
z-index: 1001 !important;
}
.@{prefix} {
.ant-menu-rtl.ant-menu-inline,
.ant-menu-rtl.ant-menu-vertical {
border-left: none;
}
.ant-menu-item-only-child {
border-radius: @border-radius-base;
}
//bor
/* .ant-input-group-addon:first-child {
!* border: #d9d9d9 1px solid;
border-radius: 0 6px 6px 0;*!
}*/
}
</style>
@@ -1,291 +0,0 @@
import type { BaseTableColumn } from '@core/type/baseTable'
import {toJalali, toJalaliNoTime, toTime} from '@core/utils/useDataTime'
import CustomTag from '@/components/CustomTag.vue'
import { usePrice } from '@core/utils/usePrice'
/*import BudgetSelect from '@/components/BudgetSelect.vue'
import { useStaticApiListsStore } from '@/store/staticApisStore'
import type { FilterSelect } from '@/model/filterSelect'
import type { DocItem } from '@/model/docType'*/
//define store
//const store = useStaticApiListsStore()
//document types
/*let docTypes: DocItem[]*/
/*onMounted(async () => {
docTypes = await store.readyDocTypeList()
})
const filteredDocTypes: FilterSelect = docTypes?.map((x: DocItem) => {
return {
label: x.title,
value: x.id
}
})*/
export const cols: BaseTableColumn[] = [
{
title: 'ردیف',
dataIndex: 'id',
key: 'id',
filterable: false,
sortable: false,
customRender: (record: any) => {
return record.index + 1
}
},
// {
// title: 'کد کاربری',
// dataIndex: 'id',
// key: 'id',
// type: 'Number'
// // className: 'column-money'
// },
{
title: 'نام',
dataIndex: 'title',
key: 'title',
type: 'Text'
},
{
title: 'تصویر',
dataIndex: 'title',
key: 'title',
type: 'Text'
},
{
title: 'وزن',
dataIndex: 'amount',
key: 'amount',
type: 'Number'
},
{
title: 'واحد',
dataIndex: 'unit',
key: 'unit',
type: 'Text'
},
{
title: 'دسته بندی',
dataIndex: 'group_type',
key: 'group_type',
type: 'Text'
},
{
title: 'قیمت',
dataIndex: 'price',
key: 'price',
type: 'Text',
},
{
title: 'متد',
dataIndex: 'reading_method',
key: 'reading_method',
type: 'Text',
customRender: (record) => {
return (
<div class="flex justify-center">
<CustomTag>
{record?.record.reading_method ? record?.record.reading_method : 'دستی'}
</CustomTag>
</div>
)
}
},
/*{
title: 'کارمزد درصدی موقع خرید',
dataIndex: 'percent_fee_buy',
key: 'percent_fee_buy',
type: 'Number'
},
{
title: 'کارمزد درصدی موقع فروش',
dataIndex: 'percent_fee_sell',
key: 'percent_fee_sell',
type: 'Number'
},*/
{
title: 'هزینه نقدی موقع خرید',
dataIndex: 'cash_fee_buy',
key: 'cash_fee_buy',
type: 'Number'
},
{
title: 'هزینه نقدی موقع فروش',
dataIndex: 'cash_fee_sell',
key: 'cash_fee_sell',
type: 'Number'
},
{
title: 'وضعیت محصول',
dataIndex: 'active',
key: 'active',
type: 'Boolean',
customRender: (record) => {
return (
<div class="flex justify-center">
<CustomTag
class="px-3 font-bold py-1"
textColor={record?.record.active ? '#65a30d' : '#dc2626'}
bgColor={record?.record.active ? '#ecfccb' : '#fee2e2'}
>
{record?.record.active ? 'باز' : 'بسته'}
</CustomTag>
</div>
)
}
},
{
title: 'عملیات',
dataIndex: 'action',
key: 'action',
slot: 'action',
filterable: false
}
// {
// title: 'کد معرف',
// dataIndex: 'referral_id',
// key: 'referral_id',
// type: 'Text'
// },
// {
// title: 'نام معرف کاربر',
// dataIndex: 'referral.name',
// key: 'referral.name',
// type: 'Text'
// },
// {
// title: 'موجودی ریالی (ریال)',
// dataIndex: 'wallet.cash',
// key: 'wallet.cash',
// type: 'Number',
// customRender(data) {
// return usePrice(data.record?.wallet[0]?.cash ?? 0, true, '')
// }
// },
// {
// title: 'موجودی طلایی (گرم)',
// dataIndex: 'wallet.gold',
// key: 'wallet.gold',
// type: 'Number',
// filterable: false,
// customRender(data) {
// let sum = 0
// data.record.wallet.forEach((x) => (sum += x.gold))
// return sum.toFixed(3)
// }
// },
// {
// title: 'اخرین بازدید',
// dataIndex: 'last_seen_at',
// key: 'last_seen_at',
// type: 'DateTime',
// customRender(data) {
// return toJalali(data.record.last_seen_at)
// }
// },
// {
// title: 'وضعیت کاربر',
// dataIndex: 'active',
// key: 'active',
// type: 'Boolean',
// customRender: ({ record }) => {
// return record.active ? (
// <div class="flex justify-center">
// <CustomTag textColor="#25855A" bgColor="#E3F4E9" class={'font-bold px-6'}>
// فعال
// </CustomTag>
// </div>
// ) : (
// <div class="flex justify-center">
// <CustomTag textColor="#C53030" bgColor="#FECACA" class={'font-bold px-6'}>
// غیر فعال
// </CustomTag>
// </div>
// )
// }
// },
// {
// title: 'وضعیت سفارش دهی',
// dataIndex: 'trade_active',
// key: 'trade_active',
// sortable: false,
// type: 'Boolean',
// customRender: ({ record }) => {
// return record.trade_active ? (
// <div class="flex justify-center">
// <CustomTag textColor="#25855A" bgColor="#E3F4E9" class={'font-bold px-6'}>
// مجاز
// </CustomTag>
// </div>
// ) : (
// <div class="flex justify-center">
// <CustomTag textColor="#C53030" bgColor="#FECACA" class={'font-bold px-6'}>
// غیر مجاز
// </CustomTag>
// </div>
// )
// }
// },
/*{
title: 'نقش',
dataIndex: 'user_type_record',
key: 'user_type_record',
type: 'Select',
filterable: false,
sortable: false,
filterComponentProps: {
multiple: true
},
filterField: 'user_type_obj.id',
filterComponent: ReadyUserTypeSelect,
/!* customRender: (record: any) => {
return record.record.user_type_record?.value
}*!/
customRender: ({ record }) => {
const items = record.is_admin ? (
<CustomTag textColor="#25855A" bgColor="#E3F4E9">
ادمین
</CustomTag>
) : (
record.user_type_obj.map((item: { title: any }, index: any) => {
if (item) {
return (
<CustomTag textColor="#25855A" bgColor="#E3F4E9">
{item?.title}
</CustomTag>
)
}
})
)
return <div class="flex justify-center items-center gap-2">{items}</div>
}
},
{
title: 'حوزه',
dataIndex: 'section',
key: 'section'
},
{
title: 'شماره موبایل',
dataIndex: 'mobile',
type: 'Number',
key: 'mobile'
},
{
title: 'تاریخ ثبت',
dataIndex: 'created_at',
key: 'created_at',
type: 'DateTime',
customRender: (record: any) => {
return toJalaliNoTime(record.record.created_at)
}
},*/
// {
// title: 'عملیات',
// dataIndex: 'action',
// key: 'action',
// slot: 'action',
// fixed: 'right',
// filterable: false
// }
]
@@ -1,54 +0,0 @@
import type { BaseTableColumn } from '@core/type/baseTable'
import { toJalaliNoTime, toTime } from '@core/utils/useDataTime'
import CustomTag from '@/components/CustomTag.vue'
import { usePrice } from '@core/utils/usePrice'
/*import BudgetSelect from '@/components/BudgetSelect.vue'
import { useStaticApiListsStore } from '@/store/staticApisStore'
import type { FilterSelect } from '@/model/filterSelect'
import type { DocItem } from '@/model/docType'*/
//define store
//const store = useStaticApiListsStore()
//document types
/*let docTypes: DocItem[]*/
/*onMounted(async () => {
docTypes = await store.readyDocTypeList()
})
const filteredDocTypes: FilterSelect = docTypes?.map((x: DocItem) => {
return {
label: x.title,
value: x.id
}
})*/
export const cols: BaseTableColumn[] = [
{
title: 'ردیف',
dataIndex: 'id',
key: 'id',
filterable: false,
sortable: false,
customRender: (record: any) => {
return record.index + 1
}
},
{
title: 'کد قلک',
dataIndex: 'id',
key: 'id',
type: 'Number'
// className: 'column-money'
},
{
title: 'نام قلک',
dataIndex: 'fund.title',
key: 'fund.title',
type: 'Text'
},
{
title: 'موجودی طلا',
dataIndex: 'gold',
key: 'gold',
type: 'Number'
},
]
@@ -0,0 +1,92 @@
import type { BaseTableColumn } from '@core/type/baseTable'
import CustomTag from '@/components/CustomTag.vue'
export const cols: BaseTableColumn[] = [
{
title: 'ردیف',
dataIndex: 'id',
key: 'id',
filterable: false,
sortable: false,
customRender: (record: any) => record.index + 1
},
{
title: 'نام',
dataIndex: 'title',
key: 'title',
type: 'Text'
},
{
title: 'وزن',
dataIndex: 'amount',
key: 'amount',
type: 'Number'
},
{
title: 'واحد',
dataIndex: 'unit',
key: 'unit',
type: 'Text'
},
{
title: 'دسته بندی',
dataIndex: 'group_type',
key: 'group_type',
type: 'Text'
},
{
title: 'قیمت',
dataIndex: 'price',
key: 'price',
type: 'Text'
},
{
title: 'متد',
dataIndex: 'reading_method',
key: 'reading_method',
type: 'Text',
customRender: (record) => (
<div class="flex justify-center">
<CustomTag textColor="#1d4ed8" bgColor="#dbeafe">
{record?.record.reading_method ? record?.record.reading_method : 'دستی'}
</CustomTag>
</div>
)
},
{
title: 'هزینه نقدی موقع خرید',
dataIndex: 'cash_fee_buy',
key: 'cash_fee_buy',
type: 'Number'
},
{
title: 'هزینه نقدی موقع فروش',
dataIndex: 'cash_fee_sell',
key: 'cash_fee_sell',
type: 'Number'
},
{
title: 'وضعیت محصول',
dataIndex: 'active',
key: 'active',
type: 'Boolean',
customRender: (record) => (
<div class="flex justify-center">
<CustomTag
class="px-3 font-bold py-1"
textColor={record?.record.active ? '#65a30d' : '#dc2626'}
bgColor={record?.record.active ? '#ecfccb' : '#fee2e2'}
>
{record?.record.active ? 'باز' : 'بسته'}
</CustomTag>
</div>
)
},
{
title: 'عملیات',
dataIndex: 'action',
key: 'action',
slot: 'action',
filterable: false
}
]
@@ -1,163 +0,0 @@
import type { BaseTableColumn } from '@core/type/baseTable'
import { toJalaliNoTime, toTime } from '@core/utils/useDataTime'
import CustomTag from '@/components/CustomTag.vue'
import { usePrice } from '@core/utils/usePrice'
/*import BudgetSelect from '@/components/BudgetSelect.vue'
import { useStaticApiListsStore } from '@/store/staticApisStore'
import type { FilterSelect } from '@/model/filterSelect'
import type { DocItem } from '@/model/docType'*/
//define store
//const store = useStaticApiListsStore()
//document types
/*let docTypes: DocItem[]*/
/*onMounted(async () => {
docTypes = await store.readyDocTypeList()
})
const filteredDocTypes: FilterSelect = docTypes?.map((x: DocItem) => {
return {
label: x.title,
value: x.id
}
})*/
export const cols: BaseTableColumn[] = [
{
title: 'ردیف',
dataIndex: 'id',
key: 'id',
filterable: false,
sortable: false,
customRender: (record: any) => {
return record.index + 1
}
},
{
title: 'کد کاربری',
dataIndex: 'id',
key: 'id',
type: 'Number'
// className: 'column-money'
},
{
title: 'نام و نام خانوادگی',
dataIndex: 'name',
key: 'name',
type: 'Text'
},
{
title: 'شماره موبایل',
dataIndex: 'mobile',
key: 'mobile',
type: 'Number'
},
{
title: 'نوع کاربر',
dataIndex: 'type_str',
key: 'type_str',
type: 'Text'
},
{
title: 'کد معرف',
dataIndex: 'referral_id',
key: 'referral_id',
type: 'Text'
},
{
title: 'وضعیت کاربر',
dataIndex: 'active',
key: 'active',
type: 'Boolean',
customRender: ({ record }) => {
return record.active ? (
<div class="flex justify-center">
<CustomTag textColor="#25855A" bgColor="#E3F4E9" class={'font-bold px-6'}>
فعال
</CustomTag>
</div>
) : (
<div class="flex justify-center">
<CustomTag textColor="#C53030" bgColor="#FECACA" class={'font-bold px-6'}>
غیر فعال
</CustomTag>
</div>
)
}
},
{
title: 'وضعیت سفارش دهی',
dataIndex: 'active',
key: 'active',
filterable: false,
sortable: false,
customRender: ({ record }) => {
return record.trade_active === 1 ? (
<div class="flex justify-center">
<CustomTag textColor="#25855A" bgColor="#E3F4E9" class={'font-bold px-6'}>
مجاز
</CustomTag>
</div>
) : (
<div class="flex justify-center">
<CustomTag textColor="#C53030" bgColor="#FECACA" class={'font-bold px-6'}>
غیر مجاز
</CustomTag>
</div>
)
}
},
/*{
title: 'نقش',
dataIndex: 'user_type_record',
key: 'user_type_record',
type: 'Select',
filterable: false,
sortable: false,
filterComponentProps: {
multiple: true
},
filterField: 'user_type_obj.id',
filterComponent: ReadyUserTypeSelect,
/!* customRender: (record: any) => {
return record.record.user_type_record?.value
}*!/
customRender: ({ record }) => {
const items = record.is_admin ? (
<CustomTag textColor="#25855A" bgColor="#E3F4E9">
ادمین
</CustomTag>
) : (
record.user_type_obj.map((item: { title: any }, index: any) => {
if (item) {
return (
<CustomTag textColor="#25855A" bgColor="#E3F4E9">
{item?.title}
</CustomTag>
)
}
})
)
return <div class="flex justify-center items-center gap-2">{items}</div>
}
},
{
title: 'حوزه',
dataIndex: 'section',
key: 'section'
},
{
title: 'شماره موبایل',
dataIndex: 'mobile',
type: 'Number',
key: 'mobile'
},
{
title: 'تاریخ ثبت',
dataIndex: 'created_at',
key: 'created_at',
type: 'DateTime',
customRender: (record: any) => {
return toJalaliNoTime(record.record.created_at)
}
},*/
]
File diff suppressed because it is too large Load Diff
+6 -20
View File
@@ -1,16 +1,14 @@
<template>
<div>
<div class="h-full min-h-0">
<BasicMainCard>
<div class="flex flex-col md:flex-row md:h-full">
<!-- {{ menu }}-->
<BasicMenu width="11rem" :menu="menu" mode="horizontal" class="md:hidden overflow-auto" />
<div class="flex h-full min-h-0 min-w-0 flex-col md:flex-row">
<BasicMenu width="11rem" :menu="menu" mode="horizontal" class="shrink-0 overflow-auto md:hidden" />
<BasicMenu
width="13rem"
:menu="menu"
class="hidden md:inline overflow-auto min-w-[13.5rem]"
class="hidden min-w-[13.5rem] shrink-0 overflow-auto md:inline"
/>
<!-- <component :is="selectedComponent" class="mr-5" />-->
<RouterView class="md:mr-5 md:mt-0 mt-5 w-full"></RouterView>
<RouterView class="mt-5 min-h-0 min-w-0 w-full flex-1 overflow-hidden md:mr-5 md:mt-0" />
</div>
</BasicMainCard>
</div>
@@ -20,21 +18,9 @@
import BasicMenu from '@/components/BasicMenu/index.vue'
import { useRouterStore } from '@core/stores/router'
import { get } from 'lodash'
//import Nature from '@/pages/menuPages/setting/limitedOrder/index.vue'
//s import test from './limitedOrder'
//import Budget from '@/pages/menuPages/setting/budget/index.vue'
//import ProjectType from '@/pages/menuPages/setting/projectType/index.vue'
//import DocumentType from '@/pages/menuPages/setting/documentType/index.vue'
// import Management from '@/pages/menuPages/setting/management/index.vue'
//import Accessibility from '@/pages/menuPages/setting/accessibility/index.vue'
//import SubmitProcess from '@/pages/menuPages/setting/submitProcess/index.vue'
//import documentGroup from '@/pages/menuPages/setting/documentGroup/index.vue'
//import userType from '@/pages/menuPages/setting/userType/index.vue'
const routerStore = useRouterStore()
const menu = computed(() => {
const item = routerStore.routes.find((item) => item.name === 'products')
//console.log('aaaa', get(item, 'children[0].children', []))
const item = routerStore.routes.find((item) => item.name === 'products')
return get(item, 'children[0].children', [])
})
</script>
@@ -2,9 +2,9 @@
<div>
<div
:style="{ backgroundImage: `url(${bg1})` }"
class="w-[80vw] h-full bg-cover flex items-center justify-center"
class="flex min-h-[50vh] w-full items-center justify-center bg-cover px-4 py-6 md:h-full md:w-[80vw] md:px-0 md:py-0"
>
<div class="shadow-2xl h-[25vh] w-1/3 rounded-lg p-10 bg-white">
<div class="w-full max-w-md rounded-lg bg-white p-4 shadow-2xl md:h-[25vh] md:w-1/3 md:p-10">
<a-form :model="form" ref="formRef" autocomplete="off" @finish="ok">
<div class="gap-4">
<a-form-item label="سقف واریز اولیه (ریال)" name="value" :rules="inputRules()">
@@ -16,7 +16,7 @@
</a-form-item>-->
<a-form-item>
<BaseButton
class="mt-10 w-full"
class="mt-4 w-full md:mt-10"
:disabled="loading"
type="primary"
size="middle"
@@ -0,0 +1,194 @@
<template>
<a-form :model="form" ref="formRef" autocomplete="off">
<div class="space-y-3 md:grid md:grid-cols-4 md:gap-2 md:space-y-0">
<a-form-item label="انتخاب گروه">
<BasicSelect
v-model:value="selectedGroup"
:items="groupItems"
valueName="name"
keyName="function"
mode="multiple"
class="w-full"
/>
</a-form-item>
<a-form-item label="شماره های تکی" name="numbers" class="md:col-span-3">
<BasicSelect
v-model:value="form.single"
mode="tags"
valueName="name"
keyName="mobile"
type="tel"
noData="لطفا شماره موبایل را وارد کنید و سپس روی کیبورد خود کلید تایید را بزنید"
class="w-full sms-selector"
>
<template #notFoundContent>
<a-spin v-if="loading" size="small" />
<div v-else class="w-full text-center font-bold">لطفا شماره را وارد کنید</div>
</template>
</BasicSelect>
</a-form-item>
<a-form-item label="انتخاب کاربر" class="md:col-span-4 mb-0">
<BasicInput
place-holder="جستجو"
class="mb-2"
@onchange="filterNumbers"
:disabled="selectedGroup.length === 0"
/>
<a-table
class="hidden md:block"
rowKey="mobile"
:row-selection="{ selectedRowKeys: form.numbers, onChange: onSelectChange }"
:columns="columns"
:scroll="{ y: '20vh', 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"
>
<div v-if="customersLoading" class="flex justify-center py-6">
<a-spin size="small" />
</div>
<div v-else-if="!groupNumbers.length" class="py-6 text-center text-sm text-gray-500">
کاربری یافت نشد
</div>
<label
v-for="customer in groupNumbers"
:key="customer.mobile"
class="flex cursor-pointer items-center gap-2 rounded-lg border border-transparent bg-white px-3 py-2 active:bg-gray-100"
:class="isSelected(customer.mobile) ? 'border-blue-200 bg-blue-50' : ''"
>
<a-checkbox
:checked="isSelected(customer.mobile)"
@change="(e) => toggleCustomer(customer.mobile, e.target.checked)"
@click.stop
/>
<div class="min-w-0 flex-1">
<div class="truncate text-sm font-600 text-gray-900">{{ customer.name || '-' }}</div>
<div class="truncate text-xs text-gray-500">{{ customer.mobile }}</div>
</div>
<span class="shrink-0 text-xs text-gray-400">#{{ customer.id }}</span>
</label>
</div>
</a-form-item>
<a-form-item label="متن پیام" name="message" class="md:col-span-4 mb-0">
<BasicTextArea v-model:value="form.message" />
<div class="mt-2 space-y-2 md:flex md:items-center md:justify-between md:flex-row-reverse md:space-y-0">
<BaseButton type="primary" class="w-full md:w-10rem" @click="addName">افزودن نام کاربر</BaseButton>
<p class="text-xs leading-5 text-gray-500 md:max-w-[70%]">
برای استفاده از نام اشخاص در پیام از گزینه افزودن نام کاربر استفاده کنید. این متغیر با
عنوان (%name) به متن پیام اضافه می‌گردد.
</p>
</div>
</a-form-item>
</div>
</a-form>
</template>
<script setup lang="ts">
import type { FormInstance } from 'ant-design-vue'
import type { FunctionItem } from '@/model/Item'
import type { Customer } from '@/model/customer'
import type { Sms } from '@/model/sms'
import { getCustomerList } from '@/api/sms'
const props = defineProps<{
form: Sms
groupItems?: FunctionItem[]
loading?: boolean
}>()
const formRef = ref<FormInstance>()
const selectedGroup = ref<string[]>([])
const groupNumbers = ref<Customer[]>([])
const customersLoading = ref(false)
let baseGroupNumbers: Customer[] = []
const columns = [
{ title: 'کد کاربر', dataIndex: 'id' },
{ title: 'نام و نام خانوادگی', dataIndex: 'name' },
{ title: 'شماره همراه', dataIndex: 'mobile' }
]
function addName() {
props.form.message = (props.form.message ?? '').concat('', '%name')
}
function filterNumbers(val: string) {
if (val !== '') {
groupNumbers.value = baseGroupNumbers.filter((v) => v.name?.includes(val))
} else {
groupNumbers.value = baseGroupNumbers
}
}
async function loadGroupNumbers(val: string[]) {
if (!val.length) {
groupNumbers.value = []
baseGroupNumbers = []
return
}
try {
customersLoading.value = true
const { customers } = await getCustomerList(val)
groupNumbers.value = customers
baseGroupNumbers = customers
} catch (e) {
console.error(e)
} finally {
customersLoading.value = false
}
}
function onSelectChange(changableRowKeys: string[]) {
props.form.numbers = changableRowKeys
}
function isSelected(mobile?: string) {
if (!mobile) return false
return props.form.numbers?.includes(mobile) ?? false
}
function toggleCustomer(mobile: string | undefined, checked: boolean) {
if (!mobile) return
const numbers = new Set(props.form.numbers ?? [])
if (checked) numbers.add(mobile)
else numbers.delete(mobile)
props.form.numbers = Array.from(numbers)
}
watch(selectedGroup, (val) => {
loadGroupNumbers(val ?? [])
})
function resetAll() {
formRef.value?.resetFields()
selectedGroup.value = []
groupNumbers.value = []
baseGroupNumbers = []
}
defineExpose({
validateFields: () => formRef.value?.validateFields(),
resetAll
})
</script>
<style scoped>
.sms-selector :deep(.ant-select-selector) {
max-height: 5vh;
overflow-y: auto;
}
@media (max-width: 767px) {
.sms-selector :deep(.ant-select-selector) {
max-height: 12vh;
}
}
</style>
@@ -0,0 +1,65 @@
<template>
<MobileCardList
v-model:search="search"
:loading="loading"
:has-more="hasMore"
:show-empty="!loading && items.length === 0"
empty-text="پیامکی یافت نشد"
search-placeholder="جستجو پیامک"
show-refresh
@search="reload"
@refresh="reload"
@load-more="loadMore"
>
<SmsMobileListItem
v-for="item in items"
:key="item.id"
:item="item"
:hide-recipient="!!mobile"
@open="openSheet"
/>
</MobileCardList>
<SmsMobileDetailSheet v-model:visible="sheetVisible" :item="selectedItem" />
</template>
<script setup lang="ts">
import { getSMSTable } from '@/api/sms'
import type { SmsRecord } from '@/model/sms'
import MobileCardList from '@/components/mobile/MobileCardList.vue'
import { useMobileDetailSheet } from '@/components/mobile/composables/useMobileDetailSheet'
import { useMobileTableList } from '@/components/mobile/composables/useMobileTableList'
import type { TableInput } from '@core/type/baseTable'
import SmsMobileDetailSheet from './SmsMobileDetailSheet.vue'
import SmsMobileListItem from './SmsMobileListItem.vue'
const props = defineProps<{
mobile?: number | string
}>()
const { sheetVisible, selectedItem, openSheet, syncSelectedItem } = useMobileDetailSheet<SmsRecord>()
function fetchSms(params: TableInput) {
if (props.mobile) {
params.filters = params.filters ?? []
params.filters.push({
field: 'receiver',
type: 'text',
value: props.mobile,
cr: 'and',
op: 'eq',
hidden: false
})
}
return getSMSTable(params)
}
const { loading, search, items, hasMore, reload, loadMore, fetch } = useMobileTableList<SmsRecord>({
fetchApi: fetchSms,
onFetched: (rows) => syncSelectedItem(rows)
})
onMounted(() => reload())
defineExpose({ fetch, openSheet })
</script>
@@ -0,0 +1,55 @@
<template>
<MobileDetailSheet v-model:visible="visibleProxy" :item="item" :title="sheetTitle">
<template #default="{ item: row }">
<MobileSheetHeader :title="sheetTitle" :subtitle="dateLabel(row)" :show-title="isMobile" />
<MobileInfoGrid>
<MobileDetailRow label="کد" :value="row.id ?? '-'" />
<MobileDetailRow label="شماره تلفن" :value="phoneLabel(row)" />
<MobileDetailRow label="نام دریافت کننده" full-width :value="recipientName(row)" />
<MobileDetailRow label="تاریخ و ساعت" full-width :value="dateLabel(row)" />
<MobileDetailRow label="پیام" full-width :value="row.sms || '-'" />
</MobileInfoGrid>
</template>
</MobileDetailSheet>
</template>
<script setup lang="ts">
import type { SmsRecord } from '@/model/sms'
import MobileDetailRow from '@/components/mobile/MobileDetailRow.vue'
import MobileDetailSheet from '@/components/mobile/MobileDetailSheet.vue'
import MobileInfoGrid from '@/components/mobile/MobileInfoGrid.vue'
import MobileSheetHeader from '@/components/mobile/MobileSheetHeader.vue'
import { isMobile } from '@core/utils/is'
import { toJalali } from '@core/utils/useDataTime'
const props = defineProps<{
visible: boolean
item: SmsRecord | null
}>()
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
}>()
const visibleProxy = computed({
get: () => props.visible,
set: (value: boolean) => emit('update:visible', value)
})
const sheetTitle = computed(() => recipientName(props.item) || `پیامک #${props.item?.id ?? '-'}`)
function recipientName(row: SmsRecord | null) {
if (!row) return '-'
if (row.person?.name) return row.person.name
return row.person?.mobile || '-'
}
function phoneLabel(row: SmsRecord) {
return row.person?.mobile || row.receiver || '-'
}
function dateLabel(row: SmsRecord) {
return row.created_at ? toJalali(row.created_at) : '-'
}
</script>
@@ -0,0 +1,34 @@
<template>
<MobileCard clickable @click="$emit('open', item)">
<div class="flex items-start justify-between gap-2">
<span class="min-w-0 truncate text-sm font-700 text-gray-900">{{ recipientName }}</span>
<div class="shrink-0 space-y-0.5 text-left">
<div class="text-xs text-gray-400">{{ dateLabel }}</div>
<div v-if="!hideRecipient" class="text-xs text-gray-600">{{ phoneLabel }}</div>
</div>
</div>
</MobileCard>
</template>
<script setup lang="ts">
import type { SmsRecord } from '@/model/sms'
import MobileCard from '@/components/mobile/MobileCard.vue'
import { toJalali } from '@core/utils/useDataTime'
const props = defineProps<{
item: SmsRecord
hideRecipient?: boolean
}>()
defineEmits<{
(e: 'open', item: SmsRecord): void
}>()
const recipientName = computed(() => {
if (props.item.person?.name) return props.item.person.name
return props.item.person?.mobile || '-'
})
const phoneLabel = computed(() => props.item.person?.mobile || props.item.receiver || '-')
const dateLabel = computed(() => (props.item.created_at ? toJalali(props.item.created_at) : '-'))
</script>
+29 -61
View File
@@ -1,11 +1,19 @@
<template>
<div>
<div class="h-full min-h-0 min-w-0 max-w-full overflow-hidden">
<BasicShowContext title="لیست پیامک ها">
<BaseTable v-bind="tblProps" @register="submit" ref="tbl">
<template v-if="!mobile" #extra>
<BaseButton type="primary" size="small" class="flex shrink-0 md:hidden" @click="emits('add')">
<div>ارسال پیامک</div>
<BaseIcon icon="vuesax-linear:sms" Size="16px" />
</BaseButton>
</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" class="flex" @click="emits('add')">
<div>ارسال پیامک</div>
<BaseIcon icon="vuesax-linear:sms" Size="16px"/>
<BaseIcon icon="vuesax-linear:sms" Size="16px" />
</BaseButton>
</template>
<template #action="{ column, record }">
@@ -19,42 +27,30 @@
</div>-->
</template>
</BaseTable>
<SmsMobileCardList v-else ref="mobileListRef" class="min-h-0 flex-1" :mobile="mobile" />
</div>
</BasicShowContext>
</div>
</template>
<script lang="tsx" setup>
import type {TableInput} from '@core/type/baseTable'
import type { TableInput } from '@core/type/baseTable'
import useBaseTable from '@core/components/BaseTableOld/hooks/useBaseTable'
import Icon from '@core/components/BaseIcon/src/Icon.vue'
import {changeCustomerActivation, getCustomerTable} from '@/api/customer'
import {cols} from '../hooks/smsColumns'
import {useUserStore} from '@/store/userStore'
import {usePrefix} from '@core/composable/usePrefix'
import {message} from 'ant-design-vue'
import {getOrderTable} from '@/api/order'
import {getPriceTable} from '@/api/price'
import {getSMSTable} from '@/api/sms'
const [submit, reload] = useBaseTable()
import { cols } from '../hooks/smsColumns'
import { getSMSTable } from '@/api/sms'
import SmsMobileCardList from './SmsMobileCardList.vue'
import { isMobile as isMobileScreen } from '@core/utils/is'
interface Prop {
mobile: number
mobile?: number | string
}
const props = defineProps<Prop>()
const emits = defineEmits(['delete', 'edit', 'add', 'info'])
// const { prefixCls } = usePrefix('pophoverstatus')
//permission
/*function checkPermission(temp: string) {
const store = useUserStore()
return store.canAccess(temp)
}*/
//define table
const table = ref(null)
const [submit, reload] = useBaseTable()
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
const tblProps = ref<any>({
columns: cols,
@@ -62,52 +58,24 @@ const tblProps = ref<any>({
if (props.mobile) {
params.filters = []
params.filters?.push({
field: "receiver",
type: "text",
field: 'receiver',
type: 'text',
value: props.mobile,
cr: "and",
op: "eq",
cr: 'and',
op: 'eq',
hidden: false
})
}
return getSMSTable(params)
}
//bordered: true
})
// const loading = ref<boolean>(false)
const fetch = () => {
// console.log('sss')
reload()
async function fetch() {
await Promise.allSettled([reload(), mobileListRef.value?.fetch?.() as Promise<void>])
}
// const spinTable = (val: boolean) => {
// // console.log('sss')
// table?.value?.loading.value = val
// }
defineExpose({
fetch
// spinTable
})
</script>
<style lang="less">
@prefix: ~'@{prefixCls}-pophoverstatus';
.@{prefix} {
.ant-menu-rtl.ant-menu-inline,
.ant-menu-rtl.ant-menu-vertical {
border-left: none;
}
.ant-menu-item-only-child {
border-radius: @border-radius-base;
}
//bor
/* .ant-input-group-addon:first-child {
!* border: #d9d9d9 1px solid;
border-radius: 0 6px 6px 0;*!
}*/
}
</style>
+44 -217
View File
@@ -6,117 +6,54 @@
</div>
</BasicMainCard>
<BasicModal
v-model:visible="priceConfirmVisible"
width="25rem"
<BasicActionSheet
v-model:visible="addSMSVisible"
title="ارسال پیامک"
modal-width="80vw"
drawer-height="90vh"
:loading="loading"
:showButton="true"
:showCancel="true"
:showOk="true"
ok-text="ارسال"
cancel-text="انصراف"
@ok="ok"
:confirm-loading="loading"
@close="priceConfirmVisible = false"
@close="closeSmsSheet"
@cancel="closeSmsSheet"
>
<div class="flex items-center gap-2 font-bold">
<BaseIcon icon="vuesax-linear:info-circle" color="orange" />
<div>آیا مطمئن به تغییر قیمت می باشید؟</div>
</div>
</BasicModal>
<div v-if="isMobile" class="mb-3 text-base font-700">ارسال پیامک</div>
<SmsEditorFormBody ref="smsEditorRef" :form="form" :group-items="groupItems" :loading="loading" />
</BasicActionSheet>
</div>
<BasicModal
v-model:visible="addSMSVisible"
width="80vw"
title=""
@ok="ok"
:loading="loading"
@close="addSMSVisible = false"
>
<!-- <a-tabs v-model:activeKey="activeKey">
<a-tab-pane :key="1" tab="ارسال تکی" />
<a-tab-pane :key="2" tab="ارسال گروهی" />
</a-tabs>-->
<a-form :model="form" ref="formRef" autocomplete="off" class="md:grid md:grid-cols-4 gap-2">
<a-form-item label="انتخاب گروه">
<BasicSelect
v-model:value="selectedGroup"
:items="groupItems"
valueName="name"
keyName="function"
mode="multiple"
class="w-full"
@change="getGroupNumbers"
/>
</a-form-item>
<a-form-item label="شماره های تکی" name="numbers" class="md:col-span-3">
<BasicSelect
v-model:value="form.single"
mode="tags"
valueName="name"
keyName="mobile"
type="tel"
noData="لطفا شماره موبایل را وارد کنید و سپس روی کیبورد خود کلید تایید را بزنید"
class="w-full smsSelector"
>
<template #notFoundContent>
<a-spin v-if="loading" size="small" />
<div v-else class="w-full text-center font-bold">لطفا شماره را وارد کنید</div>
</template>
</BasicSelect>
</a-form-item>
<a-form-item label="انتخاب کاربر" class="md:col-span-4">
<BasicInput
place-holder="جستجو"
class="mb-2"
@onchange="filterNumbers"
:disabled="selectedGroup.length === 0"
/>
<a-table
rowKey="mobile"
:row-selection="{ selectedRowKeys: form.numbers, onChange: onSelectChange }"
:columns="columns"
:scroll="{ y: '20vh', x: 500 }"
:pagination="false"
:data-source="groupNumbers"
>
</a-table>
</a-form-item>
<a-form-item label="متن پیام" name="message" class="md:col-span-4">
<BasicTextArea v-model:value="form.message" />
<div class="md:flex items-center justify-between mt-2 md:flex-row-reverse">
<BaseButton @click="addName()" type="primary" class="w-full md:w-10rem"
>افزودن نام کاربر</BaseButton
>
<div class="text-gray-500">
برای استفاده از نام اشخاص در پیام از گزینه افزودن نام کاربر استفاده کنید این متغیر با
عنوان( name% )به متن پیام اضافه میگردد
</div>
</div>
</a-form-item>
<a-form-item class="col-span-4">
<!-- <NumbersTable
v-if="selectedGroup.length > 0"
type="check"
:items="form.numbers"
:filter="selectedGroup"
/>-->
</a-form-item>
</a-form>
</BasicModal>
</template>
<script setup lang="ts">
// import BasicInfoModal from '@/components/BasicInfoModal'
import { ref } 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 type { Customer } from '@/model/customer'
import { createSMS, getCustomerList, getGroupsList } from '@/api/sms'
import { createSMS, getGroupsList } from '@/api/sms'
import type { Sms } from '@/model/sms'
import { isMobile } from '@core/utils/is'
//loading
const loading = ref<boolean>(false)
const selectedGroup = ref<string[]>([])
const groupItems = ref<FunctionItem[]>()
const groupNumbers = ref<Customer[]>()
let baseGroupNumbers: Customer[] = []
const table = ref<null | { fetch: () => void }>(null)
const smsEditorRef = ref<{
validateFields?: () => ReturnType<NonNullable<FormInstance['validateFields']>>
resetAll?: () => void
}>()
const form = ref<Sms>({
message: '',
numbers: [],
single: []
})
const addSMSVisible = ref<boolean>(false)
onMounted(() => getGroups())
@@ -125,160 +62,50 @@ async function getGroups() {
loading.value = true
groupItems.value = await getGroupsList()
} catch (e) {
console.log(e)
console.error(e)
} finally {
loading.value = false
}
}
const addName = () => {
form.value.message = form.value.message.concat('', '%name')
function refreshTable() {
table.value?.fetch()
}
function filterNumbers(val: string) {
// console.log(val)
if (val !== '') {
groupNumbers.value = baseGroupNumbers?.filter((v) => v.name.includes(val))
} else groupNumbers.value = baseGroupNumbers
function closeSmsSheet() {
addSMSVisible.value = false
}
// const refreshTable = () => table?.value?.fetch()
async function getGroupNumbers(val: string[]) {
if (val) {
try {
loading.value = true
const { customers } = await getCustomerList(val)
groupNumbers.value = customers
baseGroupNumbers = customers
} catch (e) {
console.log(e)
} finally {
loading.value = false
}
}
}
watch(selectedGroup, (val) => {
console.log(val)
if (val) {
getGroupNumbers(val)
}
})
const table = ref(null)
const refreshTable = () => table?.value?.fetch()
const activeKey = ref(1)
let updateKey = ref<number>(0)
/*
function onselected(val) {
console.log()
}
*/
const columns = [
{
title: 'کد کاربر',
dataIndex: 'id'
},
{
title: 'نام و نام خانوادگی',
dataIndex: 'name'
},
{
title: 'شماره همراه',
dataIndex: 'mobile'
}
]
const onSelectChange = (changableRowKeys: string[]) => {
console.log('selectedRowKeys changed: ', changableRowKeys)
// if (changableRowKeys !== undefined) {
form.value.numbers = changableRowKeys
// }
}
//role confirm items
//form
const form = ref<Sms>({
message: '',
numbers: [],
single: []
})
const formRef = ref<FormInstance>()
//edit
let editId: number | undefined = undefined
//add
async function add() {
loading.value = true
editId = undefined
// await store.getUsersList()
addSMSVisible.value = true
updateKey.value++
resetData()
loading.value = false
}
//reset data
const resetData = () => {
formRef.value?.resetFields()
function resetData() {
form.value = {
message: '',
numbers: [],
single: []
}
groupNumbers.value = []
selectedGroup.value = []
smsEditorRef.value?.resetAll?.()
}
//add project modal
const addSMSVisible = ref<boolean>(false)
const priceConfirmVisible = ref<boolean>(false)
//submit user
function ok() {
formRef.value?.validateFields().then(async () => {
smsEditorRef.value?.validateFields?.().then(async () => {
try {
loading.value = true
if (!form.value.numbers) form.value.numbers = []
form.value.numbers?.push(...(form.value.single ?? []))
// console.log(form.value.numbers)
// delete form.value.single
form.value.numbers.push(...(form.value.single ?? []))
await createSMS(form.value)
// form.value.single = []
resetData()
addSMSVisible.value = false
refreshTable()
message.success('با موفقیت ارسال شد')
} catch (e) {
console.log(e)
console.error(e)
} finally {
loading.value = false
}
})
}
/*async function ok() {
loading.value = true
try {
await createPrice(form.value)
refreshTable()
priceConfirmVisible.value = false
addSMSVisible.value = false
// await confirmActOnApi('آیا از اعمال قیمت جدید اطمینان دارید؟', form.value, createPrice)
} catch (e) {
console.error(e)
// message.error('خطا در بارگذاری اطلاعات')
} finally {
loading.value = false
}
}*/
</script>
<style>
.smsSelector .ant-select-selector {
max-height: 5vh;
overflow-y: auto;
}
</style>
@@ -0,0 +1,65 @@
<template>
<MobileCardList
v-model:search="search"
:loading="loading"
:has-more="hasMore"
:show-empty="!loading && items.length === 0"
empty-text="گزارشی یافت نشد"
search-placeholder="جستجو گزارش"
show-refresh
@search="reload"
@refresh="reload"
@load-more="loadMore"
>
<TransactionMobileListItem
v-for="item in items"
:key="item.id"
:item="item"
:hide-customer="!!customerId"
@open="openSheet"
/>
</MobileCardList>
<TransactionMobileDetailSheet v-model:visible="sheetVisible" :item="selectedItem" />
</template>
<script setup lang="ts">
import { getTransactionTable } from '@/api/transaction'
import type { Transaction } from '@/model/transaction'
import MobileCardList from '@/components/mobile/MobileCardList.vue'
import { useMobileDetailSheet } from '@/components/mobile/composables/useMobileDetailSheet'
import { useMobileTableList } from '@/components/mobile/composables/useMobileTableList'
import type { TableInput } from '@core/type/baseTable'
import TransactionMobileDetailSheet from './TransactionMobileDetailSheet.vue'
import TransactionMobileListItem from './TransactionMobileListItem.vue'
const props = defineProps<{
customerId?: number
}>()
const { sheetVisible, selectedItem, openSheet, syncSelectedItem } = useMobileDetailSheet<Transaction>()
function fetchTransactions(params: TableInput) {
if (props.customerId) {
params.filters = params.filters ?? []
params.filters.push({
field: 'customer_id',
type: 'text',
value: props.customerId,
cr: 'and',
op: 'eq',
hidden: false
})
}
return getTransactionTable(params)
}
const { loading, search, items, hasMore, reload, loadMore, fetch } = useMobileTableList<Transaction>({
fetchApi: fetchTransactions,
onFetched: (rows) => syncSelectedItem(rows)
})
onMounted(() => reload())
defineExpose({ fetch, openSheet })
</script>
@@ -0,0 +1,101 @@
<template>
<MobileDetailSheet v-model:visible="visibleProxy" :item="item" :title="sheetTitle">
<template #default="{ item: row }">
<MobileSheetHeader :title="sheetTitle" :subtitle="dateLabel(row)" :show-title="isMobile" />
<div class="flex items-center gap-2">
<span class="rounded-lg border px-2 py-0.5 text-xs font-600" :class="typeTagClass(row)">
{{ typeLabel(row) }}
</span>
</div>
<div class="space-y-2">
<MobileStatCard
bordered
:variant="(row.gold ?? 0) > 0 ? 'success' : 'danger'"
label="مقدار طلا (گرم)"
:value="goldLabel(row)"
>
<template #prefix>
<BaseIcon icon="vuesax-linear:coin" size="18" :color="(row.gold ?? 0) > 0 ? '#15803d' : '#dc2626'" />
</template>
</MobileStatCard>
<MobileStatCard
bordered
:variant="(row.cash ?? 0) > 0 ? 'success' : 'danger'"
label="مقدار پول (ریال)"
:value="usePrice(row.cash ?? 0, true, '')"
>
<template #prefix>
<BaseIcon icon="vuesax-linear:empty-wallet" size="18" :color="(row.cash ?? 0) > 0 ? '#15803d' : '#dc2626'" />
</template>
</MobileStatCard>
</div>
<MobileInfoGrid>
<MobileDetailRow label="کد تراکنش" :value="row.id ?? '-'" />
<MobileDetailRow label="نام تراکنش" :value="row.type_str || '-'" />
<MobileDetailRow label="علت تراکنش" full-width :value="noteLabel(row)" />
<MobileDetailRow label="نام مشتری" full-width :value="row.customer?.name || '-'" />
<MobileDetailRow label="تاریخ تراکنش" :value="dateLabel(row)" />
</MobileInfoGrid>
</template>
</MobileDetailSheet>
</template>
<script setup lang="ts">
import type { Transaction } from '@/model/transaction'
import MobileDetailRow from '@/components/mobile/MobileDetailRow.vue'
import MobileDetailSheet from '@/components/mobile/MobileDetailSheet.vue'
import MobileInfoGrid from '@/components/mobile/MobileInfoGrid.vue'
import MobileSheetHeader from '@/components/mobile/MobileSheetHeader.vue'
import MobileStatCard from '@/components/mobile/MobileStatCard.vue'
import { isMobile } from '@core/utils/is'
import { toJalaliNoTime } from '@core/utils/useDataTime'
import { usePrice } from '@core/utils/usePrice'
const props = defineProps<{
visible: boolean
item: Transaction | null
}>()
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
}>()
const visibleProxy = computed({
get: () => props.visible,
set: (value: boolean) => emit('update:visible', value)
})
const sheetTitle = computed(() => props.item?.type_str || `تراکنش #${props.item?.id ?? '-'}`)
function isMoneyType(row: Transaction) {
return row.type === 4 || row.type === 3
}
function typeLabel(row: Transaction) {
return isMoneyType(row) ? 'پول' : 'طلا'
}
function typeTagClass(row: Transaction) {
return isMoneyType(row)
? 'border-green-300 bg-green-50 text-green-700'
: 'border-amber-300 bg-amber-50 text-amber-700'
}
function dateLabel(row: Transaction) {
return row.created_at ? toJalaliNoTime(row.created_at) : '-'
}
function goldLabel(row: Transaction) {
const gold = row.gold
if (gold == null) return '-'
return `${Number(Number(gold).toFixed(3))} گرم`
}
function noteLabel(row: Transaction) {
if (row.note == null) return '-'
return String(row.note)
}
</script>
@@ -0,0 +1,43 @@
<template>
<MobileCard clickable @click="$emit('open', item)">
<div class="space-y-1">
<div class="flex items-center justify-between gap-2">
<div class="flex min-w-0 items-center gap-2">
<span class="shrink-0 rounded-md border px-1.5 py-0.5 text-xs font-600" :class="typeTagClass">
{{ typeLabel }}
</span>
<span class="truncate text-sm font-700 text-gray-900">{{ item.type_str || '-' }}</span>
</div>
<span class="shrink-0 text-xs text-gray-400">{{ dateLabel }}</span>
</div>
<div v-if="!hideCustomer" class="truncate text-xs text-gray-600">{{ customerName }}</div>
</div>
</MobileCard>
</template>
<script setup lang="ts">
import type { Transaction } from '@/model/transaction'
import MobileCard from '@/components/mobile/MobileCard.vue'
import { toJalaliNoTime } from '@core/utils/useDataTime'
const props = defineProps<{
item: Transaction
hideCustomer?: boolean
}>()
defineEmits<{
(e: 'open', item: Transaction): void
}>()
const isMoneyType = computed(() => props.item.type === 4 || props.item.type === 3)
const typeLabel = computed(() => (isMoneyType.value ? 'پول' : 'طلا'))
const typeTagClass = computed(() =>
isMoneyType.value
? 'border-green-300 bg-green-50 text-green-700'
: 'border-amber-300 bg-amber-50 text-amber-700'
)
const customerName = computed(() => props.item.customer?.name || '-')
const dateLabel = computed(() => (props.item.created_at ? toJalaliNoTime(props.item.created_at) : '-'))
</script>
@@ -1,7 +1,8 @@
<template>
<div>
<div class="h-full min-h-0 min-w-0 max-w-full overflow-hidden">
<BasicShowContext title="لیست گزارشات">
<BaseTable v-bind="tblProps" @register="submit" ref="tbl">
<div class="flex h-full min-h-0 flex-col">
<BaseTable v-if="!isMobile" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl">
<!-- <template #extraHeader>
<BaseButton type="primary" size="small" class="flex" @click="emits('add')">
<div>افزودن کیف پول</div>
@@ -49,6 +50,14 @@
</div>
</template>
</BaseTable>
<TransactionMobileCardList
v-else
ref="mobileListRef"
class="min-h-0 flex-1"
:customer-id="customerId"
/>
</div>
</BasicShowContext>
</div>
</template>
@@ -63,17 +72,18 @@ import {useUserStore} from '@/store/userStore'
import {usePrefix} from '@core/composable/usePrefix'
import {message} from 'ant-design-vue'
import {getWalletTable} from '@/api/wallet'
import {getTransactionTable} from '@/api/transaction'
import {getSMSTable} from "@/api/sms";
import { getTransactionTable } from '@/api/transaction'
import TransactionMobileCardList from './TransactionMobileCardList.vue'
import { isMobile } from '@core/utils/is'
interface Prop {
customerId: number
customerId?: number
}
const props = defineProps<Prop>()
const [submit, reload] = useBaseTable()
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
/*const select = (val) => {
val.sts === 1 ? return ['1'] : ['2']
}*/
@@ -124,9 +134,8 @@ const loading = ref<boolean>(false)
}
}*/
const fetch = () => {
// console.log('sss')
reload()
async function fetch() {
await Promise.allSettled([reload(), mobileListRef.value?.fetch?.() as Promise<void>])
}
// const spinTable = (val: boolean) => {
// // console.log('sss')
@@ -0,0 +1,55 @@
<template>
<MobileCardList
v-model:search="search"
:loading="loading"
:has-more="hasMore"
:show-empty="!loading && items.length === 0"
empty-text="مدیری یافت نشد"
search-placeholder="جستجو مدیر"
show-refresh
@search="reload"
@refresh="reload"
@load-more="loadMore"
>
<ManagerMobileListItem
v-for="item in items"
:key="item.id"
:item="item"
@open="openSheet"
@edit="(row) => emit('edit', row)"
/>
</MobileCardList>
<ManagerMobileDetailSheet
v-model:visible="sheetVisible"
:item="selectedItem"
@delete="(id) => emit('delete', id)"
/>
</template>
<script setup lang="ts">
import { getUserTable } from '@/api/user'
import type { User } from '@/model/user'
import MobileCardList from '@/components/mobile/MobileCardList.vue'
import { useMobileDetailSheet } from '@/components/mobile/composables/useMobileDetailSheet'
import { useMobileTableList } from '@/components/mobile/composables/useMobileTableList'
import ManagerMobileDetailSheet from './ManagerMobileDetailSheet.vue'
import ManagerMobileListItem from './ManagerMobileListItem.vue'
const emit = defineEmits<{
(e: 'info', item: User): void
(e: 'edit', item: User): void
(e: 'delete', id: number): void
}>()
const { sheetVisible, selectedItem, openSheet, syncSelectedItem } = useMobileDetailSheet<User>()
const { loading, search, items, hasMore, reload, loadMore, fetch } = useMobileTableList<User>({
fetchApi: getUserTable,
onFetched: (rows) => syncSelectedItem(rows)
})
onMounted(() => reload())
defineExpose({ fetch, openSheet })
</script>
@@ -0,0 +1,64 @@
<template>
<MobileDetailSheet v-model:visible="visibleProxy" :item="item" :title="sheetTitle">
<template #default="{ item: row }">
<MobileSheetHeader :title="sheetTitle" :subtitle="mobileLabel(row)" :show-title="isMobile">
<template #title>
<div class="flex w-full items-center justify-between gap-3">
<span class="min-w-0 truncate">{{ sheetTitle }}</span>
<MobileIconButton
plain
icon="vuesax-linear:user-remove"
color="#EB3535"
:size="22"
@click="row.id && $emit('delete', row.id)"
/>
</div>
</template>
</MobileSheetHeader>
<MobileInfoGrid>
<MobileDetailRow label="کد کاربری" :value="row.id ?? '-'" />
<MobileDetailRow label="نام و نام خانوادگی" full-width :value="row.name || '-'" />
<MobileDetailRow label="شماره موبایل" full-width :value="mobileLabel(row)" />
<MobileDetailRow v-if="row.created_at" label="تاریخ ثبت" full-width :value="createdAtLabel(row)" />
</MobileInfoGrid>
</template>
</MobileDetailSheet>
</template>
<script setup lang="ts">
import type { User } from '@/model/user'
import MobileDetailRow from '@/components/mobile/MobileDetailRow.vue'
import MobileDetailSheet from '@/components/mobile/MobileDetailSheet.vue'
import MobileIconButton from '@/components/mobile/MobileIconButton.vue'
import MobileInfoGrid from '@/components/mobile/MobileInfoGrid.vue'
import MobileSheetHeader from '@/components/mobile/MobileSheetHeader.vue'
import { isMobile } from '@core/utils/is'
import { toJalaliNoTime } from '@core/utils/useDataTime'
const props = defineProps<{
visible: boolean
item: User | null
}>()
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
(e: 'delete', id: number): void
}>()
const visibleProxy = computed({
get: () => props.visible,
set: (value: boolean) => emit('update:visible', value)
})
const sheetTitle = computed(() => props.item?.name || `مدیر #${props.item?.id ?? '-'}`)
function mobileLabel(row: User) {
if (row.mobile == null || row.mobile === '') return '-'
return String(row.mobile)
}
function createdAtLabel(row: User) {
return row.created_at ? toJalaliNoTime(row.created_at) : '-'
}
</script>
@@ -0,0 +1,41 @@
<template>
<MobileCard clickable @click="$emit('open', item)">
<div class="flex items-center gap-2">
<div class="min-w-0 flex-1 space-y-1">
<div class="flex items-center justify-between gap-2">
<span class="truncate text-sm font-700 text-gray-900">{{ item.name || '-' }}</span>
<span class="shrink-0 text-xs text-gray-400">#{{ item.id ?? '-' }}</span>
</div>
<div class="truncate text-xs text-gray-600">{{ mobileLabel }}</div>
</div>
<MobileIconButton
icon="vuesax-linear:edit"
color="#4572E9"
:size="20"
@click="$emit('edit', item)"
/>
</div>
</MobileCard>
</template>
<script setup lang="ts">
import type { User } from '@/model/user'
import MobileCard from '@/components/mobile/MobileCard.vue'
import MobileIconButton from '@/components/mobile/MobileIconButton.vue'
const props = defineProps<{
item: User
}>()
defineEmits<{
(e: 'open', item: User): void
(e: 'edit', item: User): void
}>()
const mobileLabel = computed(() => {
if (props.item.mobile == null || props.item.mobile === '') return '-'
return String(props.item.mobile)
})
</script>
+27 -58
View File
@@ -1,7 +1,15 @@
<template>
<div>
<div class="h-full min-h-0 min-w-0 max-w-full overflow-hidden">
<BasicShowContext title="لیست مدیران">
<BaseTable v-bind="tblProps" @register="submit" ref="tbl">
<template #extra>
<BaseButton type="primary" size="small" class="flex shrink-0 md:hidden" @click="emits('add')">
<div>افزودن مدیر</div>
<BaseIcon icon="vuesax-linear:user-add" Size="16px" />
</BaseButton>
</template>
<div class="flex h-full min-h-0 flex-col">
<BaseTable v-if="!isMobile" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl">
<template #extraHeader>
<BaseButton type="primary" size="small" class="flex" @click="emits('add')">
<div>افزودن مدیر</div>
@@ -10,20 +18,6 @@
</template>
<template #action="{ column, record }">
<div class="gap-2 flex justify-center">
<!-- <a-tooltip
:key="record.active"
placement="bottom"
class="cursor-pointer"
@click="changeUserActivationFunction(record.id)"
>
<template #title>
<span>{{ record.active ? 'غیرفعال کردن' : 'فعال کردن' }}</span>
</template>
<Icon
:icon="record.active ? 'vuesax-linear:tick-circle' : 'vuesax-linear:close-circle'"
:color="record.active ? '#54AD06' : '#ad0627'"
/>
</a-tooltip>-->
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('info', record)">
<template #title>
<span>مشخصات</span>
@@ -45,6 +39,16 @@
</div>
</template>
</BaseTable>
<ManagerMobileCardList
v-else
ref="mobileListRef"
class="min-h-0 flex-1"
@info="(row) => emits('info', row)"
@edit="(row) => emits('edit', row)"
@delete="(id) => emits('delete', id)"
/>
</div>
</BasicShowContext>
</div>
</template>
@@ -52,64 +56,29 @@
<script lang="tsx" setup>
import type { TableInput } from '@core/type/baseTable'
import useBaseTable from '@core/components/BaseTableOld/hooks/useBaseTable'
import { toJalali, toJalaliNoTime } from '@core/utils/useDataTime'
import { message, Tag } from 'ant-design-vue'
import CustomTag from '@/components/CustomTag.vue'
import Icon from '@core/components/BaseIcon/src/Icon.vue'
import { changeUserActivation, getUserTable } from '@/api/user'
import { useUserStore } from '@/store/userStore'
import { getUserTable } from '@/api/user'
import { cols } from '../hooks/usersColumns'
import { changeCustomerActivation } from '@/api/customer'
import ManagerMobileCardList from './ManagerMobileCardList.vue'
import { isMobile } from '@core/utils/is'
const [submit, reload] = useBaseTable()
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
//emits
const emits = defineEmits(['delete', 'edit', 'add', 'info'])
//define table
const table = ref(null)
//permission
function checkPermission(temp: string) {
const store = useUserStore()
return store.canAccess(temp)
}
const loading = ref<boolean>(false)
async function changeUserActivationFunction(id: number) {
try {
loading.value = true
await changeUserActivation(id)
reload()
message.success('تغییر وضعیت با موفقیت انجام شد')
} catch (e) {
console.error(e)
} finally {
loading.value = false
}
}
const tblProps = ref<any>({
columns: cols,
api: (params: TableInput) => {
return getUserTable(params)
}
//bordered: true
})
const fetch = () => {
// console.log('sss')
reload()
async function fetch() {
await Promise.allSettled([reload(), mobileListRef.value?.fetch?.() as Promise<void>])
}
// const spinTable = (val: boolean) => {
// // console.log('sss')
// table?.value?.loading.value = val
// }
defineExpose({
fetch
// spinTable
})
</script>
<style scoped></style>
@@ -0,0 +1,67 @@
<template>
<MobileCardList
v-model:search="search"
:loading="loading"
:has-more="hasMore"
:show-empty="!loading && items.length === 0"
empty-text="کیف پولی یافت نشد"
search-placeholder="جستجو کیف پول"
show-refresh
@search="reload"
@refresh="reload"
@load-more="loadMore"
>
<WalletMobileListItem
v-for="item in items"
:key="item.id"
:item="item"
@open="openSheet"
/>
</MobileCardList>
<WalletMobileDetailSheet
v-model:visible="sheetVisible"
:item="selectedItem"
/>
</template>
<script setup lang="ts">
import { getWalletTable } from '@/api/wallet'
import type { Wallet } from '@/model/wallet'
import MobileCardList from '@/components/mobile/MobileCardList.vue'
import { useMobileDetailSheet } from '@/components/mobile/composables/useMobileDetailSheet'
import { useMobileTableList } from '@/components/mobile/composables/useMobileTableList'
import type { TableInput, TableOutput } from '@core/type/baseTable'
import WalletMobileDetailSheet from './WalletMobileDetailSheet.vue'
import WalletMobileListItem from './WalletMobileListItem.vue'
type WalletTableResponse = TableOutput<Wallet> & {
sums?: {
cash?: number
gold?: number
cashToGold?: number
goldToCash?: number
}
}
const emit = defineEmits<{
(e: 'sums', sums: WalletTableResponse['sums']): void
}>()
const { sheetVisible, selectedItem, openSheet, syncSelectedItem } = useMobileDetailSheet<Wallet>()
async function fetchWallet(params: TableInput) {
const data = (await getWalletTable(params)) as WalletTableResponse
emit('sums', data?.sums)
return data
}
const { loading, search, items, hasMore, reload, loadMore, fetch } = useMobileTableList<Wallet>({
fetchApi: fetchWallet,
onFetched: (rows) => syncSelectedItem(rows)
})
onMounted(() => reload())
defineExpose({ fetch, openSheet })
</script>
@@ -0,0 +1,59 @@
<template>
<MobileDetailSheet v-model:visible="visibleProxy" :item="item" :title="sheetTitle">
<template #default="{ item: row }">
<MobileSheetHeader :title="sheetTitle" :show-title="isMobile" />
<div class="space-y-2">
<MobileStatCard bordered variant="success" label="موجودی ریالی" :value="usePrice(row.cash ?? 0, true, '')">
<template #prefix>
<BaseIcon icon="vuesax-linear:empty-wallet" size="18" color="#15803d" />
</template>
</MobileStatCard>
<MobileStatCard bordered variant="warning" label="موجودی طلایی" :value="goldLabel(row)">
<template #prefix>
<BaseIcon icon="vuesax-linear:coin" size="18" color="#b45309" />
</template>
</MobileStatCard>
</div>
<MobileInfoGrid>
<MobileDetailRow label="کد کیف پول" :value="row.id ?? '-'" />
<MobileDetailRow label="نام مشتری" :value="row.customer?.name || '-'" />
<MobileDetailRow label="شماره موبایل" :value="row.customer?.mobile ?? '-'" />
</MobileInfoGrid>
</template>
</MobileDetailSheet>
</template>
<script setup lang="ts">
import type { Wallet } from '@/model/wallet'
import MobileDetailRow from '@/components/mobile/MobileDetailRow.vue'
import MobileDetailSheet from '@/components/mobile/MobileDetailSheet.vue'
import MobileInfoGrid from '@/components/mobile/MobileInfoGrid.vue'
import MobileSheetHeader from '@/components/mobile/MobileSheetHeader.vue'
import MobileStatCard from '@/components/mobile/MobileStatCard.vue'
import { isMobile } from '@core/utils/is'
import { usePrice } from '@core/utils/usePrice'
const props = defineProps<{
visible: boolean
item: Wallet | null
}>()
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
}>()
const visibleProxy = computed({
get: () => props.visible,
set: (value: boolean) => emit('update:visible', value)
})
const sheetTitle = computed(() => props.item?.customer?.name || `کیف پول #${props.item?.id ?? '-'}`)
function goldLabel(row: Wallet) {
const gold = row.gold
if (gold == null) return '-'
return `${Number(Number(gold).toFixed(3))} گرم`
}
</script>
@@ -0,0 +1,43 @@
<template>
<MobileCard clickable @click="$emit('open', item)">
<div class="space-y-1">
<div class="flex items-center justify-between gap-2">
<span class="min-w-0 truncate text-sm font-700 text-gray-900">{{ customerName }}</span>
<span class="shrink-0 text-xs text-gray-400">#{{ item.id ?? '-' }}</span>
</div>
<div class="flex items-center justify-between gap-2 text-xs text-gray-600">
<span>
<span class="text-gray-500">ریال:</span>
<span class="font-600 text-gray-900">{{ cashLabel }}</span>
</span>
<span>
<span class="text-gray-500">طلا:</span>
<span class="font-700 text-gray-900">{{ goldLabel }}</span>
</span>
</div>
</div>
</MobileCard>
</template>
<script setup lang="ts">
import type { Wallet } from '@/model/wallet'
import MobileCard from '@/components/mobile/MobileCard.vue'
import { usePrice } from '@core/utils/usePrice'
const props = defineProps<{
item: Wallet
}>()
defineEmits<{
(e: 'open', item: Wallet): void
}>()
const customerName = computed(() => props.item.customer?.name || '-')
const cashLabel = computed(() => usePrice(props.item.cash ?? 0, true, ''))
const goldLabel = computed(() => {
const gold = props.item.gold
if (gold == null) return '-'
return `${Number(Number(gold).toFixed(3))} گرم`
})
</script>
+81 -128
View File
@@ -1,110 +1,73 @@
<template>
<div>
<div class="h-full min-h-0">
<BasicShowContext title="لیست کیف پول">
<BaseTable v-bind="tblProps" @register="submit" ref="tbl">
<!-- <template #extraHeader>
<BaseButton type="primary" size="small" class="flex" @click="emits('add')">
<div>افزودن کیف پول</div>
<BaseIcon icon="vuesax-linear:wallet-add" Size="16px" />
</BaseButton>
</template>-->
<template #extraHeader>
<div class="w-60vw hidden md:block">
<div class="grid grid-cols-2 gap-3 w-full py-2 px-5">
<div
:class="
sums?.cash >= 0
? sums?.cash === 0
? 'bg-amber-100'
: 'bg-green-100'
: 'bg-red-100'
"
class="rounded-lg p-2"
>
<div class="mb-1">مجموع موجودی کیف پول:</div>
<div class="flex gap-2">
<div
class="gap-5 rounded-lg border-1 border-solid border-gray flex-1 p-1 text-center"
>
<div class="font-bold">{{ usePrice(sums?.cash ?? 0) }}</div>
</div>
<div
class="gap-5 rounded-lg border-1 border-solid border-gray flex-1 p-1 text-center"
>
<div class="font-bold">
{{ usePrice(sums?.cashToGold.toFixed(2) ?? 0, true, 'گرم') }}
</div>
</div>
</div>
<div class="flex h-full min-h-0 flex-col gap-3">
<div class="hidden shrink-0 md:grid md:grid-cols-1 md:gap-3 lg:grid-cols-2">
<div
class="rounded-lg p-2.5 text-sm"
:class="walletSumTone(sums?.cash)"
>
<div class="mb-2 font-medium">مجموع موجودی کیف پول</div>
<div class="grid grid-cols-2 gap-2">
<div class="rounded-lg border border-gray-200 bg-white/70 px-2 py-1.5 text-center">
<div class="text-xs text-gray-500">ریال</div>
<div class="mt-1 font-bold break-all">{{ usePrice(sums?.cash ?? 0) }}</div>
</div>
<div
:class="
sums?.gold >= 0
? sums?.gold === 0
? 'bg-amber-100'
: 'bg-green-100'
: 'bg-red-100'
"
class="rounded-lg p-2"
>
<div class="mb-1">مجموع موجودی کیف طلا:</div>
<div class="flex gap-2">
<div
class="gap-5 rounded-lg border-1 border-solid border-gray flex-1 p-1 text-center"
>
<div class="font-bold">{{ usePrice(sums?.gold ?? 0, true, 'گرم') }}</div>
</div>
<div
class="gap-5 rounded-lg border-1 border-solid border-gray flex-1 p-1 text-center"
>
<div class="font-bold">{{ usePrice(sums?.goldToCash ?? 0) }}</div>
</div>
<div class="rounded-lg border border-gray-200 bg-white/70 px-2 py-1.5 text-center">
<div class="text-xs text-gray-500">گرم</div>
<div class="mt-1 font-bold">
{{ usePrice(Number(sums?.cashToGold ?? 0).toFixed(2), true, 'گرم') }}
</div>
</div>
</div>
</div>
</template>
<template #action="{ column, record }">
<div class="gap-2 flex justify-center">
<!-- <a-tooltip
:key="record.active"
placement="bottom"
class="cursor-pointer"
@click="changeCustomerActivationFunction(record.id)"
>
<template #title>
<span>{{ record.active ? 'غیرفعال کردن' : 'فعال کردن' }}</span>
</template>
<Icon
:icon="record.active ? 'vuesax-linear:tick-circle' : 'vuesax-linear:close-circle'"
:color="record.active ? '#54AD06' : '#ad0627'"
/>
</a-tooltip>-->
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('info', record)">
<template #title>
<span>مشخصات</span>
</template>
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
</a-tooltip>
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('edit', record)">
<template #title>
<span>ویرایش</span>
</template>
<Icon icon="vuesax-linear:edit" color="#4572E9" />
</a-tooltip>
<!-- <a-tooltip
placement="bottom"
class="cursor-pointer"
@click="emits('delete', record.id)"
>
<template #title>
<span>حذف</span>
</template>
<Icon icon="vuesax-linear:trash" color="#EB3535" />
</a-tooltip>-->
<div
class="rounded-lg p-2.5 text-sm"
:class="walletSumTone(sums?.gold)"
>
<div class="mb-2 font-medium">مجموع موجودی کیف طلا</div>
<div class="grid grid-cols-2 gap-2">
<div class="rounded-lg border border-gray-200 bg-white/70 px-2 py-1.5 text-center">
<div class="text-xs text-gray-500">گرم</div>
<div class="mt-1 font-bold">
{{ usePrice(sums?.gold ?? 0, true, 'گرم') }}
</div>
</div>
<div class="rounded-lg border border-gray-200 bg-white/70 px-2 py-1.5 text-center">
<div class="text-xs text-gray-500">ریال</div>
<div class="mt-1 font-bold break-all">{{ usePrice(sums?.goldToCash ?? 0) }}</div>
</div>
</div>
</div>
</template>
</BaseTable>
</div>
<BaseTable v-if="!isMobile" class="min-h-0 min-w-0 flex-1" v-bind="tblProps" @register="submit" ref="tbl">
<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>
</template>
<Icon icon="vuesax-linear:stickynote" color="#10913A" />
</a-tooltip>
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('edit', record)">
<template #title>
<span>ویرایش</span>
</template>
<Icon icon="vuesax-linear:edit" color="#4572E9" />
</a-tooltip>
</div>
</template>
</BaseTable>
<WalletMobileCardList
v-else
ref="mobileListRef"
class="min-h-0 flex-1"
@sums="onMobileSums"
/>
</div>
</BasicShowContext>
</div>
</template>
@@ -113,30 +76,21 @@
import type { TableInput } from '@core/type/baseTable'
import useBaseTable from '@core/components/BaseTableOld/hooks/useBaseTable'
import Icon from '@core/components/BaseIcon/src/Icon.vue'
import { changeCustomerActivation, getCustomerTable } from '@/api/customer'
import { changeCustomerActivation } from '@/api/customer'
import { cols } from '../hooks/walletColumns'
import { useUserStore } from '@/store/userStore'
import { usePrefix } from '@core/composable/usePrefix'
import { message } from 'ant-design-vue'
import { getWalletTable } from '@/api/wallet'
import { usePrice } from '../../../../core/utils/usePrice'
import WalletMobileCardList from './WalletMobileCardList.vue'
import { isMobile } from '@core/utils/is'
const [submit, reload] = useBaseTable()
/*const select = (val) => {
val.sts === 1 ? return ['1'] : ['2']
}*/
//emits
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
const emits = defineEmits(['delete', 'edit', 'add', 'info', 'sums'])
const { prefixCls } = usePrefix('pophoverstatus')
//permission
/*function checkPermission(temp: string) {
const store = useUserStore()
return store.canAccess(temp)
}*/
//define table
const table = ref(null)
const sums = ref<any>()
@@ -146,13 +100,23 @@ const tblProps = ref<any>({
const data = await getWalletTable(params)
sums.value = data?.sums
emits('sums', data?.sums)
console.log(sums.value)
return data
}
//bordered: true
})
const loading = ref<boolean>(false)
function walletSumTone(value?: number) {
if (value == null || value >= 0) {
return value === 0 ? 'bg-amber-100' : 'bg-green-100'
}
return 'bg-red-100'
}
function onMobileSums(payload: unknown) {
sums.value = payload
emits('sums', payload)
}
async function changeCustomerActivationFunction(id: number) {
try {
loading.value = true
@@ -166,17 +130,12 @@ async function changeCustomerActivationFunction(id: number) {
}
}
const fetch = () => {
// console.log('sss')
reload()
async function fetch() {
await Promise.allSettled([reload(), mobileListRef.value?.fetch?.() as Promise<void>])
}
// const spinTable = (val: boolean) => {
// // console.log('sss')
// table?.value?.loading.value = val
// }
defineExpose({
fetch
// spinTable
})
</script>
@@ -192,11 +151,5 @@ defineExpose({
.ant-menu-item-only-child {
border-radius: @border-radius-base;
}
//bor
/* .ant-input-group-addon:first-child {
!* border: #d9d9d9 1px solid;
border-radius: 0 6px 6px 0;*!
}*/
}
</style>
+6 -4
View File
@@ -1,7 +1,8 @@
<template>
<div>
<div class="h-full min-h-0">
<BasicMainCard>
<div class="md:hidden">
<div class="flex h-full min-h-0 flex-col">
<div class="shrink-0 md:hidden">
<!-- <div class="bg-gray-100 grid grid-cols-2 gap-3 w-full py-2 px-5">
<div class="gap-5 text-xs">
<div>مجموع موجودی کیف پول:</div>
@@ -70,10 +71,10 @@
</div>
</div>
</div>
<div class="flex w-full h-full">
<div class="flex min-h-0 flex-1 flex-col">
<tbl
ref="table"
class="w-full h-full"
class="h-full min-h-0 w-full"
@info="info"
@edit="edit"
@sums="setSums"
@@ -85,6 +86,7 @@
<component :is="selectedComponent" class="mr-5" />-->
<!-- <BasicShowContext title="پروژه ها"> <Search /> </BasicShowContext>-->
</div>
</div>
</BasicMainCard>
<BasicModal
v-model:visible="addUserVisible"
@@ -0,0 +1,72 @@
<template>
<MobileCardList
v-model:search="search"
:loading="loading"
:has-more="hasMore"
:show-empty="!loading && items.length === 0"
empty-text="تراکنشی یافت نشد"
search-placeholder="جستجو برداشت و واریز"
show-refresh
@search="reload"
@refresh="reload"
@load-more="loadMore"
>
<WithdrawMobileListItem
v-for="item in items"
:key="item.id"
:item="item"
@open="openSheet"
/>
</MobileCardList>
<WithdrawMobileDetailSheet
v-model:visible="sheetVisible"
:item="selectedItem"
@withdraw="(row) => emit('withdraw', row)"
@weight="(row) => emit('weight', row)"
@change-status="changeStatusHandler"
/>
</template>
<script setup lang="ts">
import { message } from 'ant-design-vue'
import { getWithdrawTable, updateSts } from '@/api/wallet'
import type { Withdraw } from '@/model/wallet'
import MobileCardList from '@/components/mobile/MobileCardList.vue'
import { useMobileDetailSheet } from '@/components/mobile/composables/useMobileDetailSheet'
import { useMobileTableList } from '@/components/mobile/composables/useMobileTableList'
import { status } from '@/pages/menuPages/withdraw/information/hooks/status'
import WithdrawMobileDetailSheet from './WithdrawMobileDetailSheet.vue'
import WithdrawMobileListItem from './WithdrawMobileListItem.vue'
const emit = defineEmits<{
(e: 'withdraw', item: Withdraw): void
(e: 'weight', item: Withdraw): void
}>()
const { sheetVisible, selectedItem, openSheet, syncSelectedItem } = useMobileDetailSheet<Withdraw>()
const { loading, search, items, hasMore, reload, loadMore, fetch } = useMobileTableList<Withdraw>({
fetchApi: getWithdrawTable,
onFetched: (rows) => syncSelectedItem(rows)
})
async function changeStatusHandler(
item: Withdraw,
statusEntry: [string, (typeof status)[keyof typeof status]]
) {
if (!item.id) return
try {
await updateSts(item.id, { sts: statusEntry[0] })
message.success('وضعیت ثبت شد')
await fetch()
} catch (e) {
console.error(e)
message.error('خطا در ثبت اطلاعات')
}
}
onMounted(() => reload())
defineExpose({ fetch, openSheet })
</script>
@@ -0,0 +1,146 @@
<template>
<MobileDetailSheet v-model:visible="visibleProxy" :item="item" :title="sheetTitle">
<template #default="{ item: row }">
<MobileSheetHeader :subtitle="dateLabel(row)" :show-title="isMobile">
<template #title>
<div class="flex w-full items-center justify-between gap-3">
<span class="min-w-0 truncate">{{ sheetTitle }}</span>
<div class="flex shrink-0 items-center gap-2">
<MobileIconButton
v-if="!row.tracked_number"
plain
icon="vuesax-linear:tick-circle"
color="#16A34A"
:size="22"
@click="$emit('withdraw', row)"
/>
<MobileIconButton
plain
icon="vuesax-linear:3d-cube-scan"
color="#1D4ED8"
:size="22"
@click="$emit('weight', row)"
/>
<MobileIconButton
v-if="showStatusActions(row)"
plain
icon="vuesax-linear:document-download"
color="#1D4ED8"
:size="22"
@click="goToFactor(row)"
/>
<ADropdown v-if="showStatusActions(row)" :trigger="['click']">
<MobileIconButton plain icon="vuesax-linear:status-up" color="#CA8A04" :size="22" />
<template #overlay>
<a-menu class="flex flex-col">
<a-menu-item
v-for="(statusItem, index) in statusOptions(row)"
:key="index"
class="m-1 rounded border-none px-2 py-1"
:style="{ color: statusItem[1].color }"
@click="$emit('changeStatus', row, statusItem)"
>
<span class="font-normal">{{ statusItem[1].title }}</span>
</a-menu-item>
</a-menu>
</template>
</ADropdown>
</div>
</div>
</template>
</MobileSheetHeader>
<span class="inline-flex rounded-lg border px-2 py-0.5 text-xs font-600 border-amber-300 bg-amber-50 text-amber-700">
{{ row.sts_str || '-' }}
</span>
<div class="space-y-2">
<MobileStatCard bordered variant="danger" label="مقدار (ریال)" :value="usePrice(row.cash ?? 0, true, '')">
<template #prefix>
<BaseIcon icon="vuesax-linear:empty-wallet" size="18" color="#dc2626" />
</template>
</MobileStatCard>
<MobileStatCard bordered variant="warning" label="طلا" :value="goldLabel(row)">
<template #prefix>
<BaseIcon icon="vuesax-linear:coin" size="18" color="#b45309" />
</template>
</MobileStatCard>
</div>
<MobileInfoGrid>
<MobileDetailRow label="علت تراکنش" full-width :value="noteLabel(row)" />
<MobileDetailRow label="شماره پیگیری" :value="row.tracked_number ?? '-'" />
<MobileDetailRow label="کد سفارش" :value="row.id ?? '-'" />
<MobileDetailRow label="کد کاربر" :value="row.customer_id ?? '-'" />
<MobileDetailRow label="نام و نام خانوادگی" full-width :value="row.customer?.name || '-'" />
<MobileDetailRow label="شماره کارت" full-width :value="row.bank_account?.cart_number || '-'" />
<MobileDetailRow label="تاریخ ثبت" full-width :value="dateLabel(row)" />
</MobileInfoGrid>
</template>
</MobileDetailSheet>
</template>
<script setup lang="ts">
import type { Withdraw } from '@/model/wallet'
import MobileDetailRow from '@/components/mobile/MobileDetailRow.vue'
import MobileDetailSheet from '@/components/mobile/MobileDetailSheet.vue'
import MobileIconButton from '@/components/mobile/MobileIconButton.vue'
import MobileInfoGrid from '@/components/mobile/MobileInfoGrid.vue'
import MobileSheetHeader from '@/components/mobile/MobileSheetHeader.vue'
import MobileStatCard from '@/components/mobile/MobileStatCard.vue'
import { status } from '@/pages/menuPages/withdraw/information/hooks/status'
import { isMobile } from '@core/utils/is'
import { toJalali } from '@core/utils/useDataTime'
import { usePrice } from '@core/utils/usePrice'
import { toNumber } from 'lodash'
import { useRouter } from 'vue-router'
const props = defineProps<{
visible: boolean
item: Withdraw | null
}>()
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
(e: 'withdraw', item: Withdraw): void
(e: 'weight', item: Withdraw): void
(e: 'changeStatus', item: Withdraw, statusEntry: [string, (typeof status)[keyof typeof status]]): void
}>()
const router = useRouter()
const visibleProxy = computed({
get: () => props.visible,
set: (value: boolean) => emit('update:visible', value)
})
const sheetTitle = computed(() => props.item?.customer?.name || `برداشت #${props.item?.id ?? '-'}`)
function showStatusActions(row: Withdraw) {
const sts = row.sts ?? 0
return sts >= 50 && sts <= 70
}
function statusOptions(row: Withdraw) {
return Object.entries(status).filter((item) => toNumber(item[0]) !== row.sts) as [string, (typeof status)[keyof typeof status]][]
}
function goToFactor(row: Withdraw) {
if (!row.id) return
router.push({ name: 'factor', params: { id: row.id } })
}
function dateLabel(row: Withdraw) {
return row.created_at ? toJalali(row.created_at) : '-'
}
function noteLabel(row: Withdraw) {
if (row.note == null || row.note === '') return '-'
return String(row.note)
}
function goldLabel(row: Withdraw) {
if (row.gold == null) return '-'
return String(row.gold)
}
</script>
@@ -0,0 +1,39 @@
<template>
<MobileCard clickable @click="$emit('open', item)">
<div class="space-y-1">
<div class="flex items-center justify-between gap-2">
<span class="truncate text-sm font-700 text-gray-900">{{ customerName }}</span>
<span class="shrink-0 text-xs text-gray-400">{{ dateLabel }}</span>
</div>
<div class="flex items-center justify-between gap-2 text-xs">
<span class="truncate text-gray-600">{{ noteLabel }}</span>
<span class="shrink-0 font-700 text-red-600">{{ cashLabel }}</span>
</div>
</div>
</MobileCard>
</template>
<script setup lang="ts">
import type { Withdraw } from '@/model/wallet'
import MobileCard from '@/components/mobile/MobileCard.vue'
import { toJalaliNoTime } from '@core/utils/useDataTime'
import { usePrice } from '@core/utils/usePrice'
const props = defineProps<{
item: Withdraw
}>()
defineEmits<{
(e: 'open', item: Withdraw): void
}>()
const noteLabel = computed(() => {
if (props.item.note == null || props.item.note === '') return `#${props.item.id ?? '-'}`
return String(props.item.note)
})
const customerName = computed(() => props.item.customer?.name || '-')
const dateLabel = computed(() => (props.item.created_at ? toJalaliNoTime(props.item.created_at) : '-'))
const cashLabel = computed(() => usePrice(props.item.cash ?? 0, true, ''))
</script>
@@ -1,7 +1,8 @@
<template>
<div>
<div class="h-full min-h-0 min-w-0 max-w-full overflow-hidden">
<BasicShowContext title="لیست برداشت و واریز">
<BaseTable v-bind="tblProps" @register="submit" ref="tbl">
<div class="flex h-full min-h-0 flex-col">
<BaseTable v-if="!isMobile" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl">
<template #extraHeader>
<!-- <BaseButton type="primary" size="small" class="flex" @click="emits('add')">
<div>افزودن مشتری عمده</div>
@@ -61,6 +62,15 @@
</div>
</template>
</BaseTable>
<WithdrawMobileCardList
v-else
ref="mobileListRef"
class="min-h-0 flex-1"
@withdraw="(row) => emits('withdraw', row)"
@weight="(row) => emits('weight', row)"
/>
</div>
</BasicShowContext>
</div>
</template>
@@ -76,8 +86,11 @@ import { usePrefix } from '@core/composable/usePrefix'
import { message } from 'ant-design-vue'
import { getOrderTable } from '@/api/order'
import {getWithdrawTable, updateSts} from '@/api/wallet'
import WithdrawMobileCardList from './WithdrawMobileCardList.vue'
import { isMobile } from '@core/utils/is'
const [submit, reload] = useBaseTable()
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
/*const select = (val) => {
val.sts === 1 ? return ['1'] : ['2']
}*/
@@ -117,9 +130,8 @@ async function changeCustomerActivationFunction(id: number) {
}
}
const fetch = () => {
// console.log('sss')
reload()
async function fetch() {
await Promise.allSettled([reload(), mobileListRef.value?.fetch?.() as Promise<void>])
}
// const spinTable = (val: boolean) => {
// // console.log('sss')
@@ -144,7 +156,7 @@ const test = (record: any) => {
const changeStatusHandler = async (status: any) => {
try {
await changeStatus(withdrawId.value as number, status)
reload()
await fetch()
} catch (e: any) {
console.log(e)
}
+48 -15
View File
@@ -20,6 +20,7 @@
v-model:visible="confirmWithdrawVisible"
width="15rem"
title="تایید برداشت"
wrap-class-name="withdraw-stack-modal"
@ok="okWithdraw"
:loading="loading"
@close="confirmWithdrawVisible = false"
@@ -51,22 +52,20 @@
v-model:visible="confirmWeightVisible"
width="40rem"
title="ثبت اطلاعات"
wrap-class-name="withdraw-stack-modal"
:loading="loading"
@close="confirmWeightVisible = false"
>
<div v-for="(item, index) in confirmWithdraw?.transaction">
<div v-for="(item, index) in weightTransactions" :key="index">
<div class="bg-gray-100 rounded-lg p-4 mt-2">
<div class="mb-4">{{ item?.title }} ({{ (item.gold * -1) }} عدد)</div>
<div class="mb-4">{{ item?.title }} ({{ weightCount(item) }} عدد)</div>
<ADivider class="!my-0 "/>
<div v-if="item.gold && item.gold * -1 > 0">
<div v-if="weightCount(item) > 0">
<div class="h-7 grid grid grid-cols-3">
<span class="col-span-1 flex "></span>
<span class="col-span-1 flex justify-center">وزن جدید</span>
</div>
<div v-for="i in Math.abs(item.gold * -1)" class="grid grid-cols-3 gap-2 mb-2" >
<!-- <span class="col-span-1 text-xs font-medium py-[8px] px-[10px]">{{
i
}}</span>-->
<div v-for="i in weightCount(item)" :key="i" class="grid grid-cols-3 gap-2 mb-2" >
<span class="col-span-1 text-xs font-medium py-[8px] px-[10px]">{{ item?.title }} {{ i }}</span>
<BasicInput class="col-span-1 text-xs font-medium w-10rem" v-model:value="newWeightArray[i]" />
<BaseButton class="col-span-1 text-xs font-medium w-4rem mr-4" @click="ok(i)">ثبت</BaseButton>
@@ -74,6 +73,9 @@
</div>
</div>
</div>
<div v-if="!weightTransactions.length" class="py-6 text-center text-sm text-gray-500">
اطلاعات تراکنش یافت نشد
</div>
</BasicModal>
</div>
</template>
@@ -97,7 +99,7 @@ import type { Customer } from '@/model/customer'
import { createCustomer, deleteCustomer, updateCustomer } from '@/api/customer'
import type { Order } from '@/model/order'
import type { Wallet, Withdraw } from '@/model/wallet'
import {completeWithdraw, sendWeights} from '@/api/wallet'
import {completeWithdraw, getWithdrawById, sendWeights} from '@/api/wallet'
import Button from "@core/components/BaseButton";
//import DatePicker from '@core/components/_sample/components/datePicker.vue'
@@ -214,7 +216,8 @@ async function edit(record: Customer) {
}*/
function openWithdraw(val: Withdraw) {
confirmWithdraw = val
confirmWithdraw.value = val
resetData()
confirmWithdrawVisible.value = true
}
@@ -241,7 +244,17 @@ onMounted(async () => {
//add project modal
const confirmWithdrawVisible = ref<boolean>(false)
const confirmWeightVisible = ref<boolean>(false)
let confirmWithdraw: Withdraw
const confirmWithdraw = ref<Withdraw | null>(null)
const weightTransactions = computed(() => {
const tx = confirmWithdraw.value?.transaction
return Array.isArray(tx) ? tx : []
})
function weightCount(item: { gold?: number }) {
const count = Math.floor(Math.abs(Number(item?.gold ?? 0)))
return Number.isFinite(count) ? count : 0
}
// const userInfoVisible = ref<boolean>(false)
//submit weight
@@ -251,7 +264,7 @@ async function okWithdraw() {
// return
loading.value = true
try {
form.value.id = confirmWithdraw
form.value.id = confirmWithdraw.value?.id
await completeWithdraw(form.value)
confirmWithdrawVisible.value = false
refreshTable()
@@ -269,10 +282,24 @@ async function okWithdraw() {
})
}
const newWeightArray = ref([])
function openWeight(val) {
confirmWithdraw = val
confirmWeightVisible.value = true
const newWeightArray = ref<Record<number, number | undefined>>({})
async function openWeight(val: Withdraw) {
try {
loading.value = true
newWeightArray.value = {}
if (val.id && !Array.isArray(val.transaction)) {
confirmWithdraw.value = await getWithdrawById(val.id)
} else {
confirmWithdraw.value = val
}
confirmWeightVisible.value = true
} catch (e) {
console.error(e)
message.error('خطا در بارگذاری اطلاعات')
} finally {
loading.value = false
}
}
function ok(i) {
try {
@@ -289,3 +316,9 @@ function ok(i) {
</script>
<style scoped></style>
<style>
.withdraw-stack-modal {
z-index: 1100 !important;
}
</style>