7.组件框架

7.0.Element UI

js -> jquery ->jqueryui /easyUI / bootstrap UI

js -> vue -> elementUI

vue 2

https://element.eleme.cn/#/zh-CN

$ npm i element-ui -S
<!-- 引入样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<!-- 引入组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>

在 main.js 中写入以下内容:

import Vue from 'vue';

import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';

import App from './App.vue';

Vue.use(ElementUI);

new Vue({
  el: '#app',
  render: h => h(App)
});

7.1.分列

一行 24列

   <el-form-item label="标签" prop="id">    
      <el-col :span="6">
		...
      </el-col>
      <el-col align="right"  :span="3"  >标签&nbsp;&nbsp;&nbsp;&nbsp;</el-col>
      <el-col :span="6">
		...
      </el-col>
      <el-col align="right"  :span="3"  >标签&nbsp;&nbsp;&nbsp;&nbsp;</el-col>
      <el-col :span="6">
		...
      </el-col>
    </el-form-item>

7.2.上传下载

7.2.1.1.上传组件
       <!--
            list-type:  文件列表的类型	string	text/picture/picture-card	
            action :  必选参数,上传的地址	string
            :accept :  接受上传的文件类型
            :limit : 最大允许上传个数
            :on-exceed : 文件超出个数限制时的钩子
            :on-preview : 点击文件列表中已上传的文件时的钩子
            :on-remove : 文件列表移除文件时的钩子
            :before-upload : 上传文件之前的钩子
            :file-list : 上传的文件列表 array
            :auto-upload : 是否在选取文件后立即进行上传

            :data: 上传时附带的额外参数
            :multiple : 是否支持多选文件
            :show-file-list: 是否显示已上传文件列表 boolean
            :on-success: 文件上传成功时的钩子

            :headers="Myhead" 设置上传头
          -->
          <el-upload
            
            list-type="picture-card"
            ref="upload"
            :action="uploadUrl"
            :accept="acceptFileType"
            :limit="1"
            :data="{'type':'brand_logo'}"
            :on-exceed="handleExceed"
            :on-preview="handlePreview"
            :on-remove="handleRemove"
            :before-upload="beforeUpload"
            :file-list="fileList"
            :auto-upload="true"

             :headers="Myhead" 
         
            :show-file-list="showFileList"
            :on-success="handleUploadSuccess"
          >
            <i class="el-icon-plus"></i>
            <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过1MB</div>
          </el-upload>
          <el-dialog :visible.sync="dialogVisible">
            <img width="100%" :src="dialogImageUrl" alt="" />
          </el-dialog>

data中的对应属性

	  imageUrl: "",
      uploadUrl:  "http://localhost:8900/renren-fast/localUpload/uploadForType",

      uploadTemplateDialog: false,
      fileList: [],        
      uploadLoading: false,
      acceptFileType: "image/png, image/jpeg", // 设置上传文件类型
      downLoadLoading: "",

      dialogImageUrl: "",
      dialogVisible: false,

      showFileList: true,
7.2.1.2. 对应钩子函数放到methods里
      // 文件超出个数限制时的钩子
    handleExceed(files, fileList) {
      this.$message.warning("只能选择1个文件!");
    },



    // 文件列表移除文件时的钩子
    handleRemove(file, fileList) {
      //console.log(file,fileList);
    },

    // 点击文件列表中已上传的文件时的钩子 ,  在 模态窗中显示
    handlePreview(file) {
      // console.log(file);
      console.log(file.url);
      this.dialogImageUrl = file.url;
      this.dialogVisible = true;
    },

    // 上传文件之前的钩子
    beforeUpload(file) {
      var that = this;
      // console.log("file:",file)
      //文件类型
      var fileName = file.name.substring(file.name.lastIndexOf(".") + 1);
      if ( !(fileName == "png" || fileName == "jpg"  || fileName == "jpeg") ) {
        that.uploadTemplateDialog = false;
        that.$message({
          type: "error",
          showClose: true,
          duration: 3000,
          message: "文件类型不是.png/.jpg文件!",
        });
        return false;
      }

      //读取文件大小
      var fileSize = file.size;
      // console.log(fileSize);
      if (fileSize > 1048576) {
        that.uploadTemplateDialog = false;
        that.$message({
          type: "error",
          showClose: true,
          duration: 3000,
          message: "文件大于1M!",
        });
        return false;
      }

      // 上传时 的 遮罩
      that.downloadLoading = that.$loading({
        lock: true,
        text: "数据上传中...",
        spinner: "el-icon-loading",
        background: "rgba(0,0,0,0.7)",
      });

     
      return true;
    },


    //文件上传成功时的钩子
    handleUploadSuccess(res, file) {
      // console.log(res)
      console.log("上传成功..." )

      // 用于在页面回显 图片
      try {
        this.imageUrl = file.raw;
      } catch (error) {
        this.imageUrl = window.URL.createObjectURL(file.raw);
      }

      // console.log("this.imageUrl ", this.imageUrl  )

      this.downloadLoading.close();
      this.uploadLoading = false;
      this.dataForm.brandLogoPath = res.path

      this.showFileList = true;

    },
7.2.1.3. 上传头信息

写在 计算函数里

  computed: {
      Myhead: function () {
          return {token: this.$cookie.get('token')}
      }
  },
7.2.1.4. 回显

在 init 函数里 查看到 对应 的记录信息之后

   this.fileList.pop();
   this.fileList.push({name:  this.dataForm.brandLogoPath, url:  this.dataForm.brandLogoPath });
7.2.1.5.后台处理

常量 配置

public class Const {

    /**
     * 本地存储 文件所在的路径
     */
    public static final String UPLOAD_LOCAL_PATH = "D:/upload/images/webshop/";

    /**
     * 本地存储 文件存储的路径
     */
    public static final String UPLOAD_LOCAL_PATH_WEB_URL = "http://127.0.0.1:8900/renren-fast/upload/";
}

在对应的controller中

    @RequestMapping("/upload")
    @ResponseBody
    public R upload(@RequestParam("file") MultipartFile file) throws IOException {

        System.out.println("file => " + file.getOriginalFilename());
        if (file == null || file.isEmpty()) {
            System.out.println(" = " + "上传失败,请选择文件");
            return R.error("上传失败,请选择文件");
        } else {
            String path = FileUtils.saveFile(file);
            System.out.println("path = " + path);
            return R.ok().put("path", path);
        }
    }
    @RequestMapping("/uploadForType")
    @ResponseBody
    public R uploadForType(@RequestParam("type")String type, @RequestParam("file") MultipartFile file) throws IOException {

        System.out.println("type = " + type);
        System.out.println("file => " + file.getOriginalFilename());
        if (file == null || file.isEmpty()) {
            System.out.println(" = " + "上传失败,请选择文件");
            return R.error("上传失败,请选择文件");
        } else {
            String path = FileUtils.saveFileForType(file, type);
            System.out.println("path = " + path);
            return R.ok().put("path", path).put("type", type);
        }
    }

