尚品汇后台管理系统

1、登录(退出登录)

后台接口swagger在线文档:
后台接口1
后台接口2

1、将所有的api接口url更换为和swagger在线文档相同的url。
2、跨域解决
devServer部分(proxy代理解决跨域)

devServer: {
    
    
    port: port,
    open: true,
    overlay: {
    
    
      warnings: false,
      errors: true
    },
    proxy: {
    
    
      // 会把请求路径中的/api换为后面的代理服务器
      '/dev-api': {
    
    
        // 提供数据的服务器地址
        target: 'http://39.98.123.211',
        pathRewrite: {
    
     '^/dev-api': '' }
      }
    }

  },

记得重启项目
3、切记将请求拦截器的X-Token改为token,否则后台会返回jwt空错误。
config.headers['token'] = getToken()
退出登录部分可以不做修改。

2、路由设置(***)

路由部分代码

export const constantRoutes = [
  {
    
    
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  },

  {
    
    
    path: '/404',
    component: () => import('@/views/404'),
    hidden: true
  },

  {
    
    
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    children: [{
    
    
      path: 'dashboard',
      name: 'Dashboard',
      component: () => import('@/views/dashboard/index'),
      meta: {
    
     title: '首页', icon: 'dashboard' }
    }
    ]
  },
  {
    
    
    path: '/product',
    name: 'Product',
    component: Layout,
    meta: {
    
     title: '商品管理', icon: 'el-icon-goods' },
    children: [{
    
    
      path: 'trademark',
      name: 'TradeMark',
      component: () => import('@/views/tradeMark'),
      meta: {
    
     title: '品牌管理' }
    }, {
    
    
      path: 'attr',
      name: 'Attr',
      component: () => import('@/views/Attr'),
      meta: {
    
     title: '平台属性管理' }
    }, {
    
    
      path: 'sku',
      name: 'Sku',
      component: () => import('@/views/Sku'),
      meta: {
    
     title: 'Sku管理' }
    }, {
    
    
      path: 'spu',
      name: 'Spu',
      component: () => import('@/views/Spu'),
      meta: {
    
     title: 'Spu管理' }
    }
    ]
  },
  // 404 page must be placed at the end !!!
  {
    
     path: '*', redirect: '/404', hidden: true }
]

路由表component属性

component用法一

path: 'trademark',
name: 'TradeMark',
component: () => import('@/views/tradeMark'),

当访问path为trademark时,会显示tradeMark这个组件。上面只是component最简单的应用。
component用法二

 	path: '/product',
    name: 'Product',
    component: Layout,
    meta: {
    
     title: '商品管理', icon: 'el-icon-goods' },
    children: [{
    
    
      path: 'trademark',
      name: 'TradeMark',
      component: () => import('@/views/tradeMark'),
      meta: {
    
     title: '品牌管理' }
    }]

如果一个路由只是一个菜单项,并不对应相应的组件,那么此时的component为本菜单项所在的路由组件。

例如:上面代码中product只是一个菜单项,并不对应相应组件。所以他的component应该为他所在的组件,即component: Layout。trademark为子菜单项,有具体对应的组件,所以component: () => import('@/views/tradeMark'),trademark的component原理就是用法一。

前端菜单栏显示的实现具体如下:

遍历路由信息数组,将路由项渲染为菜单项。菜单项的信息在路由信息中获取。
(1)如果路由项只有一个子路由,则将该子路由信息作为作为菜单项信息。
(2)如果路由项有多个子路由,将父路由信息作为一级菜单项信息。然后,遍历所有的子路由,生成相应的二级菜单项。

这个前端菜单项显示代码值得研究,写的非常好。

3、品牌列表模块

3.1 el-pagination layout属性

layout是指分页器各组件的排列方式,例如:
layout=" prev, pager, next, jumper,->,sizes,total"

在这里插入图片描述
->后面的组件会被放在页面的最右边。如下图所示:
在这里插入图片描述

3.2 封装四个模块api

四个模块分别为attr、sku、spu、tradeMark。
每个模块的api封装为对应的js文件,如下图所示:
在这里插入图片描述
为了方便使用,将四个模块再次封装到一个js文件中,然后统一暴露。
在这里插入图片描述
这样如果我们使用时,只需要import这个js文件就可以使用了,但是为了更加简介,我们可以将这个js文件在main.js中注册,将其是设置为全局属性。

