vue2x的语法

背景:使用table组件模仿windows文件列表

效果:如下图

文件列表不需要分页,因此这里没有做分页功能。

基础结构:

<div class="full" style="overflow: auto;user-select: none;position:relative;" >
    <a-table class="file_list_table"
             :columns="columns"
             :data-source="data"
             :pagination="false"
             size="small"
             :showSorterTooltip="false"
             :customRow="setRow"
             @resizeColumn="handleResizeColumn"
             @change="change_table"
             style="padding:0 10px; "
    >

   </a-table>

</div>

class是为了用来修改当前a-table组件样式用的,并且不影响其他地方正常使用a-table的样式。

columns放表头信息

data-source放表数据信息

pagination 是否展示分页组件

size设置表大小,可以看做缩放大小,这里参考windows系统的文件管理器,设置最小

showSorterTooltip 鼠标放在表头时,是否弹出提醒排序信息(很丑)

customRow  设置行属性,主要是用来设置行点击事件,双击事件。(这里针对单双击事件冲突问题a-table没有处理,难题交给了我们开发者,同时设置单双击事件时,双击会触发一次双击事件和触发两次单击事件)

resizeColumn 拖拽表头宽度的鼠标事件,会告知鼠标移动了多少距离,拖动的哪一个表头字段

change  发生排序,筛选,分页页码切换会触发该事件

.....太多了,不懂的看文档吧

https://www.antdv.com/components/table-cnicon-default.png?t=M85Bhttps://www.antdv.com/components/table-cn

定义字段

根据windows文件管理器,我们肉眼可见需要四个字段:

name文件名、type文件类型、update修改日期、size大小

[
      {
        width: 500,
        title: '名称',
        dataIndex: 'name',
        key: 'name',
        sorter:(a, b) => this.compareName(a, b),
        sortOrder:this.sorted.columnKey === 'name' && this.sorted.order,
        ellipsis: true,
        resizable: true,
      },
      {width: 220,title: '修改日期', dataIndex: 'date',key: 'date',ellipsis: true, resizable: true,},
      {
        width: 170,
        title: '类型',
        dataIndex: 'type',
        key: 'type',
        sorter:  (a, b) => this.compareType(a.type, b.type),
        sortOrder: this.sorted.columnKey === 'type' && this.sorted.order,
        ellipsis: true,resizable: true,
      },
      { width: 140,title: '大小', dataIndex: 'size',key: 'size',ellipsis: true,resizable: true, },
      { title: '', dataIndex: 'empty',key: 'empty',},
]

这里最后一个字段,是做到最后才添加的,因为需要有一列自适应的单元格,才能确保其他列在table组件大小变化时保持宽度不变。windows文件管理器就是这么个效果。

定义数据

文件展示大小,文件夹不需要

文件或者文件夹都需要展示图标,设置图标名称

1、文件

{
    "size":26851,
    "name":"456.txt",
    "icon":"txt",
    "update":"2022/09/10 23:56",
    "id":"a2",
    "type":"file"
}

2、文件夹

{
    "name":"456",
    "icon":"dir",
    "update":"2022/09/10 23:56",
    "type":"dir"
}

自定义展示字段数据

不想要普通的字符串,通过如下方式

如下图:


 <a-table class="file_list_table"
             :columns="columns"
             :data-source="data"
             :pagination="false"
             size="small"
             :showSorterTooltip="false"
             :customRow="setRow"
             @resizeColumn="handleResizeColumn"
             @change="change_table"
             style="padding:0 10px; "
    >

      <template v-slot:bodyCell="{column,record}">
        <template v-if="column.dataIndex==='name'">
          <div :id="'FileList_'+record.name">
            <img class="file_list_icon" :src="getIconUrl(record)">
            <span style="font-size: 15px;">{{ record.name }}</span>
          </div>
        </template>
      </template>

</a-table>
<template v-slot:bodyCell="{column,record}">  表示你在设置表格数据部分
<template v-if="column.dataIndex==='name'">   表示你在设置字段为name的数据展示内容
<div :id="'FileList_'+record.name">
            <img class="file_list_icon" :src="getIconUrl(record)">
            <span style="font-size: 15px;">{{ record.name }}</span>
</div>

 效果:

 其他字段同理

表格样式修改

 利用我们在组件上添加的class名,可以方便的设置指定的样式。当然你需要会看控制台。

 也需要会一些css选择器的用法

