vue3项目学习:vite+vue3+js项目

创建vue3项目

在vue-cli的主页可以看到目前vue-cli处于维护状态,而且官方也推荐vite创建vue项目。但是总的来说我感觉vue-cli创建vue项目更加简单方便。

1.使用vue-cli创建

1.安装vue-cli

先查看是否安装vue-cli
在cmd窗口输入vue -V查看版本,如果出现
在这里插入图片描述
则说明存在vue-cli,如果出现
在这里插入图片描述

则需要安装vue-cli

npm i -g @vue/cli

如果需要升级版本则可以输入

npm update -g @vue/cli

2.创建vue3项目

输入

vue create vue3-demo

即可进入模板选择
在这里插入图片描述
等待生成
在这里插入图片描述
创建成功后进入创建好的项目文件夹中,在cmd窗口输入npm run serve即可启动项目。
可以看到我们创建好的项目:
在这里插入图片描述

2.使用vite创建

1.创建vite项目

npm create vite@latest

在这里插入图片描述
选项很简单,只有名称,框架,js还是ts,我这里先用的js
创建好的项目目录:
在这里插入图片描述
输入:npm inpm run dev启动

3.配置vue3项目

1.修改vite.config.js文件

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

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  base: './', //打包后静态资源读取
  server: {
    host: '0.0.0.0',
    // port: 8080,
    open: true,
  },
  resolve: {
    //别名配置,引用src路径下的东西可以通过@如:import Layout from '@/layout/index.vue'
    alias: [
      {
        find: '@',
        replacement: resolve(__dirname, 'src'),
      },
    ],
  },
});

2.配置vite环境变量

默认情况下,开发服务器 (dev 命令) 运行在 development (开发) 模式,而 build 命令则运行在 production (生产) 模式。

vite 提供了两种模式:开发模式(development)和生产模式(production)
在根目录创建.env.development

NODE_ENV=development
​
VITE_APP_WEB_URL= 'YOUR WEB URL'

在根目录创建.env.production

NODE_ENV=production
​
VITE_APP_WEB_URL= 'YOUR WEB URL'

使用:

const BaseUrl = import.meta.env.VITE_APP_WEB_URL

3.配置代理(处理跨域)

修改vite.config.js文件中的server

  server: {
    // 配置前端服务地址和端口
    //服务器主机名
    host: '0.0.0.0',
    //端口号
    // port: 8080,
    //设为 true 时若端口已被占用则会直接退出,而不是尝试下一个可用端口
    strictPort: false,
    //服务器启动时自动在浏览器中打开应用程序,当此值为字符串时,会被用作 URL 的路径名
    open: false,
    //自定义代理规则
    proxy: {
      // 选项写法
      '/api': {
        target: 'http://xxx.xxx.xxx.xxx:xxxx',
        changeOrigin: true,
        rewrite: path => path.replace(/^\/api/, '')
      }
    }
  },

4.添加css预处理器sass

npm install -D sass sass-loader

5.添加Vue Router

npm install vue-router@4

src目录下创建router/index.js

import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';

const routes = [];

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

export default router;

main.js文件中引入

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

const app = createApp(App);
app.use(router);
app.mount('#app');

6.封装axios

npm install axios

src创建utils/request.js

import axios from 'axios';
import { ElMessage } from 'element-plus';
import { useUserStore } from '@/store/user';

const service = axios.create({
  baseURL: import.meta.env.VUE_APP_BASE_API,
  timeout: 5000,
});

