vue ele antd

155 阅读11分钟

ajax

import axios from 'axios'
import $store from "@/store/index";
import qs from "qs";

// 判断当前的运行环境 process.env.NODE_ENV
// isDev 为真 开发环境 --- npm run serve
// isDev 为假 非开发环境(测试环境,生产环境)- npm run build
// const isDev = process.env.NODE_ENV === 'development'
const r = axios.create({
  baseURL: process.env.VUE_APP_API_URL
})
// 请求拦截器 - 所有的请求开始之前先到此处
r.interceptors.request.use((config) => {
  return config
}, (error) => {
  return Promise.reject(error)
})

// 响应拦截器 --- 所有请求的相应先到此处
r.interceptors.response.use((response) => {
  // 可以设置 加载的动画效果 的隐藏
  return response.data
}, (error) => {
  return Promise.reject(error)
})

const ajax = {
  login: (params) => {
    return r({
      url: "/login",
      method: "post",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
      },
      data: qs.stringify(params)
    })
  },
  logout: () => {
    return r({
      url: "/logout",
      method: "post",
      headers: {
        "token": $store.state.token || ""
      }
    })
  },
  get: (url, params) => {
    return r({
      url,
      method: "get",
      headers: {
        "token": $store.state.token || ""
      },
      params
    })
  },
  post: (url, params) => {
    console.log(params);
    return r({
      url,
      method: "post",
      headers: {
        // "Content-Type": "application/x-www-form-urlencoded",
        'Content-Type':'application/json',
        "token": $store.state.token || ""
      },
      // data: typeof params === "object" ? qs.stringify(params) : params,
      data:params // data [post] params(query)
    })
  },
  upload: (url, params) => {
    let fd = new FormData()
    for(let key in params){
      fd.append(key, params[key])
    }
    return r({
      url,
      method: "post",
      headers: {
        "Content-Type": "multipart/form-data",
        "token": $store.state.token || ""
      },
      data: fd
    })
  },
  jsonp: (url) => {
    return new Promise((resolve, reject) => {
      // 这里的 "jsonCallBack" ,和调用的 jsonp 的 url 中的 callback 值相对应
      window.jsonCallBack = result => {
        resolve(result);
      };
      let JSONP = document.createElement("script");
      JSONP.type = "text/javascript";
      JSONP.src = url;
      document.getElementsByTagName("head")[0].appendChild(JSONP);

      setTimeout(() => {
        document.getElementsByTagName("head")[0].removeChild(JSONP);
      }, 500);
    });
  }
}

export default ajax

main.js
import ajax from './api/ajax'
Vue.prototype.ajax = ajax;

element ul

时间验证

image.png

 endTime: [
              { required: true, message: "请选择结束时间", trigger: "change" },
              {
                validator: (rule, value, callback) => {
                  const { startTime, endTime } = this.form;
                  if (
                    endTime &&
                    startTime &&
                    moment(endTime).isBefore(moment(startTime))
                  ) {
                    callback(new Error("结束时间不能早于开始时间"));
                  } else {
                    callback();
                  }
                },
                trigger: "change",
              },
            ],

文档图片预览插件

<ExcelPreview :isWatermark="false" :file-url="url" v-if="url" @setPreviewLoading="setPreviewLoading"/>

<template>
  <div class="Office-Preview" v-loading="loading">
    <VueOfficeDocx
      v-if="fileType == 'docx2'"
      :src="fileUrl"
      @rendered="rendered"
      @error="onError"
    ></VueOfficeDocx>

    <div
      v-loading="previewLoading"
      ref="docxContainer"
      class="docx-container"
      v-if="fileType == 'docx1'"
    ></div>

    <div class="excel-preview-container" v-if="fileType == 'excel'">
      <VueOfficeExcel
        :src="url"
        @rendered="rendered"
        v-if="fileType == 'excel'"
        ref="excelContainer"
        @error="onError"
      ></VueOfficeExcel>
    </div>

    <VueOfficePdf
      v-if="fileType == 'pdf'"
      :src="url"
      @rendered="rendered"
      @error="onError"
    ></VueOfficePdf>

    <div class="other-Preview">
      <el-image
        @load="loading = false"
        @error="loading = false"
        :src="url"
        v-if="fileType == 'img'"
      ></el-image>
      <video
        ref="videoPlayer"
        autoplay
        muted
        loop
        :src="fileUrl"
        v-if="fileType == 'mp4'"
      ></video>
      <div v-if="fileType === 'txt'" style="white-space: pre-wrap">
        <pre>{{ fileContent }}</pre>
      </div>
      <pre v-if="fileType == 'loadErr'">
文件无法解析或加载失败,可能存在编码问题或文件内容乱码。</pre
      >
    </div>
    <el-empty
      v-if="fileType == 'errType'"
      image=""
      description="暂无文件~"
      :image-size="300"
    ></el-empty>
  </div>
</template>

<script>
//引入VueOfficeDocx组件
import VueOfficeDocx from "@vue-office/docx";
//引入VueOfficeExcel组件
import VueOfficeExcel from "@vue-office/excel";
//引入VueOfficeExcel相关样式
import "@vue-office/excel/lib/index.css";
//引入VueOfficePdf组件
import VueOfficePdf from "@vue-office/pdf";

import { getToken } from "@/utils/auth";
import axios from "axios";

import BaseApi from "@/api/modules/BaseApi.js";

import { renderAsync } from "docx-preview";

export default {
  name: "Office-Preview",
  props: {
    fileUrl: {
      type: String,
      default: "",
    },
    isWatermark: {
      type: Boolean,
      default: true
    }
  },
  data() {
    return {
      fileType: "",
      fileContent: "",
      url: "",
      previewLoading: false,
      show: false,
      loading: false,
    };
  },
  components: {
    VueOfficeExcel,
    VueOfficeDocx,
    VueOfficePdf,
  },
  mounted() {
    // this.init();
  },
  beforeDestroy() {
    this.url = "";

    this.pauseVideo();
  },
  watch: {
    fileUrl: {
      handler(newVal) {
        if (newVal) {
          this.fileType = "";
          this.init();
        } else {
          this.pauseVideo();
        }
      },
      immediate: true,
    },
  },
  methods: {
    init() {
      if (!this.fileUrl) {
        this.fileType = "errType";
        this.$emit("setPreviewLoading");
        return;
      }
      var fileName = this.fileUrl.split("/").pop();
      var fileExtension = fileName.substring(fileName.lastIndexOf(".") + 1);

      if (fileExtension == "doc" || fileExtension == "docx") {
        
        if(this.isWatermark) {
          this.fileType = "docx1";
          this.previewClick();
        } else {
          this.fileType = "docx2";
          this.url = this.fileUrl
        }
      } else if (fileExtension == "xls" || fileExtension == "xlsx") {
        if(this.isWatermark) {
          this.getUrl("excel");
        } else {
          this.url = this.fileUrl
          this.fileType = "excel";
        }
      } else if (fileExtension == "pdf") {
        if(this.isWatermark) {
          this.getUrl("pdf");
        } else {
          this.url = this.fileUrl
          this.fileType = "pdf";
        }
      } else if (
        fileExtension == "jpeg" ||
        fileExtension == "webp" ||
        fileExtension == "JPG" ||
        fileExtension == "jpg" ||
        fileExtension == "png" ||
        fileExtension == "PNG"
      ) {
        if(this.isWatermark) {
          this.getUrl("img");
        } else {
          this.url = this.fileUrl
          this.fileType = "img";
        }
        this.$emit("setPreviewLoading");

      } else if (fileExtension == "mp4") {
        this.fileType = "mp4";
        this.$emit("setPreviewLoading");
      } else if (fileExtension == "txt") {
        this.fileType = "txt";
        fetch(this.fileUrl, {
          headers: {
            "Content-Type": "text/plain; charset=utf-8",
            cache: "no-cache", // 避免缓存
          },
        })
          .then((response) => response.arrayBuffer())
          .then((buffer) => {
            const decoder = new TextDecoder("utf-8"); // 如果文件是 GBK 编码,替换为 'gbk'
            const decodedText = decoder.decode(buffer);

            // 检测乱码的改进方案
            const isMostlyGarbage = (text) => {
              // 判断是否有太多不可见字符
              const nonVisibleChars =
                text.match(/[\u0000-\u001F\uFFFD]/g) || [];
              // 判断中文、英文、数字等常见字符是否少于 50%
              const visibleChars =
                text.match(/[\u4e00-\u9fa5A-Za-z0-9\s]/g) || [];
              return nonVisibleChars.length > visibleChars.length;
            };

            if (isMostlyGarbage(decodedText)) {
              this.fileContent =
                "文件无法解析或加载失败,可能存在编码问题或文件内容乱码。";
            } else {
              this.fileContent = decodedText;
            }
            this.$emit("setPreviewLoading");
          })
          .catch((error) => {
            this.fileContent = "文件无法解析或加载失败。";
            this.$emit("setPreviewLoading");
          });
      } else {
        this.fileType = "errType";
        this.$emit("setPreviewLoading");
      }
    },

    getUrl(type) {
      this.loading = true;
      let arr = this.fileUrl.split('/')
      let bucketName = arr[3]
      let url =
        process.env.VUE_APP_BASE_API +
        `/smc-file-service/api/v1/file/download/${bucketName}`;
      axios
        .get(url, {
          params: { address: this.fileUrl},
          headers: {
            Authorization: "bearer" + " " + getToken(),
          },
          responseType: "arraybuffer",
        })
        .then((response) => {
          let blob = new Blob([response.data], { type: "text/plain" });
          // // 创建一个临时的URL指向这个文件流
          this.url = URL.createObjectURL(blob);
          setTimeout(() => {
            this.fileType = type;
          });
        })
        .catch((error) => {
          this.loading = false;
          this.fileType = "errType";
          this.$emit("setPreviewLoading");
        });
    },

    previewClick() {
      this.previewLoading = true;
      let arr = this.fileUrl.split('/')
      let bucketName = arr[3]
      let url =
        process.env.VUE_APP_BASE_API +
        `/smc-file-service/api/v1/file/download/${bucketName}`;
      axios
        .get(url, {
          params: { address: this.fileUrl},
          headers: {
            Authorization: "bearer" + " " + getToken(),
          },
          responseType: "arraybuffer",
        })
        .then((response) => {
          this.previewLoading = false;
          let blob = new Blob([response.data], { type: "text/plain" });
          const reader = new FileReader();
          reader.onload = async (e) => {
            const arrayBuffer = e.target.result;
            const container = this.$refs.docxContainer;

            // 渲染 DOCX 文件内容
            await renderAsync(arrayBuffer, container, null, {
              className: "docx-preview",
              inWrapper: true,
            });

            // 添加水印
            this.addWatermarkToPages(container);
          };
          reader.readAsArrayBuffer(blob);
        })
        .catch((error) => {
          console.log(error, "errorerror");
        });
    },
    async addWatermarkToPages(container, watermarkText) {
      let res = await BaseApi.getPersonalInformation();
      let name = "";
      let phone = "";

      if (res.code == 200) {
        name = res.data.nickName;
        phone = res.data.mobile;

        if (phone) {
          phone = phone.slice(-4);
        }
      }
      // 获取所有页面元素
      const pages = container.querySelectorAll(".docx-preview");
      if (!pages.length) return;

      pages.forEach((page) => {
        // 创建水印容器
        const watermark = document.createElement("div");
        watermark.className = "watermark";

        // 设置水印样式为背景平铺
        Object.assign(watermark.style, {
          position: "absolute",
          top: 0,
          left: 0,
          width: "100%",
          height: "100%",
          background: `url('data:image/svg+xml;utf8,${encodeURIComponent(
            this.generateWatermarkSVG(`${name} ${phone}`)
          )}')`,
          backgroundRepeat: "repeat", // 平铺背景
          backgroundSize: "300px 200px", // 水印间距
          pointerEvents: "none", // 确保水印不可点击
          zIndex: 1000, // 确保水印在最上层
        });

        // 设置页面样式(确保页面相对定位)
        page.style.position = "relative";

        // 添加水印容器到页面
        page.appendChild(watermark);
      });
    },

    async addWatermarkToPages1(container, watermarkText) {
      let res = await BaseApi.getPersonalInformation();
      let name = "";
      let phone = "";

      if (res.code == 200) {
        name = res.data.nickName;
        phone = res.data.mobile;

        if (phone) {
          phone = phone.slice(-4);
        }
      }
      // 获取所有页面元素
      // const pages = document.querySelectorAll(".excel-preview-container");
      const pages = document.querySelectorAll(".x-spreadsheet-sheet");

      if (!pages.length) return;

      pages.forEach((page) => {
        // 创建水印容器
        const watermark = document.createElement("div");
        watermark.className = "watermark";

        // 设置水印样式为背景平铺
        Object.assign(watermark.style, {
          position: "absolute",
          top: 0,
          left: 0,
          width: "100%",
          height: "100%",
          background: `url('data:image/svg+xml;utf8,${encodeURIComponent(
            this.generateWatermarkSVG(`${name} ${phone}`)
          )}')`,
          backgroundRepeat: "repeat", // 平铺背景
          backgroundSize: "300px 200px", // 水印间距
          pointerEvents: "none", // 确保水印不可点击
          zIndex: 1000, // 确保水印在最上层
        });

        // 设置页面样式(确保页面相对定位)
        page.style.position = "relative";

        // 添加水印容器到页面
        page.appendChild(watermark);
      });
    },

    // 生成水印的 SVG 图像(文字为透明背景)
    generateWatermarkSVG(watermarkText) {
      return `
    <svg xmlns="http://www.w3.org/2000/svg" width="300" height="200">
      <text x="30%" y="20%" dominant-baseline="middle" text-anchor="middle"
        fill="rgba(0, 0, 0, 0.2)" font-size="20" font-family="Arial" transform="rotate(30)">
        ${watermarkText}
      </text>
    </svg>
  `;
    },
    rendered() {
      if (this.fileType == "excel" && this.isWatermark) {
        this.addWatermarkToPages1();
      }
      this.loading = false;
      this.$emit("setPreviewLoading");
    },
    onError() {
      // console.error("加载戳错");
      this.loading = false;
      this.fileType = "loadErr";
      this.$emit("setPreviewLoading");
    },
    pauseVideo() {
      if (this.$refs.videoPlayer) {
        this.$refs.videoPlayer.pause();
      }
    },
  },
};
</script>

