1404/11/04- first

This commit is contained in:
2026-01-24 17:45:54 +03:30
commit edf1824229
554 changed files with 59581 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
<template>
<a-card class="m-5 rounded-xl h-[91vh]">
<slot></slot>
</a-card>
</template>
<script></script>
<style scoped></style>
+52
View File
@@ -0,0 +1,52 @@
<template>
<div :class="isLine ? 'flex justify-between items-center' : ''">
<div class="flex items-center gap-1">
<div class="text-gray-400">{{ title }}:</div>
<BasicIcon
v-if="info"
icon="vuesax-linear:info-circle"
size="18"
color="orange"
@click="infoVisible = true"
/>
</div>
<div v-if="value !== undefined" class="font-bold mt-1">{{ value }}</div>
<div v-else class="mt-1">
<slot></slot>
</div>
</div>
<!-- <van-dialog
v-model:show="infoVisible"
class="updateButton"
confirm-button-text="متوجه شدم"
show-confirm-button
@confirm="update()"
>
<div class="flex flex-col items-center p-2">
&lt;!&ndash; <div class="home_appear_fade_in_up delay-100">به روز رسانی</div>&ndash;&gt;
<div v-if="infoHead" class="font-bold">{{ infoHead }}</div>
<div class="home_appear_fade_in_up mt-2 text-sm">{{ info }}</div>
</div>
</van-dialog>-->
</template>
<script setup lang="ts">
import { update } from 'lodash'
import { ref } from 'vue'
// import anim from '@/assets/lottie/animInfo.json'
interface Prop {
title: string
info?: string
infoHead?: string
isLine?: boolean
value?: string | number
}
const props = withDefaults(defineProps<Prop>(), {
isLine: false
})
const infoVisible = ref<boolean>()
</script>
<style scoped lang="less"></style>
+105
View File
@@ -0,0 +1,105 @@
<template>
<!-- <BasicModal
v-model:visible="props.visible"
width="30rem"
:title="props.title"
@close="emits('close')"
>
<div :class="[prefixCls]" class="bg-[#F6F6F4] flex justify-between p-3">
<div>
<div class="font-bold">{{ props.name }}</div>
<div class="text-primary-type">{{ props.type }}</div>
&lt;!&ndash; <div class="text-xl">fggh</div>&ndash;&gt;
</div>
<div>
<div>{{ props.date }}</div>
<div class="text-left">{{ props.time }}</div>
</div>
</div>
<div></div>
</BasicModal>-->
<a-modal
v-model:visible="localValue"
:title="props.title"
centered
width="80%"
@cancel="emits('close')"
@close="emits('close')"
footer
>
<div class="overflow-y-auto max-h-[70vh] mt-10">
<div :class="[prefixCls]" class="bg-blue-50 rounded flex justify-between p-3">
<div>
<div class="font-bold">{{ props.name }}</div>
<div class="text-primary-type mt-1">{{ props.type }}</div>
</div>
<div>
<div>{{ props.date }}</div>
<div class="text-left">{{ props.time }}</div>
</div>
</div>
</div>
<div class="bg-[#CACED8] w-full h-[1px] my-8" />
<div class="text-[#949DB0]">{{ props.itemsTitle }}</div>
<div class="grid md:grid-cols-4 mt-5 gap-5 p-5">
<div v-for="val in props.items">
<div class="text-[#949DB0]">{{ val.key }}</div>
<div class="font-bold">{{ val.value }}</div>
</div>
<div class="md:col-span-5" v-if="props.description">
<div class="text-[#949DB0]">توضیحات</div>
<div class="font-bold">{{ props.description ?? '-' }}</div>
</div>
</div>
</a-modal>
</template>
<script setup lang="ts">
//define emits
import { usePrefix } from '@core/composable/usePrefix'
import type { KeyVal } from '@/components/BasicInfoModal/model/Info'
import { onMounted } from 'vue'
//props
interface Prop {
visible?: boolean
title?: string
name?: string
date?: string
time?: string
type?: string | number
itemsTitle?: string
description?: string
items?: KeyVal[]
}
//emits
const emits = defineEmits(['update:visible', 'close'])
const localValue = computed({
get() {
return props.visible
},
set(value) {
emits('update:visible', value)
}
})
//set type color
const props = defineProps<Prop>()
//style
const { prefixCls } = usePrefix('info-modal')
</script>
<style lang="less">
@prefix: ~'@{prefixCls}-info-modal';
.@{prefix} {
.text-primary-type {
color: @primary-color;
}
}
</style>
@@ -0,0 +1,16 @@
export interface Info {
title?: string
date?: string
time?: string
type?: string | number
typeColor?: string
mobile?: number
name?: string
itemsTitle?: string
items?: Array<KeyVal>
description?: string
}
export interface KeyVal {
key: string
value?: string | number
}
+125
View File
@@ -0,0 +1,125 @@
<template>
<div :class="[prefixCls]">
<div>
{{ props.title }}
</div>
<a-input-password
v-if="isPassword"
v-model:value="localValue"
:addon-after="props.addonAfter"
:addon-before="props.addonBefore"
:disabled="props.disabled"
:placeHolder="props.placeHolder"
:size="props.size"
:type="props.type"
class=""
@change="handlerChange"
/>
<a-input
v-else
v-model:value="localValue"
:addon-after="props.addonAfter"
:addon-before="props.addonBefore"
:disabled="props.disabled"
:placeHolder="props.placeHolder"
:size="props.size"
:type="props.type === 'price' ? 'tel' : props.type"
class=""
@change="handlerChange"
/>
</div>
</template>
<script lang="ts" setup>
import { usePrefix } from '@core/composable/usePrefix'
import { computed } from 'vue'
import { usePrice } from '@core/utils/usePrice'
//Props
interface Prop {
title?: String
value?: String | Number
size?: String
type?: String
isPassword?: boolean
disabled?: boolean
addonAfter?: String
addonBefore?: String
addonBgColor?: String
addonTextColor?: String
placeHolder?: String
}
const addonBgColor = computed(() => props.addonBgColor)
const addonTextColor = computed(() => props.addonTextColor)
//emits
const localValue = computed({
set(value) {
// console.log(value?.toString().replaceAll(',', ''))
props.type === 'price'
? emits('update:value', value?.toString().replaceAll(',', ''))
: emits('update:value', value)
},
get() {
let a =
props.type === 'price'
? props.value
? usePrice(props.value as number, true, '').trim()
: ''
: props.value
// console.log(props.value)
return a
}
})
const props = withDefaults(defineProps<Prop>(), {
isPassword: false
})
const emits = defineEmits(['update:value', 'onchange'])
const handlerChange = (e: any) => {
// emits('onchange', e.target.value)
props.type === 'price'
? emits('update:value', e.target.value?.toString().replaceAll(',', ''))
: emits('update:value', e.target.value)
props.type === 'price'
? emits('onchange', e.target.value?.toString().replaceAll(',', ''))
: emits('onchange', e.target.value)
// console.log(e.target.value)
}
//css style
const { prefixCls } = usePrefix('input')
</script>
<style lang="less">
@prefix: ~'@{prefixCls}-input';
.@{prefix} {
/* .ant-input-group-addon:first-child {
!* border: #d9d9d9 1px solid;
border-radius: 0 6px 6px 0;*!
}*/
.ant-input-group {
direction: ltr;
}
.ant-input-group > .ant-input:first-child,
.ant-input-group-addon:first-child {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-bottom-left-radius: @border-radius-base;
border-top-left-radius: @border-radius-base;
}
/*.ant-input-group > .ant-input-rtl:first-child,
.ant-input-group-rtl .ant-input-group-addon:first-child {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-bottom-left-radius: @border-radius-base;
border-top-left-radius: @border-radius-base;
}*/
.ant-input-group > .ant-input:last-child,
.ant-input-group-addon:last-child {
border-radius: 0 @border-radius-base @border-radius-base 0;
}
}
</style>
+11
View File
@@ -0,0 +1,11 @@
<template>
<a-card class="m-5 rounded-xl md:h-[91vh]">
<slot></slot>
</a-card>
</template>
<script></script>
<style scoped>
</style>
+9
View File
@@ -0,0 +1,9 @@
<template>
<a-card class="h-[93vh]">
<slot></slot>
</a-card>
</template>
<script></script>
<style scoped></style>
@@ -0,0 +1,40 @@
<template>
<template v-for="item in props.menu" :key="item.name">
<AMenuItem :key="item.name" v-if="!item.children" @click="handleClickMenu(item)">
<template #icon v-if="item?.meta?.icon">
<BaseIcon :icon="item?.meta?.icon" />
</template>
{{ item?.meta?.title }}
</AMenuItem>
<ASubMenu :key="item.name" v-else>
<template #icon v-if="item?.meta?.icon">
<BaseIcon :icon="item.meta.icon" />
</template>
<template #title>{{ item.meta.title }}</template>
<BasicMenuItem v-if="item.children" :menu="item.children" />
</ASubMenu>
</template>
</template>
<script setup lang="ts">
// import BasicMenuItem from './BasicMenuItem'
import { defineProps } from 'vue'
import type { RouteRecordRaw } from 'vue-router'
import { useRouter } from 'vue-router'
interface Prop {
menu: RouteRecordRaw[]
}
const router = useRouter()
function handleClickMenu(item: RouteRecordRaw) {
//debugger
router.push({ name: item.name })
}
const props = defineProps<Prop>()
</script>
<style scoped></style>
+56
View File
@@ -0,0 +1,56 @@
<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>
</template>
<script setup lang="ts">
import type { MenuMode, MenuTheme } from 'ant-design-vue'
import { defineProps } from 'vue'
//import Icon from '@core/components/BaseIcon/src/Icon.vue'
import BasicMenuItem from '@/components/BasicMenu/components/BasicMenuItem.vue'
import type { RouteRecordRaw } from 'vue-router'
const selectedKeys = ref<string[]>([])
const router = useRouter()
/*const state = reactive({
selectedKeys: ['1'],
openKeys: ['sub1']
})*/
//define emit
//const emits = defineEmits(['onChange'])
//selected key
//const selectedKeys = ref<Array<string>>(['1'])
/*watch(selectedKeys, (val) => {
emits('onChange', val[0])
})*/
//prop
interface Prop {
menu: RouteRecordRaw[]
mode?: MenuMode
theme?: MenuTheme
width?: string
}
const props = withDefaults(defineProps<Prop>(), {
mode: 'inline',
theme: 'light',
width: '20rem'
})
onMounted(() => {
selectedKeys.value = [router.currentRoute.value.name as string]
//emits('onChange', '1')
})
</script>
<style scoped></style>
+235
View File
@@ -0,0 +1,235 @@
<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>
<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>
<template #overlay>
<AMenu>
<AMenuItem>
<div class="flex flex-col">
<span class="font-bold">{{ userStore.userInfo?.name }}</span>
<span>{{ userStore.userInfo?.post_user }}</span>
</div>
</AMenuItem>
<AMenuDivider />
<AMenuItem @click="myInfoVisible = true">
<div class="flex items-center gap-1">
<Icon icon="vuesax-linear:profile" size="18" />
<span>اطلاعات حساب کاربری</span>
</div>
</AMenuItem>
<AMenuItem @click="clearLocalStorage">
<div class="flex items-center gap-1">
<Icon icon="vuesax-linear:logout" size="18" />
<span>خروج</span>
</div>
</AMenuItem>
</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>
</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 { useUserStore } from '@/store/userStore'
import type { User } from '@/model/user'
import { createUser, updateMyInformation, updateUser } from '@/api/user'
import { message } from 'ant-design-vue/es'
import { onMounted, ref } 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";
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>()
const form = ref<User>({
fname: '',
lname: '',
mobile: '',
post_user: '',
manager_id: '',
responsible_id: '',
section: '',
section_id: undefined,
process_id: '',
qc_id: undefined,
user_type: undefined,
permission: []
})
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
try {
await updateMyInformation(form.value)
userStore.getMyInfo()
message.success('اطلاعات با موفقیت ویرایش شد')
myInfoVisible.value = false
} catch (e) {
// message.error('خطا در بارگذاری اطلاعات')
console.error(e)
} finally {
loading.value = false
}
})
}
//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)
}
</script>
<style scoped lang="less"></style>
+71
View File
@@ -0,0 +1,71 @@
<template>
<div>
<a-modal
v-model:visible="localValue"
:title="props.title"
centered
@ok="emits('ok')"
@cancel="emits('close')"
@close="emits('close')"
:confirm-loading="props.confirmLoading"
:width="props.width"
:ok-text="props.okText"
:cancel-text="props.cancelText"
:wrapClassName="props.wrapClassName"
>
<template v-if="props.customFooter" #footer>
<slot name="footer" />
</template>
<a-spin :spinning="props.loading">
<div class="overflow-y-auto py-2 px-1 relative max-h-[80vh]" id="body">
<slot />
</div>
</a-spin>
</a-modal>
</div>
</template>
<script setup lang="ts">
//define emit
const emits = defineEmits(['ok', 'close', 'update:visible'])
//define props
interface Prop {
visible: boolean
title?: string
okText?: string
width?: string
cancelText?: string
customFooter?: boolean
confirmLoading?: boolean
loading?: boolean
wrapClassName?: string
}
const props = withDefaults(defineProps<Prop>(), {
visible: false,
cancelText: 'انصراف',
okText: 'ثبت',
customFooter: false,
confirmLoading: false,
loading: false,
wrapClassName: ''
})
const localValue = computed({
get() {
return props.visible
},
set(value) {
emits('update:visible', value)
}
})
</script>
<style>
.ant-modal-content {
border-radius: 0.6rem;
padding-top: 0.5rem;
}
</style>
+108
View File
@@ -0,0 +1,108 @@
<template>
<div :class="[prefixCls]">
<div>
{{ props.title }}
</div>
<a-select
class="w-full"
:size="props.size"
v-model:value="localValue"
:disabled="props.disabled"
show-search
:mode="props.mode"
:style="props.style"
:multiple="props.multiple"
@change="handleChange"
:placeholder="props.placeholder"
:allowClear="props.allowClear"
:filter-option="filterOption"
>
<template #notFoundContent v-if="noData">
<div class="w-full text-center font-bold">{{ noData }}</div>
</template>
<a-select-option
:label="val[props.valueName]"
v-for="val in props.items"
:value="val[props.keyName]"
>
<a-tooltip v-if="props.hasTooltip" placement="left" class="cursor-pointer">
<template #title>
<span>{{ val[props.valueName] }}</span>
</template>
<div class="truncate max-w-40">{{ val[props.valueName] }}</div>
</a-tooltip>
<div v-else>{{ val[props.valueName] }}</div>
</a-select-option>
</a-select>
</div>
</template>
<script setup lang="ts">
import { usePrefix } from '@core/composable/usePrefix'
//Props
interface Prop {
title?: string
value?: string | number | boolean
style?: string
size?: string
keyName: string
disabled?: boolean
mode?: string
valueName: string
multiple?: boolean
items?: any[]
placeholder?: string
allowClear?: boolean
hasTooltip?: boolean
noData?: string
}
const props = withDefaults(defineProps<Prop>(), {
allowClear: true,
hasTooltip: true
})
// console.log('props.items ====>', props.items)
//define emits
const emit = defineEmits(['update:value', 'change'])
const localValue = computed({
get() {
return props.value
},
set(value) {
emit('update:value', value)
}
})
const filterOption = (input: string, option: any) => {
return option.label?.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
const handleChange = (e: any) => {
// console.log('e ====>', e)
emit(
'change',
props.items?.find((x) => x[props.keyName] === e)
)
}
//css style
const { prefixCls } = usePrefix('select')
</script>
<style lang="less">
@prefix: ~'@{prefixCls}-select';
.@{prefix} {
//padding: 5px;
/* .ant-select {
.ant-select-selector {
border-color: @primary-color;
border-radius: @border-radius-base;
}
}*/
}
</style>
+17
View File
@@ -0,0 +1,17 @@
<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%]">
<slot />
</div>
</div>
</template>
<script setup lang="ts">
interface Prop {
title: string
}
const props = defineProps<Prop>()
</script>
<style scoped></style>
+63
View File
@@ -0,0 +1,63 @@
<template>
<div v-if="!props.loading">
<div class="gap-2">
<a-tag
v-for="item in props.items"
:color="props.color"
:closable="props.closable"
@close="emits('delete', item.id)"
class="cursor-pointer"
@click="emits('edit', item.id)"
>{{ item.name }}
</a-tag>
<a-tag
style="background: #fff; border-style: dashed; margin-top: 0.5rem"
@click="emits('add')"
class="cursor-pointer"
>
<div class="flex items-center">
<BaseIcon icon="vuesax-linear:add" size="0.7rem" />
<div class="mr-1">افزودن</div>
</div>
</a-tag>
</div>
</div>
<div v-else>
<ContentLoader
width="350"
height="100"
viewBox="0 0 350 100"
backgroundColor="#f5f5f5"
foregroundColor="#dbdbdb"
>
<rect x="0" y="0" rx="3" ry="3" width="80" height="20" />
<rect x="90" y="0" rx="3" ry="3" width="80" height="20" />
<rect x="180" y="0" rx="3" ry="3" width="80" height="20" />
<rect x="270" y="0" rx="3" ry="3" width="80" height="20" />
</ContentLoader>
</div>
</template>
<script setup lang="ts">
import { ContentLoader } from 'vue-content-loader'
//define emits
const emits = defineEmits(['add', 'edit', 'delete'])
//define props
interface Prop {
items?: Array<string>
color: string
loading?: boolean
closable?: boolean
}
const props = withDefaults(defineProps<Prop>(), {
closable: false
})
/*watch(props, (val) => {
console.log('val ====>', val)
})*/
</script>
<style scoped></style>
+4
View File
@@ -0,0 +1,4 @@
export interface TagItem {
id: number | undefined
name: string | undefined
}
+51
View File
@@ -0,0 +1,51 @@
<template>
<div :class="[prefixCls]">
<div>
{{ props.title }}
</div>
<a-textarea
v-model:value="localValue"
class=""
:type="props.type"
:show-count="props.showCount"
:placeHolder="props.placeHolder"
/>
</div>
</template>
<script setup lang="ts">
import { usePrefix } from '@core/composable/usePrefix'
//Props
interface Prop {
title?: string
value: string | null
showCount?: boolean
maxlength?: number
placeHolder?: string
}
const props = defineProps<Prop>()
const emit = defineEmits(['update:value'])
const localValue = computed({
get() {
return props.value
},
set(value) {
emit('update:value', value)
}
})
//css style
const { prefixCls } = usePrefix('input-area')
</script>
<style lang="less">
@prefix: ~'@{prefixCls}-input-area';
.@{prefix} {
}
</style>
@@ -0,0 +1,69 @@
<template>
<a-upload-dragger
v-model:fileList="fileList"
name="file"
:multiple="props.multiple"
:action="props.url"
@change="handleChange"
:headers="props.headers"
:maxCount="props.maxCount"
:showUploadList="true"
>
<div class="py-10 px-5">
<BaseIcon icon="vuesax-linear:document-upload" size="40" color="#4A5568" />
<p class="ant-upload-text">برای آپلود، روی فایل کلیک کنید یا فایل را درون این قسمت بکشید</p>
</div>
</a-upload-dragger>
</template>
<script lang="ts" setup>
import { InboxOutlined } from '@ant-design/icons-vue'
import { message } from 'ant-design-vue'
import { defineComponent, ref } from 'vue'
import type { UploadChangeParam } from 'ant-design-vue'
const handleChange = (info: UploadChangeParam) => {
// console.log(info)
const status = info.file.status
if (status !== 'uploading') {
// console.log(info.file, info.fileList)
/*emits('ok', info.file.response.id)*/
}
if (status === 'done') {
// message.success(`${info.file.name} file uploaded successfully.`)
console.log(fileList)
emits('ok', info.file.response.id)
} else if (status === 'error') {
message.error(`خطا در بارگذاری فایل`)
} else if (status === 'removed') {
emits('delete', info.file.uid)
}
}
//props
interface Prop {
url: string
multiple?: boolean
fileList?: Array<FileList>
maxCount?: number
headers?: any
}
const props = defineProps<Prop>()
//emits
const emits = defineEmits(['update:fileList', 'ok', 'delete'])
// const fileList = ref([])
const fileList = computed({
get() {
return props.fileList || ([] as Array<FileList>)
},
set(value) {
emits('update:fileList', value)
}
})
</script>
<style scoped></style>
@@ -0,0 +1,4 @@
export interface FileList {
uid?: any | null | undefined
name?: any | null | undefined
}
+18
View File
@@ -0,0 +1,18 @@
<template>
<BasicSelect :items="budgetItems" valueName="value" keyName="id" />
<!-- <div>1111</div>-->
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useStaticApiListsStore } from '@/store/staticApisStore'
import type { Const } from '@/model/const'
const budgetItems = ref<Array<Const> | undefined>([])
//define store
const store = useStaticApiListsStore()
onMounted(async () => {
budgetItems.value = store.budgetListData ?? (await store.getBudgetConst())
})
</script>
<style lang="less"></style>
+20
View File
@@ -0,0 +1,20 @@
<template>
<div
:style="{ color: props.textColor, backgroundColor: props.bgColor }"
class="w-fit p-1 rounded-lg"
>
<slot />
</div>
</template>
<script setup lang="ts">
//props
interface Prop {
textColor: string
bgColor: string
}
const props = defineProps<Prop>()
</script>
<style scoped></style>
@@ -0,0 +1,137 @@
<template>
<div :class="[prefixCls]">
<div
class="w-[96px] h-[96px] overflow-hidden border border-solid border-blue-600 bg-gray-50 rounded-md p-2"
>
<AUpload
v-model:file-list="fileList"
name="file"
list-type="picture-card"
:showUploadList="true"
:multiple="false"
:action="uploadURL"
:headers="fileHeader"
:before-upload="beforeUpload"
@change="handleChange"
@preview="handlePreview"
>
<div v-if="fileList.length === 0">
<loading-outlined v-if="loading"></loading-outlined>
<BaseIcon v-else icon="vuesax-linear:add" color="#6B7280" />
<div class="text-gray-500 text-center text-xs font-normal">تصویر شاخص</div>
</div>
</AUpload>
<AModal :visible="previewVisible" :title="previewTitle" :footer="null" @cancel="handleCancel">
<img alt="example" style="width: 100%" :src="previewImage" />
</AModal>
</div>
</div>
</template>
<script setup lang="ts">
import type { UploadChangeParam, UploadProps } from 'ant-design-vue'
import { message } from 'ant-design-vue'
import { LoadingOutlined } from '@ant-design/icons-vue'
import { usePrefix } from '@core/composable/usePrefix'
import { uploadURL } from '@root/config'
import { getToken } from '@/utils'
import { ref } from 'vue'
const { prefixCls } = usePrefix('index-image-uploader')
const previewVisible = ref(false)
const previewImage = ref('')
const previewTitle = ref('')
const loading = ref<boolean>(false)
const fileHeader = {
Authorization: `Bearer ${getToken()}`
}
interface Props {
fileList?: Array<FileList>
}
const props = withDefaults(defineProps<Props>(), {})
const emits = defineEmits(['update:fileList', 'ok', 'delete'])
const fileList = computed({
get() {
return props.fileList || ([] as Array<FileList>)
},
set(value) {
emits('update:fileList', value)
}
})
const handlePreview = async (file: UploadProps['fileList'][number]) => {
previewImage.value = file.src
previewVisible.value = true
previewTitle.value = file.name || file.url.substring(file.url.lastIndexOf('/') + 1)
}
const handleCancel = () => {
previewVisible.value = false
previewTitle.value = ''
}
const beforeUpload = (file: UploadProps['fileList'][number]) => {
const isLt100k = file.size / 1024 / 1024 < 0.2
if (!isLt100k) {
message.error('حداکثر حجم بارگذاری 200 کیلو بایت میباشد')
}
return isLt100k
}
function handleChange(info: UploadChangeParam) {
if (info.file.status === 'uploading') {
loading.value = true
return
}
if (info.file.status === 'error') {
loading.value = false
fileList.value = []
message.error('خطا در بارگذاری')
}
if (info.file.status === 'done') {
loading.value = false
message.success('تصویر شاخص با موفقیت بارگذاری شد')
emits('ok', info.file.response.id)
}
if (info.file.status === 'removed') {
emits('delete', info.file.uid)
}
}
</script>
<style lang="less">
@prefix: ~'@{prefixCls}-index-image-uploader';
.@{prefix} {
.ant-upload-rtl.ant-upload.ant-upload-select-picture-card {
margin: 0 !important;
border: none;
}
.ant-upload {
width: 78px !important;
height: 78px !important;
background-color: #eff6ff;
border: dashed 1px #93c5fd;
border-radius: 4px;
}
.ant-upload-list-picture-card-container {
margin: 0;
width: 78px !important;
height: 78px !important;
}
.ant-upload-list-picture-card .ant-upload-list-item {
padding: 0;
border: none;
}
.ant-upload-list-picture-card .ant-upload-list-item-info {
border-radius: 4px;
}
}
</style>
@@ -0,0 +1,125 @@
<template>
<div :class="[prefixCls]">
<div class="border border-solid border-blue-600 bg-gray-50 w-full rounded-md p-2">
<div class="flex">
<AUpload
v-model:file-list="fileList"
name="file"
list-type="picture-card"
:show-upload-list="false"
:action="uploadURL"
:headers="fileHeader"
:showUploadList="true"
:before-upload="beforeUpload"
@change="handleChange"
>
<div>
<BaseIcon icon="vuesax-linear:add" color="#6B7280" />
<div class="text-gray-500 text-center text-xs font-normal">بارگذاری فایل</div>
</div>
</AUpload>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import type { UploadChangeParam, UploadProps } from 'ant-design-vue'
import { uploadURL } from '@root/config'
import { getToken } from '@/utils'
import { usePrefix } from '@core/composable/usePrefix'
import { message } from 'ant-design-vue'
const { prefixCls } = usePrefix('product-image-uploader')
const loading = ref<boolean>(false)
const fileHeader = {
Authorization: `Bearer ${getToken()}`
}
interface Props {
fileList?: Array<FileList>
}
const props = withDefaults(defineProps<Props>(), {})
const emits = defineEmits(['update:fileList', 'ok', 'delete'])
const fileList = computed({
get() {
return props.fileList || ([] as Array<FileList>)
},
set(value) {
emits('update:fileList', value)
}
})
const beforeUpload = (file: UploadProps['fileList'][number]) => {
const isLt100k = file.size / 1024 / 1024 < 0.24
if (!isLt100k) {
message.error('حداکثر حجم بارگذاری 240 کیلو بایت میباشد')
}
return isLt100k
}
function handleChange(info: UploadChangeParam) {
if (info.file.status === 'uploading') {
loading.value = true
return
}
if (info.file.status === 'error') {
loading.value = false
message.error('خطا در بارگذاری')
}
if (info.file.status === 'done') {
loading.value = false
message.success('تصویر با موفقیت بارگذاری شد')
emits('ok', info.file.response.id)
}
if (info.file.status === 'removed') {
emits('delete', info.file.uid)
}
}
</script>
<style lang="less">
@prefix: ~'@{prefixCls}-product-image-uploader';
.@{prefix} {
width: 100%;
.ant-upload-rtl.ant-upload.ant-upload-select-picture-card {
margin: 0 !important;
border: none;
}
.ant-upload-picture-card-wrapper {
width: fit-content;
}
.ant-upload {
width: 78px !important;
height: 78px !important;
background-color: #eff6ff;
border: dashed 1px #93c5fd;
border-radius: 4px;
}
.ant-upload-list-picture-card-container {
margin: 0;
width: 78px !important;
height: 78px !important;
}
.ant-upload-list-picture-card .ant-upload-list-item {
padding: 0;
border: none;
}
.ant-upload-list-picture-card .ant-upload-list-item-info {
border-radius: 4px;
}
.ant-upload-list {
display: flex !important;
flex-wrap: wrap;
gap: 8px;
}
}
</style>
@@ -0,0 +1,72 @@
<template>
<div :class="[prefixCls]">
<div class="w-[78px] h-[78px] flex rounded parent">
<img class="w-[78px] h-[78px] rounded" :src="props.src" alt="test" />
<div class="child">
<div class="flex justify-center gap-2 !z-20">
<Icon
class="cursor-pointer"
icon="vuesax-linear:trash"
size="18"
color="white"
@click="emit('remove')"
/>
<Icon
class="cursor-pointer"
icon="vuesax-linear:eye"
size="18"
color="white"
@click="() => setVisible(true)"
/>
</div>
</div>
</div>
<AImage
:src="props.src"
style="display: none"
:preview="{ visible, onVisibleChange: setVisible }"
/>
</div>
</template>
<script setup lang="ts">
import { usePrefix } from '@core/composable/usePrefix'
import Icon from '@core/components/BaseIcon/src/Icon.vue'
interface Props {
src: string
}
const props = withDefaults(defineProps<Props>(), {})
const emit = defineEmits<{
(e: 'remove'): void
}>()
const { prefixCls } = usePrefix('upload-image')
const visible = ref<boolean>(false)
const setVisible = (value: boolean) => {
visible.value = value
}
</script>
<style lang="less">
@prefix: ~'@{prefixCls}-upload-image';
.@{prefix} {
.parent:hover {
cursor: pointer;
.child {
display: flex;
}
}
.child {
background: rgba(0, 0, 0, 0.3);
position: absolute;
width: 78px;
height: 78px;
display: none;
justify-content: center;
align-items: center;
border-radius: 6px;
transition: ease-in-out 1ms;
}
}
</style>
@@ -0,0 +1,46 @@
<template>
<!-- <BasicSelect :items="items" v-model:value="localValue" valueName="value" keyName="id" />-->
<BasicSelect
v-model:value="localValue"
:items="items"
valueName="title"
keyName="title"
class="w-[10rem]"
/>
</template>
<script setup lang="ts">
import type { Const } from '@/model/const'
import { useStaticApiListsStore } from '@/store/staticApisStore'
import type { Province } from '@/model/city'
const store = useStaticApiListsStore()
const items = ref<Array<Province>>()
const emits = defineEmits(['update:value', 'loading'])
interface Prop {
value?: string | number
}
const props = defineProps<Prop>()
onMounted(async () => {
emits('loading', true)
items.value = store.cityListData ?? (await store.getCityList())
emits('loading', false)
})
const localValue = computed({
get() {
return props.value
},
set(value) {
emits('update:value', value)
}
})
</script>
<style scoped></style>
@@ -0,0 +1,46 @@
<template>
<!-- <BasicSelect :items="items" v-model:value="localValue" valueName="value" keyName="id" />-->
<BasicSelect
v-model:value="localValue"
:items="items"
valueName="title"
keyName="title"
class="w-[10rem]"
/>
</template>
<script setup lang="ts">
import type { Const } from '@/model/const'
import { useStaticApiListsStore } from '@/store/staticApisStore'
import type { Province } from '@/model/city'
const store = useStaticApiListsStore()
const items = ref<Array<Province>>()
const emits = defineEmits(['update:value', 'loading'])
interface Prop {
value?: string | number
}
const props = defineProps<Prop>()
onMounted(async () => {
emits('loading', true)
items.value = store.provincesListData ?? (await store.getProvincesList())
emits('loading', false)
})
const localValue = computed({
get() {
return props.value
},
set(value) {
emits('update:value', value)
}
})
</script>
<style scoped></style>