教你如何搭建vue3项目

前言

之前,我写过一片文章教你如何制作vue+springboot项目,教授小白可以很快的上手搭建vue和springboot项目,那么现在我感觉自己对vue相关的知识点更加熟悉,想着可以开一篇文章,教授大家如何去搭建vue3项目

如对我的上述提到的感兴趣,可参考:
教你如何制作vue+springboot项目

本项目不会教学搭建springboot,如果希望掌握springboot相关搭建,也可以参考:

IDEA构建SpringBoot多模块项目

那么接下来开始正式教学

实际项目演示

如下我给出我搭建之后的项目界面,在结尾我会放我这个项目的demo的git链接
没有特别优化界面,只是写一个差不多的界面

在这里插入图片描述

登录之后

在这里插入图片描述

Vue3特性

vue3项目是可以适用vue2的语法的,但是如果在vue3采用vue2的写法来搭建vue3项目就没有意义了

相较于Vue2,Vue3也得到了一些优化

函数式编程: Vue 3 引入了 Composition API,允许开发者通过函数组合逻辑,而不是依赖于 Options API(Vue 2 的标准写法)。这使得代码更易于组织和重用,尤其在复杂组件中
性能优化: Vue 3 在性能上进行了显著改善,初始渲染速度更快,同时更新响应更高效
TypeScript支持:Vue 3 原生支持 TypeScript,使得 TypeScript 用户能够更好地利用类型检查和 IDE 提示
以及其他…

Vue3搭建框架

在正式开始之前,我们需要梳理一下vue3项目需要用到什么
在这次教学的项目中,我们会用到这些

Vite: 具有非常快的热重载和构建速度的构建工具
Pinia: Pinia 是 Vue 3 的状态管理库,作为 Vuex 的替代品,提供了更轻便和现代的 API 进行状态管理,也就是我们的Store
TypeScript: TypeScript是JavaScript的上层语言,可以被编译成JavaScript。在TypeScript中,可以定义类,类是一种面向对象编程的基本结构
Element Plus: 本次教学项目的前端框架,如有其他需要可选别的框架
axios: 基于promise的网络请求库,可以和后端进行连接

PS: Vue3最低要求需要Node.js 12.x 或更高版本,不过,为了确保最佳的兼容性和性能,建议使用最新的稳定版本的 Node.js 和 npm,当前我搭建该项目的Node版本是18.17.1

创建项目

✨创建Vite框架项目

npm create vite@latest

✨ 然后会提示你,需要按照vite依赖包,我们填y回车

Need to install the following packages:
  create-vite@5.5.4
Ok to proceed? (y)

在这里插入图片描述

✨然后会让你输入项目名,根据自己需求修改和填写

? Project name: » vite-project
? Package name: >> vite-project

在这里插入图片描述

✨上下箭头选择vue框架

? Select a framework: » - Use arrow-keys. Return to submit.
    Vanilla
>   Vue
    React
    Preact
    Lit
    Svelte
    Solid
    Qwik
    Angular
    Others

在这里插入图片描述

✨选择TypeScript

? Select a variant: » - Use arrow-keys. Return to submit.
>   TypeScript
    JavaScript
    Customize with create-vue ↗
    Nuxt ↗

在这里插入图片描述

✨然后会提示你启动项目

Done. Now run:

  cd vue3Demo
  npm install
  npm run dev

✨使用提示的指令进行切路径,安装依赖和运行项目

在这里插入图片描述

✨然后按照提示打开运行的vite项目即可

打开之后项目运行是这样

在这里插入图片描述

此时我们的项目代码结构如下:

在这里插入图片描述

node_modules
public
	- vite.svg
src
	- assets
		- vue.svg
	components
		- HelloWorld.vue
App.vue
main.ts
style.css
vite-env.d.ts
.gitignore
index.html
package-lock.json
package.json
README.md
tsconfig.app.json
tsconfig.json
tsconfig.node.json
vite.config.ts

必要依赖安装

为了能够顺利开发我们的vue3项目,我们需要在最开始安装依赖