<style lang="scss" scoped>
.Office-Preview {
  overflow-y: scroll;
  height: 100%;
}

.other-Preview {
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}

.docx-preview-container {
  position: relative;
  overflow: auto;
  background: white;
  padding: 10px;
  border: 1px solid #ddd;
}

/* 每一页的水印样式 */
.watermark {
  user-select: none;
  pointer-events: none;
  font-size: 20px;
  color: rgba(0, 0, 0, 0.1);
  white-space: nowrap;
}

.excel-preview-container {
  position: relative;
  width: 100%;
  height: 100%;
}
</style>

下拉树形选择(单选)

image.png

 <el-form-item label="操作模块" prop="module">
        <el-select
          ref="treeSelect"
          v-model="params.module"
          placeholder="请选择"
          :popper-append-to-body="false"
        >
          <el-option
            :value="selectTree"
            class="setstyle"
            style="overflow: auto; height: 100%"
            disabled
          >
            <el-tree
              style="min-height: 50px; max-height: 150px"
              :data="depList"
              :props="{
                children: 'children',
                label: 'name',
              }"
              ref="tree"
              check-strictly
              :expand-on-click-node="false"
              :accordion="true"
              @node-click="(data) => addAdminHandleNodeClick(data)"
            ></el-tree>
          </el-option>
        </el-select>
      </el-form-item>
      
        depList : [] ,
      selectTree : '' ,
      
        addAdminHandleNodeClick(node) {
      this.$refs.treeSelect.visible = false;
      this.$emit("on-dep-select", node);
    },


el-table表格错位问题

  this.$nextTick(() => {
        this.$refs.tableBox.doLayout();
      });

下拉树形选择(多选)

image.png

 <div v-if="item.type == 'el-tree-select'">
                <el-select
                  v-model="value2"
                  placeholder="请选择"
                  multiple
                  collapse-tags
                  @change="delItem(item.name)"
                >
                  <el-option value="1" lable="1" v-show="false"></el-option>
                  <el-option
                    v-for="item in selectArr"
                    :key="item.id"
                    :value="item.id"
                    :lable="item.lable"
                    v-show="false"
                  ></el-option>
                  <el-tree
                    :props="item.props"
                    :data="item.list"
                    show-checkbox
                    :check-strictly="item.checks"
                    @check-change="handleCheckChange2(item.model, item.list,0)"
                    ref="tree2"
                    node-key="id"
                  >
                  </el-tree>
                </el-select>
              </div>
              
              
              delItem() {
      this.$refs.tree2[0].setCheckedKeys(this.value2);
      this.$refs.tree2[1].setCheckedKeys(this.value2_1);
    },
    
    
      handleCheckChange2(name, list,num) {
      this.$nextTick(() => {
        let data = this.$refs.tree2[num].getCheckedNodes(false, true) 
        // let data1 = this.$refs.tree2[1].getCheckedNodes(false, true);
        this.value2 = data.map((item) => item.label);
        // this.value2_1 = data1.map((item) => item.label);
        this.selectArr = data;
        // console.log(data1,'data1');
        let ids = data.map((item) => item.id)
        this.condForm[name] = ids;
        this.$refs.tree2[0].setCheckedKeys(ids);
        this.$refs.tree2[1].setCheckedKeys(ids);
        // this.condForm['organizationName'] = this.value2;
      });
    },

动态设置表格头

image.png

组件调用

          <CustomizedColumn
            :slotList="['assetStatusSlot']"
            :btnWidth="'120'"
            :isSelect="true"
            :tableColumn="columnList"
            :columnList="columnList"
            :tableData="tableData"
            titleUp="表头设置"
            @tableColumnChange="tableColumnChange"
            @handleSelectionChange="handleSelectionChange"
          >
            <template slot="assetStatusSlot" slot-scope="scope">
              <template v-if="scope.row.row">
                <div class="tag-status">
                  <span
                    :style="{
                      backgroundColor: getStatusTagType(
                        scope.row.row.assetStatus
                      ),
                      height: '8px',
                      width: '8px',
                    }"
                  ></span>
                  <span>{{ statusText(scope.row.row.assetStatus) }}</span>
                </div>
              </template>

              <div v-else>-</div>
            </template>

            <template slot="operationSlot" slot-scope="scope">
              <el-button type="text" @click="preview(scope.row)"
                >查看</el-button
              >
              <el-button type="text" @click="update(scope.row)">编辑</el-button>
            </template>
          </CustomizedColumn>

组件的封装

<template>
  <div style="width: 100%; height: 100%">
    <ColumnUp
      v-if="visibles"
      :visibles.sync="visibles"
      :initTableColumn="initTableColumn"
      :titleUp="titleUp"
      @newinitTableColumn="newTableColumn"
    >
      <template v-slot:content>
        <p>点击文字拖拽</p>
      </template>
    </ColumnUp>
    <el-table
      :key="tableKey"
      class="table"
      v-if="myData.tableColumn.length >= 1"
      @selection-change="handleSelectionChange"
      v-loading="loading"
      ref="table"
      :data="myData.tableData"
      border
      style="width: 100%"
      height="100%"
      :header-cell-style="{
        fontSize: '14px',
        backgroundColor: '#f5f5f5',
        color: '#333',
      }"
    >
      <el-table-column
        v-if="this.isSelect"
        type="selection"
        width="55"
        fixed="left"
      >
      </el-table-column>
      <template>
        <RecursiveColumn
          v-for="column in myData.tableColumn"
          :column="column"
          :key="column.id"
        >
          <!-- <template > -->
          <slot v-for="it in slotList" :slot="it" slot-scope="scope">
            <template>
              <slot :name="it" :row="scope.row" />
            </template>
          </slot>

          <!-- </template> -->
        </RecursiveColumn>
      </template>

      <!-- <el-table-column
        label="操作"
        fixed="right"
        :show-overflow-tooltip="true"
        :width="'auto'"
      >
        <template #header>
          <div class="flex flex-align-c">
            <span>操作</span>
            <i
              @click="handlerSetColumn"
              style="
                font-size: 20px;
                margin-left: 10px;
                cursor: pointer;
                color: blue;
              "
              class="el-icon-setting"
            ></i>
          </div>
        </template>
        <template>
          <el-button type="primary" size="mini">删除</el-button>
        </template>
      </el-table-column> -->

      <el-table-column
        label="操作"
        v-if="showOperation"
        fixed="right"
        :show-overflow-tooltip="true"
        :width="btnWidth"
      >
        <template #header>
          <div class="flex flex-align-c">
            <span>操作</span>
            <i
              @click="handlerSetColumn"
              v-if="showSetHeader"
              style="font-size: 20px; margin-left: 10px; cursor: pointer"
              class="el-icon-setting"
            ></i>
          </div>
        </template>
        <template slot-scope="scope">
          <slot name="operationSlot" :row="scope.row" />
        </template>
      </el-table-column>
      <template slot="empty">
        <div class="table-empty">
          <img src="@/assets/images/empty-table.png" style="width: 220px" />
          <span style="margin-left: 24px">暂无数据~</span>
        </div>
      </template>
    </el-table>
  </div>
