Vue3+Vite+TS+VueRouter+Element-plus第一阶段架构

本篇内容

环境搭建、依赖安装、路由基本结构创建、界面展示、记录遇到的问题和解决方式

环境搭建

Node、Npm、Vue3、Vite、TS、VueRouter、Element-Plus等依赖和插件安装。

Node安装

参照官网安装最新Node
使用nvm管理和安装
(PS:mac和window对于nvm的安装顺则顺,不顺会很难整,给进阶者使用,不建议小白硬整。)

Npm安装镜像

Node装好了就自带了,但是需要使用国内镜像。

npm config set registry https://registry.npm.taobao.org

顺带把yarn装上。(yarn最大的用处就是,npm有时候装不了的它可以,算是一个补充优化的包管理安装工具。)

安装yarn
npm install -g yarn

开始真正创建项目

创建一个项目文件夹,在目录下初始化npm项目:

npm init -y

继续安装必要的依赖,安装 Vite:

npm install -D vite

安装 Vue 3:

npm install vue

安装 TypeScript:

npm install -D typescript

安装 Element-Plus:

npm install element-plus

安装 Vue Router:

npm install vue-router

安装 TypeScript 类型定义:
安装 Vue 和 Vue Router 的 TypeScript 类型定义:

npm install -D @types/vue @types/vue-router
如果一切顺利你就可以继续创建项目了,而如果不顺利我大概讲一下可能遇到的问题。
  • npm的镜像问题,可以自行搜索,mac可能还有权限问题,可以搜索mac如何

开始建立项目结构

这只是当前最简单的结构,后续还会更新。

  • src/:存放源代码
    —— assets /: 存放图片/svg/fonts等静态资源
    —— compoments /: 公共组件等
    —— router :/ 路由文件
    —— styles :/ 样式文件
    —— views :/ 主要功能组件
    —— App.vue
    —— main.ts
  • index.html :/入口html
  • tsconfig.json :/ typescript配置文件
  • vite.config.mjs :/ vite项目的主要配置文件

以下是每个文件简单的结构

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>V3ViteTS项目</title>
</head>
<body>
  <div id="app"></div>
  <script type="module" src="/src/main.ts"></script>
</body>
</html>

tsconfig.json

{
  "compilerOptions": {
    "moduleResolution": "nodenext", // 或 "node" 对于较旧的 Node.js 版本
    "module": "nodenext",
    "target": "esnext",
    "strict": true,
    "jsx": "preserve",
    "esModuleInterop": true,
    "sourceMap": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    },
    "types": ["vue"] // 添加 "vue" 到 types 数组中
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "tests/**/*.ts",
    "tests/**/*.tsx"
  ],
  "exclude": [
    "node_modules",
    "**/*.spec.ts"
  ]
}

文件部分常用熟悉属性解释:

compilerOptions:包含编译选项。
target: 目标 JavaScript 版本,这里是 esnext。
module: 模块系统,这里是 esnext。
lib: 应该包含的标准库类型声明。
allowJs: 是否允许编译 JavaScript 文件。
skipLibCheck: 是否跳过类型声明文件的类型检查。
esModuleInterop: 启用 ES 模块互操作性。
allowSyntheticDefaultImports: 允许从模块默认导入没有默认导出的对象。
strict: 开启所有严格的类型检查选项。
forceConsistentCasingInFileNames: 强制在文件名中使用一致的大小写。
noFallthroughCasesInSwitch: 禁止 switch 语句中的 fall-through。
moduleResolution: 模块解析策略,这里是 node。
resolveJsonModule: 允许导入 .json 文件。
isolatedModules: 将每个文件作为一个独立的模块进行编译。
noEmit: 不生成输出文件。
jsx: JSX 处理模式。

include:要包含在编译过程中的文件路径。
["src"]: 包含 src 目录下的所有文件。
exclude:要排除在编译过程之外的文件路径。
["node_modules", "*/.spec.ts"]: 排除 node_modules 目录下的所有文件和所有的测试文件。


