本文最后更新于 2024-08-19,文章内容距离上一次更新已经过去了很久啦,可能已经过时了,请谨慎参考喵。

前情提要

https://blog.imbhj.com/archives/fsaSCzwqhttps://blog.imbhj.com/archives/VePoGk3Dhttps://blog.imbhj.com/archives/csO7QabUhttps://blog.imbhj.com/archives/ZemDwiHzhttps://blog.imbhj.com/archives/jtLH9rHj

创建VUE3项目

在博客根目录的上级目录,执行:

npm create vite@latest goblog-admin-vue3

类型选择 Vue ,语言方式选择 JavaScript

进入创建好的 goblog-admin-vue3 目录,执行:

npm install

然后在项目根目录下执行测试一下:

npm run dev

欧克,没什么问题,然后不需要示例页面,需要删除 /src/components/HelloWorld.vue 这个文件

然后修改 /src/App.vue

<template>
  <router-view>

  </router-view>
</template>


<script setup>

</script>


<style scoped>

</style>

继续删除 /src/assets/vue.svg/src/style.css 这两个文件,修改 /index.html

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>GoBlog 运营后台</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>

修改 /src/main.js

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

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

然后重启项目测试一下,页面是空白的,标题已被修改,图标没变,就OK了

创建目录结构:

/src/api/
/src/views/
/src/store/
/src/utils/
/src/router/
/src/permission/

安装 vue-router 依赖、element-plus 依赖:

npm install vue-router
npm install element-plus --save

处理基本结构代码

第一步修改 /src/main.js

import { createApp } from 'vue'
import App from './App.vue'
// router 引入
import router from './router/router.js'
// element-plus 引入
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

// app 实列
const app = createApp(App)
app.use(router)
app.use(ElementPlus)
app.mount('#app')

下一步创建路由文件 /src/router/router.js

哎呀,代码丢失了(这里先空着)

创建登录页面 /src/views/login.vue

<template>
<div>
  <el-button type="primary">测试按钮</el-button>
</div>
</template>

<script setup>

</script>

<style lang="scss">

</style>

返回 /src/router/router.js 中:

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

/*
 * 路由表
 */

const router = createRouter({
    // 去掉 URL 中的 #
    history: createWebHistory(),
    routes:[
        {path:'/login', component:()=>import('@/views/login.vue')},
    ]
})

export default router;

修改 /vite.config.js

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

// https://vitejs.dev/config/
export default defineConfig({
  // 解决 @ 引入问题
  resolve:{
    alias:{
      '@': path.resolve(__dirname, './src'),
    }
  },
  plugins: [vue()],
})

启动项目测试一下:

构建登陆页面基本结构

修改 /src/main.js 引入 element 的 icon 组件:

import { createApp } from 'vue'
import App from './App.vue'
// router 引入
import router from './router/router.js'
// element-plus 引入
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
// element 图标引入
import * as ElementPlusIconsVue from '@element-plus/icons-vue'

// app 实列
const app = createApp(App)
app.use(router)
app.use(ElementPlus)

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

app.mount('#app')

然后回到 /src/views/login.vue

<template>
  <div class="login-container">
    <el-form class="login-form">
      <!--  标题  -->
      <div class="title-container">GoBlog 运营后台</div>
      <!--  用户名  -->
      <el-form-item style="margin-bottom: 30px">
        <el-input placeholder="请输入用户名" name="username" type="text" prefix-icon="User"></el-input>
      </el-form-item>
      <!--  密码  -->
      <el-form-item style="margin-bottom: 30px">
        <el-input placeholder="请输入密码" name="password" type="text" prefix-icon="Key" suffix-icon="view"></el-input>
      </el-form-item>
      <!--  登录按钮  -->
      <el-button type="primary" style="width: 100%; margin-bottom: 30px">登录</el-button>
    </el-form>
  </div>
</template>

<script setup>

</script>

<style lang="scss">

</style>

运行测试一下:

ok,没啥问题

页面样式调整

首先安装一个 sass 依赖:(项目根目录下执行)

npm install sass sass-loader

创建 styles 目录:

/src/styles/

创建主样式文件 /src/styles/index.scss

html, body, #app {
  height: 100%;
  margin: 0;
  padding: 0;
}

在全局配置 /src/main.js 中引入css样式:

import { createApp } from 'vue'
import App from './App.vue'
// router 引入
import router from './router/router.js'
// element-plus 引入
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
// element 图标引入
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
// index.scss 全局样式引入
import '@/styles/index.scss'

// app 实列
const app = createApp(App)
// 挂载路由
app.use(router)
// 挂载 element-plus
app.use(ElementPlus)
// 挂载 element-icon 组件
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
    app.component(key, component)
}
// 挂载 vue app
app.mount('#app')

下一步在 /src/views/login.vue 中继续写样式:

<template>
  <div class="login-container">
    <el-form class="login-form">
      <!--  标题  -->
      <div class="title-container">GoBlog 运营后台</div>
      <!--  用户名  -->
      <el-form-item style="margin-bottom: 30px">
        <el-input placeholder="请输入用户名" name="username" type="text" prefix-icon="User"></el-input>
      </el-form-item>
      <!--  密码  -->
      <el-form-item style="margin-bottom: 30px">
        <el-input placeholder="请输入密码" name="password" type="text" prefix-icon="Key" suffix-icon="view"></el-input>
      </el-form-item>
      <!--  登录按钮  -->
      <el-button type="primary" style="width: 100%; margin-bottom: 30px">登录</el-button>
    </el-form>
  </div>
</template>

<script setup>

</script>

<style lang="scss" scoped>
.login-container{
  background-image: linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%);
  height: 100%;

  .login-form{
    width: 400px;
    border-radius: 1px;
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);

    .title-container{
      font-size: 30px;
      line-height: 1.5;
      text-align: center;
      margin-bottom: 30px;
      font-weight: bold;
    }
  }
}
</style>

效果如图:

表单校验以及密码掩盖

还是在上一节的 /src/views/login.vue 中:

<template>
  <div class="login-container">
    <el-form class="login-form" :rules="rules" :model="loginForm">
      <!--  标题  -->
      <div class="title-container">GoBlog 运营后台</div>
      <!--  用户名  -->
      <el-form-item style="margin-bottom: 30px" prop="username">
        <el-input placeholder="请输入用户名" name="username" type="text" prefix-icon="User" v-model="loginForm.username"></el-input>
      </el-form-item>
      <!--  密码  -->
      <el-form-item style="margin-bottom: 30px" prop="password">
        <el-input placeholder="请输入密码" name="password" type="text" prefix-icon="Key" suffix-icon="view" v-model="loginForm.password"></el-input>
      </el-form-item>
      <!--  登录按钮  -->
      <el-button type="primary" style="width: 100%; margin-bottom: 30px">登录</el-button>
    </el-form>
  </div>
</template>

<script setup>
import {ref} from 'vue'

// 表单验证校验
const rules = {
  username: [{required: true, message: "请输入用户名", trigger: "blur"}],
  password: [{required: true, message: "请输入密码", trigger: "blur"}]
}

const loginForm = ref({})
</script>

<style lang="scss" scoped>
.login-container{
  background-image: linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%);
  height: 100%;

  .login-form{
    width: 400px;
    border-radius: 1px;
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);

    .title-container{
      font-size: 30px;
      line-height: 1.5;
      text-align: center;
      margin-bottom: 30px;
      font-weight: bold;
    }
  }
}
</style>

效果如下:

继续在 /src/views/login.vue 中:

<template>
  <div class="login-container">
    <el-form class="login-form" :rules="rules" :model="loginForm">
      <!--  标题  -->
      <div class="title-container">GoBlog 运营后台</div>
      <!--  用户名  -->
      <el-form-item style="margin-bottom: 30px" prop="username">
        <el-input placeholder="请输入用户名" name="username" type="text" prefix-icon="User"
                  v-model="loginForm.username"></el-input>
      </el-form-item>
      <!--  密码  -->
      <el-form-item style="margin-bottom: 30px" prop="password">
        <el-input placeholder="请输入密码" name="password" :type="flagType" v-model="loginForm.password">
          <template #prefix>
            <el-icon>
              <Key/>
            </el-icon>
          </template>
          <template #suffix>
            <span @click="changeView">
              <el-icon v-if="flag === true" style="cursor: pointer">
                <Hide/>
              </el-icon>
              <el-icon v-else-if="flag === false" style="cursor: pointer">
                <View/>
              </el-icon>
            </span>
          </template>
        </el-input>
      </el-form-item>
      <!--  登录按钮  -->
      <el-button type="primary" style="width: 100%; margin-bottom: 30px">登录</el-button>
    </el-form>
  </div>