</template>
<script>
import ColumnUp from "./column-up";
import RecursiveColumn from "./RecursiveColumn";
export default {
  name: "index",
  props: {
    showOperation: {
      type: Boolean,
      default: true,
    },
    isSelect: {
      type: Boolean,
      default: false,
    },
    titleUp: {
      type: String,
      default: "",
    },
    showSetHeader: {
      type: Boolean,
      default: true,
    },
    tableColumn: {
      type: Array,
      default: [],
    },
    btnWidth: {
      type: String,
      default: "100",
    },
    columnList: {
      type: Array,
      default: () => [],
    },
    slotList: {
      type: Array,
      default: () => [],
    },
    loading: {
      type: Boolean,
      default: false,
    },
    tableData: {
      type: Array,
      // default: () => [
      //   {
      //     id: 2,
      //     name: "胡秋林",
      //     yuangonghao: 123,
      //     ruzhishijian: "2024-09-06",
      //     siling: 10,
      //     banciguanli: 1,
      //     banciguanli2: 22,
      //     laodongguanxi: 1,
      //     huamingce: 1,
      //   },
      // ],
      default: () => [],
    },
  },
  components: {
    ColumnUp,
    RecursiveColumn,
  },
  data() {
    return {
      // loading: false,
      visibles: false,
      tableKey: 1,
      myData: {
        tableData: [],
        tableColumn: [],
      },      initTableColumn: [],
      // tableColumn: [
      //   {
      //     id: 1,
      //     label: "姓名",
      //     prop: "name",
      //     parentTpe: 0,
      //   },
      //   {
      //     id: 3,
      //     label: "劳动关系",
      //     prop: "laodongguanxi",
      //     parentTpe: 0,
      //     children: [
      //       {
      //         id: 7,
      //         label: "入职日期",
      //         prop: "ruzhishijian",
      //         parentTpe: 1, // 二级节点的 parentTpe 指向对应的一级节点 id
      //       },
      //       {
      //         id: 8,
      //         prop: "siling",
      //         label: "司龄",
      //         parentTpe: 1, // 二级节点的 parentTpe 指向对应的一级节点 id
      //       },
      //     ],
      //   },
      // ],
    };
  },
  watch: {
    tableData(val) {
      // console.log("表格数据改变");
      this.$nextTick(() => {
        this.$set(this.myData, "tableData", val);
      });
    },
    tableColumn(val) {
      // console.log('表头数据改变', val)
      this.$nextTick(() => {
        this.$set(this.myData, "tableColumn", val);
      });
    },
  },
  mounted() {
    this.headerDragend();
  },
  created() {
    this.initTableColumn = JSON.parse(JSON.stringify(this.columnList));
    // this.addParentId(this.tableColumn);
  },
  methods: {
    // 表格多选
    handleSelectionChange(ids) {
      this.$emit("handleSelectionChange", ids);
    },
    headerDragend() {
      this.$nextTick(() => {
        if (this.$refs.table && this.$refs.table.doLayout) {
          this.$refs.table.doLayout();
        }
      });
    },
    // addParentId(columns, parentId = null) {
    //   columns.forEach((column) => {
    //     // 如果没有父节点 ID,则将当前节点的 parentId 设置为 null
    //     column.parentId = parentId;
    //     column.checked = true;
    //     // 如果当前节点有子节点,则递归处理子节点
    //     if (column.children && column.children.length > 0) {
    //       this.addParentId(column.children, column.id);
    //     }
    //   });
    // },
    processArray(arr) {
      function updateCheckedStatus(item) {
        if (item.children) {
          // 递归处理子级
          item.children.forEach((child) => updateCheckedStatus(child));
          // 如果子级中有一个是checked为true,那么当前项也应设置为true
          item.checked = item.children.some((child) => child.checked);
        }
        return item;
      }
      // 处理数组并过滤掉结果中的null
      return arr.map((item) => updateCheckedStatus(item));
    },
    // 改变之后
    newTableColumn(column) {
      this.$nextTick(() => {
        this.$refs.table.doLayout();
      })
      this.$emit("tableColumnChange", column);
      this.tableKey += 1;
    },
    handlerSetColumn() {
      this.tableKey += 1;
      // console.log(this.initTableColumn);
      this.initTableColumn = this.columnList;
      this.visibles = true;
    },

    content(row, column, cellValue) {
      return cellValue || cellValue === 0 || cellValue === "0"
        ? cellValue
        : "-"; // 如果 cellValue 为空,则返回 '--'
    },
  },
  computed: {},
};
</script>

<style lang="scss" scoped>
/* 防止表头内容换行的CSS */
::v-deep .el-table .el-table__header-wrapper tr {
  white-space: nowrap;
  line-height: 23px;
}

::v-deep .cell:hover {
  color: none !important;
}

::v-deep .el-table__empty-block {
  width: 100% !important;
}
</style>

column-up


<template>
  <el-dialog
    width="30%"
    :title="titleUp"
    :visible.sync="visible"
    :before-close="handleClose"
  >
    <div>
      <slot name="content"></slot>
      <el-tree
        :key="tableKey"
        
        show-checkbox
        :data="tableColumn"
        :props="defaultProps"
        ref="tree"
        node-key="id"
        draggable
        default-expand-all
        @check="check"
        @check-change="updateCheckedTrueFalse"
        @node-drop="handleNodeDrop"
        :allow-drop="allowDrop"
        :allow-drag="allowDrag"
      >
      </el-tree>
    </div>
    <span slot="footer" class="dialog-footer">
      <el-button @click="resetTree">重置</el-button>
      <span>
        <el-button @click="close">取 消</el-button>
      <el-button type="primary" @click="handleSubmit">确 定</el-button>
      </span>
    </span>
  </el-dialog>
</template>
    <script>
export default {
  props: {
    titleUp: {
      type: String,
      default: "",
    },
    initTableColumn: {
      type: Array,
      default: [],
    },
    visibles: {
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      visible: false,
      defaultProps: {
        children: "children",
        label: "label",
        disabled: "disabled",
      },
      tableKey: 1,
      defaultCheckedNodes: [],
      tableColumn: [],
    };
  },
  watch: {
    visibles(n) {
      this.visible = n;
    },
  },
  mounted() {
    this.tableColumn = JSON.parse(JSON.stringify(this.initTableColumn));
    this.visible = this.visibles;
    this.setCheckedNodesTrue(); // 设置默认勾选
  },
  methods: {
    setCheckedNodesTrue() {
      this.$nextTick(() => {
        this.defaultCheckedNodes = this.findNode(this.initTableColumn, []);
        this.$refs.tree.setCheckedNodes(
          this.findNode(this.initTableColumn, [])
        );
      });
    },
    findNode(node, ary) {
      node.forEach((n) => {
        if (n.checked) ary.push({ id: n.id, label: n.label });
        if (n.children && n.children.length > 0) {
          this.findNode(n.children, ary);
        }
      });
      return ary;
    },
    allowDrop(draggingNode, dropNode, type) {
      // console.log(draggingNode, dropNode, type)
      if (!draggingNode.data.modifiableFlag || !dropNode.data.modifiableFlag) {
        return false;
      }
      if (draggingNode.data.level === dropNode.data.level) {
        if (draggingNode.data.parentId === dropNode.data.parentId) {
          return type === "prev" || type === "next";
        } else {
          return false;
        }
      } else {
        return false;
      }
    },
    updateCheckedStatus(data, id, checked) {
      for (const item of data) {
        if (item.id === id) {
          item.checked = checked;
          break;
        }
        if (item.children && item.children.length) {
          this.updateCheckedStatus(item.children, id, checked);
        }
      }
    },
    updateCheckedTrueFalse(a, b) {
      a.checked = b;
    },
    handleNodeDrop(before,after,inner,event) {
      // let sort1 = before.data.sortNum
      // let sort2 = after.data.sortNum
      // before.data.sortNum = sort2
      // after.data.sortNum = sort1
      this.$nextTick(() => {
        this.$refs.tree.setCheckedNodes(this.defaultCheckedNodes);
      });
    },
    check(data, checkNodes) {
      this.defaultCheckedNodes = checkNodes.checkedNodes;
      // console.log(this.defaultCheckedNodes);
    },
    close() {
      this.tableKey += 1;
      this.tableColumn = JSON.parse(JSON.stringify(this.initTableColumn));
      this.$emit("update:visibles", false);
    },
    allowDrag() {
      return true;
    },
    handleClose(done) {
      done();
      this.close();
    },
    async handleSubmit() {
      // console.log(this.initTableColumn,'this.initTableColumn',this.tableColumn);
      await this.initSort(this.tableColumn)

      // console.log(this.tableColumn, '556666')
      this.$emit("newinitTableColumn", this.tableColumn);
      this.close();
    },

    initSort(arr) {
      arr.map((it, index) => {
        it.sortNum = index + 1
        if(it.children.length >= 1) {
          it.children = this.initSort(it.children)
        }
        return it
      })

      return arr

    },

    resetTree (){
      // 获取所有节点的key
      const allKeys = this.tableColumn.map(node=>node.id)
      // 使用setCheckedKeys全选
      this.$refs.tree.setCheckedKeys(allKeys)
      this.$nextTick(() => {
        this.handleSubmit()
      })
      
    } ,
  },
};
</script>

<style lang="scss" scoped>
::v-deep .el-dialog__body {
  height: 55vh;
  overflow: auto;
}

