主要是尚品汇(后台项目)

饿了么:模板介绍
简洁版: https://github.com/PanJiaChen/vue-admin-template
加强版: https://github.com/PanJiaChen/vue-element-admin
好笔记:https://blog.csdn.net/weixin_44337386/article/details/124228661?spm=1001.2014.3001.5502

模板的文件与文件夹认知【简洁版】

build
     ----index.js webpack配置文件【很少修改这个文件】
mock
    ----mock数据的文件夹【模拟一些假的数据mockjs实现的】,因为咱们实际开发的时候,利用的是真是接口

node_modules
     ------项目依赖的模块

public
     ------ico图标,静态页面,publick文件夹里面经常放置一些静态资源,而且在项目打包的时候webpack不会编译这个文件夹,原封不动的打包到dist文件夹里面

src
    -----程序员源代码的地方
    ------api文件夹:涉及请求相关的
    ------assets文件夹:里面放置一些静态资源(一般共享的),放在aseets文件夹里面静态资源,在webpack打包的时候,会进行编译
    ------components文件夹:一般放置非路由组件获取全局组件
    ------icons这个文件夹的里面放置了一些svg矢量图
    ------layout文件夹:他里面放置一些组件与混入
    ------router文件夹:与路由相关的
    -----store文件夹:一定是与vuex相关的
    -----style文件夹:与样式相关的
    ------utils文件夹:request.js是axios二次封装文件****
    ------views文件夹:里面放置的是路由组件

App.vue:根组件
main.js:入口文件
permission.js:与导航守卫有关、
settings:项目配置项文件
.env.development
.env.producation

0、起步

静态页面
书写API
axios二次封装
换成真实接口之后需要解决代理跨域问题(解决代理跨域问题)

后台管理系统API接口在线文档:
http://39.98.123.211:8170/swagger-ui.html
http://39.98.123.211:8216/swagger-ui.html

1、跨域

主要是尚品汇(后台项目)_第1张图片
vue.config.js
主要是尚品汇(后台项目)_第2张图片

2、退出

从哪里退出,登录就从哪里进入

async logout() {
  await this.$store.dispatch('user/logout')
  this.$router.push(`/login?redirect=${this.$route.fullPath}`)
}

3、路由搭建

/* 引入最外层骨架的一级路由组件*/
import Layout from '@/layout'
{
   path: '/',
   component: Layout,
   redirect: '/dashboard',
   children: [{
     path: 'dashboard',
     name: 'Dashboard',
     component: () => import('@/views/dashboard/index'),
     meta: { title: '首页', icon: 'dashboard' }
   }]
 },

 {
   path: '/product',
   component: Layout,
   meta: { title: '商品管理', icon: 'dashboard' },
   children: [
     {
       path: 'trademark',
       name: 'TradeMark',
       component: () => import('@/views/product/tradeMark'),
       meta: { title: '品牌管理' }
     },
     {
       path: 'attr',
       name: 'Attr',
       component: () => import('@/views/product/Attr'),
       meta: { title: '平台属性管理' }
     },
     {
       path: 'spu',
       name: 'Spu',
       component: () => import('@/views/product/Spu'),
       meta: { title: 'Spu管理' }
     },
     {
       path: 'sku',
       name: 'Sku',
       component: () => import('@/views/product/Sku'),
       meta: { title: 'Sku管理' }
     },
   ]
 },

3、element 样式修改


element 样式修改:源代码写错了,修改源代码。

主要是尚品汇(后台项目)_第3张图片

4、网站的标题修改

在这里插入图片描述

主要是尚品汇(后台项目)_第4张图片
主要是尚品汇(后台项目)_第5张图片

主要是尚品汇(后台项目)_第6张图片

5、语法校验

主要是尚品汇(后台项目)_第7张图片
在这里插入图片描述

 module.exports = {
  lintOnSave: false,//关闭语法检查
}

6、全局颜色配置

全局颜色配置

新建css文件夹

在这里插入图片描述

文件夹中的内容

/* 改变主题色变量 */
$--color-primary: teal;

/* 改变 icon 字体路径变量,必需 */
$--font-path: '~element-ui/lib/theme-chalk/fonts';

@import "~element-ui/packages/theme-chalk/src/index";

在mian.js中引入

最后在文件mian.js入口引入上面的文件。

7、封装全局的api

文件格式

主要是尚品汇(后台项目)_第8张图片
接口文件
主要是尚品汇(后台项目)_第9张图片
index文件

import * as tradeMark from './product/tradeMark'
import * as attr from './product/attr'
import * as sku from './product/sku'
import * as spu from './product/spu'

export default{
  tradeMark,
  attr,
  sku,
  spu
}

mian.js入口文件引入api
主要是尚品汇(后台项目)_第10张图片
组件中使用
在这里插入图片描述

//获取品牌列表的数据
async getPageList(pager = 1) {
  this.page = pager;
  //解构出参数
  const { page, limit } = this;
  //获取品牌列表的接口
  //当你向服务器发请求的时候,这个函数需要带参数,因此老师在data当中初始化两个字段,代表给服务器传递参数
  let result = await this.$API.trademark.reqTradeMarkList(page, limit);
  if (result.code == 200) {
    //分别是展示数据总条数与列表展示的数据
    this.total = result.data.total;
    this.list = result.data.records;
  }
},

8、分页器

主要是尚品汇(后台项目)_第11张图片

<el-pagination
  style="margin-top: 20px; text-align: center"
  :current-page="page"
  :total="total"
  :page-size="limit"
  :pager-count="7"
  :page-sizes="[3, 5, 10]"
  @current-change="getPageList"
  @size-change="handleSizeChange"
  layout="prev, pager, next, jumper,->,sizes,total"
>
el-pagination>

如图
在这里插入图片描述

9、测试接口是否成功

在浏览器地址栏中添加 平台的地址+接口

在这里插入图片描述

10、in promise Error: 成功at __webpack_exports__.default报错

在这里插入图片描述
主要是尚品汇(后台项目)_第12张图片
解决方法:

封装asiox文件:res.code!=200

 // 响应头
  response => {
    const res = response.data

    // if the custom code is not 20000, it is judged as an error.
    if (res.code !== 20000 && res.code!=200) {
      Message({
        message: res.message || 'Error',
        type: 'error',
        duration: 5 * 1000
      })

      // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
      if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
        // to re-login
        MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
          confirmButtonText: 'Re-Login',
          cancelButtonText: 'Cancel',
          type: 'warning'
        }).then(() => {
          store.dispatch('user/resetToken').then(() => {
            location.reload()
          })
        })
      }
      return Promise.reject(new Error(res.message || 'Error'))
    } else {
      return res
    }
  },

11、查看当前使用的地址:location.origin

以下情况是在你本地跑项目的时候的地址。

let x = location.origin
console.log(x) //'http://localhost:8080'

12、查看当前使用的地址是否与成功的接口一致

报错接口

在这里插入图片描述
成功接口
在这里插入图片描述

报错接口缺少dev-api
成功
在这里插入图片描述

13、新增修改删除、取消细节

1 新增修改用一个函数只需要判断一下是否有id存在
2 对于新增的品牌,给服务器传递数据,不需要传递ID,ID是由服务器生成
3 对于修改某一个品牌的操作,前端携带的参数需要带上id,你需要告诉服务器修改的是哪一个品牌

//处理添加品牌
//新增品牌: /admin/product/baseTrademark/save   post  携带两个参数:品牌名称、品牌logo
//切记:对于新增的品牌,给服务器传递数据,不需要传递ID,ID是由服务器生成

//修改品牌的
//修改品牌 /admin/product/baseTrademark/update   put   携带三个参数:id、品牌名称、品牌logo
//切记:对于修改某一个品牌的操作,前端携带的参数需要带上id,你需要告诉服务器修改的是哪一个品牌

export const reqAddOrUpdateTradeMark = (tradeMark) => {
    //带给服务器数据携带ID---修改
    if (tradeMark.id) {
        return request({ url: '/admin/product/baseTrademark/update', method: 'put', data: tradeMark });
    } else {
        //新增品牌
        return request({ url: '/admin/product/baseTrademark/save', method: 'post', data: tradeMark });
    }
}

点击确定:新增可以是返回第一页修改该留在当前页中

//添加按钮(添加品牌|修改品牌)
addOrUpdateTradeMark() {
  //当全部验证字段通过,再去书写业务逻辑
  this.$refs.ruleForm.validate(async (success) => {
    //如果全部字段符合条件
    if (success) {
      this.dialogFormVisible = false;
      //发请求(添加品牌|修改品牌)
      let result = await this.$API.trademark.reqAddOrUpdateTradeMark(
        this.tmForm
      );
      if (result.code == 200) {
        //弹出信息:添加品牌成功、修改品牌成功
        this.$message({
          type: "success",
          message: this.tmForm.id ? "修改品牌成功" : "添加品牌成功",
        });
        //添加或者修改品牌成功以后,需要再次获取品牌列表进行展示
        //如果添加品牌: 停留在第一页,修改品牌应该留在当前页面
        this.getPageList(this.tmForm.id ? this.page : 1);
      }
    } else {
      console.log("error submit!!");
      return false;
    }
  });
},

删除:需要留在上一条数据的页数

this.getList(this.list.length > 1this.page: this.page-1)

deleteTradeMark(row){
  this.$confirm(`此操作将永久删除${row.tmName}?`, '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(async() => {
    let res = await reqDeleteTradeMark(row.id)
    if(res.code == 200){
      this.$message({
        type: 'success',
        message: '删除成功!'
      });
      this.getList(this.list.length > 1? this.page: this.page-1)
    }
    
  }).catch(() => {
    this.$message({
      type: 'info',
      message: '已取消删除'
    })       
  })
},

点击取消的情况下验证但不重置表单

 close(formName){
      this.dialogFormVisible = false;this.$refs[formName].clearValidate()
    },

14、对象清空,细节

在这里插入图片描述
在这里插入图片描述
主要是尚品汇(后台项目)_第13张图片

15、修改对象的key,value不变


http://www.manongjc.com/detail/23-rysoxwfnefjhlto.html

要修改的代码

 1 var array = [
 2     {
 3         id:1,
 4         name:"小明"
 5     },
 6     {
 7         id:2,
 8         name:"小红"
 9     }
10 ];

