vite-vue3脚手架(参考帝莎编程-后台管理系统开发)

一 创建项目vite-vue3-app

项目原型: http://shopadmin.dishawang.com/#/

vite: https://vitejs.cn/vite3-cn/

# npm 7+, extra double-dash is needed:
npm create vite@latest vite-vue3-app -- --template vue

cd  vite-vue3-app 
npm install
npm run dev

# 安装依赖
npm install element-plus
npm install vue-router@4
npm install pinia
npm install axios
# npm install ant-design-vue
# npm install @ant-design/icons-vue

二 引入ElementPlus

ElementPlus: https://element-plus.gitee.io/zh-CN/

npm install element-plus
// main.ts
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')

三 集成 Windi CSS

npm i -D vite-plugin-windicss windicss
// vite.config.js
import WindiCSS from 'vite-plugin-windicss'

export default {
  plugins: [
    WindiCSS(),
  ],
}
// main.js
import 'virtual:windi.css'

四 引入VueRouter

npm install vue-router@4
// 新建 router文件夹,在router文件夹下新建index.js
// 1. 定义路由组件.
// 也可以从其他文件导入
const Home = { template: '<div>Home</div>' }
const About = { template: '<div>About</div>' }

// 2. 定义一些路由
// 每个路由都需要映射到一个组件。
// 我们后面再讨论嵌套路由。
const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About },
]

// 3. 创建路由实例并传递 `routes` 配置
// 你可以在这里输入更多的配置,但我们在这里
// 暂时保持简单
const router = VueRouter.createRouter({
  // 4. 内部提供了 history 模式的实现。为了简单起见,我们在这里使用 hash 模式。
  history: VueRouter.createWebHashHistory(),
  routes, // `routes: routes` 的缩写
})
// main.js
// 5. 创建并挂载根实例
const app = Vue.createApp({})
//确保 _use_ 路由实例使
//整个应用支持路由。
app.use(router)

app.mount('#app')

// 现在,应用已经启动了!

五 路由配置和404页面捕获

// vite.config.js 配置路径别名
import path from 'path'

export default defineConfig({
  resolve:{
    alias:{
      "~":path.resolve(__dirname,"src")
    }
  },
  plugins: [vue(),WindCSS()]
})
<!-- 在src下新建pages文件夹, 新建Index.vue -->
<template>
  index
</template>

<script>
export default {

}
</script>

<style>

</style>
//修改routers/index.js
import Index from '~/pages/Index.vue'

const routes=[{
    path:"/",
    component:Index
}]
<!-- 修改App.vue -->
<template>
    <router-view></router-view>
</template>

依次添加About.vue,404.vue。其中404.vue在ElementPlus的result结果里面可以选一种使用。

六 登录页面

<template>
  <el-row class="min-h-screen bg-indigo-500">
    <el-col :lg="16" ms="8" 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>
    <el-col
      :lg="8" ms="8"
      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-x-2"
      >
        <span class="h-[1px] w-16 bg-gray-200"></span>
        <span>账号密码登录</span>
        <span class="h-[1px] w-16 bg-gray-200"></span>
      </div>
      <div>
        <el-form :model="form" class="w-[250px]">
          <el-form-item label="用户名">
            <el-input v-model="form.username" placeholder="请输入用户名" />
          </el-form-item>
          <el-form-item label="密码">
            <el-input v-model="form.password" placeholder="请输入密码" />
          </el-form-item>
          <el-form-item>
            <el-button type="primary" @click="onSubmit" class="w-[250px]" round color="#626aef">登录</el-button>
          </el-form-item>
        </el-form>
      </div>
    </el-col>
  </el-row>
</template>

<script setup>
import { reactive } from "vue";
const form = reactive({
  username: "",
  password: "",
});

const onSubmit = () => {
  console.log("submit!");
};
</script>

<style>
</style>

七 登录页响应式处理

https://element-plus.gitee.io/zh-CN/component/layout.html#col-%E5%B1%9E%E6%80%A7
将左边的span="16"改成:lg="16" ms="8"
将右边的span="8"改成:lg="8" ms="8"
lg:≥1200px 响应式栅格数或者栅格属性对象
md	≥992px 响应式栅格数或者栅格属性对象

八 全局引入图标

npm install @element-plus/icons-vue
# 参考:
https://element-plus.gitee.io/zh-CN/component/input.html#%E5%B8%A6%E5%9B%BE%E6%A0%87%E7%9A%84%E8%BE%93%E5%85%A5%E6%A1%86

