快速搭建SpringBoot3+Vue3+ElementPlus管理系统

快速搭建SpringBoot3+Vue3管理系统

前端项目搭建(默认开发环境:node20,Jdk17)

创建项目并下载依赖--执行以下命令

  1. 创建项目
javascript 复制代码
yarn create vite admin-vue --template

2.下载依赖(也可按需下载)

javascript 复制代码
yarn add echarts --save
yarn add vue-router --save
yarn add element-plus --save
yarn add axios --save
yarn add sass--save

3.导入IDEA

4.配置快速启动



也可以在终端中使用命令启动,如下:

5.配置Axios

在src目录下新建axios目录,并创建request.js文件

javascript 复制代码
import axios from 'axios'
const request = axios.create({
    timeout: 20000,
    baseURL: 'http://127.0.0.1:8080',
})
export default request

6.配置路由

在src目录下新建axios目录,并创建router.js文件

javascript 复制代码
import {createRouter,createWebHashHistory} from "vue-router";
// 在router.js文件中创建路由实例对象
const router=createRouter({
    history:createWebHashHistory(),
    routes:[//路由规则

    ]
})
// 输出实例对象
export default router;

7.挂载ElementPlus和路由

在main.js中挂载

javascript 复制代码
import { createApp } from 'vue'
import './style.css'
// 导入element-plu
import ElementPlus from 'element-plus'
// 导入路由
import router from './router/router.js'
// 导入css样式
import 'element-plus/dist/index.css'
import App from './App.vue'
// 挂载element-plu和router
createApp(App).use(ElementPlus).use(router).mount('#app')

8.绝对路径配置

在vite.config.js中配置绝对路径别名--@

javascript 复制代码
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { fileURLToPath, URL } from 'node:url'
// https://vite.dev/config/
export default defineConfig({
  plugins: [vue()],
  // 绝对路径别名
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})

8.创建布局组件
8.1 顶部水平导航

html 复制代码
<script setup>
import {onMounted, ref} from "vue";
import router from "@/router/router.js";
// 图片地址
const url=ref("/element-plus-logo.svg")
// 将用户信息从浏览器缓存中取出
const user=ref(JSON.parse(localStorage.getItem('xm-user') || '{}'))
// 退出登录
const logout=()=>{
  localStorage.removeItem('xm-user');
  router.push("/login");
}
</script>

<template>
  <!--  顶部导航栏-->
  <el-menu
      router
      class="el-menu-demo"
      mode="horizontal"
      text-color="grey"
      background-color="white"
      :ellipsis="false"
  >
    <el-menu-item index="1">
      <el-image style="width: 100px; height: 100px" :src="url"  />|商品信息管理系统
    </el-menu-item>

    <el-menu-item index="/">首页
    </el-menu-item>

    <el-menu-item index="/TeachingCenter">

      <span>教学中心</span>
    </el-menu-item>
    <el-menu-item index="/TeachingCenter">
      <span>教学资源</span>
    </el-menu-item>
    <el-menu-item index="/Goods">
      <span>商品信息管理</span>
    </el-menu-item>
    <el-menu-item index="/TeachingCenter">
      <span>教学实践</span>
    </el-menu-item>
    <el-menu-item index="/TeachingCenter">
      <span>问答社区</span>
    </el-menu-item>
    <el-sub-menu index="10" v-if="user.id">
      <template #title>
        <img :src="user.avatar" width="40px" height="40px" style="border-radius: 50%;margin-right: 5px" />
        {{ user.name }}
      </template>
      <el-menu-item index="/manage/personal">个人中心</el-menu-item>
      <el-menu-item index="10-2">修改密码</el-menu-item>
      <el-menu-item index="10-3" @click="logout">退出登录</el-menu-item>
    </el-sub-menu>
    <el-menu-item index="/login" v-else>
      <el-button type="primary" round>登录</el-button>
    </el-menu-item>


  </el-menu>
</template>

<style scoped>
.el-menu--horizontal > .el-menu-item:nth-child(1) {
  margin-right: auto;
}
</style>

8.2 侧边导航栏