a-table组件的表头灰色还有边框线,很明显这里不需要这些样式

 通过如下操作,表头变白色,去掉边框

(之所以能实现修改样式,就是利用了优先级的原理而已)

.file_list_table .ant-table-thead > tr > th {
  background-color: white;
  border: none;
}

比如:你可以修改成喜庆色。。。。。。。。。。。 

 

 言归正传

(1)发现每一行数据底部都有一条横线,尝试改掉

.file_list_table .ant-table-tbody > tr > td {
  border: none;
}

 (2)发现单元格的内边距太大,接着改掉

.file_list_table .ant-table.ant-table-small .ant-table-title,
.file_list_table .ant-table.ant-table-small .ant-table-footer,
.file_list_table .ant-table.ant-table-small .ant-table-thead > tr > th,
.file_list_table .ant-table.ant-table-small .ant-table-tbody > tr > td,
.file_list_table .ant-table.ant-table-small tfoot > tr > th,
.file_list_table .ant-table.ant-table-small tfoot > tr > td {
  padding: 3px 8px;
}

(3)发现鼠标放到数据行时,背景高亮颜色不好看,我改改改

.file_list_table .ant-table-tbody > tr.ant-table-row:hover > td,
.file_list_table .ant-table-tbody > tr > td.ant-table-cell-row-hover {
  background: none;
}

.file_list_table .ant-table.ant-table-small .ant-table-tbody .ant-table-row:hover{
  background-color: #d1edf6;
}

(4)。。。。应该都学会了吧

排序功能

看官方文档,可以只一列排序,也可以设置多列同时排序

这里就先以"文件名name"举例,添加排序功能

 为字段属性添加sorter属性,传递一个比较函数,通过反复点击windows文件管理器的排序,推测出排序原理,转换成如下排序方式:

(1)首先正向排序时,将文件与文件夹分开两份,文件夹在优先在上面,文件在下面

(2)然后再按照名称排序,通过js自带的字符串比较函数进行排序  

    en   按照英文首字母排序,大写在前,小写在后 

    zh   按照中文拼音排序

这里用en

同理再按照文件类型,模仿一个排序就可以了。不过多赘述。

再看windows文件管理器,多列排序时,只生效一种

看似已经满足使用,做起来就发现没有那么容易。总觉得少一个功能函数,应该有一个清除排序效果的功能,这样点击排序的时候,通过change就可以先清除排序效果,然后再排序,就实现只有一列生效了。找了一圈,没有找到。

多次搜索尝试,注意到sortOrder属性,它才是真正控制排序的源头。

sortOrder排序的受控属性,外界可用此控制列的排序,可设置为 'ascend' 'descend' falseboolean|string

给字段添加这个属性,设置为‘ascend’,你会发现列表默认直接排好序,就是正序排序,而且此时再点击排序按钮,发现没有任何反应,就是一直保持着正向排序。但change的回调是一直存在的。结合这两个内容可以通过设置变量的方式,通过change动态修改变量,达到修改排序效果。

 添加一个sorted的数据变量,内容其实就是change返回来的格式,这里设置了默认值,用来实现默认排序。

通过表达式来设置sortOrder的值,这样就设置一个变量sorted即可控制所有需要排序的字段。

不能说有20个字段,就设置20个变量来控制排序。。。。。

 再看change回调,获取排序信息

 设置有排序功能字段的sortOrder字段值

 搞定。

拖拽调整列宽

 在字段属性中加上resizable字段。true

然后添加回调事件,在回调函数中,写上固定语法即可。

 

设置选中行效果:

 添加两个变量,一个是选中行前styleName,另一个是选中行时selectedName,

选中行样式:

.file_list_selected_row_style{
  background-color: #b2dfef;
  border: 1px solid #4697db;
  transition: all 0.2s ease-out;
}

点击行时,通过设置单击事件

 

这里还是通过操作document元素来实现的,很容易,虽然不建议。。。

随便找个单元格,通过将数据主键作为元素id,如下图

 因为不能直接设置tr元素,因此我们需要通过子元素往上找,就能找到tr元素了,

然后操作tr元素添加样式或者移除样式即可。如下图

 编写两个方法,一个是移除行样式,一个是设置行样式

 还有多选没实现,反正都一个原理,有空在整

 整体效果:

附 列表代码:

<template>
  <div class="full" style="overflow: auto;user-select: none;position:relative;" >
<!--    通过div监听点击表格以外的空白取消选中行-->
    <div class="full"   style="position: absolute;height: 100%;" @click="click_blank"></div>
    <a-table class="file_list_table"
             :columns="columns"
             :data-source="data"
             :pagination="false"
             size="small"
             :showSorterTooltip="false"
             :customRow="setRow"
             @resizeColumn="handleResizeColumn"
             @change="change_table"
             style="padding:0 10px; "
    >
      <template v-slot:bodyCell="{column,record}">
        <template v-if="column.dataIndex==='name'">
          <div :id="'FileList_'+record.name">
            <img class="file_list_icon" :src="getIconUrl(record)">
            <span style="font-size: 15px;">{{ record.name }}</span>
          </div>
        </template>
        <template v-if="column.dataIndex==='type'">
          <span style="font-size: 16px;color: #868686;">{{record.type === 'dir' ? '文件夹' : getEndName(record.name) + '文件' }}</span>
        </template>
        <template v-if="column.dataIndex==='date'">
          <span style="font-size: 16px;color: #868686;">{{ record.update}}</span>
        </template>
        <template v-if="column.dataIndex==='size'">
          <button class="empty_button full"  v-if="record.type !== 'dir'" style="text-align: right;font-size: 16px;color: #868686;">{{ getFileSizeString(record.size) }}</button>
        </template>
      </template>

    </a-table>


  </div>
