change responsive]
This commit is contained in:
Vendored
+5
-2
@@ -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']
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
+5
-10
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
/** ارتفاع یکسان برای تمام bottom action sheet ها */
|
||||
export const ACTION_SHEET_HEIGHT = '80vh'
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
//ok Code
|
||||
/*html {
|
||||
overflow: scroll;
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
max-width: 100%;
|
||||
overflow-x: hidden;
|
||||
}*/
|
||||
}
|
||||
|
||||
.scroll-none{
|
||||
&::-webkit-scrollbar {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 @@
|
||||
<!– <page-footer :link-list="footerLinks" :copyright="copyright" />–>
|
||||
</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;
|
||||
|
||||
@@ -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>
|
||||
<!– <template #title>1111</template>–>
|
||||
<!– <div class="flex"></div>–>
|
||||
</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>
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,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
@@ -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
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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('اطلاعات جامع')
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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')
|
||||
|
||||
@@ -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=""
|
||||
>
|
||||
<!– <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>
|
||||
<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=""
|
||||
>
|
||||
<!– <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>
|
||||
<!– <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>
|
||||
<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
@@ -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
Reference in New Issue
Block a user