html 复制代码
<script setup>

import {Goods, House, Promotion, User,Avatar} from "@element-plus/icons-vue";
</script>

<template>
  <!--      侧边导航栏-->
  <el-aside width="200px" >
    <el-menu default-active="1" style="min-height: 100%;overflow-x: hidden;"
             class="el-menu"
             background-color="white"
             text-color="grey"
             router>
      <!--            <div style="height: 60px; line-height: 60px; text-align: center;">
                    <img src="../assets/logo.png" alt="" style="width: 20px;position: relative; top: 5px;margin-right: 5px;">
                    <b style="color: white;">后台管理系统</b>
                  </div>-->


      <el-menu-item index="/manage/index" >
        <el-icon><House /></el-icon>
        主页</el-menu-item>
      <el-menu-item index="/manage/goods">
        <el-icon><Goods /></el-icon>
        <span>商品信息管理</span>
      </el-menu-item>
      <el-sub-menu index="2">
        <template #title><el-icon><User /></el-icon>用户管理</template>
        <el-menu-item ><el-icon><User /></el-icon>用户信息</el-menu-item>
        <el-menu-item index="/manage/admin">
          <el-icon><Avatar /></el-icon>
          <span>管理员信息</span>
        </el-menu-item>
      </el-sub-menu>


      <el-menu-item ><el-icon><Promotion /></el-icon>订单管理</el-menu-item>


    </el-menu>
  </el-aside>
</template>

<style scoped>
.el-menu{
  height: 100vh;
}
</style>

8.3 底部导航栏

html 复制代码
<script setup>

</script>

<template>
  <p alog-group="copy">©2024 商品信息管理系统
    <a rel="noopener" href="//www.baidu.com/duty/">使用必读</a>
    <a rel="noopener" href="https://tieba.baidu.com/tb/eula.html">协议</a>
    <a href="https://tieba.baidu.com/tb/cms/tieba-fe/tieba_promise.html">隐私政策</a>
    <a rel="noopener" href="//tieba.baidu.com/hermes/feedback">投诉反馈</a>
    信息网络传播视听节目许可证 0110516 违法和不良信息举报电话:400-921-5119
    <a rel="noopener" href="http://net.china.cn/chinese/index.htm" target="_blank">
      <img src="//tb2.bdstatic.com/tb/static-common/img/net_7a8d27a.png" width="20"></a>
    <a rel="noopener" href="http://www.bj.cyberpolice.cn/index.htm" target="_blank"><img title="首都网络110报警服务" src="//tb2.bdstatic.com/tb/static-common/img/110_97c9232.png" width="20"></a>
  </p>
</template>

<style scoped>

</style>

9.创建整体布局,引用子组件

html 复制代码
<script setup>
// 注册子组件
import Header from "@/components/Header.vue";
import Footer from "@/components/Footer.vue";
import Aside from "@/components/Aside.vue";
import {ref} from "vue";
import {useRouter} from "vue-router";
// 获取全局路由
const router=useRouter();
// 判断是否登录
// 将用户信息从浏览器缓存中取出
const user=ref(JSON.parse(localStorage.getItem('xm-user') || '{}'))
if (user.value.name==null){//如果没有数据
  router.push("/login");
}
</script>

<template>
  <!-- container布局 -->
  <div class="common-layout">
    <el-container>
      <!--      顶部导航栏-->
      <el-header><Header></Header></el-header>
      <el-container>
        <!--        侧边导航栏-->
        <el-aside width="200px"><Aside></Aside></el-aside>
        <el-container>
          <!--          内容显示区域-->
          <el-main>
            <!--            路由视图-->
            <router-view></router-view>

          </el-main>
          <!--          底部导航栏-->
          <el-footer><Footer></Footer></el-footer>
        </el-container>
      </el-container>
    </el-container>
  </div>
</template>

<style scoped>

</style>

10.创建商品信息组件:Goods.vue

