【Vue】Ego商城项目跟做

技术栈

Vue全家桶:Vue + VueRouter + Vuex + Axios +ElementUI

依赖安装

网络请求:npm install --save axios --no-fund

Element:vue add element

后端相关依赖:npm install --save express cors mysql --no-fund

token:npm install --save jsonwebtoken --no-fund

对象转换:npm install querystring --no-fund

文件上传:npm install --save multer --no-fund

富文本编辑器:npm install wangeditor --save --no-fund

快捷运行方案

npm install -g concurrently --no-fund

npm install -g nodemon --no-fund

项目框架搭建

1.使用vue create xxx命令创建项目
在这里插入图片描述

2.使用VS Code打开项目

在这里插入图片描述

端口号修改:修改vue.config.js文件,增加如下代码

在这里插入图片描述

添加elementUI

1.终端输入vue add element添加element
在这里插入图片描述

添加完毕后,项目新增element文件
在这里插入图片描述

2.运行项目,查看项目是否可正常运行

在这里插入图片描述注:因为版本问题,可能运行时会报错,以及element组件无法根据使用自动追加对应的库,所以这里element.js文件我改了,如上图所示

数据库准备

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;-- ----------------------------
-- Table structure for category
-- ----------------------------
DROP TABLE IF EXISTS `category`;
CREATE TABLE `category`  (`id` int(11) NOT NULL,`cid` int(11) NULL DEFAULT NULL,`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`pid` int(11) NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of category
-- ----------------------------
INSERT INTO `category` VALUES (1, 1001, '家用电器', 1);
INSERT INTO `category` VALUES (2, 1002, '手机/运营商/数码', 1);
INSERT INTO `category` VALUES (3, 1003, '电脑/办公', 1);
INSERT INTO `category` VALUES (4, 1004, '家具/家居', 1);
INSERT INTO `category` VALUES (5, 10001, '电视', 1001);
INSERT INTO `category` VALUES (6, 10001, '手机通讯', 1002);
INSERT INTO `category` VALUES (7, 10001, '电脑整机', 1003);
INSERT INTO `category` VALUES (8, 10001, '厨具', 1004);
INSERT INTO `category` VALUES (9, 10002, '空调', 1001);
INSERT INTO `category` VALUES (10, 10002, '运营商', 1002);
INSERT INTO `category` VALUES (11, 10002, '电脑配件', 1003);
INSERT INTO `category` VALUES (12, 10002, '家纺', 1004);
INSERT INTO `category` VALUES (13, 10003, '洗衣机', 1001);
INSERT INTO `category` VALUES (14, 10003, '摄影', 1002);
INSERT INTO `category` VALUES (15, 10003, '外设产品', 1003);
INSERT INTO `category` VALUES (16, 10003, '灯具', 1004);
INSERT INTO `category` VALUES (17, 10004, '冰箱', 1001);
INSERT INTO `category` VALUES (18, 10004, '摄像', 1002);
INSERT INTO `category` VALUES (19, 10004, '游戏设备', 1003);
INSERT INTO `category` VALUES (20, 10004, '家具', 1004);
INSERT INTO `category` VALUES (21, 100001, '超薄电视', 10001);
INSERT INTO `category` VALUES (22, 100002, '全面屏电视', 10001);-- ----------------------------
-- Table structure for project
-- ----------------------------
DROP TABLE IF EXISTS `project`;
CREATE TABLE `project`  (`id` int(11) NOT NULL AUTO_INCREMENT,`title` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '标题',`image` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '图片路径',`sellPoint` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`price` int(10) NULL DEFAULT NULL COMMENT '价格',`cid` int(11) NULL DEFAULT NULL,`num` int(11) NULL DEFAULT NULL,`barcode` tinyint(4) NULL DEFAULT NULL,`status` tinyint(4) NULL DEFAULT NULL,`created` datetime(0) NULL DEFAULT NULL,`updated` datetime(0) NULL DEFAULT NULL,`descs` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL,PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 31 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of project
-- ----------------------------
INSERT INTO `project` VALUES (1, '隐形的守护', NULL, '互动游戏', 128, 1, 4, NULL, 1, NULL, NULL, '这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!');
INSERT INTO `project` VALUES (2, '饥饿游戏', NULL, '影视作品', 58, 1, 5, NULL, 1, NULL, NULL, NULL);
INSERT INTO `project` VALUES (3, '家族4', NULL, 'slg游戏', 18, NULL, 123, NULL, 1, NULL, NULL, '这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!');
INSERT INTO `project` VALUES (4, '家族1', NULL, 'slg游戏', 12, NULL, 123, NULL, 1, NULL, NULL, '这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!');
INSERT INTO `project` VALUES (5, '家族2', NULL, 'slg游戏', 14, NULL, 22, NULL, 1, NULL, NULL, '这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!');
INSERT INTO `project` VALUES (6, '家族3', NULL, 'slg游戏', 18, NULL, 42, NULL, 1, NULL, NULL, '这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!这是描述!!!!');
INSERT INTO `project` VALUES (7, '三国群英传1', NULL, 'slg游戏', 12, NULL, 421, NULL, 1, NULL, NULL, '光荣只会卖情怀');
INSERT INTO `project` VALUES (9, '三国群英传3', NULL, 'slg游戏', 12, NULL, 421, NULL, 1, NULL, NULL, '光荣只会卖情怀');
INSERT INTO `project` VALUES (10, '三国群英传4', NULL, 'slg游戏', 12, NULL, 421, NULL, 1, NULL, NULL, '光荣只会卖情怀');
INSERT INTO `project` VALUES (11, '三国群英传5', NULL, 'slg游戏', 12, NULL, 421, NULL, 1, NULL, NULL, '光荣只会卖情怀');
INSERT INTO `project` VALUES (12, '三国群英传6', NULL, 'slg游戏', 12, NULL, 421, NULL, 1, NULL, NULL, '光荣只会卖情怀');
INSERT INTO `project` VALUES (13, '三国群英传7', NULL, 'slg游戏', 12, NULL, 421, NULL, 1, NULL, NULL, '光荣只会卖情怀');
INSERT INTO `project` VALUES (14, '三国群英传8', NULL, 'slg游戏', 12, NULL, 421, NULL, 1, NULL, NULL, '毫无亮点,走网游路线了');
INSERT INTO `project` VALUES (15, '三国志1', NULL, 'slg游戏', 32, NULL, 32, NULL, 1, NULL, NULL, '光荣只会卖情怀');
INSERT INTO `project` VALUES (16, '三国志2', NULL, 'slg游戏', 32, NULL, 32, NULL, 1, NULL, NULL, '光荣只会卖情怀');
INSERT INTO `project` VALUES (17, '三国志3', NULL, 'slg游戏', 32, NULL, 32, NULL, 1, NULL, NULL, '光荣只会卖情怀');
INSERT INTO `project` VALUES (18, '三国志4', NULL, 'slg游戏', 32, NULL, 32, NULL, 1, NULL, NULL, '光荣只会卖情怀');
INSERT INTO `project` VALUES (19, '三国志5', NULL, 'slg游戏', 32, NULL, 32, NULL, 1, NULL, NULL, '光荣只会卖情怀');
INSERT INTO `project` VALUES (20, '三国志6', NULL, 'slg游戏', 32, NULL, 32, NULL, 1, NULL, NULL, '光荣只会卖情怀');
INSERT INTO `project` VALUES (21, '三国志7', NULL, 'slg游戏', 32, NULL, 32, NULL, 1, NULL, NULL, '光荣只会卖情怀');
INSERT INTO `project` VALUES (22, '三国志8', NULL, 'slg游戏', 32, NULL, 32, NULL, 1, NULL, NULL, '光荣只会卖情怀');
INSERT INTO `project` VALUES (23, '三国志9', NULL, 'slg游戏', 32, NULL, 32, NULL, 1, NULL, NULL, '光荣只会卖情怀');
INSERT INTO `project` VALUES (24, '三国志10', NULL, 'slg游戏', 32, NULL, 32, NULL, 1, NULL, NULL, '光荣只会卖情怀');
INSERT INTO `project` VALUES (25, '三国志11', NULL, 'slg游戏', 32, NULL, 32, NULL, 1, NULL, NULL, '光荣只会卖情怀');
INSERT INTO `project` VALUES (26, '三国志12', NULL, 'slg游戏', 32, NULL, 32, NULL, 1, NULL, NULL, '光荣只会卖情怀');
INSERT INTO `project` VALUES (27, '三国志13', NULL, 'slg游戏', 32, NULL, 32, NULL, 1, NULL, NULL, '光荣只会卖情怀');
INSERT INTO `project` VALUES (28, '三国无双1', 'http://localhost:3000\\1732370097961-1.jpg', '任务强度高', 58, 10002, 500, NULL, 1, NULL, NULL, '<p>方式的监护人突然<strike>让我如很高的发挥</strike><font color=\"#f9963b\">地方规划人都热</font>帖该问题公司的归属感和无人</p>');
INSERT INTO `project` VALUES (30, '三国无双4', 'http://localhost:3000\\1732702595899-1.jpg', '情怀', 58, 10003, 12, NULL, 1, NULL, NULL, '<p>是的话<font color=\"#c24f4a\">标点符号的分</font>工</p>');-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (`id` int(11) NOT NULL AUTO_INCREMENT,`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`email` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (7, 'cy', '123456', '123456');
INSERT INTO `user` VALUES (8, 'admin', '123456', 'admin@123.com');
INSERT INTO `user` VALUES (9, 'admin1', '123456', 'admin@123.com');
INSERT INTO `user` VALUES (10, 'admin2', '123456', 'admin@123.com');SET FOREIGN_KEY_CHECKS = 1;

项目开发

1.准备页签栏

src/views文件夹内创建main/ADCategory.vuemain/ParamsView.vuemain/ProductView.vueLayoutView.vue文件

在这里插入图片描述

修改router/index.js文件,增加对应路由跳转配置

import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '../views/HomeView.vue'
import Layout from '../views/LayoutView.vue'Vue.use(VueRouter)const routes = [{path: '/',name: 'Layout',component: Layout,children:[{path: '',name: 'home',component: HomeView},{path:"product",name:"Product",component:() => import("../views/main/ProductView.vue")},{path:"params",name:"Params",component:() => import("../views/main/ParamsView.vue")},{path:"ad",name:"ADCategory",component:() => import("../views/main/ADCategory.vue")}]},
]
const router = new VueRouter({routes,// 去除路径上的#号mode:"history"
})
export default router

2.登录拦截

router文件夹下新建permission.js文件,编写拦截代码

import router from "./index"
import store from "../store"// 访问路径前判断权限
router.beforeEach((to,from,next) =>{if(to.meta.isLogin){// 暂未实现tokenlet token = false;console.log(token);if(token){next();}else{next({name:"Login"})}}else{next();}
})

修改router/index.js文件,给需要登录才能访问的路由设置isLogin参数

import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '../views/HomeView.vue'
import Layout from '../views/LayoutView.vue'Vue.use(VueRouter)const routes = [{path: '/',name: 'Layout',component: Layout,children:[{path: '',name: 'home',component: HomeView,meta:{isLogin:true}},{path:"product",name:"Product",component:() => import("../views/main/ProductView.vue"),meta:{isLogin:true}},{path:"params",name:"Params",component:() => import("../views/main/ParamsView.vue"),meta:{isLogin:true}},{path:"ad",name:"ADCategory",component:() => import("../views/main/ADCategory.vue"),meta:{isLogin:true}}]},{path:'/login',name:'Login',component:() => import("../views/LoginView.vue")}
]
const router = new VueRouter({routes,// 去除路径上的#号mode:"history"
})
export default router

修改main.js文件,导入permission.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import './plugins/element.js'
// 导入permission.js文件
import './router/permission'Vue.config.productionTip = falsenew Vue({router,store,render: h => h(App)
}).$mount('#app')

编写LoginView.vue文件

<template><div>登录注册</div>
</template><script>
export default {
}
</script><style>
</style>

运行效果
在这里插入图片描述

3.编写登录注册页

编写LoginView.vue文件

<template><div class="login"><el-card class="box-card"><div slot="header" class="clearfix"><span>Ego商城后台管理系统</span></div><div><el-tabs v-model="currentIndex" stretch type="border-card" @tab-click="handleTabClick"><el-tab-pane label="登录" name="login"><el-form :model="loginForm" status-icon ref="loginForm" :rules="rules"><el-form-item label="用户名:" label-width="80px" prop="username"><el-input type="text" v-model="loginForm.username" /></el-form-item><el-form-item label="密码:" label-width="80px" prop="password"><el-input type="password" v-model="loginForm.password" /></el-form-item><el-form-item ><el-button type="primary" @click="submitForm('loginForm')">登录</el-button></el-form-item></el-form></el-tab-pane><el-tab-pane label="注册" name="register"><el-form :model="registerForm" status-icon ref="registerForm" :rules="rules"><el-form-item label="用户名:" label-width="80px" prop="username"><el-input type="text" v-model="registerForm.username" /></el-form-item><el-form-item label="邮箱:" label-width="80px" prop="email"><el-input type="text" v-model="registerForm.email" /></el-form-item><el-form-item label="密码:" label-width="80px" prop="password"><el-input type="password" v-model="registerForm.password" /></el-form-item><el-form-item label="确认密码:" label-width="80px" prop="configurePassword"><el-input type="password" v-model="registerForm.configurePassword" /></el-form-item><el-form-item ><el-button type="primary" @click="submitForm('registerForm')">注册</el-button></el-form-item></el-form></el-tab-pane></el-tabs></div></el-card></div></template><script>
export default {data(){// 验证规则var validateUsername = (rule,value,callback) =>{if(value === ""){callback(new Error("请输入用户名"));}else if(value.length < 6){callback(new Error("长度不足6位"));}else{callback();}}var validatePassword = (rule,value,callback) =>{if(value === ""){callback(new Error("请输入密码"));}else{callback();}}var validateConfigurePassword = (rule,value,callback) =>{if(value === ""){callback(new Error("请输入密码"));}else if(value !== this.registerForm.password){callback(new Error("两次密码不一致"));}else{callback();}}return{currentIndex:"login",loginForm:{username:"",password:""},registerForm:{username:"",password:"",email:"",configurePassword:""},activeTab:"login",rules:{username:[{validator:validateUsername,trigger:'blur'}],password:[{validator:validatePassword,trigger:'blur'}],configurePassword:[{validator:validateConfigurePassword,trigger:'blur'}]}}},methods:{submitForm( formName ){this.$refs[formName].validate((valid) =>{if(valid){// 根据当前所在tabs页签的name的值去执行对应页签的逻辑if(this.activeTab === "login"){// 提交注册表单逻辑console.log(this.loginForm);}else if(this.activeTab === "register"){// 提交注册表单逻辑console.log(this.registerForm);}else{return;}}})},// 获取当前所在tabs页签的namehandleTabClick(tab){this.activeTab = tab.name;}}
}
</script><style scoped lang="less">
.login{width:1200px;margin: 0 auto;.box-card{width: 500px;margin: 100px auto;}
}
</style>

运行效果

在这里插入图片描述

4.nodeJS后台实现

在项目目录下新建server目录(与src目录同级),并新建index.jsrouter.jsconfig.js文件

编写index.js,实现服务器配置

const express = require("express");
const app = express();
const cors = require("cors");
const bodyParser = require("body-parser");
const router = require("./router")app.use(cors());
app.use(bodyParser.urlencoded({extended:true
}))
app.use("/api",router);
// 监听端口号
app.listen(3000,() =>{console.log(3000)
})

编写config.js,配置数据库链接配置

const mysql = require("mysql");
const client = mysql.createConnection({host:"localhost",user:"root",password:"123456",database:"ego_shop_one"
})
const sqlClient = (sql,arr,callback) =>{client.query(sql,arr,(error,result)=>{if(error){console.log(error);return;}callback(result);})
}module.exports = sqlClient;

编写router.js,编写服务器数据操作逻辑

const express = require("express");
const router = express.Router();
const sqlClient = require("./config")
const jwt = require("jsonwebtoken")/*** 注册*/
router.post("/register",(req,res) =>{const {username,password,email} = req.body;const sql = "INSERT INTO user VALUES(null,?,?,?)";const arr = [username,password,email];sqlClient(sql,arr,result=>{if(result.affectedRows > 0){res.send({status:200,msg:"注册成功"})}else{res.send({status:401,msg:"注册失败"})}})
})/*** 登录*/
router.post("/login",(req,res) =>{const {username,password} = req.body;const sql = "SELECT * FROM user WHERE username = ? and password = ?";const arr = [username,password];sqlClient(sql,arr,result =>{if(result.length > 0){let token = jwt.sign({username,id:result[0].id},'soomekeys')res.send({status:200,token,username})}else{res.send({status:401,msg:"登录失败"})}})
})
module.exports = router;

修改package.json文件,增加快捷运行脚本"dev": "concurrently \"npm run serve\" \"nodemon server/index.js\""

{"name": "my_shopyigou","version": "0.1.0","private": true,"scripts": {"serve": "vue-cli-service serve","build": "vue-cli-service build","lint": "vue-cli-service lint","dev": "concurrently \"npm run serve\" \"nodemon server/index.js\"" },......
}

运行npm run dev,可同步运行服务器和客户端;使用postman测试后端接口

在这里插入图片描述

5.前后端登录注册对接

在src目录下新建api目录,新建api/base.jsapi/index.js文件

编写api/index.js,实现请求跳转

import axios from "../utils/request"
import base from "./base"const api = {/*** 注册*/register(params){return axios.post(base.baseUrl + base.register,params)},/*** 登录*/login(params){return axios.post(base.baseUrl + base.login,params)}
}
export default api;

编写api/base.js

const base = {baseUrl:"http://localhost:3000",register:"/api/register",login:"/api/login"
}
export default base;

修改router/permission.js

import router from "./index"
import store from "../store"// 访问路径前判断权限
router.beforeEach((to,from,next) =>{if(to.meta.isLogin){// 获取tokenlet token = store.state.login.user.token;console.log(token);if(token){next();}else{next({name:"Login"})}}else{next();}
})

新建store/modules/login.js文件

export default{namespaced:true,state:{user:{username:"",token:""}},mutations:{setUser(state,user){state.user = user;}}
}

修改store/index.js文件

import Vue from 'vue'
import Vuex from 'vuex'
import login from './modules/login'Vue.use(Vuex)export default new Vuex.Store({modules: {login}
})

在src目录下新建utils目录,新建utils/request.jsutils/init.js文件

编写utils/request.js文件

import axios from 'axios'
import qs from "querystring"
import store from "../store"/*** 处理失败的方法*/
const errorHandle = (status,info) =>{switch(status){case 400:console.log("语义有误,当前请求无法被服务器理解。");break;case 401:// token:令牌console.log("服务器认证失败!");break;case 403:console.log("服务器已经理解请求,但是拒绝执行它!");break;case 404:console.log("请检查网络请求地址!");break;      case 500:console.log("服务器遇到了一个未曾预料的状态,导致它无法完成对请求的处理");break;  case 502:console.log("作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到的请求异常");break;default:console.log(info);break;}
}/*** 创建axios实例对象*/
const instance = axios.create({// 公共配置timeout:5000
})/*** 请求拦截*/
instance.interceptors.request.use((config) => {if(config.method === "post"){config.data = qs.stringify(config.data)}return config},(error) => {return Promise.reject(error);} 
);
/*** 响应拦截*/
instance.interceptors.response.use(//完成了(response) => {return response.status === 200 ? Promise.resolve(response) : Promise.reject(response);},(error) => {const {response} = error;errorHandle(response.status,response.info);}
);export default instance

编写utils/init.js文件

import store from "../store"
if(localStorage.getItem("ego")){store.commit("login/setUser",JSON.parse(localStorage.getItem("ego")))
}

修改main.js文件,导入使用./utils/init

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import './plugins/element.js'
import './router/permission'
import './utils/init'Vue.config.productionTip = falsenew Vue({router,store,render: h => h(App)
}).$mount('#app')

修改LoginView.vue代码,添加实际登录注册请求逻辑

<script>
import { mapMutations } from "vuex";
import api from "../api"
export default {......methods:{...mapMutations("login",["setUser"]),submitForm( formName ){this.$refs[formName].validate((valid) =>{if(valid){if(this.activeTab === "login"){// 提交登录表单逻辑api.login(this.loginForm).then(res =>{// console.log(res.data);if(res.data.status === 200){this.setUser(res.data);localStorage.setItem("ego",JSON.stringify(res.data));this.$router.push('/')}else{const h = this.$createElement;this.$notify({title:"登录失败",message:h("i","用户名密码错误")})}})}else if(this.activeTab === "register"){// 提交注册表单逻辑// console.log(this.registerForm);api.register(this.registerForm).then(res =>{if(res.data.status === 200){const h = this.$createElement;this.$notify({title:"注册成功",message:h("i","请前往登录页面登录")});}else{const h = this.$createElement;this.$notify({title:"注册失败",message:h("i","请重新注册")})}})}else{return;}}})},handleTabClick(tab){this.activeTab = tab.name;}}
}
</script>

运行效果

在这里插入图片描述

登录后,会自动跳转至首页且本地缓存中增加token

在这里插入图片描述

注册

在这里插入图片描述

在这里插入图片描述

暂未实现重复用户验证

6.导航栏实现

components目录下新建HeaderNav.vue,编写导航栏组件

<template><el-menu :default-active="active" mode="horizontal"background-color="#545c64"text-color="#fff"active-text-color="#ffd04b"router ><el-menu-item index="/">首页</el-menu-item><el-menu-item index="/product">商品管理</el-menu-item><el-menu-item index="/params">规格参数</el-menu-item><el-menu-item index="/ad">广告管理</el-menu-item><el-menu-item class="user"><span class="user-name">{{ user.username }}</span><el-button @click="logoutHandle">退出</el-button></el-menu-item></el-menu>
</template><script>
import { mapState,mapMutations } from 'vuex'
export default {data(){return {active:"/"}},computed:{...mapState("login",["user"])},methods:{...mapMutations("login",["setUser"]),logoutHandle(){this.setUser({})localStorage.removeItem('ego')this.$router.push('/login')}}
}
</script><style lang="less" scoped>
.user{float: right !important;margin-right: 20px !important;line-height: 60px !important;.user-name{color:#fff;margin-right: 10px;font-size: 15px;border: 1px solid #fff;border-radius: 50%;width:40px;height: 40px;display: inline-block;line-height: 40px;overflow: hidden;}
}
.set-lang{float: right !important;
}
.el-dropdown-link{color:#fff;
}</style>

修改LayoutView.vue文件,引入使用导航栏

<template><div><header-nav></header-nav><router-view></router-view></div>
</template><script>
import HeaderNav from "@/components/HeaderNav.vue"
export default {components:{HeaderNav}
}
</script><style>
</style>

运行效果
在这里插入图片描述

*.公共样式导入

在src/assets目录下新建css目录,编写common.css文件

body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,label
{margin: 0;padding: 0;
}
body{text-align: center;background: #f1f1f1;}
li{list-style: none;}
a{text-decoration: none;}
input,button,img{border: none;}
.active{color: #409eff;}

修改main.js文件,导入使用公共样式

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import './plugins/element.js'
import './router/permission'
import './utils/init'
import './assets/css/common.css'
Vue.config.productionTip = falsenew Vue({router,store,render: h => h(App)
}).$mount('#app')

7.首页页面实现

在src/view/main目录下,新建HomePage目录,用于存放所有首页视图页面,将HomeView.vue移动至该目录下

修改src/router目录下的index.js文件,更新HomeView的路径

import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '../views/main/HomePage/HomeView.vue'
import Layout from '../views/LayoutView.vue'
......

7.1.左导航栏

在HomePage目录下新建HomeNavView.vue文件,编写左导航栏组件

<template><div class="index-warp"><div class="index-left"><div class="index-left-block"><h2>全部产品</h2><div v-for="(product,index) in productList" :key="index"><h3>{{ product.title }}</h3><ul><li v-for="(item,index) in product.list" :key="index"><a :href="item.url">{{ item.name }}</a><span class="hot-tag" v-if="item.hot">HOT</span></li></ul></div></div></div></div>
</template><script>
export default {data(){return{productList:[{title:"手机应用类",list:[{id:1,name:"baidu",url:"www.baidu.com",hot:true},{id:2,name:"baidu2",url:"www.baidu.com",hot:false},{id:3,name:"baidu3",url:"www.baidu.com",hot:false}]},{title:"PC产品类",list:[{id:1,name:"华为",url:"www.baidu.com",hot:true},{id:2,name:"mac",url:"www.baidu.com",hot:false},{id:3,name:"微软",url:"www.baidu.com",hot:false}]}]}}
}
</script><style scoped lang="less">.index-wrap{width: 1200px;margin:0 auto;overflow: hidden;
}.index-left{float: left;width: 300px;text-align: left;
}
.index-right{float:left;width:900px;
}
.index-left-block{margin: 15px;background: #fff;box-shadow: 0 0 1px #ddd;
}.index-left-block .hr{margin-bottom: 20px;height: 1px;width: 100%;background: #ddd;
}
.index-left-block h2{background: #4fc08d;color: #fff;padding: 10px 15px;margin-bottom: 20px;
}
.index-left-block h3{padding: 0 15px 5px 15px;font-weight: bold;color: #222;
}.index-left-block ul{padding: 10px 15px;
}.index-left-block li{padding:5px;a{color:#222;}
}
.index-board-list{overflow:hidden;margin-top: 15px;
}
.index-board-item{float: left;width: 400px;background: #fff;box-shadow: 0 0 1px #ddd;padding: 20px;margin-right: 20px;margin-bottom: 20px;
}.index-board-item-inner{min-height: 125px;padding-left: 120px;
}
/**
.index-board-openproduct .index-board-item-inner{background: url(../assets/images/1.png) no-repeat;
}
.index-board-logo .index-board-item-inner{background: url(../assets/images/2.jpeg) no-repeat;
}
.index-board-golife .index-board-item-inner{background: url(../assets/images/1.png) no-repeat;
}
.index-board-heigh .index-board-item-inner{background: url(../assets/images/2.jpeg) no-repeat;
}*/
.index-board-item h2{font-size: 18px;font-weight: bold;color: #000;margin-bottom: 15px;
}.line-last{margin-right: 0;
}.index-board-button{margin-top: 20px;
}
.lastest-news{min-height: 350px;
}.hot-tag{background: red;color: #fff;
}
.new-item{display: inline-block;width: 230px;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;
}
.swiperimg{width: 100%;height: 350px;
}
.swiper-size{margin-block: 15px;
}
.button{background: #4fc08d;color:#fff;display: inline-block;padding: 10px 20px;cursor: pointer;
}
</style>

修改HomeView.vue文件,导入使用左导航栏组件

<template><div class="home"><home-nav></home-nav></div>
</template><script>
import HomeNav from "./HomeNavView.vue"
export default {name: 'HomeView',components: {HomeNav}
}
</script>

运行效果

在这里插入图片描述

7.2.轮播图

安装轮播图插件:cnpm install swiper@5.x vue-awesome-swiper@3.1.3 --save --no-fund

注:使用新版本可以使用npm,但要下载使用旧版本,需使用cnpm

修改main.js文件,引入使用轮播插件

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import './plugins/element.js'
import './router/permission'
import './utils/init'
import './assets/css/common.css'
// 引入轮播图插件
import VueAwesomeSwiper from 'vue-awesome-swiper'
import 'swiper/css/swiper.css'
// 使用VueAwesomeSwiper
Vue.use(VueAwesomeSwiper)
Vue.config.productionTip = falsenew Vue({router,store,render: h => h(App)
}).$mount('#app')

在src/assets目录下新建slideShow目录,存放需要轮播的图片

在HomePage目录下新建HomeSwiper.vue文件,实现轮播组件

<template><div class="swiper-size"><swiper :options="swiperOption"><swiper-slide v-for="(imgSrc,index) in bannerImg" :key="index"><img class="swiperimg" :src="imgSrc" alt=""/></swiper-slide><!-- 轮播图下方轮播节点 --><div class="swiper-pagination" slot="pagination"></div><!-- 左右切换按钮 --><div class="swiper-button-prev" slot="button-prev"></div><div class="swiper-button-next" slot="button-next"></div></swiper></div>
</template><script>
export default {data(){return{swiperOption:{pagination:{el:".swiper-pagination",},autoplay:true,navigation:{nextEl:".swiper-button-next",prevEl:".swiper-button-prev"},loop:true},bannerImg:[require("@/assets/slideShow/1.png"),require("@/assets/slideShow/2.jpg"),require("@/assets/slideShow/3.jpg"),require("@/assets/slideShow/4.jpg"),]}}
}
</script><style scoped lang="less">
.swiper-size{margin-top: 15px;
}
.swiperimg{width: 100%;height: 450px;
}
</style>

修改HomeView.vue文件,引入轮播组件

<template><div class="index-wrap"><div class="index-left"><home-nav></home-nav></div><div class="index-right"><home-swiper></home-swiper></div></div>
</template><script>
import HomeNav from "./HomeNavView.vue"
import HomeSwiper from "./HomeSwiper.vue"
export default {name: 'HomeView',components: {HomeNav,HomeSwiper}
}
</script>
<style lang="less" scoped>
.index-wrap{width: 1500px;margin:0 auto;overflow: hidden;
}.index-left{float: left;width: 300px;text-align: left;
}
.index-right{float:left;width:1200px;
}
</style>

运行效果

在这里插入图片描述

7.2.1.封装组件

在components目录下,新建SwiperView.vue文件,用做通用轮播图组件

编辑SwiperView.vue文件,跟HomeSwiper.vue文件内容差不多,只是图片信息改用props接收

<template><div class="swiper-size"><swiper :options="swiperOption"><swiper-slide v-for="(imgSrc,index) in bannerSwiperImg" :key="index"><img class="swiper-img" :src="imgSrc" alt=""/></swiper-slide><!-- 轮播图下方轮播节点 --><div class="swiper-pagination" slot="pagination"></div><!-- 左右切换按钮 --><div class="swiper-button-prev" slot="button-prev"></div><div class="swiper-button-next" slot="button-next"></div></swiper></div>
</template><script>
export default {data(){return {// 轮播图选项swiperOption:{pagination:{el:".swiper-pagination"},// 是否自动轮播autoplay:true,// 切换组件navigation:{nextEl:".swiper-button-next",prevEl:".swiper-button-prev"},// 轮播进度loop:true}}},props:{bannerSwiperImg:{type:Array,default(){// 没有传入数据,则默认使用一下图片return [require("@/assets/slideShow/1.png"),]}}},}
</script><style scoped>
.swiper-size{margin-top: 15px;
}
.swiper-img{width: 100%;height: 450px;
}
</style>

修改HomeView.vue文件,改为使用通用组件

<template><div class="index-wrap"><div class="index-left"><home-nav-view /></div><div class="index-right"><swiper-view :bannerSwiperImg="bannerSwiperImg"/></div></div>
</template><script>
import SwiperView from '@/components/SwiperView.vue'
import HomeNavView from './HomeNavView.vue'export default {name: 'HomeView',data(){return {bannerSwiperImg:[require("@/assets/slideShow/2.jpg"),require("@/assets/slideShow/1.png"),require("@/assets/slideShow/4.jpg"),require("@/assets/slideShow/3.jpg"),]}},components: {HomeNavView,SwiperView},}
</script><style scoped>
.index-wrap{width: 1500px;margin:0 auto;overflow: hidden;
}.index-left{float: left;width: 300px;text-align: left;
}
.index-right{float:left;width:1200px;
}
</style>

运行效果

在这里插入图片描述

7.3.信息列表

在HomePage目录下新建HomeProductList.vue文件

<template><div class="index-board-list"><div class="index-board-item" v-for="(item,index) in buyData" :key="index":class="['index-board-' + item.url,{'line-last':index%2 !== 0}]"><div class="index-board-item-inner"><h2>{{ item.title }}</h2><p>{{ item.desc }}</p><div class="index-board-button"><router-link  to="/details" class="button">立即购买</router-link></div></div></div></div>
</template><script>
export default {data(){return{buyData:[{title:"开放产品",desc:"开放产品描述",url:"openproduct"},{title:"品牌营销",desc:"品牌营销描述",url:"logo"},{title:"电子产品",desc:"电子产品描述",url:"golife"},{title:"酒水产品",desc:"酒水产品描述",url:"heigh"},]}}
}
</script><style scoped lang="less">
.index-board-list{overflow:hidden;margin-top: 15px;
}
.index-board-item{float: left;width: 550px;background: #fff;box-shadow: 0 0 1px #ddd;padding: 20px;margin-right: 20px;margin-bottom: 20px;
}.index-board-item-inner{min-height: 125px;padding-left: 120px;
}.index-board-openproduct .index-board-item-inner{background: url(@/assets/images/1.jpg) no-repeat;background-size: 120px 120px;
}
.index-board-logo .index-board-item-inner{background: url(@/assets/images/2.jpeg) no-repeat;background-size: 120px 120px;
}
.index-board-golife .index-board-item-inner{background: url(@/assets/images/3.jpg) no-repeat;background-size: 120px 120px;
}
.index-board-heigh .index-board-item-inner{background: url(@/assets/images/4.jpg) no-repeat;background-size: 120px 120px;
}.index-board-item h2{font-size: 18px;font-weight: bold;color: #000;margin-bottom: 15px;
}.line-last{margin-right: 0;
}.index-board-button{margin-top: 20px;
}
.lastest-news{min-height: 350px;
}
.new-item{display: inline-block;width: 230px;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;
}
.button{background: #4fc08d;color:#fff;display: inline-block;padding: 10px 20px;cursor: pointer;
}
</style>

百度找几张图标,放在src/assets/images目录下

修改HomeView.vue文件,添加使用HomeProductList组件

<template><div class="index-wrap"><div class="index-left"><home-nav></home-nav></div><div class="index-right"><home-swiper></home-swiper><home-product-list></home-product-list></div></div>
</template><script>
import HomeNav from "./HomeNavView.vue"
import HomeSwiper from "./HomeSwiper.vue"
import HomeProductList from "./HomeProductList.vue"
export default {name: 'HomeView',components: {HomeNav,HomeSwiper,HomeProductList}
}
</script>
<style lang="less" scoped>
.index-wrap{width: 1500px;margin:0 auto;overflow: hidden;
}.index-left{float: left;width: 300px;text-align: left;
}
.index-right{float:left;width:1200px;
}
</style>

运行效果

在这里插入图片描述

7.4.详情页实现

在src/views/main/HomePage目录下新建sub目录,用于存放子页面文件

新建DetailsView.vue,编写详情页信息

<template><div class="detail-wrap"><div class="detail-left"><div class="product-board"><img src="@/assets/images/1.jpg" alt=""><ul><router-link tag="li" active-class="active"v-for="(item,index) in detailsNav" :key="index" :to="'/details/' + item.id">{{ item.title }}</router-link></ul></div></div><div class="detail-right"><router-view></router-view></div></div>
</template><script>
export default {data(){return {detailsNav:[{title:"开放产品",id:"openproduct"},{title:"品牌营销",id:"logo"},{title:"电子产品",id:"golife"},{title:"酒水产品",id:"heigh"},]}}
}
</script><style >
.detail-wrap{width: 1200px;margin: 0 auto;overflow: hidden;padding-top: 20px;
}
.detail-left {float: left;width: 200px;text-align: center;
}
.detail-right{float: left;width: 980px;margin-left: 20px;
}
.product-board{background: #fff;padding: 20px 0;
}
.product-board img{width: 120px;height: 120px;
}
.product-board ul{margin-top: 20px;
}
.product-board li{text-align: left;padding: 10px 15px;cursor: pointer;
}
.product-board li.active,
.product-board li:hover{background: #4fc08d;color:#fff;
}
.product-board li a{display: block;
}
/* 下方为右边区域子页面样式 */
.sales-board{background: #fff;
}
.sales-board-intro h2{font-size: 20px;padding: 20px;
}
.sales-board-intro p{background: #f7fcff;padding: 10px 20px;color: #999;line-height: 1.8;
}
.sales-board-form{padding: 10px 20px;font-size: 14px;
}
.sales-board-line{clear: both;padding-bottom: 20px
}
.sales-board-line-left{display: inline-block;margin-right: 10px;
}
.sales-board-line-right {display: inline-block;width: 25%;
}
.sales-board-des {border-top: 20px solid #fff;background: #fff;padding: 15px 20px;
}
.sales-board-des p {line-height: 1.6;
}
.sales-board-des h2{font-size: 20px;padding-bottom: 15px;
}
.sales-board-des h3{font-size: 18px;font-weight: bold;padding: 20px 0 10px 0;
}
.sales-board-des li{padding: 5px 0;
}
.sales-board-table{width:100%;margin-top: 20px;
}
.sales-board-table th{border: 1px solid #4fc08d;color:#fff;
}
.sales-board-table td{border: 1px solid #f0f2f5;padding:15px;
}
</style>

新建Openproduct.vueGolife.vueHeigh.vueLogo.vue四个模块文件

编辑Openproduct.vue

<template>
<div class="open"><div class="sales-board"><div class="sales-board-intro"><h2>开放产品</h2><p>中国和拉美虽然相距遥远,但共同的梦想和追求将双方紧紧联系在一起。<br>在遥远的南美国家哥伦比亚,一场与中国的 “地铁之约” 正在精彩上演。<br>两年来,哥伦比亚首都波哥大地铁一号线项目建设热火朝天,50 名当地青年学员更是不远万里,先后来到中国西安进行专业培训。<br>这不仅仅是一次学习之旅,更是中拉基建合作的生动写照。<br>中国与哥伦比亚携手,共同为城市交通注入新活力。从规划到建设,每一个环节都凝聚着双方的智慧和努力。</p><div class="sales-board-form"><div class="sales-board-line-left">购买数量:</div><div class="sales-board-line-right"><!-- 购买数量组件 --><Counter :counterObj="counterObj"></Counter></div></div><div class="sales-board-form"><div class="sales-board-line-left">产品类型:</div><!-- 产品类型选择组件 --><Types :selecterData="selecterData"/></div><div class="sales-board-form"><div class="sales-board-line-left">有效时间:</div><!-- 有效时间选择组件 --><Timer :timerData="timerData"/></div></div><div class="sales-board-des"><h2>产品说明</h2><p>这是产品说明...这是产品说明......这是产品说明...这是产品说明......这是产品说明...这是产品说明......</p><h2>用户行为指标</h2><ul><li>用户行为指标...用户行为指标...用户行为指标...用户行为指标...</li><li>用户行为指标1</li><li>用户行为指标2</li><li>用户行为指标3</li><li>用户行为指标4</li></ul><h3>浏览网站方式</h3><ul><li>浏览网站方式1</li><li>浏览网站方式2</li><li>浏览网站方式3</li><li>浏览网站方式4</li><li>浏览网站方式5</li></ul></div></div>
</div>
</template><script>
import Counter from '@/components/HomePage/counter'
import Types from '@/components/HomePage/types'
import Timer from '@/components/HomePage/timer'
export default {data(){return {counterObj:{min:1,max:20},selecterData:[{value:"经典型",id:1},{value:"加强型",id:2},{value:"至尊豪华型",id:3},],timerData:[{value:'一个月',id:1},{value:'三个月',id:3},{value:'六个月',id:6},{value:'一年',id:12},]}},components:{Counter,Types,Timer}
}
</script><style scoped>
.open{text-align: left;
}
.buy-dialog-title{font-size: 16px;font-weight: bold;
}
.buy-dialog-btn{margin-top: 20px;
}
.buy-dialog-table{width:100%;margin-bottom: 20px;
}
.buy-dialog-table td,
.buy-dialog-table th{border: 1px solid #e3e3e3;text-align: center;padding: 5px 0;
}
.buy-dialog-table th{background: #4fc08d;color: #fff;border: 1px solid #4fc08d;
}
.button{background: #4fc08d;color: #fff;display: inline-block;padding: 10px 20px;cursor: pointer;
}
</style>

修改router/index.js文件,增加详情页路由

import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '../views/main/HomePage/HomeView.vue'
import Layout from '../views/LayoutView.vue'Vue.use(VueRouter)const routes = [{path: '/',name: 'Layout',component: Layout,children:[{path: '',name: 'home',component: HomeView,meta:{isLogin:true}},{path:"product",name:"Product",component:() => import("../views/main/ProductPage"),meta:{isLogin:true}},{path:"params",name:"Params",component:() => import("../views/main/ParamsView.vue"),meta:{isLogin:true}},{path:"ad",name:"ADCategory",component:() => import("../views/main/ADCategory.vue"),meta:{isLogin:true}},{path:"details",name:"Details",component:() => import("../views/main/HomePage/sub/DetailsView.vue"),children:[{path:"openproduct",name:"OpenProduct",component:() => import("../views/main/HomePage/sub/Openproduct.vue")},{path:"golife",name:"Golife",component:() => import("../views/main/HomePage/sub/Golife.vue")},{path:"logo",name:"Logo",component:() => import("../views/main/HomePage/sub/Logo.vue")},{path:"heigh",name:"Heigh",component:() => import("../views/main/HomePage/sub/Heigh.vue")},],meta:{isLogin:true}}]},{path:'/login',name:'Login',component:() => import("../views/LoginView.vue")}
]const router = new VueRouter({routes,// 去除路径上的#号mode:"history"
})export default router

修改HomeProductList.vue文件,增加具体跳转路由信息

<template><div class="index-board-list"><div class="index-board-item" v-for="(item,index) in buyData" :key="index":class="['index-board-' + item.url,{'line-last':index%2 !== 0}]"><div class="index-board-item-inner"><h2>{{ item.title }}</h2><p>{{ item.desc }}</p><div class="index-board-button"><!-- 拼接具体跳转位置 --><router-link  :to="'/details/' + item.url" class="button">立即购买</router-link></div></div></div></div>
</template>
7.3.1.购买数量组件

在src/components目录下新建HomePage目录,新建counter.vue文件实现购买数量组件

<template><div class="counter-component"><div  class="counter-btn" @click="minHandle">-</div><div class="counter-show"><input type="text" v-model="counter" @keyup="innerHeight"></div><div  class="counter-btn" @click="addHandle">+</div></div>
</template><script>
export default {data(){return {counter : 1}},props:{counterObj:{type:Object,default(){return{ min:1,max:1}}}},methods:{minHandle(){if(this.counter <= this.counterObj.min){return;}this.counter--;},addHandle(){if(this.counter >= this.counterObj.max){return;}this.counter++;},innerHeight(){var fix;if(typeof this.counter === "string"){fix = Number(this.counter.replace(/\D/g,""));}else{// 如果用户输入的是字符串,则将最小值赋值给fixfix = this.counterObj.min;}if(fix <= this.counterObj.min){fix = this.counterObj.min;}if(fix > this.counterObj.max){fix = this.counterObj.max;}this.counter = fix;}}
}
</script><style scoped>
.counter-component{position: relative;display: inline-block;overflow: hidden;vertical-align: middle;
}
.counter-show{float: left;width: 50px;
}
.counter-show input{width: 50px;border: none;text-align: center;border-top: 1px solid #e3e3e3;border-bottom: 1px solid #e3e3e3;height: 23px;line-height: 23px;
}
.counter-btn{border: 1px solid #e3e3e3;float: left;height: 25px;line-height: 25px;width: 25px;text-align: center;cursor: pointer;
}
.counter-btn:hover{border-color: #4fc08d;background: #4fc08d;color: #fff;
}
</style>
7.4.2.产品类型选择组件

在src/components/HomePage目录下,新建types.vue文件实现产品类型选择组件

<template><div class="selection-component"><div class="selection-show" @click="showListHendle"> <span>{{ selecterData[currentIndex].value }}</span><div class="arrow"></div></div><div class="selection-list" v-show="isOpen"><ul><li v-for="(item,index) in selecterData" :key="index"@click="selectHandle(index)">{{ item.value }}</li></ul></div></div>
</template><script>
export default {data(){return{isOpen:false,currentIndex:0,}},props:{selecterData:{type:Array,default(){return [{value:"test",id:1}];}}},methods:{showListHendle(){this.isOpen = !this.isOpen;},selectHandle(index){this.currentIndex = index;this.isOpen = false;}}
}
</script><style scoped>
.selection-component{position: relative;display: inline-block;
}
.selection-show{border: 1px solid #e3e3e3;padding: 0 20px 0 10px;display: inline-block;position: relative;cursor: pointer;height: 25px;line-height: 25px;border-radius: 3px;background: #fff;
}
.selection-show .arrow{display: inline-block;border-left: 4px solid transparent;border-right: 4px solid transparent;border-top: 5px solid transparent;width: 0;height: 0;margin-top: -1px;margin-left: 6px;margin-right: -14px;vertical-align: middle;
}
.selection-list{display: inline-block;position: absolute;left: 0;top: 25px;width: 100%;background: #fff;border-top: 1px solid #e3e3e3;border-bottom: 1px solid #e3e3e3;z-index: 5;
}
.selection-list li{padding: 5px 15px 5px 10px;border-left: 1px solid #e3e3e3;border-right: 1px solid #e3e3e3;cursor: pointer;background: #fff;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;
}
.selection-list li:hover{background: #e3e3e3;
}
</style>
7.4.3.有效时间选择组件

在src/components/HomePage目录下,新建timer.vue文件实现产品类型选择组件

<template><div class="chooser-component"><ul class="chooser-list"><li @click="timerHandle(index)" :class="{active:nowIndex === index}" v-for="(item,index) in timerData" :key="index">{{ item.value }}</li></ul></div>
</template><script>
export default {data(){return {nowIndex:0,}},props:{timerData:{type:Array,default(){return [{value:"test",id:1}];}}},methods:{timerHandle(index){this.nowIndex = index}}
}
</script><style scoped>
.chooser-component{position: relative;display: inline-block;
}
.chooser-list li{display: inline-block;border:1px solid #e3e3e3;height: 25px;line-height: 25px;padding: 0 8px;margin-right: 5px;border-radius: 3px;text-align: center;cursor: pointer;
}
.chooser-list li.active{border-color: #4fc08d;background: #4fc08d;color: #fff;
}
</style>

另外三个模块暂时忽略

在这里插入图片描述

运行效果

在这里插入图片描述

8.商品管理页实现

src/views/main目录下新建ProductPage目录,用于存放商品管理页面的相关组件

ProductPage目录下,新建index.vue文件,作为商品管理页主入口

<template><div class="product">商品管理</div>
</template><script>
export default {
}
</script><style>
.product{width: 1200px;margin: 0 auto;margin-top: 20px;
}
</style>

修改product的路由配置

	{path:'product',name:'Product',component:() => import('../views/main/ProductPage'),meta:{isLogin:true}},

8.1.服务器数据接口

修改server/router.js文件,增加商品管理相关操作接口

const url = require("url")
const fs = require("fs")
const multer = require("multer")/*** 商品查询*/
router.get("/backend/item/selectTbItemAllByPage",(req,res)=>{// 分页const page = url.parse(req.url,true).query.page || 1;const sql = "select * from project order by id desc limit 10 offset " + (page - 1) * 10;sqlClient(sql,null,result=>{if(result.length > 0){res.send({status:200,result})}else{res.send({status:401,msg:"暂无数据"})}})
})
/*** 商品总数*/
router.get("/total",(req,res)=>{const sql = "SELECT COUNT(*) AS count FROM project where id";sqlClient(sql,null,result =>{if(result.length > 0){res.send({status:200,result})}else{res.send({status:500,msg:"暂无更多数据"})}})
})/*** 模糊查询*/
router.get("/search",(req,res)=>{const search = url.parse(req.url,true).query.search;const sql = "SELECT * FROM project WHERE concat(`title`,`sellPoint`,`descs`) LIKE '%" + search + "%'";sqlClient(sql,null,result =>{if(result.length > 0){res.send({ status:200,result})}else{res.send({status:500,msg:"暂无数据"})}})
})/*** 类目选择*/
router.get("/backend/itemCategory/selectItemCategoryByParentId",(req,res)=>{const id = url.parse(req.url,true).query.id || 1;const sql = "SELECT cid,pid,name FROM category WHERE pid = ?";const arr = [id]sqlClient(sql,arr,result =>{if(result.length > 0){res.send({status:200,result})}else{res.send({status:500,msg:"暂无数据"})}})
})/*** 上传图片*/
var storage = multer.diskStorage({destination:function(req,file,cb){cb(null,'./upload/')},filename:function(req,file,cb){cb(null,Date.now() + "-" + file.originalname)}
})var createFolder = function (folder) {try {fs.accessSync(folder)} catch (e) {fs.mkdirSync(folder)}
}var uploadFolder = './upload/';
createFolder(uploadFolder);
var upload = multer({storage : storage});router.post('/upload',upload.single('file'),function(req,res,next){var file = req.file;console.log('文件类型:%s',file.mimetype);console.log('原文件名:%s',Buffer.from(file.originalname, "latin1").toString("utf8"));console.log('文件大小:%s',file.size);console.log('文件保存路径:%s',Buffer.from(file.path, "latin1").toString("utf8"));res.json({res_code:'0',name:Buffer.from(file.originalname, "latin1").toString("utf8"),url:Buffer.from(file.path, "latin1").toString("utf8")});
})/*** 添加商品*/
router.get("/backend/item/insertTbItem",(req,res)=>{const cid = url.parse(req.url,true).query.cid || "";const title = url.parse(req.url,true).query.title || "";const sellPoint = url.parse(req.url,true).query.sellPoint || "";const price = url.parse(req.url,true).query.price || "";const num = url.parse(req.url,true).query.num || "";const image = url.parse(req.url,true).query.image || "";const desc = url.parse(req.url,true).query.desc || "";const sql = "INSERT INTO project(`title`,`image`,`sellPoint`,`price`,`cid`,`num`,`status`,`descs`) VALUES(?,?,?,?,?,?,1,?)"const arr = [title,image,sellPoint,price,cid,num,desc];sqlClient(sql,arr,result=>{if(result.affectedRows > 0){res.send({status:200,msg:"添加成功"})}else{res.send({status:500,msg:"添加失败"})}})
})/*** 商品删除*/
router.get("/backend/item/deleteItemById",(req,res) =>{const id = url.parse(req.url,true).query.id;const sql = "DELETE FROM project WHERE id = ?";const arr = [id];sqlClient(sql,arr,result =>{if(result.affectedRows > 0){res.send({status:200,msg:"删除成功"})}else{res.send({status:500,msg:"删除失败"})}})
})/*** 预更新*/
router.get("/backend/item/preUpdateItem",(req,res) =>{const id = url.parse(req.url,true).query.id;const sql = "SELECT * FROM project WHERE id = ?";sqlClient(sql,[id],result =>{if(result.length > 0){res.send({status:200,result})}else{res.send({status:500,msg:"预更新失败"})}})
})/*** 编辑商品信息*/
router.get("/backend/item/updateTbItem",(req,res) =>{const cid = url.parse(req.url,true).query.cid || "";const title = url.parse(req.url,true).query.title || "";const sellPoint = url.parse(req.url,true).query.sellPoint || "";const price = url.parse(req.url,true).query.price || "";const num = url.parse(req.url,true).query.num || "";const image = url.parse(req.url,true).query.image || "";const desc = url.parse(req.url,true).query.desc || "";const id = url.parse(req.url,true).query.id;const sql = "UPDATE project set title=?,sellPoint=?,cid=?,price=?,descs=?,image=?,num=? WHERE id=?";const arr = [title,sellPoint,cid,price,desc,image,num,id]sqlClient(sql,arr,(result) =>{if(result.affectedRows > 0){res.send({status:200,msg:"修改成功"})}else{res.send({status:500,msg:"修改失败"})}})
})

修改server/router.js文件,增加静态文件位置

const express = require("express");
const app = express();
const cors = require("cors");
const bodyParser = require("body-parser");
const router = require("./router")app.use(cors());
app.use(bodyParser.urlencoded({extended:true
}))app.use("/api",router);
app.use(express.static("upload"))
// 监听端口号
app.listen(3000,() =>{console.log(3000)
})

src/api/base.js文件中增加对应信息

const base = {baseUrl:"http://localhost:3000",register:"/api/register",	// 注册login:"/api/login",	// 登录selectTbItemAllByPage:"/api/backend/item/selectTbItemAllByPage", // 商品列表total:"/api/total", // 商品总数search:"/api/search",     // 模糊查询selectItemCategoryByParentId:"/api/backend/itemCategory/selectItemCategoryByParentId",  // 类目选择insertTbItem:"/api/backend/item/insertTbItem",  // 商品添加deleteItemById:"/api/backend/item/deleteItemById", //商品删除preUpdateItem:"/api/backend/item/preUpdateItem",      // 预更新updateTbItem:"/api/backend/item/updateTbItem",    // 修改商品
}
export default base;

修改src/api/index.js文件,增加商品查询api

	/*** 商品列表*/selectTbItemAllByPage(params){return axios.get(base.baseUrl + base.selectTbItemAllByPage,{params})},/*** 商品数量*/total(){return axios.get(base.baseUrl + base.total)},/*** 模糊查询*/search(params){return axios.get(base.baseUrl + base.search,{params})},/*** 类目选择*/selectItemCategoryByParentId(params){return axios.get(base.baseUrl + base.selectItemCategoryByParentId,{params})},/*** 添加商品*/insertTbItem(params){return axios.get(base.baseUrl + base.insertTbItem,{params})},/*** 商品删除*/deleteItemById(params){return axios.get(base.baseUrl + base.deleteItemById,{params})},/*** 预更新*/preUpdateItem(params){return axios.get(base.baseUrl + base.preUpdateItem,{params})},/*** 修改商品*/updateTbItem(params){return axios.get(base.baseUrl + base.updateTbItem,{params})}

8.2.eventbus工具类实现

src/utils目录下新建eventbut.js文件,实现EventBus,方便组件之间的数据传递

import Vue from "vue"const EventBus = new Vue();
Object.defineProperties(Vue.prototype,{$bus:{get(){return EventBus;}}
})

修改main.js文件,【这里顺便加api的引用,方便后面调用api】

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import './plugins/element.js'
import './router/permission'
import './utils/init'
import './assets/css/common.css'
// 引入轮播图插件
import VueAwesomeSwiper from 'vue-awesome-swiper'
import 'swiper/css/swiper.css'
// 引入api
import api from "./api"
// 引入eventbus
import "./utils/eventbus"Vue.prototype.$api = api
// 使用VueAwesomeSwiper
Vue.use(VueAwesomeSwiper)
Vue.config.productionTip = falsenew Vue({router,store,render: h => h(App)
}).$mount('#app')

8.3.商品列表信息

src/views/main/ProductPage目录下,新建ProductList.vue文件,实现商品列表展示

<template><div><el-table :data="tableData"><el-table-column show-overflow-tooltip prop="id" label="产品id" width="100"></el-table-column><el-table-column show-overflow-tooltip prop="title" label="产品名称" width="150"></el-table-column><el-table-column show-overflow-tooltip prop="image" label="产品图片" width="100"></el-table-column><el-table-column show-overflow-tooltip prop="sellPoint" label="产品卖点" width="300"></el-table-column><el-table-column show-overflow-tooltip prop="price" label="产品价格" width="100"></el-table-column><el-table-column show-overflow-tooltip prop="num" label="产品数量" width="100"></el-table-column><el-table-column show-overflow-tooltip prop="descs" label="产品描述"><template v-slot="scope"><div v-html="scope.row.descs"></div></template></el-table-column><el-table-column label="操作"><template v-slot="scope"><el-button size="mini" @click="handleEdit(scope.$index, scope.row)">编辑</el-button><el-button size="mini" type="danger" @click="handleDelete(scope.$index, scope.row)">删除</el-button></template></el-table-column></el-table></div>
</template><script>
export default {data(){return {tableData:[]}},mounted(){this.http(1);// 接收分页组件传递的数据this.$bus.$on("page",page =>{this.http(page)})// 接收搜索框组件传递的数据this.$bus.$on("searchData",data=>{this.tableData = data})this.$bus.$on("refresh",flag =>{this.http(1)})},methods:{http(page){this.$api.selectTbItemAllByPage({page}).then(res =>{// 获取对应页的商品数据if(res.data.status === 200){this.tableData =res.data.result}})},// 编辑按钮handleEdit(index,row){this.$bus.$emit("onEditorEvent",row);},// 删除按钮handleDelete(index,row){this.$confirm("此操作会永久删除该数据,是否继续?","提示",{confirmButtonText:"确定",cancelButtonText:"取消",type:"warning",}).then(() =>{this.$api.deleteItemById({ id:row.id }).then(res =>{if(res.data.status === 200){this.$message({type:"success",message:"删除成功"})this.http(1);}else{this.$message({type:"error",message:"删除失败"})}})}).catch(() => {this.$message({type:"info",message:"已取消删除"})})},}
}
</script><style scoped></style>

修改index.vue文件,增加使用列表组件

<template><div class="product"><ProductList/></div></template><script>import ProductList from './ProductList.vue'export default {components:{ProductList}}</script><style>.product{width: 1200px;margin: 0 auto;margin-top: 20px;}</style>

运行效果

在这里插入图片描述

8.4.搜索框

ProductPage目录下新建ProductHeader.vue文件

<template><div class="head"><el-form ref="searchForm" :model="search" @submit.native.prevent><el-form-item><el-input v-model="search.content" @keyup.enter.native="onSubmitSearch"/></el-form-item><el-form-item><el-button type="primary" @click="onSubmitSearch">查询</el-button></el-form-item><el-form-item><el-button type="primary" @click="addFormHandle">添加</el-button></el-form-item></el-form></div>
</template><script>
export default {data(){return {search:{content:""}}},methods:{onSubmitSearch(){// 查询this.$api.search({search:this.search.content}).then(res =>{// 将查询到的数据传递给接收对象this.$bus.$emit('searchData',res.data.result)}).catch(error =>{console.log(error);})},addFormHandle(){// 打开添加商品窗口this.$bus.$emit("onAddEvent",true)}}
}
</script><style scoped lang="less">
.el-form{overflow: hidden;clear: both;.el-form-item{float: left;margin-right: 10px;.el-input{width:1030px;}}
}
.head{margin-top: 20px;width: 100%;
}
</style>

修改index.vue文件,增加使用搜索框组件

<template><div class="product"><ProductHeader/><ProductList/></div></template><script>import ProductList from './ProductList.vue'import ProductHeader from './ProductHeader.vue'export default {components:{ProductList,ProductHeader}}</script><style>.product{width: 1200px;margin: 0 auto;margin-top: 20px;}</style>

运行效果

在这里插入图片描述

8.5.分页

ProductPage目录下新建ProductPagination.vue文件

<template><!-- 分页 --><div class="pagination-container"><el-paginationlayout="prev,pager,next,jumper":current-page.sync="currentPage"@current-change="hangleCurrentChange":total="total" @size-change="handleSizechange"></el-pagination></div>
</template><script>
export default {data(){return {currentPage:1,total:0}},methods:{handleSizechange(){},hangleCurrentChange(val){this.$bus.$emit("page",val)}},mounted(){this.$api.total().then(res =>{if(res.data.status === 200){this.total = res.data.result[0]["count"]}})}
}
</script><style scoped>
.pagination-container{margin-top : 20px;
}
</style>

修改index.vue文件,增加使用分页组件

<template><div class="product"><product-header></product-header><product-list/><product-pagination/></div>
</template><script>
import ProductHeader from './ProductHeader.vue'
import ProductList from './ProductList'
import ProductPagination from './ProductPagination.vue'
export default {components:{ProductList,ProductPagination,ProductHeader}
}
</script><style>
.product{width: 1200px;margin: 0 auto;margin-top: 20px;
}
</style>

运行效果

在这里插入图片描述

8.6.添加商品窗口

ProductPage目录下新建ProductAdd.vuePriductUpload.vueProductTree.vueProductWangEditor.vue文件

编写ProductAdd.vue文件

<template><el-dialog  title="添加产品":visible.sync="diaologAddVisible"width="60%":before-close="handleClose"><el-form label-width="80px":model="addForm"ref="addForm"    ><el-form-item label="商品类目:"><span class="location tree">{{ treeData.name }}</span><el-button type="primary" class="location" @click="dialogCategoryHandle">类目选择</el-button><el-dialogwidth="50%"append-to-bodytitle="类目选择":visible.sync="dialogCategoryVisible"><product-tree @onTree="getTreeData"></product-tree><span slot="footer" class="dialog-footer"><el-button type="primary" @click="dialogCategoryClose">确认</el-button></span></el-dialog></el-form-item><el-form-item label="商品名称:"><el-input v-model="addForm.name"></el-input></el-form-item><el-form-item label="商品卖点:"><el-input v-model="addForm.sellPoint"></el-input></el-form-item><el-form-item label="商品价格:"><el-input v-model="addForm.price"></el-input></el-form-item><el-form-item label="商品数量:"><el-input v-model="addForm.num"></el-input></el-form-item><el-form-item label="商品图片:"><img class="upload-img" :src="uploadData.url" alt=""><el-button type="primary" class="location" @click="dialogUploadHandle">上传图片</el-button><el-dialogwidth="50%"append-to-bodytitle="图片上传":visible.sync="dialogUploadVisible"><priduct-upload @onUpload="getOnUpload"></priduct-upload><span slot="footer" class="dialog-footer"><el-button type="primary" @click="dialogUploadClose">确认</el-button></span></el-dialog></el-form-item><el-form-item label="商品描述:"><ProductWangEditor @onEditor="getEditor"></ProductWangEditor></el-form-item></el-form><span slot="footer" class="dialog-footer"><el-button @click="addFormClose">取消</el-button><el-button type="primary" @click="addProductHandle">确认</el-button></span></el-dialog>
</template><script>
import PriductUpload from './PriductUpload.vue';
import ProductTree from './ProductTree.vue';
import ProductWangEditor from './ProductWangEditor.vue';
export default{components:{ProductTree,PriductUpload,ProductWangEditor},data(){return {diaologAddVisible : false,dialogCategoryVisible:false,dialogUploadVisible:false,addForm:{name:"",sellPoint:"",price:"",num:"",},treeData:{},    // 类目选择    uploadData:{},  // 存储图片editorData:"",}},mounted(){this.$bus.$on("onAddEvent",flag =>{this.diaologAddVisible = flag})},methods:{handleClose(done){this.$confirm("确认关闭").then(_ =>{done();}).catch(_ =>{});},addFormClose(){this.diaologAddVisible = false},dialogCategoryHandle(){this.dialogCategoryVisible = true},dialogCategoryClose(){this.dialogCategoryVisible = false},dialogUploadHandle(){this.dialogUploadVisible = true},dialogUploadClose(){this.dialogUploadVisible = false},// 读取类目选择数据getTreeData(data){// console.log(data);this.treeData = data;},// 读取图片地址getOnUpload(data){if(data.url){data.url = data.url.replace("upload","http://localhost:3000")}this.uploadData = data;},//接收富文本数据getEditor(data){this.editorData = data;},// 添加产品addProductHandle(){this.$api.insertTbItem({cid:this.treeData.cid,title:this.addForm.name,sellPoint:this.addForm.sellPoint,price:this.addForm.price,num:this.addForm.num,desc:this.editorData,image:this.uploadData.url}).then(res =>{if(res.data.status === 200){this.diaologAddVisible = false;this.$bus.$emit("refresh",true)}}).catch(error=>{console.log(error);})}}
}
</script><style scoped>
.location{float: left;
}
.tree{margin-right: 20px;
}
.upload-img{width: 300px;float: left;margin-right: 20px;
}
</style>

修改index.vue文件,增加使用添加商品组件

<template><div class="product"><product-header></product-header><product-list/><product-pagination/><product-add></product-add></div>
</template><script>
import ProductHeader from './ProductHeader.vue'
import ProductList from './ProductList'
import ProductPagination from './ProductPagination.vue'
import ProductAdd from './ProductAdd'
export default {components:{ProductList,ProductPagination,ProductHeader,ProductAdd}
}
</script><style>
.product{width: 1200px;margin: 0 auto;margin-top: 20px;
}
</style>
8.6.1.图片上传组件

编写PriductUpload.vue文件

<template><!-- 图片上传一定给的是单独的地址,而且跨域是后台解决的 --><el-uploadclass="upload-demo"ref="upload"action="http://localhost:3000/api/upload":on-remove="handleRemove":file-list="fileList":auto-upload="false":on-success="handleSuccess"><el-button slot="trigger" size="small" type="primary">选取文件</el-button><el-button style="margin-left: 10px;" size="small" type="success" @click="submitUpload">上传到服务器</el-button><div slot="tip" class="el-upload__tip">目前仅支持单张图片上传</div></el-upload>
</template><script>
export default {data(){return {fileList:[]}},methods:{handleRemove(file, fileList) {return this.$confirm(`确定移除 ${ file.name }?`);},handleSuccess(response){// 上传成功后返回的数据responsethis.$emit("onUpload",response)},submitUpload(){this.$refs.upload.submit();}}
}
</script><style></style>
8.6.2.类目选择组件

编写ProductTree.vue文件

<template><el-tree:props="defaultProps":load="loadNode"lazy@node-click="handleNodeClick"></el-tree>
</template><script>
export default {data() {return {data: [],defaultProps: {children: 'children',label: 'name'}};},mounted(){},methods: {handleNodeClick(data) {this.$emit("onTree",data)},loadNode(node, resolve){// 第一层数据if(node.level ===0){this.$api.selectItemCategoryByParentId().then(res =>{if(res.data.status === 200){return resolve(res.data.result)}else{return resolve([])}})}// 后续展开的数据if(node.level >= 1){this.$api.selectItemCategoryByParentId({id:node.data.cid}).then(res =>{if(res.data.status === 200){return resolve(res.data.result)}else{return resolve([])}}).catch(error =>{return resolve([])})}}}
}
</script><style></style>
8.6.3.富文本编辑器组件

编写ProductWangEditor.vue文件

<template><div ref="editorWang"style="text-align: left;"></div>
</template><script>
import wangEditor from 'wangeditor'
export default {data(){return {editor:null,    // editor对象editorData:''   // 承载编辑器数据}},props:{currentEditorData:{type:String,default:""}},watch:{currentEditorData(newVal, oldVal){this.editor.txt.html(newVal);}},mounted(){this.editor = new wangEditor(this.$refs.editorWang);// 配置 onchange 回调函数,将数据同步到vue中this.editor.config.onchange = (newHtml) =>{this.editorData = newHtml;this.$emit("onEditor",this.editorData);}// 自定义菜单配置this.editor.config.menus = ['head',         // 标题'bold',         // 粗体'fontSize',     // 字号'fontName',     // 字体'italic',       // 斜体'underline',    // 下划线'strikeThrough',// 删除线'foreColor',    // 文字颜色'backColor',    // 背景颜色'link',         // 插入链接'list',         // 列表'justify',      // 对齐方式'quote',        // 引用'emoticon',     // 标签'image',        // 插入图片'table',        // 表格'code',         // 插入代码'undo',         // 撤销'redo',         // 重复];this.editor.create();   // 创建编辑器},beforeDestroy(){// 调用销毁API对当前编辑器实例进行销毁this.editor.destroy();this.editor = null;}}
</script><style></style>

运行效果

在这里插入图片描述

8.7.编辑商品窗口

ProductPage目录下新建ProductEditor.vue文件

<template><el-dialog  title="编辑产品":visible.sync="diaologEditorVisible"width="60%":before-close="handleClose"><el-form label-width="80px":model="editorForm"ref="editorForm"    ><el-form-item label="商品类目:"><span class="location tree">{{ treeData.name }}</span><el-button type="primary" class="location" @click="dialogCategoryHandle">类目选择</el-button><el-dialogwidth="50%"append-to-bodytitle="类目选择":visible.sync="dialogCategoryVisible"><product-tree @onTree="getTreeData"></product-tree><span slot="footer" class="dialog-footer"><el-button type="primary" @click="dialogCategoryClose">确认</el-button></span></el-dialog></el-form-item><el-form-item label="商品名称:"><el-input v-model="editorForm.name"></el-input></el-form-item><el-form-item label="商品卖点:"><el-input v-model="editorForm.sellPoint"></el-input></el-form-item><el-form-item label="商品价格:"><el-input v-model="editorForm.price"></el-input></el-form-item><el-form-item label="商品数量:"><el-input v-model="editorForm.num"></el-input></el-form-item><el-form-item label="商品图片:"><img class="upload-img" :src="uploadData.url" alt=""><el-button type="primary" class="location" @click="dialogUploadHandle">上传图片</el-button><el-dialogwidth="50%"append-to-bodytitle="图片上传":visible.sync="dialogUploadVisible"><priduct-upload @onUpload="getOnUpload"></priduct-upload><span slot="footer" class="dialog-footer"><el-button type="primary" @click="dialogUploadClose">确认</el-button></span></el-dialog></el-form-item><el-form-item label="商品描述:"><ProductWangEditor @onEditor="getEditor" :currentEditorData="editorData"></ProductWangEditor></el-form-item></el-form><span slot="footer" class="dialog-footer"><el-button @click="editorFormClose">取消</el-button><el-button type="primary" @click="editorProductHandle">确认</el-button></span></el-dialog>
</template><script>
import PriductUpload from './PriductUpload.vue';
import ProductTree from './ProductTree.vue';
import ProductWangEditor from './ProductWangEditor.vue';
export default{components:{ProductTree,PriductUpload,ProductWangEditor},data(){return {diaologEditorVisible : false,dialogCategoryVisible:false,dialogUploadVisible:false,editorForm:{name:"",sellPoint:"",price:"",num:"",},treeData:{cid:"",name:""},    // 类目选择    uploadData:{url:""},  // 存储图片editorData:"",currentData:{}}},mounted(){this.$bus.$on("onEditorEvent",row =>{this.diaologEditorVisible = true;this.currentData = row;// 获取更新数据this.$api.preUpdateItem({id:row.id}).then((res)=>{// 赋值this.treeData.name = res.data.result[0].cid;this.treeData.cid = res.data.result[0].cid;this.editorForm.name = res.data.result[0].titlethis.editorForm.sellPoint = res.data.result[0].sellPointthis.editorForm.price = res.data.result[0].pricethis.editorForm.num = res.data.result[0].numthis.uploadData.url = res.data.result[0].imagethis.editorData = res.data.result[0].descs})})},methods:{handleClose(done){this.$confirm("确认关闭").then(_ =>{done();}).catch(_ =>{});},editorFormClose(){this.diaologEditorVisible = false},dialogCategoryHandle(){this.dialogCategoryVisible = true},dialogCategoryClose(){this.dialogCategoryVisible = false},dialogUploadHandle(){this.dialogUploadVisible = true},dialogUploadClose(){this.dialogUploadVisible = false},// 读取类目选择数据getTreeData(data){// console.log(data);this.treeData = data;},// 读取图片地址getOnUpload(data){if(data.url){data.url = data.url.replace("upload","http://localhost:3000")}this.uploadData = data;},//接收富文本数据getEditor(data){this.editorData = data;},// 编辑产品editorProductHandle(){this.$api.updateTbItem({id:this.currentData.id,cid:this.treeData.cid,title:this.editorForm.name,sellPoint:this.editorForm.sellPoint,price:this.editorForm.price,num:this.editorForm.num,desc:this.editorData,image:this.uploadData.url}).then(res =>{if(res.data.status === 200){this.diaologEditorVisible = false;this.$bus.$emit("refresh",true)}}).catch(error=>{console.log(error);})}}
}
</script><style scoped>
.location{float: left;
}
.tree{margin-right: 20px;
}
.upload-img{width: 300px;float: left;margin-right: 20px;
}
</style>

修改index.vue文件,增加使用修改商品组件

<template><div class="product"><product-header></product-header><product-list/><product-pagination/><product-add></product-add><ProductEditor/></div>
</template><script>
import ProductHeader from './ProductHeader.vue'
import ProductList from './ProductList'
import ProductPagination from './ProductPagination.vue'
import ProductAdd from './ProductAdd'
import ProductEditor from './ProductEditor.vue'
export default {components:{ProductList,ProductPagination,ProductHeader,ProductAdd,ProductEditor}
}
</script><style>
.product{width: 1200px;margin: 0 auto;margin-top: 20px;
}
</style>

运行效果

在这里插入图片描述

报错处理

1.找不到element模块

报错截图

在这里插入图片描述

处理方法

修改element.js文件,注释掉lang和locale的引用

import Vue from 'vue'
import element from 'element-ui'
//导入组件相关样式
import 'element-ui/lib/theme-chalk/index.css'
// import lang from 'element-ui/lib/locale/lang/'
// import locale from 'element-ui/lib/locale'// locale.use(lang)Vue.use(element)

2.找不到less-loader模块

报错截图

在这里插入图片描述

处理方法

使用npm install less-loader less --save-dev安装模块

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python简单算法

冒泡 def boll(lis):i 0while i<len(lis)-1:j 0while j<len(lis)-1-i:if lis[j] > lis[j1]:lis[j],lis[j 1] lis[j1],lis[j]j1i1选择排序 def selct1(lit):i 0while i<len(lit)-1:j i1min1 iwhile j < len(lit):if lit[j] < lit[min1]:min1 jj 1li…

2024年第15届蓝桥杯C/C++组蓝桥杯JAVA实现

目录 第一题握手&#xff0c;这个直接从49累加到7即可&#xff0c;没啥难度&#xff0c;后面7个不握手就好了&#xff0c;没啥讲的&#xff0c;(然后第二个题填空好难&#xff0c;嘻嘻不会&#xff09; 第三题.好数​编辑 第四题0R格式 宝石组合 数字接龙 最后一题:拔河 第…

【Docker】常用命令汇总

Docker 是1个开源的应用容器引擎&#xff0c;基于Go 语言并遵从 Apache2.0 协议开源。 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上&#xff0c;也可以实现虚拟化。 容器是完全使用沙箱机制&#xff0c;相…

如何选择最适合企业的ETL解决方案?

在今天的大数据时代&#xff0c;企业的数据管理和处理变得愈发重要。企业也越来越依赖于数据仓库和数据湖来提取、转换和加载&#xff08;ETL&#xff09;关键业务信息。一个高效、灵活的ETL解决方案不仅能提升数据处理能力&#xff0c;还能为企业决策提供有力支持。然而&#…

EG3D: Efficient Geometry-aware 3D Generative Adversarial Networks 学习笔记

1 Contributions 混合显式-隐式网络架构&#xff1a;提出了一种 Tri-plane 的3D表征方法&#xff0c;结合显式体素网格与隐式解码器的优点 速度快&#xff0c;内存效率高&#xff1b; 支持高分辨率生成&#xff0c;保持3D表征的灵活性和表达能力。与纯显式或隐式方法相比&#…

第十六届蓝桥杯模拟赛(第一期)-Python

本次模拟赛我认为涉及到的知识点&#xff1a; 分解质因数 Python的datetime库 位运算 简单dp 1、填空题 【问题描述】 如果一个数 p 是个质数&#xff0c;同时又是整数 a 的约数&#xff0c;则 p 称为 a 的一个质因数。 请问 2024 有多少个质因数。 【答案提交】 这是一道结…

ubuntu 安装 docker 记录

本文假设系统为 Ubuntu&#xff0c;从 16.04 到 24.04&#xff0c;且通过 APT 命令安装。理论上也其他 Debian 系的操作系统。 WSL 也一样。 感觉 Docker 官方在强推 Docker Desktop&#xff0c;搜索 Docker 安装文档&#xff0c;一不小心就被导航到了 Docker Desktop 的安装页…

太速科技-512-基于ZU19EG的4路100G 8路40G的光纤汇流计算卡

基于ZU19EG的4路100G 8路40G的光纤汇流计算卡 一、板卡概述 本板卡系我司自主设计研发&#xff0c;基于Xilinx公司Zynq UltraScale MPSOC系列SOC XCZU19EG-FFVC1760架构&#xff0c;ARM端搭载一组64-bit DDR4&#xff0c;总容量达4GB&#xff0c;可稳定运行在2400MT/s…

C#基础56-60

56.字符数组x中存有任意一串字符&#xff1b;串中的所有小写字母改写成大写字母&#xff0c;如果是大写字母改为小写字母&#xff0c;其他字符不变。最后把已处理的字符串仍重新存入字符数组x中&#xff0c;最后调用函数把结果输出到控制台中。 57.求出100以上1000以内所有个位…

计算机网络:应用层知识点概述及习题

网课资源&#xff1a; 湖科大教书匠 1、概述 习题1 1 在计算机网络体系结构中&#xff0c;应用层的主要功能是 A. 实现进程之间基于网络的通信 B. 通过进程之间的交互来实现特定网络应用 C. 实现分组在多个网络上传输 D. 透明传输比特流 2 以下不属于TCP/IP体系结构应用层范畴…

深入浅出摸透AIGC文生图产品SD(Stable Diffusion)

hihi,朋友们,时隔半年(24年11月),终于能腾出时间唠一唠SD了🤣,真怕再不唠一唠,就轮不到SD了,技术更新换代是在是太快! 朋友们,最近(24年2月)是真的没时间整理笔记,每天都在疯狂的学习Stable Diffusion和WebUI & ComfyUI,工作实在有点忙,实践期间在飞书上…

uniapp+vue2+uview2.0导航栏组件二次封装

样式 代码 <template><view class"navBar"><u-navbar :title"title" :titleColor"titleColor" :bgColor"bgColor" :safeAreaInsetTop"safeAreaInsetTop":autoBack"true" leftClick"leftClic…

【Git】常用命令汇总

目录 一.安装及配置 1.在 Windows 上安装 2.用户信息 3.差异分析工具 二.基础 1.创建仓库 2.提交与修改 三.分支管理 1.创建分支 2.合并分支 四.远程操作 1.管理 Git 仓库中的远程仓库 2.数据的获取与推送 五.标签 1.创建轻量标签和附注标签 2.查看标签和标签信…

PIMPL模式和D指针

目录 一、PIMPL模式概念&#xff1a; 1.1 pImpl手法的优势和目的 1.1.1屏蔽实现细节 1.2 .2加速编译 1.2.3 更好的二进制兼容性 1.2.4 惰性分配 二、PIMPL模式项目中应用&#xff1a; 2.1 项目背景 2.2 对外接口代码&#xff1a; 2.2.1 对外库接口头文件 2.2.2 对外…

Android显示系统(01)- 架构分析

一、前言&#xff1a; Android是基于Linux的&#xff0c;而显示设备的驱动也都是和Linux普通设备一样去管理&#xff0c;也就是说归根结底还是要按照Linux的方式组织数据送给LCD&#xff0c;因此&#xff0c;我们理解Android设计的这一套复杂的显示系统时候&#xff0c;一定要…

15分钟做完一个小程序,腾讯这个工具有点东西

我记得很久之前&#xff0c;我们都在讲什么低代码/无代码平台&#xff0c;这个概念很久了&#xff0c;但是&#xff0c;一直没有很好的落地&#xff0c;整体的效果也不算好。 自从去年 ChatGPT 这类大模型大火以来&#xff0c;各大科技公司也都推出了很多 AI 代码助手&#xff…

跨标签通信的几种方式

以前面试被问到过&#xff0c;就了解了一下。还有其他方式&#xff0c;但是实际开发中&#xff0c;使用第一个就可以了 目录 1. 使用BroadcastChannel 2. 使用SharedWorker 3. 使用webSocket 1. 使用BroadcastChannel 它允许同源&#xff08;协议、域名、端口都相同&#x…

深度神经网络模型压缩学习笔记二:离线量化算法和工具、实现原理和细节

文章目录 一、离线量化基础概念1&#xff09;离线量化定义2&#xff09;离线量化优缺点3&#xff09;如何生产一个硬件能跑的量化模型&#xff1f;4&#xff09;离线量化的类型5&#xff09;如何计算scale&#xff0c;zero_point?6&#xff09;离线量化概念7&#xff09;PTQ与…

HTML详解(1)

1.HTML定义 HTML&#xff1a;超文本标记语言。超文本&#xff1a;通过链接可以把多个网页链接到一起标记&#xff1a;标签&#xff0c;带括号的文本后缀&#xff1a;.html 标签语法&#xff1a;<strong>需加粗文字</strong> 成对出现&#xff0c;中间包裹内容&l…

【21-30期】Java技术深度剖析:从分库分表到微服务的核心问题解析

&#x1f680; 作者 &#xff1a;“码上有前” &#x1f680; 文章简介 &#xff1a;Java &#x1f680; 欢迎小伙伴们 点赞&#x1f44d;、收藏⭐、留言&#x1f4ac; 文章题目&#xff1a;Java技术深度剖析&#xff1a;从分库分表到微服务的核心问题解析 摘要&#xff1a; 本…