美文网首页react & vue & angular
Vue3.0 Vite + windicss + ...实战项目

Vue3.0 Vite + windicss + ...实战项目

作者: coderhzc | 来源:发表于2022-10-10 22:11 被阅读0次
    1. Vite工具的安装:
    npm install vite -g  // 全局安装
    npm install vite -D // 局部安装
    
    1. Vite冷服务启动,ES6 import
    2. 开发中热更新
    3. 按需进行编译, 不会刷新全部DOM

    4.1 npm下载命令: npm init vite-app 项目名称

    实际截图:

    image.png

    4.2. 要安装对应的依赖包和运行脚本:
    命令:

    依赖包: npm install 
    运行脚本: npm run dev (or `yarn dev`)
    
    1. yarn 下载命令: yarn create vite-app 项目名称:
      依赖包: yarn install
      运行脚本: yarn dev

    Vite 只支持vue 3.0

    Vite 配置文件和别名的设置

    在项目的最外层新建一个文件叫:Vite.config.js 文件

    const { resolve } = require("path")
    export default {
      alias: {'/@/':resolve(__dirname,"src")}
    }
    

    在其他页面的使用: /@/

    import HelloWorld from "/@/components/HelloWorld.vue"
    

    二. windicss 安装

    windicss 链接地址

    image.png

    三.然后对着文档修改对应的标签的样式了

    使用windicss修改具体对应样式

    image.png

    三.一 具体修改一个按钮的用法:

    bg-purplr-500: 代表背景颜色;
    text-indigo-50: 代表文字的大小;
    px-4: 水平的内边距为4;
    py-2: 垂直的内边距为2;
    rounded-full: 代表按钮的圆角
    hover:(bg-purple-900): 代表鼠标经过时候的背景颜色为最深的紫色
    transition-all: 过渡动画
    duration-700:过渡时间 
    ......
    看文档说明
    
    image.png
    image.png

    三.二 向上面那种写的话 比较长 也很繁琐,可以单独抽离出来

    <button class="btn"></button>
    
    <style scoped>
    .btn {
      // @apply 这个是windicss中的一个指令
      @apply bg-purple-500 text-indigo-50 px-4 py-2 rounded-full transition-all duration-700 hover:(bg-purple-900)
    }
    </style>
    

    实际截图

    image.png

    四.配置文件路径别名

    vite.config.js 文件的配置
    import { defineConfig } from 'vite'
    import vue from '@vitejs/plugin-vue'
    // 1.1 windicss 插件的引入
    import WindiCSS from 'vite-plugin-windicss'
    
    // 2.1配置路径别名
    import path from "path"
    // https://vitejs.dev/config/
    export default defineConfig({
      resolve: {
        alias: {
          // 2.2 ~:代表src文件夹: path.resolve(__dirname, "src"):指定具体的文件名路径
          "~": path.resolve(__dirname, "src")
        }
      },
      plugins: [
        vue(),
    
        // 1.2. 使用Windicss 函数
        WindiCSS()
      ]
    })
    

    五.响应式布局

    <template>
      <el-row class="min-h-screen bg-indigo-500">
        <!-- 
            响应式布局: 
            lg: 当尺寸大于1200以上的时候 才会显示 左侧占16 
            md: 当尺寸大于992以上的时候 才会显示 左侧占整个屏幕的一半 
        -->
        <el-col :lg="16" :md="12" class="flex items-center justify-center ">
          <div>
            <div class="font-bold text-5xl text-light-50 mb-4">欢迎光临</div>
            <div class="text-gray-200 text-sm">此站点是《Vue3+vite商城开发》视频课程的演示地址</div>
          </div>
        </el-col>
        <!-- 
            响应式布局: 
            响应式布局: 
            lg: 当尺寸大于1200以上的时候 才会显示 右侧侧占8
            md: 当尺寸大于992以上的时候 才会显示 右侧占整个屏幕的一半 
         -->
        <el-col :lg="8" :md="12" class=" bg-light-50 flex items-center justify-center flex-col">
          <h2 class="font-bold text-3xl text-gray-800">欢迎回来</h2>
          <div class="flex items-center justify-center my-5 text-gray-300 space-y-2">
            <span class="h-[1px] w-16 bg-gray-200"></span>
            <span >账号密码登录</span>
            <span class="h-[1px] w-16 bg-gray-200"></span>
          </div>
    
          <el-from :model="from" label-width="120px" class="w-[250px]">
            <el-form-item label="账号">
              <el-input v-model="from.username" placeholder="请输入用户名"></el-input>
            </el-form-item>
    
            <el-form-item label="密码">
              <el-input v-model="from.password" placeholder="请输入密码"></el-input>
            </el-form-item>
            <el-form-item>
              <el-button class="w-[250px]" type="primary" round color="#626aef" @click="onSubmit">登 录</el-button>
            </el-form-item>
          </el-from>
        </el-col>
      </el-row>
    </template>
    
    <script setup>
    import { reactive } from "vue "
    
    const from = reactive({
      username: "",
      password: ""
    })
    
    const onSubmit = () => {
      console.log("提交成功~");
    }
    </script>
    
    <style>
    
    </style>
    

    六 抽离windicss 定义的class类

    在标签上定义了太多的class类看着很繁琐 不简洁,
    1. 在标签上定义一个class 例如:
    // <el-col :lg="8" :md="12" class="bg-light-50 flex items-center justify-center flex-col"></el-col>
     <el-col :lg="8" :md="12" class="col-style"></el-col>
    
    2. 在 style 中使用 关键字 @apply 对应右边就是上面标签的的class中的类型
    <style>
    .col-style {
      @apply bg-light-50 flex items-center justify-center flex-col;
    }
    
    </style>
    

    七.使用cookie储存后天返回的token

    vueuse cookie的使用

    // @vueuse/integrations
    1. 首先安装@vueuse/integrations 下面的要依赖上面这个库: npm i @vueuse/integrations
    2. 然后安装 usecookie: npm i universal-cookie
    
    3. 使用useCookie (在需要使用的页面引入这个库)
    import { useCookies } from '@vueuse/integrations/useCookies';
    
    4. 具体使用
    const cookie = useCookies()
    console.log(cookie);
    
    image.png
    5. 完整版代码如下:
    <template>
      <el-button @click="setCookie">设置</el-button>
      <el-button @click="getCookie">读取</el-button>
      <el-button @click="deleteCookie">删除</el-button>
    </template>
    
    <script setup>
    // 1. 引入
    import { useCookies } from '@vueuse/integrations/useCookies';
    
    const cookie = useCookies()
    console.log(cookie);
    
    const setCookie = () => {
      // cookie.set("需要设置储存cookie的名称", "需要存储的值")
      cookie.set("admin-cookie", "123456")
    }
    
    const getCookie = () => {
      // cookie.get("获取: 需要你当时设置存储时的键")
      cookie.get("admin-cookie")
    }
    
    const deleteCookie = () => {
      // cookie.remove("删除: 需要你当时设置存储时的键")
      cookie.remove("admin-cookie")
    }
    </script>
    

    八.对axios 对后端返回的数据进行处理

    请求拦截器的数据处理

    image.png

    响应拦截器的数据处理

    image.png image.png

    具体代码如下:

    import axios from "axios";
    // 引入消息提示组件
    import { ElNotification } from "element-plus";
    // 使用useCookie 在请求拦截器上面添加请求头
    import { useCookies } from "@vueuse/integrations/useCookies";
    const cookie = useCookies();
    const service = axios.create({
      baseURL: "/api",
    });
    
    // 添加请求拦截器
    service.interceptors.request.use(
      function (config) {
        // console.log(config);
        // 往header头自动添加token
        const token = cookie.get("admin-token");
        if (token) {
          config.headers["token"] = token;
        }
        return config;
      },
      function (error) {
        // 对请求错误做些什么
        return Promise.reject(error);
      }
    );
    
    // 添加响应拦截器
    service.interceptors.response.use(
      function (response) {
        // 对后端返回的数据进行处理
        return response.data.data;
      },
      function (error) {
        // 对响应错误做点什么
        return ElNotification({
          message: error.response.data.msg || "请求失败",
          type: "error",
          duration: 2000,
        });
      }
    );
    
    export default service;
    
    

    九.如何防止Vuex中的数据刷新页面 数据丢失问题

    需求: 比如在登录成功以后拿到后台返回的token,然后拿着后端返回的token,再去请求当前用户的信息,把请求过来的当前用户信息保存到vuex中, 此时确实是保存了,在页面上使用 $store.state.user 可以获取到请求的用户信息数据,但是你刷新一下浏览器,vuex中的state里面的数据就会消失了,此时该怎么办呢?
    

    错误做法:

    image.png

    正确做法

    image.png

    这个在写ts 项目的时候 我以前记录过如何处理vuex中数据丢失,这里不过多写了

    十.如何点击回车键实现登录

    1. 引入两个生命周期函数
    import { reactive, ref, onMounted, onBeforeUnmount } from "vue";
    
    2. 登录函数
    // 登录
    const onSubmit = () => {}
    
    3. 添加键盘回车 登录
    const onKeyUp = (e) => {
      console.log(e);
      if (e.key == 'Enter') onSubmit()
    }
    
    4. 当页面加载完成时回调
    onMounted(() => {
      document.addEventListener("keyup", onKeyUp)
    })
    
    5. 当页面销毁时,移除键盘监听
    onBeforeUnmount(() => {
      document.removeEventListener("keyup", onKeyUp)
    })
    

    十一.如何实现 只要路由发生了变化 要实现loading效果(全局loading效果)

    1. 需要安装一个进度loading 插件
    npm install nprogress
    
    2. 在main.js文件中引入nprogress  样式
    import "nprogress/nprogress.css"
    
    3. 在utils.js 文件中引入nprogress  实例
    import nprogress  from "nprogress"
    
    4. 在utils.js 文件中开始封装这个全局的loading
    import nprogress  from "nprogress"
    // 显示全屏loading 
    export const showFullLoading = () => {
      nprogress.start()
    }
    // 隐藏全屏loading
    export const hideFullLoading = () => {
      nprogress.done()
    }
    
    4.1 显示全屏loading 
    export const showFullLoading = () => {
      nprogress.start()
    }
    4.2 隐藏全屏loading
    export const hideFullLoading = () => {
      nprogress.done()
    }
    
    5.  只要路由发生了变化 要实现loading效果,那么就在路由前置导航守卫中实现这个功能
    // 专门处理权限相关的代码
    
    // 1. 引入路由实例
    import router from "~/router";
    import { getToken } from "~/commonCookie/Auth";
    import { Toast,showFullLoading,hideFullLoading } from "~/commonCookie/utils";
    import store from "./store";
    
    // 前置路由守卫
    router.beforeEach(async (to, from, next) => {
      // 只要路由发生了变化 要实现loading效果
      showFullLoading()
    
    
      const token = getToken();
      if (!token && to.path !== "/login") {
        Toast("请先登录", "error");
        return next({ path: "/login" });
      }
      // 防止重复登录
      if (token && to.path == "/login") {
        Toast("请务重复登录", "error");
        return next({ path: from.path ? from.path : "/" });
      }
    
      // 如果用户登录了 ,自动获取用户信息, 并储存在vuex中,
      // 这种是为了防止 第一次可以获取到数据, 但是在页面刷新以后数据会丢失
      if (token) {
        await store.dispatch("getInfo");
      }
      next();
    });
    
    // 全局后置守卫
    router.afterEach((to,from)=> {
      // 当路由加载完成以后 关闭全局的进度条
      hideFullLoading()
    })
    
    

    详细流程如下:

    image.png

    十二.如何切换路由的时候动态改变浏览器顶部的页面标题呢? 例如:

    image.png image.png

    十三.el-dropdown组件的使用

    1. 如何监听到下拉菜单的item项 的事件监听
        -- 他有一个 @command="handleCommand" 事件
    2. 绑定了command事件后 必须要在item项中绑定command属性才会生效
     <el-dropdown-item command="rePassword">修改密码</el-dropdown-item>
    
    // 监听下拉菜单的时间
    const handleCommand = (e) => {
      console.log(e);
    }
    

    具体代码操作如下:

    image.png

    十四.实现前端手动刷新

    <el-button @click="handelRefresh"></el-button>
    const handleRefresh = () => location.reload();
    

    十五.前端实现全屏

    VueUse的核心安装包
    useFullscreen 使用地址

    1.  VueUse的核心安装包:  npm i @vueuse/core
    
    2. 在当前需要使用全屏的页面引入
    import { useFullscreen } from '@vueuse/core'
    
    页面标签
    <el-button @click="handelRefresh"></el-button>
    
    
    /**
     * isFullscreen: 是否全屏
     * 
     * enter: 开始全屏
     * 
     * exit: 退出全屏
     * 
     * toggle: 来回切换
     * 
     * **/ 
    const { isFullscreen, enter, exit, toggle } = useFullscreen()
    

    具体代码如下:

    image.png

    十六.如何封装一个抽屉组件呢?

    1. 基本使用:
    <el-button @click="handleCommand ">打开抽屉</el-button>
    <el-drawer v-model="showDrawer" size="45%" title="修改密码" :close-on-click-modal="false">
        <el-form :rules="rules" :model="from" label-width="100px" ref="formRef">
          <el-form-item label="旧密码" prop="oldpassword">
            <el-input v-model="from.oldpassword" placeholder="请输入旧密码"></el-input>
          </el-form-item>
    
          <el-form-item label="新密码" prop="password">
            <el-input v-model="from.password" show-password type="password" placeholder="请输入密码"></el-input>
          </el-form-item>
    
          <el-form-item label="确认密码" prop="repassword">
            <el-input v-model="from.repassword" show-password type="password" placeholder="请输入密码"></el-input>
          </el-form-item>
    
          <el-form-item class="py-6">
            <el-button :loading="loading" type="primary" @click="onSubmit">提交
            </el-button>
          </el-form-item>
        </el-form>
      </el-drawer> 
    <script setup>
    // 打开抽屉
    const handleCommand = (e) => {
     showDrawer.value = true;
    }
    const showDrawer = ref(false); // 修改密码
    const from = reactive({
      oldpassword: "",
      password: "",
      repassword: ""
    })
    
    // 表达校验
    const rules = reactive({
      oldpassword: [
        { required: true, message: '旧密码不能为空', trigger: 'blur' },
        { min: 3, max: 15, message: '旧密码的字符长度为3-15个字符', trigger: 'blur' },
      ],
      password: [
        { required: true, message: '新密码不能为空', trigger: 'blur' },
        { min: 3, max: 15, message: '新密码的字符长度为3-15个字符', trigger: 'blur' },
      ],
      repassword: [
        { required: true, message: '确认密码不能为空', trigger: 'blur' },
        { min: 3, max: 15, message: '确认密码的字符长度为3-15个字符', trigger: 'blur' },
      ],
    })
    const formRef = ref(null);
    const loading = ref(false)
    
    const onSubmit = () => {
      formRef.value.validate((valid) => {
        if (!valid) {
          return false
        }
        // loading.value = true
        updatePassword(from).then(res => {
          Toast("修改密码成功,请重新登录")
          store.dispatch("logout");
          // 跳转回登录页面
          router.push("/login")
        }).finally(() => {
    
          loading.value = false
        })
      })
    }
    </script>
    
    2. 封装组件
    /**
     * defineExpose: 
     * 向父组件暴露以下方法: (因为官方文档记载 在<script setup> 组件默认是关闭状态的, 不会暴露任何在<script setup> 中声明的绑定)
     *  
     * 为了在<script setup> 组件中明确要暴露出去的属性,使用 defineExpose编译器
     * 
     * <script setup>
       import { ref } from 'vue'
    
       const a = 1
       const b = ref(2)
    
       defineExpose({
        a,
        b
       })
     * 
     * 可以参考官方文档: https://cn.vuejs.org/api/sfc-script-setup.html#defineexpose
     * 
     * **/
    1.FormDrawer.vue 组件 封装一个完整版抽屉组件
    <template>
      <el-drawer v-model="showDrawer" :size="size" :title="title" :close-on-click-modal="false" :destroy-on-close="destroyOnClose">
        <div class="form-drawer">
          <div class="body">
            <slot></slot>
          </div>
          <div class="actions">
            <el-button type="primary" :loading="loading" @click="submit">{{ confirmText }}</el-button>
            <el-button type="default" @click="close">取消</el-button>
          </div>
        </div>
      </el-drawer>
    </template>
    
    <script setup>
    import { ref } from "vue";
    
    const showDrawer = ref(false);
    
    // 打开
    const open = () => showDrawer.value = true;
    
    // 关闭
    const close = () => showDrawer.value = false;
    
    // defineProps父组件向子组件传递动态参数 只能在 script setup 中使用的
    const props = defineProps({
      title: String,
      size: {
        type: String,
        default: "45%"
      },
    
      destroyOnClose: {
        type: Boolean,
        default: false
      },
      confirmText: {
        type:String,
        default: "提交"
      },
    
      loading:{
        type: Boolean,
        default: false
      }
    })
    
    // defineEmits: 事件的提交 只能在 script setup 中使用的
    const emit = defineEmits(['submit']);
    const submit = () => emit("submit")
    
    // 使用 script setup 的组件是默认关闭的——即通过模板引用或者 $parent 链获取到的组件的公开实例,不会暴露任何在 script setup 中声明的绑定。
    // 可以通过 defineExpose 编译器宏来显式指定在 script setup 组件中要暴露出去的属性:
    defineExpose({
      open,
      close
    })
    </script>
    
    <style>
    .form-drawer {
      width: 100%;
      height: 100%;
      position: relative;
      @apply flex flex-col;
    }
    
    .form-drawer .body {
      flex: 1;
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 50px;
      overflow-y: auto;
    }
    
    .form-drawer .actions {
      height: 50px;
      @apply mt-auto flex items-center
    }
    </style>
    
    2. 在父组件中使用 Father.vue
    <template>
    <el-button @click="handleCommand">
     <FormDrawer ref="formDrawerRef" @submit="onSubmit" title="修改密码" destroyOnClose :loading="loading">
        <el-form :rules="rules" :model="from" label-width="100px" ref="formRef">
          <el-form-item label="旧密码" prop="oldpassword">
            <el-input v-model="from.oldpassword" placeholder="请输入旧密码"></el-input>
          </el-form-item>
    
          <el-form-item label="新密码" prop="password">
            <el-input v-model="from.password" show-password type="password" placeholder="请输入密码"></el-input>
          </el-form-item>
    
          <el-form-item label="确认密码" prop="repassword">
            <el-input v-model="from.repassword" show-password type="password" placeholder="请输入密码"></el-input>
          </el-form-item>
        </el-form>
      </FormDrawer>
    
    <script>
    import FormDrawer from ".~/components/FormDrawer/FormDrawer.vue";
    const store = useStore();
    const formDrawerRef = ref(null)
    const showDrawer = ref(false); // 修改密码
    const from = reactive({
      oldpassword: "",
      password: "",
      repassword: ""
    })
    
    // 表达校验
    const rules = reactive({
      oldpassword: [
        { required: true, message: '旧密码不能为空', trigger: 'blur' },
        { min: 3, max: 15, message: '旧密码的字符长度为3-15个字符', trigger: 'blur' },
      ],
      password: [
        { required: true, message: '新密码不能为空', trigger: 'blur' },
        { min: 3, max: 15, message: '新密码的字符长度为3-15个字符', trigger: 'blur' },
      ],
      repassword: [
        { required: true, message: '确认密码不能为空', trigger: 'blur' },
        { min: 3, max: 15, message: '确认密码的字符长度为3-15个字符', trigger: 'blur' },
      ],
    })
    const formRef = ref(null);
    const loading = ref(false)
    // 打开抽屉
    const handleCommand = (e) => {
          formDrawerRef.value.open()
    }
    const onSubmit = () => {
      formRef.value.validate((valid) => {
        if (!valid) {
          return false
        }
        loading.value = true
        updatePassword(from).then(res => {
          Toast("修改密码成功,请重新登录")
          store.dispatch("logout");
          // 跳转回登录页面
          router.push("/login")
        }).finally(() => {
    
          loading.value = false
        })
      })
    }
    </script>
    

    实际截图

    image.png

    十七. 实现按钮 loading效果

    上面十六我的是现实是直接从如组件传值到子组件中的那种做法控制按钮的loading

    1. FormDrawer.vue 封装组件
    <template>
      <el-drawer v-model="showDrawer" :size="size" :title="title" :close-on-click-modal="false" :destroy-on-close="destroyOnClose">
        <div class="form-drawer">
          <div class="body">
            <slot></slot>
          </div>
          <div class="actions">
            <el-button type="primary" :loading="loading" @click="submit">{{ confirmText }}</el-button>
            <el-button type="default" @click="close">取消</el-button>
          </div>
        </div>
      </el-drawer>
    </template>
    
    <script setup>
    import { ref } from "vue";
    
    const showDrawer = ref(false);
    
    // 打开
    const open = () => showDrawer.value = true;
    
    // 关闭
    const close = () => showDrawer.value = false;
    
    // defineProps父组件向子组件传递动态参数 只能在 script setup 中使用的
    const props = defineProps({
      title: String,
      size: {
        type: String,
        default: "45%"
      },
    
      destroyOnClose: {
        type: Boolean,
        default: false
      },
      confirmText: {
        type:String,
        default: "提交"
      },
    })
    
    // defineEmits: 事件的提交 只能在 script setup 中使用的
    const emit = defineEmits(['submit']);
    const submit = () => emit("submit");
    
    // 按钮的loading效果
    const loading = ref(false);
    const showLoading = () => loading.value = true;
    const hiddenLoading = () => loading.value = false;
    
    // 使用 script setup 的组件是默认关闭的——即通过模板引用或者 $parent 链获取到的组件的公开实例,不会暴露任何在 script setup 中声明的绑定。
    // 可以通过 defineExpose 编译器宏来显式指定在 script setup 组件中要暴露出去的属性:
    defineExpose({
      open,
      close,
      showLoading,
      hiddenLoading
    })
    </script>
    
    <style>
    .form-drawer {
      width: 100%;
      height: 100%;
      position: relative;
      @apply flex flex-col;
    }
    
    .form-drawer .body {
      flex: 1;
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 50px;
      overflow-y: auto;
    }
    
    .form-drawer .actions {
      height: 50px;
      @apply mt-auto flex items-center
    }
    </style>
    
    2. 父组件中 Father.vue
    <el-button @click="handleCommand"> </el-button>
      <FormDrawer ref="formDrawerRef" @submit="onSubmit" title="修改密码" destroyOnClose>
        <el-form :rules="rules" :model="from" label-width="100px" ref="formRef">
          <el-form-item label="旧密码" prop="oldpassword">
            <el-input v-model="from.oldpassword" placeholder="请输入旧密码"></el-input>
          </el-form-item>
    
          <el-form-item label="新密码" prop="password">
            <el-input v-model="from.password" show-password type="password" placeholder="请输入密码"></el-input>
          </el-form-item>
    
          <el-form-item label="确认密码" prop="repassword">
            <el-input v-model="from.repassword" show-password type="password" placeholder="请输入密码"></el-input>
          </el-form-item>
        </el-form>
      </FormDrawer>
    
    
    <script setup>
    const handleCommand = (e) => {
          formDrawerRef.value.open()
    }
    
    const onSubmit = () => {
      formRef.value.validate((valid) => {
        if (!valid) {
          return false
        }
        formDrawerRef.value.showLoading()
        updatePassword(from).then(res => {
          Toast("修改密码成功,请重新登录")
          store.dispatch("logout");
          // 跳转回登录页面
          router.push("/login")
        }).finally(() => {
    
          formDrawerRef.value.hiddenLoading()
        })
      })
    }
    </script>
    
    

    十七 是我在抽屉内部封装的组件


    image.png

    十八.当进入后台管理系统的时候, 你点击 进入到当前路由,但是刷新页面 又重置了,该怎么办呢?

    image.png

    相关文章

      网友评论

        本文标题:Vue3.0 Vite + windicss + ...实战项目

        本文链接:https://www.haomeiwen.com/subject/ijqaartx.html