.dialog-footer {
  width: 100%;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
</style>

checkbox

(除了可以用树形结构tree,那就是自定义复选框了,常常用来做权限管理)

checkbox 复选框(二级)

blog.csdn.net/Hei_lovely_…

<template>
  <el-dialog
    width="80%"
    title="选择设备"
    :visible.sync="visible"
    :before-close="handleClose"
  >
    <div class="equipment-content">
      <div class="flex equipment-content-tree">
        <div class="flex-one equipment-left equipment-share">
          <div class="search-body">
            <el-row :gutter="18">
              <el-col :span="8">
                <el-input
                  placeholder="搜索关键字"
                  size="mini"
                  v-model="searchValue"
                  clearable
                >
                </el-input>
              </el-col>
              <el-col :span="11" class="flex">
                <el-button size="mini" type="primary">搜索</el-button>
                <el-button size="mini">重置</el-button>
              </el-col>
            </el-row>
          </div>
          <div class="tree-body">
            <div v-for="item in list" :key="item.id">
              <div class="flex flex-align-c">
                <el-checkbox
                  :indeterminate="item['indeterminate']"
                  v-model="item['checkbox']"
                  @change="handleCheckChange(item, $event)"
                />
                <div class="flex flex-dir-c flex-jus-c checkbox-right">
                  <b>{{ item.name }}</b>
                  <span>{{ item.mater }}</span>
                </div>
              </div>
            </div>
          </div>
          <el-pagination
            class="text-center"
            background
            layout="prev, pager, next"
            :total="50"
          >
          </el-pagination>
        </div>
        <div class="line"></div>
        <div class="flex-one equipment-right equipment-share">
          <div class="search-body">
            <el-row :gutter="18">
              <el-col :span="8">
                <el-input
                  placeholder="搜索关键字"
                  size="mini"
                  v-model="searchValueChldren"
                  clearable
                >
                </el-input>
              </el-col>
              <el-col :span="11" class="flex">
                <el-button size="mini" type="primary">搜索</el-button>
                <el-button size="mini">重置</el-button>
              </el-col>
            </el-row>
          </div>
          <div class="tree-body">
            <div v-for="item in checkboxChild" :key="item.id">
              <div class="flex flex-align-c">
                <el-checkbox
                  v-model="item['checkbox']"
                  @change="handleCheckedChldrenChange(item, $event)"
                />
                <div class="flex flex-dir-c flex-jus-c checkbox-right">
                  <b>{{ item.name }}</b>
                  <span>{{ item.mater }}</span>
                </div>
              </div>
            </div>
          </div>
          <el-pagination
            class="text-center"
            background
            layout="prev, pager, next"
            :total="50"
          >
          </el-pagination>
        </div>
      </div>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="handleSubmit">保 存</el-button>
        <el-button @click="cancel">取 消</el-button>
      </div>
    </div>
  </el-dialog>
</template>
  <script>
export default {
  components: {},
  props: {
    visibles: {
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      searchValueChldren: "", // 子搜索
      searchValue: "", // 夫搜索
      visible: false, // 开关
      cureentCheckboxItem: {}, // 当前选中得对象
      isIndeterminate: true, // 控制半选全选
      checkboxChild: [], // 右侧显示得子集
      list: [
        {
          id: 0,
          name: "都江堰市",
          mater: "智能分析服务器",
          checkbox: false,
          indeterminate: false,
          children: [
            {
              name: "建安和流与0-0",
              mater: "江安河812大的公司规定",
              checkbox: false,
              id: "015",
            },
            {
              name: "建安和流与0-1",
              mater: "江安河812大的公司规定",
              checkbox: false,
              id: "016",
            },
          ],
        },
        {
          id: 1,
          name: "都江堰市1",
          mater: "智能分析服务器1",
          indeterminate: false,
          checkbox: false,
          children: [
            {
              name: "建安和流与1-1",
              mater: "江安河812大的公司规定",
              checkbox: false,
              id: "115",
            },
            {
              name: "建安和流与1-2",
              mater: "江安河812大的公司规定",
              checkbox: false,
              id: "116",
            },
          ],
        },
      ],
    };
  },
  watch: {
    visibles(n) {
      this.visible = n;
    },
    cureentCheckboxItem(v) {
      this.checkboxChild = this.list.filter((item) => item.id === v.id)[0][
        "children"
      ];
    },
  },

  mounted() {
    console.warn('equipment')
    this.visible = this.visibles;
  },
  methods: {
    // 子点击复选框执行事件
    handleCheckedChldrenChange(val, e) {
      let checkedCount = this.cureentCheckboxItem["children"].filter(
        (item) => item["checkbox"]
      )["length"];
      let cureentList = this.list.filter(
        (item) => item.id == this.cureentCheckboxItem.id
      );
      cureentList[0]["checkbox"] =
        checkedCount === cureentList[0]["children"]["length"];
      cureentList[0]["indeterminate"] =
        checkedCount > 0 && checkedCount < cureentList[0]["children"]["length"];
    },
    // 父点击复选框执行事件
    handleCheckChange(val, e) {
      val.indeterminate = false;
      val.children = val.children.map((item) => {
        item["checkbox"] = e;
        return item;
      });
      // if (!val.checkbox) return;
      this.cureentCheckboxItem = val;
    },
    // 保存
    handleSubmit() {
      this.cancel();
    },
    // 取消
    cancel() {
      this.visible = false;
      this.$emit("update:visibles", false);
    },
    handleClose(done) {
      done();
      this.cancel();
    },
  },
};
</script>
  <style scoped lang='scss'>
::v-deep .el-dialog__body {
  padding: 5px 25px;
}
.equipment-content {
  .equipment-content-tree {
    height: 540px;
    .line {
      width: 0px;
      height: 100%;
      border: 2px solid #ddd;
      margin: 0 40px;
    }
    .tree-body {
      height: 440px;
      overflow-y: auto !important;
      margin-top: 30px;
      .checkbox-right {
        margin-left: 20px;
        line-height: 23px;
      }
    }
  }
}
.dialog-footer {
  margin: 20px 0 10px 0;
  text-align: center;
}
</style>
  

checkbox 复选框(三级或超过三级)

image.png

<template>
  <el-scrollbar style="height: 90%">
    <div class="menuList">
      <el-row style="margin-top: 20px">
        <div class="menuList-table">
          <!-- 总全选-->
          <el-checkbox
            :indeterminate="indeterminate"
            v-model="isCheckAll"
            @change="checkAll"
            >全选
          </el-checkbox>
          <!--这里使用element-ui 的折叠板-->
          <el-collapse>
            <el-collapse-item
              v-for="(one, oneIndex) in menuData"
              :key="oneIndex"
            >
              <template slot="title">
                <!-- 一级 权限列表-->
                <el-checkbox
                  :indeterminate="one.indeterminate"
                  v-model="one.checked"
                  @change="checkedOneAll(oneIndex, one.id, $event)"
                  :key="oneIndex"
                  >{{ one.name }}
                </el-checkbox>
              </template>
              <!-- 二级 权限列表-->
              <el-checkbox
                v-for="(two, twoIndex) in one.children"
                v-model="two.checked"
                @change="
                  checkedTwoAll(oneIndex, twoIndex, two.id, one.id, $event)
                "
                :indeterminate="two.indeterminate"
                :key="twoIndex"
                >{{ two.name }}
                <div style="position: absolute" v-if="two.children.length > 0">
                  <el-checkbox
                    style="display: block; line-height: 2"
                    v-for="(three, threeIndex) in two.children"
                    :key="threeIndex"
                    v-model="three.checked"
                    :title="three.name"
                    @change="
                      checkedThreeAll(
                        oneIndex,
                        twoIndex,
                        threeIndex,
                        three.id,
                        two.id,
                        $event
                      )
                    "
                    >{{ three.name | filterName }}
                  </el-checkbox>
                </div>
              </el-checkbox>
            </el-collapse-item>
          </el-collapse>
        </div>
      </el-row>
    </div>
  </el-scrollbar>
</template>
<script>
export default {
  name: "menuList",
  components: {},
  props: {},
  data() {
    return {
      menuData: [
        {
          id: 73,
          name: "地图",
          children: [
            {
              id: 35,
              name: "实时态势",
              children: [],
              checked: false,
            },
            {
              id: 69,
              name: "实时态势",
              children: [],
              checked: false,
            },
            {
              id: 68,
              name: "统计图",
              children: [],
              checked: false,
            },
            {
              id: 21,
              name: "禁飞区列表",
              children: [],
              checked: false,
            },
            {
              id: 22,
              name: "禁飞区添加",
              children: [],
              checked: false,
            },
          ],
          checked: false,
        },
        {
          id: 74,
          name: "设备",
          children: [
            {
              id: 2,
              name: "无人机列表",
              children: [],
              checked: false,
            },
            {
              id: 79,
              name: "警用无人机列表",
              children: [
                {
                  id: 108,
                  name: "进入警用无人机修改页面",
                  children: [],
                  checked: false,
                },
                {
                  id: 111,
                  name: "警用无人机修改操作",
                  children: [],
                  checked: false,
                },
                {
                  id: 110,
                  name: "进入警用无人机详情页面",
                  children: [],
                  checked: false,
                },
                {
                  id: 151,
                  name: "警用无人机删除",
                  children: [],
                  checked: false,
                },
                {
                  id: 109,
                  name: "进入警用无人机添加页面",
                  children: [],
                  checked: false,
                },
              ],
              checked: false,
            },
            {
              id: 66,
              name: "定位器列表",
              children: [],
              checked: false,
            },
            {
              id: 30,
              name: "无人机厂商列表",
              children: [],
              checked: false,
            },
            {
              id: 3,
              name: "添加无人机",
              children: [],
              checked: false,
            },
            {
              id: 67,
              name: "新增定位器",
              children: [],
              checked: false,
            },
          ],
          checked: false,
        },
        {
          id: 75,
          name: "人员",
          children: [
            {
              id: 59,
              name: "持有者列表",
              children: [],
              checked: false,
            },
            {
              id: 172,
              name: "新增持有者",
              children: [],
              checked: false,
            },
            {
              id: 178,
              name: "待审核名单",
              children: [],
              checked: false,
            },
            {
              id: 192,
              name: "历史用户日志",
              children: [],
              checked: false,
            },
            {
              id: 175,
              name: "任务小组列表",
              children: [],
              checked: false,
            },
          ],
          checked: false,
        },
        {
          id: 76,
          name: "报批",
          children: [
            {
              id: 7,
              name: "飞行计划列表",
              children: [],
              checked: false,
            },
            {
              id: 8,
              name: "飞行计划/报备",
              children: [],
              checked: false,
            },
          ],
          checked: false,
        },
        
        
        {
          id: 80,
          name: "权限",
          children: [
            {
              id: 141,
              name: "警员权限管理",
              children: [
                {
                  id: 142,
                  name: "新增子权限组",
                  children: [],
                  checked: false,
                },
              ],
              checked: false,
            },
            {
              id: 190,
              name: "警员访问权限",
              children: [],
              checked: false,
            },
            {
              id: 193,
              name: "模块访问权限",
              children: [],
              checked: false,
            },
            {
              id: 81,
              name: "权限组列表",
              children: [
                {
                  id: 85,
                  name: "新增权限组",
                  children: [],
                  checked: false,
                },
                {
                  id: 88,
                  name: "查看用户列表",
                  children: [],
                  checked: false,
                },
                {
                  id: 87,
                  name: "查看菜单列表",
                  children: [],
                  checked: false,
                },
                {
                  id: 86,
                  name: "删除权限组",
                  children: [],
                  checked: false,
                },
              ],
              checked: false,
            },
          ],
          checked: false,
        },
        
        
        
       
        
      ],

      isCheckAll: false, //一级全选状态
      indeterminate: false,
    };
  },
  computed: {},
  methods: {
    //总change事件
    checkAll(e) {
      this.ischeckAll = e;
      console.log('eeeeeeeeeeeee',e);
      if (e === true) {
        this.indeterminate = false;
        for (var i = 0, len = this.menuData.length; i < len; i++) {
          //二级全选反选不确定
          this.menuData[i].checked = e;
          this.menuData[i].indeterminate = false;
          for (
            var j = 0, len1 = this.menuData[i].children.length;
            j < len1;
            j++
          ) {
            this.menuData[i].children[j].checked = e;
            for (
              var k = 0, len2 = this.menuData[i].children[j].children.length;
              k < len2;
              k++
            ) {
              this.menuData[i].children[j].children[k].checked = e;
            }
          }
        }
      } else {
        this.indeterminate = false;
        for (let i = 0, len = this.menuData.length; i < len; i++) {
          //三级全选反选不确定
          this.menuData[i].checked = e;
          this.menuData[i].indeterminate = false;
          for (
            let j = 0, len1 = this.menuData[i].children.length;
            j < len1;
            j++
          ) {
            this.menuData[i].children[j].checked = e;
            for (
              let k = 0, len2 = this.menuData[i].children[j].children.length;
              k < len2;
              k++
            ) {
              this.menuData[i].children[j].children[k].checked = e;
            }
          }
        }
      }
    },
    //一级change事件
    checkedOneAll(oneIndex, oneId, e) {
      this.menuData[oneIndex].checked = e; //一级勾选后,子级全部勾选或者取消
      if (e === true) {
        //去掉一级不确定状态
        this.menuData[oneIndex].indeterminate = false;
      }
      let childrenArray = this.menuData[oneIndex].children;
      if (childrenArray.length > 0) {
        childrenArray.forEach((oneItem) => {
          oneItem.checked = e;
          if (oneItem.children.length > 0) {
            oneItem.children.forEach((twoItem) => {
              twoItem.checked = e;
            });
          }
        });
      }
      this.getIsCheckAll();
    },
    //二级change事件
    checkedTwoAll(oneIndex, twoIndex, twoId, oneId, e) {
      var childrenArray = this.menuData[oneIndex].children;
      var tickCount = 0,
        unTickCount = 0,
        len = childrenArray.length;
      for (var i = 0; i < len; i++) {
        if (twoId === childrenArray[i].id) childrenArray[i].checked = e;
        if (childrenArray[i].checked === true) tickCount++;
        if (childrenArray[i].checked === false) unTickCount++;
      }
      //判断二级下面是否还有三级,点击选择二级(选择与不选)时候下面三级是全选还是全不选
      if (childrenArray[twoIndex].children.length > 0) {
        childrenArray[twoIndex].children.forEach((threeItem) => {
          threeItem.checked = e;
        });
        //判断二级是否选中
        childrenArray[twoIndex].checked = e;
        if (e === true) {
          childrenArray[twoIndex].indeterminate = false;
        }
      }
      if (tickCount === len) {
        //二级全勾选
        this.menuData[oneIndex].checked = e;
        this.menuData[oneIndex].indeterminate = false;
      } else if (unTickCount === len) {
        //二级全不勾选
        this.menuData[oneIndex].checked = e;
        this.menuData[oneIndex].indeterminate = false;
      } else {
        this.menuData[oneIndex].checked = e;
        this.menuData[oneIndex].indeterminate = true; //添加一级不确定状态
      }

      this.getIsCheckAll();
    },
    //三级change事件
    checkedThreeAll(oneIndex, twoIndex, threeIndex, threeId, twoId, e) {
      let childrenArray = this.menuData[oneIndex].children[twoIndex].children;
      let tickCount = 0,
        unTickCount = 0,
        len = childrenArray.length;
      for (let i = 0; i < len; i++) {
        if (threeId === childrenArray[i].id) childrenArray[i].checked = e;
        if (childrenArray[i].checked === true) tickCount++;
        if (childrenArray[i].checked === false) unTickCount++;
      }
      if (tickCount === len) {
        //三级全勾选
        this.menuData[oneIndex].children[twoIndex].checked = true;
        this.menuData[oneIndex].children[twoIndex].indeterminate = false;
        this.menuData[oneIndex].checked = true;
        this.menuData[oneIndex].indeterminate = false; //添加二级不确定状态
      } else if (unTickCount === len) {
        //三级全不勾选
        this.menuData[oneIndex].children[twoIndex].checked = false;
        this.menuData[oneIndex].children[twoIndex].indeterminate = false;
        this.menuData[oneIndex].checked = false;
        this.menuData[oneIndex].indeterminate = true; //添加二级不确定状态
        this.isCheckAll = false;
        this.indeterminate = true;
      } else if (tickCount !== len) {
        //三级勾选几个
        this.menuData[oneIndex].children[twoIndex].checked = e;
        this.menuData[oneIndex].children[twoIndex].indeterminate = true;
        this.menuData[oneIndex].checked = false;
        this.menuData[oneIndex].indeterminate = true; //添加二级不确定状态
        this.isCheckAll = false;
        this.indeterminate = true;
      }
      this.getIsCheckAll();
    },
    /**
     *是否全选
     */
    getIsCheckAll() {
      var tickCount = 0,
        unTickCount = 0,
        ArrLength = this.menuData.length;
      for (var j = 0; j < ArrLength; j++) {
        //全选checkbox状态
        if (this.menuData[j].checked === true) tickCount++;
        if (this.menuData[j].checked === false) unTickCount++;
      }
      if (tickCount === ArrLength) {
        //二级全勾选
        this.isCheckAll = true;
        this.indeterminate = false;
      } else if (unTickCount === ArrLength) {
        //二级全不勾选
        this.isCheckAll = false;
        this.indeterminate = false;
      } else {
        this.isCheckAll = false;
        this.indeterminate = true; //添加一级不确定状态
      }
    },
    /**
     * 获取列表数据
     *
     */
    getList() {
      this.menuData.forEach((oneItem, oneIndex) => {
        console.log('+++++',oneIndex);
        if (oneItem.children.length > 0) {
          let oneCountNum = oneItem.children.length;
          let isOneCheckedNum = 0;
          oneItem.children.forEach((twoItem) => {
            if (twoItem.checked) {
              isOneCheckedNum += 1;
            }
            if (twoItem.children.length > 0) {
              let twoCountNum = twoItem.children.length;
              let isTwoCheckedNum = 0;
              twoItem.children.forEach((three) => {
                if (three.checked) {
                  isTwoCheckedNum += 1;
                }
              });
              twoItem.checked = isTwoCheckedNum === twoCountNum;
              twoItem.indeterminate =
                isTwoCheckedNum > 0 && isTwoCheckedNum < twoCountNum;
            }
          });
          oneItem.checked = isOneCheckedNum === oneCountNum;
          oneItem.indeterminate =
            isOneCheckedNum > 0 && isOneCheckedNum < oneCountNum;
        }
      });
    },
  },
  created() {
    this.getList();
  },
  watch: {},
  filters: {
    filterName(value) {
      return value.substring(0, 5) + "...";
    },
  },
};
</script>
<style lang="less" scoped>
/deep/ .el-collapse-item__content {
  padding-bottom: 0;
  min-height: 200px;
  font-size: 33px;
  margin-left: 2%;
}
/deep/.el-checkbox {
  margin-right: 60px !important;
}
</style>

table

通过hover出发table内容可编辑

QQ录屏20240115100731.gif

<template>
  <div class="fingerprintables">
    <el-dialog
      title="编辑人员身份"
      :visible.sync="visible"
      width="600px"
      :before-close="handleClose"
    >
      <el-table
        :data="tableData"
        :row-class-name="tableRowClassName"
        border
        max-height="780"
        style="width: 100%"
        size="mini"
        @cell-mouse-enter="enterTab"
        @cell-mouse-leave="leaveTab"
      >
        <el-table-column
          label="人员身份"
          prop="userTyep"
          :render-header="renderHeader"
        >
          <template slot-scope="scope">
            <span
              v-if="
                scope.row.index === tabClickIndex &&
                tabClickLabel === '人员身份' &&
                scope.row.index != 0
              "
            >
              <el-input
                v-model="scope.row.userTyep"
                type="text"
                maxlength="20"
                placeholder="请输入人员身份"
                size="mini"
                @blur="inputBlur(scope.row)"
              />
            </span>
            <span v-else>{{ scope.row.userTyep }}</span>
          </template>
        </el-table-column>
        <el-table-column label="描述" prop="describe">
          <template slot-scope="scope">
            <span
              v-if="
                scope.row.index === tabClickIndex &&
                tabClickLabel === '描述' &&
                scope.row.index != 0
              "
            >
              <el-input
                v-model="scope.row.describe"
                type="text"
                maxlength="20"
                placeholder="请输入描述"
                size="mini"
                @blur="inputBlur(scope.row)"
              />
            </span>
            <span v-else>{{ scope.row.describe }}</span>
          </template>
        </el-table-column>
        <el-table-column label="状态" prop="status">
          <template slot-scope="scope">
            <el-switch
              v-model="scope.row.status"
              active-color="#13ce66"
              inactive-color="#ff4949"
            >
            </el-switch>
          </template>
        </el-table-column>
      </el-table>
      <span slot="footer" class="dialog-footer">
        <el-button @click="cancel" type="primary">确 定</el-button>
        <el-button @click="cancel">取 消</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
export default {
  name: "test",
  data() {
    return {
      visible: true,
      tableData: [{ userTyep: "默认身份", describe: "默认身份", status: true }],
      tabClickIndex: 0,
      tabClickLabel: "",
    };
  },
  components: {},
  mounted() {},
  methods: {
    cancel() {
      // 关闭按钮
      this.visible = false;
      this.$emit("cancel", false);
    },
    handleClose(done) {
      done();
      this.cancel();
    },
    tableRowClassName({ row, rowIndex }) {
      // 把每一行的索引放进row
      row.index = rowIndex;
    },
    // enterTab row 当前行 column 当前列
    enterTab(row, column, cell, event) {
      switch (column.label) {
        case "人员身份":
          this.tabClickIndex = row.index;
          this.tabClickLabel = column.label;
          break;
        case "描述":
          this.tabClickIndex = row.index;
          this.tabClickLabel = column.label;
          break;
        default:
          return;
      }
      console.log("enterTab", this.tabClickIndex, row.adName, row.userTyep);
    },
    leaveTab() {
      this.tabClickIndex = null;
      this.tabClickLabel = "";
    },
    // 失去焦点初始化
    inputBlur(row) {
      // console.log('row', row)
      this.tabClickIndex = null;
      this.tabClickLabel = "";
    },
    renderHeader(h) {
      return (
        <div>
          <span>人员身份</span>
          <i
            class="el-icon-plus set-icon"
            style="font-size:15px;margin-left:10px;cursor: pointer"
            onClick={this.handelAddRow}
          ></i>
        </div>
      );
    },
    handelAddRow() {
      this.tableData.push({ userTyep: "", describe: "", status: false });
    },
  },
};
</script>
<style scoped lang='scss'>
</style>

页码动态计算高度

<template>

<div :class="{'hidden':hidden}" class="pagination-container">

<el-pagination

:background="background"

:current-page.sync="currentPage"

:page-size.sync="pageSize"

:layout="layout"

:page-sizes="pageSizes"

:total="total"

v-bind="$attrs"

@size-change="handleSizeChange"

@current-change="handleCurrentChange"

/>

</div>

</template>

  


<script>

import { scrollTo } from '@/utils/scroll-to'

  


export default {

name: 'Pagination',

props: {

total: {

required: true,

type: Number

},

page: {

type: Number,

default: 1

},

limit: {

type: Number,

default: 20

},

pageSizes: {

type: Array,

default() {

return [10, 20, 30, 50]

}

},

layout: {

type: String,

default: 'total, sizes, prev, pager, next, jumper'

},

background: {

type: Boolean,

default: true

},

autoScroll: {

type: Boolean,

default: true

},

hidden: {

type: Boolean,

default: false

}

},

computed: {

currentPage: {

get() {

return this.page

},

set(val) {

this.$emit('update:page', val)

}

},

pageSize: {

get() {

return this.limit

},

set(val) {

this.$emit('update:limit', val)

}

}

},

methods: {

handleSizeChange(val) {

this.$emit('pagination', { page: this.currentPage, limit: val })

if (this.autoScroll) {

scrollTo(0, 800)

}

},

handleCurrentChange(val) {

this.$emit('pagination', { page: val, limit: this.pageSize })

if (this.autoScroll) {

scrollTo(0, 800)

}

}

}

}

</script>

  


<style scoped>

.pagination-container {

background: #fff;

padding: 0;

}

.pagination-container.hidden {

display: none;

}

</style>
<Pagination

v-show="devicePagination.total > 0"

slot="down"

:total="devicePagination.total"

:page.sync="devicePagination.page"

:limit.sync="devicePagination.limit"

@pagination="getDeviceList"

/>

el-table合计总计

1737090392541.jpg

<template>
  <div class="component-main">
    <div class="component-table">
      <el-table
        :data="tableData"
        border
        max-height="300"
        :header-cell-style="{
          fontSize: '14px',
          backgroundColor: '#f5f5f5',
          color: '#333',
        }"
      >
        <el-table-column prop="index" label="序号" width="60">
          <template v-slot="scope">
            {{ scope.$index + 1 }}
          </template>
        </el-table-column>

        <el-table-column prop="itemName" label="物品名称">
          <template v-slot="scope">
            <el-input
              v-model="scope.row.itemName"
              placeholder="请输入内容"
            ></el-input>
          </template>
        </el-table-column>
        <el-table-column prop="specificationModel" label="规格型号">
          <template v-slot="scope">
            <el-input
              v-model="scope.row.specificationModel"
              placeholder="请输入内容"
            ></el-input>
          </template>
        </el-table-column>
        <el-table-column prop="purchaseUnit" label="单位">
          <template v-slot="scope">
            <el-input
              v-model="scope.row.purchaseUnit"
              placeholder="请输入内容"
            ></el-input>
          </template>
        </el-table-column>
        <el-table-column prop="itemNumber" label="申购数量">
          <template v-slot="scope">
            <el-input
              v-model="scope.row.itemNumber"
              placeholder="请输入内容"
              @input="validateSize(scope.row)"
            ></el-input>
          </template>
        </el-table-column>
        <el-table-column prop="unitPrice" label="预购单价(元)">
          <template v-slot="scope">
            <el-input
              v-model="scope.row.unitPrice"
              placeholder="请输入内容"
              @input="validateNum(scope.row)"
            ></el-input>
          </template>
        </el-table-column>
        <el-table-column prop="totalPrice" label="总价(元)">
          <template #default="scope">
            <span>{{
              scope.row.totalPrice === "-" ? "-" : formatPrice(scope.row.totalPrice)
            }}</span>
          </template>
        </el-table-column>
        <el-table-column prop="remark" label="备注">
          <template v-slot="scope">
            <el-input
              v-model="scope.row.remark"
              placeholder="请输入内容"
            ></el-input>
          </template>
        </el-table-column>
        <el-table-column fixed="right" label="操作" :width="'100px'">
          <template v-slot="scope">
            <div>
              <el-button
                type="text"
                size="small"
                style="color: red"
                @click="handleDelete(scope.$index)"
                >删除</el-button
              >
            </div>
          </template>
        </el-table-column>
        <template slot="empty">
          <span>暂无数据~</span>
        </template>
      </el-table>
    </div>

    <div class="table-btns" v-if="tableData.length">
      共{{ tableData.length }}件,合计{{ formatPrice(totalPrice) }}元
    </div>

    <!-- 添加一行按钮 -->
    <div class="table-btn" @click="addRow">
      <i class="el-icon-plus"></i>
      添加一行
    </div>
  </div>
