Init git
This commit is contained in:
commit
d222e7d7c1
3
.env
Normal file
3
.env
Normal file
@ -0,0 +1,3 @@
|
||||
NODE_ENV=development
|
||||
VUE_PUBLIC_PATH=/complexes-frame
|
||||
VUE_APP_API_URL=https://dev.joywork.ru
|
||||
3
.env.development.example
Normal file
3
.env.development.example
Normal file
@ -0,0 +1,3 @@
|
||||
NODE_ENV=local
|
||||
VUE_PUBLIC_PATH=/complexes-frame
|
||||
VUE_APP_API_URL=http://localhost
|
||||
3
.env.local.example
Normal file
3
.env.local.example
Normal file
@ -0,0 +1,3 @@
|
||||
NODE_ENV=local
|
||||
VUE_PUBLIC_PATH=/complexes-frame
|
||||
VUE_APP_API_URL=http://localhost
|
||||
3
.env.production.example
Normal file
3
.env.production.example
Normal file
@ -0,0 +1,3 @@
|
||||
NODE_ENV=production
|
||||
VUE_PUBLIC_PATH=/complexes-frame
|
||||
VUE_APP_API_URL=https://joywork.ru
|
||||
26
.gitignore
vendored
Normal file
26
.gitignore
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
.env.development
|
||||
.env.production
|
||||
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
19
README.md
Normal file
19
README.md
Normal file
@ -0,0 +1,19 @@
|
||||
# complexes
|
||||
|
||||
## Project setup
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||
5
babel.config.js
Normal file
5
babel.config.js
Normal file
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
||||
19
jsconfig.json
Normal file
19
jsconfig.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "esnext",
|
||||
"baseUrl": "./",
|
||||
"moduleResolution": "node",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
]
|
||||
}
|
||||
}
|
||||
81
package.json
Normal file
81
package.json
Normal file
@ -0,0 +1,81 @@
|
||||
{
|
||||
"name": "complexes",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "VUE_APP_IS_SERVE=true vue-cli-service serve --mode local",
|
||||
"serve:dev": "VUE_APP_IS_SERVE=true vue-cli-service serve --mode development",
|
||||
"serve:prod": "VUE_APP_IS_SERVE=true vue-cli-service serve --mode production",
|
||||
"build": "vue-cli-service build --mode local",
|
||||
"build:dev": "vue-cli-service build --mode development",
|
||||
"build:prod": "vue-cli-service build --mode production"
|
||||
},
|
||||
"dependencies": {
|
||||
"@rushstack/eslint-patch": "^1.6.1",
|
||||
"@vue/eslint-config-prettier": "^9.0.0",
|
||||
"@vuelidate/core": "^2.0.3",
|
||||
"@vuelidate/validators": "^2.0.4",
|
||||
"axios": "^1.6.5",
|
||||
"core-js": "^3.8.3",
|
||||
"i": "^0.3.7",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"moment-timezone": "^0.5.46",
|
||||
"npm": "^10.5.2",
|
||||
"ol": "^8.2.0",
|
||||
"pinia": "^2.1.7",
|
||||
"primevue": "3.32",
|
||||
"quill": "^2.0.0",
|
||||
"sass": "^1.69.7",
|
||||
"tiny-emitter": "^2.1.0",
|
||||
"vue": "^3.2.13",
|
||||
"vue-cropperjs": "^5.0.0",
|
||||
"vue-dadata": "^3.0.0-beta.13",
|
||||
"vue-router": "^4.0.3",
|
||||
"vue-yandex-maps": "^2.1.3",
|
||||
"vue3-perfect-scrollbar": "^2.0.0",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"vuex": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/vue-cropperjs": "^4.1.6",
|
||||
"@vue/cli-plugin-babel": "~5.0.0",
|
||||
"@vue/cli-plugin-router": "~5.0.0",
|
||||
"@vue/cli-plugin-vuex": "~5.0.0",
|
||||
"@vue/cli-service": "~5.0.0",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-prettier": "^8.0.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-vue": "^9.19.2",
|
||||
"postcss": "^8.4.33",
|
||||
"prettier": "3.1.1",
|
||||
"sass-loader": "^13.3.3",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"vue-cli-plugin-tailwind": "~3.0.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/vue3-essential",
|
||||
"eslint:recommended",
|
||||
"@vue/typescript/recommended",
|
||||
"plugin:prettier/recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020
|
||||
},
|
||||
"rules": {
|
||||
"no-undef": "off",
|
||||
"vue/multi-word-component-names": "off"
|
||||
}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead",
|
||||
"not ie 11"
|
||||
]
|
||||
}
|
||||
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
21
public/index.html
Normal file
21
public/index.html
Normal file
@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://api-maps.yandex.ru/2.1/?apikey=692f0a05-cb88-4158-88b9-06bc0dc93004&load=package.standard&lang=ru-RU&ns=yandexMap"
|
||||
async>
|
||||
</script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.8.0/jszip.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.8.0/xlsx.js"></script>
|
||||
<title>JoyWork</title>
|
||||
</head>
|
||||
<body>
|
||||
<input type="hidden" value="4" id="session_user_id"/>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
104
src/app/App.vue
Normal file
104
src/app/App.vue
Normal file
@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import "@/app/styles/main.scss";
|
||||
import api from "@/shared/api";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { watch } from "vue";
|
||||
|
||||
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
|
||||
watch(
|
||||
() => route.params,
|
||||
(newParams) => {
|
||||
const token = newParams.token;
|
||||
|
||||
|
||||
if (newParams.token) {
|
||||
api.getFrameAccess(token).then((response) => {
|
||||
//console.log(response.data.access, 'response for access');
|
||||
|
||||
// if (response.data.access !== true) {
|
||||
// router.push('/access-denied');
|
||||
// return;
|
||||
// }
|
||||
|
||||
//console.log('log inside .then block');
|
||||
});
|
||||
|
||||
// No use of `response` here
|
||||
//console.log('log in app.vue which is first');
|
||||
}
|
||||
//console.log('log in app.vue which is first');
|
||||
|
||||
api.getAllColors(token)
|
||||
.then(({ data }) => {
|
||||
if (typeof data === 'object' && data !== null) {
|
||||
data.not_for_sale = '#dedede'
|
||||
data.sold = '#757575'
|
||||
const storedColors = localStorage.getItem("colors");
|
||||
if (storedColors) {
|
||||
const parsedColors = JSON.parse(storedColors);
|
||||
|
||||
|
||||
if (JSON.stringify(parsedColors) !== JSON.stringify(data)) {
|
||||
localStorage.setItem("colors", JSON.stringify(data));
|
||||
}
|
||||
} else {
|
||||
localStorage.setItem("colors", JSON.stringify(data));
|
||||
}
|
||||
|
||||
const colors = JSON.parse(localStorage.getItem('colors'));
|
||||
const frameColor = colors.frame_color;
|
||||
const freeColor = colors.color_free;
|
||||
const reservedColor = colors.color_reserved;
|
||||
|
||||
document.documentElement.style.setProperty("--main-color", frameColor)
|
||||
}
|
||||
// else {
|
||||
// document.documentElement.style.setProperty("--main-color", "#07a860") // frameColor
|
||||
// }
|
||||
|
||||
});
|
||||
}
|
||||
,
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-size: 15px;
|
||||
}
|
||||
.jw__layout {
|
||||
font-size: 14px;
|
||||
}
|
||||
.p-component-overlay{
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.p-galleria .p-galleria-item-nav{
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.p-galleria-item-nav{
|
||||
top: 40% !important;
|
||||
}
|
||||
|
||||
.p-link:focus {
|
||||
outline: 0 none;
|
||||
outline-offset: 0;
|
||||
box-shadow: 0 0 0 0.2rem #22c55e;
|
||||
}
|
||||
|
||||
</style>
|
||||
42
src/app/images/rocket-spinner.svg
Normal file
42
src/app/images/rocket-spinner.svg
Normal file
@ -0,0 +1,42 @@
|
||||
<svg height="900" viewBox="0 0 900 900" width="900" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<linearGradient id="b" x1="50%" x2="50%" y1="0%" y2="100%">
|
||||
<stop offset="0" stop-color="#43a047"/>
|
||||
<stop offset="1" stop-color="#27702a"/>
|
||||
</linearGradient>
|
||||
<path id="rocket"
|
||||
d="m29.1695563 302.722947 15.7030684-15.702338c4.3362202-4.336223 4.3362202-11.366116 0-15.702339-4.3347397-4.336223-11.3653678-4.335482-15.7023282 0l-15.7030684 15.702339c-4.33622023 4.336223-4.33622023 11.366115 0 15.702338 4.3362201 4.336223 11.366108 4.336223 15.7023282 0zm47.1077249 0c-4.33548-4.336223-11.3661081-4.336223-15.7023282 0l-57.32278786 57.322825c-4.33622019 4.336223-4.33622019 11.366115 0 15.702338 4.33547996 4.335483 11.36610806 4.336223 15.70232826 0l57.3227878-57.322824c4.3369604-4.336223 4.3369604-11.366116 0-15.702339zm301.4879588-292.0666024c-.698029-4.88546801-4.53682-8.7235213-9.422285-9.42155105-1.193978-.17025116-29.704367-4.09713114-69.286255 3.00752373-51.351417 9.21724962-98.821111 33.19971612-136.416243 70.79487302-10.027047 10.027053-21.067457 22.2880973-31.958343 35.4862627-13.458715-3.048235-57.7180663-8.62063-101.5118175 35.17463-38.81057707 38.809862-16.665357 61.847805-15.7030684 62.810094 2.0822443 2.082245 4.906191 3.252537 7.850794 3.252537 2.9446029 0 5.7692898-1.170292 7.850794-3.251797 6.2918865-6.291891 14.6563946-9.756872 23.5538624-9.756872 8.8974679 0 17.2619759 3.464981 23.5531222 9.756132l23.5546027 23.553877-7.8515342 7.85154c-13.0175432 13.016071-13.0205041 34.087983 0 47.107015 13.0160628 13.016811 34.0887008 13.019772 47.1069848 0l7.851534-7.85154s23.522773 23.523529 23.565706 23.565722c12.975351 12.988683 12.97165 34.11167-.011843 47.095171-2.082245 2.082246-3.252536 4.906935-3.252536 7.85154 0 2.945345 1.169551 5.768553 3.252536 7.851539.516674.516675 7.414063 7.15573 19.71211 7.15573 10.577772 0 25.152002-4.912856 43.097943-22.858809 43.794491-43.794519 38.2221-88.053159 35.174607-101.512623 13.198897-10.890893 25.459934-21.93131 35.48698-31.959103 37.51815-37.518174 61.56648-85.000482 70.794827-136.4163319 7.10391-39.5796932 3.178513-68.0908405 3.007522-69.2855595zm-325.041081 165.8882874c-7.8804029 0-15.5091303 1.63071-22.5020071 4.732242 2.1688503-5.152688 6.5739111-11.799145 14.6512131-19.877193 19.1354776-19.13549 43.1016437-31.694844 69.780723-30.597094-13.683743 18.089556-26.1017062 36.677281-34.6638535 52.889633-8.2297876-4.655259-17.5536233-7.147588-27.2660755-7.147588zm70.660107 94.773638c-4.337701 4.338444-11.361667 4.339184-15.701588 0-4.329558-4.329561-4.329558-11.373517 0-15.702338l62.810053-62.810094c4.328078-4.32882 11.37277-4.330301 15.703068 0 4.329559 4.329561 4.329559 11.373518 0 15.703079zm94.21545 62.810094c-8.097288 8.097293-14.756325 12.503837-19.913451 14.666767 7.03729-15.90886 6.243772-34.472158-2.380554-49.782919 16.227886-8.570295 34.836325-21.003811 52.942154-34.703108.637331 15.384783-3.193317 42.36441-30.648149 69.81926zm70.661587-133.471727c-31.98499 31.985012-77.493836 66.044126-107.545367 80.884845l-18.074739-18.074751 39.25619-39.256216c13.017544-13.016811 13.020505-34.088723 0-47.107755-12.987934-12.987943-34.121271-12.985722-47.107724 0l-39.256191 39.256216-18.0739995-18.074011c14.8392295-30.05081 48.8990625-75.559685 80.8840525-107.5461766 27.88268-27.8826984 60.940253-47.4882296 98.526502-58.4990382l69.805891 69.8059358c-9.228347 31.327694-26.519932 66.716247-58.414615 98.610951zm64.443723-123.9857758-50.441685-50.4417171c23.893625-4.3606504 43.557621-4.2925499 54.235323-3.7314613.549984 10.6347756.586996 30.2047762-3.793638 54.1731784zm-119.402242 6.2171282c-17.356724 17.3559956-17.360425 45.4511376 0 62.8100936 17.314532 17.314543 45.4896 17.318244 62.810053 0 17.316753-17.316763 17.316753-45.49333 0-62.8108339-17.356724-17.3545148-45.450368-17.3589561-62.810053.0007403zm47.107725 47.1077556c-8.658376 8.658381-22.74628 8.658381-31.405397 0-8.678362-8.678368-8.680582-22.725569 0-31.404677 8.678363-8.6783678 22.724814-8.679108 31.405397 0 8.657636 8.658382 8.657636 22.746295 0 31.404677zm-188.4301594 204.132619-15.7023282 15.702338c-4.3362202 4.336223-4.3362202 11.366116 0 15.702339 4.33548 4.335483 11.3661081 4.336223 15.7023282 0l15.7023284-15.702339c4.33622-4.336223 4.33622-11.366115 0-15.702338-4.33622-4.336963-11.3668484-4.336963-15.7023284 0z"/>
|
||||
<animateTransform xlink:href="#circle"
|
||||
attributeName="transform"
|
||||
attributeType="XML"
|
||||
type="rotate"
|
||||
from="0 440 440"
|
||||
to="360 440 440"
|
||||
dur="3s"
|
||||
repeatCount="indefinite" />
|
||||
</defs>
|
||||
<g id="spinner" fill="none" transform="translate(10, 10)">
|
||||
<g id="circle">
|
||||
<path d="m437.5 858.981693c-232.788022 0-421.5-188.703781-421.5-421.481693s188.711978-421.4816934 421.5-421.4816934 421.5 188.7037814 421.5 421.4816934-188.711978 421.481693-421.5 421.481693zm0-2.952586c231.157283 0 418.547285-187.381864 418.547285-418.529107s-187.390002-418.5291071-418.547285-418.5291071-418.5472855 187.3818641-418.5472855 418.5291071 187.3900025 418.529107 418.5472855 418.529107z"
|
||||
fill="#eaeaea"/>
|
||||
<path d="m842.537778 437.869198c-.199121-223.796198-181.430278-405.180293-405.037778-405.3795821v-.0166474c-8.621726-.3860271-15.493255-7.503992-15.493255-16.2282428 0-8.72425067 6.871529-15.84221559 15.493255-16.22824271v-.01648299c.206248 0 .412462.00014284.618643.00042842.039678-.00028546.079388-.00042842.119131-.00042842.058717 0 .117362.00031205.175932.0009346 241.204482.49347657 436.586294 196.3446814 436.586294 437.8682634 0 8.971715-7.266879 16.244726-16.231029 16.244726-8.964149 0-16.231028-7.273011-16.231028-16.244726z"
|
||||
fill="#cfcfcf"/>
|
||||
</g>
|
||||
<path d="m420.531033 858.755274c-.199121-223.796198-181.430278-405.180293-405.0377784-405.379582v-.016648c-8.62172632-.386027-15.4932546-7.503992-15.4932546-16.228242 0-8.724251 6.87152828-15.842216 15.4932546-16.228243v-.016483c.2062477 0 .4124621.000143.618643.000428.0396777-.000285.0793877-.000428.1191311-.000428.0587174 0 .117362.000312.1759322.000935 241.2044821.493476 436.5862941 196.344681 436.5862941 437.868263 0 8.971715-7.266879 16.244726-16.231029 16.244726s-16.231029-7.273011-16.231029-16.244726z"
|
||||
transform="matrix(-1 0 0 -1 452.993254 1295.886076)"/>
|
||||
<g fill-rule="evenodd" transform="matrix(.70710678 -.70710678 .70710678 .70710678 169.299423 438)">
|
||||
<use fill="url(#b)" xlink:href="#rocket"/>
|
||||
<animateMotion id="rocket1"
|
||||
path="M0,0 0 60 0 0"
|
||||
begin="0s; rocket2.end"
|
||||
dur="2s" repeatCount="indefinite"
|
||||
fill="freeze" />
|
||||
<animateMotion id="rocket2"
|
||||
path="M0,0 0 -60 0 0"
|
||||
begin="rocket1.end"
|
||||
dur="2s" repeatCount="indefinite"
|
||||
fill="freeze" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.0 KiB |
97
src/app/router/index.js
Normal file
97
src/app/router/index.js
Normal file
@ -0,0 +1,97 @@
|
||||
import { createRouter, createWebHashHistory, createWebHistory, RouterLink, RouterView } from 'vue-router'
|
||||
//import HomeView from '../views/HomeView.vue'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: "/:token/view",
|
||||
name: "view-pages",
|
||||
component: () => import("@/pages/View"),
|
||||
meta: {
|
||||
isViewPage: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'complexes-list',
|
||||
component: () => import('@/pages/ComplexesView.vue'),
|
||||
},
|
||||
{
|
||||
path: ":id",
|
||||
name: "complex-homes-router",
|
||||
// component: () => import("@/pages/ComplexViewMode"),
|
||||
component: RouterView,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'complex-homes',
|
||||
component: () => import("@/pages/ComplexViewMode"),
|
||||
},
|
||||
{
|
||||
path: 'apartments',
|
||||
name: 'complex-apartments',
|
||||
component: () => import('@/pages/ApartmentsList.vue')
|
||||
},
|
||||
{
|
||||
path: "home/:house_id",
|
||||
name: "home",
|
||||
component: () => import("@/pages/HomePage.vue"),
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "view-home",
|
||||
component: () => import("@/pages/HomePage/ViewHome.vue"),
|
||||
},
|
||||
{
|
||||
path: "apartment/:apartment_id",
|
||||
name: "apartment-detail",
|
||||
component: () => import("@/pages/HomePage/ViewHome.vue"),
|
||||
}
|
||||
// {
|
||||
// path: "apartment-view/:apartment_id",
|
||||
// name: "apartment-view",
|
||||
// component: () => import("@/shared/layouts/ViewApartment.vue")
|
||||
// }
|
||||
],
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/archived-objects",
|
||||
name: "archived-objects-page",
|
||||
component: () => import("@/pages/Edit"),
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "archive-main",
|
||||
component: () => import("@/pages/MainPage.vue"),
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "/apartments/:apartment_id",
|
||||
name: "apartment",
|
||||
component: () => import("@/pages/Apartment"),
|
||||
},
|
||||
{
|
||||
path: "/:catchAll(.*)",
|
||||
// redirect: { path: '/view' }
|
||||
},
|
||||
{
|
||||
path:"/test-layout",
|
||||
component: () => import("@/shared/layouts/TestLayout.vue"),
|
||||
},
|
||||
{
|
||||
path: "/access-denied",
|
||||
component: () => import("@/shared/layouts/AccessDenied.vue"),
|
||||
}
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory('/complexes-iframe/'),
|
||||
|
||||
routes
|
||||
})
|
||||
|
||||
export default router
|
||||
101
src/app/router/index.old.js
Normal file
101
src/app/router/index.old.js
Normal file
@ -0,0 +1,101 @@
|
||||
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
|
||||
//import HomeView from '../views/HomeView.vue'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: "/",
|
||||
name: "main",
|
||||
component: () => import("@/pages/MainPage.vue"),
|
||||
},
|
||||
{
|
||||
path: "/view",
|
||||
name: "complexes-view",
|
||||
component: () => import("@/pages/ComplexesViewWrapper.vue"),
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'complexes-view-list',
|
||||
component: () => import('@/pages/ComplexesView.vue'),
|
||||
},
|
||||
{
|
||||
path: ":id",
|
||||
name: "complexView",
|
||||
component: () => import("@/pages/ComplexViewMode"),
|
||||
},
|
||||
{
|
||||
path: "home/:id",
|
||||
name: "complexes-view-home",
|
||||
component: () => import("@/pages/HomePage.vue"),
|
||||
children: [
|
||||
{
|
||||
path: "view-home",
|
||||
name: "complexes-view-viewHome",
|
||||
component: () => import("@/pages/HomePage/ViewHome.vue"),
|
||||
},
|
||||
]
|
||||
}
|
||||
// {
|
||||
// path: "home/:id",
|
||||
// name: "home",
|
||||
// component: () => import("@/pages/HomePage.vue"),
|
||||
// children: [
|
||||
// ]
|
||||
// }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "/import",
|
||||
name: "import",
|
||||
component: () => import("@/pages/ImportHomes.vue"),
|
||||
},
|
||||
{
|
||||
path: "/complex/:id",
|
||||
name: "complex",
|
||||
component: () => import("@/pages/ComplexPage.vue"),
|
||||
},
|
||||
{
|
||||
path: "/home/:id",
|
||||
name: "home",
|
||||
component: () => import("@/pages/HomePage.vue"),
|
||||
children: [
|
||||
{
|
||||
path: "main",
|
||||
name: "homeMain",
|
||||
component: () => import("@/pages/HomePage/MainPage.vue"),
|
||||
},
|
||||
{
|
||||
path: "chess",
|
||||
name: "chess",
|
||||
component: () => import("@/pages/HomePage/ChessPage.vue"),
|
||||
},
|
||||
{
|
||||
path: "view-home",
|
||||
name: "viewHome",
|
||||
component: () => import("@/pages/HomePage/ViewHome.vue"),
|
||||
},
|
||||
{
|
||||
path: "planLayout",
|
||||
name: "planLayoutPage",
|
||||
component: () => import("@/pages/HomePage/PlanLayoutPage.vue"),
|
||||
},
|
||||
{
|
||||
path: "floor-plan/:floor_plan_id",
|
||||
name: "floors",
|
||||
component: () => import("@/pages/EditPlanFloors.vue"),
|
||||
},
|
||||
{
|
||||
path: "facades",
|
||||
name: "facades",
|
||||
component: () => import("@/pages/EditFacades.vue"),
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
|
||||
routes
|
||||
})
|
||||
|
||||
export default router
|
||||
263
src/app/store/complex.js
Normal file
263
src/app/store/complex.js
Normal file
@ -0,0 +1,263 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { onMounted, reactive, ref, watch } from "vue";
|
||||
import api from '@/shared/api'
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { copyObject } from "@/shared/utils/util";
|
||||
|
||||
const INITIAL_FILTERS = {
|
||||
selectedTypes: [],
|
||||
|
||||
selectedMetros: [],
|
||||
selectedRayons: [],
|
||||
selectedComplexes: [],
|
||||
selectedDecorations: [],
|
||||
selectedComplexTypes: [],
|
||||
address: '',
|
||||
period: '',
|
||||
area_from: '',
|
||||
area_to: '',
|
||||
rooms: [],
|
||||
useSquarePrice: false,
|
||||
rayons: [],
|
||||
squareKitchen_min: '',
|
||||
squareKitchen_max: '',
|
||||
complexes: [],
|
||||
// area_from: '',
|
||||
// area_to: '',
|
||||
floor_from: '',
|
||||
floor_to: '',
|
||||
price_from: '',
|
||||
price_to: '',
|
||||
// 'metros': [],
|
||||
// 'period': 0,
|
||||
// 'complexes': [],
|
||||
// 'decorations': [],
|
||||
// 'builders': [],
|
||||
// 'types_room': [],
|
||||
// 'types_complex': [],
|
||||
// 'payment_types': [],
|
||||
// 'decoration_types': [],
|
||||
// 'sq_all_do': null,
|
||||
// 'sq_all_ot': null,
|
||||
// 'flat_floor_ot': null,
|
||||
// 'flat_floor_do': null,
|
||||
// 'stoim_ot': null,
|
||||
// 'stoim_do': null,
|
||||
// 'region_id': 0,
|
||||
// 'country_id': 0,
|
||||
}
|
||||
|
||||
export const useComplexStore = defineStore("object", () => {
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const agencyName = ref(null);
|
||||
const agencyLogo = ref(null);
|
||||
const agencyPhone = ref(null);
|
||||
const activeType = ref("tile");
|
||||
const activePage = ref(1);
|
||||
const totalRecords = ref(0);
|
||||
const allPages = ref(0);
|
||||
const load = ref(true);
|
||||
const archiveLoad = ref(true);
|
||||
const complexes = ref({});
|
||||
const loaders = reactive({
|
||||
list: true,
|
||||
archivedList: true
|
||||
})
|
||||
|
||||
const archivedComplexes = ref([])
|
||||
const archivedAllPages = ref(0)
|
||||
const archivedTotalRecords = ref(0);
|
||||
const activeComplex = ref(null)
|
||||
|
||||
const rowsComplex = ref(10);
|
||||
const types = ref({
|
||||
'list': "Список",
|
||||
'tile': "Плитка",
|
||||
// 'table': "Таблица",
|
||||
});
|
||||
|
||||
const setActiveComplex = (complex) => {
|
||||
activeComplex.value = complex
|
||||
}
|
||||
|
||||
const parseQueryFilters = () => {
|
||||
const multipleKeys = ['selectedTypes', 'selectedComplexTypes', 'rooms', 'selectedMetros', 'complexes', 'selectedDecorations']
|
||||
|
||||
|
||||
Object.entries(route.query).forEach(([key, value]) => {
|
||||
if (multipleKeys.includes(key)) {
|
||||
if (Array.isArray(value)) {
|
||||
value = value.map((num) => {
|
||||
if (!isNaN(+num) && key != 'rooms') {
|
||||
return parseInt(num)
|
||||
}
|
||||
|
||||
return num
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof value == 'string' ) {
|
||||
if (!isNaN(+value) && key != 'rooms') {
|
||||
value = [parseInt(value)];
|
||||
} else {
|
||||
value = [value];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (key == 'rayons') {
|
||||
if (Array.isArray(value)) {
|
||||
value = value.map((num) => num);
|
||||
}
|
||||
|
||||
if (typeof value == 'string' ) {
|
||||
value = [value];
|
||||
}
|
||||
}
|
||||
|
||||
complex_filter.value[key] = value == 'true' ? true : value == 'false' ? false : value
|
||||
})
|
||||
|
||||
if (route.query.page) {
|
||||
activePage.value = +route.query.page
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onMounted(parseQueryFilters)
|
||||
|
||||
const changeActiveType = (type) => {
|
||||
activeType.value = type;
|
||||
};
|
||||
|
||||
|
||||
|
||||
const clearFilters = () => {
|
||||
router.replace({query: {}});
|
||||
complex_filter.value = copyObject(INITIAL_FILTERS)
|
||||
}
|
||||
|
||||
const complex_filter = ref(copyObject(INITIAL_FILTERS));
|
||||
|
||||
watch(activePage, (newValue) => {
|
||||
|
||||
if (['main', 'complexes-list'].includes(route.name) && !loaders.list) {
|
||||
getListComplex();
|
||||
}
|
||||
if (route.name == 'archive-main' && !loaders.archivedList) {
|
||||
getArchivedListComplex()
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => route.name, (page, oldPage) => {
|
||||
if (page == oldPage) return;
|
||||
|
||||
if (page == 'archive-main') {
|
||||
getArchivedListComplex()
|
||||
} else if (page == 'main') {
|
||||
getListComplex()
|
||||
}
|
||||
}, { deep: true })
|
||||
|
||||
async function getListComplex(withPage = true){
|
||||
loaders.list = true;
|
||||
if (!withPage) activePage.value = 1
|
||||
api.searchComplexes(
|
||||
{
|
||||
...complex_filter.value,
|
||||
address: complex_filter.value.address.trim()
|
||||
},
|
||||
activePage.value,
|
||||
!route.meta.isViewPage
|
||||
)
|
||||
.then((response) => {
|
||||
|
||||
var res = response.data;
|
||||
console.log('Response from backend:', res);
|
||||
agencyName.value = res.agency_name;
|
||||
agencyLogo.value = res.agency_logo;
|
||||
agencyPhone.value = res.agency_phone;
|
||||
complexes.value = res.list;
|
||||
totalRecords.value = res.total;
|
||||
allPages.value = res.allPages;
|
||||
|
||||
const containers = document.getElementsByTagName('main');
|
||||
|
||||
if (containers) {
|
||||
containers[0].scrollTo({ top: 0, behavior: 'smooth'})
|
||||
}
|
||||
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.log(error);
|
||||
})
|
||||
.finally(() => {
|
||||
loaders.list = false
|
||||
});
|
||||
console.log(complexes.value ,"res.listres.listres.listres.listres.listres.list");
|
||||
}
|
||||
|
||||
async function getArchivedListComplex(withPage = true){
|
||||
loaders.archivedList = true
|
||||
if (!withPage) activePage.value = 1
|
||||
api.searchComplexes(
|
||||
{
|
||||
...complex_filter.value,
|
||||
address: complex_filter.value.address.trim(),
|
||||
is_archive: true
|
||||
},
|
||||
activePage.value,
|
||||
!route.meta.isViewPage
|
||||
)
|
||||
.then((response) => {
|
||||
var res = response.data;
|
||||
agencyName.value = res.agency_name;
|
||||
agencyLogo.value = res.agency_logo;
|
||||
agencyPhone.value = res.agency_phone;
|
||||
archivedComplexes.value = res.list;
|
||||
archivedTotalRecords.value = res.total;
|
||||
archivedAllPages.value = res.allPages;
|
||||
|
||||
const containers = document.getElementsByTagName('main');
|
||||
|
||||
if (containers) {
|
||||
containers[0].scrollTo({ top: 0, behavior: 'smooth'})
|
||||
}
|
||||
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.log(error);
|
||||
})
|
||||
.finally(() => {
|
||||
loaders.archivedList = false
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
activeType,
|
||||
types,
|
||||
changeActiveType,
|
||||
activePage,
|
||||
getListComplex,
|
||||
getArchivedListComplex,
|
||||
archivedAllPages,
|
||||
archivedTotalRecords,
|
||||
archivedComplexes,
|
||||
rowsComplex,
|
||||
allPages,
|
||||
complex_filter,
|
||||
complexes,
|
||||
load,
|
||||
loaders,
|
||||
archivedListLoading: loaders.archivedList,
|
||||
archiveLoad,
|
||||
totalRecords,
|
||||
activeComplex,
|
||||
setActiveComplex,
|
||||
clearFilters,
|
||||
agencyName,
|
||||
agencyLogo,
|
||||
agencyPhone
|
||||
};
|
||||
});
|
||||
181
src/app/store/fields.js
Normal file
181
src/app/store/fields.js
Normal file
@ -0,0 +1,181 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { onMounted, reactive, ref, watch } from "vue";
|
||||
import api from '@/shared/api'
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
export const useFieldsStore = defineStore("fields", () => {
|
||||
const route = useRoute()
|
||||
const decorations = reactive([])
|
||||
const complexes_types = reactive([])
|
||||
const complex_suggestions = reactive([])
|
||||
const countries = reactive([])
|
||||
const metros = reactive([])
|
||||
const windowsPlacements = reactive([])
|
||||
const rf_regions = reactive([])
|
||||
const rayons = reactive([])
|
||||
const complex_status = reactive([])
|
||||
const currentRegionInfo = reactive({
|
||||
region_id: null,
|
||||
region_id_user: null,
|
||||
kladr: null,
|
||||
label: null
|
||||
})
|
||||
const regionsInfo = reactive({
|
||||
regions: [],
|
||||
regions_list: {}
|
||||
})
|
||||
setTimeout(() => {
|
||||
const colors = JSON.parse(localStorage.getItem('colors'))
|
||||
const resColor = colors.color_reserved
|
||||
const freeColor = colors.color_free
|
||||
|
||||
complex_status.value = [
|
||||
{
|
||||
name: 'Свободные',
|
||||
status: 'available',
|
||||
color: freeColor,
|
||||
},
|
||||
{
|
||||
name: 'Забронированные',
|
||||
status: 'reservation',
|
||||
color: resColor,
|
||||
},
|
||||
{
|
||||
name: 'Проданные',
|
||||
status: 'sold',
|
||||
color: '#757575',
|
||||
},
|
||||
{
|
||||
name: 'Недоступные',
|
||||
status: 'not_for_sale',
|
||||
color: '#dedede',
|
||||
},
|
||||
]
|
||||
|
||||
console.log(complex_status, 'СТАТУСЫ СПУСТЯ СЕКУНДУ')
|
||||
}, 1000)
|
||||
|
||||
const builders = reactive({
|
||||
list: [],
|
||||
options: []
|
||||
})
|
||||
|
||||
// const fetchComplexStatuses = () => {
|
||||
// api.getComplexStatus().then((response) => {
|
||||
// if (response.data) {
|
||||
// complex_status.push(...response.data)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
const refetchDecorationsList = () => {
|
||||
api.getDecorations()
|
||||
.then((response) => {
|
||||
decorations.splice(0)
|
||||
decorations.push(...response.data);
|
||||
})
|
||||
}
|
||||
|
||||
const fetchComplexSuggestions = (isViewPage) => api.getComplexSuggestions(isViewPage)
|
||||
.then((response) => {
|
||||
complex_suggestions.push(...response.data.list)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
api.getDecorations()
|
||||
.then((response) => {
|
||||
decorations.push(...response.data);
|
||||
})
|
||||
|
||||
api.getComplexTypes()
|
||||
.then((response) => {
|
||||
complexes_types.push(...response.data)
|
||||
})
|
||||
|
||||
// api.getCountry()
|
||||
// .then((response) => {
|
||||
// // console.log(response, 'responseresponseresponseresponseresponse');
|
||||
//
|
||||
// countries.push(...response.data?.country)
|
||||
// })
|
||||
|
||||
// api.getRFRegions()
|
||||
// .then((response) => {
|
||||
// const currentRegion = response.data.regions.find(region => region.code == response.data.region_id_user)
|
||||
//
|
||||
// if (currentRegion) {
|
||||
// rayons.push(...currentRegion.rayons)
|
||||
// }
|
||||
//
|
||||
// api.getMetro(response.data.region_id_user).then(metrosResponse => {
|
||||
// metros.push(...metrosResponse.data)
|
||||
// })
|
||||
//
|
||||
// currentRegionInfo.region_id = parseInt(response.data.region_id_user)
|
||||
// currentRegionInfo.region_id_user = parseInt(response.data.region_id_user);
|
||||
//
|
||||
// regionsInfo.regions.push(...response.data.regions)
|
||||
// regionsInfo.regions_list = response.data.regions_list
|
||||
//
|
||||
// currentRegionInfo.kladr = regionsInfo.regions_list[currentRegionInfo.region_id].kladr
|
||||
// currentRegionInfo.label = regionsInfo.regions_list[currentRegionInfo.region_id].label
|
||||
// })
|
||||
|
||||
api.getWindowsPlacements()
|
||||
.then((response) => {
|
||||
windowsPlacements.push(...response.data)
|
||||
})
|
||||
|
||||
// api.getBuilders()
|
||||
// .then((response) => {
|
||||
// builders.list = response.data.list
|
||||
// builders.options.push(...response.data?.options)
|
||||
// })
|
||||
|
||||
|
||||
fetchComplexSuggestions(!route.meta.isViewPage)
|
||||
|
||||
// fetchComplexStatuses()
|
||||
})
|
||||
|
||||
const changeRegionInfoRegionId = (payload) => {
|
||||
currentRegionInfo.region_id = payload
|
||||
|
||||
api.getMetro(currentRegionInfo.region_id)
|
||||
.then((response) => {
|
||||
metros.splice(0)
|
||||
})
|
||||
}
|
||||
|
||||
const metroListChanged = (region_id) => {
|
||||
api.getMetro(region_id)
|
||||
.then((response) => {
|
||||
metros.splice(0)
|
||||
metros.push(...response.data)
|
||||
})
|
||||
}
|
||||
|
||||
watch(() => route.meta.isViewPage, (newValue, prevValue) => {
|
||||
if (newValue !== prevValue) {
|
||||
fetchComplexSuggestions(!newValue)
|
||||
}
|
||||
}, { deep: true })
|
||||
|
||||
return {
|
||||
decorations,
|
||||
complexes_types,
|
||||
countries,
|
||||
rf_regions,
|
||||
rayons,
|
||||
metros,
|
||||
currentRegionInfo,
|
||||
regionsInfo,
|
||||
windowsPlacements,
|
||||
complex_suggestions,
|
||||
changeRegionInfoRegionId,
|
||||
metroListChanged,
|
||||
builders,
|
||||
complex_status,
|
||||
refetchDecorationsList
|
||||
};
|
||||
});
|
||||
28
src/app/store/house.js
Normal file
28
src/app/store/house.js
Normal file
@ -0,0 +1,28 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
import api from '@/shared/api'
|
||||
|
||||
export const useHouseStore = defineStore("house", () => {
|
||||
const house = ref({});
|
||||
const complex = ref({'dop':{}, 'metro':{}});
|
||||
|
||||
const get_house = async (house_id, includes = []) => {
|
||||
return api.getHouseOne(house_id, includes)
|
||||
.then((response) => {
|
||||
const res = response.data;
|
||||
if(res.house) house.value = res.house;
|
||||
if(res.complex) complex.value = res.complex;
|
||||
|
||||
return response
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
house,
|
||||
complex,
|
||||
get_house
|
||||
};
|
||||
});
|
||||
28
src/app/store/navigate.js
Normal file
28
src/app/store/navigate.js
Normal file
@ -0,0 +1,28 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
export const useNavigateStore = defineStore("navigate", () => {
|
||||
const pages = ref([]);
|
||||
const route = useRoute();
|
||||
|
||||
const setPages = (newPages) => {
|
||||
const token = route.params.token;
|
||||
|
||||
pages.value = newPages.map(page => {
|
||||
// Найдём часть после `/view`, чтобы не дублировать
|
||||
const viewIndex = page.path.indexOf('/view');
|
||||
const suffix = viewIndex !== -1 ? page.path.slice(viewIndex + 5) : ''; // +5 чтобы пропустить "/view"
|
||||
|
||||
return {
|
||||
...page,
|
||||
path: `/${token}/view${suffix}`
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
pages,
|
||||
setPages
|
||||
};
|
||||
});
|
||||
10
src/app/styles/_mixins.scss
Normal file
10
src/app/styles/_mixins.scss
Normal file
@ -0,0 +1,10 @@
|
||||
@mixin font-face($font-family, $url, $weight, $style) {
|
||||
@font-face {
|
||||
font-family: "#{$font-family}";
|
||||
src: url("@/shared/assets/fonts/#{$url}.woff") format("woff"),
|
||||
url("@/shared/assets/fonts/#{$url}.woff2") format("woff2");
|
||||
font-weight: #{$weight};
|
||||
font-display: swap;
|
||||
font-style: $style;
|
||||
}
|
||||
}
|
||||
9
src/app/styles/function.scss
Normal file
9
src/app/styles/function.scss
Normal file
@ -0,0 +1,9 @@
|
||||
@use "sass:math";
|
||||
|
||||
@function vw($value, $base: 1440) {
|
||||
@return math.div($value, $base) * 100vw;
|
||||
}
|
||||
|
||||
@function vmin($value, $base: 320) {
|
||||
@return math.div($value, $base) * 100vmin;
|
||||
}
|
||||
214
src/app/styles/main.scss
Normal file
214
src/app/styles/main.scss
Normal file
@ -0,0 +1,214 @@
|
||||
@import "./mixins";
|
||||
|
||||
@import "primevue/resources/themes/lara-light-indigo/theme.css";
|
||||
// @import url("https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700;900&display=swap");
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
//@layer vendor, custom;
|
||||
|
||||
:root {
|
||||
--main-color: #ED4B00;
|
||||
--reservation-color: #f2b720;
|
||||
--available-color: #4caf50;
|
||||
}
|
||||
|
||||
.main-bg {
|
||||
background-color: var(--main-color);
|
||||
}
|
||||
|
||||
@include font-face("Lato", "Lato-Black", 900, "normal");
|
||||
@include font-face("Lato", "Lato-ExtraBold", 800, "normal");
|
||||
@include font-face("Lato", "Lato-Bold", 700, "normal");
|
||||
@include font-face("Lato", "Lato-SemiBold", 600, "normal");
|
||||
@include font-face("Lato", "Lato-Medium", 500, "normal");
|
||||
@include font-face("Lato", "Lato-Regular", 400, "normal");
|
||||
@include font-face("Lato", "Lato-Light", 300, "normal");
|
||||
@include font-face("Lato", "Lato-ExtraLight", 200, "normal");
|
||||
@include font-face("Lato", "Lato-Thin", 100, "normal");
|
||||
|
||||
html , body {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.modelWindow {
|
||||
z-index: 1000 !important;
|
||||
}
|
||||
|
||||
|
||||
img, video {
|
||||
display: unset;
|
||||
vertical-align: unset;
|
||||
}
|
||||
|
||||
.p-menuitem-link {
|
||||
border: none !important;
|
||||
}
|
||||
.p-link:focus {
|
||||
box-shadow: unset !important;
|
||||
}
|
||||
|
||||
.p-autocomplete input {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.p-autocomplete input:focus, .p-autocomplete input:hover {
|
||||
box-shadow: unset !important;
|
||||
border-color: rgb(76 175 80 / 1) !important;;
|
||||
}
|
||||
|
||||
.p-dropdown:not(.p-disabled):hover {
|
||||
border-color: rgb(76 175 80 / 1);
|
||||
}
|
||||
|
||||
.p-dropdown:not(.p-disabled):focus {
|
||||
border-color: rgb(76 175 80 / 1) !important;
|
||||
}
|
||||
.p-dropdown{
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.p-dropdown:not(.p-disabled).p-focus {
|
||||
outline: 0 none;
|
||||
outline-offset: 0;
|
||||
box-shadow: 0 0 0 0.2rem #C7D2FE;
|
||||
border-color: rgb(76 175 80 / 1) !important;
|
||||
}
|
||||
|
||||
.loader-div{
|
||||
position: fixed;
|
||||
width: 105vw ;
|
||||
height: 100vh;
|
||||
z-index: 2000000;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background-color: rgb(255, 255, 255);
|
||||
svg{
|
||||
fill: #07a860 !important;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width :768px) {
|
||||
|
||||
.loader-div{
|
||||
position: fixed;
|
||||
width: 100vw ;
|
||||
height: 100vh;
|
||||
z-index: 2000000;
|
||||
background-color: rgb(255, 255, 255);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
top: 0;
|
||||
transform: translateX(150px);
|
||||
}
|
||||
}
|
||||
|
||||
.custom-header {
|
||||
display: flex !important;
|
||||
|
||||
.p-checkbox-box, input {
|
||||
box-shadow: unset !important;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 5px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.loadingScreen {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
|
||||
&__content {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #dedede;
|
||||
background: var(--linear,
|
||||
linear-gradient(180deg, #fefefe 0%, #f6f6f6 100%));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 30px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 21px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
&__text {
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
@apply text-grey-900;
|
||||
}
|
||||
}
|
||||
|
||||
.p-paginator .p-paginator-pages .p-paginator-page.p-highlight {
|
||||
color: #FFFFFF!important;
|
||||
background: var(--main-color)!important;
|
||||
}
|
||||
|
||||
.p-dropdown-panel {
|
||||
max-width: 435px;
|
||||
}
|
||||
.p-dropdown-panel .p-dropdown-items {
|
||||
padding: 0!important;
|
||||
}
|
||||
|
||||
.p-dropdown-items-wrapper {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.primary-btn {
|
||||
background-color: var(--main-color);
|
||||
width: 207px;
|
||||
height: 37px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: white;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
border: 1px solid #DDDDDD;
|
||||
}
|
||||
|
||||
/* Уменьшаем ширину скроллбара */
|
||||
::-webkit-scrollbar {
|
||||
width: 4px; /* Тонкий скроллбар */
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
/* Делаем трек (фон под скроллом) */
|
||||
::-webkit-scrollbar-track {
|
||||
background: #f0f0f0; /* Цвет фона скроллбара */
|
||||
// border-radius: 10px;
|
||||
}
|
||||
|
||||
/* Сама "ручка" (ползунок) */
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--main-color); /* Цвет ползунка */
|
||||
// border-radius: 10px; /* Закругляем края */
|
||||
}
|
||||
|
||||
/* При наведении делаем ползунок темнее */
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--main-color);
|
||||
}
|
||||
// html {
|
||||
// overflow-y: scroll; /* Всегда оставляет место для скролла */
|
||||
// }
|
||||
61
src/app/types/complex.ts
Normal file
61
src/app/types/complex.ts
Normal file
@ -0,0 +1,61 @@
|
||||
export type TTypeGrid = Record<string, string>;
|
||||
|
||||
export type TStatus = Record<string, string>;
|
||||
|
||||
export type TTypeObject = Record<string, string>;
|
||||
|
||||
export type TCountry = Record<string, string>;
|
||||
|
||||
export type TSubject = Record<string, string>;
|
||||
|
||||
export type TMetro = Record<string, string>;
|
||||
|
||||
export type TTypeToMetro = Record<string, string>;
|
||||
|
||||
export type TVideo = Record<string, string>;
|
||||
|
||||
export type TUniqueOffer = {
|
||||
title: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export type TComplex = {
|
||||
_id?: string;
|
||||
idDomClick: string;
|
||||
type: TTypeObject;
|
||||
country: TCountry;
|
||||
region: string;
|
||||
adress: string;
|
||||
metro: TMetro;
|
||||
timeToMetro: {
|
||||
time: number;
|
||||
typeToMetro: TTypeToMetro;
|
||||
};
|
||||
title: string;
|
||||
mainDescription: {
|
||||
title: string;
|
||||
text: string;
|
||||
};
|
||||
secondaryDescription?: {
|
||||
title: string;
|
||||
text: string;
|
||||
};
|
||||
video?: string;
|
||||
typeVideo?: TVideo;
|
||||
uniqueOffer?: TUniqueOffer[] | string[];
|
||||
image: string;
|
||||
stocks?: string;
|
||||
queue?: string;
|
||||
};
|
||||
|
||||
export type TComplexNew = {
|
||||
_id?: string;
|
||||
type: string;
|
||||
idDomClick: string;
|
||||
country: string;
|
||||
region: string;
|
||||
address: string;
|
||||
metro: string;
|
||||
title: string;
|
||||
uniqueOffer: TUniqueOffer[];
|
||||
};
|
||||
88
src/app/types/facadeCanvas.js
Normal file
88
src/app/types/facadeCanvas.js
Normal file
@ -0,0 +1,88 @@
|
||||
import Collection from "ol/Collection";
|
||||
import Feature from "ol/Feature";
|
||||
import Map from "ol/Map";
|
||||
import Overlay from "ol/Overlay";
|
||||
import View from "ol/View";
|
||||
import { Coordinate } from "ol/coordinate";
|
||||
import { Polygon } from "ol/geom";
|
||||
import Draw from "ol/interaction/Draw";
|
||||
import Modify from "ol/interaction/Modify";
|
||||
import Select from "ol/interaction/Select";
|
||||
import Snap from "ol/interaction/Snap";
|
||||
import ImageLayer from "ol/layer/Image";
|
||||
import VectorLayer from "ol/layer/Vector";
|
||||
import { Projection } from "ol/proj";
|
||||
import Static from "ol/source/ImageStatic";
|
||||
import VectorSource from "ol/source/Vector";
|
||||
import Style from "ol/style/Style";
|
||||
|
||||
/*TDraw = {
|
||||
interaction: Draw;
|
||||
sketchFeature_: null | Feature<Polygon>;
|
||||
setActive: (active: boolean) => void;
|
||||
coordToString: (coordXY: Coordinate) => string;
|
||||
isOnePoint: (coords: Coordinate[]) => boolean;
|
||||
getFeature: () => Feature;
|
||||
isEmpty: () => boolean;
|
||||
};
|
||||
|
||||
export type TSelect = {
|
||||
interaction: Select;
|
||||
setEvents: () => void;
|
||||
setActive: (active: boolean) => void;
|
||||
clear: () => void;
|
||||
};
|
||||
|
||||
export type TModify = {
|
||||
interaction: Modify;
|
||||
setActive: (active: boolean) => void;
|
||||
};
|
||||
|
||||
export type Facades = {
|
||||
id: string;
|
||||
floor_position: string;
|
||||
points: Coordinate[];
|
||||
};
|
||||
|
||||
export type TOlMap = {
|
||||
map: Map;
|
||||
features: Collection<Feature<Polygon>>;
|
||||
sourceVector: VectorSource;
|
||||
draw: TDraw;
|
||||
modify: TModify;
|
||||
select: TSelect;
|
||||
snap: Snap;
|
||||
imageElement: HTMLImageElement;
|
||||
imageLayer: ImageLayer<Static>;
|
||||
target: string;
|
||||
vector: VectorLayer<VectorSource>;
|
||||
overlay: Overlay;
|
||||
color: string;
|
||||
opacity: number;
|
||||
hovered: boolean;
|
||||
selectFeature: Feature<Polygon>;
|
||||
onImageLoad: () => void;
|
||||
createMap: (imageElement: HTMLImageElement, target: string) => void;
|
||||
addInteractionsToMap: (map: Map, vector: VectorLayer<VectorSource>) => void;
|
||||
setColor: (color: string, opacity: number) => void;
|
||||
hexToRgba: (hex: string, opacity?: number) => string;
|
||||
addEvents: () => void;
|
||||
removeStyleFromFeatures: (currentFeature?: Feature) => void;
|
||||
createVector: () => VectorLayer<VectorSource>;
|
||||
createSourceVector: () => VectorSource;
|
||||
createStyle: () => Style;
|
||||
createView: (
|
||||
imageElement: HTMLImageElement | HTMLElement,
|
||||
target?: HTMLElement
|
||||
) => View;
|
||||
createProjection: (
|
||||
imageElement: HTMLImageElement | HTMLElement,
|
||||
target?: HTMLElement
|
||||
) => Projection;
|
||||
createImageLayer: (
|
||||
imageElement: HTMLImageElement,
|
||||
view?: View
|
||||
) => ImageLayer<Static>;
|
||||
createImageStatic: (imageElement: HTMLImageElement, view?: View) => Static;
|
||||
createOverlay: (container: HTMLElement) => Overlay;
|
||||
};*/
|
||||
84
src/app/types/facadeViewCanvas.js
Normal file
84
src/app/types/facadeViewCanvas.js
Normal file
@ -0,0 +1,84 @@
|
||||
import View from "ol/View";
|
||||
import Collection from "ol/Collection";
|
||||
import Map from "ol/Map";
|
||||
import ImageLayer from "ol/layer/Image";
|
||||
import VectorLayer from "ol/layer/Vector";
|
||||
import { Projection } from "ol/proj";
|
||||
import Static from "ol/source/ImageStatic";
|
||||
import VectorSource from "ol/source/Vector";
|
||||
import Feature from "ol/Feature";
|
||||
import { Polygon } from "ol/geom";
|
||||
import Style from "ol/style/Style";
|
||||
import Overlay from "ol/Overlay";
|
||||
import Modify from "ol/interaction/Modify";
|
||||
import Select from "ol/interaction/Select";
|
||||
import Draw from "ol/interaction/Draw";
|
||||
import { Coordinate } from "ol/coordinate";
|
||||
|
||||
/*export type TOlMap = {
|
||||
map: Map;
|
||||
target: string;
|
||||
imageElement: HTMLImageElement;
|
||||
imageLayer: ImageLayer<Static>;
|
||||
vector: VectorLayer<VectorSource>;
|
||||
sourceVector: VectorSource;
|
||||
features: Collection<Feature<Polygon>>;
|
||||
overlay: Overlay;
|
||||
opacity: number;
|
||||
color: string;
|
||||
modify: TModify;
|
||||
select: TSelect;
|
||||
hovered: boolean;
|
||||
selectFeature: Feature<Polygon>;
|
||||
onImageLoad: () => void;
|
||||
createMap: (imageElement: HTMLImageElement, target: string) => void;
|
||||
createImageLayer: (imageElement: HTMLImageElement) => ImageLayer<Static>;
|
||||
createImageStatic: (imageElement: HTMLImageElement) => Static;
|
||||
createView: (
|
||||
imageElement: HTMLImageElement,
|
||||
targetElement: HTMLElement
|
||||
) => View;
|
||||
createProjection: (imageElement: HTMLImageElement) => Projection;
|
||||
createVector: () => VectorLayer<VectorSource>;
|
||||
createSourceVector: () => VectorSource;
|
||||
createStyle: () => Style;
|
||||
createOverlay: (container: HTMLElement) => Overlay;
|
||||
addInteractionsToMap: (map: Map, vector: VectorLayer<VectorSource>) => void;
|
||||
addEvents: () => void;
|
||||
hexToRgba: (color: string, opacity?: number) => string;
|
||||
removeStyleFromFeatures: (currentFeature?: Feature) => void;
|
||||
resize: () => void;
|
||||
setColor: (color: string, opacity?: number) => void;
|
||||
};
|
||||
|
||||
export type TModify = {
|
||||
interaction: Modify;
|
||||
setActive: (active: boolean) => void;
|
||||
};
|
||||
|
||||
export type TSelect = {
|
||||
interaction: Select;
|
||||
setEvents: () => void;
|
||||
setActive: (active: boolean) => void;
|
||||
clear: () => void;
|
||||
};
|
||||
|
||||
export type TDraw = {
|
||||
interaction: Draw;
|
||||
setActive: (active: boolean) => void;
|
||||
coordToString: (coordXY: Coordinate) => string;
|
||||
isOnePoint: (coords: Coordinate[]) => boolean;
|
||||
getFeature: () => Feature;
|
||||
};
|
||||
|
||||
export type Area = {
|
||||
id: string;
|
||||
points: Coordinate[];
|
||||
section?: string;
|
||||
floor?: string;
|
||||
};
|
||||
|
||||
export interface MyFeature<T extends Polygon> extends Feature<T> {
|
||||
floor: string;
|
||||
section: string;
|
||||
}*/
|
||||
34
src/app/types/facades.js
Normal file
34
src/app/types/facades.js
Normal file
@ -0,0 +1,34 @@
|
||||
import { Coordinate } from "ol/coordinate";
|
||||
|
||||
/*export interface IFacade {
|
||||
readonly id: string;
|
||||
image: string | HTMLImageElement;
|
||||
title: string;
|
||||
order: number;
|
||||
isPublish: boolean;
|
||||
floors: FloorWrapper;
|
||||
}
|
||||
|
||||
export type FloorWrapper = {
|
||||
countFloors: number;
|
||||
items: Floor[];
|
||||
};
|
||||
|
||||
export type Floor = {
|
||||
readonly id: string;
|
||||
points: Point[];
|
||||
numberFloor: number;
|
||||
section: number;
|
||||
};
|
||||
|
||||
export type Point = {
|
||||
x: number;
|
||||
y: number;
|
||||
};*/
|
||||
|
||||
/*type['Area'] = {
|
||||
id,
|
||||
points: [],
|
||||
section: String,
|
||||
floor: String,
|
||||
};*/
|
||||
20
src/components/BurgerIcon.vue
Normal file
20
src/components/BurgerIcon.vue
Normal file
@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40" fill="none">
|
||||
<rect x="0.5" y="0.5" width="39" height="39" rx="4.5" fill="white"/>
|
||||
<rect x="0.5" y="0.5" width="39" height="39" rx="4.5" :stroke="computedColor"/>
|
||||
<rect x="8" y="13" width="24" height="2" rx="1" :fill="computedColor"/>
|
||||
<rect x="8" y="19" width="24" height="2" rx="1" :fill="computedColor"/>
|
||||
<rect x="8" y="25" width="24" height="2" rx="1" :fill="computedColor"/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Burger',
|
||||
computed: {
|
||||
computedColor() {
|
||||
return this.mainColor || document.documentElement.style.getPropertyValue("--main-color") || '#ED4B00';
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
23
src/components/CanvasNext.vue
Normal file
23
src/components/CanvasNext.vue
Normal file
@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="75"
|
||||
height="75"
|
||||
viewBox="0 0 75 75"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M14.2684 0.522583H60.7878C64.3852 0.526004 67.8344 1.9566 70.3782 4.50038C72.9219 7.04416 74.3525 10.4933 74.356 14.0907V60.6101C74.3525 64.2076 72.9219 67.6567 70.3782 70.2005C67.8344 72.7443 64.3852 74.1749 60.7878 74.1783H14.2684C10.671 74.1749 7.22183 72.7443 4.67805 70.2005C2.13427 67.6567 0.703667 64.2076 0.700249 60.6101V14.0907C0.703667 10.4933 2.13427 7.04416 4.67805 4.50038C7.22183 1.9566 10.671 0.526004 14.2684 0.522583ZM12.5368 18.8331C12.5368 19.0045 12.6049 19.1688 12.7261 19.29C12.8473 19.4112 13.0116 19.4792 13.1829 19.4792C22.9345 19.5009 32.3052 23.2677 39.3587 30.0013C46.4122 36.735 50.6095 45.9209 51.0833 55.661L43.4335 47.9982C43.3121 47.8812 43.1498 47.8163 42.9812 47.8173C42.8087 47.8156 42.6421 47.8804 42.516 47.9982L39.7249 50.8023C39.6065 50.9231 39.5402 51.0854 39.5402 51.2546C39.5402 51.4237 39.6065 51.5861 39.7249 51.7068L53.293 65.3138C53.4175 65.43 53.5814 65.4947 53.7517 65.4947C53.9221 65.4947 54.086 65.43 54.2105 65.3138L67.7786 51.7327C67.8404 51.6747 67.8897 51.6046 67.9234 51.5268C67.9571 51.449 67.9744 51.3652 67.9744 51.2804C67.9744 51.1956 67.9571 51.1118 67.9234 51.034C67.8897 50.9562 67.8404 50.8862 67.7786 50.8281L64.9875 48.0241C64.8645 47.9049 64.7 47.8382 64.5287 47.8382C64.3575 47.8382 64.193 47.9049 64.07 48.0241L56.3168 55.7773C55.8668 44.6261 51.1249 34.0802 43.082 26.3431C35.0391 18.606 24.3173 14.2763 13.1571 14.2587C12.9857 14.2587 12.8214 14.3268 12.7002 14.448C12.5791 14.5691 12.511 14.7335 12.511 14.9048L12.5368 18.8331Z"
|
||||
fill="#F3F3F3"
|
||||
/>
|
||||
<path
|
||||
d="M12.5368 18.8331C12.5368 19.0045 12.6049 19.1688 12.7261 19.29C12.8473 19.4112 13.0116 19.4792 13.1829 19.4792C22.9345 19.5009 32.3052 23.2677 39.3587 30.0013C46.4122 36.735 50.6095 45.9209 51.0833 55.661L43.4335 47.9982C43.3121 47.8812 43.1498 47.8163 42.9812 47.8173C42.8087 47.8156 42.6421 47.8804 42.516 47.9982L39.7249 50.8023C39.6065 50.9231 39.5402 51.0854 39.5402 51.2546C39.5402 51.4237 39.6065 51.5861 39.7249 51.7068L53.293 65.3138C53.4175 65.43 53.5814 65.4947 53.7517 65.4947C53.9221 65.4947 54.086 65.43 54.2105 65.3138L67.7786 51.7327C67.8404 51.6747 67.8897 51.6046 67.9234 51.5268C67.9571 51.449 67.9744 51.3652 67.9744 51.2804C67.9744 51.1956 67.9571 51.1118 67.9234 51.034C67.8897 50.9562 67.8404 50.8862 67.7786 50.8281L64.9875 48.0241C64.8645 47.9049 64.7 47.8382 64.5287 47.8382C64.3575 47.8382 64.193 47.9049 64.07 48.0241L56.3168 55.7773C55.8668 44.6261 51.1249 34.0802 43.082 26.3431C35.0391 18.606 24.3173 14.2763 13.1571 14.2587C12.9857 14.2587 12.8214 14.3268 12.7002 14.448C12.5791 14.5691 12.511 14.7335 12.511 14.9048L12.5368 18.8331Z"
|
||||
:fill="mainColor"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const colors = JSON.parse(localStorage.getItem("colors"));
|
||||
const mainColor = colors.frame_color;
|
||||
</script>
|
||||
23
src/components/CanvasPrev.vue
Normal file
23
src/components/CanvasPrev.vue
Normal file
@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="74"
|
||||
height="75"
|
||||
viewBox="0 0 74 75"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M60.0876 0.522583H13.5682C9.97071 0.526004 6.52158 1.9566 3.9778 4.50038C1.43402 7.04416 0.00342126 10.4933 0 14.0907V60.6101C0.00342126 64.2076 1.43402 67.6567 3.9778 70.2005C6.52158 72.7443 9.97071 74.1749 13.5682 74.1783H60.0876C63.685 74.1749 67.1341 72.7443 69.6779 70.2005C72.2217 67.6567 73.6523 64.2076 73.6557 60.6101V14.0907C73.6523 10.4933 72.2217 7.04416 69.6779 4.50038C67.1341 1.9566 63.685 0.526004 60.0876 0.522583ZM61.8191 18.8331C61.8191 19.0045 61.751 19.1688 61.6299 19.29C61.5087 19.4112 61.3444 19.4792 61.173 19.4792C51.4214 19.5009 42.0507 23.2677 34.9972 30.0013C27.9437 36.735 23.7464 45.9209 23.2726 55.661L30.9225 47.9982C31.0439 47.8812 31.2062 47.8163 31.3748 47.8173C31.5473 47.8156 31.7139 47.8804 31.8399 47.9982L34.6311 50.8023C34.7495 50.9231 34.8158 51.0854 34.8158 51.2546C34.8158 51.4237 34.7495 51.5861 34.6311 51.7068L21.0629 65.3138C20.9385 65.43 20.7745 65.4947 20.6042 65.4947C20.4339 65.4947 20.2699 65.43 20.1455 65.3138L6.57733 51.7327C6.51552 51.6747 6.46626 51.6046 6.43258 51.5268C6.39891 51.449 6.38153 51.3652 6.38153 51.2804C6.38153 51.1956 6.39891 51.1118 6.43258 51.034C6.46626 50.9562 6.51552 50.8861 6.57733 50.8281L9.36849 48.0241C9.49144 47.9048 9.65597 47.8382 9.82722 47.8382C9.99847 47.8382 10.163 47.9048 10.286 48.0241L18.0392 55.7773C18.4892 44.6261 23.231 34.0802 31.274 26.3431C39.3169 18.606 50.0386 14.2763 61.1989 14.2587C61.3702 14.2587 61.5345 14.3268 61.6557 14.448C61.7769 14.5691 61.845 14.7335 61.845 14.9048L61.8191 18.8331Z"
|
||||
fill="#F3F3F3"
|
||||
/>
|
||||
<path
|
||||
d="M61.8191 18.8331C61.8191 19.0045 61.751 19.1688 61.6299 19.29C61.5087 19.4112 61.3444 19.4792 61.173 19.4792C51.4214 19.5009 42.0507 23.2677 34.9972 30.0013C27.9437 36.735 23.7464 45.9209 23.2726 55.661L30.9225 47.9982C31.0439 47.8812 31.2062 47.8163 31.3748 47.8173C31.5473 47.8156 31.7139 47.8804 31.8399 47.9982L34.6311 50.8023C34.7495 50.9231 34.8158 51.0854 34.8158 51.2546C34.8158 51.4237 34.7495 51.5861 34.6311 51.7068L21.0629 65.3138C20.9385 65.43 20.7745 65.4947 20.6042 65.4947C20.4339 65.4947 20.2699 65.43 20.1455 65.3138L6.57733 51.7327C6.51552 51.6747 6.46626 51.6046 6.43258 51.5268C6.39891 51.449 6.38153 51.3652 6.38153 51.2804C6.38153 51.1956 6.39891 51.1118 6.43258 51.034C6.46626 50.9562 6.51552 50.8861 6.57733 50.8281L9.36849 48.0241C9.49144 47.9048 9.65597 47.8382 9.82722 47.8382C9.99847 47.8382 10.163 47.9048 10.286 48.0241L18.0392 55.7773C18.4892 44.6261 23.231 34.0802 31.274 26.3431C39.3169 18.606 50.0386 14.2763 61.1989 14.2587C61.3702 14.2587 61.5345 14.3268 61.6557 14.448C61.7769 14.5691 61.845 14.7335 61.845 14.9048L61.8191 18.8331Z"
|
||||
:fill="mainColor"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const colors = JSON.parse(localStorage.getItem('colors'))
|
||||
const mainColor = colors.frame_color
|
||||
</script>
|
||||
59
src/components/HelloWorld.vue
Normal file
59
src/components/HelloWorld.vue
Normal file
@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div class="hello">
|
||||
<h1>{{ msg }}</h1>
|
||||
<p>
|
||||
For a guide and recipes on how to configure / customize this project,<br>
|
||||
check out the
|
||||
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
|
||||
</p>
|
||||
<h3>Installed CLI Plugins</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router" target="_blank" rel="noopener">router</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-vuex" target="_blank" rel="noopener">vuex</a></li>
|
||||
</ul>
|
||||
<h3>Essential Links</h3>
|
||||
<ul>
|
||||
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
|
||||
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
|
||||
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
|
||||
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
|
||||
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
|
||||
</ul>
|
||||
<h3>Ecosystem</h3>
|
||||
<ul>
|
||||
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
|
||||
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
|
||||
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
|
||||
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'HelloWorld',
|
||||
props: {
|
||||
msg: String
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
h3 {
|
||||
margin: 40px 0 0;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
}
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
</style>
|
||||
249
src/components/IframeApartmentType.vue
Normal file
249
src/components/IframeApartmentType.vue
Normal file
@ -0,0 +1,249 @@
|
||||
<template>
|
||||
<div class="apartment_type_item">
|
||||
<div class="founded_aparts">
|
||||
<div class="type">1к</div>
|
||||
<div class="count">7 квартир найдено</div>
|
||||
</div>
|
||||
<div class="apart_info">
|
||||
<div class="plane_img">
|
||||
<div class="hover-bg"></div>
|
||||
<img src="@/shared/assets/icons/zoom.svg" alt="" class="zoom" />
|
||||
<img
|
||||
src="https://img.dmclk.ru/vitrina/fe/1a/fe1a0ecc45398fc537df972f0544e42d546707dd.jpg"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<Dropdown
|
||||
v-model="selectedValue"
|
||||
:options="options"
|
||||
optionLabel="label"
|
||||
placeholder="Выберите квартиру"
|
||||
class="custom-dropdown"
|
||||
>
|
||||
<template #option="{ option }">
|
||||
<div class="option-wrapper">
|
||||
<div class="apart-info">
|
||||
<span
|
||||
class="apart-status"
|
||||
:style="{ backgroundColor: getBackgroundColor(option.status_type) }"
|
||||
></span>
|
||||
<div class="apart-price-wrapper">
|
||||
<span class="full-price"
|
||||
>{{ option.apartment_price }} руб.</span
|
||||
>
|
||||
<div class="squere-info">
|
||||
<span class="squere">{{ option.apartment_squere }} м2</span>
|
||||
<span class="dash"> - </span>
|
||||
<span class="price-squere"
|
||||
>{{ option.squere_price }} руб./м2</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="apart-additional">
|
||||
<span class="floor">{{ option.apartment_floor }} этаж</span>
|
||||
<span class="number">№{{ option.apartment_number }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Dropdown>
|
||||
<div class="primary-btn type_btn">Подробнее</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Dropdown from "primevue/dropdown";
|
||||
import { ref } from "vue";
|
||||
|
||||
|
||||
const colors = JSON.parse(localStorage.getItem('colors')) || {}
|
||||
|
||||
|
||||
const statusMap = {
|
||||
reservation: 'color_reserved',
|
||||
available: 'color_free',
|
||||
not_for_sale: 'not_for_sale',
|
||||
sold: 'sold',
|
||||
}
|
||||
|
||||
const getBackgroundColor = (statusType) => {
|
||||
|
||||
const colorKey = statusMap[statusType]
|
||||
return colors[colorKey]
|
||||
}
|
||||
|
||||
const options = [
|
||||
{
|
||||
label: "",
|
||||
value: "40",
|
||||
apartment_status: "free",
|
||||
apartment_price: "7360000₽",
|
||||
apartment_squere: "36,6",
|
||||
squere_price: "189637",
|
||||
apartment_floor: "10",
|
||||
apartment_number: "40",
|
||||
status_color: "#ED4B00",
|
||||
},
|
||||
{
|
||||
label: "",
|
||||
value: "41",
|
||||
apartment_status: "free",
|
||||
apartment_price: "8200000",
|
||||
apartment_squere: "40,2",
|
||||
squere_price: "204478",
|
||||
apartment_floor: "11",
|
||||
apartment_number: "41",
|
||||
status_color: "#2100B1",
|
||||
},
|
||||
{
|
||||
label: "",
|
||||
value: "42",
|
||||
apartment_status: "free",
|
||||
apartment_price: "9000000",
|
||||
apartment_squere: "45,1",
|
||||
squere_price: "199556",
|
||||
apartment_floor: "12",
|
||||
apartment_number: "42",
|
||||
status_color: "#2100B1",
|
||||
},
|
||||
];
|
||||
|
||||
const selectedValue = ref(null);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.apartment_type_item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 463px;
|
||||
max-height: 490px;
|
||||
border: 1px solid #dedede;
|
||||
border-radius: 6px;
|
||||
padding-bottom: 26px;
|
||||
& .founded_aparts {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
color: #757575;
|
||||
margin: 20px 0 0 20px;
|
||||
& .type {
|
||||
border: 1px solid #757575;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
padding: 3px 6px;
|
||||
}
|
||||
& .count {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
& .apart_info {
|
||||
padding: 0 40px;
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
& .plane_img {
|
||||
position: relative;
|
||||
|
||||
& img:hover {
|
||||
}
|
||||
|
||||
& .hover-bg {
|
||||
position: absolute;
|
||||
background-color: #fff;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: opacity 0.3s ease, visibility 0s 0.3s;
|
||||
}
|
||||
|
||||
& .zoom {
|
||||
display: none;
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
& .plane_img:hover {
|
||||
cursor: pointer;
|
||||
& .zoom {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
transform: translate(-50%, -50%);
|
||||
transition: opacity 0.3s ease, visibility 0s;
|
||||
}
|
||||
|
||||
& .hover-bg {
|
||||
opacity: 0.7;
|
||||
visibility: visible;
|
||||
transition: opacity 0.2s ease, visibility 0s;
|
||||
}
|
||||
}
|
||||
& .select {
|
||||
margin-top: 10px;
|
||||
}
|
||||
& .type_btn {
|
||||
align-self: center;
|
||||
width: 130px;
|
||||
height: 40px;
|
||||
color: #ffffff;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
line-height: 0;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.option-wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-left: 5px;
|
||||
& .apart-info {
|
||||
& .apart-status {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 18px;
|
||||
}
|
||||
& .apart-price-wrapper {
|
||||
& .full-price {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
}
|
||||
& .squere {
|
||||
font-size: 12px;
|
||||
color: #333333;
|
||||
}
|
||||
& .price-squere {
|
||||
font-size: 12px;
|
||||
color: #757575;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* & .apartment-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
} */
|
||||
& .apart-additional {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 12px;
|
||||
& .floor {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</style>
|
||||
177
src/components/IframeNavbar.vue
Normal file
177
src/components/IframeNavbar.vue
Normal file
@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<!-- <div v-if="apartmentsLoader" class="loader-div">
|
||||
<RocketSpinner/>
|
||||
</div> -->
|
||||
<div class="navbar-wrapper">
|
||||
<div class="close-btn" @click="closeIframeNavbar">
|
||||
<span class="close-icon">×</span>
|
||||
</div>
|
||||
<nav class="menu">
|
||||
<ul>
|
||||
<li v-for="item in filteredMenuItems" :key="item.text">
|
||||
<a class="menu-item" @click="changeViewMode(item.viewMode)">
|
||||
<img :src="item.icon" :alt="item.text" class="menu-icon" />
|
||||
<span class="menu-text">{{ item.text }}</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "IframeNavbar",
|
||||
props: {
|
||||
visibility: Object,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
screenWidth: window.innerWidth,
|
||||
menuItems: [
|
||||
{
|
||||
viewMode: {icon: "list", name: "Список", value: "list", visible: true},
|
||||
id: "all",
|
||||
text: "Все объекты",
|
||||
icon: require("@/shared/assets/icons/iframeNavIcons/all-objects.svg")
|
||||
},
|
||||
{
|
||||
viewMode: {icon: "facades", name: "Фасады", value: "facades", visible: true},
|
||||
id: "facades",
|
||||
text: "Фасады",
|
||||
icon: require("@/shared/assets/icons/iframeNavIcons/facades.svg")
|
||||
},
|
||||
{
|
||||
viewMode: {icon: "layouts", name: "Планировки", value: "layouts", visible: true},
|
||||
id: "plans",
|
||||
text: "Планировки",
|
||||
icon: require("@/shared/assets/icons/iframeNavIcons/plate.svg")
|
||||
},
|
||||
{
|
||||
viewMode: {icon: "tile", name: "Плитка", value: "tile", visible: true},
|
||||
id: "plate",
|
||||
text: "Плитка",
|
||||
icon: require("@/shared/assets/icons/iframeNavIcons/flat-planes.svg")
|
||||
},
|
||||
{
|
||||
viewMode: {icon: "grid", name: "Сетка", value: "grid", visible: true},
|
||||
id: "net",
|
||||
text: "Сетка",
|
||||
icon: require("@/shared/assets/icons/iframeNavIcons/tile.svg")
|
||||
},
|
||||
{
|
||||
viewMode: {icon: "floors", name: "Этажи", value: "floors", visible: true},
|
||||
id: "floors",
|
||||
text: "Этажи",
|
||||
icon: require("@/shared/assets/icons/iframeNavIcons/floors.svg")
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
filteredMenuItems() {
|
||||
return this.menuItems.filter(item => {
|
||||
const isVisible = this.visibility[item.id];
|
||||
const isFacadesHiddenOnSmallScreen = item.id === "facades" && this.screenWidth < 1210;
|
||||
return isVisible && !isFacadesHiddenOnSmallScreen;
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
changeViewMode(value) {
|
||||
this.$emit("view-mode-emit", value);
|
||||
this.$emit("update:viewType", value);
|
||||
this.$emit("closeIframeNavbar");
|
||||
},
|
||||
closeIframeNavbar() {
|
||||
this.$emit("closeIframeNavbar");
|
||||
},
|
||||
updateScreenWidth() {
|
||||
this.screenWidth = window.innerWidth;
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
window.addEventListener("resize", this.updateScreenWidth);
|
||||
},
|
||||
beforeUnmount() {
|
||||
window.removeEventListener("resize", this.updateScreenWidth);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss">
|
||||
.navbar-wrapper {
|
||||
position: relative;
|
||||
max-width: 300px;
|
||||
min-width: 300px;
|
||||
height: 100vh;
|
||||
background: url('@/shared/assets/images/skyscraper.svg');
|
||||
background-color: var(--main-color);
|
||||
background-position: bottom;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
.bg-svg {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
/* user-select: none; */
|
||||
}
|
||||
.menu {
|
||||
padding-top: 36px;
|
||||
display: flex;
|
||||
/* justify-content: center; */
|
||||
margin-left: 44px;
|
||||
}
|
||||
.menu-item {
|
||||
color: white;
|
||||
font-size: 26px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 40px;
|
||||
transition: opacity 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
.menu-item:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
.close-btn {
|
||||
display: none;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background-color: #ffffff;
|
||||
border-radius: 6px;
|
||||
/* display: flex; */
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: 16px;
|
||||
}
|
||||
.close-icon {
|
||||
font-size: 35px;
|
||||
color: var(--main-color);
|
||||
user-select: none;
|
||||
line-height: 0.03;
|
||||
}
|
||||
@media(max-width: 768px) {
|
||||
.close-btn {
|
||||
display: flex;
|
||||
}
|
||||
.menu {
|
||||
padding-top: 76px;
|
||||
}
|
||||
}
|
||||
@media(max-width: 405px) {
|
||||
.navbar-wrapper {
|
||||
max-width: 405px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
469
src/components/IframeSearchForm.vue
Normal file
469
src/components/IframeSearchForm.vue
Normal file
@ -0,0 +1,469 @@
|
||||
<template>
|
||||
<div class="formTitle text-[32px]">Фильтр поиска</div>
|
||||
<div class="form">
|
||||
<div class="filter-group">
|
||||
<div class="form__item">
|
||||
<label for="type">Тип комплекса</label>
|
||||
<div class="card flex justify-content-center" style="gap: 10px">
|
||||
<MultiSelect
|
||||
:pt="multiSelectStyle"
|
||||
id="type"
|
||||
v-model="filter.selectedComplexTypes"
|
||||
:options="complexes_types"
|
||||
filter
|
||||
optionLabel="name"
|
||||
optionValue="code"
|
||||
placeholder="Выберите тип"
|
||||
selectedItemsLabel="Выбрано: {0}"
|
||||
emptyFilterMessage="Ничего не найдено"
|
||||
:maxSelectedLabels="1"
|
||||
class="w-full md:w-20rem"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form__item">
|
||||
<label class="text-grey-900">Тип объекта</label>
|
||||
<MultiSelect
|
||||
v-model="filter.selectedTypes"
|
||||
:options="roomTypesOptions"
|
||||
selectedItemsLabel="Выбрано: {0}"
|
||||
filter
|
||||
emptyFilterMessage="Ничего не найдено"
|
||||
:maxSelectedLabels="1"
|
||||
optionValue="key"
|
||||
optionLabel="name"
|
||||
placeholder="Выберите тип"
|
||||
class="w-full md:w-20rem"
|
||||
:pt="multiSelectStyle"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="showTypeRooms"
|
||||
class="filters__roms flex flex-col justify-start items-start h-full w-max"
|
||||
>
|
||||
<label class="text-grey-900 mb-2">Количество комнат</label>
|
||||
<div class="btns flex flex-row items-stretch grow h-full">
|
||||
<SelectButton
|
||||
v-model="filter.rooms"
|
||||
:options="typeRooms"
|
||||
option-label="name"
|
||||
option-value="value"
|
||||
multiple
|
||||
aria-labelledby="multiple"
|
||||
:pt="{
|
||||
button: {
|
||||
class: ['2xl:!h-[37px] xl:!h-[37px]'],
|
||||
},
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<div class="form__item">
|
||||
<label for="type">Район</label>
|
||||
<div class="card flex justify-content-center" style="gap: 10px">
|
||||
<MultiSelect
|
||||
:pt="multiSelectStyle"
|
||||
id="type"
|
||||
v-model="filter.rayons"
|
||||
:options="rayons"
|
||||
filter
|
||||
emptyFilterMessage="Ничего не найдено"
|
||||
optionLabel="name"
|
||||
optionValue="name"
|
||||
placeholder="Выберите тип"
|
||||
selectedItemsLabel="Выбрано: {0}"
|
||||
:maxSelectedLabels="1" class="w-full md:w-20rem" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form__item">
|
||||
<label for="metro">Метро</label>
|
||||
<div class="card flex justify-content-center">
|
||||
<MultiSelect
|
||||
:pt="multiSelectStyle"
|
||||
id="metro"
|
||||
v-model="filter.selectedMetros"
|
||||
:options="metros"
|
||||
filter
|
||||
emptyFilterMessage="Ничего не найдено"
|
||||
optionLabel="name"
|
||||
optionValue="code"
|
||||
placeholder="Выберите станции"
|
||||
selectedItemsLabel="Выбрано: {0}"
|
||||
:maxSelectedLabels="1" class="w-full md:w-20rem" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form__item">
|
||||
<label for="address">Адрес</label>
|
||||
<div class="card flex justify-content-center">
|
||||
<InputText :pt="{ root: { class: ['w-[300px]'] } }" placeholder="Улица, проспект, шоссе, переулок" id="address" v-model="filter.address" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form__item">
|
||||
<label for="address">Название комплекса</label>
|
||||
<MultiSelect :pt="multiSelectStyle" id="metro" v-model="filter.complexes" :options="complex_suggestions" filter optionLabel="name" optionValue="id" placeholder="Выберите комплексы"
|
||||
selectedItemsLabel="Выбрано: {0}"
|
||||
:maxSelectedLabels="1" class="w-full md:w-20rem" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<div class="form__item">
|
||||
<label for="period">Срок сдачи (не позднее)</label>
|
||||
<div class="card flex justify-content-center">
|
||||
<Dropdown emptyFilterMessage="Ничего не найдено" filter showClear id="period" :pt="{ itemLabel: { class: [ 'min-w-[300px]' ] }, header: { class: ['custom-header'] } }" :options="periods" optionLabel="name" optionValue="value" placeholder="Не выбрано" v-model="filter.period"></Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form__item">
|
||||
<label for="decoration">Тип отделки</label>
|
||||
<div class="card flex justify-content-center">
|
||||
<MultiSelect :pt="multiSelectStyle" id="decoration" v-model="filter.selectedDecorations" :options="decorations" emptyFilterMessage="Ничего не найдено" filter optionLabel="name" optionValue="code" placeholder="Выберите отделку"
|
||||
selectedItemsLabel="Выбрано: {0}"
|
||||
:maxSelectedLabels="1" class="w-full md:w-20rem" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form__item">
|
||||
<label style="display: flex;" for="area">Плошадь, <small-span class="form-label with-pad"> м<sup>2</sup></small-span></label>
|
||||
<div class="card flex justify-content-center" style="gap: 10px">
|
||||
<InputNumber :pt="{ root: { class: [ 'number-input', 'w-[142px]' ] } }" type="number" placeholder="от" id="area_from" v-model="filter.area_from" />
|
||||
<InputNumber :pt="{ root: { class: [ 'number-input', 'w-[142px]' ] } }" type="number" placeholder="до" id="area_to" v-model="filter.area_to" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form__item">
|
||||
<label style="display: flex;" for="area">Площадь кухни, <small-span class="form-label with-pad"> м<sup>2</sup></small-span></label>
|
||||
<div class="card flex justify-content-center" style="gap: 10px">
|
||||
<InputNumber :pt="{ root: { class: [ 'number-input', 'w-[142px]', '!p-0' ] } }" type="number" placeholder="от" id="area_from" v-model="filter.squareKitchen_min" />
|
||||
<InputNumber :pt="{ root: { class: [ 'number-input', 'w-[142px]', '!p-0' ] } }" type="number" placeholder="до" id="area_to" v-model="filter.squareKitchen_max" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <div class="form__item" style="width: 100%;">
|
||||
<label for="price">Цена</label>
|
||||
<div style="gap: 10px; display: flex; justify-content: space-between;">
|
||||
<div class="card flex justify-content-center" style="gap: 10px">
|
||||
<InputNumber :pt="{ root: { class: [ 'number-input', 'min-w-[134.5px]' ] } }" type="number" placeholder="от" id="price_from" v-model="filter.price_from" />
|
||||
<InputNumber :pt="{ root: { class: [ 'number-input', 'min-w-[134.5px]' ] } }" type="number" placeholder="до" id="price_to" v-model="filter.price_to" />
|
||||
<div style="display: flex; align-items:center; gap: 5px">
|
||||
<InputSwitch v-model="filter.useSquarePrice" />
|
||||
<div style="text-wrap: nowrap;" class="text-grey-clearFilters900">Цена за кв.м.</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="btnClearFilters bg-white border border-red text-sm px-4 py-2.5 text-red rounded-[30px] h-max self-end"
|
||||
@click="() => $emit('clear')"
|
||||
>
|
||||
Очистить фильтры
|
||||
</button>
|
||||
</div>
|
||||
</div> -->
|
||||
<div style="display: flex; width: 100%; justify-content: space-between">
|
||||
<div style="gap: 46px; display: flex;">
|
||||
<div class="form__item">
|
||||
<label for="floor">Этажность</label>
|
||||
<div class="card flex justify-content-center" style="gap: 10px">
|
||||
<InputNumber :pt="{ root: { class: [ 'number-input', 'w-[142px]' ] } }" type="number" placeholder="от" id="floor_from" v-model="filter.floor_from" />
|
||||
<InputNumber :pt="{ root: { class: [ 'number-input', 'w-[142px]' ] } }" type="number" placeholder="до" id="floor_to" v-model="filter.floor_to" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form__item">
|
||||
<label for="price">Цена</label>
|
||||
<div class="card flex justify-content-center" style="gap: 10px">
|
||||
<div class="card flex justify-content-center" style="gap: 10px">
|
||||
<InputNumber :pt="{ root: { class: [ 'number-input', 'min-w-[134.5px]' ] } }" type="number" placeholder="от" id="price_from" v-model="filter.price_from" />
|
||||
<InputNumber :pt="{ root: { class: [ 'number-input', 'min-w-[134.5px]' ] } }" type="number" placeholder="до" id="price_to" v-model="filter.price_to" />
|
||||
<div style="display: flex; align-items:center; gap: 5px">
|
||||
<InputSwitch v-model="filter.useSquarePrice" />
|
||||
<div style="text-wrap: nowrap;" class="text-grey-clearFilters900">Цена за кв.м.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
style="border-radius: 6px; height: 47px; border-color: #c68484;"
|
||||
class="btnClearFilters bg-white border border-red text-sm px-4 text-red rounded-[30px] h-max self-end"
|
||||
@click="() => $emit('clear')"
|
||||
>
|
||||
Очистить фильтры
|
||||
</button>
|
||||
</div>
|
||||
<MyButton
|
||||
:theme="'green'"
|
||||
class="!w-full !rounded-1x mx-auto !px-5 find-button"
|
||||
@click="submit"
|
||||
>Найти</MyButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, toRefs } from "vue";
|
||||
import Listbox from 'primevue/listbox';
|
||||
import InputNumber from "primevue/inputnumber";
|
||||
import api from "@/shared/api";
|
||||
import MyButton from "@/shared/UI/MyButton.vue";
|
||||
import { room_types, room_types_which_has_rooms } from "@/shared/utils/util";
|
||||
import SelectButton from "primevue/selectbutton";
|
||||
import { typeRooms } from "@/shared/utils/util";
|
||||
import { useFieldsStore } from "@/app/store/fields";
|
||||
import { reactive } from "vue";
|
||||
|
||||
export default {
|
||||
components: {Listbox, InputNumber, MyButton, SelectButton},
|
||||
props: {
|
||||
filter: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const fieldsStore = useFieldsStore()
|
||||
|
||||
const multiSelectStyle = {
|
||||
root: {
|
||||
class: [
|
||||
'!rounded !border !border-grey-400 !p-3 !h-max',
|
||||
],
|
||||
},
|
||||
input: {
|
||||
class: [
|
||||
'!flex !flex-row !items-center !p-0 !text-sm !leading-none !text-grey-900 !mr-3',
|
||||
],
|
||||
},
|
||||
wrapper: {
|
||||
class: ['!p-0'],
|
||||
},
|
||||
item: {
|
||||
class: ['!text-sm'],
|
||||
},
|
||||
label: {
|
||||
class: ['!p-0']
|
||||
},
|
||||
header: {
|
||||
class: ['custom-header']
|
||||
}
|
||||
};
|
||||
// const {filter} = toRefs(props)
|
||||
const roomTypesOptions = Object.entries(room_types).map(([room_key, room_value]) => ({
|
||||
key: room_key,
|
||||
name: room_value
|
||||
}))
|
||||
|
||||
return {
|
||||
roomTypesOptions,
|
||||
multiSelectStyle,
|
||||
// filter,
|
||||
metros: fieldsStore.metros,
|
||||
rayons: fieldsStore.rayons,
|
||||
complex_suggestions: fieldsStore.complex_suggestions,
|
||||
decorations: fieldsStore.decorations,
|
||||
complexes_types: fieldsStore.complexes_types,
|
||||
rf_regions: fieldsStore.rf_regions,
|
||||
typeRooms,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
periods() {
|
||||
const date = (new Date(Date.now()))
|
||||
const currentYear = date.getFullYear();
|
||||
const currentQuarter = Math.ceil(date.getMonth() / 3) - 1;
|
||||
return this.generateQuarters(currentQuarter, currentYear, currentYear + 10)
|
||||
},
|
||||
showTypeRooms() {
|
||||
if (Array.isArray(this.filter.selectedTypes)) {
|
||||
return this.filter.selectedTypes.some((selectedType) => room_types_which_has_rooms.includes(selectedType));
|
||||
}
|
||||
|
||||
if (typeof this.filter.selectedTypes === 'string') {
|
||||
return room_types_which_has_rooms.includes(this.filter.selectedTypes)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
showTypeRooms(newValue) {
|
||||
if (!newValue) {
|
||||
this.filter.rooms = []
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
generateQuarters(startQuarter , startYear, endYear) {
|
||||
const quarters = ['1 квартал', '2 квартал', '3 квартал', '4 квартал'];
|
||||
const options = [];
|
||||
|
||||
options.push({
|
||||
name: 'Сдан',
|
||||
value: 'completed'
|
||||
});
|
||||
for (let year = startYear; year <= endYear; year++) {
|
||||
for (let i = 0; i < quarters.length; i++) {
|
||||
// Skip quarters before Q3 in the starting year
|
||||
if (year === startYear && i < startQuarter) continue;
|
||||
|
||||
options.push({
|
||||
name: `${quarters[i]} ${year}`,
|
||||
value: `${year}Q${i + 1}`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
},
|
||||
submit() {
|
||||
this.$emit('onSubmit')
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.d-flex {
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
input {
|
||||
padding: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.number-input {
|
||||
input {
|
||||
width: 75px
|
||||
}
|
||||
}
|
||||
|
||||
.form .p-dropdown-label {
|
||||
width: 150px !important;
|
||||
}
|
||||
.form .p-dropdown-label-empty {
|
||||
width: 80px !important;
|
||||
}
|
||||
|
||||
.form .p-multiselect.p-inputwrapper, .form .p-dropdown {
|
||||
border-radius: 6px !important;
|
||||
justify-content: space-between !important;
|
||||
padding: 8px !important;
|
||||
width: 300px !important;
|
||||
height: 36px !important;
|
||||
}
|
||||
.form .p-placeholder, .form .p-multiselect-label {
|
||||
font-weight: 500;
|
||||
font-size: 16px !important;
|
||||
line-height: 19.2px !important;
|
||||
color: #757575 !important;
|
||||
}
|
||||
|
||||
.form .p-inputswitch.p-focus .p-inputswitch-slider {
|
||||
box-shadow: unset ;
|
||||
}
|
||||
|
||||
.form input::placeholder {
|
||||
font-weight: 500;
|
||||
font-size: 16px !important;
|
||||
line-height: 19.2px !important;
|
||||
color: #757575 !important;
|
||||
}
|
||||
|
||||
.custom-header {
|
||||
display: flex !important;
|
||||
|
||||
.p-checkbox-box, input {
|
||||
box-shadow: unset !important;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 5px !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
<style scoped lang="scss">
|
||||
.form {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
margin-top: 30px;
|
||||
row-gap: 20px;
|
||||
.form__item {
|
||||
flex-direction: column;
|
||||
width: 300px;
|
||||
height: auto;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
.p-invalid{
|
||||
color: #e24c4c;
|
||||
}
|
||||
|
||||
label {
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
min-width: 110px;
|
||||
@apply text-grey-900;
|
||||
span {
|
||||
@apply text-red;
|
||||
}
|
||||
}
|
||||
.item__content {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
column-gap: 16px;
|
||||
row-gap: 16px;
|
||||
}
|
||||
.item__text {
|
||||
@apply text-grey-900;
|
||||
font-size: 16px;
|
||||
}
|
||||
.item__btns {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
column-gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.find-button {
|
||||
background: linear-gradient(to bottom, rgba(67, 160, 71, 1) 0%, rgba(56, 142, 60, 1) 100%);
|
||||
cursor: pointer;
|
||||
font-family: "Lato";
|
||||
padding: 20px 0 22px !important;
|
||||
color: #ffffff;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
background: #388E3C;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.formTitle {
|
||||
font-weight: 400;
|
||||
font-size: 32px;
|
||||
line-height: 38.4px;
|
||||
color: #333333
|
||||
}
|
||||
|
||||
|
||||
.filter-group {
|
||||
display: flex;
|
||||
gap: 40px;
|
||||
}
|
||||
</style>
|
||||
35
src/components/Loader.vue
Normal file
35
src/components/Loader.vue
Normal file
@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<div class="linear-loader-container">
|
||||
<div class="linear-loader"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.linear-loader-container {
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
background-color: #f3f3f3;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 10px;
|
||||
|
||||
.linear-loader {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: linear-gradient(90deg, #3498db 25%, #f3f3f3 50%, #3498db 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: loading 1s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@keyframes loading {
|
||||
0% {
|
||||
background-position: 100% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: -100% 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
47
src/components/MapIcon.vue
Normal file
47
src/components/MapIcon.vue
Normal file
@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 69 100"
|
||||
:width="width"
|
||||
:height="height"
|
||||
>
|
||||
<path
|
||||
d="M34.5074 0C15.7476 0 0.537109 15.1083 0.537109
|
||||
33.746C0.537109 43.2806 9.89002 60.3623 9.89002 60.3623L33.2552
|
||||
100L57.6341 60.8251C57.6341 60.8251 68.4635 44.5271 68.4635
|
||||
33.746C68.4663 15.1083 53.2586 0 34.5074 0ZM34.3484 52.3325C23.6042
|
||||
52.3325 14.9015 43.6299 14.9015 32.8743C14.9015 22.1329 23.6014
|
||||
13.4359 34.3484 13.4359C45.0869 13.4359 53.7981 22.1329 53.7981
|
||||
32.8743C53.7981 43.6299 45.0869 52.3325 34.3484 52.3325Z"
|
||||
:fill="mainColor"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// import { onBeforeMount } from "vue";
|
||||
export default {
|
||||
name: "MapIcon",
|
||||
props: {
|
||||
mainColor: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: "ED4B00",
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "40px",
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "54px",
|
||||
},
|
||||
},
|
||||
// onBeforeMount() {
|
||||
// console.log('onbeforemount');
|
||||
|
||||
// }
|
||||
};
|
||||
</script>
|
||||
96
src/components/Navigation.vue
Normal file
96
src/components/Navigation.vue
Normal file
@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<div class="navigation-container" v-if="pages.length">
|
||||
<div class="breadcrumbs">
|
||||
<router-link
|
||||
v-if="pages.length > 1"
|
||||
:to="pages[pages.length - 2].path"
|
||||
class="back-step"
|
||||
>
|
||||
<svg fill="none" viewBox="0 0 16 10" height="18" width="20" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="scale(-1, 1) translate(-15, 0)">
|
||||
<path fill="#02076F" d="M15.2069 4.864L10.6149 9.456L10.0789 8.896C9.96688 8.77867 9.92154 8.664 9.94288 8.552C9.96421 8.43467 10.0282 8.32267 10.1349 8.216L12.2629 6.096C12.5242 5.83467 12.7642 5.61867 12.9829 5.448C12.6949 5.48533 12.3935 5.51467 12.0789 5.536C11.7642 5.55733 11.4389 5.568 11.1029 5.568H0.974875V4.144H11.1029C11.4389 4.144 11.7642 4.15467 12.0789 4.176C12.3989 4.19733 12.7029 4.22933 12.9909 4.272C12.7722 4.096 12.5295 3.87733 12.2629 3.616L10.1189 1.48C10.0122 1.37333 9.94821 1.264 9.92688 1.152C9.90554 1.03467 9.95088 0.917333 10.0629 0.799999L10.5989 0.24L15.2069 4.864Z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
</router-link>
|
||||
<div class = "breadcrumbs-links">
|
||||
<router-link
|
||||
v-for="(page, index) in pages"
|
||||
:key="index"
|
||||
:to="index === pages.length - 1 ? null : page.path"
|
||||
:class="{'active-step': index === pages.length - 1}"
|
||||
>
|
||||
{{ page.name }}
|
||||
<!-- <svg v-if="index < (pages.length - 1)" xmlns="http://www.w3.org/2000/svg" width="20" height="18" viewBox="0 0 16 10" fill="none">
|
||||
<path d="M15.2069 4.864L10.6149 9.456L10.0789 8.896C9.96688 8.77867 9.92154 8.664 9.94288 8.552C9.96421 8.43467 10.0282 8.32267 10.1349 8.216L12.2629 6.096C12.5242 5.83467 12.7642 5.61867 12.9829 5.448C12.6949 5.48533 12.3935 5.51467 12.0789 5.536C11.7642 5.55733 11.4389 5.568 11.1029 5.568H0.974875V4.144H11.1029C11.4389 4.144 11.7642 4.15467 12.0789 4.176C12.3989 4.19733 12.7029 4.22933 12.9909 4.272C12.7722 4.096 12.5295 3.87733 12.2629 3.616L10.1189 1.48C10.0122 1.37333 9.94821 1.264 9.92688 1.152C9.90554 1.03467 9.95088 0.917333 10.0629 0.799999L10.5989 0.24L15.2069 4.864Z" fill="#7D7D7D"/>
|
||||
</svg> -->
|
||||
<span v-if="index < (pages.length - 1)"> \ </span>
|
||||
</router-link>
|
||||
</div>
|
||||
<span class="lw-text lw-text--medi lw-text--success" v-if="isActiveApartment">{{ apartmentInfo }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useNavigateStore } from "@/app/store/navigate";
|
||||
|
||||
const navigateStore = useNavigateStore();
|
||||
|
||||
console.log(navigateStore, 'NAVIGATESTORE');
|
||||
|
||||
const pages = computed(() => navigateStore.pages);
|
||||
console.log(pages, 'pages');
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.navigation-container {
|
||||
padding-bottom: 20px;
|
||||
|
||||
.breadcrumbs {
|
||||
display: flex;
|
||||
align-items: center; // Align items vertically
|
||||
gap: 10px; // Increase gap for better spacing
|
||||
padding: var(--main-offset) 0;
|
||||
justify-content:center;
|
||||
margin-top: 30px;
|
||||
.ggg{
|
||||
transform:rotateZ(-180deg) !important;
|
||||
}
|
||||
.breadcrumbs-links{
|
||||
display:flex;
|
||||
flex-wrap:wrap;
|
||||
width:90%;
|
||||
transform:translatex(20px)
|
||||
a{
|
||||
font-size:14px;
|
||||
}
|
||||
}
|
||||
.router-link-active {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #7d7d7d;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
border-bottom: none !important;
|
||||
|
||||
svg {
|
||||
/* margin-left: 10px */
|
||||
}
|
||||
}
|
||||
|
||||
.router-link-exact-active,
|
||||
.active-step {
|
||||
color: #02076F;
|
||||
pointer-events: none; // Disable pointer events
|
||||
cursor: default;
|
||||
|
||||
&:hover {
|
||||
color: #02076F !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
9
src/components/NoResults.vue
Normal file
9
src/components/NoResults.vue
Normal file
@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<div style="display: flex; justify-content: center; flex-direction: column; align-items: center; gap: 20px;">
|
||||
<BlockSvg />
|
||||
<span>К сожалению, по вашему запросу объявлений не найдено</span>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import BlockSvg from "@/shared/assets/icons/block.vue"
|
||||
</script>
|
||||
38
src/components/NoResultsForDeveloper.vue
Normal file
38
src/components/NoResultsForDeveloper.vue
Normal file
@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<div style="display: flex; justify-content: center; flex-direction: column; align-items: center; gap: 20px;">
|
||||
<BlockSvg />
|
||||
<span class="text-box">
|
||||
В данный момент у вас еще не созданы Жилые комплексы. <br />
|
||||
Перейдите в раздел создания.
|
||||
</span>
|
||||
<a class="go-objects-btn" href="/complexes/#/my-objects">
|
||||
Перейти
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import BlockSvg from "@/shared/assets/icons/block.vue"
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.text-box
|
||||
{
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.go-objects-btn
|
||||
{
|
||||
display: flex;
|
||||
padding: 4px 14px;
|
||||
line-height: 18px;
|
||||
font-size: 16px;
|
||||
border: 1px solid #d0d0d0;
|
||||
border-radius: 3px;
|
||||
color: #237ef7 !important;
|
||||
border-color: #3a8efa !important;
|
||||
cursor: pointer;
|
||||
background: -webkit-linear-gradient(top, #fff, #f5f5f5);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
</style>
|
||||
43
src/components/RocketSpinner.vue
Normal file
43
src/components/RocketSpinner.vue
Normal file
@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<div class="rocket-spinner">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#07a860" height="900" viewBox="0 0 900 900" width="900">
|
||||
<defs>
|
||||
<linearGradient id="b" x1="50%" x2="50%" y1="0%" y2="100%">
|
||||
<stop offset="0" stop-color="#43a047"/>
|
||||
<stop offset="1" stop-color="#27702a"/>
|
||||
</linearGradient>
|
||||
<path id="rocket" d="m29.1695563 302.722947 15.7030684-15.702338c4.3362202-4.336223 4.3362202-11.366116 0-15.702339-4.3347397-4.336223-11.3653678-4.335482-15.7023282 0l-15.7030684 15.702339c-4.33622023 4.336223-4.33622023 11.366115 0 15.702338 4.3362201 4.336223 11.366108 4.336223 15.7023282 0zm47.1077249 0c-4.33548-4.336223-11.3661081-4.336223-15.7023282 0l-57.32278786 57.322825c-4.33622019 4.336223-4.33622019 11.366115 0 15.702338 4.33547996 4.335483 11.36610806 4.336223 15.70232826 0l57.3227878-57.322824c4.3369604-4.336223 4.3369604-11.366116 0-15.702339zm301.4879588-292.0666024c-.698029-4.88546801-4.53682-8.7235213-9.422285-9.42155105-1.193978-.17025116-29.704367-4.09713114-69.286255 3.00752373-51.351417 9.21724962-98.821111 33.19971612-136.416243 70.79487302-10.027047 10.027053-21.067457 22.2880973-31.958343 35.4862627-13.458715-3.048235-57.7180663-8.62063-101.5118175 35.17463-38.81057707 38.809862-16.665357 61.847805-15.7030684 62.810094 2.0822443 2.082245 4.906191 3.252537 7.850794 3.252537 2.9446029 0 5.7692898-1.170292 7.850794-3.251797 6.2918865-6.291891 14.6563946-9.756872 23.5538624-9.756872 8.8974679 0 17.2619759 3.464981 23.5531222 9.756132l23.5546027 23.553877-7.8515342 7.85154c-13.0175432 13.016071-13.0205041 34.087983 0 47.107015 13.0160628 13.016811 34.0887008 13.019772 47.1069848 0l7.851534-7.85154s23.522773 23.523529 23.565706 23.565722c12.975351 12.988683 12.97165 34.11167-.011843 47.095171-2.082245 2.082246-3.252536 4.906935-3.252536 7.85154 0 2.945345 1.169551 5.768553 3.252536 7.851539.516674.516675 7.414063 7.15573 19.71211 7.15573 10.577772 0 25.152002-4.912856 43.097943-22.858809 43.794491-43.794519 38.2221-88.053159 35.174607-101.512623 13.198897-10.890893 25.459934-21.93131 35.48698-31.959103 37.51815-37.518174 61.56648-85.000482 70.794827-136.4163319 7.10391-39.5796932 3.178513-68.0908405 3.007522-69.2855595zm-325.041081 165.8882874c-7.8804029 0-15.5091303 1.63071-22.5020071 4.732242 2.1688503-5.152688 6.5739111-11.799145 14.6512131-19.877193 19.1354776-19.13549 43.1016437-31.694844 69.780723-30.597094-13.683743 18.089556-26.1017062 36.677281-34.6638535 52.889633-8.2297876-4.655259-17.5536233-7.147588-27.2660755-7.147588zm70.660107 94.773638c-4.337701 4.338444-11.361667 4.339184-15.701588 0-4.329558-4.329561-4.329558-11.373517 0-15.702338l62.810053-62.810094c4.328078-4.32882 11.37277-4.330301 15.703068 0 4.329559 4.329561 4.329559 11.373518 0 15.703079zm94.21545 62.810094c-8.097288 8.097293-14.756325 12.503837-19.913451 14.666767 7.03729-15.90886 6.243772-34.472158-2.380554-49.782919 16.227886-8.570295 34.836325-21.003811 52.942154-34.703108.637331 15.384783-3.193317 42.36441-30.648149 69.81926zm70.661587-133.471727c-31.98499 31.985012-77.493836 66.044126-107.545367 80.884845l-18.074739-18.074751 39.25619-39.256216c13.017544-13.016811 13.020505-34.088723 0-47.107755-12.987934-12.987943-34.121271-12.985722-47.107724 0l-39.256191 39.256216-18.0739995-18.074011c14.8392295-30.05081 48.8990625-75.559685 80.8840525-107.5461766 27.88268-27.8826984 60.940253-47.4882296 98.526502-58.4990382l69.805891 69.8059358c-9.228347 31.327694-26.519932 66.716247-58.414615 98.610951zm64.443723-123.9857758-50.441685-50.4417171c23.893625-4.3606504 43.557621-4.2925499 54.235323-3.7314613.549984 10.6347756.586996 30.2047762-3.793638 54.1731784zm-119.402242 6.2171282c-17.356724 17.3559956-17.360425 45.4511376 0 62.8100936 17.314532 17.314543 45.4896 17.318244 62.810053 0 17.316753-17.316763 17.316753-45.49333 0-62.8108339-17.356724-17.3545148-45.450368-17.3589561-62.810053.0007403zm47.107725 47.1077556c-8.658376 8.658381-22.74628 8.658381-31.405397 0-8.678362-8.678368-8.680582-22.725569 0-31.404677 8.678363-8.6783678 22.724814-8.679108 31.405397 0 8.657636 8.658382 8.657636 22.746295 0 31.404677zm-188.4301594 204.132619-15.7023282 15.702338c-4.3362202 4.336223-4.3362202 11.366116 0 15.702339 4.33548 4.335483 11.3661081 4.336223 15.7023282 0l15.7023284-15.702339c4.33622-4.336223 4.33622-11.366115 0-15.702338-4.33622-4.336963-11.3668484-4.336963-15.7023284 0z"/>
|
||||
<animateTransform xlink:href="#circle" attributeName="transform" attributeType="XML" type="rotate" from="0 440 440" to="360 440 440" dur="3s" repeatCount="indefinite"/>
|
||||
</defs>
|
||||
<g id="spinner" fill="none" transform="translate(10, 10)">
|
||||
<g id="circle">
|
||||
<path d="m437.5 858.981693c-232.788022 0-421.5-188.703781-421.5-421.481693s188.711978-421.4816934 421.5-421.4816934 421.5 188.7037814 421.5 421.4816934-188.711978 421.481693-421.5 421.481693zm0-2.952586c231.157283 0 418.547285-187.381864 418.547285-418.529107s-187.390002-418.5291071-418.547285-418.5291071-418.5472855 187.3818641-418.5472855 418.5291071 187.3900025 418.529107 418.5472855 418.529107z" fill="#eaeaea"/>
|
||||
<path d="m842.537778 437.869198c-.199121-223.796198-181.430278-405.180293-405.037778-405.3795821v-.0166474c-8.621726-.3860271-15.493255-7.503992-15.493255-16.2282428 0-8.72425067 6.871529-15.84221559 15.493255-16.22824271v-.01648299c.206248 0 .412462.00014284.618643.00042842.039678-.00028546.079388-.00042842.119131-.00042842.058717 0 .117362.00031205.175932.0009346 241.204482.49347657 436.586294 196.3446814 436.586294 437.8682634 0 8.971715-7.266879 16.244726-16.231029 16.244726-8.964149 0-16.231028-7.273011-16.231028-16.244726z" fill="#cfcfcf"/>
|
||||
</g>
|
||||
<path d="m420.531033 858.755274c-.199121-223.796198-181.430278-405.180293-405.0377784-405.379582v-.016648c-8.62172632-.386027-15.4932546-7.503992-15.4932546-16.228242 0-8.724251 6.87152828-15.842216 15.4932546-16.228243v-.016483c.2062477 0 .4124621.000143.618643.000428.0396777-.000285.0793877-.000428.1191311-.000428.0587174 0 .117362.000312.1759322.000935 241.2044821.493476 436.5862941 196.344681 436.5862941 437.868263 0 8.971715-7.266879 16.244726-16.231029 16.244726s-16.231029-7.273011-16.231029-16.244726z" transform="matrix(-1 0 0 -1 452.993254 1295.886076)"/>
|
||||
<g fill-rule="evenodd" transform="matrix(.70710678 -.70710678 .70710678 .70710678 169.299423 438)">
|
||||
<use :fill="main_color" xlink:href="#rocket"/>
|
||||
<animateMotion id="rocket1" path="M0,0 0 60 0 0" begin="0s; rocket2.end" dur="2s" repeatCount="indefinite" fill="freeze"/>
|
||||
<animateMotion id="rocket2" path="M0,0 0 -60 0 0" begin="rocket1.end" dur="2s" repeatCount="indefinite" fill="freeze"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const colors = JSON.parse(localStorage.getItem('colors'));
|
||||
const main_color = colors.frame_color;
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rocket-spinner {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transform: scale(0.5) translate(0px);
|
||||
}
|
||||
</style>
|
||||
469
src/components/SearchForm.vue
Normal file
469
src/components/SearchForm.vue
Normal file
@ -0,0 +1,469 @@
|
||||
<template>
|
||||
<div class="formTitle text-[32px]">Фильтр поиска</div>
|
||||
<div class="form">
|
||||
<div class="filter-group">
|
||||
<div class="form__item">
|
||||
<label for="type">Тип комплекса</label>
|
||||
<div class="card flex justify-content-center" style="gap: 10px">
|
||||
<MultiSelect
|
||||
:pt="multiSelectStyle"
|
||||
id="type"
|
||||
v-model="filter.selectedComplexTypes"
|
||||
:options="complexes_types"
|
||||
filter
|
||||
optionLabel="name"
|
||||
optionValue="code"
|
||||
placeholder="Выберите тип"
|
||||
selectedItemsLabel="Выбрано: {0}"
|
||||
emptyFilterMessage="Ничего не найдено"
|
||||
:maxSelectedLabels="1"
|
||||
class="w-full md:w-20rem"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form__item">
|
||||
<label class="text-grey-900">Тип объекта</label>
|
||||
<MultiSelect
|
||||
v-model="filter.selectedTypes"
|
||||
:options="roomTypesOptions"
|
||||
selectedItemsLabel="Выбрано: {0}"
|
||||
filter
|
||||
emptyFilterMessage="Ничего не найдено"
|
||||
:maxSelectedLabels="1"
|
||||
optionValue="key"
|
||||
optionLabel="name"
|
||||
placeholder="Выберите тип"
|
||||
class="w-full md:w-20rem"
|
||||
:pt="multiSelectStyle"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="showTypeRooms"
|
||||
class="filters__roms flex flex-col justify-start items-start h-full w-max"
|
||||
>
|
||||
<label class="text-grey-900 mb-2">Количество комнат</label>
|
||||
<div class="btns flex flex-row items-stretch grow h-full">
|
||||
<SelectButton
|
||||
v-model="filter.rooms"
|
||||
:options="typeRooms"
|
||||
option-label="name"
|
||||
option-value="value"
|
||||
multiple
|
||||
aria-labelledby="multiple"
|
||||
:pt="{
|
||||
button: {
|
||||
class: ['2xl:!h-[37px] xl:!h-[37px]'],
|
||||
},
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<div class="form__item">
|
||||
<label for="type">Район</label>
|
||||
<div class="card flex justify-content-center" style="gap: 10px">
|
||||
<MultiSelect
|
||||
:pt="multiSelectStyle"
|
||||
id="type"
|
||||
v-model="filter.rayons"
|
||||
:options="rayons"
|
||||
filter
|
||||
emptyFilterMessage="Ничего не найдено"
|
||||
optionLabel="name"
|
||||
optionValue="name"
|
||||
placeholder="Выберите тип"
|
||||
selectedItemsLabel="Выбрано: {0}"
|
||||
:maxSelectedLabels="1" class="w-full md:w-20rem" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form__item">
|
||||
<label for="metro">Метро</label>
|
||||
<div class="card flex justify-content-center">
|
||||
<MultiSelect
|
||||
:pt="multiSelectStyle"
|
||||
id="metro"
|
||||
v-model="filter.selectedMetros"
|
||||
:options="metros"
|
||||
filter
|
||||
emptyFilterMessage="Ничего не найдено"
|
||||
optionLabel="name"
|
||||
optionValue="code"
|
||||
placeholder="Выберите станции"
|
||||
selectedItemsLabel="Выбрано: {0}"
|
||||
:maxSelectedLabels="1" class="w-full md:w-20rem" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form__item">
|
||||
<label for="address">Адрес</label>
|
||||
<div class="card flex justify-content-center">
|
||||
<InputText :pt="{ root: { class: ['w-[300px]'] } }" placeholder="Улица, проспект, шоссе, переулок" id="address" v-model="filter.address" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form__item">
|
||||
<label for="address">Название комплекса</label>
|
||||
<MultiSelect :pt="multiSelectStyle" id="metro" v-model="filter.complexes" :options="complex_suggestions" filter optionLabel="name" optionValue="id" placeholder="Выберите комплексы"
|
||||
selectedItemsLabel="Выбрано: {0}"
|
||||
:maxSelectedLabels="1" class="w-full md:w-20rem" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<div class="form__item">
|
||||
<label for="period">Срок сдачи (не позднее)</label>
|
||||
<div class="card flex justify-content-center">
|
||||
<Dropdown emptyFilterMessage="Ничего не найдено" filter showClear id="period" :pt="{ itemLabel: { class: [ 'min-w-[300px]' ] }, header: { class: ['custom-header'] } }" :options="periods" optionLabel="name" optionValue="value" placeholder="Не выбрано" v-model="filter.period"></Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form__item">
|
||||
<label for="decoration">Тип отделки</label>
|
||||
<div class="card flex justify-content-center">
|
||||
<MultiSelect :pt="multiSelectStyle" id="decoration" v-model="filter.selectedDecorations" :options="decorations" emptyFilterMessage="Ничего не найдено" filter optionLabel="name" optionValue="code" placeholder="Выберите отделку"
|
||||
selectedItemsLabel="Выбрано: {0}"
|
||||
:maxSelectedLabels="1" class="w-full md:w-20rem" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form__item">
|
||||
<label style="display: flex;" for="area">Плошадь, <small-span class="form-label with-pad"> м<sup>2</sup></small-span></label>
|
||||
<div class="card flex justify-content-center" style="gap: 10px">
|
||||
<InputNumber :pt="{ root: { class: [ 'number-input', 'w-[142px]' ] } }" type="number" placeholder="от" id="area_from" v-model="filter.area_from" />
|
||||
<InputNumber :pt="{ root: { class: [ 'number-input', 'w-[142px]' ] } }" type="number" placeholder="до" id="area_to" v-model="filter.area_to" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form__item">
|
||||
<label style="display: flex;" for="area">Площадь кухни, <small-span class="form-label with-pad"> м<sup>2</sup></small-span></label>
|
||||
<div class="card flex justify-content-center" style="gap: 10px">
|
||||
<InputNumber :pt="{ root: { class: [ 'number-input', 'w-[142px]', '!p-0' ] } }" type="number" placeholder="от" id="area_from" v-model="filter.squareKitchen_min" />
|
||||
<InputNumber :pt="{ root: { class: [ 'number-input', 'w-[142px]', '!p-0' ] } }" type="number" placeholder="до" id="area_to" v-model="filter.squareKitchen_max" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <div class="form__item" style="width: 100%;">
|
||||
<label for="price">Цена</label>
|
||||
<div style="gap: 10px; display: flex; justify-content: space-between;">
|
||||
<div class="card flex justify-content-center" style="gap: 10px">
|
||||
<InputNumber :pt="{ root: { class: [ 'number-input', 'min-w-[134.5px]' ] } }" type="number" placeholder="от" id="price_from" v-model="filter.price_from" />
|
||||
<InputNumber :pt="{ root: { class: [ 'number-input', 'min-w-[134.5px]' ] } }" type="number" placeholder="до" id="price_to" v-model="filter.price_to" />
|
||||
<div style="display: flex; align-items:center; gap: 5px">
|
||||
<InputSwitch v-model="filter.useSquarePrice" />
|
||||
<div style="text-wrap: nowrap;" class="text-grey-clearFilters900">Цена за кв.м.</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="btnClearFilters bg-white border border-red text-sm px-4 py-2.5 text-red rounded-[30px] h-max self-end"
|
||||
@click="() => $emit('clear')"
|
||||
>
|
||||
Очистить фильтры
|
||||
</button>
|
||||
</div>
|
||||
</div> -->
|
||||
<div style="display: flex; width: 100%; justify-content: space-between">
|
||||
<div style="gap: 46px; display: flex;">
|
||||
<div class="form__item">
|
||||
<label for="floor">Этажность</label>
|
||||
<div class="card flex justify-content-center" style="gap: 10px">
|
||||
<InputNumber :pt="{ root: { class: [ 'number-input', 'w-[142px]' ] } }" type="number" placeholder="от" id="floor_from" v-model="filter.floor_from" />
|
||||
<InputNumber :pt="{ root: { class: [ 'number-input', 'w-[142px]' ] } }" type="number" placeholder="до" id="floor_to" v-model="filter.floor_to" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form__item">
|
||||
<label for="price">Цена</label>
|
||||
<div class="card flex justify-content-center" style="gap: 10px">
|
||||
<div class="card flex justify-content-center" style="gap: 10px">
|
||||
<InputNumber :pt="{ root: { class: [ 'number-input', 'min-w-[134.5px]' ] } }" type="number" placeholder="от" id="price_from" v-model="filter.price_from" />
|
||||
<InputNumber :pt="{ root: { class: [ 'number-input', 'min-w-[134.5px]' ] } }" type="number" placeholder="до" id="price_to" v-model="filter.price_to" />
|
||||
<div style="display: flex; align-items:center; gap: 5px">
|
||||
<InputSwitch v-model="filter.useSquarePrice" />
|
||||
<div style="text-wrap: nowrap;" class="text-grey-clearFilters900">Цена за кв.м.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
style="border-radius: 6px; height: 47px; border-color: #c68484;"
|
||||
class="btnClearFilters bg-white border border-red text-sm px-4 py-2.5 text-red rounded-[30px] h-max self-end"
|
||||
@click="() => $emit('clear')"
|
||||
>
|
||||
Очистить фильтры
|
||||
</button>
|
||||
</div>
|
||||
<MyButton
|
||||
:theme="'green'"
|
||||
class="!w-full !rounded-1x mx-auto !px-5 find-button"
|
||||
@click="submit"
|
||||
>Найти</MyButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, toRefs } from "vue";
|
||||
import Listbox from 'primevue/listbox';
|
||||
import InputNumber from "primevue/inputnumber";
|
||||
import api from "@/shared/api";
|
||||
import MyButton from "@/shared/UI/MyButton.vue";
|
||||
import { room_types, room_types_which_has_rooms } from "@/shared/utils/util";
|
||||
import SelectButton from "primevue/selectbutton";
|
||||
import { typeRooms } from "@/shared/utils/util";
|
||||
import { useFieldsStore } from "@/app/store/fields";
|
||||
import { reactive } from "vue";
|
||||
|
||||
export default {
|
||||
components: {Listbox, InputNumber, MyButton, SelectButton},
|
||||
props: {
|
||||
filter: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const fieldsStore = useFieldsStore()
|
||||
|
||||
const multiSelectStyle = {
|
||||
root: {
|
||||
class: [
|
||||
'!rounded !border !border-grey-400 !p-3 !h-max',
|
||||
],
|
||||
},
|
||||
input: {
|
||||
class: [
|
||||
'!flex !flex-row !items-center !p-0 !text-sm !leading-none !text-grey-900 !mr-3',
|
||||
],
|
||||
},
|
||||
wrapper: {
|
||||
class: ['!p-0'],
|
||||
},
|
||||
item: {
|
||||
class: ['!text-sm'],
|
||||
},
|
||||
label: {
|
||||
class: ['!p-0']
|
||||
},
|
||||
header: {
|
||||
class: ['custom-header']
|
||||
}
|
||||
};
|
||||
// const {filter} = toRefs(props)
|
||||
const roomTypesOptions = Object.entries(room_types).map(([room_key, room_value]) => ({
|
||||
key: room_key,
|
||||
name: room_value
|
||||
}))
|
||||
|
||||
return {
|
||||
roomTypesOptions,
|
||||
multiSelectStyle,
|
||||
// filter,
|
||||
metros: fieldsStore.metros,
|
||||
rayons: fieldsStore.rayons,
|
||||
complex_suggestions: fieldsStore.complex_suggestions,
|
||||
decorations: fieldsStore.decorations,
|
||||
complexes_types: fieldsStore.complexes_types,
|
||||
rf_regions: fieldsStore.rf_regions,
|
||||
typeRooms,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
periods() {
|
||||
const date = (new Date(Date.now()))
|
||||
const currentYear = date.getFullYear();
|
||||
const currentQuarter = Math.ceil(date.getMonth() / 3) - 1;
|
||||
return this.generateQuarters(currentQuarter, currentYear, currentYear + 10)
|
||||
},
|
||||
showTypeRooms() {
|
||||
if (Array.isArray(this.filter.selectedTypes)) {
|
||||
return this.filter.selectedTypes.some((selectedType) => room_types_which_has_rooms.includes(selectedType));
|
||||
}
|
||||
|
||||
if (typeof this.filter.selectedTypes === 'string') {
|
||||
return room_types_which_has_rooms.includes(this.filter.selectedTypes)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
showTypeRooms(newValue) {
|
||||
if (!newValue) {
|
||||
this.filter.rooms = []
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
generateQuarters(startQuarter , startYear, endYear) {
|
||||
const quarters = ['1 квартал', '2 квартал', '3 квартал', '4 квартал'];
|
||||
const options = [];
|
||||
|
||||
options.push({
|
||||
name: 'Сдан',
|
||||
value: 'completed'
|
||||
});
|
||||
for (let year = startYear; year <= endYear; year++) {
|
||||
for (let i = 0; i < quarters.length; i++) {
|
||||
// Skip quarters before Q3 in the starting year
|
||||
if (year === startYear && i < startQuarter) continue;
|
||||
|
||||
options.push({
|
||||
name: `${quarters[i]} ${year}`,
|
||||
value: `${year}Q${i + 1}`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
},
|
||||
submit() {
|
||||
this.$emit('onSubmit')
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.d-flex {
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
input {
|
||||
padding: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.number-input {
|
||||
input {
|
||||
width: 75px
|
||||
}
|
||||
}
|
||||
|
||||
.form .p-dropdown-label {
|
||||
width: 150px !important;
|
||||
}
|
||||
.form .p-dropdown-label-empty {
|
||||
width: 80px !important;
|
||||
}
|
||||
|
||||
.form .p-multiselect.p-inputwrapper, .form .p-dropdown {
|
||||
border-radius: 6px !important;
|
||||
justify-content: space-between !important;
|
||||
padding: 8px !important;
|
||||
width: 300px !important;
|
||||
height: 36px !important;
|
||||
}
|
||||
.form .p-placeholder, .form .p-multiselect-label {
|
||||
font-weight: 500;
|
||||
font-size: 16px !important;
|
||||
line-height: 19.2px !important;
|
||||
color: #757575 !important;
|
||||
}
|
||||
|
||||
.form .p-inputswitch.p-focus .p-inputswitch-slider {
|
||||
box-shadow: unset ;
|
||||
}
|
||||
|
||||
.form input::placeholder {
|
||||
font-weight: 500;
|
||||
font-size: 16px !important;
|
||||
line-height: 19.2px !important;
|
||||
color: #757575 !important;
|
||||
}
|
||||
|
||||
.custom-header {
|
||||
display: flex !important;
|
||||
|
||||
.p-checkbox-box, input {
|
||||
box-shadow: unset !important;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 5px !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
<style scoped lang="scss">
|
||||
.form {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
margin-top: 30px;
|
||||
row-gap: 20px;
|
||||
.form__item {
|
||||
flex-direction: column;
|
||||
width: 300px;
|
||||
height: auto;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
.p-invalid{
|
||||
color: #e24c4c;
|
||||
}
|
||||
|
||||
label {
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
min-width: 110px;
|
||||
@apply text-grey-900;
|
||||
span {
|
||||
@apply text-red;
|
||||
}
|
||||
}
|
||||
.item__content {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
column-gap: 16px;
|
||||
row-gap: 16px;
|
||||
}
|
||||
.item__text {
|
||||
@apply text-grey-900;
|
||||
font-size: 16px;
|
||||
}
|
||||
.item__btns {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
column-gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.find-button {
|
||||
background: linear-gradient(to bottom, rgba(67, 160, 71, 1) 0%, rgba(56, 142, 60, 1) 100%);
|
||||
cursor: pointer;
|
||||
font-family: "Lato";
|
||||
padding: 20px 0 22px !important;
|
||||
color: #ffffff;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
background: #388E3C;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.formTitle {
|
||||
font-weight: 400;
|
||||
font-size: 32px;
|
||||
line-height: 38.4px;
|
||||
color: #333333
|
||||
}
|
||||
|
||||
|
||||
.filter-group {
|
||||
display: flex;
|
||||
gap: 40px;
|
||||
}
|
||||
</style>
|
||||
122
src/components/Tabs.vue
Normal file
122
src/components/Tabs.vue
Normal file
@ -0,0 +1,122 @@
|
||||
<!-- Tabs.vue -->
|
||||
<template>
|
||||
<div class="tabs">
|
||||
<div class="tab-block">
|
||||
<ul>
|
||||
<li
|
||||
v-for="(tab, index) in listTabs"
|
||||
:key="index"
|
||||
:class="`${activeTab === index ? 'active' : ''}${ !tab.visible ? 'is_not_visible' : ''}`"
|
||||
@click="selectTab(index)"
|
||||
>
|
||||
<span v-html="tab.icon"></span>
|
||||
<span>
|
||||
{{ tab.label }}
|
||||
</span>
|
||||
<span v-if="tab.count" class="count">
|
||||
{{ tab.count > 9 ? '9+' : tab.count }}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<slot name="custom-tab"></slot>
|
||||
</div>
|
||||
|
||||
<!-- Display the content of the active tab -->
|
||||
<div class="tab-content">
|
||||
<slot :name="'tab-' + activeTab"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
// Define props to accept tabs array from the parent component
|
||||
const props = defineProps({
|
||||
tabs: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
onClickTab: {
|
||||
type: Function,
|
||||
required: false,
|
||||
default: () => {}
|
||||
}
|
||||
});
|
||||
|
||||
const listTabs = computed(() => {
|
||||
if (!props.tabs[activeTab.value].visible)
|
||||
{
|
||||
activeTab.value = 0
|
||||
}
|
||||
|
||||
return props.tabs
|
||||
})
|
||||
|
||||
// Active tab state
|
||||
const activeTab = ref(0);
|
||||
|
||||
// Function to change the active tab
|
||||
function selectTab(index) {
|
||||
activeTab.value = index;
|
||||
|
||||
props.onClickTab()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tabs {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tab-block {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #dedede;
|
||||
}
|
||||
|
||||
.tab-block ul {
|
||||
font-size: 16px;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.tab-block ul li {
|
||||
padding: 10px 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
cursor: pointer;
|
||||
border-bottom: 2px solid transparent;
|
||||
}
|
||||
|
||||
.tab-block ul li.active {
|
||||
/* background-color: #f0f8ff; */
|
||||
border-bottom: 2px solid #4caf50;
|
||||
fill: #4caf50;
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.tab-block ul li .count {
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
height: 24px;
|
||||
width: 26px;
|
||||
margin-left: 5px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
height: 95%;
|
||||
}
|
||||
|
||||
.is_not_visible {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
117
src/components/YandexMaps.vue
Normal file
117
src/components/YandexMaps.vue
Normal file
@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<MapsModalLayout
|
||||
class="edit-in-maps open"
|
||||
title=""
|
||||
@closeModel="$emit('closeModel')"
|
||||
>
|
||||
<template v-if="center.length">
|
||||
<yandex-map
|
||||
v-model="map"
|
||||
:settings="{
|
||||
location: {
|
||||
center,
|
||||
zoom: hasHouse ? 15 : 10,
|
||||
controls: ['fullscreenControl', 'zoomControl']
|
||||
},
|
||||
}"
|
||||
width="100%"
|
||||
height="500px"
|
||||
>
|
||||
<yandex-map-default-scheme-layer/>
|
||||
<yandex-map-default-features-layer/>
|
||||
<yandex-map-marker
|
||||
:settings="{
|
||||
coordinates: marker,
|
||||
onDragEnd: ([long, lat]) => $emit('coordsChoosed', [long.toPrecision(8), lat.toPrecision(8)]),
|
||||
draggable: true
|
||||
}"
|
||||
>
|
||||
<div class="marker"/>
|
||||
</yandex-map-marker>
|
||||
</yandex-map>
|
||||
<button @click="$emit('closeMaps')" class="close">Закрыть</button>
|
||||
</template>
|
||||
</MapsModalLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import MapsModalLayout from '@/shared/UI/mapsModalLayout.vue';
|
||||
import { onMounted } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import { YandexMap, YandexMapDefaultSchemeLayer, YandexMapDefaultFeaturesLayer, YandexMapMarker } from 'vue-yandex-maps';
|
||||
|
||||
const props = defineProps({
|
||||
regionLabel: Object,
|
||||
address: String,
|
||||
coords: Array,
|
||||
hasHouse: Boolean,
|
||||
})
|
||||
|
||||
const center = ref([])
|
||||
const marker = ref([])
|
||||
const map = ref(null)
|
||||
|
||||
onMounted(() => {
|
||||
const trimmedLabel = props.regionLabel.replace(/\s+/g, ' ').trim();
|
||||
window.yandexMap.geocode(trimmedLabel).then((res) => {
|
||||
var [coord1, cord2] = res.geoObjects.get(0).geometry.getCoordinates();
|
||||
center.value = [cord2.toPrecision(8), coord1.toPrecision(8)]
|
||||
|
||||
if (props.coords.length) {
|
||||
center.value = props.coords
|
||||
marker.value = props.coords
|
||||
} else if (props.address) {
|
||||
window.yandexMap.geocode((props.address || '').replace(/\s+/g, ' ').trim()).then((res) => {
|
||||
var [coord1, cord2] = res.geoObjects.get(0).geometry.getCoordinates();
|
||||
|
||||
marker.value = [cord2.toPrecision(8), coord1.toPrecision(8)]
|
||||
center.value = [cord2.toPrecision(8), coord1.toPrecision(8)]
|
||||
})
|
||||
.catch(console.log)
|
||||
} else {
|
||||
marker.value = center.value
|
||||
}
|
||||
})
|
||||
.catch(console.log)
|
||||
})
|
||||
|
||||
</script>
|
||||
<style scoped>
|
||||
.marker {
|
||||
position: relative;
|
||||
width: 34px;
|
||||
height: 41px;
|
||||
background-image: url("/src/shared/assets/images/mark.png");
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
line-height: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close {
|
||||
background: rgb(67, 160, 71);
|
||||
background-image: -moz-linear-gradient(top, rgba(67, 160, 71, 1) 0%, rgba(56, 142, 60, 1) 100%);
|
||||
background-image: -webkit-linear-gradient(top, rgba(67, 160, 71, 1) 0%, rgba(56, 142, 60, 1) 100%);
|
||||
background-image: linear-gradient(to bottom, rgba(67, 160, 71, 1) 0%, rgba(56, 142, 60, 1) 100%);
|
||||
filter: progid: DXImageTransform.Microsoft.gradient( startColorstr='#43a047', endColorstr='#388e3c', GradientType=0);
|
||||
cursor: pointer;
|
||||
font-family: "Lato";
|
||||
padding: 7px 22px 11px;
|
||||
color: #ffffff;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #328436;
|
||||
-webkit-transition-property: background;
|
||||
-webkit-transition-duration: 0.5s;
|
||||
-webkit-transition-timing-function: ease;
|
||||
}
|
||||
|
||||
.close:hover {
|
||||
background: #328436 !important;
|
||||
color: #ffffff !important;
|
||||
border: 1px solid #328436 !important;
|
||||
}
|
||||
|
||||
</style>
|
||||
165
src/entities/changeSideWorld.vue
Normal file
165
src/entities/changeSideWorld.vue
Normal file
@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<div class="compass">
|
||||
<div
|
||||
class="compassImage"
|
||||
ref="wrapper"
|
||||
:style="`transform: rotate(${props.formData.light_angle}deg)`"
|
||||
@mouseleave="drag = false"
|
||||
@mousemove="mousemoveHandler($event)"
|
||||
>
|
||||
<svg
|
||||
width="300"
|
||||
height="300"
|
||||
viewBox="0 0 300 300"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M150 299C232.29 299 299 232.29 299 150C299 67.7096 232.29 1 150 1C67.7096 1 1 67.7096 1 150C1 232.29 67.7096 299 150 299Z"
|
||||
stroke="#9E9E9E"
|
||||
stroke-width="2"
|
||||
stroke-dasharray="8 8"
|
||||
/>
|
||||
<path
|
||||
d="M150 225C191.421 225 225 191.421 225 150C225 108.579 191.421 75 150 75"
|
||||
stroke="#9E9E9E"
|
||||
stroke-width="2"
|
||||
/>
|
||||
<path
|
||||
d="M150 225C108.579 225 75 191.421 75 150C75 108.579 108.579 75 150 75"
|
||||
stroke="#F2B720"
|
||||
stroke-width="2"
|
||||
stroke-dasharray="10 10"
|
||||
/>
|
||||
<path
|
||||
d="M166 75.5003L149.5 85.4596V65.541L166 75.5003Z"
|
||||
fill="#F2B720"
|
||||
/>
|
||||
<path
|
||||
d="M150 230C152.761 230 155 227.761 155 225C155 222.239 152.761 220 150 220C147.239 220 145 222.239 145 225C145 227.761 147.239 230 150 230Z"
|
||||
fill="#F2B720"
|
||||
/>
|
||||
<path
|
||||
d="M143 144.038L146.24 153.76L155.962 157L175 125L143 144.038Z"
|
||||
fill="#909090"
|
||||
/>
|
||||
<path d="M124 176L156 156.962L143.038 144L124 176Z" fill="#FF3838" />
|
||||
<path
|
||||
d="M87.3698 143.874L82.693 145.682C82.6285 145.706 82.5704 145.74 82.5131 145.773C81.7921 144.491 80.7754 143.449 79.5817 142.702C79.6184 142.645 79.6543 142.586 79.6821 142.524L81.7109 137.939C81.8464 137.631 81.8546 137.291 81.7337 136.978C81.6129 136.666 81.378 136.419 81.0709 136.283C80.7643 136.148 80.4226 136.139 80.1105 136.26C79.7983 136.38 79.5513 136.616 79.4159 136.923L77.3871 141.508C77.3584 141.571 77.3392 141.636 77.3225 141.702C75.9663 141.321 74.5116 141.268 73.0769 141.598C73.0633 141.533 73.0479 141.468 73.0232 141.404L71.2156 136.727C70.9661 136.082 70.2386 135.76 69.5934 136.009C68.9479 136.258 68.6257 136.986 68.8753 137.632L70.6829 142.309C70.7078 142.373 70.7411 142.431 70.7743 142.488C69.4909 143.21 68.4506 144.226 67.7025 145.42C67.6463 145.384 67.588 145.348 67.5245 145.32L62.94 143.291C62.6329 143.156 62.2922 143.147 61.9795 143.268C61.6668 143.388 61.4203 143.624 61.2844 143.931C61.1493 144.238 61.1402 144.579 61.2611 144.891C61.382 145.204 61.6178 145.45 61.9243 145.586L66.5095 147.615C66.5727 147.643 66.6375 147.662 66.7038 147.68C66.3227 149.035 66.2708 150.49 66.5999 151.924C66.5344 151.938 66.4691 151.952 66.4051 151.977L61.7284 153.785C61.0828 154.034 60.7607 154.762 61.0102 155.408C61.2597 156.053 61.9877 156.375 62.6333 156.126L67.31 154.318C67.3745 154.293 67.433 154.261 67.4904 154.227C68.2114 155.51 69.2277 156.551 70.4223 157.298C70.3857 157.355 70.3497 157.413 70.3215 157.476L68.2932 162.061C68.1577 162.368 68.1495 162.709 68.2704 163.021C68.3912 163.334 68.6261 163.58 68.9332 163.717C69.2398 163.852 69.5809 163.861 69.8936 163.74C70.2058 163.619 70.4528 163.383 70.5882 163.077L72.617 158.492C72.6457 158.429 72.6649 158.363 72.6816 158.297C74.0368 158.678 75.4912 158.731 76.9263 158.402C76.9399 158.467 76.9543 158.532 76.979 158.596L78.7866 163.273C79.0362 163.918 79.7642 164.241 80.4097 163.991C81.0549 163.742 81.3765 163.014 81.127 162.368L79.3193 157.692C79.2944 157.627 79.2616 157.569 79.2289 157.512C80.5117 156.791 81.5525 155.774 82.3002 154.58C82.3572 154.617 82.4155 154.652 82.4786 154.68L87.0632 156.709C87.3704 156.844 87.711 156.853 88.0237 156.732C88.3364 156.611 88.5829 156.375 88.7189 156.069C88.854 155.762 88.863 155.421 88.7422 155.109C88.6213 154.796 88.3855 154.55 88.0789 154.413L83.4941 152.386C83.4306 152.357 83.3653 152.338 83.2998 152.321C83.6805 150.965 83.7328 149.511 83.4037 148.077C83.4691 148.063 83.5341 148.047 83.5982 148.023L88.2749 146.215C88.9205 145.966 89.2426 145.238 88.9931 144.592C88.7432 143.947 88.0153 143.624 87.3698 143.874ZM80.4705 147.886C81.636 150.902 80.1307 154.303 77.1153 155.468C74.0999 156.634 70.6986 155.129 69.5331 152.114C68.3676 149.098 69.8728 145.697 72.8882 144.532C75.9036 143.366 79.305 144.871 80.4705 147.886Z"
|
||||
fill="#F2B720"
|
||||
/>
|
||||
<path
|
||||
d="M80.4705 147.886C81.636 150.902 80.1307 154.303 77.1153 155.468C74.0999 156.634 70.6986 155.129 69.5331 152.114C68.3676 149.098 69.8728 145.697 72.8882 144.532C75.9036 143.366 79.305 144.871 80.4705 147.886Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M155.466 41.0002H153.705C153.6 40.4936 153.418 40.0485 153.158 39.665C152.902 39.2814 152.589 38.9595 152.22 38.6991C151.856 38.4339 151.451 38.235 151.006 38.1025C150.561 37.9699 150.097 37.9036 149.614 37.9036C148.733 37.9036 147.935 38.1261 147.22 38.5712C146.51 39.0163 145.944 39.6721 145.523 40.5386C145.106 41.405 144.898 42.468 144.898 43.7275C144.898 44.9869 145.106 46.0499 145.523 46.9164C145.944 47.7829 146.51 48.4386 147.22 48.8837C147.935 49.3288 148.733 49.5513 149.614 49.5513C150.097 49.5513 150.561 49.485 151.006 49.3525C151.451 49.2199 151.856 49.0234 152.22 48.763C152.589 48.4978 152.902 48.1735 153.158 47.79C153.418 47.4017 153.6 46.9566 153.705 46.4547H155.466C155.333 47.1981 155.092 47.8634 154.741 48.4505C154.391 49.0376 153.955 49.5371 153.435 49.9491C152.914 50.3563 152.329 50.6664 151.68 50.8795C151.036 51.0925 150.348 51.1991 149.614 51.1991C148.373 51.1991 147.27 50.896 146.304 50.29C145.338 49.6839 144.578 48.8222 144.024 47.7047C143.47 46.5873 143.193 45.2616 143.193 43.7275C143.193 42.1934 143.47 40.8676 144.024 39.7502C144.578 38.6328 145.338 37.771 146.304 37.165C147.27 36.5589 148.373 36.2559 149.614 36.2559C150.348 36.2559 151.036 36.3624 151.68 36.5755C152.329 36.7886 152.914 37.1011 153.435 37.513C153.955 37.9202 154.391 38.4173 154.741 39.0045C155.092 39.5868 155.333 40.2521 155.466 41.0002Z"
|
||||
fill="#9E9E9E"
|
||||
/>
|
||||
<path
|
||||
d="M145.524 244.455V259H143.762V244.455H145.524ZM148.698 251.074V252.636H144.245V251.074H148.698ZM161.035 251.727C161.035 253.261 160.758 254.587 160.204 255.705C159.65 256.822 158.89 257.684 157.924 258.29C156.958 258.896 155.855 259.199 154.615 259.199C153.374 259.199 152.271 258.896 151.305 258.29C150.339 257.684 149.579 256.822 149.025 255.705C148.471 254.587 148.194 253.261 148.194 251.727C148.194 250.193 148.471 248.867 149.025 247.75C149.579 246.633 150.339 245.771 151.305 245.165C152.271 244.559 153.374 244.256 154.615 244.256C155.855 244.256 156.958 244.559 157.924 245.165C158.89 245.771 159.65 246.633 160.204 247.75C160.758 248.867 161.035 250.193 161.035 251.727ZM159.331 251.727C159.331 250.468 159.12 249.405 158.698 248.538C158.282 247.672 157.716 247.016 157.001 246.571C156.291 246.126 155.495 245.903 154.615 245.903C153.734 245.903 152.936 246.126 152.221 246.571C151.511 247.016 150.945 247.672 150.524 248.538C150.107 249.405 149.899 250.468 149.899 251.727C149.899 252.987 150.107 254.05 150.524 254.916C150.945 255.783 151.511 256.438 152.221 256.884C152.936 257.329 153.734 257.551 154.615 257.551C155.495 257.551 156.291 257.329 157.001 256.884C157.716 256.438 158.282 255.783 158.698 254.916C159.12 254.05 159.331 252.987 159.331 251.727Z"
|
||||
fill="#9E9E9E"
|
||||
/>
|
||||
<path
|
||||
d="M236.762 157V142.455H241.848C242.861 142.455 243.697 142.63 244.355 142.98C245.013 143.326 245.503 143.792 245.825 144.379C246.147 144.962 246.308 145.608 246.308 146.318C246.308 146.943 246.197 147.459 245.974 147.866C245.756 148.274 245.467 148.596 245.108 148.832C244.752 149.069 244.367 149.244 243.95 149.358V149.5C244.395 149.528 244.842 149.685 245.292 149.969C245.742 150.253 246.118 150.66 246.421 151.19C246.724 151.721 246.876 152.369 246.876 153.136C246.876 153.866 246.71 154.521 246.379 155.104C246.047 155.686 245.524 156.148 244.809 156.489C244.094 156.83 243.164 157 242.018 157H236.762ZM238.524 155.438H242.018C243.169 155.438 243.985 155.215 244.468 154.77C244.956 154.32 245.2 153.776 245.2 153.136C245.2 152.644 245.074 152.189 244.823 151.773C244.572 151.351 244.215 151.015 243.751 150.764C243.287 150.509 242.738 150.381 242.103 150.381H238.524V155.438ZM238.524 148.847H241.791C242.321 148.847 242.799 148.742 243.225 148.534C243.656 148.326 243.997 148.032 244.248 147.653C244.504 147.275 244.632 146.83 244.632 146.318C244.632 145.679 244.409 145.137 243.964 144.692C243.519 144.242 242.814 144.017 241.848 144.017H238.524V148.847Z"
|
||||
fill="#9E9E9E"
|
||||
/>
|
||||
<path
|
||||
d="M38.4198 157.199C37.4823 157.199 36.6466 157.038 35.9127 156.716C35.1835 156.394 34.6035 155.946 34.1726 155.374C33.7465 154.796 33.5145 154.126 33.4766 153.364H35.2663C35.3042 153.832 35.4652 154.237 35.7493 154.578C36.0334 154.914 36.4051 155.175 36.8644 155.359C37.3236 155.544 37.8326 155.636 38.3913 155.636C39.0163 155.636 39.5703 155.527 40.0533 155.31C40.5362 155.092 40.915 154.789 41.1896 154.401C41.4643 154.012 41.6016 153.562 41.6016 153.051C41.6016 152.516 41.469 152.045 41.2038 151.638C40.9387 151.226 40.5504 150.904 40.0391 150.672C39.5277 150.44 38.9027 150.324 38.1641 150.324H36.9993V148.761H38.1641C38.7417 148.761 39.2484 148.657 39.684 148.449C40.1243 148.241 40.4676 147.947 40.7138 147.568C40.9647 147.189 41.0902 146.744 41.0902 146.233C41.0902 145.741 40.9813 145.312 40.7635 144.947C40.5457 144.583 40.2379 144.299 39.8402 144.095C39.4472 143.892 38.9832 143.79 38.4482 143.79C37.9463 143.79 37.4728 143.882 37.0277 144.067C36.5874 144.247 36.2275 144.509 35.9482 144.855C35.6688 145.196 35.5173 145.608 35.4936 146.091H33.7891C33.8175 145.329 34.0471 144.661 34.478 144.088C34.9089 143.51 35.4723 143.061 36.1683 142.739C36.8691 142.417 37.6385 142.256 38.4766 142.256C39.3762 142.256 40.148 142.438 40.7919 142.803C41.4359 143.162 41.9306 143.638 42.2763 144.23C42.6219 144.822 42.7948 145.461 42.7948 146.148C42.7948 146.967 42.5793 147.665 42.1484 148.243C41.7223 148.821 41.1423 149.221 40.4084 149.443V149.557C41.327 149.708 42.0443 150.099 42.5604 150.729C43.0765 151.354 43.3345 152.128 43.3345 153.051C43.3345 153.842 43.1191 154.552 42.6882 155.182C42.2621 155.807 41.6797 156.299 40.9411 156.659C40.2024 157.019 39.362 157.199 38.4198 157.199Z"
|
||||
fill="#9E9E9E"
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="point"
|
||||
ref="point"
|
||||
@mousedown="mouseDownHandler()"
|
||||
@mouseup="drag = false"
|
||||
></div>
|
||||
</div>
|
||||
<InputText v-model="props.formData.light_angle" @update:modelValue="changedFromInput"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
formData: Object
|
||||
})
|
||||
const point = ref(HTMLElement);
|
||||
const wrapper = ref(HTMLElement);
|
||||
const drag = ref(false);
|
||||
// const degrees = ref("0");
|
||||
let coordPoint = null;
|
||||
let x1 = 0;
|
||||
let y1 = 0;
|
||||
|
||||
const emits = defineEmits(
|
||||
["update:degrees"]
|
||||
);
|
||||
|
||||
const mouseDownHandler = () => {
|
||||
if (wrapper.value && point.value) {
|
||||
drag.value = true;
|
||||
coordPoint = wrapper.value.getBoundingClientRect();
|
||||
x1 = coordPoint.left + coordPoint.width / 2;
|
||||
y1 = coordPoint.top + coordPoint.height / 2;
|
||||
}
|
||||
};
|
||||
|
||||
const changedFromInput = (newVal) => {
|
||||
wrapper.value.style.transform = "rotate(" + newVal + "deg)";
|
||||
}
|
||||
|
||||
const mousemoveHandler = (e) => {
|
||||
if (wrapper.value && point.value && drag.value) {
|
||||
let angle = Math.atan2(e.y - y1, e.x - x1);
|
||||
let degreesEd = angle * 57.3;
|
||||
|
||||
if (angle < 0) {
|
||||
degreesEd = 360 + degreesEd;
|
||||
}
|
||||
|
||||
wrapper.value.style.transform = "rotate(" + degreesEd + "deg)";
|
||||
props.formData.light_angle = String(Math.floor(degreesEd));
|
||||
}
|
||||
};
|
||||
|
||||
watch(props.formData.light_angle, (newV) => {
|
||||
if (wrapper.value) {
|
||||
|
||||
wrapper.value.style.transform = "rotate(" + newV + "deg)";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.compass {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
row-gap: 20px;
|
||||
|
||||
.compassImage {
|
||||
position: relative;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.point {
|
||||
position: absolute;
|
||||
top: calc(50% - 12.5px);
|
||||
right: -12.5px;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
border-radius: 100%;
|
||||
background-color: #3a8efa;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
751
src/entities/complexItem.vue
Normal file
751
src/entities/complexItem.vue
Normal file
@ -0,0 +1,751 @@
|
||||
<template>
|
||||
<div class="complexItem">
|
||||
<div class="complexItem__image">
|
||||
<div v-if="isEmpty(complex.images)" class="no-image">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" width="130px" height="130px" viewBox="0 0 217.267 217.267" style="enable-background:new 0 0 217.267 217.267;" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M163.627,138.057c1.94,0,3.505-1.564,3.505-3.505V83.749c0-1.937-1.564-3.504-3.505-3.504 c-1.937,0-3.504,1.567-3.504,3.504v50.803C160.123,136.493,161.69,138.057,163.627,138.057z" fill="#506979"></path>
|
||||
<path d="M108.633,0C48.73,0,0,48.732,0,108.633c0,29.931,12.167,57.071,31.816,76.738c0.014,0.014,0.017,0.034,0.031,0.048 s0.032,0.018,0.046,0.031c19.666,19.646,46.807,31.815,76.74,31.815c59.901,0,108.634-48.731,108.634-108.633S168.531,0,108.633,0 z M108.633,7.009c26.758,0,51.12,10.416,69.285,27.379l-26.936,26.938H52.564c-9.661,0-17.521,7.861-17.521,17.521v59.573 c0,9.661,7.861,17.521,17.521,17.521h3.805l-21.979,21.978C17.42,159.753,7.009,135.394,7.009,108.633 C7.009,52.599,52.595,7.009,108.633,7.009z M86.196,108.63c0-12.363,10.056-22.42,22.417-22.42c4.921,0,9.466,1.615,13.172,4.313 l-1.875,1.875c-3.207-2.24-7.094-3.564-11.297-3.564c-10.913,0-19.789,8.879-19.789,19.792c0,4.195,1.321,8.09,3.559,11.3 l-1.874,1.868C87.811,118.095,86.196,113.554,86.196,108.63z M131.034,108.63c0,12.364-10.054,22.418-22.418,22.418 c-4.915,0-9.449-1.608-13.145-4.298l1.874-1.876c3.203,2.231,7.082,3.546,11.274,3.546c10.91,0,19.794-8.877,19.794-19.791 c0-4.187-1.321-8.071-3.549-11.272l1.875-1.875C129.433,99.181,131.034,103.717,131.034,108.63z M95.833,108.63 c0-7.051,5.735-12.784,12.78-12.784c2.257,0,4.347,0.639,6.184,1.668l-17.304,17.306C96.469,112.972,95.833,110.885,95.833,108.63 z M121.397,108.63c0,7.049-5.735,12.782-12.781,12.782c-2.244,0-4.322-0.633-6.158-1.652l17.29-17.289 C120.769,104.31,121.397,106.383,121.397,108.63z M90.467,131.753c5.005,3.936,11.298,6.311,18.146,6.311 c16.229,0,29.431-13.206,29.431-29.431c0-6.848-2.375-13.141-6.311-18.149l22.148-22.146h10.814 c5.797,0,10.513,4.717,10.513,10.513v59.573c0,5.797-4.716,10.513-10.513,10.513H73.289L90.467,131.753z M126.784,85.523 c-5.007-3.948-11.311-6.326-18.168-6.326c-16.23,0-29.426,13.204-29.426,29.429c0,6.858,2.375,13.158,6.321,18.172l-22.136,22.135 H52.561c-5.797,0-10.513-4.716-10.513-10.514V78.847c0-5.795,4.716-10.513,10.513-10.513h91.413L126.784,85.523z M108.633,210.257 c-26.759,0-51.12-10.417-69.287-27.377l26.934-26.939h98.422c9.66,0,17.521-7.86,17.521-17.521V78.847 c0-9.661-7.861-17.521-17.521-17.521h-3.806l21.977-21.979c16.968,18.17,27.381,42.529,27.381,69.287 C210.258,164.667,164.667,210.257,108.633,210.257z" fill="#506979"></path>
|
||||
<path d="M53.002,54.317h23.654c1.937,0,3.504-1.567,3.504-3.504s-1.567-3.504-3.504-3.504H53.002 c-1.938,0-3.504,1.567-3.504,3.504S51.064,54.317,53.002,54.317z" fill="#506979"></path>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<Galleria
|
||||
v-else
|
||||
:value="complex.images"
|
||||
:responsiveOptions="responsiveOptions"
|
||||
:showThumbnails="false"
|
||||
:showItemNavigators="complex.images.length > 1"
|
||||
:numVisible="5"
|
||||
:showItemNavigatorsOnHover="complex.images.length > 1"
|
||||
containerStyle="max-width: 640px">
|
||||
<template #item="slotProps">
|
||||
<img :src="slotProps.item.image" :alt="slotProps.item.name" style="width: 100%;" />
|
||||
</template>
|
||||
</Galleria>
|
||||
</div>
|
||||
|
||||
<RouterLink style="display: flex" :to="{path: complexLink, query: $route.query }">
|
||||
<div class="complexItem__content">
|
||||
<div class="complexItem__head">
|
||||
<div v-if="!route.meta.isViewPage" class="complexItem__nav">
|
||||
<div class="complexItem__develop" v-html="complex.builder.name"></div>
|
||||
<div class="complexItem__btnMenu">
|
||||
<Button style="background: transparent; border: none; padding: 0 !important" type="button" @click="toggle" aria-haspopup="true" aria-controls="overlay_menu">
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M10.591 17.341C9.71231 18.2196 8.28768 18.2196 7.409 17.341C6.53032 16.4623 6.53032 15.0377 7.409 14.159C8.28768 13.2803 9.71231 13.2803 10.591 14.159C11.4697 15.0377 11.4697 16.4623 10.591 17.341Z"
|
||||
fill="#BDBDBD"
|
||||
/>
|
||||
<path
|
||||
d="M10.591 10.591C9.71231 11.4697 8.28768 11.4697 7.409 10.591C6.53032 9.71231 6.53032 8.28768 7.409 7.40901C8.28768 6.53033 9.71231 6.53033 10.591 7.40901C11.4697 8.28768 11.4697 9.71231 10.591 10.591Z"
|
||||
fill="#BDBDBD"
|
||||
/>
|
||||
<path
|
||||
d="M10.591 3.84098C9.71231 4.71966 8.28768 4.71966 7.409 3.84098C6.53032 2.96231 6.53032 1.53768 7.409 0.659007C8.28768 -0.219669 9.71231 -0.219669 10.591 0.659007C11.4697 1.53768 11.4697 2.96231 10.591 3.84098Z"
|
||||
fill="#BDBDBD"
|
||||
/>
|
||||
</svg>
|
||||
</Button>
|
||||
<Menu :pt="{ root: { class: ['custom-p-menu'] } }" ref="menuItems" id="overlay_menu" :model="items" :popup="true" />
|
||||
</div>
|
||||
<!-- <div class="complexItem__btnMenu">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M19.2678 8.23232C20.2441 9.20863 20.2441 10.7916 19.2678 11.7679C18.2915 12.7442 16.7085 12.7442 15.7322 11.7679C14.7559 10.7916 14.7559 9.20863 15.7322 8.23232C16.7085 7.25601 18.2915 7.25601 19.2678 8.23232Z"
|
||||
fill="#BDBDBD"
|
||||
/>
|
||||
<path
|
||||
d="M11.7678 8.23232C12.7441 9.20863 12.7441 10.7916 11.7678 11.7679C10.7915 12.7442 9.20854 12.7442 8.23223 11.7679C7.25592 10.7916 7.25592 9.20863 8.23223 8.23232C9.20854 7.25601 10.7915 7.25601 11.7678 8.23232Z"
|
||||
fill="#BDBDBD"
|
||||
/>
|
||||
<path
|
||||
d="M4.26776 8.23232C5.24406 9.20863 5.24406 10.7916 4.26776 11.7679C3.29145 12.7442 1.70854 12.7442 0.73223 11.7679C-0.244077 10.7916 -0.244077 9.20863 0.73223 8.23232C1.70854 7.25601 3.29145 7.25601 4.26776 8.23232Z"
|
||||
fill="#BDBDBD"
|
||||
/>
|
||||
</svg>
|
||||
</div> -->
|
||||
</div>
|
||||
<div class="complexItem__title">{{ complex.name }}</div>
|
||||
</div>
|
||||
<div class="complexItem__location">
|
||||
<div class="complexItem__metroList">
|
||||
<template v-if="complex.metros">
|
||||
<div class="complexItem__metro" v-for="(metro, index) in complex.metros" :key="index">
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M13.242 2.24765L9.99989 12.4689L6.75798 2.24765C2.90144 3.57322 0 7.16913 0 11.2916C0 13.7746 1.01298 16.0644 2.65178 17.752H7.35212L7.84488 15.6151C2.22957 13.4149 3.29134 6.59717 5.32701 5.48428C5.56864 5.55694 9.10942 17.7017 9.10942 17.7017C9.15779 17.7017 9.31493 17.7017 9.50692 17.7017C9.54747 17.7017 9.70018 17.7017 9.89048 17.7017C9.95934 17.7017 10.0337 17.7017 10.1091 17.7017C10.2384 17.7017 10.3725 17.7017 10.4924 17.7017C10.6846 17.7017 10.8416 17.7017 10.8902 17.7017C10.8902 17.7017 14.4311 5.55694 14.6726 5.48428C16.7085 6.59717 17.7698 13.4149 12.1545 15.6151L12.6475 17.752H17.3478C18.9868 16.0644 19.9998 13.7746 19.9998 11.2916C19.9998 7.16913 17.0983 3.57322 13.242 2.24765Z"
|
||||
fill="#8EB3F5"
|
||||
/>
|
||||
</svg>
|
||||
{{ metro.metro }}
|
||||
<div class="time">{{ metro.subway_time}} минут <span v-if="metro.subway_distance == 1">пешком</span><span v-else-if="metro.subway_distance == 2">на транспорте</span></div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="complexItem__address">
|
||||
{{ complex.address.address }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="complexItem__change">
|
||||
|
||||
<span v-for="(corpus) in complex.corpuses"
|
||||
:key="corpus.id">
|
||||
{{ corpus.line }} очередь. Корпус {{ corpus.corpus }} - {{ corpus.deadline}}
|
||||
</span>
|
||||
</div>
|
||||
<!-- <div class="complexItem__advertisement" v-if="!route.meta.isViewPage">
|
||||
<RouterLink :to="baseLink" class="complexItem__advertisementLink">
|
||||
Реклама
|
||||
</RouterLink>
|
||||
</div>-->
|
||||
<div class="complexItem__counts">
|
||||
<RouterLink :to="{path: complexLink, query: $route.query }" class="complexItem__houseLink" style="width: 100px;">
|
||||
Домов: <strong>{{ complex.houses_total }}</strong>
|
||||
</RouterLink>
|
||||
<RouterLink :to="apartmentsLink" class="complexItem__roomLink" style="width: 100px;">
|
||||
Помещений: <strong>{{ complex.total_rooms}}</strong>
|
||||
</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
</RouterLink>
|
||||
<ConfirmPopup group="deleteHouse" :pt="confirmpopupStyle"></ConfirmPopup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { confirmpopupStyle, isEmpty } from "@/shared/utils/util";
|
||||
import { computed } from "vue";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import Button from 'primevue/button'
|
||||
import Menu from 'primevue/menu'
|
||||
import ConfirmPopup from "primevue/confirmpopup";
|
||||
import { useConfirm } from "primevue/useconfirm";
|
||||
import ModalLayout from "@/shared/UI/modalLayout.vue";
|
||||
import Dialog from "primevue/dialog";
|
||||
|
||||
const visible = ref(true)
|
||||
const confirm = useConfirm()
|
||||
const props = defineProps({
|
||||
complex: {},
|
||||
onComplexEdit: Function,
|
||||
onComplexArchive: Function,
|
||||
onComplexDelete: Function,
|
||||
})
|
||||
const route = useRoute()
|
||||
|
||||
const menuItems = ref();
|
||||
const items = ref();
|
||||
|
||||
const showDeleteTemplate = (event, index) => {
|
||||
confirm.require({
|
||||
target: event.currentTarget,
|
||||
group: "deleteComplex",
|
||||
message: "Удалить Комплекс?",
|
||||
acceptLabel: "Да",
|
||||
rejectLabel: "Нет",
|
||||
acceptClass: "!bg-green !shadow-none",
|
||||
rejectClass: "!bg-red !shadow-none",
|
||||
accept: () => {
|
||||
// emits('deleteItem')
|
||||
},
|
||||
reject: () => {
|
||||
return;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const showArchiveTemplate = (event, index) => {
|
||||
confirm.require({
|
||||
target: event.currentTarget,
|
||||
group: "deleteComplex",
|
||||
message: "Перенести Комплекс в архив?",
|
||||
acceptLabel: "Да",
|
||||
rejectLabel: "Нет",
|
||||
acceptClass: "!bg-green !shadow-none",
|
||||
rejectClass: "!bg-red !shadow-none",
|
||||
accept: () => {
|
||||
emits('deleteItem')
|
||||
},
|
||||
reject: () => {
|
||||
return;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (route.name != 'archive-main') {
|
||||
items.value = [
|
||||
{
|
||||
items: [
|
||||
{
|
||||
label: 'Перенести в архив',
|
||||
command: () => props.onComplexArchive()
|
||||
},
|
||||
|
||||
{
|
||||
label: 'Редактировать',
|
||||
command: () => props.onComplexEdit()
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
} else {
|
||||
items.value = [
|
||||
{
|
||||
items: [
|
||||
{
|
||||
label: 'Вернуть из архива',
|
||||
command: () => props.onComplexArchive()
|
||||
},
|
||||
|
||||
{
|
||||
label: 'Удалить',
|
||||
command: () => props.onComplexDelete()
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
const toggle = (event) => {
|
||||
event.stopPropagation()
|
||||
event.preventDefault();
|
||||
menuItems.value.toggle(event);
|
||||
};
|
||||
|
||||
const baseLink = computed(() => {
|
||||
return route.name == 'complexes-list' ? '/view' : '/my-objects'
|
||||
})
|
||||
const complexLink = computed(() => {
|
||||
return `${baseLink.value}/${props.complex.id}`
|
||||
})
|
||||
|
||||
const apartmentsLink = computed(() => {
|
||||
return {path: complexLink.value + '/apartments', query: route.query }
|
||||
})
|
||||
|
||||
const responsiveOptions = [
|
||||
{
|
||||
breakpoint: '1600px',
|
||||
numVisible: 4
|
||||
},
|
||||
{
|
||||
breakpoint: '976px',
|
||||
numVisible: 3
|
||||
},
|
||||
{
|
||||
breakpoint: '200px',
|
||||
numVisible: 1
|
||||
}
|
||||
];
|
||||
|
||||
const change = computed(() => {
|
||||
const queue = "1 очередь";
|
||||
const corps = "Корпус 1";
|
||||
const status = "Сдан";
|
||||
return `${queue}. ${corps} - ${status}`;
|
||||
});
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.p-button:focus {
|
||||
box-shadow: unset !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/app/styles/function.scss";
|
||||
|
||||
|
||||
.complexItem {
|
||||
width: 100%;
|
||||
height: 353px;
|
||||
display: grid;
|
||||
grid-template-columns: vw(400, 1920) 1fr;
|
||||
@apply bg-white;
|
||||
overflow: hidden;
|
||||
|
||||
.no-image{
|
||||
height: 353px;
|
||||
|
||||
svg {
|
||||
top: 60px;
|
||||
left: 50%;
|
||||
margin-left: -65px;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.complexItem__title {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
&.medium {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
grid-template-columns: 1fr;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
|
||||
.complexItem__content {
|
||||
grid-template-columns: 1fr;
|
||||
padding: 16px;
|
||||
}
|
||||
.complexItem__head {
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
border-bottom: 1px solid;
|
||||
@apply border-grey;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
.complexItem__nav {
|
||||
width: 100%;
|
||||
margin-left: 0;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.complexItem__btnMenu {
|
||||
margin-left: auto;
|
||||
}
|
||||
.complexItem__title {
|
||||
font-size: 18px;
|
||||
}
|
||||
.complexItem__location {
|
||||
width: 100%;
|
||||
padding: 16px 0;
|
||||
}
|
||||
.complexItem__change {
|
||||
grid-column: 1;
|
||||
grid-row: 3;
|
||||
border-left: 0;
|
||||
padding: 16px 0;
|
||||
max-height: 130px;
|
||||
overflow: auto;
|
||||
}
|
||||
.complexItem__advertisement {
|
||||
grid-row: 4;
|
||||
grid-column: 1;
|
||||
padding: 16px 0;
|
||||
}
|
||||
.complexItem__counts {
|
||||
grid-row: 5;
|
||||
grid-column: 1;
|
||||
padding: 16px 0 0 0;
|
||||
|
||||
strong {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
height: 343px;
|
||||
}
|
||||
}
|
||||
.no-image{
|
||||
height: 343px;
|
||||
|
||||
svg {
|
||||
top: 60px;
|
||||
left: 50%;
|
||||
margin-left: -65px;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 0.8fr;
|
||||
}
|
||||
&__head {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
grid-row: 1;
|
||||
grid-column: span 2;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
padding: 30px;
|
||||
border-bottom: 1px solid;
|
||||
@apply border-grey;
|
||||
}
|
||||
|
||||
&__title {
|
||||
width: max-content;
|
||||
position: relative;
|
||||
font-size: 27px;
|
||||
line-height: 100%;
|
||||
|
||||
&::before {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
@apply bg-black;
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: -2px;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__nav {
|
||||
width: auto;
|
||||
height: auto;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
&__develop {
|
||||
@apply text-grey-900;
|
||||
font-size: 15px;
|
||||
line-height: 100%;
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
&__btnMenu {
|
||||
width: max-content;
|
||||
height: max-content;
|
||||
svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
&__location {
|
||||
grid-row: 2;
|
||||
grid-column: 1;
|
||||
width: 100%;
|
||||
padding: 20px 30px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
border-bottom: 1px solid;
|
||||
@apply border-grey;
|
||||
}
|
||||
&__metro {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
column-gap: 10px;
|
||||
.time {
|
||||
@apply text-grey-900;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
&__change {
|
||||
width: 100%;
|
||||
grid-row: 2;
|
||||
grid-column: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
border-left: 1px solid;
|
||||
border-bottom: 1px solid;
|
||||
@apply border-grey;
|
||||
padding: 20px 30px;
|
||||
font-size: 15px;
|
||||
max-height: 130px;
|
||||
overflow: auto;
|
||||
}
|
||||
&__advertisement {
|
||||
width: 100%;
|
||||
grid-row: 3;
|
||||
grid-column: 1;
|
||||
padding: 18px 30px;
|
||||
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
&__advertisementLink {
|
||||
border-radius: 3px;
|
||||
border: 1px solid;
|
||||
@apply border-green;
|
||||
background: var(
|
||||
--linear,
|
||||
linear-gradient(180deg, #fefefe 0%, #f6f6f6 100%)
|
||||
);
|
||||
padding: 8px 12px;
|
||||
@apply text-green;
|
||||
@apply transition-colors;
|
||||
&:hover {
|
||||
background: transparent;
|
||||
@apply bg-green;
|
||||
@apply text-white;
|
||||
@apply transition-colors;
|
||||
}
|
||||
}
|
||||
&__counts {
|
||||
grid-row: 3;
|
||||
grid-column: 2;
|
||||
padding: 18px 30px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
column-gap: 10px;
|
||||
border-left: 1px solid;
|
||||
@apply border-grey;
|
||||
|
||||
a {
|
||||
padding: 8px 20px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #dedede;
|
||||
background: var(
|
||||
--linear,
|
||||
linear-gradient(180deg, #fefefe 0%, #f6f6f6 100%)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
&__metroList {
|
||||
max-height: 50px;
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: 2000px) {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
display: grid;
|
||||
grid-template-columns: vw(300, 1440) 1fr;
|
||||
@apply bg-white;
|
||||
|
||||
&__image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
height: 300px;
|
||||
}
|
||||
}
|
||||
.no-image{
|
||||
height: 300px;
|
||||
|
||||
svg {
|
||||
top: 60px;
|
||||
left: 50%;
|
||||
margin-left: -65px;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1900px) {
|
||||
width: 100%;
|
||||
height: 270px;
|
||||
display: grid;
|
||||
grid-template-columns: vw(300, 1440) 1fr;
|
||||
@apply bg-white;
|
||||
&__image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
height: 270px;
|
||||
}
|
||||
}
|
||||
.no-image{
|
||||
height: 270px;
|
||||
|
||||
svg {
|
||||
top: 60px;
|
||||
left: 50%;
|
||||
margin-left: -65px;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.complexItem__btnMenu {
|
||||
box-shadow: unset !important;
|
||||
svg {
|
||||
width: 18px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1600px) {
|
||||
width: 100%;
|
||||
height: 250px;
|
||||
display: grid;
|
||||
grid-template-columns: vw(300, 1440) 1fr;
|
||||
@apply bg-white;
|
||||
|
||||
&.medium {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
grid-template-columns: 1fr;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
|
||||
.complexItem__content {
|
||||
grid-template-columns: 1fr;
|
||||
padding: 16px;
|
||||
}
|
||||
/*.complexItem__image {
|
||||
height: 280px;
|
||||
}*/
|
||||
.complexItem__head {
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
border-bottom: 1px solid;
|
||||
@apply border-grey;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
.complexItem__nav {
|
||||
width: 100%;
|
||||
margin-left: 0;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.complexItem__btnMenu {
|
||||
margin-left: auto;
|
||||
}
|
||||
.complexItem__title {
|
||||
font-size: 18px;
|
||||
}
|
||||
.complexItem__location {
|
||||
width: 100%;
|
||||
padding: 16px 0;
|
||||
}
|
||||
.complexItem__change {
|
||||
border-left: 0;
|
||||
padding: 16px 0;
|
||||
max-height: 130px;
|
||||
overflow: auto;
|
||||
}
|
||||
.complexItem__advertisement {
|
||||
padding: 16px 0;
|
||||
}
|
||||
.complexItem__counts {
|
||||
padding: 16px 0 0 0;
|
||||
}
|
||||
}
|
||||
&__image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
height: 250px;
|
||||
}
|
||||
}
|
||||
.no-image{
|
||||
height: 250px;
|
||||
|
||||
svg {
|
||||
top: 60px;
|
||||
left: 50%;
|
||||
margin-left: -65px;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 0.8fr;
|
||||
}
|
||||
&__head {
|
||||
padding: 26px;
|
||||
border-bottom: 1px solid;
|
||||
@apply border-grey;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
&__nav {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
&__develop {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&__location {
|
||||
padding: 16px 25px;
|
||||
}
|
||||
&__metro {
|
||||
column-gap: 8px;
|
||||
margin-bottom: 0;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
}
|
||||
&__change {
|
||||
padding: 16px 25px;
|
||||
font-size: 14px;
|
||||
}
|
||||
&__advertisement {
|
||||
padding: 16px 25px;
|
||||
}
|
||||
&__advertisementLink {
|
||||
padding: 6px 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
&__counts {
|
||||
column-gap: 8px;
|
||||
|
||||
a {
|
||||
padding: 8px 18px;
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.complexItem__content {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
424
src/entities/complexItemIframe.vue
Normal file
424
src/entities/complexItemIframe.vue
Normal file
@ -0,0 +1,424 @@
|
||||
<template>
|
||||
<div class="item">
|
||||
<div class="no-image-wrapper">
|
||||
<!-- Если изображений нет, показать SVG -->
|
||||
<div v-if="!complex.images || complex.images.length === 0" class="no-image">
|
||||
<svg data-v-0974c00c="" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" width="130px" height="130px" viewBox="0 0 217.267 217.267" xml:space="preserve"><g data-v-0974c00c=""><path data-v-0974c00c="" d="M163.627,138.057c1.94,0,3.505-1.564,3.505-3.505V83.749c0-1.937-1.564-3.504-3.505-3.504 c-1.937,0-3.504,1.567-3.504,3.504v50.803C160.123,136.493,161.69,138.057,163.627,138.057z" fill="#506979"></path><path data-v-0974c00c="" d="M108.633,0C48.73,0,0,48.732,0,108.633c0,29.931,12.167,57.071,31.816,76.738c0.014,0.014,0.017,0.034,0.031,0.048 s0.032,0.018,0.046,0.031c19.666,19.646,46.807,31.815,76.74,31.815c59.901,0,108.634-48.731,108.634-108.633S168.531,0,108.633,0 z M108.633,7.009c26.758,0,51.12,10.416,69.285,27.379l-26.936,26.938H52.564c-9.661,0-17.521,7.861-17.521,17.521v59.573 c0,9.661,7.861,17.521,17.521,17.521h3.805l-21.979,21.978C17.42,159.753,7.009,135.394,7.009,108.633 C7.009,52.599,52.595,7.009,108.633,7.009z M86.196,108.63c0-12.363,10.056-22.42,22.417-22.42c4.921,0,9.466,1.615,13.172,4.313 l-1.875,1.875c-3.207-2.24-7.094-3.564-11.297-3.564c-10.913,0-19.789,8.879-19.789,19.792c0,4.195,1.321,8.09,3.559,11.3 l-1.874,1.868C87.811,118.095,86.196,113.554,86.196,108.63z M131.034,108.63c0,12.364-10.054,22.418-22.418,22.418 c-4.915,0-9.449-1.608-13.145-4.298l1.874-1.876c3.203,2.231,7.082,3.546,11.274,3.546c10.91,0,19.794-8.877,19.794-19.791 c0-4.187-1.321-8.071-3.549-11.272l1.875-1.875C129.433,99.181,131.034,103.717,131.034,108.63z M95.833,108.63 c0-7.051,5.735-12.784,12.78-12.784c2.257,0,4.347,0.639,6.184,1.668l-17.304,17.306C96.469,112.972,95.833,110.885,95.833,108.63 z M121.397,108.63c0,7.049-5.735,12.782-12.781,12.782c-2.244,0-4.322-0.633-6.158-1.652l17.29-17.289 C120.769,104.31,121.397,106.383,121.397,108.63z M90.467,131.753c5.005,3.936,11.298,6.311,18.146,6.311 c16.229,0,29.431-13.206,29.431-29.431c0-6.848-2.375-13.141-6.311-18.149l22.148-22.146h10.814 c5.797,0,10.513,4.717,10.513,10.513v59.573c0,5.797-4.716,10.513-10.513,10.513H73.289L90.467,131.753z M126.784,85.523 c-5.007-3.948-11.311-6.326-18.168-6.326c-16.23,0-29.426,13.204-29.426,29.429c0,6.858,2.375,13.158,6.321,18.172l-22.136,22.135 H52.561c-5.797,0-10.513-4.716-10.513-10.514V78.847c0-5.795,4.716-10.513,10.513-10.513h91.413L126.784,85.523z M108.633,210.257 c-26.759,0-51.12-10.417-69.287-27.377l26.934-26.939h98.422c9.66,0,17.521-7.86,17.521-17.521V78.847 c0-9.661-7.861-17.521-17.521-17.521h-3.806l21.977-21.979c16.968,18.17,27.381,42.529,27.381,69.287 C210.258,164.667,164.667,210.257,108.633,210.257z" fill="#506979"></path><path data-v-0974c00c="" d="M53.002,54.317h23.654c1.937,0,3.504-1.567,3.504-3.504s-1.567-3.504-3.504-3.504H53.002 c-1.938,0-3.504,1.567-3.504,3.504S51.064,54.317,53.002,54.317z" fill="#506979"></path></g></svg>
|
||||
</div>
|
||||
|
||||
<!-- Если изображения есть, показать галерею -->
|
||||
<Galleria
|
||||
v-else
|
||||
class="complex-galleria"
|
||||
:value="complex.images"
|
||||
:responsiveOptions="responsiveOptions"
|
||||
:showThumbnails="false"
|
||||
:showItemNavigators="complex.images.length > 1"
|
||||
:numVisible="5"
|
||||
:touchUI="true"
|
||||
:scrollable="true"
|
||||
:showItemNavigatorsOnHover="complex.images.length > 1"
|
||||
containerStyle="max-width: 640px"
|
||||
>
|
||||
<template #item="slotProps">
|
||||
<img
|
||||
:src="slotProps.item.image"
|
||||
:alt="slotProps.item.name"
|
||||
style="width: 100%; height: 300px; object-fit: cover;"
|
||||
/>
|
||||
</template>
|
||||
</Galleria>
|
||||
</div>
|
||||
<div class="complex-info">
|
||||
<div class="complex-title">{{ complex.name }}</div>
|
||||
<div class="line"></div>
|
||||
<div class="complex-address-info">
|
||||
<template v-if="complex.metros">
|
||||
|
||||
<div class="metro-wrapper" v-for="(metro, index) in complex.metros" :key="index">
|
||||
<img
|
||||
src="@/shared/assets/icons/metro.svg"
|
||||
alt=""
|
||||
class="metro-icon"
|
||||
/>
|
||||
<span class="metro-title">{{ metro.metro }}</span>
|
||||
<div class="metro-distance">{{ metro.subway_time}} минут <span v-if="metro.subway_distance == 1">пешком</span><span v-else-if="metro.subway_distance == 2">на транспорте</span></div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="complex-address">{{ complex.address.address }}</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="complex-price-and-count-info">
|
||||
<div class="complex-price">
|
||||
Стоимость: <span>от {{ formatTotalPrice(totalMinPrice) }} руб</span>
|
||||
</div>
|
||||
<div class="count-info">
|
||||
Найдено в ЖК:
|
||||
<span>{{ complex.houses_total }} домов </span>
|
||||
|
||||
|
||||
<span>{{ complex.total_rooms}} квартир</span>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<RouterLink v-if="!isHome" :to="{path: complexLink, query: $route.query }" class="complexItem__roomLink">
|
||||
<div class="primary-btn_all">Посмотреть все дома</div>
|
||||
</RouterLink>
|
||||
<RouterLink v-if="!isHome" :to="{path: complexLink, query: $route.query }" class="complexItem__roomLink">
|
||||
<div class="primary-btn_all mob">Посмотреть дома</div>
|
||||
</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { confirmpopupStyle, isEmpty } from "@/shared/utils/util";
|
||||
import { computed } from "vue";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import Button from "primevue/button";
|
||||
import Menu from "primevue/menu";
|
||||
import ConfirmPopup from "primevue/confirmpopup";
|
||||
import { useConfirm } from "primevue/useconfirm";
|
||||
import ModalLayout from "@/shared/UI/modalLayout.vue";
|
||||
import Dialog from "primevue/dialog";
|
||||
|
||||
const visible = ref(true);
|
||||
const confirm = useConfirm();
|
||||
const props = defineProps({
|
||||
complex: {},
|
||||
onComplexEdit: Function,
|
||||
onComplexArchive: Function,
|
||||
onComplexDelete: Function,
|
||||
isHome: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
minPrice: {
|
||||
type: String,
|
||||
required: false
|
||||
}
|
||||
});
|
||||
const route = useRoute();
|
||||
console.log(props.minPrice);
|
||||
|
||||
const totalMinPrice = props.complex.min_total_amount ? props.complex.min_total_amount : props.minPrice;
|
||||
console.log(totalMinPrice, 'totalMinPrice');
|
||||
|
||||
|
||||
const menuItems = ref();
|
||||
const items = ref();
|
||||
|
||||
const showDeleteTemplate = (event, index) => {
|
||||
confirm.require({
|
||||
target: event.currentTarget,
|
||||
group: "deleteComplex",
|
||||
message: "Удалить Комплекс?",
|
||||
acceptLabel: "Да",
|
||||
rejectLabel: "Нет",
|
||||
acceptClass: "!bg-green !shadow-none",
|
||||
rejectClass: "!bg-red !shadow-none",
|
||||
accept: () => {
|
||||
// emits('deleteItem')
|
||||
},
|
||||
reject: () => {
|
||||
return;
|
||||
},
|
||||
});
|
||||
};
|
||||
const formatTotalPrice = (value) => {
|
||||
if (!value) return '0';
|
||||
return Number(value).toLocaleString('ru-RU');
|
||||
};
|
||||
|
||||
|
||||
const showArchiveTemplate = (event, index) => {
|
||||
confirm.require({
|
||||
target: event.currentTarget,
|
||||
group: "deleteComplex",
|
||||
message: "Перенести Комплекс в архив?",
|
||||
acceptLabel: "Да",
|
||||
rejectLabel: "Нет",
|
||||
acceptClass: "!bg-green !shadow-none",
|
||||
rejectClass: "!bg-red !shadow-none",
|
||||
accept: () => {
|
||||
emits("deleteItem");
|
||||
},
|
||||
reject: () => {
|
||||
return;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (route.name != "archive-main") {
|
||||
items.value = [
|
||||
{
|
||||
items: [
|
||||
{
|
||||
label: "Перенести в архив",
|
||||
command: () => props.onComplexArchive(),
|
||||
},
|
||||
|
||||
{
|
||||
label: "Редактировать",
|
||||
command: () => props.onComplexEdit(),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
} else {
|
||||
items.value = [
|
||||
{
|
||||
items: [
|
||||
{
|
||||
label: "Вернуть из архива",
|
||||
command: () => props.onComplexArchive(),
|
||||
},
|
||||
|
||||
{
|
||||
label: "Удалить",
|
||||
command: () => props.onComplexDelete(),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
const toggle = (event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
menuItems.value.toggle(event);
|
||||
};
|
||||
|
||||
const baseLink = computed(() => {
|
||||
console.log(route.params);
|
||||
|
||||
|
||||
return route.name == "complexes-list" ? `/view` : "/my-objects";
|
||||
});
|
||||
const complexLink = computed(() => {
|
||||
return `/${route.params.token}/view/${props.complex.id}`;
|
||||
});
|
||||
|
||||
const apartmentsLink = computed(() => {
|
||||
return { path: complexLink.value + "/apartments", query: route.query };
|
||||
});
|
||||
|
||||
const responsiveOptions = [
|
||||
{
|
||||
breakpoint: "1600px",
|
||||
numVisible: 4,
|
||||
},
|
||||
{
|
||||
breakpoint: "976px",
|
||||
numVisible: 3,
|
||||
},
|
||||
{
|
||||
breakpoint: "768px",
|
||||
numVisible: 1,
|
||||
},
|
||||
];
|
||||
|
||||
const change = computed(() => {
|
||||
const queue = "1 очередь";
|
||||
const corps = "Корпус 1";
|
||||
const status = "Сдан";
|
||||
return `${queue}. ${corps} - ${status}`;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.no-image {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.item {
|
||||
display: flex;
|
||||
gap: 40px;
|
||||
max-width: 100%;
|
||||
position: relative;
|
||||
/* align-items: center!important; */
|
||||
|
||||
|
||||
& .line {
|
||||
border-top: 1px solid #f3f3f3;
|
||||
/* margin: 20px 0 20px 0; */
|
||||
width: 400px;
|
||||
}
|
||||
& .complex-info > * {
|
||||
flex-grow: 1;
|
||||
}
|
||||
& .complex-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* gap: 20px; */
|
||||
min-width: 100px;
|
||||
|
||||
& .complex-title {
|
||||
font-size: 27px;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
}
|
||||
& .complex-address-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* gap: 20px; */
|
||||
|
||||
& .metro-wrapper {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
|
||||
& .metro-title {
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
color: #222222;
|
||||
}
|
||||
& .metro-distance {
|
||||
color: #757575;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
& .complex-address {
|
||||
font-size: 15px;
|
||||
color: #333333;
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
& .complex-price-and-count-info {
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
& span {
|
||||
color: var(--main-color);
|
||||
font-weight: normal;
|
||||
}
|
||||
& .count-info {
|
||||
& a {
|
||||
text-decoration: underline;
|
||||
color: var(--main-color);
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
& .complex-img {
|
||||
width: 100%; /* Убедитесь, что контейнер занимает 100% доступного пространства */
|
||||
//max-width: 300px; /* Ограничьте максимальную ширину, как указано в containerStyle */
|
||||
height: 300px; /* Устанавливайте фиксированную высоту, если нужно */
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
& .complex-galleria {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
& .img {
|
||||
object-fit: contain; /* Это гарантирует, что изображения будут встраиваться без искажения */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
& .no-image {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
& img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
}
|
||||
& .primary-btn_all {
|
||||
background-color: var(--main-color);
|
||||
width: 207px;
|
||||
height: 37px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: white;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
}
|
||||
& .primary-btn_all.mob {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
//@media (max-width: 1024px) {
|
||||
// .item {
|
||||
// max-width: 80vw;
|
||||
// }
|
||||
//}
|
||||
@media (max-width: 768px) {
|
||||
.item {
|
||||
width: 100vw;
|
||||
flex-direction: column;
|
||||
& .primary-btn_all {
|
||||
display: none;
|
||||
}
|
||||
& .primary-btn_all.mob {
|
||||
display: flex;
|
||||
}
|
||||
.complex-title {
|
||||
font-size: 24px !important;
|
||||
line-height: 29px !important;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.complex-address-info {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.complex-price-and-count-info {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.complexItem__roomLink {
|
||||
padding-top: 10px;
|
||||
}
|
||||
.metro-wrapper {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.galleria-item img {
|
||||
}
|
||||
|
||||
.primary-btn_all.mob {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
padding: 0 20px 0 20px;
|
||||
}
|
||||
|
||||
.no-image-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.no-image-wrapper {
|
||||
width: 300px !important;
|
||||
max-width: 300px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
@media (max-width: 768px) {
|
||||
.item {
|
||||
.p-galleria-item-nav-onhover .p-galleria-item-nav {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
242
src/entities/homeItem.vue
Normal file
242
src/entities/homeItem.vue
Normal file
@ -0,0 +1,242 @@
|
||||
<template>
|
||||
<div class="homeItem">
|
||||
<div class="homeItem__image">
|
||||
<img v-if="houseImage" :src="houseImage" alt="" />
|
||||
</div>
|
||||
<div class="homeItem__content">
|
||||
<button class="homeItem__btnMenu">
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6.375 7.5C6.07833 7.5 5.78832 7.58797 5.54165 7.7528C5.29497 7.91762 5.10271 8.15189 4.98918 8.42598C4.87565 8.70007 4.84594 9.00166 4.90382 9.29264C4.9617 9.58361 5.10456 9.85088 5.31434 10.0607C5.52412 10.2704 5.79139 10.4133 6.08237 10.4712C6.37334 10.5291 6.67494 10.4994 6.94903 10.3858C7.22312 10.2723 7.45738 10.08 7.62221 9.83336C7.78703 9.58668 7.875 9.29667 7.875 9C7.875 8.60218 7.71697 8.22065 7.43566 7.93934C7.15436 7.65804 6.77283 7.5 6.375 7.5ZM6.375 12.75C6.07833 12.75 5.78832 12.838 5.54165 13.0028C5.29497 13.1676 5.10271 13.4019 4.98918 13.676C4.87565 13.9501 4.84594 14.2517 4.90382 14.5426C4.9617 14.8336 5.10456 15.1009 5.31434 15.3107C5.52412 15.5204 5.79139 15.6633 6.08237 15.7212C6.37334 15.7791 6.67494 15.7494 6.94903 15.6358C7.22312 15.5223 7.45738 15.33 7.62221 15.0834C7.78703 14.8367 7.875 14.5467 7.875 14.25C7.875 13.8522 7.71697 13.4706 7.43566 13.1893C7.15436 12.908 6.77283 12.75 6.375 12.75ZM11.625 5.25C11.9217 5.25 12.2117 5.16203 12.4584 4.99721C12.705 4.83238 12.8973 4.59812 13.0108 4.32403C13.1244 4.04994 13.1541 3.74834 13.0962 3.45737C13.0383 3.16639 12.8954 2.89912 12.6857 2.68934C12.4759 2.47956 12.2086 2.3367 11.9176 2.27882C11.6267 2.22094 11.3251 2.25065 11.051 2.36418C10.7769 2.47771 10.5426 2.66997 10.3778 2.91665C10.213 3.16332 10.125 3.45333 10.125 3.75C10.125 4.14783 10.283 4.52936 10.5643 4.81066C10.8456 5.09197 11.2272 5.25 11.625 5.25ZM6.375 2.25C6.07833 2.25 5.78832 2.33797 5.54165 2.5028C5.29497 2.66762 5.10271 2.90189 4.98918 3.17598C4.87565 3.45006 4.84594 3.75166 4.90382 4.04264C4.9617 4.33361 5.10456 4.60088 5.31434 4.81066C5.52412 5.02044 5.79139 5.1633 6.08237 5.22118C6.37334 5.27906 6.67494 5.24935 6.94903 5.13582C7.22312 5.02229 7.45738 4.83003 7.62221 4.58336C7.78703 4.33668 7.875 4.04667 7.875 3.75C7.875 3.35218 7.71697 2.97064 7.43566 2.68934C7.15436 2.40804 6.77283 2.25 6.375 2.25ZM11.625 12.75C11.3283 12.75 11.0383 12.838 10.7916 13.0028C10.545 13.1676 10.3527 13.4019 10.2392 13.676C10.1257 13.9501 10.0959 14.2517 10.1538 14.5426C10.2117 14.8336 10.3546 15.1009 10.5643 15.3107C10.7741 15.5204 11.0414 15.6633 11.3324 15.7212C11.6233 15.7791 11.9249 15.7494 12.199 15.6358C12.4731 15.5223 12.7074 15.33 12.8722 15.0834C13.037 14.8367 13.125 14.5467 13.125 14.25C13.125 13.8522 12.967 13.4706 12.6857 13.1893C12.4044 12.908 12.0228 12.75 11.625 12.75ZM11.625 7.5C11.3283 7.5 11.0383 7.58797 10.7916 7.7528C10.545 7.91762 10.3527 8.15189 10.2392 8.42598C10.1257 8.70007 10.0959 9.00166 10.1538 9.29264C10.2117 9.58361 10.3546 9.85088 10.5643 10.0607C10.7741 10.2704 11.0414 10.4133 11.3324 10.4712C11.6233 10.5291 11.9249 10.4994 12.199 10.3858C12.4731 10.2723 12.7074 10.08 12.8722 9.83336C13.037 9.58668 13.125 9.29667 13.125 9C13.125 8.60218 12.967 8.22065 12.6857 7.93934C12.4044 7.65804 12.0228 7.5 11.625 7.5Z"
|
||||
fill="#2A2A2A"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="homeItem__desc">{{ house.name }}</div>
|
||||
<div class="homeItem__title">{{ house.name_complex }}</div>
|
||||
<div class="homeItem__roms">Помещений: <strong>{{ house.total_rooms }}</strong></div>
|
||||
</div>
|
||||
<div class="homeItem__nav">
|
||||
<Button
|
||||
v-if="!isArchivePage"
|
||||
v-tooltip="'Редактировать дом'"
|
||||
severity="secondary"
|
||||
class="homeItem__edit"
|
||||
@click="router.push('/my-objects/' + $route.params.id + '/home/'+house.id)"
|
||||
>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M15.75 9.00002C15.5511 9.00002 15.3603 9.07903 15.2197 9.21969C15.079 9.36034 15 9.5511 15 9.75002V14.25C15 14.4489 14.921 14.6397 14.7803 14.7803C14.6397 14.921 14.4489 15 14.25 15H3.75C3.55109 15 3.36032 14.921 3.21967 14.7803C3.07902 14.6397 3 14.4489 3 14.25V3.75002C3 3.5511 3.07902 3.36034 3.21967 3.21969C3.36032 3.07903 3.55109 3.00002 3.75 3.00002H8.25C8.44891 3.00002 8.63968 2.921 8.78033 2.78035C8.92098 2.63969 9 2.44893 9 2.25002C9 2.0511 8.92098 1.86034 8.78033 1.71969C8.63968 1.57903 8.44891 1.50002 8.25 1.50002H3.75C3.15326 1.50002 2.58097 1.73707 2.15901 2.15903C1.73705 2.58098 1.5 3.15328 1.5 3.75002V14.25C1.5 14.8468 1.73705 15.419 2.15901 15.841C2.58097 16.263 3.15326 16.5 3.75 16.5H14.25C14.8467 16.5 15.419 16.263 15.841 15.841C16.2629 15.419 16.5 14.8468 16.5 14.25V9.75002C16.5 9.5511 16.421 9.36034 16.2803 9.21969C16.1397 9.07903 15.9489 9.00002 15.75 9.00002ZM4.5 9.57001V12.75C4.5 12.9489 4.57902 13.1397 4.71967 13.2803C4.86032 13.421 5.05109 13.5 5.25 13.5H8.43C8.5287 13.5006 8.62655 13.4817 8.71793 13.4443C8.80931 13.407 8.89242 13.352 8.9625 13.2825L14.1525 8.08502L16.2825 6.00002C16.3528 5.93029 16.4086 5.84734 16.4467 5.75595C16.4847 5.66455 16.5043 5.56652 16.5043 5.46752C16.5043 5.36851 16.4847 5.27048 16.4467 5.17908C16.4086 5.08769 16.3528 5.00474 16.2825 4.93502L13.1025 1.71752C13.0328 1.64722 12.9498 1.59142 12.8584 1.55335C12.767 1.51527 12.669 1.49567 12.57 1.49567C12.471 1.49567 12.373 1.51527 12.2816 1.55335C12.1902 1.59142 12.1072 1.64722 12.0375 1.71752L9.9225 3.84002L4.7175 9.03752C4.64799 9.1076 4.59299 9.19071 4.55567 9.28209C4.51835 9.37346 4.49943 9.47131 4.5 9.57001ZM12.57 3.30752L14.6925 5.43002L13.6275 6.49502L11.505 4.37252L12.57 3.30752ZM6 9.87752L10.4475 5.43002L12.57 7.55252L8.1225 12H6V9.87752Z"
|
||||
fill="#666666"
|
||||
/>
|
||||
</svg>
|
||||
</Button>
|
||||
<Button
|
||||
v-else
|
||||
severity="secondary"
|
||||
class="homeItem__edit"
|
||||
v-tooltip="'Вернуть из архива'"
|
||||
@click="$emit('unArchiveItem')"
|
||||
>
|
||||
<Unarchive />
|
||||
</Button>
|
||||
<!-- <Button severity="secondary" class="homeItem__copy">
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 15H6C5.40326 15 4.83097 14.7629 4.40901 14.341C3.98705 13.919 3.75 13.3467 3.75 12.75V5.25C3.75 5.05109 3.67098 4.86032 3.53033 4.71967C3.38968 4.57902 3.19891 4.5 3 4.5C2.80109 4.5 2.61032 4.57902 2.46967 4.71967C2.32902 4.86032 2.25 5.05109 2.25 5.25V12.75C2.25 13.7446 2.64509 14.6984 3.34835 15.4017C4.05161 16.1049 5.00544 16.5 6 16.5H12C12.1989 16.5 12.3897 16.421 12.5303 16.2803C12.671 16.1397 12.75 15.9489 12.75 15.75C12.75 15.5511 12.671 15.3603 12.5303 15.2197C12.3897 15.079 12.1989 15 12 15ZM15.75 6.705C15.7422 6.6361 15.7271 6.56822 15.705 6.5025V6.435C15.6689 6.35788 15.6208 6.287 15.5625 6.225L11.0625 1.725C11.0005 1.66666 10.9296 1.61856 10.8525 1.5825H10.785L10.545 1.5H7.5C6.90326 1.5 6.33097 1.73705 5.90901 2.15901C5.48705 2.58097 5.25 3.15326 5.25 3.75V11.25C5.25 11.8467 5.48705 12.419 5.90901 12.841C6.33097 13.2629 6.90326 13.5 7.5 13.5H13.5C14.0967 13.5 14.669 13.2629 15.091 12.841C15.5129 12.419 15.75 11.8467 15.75 11.25V6.75C15.75 6.75 15.75 6.75 15.75 6.705ZM11.25 4.0575L13.1925 6H12C11.8011 6 11.6103 5.92098 11.4697 5.78033C11.329 5.63968 11.25 5.44891 11.25 5.25V4.0575ZM14.25 11.25C14.25 11.4489 14.171 11.6397 14.0303 11.7803C13.8897 11.921 13.6989 12 13.5 12H7.5C7.30109 12 7.11032 11.921 6.96967 11.7803C6.82902 11.6397 6.75 11.4489 6.75 11.25V3.75C6.75 3.55109 6.82902 3.36032 6.96967 3.21967C7.11032 3.07902 7.30109 3 7.5 3H9.75V5.25C9.75 5.84674 9.98705 6.41903 10.409 6.84099C10.831 7.26295 11.4033 7.5 12 7.5H14.25V11.25Z"
|
||||
fill="#666666"
|
||||
/>
|
||||
</svg>
|
||||
</Button> -->
|
||||
<Button
|
||||
v-if="isArchivePage"
|
||||
severity="secondary"
|
||||
class="homeItem__delete"
|
||||
v-tooltip="'Удалить дом'"
|
||||
@click.prevent="showDeleteTemplate($event)"
|
||||
>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M7.5 13.5C7.69891 13.5 7.88968 13.421 8.03033 13.2803C8.17098 13.1397 8.25 12.9489 8.25 12.75V8.25C8.25 8.05109 8.17098 7.86032 8.03033 7.71967C7.88968 7.57902 7.69891 7.5 7.5 7.5C7.30109 7.5 7.11032 7.57902 6.96967 7.71967C6.82902 7.86032 6.75 8.05109 6.75 8.25V12.75C6.75 12.9489 6.82902 13.1397 6.96967 13.2803C7.11032 13.421 7.30109 13.5 7.5 13.5ZM15 4.5H12V3.75C12 3.15326 11.7629 2.58097 11.341 2.15901C10.919 1.73705 10.3467 1.5 9.75 1.5H8.25C7.65326 1.5 7.08097 1.73705 6.65901 2.15901C6.23705 2.58097 6 3.15326 6 3.75V4.5H3C2.80109 4.5 2.61032 4.57902 2.46967 4.71967C2.32902 4.86032 2.25 5.05109 2.25 5.25C2.25 5.44891 2.32902 5.63968 2.46967 5.78033C2.61032 5.92098 2.80109 6 3 6H3.75V14.25C3.75 14.8467 3.98705 15.419 4.40901 15.841C4.83097 16.2629 5.40326 16.5 6 16.5H12C12.5967 16.5 13.169 16.2629 13.591 15.841C14.0129 15.419 14.25 14.8467 14.25 14.25V6H15C15.1989 6 15.3897 5.92098 15.5303 5.78033C15.671 5.63968 15.75 5.44891 15.75 5.25C15.75 5.05109 15.671 4.86032 15.5303 4.71967C15.3897 4.57902 15.1989 4.5 15 4.5ZM7.5 3.75C7.5 3.55109 7.57902 3.36032 7.71967 3.21967C7.86032 3.07902 8.05109 3 8.25 3H9.75C9.94891 3 10.1397 3.07902 10.2803 3.21967C10.421 3.36032 10.5 3.55109 10.5 3.75V4.5H7.5V3.75ZM12.75 14.25C12.75 14.4489 12.671 14.6397 12.5303 14.7803C12.3897 14.921 12.1989 15 12 15H6C5.80109 15 5.61032 14.921 5.46967 14.7803C5.32902 14.6397 5.25 14.4489 5.25 14.25V6H12.75V14.25ZM10.5 13.5C10.6989 13.5 10.8897 13.421 11.0303 13.2803C11.171 13.1397 11.25 12.9489 11.25 12.75V8.25C11.25 8.05109 11.171 7.86032 11.0303 7.71967C10.8897 7.57902 10.6989 7.5 10.5 7.5C10.3011 7.5 10.1103 7.57902 9.96967 7.71967C9.82902 7.86032 9.75 8.05109 9.75 8.25V12.75C9.75 12.9489 9.82902 13.1397 9.96967 13.2803C10.1103 13.421 10.3011 13.5 10.5 13.5Z"
|
||||
fill="#666666"
|
||||
/>
|
||||
</svg>
|
||||
</Button>
|
||||
<Button
|
||||
v-else
|
||||
v-tooltip="'Перенести в архив'"
|
||||
severity="secondary"
|
||||
class="homeItem__archive"
|
||||
@click="showUrchiveTemplate"
|
||||
>
|
||||
<ArchveIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<ConfirmPopup group="deleteHouse" :pt="confirmpopupStyle"></ConfirmPopup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import ArchveIcon from '@/shared/assets/icons/archive.vue'
|
||||
import Unarchive from "@/shared/assets/icons/unarchive.vue";
|
||||
import ConfirmPopup from "primevue/confirmpopup";
|
||||
import { useConfirm } from "primevue/useconfirm";
|
||||
import { confirmpopupStyle } from "@/shared/utils/util";
|
||||
const props = defineProps({
|
||||
house: Object,
|
||||
isArchivePage: Boolean
|
||||
});
|
||||
const router = useRouter();
|
||||
const confirm = useConfirm();
|
||||
const emits = defineEmits(
|
||||
["deleteItem", "archiveItem"]
|
||||
);
|
||||
const houseImage = computed(() => {
|
||||
return props.house?.images.length ? props.house?.images[0].url : null;
|
||||
})
|
||||
|
||||
const showDeleteTemplate = (event, index) => {
|
||||
confirm.require({
|
||||
target: event.currentTarget,
|
||||
group: "deleteHouse",
|
||||
message: "Удалить Дом?",
|
||||
acceptLabel: "Да",
|
||||
rejectLabel: "Нет",
|
||||
acceptClass: "!bg-green !shadow-none",
|
||||
rejectClass: "!bg-red !shadow-none",
|
||||
accept: () => {
|
||||
emits('deleteItem')
|
||||
},
|
||||
reject: () => {
|
||||
return;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const showUrchiveTemplate = (event, index) => {
|
||||
confirm.require({
|
||||
target: event.currentTarget,
|
||||
group: "deleteHouse",
|
||||
message: "Перенести в архив Дом?",
|
||||
acceptLabel: "Да",
|
||||
rejectLabel: "Нет",
|
||||
acceptClass: "!bg-green !shadow-none",
|
||||
rejectClass: "!bg-red !shadow-none",
|
||||
accept: () => {
|
||||
emits('archiveItem')
|
||||
},
|
||||
reject: () => {
|
||||
return;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.p-confirm-popup-footer {
|
||||
text-align: center !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.homeItem {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 224px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
box-shadow: 0px 4px 8px 0px rgba(0, 0, 0, 0.05);
|
||||
@apply bg-white;
|
||||
border-radius: 4px;
|
||||
&__image {
|
||||
width: 100%;
|
||||
height: calc(100% - 64px);
|
||||
flex-grow: 1;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
&__content {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: calc(100% - 64px);
|
||||
z-index: 3;
|
||||
background-color: rgba($color: #ffffff, $alpha: 0.85);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
row-gap: 10px;
|
||||
}
|
||||
|
||||
&__btnMenu {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
&__desc,
|
||||
&__rooms {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-weight: 600;
|
||||
font-style: 18px;
|
||||
}
|
||||
|
||||
&__nav {
|
||||
position: relative;
|
||||
@apply bg-white;
|
||||
width: 100%;
|
||||
height: 64px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
column-gap: 8px;
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: 1px solid #dedede;
|
||||
padding: 8px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
447
src/entities/homeItemIframe.vue
Normal file
447
src/entities/homeItemIframe.vue
Normal file
@ -0,0 +1,447 @@
|
||||
<template>
|
||||
<div class="item">
|
||||
<div class="home-img">
|
||||
<div v-if="!home.images || home.images.length === 0" class="no-image">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
version="1.1"
|
||||
id="Capa_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="130px"
|
||||
height="130px"
|
||||
viewBox="0 0 217.267 217.267"
|
||||
style="enable-background: new 0 0 217.267 217.267"
|
||||
xml:space="preserve"
|
||||
>
|
||||
<g>
|
||||
<path
|
||||
d="M163.627,138.057c1.94,0,3.505-1.564,3.505-3.505V83.749c0-1.937-1.564-3.504-3.505-3.504 c-1.937,0-3.504,1.567-3.504,3.504v50.803C160.123,136.493,161.69,138.057,163.627,138.057z"
|
||||
fill="#506979"
|
||||
></path>
|
||||
<path
|
||||
d="M108.633,0C48.73,0,0,48.732,0,108.633c0,29.931,12.167,57.071,31.816,76.738c0.014,0.014,0.017,0.034,0.031,0.048 s0.032,0.018,0.046,0.031c19.666,19.646,46.807,31.815,76.74,31.815c59.901,0,108.634-48.731,108.634-108.633S168.531,0,108.633,0 z M108.633,7.009c26.758,0,51.12,10.416,69.285,27.379l-26.936,26.938H52.564c-9.661,0-17.521,7.861-17.521,17.521v59.573 c0,9.661,7.861,17.521,17.521,17.521h3.805l-21.979,21.978C17.42,159.753,7.009,135.394,7.009,108.633 C7.009,52.599,52.595,7.009,108.633,7.009z M86.196,108.63c0-12.363,10.056-22.42,22.417-22.42c4.921,0,9.466,1.615,13.172,4.313 l-1.875,1.875c-3.207-2.24-7.094-3.564-11.297-3.564c-10.913,0-19.789,8.879-19.789,19.792c0,4.195,1.321,8.09,3.559,11.3 l-1.874,1.868C87.811,118.095,86.196,113.554,86.196,108.63z M131.034,108.63c0,12.364-10.054,22.418-22.418,22.418 c-4.915,0-9.449-1.608-13.145-4.298l1.874-1.876c3.203,2.231,7.082,3.546,11.274,3.546c10.91,0,19.794-8.877,19.794-19.791 c0-4.187-1.321-8.071-3.549-11.272l1.875-1.875C129.433,99.181,131.034,103.717,131.034,108.63z M95.833,108.63 c0-7.051,5.735-12.784,12.78-12.784c2.257,0,4.347,0.639,6.184,1.668l-17.304,17.306C96.469,112.972,95.833,110.885,95.833,108.63 z M121.397,108.63c0,7.049-5.735,12.782-12.781,12.782c-2.244,0-4.322-0.633-6.158-1.652l17.29-17.289 C120.769,104.31,121.397,106.383,121.397,108.63z M90.467,131.753c5.005,3.936,11.298,6.311,18.146,6.311 c16.229,0,29.431-13.206,29.431-29.431c0-6.848-2.375-13.141-6.311-18.149l22.148-22.146h10.814 c5.797,0,10.513,4.717,10.513,10.513v59.573c0,5.797-4.716,10.513-10.513,10.513H73.289L90.467,131.753z M126.784,85.523 c-5.007-3.948-11.311-6.326-18.168-6.326c-16.23,0-29.426,13.204-29.426,29.429c0,6.858,2.375,13.158,6.321,18.172l-22.136,22.135 H52.561c-5.797,0-10.513-4.716-10.513-10.514V78.847c0-5.795,4.716-10.513,10.513-10.513h91.413L126.784,85.523z M108.633,210.257 c-26.759,0-51.12-10.417-69.287-27.377l26.934-26.939h98.422c9.66,0,17.521-7.86,17.521-17.521V78.847 c0-9.661-7.861-17.521-17.521-17.521h-3.806l21.977-21.979c16.968,18.17,27.381,42.529,27.381,69.287 C210.258,164.667,164.667,210.257,108.633,210.257z"
|
||||
fill="#506979"
|
||||
></path>
|
||||
<path
|
||||
d="M53.002,54.317h23.654c1.937,0,3.504-1.567,3.504-3.504s-1.567-3.504-3.504-3.504H53.002 c-1.938,0-3.504,1.567-3.504,3.504S51.064,54.317,53.002,54.317z"
|
||||
fill="#506979"
|
||||
></path>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<Galleria
|
||||
class="home-galleria"
|
||||
v-else
|
||||
:value="home.images"
|
||||
:responsiveOptions="responsiveOptions"
|
||||
:showThumbnails="false"
|
||||
:showItemNavigators="home.images.length > 1"
|
||||
:numVisible="5"
|
||||
:showItemNavigatorsOnHover="home.images.length > 1"
|
||||
containerStyle="max-width: 640px"
|
||||
>
|
||||
<template #item="slotProps">
|
||||
<img
|
||||
:src="slotProps.item.url"
|
||||
:alt="slotProps.item.name"
|
||||
style="width: 100%; height: 300px; object-fit: cover;"
|
||||
/>
|
||||
</template>
|
||||
</Galleria>
|
||||
</div>
|
||||
<div class="home-info">
|
||||
<div class="home-title">{{ home.name }}</div>
|
||||
<div class="line"></div>
|
||||
<div class="home-address-info">
|
||||
<template v-if="metros">
|
||||
|
||||
<div class="metro-wrapper" v-for="(metro, index) in metros" :key="index">
|
||||
<img
|
||||
src="@/shared/assets/icons/metro.svg"
|
||||
alt=""
|
||||
class="metro-icon"
|
||||
/>
|
||||
<span class="metro-title">{{ metro.metro }}</span>
|
||||
<div class="metro-distance">{{ metro.subway_time}} минут <span v-if="metro.subway_distance == 1">пешком</span><span v-else-if="metro.subway_distance == 2">на транспорте</span></div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="!isHome" class="home-address">{{ home.address }}</div>
|
||||
<div v-else class="home-address">{{ home.address }}</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="home-price-and-count-info">
|
||||
<div class="home-price">
|
||||
Стоимость: <span>от {{ formatTotalPrice(props.home.min_total_amount) }} руб</span>
|
||||
</div>
|
||||
<div class="count-info">
|
||||
Найдено в доме:
|
||||
<div class="homeItem__roomLink">
|
||||
|
||||
{{ home.total_rooms}} квартир
|
||||
</div>
|
||||
<!-- <RouterLink :to="apartmentsLink" class="homeItem__roomLink"> -->
|
||||
<!-- </RouterLink> -->
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<RouterLink :to="apartmentsLink" class="complexItem__roomLink">
|
||||
<div class="primary-btn_all">Посмотреть все квартиры</div>
|
||||
</RouterLink>
|
||||
<RouterLink :to="apartmentsLink" class="complexItem__roomLink">
|
||||
<div class="primary-btn_all mob">Посмотреть квартиры</div>
|
||||
</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { confirmpopupStyle, isEmpty } from "@/shared/utils/util";
|
||||
import { computed } from "vue";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import Button from "primevue/button";
|
||||
import Menu from "primevue/menu";
|
||||
import ConfirmPopup from "primevue/confirmpopup";
|
||||
import { useConfirm } from "primevue/useconfirm";
|
||||
import ModalLayout from "@/shared/UI/modalLayout.vue";
|
||||
import Dialog from "primevue/dialog";
|
||||
|
||||
const visible = ref(true);
|
||||
const confirm = useConfirm();
|
||||
const props = defineProps({
|
||||
home: {},
|
||||
onComplexEdit: Function,
|
||||
onComplexArchive: Function,
|
||||
onComplexDelete: Function,
|
||||
minPrice: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
metros: {
|
||||
type: Array,
|
||||
required: false,
|
||||
},
|
||||
complexId: {
|
||||
type: Number,
|
||||
default: null
|
||||
}
|
||||
});
|
||||
const route = useRoute();
|
||||
|
||||
const totalMinPrice = props.home.min_total_amount ? props.home.min_total_amount : props.minPrice;
|
||||
|
||||
const menuItems = ref();
|
||||
const items = ref();
|
||||
|
||||
const showDeleteTemplate = (event, index) => {
|
||||
confirm.require({
|
||||
target: event.currentTarget,
|
||||
group: "deleteComplex",
|
||||
message: "Удалить Комплекс?",
|
||||
acceptLabel: "Да",
|
||||
rejectLabel: "Нет",
|
||||
acceptClass: "!bg-green !shadow-none",
|
||||
rejectClass: "!bg-red !shadow-none",
|
||||
accept: () => {
|
||||
// emits('deleteItem')
|
||||
},
|
||||
reject: () => {
|
||||
return;
|
||||
},
|
||||
});
|
||||
};
|
||||
const formatTotalPrice = (value) => {
|
||||
if (!value) return '0';
|
||||
return Number(value).toLocaleString('ru-RU');
|
||||
};
|
||||
|
||||
// console.log(props.home?.rooms, 'props.home?.rooms');
|
||||
|
||||
|
||||
const showArchiveTemplate = (event, index) => {
|
||||
confirm.require({
|
||||
target: event.currentTarget,
|
||||
group: "deleteComplex",
|
||||
message: "Перенести Комплекс в архив?",
|
||||
acceptLabel: "Да",
|
||||
rejectLabel: "Нет",
|
||||
acceptClass: "!bg-green !shadow-none",
|
||||
rejectClass: "!bg-red !shadow-none",
|
||||
accept: () => {
|
||||
emits("deleteItem");
|
||||
},
|
||||
reject: () => {
|
||||
return;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (route.name != "archive-main") {
|
||||
items.value = [
|
||||
{
|
||||
items: [
|
||||
{
|
||||
label: "Перенести в архив",
|
||||
command: () => props.onComplexArchive(),
|
||||
},
|
||||
|
||||
{
|
||||
label: "Редактировать",
|
||||
command: () => props.onComplexEdit(),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
} else {
|
||||
items.value = [
|
||||
{
|
||||
items: [
|
||||
{
|
||||
label: "Вернуть из архива",
|
||||
command: () => props.onComplexArchive(),
|
||||
},
|
||||
|
||||
{
|
||||
label: "Удалить",
|
||||
command: () => props.onComplexDelete(),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
const toggle = (event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
menuItems.value.toggle(event);
|
||||
};
|
||||
|
||||
const complexLink = computed(() => {
|
||||
return `/${route.params.token}/view/${props.complexId}/home/${props.home.id}`;
|
||||
});
|
||||
|
||||
const apartmentsLink = computed(() => {
|
||||
return { path: complexLink.value, query: route.query };
|
||||
});
|
||||
|
||||
const responsiveOptions = [
|
||||
{
|
||||
breakpoint: "1600px",
|
||||
numVisible: 4,
|
||||
},
|
||||
{
|
||||
breakpoint: "976px",
|
||||
numVisible: 3,
|
||||
},
|
||||
{
|
||||
breakpoint: "768px",
|
||||
numVisible: 1,
|
||||
},
|
||||
];
|
||||
|
||||
const change = computed(() => {
|
||||
const queue = "1 очередь";
|
||||
const corps = "Корпус 1";
|
||||
const status = "Сдан";
|
||||
return `${queue}. ${corps} - ${status}`;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.item {
|
||||
display: flex;
|
||||
gap: 40px;
|
||||
max-width: 100%;
|
||||
position: relative;
|
||||
/* align-items: center!important; */
|
||||
|
||||
|
||||
& .line {
|
||||
border-top: 1px solid #f3f3f3;
|
||||
/* margin: 20px 0 20px 0; */
|
||||
/* padding-bottom: 20px; */
|
||||
width: 400px;
|
||||
}
|
||||
& .home-info > * {
|
||||
flex-grow: 1;
|
||||
}
|
||||
& .home-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* gap: 20px; */
|
||||
min-width: 100px;
|
||||
/* max-height: 300px; */
|
||||
|
||||
|
||||
& .home-title {
|
||||
font-size: 27px;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
}
|
||||
& .home-address-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* gap: 20px; */
|
||||
|
||||
& .metro-wrapper {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
|
||||
& .metro-title {
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
color: #222222;
|
||||
}
|
||||
& .metro-distance {
|
||||
color: #757575;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
& .home-address {
|
||||
margin-top: 20px;
|
||||
font-size: 15px;
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
& .home-price-and-count-info {
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
& span {
|
||||
color: var(--main-color);
|
||||
font-weight: normal;
|
||||
}
|
||||
& .count-info {
|
||||
display: flex;
|
||||
& .homeItem__roomLink {
|
||||
//text-decoration: underline;
|
||||
color: var(--main-color);
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
& .home-img {
|
||||
/* width: 300px;
|
||||
height: 300px;
|
||||
overflow: hidden; */
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
height: 300px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
& .home-galleria {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
& .img {
|
||||
object-fit: contain;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
& .no-image {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
& img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
}
|
||||
& .primary-btn_all {
|
||||
background-color: var(--main-color);
|
||||
width: 207px;
|
||||
height: 37px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: white;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
}
|
||||
& .primary-btn_all.mob {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
//@media (max-width: 1024px) {
|
||||
// .item {
|
||||
// max-width: 80vw;
|
||||
// }
|
||||
//}
|
||||
@media (max-width: 768px) {
|
||||
.item {
|
||||
width: 100vw;
|
||||
flex-direction: column;
|
||||
& .primary-btn_all {
|
||||
display: none;
|
||||
}
|
||||
& .primary-btn_all.mob {
|
||||
display: flex;
|
||||
padding: 0 20px;
|
||||
}
|
||||
.home-img {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
.home-title {
|
||||
font-size: 24px !important;
|
||||
line-height: 29px !important;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.home-address-info {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.home-price-and-count-info {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.complexItem__roomLink {
|
||||
padding-top: 10px;
|
||||
}
|
||||
.metro-wrapper {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.galleria-item img {
|
||||
}
|
||||
|
||||
.primary-btn_all.mob {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.no-image-wrapper {
|
||||
width: 300px !important;
|
||||
max-width: 300px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
@media (max-width: 768px) {
|
||||
.item {
|
||||
.p-galleria-item-nav-onhover .p-galleria-item-nav {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
122
src/entities/tableEditeble.vue
Normal file
122
src/entities/tableEditeble.vue
Normal file
@ -0,0 +1,122 @@
|
||||
<template>
|
||||
<table
|
||||
class="tableEditeble flex flex-col overflow-x-scroll"
|
||||
@dragover.prevent
|
||||
>
|
||||
<!-- @drop.prevent="(e) => emits('onDrop', e)" -->
|
||||
<thead
|
||||
:class="[
|
||||
'w-max grid text-center bg-[#F2F3F3]',
|
||||
`grid-cols-[30px_repeat(9,_1fr)]`,
|
||||
]"
|
||||
:style="`grid-template-columns: 30px repeat(${props.data.head.length}, 1fr);`"
|
||||
>
|
||||
<tr class="flex justify-center border border-b-0 border-grey-400"></tr>
|
||||
<tr
|
||||
v-for="(row, indexRow) in props.data.head"
|
||||
:key="indexRow"
|
||||
class="flex justify-center border border-b-0 border-grey-400 w-max"
|
||||
>
|
||||
<td class="min-w-[250px] max-w-[250px]">{{ row.label }}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody
|
||||
ref="bodyRef"
|
||||
:class="[
|
||||
'min-h-[200px] min-w-full w-max grid text-center border-t border-x border-grey-400',
|
||||
`grid-cols-[30px_repeat(9,_1fr)]`,
|
||||
]"
|
||||
:style="`grid-template-columns: 30px repeat(${props.data.head.length}, 1fr);`"
|
||||
>
|
||||
<tr
|
||||
class="grid grid-cols-1 border-r border-grey-400"
|
||||
:class="[`grid-rows-[repeat(${props.data.body[0].items.length},_1fr)]`]"
|
||||
>
|
||||
<td
|
||||
v-for="(cell, indexCell) in props.data.body[0].items"
|
||||
:key="indexCell"
|
||||
class="border border-grey-400 flex items-center justify-center px-2"
|
||||
>
|
||||
{{ indexCell }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-for="(col, indexCol) in props.data.head"
|
||||
:key="indexCol"
|
||||
class="grid grid-cols-1 min-w-[251px] w-max border-x border-grey-400"
|
||||
:class="[`grid-rows-[repeat(${col.length},_1fr)]`, props.data.body[indexCol].items[0] == null ? '' : 'edit-table-tr']"
|
||||
style="display: flex; position: relative; flex-direction: column; justify-content: space-between;"
|
||||
@dragover="(e) => dragOverHandler(e)"
|
||||
@drop.prevent="(e) => dragEndHandler(e, indexCol)"
|
||||
@dragleave="(e) => dragLeaveHandler(e)"
|
||||
>
|
||||
<td
|
||||
v-for="(cell, indexCell) in props.data.body[indexCol].items"
|
||||
:key="indexCell"
|
||||
class="border border-x-0 border-grey-400 flex items-center justify-center min-w-[250px] max-w-[250px] py-3"
|
||||
style="flex: 1; overflow: hidden"
|
||||
>
|
||||
{{ cell }}
|
||||
<img
|
||||
@click="$emit('onRemoveDropped', $event, indexCol, props.data.body[indexCol])"
|
||||
style="position: absolute; right: 0; top: 0; cursor: pointer;"
|
||||
v-if="indexCell == 0 && cell"
|
||||
src="@/shared/assets/images/complexes/close.svg"
|
||||
alt=""
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ITableData } from "@/widgets/importSteps/fieldStep.vue";
|
||||
import { ref } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
data: ITableData,
|
||||
});
|
||||
|
||||
const bodyRef = ref()
|
||||
|
||||
const emits = defineEmits(["onDrop", "onRemoveDropped"]);
|
||||
|
||||
|
||||
const dragOverHandler = (e) => {
|
||||
e.target.closest('tr').style.backgroundColor = "#FFFBE3";
|
||||
};
|
||||
const dragEndHandler = (e, index) => {
|
||||
e.target.closest('tr').style.backgroundColor = "#ffffff";
|
||||
emits('onDrop', e, index)
|
||||
};
|
||||
|
||||
const dragLeaveHandler = (e) => {
|
||||
e.target.closest('tr').style.backgroundColor = "#ffffff";
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
table {
|
||||
& * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
td {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
max-height: 46px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-table-tr {
|
||||
cursor: pointer;
|
||||
}
|
||||
.edit-table-tr:hover {
|
||||
background: #E4F8F0 !important;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2) !important;
|
||||
}
|
||||
|
||||
.tableEditeble {
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
107
src/entities/tableFromFile.vue
Normal file
107
src/entities/tableFromFile.vue
Normal file
@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<table
|
||||
class="tableFromFile flex flex-col overflow-x-scroll"
|
||||
@dragover.prevent
|
||||
>
|
||||
<thead
|
||||
:class="[
|
||||
'w-max grid text-center bg-[#F2F3F3]',
|
||||
`grid-cols-[30px_repeat(9,_1fr)]`,
|
||||
]"
|
||||
:style="`grid-template-columns: 30px repeat(${props.data.head.length}, 1fr);`"
|
||||
>
|
||||
<tr class="flex justify-center border border-b-0 border-grey-400"></tr>
|
||||
<tr
|
||||
v-for="(row, indexRow) in props.data.head"
|
||||
:key="indexRow"
|
||||
class="flex justify-center border border-b-0 border-grey-400 w-max"
|
||||
>
|
||||
<td class="min-w-[250px] max-w-[250px]">{{ row }}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody
|
||||
:class="['w-max grid text-center', `grid-cols-[30px_repeat(9,_1fr)]`]"
|
||||
:style="`grid-template-columns: 30px repeat(${props.data.head.length}, 1fr);`"
|
||||
>
|
||||
<tr
|
||||
class="grid grid-cols-1"
|
||||
:class="[`grid-rows-[repeat(${props.data.body[0].items.length},_1fr)]`]"
|
||||
>
|
||||
<td
|
||||
v-for="(cell, indexCell) in props.data.body[0].items"
|
||||
:key="indexCell"
|
||||
class="border border-grey-400 flex items-center justify-center px-2 main-tds"
|
||||
>
|
||||
{{ indexCell }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-for="(col, indexCol) in props.data.body"
|
||||
:key="indexCol"
|
||||
class="grid grid-cols-1 w-max border-x border-grey-400"
|
||||
:class="[`grid-rows-[repeat(${col.items.length},_1fr)]`, props.data.body[indexCol].items[0] == null ? '' : 'edit-table-tr']"
|
||||
@dragstart="(e) => onDragStart(e, indexCol)"
|
||||
@dragover.stop
|
||||
@dragend="(e) => dragEndHandler(e)"
|
||||
@drop.prevent="(e) => emits('onDrop', e, indexCol)"
|
||||
draggable="true"
|
||||
style="display: flex; position: relative; flex-direction: column; justify-content: space-between;">
|
||||
<td
|
||||
v-for="(cell, indexCell) in col.items"
|
||||
:key="indexCell"
|
||||
class="border border-x-0 border-grey-400 flex items-center justify-center min-w-[250px] max-w-[250px]"
|
||||
style="flex: 1; overflow: hidden"
|
||||
>
|
||||
{{ cell }}
|
||||
|
||||
<svg style="position: absolute; right: 0; top: 0;" v-if="indexCell == 0 && cell" xmlns="http://www.w3.org/2000/svg" fill="#acacac" width="24px" height="24px" viewBox="0 0 1024 1024" class="icon">
|
||||
<path d="M909.3 506.3L781.7 405.6a7.23 7.23 0 0 0-11.7 5.7V476H548V254h64.8c6 0 9.4-7 5.7-11.7L517.7 114.7a7.14 7.14 0 0 0-11.3 0L405.6 242.3a7.23 7.23 0 0 0 5.7 11.7H476v222H254v-64.8c0-6-7-9.4-11.7-5.7L114.7 506.3a7.14 7.14 0 0 0 0 11.3l127.5 100.8c4.7 3.7 11.7.4 11.7-5.7V548h222v222h-64.8c-6 0-9.4 7-5.7 11.7l100.8 127.5c2.9 3.7 8.5 3.7 11.3 0l100.8-127.5c3.7-4.7.4-11.7-5.7-11.7H548V548h222v64.8c0 6 7 9.4 11.7 5.7l127.5-100.8a7.3 7.3 0 0 0 .1-11.4z"/>
|
||||
</svg>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ITableData } from "@/widgets/importSteps/fieldStep.vue";
|
||||
|
||||
const props = defineProps({
|
||||
data: ITableData
|
||||
});
|
||||
const emits = defineEmits(
|
||||
["onDrop"]
|
||||
);
|
||||
|
||||
const onDragStart = (event, index) => {
|
||||
if (event.dataTransfer)
|
||||
event.dataTransfer.setData("col_id", index.toString());
|
||||
};
|
||||
|
||||
const dragEndHandler = (event) => {
|
||||
//
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
table {
|
||||
td {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
max-height: 46px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-table-tr {
|
||||
cursor: pointer
|
||||
}
|
||||
.edit-table-tr:hover {
|
||||
background: #E4F8F0 !important;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2) !important;
|
||||
}
|
||||
|
||||
.tableFromFile {
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
489
src/entities/viewHome/chessLayoutsItem.vue
Normal file
489
src/entities/viewHome/chessLayoutsItem.vue
Normal file
@ -0,0 +1,489 @@
|
||||
<template>
|
||||
<div
|
||||
class="chessLayoutsCart chessLayouts__item rounded-md border border-grey-400 p-5"
|
||||
style="justify-content: space-between;"
|
||||
>
|
||||
<div>
|
||||
<div class="chessLayoutsCart__found">
|
||||
<div class="chessLayoutsCart__countRoom">{{ plan.rooms.length && plan.rooms[0].rooms_count }}</div>
|
||||
<div class="chessLayoutsCart__text">{{ plan.rooms.length }} квартир найдено</div>
|
||||
</div>
|
||||
<div class="chessLayoutsCart__image" @click="emits('openWindow', { ...selectedPlanRoom, complex_name: plan.complex_name, house_name: plan.house_name })">
|
||||
<div class="image__hover">
|
||||
<svg
|
||||
width="80"
|
||||
height="80"
|
||||
viewBox="0 0 80 80"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M72.8025 62.1975L60.3037 49.6975C58.0856 47.4794 54.5888 47.0231 51.8113 48.2762L49.3456 45.8106C52.8763 41.4994 55 35.9944 55 30C55 16.2144 43.7856 5 30 5C16.2144 5 5 16.2144 5 30C5 43.7856 16.2144 55 30 55C35.9944 55 41.4994 52.8763 45.8106 49.3456L48.2506 51.7856C47.7756 52.7806 47.5 53.865 47.5 55C47.5 57.0031 48.28 58.8869 49.6975 60.3025L62.1963 72.8025C63.6138 74.22 65.4969 75 67.5 75C69.5031 75 71.3869 74.22 72.8025 72.8037C74.22 71.3863 75 69.5031 75 67.5C75 65.4969 74.22 63.6131 72.8025 62.1975ZM30 50C18.9719 50 10 41.0281 10 30C10 18.9719 18.9719 10 30 10C41.0281 10 50 18.9719 50 30C50 41.0281 41.0281 50 30 50ZM69.2663 69.2675C68.3237 70.2125 66.6769 70.2125 65.7325 69.2675L53.2325 56.7675C53.2325 56.7675 53.2325 56.7675 53.2325 56.7662C52.76 56.295 52.5 55.6675 52.5 55C52.5 54.3325 52.76 53.705 53.2338 53.2325C53.705 52.76 54.3325 52.5 55 52.5C55.6675 52.5 56.295 52.76 56.7675 53.2325L69.2675 65.7338C69.74 66.205 70 66.8325 70 67.5C70 68.1675 69.74 68.795 69.2663 69.2675ZM40 30C40 31.3806 38.8806 32.5 37.5 32.5H32.5V37.5C32.5 38.8806 31.3806 40 30 40C28.6194 40 27.5 38.8806 27.5 37.5V32.5H22.5C21.1194 32.5 20 31.3806 20 30C20 28.6194 21.1194 27.5 22.5 27.5H27.5V22.5C27.5 21.1194 28.6194 20 30 20C31.3806 20 32.5 21.1194 32.5 22.5V27.5H37.5C38.8806 27.5 40 28.6194 40 30Z"
|
||||
fill="#222222"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<img
|
||||
:src="plan.images[0].url"
|
||||
:alt="plan.images[0].name"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column;">
|
||||
|
||||
<!-- :style="{ overflow: 'hidden' }" -->
|
||||
<Dropdown
|
||||
class="mb-2.5"
|
||||
:options="filteredRooms"
|
||||
v-model="selectedPlanRoom"
|
||||
@change="(event) => isDetailsOpened && emits('openWindow', { ...event.value, complex_name: plan.complex_name, house_name: plan.house_name })"
|
||||
:pt="{
|
||||
root: {
|
||||
class: ['!h-max'],
|
||||
},
|
||||
input: {
|
||||
class: ['!p-0 !flex !h-max'],
|
||||
},
|
||||
trigger: {
|
||||
class: ['ml-2'],
|
||||
},
|
||||
item: {
|
||||
class: ['!flex'],
|
||||
},
|
||||
}"
|
||||
>
|
||||
<template #dropdownicon>
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6.76693 8.7418L10.0003 11.9752L13.2336 8.7418C13.5586 8.4168 14.0836 8.4168 14.4086 8.7418C14.7336 9.0668 14.7336 9.59183 14.4086 9.91683L10.5836 13.7418C10.2586 14.0668 9.73362 14.0668 9.40862 13.7418L5.58359 9.91683C5.25859 9.59183 5.25859 9.0668 5.58359 8.7418C5.90859 8.42513 6.44193 8.4168 6.76693 8.7418Z"
|
||||
fill="#666666"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
<template #value v-if="selectedPlanRoom">
|
||||
<div class="option-wrapper">
|
||||
<div class="apart-info">
|
||||
<div
|
||||
class="apart-status"
|
||||
:style="{ backgroundColor: getBackgroundColor(selectedPlanRoom.complex_status_info?.status_type) }"
|
||||
></div>
|
||||
<div class="apart-price-wrapper">
|
||||
<span class="mainPrice font-bold mb-2"
|
||||
>{{ formatNumber(selectedPlanRoom.total_amount) }} руб.</span
|
||||
>
|
||||
<div class="squere-info">
|
||||
<span class="squere">{{ selectedPlanRoom.area }} м2</span>
|
||||
<span class="dash"> - </span>
|
||||
<span class="price-squere"
|
||||
>{{formatNumber(((selectedPlanRoom.total_amount || 0) / (selectedPlanRoom.area || 0)).toFixed(2))}} руб./м2</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="apart-additional">
|
||||
<div class="additional-wrapper">
|
||||
<span class="floor">{{ selectedPlanRoom.floor }} этаж</span>
|
||||
<span class="number">№{{ selectedPlanRoom.number }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #option="slotProps">
|
||||
<div class="option-wrapper">
|
||||
<div class="apart-info">
|
||||
<div
|
||||
class="apart-status"
|
||||
:style="{ backgroundColor: getBackgroundColor(slotProps.option.complex_status_info.status_type) }"
|
||||
></div>
|
||||
<div class="apart-price-wrapper">
|
||||
<span class="mainPrice font-bold mb-2"
|
||||
>{{ formatNumber(slotProps.option.total_amount) }} руб.</span
|
||||
>
|
||||
<div class="squere-info">
|
||||
<span class="squere">{{ slotProps.option.area }} м2</span>
|
||||
<span class="dash"> - </span>
|
||||
<span class="price-squere"
|
||||
>{{formatNumber(((slotProps.option.total_amount || 0) / (slotProps.option.area || 0)).toFixed(2))}} руб./м2</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="apart-additional">
|
||||
<div class="additional-wrapper">
|
||||
<span class="floor">{{ slotProps.option.floor }} этаж</span>
|
||||
<span class="number">№{{ slotProps.option.number }}</span>
|
||||
</div>
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6.76693 8.7418L10.0003 11.9752L13.2336 8.7418C13.5586 8.4168 14.0836 8.4168 14.4086 8.7418C14.7336 9.0668 14.7336 9.59183 14.4086 9.91683L10.5836 13.7418C10.2586 14.0668 9.73362 14.0668 9.40862 13.7418L5.58359 9.91683C5.25859 9.59183 5.25859 9.0668 5.58359 8.7418C5.90859 8.42513 6.44193 8.4168 6.76693 8.7418Z"
|
||||
fill="#666666"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="info-wrapper" style="display: flex; gap: 10px;">
|
||||
|
||||
<div
|
||||
class="chessLayoutsCart__status"
|
||||
style="margin-top: 13px;"
|
||||
:style="'background-color:' + slotProps.option.complex_status_info.status_color"
|
||||
></div>
|
||||
<div class="chessLayoutsCart__price border-r border-grey-400 grow">
|
||||
<div class="mainPrice font-bold mb-2">
|
||||
{{ slotProps.option.price }}
|
||||
</div>
|
||||
<div class="mainPrice font-bold mb-2">{{ formatNumber(slotProps.option.total_amount) }} руб.</div>
|
||||
<div class="desc text-xs">
|
||||
{{ slotProps.option.area }} м2
|
||||
<span class="text-grey-900"
|
||||
>- {{formatNumber(((slotProps.option.total_amount || 0) / (slotProps.option.area || 0)).toFixed(2))}} руб./м2</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="option-additional-info" style="display: flex; gap: 10px; padding-left: 10px;
|
||||
justify-content: center; align-items: center;">
|
||||
|
||||
<div class="chessLayoutsCart__floor flex flex-col ml-5 py-1 xl:ml-2">
|
||||
<div class="floor text-xs text-black mb-2.5">
|
||||
{{ slotProps.option.floor }} этаж
|
||||
</div>
|
||||
<div class="numberRoom text-grey-900 text-xs">
|
||||
№{{ slotProps.option.number }}
|
||||
</div>
|
||||
</div>
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6.76693 8.7418L10.0003 11.9752L13.2336 8.7418C13.5586 8.4168 14.0836 8.4168 14.4086 8.7418C14.7336 9.0668 14.7336 9.59183 14.4086 9.91683L10.5836 13.7418C10.2586 14.0668 9.73362 14.0668 9.40862 13.7418L5.58359 9.91683C5.25859 9.59183 5.25859 9.0668 5.58359 8.7418C5.90859 8.42513 6.44193 8.4168 6.76693 8.7418Z"
|
||||
fill="#666666"
|
||||
/>
|
||||
</svg>
|
||||
</div> -->
|
||||
</template>
|
||||
</Dropdown>
|
||||
<MyButton
|
||||
:theme="'green'"
|
||||
:style="backgroundColor = { backgroundColor: 'var(--main-color)' }"
|
||||
class="!w-max !rounded-2xl mx-auto !px-5"
|
||||
@click="emits('openWindow', { ...selectedPlanRoom, complex_name: plan.complex_name, house_name: plan.house_name })"
|
||||
>Подробнее</MyButton
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { formatNumber } from "@/shared/utils/util";
|
||||
import { computed, nextTick, onMounted, ref, watch } from "vue";
|
||||
import 'perfect-scrollbar/css/perfect-scrollbar.css';
|
||||
|
||||
|
||||
// const dropdownRef = ref(null);
|
||||
let psInstance = null;
|
||||
|
||||
const colors = JSON.parse(localStorage.getItem('colors')) || {}
|
||||
|
||||
|
||||
const statusMap = {
|
||||
reservation: 'color_reserved',
|
||||
available: 'color_free',
|
||||
not_for_sale: 'not_for_sale',
|
||||
sold: 'sold',
|
||||
}
|
||||
|
||||
const getBackgroundColor = (statusType) => {
|
||||
|
||||
const colorKey = statusMap[statusType]
|
||||
return colors[colorKey]
|
||||
}
|
||||
|
||||
// let wrapper = document.querySelector('[data-pc-section="wrapper"]');
|
||||
// if (wrapper.classList.contains('px-4')) {
|
||||
// console.log('px-4px-4px-4px-4px-4px-4px-4px-4');
|
||||
|
||||
// console.log(wrapper, 'wrappperrr');
|
||||
// wrapper.classList.remove('px-4');
|
||||
// wrapper.classList.add('px-0');
|
||||
// }
|
||||
|
||||
const applyPerfectScrollbar = () => {
|
||||
console.log('Applying Perfect Scrollbar...');
|
||||
const panel = document.querySelector('.p-dropdown-panel');
|
||||
|
||||
nextTick(panel.style.overflow = 'hidden')
|
||||
const itemsWrapper = document.querySelector('.p-dropdown-items-wrapper');
|
||||
console.log(itemsWrapper);
|
||||
|
||||
if (itemsWrapper) {
|
||||
// panel.style.overflow = 'hidden';
|
||||
// itemsWrapper.style.setProperty('max-height', '200px', 'important');
|
||||
itemsWrapper.style.setProperty('overflow', 'hidden', 'important');
|
||||
|
||||
// Если экземпляр уже существует, уничтожаем его перед повторной инициализацией
|
||||
if (psInstance) {
|
||||
psInstance.destroy();
|
||||
psInstance = null; // Обнуляем psInstance
|
||||
console.log('PerfectScrollbar destroyed');
|
||||
}
|
||||
|
||||
// Теперь создаем новый экземпляр
|
||||
psInstance = new PerfectScrollbar(itemsWrapper, {
|
||||
wheelSpeed: 0.08,
|
||||
wheelPropagation: false,
|
||||
minScrollbarLength: 5,
|
||||
});
|
||||
|
||||
// Настройка внешнего вида полосы прокрутки
|
||||
psInstance.scrollbarYRail.style.right = '10px';
|
||||
psInstance.scrollbarYRail.style.top = '10px';
|
||||
psInstance.scrollbarYRail.style.bottom = '10px';
|
||||
psInstance.scrollbarYRail.style.maxHeight = '180px';
|
||||
psInstance.scrollbarYRail.style.color = 'var(--main-color)';
|
||||
psInstance.scrollbarYRail.style.backgroundColor = '#BDBDBD';
|
||||
psInstance.scrollbarYRail.style.width = '4px';
|
||||
psInstance.scrollbarY.style.backgroundColor = 'var(--main-color)';
|
||||
psInstance.scrollbarY.style.width = '5px';
|
||||
psInstance.scrollbarY.style.right = '0px';
|
||||
|
||||
console.log('PerfectScrollbar initialized:', psInstance);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
const emits = defineEmits(
|
||||
["openWindow"]
|
||||
);
|
||||
|
||||
const props = defineProps(["plan", "isDetailsOpened"])
|
||||
const selectedPlanRoom = ref({})
|
||||
const filteredRooms = computed(() => {
|
||||
return props.plan.rooms.filter((room) => room.visible)
|
||||
})
|
||||
watch(filteredRooms, (newV, old) => {
|
||||
if (newV.length != old.length) {
|
||||
selectedPlanRoom.value = newV[0]
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// applyPerfectScrollbar();
|
||||
if (!selectedPlanRoom.value.id) selectedPlanRoom.value = filteredRooms.value[0]
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.chessLayoutsCart {
|
||||
width: 100%;
|
||||
max-width: 489px!important;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
|
||||
&__found {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
&__countRoom {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
@apply border;
|
||||
@apply border-grey-900;
|
||||
@apply text-grey-900;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
&__text {
|
||||
font-size: 12px;
|
||||
@apply text-grey-900;
|
||||
}
|
||||
|
||||
&__image {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
padding: 0 22px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.image__hover {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 3;
|
||||
background-color: rgba(#ffffff, 0.7);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: 0.5s all ease-in-out;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.image__hover {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transition: 0.5s all ease-in-out;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__info {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
&__status {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 100%;
|
||||
margin-top: 8px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
&__price {
|
||||
min-width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.ps__rail-y {
|
||||
/* width: 200px!important; */
|
||||
}
|
||||
|
||||
.chessLayoutsCart__price {
|
||||
padding-right: 20px;
|
||||
}
|
||||
.option-wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-left: 28px;
|
||||
width: 100%;
|
||||
/* height: 70px; */
|
||||
& .apart-info {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
& .apart-status {
|
||||
min-width: 8px;
|
||||
min-height: 8px;
|
||||
max-width: 8px;
|
||||
max-height: 8px;
|
||||
border-radius: 50%;
|
||||
/* position: absolute; */
|
||||
/* left: 20px; */
|
||||
/* top: 18px; */
|
||||
}
|
||||
& .apart-price-wrapper {
|
||||
& .full-price {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
}
|
||||
& .squere {
|
||||
font-size: 12px;
|
||||
color: #333333;
|
||||
}
|
||||
& .price-squere {
|
||||
font-size: 12px;
|
||||
color: #757575;
|
||||
}
|
||||
}
|
||||
}
|
||||
& .apart-additional {
|
||||
display: flex;
|
||||
font-size: 12px;
|
||||
align-items: center;
|
||||
padding-left: 20px;
|
||||
border-left: 1px solid #dedede;
|
||||
/* padding-right: 51px; */
|
||||
gap: 24px;
|
||||
|
||||
& .additional-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* gap: 18px; */
|
||||
& .floor {
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.chessLayoutsCart {
|
||||
width: 341px;
|
||||
}
|
||||
.option-wrapper {
|
||||
padding: 0!important;
|
||||
padding-left: 10px!important;
|
||||
}
|
||||
.apart-price-wrapper {
|
||||
padding-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.chessLayoutsCart {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@media (max-width: 500px) {
|
||||
.chessLayoutsCart {
|
||||
width: 341px;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
9
src/eventBus.js
Normal file
9
src/eventBus.js
Normal file
@ -0,0 +1,9 @@
|
||||
// eventBus.js
|
||||
import emitter from 'tiny-emitter/instance'
|
||||
|
||||
export default {
|
||||
$on: (...args) => emitter.on(...args),
|
||||
$once: (...args) => emitter.once(...args),
|
||||
$off: (...args) => emitter.off(...args),
|
||||
$emit: (...args) => emitter.emit(...args)
|
||||
}
|
||||
679
src/features/useEditFacades.js
Normal file
679
src/features/useEditFacades.js
Normal file
@ -0,0 +1,679 @@
|
||||
import Map from "ol/Map";
|
||||
import ZoomSlider from "ol/control/ZoomSlider";
|
||||
import Zoom from "ol/control/Zoom";
|
||||
import VectorLayer from "ol/layer/Vector";
|
||||
import VectorSource from "ol/source/Vector";
|
||||
import Collection from "ol/Collection";
|
||||
import Style from "ol/style/Style";
|
||||
import Fill from "ol/style/Fill";
|
||||
import Stroke from "ol/style/Stroke";
|
||||
import Circle from "ol/style/Circle";
|
||||
import View from "ol/View";
|
||||
import ImageLayer from "ol/layer/Image";
|
||||
import Static from "ol/source/ImageStatic";
|
||||
import { getCenter } from "ol/extent";
|
||||
import { Projection } from "ol/proj";
|
||||
import Overlay from "ol/Overlay";
|
||||
import OlModify from "ol/interaction/Modify";
|
||||
import { shiftKeyOnly, singleClick } from "ol/events/condition";
|
||||
import OlSelect from "ol/interaction/Select";
|
||||
import OlDraw, { DrawEvent } from "ol/interaction/Draw";
|
||||
import { Coordinate, toStringXY } from "ol/coordinate";
|
||||
import OlSnap from "ol/interaction/Snap";
|
||||
import { Polygon } from "ol/geom";
|
||||
import Feature, { FeatureLike } from "ol/Feature";
|
||||
import { Pixel } from "ol/pixel";
|
||||
import {
|
||||
Facades,
|
||||
TDraw,
|
||||
TModify,
|
||||
TOlMap,
|
||||
TSelect,
|
||||
} from "@/app/types/facadeCanvas";
|
||||
import { ref } from "vue";
|
||||
import { Area } from "@/app/types/facades";
|
||||
|
||||
export default function createCanvas(
|
||||
facade,
|
||||
onFigureAdded,
|
||||
onOverlayToggled,
|
||||
) {
|
||||
const olMap = ref();
|
||||
const sourceVctr = ref(null)
|
||||
const start = (modalEl) => {
|
||||
olMap.value = new OlMap(facade.image, 'map', function () {
|
||||
onMapLoad();
|
||||
});
|
||||
setMapHeight(modalEl);
|
||||
};
|
||||
|
||||
const setMapHeight = function (modalEl) {
|
||||
const map = document.querySelector("#map");
|
||||
const modalDialog = modalEl.querySelector(".modal-dialog");
|
||||
|
||||
const scrollTop =
|
||||
window.pageYOffset ||
|
||||
document.documentElement.scrollTop ||
|
||||
document.body.scrollTop;
|
||||
|
||||
const marginY =
|
||||
parseInt(modalDialog.style.marginTop) +
|
||||
parseInt(modalDialog.style.marginBottom);
|
||||
|
||||
map.style.height =
|
||||
window.innerHeight -
|
||||
(map.getBoundingClientRect().top - scrollTop) -
|
||||
marginY +
|
||||
"px";
|
||||
};
|
||||
|
||||
const colorInit = () => {
|
||||
olMap.value.setColor(facade.hover_color, 0.25);
|
||||
};
|
||||
|
||||
function OlMap(imageUrl, target, onMapLoad) {
|
||||
this.target = target;
|
||||
this.imageElement = new Image();
|
||||
|
||||
this.imageElement.onload = () => {
|
||||
this.onImageLoad();
|
||||
onMapLoad();
|
||||
};
|
||||
|
||||
this.imageElement.src = imageUrl;
|
||||
}
|
||||
|
||||
OlMap.prototype.onImageLoad = function () {
|
||||
this.createMap(this.imageElement, this.target);
|
||||
};
|
||||
|
||||
OlMap.prototype.createMap = function (
|
||||
imageElement,
|
||||
target
|
||||
) {
|
||||
this.imageLayer = this.createImageLayer(imageElement);
|
||||
|
||||
const targetElement = document.getElementById(target);
|
||||
const view = this.createView(imageElement);
|
||||
|
||||
this.vector = this.createVector();
|
||||
this.overlay = this.createOverlay(document.getElementById("popup"));
|
||||
|
||||
const paneNode = document.createElement("div");
|
||||
paneNode.className = "ol-map__pane";
|
||||
|
||||
targetElement.appendChild(paneNode);
|
||||
|
||||
this.map = new Map({
|
||||
layers: [this.imageLayer, this.vector],
|
||||
overlays: [this.overlay],
|
||||
controls: [],
|
||||
target: paneNode,
|
||||
view: view,
|
||||
pixelRatio: 1,
|
||||
});
|
||||
|
||||
const zoomNode = document.createElement("div");
|
||||
zoomNode.className = "ol-map__zoom";
|
||||
|
||||
const zoomSlider = new ZoomSlider({
|
||||
className: "ol-map__zoom-slider facade-zoom-slider",
|
||||
});
|
||||
|
||||
targetElement.prepend(zoomNode);
|
||||
|
||||
zoomSlider.setTarget(zoomNode);
|
||||
this.map.addControl(zoomSlider);
|
||||
|
||||
const ctrlButtons = new Zoom({
|
||||
target: zoomNode,
|
||||
className: "ol-map__zoom-btns",
|
||||
zoomInLabel: "+",
|
||||
zoomOutLabel: "\u2212",
|
||||
delta: 1,
|
||||
});
|
||||
|
||||
const zoomLabelNode = document.createElement("div");
|
||||
zoomLabelNode.className = "ol-map__zoom-label";
|
||||
zoomLabelNode.textContent = "масштаб";
|
||||
|
||||
zoomNode.appendChild(zoomLabelNode);
|
||||
this.map.addControl(ctrlButtons);
|
||||
this.addInteractionsToMap(this.map, this.vector);
|
||||
this.addEvents();
|
||||
};
|
||||
|
||||
OlMap.prototype.addInteractionsToMap = function (
|
||||
map,
|
||||
vector
|
||||
) {
|
||||
this.modify = new Modify(map);
|
||||
this.modify.setActive(true);
|
||||
|
||||
this.select = new Select(map);
|
||||
this.select.setActive(true);
|
||||
|
||||
this.draw = new Draw(map, vector);
|
||||
this.draw.setActive(true);
|
||||
|
||||
this.snap = new Snap(map, vector);
|
||||
};
|
||||
|
||||
OlMap.prototype.setColor = function (
|
||||
color,
|
||||
opacity
|
||||
) {
|
||||
this.color = color;
|
||||
this.opacity = opacity || 20;
|
||||
|
||||
const style = this.vector?.getStyle();
|
||||
if (style) style.getFill().setColor(this.hexToRgba(this.color, this.opacity));
|
||||
};
|
||||
|
||||
OlMap.prototype.hexToRgba = function (
|
||||
hex,
|
||||
opacity
|
||||
) {
|
||||
if (!hex) return;
|
||||
hex = hex.replace("#", "");
|
||||
|
||||
const color = {
|
||||
r: parseInt(hex.substring(0, 2), 16),
|
||||
g: parseInt(hex.substring(2, 4), 16),
|
||||
b: parseInt(hex.substring(4, 6), 16),
|
||||
};
|
||||
|
||||
if (opacity) {
|
||||
opacity = opacity > 1 ? opacity / 100 : opacity;
|
||||
return (
|
||||
"rgba(" +
|
||||
color.r +
|
||||
", " +
|
||||
color.g +
|
||||
", " +
|
||||
color.b +
|
||||
", " +
|
||||
opacity +
|
||||
")"
|
||||
);
|
||||
} else {
|
||||
return "rgba(" + color.r + ", " + color.g + ", " + color.b + ")";
|
||||
}
|
||||
};
|
||||
|
||||
/* Events */
|
||||
|
||||
OlMap.prototype.addEvents = function () {
|
||||
this.map.on("click", function () {
|
||||
//клик в области карты
|
||||
});
|
||||
|
||||
this.map.on("pointermove", function (event) {
|
||||
const opacity = olMap.value.opacity === 1 ? 0.99 : olMap.value.opacity;
|
||||
|
||||
const hoverStyle = new Style({
|
||||
fill: new Fill({
|
||||
color: olMap.value.hexToRgba(olMap.value.color, 1 - opacity),
|
||||
}),
|
||||
stroke: new Stroke({
|
||||
color: "#0000000",
|
||||
width: 0.01,
|
||||
}),
|
||||
image: new Circle({
|
||||
radius: 7,
|
||||
fill: new Fill({
|
||||
color: olMap.value.hexToRgba(olMap.value.color),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
let currentFeature;
|
||||
|
||||
this.forEachFeatureAtPixel(
|
||||
event.pixel,
|
||||
function (feature) {
|
||||
if (feature.getGeometry().getType() == "Polygon") {
|
||||
currentFeature = feature;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (
|
||||
currentFeature &&
|
||||
olMap.value.features.getArray().indexOf(currentFeature) > -1
|
||||
) {
|
||||
olMap.value.hovered = true;
|
||||
currentFeature.setStyle(hoverStyle);
|
||||
olMap.value.removeStyleFromFeatures(currentFeature);
|
||||
} else if (olMap.value.hovered) {
|
||||
olMap.value.hovered = false;
|
||||
olMap.value.removeStyleFromFeatures();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
OlMap.prototype.removeStyleFromFeatures = function (
|
||||
currentFeature
|
||||
) {
|
||||
this.sourceVector?.getFeatures().forEach((feature) => {
|
||||
if (currentFeature) {
|
||||
if (feature.getId() !== currentFeature.getId()) {
|
||||
feature.setStyle(olMap.value.createStyle());
|
||||
}
|
||||
} else {
|
||||
feature.setStyle(olMap.value.createStyle());
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* Vector, Style */
|
||||
|
||||
OlMap.prototype.createVector = function (){
|
||||
this.sourceVector = this.createSourceVector();
|
||||
sourceVctr.value = this.sourceVector
|
||||
|
||||
const style = this.createStyle();
|
||||
|
||||
return new VectorLayer({
|
||||
source: this.sourceVector,
|
||||
style: style,
|
||||
});
|
||||
};
|
||||
|
||||
OlMap.prototype.createSourceVector = function () {
|
||||
this.features = new Collection();
|
||||
|
||||
return new VectorSource({
|
||||
features: this.features,
|
||||
});
|
||||
};
|
||||
|
||||
OlMap.prototype.createStyle = function () {
|
||||
return new Style({
|
||||
fill: new Fill({
|
||||
color: olMap.value.hexToRgba(olMap.value.color, 0.25),
|
||||
}),
|
||||
stroke: new Stroke({
|
||||
color: "#ffcc33",
|
||||
width: 2,
|
||||
}),
|
||||
image: new Circle({
|
||||
radius: 7,
|
||||
fill: new Fill({
|
||||
color: "#ffcc33",
|
||||
}),
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
/* View, Layer, Image */
|
||||
|
||||
OlMap.prototype.createView = function (
|
||||
imageElement
|
||||
) {
|
||||
const projection = this.createProjection(imageElement);
|
||||
|
||||
return new View({
|
||||
projection: projection,
|
||||
center: getCenter([0, 0, imageElement.width, imageElement.height]),
|
||||
zoom: 2,
|
||||
minZoom: 1,
|
||||
maxZoom: 6,
|
||||
});
|
||||
};
|
||||
|
||||
OlMap.prototype.createProjection = function (
|
||||
imageElement
|
||||
) {
|
||||
return new Projection({
|
||||
code: "olimage",
|
||||
units: "pixels",
|
||||
extent: [0, 0, imageElement.width, imageElement.height],
|
||||
});
|
||||
};
|
||||
|
||||
OlMap.prototype.createImageLayer = function (
|
||||
imageElement
|
||||
) {
|
||||
const imageStatic = this.createImageStatic(imageElement);
|
||||
|
||||
return new ImageLayer({
|
||||
source: imageStatic,
|
||||
});
|
||||
};
|
||||
OlMap.prototype.createImageStatic = function (
|
||||
imageElement
|
||||
) {
|
||||
return new Static({
|
||||
url: imageElement.src,
|
||||
imageExtent: [0, 0, imageElement.width, imageElement.height],
|
||||
});
|
||||
};
|
||||
|
||||
OlMap.prototype.createOverlay = function (
|
||||
container
|
||||
) {
|
||||
return new Overlay({
|
||||
element: container,
|
||||
autoPan: {
|
||||
animation: {
|
||||
duration: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/* Interactions */
|
||||
|
||||
function Modify(map) {
|
||||
this.interaction = new OlModify({
|
||||
features: olMap.value ? olMap.value.features : new Collection(),
|
||||
deleteCondition: function (event) {
|
||||
return shiftKeyOnly(event) && singleClick(event);
|
||||
},
|
||||
});
|
||||
|
||||
map.addInteraction(this.interaction);
|
||||
}
|
||||
|
||||
Modify.prototype.setActive = function (active) {
|
||||
this.interaction.setActive(active);
|
||||
};
|
||||
|
||||
function Select(map) {
|
||||
this.interaction = new OlSelect();
|
||||
map.addInteraction(this.interaction);
|
||||
this.setEvents();
|
||||
}
|
||||
|
||||
Select.prototype.setEvents = function () {
|
||||
this.interaction.on(
|
||||
"change:active",
|
||||
function () {
|
||||
this.clear();
|
||||
}.bind(this)
|
||||
);
|
||||
};
|
||||
|
||||
Select.prototype.setActive = function (active) {
|
||||
this.interaction.setActive(active);
|
||||
};
|
||||
|
||||
Select.prototype.clear = function () {
|
||||
const selectedFeatures = this.interaction.getFeatures();
|
||||
|
||||
selectedFeatures.forEach(() => selectedFeatures.remove(this.interaction.getFeatures()[0])
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* type TDraw
|
||||
*/
|
||||
|
||||
function Draw(map, vector) {
|
||||
this.interaction = new OlDraw({
|
||||
source: vector.getSource(),
|
||||
type: /** @type {ol.geom.GeometryType} */ "Polygon",
|
||||
freehand: false,
|
||||
condition: function (event) {
|
||||
return event.originalEvent.which == 1;
|
||||
},
|
||||
});
|
||||
|
||||
this.interaction.setActive(false);
|
||||
map.addInteraction(this.interaction);
|
||||
}
|
||||
|
||||
Draw.prototype.coordToString = function (coordXY) {
|
||||
return toStringXY(coordXY, 3);
|
||||
};
|
||||
|
||||
Draw.prototype.isOnePoint = function (
|
||||
coords
|
||||
) {
|
||||
return coords.every(
|
||||
function (coordXY) {
|
||||
return this.coordToString(coords[0]) == this.coordToString(coordXY);
|
||||
}.bind(this)
|
||||
);
|
||||
};
|
||||
|
||||
Draw.prototype.isEmpty = function () {
|
||||
if (!this.getFeature()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const coords = this.getFeature().getGeometry().getCoordinates()[0];
|
||||
|
||||
if (coords.length == 0 || this.isOnePoint(coords)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
Draw.prototype.getFeature = function () {
|
||||
return this.sketchFeature_;
|
||||
};
|
||||
|
||||
Draw.prototype.getActive = function () {
|
||||
return this.interaction && this.interaction.getActive();
|
||||
};
|
||||
|
||||
Draw.prototype.setActive = function (active) {
|
||||
this.interaction.setActive(active);
|
||||
};
|
||||
|
||||
function Snap(map, vector) {
|
||||
const snap = new OlSnap({
|
||||
source: vector.getSource(),
|
||||
});
|
||||
|
||||
map.addInteraction(snap);
|
||||
}
|
||||
|
||||
function onMapLoad() {
|
||||
initFeatures();
|
||||
|
||||
addMapEvents();
|
||||
|
||||
colorInit();
|
||||
}
|
||||
|
||||
function initFeatures() {
|
||||
(facade.figures || []).forEach(function (item) {
|
||||
const feature = new Feature({
|
||||
geometry: new Polygon([item.points]),
|
||||
});
|
||||
|
||||
feature.setId(item.id);
|
||||
feature.set("enrance_id", item.enrance_id);
|
||||
feature.set("floor_id", item.floor_id);
|
||||
|
||||
try {
|
||||
olMap.value.sourceVector.addFeature(feature);
|
||||
} catch (e) {
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addMapEvents() {
|
||||
if (!olMap.value) return;
|
||||
olMap.value.map
|
||||
.getViewport()
|
||||
.addEventListener("contextmenu", function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
rightClickHandler(event);
|
||||
});
|
||||
|
||||
olMap.value.draw.interaction.on("change", function () {
|
||||
//
|
||||
});
|
||||
|
||||
olMap.value.draw.interaction.on("drawstart", function (event) {
|
||||
olMap.value.draw.sketchFeature_ = event.target.sketchFeature_;
|
||||
});
|
||||
|
||||
olMap.value.draw.interaction.on("drawend", function (event) {
|
||||
olMap.value.draw.sketchFeature_ = event.target.sketchFeature_;
|
||||
const id = Math.random().toString(16).slice(2);
|
||||
|
||||
const feature = event.feature;
|
||||
// event.feature.setId(id);
|
||||
addFacade(feature);
|
||||
});
|
||||
|
||||
// olMap.value.modify.interaction.on("modifyend", function (event) {
|
||||
// console.log("modifyend");
|
||||
|
||||
// const calls = [];
|
||||
// event.features.forEach(function (feature) {
|
||||
// calls.push(editFacade(feature));
|
||||
// });
|
||||
// });
|
||||
|
||||
olMap.value.map.on("dblclick", function (e) {
|
||||
if (olMap.value.draw.interaction.getPointerCount()) {
|
||||
olMap.value.draw.interaction.removeLastPoint();
|
||||
olMap.value.draw.interaction.removeLastPoint();
|
||||
}
|
||||
|
||||
showOverlayAtPixel(olMap.value.map.getEventPixel(e.originalEvent));
|
||||
});
|
||||
}
|
||||
|
||||
function rightClickHandler(event) {
|
||||
if (!olMap.value.draw.isEmpty()) {
|
||||
olMap.value.draw.interaction.removeLastPoint();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!olMap.value.draw.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
showOverlayAtPixel(olMap.value.map.getEventPixel(event));
|
||||
}
|
||||
|
||||
function showOverlayAtPixel(pixel) {
|
||||
const feature = getFeatureAtPixel(pixel);
|
||||
|
||||
if (!feature) {
|
||||
return;
|
||||
}
|
||||
|
||||
showOverLay(feature);
|
||||
}
|
||||
|
||||
function getFeatureAtPixel(pixel) {
|
||||
let polygonFeature;
|
||||
|
||||
olMap.value.map.forEachFeatureAtPixel(pixel, function (feature) {
|
||||
if (
|
||||
feature.getGeometry().getType() == "Polygon" &&
|
||||
olMap.value.features.getArray().indexOf(feature) >
|
||||
-1
|
||||
) {
|
||||
polygonFeature = feature;
|
||||
}
|
||||
});
|
||||
|
||||
return polygonFeature;
|
||||
}
|
||||
|
||||
// function editFacade(feature) {
|
||||
// const geom = feature.getGeometry();
|
||||
|
||||
// console.log(facade, 'editFacadettt');
|
||||
|
||||
|
||||
// facade.figures.find(
|
||||
// (figure) => figure.id == feature.getId()
|
||||
// ).points = geom.getCoordinates()[0];
|
||||
|
||||
// showOverLay(feature);
|
||||
// }
|
||||
|
||||
function addFacade(feature) {
|
||||
const geom = feature.getGeometry();
|
||||
|
||||
const data = {
|
||||
id: String(feature.getId()),
|
||||
points: geom.getCoordinates()[0],
|
||||
};
|
||||
|
||||
onFigureAdded(geom.getCoordinates()[0]).then((figure) => {
|
||||
if (figure) {
|
||||
feature.setId(figure.id)
|
||||
showOverLay(feature);
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
function saveOverlay(data) {
|
||||
closeOverlay();
|
||||
}
|
||||
|
||||
function showOverLay(feature) {
|
||||
onOverlayToggled(feature.getId())
|
||||
olMap.value.select.clear();
|
||||
|
||||
olMap.value.select.interaction.getFeatures().push(feature);
|
||||
olMap.value.selectFeature = feature;
|
||||
|
||||
const coordinates = feature.getGeometry().getCoordinates()[0];
|
||||
let currentCoordinate = coordinates[0];
|
||||
|
||||
coordinates.forEach(function (coord) {
|
||||
if (coord[0] > currentCoordinate[0] && coord[1] > currentCoordinate[1]) {
|
||||
currentCoordinate = coord;
|
||||
}
|
||||
});
|
||||
|
||||
olMap.value.overlay.setPosition(currentCoordinate);
|
||||
|
||||
addOverlayEvents(feature);
|
||||
}
|
||||
|
||||
function addOverlayEvents(feature) {
|
||||
const popup = document.querySelector("#popup");
|
||||
popup.addEventListener("mousedown", (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
document.getElementById("popup-closer").addEventListener("click", function () {
|
||||
closeOverlay();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function closeOverlay() {
|
||||
olMap.value.overlay.setPosition(undefined);
|
||||
const popupClose= document
|
||||
.querySelector("#popup")
|
||||
.querySelector("#popup-closer");
|
||||
popupClose.blur();
|
||||
olMap.value.selectFeature = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
function deleteSelectedFeature(feature_id) {
|
||||
const feature = sourceVctr.value.getFeatures().find((feature) => feature.getId() == feature_id)
|
||||
if (feature) {
|
||||
olMap.value.sourceVector.removeFeature(feature);
|
||||
closeOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
start,
|
||||
olMap,
|
||||
saveOverlay,
|
||||
showOverLay,
|
||||
deleteSelectedFeature,
|
||||
};
|
||||
}
|
||||
88
src/features/useEntranceHooks.js
Normal file
88
src/features/useEntranceHooks.js
Normal file
@ -0,0 +1,88 @@
|
||||
import { computed, ref, watch } from "vue";
|
||||
import api from "@/shared/api";
|
||||
|
||||
export default function useEntranceHooks() {
|
||||
const entranceList = ref([]);
|
||||
const entrancesLoader = ref(true)
|
||||
|
||||
const getEntranceList = (house_id) => {
|
||||
return api.getHouseEntrances(house_id, ['rooms'])
|
||||
.then((response) => {
|
||||
entranceList.value = response.data
|
||||
})
|
||||
.finally(() => entrancesLoader.value = false)
|
||||
.catch(function(error) {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
const updateEntrance = async (entrance, fields) => {
|
||||
api.updateEntrance(entrance.id, fields)
|
||||
.then((response) => {
|
||||
entrance = {...entrance , ...fields}
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
const deleteEntrance = async (entrance_id) => {
|
||||
api.deleteEntrance(entrance_id)
|
||||
.then(() => {
|
||||
entranceList.value = entranceList.value.filter(entrance => entrance.id !== entrance_id)
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
const addNewEntrance = (house_id, fields = {}) => {
|
||||
fields.position = entranceList.value.length + 1;
|
||||
fields.name = `Подъезд ${entranceList.value.length + 1}`;
|
||||
|
||||
return api.createEntrance(house_id, fields)
|
||||
.then((response) => {
|
||||
entranceList.value.push(response.data)
|
||||
return response.data
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.log(error);
|
||||
});
|
||||
};
|
||||
|
||||
const sortedEntranceList = computed(() => {
|
||||
return entranceList.value.sort((a, b) => {
|
||||
if (a.position > b.position) {
|
||||
return 1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const updateTitle = (index, name) => {
|
||||
const entrance = entranceList.value.find((el) => el.id === index);
|
||||
if (entrance) {
|
||||
updateEntrance(entrance, {name}).then(() => {
|
||||
entrance.name = name;
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
watch(sortedEntranceList, (value) => {
|
||||
value.forEach((entrance, index) => {
|
||||
updateEntrance(entrance, { position: index + 1 })
|
||||
})
|
||||
});
|
||||
|
||||
return {
|
||||
entranceList,
|
||||
addNewEntrance,
|
||||
sortedEntranceList,
|
||||
updateEntrance,
|
||||
updateTitle,
|
||||
getEntranceList,
|
||||
deleteEntrance,
|
||||
entrancesLoader
|
||||
};
|
||||
}
|
||||
34
src/features/useFloorHook.js
Normal file
34
src/features/useFloorHook.js
Normal file
@ -0,0 +1,34 @@
|
||||
import api from "@/shared/api";
|
||||
import { computed, watch } from "vue";
|
||||
|
||||
export default function useFloorHooks(floors) {
|
||||
const updateFloor = (floor, fields) => {
|
||||
return api.updateFloor(floor.id, fields)
|
||||
.then((response) => {
|
||||
floor = {...floor , ...fields}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
const sortedFloors = computed(() => {
|
||||
return floors ? floors.sort((a, b) => {
|
||||
if (a.position > b.position) {
|
||||
return 1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}) : [];
|
||||
});
|
||||
|
||||
watch(sortedFloors, (value) => {
|
||||
value.forEach((floor, index) => {
|
||||
updateFloor(floor, { position: index + 1 })
|
||||
})
|
||||
});
|
||||
return {
|
||||
sortedFloors,
|
||||
updateFloor,
|
||||
};
|
||||
}
|
||||
26
src/features/useHouseHook.js
Normal file
26
src/features/useHouseHook.js
Normal file
@ -0,0 +1,26 @@
|
||||
import api from "@/shared/api";
|
||||
import { ref } from "vue";
|
||||
|
||||
export default function useHouseHook() {
|
||||
const house = ref({});
|
||||
const complex = ref({'dop':{}, 'metro':{}});
|
||||
|
||||
const get_house = async (house_id) => {
|
||||
api.getHouseOne(house_id)
|
||||
.then((response) => {
|
||||
var res = response.data;
|
||||
if(res.house) house.value = res.house;
|
||||
if(res.complex) complex.value = res.complex;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
house,
|
||||
complex,
|
||||
get_house
|
||||
};
|
||||
}
|
||||
578
src/features/useViewFacades.js
Normal file
578
src/features/useViewFacades.js
Normal file
@ -0,0 +1,578 @@
|
||||
import Map from "ol/Map";
|
||||
import View from "ol/View";
|
||||
import ImageLayer from "ol/layer/Image";
|
||||
import Projection from "ol/proj/Projection";
|
||||
import { defaults as InteractionDefault } from "ol/interaction/defaults";
|
||||
import { defaults as ControlDefault } from "ol/control/defaults";
|
||||
import Static from "ol/source/ImageStatic";
|
||||
import { getCenter } from "ol/extent";
|
||||
import VectorLayer from "ol/layer/Vector";
|
||||
import VectorSource from "ol/source/Vector";
|
||||
import Collection from "ol/Collection";
|
||||
import Style from "ol/style/Style";
|
||||
import Fill from "ol/style/Fill";
|
||||
import Stroke from "ol/style/Stroke";
|
||||
import Circle from "ol/style/Circle";
|
||||
import Overlay from "ol/Overlay";
|
||||
import OlSelect from "ol/interaction/Select";
|
||||
import OlModify from "ol/interaction/Modify";
|
||||
import Feature from "ol/Feature";
|
||||
import { Polygon } from "ol/geom";
|
||||
import {
|
||||
Area,
|
||||
MyFeature,
|
||||
TModify,
|
||||
TOlMap,
|
||||
TSelect,
|
||||
} from "@/app/types/facadeViewCanvas";
|
||||
import { ref, watch } from "vue";
|
||||
import useWindowSize from "./useWindowSize";
|
||||
//import { Coordinate } from "ol/coordinate";
|
||||
|
||||
/*export type Facade = {
|
||||
id: string;
|
||||
image: string;
|
||||
figures: Area[];
|
||||
};*/
|
||||
|
||||
export default function useViewFacades() {
|
||||
const {isMobile} = useWindowSize()
|
||||
const mapObj = ref(null);
|
||||
const activeFacade = ref(0);
|
||||
const currentFacade = ref(null);
|
||||
const facades = ref(null)
|
||||
const optionsFloor = ref({
|
||||
floor: null,
|
||||
entrance: null,
|
||||
rooms: [],
|
||||
id: null,
|
||||
})
|
||||
const openedFigure = ref({
|
||||
floor: null,
|
||||
entrance: null,
|
||||
rooms: [],
|
||||
id: null,
|
||||
})
|
||||
const openModal = ref(false);
|
||||
const existingOverlays = ref(null);
|
||||
|
||||
const opacity = ref();
|
||||
|
||||
const emptyHoverStyle = ref()
|
||||
const hoverStyle = ref();
|
||||
|
||||
watch(currentFacade, () => {
|
||||
opacity.value = currentFacade.value.opacity === 1 ? 0.99 : currentFacade.value.opacity
|
||||
hoverStyle.value = new Style({
|
||||
fill: new Fill({
|
||||
color: mapObj.value.hexToRgba(currentFacade.value.hover_color, 1 - opacity.value),
|
||||
}),
|
||||
stroke: new Stroke({
|
||||
color: mapObj.value.hexToRgba(currentFacade.value.hover_color),
|
||||
width: 2,
|
||||
}),
|
||||
image: new Circle({
|
||||
radius: 7,
|
||||
fill: new Fill({
|
||||
color: mapObj.value.hexToRgba(currentFacade.value.hover_color),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
})
|
||||
|
||||
watch(mapObj, (mapObjValue) => {
|
||||
emptyHoverStyle.value = new Style({
|
||||
fill: new Fill({
|
||||
color: mapObjValue.hexToRgba('#adadad', 0.8),
|
||||
}),
|
||||
stroke: new Stroke({
|
||||
color: mapObjValue.hexToRgba('#adadad'),
|
||||
width: 2,
|
||||
}),
|
||||
image: new Circle({
|
||||
radius: 7,
|
||||
fill: new Fill({
|
||||
color: mapObjValue.hexToRgba('#adadad'),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
function start(facadesList) {
|
||||
if (facadesList) facades.value = facadesList
|
||||
|
||||
currentFacade.value = facades.value[activeFacade.value];
|
||||
mapObj.value = new OlMap(currentFacade.value.image, "map", function (
|
||||
olMap
|
||||
) {
|
||||
onMapLoad(olMap);
|
||||
});
|
||||
window.addEventListener("resize", () => {
|
||||
if (mapObj.value) {
|
||||
mapObj.value.resize();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function OlMap(imageUrl, target, onMapLoad) {
|
||||
this.target = target;
|
||||
this.imageElement = new Image();
|
||||
|
||||
this.imageElement.onload = () => {
|
||||
this.onImageLoad();
|
||||
onMapLoad(this);
|
||||
};
|
||||
|
||||
this.imageElement.src = imageUrl;
|
||||
}
|
||||
|
||||
OlMap.prototype.onImageLoad = function () {
|
||||
this.createMap(this.imageElement, this.target);
|
||||
};
|
||||
|
||||
OlMap.prototype.createMap = function (
|
||||
imageElement,
|
||||
target
|
||||
) {
|
||||
this.imageLayer = this.createImageLayer(imageElement);
|
||||
|
||||
const targetElement = document.getElementById(target);
|
||||
const view = this.createView(imageElement, targetElement);
|
||||
|
||||
this.vector = this.createVector();
|
||||
|
||||
this.map = new Map({
|
||||
layers: [this.imageLayer, this.vector],
|
||||
overlays: [],
|
||||
controls: ControlDefault({
|
||||
zoom: false,
|
||||
rotate: false,
|
||||
}),
|
||||
target: "map",
|
||||
view: view,
|
||||
pixelRatio: 1,
|
||||
interactions: InteractionDefault({
|
||||
mouseWheelZoom: false,
|
||||
dragPan: false,
|
||||
}),
|
||||
});
|
||||
|
||||
this.addInteractionsToMap(this.map, this.vector);
|
||||
this.addEvents();
|
||||
|
||||
const containerHeight = targetElement.clientHeight;
|
||||
const imageWidth = imageElement.width;
|
||||
const imageHeight = imageElement.height;
|
||||
const mapWidth = (containerHeight / imageHeight) * imageWidth;
|
||||
this.map.setSize([mapWidth, containerHeight]);
|
||||
if (existingOverlays.value) {
|
||||
mapObj.value.map.addOverlay(existingOverlays.value);
|
||||
this.overlay = existingOverlays.value;
|
||||
} else {
|
||||
this.overlay = this.createOverlay(document.getElementById("popup"));
|
||||
mapObj.value.map.addOverlay(this.overlay);
|
||||
mapObj.value.overlay.setPosition(undefined)
|
||||
}
|
||||
};
|
||||
|
||||
OlMap.prototype.createImageLayer = function (
|
||||
imageElement
|
||||
) {
|
||||
const imageStatic = this.createImageStatic(imageElement);
|
||||
|
||||
return new ImageLayer({
|
||||
source: imageStatic,
|
||||
});
|
||||
};
|
||||
OlMap.prototype.createImageStatic = function (
|
||||
imageElement
|
||||
) {
|
||||
return new Static({
|
||||
url: imageElement.src,
|
||||
imageExtent: [0, 0, imageElement.width, imageElement.height],
|
||||
});
|
||||
};
|
||||
OlMap.prototype.createView = function (
|
||||
imageElement,
|
||||
targetElement
|
||||
) {
|
||||
const projection = this.createProjection(imageElement);
|
||||
const containerHeight = document.getElementById("map").clientHeight;
|
||||
const imageResolution = imageElement.naturalHeight / containerHeight;
|
||||
|
||||
return new View({
|
||||
resolution: imageResolution,
|
||||
projection: projection,
|
||||
center: getCenter([0, 0, imageElement.width, imageElement.height]),
|
||||
zoom: 0,
|
||||
});
|
||||
};
|
||||
|
||||
OlMap.prototype.createProjection = function (
|
||||
imageElement
|
||||
) {
|
||||
return new Projection({
|
||||
code: "olimage",
|
||||
units: "pixels",
|
||||
extent: [0, 0, imageElement.width, imageElement.height],
|
||||
});
|
||||
};
|
||||
|
||||
OlMap.prototype.createVector = function () {
|
||||
this.sourceVector = this.createSourceVector();
|
||||
|
||||
const style = this.createStyle();
|
||||
|
||||
return new VectorLayer({
|
||||
source: this.sourceVector,
|
||||
style: style,
|
||||
});
|
||||
};
|
||||
|
||||
OlMap.prototype.createSourceVector = function () {
|
||||
this.features = new Collection();
|
||||
|
||||
return new VectorSource({
|
||||
features: this.features,
|
||||
});
|
||||
};
|
||||
|
||||
OlMap.prototype.createStyle = function () {
|
||||
return new Style({
|
||||
fill: new Fill({
|
||||
color: this.hexToRgba(currentFacade.value.hover_color, 0.2),
|
||||
}),
|
||||
image: new Circle({
|
||||
radius: 7,
|
||||
fill: new Fill({
|
||||
color: this.hexToRgba(currentFacade.value.hover_color),
|
||||
}),
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
OlMap.prototype.createOverlay = function (
|
||||
container
|
||||
){
|
||||
return new Overlay({
|
||||
element: container,
|
||||
autoPan: {
|
||||
animation: {
|
||||
duration: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
OlMap.prototype.addInteractionsToMap = function (
|
||||
map,
|
||||
vector
|
||||
) {
|
||||
this.modify = new Modify(map);
|
||||
this.modify.setActive(true);
|
||||
};
|
||||
|
||||
OlMap.prototype.addEvents = function () {
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const mapObj = this;
|
||||
this.map.on("click", function (event) {
|
||||
let currentFeature;
|
||||
this.forEachFeatureAtPixel(
|
||||
event.pixel,
|
||||
function (feature) {
|
||||
if (feature.getGeometry().getType() == "Polygon") {
|
||||
currentFeature = feature;
|
||||
}
|
||||
}
|
||||
);
|
||||
if (currentFeature) {
|
||||
if (openModal.value) openModal.value = false;
|
||||
|
||||
if (openedFigure.value.id) {
|
||||
closeOverlay()
|
||||
|
||||
openedFigure.value = {
|
||||
floor: null,
|
||||
entrance: null,
|
||||
rooms: [],
|
||||
id: null,
|
||||
}
|
||||
} else {
|
||||
openedFigure.value = {
|
||||
floor: currentFeature.get("floor"),
|
||||
entrance: currentFeature.get("entrance"),
|
||||
rooms: currentFeature.get("rooms"),
|
||||
id: currentFeature.getId()
|
||||
};
|
||||
|
||||
showOverLay(currentFeature)
|
||||
}
|
||||
} else {
|
||||
closeOverlay()
|
||||
|
||||
openedFigure.value = {
|
||||
floor: null,
|
||||
entrance: null,
|
||||
rooms: [],
|
||||
id: null,
|
||||
}
|
||||
openModal.value = false
|
||||
}
|
||||
});
|
||||
|
||||
this.map.on("pointermove", function (event) {
|
||||
if (openedFigure.value.id) return;
|
||||
let currentFeature;
|
||||
this.forEachFeatureAtPixel(
|
||||
event.pixel,
|
||||
function (feature) {
|
||||
if (feature.getGeometry().getType() == "Polygon") {
|
||||
currentFeature = feature;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (
|
||||
currentFeature &&
|
||||
mapObj.features.getArray().indexOf(currentFeature) > -1
|
||||
) {
|
||||
optionsFloor.value = {
|
||||
floor: currentFeature.get("floor"),
|
||||
entrance: currentFeature.get("entrance"),
|
||||
rooms: currentFeature.get("rooms"),
|
||||
id: currentFeature.getId()
|
||||
};
|
||||
mapObj.hovered = true;
|
||||
currentFeature.setStyle(currentFeature.get("rooms").filter((room) => room.visible).length ? hoverStyle.value : emptyHoverStyle.value);
|
||||
mapObj.removeStyleFromFeatures(currentFeature, mapObj);
|
||||
} else if (mapObj.hovered) {
|
||||
mapObj.hovered = false;
|
||||
mapObj.removeStyleFromFeatures(null, mapObj);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
OlMap.prototype.hexToRgba = function (
|
||||
hex,
|
||||
opacity
|
||||
) {
|
||||
if (!hex) return;
|
||||
hex = hex.replace("#", "");
|
||||
|
||||
const color = {
|
||||
r: parseInt(hex.substring(0, 2), 16),
|
||||
g: parseInt(hex.substring(2, 4), 16),
|
||||
b: parseInt(hex.substring(4, 6), 16),
|
||||
};
|
||||
|
||||
if (opacity) {
|
||||
opacity = opacity > 1 ? opacity / 100 : opacity;
|
||||
return (
|
||||
"rgba(" +
|
||||
color.r +
|
||||
", " +
|
||||
color.g +
|
||||
", " +
|
||||
color.b +
|
||||
", " +
|
||||
opacity +
|
||||
")"
|
||||
);
|
||||
} else {
|
||||
return "rgba(" + color.r + ", " + color.g + ", " + color.b + ")";
|
||||
}
|
||||
};
|
||||
|
||||
OlMap.prototype.removeStyleFromFeatures = function (
|
||||
currentFeature,
|
||||
mapObj
|
||||
) {
|
||||
this.sourceVector?.getFeatures().forEach(function (feature) {
|
||||
if (currentFeature) {
|
||||
if (feature.getId() !== currentFeature.getId()) {
|
||||
feature.setStyle(mapObj.createStyle());
|
||||
}
|
||||
} else {
|
||||
feature.setStyle(mapObj.createStyle());
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
OlMap.prototype.resize = function () {
|
||||
if (this.map) {
|
||||
const image = this.imageElement;
|
||||
const container = document.getElementById("map");
|
||||
|
||||
if (container) {
|
||||
const containerHeight = container.clientHeight;
|
||||
const imageResolution = image.naturalHeight / containerHeight;
|
||||
this.map.getView().setResolution(imageResolution);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
OlMap.prototype.setColor = function (
|
||||
color,
|
||||
opacity
|
||||
) {
|
||||
this.color = color;
|
||||
this.opacity = opacity || 20;
|
||||
|
||||
const style = this.vector.getStyle();
|
||||
style.getFill().setColor(this.hexToRgba(this.color, this.opacity));
|
||||
};
|
||||
|
||||
/* Interactions */
|
||||
|
||||
function Modify(map) {
|
||||
this.interaction = new OlModify({
|
||||
features: mapObj.value ? mapObj.value.features : new Collection(),
|
||||
// deleteCondition: function (event) {
|
||||
// return shiftKeyOnly(event) && singleClick(event);
|
||||
// },
|
||||
});
|
||||
|
||||
// map.addInteraction(this.interaction);
|
||||
}
|
||||
|
||||
Modify.prototype.setActive = function (active) {
|
||||
// this.interaction.setActive(active);
|
||||
};
|
||||
|
||||
function Select(map) {
|
||||
this.interaction = new OlSelect();
|
||||
map.addInteraction(this.interaction);
|
||||
}
|
||||
|
||||
Select.prototype.setActive = function (active) {
|
||||
this.interaction.setActive(active);
|
||||
};
|
||||
|
||||
Select.prototype.clear = function () {
|
||||
const selectedFeatures = this.interaction.getFeatures();
|
||||
|
||||
// selectedFeatures.forEach(selectedFeatures.remove, selectedFeatures);
|
||||
};
|
||||
|
||||
function onMapLoad(mapObj) {
|
||||
initFeatures();
|
||||
|
||||
addMapEvents(mapObj);
|
||||
|
||||
colorInit();
|
||||
}
|
||||
|
||||
function changeFeatureRooms(id, rooms) {
|
||||
mapObj.value.features.forEach((feature) => {
|
||||
if (feature.getId() == id) {
|
||||
feature.set('rooms', rooms)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function initFeatures() {
|
||||
const figures = currentFacade.value.figures;
|
||||
figures.forEach(function (item) {
|
||||
const polygon = new Polygon([item.points]);
|
||||
const feature = new Feature({
|
||||
geometry: polygon,
|
||||
});
|
||||
|
||||
feature.setStyle(mapObj.value.createStyle());
|
||||
|
||||
feature.setId(item.id);
|
||||
feature.set("entrance", item.entrance);
|
||||
feature.set("floor", item.floor);
|
||||
feature.set('rooms', item.rooms)
|
||||
|
||||
try {
|
||||
mapObj.value.sourceVector.addFeature(feature);
|
||||
} catch (e) {
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addMapEvents(mapObj) {
|
||||
if (!mapObj.value) return;
|
||||
mapObj.value.map
|
||||
.getViewport()
|
||||
.addEventListener("mousemove", function (event) {
|
||||
event.preventDefault();
|
||||
});
|
||||
mapObj.value.map.getViewport().addEventListener("click", function (event) {
|
||||
event.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
function showOverLay(feature) {
|
||||
const popup = document.querySelector("#popup");
|
||||
|
||||
if (popup.style.left != 'unset') {
|
||||
popup.style.left = 'unset'
|
||||
}
|
||||
// mapObj.value.select.interaction.getFeatures().push(feature);
|
||||
// mapObj.value.selectFeature = feature;
|
||||
|
||||
const coordinates = feature.getGeometry().getCoordinates()[0];
|
||||
let currentCoordinate = coordinates[0];
|
||||
|
||||
coordinates.forEach(function (coord) {
|
||||
if (coord[0] > currentCoordinate[0] && coord[1] > currentCoordinate[1]) {
|
||||
currentCoordinate = coord;
|
||||
}
|
||||
});
|
||||
console.log(mapObj.value.overlay.element.classList.add('mobile'));
|
||||
|
||||
mapObj.value.overlay.setPosition(currentCoordinate);
|
||||
// const popup = document.querySelector("#popup");
|
||||
popup.style.display = "block";
|
||||
}
|
||||
|
||||
function closeOverlay() {
|
||||
mapObj.value.overlay.setPosition(undefined);
|
||||
mapObj.value.selectFeature = null;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const colorInit = () => {
|
||||
mapObj.value.setColor(currentFacade.value.hover_color, currentFacade.value.opacity);
|
||||
};
|
||||
|
||||
const changeFacade = (type) => {
|
||||
if (type === "next") {
|
||||
activeFacade.value++;
|
||||
if (activeFacade.value === facades.value.length) activeFacade.value = 0;
|
||||
currentFacade.value = facades.value[activeFacade.value];
|
||||
}
|
||||
if (type === "prev") {
|
||||
if (activeFacade.value === 0) {
|
||||
activeFacade.value = facades.value.length - 1;
|
||||
} else {
|
||||
activeFacade.value--;
|
||||
}
|
||||
currentFacade.value = facades.value[activeFacade.value];
|
||||
}
|
||||
|
||||
existingOverlays.value = mapObj.value.map
|
||||
.getOverlays()
|
||||
.getArray()
|
||||
.slice()[0];
|
||||
|
||||
mapObj.value.map.setTarget(null);
|
||||
mapObj.value = null;
|
||||
|
||||
start();
|
||||
};
|
||||
return {
|
||||
start,
|
||||
optionsFloor,
|
||||
changeFacade,
|
||||
currentFacade,
|
||||
openedFigure,
|
||||
openModal,
|
||||
closeOverlay,
|
||||
changeFeatureRooms
|
||||
};
|
||||
}
|
||||
733
src/features/useViewFloors.js
Normal file
733
src/features/useViewFloors.js
Normal file
@ -0,0 +1,733 @@
|
||||
import Map from "ol/Map";
|
||||
import ZoomSlider from "ol/control/ZoomSlider";
|
||||
import Zoom from "ol/control/Zoom";
|
||||
import VectorLayer from "ol/layer/Vector";
|
||||
import VectorSource from "ol/source/Vector";
|
||||
import Collection from "ol/Collection";
|
||||
import Style from "ol/style/Style";
|
||||
import Fill from "ol/style/Fill";
|
||||
import Stroke from "ol/style/Stroke";
|
||||
import Circle from "ol/style/Circle";
|
||||
import View from "ol/View";
|
||||
import ImageLayer from "ol/layer/Image";
|
||||
import Static from "ol/source/ImageStatic";
|
||||
import { getCenter } from "ol/extent";
|
||||
import { Projection } from "ol/proj";
|
||||
import Overlay from "ol/Overlay";
|
||||
import OlModify from "ol/interaction/Modify";
|
||||
import { shiftKeyOnly, singleClick } from "ol/events/condition";
|
||||
import OlSelect from "ol/interaction/Select";
|
||||
import OlDraw from "ol/interaction/Draw";
|
||||
import { toStringXY } from "ol/coordinate"; // Coordinate,
|
||||
import OlSnap from "ol/interaction/Snap";
|
||||
import { Polygon } from "ol/geom"; // Geometry, Point,
|
||||
import Feature, { FeatureLike } from "ol/Feature";
|
||||
// import { Pixel } from "ol/pixel";
|
||||
// import { Area, TDraw, TModify, TSelect } from "@/app/types/facadeViewCanvas";
|
||||
import { nextTick, ref } from "vue";
|
||||
|
||||
/*type TOlMap = {
|
||||
map: Map;
|
||||
features: any;
|
||||
sourceVector: VectorSource;
|
||||
draw: TDraw;
|
||||
modify: TModify;
|
||||
select: TSelect;
|
||||
snap: OlSnap;
|
||||
imageElement: HTMLImageElement;
|
||||
imageLayer: ImageLayer<Static>;
|
||||
target: string;
|
||||
vector: VectorLayer<VectorSource>;
|
||||
overlay: Overlay;
|
||||
color: string;
|
||||
opacity: number;
|
||||
hovered: boolean;
|
||||
zoomSliderWrapper: HTMLElement;
|
||||
popupWrapper: HTMLElement;
|
||||
isMouseOverMap: boolean;
|
||||
onImageLoad: () => void;
|
||||
createMap: (imageElement: HTMLImageElement, target: string) => void;
|
||||
addInteractionsToMap: (map: Map, vector: VectorLayer<VectorSource>) => void;
|
||||
setColor: (color: string, opacity: number) => void;
|
||||
hexToRgba: (hex: string, opacity?: number) => string;
|
||||
addEvents: () => void;
|
||||
removeStyleFromFeatures: (currentFeature?: Feature) => void;
|
||||
createVector: () => VectorLayer<VectorSource>;
|
||||
createSourceVector: () => VectorSource;
|
||||
createStyle: () => Style;
|
||||
createView: (imageElement: HTMLImageElement) => View;
|
||||
createProjection: (imageElement: HTMLImageElement) => Projection;
|
||||
createImageLayer: (imageElement: HTMLImageElement) => ImageLayer<Static>;
|
||||
createImageStatic: (imageElement: HTMLImageElement) => Static;
|
||||
createOverlay: (container: HTMLElement) => Overlay;
|
||||
};*/
|
||||
export default function useViewFloors() {
|
||||
const olMap = ref();
|
||||
const openWindowInfo = ref(false);
|
||||
const windowInfoData = ref({});
|
||||
const areas = ref([])
|
||||
const image = ref(null)
|
||||
const hoveredFigure = ref(null)
|
||||
|
||||
|
||||
function start(
|
||||
map,
|
||||
container,
|
||||
zoomSliderWrapper,
|
||||
popupWrapper,
|
||||
imageUrl,
|
||||
figures
|
||||
) {
|
||||
areas.value = figures
|
||||
image.value = imageUrl
|
||||
|
||||
olMap.value = new OlMap(
|
||||
image.value,
|
||||
map || "map",
|
||||
zoomSliderWrapper,
|
||||
popupWrapper,
|
||||
function (olMap) {
|
||||
onMapLoad(olMap);
|
||||
}
|
||||
);
|
||||
|
||||
// setMapHeight(map, container);
|
||||
}
|
||||
|
||||
// const setMapHeight = function (map, container) {
|
||||
// const scrollTop =
|
||||
// window.pageYOffset ||
|
||||
// document.documentElement.scrollTop ||
|
||||
// document.body.scrollTop;
|
||||
|
||||
// const marginY =
|
||||
// parseInt(container.style.marginTop) +
|
||||
// parseInt(container.style.marginBottom);
|
||||
|
||||
// map.style.height = container.clientHeight -
|
||||
// (map.getBoundingClientRect().top - scrollTop) -
|
||||
// marginY +
|
||||
// "px";
|
||||
// };
|
||||
|
||||
function OlMap(
|
||||
|
||||
imageUrl,
|
||||
target,
|
||||
zoomContainer,
|
||||
popupWrapper,
|
||||
onMapLoad
|
||||
) {
|
||||
this.target = target;
|
||||
this.imageElement = new Image();
|
||||
this.zoomSliderWrapper = zoomContainer;
|
||||
this.popupWrapper = popupWrapper;
|
||||
|
||||
this.imageElement.onload = () => {
|
||||
this.onImageLoad();
|
||||
onMapLoad(this);
|
||||
};
|
||||
|
||||
this.imageElement.src = imageUrl;
|
||||
}
|
||||
|
||||
OlMap.prototype.onImageLoad = function () {
|
||||
this.createMap(this.imageElement, this.target);
|
||||
};
|
||||
|
||||
OlMap.prototype.createMap = function (
|
||||
|
||||
imageElement,
|
||||
target
|
||||
) {
|
||||
document.querySelector('.ol-map__zoom')?.remove()
|
||||
document.querySelector('.ol-map__pane')?.remove()
|
||||
|
||||
this.imageLayer = this.createImageLayer(imageElement);
|
||||
|
||||
const targetElement = document.getElementById(target);
|
||||
const view = this.createView(imageElement);
|
||||
|
||||
this.vector = this.createVector();
|
||||
this.overlay = this.createOverlay(this.popupWrapper);
|
||||
|
||||
const paneNode = document.createElement("div");
|
||||
paneNode.className = "ol-map__pane";
|
||||
|
||||
targetElement.appendChild(paneNode);
|
||||
|
||||
this.map = new Map({
|
||||
layers: [this.imageLayer, this.vector],
|
||||
overlays: [this.overlay],
|
||||
controls: [],
|
||||
target: paneNode,
|
||||
view: view,
|
||||
pixelRatio: 1,
|
||||
});
|
||||
|
||||
const zoomNode = document.createElement("div");
|
||||
zoomNode.className = "ol-map__zoom";
|
||||
|
||||
const zoomSlider = new ZoomSlider({
|
||||
className: "ol-map__zoom-slider",
|
||||
});
|
||||
|
||||
this.zoomSliderWrapper.prepend(zoomNode);
|
||||
|
||||
zoomSlider.setTarget(zoomNode);
|
||||
this.map.addControl(zoomSlider);
|
||||
|
||||
const ctrlButtons = new Zoom({
|
||||
target: zoomNode,
|
||||
className: "ol-map__zoom-btns",
|
||||
zoomInLabel: "+",
|
||||
zoomOutLabel: "\u2212",
|
||||
delta: 1,
|
||||
});
|
||||
|
||||
const zoomLabelNode = document.createElement("div");
|
||||
zoomLabelNode.className = "ol-map__zoom-label";
|
||||
zoomLabelNode.textContent = "масштаб";
|
||||
|
||||
zoomNode.appendChild(zoomLabelNode);
|
||||
this.map.addControl(ctrlButtons);
|
||||
this.addInteractionsToMap(this.map, this.vector);
|
||||
this.addEvents();
|
||||
};
|
||||
|
||||
OlMap.prototype.addInteractionsToMap = function (
|
||||
|
||||
map,
|
||||
vector
|
||||
) {
|
||||
// this.modify = new Modify(map);
|
||||
// this.modify.setActive(true);
|
||||
|
||||
// this.select = new Select(map);
|
||||
// this.select.setActive(true);
|
||||
|
||||
// this.draw = new Draw(map, vector);
|
||||
// this.draw.setActive(true);
|
||||
|
||||
this.snap = new Snap(map, vector);
|
||||
};
|
||||
|
||||
OlMap.prototype.setColor = function (
|
||||
|
||||
color,
|
||||
opacity
|
||||
) {
|
||||
this.color = color;
|
||||
this.opacity = opacity || 20;
|
||||
|
||||
const style = this.vector.getStyle();
|
||||
style.getFill().setColor(this.hexToRgba(this.color, this.opacity / 100));
|
||||
};
|
||||
|
||||
OlMap.prototype.hexToRgba = function (
|
||||
|
||||
hex,
|
||||
opacity
|
||||
){
|
||||
if (!hex) return;
|
||||
hex = hex.replace("#", "");
|
||||
|
||||
const color = {
|
||||
r: parseInt(hex.substring(0, 2), 16),
|
||||
g: parseInt(hex.substring(2, 4), 16),
|
||||
b: parseInt(hex.substring(4, 6), 16),
|
||||
};
|
||||
|
||||
if (opacity) {
|
||||
opacity = opacity > 1 ? opacity / 100 : opacity;
|
||||
return (
|
||||
"rgba(" +
|
||||
color.r +
|
||||
", " +
|
||||
color.g +
|
||||
", " +
|
||||
color.b +
|
||||
", " +
|
||||
opacity +
|
||||
")"
|
||||
);
|
||||
} else {
|
||||
return "rgba(" + color.r + ", " + color.g + ", " + color.b + ")";
|
||||
}
|
||||
};
|
||||
|
||||
/* Events */
|
||||
|
||||
OlMap.prototype.addEvents = function () {
|
||||
this.map.on("click", function (event) {
|
||||
let currentFeature;
|
||||
this.forEachFeatureAtPixel(
|
||||
event.pixel,
|
||||
function (feature) {
|
||||
if (feature.getGeometry().getType() == "Polygon") {
|
||||
currentFeature = feature;
|
||||
}
|
||||
}
|
||||
);
|
||||
openWindowInfo.value = true;
|
||||
windowInfoData.value = {};
|
||||
});
|
||||
|
||||
this.map.getViewport().addEventListener("mouseover", () => {
|
||||
this.isMouseOverMap = true;
|
||||
});
|
||||
|
||||
this.map.getViewport().addEventListener("mouseout", () => {
|
||||
this.isMouseOverMap = false;
|
||||
});
|
||||
|
||||
this.map.getViewport().addEventListener("wheel", (event) => {
|
||||
if (!this.isMouseOverMap) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
this.map.on("pointermove", function (event) {
|
||||
let currentFeature;
|
||||
|
||||
this.forEachFeatureAtPixel(
|
||||
event.pixel,
|
||||
function (feature) {
|
||||
if (feature.getGeometry().getType() == "Polygon") {
|
||||
currentFeature = feature;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
olMap.value.removeStyleFromFeatures(currentFeature)
|
||||
|
||||
if (
|
||||
currentFeature &&
|
||||
olMap.value.features.getArray().indexOf(currentFeature) > -1
|
||||
) {
|
||||
olMap.value.hovered = true;
|
||||
showOverLay(currentFeature);
|
||||
this.getTargetElement().style.cursor = "pointer";
|
||||
} else if (olMap.value.hovered) {
|
||||
closeOverlay();
|
||||
olMap.value.hovered = false;
|
||||
this.getTargetElement().style.cursor = "default";
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
OlMap.prototype.removeStyleFromFeatures = function (
|
||||
|
||||
currentFeature
|
||||
) {
|
||||
this.sourceVector?.getFeatures().forEach(function (feature) {
|
||||
if (feature !== currentFeature) {
|
||||
feature.setStyle(
|
||||
new Style({
|
||||
fill: new Fill({
|
||||
color: olMap.value.hexToRgba(feature.get('visible') ? "#4caf50" : "#cfcfcf", 0.7),
|
||||
}),
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* Vector, Style */
|
||||
|
||||
OlMap.prototype.createVector = function (
|
||||
|
||||
){
|
||||
this.sourceVector = this.createSourceVector();
|
||||
|
||||
const style = this.createStyle();
|
||||
|
||||
return new VectorLayer({
|
||||
source: this.sourceVector,
|
||||
style: style,
|
||||
});
|
||||
};
|
||||
|
||||
OlMap.prototype.createSourceVector = function () {
|
||||
this.features = new Collection();
|
||||
|
||||
return new VectorSource({
|
||||
features: this.features,
|
||||
});
|
||||
};
|
||||
|
||||
OlMap.prototype.createStyle = function () {
|
||||
return new Style({
|
||||
fill: new Fill({
|
||||
color: "rgba(255, 56, 56, 0.2)",
|
||||
}),
|
||||
stroke: new Stroke({
|
||||
color: "#ffcc33",
|
||||
width: 0.1,
|
||||
}),
|
||||
image: new Circle({
|
||||
radius: 7,
|
||||
fill: new Fill({
|
||||
color: "#ffcc33",
|
||||
}),
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
/* View, Layer, Image */
|
||||
|
||||
OlMap.prototype.createView = function (
|
||||
|
||||
imageElement
|
||||
) {
|
||||
const projection = this.createProjection(imageElement);
|
||||
|
||||
return new View({
|
||||
projection: projection,
|
||||
center: getCenter([0, 0, imageElement.width, imageElement.height]),
|
||||
zoom: 0,
|
||||
minZoom: 1,
|
||||
maxZoom: 6,
|
||||
});
|
||||
};
|
||||
|
||||
OlMap.prototype.createProjection = function (
|
||||
|
||||
imageElement
|
||||
) {
|
||||
return new Projection({
|
||||
code: "olimage",
|
||||
units: "pixels",
|
||||
extent: [0, 0, imageElement.width, imageElement.height],
|
||||
});
|
||||
};
|
||||
|
||||
OlMap.prototype.createImageLayer = function (
|
||||
|
||||
imageElement
|
||||
) {
|
||||
const imageStatic = this.createImageStatic(imageElement);
|
||||
|
||||
return new ImageLayer({
|
||||
source: imageStatic,
|
||||
});
|
||||
};
|
||||
OlMap.prototype.createImageStatic = function (
|
||||
|
||||
imageElement
|
||||
) {
|
||||
return new Static({
|
||||
url: imageElement.src,
|
||||
imageExtent: [0, 0, imageElement.width, imageElement.height],
|
||||
});
|
||||
};
|
||||
|
||||
OlMap.prototype.createOverlay = function (
|
||||
|
||||
container
|
||||
) {
|
||||
return new Overlay({
|
||||
element: container,
|
||||
autoPan: true,
|
||||
insertFirst: true,
|
||||
});
|
||||
};
|
||||
|
||||
/* Interactions */
|
||||
|
||||
|
||||
function Modify(map) {
|
||||
this.interaction = new OlModify({
|
||||
features: olMap.value ? olMap.value.features : new Collection(),
|
||||
deleteCondition: function (event) {
|
||||
return shiftKeyOnly(event) && singleClick(event);
|
||||
},
|
||||
});
|
||||
|
||||
map.addInteraction(this.interaction);
|
||||
}
|
||||
|
||||
Modify.prototype.setActive = function (active) {
|
||||
this.interaction.setActive(active);
|
||||
};
|
||||
|
||||
|
||||
function Select(map) {
|
||||
this.interaction = new OlSelect();
|
||||
map.addInteraction(this.interaction);
|
||||
this.setEvents();
|
||||
}
|
||||
|
||||
Select.prototype.setEvents = function () {
|
||||
this.interaction.on(
|
||||
"change:active",
|
||||
function () {
|
||||
this.clear();
|
||||
}.bind(this)
|
||||
);
|
||||
};
|
||||
|
||||
Select.prototype.setActive = function (active) {
|
||||
this.interaction.setActive(active);
|
||||
};
|
||||
|
||||
Select.prototype.clear = function () {
|
||||
const selectedFeatures = this.interaction.getFeatures();
|
||||
|
||||
// selectedFeatures.forEach(selectedFeatures.remove, selectedFeatures);
|
||||
};
|
||||
|
||||
/**
|
||||
* type TDraw
|
||||
*/
|
||||
|
||||
|
||||
function Draw(map, vector) {
|
||||
this.interaction = new OlDraw({
|
||||
source: vector.getSource(),
|
||||
type: /** @type {ol.geom.GeometryType} */ "Polygon",
|
||||
freehand: false,
|
||||
condition: function (event) {
|
||||
// Left Click
|
||||
return event.originalEvent.which == 1;
|
||||
},
|
||||
});
|
||||
|
||||
this.interaction.setActive(false);
|
||||
map.addInteraction(this.interaction);
|
||||
}
|
||||
|
||||
Draw.prototype.coordToString = function (coordXY) {
|
||||
return toStringXY(coordXY, 3);
|
||||
};
|
||||
|
||||
Draw.prototype.isOnePoint = function (
|
||||
|
||||
coords
|
||||
) {
|
||||
return coords.every(
|
||||
function (coordXY) {
|
||||
return this.coordToString(coords[0]) == this.coordToString(coordXY);
|
||||
}.bind(this)
|
||||
);
|
||||
};
|
||||
|
||||
Draw.prototype.getFeature = function () {
|
||||
// return this.interaction.sketchFeature_;
|
||||
};
|
||||
|
||||
Draw.prototype.getActive = function () {
|
||||
return this.interaction && this.interaction.getActive();
|
||||
};
|
||||
|
||||
Draw.prototype.setActive = function (active) {
|
||||
this.interaction.setActive(active);
|
||||
};
|
||||
|
||||
function Snap(map, vector) {
|
||||
const snap = new OlSnap({
|
||||
source: vector.getSource(),
|
||||
});
|
||||
|
||||
map.addInteraction(snap);
|
||||
}
|
||||
|
||||
const colorInit = () => {
|
||||
olMap.value.setColor("#FF3838", 0.2);
|
||||
};
|
||||
|
||||
function onMapLoad(olMap) {
|
||||
initFeatures();
|
||||
|
||||
addMapEvents();
|
||||
|
||||
colorInit();
|
||||
}
|
||||
|
||||
|
||||
function initFeatures() {
|
||||
areas.value.forEach(function (item) {
|
||||
// const feature = new Feature({
|
||||
// geometry: new Polygon([item.points]),
|
||||
// });
|
||||
|
||||
const feature = new Feature(new Polygon([item.points]));
|
||||
|
||||
feature.setId(item.id);
|
||||
feature.set('visible', item.visible)
|
||||
feature.setStyle(
|
||||
new Style({
|
||||
fill: new Fill({
|
||||
color: olMap.value.hexToRgba(item.visible ? "#4caf50" : "#cfcfcf", 0.7),
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
const geo = feature.getGeometry()
|
||||
const center = getCenter(geo.getExtent());
|
||||
const imageCenter = getCenter(olMap.value.imageLayer.values_.source.getImageExtent())
|
||||
// geo.setFlatCoordinates([item.points.map(([x, y]) => [x, y ])])
|
||||
geo.scale(1, -1, imageCenter)
|
||||
// geo.rotate(270, center)
|
||||
|
||||
try {
|
||||
olMap.value.sourceVector.addFeature(feature);
|
||||
} catch (e) {
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addMapEvents() {
|
||||
if (!olMap.value) return;
|
||||
olMap.value.map
|
||||
.getViewport()
|
||||
.addEventListener("contextmenu", function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
// rightClickHandler(event);
|
||||
});
|
||||
|
||||
// olMap.value.draw.interaction.on("change", function (event) {
|
||||
// console.log(event);
|
||||
// });
|
||||
|
||||
// olMap.value.draw.interaction.on("drawstart", function (event) {
|
||||
// console.log(event);
|
||||
// });
|
||||
|
||||
// olMap.value.draw.interaction.on("drawend", function (event) {
|
||||
// const feature: Feature<Polygon> = event.feature as Feature<Polygon>;
|
||||
// addFacade(feature);
|
||||
// });
|
||||
|
||||
// olMap.value.modify.interaction.on("modifyend", function (event) {
|
||||
// const calls = [];
|
||||
// });
|
||||
|
||||
olMap.value.map.on("dblclick", function (e) {
|
||||
if (olMap.value.draw?.interaction.getPointerCount()) {
|
||||
olMap.value.draw.interaction.removeLastPoint();
|
||||
olMap.value.draw.interaction.removeLastPoint();
|
||||
}
|
||||
|
||||
showOverlayAtPixel(olMap.value.map.getEventPixel(e.originalEvent));
|
||||
});
|
||||
}
|
||||
|
||||
function rightClickHandler(event) {
|
||||
if (olMap.value.draw.interaction.getPointerCount() !== 0) {
|
||||
olMap.value.draw.interaction.removeLastPoint();
|
||||
return;
|
||||
}
|
||||
|
||||
if (olMap.value.draw.interaction.getPointerCount() !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
showOverlayAtPixel(olMap.value.map.getEventPixel(event));
|
||||
}
|
||||
|
||||
function showOverlayAtPixel(pixel) {
|
||||
const feature = getFeatureAtPixel(pixel);
|
||||
|
||||
if (!feature) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function getFeatureAtPixel(pixel) {
|
||||
let polygonFeature;
|
||||
|
||||
olMap.value.map.forEachFeatureAtPixel(pixel, function (feature, layer) {
|
||||
if (
|
||||
feature.getGeometry().getType() == "Polygon" &&
|
||||
olMap.value.features.getArray().indexOf(feature) >
|
||||
-1
|
||||
) {
|
||||
polygonFeature = feature;
|
||||
}
|
||||
});
|
||||
|
||||
return polygonFeature;
|
||||
}
|
||||
|
||||
// function addFacade(feature) {
|
||||
// const geom = feature.getGeometry();
|
||||
|
||||
// const id = Math.random().toString(16).slice(2);
|
||||
// const data = {
|
||||
// id,
|
||||
// points: geom.getCoordinates()[0],
|
||||
// };
|
||||
|
||||
// areas.push(data);
|
||||
|
||||
// localStorage.setItem("areas", JSON.stringify(areas));
|
||||
|
||||
// showOverLay(feature);
|
||||
// }
|
||||
|
||||
function showOverLay(feature) {
|
||||
closeOverlay()
|
||||
// onOverlayToggled(feature.id_)
|
||||
hoveredFigure.value = feature
|
||||
// olMap.value.select.clear();
|
||||
// olMap.value.select.interaction.getFeatures().push(feature);
|
||||
|
||||
const geo = feature.getGeometry();
|
||||
const center = getCenter(geo.getExtent());
|
||||
|
||||
// hoveredFigure.value.visible
|
||||
feature.setStyle(
|
||||
new Style({
|
||||
fill: new Fill({
|
||||
color: olMap.value.hexToRgba("#cfcfcf", 0.1),
|
||||
}),
|
||||
stroke: new Stroke({
|
||||
color: "#FF3838",
|
||||
width: 0.1,
|
||||
}),
|
||||
})
|
||||
);
|
||||
nextTick(() => {
|
||||
if (!hoveredFigure.value.visible) {
|
||||
return
|
||||
}
|
||||
olMap.value.overlay.setPosition(center);
|
||||
olMap.value.overlay.setPositioning("top-center");
|
||||
|
||||
addOverlayEvents(feature);
|
||||
})
|
||||
}
|
||||
|
||||
function addOverlayEvents(feature) {
|
||||
const popup = document.querySelector("#popup");
|
||||
popup.addEventListener("mousedown", (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
function closeOverlay(feature) {
|
||||
if (hoveredFigure.value) {
|
||||
hoveredFigure.value.setStyle(
|
||||
new Style({
|
||||
fill: new Fill({
|
||||
color: olMap.value.hexToRgba(hoveredFigure.value.visible ? "#4caf50" : "#cfcfcf", 0.7),
|
||||
}),
|
||||
})
|
||||
);
|
||||
}
|
||||
hoveredFigure.value = null
|
||||
olMap.value.overlay.setPosition(undefined);
|
||||
return false;
|
||||
}
|
||||
|
||||
return {
|
||||
openWindowInfo,
|
||||
windowInfoData,
|
||||
start,
|
||||
hoveredFigure,
|
||||
};
|
||||
}
|
||||
26
src/features/useWindowSize.js
Normal file
26
src/features/useWindowSize.js
Normal file
@ -0,0 +1,26 @@
|
||||
import { ref, onMounted, onUnmounted } from "vue";
|
||||
|
||||
export default function useWindowSize() {
|
||||
const isMobile = ref(false);
|
||||
const isTablet = ref(false);
|
||||
|
||||
const updateSize = () => {
|
||||
const width = window.innerWidth;
|
||||
isMobile.value = width <= 408;
|
||||
isTablet.value = width > 408 && width <= 768;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
updateSize(); // Initial check
|
||||
window.addEventListener("resize", updateSize);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener("resize", updateSize);
|
||||
});
|
||||
|
||||
return {
|
||||
isMobile,
|
||||
isTablet
|
||||
};
|
||||
}
|
||||
77
src/main.js
Normal file
77
src/main.js
Normal file
@ -0,0 +1,77 @@
|
||||
import { createApp } from "vue";
|
||||
import App from "@/app/App.vue";
|
||||
import { createPinia } from "pinia";
|
||||
import router from "@/app/router";
|
||||
import PrimeVue from "primevue/config";
|
||||
|
||||
import ConfirmationService from "primevue/confirmationservice";
|
||||
|
||||
|
||||
//Components
|
||||
import InputText from "primevue/inputtext";
|
||||
import Dropdown from "primevue/dropdown";
|
||||
import Textarea from "primevue/textarea";
|
||||
import MultiSelect from "primevue/multiselect";
|
||||
import Calendar from "primevue/calendar";
|
||||
import InputSwitch from "primevue/inputswitch";
|
||||
import Message from "primevue/message";
|
||||
import primeVueSettings from "./shared/utils/primeVueSettings";
|
||||
import MyButton from "@/shared/UI/MyButton.vue";
|
||||
import Tooltip from "primevue/tooltip";
|
||||
import DataTable from "primevue/datatable";
|
||||
import Column from "primevue/column";
|
||||
import Button from "primevue/button";
|
||||
import TabView from "primevue/tabview";
|
||||
import TabPanel from "primevue/tabpanel";
|
||||
import Dialog from "primevue/dialog";
|
||||
import Galleria from 'primevue/galleria';
|
||||
import Paginator from 'primevue/paginator';
|
||||
import Carousel from 'primevue/carousel';
|
||||
import VueCropper from 'vue-cropperjs';
|
||||
import ToastService from 'primevue/toastservice'
|
||||
import Editor from 'primevue/editor';
|
||||
import 'cropperjs/dist/cropper.css';
|
||||
import { createYmaps } from "vue-yandex-maps";
|
||||
|
||||
const app = createApp(App);
|
||||
const pinia = createPinia();
|
||||
|
||||
app.component("VueCropper", VueCropper);
|
||||
app.component("InputText", InputText);
|
||||
app.component("Carousel", Carousel);
|
||||
app.component("Dropdown", Dropdown);
|
||||
app.component("Textarea", Textarea);
|
||||
app.component("MultiSelect", MultiSelect);
|
||||
app.component("Calendar", Calendar);
|
||||
app.component("InputSwitch", InputSwitch);
|
||||
app.component("Message", Message);
|
||||
app.component("DataTable", DataTable);
|
||||
app.component("Column", Column);
|
||||
app.component("Button", Button);
|
||||
app.component("MyButton", MyButton);
|
||||
app.component("TabView", TabView);
|
||||
app.component("TabPanel", TabPanel);
|
||||
app.component("Dialog", Dialog);
|
||||
app.component("Galleria", Galleria);
|
||||
app.component("Paginator", Paginator);
|
||||
app.component('Editor', Editor);
|
||||
|
||||
app.directive("tooltip", Tooltip);
|
||||
|
||||
app.use(pinia);
|
||||
app.use(router);
|
||||
app.use(ToastService)
|
||||
app.use(PrimeVue, primeVueSettings);
|
||||
app.use(ConfirmationService, {});
|
||||
app.use(createYmaps({
|
||||
apikey: '692f0a05-cb88-4158-88b9-06bc0dc93004',
|
||||
lang: 'ru-RU',
|
||||
// load: 'package.standard',
|
||||
// ns: 'yandexMap'
|
||||
// &onload=onYandexMapInit
|
||||
}));
|
||||
app.mount("#app");
|
||||
|
||||
|
||||
import eventBus from "./eventBus";
|
||||
window.complexBus = eventBus
|
||||
462
src/pages/Apartment.vue
Normal file
462
src/pages/Apartment.vue
Normal file
@ -0,0 +1,462 @@
|
||||
<template>
|
||||
<ApartmentLayout :apartment="apartment">
|
||||
<div class="apartment-show">
|
||||
<div class="apartment-show__content" v-if="apartment">
|
||||
<div class="apartment-show__head">
|
||||
<!-- <div class="apartment-show__title mr-auto text-3xl">
|
||||
{{ room_types["apartment.type"] }} № {{ apartment.number }}
|
||||
</div> -->
|
||||
</div>
|
||||
<div class="apartment-show__info">
|
||||
<Button
|
||||
v-tooltip="'Посмотреть дом'"
|
||||
severity="secondary"
|
||||
class="homeItem__edit"
|
||||
@click="handleClickDom"
|
||||
>
|
||||
<house />
|
||||
</Button>
|
||||
<div class="apartment-show__type" v-if="room_types_which_has_rooms.includes(apartment.type)" >Комнат - {{ apartment.rooms_count }}</div>
|
||||
<div class="apartment-show__square">{{ apartment.area }} м2</div>
|
||||
<Dropdown
|
||||
:options="availableFloorsNames"
|
||||
optionLabel="label"
|
||||
optionValue="value"
|
||||
v-model="activeFloor"
|
||||
@change="floorChanged"
|
||||
:pt="{
|
||||
root: {
|
||||
class: ['!border-0'],
|
||||
},
|
||||
input: {
|
||||
class: ['!text-black'],
|
||||
},
|
||||
}"
|
||||
></Dropdown>
|
||||
<div
|
||||
:style="{'background-color' : apartment?.complex_status_info.status_color}"
|
||||
class="apartment-show__status"
|
||||
>
|
||||
{{ apartment?.complex_status_info.status_name }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="apartment-show__image">
|
||||
<div class="image">
|
||||
<img
|
||||
v-if="apartment.plan_url"
|
||||
:src="apartment.plan_url"
|
||||
alt=""
|
||||
class=""
|
||||
/>
|
||||
<img
|
||||
v-else
|
||||
style="width: 240px"
|
||||
src="@/shared/assets/images/complexes/gallery.svg"
|
||||
alt=""
|
||||
class=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="apartment.status_type == 'available' || apartment.has_light_angle" class="apartment-show__btns">
|
||||
<div v-if="apartment.has_light_angle">
|
||||
<div class="cell">
|
||||
<img
|
||||
style="width: 95px"
|
||||
:src="require('@/shared/assets/images/said.png')"
|
||||
alt=""
|
||||
:style="`transform: rotate(${apartment.light_angle}deg)`"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<MyButton v-if="apartment.status_type == 'available'" :theme="'green'" @click="handleApplicationClick(apartment.id)">Заявка на квартиру</MyButton>
|
||||
</div>
|
||||
<div class="apartment-show__price">
|
||||
{{ formatNumber(apartment.total_amount) }} руб.
|
||||
</div>
|
||||
<div class="apartment-show__priceSqure">
|
||||
{{
|
||||
formatNumber(((apartment.total_amount || 0) / (apartment.area || 0)).toFixed(2))
|
||||
}}
|
||||
руб. за м2
|
||||
</div>
|
||||
<div class="apartment-show__settings">
|
||||
<div class="settings__title">Характеристики</div>
|
||||
<div class="settings__table">
|
||||
<div class="row">
|
||||
<div class="cell">Номер помещения</div>
|
||||
<div class="cell">{{ apartment.number }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell">Подъезд</div>
|
||||
<div class="cell">{{ apartment.entrance_name }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell">Этаж</div>
|
||||
<div class="cell">{{ apartment.floor }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell">Название дома</div>
|
||||
<div class="cell">{{ apartment.house_name }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell">Название ЖК</div>
|
||||
<div class="cell">{{ apartment.complex_name }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell">Площадь, м2</div>
|
||||
<div class="cell">{{ apartment.area }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell">Отделка</div>
|
||||
<div class="cell">{{ getDecoration(apartment.decoration_id) }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell">Куда выходят окна</div>
|
||||
<div class="cell">
|
||||
{{ getWindowsPlacement(apartment.windows_placement_id) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell">Высота потолков</div>
|
||||
<div class="cell">{{ apartment.ceiling_height }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell">Наличие Парковки</div>
|
||||
<div class="cell">{{ apartment.house_parking ? apartment.house_parking : 'Нет' }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell">Охрана</div>
|
||||
<div class="cell">{{ apartment.house_security ? 'Да' : 'Нет' }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell">Огороженная территория</div>
|
||||
<div class="cell">{{ apartment.house_fenced_area ? 'Да' : 'Нет' }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell">Спортивная площадка</div>
|
||||
<div class="cell">{{ apartment.house_sports_ground ? 'Да' : 'Нет' }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell">Детская площадка</div>
|
||||
<div class="cell">{{ apartment.house_play_ground ? 'Да' : 'Нет' }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell">Школа</div>
|
||||
<div class="cell">{{ apartment.house_school ? 'Да' : 'Нет' }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell">Детский сад</div>
|
||||
<div class="cell">{{ apartment.house_kinder_garten ? 'Да' : 'Нет' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ApartmentLayout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import api from "@/shared/api";
|
||||
import ApartmentLayout from "@/shared/layouts/ApartmentLayout.vue";
|
||||
import { formatNumber, room_types, room_types_which_has_rooms } from "@/shared/utils/util";
|
||||
import { ref } from "vue";
|
||||
import { useFieldsStore } from '@/app/store/fields';
|
||||
import house from "@/shared/assets/icons/house.vue"
|
||||
export default {
|
||||
components: {
|
||||
ApartmentLayout,house,
|
||||
},
|
||||
setup() {
|
||||
const apartment = ref();
|
||||
const activeFloor = ref()
|
||||
const fieldsStore = useFieldsStore()
|
||||
const entrance = ref()
|
||||
const is_loading = ref(false)
|
||||
return { apartment, room_types, formatNumber, decorations: fieldsStore.decorations, windowsPlacements: fieldsStore.windowsPlacements, entrance, activeFloor, is_loading, room_types_which_has_rooms };
|
||||
},
|
||||
computed: {
|
||||
entranceAvailableFloors() {
|
||||
if (this.apartment) {
|
||||
return this.entrance.floors
|
||||
.filter((floor) => floor.rooms.find((room) => room.position == this.apartment.position))
|
||||
}
|
||||
|
||||
return []
|
||||
},
|
||||
availableFloorsNames() {
|
||||
return this.entranceAvailableFloors.map((floor) => ({
|
||||
label: `${floor.position} Этаж`,
|
||||
value: floor.position,
|
||||
}))
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.mountPage()
|
||||
},
|
||||
methods: {
|
||||
getDecoration(decoration_id) {
|
||||
if (!decoration_id) return ''
|
||||
|
||||
return this.decorations.find((decoration) => decoration.code == decoration_id)?.name
|
||||
},
|
||||
getWindowsPlacement(windows_placement_id) {
|
||||
if (!windows_placement_id) return ''
|
||||
|
||||
return this.windowsPlacements.find((windowsPlacement) => windowsPlacement.id == windows_placement_id)?.name
|
||||
},
|
||||
floorChanged(event) {
|
||||
this.entrance.floors.forEach((floor) => {
|
||||
floor.rooms.find((room) => {
|
||||
if (room.floor == event.value && room.position == this.apartment.position ) {
|
||||
this.$router.replace(`/apartments/${room.id}`)
|
||||
this.mountPage()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
mountPage() {
|
||||
if (this.is_loading) return
|
||||
this.is_loading = true
|
||||
api.get_room_entrance(this.$route.params.apartment_id).then((res) => {
|
||||
this.entrance = res.data;
|
||||
this.entrance.floors.forEach((floor) => floor.rooms.find((room) => {
|
||||
if (room.id == this.$route.params.apartment_id) {
|
||||
this.apartment = room
|
||||
this.activeFloor = room.floor
|
||||
}
|
||||
}))
|
||||
}).finally(() => this.is_loading = false)
|
||||
},
|
||||
handleApplicationClick(room_id) {
|
||||
const reqForm = document.querySelector('#req_form')
|
||||
if (reqForm) {
|
||||
const input = document.createElement('input')
|
||||
input.type = 'hidden';
|
||||
input.name = 'complex_room_id';
|
||||
input.value = room_id;
|
||||
|
||||
reqForm.appendChild(input)
|
||||
}
|
||||
const actionsContainer = document.querySelector('.jw__add-popup')
|
||||
|
||||
if (actionsContainer) {
|
||||
const addReqElement = actionsContainer.querySelector('.add_req_new')
|
||||
|
||||
if (addReqElement) {
|
||||
addReqElement.click()
|
||||
}
|
||||
}
|
||||
},
|
||||
handleClickDom() {
|
||||
const routeData = this.$router.resolve({path: '/view/' + this.entrance.complex_id + '/home/'+this.apartment.house_id});
|
||||
window.open(routeData.href, '_blank');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.apartment-show {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
&.openFull {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
// box-shadow: -2px 0px 6px 1px rgba(0, 0, 0, 0.25);
|
||||
padding: 0 50px 50px 50px;
|
||||
}
|
||||
|
||||
|
||||
&__head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
&__info {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
column-gap: 30px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
&__status {
|
||||
@apply bg-green;
|
||||
@apply text-white;
|
||||
@apply rounded-s-md;
|
||||
font-weight: 700;
|
||||
padding: 8px 10px;
|
||||
margin-left: auto;
|
||||
margin-right: -50px;
|
||||
}
|
||||
|
||||
&__image {
|
||||
width: 100%;
|
||||
min-width: 500px;
|
||||
position: relative;
|
||||
margin-bottom: 30px;
|
||||
|
||||
.image {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
max-height: 500px;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.image__loop {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(#ffffff, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: 0.5s all ease-in-out;
|
||||
|
||||
svg {
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.image__loop {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transition: 0.5s all ease-in-out;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__btns {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-bottom: 30px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
&__price {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
&__priceSqure {
|
||||
font-size: 18px;
|
||||
@apply text-grey-900;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
&__settings {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
.settings__title {
|
||||
font-size: 18px;
|
||||
line-height: 100%;
|
||||
font-weight: 700;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.settings__table {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
|
||||
.row {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: grid;
|
||||
grid-template-columns: 250px 1fr;
|
||||
@apply border-b;
|
||||
@apply border-grey-400;
|
||||
|
||||
.cell {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
padding: 11px 30px;
|
||||
@apply text-black;
|
||||
font-size: 14px;
|
||||
font-weight: 300;
|
||||
&:first-child {
|
||||
padding: 11px 0;
|
||||
@apply border-r;
|
||||
@apply border-grey-400;
|
||||
@apply text-grey-900;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1600px) {
|
||||
&.open {
|
||||
visibility: visible;
|
||||
width: 550px;
|
||||
transition: 0.5s all ease-in-out;
|
||||
}
|
||||
|
||||
&__head {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 24px;
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
&__status {
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
&__image {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
&__btns {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
&__price {
|
||||
font-size: 22px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
&__priceSqure {
|
||||
font-size: 14px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
494
src/pages/ApartmentDetailIframe.vue
Normal file
494
src/pages/ApartmentDetailIframe.vue
Normal file
@ -0,0 +1,494 @@
|
||||
<template>
|
||||
<div class="detail-apartment-wrapper">
|
||||
<requestModal @close="closeReqModal" :isOpen="isReqOpen" :info="info" />
|
||||
<Teleport to="body">
|
||||
<div @click.stop="emits('update:is-open-window', { ...hoveredFigure.room, complex_name: apartment.complex_name, house_name: apartment.house_name, floor: activeFloor.position, entrance_name: apartment.entrance_name})" id="popup" class="popup" ref="popup">
|
||||
<template v-if="hoveredFigure">
|
||||
<div class="popup__countRoom" :style="{'background-color': hoveredFigure.room.complex_status_info.status_color}">{{hoveredFigure.room.layout_feature !== 'studio' ? hoveredFigure.room.rooms_count + ' к' : 'ст'}}</div>
|
||||
<div class="popup__number">№ {{hoveredFigure.room.number}}</div>
|
||||
<div class="popup__price">{{formatNumber(hoveredFigure.room.total_amount)}} руб.</div>
|
||||
<div class="popup__square">{{hoveredFigure.room.area}} м2 <span>- {{formatNumber(((hoveredFigure.room.total_amount || 0) / (hoveredFigure.room.area || 0)).toFixed(2))}} руб./м2</span></div>
|
||||
</template>
|
||||
</div>
|
||||
</Teleport>
|
||||
<div class="apartment-number">Квартира{{ apartment.number }} </div>
|
||||
<div class="apartment-detail" style="justify-content: space-between;">
|
||||
<div class="apartment-image">
|
||||
<div class="apartment-status" :style="{ backgroundColor: getBackgroundColor(apartment.status_type) }">{{ apartment.status_name }}</div>
|
||||
<img :src="apartment.plan_url" alt="" class="image" />
|
||||
</div>
|
||||
<!-- <div class="vertical-line"></div> -->
|
||||
<div class="apartment-detail-info">
|
||||
<div class="sale-info">
|
||||
Обратитесь в отдел продаж по телефону
|
||||
<span>{{ formatPhone(agencyPhone) }}</span>
|
||||
</div>
|
||||
<div class="apartment-title">{{ room_types[apartment.type] }} в {{ apartment.house_name }}</div>
|
||||
<table class="apartment-additional-info">
|
||||
<tr>
|
||||
<td class="td-title">Кол-во комнат</td>
|
||||
<td>{{ apartment.rooms_count }} ккв</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="td-title">Название ЖК</td>
|
||||
<td>{{ apartment.complex_name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="td-title">№ дома</td>
|
||||
<td>{{ apartment.house_name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="td-title">{{ apartment.entrance_name }}</td>
|
||||
<td>5</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="td-title">№ кв</td>
|
||||
<td>{{ apartment.number }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="td-title">Этаж</td>
|
||||
<td>{{ apartment.floor }}</td>
|
||||
</tr>
|
||||
<tr v-if="apartment.windows_placement_id">
|
||||
<td class="td-title">Куда выходят окна</td>
|
||||
<td>{{ apartment.windows_placement_id }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="td-title">Тип</td>
|
||||
<td>{{ room_types[apartment.type] }}</td>
|
||||
</tr>
|
||||
<tr v-if="apartment.decoration_id">
|
||||
<td class="td-title">Отделка</td>
|
||||
<td>{{ getDecoration(apartment.decoration_id) }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="price-wrapper">
|
||||
<table class="price-table">
|
||||
<tr>
|
||||
<td class="full-price">{{ formatTotalPrice(apartment.total_amount) }} руб.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="squere-price">197 658 руб./м2</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="primary-btn detail">Забронировать</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-bottom">
|
||||
<div class="bottom-title">Расположение на этаже</div>
|
||||
<div class="chessFloors__resize" ref="zoomSlider"></div>
|
||||
<div
|
||||
class="chessFloors__mapFloor overflow-x-auto relative"
|
||||
ref="container"
|
||||
>
|
||||
<div class="map ol-map" :id="canvasId" ref="map"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Navigation from '@/components/Navigation.vue';
|
||||
import Apartment from './Apartment.vue';
|
||||
import api from '@/shared/api';
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { useComplexStore } from '@/app/store/complex';
|
||||
import { formatNumber, room_types } from '@/shared/utils/util';
|
||||
import useViewFloors from "@/features/useViewFloors";
|
||||
import { useRoute } from 'vue-router';
|
||||
import requestModal from '@/shared/UI/requestModal.vue';
|
||||
const complexHook = useComplexStore();
|
||||
const useViewFloor = useViewFloors();
|
||||
|
||||
const closeReqModal = () => {
|
||||
isReqOpen.value = false
|
||||
}
|
||||
|
||||
const info = ref({});
|
||||
|
||||
const hoveredFigure = ref(null)
|
||||
const agencyPhone = ref(null)
|
||||
agencyPhone.value = localStorage.getItem('agencyPhone') || ''
|
||||
|
||||
const decorations = ref([])
|
||||
const activeFloor = ref(null)
|
||||
const route = useRoute()
|
||||
const props = defineProps({
|
||||
apartment: Object,
|
||||
agencyPhone: String,
|
||||
canvasId: {
|
||||
type: String,
|
||||
default: "map"
|
||||
}
|
||||
})
|
||||
const popup = ref(HTMLElement);
|
||||
const zoomSlider = ref(HTMLElement);
|
||||
const container = ref(HTMLElement);
|
||||
const map = ref(HTMLElement);
|
||||
const colors = JSON.parse(localStorage.getItem('colors')) || {}
|
||||
|
||||
|
||||
const statusMap = {
|
||||
reservation: 'color_reserved',
|
||||
available: 'color_free',
|
||||
not_for_sale: 'not_for_sale',
|
||||
sold: 'sold',
|
||||
}
|
||||
|
||||
const setHoveredFigureVisible = (id) => {
|
||||
hoveredFigure.value = activeFloor.value.plan.figures.find((figure) => figure.id === id)
|
||||
if(useViewFloor.hoveredFigure.value) {
|
||||
useViewFloor.hoveredFigure.value.visible = hoveredFigure.value && hoveredFigure.value.room.visible
|
||||
}
|
||||
}
|
||||
watch(() => useViewFloor.hoveredFigure.value?.id_, setHoveredFigureVisible)
|
||||
|
||||
const figuresData = ref([]);
|
||||
|
||||
watch(figuresData, (value) => {
|
||||
const figures = props.customFigures ? props.customFigures : value
|
||||
console.log(figures, "figures");
|
||||
|
||||
useViewFloor.start(
|
||||
map.value.getAttribute('id'),
|
||||
container.value,
|
||||
zoomSlider.value,
|
||||
popup.value,
|
||||
activeFloor.value.plan.image,
|
||||
figures// newV.plan.figures ? newV.plan.figures.map((figure) => ({ id: figure.id, points: figure.points.map((point) => ([point.y, point.x])) })) : []
|
||||
)
|
||||
})
|
||||
watch(activeFloor, (newV) => {
|
||||
figuresData.value = newV.plan.figures && newV.plan.figures.map((figure) => {
|
||||
return { id: figure.id, visible: figure.room.visible, points: figure.points.map((point) => {
|
||||
return [point.x, point.y]
|
||||
}) }
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
const getBackgroundColor = (statusType) => {
|
||||
const colorKey = statusMap[statusType]
|
||||
return colors[colorKey]
|
||||
}
|
||||
|
||||
const formatTotalPrice = (value) => {
|
||||
if (!value) return '0';
|
||||
return Number(value).toLocaleString('ru-RU');
|
||||
};
|
||||
|
||||
const formatPhone = (phone) => {
|
||||
if (!phone) {
|
||||
phone = '';
|
||||
}
|
||||
const raw = phone.replace(/\D/g, '');
|
||||
|
||||
if (raw.length !== 11 || !phone.startsWith('+7')) {
|
||||
return phone;
|
||||
}
|
||||
|
||||
return `+${raw[0]} ${raw.slice(1, 4)} ${raw.slice(4, 7)} ${raw.slice(7, 9)} ${raw.slice(9, 11)}`;
|
||||
};
|
||||
|
||||
const loadDecorations = async () => {
|
||||
if (decorations.value.length > 0) return
|
||||
const response = await api.getDecorations()
|
||||
decorations.value = response.data
|
||||
}
|
||||
|
||||
const getDecoration = (decoration_id) => {
|
||||
if (!decoration_id) return ''
|
||||
|
||||
return decorations.value.find((decoration) => decoration.code == decoration_id)?.name
|
||||
}
|
||||
|
||||
|
||||
onMounted(async () => {
|
||||
loadDecorations()
|
||||
if (complexHook.agencyPhone) {
|
||||
agencyPhone.value = complexHook.agencyPhone
|
||||
localStorage.setItem('agencyPhone', complexHook.agencyPhone)
|
||||
console.log(agencyPhone,"numberr");
|
||||
|
||||
}
|
||||
|
||||
await api.getHouseFloorPlans(route.params.house_id)
|
||||
.then((response) => {
|
||||
if (response.data && Array.isArray(response.data)) {
|
||||
const entrancesData = response.data
|
||||
entrancesData.forEach(entrance => {
|
||||
const floor = entrance.floors.find(floor => floor.id == props.apartment.floor_id)
|
||||
activeFloor.value = floor
|
||||
|
||||
// entrance.floors.forEach(floor => {
|
||||
// if () {
|
||||
|
||||
// }
|
||||
// })
|
||||
})
|
||||
// entrances.value = response.data
|
||||
// selectedEntranceID.value = entrances.value[0].id
|
||||
// activeFloor.value = entrances.value[0].floors[entrances.value[0].floors.length - 1]
|
||||
// useViewFloor.start(map.value, container.value, zoomSlider.value, popup.value, activeFloor.value.plan.image)
|
||||
}
|
||||
setHoveredFigureVisible(useViewFloor.hoveredFigure.value?.id_)
|
||||
})
|
||||
})
|
||||
|
||||
// console.log(agencyPhone);
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.apartment-number {
|
||||
font-size: 32px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
.apartment-detail {
|
||||
width: 96vw;
|
||||
height: 100%;
|
||||
max-height: 609px;
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
gap: 50px;
|
||||
|
||||
& .vertical-line {
|
||||
width: 1px;
|
||||
background-color: #DEDEDE;
|
||||
height: 100%;
|
||||
margin: 0 100px;
|
||||
}
|
||||
& .apartment-image {
|
||||
flex-shrink: 0;
|
||||
width: 100%;
|
||||
max-width: 640px;
|
||||
height: 592px;
|
||||
max-height: 593px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
& .apartment-status {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
background-color: #ED4B00;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4px 6px;
|
||||
border-radius: 3px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
& img {
|
||||
width: 150%;
|
||||
height: 150%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
& .apartment-detail-info {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* justify-content: space-between; */
|
||||
max-width: 600px;
|
||||
& .sale-info {
|
||||
width: 600px;
|
||||
height: 76px;
|
||||
border: 1px solid var(--main-color);
|
||||
border-radius: 16px;
|
||||
color: #7D7D7D;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
& span {
|
||||
font-size: 24px;
|
||||
color: var(--main-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
& .apartment-title {
|
||||
font-size: 24px;
|
||||
margin: 20px 0;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid #F2F3F5;
|
||||
}
|
||||
& .apartment-additional-info {
|
||||
margin-top: 10px;
|
||||
width: 100%;
|
||||
.td-title {
|
||||
font-weight: normal;
|
||||
color: #7d7d7d;
|
||||
}
|
||||
& tr {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
& .detail {
|
||||
width: 100%;
|
||||
max-width: 320px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
}
|
||||
& .price-wrapper {
|
||||
border-top: 1px solid #F2F3F5;
|
||||
border-bottom: 1px solid #F2F3F5;
|
||||
margin: 20px 0;
|
||||
padding: 20px 0;
|
||||
& .price-table {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
& .full-price {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: var(--main-color);
|
||||
}
|
||||
& .squere-price {
|
||||
font-size: 12px;
|
||||
color: #757575;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (max-width: 1024px) {
|
||||
.apartment-detail {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 0 24px;
|
||||
& .apartment-image {
|
||||
width: 100%;
|
||||
max-width: unset;
|
||||
}
|
||||
& .vertical-line {
|
||||
display: none;
|
||||
}
|
||||
& .apartment-detail-info {
|
||||
margin-top: 20px;
|
||||
width: 100%;
|
||||
max-width: unset;
|
||||
& .sale-info {
|
||||
width: 100%;
|
||||
}
|
||||
& .detail {
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.detail-bottom {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
margin-top: 600px !important;
|
||||
}
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.apartment-detail {
|
||||
/* flex-direction: column; */
|
||||
|
||||
/* width: 100%; */
|
||||
& .apartment-detail-info {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
.detail-bottom {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
//margin-top: 600px;
|
||||
}
|
||||
.chessFloors__resize {
|
||||
display: none;
|
||||
}
|
||||
.ol-map {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
&__pane {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.popup {
|
||||
position: absolute;
|
||||
display: grid;
|
||||
width: max-content;
|
||||
grid-template-columns: 30px 1fr;
|
||||
grid-gap: 4px 20px;
|
||||
background-color: #ffffff;
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
|
||||
&__countRoom {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
// background-color: #4caf50;
|
||||
color: white;
|
||||
border-radius: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
&__number {
|
||||
grid-column: 1;
|
||||
grid-row: 2;
|
||||
font-size: 14px;
|
||||
align-self: center;
|
||||
color: grey;
|
||||
}
|
||||
&__price {
|
||||
grid-column: 2;
|
||||
grid-row: 2;
|
||||
font-weight: 700;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
&__square {
|
||||
grid-row: 1;
|
||||
grid-column: 2;
|
||||
font-size: 16px;
|
||||
color: black;
|
||||
span {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chessFloors__mapFloor {
|
||||
width: 100%;
|
||||
height: 700px;
|
||||
}
|
||||
.bottom-title {
|
||||
font-size: 32px;
|
||||
margin-top: 10px;
|
||||
padding-left: 115px;
|
||||
}
|
||||
.detail-apartment-wrapper {
|
||||
background-color: #ffffff;
|
||||
z-index: 1000;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.detail-close-btn {
|
||||
position: absolute;
|
||||
right: 150px;
|
||||
top: 15px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
226
src/pages/ApartmentIframe.vue
Normal file
226
src/pages/ApartmentIframe.vue
Normal file
@ -0,0 +1,226 @@
|
||||
<template>
|
||||
<div class="apartment">
|
||||
<div class="apartment-img">
|
||||
<img
|
||||
v-if="room.plan_url"
|
||||
:src="room.plan_url"
|
||||
/>
|
||||
<img
|
||||
v-else
|
||||
style="width: 120px"
|
||||
src="@/shared/assets/images/complexes/gallery.svg"
|
||||
/>
|
||||
<div class="apartment-status"
|
||||
:style="{ backgroundColor: getBackgroundColor(room.status_type)}"
|
||||
>{{ room.status_name }}</div>
|
||||
</div>
|
||||
<div class="apartment-info">
|
||||
<table class="info-table">
|
||||
<tr>
|
||||
<td class="type-title">{{ room_types[room.type] }}:</td>
|
||||
<td class="td-value">{{ room.area }} кв.м2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="full-price">{{ formatNumber(room.total_amount) }} руб.</td>
|
||||
<td class="td-value">{{ getAreaPrice(room.area, room.total_amount) }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class="apartment-additional-info">
|
||||
<tr v-if="room.rooms_count">
|
||||
<td class="td-title">Кол-во комнат</td>
|
||||
<td>{{ room.rooms_count }} ккв</td>
|
||||
</tr>
|
||||
<tr v-if="room.entrance_name">
|
||||
<td class="td-title">№ подъезда</td>
|
||||
<td>{{ room.entrance_name }}</td>
|
||||
</tr>
|
||||
<tr v-if="room.number">
|
||||
<td class="td-title">№ кв</td>
|
||||
<td>{{ room.number }}</td>
|
||||
</tr>
|
||||
<tr v-if="room.floor">
|
||||
<td class="td-title">Этаж</td>
|
||||
<td>{{ room.floor }}</td>
|
||||
</tr>
|
||||
<tr v-if="room.windows_placement_id">
|
||||
<td class="td-title">Куда выходят окна</td>
|
||||
<td>{{ getWindowsPlacement(room.windows_placement_id) }}</td>
|
||||
</tr>
|
||||
<tr v-if="room.type">
|
||||
<td class="td-title">Тип</td>
|
||||
<td>{{ room_types[room.type] }}</td>
|
||||
</tr> <tr v-if="room.decoration_id">
|
||||
<td class="td-title">Отделка</td>
|
||||
<td>{{ getDecoration(room.decoration_id) }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="primary-btn apt" @click="emits('openApartment', room)">
|
||||
Подробнее
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { room_types } from '@/shared/utils/util';
|
||||
|
||||
const props = defineProps({
|
||||
room: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
decorations: {
|
||||
type: Array,
|
||||
},
|
||||
windowsPlacements: {
|
||||
type: Array,
|
||||
}
|
||||
});
|
||||
|
||||
const emits = defineEmits(
|
||||
["openApartment"]
|
||||
);
|
||||
|
||||
|
||||
// const room_types = {
|
||||
|
||||
// }
|
||||
|
||||
const colors = JSON.parse(localStorage.getItem('colors')) || {}
|
||||
|
||||
|
||||
const statusMap = {
|
||||
reservation: 'color_reserved',
|
||||
available: 'color_free',
|
||||
not_for_sale: 'not_for_sale',
|
||||
sold: 'sold',
|
||||
}
|
||||
|
||||
|
||||
const getBackgroundColor = (statusType) => {
|
||||
const colorKey = statusMap[statusType]
|
||||
return colors[colorKey]
|
||||
}
|
||||
|
||||
const formatNumber = (price) => Number(price).toLocaleString("ru-RU");
|
||||
|
||||
const getDecoration = (decoration_id) => {
|
||||
if (!props.room.decoration_id) return 'Не указано'
|
||||
return props.decorations.find((decoration) => decoration.code == decoration_id)?.name
|
||||
}
|
||||
const getWindowsPlacement = (windows_placement_id) => {
|
||||
if (!windows_placement_id || !props.windowsPlacements) return ''
|
||||
|
||||
return props.windowsPlacements.find((windowsPlacement) => windowsPlacement.id == windows_placement_id)?.name
|
||||
}
|
||||
|
||||
const getAreaPrice = (area, total_amount) => {
|
||||
area = parseFloat(area)
|
||||
total_amount = parseFloat(total_amount)
|
||||
if (!area || !total_amount) return '—'
|
||||
|
||||
const price = Math.round(total_amount / area) // Округляем до целого
|
||||
return `${price.toLocaleString('ru-RU')} руб./м²`
|
||||
}
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.apartment {
|
||||
padding: 20px 30px;
|
||||
border: 1px solid #DEDEDE;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
max-width: 330px;
|
||||
|
||||
& .apartment-img {
|
||||
/* width: 270px; */
|
||||
height: 250px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
& img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
& .apartment-status {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
background-color: #ED4B00;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4px 6px;
|
||||
border-radius: 3px;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
& .apt {
|
||||
border-radius: 20px;
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
}
|
||||
& .apartment-info {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-top: 20px;
|
||||
& .info-table {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-bottom: 1px solid #F2F3F5;
|
||||
padding-bottom: 20px;
|
||||
& .td-value {
|
||||
font-size: 12px;
|
||||
color: #757575;
|
||||
font-weight: normal;
|
||||
text-align: right;
|
||||
}
|
||||
& .full-price {
|
||||
color: var(--main-color);
|
||||
}
|
||||
& tr {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
& tr + tr {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
& .apartment-additional-info {
|
||||
margin-top: 10px;
|
||||
width: 100%;
|
||||
.td-title {
|
||||
font-weight: normal;
|
||||
color: #7D7D7D;
|
||||
}
|
||||
& tr {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@media screen and (max-width:950px) {
|
||||
.apartment{
|
||||
padding: 20px 30px;
|
||||
border: 1px solid #DEDEDE;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
max-width: 351px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
15
src/pages/ApartmentListIframe.vue
Normal file
15
src/pages/ApartmentListIframe.vue
Normal file
@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<div class="apartments-warpper">
|
||||
<ApartmentIframe />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ApartmentIframe from './ApartmentIframe.vue';
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.apartments-wrapper {
|
||||
border: 1px solid black;
|
||||
}
|
||||
</style>
|
||||
292
src/pages/ApartmentsList.vue
Normal file
292
src/pages/ApartmentsList.vue
Normal file
@ -0,0 +1,292 @@
|
||||
<template>
|
||||
<div class="homePage relative bg-white flex justify-start items-start">
|
||||
<div v-if="apartmentsLoader" class="loader-div">
|
||||
<RocketSpinner/>
|
||||
</div>
|
||||
<!-- <IframeNavbar class="iframe-navbar-left"/> -->
|
||||
<div
|
||||
class="viewHome flex flex-col w-full h-full min-h-full justify-start items-start px-7.5 py-5"
|
||||
ref="viewHome"
|
||||
v-else
|
||||
>
|
||||
|
||||
<ViewHead
|
||||
:show_plans="false"
|
||||
:show_floors="false"
|
||||
:show_facades="false"
|
||||
:show_change_view="false"
|
||||
@update:isOpenFiltersMenu="isOpenFiltersMenu = $event"
|
||||
:filters="filters"
|
||||
:filteredCount="filterCounter"
|
||||
@update:viewType="viewType = $event"
|
||||
/>
|
||||
<div class="viewHome__body w-full h-full">
|
||||
<ChessList
|
||||
:decorations="fieldsStore.decorations"
|
||||
:windowsPlacements="fieldsStore.windowsPlacements"
|
||||
@update:is-open-window="openApartment"
|
||||
:entrances="entrances"
|
||||
:openedApartment="openedApartment"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<teleport to="body">
|
||||
<WindowInfoForList
|
||||
v-if="openedApartment"
|
||||
:isOpen="isOpenWindow"
|
||||
:apartment="openedApartment"
|
||||
:availableFloors="openApartmentAvailableFloors"
|
||||
@update:is-open="closeApartment"
|
||||
@floor:changed="changeApartment"
|
||||
:decorations="fieldsStore.decorations"
|
||||
:windowsPlacements="fieldsStore.windowsPlacements"
|
||||
/>
|
||||
</teleport>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import ChessList from "@/widgets/homePage/viewHome/chessList.vue";
|
||||
import ViewHead from "@/widgets/homePage/viewHome/viewHead.vue";
|
||||
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from "vue";
|
||||
import api from "@/shared/api";
|
||||
import { useRoute } from "vue-router";
|
||||
import WindowInfoForList from "@/widgets/homePage/viewHome/windowInfoForList.vue";
|
||||
import { copyObject, fixRoomVisible } from "@/shared/utils/util";
|
||||
import eventBus from "@/eventBus";
|
||||
import { useFieldsStore } from "@/app/store/fields";
|
||||
import IframeNavbar from "@/components/IframeNavbar.vue";
|
||||
import RocketSpinner from "@/components/RocketSpinner.vue";
|
||||
const fieldsStore = useFieldsStore()
|
||||
|
||||
const route = useRoute();
|
||||
const entrances = ref([]);
|
||||
const openedApartment = ref({});
|
||||
const isOpenWindow = ref(false);
|
||||
const INITIAL_FILTERS = {
|
||||
rooms: [],
|
||||
selectedTypes: [],
|
||||
selectedDecorations: [],
|
||||
floor: {
|
||||
from: '',
|
||||
to: ''
|
||||
},
|
||||
price: {
|
||||
min: "",
|
||||
max: "",
|
||||
},
|
||||
square: {
|
||||
min: "",
|
||||
max: "",
|
||||
},
|
||||
squareKitchen: {
|
||||
min: "",
|
||||
max: "",
|
||||
},
|
||||
useSquarePrice: false,
|
||||
hiddenPrice: false,
|
||||
onlyFree: false,
|
||||
view: {
|
||||
name: "Список",
|
||||
value: "list",
|
||||
icon: "list",
|
||||
},
|
||||
}
|
||||
const filters = ref(copyObject(INITIAL_FILTERS));
|
||||
const apartmentsLoader = ref(true)
|
||||
|
||||
const clearFilters = () => {
|
||||
filters.value = copyObject(INITIAL_FILTERS)
|
||||
}
|
||||
|
||||
const filterCounter = computed(() => {
|
||||
return (entrances.value || []).reduce((acc, entrance) => {
|
||||
return (
|
||||
acc +
|
||||
entrance.floors.reduce((floorAcc, floor) => {
|
||||
return (
|
||||
floorAcc +
|
||||
floor.rooms.reduce((roomsAcc, room) => {
|
||||
return roomsAcc + +room.visible;
|
||||
}, 0)
|
||||
);
|
||||
}, 0)
|
||||
);
|
||||
}, 0);
|
||||
});
|
||||
|
||||
const openApartmentAvailableFloors = computed(() => {
|
||||
if (openedApartment.value && openedApartmentEntrance.value) {
|
||||
return openedApartmentEntrance.value.floors
|
||||
.filter((floor) => floor.rooms.find((room) => room.position == openedApartment.value.position))
|
||||
}
|
||||
|
||||
return []
|
||||
})
|
||||
|
||||
const openedApartmentEntrance = computed(() => {
|
||||
if (openedApartment.value) {
|
||||
return entrances.value.find(
|
||||
(entrance) => entrance.id == openedApartment.value.entrance_id
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
const openApartment = (apartment) => {
|
||||
openedApartment.value = apartment;
|
||||
isOpenWindow.value = true;
|
||||
};
|
||||
|
||||
const closeApartment = () => {
|
||||
isOpenWindow.value = false;
|
||||
setTimeout(() => {
|
||||
openedApartment.value = {};
|
||||
}, 350);
|
||||
};
|
||||
|
||||
|
||||
const changeApartment = (floorPosition) => {
|
||||
const floor = openedApartmentEntrance.value.floors.find(
|
||||
(entranceFloor) => entranceFloor.position == floorPosition
|
||||
);
|
||||
|
||||
openApartment(
|
||||
floor.rooms.find((room) => room.position == openedApartment.value.position)
|
||||
);
|
||||
};
|
||||
|
||||
const filterChange = () => {
|
||||
entrances.value.forEach(entrance => {
|
||||
entrance.floors.forEach(floor => {
|
||||
floor.rooms.forEach((room) => fixRoomVisible(room, filters.value))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
watch(filters,
|
||||
(newV) => {
|
||||
if (newV) {
|
||||
filterChange()
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
const parseQueryFilters = () => {
|
||||
Object.entries(route.query).forEach(([key, value]) => {
|
||||
const skipKeys = ['period','selectedMetros', 'address', 'complexes']
|
||||
if (skipKeys.includes(key)) return
|
||||
|
||||
if (key == 'area_from') {
|
||||
key = 'square_min'
|
||||
}
|
||||
if (key == 'area_to') {
|
||||
key = 'square_max'
|
||||
}
|
||||
|
||||
if (key == 'price_from') {
|
||||
key = 'price_min'
|
||||
}
|
||||
|
||||
if (key == 'price_to') {
|
||||
key = 'price_max'
|
||||
}
|
||||
|
||||
if (key == 'selectedDecorations' && Array.isArray(value)) {
|
||||
value = value.map((num) => parseInt(num));
|
||||
}
|
||||
|
||||
if (key == 'selectedDecorations' && typeof value == 'string' ) {
|
||||
value = [parseInt(value)];
|
||||
}
|
||||
|
||||
if (key == 'selectedWindowsPlacements' && Array.isArray(value)) {
|
||||
value = value.map((num) => parseInt(num));
|
||||
}
|
||||
|
||||
if (key == 'selectedWindowsPlacements' && typeof value == 'string' ) {
|
||||
value = [parseInt(value)];
|
||||
}
|
||||
|
||||
|
||||
const keys = key.split('_')
|
||||
if (keys.length > 1) {
|
||||
filters.value[keys[0]][keys[1]] = value
|
||||
} else {
|
||||
filters.value[key] = value == 'true' ? true : value == 'false' ? false : value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await api.getComplexEntrances(route.params.id, ["rooms"]).then((response) => {
|
||||
const updatedEntrances = response.data.sort((a, b) =>
|
||||
a.position > b.position ? 1 : -1
|
||||
);
|
||||
|
||||
updatedEntrances.forEach(
|
||||
(entrance) =>
|
||||
(entrance.floors = entrance.floors.sort((a, b) =>
|
||||
a.position > b.position ? 1 : -1
|
||||
))
|
||||
);
|
||||
|
||||
updatedEntrances.forEach((entrance) =>
|
||||
entrance.floors.forEach((floor) =>
|
||||
floor.rooms.forEach((room) => {
|
||||
room.visible = false;
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
entrances.value = updatedEntrances;
|
||||
}).finally(() => apartmentsLoader.value = false);
|
||||
await nextTick()
|
||||
parseQueryFilters()
|
||||
filterChange()
|
||||
|
||||
eventBus.$on('clear-filters', clearFilters)
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
eventBus.$off('clear-filters', clearFilters)
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
|
||||
.homePage {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
/* overflow-x: hidden; */
|
||||
}
|
||||
.viewHome {
|
||||
position: relative;
|
||||
width: 80%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
flex-grow: 1;
|
||||
|
||||
&__body {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 700px;
|
||||
}
|
||||
}
|
||||
|
||||
.iframe-navbar-left {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
max-height: 100vh;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
769
src/pages/ComplexPage.vue
Normal file
769
src/pages/ComplexPage.vue
Normal file
@ -0,0 +1,769 @@
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<FormAddComplex
|
||||
:isOpen="formEditComplexOpen"
|
||||
@closeModel="formEditComplexOpen = !formEditComplexOpen"
|
||||
:complex="copyObject(complex)"
|
||||
@update-complex="onComplexUpdate"
|
||||
/>
|
||||
</Teleport>
|
||||
<div class="complexPage">
|
||||
<ListHome
|
||||
@open-add-home="openAddHome"
|
||||
:complex_id="complex_id"
|
||||
:houses="houses"
|
||||
:loadHome="loadHome"
|
||||
/>
|
||||
<div class="infoByComplex">
|
||||
<div class="infoByComplex__title">{{ complex.name }}</div>
|
||||
<div class="infoByComplex__content">
|
||||
<div class="infoByComplex-block infoByComplex__infoBlock">
|
||||
<div class="block-head infoBlock__head">
|
||||
<div class="head__title">Информация об объекте</div>
|
||||
<div class="head__btnMenu">
|
||||
<Button style="background: transparent; border: none" type="button" @click="toggle" aria-haspopup="true" aria-controls="overlay_menu">
|
||||
<svg
|
||||
width="18px"
|
||||
height="18px"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M10.591 17.341C9.71231 18.2196 8.28768 18.2196 7.409 17.341C6.53032 16.4623 6.53032 15.0377 7.409 14.159C8.28768 13.2803 9.71231 13.2803 10.591 14.159C11.4697 15.0377 11.4697 16.4623 10.591 17.341Z"
|
||||
fill="#BDBDBD"
|
||||
/>
|
||||
<path
|
||||
d="M10.591 10.591C9.71231 11.4697 8.28768 11.4697 7.409 10.591C6.53032 9.71231 6.53032 8.28768 7.409 7.40901C8.28768 6.53033 9.71231 6.53033 10.591 7.40901C11.4697 8.28768 11.4697 9.71231 10.591 10.591Z"
|
||||
fill="#BDBDBD"
|
||||
/>
|
||||
<path
|
||||
d="M10.591 3.84098C9.71231 4.71966 8.28768 4.71966 7.409 3.84098C6.53032 2.96231 6.53032 1.53768 7.409 0.659007C8.28768 -0.219669 9.71231 -0.219669 10.591 0.659007C11.4697 1.53768 11.4697 2.96231 10.591 3.84098Z"
|
||||
fill="#BDBDBD"
|
||||
/>
|
||||
</svg>
|
||||
</Button>
|
||||
<Menu :pt="{ root: { class: ['custom-p-menu'] } }" ref="menuItems" id="overlay_menu" :model="items" :popup="true" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="block-content" style="display: flex; flex-direction: column; gap: 5px;">
|
||||
<div class="textBlock__item">
|
||||
<div class="item__label">
|
||||
Тип комплекса: Жилой комплекс в новостройке
|
||||
</div>
|
||||
</div>
|
||||
<div class="textBlock__item">
|
||||
<div class="item__label">
|
||||
Адрес: {{ complex.address.address }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="textBlock__item" v-if="complex.metros?.length">
|
||||
<div class="item__label" style="display: flex; align-items: start; gap: 10px;">
|
||||
<span>Метро: </span>
|
||||
<div>
|
||||
<div :key="metro.id" style="display: flex; gap: 5px" v-for="metro in complex.metros">
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M13.242 2.24765L9.99989 12.4689L6.75798 2.24765C2.90144 3.57322 0 7.16913 0 11.2916C0 13.7746 1.01298 16.0644 2.65178 17.752H7.35212L7.84488 15.6151C2.22957 13.4149 3.29134 6.59717 5.32701 5.48428C5.56864 5.55694 9.10942 17.7017 9.10942 17.7017C9.15779 17.7017 9.31493 17.7017 9.50692 17.7017C9.54747 17.7017 9.70018 17.7017 9.89048 17.7017C9.95934 17.7017 10.0337 17.7017 10.1091 17.7017C10.2384 17.7017 10.3725 17.7017 10.4924 17.7017C10.6846 17.7017 10.8416 17.7017 10.8902 17.7017C10.8902 17.7017 14.4311 5.55694 14.6726 5.48428C16.7085 6.59717 17.7698 13.4149 12.1545 15.6151L12.6475 17.752H17.3478C18.9868 16.0644 19.9998 13.7746 19.9998 11.2916C19.9998 7.16913 17.0983 3.57322 13.242 2.24765Z"
|
||||
fill="#8EB3F5"
|
||||
/>
|
||||
</svg>
|
||||
{{ metro.metro }}
|
||||
<div class="time">{{ metro.subway_time}} минут <span v-if="metro.subway_type == 1">пешком</span><span v-else-if="metro.subway_type == 2">на транспорте</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="infoByComplex-block infoByComplex__media">
|
||||
<div class="block-head infoBlock__head">
|
||||
<div class="head__title">Медиа-материалы</div>
|
||||
</div>
|
||||
<div class="block__plug media__plug">
|
||||
<div class="card">
|
||||
<FileUpload
|
||||
:pt="{
|
||||
root: { class: ['flex', 'flex-col-reverse'] },
|
||||
content: {
|
||||
class: [
|
||||
'!border-transparent',
|
||||
'!p-0',
|
||||
files.length > 0 ? '' : 'dashed-content',
|
||||
],
|
||||
},
|
||||
buttonbar: { class: ['!bg-white', '!border-transparent'] },
|
||||
}"
|
||||
@upload="onTemplatedUpload($event)"
|
||||
:multiple="true"
|
||||
accept="image/*"
|
||||
:maxFileSize="10000000"
|
||||
@select="onSelectedFiles"
|
||||
>
|
||||
<template
|
||||
#header="{
|
||||
chooseCallback,
|
||||
uploadCallback,
|
||||
clearCallback,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="flex flex-wrap justify-center align-items-center flex-1 gap-2"
|
||||
>
|
||||
<div class="flex gap-2">
|
||||
<!-- <Button
|
||||
@click="chooseCallback()"
|
||||
icon="pi pi-images"
|
||||
rounded
|
||||
outlined
|
||||
></Button> -->
|
||||
<MyButton
|
||||
class="plug__btn"
|
||||
:theme="'green'"
|
||||
@click.stop="chooseCallback()"
|
||||
><svg
|
||||
width="19"
|
||||
height="18"
|
||||
viewBox="0 0 19 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M9.5 1.5C8.01664 1.5 6.5666 1.93987 5.33323 2.76398C4.09986 3.58809 3.13856 4.75943 2.57091 6.12987C2.00325 7.50032 1.85472 9.00832 2.14411 10.4632C2.4335 11.918 3.14781 13.2544 4.1967 14.3033C5.2456 15.3522 6.58197 16.0665 8.03683 16.3559C9.49168 16.6453 10.9997 16.4968 12.3701 15.9291C13.7406 15.3614 14.9119 14.4001 15.736 13.1668C16.5601 11.9334 17 10.4834 17 9C17 8.01509 16.806 7.03982 16.4291 6.12987C16.0522 5.21993 15.4997 4.39314 14.8033 3.6967C14.1069 3.00026 13.2801 2.44781 12.3701 2.0709C11.4602 1.69399 10.4849 1.5 9.5 1.5ZM9.5 15C8.31332 15 7.15328 14.6481 6.16658 13.9888C5.17989 13.3295 4.41085 12.3925 3.95673 11.2961C3.5026 10.1997 3.38378 8.99334 3.61529 7.82946C3.8468 6.66557 4.41825 5.59647 5.25736 4.75736C6.09648 3.91824 7.16558 3.3468 8.32946 3.11529C9.49335 2.88378 10.6997 3.0026 11.7961 3.45672C12.8925 3.91085 13.8295 4.67988 14.4888 5.66658C15.1481 6.65327 15.5 7.81331 15.5 9C15.5 10.5913 14.8679 12.1174 13.7426 13.2426C12.6174 14.3679 11.0913 15 9.5 15ZM12.5 8.25H10.25V6C10.25 5.80109 10.171 5.61032 10.0303 5.46967C9.88968 5.32902 9.69892 5.25 9.5 5.25C9.30109 5.25 9.11033 5.32902 8.96967 5.46967C8.82902 5.61032 8.75 5.80109 8.75 6V8.25H6.5C6.30109 8.25 6.11033 8.32902 5.96967 8.46967C5.82902 8.61032 5.75 8.80109 5.75 9C5.75 9.19891 5.82902 9.38968 5.96967 9.53033C6.11033 9.67098 6.30109 9.75 6.5 9.75H8.75V12C8.75 12.1989 8.82902 12.3897 8.96967 12.5303C9.11033 12.671 9.30109 12.75 9.5 12.75C9.69892 12.75 9.88968 12.671 10.0303 12.5303C10.171 12.3897 10.25 12.1989 10.25 12V9.75H12.5C12.6989 9.75 12.8897 9.67098 13.0303 9.53033C13.171 9.38968 13.25 9.19891 13.25 9C13.25 8.80109 13.171 8.61032 13.0303 8.46967C12.8897 8.32902 12.6989 8.25 12.5 8.25Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
Добавить материалы
|
||||
</MyButton>
|
||||
<!-- <Button
|
||||
@click="uploadEvent(uploadCallback)"
|
||||
icon="pi pi-cloud-upload"
|
||||
rounded
|
||||
outlined
|
||||
severity="success"
|
||||
:disabled="!files || files.length === 0"
|
||||
></Button>
|
||||
<Button
|
||||
@click="clearCallback()"
|
||||
icon="pi pi-times"
|
||||
rounded
|
||||
outlined
|
||||
severity="danger"
|
||||
:disabled="!files || files.length === 0"
|
||||
></Button> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template
|
||||
#content="{
|
||||
uploadedFiles,
|
||||
removeUploadedFileCallback,
|
||||
removeFileCallback,
|
||||
}"
|
||||
>
|
||||
<div v-if="files.length > 0">
|
||||
<div
|
||||
class="flex flex-wrap !p-0 sm:p-5 gap-2"
|
||||
style="max-height: 385px; overflow: auto"
|
||||
>
|
||||
<div
|
||||
v-for="(file, index) of files"
|
||||
:key="file.name + file.type + file.size"
|
||||
style="align-items: center"
|
||||
class="upload-file-card flex-grow justify-between card m-0 px-6 flex flex-column border-1 surface-border gap-3"
|
||||
>
|
||||
<div style="min-width: 100px; max-width: 100px">
|
||||
<img
|
||||
role="presentation"
|
||||
:alt="file.name"
|
||||
:src="file.objectURL"
|
||||
width="100"
|
||||
height="50"
|
||||
style="max-height: 100px;object-fit: contain;"
|
||||
/>
|
||||
</div>
|
||||
<span class="font-semibold file-name">{{
|
||||
file.name
|
||||
}}</span>
|
||||
<div
|
||||
style="
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
text-wrap: nowrap;
|
||||
"
|
||||
>
|
||||
{{ formatSize(file.size) }}
|
||||
</div>
|
||||
<div class="flex-col justify-around image-actions-container">
|
||||
<img src="@/shared/assets/images/complexes/settings.svg" @click.stop="openCropper(file)" alt="Настроить">
|
||||
<img src='@/shared/assets/images/complexes/trash.svg' @click.stop="onRemoveTemplatingFile(file.id, index)" alt="Удалить">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
style="
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
"
|
||||
>
|
||||
<div class="plug__icon">
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M31.6668 16.6667C31.2248 16.6667 30.8009 16.8423 30.4883 17.1548C30.1758 17.4674 30.0002 17.8913 30.0002 18.3333V23.9667L27.5335 21.5C26.6625 20.6359 25.4854 20.151 24.2585 20.151C23.0316 20.151 21.8545 20.6359 20.9835 21.5L19.8168 22.6833L15.6835 18.5333C14.8125 17.6692 13.6354 17.1844 12.4085 17.1844C11.1816 17.1844 10.0045 17.6692 9.1335 18.5333L6.66683 21.0167V11.6667C6.66683 11.2246 6.84242 10.8007 7.15498 10.4882C7.46755 10.1756 7.89147 10 8.3335 10H21.6668C22.1089 10 22.5328 9.82441 22.8453 9.51185C23.1579 9.19929 23.3335 8.77536 23.3335 8.33334C23.3335 7.89131 23.1579 7.46739 22.8453 7.15482C22.5328 6.84226 22.1089 6.66667 21.6668 6.66667H8.3335C7.00741 6.66667 5.73564 7.19345 4.79796 8.13114C3.86028 9.06882 3.3335 10.3406 3.3335 11.6667V32.0333C3.33789 33.2608 3.82745 34.4368 4.69542 35.3047C5.56339 36.1727 6.73934 36.6623 7.96683 36.6667H28.7002C29.1518 36.6631 29.6007 36.5957 30.0335 36.4667C30.9959 36.1967 31.8431 35.6185 32.4453 34.8207C33.0474 34.0229 33.3711 33.0495 33.3668 32.05V18.3333C33.3669 18.1116 33.3227 17.8921 33.2369 17.6877C33.151 17.4833 33.0253 17.2981 32.8669 17.1429C32.7086 16.9877 32.5209 16.8656 32.3149 16.7838C32.1088 16.7021 31.8885 16.6622 31.6668 16.6667ZM8.3335 33.3333C7.89147 33.3333 7.46755 33.1577 7.15498 32.8452C6.84242 32.5326 6.66683 32.1087 6.66683 31.6667V25.7167L11.4835 20.9C11.7271 20.6578 12.0566 20.5218 12.4002 20.5218C12.7437 20.5218 13.0732 20.6578 13.3168 20.9L25.7668 33.3333H8.3335ZM30.0002 31.6667C29.9895 31.9894 29.8852 32.3021 29.7002 32.5667L22.1668 25L23.3502 23.8333C23.4697 23.7114 23.6123 23.6145 23.7697 23.5484C23.9271 23.4822 24.0961 23.4481 24.2668 23.4481C24.4376 23.4481 24.6066 23.4822 24.764 23.5484C24.9214 23.6145 25.064 23.7114 25.1835 23.8333L30.0002 28.6833V31.6667ZM35.0002 6.66667H33.3335V5C33.3335 4.55798 33.1579 4.13405 32.8453 3.82149C32.5328 3.50893 32.1089 3.33334 31.6668 3.33334C31.2248 3.33334 30.8009 3.50893 30.4883 3.82149C30.1758 4.13405 30.0002 4.55798 30.0002 5V6.66667H28.3335C27.8915 6.66667 27.4675 6.84226 27.155 7.15482C26.8424 7.46739 26.6668 7.89131 26.6668 8.33334C26.6668 8.77536 26.8424 9.19929 27.155 9.51185C27.4675 9.82441 27.8915 10 28.3335 10H30.0002V11.6667C30.0002 12.1087 30.1758 12.5326 30.4883 12.8452C30.8009 13.1577 31.2248 13.3333 31.6668 13.3333C32.1089 13.3333 32.5328 13.1577 32.8453 12.8452C33.1579 12.5326 33.3335 12.1087 33.3335 11.6667V10H35.0002C35.4422 10 35.8661 9.82441 36.1787 9.51185C36.4912 9.19929 36.6668 8.77536 36.6668 8.33334C36.6668 7.89131 36.4912 7.46739 36.1787 7.15482C35.8661 6.84226 35.4422 6.66667 35.0002 6.66667Z"
|
||||
fill="#BDBDBD"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="plug__text">
|
||||
У вас еще не загружены видео и фото материалы
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</FileUpload>
|
||||
<Cropper
|
||||
v-if="fileToCrop"
|
||||
:imgSrc="fileToCrop.objectURL"
|
||||
:isOpen="isOpenCropper"
|
||||
:onClose="closeCropper"
|
||||
@save:image="handelSaveImage"
|
||||
/>
|
||||
</div>
|
||||
<!-- <MyButton class="plug__btn" :theme="'green'" @click.stop="chooseCallback()"
|
||||
><svg
|
||||
width="19"
|
||||
height="18"
|
||||
viewBox="0 0 19 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M9.5 1.5C8.01664 1.5 6.5666 1.93987 5.33323 2.76398C4.09986 3.58809 3.13856 4.75943 2.57091 6.12987C2.00325 7.50032 1.85472 9.00832 2.14411 10.4632C2.4335 11.918 3.14781 13.2544 4.1967 14.3033C5.2456 15.3522 6.58197 16.0665 8.03683 16.3559C9.49168 16.6453 10.9997 16.4968 12.3701 15.9291C13.7406 15.3614 14.9119 14.4001 15.736 13.1668C16.5601 11.9334 17 10.4834 17 9C17 8.01509 16.806 7.03982 16.4291 6.12987C16.0522 5.21993 15.4997 4.39314 14.8033 3.6967C14.1069 3.00026 13.2801 2.44781 12.3701 2.0709C11.4602 1.69399 10.4849 1.5 9.5 1.5ZM9.5 15C8.31332 15 7.15328 14.6481 6.16658 13.9888C5.17989 13.3295 4.41085 12.3925 3.95673 11.2961C3.5026 10.1997 3.38378 8.99334 3.61529 7.82946C3.8468 6.66557 4.41825 5.59647 5.25736 4.75736C6.09648 3.91824 7.16558 3.3468 8.32946 3.11529C9.49335 2.88378 10.6997 3.0026 11.7961 3.45672C12.8925 3.91085 13.8295 4.67988 14.4888 5.66658C15.1481 6.65327 15.5 7.81331 15.5 9C15.5 10.5913 14.8679 12.1174 13.7426 13.2426C12.6174 14.3679 11.0913 15 9.5 15ZM12.5 8.25H10.25V6C10.25 5.80109 10.171 5.61032 10.0303 5.46967C9.88968 5.32902 9.69892 5.25 9.5 5.25C9.30109 5.25 9.11033 5.32902 8.96967 5.46967C8.82902 5.61032 8.75 5.80109 8.75 6V8.25H6.5C6.30109 8.25 6.11033 8.32902 5.96967 8.46967C5.82902 8.61032 5.75 8.80109 5.75 9C5.75 9.19891 5.82902 9.38968 5.96967 9.53033C6.11033 9.67098 6.30109 9.75 6.5 9.75H8.75V12C8.75 12.1989 8.82902 12.3897 8.96967 12.5303C9.11033 12.671 9.30109 12.75 9.5 12.75C9.69892 12.75 9.88968 12.671 10.0303 12.5303C10.171 12.3897 10.25 12.1989 10.25 12V9.75H12.5C12.6989 9.75 12.8897 9.67098 13.0303 9.53033C13.171 9.38968 13.25 9.19891 13.25 9C13.25 8.80109 13.171 8.61032 13.0303 8.46967C12.8897 8.32902 12.6989 8.25 12.5 8.25Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
Добавить материалы
|
||||
</MyButton> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="infoByComplex-block infoByComplex__generalPlan">
|
||||
<div class="block-disabled" />
|
||||
<div class="block-head infoBlock__head">
|
||||
<div class="head__title"><span>Генпланы</span><span>В разработке</span></div>
|
||||
</div>
|
||||
<div class="block__plug media__plug">
|
||||
<div class="plug__icon">
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M4.16651 14.7667L19.1665 23.4333C19.4199 23.5796 19.7073 23.6566 19.9998 23.6566C20.2924 23.6566 20.5798 23.5796 20.8332 23.4333L35.8332 14.7667C36.0851 14.6212 36.2945 14.4124 36.4407 14.161C36.5869 13.9095 36.6648 13.6242 36.6665 13.3333C36.6677 13.0396 36.5912 12.7509 36.4449 12.4962C36.2985 12.2416 36.0875 12.0302 35.8332 11.8833L20.8332 3.23333C20.5798 3.08705 20.2924 3.01004 19.9998 3.01004C19.7073 3.01004 19.4199 3.08705 19.1665 3.23333L4.16651 11.8833C3.91217 12.0302 3.70115 12.2416 3.55481 12.4962C3.40847 12.7509 3.33201 13.0396 3.33318 13.3333C3.33494 13.6242 3.41278 13.9095 3.55897 14.161C3.70516 14.4124 3.91462 14.6212 4.16651 14.7667ZM19.9998 6.66666L31.6665 13.3333L19.9998 20L8.33318 13.3333L19.9998 6.66666ZM34.1665 18.6167L19.9998 26.6667L5.83318 18.55C5.64296 18.4398 5.43281 18.3683 5.21485 18.3397C4.99688 18.311 4.77541 18.3258 4.56317 18.3831C4.35094 18.4404 4.15214 18.5392 3.97823 18.6737C3.80432 18.8081 3.65873 18.9757 3.54985 19.1667C3.33256 19.5494 3.27545 20.0025 3.39098 20.4271C3.50651 20.8517 3.78529 21.2135 4.16651 21.4333L19.1665 30.1C19.4199 30.2463 19.7073 30.3233 19.9998 30.3233C20.2924 30.3233 20.5798 30.2463 20.8332 30.1L35.8332 21.4333C36.2144 21.2135 36.4932 20.8517 36.6087 20.4271C36.7242 20.0025 36.6671 19.5494 36.4498 19.1667C36.341 18.9757 36.1954 18.8081 36.0215 18.6737C35.8475 18.5392 35.6488 18.4404 35.4365 18.3831C35.2243 18.3258 35.0028 18.311 34.7848 18.3397C34.5669 18.3683 34.3567 18.4398 34.1665 18.55V18.6167ZM34.1665 25.2833L19.9998 33.3333L5.83318 25.2167C5.64296 25.1065 5.43281 25.035 5.21485 25.0064C4.99688 24.9777 4.77541 24.9925 4.56317 25.0498C4.35094 25.1071 4.15214 25.2059 3.97823 25.3403C3.80432 25.4748 3.65873 25.6424 3.54985 25.8333C3.33256 26.216 3.27545 26.6691 3.39098 27.0938C3.50651 27.5184 3.78529 27.8801 4.16651 28.1L19.1665 36.7667C19.4199 36.9129 19.7073 36.99 19.9998 36.99C20.2924 36.99 20.5798 36.9129 20.8332 36.7667L35.8332 28.1C36.2144 27.8801 36.4932 27.5184 36.6087 27.0938C36.7242 26.6691 36.6671 26.216 36.4498 25.8333C36.341 25.6424 36.1954 25.4748 36.0215 25.3403C35.8475 25.2059 35.6488 25.1071 35.4365 25.0498C35.2243 24.9925 35.0028 24.9777 34.7848 25.0064C34.5669 25.035 34.3567 25.1065 34.1665 25.2167V25.2833Z"
|
||||
fill="#BDBDBD"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="plug__text">
|
||||
У вас еще не загружено ни одного генплана
|
||||
</div>
|
||||
<MyButton :theme="'green'" class="plug__btn"
|
||||
><svg
|
||||
width="19"
|
||||
height="18"
|
||||
viewBox="0 0 19 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M9.5 1.5C8.01664 1.5 6.5666 1.93987 5.33323 2.76398C4.09986 3.58809 3.13856 4.75943 2.57091 6.12987C2.00325 7.50032 1.85472 9.00832 2.14411 10.4632C2.4335 11.918 3.14781 13.2544 4.1967 14.3033C5.2456 15.3522 6.58197 16.0665 8.03683 16.3559C9.49168 16.6453 10.9997 16.4968 12.3701 15.9291C13.7406 15.3614 14.9119 14.4001 15.736 13.1668C16.5601 11.9334 17 10.4834 17 9C17 8.01509 16.806 7.03982 16.4291 6.12987C16.0522 5.21993 15.4997 4.39314 14.8033 3.6967C14.1069 3.00026 13.2801 2.44781 12.3701 2.0709C11.4602 1.69399 10.4849 1.5 9.5 1.5ZM9.5 15C8.31332 15 7.15328 14.6481 6.16658 13.9888C5.17989 13.3295 4.41085 12.3925 3.95673 11.2961C3.5026 10.1997 3.38378 8.99334 3.61529 7.82946C3.8468 6.66557 4.41825 5.59647 5.25736 4.75736C6.09648 3.91824 7.16558 3.3468 8.32946 3.11529C9.49335 2.88378 10.6997 3.0026 11.7961 3.45672C12.8925 3.91085 13.8295 4.67988 14.4888 5.66658C15.1481 6.65327 15.5 7.81331 15.5 9C15.5 10.5913 14.8679 12.1174 13.7426 13.2426C12.6174 14.3679 11.0913 15 9.5 15ZM12.5 8.25H10.25V6C10.25 5.80109 10.171 5.61032 10.0303 5.46967C9.88968 5.32902 9.69892 5.25 9.5 5.25C9.30109 5.25 9.11033 5.32902 8.96967 5.46967C8.82902 5.61032 8.75 5.80109 8.75 6V8.25H6.5C6.30109 8.25 6.11033 8.32902 5.96967 8.46967C5.82902 8.61032 5.75 8.80109 5.75 9C5.75 9.19891 5.82902 9.38968 5.96967 9.53033C6.11033 9.67098 6.30109 9.75 6.5 9.75H8.75V12C8.75 12.1989 8.82902 12.3897 8.96967 12.5303C9.11033 12.671 9.30109 12.75 9.5 12.75C9.69892 12.75 9.88968 12.671 10.0303 12.5303C10.171 12.3897 10.25 12.1989 10.25 12V9.75H12.5C12.6989 9.75 12.8897 9.67098 13.0303 9.53033C13.171 9.38968 13.25 9.19891 13.25 9C13.25 8.80109 13.171 8.61032 13.0303 8.46967C12.8897 8.32902 12.6989 8.25 12.5 8.25Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
Добавить генплан
|
||||
</MyButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<FormAddHome
|
||||
:isOpen="isOpenForm"
|
||||
ref="formAddHome"
|
||||
:complex="complex"
|
||||
@close-model="isOpenForm = !isOpenForm"
|
||||
@add_house="add_house"
|
||||
/>
|
||||
<Toast />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import Toast from "primevue/toast";
|
||||
import FileUpload from "primevue/fileupload";
|
||||
import Menu from "primevue/menu";
|
||||
import Button from "primevue/button";
|
||||
import { useToast } from "primevue/usetoast";
|
||||
|
||||
import { useComplexStore } from "@/app/store/complex";
|
||||
|
||||
import bus from "@/eventBus";
|
||||
import api from "@/shared/api";
|
||||
import MyButton from "@/shared/UI/MyButton.vue";
|
||||
import { copyObject } from "@/shared/utils/util";
|
||||
|
||||
import FormAddHome from "@/widgets/forms/formAddHome.vue";
|
||||
import ListHome from "@/widgets/listHome.vue";
|
||||
import Cropper from "@/widgets/Cropper.vue";
|
||||
import FormAddComplex from "@/widgets/forms/formAddComplex.vue";
|
||||
|
||||
const route = useRoute();
|
||||
const toast = useToast();
|
||||
|
||||
const complexStore = useComplexStore()
|
||||
|
||||
const isOpenForm = ref(false);
|
||||
const formEditComplexOpen = ref(false)
|
||||
const complex = ref({ address: {}, metro: {} });
|
||||
const complex_id = route.params.id;
|
||||
const formAddHome = ref(null);
|
||||
const houses = ref([]);
|
||||
const loadHome = ref(true);
|
||||
const fileToCrop = ref();
|
||||
const isOpenCropper = ref(false);
|
||||
const totalSize = ref(0);
|
||||
const totalSizePercent = ref(0);
|
||||
const files = ref([]);
|
||||
const menuItems = ref();
|
||||
const items = ref([
|
||||
{
|
||||
items: [
|
||||
{
|
||||
label: 'Редактировать',
|
||||
command: () => formEditComplexOpen.value = true
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
|
||||
const toggle = (event) => {
|
||||
menuItems.value.toggle(event);
|
||||
};
|
||||
|
||||
|
||||
const openCropper = (file) => {
|
||||
fileToCrop.value = file;
|
||||
isOpenCropper.value = true;
|
||||
};
|
||||
|
||||
const handelSaveImage = (blob) => {
|
||||
if (fileToCrop.value) {
|
||||
const index = files.value.findIndex((file) => file.objectURL == fileToCrop.value.objectURL)
|
||||
|
||||
const file = new File(
|
||||
[blob],
|
||||
fileToCrop.value.name,
|
||||
{ type: blob.type }
|
||||
);
|
||||
file.objectURL = URL.createObjectURL(blob)
|
||||
|
||||
const bodyFormData = new FormData();
|
||||
bodyFormData.append("photo_id", fileToCrop.value.id);
|
||||
bodyFormData.append("photo", file);
|
||||
|
||||
return api.updateComplexPhoto(bodyFormData)
|
||||
.then((response) => {
|
||||
if (response.data) {
|
||||
const image = response.data.image
|
||||
fetch(image)
|
||||
.then((response) => response.blob())
|
||||
.then((blob) => {
|
||||
const file = new File(
|
||||
[blob],
|
||||
image.substring(image.lastIndexOf("/") + 1, image.length),
|
||||
{ type: blob.type }
|
||||
);
|
||||
file.objectURL = image;
|
||||
file.id = response.data.id
|
||||
|
||||
files.value[index] = file
|
||||
closeCropper();
|
||||
});
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
const closeCropper = () => {
|
||||
fileToCrop.value = null;
|
||||
isOpenCropper.value = false;
|
||||
};
|
||||
|
||||
const onRemoveTemplatingFile = (photo_id, index) => {
|
||||
api.deleteComplexPhoto(photo_id)
|
||||
.then((response) => {
|
||||
if (response.data) {
|
||||
files.value.splice(index, 1);
|
||||
}
|
||||
|
||||
})
|
||||
};
|
||||
|
||||
const onClearTemplatingUpload = (clear) => {
|
||||
clear();
|
||||
totalSize.value = 0;
|
||||
totalSizePercent.value = 0;
|
||||
};
|
||||
|
||||
const showUploadSuccess = () => {
|
||||
toast.add({ severity: 'success', summary: 'Загружено', detail: 'Медиа-материалы успешно загружены', life: 3000 });
|
||||
};
|
||||
|
||||
const onSelectedFiles = (event) => {
|
||||
const promises = event.files.map((file, index) => {
|
||||
const bodyFormData = new FormData();
|
||||
bodyFormData.append("complex_id", complex_id);
|
||||
bodyFormData.append("photos[]", file);
|
||||
return api.addPhotoToComplex(bodyFormData)
|
||||
.then((response) => {
|
||||
if (response.data) {
|
||||
const currentFile = event.files[index]
|
||||
currentFile.id = response.data[0].id
|
||||
files.value.push(file)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Promise.all(promises).then(() => {
|
||||
// files.value.push(...selectedFiles);
|
||||
let counterSize = 0
|
||||
files.value.forEach((file) => {
|
||||
counterSize += parseInt(formatSize(file.size));
|
||||
});
|
||||
totalSize.value = counterSize
|
||||
|
||||
showUploadSuccess()
|
||||
})
|
||||
|
||||
};
|
||||
|
||||
const uploadEvent = (callback) => {
|
||||
totalSizePercent.value = totalSize.value / 10;
|
||||
callback();
|
||||
};
|
||||
|
||||
// const onTemplatedUpload = () => {
|
||||
// toast.add({ severity: "info", summary: "Success", detail: "File Uploaded", life: 3000 });
|
||||
// };
|
||||
|
||||
const formatSize = (bytes) => {
|
||||
const k = 1024;
|
||||
const dm = 3;
|
||||
const sizes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||
|
||||
if (bytes === 0) {
|
||||
return `0 ${sizes[0]}`;
|
||||
}
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
const formattedSize = parseFloat((bytes / Math.pow(k, i)).toFixed(dm));
|
||||
|
||||
return `${formattedSize} ${sizes[i]}`;
|
||||
};
|
||||
async function getHouses() {
|
||||
loadHome.value = true;
|
||||
api.getComplexHouses(complex_id)
|
||||
.then((response) => {
|
||||
var res = response.data;
|
||||
houses.value = res;
|
||||
loadHome.value = false;
|
||||
})
|
||||
}
|
||||
|
||||
async function get_complex_one() {
|
||||
return await api.getComplexOne(complex_id, false)
|
||||
.then((response) => {
|
||||
var res = response.data;
|
||||
complexStore.setActiveComplex(res)
|
||||
complex.value = res;
|
||||
if (complex.value.images && complex.value.images.length) {
|
||||
complex.value.images.forEach(({ image, id }) => {
|
||||
fetch(image)
|
||||
.then((response) => response.blob())
|
||||
.then((blob) => {
|
||||
const file = new File(
|
||||
[blob],
|
||||
image.substring(image.lastIndexOf("/") + 1, image.length),
|
||||
{ type: blob.type }
|
||||
);
|
||||
file.objectURL = image;
|
||||
file.id = id
|
||||
files.value.push(file)
|
||||
});
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const openAddHome = () => {
|
||||
isOpenForm.value = !isOpenForm.value;
|
||||
formAddHome.value.open();
|
||||
};
|
||||
|
||||
const add_house = () => {
|
||||
isOpenForm.value = !isOpenForm.value;
|
||||
getHouses();
|
||||
};
|
||||
|
||||
const removeHouse = (house_id) => {
|
||||
api.deleteHouse(house_id).then(() => {
|
||||
houses.value = houses.value.filter((house) => house.id !== house_id)
|
||||
})
|
||||
}
|
||||
|
||||
const onComplexUpdate = (updatedComplex) => {
|
||||
complex.value = {...updatedComplex }
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
get_complex_one();
|
||||
getHouses();
|
||||
|
||||
|
||||
bus.$on('remove-house', removeHouse)
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
bus.$off('remove-house', removeHouse)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/app/styles/function.scss";
|
||||
.complexPage {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@apply bg-white;
|
||||
// margin: 0 30px;
|
||||
border-radius: 4px;
|
||||
padding: 40px 30px;
|
||||
padding-bottom: 0;
|
||||
overflow-y: scroll;
|
||||
.infoByComplex {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 30px;
|
||||
|
||||
|
||||
.head__title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 32px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
&__content {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr 1fr;
|
||||
grid-column-gap: vw(30, 1920);
|
||||
}
|
||||
&-block {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
padding: 30px;
|
||||
border: 1px solid;
|
||||
position: relative;
|
||||
@apply border-grey-400;
|
||||
.block-head {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid;
|
||||
@apply border-grey-400;
|
||||
padding-bottom: 30px;
|
||||
margin-bottom: 30px;
|
||||
.head__title {
|
||||
@apply text-grey-900;
|
||||
}
|
||||
}
|
||||
.block__content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.block__plug {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
row-gap: 15px;
|
||||
.plug__icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
||||
svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.plug__text {
|
||||
font-size: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
.plug__btn {
|
||||
@apply bg-green;
|
||||
@apply text-white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
&__infoBlock {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
.listHomes {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
flex-grow: 1;
|
||||
|
||||
&__title {
|
||||
width: 100%;
|
||||
font-size: 32px;
|
||||
padding-bottom: 30px;
|
||||
border-bottom: 1px solid;
|
||||
@apply border-grey-400;
|
||||
}
|
||||
|
||||
&__nav {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
&__list {
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
width: calc(100% + 60px);
|
||||
height: 200px;
|
||||
left: -30px;
|
||||
display: flex;
|
||||
@apply bg-grey-200;
|
||||
}
|
||||
}
|
||||
|
||||
.file-name {
|
||||
display: block;
|
||||
max-width: 140px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
|
||||
.upload-file-card {
|
||||
cursor: pointer;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
margin: 1px;
|
||||
}
|
||||
.upload-file-card:hover {
|
||||
background: aliceblue;
|
||||
outline: 1px solid #dedede;
|
||||
}
|
||||
|
||||
.image-actions-container {
|
||||
display: flex;
|
||||
width: 30px;
|
||||
height: 100%;
|
||||
padding: 0 5px;
|
||||
border-radius: 4px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
background-color: #fff;
|
||||
transition: .25s all;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.upload-file-card:hover .image-actions-container {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.dashed-content {
|
||||
border: 2px dashed #c8c2c2 !important;
|
||||
border-radius: 6px !important;
|
||||
}
|
||||
|
||||
.block-disabled {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: #cbcbcb38;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.complexPage .p-button:focus {
|
||||
box-shadow: unset !important;
|
||||
}
|
||||
.p-submenu-header {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.p-menuitem-link {
|
||||
border: none !important;
|
||||
}
|
||||
</style>
|
||||
162
src/pages/ComplexViewMode/index.vue
Normal file
162
src/pages/ComplexViewMode/index.vue
Normal file
@ -0,0 +1,162 @@
|
||||
<template>
|
||||
<div class="complexPage">
|
||||
<ListHome @open-add-home="openAddHome" :complex="complex" :houses="filteredHouses" :loadHome="loadHome" />
|
||||
</div>
|
||||
<Toast />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ListHome from "./widgets/ListHome.vue";
|
||||
import { ref, onMounted, computed } from "vue";
|
||||
const isOpenForm = ref(false);
|
||||
import { useRoute } from "vue-router";
|
||||
import Toast from "primevue/toast";
|
||||
import api from '@/shared/api'
|
||||
import { useComplexStore } from "@/app/store/complex";
|
||||
|
||||
const route = useRoute();
|
||||
const complexStore = useComplexStore()
|
||||
const complex = ref({ address: {}, metro: {} });
|
||||
const complex_id = route.params.id;
|
||||
const formAddHome = ref(null);
|
||||
const houses = ref([]);
|
||||
const loadHome = ref(true);
|
||||
const files = ref([]);
|
||||
const complex_filter = ref({});
|
||||
|
||||
const parseQueryFilters = () => {
|
||||
const multipleKeys = ['selectedTypes', 'selectedComplexTypes', 'rooms', 'rayons', 'selectedMetros', 'complexes', 'selectedDecorations']
|
||||
|
||||
|
||||
Object.entries(route.query).forEach(([key, value]) => {
|
||||
if (multipleKeys.includes(key)) {
|
||||
if (Array.isArray(value)) {
|
||||
value = value.map((num) => {
|
||||
if (!isNaN(+num) && key != 'rooms') {
|
||||
return parseInt(num)
|
||||
}
|
||||
|
||||
return num
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof value == 'string') {
|
||||
if (!isNaN(+value) && key != 'rooms') {
|
||||
value = [parseInt(value)];
|
||||
} else {
|
||||
value = [value];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
complex_filter.value[key] = value == 'true' ? true : value == 'false' ? false : value
|
||||
})
|
||||
}
|
||||
|
||||
const fixRoomVisible = (room) => {
|
||||
room.visible = true;
|
||||
|
||||
if (complex_filter.value.price_from !== '' && complex_filter.value.price_from > room.total_amount) {
|
||||
room.visible = false
|
||||
}
|
||||
|
||||
if (complex_filter.value.price_to !== '' && complex_filter.value.price_to < room.total_amount) {
|
||||
room.visible = false
|
||||
}
|
||||
|
||||
if (complex_filter.value.area_from !== '' && complex_filter.value.area_from > room.area) {
|
||||
room.visible = false
|
||||
}
|
||||
|
||||
if (parseInt(complex_filter.value.area_to, 10) && complex_filter.value.area_to < room.area) {
|
||||
room.visible = false
|
||||
}
|
||||
|
||||
if (Array.isArray(complex_filter.value.selectedDecorations) && room.decoration_id && !complex_filter.value.selectedDecorations.includes(room.decoration_id.toString())) {
|
||||
room.visible = false
|
||||
}
|
||||
}
|
||||
|
||||
function getHouses() {
|
||||
loadHome.value = true;
|
||||
return api.getComplexHouses(complex_id)
|
||||
.then((response) => {
|
||||
var res = response.data;
|
||||
houses.value = res;
|
||||
loadHome.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
const filteredHouses = computed(() => houses.value.filter((house) => {
|
||||
if (complex_filter.value.period && complex_filter.value.period.length) {
|
||||
if (complex_filter.value.period == 'completed') {
|
||||
const date = (new Date(Date.now()))
|
||||
const currentYear = date.getFullYear();
|
||||
const currentQuarter = Math.ceil(date.getMonth() / 3) - 1;
|
||||
if (house.deadline_quarter && house.deadline_year && house.deadline_quarter >= currentQuarter || house.deadline_year > currentYear) {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
const [year, quarter] = complex_filter.value.period.split('Q')
|
||||
if (house.deadline_quarter != quarter || house.deadline_year != year) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
house.rooms.forEach(fixRoomVisible)
|
||||
house.rooms = house.rooms.filter(room => room.visible);
|
||||
|
||||
return house.rooms.length
|
||||
}))
|
||||
|
||||
function get_complex_one() {
|
||||
return api.getComplexOne(complex_id)
|
||||
.then((response) => {
|
||||
complex.value = response.data;
|
||||
complexStore.setActiveComplex(complex.value)
|
||||
if (complex.value.images && complex.value.images.length) {
|
||||
complex.value.images.forEach(({ image, id }) => {
|
||||
fetch(image)
|
||||
.then((response) => response.blob())
|
||||
.then((blob) => {
|
||||
const file = new File(
|
||||
[blob],
|
||||
image.substring(image.lastIndexOf("/") + 1, image.length),
|
||||
{ type: blob.type }
|
||||
);
|
||||
file.objectURL = image;
|
||||
file.id = id;
|
||||
files.value.push(file);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const openAddHome = () => {
|
||||
isOpenForm.value = !isOpenForm.value;
|
||||
formAddHome.value.open();
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
parseQueryFilters()
|
||||
await get_complex_one();
|
||||
await getHouses();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/app/styles/function.scss";
|
||||
|
||||
.complexPage {
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
@apply bg-white;
|
||||
border-radius: 4px;
|
||||
padding: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
300
src/pages/ComplexViewMode/widgets/HomeItem.vue
Normal file
300
src/pages/ComplexViewMode/widgets/HomeItem.vue
Normal file
@ -0,0 +1,300 @@
|
||||
<template>
|
||||
<div class="homeItem">
|
||||
<div class="homeItem__image">
|
||||
<img v-if="houseImage" :src="houseImage" alt="" />
|
||||
<svg
|
||||
v-else
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
version="1.1"
|
||||
id="Capa_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="130px"
|
||||
height="130px"
|
||||
viewBox="0 0 217.267 217.267"
|
||||
style="enable-background: new 0 0 217.267 217.267"
|
||||
xml:space="preserve"
|
||||
>
|
||||
<g>
|
||||
<path
|
||||
d="M163.627,138.057c1.94,0,3.505-1.564,3.505-3.505V83.749c0-1.937-1.564-3.504-3.505-3.504 c-1.937,0-3.504,1.567-3.504,3.504v50.803C160.123,136.493,161.69,138.057,163.627,138.057z"
|
||||
fill="#506979"
|
||||
></path>
|
||||
<path
|
||||
d="M108.633,0C48.73,0,0,48.732,0,108.633c0,29.931,12.167,57.071,31.816,76.738c0.014,0.014,0.017,0.034,0.031,0.048 s0.032,0.018,0.046,0.031c19.666,19.646,46.807,31.815,76.74,31.815c59.901,0,108.634-48.731,108.634-108.633S168.531,0,108.633,0 z M108.633,7.009c26.758,0,51.12,10.416,69.285,27.379l-26.936,26.938H52.564c-9.661,0-17.521,7.861-17.521,17.521v59.573 c0,9.661,7.861,17.521,17.521,17.521h3.805l-21.979,21.978C17.42,159.753,7.009,135.394,7.009,108.633 C7.009,52.599,52.595,7.009,108.633,7.009z M86.196,108.63c0-12.363,10.056-22.42,22.417-22.42c4.921,0,9.466,1.615,13.172,4.313 l-1.875,1.875c-3.207-2.24-7.094-3.564-11.297-3.564c-10.913,0-19.789,8.879-19.789,19.792c0,4.195,1.321,8.09,3.559,11.3 l-1.874,1.868C87.811,118.095,86.196,113.554,86.196,108.63z M131.034,108.63c0,12.364-10.054,22.418-22.418,22.418 c-4.915,0-9.449-1.608-13.145-4.298l1.874-1.876c3.203,2.231,7.082,3.546,11.274,3.546c10.91,0,19.794-8.877,19.794-19.791 c0-4.187-1.321-8.071-3.549-11.272l1.875-1.875C129.433,99.181,131.034,103.717,131.034,108.63z M95.833,108.63 c0-7.051,5.735-12.784,12.78-12.784c2.257,0,4.347,0.639,6.184,1.668l-17.304,17.306C96.469,112.972,95.833,110.885,95.833,108.63 z M121.397,108.63c0,7.049-5.735,12.782-12.781,12.782c-2.244,0-4.322-0.633-6.158-1.652l17.29-17.289 C120.769,104.31,121.397,106.383,121.397,108.63z M90.467,131.753c5.005,3.936,11.298,6.311,18.146,6.311 c16.229,0,29.431-13.206,29.431-29.431c0-6.848-2.375-13.141-6.311-18.149l22.148-22.146h10.814 c5.797,0,10.513,4.717,10.513,10.513v59.573c0,5.797-4.716,10.513-10.513,10.513H73.289L90.467,131.753z M126.784,85.523 c-5.007-3.948-11.311-6.326-18.168-6.326c-16.23,0-29.426,13.204-29.426,29.429c0,6.858,2.375,13.158,6.321,18.172l-22.136,22.135 H52.561c-5.797,0-10.513-4.716-10.513-10.514V78.847c0-5.795,4.716-10.513,10.513-10.513h91.413L126.784,85.523z M108.633,210.257 c-26.759,0-51.12-10.417-69.287-27.377l26.934-26.939h98.422c9.66,0,17.521-7.86,17.521-17.521V78.847 c0-9.661-7.861-17.521-17.521-17.521h-3.806l21.977-21.979c16.968,18.17,27.381,42.529,27.381,69.287 C210.258,164.667,164.667,210.257,108.633,210.257z"
|
||||
fill="#506979"
|
||||
></path>
|
||||
<path
|
||||
d="M53.002,54.317h23.654c1.937,0,3.504-1.567,3.504-3.504s-1.567-3.504-3.504-3.504H53.002 c-1.938,0-3.504,1.567-3.504,3.504S51.064,54.317,53.002,54.317z"
|
||||
fill="#506979"
|
||||
></path>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="homeItem__content">
|
||||
<div class="homeItem__desc">
|
||||
<div class="homeItem__descComplex">{{ house.name_complex }}</div>
|
||||
<h2 class="homeItem__houseName">
|
||||
<RouterLink :to="{path: homeViewRoute, query: $route.query}">
|
||||
{{ house.name }}
|
||||
</RouterLink>
|
||||
</h2>
|
||||
<p class="homeItem__houseAddress">{{ house.address }}</p>
|
||||
</div>
|
||||
<div class="homeItem__rooms">
|
||||
<RouterLink
|
||||
v-for="(groupedRooms, groupType) of computedRooms"
|
||||
class="homeItem__roomItem"
|
||||
:id="groupType"
|
||||
:key="groupType"
|
||||
:to="{path: `/view/${props.house.complex_id}/home/${house.id}`, query: groupType == 'studio' ? { selectedTypes: ['apartment'], rooms: [groupType], viewMode: 'tile' } : containsNumber(groupType) ? { selectedTypes: ['apartment'], rooms: [groupType], viewMode: 'tile'} : { ...$route.query, selectedTypes: [groupType], viewMode: 'tile'} }"
|
||||
>
|
||||
<div>
|
||||
<div>{{ getRoomsCountGroupTypeString(groupType) }}{{ room_types[groupType] || layout_futures[groupType] }}</div>
|
||||
<div>{{ groupedRooms.length }} шт.</div>
|
||||
</div>
|
||||
<div>
|
||||
от {{ formatNumber(Math.min(...groupedRooms.map((room) => room.total_amount || 0))) }}
|
||||
</div>
|
||||
</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { room_types, layout_futures, containsNumber, formatNumber } from "@/shared/utils/util";
|
||||
import { computed, onMounted } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
const props = defineProps({
|
||||
house: Object,
|
||||
});
|
||||
const route = useRoute();
|
||||
const emits = defineEmits(["deleteItem"]);
|
||||
const houseImage = computed(() => {
|
||||
return props.house?.images.length ? props.house?.images[0].url : null;
|
||||
});
|
||||
|
||||
const homeViewRoute = computed(() => {
|
||||
return `/view/${route.params.id}/home/${props.house.id}`
|
||||
})
|
||||
|
||||
const sortObjectByStudioFirst = (obj) => {
|
||||
const keys = Object.keys(obj).sort((a, b) => {
|
||||
if (a === "studio" || b === "studio") return -1;
|
||||
|
||||
if (isNaN(+a) || isNaN(+b)) return 1
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
const sortedObj = {};
|
||||
keys.forEach(key => {
|
||||
sortedObj[key] = obj[key];
|
||||
});
|
||||
|
||||
return sortedObj;
|
||||
}
|
||||
const computedRooms = computed(() => {
|
||||
if (!props.house.rooms.length) return [];
|
||||
const rooms = props.house.rooms.filter((room) => (room.status == 'available' || room.status == 'reservation')).reduce((acc, room) => {
|
||||
const { type, layout_feature, rooms_count } = room;
|
||||
|
||||
const is_studio = layout_feature && layout_feature == "studio";
|
||||
if (is_studio) {
|
||||
if (!acc["studio"]) {
|
||||
acc["studio"] = [];
|
||||
}
|
||||
|
||||
acc["studio"].push(room);
|
||||
return acc;
|
||||
}
|
||||
|
||||
if (rooms_count && type === 'apartment' && !is_studio) {
|
||||
if (rooms_count < 5) {
|
||||
if (!acc[rooms_count]) {
|
||||
acc[rooms_count] = []
|
||||
}
|
||||
|
||||
acc[rooms_count].push(room)
|
||||
return acc
|
||||
} else {
|
||||
if (!acc['5-more']) {
|
||||
acc['5-more'] = []
|
||||
}
|
||||
|
||||
acc['5-more'].push(room)
|
||||
return acc
|
||||
}
|
||||
}
|
||||
|
||||
if (!acc[type]) {
|
||||
acc[type] = [];
|
||||
}
|
||||
acc[type].push(room);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
|
||||
return sortObjectByStudioFirst(rooms)
|
||||
});
|
||||
|
||||
const getRoomsCountGroupTypeString = (groupType) => {
|
||||
if (containsNumber(groupType)) {
|
||||
if (groupType !== '5-more') {
|
||||
return groupType + ' К.кв'
|
||||
}
|
||||
|
||||
return '5+ К.кв'
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.homeItem {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-height: 521px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
box-shadow: 0px 4px 8px 0px rgba(0, 0, 0, 0.05);
|
||||
@apply bg-white;
|
||||
border-radius: 4px;
|
||||
&__image {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 250px;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.no-image {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
&__floorInfo {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
gap: 4px;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
|
||||
div {
|
||||
background-color: #fff;
|
||||
border-radius: 2px;
|
||||
border: 1px solid #a8acb43b;
|
||||
font-size: 12px;
|
||||
line-height: 24px;
|
||||
white-space: nowrap;
|
||||
padding: 0 4px;
|
||||
}
|
||||
}
|
||||
&__content {
|
||||
width: 100%;
|
||||
background-color: rgba($color: #ffffff, $alpha: 0.85);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
row-gap: 10px;
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
&__rooms {
|
||||
font-size: 16px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
border-top: 1px solid #cfd8e1;
|
||||
padding: 16px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
&__roomItem {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
|
||||
& > div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
|
||||
& > div:nth-child(1) {
|
||||
font-weight: bold;
|
||||
letter-spacing: 0.25px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #43a047;
|
||||
}
|
||||
}
|
||||
|
||||
&__desc {
|
||||
width: 100%;
|
||||
padding: 16px 16px 0 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
&__descComplex,
|
||||
&__houseAddress {
|
||||
font-size: 14px;
|
||||
max-width: 250px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
line-height: 20px;
|
||||
letter-spacing: 0.28px;
|
||||
}
|
||||
|
||||
&__houseName {
|
||||
font-size: 24px;
|
||||
line-height: 24px;
|
||||
border-bottom: 1px solid;
|
||||
width: max-content;
|
||||
letter-spacing: 0.48px;
|
||||
padding-bottom: 3px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
&__nav {
|
||||
position: relative;
|
||||
@apply bg-white;
|
||||
width: 100%;
|
||||
height: 64px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
column-gap: 8px;
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: 1px solid #dedede;
|
||||
padding: 8px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
499
src/pages/ComplexViewMode/widgets/ListHome.vue
Normal file
499
src/pages/ComplexViewMode/widgets/ListHome.vue
Normal file
@ -0,0 +1,499 @@
|
||||
<template>
|
||||
|
||||
<div v-show="loadHome" class="loader-div ListHomeLoader">
|
||||
<RocketSpinner/>
|
||||
</div>
|
||||
<div v-if="!loadHome" class="listHome">
|
||||
<!-- <div class="listHome__title">Список домов</div> -->
|
||||
<div class="list-wrapper">
|
||||
<div class="listHome__list">
|
||||
<div class="company-logo">
|
||||
<!-- remove dev for prod or local -->
|
||||
<img :src="'https://dev.joywork.ru/photos/agency/' + data.agencyLogo" alt="" class="company-logo-img w-[40px] h-[40px] object-contain md:w-[100px] md:h-[100px]">
|
||||
<span class="company-name">{{ data.agencyName }}</span>
|
||||
|
||||
</div>
|
||||
<Navigation />
|
||||
<div class="show-map-btn" @click="toggleMap">
|
||||
<MapIcon :mainColor="mainColor" :height="'24px'" :width="'24px'" />
|
||||
<span v-if="!mapIsOpen">Посмотреть объекты на карте</span>
|
||||
<span v-else>Скрыть карту</span>
|
||||
</div>
|
||||
<template v-if="activeListItem === 'activeHome' && !mapIsOpen">
|
||||
<homeItemIframe v-for="house in houses" :metros="metros" :key="house?.id" :home="house" :minPrice="minPrice" :complexId="complexId"/>
|
||||
</template>
|
||||
<template v-else></template>
|
||||
</div>
|
||||
<div class="yandex-map-wrapper" v-if="isLargeScreen && !mapIsOpen || mapIsOpen" >
|
||||
<YandexMap
|
||||
v-show="!loadHome"
|
||||
ref="map"
|
||||
v-model="map"
|
||||
:settings="mapSettings"
|
||||
:mapsRenderWaitDuration="1000"
|
||||
|
||||
class="grayscale-map"
|
||||
>
|
||||
<YandexMapDefaultSchemeLayer :customization = "mapCustomization"/>
|
||||
<YandexMapDefaultFeaturesLayer />
|
||||
<YandexMapControls :settings="{ position: 'left' }">
|
||||
<YandexMapZoomControl />
|
||||
</YandexMapControls>
|
||||
<YandexMapMarker
|
||||
v-for="marker in formattedCoords.filter((el)=>el!==null)"
|
||||
:key="marker.id"
|
||||
:settings="{coordinates: marker}"
|
||||
:icon="require('@/shared/assets/images/ymarker.svg')"
|
||||
>
|
||||
<div class="marker" :complex-id="complex.id" @click="selectComplex(complex.id)">
|
||||
<MapIcon :mainColor="mainColor" />
|
||||
</div>
|
||||
</YandexMapMarker>
|
||||
</YandexMap>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref, watch, computed, nextTick } from "vue";
|
||||
// import homeItem from "./HomeItem.vue";
|
||||
import homeItemIframe from "@/entities/homeItemIframe.vue";
|
||||
import MapIcon from "@/components/MapIcon.vue";
|
||||
// import { YMap } from '@yandex/ymaps3-types';
|
||||
import yMarker from '@/shared/assets/images/ymarker.svg';
|
||||
import RocketSpinner from "@/components/RocketSpinner.vue";
|
||||
|
||||
import {
|
||||
YandexMap,
|
||||
YandexMapDefaultSchemeLayer,
|
||||
YandexMapDefaultFeaturesLayer,
|
||||
YandexMapDefaultMarker,
|
||||
YandexMapControls,
|
||||
// YandexMapPlacemark,
|
||||
YandexMapMarker,
|
||||
YandexMapZoomControl,
|
||||
getBoundsFromCoords,
|
||||
getCenterFromCoords,
|
||||
ymaps
|
||||
} from 'vue-yandex-maps';
|
||||
import Navigation from "@/components/Navigation.vue";
|
||||
import { useComplexStore } from "@/app/store/complex";
|
||||
import { useRoute } from "vue-router";
|
||||
import { onUnmounted } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
complex: null,
|
||||
houses: [],
|
||||
loadHome: true
|
||||
});
|
||||
|
||||
|
||||
const data = useComplexStore();
|
||||
const complexId = useRoute().params.id;
|
||||
const minPrice = ref(null)
|
||||
const markerCoords = ref([]);
|
||||
const formattedCoords = ref([]);
|
||||
const metros = ref([]);
|
||||
const center = ref([25.7558, 12.6173]);
|
||||
const mapCenter = [];
|
||||
const mainColor = ref(null)
|
||||
const mapIsOpen = ref(false);
|
||||
const isLargeScreen = computed(() => windowWidth.value >= 1200);
|
||||
const windowWidth = ref(window.innerWidth);
|
||||
const map = ref(null)
|
||||
|
||||
const complexesArray = ref([]);
|
||||
const complex = ref(null);
|
||||
|
||||
const selectedComplexId = ref(null);
|
||||
|
||||
|
||||
const mapSettings = computed(() => ({
|
||||
location: {
|
||||
center: center.value,
|
||||
zoom: 14,
|
||||
},
|
||||
}));
|
||||
// console.log(mapSettings, 'settings');
|
||||
const colors = localStorage.getItem("colors")
|
||||
mainColor.value = JSON.parse(colors).frame_color
|
||||
|
||||
// console.log(mainColor.value, 'testlog');
|
||||
|
||||
|
||||
watch(
|
||||
() => data.complexes, // Следим за data.complexes
|
||||
(newComplexes) => {
|
||||
if (!newComplexes) return; // Если данных нет, выходим
|
||||
|
||||
|
||||
complexesArray.value = Object.values(newComplexes);
|
||||
if (complexId) {
|
||||
complex.value = complexesArray.value.find((c) => c.id == complexId);
|
||||
metros.value = complex.value?.metros
|
||||
minPrice.value = complex.value?.min_total_amount
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const selectComplex = (id) => {
|
||||
selectedComplexId.value = id;
|
||||
};
|
||||
|
||||
const updateWindowWidth = () => {
|
||||
windowWidth.value = window.innerWidth;
|
||||
};
|
||||
|
||||
watch(windowWidth, (newVal) => {
|
||||
if (newVal < 1200) {
|
||||
isLargeScreen.value = false;
|
||||
} else {
|
||||
isLargeScreen.value = true;
|
||||
}
|
||||
});
|
||||
const toggleMap = () => {
|
||||
mapIsOpen.value = !mapIsOpen.value
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
mapIsOpen.value = false
|
||||
window.addEventListener('resize', updateWindowWidth);
|
||||
if (typeof window.ymaps === 'undefined') {
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://api-maps.yandex.ru/2.1/?apikey=692f0a05-cb88-4158-88b9-06bc0dc93004&lang=ru_RU';
|
||||
document.head.appendChild(script);
|
||||
|
||||
script.onload = () => {
|
||||
initGeocode();
|
||||
};
|
||||
} else {
|
||||
initGeocode();
|
||||
}
|
||||
});
|
||||
function initGeocode() {
|
||||
console.log('func called');
|
||||
|
||||
window.ymaps.ready().then(() => {
|
||||
const geocodePromises = props.houses.map(complex => {
|
||||
return window.ymaps.geocode(complex.address)
|
||||
.then(res => {
|
||||
const firstGeoObject = res.geoObjects.get(0);
|
||||
if (firstGeoObject) {
|
||||
const coords = firstGeoObject.geometry.getCoordinates();
|
||||
return { id: complex.id, address: complex.address, coords };
|
||||
} else {
|
||||
return { id: complex.id, address: complex.address, coords: null };
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(`Ошибка при геокодировании ${complex.address}:`, error);
|
||||
return { id: complex.id, address: complex.address, coords: null };
|
||||
});
|
||||
});
|
||||
|
||||
Promise.all(geocodePromises).then(results => {
|
||||
console.log(geocodePromises, 'geocodePromises');
|
||||
markerCoords.value = results
|
||||
.filter(item => item.coords !== null)
|
||||
.map(item => item.coords);
|
||||
formattedCoords.value = markerCoords.value.map(coords => [coords[1], coords[0]]);
|
||||
|
||||
if (formattedCoords?.value.length > 1) {
|
||||
const bounds = ref(getBoundsFromCoords(formattedCoords.value));
|
||||
const rawCenter = getCenterFromCoords(bounds.value);
|
||||
center.value = [rawCenter[0], rawCenter[1]];
|
||||
console.log(center.value, 'center.value');
|
||||
} else if(formattedCoords?.value.length === 1){
|
||||
center.value = formattedCoords.value[0];
|
||||
}else{
|
||||
console.log(" ");
|
||||
}
|
||||
|
||||
});
|
||||
}).catch(error => {
|
||||
console.error('Ошибка загрузки Yandex Maps:', error);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.houses,
|
||||
(newHouses) => {
|
||||
if (newHouses?.length > 0) {
|
||||
initGeocode();
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
|
||||
watch(center, (newCenter) => {
|
||||
console.log('Обновился центр карты:', newCenter);
|
||||
});
|
||||
onMounted(async () => {
|
||||
// await initGeocode();
|
||||
|
||||
await nextTick();
|
||||
|
||||
setTimeout(() => {
|
||||
if (map.value) {
|
||||
const ymaps = map.value.ymaps;
|
||||
const ymapInstance = map.value.map;
|
||||
|
||||
if (ymaps && ymapInstance) {
|
||||
const bounds = ymaps.geoQuery(markers.value).getBounds();
|
||||
if (bounds) {
|
||||
ymapInstance.setBounds(bounds, { checkZoomRange: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 500);
|
||||
});
|
||||
|
||||
|
||||
const emits = defineEmits(
|
||||
["openAddHome"]
|
||||
);
|
||||
|
||||
const activeListItem = ref("activeHome");
|
||||
|
||||
|
||||
const toggleHome = (tab) => {
|
||||
activeListItem.value = tab;
|
||||
}
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', updateWindowWidth);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.listHome {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.show-map-btn {
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
width: 100%;
|
||||
border: 1px solid var(--main-color);
|
||||
border-radius: 6px;
|
||||
padding: 8px 0;
|
||||
margin-top: 28px;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
color: var(--main-color);
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.listHome .yandex-map-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 50%;
|
||||
height: 100vh;
|
||||
max-height: 100vh;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.loader {
|
||||
/* position: absolute; */
|
||||
/* top: 500px; */
|
||||
/* left: auto; */
|
||||
width: 110px;
|
||||
max-width: none;
|
||||
display: flex;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
height: 1000px;
|
||||
img {
|
||||
/* width: 110px;
|
||||
max-width: none; */
|
||||
}
|
||||
}
|
||||
|
||||
.list-wrapper {
|
||||
display: flex;
|
||||
width: 100vw;
|
||||
& .listHome__list {
|
||||
width: 50%;
|
||||
padding:50px;
|
||||
height:auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: start;
|
||||
position: relative;
|
||||
background-color: #fff;
|
||||
margin-bottom: 10px;
|
||||
& .company-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 40px;
|
||||
font-weight: bold;
|
||||
gap: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.listHome {
|
||||
width: 100%;
|
||||
/* height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start; */
|
||||
|
||||
&__title {
|
||||
width: 100%;
|
||||
font-size: 32px;
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid;
|
||||
@apply border-grey-400;
|
||||
}
|
||||
&__list {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
// left: -30px;
|
||||
// width: calc(100% + 60px);
|
||||
height: auto;
|
||||
display: grid;
|
||||
/* grid-template-columns: repeat(4, 1fr);
|
||||
grid-gap: 30px; */
|
||||
gap: 40px;
|
||||
@apply bg-grey-200;
|
||||
padding: 30px 0;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1440px) {
|
||||
&__list {
|
||||
// grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1200px) {
|
||||
.show-map-btn {
|
||||
display: flex;
|
||||
}
|
||||
.listHome__list{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.listHome .yandex-map-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 50%;
|
||||
height: 100vh;
|
||||
max-height: 100vh;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
&__list {
|
||||
// grid-template-columns: repeat(1, 1fr);
|
||||
}
|
||||
}
|
||||
}
|
||||
.listHome .yandex-map-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 50%;
|
||||
height: 100vh;
|
||||
max-height: 100vh;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.marker {
|
||||
width: 30px;
|
||||
height: 70px;
|
||||
cursor: pointer;
|
||||
left: -24px;
|
||||
top: -51px;
|
||||
max-width: unset;
|
||||
position: absolute;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.marker:hover {
|
||||
transform: scale(1.05);
|
||||
filter: drop-shadow(0 4px 10px color-mix(in srgb, var(--main-color) 50%, white 50%));
|
||||
|
||||
}
|
||||
|
||||
/* Стили для кнопок зума */
|
||||
.ymaps3x0--zoom-control_vertical {
|
||||
display: flex!important;
|
||||
gap: 10px!important;
|
||||
}
|
||||
.ymaps3x0--control__background {
|
||||
background: unset!important;
|
||||
box-shadow: unset!important;
|
||||
}
|
||||
|
||||
.ymaps3x0--control-button {
|
||||
border-radius: 50%!important;
|
||||
|
||||
padding: 5px!important;
|
||||
box-shadow: 0 2px 6px 0 #0003;
|
||||
|
||||
}
|
||||
.ymaps3x0--main-engine-container {
|
||||
filter: grayscale(100%) brightness(90%)!important;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1200px) {
|
||||
.listHome .yandex-map-wrapper {
|
||||
position: absolute;
|
||||
top: 430px;
|
||||
right: 0;
|
||||
left: 0;
|
||||
width: 95vw;
|
||||
height: 57vh;
|
||||
max-height: 100vh;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
overflow: hidden;
|
||||
margin: auto auto 100px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.listHome {
|
||||
.listHome__list {
|
||||
padding: 20px !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
.company-name {
|
||||
font-family: 'Lato', sans-serif;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 19px;
|
||||
}
|
||||
.navigation-container {
|
||||
padding: 0 !important;
|
||||
}
|
||||
.breadcrumbs {
|
||||
margin: 0 !important;
|
||||
}
|
||||
.show-map-btn {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
29
src/pages/ComplexesView.vue
Normal file
29
src/pages/ComplexesView.vue
Normal file
@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<div v-if="loaders.list" class="loader-div Loader_list">
|
||||
<RocketSpinner/>
|
||||
</div>
|
||||
<ListItems v-else :isViewPage="true" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useComplexStore } from "@/app/store/complex";
|
||||
import ListItems from "@/widgets/ListItems.vue";
|
||||
import RocketSpinner from "@/components/RocketSpinner.vue";
|
||||
const { loaders } = useComplexStore()
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.loader{
|
||||
width:110px;
|
||||
max-width: none;
|
||||
display: flex;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
height: 1000px;
|
||||
|
||||
img {
|
||||
/* width:110px;
|
||||
max-width: none; */
|
||||
}
|
||||
}
|
||||
</style>
|
||||
13
src/pages/Edit/index.vue
Normal file
13
src/pages/Edit/index.vue
Normal file
@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<DefaultLayout>
|
||||
<RouterView></RouterView>
|
||||
</DefaultLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import DefaultLayout from "@/shared/layouts/defaultLayout.vue";
|
||||
import { onMounted } from "vue";
|
||||
onMounted(() => {
|
||||
document.getElementById('vue-page')?.classList.add('active')
|
||||
})
|
||||
</script>
|
||||
508
src/pages/EditFacades.vue
Normal file
508
src/pages/EditFacades.vue
Normal file
@ -0,0 +1,508 @@
|
||||
<template>
|
||||
|
||||
<WindowEditFacades
|
||||
v-if="activeFacade"
|
||||
:entranceFloorsOptions="entrancesOptions"
|
||||
:active-facade="activeFacade"
|
||||
:visible="visibleEditWindow"
|
||||
@update:visible="(visibleEditWindow = false), (activeFacade = null)"
|
||||
@save-facade="updateFacade"
|
||||
/>
|
||||
<div v-if="!showLoader" class="loader-div">
|
||||
<RocketSpinner/>
|
||||
</div>
|
||||
|
||||
<div class="editFacades bg-white p-7.5 h-full">
|
||||
<div class="name text-[32px] mb-7.5">
|
||||
<span class="text-grey-900">Фасады:</span> {{complex.name}} —
|
||||
{{house.name}}
|
||||
</div>
|
||||
<Message :closable="false">
|
||||
<template #messageicon>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-[18px] h-auto mr-2.5"
|
||||
>
|
||||
<path
|
||||
d="M9.00161 8.25C8.8027 8.25 8.61193 8.32902 8.47128 8.46967C8.33063 8.61032 8.25161 8.80109 8.25161 9V11.25C8.25161 11.4489 8.33063 11.6397 8.47128 11.7803C8.61193 11.921 8.8027 12 9.00161 12C9.20052 12 9.39129 11.921 9.53194 11.7803C9.67259 11.6397 9.75161 11.4489 9.75161 11.25V9C9.75161 8.80109 9.67259 8.61032 9.53194 8.46967C9.39129 8.32902 9.20052 8.25 9.00161 8.25ZM9.00161 6C8.85327 6 8.70827 6.04399 8.58493 6.1264C8.46159 6.20881 8.36546 6.32594 8.3087 6.46299C8.25193 6.60003 8.23708 6.75083 8.26602 6.89632C8.29496 7.0418 8.36639 7.17544 8.47128 7.28033C8.57617 7.38522 8.70981 7.45665 8.85529 7.48559C9.00078 7.51453 9.15158 7.49968 9.28862 7.44291C9.42567 7.38614 9.5428 7.29001 9.62521 7.16668C9.70762 7.04334 9.75161 6.89834 9.75161 6.75C9.75161 6.55109 9.67259 6.36032 9.53194 6.21967C9.39129 6.07902 9.20052 6 9.00161 6ZM9.00161 1.5C8.01669 1.5 7.04142 1.69399 6.13148 2.0709C5.22154 2.44781 4.39475 3.00026 3.69831 3.6967C2.29178 5.10322 1.50161 7.01088 1.50161 9C1.49505 10.7319 2.0947 12.4114 3.19661 13.7475L1.69661 15.2475C1.59254 15.353 1.52204 15.4869 1.49401 15.6324C1.46598 15.7779 1.48167 15.9284 1.53911 16.065C1.6014 16.1999 1.70239 16.3133 1.82924 16.3908C1.9561 16.4682 2.10311 16.5063 2.25161 16.5H9.00161C10.9907 16.5 12.8984 15.7098 14.3049 14.3033C15.7114 12.8968 16.5016 10.9891 16.5016 9C16.5016 7.01088 15.7114 5.10322 14.3049 3.6967C12.8984 2.29018 10.9907 1.5 9.00161 1.5ZM9.00161 15H4.05911L4.75661 14.3025C4.82747 14.233 4.88385 14.1502 4.92247 14.0588C4.9611 13.9674 4.9812 13.8692 4.98161 13.77C4.97879 13.5722 4.89792 13.3835 4.75661 13.245C3.77455 12.264 3.16299 10.9729 3.02613 9.59159C2.88926 8.21029 3.23557 6.82425 4.00603 5.66964C4.77649 4.51503 5.92345 3.66327 7.25149 3.25948C8.57954 2.85569 10.0065 2.92485 11.2893 3.45519C12.572 3.98552 13.6312 4.94421 14.2864 6.16792C14.9416 7.39163 15.1523 8.80466 14.8825 10.1663C14.6126 11.5279 13.8791 12.7538 12.8068 13.6352C11.7344 14.5166 10.3897 14.9989 9.00161 15Z"
|
||||
fill="#3A8EFA"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
<template #default>
|
||||
<p class="2xl:text-base 2xl:!leading-8 xl:text-sm xl:!leading-7">
|
||||
Оптимальный размер изображения 2200х1000 пикселей, минимальный —
|
||||
1100х900 пикселей. <br />
|
||||
Для отображения изображения без замыливания по краям рекомендуемый
|
||||
размер — 2600х1000 пикселей. <br />
|
||||
Максимальный размер файла 5 МБ. <br />Допустимые форматы - png, jpeg
|
||||
</p>
|
||||
</template>
|
||||
</Message>
|
||||
<div class="uploadFacade">
|
||||
<label
|
||||
for="uploadFacade"
|
||||
class="w-max flex justify-start items-center bg-green px-3 py-2 rounded text-white mt-5 mb-[50px] cursor-pointer"
|
||||
>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="mr-3"
|
||||
>
|
||||
<path
|
||||
d="M16.791 11.517C16.7406 11.4354 16.6702 11.368 16.5864 11.3213C16.5027 11.2746 16.4084 11.25 16.3125 11.25H15.1875V5.0625C15.1871 4.76425 15.0684 4.47834 14.8576 4.26745C14.6467 4.05656 14.3607 3.9379 14.0625 3.9375H8.67043L7.14769 2.41481C7.09548 2.36256 7.03348 2.32111 6.96525 2.29283C6.89701 2.26455 6.82387 2.24999 6.75 2.25H2.25C1.95175 2.2504 1.66584 2.36906 1.45495 2.57995C1.24406 2.79084 1.1254 3.07675 1.125 3.375V15.1875H1.12748C1.12678 15.2614 1.14077 15.3346 1.16864 15.403C1.19651 15.4714 1.23769 15.5336 1.2898 15.5859C1.34191 15.6383 1.40391 15.6797 1.47218 15.7079C1.54045 15.7361 1.61364 15.7504 1.6875 15.75H14.625C14.7295 15.75 14.8319 15.7209 14.9207 15.666C15.0096 15.6111 15.0814 15.5325 15.1282 15.4391L16.8157 12.0641C16.8586 11.9784 16.8789 11.8831 16.8746 11.7873C16.8703 11.6915 16.8415 11.5985 16.791 11.517ZM14.2773 14.625H2.59774L3.72274 12.375H15.4023L14.2773 14.625Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
Загрузить фасад
|
||||
</label>
|
||||
<input
|
||||
type="file"
|
||||
name=""
|
||||
id="uploadFacade"
|
||||
class="hidden"
|
||||
@change="uploadFacade($event)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="facadesList">
|
||||
<div
|
||||
v-for="facade in sortedFacades"
|
||||
:key="facade.id"
|
||||
class="facadeItem"
|
||||
:draggable="true"
|
||||
@dragstart="(e) => dragStartHandler(e, facade)"
|
||||
@dragover.prevent="(e) => dragOverHandler(e)"
|
||||
@dragend="(e) => dragEndHandler(e)"
|
||||
@drop.prevent="(e) => dropHandler(e, facade)"
|
||||
>
|
||||
<div class="iconDrop">
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clip-path="url(#clip0_931_1222)">
|
||||
<path
|
||||
d="M23.7454 11.3976L20.3168 7.96903C20.1473 7.79936 19.9463 7.71459 19.7141 7.71459C19.482 7.71459 19.2812 7.79936 19.1115 7.96903C18.942 8.13879 18.8571 8.33971 18.8571 8.57181V10.2862H13.7142V5.14331H15.4286C15.6608 5.14331 15.8617 5.05845 16.0313 4.88887C16.2008 4.7192 16.2856 4.51848 16.2856 4.28623C16.2856 4.05394 16.2008 3.85302 16.0313 3.6834L12.6026 0.254802C12.4332 0.0851782 12.2323 0.000366211 12 0.000366211C11.7677 0.000366211 11.5669 0.0852251 11.3972 0.254802L7.96871 3.6834C7.79899 3.85316 7.71428 4.05408 7.71428 4.28623C7.71428 4.51824 7.79904 4.71916 7.96871 4.88887C8.13848 5.05845 8.3392 5.14331 8.5715 5.14331H10.2858V10.2863H5.14294V8.57186C5.14294 8.33957 5.05809 8.1387 4.88841 7.96908C4.71865 7.79941 4.51774 7.71464 4.28563 7.71464C4.05357 7.71464 3.85266 7.79941 3.68299 7.96908L0.254436 11.3976C0.0848588 11.5673 0 11.7681 0 12.0002C0 12.2324 0.0848588 12.4333 0.254436 12.6029L3.68299 16.0313C3.85266 16.2008 4.05343 16.2861 4.28563 16.2861C4.51793 16.2861 4.71879 16.2009 4.88841 16.0313C5.05809 15.862 5.14294 15.6608 5.14294 15.4287V13.7142H10.2858V18.8571H8.57164C8.33934 18.8571 8.13843 18.9421 7.96881 19.1116C7.79913 19.2812 7.71437 19.4821 7.71437 19.7142C7.71437 19.9464 7.79913 20.1473 7.96881 20.3172L11.3974 23.7455C11.5672 23.915 11.768 23.9999 12.0002 23.9999C12.2323 23.9999 12.4334 23.915 12.6028 23.7455L16.0313 20.3172C16.2011 20.1474 16.2859 19.9464 16.2859 19.7142C16.2859 19.4821 16.2011 19.2813 16.0313 19.1116C15.8618 18.9421 15.6608 18.8571 15.4286 18.8571H13.7145V13.7143H18.8573V15.429C18.8573 15.6611 18.9421 15.8621 19.1115 16.0317C19.2814 16.2011 19.4824 16.2863 19.7145 16.2863C19.9464 16.2863 20.1473 16.2012 20.3172 16.0317L23.7456 12.603C23.9153 12.4333 24 12.2324 24 12.0002C24 11.7681 23.9153 11.5673 23.7454 11.3976Z"
|
||||
fill="#3A8EFA"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_931_1222">
|
||||
<rect width="24" height="24" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="facadeItem__content">
|
||||
<div class="facadeItem__title">{{ facade.name }}</div>
|
||||
<div class="facadeItem__imageWrapper">
|
||||
<div class="imageHover">
|
||||
<Button
|
||||
severity="success"
|
||||
@click="
|
||||
visibleEditWindow = true;
|
||||
activeFacade = facade;
|
||||
"
|
||||
>Начать разметку</Button
|
||||
>
|
||||
</div>
|
||||
<img
|
||||
:src="facade.image"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<div class="facadeItem__footer">
|
||||
<div class="is_published">
|
||||
<InputSwitch
|
||||
@change="onPublishedChange(facade)"
|
||||
id="is_published"
|
||||
v-model="facade.is_published"
|
||||
/>
|
||||
<label for="is_published">Опубликован</label>
|
||||
</div>
|
||||
<Button
|
||||
severity="danger"
|
||||
class="isDelete"
|
||||
@click="showTemplate($event, facade.id)"
|
||||
>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M7.5 13.5C7.69891 13.5 7.88968 13.421 8.03033 13.2803C8.17098 13.1397 8.25 12.9489 8.25 12.75V8.25C8.25 8.05109 8.17098 7.86032 8.03033 7.71967C7.88968 7.57902 7.69891 7.5 7.5 7.5C7.30109 7.5 7.11032 7.57902 6.96967 7.71967C6.82902 7.86032 6.75 8.05109 6.75 8.25V12.75C6.75 12.9489 6.82902 13.1397 6.96967 13.2803C7.11032 13.421 7.30109 13.5 7.5 13.5ZM15 4.5H12V3.75C12 3.15326 11.7629 2.58097 11.341 2.15901C10.919 1.73705 10.3467 1.5 9.75 1.5H8.25C7.65326 1.5 7.08097 1.73705 6.65901 2.15901C6.23705 2.58097 6 3.15326 6 3.75V4.5H3C2.80109 4.5 2.61032 4.57902 2.46967 4.71967C2.32902 4.86032 2.25 5.05109 2.25 5.25C2.25 5.44891 2.32902 5.63968 2.46967 5.78033C2.61032 5.92098 2.80109 6 3 6H3.75V14.25C3.75 14.8467 3.98705 15.419 4.40901 15.841C4.83097 16.2629 5.40326 16.5 6 16.5H12C12.5967 16.5 13.169 16.2629 13.591 15.841C14.0129 15.419 14.25 14.8467 14.25 14.25V6H15C15.1989 6 15.3897 5.92098 15.5303 5.78033C15.671 5.63968 15.75 5.44891 15.75 5.25C15.75 5.05109 15.671 4.86032 15.5303 4.71967C15.3897 4.57902 15.1989 4.5 15 4.5ZM7.5 3.75C7.5 3.55109 7.57902 3.36032 7.71967 3.21967C7.86032 3.07902 8.05109 3 8.25 3H9.75C9.94891 3 10.1397 3.07902 10.2803 3.21967C10.421 3.36032 10.5 3.55109 10.5 3.75V4.5H7.5V3.75ZM12.75 14.25C12.75 14.4489 12.671 14.6397 12.5303 14.7803C12.3897 14.921 12.1989 15 12 15H6C5.80109 15 5.61032 14.921 5.46967 14.7803C5.32902 14.6397 5.25 14.4489 5.25 14.25V6H12.75V14.25ZM10.5 13.5C10.6989 13.5 10.8897 13.421 11.0303 13.2803C11.171 13.1397 11.25 12.9489 11.25 12.75V8.25C11.25 8.05109 11.171 7.86032 11.0303 7.71967C10.8897 7.57902 10.6989 7.5 10.5 7.5C10.3011 7.5 10.1103 7.57902 9.96967 7.71967C9.82902 7.86032 9.75 8.05109 9.75 8.25V12.75C9.75 12.9489 9.82902 13.1397 9.96967 13.2803C10.1103 13.421 10.3011 13.5 10.5 13.5Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
</Button>
|
||||
<ConfirmPopup
|
||||
group="deletePlan"
|
||||
:pt="confirmpopupStyle"
|
||||
></ConfirmPopup>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import ConfirmPopup from "primevue/confirmpopup";
|
||||
import { useConfirm } from "primevue/useconfirm";
|
||||
import RocketSpinner from "@/components/RocketSpinner.vue";
|
||||
import { useHouseStore } from "@/app/store/house";
|
||||
|
||||
import api from "@/shared/api";
|
||||
import { confirmpopupStyle } from "@/shared/utils/util";
|
||||
|
||||
import WindowEditFacades from "@/widgets/editFacades/windowEditFacades.vue";
|
||||
const showLoader = ref(false)
|
||||
|
||||
const route = useRoute()
|
||||
const facades = ref([]);
|
||||
const confirm = useConfirm();
|
||||
const activeFacade = ref(false);
|
||||
const visibleEditWindow = ref(false);
|
||||
const currentItem = ref(false);
|
||||
const entrancesOptions = ref([])
|
||||
const houseStore = useHouseStore()
|
||||
const complex = computed(() => houseStore.complex)
|
||||
const house = computed(() => houseStore.house)
|
||||
|
||||
const showTemplate = (event, id) => {
|
||||
confirm.require({
|
||||
target: event.currentTarget,
|
||||
group: "deletePlan",
|
||||
message: "Удалить фасад?",
|
||||
acceptLabel: "Да",
|
||||
rejectLabel: "Нет",
|
||||
acceptClass: "!bg-red !shadow-none",
|
||||
rejectClass: "!bg-green !shadow-none",
|
||||
accept: () => {
|
||||
deleteItem(id);
|
||||
},
|
||||
reject: () => {
|
||||
return;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const dragStartHandler = (e, item) => {
|
||||
currentItem.value = item;
|
||||
};
|
||||
|
||||
const dragOverHandler = (e) => {
|
||||
const block = e
|
||||
.composedPath()
|
||||
.find((el) =>
|
||||
el.classList.contains("facadeItem")
|
||||
);
|
||||
if (!block) return;
|
||||
(block.querySelector(".facadeItem__content")).style.opacity =
|
||||
"0";
|
||||
block.style.backgroundColor = "#FFFBE3";
|
||||
block.style.border = "2px dashed #DEDEDE";
|
||||
};
|
||||
|
||||
const dragEndHandler = (e) => {
|
||||
const block = e
|
||||
.composedPath()
|
||||
.find((el) =>
|
||||
el.classList.contains("facadeItem")
|
||||
);
|
||||
if (!block) return;
|
||||
(block.querySelector(".facadeItem__content")).style.opacity =
|
||||
"1";
|
||||
block.style.backgroundColor = "#ffffff";
|
||||
block.style.border = "1px solid #DEDEDE";
|
||||
};
|
||||
|
||||
const dropHandler = (e, item) => {
|
||||
e.preventDefault();
|
||||
facades.value = facades.value.map((facade, index) => {
|
||||
let updatedFacade;
|
||||
if (facade.id === item.id) {
|
||||
updatedFacade = { ...facade, position: currentItem.value.position };
|
||||
} else if (facade.id === currentItem.value.id) {
|
||||
updatedFacade = { ...facade, position: item.position };
|
||||
} else {
|
||||
updatedFacade = facade
|
||||
}
|
||||
|
||||
return updatedFacade;
|
||||
});
|
||||
|
||||
sortedFacades.value.forEach((facade, index) => {
|
||||
updateFacade({id: facade.id, position: index + 1 })
|
||||
})
|
||||
|
||||
const block = e
|
||||
.composedPath()
|
||||
.find((el) =>
|
||||
el.classList.contains("facadeItem")
|
||||
);
|
||||
if (!block) return;
|
||||
(block.querySelector(".facadeItem__content")).style.opacity =
|
||||
"1";
|
||||
block.style.backgroundColor = "#ffffff";
|
||||
block.style.border = "1px solid #DEDEDE";
|
||||
};
|
||||
|
||||
const sortItems = (a, b) => {
|
||||
if (a.position > b.position) {
|
||||
return 1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
const sortedFacades = computed(() => {
|
||||
return facades.value.sort(sortItems)
|
||||
})
|
||||
|
||||
const deleteItem = async (id) => {
|
||||
api.deleteHouseFacade(id)
|
||||
.then((response) => {
|
||||
if (response.data && response.data.status) {
|
||||
const index = facades.value.findIndex((el) => el.id === id);
|
||||
facades.value.splice(index, 1);
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
const uploadFacade = (e) => {
|
||||
if (!e.target.files.length) return;
|
||||
|
||||
var bodyFormData = new FormData();
|
||||
bodyFormData.append("house_id", route.params.house_id);
|
||||
bodyFormData.append("position", facades.value.length + 1);
|
||||
bodyFormData.append("hover_color", "#FF0A0A");
|
||||
bodyFormData.append("opacity", "0.5");
|
||||
bodyFormData.append("photo", e.target.files[0]);
|
||||
|
||||
return api.addFacadeToHouse(bodyFormData)
|
||||
.then((response) => {
|
||||
if (response.data) {
|
||||
facades.value.push(response.data)
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const save = (facade_id, fields) => {
|
||||
const payload = {...fields}
|
||||
delete payload.figures
|
||||
return api.updateFacade(facade_id, payload)
|
||||
.then((response) => {
|
||||
if (response.data) {
|
||||
if (visibleEditWindow.value) visibleEditWindow.value = false;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const updateFacade = async (facade) => {
|
||||
await save(facade.id, facade)
|
||||
|
||||
const popupCloser = document.getElementById('#popup-closer');
|
||||
|
||||
if (popupCloser) {
|
||||
popupCloser.style.display = 'none';
|
||||
}
|
||||
};
|
||||
|
||||
const onPublishedChange = (facade) => {
|
||||
save(facade.id, { is_published: facade.is_published })
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await api.getFacades(route.params.house_id, ['not_published'])
|
||||
.then((response) => {
|
||||
if (response.data && Array.isArray(response.data)) {
|
||||
facades.value = response.data
|
||||
}
|
||||
})
|
||||
|
||||
await houseStore.get_house(route.params.house_id)
|
||||
|
||||
await api.getHouseEntrances(route.params.house_id)
|
||||
.then((response) => {
|
||||
if (response.data && Array.isArray(response.data)) {
|
||||
entrancesOptions.value = response.data.map((entrance) => ({
|
||||
name: entrance.name,
|
||||
value: entrance.id,
|
||||
floors: entrance.floors.map((floor) => ({
|
||||
name: floor.position,
|
||||
value: floor.id
|
||||
}))
|
||||
}))
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.editFacades {
|
||||
overflow-y: scroll;
|
||||
width: 100%;
|
||||
}
|
||||
.facadesList {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-rows: repeat(auto-fill, 438px);
|
||||
grid-gap: 30px;
|
||||
|
||||
.facadeItem {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
background-color: #dddddd;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #bdbdbd;
|
||||
|
||||
&__content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
transition: 0.5s opacity ease-in-out;
|
||||
}
|
||||
|
||||
.iconDrop {
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
right: -8px;
|
||||
z-index: 3;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
cursor: grab;
|
||||
svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.facadeItem__imageWrapper {
|
||||
.imageHover {
|
||||
&::before {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transition: 0.5s all ease-in-out;
|
||||
}
|
||||
|
||||
button {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transition: 0.5s all ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
position: absolute;
|
||||
top: 28px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
&__imageWrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.imageHover {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 2;
|
||||
background-image: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.7) 15%,
|
||||
rgba(255, 255, 255, 0) 57.08%
|
||||
);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
content: "";
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.7) 100%,
|
||||
rgba(255, 255, 255, 0) 57.08%
|
||||
);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: 0.5s all ease-in-out;
|
||||
}
|
||||
|
||||
button {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: 0.5s all ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__footer {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
padding: 20px;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.is_published {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
column-gap: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
217
src/pages/EditPlanFloors.vue
Normal file
217
src/pages/EditPlanFloors.vue
Normal file
@ -0,0 +1,217 @@
|
||||
<template>
|
||||
<div class="editPlanFloors bg-white">
|
||||
<div class="editPlanFloors__title mb-7.5">
|
||||
Редактирование планировки этажа
|
||||
</div>
|
||||
<Message severity="info" :closable="false">
|
||||
<template #messageicon>
|
||||
<svg
|
||||
width="18"
|
||||
height="19"
|
||||
viewBox="0 0 18 19"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-[22px] h-auto mr-2.5"
|
||||
>
|
||||
<path
|
||||
d="M9.00161 8.75C8.8027 8.75 8.61193 8.82902 8.47128 8.96967C8.33063 9.11032 8.25161 9.30109 8.25161 9.5V11.75C8.25161 11.9489 8.33063 12.1397 8.47128 12.2803C8.61193 12.421 8.8027 12.5 9.00161 12.5C9.20052 12.5 9.39129 12.421 9.53194 12.2803C9.67259 12.1397 9.75161 11.9489 9.75161 11.75V9.5C9.75161 9.30109 9.67259 9.11032 9.53194 8.96967C9.39129 8.82902 9.20052 8.75 9.00161 8.75ZM9.00161 6.5C8.85327 6.5 8.70827 6.54399 8.58493 6.6264C8.46159 6.70881 8.36546 6.82594 8.3087 6.96299C8.25193 7.10003 8.23708 7.25083 8.26602 7.39632C8.29496 7.5418 8.36639 7.67544 8.47128 7.78033C8.57617 7.88522 8.70981 7.95665 8.85529 7.98559C9.00078 8.01453 9.15158 7.99968 9.28862 7.94291C9.42567 7.88614 9.5428 7.79001 9.62521 7.66668C9.70762 7.54334 9.75161 7.39834 9.75161 7.25C9.75161 7.05109 9.67259 6.86032 9.53194 6.71967C9.39129 6.57902 9.20052 6.5 9.00161 6.5ZM9.00161 2C8.01669 2 7.04142 2.19399 6.13148 2.5709C5.22154 2.94781 4.39475 3.50026 3.69831 4.1967C2.29178 5.60322 1.50161 7.51088 1.50161 9.5C1.49505 11.2319 2.0947 12.9114 3.19661 14.2475L1.69661 15.7475C1.59254 15.853 1.52204 15.9869 1.49401 16.1324C1.46598 16.2779 1.48167 16.4284 1.53911 16.565C1.6014 16.6999 1.70239 16.8133 1.82924 16.8908C1.9561 16.9682 2.10311 17.0063 2.25161 17H9.00161C10.9907 17 12.8984 16.2098 14.3049 14.8033C15.7114 13.3968 16.5016 11.4891 16.5016 9.5C16.5016 7.51088 15.7114 5.60322 14.3049 4.1967C12.8984 2.79018 10.9907 2 9.00161 2ZM9.00161 15.5H4.05911L4.75661 14.8025C4.82747 14.733 4.88385 14.6502 4.92247 14.5588C4.9611 14.4674 4.9812 14.3692 4.98161 14.27C4.97879 14.0722 4.89792 13.8835 4.75661 13.745C3.77455 12.764 3.16299 11.4729 3.02613 10.0916C2.88926 8.71029 3.23557 7.32425 4.00603 6.16964C4.77649 5.01503 5.92345 4.16327 7.25149 3.75948C8.57954 3.35569 10.0065 3.42485 11.2893 3.95519C12.572 4.48552 13.6312 5.44421 14.2864 6.66792C14.9416 7.89163 15.1523 9.30466 14.8825 10.6663C14.6126 12.0279 13.8791 13.2538 12.8068 14.1352C11.7344 15.0166 10.3897 15.4989 9.00161 15.5Z"
|
||||
fill="#3A8EFA"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
<template #default>
|
||||
<p
|
||||
class="2xl:max-w-[1300px] xl:max-w-[990px] 2xl:text-base xl:text-sm"
|
||||
>
|
||||
Отметьте на поэтажном плане границы помещения. Кликните кнопкой мыши
|
||||
на размеченную область и свяжите её с соответствующей планировкой.
|
||||
Разметьте таким образом каждое помещение на плане. Если возникнут
|
||||
вопросы обратитесь к инструкции. Быстрые кнопки: Левая кнопка мыши =
|
||||
поставить точку, Правая кнопка мыши = удалить точку
|
||||
</p>
|
||||
</template>
|
||||
</Message>
|
||||
<MarkupFloor
|
||||
v-if="floorPlan"
|
||||
:floorPlan="floorPlan"
|
||||
:floorsOptions="floorsOptions"
|
||||
:entrancesOptions="entrancesOptions"
|
||||
:dataForm="dataForm"
|
||||
class="mt-7.5"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import MarkupFloor from "@/widgets/editPlanFloor/markupFloor.vue";
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import debounce from 'lodash.debounce'
|
||||
import bus from "@/eventBus";
|
||||
import api from "@/shared/api";
|
||||
import { useHouseStore } from "@/app/store/house";
|
||||
|
||||
const entranceList = ref([]);
|
||||
const floorPlan = ref(null);
|
||||
const houseStore = useHouseStore()
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const getEntranceList = () => {
|
||||
return api.getHouseEntrances(route.params.house_id, ['rooms'])
|
||||
.then((response) => {
|
||||
entranceList.value = response.data
|
||||
})
|
||||
.catch((error) => console.log(error));
|
||||
}
|
||||
|
||||
const getFloorPlan = () => {
|
||||
return api.getFloorPlan(route.params.floor_plan_id)
|
||||
.then((response) => {
|
||||
floorPlan.value = response.data
|
||||
|
||||
})
|
||||
.catch((error) => console.log(error));
|
||||
}
|
||||
|
||||
const INITIAL_DATA_FORM = {
|
||||
light_angle: 0,
|
||||
has_light_angle: true,
|
||||
floor_positions: [],
|
||||
is_use_all_entrances: false,
|
||||
entrances: [],
|
||||
image: null,
|
||||
};
|
||||
|
||||
const dataForm = ref({...INITIAL_DATA_FORM})
|
||||
|
||||
const floorsOptions = computed(() => {
|
||||
const positions = {}
|
||||
|
||||
if (entranceList.value.length) {
|
||||
entranceList.value.forEach( (entrance) => {
|
||||
if (dataForm.value.entrances?.find((formEntracnce) => formEntracnce.id == entrance.id) || dataForm.value.is_use_all_entrances) {
|
||||
entrance.floors.forEach(floor => {
|
||||
if (positions[`${floor.position}`]) {
|
||||
positions[`${floor.position}`].push(floor.id)
|
||||
} else {
|
||||
positions[`${floor.position}`] = [floor.id]
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return Object.entries(positions).map(([key, value]) => ({ name: key, value }))
|
||||
});
|
||||
|
||||
const entrancesOptions = computed(() => {
|
||||
return entranceList.value.map((entrance) => ({
|
||||
name: entrance.name, value: {id: entrance.id, position: entrance.position}
|
||||
}))
|
||||
})
|
||||
|
||||
const debouncedWatch = debounce(async (value) => {
|
||||
const floor_ids = dataForm.value.floor_positions?.reduce((acc, curr) => {
|
||||
Array.from(curr).forEach((value) => {
|
||||
acc = [...acc, value]
|
||||
})
|
||||
|
||||
return acc
|
||||
}, [])
|
||||
|
||||
await api.updateFloorPlans(
|
||||
[floorPlan.value.id],
|
||||
floor_ids,
|
||||
value
|
||||
).then((response) => {
|
||||
if (response.data && Array.isArray(response.data)) {
|
||||
floorPlan.value = response.data[0]
|
||||
}
|
||||
}).catch(error => console.log(error));
|
||||
}, 300)
|
||||
|
||||
watch(() => dataForm.value, debouncedWatch, { deep: true })
|
||||
watch(() => dataForm.value.is_use_all_entrances, (updatedIsUseAll, oldValue) => {
|
||||
if (updatedIsUseAll != oldValue && !updatedIsUseAll && entranceList.value.length) {
|
||||
dataForm.value.floor_positions = []
|
||||
}
|
||||
})
|
||||
watch(() => dataForm.value.entrances, (value, oldValue) => {
|
||||
if (value?.length !== oldValue?.length && entranceList.value.length) {
|
||||
dataForm.value.floor_positions = []
|
||||
}
|
||||
})
|
||||
|
||||
const figureDeleted = (figure_id) => {
|
||||
api.deleteFloorPlanFigure(figure_id)
|
||||
.then((response) => {
|
||||
if (response.data) {
|
||||
const index = floorPlan.value.figures.findIndex((figure) => figure.id === figure_id)
|
||||
|
||||
floorPlan.value.figures.splice(index, 1)
|
||||
}
|
||||
})
|
||||
.catch(error => console.log(error));
|
||||
}
|
||||
|
||||
const figureAdded = (figure) => {
|
||||
return api.addFigureToFloorPlan(floorPlan.value.id, figure)
|
||||
.then((response) => {
|
||||
if (response.data) {
|
||||
floorPlan.value.figures.push(response.data)
|
||||
}
|
||||
})
|
||||
.catch(error => console.log(error));
|
||||
}
|
||||
|
||||
const figurePlanSelected = ({room_plan_id, figure_id, background}) => {
|
||||
api.updateFigure(figure_id, { room_plan_id, background })
|
||||
.then((response) => {
|
||||
if (response.data) {
|
||||
const figure = floorPlan.value.figures.find((figure) => figure.id === figure_id)
|
||||
|
||||
Object.keys(response.data).forEach((key) => {
|
||||
figure[key] = response.data[key]
|
||||
})
|
||||
}
|
||||
}).catch(error => console.log(error));
|
||||
}
|
||||
|
||||
|
||||
onMounted(async () => {
|
||||
await getFloorPlan()
|
||||
await getEntranceList()
|
||||
|
||||
houseStore.get_house(route.params.house_id)
|
||||
|
||||
bus.$on('canvas:figure-added', figureAdded)
|
||||
|
||||
bus.$on('floor-plan:figure-deleted', figureDeleted)
|
||||
|
||||
bus.$on('floor-plan:figure-plan-selected', figurePlanSelected)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
bus.$off('canvas:figure-added', figureAdded)
|
||||
bus.$off('floor-plan:figure-deleted', figureDeleted)
|
||||
bus.$off('floor-plan:figure-plan-selected', figurePlanSelected)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.editPlanFloors {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 30px;
|
||||
overflow-y: scroll;
|
||||
|
||||
&__title {
|
||||
font-size: 32px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
54
src/pages/HomePage.vue
Normal file
54
src/pages/HomePage.vue
Normal file
@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div class="homePage relative bg-white flex justify-start items-start">
|
||||
<RouterView></RouterView>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss">
|
||||
.homePage {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.slide-enter-active,
|
||||
.slide-leave-active {
|
||||
transition: transform 1s ease, opacity 1s ease;
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
|
||||
.slide-enter-from {
|
||||
// position: absolute;
|
||||
// top: 0;
|
||||
// bottom: 0;
|
||||
// left: 100%;
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
.slide-enter-to {
|
||||
// position: absolute;
|
||||
// top: 0;
|
||||
// bottom: 0;
|
||||
// left: 0%;
|
||||
transform: translateX(0%);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.slide-leave-from {
|
||||
// position: absolute;
|
||||
// top: 0;
|
||||
// bottom: 0;
|
||||
// left: 0%;
|
||||
transform: translateX(0%);
|
||||
opacity: 1;
|
||||
}
|
||||
.slide-leave-to {
|
||||
// position: absolute;
|
||||
// overflow: hidden;
|
||||
// top: 0;
|
||||
// bottom: 0;
|
||||
// left: -100%;
|
||||
transform: translateX(-100%);
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
134
src/pages/HomePage/ChessPage.vue
Normal file
134
src/pages/HomePage/ChessPage.vue
Normal file
@ -0,0 +1,134 @@
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<FormAddRoom
|
||||
:isOpen="isOpen"
|
||||
@closeModel="closeModal"
|
||||
:floor="openedFloor"
|
||||
:room="openedRoom"
|
||||
/>
|
||||
</Teleport>
|
||||
<div class="chessPage p-7.5">
|
||||
<div class="chessPage__title mb-7.5">
|
||||
Детализация шахматки:
|
||||
<div class="subtitle">{{ complex.name }} — {{ house.name }}</div>
|
||||
</div>
|
||||
<Message
|
||||
severity="info"
|
||||
:closable="false"
|
||||
:pt="{ root: { class: ['!bg-blue-100 !border-blue-500 !mb-7.5'] } }"
|
||||
>
|
||||
<template #messageicon>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="mr-3.5"
|
||||
>
|
||||
<path
|
||||
d="M8.99966 8.25C8.80074 8.25 8.60998 8.32902 8.46933 8.46967C8.32867 8.61032 8.24966 8.80109 8.24966 9V11.25C8.24966 11.4489 8.32867 11.6397 8.46933 11.7803C8.60998 11.921 8.80074 12 8.99966 12C9.19857 12 9.38933 11.921 9.52999 11.7803C9.67064 11.6397 9.74966 11.4489 9.74966 11.25V9C9.74966 8.80109 9.67064 8.61032 9.52999 8.46967C9.38933 8.32902 9.19857 8.25 8.99966 8.25ZM8.99966 6C8.85132 6 8.70631 6.04399 8.58298 6.1264C8.45964 6.20881 8.36351 6.32594 8.30675 6.46299C8.24998 6.60003 8.23513 6.75083 8.26407 6.89632C8.29301 7.0418 8.36444 7.17544 8.46933 7.28033C8.57421 7.38522 8.70785 7.45665 8.85334 7.48559C8.99882 7.51453 9.14962 7.49968 9.28667 7.44291C9.42371 7.38614 9.54085 7.29001 9.62326 7.16668C9.70567 7.04334 9.74966 6.89834 9.74966 6.75C9.74966 6.55109 9.67064 6.36032 9.52999 6.21967C9.38933 6.07902 9.19857 6 8.99966 6ZM8.99966 1.5C8.01474 1.5 7.03947 1.69399 6.12953 2.0709C5.21959 2.44781 4.39279 3.00026 3.69635 3.6967C2.28983 5.10322 1.49966 7.01088 1.49966 9C1.4931 10.7319 2.09275 12.4114 3.19466 13.7475L1.69466 15.2475C1.59059 15.353 1.52009 15.4869 1.49206 15.6324C1.46403 15.7779 1.47972 15.9284 1.53716 16.065C1.59945 16.1999 1.70043 16.3133 1.82729 16.3908C1.95415 16.4682 2.10116 16.5063 2.24966 16.5H8.99966C10.9888 16.5 12.8964 15.7098 14.303 14.3033C15.7095 12.8968 16.4997 10.9891 16.4997 9C16.4997 7.01088 15.7095 5.10322 14.303 3.6967C12.8964 2.29018 10.9888 1.5 8.99966 1.5ZM8.99966 15H4.05716L4.75466 14.3025C4.82552 14.233 4.8819 14.1502 4.92052 14.0588C4.95914 13.9674 4.97924 13.8692 4.97966 13.77C4.97684 13.5722 4.89597 13.3835 4.75466 13.245C3.77259 12.264 3.16104 10.9729 3.02417 9.59159C2.88731 8.21029 3.23361 6.82425 4.00408 5.66964C4.77454 4.51503 5.9215 3.66327 7.24954 3.25948C8.57759 2.85569 10.0045 2.92485 11.2873 3.45519C12.5701 3.98552 13.6293 4.94421 14.2845 6.16792C14.9397 7.39163 15.1503 8.80466 14.8805 10.1663C14.6107 11.5279 13.8771 12.7538 12.8048 13.6352C11.7325 14.5166 10.3877 14.9989 8.99966 15Z"
|
||||
fill="#3A8EFA"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
<template #default>
|
||||
На этой странице вы можете заполнить дом помещениями без использования
|
||||
импорта, а также быстро вносить изменения в отдельные помещения.
|
||||
</template>
|
||||
</Message>
|
||||
<EntrancesList class="mt-7.5" />
|
||||
</div>
|
||||
<!-- <ConfirmDialog group="deleteEntrance" :pt="confirmpopupStyle"></ConfirmDialog> -->
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import EntrancesList from "@/widgets/homePage/entranceList/entranceList.vue";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { useRoute } from 'vue-router';
|
||||
import useHouseHook from "@/features/useHouseHook";
|
||||
import FormAddRoom from "@/widgets/forms/formAddRoom.vue";
|
||||
import bus from "@/eventBus.js"
|
||||
import { useHouseStore } from "@/app/store/house";
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const house_id = route.params.house_id;
|
||||
const houseStore = useHouseStore()
|
||||
|
||||
const complex = computed(() => houseStore.complex)
|
||||
const house = computed(() => houseStore.house)
|
||||
|
||||
const isOpen = ref(false)
|
||||
|
||||
const openedFloor = ref({})
|
||||
const openedRoom = ref({})
|
||||
// open:copy-room
|
||||
|
||||
|
||||
const closeModal = (room, action) => {
|
||||
if (room) {
|
||||
if (action == 'update') {
|
||||
Object.entries(room).forEach(([key, value]) => {
|
||||
openedRoom.value[key] = value
|
||||
})
|
||||
}else if (action == 'save') {
|
||||
openedFloor.value.rooms.push(room)
|
||||
}
|
||||
}
|
||||
|
||||
isOpen.value = false
|
||||
openedFloor.value = {}
|
||||
|
||||
setTimeout(() => {
|
||||
openedRoom.value = {}
|
||||
}, 300)
|
||||
}
|
||||
|
||||
|
||||
|
||||
bus.$on('open:add-room', (floor) => {
|
||||
isOpen.value = true
|
||||
openedFloor.value = floor
|
||||
})
|
||||
|
||||
bus.$on('open:edit-room', (floor, room) => {
|
||||
isOpen.value = true
|
||||
openedFloor.value = floor
|
||||
openedRoom.value = room
|
||||
})
|
||||
|
||||
// bus.$on('open:copy-room', (floor, room) => {
|
||||
// openedFloor.value = floor
|
||||
// openedRoom.value = room
|
||||
// isCopyRoomModalOpen.value = true
|
||||
// })
|
||||
|
||||
onMounted(() =>{
|
||||
houseStore.get_house(house_id);
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.chessPage {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
|
||||
&__title {
|
||||
font-size: 32px;
|
||||
line-height: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-end;
|
||||
|
||||
.subtitle {
|
||||
font-size: 21px;
|
||||
line-height: 100%;
|
||||
@apply text-grey-900;
|
||||
margin-left: 10px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
378
src/pages/HomePage/MainPage.vue
Normal file
378
src/pages/HomePage/MainPage.vue
Normal file
@ -0,0 +1,378 @@
|
||||
<template>
|
||||
<FormAddHome
|
||||
:isOpen="isOpenEditForm"
|
||||
ref="formEditHome"
|
||||
:complex="complex"
|
||||
:house="house"
|
||||
@close-model="isOpenEditForm = !isOpenEditForm"
|
||||
@add_house="edit_house"
|
||||
/>
|
||||
<div class="mainPage p-7.5 w-full">
|
||||
<div class="mainPage__title mb-7.5">{{ house.name }}</div>
|
||||
<HomeNav v-if="house" :house="house" class="mb-7.5" />
|
||||
<RouterLink
|
||||
v-if="house.rooms_count"
|
||||
:to="$route.path + '/view'"
|
||||
class="mainPage__viewChess mb-7.5 bg-green flex flex-row items-center px-5 py-3 gap-x-1.5 text-white rounded"
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M4.25 0.6875H1.25C1.10142 0.689442 0.959472 0.749329 0.8544 0.8544C0.749329 0.959472 0.689442 1.10142 0.6875 1.25V4.25C0.689442 4.39858 0.749329 4.54053 0.8544 4.6456C0.959472 4.75067 1.10142 4.81056 1.25 4.8125H4.25C4.39858 4.81056 4.54053 4.75067 4.6456 4.6456C4.75067 4.54053 4.81056 4.39858 4.8125 4.25V1.25C4.81056 1.10142 4.75067 0.959472 4.6456 0.8544C4.54053 0.749329 4.39858 0.689442 4.25 0.6875ZM3.6875 3.6875H1.8125V1.8125H3.6875V3.6875ZM14.75 0.6875H11.75C11.6014 0.689442 11.4595 0.749329 11.3544 0.8544C11.2493 0.959472 11.1894 1.10142 11.1875 1.25V4.25C11.1894 4.39858 11.2493 4.54053 11.3544 4.6456C11.4595 4.75067 11.6014 4.81056 11.75 4.8125H14.75C14.8986 4.81056 15.0405 4.75067 15.1456 4.6456C15.2507 4.54053 15.3106 4.39858 15.3125 4.25V1.25C15.3106 1.10142 15.2507 0.959472 15.1456 0.8544C15.0405 0.749329 14.8986 0.689442 14.75 0.6875ZM14.1875 3.6875H12.3125V1.8125H14.1875V3.6875ZM9.5 0.6875H6.5C6.35142 0.689442 6.20947 0.749329 6.1044 0.8544C5.99933 0.959472 5.93944 1.10142 5.9375 1.25V4.25C5.93944 4.39858 5.99933 4.54053 6.1044 4.6456C6.20947 4.75067 6.35142 4.81056 6.5 4.8125H9.5C9.64858 4.81056 9.79053 4.75067 9.8956 4.6456C10.0007 4.54053 10.0606 4.39858 10.0625 4.25V1.25C10.0606 1.10142 10.0007 0.959472 9.8956 0.8544C9.79053 0.749329 9.64858 0.689442 9.5 0.6875ZM8.9375 3.6875H7.0625V1.8125H8.9375V3.6875ZM4.25 5.9375H1.25C1.10142 5.93944 0.959472 5.99933 0.8544 6.1044C0.749329 6.20947 0.689442 6.35142 0.6875 6.5V9.5C0.689442 9.64858 0.749329 9.79053 0.8544 9.8956C0.959472 10.0007 1.10142 10.0606 1.25 10.0625H4.25C4.39858 10.0606 4.54053 10.0007 4.6456 9.8956C4.75067 9.79053 4.81056 9.64858 4.8125 9.5V6.5C4.81056 6.35142 4.75067 6.20947 4.6456 6.1044C4.54053 5.99933 4.39858 5.93944 4.25 5.9375ZM3.6875 8.9375H1.8125V7.0625H3.6875V8.9375ZM9.5 5.9375H6.5C6.35142 5.93944 6.20947 5.99933 6.1044 6.1044C5.99933 6.20947 5.93944 6.35142 5.9375 6.5V9.5C5.93944 9.64858 5.99933 9.79053 6.1044 9.8956C6.20947 10.0007 6.35142 10.0606 6.5 10.0625H9.5C9.64858 10.0606 9.79053 10.0007 9.8956 9.8956C10.0007 9.79053 10.0606 9.64858 10.0625 9.5V6.5C10.0606 6.35142 10.0007 6.20947 9.8956 6.1044C9.79053 5.99933 9.64858 5.93944 9.5 5.9375ZM8.9375 8.9375H7.0625V7.0625H8.9375V8.9375ZM14.75 5.9375H11.75C11.6014 5.93944 11.4595 5.99933 11.3544 6.1044C11.2493 6.20947 11.1894 6.35142 11.1875 6.5V9.5C11.1894 9.64858 11.2493 9.79053 11.3544 9.8956C11.4595 10.0007 11.6014 10.0606 11.75 10.0625H14.75C14.8986 10.0606 15.0405 10.0007 15.1456 9.8956C15.2507 9.79053 15.3106 9.64858 15.3125 9.5V6.5C15.3106 6.35142 15.2507 6.20947 15.1456 6.1044C15.0405 5.99933 14.8986 5.93944 14.75 5.9375ZM14.1875 8.9375H12.3125V7.0625H14.1875V8.9375ZM9.5 11.1875H6.5C6.35142 11.1894 6.20947 11.2493 6.1044 11.3544C5.99933 11.4595 5.93944 11.6014 5.9375 11.75V14.75C5.93944 14.8986 5.99933 15.0405 6.1044 15.1456C6.20947 15.2507 6.35142 15.3106 6.5 15.3125H9.5C9.64858 15.3106 9.79053 15.2507 9.8956 15.1456C10.0007 15.0405 10.0606 14.8986 10.0625 14.75V11.75C10.0606 11.6014 10.0007 11.4595 9.8956 11.3544C9.79053 11.2493 9.64858 11.1894 9.5 11.1875ZM8.9375 14.1875H7.0625V12.3125H8.9375V14.1875Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
Посмотреть объект
|
||||
</RouterLink>
|
||||
<div
|
||||
class="mainPage__information w-full border border-grey-400 px-7.5 py-5"
|
||||
>
|
||||
<div class="information__head border-b border-grey-400 pb-7.5 mb-7.5">
|
||||
<div class="information__title">Информация о доме</div>
|
||||
<MyButton
|
||||
@click.stop="() => {
|
||||
isOpenEditForm = true
|
||||
formEditHome.open();
|
||||
}"
|
||||
:disabled="!house.id"
|
||||
:theme="'grey-icon'"
|
||||
class="information__btnEdit text-sm text-grey-900"
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M14.75 8C14.5511 8 14.3603 8.07902 14.2197 8.21967C14.079 8.36032 14 8.55109 14 8.75V13.25C14 13.4489 13.921 13.6397 13.7803 13.7803C13.6397 13.921 13.4489 14 13.25 14H2.75C2.55109 14 2.36032 13.921 2.21967 13.7803C2.07902 13.6397 2 13.4489 2 13.25V2.75C2 2.55109 2.07902 2.36032 2.21967 2.21967C2.36032 2.07902 2.55109 2 2.75 2H7.25C7.44891 2 7.63968 1.92098 7.78033 1.78033C7.92098 1.63968 8 1.44891 8 1.25C8 1.05109 7.92098 0.860322 7.78033 0.71967C7.63968 0.579018 7.44891 0.5 7.25 0.5H2.75C2.15326 0.5 1.58097 0.737053 1.15901 1.15901C0.737053 1.58097 0.5 2.15326 0.5 2.75V13.25C0.5 13.8467 0.737053 14.419 1.15901 14.841C1.58097 15.2629 2.15326 15.5 2.75 15.5H13.25C13.8467 15.5 14.419 15.2629 14.841 14.841C15.2629 14.419 15.5 13.8467 15.5 13.25V8.75C15.5 8.55109 15.421 8.36032 15.2803 8.21967C15.1397 8.07902 14.9489 8 14.75 8ZM3.5 8.57V11.75C3.5 11.9489 3.57902 12.1397 3.71967 12.2803C3.86032 12.421 4.05109 12.5 4.25 12.5H7.43C7.5287 12.5006 7.62655 12.4817 7.71793 12.4443C7.80931 12.407 7.89242 12.352 7.9625 12.2825L13.1525 7.085L15.2825 5C15.3528 4.93028 15.4086 4.84733 15.4467 4.75593C15.4847 4.66454 15.5043 4.56651 15.5043 4.4675C15.5043 4.36849 15.4847 4.27046 15.4467 4.17907C15.4086 4.08767 15.3528 4.00472 15.2825 3.935L12.1025 0.7175C12.0328 0.647204 11.9498 0.591408 11.8584 0.553331C11.767 0.515255 11.669 0.495651 11.57 0.495651C11.471 0.495651 11.373 0.515255 11.2816 0.553331C11.1902 0.591408 11.1072 0.647204 11.0375 0.7175L8.9225 2.84L3.7175 8.0375C3.64799 8.10758 3.59299 8.19069 3.55567 8.28207C3.51835 8.37345 3.49943 8.4713 3.5 8.57ZM11.57 2.3075L13.6925 4.43L12.6275 5.495L10.505 3.3725L11.57 2.3075ZM5 8.8775L9.4475 4.43L11.57 6.5525L7.1225 11H5V8.8775Z"
|
||||
fill="#666666"
|
||||
/>
|
||||
</svg>
|
||||
Редактировать
|
||||
</MyButton>
|
||||
</div>
|
||||
<div class="information__content">
|
||||
<div class="information__image flex flex-col items-center">
|
||||
<Galleria
|
||||
v-if="galleryImages.length"
|
||||
:activeIndex="activeImageIndex"
|
||||
@update:activeIndex="(index) => activeImageIndex = index"
|
||||
:value="galleryImages"
|
||||
:showThumbnails="false"
|
||||
:showItemNavigators="galleryImages.length > 1"
|
||||
:numVisible="galleryImages.length + 1"
|
||||
:showItemNavigatorsOnHover="galleryImages.length > 1"
|
||||
containerStyle="width: 100%"
|
||||
>
|
||||
<template #item="slotProps">
|
||||
<img :src="slotProps.item.url" :alt="slotProps.item.name" style="width: 100%;" />
|
||||
</template>
|
||||
</Galleria>
|
||||
<div class="img__btns flex flex-row mt-5 gap-x-2">
|
||||
<input @change="onImagesAdded" multiple accept="image/*" type="file" class="hidden" ref="imagesInput" />
|
||||
<MyButton @click.stop="onAddImagesClick" :theme="'green'" class="text-sm">
|
||||
<svg
|
||||
width="19"
|
||||
height="18"
|
||||
viewBox="0 0 19 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M7.0325 5.78251L8.75 4.05751V11.25C8.75 11.4489 8.82902 11.6397 8.96967 11.7803C9.11032 11.921 9.30109 12 9.5 12C9.69891 12 9.88968 11.921 10.0303 11.7803C10.171 11.6397 10.25 11.4489 10.25 11.25V4.05751L11.9675 5.78251C12.0372 5.85281 12.1202 5.90861 12.2116 5.94668C12.303 5.98476 12.401 6.00436 12.5 6.00436C12.599 6.00436 12.697 5.98476 12.7884 5.94668C12.8798 5.90861 12.9628 5.85281 13.0325 5.78251C13.1028 5.71279 13.1586 5.62984 13.1967 5.53845C13.2347 5.44705 13.2543 5.34902 13.2543 5.25001C13.2543 5.151 13.2347 5.05298 13.1967 4.96158C13.1586 4.87019 13.1028 4.78724 13.0325 4.71751L10.0325 1.71751C9.96117 1.64923 9.87706 1.59571 9.785 1.56001C9.6024 1.485 9.39759 1.485 9.215 1.56001C9.12294 1.59571 9.03883 1.64923 8.9675 1.71751L5.9675 4.71751C5.89757 4.78744 5.8421 4.87046 5.80425 4.96183C5.76641 5.05319 5.74693 5.15112 5.74693 5.25001C5.74693 5.34891 5.76641 5.44683 5.80425 5.5382C5.8421 5.62957 5.89757 5.71258 5.9675 5.78251C6.03743 5.85244 6.12045 5.90791 6.21181 5.94576C6.30318 5.9836 6.4011 6.00308 6.5 6.00308C6.59889 6.00308 6.69682 5.9836 6.78819 5.94576C6.87955 5.90791 6.96257 5.85244 7.0325 5.78251ZM16.25 9.00001C16.0511 9.00001 15.8603 9.07903 15.7197 9.21968C15.579 9.36034 15.5 9.5511 15.5 9.75001V14.25C15.5 14.4489 15.421 14.6397 15.2803 14.7803C15.1397 14.921 14.9489 15 14.75 15H4.25C4.05109 15 3.86032 14.921 3.71967 14.7803C3.57902 14.6397 3.5 14.4489 3.5 14.25V9.75001C3.5 9.5511 3.42098 9.36034 3.28033 9.21968C3.13968 9.07903 2.94891 9.00001 2.75 9.00001C2.55109 9.00001 2.36032 9.07903 2.21967 9.21968C2.07902 9.36034 2 9.5511 2 9.75001V14.25C2 14.8468 2.23705 15.419 2.65901 15.841C3.08097 16.263 3.65326 16.5 4.25 16.5H14.75C15.3467 16.5 15.919 16.263 16.341 15.841C16.7629 15.419 17 14.8468 17 14.25V9.75001C17 9.5511 16.921 9.36034 16.7803 9.21968C16.6397 9.07903 16.4489 9.00001 16.25 9.00001Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
Добавить изображение
|
||||
</MyButton>
|
||||
<MyButton @click.stop="deleteImage" :theme="'grey-icon'">
|
||||
<svg
|
||||
width="19"
|
||||
height="18"
|
||||
viewBox="0 0 19 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 13.5C8.19891 13.5 8.38968 13.421 8.53033 13.2803C8.67098 13.1397 8.75 12.9489 8.75 12.75V8.25C8.75 8.05109 8.67098 7.86032 8.53033 7.71967C8.38968 7.57902 8.19891 7.5 8 7.5C7.80109 7.5 7.61032 7.57902 7.46967 7.71967C7.32902 7.86032 7.25 8.05109 7.25 8.25V12.75C7.25 12.9489 7.32902 13.1397 7.46967 13.2803C7.61032 13.421 7.80109 13.5 8 13.5ZM15.5 4.5H12.5V3.75C12.5 3.15326 12.2629 2.58097 11.841 2.15901C11.419 1.73705 10.8467 1.5 10.25 1.5H8.75C8.15326 1.5 7.58097 1.73705 7.15901 2.15901C6.73705 2.58097 6.5 3.15326 6.5 3.75V4.5H3.5C3.30109 4.5 3.11032 4.57902 2.96967 4.71967C2.82902 4.86032 2.75 5.05109 2.75 5.25C2.75 5.44891 2.82902 5.63968 2.96967 5.78033C3.11032 5.92098 3.30109 6 3.5 6H4.25V14.25C4.25 14.8467 4.48705 15.419 4.90901 15.841C5.33097 16.2629 5.90326 16.5 6.5 16.5H12.5C13.0967 16.5 13.669 16.2629 14.091 15.841C14.5129 15.419 14.75 14.8467 14.75 14.25V6H15.5C15.6989 6 15.8897 5.92098 16.0303 5.78033C16.171 5.63968 16.25 5.44891 16.25 5.25C16.25 5.05109 16.171 4.86032 16.0303 4.71967C15.8897 4.57902 15.6989 4.5 15.5 4.5ZM8 3.75C8 3.55109 8.07902 3.36032 8.21967 3.21967C8.36032 3.07902 8.55109 3 8.75 3H10.25C10.4489 3 10.6397 3.07902 10.7803 3.21967C10.921 3.36032 11 3.55109 11 3.75V4.5H8V3.75ZM13.25 14.25C13.25 14.4489 13.171 14.6397 13.0303 14.7803C12.8897 14.921 12.6989 15 12.5 15H6.5C6.30109 15 6.11032 14.921 5.96967 14.7803C5.82902 14.6397 5.75 14.4489 5.75 14.25V6H13.25V14.25ZM11 13.5C11.1989 13.5 11.3897 13.421 11.5303 13.2803C11.671 13.1397 11.75 12.9489 11.75 12.75V8.25C11.75 8.05109 11.671 7.86032 11.5303 7.71967C11.3897 7.57902 11.1989 7.5 11 7.5C10.8011 7.5 10.6103 7.57902 10.4697 7.71967C10.329 7.86032 10.25 8.05109 10.25 8.25V12.75C10.25 12.9489 10.329 13.1397 10.4697 13.2803C10.6103 13.421 10.8011 13.5 11 13.5Z"
|
||||
fill="#666666"
|
||||
/>
|
||||
</svg>
|
||||
</MyButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="information__textBlock flex flex-col">
|
||||
<div class="textBlock__item">
|
||||
<div class="item__label">Тип комплекса:</div>
|
||||
<div class="item__text">Жилой комплекс в новостройке</div>
|
||||
</div>
|
||||
<div class="textBlock__item">
|
||||
<div class="item__label">Адрес:</div>
|
||||
<div class="item__text">{{ house.address }}</div>
|
||||
</div>
|
||||
<div class="textBlock__item">
|
||||
<div class="item__label">Тип строения:</div>
|
||||
<div class="item__text">{{ house.type_building_text }}</div>
|
||||
</div>
|
||||
<div class="textBlock__item">
|
||||
<div class="item__label">Тип помещений по умолчанию:</div>
|
||||
<div class="item__text">{{ house.placement_type_text }}</div>
|
||||
</div>
|
||||
|
||||
<!-- <div class="textBlock__item">
|
||||
<div class="item__label">Метро:</div>
|
||||
<div v-if="complex.metros">
|
||||
<div style="display: flex; gap: 5px" v-for="metro in complex.metros">
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M13.242 2.24765L9.99989 12.4689L6.75798 2.24765C2.90144 3.57322 0 7.16913 0 11.2916C0 13.7746 1.01298 16.0644 2.65178 17.752H7.35212L7.84488 15.6151C2.22957 13.4149 3.29134 6.59717 5.32701 5.48428C5.56864 5.55694 9.10942 17.7017 9.10942 17.7017C9.15779 17.7017 9.31493 17.7017 9.50692 17.7017C9.54747 17.7017 9.70018 17.7017 9.89048 17.7017C9.95934 17.7017 10.0337 17.7017 10.1091 17.7017C10.2384 17.7017 10.3725 17.7017 10.4924 17.7017C10.6846 17.7017 10.8416 17.7017 10.8902 17.7017C10.8902 17.7017 14.4311 5.55694 14.6726 5.48428C16.7085 6.59717 17.7698 13.4149 12.1545 15.6151L12.6475 17.752H17.3478C18.9868 16.0644 19.9998 13.7746 19.9998 11.2916C19.9998 7.16913 17.0983 3.57322 13.242 2.24765Z"
|
||||
fill="#8EB3F5"
|
||||
/>
|
||||
</svg>
|
||||
{{ metro.metro }}
|
||||
<div class="time">{{ metro.subway_time}} минут <span v-if="metro.subway_type == 1">пешком</span><span v-else-if="metro.subway_type == 2">на транспорте</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="textBlock__item">
|
||||
<div class="item__label">Название ЖК:</div>
|
||||
<div class="item__text">{{ complex.name }}</div>
|
||||
</div>
|
||||
<div class="textBlock__item">
|
||||
<div class="item__label">Описание:</div>
|
||||
<div class="item__text">
|
||||
{{ complex.dop.text_desc }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="textBlock__item">
|
||||
<div class="item__label">Стадия строительства:</div>
|
||||
<span v-if="isEmpty(house.status_corpus_text)" class="text-grey-400">Неуказано</span>
|
||||
<div class="item__text">
|
||||
{{ house.status_corpus_text }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="textBlock__item">
|
||||
<div class="item__label">Срок сдачи:</div>
|
||||
<span v-if="isEmpty(house.deadline)" class="text-grey-400">Неуказано</span>
|
||||
<div v-else class="item__text">{{ house.deadline }}</div>
|
||||
</div>
|
||||
<div class="textBlock__item">
|
||||
<div class="item__label">Сроки строительства:</div>
|
||||
<span v-if="isEmpty(house.development)" class="text-grey-400">Неуказано</span>
|
||||
<div v-else class="item__text">{{ house.development }}</div>
|
||||
</div>
|
||||
<div class="textBlock__item">
|
||||
<div class="item__label">Кадастровый номер:</div>
|
||||
<span v-if="isEmpty(house.cadastral_number)" class="text-grey-400">Неуказано</span>
|
||||
<div v-else class="item__text">
|
||||
{{ house.cadastral_number }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="textBlock__item">
|
||||
<div class="item__label">Охрана:</div>
|
||||
<div class="item__text">
|
||||
{{ house.security == 1 ? 'Да' : 'Нет' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="textBlock__item">
|
||||
<div class="item__label">Огороженная территория:</div>
|
||||
<div class="item__text">
|
||||
{{ house.fenced_area == 1 ? 'Да' : 'Нет' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="textBlock__item">
|
||||
<div class="item__label">Спортивная площадка:</div>
|
||||
<div class="item__text">
|
||||
{{ house.sports_ground == 1 ? 'Да' : 'Нет' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="textBlock__item">
|
||||
<div class="item__label">Детская площадка:</div>
|
||||
<div class="item__text">
|
||||
{{ house.play_ground == 1 ? 'Да' : 'Нет' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="textBlock__item">
|
||||
<div class="item__label">Школа:</div>
|
||||
<div class="item__text">
|
||||
{{ house.school == 1 ? 'Да' : 'Нет' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="textBlock__item">
|
||||
<div class="item__label">Детский сад:</div>
|
||||
<div class="item__text">
|
||||
{{ house.kinder_garten == 1 ? 'Да' : 'Нет' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import MyButton from "@/shared/UI/MyButton.vue";
|
||||
import HomeNav from "@/widgets/homePage/homeNav.vue";
|
||||
import { ref, onMounted, watch, computed } from "vue";
|
||||
import { useRoute } from 'vue-router';
|
||||
import { isEmpty } from "@/shared/utils/util";
|
||||
import FormAddHome from "@/widgets/forms/formAddHome.vue";
|
||||
import api from "@/shared/api";
|
||||
import { useHouseStore } from "@/app/store/house";
|
||||
|
||||
// Route
|
||||
const route = useRoute();
|
||||
|
||||
const imagesInput = ref(null)
|
||||
const formEditHome = ref(null);
|
||||
const isOpenEditForm = ref(false)
|
||||
|
||||
const houseStore = useHouseStore()
|
||||
const activeImageIndex = ref(0)
|
||||
const activeImage = ref(null)
|
||||
const galleryImages = ref([])
|
||||
|
||||
const complex = computed(() => houseStore.complex)
|
||||
const house = computed(() => houseStore.house)
|
||||
|
||||
const edit_house = () => {
|
||||
houseStore.get_house(house_id).then(() => {
|
||||
isOpenEditForm.value = !isOpenEditForm.value;
|
||||
});
|
||||
}
|
||||
|
||||
const house_id = route.params.house_id;
|
||||
|
||||
watch(activeImageIndex, (index) => activeImage.value = galleryImages.value[index])
|
||||
|
||||
watch(() => house.value.images, (updatedImages) => {
|
||||
if (!updatedImages) return
|
||||
|
||||
if (!activeImage.value) activeImage.value = updatedImages[0]
|
||||
galleryImages.value = updatedImages
|
||||
}, { deep: true, immediate: true })
|
||||
|
||||
|
||||
const onAddImagesClick = () => imagesInput.value.click()
|
||||
|
||||
const uploadImage = (file) => {
|
||||
var bodyFormData = new FormData();
|
||||
bodyFormData.append("house_id", house_id);
|
||||
bodyFormData.append("photos[]", file);
|
||||
api.addPhotoToHouse(bodyFormData)
|
||||
.then((response) => {
|
||||
if (response.data && Array.isArray(response.data)) {
|
||||
if (!house.value.images) house.value.images = []
|
||||
|
||||
house.value.images.push(...response.data)
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onImagesAdded = (e) => {
|
||||
Array.from(e.target.files).forEach((file) => {
|
||||
uploadImage(file);
|
||||
});
|
||||
}
|
||||
|
||||
const deleteImage = () => {
|
||||
return api.deleteHousePhoto(activeImage.value.id)
|
||||
.then((response) => {
|
||||
if (response.data) {
|
||||
house.value.images.splice(activeImageIndex.value, 1)
|
||||
activeImageIndex.value = activeImageIndex.value ? activeImageIndex.value - 1 : 0;
|
||||
activeImage.value = galleryImages[activeImageIndex.value]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
houseStore.get_house(house_id);
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.mainPage {
|
||||
&__title {
|
||||
font-size: 32px;
|
||||
}
|
||||
&__viewChess {
|
||||
width: max-content;
|
||||
}
|
||||
&__information {
|
||||
max-width: 1390px;
|
||||
|
||||
.information__head {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.information__image {
|
||||
justify-content: center
|
||||
}
|
||||
|
||||
.information__title {
|
||||
@apply text-grey-900;
|
||||
}
|
||||
|
||||
.information__content {
|
||||
display: grid;
|
||||
grid-template-columns: 432px 1fr;
|
||||
grid-column-gap: 30px;
|
||||
}
|
||||
|
||||
.information__textBlock {
|
||||
row-gap: 30px;
|
||||
.textBlock__item {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: grid;
|
||||
grid-template-columns: 150px 1fr;
|
||||
grid-column-gap: 30px;
|
||||
|
||||
.item__label,
|
||||
.item__text {
|
||||
font-size: 15px;
|
||||
}
|
||||
.item__text span {
|
||||
color: #bdbdbd;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1600px) {
|
||||
&__information {
|
||||
.information__textBlock {
|
||||
row-gap: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.p-galleria-item-nav-onhover .p-galleria-item-wrapper:hover .p-galleria-item-nav {
|
||||
color: #d19090;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
118
src/pages/HomePage/PlanLayoutPage.vue
Normal file
118
src/pages/HomePage/PlanLayoutPage.vue
Normal file
@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<div class="defaultPage planLayoutPage bg-white h-full">
|
||||
<div class="titlePage planLayoutPage__title">
|
||||
{{complex.name}} — {{house.name}}
|
||||
</div>
|
||||
<TabView @update:activeIndex="updateActiveIndex($event)" v-if="house.id" class="w-full" :active-index="activeIndex">
|
||||
<TabPanel header="Планировка помещений">
|
||||
<PlanRooms v-if="activeIndex == 0" :house="house" />
|
||||
</TabPanel>
|
||||
<TabPanel header="Планировка этажей">
|
||||
<PlanFloors v-if="activeIndex == 1" :house="house" />
|
||||
</TabPanel>
|
||||
</TabView>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import PlanRooms from "@/widgets/homePage/planLayout/planRooms.vue";
|
||||
import PlanFloors from "@/widgets/homePage/planFloors/planFloors.vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import useHouseHook from "@/features/useHouseHook";
|
||||
import { useHouseStore } from "@/app/store/house";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const activeIndex = ref(0);
|
||||
|
||||
const house_id = route.params.house_id;
|
||||
|
||||
const houseStore = useHouseStore()
|
||||
const complex = computed(() => houseStore.complex)
|
||||
const house = computed(() => houseStore.house)
|
||||
const updateActiveIndex = (event) => {
|
||||
activeIndex.value = event
|
||||
router.push({params: { tab: activeIndex.value ? 'floors' : 'rooms' }})
|
||||
}
|
||||
onMounted(async () =>{
|
||||
await houseStore.get_house(house_id);
|
||||
|
||||
if (route.params.tab === "floors")
|
||||
activeIndex.value = 1
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.defaultPage {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
padding: 30px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.titlePage {
|
||||
font-size: 32px;
|
||||
line-height: 120%;
|
||||
font-weight: 400;
|
||||
}
|
||||
.tabWidget {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
|
||||
&__nav {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
@apply border-b;
|
||||
@apply border-grey-400;
|
||||
column-gap: 40px;
|
||||
|
||||
.nav__item {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
padding-bottom: 23px;
|
||||
font-size: 16px;
|
||||
margin-bottom: -1px;
|
||||
|
||||
&.active {
|
||||
font-weight: 700;
|
||||
border-bottom: 3px solid;
|
||||
@apply border-green;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1600px) {
|
||||
column-gap: 30px;
|
||||
.nav__item {
|
||||
padding-bottom: 18px;
|
||||
font-size: 14px;
|
||||
&.active {
|
||||
border-bottom-width: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.planLayoutPage {
|
||||
&__title {
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1600px) {
|
||||
&__title {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
447
src/pages/HomePage/ViewHome.vue
Normal file
447
src/pages/HomePage/ViewHome.vue
Normal file
@ -0,0 +1,447 @@
|
||||
<template>
|
||||
<IframeNavbar @closeIframeNavbar="closeIframeNavbar" v-if="navbarIsOpen || windowWidth > 768" @view-mode-emit="changeViewMode" class="iframe-navbar-left" :visibility="visibilityItems"/>
|
||||
<div class="viewHome flex flex-col h-full min-h-full justify-start items-start"
|
||||
:class="[filters.view?.value !== 'facades' ? 'px-7.5 py-5' : '']" ref="viewHome">
|
||||
<teleport to="body">
|
||||
<WindowInfoForList
|
||||
v-if="openedApartment"
|
||||
:house="house"
|
||||
:isOpen="isOpenWindow"
|
||||
:apartment="openedApartment"
|
||||
:availableFloors="openApartmentAvailableFloors"
|
||||
@update:is-open="closeApartment"
|
||||
@floor:changed="changeApartment"
|
||||
:decorations="fieldsStore.decorations"
|
||||
:windowsPlacements="fieldsStore.windowsPlacements"
|
||||
@update:is-open-window="openApartment"
|
||||
@full:plan-state-updated="toggleIsOpenFullPlan"
|
||||
:house_name="house?.name"
|
||||
:complex_name="complex?.name"
|
||||
:complex_phone="complex?.dop?.phone_op"
|
||||
:filters="filters" />
|
||||
</teleport>
|
||||
<!-- <ApartmentDetailIframe class="apartment_detail_modal" v-if="apartmentDetailIsOpen" canvasId="apartmentPlan" :apartment="openedApartment" /> -->
|
||||
<div v-if="apartmentsLoader" class="loader-div">
|
||||
<RocketSpinner/>
|
||||
</div>
|
||||
|
||||
<!-- <template v-else-if="route.params.apartment_id">
|
||||
<ApartmentDetailIframe canvasId="apartmentPlan" v-if="openedApartment" :apartment="openedApartment" />
|
||||
</template> -->
|
||||
|
||||
<template v-else>
|
||||
|
||||
<ViewHead v-if="house && filters.view.value !== 'facades'"
|
||||
:show_plans="!!plans.filter(plan => {
|
||||
return plan.rooms.some(room => room.visible
|
||||
)}).length"
|
||||
:show_floors="!!house.floors_plans_count_where_has_floor"
|
||||
:show_facades="!!house.facades_count"
|
||||
@update:isOpenFiltersMenu="isOpenFiltersMenu = $event"
|
||||
:filters="filters"
|
||||
:filteredCount="filterCounter"
|
||||
@update:viewType="viewType = $event"
|
||||
:complex_status = "fieldsStore.complex_status"
|
||||
@openIframeNavbar="openIframeNavbar"
|
||||
:navBarClosed="navBarClosed"
|
||||
/>
|
||||
<div class="viewHome__body w-full h-full">
|
||||
<ChessTile v-if="filters.view?.value === 'tile' || filters.view?.value === 'grid'" :typeView="filters.view?.value"
|
||||
:entrances="entrances" :hidden-price="filters.hiddenPrice" :house_name="house?.name"
|
||||
:complex_name="complex?.name" @openWindow="openApartment" :openedApartment="openedApartment" />
|
||||
<ChessLayouts v-if="filters.view?.value === 'layouts'" @update:is-open-window="openApartment" :plans="plans"
|
||||
:house_name="house?.name" :complex_name="complex?.name" :isDetailsOpened="isOpenWindow" />
|
||||
<ChessFloors v-if="filters.view?.value === 'floors' && !isOpenFullPlan" @update:is-open-window="openApartment"
|
||||
:house_name="house?.name" :complex_name="complex?.name" :filters="filters" />
|
||||
<ChessFacade v-if="filters.view?.value === 'facades'" :view-type="viewType" :house_name="house?.name"
|
||||
:complex_name="complex?.name" :filters="filters" :decorations="fieldsStore.decorations"
|
||||
:windowsPlacements="fieldsStore.windowsPlacements" />
|
||||
<ChessList v-if="filters.view?.value === 'list'" @update:is-open-window="openApartment" @openApartment="(apartment) => openApartmentDetail(apartment)" :entrances="entrances"
|
||||
:decorations="fieldsStore.decorations" :windowsPlacements="fieldsStore.windowsPlacements" :openedApartment="openedApartment" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, onUnmounted, ref, watch, watchEffect } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import RocketSpinner from "@/components/RocketSpinner.vue";
|
||||
import { useFieldsStore } from "@/app/store/fields";
|
||||
import { useHouseStore } from "@/app/store/house";
|
||||
|
||||
import ChessTile from "@/widgets/homePage/viewHome/chessTile.vue";
|
||||
import ChessLayouts from "@/widgets/homePage/viewHome/chessLayouts.vue";
|
||||
import ChessFloors from "@/widgets/homePage/viewHome/chessFloors.vue";
|
||||
import ChessList from "@/widgets/homePage/viewHome/chessList.vue";
|
||||
import ViewHead from "@/widgets/homePage/viewHome/viewHead.vue";
|
||||
import WindowInfoForList from "@/widgets/homePage/viewHome/windowInfoForList.vue";
|
||||
import ChessFacade from "@/widgets/homePage/viewHome/chessFacade.vue";
|
||||
import api from "@/shared/api";
|
||||
import { copyObject, fixRoomVisible } from "@/shared/utils/util";
|
||||
import eventBus from "@/eventBus";
|
||||
import IframeNavbar from "@/components/IframeNavbar.vue";
|
||||
import BurgerIcon from "@/components/BurgerIcon.vue";
|
||||
import Navigation from "@/components/Navigation.vue";
|
||||
import ApartmentDetailIframe from "../ApartmentDetailIframe.vue";
|
||||
|
||||
const houseStore = useHouseStore()
|
||||
|
||||
const complex = computed(() => houseStore.complex)
|
||||
const house = computed(() => houseStore.house)
|
||||
const apartmentDetailIsOpen = ref(false)
|
||||
|
||||
const viewHome = ref();
|
||||
const isOpenFiltersMenu = ref(false);
|
||||
const isOpenWindow = ref(false);
|
||||
const viewType = ref("park");
|
||||
const INITIAL_FILTERS = {
|
||||
rooms: [],
|
||||
selectedTypes: [],
|
||||
selectedDecorations: [],
|
||||
floor: {
|
||||
from: '',
|
||||
to: ''
|
||||
},
|
||||
price: {
|
||||
min: "",
|
||||
max: "",
|
||||
},
|
||||
square: {
|
||||
min: "",
|
||||
max: "",
|
||||
},
|
||||
squareKitchen: {
|
||||
min: "",
|
||||
max: "",
|
||||
},
|
||||
useSquarePrice: false,
|
||||
hiddenPrice: false,
|
||||
onlyFree: false,
|
||||
view: {
|
||||
name: "Список",
|
||||
value: "list",
|
||||
icon: "list",
|
||||
visible: true,
|
||||
},
|
||||
adsTargets: [],
|
||||
adsStates: [],
|
||||
limit: {
|
||||
total: {
|
||||
min: "",
|
||||
max: ""
|
||||
},
|
||||
area: {
|
||||
min: "",
|
||||
max: ""
|
||||
},
|
||||
kitchen_area: {
|
||||
min: "",
|
||||
max: ""
|
||||
},
|
||||
room_types: [],
|
||||
decoration_types: [],
|
||||
window_placements: [],
|
||||
}
|
||||
};
|
||||
|
||||
const apartmentsLoader = ref(true);
|
||||
const filters = ref(copyObject(INITIAL_FILTERS));
|
||||
const filtersLimit = ref({});
|
||||
|
||||
const openedApartment = ref(null)
|
||||
const navbarIsOpen = ref (false)
|
||||
|
||||
const entrances = ref([]);
|
||||
const plans = ref([])
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const fieldsStore = useFieldsStore()
|
||||
const isOpenFullPlan = ref(false)
|
||||
const navBarClosed = ref(null)
|
||||
const emit = defineEmits(["view-mode-emit", "update:viewType", "closeIframeNavbar"]);
|
||||
const windowWidth = ref(window.innerWidth);
|
||||
|
||||
const updateWidth = () => {
|
||||
windowWidth.value = window.innerWidth
|
||||
}
|
||||
|
||||
const openIframeNavbar = () => {
|
||||
navbarIsOpen.value = true
|
||||
eventBus.$emit('navbarIsOpened')
|
||||
|
||||
}
|
||||
const closeIframeNavbar = () => {
|
||||
navbarIsOpen.value = false
|
||||
eventBus.$emit('navbarIsClosed')
|
||||
// navBarClosed.value = true
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', updateWidth)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', updateWidth)
|
||||
})
|
||||
|
||||
const clearFilters = () => {
|
||||
filters.value = copyObject({ ...INITIAL_FILTERS, view: filters.value.view, limit: filtersLimit.value })
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (!openedApartment || route.params.apartment_id !== openedApartment.value?.id) {
|
||||
api.get_room_entrance(route.params.apartment_id).then((res) => {
|
||||
const entrance = res.data;
|
||||
entrance.floors.forEach((floor) => floor.rooms.find((room) => {
|
||||
if (room.id == route.params.apartment_id) {
|
||||
openedApartment.value = room
|
||||
console.log(openedApartment.value);
|
||||
}
|
||||
}))
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const openApartmentDetail = (apartment) => {
|
||||
console.log('clicked');
|
||||
openedApartment.value = apartment
|
||||
const apartmentId = apartment.id
|
||||
isOpenWindow.value = true
|
||||
}
|
||||
|
||||
const changeViewMode = (value) => {
|
||||
console.log(value);
|
||||
const { apartment_id, ...paramsWithoutApartmentId } = route.params;
|
||||
const newParams = { ...paramsWithoutApartmentId };
|
||||
router.push({
|
||||
name: 'view-home', // остаёмся на той же странице
|
||||
params: newParams,
|
||||
query: route.query,
|
||||
});
|
||||
filters.value.view = value
|
||||
}
|
||||
|
||||
const plansArr = computed(() => plans.value ?? []);
|
||||
const visibilityItems = computed(() => ({
|
||||
all: true,
|
||||
facades: !!house.value.facades_count,
|
||||
plans: Array.isArray(plansArr.value) && plansArr.value.some(plan =>
|
||||
Array.isArray(plan.rooms) && plan.rooms.some(room => room.visible)
|
||||
),
|
||||
plate: true,
|
||||
net: true,
|
||||
floors: !!house.value.floors_plans_count_where_has_floor
|
||||
}));
|
||||
|
||||
console.log(plansArr, 'plansArr');
|
||||
|
||||
watch(() => route.params.apartment_id, {
|
||||
immediate: true,
|
||||
handler(apartmentId) {
|
||||
if (apartmentId) {
|
||||
this.loadApartmentById(apartmentId)
|
||||
} else {
|
||||
this.isApartmentDetail = false
|
||||
this.openedApartment = null
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
watch(filters, (newV) => {
|
||||
if (!newV) return;
|
||||
|
||||
console.log(newV, 'filters filters');
|
||||
|
||||
const updateRoomsVisibility = (rooms) => {
|
||||
rooms.forEach((room) => fixRoomVisible(room, filters.value));
|
||||
};
|
||||
|
||||
entrances.value.forEach(({ floors }) =>
|
||||
floors.forEach(({ rooms }) => updateRoomsVisibility(rooms))
|
||||
);
|
||||
|
||||
plans.value.forEach(({ rooms }) => updateRoomsVisibility(rooms));
|
||||
}, { deep: true });
|
||||
|
||||
const filterCounter = computed(() => {
|
||||
switch (filters.value.view?.value) {
|
||||
case 'layouts':
|
||||
return countFilteredPlansRooms
|
||||
default:
|
||||
return countFilteredRooms
|
||||
}
|
||||
})
|
||||
|
||||
// filters.view = ChessTile
|
||||
const countFilteredPlansRooms = computed(() => {
|
||||
return (plans.value || []).reduce(((acc, plan) => {
|
||||
return acc + plan.rooms.reduce((roomsAcc, room) => {
|
||||
return roomsAcc + +room.visible
|
||||
}, 0)
|
||||
}), 0)
|
||||
})
|
||||
const countFilteredRooms = computed(() => {
|
||||
return (entrances.value || []).reduce(((acc, entrance) => {
|
||||
return acc + entrance.floors.reduce((floorAcc, floor) => {
|
||||
return floorAcc + floor.rooms.reduce((roomsAcc, room) => {
|
||||
return roomsAcc + +room.visible
|
||||
}, 0)
|
||||
}, 0)
|
||||
}), 0)
|
||||
})
|
||||
|
||||
const loadEntrances = async () => {
|
||||
await api.getHouseEntrances(route.params.house_id, ['rooms']).then((response) => {
|
||||
const updatedEntrances = response.data.sort((a, b) => a.position > b.position ? 1 : -1)
|
||||
|
||||
updatedEntrances
|
||||
.forEach((entrance) => entrance.floors = entrance.floors
|
||||
.sort((a, b) => a.position > b.position ? 1 : -1))
|
||||
|
||||
updatedEntrances.forEach(
|
||||
entrance => entrance.floors
|
||||
.forEach(floor => floor.rooms
|
||||
.forEach(room => { room.visible = true })))
|
||||
|
||||
entrances.value = updatedEntrances
|
||||
}).finally(() => apartmentsLoader.value = false)
|
||||
}
|
||||
|
||||
const toggleIsOpenFullPlan = (payload) => {
|
||||
isOpenFullPlan.value = payload
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (route.params.house_id) {
|
||||
await loadEntrances();
|
||||
await api.getHousePlans(route.params.house_id)
|
||||
.then((response) => {
|
||||
var res = response.data;
|
||||
|
||||
if (res) {
|
||||
const filteredPlans = res.filter((plan) => plan.rooms.length);
|
||||
filteredPlans.forEach(plan => {
|
||||
plan.rooms.forEach(room => {
|
||||
room.visible = true
|
||||
})
|
||||
})
|
||||
plans.value = filteredPlans
|
||||
}
|
||||
})
|
||||
await houseStore.get_house(route.params.house_id, ['rooms']).then((response) => {
|
||||
if (response.data) {
|
||||
if (response.data.house && !response.data.house.rooms_count) router.push(`/my-objects/${route.params.id}/home/${route.params.house_id}`)
|
||||
}
|
||||
})
|
||||
|
||||
await api.getFilterFields(route.params.house_id)
|
||||
.then(({ data }) => {
|
||||
filtersLimit.value = data;
|
||||
filters.value.limit = filtersLimit.value;
|
||||
})
|
||||
}
|
||||
|
||||
eventBus.$on('clear-filters', clearFilters)
|
||||
eventBus.$on('update-model', loadEntrances)
|
||||
eventBus.$on('openApartment', openApartment )
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
eventBus.$off('clear-filters', clearFilters)
|
||||
eventBus.$off('update-model', loadEntrances)
|
||||
})
|
||||
|
||||
const openApartment = (apartment) => {
|
||||
openedApartment.value = apartment
|
||||
isOpenWindow.value = true
|
||||
}
|
||||
|
||||
const closeApartment = () => {
|
||||
isOpenWindow.value = false
|
||||
setTimeout(() => {
|
||||
openedApartment.value = {}
|
||||
}, 350)
|
||||
}
|
||||
|
||||
const changeApartment = (floorPosition) => {
|
||||
const floor = openedApartmentEntrance.value.floors.find((entranceFloor) => entranceFloor.position == floorPosition)
|
||||
|
||||
openApartment(floor.rooms.find((room) => room.position == openedApartment.value.position))
|
||||
}
|
||||
|
||||
const openedApartmentEntrance = computed(() => {
|
||||
if (openedApartment.value) {
|
||||
return entrances.value.find((entrance) => entrance.id == openedApartment.value.entrance_id)
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
|
||||
const openApartmentAvailableFloors = computed(() => {
|
||||
if (openedApartment.value && openedApartmentEntrance.value) {
|
||||
return openedApartmentEntrance.value.floors
|
||||
.filter((floor) => floor.rooms.find((room) => room.position == openedApartment.value.position))
|
||||
}
|
||||
|
||||
return []
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.viewHome {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
// max-width: calc(100vw - 350px);
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
flex-grow: 1;
|
||||
|
||||
&__body {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 700px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.iframe-navbar-left {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
max-height: 100vh;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
@media(max-width: 768px) {
|
||||
.iframe-navbar-left {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 23;
|
||||
}
|
||||
.loader-div{
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
@media(min-width: 768px) {
|
||||
.viewHome {
|
||||
max-width: calc(100vw + 350px);
|
||||
}
|
||||
}
|
||||
|
||||
.apartment_detail_modal {
|
||||
position: absolute;
|
||||
left: -350px;
|
||||
top: 0;
|
||||
}
|
||||
</style>
|
||||
260
src/pages/ImportHomes.vue
Normal file
260
src/pages/ImportHomes.vue
Normal file
@ -0,0 +1,260 @@
|
||||
<template>
|
||||
<div class="importHome bg-white">
|
||||
<div class="importHome__title">Импорт помещений</div>
|
||||
<Steps :active-item="activeItem" :items="stepItems" />
|
||||
<div class="tabContent w-full mt-7.5">
|
||||
<template v-if="!uploadedFileInfo.is_processing">
|
||||
<UploadStep
|
||||
v-if="activeItem === 1"
|
||||
:isRequesting="isRequesting"
|
||||
@update:active-item="activeItem = $event"
|
||||
@done:comparison-prepared="handlePrepared" @updateImport="updateImport"
|
||||
/>
|
||||
<FieldStep
|
||||
v-if="activeItem === 2"
|
||||
:isRequesting="isRequesting"
|
||||
@update:active-item="activeItem = $event"
|
||||
@updateImport="updateImport"
|
||||
:data="uploadedFileInfo"
|
||||
/>
|
||||
<ObjectStep
|
||||
v-if="activeItem === 3"
|
||||
:isRequesting="isRequesting"
|
||||
@update:active-item="activeItem = $event"
|
||||
@updateImport="updateImport"
|
||||
@open:create-home="(key) => {
|
||||
isCreateHomeOpen = true
|
||||
formAddHome.open()
|
||||
keyToUseWhileAddingHouse = key;
|
||||
}"
|
||||
:houses="housesList"
|
||||
:fileHousesMapping="uploadedFileInfo.house_name_to_id"
|
||||
@setHouse="uploadedFileInfo.house_name_to_id = $event"
|
||||
/>
|
||||
<StatusStep
|
||||
v-if="activeItem === 4 && uploadedFileInfo.dynamic_fields"
|
||||
:isRequesting="isRequesting"
|
||||
@update:active-item="activeItem = $event"
|
||||
@updateImport="updateImport"
|
||||
:statusesFromFile="uploadedFileInfo.dynamic_fields.complex_status_id"
|
||||
:typesFromFile="uploadedFileInfo.dynamic_fields.type"
|
||||
/>
|
||||
</template>
|
||||
<div class="loadingScreen" v-else>
|
||||
<div class="loadingScreen__content">
|
||||
<div class="loadingScreen__title">
|
||||
Вы загрузили файл.
|
||||
</div>
|
||||
<div class="loadingScreen__text max-w-[318px]">
|
||||
Идет обработка и автоматическое распознавание файла. Процесс может
|
||||
занимать от 1 до 10 минут.
|
||||
</div>
|
||||
<div style="width: 320px; margin-top: 10px">
|
||||
<Loader />
|
||||
</div>
|
||||
</div>
|
||||
<Message :closable="false" severity="info" :pt="{ root: { class: ['!bg-blue-100 !border-blue-500'] } }">
|
||||
<template #messageicon>
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" class="mr-3">
|
||||
<path
|
||||
d="M8.99966 8.25C8.80074 8.25 8.60998 8.32902 8.46933 8.46967C8.32867 8.61032 8.24966 8.80109 8.24966 9V11.25C8.24966 11.4489 8.32867 11.6397 8.46933 11.7803C8.60998 11.921 8.80074 12 8.99966 12C9.19857 12 9.38933 11.921 9.52999 11.7803C9.67064 11.6397 9.74965 11.4489 9.74965 11.25V9C9.74965 8.80109 9.67064 8.61032 9.52999 8.46967C9.38933 8.32902 9.19857 8.25 8.99966 8.25ZM8.99966 6C8.85132 6 8.70631 6.04399 8.58298 6.1264C8.45964 6.20881 8.36351 6.32594 8.30675 6.46299C8.24998 6.60003 8.23513 6.75083 8.26407 6.89632C8.293 7.0418 8.36444 7.17544 8.46933 7.28033C8.57421 7.38522 8.70785 7.45665 8.85334 7.48559C8.99882 7.51453 9.14962 7.49968 9.28667 7.44291C9.42371 7.38614 9.54085 7.29001 9.62326 7.16668C9.70567 7.04334 9.74965 6.89834 9.74965 6.75C9.74965 6.55109 9.67064 6.36032 9.52999 6.21967C9.38933 6.07902 9.19857 6 8.99966 6ZM8.99966 1.5C8.01474 1.5 7.03947 1.69399 6.12953 2.0709C5.21959 2.44781 4.39279 3.00026 3.69635 3.6967C2.28983 5.10322 1.49966 7.01088 1.49966 9C1.4931 10.7319 2.09275 12.4114 3.19466 13.7475L1.69466 15.2475C1.59059 15.353 1.52009 15.4869 1.49206 15.6324C1.46403 15.7779 1.47972 15.9284 1.53716 16.065C1.59945 16.1999 1.70043 16.3133 1.82729 16.3908C1.95415 16.4682 2.10116 16.5063 2.24966 16.5H8.99966C10.9888 16.5 12.8964 15.7098 14.303 14.3033C15.7095 12.8968 16.4997 10.9891 16.4997 9C16.4997 7.01088 15.7095 5.10322 14.303 3.6967C12.8964 2.29018 10.9888 1.5 8.99966 1.5ZM8.99966 15H4.05716L4.75466 14.3025C4.82552 14.233 4.8819 14.1502 4.92052 14.0588C4.95914 13.9674 4.97924 13.8692 4.97966 13.77C4.97684 13.5722 4.89597 13.3835 4.75466 13.245C3.77259 12.264 3.16104 10.9729 3.02417 9.59159C2.88731 8.21029 3.23361 6.82425 4.00408 5.66964C4.77454 4.51503 5.9215 3.66327 7.24954 3.25948C8.57758 2.85569 10.0045 2.92485 11.2873 3.45519C12.5701 3.98552 13.6293 4.94421 14.2845 6.16792C14.9397 7.39163 15.1503 8.80466 14.8805 10.1663C14.6107 11.5279 13.8771 12.7538 12.8048 13.6352C11.7325 14.5166 10.3877 14.9989 8.99966 15Z"
|
||||
fill="#3A8EFA" />
|
||||
</svg>
|
||||
</template>
|
||||
<template #default>
|
||||
Смотрите пошаговые инструкции по созданию шахматки в Базе знаний.
|
||||
Клиентская поддержка: 8 800 201 89 61 (бесплатно из России).
|
||||
</template>
|
||||
</Message>
|
||||
<MyButton :theme="'green'" @click="emits('update:activeItem', 0)">Прервать и загрузить другой файл</MyButton>
|
||||
</div>
|
||||
</div>
|
||||
<FormAddHome :isOpen="isCreateHomeOpen" ref="formAddHome" @close-model="isCreateHomeOpen = !isCreateHomeOpen"
|
||||
@add_house="add_house" />
|
||||
</div>
|
||||
<Toast />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import UploadStep from "@/widgets/importSteps/uploadStep.vue";
|
||||
import FieldStep from "@/widgets/importSteps/fieldStep.vue";
|
||||
import ObjectStep from "@/widgets/importSteps/objectStep.vue";
|
||||
import StatusStep from "@/widgets/importSteps/statusStep.vue";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import FormAddHome from "@/widgets/forms/formAddHome.vue";
|
||||
import api from "@/shared/api";
|
||||
import { generateAlphabetSequence } from "@/shared/utils/util";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import Loader from "@/components/Loader.vue";
|
||||
import Steps from "@/widgets/steps.vue";
|
||||
import { useToast } from "primevue/usetoast";
|
||||
|
||||
const uploadedFileInfo = ref({
|
||||
body: null,
|
||||
head: null
|
||||
})
|
||||
const keyToUseWhileAddingHouse = ref()
|
||||
const isRequesting = ref()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const toast = useToast();
|
||||
|
||||
const handlePrepared = (inputData) => {
|
||||
if (!inputData) return;
|
||||
|
||||
api.getHouseImport(inputData.id).then((response) => {
|
||||
uploadedFileInfo.value.body = response.data.document_preview.body.map((data, index) => ({
|
||||
items: data,
|
||||
initialIndex: index
|
||||
}))
|
||||
|
||||
uploadedFileInfo.value.head = generateAlphabetSequence(response.data.document_preview.head.length);
|
||||
uploadedFileInfo.value.import_id = response.data.id;
|
||||
uploadedFileInfo.value.created_at = response.data.created_at;
|
||||
uploadedFileInfo.value.is_processing = response.data.status.includes('in_progress')
|
||||
uploadedFileInfo.value.house_name_to_id = response.data.house_name_to_id
|
||||
uploadedFileInfo.value.dynamic_fields = response.data.dynamic_fields
|
||||
|
||||
router.replace({ query: { import_id: inputData.id } })
|
||||
activeItem.value = inputData.step
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
const add_house = (house_id) => {
|
||||
api.getAllHouses()
|
||||
.then((response) => {
|
||||
if (keyToUseWhileAddingHouse.value) {
|
||||
uploadedFileInfo.value.house_name_to_id[keyToUseWhileAddingHouse.value] = house_id
|
||||
} else {
|
||||
uploadedFileInfo.value.house_name_to_id = house_id
|
||||
}
|
||||
|
||||
housesList.value = response.data
|
||||
|
||||
isCreateHomeOpen.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const isCreateHomeOpen = ref(false)
|
||||
const formAddHome = ref(null)
|
||||
const activeItem = ref(1);
|
||||
const housesList = ref([])
|
||||
const stepItems = [
|
||||
{
|
||||
id: 1,
|
||||
text: "Загрузка файла",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
text: "Сопоставление полей",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
text: "Объекты",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
text: "Статусы и типы",
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
text: "Завершение",
|
||||
},
|
||||
];
|
||||
|
||||
onMounted(() => {
|
||||
api.getAllHouses()
|
||||
.then((response) => {
|
||||
housesList.value = response.data
|
||||
})
|
||||
|
||||
if (route.query.import_id) {
|
||||
api.getHouseImport(route.query.import_id).then((importData) => {
|
||||
if (!importData.data.id) {
|
||||
router.replace({ query: {} });
|
||||
} else {
|
||||
uploadedFileInfo.value.body = importData.data.document_preview.body.map((data, index) => ({
|
||||
items: data,
|
||||
initialIndex: index
|
||||
}))
|
||||
uploadedFileInfo.value.head = importData.data.document_preview.head
|
||||
uploadedFileInfo.value.import_id = importData.data.id
|
||||
uploadedFileInfo.value.step = importData.data.step
|
||||
uploadedFileInfo.value.created_at = importData.data.created_at
|
||||
uploadedFileInfo.value.mapping_fields = importData.data.mapping_fields
|
||||
uploadedFileInfo.value.is_processing = importData.data.status.includes('in_progress')
|
||||
uploadedFileInfo.value.house_name_to_id = importData.data.house_name_to_id
|
||||
uploadedFileInfo.value.dynamic_fields = importData.data.dynamic_fields
|
||||
activeItem.value = importData.data.step
|
||||
}
|
||||
|
||||
if (importData.data.status == 'done' && importData.data.step == 5) {
|
||||
router.push('/my-objects')
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => uploadedFileInfo.value.is_processing, () => {
|
||||
if (route.query.import_id && uploadedFileInfo.value.is_processing) {
|
||||
const interval = setInterval(() => {
|
||||
api.getHouseImportStatus(route.query.import_id).then((response) => {
|
||||
if (response.data.status.includes('done')) {
|
||||
clearInterval(interval)
|
||||
api.getHouseImport(route.query.import_id)
|
||||
.then((importData) => {
|
||||
uploadedFileInfo.value.import_id = importData.data.id
|
||||
uploadedFileInfo.value.step = importData.data.step
|
||||
uploadedFileInfo.value.created_at = importData.data.created_at
|
||||
uploadedFileInfo.value.mapping_fields = importData.data.mapping_fields
|
||||
uploadedFileInfo.value.is_processing = importData.data.status.includes('in_progress')
|
||||
uploadedFileInfo.value.house_name_to_id = importData.data.house_name_to_id
|
||||
uploadedFileInfo.value.dynamic_fields = importData.data.dynamic_fields
|
||||
activeItem.value = importData.data.step
|
||||
|
||||
if (importData.data.status == 'done' && importData.data.step == 5) {
|
||||
toast.add({ severity: 'success', summary: 'Импорт заверщен', detail: 'Импорт помещений успешно завершен', life: 3000 });
|
||||
router.push('/my-objects')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
uploadedFileInfo.value.is_processing = !response.data.status || response.data.status.includes('in_progress')
|
||||
}
|
||||
})
|
||||
}, 2000)
|
||||
}
|
||||
})
|
||||
|
||||
const updateImport = (fields) => {
|
||||
isRequesting.value = true
|
||||
api.updateHouseImport(route.query.import_id, fields)
|
||||
.then(() => {
|
||||
api.getHouseImportStatus(route.query.import_id)
|
||||
.then((response) => {
|
||||
uploadedFileInfo.value.is_processing = response.data.status.includes('in_progress')
|
||||
|
||||
activeItem.value = response.data.step
|
||||
});
|
||||
}).finally(() => isRequesting.value = false)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.importHome {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
padding: 30px;
|
||||
overflow-y: scroll;
|
||||
|
||||
&__title {
|
||||
font-size: 32px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
32
src/pages/MainPage.vue
Normal file
32
src/pages/MainPage.vue
Normal file
@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<template v-if="route.name != 'archive-main'">
|
||||
<div v-if="loaders.list" class="loader-div">
|
||||
<RocketSpinner/>
|
||||
</div>
|
||||
<ListItems v-else />
|
||||
</template>
|
||||
<template v-else>
|
||||
<div v-if="loaders.archivedList" class="loader-div">
|
||||
<RocketSpinner/>
|
||||
</div>
|
||||
<ArchivedListItems v-else />
|
||||
</template>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useComplexStore } from "@/app/store/complex";
|
||||
import ArchivedListItems from "@/widgets/ArchivedListItems.vue";
|
||||
import ListItems from "@/widgets/ListItems.vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import RocketSpinner from "@/components/RocketSpinner.vue";
|
||||
|
||||
const { loaders } = useComplexStore()
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
</script>
|
||||
<style lang="scss">
|
||||
|
||||
</style>
|
||||
|
||||
9
src/pages/View/index.vue
Normal file
9
src/pages/View/index.vue
Normal file
@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<ViewLayout>
|
||||
<RouterView></RouterView>
|
||||
</ViewLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ViewLayout from "@/shared/layouts/ViewLayout.vue";
|
||||
</script>
|
||||
51
src/shared/UI/MyButton.vue
Normal file
51
src/shared/UI/MyButton.vue
Normal file
@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<button :disabled="disabled" :class="['btn', theme]"><slot /></button>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props =defineProps({
|
||||
theme: "green" | "grey-icon",
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.btn {
|
||||
width: auto;
|
||||
height: auto;
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
column-gap: 6px;
|
||||
|
||||
&.green {
|
||||
@apply bg-green;
|
||||
@apply text-white;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
&.brilliant_azure {
|
||||
@apply bg-blue;
|
||||
@apply text-white;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
&.grey-icon {
|
||||
border-radius: 3px;
|
||||
background: linear-gradient(180deg, #fefefe 0%, #f6f6f6 100%);
|
||||
@apply border;
|
||||
@apply border-grey-400;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
@apply bg-grey-400;
|
||||
@apply text-grey-900;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
208
src/shared/UI/fileUpload.vue
Normal file
208
src/shared/UI/fileUpload.vue
Normal file
@ -0,0 +1,208 @@
|
||||
<template>
|
||||
<div
|
||||
class="fileUpload"
|
||||
@dragover="dragover"
|
||||
@dragleave="dragleave"
|
||||
@drop="(e) => drop(e)"
|
||||
>
|
||||
<input
|
||||
class="hidden"
|
||||
type="file"
|
||||
id="fileInput"
|
||||
name="file"
|
||||
ref="fileInput"
|
||||
@change="(e) => onChange()"
|
||||
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
|
||||
/>
|
||||
<div class="filePreview" v-if="file">
|
||||
<div class="file">
|
||||
<button class="file__delete" @click="deleteFile()">
|
||||
<svg
|
||||
width="10"
|
||||
height="10"
|
||||
viewBox="0 0 10 10"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6.05745 4.99999L9.28245 1.78249C9.42368 1.64126 9.50302 1.44971 9.50302 1.24999C9.50302 1.05026 9.42368 0.858714 9.28245 0.717487C9.14123 0.576259 8.94968 0.496918 8.74995 0.496918C8.55023 0.496918 8.35868 0.576259 8.21745 0.717487L4.99995 3.94249L1.78245 0.717487C1.64123 0.576259 1.44968 0.496918 1.24995 0.496918C1.05023 0.496918 0.858682 0.576259 0.717454 0.717487C0.576226 0.858714 0.496885 1.05026 0.496885 1.24999C0.496885 1.44971 0.576226 1.64126 0.717454 1.78249L3.94245 4.99999L0.717454 8.21749C0.647158 8.28721 0.591362 8.37016 0.553286 8.46155C0.515209 8.55295 0.495605 8.65098 0.495605 8.74999C0.495605 8.849 0.515209 8.94703 0.553286 9.03842C0.591362 9.12981 0.647158 9.21276 0.717454 9.28249C0.787176 9.35278 0.870127 9.40858 0.961522 9.44666C1.05292 9.48473 1.15095 9.50434 1.24995 9.50434C1.34896 9.50434 1.44699 9.48473 1.53839 9.44666C1.62978 9.40858 1.71273 9.35278 1.78245 9.28249L4.99995 6.05749L8.21745 9.28249C8.28718 9.35278 8.37013 9.40858 8.46152 9.44666C8.55292 9.48473 8.65095 9.50434 8.74995 9.50434C8.84896 9.50434 8.94699 9.48473 9.03839 9.44666C9.12978 9.40858 9.21273 9.35278 9.28245 9.28249C9.35275 9.21276 9.40855 9.12981 9.44662 9.03842C9.4847 8.94703 9.5043 8.849 9.5043 8.74999C9.5043 8.65098 9.4847 8.55295 9.44662 8.46155C9.40855 8.37016 9.35275 8.28721 9.28245 8.21749L6.05745 4.99999Z"
|
||||
fill="#666666"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="file__icon"></div>
|
||||
<div class="file__name">{{ file ? file.name : "" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<label for="fileInput" class="label" v-else>
|
||||
<div v-if="isDragging">Отпустите, чтобы удалить файлы.</div>
|
||||
<div
|
||||
v-else
|
||||
class="flex flex-col justify-center items-center text-grey-900 gap-2 text-sm"
|
||||
>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.71 7.71L11 5.41V15C11 15.2652 11.1054 15.5196 11.2929 15.7071C11.4804 15.8946 11.7348 16 12 16C12.2652 16 12.5196 15.8946 12.7071 15.7071C12.8946 15.5196 13 15.2652 13 15V5.41L15.29 7.71C15.383 7.80373 15.4936 7.87813 15.6154 7.92889C15.7373 7.97966 15.868 8.0058 16 8.0058C16.132 8.0058 16.2627 7.97966 16.3846 7.92889C16.5064 7.87813 16.617 7.80373 16.71 7.71C16.8037 7.61704 16.8781 7.50644 16.9289 7.38458C16.9797 7.26272 17.0058 7.13202 17.0058 7C17.0058 6.86799 16.9797 6.73729 16.9289 6.61543C16.8781 6.49357 16.8037 6.38297 16.71 6.29L12.71 2.29C12.6149 2.19896 12.5028 2.1276 12.38 2.08C12.1365 1.97999 11.8635 1.97999 11.62 2.08C11.4973 2.1276 11.3851 2.19896 11.29 2.29L7.29 6.29C7.19676 6.38324 7.1228 6.49393 7.07234 6.61575C7.02188 6.73758 6.99591 6.86814 6.99591 7C6.99591 7.13186 7.02188 7.26243 7.07234 7.38425C7.1228 7.50607 7.19676 7.61677 7.29 7.71C7.38324 7.80324 7.49393 7.8772 7.61575 7.92766C7.73757 7.97812 7.86814 8.00409 8 8.00409C8.13186 8.00409 8.26243 7.97812 8.38425 7.92766C8.50607 7.8772 8.61676 7.80324 8.71 7.71ZM21 12C20.7348 12 20.4804 12.1054 20.2929 12.2929C20.1054 12.4804 20 12.7348 20 13V19C20 19.2652 19.8946 19.5196 19.7071 19.7071C19.5196 19.8946 19.2652 20 19 20H5C4.73478 20 4.48043 19.8946 4.29289 19.7071C4.10536 19.5196 4 19.2652 4 19V13C4 12.7348 3.89464 12.4804 3.70711 12.2929C3.51957 12.1054 3.26522 12 3 12C2.73478 12 2.48043 12.1054 2.29289 12.2929C2.10536 12.4804 2 12.7348 2 13V19C2 19.7957 2.31607 20.5587 2.87868 21.1213C3.44129 21.6839 4.20435 22 5 22H19C19.7957 22 20.5587 21.6839 21.1213 21.1213C21.6839 20.5587 22 19.7957 22 19V13C22 12.7348 21.8946 12.4804 21.7071 12.2929C21.5196 12.1054 21.2652 12 21 12Z"
|
||||
fill="#4CAF50"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<strong class="text-black text-base"
|
||||
>Нажмите на эту область или перенесите в нее файл.</strong
|
||||
>
|
||||
Допустимые расширения файла: .xls, .xlsx, .csv, .xml
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
|
||||
const emits = defineEmits([
|
||||
"updateModel",
|
||||
"done:comparison-prepared"
|
||||
]);
|
||||
|
||||
const isDragging = ref(false);
|
||||
const file = ref(File | null);
|
||||
const fileInput = ref(HTMLInputElement);
|
||||
|
||||
function csvToJson(csvString) {
|
||||
const rows = csvString
|
||||
.split("\n");
|
||||
|
||||
const headers = rows[0]
|
||||
.split(",");
|
||||
|
||||
const jsonData = [];
|
||||
for (let i = 1; i < rows.length; i++) {
|
||||
|
||||
const values = rows[i]
|
||||
.split(",");
|
||||
|
||||
const obj = {};
|
||||
|
||||
for (let j = 0; j < headers.length; j++) {
|
||||
|
||||
const key = headers[j].trim();
|
||||
const value = values[j] ? values[j].trim() : values[j];
|
||||
|
||||
obj[key] = value;
|
||||
}
|
||||
|
||||
jsonData.push(obj);
|
||||
}
|
||||
return jsonData;
|
||||
}
|
||||
|
||||
function XLSXToJson(xlsxString) {
|
||||
var workbook = XLSX.read(xlsxString, {
|
||||
type: 'binary'
|
||||
});
|
||||
|
||||
return XLSX.utils.sheet_to_row_object_array(workbook.Sheets[workbook.SheetNames[0]])
|
||||
}
|
||||
|
||||
function getFileNameWithExt(file) {
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
const name = file.name;
|
||||
const lastDot = name.lastIndexOf('.');
|
||||
|
||||
const fileName = name.substring(0, lastDot);
|
||||
const ext = name.substring(lastDot + 1);
|
||||
|
||||
return { fileName, extension: ext }
|
||||
|
||||
}
|
||||
|
||||
|
||||
const onChange = (event) => {
|
||||
if (fileInput.value) {
|
||||
if (fileInput.value.files) {
|
||||
file.value = fileInput.value.files[0];
|
||||
|
||||
if (file.value.size < 2097152) {
|
||||
const fileNameInfo = getFileNameWithExt(file.value);
|
||||
|
||||
if (['xms', 'xlsx', 'csv'].includes(fileNameInfo.extension)) {
|
||||
emits("updateModel", file.value);
|
||||
|
||||
}
|
||||
} else {
|
||||
file.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const dragover = (e) => {
|
||||
e.preventDefault();
|
||||
isDragging.value = true;
|
||||
};
|
||||
const dragleave = () => {
|
||||
isDragging.value = false;
|
||||
};
|
||||
const drop = (e) => {
|
||||
e.preventDefault();
|
||||
if (fileInput.value && e.dataTransfer) {
|
||||
fileInput.value.files = e.dataTransfer.files;
|
||||
onChange();
|
||||
isDragging.value = false;
|
||||
}
|
||||
};
|
||||
const deleteFile = () => {
|
||||
file.value = null;
|
||||
emits("updateModel", null);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.fileUpload {
|
||||
border-radius: 3px;
|
||||
border: 1px dashed #dedede;
|
||||
background: var(--linear, linear-gradient(180deg, #fefefe 0%, #f6f6f6 100%));
|
||||
padding: 30px;
|
||||
& * {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.filePreview {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
word-break: break-all;
|
||||
align-items: center;
|
||||
.file {
|
||||
position: relative;
|
||||
width: 50%;
|
||||
border: 1px dashed #dedede;
|
||||
height: 140px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
&__delete {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
}
|
||||
&__name {
|
||||
margin-top: auto;
|
||||
margin-bottom: 10px;
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
50
src/shared/UI/formItemLayout.vue
Normal file
50
src/shared/UI/formItemLayout.vue
Normal file
@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<div class="itemLayout">
|
||||
<label class="itemLayout__label">{{ label }}</label>
|
||||
<div
|
||||
class="itemLayout__content"
|
||||
:class="direction === 'column' ? 'flex-col' : 'flex-row'"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
label: string;
|
||||
direction?: string;
|
||||
}>(),
|
||||
{
|
||||
direction: "column",
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.itemLayout {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 468px;
|
||||
grid-column-gap: 30px;
|
||||
align-items: flex-start;
|
||||
|
||||
&__label {
|
||||
@apply text-grey-900;
|
||||
font-size: 14px;
|
||||
line-height: 100%;
|
||||
margin-top: 12px;
|
||||
}
|
||||
&__content {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
row-gap: 20px;
|
||||
column-gap: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
107
src/shared/UI/mapsModalLayout.vue
Normal file
107
src/shared/UI/mapsModalLayout.vue
Normal file
@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<div class="modalWindow" ref="modalWindowEl">
|
||||
<div class="modalWindow__wrapper" :class="wrapperClass">
|
||||
<div class="modalWindow__title">{{ props.title }}</div>
|
||||
<!-- <button class="modalWindow__close" @click="$emit('closeModel')">
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M0.93511 18C0.698006 18 0.460901 17.9099 0.280756 17.7284C-0.0808901 17.3667 -0.0808901 16.7804 0.280756 16.4187L16.4283 0.271235C16.7899 -0.0904116 17.3762 -0.0904116 17.7379 0.271235C18.0995 0.632881 18.0995 1.2192 17.7379 1.58107L1.59059 17.7284C1.40909 17.9087 1.17199 18 0.93511 18Z"
|
||||
fill="#757579"
|
||||
/>
|
||||
<path
|
||||
d="M17.0838 18C16.8466 18 16.6098 17.9099 16.4294 17.7284L0.280756 1.58107C-0.0808901 1.2192 -0.0808901 0.632881 0.280756 0.271235C0.642403 -0.0904116 1.22872 -0.0904116 1.59059 0.271235L17.7379 16.4187C18.0995 16.7804 18.0995 17.3667 17.7379 17.7284C17.5564 17.9087 17.3195 18 17.0838 18Z"
|
||||
fill="#757579"
|
||||
/>
|
||||
</svg>
|
||||
</button> -->
|
||||
<slot />
|
||||
<slot name="formBtns" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
|
||||
const emits = defineEmits(["closeModel"]);
|
||||
const props = defineProps ({
|
||||
title: String,
|
||||
wrapperClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
})
|
||||
|
||||
const modalWindowEl = ref(HTMLElement);
|
||||
|
||||
document.addEventListener("click", (e) => {
|
||||
if (e.target === modalWindowEl.value) {
|
||||
emits("closeModel");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.modalWindow {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: 0.3s all ease-in-out;
|
||||
|
||||
&.open {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transition: 0.3s all ease-in-out;
|
||||
}
|
||||
|
||||
&__wrapper {
|
||||
position: relative;
|
||||
width: 500px;
|
||||
height: 450px;
|
||||
@apply bg-white;
|
||||
padding: 30px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 28px;
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
&__close {
|
||||
position: absolute;
|
||||
right: 30px;
|
||||
top: 30px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
123
src/shared/UI/modalLayout.vue
Normal file
123
src/shared/UI/modalLayout.vue
Normal file
@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<div class="modalWindow" ref="modalWindowEl">
|
||||
<div class="modalWindow__wrapper" :class="wrapperClass">
|
||||
<div v-if="has_restricted_access" class="warning_msg">
|
||||
Вы не сможете добавить комплекс, так как у вас не оплачен тариф
|
||||
</div>
|
||||
<div class="modalWindow__title">{{ props.title }}</div>
|
||||
<button class="modalWindow__close" @click="$emit('closeModel')">
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M0.93511 18C0.698006 18 0.460901 17.9099 0.280756 17.7284C-0.0808901 17.3667 -0.0808901 16.7804 0.280756 16.4187L16.4283 0.271235C16.7899 -0.0904116 17.3762 -0.0904116 17.7379 0.271235C18.0995 0.632881 18.0995 1.2192 17.7379 1.58107L1.59059 17.7284C1.40909 17.9087 1.17199 18 0.93511 18Z"
|
||||
fill="#757579"
|
||||
/>
|
||||
<path
|
||||
d="M17.0838 18C16.8466 18 16.6098 17.9099 16.4294 17.7284L0.280756 1.58107C-0.0808901 1.2192 -0.0808901 0.632881 0.280756 0.271235C0.642403 -0.0904116 1.22872 -0.0904116 1.59059 0.271235L17.7379 16.4187C18.0995 16.7804 18.0995 17.3667 17.7379 17.7284C17.5564 17.9087 17.3195 18 17.0838 18Z"
|
||||
fill="#757579"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<slot />
|
||||
<slot name="formBtns" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
|
||||
const emits = defineEmits(["closeModel"]);
|
||||
const props = defineProps ({
|
||||
title: String,
|
||||
wrapperClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
})
|
||||
|
||||
const has_restricted_access = ref(!!Number(document.getElementById('has_restricted_access')?.value))
|
||||
const hasDeveloper = ref(!!Number(document.getElementById('has_developer')?.value))
|
||||
|
||||
const modalWindowEl = ref(HTMLElement);
|
||||
|
||||
document.addEventListener("click", (e) => {
|
||||
if (e.target === modalWindowEl.value) {
|
||||
emits("closeModel");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.modalWindow {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: 0.3s all ease-in-out;
|
||||
|
||||
&.open {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transition: 0.3s all ease-in-out;
|
||||
}
|
||||
|
||||
&__wrapper {
|
||||
position: relative;
|
||||
width: 760px;
|
||||
height: 80vh;
|
||||
@apply bg-white;
|
||||
padding: 30px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 28px;
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
&__close {
|
||||
position: absolute;
|
||||
right: 30px;
|
||||
top: 30px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.warning_msg
|
||||
{
|
||||
background-color: #f2dede;
|
||||
border: 1px solid #ebcccc;
|
||||
border-radius: 4px;
|
||||
color: #a94442;
|
||||
padding: 10px 20px 12px;
|
||||
margin-bottom: 30px;
|
||||
font-size: 16px;
|
||||
font-family: "Lato";
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
234
src/shared/UI/requestModal.vue
Normal file
234
src/shared/UI/requestModal.vue
Normal file
@ -0,0 +1,234 @@
|
||||
<template>
|
||||
<div v-if="isOpen" class="modal-overlay" @click.self="$emit('close')">
|
||||
<div class="modal-content">
|
||||
<div v-if="openCongMess" class="infoModal">
|
||||
<div class="infoModal-content">
|
||||
<p>Ваше заявка отправлено</p>
|
||||
<p>Скоро с вами свяжется наш менеджер!</p>
|
||||
</div>
|
||||
</div>
|
||||
<span class="request-title">Забронировать</span>
|
||||
<div class="wrapper">
|
||||
<div class="request-row">
|
||||
<slot name="name-input">
|
||||
<InputText
|
||||
id="fullName"
|
||||
v-model="fullName"
|
||||
@input="formatFullName"
|
||||
placeholder="Введите ваше имя"
|
||||
:class="errors.fullName ? 'input-error' : ''"
|
||||
/>
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
<div class="request-row">
|
||||
<slot name="phone-input">
|
||||
<InputMask
|
||||
id="phone"
|
||||
v-model="phoneNumber"
|
||||
mask="+7 (999) 999-9999"
|
||||
placeholder="+7 (999) 999-99-99 "
|
||||
:class="errors.phoneNumber ? 'input-error' : ''"
|
||||
/>
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
<button class="close-req-btn" @click="$emit('close')">
|
||||
<svg width="20" height="20" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M22.8281 20L31.4141 11.4141C32.1953 10.6328 32.1953 9.36719 31.4141 8.58594C30.6328 7.80469 29.3672 7.80469 28.5859 8.58594L20 17.1719L11.4141 8.58594C10.6328 7.80469 9.36719 7.80469 8.58594 8.58594C7.80469 9.36719 7.80469 10.6328 8.58594 11.4141L17.1719 20L8.58594 28.5859C7.80469 29.3672 7.80469 30.6328 8.58594 31.4141C8.97656 31.8047 9.48828 32 10 32C10.5117 32 11.0234 31.8047 11.4141 31.4141L20 22.8281L28.5859 31.4141C28.9766 31.8047 29.4883 32 30 32C30.5117 32 31.0234 31.8047 31.4141 31.4141C32.1953 30.6328 32.1953 29.3672 31.4141 28.5859L22.8281 20Z" fill="#757575"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="primary-btn" @click="submitForm">Создать заявку</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import InputText from 'primevue/inputtext';
|
||||
import InputMask from 'primevue/inputmask';
|
||||
import api from "@/shared/api";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
isOpen: Boolean,
|
||||
info: {
|
||||
type: Object,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
components: {
|
||||
InputText,
|
||||
InputMask,
|
||||
},
|
||||
emits: ["close"],
|
||||
setup(props, { emit }) {
|
||||
const fullName = ref("");
|
||||
const phoneNumber = ref("");
|
||||
const errors = ref({ fullName: false, phoneNumber: false });
|
||||
const router = useRoute();
|
||||
const token = router.params.token || 'c5c567a2b70a0e1a83ae0c30e1ed677b';
|
||||
const openCongMess = ref(false);
|
||||
const formatFullName = (event) => {
|
||||
let value = event.target.value;
|
||||
value = value.replace(/[^А-Яа-яЁё\s-]/g, " ");
|
||||
value = value
|
||||
.split(/\s+/)
|
||||
.map((word) => (word ? word.charAt(0).toUpperCase() + word.slice(1).toLowerCase() : ""))
|
||||
.join(" ");
|
||||
fullName.value = value;
|
||||
};
|
||||
|
||||
const validateForm = () => {
|
||||
errors.value.fullName = fullName.value.trim().split(" ").length < 1;
|
||||
errors.value.phoneNumber = !/^\+7 \(\d{3}\) \d{3}-\d{4}$/.test(phoneNumber.value);
|
||||
return !errors.value.fullName && !errors.value.phoneNumber;
|
||||
};
|
||||
|
||||
const submitForm = async () => {
|
||||
if (!validateForm()) return;
|
||||
|
||||
const formData = new FormData();
|
||||
// const formatedPhoneNumber = phoneNumber.replace(/\D/g, "").replace(/^7/, "7");
|
||||
const formatedPhoneNumber = "+" + String(phoneNumber.value || "").replace(/\D/g, "").replace(/^7/, "7");
|
||||
console.log(formatedPhoneNumber);
|
||||
formData.append("token", token);
|
||||
formData.append("full_name", fullName.value);
|
||||
formData.append("phone_number", formatedPhoneNumber);
|
||||
formData.append("complex_name", props.info.complexName);
|
||||
formData.append("house_name", props.info.houseName);
|
||||
formData.append("entrance_name", props.info.entranceName);
|
||||
formData.append("room_number", props.info.roomNumber);
|
||||
|
||||
try {
|
||||
const response = await api.createRequisition(formData);
|
||||
console.log("Ответ API:", response);
|
||||
openCongMess.value = true;
|
||||
setTimeout(() => {
|
||||
emit('close')
|
||||
openCongMess.value = false;
|
||||
}, 3000);
|
||||
fullName.value = "";
|
||||
phoneNumber.value = "";
|
||||
} catch (error) {
|
||||
console.error("Ошибка при отправке заявки:", error);
|
||||
}
|
||||
|
||||
};
|
||||
return { fullName, phoneNumber, formatFullName, submitForm, errors ,openCongMess };
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
position: relative;
|
||||
width: 600px;
|
||||
height: 350px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 25px;
|
||||
align-items: center;
|
||||
}
|
||||
.request-title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 90%;
|
||||
|
||||
}
|
||||
.request-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
align-items: center;
|
||||
gap:20px;
|
||||
input{
|
||||
width: 100%;
|
||||
height: 55px;
|
||||
}
|
||||
}
|
||||
|
||||
.close-req-btn {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.input-error {
|
||||
border: 1px solid red;
|
||||
}
|
||||
|
||||
.infoModal{
|
||||
background: #ffffff;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
height: 80%;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.infoModal-content{
|
||||
width: 100%;
|
||||
height: 80%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
font-size: 21px;
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: 768px ) {
|
||||
.modal-content{
|
||||
width: 480px;
|
||||
gap: 30px;
|
||||
.request-title{
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 500px ) {
|
||||
.modal-content{
|
||||
gap: 10px;
|
||||
width: 280px;
|
||||
text-align: center;
|
||||
input{
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
.infoModal-content{
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
287
src/shared/api.js
Normal file
287
src/shared/api.js
Normal file
@ -0,0 +1,287 @@
|
||||
import axios from "axios"
|
||||
// const URL = process.env.VUE_APP_API_URL;
|
||||
// const URL = 'https://joywork.ru';
|
||||
// const URL = 'http://localhost:8080';
|
||||
// const URL = 'http://localhost';
|
||||
const URL = 'https://dev.joywork.ru';
|
||||
|
||||
|
||||
// console.log(URL, 'URL URL');
|
||||
|
||||
const api_instance = axios.create({
|
||||
baseURL: '/ajax/vue_ajax/complex_axios.php',
|
||||
});
|
||||
|
||||
// const api_instance = axios.create({
|
||||
// baseURL: `${URL}/ajax/vue_ajax/complex_axios.php`,
|
||||
// // withCredentials: true
|
||||
// })
|
||||
|
||||
api_instance.defaults.headers.common['Access-Control-Allow-Origin'] = '*';
|
||||
|
||||
const get_user_id = () => {
|
||||
if (window.auth_user_id) {
|
||||
return window.auth_user_id
|
||||
}
|
||||
console.log(window.auth_user_id, 'window.auth_user_id');
|
||||
|
||||
window.auth_user_id = document.getElementById('session_user_id').value
|
||||
return window.auth_user_id
|
||||
}
|
||||
|
||||
const callApi = (request, params = {}) => {
|
||||
return api_instance.post('', {
|
||||
request,
|
||||
user_id: get_user_id(),
|
||||
...params
|
||||
})
|
||||
}
|
||||
|
||||
const callWithFormData = (request, formData) => {
|
||||
formData.append("request", request);
|
||||
formData.append("user_id", get_user_id());
|
||||
return api_instance.post('', formData, {
|
||||
headers: { "Content-Type": "multipart/form-data" },
|
||||
})
|
||||
}
|
||||
|
||||
// Entrances
|
||||
const getHouseEntrances = (house_id, includes = []) => callApi('get_house_entrances', {includes, house_id})
|
||||
|
||||
const createEntrance = (house_id, fields ) => callApi('add_house_entrance', {house_id, fields})
|
||||
|
||||
const updateEntrance = (entrance_id, fields) => callApi('update_house_entrance', {entrance_id, fields})
|
||||
|
||||
const deleteEntrance = (entrance_id) => callApi('delete_house_entrance', {entrance_id})
|
||||
|
||||
const getComplexEntrances = (complex_id, includes = []) => callApi('get_complex_entrances', {includes, complex_id})
|
||||
|
||||
// Complexes
|
||||
const getComplexOne = (complex_id, object_list = false) => callApi('get_complex_one', { complex_id, object_list})
|
||||
|
||||
const getComplex = (country_id, region_id) => callApi('get_complex', {country_id, region_id})
|
||||
const getComplexStatus = () => callApi('get_complex_status')
|
||||
const searchComplexes = (filter = {}, page, use_empty_rooms) => callApi('search_for_iframe', {filter, page, use_empty_rooms})
|
||||
const getComplexTypes = () => callApi('get_types_complex')
|
||||
const saveComplex = (complex, newBuilderPhone, newBuilderLink) => callApi('save_complex', {complex, newBuilderPhone, newBuilderLink })
|
||||
const archiveComplex = (payload) => callApi('complex_archive', payload)
|
||||
const deleteComplex = (complex_id) => callApi('complex_delete', { complex_id })
|
||||
|
||||
const addPhotoToComplex = (formData) => callWithFormData('add_photos_to_complex', formData)
|
||||
const updateComplexPhoto = (formData) => callWithFormData('update_complex_photo', formData)
|
||||
const deleteComplexPhoto = (photo_id) => callApi('delete_complex_photo', {photo_id})
|
||||
// Houses
|
||||
const getComplexHouses = (complex_id) => callApi('get_houses', {complex_id})
|
||||
|
||||
const getHouseOne = (house_id, includes = []) => callApi('get_house_one', {house_id, includes})
|
||||
|
||||
const getHouseTypes = () => callApi('get_house_types')
|
||||
|
||||
const addEditHouse = (house) => callApi('add_edit_house', {house})
|
||||
|
||||
const addPhotoToHouse = (formData) => callWithFormData('add_photos_to_house', formData)
|
||||
const deleteHousePhoto = (photo_id) => callApi('delete_house_photo', {photo_id})
|
||||
// Floors
|
||||
const createFloor = (entrance_id, fields) => callApi('create_floor', {fields, entrance_id})
|
||||
const updateFloor = (floor_id, fields) => callApi('update_floor', {floor_id, fields})
|
||||
const deleteFloor = (floor_id) => callApi('delete_floor', {floor_id})
|
||||
const copyFloor = (floor_id, position) => callApi('copy_floor', {floor_id, position})
|
||||
|
||||
// Floor Plans
|
||||
const getFloorPlans = (house_id) => callApi('get_house_floors_plans', {house_id})
|
||||
const getFloorPlan = (floor_plan_id) => callApi('get_floor_plan', {floor_plan_id})
|
||||
const getHouseFloorPlans = (house_id) => callApi('get_entrances_floor_plans', {house_id})
|
||||
const updateFloorPlans = (plan_ids = [], floor_ids = [], fields, use_bulk = false) => callApi('update_house_floor_plans', {plan_ids, floor_ids, fields, use_bulk})
|
||||
const deleteFloorPlan = (floor_plan_id) => callApi('delete_floor_plan', {floor_plan_id})
|
||||
const addFloorPlan = (formData) => callWithFormData('add_floor_plan_to_house', formData)
|
||||
const updateFloorPlanPhoto = (formData) => callWithFormData('update_photo_floor_plan', formData)
|
||||
// Floor Plan Figures
|
||||
const deleteFloorPlanFigure = (figure_id) => callApi('delete_floor_plan_figure', {figure_id})
|
||||
|
||||
const addFigureToFloorPlan = (floor_plan_id, fields) => callApi('add_floor_plan_figure', {floor_plan_id, fields})
|
||||
|
||||
const updateFigure = (figure_id, fields) => callApi('update_floor_plan_figure', {figure_id, fields})
|
||||
|
||||
// Facades
|
||||
const getFacades = (house_id, includes = []) => callApi('get_house_facades', {house_id, includes})
|
||||
|
||||
const deleteHouseFacade = (facade_id) => callApi('delete_house_facade', {facade_id})
|
||||
|
||||
const addFacadeToHouse = (formData) => callWithFormData('add_facade_to_house', formData)
|
||||
|
||||
const updateFacade = (facade_id, fields) => callApi('update_house_facade', {facade_id, fields})
|
||||
|
||||
// Facade Figures
|
||||
const addFigureToFacade = (facade_id, points) => callApi('add_facade_figure', {facade_id, points})
|
||||
const deleteFacadeFigure = (figure_id) => callApi('delete_facade_figure', {figure_id})
|
||||
const updateFacadeFigure = (figure_id, fields) => callApi('update_facade_figure', {figure_id, fields})
|
||||
|
||||
// Rooms
|
||||
const updateRoom = (room) => callApi('update_room', {room})
|
||||
const saveRoom = (room) => callApi('save_room', {room})
|
||||
const get_room_entrance = (id) => callApi('get_room_entrance', {id});
|
||||
const deleteRoom = (room_id) => callApi('delete_room', {room_id})
|
||||
const copyRoom = (room_id, {floor_id, position, copies_count, floors_with_position }) => callApi('copy_room', {room_id, floor_id, position, copies_count, floors_with_position})
|
||||
const getRoomRequisition = (id) => callApi('get_room_requisition', {id});
|
||||
|
||||
// Room Plans
|
||||
const createRoomPlan = (formData) => callWithFormData('add_plan_to_house', formData)
|
||||
const connectRoomsToPlan = (room_ids, plan_id) => callApi('update_rooms_plan_link', {room_ids, plan_id})
|
||||
const sendRoomsToAdvert = (room_ids, house_id, type) => callApi('send_rooms_to_advert', {room_ids, house_id, type})
|
||||
const deleteRoomPlans = (plan_ids) => callApi('delete_house_plans', {plan_ids})
|
||||
|
||||
const getHousePlans = (house_id) => callApi('get_house_plans', {house_id})
|
||||
|
||||
const updateRoomPlans = (plan_ids, fields) => callApi('update_house_plans', {plan_ids, fields})
|
||||
|
||||
// Room Plan Photos
|
||||
|
||||
const addPhotoRomRoomPlan = (formData) => callWithFormData('add_photos_to_room_plan', formData)
|
||||
const updateRoomPlanPhotoPosition = (image_id, position) => callApi('update_plan_photo_position', {image_id, position})
|
||||
const deleteRoomPlanPhoto = (photo_id) => callApi('delete_photo_from_room_plan', photo_id)
|
||||
|
||||
// Country
|
||||
const getCountry = () => callApi('get_country')
|
||||
|
||||
// Metro
|
||||
|
||||
const getMetro = (region_id) => callApi('get_metro', {region_id})
|
||||
|
||||
// Regions
|
||||
|
||||
const getRFRegions = () => callApi('get_rf_regions')
|
||||
|
||||
// Decorations
|
||||
const getDecorations = () => callApi('get_decorations')
|
||||
|
||||
// Windows placements
|
||||
const getWindowsPlacements = () => callApi('get_windows_placements')
|
||||
|
||||
const deleteHouse = (house_id) => callApi('delete_house', { house_id })
|
||||
|
||||
const getComplexSuggestions = (use_empty_rooms = false) => callApi('get_complex_names_for_iframe', { use_empty_rooms })
|
||||
|
||||
const saveHouseUTP = (formData) => callWithFormData('save_house_utp', formData)
|
||||
|
||||
const saveHouseDecoration = (formData) => callWithFormData('save_house_decoration', formData)
|
||||
|
||||
const deleteHouseUtp = (id) => callApi('delete_house_utp', { id })
|
||||
|
||||
const deleteHouseDecoration = (id) => callApi('delete_house_decoration', { id })
|
||||
|
||||
const saveComplexAction = (formData) => callWithFormData('save_complex_action', formData)
|
||||
|
||||
const deleteComplexAction = (id) => callApi('delete_complex_action', { id })
|
||||
|
||||
const getBuilders = () => callApi('get_builders');
|
||||
|
||||
const getAllHouses = () => callApi('get_all_houses')
|
||||
|
||||
const createImport = (formData) => callWithFormData('create_import', formData);
|
||||
|
||||
const getHouseImport = (import_id) => callApi('get_house_import', { import_id })
|
||||
|
||||
const getHouseImportStatus = (import_id) => callApi('get_house_import_status', { import_id })
|
||||
|
||||
const updateHouseImport = (import_id, fields) => callApi("update_house_import", { import_id, fields })
|
||||
|
||||
const getRoomFigures = (room_id) => callApi('get_room_figures', { room_id })
|
||||
|
||||
const getFilterFields = (house_id) => callApi("get_filter_fields", { house_id })
|
||||
|
||||
// Frame access
|
||||
|
||||
const getFrameAccess = (token) => callApi("get_frame_access", { token })
|
||||
|
||||
// Frame colors
|
||||
|
||||
const getAllColors = (token) => callApi("get_frame_colors", { token })
|
||||
// Create requisition
|
||||
const createRequisition = (formData) => callWithFormData('create_requisition', formData)
|
||||
|
||||
export default {
|
||||
URL,
|
||||
callApi,
|
||||
getHouseEntrances,
|
||||
getComplexEntrances,
|
||||
createEntrance,
|
||||
updateEntrance,
|
||||
getComplexHouses,
|
||||
getComplexOne,
|
||||
searchComplexes,
|
||||
deleteEntrance,
|
||||
updateFloor,
|
||||
getHouseOne,
|
||||
getFloorPlan,
|
||||
updateFloorPlans,
|
||||
deleteFloorPlanFigure,
|
||||
addFigureToFloorPlan,
|
||||
updateFigure,
|
||||
deleteHouseFacade,
|
||||
addFacadeToHouse,
|
||||
updateFacade,
|
||||
getFacades,
|
||||
connectRoomsToPlan,
|
||||
sendRoomsToAdvert,
|
||||
addPhotoToHouse,
|
||||
deleteHousePhoto,
|
||||
getHousePlans,
|
||||
updateRoom,
|
||||
deleteRoom,
|
||||
addFigureToFacade,
|
||||
deleteFacadeFigure,
|
||||
updateFacadeFigure,
|
||||
getComplexTypes,
|
||||
getCountry,
|
||||
getMetro,
|
||||
getRFRegions,
|
||||
saveComplex,
|
||||
archiveComplex,
|
||||
deleteComplex,
|
||||
getComplex,
|
||||
getComplexStatus,
|
||||
getDecorations,
|
||||
getWindowsPlacements,
|
||||
getHouseTypes,
|
||||
addEditHouse,
|
||||
copyRoom,
|
||||
createFloor,
|
||||
deleteFloor,
|
||||
copyFloor,
|
||||
addFloorPlan,
|
||||
getFloorPlans,
|
||||
deleteFloorPlan,
|
||||
getHouseFloorPlans,
|
||||
saveRoom,
|
||||
get_room_entrance,
|
||||
getRoomRequisition,
|
||||
updateFloorPlanPhoto,
|
||||
updateComplexPhoto,
|
||||
deleteComplexPhoto,
|
||||
addPhotoToComplex,
|
||||
createRoomPlan,
|
||||
deleteRoomPlans,
|
||||
updateRoomPlans,
|
||||
addPhotoRomRoomPlan,
|
||||
updateRoomPlanPhotoPosition,
|
||||
deleteRoomPlanPhoto,
|
||||
deleteHouse,
|
||||
getComplexSuggestions,
|
||||
saveHouseUTP,
|
||||
saveHouseDecoration,
|
||||
deleteHouseUtp,
|
||||
deleteHouseDecoration,
|
||||
saveComplexAction,
|
||||
deleteComplexAction,
|
||||
getBuilders,
|
||||
getAllHouses,
|
||||
createImport,
|
||||
getHouseImport,
|
||||
updateHouseImport,
|
||||
getHouseImportStatus,
|
||||
getRoomFigures,
|
||||
getFilterFields,
|
||||
getFrameAccess,
|
||||
getAllColors,
|
||||
createRequisition
|
||||
}
|
||||
BIN
src/shared/assets/fonts/Lato-Black.woff
Normal file
BIN
src/shared/assets/fonts/Lato-Black.woff
Normal file
Binary file not shown.
BIN
src/shared/assets/fonts/Lato-Black.woff2
Normal file
BIN
src/shared/assets/fonts/Lato-Black.woff2
Normal file
Binary file not shown.
BIN
src/shared/assets/fonts/Lato-Bold.woff
Normal file
BIN
src/shared/assets/fonts/Lato-Bold.woff
Normal file
Binary file not shown.
BIN
src/shared/assets/fonts/Lato-Bold.woff2
Normal file
BIN
src/shared/assets/fonts/Lato-Bold.woff2
Normal file
Binary file not shown.
BIN
src/shared/assets/fonts/Lato-ExtraBold.woff
Normal file
BIN
src/shared/assets/fonts/Lato-ExtraBold.woff
Normal file
Binary file not shown.
BIN
src/shared/assets/fonts/Lato-ExtraBold.woff2
Normal file
BIN
src/shared/assets/fonts/Lato-ExtraBold.woff2
Normal file
Binary file not shown.
BIN
src/shared/assets/fonts/Lato-ExtraLight.woff
Normal file
BIN
src/shared/assets/fonts/Lato-ExtraLight.woff
Normal file
Binary file not shown.
BIN
src/shared/assets/fonts/Lato-ExtraLight.woff2
Normal file
BIN
src/shared/assets/fonts/Lato-ExtraLight.woff2
Normal file
Binary file not shown.
BIN
src/shared/assets/fonts/Lato-Light.woff
Normal file
BIN
src/shared/assets/fonts/Lato-Light.woff
Normal file
Binary file not shown.
BIN
src/shared/assets/fonts/Lato-Light.woff2
Normal file
BIN
src/shared/assets/fonts/Lato-Light.woff2
Normal file
Binary file not shown.
BIN
src/shared/assets/fonts/Lato-Medium.woff
Normal file
BIN
src/shared/assets/fonts/Lato-Medium.woff
Normal file
Binary file not shown.
BIN
src/shared/assets/fonts/Lato-Medium.woff2
Normal file
BIN
src/shared/assets/fonts/Lato-Medium.woff2
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user