# 部分引入
<template #prefix>
	<el-icon class="el-input__icon"><User /></el-icon>
</template>

import { User } from "@element-plus/icons-vue";

# 全局引入  修改main.ts
import * as ElementPlusIconsVue from '@element-plus/icons-vue'

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

九 结合@apply实现样式抽离

对Login.vue页面中的css使用@apply进行重构

<template>
  <el-row class="login-container">
    <el-col :lg="16" ms="8" class="left">
      <div>
        <div>欢迎光临</div>
        <div>此站点是(vue3+vite实战商城后台开发)</div>
      </div>
    </el-col>
    <el-col :lg="8" ms="8" class="right">
      <h2 class="title">欢迎回来</h2>
      <div>
        <span class="line"></span>
        <span>账号密码登录</span>
        <span class="line"></span>
      </div>
      <div>
        <el-form :model="form" class="w-[250px]">
          <el-form-item label="用户名">
            <el-input v-model="form.username" placeholder="请输入用户名">
              <template #prefix>
                <el-icon class="el-input__icon"><User /></el-icon>
              </template>
            </el-input>
          </el-form-item>
          <el-form-item label="密 码">
            <el-input
              v-model="form.password"
              placeholder="请输入密码"
              class="ml-2"
            >
              <template #prefix>
                <el-icon class="el-input__icon"><Lock /></el-icon>
              </template>
            </el-input>
          </el-form-item>
          <el-form-item>
            <el-button
              type="primary"
              @click="onSubmit"
              class="w-[250px]"
              round
              color="#626aef"
              >登录</el-button
            >
          </el-form-item>
        </el-form>
      </div>
    </el-col>
  </el-row>
</template>

<script setup>
import { reactive } from "vue";
const form = reactive({
  username: "",
  password: "",
});

const onSubmit = () => {
  console.log("submit!");
};
</script>

<style scoped>
.login-container {
  @apply min-h-screen bg-indigo-500;
}
.login-container .left {
  @apply flex items-center justify-center;
}
.login-container .right {
  @apply bg-light-50 flex items-center justify-center flex-col;
}
.left > div > div:first-child {
  @apply font-bold text-5xl text-light-50 mb-4;
}
.left > div > div:last-child {
  @apply text-gray-200 text-sm;
}
.right .title {
  @apply font-bold text-3xl text-gray-800;
}
.right > div {
  @apply flex items-center justify-center my-5 text-gray-300 space-x-2;
}
.right .line {
  @apply h-[1px] w-16 bg-gray-200;
}
</style>

十 setup语法糖和组合式api

ref-->基本类型
reactive -->引用类型

十一 登录表单验证处理

# 参考:https://element-plus.gitee.io/zh-CN/component/form.html#%E8%A1%A8%E5%8D%95%E6%A0%A1%E9%AA%8C

十二 引入axios请求库和登录接口交互

接口文档:http://dishaxy.dishait.cn/shopadminapi
http://ceshi13.dishait.cn
# 安装axios
npm install axios
// 在src目录下新建axios.js
import axios from 'axios'

// 创建实例时配置默认值
const service = axios.create({
    baseURL: '/api'
  });
export default service;
// 修改vue.config.js, 设置跨域
// 参考:https://vitejs.cn/vite3-cn/config/server-options.html
 server: {
    proxy: {
      '/api': {
        target: 'http://ceshi13.dishait.cn',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      },
    }
},
// 在src目录下新建api目录,新增manager.js

import service from '~/axios'

export default function login(username, password) {
    return service.post("/admin/login", {
        username,
        password
    })
}
// 修改Login.vue
import login from "~/api/manager";
import { useRouter } from "vue-router";

const router = useRouter();

const onSubmit = () => {
  formRef.value.validate((valid) => {
    if (!valid) {
      return false;
    }
    login(form.username, form.password)
      .then((res) => {
        console.log(res);
        //提示成功
        ElNotification({
          message: res.data.data.msg || "登录成功",
          type: "success",
          duration: 1000,
        });
        //存储token

        //跳转到后台主页
        router.push("/");
      })
      .catch((err) => {
        ElNotification({
          message: err.response.data.msg || "请求失败",
          type: "error",
          duration: 3000,
        });
      });
  });
};

十三 引入cookie存储用户token

npm i @vueuse/integrations
npm i universal-cookie
<!--在Index.vue中进行cookie测试-->

<template>
  <div>
    后台首页
    <el-button @click="set">设置</el-button>
    <el-button @click="get">读取</el-button>
    <el-button @click="remove">删除</el-button>
  </div>
</template>