javascript 复制代码
<script setup>
import {onMounted, ref} from "vue";
import request from "../../axios/request.js";
import {ElMessage, ElMessageBox} from "element-plus";
import {
  Delete,
  Edit,
  Message,
  Search,
  Plus,
  House,
  User,
  Promotion,
  Goods,
  Download,
  UploadFilled
} from "@element-plus/icons-vue";

const list = ref([])
// 查询条件
const name = ref("")
// 模态框是否显示
const dialogVisible = ref(false);
// 商品的信息
const goods = ref({
  id: "",
  name: "",
  price: "",
  intro: "",
  amount: "",
  goodsType: "",
  imgurl: "",
  remark: "未上架"
})


// 表单校验规则
const rules = {
  name: [
    {required: true, message: "请输入名称", trigger: "blur"}
  ],
  price: [
    {required: true, message: "请输入价格", trigger: "blur"}
  ],
  intro: [
    {required: true, message: "请输入简介", trigger: "blur"}
  ],
  amount: [
    {required: true, message: "请输入库存", trigger: "blur"}
  ],
  goodsType: [
    {required: true, message: "请选择类别", trigger: "blur"}
  ],
  remark: [
    {required: true, message: "请选择状态", trigger: "blur"}
  ]
}

// 表单引用
const goodsForm = ref();
// 分页参数
const pageNum = ref(1)
const pageSize = ref(10)
const total = ref(0)

// 保存选中的数据
const multipleSelection = ref([])

// 查询商品信息
const getList = () => {
  request({
    url: '/goods/page',
    method: 'get',
    params: {
      page: pageNum.value,
      limit: pageSize.value,
      goodsName: name.value,
    }
  }).then((res) => {
    // console.log(res)
    list.value = res.data.data;
    total.value = res.data.count;
    // name.value=""
  })
}

// 改变每页数据量
const handleSizeChange = (pSize) => {
  // console.log(pSize+`items per page`)
  pageSize.value = pSize;
  getList()
}
// 改变当前页
const handleCurrentChange = (pNum) => {
  // console.log(`current page: `+pNum)
  pageNum.value = pNum;
  getList()
}
// 根据id删除
const del = (id) => {
  // console.log(id)
  ElMessageBox.confirm(
      '是否确认删除?',
      'Warning',
      {
        confirmButtonText: 'OK',
        cancelButtonText: 'Cancel',
        type: 'warning',
      }
  )
      .then(() => {
        request({
          url: '/goods/del',
          method: 'get',
          params: {id: id}
        }).then(res => {
          if (res.data.code == 200) {
            ElMessage({
              type: 'success',
              message: '删除成功!',
            })
          } else {
            ElMessage({
              type: 'error',
              message: '删除失败!',
            })
          }
          getList()
        })
      })
      .catch(() => {
        ElMessage({
          type: 'warning',
          message: '取消删除!',
        })
      })
}

// 搜索
const search = () => {
  getList()
}

// 新增
const add = () => {
  dialogVisible.value = true
}
// 数据提交
const save = () => {
//   校验表单是否合法
  goodsForm.value.validate((flag, err) => {
    if (flag) {//验证通过
      request({
        url: '/goods/save',
        method: 'post',
        data: goods.value
      }).then(res => {
        getList()
        goods.value = ""
        dialogVisible.value = false
        ElMessage({
          type: 'success',
          message: '保存成功',
        })
      })
    } else {
      ElMessage({
        type: 'error',
        message: '验证没通过!',
      })
    }
  })
}
// 修改
const edit = (item) => {
  // 深拷贝
  const newObj = Object.assign({}, item)
  dialogVisible.value = true
  goods.value = newObj
}
// 获取已选择的数据
const handleSelectionChange = (val) => {
  // console.log(val)
  multipleSelection.value = val;
}
// 批量删除
const delBatch = () => {
// 获取需要删除的数据的id--数组
  let ids = multipleSelection.value.map(item => item.id);
  // console.log("ids",ids)
  if (ids.length != 0) {
    ElMessageBox.confirm(
        '是否确认删除?',
        'Warning',
        {
          confirmButtonText: 'OK',
          cancelButtonText: 'Cancel',
          type: 'warning',
        }
    ).then(() => {
      request({
        url: '/goods/delBatch',
        method: 'post',
        data: ids
      }).then(res => {
        ElMessage({
          type: 'success',
          message: '删除成功!',
        })
        getList()
        // console.log(res)
      })
    }).catch(() => {
      ElMessage({
        type: 'warning',
        message: '取消删除!',
      })
    })
  } else {
    ElMessage({
      type: 'error',
      message: '请先选择数据!'
    })
  }
}
// 导入成功后重新加载数据
const handleExcelImportSuccess = () => {
  getList();
  ElMessage({
    type: 'success',
    message: '上传成功!',
  })
}