</template>

<script setup>
import {ref} from 'vue'

// 表单验证校验
const rules = {
  username: [{required: true, message: "请输入用户名", trigger: "blur"}],
  password: [{required: true, message: "请输入密码", trigger: "blur"}]
}

const loginForm = ref({})

// changeView 切换密码显示状态
const flagType = ref("password")
const flag = ref(true)
const changeView = () => {
  flag.value = !flag.value
  flagType.value = flag.value ? "password" : "text"
}
</script>

<style lang="scss" scoped>
.login-container {
  background-image: linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%);
  height: 100%;

  .login-form {
    width: 400px;
    border-radius: 1px;
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);

    .title-container {
      font-size: 30px;
      line-height: 1.5;
      text-align: center;
      margin-bottom: 30px;
      font-weight: bold;
    }
  }
}
</style>

效果如图:

封装axios模块

首先安装 axios 依赖:(项目根目录下执行)

npm install axios@latest --save

在项目根目录下创建 /.env.development 文件:

# 名称
VITE_APP_ENV = 'development'
# BASE URL
VITE_APP_BASE_API = '/api'

再在根目录下创建测试环境的配置文件 /.env.test

# 名称
VITE_APP_ENV = 'test'
# BASE URL
VITE_APP_BASE_API = '/api/test'

最后还有生产环境的配置文件 /.env.production

# 名称
VITE_APP_ENV = 'production'
# BASE URL
VITE_APP_BASE_API = '/api/production'

创建 /src/utils/request.js 文件:

/*
* axios 封装
*/

import axios from 'axios'

// axios 创建
const service = axios.create({
    baseURL: import.meta.env.VITE_APP_BASE_API,
    timeout: 5000,
})

// request 核心函数
function request (options) {
    options.method = options.method || 'get'
    if (options.method.toLowerCase() === 'get') {
        options.params = options.data
    }
    service.defaults.baseURL = import.meta.env.VITE_APP_BASE_API
    return service(options)
}

export default request

修改根目录下的 package.json ,将启动模式更改为设定好的:

{
  "name": "goblog-admin-vue3",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite --mode development",
    "test": "vite --mode test",
    "production": "vite --mode production",
    "build:dev": "vite build --mode development",
    "build:test": "vite build --mode test",
    "build:production": "vite build --mode production",
    "preview": "vite preview"
  },
  "dependencies": {
    "axios": "^1.6.8",
    "element-plus": "^2.7.2",
    "sass": "^1.76.0",
    "sass-loader": "^14.2.1",
    "vue": "^3.4.21",
    "vue-router": "^4.3.2"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^5.0.4",
    "vite": "^5.2.0"
  }
}

回到 /src/main.js 中测试打印一下环境参数:

import { createApp } from 'vue'
import App from './App.vue'
// router 引入
import router from './router/router.js'
// element-plus 引入
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
// element 图标引入
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
// index.scss 全局样式引入
import '@/styles/index.scss'

// 输出运行环境
console.log("运行环境为:", import.meta.env.VITE_APP_ENV)
console.log("BASE URL:", import.meta.env.VITE_APP_BASE_API)

// app 实列
const app = createApp(App)
// 挂载路由
app.use(router)
// 挂载 element-plus
app.use(ElementPlus)
// 挂载 element-icon 组件
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
    app.component(key, component)
}
// 挂载 vue app
app.mount('#app')

测试一下:

修改启动端口及配置代理

首先修改根目录下的 /vite.config.js

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

// https://vitejs.dev/config/
export default defineConfig({
    // 解决 @ 引入问题
    resolve: {
        alias: {
            '@': path.resolve(__dirname, './src'),
        }
    },
    plugins: [vue()],
    lintOnSave: false,// 关闭校验
    productionSourceMap: false,// 生产环境是否要生成 sourceMpa
    publicPath: "/",//部署应用包时的基本 URL (不可以是相对路径,否则会出现 cannot get 错误)
    outputDir: "dist",// build 时输出的文件目录
    assetsDir: "assets",// 放置静态文件的目录
    server: {
        port: 5002,//运行时的端口
        host: '0.0.0.0',// 运行时的域名
        https: false,// 是否启用 https
        open: false,// 是否直接打开浏览器
        proxy: {// 配置后端代理访问的地址
            "/api": {
                target: 'http://127.0.0.1:5001',
                changeOrigin: true,
            }
        },
        client: {
            overlay: false,
        }
    }
})