</template>

<script>
export default {
  props: {
    tableData: {
      type: Array,
      default: () => [],
    },
  },
  data() {
    return {
      addHeight: 6,
    };
  },
  computed: {
    // 计算总价
    totalPrice() {
      return this.tableData.reduce((sum, row) => {
        return sum + (row.totalPrice !== "-" ? row.totalPrice : 0);
      }, 0);
    },
  },
  methods: {
    // 添加新行
    addRow() {
      this.addHeight = this.addHeight + 6;
      this.$emit("addRow");
    },
    handleClick(address) {
      this.fileUrl = address;
      this.lookFilesDialogVisible = true;
    },
    handleDelete(idx) {
      this.$confirm("确认删除?", "提示", { type: "warning" }).then(() => {
        this.$emit("deleteRow", idx);
      });
    },
    // 验证金额(限制为最多两位小数)
    validateNum(row) {
      let value = row.unitPrice;
      // 仅允许数字和最多两位小数
      value = value.replace(/[^\d.]/g, ""); // 移除非数字和小数点字符
      value = value.replace(/\.{2,}/g, "."); // 确保只允许一个小数点
      value = value.replace(/^0+(\d)/, "$1"); // 去掉前导多余零
      value = value.replace(/^(\d+)\.(\d{2}).*$/, "$1.$2"); // 限制两位小数
      row.unitPrice = value; // 更新金额值
      this.updatePrice(row);
    },

    // 验证数量(限制为整数)
    validateSize(row) {
      let value = row.itemNumber;
      // 仅允许整数
      value = value.replace(/[^\d]/g, ""); // 移除非数字字符
      row.itemNumber = value; // 更新数量值
      this.updatePrice(row);
    },
    formatPrice(value) {
      return value ? value.toFixed(2) : "0.00";
    },
    updatePrice(row) {
      const unitPrice = parseFloat(row.unitPrice) || 0;
      const itemNumber = parseFloat(row.itemNumber) || 0;
      row.totalPrice =
        unitPrice > 0 && itemNumber > 0 ? parseFloat((unitPrice * itemNumber).toFixed(2)) : "-";
    },
  },
};
</script>