在 FileUtils 工具类中

    /**
     * @description 文件上传,保存在本地硬盘,返回文件请求路径
     * @param picFile
     * @return
     * @throws IOException
     */
    /**/
    public static String saveFile(MultipartFile picFile) throws IOException {
        String location = null;
        String destName = "";
        if(!picFile.isEmpty()){
            //得到真实路径
            String fileName = picFile.getOriginalFilename();
            String kzm = fileName.substring(fileName.lastIndexOf("."));
            //要保存的文件名
            destName = UUID.randomUUID().toString()+kzm;
            //取得上传文件存储路径
            //File directory = new File("");// 参数为空
            //String path = directory.getCanonicalPath()+"/src/main/webapp"+Const.PIC_FILE;
            String path = Const.UPLOAD_LOCAL_PATH;
            //如果上传文件存储路径不存在则创建一个
            File s2 = new File(path);
            if (s2.exists()==false) {
                s2.mkdirs();
            }
            //文件路径
            location = path+"/"+destName;
            try {
                picFile.transferTo(new File(location));
            } catch (IllegalStateException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        String url = Const.UPLOAD_LOCAL_PATH_WEB_URL + destName;
        return url;
    }

    /**
     * @description 文件上传,保存在本地硬盘,返回文件请求路径
     * @param picFile
     * @return
     * @throws IOException
     */
    /**/
    public static String saveFileForType(MultipartFile picFile, String type) throws IOException {
        String location = null;
        String destName = "";
        if(!picFile.isEmpty()){
            //得到真实路径
            String fileName = picFile.getOriginalFilename();
            String kzm = fileName.substring(fileName.lastIndexOf("."));
            //要保存的文件名
            destName = UUID.randomUUID().toString()+kzm;
            //取得上传文件存储路径
            //File directory = new File("");// 参数为空
            //String path = directory.getCanonicalPath()+"/src/main/webapp"+Const.PIC_FILE;
            String path = Const.UPLOAD_LOCAL_PATH + type + "/";
            //如果上传文件存储路径不存在则创建一个
            File s2 = new File(path);
            if (s2.exists()==false) {
                s2.mkdirs();
            }
            //文件路径
            location = path+"/"+destName;
            try {
                picFile.transferTo(new File(location));
            } catch (IllegalStateException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        String url = Const.UPLOAD_LOCAL_PATH_WEB_URL + type + "/" + destName;
        return url;
    }

7.2.2.图片显示

在后台 的项目中 要增加

其中 Const.UPLOAD_LOCAL_PATH 为 文件存储的本地路径

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/upload/**").addResourceLocations("file:" + Const.UPLOAD_LOCAL_PATH);
    }
}

只要 实体类的属性值为 : http://127.0.0.1:8900/renren-fast/upload/brand_logo/3b73aaa7-ec21-4601-9432-ecabf07750d7.jpg

        <template slot-scope="scope" >
          <img :src="scope.row.brandLogoPath"  style="width: 150px; height: 60px" />
        </template>

在 renren-fast 项目中

路径 : io.renren.config.ShiroConfig

由于 没有传递 token 还要 关闭 shiro的权限控制 ShiroConfig 类

filterMap.put("/upload/**", "anon");

7.3.下拉框

7.3.0.vue-template

  <el-select v-model="value" placeholder="请选择">
    <el-option
      v-for="item in options"
      :key="item.value"
      :label="item.label"
      :value="item.value">
    </el-option>
  </el-select>

7.3.1. 准备数据

// 在data 中增加用于 迭代的数组
data () {
      return {
        options:[],
          ...
// 在 加载完成的钩子函数中 增加调用 
created() {
    this.oneDataList();
},

   
// 在methods里增加 获取信息的方法
oneDataList() {
    console.log("请求后台");
    this.dataListLoading = true;
    this.$http({
        url: this.$http.adornUrl("/base/categoryone/listAll"),
        method: "get",
    }).then(({ data }) => {
        console.log(data)
        if (data && data.code === 0) {
            this.options = data.list;
        } else {
            this.options = [];
        }
    });
},

7.3.2. 下拉组件

因为要 同时 保存 一级分类 的 id 和 name 所以 option 的 :value 有变化 , 同时 要结合 @change 事件

 <el-select v-model="dataForm.coneName" placeholder="请选择一级分类" 
            @change="checkedOption"  value-key="coneId" >
     <el-option
                v-for="item in options"
                :key="item.coneId"
                :label="item.coneName +':' + item.coneId"
                :value="item"
                >
     </el-option>
</el-select>

7.3.3.封装数据

在methods 里 处理数据

// 改变 下拉框
checkedOption(item){
    this.dataForm.coneId = item.coneId
    this.dataForm.coneName = item.coneName
},

7.4.模态窗

通过 父子组件 + dialog 方式 完成

7.4.0.建立子组件

先在common下建立子组件 , 结构与 列表页相似

<template>
<!-- 模态窗-->
<el-dialog title="选择品牌"  width="80%"
      :close-on-click-modal="false"
      :visible.sync="visible"
    >

	<!-- 显示内容 -->    
    <el-form :inline="true" :model="whereForm" @keyup.enter.native="getDataList()">
      <el-form-item>
        <el-input    v-model="whereForm.key" placeholder="参数名" clearable></el-input>
      </el-form-item>
      <el-form-item>
        <el-button @click="getDataList()">查询</el-button>
      </el-form-item>
    </el-form>
    <el-table
      :data="dataList"
      border
      v-loading="dataListLoading"
      :highlight-current-row = "true"
      @current-change="handleCurrentChange"
      style="width: 100%;">


      <el-table-column
        prop="brandId"
        header-align="center"
        align="center"
        label="品牌主键">
      </el-table-column>
      <el-table-column
        prop="brandCnName"
        header-align="center"
        align="center"
        label="中文名">
      </el-table-column>
      <el-table-column
        prop="brandEnName"
        header-align="center"
        align="center"
        label="英文名">
      </el-table-column>
      <el-table-column
        prop="brandLogoPath"
        header-align="center"
        align="center"  width="180"
        label="logo路径">
        <template slot-scope="scope" >
          <img :src="scope.row.brandLogoPath"  style="width: 80px; height: 30px" />
        </template>
      </el-table-column>
      <el-table-column
        prop="brandWebUrl"
        header-align="center"
        align="center"
           width="220"
        label="网址">
        <template slot-scope="scope">
            <!-- <el-button type="text" size="small" >{{scope.row.brandWebUrl}}</el-button> -->
            <a :href="scope.row.brandWebUrl"  target="_blank" >{{scope.row.brandWebUrl}}</a>
        </template>
      </el-table-column>
      <el-table-column
        prop="brandNum"
        header-align="center"
        align="center"
           width="80"
        label="权重">
      </el-table-column>
      <el-table-column
        prop="brandInfo"
        header-align="center"
        align="center"
        label="说明">
      </el-table-column>
      <el-table-column
        prop="brandFoundedDate"
        header-align="center"
        align="center"
          :formatter="dateFormat"
        label="成立时间">
      </el-table-column>
    </el-table>
    <el-pagination
      @size-change="sizeChangeHandle"
      @current-change="currentChangeHandle"
      :current-page="pageIndex"
      :page-sizes="[10, 20, 50, 100]"
      :page-size="pageSize"
      :total="totalPage"
      layout="total, sizes, prev, pager, next, jumper">
    </el-pagination>


	<!--操作按钮-->
    <span slot="footer" class="dialog-footer">
      <el-button @click="visible = false">取 消</el-button>
      <el-button type="primary" @click="selectedBrand()">确 定</el-button>
    </span>

</el-dialog>
</template>

<script>

  export default {

    data () {
      return {
        currentRow: null,         
        visible: false, // 模态窗 显隐     
        whereForm: {
         key: ''
        },
        dataList: [],
        pageIndex: 1,
        pageSize: 10,
        totalPage: 0,
        dataListLoading: false,
        dataListSelections: [],
        addOrUpdateVisible: false
      }
    },


    methods: {

    /**
     * 选中 关闭模态窗
     */
    selectedBrand() {
      // console.log(this.currentRow)
      this.visible = false;
      // 触发 父组件的事件
      this.$emit("selectedBrand", this.currentRow);
    },

    
    handleCurrentChange(val) {
      this.currentRow = val;
    },

    /**
     * 初始化方法 ,  由父组件调用
     */
    init (id) {    
      this.visible = true
      this.$nextTick(() => {
        this.getDataList();
      })
    },


    dateFormat(row,column){
         
       var dT=new Date(row.brandFoundedDate);//row 表示一行数据, dateTime 表示要格式化的字段名称
       return dT.getFullYear()+"-"+(dT.getMonth()+1)+"-"+dT.getDate();
    },

   
      // 获取数据列表
      getDataList () {
        this.dataListLoading = true
        this.$http({
          url: this.$http.adornUrl('/goods/brandinfo/list'),
          method: 'get',
          params: this.$http.adornParams({
            'page': this.pageIndex,
            'limit': this.pageSize,
            'key':  this.whereForm.key
          })
        }).then(({data}) => {
          if (data && data.code === 0) {
            this.dataList = data.page.list
            this.totalPage = data.page.totalCount
          } else {
            this.dataList = []
            this.totalPage = 0
          }
          this.dataListLoading = false
        })
      },
      // 每页数
      sizeChangeHandle (val) {
        this.pageSize = val
        this.pageIndex = 1
        this.getDataList()
      },
      // 当前页
      currentChangeHandle (val) {
        this.pageIndex = val
        this.getDataList()
      },
      // 多选
      selectionChangeHandle (val) {
        this.dataListSelections = val
      },

    }
  }
</script>

7.4.1.引入组件

先引入这个el-dialog组件

 import BrandDialog from "../common/brand-dialog";

根据具体的路径完成一个组件页面的引入,并且取名叫做BrandDialog ;

然后 声明组件的使用

 components: { BrandDialog }, 

根据规则定义一个组件,按照驼峰的形式BrandDialog 转换成组件的名字,

在template标签里面添加一个组件

特别注意: 子组件标签 与 原来模版内容 要并列在 template 结点下,

但template 必须有唯一根结点

<brand-dialog
      v-if="brandDialogVisible"
      ref="brandDialog" ></brand-dialog>

通过 brandDialogVisible 值来 控制 显隐, 在data中 声明并赋初始值为false

  // 品牌模态窗 显隐标识
  brandDialogVisible: false,

7.4.2.触发组件显示

增加元素, 触发的方法,

    <el-form-item label="选择品牌" prop="brdId">
      <el-col :span="12">
            <el-input v-model="dataForm.brandCnName" placeholder="选择品牌" :disabled="true" ></el-input>
        </el-col>
        <el-col :span="6"  >
          <el-button type="success" round @click="openBrandDialog" >选择品牌</el-button >
        </el-col>
    </el-form-item>

添加对应的函数

      /**
       * 打开 模态窗
       */
      openBrandDialog(){
          // 设置模态窗 显示
          this.brandDialogVisible = true;
          this.$nextTick(() => {
            // 调用 模态窗的初始化方法  
            this.$refs.brandDialog.init();
          });
      },

this.$nextTick()这是Vue生命周期中的钩子函数的其中之一,在显示的时候执行参数对应的函数,

然后就是this.$refs.brandDialog.init();使用this.$refs.组件名.组件方法(参数),

组件名称是在设定的时候通过ref="brandDialog"设定组件的名称变成refs的直接使用。

7.4.3.子组件数据

然后我们在组件里面,组件中的主体是在

<el-dialog title="选择品牌"  width="80%"
      :close-on-click-modal="false"
      :visible.sync="visible"
    >
    ... <!--数据显示 -->
    <span slot="footer" class="dialog-footer">
      <el-button @click="visible = false">取 消</el-button>
      <el-button type="primary" @click="selectedBrand()">确 定</el-button>
    </span>
 </el-dialog>

里面的,通过 :visible.sync="visible" 绑定一个boolean值来完成对于dialog的显示,

然后title完成对于弹窗的题目的设定,

然后展示init方法

    /**
     * 初始化方法 ,  由父组件调用
     */
    init (id) {    
      this.visible = true
      this.$nextTick(() => {
          //调用查询数据的方法
        this.getDataList();
      })
    },

所有的 v-if 想要产生效果都需要设定默认值才能够使用
在这里具体逻辑就是this.visible = true;显示dialog,

然后就是 this.getDataList(); 查询列表数据

7.4.4.点击选中

在 data 声明用于记录选中信息

  currentRow: null,

在 el-table 中增加 highlight-current-row 是否要高亮当前选中行

@current-change=“handleCurrentChange” 当表格的当前行发生变化的时候会触发该事件

 <el-table
      :data="dataList"
      border
      v-loading="dataListLoading"
      :highlight-current-row = "true"
      @current-change="handleCurrentChange"
      style="width: 100%;">

对就的处理函数

    
    handleCurrentChange(val) {
      // 将选中的行 赋值给  currentRow,  要先在 data中声明  
      this.currentRow = val;
    },

7.4.5.回传信息

点击 确定按钮 执行函数

    /**
     * 选中 关闭模态窗
     */
    selectedBrand() {
      // console.log(this.currentRow)
      // 隐藏 模态窗
      this.visible = false;
      // 触发 父组件的事件, 将选中行信息传回
      this.$emit("selectedBrand", this.currentRow);
    },

7.4.6.父组件接值

在 组件引用里 加入

    <brand-dialog
      v-if="brandDialogVisible"
      ref="brandDialog"

      @selectedBrand="selectedBrand"  ></brand-dialog>

@selectedBrand="selectedBrand"

等号 左边 与 子组件调用的方法相对应

等号 右边 是对应 父组件的函数

对应的函数, 接收到子组件传来信息, 并对dataForm 进行赋值

  selectedBrand(brandData){
        console.log(brandData)
        this.dataForm.brdId = brandData.brandId;
        this.dataForm.brandCnName = brandData.brandCnName;
      },

7.5.树

7.5.1.vue页面

<template>
  <div>
 <el-switch v-model="draggable" active-text="开启拖拽" inactive-text="关闭拖拽"></el-switch>
    <el-button v-if="draggable" @click="batchSave" >批量保存</el-button>
    <el-button type="danger"    @click="batchDelete" >批量删除</el-button>

  <el-tree 
      :data="data" 
      :props="defaultProps" 
      @node-click="handleNodeClick" 
      :expand-on-click-node="false"
      show-checkbox     
      node-key="areaId"
      :default-expanded-keys="expandedKey"
      :draggable="draggable"
      :allow-drop="allowDrop"
      @node-drop="handleDrop"
      ref="dataTree"  >

      <span class="custom-tree-node" slot-scope="{ node, data }">
        <span>{{ node.label }}</span>
        <span>
          <el-button  
            v-if="node.level <=4"
            type="text"
            size="mini" 
            @click="() => append(data)">
            添加
          </el-button>

          <el-button type="text" size="mini" @click="edit(data)">修改</el-button>

          <el-button 
            v-if="node.childNodes.length==0"
            type="text"
            size="mini"
            @click="() => remove(node, data)">
            删除
          </el-button>
        </span>
      </span>
  </el-tree>

  <!-- 添加 对话窗 -->
  <el-dialog
    :title="title"
    :visible.sync="dialogVisible"
    width="30%"
    :close-on-click-modal="false"
    >
   <el-form :model="basearea">
    <el-form-item label="地区名称" >
      <el-input v-model="basearea.areaName" autocomplete="off"></el-input>
    </el-form-item>    
    <el-form-item label="地区代号" >
      <el-input v-model="basearea.areaCode" autocomplete="off"></el-input>
    </el-form-item>    
  </el-form>
    <span slot="footer" class="dialog-footer">
      <el-button @click="dialogVisible = false">取 消</el-button>
      <el-button type="primary" @click="submitData">确 定</el-button>
    </span>
  </el-dialog>
  </div>
</template>

<script>

  export default {

    activated () {
      this.getDataList()
    },
    data() {
      return {
        basearea: {
          areaId: 0,
          areaParId: '',
          areaName: '',
          areaCode: '',
          areaLevel: '',
          areaParName: '',
          areaSort: ''
        },


        dialogType:"", // 对话窗类型 add/edit
        dialogVisible: false, // 对话窗 显隐
        title: "",     // 对话窗 标题

        maxLevel: 0, // 最大深度
        draggable: false, // 是否可以拖拽
        updateNodes: [], // 拖拽后要更新的结点集合
        pCid: [],  //  父结点

        data: [],   // 树形 显示数据
        expandedKey: [],  // 展开结点ID

        defaultProps: {
          children: 'children',
          label: 'areaName'
        }
      };
    },
    methods: {
      /**
       *点击事件
       */
      handleNodeClick(data) {
        console.log(data);
      },
      /**
       * 得到 数据
       */
      getDataList(){
        this.dataListLoading = true
        this.$http({
          url: this.$http.adornUrl('/base/basearea/list/tree'),
          method: 'get'
        }).then(({data}) => {
          console.log("数据加载成功!..." , data)
          this.data=data.data;
          this.dataListLoading = false
        })
      },
      /**
       * 批量删除
       */
      batchDelete() {
        let areaIds = [];
        let checkedNodes = this.$refs.dataTree.getCheckedNodes();
        console.log("被选中的元素", checkedNodes);
        for (let i = 0; i < checkedNodes.length; i++) {
          console.log(checkedNodes[i]);
          // 没有子结点
          if(checkedNodes[i].children==null){
              areaIds.push(checkedNodes[i].areaId);
          }
         
        }
        this.$confirm(`是否批量删除【${areaIds}】分类?`, "提示", {
          confirmButtonText: "确定",
          cancelButtonText: "取消",
          type: "warning"
        })
          .then(() => {
            this.$http({
              url: this.$http.adornUrl("/base/basearea/delete"),
              method: "post",
              data: this.$http.adornData(areaIds, false)
            }).then(({ data }) => {
              this.$message({
                message: "信息批量删除成功",
                type: "success"
              });
             this.getDataList();
                //设置需要默认展开的菜单
            //  this.expandedKey = [this.basearea.areaParId];
            });
          })
          .catch(() => {});
      },

        /** 
         * 拖拽成功完成时触发的事件函数
         * 
         * 拖拽方法 四个参数
         * 被拖拽节点对应的 Node、
         * 结束拖拽时最后进入的节点、
         * 被拖拽节点的放置位置(before、after、inner)、
         * event
         */
      handleDrop(draggingNode, dropNode, dropType, ev) {
        
        // console.log("handleDrop: ", draggingNode, dropNode, dropType);
        //1、当前节点最新的父节点id
        let pCid = 0;
        let siblings = null;  // 兄弟结点集合
        if (dropType == "before" || dropType == "after") {
          pCid =
            dropNode.parent.data.areaId == undefined
              ? 0
              : dropNode.parent.data.areaId;
          siblings = dropNode.parent.childNodes;

        } else {
          pCid = dropNode.data.areaId;
          siblings = dropNode.childNodes;
        }

        this.pCid.push(pCid);
        console.log(pCid, siblings)

        //2、当前拖拽节点的最新顺序,
        for (let i = 0; i < siblings.length; i++) {
          
     
           let parName = siblings[i].parent.data.areaName
    

          // 如果是拖动的结点
          if (siblings[i].data.areaId == draggingNode.data.areaId) {
            //如果遍历的是当前正在拖拽的节点
            let catLevel = draggingNode.level;
            if (siblings[i].level != draggingNode.level) {
              //当前节点的层级发生变化
              catLevel = siblings[i].level;

              //修改他子节点的层级
              this.updateChildNodeLevel(siblings[i]);
            }
            this.updateNodes.push({
              areaId: siblings[i].data.areaId,
              areaSort: i,
              areaParId: pCid,
              areaLevel: catLevel-1,
              areaParName: parName
            });
          } else {
            this.updateNodes.push({ areaId: siblings[i].data.areaId, areaSort: i });
          }
        }

        //3、当前拖拽节点的最新层级
        console.log("updateNodes", this.updateNodes);

      },
      
        /**
         * 更新被拖拽结点子结点信息
         */
      updateChildNodeLevel(node) {
        console.log("updateChildNodeLevel:", node)
        if (node.childNodes.length > 0) {
          for (let i = 0; i < node.childNodes.length; i++) {
            var cNode = node.childNodes[i].data;
            this.updateNodes.push({
              areaId: cNode.areaId,
              areaLevel: node.childNodes[i].level-1
            });
            this.updateChildNodeLevel(node.childNodes[i]);
          }
        }
      },

    /**
     * 批量保存
     */
    batchSave() {

      console.log("批量保存", this.updateNodes )

      this.$http({
        url: this.$http.adornUrl("/base/basearea/update/sort"),
        method: "post",
        data: this.$http.adornData(this.updateNodes, false)
      }).then(({ data }) => {
        this.$message({
          message: "地理信息顺序等修改成功",
          type: "success"
        });
        //刷新出新的菜单
        this.getDataList();
        //设置需要默认展开的菜单
        this.expandedKey = this.pCid;
        this.updateNodes = [];
        this.maxLevel = 0;
        // this.pCid = 0;
      });
    },

      /**
       * 拖拽时判定目标节点能否被放置
       */
      allowDrop(draggingNode, dropNode, type) {
        //1、被拖动的当前节点以及所在的父节点总层数不能大于3

        //1)、被拖动的当前节点总层数
        console.log("allowDrop:", draggingNode, dropNode, type);
        //
        this.countNodeLevel(draggingNode);
        console.log(this.maxLevel ,"><", draggingNode.level)
        //当前正在拖动的节点+父节点所在的深度不大于3即可
        let deep = Math.abs(this.maxLevel - draggingNode.level) + 1;
        console.log("深度:", deep);

        //   this.maxLevel
        if (type == "inner") {
          // console.log(
          //   `this.maxLevel:${this.maxLevel};draggingNode.data.catLevel:${draggingNode.data.catLevel};dropNode.level:${dropNode.level}`
          // );
          return deep + dropNode.level <= 5;
        } else {
          return deep + dropNode.parent.level <= 5;
        }
      },

        /**
         * 找到所有子节点,求出最大深度
         */
      countNodeLevel(node) {
        //判断是有子结点
        if (node.childNodes != null && node.childNodes.length > 0) {
          for (let i = 0; i < node.childNodes.length; i++) {
            if (node.childNodes[i].level > this.maxLevel) {
              this.maxLevel = node.childNodes[i].level;
            }

            // 递归 调用
            this.countNodeLevel(node.childNodes[i]);
          }
        }else{
          this.maxLevel=node.level
        }
      },

        /**
         * 点击修改按钮
         */
      edit(data) {
       
        console.log("要修改的数据", data);
        this.dialogType = "edit";
        this.title = "修改地理信息";
     
        this.dialogVisible = true;

        //发送请求获取当前节点最新的数据
        this.$http({
          url: this.$http.adornUrl(`/base/basearea/info/${data.areaId}`),
          method: "get"
        }).then(({ data }) => {
          //请求成功
          console.log("要回显的数据", data);
            this.basearea.areaParId = data.baseArea.areaParId;
            this.basearea.areaLevel = data.baseArea.areaLevel;
            this.basearea.areaId = data.baseArea.areaId;
            this.basearea.areaName = data.baseArea.areaName;
            this.basearea.areaSort = data.baseArea.areaSort;
            this.basearea.areaCode = data.baseArea.areaCode;
            this.basearea.areaParName = data.baseArea.areaParName;

        });
      },
      
        /**
         * 点击添加按钮
         */
      append(data) {
      
        this.dialogType = "add";
        this.title = "添加地理信息";
        // this.basearea.areaName = "";
        // 打开对话窗
        this.dialogVisible=true;
        // 初始化数据

        this.basearea.areaParId = data.areaId;
        this.basearea.areaLevel = data.areaLevel * 1 + 1;
        this.basearea.areaId = null;
        this.basearea.areaName = "";
        this.basearea.areaSort = 0;
        this.basearea.areaCode = "";
        this.basearea.areaParName = data.areaName;
      },

        /**
         * 点击 对话窗 确定按钮
         */
      submitData(){

        if (this.dialogType == "add") {
          this.addCategory();
        }
        if (this.dialogType == "edit") {
          this.editCategory();
        }
      },
        /**
         * 修改数据
         */
      editCategory(){

        var { areaId, areaName, areaCode} = this.basearea;
        this.$http({
          url: this.$http.adornUrl("/base/basearea/update"),
          method: "post",
          data: this.$http.adornData( {  areaId, areaName, areaCode}, false)
        }).then(({ data }) => {
          this.$message({
            message: "分类信息修改成功",
            type: "success"
          });
          //关闭对话框
          this.dialogVisible = false;
          //刷新出新的菜单
          this.getDataList();
          //设置需要默认展开的菜单
          this.expandedKey = [this.basearea.areaParId];
        });
      },

        /** 
        *  添加 分类信息
        */
      addCategory(){

        console.log("data", this.basearea)
        this.$http({
          url: this.$http.adornUrl("/base/basearea/save"),
          method: "post",
          data: this.$http.adornData(this.basearea, false)
        }).then(({ data }) => {
          this.$message({
            message: "地理信息保存成功",
            type: "success"
          });
          //关闭对话框
          this.dialogVisible = false;
          //刷新出新的菜单
          this.getDataList();
          //设置需要默认展开的菜单
          this.expandedKey = [this.basearea.areaParId];
        });
      },

      /**
       * 移除 结点
       */
      remove(node, data) {

        var ids = [data.areaId]
        console.log(ids);
        this.$confirm(`确定对[${data.areaName}]删除操作?`, '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          this.$http({
            url: this.$http.adornUrl('/base/basearea/delete'),
            method: 'post',
            data: this.$http.adornData(ids, false)
          }).then(({data}) => {
            if (data && data.code === 0) {
              this.$message({
                message: '操作成功',
                type: 'success',
                duration: 1500,
                onClose: () => {
                  this.getDataList()
                }
              })
              // 为默认打开的结点(也就是当前删除结点的父结点)赋值
              this.expandedKey = [node.parent.data.areaId];
            } else {
              this.$message({
                message: data.msg,
                type: 'error',
                duration: 1500,
                onClose: () => {
                  this.getDataList()
                }
              })
                // 为默认打开的结点(也就是当前删除结点的父结点)赋值
              this.expandedKey = [node.parent.data.areaId];
            }
            console.log("删除成功!")
          })
        }).catch(()=>{ 
           console.log("取消删除!")
        });

      },
    }
   
  }