less/scss

vite它提供了对 .scss, .sass, .less, .styl 和 .stylus 文件的内置支持,但必须安装相应的预处理器依赖;
这次项目使用 less , scss

自动导入

本次项目,会安装自动导入的依赖,来减少导入相关的冗余代码量
一个用于自动导入 Vue 组件的插件,一个用于自动导入常用的函数

router

vue的路由依赖,用于界面的跳转,传参

pinia

Pinia是Vue的存储库,它允许您跨组件/页面共享状态,作用类似于Vuex的Store

axios

和后端交互依赖

element-plus

前端界面框架,用于构筑界面

汇总依赖安装指令

npm add -D less 
npm add -D sass 
npm i postcss-nesting
npm install -D unplugin-vue-components unplugin-auto-import
npm install vue-router@4 
npm install pinia 
npm install axios
npm install element-plus --save
npm install @types/node 
npm install @element-plus/icons-vue

配置逻辑添加

为了项目正常搭建运行,接下来我们将作一些配置新增和修改

vite.config.ts

未修改前

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vite.dev/config/
export default defineConfig({
  plugins: [vue()],
})

修改后

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/dist/vite'
import Components from 'unplugin-vue-components/dist/vite'
import postcssNesting from "postcss-nesting"
import { resolve } from "path"; // 这里需要引入进来,否则不会识别到@别名
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
// https://vite.dev/config/
export default defineConfig({
  base: "./",// 打包时打包位置,不按照这个会找不到项目关联文件位置
  plugins: [
    vue(),
    // 自动引入配置
    AutoImport({
      imports: ['vue', 'vue-router'],
      dts: 'src/auto-import.d.ts'
    }),
    Components({
      resolvers: [ElementPlusResolver()],
      dts: 'src/components.d.ts'
    })
  ],
  // 配置scss,样式
  css: {
    postcss: {
      plugins: [
        postcssNesting
      ]
    }
  },
  server: {
    proxy: {
      // 后端配置地址,通过特定路径映射到对应后端ip端口
      '/back': {
        target: 'http://127.0.0.1:8081',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/back/, '')
      }
    }
  },
  // 配置@别名,在引入文件的时候可以用@代替到
  resolve: {
    alias: {
      "@": resolve(__dirname, "./src"),
      "@types": resolve(__dirname, "./src/types")
    }
  }
})

routes的index.ts

在src下新建routes文件夹,在里面建立index.ts文件

import { createRouter, createWebHistory } from "vue-router";
let routes= [
    {
        path: '/',
        name: 'login',
        //使用如下方式进行路由懒加载,防止白屏
        component: () => import('../view/login.vue')
    },
    {
        path: '/home',
        name: 'home',
        component: () => import('@/view/home.vue'),
      },
    {
    path: '/:catchAll(.*)',
    name: '404',
    component: () => import('../view/notFound.vue'),
    }
]

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

export default router

这里我引入了三个路由,一个登录界面,一个主页面还有一个404界面,你引入了几个,相应需要在对应路径建立新增对应的vue文件

store的home.ts

在src下新增store文件夹,在里面建立home.ts文件

import { defineStore } from 'pinia';

export const useMain = defineStore('main', {
    // 相当于vue2的data
    state: () => {
        return {
            title: 'vue3Demo',
            userName: '',
            count: 0
        }
    },
    // 相当于计算属性
    getters: {
        // count累加
        countAdd: (state) => {
            return state.count++;
        }
    },
    actions: {
        // 相当于vuex的mutation + action,可以写同步和异步,这里示例赋值用户名
        setUserName(userName: string) {
            this.userName = userName;
        }
    }
});

request的request.ts

在src下新建request文件夹,里面新增request.ts代码,封装axios

import axios from 'axios'
import { CreateAxiosDefaults } from 'axios';
// import {ElMessage} from "element-plus";
import { useRouter } from "vue-router";
const router = useRouter();
export const baseUrl = '/sso'; // 设置后端基础路径
// export const baseUrl = 'http://127.0.0.1:8081'; // 如果使用这个,vite.config.ts的proxy就不需要
interface CustomAxiosDefaults extends CreateAxiosDefaults {
    headers: {
        'Content-Type': 'application/json',
        'token': string,
        'X-Requested-With': string,
    };
}