// 导出数据
const exp = () => {
  window.open(`http://192.168.31.242:8080/goods/export`)
}


// excle导入前验证-->文件类别、文件大小
const cheackExcle = (rawFile) => {
  // console.log(rawFile.size)
  // 获取文件后缀
  let testmsg = rawFile.name.substring(rawFile.name.lastIndexOf('.') + 1)
  if (testmsg !== 'xlsx') {
    ElMessage.error('只能上传后缀为.xlsx的excle文件')
    return false
  } else if (rawFile.size / 1024 / 1024 > 2) {
    ElMessage.error('文件大小不能超过2M')
    return false
  }
  return true
}


// 图片上传之前的钩子--》判断文件是否是图片、图片大小是否符合要求
const beforeAvatarUpload = (rawFile) => {
  console.log("类型:", rawFile.type)
  console.log(rawFile.type !== 'image/png');
  // 获取文件后缀
  let testmsg = rawFile.name.substring(rawFile.name.lastIndexOf('.') + 1)
  console.log("后缀:", testmsg)
  // 判断文件类型
  const extension = testmsg === 'jpg' || testmsg === 'png' || testmsg === 'jpeg' || testmsg === 'gif'
  if (!extension) {//不符合要求
    ElMessage.error('图片必须是jpg/jpeg/png格式!')
    return false
  }
  // 校验文件大小
  if (rawFile.size / 1024 / 1024 > 2) {
    ElMessage.error('图片大小不能超过2MB!')
    return false
  }
  return true
}
// 图片上传后的钩子函数-->回写图片地址
const handleAvatarSuccess = (res) => {
  console.log(res)
  goods.value.imgurl = res.msg
}

//生命周期函数
onMounted(() => {
  getList();
})
</script>

<template>
  <!--  数据表格-->
  <el-form-item label="名称" style="margin-top: 20px">
    <el-input v-model="name" style="width: 200px" placeholder="请输入商品名称"></el-input>
    <el-button type="primary" :icon="Search" style="margin-left: 10px" @click="search">搜索</el-button>
    <el-button type="primary" :icon="Plus" style="margin-left: 10px" @click="add">新增</el-button>
    <el-button type="danger" :icon="Delete" style="margin-left: 10px" @click="delBatch">批量删除</el-button>
    <el-upload :action="'http://' + '192.168.31.242:8080/goods/import'"
               :show-file-list="false"
               accept="xlsx"
               :before-upload="cheackExcle"
               :on-success="handleExcelImportSuccess"
               style="display: inline-block">
      <el-button type="primary" style="margin-left: 10px" :icon="UploadFilled">导入</el-button>
    </el-upload>

    <el-button type="warning" style="margin-left: 10px" :icon="Download" @click="exp">导出</el-button>
  </el-form-item>
  <el-table :data="list" width="100%" stripe @selection-change="handleSelectionChange">
    <el-table-column type="selection" width="55"/>
    <el-table-column type="index" label="序号" width="80" :index="1"></el-table-column>
    <!--  <el-table-column prop="id" label="ID"></el-table-column>-->
    <el-table-column prop="name" label="商品名称"></el-table-column>
    <el-table-column prop="price" label="单价"></el-table-column>
    <el-table-column prop="intro" label="简介" width="100" show-overflow-tooltip></el-table-column>
    <el-table-column prop="amount" label="库存"></el-table-column>
    <el-table-column prop="goodsType" label="类别"></el-table-column>
    <el-table-column prop="remark" label="状态"></el-table-column>
    <el-table-column label="图片" width="120">
      <template #default="scope">
        <el-image style="width: 100px; height: 80px; padding: 5px;" :src="scope.row.imgurl" :zoom-rate="1.2"
                  :max-scale="7" :min-scale="0.2" :preview-src-list="[scope.row.imgurl]" :preview-teleported="true"/>
      </template>
    </el-table-column>

    <el-table-column label="操作" width="300" fixed="right">
      <template #default="scope">

        <el-button type="primary" :icon="Edit" @click="edit(scope.row)">修改</el-button>
        <el-button type="danger" :icon="Delete" @click="del(scope.row.id)">删除</el-button>
      </template>
    </el-table-column>
  </el-table>