// 请求拦截器
service.interceptors.request.use(
  (config) => {
    // 注入token;
    const userStore = useUserStore();
    if (userStore.token) {
      // 如果存在token则注入token
      config.headers.Authorization = userStore.token;
    }
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

// 响应拦截器
service.interceptors.response.use(
  (response) => {
    if (response.status === 200) {
      return response.data;
    } else {
      ElMessage.error(response.statusText);
      return Promise.reject(new Error(response.statusText));
    }
  },
  (error) => {
    if (
      error.response &&
      error.response.data &&
      error.response.status === 424
    ) {
      store.dispatch('user/logout');
    }
    ElMessage.error(error.response.data.msg);
    return Promise.reject(error);
  }
);

export default service;


使用:创建src/api/index.js

import request from '@/utils/request';

export function DataRequest(userInfo) {
  return request({
    url: '/api/admin/oauth2/token',
    method: 'POST',
    headers: {
      Authorization: '',
      'Content-Type': 'application/x-www-form-urlencoded',
      Accept: '*/*',
    },
    data: {
      username: userInfo.username,
    },
  });
}
export function paramPost(userInfo) {
  return request({
    url: '/api/admin/oauth2/token',
    method: 'POST',
    headers: {
      Authorization: '',
      'Content-Type': 'application/x-www-form-urlencoded',
      Accept: '*/*',
    },
    params: {
      username: userInfo.username
    },
  });
}

7.Vue 存储库Pinia

其实pinia就是vuex的升级版,pina对vuex进行了升级,现在只有state,getters,actions

npm install pinia

创建文件夹src/store
main.js中引入:

import { createPinia } from 'pinia'

app.use(createPinia())

在store中创建想要存的,比如user.js

import { defineStore } from 'pinia';

// 第一个参数是应用程序中 store 的唯一 id
export const useUsersStore = defineStore('users', {
  state: () => {
    return {
      name: 'userName',
      age: 25,
      sex: '男',
    };
  },
  getters: {
    getAddAge: (state) => {
      return (num) => state.age + num;
    },
    getNameAndAge() {
      return this.name + this.getAddAge; // 调用其它getter
    },
  },
  actions: {
    saveName(name) {
      this.name = name;
    },
  },
});

8.配置svg

安装依赖

npm install vite-plugin-svg-icons -D

创建components/SvgIcon/index.vue

<template>
  <svg :class="svgClass" aria-hidden="true">
    <use :xlink:href="iconName" />
  </svg>
</template>

<script setup lang='ts'>
import { computed } from 'vue'
const props = defineProps({
  iconClass: {
    type: String,
    required: true,
  },
  className: {
    type: String,
    default: () => '',
  },
})

const iconName = computed(() => {
  console.log(`#icon-${props.iconClass}`)
  return `#icon-${props.iconClass}`
})
const svgClass = computed(() => {
  return props.className ? 'svg-icon ' + props.className : 'svg-icon'
})
</script>
<style scoped lang="scss">
.svg-icon {
  width: 1em;
  height: 1em;
  vertical-align: -0.15em;
  fill: currentColor;
  overflow: hidden;
}

.svg-external-icon {
  background-color: currentColor;
  mask-size: cover !important;
  display: inline-block;
}
</style>

创建src/icons/svg文件夹存放svg文件
创建src/icons/index.js注册svg
在main.js中引入

// 引入svg组件
import IconSvg from '@/icons/index'
import "virtual:svg-icons-register"
app.use(IconSvg)

在vite.config.js中引入

import path from 'path'
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'

  plugins: [
    // 注册所有的svg文件生成svg雪碧图
    createSvgIconsPlugin({
      iconDirs: [path.resolve(process.cwd(), "src/icons/svg")], // icon存放的目录
      symbolId: "icon-[name]", // symbol的id
      inject: "body-last", // 插入的位置
      customDomId: "__svg__icons__dom__" // svg的id
    })

  ],

使用:

 <svg-icon icon-class="login-user"></svg-icon>

9.引入elementplus

elementPlus

npm install element-plus --save
  1. 按需引入(推荐)
npm install -D unplugin-vue-components unplugin-auto-import
// vite.config.ts
import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  // ...
  plugins: [
    // ...
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
})
  1. 全局引入(不推荐)
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'

const app = createApp(App)

app.use(ElementPlus)
app.mount('#app')

10.axios封装后使用多个BASE_URL代理

例如我有两个baseUrl需要进行代理,但是axios封装好了默认使用其中一个,此时可以使用axios的baseURL的功能:
.env.development中写入两个URL:

NODE_ENV=development
​
VUE_APP_BASE_API="'api'"
VUE_APP_GD_API="'gdurl'"

在vite.config.js中:

    proxy: {
      // 选项写法
      '/api': {
        target: 'http://4xxx.xxx.xxx.xxx:20010',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      },
      '/gdurl': {
        target: 'https://restapi.amap.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/gdurl/, '')
      },
    },

使用:
在axios的封装文件中使用默认的url:
在这里插入图片描述
在需要使用不是BASE_API的接口处:
在这里插入图片描述

4.初始化项目结构

  1. App.vue初始化
<template>
  <router-view />
</template>

<style lang="scss"></style>
  1. 删除views文件夹中所有.vue文件
  2. 删除components文件夹下所有.vue文件
  3. 初始化router/index.js中的代码
import { createRouter, createWebHashHistory } from 'vue-router'

const routes = [

]

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

export default router

5.创建layout文件夹

1.创建src/layout文件夹

  • layout
    • components
      • Sidebar
      • Navbar
      • AppMain
    • index.vue

index.vue导入组件

<template>
  <div class="app-wrapper">
    <div class="fixed-header">
      <navbar class="navbar-container"></navbar>
    </div>
    <div class="main-container">
      <sidebar
        class="sidebar-container"
        :style="{ backgroundColor: variables.menuBg }"
      ></sidebar>
      <tags-view class="tags-container"></tags-view>
      <el-scrollbar>
        <app-main></app-main>
      </el-scrollbar>
    </div>
  </div>
</template>

<script setup>
import Navbar from './components/Navbar/index.vue';
import Sidebar from './components/Sidebar/index.vue';
import AppMain from './components/AppMain/index.vue';
import variables from '@/styles/variables.module.scss';
import TagsView from './components/TagsView/index.vue';
</script>

<style lang="scss" scoped>
@import '../styles/mixin.scss';
@import '../styles/variables.module.scss';
.app-wrapper {
  @include clearfix;
  @include relative;
}
.fixed-header {
  position: fixed;
  top: 0;
  right: 0;
  left: 0;
  z-index: 9;
  width: 100%;
}
</style>


2.创建layout通用的css文件

1.variables.module.scss

在src/styles中创建variables.module.scss文件定义常用的常量并导出

// navbar
$navbarHeight: 50px;
// tags-view
$tagsHeight: 35px;
// sidebar
$menuText: #bfcbd9;
$menuActiveText: #ffffff;
$subMenuActiveText: #f4f4f5;
$menuBg: #304156;
$menuHover: #263445;
$subMenuBg: #1f2d3d;
$subMenuHover: #001528;
$sideBarWidth: 210px;

// JS 与 scss 共享变量,在 scss 中通过 :export 进行导出,在 js 中可通过 ESM 进行导入
:export {
  menuText: $menuText ;
  menuActiveText: $menuActiveText;
  subMenuActiveText: $subMenuActiveText;
  menuBg: $menuBg;
  menuHover: $menuHover;
  subMenuBg: $subMenuBg;
  subMenuHover: $subMenuHover;
  sideBarWidth: $sideBarWidth;
}
2.mixin.scss

定义通用样式

// 清除浮动
@mixin clearfix {
  &:after {
    content: "";
    display: table;
    clear: both;
  }
}

// 滚动条
@mixin scrollBar {
  &::-webkit-scrollbar-track-piece {
    background: #d3dce6;
  }

  &::-webkit-scrollbar {
    width: 6px;
  }

  &::-webkit-scrollbar-thumb {
    background: #99a9bf;
    border-radius: 20px;
  }
}

// position
@mixin relative {
  position: relative;
  width: 100%;
  height: 100%;
}
3.layout.scss
#app {
  .navbar-container {
    height: $navbarHeight;
    overflow: hidden;
    position: relative;
    background-color: #fff;
    box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
  }

  .main-container {
    height: calc(100% - $navbarHeight);
    transition: margin-left 0.28s;
    padding-top: $navbarHeight;
    position: relative;
    overflow: hidden;
  }

  .app-main {
    width: 100%;
    min-height: calc(100vh - calc($navbarHeight + calc($tagsHeight + 1px)));
    position: relative;
    overflow: hidden;
    padding: 10px 0 10px calc($sideBarWidth + 10px);
    box-sizing: border-box;
    background-color: var(--el-bg-color-page);
  }

  .tags-container {
    width: 100%;
    height: $tagsHeight;
    background-color: var(--el-bg-color);
    border: 1px solid var(--el-border-color-light);
    box-shadow: 0 1px 1px var(--el-box-shadow-light);
    padding-left: $sideBarWidth;
  }

  .sidebar-container {
    transition: width 0.28s;
    width: $sideBarWidth !important;
    height: calc(100% - $navbarHeight);
    position: fixed;
    top: $navbarHeight;
    bottom: 0;
    left: 0;
    z-index: 1001;
    overflow: hidden;

    // 重置 element-plus 的css
    .horizontal-collapse-transition {
      transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
    }

    .scrollbar-wrapper {
      overflow-x: hidden !important;
    }

    .el-scrollbar__bar.is-vertical {
      right: 0px;
    }

    .el-scrollbar {
      height: 100%;
    }

    &.has-logoel-scrollbar {
      height: calc(100% - 50px);
    }

    .is-horizontal {
      display: none;
    }

    a {
      display: inline-block;
      width: 100%;
      overflow: hidden;
    }

    .svg-icon {
      margin-right: 16px;
    }

    .sub-el-icon {
      margin-right: 12px;
      margin-left: -2px;
    }

    .el-menu {
      border: none;
      height: 100%;
      width: 100% !important;
    }

    .is-active>.el-submenu__title {
      color: $subMenuActiveText !important;
    }

    & .nest-menu .el-submenu>.el-submenu__title,
    & .el-submenu .el-menu-item {
      min-width: $sideBarWidth !important;
    }
  }

  .hideSidebar {
    .sidebar-container {
      width: 54px !important;
    }

    .main-container {
      margin-left: 54px;
    }

    .submenu-title-noDropdown {
      padding: 0 !important;
      position: relative;

      .el-tooltip {
        padding: 0 !important;

        .svg-icon {
          margin-left: 20px;
        }

        .sub-el-icon {
          margin-left: 19px;
        }
      }
    }

    .el-submenu {
      overflow: hidden;

      &>.el-submenu__title {
        padding: 0 !important;

        .svg-icon {
          margin-left: 20px;
        }

        .sub-el-icon {
          margin-left: 19px;
        }

        .el-submenu__icon-arrow {
          display: none;
        }
      }
    }

    .el-menu--collapse {
      .el-submenu {
        &>.el-submenu__title {
          &>span {
            height: 0;
            width: 0;
            overflow: hidden;
            visibility: hidden;
            display: inline-block;
          }
        }
      }
    }
  }

  .el-menu--collapse .el-menu .el-submenu {
    min-width: $sideBarWidth !important;
  }

  .withoutAnimetion {

    .main-container,
    .sidebar-container {
      transition: none;
    }
  }
}