const request = axios.create({
    baseURL: baseUrl, // 基础路径
    timeout: 60000, // 超时断开连接
    withCredentials: true,
    headers: {
        'Content-Type': 'application/json',
        'token': 'x-auth-token',
        'X-Requested-With': 'XMLHttpRequest',
    },
} as CustomAxiosDefaults);

// request拦截器
request.interceptors.request.use(
    
    config => {
        return config
    },
    error => {
        // 对请求错误做些什么
        Promise.reject(error)
    }
)

// response 拦截器
request.interceptors.response.use(
    response => {
        // 根据自己后端实际响应体封装是否要报错提示
        // if (response.data && response.data.header) {
        //     const  code = response.data.header.code
        //     if (code !== 0) {
        //         ElMessage.error(response.data.header.message)
        //         return;
        //     }
        // }
        // 对响应数据做点什么
        return response.data
    },
    error => {
        // 对响应错误做点什么
        return Promise.reject(error)
    }
)
export default request

Element-puls.d.ts

在src下新建文件Element-puls.d.ts

export const ElMessage: any;
declare global {
    const ElMessage:typeof import('element-plus')['ElMessage']
    const ElLoading:typeof import('element-plus')['ElLoading']
}

main.ts

初始main.ts代码如下

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'

createApp(App).mount('#app')

修改后:

import { createPinia } from 'pinia';
import { createApp } from 'vue';
import App from './App.vue';
import router from "@/routes/index";
import 'element-plus/dist/index.css';
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
const pinia = createPinia();
const app = createApp(App);


for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
    app.component(key, component)
  }

app.use(pinia);
app.use(router);
app.mount('#app');

package.json

初始为:

{
  "name": "vue3demo",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vue-tsc -b && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "@element-plus/icons-vue": "^2.3.1",
    "axios": "^1.7.7",
    "element-plus": "^2.8.6",
    "postcss-nesting": "^13.0.1",
    "pinia": "^2.2.4",
    "vue": "^3.5.12",
    "vue-router": "^4.4.5"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^5.1.4",
    "less": "^4.2.0",
    "sass": "^1.80.4",
    "typescript": "~5.6.2",
    "unplugin-auto-import": "^0.18.3",
    "unplugin-vue-components": "^0.27.4",
    "vite": "^5.4.9",
    "vue-tsc": "^2.1.6"
  }
}

需要把打包的build改掉,其他不动,因为不去掉,在打包的时候你会被折磨死

{
  "name": "vue3demo",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",// 修改这里
    "preview": "vite preview"
  },
  "dependencies": {
    "@element-plus/icons-vue": "^2.3.1",
    "axios": "^1.7.7",
    "element-plus": "^2.8.6",
    "postcss-nesting": "^13.0.1",
    "pinia": "^2.2.4",
    "vue": "^3.5.12",
    "vue-router": "^4.4.5"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^5.1.4",
    "less": "^4.2.0",
    "sass": "^1.80.4",
    "typescript": "~5.6.2",
    "unplugin-auto-import": "^0.18.3",
    "unplugin-vue-components": "^0.27.4",
    "vite": "^5.4.9",
    "vue-tsc": "^2.1.6"
  }
}

tsconfig.json

初始

{
  "files": [],
  "references": [
    { "path": "./tsconfig.app.json" },
    { "path": "./tsconfig.node.json" }
  ]
}

修改后:

{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "Node",
    "skipLibCheck": true,
    "strict": true,
    "jsx": "preserve",
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.d.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "Element-plus.d.ts"
  ],
  "vueCompilerOptions": {
    "target": 3
  }
}

App.vue

初始

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