</script>

7.5.2.后台准备数据

后台 实体类 增加 子结点集合属性

	@TableField(exist = false)
	private List<BaseAreaEntity> children;

后台controller 中 准备显示 树型数据

    /**
     * 查询出所有的分类及子类, 以数形结构组装越来
     */
    @RequestMapping("/list/tree")
    public R listWithTree(){
        List<BaseAreaEntity> baseAreaTrees = baseAreaService.listWithTree();

        return R.ok().put("data", baseAreaTrees);
    }

serviceImpl中

    @Override
    public List<BaseAreaEntity> listWithTree() {

        //1、查出所有分类
        List<BaseAreaEntity> allList = baseMapper.selectList(null);
        //2、组装成父子的树形结构
        //2.1)、找到所有的一级分类
        List<BaseAreaEntity> baseAreas = allList.stream()
                .filter(baseAreaEntity ->
                    baseAreaEntity.getAreaParId()==null
                )
                .peek((baseAreaEntity)->{
                    baseAreaEntity.setChildren(getChildrens(baseAreaEntity, allList));
                })
                .sorted((b1,b2)->{
                    return (b1.getAreaSort()==null?0:b1.getAreaSort()) - (b2.getAreaSort()==null?0:b2.getAreaSort());
                })
                .collect(Collectors.toList());

            return baseAreas;
    }

    //递归查找所有菜单的子菜单
    private List<BaseAreaEntity> getChildrens(BaseAreaEntity root, List<BaseAreaEntity> all){

        return all.stream()
                .filter(baseAreaEntity -> root.getAreaId().equals(baseAreaEntity.getAreaParId()))
                .peek(baseAreaEntity -> {
                    //1、找到子菜单
                    List<BaseAreaEntity> childrens = getChildrens(baseAreaEntity, all);
                    if (childrens != null && childrens.size()>0) {
                        baseAreaEntity.setChildren(childrens);
                    }
                })
                .sorted((b1,b2)->{
                    return (b1.getAreaSort()==null?0:b1.getAreaSort()) - (b2.getAreaSort()==null?0:b2.getAreaSort());
                })
                .collect(Collectors.toList());

    }

