chenlei před 1 dnem
revize
34403b3149
44 změnil soubory, kde provedl 4018 přidání a 0 odebrání
  1. 36 0
      .gitignore
  2. 3 0
      .vscode/extensions.json
  3. 38 0
      README.md
  4. 13 0
      index.html
  5. 8 0
      jsconfig.json
  6. 34 0
      package.json
  7. 2219 0
      pnpm-lock.yaml
  8. 23 0
      postcss.config.js
  9. binární
      public/favicon.ico
  10. 25 0
      src/App.vue
  11. 7 0
      src/assets/elements.scss
  12. binární
      src/assets/fonts/FZJinLing.TTF
  13. binární
      src/assets/fonts/SOURCEHANSERIFCN-BOLD.OTF
  14. binární
      src/assets/fonts/SourceHanSerifCN-Light.otf
  15. binární
      src/assets/fonts/SourceHanSerifCN-Medium.otf
  16. binární
      src/assets/fonts/SourceHanSerifCN-Regular.otf
  17. binární
      src/assets/fonts/SourceHanSerifCN-SemiBold.otf
  18. binární
      src/assets/images/icon-ditumoshi.png
  19. binární
      src/assets/images/icon-ditumoshi1.png
  20. binární
      src/assets/images/icon-fanhui.png
  21. binární
      src/assets/images/icon-tuwenmoshi.png
  22. binární
      src/assets/images/icon-tuwenmoshi1.png
  23. binární
      src/assets/images/icon-xia.png
  24. binární
      src/assets/images/logo.png
  25. binární
      src/assets/images/mini-map.jpg
  26. binární
      src/assets/images/scene-btn.png
  27. 111 0
      src/assets/main.css
  28. 7 0
      src/assets/utils.scss
  29. binární
      src/components/Sidebar/images/search-bg.png
  30. binární
      src/components/Sidebar/images/search-icon.png
  31. binární
      src/components/Sidebar/images/sidebar-hide-bg.png
  32. binární
      src/components/Sidebar/images/sidebar-hide-icon.png
  33. 231 0
      src/components/Sidebar/index.vue
  34. 81 0
      src/components/Tabbar/index.vue
  35. 0 0
      src/constants.js
  36. 13 0
      src/main.js
  37. 805 0
      src/pages/Home/images/map.svg
  38. 109 0
      src/pages/Home/index.vue
  39. binární
      src/pages/Picture/images/bg.jpg
  40. 172 0
      src/pages/Picture/index.vue
  41. 19 0
      src/router/index.js
  42. 12 0
      src/stores/counter.js
  43. 12 0
      src/stores/sidebar.js
  44. 40 0
      vite.config.js

+ 36 - 0
.gitignore

@@ -0,0 +1,36 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+.DS_Store
+dist
+dist-ssr
+coverage
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+*.tsbuildinfo
+
+.eslintcache
+
+# Cypress
+/cypress/videos/
+/cypress/screenshots/
+
+# Vitest
+__screenshots__/

+ 3 - 0
.vscode/extensions.json

@@ -0,0 +1,3 @@
+{
+  "recommendations": ["Vue.volar"]
+}

+ 38 - 0
README.md