然后启动测试一下:

封装API及处理登录请求

创建 /src/api/index.js

/*
 * API 接口的封装
 */

import request from "@/utils/request.js"

export default {
    // 登录接口
    login(params) {
        return request({
            url: '/sysAdmin/login',
            method: 'post',
            data: params
        })
    }
}

下一步回到 /src/main.js 中绑定这个接口:

import { createApp } from 'vue'
import App from './App.vue'
// router 引入
import router from './router/router.js'
// element-plus 引入
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
// element 图标引入
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
// index.scss 全局样式引入
import '@/styles/index.scss'
// API 引入
import api from './api'

// 输出运行环境
console.log("运行环境为:", import.meta.env.VITE_APP_ENV)
console.log("BASE URL:", import.meta.env.VITE_APP_BASE_API)

// app 实列
const app = createApp(App)
// 挂载路由
app.use(router)
// 挂载 element-plus
app.use(ElementPlus)
// 挂载 element-icon 组件
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
    app.component(key, component)
}
// 调用 API
app.config.globalProperties.$api = api
// 挂载 vue app
app.mount('#app')

下一步来到 /src/views/login.vue 中绑定登录的API:

<template>
  <div class="login-container">
    <el-form class="login-form" :rules="rules" :model="loginForm" ref="loginFormRef">
      <!--  标题  -->
      <div class="title-container">GoBlog 运营后台</div>
      <!--  用户名  -->
      <el-form-item style="margin-bottom: 30px" prop="username">
        <el-input placeholder="请输入用户名" name="username" type="text" prefix-icon="User"
                  v-model="loginForm.username"></el-input>
      </el-form-item>
      <!--  密码  -->
      <el-form-item style="margin-bottom: 30px" prop="password">
        <el-input placeholder="请输入密码" name="password" :type="flagType" v-model="loginForm.password">
          <template #prefix>
            <el-icon>
              <Key/>
            </el-icon>
          </template>
          <template #suffix>
            <span @click="changeView">
              <el-icon v-if="flag === true" style="cursor: pointer">
                <Hide/>
              </el-icon>
              <el-icon v-else-if="flag === false" style="cursor: pointer">
                <View/>
              </el-icon>
            </span>
          </template>
        </el-input>
      </el-form-item>
      <!--  登录按钮  -->
      <el-button type="primary" style="width: 100%; margin-bottom: 30px" @click="handleLogin">登录</el-button>
    </el-form>
  </div>
</template>

<script setup>
import {ref, getCurrentInstance} from 'vue'
const {proxy} = getCurrentInstance()

// 表单验证校验
const rules = {
  username: [{required: true, message: "请输入用户名", trigger: "blur"}],
  password: [{required: true, message: "请输入密码", trigger: "blur"}]
}

const loginForm = ref({})

// changeView 切换密码显示状态
const flagType = ref("password")
const flag = ref(true)
const changeView = () => {
  flag.value = !flag.value
  flagType.value = flag.value ? "password" : "text"
}

// 处理登录
const loginFormRef = ref()
const handleLogin = () => {
  loginFormRef.value.validate(valid => {
    if (!valid) return
    proxy.$api.login(loginForm.value).then((res) => {
      console.log("请求的响应数据:", res)
    }).catch(err => {
      console.log(err)
    })
  })
}
</script>

<style lang="scss" scoped>
.login-container {
  background-image: linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%);
  height: 100%;

  .login-form {
    width: 400px;
    border-radius: 1px;
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);

    .title-container {
      font-size: 30px;
      line-height: 1.5;
      text-align: center;
      margin-bottom: 30px;
      font-weight: bold;
    }
  }
}
</style>

然后我们测试一下是否能正确打印返回的信息:

请求拦截和响应拦截

进入到 /src/utils/request.js 文件中:

/*
 * axios 封装
 */

import axios from 'axios'
import router from '@/router/router.js'
import {ElMessage} from "element-plus";

// axios 创建
const service = axios.create({
    baseURL: import.meta.env.VITE_APP_BASE_API,
    timeout: 5000,
})