// 引入api请求接口
import API from '@/api'
Vue.prototype.$API = API

之后在组件中使用四个模块的api函数时,可直接通过this.$API.函数名调用。

3.3 this滥用和参数解构

data中的数据十分常用,当我们使用时可能会频繁的通过this.使用data中的数据,这样容易发生滥用this去读取data中数据,程序性能会下降。具体原因链接描述
解决方法:ES6允许我们通过参数解构来避免this的滥用。

例如,getPageList函数需要使用page, limit两个属性。直接const { page, limit } = this解构出这两个属性。

export default {
    
    
  name: 'TradeMark',
  data() {
    
    
    return {
    
    
      page: 1,
      limit: 3
    }
  },
  mounted() {
    
    
    this.getPageList()
  },
  methods: {
    
    
    getPageList() {
    
    
      const {
    
     page, limit } = this
      this.$API.trademark.getTradeMarkInfo(page, limit)
    }
  }
}

3.4 品牌列表展示(插槽)

1、获取数据函数
设置了pager = 1参数,调用时,如果传值就会将值赋给pager ,如果不传pager = 1。即默认获取的是第一页数据。

 async getPageList(pager = 1) {
    
    
      this.page = pager
      const {
    
     page, limit } = this
      const result = await this.$API.trademark.getTradeMarkInfo(page, limit)
      if (result.code === 200) {
    
    
        this.total = result.data.total
        this.list = result.data.records
      }
    }

2、需要注意的是插槽的使用。我们需要通过插槽获取父组件数据。

el-table插槽使用方法:
在这里插入图片描述
品牌logo列使用了插槽。所以通过一下代码探究插槽的使用

<el-table-column prop="logoUrl" label="品牌LOGO" align="center">
        <template slot-scope="{row, column, $index}">
          <p>{
   
   {row}}</p>
          <p>{
   
   {column}}</p>
          <p>{
   
   {$index}}</p>
        </template>
</el-table-column>

下面三个箭头分别对应slot-scope="{row, column, $index}中的row, column, $ index。
row表示表格当前一行的全部信息;
column表示当前列的一些属性;
$index表示当前行数据在整个数据数组中的下标。
在这里插入图片描述
这里我们只需要用到当前行所包含的品牌logo图片链接,所以只需要使用row,完整代码如下:

 <el-table :data="list" style="width: 100%">
      <el-table-column type="index" label="序号" width="80px" align="center">
      </el-table-column>
      <el-table-column prop="tmName" label="品牌名称" align="center">
      </el-table-column>
      <el-table-column prop="logoUrl" label="品牌LOGO" align="center">
        <template slot-scope="{row}">
          <img :src="row.logoUrl" style="width: 80px;height: 80px"/>
        </template>
      </el-table-column>
      <el-table-column prop="prop"  label="操作" align="center">
        <template slot-scope="{row}">
          <el-button type="primary">修改</el-button>
          <el-button type="danger">删除</el-button>
        </template>
      </el-table-column>
 </el-table>

3、分页器修改页面大小和修改当前页码都比较简单,直接贴代码。

// 修改当前页码
     handleCurrentChange(current) {
    
    
      this.getPageList(current)
    },
 // 修改页面大小
   handleSizeChange(size) {
    
    
      this.limit = size
      this.getPageList()
    },

注意:handleSizeChange中的getPageList需要带参数,因为getPageList定义了默认参数pager=1,如果不传入current,则getPageList的默认参数pager=1就会生效,就会一直请求第一页数据。

3.5 品牌信息新增和修改

1、头像上传组件
新增中比较重要的时头像上传组件,代码如下

 <el-upload
            class="avatar-uploader"
            action="dev-api/admin/product/fileUpload"
            :show-file-list="false"
            :on-success="handleAvatarSuccess"
            :before-upload="beforeAvatarUpload"
          >
            <img v-if="tmFrom.logoUrl" :src="tmFrom.logoUrl" class="avatar">
            <i v-else class="el-icon-plus avatar-uploader-icon"></i>
            <div slot="tip" class="el-upload__tip">只能上传jpg文件,且不超过2MB</div>
</el-upload>