7.5.3.批量保存修改结点数据

controller中的方法

    @RequestMapping("/update/sort")
    public R updateSort(@RequestBody BaseAreaEntity[] baseAreaEntities){
        System.out.println("Arrays.toString(baseAreaEntities) = " + Arrays.toString(baseAreaEntities));
        baseAreaService.updateBatchById(Arrays.asList(baseAreaEntities));
        return R.ok();
    }

7.6.联动

7.6.1.后台准备树形数据

@Override
    public List<Map<String, Object>> listCascader() {

        List<Map<String, Object>> list = categoryOneDao.selectList(null).stream()
                .map(categoryOneEntity -> {
                    Map<String, Object> oneMap = new HashMap<>();
                    oneMap.put("categoryId", categoryOneEntity.getConeId());
                    oneMap.put("categoryName", categoryOneEntity.getConeName());

                    List<CategoryTwoEntity> categoryTwoEntities = categoryTwoDao.selectList(
                            new LambdaQueryWrapper<CategoryTwoEntity>()
                            .eq(CategoryTwoEntity::getConeId, categoryOneEntity.getConeId()));

                    if (categoryTwoEntities != null && categoryTwoEntities.size()>0) {
                        List<Map<String, Object>> twoList = categoryTwoEntities.stream()
                                .map(categoryTwoEntity -> {
                                    Map<String, Object> twoMap = new HashMap<>();
                                    twoMap.put("categoryId", categoryTwoEntity.getCtwoId());
                                    twoMap.put("categoryName", categoryTwoEntity.getCtwoName());

                                    List<CategoryThreeEntity> categoryThreeEntitys = this.list(new LambdaQueryWrapper<CategoryThreeEntity>()
                                            .eq(CategoryThreeEntity::getCtwoId, categoryTwoEntity.getCtwoId()));

                                    if (categoryThreeEntitys != null && categoryThreeEntitys.size() > 0) {
                                        List<Map<String, Object>> thrList = categoryThreeEntitys.stream().map(categoryThreeEntity -> {
                                            Map<String, Object> thrMap = new HashMap<>();
                                            thrMap.put("categoryId", categoryThreeEntity.getCthrId());
                                            thrMap.put("categoryName", categoryThreeEntity.getCthrName());
                                            return thrMap;
                                        }).collect(Collectors.toList());
                                        twoMap.put("children", thrList);
                                    }
                                    return twoMap;
                                })
                                .collect(Collectors.toList());

                        oneMap.put("children", twoList);
                    }

                    return oneMap;
                })
                .collect(Collectors.toList());

        return list;
    }