</template>
<script>
export default {
  name: "FileList",
  data(){
    return{
      sorted:{ order: 'ascend', field: 'name', columnKey: 'name'},
      columns:[],
      resourceFileList:[],
      data:[],
      path:[],
      selectedName:'',//当前选中行
      styleName:'',//样式显示所在行
    }
  },
  created() {
    this.getResourceList();
  },
  mounted() {
    this.columns=[
      {
        width: 500,
        title: '名称',
        dataIndex: 'name',
        key: 'name',
        sorter:(a, b) => this.compareName(a, b),
        sortOrder:this.sorted.columnKey === 'name' && this.sorted.order,
        ellipsis: true,
        resizable: true,
      },
      {width: 220,title: '修改日期', dataIndex: 'date',key: 'date',ellipsis: true, resizable: true,},
      {
        width: 170,
        title: '类型',
        dataIndex: 'type',
        key: 'type',
        sorter:  (a, b) => this.compareType(a.type, b.type),
        sortOrder: this.sorted.columnKey === 'type' && this.sorted.order,
        ellipsis: true,resizable: true,
      },
      { width: 140,title: '大小', dataIndex: 'size',key: 'size',ellipsis: true,resizable: true, },
      { title: '', dataIndex: 'empty',key: 'empty',},
    ];
    this.initListener();
  },
  methods:{
    /**
     * 初始化监听器
     */
    initListener() {
      //传送数据
      this.$event.$on('FileList_set_data',data1=>{
        this.data=data1;
      });
      this.$event.$on('FileList_set_path',path1=>{
        this.path=path1;
      });
    },
    setSort(){
      for(let i=0;i<this.columns.length;i++){
        if(this.columns[i].sorter){
          this.columns[i].sortOrder=this.sorted.columnKey === this.columns[i].key && this.sorted.order;
        }
      }
    },
    /**
     * 拖拽监听
     */
    handleResizeColumn(w, col){
      col.width = w;
    },
    /**
     * 计算文件大小
     */
    getFileSizeString(value){
      if(value<1024){
        return this.getFormatNum(value)+' B';
      }else{
        value=parseInt(value/1024);
        return this.getFormatNum(value)+' KB';
      }
    },
    /**
     * 三位一逗
     */
    getFormatNum(number){
      const reg = /(\d)(?=(?:\d{3})+$)/g;
      return  number.toString().replace(reg,'$1,');
    },
    /**
     * 设置行的单击双击等监听事件
     */
    setRow(record,index){
      return {
        onClick:()=>{
          this.removeSelectedRowStyle();
          this.setSelectedRowStyle(record);
        },
        onDblclick:()=>{
          this.removeSelectedRowStyle();
          this.doubleRow(record,index);
        },
      };
    },
    /**
     * 双击行,触发事件
     * @param record
     */
    doubleRow(record){
      if(record.type==='dir'){
        this.path.push(record.name);
        if(this.path[0]==='此电脑'){
          this.path.shift();
        }
        this.$event.$emit('FileManager_set_data',JSON.parse(JSON.stringify(this.path)));
      }

    },
    setSelectedRowStyle(record){
      this.selectedName=record.name;
      //添加当前选中行的样式
      let tr=document.getElementById("FileList_"+this.selectedName).parentElement.parentElement;
      if(tr)tr.className+=' file_list_selected_row_style';
      this.styleName=this.selectedName;
      this.$event.$emit('FileManager_set_footer',{size:this.data.length,selectSize:1});
    },
    removeSelectedRowStyle(){
      if(this.styleName&&this.styleName!==''){
        try{
          let tr=document.getElementById("FileList_"+this.styleName).parentElement.parentElement;
          if(tr)tr.className=tr.className.replace(' file_list_selected_row_style','');
        }catch (e){
          this.nullHave(e);
        }
      }
      this.styleName='';
      this.$event.$emit('FileManager_set_footer',{size:this.data.length,selectSize:0});
    },
    click_blank(){
      this.removeSelectedRowStyle();
    },
    nullHave(){
    },
    /**
     * 读取资源文件列表
     */
    getResourceList(){
      const files = require.context('@/assets/file', false, /.png$/);
      files.keys().forEach(e => {
        this.resourceFileList.push(e.replace("./", ""));
      });
    },
    /**
     * 列表切换事件
     */
    change_table(pagination, filters, sorter){
      this.sorted= sorter;
      this.setSort();
    },
    /**
     * 根据类型和icon值 来获取图标文件地址
     */
    getIconUrl(item){
      let url= require("@/assets/file/" + ((!item.icon || item.icon === '' || this.resourceFileList.indexOf(item.icon+'.png') < 0) ? ((item.type === 'dir') ? 'dir' : 'unknow') : item.icon) + '.png');
      return url;
    },
    /**
     * 解析出后缀名
     */
    getEndName(name){
      if (name && name.indexOf(".") >= 0) {
        try {
          return name.substring(name.lastIndexOf(".") + 1, name.length).toUpperCase();
        } catch (e) {
          this.nullHave(e);
        }
      }
      return "";
    },

    /**
     * 文件名 排序要用的 文件夹优先原则
     * @param a
     * @param b
     * @returns {number}
     */
    compareName(a, b){
      let abs;
      if (a.type !== 'dir' && b.type === 'dir') abs= 1;
      else if (a.type === 'dir' && b.type !== 'dir') abs= -1;
      else abs=  a.name.localeCompare(b.name, 'en');
      return abs;
    },
    /**
     * 文件类型,排序要用的 文件夹优先原则
     * @param a
     * @param b
     * @returns {number}
     */
    compareType(a, b){
      if ((a===b)) return 0;
      if (a !== 'dir' && b === 'dir') return 1;
      if (a === 'dir' && b !== 'dir') return -1;
      return a.localeCompare(b, 'en');
    },
  },
}
</script>

<style>
.file_list_table {

}

.file_list_table .ant-table-small .ant-table-thead > tr > th {
  background-color: transparent;
  border: none;
}

.file_list_table .ant-table-tbody > tr > td {
  border: none;
}

.file_list_table .ant-table-tbody > tr.ant-table-row:hover > td,
.file_list_table .ant-table-tbody > tr > td.ant-table-cell-row-hover {
  background: none;
}

.file_list_table .ant-table.ant-table-small .ant-table-title,
.file_list_table .ant-table.ant-table-small .ant-table-footer,
.file_list_table .ant-table.ant-table-small .ant-table-thead > tr > th,
.file_list_table .ant-table.ant-table-small .ant-table-tbody > tr > td,
.file_list_table .ant-table.ant-table-small tfoot > tr > th,
.file_list_table .ant-table.ant-table-small tfoot > tr > td {
  padding: 3px 8px;
}
.file_list_table .ant-table-column-sort{
  background: none;
}
.file_list_table .ant-table.ant-table-small .ant-table-tbody .ant-table-row:hover{
  background-color: #d1edf6;
}



.file_list_icon {
  width: 20px;
  height: 20px;
}
.file_list_selected_row_style{
  background-color: #b2dfef;
  border: 1px solid #4697db;
  transition: all 0.2s ease-out;
}
</style>

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