@@ -0,0 +1,38 @@
+# zh-navigation-platform
+
+This template should help get you started developing with Vue 3 in Vite.
+
+## Recommended IDE Setup
+
+[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
+
+## Recommended Browser Setup
+
+- Chromium-based browsers (Chrome, Edge, Brave, etc.):
+  - [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
+  - [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
+- Firefox:
+  - [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
+  - [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)
+
+## Customize configuration
+
+See [Vite Configuration Reference](https://vite.dev/config/).
+
+## Project Setup
+
+```sh
+pnpm install
+```
+
+### Compile and Hot-Reload for Development
+
+```sh
+pnpm dev
+```
+
+### Compile and Minify for Production
+
+```sh
+pnpm build
+```

+ 13 - 0
index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="">
+  <head>
+    <meta charset="UTF-8">
+    <link rel="icon" href="/favicon.ico">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>珠海市历史建筑三维展示与导览平台</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.js"></script>
+  </body>
+</html>

+ 8 - 0
jsconfig.json

@@ -0,0 +1,8 @@
+{
+  "compilerOptions": {
+    "paths": {
+      "@/*": ["./src/*"]
+    }
+  },
+  "exclude": ["node_modules", "dist"]
+}

+ 34 - 0
package.json

@@ -0,0 +1,34 @@
+{
+  "name": "zh-navigation-platform",
+  "version": "0.0.0",
+  "private": true,
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "autoprefixer": "^10.4.23",
+    "element-plus": "^2.13.1",
+    "pinia": "^3.0.4",
+    "postcss-px-to-viewport": "^1.1.1",
+    "svg-pan-zoom": "^3.6.1",
+    "swiper": "^12.1.0",
+    "vue": "^3.5.26",
+    "vue-router": "^4.6.4"
+  },
+  "devDependencies": {
+    "@vitejs/plugin-vue": "^6.0.3",
+    "prettier": "^3.8.1",
+    "prettier-plugin-vue": "^1.1.6",
+    "sass": "^1.97.3",
+    "unplugin-auto-import": "^21.0.0",
+    "unplugin-vue-components": "^31.0.0",
+    "vite": "^7.3.1",
+    "vite-plugin-vue-devtools": "^8.0.5"
+  },
+  "engines": {
+    "node": "^20.19.0 || >=22.12.0"
+  }
+}

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 2219 - 0
pnpm-lock.yaml


+ 23 - 0
postcss.config.js

@@ -0,0 +1,23 @@
+export default {
+  plugins: {
+    autoprefixer: {},
+    "postcss-px-to-viewport": {
+      unitToConvert: "px", // 需要转换的单位,默认为"px"
+      viewportWidth: 1920, // 设计稿的视口宽度
+      unitPrecision: 5, // 单位转换后保留的精度
+      propList: ["*"], // 能转化为vw的属性列表
+      viewportUnit: "vw", // 希望使用的视口单位
+      fontViewportUnit: "vw", // 字体使用的视口单位
+      selectorBlackList: [], // 需要忽略的CSS选择器,不会转为视口单位,使用原有的px等单位。
+      minPixelValue: 1, // 设置最小的转换数值,如果为1的话,只有大于1的值会被转换
+      mediaQuery: true, // 媒体查询里的单位是否需要转换单位
+      replace: true, //  是否直接更换属性值,而不添加备用属性
+      exclude: undefined, // 忽略某些文件夹下的文件或特定文件,例如 'node_modules' 下的文件
+      include: undefined, // 如果设置了include,那将只有匹配到的文件才会被转换
+      landscape: false, // 是否添加根据 landscapeWidth 生成的媒体查询条件 @media (orientation: landscape)
+      landscapeUnit: "vw", // 横屏时使用的单位
+      landscapeWidth: 1920, // 横屏时使用的视口宽度
+      exclude: [/node_modules/],
+    },
+  },
+};

binární
public/favicon.ico


+ 25 - 0
src/App.vue

@@ -0,0 +1,25 @@
+<script setup>
+import { RouterView } from "vue-router";
+import Sidebar from "@/components/Sidebar/index.vue";
+import Tabbar from "@/components/Tabbar/index.vue";
+</script>
+
+<template>
+  <RouterView />
+  <Sidebar />
+  <Tabbar />
+</template>
+
+<style lang="scss">
+:root {
+  --design-width: 1920;
+  --design-height: 1014;
+}
+
+body,
+#app {
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+}
+</style>

+ 7 - 0
src/assets/elements.scss

@@ -0,0 +1,7 @@
+@forward "element-plus/theme-chalk/src/common/var.scss" with (
+  $colors: (
+    "primary": (
+      "base": #98382E,
+    ),
+  )
+);

binární
src/assets/fonts/FZJinLing.TTF


binární
src/assets/fonts/SOURCEHANSERIFCN-BOLD.OTF


binární
src/assets/fonts/SourceHanSerifCN-Light.otf


binární
src/assets/fonts/SourceHanSerifCN-Medium.otf


binární
src/assets/fonts/SourceHanSerifCN-Regular.otf


binární
src/assets/fonts/SourceHanSerifCN-SemiBold.otf


binární
src/assets/images/icon-ditumoshi.png


binární
src/assets/images/icon-ditumoshi1.png


binární
src/assets/images/icon-fanhui.png


binární
src/assets/images/icon-tuwenmoshi.png


binární
src/assets/images/icon-tuwenmoshi1.png


binární
src/assets/images/icon-xia.png


binární
src/assets/images/logo.png


binární
src/assets/images/mini-map.jpg


binární
src/assets/images/scene-btn.png


+ 111 - 0
src/assets/main.css

@@ -0,0 +1,111 @@
+:root {
+  --z-index-normal: 1;
+  --z-index-top: 1000;
+  --z-index-popper: 2000;
+  --z-hot-popper: 3000;
+}
+
+body,
+ol,
+ul,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+p,
+th,
+td,
+dl,
+dd,
+form,
+fieldset,
+legend,
+input,
+textarea,
+select {
+  margin: 0;
+  padding: 0;
+}
+* {
+  box-sizing: border-box;
+}
+body {
+  color: #383838;
+  text-align: justify;
+  font-family: "SourceHanSerifSC-Regular";
+  -webkit-tap-highlight-color: transparent;
+}
+a {
+  color: #fff;
+  cursor: pointer;
+  text-decoration: none;
+}
+em {
+  font-style: normal;
+}
+li {
+  list-style: none;
+}
+img {
+  border: 0;
+  vertical-align: middle;
+}
+table {
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+p {
+  word-wrap: break-word;
+}
+iframe {
+  border: none;
+}
+
+@font-face {
+  font-family: "SourceHanSerifSC-Bold";
+  src: url("@/assets/fonts/SOURCEHANSERIFCN-BOLD.otf");
+}
+@font-face {
+  font-family: "SourceHanSerifSC-Regular";
+  src: url("@/assets/fonts/SOURCEHANSERIFCN-REGULAR.otf");
+}
+
+.limit-line {
+  display: -webkit-box;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  -webkit-line-clamp: 1;
+  -webkit-box-orient: vertical;
+  word-break: break-all;
+  word-wrap: break-word;
+}
+
+.line-2 {
+  -webkit-line-clamp: 2;
+}
+
+.line-3 {
+  -webkit-line-clamp: 3;
+}
+
+.hidden {
+  display: none !important;
+  visibility: hidden !important;
+}
+
+.darkGlass {
+  background-color: rgba(0, 0, 0, 0.5);
+}
+
+.message-outer {
+  position: absolute;
+  display: table;
+  height: 100%;
+  width: 100%;
+
+  * {
+    transition: all 0.3s;
+  }
+}

+ 7 - 0
src/assets/utils.scss

@@ -0,0 +1,7 @@
+@function vh-calc($num) {
+  @return calc(100vh * ($num / var(--design-height)));
+}
+
+@function vw-calc($num) {
+  @return calc(100vw * ($num / var(--design-width)));
+}

binární
src/components/Sidebar/images/search-bg.png


binární
src/components/Sidebar/images/search-icon.png


binární
src/components/Sidebar/images/sidebar-hide-bg.png


binární
src/components/Sidebar/images/sidebar-hide-icon.png


+ 231 - 0
src/components/Sidebar/index.vue

@@ -0,0 +1,231 @@
+<template>
+  <div class="sidebar" :class="{ 'sidebar--hidden': !visible }">
+    <div class="sidebar-hide-btn" @click="toggle" />
+
+    <img draggable="false" class="sidebar-logo" src="@/assets/images/logo.png" alt="logo" />
+
+    <div class="sidebar-search">
+      <input name="search" placeholder="请搜索建筑名称" />
+      <i class="sidebar-search-icon" />
+    </div>
+
+    <el-tabs v-model="activeTab" class="sidebar-tabs" stretch @tab-change="handleTabChange">
+      <el-tab-pane label="区域划分" name="area">
+        <el-scrollbar>
+          <el-collapse v-for="item in 10" :key="item" class="sidebar-collapse">
+            <el-collapse-item title="区域1" name="area1">
+              <template #icon="{ isActive }">
+                <span class="tag">20</span>
+                <span class="icon" :class="{ 'icon--active': isActive }" />
+              </template>
+
+              <ul>
+                <li>·建筑1</li>
+                <li>·建筑2</li>
+                <li>·建筑3</li>
+              </ul>
+            </el-collapse-item>
+          </el-collapse>
+        </el-scrollbar>
+      </el-tab-pane>
+      <el-tab-pane label="类型划分" name="type" />
+    </el-tabs>
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+import { storeToRefs } from 'pinia'
+import { useSidebarStore } from '@/stores/sidebar'
+
+const { visible } = storeToRefs(useSidebarStore())
+const { toggle } = useSidebarStore()
+
+const search = ref('')
+const activeTab = ref('area')
+
+const handleTabChange = (tab) => {
+  console.log(tab)
+}
+</script>
+
+<style scoped lang="scss">
+
+@use '@/assets/utils.scss';
+
+.sidebar {
+  position: absolute;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  width: utils.vh-calc(300);
+  background: linear-gradient(#D9BF9D, #CFAC7D);
+  transition: transform 0.3s ease;
+
+  &--hidden {
+    transform: translateX(100%);
+
+    .sidebar-hide-btn::before {
+      transform: translate(-50%, -50%) rotate(180deg);
+    }
+  }
+
+  :deep(.sidebar-tabs) {
+    --el-text-color-primary: #4E1A00;
+    --el-font-size-base: utils.vh-calc(20);
+    --el-border-color-light: #98382E;
+    margin: utils.vh-calc(25) 0;
+    width: 100%;
+    height: 0;
+    flex: 1;
+
+    .el-tabs__item.is-active {
+      font-family: 'SourceHanSerifSC-Bold';
+    }
+    .el-tabs__nav-wrap::after {
+      height: 1px;
+    }
+    .el-tabs__item {
+      padding: 0;
+    }
+    .el-tabs__active-bar {
+      height: utils.vh-calc(3);
+    }
+    .el-tab-pane {
+      height: 100%;
+    }
+    .el-tabs__header {
+      padding: 0 utils.vh-calc(15);
+    }
+    .el-scrollbar {
+      padding: 0 utils.vh-calc(15);
+      height: 100%;
+    }
+  }
+  :deep(.sidebar-collapse) {
+    --el-fill-color-blank: transparent;
+    --el-collapse-border-color: transparent;
+    --el-collapse-header-text-color: #4E1A00;
+
+    .el-collapse-item__header {
+      padding-left: utils.vh-calc(5);
+      height: utils.vh-calc(45);
+      line-height: utils.vh-calc(45);
+      min-height: utils.vh-calc(45);
+      font-size: utils.vh-calc(18);
+    }
+    .el-collapse-item__content {
+      padding-bottom: 0;
+    }
+    .tag {
+      width: utils.vh-calc(28);
+      height: utils.vh-calc(18);
+      line-height: utils.vh-calc(18);
+      text-align: center;
+      color: #F8D561;
+      font-size: utils.vh-calc(14);
+      border-radius: 100px;
+      background: #98382E;
+    }
+    .icon {
+      margin-left: utils.vh-calc(12);
+      width: utils.vh-calc(18);
+      height: utils.vh-calc(10);
+      transition: transform 0.3s ease;
+      background: url('@/assets/images/icon-xia.png') no-repeat center / contain;
+
+      &--active {
+        transform: rotate(180deg);
+      }
+    }
+    ul {
+      padding: 0 utils.vh-calc(7);
+
+      li {
+        height: utils.vh-calc(40);
+        line-height: utils.vh-calc(40);
+        font-size: utils.vh-calc(16);
+        color: #4E1A00;
+        cursor: pointer;
+
+        &:hover {
+          color: #98382E;
+        }
+      }
+    }
+  }
+  &-search {
+    flex-shrink: 0;
+    position: relative;
+    display: flex;
+    align-items: center;
+    width: utils.vh-calc(270);
+    height: utils.vh-calc(44);
+    background: url('./images/search-bg.png') no-repeat center / contain;
+
+    input {
+      flex: 1;
+      width: 0;
+      padding: 0 utils.vh-calc(20);
+      height: 100%;
+      background: transparent;
+      border: none;
+      color: #F7E8D4;
+      font-size: utils.vh-calc(18);
+
+      &::placeholder {
+        color: #F7E8D4;
+      }
+      &:focus {
+        outline: none;
+      }
+    }
+    i {
+      flex-shrink: 0;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: utils.vh-calc(46);
+      height: 100%;
+      cursor: pointer;
+
+      &::before {
+        content: '';
+        display: block;
+        width: utils.vh-calc(24);
+        height: utils.vh-calc(22);
+        background: url('./images/search-icon.png') no-repeat center / contain;
+      }
+    }
+  }
+  &-logo {
+    margin: utils.vh-calc(36) 0 utils.vh-calc(54);
+    width: utils.vh-calc(252);
+    height: utils.vh-calc(89);
+  }
+  &-hide-btn {
+    position: absolute;
+    top: 50%;
+    left: utils.vh-calc(-18);
+    transform: translateY(-50%);
+    width: utils.vh-calc(18);
+    height: utils.vh-calc(70);
+    cursor: pointer;
+    background: url('./images/sidebar-hide-bg.png') no-repeat center / contain;
+
+    &::before {
+      content: '';
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      width: utils.vh-calc(11);
+      height: utils.vh-calc(18);
+      transform: translate(-50%, -50%);
+      background: url('./images/sidebar-hide-icon.png') no-repeat center / contain;
+    }
+  }
+}
+</style>

+ 81 - 0
src/components/Tabbar/index.vue

@@ -0,0 +1,81 @@
+<template>
+  <div class="tabbar">
+    <div
+      v-for="item in LIST"
+      :key="item.value"
+      class="tabbar-item"
+      :class="{ 'tabbar-item--active': active === item.pathName }"
+      @click="handleClick(item.pathName)"
+    >
+      <img
+        :src="active === item.pathName ? item.iconActive : item.icon"
+        alt="tabbar-icon-1"
+      />
+      <span>{{ item.title }}</span>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { computed } from "vue";
+import { useRoute, useRouter } from "vue-router";
+import Icon1 from "@/assets/images/icon-ditumoshi.png";
+import Icon1Active from "@/assets/images/icon-ditumoshi1.png";
+import Icon2 from "@/assets/images/icon-tuwenmoshi.png";
+import Icon2Active from "@/assets/images/icon-tuwenmoshi1.png";
+
+const route = useRoute();
+const router = useRouter();
+const LIST = [
+  {
+    icon: Icon1,
+    iconActive: Icon1Active,
+    title: "图文模式",
+    pathName: "Picture",
+  },
+  {
+    icon: Icon2,
+    iconActive: Icon2Active,
+    title: "地图模式",
+    pathName: "Home",
+  },
+];
+const active = computed(() => route.name);
+
+const handleClick = (pathName) => {
+  router.push({ name: pathName });
+};
+</script>
+
+<style scoped lang="scss">
+@use "@/assets/utils.scss";
+
+.tabbar {
+  position: absolute;
+  bottom: utils.vh-calc(30);
+  left: 50px;
+  display: flex;
+  align-items: center;
+  gap: 24px;
+  z-index: 99;
+
+  &-item {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    gap: utils.vh-calc(8);
+    cursor: pointer;
+    color: #4e1a00;
+    font-size: utils.vh-calc(20);
+
+    &--active {
+      color: #98382e;
+      font-family: "SourceHanSerifSC-Bold";
+    }
+    img {
+      width: utils.vh-calc(80);
+      height: utils.vh-calc(80);
+    }
+  }
+}
+</style>

+ 0 - 0
src/constants.js


+ 13 - 0
src/main.js

@@ -0,0 +1,13 @@
+import { createApp } from 'vue'
+import { createPinia } from 'pinia'
+import './assets/main.css'
+
+import App from './App.vue'
+import router from './router'
+
+const app = createApp(App)
+
+app.use(createPinia())
+app.use(router)
+
+app.mount('#app')

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 805 - 0
src/pages/Home/images/map.svg


+ 109 - 0
src/pages/Home/index.vue

@@ -0,0 +1,109 @@
+<template>
+  <div class="home" :class="{ 'home--sidebar-visible': sidebarVisible }">
+    <div class="map-wrapper" v-html="mapSvgContent" ref="mapWrapper"></div>
+
+    <img src="@/assets/images/mini-map.jpg" alt="mini-map" class="mini-map" />
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue'
+import { storeToRefs } from 'pinia'
+import svgPanZoom from 'svg-pan-zoom'
+import mapSvgContent from './images/map.svg?raw'
+import { useSidebarStore } from '@/stores/sidebar'
+
+const { visible: sidebarVisible } = storeToRefs(useSidebarStore())
+const mapWrapper = ref(null)
+let panZoomInstance = null
+
+onMounted(async () => {
+  await nextTick()
+  if (!mapWrapper.value) return
+  const svgEl = mapWrapper.value.querySelector('svg')
+  if (svgEl) {
+    panZoomInstance = svgPanZoom(svgEl, {
+      controlIconsEnabled: false,
+      panEnabled: false,
+      zoomEnabled: false,
+      dblClickZoomEnabled: false,
+      mouseWheelZoomEnabled: false,
+      fit: false,
+      contain: true, // 铺满容器(cover),fit 为 true 时会留白
+      center: true,
+      minZoom: 0.5,
+      maxZoom: 10,
+      zoomScaleSensitivity: 0.35,
+    })
+  }
+})
+
+onBeforeUnmount(() => {
+  if (panZoomInstance) {
+    panZoomInstance.destroy()
+  }
+})
+</script>
+
+<style scoped lang="scss">
+@use '@/assets/utils.scss';
+
+.home {
+  position: absolute;
+  inset: 0;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+}
+
+.map-wrapper {
+  position: absolute;
+  inset: 0;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+}
+
+.map-wrapper :deep(svg) {
+  width: 100%;
+  height: 100%;
+}
+
+.map-wrapper :deep([id^='icon-']) {
+  transform-origin: 50% 50%;
+  transform-box: fill-box;
+  transition: transform 0.25s ease-out;
+  cursor: pointer;
+}
+.map-wrapper :deep([id^='icon-']:hover) {
+  transform: scale(1.2);
+}
+
+.mini-map {
+  position: absolute;
+  right: utils.vh-calc(18);
+  bottom: utils.vh-calc(28);
+  width: utils.vh-calc(404);
+  height: utils.vh-calc(220);
+  transition: right 0.3s ease;
+}
+
+.home--sidebar-visible .mini-map {
+  right: utils.vh-calc(318);
+}
+
+@keyframes breathe {
+  0%,
+  100% {
+    opacity: 0.3;
+  }
+  50% {
+    opacity: 1;
+  }
+}
+.map-wrapper :deep([id^='select-']) {
+  transform-origin: 50% 50%;
+  transform-box: fill-box;
+  animation: breathe 2.5s ease-in-out infinite;
+}
+</style>

binární
src/pages/Picture/images/bg.jpg


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 172 - 0
src/pages/Picture/index.vue


+ 19 - 0
src/router/index.js

@@ -0,0 +1,19 @@
+import { createRouter, createWebHashHistory } from "vue-router";
+
+const router = createRouter({
+  history: createWebHashHistory(import.meta.env.BASE_URL),
+  routes: [
+    {
+      path: "/map",
+      component: () => import("@/pages/Home/index.vue"),
+      name: "Home",
+    },
+    {
+      path: "/",
+      component: () => import("@/pages/Picture/index.vue"),
+      name: "Picture",
+    },
+  ],
+});
+
+export default router;

+ 12 - 0
src/stores/counter.js

@@ -0,0 +1,12 @@
+import { ref, computed } from 'vue'
+import { defineStore } from 'pinia'
+
+export const useCounterStore = defineStore('counter', () => {
+  const count = ref(0)
+  const doubleCount = computed(() => count.value * 2)
+  function increment() {
+    count.value++
+  }
+
+  return { count, doubleCount, increment }
+})

+ 12 - 0
src/stores/sidebar.js

@@ -0,0 +1,12 @@
+import { ref } from 'vue'
+import { defineStore } from 'pinia'
+
+export const useSidebarStore = defineStore('sidebar', () => {
+  const visible = ref(true)
+
+  function toggle() {
+    visible.value = !visible.value
+  }
+
+  return { visible, toggle }
+})

+ 40 - 0
vite.config.js

@@ -0,0 +1,40 @@
+import { fileURLToPath, URL } from 'node:url'
+
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+
+import AutoImport from "unplugin-auto-import/vite";
+import Components from "unplugin-vue-components/vite";
+import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
+
+// https://vite.dev/config/
+export default defineConfig({
+  plugins: [
+    vue(),
+    AutoImport({
+      resolvers: [
+        ElementPlusResolver({
+          importStyle: "sass",
+        }),
+      ],
+    }),
+    Components({
+      resolvers: [ElementPlusResolver({ importStyle: "sass" })],
+    }),
+  ],
+  resolve: {
+    alias: {
+      '@': fileURLToPath(new URL('./src', import.meta.url))
+    },
+  },
+  css: {
+    preprocessorOptions: {
+      scss: {
+        api: "modern-compiler",
+        additionalData: `
+          @use "@/assets/elements.scss" as *;
+        `,
+      },
+    },
+  },
+})