其中 categoryId 为 id , categoryName 为 name , children 为子结点

7.6.2.页面组件

 <el-cascader  style="width:280px;"
              filterable
              clearable 
              placeholder="试试搜索:手机"
              :props="cascaderProps"
              change-on-select
              :options="categoryOptions"        

              @change="handleChange"
              ref="cascader"
              v-model="categoryPath" >
</el-cascader>

data 增加

        categoryOptions: [],
        cascaderProps:{
          value:"categoryId",
          label:"categoryName",
          children:"children"
        },
        categoryPath:[],

methods

 handleChange(value) {
       console.log(value);   
     	// 在高版本中才有这样的方法
       console.log(this.$refs["cascader"].getCheckedNodes()[0].label)
       console.log(this.$refs["cascader"].getCheckedNodes()[0].pathLabels) //对应的label数组
 },

低版本可以在

      for(let i=0; i<this.categoryOptions.length; i++ ){
            console.log( this.categoryOptions[i].categoryId )
            if(this.categoryOptions[i].categoryId == this.categoryPath[0]){
             
              this.dataForm.coneId = this.categoryOptions[i].categoryId
              this.dataForm.coneName = this.categoryOptions[i].categoryName
              for(let l=0; l<this.categoryOptions[i].children.length; l++){
                  if(this.categoryOptions[i].children[l].categoryId == this.categoryPath[1]){
                    this.dataForm.ctwoId = this.categoryOptions[i].children[l].categoryId
                    this.dataForm.ctwoName = this.categoryOptions[i].children[l].categoryName
                    for(let k=0; k<this.categoryOptions[i].children[l].children.length; k++){
                        if(this.categoryOptions[i].children[l].children[k].categoryId == this.categoryPath[2]){
                          this.dataForm.cthrId = this.categoryOptions[i].children[l].children[k].categoryId
                          this.dataForm.cthrName = this.categoryOptions[i].children[l].children[k].categoryName
                        }
                    }
                  }
              }
            }
        }

        console.log("data:", this.dataForm)

