瀏覽代碼

🎉 框架搭建和首页

imengyu 2 周之前
當前提交
aeb5150260
共有 61 個文件被更改,包括 4837 次插入0 次删除
  1. 30 0
      .gitignore
  2. 3 0
      .vscode/extensions.json
  3. 33 0
      README.md
  4. 1 0
      env.d.ts
  5. 13 0
      index.html
  6. 3759 0
      package-lock.json
  7. 33 0
      package.json
  8. 二進制
      public/favicon.ico
  9. 14 0
      src/App.vue
  10. 二進制
      src/assets/fonts/Impact.ttf
  11. 二進制
      src/assets/fonts/Impact.woff
  12. 二進制
      src/assets/fonts/Impact.woff2
  13. 二進制
      src/assets/fonts/STSongti-SC-Black.ttf
  14. 二進制
      src/assets/fonts/STSongti-SC-Black.woff
  15. 二進制
      src/assets/fonts/STSongti-SC-Black.woff2
  16. 二進制
      src/assets/fonts/SourceHanSerifCN-Bold.otf
  17. 二進制
      src/assets/fonts/SourceHanSerifCN-Bold.ttf
  18. 二進制
      src/assets/fonts/SourceHanSerifCN-Bold.woff
  19. 二進制
      src/assets/fonts/SourceHanSerifCN-Bold.woff2
  20. 二進制
      src/assets/fonts/nzgrRuyinZouZhangKai.ttf
  21. 二進制
      src/assets/fonts/nzgrRuyinZouZhangKai.woff
  22. 二進制
      src/assets/fonts/nzgrRuyinZouZhangKai.woff2
  23. 二進制
      src/assets/images/Bg1.png
  24. 二進制
      src/assets/images/Bg2.png
  25. 二進制
      src/assets/images/Icon@2x.png
  26. 二進制
      src/assets/images/LargeTitle1.png
  27. 二進制
      src/assets/images/LargeTitle2.png
  28. 二進制
      src/assets/images/LargeTitle3.png
  29. 二進制
      src/assets/images/LogoIcon.png
  30. 二進制
      src/assets/images/TitleMiniHeader.png
  31. 二進制
      src/assets/images/footer/FooterPrinting.png
  32. 二進制
      src/assets/images/footer/GonganLogo.png
  33. 二進制
      src/assets/images/index/Box1.png
  34. 二進制
      src/assets/images/index/Box2.png
  35. 二進制
      src/assets/images/index/Box3.png
  36. 二進制
      src/assets/images/index/BoxPrinting1.png
  37. 二進制
      src/assets/images/index/BoxPrinting2.png
  38. 二進制
      src/assets/images/index/BoxPrinting4.png
  39. 二進制
      src/assets/images/index/ButtonMore.png
  40. 二進制
      src/assets/images/index/IntrodLeft.png
  41. 二進制
      src/assets/images/index/IntrodRight.png
  42. 二進制
      src/assets/images/placeholder/Large.jpg
  43. 二進制
      src/assets/images/placeholder/Large2.jpg
  44. 二進制
      src/assets/images/placeholder/Midium.jpg
  45. 4 0
      src/assets/scss/colors.scss
  46. 22 0
      src/assets/scss/components.scss
  47. 7 0
      src/assets/scss/fix.scss
  48. 32 0
      src/assets/scss/fonts.scss
  49. 176 0
      src/assets/scss/main.scss
  50. 91 0
      src/components/Footer.vue
  51. 134 0
      src/components/NavBar.vue
  52. 19 0
      src/main.ts
  53. 33 0
      src/router/index.ts
  54. 12 0
      src/stores/counter.ts
  55. 15 0
      src/views/AboutView.vue
  56. 329 0
      src/views/HomeView.vue
  57. 15 0
      src/views/NotFoundView.vue
  58. 12 0
      tsconfig.app.json
  59. 11 0
      tsconfig.json
  60. 19 0
      tsconfig.node.json
  61. 20 0
      vite.config.ts

+ 30 - 0
.gitignore

@@ -0,0 +1,30 @@
+# 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
+
+/cypress/videos/
+/cypress/screenshots/
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+*.tsbuildinfo