<!--  数据分页-->
  <div style="padding: 10px">
    <el-pagination
        v-model:current-page="pageNum"
        v-model:page-size="pageSize"
        :page-sizes="[10, 20, 30, 40]"
        size="large"
        background
        layout="total, sizes, prev, pager, next, jumper"
        :total="total"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
    />
  </div>
  <!--模态框-->
  <el-dialog
      v-model="dialogVisible"

      :title="goods.id ? '修改商品' : '新增商品'"
      width="500"
      ref="goodsForm"
  >
    <el-form :model="goods" :rules="rules" ref="goodsForm">
      <el-form-item label="商品名称" prop="name">
        <el-input v-model="goods.name" autocomplete="off"/>
      </el-form-item>
      <el-form-item label="商品单价" prop="price">
        <el-input v-model="goods.price" autocomplete="off"/>
      </el-form-item>
      <el-form-item label="商品简介" prop="intro">
        <el-input v-model="goods.intro" autocomplete="off"/>
      </el-form-item>
      <el-form-item label="商品库存" prop="amount">
        <el-input v-model="goods.amount" autocomplete="off"/>
      </el-form-item>
      <el-form-item label="商品类别" prop="goodsType">
        <el-select v-model="goods.goodsType" placeholder="请选择商品类别">
          <el-option label="手机" value="手机"/>
          <el-option label="电脑" value="电脑"/>
          <el-option label="水果" value="水果"/>
        </el-select>
      </el-form-item>
      <!--  单选框-->
      <el-form-item label="状态" prop="remark">
        <el-radio-group v-model="goods.remark">
          <el-radio value="上架">上架</el-radio>
          <el-radio value="未上架">未上架</el-radio>
        </el-radio-group>
      </el-form-item>
      <!--      图片上传-->
      <el-form-item label="商品图片">
        <el-upload
            class="avatar-uploader"
            :action="'http://' + '192.168.31.242:8080/files/upload'"
            :show-file-list="false"
            :on-success="handleAvatarSuccess"
            :before-upload="beforeAvatarUpload"
        >
          <img v-if="goods.imgurl" :src="goods.imgurl" class="avatar" style="width: 300px"/>
          <el-icon v-else class="avatar-uploader-icon">
            <Plus/>
          </el-icon>
        </el-upload>
      </el-form-item>
    </el-form>
    <template #footer>
      <div class="dialog-footer">
        <el-button @click="dialogVisible = false">取消</el-button>
        <el-button type="primary" @click="save">
          提交
        </el-button>
      </div>
    </template>
  </el-dialog>
</template>

<style scoped>
.avatar-uploader .el-upload {
  border: 1px dashed var(--el-border-color);
  border-radius: 6px;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  transition: var(--el-transition-duration-fast);
}

.avatar-uploader .el-upload:hover {
  border-color: var(--el-color-primary);
}

.el-icon.avatar-uploader-icon {
  font-size: 28px;
  color: #8c939d;
  width: 178px;
  height: 178px;
  text-align: center;
}
</style>

11.创建首页组件:Index.vue

javascript 复制代码
<script setup>
import { reactive, onMounted,ref} from 'vue'
import { Memo } from '@element-plus/icons-vue'
import * as echarts from 'echarts'
import request from "@/axios/request.js";