7.7.时间,日历

7.7.1.时间信息

Date.now() 取当前时间

原始格式 : 2018-10-30T10:06:20.000Z

new Date(this.xxDate).getTime() 转化为时间戳

7.7.2.按格式 显示 时间

  <el-table-column
        prop="supplierFoundedDate"
        header-align="center"
        align="center"
           :formatter="dateFormat"
        label="成立时间">
      </el-table-column>

对应 函数

     dateFormat(row,column){    
       var dT = new Date(row.supplierFoundedDate);//row 表示一行数据, supplierFoundedDate 表示要格式化的字段名称
       return dT.getFullYear()+"-"+(dT.getMonth()+1)+"-"+dT.getDate();
     },

7.7.3.日历插件

        <el-date-picker
          v-model="dataForm.supplierFoundedDate"
          type="date"
          placeholder="选择成立时间">
        </el-date-picker>

可能是时区等原因, 返回原始格式 : 2018-10-30T10:06:20.000Z, 传递后台 不能成功转换成java.util.Date() 会报错
可以通过 new Date(this.dataForm.supplierFoundedDate).getTime() 转化为时间戳 来 解决

7.8.表格扩展

在 列表 页面的 el-table 中加入

      <el-table-column fixed type="expand">
      <template slot-scope="props">
        <el-form label-position="left" inline class="demo-table-expand">
          <el-form-item label="银行名称:">
            <span>{{ props.row.bankName }}</span>
          </el-form-item>
          <el-form-item label="银行账号:">
            <span>{{ props.row.bankSn }}</span>
          </el-form-item>

          <el-form-item label="地址:" style="width: 100%" >
            <span>{{ props.row.supplierAddress }}</span>
          </el-form-item>
          <el-form-item label="描述:" style="width: 100%" >
            <span>{{ props.row.supplierInfo }}</span>
          </el-form-item>
        </el-form>
      </template>
      </el-table-column>

7.9.富文本

7.9.1.tinymce

下载 地址 : https://www.tiny.cloud/get-tiny/self-hosted/

语言包 地址: https://www.tiny.cloud/get-tiny/language-packages/

# 安装
npm install tinymce -S


# vue2.0版本应该使用
npm install --save "@tinymce/tinymce-vue@^3"


# vue3.0版本应该使用
npm install --save "@tinymce/tinymce-vue@^4"


# 再运行 
npm install tinymce -S

安装之后,在 node_modules 中找到 tinymce/skins 目录,然后将 skins 目录拷贝到 static 目录下

结构 如:
static

​ tinymce

​ skins

​ zh_CN.js

在页面中引入以下文件

import tinymce from 'tinymce/tinymce'
import 'tinymce/themes/silver/theme'
import Editor from '@tinymce/tinymce-vue'

import 'tinymce/icons/default/icons';
import 'tinymce/plugins/image';
import 'tinymce/plugins/media';// 插入视频插件
import 'tinymce/plugins/table';// 插入表格插件
import 'tinymce/plugins/lists';// 列表插件
import 'tinymce/plugins/wordcount';// 字数统计插件

tinymce-vue 是一个组件,需要在 components 中注册,

 components: { Editor },

然后直接使用

