...
This commit is contained in:
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="icon" href="/favicon.ico">
|
<link rel="icon" href="/favicon.ico">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>نیلگون</title>
|
<title>مدیریت اموال نیلگون</title>
|
||||||
<link href="https://fonts.cdnfonts.com/css/lato" rel="stylesheet">
|
<link href="https://fonts.cdnfonts.com/css/lato" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|||||||
Generated
+968
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,9 @@
|
|||||||
"@fortawesome/free-regular-svg-icons": "^6.7.2",
|
"@fortawesome/free-regular-svg-icons": "^6.7.2",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
||||||
"@fortawesome/vue-fontawesome": "^3.0.3",
|
"@fortawesome/vue-fontawesome": "^3.0.3",
|
||||||
|
"@neshan-maps-platform/mapbox-gl": "^1.1.3",
|
||||||
|
"@neshan-maps-platform/ol": "^1.0.5",
|
||||||
|
"@neshan-maps-platform/vue3-openlayers": "^2.0.1",
|
||||||
"@primevue/core": "^4.2.5",
|
"@primevue/core": "^4.2.5",
|
||||||
"@primevue/themes": "^4.2.4",
|
"@primevue/themes": "^4.2.4",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
@@ -22,9 +25,11 @@
|
|||||||
"filepond-plugin-file-validate-type": "^1.2.9",
|
"filepond-plugin-file-validate-type": "^1.2.9",
|
||||||
"filepond-plugin-image-preview": "^4.6.12",
|
"filepond-plugin-image-preview": "^4.6.12",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
|
"mapbox-gl": "^1.13.3",
|
||||||
"photoswipe": "^5.4.4",
|
"photoswipe": "^5.4.4",
|
||||||
"primeicons": "^7.0.0",
|
"primeicons": "^7.0.0",
|
||||||
"primevue": "^4.2.4",
|
"primevue": "^4.2.4",
|
||||||
|
"v-mapbox": "^5.1.0",
|
||||||
"vue": "^3.4.34",
|
"vue": "^3.4.34",
|
||||||
"vue-cookies": "^1.8.3",
|
"vue-cookies": "^1.8.3",
|
||||||
"vue-filepond": "^7.0.4",
|
"vue-filepond": "^7.0.4",
|
||||||
@@ -34,10 +39,12 @@
|
|||||||
"@primevue/auto-import-resolver": "^4.2.4",
|
"@primevue/auto-import-resolver": "^4.2.4",
|
||||||
"@rushstack/eslint-patch": "^1.8.0",
|
"@rushstack/eslint-patch": "^1.8.0",
|
||||||
"@vitejs/plugin-vue": "^5.0.5",
|
"@vitejs/plugin-vue": "^5.0.5",
|
||||||
|
"@vue-leaflet/vue-leaflet": "^0.10.1",
|
||||||
"@vue/eslint-config-prettier": "^9.0.0",
|
"@vue/eslint-config-prettier": "^9.0.0",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-plugin-vue": "^9.23.0",
|
"eslint-plugin-vue": "^9.23.0",
|
||||||
|
"leaflet": "^1.9.4",
|
||||||
"postcss": "^8.4.40",
|
"postcss": "^8.4.40",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"sass": "^1.55.0",
|
"sass": "^1.55.0",
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
@@ -1,6 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ProductService } from '@/service/ProductService';
|
import { ProductService } from '@/service/ProductService';
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
import { BelongingService } from '@/service/BelongingService';
|
||||||
|
|
||||||
const products = ref(null);
|
const products = ref(null);
|
||||||
|
|
||||||
@@ -9,26 +10,26 @@ function formatCurrency(value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
ProductService.getProductsSmall().then((data) => (products.value = data));
|
BelongingService.getAllBelongings().then((data) => (products.value = data));
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="font-semibold text-xl mb-4">Recent Sales</div>
|
<div class="font-semibold text-xl mb-4">آخرین دارایی های افزوده شده</div>
|
||||||
<DataTable :value="products" :rows="5" :paginator="true" responsiveLayout="scroll">
|
<DataTable :value="products" :rows="5" :paginator="true" responsiveLayout="scroll">
|
||||||
<Column style="width: 15%" header="Image">
|
<Column style="width: 15%" header="Image">
|
||||||
<template #body="slotProps">
|
<template #body="slotProps">
|
||||||
<img :src="`https://primefaces.org/cdn/primevue/images/product/${slotProps.data.image}`" :alt="slotProps.data.image" width="50" class="shadow" />
|
<img :src="`${slotProps.data.mainImageUrl}`" :alt="slotProps.data.name" width="50" class="shadow" />
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
<Column field="name" header="Name" :sortable="true" style="width: 35%"></Column>
|
<Column field="name" header="نام" :sortable="true" style="width: 35%"></Column>
|
||||||
<Column field="price" header="Price" :sortable="true" style="width: 35%">
|
<Column field="price" header="دسته بندی" :sortable="true" style="width: 35%">
|
||||||
<template #body="slotProps">
|
<template #body="slotProps">
|
||||||
{{ formatCurrency(slotProps.data.price) }}
|
{{ slotProps.data.category.nativeName }}
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
<Column style="width: 15%" header="View">
|
<Column style="width: 15%" header="نمایش">
|
||||||
<template #body>
|
<template #body>
|
||||||
<Button icon="pi pi-search" type="button" class="p-button-text"></Button>
|
<Button icon="pi pi-search" type="button" class="p-button-text"></Button>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -11,32 +11,17 @@ function setChartData() {
|
|||||||
const documentStyle = getComputedStyle(document.documentElement);
|
const documentStyle = getComputedStyle(document.documentElement);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
|
labels: ['زمین', 'ساختمان', 'خودرو', 'ارز'],
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
label: 'Subscriptions',
|
label: 'تعداد دارایی ها',
|
||||||
backgroundColor: documentStyle.getPropertyValue('--p-primary-400'),
|
backgroundColor: documentStyle.getPropertyValue('--p-primary-400'),
|
||||||
data: [4000, 10000, 15000, 4000],
|
font: {
|
||||||
barThickness: 32
|
family: 'Estedad', // Set custom font for y-axis labels
|
||||||
},
|
size: 14 // Set font size
|
||||||
{
|
|
||||||
type: 'bar',
|
|
||||||
label: 'Advertising',
|
|
||||||
backgroundColor: documentStyle.getPropertyValue('--p-primary-300'),
|
|
||||||
data: [2100, 8400, 2400, 7500],
|
|
||||||
barThickness: 32
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'bar',
|
|
||||||
label: 'Affiliate',
|
|
||||||
backgroundColor: documentStyle.getPropertyValue('--p-primary-200'),
|
|
||||||
data: [4100, 5200, 3400, 7400],
|
|
||||||
borderRadius: {
|
|
||||||
topLeft: 8,
|
|
||||||
topRight: 8
|
|
||||||
},
|
},
|
||||||
borderSkipped: true,
|
data: [18, 13, 5, 11],
|
||||||
barThickness: 32
|
barThickness: 32
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -55,7 +40,11 @@ function setChartOptions() {
|
|||||||
x: {
|
x: {
|
||||||
stacked: true,
|
stacked: true,
|
||||||
ticks: {
|
ticks: {
|
||||||
color: textMutedColor
|
color: textMutedColor,
|
||||||
|
font: {
|
||||||
|
family: 'Estedad', // Set custom font for y-axis labels
|
||||||
|
size: 14 // Set font size
|
||||||
|
}
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
color: 'transparent',
|
color: 'transparent',
|
||||||
@@ -65,7 +54,11 @@ function setChartOptions() {
|
|||||||
y: {
|
y: {
|
||||||
stacked: true,
|
stacked: true,
|
||||||
ticks: {
|
ticks: {
|
||||||
color: textMutedColor
|
color: textMutedColor,
|
||||||
|
font: {
|
||||||
|
family: 'Estedad', // Set custom font for y-axis labels
|
||||||
|
size: 14 // Set font size
|
||||||
|
}
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
color: borderColor,
|
color: borderColor,
|
||||||
@@ -90,7 +83,13 @@ onMounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="font-semibold text-xl mb-4">Revenue Stream</div>
|
<div class="font-semibold text-xl mb-4">تقسیم بندی دارایی ها</div>
|
||||||
<Chart type="bar" :data="chartData" :options="chartOptions" class="h-80" />
|
<Chart type="bar" :data="chartData" :options="chartOptions" class="h-80" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.card {
|
||||||
|
font-family: 'Estedad', sans-serif !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -3,60 +3,39 @@
|
|||||||
<div class="card mb-0">
|
<div class="card mb-0">
|
||||||
<div class="flex justify-between mb-4">
|
<div class="flex justify-between mb-4">
|
||||||
<div>
|
<div>
|
||||||
<span class="block text-muted-color font-medium mb-4">Orders</span>
|
<span class="block text-muted-color font-medium mb-4">تعداد دارایی ها</span>
|
||||||
<div class="text-surface-900 dark:text-surface-0 font-medium text-xl">152</div>
|
<div class="text-surface-900 dark:text-surface-0 font-medium text-xl">100</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-center bg-blue-100 dark:bg-blue-400/10 rounded-border" style="width: 2.5rem; height: 2.5rem">
|
<div class="flex items-center justify-center bg-blue-100 dark:bg-blue-400/10 rounded-border" style="width: 2.5rem; height: 2.5rem">
|
||||||
<i class="pi pi-shopping-cart text-blue-500 !text-xl"></i>
|
<i class="pi pi-shopping-cart text-blue-500 !text-xl"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-primary font-medium">24 new </span>
|
|
||||||
<span class="text-muted-color">since last visit</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-span-12 lg:col-span-6 xl:col-span-3">
|
<div class="col-span-12 lg:col-span-6 xl:col-span-3">
|
||||||
<div class="card mb-0">
|
<div class="card mb-0">
|
||||||
<div class="flex justify-between mb-4">
|
<div class="flex justify-between mb-4">
|
||||||
<div>
|
<div>
|
||||||
<span class="block text-muted-color font-medium mb-4">Revenue</span>
|
<span class="block text-muted-color font-medium mb-4">ارز ها</span>
|
||||||
<div class="text-surface-900 dark:text-surface-0 font-medium text-xl">$2.100</div>
|
<div class="text-surface-900 dark:text-surface-0 font-medium text-xl">2800 دلار</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-center bg-orange-100 dark:bg-orange-400/10 rounded-border" style="width: 2.5rem; height: 2.5rem">
|
<div class="flex items-center justify-center bg-orange-100 dark:bg-orange-400/10 rounded-border" style="width: 2.5rem; height: 2.5rem">
|
||||||
<i class="pi pi-dollar text-orange-500 !text-xl"></i>
|
<i class="pi pi-dollar text-orange-500 !text-xl"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-primary font-medium">%52+ </span>
|
|
||||||
<span class="text-muted-color">since last week</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-span-12 lg:col-span-6 xl:col-span-3">
|
<div class="col-span-12 lg:col-span-6 xl:col-span-3">
|
||||||
<div class="card mb-0">
|
<div class="card mb-0">
|
||||||
<div class="flex justify-between mb-4">
|
<div class="flex justify-between mb-4">
|
||||||
<div>
|
<div>
|
||||||
<span class="block text-muted-color font-medium mb-4">Customers</span>
|
<span class="block text-muted-color font-medium mb-4">تعداد کاربران</span>
|
||||||
<div class="text-surface-900 dark:text-surface-0 font-medium text-xl">28441</div>
|
<div class="text-surface-900 dark:text-surface-0 font-medium text-xl">3 عدد</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-center bg-cyan-100 dark:bg-cyan-400/10 rounded-border" style="width: 2.5rem; height: 2.5rem">
|
<div class="flex items-center justify-center bg-cyan-100 dark:bg-cyan-400/10 rounded-border" style="width: 2.5rem; height: 2.5rem">
|
||||||
<i class="pi pi-users text-cyan-500 !text-xl"></i>
|
<i class="pi pi-users text-cyan-500 !text-xl"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-primary font-medium">520 </span>
|
|
||||||
<span class="text-muted-color">newly registered</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-span-12 lg:col-span-6 xl:col-span-3">
|
|
||||||
<div class="card mb-0">
|
|
||||||
<div class="flex justify-between mb-4">
|
|
||||||
<div>
|
|
||||||
<span class="block text-muted-color font-medium mb-4">Comments</span>
|
|
||||||
<div class="text-surface-900 dark:text-surface-0 font-medium text-xl">152 Unread</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-center bg-purple-100 dark:bg-purple-400/10 rounded-border" style="width: 2.5rem; height: 2.5rem">
|
|
||||||
<i class="pi pi-comment text-purple-500 !text-xl"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<span class="text-primary font-medium">85 </span>
|
|
||||||
<span class="text-muted-color">responded</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -43,6 +43,11 @@ const router = createRouter({
|
|||||||
name: 'create-belonging',
|
name: 'create-belonging',
|
||||||
component: () => import('@/views/pages/Belonging/CreateBelonging.vue')
|
component: () => import('@/views/pages/Belonging/CreateBelonging.vue')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/belonging/edit-belonging/:belongingId',
|
||||||
|
name: 'edit-belonging',
|
||||||
|
component: () => import('@/views/pages/Belonging/CreateBelonging.vue')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/belonging/list-belongings',
|
path: '/belonging/list-belongings',
|
||||||
name: 'list-belongings',
|
name: 'list-belongings',
|
||||||
|
|||||||
@@ -1,17 +1,60 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
export const BelongingService = {
|
export const BelongingService = {
|
||||||
getAllBelongings: async ()=> {
|
getBelonging: async (belongingId) => {
|
||||||
|
const config = {
|
||||||
|
method: 'get',
|
||||||
|
url: '/api/belongings/admin/get-one/' + belongingId,
|
||||||
|
withCredentials: true
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const response = await axios(config);
|
||||||
|
return response.data.data;
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getAllBelongings: async () => {
|
||||||
const config = {
|
const config = {
|
||||||
method: 'get',
|
method: 'get',
|
||||||
url: '/api/belongings/admin/get-all',
|
url: '/api/belongings/admin/get-all',
|
||||||
withCredentials: true,
|
withCredentials: true
|
||||||
}
|
};
|
||||||
try {
|
try {
|
||||||
const response = await axios(config)
|
const response = await axios(config);
|
||||||
return response.data.data
|
return response.data.data;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err)
|
console.log(err);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createBelonging: async (data) => {
|
||||||
|
const config = {
|
||||||
|
method: 'post',
|
||||||
|
url: '/api/belongings/admin/create',
|
||||||
|
withCredentials: true,
|
||||||
|
data
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const response = await axios(config);
|
||||||
|
return 'success';
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
return err.response.data.message;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateBelonging: async (belongingId, data) => {
|
||||||
|
const config = {
|
||||||
|
method: 'put',
|
||||||
|
url: '/api/belongings/admin/update/' + belongingId,
|
||||||
|
withCredentials: true,
|
||||||
|
data
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const response = await axios(config);
|
||||||
|
return 'success';
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
return err.response.data.message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ import StatsWidget from '@/components/dashboard/StatsWidget.vue';
|
|||||||
|
|
||||||
<div class="col-span-12 xl:col-span-6">
|
<div class="col-span-12 xl:col-span-6">
|
||||||
<RecentSalesWidget />
|
<RecentSalesWidget />
|
||||||
<BestSellingWidget />
|
<!-- <BestSellingWidget />-->
|
||||||
</div>
|
</div>
|
||||||
<div class="col-span-12 xl:col-span-6">
|
<div class="col-span-12 xl:col-span-6">
|
||||||
<RevenueStreamWidget />
|
<RevenueStreamWidget />
|
||||||
<NotificationsWidget />
|
<!-- <NotificationsWidget />-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,21 +1,98 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { Listbox, InputText } from 'primevue';
|
import { Listbox, InputText } from 'primevue';
|
||||||
import { onBeforeMount, ref } from 'vue';
|
import { onBeforeMount, ref, watch } from 'vue';
|
||||||
import { CategoryService } from '@/service/CategoryService';
|
import { CategoryService } from '@/service/CategoryService';
|
||||||
import ImageUploader from '@/views/uikit/ImageUploader.vue';
|
import ImageUploader from '@/views/uikit/ImageUploader.vue';
|
||||||
import ImageGallery from '@/views/uikit/ImageGallery.vue';
|
import ImageGallery from '@/views/uikit/ImageGallery.vue';
|
||||||
|
import KiwiChips from '@/views/uikit/KiwiChips.vue';
|
||||||
|
import { useToast } from 'primevue/usetoast';
|
||||||
|
import { BelongingService } from '@/service/BelongingService';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
import MapComponent from '@/views/uikit/MapComponent.vue';
|
||||||
|
|
||||||
|
const toast = useToast();
|
||||||
const categories = ref([]);
|
const categories = ref([]);
|
||||||
const selectedCategory = ref();
|
const selectedCategory = ref();
|
||||||
const belongingName = ref('');
|
const belongingName = ref('');
|
||||||
const description = ref('');
|
const description = ref('');
|
||||||
const imageNames = ref([]);
|
const imageNames = ref([]);
|
||||||
|
const features = ref([]);
|
||||||
|
const featuresValues = ref({});
|
||||||
const documentNames = ref([]);
|
const documentNames = ref([]);
|
||||||
|
const documentData = ref([]);
|
||||||
const mainImageName = ref([]);
|
const mainImageName = ref([]);
|
||||||
const tags = ref('');
|
const belonging = ref({
|
||||||
const signedImageUrls = ref([{ url: 'https://upload.hitromall.com/product-images/0ff87b0cc333539a361e007d20aa9685-co3.jpg' }]);
|
location: {
|
||||||
|
type: 'Point',
|
||||||
|
coordinates: [0, 0]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const tags = ref([]);
|
||||||
|
const signedImageUrls = ref([]);
|
||||||
|
const signedDocumentsUrls = ref([]);
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
const mapKey = 'web.79abbab4970e401781f434da3e41cfa6';
|
||||||
|
const serviceKey = 'service.11d860f52c434bd29e5f84d0a0a41108';
|
||||||
|
const mapRef = ref();
|
||||||
|
|
||||||
onBeforeMount(async () => {
|
onBeforeMount(async () => {
|
||||||
categories.value = await CategoryService.getAllCategories();
|
categories.value = await CategoryService.getAllCategories();
|
||||||
|
console.log(route.name);
|
||||||
|
if (route.name === 'edit-belonging') {
|
||||||
|
belonging.value = await BelongingService.getBelonging(route.params.belongingId);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
const handleSelectCategory = ($event) => {
|
||||||
|
if ($event.value) {
|
||||||
|
belonging.value.category = $event.value;
|
||||||
|
selectedCategory.value = categories.value.find((x) => x._id === $event.value);
|
||||||
|
features.value = selectedCategory.value.features;
|
||||||
|
} else {
|
||||||
|
belonging.value = {
|
||||||
|
location: {
|
||||||
|
type: 'Point',
|
||||||
|
coordinates: [0, 0]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
selectedCategory.value = {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveBelonging = async () => {
|
||||||
|
let status = 'failed';
|
||||||
|
console.log(belonging);
|
||||||
|
if (belonging?.value._id?.trim()) {
|
||||||
|
status = await BelongingService.updateBelonging(belonging.value._id, belonging.value);
|
||||||
|
} else {
|
||||||
|
status = await BelongingService.createBelonging(belonging.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === 'success') {
|
||||||
|
toast.add({ severity: 'success', summary: 'موفق', detail: 'ویژگی جدید ساخته شد.', life: 3000 });
|
||||||
|
} else {
|
||||||
|
toast.add({ severity: 'error', summary: 'ناموفق', detail: status, life: 3000 });
|
||||||
|
}
|
||||||
|
|
||||||
|
await router.push('/belonging/list-belongings');
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => mapRef.value?.state.map,
|
||||||
|
(nv, ol) => {
|
||||||
|
if (ol) return;
|
||||||
|
setTimeout(() => {
|
||||||
|
mapRef.value?.state.map?.setMapType('standard-night');
|
||||||
|
if (mapRef.value?.state.mapType) mapRef.value.state.mapType = 'standard-night';
|
||||||
|
}, 2000);
|
||||||
|
setTimeout(() => {
|
||||||
|
mapRef.value?.search({
|
||||||
|
term: 'ولی عصر',
|
||||||
|
coords: [51.36281969540723, 35.69672648316882]
|
||||||
|
});
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div className="card">
|
<div className="card">
|
||||||
@@ -26,38 +103,63 @@ onBeforeMount(async () => {
|
|||||||
<div class="w-3/4 ml-3">
|
<div class="w-3/4 ml-3">
|
||||||
<div class="">
|
<div class="">
|
||||||
<label> نام دارایی </label>
|
<label> نام دارایی </label>
|
||||||
<InputText v-model="belongingName" class="w-full my-2" placeholder="مثلا: 100 عدد کولر گازی" />
|
<InputText v-model="belonging.name" class="w-full my-2" placeholder="مثلا: 100 عدد کولر گازی" />
|
||||||
</div>
|
</div>
|
||||||
<div class="my-6">
|
<div class="my-6">
|
||||||
<label> توضیحات </label>
|
<label> توضیحات </label>
|
||||||
<Textarea v-model="description" class="w-full my-2" placeholder="مثلا: در تاریخ 1403/09/11 خریداری شد." rows="6" />
|
<Textarea v-model="belonging.description" class="w-full my-2" placeholder="مثلا: در تاریخ 1403/09/11 خریداری شد." rows="6" />
|
||||||
</div>
|
</div>
|
||||||
<div class="my-6" v-if="signedImageUrls.length">
|
<div class="my-6">
|
||||||
<label> تصاویر </label>
|
<label> تصاویر، فایل ها و مدارک </label>
|
||||||
<ImageGallery v-model="signedImageUrls" gallery-id="images" />
|
<ImageUploader v-model="belonging.documentNames" :max-files="50" mode="private" delete-endpoint="/api/files/bucket/delete/belonging-documents" upload-endpoint="/api/files/bucket/upload-multi-filepond/belonging-documents" />
|
||||||
|
<div v-if="signedImageUrls.length > 0">
|
||||||
|
<ImageGallery v-model="documentData" gallery-id="images" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="my-6" v-if="signedImageUrls.length">
|
<div class="my-6" v-if="signedDocumentsUrls.length > 0">
|
||||||
<label> فایل ها و مدارک </label>
|
<label> فایل ها و مدارک </label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="my-6 max-h-96 overflow-y-auto" v-if="features.length > 0">
|
||||||
|
<label> اطلاعات دارایی </label>
|
||||||
|
<div class="my-6 mr-10" v-for="feature of features">
|
||||||
|
<label>{{ feature.nativeName }}</label>
|
||||||
|
<InputText v-model="featuresValues[feature._id]" class="w-1/3 my-2 block" placeholder="" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="my-10">
|
||||||
|
<label> موقعیت دارایی </label>
|
||||||
|
<MapComponent v-model="belonging.location.coordinates" />
|
||||||
|
<!-- <MapIrComponent />-->
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-1/4">
|
<div class="w-1/4">
|
||||||
<div>
|
<div>
|
||||||
<label> دسته بندی </label>
|
<label> دسته بندی </label>
|
||||||
<Listbox v-model="selectedCategory" :options="categories" optionLabel="nativeName" optionValue="_id" checkmark :highlightOnSelect="false" class="w-full my-2" />
|
<Listbox @change="handleSelectCategory($event)" :options="categories" optionLabel="nativeName" optionValue="_id" checkmark :highlightOnSelect="false" class="w-full my-2" />
|
||||||
</div>
|
</div>
|
||||||
<div class="my-6">
|
<div class="my-6">
|
||||||
<label> تصویر اصلی </label>
|
<label> تصویر اصلی </label>
|
||||||
<ImageUploader v-model="mainImageName" :max-files="1" mode="private" delete-endpoint="/api/files/bucket/delete/belonging-images" upload-endpoint="/api/files/bucket/upload-multi-filepond/belonging-images" />
|
<ImageUploader
|
||||||
|
v-model="belonging.mainImageName"
|
||||||
|
accepted-files="image/*"
|
||||||
|
:max-files="1"
|
||||||
|
mode="private"
|
||||||
|
delete-endpoint="/api/files/bucket/delete/belonging-images"
|
||||||
|
upload-endpoint="/api/files/bucket/upload-multi-filepond/belonging-images"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="my-8">
|
<div class="my-8">
|
||||||
<label> برچسب ها </label>
|
<label> برچسب ها </label>
|
||||||
<Chips v-model="tags" separator="," class="w-full" placeholder="مثلا: زمین، ملک، کیانپارس..." />
|
<KiwiChips v-model="belonging.tags" class="w-full" placeholder="مثلا: زمین، ملک، کیانپارس..." />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<Button>ذخیره</Button>
|
<Button @click="saveBelonging">ذخیره</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<style>
|
||||||
|
@import url('leaflet/dist/leaflet.css');
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { FilterMatchMode } from '@primevue/core/api';
|
|||||||
import { useToast } from 'primevue/usetoast';
|
import { useToast } from 'primevue/usetoast';
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
import { BelongingService } from '@/service/BelongingService';
|
import { BelongingService } from '@/service/BelongingService';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
BelongingService.getAllBelongings().then((data) => (belongings.value = data));
|
BelongingService.getAllBelongings().then((data) => (belongings.value = data));
|
||||||
@@ -15,22 +16,12 @@ const belongings = ref();
|
|||||||
const productDialog = ref(false);
|
const productDialog = ref(false);
|
||||||
const deleteProductDialog = ref(false);
|
const deleteProductDialog = ref(false);
|
||||||
const deleteProductsDialog = ref(false);
|
const deleteProductsDialog = ref(false);
|
||||||
const product = ref({});
|
const belonging = ref({});
|
||||||
const selectedBelongings = ref();
|
const selectedBelongings = ref();
|
||||||
const filters = ref({
|
const filters = ref({
|
||||||
global: { value: null, matchMode: FilterMatchMode.CONTAINS }
|
global: { value: null, matchMode: FilterMatchMode.CONTAINS }
|
||||||
});
|
});
|
||||||
const submitted = ref(false);
|
const submitted = ref(false);
|
||||||
const statuses = ref([
|
|
||||||
{ label: 'INSTOCK', value: 'instock' },
|
|
||||||
{ label: 'LOWSTOCK', value: 'lowstock' },
|
|
||||||
{ label: 'OUTOFSTOCK', value: 'outofstock' }
|
|
||||||
]);
|
|
||||||
|
|
||||||
function formatCurrency(value) {
|
|
||||||
if (value) return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function openNew() {
|
function openNew() {
|
||||||
product.value = {};
|
product.value = {};
|
||||||
@@ -65,9 +56,10 @@ function saveProduct() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function editProduct(prod) {
|
const router = useRouter();
|
||||||
product.value = { ...prod };
|
|
||||||
productDialog.value = true;
|
function editBelonging(belongingId) {
|
||||||
|
router.push('/belonging/edit-belonging/' + belongingId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function confirmDeleteProduct(prod) {
|
function confirmDeleteProduct(prod) {
|
||||||
@@ -112,7 +104,7 @@ function confirmDeleteSelected() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function deleteSelectedBelongings() {
|
function deleteSelectedBelongings() {
|
||||||
products.value = products.value.filter((val) => !selectedBelongings.value.includes(val));
|
belonging.value = belonging.value.filter((val) => !selectedBelongings.value.includes(val));
|
||||||
deleteProductsDialog.value = false;
|
deleteProductsDialog.value = false;
|
||||||
selectedBelongings.value = null;
|
selectedBelongings.value = null;
|
||||||
toast.add({ severity: 'success', summary: 'Successful', detail: 'Products Deleted', life: 3000 });
|
toast.add({ severity: 'success', summary: 'Successful', detail: 'Products Deleted', life: 3000 });
|
||||||
@@ -144,8 +136,7 @@ function getStatusLabel(status) {
|
|||||||
<Button label="حذف دارایی" icon="pi pi-trash" severity="secondary" @click="confirmDeleteSelected" :disabled="!selectedBelongings || !selectedBelongings.length" />
|
<Button label="حذف دارایی" icon="pi pi-trash" severity="secondary" @click="confirmDeleteSelected" :disabled="!selectedBelongings || !selectedBelongings.length" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #end>
|
<template #end> </template>
|
||||||
</template>
|
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
|
|
||||||
<DataTable
|
<DataTable
|
||||||
@@ -175,20 +166,20 @@ function getStatusLabel(status) {
|
|||||||
<Column selectionMode="multiple" style="width: 3rem" :exportable="false"></Column>
|
<Column selectionMode="multiple" style="width: 3rem" :exportable="false"></Column>
|
||||||
<Column header="تصویر">
|
<Column header="تصویر">
|
||||||
<template #body="slotProps">
|
<template #body="slotProps">
|
||||||
<img :src="`https://primefaces.org/cdn/primevue/images/product/${slotProps.data.image}`" :alt="slotProps.data.image" class="rounded" style="width: 64px" />
|
<img :src="`${slotProps.data.mainImageUrl}`" :alt="slotProps.data.name" class="rounded" style="width: 64px" />
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
<Column field="belongingCode" header="کد" style="min-width: 12rem"></Column>
|
<Column field="belongingCode" header="کد" style="min-width: 12rem"></Column>
|
||||||
<Column field="name" header="نام" sortable style="min-width: 16rem"></Column>
|
<Column field="name" header="نام" sortable style="min-width: 16rem"></Column>
|
||||||
<Column field="category" header="دسته بندی" sortable style="min-width: 10rem">
|
<Column field="category" header="دسته بندی" sortable style="min-width: 10rem">
|
||||||
<template #body="slotProps">
|
<template #body="slotProps">
|
||||||
{{ slotProps.data.nativeName }}
|
{{ slotProps.data.category.nativeName }}
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
<Column :exportable="false" style="min-width: 12rem">
|
<Column :exportable="false" style="min-width: 12rem">
|
||||||
<template #body="slotProps">
|
<template #body="slotProps">
|
||||||
<Button icon="pi pi-pencil" outlined rounded class="mr-2" @click="editProduct(slotProps.data)" />
|
<Button icon="pi pi-pencil" outlined rounded class="mr-2 mx-3" @click="editBelonging(slotProps.data._id)" />
|
||||||
<Button icon="pi pi-trash" outlined rounded severity="danger" @click="confirmDeleteProduct(slotProps.data)" />
|
<Button icon="pi pi-trash" outlined rounded severity="danger" class="mx-3" @click="confirmDeleteProduct(slotProps.data)" />
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
</DataTable>
|
</DataTable>
|
||||||
@@ -196,7 +187,7 @@ function getStatusLabel(status) {
|
|||||||
|
|
||||||
<Dialog v-model:visible="productDialog" :style="{ width: '450px' }" header="Product Details" :modal="true">
|
<Dialog v-model:visible="productDialog" :style="{ width: '450px' }" header="Product Details" :modal="true">
|
||||||
<div class="flex flex-col gap-6">
|
<div class="flex flex-col gap-6">
|
||||||
<img v-if="product.image" :src="`https://primefaces.org/cdn/primevue/images/product/${product.image}`" :alt="product.image" class="block m-auto pb-4" />
|
<img v-if="belonging.image" :src="`https://primefaces.org/cdn/primevue/images/product/${product.image}`" :alt="product.image" class="block m-auto pb-4" />
|
||||||
<div>
|
<div>
|
||||||
<label for="name" class="block font-bold mb-3">Name</label>
|
<label for="name" class="block font-bold mb-3">Name</label>
|
||||||
<InputText id="name" v-model.trim="product.name" required="true" autofocus :invalid="submitted && !product.name" fluid />
|
<InputText id="name" v-model.trim="product.name" required="true" autofocus :invalid="submitted && !product.name" fluid />
|
||||||
@@ -255,13 +246,13 @@ function getStatusLabel(status) {
|
|||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<i class="pi pi-exclamation-triangle !text-3xl" />
|
<i class="pi pi-exclamation-triangle !text-3xl" />
|
||||||
<span v-if="product"
|
<span v-if="product"
|
||||||
>Are you sure you want to delete <b>{{ product.name }}</b
|
>Are you sure you want to delete <b>{{ product.name }}</b
|
||||||
>?</span
|
>?</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<Button label="No" icon="pi pi-times" text @click="deleteProductDialog = false" />
|
<Button label="خیر" icon="pi pi-times" text @click="deleteProductDialog = false" />
|
||||||
<Button label="Yes" icon="pi pi-check" @click="deleteProduct" />
|
<Button label="بله" icon="pi pi-check" @click="deleteProduct" />
|
||||||
</template>
|
</template>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ function deleteSelectedCategories() {
|
|||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex flex-wrap gap-2 items-center justify-between">
|
<div class="flex flex-wrap gap-2 items-center justify-between">
|
||||||
<h4 class="m-0">مدیریت اموال</h4>
|
<h4 class="m-0">مدیریت دسته بندی ها</h4>
|
||||||
<IconField>
|
<IconField>
|
||||||
<InputIcon>
|
<InputIcon>
|
||||||
<i class="pi pi-search" />
|
<i class="pi pi-search" />
|
||||||
|
|||||||
@@ -93,15 +93,6 @@ function findIndexById(id) {
|
|||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createId() {
|
|
||||||
let id = '';
|
|
||||||
var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
||||||
for (var i = 0; i < 5; i++) {
|
|
||||||
id += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
||||||
}
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
function exportCSV() {
|
function exportCSV() {
|
||||||
dt.value.exportCSV();
|
dt.value.exportCSV();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Tabs, TabList, Tab } from 'primevue';
|
|||||||
import { useToast } from 'primevue/usetoast';
|
import { useToast } from 'primevue/usetoast';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { FeatureService } from '@/service/FeatureService';
|
import { FeatureService } from '@/service/FeatureService';
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const selectedTab = ref('');
|
const selectedTab = ref('');
|
||||||
const selectedFeature = ref();
|
const selectedFeature = ref();
|
||||||
|
|||||||
@@ -1,95 +1,143 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div :id="galleryId" class="pswp-gallery pswp-gallery--single-column my-3 flex min-w-0 flex-row flex-nowrap overflow-x-auto rounded-md bg-brand-dark-3 p-3">
|
||||||
:id="galleryId"
|
<div v-for="(item, key) in itemsData" :key="key" class="relative mx-3 flex-shrink-0">
|
||||||
class="pswp-gallery pswp-gallery--single-column my-3 flex min-w-0 flex-row flex-nowrap overflow-x-auto rounded-md bg-brand-dark-3 p-3"
|
<!-- Image Display -->
|
||||||
>
|
<template v-if="isImageFile(item.url)">
|
||||||
<div v-for="(image, key) in imagesData" :key="key" class="relative">
|
<a :href="item.url" :data-pswp-width="item.width" :data-pswp-height="item.height" target="_blank" rel="noreferrer" class="block">
|
||||||
<a
|
<img :src="item.url" alt="" class="max-h-60 rounded-md" :style="{ height: '150px' }" />
|
||||||
:href="image.url"
|
</a>
|
||||||
:data-pswp-width="image.width"
|
</template>
|
||||||
:data-pswp-height="image.height"
|
|
||||||
target="_blank"
|
<!-- File Display -->
|
||||||
rel="noreferrer"
|
<template v-else>
|
||||||
class="mx-3 block flex-shrink-0"
|
<div class="flex h-[150px] w-[200px] flex-col items-center justify-center rounded-md border-2 border-gray-200 bg-white p-4">
|
||||||
>
|
<!-- File Icon -->
|
||||||
<img :src="image.url" alt="" class="max-h-60 rounded-md" :style="{ height: '150px' }" />
|
<font-awesome-icon :icon="getFileIcon(item.url)" class="mb-2 text-4xl" :class="getIconColor(item.url)" />
|
||||||
</a>
|
|
||||||
<button
|
<!-- File Name -->
|
||||||
class="absolute right-4 top-2 flex h-6 w-6 items-center justify-center rounded-full text-brand-danger"
|
<p class="mb-2 max-w-full truncate text-sm font-medium text-gray-700">
|
||||||
@click="removeImage(key)"
|
{{ getFileName(item.url) }}
|
||||||
>
|
</p>
|
||||||
<font-awesome-icon icon="fa-solid fa-x" />
|
|
||||||
</button>
|
<!-- Download Button -->
|
||||||
|
<Button icon="pi pi-download" severity="secondary" size="small" @click="downloadFile(item.url)" class="p-button-sm"> Download </Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Remove Button -->
|
||||||
|
<button class="absolute right-4 top-2 flex h-6 w-6 items-center justify-center rounded-full bg-white/80 text-red-500 hover:bg-white" @click="removeItem(key)">
|
||||||
|
<font-awesome-icon icon="fa-solid fa-x" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch, onMounted } from 'vue'
|
import { ref, watch, onMounted } from 'vue';
|
||||||
import PhotoSwipeLightbox from 'photoswipe/lightbox'
|
import PhotoSwipeLightbox from 'photoswipe/lightbox';
|
||||||
import 'photoswipe/style.css'
|
import 'photoswipe/style.css';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||||
|
import { Button } from 'primevue';
|
||||||
|
|
||||||
function updateImagesData(images) {
|
// File type checking functions
|
||||||
for (let i = 0; i < images.length; i++) {
|
function isImageFile(url) {
|
||||||
const img = new Image()
|
const extension = url.toLowerCase().split('.').pop();
|
||||||
img.src = images[i].url
|
return ['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(extension);
|
||||||
img.onload = function () {
|
}
|
||||||
images[i].height = img.naturalHeight
|
|
||||||
images[i].width = img.naturalWidth
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return images
|
function getFileIcon(url) {
|
||||||
|
const extension = url.toLowerCase().split('.').pop();
|
||||||
|
const iconMap = {
|
||||||
|
pdf: 'fa-solid fa-file-pdf',
|
||||||
|
doc: 'fa-solid fa-file-word',
|
||||||
|
docx: 'fa-solid fa-file-word',
|
||||||
|
xls: 'fa-solid fa-file-excel',
|
||||||
|
xlsx: 'fa-solid fa-file-excel',
|
||||||
|
txt: 'fa-solid fa-file-lines',
|
||||||
|
zip: 'fa-solid fa-file-zipper'
|
||||||
|
// Add more file types as needed
|
||||||
|
};
|
||||||
|
return iconMap[extension] || 'fa-solid fa-file';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getIconColor(url) {
|
||||||
|
const extension = url.toLowerCase().split('.').pop();
|
||||||
|
const colorMap = {
|
||||||
|
pdf: 'text-red-500',
|
||||||
|
doc: 'text-blue-500',
|
||||||
|
docx: 'text-blue-500',
|
||||||
|
xls: 'text-green-500',
|
||||||
|
xlsx: 'text-green-500',
|
||||||
|
txt: 'text-gray-500',
|
||||||
|
zip: 'text-yellow-600'
|
||||||
|
// Add more file types as needed
|
||||||
|
};
|
||||||
|
return colorMap[extension] || 'text-gray-400';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFileName(url) {
|
||||||
|
return url.split('/').pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadFile(url) {
|
||||||
|
window.open(url, '_blank');
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateItemsData(items) {
|
||||||
|
return items.map((item) => {
|
||||||
|
if (isImageFile(item.url)) {
|
||||||
|
// Handle images
|
||||||
|
const img = new Image();
|
||||||
|
img.src = item.url;
|
||||||
|
img.onload = function () {
|
||||||
|
item.height = img.naturalHeight;
|
||||||
|
item.width = img.naturalWidth;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
galleryId: {
|
galleryId: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
const imagesData = ref([])
|
const itemsData = ref([]);
|
||||||
|
let lightbox = null;
|
||||||
|
|
||||||
let lightbox = null
|
|
||||||
|
|
||||||
// Lifecycle hook: mounted
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (!lightbox) {
|
if (!lightbox) {
|
||||||
console.log('Launching Gallery')
|
lightbox = new PhotoSwipeLightbox({
|
||||||
console.log({
|
gallery: '#' + props.galleryId,
|
||||||
gallery: '#' + props.galleryId,
|
children: 'a',
|
||||||
children: 'a'
|
pswpModule: () => import('photoswipe')
|
||||||
})
|
});
|
||||||
lightbox = new PhotoSwipeLightbox({
|
lightbox.init();
|
||||||
gallery: '#' + props.galleryId,
|
}
|
||||||
children: 'a',
|
itemsData.value = updateItemsData(props.modelValue);
|
||||||
pswpModule: () => import('photoswipe')
|
});
|
||||||
})
|
|
||||||
lightbox.init()
|
|
||||||
}
|
|
||||||
imagesData.value = updateImagesData(props.modelValue)
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.modelValue,
|
() => props.modelValue,
|
||||||
(newImages) => {
|
(newItems) => {
|
||||||
console.log('propsimages', newImages)
|
itemsData.value = updateItemsData(newItems);
|
||||||
imagesData.value = updateImagesData(newImages)
|
},
|
||||||
},
|
{ deep: true, immediate: true }
|
||||||
{ deep: true, immediate: true }
|
);
|
||||||
)
|
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue']);
|
||||||
function removeImage(index) {
|
|
||||||
const newImages = [...props.modelValue]
|
function removeItem(index) {
|
||||||
newImages.splice(index, 1)
|
const newItems = [...props.modelValue];
|
||||||
emit('update:modelValue', newImages)
|
newItems.splice(index, 1);
|
||||||
|
emit('update:modelValue', newItems);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -25,12 +25,11 @@
|
|||||||
onload: (response) => handleRevertResponse(response)
|
onload: (response) => handleRevertResponse(response)
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
label-idle="برای آپلود تصاویر کلیک کنید"
|
label-idle="برای آپلود کلیک کنید"
|
||||||
label-tap-to-undo=" برای حذف کلیک کنید "
|
label-tap-to-undo=" برای حذف کلیک کنید "
|
||||||
label-file-processing=" در حال آپلود "
|
label-file-processing=" در حال آپلود "
|
||||||
label-file-processing-complete=" آپلود فایل تکمیل شد "
|
label-file-processing-complete=" آپلود فایل تکمیل شد "
|
||||||
:allow-multiple="true"
|
:allow-multiple="true"
|
||||||
accepted-file-types="image/jpeg, image/png"
|
|
||||||
v-model="uploadedFiles"
|
v-model="uploadedFiles"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -59,6 +58,10 @@ const props = defineProps({
|
|||||||
type: Number,
|
type: Number,
|
||||||
default: 1
|
default: 1
|
||||||
},
|
},
|
||||||
|
acceptedFiles: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
mode: {
|
mode: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<template>
|
||||||
|
<!-- Outer container that mimics a text field with a green border -->
|
||||||
|
<div class="flex flex-wrap items-center gap-2 rounded p-2 cursor-text" @click="focusInput">
|
||||||
|
<InputText v-model="inputValue" @keyup.enter="addChip" :placeholder="placeholder" class="w-full min-w-[100px]" />
|
||||||
|
<Chip v-for="(tag, index) in localTags" :key="tag" :label="tag" removable @remove="removeChip(index)" class="text-sm" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
import Chip from 'primevue/chip';
|
||||||
|
import InputText from 'primevue/inputtext';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: 'Example: Building, 500 sqft...'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue']);
|
||||||
|
|
||||||
|
// Local reactive references
|
||||||
|
const localTags = ref([...props.modelValue]);
|
||||||
|
const inputValue = ref('');
|
||||||
|
const inputRef = ref(null);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(newVal) => {
|
||||||
|
localTags.value = [...newVal];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
function addChip() {
|
||||||
|
const value = inputValue.value.trim();
|
||||||
|
if (value && !localTags.value.includes(value)) {
|
||||||
|
localTags.value.push(value);
|
||||||
|
emit('update:modelValue', localTags.value);
|
||||||
|
}
|
||||||
|
inputValue.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeChip(index) {
|
||||||
|
localTags.value.splice(index, 1);
|
||||||
|
emit('update:modelValue', localTags.value);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
No <style> block is needed if you're using Tailwind CSS utility classes.
|
||||||
|
However, if you need to override or add custom CSS, do it here or in a global stylesheet.
|
||||||
|
-->
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
<template>
|
||||||
|
<div class="map-container">
|
||||||
|
<!-- Search box -->
|
||||||
|
<div class="search-container">
|
||||||
|
<input v-model="searchQuery" @keyup.enter="handleSearch" placeholder="جستجو" class="search-input" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<l-map ref="map" v-model:zoom="zoom" :center="center" :use-global-leaflet="false" @click="handleClick($event)">
|
||||||
|
<!-- Base layers -->
|
||||||
|
<l-control-layers position="topright" />
|
||||||
|
|
||||||
|
<!-- OpenStreetMap layer (default) -->
|
||||||
|
<l-tile-layer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" layer-type="base" name="نمای نقشه ای" :attribution="attribution" />
|
||||||
|
|
||||||
|
<!-- Satellite layer (optional) -->
|
||||||
|
<l-tile-layer
|
||||||
|
:visible="false"
|
||||||
|
url="https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}"
|
||||||
|
layer-type="base"
|
||||||
|
name="نمای ماهواره ای"
|
||||||
|
attribution="Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Marker -->
|
||||||
|
<l-marker v-if="marker.length > 0" :lat-lng="marker">
|
||||||
|
<l-popup> نقطه انتخاب شده </l-popup>
|
||||||
|
</l-marker>
|
||||||
|
</l-map>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
import 'leaflet/dist/leaflet.css';
|
||||||
|
import { LMap, LTileLayer, LMarker, LPopup, LControlLayers } from '@vue-leaflet/vue-leaflet';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
// Props and emits
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue']);
|
||||||
|
|
||||||
|
// Map configuration
|
||||||
|
const zoom = ref(13);
|
||||||
|
const center = ref([31.3183, 48.6706]);
|
||||||
|
const attribution = ref('© OpenStreetMap contributors');
|
||||||
|
const marker = ref([...props.modelValue]);
|
||||||
|
const map = ref(null);
|
||||||
|
|
||||||
|
// Search functionality
|
||||||
|
const searchQuery = ref('');
|
||||||
|
|
||||||
|
const handleSearch = async () => {
|
||||||
|
if (!searchQuery.value) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(searchQuery.value)}`);
|
||||||
|
|
||||||
|
if (response.data && response.data.length > 0) {
|
||||||
|
const { lat, lon } = response.data[0];
|
||||||
|
center.value = [parseFloat(lat), parseFloat(lon)];
|
||||||
|
zoom.value = 16;
|
||||||
|
marker.value = [parseFloat(lat), parseFloat(lon)];
|
||||||
|
emit('update:modelValue', marker.value);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Search failed:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Watch for changes in modelValue and sync with marker
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(newValue) => {
|
||||||
|
marker.value = [...newValue];
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update marker and emit changes to modelValue
|
||||||
|
const handleClick = (event) => {
|
||||||
|
marker.value = [event.latlng.lat, event.latlng.lng];
|
||||||
|
emit('update:modelValue', marker.value);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.map-container {
|
||||||
|
font-family: 'Estedad', sans-serif !important;
|
||||||
|
height: 500px;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-container {
|
||||||
|
font-family: 'Estedad', serif !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
z-index: 1000;
|
||||||
|
background: white;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 250px;
|
||||||
|
font-family: 'Estedad', sans-serif;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<script setup>
|
||||||
|
import { VMap } from 'v-mapbox';
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import mapbox from 'mapbox-gl'; // Import the Mapbox GL library
|
||||||
|
|
||||||
|
if (mapbox.getRTLTextPluginStatus() === 'unavailable') {
|
||||||
|
mapbox.setRTLTextPlugin(
|
||||||
|
'https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-rtl-text/v0.2.3/mapbox-gl-rtl-text.js',
|
||||||
|
null,
|
||||||
|
true // Lazy load the plugin
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define your map style and Map.ir API token
|
||||||
|
const mapStyle = 'https://map.ir/vector/styles/main/mapir-style-dark.json'; // Replace it with your chosen style
|
||||||
|
const MAPIR_TOKEN =
|
||||||
|
'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6Ijk2MWQyMWVjZmQyODI1NzY0NDdmMjczNTE0NTA5NGVjYjgxMjBmOWU0ZmM5Mjc2N2QxNGRmZmRmMjMzNDkzMWVmNDlkMGZiMzhkNmUzYTY4In0.eyJhdWQiOiIzMDM0NSIsImp0aSI6Ijk2MWQyMWVjZmQyODI1NzY0NDdmMjczNTE0NTA5NGVjYjgxMjBmOWU0ZmM5Mjc2N2QxNGRmZmRmMjMzNDkzMWVmNDlkMGZiMzhkNmUzYTY4IiwiaWF0IjoxNzM2MDc0MTI0LCJuYmYiOjE3MzYwNzQxMjQsImV4cCI6MTczODU3OTcyNCwic3ViIjoiIiwic2NvcGVzIjpbImJhc2ljIl19.Uz6Becl2wXziCtOooCirPeQQilnxFXKRs-zBYqfiHpj-MeVMLV99O2xqpb-dqOp32_wH0uwim1tqf_yI2VSYpVWyGxCqSBGAP4Exo_3Tf9oyOOHy77VMBhbkqJUL_PxQ7VgQShCFas_CH3pz0Mkmeo03-gBOT9qEGOJOk5P_ExFUyS-6z6vADBclsallGnggP7iOwsTkoIz-qzqaBoJUptqmUOeYObHxGv3DbWT3zWuoaNY1FFj289wbx3EANkD_XrPrfoYHDvc_Ibj4HDjEGjSg2F94Y3Cqnau7mZXQEukzKgHyhsHFanjA44gQg5O5IixoUu3MQKK4WymopfwA8g'; // Replace it with your Map.ir API token
|
||||||
|
|
||||||
|
// Method to transform the request
|
||||||
|
const transformRequest = (url) => {
|
||||||
|
return {
|
||||||
|
url,
|
||||||
|
headers: {
|
||||||
|
'x-api-key': MAPIR_TOKEN // Mapir API key
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Assign mapbox to be available when the component is created
|
||||||
|
let mapboxInstance;
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
mapboxInstance = mapbox;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<VMap
|
||||||
|
:options="{
|
||||||
|
style: mapStyle,
|
||||||
|
transformRequest,
|
||||||
|
zoom: 11,
|
||||||
|
center: [51.375433528216654, 35.73356434056531]
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
Reference in New Issue
Block a user