<style lang="scss" scoped>
.component-main {
  padding: 20px;
}

.table-btn,
.table-btns {
  opacity: 0.9;
  font-size: 14px;
  color: #616367;
  height: 40px;
  line-height: 40px;
  text-align: center;
  border: 1px solid #e4e4e4;
  border-top: none;
}

.table-btn:hover {
  cursor: pointer;
  color: #039340;
}

::v-deep .lookFilesDialog .el-dialog {
  width: 100%;
  height: 100vh;
  margin-top: 0px !important;
  display: flex;
  flex-direction: column;
}

::v-deep .lookFilesDialog .el-dialog__body {
  height: 100%;
}

::v-deep .el-dialog__wrapper {
  overflow: hidden;
}
</style>

封装简单的table

image.png

组件.vue
<template>
  <div class="table">
    <el-table :data="tableData" style="width: 100%" :height="height">
      <el-table-column
        v-if="column[0].selection"
        type="selection"
        width="42"
        align="center"
      />
      <template v-for="item in column">
        <el-table-column
          v-loading="loading"
          v-if="item.slot"
          :key="item.key"
          :label="item.name"
          :prop="item.key"
          :min-width="item.minWidth"
          :align="item.align || 'center'"
          :width="item.width"
          :fixed="item.fixed"
        >
          <template slot-scope="scope">
            <slot
              :name="item.key"
              :row="scope.row"
              :index="scope.$index"
            ></slot>
          </template>
        </el-table-column>
        <el-table-column
          v-else
          :key="item.key"
          v-loading="loading"
          :label="item.name"
          :prop="item.key"
          :min-width="item.minWidth"
          :align="item.align || 'center'"
          :width="item.width"
          :fixed="item.fixed"
        />
      </template>
    </el-table>
  </div>
</template>
  
  <script>
export default {
  props: {
    column: {
      type: Array,
      default: [],
    },
    tableData: {
      type: Array,
      default: [],
    },
    loading: {
      type: Boolean,
      default: false,
    },
    height: {
      type: Number,
      default: 250,
    },
  },
  data() {
    return {
      currentIndex: null,
    };
  },
  components: {},
  mounted() {},
  methods: {
    handlerClick(item, index) {
      if (this.slotType.includes("locating")) {
        this.currentIndex = index;
        this.$bus.$emit(this.slotType, item);
      }
    },
    handlerItemClick(item) {
      this.$emit("handlerItemClick", item);
    },
  },
};
</script>
  <style scoped lang='scss'>
