1. 搜索框
// 子组件 搜索框
<template>
<el-popover
placement="bottom"
width="200"
trigger="manual"
v-model="visible"
@show="showPopover"
>
<el-input
placeholder="请输入内容"
v-model="value"
clearable
@keyup.enter.native="confirm"
ref="sInput"
>
</el-input>
<el-button type="primary" size="mini" @click="search" style="margin-top:5px"
>搜索</el-button
>
<el-button size="mini" @click="resetData">重置</el-button>
<div
slot="reference"
style="margin-left:5px"
@click.stop="popClick"
v-click-outside="closeOver"
>
<svg-icon :icon-class="!value ? 'icon_search_n' : 'icon_search_h'" />
</div>
</el-popover>
</template>
<script>
export default {
name: "select-header",
data() {
return {
value: "",
visible: false
};
},
props: {
tableType: {
type: String,
default: ""
},
type: {
type: String,
default: ""
},
defaultValue: {
type: String,
default: ""
},
options: {
type: Array,
default: function() {
return [];
}
},
defaultProps: {
type: Object,
default: function() {
return {
label: "label",
value: "value"
};
}
}
},
watch: {
defaultValue(newVal, oldVal) {
const self = this;
self.value = newVal;
}
},
methods: {
// 打开弹出框并聚焦
showPopover() {
this.$nextTick(() => {
this.$refs.sInput.focus();
});
},
// 打开或关闭弹窗
popClick(e) {
this.visible = !this.visible;
},
// 关闭弹窗
closeOver() {
this.visible = false;
},
// 搜索
search() {
this.visible = false;
this.$emit("inputSearch", {
type: this.type, //组件传递的指
value: this.value // 输入框绑定的元素
});
},
// 重置
resetData() {
this.value = "";
this.visible = false;
this.$emit("inputReset", {
type: this.type,
value: this.value
});
}
},
// 指令
directives: {
clickOutside: {
bind(el, binding, vnode) {
function clickHandler(e) {
// 这里判断点击的元素是否是本身,是本身,则返回
if (el.contains(e.target)) {
return false;
}
// 判断指令中是否绑定了函数
if (binding.expression) {
// 如果绑定了函数 则调用那个函数,此处binding.value就是handleClose方法
binding.value(e);
}
}
// 给当前元素绑定个私有变量,方便在unbind中可以解除事件监听
el.__vueClickOutside__ = clickHandler;
document.addEventListener("click", clickHandler);
},
update() {},
unbind(el, binding) {
// 解除事件监听
document.removeEventListener("click", el.__vueClickOutside__);
delete el.__vueClickOutside__;
}
}
}
};
</script>
2. 二次封装的 Table 组件
2.1 Table 组件 HTML
// Table 子组件 HTML
<template>
<!-- 公用表格组件 适用简单表格类型
参数均可改为配置
待增加:分页复选框勾选
-->
<div
class="table-public-box"
:class="{ 'w-table_moving': dragState.dragging }"
:style="{ padding: isShowPagina ? '12px' : '0' }"
>
<!-- 表格 -->
<el-table
:ref="tableRef"
v-loading="loading"
class="w-table"
:max-height="maxHeight"
:height="height"
stripe
:data="tableData"
:row-key="rowKey"
:row-class-name="rowClassName"
:border="border"
:cell-style="{
...cellStyle,
'font-size': tableStyle.fontSize,
'height': tableStyle.bodyLineHeight
}"
:tree-props="treeProps"
:highlight-current-row="highlightCurrentRow"
:header-cell-style="{
...headerCellStyle,
'font-size': tableStyle.fontSize,
'height': tableStyle.headLineHeight
}"
:span-method="objectSpanMethod"
@current-change="handleSelectionChange"
@selection-change="selectionOnChange"
@row-dblclick="dblClick"
:header-cell-class-name="headerCellClassName"
>
<template v-if="tableData.length == 0" slot="empty">
<div class="empty-box">
<img src="@/assets/system_images/table-empty.png" alt="暂无数据" />
<div style="margin-top: -40px">暂无数据</div>
</div>
</template>
<!-- 枚举表头 -->
<!-- <template> -->
<!-- 复选框 -->
<el-table-column
v-if="isSelection"
type="selection"
width="55"
fixed="left"
:selectable="selectionFucn"
/>
<el-table-column v-if="isShowRadio" fixed="left" width="36">
<template slot-scope="scope">
<el-radio v-model="checked" :label="scope.row.id"></el-radio>
</template>
</el-table-column>
<!-- 索引 -->
<el-table-column
v-if="isIndex"
type="index"
label="序号"
width="52"
fixed="left"
/>
<!-- 展开行 -->
<el-table-column
v-if="isExpand"
type="expand"
width="36"
>
<template slot-scope="{ row, $index }">
<slot name="expand" :row="row" :index="$index"></slot>
</template>
</el-table-column>
<!-- 操作 -->
<el-table-column
v-if="handleItem != ''"
:prop="handleItem.prop"
:fixed="handleItem.fixed"
:label="handleItem.label"
:width="handleItem.width"
:min-width="handleItem.minWidth || 55"
>
<template slot-scope="item">
<!-- 操作列 现在只有按钮类型,可根据需求不断迭代组件 -->
<div class="table-buttons">
<span
v-permission="btn.per || 'default:show'"
v-for="btn in handleItem.buttons"
:key="btn.type"
@click="
onBtnClick({
btn, // 按钮
item: item.row // 行数据
})
"
>
<span
v-if="handleShowBtn ? handleShowBtn(item.row, btn) : true"
:style="{
color: handleBtnColor
? handleBtnColor(item.row, btn)
: btn.color
? btn.color
: variables.systemColor,
...btn.style
}"
>
{{ btn.label }}
</span>
</span>
</div>
</template>
</el-table-column>
<!-- 其他类型 -->
<el-table-column
v-for="(header, index) in columnsData"
:key="index"
:prop="header.prop"
:label="header.label"
:width="header.width"
:min-width="header.minWidth"
:fixed="header.fixed"
:sortable="header.sortable || false"
:show-overflow-tooltip="showTooltip"
:column-key="header.filter || ''"
:render-header="(h, { column }) => renderHeader(h, { column }, header)"
>
<template slot-scope="item">
<!-- 表单列存在复杂结构类型,比如需要自定义input框、select选择器、嵌套表格等,
在父组件使用具名插槽传入dom
-->
<template v-if="header.slot">
<!-- name为插槽名 item为当前行数据,用于父组件dom处获取 -->
<slot
:name="header.slot"
:$index="item.$index"
:item="item.row"
:header="header"
></slot>
</template>
<!-- 其他普通数据类型 -->
<div v-else class="row-text-hide">
{{ item.row[header.prop] || "-" }}
</div>
</template>
</el-table-column>
<!-- </template> -->
</el-table>
<!-- 分页组件 -->
<Pagination
v-if="isShowPagina"
class="pagina-box"
:style="{ 'justify-content': paginationSite }"
:page-sizes="PaginationArray"
:total="apiData.total || 0"
:page.sync="apiData.current"
:limit.sync="apiData.size"
@pagination="paginaOnChange"
/>
</div>
</template>
<script src="./index.js"></script>
<style scoped lang="scss" src="./index.scss"></style>
2.2 Table 组件 JS
import { immediatePlatoon } from "@/api/order-manage/thrunk-order";
import Pagination from "@/components/Pagination";
import SelectHeader from "@/components/SelectHeader"
import variables from '@/styles/variables.scss'
import {
props,
cellStyle,
treeProps,
headerCellStyle
} from './constant'
export default {
name: "TablePublic",
components: {
Pagination, SelectHeader
},
props,
data() {
return {
loading: false,
columnsData: [], // 表头
tableData: [], // 列表数据
apiData: {}, // 接口数据
cellStyle,
treeProps,
headerCellStyle, //表格表头样式
paramsData: {},
handleItem: '', //操作栏
variables: variables, //系统颜色变量
dragState: {
start: -1, // 起始元素的index
end: -1, //结束元素的index
move: -1, // 移动鼠标时所覆盖的元素 index
dragging: false, // 是否正在拖动
direction: undefined // 拖动方向
},
checked: null
}
},
watch: {
// 更改动作目前有:重置、搜索、分页,存在业务类型更改,迭代组件
// 观测父组件参数更改 请求接口(存在问题: 双向绑定数据也会观测到)
params: {
handler(newData, oldData) {
this.paramsData = newData
},
immediate: true,
deep: true,
},
// 通过人为触发更新列表
resetTable: {
handler(newData, oldData) {
this.getData()
},
deep: true
},
childCustomData(newData, oldData) {
this.tableData = !this.isInit ? newData : oldData
if (!this.isInit) {//父组件传递table数据造成total丢失问题
this.apiData.current = this.params.pageIndex
this.apiData.total = this.total
}
},
columns(newData, oldData) {
this.initColumns()
}
},
created() {
this.$emit('selectData', {});
// 初始化
if (typeof this.tableApi !== 'function') {
throw Error('table api 不能为空!')
}
//初始化表格
this.initColumns()
// 如直接主表带入数据,无需请求接口
if (this.isInit) this.getData()
if (!this.isInit) {
this.tableData = this.childCustomData
this.apiData.total = this.total
};
},
methods: {
initColumns() {
this.handleItem = ''
let parseColumnsData = JSON.parse(JSON.stringify(this.columns))
parseColumnsData.some(item => {
if (item.prop == 'handle') {
this.handleItem = item
return
}
})
this.columnsData = parseColumnsData.filter(item => item.prop != 'handle')
},
getData() {
this.loading = true
// 部分接口入参为pageIndex和pageSize
// if (this.isPageSize) {
// this.paramsData = { ...this.params, pageIndex: this.params.current, pageSize: this.params.size }
// delete this.paramsData.current
// delete this.paramsData.size
// }
this.tableApi(this.paramsData).then(res => {
if (this.diyTableFunc) {
if (typeof this.diyTableFunc === 'function') {
const {
data = {}
} = this.diyTableFunc(res);
const {
records = []
} = data;
this.tableData = records
this.apiData = data
} else {
throw Error('diyTableDataFunc必须为函数!')
}
return
}
const {
data = {}
} = res;
const {
records = []
} = data;
this.customData && this.customData(data)
this.tableData = this.uniqueData ? data.zjxlFeeStatDOPageVO.records : records
this.apiData = this.uniqueData ? data.zjxlFeeStatDOPageVO : data
this.$emit('getSuccess', records)
this.$emit('setTableData', {records,totals:records.length})
}).catch(err => {
console.log(err)
}).finally(() => {
setTimeout(() => {
this.loading = false
}, 200);
})
},
// 复选框变化事件
selectionOnChange(data) {
this.$emit("selectionOnChange", data)
},
// 操作按钮触发事件
onBtnClick(data) {
this.$emit("onBtnClick", data)
},
showTableLoading(flag){
this.loading = flag
},
// 工具栏按钮触发事件
onToolClick(data) {
this.$emit("onToolClick", data)
},
// 分页变化事件
paginaOnChange(data) {
this.$emit("paginaOnChange", data)
},
// 单元格某一行被双击
dblClick(data) {
this.$emit("onDblClick", data)
},
// 选中全部数据
onChoiceAll() {
this.toggleRowAll(true)
},
// 清空选中数据
onClearAll() {
this.toggleRowAll(false)
},
toggleRowAll(state) {
if (state) {
this.onChoiceRow(this.tableData.map(item => item[this.rowKey]))
} else this.$refs[this.tableRef].clearSelection()
},
/**
* 选中行数据
* @param {Array 选中行数据} rows
* @param {String 匹配key值,默认使用rowKey} key
*/
onChoiceRow(rows, key = this.rowKey) {
this.toggleRowSelection(rows, key, true)
},
// 取消选中行数据
onClearRow(rows, key = this.rowKey) {
this.toggleRowSelection(rows, key, false)
},
/**
*
* @param {Array 选中行数据} rows
* @param {String 匹配key值,默认使用rowKey} key
* @param {Boolean true选中 | false取消} state
*/
toggleRowSelection(rows, key, state) {
if (Array.isArray(rows)) {
rows.forEach(row => {
const getItem = this.tableData.find((item => item[key] === row))
if (getItem) this.$refs[this.tableRef].toggleRowSelection(getItem, state);
});
} else {
throw Error("传入的数据类型必须为数组!")
}
},
// 自定义表头渲染处理函数
renderHeader(createElement, { column }, header) {
// 自定义表头样式,插入校验标识
if (this.setValidate) {
if (header.valid) {
return createElement(
'div',
[
createElement('span', { style: 'color:red' }, '*'),
createElement('span', header.label)
],
)
}
} else if (header.type == 'input') {//输入框筛选
return createElement(
'div',
{
style: 'display:inline-flex;'
},
[
createElement('div', header.label),
createElement(SelectHeader, {
style: 'cursor: pointer;',
props: {
type: header.prop,
// options: this.specIdOptions, // 下拉框选项
// defaultValue: this.examinerFieldChname, // 默认值
// defaultProps: {
// value: header.prop,
// label: header.prop
// }
},
on: {
inputSearch: this.inputSearch,
inputReset: this.inputReset
},
})
]
)
}
return createElement(
'div', {
'class': ['thead-cell'],
on: {
mousedown: ($event) => {
this.handleMouseDown($event, column)
},
mouseup: ($event) => {
this.handleMouseUp($event, column)
},
mousemove: ($event) => {
this.handleMouseMove($event, column)
}
}
}, [
// 添加 <a> 用于显示表头 label
createElement('a', column.label),
// 添加一个空标签用于显示拖动动画
createElement('span', {
'class': ['virtual']
})
])
},
handleSelectionChange(row) {
this.tableData.map(item => {
return {
...item,
checked: item.id !== row.id ? false : true
}
})
this.$emit('selectData', row);
},
// 头部筛选(输入框筛选)
// 使用页面:线下计划-计划单:指定单请车表单
inputSearch(row) {
const { type, value } = row
if (!this.isInit) {
this.$emit('inputSearch', row)
return
}
this.paramsData = { ...this.params,...this.paramsData, [type]: value }
this.getData()
},
// 头部重置(输入框筛选)
inputReset(row) {
if (!this.isInit) {
this.$emit('inputReset')
return
}
this.getData()
},
// 按下鼠标开始拖动
handleMouseDown(e, column) {
this.dragState.dragging = true
this.dragState.start = parseInt(column.columnKey)
// 给拖动时的虚拟容器添加宽高
let table = document.getElementsByClassName('w-table')[0]
let virtual = document.getElementsByClassName('virtual')
for (let item of virtual) {
item.style.height = table.clientHeight - 1 + 'px'
item.style.width = item.parentElement.parentElement.clientWidth + 'px'
}
},
// 鼠标放开结束拖动
handleMouseUp(e, column) {
this.dragState.end = parseInt(column.columnKey) // 记录起始列
this.dragColumn(this.dragState)
// 初始化拖动状态
this.dragState = {
start: -1,
end: -1,
move: -1,
dragging: false,
direction: undefined
}
},
// 拖动中
handleMouseMove(e, column) {
if (this.dragState.dragging) {
let index = parseInt(column.columnKey) // 记录起始列
if (index - this.dragState.start !== 0) {
this.dragState.direction = index - this.dragState.start < 0 ? 'left' : 'right' // 判断拖动方向
this.dragState.move = parseInt(column.columnKey)
} else {
this.dragState.direction = undefined
}
} else {
return false
}
},
// 拖动易位
dragColumn({
start,
end,
direction
}) {
//表格拉伸时不做拖动处理
if (start == -1) {
return
}
// 交换数组拖拽起始元素的位置
let tempItem = this.columnsData[end]
this.$set(this.columnsData, end, this.columnsData[start])
this.$set(this.columnsData, start, tempItem)
},
//动态修改class
headerCellClassName({
column,
columnIndex
}) {
return (columnIndex - 1 === this.dragState.move ? `darg_active_${this.dragState.direction}` : '')
}
}
}
往表头插入搜索组件 代码块图
2.3 Table组件 配置文件
/**
* 表格常量数据 可拓展配置
*/
import object from "element-resize-detector/src/detection-strategy/object"
export const cellStyle = {
textAlign: 'left',
color: '#0B1019',
}
export const treeProps = {
children: 'children',
hasChildren: 'hasChildren'
}
export const headerCellStyle = {
textAlign: 'left',
background: '#f2f3f5',
color: '#0B1019',
}
// 子组件入参校验
export const props = {
// 表头数据
columns: {
type: Array,
required: true,
default: () => {
return []
}
},
// 表头初始化数据
data: {
type: Array,
required: false,
default: () => {
return []
}
},
// 表格数据请求接口api
tableApi: {
type: Function,
required: false,
default: () => {
return () => { }
}
},
//表格对象
tableRef: {
type: String,
required: false,
default: "tableRef"
},
// loading: {
// type: Boolean,
// required: false,
// },
// 重置列表数据 - 重新请求接口
resetTable: {
type: Boolean,
required: false,
default: false
},
// 列表参数
params: {
type: Object,
required: false,
default: () => {
return {}
}
},
//table 传递进来的数据,init为false时会造成data数据为空
childCustomData: {
type: Array,
required: false,
default: () => {
return []
}
},
total: {
type: Number,
require: false,
default: 10,
},
// 列表行key值
rowKey: {
type: String,
required: false,
default: 'id'
},
// 是否启用复选框 ,目前默认第一列,可迭代配置参数进行指定
isSelection: {
type: Boolean,
required: false,
default: false
},
// 是否启用单选框
isShowRadio: {
type: Boolean,
required: false,
default: false
},
// 是否启动索引列,目前默认第二列,可迭代配置参数进行指定
isIndex: {
type: Boolean,
required: false,
default: false
},
// 是否显示分页组件
isShowPagina: {
type: Boolean,
required: false,
default: true
},
// 是否展开
isExpand: {
type: Boolean,
required: false,
default: false
},
// 是否初始化表格数据,也就是请求接口数据
isInit: {
type: Boolean,
required: false,
default: true
},
// 是否高亮选中行 (当 isExpand 开启后,不需要选中样式 )
highlightCurrentRow: {
type: Boolean,
required: false,
default: true,
},
rowClassName: {
type: Function,
required: false,
},
//列表接口入参为pageIndex和pageSize切换为current、size
isPageSize: {
type: Boolean,
required: false,
default: false
},
showTooltip: {
type: Boolean,
required: false,
default: true
},
// 表格边框
border: {
type: Boolean,
required: false,
default: true
},
// 是否分页
isPagina: {
type: Boolean,
required: false,
default: false
},
// 自定义组装列表数据函数
diyTableFunc: {
type: Function,
required: false,
default: undefined
},
// 表格顶部工具栏入参
toolData: {
type: Array,
required: false,
default: () => {
return []
}
},
setValidate: {
type: Boolean,
require: false,
default: () => false
},
//最大高度
maxHeight: {
type: Number | String,
required: false,
default: 620
},
//高度
height: {
type: Number | String,
},
// 配置CheckBox disabled函数
selectionFucn: {
type: Function,
required: false,
default: () => {
return () => { }
}
},
filterMethod: {
type: Function,
required: false,
default: undefined
},
//操作栏按钮控制显示隐藏
handleShowBtn: {
type: Function,
required: false,
default: undefined
},
// 按钮颜色
handleBtnColor: {
type: Function,
required: false,
default: () => {
return () => { }
}
},
//翻页参数
PaginationArray: {
type: Array,
required: false,
default: () => {
return [10, 30, 50, 100]
}
},
total: {
type: Number,
required: false,
},
// 外部组件需要自定义数据
customData: {
type: Function,
required: false,
default: undefined
},
// 需要特殊处理的数据
uniqueData: {
type: Boolean,
required: false,
default: false
},
objectSpanMethod: {
type: Function,
required: false,
default: () => {
return () => { }
}
},
//分页器位置
paginationSite: {
type: String,
required: false,
default: 'center'
},
//表格样式
tableStyle: {
type: Object,
required: false,
default: () => {
return {
fontSize: '14px',
headLineHeight: '44px',
bodyLineHeight: '40px'
}
}
},
}
2.4 Table组件样式
@import "~@/styles/variables.scss";
.table-public-box {
width: 100%;
background-color: #fff;
.table-buttons {
display: flex-start;
justify-content: center;
span > span {
cursor: pointer;
color: $systemColor;
margin: 0 4px;
}
.disabled {
color: gray;
}
}
.pagina-box {
display: flex;
justify-content: center;
margin-top: 8px;
}
.empty-box {
padding: 20px 0;
}
.row-text-hide {
overflow: hidden !important;
text-overflow: ellipsis !important;
white-space: nowrap !important;
}
.el-table th {
padding: 0;
.virtual {
position: fixed;
display: block;
width: 0;
height: 0;
margin-left: -10px;
z-index: 99;
background: none;
border: none;
}
&.darg_active_left {
.virtual {
border-left: 2px dotted #666;
}
}
&.darg_active_right {
.virtual {
border-right: 2px dotted #666;
}
}
}
.thead-cell {
padding: 0;
display: inline-flex;
flex-direction: column;
align-items: left;
cursor: pointer;
overflow: initial;
&:before {
content: "";
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
}
&.w-table_moving {
.el-table th .thead-cell {
cursor: move !important;
}
.el-table__fixed {
cursor: not-allowed;
}
}
}
::v-deep .el-radio__label {
display: none;
}
.empty-box {
img {
width: 109px;
height: 102px;
}
}
::v-deep .headerCellClassName:first-child {
color: red;
}
2.5 父组件使用
import TablePublic from "@/components/TablePublic/index.vue";
<TablePublic
ref="tableRef"
class="tables"
:isIndex="true"
:isInit="false"
:maxHeight="450"
:isShowPagina="false"
:columns="tableConfig"
:data="tableData"
:childCustomData="tableData"
@inputSearch="inputSearch"
@inputReset="inputReset"
@selectionOnChange="selectionOnChange"
/>
export default {
name: "assign-car-dialog",
components: { TablePublic, TableForm },
data() {
return {
tableConfig = [
{ label: '计划单号', prop: 'orderCode', minWidth: 140, type: 'input' },
{ label: '出发基地', prop: 'fromWarehouseName', minWidth: 100 },
{ label: '目的地', prop: 'toCityName', minWidth: 100 },
{ label: '客户BAAN编码', prop: 'clientBaanCode', minWidth: 130 },
{ label: '商品编码', prop: 'materialCode', minWidth: 100 },
{ label: '排车数量', prop: 'waitArrangeNum', minWidth: 100 },
{ label: 'ERP单号', prop: 'erpSaleOrderCode', minWidth: 140 },
{ label: '来源单号', prop: 'sourceOrderNo', minWidth: 100 },
{ label: '收货人', prop: 'receiverName', minWidth: 100 },
{ label: '销售部门', prop: 'saleDepartment', minWidth: 100 },
],
tableData:[],
}},
.....
}