// 格式化日期时间
const formattedDate=()=>{
  const date = new Date();
  const year = date.getFullYear();
  const month = (date.getMonth() + 1).toString().padStart(2, '0');
  const day = date.getDate().toString().padStart(2, '0');
  const hour = date.getHours().toString().padStart(2, '0');
  const minute = date.getMinutes().toString().padStart(2, '0');
  const second = date.getSeconds().toString().padStart(2, '0');
  const formattedDate = `${year}-${month}-${day} ${hour}:${minute}:${second}`;
  console.log(formattedDate);
  return formattedDate;

}
let date=formattedDate();
const loginInfo=ref({
  loginTime: date,
  loginPlace: "云南-昆明"
})
const data = reactive({
  user: JSON.parse(localStorage.getItem('xm-user') || '{}'),
  noticeData: []
})

// 查询公告信息
const getNoticeList = () => {
  request({
    url: '/notice/page',
    method: 'get',
    params: {
      page: 1,
      limit: 4,
    }
  }).then((res) => {
    // console.log(res)
    data.noticeData = res.data.data;
  })
}

// 商品数量
const total=ref(0)
// 将用户信息从浏览器缓存中取出
const user=ref(JSON.parse(localStorage.getItem('xm-user') || '{}'))
// 查询商品信息
const getList = () => {
  request({
    url: '/goods/page',
    method: 'get',
    params: {
      page: 1,
      limit: 10,
      goodsName: name.value,
    }
  }).then((res) => {
    // console.log(res)
    // list.value = res.data.data;
    total.value = res.data.count;
    console.log(total.value)
    // name.value=""
  })
}
// 图表1:月度销售额
const initCharts1 = () => {
  const myChart = echarts.init(document.getElementById('salesVolume'))
  myChart.setOption({
    color: ['#1493fa'],
    title: { text: '2024年月度销售额' },
    xAxis: {
      data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
      name: '月份',
      axisLabel: {
        interval: 0
      },
    },
    yAxis: {
      name: '单位(千万元)',
    },
    grid: {
      left: '3%',
      right: '8%',
      bottom: '5%',
      containLabel: true,
    },
    legend: {},
    series: [
      {
        data: [6, 7, 8.5, 8, 9, 10, 13, 12, 10, 16, 15, 14],
        type: 'line',
        name: '销售额',
        smooth: true,
        label: {
          show: true,
          position: 'top',
        }
      }
    ]
  })
  // 图表自适应大小
  window.onresize = () => {
    myChart.resize()
  }
}
// 图表2:2024年订单数量
const initCharts2 = () => {
  const myChart = echarts.init(document.getElementById('orderQuantity'))
  myChart.setOption({
    title: { text: '2024年订单数量' },
    color: ['#1493fa'],
    grid: {
      left: '3%',
      right: '8%',
      bottom: '3%',
      containLabel: true,
    },
    xAxis: {
      type: 'category',
      data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
      name: '月份',
      // 类目轴中在 boundaryGap 为 true 的时候有效,可以保证刻度线和标签对齐
      axisTick: {
        alignWithLabel: true,
      },
      axisLabel: {
        interval: 0,rotate: 45 // 设置刻度标签旋转角度为45度
      },
    },
    legend: {},
    yAxis: {
      name: '单位(个)',
    },
    series: [
      {
        data: [400, 450, 300, 230, 250, 300, 400, 350, 160, 350, 380, 400],
        type: 'bar',
        barWidth: '60%',
        name: '订单总数',
        label: {
          show: true,
          position: 'top',
        }
      }
    ]
  })
  // 图表自适应大小
  window.onresize = () => {
    myChart.resize()
  }
}

//生命周期函数
onMounted(() => {
  getList();
  getNoticeList()
  // 初始化图表
  initCharts1()
  initCharts2()
})
</script>

<template>

  <el-row :gutter="20">
    <!--    登录信息-->
    <el-col :span="6">
      <el-card class="box-card">
<!--      <el-card class="box-card">
        <template #header>
          <div class="card-header">
            <img :src="user.avatar" style="width: 40px; height: 40px;">