修改后代码

  var array = [
      {
         value:1,
         label:"小明"
     },
     {
         value:2,
         label:"小红"
     }
 ];

修改步骤

  // 原生js
  var array = [
      {
         id:1,
         name:"小明"
     },
     {
         id:2,
         name:"小红"
     }
 ];
 //旧key到新key的映射
 var keyMap = {
     "id" : "value",
     "name" : "label"
 };
 
 for(var i = 0;i < array.length;i++){
     var obj = array[i];
     for(var key in obj){
         var newKey = keyMap[key];
         if(newKey){
           obj[newKey] = obj[key];
            delete obj[key];
        }
      }
 }
 console.log(array);
 
 
 // ES6 写法
var result = array.map(o =>{
     return{
         value:o.id,
         label:o.name
     }
});
console.log(result);

16、 el-upload自定义上传excel文件

主要是尚品汇(后台项目)_第14张图片

<el-button type="text" size="small" @click="downloadFile">点击下载导入模板el-button>
<el-upload
   action=""
    class="upload-demo"
    ref="upload"
    :headers="headers"
    drag
    :accept="acceptType"
    :http-request="readExcel"
    name="file"
    :limit="1"
  >
    <i class="el-icon-upload">i>
    <div class="el-upload__text">
      将文件拖到此处,或<em>点击上传em>
    div>
    <div class="el-upload__tip" slot="tip">
      温馨提示:只能上传xls/xlsx文件
    div>
  el-upload>
<el-button @click="onSubmit" >确定el-button>

引入XLSX

import XLSX from "xlsx";

tabJson的数据样式

主要是尚品汇(后台项目)_第15张图片

转换数据:将上传的表格数据转换成json数据

readExcel(params) {
 	// 获取上传的文件对象
   const file = params.file;
    // 通过FileReader对象读取文件
   this.file2Xce(file).then((tabJson) => {
     let xlsxJson = tabJson;
     this.fileList = [...xlsxJson[0].sheet];
     // 将表格数据转化成后端需要的格式
     var result = this.fileList.map((key) => {
         return {
           name: key.姓名,
           phone: key.电话,
           address : key.地址,
           sex : key.性别,
           birthday : key.出生日期,
           grade : key.年级,
           school : key.学校,
           classes : key.班级,
           remark : key.备注,
           starLevel : key.星级,
         };
       });
       this.guestBat = result
   });
 },
 // 提取表格数据
file2Xce(file) {
 return new Promise(function (resolve, reject) {
  	// 通过FileReader对象读取文件
    const reader = new FileReader();
    reader.onload = function (e) {
      const data = e.target.result;
      // 以二进制流方式读取得到整份excel表格对象
      this.wb = XLSX.read(data, {
        type: "binary",
      });
      const result = [];
      // 遍历每张工作表进行读取(这里默认只读取第一张表)
      this.wb.SheetNames.forEach((sheetName) => {
        result.push({
          sheetName: sheetName,
          sheet: XLSX.utils.sheet_to_json(this.wb.Sheets[sheetName]),
          // 利用 sheet_to_json 方法将 excel 转成 json 数据
        });
      });
      resolve(result);
    };
    reader.readAsBinaryString(file);
  });
},

点击确定获取表格的字段

onSubmit() {
  this.$refs["form"].validate(async (valid) => {
    if (valid) {
      if (this.guestBat.length === 0) {
        $msg("请先选择导入的文件", 2);
        return false;
      }

      if (!this.uploadnot) {
        let object = {
          customerList: this.guestBat,
        };

        const res = await addCustomerBat(object);
        if (res.state === "success") {
          window.$msg("批量导入成功");
        }
      } else {
        $msg("上传文件中有必填的数据项为空,请检查后再上传", 1);
      }
    }
  });
},

主要是尚品汇(后台项目)_第16张图片

下载模板

downloadFile() {
  let link = document.createElement("a"); // 创建a标签
  link.style.display = "none";
  link.href =
    "https://coduckfile.oss-cn-hangzhou.aliyuncs.com/kdy/批量导入客户_1.xlsx"; // 设置下载地址
  link.setAttribute("download", ""); // 添加downLoad属性
  document.body.appendChild(link);
  link.click();
},

=====================分割线,下面是点击按钮上传表格数据

点击按钮上传文件不用组件上传

 readExcel (e) {
   const files = e.target.files
   if (files.length < 1) {
     return false
   } else if (!/\.(xls|xlsx)$/.test(files[0].name.toLowerCase())) {
     $msg('上传文件格式不正确,请上传xls或者xlsx格式')
     return false
   }
   this.arrfile = []
   const fileReader = new FileReader()
   fileReader.onload = (ev) => {
     try {
       const data = ev.target.result
       const workbook = XLSX.read(data, {
         type: 'binary',
       }) // 读取数据
       const wsname = workbook.SheetNames[0] // 取第一张表
       const sheet2JSONOpts = {
         /** Default value for null/undefined values */
         defval: ''//给defval赋值为空的字符串
       }
       const ws = XLSX.utils.sheet_to_json(workbook.Sheets[wsname],sheet2JSONOpts) // 生成json表格内容

       ws.map((v,index) => {
         let obj = {}
         obj.name = v.姓名
         obj.phone = v.电话
         obj.address = v.地址
         obj.sex = v.性别
         obj.birthday = v.出生日期
         obj.grade = v.年级
         obj.school = v.学校
         obj.classes = v.班级
         obj.remark = v.备注
         obj.starLevel = v.星级
         if(obj.tel === "") {
           this.emptyntel = true
         }
         if(obj.name === "" || obj.phone === "") {
           $msg("第"+index+"行有必填的数据项为空,请检查后再上传",1)
           this.uploadnot = true
           return false
         }

         this.arrfile.push(obj)
       })
       console.log(JSON.stringify(this.arrfile))
       if(!this.uploadnot) {
         this.$refs.upload.value = null
         $msg("文件添加成功")
       }
     } catch (e) {
       console.log(e)
       return false
     }
   }
   fileReader.readAsBinaryString(files[0])
 },

17、点击按钮向数组中添加一条数据

初始值展位现象

主要是尚品汇(后台项目)_第17张图片
主要是尚品汇(后台项目)_第18张图片
修改

data(){
	return{
		attrInfo: {
        attrName: "", //属性名
        attrValueList: [
          //属性值,因为属性值可以有多个因此用数组,每一个属性值都是一个对象需要attrId,valueName
          
        ],
        categoryId: 0, //三级分类的id
        categoryLevel: 3, //因为服务器也需要区分几级id
      },
	}
}

点击取消按钮清空数据

主要是尚品汇(后台项目)_第19张图片
解决:
在点击进去这个页面的时候让数据初始化即可

addAttr() {
  // 1 打开属性值页面
  this.isShowTable = false
  // 将属性页能够读取到的值传给属性值页面
  this.attrInfo =  {
    attrName: "", //属性名
    attrValueList: [
    ],
    categoryId: this.category3Id, //三级分类的id
    categoryLevel: 3, //因为服务器也需要区分几级id
  }
},

赋值 浅复制 深复制

直接赋值 浅复制 深复制

//按需引入lodash当中的深拷贝
import cloneDeep from "lodash/cloneDeep";
this.attrInfo = row  // 直接赋值
this.attrInfo = {...row} //浅复制
//由于数据结构当中存在对象里面套数组,数组里面有套对象,因此需要使用深拷贝解决这类问题
this.attrInfo = cloneDeep(row) 

在这里插入图片描述
1、直接赋值更改数据点击取消会将所有的值更改
在这里插入图片描述
2、浅复制更改数据点击取消会将所有的值更改
在这里插入图片描述

解决;由于数据结构当中存在对象里面套数组,数组里面有套对象,因此需要使用深拷贝解决这类问题

18、点击一条添加一条

主要是尚品汇(后台项目)_第20张图片

<el-form-item >
            <span slot="label">
              <el-tooltip
                content="图片"
                placement="top"
              >
                <i class="el-icon-question">i>
              el-tooltip>
              图片
            span>
            <el-row v-for="(item,index) in topicList" :key="index">
              <el-col :span="4">
                <el-upload
                  class="avatar-uploader"
                  accept='image/*'
                  :action="url"
                  :show-file-list="false"
                  :on-success="(res,file)=>handleAvatarSuccess(res, file,index)"
                  :before-upload="beforeAvatarUpload"
                  :headers="headers"
                >
                  <img v-if="item.icon" :src="origin1 + item.icon" class="avatar" />
                  <i v-else class="el-icon-plus avatar-uploader-icon">i>
                el-upload>
              el-col>
              <!--  :span="9">
                <el-select
                  :key="index"
                  filterable
                  clearable
                  v-model="item.layerId"
                  placeholder="请选择"
                  style="width: 85%"
                >
                  <el-option
                    v-for="item1 in sensorData"
                    :key="item1.value"
                    :label="item1.label"
                    :value="item1.layerId"
                  >
                  el-option>
                el-select>
              el-col>
              <el-col :span="9">
                <el-select
                  :key="index"
                  v-model="item.vertical"
                  filterable
                  clearable
                  placeholder="请输入"
                  style="width: 85%"
                >
                  <el-option
                    v-for="dict in dict.eqp_layer_vertical"
                    :key="dict.value"
                    :label="dict.label"
                    :value="dict.value"
                  >
                  el-option>
                el-select>
              el-col>
              <el-col :span="2">
                <i class="el-icon-plus el-icon-circle-plus-outline" @click="pushClick(item)" v-if="item.icon && item.layerId && item.vertical"> i>
                <i class="el-icon-plus el-icon-remove-outline" @click="removeClick(index)" v-if="topicList.length > 1">i>
              el-col>
            el-row>
data(){
	return{
		topicList: [
		  {icon:'',layerId:'',vertical:''},
		],
	}
}


添加、删除

pushClick(item) {
   this.topicList.push(this.initItem? JSON.parse(JSON.stringify(this.initItem)) :{})
 },
 removeClick(index) {
   this.topicList.splice(index,1)
 },

验证不为空