.table-font {
  font-size: 16px !important;
  line-height: 28px;
  color: rgba(255, 255, 255, 0.9);
  font-size: 16px;
  font-family: Source Han Sans CN;
  font-weight: 400;
}
.table {
  margin-top: 10px;
  .w50 {
    width: 50px !important;
  }
  .table-tr {
    margin: 5px 0;
    color: rgba(152, 173, 197, 0.8) !important;
  }

  .table-chlid-tr {
    cursor: pointer;
    font-size: 13px;
    opacity: 0.9;

    .table-chlid-locating {
      transition: all 0.8s;
      width: 20px;
      height: 20px;
      cursor: pointer;
    }
    .active {
      transition: all 0.8s;
      background-color: rgba($color: #ddd, $alpha: 0.1);
      border: 1px dashed #ddd;
    }

    &:nth-child(even) {
      background-image: url("../../../../assets/images/list-backg.png");
      background-repeat: no-repeat;
      background-size: 100% 100%;
    }
  }
}
/*最外层透明*/
::v-deep .el-table,
::v-deep .el-table__expanded-cell {
  background-color: transparent;
  border: none !important;
}
/* 表格内背景颜色 */
::v-deep .el-table th,
::v-deep .el-table tr,
::v-deep .el-table td {
  background-color: transparent !important;
  // border: none !important;
  border: none;
  color: #fff !important;
}

::v-deep .el-table__row {
  &:nth-child(even) {
    background-image: url("../../../../assets/images/list-backg.png");
    background-repeat: no-repeat;
    background-size: 100% 100%;
  }
}

::v-deep .el-table__body-wrapper .is-scrolling-none {
  border: none !important;
}
::v-deep .el-table__body-wrapper::-webkit-scrollbar {
  /*width: 0;宽度为0隐藏*/
  width: 0px;
}
::v-deep .el-table__body-wrapper::-webkit-scrollbar-thumb {
  border-radius: 2px;
  height: 50px;
  background: #eee;
}
::v-deep .el-table__body-wrapper::-webkit-scrollbar-track {
  box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
  border-radius: 2px;
  background: rgba(0, 0, 0, 0.4);
}
::v-deep .el-table .cell{
  line-height: 15px !important;
}
</style>

使用

<MapTable :height="680" :column="column" :tableData="tableData" :loading="loading">
        <template #location="scope">
          <div class="flex flex-align-c">
            <img
              src="../../../../../../../assets/images/locating.png"
              class="table-chlid-locating"
              :class="currentIndex == scope.index ? 'active' : ''"
              alt=""
              @click="handlerClick(scope)"
            />
            <div style="margin-left: 10px">{{ scope.index + 1 }}</div>
          </div>
        </template>
        <template #action="scope">
          <el-button type="primary" size="mini" >查询</el-button>
        </template>
        <template #address="scope">
            <el-link @click="handlerItemClick(scope.row)" type="primary">{{
              scope.row.address
            }}</el-link>
          </template>
      </MapTable>
      
       column: [
        { key: "location", name: "序号", slot: true },
        { key: "baseStationGroupName", name: "名称" },
        { key: "teamNum", name: "经度" },
        { key: "personNum", name: "维度" },
        { key: "action", name: "",slot: true },
      ],
      tableData: [],

前端实现左右分页查询和勾选状态(不依靠后端)

组件(夫)equipment

<template>
  <el-dialog
    width="50%"
    title="选择设备"
    :visible.sync="visible"
    :before-close="handleClose"
  >
    <div class="equipment-content">
      <div
        v-if="initConfigLinst.length > 0"
        class="flex equipment-content-tree"
      >
        <Custom
          ref="Custom0"
          :key="initConfigLinst.length"
          v-if="initConfigLinst.length > 0"
          :list="initConfigLinst"
          :search="leftSearch"
          :selected="false"
        />
        <div class="line"></div>
        <Custom
          ref="Custom1"
          :key="initLinst.length"
          v-if="initLinst.length > 0"
          :list="initLinst"
          :search="rightSearch"
          :selected="true"
        />
      </div>
      <div v-else style="height: 300px" v-loading="true"></div>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="handleSubmit">保 存</el-button>
        <el-button @click="cancel">取 消</el-button>
      </div>
    </div>
  </el-dialog>
</template>
  <script>
import Custom from "./custom.vue";
export default {
  components: {
    Custom,
  },
  props: {
    visibles: {
      type: Boolean,
      default: false,
    },
    inspectType: {
      type: Number,
      default: 1,
    },
  },
  data() {
    return {
      visible: false, // 开关
      initConfigLinst: [], //待选
      initLinst: [], //已选择
      leftSearch: {
        // 左侧搜索
        inspectType: 1, //	巡检方式,1-按时间段,2-按次数
        pageNum: 1,
        pageSize: 9,
        searchKey: "",
        taskModule: "", // 任务模块,巡检方式为时间段时需要传入,按次数时不传
      },
      rightSearch: {
        // 右侧侧搜索
        inspectType: 1, //	巡检方式,1-按时间段,2-按次数
        pageNum: 1,
        pageSize: 9,
        searchKey: "",
        taskModule: "", // 任务模块,巡检方式为时间段时需要传入,按次数时不传
      },
    };
  },
  watch: {
    visibles(n) {
      this.visible = n;
    },
  },
  created() {
    // 默认值附带组件参数
    this.getDetail();
    // TOOD渲染获取设备配置分页列表
    this.renderDevice();
  },
  mounted() {
    this.eventBus();
    console.warn("equipment");
  },
  methods: {
    // 验证格式
    isArrayWithObjectsOfFormat(arr) {
      return (
        Array.isArray(arr) &&
        arr.every(
          (item) =>
            typeof item === "object" && item !== null && "primaryId" in item
        )
      );
    },
    // 默认值附带组件参数比如当前的第几项
    getDetail() {
      this.visible = this.visibles;
      // console.log(this.$attrs.equipmentInfo, "equipmentInfo");
      this.leftSearch.inspectType = this.inspectType;
      this.inspectType == 1
        ? (this.leftSearch.taskModule =
            "任务" + (Number(this.$attrs.equipmentInfo.index) + 1))
        : delete this.leftSearch.taskModule;
      this.rightSearch.inspectType = this.inspectType;
      this.inspectType == 1
        ? (this.rightSearch.taskModule =
            "任务" + (Number(this.$attrs.equipmentInfo.index) + 1))
        : delete this.leftSearch.taskModule;
      this.getRightChebox();
    },
    async renderDevice() {
      let [url, params] = [];
      url = "/eps-biz-service/api/v1/inspect/list/device/config";
      params = {
        inspectType: this.inspectType, //	巡检方式,1-按时间段,2-按次数
        pageNum: 1,
        pageSize: 9999,
        searchKey: "",
      };
      this.inspectType == 1
        ? (params.taskModule =
            "任务" + (Number(this.$attrs.equipmentInfo.index) + 1))
        : delete params.taskModule;
      const res = await this.$ajax.get(url, params);
      this.responseSuuces(res, () => {
        console.warn(res);
        let { list } = res.data;
        list = list.map((item) => {
          item.selected = false;
          return item;
        });
        // 获取渲染右侧数据
        this.getRightChebox();
        // 判断左侧的勾选默认放到右侧勾选中
        this.transitionSelected(list);
      });
    },
    // 判断左侧的勾选默认放到右侧勾选中
    transitionSelected(list) {
      if (this.initLinst.length) {
        for (let i = 0; i < list.length; i++) {
          const matchingIndex = this.initLinst.findIndex(
            (item) => item.primaryId === list[i].primaryId
          );
          if (matchingIndex !== -1) {
            list[i].selected = true;
          }
        }
      }
      this.initConfigLinst = list;
    },
    // 获取渲染右侧数据
    getRightChebox() {
      if (
        this.isArrayWithObjectsOfFormat(
          this.$attrs.equipmentInfo.deviceUpdateList
        )
      ) {
        this.initLinst = this.$attrs.equipmentInfo.deviceUpdateList;
      } else {
        this.initLinst = [];
      }
    },
    responseSuuces(res, fn) {
      res.code === 200 ? fn() : this.$message.error(res.message);
    },

    // 保存
    handleSubmit() {
      let obj = this.$attrs.equipmentInfo;
      obj.deviceUpdateList = this.initLinst;
      this.$emit("handleSubmit", obj);
      this.cancel();
    },
    // 两则的勾选穿梭
    eventBus() {
      this.$bus.$on("gaibains", (v, b) => {
        let index = this.initLinst.findIndex(
          (item) => item.primaryId === v.primaryId
        );
        // 如果是左边点击
        if (b) {
          this.initLinst.splice(index, 1);
          this.initConfigLinst.forEach((item) => {
            if (item.primaryId == v.primaryId) {
              item.selected = v.selected;
            }
          });
          // 如果是右边点击
        } else {
          if (v.selected) {
            if (index == -1) this.initLinst.unshift(v);
          } else {
            this.initLinst.splice(index, 1);
          }
        }
      });
    },
    // 取消
    cancel() {
      this.visible = false;
      this.$emit("update:visibles", false);
    },
    handleClose(done) {
      done();
      this.cancel();
    },
  },
  destroyed() {
    this.$bus.$off("gaibains"); // bus
  },
};
</script>
  <style scoped lang='scss'>
::v-deep .el-dialog__body {
  padding: 5px 25px;
}
.equipment-content {
  .equipment-content-tree {
    height: 540px;
    .line {
      width: 0px;
      height: 100%;
      border: 2px solid #ddd;
      margin: 0 40px;
    }
  }
}
.dialog-footer {
  margin: 20px 0 10px 0;
  text-align: center;
}
</style>
  

上传

<el-upload accept=".png, .jpg,.mp4" action="" class="avatar-uploader" :show-file-list="false"
    :on-change="changeupload" :http-request="handelrUploads">
    <img v-if="initImageUrl" :src="initImageUrl" class="avatar">
    <i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
  handelrUploads(param) {
      var file = param.file
      let formData = new FormData()
      formData.append("file", file);
      this.$ajax.uploads('/eps-biz-service/api/v1/inspectAnalysis/imageUpload', formData).then((res) => {
        res.code === 200 ? this.imageUrl = res.data : this.$message.error(res.message)
      })
        .catch(err => {
          console.log(err)
        })
    },
    changeupload(file) {
      const isMP4 = file.raw.type === 'video/mp4'
      const isJPG = file.raw.type === 'image/jpeg'
      const isPNG = file.raw.type === 'image/png'
      const isJPEG = file.raw.type === 'image/jpeg'
      if (!isPNG && !isJPG && !isMP4 && !isJPEG) {
        this.$message.warning('只能上传图片和视频!')
        return false
      } else {
        this.initImageUrl = URL.createObjectURL(file.raw);//赋值图片的url,用于图片回显功能
      }
    },

组件(子)custom

<template>
  <div class="equipment-left equipment-share">
    <div class="search-body">
      <el-row :gutter="18">
        <el-col :span="8">
          <el-input
            placeholder="搜索关键字"
            size="mini"
            v-model="search.searchKey"
            clearable
          >
          </el-input>
        </el-col>
        <el-col :span="11" class="flex">
          <el-button size="mini" type="primary" @click="dataFilter"
            >搜索</el-button
          >
          <el-button size="mini" @click="CZ()">重置</el-button>
        </el-col>
      </el-row>
    </div>
    <div class="tree-body">
      <div v-for="item in resUltList" :key="item.id">
        <div class="flex flex-align-c">
          <el-checkbox
            v-model="item['selected']"
            @change="handleCheckChange(item, $event)"
          />
          <div class="flex flex-dir-c flex-jus-c checkbox-right">
            <b>{{ item.organization }}</b>
            <span>{{ item.deviceName }}</span>
          </div>
        </div>
      </div>
    </div>
    <!-- :hide-on-single-page="true" -->
    <el-pagination
      class="text-center"
      background
      layout="prev, pager, next"
      :total="total"
      @current-change="(e) => handleCurrentChange(e, 'config')"
      :current-page.sync="search.pageNum"
      :page-size="search.pageSize"
    >
    </el-pagination>
  </div>
</template>

<script>
export default {
  name: "custom",
  props: {
    search: {
      type: Object,
      default: {},
    },

    list: {
      type: Array,
      default: [],
    },
  },
  components: {},
  data() {
    return {
      resUltList: [],
      total: 0,
    };
  },
  created() {},
  mounted() {
    this.dataFilter();
  },
  methods: {
    dataFilter() {
      // name过滤
      let list = this.list.filter((item, index) => {
        if (this.search.searchKey === "") {
          return item;
        }
        return item.deviceName.includes(this.search.searchKey);
      });
      // 实现分页
      this.resUltList = list.filter(
        (item, index) =>
          index < this.search.pageNum * this.search.pageSize &&
          index >= this.search.pageSize * (this.search.pageNum - 1)
      );
      // 总数量赋值
      this.total = list.length;
    },
    handleCheckChange(val, e) {
      val.selected = e;
      this.$bus.$emit("gaibains", val, this.$attrs.selected);
      //   this.dataFilter();
    },
    handleCurrentChange(e, type) {
      this.search.pageNum = e;
      this.dataFilter();
    },
    CZ() {
      this.search.pageNum = 1;
      this.search.pageSize = 10;
      this.search.searchKey = "";
      this.dataFilter();
    },
  },
};
</script>

<style lang='scss' scoped>
.tree-body {
  height: 440px;
  overflow-y: auto !important;
  margin-top: 30px;
  .checkbox-right {
    margin-left: 20px;
    line-height: 23px;
  }
}
</style>

image.png

<el-form-item prop="password">
          <span slot="label" style="display: inline-block">
            人员密码
            <el-tooltip effect="dark" content="默认密码为123456" placement="bottom">
              <i class="el-icon-question" />
            </el-tooltip>
          </span>
          <el-input v-model="forms.password" clearable type="password" style="width: 250px" placeholder="******"
            maxlength="50" />
        </el-form-item>

image.png

 <el-form-item label="车身颜色" prop="color">
            <el-select v-model="form.color" placeholder="请选择车牌颜色" style="width: 220px">
              <template v-slot:prefix>
                <div class="square test" :style="{
                  backgroundColor: getBackgrund(form.color, 'color', color),
                }"></div>
              </template>
              <el-option v-for="item in color" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue">
                <div class="flex flex-align-c">
                  <div class="square" :style="{
                    backgroundColor: item.cssClass
                  }"></div>
                  <span>{{ item.dictLabel }}</span>
                </div>
              </el-option>
            </el-select>
          </el-form-item>

