一、环境搭建
项目代码
第一步:
创建新项目
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本身触发的响应事件,调用接口数据,渲染即可
分页功能的实现,分为两个:
- 商品页面初始化时展示全部的商品列表
思路:点击当前页码时el组件自身带的当前页号索引传递给父组件,触发父组件中的请求当前页面的参数进行页面渲染
- 搜索时全部的商品列表
思路:由于后端接口没有进行分页,就需要重新分组展示,查询数据后将第一页的内容先渲染上,然后点击当前页码时el组件自身带的当前页号索引传递给父组件,触发父组件中的请求当前页面的参数进行页面渲染,渲染的是处理过后的内容
共同点是都要改变当前的tableData值,与total查询总条数,展现列表页数.
total查询总条数/展现列表页数=当前展示页面的总条数