if(!this.topicList[this.topicList.length-1].icon){
 this.$message.info('图片不能为空,不容许添加')
  return ;
}
if(!this.topicList[this.topicList.length-1].layerId){
 this.$message.info('层位不能为空,不容许添加')
   return ;
}
if(!this.topicList[this.topicList.length-1].vertical){
  this.$message.info('位置不能为空,不容许添加')
 return;
}

19、响应式数据 this.$set

在这里插入图片描述

主要是尚品汇(后台项目)_第21张图片

主要是尚品汇(后台项目)_第22张图片
主要是尚品汇(后台项目)_第23张图片

添加、修改属性(包括lodash当中的深拷贝)

//按需引入lodash当中的深拷贝
import cloneDeep from "lodash/cloneDeep";
//添加属性值回调
addAttrValue() {
 //向属性值的数组里面添加元素
 //attrId:是你相应的属性的id,目前而言我们是添加属性的操作,还没有相应的属性的id,目前而言带给服务器的id为undefined
 //valueName:相应的属性值的名称
  this.attrInfo.attrValueList.push({
    attrId: this.attrInfo.id, //对于修改某一个属性的时候,可以在已有的属性值基础之上新增新的属性值(新增属性值的时候,需要把已有的属性的id带上)
    valueName: "",
    flag:true 
    //flag属性:给每一个属性值添加一个标记flag,用户切换查看模式与编辑模式,好处,每一个属性值可以控制自己的模式切换
   //当前flag属性,响应式数据(数据变化视图跟着变化
  })
  this.$nextTick(()=>{ // 新增获取焦点
    this.$refs[this.attrInfo.attrValueList.length-1].focus()
  })
},
//修改某一个属性
updateAttr(row) {
  //isShowTable变为false
  this.isShowTable = false;
  //将选中的属性赋值给attrInfo
  //由于数据结构当中存在对象里面套数组,数组里面有套对象,因此需要使用深拷贝解决这类问题
  //深拷贝,浅拷贝在面试的时候出现频率很高,切记达到手写深拷贝与浅拷贝
  this.attrInfo = cloneDeep(row);
  //在修改某一个属性的时候,将相应的属性值元素添加上flag这个标记
  this.attrInfo.attrValueList.forEach((item) => {
    //这样书写也可以给属性值添加flag自动,但是会发现视图不会跟着变化(因为flag不是响应式数据)
    //因为Vue无法探测普通的新增 property,这样书写的属性并非响应式属性(数据变化视图跟这边)
    //第一个参数:对象  第二个参数:添加新的响应式属性  第三参数:新的属性的属性值
    this.$set(item, "flag", false);
  });
},

属性值修改

<el-button
  type="primary"
  icon="el-icon-plus"
  @click="addAttrValue"
  :disabled="!attrInfo.attrName"
  >添加属性值el-button
>
<el-button @click="isShowTable = true">取消el-button>
<el-table
  style="width: 100%; margin: 20px 0px"
  border
  :data="attrInfo.attrValueList"
>
  <el-table-column align="center" type="index" label="序号" width="80">
  el-table-column>
  <el-table-column width="width" prop="prop" label="属性值名称">
    <template slot-scope="{ row, $index }">
      
      <el-input
        v-model="row.valueName"
        placeholder="请输入属性值名称"
        size="mini"
        v-if="row.flag"
        @blur="toLook(row)"
        @keyup.native.enter="toLook(row)"
        :ref="$index"
      >el-input>
      <span
        v-else
        @click="toEdit(row, $index)"
        style="display: block"
        >{{ row.valueName }}span
      >
    template>
  el-table-column>
  <el-table-column width="width" prop="prop" label="操作">
    <template slot-scope="{ row, $index }">
      
      <el-popconfirm :title="`确定删除${row.valueName}?`" @onConfirm="deleteAttrValue($index)">
        <el-button
          type="danger"
          icon="el-icon-delete"
          size="mini"
          slot="reference"
        >el-button>
      el-popconfirm>
    template>
  el-table-column>
el-table>

20、属性值 :input 焦点 ref

主要是尚品汇(后台项目)_第24张图片

//点击span的回调,变为编辑模式
toEdit(row, index) {
  row.flag = true;
  this.$nextTick(() => {
    //获取相应的input表单元素实现聚焦
    this.$refs[index].focus();
  });
},
//失却焦点的事件---切换为查看模式,展示span
toLook(row) {
  // 如果属性值为空不能作为新的属性值,需要给用户提示,让他输入一个其他的属性值
  if(row.valueName.trim()==''){
    this.$message('请你输入一个正常的属性值');
    return;
  }
   //新增的属性值不能与已有的属性值重复
  let isRepat  = this.attrInfo.attrValueList.some(item=>{
      //需要将row从数组里面判断的时候去除
      //row最新新增的属性值【数组的最后一项元素】
      //判断的时候,需要把已有的数组当中新增的这个属性值去除
      if(row!==item){
        return row.valueName==item.valueName;
      }
  });
  if(isRepat) return;
  // row:形参是当前用户添加的最新的属性值
  // 当前编辑模式变为查看模式【让input消失,显示span】
  row.flag = false;
},

主要是尚品汇(后台项目)_第25张图片

21、属性:删除 保存

主要是尚品汇(后台项目)_第26张图片
主要是尚品汇(后台项目)_第27张图片

保存 1.delete item.flag;2.属性值不能为空

主要是尚品汇(后台项目)_第28张图片

 //气泡确认框确定按钮的回调
 //最新版本的ElementUI----2.15.6,目前模板中的版本号2.13.x
 deleteAttrValue(index){
   //当前删除属性值的操作是不需要发请求的
    this.attrInfo.attrValueList.splice(index,1);
 },
 //保存按钮:进行添加属性或者修改属性的操作
 async addOrUpdateAttr(){
   //整理参数:1,如果用户添加很多属性值,且属性值为空的不应该提交给服务器
   //提交给服务器数据当中不应该出现flag字段
   this.attrInfo.attrValueList = this.attrInfo.attrValueList.filter(item=>{
       //过滤掉属性值不是空的
       if(item.valueName!=''){
         //删除掉flag属性
         delete item.flag;
         return true;
       }
   })
      //发请求
     let res = await this.$API.attr.reqAddOrUpdateAttr(this.attrInfo);
     if(res.code== 200){
     //展示平台属性的信号量进行切换
     this.isShowTable = true;
     //提示消失
     this.$message({type:'success',message:'保存成功'});
     //保存成功以后需要再次获取平台属性进行展示
     this.getAttrList();
   }
}

22、三级联动

在这里插入图片描述

import request from '@/utils/request';

// 获取SPU列表数据的接口
// /admin/product/{page}/{limit}   get   page limit category3Id
export const reqSpuList = (page,limit,category3Id)=>request({
  url:`/admin/product/${page}/${limit}`,
  method:'get',
  params:{category3Id}
})

选择一级,其他两级置空

getCategoryId({ categoryId, level }) {
  if (level == 1) {
    this.category1Id = categoryId;
    this.category2Id = "";
    this.category3Id = "";
  } else if (level == 2) {
    this.category2Id = categoryId;
    this.category3Id = "";
  } else {
    this.category3Id = categoryId;
    this.getSpuList();
  }
},
async getSpuList(pages = 1) {
  this.page = pages;
  const { page, limit, category3Id } = this;
  let res = await this.$API.spu3.reqSpuList(page, limit, category3Id);
  if (res.code == 200) {
    this.total = res.data.total;
    this.records = res.data.records;
  }
},

封装三级联动组件

在这里插入图片描述

<template>
  <div>
    <!-- inline:代表的是行内表单,代表一行可以放置多个表单元素 -->
    <el-form :inline="true" class="demo-form-inline" :model="cForm">
      <el-form-item label="一级分类">
        <el-select
          placeholder="请选择"
          v-model="cForm.category1Id"
          @change="handler1"
          :disabled="show"
        >
          <el-option
            :label="c1.name"
            :value="c1.id"
            v-for="(c1, index) in list1"
            :key="c1.id"
          ></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="二级分类">
        <el-select
          placeholder="请选择"
          v-model="cForm.category2Id"
          @change="handler2"
          :disabled="show"
        >
          <el-option
            :label="c2.name"
            :value="c2.id"
            v-for="(c2, index) in list2"
            :key="c2.id"
          ></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="三级分类">
        <el-select
          placeholder="请选择"
          v-model="cForm.category3Id"
          @change="handler3"
          :disabled="show"
        >
          <el-option
            :label="c3.name"
            :value="c3.id"
            v-for="(c3, index) in list3"
            :key="c3.id"
          ></el-option>
        </el-select>
      </el-form-item>
    </el-form>
  </div>
</template>

<script>
export default {
  name: "CategorySelect",
  props: ["show"],
  data() {
    return {
      //一级分类的数据
      list1: [],
      //二级分类的数据
      list2: [],
      //三级分类的数据
      list3: [],
      //收集相应的一级二级三级分类的id
      cForm: {
        category1Id: "",
        category2Id: "",
        category3Id: "",
      },
    };
  },
  //组件挂载完毕:向服务器发请求,获取相应的一级分类的数据
  mounted() {
    //获取一级分类的数据的方法
    this.getCategory1List();
  },
  methods: {
    //获取一级分类数据的方法
    async getCategory1List() {
      //获取一级分类的请求:不需要携带参数
      let result = await this.$API.attr.reqCategory1List();
      if (result.code == 200) {
        this.list1 = result.data;
      }
    },
    //一级分类的select事件回调(当一级分类的option发生变化的时候获取相应二级分类的数据)
    async handler1() {
      //清除数据
      this.list2 = [];
      this.list3 = [];
      this.cForm.category2Id = "";
      this.cForm.category3Id = "";
      //解构出一级分类的id
      const { category1Id } = this.cForm;
      this.$emit("getCategoryId", { categoryId: category1Id, level: 1 });
      //通过一级分类的id,获取二级分类的数据
      let result = await this.$API.attr.reqCategory2List(category1Id);
      if (result.code == 200) {
        this.list2 = result.data;
      }
    },
    //二级分类的select事件回调(当二级分类的option发生变化的时候获取相应三级分类的数据)
    async handler2() {
      //清除数据
      this.list3 = [];
      this.cForm.category3Id = "";
      //结构出数据
      const { category2Id } = this.cForm;
      this.$emit("getCategoryId", { categoryId: category2Id, level: 2 });
      let result = await this.$API.attr.reqCategory3List(category2Id);
      if (result.code == 200) {
        this.list3 = result.data;
      }
    },
    //三级分类的事件回调
    handler3() {
      //获取三级分类的id
      const { category3Id } = this.cForm;
      this.$emit("getCategoryId", { categoryId: category3Id, level: 3 });
    },
  },
};
</script>