&lt;!&ndash;            <el-avatar class="avatar" src="@/assets/img.png" shape="square" :size="40"> </el-avatar>&ndash;&gt;
            <span style="font-size: 24px;">{{ user.name }} </span>
          </div>
        </template>
        <div class="info">
          <p>登录时间:{{ loginInfo.loginTime }}</p>
          <p>登录地点:{{ loginInfo.loginPlace }}</p>
        </div>
      </el-card>-->

      <div style="display: flex">
        <div class="card" style="flex: 50%; height: 400px;width: 200px">
          <div style="font-weight: bold; font-size: 18px; padding: 10px 0 30px 10px">系统公告</div>
          <el-timeline style="max-width: 600px">
            <el-timeline-item
                v-for="(item, index) in data.noticeData"
                :key="index"
                :timestamp="item.time"
            >
              {{ item.title }}
            </el-timeline-item>
          </el-timeline>
        </div>
        <div style="flex: 50%"></div>
      </div>
      </el-card>
    </el-col>
    <!-- 单月统计信息展示 -->
    <el-col :span="18" >
      <el-card class="box-card">
        <template #header>
          <div class="card-header">
            6月统计信息
          </div>
        </template>
        <div class="info" style="height: 340px">
          <el-row :gutter="24">
            <!-- 商品数量 -->
            <el-col :span="8">
              <div class="card-container">
                <div class="card-left-container" style="background-color: #EEAD0E;">
                  <el-icon :size="90">
                    <Memo />
                  </el-icon>
                </div>
                <div class="card-right-container">
                  <p class="number">{{ total }}</p>
                  <p>商品数量(个)</p>
                </div>
              </div>
            </el-col>
            <!-- 商品分类数量 -->
            <el-col :span="8">
              <div class="card-container">
                <div class="card-left-container" style="background-color: #AB82FF;">
                  <el-icon :size="90">
                    <Memo />
                  </el-icon>
                </div>
                <div class="card-right-container">
                  <p class="number">20</p>
                  <p>商品分类数量(个)</p>
                </div>
              </div>
            </el-col>
            <!-- 用户访问次数 -->
            <el-col :span="8">
              <div class="card-container">
                <div class="card-left-container" style=" background-color: #63B8FF;">
                  <el-icon :size="90">
                    <Memo />
                  </el-icon>
                </div>
                <div class="card-right-container">
                  <p class="number">121</p>
                  <p>用户访问次数(次)</p>
                </div>
              </div>
            </el-col>
          </el-row>
        </div>
      </el-card>
    </el-col>
  </el-row>
  <!-- 图表区域 -->
  <el-row :gutter="20" style="margin-top: 10px">
    <el-col :span="12">
      <!-- 通过折线图展示2024年月度销售额 -->
      <el-card class="box-card">
        <div id="salesVolume" style="width: auto; height:400px;">

        </div>
      </el-card>
    </el-col>
    <el-col :span="12">
      <!-- 通过柱状图展示2024年订单数量 -->
      <el-card class="box-card">
        <div id="orderQuantity" style="width: auto; height:400px;">

        </div>
      </el-card>
    </el-col>
  </el-row>
</template>

<style scoped>

</style>

12.创建登录组件:Login.vue

javascript 复制代码
<script setup>
import {ref} from "vue";
import {User,Lock} from "@element-plus/icons-vue";
import request from "@/axios/request.js";
import router from "@/router/router.js";
import {ElMessage} from "element-plus";
import {useRouter} from "vue-router";
// const router=useRouter()

const user=ref({
  username:"",
  password:"",
  role:""
})
const rules={
  username:[
    {required:true,message:"请输入用户名",trigger:"blur"}
  ],
  password:[
    {required:true,message:"请输入密码",trigger:"blur"}
  ],
  role:[
    {required:true,message:"请选择角色",trigger:"blur"}
  ]
}
// 表单引用
const formRef=ref()
// 登录验证
const login=()=>{
  formRef.value.validate((flag)=>{
    if(flag){
      request({
        url:"/login",
        method:"post",
        data:user.value
      }).then(res => {
        console.log(res)
        if (res.data.code==200){
          ElMessage.success("登录成功!")
          // 将用户信息保存到浏览器缓存中
          localStorage.setItem('xm-user', JSON.stringify(res.data.data))
          // 跳转首页
          router.push("/manage/index")
        }else {
          ElMessage.error(res.data.msg)
        }
      })
    }
  })
}

