Vue3-后台管理系统(第三篇上)

一、环境搭建

项目代码
第一步:
创建新项目

vue create 项目名

第二步:
设置全局css样式、引入图标库
reset.css
到main.js文件中引入css样式
在这里插入图片描述

到阿里巴巴矢量库,将需要的图标添加到项目中,点击复制代码引入
在这里插入图片描述现在好像不支持链接的形式,可以下载文件本地引入即可

第三步:
添加elementUi组件

npm install element-plus --save

如果提示需要安装python2.x版本的环境就去官网下载一个2.x的版本,下载后可能没有Script文件,想要生成找到此文件运行setup.py.git,配置环境变即可
在这里插入图片描述

第四步:
界面路由搭建
搭建需要的路由格式
Vue3-路由跳转专题详细总结

import {
  createRouter,
  createWebHistory
} from 'vue-router'
import Layout from '../views/Layout/index.vue'
import Login from '../views/Login/LoginPage.vue'
//异步加载=>import
const GoodsPage = () => import('../views/Goods/GoodPage.vue')
const AdvertPage = () => import('../views/Advert/AdvertPage.vue')
const OrderPage = () => import('../views/Order/OrderPage.vue')
const HomePage = () => import('../views/Home/HomePage.vue')
const ParamsPage = () => import('../views/Params/ParamsPage.vue')
const OrderBack = () => import('../views/Order/OrderBack/OrderBack.vue')
const OrderList = () => import('../views/Order/OrderList/OrderList.vue')
const AddGoods = ()=> import('../views/Goods/GoodAddPage.vue')
const routes = [{
    path: '',
    component: Layout,
    children: [{
        path: '',
        name: "HomePage",
        component: HomePage
      },
      {
        path: '/goods',
        name: "GoodsPage",
        component: GoodsPage
      },
      {
        path: '/advert',
        name: "AdvertPage",
        component: AdvertPage
      },
      {
        path: '/order',
        //重定向路由
        component: OrderPage,
        redirect:'/order/orderlist',
        children: [{
            path: 'orderlist',
            name: "OrderList",
            component: OrderList
          },
          {
            path: 'order-back',
            name: "OrderBack",
            component: OrderBack
          }
        ]
      },
      {
        path: '/params',
        name: "ParamsPage",
        component: ParamsPage
      },
      {
        path:'/addGoods',
        component: AddGoods
      }
    ]
  },
  {
    path: '/login',
    name: 'Login',
    component: Login
  }
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router

二、UI绘制

layout布局分为left导航栏和content内容区,先用css+html布局好,然后在进行填充
在这里插入图片描述
menu 使用的是fixed布局保持始终浏览器的左上角,开始的时候先给menu设置好高度,等装入组件用组件撑开高度删掉就好

<style scoped>
.menu {
  background-color: #545c64;
  position: fixed;
  top: 0;
  bottom: 0;
  z-index: 10;
}
.content {
  margin-top: 0;
  margin-left: 180px;
  transition: all 0.6s;
}
.isActive {
  margin-left: 64px;
  transition: all 0.3s;
}
</style>

右侧是使用class切换样式达到的效果

<template>
  <div class="layout">
    <MymenuPage class="menu" :isCollapse="isCollapse"></MymenuPage>
    <ContentPage
      class="content"
      :class="{ isActive: isCollapse }"
      :isCollapse="isCollapse"
      @changeCollapse="changeCollapse"
    ></ContentPage>
  </div>
</template>

找到组件中的nav导航,将对应的路由路径进行填充,vue3图标不能显示的原因是改版后使用时需要单个引入到文件中,也可以遍历挂在全局上
ElementUI3
menu文件

<template>
  <div class="nav">
    <el-menu
      active-text-color="#ffd04b"
      background-color="#545c64"
      class="el-menu-vertical-demo"
      default-active="$route.path"
      text-color="#fff"
      router
      :collapse="isCollapse"
    >
      <el-menu-item index="" class="firstTitle">
        <span>商城管理系统</span>
      </el-menu-item>
      <el-menu-item index="/">
        <el-icon><document /></el-icon>
        <span>首页</span>
      </el-menu-item>
      <el-menu-item index="/goods">
        <el-icon><document /></el-icon>
        <span>商品管理</span>
      </el-menu-item>
      <el-menu-item index="/params">
        <el-icon><setting /></el-icon>
        <span>规格参数</span>
      </el-menu-item>
      <el-menu-item index="/advert">
        <el-icon><setting /></el-icon>
        <span>广告分类</span>
      </el-menu-item>
      <el-sub-menu index="/order">
        <template #title>
          <el-icon><location /></el-icon>
          <span>订单管理</span>
        </template>
        <el-menu-item-group>
          <el-menu-item index="/order/orderlist">item one</el-menu-item>
          <el-menu-item index="/order/order-back">item one</el-menu-item>
        </el-menu-item-group>
      </el-sub-menu>
    </el-menu>
  </div>
</template>
<script>
import { Location, Setting, Menu, Document } from "@element-plus/icons-vue";
export default {
  props: ["isCollapse"],
  data() {
    return {};
  },
  components: {
    Location,
    Setting,
    Menu,
    Document,
  },
};
</script>

<style scoped>
.el-menu-vertical-demo:not(.el-menu--collapse) {
  width: 180px;
  min-height: 400px;
}
.el-sub-menu .el-menu-item {
  min-width: 179px;
}
.el-menu-item {
  align-items: center;
  justify-content: left;
}
.firstTitle {
  justify-content: center;
  font-size: 15px;
}
</style>

本身组件的时间需要进行添加方法,不然找不到会报错
在这里插入图片描述子组件调用父类传过来的的方法
在这里插入图片描述
使用前需要在父组件中的子组建传递这个方法

content布局文件

<template>
  <div class="content_st">
    <div class="head">
      <span
        v-show="isCollapse"
        class="iconfont icon-cebianshouqi"
        @click="changeMenu"
      ></span>
      <span
        v-show="!isCollapse"
        class="iconfont icon-cebianshouqi1"
        @click="changeMenu"
      ></span>
    </div>

    <div class="contentView">
      <router-view></router-view>
    </div>
  </div>
</template>
<script>
export default {
  props: ["isCollapse"],
  data() {
    return {
      input: "",
      tableData: [
        {
          date: "2016-05-03",
          name: "Tom",
          address: "No. 189, Grove St, Los Angeles",
        },
        {
          date: "2016-05-02",
          name: "Tom",
          address: "No. 189, Grove St, Los Angeles",
        },
        {
          date: "2016-05-04",
          name: "Tom",
          address: "No. 189, Grove St, Los Angeles",
        },
        {
          date: "2016-05-01",
          name: "Tom",
          address: "No. 189, Grove St, Los Angeles",
        },
      ],
    };
  },
  methods: {
    changeMenu() {
      this.$emit("changeCollapse");
    },
    sendInputData(value) {
      console.log(value);
      //获取input输入框内容
      //调取搜索接口数据
      //进行渲染页面
      //搜索时默认回车显示数据为初始数据
    },
  },
};
</script>
<style scoped>
.content_st {
  height: 860px;
  background-color: #f4f4f4;
}
.head {
  height: 50px;
  background-color: #1574c2;
  display: flex;
  align-items: center;
  padding: 0 20px;
}
.icon-cebianshouqi1,
.icon-cebianshouqi {
  font-size: 20px;
}

.content_st .contentView {
  margin: 0 20px;
}

</style>

三、node模拟后台服务搭建

//没有安装这个的可以安装一下网络请求库
npm install axios
cnpm i express -S
cnpm i mysql -S

在node_modules同级新建文件夹server
在这里插入图片描述在这里插入图片描述

index.js

//搭建服务
const express = require('express');

const app = express();

const router = require('./router');

app.use('/api',router)

app.listen(8989,()=>{
    console.log(8989)
})

mysql.js

//连接数据库  1.安装mysql 2.创建连接
const mysql = require('mysql')

//创建数据库连接
const client = mysql.createConnection({
    host: 'localhost', //数据域名 地址
    user: 'root', //数据名称
    password: '123456', //数据库密码 xampp集成
    database: 'ego',
    port:'3306'
})

//封装数据库操作语句 sql语句 参数数组arr  callback成功函数结果
function sqlFun(sql, arr,callback) {
    client.query(sql,arr, function (error, result) {
        if (error) {
            console.log('数据库语句错误');
            return;
        }
        callback(result)
    })
}

module.exports=sqlFun
//搭建服务
const express = require('express');

const router = express.Router();
const sqlFn = require('./mysql')
//路由接口
router.get('/',(req,res)=>{
    res.send("hello");
})
/**
 * 商品查询接口 search
 * 参数:search
 */
 router.get("/search", (req, res) => {
    var search = req.query.search;
    const sql = "select * from project where concat(`title`,`sellPoint`,`descs`) like '%" + search + "%'";
    sqlFn(sql, null, (result) => {
        if (result.length > 0) {
            res.send({
                status: 200,
                result
            })
        } else {
            res.send({
                status: 500,
                msg: "暂无数据"
            })
        }
    })
})
 
router.get('/projectList', (req, res) => {
    const page = req.query.page || 1;
    const sqlLen = "select * from project where id";
    sqlFn(sqlLen, null, data => {
        let len = data.length;
        const sql = "select * from project order by id desc limit 8 offset " + (page - 1) * 8;
        sqlFn(sql, null, result => {
            if (result.length > 0) {
                res.send({
                    status: 200,
                    data: result,
                    pageSize: 8,
                    total: len
                })
            } else {
                res.send({
                    status: 500,
                    msg: "暂无数据"
                })
            }
        })
    })
})
module.exports = router

输入

nodemon

在这里插入图片描述
测试一下是否启动成功,输入地址

在这里插入图片描述

将mysql文件导入数据库,查询数据成功
在这里插入图片描述

四、商品列表渲染页面

首先解决跨域问题
跨域问题详解
在这里插入图片描述

搭建请求api路径,咱们的目的就是为了方便想要挂在到原型链上使用,也可以直接这样请求

      axios.get('/api/api/projectList',{page :  1})
      .then(res => {
        console.log(res)
      })
      .catch(err => {
        console.error(err);
      });

挂在到原型链上使用VUE3挂在到原型链
在这里插入图片描述

address.js

const base = {
    projectList: '/api/api/projectList',
    search: '/api/api/search'
}
export default base;

index.js

import axios from 'axios';
import base from './address';

const api = {
    //获取商品列表
    getGoodList(params) {
        return axios.get(base.projectList, {
            params
        });
    },
    getSearchList(params){
        return axios.get(base.search, {
            params
        });
    }
};

export default api;

homePage.vue

<template>
  <div class="homeStyle">
    <div class="search">
      <div class="searchLeft">
        <el-input
          class="searchInput"
          v-model="input"
          placeholder="请输入查询的内容"
          @change="sendInputData"
        />
      </div>
      <div class="searchBtnRight">
        <el-button type="primary" @click="searchData(input)">查询</el-button>
        <el-button type="success">
          <router-link to="/addGoods" class="linkStyle">添加</router-link>
        </el-button>
      </div>
    </div>
    <div class="table-bd">
      <el-table :data="tableData" style="width: 100%" border>
        <el-table-column type="selection" width="40" />
        <el-table-column prop="id" label="商品ID" width="140" />
        <el-table-column prop="price" label="商品价格" width="140" />
        <el-table-column prop="title" label="商品类目" width="140" />
        <el-table-column prop="image" label="规格图片" />
        <el-table-column prop="sellPoint" label="商品卖点" />
        <el-table-column prop="descs" label="商品描述" />
        <el-table-column prop="name" label="操作" width="180">
          <template #default="scope">
            <el-button size="small" @click="handleEdit(scope.$index, scope.row)"
              >Edit</el-button
            >
            <el-button
              size="small"
              type="danger"
              @click="handleDelete(scope.$index, scope.row)"
              >Delete</el-button
            >
          </template>
        </el-table-column>
      </el-table>
    </div>
    <div class="page_pg">
      <MyPagination
        @changePage="changePage"
        :tableDataTotalLen="total"
        :tablePageSize="pageSize"
      ></MyPagination>
    </div>
  </div>
</template>
<script>
import MyPagination from "../../components/Pagin/MyPagination.vue";
import { reactive } from "vue";
export default {
  setup() {
    return reactive({
      tableData: [
        {
          date: "2016-05-03",
          name: "Tom",
          address: "No. 189, Grove St, Los Angeles",
        },
        {
          date: "2016-05-02",
          name: "Tom",
          address: "No. 189, Grove St, Los Angeles",
        },
        {
          date: "2016-05-04",
          name: "Tom",
          address: "No. 189, Grove St, Los Angeles",
        },
        {
          date: "2016-05-01",
          name: "Tom",
          address: "No. 189, Grove St, Los Angeles",
        },
      ],
    });
  },
  data() {
    return {
      input: "",
      total: "",
      pageSize: "",
      type: 0,
      list: [],
    };
  },
  watch:{
    input(newValue,oldValue){
      if(newValue != oldValue){
        this.getSearchData(newValue);
      }
    }
  },
  //默认type等于0,默认是商品列表数据
  //type等于1时,代表是搜索页面数据
  created() {
    this.initData(1);
  },
  updated() {},
  methods: {
    handleEdit(number, User) {
      console.log(index, row);
    },
    handleDelete(number, User) {
      console.log(index, row);
    },
    changePage(pageIndex) {
      //此时拿到数据渲染页面,重新请求数据
      if (this.type == 0) {
        this.initData(pageIndex);
      } else if (this.type == 1) {
        this.tableData = this.list.slice((pageIndex - 1) * 3, pageIndex * 3);
      }

      //查询的应该是Search数组下面的
    },
    initData(page) {
      // axios.get('/api/api/projectList',{page :  1})
      // .then(res => {
      //   console.log(res)
      // })
      // .catch(err => {
      //   console.error(err);
      // });
      this.$http
        .getGoodList({ page })
        .then((res) => {
          if (res.data.status === 200) {
            console.log(res.data);
            this.tableData = res.data.data;
            //正常是后端进行分页处理
            this.total = res.data.total;
            this.pageSize = res.data.pageSize;
            this.type = 0;
          }
        })
        .catch((err) => {
          console.log(err);
        });
    },
    getSearchData(value) {
      this.$http
        .getSearchList({
          search: value,
        })
        .then((res) => {
          if (res.data.status === 200) {
            console.log(value);
            console.log(res.data);
            // if ((res.data.result.length = 1)) {
            //   this.tableData = res.data.result.slice(0, 3);
            // } else if ((res.data.result.length = 2)) {
            // } else {
            // }
            this.total = res.data.result.length;
            this.pageSize = 3;
            this.list = res.data.result;
            this.tableData = res.data.result.slice(0, 3);
            this.type = 1;
            console.log(this.tableData);
          } else {
            this.tableData = [];
            this.pageSize = 1;
            this.total = 1;
          }
        })
        .catch((err) => {
          console.log(err);
        });
    },
    sendInputData(value) {
      //当value有值时
      if (value) {
        this.getSearchData(value);
      } else {
        this.initData(1);
      }
    },
  },
  components: {
    MyPagination,
  },
};
</script>
<style scoped>
.page_pg {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}
.table-bd {
  margin: 0 20px;
}
.search {
  display: flex;
  height: 40px;
  overflow: hidden;
  margin: 20px 20px;
}
.el-input {
  height: 40px;
}

.search .searchLeft {
  flex: 1;
  margin-right: 20px;
}
.search .searchBtnRight {
  display: flex;
  align-items: center;
  width: 300px;
}
.router-link-active,
.router-link-exact-active,
a:-webkit-any-link {
  text-decoration: none;
  color: white;
}
</style>

搜索功能的实现,利用el本身触发的响应事件,调用接口数据,渲染即可
分页功能的实现,分为两个:

  1. 商品页面初始化时展示全部的商品列表

思路:点击当前页码时el组件自身带的当前页号索引传递给父组件,触发父组件中的请求当前页面的参数进行页面渲染

  1. 搜索时全部的商品列表

思路:由于后端接口没有进行分页,就需要重新分组展示,查询数据后将第一页的内容先渲染上,然后点击当前页码时el组件自身带的当前页号索引传递给父组件,触发父组件中的请求当前页面的参数进行页面渲染,渲染的是处理过后的内容

共同点是都要改变当前的tableData值,与total查询总条数,展现列表页数.

total查询总条数/展现列表页数=当前展示页面的总条数

在这里插入图片描述
项目代码

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

可可鸭~

想吃糖~我会甜

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值