<style scoped>
</style>

23、跨组件通信:vue中的 v-bind="$attrs" v-on="$listeners"

主要是尚品汇(后台项目)_第29张图片
来源:https://juejin.cn/post/7009114318750875685

按钮组件(element -ui自带提示功能,这里为了演示跨组件通信)

必须使用v-bind,不能够简写为:
必须使用v-on,不能简写为@
props接收title属性时,$attrs就接收不到title属性,只能接收剩下的属性值
$listeners监听事件

<template>
  <a :title="title" style="margin:10px">
    <el-button v-bind="$attrs" v-on="$listeners">el-button>
  a>
template>

<script>
export default {
  name: "HintButton",
  props: ["title"]
};
script>

使用

<hint-button
type="success"
icon="el-icon-plus"
size="mini"
title="添加sku"
@click="addSku"
></hint-button>

24、中国标准时间转化yyyy-mm-dd

const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
this.st = this.formatDate(start, "yyyy-MM-dd");
this.et = this.formatDate(end, "yyyy-MM-dd");

formatDate(date, fmt) {
  if (/(y+)/.test(fmt)) {
    fmt = fmt.replace(
      RegExp.$1,
      (date.getFullYear() + "").substr(4 - RegExp.$1.length)
    );
  }
  let o = {
    "M+": date.getMonth() + 1,
    "d+": date.getDate(),
    "h+": date.getHours(),
    "m+": date.getMinutes(),
    "s+": date.getSeconds(),
  };
  for (let k in o) {
    if (new RegExp(`(${k})`).test(fmt)) {
      let str = o[k] + "";
      fmt = fmt.replace(
        RegExp.$1,
        RegExp.$1.length === 1 ? str : this.padLeftZero(str)
      );
    }
  }
  return fmt;
},
padLeftZero(str) {
  return ("00" + str).substr(str.length);
},

25、sync属性修饰符

主要是尚品汇(后台项目)_第30张图片

两种方法

主要是尚品汇(后台项目)_第31张图片

使用: child child2 组件一样
注意使用update:money money为变量

主要是尚品汇(后台项目)_第32张图片

26、vue 复制粘贴插件 vue-clipboard2


下载插件

npm install vue-clipboard2 --save

mian.js引入注册

import VueClipboard from 'vue-clipboard2'
Vue.use(VueClipboard)

示例使用

<el-table-column width="300" property="authCode" :show-overflow-tooltip="true" label="鉴权码">el-table-column>
<el-table-column label="操作" width="150">
  <template slot-scope="scope">
    <el-button v-clipboard:copy="scope.row.authCode" v-clipboard:success="onCopy" v-clipboard:error="onError" type="text" size="small">复制el-button>
    <el-button @click="copy(scope.row,scope.$index)"  type="text" size="small">复制一行el-button>
  template>
el-table-column>
onCopy(e) {
  if(e.text){
    this.$message({
      message: `复制${e.text}成功`,
      type: 'success'
    });
  }
},
onError(e) {
  this.$message({
    message: '复制失败',
    type: 'error'
  });
},
 // 单个复制一行(会出现一模一样的一行数据,tabledata:表格数据,)
copy(val, index) {
	this.tabledata.splice(index, 0,JSON.parse(JSON.stringify(val)))
},  

27、el-upload 照片墙

主要是尚品汇(后台项目)_第33张图片

action:图片上传的地址
list-type: 文件列表的类型
on-preview:图片预览的时候会触发
on-remove:当删除图片的时候会触发
file-list:照片墙需要展示的数据【数组:数组里面的元素务必要有name(如果没有name可以用uid代替)、url属性。】
on-preview:图片预览功能
on-remove:删除图片的时候会触发

<el-upload
  action="/dev-api/admin/product/fileUpload"
  list-type="picture-card"
  :on-preview="handlePictureCardPreview"
  :on-remove="handleRemove"
  :on-success="handlerSuccess"
  :file-list="spuImageList">
  <i class="el-icon-plus">i>
el-upload>
<el-dialog :visible.sync="dialogVisible">
  <img width="100%" :src="dialogImageUrl" alt="">
el-dialog>
<script>
  export default {
    data() {
      return {
        dialogImageUrl: '',
        dialogVisible: false,
        spuImageList:[]
      };
    },
    methods: {}
  }
script>
// 删除
handleRemove(file, fileList) {
 // file参数:代表的是删除的那个张图片
 // fileList:照片墙删除某一张图片以后,剩余的其他的图片
 // 收集照片墙图片的数据
 this.spuImageList = fileList;
 // `注意` 对于已有的图片【照片钱中显示的图片:有name、url字段的】,因为照片墙显示数据务必要有这两个属性
 // 对于服务器而言,不需要name、url字段,将来对于有的图片的数据在提交给服务器的时候,需要处理的
},
// 预览
handlePictureCardPreview(file) {
 // 将图片地址赋值给这个属性
  this.dialogImageUrl = file.url;
  //对话框显示
  this.dialogVisible = true;
},
handlerSuccess(response, file, fileList){
	// 收集图片的信息
    this.spuImageList = fileList;
},


修改回显图片: 处理数据方式1

//获取spu图片的数据
let spuImageResult = await this.$API.spu.reqSpuImageList(spu.id);
if (spuImageResult.code == 200) {
  let listArr = spuImageResult.data;
  //由于照片墙显示图片的数据需要数组,数组里面的元素需要有name与url字段
  //需要把服务器返回的数据进行修改
  listArr.forEach((item) => {
    item.name = item.imgName;
    item.url = item.imgUrl;
  });
  //把整理好的数据赋值给
  this.spuImageList = listArr;
}

处理数据方式2

//由于照片墙显示图片的数据需要数组,数组里面的元素需要有name与url字段
 //需要把服务器返回的数据进行修改
this.spuImageList = spuImageResult.data.map(i=>{
   return{
     ...i,
     name :i.imgName,
  	 url :i.imgUrl,
   }
 });
 

28、 转json工具

主要是尚品汇(后台项目)_第34张图片

地址:https://toolgg.com/excel-to-json.html

29、 获取对象的key与value

https://blog.csdn.net/GongWei_/article/details/109670992

 datas.Sheet.forEach((element,index) => {
    this.dataList.push( Object.values(element))
    if(index==0){
      this.header = Object.keys(element)
    }
  });
  console.log(this.header);
  console.log(JSON.stringify(this.dataList));

30、 过滤数组 filter

已知:全部属性列表 (this.saleAttrList) 、表格属性列表 (this.spu.spuSaleAttrList)
未知:未选择属性列表 (下拉框的列表)

主要是尚品汇(后台项目)_第35张图片

 computed: {
    //计算出还未选择的销售属性
    unSelectSaleAttr() {
      //整个平台的销售属性一共三个:尺寸、颜色、版本 ----saleAttrList
      //当前SPU拥有的属于自己的销售属性SPU.spuSaleAttrList  ----颜色
      //数组的过滤方法,可以从已有的数组当中过滤出用户需要的元素,并未返回一个新的数据
      let result = this.saleAttrList.filter((item) => {
        //every数组的方法,会返回一个布尔值【真,假的】
        return this.spu.spuSaleAttrList.every((i) => {
          return item.name != i.saleAttrName;
        });
      });
      return result;
    },
  },

31、 el-select

v-model:收集数据【:value中的数据】
:label:input框显示的数据

在这里插入图片描述

 <el-select
  :placeholder="`还有${unSelectSaleAttr.length}未选择`"
  v-model="attrIdAndAttrName"
>
  <el-option
    :label="unselect.name"
    :value="`${unselect.id}:${unselect.name}`"
    v-for="(unselect, index) in unSelectSaleAttr"
    :key="unselect.id"
  ></el-option>
</el-select> 

32、表格: input 与button 切换

select选择添加一条销售属性,对应的表格中多一条销售属性数据。添加之后,清空select值

在这里插入图片描述

:value=“${unselect.id}:${unselect.name}” 可以使用拼接的形式收集想要的数据

<el-select
  :placeholder="`还有${unSelectSaleAttr.length}未选择`"
  v-model="attrIdAndAttrName"
>
  <el-option
    :label="unselect.name"
    :value="`${unselect.id}:${unselect.name}`"
    v-for="(unselect, index) in unSelectSaleAttr"
    :key="unselect.id"
  >el-option>
el-select>
<el-button
  type="primary"
  icon="el-icon-plus"
  :disabled="!attrIdAndAttrName"
  @click="addSaleAttr"
  >添加销售属性el-button
>

通过将收集的数据分割、解构得到对应的属性

在这里插入图片描述

//添加新的销售属性
addSaleAttr() {
	//已经收集需要添加的销售属性的信息
	//把收集到的销售属性数据进行分割
	const [baseSaleAttrId, saleAttrName] = this.attrIdAndAttrName.split(":");
	//向SPU对象的spuSaleAttrList属性里面添加新的销售属性
	let newSaleAttr = {
	  baseSaleAttrId,
	  saleAttrName,
	  spuSaleAttrValueList: [],
	};
	//添加新的销售属性
	this.spu.spuSaleAttrList.push(newSaleAttr);
	//清空数据
	this.attrIdAndAttrName = "";
},

主要是尚品汇(后台项目)_第36张图片

tag inpt button

<el-table-column prop="prop" label="属性值名称列表" width="width">
 <template slot-scope="{ row, $index }">
   
   <el-tag
     :key="tag.id"
     v-for="(tag, index) in row.spuSaleAttrValueList"
     closable
     :disable-transitions="false"
     @close="row.spuSaleAttrValueList.splice(index, 1)"
     >{{ tag.saleAttrValueName }}el-tag
   >
   
   <el-input
     class="input-new-tag"
     v-if="row.inputVisible"
     v-model="row.inputValue"
     ref="saveTagInput"
     size="small"
     @blur="handleInputConfirm(row)"
   >
   el-input>
   <el-button
     v-else
     class="button-new-tag"
     size="small"
     @click="addSaleAttrValue(row)"
     >添加el-button
   >
 template>
