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

快速搭建SpringBoot3+Vue3管理系统

  • 前端项目搭建(默认开发环境:node20,Jdk17)
    • 创建项目并下载依赖--执行以下命令

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

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

  1. 创建项目
yarn create vite admin-vue --template

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

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文件

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文件

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

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

7.挂载ElementPlus和路由
在main.js中挂载

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中配置绝对路径别名–@

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 顶部水平导航

<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 侧边导航栏

<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 底部导航栏

<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.创建整体布局,引用子组件

<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

<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

<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

<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

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;

效果图:
在这里插入图片描述
首页:
在这里插入图片描述
商品信息管理:
在这里插入图片描述

完整项目可以从资源下载

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/933541.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

链式设计模式——装饰模式和职责链模式

一、装饰模式 1、概述 动态地给一个对象添加一些额外的职责&#xff0c;就增加功能来说&#xff0c;装饰模式比生成子类更为灵活。 ConcreteComponent &#xff1a;是定义了一个具体的对象&#xff0c;可以给这个对象添加一些职责&#xff1b;Decorator &#xff1a;装饰抽象…

【Elasticsearch】实现用户行为分析

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/literature?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;…

Linux评估网络性能

网络性能直接影响应用程序对外提供服务的稳定性和可靠性 ping命令检测网络的连通性 如果网络反应缓慢&#xff0c;或连接中断&#xff0c;可以用ping来测试网络的连通情况 time值(单位为毫秒)显示了两台主机之间的网络延时情况。如果此值很大&#xff0c;则表示网络的延时很大…

工业智能网关如何为企业实现智能制造赋能?

在数字化转型的浪潮中&#xff0c;工业智能网关作为连接物理世界与数字世界的桥梁&#xff0c;正逐步成为智能制造领域的核心组件。本文将通过一个实际使用案例&#xff0c;深入剖析工业智能网关如何助力企业实现生产流程的优化、数据的高效采集与分析&#xff0c;以及智能化决…

【LLMs】用LM Studio本地部署离线大语言模型

文章目录 一、下载LM Studio二、下载大语言模型1. 查看模型介绍2. 点击模型文件进行下载2.1 完整下载2.2 部分下载 三、加载模型1. 打开LM Studio图形化界面&#xff0c;点击**My Models**2. 然后&#xff0c;点击“...”&#xff0c;选择“change”&#xff0c;选择刚下载好的…

Python_Flask04(牛马问答平台01)

项目名称&#xff1a;牛马问答平台 项目简介&#xff1a;1.主要用来给社会上的牛马们探讨老板的恶心之处。 2. 用来学习交流。 技术手段&#xff1a;Python3.x Flask render_template pymysql flask_sqlalchemy........ 注意该博客为迭代项目&#xff0c;于最后一期展示完整…

【时时三省】(C语言基础)结构体内存对齐练习题

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ----CSDN 时时三省 练习一 这个输出结果是8 练习二 这个输出结果是16 练习三 这个输出结果是32 上面的输出结果都是根据结构体对齐规则来计算的

在ARM Linux应用层下使用SPI驱动WS2812

文章目录 1、前言2、结果展示3、接线4、SPI驱动WS2812原理4.1、0码要发送的字节4.2、1码要发送的字节4.3、SPI时钟频率 5、点亮RGB5.1、亮绿灯5.2、亮红灯5.3、亮蓝灯5.4、完整程序 6、RGB呼吸灯7、总结 1、前言 事情是这样的&#xff0c;前段时间&#xff0c;写了一个基于RK3…

unity3d—demo(2d人物左右移动发射子弹)

目录 人物代码示例&#xff1a; 子弹代码示例&#xff1a; 总结上面代码&#xff1a; 注意点&#xff1a; 人物代码示例&#xff1a; using System.Collections; using System.Collections.Generic; using UnityEngine;public class PlayerTiao : MonoBehaviour {public f…

Python subprocess.run 使用注意事项,避免出现list index out of range

在执行iOS UI 自动化专项测试的时候&#xff0c;在运行第一遍的时候遇到了这样的错误&#xff1a; 2024-12-04 20:22:27 ERROR conftest pytest_runtest_makereport 106 Test test_open_stream.py::TestOpenStream::test_xxx_open_stream[iPhoneX-xxx-1-250] failed with err…