.el-menu--vertical {
  &>.el-menu {
    .svg-icon {
      margin-right: 16px;
    }

    .sub-el-icon {
      margin-right: 12px;
      margin-left: -2px;
    }
  }

  // 菜单项过长时
  >.el-menu--popup {
    max-height: 100vh;
    overflow-y: auto;

    &::-webkit-scrollbar-track-piece {
      background: #d3dce6;
    }

    &::-webkit-scrollbar {
      width: 6px;
    }

    &::-webkit-scrollbar-thumb {
      background: #99a9bf;
      border-radius: 20px;
    }
  }
}
4.在index.scss中导入
@import './variables.module.scss';
@import './mixin.scss';
@import './layout.scss';

html,
body {
  height: 100%;
  margin: 0;
  padding: 0;
  -moz-osx-font-smoothing: grayscale;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei,Arial, sans-serif;
  position: relative;
}

#app {
  height: 100%;
}

*,
*:before,
*:after {
  box-sizing: inherit;
  margin: 0;
  padding: 0;
}

a:focus,
a:active {
  outline: none;
}

a,
a:focus,
a:hover {
  cursor: pointer;
  color: inherit;
  text-decoration: none;
}

div:focus {
  outline: none;
}

.clearfix {
  &:after {
    visibility: hidden;
    display: block;
    font-size: 0;
    content: '';
    clear: both;
    height: 0;
  }
}

在main.js中导入

import ‘@/styles/index.scss’

5.在layout/index.vue中引入并定义sidebar的动态背景颜色
<template>
  <div class="app-wrapper">
    <div class="fixed-header">
      <navbar class="navbar-container"></navbar>
    </div>
    <div class="main-container">
      <sidebar
        class="sidebar-container"
        :style="{ backgroundColor: variables.menuBg }"
      ></sidebar>
      <tags-view class="tags-container"></tags-view>
      <el-scrollbar>
        <app-main></app-main>
      </el-scrollbar>
    </div>
  </div>
</template>

<script setup>
import Navbar from './components/Navbar/index.vue';
import Sidebar from './components/Sidebar/index.vue';
import AppMain from './components/AppMain/index.vue';
import variables from '@/styles/variables.module.scss';
import TagsView from './components/TagsView/index.vue';
</script>

<style lang="scss" scoped>
@import '../styles/mixin.scss';
@import '../styles/variables.module.scss';
.app-wrapper {
  @include clearfix;
  @include relative;
}
.fixed-header {
  position: fixed;
  top: 0;
  right: 0;
  left: 0;
  z-index: 9;
  width: 100%;
}
</style>


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

别出声~~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值