vite.config.mjs

// vite.config.mjs
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import Components from 'unplugin-vue-components/vite';
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
import path from 'path';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@import "@/styles/variables.scss";`, // 引入全局 SCSS 文件
      },
    },
  },
  server: {
    host: '0.0.0.0',
    port: 5173,
    strictPort: true,
    open: true, // 自动打开浏览器
  },
  build: {
    outDir: 'dist',
    assetsDir: 'static',
    rollupOptions: {
      input: {
        main: path.resolve(__dirname, 'index.html'),
      },
    },
  },
});

文件扩展名为 .mjs 表示它是 ES 模块,为什么要用mjs:
.mjs 文件扩展名是专门为 ECMAScript 模块 (ESM) 设计的。ESM 是一个较新的模块系统,它被设计来兼容浏览器和服务器端的 JavaScript 环境。使用 import 和 export 语句来管理依赖和导出,ESM 支持异步加载,这对于前端代码分割和延迟加载非常有用。

与 CommonJS(也就是常见的.js) 相比,ESM 提供了更为静态的结构,它要求所有的 import 和 export 语句都位于文件的顶部,这使得 JavaScript 引擎可以在执行代码之前分析出整个文件的依赖结构。这种静态结构不仅使得代码的静态分析和打包更有效,还允许 JavaScript 引擎优化模块的加载时间。

文件内容简单讲解:

导入了一些必要的模块:

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import Components from 'unplugin-vue-components/vite';
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
import path from 'path';

defineConfig: 从 Vite 主包中导入,用于定义配置对象。
vue: Vue.js 的官方 Vite 插件。
Components: 一个 Vite 插件,用于自动按需引入组件。
ElementPlusResolver: 用于 Components 插件来识别 Element Plus 组件。
path: Node.js 的内置模块,用于处理文件路径。

以上有两个模块没有安装:unplugin-vue-components、vitejs/plugin-vue,自行安装一下

npm install @vitejs/plugin-vue
npm install unplugin-vue-components

unplugin-vue-components 是一个 Vue.js 的 Vite 插件,用于自动按需引入 Vue 组件。它的主要目的是为了减少应用程序的体积,通过仅引入实际使用的组件,而不是整个组件库。这有助于提高应用程序的性能,特别是在使用大型 UI 库(如 Element Plus 或 Vuetify)时。

vitejs/plugin-vue 是 Vite 构建工具的一个官方插件,用于支持 Vue.js 的开发和构建。这个插件提供了 Vue.js 项目所需的各种功能,包括 .vue 单文件组件的支持、模板编译、TypeScript 集成等。

配置项

export default defineConfig({
  // 配置项
});

插件配置

plugins: [
  vue(), // 使用 Vue.js 插件
  Components({
    resolvers: [ElementPlusResolver()], // 自动按需引入 Element Plus 组件
  }),
],

vue(): 启用 Vue.js 支持。这会告诉 Vite 如何处理 .vue 文件,并且提供一些有用的构建优化。
Components: 用于按需引入 Vue 组件。这里配置了 ElementPlusResolver,意味着当你的 Vue 组件引用了 Element Plus 的组件时,Vite 将自动引入所需的样式和脚本文件,而不是整个库。

CSS 预处理器配置

css: {
  preprocessorOptions: {
    scss: {
      additionalData: `@import "@/styles/variables.scss";`, // 引入全局 SCSS 文件
    },
  },
},

scss: 配置 SCSS 预处理器。这里的 additionalData 选项会将指定的 SCSS 文件内容添加到所有 SCSS 文件的头部,通常用来引入全局变量或混合宏。

开发服务器配置

server: {
  host: '0.0.0.0',
  port: 5173,
  strictPort: true,
  open: true, // 自动打开浏览器
},

host: 开发服务器绑定的 IP 地址。0.0.0.0 表示服务器可被任何设备访问。
port: 开发服务器监听的端口。
strictPort: 如果指定端口已被占用,则 Vite 不会尝试自动更换端口,而是直接报错。
open: 当服务器启动时自动在默认浏览器中打开应用程序。

构建配置

build: {
  outDir: 'dist',
  assetsDir: 'static',
  rollupOptions: {
    input: {
      main: path.resolve(__dirname, 'index.html'),
    },
  },
},

outDir: 构建输出的目录。
assetsDir: 静态资源的目录。
rollupOptions: Rollup 构建器的配置选项。
input: 构建过程中的入口文件。这里使用 __dirname 和 path.resolve 来确保正确的文件路径。

SO!你知道上述那些文件其实主要是这边怎么定义就得怎么命名了吗?所以!如果有问题,你先根据这边的定义配置好好看看你的文件名字,如果想要自定义,你也可以对应修改即可。

定义简单的布局,头部导航加侧边栏。

侧边栏以动态路由导航生成,不要写死。头部先包含logo、搜索框、国际化、用户信息。

先定义路由,在router文件下生成index.ts文件:
(包含四个路由,其中第一个为首页,其他为表格、表单、图像。)

// router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue';

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home,
    meta: {
      title: 'Home',
      icon: 'el-icon-s-home'
    }
  },
  {
    path: '/table',
    name: 'About',
    component: () => import('../views/tables/index.vue'),
    meta: {
      title: 'About',
      icon: 'el-icon-user-solid'
    }
  },
  {
    path: '/form',
    name: 'Form',
    component: () => import('../views/form/index.vue'),
    meta: {
      title: 'Form',
      icon: 'el-icon-user-solid'
    }
  },
  {
    path: '/chart',
    name: 'Chart',
    component: () => import('../views/chart/index.vue'),
    meta: {
      title: 'Chart',
      icon: 'el-icon-user-solid'
    }
  },
];

const router = createRouter({
  history: createWebHistory(),
  routes,
});

export default router;

同时创建对应组件,在views目录下:

  • views
    ——chart
    ————index.vue
    ——tables
    ————index.vue
    ——form
    ————index.vue
    chart/index.vue
<template>
    <div>
      <h1>图表</h1>
    </div>
  </template>
  
  <script lang="ts">
  import { defineComponent } from 'vue';
  
  export default defineComponent({
    name: 'ChartView',
  });
  </script>

tables/index.vue

<template>
    <div>
      <h1>表格</h1>
    </div>
  </template>
  
  <script lang="ts">
  import { defineComponent } from 'vue';
  
  export default defineComponent({
    name: 'TablesView',
  });
  </script>

form/index.vue

<template>
    <div>
      <h1>表单</h1>
    </div>
  </template>
  
  <script lang="ts">
  import { defineComponent } from 'vue';
  
  export default defineComponent({
    name: 'FormView',
  });
  </script>

生成头部导航和侧边懒,在src/components下生成Header.vue、Label.vue、Sidebar.vue、SidebarItem.vue

Header.vue

<template>
  <div class="header">
    <el-row type="flex" justify="space-between" align="middle">
      <el-col :span="6">
        <el-link href="/" type="primary">Logo</el-link>
      </el-col>
      <el-col :span="12">
        <el-input placeholder="Search" prefix-icon="el-icon-search" v-model="searchInput"></el-input>
      </el-col>
      <el-col :span="6" class="right-section">
        <el-link @click="toggleLang">{{ lang }}</el-link>
        <el-dropdown trigger="click">
          <span class="el-dropdown-link">
            User Info<i class="el-icon-arrow-down el-icon--right"></i>
          </span>
          <template #dropdown>
            <el-dropdown-menu>
              <el-dropdown-item>Profile</el-dropdown-item>
              <el-dropdown-item>Settings</el-dropdown-item>
              <el-dropdown-item divided @click="logout">Logout</el-dropdown-item>
            </el-dropdown-menu>
          </template>
        </el-dropdown>
      </el-col>
    </el-row>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const searchInput = ref('');
const lang = ref('EN');
const toggleLang = () => {
  lang.value = lang.value === 'EN' ? 'CN' : 'EN';
};

const logout = () => {
  console.log('User logged out');
};
</script>

<style scoped>
.header {
  background-color: #f0f0f0;
  padding: 10px;
}
.right-section {
  display: flex;
  justify-content: flex-end;
}
.el-dropdown-link {
  cursor: pointer;
  color: #409eff;
}
</style>
</script>

<style scoped>
.header {
  background-color: #f0f0f0;
  padding: 10px;
}
.right-section {
  display: flex;
  justify-content: flex-end;
}
.el-dropdown-link {
  cursor: pointer;
  color: #409eff;
}
</style>

Label.vue

<template>
  <div id="app">
    <Header />
    <el-container>
      <el-aside width="200px">
        <Sidebar />
      </el-aside>
      <el-main>
        <router-view />
      </el-main>
    </el-container>
  </div>
</template>

<script lang="ts">
import Header from './Header.vue';
import Sidebar from './Sidebar.vue';

export default {
  components: {
    Header,
    Sidebar,
  },
};
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}
</style>

Sidebar.vue

<template>
  <el-menu
    :default-active="$route.path"
    class="el-menu-vertical-demo"
    mode="vertical"
    unique-opened
    router
    background-color="#545c64"
    text-color="#fff"
    active-text-color="#ffd04b"
  >
    <sidebar-item
      v-for="route in routes"
      :key="route.name"
      :route="route"
    />
  </el-menu>
</template>

<script setup lang="ts">
import { useRoute, useRouter } from 'vue-router';
import SidebarItem from './SidebarItem.vue';

// 获取当前路由
const route = useRoute();
// 获取 Vue Router 实例
const router = useRouter();

// 获取所有路由信息
const routes = router.getRoutes().filter(route => {
  return route.meta && route.meta.title;
});
</script>

SidebarItem.vue

<template>
  <li v-if="!route.children || !route.children.length">
    <el-menu-item :index="route.path" :key="route.name">
      <i :class="route.meta.icon"></i>
      <span slot="title">{{ route.meta.title }}</span>
    </el-menu-item>
  </li>
  <li v-else>
    <el-sub-menu :index="route.path" :key="route.name">
      <template #title>
        <i :class="route.meta.icon"></i>
        <span>{{ route.meta.title }}</span>
      </template>
      <sidebar-item
        v-for="child in route.children"
        :key="child.name"
        :route="child"
      />
    </el-sub-menu>
  </li>
</template>

<script setup lang="ts">
defineProps({
  route: {
    type: Object,
    required: true
  }
});
</script>

开始构建最后的几个文件吧!

App.vue

// App.vue
<template>
  <Layout />
</template>

<script setup lang="ts">
import Layout from './components/Layout.vue';
</script>

*main.ts

// main.ts
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css'; // 引入 Element Plus 的样式

const app = createApp(App);

app.use(ElementPlus);
app.use(router);

app.mount('#app');

大功告成、最后一步:

再安装一下依赖:

npm install

如果报错就试试yarn

yarn install

没有报错就尝试启动:
npm run dev

如果报错就一步步解决吧!哈哈哈。

实在解决不了就拿来主义:我把第一阶段在github上面打了一个tag,你直接下下来即可。完整项目地址:v3viteTsElement,点击查看。

而命令行直接干:

git clone --single-branch --depth 1 --branch tag1.0 https://github.com/RDzdf/v3viteTsElement.git

干下来后:install一下就ok,npm版本比低于9.5就成。node也不要低于20.0.

总结:

写得有些潦草,估计会有很多问题,写多了就容易暴躁!开始乱搞,就这样吧!有问题可留言,我会耐心给你们解决的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容