// 请求拦截
service.interceptors.request.use((req) => {
    const headers = req.headers
    const token = 'admin'
    if (!headers.Authorization) {
        headers.Authorization = 'Bearer ' + token
    }
    return req
})

// 响应拦截
service.interceptors.response.use((res) => {
    const {code, data, message} = res.data
    if (code == 403) {
        ElMessage.error(message)
        // todo
        setTimeout(() => {
            router.push('/login')
        }, 1500)
    } else if (code == 200) {
        return res.data
    } else {
        ElMessage.error(message)
    }
})

// request 核心函数
function request(options) {
    options.method = options.method || 'get'
    if (options.method.toLowerCase() === 'get') {
        options.params = options.data
    }
    service.defaults.baseURL = import.meta.env.VITE_APP_BASE_API
    return service(options)
}

export default request

然后启动测试一下:

本地存储方案

首先需要在环境配置中增加本地命名空间,在根目录下 .env.development.env.production.env.test 三个文件中加入以下配置项:

# 本地存储命名
VUE_APP_STORAGE_KEY = 'goblog-admin-vue3'

创建存储配置文件 /src/utils/storage.js

/*
* 本地存储
*/

export default {
    // 读取储存文件
    getStorage() {
        return JSON.parse(localStorage.getItem(import.meta.env.VUE_APP_STORAGE_KEY) || "{}")
    },
    // 存储
    setItem(key, val) {
        let storage = this.getStorage()
        storage[key] = val
        localStorage.setItem(import.meta.env.VUE_APP_STORAGE_KEY, JSON.stringify(storage))
    },
    // 读取
    getItem(key) {
        return this.getStorage()[key]
    },
    // 清除
    clearItem(key) {
        let storage = this.getStorage()
        delete storage[key]
        localStorage.setItem(import.meta.env.VUE_APP_STORAGE_KEY, JSON.stringify(storage))
    },
    // 全部清除
    clearAllItem() {
        localStorage.clear()
    }
}

下一步在 /src/main.js 中配置一下:

import { createApp } from 'vue'
import App from './App.vue'
// router 引入
import router from './router/router.js'
// element-plus 引入
import ElementPlus from 'element-plus'
// element-plus 样式引入
import 'element-plus/dist/index.css'
// element 图标引入
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
// index.scss 全局样式引入
import './styles/index.scss'
// API 引入
import api from './api'
// storage 本地存储引入
import storage from "./utils/storage.js"

// 输出运行环境
console.log("运行环境为:", import.meta.env.VITE_APP_ENV)
console.log("BASE URL:", import.meta.env.VITE_APP_BASE_API)

// app 实列
const app = createApp(App)
// 挂载路由
app.use(router)
// 挂载 element-plus
app.use(ElementPlus)
// 挂载 element-icon 组件
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
    app.component(key, component)
}
// 调用 API
app.config.globalProperties.$api = api
// 调用 storage
app.config.globalProperties.$storage = storage
// 挂载 vue app
app.mount('#app')

安装 vuex 依赖:(项目根目录下执行)

npm install vuex --save

创建 /src/stor/metations.js 文件:

/*
* store 和本地存储
*/

import storage from "@/utils/storage.js"

export default {
    // token 存储
    saveToken(state, token) {
        state.token = token
        storage.setItem('token', token)
    },
    // 用户信息存储
    saveSysAdmin(state, sysAdmin) {
        state.sysAdmin = sysAdmin
        storage.setItem('sysAdmin', sysAdmin)
    },
    // 左侧菜单栏存储
    saveLeftMenuList(state, leftMenuList) {
        state.leftMenuList = leftMenuList
        storage.setItem('leftMenuList', leftMenuList)
    },
    // 当前用户权限列表存储
    savePermissionList(state, permissionList) {
        state.permissionList = permissionList
        storage.setItem('permissionList', permissionList)
    }
}

创建 /src/store/index.js 文件:

/*
* 获取存储的方法
*/

import {createStore} from 'vuex'
import mutations from "./mutations.js"
import storage from "../utils/storage.js"

const state = {
    token: "" || storage.getItem("token"),
    sysAdmin: "" || storage.getItem("sysAdmin"),
    leftMenuList: "" || storage.getItem("leftMenuList"),
    permissionList: "" || storage.getItem("permissionList"),
}

export default createStore({
    state,
    mutations
})

