快速搭建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;

效果图:

首页:

商品信息管理:

完整项目可以从资源下载

相关推荐
IT毕设实战小研6 分钟前
基于大数据的跨国外派人员适应满意度与留存影响因素可视化分析
android·java·大数据·python·django·课程设计
letisgo59 分钟前
JAVA 高级进阶10篇《消息队列实战:RocketMQ/Kafka选型与“不丢不重有序“三连解》
java·面试·kafka·消息队列·rocketmq
2401_8504811714 分钟前
UVA101
java
Lyyaoo.24 分钟前
【回溯】【中等】全排列
java·数据结构·算法
xixingzhe225 分钟前
spring boot对接Dify
java·spring boot·dify
数据狐(Datafox)27 分钟前
京东商品详情API接口解析(附 JSON 样例)
java·开发语言·json
步行cgn29 分钟前
Spring Boot 指定数据来源详解
spring boot·后端·python
Wang's Blog31 分钟前
Java框架快速入门: Spring Security+OAuth2之深度定制UserDetailsService与密码无缝升级
java·开发语言·spring
土司大王36 分钟前
LeetCode 79 单词搜索:Java 回溯模板、网格 DFS 与剪枝优化
java·算法·leetcode·深度优先
白远山39 分钟前
智慧场馆解决方案小程序系统开发实战与架构设计指南
java·开发语言·小程序·架构