+ 3 - 0
.vscode/extensions.json

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

+ 33 - 0
README.md

@@ -0,0 +1,33 @@
+# mingnan-website
+
+This template should help get you started developing with Vue 3 in Vite.
+
+## Recommended IDE Setup
+
+[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
+
+## Type Support for `.vue` Imports in TS
+
+TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
+
+## Customize configuration
+
+See [Vite Configuration Reference](https://vite.dev/config/).
+
+## Project Setup
+
+```sh
+npm install
+```
+
+### Compile and Hot-Reload for Development
+
+```sh
+npm run dev
+```
+
+### Type-Check, Compile and Minify for Production
+
+```sh
+npm run build
+```

+ 1 - 0
env.d.ts

@@ -0,0 +1 @@
+/// <reference types="vite/client" />

+ 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>Vite App</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.ts"></script>
+  </body>
+</html>

文件差異過大導致無法顯示
+ 3759 - 0
package-lock.json


+ 33 - 0
package.json

@@ -0,0 +1,33 @@
+{
+  "name": "mingnan-website",
+  "version": "0.0.0",
+  "private": true,
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "run-p type-check \"build-only {@}\" --",
+    "preview": "vite preview",
+    "build-only": "vite build",
+    "type-check": "vue-tsc --build"
+  },
+  "dependencies": {
+    "bootstrap": "^5.3.0",
+    "pinia": "^3.0.1",
+    "vue": "^3.5.13",
+    "vue-router": "^4.5.0",
+    "vue3-carousel": "^0.15.0"
+  },
+  "devDependencies": {
+    "@tsconfig/node22": "^22.0.1",
+    "@types/node": "^22.14.0",
+    "@vitejs/plugin-vue": "^5.2.3",
+    "@vitejs/plugin-vue-jsx": "^4.1.2",
+    "@vue/tsconfig": "^0.7.0",
+    "npm-run-all2": "^7.0.2",
+    "sass": "^1.87.0",
+    "typescript": "~5.8.0",
+    "vite": "^6.2.4",
+    "vite-plugin-vue-devtools": "^7.7.2",
+    "vue-tsc": "^2.2.8"
+  }
+}

二進制
public/favicon.ico


+ 14 - 0
src/App.vue

@@ -0,0 +1,14 @@
+<template>
+  <NavBar />
+  <main>
+    <RouterView />
+  </main>
+  <Footer />
+</template>
+
+<script setup lang="ts">
+import { RouterLink, RouterView } from 'vue-router'
+import NavBar from './components/NavBar.vue';
+import Footer from './components/Footer.vue';
+
+</script>

二進制
src/assets/fonts/Impact.ttf


二進制
src/assets/fonts/Impact.woff


二進制
src/assets/fonts/Impact.woff2


二進制
src/assets/fonts/STSongti-SC-Black.ttf


二進制
src/assets/fonts/STSongti-SC-Black.woff


二進制
src/assets/fonts/STSongti-SC-Black.woff2


二進制
src/assets/fonts/SourceHanSerifCN-Bold.otf


二進制
src/assets/fonts/SourceHanSerifCN-Bold.ttf


二進制
src/assets/fonts/SourceHanSerifCN-Bold.woff


二進制
src/assets/fonts/SourceHanSerifCN-Bold.woff2


二進制
src/assets/fonts/nzgrRuyinZouZhangKai.ttf


二進制
src/assets/fonts/nzgrRuyinZouZhangKai.woff


二進制
src/assets/fonts/nzgrRuyinZouZhangKai.woff2


二進制
src/assets/images/Bg1.png


二進制
src/assets/images/Bg2.png


二進制
src/assets/images/Icon@2x.png


二進制
src/assets/images/LargeTitle1.png


二進制
src/assets/images/LargeTitle2.png


二進制
src/assets/images/LargeTitle3.png


二進制
src/assets/images/LogoIcon.png


二進制
src/assets/images/TitleMiniHeader.png


二進制
src/assets/images/footer/FooterPrinting.png


二進制
src/assets/images/footer/GonganLogo.png


二進制
src/assets/images/index/Box1.png


二進制
src/assets/images/index/Box2.png


二進制
src/assets/images/index/Box3.png


二進制
src/assets/images/index/BoxPrinting1.png


二進制
src/assets/images/index/BoxPrinting2.png


二進制
src/assets/images/index/BoxPrinting4.png


二進制
src/assets/images/index/ButtonMore.png


二進制
src/assets/images/index/IntrodLeft.png


二進制
src/assets/images/index/IntrodRight.png


二進制
src/assets/images/placeholder/Large.jpg


二進制
src/assets/images/placeholder/Large2.jpg


二進制
src/assets/images/placeholder/Midium.jpg


+ 4 - 0
src/assets/scss/colors.scss

@@ -0,0 +1,4 @@
+$primary-color: #bd4b36;
+$text-color: #333;
+$text-second-color: #6d6d6d;
+$selection-max-width: 1250px;

+ 22 - 0
src/assets/scss/components.scss

@@ -0,0 +1,22 @@
+@use "./colors.scss" as *;
+
+.simple-carousel2-left-right {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+  width: 100px;
+  margin-top: 40px;
+
+  div {
+    width: 30px;
+    height: 30px;
+    font-size: 25px;
+    text-align: center;
+    line-height: 30px;
+    cursor: pointer;
+    -webkit-user-select: none;
+    user-select: none;
+    color: $primary-color;
+  }
+}

+ 7 - 0
src/assets/scss/fix.scss

@@ -0,0 +1,7 @@
+.carousel-light {
+  --vc-nav-color: #fff;
+  --vc-clr-primary: #fff;
+  --vc-clr-secondary: #cfcfcf7f;
+  --vc-clr-white: #333333;
+  --vc-pgn-active-color: var(--vc-clr-primary)
+}

+ 32 - 0
src/assets/scss/fonts.scss

@@ -0,0 +1,32 @@
+@font-face {
+  font-family: nzgrRuyinZouZhangKai;
+  src: 
+    url('@/assets/fonts/nzgrRuyinZouZhangKai.woff') format('woff')
+    url('@/assets/fonts/nzgrRuyinZouZhangKai.ttf') format('truetype')
+    url('@/assets/fonts/nzgrRuyinZouZhangKai.woff2') format('woff2');
+  font-weight: normal;
+}
+@font-face {
+  font-family: STSongtiSCBlack;
+  src: 
+    url('@/assets/fonts/STSongti-SC-Black.woff') format('woff') 
+    url('@/assets/fonts/STSongti-SC-Black.ttf') format('truetype')
+    url('@/assets/fonts/STSongti-SC-Black.woff2') format('woff2');
+  font-weight: normal;
+}
+@font-face {
+  font-family: Impact;
+  src: 
+    url('@/assets/fonts/Impact.woff') format('woff') 
+    url('@/assets/fonts/Impact.ttf') format('truetype')
+    url('@/assets/fonts/Impact.woff2') format('woff2');
+  font-weight: normal;
+}
+@font-face {
+  font-family: SourceHanSerifCNBold;
+  src: 
+    url('@/assets/fonts/SourceHanSerifCN-Bold.woff') format('woff') 
+    //url('@/assets/fonts/SourceHanSerifCN-Bold.ttf') format('truetype')
+    ;//url('@/assets/fonts/SourceHanSerifCN-Bold.woff2') format('woff2');
+  font-weight: normal;
+}

+ 176 - 0
src/assets/scss/main.scss

@@ -0,0 +1,176 @@
+@use "./fonts.scss";
+@use "./fix.scss";
+@use "./components.scss";
+@use "./colors.scss" as *;
+
+body,
+html {
+  font-size: 16px;
+  font-weight: normal;  
+  margin: 0;
+  padding: 0;
+  color: $text-color;
+}
+main {
+
+}
+
+//Header
+
+.main-header-box {
+  position: relative;
+  width: 100%;
+  min-height: 600px;
+  background-color: $primary-color;
+
+  img {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    object-fit: cover;
+    z-index: 0;
+  }
+}
+.main-center-text {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  text-align: center;
+}
+
+//Utitles
+
+.main-background {
+  background-size: 100% auto;
+  background-repeat: repeat;
+  background-position: center;
+
+  &-type1 {
+    background-image: url('@/assets/images/Bg1.png');;
+  }
+  &-type2 {
+    background-image: url('@/assets/images/Bg2.png');;
+  }
+  &-type3 {
+    background-image: url('@/assets/images/index/IntrodRight.png');;
+  }
+}
+
+//Boxs
+
+.main-news-box {
+  position: relative;
+  padding: 24px;
+  background-color: #fff;
+  background-size: cover;
+  background-position: center;
+  width: 400px;
+  height: 270px;
+  margin-right: 24px;
+
+  &::before {
+    content: '';
+    display: block;
+    position: absolute;
+    right: 0;
+    left: 0;
+    bottom: 0;
+    height: 120px;
+    background: linear-gradient(
+      180deg,
+      rgba(#000, 0) 0%,
+      rgba(#000, 0.5) 100%
+    )
+  }
+
+  .desc {
+    position: absolute;
+    right: 0;
+    left: 0;
+    bottom: 0;
+    display: flex;
+    flex-direction: column;
+    padding: 24px;
+    color: #fff;
+
+    h5 {
+      font-family: SourceHanSerifCNBold;
+      font-size: 1.1rem;
+      margin-bottom: 5px;
+    }
+    p {
+      font-size: 0.8rem;
+      margin: 0;
+    }
+  }
+}
+
+//Section
+
+.main-section {
+  position: relative;
+  padding: 120px 100px;
+
+
+  h2 {
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    margin: 0;
+    font-size: 2rem;
+    font-family: SourceHanSerifCNBold;
+
+    &::after, &::before {
+      content: '';
+      display: inline-block;
+      width: 20px;
+      height: 20px;
+      background-size: 20px;
+      background-image: url('@/assets/images/TitleMiniHeader.png');
+    }
+    &::after {
+      margin-left: 10px;
+    } 
+    &::before {
+      margin-right: 10px;
+    }
+  }
+  
+  .content {
+    max-width: $selection-max-width;
+    margin: 0 auto;
+
+    .title {
+
+      display: flex;
+      flex-direction: row;
+      align-items: center;
+      justify-content: center;
+
+      margin-bottom: 40px;
+
+      &.left-right {
+        justify-content: space-between;
+      }
+
+      .small-more {
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+        font-size: 0.9rem;
+        color: $text-second-color;
+        cursor: pointer;
+        -webkit-user-select: none;
+        user-select: none;
+
+        img {
+          width: 80px;
+          margin-left: 20px;
+        }
+      }
+    }
+  }
+}

+ 91 - 0
src/components/Footer.vue

@@ -0,0 +1,91 @@
+<template>
+  <footer class="main-footer">
+    <div>
+      <div class="row">
+        <div class="col-sm-12 col-md-6">
+          <div class="logo">
+            <img src="@/assets/images/LogoIcon.png" />
+            <h2>闽南(厦门)文化生态保护区</h2>
+          </div>
+        </div>
+        <div class="col-sm-12 col-md-6">
+          <div class="d-block links text-end">
+            <span>友情链接:</span>
+            <a href="#">厦门市文化馆</a>
+            <a href="#">厦门市图书馆</a>
+            <a href="#">厦门市博物馆</a>
+          </div>
+        </div>
+      </div>
+      <div class="row">
+        <div class="links">
+          <a href="#"><img src="@/assets/images/footer/GonganLogo.png" />闽公网安备 44040202000131号</a>
+          <a href="#">闽ICP备09020130号</a>
+        </div>
+      </div>
+    </div>
+  </footer>
+</template>
+
+<script setup lang="ts">
+
+
+</script>
+
+<style lang="scss">
+@use '@/assets/scss/colors.scss' as *;
+
+.main-footer {
+  background-color: $primary-color;
+  color: #fff;
+
+  > div {
+    padding: 60px 55px;
+    background-image: url("@/assets/images/footer/FooterPrinting.png");
+    background-position: bottom 0 right 11px;
+    background-repeat: no-repeat;
+    background-size: 533px;
+  }
+
+  .logo {
+    display: flex;
+    align-items: center;
+    margin-bottom: 10px;
+    font-family: nzgrRuyinZouZhangKai;
+    margin-bottom: 48px;
+
+    h2 {
+      margin: 0;
+    }
+    img {
+      width: 30px;
+      height: 30px;
+      margin-right: 10px;
+    }
+  }
+  .links {
+    display: flex;
+    align-items: center;
+  }
+
+  span {
+    font-size: 0.9rem;
+    margin-right: 40px;
+  }
+
+  a {
+    text-decoration: none;
+    color: #F9EDD3;
+    font-size: 0.9rem;
+    margin-right: 40px;
+
+    &:hover {
+      color: #fff;
+    }
+
+    img {
+      margin-right: 10px;
+    }
+  }
+}
+</style>

+ 134 - 0
src/components/NavBar.vue

@@ -0,0 +1,134 @@
+<template>
+  <nav 
+    :class="[
+      scrollValue > 200 ? 'nav-scrolled' : 'nav-not-scrolled',
+    ]"
+  >
+    <div class="group">
+      <RouterLink to="/">首页</RouterLink>
+      <RouterLink to="/news">资讯动态</RouterLink>
+      <RouterLink to="/introduction">文化概况</RouterLink>
+      <RouterLink to="/inheritor">保护传承</RouterLink>
+    </div>
+    <div class="group">
+      <div class="headerlogos">
+        <img src="@/assets/images/LogoIcon.png" />
+        <div>
+          <p class="large">闽南文化生态保护区<span>(厦门市)</span></p>
+          <p>Minnan Cultural Ecological Protection Area</p>
+        </div>
+      </div>
+    </div>
+    <div class="group">
+      <RouterLink to="/communicate">传播交流</RouterLink>
+      <RouterLink to="/research">理论研究</RouterLink>
+      <RouterLink to="/fusion">文旅融合</RouterLink>
+      <RouterLink to="/about">关于我们</RouterLink> 
+    </div>
+  </nav>
+</template>
+
+<script setup lang="ts">
+import { onBeforeUnmount, onMounted, ref } from 'vue';
+
+const scrollValue = ref(0);
+
+function onScroll() {
+  scrollValue.value = window.scrollY;
+}
+
+onMounted(() => {
+  window.addEventListener('scroll', onScroll);
+});
+onBeforeUnmount(() => {
+  window.removeEventListener('scroll', onScroll);
+});
+
+</script>
+
+<style lang="scss">
+@use '@/assets/scss/colors.scss' as *;
+
+nav {
+  $nav-height: 65px;
+
+  position: fixed;
+  display: flex;
+  justify-content: space-around;
+  align-items: center;
+  z-index: 100;
+  width: 100%;
+  height: $nav-height;
+  background-color: transparent;
+  border-bottom: 1px solid rgba(#fff, 0.2);
+  color: #fff;
+  transition: all ease-in-out 0.3s;
+
+  &.nav-scrolled {
+    background-color: $primary-color;
+  }
+
+  .group {
+    display: flex;
+    gap: 1rem; 
+
+    a {
+      color: rgba(#fff, 0.8);
+      width: 100px;
+      height: $nav-height;
+      line-height: $nav-height;
+      text-align: center;
+      text-decoration: none;
+
+      &:focus {
+        outline: none;
+      }
+      &.router-link-active, &.router-link-exact-active, &:hover {
+        color: #fff;
+      }
+      &.router-link-exact-active {
+        font-weight: bold;
+      }
+    }
+
+  }
+  .headerlogos {
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+
+    img {
+      width: 45px;
+      height: 45px; 
+      margin-right: 10px;
+    }
+    > div {
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+      font-family: nzgrRuyinZouZhangKai;
+      margin-bottom: 6px;
+
+      p {
+        margin: 0;
+        font-size: 1rem;
+        height: 20px;
+
+        span {
+          font-size: 1rem;
+          margin-left: 10px;
+        }
+        &.large {
+          height: 33px;
+          font-size: 1.8rem;
+          letter-spacing: -0.1rem;
+        }
+      }
+    }
+  }
+}
+
+@media (min-width: 1024px) {
+ 
+}
+</style>

+ 19 - 0
src/main.ts

@@ -0,0 +1,19 @@
+
+import 'bootstrap/dist/css/bootstrap.css'
+import 'bootstrap/dist/css/bootstrap-grid.css'
+import 'bootstrap/dist/css/bootstrap-utilities.css'
+import './assets/scss/main.scss'
+import 'vue3-carousel/carousel.css'
+
+import { createApp } from 'vue'
+import { createPinia } from 'pinia'
+
+import App from './App.vue'
+import router from './router'
+
+const app = createApp(App)
+
+app.use(createPinia())
+app.use(router)
+
+app.mount('#app')

+ 33 - 0
src/router/index.ts

@@ -0,0 +1,33 @@
+import { createRouter, createWebHistory } from 'vue-router'
+import HomeView from '../views/HomeView.vue'
+import NotFoundView from '../views/NotFoundView.vue'
+
+const router = createRouter({
+  history: createWebHistory(import.meta.env.BASE_URL),
+  routes: [
+    {
+      path: '/',
+      name: 'home',
+      component: HomeView,
+    },
+    {
+      path: '/about',
+      name: 'about',
+      // route level code-splitting
+      // this generates a separate chunk (About.[hash].js) for this route
+      // which is lazy-loaded when the route is visited.
+      component: () => import('../views/AboutView.vue'),
+    },
+    {
+      path: '/404',
+      name: 'NotFound',
+      component: NotFoundView
+    },
+    {
+      path: '/:pathMatch(.*)*', // 匹配所有不存在的路径
+      redirect: '/404'
+    }
+  ],
+})
+
+export default router

+ 12 - 0
src/stores/counter.ts

@@ -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 }
+})

+ 15 - 0
src/views/AboutView.vue

@@ -0,0 +1,15 @@
+<template>
+  <div class="about">
+    <h1>This is an about page</h1>
+  </div>
+</template>
+
+<style>
+@media (min-width: 1024px) {
+  .about {
+    min-height: 100vh;
+    display: flex;
+    align-items: center;
+  }
+}
+</style>

文件差異過大導致無法顯示
+ 329 - 0
src/views/HomeView.vue


+ 15 - 0
src/views/NotFoundView.vue

@@ -0,0 +1,15 @@
+<template>
+  <div class="about">
+    <h1>This is an about page</h1>
+  </div>
+</template>
+
+<style>
+@media (min-width: 1024px) {
+  .about {
+    min-height: 100vh;
+    display: flex;
+    align-items: center;
+  }
+}
+</style>

+ 12 - 0
tsconfig.app.json

@@ -0,0 +1,12 @@
+{
+  "extends": "@vue/tsconfig/tsconfig.dom.json",
+  "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
+  "exclude": ["src/**/__tests__/*"],
+  "compilerOptions": {
+    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+
+    "paths": {
+      "@/*": ["./src/*"]
+    }
+  }
+}

+ 11 - 0
tsconfig.json

@@ -0,0 +1,11 @@
+{
+  "files": [],
+  "references": [
+    {
+      "path": "./tsconfig.node.json"
+    },
+    {
+      "path": "./tsconfig.app.json"
+    }
+  ]
+}

+ 19 - 0
tsconfig.node.json

@@ -0,0 +1,19 @@
+{
+  "extends": "@tsconfig/node22/tsconfig.json",
+  "include": [
+    "vite.config.*",
+    "vitest.config.*",
+    "cypress.config.*",
+    "nightwatch.conf.*",
+    "playwright.config.*",
+    "eslint.config.*"
+  ],
+  "compilerOptions": {
+    "noEmit": true,
+    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+
+    "module": "ESNext",
+    "moduleResolution": "Bundler",
+    "types": ["node"]
+  }
+}

+ 20 - 0
vite.config.ts

@@ -0,0 +1,20 @@
+import { fileURLToPath, URL } from 'node:url'
+
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+import vueJsx from '@vitejs/plugin-vue-jsx'
+import vueDevTools from 'vite-plugin-vue-devtools'
+
+// https://vite.dev/config/
+export default defineConfig({
+  plugins: [
+    vue(),
+    vueJsx(),
+    vueDevTools(),
+  ],
+  resolve: {
+    alias: {
+      '@': fileURLToPath(new URL('./src', import.meta.url))
+    },
+  },
+})