再回到 /src/main.js 文件中:

import { createApp } from 'vue'
import App from './App.vue'
// router 引入
import router from './router/router.js'
// element-plus 引入
import ElementPlus from 'element-plus'
// element-plus 样式引入
import 'element-plus/dist/index.css'
// element 图标引入
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
// index.scss 全局样式引入
import './styles/index.scss'
// API 引入
import api from './api'
// storage 本地存储引入
import storage from "./utils/storage.js"
// store 引入
import store from './store'

// 输出运行环境
console.log("运行环境为:", import.meta.env.VITE_APP_ENV)
console.log("BASE URL:", import.meta.env.VITE_APP_BASE_API)

// app 实列
const app = createApp(App)
// 挂载路由
app.use(router)
// 挂载 element-plus
app.use(ElementPlus)
// 挂载 element-icon 组件
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
    app.component(key, component)
}
// 调用 API
app.config.globalProperties.$api = api
// 调用 storage
app.config.globalProperties.$storage = storage
// 挂载 store
app.use(store)
// 挂载 vue app
app.mount('#app')

下一步回到登陆页面 /src/views/login.vue 继续处理:

<template>
  <div class="login-container">
    <el-form class="login-form" :rules="rules" :model="loginForm" ref="loginFormRef">
      <!--  标题  -->
      <div class="title-container">GoBlog 运营后台</div>
      <!--  用户名  -->
      <el-form-item style="margin-bottom: 30px" prop="username">
        <el-input placeholder="请输入用户名" name="username" type="text" prefix-icon="User"
                  v-model="loginForm.username"></el-input>
      </el-form-item>
      <!--  密码  -->
      <el-form-item style="margin-bottom: 30px" prop="password">
        <el-input placeholder="请输入密码" name="password" :type="flagType" v-model="loginForm.password">
          <template #prefix>
            <el-icon>
              <Key/>
            </el-icon>
          </template>
          <template #suffix>
            <span @click="changeView">
              <el-icon v-if="flag === true" style="cursor: pointer">
                <Hide/>
              </el-icon>
              <el-icon v-else-if="flag === false" style="cursor: pointer">
                <View/>
              </el-icon>
            </span>
          </template>
        </el-input>
      </el-form-item>
      <!--  登录按钮  -->
      <el-button type="primary" style="width: 100%; margin-bottom: 30px" @click="handleLogin">登录</el-button>
    </el-form>
  </div>
</template>

<script setup>
import {ref, getCurrentInstance} from 'vue'
import {useStore} from 'vuex'

const {proxy} = getCurrentInstance()

// 表单验证校验
const rules = {
  username: [{required: true, message: "请输入用户名", trigger: "blur"}],
  password: [{required: true, message: "请输入密码", trigger: "blur"}]
}

const loginForm = ref({})

// changeView 切换密码显示状态
const flagType = ref("password")
const flag = ref(true)
const changeView = () => {
  flag.value = !flag.value
  flagType.value = flag.value ? "password" : "text"
}

// 处理登录
const loginFormRef = ref({})
const store = useStore()
const handleLogin = () => {
  loginFormRef.value.validate(valid => {
    if (!valid) return
    proxy.$api.login(loginForm.value).then((res) => {
      // console.log("请求的响应数据:", res)
      proxy.$message.success("登录成功")
      store.commit("saveToken", res.data.token)
      store.commit("saveSysAdmin", res.data.sysAdmin)
      store.commit("saveLeftMenuList", res.data.leftMenuList)
      store.commit("savePermissionList", res.data.permissionList)
    }).catch(err => {
      console.log(err)
    })
  })
}
</script>

<style lang="scss" scoped>
.login-container {
  background-image: linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%);
  height: 100%;

  .login-form {
    width: 400px;
    border-radius: 1px;
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);

    .title-container {
      font-size: 30px;
      line-height: 1.5;
      text-align: center;
      margin-bottom: 30px;
      font-weight: bold;
    }
  }
}
</style>

测试一下:

写在半路

这个项目可能就要胎死腹中了,B站老师的教程留了一手,左侧菜单和权限列表没有数据,我也不太会写,我就是个教程的搬运工,所以后面可能就搞其他去了,完整代码我问了,需要收费,大概400元,地址:

https://www.bilibili.com/video/BV1AK421v7z6/?share_source=copy_web