怎么样能使Ubuntu的文件浏览器显示当前目录的路径,而不是只显示一个文件名?

默认情况下Ubuntu的文件浏览器是只显示当前目录的目录名的&#xff0c;这很不便我们查看路径或直接利用路径进行定位&#xff0c;那么怎么样能使Ubuntu的文件浏览器显示当前目录的路径呢&#xff1f; 两种方法&#xff1a; 第1种-临时方法 按下快捷键 Ctrl L&#xff0c;导航…

自制shell命令行解释器,深入理解Linux系统命令行实现原理

个人主页&#xff1a;敲上瘾-CSDN博客 个人专栏&#xff1a;Linux学习、游戏、数据结构、c语言基础、c学习、算法 目录 ​编辑 1.打印命令提示符 ​编辑 2.获取用户输入指令 3.重定向分析 4.命令行参数表与环境变量表 5.命令解析 6.命令执行 6.1.创建子进程 6.2.文件…

LangChain:大模型AI应用开发的强大引擎

文章目录 LangChain的核心功能LangChain的典型使用场景LangChain的未来展望《LangChain大模型AI应用开发实践》编辑推荐内容简介作者简介目录 在人工智能领域&#xff0c;大型语言模型&#xff08;LLMs&#xff09;的应用开发正逐渐成为技术前沿的热门话题。随着技术的不断进步…

【MFC】vs2019中使用sqlite3完成学生管理系统

目录 效果图list Contral 控件的简单使用使用sqlite3 效果图 使用sqlite3完成简单的数据库操作。 list Contral 控件的简单使用 本章只介绍基本应用 添加表头&#xff1a;语法&#xff1a; int InsertColumn(int nCol, LPCTSTR lpszColumnHeading, int nFormat LVCFMT_LEFT…

杨振宁大学物理视频中黄色的字,c#写程序去掉

先看一下效果&#xff1a;&#xff08;还有改进的余地&#xff09; 我的方法是笨方法&#xff0c;也比较刻板。 1&#xff0c;首先想到&#xff0c;把屏幕打印下来。c#提供了这样一个函数&#xff1a; Bitmap bmp new Bitmap(640, 480, PixelFormat.Format32bppArgb); // 创…

Android 逆向/反编译/Hook修改应用行为 基础实现

前言&#xff1a;本文通过一个简单的情景案例实现安卓逆向的基本操作 一、情景描述 本文通过一个简单的情景案例来实现安卓逆向的基本操作。在这个案例中所使用的项目程序是我自己的Demo程序&#xff0c;不会造成任何的财产侵害&#xff0c;本文仅作为日常记录及案例分享。实…

OSCP - Proving Grounds - Zino

主要知识点 SMB知识python脚本提权 具体步骤 执行nmap Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-10-10 01:24 UTC Nmap scan report for 192.168.52.64 Host is up (0.00077s latency). Not shown: 65529 filtered tcp ports (no-response) PORT STATE SER…

VLA模型

目录 引言1. 机器人大模型面临的挑战2. 目前的数据集2.1 RT-12.2 Open X-Embedding2.3 DROID 3. 目前的VLA模型3.1 Goat3.2 RT-13.2.1 总体架构3.2.2 效果 3.3 RT-23.3.1 总体架构3.3.2 效果 3.4 RT-X3.4.1 模型效果1). RT-1-X2). RT-2-X 3.5 RT-H3.5.1 总体架构3.5.2 效果 3.6…

aws(学习笔记第十六课) 使用负载均衡器(ELB)解耦webserver以及输出ELB的日志到S3

aws(学习笔记第十六课) 使用负载均衡器(ELB)以及输出ELB的日志到S3 学习内容&#xff1a; 使用负载均衡器(ELB)解耦web server输出ELB的日志到S3 1. 使用负载均衡器(ELB) 全体架构 使用ELB(Elastic Load Balancer)能够解耦外部internet访问和web server之间的耦合&#xff0c…

如何使用Java编写Jmeter函数

Jmeter 自带有各种功能丰富的函数&#xff0c;可以帮助我们进行测试&#xff0c;但有时候提供的这些函数并不能满足我们的要求&#xff0c;这时候就需要我们自己来编写一个自定义的函数了。例如我们在测试时&#xff0c;有时候需要填入当前的时间&#xff0c;虽然我们可以使用p…