<Editor id="tinymce" v-model="tinymceHtml" :init="editorInit"></Editor>

编辑器需要一个 skin 才能正常工作,所以要设置一个 skin_url 指向之前复制出来的 skin 文件

在data 中配置初始化属性

editorInit: {
    language_url: '/static/tinymce/zh_CN.js',
    language: 'zh_CN',
    skin_url: '/static/tinymce/skins/ui/oxide', // skin路径
    height: 300, // 编辑器高度
    branding: false, // 是否禁用“Powered by TinyMCE”
    menubar: true, // 顶部菜单栏显示
    plugins: this.plugins,
    toolbar: this.toolbar,
}

同时在 mounted 中也需要初始化一次:

mounted (){
	tinymce.init({});
},

如果在这里传入上面的 init 对象,并不能生效,但什么参数都不传也会报错,所以这里传入一个空对象

https://blog.csdn.net/zjiang1994/article/details/79880806

https://blog.csdn.net/zjiang1994/article/details/79856058

完成了上面的初始化之后,就已经能正常运行编辑器了,但只有一些基本功能

tinymce 通过添加插件 plugins 的方式来添加功能

比如要添加一个上传图片的功能,就需要用到 image 插件,添加超链接需要用到 link 插件

<template>
  <div class='tinymce'>
    <h1>tinymce</h1>
    <editor id='tinymce' v-model='tinymceHtml' :init='init'></editor>
    <div v-html='tinymceHtml'></div>
  </div>
</template>

<script>
import tinymce from 'tinymce/tinymce'
import 'tinymce/themes/silver/theme'
import Editor from '@tinymce/tinymce-vue'
import 'tinymce/plugins/image'
import 'tinymce/plugins/link'
import 'tinymce/plugins/code'
import 'tinymce/plugins/table'
import 'tinymce/plugins/lists'
import 'tinymce/plugins/contextmenu'
import 'tinymce/plugins/wordcount'
import 'tinymce/plugins/colorpicker'
import 'tinymce/plugins/textcolor'
    
export default {
  name: 'tinymce',
  data () {
    return {
      tinymceHtml: '请输入内容',
      init: {
          language_url: '/static/tinymce/zh_CN.js',
          language: 'zh_CN',
          skin_url: '/static/tinymce/skins/ui/oxide', // skin路径
          content_css:'/static/tinymce/skins/content/default/content.css',
          height: 300, // 编辑器高度
          branding: false, // 是否禁用“Powered by TinyMCE”
          menubar: true, // 顶部菜单栏显示
          font_formats: '微软雅黑=Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif;苹果苹方=PingFang SC,Microsoft YaHei,sans-serif;宋体=simsun,serif',
          fontsize_formats:'12px 14px 16px 18px 20px 22px 24px 26px 28px 30px 32px 34px 36px 38px 40px 50px 60px 70px 80px 90px 100px 120px 140px 160px 180px 200px',
          plugins: 'link lists image code table colorpicker textcolor wordcount contextmenu',
          toolbar:'bold italic underline strikethrough | fontsizeselect | forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist | outdent indent blockquote | undo redo | link unlink image code | removeformat',
      }
    }
  },
  mounted () {
    tinymce.init({})
  },
  components: {Editor}
}
</script>

但是当富文本在某一个弹窗上使用时,

工具栏会出现下拉选择时的层级比弹窗的小,所以,选项会被弹窗遮挡。

而解决这个问题,需要把工具栏的下拉框的层级提高,

找到static/tinymce/skins/ui/oxide/skin.min.css文件

把class名为tox-tinymce-aux的第一个的z-index属性变大即可

tinymce 在初始化的属性对象中( init对象中 )提供了 images_upload_url 等 api 让用户配置上传图片的相关参数

但为了在不麻烦后端的前提下适配自家的项目,还是得用 images_upload_handler 来自定义一个上传方法

     images_upload_handler: (blobInfo, success, failure) => {
       // const img = 'data:image/jpeg;base64,' + blobInfo.base64();
      // success(img);
       this.handleImgUpload(blobInfo, success, failure);
     }

这个方法会提供三个参数:blobInfo, success, failure

其中 blobinfo 是一个对象,包含上传文件的信息

successfailure 是函数,上传成功的时候向 success 传入一个图片地址,失败的时候向 failure 传入报错信息

handleImgUpload (blobInfo, success, failure) {
      // 上传的文件信息
        let fd = new FormData();
        fd.append("file", blobInfo.blob());
        fd.append("type","tinymec")
        this.$http({
          url: this.$http.adornUrl(
            "/localUpload/uploadForType" 
          ),
          method: "post",
          data: fd,
          headers: {
            "Content-Type":
              "multipart/form-data;boundary=" + new Date().getTime(),
            "token": this.$cookie.get('token')
          },
        }).then(({ data }) => {
          if (data && data.code === 0) {
            this.$message({
              message: data.path + ",操作成功",
              type: "success",
              duration: 1500,
              onClose: () => {
                 success(data.path)
              },
            });
          } else {
            failure('error')
          }
        });
}

7.10.Switch 开关

页面元素

 <el-table-column
        prop="goodsImgsState"
        header-align="center"
        align="center"
        label="当前状态 : 0 不可用 1 使用">
          <template slot-scope="scope">
          <el-switch
            v-model="scope.row.goodsImgsState"
            active-color="#13ce66"
            inactive-color="#ff4949"
            :active-value="'1'"
            :inactive-value="'0'"
            @change="updateType(scope.row)"
          ></el-switch>
        </template>
      </el-table-column>

对应 函数

    updateType(data){
        console.log(data);
        console.log("最新信息", data);
        // es6  var let
        let { goodsImgsId, goodsImgsState } = data;
        //发送请求修改状态
        this.$http({
          url: this.$http.adornUrl("/goods/goodsimgs/update/state"),
          method: "post",
          data: this.$http.adornData({ goodsImgsId, goodsImgsState  }, false),
        }).then(({ data }) => {
          this.$message({
            type: "success",
            message: "状态更新成功",
          });
          this.getDataList();
        });
      },

7.11. tag

三元运算

  <el-table-column
        prop="supplierState"
        header-align="center"
        align="center"
        label="状态">
         <template slot-scope="scope">
           <el-tag  :type="scope.row.supplierState !='1'?'danger':''" size="medium">{{ scope.row.supplierState !='1'?'关停':'正常' }}</el-tag>
        </template>
      </el-table-column>

v-if

   <el-table-column
        prop="supplierIsJoin"
        header-align="center"
        align="center"
        label="是否入住">
          <template slot-scope="scope">
            <div v-if="scope.row.supplierIsJoin === '0'">
                <el-tag type="info"  >未入住</el-tag>
            </div>
            <div v-if="scope.row.supplierIsJoin === '1'">
                <el-tag type="success"  >入住</el-tag>
            </div>
            <div v-else-if="scope.row.supplierIsJoin === '2'">
               <el-tag    >协商中...</el-tag>
            </div>
        </template>
      </el-table-column>

7.12.页面校验

在 data 的 dataRule 中 设置规则