</script>

<template>
  <div class="login-container">
    <div class="login-box">
      <div style="font-weight: bold; font-size: 24px; text-align: center; margin-bottom: 30px; color: #1450aa">欢 迎 登 录</div>
      <el-form ref="formRef" :model="user" :rules="rules">
        <el-form-item prop="username">
          <el-input :prefix-icon="User" size="large" v-model="user.username" placeholder="请输入账号"></el-input>
        </el-form-item>
        <el-form-item prop="password">
          <el-input show-password :prefix-icon="Lock" size="large" v-model="user.password" placeholder="请输入密码"></el-input>
        </el-form-item>
        <el-form-item prop="role">
          <el-select size="large" v-model="user.role" placeholder="请选择角色">
            <el-option value="ADMIN" label="管理员"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button size="large" type="primary" style="width: 100%" @click="login">登 录</el-button>
        </el-form-item>
        <div style="text-align: right">
          还没有账号?请 <router-link to="/register"><b style="color: dodgerblue">注册</b></router-link>
        </div>
      </el-form>
    </div>
  </div>
</template>

<style scoped>
.login-container {
  height: 100vh;
  overflow: hidden;
  display: flex;
  justify-content: center;
  align-items: center;
  background: linear-gradient(to top, #7f7fd5, #86a8e7, #91eae4);
}
.login-box {
  width: 350px;
  padding: 30px;
  border-radius: 5px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
  background-color: rgba(255, 255, 255, 0.5);
}
</style>

13.添加路由规则--router.js

javascript 复制代码
import {createRouter,createWebHashHistory} from "vue-router";
// 在router.js文件中创建路由实例对象
const router=createRouter({
    history:createWebHashHistory(),
    routes:[//路由规则
        {path:"/",redirect:"/login"},
        {path:"/manage",component:()=>import('@/views/Manage.vue'),
        children:[//子路由
            {path:"index",component:()=>import('@/views/manage/Index.vue')},
            {path:"goods",component:()=>import('@/views/manage/Goods.vue')},
            {path:"admin",component:()=>import('@/views/manage/Admin.vue')},
            {path:"personal",component:()=>import('@/views/manage/Personal.vue')},
            {path:"password",component:()=>import('@/views/manage/Password.vue')},
            {path:"notice",component:()=>import('@/views/manage/Notice.vue')},
        ]
        },
        {path:"/login",component:()=>import('@/views/Login.vue')},
        {path:"/404",component:()=>import('@/views/404.vue')},
        {path:"/register",component:()=>import('@/views/Register.vue')},
        {path: '/:pathMatch(.*)', redirect: '/404' }


    ]
})
// 输出实例对象
export default router;

效果图:

首页:

商品信息管理:

完整项目可以从资源下载

相关推荐
HiWorldNice1 分钟前
如何在Centos7中设置tomcat开机自启动
java·centos·tomcat
MavenTalk7 分钟前
Spring Cloud Alibaba:一站式微服务解决方案
java·数据库·spring boot·spring cloud·微服务·netflix
HUWD27 分钟前
Java 实现给pdf文件指定位置盖章功能
java·pdf
cwtlw31 分钟前
如何创建maven工程
java·笔记·后端·学习·maven
路在脚下@33 分钟前
Spring Boot中使用YAML配置文件
java·spring boot
发光者35 分钟前
Maven、mybatis框架
java·数据库·maven·mybatis
梓沂42 分钟前
pom.xml中dependencyManagement的作用
xml·java·前端
太空漫步111 小时前
逆序对个数
java·算法·排序算法
养佳工具人1 小时前
Java各种排序
java·算法·排序算法
Fly_hao.belief1 小时前
泛型的讲解(泛型类、泛型方法、泛型接口)
java