el-table-column>

v-if="row.inputVisible" 用来显示隐藏input。表格中本没有inputVisible这个字段,需要动态添加,在给后台发送数据的时候记得去掉
注意:不能在data中单独定义,表格中有多条属性时,点击其中一个添加按钮显示所有input切换现象。

@close="row.spuSaleAttrValueList.splice(index, 1)"删除逻辑处理。

   //添加按钮的回调
   addSaleAttrValue(row) {
     //点击销售属性值当中添加按钮的时候,需要有button变为input,通过当前销售属性的inputVisible控制
     //挂载在销售属性身上的响应式数据inputVisible,控制button与input切换
     this.$set(row, "inputVisible", true);
     //通过响应式数据inputValue字段收集新增的销售属性值
     this.$set(row, "inputValue", "");
   },
   //el-input失却焦点的事件
   handleInputConfirm(row) {
     //解构出销售属性当中收集数据
     const { baseSaleAttrId, inputValue } = row;
     //新增的销售属性值的名称不能为空
     if (inputValue.trim() == "") {
       this.$message("属性值不能为空");
       return;
     }
     //属性值不能重复,这里也可以用some
     // let result = row.spuSaleAttrValueList.every(
     //   (item) => item.saleAttrValueName != inputValue
     // );
     // if (!result) return;
     // 或者
     let res = row.spuSaleAttrValueList.some(i=>{
       return i.saleAttrName == inputValue
     })
     if(res) return
     //新增的销售属性值
     let newSaleAttrValue = { baseSaleAttrId, saleAttrValueName: inputValue };
     //新增
     row.spuSaleAttrValueList.push(newSaleAttrValue);
     //修改inputVisible为false,不就显示button
     row.inputVisible = false;
   },
   


保存(子组件)
flag: this.spu.id ? "修改" : "添加", 点击添加返回第一页,修改返回修改数据的那一页

	 //保存按钮的回调
    async addOrUpdateSpu() {
      //整理参数:需要整理照片墙的数据
      //携带参数:对于图片,需要携带imageName与imageUrl字段
      this.spu.spuImageList = this.spuImageList.map((item) => {
        return {
          imageName: item.name,
          imageUrl: (item.response && item.response.data) || item.url,
        };
      });
      //发请求
      let result = await this.$API.spu.reqAddOrUpdateSpu(this.spu);
      if (result.code == 200) {
        //提示
        this.$message({ type: "success", message: "保存成功" });
        //通知父组件回到场景0
        this.$emit("changeScene", {
          scene: 0,
          flag: this.spu.id ? "修改" : "添加",  
        });
      }
      //清除数据
      Object.assign(this._data, this.$options.data());
    },
    //取消按钮
    cancel() {
      //取消按钮的回调,通知父亲切换场景为0
      this.$emit("changeScene", { scene: 0, flag: "" });
      //清理数据
      //Object.assign:es6中新增的方法可以合并对象
      //组件实例this._data,可以操作data当中响应式数据
      //this.$options可以获取配置对象,配置对象的data函数执行,返回的响应式数据为空的
      Object.assign(this._data, this.$options.data());
    },

父组件

changeScene({ scene, flag }) {
  //flag这个形参为了区分保存按钮是添加还是修改
  //切换结构(场景)
  this.scene = scene;
  //子组件通知父组件切换场景,需要再次获取SPU列表的数据进行展示
  if (flag == "修改") {
    this.getSpuList(this.page);
  } else {
    this.getSpuList();
  }
},

33、this._data, this.$options 清空数据;

可以用来清空数据:当你保存或取消的时候可以清空表单数据,保证点击新增的时候,表单数据是空的

在这里插入图片描述
主要是尚品汇(后台项目)_第37张图片
主要是尚品汇(后台项目)_第38张图片

34、el-cascader


element el-cascader动态加载数据 (多级联动,落地方案)https://ouyang.blog.csdn.net/article/details/116765409?spm=1001.2014.3001.5502

35、spu新增、修改

新增、修改接口

//修改SPU||添加SPU:对于修改或者添加,携带给服务器参数大致一样的,唯一的区别就是携带的参数是否带id
export const reqAddOrUpdateSpu = (spuInfo) => {
    //携带的参数带有id----修改spu
    if (spuInfo.id) {
        return request({ url: '/admin/product/updateSpuInfo', method: 'post', data: spuInfo });
    } else {
        //携带的参数不带id---添加SPU
        return request({ url: '/admin/product/saveSpuInfo', method: 'post', data: spuInfo });
    }
}

36、合并数组; ...运算符

let arr =  [1,2,3]
let arr1 = [4,5,6]
let allArr = [...arr,...arr1]
// allArr [1,2,3,4,5,6]

37、spu收集默认图片:排他

后端需要收集的是默认的图片地址
获取到用户选中图片的信息数据,需要注意,当前收集的数据当中,缺少isDefault字段,也就是控制显示默认还是设置默认的字段,需要前端动态添加。

主要是尚品汇(后台项目)_第39张图片

<el-table style="width: 100%" border :data="spuImageList"  @selection-change="handleSelectionChange">
  <el-table-column type="selection" prop="prop" width="80">
  el-table-column>
  <el-table-column prop="prop" label="图片" width="width">
     <template slot-scope="{row,$index}">
        <img :src="row.imgUrl" style="width:100px;height:100px">
     template>
  el-table-column>
  <el-table-column prop="imgName" label="名称" width="width">
  el-table-column>
  <el-table-column prop="prop" label="操作" width="width">
      <template slot-scope="{row,$index}">
          <el-button type="primary" v-if="!row.isDefault" @click="changeDefault(row)">设置默认el-button>
          <el-button v-else>默认el-button>
      template>
  el-table-column>
el-table>

获取数据的时候,添加字段

let result = await this.$API.spu.reqSpuImageLIst(spu.id);
if (result.code == 200) {
  let list = result.data;
  list.forEach(item=>{
      item.isDefault = false;
  });
  this.spuImageList = list;
}

收集默认的图片

//排他操作
changeDefault(row){
  //图片列表的数据的isDefault字段变为 0
  this.spuImageList.forEach(item=>{
      item.isDefault = 0;
  });
  //点击的那个图片的数据变为1
  row.isDefault = 1;
  //收集默认图片的地址
  this.skuInfo.skuDefaultImg = row.imgUrl;
},

38、spu销售属性、平台属性:动态渲染多个select框

主要是尚品汇(后台项目)_第40张图片

主要是尚品汇(后台项目)_第41张图片

销售属性、平台属性两个代码一致,这里只显示一个

<el-form-item label="销售属性">
  <el-form :inline="true" ref="form" label-width="80px">
    <el-form-item :label="saleAttr.saleAttrName" v-for="(saleAttr,index) in spuSaleAttrList" :key="saleAttr.id">
      <el-select placeholder="请选择" v-model="saleAttr.attrIdAndValueId">
        <el-option :label="saleAttrValue.saleAttrValueName" :value="`${saleAttr.id}:${saleAttrValue.id}`" v-for="(saleAttrValue,index) in saleAttr.spuSaleAttrValueList" :key="saleAttrValue.id">el-option>
      el-select>
    el-form-item>
  el-form>
el-form-item>

主要是尚品汇(后台项目)_第42张图片
目前数据被收集在skuSaleAttrValueList ,最后保存的时候需要整理收集数据在skuInfo.skuSaleAttrValueList

39、spu保存收集数据:reduce方法

reduce累加器:其中prev为初始值或者计算后的返回值(必须)、currentValue为当前元素(必须)、currentIndex为当前元素索引(可选)、arr为当前元素所属的对象(可选)、initialValue为传递给函数的初始值 不更改原数组,返回一个新的数组

array.reduce(function(prev, currentValue, currentIndex, arr){return prev}, initialValue)

案例1:数组去重

// 数组去重
var arr = [12, 34, 34, 342, 345, 34, 123, 345, 45, 12]
var newArr = arr.reduce(function (prev, next) {
    prev.indexOf(next) == -1 && prev.push(next)
    return prev
}, [])
console.log(arr)  // [12, 34, 34, 342, 345, 34, 123, 345, 45, 12]
console.log(newArr) // [12, 34, 342, 345, 123, 45]
  //初始化一个空数组,判断下一个元素是否在当前数组中,不存在则添加到当前数组中。

案例2:数组中元素求和

// 数组求和
var arr = [1, 2, 3, 4, 5]
var total = arr.reduce(function (prev, next) {
    return prev + next
}, 0)
console.log(total) // 15

将0当做reduce回调函数中的初始值,然后依次累加

案例3:求数组中最大值或最小值:

// 获取数组中最大值
var arr = [1347, 34773, 12, 345, 355, 425, 356, 34566, 79088]
var max = arr.reduce(function (prev, next) {
    return Math.max(prev, next)    // Math.min(prev, next)
}, 0)
console.log(max)

tips:initialValue为传递给函数的初始值,假如该值不存在时,则回调函数的初始值为数组中的第一项,即回调函数中的prev值

spu保存,处理数据

    async save() {
      //整理参数
      //整理平台属性
      const { attrInfoList, skuInfo, spuSaleAttrList, imageList } = this;
      //整理平台属的数据
      skuInfo.skuAttrValueList = attrInfoList.reduce((prev, item) => {
        if (item.attrIdAndValueId) {
          const [attrId, valueId] = item.attrIdAndValueId.split(":");
          prev.push({ attrId, valueId });
        }
        return prev;
      }, []);
      //整理销售属性
      skuInfo.skuSaleAttrValueList = spuSaleAttrList.reduce((prev, item) => {
        if (item.attrIdAndValueId) {
          const [saleAttrId, saleAttrValueId] =
            item.attrIdAndValueId.split(":");
          prev.push({ saleAttrId, saleAttrValueId });
        }
        return prev;
      }, []);
      //整理图片的数据
      skuInfo.skuImageList = imageList.map((item) => {
        return {
          imgName: item.imgName,
          imgUrl: item.imgUrl,
          isDefault: item.isDefault,
          spuImgId: item.id,
        };
      });
      //发请求
      let result = await this.$API.spu.reqAddSku(skuInfo);
      if (result.code == 200) {
        this.$message({ type: "success", message: "添加SKU成功" });
        this.$emit("changeScenes", 0);
      }
    },

