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
+5 -2
View File
@@ -10,7 +10,6 @@ export {}
declare module '@vue/runtime-core' {
export interface GlobalComponents {
_index: typeof import('./src/core/components/_sample/_index.vue')['default']
AAffix: typeof import('ant-design-vue/es')['Affix']
AAvatar: typeof import('ant-design-vue/es')['Avatar']
ABadge: typeof import('ant-design-vue/es')['Badge']
AButton: typeof import('ant-design-vue/es')['Button']
@@ -34,7 +33,6 @@ declare module '@vue/runtime-core' {
AModal: typeof import('ant-design-vue/es')['Modal']
APagination: typeof import('ant-design-vue/es')['Pagination']
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
APopover: typeof import('ant-design-vue/es')['Popover']
ARadio: typeof import('ant-design-vue/es')['Radio']
ARadioButton: typeof import('ant-design-vue/es')['RadioButton']
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
@@ -51,6 +49,7 @@ declare module '@vue/runtime-core' {
AUpload: typeof import('ant-design-vue/es')['Upload']
BaseButton: typeof import('./src/core/components/BaseButton/index.ts')['default']
BaseCard: typeof import('./src/core/components/BaseCard/index.ts')['default']
BaseCollapse: typeof import('./src/components/BaseCollapse.vue')['default']
BaseDatePicker: typeof import('./src/core/components/BaseDatePicker/index.ts')['default']
BaseIcon: typeof import('./src/core/components/BaseIcon/index.ts')['default']
BaseInputNumber: typeof import('./src/core/components/BaseInputNumber/index.ts')['default']
@@ -58,12 +57,16 @@ declare module '@vue/runtime-core' {
BaseSwitch: typeof import('./src/core/components/BaseSwitch/index.ts')['default']
BaseTable: typeof import('./src/core/components/BaseTable/index.ts')['default']
BaseTableOld: typeof import('./src/core/components/BaseTableOld/index.ts')['default']
BasicActionSheet: typeof import('./src/components/BasicActionSheet.vue')['default']
BasicCard: typeof import('./src/components/BasicCard.vue')['default']
BasicInfoItem: typeof import('./src/components/BasicInfoItem.vue')['default']
BasicInput: typeof import('./src/components/BasicInput.vue')['default']
BasicMainCard: typeof import('./src/components/BasicMainCard.vue')['default']
BasicMainCardFull: typeof import('./src/components/BasicMainCardFull.vue')['default']
BasicMobileBottomNav: typeof import('./src/components/BasicMobileBottomNav.vue')['default']
BasicMobileHeader: typeof import('./src/components/BasicMobileHeader.vue')['default']
BasicMobileHeaderLogo: typeof import('./src/components/BasicMobileHeaderLogo.vue')['default']
BasicMobileOverflowMenu: typeof import('./src/components/BasicMobileOverflowMenu.vue')['default']
BasicModal: typeof import('./src/components/BasicModal.vue')['default']
BasicSelect: typeof import('./src/components/BasicSelect.vue')['default']
BasicShowContext: typeof import('./src/components/BasicShowContext.vue')['default']
+5 -4
View File
@@ -1,9 +1,10 @@
import useAxios from "@core/utils/useAxios";
import type {Accounting, TotalAccounting} from "@/model/accounting";
import useAxios from '@core/utils/useAxios'
import type { TableInput } from '@core/type/baseTable'
import type { Accounting, TotalAccounting } from '@/model/accounting'
export const getAccountingTable = async (start: string,end: string) =>
await useAxios.post<Accounting>('/admin/dashboard/finance', {startDate: start, endDate: end});
//Accounting Tab
export const getTotalAccountingTable = async () =>
await useAxios.post<TotalAccounting>('/admin/finance');
export const getTotalAccountingTable = async (params: TableInput) =>
await useAxios.post<TotalAccounting[]>('/admin/finance', params)
+1 -1
View File
@@ -28,7 +28,7 @@ export const changeCustomerArchive = async (id: number) =>
export const confirmUser = async (id: number) =>
await useAxios.get(`/admin/product/is_valid/${id}`)
export const getCustomerArchiveTable = async (params: TableInput) =>
export const getProductArchiveTable = async (params: TableInput) =>
await useAxios.post<Array<Customer>>('/admin/product/archive/table', params)
export const getSingleTable = async (params: TableInput) =>
+2 -2
View File
@@ -5,10 +5,10 @@ import type { TableInput } from '@core/type/baseTable'
import type { Order } from '@/model/order'
import type { FunctionItem } from '@/model/Item'
import sms from '@/router/routes/sms'
import type { Sms } from '@/model/sms'
import type { Sms, SmsRecord } from '@/model/sms'
export const getSMSTable = async (params: TableInput) =>
await useAxios.post<Array<Price>>('/admin/messages/table', params)
await useAxios.post<Array<SmsRecord>>('/admin/messages/table', params)
export const getNumbersTable = async (params: TableInput) =>
await useAxios.post<Array<Price>>('/admin/messages/get_customer_list/table', params)
@@ -14,26 +14,22 @@
</template>
<script setup lang="ts">
import {CollapsePanel} from "ant-design-vue";
import { CollapsePanel } from 'ant-design-vue'
interface Props {
title: string,
activeKey: string | number,
title: string
activeKey: string | number
actives: string[] | number[]
}
const props = withDefaults(defineProps<Props>(), {});
const props = withDefaults(defineProps<Props>(), {})
</script>
<style lang="less">
.open {
width: 100%;
height: 1px;
background-color: #E5E7EB;
}
.rotate {
@@ -67,5 +63,4 @@ const props = withDefaults(defineProps<Props>(), {});
.ant-collapse > .ant-collapse-item > .ant-collapse-header {
padding: 12px 16px 0 !important;
}
</style>
</style>
+186
View File
@@ -0,0 +1,186 @@
<template>
<BasicModal
v-if="!isMobileView"
:key="`action-sheet-modal-${overlayKey}`"
v-model:visible="visibleProxy"
:title="title"
:width="modalWidth"
:loading="loading"
custom-footer
@close="handleCancel"
@ok="$emit('ok')"
>
<slot />
<template #footer>
<div
v-if="showButton && (showCancel || showOk)"
class="grid gap-3"
:class="[!showOk || !showCancel ? 'grid-cols-1' : 'grid-cols-12']"
>
<AButton
v-if="showCancel"
type="primary"
:class="[{ 'col-span-4': showOk }]"
size="large"
ghost
block
@click="handleCancel"
>
{{ cancelText }}
</AButton>
<AButton
v-if="showOk"
type="primary"
:class="[{ 'col-span-8': showCancel }]"
size="large"
block
@click="$emit('ok')"
>
{{ okText }}
</AButton>
</div>
</template>
</BasicModal>
<ADrawer
v-else-if="isMobileView"
:key="`action-sheet-drawer-${overlayKey}`"
v-model:visible="visibleProxy"
placement="bottom"
:closable="false"
:destroy-on-close="true"
:height="drawerHeight"
class="basic-action-sheet"
>
<div class="basic-action-sheet__container">
<div class="flex cursor-pointer justify-center pt-2" @click="visibleProxy = false">
<span class="w-12 rounded-sm border-t-2 border-t-solid border-t-gray-500"></span>
</div>
<div class="basic-action-sheet__content">
<slot />
</div>
<div
v-if="showButton && (showCancel || showOk)"
class="basic-action-sheet__footer grid gap-3"
:class="[!showOk || !showCancel ? 'grid-cols-1' : 'grid-cols-12']"
>
<AButton
v-if="showCancel"
type="primary"
:class="[{ 'col-span-4': showOk }]"
size="large"
ghost
block
@click="handleCancel"
>
{{ cancelText }}
</AButton>
<AButton
v-if="showOk"
type="primary"
:class="[{ 'col-span-8': showCancel }]"
size="large"
block
@click="$emit('ok')"
>
{{ okText }}
</AButton>
</div>
</div>
</ADrawer>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import BasicModal from '@/components/BasicModal.vue'
import { ACTION_SHEET_HEIGHT } from '@/constants/actionSheet'
import { isMobile } from '@core/utils/is'
interface Props {
visible?: boolean
title?: string
modalWidth?: string
loading?: boolean
drawerHeight?: string
cancelText?: string
showButton?: boolean
showCancel?: boolean
showOk?: boolean
okText?: string
}
const props = withDefaults(defineProps<Props>(), {
visible: false,
title: '',
modalWidth: '36rem',
loading: false,
drawerHeight: ACTION_SHEET_HEIGHT,
cancelText: 'بازگشت',
showButton: false,
showCancel: true,
showOk: true,
okText: 'ثبت'
})
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
(e: 'update:show', value: boolean): void
(e: 'ok'): void
(e: 'cancel'): void
(e: 'close'): void
}>()
const isMobileView = computed(() => isMobile.value)
const overlayKey = computed(() => (isMobileView.value ? 'drawer' : 'modal'))
const visibleProxy = computed({
get: () => props.visible,
set: (value: boolean) => {
emit('update:visible', value)
emit('update:show', value)
}
})
function handleCancel() {
emit('cancel')
emit('close')
visibleProxy.value = false
}
</script>
<style lang="less">
.basic-action-sheet {
.ant-drawer-content {
border-radius: 16px 16px 0 0;
overflow: hidden;
}
.ant-drawer-body {
padding: 0;
height: 100%;
}
}
.basic-action-sheet__container {
height: 100%;
min-height: 100%;
display: flex;
flex-direction: column;
}
.basic-action-sheet__content {
flex: 1;
overflow-y: auto;
padding: 0 16px 12px;
}
.basic-action-sheet__footer {
position: sticky;
bottom: 0;
z-index: 2;
background: #fff;
padding: 8px 16px calc(16px + env(safe-area-inset-bottom));
border-top: 1px solid #f0f0f0;
}
</style>
+13 -4
View File
@@ -1,11 +1,20 @@
<template>
<a-card class="m-5 rounded-xl md:h-[91vh]">
<slot></slot>
</a-card>
<div class="box-border flex h-full min-h-0 min-w-0 w-full max-w-full flex-col overflow-hidden p-3 md:p-5">
<a-card class="basic-main-card flex min-h-0 flex-1 flex-col overflow-hidden rounded-xl">
<slot></slot>
</a-card>
</div>
</template>
<script></script>
<style scoped>
.basic-main-card :deep(.ant-card-body) {
display: flex;
flex: 1;
flex-direction: column;
height: 100%;
min-height: 0;
overflow: hidden;
}
</style>
-3
View File
@@ -1,14 +1,11 @@
<template>
<div>
<!-- {{ props.menu }}-->
<AMenu
v-model:selectedKeys="selectedKeys"
:style="{ width: props.width }"
:mode="props.mode"
:theme="props.theme"
>
<!-- <a-menu-item key="1"> Navigation One </a-menu-item>-->
<!-- {{ props.menu }}-->
<BasicMenuItem :menu="props.menu" />
</AMenu>
</div>
+176
View File
@@ -0,0 +1,176 @@
<template>
<nav class="mobile-bottom-nav md:hidden" aria-label="منوی اصلی">
<button
v-for="item in navItems"
:key="String(item.name)"
type="button"
class="mobile-bottom-nav__item"
:class="{ 'mobile-bottom-nav__item--active': isRouteActive(item) }"
@click="navigateTo(item)"
>
<span class="mobile-bottom-nav__icon-wrap">
<BaseIcon
v-if="item.meta?.icon"
:icon="item.meta.icon as string"
:size="menuIconSize"
/>
</span>
<span class="mobile-bottom-nav__label">{{ item.meta?.title ?? item.name }}</span>
</button>
<BasicMobileOverflowMenu :items="overflowMenuItems" @select="navigateTo" />
</nav>
</template>
<script setup lang="ts">
import BaseIcon from '@core/components/BaseIcon'
import { menuIconSize } from '@root/config'
import { useRouterStore } from '@core/stores/router'
import BasicMobileOverflowMenu from '@/components/BasicMobileOverflowMenu.vue'
import { sortBy } from 'lodash'
import { computed } from 'vue'
import type { RouteRecordName, RouteRecordRaw } from 'vue-router'
const BOTTOM_NAV_PRIORITY: (string | symbol)[] = ['dashboard', 'orders', 'accounting', 'customers']
const router = useRouter()
const currentRoute = useRoute()
const routerStore = useRouterStore()
const allMenuItems = computed(() =>
sortBy(
routerStore.routes.filter((item) => !item.meta?.hideMenu),
(item) => item.meta?.order
)
)
const navItems = computed(() => {
const items: RouteRecordRaw[] = []
for (const name of BOTTOM_NAV_PRIORITY) {
const route = allMenuItems.value.find((item) => item.name === name)
if (route) items.push(route)
}
for (const route of allMenuItems.value) {
if (items.length >= 4) break
if (!items.some((item) => item.name === route.name)) {
items.push(route)
}
}
return items.slice(0, 4)
})
const navItemNames = computed(() => new Set(navItems.value.map((item) => item.name)))
const overflowMenuItems = computed(() =>
allMenuItems.value.filter((item) => !navItemNames.value.has(item.name))
)
function isRouteActive(route: RouteRecordRaw): boolean {
if (currentRoute.name === route.name) return true
function checkChildren(children?: RouteRecordRaw[]): boolean {
if (!children?.length) return false
for (const child of children) {
if (child.name === currentRoute.name) return true
if (checkChildren(child.children)) return true
}
return false
}
return checkChildren(route.children)
}
function navigateTo(item: RouteRecordRaw) {
const redirect = item.redirect
if (redirect && typeof redirect === 'object' && 'name' in redirect) {
router.push({ name: redirect.name as RouteRecordName })
return
}
router.push({ name: item.name })
}
</script>
<style scoped lang="less">
.mobile-bottom-nav {
position: fixed;
right: 0;
left: 0;
bottom: 0;
z-index: 100;
display: flex;
align-items: stretch;
justify-content: space-around;
gap: 4px;
padding: 6px 8px calc(6px + env(safe-area-inset-bottom));
background: #fff;
border-top: 1px solid #f0f0f0;
box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.06);
}
.mobile-bottom-nav__item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
min-width: 0;
padding: 6px 4px 8px;
border: none;
background: transparent;
cursor: pointer;
position: relative;
color: #595959;
&--active {
color: #1677ff;
.mobile-bottom-nav__icon-wrap {
background: #e6f4ff;
color: #1677ff;
}
&::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 28px;
height: 3px;
border-radius: 3px 3px 0 0;
background: #1677ff;
}
}
&:active {
opacity: 0.85;
}
}
.mobile-bottom-nav__icon-wrap {
display: inline-flex;
align-items: center;
justify-content: center;
width: 40px;
height: 32px;
border-radius: 10px;
color: #292d32;
:deep(svg path) {
stroke: currentColor;
}
}
.mobile-bottom-nav__label {
font-size: 11px;
font-weight: 500;
line-height: 1.2;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
}
</style>
+117 -146
View File
@@ -1,22 +1,18 @@
<template>
<div theme="light" :class="[prefixCls]" class="md:hidden">
<div class="flex items-center justify-center gap-2 relative">
<BaseIcon
icon="vuesax-linear:menu"
class="p-3 absolute right-2 top-1"
@click="showDrawer"
/>
<img :src="getConfigApp('avatar')" class="w-4rem mr-2 my-5" />
<div class="absolute left-2 top-3">
<div class="flex items-center w-full justify-center"></div>
<div class="mobile-header-bar">
<span class="mobile-header-bar__name">{{ displayName }}</span>
<div class="mobile-header-bar__logo">
<BasicMobileHeaderLogo />
</div>
<div class="mobile-header-bar__actions">
<button type="button" class="mobile-header-bar__icon" aria-label="رفرش صفحه" @click="handleRefresh">
<Icon icon="vuesax-linear:refresh" :size="26" />
</button>
<ADropdown placement="bottomLeft">
<a class="ant-dropdown-link" @click.prevent>
<!-- Hover me-->
<div class="flex items-center gap-1 text-gray-400">
<AAvatar :src="placeholderM" />
<Icon icon="vuesax-linear:arrow-down" size="15" />
</div>
</a>
<button type="button" class="mobile-header-bar__icon" aria-label="پروفایل">
<Icon icon="vuesax-linear:profile-circle" :size="26" />
</button>
<template #overlay>
<AMenu>
<AMenuItem>
@@ -41,98 +37,53 @@
</AMenu>
</template>
</ADropdown>
<BaseButton
iconSize="20"
icon="vuesax-linear:setting"
type="text"
v-if="layoutStore.showSettingsIcon"
@click="handleShowSettings"
/>
</div>
<BasicModal
v-model:visible="myInfoVisible"
width="40rem"
title="اطلاعات حساب کاربری"
@ok="ok"
:loading="loading"
@close="myInfoVisible = false"
>
<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()">
<BasicInput v-model:value="form.fname" />
</a-form-item>
<a-form-item label="نام خانوادگی" name="lname" :rules="inputRules()">
<BasicInput v-model:value="form.lname" />
</a-form-item>
<a-form-item label="شماره موبایل" name="mobile" :rules="inputLenRules(11)">
<BasicInput v-model:value="form.mobile" type="number" />
</a-form-item>
<a-form-item label="رمز کاربری" name="password">
<BasicInput type="password" v-model:value="form.password" />
</a-form-item>
</div>
</a-form>
</BasicModal>
</div>
<BasicModal
v-model:visible="myInfoVisible"
width="40rem"
title="اطلاعات حساب کاربری"
@ok="ok"
:loading="loading"
@close="myInfoVisible = false"
>
<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()">
<BasicInput v-model:value="form.fname" />
</a-form-item>
<a-form-item label="نام خانوادگی" name="lname" :rules="inputRules()">
<BasicInput v-model:value="form.lname" />
</a-form-item>
<a-form-item label="شماره موبایل" name="mobile" :rules="inputLenRules(11)">
<BasicInput v-model:value="form.mobile" type="number" />
</a-form-item>
<a-form-item label="رمز کاربری" name="password">
<BasicInput type="password" v-model:value="form.password" />
</a-form-item>
</div>
</a-form>
</BasicModal>
</div>
<ADrawer height="460px" :placement="placement" v-model:visible="open" :closable="false" @close="onClose">
<AMenu v-model:selectedKeys="selectedKeys" mode="vertical">
<template v-for="item in menu">
<BasicSubMenu
v-if="!item.meta?.hideChildren && item.children && item.children.length > 0"
:menu-item="item"
:key="item.name"
:level="1"
@click="onClose"
/>
<BaseMenuItem v-else :item="item" :key="item.name" @click="onClose" />
</template>
</AMenu>
</ADrawer>
</template>
<script setup lang="ts">
// import Logo from '@/assets/img/saminLogo.png'
import type { DrawerProps } from 'ant-design-vue'
import { inputLenRules, inputRules } from '@/utils/inputRules'
const placement = ref<DrawerProps['placement']>('bottom')
import { usePrefix } from '@core/composable/usePrefix'
import { useLayoutStore } from '@core/stores/layout'
import BasicMenu from '@core/layouts/composnts/BasicMenu.vue'
import placeholderM from '@core/assets/img/avatars/placeholder-m.jpg'
import Icon from '@core/components/BaseIcon/src/Icon.vue'
import { useSettingsStore } from '@core/stores/settings'
import { useStaticApiListsStore } from '@/store/staticApisStore'
import BasicMobileHeaderLogo from '@/components/BasicMobileHeaderLogo.vue'
import { useUserStore } from '@/store/userStore'
import type { User } from '@/model/user'
import { createUser, updateMyInformation, updateUser } from '@/api/user'
import { updateMyInformation } from '@/api/user'
import { message } from 'ant-design-vue/es'
import { onMounted, ref } from 'vue'
import { onMounted, ref, computed } from 'vue'
import type { FormInstance } from 'ant-design-vue'
import { cloneDeep, sortBy } from 'lodash'
import { useWindowSize } from '@vueuse/core'
import { computed } from 'vue'
import type { ComputedRef } from 'vue'
import type { RouteRecordName, RouteRecordRaw } from 'vue-router'
import { useRouterStore } from '@core/stores/router'
import BasicSubMenu from '@core/layouts/composnts/BasicSubMenu.vue'
import BaseMenuItem from '@core/layouts/composnts/BaseMenuItem.vue'
import {getConfigApp} from "@/utils/config";
import { cloneDeep } from 'lodash'
const { prefixCls } = usePrefix('basic-header')
const layoutStore = useLayoutStore()
const store = useStaticApiListsStore()
const userStore = useUserStore()
const settingsStore = useSettingsStore()
const currentRoute = useRoute()
const selectedKeys = ref<(RouteRecordName | undefined)[]>([])
const routerStore = useRouterStore()
const { width } = useWindowSize()
//my info modal
const loading = ref<boolean>(false)
const myInfoVisible = ref<boolean>(false)
const formRef = ref<FormInstance>()
@@ -150,45 +101,17 @@ const form = ref<User>({
user_type: undefined,
permission: []
})
const displayName = computed(() => {
const info = userStore.userInfo
if (info?.name) return info.name
return [info?.fname, info?.lname].filter(Boolean).join(' ') || 'کاربر'
})
onMounted(() => {
form.value = cloneDeep(userStore?.userInfo) as User
if (width.value <= 426) {
layoutStore.layout = 'side'
layoutStore.sideMenu.mode = 'vertical'
layoutStore.sideMenu.collapsed = false
console.log(layoutStore.layout + width.value)
}
const path: (RouteRecordName | undefined)[] = []
function walkInRoutes(routes: RouteRecordRaw[]) {
for (const route of routes) {
if (route.name == currentRoute.name) {
path.push(route.name)
return true
}
if (route.children && route.children.length > 0) {
path.push(route.name)
const res = walkInRoutes(route.children)
if (!res) {
path.pop()
} else {
return true
}
}
}
return false
}
walkInRoutes(routerStore.routes)
selectedKeys.value = path
})
const menu: ComputedRef<RouteRecordRaw[]> = computed(() => {
return sortBy(routerStore.routes, (item) => {
return item.meta?.order
}).filter((item) => !item.meta?.hideMenu)
})
//submit user
async function ok() {
formRef.value?.validateFields().then(async () => {
loading.value = true
@@ -198,7 +121,6 @@ async function ok() {
message.success('اطلاعات با موفقیت ویرایش شد')
myInfoVisible.value = false
} catch (e) {
// message.error('خطا در بارگذاری اطلاعات')
console.error(e)
} finally {
loading.value = false
@@ -206,30 +128,79 @@ async function ok() {
})
}
//const temp = 12
function handleCollapseSideMenu() {
layoutStore.sideMenu.collapsed = !layoutStore.sideMenu.collapsed
}
function handleShowSettings() {
settingsStore.showSettingsDrawer = !settingsStore.showSettingsDrawer
}
function clearLocalStorage() {
localStorage.clear()
location.reload()
}
const open = ref<boolean>(false)
const showDrawer = () => {
open.value = true
}
const onClose = () => {
open.value = false
console.log(open.value)
function handleRefresh() {
window.location.reload()
}
</script>
<style scoped lang="less"></style>
<style scoped lang="less">
@prefix: ~'@{prefixCls}-basic-header';
.@{prefix} {
padding: 12px 16px;
background-color: #f5f5f5;
border-bottom: none;
}
.mobile-header-bar {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 12px 16px;
background: #fff;
border: 1px solid #e8e8e8;
border-radius: 16px;
}
.mobile-header-bar__name {
max-width: 34%;
font-size: 15px;
font-weight: 600;
color: #1f1f1f;
line-height: 1.4;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.mobile-header-bar__logo {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
display: inline-flex;
align-items: center;
justify-content: center;
pointer-events: none;
}
.mobile-header-bar__actions {
display: flex;
align-items: center;
gap: 12px;
flex-shrink: 0;
}
.mobile-header-bar__icon {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0;
border: none;
background: transparent;
color: #292d32;
cursor: pointer;
line-height: 0;
&:active {
opacity: 0.7;
}
}
</style>
+15
View File
@@ -0,0 +1,15 @@
<template>
<img :src="getConfigApp('avatar')" alt="لوگو" class="mobile-header-logo" />
</template>
<script setup lang="ts">
import { getConfigApp } from '@/utils/config'
</script>
<style scoped lang="less">
.mobile-header-logo {
height: 30px;
width: auto;
object-fit: contain;
}
</style>
+167
View File
@@ -0,0 +1,167 @@
<template>
<div ref="menuRef" class="mobile-overflow-menu">
<button
type="button"
class="mobile-overflow-menu__trigger"
:class="{ 'mobile-overflow-menu__trigger--active': open }"
@click="toggleMenu"
>
<span class="mobile-overflow-menu__icon-wrap">
<BaseIcon icon="vuesax-linear:menu" :size="menuIconSize" />
</span>
<span class="mobile-overflow-menu__label">منو</span>
</button>
<div v-if="open" class="mobile-overflow-menu__panel">
<button
v-for="item in items"
:key="String(item.name)"
type="button"
class="mobile-overflow-menu__panel-item"
@click="handleSelect(item)"
>
<span class="mobile-overflow-menu__panel-label">{{ item.meta?.title ?? item.name }}</span>
<BaseIcon
v-if="item.meta?.icon"
:icon="item.meta.icon as string"
:size="menuIconSize"
class="mobile-overflow-menu__panel-icon"
/>
</button>
</div>
</div>
</template>
<script setup lang="ts">
import BaseIcon from '@core/components/BaseIcon'
import { menuIconSize } from '@root/config'
import { onClickOutside } from '@vueuse/core'
import { ref } from 'vue'
import type { RouteRecordRaw } from 'vue-router'
interface Props {
items: RouteRecordRaw[]
}
const props = defineProps<Props>()
const emit = defineEmits<{
(e: 'select', item: RouteRecordRaw): void
}>()
const open = ref(false)
const menuRef = ref<HTMLElement | null>(null)
onClickOutside(menuRef, () => {
open.value = false
})
function toggleMenu() {
open.value = !open.value
}
function handleSelect(item: RouteRecordRaw) {
open.value = false
emit('select', item)
}
</script>
<style scoped lang="less">
.mobile-overflow-menu {
position: relative;
flex: 1;
}
.mobile-overflow-menu__trigger {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
min-width: 0;
padding: 6px 4px 8px;
border: none;
background: transparent;
cursor: pointer;
position: relative;
color: #595959;
&:active {
opacity: 0.85;
}
}
.mobile-overflow-menu__trigger--active {
color: #1677ff;
.mobile-overflow-menu__icon-wrap {
background: #e6f4ff;
color: #1677ff;
}
&::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 28px;
height: 3px;
border-radius: 3px 3px 0 0;
background: #1677ff;
}
}
.mobile-overflow-menu__icon-wrap {
display: inline-flex;
align-items: center;
justify-content: center;
width: 40px;
height: 32px;
border-radius: 10px;
color: #292d32;
}
.mobile-overflow-menu__label {
font-size: 11px;
font-weight: 500;
line-height: 1.2;
}
.mobile-overflow-menu__panel {
position: absolute;
bottom: calc(100% + 12px);
left: 8px;
min-width: 220px;
background: #f5f5f5;
border: 1px solid #e8e8e8;
border-radius: 12px;
box-shadow: 0 10px 22px rgba(0, 0, 0, 0.12);
padding: 10px;
z-index: 210;
}
.mobile-overflow-menu__panel-item {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
border: none;
background: transparent;
border-radius: 8px;
padding: 10px 8px;
color: #262626;
font-size: 18px;
cursor: pointer;
text-align: right;
&:active {
background: #e9e9e9;
}
}
.mobile-overflow-menu__panel-icon {
color: #262626;
}
</style>
+6 -5
View File
@@ -1,7 +1,10 @@
<template>
<div class="py-2 pr-3 w-full h-full relative">
<div class="text-black font-bold">{{ props.title }}</div>
<div class="mt-2 overflow-y-auto p-1 h-[96%]">
<div class="relative flex h-full min-h-0 min-w-0 w-full max-w-full flex-col overflow-hidden py-2 pr-3">
<div class="flex shrink-0 min-w-0 items-center justify-between gap-2">
<div class="truncate text-black font-bold">{{ props.title }}</div>
<slot name="extra" />
</div>
<div class="mt-2 min-h-0 flex-1 overflow-hidden p-1">
<slot />
</div>
</div>
@@ -13,5 +16,3 @@ interface Prop {
}
const props = defineProps<Prop>()
</script>
<style scoped></style>
+28
View File
@@ -0,0 +1,28 @@
<template>
<article
class="rounded-2xl border border-gray-200 bg-white shadow-sm"
:class="[paddingClass, clickable ? 'cursor-pointer' : '']"
@click="clickable ? $emit('click') : undefined"
>
<slot />
</article>
</template>
<script setup lang="ts">
const props = withDefaults(
defineProps<{
clickable?: boolean
compact?: boolean
}>(),
{
clickable: false,
compact: true
}
)
defineEmits<{
(e: 'click'): void
}>()
const paddingClass = computed(() => (props.compact ? 'px-3 py-3' : 'p-4'))
</script>
+74
View File
@@ -0,0 +1,74 @@
<template>
<div class="min-w-0 h-full max-w-full overflow-x-hidden overflow-y-auto space-y-3 md:hidden">
<div v-if="$slots.toolbar" class="flex">
<slot name="toolbar" />
</div>
<div v-if="showSearch" class="flex items-center gap-2">
<a-input
v-model:value="searchModel"
:placeholder="searchPlaceholder"
class="w-full flex-1"
@keyup.enter="$emit('search')"
>
<template #prefix>
<BaseIcon icon="vuesax-linear:search-normal" size="18" color="#9ca3af" />
</template>
</a-input>
<BaseButton v-if="showRefresh" type="text" icon="vuesax-linear:refresh" iconSize="18" @click="$emit('refresh')" />
</div>
<a-spin :spinning="loading" tip="در حال دریافت اطلاعات...">
<div class="space-y-2">
<slot />
</div>
<div
v-if="showEmpty"
class="rounded-2xl border border-dashed border-gray-300 bg-gray-50 px-3 py-6 text-center text-sm text-gray-500"
>
{{ emptyText }}
</div>
<div v-if="hasMore" class="mt-3 flex justify-center">
<BaseButton size="small" @click="$emit('loadMore')">بارگذاری بیشتر</BaseButton>
</div>
</a-spin>
<slot name="footer" />
</div>
</template>
<script setup lang="ts">
const props = withDefaults(
defineProps<{
loading: boolean
hasMore: boolean
emptyText?: string
showEmpty?: boolean
search?: string
searchPlaceholder?: string
showSearch?: boolean
showRefresh?: boolean
}>(),
{
emptyText: 'موردی یافت نشد',
showEmpty: false,
searchPlaceholder: 'جستجو...',
showSearch: true,
showRefresh: false
}
)
const emit = defineEmits<{
(e: 'search'): void
(e: 'refresh'): void
(e: 'loadMore'): void
(e: 'update:search', value: string): void
}>()
const searchModel = computed({
get: () => props.search ?? '',
set: (value: string) => emit('update:search', value)
})
</script>
+50
View File
@@ -0,0 +1,50 @@
<template>
<div class="flex justify-between rounded-lg border px-3 py-2" :class="rowClass">
<div class="text-xs" :class="labelClass">{{ label }}</div>
<div class="truncate font-700 mr-1" :class="valueClass">
<slot>{{ value }}</slot>
</div>
</div>
</template>
<script setup lang="ts">
const props = withDefaults(
defineProps<{
label: string
value?: string | number
variant?: 'neutral' | 'success' | 'warning' | 'danger'
fullWidth?: boolean
valueClass?: string
}>(),
{
variant: 'neutral',
fullWidth: false
}
)
const rowClass = computed(() => {
const span = props.fullWidth ? 'col-span-2' : ''
const variants: Record<string, string> = {
neutral: 'border-gray-200 bg-gray-50',
success: 'border-green-200 bg-green-50',
warning: 'border-amber-200 bg-amber-50',
danger: 'border-red-200 bg-red-50'
}
return `${span} ${variants[props.variant]}`
})
const labelClass = computed(() => {
if (props.variant === 'success') return 'text-green-700'
if (props.variant === 'warning') return 'text-amber-700'
if (props.variant === 'danger') return 'text-red-700'
return 'text-gray-500'
})
const valueClass = computed(() => {
if (props.valueClass) return props.valueClass
if (props.variant === 'success') return 'text-green-700'
if (props.variant === 'warning') return 'text-amber-700'
if (props.variant === 'danger') return 'text-red-600'
return 'text-gray-900'
})
</script>
@@ -0,0 +1,37 @@
<template>
<BasicActionSheet
v-model:visible="visibleModel"
:title="title"
:showButton="false"
:drawerHeight="drawerHeight"
>
<div v-if="item" class="space-y-3 pb-2">
<slot :item="item" />
</div>
</BasicActionSheet>
</template>
<script setup lang="ts" generic="T">
import { ACTION_SHEET_HEIGHT } from '@/constants/actionSheet'
const props = withDefaults(
defineProps<{
visible: boolean
item: T | null
title?: string
drawerHeight?: string
}>(),
{
drawerHeight: ACTION_SHEET_HEIGHT
}
)
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
}>()
const visibleModel = computed({
get: () => props.visible,
set: (value: boolean) => emit('update:visible', value)
})
</script>
@@ -0,0 +1,34 @@
<template>
<button
type="button"
class="flex shrink-0 items-center justify-center transition-colors"
:class="
plain
? 'h-auto w-auto border-none bg-transparent p-0'
: 'h-9 w-9 rounded-full border-none bg-white active:bg-gray-50'
"
@click.stop="$emit('click')"
>
<BaseIcon :icon="icon" :size="size" :color="color" />
</button>
</template>
<script setup lang="ts">
withDefaults(
defineProps<{
icon: string
color?: string
size?: number
plain?: boolean
}>(),
{
color: '#374151',
size: 18,
plain: false
}
)
defineEmits<{
(e: 'click'): void
}>()
</script>
+16
View File
@@ -0,0 +1,16 @@
<template>
<div class="min-w-0">
<p class="text-xs text-gray-500">{{ label }}</p>
<p class="truncate text-sm font-medium text-gray-900" :class="valueClass">
<slot>{{ value }}</slot>
</p>
</div>
</template>
<script setup lang="ts">
defineProps<{
label: string
value?: string | number
valueClass?: string
}>()
</script>
+16
View File
@@ -0,0 +1,16 @@
<template>
<div class="grid gap-3" :class="gridClass">
<slot />
</div>
</template>
<script setup lang="ts">
const props = withDefaults(
defineProps<{
cols?: 1 | 2
}>(),
{ cols: 2 }
)
const gridClass = computed(() => (props.cols === 1 ? 'grid-cols-1' : 'grid-cols-2'))
</script>
@@ -0,0 +1,25 @@
<template>
<div class="space-y-3">
<div class="min-w-0 space-y-1">
<div v-if="showTitle" class="text-lg font-700 text-gray-900">
<slot name="title">{{ title }}</slot>
</div>
<div v-if="subtitle || $slots.subtitle" class="text-xs text-gray-500">
<slot name="subtitle">{{ subtitle }}</slot>
</div>
</div>
</div>
</template>
<script setup lang="ts">
withDefaults(
defineProps<{
title?: string
subtitle?: string
showTitle?: boolean
}>(),
{
showTitle: true
}
)
</script>
+45
View File
@@ -0,0 +1,45 @@
<template>
<div class="flex items-center justify-between px-4 py-3" :class="containerClass">
<div class="flex items-center">
<slot name="prefix" />
<span class="font-700" :class="[labelClass, $slots.prefix ? 'mr-2' : '']">{{ label }}</span>
</div>
<div class="font-800" :class="valueClass">
<slot>{{ value }}</slot>
</div>
</div>
</template>
<script setup lang="ts">
const props = withDefaults(
defineProps<{
label: string
value?: string | number
variant?: 'success' | 'danger' | 'neutral'
bordered?: boolean
}>(),
{
variant: 'neutral',
bordered: false
}
)
const containerClass = computed(() => {
const radius = props.bordered ? 'rounded-xl border' : 'rounded-xl'
if (props.variant === 'success') return `${radius} border-green-200 bg-green-50`
if (props.variant === 'danger') return `${radius} border-red-200 bg-red-50`
return `${radius} border-gray-200 bg-gray-50`
})
const labelClass = computed(() => {
if (props.variant === 'success') return 'text-green-700'
if (props.variant === 'danger') return 'text-red-600'
return 'text-gray-500'
})
const valueClass = computed(() => {
if (props.variant === 'success') return 'text-lg text-green-700'
if (props.variant === 'danger') return 'text-lg text-red-600'
return 'text-gray-900'
})
</script>
@@ -0,0 +1,22 @@
export function useMobileDetailSheet<T extends { id?: number }>() {
const sheetVisible = ref(false)
const selectedItem = ref<T | null>(null)
function openSheet(item: T) {
selectedItem.value = item
sheetVisible.value = true
}
function syncSelectedItem(items: T[]) {
if (!selectedItem.value?.id) return
const updated = items.find((item) => item.id === selectedItem.value?.id)
if (updated) selectedItem.value = updated
}
return {
sheetVisible,
selectedItem,
openSheet,
syncSelectedItem
}
}
@@ -0,0 +1,58 @@
import type { Ref } from 'vue'
import type { TableInput, TableOutput } from '@core/type/baseTable'
interface Options<T> {
fetchApi: (params: TableInput) => Promise<TableOutput<T>>
onFetched?: (items: T[]) => void
}
export function useMobileTableList<T extends { id?: number }>(options: Options<T>) {
const loading = ref(false)
const search = ref('')
const query = ref<TableInput>({ page: 1, limit: 10, select: [] })
const total = ref(0)
const items = ref<T[]>([]) as Ref<T[]>
const hasMore = computed(() => items.value.length < total.value)
async function fetchList(append = false) {
try {
loading.value = true
const params: TableInput = {
...query.value,
search: search.value || null
}
const res = await options.fetchApi(params)
query.value = res.query
total.value = res.query.total ?? 0
items.value = append ? [...items.value, ...res.rows] : res.rows
options.onFetched?.(items.value)
} catch {
total.value = 0
items.value = []
options.onFetched?.([])
} finally {
loading.value = false
}
}
async function reload() {
query.value.page = 1
await fetchList(false)
}
async function loadMore() {
query.value.page = (query.value.page ?? 1) + 1
await fetchList(true)
}
return {
loading,
search,
items,
hasMore,
reload,
loadMore,
fetch: reload
}
}
+15
View File
@@ -0,0 +1,15 @@
import type { Wallet } from '@/model/wallet'
export function getWallets(wallet?: Wallet | Wallet[]): Wallet[] {
if (!wallet) return []
return Array.isArray(wallet) ? wallet : [wallet]
}
export function getCashBalance(wallet?: Wallet | Wallet[]): number {
return getWallets(wallet)[0]?.cash ?? 0
}
export function getGoldBalance(wallet?: Wallet | Wallet[]): string {
const sum = getWallets(wallet).reduce((total, item) => total + (item.gold ?? 0), 0)
return sum.toFixed(3)
}
+2
View File
@@ -0,0 +1,2 @@
/** ارتفاع یکسان برای تمام bottom action sheet ها */
export const ACTION_SHEET_HEIGHT = '80vh'
+5
View File
@@ -47,6 +47,11 @@
padding: 12px;
}
.ant-drawer-bottom .ant-drawer-content {
border-radius: 16px 16px 0 0;
overflow: hidden;
}
.ant-card-body{
padding: 12px;
}
+5 -4
View File
@@ -1,8 +1,9 @@
//ok Code
/*html {
overflow: scroll;
html,
body,
#app {
max-width: 100%;
overflow-x: hidden;
}*/
}
.scroll-none{
&::-webkit-scrollbar {
+49 -18
View File
@@ -1,20 +1,17 @@
<template>
<div :class="[prefixCls]">
<div :class="[prefixCls, 'min-w-0 max-w-full']">
<ASpin tip="در حال دریافت اطلاعات..." :indicator="indicator" :spinning="loading">
<!-- <div ref="skeletonWidth" class="w-full"></div>-->
<!-- <transition>-->
<!-- TODO Check -->
<!-- relative h-full-->
<div class="scrollable-container" id="components-affix-demo-target" ref="containerRef">
<!--- TODO remove AFFix -->
<AAffix :target="() => containerRef">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center gap-1" v-if="showOptionHeader">
<!-- <AInputSearch placeholder="جستجو" enter-button />-->
<div class="table-toolbar sticky top-0 z-10 mb-2 shrink-0 bg-white">
<div class="flex flex-col gap-2 xl:flex-row xl:items-center xl:justify-between">
<div
v-if="showOptionHeader"
class="flex min-w-0 flex-1 flex-wrap items-center gap-2"
>
<AInput
@keyup.enter="handleSearchText"
placeholder="جستجو"
class="pl-1"
class="table-toolbar-search min-w-[11rem] flex-1 basis-52 sm:max-w-md"
v-model:value="searchText"
>
<template #suffix>
@@ -26,7 +23,7 @@
/>
</template>
</AInput>
<div class="flex flex-wrap md:flex-nowrap gap-1 items-center">
<div class="flex shrink-0 flex-wrap items-center gap-1">
<BaseButton
type="text"
class="text-gray-500"
@@ -79,11 +76,12 @@
</BaseButton>
</div>
</div>
<div>
<div v-if="$slots.extraHeader" class="w-full min-w-0 shrink-0 xl:w-auto xl:max-w-[55%]">
<slot name="extraHeader" />
</div>
</div>
</AAffix>
</div>
<div class="table-body min-h-0 min-w-0 flex-1 overflow-auto">
<ATable
:columns="_columns"
:data-source="data"
@@ -100,9 +98,8 @@
</template>
<!-- <template #loading> sdfsdfsdfsd </template>-->
</ATable>
<!-- absolute bottom-0 w-full-->
<!-- absolute bottom-0-->
<div class="flex items-center justify-between mt-5 w-full" v-if="showPaginate">
</div>
<div class="table-footer mt-5 flex w-full shrink-0 items-center justify-between" v-if="showPaginate">
<span> {{ totalRecord }} مورد یافت شد </span>
<div class="flex items-center gap-2 justify-between">
<APagination
@@ -683,13 +680,47 @@ defineExpose({
@prefix:~ '@{prefixCls}-base-table';
.@{prefix} {
@apply h-full;
@apply h-full max-w-full flex flex-col min-h-0 min-w-0;
.scrollable-container {
display: flex;
flex: 1;
flex-direction: column;
max-width: 100%;
min-height: 0;
min-width: 0;
overflow: hidden;
}
.table-body {
flex: 1;
min-height: 0;
min-width: 0;
overflow: auto;
}
.table-footer {
flex-shrink: 0;
}
.table-toolbar-search {
width: 100%;
}
.table-toolbar-extra:empty {
display: none;
}
.ant-table-wrapper {
border: 1px solid #eee;
border-radius: var(--border-radius-base);
}
.ant-spin-container,
.ant-spin-nested-loading {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
height: 100%;
@apply relative;
}
@@ -663,13 +663,24 @@ defineExpose({
@prefix:~ '@{prefixCls}-base-table';
.@{prefix} {
@apply h-full;
@apply h-full max-w-full flex flex-col min-h-0;
.scrollable-container {
max-width: 100%;
flex: 1;
min-height: 0;
overflow-x: auto;
overflow-y: auto;
}
.ant-table-wrapper {
border: 1px solid #eee;
border-radius: var(--border-radius-base);
}
.ant-spin-container,
.ant-spin-nested-loading {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
height: 100%;
@apply relative;
}
+8 -8
View File
@@ -5,23 +5,21 @@
:style="`height:${layoutStore.header.height};line-height:${layoutStore.header.height}`"
>
<!-- :style="`height:${layoutStore.header.height};line-height:${layoutStore.header.height}`"-->
<div class="flex items-center">
<div class="flex-1 flex">
<img :src="getConfigApp('avatar')" style="height: 38px" class="mr-5 mt-1" />
<div class="flex w-full min-w-0 items-center overflow-hidden">
<div class="flex min-w-0 flex-1 items-center overflow-hidden">
<img :src="getConfigApp('avatar')" style="height: 38px" class="mr-5 mt-1 shrink-0" />
<BaseButton
type="text"
icon="vuesax-linear:menu"
@click="handleCollapseSideMenu"
v-if="layoutStore.layout == 'side'"
class="shrink-0"
/>
<!-- justify-center flex-1 -->
<!-- <div class="">-->
<BasicMenu/>
<!-- </div>-->
<BasicMenu />
</div>
<!-- <BaseButton icon="vuesax-linear:menu" @click="handleCollapseSideMenu"></BaseButton>-->
<!-- <div @click="handleCollapseSideMenu">بستن منو</div>-->
<div class="flex items-center gap-3">
<div class="flex shrink-0 items-center gap-3">
<!-- <div>
<BaseButton size="small" @click="openNewTabBalance">تراز</BaseButton>
</div>-->
@@ -235,6 +233,8 @@ function clearLocalStorage() {
.@{prefix} {
padding: unset;
overflow: hidden;
max-width: 100%;
//height: unset;
//@apply flex flex-col justify-center ;
//height: 64px;
+25 -11
View File
@@ -1,8 +1,8 @@
<template>
<!-- 'admin-layout', 'beauty-scroll'-->
<ALayout
:class="[prefixCls, { 'overflow-y-scroll': !layoutStore.header.fixed }]"
class="h-screen"
:class="[prefixCls]"
class="h-screen max-w-[100vw] overflow-hidden"
>
<!-- v-model="drawerOpen"-->
<ADrawer v-if="isMobile">
@@ -36,10 +36,10 @@
<!-- <setting />-->
</ADrawer>
<!-- class="admin-layout-main beauty-scroll"-->
<ALayout>
<ALayout class="flex min-h-0 min-w-0 max-w-full flex-1 flex-col overflow-hidden">
<!-- 111111 this is header-->
<BasicHeader class="hidden md:block"></BasicHeader>
<BasicMobileHeader class="md:!hidden"></BasicMobileHeader>
<BasicHeader v-if="!isMobile"></BasicHeader>
<BasicMobileHeader v-else></BasicMobileHeader>
<!-- <admin-header
:class="[{ 'fixed-tabs': fixedTabs, 'fixed-header': fixedHeader, 'multi-page': multiPage }]"
:style="headerStyle"
@@ -55,10 +55,7 @@
v-show="fixedHeader"
></ALayoutHeader>-->
<!-- :style="`min-height: ${minHeight}px;`"-->
<ALayoutContent
class="admin-layout-content"
:class="[{ 'overflow-y-scroll': layoutStore.header.fixed }]"
>
<ALayoutContent class="admin-layout-content mobile-layout-content">
<!-- :style="{`min-height: ${minHeight}px;`}"-->
<!-- this is content-->
<!-- <Btn1></Btn1>-->
@@ -82,7 +79,7 @@
<RouterView v-slot="{ Component }">
<!-- mode="out-in"-->
<!-- <transition>-->
<component :is="Component" />
<component :is="Component" class="h-full min-h-0" />
<!-- </transition>-->
</RouterView>
</ALayoutContent>
@@ -91,6 +88,7 @@
&lt;!&ndash; <page-footer :link-list="footerLinks" :copyright="copyright" />&ndash;&gt;
</LayoutFooter>-->
</ALayout>
<BasicMobileBottomNav v-if="isMobile" />
<BaseSettings />
</ALayout>
</template>
@@ -102,7 +100,8 @@ import { usePrefix } from '@core/composable/usePrefix'
import { useLayoutStore } from '@core/stores/layout'
import { isMobile } from '@core/utils/is'
import BaseSettings from '@core/layouts/BaseSettings.vue'
import BasicMobileheader from "@/components/BasicMobileHeader.vue";
import BasicMobileHeader from '@/components/BasicMobileHeader.vue'
import BasicMobileBottomNav from '@/components/BasicMobileBottomNav.vue'
const { prefixCls } = usePrefix('basic-layout')
const layoutStore = useLayoutStore()
</script>
@@ -115,6 +114,21 @@ const layoutStore = useLayoutStore()
transition: all 0.2s;
}
.admin-layout-content {
width: 100%;
max-width: 100%;
flex: 1;
min-height: 0;
min-width: 0;
overflow: hidden;
}
@media (max-width: 767px) {
.mobile-layout-content {
padding-bottom: calc(72px + env(safe-area-inset-bottom));
}
}
/* .fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
+232 -126
View File
@@ -1,84 +1,174 @@
<template>
<div :class="[prefixCls, `${layoutStore.layout}-menu`]">
<!-- :selectedKeys="selectedKeys"-->
<AMenu :mode="layoutStore.sideMenu.mode" v-model:selectedKeys="selectedKeys" >
<template v-for="item in menu">
<BasicSubMenu
<div
:class="[
prefixCls,
layoutStore.layout === 'top'
? 'top-menu-nav flex min-w-0 flex-1 items-center'
: `${layoutStore.layout}-menu`
]"
>
<div
ref="menuScrollRef"
:class="[isTopMenu ? 'top-menu top-menu-scroll scroll-none min-w-0 flex-1' : '']"
@scroll="updateScrollArrows"
>
<AMenu
:mode="layoutStore.sideMenu.mode"
v-model:selectedKeys="selectedKeys"
:disabled-overflow="isTopMenu"
>
<template v-for="item in menu">
<BasicSubMenu
v-if="!item.meta?.hideChildren && item.children && item.children.length > 0"
:menu-item="item"
:key="item.name"
:level="1"
/>
<!-- <AMenuItem
v-else
:disabled="item.meta.disabled ?? false"
:key="item.name"
@click="handleClickMenuItem(item)"
>
<template #icon>
<BaseIcon :size="menuIconSize" v-if="item.meta.icon" :icon="item.meta.icon" />
</template>
<span>{{ item.meta.title ?? item.name }}</span>
&lt;!&ndash; <template #title>1111</template>&ndash;&gt;
&lt;!&ndash; <div class="flex"></div>&ndash;&gt;
</AMenuItem>-->
<BaseMenuItem v-else :item="item" :key="item.name"/>
</template>
/>
<BaseMenuItem v-else :item="item" :key="item.name" />
</template>
</AMenu>
</div>
<button
v-if="isTopMenu"
v-show="showLeftArrow"
type="button"
class="top-menu-nav__btn top-menu-nav__btn--left"
aria-label="مشاهده ادامه منو"
@click="scrollMenu('left')"
>
<BaseIcon icon="vuesax-linear:arrow-left-1" size="18" />
</button>
</AMenu >
<button
v-if="isTopMenu"
v-show="showRightArrow"
type="button"
class="top-menu-nav__btn top-menu-nav__btn--right"
aria-label="بازگشت در منو"
@click="scrollMenu('right')"
>
<BaseIcon icon="vuesax-linear:arrow-right-1" size="18" />
</button>
</div>
</template>
<script lang="ts" setup>
import {usePrefix} from '@core/composable/usePrefix'
//import { menuIconSize } from '@root/config'
import {computed} from 'vue'
import type {ComputedRef} from 'vue'
import {sortBy} from 'lodash'
//import { routes } from '@/core/router/routes'
import { computed, nextTick, onMounted, ref, watch } from 'vue'
import type { ComputedRef } from 'vue'
import { sortBy } from 'lodash'
import { useResizeObserver } from '@vueuse/core'
import { usePrefix } from '@core/composable/usePrefix'
import BasicSubMenu from '@core/layouts/composnts/BasicSubMenu.vue'
import {useLayoutStore} from '@core/stores/layout'
import { useLayoutStore } from '@core/stores/layout'
import BaseMenuItem from '@core/layouts/composnts/BaseMenuItem.vue'
import type {RouteRecordName, RouteRecordRaw} from 'vue-router'
import {useRouterStore} from '@core/stores/router'
import type { RouteRecordName, RouteRecordRaw } from 'vue-router'
import { useRouterStore } from '@core/stores/router'
const {prefixCls} = usePrefix('basic-menu')
const SCROLL_STEP = 180
const EDGE_THRESHOLD = 2
const { prefixCls } = usePrefix('basic-menu')
const layoutStore = useLayoutStore()
const currentRoute = useRoute()
const selectedKeys = ref<(RouteRecordName | undefined)[]>([])
const routerStore = useRouterStore()
/*const selectedKeys = computed(() => {
const path: (RouteRecordName | undefined)[] = []
const menuScrollRef = ref<HTMLElement | null>(null)
const showLeftArrow = ref(false)
const showRightArrow = ref(false)
const rtlScrollMode = ref<'ltr' | 'negative' | 'positive'>('ltr')
function walkInRoutes(routes: RouteRecordRaw[]) {
for (const route of routes) {
if (route.name == currentRoute.name) {
path.push(route.name)
return true
}
if (route.children && route.children.length > 0) {
path.push(route.name)
const res = walkInRoutes(route.children)
if (!res) {
path.pop()
} else {
return true
}
}
}
return false
const isTopMenu = computed(() => layoutStore.layout === 'top')
function detectRtlScrollMode(el: HTMLElement): 'ltr' | 'negative' | 'positive' {
if (getComputedStyle(el).direction !== 'rtl') return 'ltr'
const max = el.scrollWidth - el.clientWidth
if (max <= 0) return 'ltr'
const original = el.scrollLeft
el.scrollLeft = 1
const mode = el.scrollLeft === 0 ? 'positive' : 'negative'
el.scrollLeft = original
return mode
}
function ensureRtlScrollMode(el: HTMLElement) {
if (rtlScrollMode.value !== 'ltr') return
rtlScrollMode.value = detectRtlScrollMode(el)
}
function getNormalizedScroll(el: HTMLElement) {
const max = Math.max(0, el.scrollWidth - el.clientWidth)
if (max <= 1) return { max: 0, current: 0 }
ensureRtlScrollMode(el)
let current = el.scrollLeft
if (rtlScrollMode.value === 'negative') {
current = Math.abs(current)
} else if (rtlScrollMode.value === 'positive') {
current = max - current
}
walkInRoutes(routes)
return {
max,
current: Math.max(0, Math.min(max, current))
}
}
return path
})*/
function setNormalizedScroll(el: HTMLElement, normalized: number) {
const max = Math.max(0, el.scrollWidth - el.clientWidth)
const value = Math.max(0, Math.min(max, normalized))
if (rtlScrollMode.value === 'negative') {
el.scrollTo({ left: -value, behavior: 'smooth' })
return
}
if (rtlScrollMode.value === 'positive') {
el.scrollTo({ left: max - value, behavior: 'smooth' })
return
}
el.scrollTo({ left: value, behavior: 'smooth' })
}
function updateScrollArrows() {
const el = menuScrollRef.value
if (!el || !isTopMenu.value) {
showLeftArrow.value = false
showRightArrow.value = false
return
}
const { max, current } = getNormalizedScroll(el)
if (max <= 1) {
showLeftArrow.value = false
showRightArrow.value = false
return
}
showLeftArrow.value = current < max - EDGE_THRESHOLD
showRightArrow.value = current > EDGE_THRESHOLD
}
function scrollMenu(side: 'left' | 'right') {
const el = menuScrollRef.value
if (!el) return
const { max, current } = getNormalizedScroll(el)
if (max <= 1) return
setNormalizedScroll(el, side === 'left' ? current + SCROLL_STEP : current - SCROLL_STEP)
scheduleArrowUpdate()
}
function scheduleArrowUpdate() {
window.setTimeout(updateScrollArrows, 350)
}
onMounted(() => {
//console.log('aaa')
const path: (RouteRecordName | undefined)[] = []
function walkInRoutes(routes: RouteRecordRaw[]) {
@@ -103,8 +193,7 @@ onMounted(() => {
walkInRoutes(routerStore.routes)
selectedKeys.value = path
// return path
nextTick(updateScrollArrows)
})
const menu: ComputedRef<RouteRecordRaw[]> = computed(() => {
@@ -112,33 +201,100 @@ const menu: ComputedRef<RouteRecordRaw[]> = computed(() => {
return item.meta?.order
}).filter((item) => !item.meta?.hideMenu)
})
watch(menu, () => nextTick(updateScrollArrows), { deep: true })
watch(isTopMenu, () => {
rtlScrollMode.value = 'ltr'
nextTick(updateScrollArrows)
})
watch(
() => currentRoute.name,
() => nextTick(updateScrollArrows)
)
useResizeObserver(menuScrollRef, () => {
updateScrollArrows()
})
</script>
<style lang="less">
@prefix: ~ '@{prefixCls}-basic-menu';
.@{prefix} {
&.top-menu-nav {
position: relative;
min-width: 0;
flex: 1;
}
&.top-menu {
@apply pr-5;
//display: inline-flex;
.ant-menu {
// @apply gap-3;
//@apply flex-inline;
//@apply grid;
.top-menu-nav__btn {
position: absolute;
top: 50%;
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
width: 30px;
height: 36px;
border: none;
border-radius: 6px;
background: #fff;
color: @primary-color;
cursor: pointer;
transform: translateY(-50%);
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.06);
transition: background-color 0.2s ease;
&:hover {
background: lighten(@primary-color, 53%);
}
.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item, .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu {
&--left {
left: 0;
padding-right: 4px;
background: linear-gradient(to right, #fff 55%, rgba(255, 255, 255, 0.85));
}
&--right {
right: 0;
padding-left: 4px;
background: linear-gradient(to left, #fff 55%, rgba(255, 255, 255, 0.85));
}
}
&.top-menu,
.top-menu-scroll {
overflow-x: auto;
overflow-y: hidden;
flex: 1;
min-width: 0;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
.ant-menu {
display: flex;
flex-wrap: nowrap;
width: max-content;
border-bottom: none;
padding-top: 4px;
padding-bottom: 4px;
}
.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item,
.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu {
padding: 0 5px;
}
/*TODO check for line bottom menu*/
.ant-menu-horizontal > .ant-menu-item::after, .ant-menu-horizontal > .ant-menu-submenu::after {
.ant-menu-horizontal > .ant-menu-item::after,
.ant-menu-horizontal > .ant-menu-submenu::after {
right: 0 !important;
left: 0 !important;
}
.ant-menu-horizontal > .ant-menu-item-selected::after, .ant-menu-horizontal > .ant-menu-submenu-selected::after {
.ant-menu-horizontal > .ant-menu-item-selected::after,
.ant-menu-horizontal > .ant-menu-submenu-selected::after {
position: absolute;
bottom: 0;
border-bottom: 2px solid @primary-color;
@@ -147,42 +303,16 @@ const menu: ComputedRef<RouteRecordRaw[]> = computed(() => {
}
.ant-menu-item-selected {
//background-color: red;
background-color: lighten(@primary-color, 53%);
/*.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item:after{
border-bottom: 5px solid red;
background-color: red;
}*/
/*& > .ant-menu-item::after {
position: absolute;
right: 20px;
bottom: 0;
left: 20px;
border-bottom: 2px solid #0052d9;
transition: border-color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
content: '';
}*/
}
.ant-menu-item {
margin-left: 12px;
}
/*.ant-menu-item:not(.ant-menu-item-disabled):not(.ant-menu-item-selected):hover,
.ant-menu-submenu .ant-menu-submenu-title:not(.ant-menu-item-disabled):hover
{
border-radius:unset !important;
}*/
//display: inline-flex;
//justify-content: center;
}
.ant-menu-item, .ant-menu-submenu-title {
.ant-menu-item,
.ant-menu-submenu-title {
display: flex;
align-items: center;
@@ -209,20 +339,10 @@ const menu: ComputedRef<RouteRecordRaw[]> = computed(() => {
transition: background-color 0.9ms linear;
border-radius: var(--border-radius-base);
color: unset !important;
//color: red;
background-color: #f3f3f3 !important;
}
}
/* .ant-menu-inline-collapsed{
.ant-menu-item,.ant-menu-submenu-title{
//display: block;
}
}*/
.ant-menu {
padding: 10px;
user-select: none;
@@ -232,18 +352,14 @@ const menu: ComputedRef<RouteRecordRaw[]> = computed(() => {
font-size: 15px;
}
.ant-menu-item, .ant-menu-submenu-title {
//padding: unset !important;
@apply px-2 !important;
.ant-menu-item,
.ant-menu-submenu-title {
@apply px-2 !important;
}
.ant-menu.ant-menu-inline-collapsed > .ant-menu-item .ant-menu-item-icon + span,
.ant-menu.ant-menu-inline-collapsed > .ant-menu-submenu .ant-menu-item-icon + span {
display: none !important;
/*display: flex !important;
flex-direction: column !important;
justify-content: center !important;*/
}
.ant-menu.ant-menu-inline-collapsed > .ant-menu-submenu > .ant-menu-submenu-title {
@@ -253,8 +369,6 @@ const menu: ComputedRef<RouteRecordRaw[]> = computed(() => {
justify-content: center !important;
}
//,.ant-menu.ant-menu-inline-collapsed > .ant-menu-submenu
.ant-menu.ant-menu-inline-collapsed > .ant-menu-item {
padding: 0 !important;
display: flex !important;
@@ -266,14 +380,7 @@ const menu: ComputedRef<RouteRecordRaw[]> = computed(() => {
height: 36px;
}
/*.ant-menu-submenu-title,.ant-menu-submenu-arrow{
//color: #f3f3f3 !important;
}*/
.ant-menu-item.ant-menu-item-selected {
//border-radius: var(--border-radius-base);
&::after {
content: unset;
}
@@ -287,6 +394,5 @@ const menu: ComputedRef<RouteRecordRaw[]> = computed(() => {
.ant-menu-vertical > .ant-menu-item {
height: 36px;
}
}
</style>
+1 -1
View File
@@ -11,7 +11,7 @@ function isDev() {
/**
* is size screen mobile
*/
const isMobile = breakpoints.smaller('lg')
const isMobile = breakpoints.smaller('md')
/*function isMobile() {
const breakpoints = useBreakpoints(breakpointsTailwind)
console.log('change is mobile')
+2 -2
View File
@@ -11,6 +11,6 @@
import { useMediaQuery } from '@vueuse/core'
export const isLargeScreen = useMediaQuery('(min-width: 1023px)')
export const isMediumScreen = useMediaQuery('(min-width: 767px)')
export const isLargeScreen = useMediaQuery('(min-width: 1024px)')
export const isMediumScreen = useMediaQuery('(min-width: 768px)')
export const isMobileScreen = useMediaQuery('(max-width: 767px)')
+27 -19
View File
@@ -14,23 +14,31 @@ export interface Accounting {
sumAll:number
}
export interface TotalAccounting{
id: number
customer_id: number
order_id: null
customer_to_id: number
user_id: number
wallet: null
transaction_id: null
cash: number
note: string
type: number
gold: number
balance_cash: number
balance_gold: number
withdraw_id: number
updated_at: string
deleted_at: null
created_at: string
withdraw?: Withdraw
export interface AccountingCustomerRef {
id?: number
name?: string
mobile?: string
}
export interface TotalAccounting {
id: number
customer_id: number
order_id: null
customer_to_id: number
user_id: number
wallet: null
transaction_id: null
cash: number
note: string
type: number
gold: number
balance_cash: number
balance_gold: number
withdraw_id: number
updated_at: string
deleted_at: null
created_at: string
withdraw?: Withdraw
customer?: AccountingCustomerRef
customer_to?: AccountingCustomerRef
}
+5
View File
@@ -1,5 +1,10 @@
export interface Price {
id?: number
user_id?: number
price?: number
created_at?: string
time_at?: string
user?: { name?: string }
}
export interface last {
+8
View File
@@ -6,3 +6,11 @@ export interface Sms {
message: string
single?: string[]
}
export interface SmsRecord {
id?: number
receiver?: string
sms?: string
created_at?: string
person?: { name?: string; mobile?: string }
}
+8
View File
@@ -8,12 +8,18 @@ export interface Wallet {
customer: User
}
export interface WithdrawTransaction {
title?: string
gold?: number
}
export interface Withdraw {
id?: number
customer_id?: number
user_id?: number
cash?: number
gold?: number
note?: string
tracked_number?: number
date_at?: string
sts?: number
@@ -22,6 +28,8 @@ export interface Withdraw {
sts_str?: string
daj?: string
customer?: User
bank_account?: { cart_number?: string }
transaction?: WithdrawTransaction[]
}
export interface Charge {
@@ -1,15 +1,15 @@
<template>
<div class="flex">
<div class="w-full flex md:block justify-between md:justify-unset items-center">
<div class="flex items-center gap-2 mx-2">
<div class="p-1 h-8 aspect-square rounded-full" :class="bgColor">
<div class="w-full min-w-0 rounded-lg border border-gray-100 bg-gray-50/60 px-3 py-2 lg:border-0 lg:bg-transparent lg:px-0 lg:py-0">
<div class="flex w-full min-w-0 items-center justify-between gap-2 lg:block">
<div class="flex min-w-0 items-center gap-2">
<div class="aspect-square h-8 shrink-0 rounded-full p-1" :class="bgColor">
<BaseIcon icon="vuesax-linear:profile-add" :class="textColor" />
</div>
<div class="text-xs font-bold">{{ title }}</div>
<div class="truncate text-xs font-bold">{{ title }}</div>
</div>
<div class="flex flex-row-reverse gap-1 mt-5 items-center md:w-full pl-2 items-end">
<div class="flex shrink-0 flex-row-reverse items-baseline gap-1 pl-1 lg:mt-5 lg:w-full lg:pl-2">
<div class="text-xs">{{ unit }}</div>
<div class="font-bold text-xl">{{ count }}</div>
<div class="font-bold text-lg lg:text-xl">{{ count ?? 0 }}</div>
</div>
</div>
</div>
+124 -77
View File
@@ -1,49 +1,49 @@
<template>
<div class="p-5">
<a-spin size="large" :spinning="loading">
<div class="dashboard-page box-border h-full min-h-0 w-full max-w-full overflow-x-hidden overflow-y-auto p-3 md:p-4 lg:p-5">
<a-spin size="large" :spinning="loading" class="block min-w-0">
<div
class="w-full bg-[#0A1B37] flex p-2 rounded-lg md:justify-evenly flex-col shadow-lg"
class="dashboard-price-banner grid w-full min-w-0 gap-2 rounded-lg bg-[#0A1B37] p-3 shadow-lg sm:grid-cols-2 lg:flex lg:justify-evenly"
>
<div class="flex text-white gap-2 items-center">
<div>مظنه روز:</div>
<div class="font-bold">
<div class="flex flex-wrap items-center gap-x-2 gap-y-1 text-sm text-white sm:col-span-2 lg:col-span-1">
<span class="shrink-0">مظنه روز:</span>
<span class="break-all font-bold">
{{ usePrice(parseInt(dashboardInf?.price ?? '0'), true, '') }}
</div>
<div class="text-xs">ریال</div>
</span>
<span class="text-xs">ریال</span>
</div>
<div class="flex text-white gap-2 items-center">
<div>آخرین تاریخ به روز رسانی:</div>
<div class="flex flex-wrap items-center gap-x-2 gap-y-1 text-sm text-white">
<span>آخرین تاریخ به روز رسانی:</span>
</div>
<div class="flex text-white gap-2 items-center">
<div>هر مثقال طلا:</div>
<div class="font-bold"></div>
<div class="text-xs">ریال</div>
<div class="flex flex-wrap items-center gap-x-2 gap-y-1 text-sm text-white">
<span class="shrink-0">هر مثقال طلا:</span>
<span class="break-all font-bold"></span>
<span class="text-xs">ریال</span>
</div>
</div>
<div class="flex flex-col items-center md:items-unset md:grid md:grid-cols-7 gap-4 mt-3">
<BaseCard class="w-full md:w-none md:col-span-7">
<div class="mt-3 flex w-full min-w-0 flex-col gap-3 md:gap-4 lg:grid lg:grid-cols-7 lg:items-start">
<BaseCard class="w-full min-w-0 lg:col-span-7">
<div class="text-lg font-bold">مشتری</div>
<div
class="flex flex-col md:flex-row justify-around mt-5 justify-between items-dashboard h-full"
class="items-dashboard mt-3 flex h-full flex-col gap-2 lg:mt-5 lg:flex-row lg:justify-between lg:gap-0"
>
<DashboardItem
class="md:w-1/3"
class="lg:w-1/3"
color="blue"
title="مشتری جدید"
unit="نفر"
:count="dashboardInf?.new_customer_single"
/>
<div class="h-[60%] hidden md:block bg-gray-200 w-[1px]" />
<div class="hidden h-[60%] w-[1px] bg-gray-200 lg:block" />
<DashboardItem
class="md:w-1/3"
class="lg:w-1/3"
color="green"
title="کل مشتریان"
unit="نفر"
:count="dashboardInf?.all_customer_single"
/>
<div class="h-[60%] hidden md:block bg-gray-200 w-[1px]" />
<div class="hidden h-[60%] w-[1px] bg-gray-200 lg:block" />
<DashboardItem
class="md:w-1/3"
class="lg:w-1/3"
color="orange"
title="مشتریان بایگانی شده"
unit="نفر"
@@ -51,7 +51,7 @@
/>
</div>
</BaseCard>
<BaseCard class="md:hidden w-full">
<BaseCard class="w-full min-w-0 lg:hidden">
<div class="grid grid-cols-3 gap-1 text-xs">
<div class="bg-blue-600 text-white rounded p-1">مجموع</div>
<div class="bg-blue-600 text-white rounded p-1">ریال</div>
@@ -108,36 +108,34 @@
</div>
</div>
</BaseCard>
<BaseCard class="w-full h-full md:col-span-7">
<BaseCard class="h-full w-full min-w-0 lg:col-span-7">
<ASpin :spinning="tarazLoading">
<div class="grid md:grid-cols-3 gap-3 mt-2">
<div>
<div class="md:flex content-center gap-3">
<div class="w-full md:flex items-baseline gap-2 bg-gray-100 p-2 rounded-lg">
<p class="font-bold mr-2">تاریخ تراز</p>
<div class="w-full flex items-baseline flex-1 gap-3">
<p class="font-bold">از:</p>
<BaseDatePicker
v-model:value="startTaraz"
format="YYYY-MM-DD HH:mm:ss"
class="flex-1"
/>
</div>
<div class="w-full flex items-baseline flex-1 gap-3">
<p class="font-bold">تا:</p>
<BaseDatePicker
v-model:value="endTaraz"
format="YYYY-MM-DD HH:mm:ss"
class="flex-1"
/>
</div>
<BaseButton @click="getTaraz()">ثبت تاریخ</BaseButton>
<div class="mt-2 grid min-w-0 gap-3 lg:grid-cols-3">
<div class="min-w-0">
<div class="flex w-full min-w-0 flex-col gap-3 rounded-lg bg-gray-100 p-3 lg:flex-row lg:items-end lg:p-2">
<p class="shrink-0 font-bold lg:mr-2">تاریخ تراز</p>
<div class="flex min-w-0 flex-1 flex-col gap-2 sm:flex-row sm:items-baseline sm:gap-3">
<p class="shrink-0 font-bold">از:</p>
<BaseDatePicker
v-model:value="startTaraz"
format="YYYY-MM-DD HH:mm:ss"
class="min-w-0 w-full flex-1"
/>
</div>
<div class="flex min-w-0 flex-1 flex-col gap-2 sm:flex-row sm:items-baseline sm:gap-3">
<p class="shrink-0 font-bold">تا:</p>
<BaseDatePicker
v-model:value="endTaraz"
format="YYYY-MM-DD HH:mm:ss"
class="min-w-0 w-full flex-1"
/>
</div>
<BaseButton class="w-full shrink-0 sm:w-auto" @click="getTaraz()">ثبت تاریخ</BaseButton>
</div>
<div class="flex font-bold p-2">
<div class="flex flex-wrap items-center gap-2 p-2 font-bold">
<span>جمع واریزی : </span>
<div class="mr-4">{{ usePrice(balance_?.income_money ?? 0) }}</div>
<div>{{ usePrice(balance_?.income_money ?? 0) }}</div>
</div>
</div>
<div
@@ -147,9 +145,9 @@
<span class="text-center text-base font-700 bg-yellow text-white rounded-lg">
تراز طلایی
</span>
<div class="flex flex-col md:flex-row items-center justify-between md:gap-15">
<div class="w-full flex flex-col gap-3">
<div class="flex justify-between mx-5">
<div class="flex flex-col items-stretch justify-between gap-3 lg:flex-row lg:items-center lg:gap-15">
<div class="flex w-full flex-col gap-3">
<div class="flex justify-between gap-2 px-2 lg:mx-5">
<span class="font-600">تراز اصلی:</span>
<span
class="text-base"
@@ -175,9 +173,9 @@
<span class="text-center text-base font-700 bg-blue-6 rounded-lg text-white">
تراز ریالی
</span>
<div class="flex flex-col md:flex-row items-center justify-between md:gap-15">
<div class="w-full flex flex-col gap-3">
<div class="flex justify-between mx-5">
<div class="flex flex-col items-stretch justify-between gap-3 lg:flex-row lg:items-center lg:gap-15">
<div class="flex w-full flex-col gap-3">
<div class="flex justify-between gap-2 px-2 lg:mx-5">
<span class="font-600">تراز اصلی:</span>
<span
class="text-base"
@@ -292,34 +290,34 @@
:series="sumOfSellCoworkerSeries"
></apexchart>
</BaseCard>-->
<BaseCard class="w-full md:grid md:col-span-7">
<div class="font-bold text-lg mt-2 text-center md:text-right">
<BaseCard class="w-full min-w-0 lg:col-span-7">
<div class="mt-2 text-center text-lg font-bold lg:text-right">
حسابداری گلد در یک نگاه
</div>
<div class="md:flex mt-6">
<div class="flex items-baseline flex-1 gap-3">
<p>از:</p>
<div class="mt-4 flex flex-col gap-3 lg:mt-6 lg:flex-row">
<div class="flex min-w-0 flex-1 flex-col gap-2 sm:flex-row sm:items-baseline sm:gap-3">
<p class="shrink-0">از:</p>
<BaseDatePicker
v-model:value="startDate"
@on-change="getTaraz"
format="YYYY-MM-DD HH:mm:ss"
class="flex-1"
class="min-w-0 w-full flex-1"
/>
</div>
<div class="flex items-baseline flex-1 gap-3">
<p class="md:mr-6">تا:</p>
<div class="flex min-w-0 flex-1 flex-col gap-2 sm:flex-row sm:items-baseline sm:gap-3">
<p class="shrink-0 lg:mr-6">تا:</p>
<BaseDatePicker
v-model:value="endDate"
@on-change="getTaraz"
format="YYYY-MM-DD HH:mm:ss"
class="flex-1"
class="min-w-0 w-full flex-1"
/>
</div>
</div>
<div class="grid-cols-1 grid gap-2 bg-green-100 rounded-lg shadow-lg">
<div class="p-4">
<div class="font-bold text-lg">دریافتی</div>
<div class="md:grid-cols-5 grid gap-2 mt-4">
<div class="grid-cols-1 grid gap-2 rounded-lg bg-green-100 shadow-lg">
<div class="p-3 lg:p-4">
<div class="text-lg font-bold">دریافتی</div>
<div class="mt-4 grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-5 lg:gap-2">
<div>
<div>کل سود دریافت شده</div>
<div class="font-bold">{{ usePrice(balance?.fullPriceSood ?? 0) }}</div>
@@ -343,9 +341,9 @@
</div>
</div>
</div>
<div class="mt-4 bg-red-100 p-4 rounded-lg flex-col md:flex-row shadow-lg">
<div class="font-bold text-lg">خروجی</div>
<div class="md:grid-cols-2 grid mt-4">
<div class="mt-4 flex-col rounded-lg bg-red-100 p-3 shadow-lg lg:flex-row lg:p-4">
<div class="text-lg font-bold">خروجی</div>
<div class="mt-4 grid grid-cols-1 gap-3 sm:grid-cols-2 lg:gap-4">
<div>
<div>پاداش معرفی به گرم</div>
<div class="font-bold">{{ balance?.Gifts }}</div>
@@ -357,7 +355,7 @@
</div>
</div>
<div
class="mt-4 bg-blue-100 p-4 rounded-lg md:justify-evenly flex-col md:flex-row shadow-lg"
class="mt-4 flex-col rounded-lg bg-blue-100 p-3 shadow-lg lg:flex-row lg:justify-evenly lg:p-4"
>
<div>
<div class="font-bold text-lg">قیمت کل</div>
@@ -368,11 +366,11 @@
</div>
</div>
</BaseCard>
<BaseCard class="w-full md:w-none md:col-span-7">
<BaseCard class="w-full min-w-0 lg:col-span-7">
<div>
<div class="font-bold text-lg mt-2 text-center md:text-right">نوسانات قیمت</div>
<div id="chart">
<div class="toolbar text-center md:text-right mt-2 mb-2">
<div class="mt-2 text-center text-lg font-bold lg:text-right">نوسانات قیمت</div>
<div id="chart" class="min-w-0">
<div class="dashboard-chart-toolbar mt-2 mb-2 text-center lg:text-right">
<button
id="DAILY"
@click="updateData('DAILY')"
@@ -398,10 +396,10 @@
</button>
</div>
<div id="chart-timeline">
<div id="chart-timeline" class="min-w-0 overflow-hidden">
<apexchart
type="area"
height="350"
:height="chartHeight"
ref="chart"
:options="priceChartOptions"
:series="priceChartSeries"
@@ -432,6 +430,9 @@ import type { OthersPrices } from '@/model/othersPrices'
import { getNow, toJalaliNoTime } from '@core/utils/useDataTime'
import type { Balance } from '@/model/product'
import { getBalance } from "@/api/product";
import { isLargeScreen } from '@core/utils/responsive'
const chartHeight = computed(() => (isLargeScreen.value ? 350 : 280))
const tarazLoading = ref<boolean>(false)
const main = ref(null)
const type = ref<string>('week')
@@ -861,5 +862,51 @@ onMounted(() => {
@apply ! bg- [ #0A1B37 ] ! text-white rounded-lg;
}
.dashboard-chart-toolbar {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0.5rem;
button {
border: 1px solid #d1d5db;
border-radius: 0.5rem;
background: #fff;
color: #374151;
cursor: pointer;
font-family: inherit;
font-size: 0.75rem;
padding: 0.375rem 0.75rem;
transition: all 0.2s ease;
&.active,
&:hover {
background: #0a1b37;
border-color: #0a1b37;
color: #fff;
}
}
}
@media (min-width: 1024px) {
.dashboard-chart-toolbar {
justify-content: flex-start;
button {
font-size: 0.875rem;
}
}
}
.dashboard-page :deep(.ant-card) {
min-width: 0;
}
.dashboard-page :deep(.ant-picker) {
max-width: 100%;
min-width: 0;
width: 100%;
}
//chart
</style>
@@ -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')
@@ -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"

Some files were not shown because too many files have changed in this diff Show More