图片数据不能用v-model绑定,
action 必选参数,上传的地址。这里的 action取值是上传图片的接口,是后台提供好的接口。
before-upload表示上传成功前的函数

beforeAvatarUpload(file) {
    
    
      const isJPG = file.type === 'image/jpeg'
      const isLt2M = file.size / 1024 / 1024 < 2

      if (!isJPG) {
    
    
        this.$message.error('上传头像图片只能是 JPG 格式!')
      }
      if (!isLt2M) {
    
    
        this.$message.error('上传头像图片大小不能超过 2MB!')
      }
      return isJPG && isLt2M
    }

on-success表示上传成功后的函数

  handleAvatarSuccess(res, file) {
    
    
      // res是上传成功后返回给前端的数据
      // file是上传成功后服务器返回给前端的数据
      this.tmFrom.logoUrl = URL.createObjectURL(file.raw)
    }

上面两个函数饿了吗UI官网都以给出,理解即可。
2、新增和修改函数
新增和修改按钮使用的是同一个弹窗。
(1)点击添加按钮显示弹窗
记得清空上次操作的数据

 addTradeMark() {
    
    
      this.dialogFormVisible = true
      // 清空上次操作的数据
      this.tmFrom = {
    
     tmName: '', logoUrl: '' }
    }

(2)点击修改按钮显示弹窗(浅拷贝问题
this.$tmFrom = row会使得二者同时指向数据的引用,当修改tmFrom表单项时,表格中数据也会改变,如果修改后点了取消按钮,表格中数据会发生改变。所以,要解决浅拷贝

updateTradeMark(row) {
    
    
      this.dialogFormVisible = true
      // ...解决单层浅拷贝问题
      this.tmFrom = {
    
     ...row }
    }

当赋值的对象row中还有对象元素时,又会出现上面的问题,所以可以通过深拷贝JSON.parse(JSON.stringfy())实现。
(3)击弹窗的确定按钮实现数据的修改或新增
新增和修改成功后,需要再次获取品牌信息。需要注意,如果时新增则刷新后显示第一页,如果是修改,则要在当前页刷新显示。

async addOrUpdate() {
    
    
      this.dialogFormVisible = false
      let result = await this.$API.trademark.addOrUpdateTradeMark(this.tmFrom)
      if (result.code === 200) {
    
    
        this.$message.success(this.tmFrom.id ? '修改品牌成功' : '添加品牌成功')
      }
      // 现需要再次获取品牌信息,修改数据要在当前页刷新
      this.getPageList(this.tmFrom.id ? this.page : 1)
    },

新增或修改品牌API函数

// 新增或修改品牌
export const addOrUpdateTradeMark = (tradeMark) => {
    
    
  // 有id则为修改
  if (tradeMark.id) {
    
    
    return request({
    
     url: 'admin/product/baseTrademark/update', method: 'put', data: tradeMark })
  }
  // 无id则为新增
  return request({
    
     url: 'admin/product/baseTrademark/save', method: 'post', data: tradeMark })
}

3.6 表单验证

表单验证,主要分以下几步:
(1)定义rules
(2)表单绑定rules,表单项prop绑定验证属性
(3)提交表单时触发的函数内,先进行表单验证,如果成功执行后续操作,否则弹出失败。

3.7 删除品牌信息

(1)气泡确认框
@onConfirm="deleteTradeMark(row)"绑定的是删除函数

 <template >
            <el-popconfirm
              title="这是一段内容确定删除吗?"
              @onConfirm="deleteTradeMark(row)"
            >
              <el-button slot="reference" type="danger" size="small">删除</el-button>
            </el-popconfirm>
          </template>

(2)删除函数
删除成功后要刷新页面。

async deleteTradeMark(row) {
    
    
      const result = await this.$API.trademark.deleteTradeMark(row.id)
      if (result.code === 200) {
    
    
        this.$message.success('删除成功')
        // 成功后,重新获取数据
        await this.getPageList(this.list.length > 1 ? this.page : 1)
      } else {
    
    
        this.$message.error('删除失败')
      }
    }

示例:
在这里插入图片描述

4、商品属性

4.1 三级联动

(1)三级联动静态组件
示例
在这里插入图片描述
由于多个页面都使用到了上面的el-card,所以将其封装为全局组件。
main.js

import CategorySelect from './components/CategorySelect'
Vue.component(CategorySelect.name, CategorySelect)

注意:虽然该全局组件的名字是CategorySelect ,但是在使用时最好用后面的形式<category-select></category-select>
原因:html不区分大小写,会将大写字母转换为小写,并在前面加-。即CategorySelect 转换为category-select
(2)三级联动数据展示
示例:
在这里插入图片描述
html静态代码

<el-card style="margin: 20px 0">
    <el-form :inline="true" class="demo-form-inline">
      <el-form-item label="一级分类">
        <el-select v-model="formList.category1" placeholder="请选择" value="" @change="getCategory2List">
          <el-option v-for="(c1,index) in category1List" :key="c1.id" :label="c1.name" :value="c1.id"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="二级分类" >
        <el-select v-model="formList.category2" placeholder="请选择" value="" @change="getCategory3List">
          <el-option v-for="(c2,index) in category2List" :key="c2.id" :label="c2.name" :value="c2.id"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="三级分类" >
        <el-select v-model="formList.category3" placeholder="请选择" value="">
          <el-option v-for="(c3,index) in category3List" :key="c3.id" :label="c3.name" :value="c3.id"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="onSubmit">查询</el-button>
      </el-form-item>
    </el-form>
  </el-card>

data属性

data() {
    
    
    return {
    
    
      category1List: [],
      category2List: [],
      category3List: [],
      formList: {
    
    
        category1: '',
        category2: '',
        category3: ''
      }
    }

注意:formList内容是三个选择框选中属性的id。后续也是通过id去查询。

针对于选中一级属性后,自动获取二级属性列表,选中二级属性后,再自动获取三级属性列表问题。
最初思路:通过computed或者watch去实现,但都没有成功。
老师的方法:通过给选择框添加@change属性,再回调函数内去获取下一级属性列表。

@change绑定的获取三级列表函数都比较简单,这里就不过多描述。

 // 获取一级分类
    // 获取一级分类
    async getCategory1List() {
    
    
      const result = await this.$API.attr.reqCategory1List()
      if (result.code === 200) {
    
    
        this.category1List = result.data
      } else {
    
    
        alert(result.message)
      }
    },
    // 通过选中一级属性id获取二级属性列表
    async getCategory2List() {
    
    
      // 当一级分类改变时,上一次选择的二级和三级分类应设置为空
      this.category2List = []
      this.category3List = []
      this.formList.category2 = ''
      this.formList.category3 = ''
      const result = await this.$API.attr.reqCategory2List(this.formList.category1)
      if (result.code === 200) {
    
    
        this.category2List = result.data
      } else {
    
    
        alert(result.message)
      }
    },
    // 通过选中二级属性id获取三级属性列表
    async getCategory3List() {
    
    
      // 当二级分类改变时,上一次选择的三级分类应设置为空
      this.category3List = []
      this.formList.category3 = ''
      const result = await this.$API.attr.reqCategory3List(this.formList.category2)
      if (result.code === 200) {
    
    
        this.category3List = result.data
      } else {
    
    
        alert(result.message)
      }
    },

(3)将选中的三级分类id传递给父组件
这里是通过点击查询按钮实现的父子组件通信
查询按钮绑定的回调

 // 子组件将三级分类id传递给父组件,父组件使用该id去查询并展示数据
    async onSubmit() {
    
    
      this.$emit('getCategoryId', this.formList)
    }

父组件

<category-select @getCategoryId="getCategoryId"></category-select>

父组件data

data() {
    
    
    return {
    
    
      category1Id: '',
      category2Id: '',
      category3Id: ''
    }
  },

父子组件通信,父组件的接收函数:getCategoryId函数
只有当三级id不为空时,才发请求。

 // 获取子组件传递三级分类id
    getCategoryId(list) {
    
    
      this.category1Id = list.category1Id
      this.category2Id = list.category2Id
      this.category3Id = list.category3Id
      if (this.category3Id) {
    
    
        console.log('发请求')
      }
    }

4.2 商品属性展示

通过三级联动获取到要展示的属性id后,接下来就是通过该id请求数据,然后展示。

猜你喜欢

转载自blog.csdn.net/weixin_43424325/article/details/122093881