40、合并冲突时解决问题

release/20220523-090840174456201_release_1262456_91 这是远程合并产生的冲突分支,直接拉取解决。

拉取远程分支

git fetch origin 

将远程分支复制一份到本地

git checkout -b release/20220523-090840174456201_release_1262456_91 origin/release/20220523-090840174456201_release_1262456_91

获取有冲突的文件

git merge --no-ff a174bff17ef338f89a088a329bc348814dd1ce35

41、loading加载效果

点击info按钮,发送请求获取数据,弹出数据。加Loading 加载效果。

在这里插入图片描述
主要是尚品汇(后台项目)_第43张图片

问题:
1、点击第一个info按钮会出现loading 效果,关闭点击其他的不在出现。
2、快速切换info按钮,会先回显之前的数据然后清空,再回显数据。

解决:v-loading=“loading” 指令
1、:before-close=“close” 点击X之前的事件更改属性( 解决问题1、2)
或者2、点击info直接将loading属性变为真( 解决问题1)

 <el-dialog :before-close="close"/>
close(done) {
  //loading属性再次变为真
  this.loading = true;
  //清除sku列表的数据
  this.skuList = [];
  //管理对话框
  done();
},

42、深度选择器

深度选择器
原生的css 使用>>>
less 使用 /deep/
scss 使用::v-deep

主要是尚品汇(后台项目)_第44张图片

子组件的根节点div拥有父组件h3的css样式,但无法影响子组件div中h3的样式

主要是尚品汇(后台项目)_第45张图片

子组件的根节点h3拥有父组件h3的css样式。

主要是尚品汇(后台项目)_第46张图片

如果父组件(scoped)要影响子組件中的样式,要使用深度选择器

43、栅格布局与轮播图

:show-close=“false” 不显示x


<el-drawer :visible.sync="show" :show-close="false" size="50%">
  <el-row>
     <el-col :span="5">商品图片el-col>
     <el-col :span="16">
       <div style="width: 450px; height: 450px; border: 1px #eee solid">
         <el-carousel height="450px">
           <el-carousel-item
             v-for="item in skuInfo.skuImageList"
             :key="item.id"
           >
             <img :src="item.imgUrl" style="width: 100%; height: 100%" />
           el-carousel-item>
         el-carousel>
       div>
     el-col>
   el-row>
el-drawer>

主要是尚品汇(后台项目)_第47张图片
在这里插入图片描述

直接在style scoped 修改样式是不能实现的
方法1、直接在没有scoped 的style 中修改
方法2、使用深度选择器



44、数据可视化文档

主要是尚品汇(后台项目)_第48张图片
主要是尚品汇(后台项目)_第49张图片

https://gitee.com/jch1011/projects
git clone https://gitee.com/jch1011/data-visualization-code.git
npm i
npm run dev
运行即可

45、菜单权限与 vuex===》router.addRoutes动态添加路由

点击登录按钮获取token信息

// 登录页
<el-button :loading="loading" type="primary" @click.native.prevent="handleLogin">登录el-button>

派发一个action:user/login

//登录业务:发请求,带着用户名与密码给服务器(成功与失败)
handleLogin() {
   //这里是在验证表单元素(用户名与密码)的是否符合规则
   this.$refs.loginForm.validate(valid => {
     //如果符合验证规则
     if (valid) {
       //按钮会有一个loading效果
       this.loading = true;
       //派发一个action:user/login,带着用户名与密码的载荷
       this.$store.dispatch('user/login', this.loginForm).then(() => {
         //登录成功进行路由的跳转
         this.$router.push({ path: this.redirect || '/' });
         //loading效果结束
         this.loading = false
       }).catch(() => {
         this.loading = false
       })
     } else {
       console.log('error submit!!')
       return false
     }
   })
 }

getInfo:通过token,获取用户信息。

主要是尚品汇(后台项目)_第50张图片

use.js

//引入登录|退出登录|获取用户信息的接口函数
import { login, logout, getInfo } from '@/api/user'
// 获取token|设置token|删除token的函数
import { getToken, setToken, removeToken } from '@/utils/auth'
//路由模块当中重置路由的方法
import { anyRoutes, resetRouter, asyncRoutes, constantRoutes } from '@/router';
import router from '@/router';
import cloneDeep from 'lodash/cloneDeep'

//箭头函数
const getDefaultState = () => {
    return {
        //获取token
        token: getToken(),
        //存储用户名
        name: '',
        //存储用户头像
        avatar: '',
        //服务器返回的菜单信息【根据不同的角色:返回的标记信息,数组里面的元素是字符串】
        routes: [],
        //角色信息
        roles: [],
        //按钮权限的信息
        buttons: [],
        //对比之后【项目中已有的异步路由,与服务器返回的标记信息进行对比最终需要展示的理由】
        resultAsyncRoutes: [],
        //用户最终需要展示全部路由
        resultAllRputes: []
    }
}

const state = getDefaultState()

//唯一修改state的地方
const mutations = {
    //重置state
    RESET_STATE: (state) => {
        Object.assign(state, getDefaultState())
    },
    //存储token
    SET_TOKEN: (state, token) => {
        state.token = token
    },
    //存储用户信息
    SET_USERINFO: (state, userInfo) => {
        //用户名
        state.name = userInfo.name;
        //用户头像
        state.avatar = userInfo.avatar;
        //菜单权限标记
        state.routes = userInfo.routes;
        //按钮权限标记
        state.buttons = userInfo.buttons;
        //角色
        state.roles = userInfo.roles;
    },
    //最终计算出的异步路由
    SET_RESULTASYNCROUTES: (state, asyncRoutes) => {
        //vuex保存当前用户的异步路由,注意,一个用户需要展示完成路由:常量、异步、任意路由
        state.resultAsyncRoutes = asyncRoutes;
        //计算出当前用户需要展示所有路由
        state.resultAllRputes = constantRoutes.concat(state.resultAsyncRoutes, anyRoutes);
        //给路由器添加新的路由
        router.addRoutes(state.resultAllRputes)
    }
}


//定义一个函数:两个数组进行对比,对比出当前用户到底显示哪些异步路由
const computedAsyncRoutes = (asyncRoutes, routes) => {
    //过滤出当前用户【超级管理|普通员工】需要展示的异步路由
    return asyncRoutes.filter(item => {
        //数组当中没有这个元素返回索引值-1,如果有这个元素返回的索引值一定不是-1 
        if (routes.indexOf(item.name) != -1) {
            //递归:别忘记还有2、3、4、5、6级路由
            if (item.children && item.children.length) {
                item.children = computedAsyncRoutes(item.children, routes);
            }
            return true;
        }
    })
}

//actions
const actions = {
    //这里在处理登录业务
    async login({ commit }, userInfo) {
        //解构出用户名与密码
        const { username, password } = userInfo;
        let result = await login({ username: username.trim(), password: password });
        //注意:当前登录请求现在使用mock数据,mock数据code是20000
        if (result.code == 20000) {
            //vuex存储token
            commit('SET_TOKEN', result.data.token);
            //本地持久化存储token
            setToken(result.data.token);
            return 'ok';
        } else {
            return Promise.reject(new Error('faile'));
        }
    },

    //获取用户信息
    getInfo({ commit, state }) {
        return new Promise((resolve, reject) => {
            getInfo(state.token).then(response => {
                //获取用户信息:返回数据包含:
                // 用户名name、用户头像avatar、routes[返回的标志:不同的用户应该展示哪些菜单的标记]、roles(用户角色信息)、buttons【按钮的信息:按钮权限用的标记】
                const { data } = response;
                //vuex存储用户全部的信息
                commit('SET_USERINFO', data);
                commit('SET_RESULTASYNCROUTES', computedAsyncRoutes(cloneDeep(asyncRoutes), data.routes));
                resolve(data)
            }).catch(error => {
                reject(error)
            })
        })
    },

    // user logout
    logout({ commit, state }) {
        return new Promise((resolve, reject) => {
            logout(state.token).then(() => {
                removeToken() // must remove  token  first
                resetRouter()
                commit('RESET_STATE')
                resolve()
            }).catch(error => {
                reject(error)
            })
        })
    },

    // remove token
    resetToken({ commit }) {
        return new Promise(resolve => {
            removeToken() // must remove  token  first
            commit('RESET_STATE')
            resolve()
        })
    }
}

export default {
    namespaced: true,
    state,
    mutations,
    actions
}

拆分use.js功能

state

//箭头函数
const getDefaultState = () => {
    return {
        //获取token
        token: getToken(),
        //存储用户名
        name: '',
        //存储用户头像
        avatar: '',
        //服务器返回的菜单信息【根据不同的角色:返回的标记信息,数组里面的元素是字符串】
        routes: [],
        //角色信息
        roles: [],
        //按钮权限的信息
        buttons: [],
        //对比之后【项目中已有的异步路由,与服务器返回的标记信息进行对比最终需要展示的理由】
        resultAsyncRoutes: [],
        //用户最终需要展示全部路由
        resultAllRputes: []
    }
}

const state = getDefaultState()

获取用户信息

getInfo({commit,state}){
	return new Promise(resolve, reject)=>{
		getInfo(state.token).then(res=>{
			//获取用户信息:返回数据包含:
            // 用户名name、用户头像avatar、routes[返回的标志:不同的用户应该展示哪些菜单的标记]、roles(用户角色信息)、buttons【按钮的信息:按钮权限用的标记】
            const { data } = response;
            //vuex存储用户全部的信息
            commit('SET_USERINFO', data);
            commit('SET_RESULTASYNCROUTES', computedAsyncRoutes(cloneDeep(asyncRoutes), data.routes));
            resolve(data)
		}).catch(error => {
            reject(error)
        })
	}
}

异步路由后台返回的路由进行对比出当前用户到底显示哪些异步路由

