前端代码在git地址: https://github.com/178600803/-.git
如果需要后端服务请私信我,或者留言
项目总体实现的就是对数据库的增删改查,以及对前台模块的管理
截图:
1.打开后端
1.umall-api 安装依赖包
cnpm i
2.navicat for mysql 创建一个usmall数据库 ,右键--》运行sql文件--》选择 无数据.sql
3.umall-api/config/global.js
exports.dbConfig = {
host: 'localhost', //数据库地址
user: 'root',//数据库用户名
password: '123',//数据库用户密码
port: 3306,
database: 'usmall' // 数据库名字
}
4.启动后端 ,最后启动在localhost:3000
npm start
2.前端工作
1.创建项目
vue init webpack umall_admin
2.清空工作
1.assets 删完了
2.components删完了
3.router/index.js 删除了helloword相关的
4.app.vue重置
3.配置目录
-src
-assets 静态资源
-css
-js
-components 公共组件
-filters 过滤器
-pages 路由组件
-menu
menu.vue
-components 路由组件的子组件
-router 路由
-store 仓库
-util 工具类
App.vue
main.js
4.项目搭建
1.reset.css
1.assets/css/reset.css
2.main.js引入
//1.reset.css
import "./assets/css/reset.css"
2.公共组件
1.components/index.js
export default {
}
2.main.js处理
//2.处理公共组件
import Components from "./components"
for(let i in Components){
Vue.component(i,Components[i])
}
3.过滤器
1.filters/index.js
export default {
}
2.main.js处理
// 3.处理过滤器
import Filters from "./filters"
for(let i in Filters){
Vue.filter(i,Filters[i])
}
4.仓库
1.安装vuex
cnpm i vuex --save
2.配置目录
-store
index.js 导出仓库
actions.js 根级别下的actions
mutations.js 根级别下的mutations state getters
-modules 模块
3.导出仓库
import Vue from "vue"
import Vuex from "vuex"
Vue.use(Vuex)
import actions from "./actions"
import {state,mutations,getters} from "./mutations"
export default new Vuex.Store({
state,
mutations,
getters,
actions,
modules:{
}
})
4.main.js引入仓库
//4.处理仓库
import store from "./store"
new Vue({
store,
})
5.数据请求
1.安装模块
cnpm i axios qs --save
2.配置代理 config/index.js
proxyTable: {
"/api": {
target: "http://localhost:3000",
changeOrigin: true,
pathRewrite: {
"^/api": "http://localhost:3000"
}
}
},
3.util/request.js
import axios from "axios"
import qs from "qs"
let baseUrl = "/api";
//响应拦截
axios.interceptors.response.use(res => {
console.group("====本次请求的地址是:" + res.config.url + "======");
console.log(res);
console.groupEnd()
return res;
})
6.UI
1.安装
cnpm i element-ui --save
2.main.js引入
// 6.处理element-ui
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI)
7.弹框封装 util/alert.js
import Vue from "vue"
let vm = new Vue()
//成功弹框
export const successAlert = (msg) => {
vm.$message({
message: msg,
type: 'success'
});
}
//警告弹框
export const warningAlert = msg => {
vm.$message({
message: msg,
type: 'warning'
});
}
5.安装依赖包
cnpm i vuex axios qs element-ui --save
6.配置1级路由
1.pages下创建了login index
2.router/index.配置规则
const login = () => import("../pages/login/login")
const index = () => import("../pages/index/index")
export default new Router({
routes: [{
path: "/login",
component: login
},
{
path: "/",
component: index
}
]
})
3.app.vue定义路由出口
<router-view></router-view>
7.login.vue
画了静态页
<div class="login">
<div class="con">
<h3>登录</h3>
<el-input v-model="user.name" placeholder="输入账号" clearable></el-input>
<el-input v-model="user.pass" placeholder="输入密码" clearable show-password></el-input>
<div class="btn-box">
<el-button type="primary" @click="login">登录</el-button>
</div>
</div>
</div>
8.index.vue
1.粘贴布局
<el-container class="con">
<el-aside width="200px"></el-aside>
<el-container>
<el-header>Header</el-header>
<el-main>Main</el-main>
</el-container>
</el-container>
2.粘贴导航
<!-- 左侧导航 -->
<el-menu
default-active="3"
unique-opened
class="el-menu-vertical-demo"
@open="handleOpen"
@close="handleClose"
background-color="#20222A"
text-color="#fff"
active-text-color="#ffd04b">
<el-menu-item index="3">
<i class="el-icon-menu"></i>
<span slot="title">首页</span>
</el-menu-item>
<el-submenu index="1">
<template slot="title">
<i class="el-icon-location"></i>
<span>系统设置</span>
</template>
<el-menu-item-group>
<el-menu-item index="1-1">菜单管理</el-menu-item>
<el-menu-item index="1-2">角色管理</el-menu-item>
<el-menu-item index="1-2">管理员管理</el-menu-item>
</el-menu-item-group>
</el-submenu>
<el-submenu index="2">
<template slot="title">
<i class="el-icon-location"></i>
<span>商城管理</span>
</template>
<el-menu-item-group>
<el-menu-item index="1-1">商品分类</el-menu-item>
<el-menu-item index="1-2">商品规格</el-menu-item>
<el-menu-item index="1-2">商品管理</el-menu-item>
<el-menu-item index="1-2">会员管理</el-menu-item>
<el-menu-item index="1-2">轮播图管理</el-menu-item>
<el-menu-item index="1-2">秒杀活动</el-menu-item>
</el-menu-item-group>
</el-submenu>
</el-menu>
9.二级路由规则
1.pages下创建 home menu role manage classify spec goods member banner seckill
2.路由规则
//首页下面的二级路由规则
const indexRoutes=[
{
path:"menu",
component:menu
},
{
path:"role",
component:role
},
{
path:"manage",
component:manage
},
{
path:"classify",
component:classify
},
{
path:"spec",
component:spec
},
{
path:"goods",
component:goods
},
{
path:"banner",
component:banner
},
{
path:"member",
component:member
},
{
path:"seckill",
component:seckill
},
]
{
path: "/",
component: index,
children:[
{
path:"",
component:home
},
...indexRoutes
]
}
2.index.vue 定义二级路由出口
<el-main>
<!-- 2级路由出口 -->
<router-view></router-view>
</el-main>
3.将导航改用路由模式,index对应path
<el-menu
router
default-active="/"
unique-opened
class="el-menu-vertical-demo"
@open="handleOpen"
@close="handleClose"
background-color="#20222A"
text-color="#fff"
active-text-color="#ffd04b">
<el-menu-item index="/">
<i class="el-icon-menu"></i>
<span slot="title">首页</span>
</el-menu-item>
<el-submenu index="1">
<template slot="title">
<i class="el-icon-location"></i>
<span>系统设置</span>
</template>
<el-menu-item-group>
<el-menu-item index="/menu">菜单管理</el-menu-item>
<el-menu-item index="/role">角色管理</el-menu-item>
<el-menu-item index="/manage">管理员管理</el-menu-item>
</el-menu-item-group>
</el-submenu>
</el-menu>
10.菜单管理
1.拆分组件
<div>
<!-- 添加按钮 -->
<el-button type="primary" @click="willAdd">添加</el-button>
<!-- 数据列表 -->
<v-list @emit="emit($event)"></v-list>
<!-- 添加弹框 -->
<v-add :info="info" @hide="hide" ref="add"></v-add>
</div>
2.绘制添加组件
<el-dialog :title="info.title" :visible.sync="info.isShow"
@closed="close"
>
<el-form :model="form">
<el-form-item label="菜单名称" :label-width="width">
<el-input v-model="form.title" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="上级菜单" :label-width="width">
<el-select v-model="form.pid" placeholder="请选择活动区域" @change="changePid">
<el-option label="顶级菜单" :value="0"></el-option>
<!-- 少一个动态的数据 -->
<el-option v-for="item in list" :key="item.id" :label="item.title" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="菜单类型" :label-width="width">
<el-radio v-model="form.type" :label="1" disabled>目录</el-radio>
<el-radio v-model="form.type" :label="2" disabled>菜单</el-radio>
</el-form-item>
<el-form-item label="菜单图标" :label-width="width" v-if="form.type==1">
<el-select v-model="form.icon" placeholder="请选择目录图标">
<el-option value="el-icon-setting">
<i class="el-icon-setting"></i>
</el-option>
<el-option value="el-icon-goods">
<i class="el-icon-goods"></i>
</el-option>
<el-option value="el-icon-user">
<i class="el-icon-user"></i>
</el-option>
<el-option value="el-icon-camera">
<i class="el-icon-camera"></i>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="菜单地址" :label-width="width" v-else>
<el-select v-model="form.url" >
<el-option value="" label="--请选择--" disabled></el-option>
<el-option v-for="item in indexRoutes" :key="item.path" :label="item.name" :value="'/'+item.path"></el-option>
</el-select>
</el-form-item>
<el-form-item label="状态" :label-width="width">
<el-switch v-model="form.status" :active-value="1" :inactive-value="2"></el-switch>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="cancel">取 消</el-button>
<el-button type="primary" @click="add" v-if="info.isAdd">添 加</el-button>
<el-button type="primary" @click="update" v-else>修 改</el-button>
</div>
</el-dialog>
3.弹框状态
1.定义数据 menu.vue
data() {
return {
//传递给子组件的信息
info:{
//添加弹框出现状态
isShow:false,
title:"添加菜单",
isAdd:true
}
}
},
2.info传递给add.vue
<!-- 添加弹框 -->
<v-add :info="info"></v-add>
3.add.vue接收
props:["info"],
4.使用info
<el-dialog :title="info.title" :visible.sync="info.isShow">
</el-dialog>
5.add.vue 点击了取消
<el-button @click="cancel">取 消</el-button>
//点击了取消
cancel(){
this.$emit("hide")
},
menu.vue
<v-add @hide="hide"></v-add>
// 弹框消失
hide(){
this.info.isShow=false;
},
4.添加操作
1.定义初始数据
//表单数据
form:{
pid:0,
title:"",
icon:"",
type:1,
url:"",
status:1
}
2.通过v-model绑定到view上
3.点击了“添加” 按钮,准备发送添加请求
request.js
//菜单添加
export const reqAddMenu = (form) => {
return axios({
url: baseUrl + "/api/menuadd",
method: "post",
data: qs.stringify(form)
})
}
发送添加请求
//添加
add(){
reqAddMenu(this.form).then(res=>{
if(res.data.code==200){
//添加成功
successAlert(res.data.msg)
//弹框消失
this.$emit("hide")
//数据重置
this.empty()
//重新获取list
this.reqList()
}else{
warningAlert(res.data.msg)
}
})
},
//重置form数据
empty(){
this.form={
pid:0,
title:"",
icon:"",
type:1,
url:"",
status:1
}
},
5.列表获取
由于很多组件都会使用到列表数据,所以决定将列表数据写在vuex
1.store/modules/menu.js
import {
reqMenuList
} from "@/util/request.js"
const state = {
//菜单列表
list: []
}
const mutations = {
//修改list
changeList(state, arr) {
state.list = arr;
}
}
const actions = {
//请求
reqListAction(context){
reqMenuList({istree:true}).then(res=>{
context.commit("changeList",res.data.list)
})
}
}
const getters = {
list(state){
return state.list
}
}
export default {
state,
mutations,
actions,
getters,
namespaced: true
}
request.js
//菜单列表
export const reqMenuList = (params) => {
return axios({
url: baseUrl + "/api/menulist",
method: "get",
params: params
})
}
2.在store中添加menu模块
import menu from "./modules/menu"
export default new Vuex.Store({
state,
mutations,
getters,
actions,
modules:{
menu
}
})
3.menu/components/list.vue
import {mapGetters,mapActions} from "vuex"
export default {
computed:{
...mapGetters({
list:"menu/list"
})
},
methods: {
...mapActions({
reqList:"menu/reqListAction"
})
},
mounted() {
//一进来 走请求
this.reqList()
}
}
6.编辑
1.list.vue触发了编辑按钮
<el-button type="primary" @click="edit(scope.row.id)">编辑</el-button>
//点击了编辑按钮,通知menu,点了编辑
edit(id){
this.$emit("emit",id)
}
2.menu.vue接收事件
<v-list @emit="emit($event)"></v-list>
<v-add ref="add"></v-add>
//触发了编辑
emit(id){
this.info={
isShow:true,
title:"修改菜单",
isAdd:false
}
//父组件调用子组件的方法
this.$refs.add.look(id)
}
3.add.vue look执行,查询某一条数据
request.js
//菜单详情
export const reqMenuDetail = (params) => {
return axios({
url: baseUrl + "/api/menuinfo",
method: "get",
params: params
})
}
add.vue
//查看一条数据
look(id){
reqMenuDetail({id:id}).then(res=>{
this.form=res.data.list
this.form.id=id;
})
},
4.点击了修改按钮
request.js
//菜单修改
export const reqMenuUpdate = (form) => {
return axios({
url: baseUrl + "/api/menuedit",
method: "post",
data: qs.stringify(form)
})
}
add.vue
//点击了修改
update(){
reqMenuUpdate(this.form).then(res=>{
if(res.data.code==200){
successAlert("更新成功")
this.$emit("hide")
this.empty()
this.reqList()
}else{
warningAlert(res.data.msg)
}
})
}
7.做完编辑,点击编辑--》取消--》添加,数据存在 bug
<el-dialog @closed="close"></el-dialog>
//弹框关闭完成
close(){
// 如果是编辑,取消了,就要清空
if(!this.info.isAdd){
this.empty()
}
},
8.删除
1.list.vue 点击了删除按钮
<el-button type="danger" @click="del(scope.row.id)">删除</el-button>
2.request.js
//菜单删除 params={id:1}
export const reqMenuDel = (params) => {
return axios({
url: baseUrl + "/api/menudelete",
method: "post",
data:qs.stringify(params)
})
}
3.二次确认是否删除
//删除
del(id){
this.$confirm('你确定要删除吗?', '提示', {
confirmButtonText: '删除',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
//点击了确定,发起删除请求
reqMenuDel({id:id}).then(res=>{
if(res.data.code==200){
successAlert("删除成功")
this.reqList()
}else{
warningAlert(res.data.msg)
}
})
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
}
11.角色管理
1.拆分组件 role.vue
<div>
<!-- 添加按钮 -->
<el-button type="primary">添加</el-button>
<!-- 列表 -->
<v-list></v-list>
<!-- 添加弹框 -->
<v-add></v-add>
</div>
2.绘制list.vue
<el-table
:data="list"
style="width: 90%;margin:10px auto;margin-bottom: 20px;"
row-key="id"
border
>
<el-table-column
prop="id"
label="角色编号"
sortable
width="180">
</el-table-column>
<el-table-column
prop="title"
label="角色名称"
sortable
width="180">
</el-table-column>
<el-table-column
label="状态">
<template slot-scope="scope">
<el-button type="primary" v-if="scope.row.status==1">启用</el-button>
<el-button type="info" v-else>禁用</el-button>
</template>
</el-table-column>
<el-table-column
label="操作">
<template slot-scope="scope">
<el-button type="primary" @click="edit(scope.row.id)">编辑</el-button>
<el-button type="danger" @click="del(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
3.add.vue 弹框的出现和消失
1.role.vue 定义控制弹框的变量 info
data() {
return {
//控制add的状态
info:{
isShow:false,
title:"添加角色",
isAdd:true
}
}
},
2.role.vue 点击了添加按钮
methods: {
//点击了添加按钮
willadd(){
this.info={
isShow:true,
title:"添加角色",
isAdd:true
}
}
},
3.role.vue 将info传递给add.vue
<v-add :info="info"></v-add>
4.add.vue接收
props:["info"]
5.点击了取消按钮
//点击了取消
cancel(){
this.info.isShow=false;
}
4.角色添加
1.画了静态页
<el-tree
:data="menuList"
show-checkbox
node-key="id"
ref="tree"
:props="{children:'children',label:'title'}">
</el-tree>
2.request.js
//角色添加
export const reqRoleAdd = (form) => {
return axios({
url: baseUrl + "/api/roleadd",
method: "post",
data: qs.stringify(form)
})
}
//角色列表
export const reqRoleList = () => {
return axios({
url: baseUrl + "/api/rolelist",
method: "get",
})
}
//角色详情
export const reqRoleDetail = (params) => {
return axios({
url: baseUrl + "/api/roleinfo",
method: "get",
params: params
})
}
//角色修改
export const reqRoleUpdate = (form) => {
return axios({
url: baseUrl + "/api/roleedit",
method: "post",
data: qs.stringify(form)
})
}
//角色删除 params={id:1}
export const reqRoleDel = (params) => {
return axios({
url: baseUrl + "/api/roledelete",
method: "post",
data:qs.stringify(params)
})
}
3.从vuex中取出menu的list,以及修改menuList的动作 ,在tree上展示
import {mapGetters,mapActions} from "vuex"
export default {
computed:{
...mapGetters({
"menuList":"menu/list"
})
},
methods: {
...mapActions({
"reqMenuList":"menu/reqListAction"
}),
},
mounted() {
//如果menuList数组是个空的,要发起请求得到
if(this.menuList.length==0){
this.reqMenuList()
}
}
}
4.点击了添加按钮
//数据重置
empty(){
this.form={
rolename:"",
menus:[],
status:1
}
//重置树形控件
this.$refs.tree.setCheckedKeys([])
},
//点击了添加按钮
add(){
// this.$refs.tree.getCheckedKeys() 获取树形控件上的选中的key
this.form.menus=JSON.stringify(this.$refs.tree.getCheckedKeys())
reqRoleAdd(this.form).then(res=>{
if(res.data.code==200){
successAlert("添加成功");
//弹框消失
this.cancel();
//数据重置
this.empty()
//刷新角色列表的数据
}else{
warningAlert(res.data.msg)
}
})
}
5.列表
1.列表决定放到vuex中,store/modules/role.js
import {
reqRoleList
} from "@/util/request.js"
const state = {
//角色列表
list: []
}
const mutations = {
//修改list
changeList(state, arr) {
state.list = arr;
}
}
const actions = {
//请求
reqListAction(context) {
reqRoleList().then(res => {
let arr= res.data.list? res.data.list:[]
context.commit("changeList", res.data.list)
})
}
}
const getters = {
list(state) {
return state.list
}
}
export default {
state,
mutations,
actions,
getters,
namespaced: true
}
2.role 放到store
import role from "./modules/role"
export default new Vuex.Store({
state,
mutations,
getters,
actions,
modules:{
menu,
role
}
})
3.list.vue中取数据和方法
import {mapGetters,mapActions} from "vuex"
export default {
computed:{
...mapGetters({
list:"role/list"
})
},
methods: {
...mapActions({
"reqList":"role/reqListAction"
})
},
mounted() {
//一进来就请求了角色列表数据
this.reqList()
}
}
4.添加成功要重新请求列表数据
...mapActions({
"reqMenuList":"menu/reqListAction",
"reqRoleList":"role/reqListAction"
}),
reqRoleAdd(this.form).then(res=>{
if(res.data.code==200){
successAlert("添加成功");
//弹框消失
this.cancel();
//数据重置
this.empty()
//刷新角色列表的数据
this.reqRoleList()
}else{
warningAlert(res.data.msg)
}
})
6.编辑
1.list.vue 点击了编辑,通知role.vue点了编辑
<el-button type="primary" @click="edit(scope.row.id)">编辑</el-button>
//点击了编辑
edit(id){
this.$emit("edit",id)
}
2.role.vue收到自定义事件,弹框出现,add发送获取详情的请求
<!-- 列表 -->
<v-list @edit="edit"></v-list>
<!-- 添加弹框 -->
<v-add :info="info" ref="add"></v-add>
//编辑
edit(id){
this.info={
isShow:true,
title:"修改角色",
isAdd:false
}
this.$refs.add.look(id)
}
3.add.vue查询某一条数据,id给form,树形控件单独处理
//查看详情
look(id){
reqRoleDetail({id:id}).then(res=>{
this.form=res.data.list;
this.form.id=id;
this.$refs.tree.setCheckedKeys(JSON.parse(res.data.list.menus))
})
},
4.点了修改按钮
//修改
update(){
// this.$refs.tree.getCheckedKeys() 获取树形控件上的选中的key
this.form.menus=JSON.stringify(this.$refs.tree.getCheckedKeys())
reqRoleUpdate(this.form).then(res=>{
if(res.data.code==200){
successAlert("修改成功");
//弹框消失
this.cancel();
//数据重置
this.empty()
//刷新角色列表的数据
this.reqRoleList()
}else{
warningAlert(res.data.msg)
}
})
}
7.删除
1.因为每次都要二次确认,所以封装了一个全局的删除组件 components/vDel.vue
<template>
<el-button type="danger" @click="del">删除</el-button>
</template>
<script>
export default {
methods:{
del(){
this.$confirm('你确定要删除吗?', '提示', {
confirmButtonText: '删除',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$emit("confirm")
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
}
},
}
</script>
2.components/index.js 引入vDel
import vDel from "./vDel"
export default {
vDel
}
3.list.vue调用 v-del
<v-del @confirm="del(scope.row.id)"></v-del>
//删除
del(id){
//点击了确定,发起删除请求
reqRoleDel({id:id}).then(res=>{
if(res.data.code==200){
successAlert("删除成功")
this.reqList()
}else{
warningAlert(res.data.msg)
}
})
}
12.管理员管理
1.添加、列表({page:1.size:20})、编辑、修改、删除 如上。
2.分页组件
total是总的数量,
page-size是一页的数量,
分页组件会自动计算有几页。 eg:total=10,page-size=2,那么就有5页
<el-pagination
:page-size="2"
layout="prev, pager, next"
:total="10"
>
</el-pagination>
3.取管理员的总数量
1.request.js
export const reqUserNum=()=>{
return axios({
url:baseUrl+"/api/usercount",
method:"get"
})
}
2.store/modules/manage.js
定义了一个状态 total,用来保存管理员的总数
const state = {
//管理员总数
total: 0,
}
const mutations = {
//修改total
changeTotal(state, num) {
state.total = num;
},
}
const actions = {
//获取总数的请求
reqListNum(context) {
reqUserNum().then(res => {
context.commit("changeTotal", res.data.list[0].total)
})
},
}
const getters = {
total(state) {
return state.total
},
}
3.list.vue 取出total,并且要触发获取总数的请求
computed:{
...mapGetters({
total:"manage/total",
})
},
methods: {
...mapActions({
reqTotal:"manage/reqListNum",
}),
}
mounted(){
this.reqList()
//一进来页面请求数据的总数
this.reqTotal()
}
<el-pagination
:total="total"
>
</el-pagination>
4.设置一下当前一页的数量
vuex size
const state = {
//一页的条数
size: 2,
}
const mutations = {
}
const actions = {
//请求
reqListAction(context) {
reqUserList({
page: 1,
size: context.state.size
}).then(res => {
let arr = res.data.list ? res.data.list : []
context.commit("changeList", arr)
})
},
}
const getters = {
size(state) {
return state.size
},
}
list.vue
computed:{
...mapGetters({
size:"manage/size",
})
},
<el-pagination
:page-size="size"
>
</el-pagination>
5.页码发生改变,需要重新请求数据,重新请求数据需要知道是第几页。
1.在vuex中定义了一个变量page,用来记录当前是第几页
const state = {
//当前访问的页码
page: 1,
}
const mutations = {
//修改页面
changePage(state, page) {
state.page = page
}
}
const actions = {
//请求
reqListAction(context) {
reqUserList({
page: context.state.page,
size: context.state.size
}).then(res => {
let arr = res.data.list ? res.data.list : []
context.commit("changeList", arr)
})
},
//修改了page
changePageAction(context, page) {
context.commit("changePage", page)
//重新请求列表数据
context.dispatch("reqListAction")
}
}
const getters = {
page(state) {
return state.page
},
}
2.list.vue 点击了页码、上一页、下一页,修改页面
computed:{
...mapGetters({
page:"manage/page",
})
},
methods: {
...mapActions({
changePageAction:"manage/changePageAction"
}),
}
在分页组件上触发修改页码
<el-pagination
:page-size="size"
layout="prev, pager, next"
:total="total"
:current-page='page'
@current-change="changeCurrentPage"
>
</el-pagination>
//修改了当前的页码
changeCurrentPage(p){
//修改一下vuex里面的page
this.changePageAction(p)
}
6.add.vue添加成功 总数会发生改变,重新请求总数
add(){
reqUserAdd(this.form).then(res=>{
if(res.data.code==200){
successAlert("添加成功");
//弹框消失
this.cancel();
//数据重置
this.empty()
//刷新角色列表的数据
this.reqUserList()
//重新请求总数
this.reqTotal()
}else{
warningAlert(res.data.msg)
}
})
},
7.list.vue删除成功 ,总数也会发生改变,而且页码也有可能变,而且最后一页有可能都已经没有了,所以页码重置
del(id){
//点击了确定,发起删除请求
reqUserDel({uid:id}).then(res=>{
if(res.data.code==200){
successAlert("删除成功")
//删除完成,重新取总数,page设置为1
this.reqTotal()
this.changePageAction(1)
}else{
warningAlert(res.data.msg)
}
})
},
13.商品分类之 上传文件
1.原生上传文件
1.绘制html
<!-- 原生的上传文件 -->
<div class="upload-box">
<h3 class="upload-add">+</h3>
<img class="upload-img" v-if="imgUrl" :src="imgUrl" alt="">
<input class="upload-file" @change="selectImg" type="file">
</div>
.upload-box{
width: 150px;
height: 150px;
border: 1px dashed #cccccc;
position: relative;
}
.upload-add{
width: 150px;
height: 150px;
text-align: center;
line-height: 150px;
font-size: 40px;
color: #666;
font-weight: 400;
}
.upload-img{
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
}
.upload-file{
width: 150px;
height: 150px;
position: absolute;
left: 0;
top: 0;
opacity: 0;
}
2.在input上绑定一个事件 change,可以通过事件对象获取到文件,通过URL对象将file生成一个地址,图片展示
<input class="upload-file" @change="selectImg" type="file">
//上传文件开始
selectImg(e){
var file=e.target.files[0];
this.imgUrl=URL.createObjectURL(file) ;//imgUrl要在data中声明
},
//上传文件结束
<img class="upload-img" v-if="imgUrl" :src="imgUrl" alt="">
3判断了文件的大小和格式是否正确
//上传文件开始
selectImg(e){
var file=e.target.files[0];
//2M
if(file.size>2*1024*1024){
warningAlert("文件格式不能超过2M")
return;
}
//图片类型
let imgTypeArr=[".jpg",".jpeg",".png",".gif"]
//获取到后缀名
var extname=file.name.slice(file.name.lastIndexOf("."));
//判断文件格式是否正确
if(!imgTypeArr.includes(extname)){
warningAlert("请上传正确的图片格式")
return;
}
this.imgUrl=URL.createObjectURL(file)
},
//上传文件结束
4.写了数据请求,因为参数中带有文件,所以需要使用formData
//分类添加
export const reqCateAdd = (form) => {
//{z:1,a:2,v:4,d:file}
var data=new FormData()
for(let i in form){
data.append(i,form[i])
}
return axios({
url: baseUrl + "/api/cateadd",
method: "post",
data: data
})
}
5.在add.vue
data() {
return {
imgUrl:"",
//label占的宽度
width:"100px",
//表单数据
form:{
pid:0,
catename:"",
img:null,
status:1
}
}
},
在选择了图片之后,需要将文件保存在form.img
selectImg(e){
//判断大小 判断文件格式
this.imgUrl=URL.createObjectURL(file)
this.form.img=file;
}
6.在list.vue取出来数据要展示图片,在原型上加$preImg
Vue.prototype.$preImg="http://localhost:3000"
7.展示图片 list.vue
<el-table-column
label="图片">
<template slot-scope="scope">
<img :src="$preImg+scope.row.img" alt="">
</template>
</el-table-column>
8.编辑 取出来详情 add.vue
//查看一条数据
look(id){
reqCateDetail({id:id}).then(res=>{
this.form=res.data.list;
this.form.id=id
this.imgUrl=this.$preImg+ res.data.list.img
})
},
9.清空 add.vue
//重置form数据
empty(){
this.form={
pid:0,
catename:"",
img:null,
status:1
}
this.imgUrl=""
},
2.element-ui 上传文件
1.粘贴 el-upload,绑定了on-change属性
<el-upload
class="avatar-uploader"
action="#"
:show-file-list="false"
:on-change="changeImg"
>
<img v-if="imgUrl" :src="imgUrl" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
2.处理changeImg
//element-ui 上传文件
changeImg(e){
var file=e.raw;
this.imgUrl=URL.createObjectURL(file)
this.form.img=file;
},
3.如果在UI框架中想修改样式,不起作用
1.important
.upload-add{
width: 150px !important;
height: 150px;
text-align: center;
line-height: 150px;
font-size: 40px;
color: #666;
font-weight: 400;
}
2.如果1的方法也不行,那么就使用stylus穿透
cnpm i stylus stylus-loader --save
<style lang="stylus" scoped>
</style>
在页面找一个一定存在的标签
.add >>> .el-upload{
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
14.首页-图表
1.安装
cnpm i echarts --save
2.引入 home.vue
import echarts from "echarts"
3.从vuex中取出数据
export default {
computed:{
...mapGetters({
list:"cate/list"
})
},
methods: {
...mapActions({
reqList:"cate/reqListAction"
}),
},
mounted() {
this.reqList();
},
}
4.数据变了,绘制图标
watch:{
list:{
handler(){
if(this.list.length>0){
// 基于准备好的dom,初始化echarts实例
var myChart = echarts.init(document.getElementById('main'));
// 指定图表的配置项和数据
var option = {
title: {
text: '商品分类概况'
},
tooltip: {},
legend: {
data:['分类数量']
},
xAxis: {
data: this.list.map(item=>item.catename)
},
yAxis: {},
series: [{
name: '分数数量',
type: 'line',
data: this.list.map(item=>item.children?item.children.length:0)
}]
};
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
}
},
deep:true
}
}
15.商品管理- 二级联动 富文本
1.二级联动
1.粘贴list.vue add.vue
2.绘制add.vue静态页面
3.写了商品的请求 6个 ,添加 删除 详情 修改 总数 列表
4.添加 初始化数据
return {
//二级分类的列表
secondCateList:[],
//规格属性的列表
attrList:[],
//图片临时地址
imgUrl:"",
//label占的宽度
width:"100px",
//表单数据
form:{
first_cateid:"",
second_cateid:"",
goodsname:"",
price:"",
market_price:"",
img:null,
description:"",
specsid:"",
specsattr:[],
isnew:1,
ishot:1,
status:1
}
}
5.一级分类数据从vuex中取出,如果之前没有取过,就发一次请求
computed: {
...mapGetters({
//分类列表
cateList:"cate/list",
})
},
methods:{
...mapActions({
//获取分类列表
reqCateList:"cate/reqListAction",
}),
}
mounted(){
//如果没有分类就请求
if(this.cateList.length==0){
this.reqCateList()
}
}
6.修改了一级分类,需要根据一级分类计算出二级分类的列表数组
<el-form-item label="一级分类" :label-width="width">
<el-select v-model="form.first_cateid" placeholder="请选择活动区域" @change="changeFirstId">
<el-option label="--请选择--" value="" disabled></el-option>
<!-- 少一个动态的数据 -->
<el-option v-for="item in cateList" :key="item.id" :label="item.catename" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="二级分类" :label-width="width">
<el-select v-model="form.second_cateid" placeholder="请选择活动区域" >
<el-option label="--请选择--" value="" disabled></el-option>
<!-- 少一个动态的数据 -->
<el-option v-for="item in secondCateList" :key="item.id" :label="item.catename" :value="item.id"></el-option>
</el-select>
</el-form-item>
//修改了一级分类
changeFirstId(){
//找到当前first_cateid对应的那条数据的children,赋值给secondCateList,遍历
this.secondCateList=this.cateList.find(item=>item.id==this.form.first_cateid).children
//因为更换了一级分类,二级分类重置
this.form.second_cateid="";
},
7.商品规格也是从vuex中取出
computed: {
...mapGetters({
//规格列表
specList:"spec/list",
})
},
methods:{
...mapActions({
//获取规格列表
reqSpecList:"spec/reqListAction",
}),
}
mounted(){
//请求全部的规格
this.reqSpecList(true)
}
由于之前在商品规格中,使用了分页,但是在商品添加时要的是全部的规格,不能分页,所以在请求的时候,参数变成变量,根据要不要做分页,通过一个变量来决定
const actions = {
//请求
reqListAction(context,bool) {
//传递一个bool,如果是true,那么就请求全部的规格,如果是false,就请求分页
let params=bool?{}:{
page: context.state.page,
size: context.state.size
}
reqspecsList(params).then(res => {
let arr = res.data.list ? res.data.list : []
arr.forEach(item=>{
item.attrs=JSON.parse(item.attrs)
})
context.commit("changeList", arr)
})
},
}
8.修改了商品规格,需要计算一下商品属性的数组
<el-form-item label="商品规格" :label-width="width">
<el-select v-model="form.specsid" placeholder="请选择活动区域" @change="changeSpecId">
<el-option label="--请选择--" value="" disabled></el-option>
<!-- 少一个动态的数据 -->
<el-option v-for="item in specList" :key="item.id" :label="item.specsname" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="商品属性" :label-width="width">
<el-select v-model="form.specsattr" placeholder="请选择活动区域" multiple>
<el-option label="--请选择--" value="" disabled></el-option>
<!-- 少一个动态的数据 -->
<el-option v-for="item in attrList" :key="item" :label="item" :value="item"></el-option>
</el-select>
</el-form-item>
//修改了商品规格
changeSpecId(){
//当商品规格变了,就计算一下商品属性要展示的数组列表
this.attrList=this.specList.find(item=>item.id==this.form.specsid).attrs;
//选中的商品属性重置
this.form.specsattr=[]
},
9.点击了编辑按钮,查看某一条数据,这个时候二级分类会出现没有下拉框选项,需要手动触发
//查看一条数据
look(id){
reqgoodsDetail({id:id}).then(res=>{
this.form=res.data.list;
this.form.id=id
this.imgUrl=this.$preImg+ res.data.list.img
this.form.specsattr=this.form.specsattr.split(",")
//计算二级分类的列表
this.secondCateList=this.cateList.find(item=>item.id==this.form.first_cateid).children
//计算商品属性列表
this.attrList=this.specList.find(item=>item.id==this.form.specsid).attrs;
})
},
2.富文本
1.官网:http://www.wangeditor.com/
2.安装
cnpm i wangeditor --save
3.引入
import E from "wangeditor"
<el-dialog @opened="createEditor">
<div id="editor">
</div>
</el-dialog>
//创建编辑器
createEditor(){
//创建编辑器
let editor=new E("#editor");
editor.create()
},
4.取值
editor.txt.html()
5.设值
editor.txt.html('<p>123</p>')
6.使用
1.引入
import E from "wangeditor"
2.创建一个富文本编辑器
<el-dialog @opened="createEditor">
<div id="editor">
</div>
</el-dialog>
//创建编辑器
createEditor(){
//创建编辑器
this.editor=new E("#editor");
this.editor.create()
},
3.这时候出现一个bug,取消,再添加,就会多一个富文本编辑器
<div id="editor" v-if="info.isShow"></div>
4.添加或者修改之前,应该先将富文本编辑器内容取出,赋值给form.description
//添加
add(){
//取出富文本编辑器的内容,赋值给form的description
this.form.description=this.editor.txt.html()
reqgoodsAdd(this.form).then(res=>{})
},
5.当查看某一条数据的时候,数据赋值给了form,需要将form.description赋值给编辑器,如果在请求完成的时候赋值,这个时候编辑器还没有,所以赋不上,需要等编辑器创建好了再赋值
//创建编辑器
createEditor(){
//创建编辑器
this.editor=new E("#editor");
this.editor.create()
//给富文本编辑器赋值
this.editor.txt.html(this.form.description)
},
16.登录
1.发起登录请求 login.vue
login(){
//登录请求
reqUserLogin(this.user).then(res=>{
if(res.data.code==200){//登录成功
//弹成功
successAlert("登录成功");
//将用户信息保存 vuex
this.changeInfoAction(res.data.list)
//跳转
this.$router.push("/")
}else{
warningAlert(res.data.msg)
}
})
}
2.将用户信息存入vuex
1.store/modules/user.js
const state = {
//用户信息
info: sessionStorage.getItem("info") ? JSON.parse(sessionStorage.getItem("info")) : {}
}
const mutations = {
//修改用户信息
changeInfo(state, info) {
state.info = info;
if (info.username) {
//数据同步到本地存储中
sessionStorage.setItem("info", JSON.stringify(info))
} else {
sessionStorage.removeItem("info")
}
}
}
const actions = {
//页面触发的修改用户信息
changeInfoAction({
commit
}, info) {
commit("changeInfo", info)
}
}
const getters = {
//导出
info(state) {
return state.info
}
}
export default {
state,
mutations,
actions,
getters,
namespaced: true
}
2.store/index.js引入
3.登录成功的时候调用修改info的action
if(res.data.code==200){//登录成功
//弹成功
successAlert("登录成功");
//将用户信息保存 vuex
this.changeInfoAction(res.data.list)
//跳转
this.$router.push("/")
}
4.首页的头部取出数据展示 index.vue
{{info.username}}
computed:{
...mapGetters({
info:"user/info"
})
},
3.数据刷新就没有了,所以希望info存入的时候,本地存储也存一份
const mutations = {
//修改用户信息
changeInfo(state, info) {
state.info = info;
if (info.username) {
//数据同步到本地存储中
sessionStorage.setItem("info", JSON.stringify(info))
} else {
sessionStorage.removeItem("info")
}
}
}
如果用户刷新,就看一下本地存储有没有,有就将本地存储的info取出赋值给vuex,如果没有,就赋值一个{}
const state = {
//用户信息
info: sessionStorage.getItem("info") ? JSON.parse(sessionStorage.getItem("info")) : {}
}
4.退出登录
logout(){
this.changeInfoAction({})
this.$router.push("/login")
}
5.登录拦截
除了登录路由,其他的路由,都需要登录之后才能访问 router/index.js
//登录拦截
router.beforeEach((to,from,next)=>{
console.log(store);
//如果去登录 next
if(to.path==="/login"){
next()
return;
}
//去的不是登录,判断是否登录,如果登录过了,就next
if(store.state.user.info.id){
next()
return
}
//去的不是登录,也没有登录过
next("/login")
})
6.侧边栏遍历数据 index.vue
<div v-for="item in info.menus" :key="item.id">
<!-- 这个是目录 -->
<el-submenu v-if="item.children" :index="item.id+''">
<template slot="title">
<i :class="item.icon"></i>
<span>{{item.title}}</span>
</template>
<el-menu-item-group>
<el-menu-item :index="i.url" v-for="i in item.children" :key="i.id">{{i.title}}</el-menu-item>
</el-menu-item-group>
</el-submenu>
<!-- 这个直接是菜单 -->
<el-menu-item v-else :index="item.url">{{item.title}}</el-menu-item>
</div>
7.路由独享守卫 router/index.js
//路由独享守卫判断
function beforeEnter(url, next) {
store.state.user.info.menus_url.some(item => item == url) ? next() : next("/")
}
//首页下面的二级路由规则
export const indexRoutes = [{
path: "menu",
component: menu,
name: "菜单管理",
beforeEnter(to, from, next) {
beforeEnter("/menu", next)
}
},
{
path: "role",
component: role,
name: "角色管理",
beforeEnter(to, from, next) {
beforeEnter("/role", next)
}
},
{
path: "manage",
component: manage,
name: "管理员管理",
beforeEnter(to, from, next) {
beforeEnter("/manage", next)
}
},
{
path: "classify",
component: classify,
name: "商品分类",
beforeEnter(to, from, next) {
beforeEnter("/classify", next)
}
},
{
path: "spec",
component: spec,
name: "商品规格",
beforeEnter(to, from, next) {
beforeEnter("/spec", next)
}
},
{
path: "goods",
component: goods,
name: "商品管理",
beforeEnter(to, from, next) {
beforeEnter("/goods", next)
}
},
{
path: "banner",
component: banner,
name: "轮播图管理",
beforeEnter(to, from, next) {
beforeEnter("/banner", next)
}
},
{
path: "member",
component: member,
name: "会员管理",
beforeEnter(to, from, next) {
beforeEnter("/member", next)
}
},
{
path: "seckill",
component: seckill,
name: "秒杀活动",
beforeEnter(to, from, next) {
beforeEnter("/seckill", next)
}
},
]
8.修改 umall_api 里面的app.js 下面这段后端路由拦截解开
app.use(async (req, res, next) => {
if (!req.headers.authorization) {
res.send(MError("请设置请求头,并携带验证字符串"));
} else {
if (!await checkToken(req)) { // 过期
res.send(Guest([],"登录已过期或访问权限受限"));
} else {
next();
}
}
});
9.请求拦截
每次发起请求,除了登录接口之外,其他接口都需要携带一个token
//请求拦截
axios.interceptors.request.use(config=>{
//登录
if(config.url==baseUrl+"/api/userlogin"){
return config;
}
config.headers.authorization=store.state.user.info.token;
return config;
})
10.响应拦截
每次请求回来的数据,都有可能已经显示token过期,所以如果过期了,就应该 退出登录,跳到登录页面
//响应拦截
axios.interceptors.response.use(res => {
console.group("====本次请求的地址是:" + res.config.url + "======");
console.log(res);
console.groupEnd()
if(res.data.msg==="登录已过期或访问权限受限"){
warningAlert("登录已过期或访问权限受限")
//清空info
store.dispatch("user/changeInfoAction",{})
//跳转到登录
router.push("/login")
}
return res;
})