dataRule: {
         
        adminNickname: [
          {
            required: true,
            message: "昵称不能为空",
            trigger: "blur",
          },
        ],
        adminAccount: [
          {
            validator: (rule, value, callback) => {
              if (value === '') {
                callback(new Error('请输入管理员账号'));
              } else  {
                console.log("this.oldAccount==value", this.oldAccount==value)
                if(this.oldAccount==value){
                  callback();
                }else{
                  this.$http({
                    url: this.$http.adornUrl(`/admin/admininfo/checkSingleLogin/${value}`),
                    method: 'get',
                    params: this.$http.adornParams(),
                  }).then(({data}) => {
                    if (data && data.code === 0) {
                      callback();
                    } else {
                      callback(new Error('管理员账号已被使用'));
                    }
                  })
                }
              }
            },
            trigger: "blur",
          },
        ],
        adminPassword: [
          {
            validator: (rule, value, callback) => {
              if (value === '') {
                callback(new Error('请输入管理员密码'));
              } else {
                if (this.dataForm.checkPass !== '') {
                  this.$refs.dataForm.validateField('checkPass');
                }
                callback();
              }
            },
            trigger: "blur",
          },
        ],
        checkPass: [
          {
            validator: (rule, value, callback) => {
              if (value === '') {
                callback(new Error('请输入确认密码'));
              } else if (value !== this.dataForm.adminPassword) {
                callback(new Error('两次输入密码不一致!'));
              } else {
                callback();
              }
            },
            trigger: "blur",
          },
        ],
        adminPhone: [
          {
            validator: (rule, value, callback)=>{
              if (value === '') {
                callback(new Error('管理员电话不能为空'));
              } else if (!/^1[3-9]\d{9}$/.test(value)) {
                callback(new Error('请输入正确的手机号码'));
              } else {
                callback();
              }
            }, trigger: "blur",
          }
        ],
        adminInfo: [
          {
            required: true,
            message: "管理员说明 不能为空",
            trigger: "blur",
          },
          
          { max: 5, message: '长度要少于 5 个字符', trigger: 'blur' }
        ],
   }

7.13.Steps 步骤条

<template>
<div>
  <el-dialog
    title="维护一级分类关联的品牌"
    :close-on-click-modal="false"
    :visible.sync="visible">


      <el-col :span="18">
         <el-alert  style="box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04)"
         effect="dark"
          :title="dataForm.coneName"
          type="info"
          :closable="false"
          :description="dataForm.coneInfo"
          show-icon >
        </el-alert>
      </el-col>
      <el-col :span="6"  align="center" valign="center" >
          <el-button  type="primary"  round @click="openBrandDialog"  >选择品牌</el-button>
      </el-col>

    
    <el-table
        :data="tableData"
        border
        style="width: 100%">
        <el-table-column
          prop="brandId"
          label="品牌主键"
          width="180">
        </el-table-column>
        <el-table-column
          prop="brandCnName"
          label="品牌名称"
          width="180">
        </el-table-column>
        <el-table-column
          prop="coneBrdNum"
          label="序号">
        </el-table-column> 
        <el-table-column
          prop="coneBrdNum"
          label="序号"   width="200" >
           <template slot-scope="scope">
  <el-input-number :min="1" :step="1" v-model="scope.row.coneBrdNum" size="mini" placeholder="分类序号"></el-input-number>
          </template>
            
        </el-table-column> 
        <el-table-column
           fixed="right"
        header-align="center"
        align="center"
          label="操作">
            <template slot-scope="scope">

          <el-button type="text" size="small"
                @click.native.prevent="deleteRow(scope.$index, tableData)"
           >取消</el-button>
        </template>
        </el-table-column>
      </el-table>

    <span slot="footer" class="dialog-footer">
      <el-button @click="visible = false">取消</el-button>
      <el-button type="primary" @click="dataFormSubmit()">确定</el-button>
    </span>
  </el-dialog>
      <brand-dialog
      v-if="brandDialogVisible"
      ref="brandDialog"

      @selectedBrand="selectedBrand"  ></brand-dialog>
      </div>
</template>

<script>
import BrandDialog from "../common/brand-many-dialog";

  export default {
     components: { BrandDialog }, 
    data () {
      return {

  // 品牌模态窗 显隐标识
  brandDialogVisible: false,

        tableData: [],
        visible: false,
        dataForm: {
          coneBrdId: 0,
          coneId: '',
          brandId: '',
          coneBrdNum: '',
          coneName: '',
          brandCnName: '',
          coneInfo:''
        },
        dataRule: {
          coneId: [
            { required: true, message: '分类 : 一级分类 category_one 外键不能为空', trigger: 'blur' }
          ],
          brandId: [
            { required: true, message: '品牌 :品牌 brand_info 外键不能为空', trigger: 'blur' }
          ],
          coneBrdNum: [
            { required: true, message: '序号不能为空', trigger: 'blur' }
          ],
          coneName: [
            { required: true, message: '分类名称不能为空', trigger: 'blur' }
          ],
          brandCnName: [
            { required: true, message: '中文名不能为空', trigger: 'blur' }
          ]
        }
      }
    },
      methods: {

      selectedBrand(brandData){
        console.log(brandData)
          let len =  this.tableData.length
         for(let i=0;i<brandData.length;i++){
           this.tableData.push({
             "brandId":brandData[i].brandId,
             "brandCnName":brandData[i].brandCnName,
             "coneBrdNum":len + i,
             "coneId":this.dataForm.coneId,
             "coneName":this.dataForm.coneName,
             });
         }

         console.log(this.tableData)
      },

            /**
       * 打开 模态窗
       */
      openBrandDialog(){
        
        console.log(this.tableData)
        var ids = this.tableData.map(item => {
          return item.brandId
        })
        console.log(ids)

          // 设置模态窗 显示
          this.brandDialogVisible = true;
          this.$nextTick(() => {
            // 调用 模态窗的初始化方法  
            this.$refs.brandDialog.init(ids);
          });
      },

      deleteRow(index, rows) {
        // console.log(index, rows)
        rows.splice(index, 1);
        this.tableData=rows;
      },

      init (id) {
        this.dataForm.coneId = id || 0
        this.visible = true
        this.$nextTick(() => {
          
          console.log(id)


          this.$http({
            url: this.$http.adornUrl(`/goods/categorybrand/queryByConeId/${this.dataForm.coneId}`),
            method: 'get',
            params: this.$http.adornParams()
          }).then(({data}) => {
            if (data && data.code === 0) {

              console.log("data", data)

              this.dataForm.coneId = data.categoryOne.coneId
              this.dataForm.coneName = data.categoryOne.coneName
              this.dataForm.coneInfo = data.categoryOne.coneInfo

              this.tableData = data.list;
   
            }
          })
        

        })
      },
      // 表单提交
      dataFormSubmit () {
        // this.$refs['dataForm'].validate((valid) => {
        //   if (valid) {
            this.$http({
              url: this.$http.adornUrl('/goods/categorybrand/batchSave'),
              method: 'post',
              data: this.$http.adornData(this.tableData, false)
            }).then(({data}) => {
              if (data && data.code === 0) {
                this.$message({
                  message: '操作成功',
                  type: 'success',
                  duration: 1500,
                  onClose: () => {
                    this.visible = false
                    this.$emit('refreshDataList')
                  }
                })
              } else {
                this.$message.error(data.msg)
              }
            })
        //   }
        // })
      }
    }
  }
</script>

Logo

基于 Vue 的企业级 UI 组件库和中后台系统解决方案,为数万开发者服务。

更多推荐