antd

table

通过操作按钮展开

image.png

image.png

 <a-table
          :height="500"
          :columns="columns"
          :data-source="dataSource"
          :pagination="false"
          :rowKey="
            (record, index) => {
              return record.id;
            }
          "
          :expandedRowKeys="expandedRowKeys"
          :expandIconAsCell="false"
          :expandIconColumnIndex="-1"
        >
          <span slot="action" slot-scope="text, record, index" >
            <a @click="handleExpand(record,index)" v-show="record.detail.length" >{{
              expandedRowKeys[0] == record.id ? "收起" : "展开"
            }}</a>
            <a-divider type="vertical"  v-show="record.detail.length"/>
            <a @click="handleAction('edit', record, index)">编辑</a>
            <a-divider type="vertical" />
            <a-popconfirm
              v-if="dataSource.length"
              title="是否确定删除?"
              @confirm="() => handleDelete(record, index)"
            >
              <a style="color: red">删除</a>
            </a-popconfirm>
          </span>
          <div
            class="expendTableCell"
            slot="expandedRowRender"
            slot-scope="record"
            style="margin: 0"
          >
            <div style="margin: 5px 10px">
              <ul class="flex expandedRowRender-children" v-for="(item,index) in record.detail" :key="index">
                <li>状态:{{ item.status == 1 ? '展开的内容' : '发射中' }}</li>
              </ul>
            </div>
          </div>
        </a-table>
        data(){
    return{
  columns: [
        {
          title: "任务目标",
          dataIndex: "joinName",
          align: "left",
        },
        {
          title: "分配",
          dataIndex: "allocation",
          align: "center",
        },
        {
          title: "状态",
          dataIndex: "status",
          align: "center",
        },
        {
          title: "创建时间",
          dataIndex: "createdAt",
          align: "center",
        },
        {
          title: "操作",
          dataIndex: "action",
          width: 250,
          align: "center",
          scopedSlots: {
            customRender: "action",
          },
        },
      ],
      dataSource: [],
            expandedRowKeys: [],

  }
}
methods: {
  handleExpand(record,index) {
      let key = record.id;
      if (this.expandedRowKeys.length > 0) {
        //判断当前点击行是否已展开,若展开则把当前key从expandedRowKeys中移除,代表关闭当前展开列
        let index = this.expandedRowKeys.indexOf(key);
        if (index > -1) {
          this.expandedRowKeys.splice(index, 1);
        } else {
          //关闭其他展开行,展开当前点击行
          this.expandedRowKeys = [];
          this.expandedRowKeys.push(key);
        }
      } else {
        //如果当前没有展开列,把当前行绑定的唯一key值放到expandedRowKeys数组中
        this.expandedRowKeys.push(key);
      }
}
        

不通过操作栏点击,点击行触发

 <a-table
          :rowKey="(record) => record.id"
          :customRow="handleRowClick"
          :scroll="{ y: height - 250 }"
          :columns="columns"
          :data-source="list"
          :height="height - 250"
          v-if="list.length"
          :loading="loading"
          :pagination="false"
        >
        </a-table>
        
          // 给table点击行事件
    handleRowClick(record, index) {
      return {
        on: {
          click: async (v) => {
            this.width = 365;
            const { roomNumber } = record;
            // 2353后端目前只对这个数字做了处理,并且只传数字
            const res = await this.ajax.post("data/room/self", 2353);
            if (res.code == 0) {
              this.recordDetail = res.extendData;
            } else {
              this.$message.error("房间详情请求出错!请联系技术人员");
            }
          },
        },
      };
    },

进度条

<template>
    <div class="slider" ref="slider" @click.stop="handelClickSlider">
        <div class="process" :style="{ width, background: bgColor }"></div>
        <div class="thunk" ref="trunk" :style="{ left }">
            <div class="block" ref="dot"></div>
        </div>
    </div>
</template>
<script>
/*
 * min 进度条最小值
 * max 进度条最大值
 * v-model 对当前值进行双向绑定实时显示拖拽进度
 * */
export default {
    props: {
        // 最小值
        min: {
            type: Number,
            default: 0,
        },
        // 最大值
        max: {
            type: Number,
            default: 100,
        },
        // 当前值
        value: {
            type: Number,
            default: 0,
        },
        // 进度条颜色
        bgColor: {
            type: String,
            default: "#4ab157",
        },
        // 是否可拖拽
        isDrag: {
            type: Boolean,
            default: true,
        },
    },
    data() {
        return {
            slider: null, //滚动条DOM元素
            thunk: null, //拖拽DOM元素
            per: this.value, //当前值
        };
    },
    mounted() {
        this.slider = this.$refs.slider;
        this.thunk = this.$refs.trunk;
        var _this = this;
        if (!this.isDrag) return;
        this.thunk.onmousedown = function (e) {
            var width = parseInt(_this.width);
            var disX = e.clientX;
            document.onmousemove = function (e) {
                // value, left, width
                // 当value变化的时候,会通过计算属性修改left,width
                // 拖拽的时候获取的新width
                var newWidth = e.clientX - disX + width;
                // 计算百分比
                var scale = newWidth / _this.slider.offsetWidth;
                _this.per = Math.ceil((_this.max - _this.min) * scale + _this.min); //取整
                // 限制值大小
                _this.per = Math.max(_this.per, _this.min);
                _this.per = Math.min(_this.per, _this.max);

                _this.$emit("input", _this.per);
            };
            document.onmouseup = function () {
                //当拖拽停止发送事件
                _this.$emit("stop", _this.per);
                //清除拖拽事件
                document.onmousemove = document.onmouseup = null;
            };
        };
    },
    methods: {
        handelClickSlider(event) {
            //禁止点击
            if (!this.isDrag) return;
            const dot = this.$refs.dot;
            if (event.target == dot) return;
            //获取元素的宽度l
            let width = this.slider.offsetWidth;
            //获取元素的左边距
            let ev = event || window.event;
            //获取当前点击位置的百分比
            let scale = ((ev.offsetX / width) * 100).toFixed(2);
            this.per = scale;
        },
    },
    computed: {
        // 设置一个百分比,提供计算slider进度宽度和trunk的left值
        // 对应公式为  当前值-最小值/最大值-最小值 = slider进度width / slider总width
        // trunk left =  slider进度width + trunk宽度/2
        scale() {
            return (this.per - this.min) / (this.max - this.min);
        },
        width() {
            return this.slider ? this.slider.offsetWidth * this.scale + "px" : "0px";
        },
        left() {
            return this.slider ? this.slider.offsetWidth * this.scale - this.thunk.offsetWidth / 2 + "px" : "0px";
        },
    },
    watch: {
        value: {
            handler: function () {
                this.per = this.value;
            },
        },
    },
};
</script>

<style scoped>
.box {
    margin: 100px auto 0;
    width: 50%;
}

.clear:after {
    content: "";
    display: block;
    clear: both;
}

.slider {
    position: relative;
    margin: 20px 0;
    width: 30%;
    height: 10px;
    top: 50%;
    background: #747475;
    border-radius: 5px;
    cursor: pointer;
    z-index: 99999;
}

.slider .process {
    position: absolute;
    left: 0;
    top: 0;
    width: 112px;
    height: 10px;
    border-radius: 5px;
    background: #4ab157;
    z-index: 111;
}

.slider .thunk {
    position: absolute;
    left: 100px;
    top: -4px;
    width: 10px;
    height: 6px;    
    z-index: 122;
}

.slider .block {
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 1);
    transition: 0.2s all;
}

.slider .block:hover {
    transform: scale(1.1);
    opacity: 0.6;
}
</style>


<!--  <slisd :min="0" :max="100" :value="50" :isDrag="true" bgColor="#4ab157"></slisd> -->
  

树行下拉框

image.png

<el-select
            ref="treeSelect"
            v-model="params.departmentName"
            placeholder="请选择"
            :popper-append-to-body="false"
        >
          <el-option
              :value="selectTree"
              class="setstyle"
              style="overflow: auto; height: 100%"
              disabled
          >
            <el-tree
                style="min-height: 50px; max-height: 150px"
                :data="depList"
                 :props=" {
                children : 'children' ,
                label : 'label' ,
              }"
                ref="tree"
                check-strictly
                :expand-on-click-node="false"
                :accordion="true"
                @node-click="(data) => addAdminHandleNodeClick(data)"
            ></el-tree>
          </el-option>
        </el-select>

 addAdminHandleNodeClick (node){
      this.$refs.treeSelect.visible = false
      this.$emit('on-dep-select',node)
    } ,