//定义一个函数:两个数组进行对比,对比出当前用户到底显示哪些异步路由
const computedAsyncRoutes = (asyncRoutes, routes) => {
    //过滤出当前用户【超级管理|普通员工】需要展示的异步路由
    return asyncRoutes.filter(item => {
        //数组当中没有这个元素返回索引值-1,如果有这个元素返回的索引值一定不是-1 
        if (routes.indexOf(item.name) != -1) {
            //递归:别忘记还有2、3、4、5、6级路由
            if (item.children && item.children.length) {
                item.children = computedAsyncRoutes(item.children, routes);
            }
            return true;
        }
    })
}

getInfo 的获取的用户信息

在这里插入图片描述

vuex保存当前用户的异步路由,注意,一个用户需要展示完成路由:常量、异步、任意路由
router.addRoutes(state.resultAllRputes) 动态添加路由
mutations

//唯一修改state的地方
const mutations = {
    //重置state
    RESET_STATE: (state) => {
        Object.assign(state, getDefaultState())
    },
    //存储token
    SET_TOKEN: (state, token) => {
        state.token = token
    },
    //存储用户信息
    SET_USERINFO: (state, userInfo) => {
        //用户名
        state.name = userInfo.name;
        //用户头像
        state.avatar = userInfo.avatar;
        //菜单权限标记
        state.routes = userInfo.routes;
        //按钮权限标记
        state.buttons = userInfo.buttons;
        //角色
        state.roles = userInfo.roles;
    },
    //最终计算出的异步路由
    SET_RESULTASYNCROUTES: (state, asyncRoutes) => {
        //vuex保存当前用户的异步路由,注意,一个用户需要展示完成路由:常量constantRoutes、异步resultAsyncRoutes、任意路由anyRoutes
        state.resultAsyncRoutes = asyncRoutes;
        //计算出当前用户需要展示所有路由(用数组进行拼接)
        state.resultAllRputes = constantRoutes.concat(state.resultAsyncRoutes, anyRoutes);
        //动态给路由器添加新的路由
        router.addRoutes(state.resultAllRputes)
    }
}

router=>index.js

//引入Vue|Vue-router
import Vue from 'vue'
import Router from 'vue-router'

//使用路由插件
Vue.use(Router)

/* 引入最外层骨架的一级路由组件*/
import Layout from '@/layout'


//路由的配置:为什么不同用户登录我们的项目,菜单(路由)都是一样的?
//因为咱们的路由‘死的’,不管你是谁,你能看见的,操作的菜单都是一样的
//需要把项目中的路由进行拆分

//常量路由:就是不关用户是什么角色,都可以看见的路由
//什么角色(超级管理员,普通员工):登录、404、首页
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' }
    }]
  },
]

//异步理由:不同的用户(角色),需要过滤筛选出的路由,称之为异步路由
//有的用户可以看见测试管理、有的看不见
export const asyncRoutes = [
  {
  name: 'Acl',
  path: '/acl',
  component: Layout,
  redirect: '/acl/user/list',
  meta: {
    title: '权限管理',
    icon: 'el-icon-lock'
  },
  children: [
    {
      name: 'User',
      path: 'user/list',
      component: () => import('@/views/acl/user/list'),
      meta: {
        title: '用户管理',
      },
    },
    {
      name: 'Role',
      path: 'role/list',
      component: () => import('@/views/acl/role/list'),
      meta: {
        title: '角色管理',
      },
    },
    {
      name: 'RoleAuth',
      path: 'role/auth/:id',
      component: () => import('@/views/acl/role/roleAuth'),
      meta: {
        activeMenu: '/acl/role/list',
        title: '角色授权',
      },
      hidden: true,
    },
    {
      name: 'Permission',
      path: 'permission/list',
      component: () => import('@/views/acl/permission/list'),
      meta: {
        title: '菜单管理',
      },
    },
  ]
},
];


//任意路由:当路径出现错误的时候重定向404
export const anyRoutes ={ path: '*', redirect: '/404', hidden: true };


const createRouter = () => new Router({
  // mode: 'history', // require service support
  scrollBehavior: () => ({ y: 0 }),
  //因为注册的路由是‘死的’,‘活的’路由如果根据不同用户(角色)可以展示不同菜单
  routes: constantRoutes
})

const router = createRouter()

// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter() {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher // reset router
}

export default router

修改左侧菜单的路由数组

主要是尚品汇(后台项目)_第51张图片

computed: {
	//应该替换为仓库中已经计算好的需要展示的全部路由
    routes() {
      //sliderbar:需要遍历的应该是仓库计算完毕的全部路由
      return this.$store.state.user.resultAllRputes;
    },
}

46、 elementUI tree

tree树形控件获取父节点ID的实例: 修改源码就可,一般不要轻易修改,会造成其他页面破坏