<template>
  <div>
    <a href="https://vite.dev" target="_blank">
      <img src="/vite.svg" class="logo" alt="Vite logo" />
    </a>
    <a href="https://vuejs.org/" target="_blank">
      <img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
    </a>
  </div>
  <HelloWorld msg="Vite + Vue" />
</template>

<style scoped>
.logo {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
  transition: filter 300ms;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
  filter: drop-shadow(0 0 2em #42b883aa);
}
</style>

修改后

<script setup lang="ts">

</script>

<template>
  <router-view />
</template>

<style scoped>

</style>

api的api.ts

为了映射后端实体,我们先在src下新建bean文件夹,在里面建立一个User.ts
假设我后端是springboot项目,对应有登录之后返回用户信息,用户信息数据是:

@Data
public class User {
  private String userId;
  private String avatar;
  private String userName;
  private String password;
  private String email;
  private String phone;
  private String sex;
  private Integer age;
  private Integer status;
  @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
  private LocalDateTime createTime;
  @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
  private LocalDateTime updateTime;
}

那么我对应前端该User.ts代码为:

export interface User {
    userId: string;
    avatar?: string;
    userName: string;
    password: string;
    email?: string;
    phone?: string;
    sex?: string;
    age?: number;
    status?: number;
    createTime?: string;
    updateTime?: string;
}
// ?代表可以不赋值

在src下新建api文件夹,在里面新建api.ts

// 我这里是直接返回成功之后的参数,不超过的在request.ts统一报错了,大家可根据自己实际项目调整
import request from "@/request/request";
import { User} from "@/bean/User"
export const loginApi = {
    // 无参后端调用
    async test() {
        const res = (await request.get('/back/user/test')) as void
        return res.value as any
    },
    // 有参get请求
    async login(userId: string, password: string) {
        const res = (await  request.get('/sso/user/login?userId=' + userId + '&password=' + password)) as User
        return res.value as User
    },
    // 有参post请求
    async register(user: User) {
        const res = (await request.post('/sso/user/register', user)) as void
        return res.value as void
    },
}

vue界面新增

在src下新建view文件夹,在里面分别建立三个文件
login.vue
home.vue
notFound.vue

其中

login.vue代码

<template>
    <div class="login-container">
      <h1 class="title">{{ mainStore.title }}</h1>
      <form @submit.prevent="handleLogin" class="login-form">
        <div class="form-item">
          <label for="username">用户名</label>
          <input
            type="text"
            id="username"
            v-model="data.loginForm.username"
            required
            placeholder="请输入用户名"
          />
        </div>
        <div class="form-item">
          <label for="password">密码</label>
          <input
            type="password"
            id="password"
            v-model="data.loginForm.password"
            required
            placeholder="请输入密码"
          />
        </div>
        <button type="submit" class="login-button">登录</button>
      </form>
    </div>
  </template>
  <script setup lang="ts">
  import { reactive } from 'vue';
  import { useMain } from '@/store/home';
  import { useRouter } from 'vue-router';
  import { loginApi } from '@/api/api';
  import { User } from '@/bean/User';
  
  const router = useRouter();
  const mainStore = useMain();
  
  const data = reactive({
    loginForm: {
      username: '',
      password: '',
    }
  });
  
  const apiTest = () => {
    // 演示使用调用后端接口
    loginApi.login('用户名', '密码').then(val => {
        let user = val as User
        console.log('登录之后的用户信息:', user);
        
    })
  }

  const handleLogin = () => {
    mainStore.setUserName(data.loginForm.username);
    router.push({ path: '/home' });
  };
  </script>
  <style scoped>
  .login-container {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    height: 100vh;
    background: linear-gradient(135deg, #e0eafc, #cfdef3);
    font-family: 'Arial', sans-serif;
  }
  
  .title {
    font-size: 28px;
    font-weight: bold;
    margin-bottom: 30px;
    color: #333;
  }
  
  .login-form {
    display: flex;
    flex-direction: column;
    width: 350px;
    padding: 20px;
    border-radius: 8px;
    background-color: white;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
  }
  
  .form-item {
    margin-bottom: 20px;
  }
  
  label {
    margin-bottom: 8px;
    font-weight: bold;
    color: #555;
  }
  
  input {
    padding: 12px;
    border: 2px solid #ccc;
    border-radius: 5px;
    font-size: 16px;
    transition: border-color 0.3s;
  }
  
  input:focus {
    border-color: #007bff;
    outline: none;
  }
  
  .login-button {
    padding: 12px;
    background-color: #007bff;
    color: white;
    border: none;
    border-radius: 5px;
    font-size: 16px;
    cursor: pointer;
    transition: background-color 0.3s, transform 0.2s;
  }
  
  .login-button:hover {
    background-color: #0056b3;
    transform: translateY(-2px);
  }
  </style>

主界面home.vue

<template>
    <el-container style="height: 100vh;">
        <el-aside width="200px" class="aside">
            <el-menu :default-active="activeMenu" class="sidebar" @select="handleMenuSelect">
                <el-menu-item index="1" class="menu-item">仪表盘</el-menu-item>
                <el-menu-item index="2" class="menu-item">用户管理</el-menu-item>
                <el-menu-item index="3" class="menu-item">产品管理</el-menu-item>
                <el-menu-item index="4" class="menu-item">订单管理</el-menu-item>
            </el-menu>
        </el-aside>

        <el-container style="width: 100%;">
            <el-header class="header">
                <h2>后台管理系统</h2>
                <el-button @click="handleLogout" type="warning" style="float: right;">注销</el-button>
            </el-header>

            <el-main>
                <h3>{{ currentMenu }}</h3>
                <p>登录用户: {{ mainStore.userName }}</p>
            </el-main>
        </el-container>
    </el-container>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useMain } from '@/store/home';
import { useRouter } from 'vue-router';

const router = useRouter();
const mainStore = useMain();
const activeMenu = ref('1'); 
const currentMenu = ref('仪表盘');

const handleMenuSelect = (index: string) => {
    activeMenu.value = index;
    switch (index) {
        case '1':
            currentMenu.value = '仪表盘';
            break;
        case '2':
            currentMenu.value = '用户管理';
            break;
        case '3':
            currentMenu.value = '产品管理';
            break;
        case '4':
            currentMenu.value = '订单管理';
            break;
    }
};

// 处理注销
const handleLogout = () => {
    console.log('用户注销');
    router.push({path: '/login'})
};
</script>
<style scoped>
.aside {
    background-color: #2c3e50;
    color: #ecf0f1;
    width: 200px; /* 侧边栏的固定宽度 */
}

.header {
    background-color: #34495e;
    color: #ecf0f1;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 20px;
    flex: 0 0 auto; /* 固定header大小 */
}

.el-main {
    flex: 1; 
    overflow-y: auto;
}

.sidebar .el-menu-item {
    color: #ecf0f1;
}

.sidebar .el-menu-item:hover {
    background-color: #1abc9c;
}

.sidebar .el-menu-item.is-active {
    background-color: #16a085;
}

h2 {
    margin: 0;
}

h3 {
    margin: 20px 0;
}
</style>

notFound.vue界面

<template>
    <div class="not-found-container">
      <el-result
        icon="error"
        title="404"
        sub-title="抱歉,您访问的页面不存在。"
        class="result"
      >
        <template #extra>
          <el-button type="primary" @click="goBack">返回上一页</el-button>
          <el-button @click="goHome">返回首页</el-button>
        </template>
      </el-result>
    </div>
  </template>
  <script setup lang="ts">
    import { useRouter } from 'vue-router';
    const router = useRouter();
  const goBack = () => {
    router.push({path: '/'})
  };
  
  const goHome = () => {
    router.push({path: '/home'})
  };
  </script>
<style scoped>
  .not-found-container {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    background-color: #f0f2f5;
  }
  
  .result {
    text-align: center;
  }
  
  .el-button {
    margin-left: 10px;
  }
  </style>

git链接

gitee项目demo: vue3Demo

结语

以上就是我进行搭建vue3项目的教学文章,如有遗漏会进行补充

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值