<script setup>
import { useCookies } from "@vueuse/integrations/useCookies";

const cookies = useCookies();

console.log(cookies);

function set() {
  cookies.set("token", "123456");
}
function get() {
  console.log(cookies.get("token"));
}
function remove() {
  cookies.remove("token");
}
</script>

<style>
</style>

十四 请求拦截器和响应拦截器

// 修改axios.js
// 添加请求拦截器
service.interceptors.request.use(function (config) {
  // 在发送请求之前做些什么
  const cookies = useCookies();
  const token = cookies.get('admin-token')
  if (token) {
    config.headers["token"] = token
  }
  return config;
}, function (error) {
  // 对请求错误做些什么
  return Promise.reject(error);
});

// 添加响应拦截器
service.interceptors.response.use(function (response) {
  // 2xx 范围内的状态码都会触发该函数。
  // 对响应数据做点什么
  return response.data.data;
}, function (error) {
  // 超出 2xx 范围的状态码都会触发该函数。
  // 对响应错误做点什么
  ElNotification({
    message: error.response.data.msg || "请求失败",
    type: "error",
    duration: 3000,
  });
  return Promise.reject(error);
});

十五 常用工具库封装

// 封装cookie工具
// 在src目录下新建composables目录,新建auth.js
import { useCookies } from "@vueuse/integrations/useCookies";

const TOKEN_KEY = "admin-token"
const cookies = useCookies()

//获取token
export function getToken() {
    return cookies.get(TOKEN_KEY)
}
//设置token
export function setToken(token) {
    return cookies.set(TOKEN_KEY,token)
}
//删除tokenexport 
function removeToken() {
    return cookies.remove(TOKEN_KEY)
}
import { getToken } from "~/composables/auth";
const token = getToken()

//存储token
setToken(res.token);
// 封装提示工具
// 在src目录下新建composables目录,新建util.js
import { ElNotification } from "element-plus";

// ElementPlus消息提示
export function toast(message,type='success',dangerouslyUseHTMLString=false){
    //提示成功
    ElNotification({
        message,
        type,
        duration: 2000,
      });
}


//提示成功
toast("登录成功");

//提示失败
toast(error.response.data.msg || "请求失败",'error')

十六 引入vuex状态管理用户信息

# 参考:https://vuex.vuejs.org/zh/
npm install vuex@next --save
// 在src目录下新建store目录,新建index.js
import { createStore } from 'vuex'
// 创建一个新的 store 实例
const store = createStore({
    state() {
        return {
            // 用户信息
            user: {}
        }
    },
    mutations: {
        // 记录用户信息
        set_userInfo(state, user) {
            state.user = user
        }
    }
})

export default store
// 修改main.js
import store from "./store";

app.use(store)
// 修改Login.vue
import { useStore } from "vuex";
const store = useStore();

//获取用户信息
getInfo().then((res2) => {
  store.commit("set_userInfo", res2);
  console.log(res2);
});

十七 全局路由拦截实现登录判断

参考:https://router.vuejs.org/zh/guide/advanced/navigation-guards.html#%E5%85%A8%E5%B1%80%E5%89%8D%E7%BD%AE%E5%AE%88%E5%8D%AB

// 在src目录下新建permission.js
import router from '~/router'
import { getToken } from '~/composables/auth'
import { toast } from '~/composables/util'

//全局前置守卫
router.beforeEach((to, from, next) => {
    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 : '/' })
    }
    next()
})

十八 登录功能完善

// 用vuex保存用户信息 
// 修改store/index.js
import { getInfo } from "~/api/manager";
actions:{
    //获取当前登录用户信息
    getInfo({commit}){
        return new Promise((resolve,reject)=>{
            getInfo()
            .then(res=>{
                commit('set_userInfo',res)
                resolve(res)
            })
            .catch(err=>reject(err))
        })
    }
}

// 修改permission.js
import router from '~/router'
import { getToken } from '~/composables/auth'
import { toast } from '~/composables/util'
import store from '~/store'

//全局前置守卫
router.beforeEach(async (to, from, next) => {
    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()
})

//回车登录
<el-form-item label="密 码" prop="password">
  <el-input
    type="password"
    v-model="form.password"
    placeholder="请输入密码"
    class="ml-2"
    show-password
    @keyup.enter.native="onSubmit"
  >
    <template #prefix>
      <el-icon class="el-input__icon"><Lock /></el-icon>
    </template>
  </el-input>
</el-form-item>
posted @ 2025-10-04 16:28  碧水云天4  阅读(16)  评论(0)    收藏  举报