vue elementUI tree树形控件获取父节点ID的实例
   修改源码:
   情况1: element-ui没有实现按需引入打包
     node_modules\element-ui\lib\element-ui.common.js    25382行修改源码  去掉 'includeHalfChecked &&'
     // if ((child.checked || includeHalfChecked && child.indeterminate) && (!leafOnly || leafOnly && child.isLeaf)) {
     if ((child.checked || child.indeterminate) && (!leafOnly || leafOnly && child.isLeaf)) {
   情况2: element-ui实现了按需引入打包
     node_modules\element-ui\lib\tree.js    1051行修改源码  去掉 'includeHalfChecked &&'
     // if ((child.checked || includeHalfChecked && child.indeterminate) && (!leafOnly || leafOnly && child.isLeaf)) {
     if ((child.checked || child.indeterminate) && (!leafOnly || leafOnly && child.isLeaf)) {

将子节点以及角色id传过去

<template>
  <div>
    <el-input disabled :value="$route.query.roleName"></el-input>
    <el-tree
      style="margin: 20px 0"
      ref="tree"
      :data="allPermissions"
      node-key="id"
      show-checkbox
      default-expand-all
      :props="defaultProps"
    />
    <el-button :loading="loading" type="primary" @click="save">保存</el-button>
    <el-button @click="$router.replace({ name: 'Role' })">取消</el-button>
  </div>
</template>
<script>
export default {
  name: "roleAuth",

  data() {
    return {
      loading: false, // 用来标识是否正在保存请求中的标识, 防止重复提交
      allPermissions: [], // 所有
      defaultProps: {
        children: "children",
        label: "name",
      },
    };
  },

  created() {
    this.init();
  },

  methods: {
    /* 
      初始化
      */
    init() {
      const roleId = this.$route.params.id;
      this.getPermissions(roleId);
    },

    /* 
      获取指定角色的权限列表
      */
    getPermissions(roleId) {
      this.$API.permission.toAssign(roleId).then((result) => {
        const allPermissions = result.data.children;
        this.allPermissions = allPermissions;
        const checkedIds = this.getCheckedIds(allPermissions);
        this.$refs.tree.setCheckedKeys(checkedIds);
      });
    },
    /* 
      得到所有选中的id列表,只有选中的子节点id
      */
    getCheckedIds(auths, initArr = []) {
      return auths.reduce((pre, item) => {
        if (item.select && item.level === 4) {
          console.log();
          pre.push(item.id);
        } else if (item.children) {
          this.getCheckedIds(item.children, initArr);
        }
        return pre;
      }, initArr);
    },
    /* 
      保存权限列表
      */
    save() {
      var ids = this.$refs.tree.getCheckedKeys().join(",");
      this.loading = true;
      this.$API.permission
        .doAssign(this.$route.params.id, ids)
        .then((result) => {
          this.loading = false;
          this.$message.success(result.$message || "分配权限成功");
          // 必须在跳转前获取(跳转后通过this获取不到正确的数据了)
          const roleName = this.$route.query.roleName;
          const roles = this.$store.getters.roles;
          this.$router.replace("/acl/role/list", () => {
            console.log("replace onComplete");
            // 跳转成功后, 判断如果更新的是当前用户对应角色的权限, 重新加载页面以获得最新的数据
            if (roles.includes(roleName)) {
              window.location.reload();
            }
          });
        });
    },
  },
};
</script>

47、 数据库使用

主要是尚品汇(后台项目)_第52张图片

// 从课程中拉取id为51c9a74f-b688-4f2f-8470-7ee95882e0a8的课程
select * from course where id = '51c9a74f-b688-4f2f-8470-7ee95882e0a8'
// 从video_play_log 表中查询 课程id和时间大于5月26的
SELECT
	* 
FROM
	video_play_log 
WHERE
	course_id = '51c9a74f-b688-4f2f-8470-7ee95882e0a8' 
	AND created_time > '2022-5-26'
	
// 从课节中找课程
select * from course_unit where course_id = '51c9a74f-b688-4f2f-8470-7ee95882e0a8'

48、插槽 v-solt

主要是尚品汇(后台项目)_第53张图片

具名插槽

父组件slot="charts"

	<el-card>
      <Detail title="访问量" count="88460">
         <template slot="charts">
             <lineCharts>lineCharts>
         template>
        <template slot="footer">
          <span>日访问量 1234span>
        template>
      Detail>
     el-card>

子组件

<div>
     <div class="card-header">
        <span>{{title}}span>
     div>
     <div class="card-content">{{count}}div>
     <div class="card-charts">
      <slot name="charts">slot>
     div>
     <div class="card-footer">
        <slot name="footer">slot>
     div>
div>

49、 数据可视化

柱状图

主要是尚品汇(后台项目)_第54张图片

<template>
  <el-card class="box-card">
    <div slot="header" class="clearfix">
      <el-tabs v-model="activeName" class="all">
        <el-tab-pane label="销售额" name="first"> el-tab-pane>
        <el-tab-pane label="明细额" name="second"> el-tab-pane>
      el-tabs>
      <div class="right">
        <span @click="setDay">今日span>
        <span @click="setWeek">本周span>
        <span @click="setMonth">本月span>
        <span @click="setYear">本年span>
        <el-date-picker
          v-model="date"
          type="daterange"
          range-separator="-"
          start-placeholder="开始日期"
          end-placeholder="结束日期"
          value-format="yyyy-MM-dd"
          size="mini"
        >
        el-date-picker>
      div>
    div>
    <div>
      <el-row :gutter="10">
        <el-col :span="18">
          
          <div class="charts" ref="charts">div>
        el-col>
        <el-col :span="6" class="right">
          <h3>门店{{ title }}排名h3>
          <ul>
            <li>
              <span class="rindex">0span>
              <span>肯德基span>
              <span class="rvalue">123456span>
            li>
            <li>
              <span class="rindex">1span>
              <span>肯德基span>
              <span class="rvalue">123456span>
            li>
            <li>
              <span class="rindex">3span>
              <span>肯德基span>
              <span class="rvalue">123456span>
            li>
            <li>
              <span>4span>
              <span>肯德基span>
              <span class="rvalue">123456span>
            li>
          ul>
        el-col>
      el-row>
    div>
  el-card>
template>

dayjs 时间插件
echarts 本身无高度,给一个高度
mounted只执行一次,切换数据,echarts不在渲染,通过watch监听重新渲染数据

<script>
import dayjs from "dayjs";
import * as echarts from "echarts";
export default {
  data() {
    return {
      activeName: "first",
      date: [],
      charts: null,
      option: {},
    };
  },
  watch: {
  	//切换数据视图
    title() {
      this.option.title.text = this.title + "数据";
      this.option.xAxis = {
        data:
          this.title == "销售"
            ? ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
            : ["1", "2", "3", "4", "5", "6", "7"],
      };
      this.option.series = {
        data:
          this.title == "销售"
            ? [120, 200, 150, 80, 70, 110, 130]
            : [220, 20, 150, 80, 170, 10, 130],
      };
      this.charts.setOption(this.option);
    },
    // 初始数据
    listState() {
      this.mycharts.setOption({
        title: {
          text: this.title + "趋势",
        },
        tooltip: {
          trigger: "axis",
          axisPointer: {
            type: "shadow",
          },
        },
        grid: {
          left: "3%",
          right: "4%",
          bottom: "3%",
          containLabel: true,
        },
        xAxis: [
          {
            type: "category",
            data: this.listState.orderFullYearAxis,
            axisTick: {
              alignWithLabel: true,
            },
          },
        ],
        yAxis: [
          {
            type: "value",
          },
        ],
        series: [
          {
            name: "Direct",
            type: "bar",
            barWidth: "60%",
            data: this.listState.orderFullYear,
            color: "yellowgreen",
          },
        ],
      });
    },
  },
 // 只执行一次,切换数据,echarts不在渲染
  mounted() {
    this.initData();
  },
  computed: {
    title() {
      return this.activeName == "first" ? "销售" : "明细";
    },
    ...mapState({
      listState: (state) => state.home.list,
    }),
  },
  methods: {
  	// 时间获取
    setDay() {
      const day = dayjs().format("YYYY-MM-DD");
      this.date = [day, day];
    },
    setWeek() {
      const start = dayjs().day(1).format("YYYY-MM-DD");
      const end = dayjs().day(7).format("YYYY-MM-DD");
      this.date = [start, end];
    },
    setMonth() {
      const start = dayjs().startOf("month").format("YYYY-MM-DD");
      const end = dayjs().endOf("month").format("YYYY-MM-DD");
      this.date = [start, end];
    },
    setYear() {
      const start = dayjs().startOf("year").format("YYYY-MM-DD");
      const end = dayjs().endOf("year").format("YYYY-MM-DD");
      this.date = [start, end];
    },
    // 初始化echarts
    initData() {
      this.charts = echarts.init(this.$refs.charts);
      this.option = {
        title: {
          text: this.title + "数据",
          subtext: "Living Expenses in Shenzhen",
        },
        xAxis: {
          type: "category",
          data: [],
        },
        yAxis: {
          type: "value",
        },
        series: [
          {
            data: [],
            type: "bar",
            showBackground: true,
            backgroundStyle: {
              color: "rgba(180, 180, 180, 0.2)",
            },
            barWidth: "50%",
            color: "purple",
          },
        ],
      };
      this.option && this.charts.setOption(this.option);
    },
  },
};
</script>



饼图
主要是尚品汇(后台项目)_第55张图片

<div class="charts" ref="charts">div>
<script>
import echarts from "echarts";
export default {
  name: "",
  mounted() {
    //饼图
    let mychart = echarts.init(this.$refs.charts);
    mychart.setOption({
      title: {
        text: "视频",
        subtext: 1048,
        left: "center",
        top: "center",
      },
      tooltip: {
        trigger: "item",
      },
      series: [
        {
          name: "Access From",
          type: "pie",
          radius: ["40%", "70%"],
          avoidLabelOverlap: false,
          itemStyle: {
            borderRadius: 10,
            borderColor: "#fff",
            borderWidth: 2,
          },
          label: {
            show: true,
            position: "outsize",
          },
          labelLine: {
            show: true,
          },
          data: [
            { value: 1048, name: "视频" },
            { value: 735, name: "Direct" },
            { value: 580, name: "Email" },
            { value: 484, name: "Union Ads" },
            { value: 300, name: "Video Ads" },
          ],
        },
      ],
    });
    //绑定事件,更改饼图中间的数据以及title

    mychart.on("mouseover", (params) => {
      //获取鼠标移上去那条数据
      const { name, value } = params.data;
      //重新设置标题
      mychart.setOption({
        title: {
          text: name,
          subtext: value,
        },
      });
    });
  },
};
</script>

50、 iframe

iframe 父子交互方式

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta name="viewport" content="width=device-width,  initial-scale=1, maximum-scale=1, user-scalable=no">
  <link rel="icon" href="">
  <title>Document</title>
</head>
<body>
  <div>孩子</div>
  <div id='box'> </div>
  <button id="btn" onclick="say()">点击</button>
        
  <script>
    window.onload = function(){
      window.addEventListener('message', function(e) {       
          if (!e || !e.data) { return; }
          switch (e.data.type) {
              case 'boundFileKeys':
                    document.getElementById("box").innerHTML = e.data.data;
                    break;
          }
      }, false);    
      
    }
    function say() {
        //window.parent 用于iframe嵌套情况下寻找上级window对象
        //跳转打开新页面的情况下,获取父页面,即打开新页面的原始页面
        let parentWindow = window.parent.postMessage({ type: 'check',  data: '有,enen..你有米吗?' }, '*'); // 获取打开此页面的window对象
    }
    function getParentParam(){
      window.parent.postMessage({ type: 'hit',  data: '打你,就知道哭哭' }, '*');
    }
  </script>



</body>
</html>

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <link rel="icon" href="">
  <title>Document</title>
</head>
<body>
  <div></div>
  <div id='parentHello'></div><span id='box1'></span>
  <input type="button" onclick="parentHello()" value='sendMessage' >
  <button onclick="hit()">暴揍小孩</button>
  <span id='id2'></span>
  <iframe id='childIframe' src='./child.html' name='Iam' fr
  ameborder='1' style="width: 100%; height:100%; overflow: hidden;" scrolling='yes'></iframe>
</body>
<script>
  // window.onload = function(){
    // iframe id
    // iframe name
    // let nameIam = window.frames['Iam'].window
    // innerText
    // innerHTML
    function parentHello(){
      let childIframe = document.getElementById('childIframe')
      childIframe.contentWindow.postMessage({ type: 'boundFileKeys', data: "你好,小孩你有糖吗?" }, '*')
    }
    function hit(){
      let childIframe = document.getElementById('childIframe')
      childIframe.contentWindow.getParentParam();
    }
    window.addEventListener('message', function(e) {
      console.log(e);
      if (!e || !e.data) { return; }
      switch(e.data.type){
        case 'check':
            document.getElementById("box1").innerHTML = e.data.data;
            break;
        case 'hit':
            document.getElementById("id2").innerHTML = e.data.data;
            break;
      }
    }, false);
  // }
  
</script>
</html>

51、 文件解析使用

npm install file-saver --save

https://blog.csdn.net/qq_41067835/article/details/88798771
https://www.jb51.net/article/238106.htm#_label0

<div>
  <input type="file" id="myFile" value="上传">
div>
<button onclick='export2()'>下载button>
// 本地文件上传
  var upload = document.querySelector("#myFile");
  upload.addEventListener("change", function (event) {
    // readAsText() 读取文本文件,(可以使用Txt打开的文件)
    // readAsBinaryString(): 读取任意类型的文件,返回二进制字符串
    // readAsDataURL: 方法可以将读取到的文件编码成DataURL ,可以将资料(例如图片、excel文件)内嵌在网页之中,不用放到外部文件
    let reader = new FileReader() // 读取文件
    reader.readAsText(event.target.files[0])  //把目标文件转地址,文件来自于上传组件。
		// ....读取中
    reader.onload = function () {
      let py = document.getElementById('pyId').contentWindow
      py.importFile(reader.result);
    }
  }),
  // 本地文件下载
  export2 = function(){
    let aTag = document.createElement('a');
    let py = document.getElementById('pyId').contentWindow
    let value = py.exportFile()
    const content = value.toString();
    let blob = new Blob([content], { type: "text/plain;charset=utf-8" });
    saveAs(blob, '11' + ".py"); // 插件使用
    // 直接下载
    // aTag.download = "文件名" + ".py";
    // aTag.href = URL.createObjectURL(blob);
    // aTag.click();
    // URL.revokeObjectURL(blob);
  }

解析远程文件,通过解析url获取编辑器文本

function draw(py){
    fetch('./textpy/'+ py).then((res)=>{
      return res.text()
    }).then((text)=>{
      let py = document.getElementById('pyId').contentWindow
      py.importFile(text);
    })
  }
// 获取编辑器文本
const value = this.iframeWin.exportFile();
// 转成字符串
const content = value.toString();
// 将编辑器文本转成文件
var file = new File([content], "py");
let params = new FormData();
params.append("file", file);
// 上传给后端,获取编辑器文本对应的url: res.body.fileUrl,进行编辑或者是新增
 uploadFile(params)
.then((res) => {
  // 上传成功后提交作品信息
  this.loading = false;
  let props = {
    ...this.formInfo,
    sourceCodeFile: res.body.fileUrl,
  };
  props.id ? this.editWorkInfo(props) : this.saveProject(props);
})
.catch((err) => {
  this.loading = false;
  this.$message.warning("提交失败");
});

52、http-server 轻量服务器

安装

npm install http-server --save

控制台运行

http-server 

你可能感兴趣的:(elementui,vue.js)