table表格 组件
<template>
<div>
<BaseTable
:searchConfig="searchConfig"
:operateBtnConfig="operateBtn"
:tableData="tableData"
:tableConfig="tableConfig"
:pagination="pagination"
:tableLoading="tableLoading"
@search="search"
@reset="reset"
@selectionChange="selectionChange"
@changePage="changePage"
@keyupEnterNative="fetchList"
@receiveSearch="receiveSearch"
ref="BaseTable"
idField="otherId"
>
<template v-slot:operate="{row}">
<el-link type="primary" size="medium" :underline="false" @click="editRow(row)" style="margin-right: 10px;">编辑</el-link>
<el-link type="danger" size="medium" :underline="false" @click="deleteData(row)">删除</el-link>
</template>
</BaseTable>
<editForm
v-if="editVisible"
:visible="editVisible"
:editId="editId"
:companyList ="companyList"
:editType="editType"
@close="(type) => {type === 'reload' && this.fetchList();this.reloadCreators();editVisible = false}"
></editForm>
</div>
</template>
<script>
import { rules, searchConfig, tableConfig } from "./config";
import BaseTable from "@/components/BaseTable/index.vue"
import baseMixins from "@/components/BaseTable/baseMixins"
import editForm from "./components/editForm.vue"
import { getCodingTypeList, getCreators ,} from "@/api/system/encodingRules"
import{invoiceConfig ,invoiceConfigDelete} from "@/api/system/tickets"
import { deepClone } from '@/utils';
import { getBranchCompany } from "@/api/shippingWorkOrder/index";
export default {
data() {
return {
rules: rules,
searchConfig: {},
tableConfig: tableConfig,
tableData: [],
editVisible: false,
editType: 'add',
editId: [],
typeOptions: [],
companyOptions: [],
creatorsList: [],
companyList:[],
};
},
mixins: [ baseMixins ],
components: { BaseTable, editForm },
computed: {
operateBtn: function() {
return [
{
name: '新增',
plain: false,
icon: 'el-icon-plus',
event: this.handleAdd
},
{
name: '删除',
type: 'danger',
icon: 'el-icon-delete',
event: this.deleteData
},
]
},
},
created() {
this.fetchList()
Promise.allSettled([this.getBranchCompany(), this.getCodingTypeList(), this.getCreators()]).then(() => {
setTimeout(() => {
this.init()
}, 200)
})
},
methods: {
init() {
this.searchConfig = searchConfig({companyOptions: this.companyOptions, typeOptions: this.typeOptions, creatorsList: this.creatorsList})
},
reloadCreators() {
Promise.allSettled([this.getCreators()]).then(() => {
this.init()
})
},
// 录入人
async getCreators() {
let { code, data } = await getCreators();
if (code == 0) {
this.creatorsList = data.map(r => {return {value: r}});
}
},
fetchList() {
const { pageNo , pageSize } = this.pagination
const {...rest } = this.searchForm
const data = {
pageNo,
pageSize,
...rest
}
this.tableLoading = true
invoiceConfig(data).then(res => {
const { code, msg, data: { list, total } } = res
if (code === 0) {
list.forEach((r, i) => {
r.monthLength = 2
r.dayLength = 2
r.otherId = new Date().getTime() + i
})
this.tableData = list;
this.$set(this.pagination, 'total', total)
} else {
this.tableData = []
msg && this.$message.warning(msg)
}
}).finally(() => {
this.tableLoading = false
})
},
getCodingTypeList() {
getCodingTypeList().then(res => {
this.typeOptions = res.data
})
},
getBranchCompany() {
getBranchCompany().then((res) => {
this.companyList = res.data;
this.companyOptions = deepClone(res.data.map(r => { return {...r, checked: false} }));
});
},
// 新增
handleAdd() {
this.editVisible = false
this.editType = 'add'
this.editId = {}
setTimeout(() => {this.editVisible = true}, 100)
},
// 编辑
editRow(row) {
this.editVisible = false
this.editId = row
this.editType = 'edit'
setTimeout(() => {this.editVisible = true}, 100)
},
// 删除
deleteData(row) {
console.log(row);
let ids = []
if (row) {
ids.push(row.openInvoiceConfigId);
} else {
if (!this.getSelectRows()) return;
ids = this.selectRows.map(r => r.openInvoiceConfigId)
}
console.log(ids);
this.$confirm(`确认删除该编码规则吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
invoiceConfigDelete(ids).then(res => {
const { code, data, msg} = res
if (code === 0 && data) {
this.$message.success(msg || '删除成功')
this.reloadCreators()
this.fetchList()
} else {
this.$message.warning(msg || '删除失败')
}
})
});
},
},
};
</script>
<style scoped lang="scss">
</style>
===============================================================================
引入解释:
config.js 配置
import { changeOptionsName } from '@/utils/utils'
import { rulesItem } from '@/utils/constants'
export const searchConfig = ({ companyOptions, typeOptions, creatorsList }) => {
return {
inline: true,
form: [
{
type: 'select',
name: 'deptId',
placeholder: '公司',
options: changeOptionsName(companyOptions, 'id', 'name') || [],
},
{
type: 'input',
name: 'taxNum',
placeholder: '企业税号',
options: creatorsList || [],
},
]
}
}
export const tableConfig = {
highlightCurrentRow: true,
fixedSelection: 'left',
columns: [
{
prop: 'deptName',
label: '公司名',
minWidth: 100
},
{
prop: 'taxNum',
label: '企业税号',
},
{
prop: 'appKey',
label: 'APP KEY',
},
{
prop: 'appSecret',
label: 'APP SECRET',
},
{
prop: 'token',
label: 'TOKEN',
minWidth: 80
},
{
prop: 'extensionNum',
label: '分机号',
minWidth: 80
},
{
prop: 'address',
label: '地址',
minWidth: 80
},
{
prop: 'phone',
label: '电话',
minWidth: 80
},
{
prop: 'payee',
label: '收款人',
minWidth: 110
},
{
prop: 'checker',
label: '复核人',
minWidth: 110
},
{
prop: 'drawer',
label: '开票人',
minWidth: 110
},
{
prop: 'creator',
label: '录入人',
},
{
prop: 'createTime',
label: '录入时间',
minWidth: 150
},
{
prop: 'updater',
label: '修改人',
},
{
prop: 'updateTime',
label: '修改时间',
minWidth: 150
},
{
prop: 'operate',
label: '操作',
fixed: 'right',
width: 96,
align: 'center',
headerAlign: 'center',
columShow: 'true',
slot: true
}
]
}
export const editRules = {
taxNum: rulesItem,
deptId:rulesItem,
appKey:rulesItem,
appSecret:rulesItem,
token:rulesItem,
extensionNum:rulesItem,
}
searchConfig 中是搜索菜单中的搜索条件
tableConfig : 是配置tableb表格
tableConfig ={
highlightCurrentRow :true 行高亮
fixedSelection: 'left', 左对齐
columns: [{}] 列参数
}
editRules: 必填判断0
编辑弹框:
<template>
<BaseDialog :title="typeName[editType]" :visible.sync="visible" width="500px" @close="handleClose" @submit="submit" :loading="loading">
<el-form :model="form" :rules="editRules" ref="form" label-position="right">
<el-form-item label="公司名" prop="deptId">
<el-select v-model="form.deptId" filterable placeholder>
<el-option v-for="(item, index) in companyList" :key="index" :label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="企业税号" prop="taxNum">
<el-input v-model="form.taxNum" style="width: 194px;"></el-input>
</el-form-item>
<el-form-item label="APP_KEY" prop="appKey">
<el-input v-model="form.appKey" style="width: 194px;"></el-input>
</el-form-item>
<el-form-item label="APP_SECRET" prop="appSecret">
<el-input v-model="form.appSecret" style="width: 194px;"></el-input>
</el-form-item>
<el-form-item label="TOKEN" prop="token">
<el-input v-model="form.token" style="width: 194px;"></el-input>
</el-form-item>
<el-form-item label="分机号" prop="extensionNum">
<el-input v-model="form.extensionNum" style="width: 194px;"></el-input>
</el-form-item>
<el-form-item label="地址" prop="address">
<el-input v-model="form.address" style="width: 194px;" type="textarea" show-word-limit maxlength="500" rows="5"></el-input>
</el-form-item>
<el-form-item label="电话" prop="phone">
<el-input v-model="form.phone" style="width: 194px;"></el-input>
</el-form-item>
<el-form-item label="收款人" prop="payee">
<el-select v-model="form.payee" filterable placeholder>
<el-option v-for="(item, index) in listPayee" :key="index" :label="item.username" :value="item.username" ></el-option>
</el-select>
</el-form-item>
<el-form-item label="复核人" prop="checker">
<el-select v-model="form.checker" filterable placeholder>
<el-option v-for="(item, index) in listChecker" :key="index" :label="item.username" :value="item.username" ></el-option>
</el-select>
</el-form-item>
<el-form-item label="开票人" prop="drawer">
<el-select v-model="form.drawer" filterable placeholder>
<el-option v-for="(item, index) in listDrawer" :key="index" :label="item.username" :value="item.username" ></el-option>
</el-select>
</el-form-item>
</el-form>
</BaseDialog>
</template>
<script>
import { editRules } from "../config"; //调用的必填项参数
import { invoiceConfigCreate,invoiceConfigUpdate,invoiceConfigGet } from "@/api/system/tickets"; //接口数据
import {getAssignUser} from "@/api/shippingWorkOrder/index" //接口数据
export default {
props: {
editType: { // add edit copyAdd
type: String,
default: 'add'
},
visible: false,
editId: {
type: Object,
default: () => {}
},
companyList:{
type: Array,
default: () => []
}
},
data() {
return {
dialogVisible: this.visible,
typeName: {
add: '新增',
edit: '编辑',
},
typeOptions:[],
listDrawer:[],
listChecker:[],
listPayee:[],
form: {
},
editIdInfo:this.editId,
editRules: editRules,
loading: false,
};
},
created() {
this.getAssignUserList();
if(this.editType == 'edit'){
this.invoiceConfigGetList()
}
},
mounted() {
},
methods: {
handleClose(type) {
this.$emit('close', type)
},
// 提交
submit() {
this.$refs.form.validate((valid) => {
if (!valid) return false;
const data = {
...this.form,
}
this.loading = true
if (this.editType != 'edit') {
this.handleRes(invoiceConfigCreate(data))
} else {
this.handleRes(invoiceConfigUpdate(data))
}
});
},
handleRes(_Promise) {
_Promise.then(res => {
const { code, msg } = res
this.$message.success(msg || this.editType == 'add' ? '新增成功' : '编辑成功')
this.handleClose('reload')
}).finally(() => this.loading = false)
},
getAssignUserList(){
getAssignUser().then((res)=>{
this.listDrawer = res.data;
this.listChecker = res.data;
this.listPayee = res.data;
})
},
invoiceConfigGetList(){
console.log(this.editIdInfo,"222");
let params={
id:this.editIdInfo.openInvoiceConfigId
}
invoiceConfigGet(params).then((res)=>{
this.form = {
...res.data
}
})
}
}
};
</script>
<style lang="scss" scoped>
.el-form-item{
::v-deep .el-form-item__label{
width: 100px !important;
}
::v-deep .el-form-item__content{
.el-select{
width: 300px;
}
.el-input{
width: 300px !important;
}
.el-textarea{
width: 300px !important;
}
}
}
.title {
display: inline-block;
padding: 0px 5px;
color: var(--current-color);
font-size: 16px;
font-weight: 400;
margin-bottom: 7px;
margin-left: -5px;
}
::v-deep .el-dialog__footer {
text-align: center;
}
.flip-list-move {
transition: transform 0.5s;
}
.no-move {
transition: transform 0s;
}
</style>
=======================================================================
引入的base tabe 文件夹
BaseTable 下的index.vue
<template>
<div class="container baseTable-container">
<!-- 搜索栏 -->
<div class="search search-box">
<template v-if="searchConfig.form && searchConfig.form.length">
<el-form ref="searchRef" :model="searchForm" :inline="searchConfig.inline" :label-width="searchConfig.labelWidth" @submit.native.prevent>
<el-form-item v-for="(item, index) in searchConfig.form" :label="item.label" :key="index" :prop="item.name">
<el-input v-if="item.type === 'input'" v-model="searchForm[item.name]" :style="item.style" :placeholder="item.placeholder" :clearable="item.clearable === false ? false : true"
:disabled="item.disabled" :readonly="item.readonly" @keyup.enter.native="keyupEnterNative" @dblclick.native="v => commonInputDblclick(v, item)" :suffix-icon="item.suffixIcon" :prefix-icon="item.prefixIcon" ></el-input>
<el-input v-if="item.type === 'textarea'" type="textarea" v-model="searchForm[item.name]" :style="item.style" :placeholder="item.placeholder"
:clearable="item.clearable === false ? false : true" :disabled="item.disabled" :readonly="item.readonly" @keyup.enter.native="keyupEnterNative" @dblclick.native="v => commonInputDblclick(v, item)" :suffix-icon="item.suffixIcon" :prefix-icon="item.prefixIcon"></el-input>
<el-input v-if="item.type === 'number'" type="number" v-model="searchForm[item.name]" :style="item.style" :placeholder="item.placeholder"
:clearable="item.clearable === false ? false : true" :disabled="item.disabled" :readonly="item.readonly" @keyup.enter.native="keyupEnterNative" @dblclick.native="v => commonInputDblclick(v, item)" :suffix-icon="item.suffixIcon" :prefix-icon="item.prefixIcon"></el-input>
<el-date-picker
v-if="item.type === 'date'"
:type="item.datePickerType || 'daterange'"
:clearable="item.clearable === false ? false : true"
:disabled="item.disabled"
:style="item.style"
v-model="searchForm[item.name]"
:range-separator="item.rangeSeparator || '至'"
:start-placeholder="item.startPlaceholder || '开始时间'"
:end-placeholder="item.endPlaceholder || '结束时间'"
:value-format="item.valueFormat || (['daterange', 'data'].includes(item.datePickerType) ? 'yyyy-MM-dd' : ['datetimerange'].includes(item.datePickerType) ? 'yyyy-MM-dd HH:hh:mm' : 'yyyy-MM-dd')"
:default-time="item.defaultTime || (item.datePickerType === 'datetimerange' ? ['00:00:00', '23:59:59'] : null)"
:readonly="item.readonly"
@keyup.enter.native="keyupEnterNative"
></el-date-picker>
<el-time-select
v-if="item.type === 'timeSelect'"
v-model="searchForm[item.name]"
:disabled="item.disabled"
:clearable="item.clearable === false ? false : true"
:style="item.style"
:picker-options="item.pickerOptions"
:placeholder="item.placeholder"
:readonly="item.readonly"
@keyup.enter.native="keyupEnterNative">
</el-time-select>
<el-time-picker
v-if="item.type === 'timePicker'"
:is-range="item.isRange"
:disabled="item.disabled"
:clearable="item.clearable === false ? false : true"
v-model="searchForm[item.name]"
:style="item.style"
:picker-options="item.pickerOptions"
:range-separator="item.rangeSeparator || '至'"
:start-placeholder="item.startPlaceholder || '开始时间'"
:end-placeholder="item.endPlaceholder || '结束时间'"
:placeholder="item.placeholder"
:readonly="item.readonly"
@keyup.enter.native="keyupEnterNative">
</el-time-picker>
<el-select v-if="item.type === 'select'" v-model="searchForm[item.name]" :style="item.style" :placeholder="item.placeholder" :clearable="item.clearable === false ? false : true" filterable
:disabled="item.disabled" :readonly="item.readonly" @keyup.enter.native="keyupEnterNative" @change="v => {commonSelectChange(v, item)}">
<template v-if="typeof(item.renderOption) === 'function'">
<el-option v-for="(selectItem, selectIndex) in item.options" :key="selectIndex" :label="item.renderOption(selectItem)" :value="selectItem.value">
{{ item.renderOption(selectItem) }}
</el-option>
</template>
<template v-else>
<el-option v-for="(selectItem, selectIndex) in item.options" :key="selectIndex" :label="selectItem.label" :value="selectItem.value"></el-option>
</template>
</el-select>
<template v-if="item.type === 'selectGroup'">
<el-col :span="11">
<el-select v-model="searchForm[item.list[0].name]" :style="item.list[0].style" :clearable="item.list[0].clearable === false ? false : true" :placeholder="item.list[0].placeholder || ''">
<el-option v-for="(selectItem, selectIndex) in item.list[0].options" :key="selectIndex" :label="selectItem.label" :value="selectItem.value"></el-option>
</el-select>
</el-col>
<el-col style="text-align: center;" :span="2">—</el-col>
<el-col :span="11">
<el-select v-model="searchForm[item.list[1].name]" :style="item.list[1].style" :clearable="item.list[1].clearable === false ? false : true" :placeholder="item.list[1].placeholder || ''">
<el-option v-for="(selectItem, selectIndex) in item.list[1].options" :key="selectIndex" :label="selectItem.label" :value="selectItem.value"></el-option>
</el-select>
</el-col>
</template>
<template v-if="item.type === 'inputGroup'">
<el-col :span="11">
<el-input v-model="searchForm[item.list[0].name]" :type="item.list[0].type" :style="item.list[0].style" :placeholder="item.list[0].placeholder || ''" :clearable="item.list[0].clearable === false ? false : true"
:disabled="item.list[0].disabled" :readonly="item.list[0].readonly" @keyup.enter.native="keyupEnterNative" ></el-input>
</el-col>
<el-col style="text-align: center;" :span="2">—</el-col>
<el-col :span="11">
<el-input v-model="searchForm[item.list[1].name]" :type="item.list[0].type" :style="item.list[1].style" :placeholder="item.list[1].placeholder || ''" :clearable="item.list[1].clearable === false ? false : true"
:disabled="item.list[1].disabled" :readonly="item.list[1].readonly" @keyup.enter.native="keyupEnterNative" ></el-input>
</el-col>
</template>
<v-selectpage v-if="item.type === 'selectPage'" :style="item.style" :ref="`selectPage_${item.name}`" v-model="searchForm[item.name]" :data="item.options" :key-field="item.keyField"
:result-format="item.resultFormat || resultFormat"
:show-field="item.showField"
:search-field="item.searchField || 'searchField'"
:params="item.searchParams"
:multiple="item.multiple"
:clearable="item.clearable === false ? false : true"
:disabled="item.disabled"
:tb-columns="item.tbColumns || defaultSelectPageColumns" :placeholder="item.placeholder || ' '">
</v-selectpage>
<base-selectPage v-if="item.type === 'baseSelectPage'" :style="item.style" v-model="searchForm[item.name]" :data="item.options" :keyField="item.keyField"
:showField="item.showField"
:searchField="item.searchField || 'keyword'"
:concatField="item.concatField"
:defaultRow="item.defaultRow || {}"
:searchParams="item.searchParams"
:multiple="item.multiple"
:tb-columns="item.tbColumns || defaultSelectPageColumns"
:disabled="item.disabled"
:clearable="item.clearable === false ? false : true"
:placeholder="item.placeholder || ''"
@change="v => {commonSelectChange(v, item)}">
</base-selectPage>
<el-cascader v-if="item.type === 'cascader'" v-model="searchForm[item.name]" :clearable="item.clearable === false ? false : true" :options="item.options" :style="item.style" :placeholder="item.placeholder" :disabled="item.disabled" :readonly="item.readonly"></el-cascader>
<treeselect v-if="item.type === 'treeselect'" v-model="searchForm[item.name]" :options="item.options" :normalizer="typeof(item.normalizer) === 'function' ? item.normalizer : normalizer" :show-count="item.showCount === false ? false : true"
:placeholder="item.placeholder"
:disabled="item.disabled"
:multiple="item.multiple"
:clearable="item.clearable === false ? false : true"/>
<template v-if="item.type === 'slot'">
<slot :name="item.name" :form="searchForm"></slot>
</template>
</el-form-item>
<!-- 三种布局类型 last / bottom / more -->
<el-form-item v-if="!searchConfig.layoutType || searchConfig.layoutType === 'last'">
<el-button type="primary" @click="search">查 询</el-button>
<el-button @click="reset" v-if="searchConfig.showResetBtn === false ? false : true">重 置</el-button>
</el-form-item>
</el-form>
<div v-if="searchConfig.layoutType === 'bottom'" class="searchBtn">
<el-button type="primary" @click="search">查 询</el-button>
<el-button @click="reset" v-if="searchConfig.showResetBtn === false ? false : true">重 置</el-button>
</div>
</template>
<template v-else>
<p class="search-loading"><i class="el-icon-loading"></i> 加载中...</p>
</template>
</div>
<div class="line"></div>
<!-- 操作栏 -->
<div class="operateBtn btn-box" v-if="operateBtnConfig.length">
<div class="operateBtn-left">
<!-- formcreate 循环按钮使用自定义组件 -->
<BaseBtnList :list="operateBtnConfig"></BaseBtnList>
</div>
<div class="operateBtn-right">
<slot name="operateBtnRight"></slot>
</div>
</div>
<!-- Table栏 -->
<div class="baseTable" v-if="tableConfig.showTable === false ? false : true">
<el-table
v-if="!tableConfig.isVirtualTable"
ref="table"
class="tableMaxHeightClass"
v-loading="tableLoading"
:data="tableData"
:tooltip-effect="tableConfig.tooltipEffect || 'dark'"
style="width: 100%"
:border="tableConfig.border === false ? false : true"
:highlight-current-row="tableConfig.highlightCurrentRow"
:current-row-key="tableConfig.currentRowKey || 'id'"
:row-key="tableConfig.rowKey"
:max-height="tableMaxHeight"
:row-class-name="toString.call(tableConfig.rowClassName) === '[object Undefined]' && idField ? defaultRowClassName : tableConfig.rowClassName"
:cell-class-name="tableConfig.cellClassName"
:header-row-class-name="tableConfig.headerRowClassName"
:header-cell-class-name="tableConfig.headerCellClassName"
:span-method="({row, column, rowIndex, columnIndex}) => toString.call(tableConfig.spanMethod) === '[object Undefined]' ? () => {} : tableConfig.spanMethod({row, column, rowIndex, columnIndex, tableRef: $refs.table})"
@select="select"
@select-all="selectAll"
@selection-change="selectionChange"
@row-dblclick="rowDblclick"
@row-click="rowClick"
@cell-click="cellClick"
@row-contextmenu="rowContextmenu"
@header-dragend="headerDragend"
@sort-change="sortChange">
<el-table-column
type="expand"
v-if="tableConfig.showExpand"
align="center"
width="55">
<template slot-scope="props">
<slot name="expand" :props="props" :row="props.row"></slot>
</template>
</el-table-column>
<el-table-column
type="selection"
v-if="tableConfig.showSelection === false ? false : true"
:reserve-selection="tableConfig.reserveSelection === true ? true : false"
:fixed="tableConfig.fixedSelection"
:selectable="tableConfig.selectable"
align="center"
width="44">
</el-table-column>
<el-table-column
type="index"
v-if="tableConfig.showIndex === false ? false : true"
align="center"
label="序号"
width="56">
</el-table-column>
<template v-for="(item, index) in tableConfig.columns">
<el-table-column
v-if="(item.columShow === 'false' || item.columShow === false) ? false : true"
:key="index"
:width="item.width"
:min-width="item.minWidth || 120"
:prop="item.prop"
:label="item.label"
:fixed="item.fixed"
:sortable="item.sortable === false ? false : true"
:render-header="item.renderHeader"
:align="item.align || 'left'"
:header-align="item.headerAlign || 'left'"
:class-name="item.className"
:sort-method="item.sortMethod"
:show-overflow-tooltip="item.showOverflowTooltip === false ? false : true">
<template slot-scope="scope" v-if="!item.formatter">
<slot v-if="item.slot" :name="item.prop" :scope="scope" :index="scope.$index" :row="scope.row"></slot>
<span v-else>{{ scope.row[item.prop] }}</span>
</template>
<!-- 合并单元格 传children -->
<template v-if="item.children">
<el-table-column
v-for="(childItem, childIndex) in item.children"
:key="childIndex"
:label="childItem.label"
:prop="childItem.prop"
:width="childItem.width"
:min-width="childItem.minWidth || 100"
:align="childItem.align || 'left'"
:header-align="childItem.headerAlign || 'left'"
:class-name="childItem.className"
show-overflow-tooltip
:sortable="item.sortable === false ? false : true"
:sort-method="item.sortMethod"
>
<template slot-scope="scope">
<slot v-if="childItem.slot" :name="childItem.prop" :scope="scope" :index="scope.$index" :row="scope.row"></slot>
<span v-else>{{ scope.row[childItem.prop] ? scope.row[childItem.prop] : childItem.empty }}</span>
</template>
</el-table-column>
</template>
</el-table-column>
</template>
</el-table>
<!-- 虚拟表格 -->
<u-table
v-else
ref="table"
class="tableMaxHeightClass"
v-loading="tableLoading"
:data="tableData"
:tooltip-effect="tableConfig.tooltipEffect || 'dark'"
style="width: 100%"
:border="tableConfig.border === false ? false : true"
:highlight-current-row="tableConfig.highlightCurrentRow"
:current-row-key="tableConfig.currentRowKey || 'id'"
:max-height="tableConfig.tableMaxHeight || tableMaxHeight"
:row-class-name="toString.call(tableConfig.rowClassName) === '[object Undefined]' && idField ? defaultRowClassName : tableConfig.rowClassName"
:cell-class-name="tableConfig.cellClassName"
:header-row-class-name="tableConfig.headerRowClassName"
:header-cell-class-name="tableConfig.headerCellClassName"
:span-method="({row, column, rowIndex, columnIndex}) => toString.call(tableConfig.spanMethod) === '[object Undefined]' ? () => {} : tableConfig.spanMethod({row, column, rowIndex, columnIndex, tableRef: $refs.table})"
@select="select"
@select-all="selectAll"
@selection-change="selectionChange"
@row-dblclick="rowDblclick"
@row-click="rowClick"
@cell-click="cellClick"
@row-contextmenu="rowContextmenu"
@header-dragend="headerDragend"
@sort-change="sortChange"
use-virtual
:row-height="tableConfig.rowHeight || uTableRowHeight"
>
<u-table-column
type="expand"
v-if="tableConfig.showExpand"
align="center"
width="56">
<template slot-scope="props">
<slot name="expand" :props="props" :row="props.row"></slot>
</template>
</u-table-column>
<u-table-column
type="selection"
v-if="tableConfig.showSelection === false ? false : true"
:fixed="tableConfig.fixedSelection"
:selectable="tableConfig.selectable"
align="center"
header-align="center"
width="40">
</u-table-column>
<!-- 有些需要在序号前加东西 -->
<u-table-column
v-if="tableConfig.showOther"
align="center"
width="56">
<template slot-scope="scope">
<slot name="showOther" :scope="scope" :index="scope.$index" :row="scope.row"></slot>
</template>
</u-table-column>
<u-table-column
type="index"
v-if="tableConfig.showIndex === false ? false : true"
align="center"
label="序号"
class-name="uIndex"
width="56">
</u-table-column>
<template v-for="(item, index) in tableConfig.columns">
<u-table-column
v-if="(item.columShow === 'false' || item.columShow === false) ? false : true"
:key="index"
:width="item.width"
:min-width="item.minWidth || 120"
:prop="item.prop"
:label="item.label"
:fixed="item.fixed"
:sortable="item.sortable === false ? false : true"
:render-header="item.renderHeader"
:align="item.align || 'left'"
:header-align="item.headerAlign || 'left'"
:class-name="item.className"
:sort-method="item.sortMethod"
:show-overflow-tooltip="item.showOverflowTooltip === false ? false : true">
<template slot-scope="scope" v-if="!item.formatter">
<slot v-if="item.slot" :name="item.prop" :scope="scope" :index="scope.$index" :row="scope.row"></slot>
<span v-else>{{ scope.row[item.prop] }}</span>
</template>
<!-- 合并单元格 传children -->
<template v-if="item.children">
<u-table-column
v-for="(childItem, childIndex) in item.children"
:key="childIndex"
:label="childItem.label"
:prop="childItem.prop"
:width="childItem.width"
:min-width="childItem.minWidth || 100"
:align="childItem.align || 'left'"
:header-align="childItem.headerAlign || 'left'"
:class-name="childItem.className"
show-overflow-tooltip
:sortable="item.sortable === false ? false : true"
:sort-method="item.sortMethod"
>
<template slot-scope="scope">
<slot v-if="childItem.slot" :name="childItem.prop" :scope="scope" :index="scope.$index" :row="scope.row"></slot>
<span v-else>{{ scope.row[childItem.prop] ? scope.row[childItem.prop] : childItem.empty }}</span>
</template>
</u-table-column>
</template>
</u-table-column>
</template>
</u-table>
<div class="pagination" v-if="tableConfig.showPagination === false ? false : true">
<el-pagination
background
@size-change="val => handlePageChange(val, 'pageSize')"
@current-change="val => handlePageChange(val, 'pageNo')"
:current-page="basePagination.pageNo"
:page-sizes="basePagination.pageSizes"
:page-size="basePagination.pageSize"
:pager-count="pagerCount"
:layout="pagerCount === 7 ? 'total, sizes, prev, pager, next, jumper' : 'total, pager'"
:total="basePagination.total">
</el-pagination>
</div>
</div>
</div>
</template>
<script>
import tableConfigMixins from "@/views/mixin/tableConfigMixins"
import { defaultSelectPageColumns } from "@/utils/constants";
import { resetSelectPage } from "@/utils/utils";
import { deepClone } from '@/utils';
import treeselect from "@riophae/vue-treeselect";
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
import BaseBtnList from '@/components/BaseBtnList/index.vue'
export default {
name: 'BaseTable',
props: {
// 搜索栏配置
searchConfig: {
type: Object,
default: () => {
return {
inline: true,
layoutType: 'last',
form: [
{
// type: 'input',
// name: 'name',
// label: '活动名称',
// placeholder: '活动名称'
}
]
}
}
},
// 操作栏配置
operateBtnConfig: {
type: Array,
default: () => {
return []
}
},
// table数据
tableData: {
type: Array,
default: () => {
return []
}
},
// table栏配置
tableConfig: {
showTable: true,
showSelection: true,
isVirtualTable: false, // 是否虚拟表格
columns: [
{
prop: '',
label: '',
minWidth: ''
}
]
},
tableLoading: false,
// 分页配置
pagination: {
type: Object,
default: () => {
return {
pageNo: 1,
total: 0,
pageSize: 30,
pageSizes: [ 30, 100, 500, 1000 ]
}
}
},
// table的唯一id字段, 用做默认row-class-name使用
idField: String
},
components: {treeselect, BaseBtnList},
data() {
return {
// 选中的数据
selectRows: [],
// 表单
searchForm: {},
// 分页
basePagination: this.pagination,
// 分页组件的默认table配置
defaultSelectPageColumns: defaultSelectPageColumns,
uTableRowHeight: 30,
pagerCount: document.body.clientWidth < 992 ? 5 : 7
}
},
mixins: [ tableConfigMixins ],
created() {
},
mounted() {
this.searchConfig?.form?.forEach(r => {
if (r.defaultValue != null) {
this.$nextTick(() => {
this.$set(this.searchForm, r.name, r.defaultValue)
})
}
})
},
methods: {
select(rows, row) {
this.$emit('select', rows, row)
},
selectAll(rows) {
this.$emit('selectAll', rows)
},
selectionChange(rows) {
this.selectRows = rows
this.$emit('selectionChange', rows)
},
search() {
let obj = deepClone(this.searchForm)
let searchForm = {}
for (const key in obj) {
if (key != 'undefined') {
searchForm[key] = obj[key]
}
}
this.$emit('search', searchForm)
},
reset() {
this.$refs.searchRef.resetFields()
this.searchConfig.form.forEach(r => {
if (r.type === 'selectPage') {
resetSelectPage(`selectPage_${r.name}`, this)
}
})
this.searchForm = {}
this.$nextTick(() => {
this.searchConfig?.form?.forEach(r => {
if (r.defaultValue != null) {
this.$set(this.searchForm, r.name, r.type === 'selectPage' ?
String(typeof(r.defaultValue) === 'function' ? r.defaultValue()
:
r.defaultValue) : typeof(r.defaultValue) === 'function' ? r.defaultValue() : r.defaultValue)
}
})
// 重置数据
let obj = deepClone(this.searchForm)
let searchForm = {}
for (const key in obj) {
if (key != 'undefined') {
searchForm[key] = obj[key]
}
}
this.$emit('reset', searchForm)
})
},
handlePageChange(val, type) {
this.$set(this.basePagination, type, val)
type === 'pageSize' && (this.$set(this.basePagination, 'pageNo', 1))
this.$emit('changePage', this.basePagination)
},
rowDblclick(row, column, event) {
this.$emit('rowDblclick', row, column, event)
},
rowClick(row, column, event) {
this.$emit('rowClick', row, column, event)
},
cellClick(row, column, cell, event) {
this.$emit('cellClick', row, column, cell, event)
},
rowContextmenu(row, column, event) {
this.$emit('rowContextmenu', row, column, event)
},
// 设置默认值
setDefaultValue() {
this.searchConfig?.form?.forEach(r => {
if (r.defaultValue != null) {
this.$set(this.searchForm, r.name, r.type === 'selectPage' ?
String(typeof(r.defaultValue) === 'function' ? r.defaultValue()
:
r.defaultValue) : typeof(r.defaultValue) === 'function' ? r.defaultValue() : r.defaultValue)
}
})
},
resultFormat(res) {
const {list, total} = res.data
return {list, totalRow: total}
},
// 输入框触发enter事件
keyupEnterNative() {
this.$emit('keyupEnterNative')
},
// 列宽拖拽
headerDragend(newWidth, oldWidth, column, event) {
this.$emit('headerDragend', newWidth, oldWidth, column, event)
},
// 设置勾选时默认的rowClassName
defaultRowClassName({row}) {
const isSelect = this.selectRows.some(r => r[this.idField] === row[this.idField])
if (isSelect) return'select-row'
},
commonSelectChange(v, item) {
toString.call(item.change) === '[object Function]' ? item.change(v) : null
},
commonInputDblclick(v, item) {
toString.call(item.dblclick) === '[object Function]' ? item.dblclick(v) : null
},
// 转换tree数据结构
normalizer(node) {
if (node.children && !node.children.length) {
delete node.children;
}
return {
id: node.id,
label: node.name,
children: node.children
};
},
sortChange({ column, prop, order }) {
this.$emit('sortChange', {column, prop, order, tableData: this.$refs?.table?.tableData})
}
},
watch: {
'searchConfig': function() {
this.setDefaultValue()
},
'pagination': {
handler(n, o) {
this.basePagination = n
},
deep: true
},
tableData: function(n) {
if (n.length && this.tableConfig.isVirtualTable) {
this.$nextTick(() => {
this.uTableRowHeight = document.querySelectorAll('.uIndex')?.[1]?.getBoundingClientRect()?.height || 30
})
}
},
'searchForm': {
handler(n, o) {
this.$emit('receiveSearch', n)
},
deep: true
},
}
}
</script>
<style lang="scss" scoped>
.container {
padding: 0 0 20px;
min-height: calc(100vh - 94px);
width: calc(100% - 20px);
margin: 0 auto;
margin-top: 10px;
border-radius: 4px;
}
.line {
width: 100%;
height: 10px;
}
.search, .operateBtn, .baseTable{
}
.search {
padding: 17px 20px 7px;
border-radius: 4px;
.search-loading {
padding-bottom: 20px;
font-size: 15px;
}
::v-deep .el-form-item{
margin-bottom: 10px;
margin-top: 0;
}
::v-deep .el-button--small {
transform: translateY(-1px);
}
}
.operateBtn {
padding: 16px 20px 6px;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
display: flex;
justify-content: space-between;
align-items: center;
::v-deep .iconfont {
font-size: 12px;
margin-right: 5px;
}
.operateBtn-left {
::v-deep .el-button{
margin-left: 0;
margin-right: 10px;
margin-bottom: 10px;
}
}
.operateBtn-right {
max-width: 40%;
}
}
.baseTable {
padding: 0 20px 15px;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
}
.searchBtn {
text-align: center;
padding: 0 0 20px;
}
.pagination {
text-align: right;
padding-top: 15px;
}
@media screen and (max-width: 1366px) {
::v-deep .el-select, ::v-deep .el-input {
max-width: 120px;
}
::v-deep .el-date-editor--daterange.el-input, ::v-deep .el-date-editor--daterange.el-input__inner, ::v-deep .el-date-editor--timerange.el-input, ::v-deep .el-date-editor--timerange.el-input__inner {
width: 220px;
}
::v-deep .el-form-item__label {
vertical-align: super !important;
}
.el-form-item {
margin-bottom: 10px !important;
margin-right: 10px !important;
}
.search .el-form-item.el-form-item--small .v-selectpage {
width: 120px;
}
.search .el-form-item.el-form-item--mini .v-selectpage div.sp-input-container div.sp-base{
width: 120px;
}
.pagination {
max-width: 95%;
}
}
</style>
baseTable 文件夹下的 baseMixins.js
import baseFetchOptions from "@/views/mixin/baseFetchOptions"
import { deepClone } from "@/utils/index";
export default {
data() {
return {
tableLoading: false,
searchForm: {},
tableData: [],
selectRows: [],
pagination: {
pageNo: 1,
total: 0,
pageSize: 30,
pageSizes: [ 30, 100, 500, 1000 ]
}
}
},
mixins: [ baseFetchOptions ],
created() {
},
methods: {
// 默认的请求列表函数
fetchList() {},
// 搜索
search(form) {
this.searchForm = form
this.fetchList()
},
// 接收改变的searchForm
receiveSearch(form) {
this.searchForm = form
},
// 重置
reset(form) {
this.searchForm = form
this.pagination.pageNo = 1
this.fetchList()
},
// 选中行
selectionChange(rows) {
this.selectRows = rows
},
// 分页事件
changePage(pageCnfig) {
this.pagination = deepClone(pageCnfig)
this.fetchList()
},
// 限制多选
getSelectRows() {
const { length } = this.selectRows
if (!length) {
this.$message.warning("至少选择一条数据!")
return false
}
return true
},
// 限制单选
getSelectRow() {
const { length } = this.selectRows
if (length != 1) {
this.$message.warning("请选择一条数据!")
return false
}
return true
}
}
}
===============================================================
baseTable 下的index 引入的 tableConfigMixins
tableConfigMixins:
// 此mixins用做处理所有table max height
import { debounce } from 'throttle-debounce'
export default {
data() {
return {
tableMaxHeight: localStorage.getItem('tableMaxHeight') || 1000,
otherHeight: 0, // 防止有别的高度没计算到
}
},
computed: {
},
created() {
},
mounted() {
this.setTableMaxHeight()
window.onresize = debounce(200, () => {
this.setTableMaxHeight()
})
},
methods: {
setTableMaxHeight() {
this.$nextTick(() => {
setTimeout(() => {
const { height } = document.body.getBoundingClientRect()
const { height: navHeight } = document.querySelector('.navbar').parentElement.getBoundingClientRect()
const { height: tableHeaderHeight, top: tableHeaderMarginTop } = document.querySelector('.el-table__header-wrapper').getBoundingClientRect()
// 使用属性控制
const h1 = height - 30 - tableHeaderHeight - tableHeaderMarginTop - this.otherHeight
this.tableMaxHeight = h1 < 450 ? 450 : h1
// 使用class和属性控制
if (document.querySelector('.el-table.tableMaxHeightClass')) {
const { height: searchH } = document.querySelector('.search-box')?.getBoundingClientRect() || {height: 0}
const { height: btnH } = document.querySelector('.btn-box')?.getBoundingClientRect() || {height: 0}
const { height: paginationH } = document.querySelector('.pagination')?.getBoundingClientRect() || {}
const h = (height - navHeight - searchH - btnH - paginationH - 80 - this.otherHeight)
const _h = h < 450 ? 450 : h
document.querySelector('.el-table.tableMaxHeightClass').style.maxHeight = _h + 'px'
this.tableMaxHeight = _h
}
// 使用class和属性控制, 虚拟 table 使用
if (document.querySelector('.tableMaxHeightClass')?.querySelector('.el-table')) {
const { height: searchH } = document.querySelector('.search-box')?.getBoundingClientRect()
const { height: btnH } = document.querySelector('.btn-box')?.getBoundingClientRect()
const { height: paginationH } = document.querySelector('.pagination')?.getBoundingClientRect() || {}
const h = (height - navHeight - searchH - btnH - paginationH - 80 - this.otherHeight)
const _h = h < 450 ? 450 : h
document.querySelector('.tableMaxHeightClass').querySelector('.el-table').style.maxHeight = _h + 'px'
this.tableMaxHeight = _h
}
},500)
})
}
},
}
====================================================================================
import { defaultSelectPageColumns } from "@/utils/constants";
defaultSelectPageColumns :
// 基础分页下拉Columns
export const defaultSelectPageColumns = [
{ title: 'CODE', data: 'code', width: 130 },
{ title: '中文名', data: 'nameCn', minWidth: 180 },
{ title: '英文名', data: 'nameEn', minWidth: 250 },
]
====================================================================================
import { resetSelectPage } from "@/utils/utils";
resetSelectPage :
/**
* 清除selectPage选中项
* @param {*} name
* @param {*} $vm vue实例
*/
export function resetSelectPage (name, $vm) {
const el = toString.call($vm.$refs[name]) === '[object Array]' ? $vm.$refs[name][0] : $vm.$refs[name]
el?.remove?.()
// 处理重置后多选selectPage会展开下拉框问题
setTimeout(() => {
el?.close?.()
})
}
============================================================================================
import { deepClone } from '@/utils';
// 深拷贝对象
// https://github.com/JakHuang/form-generator/blob/dev/src/utils/index.js#L107
export function deepClone(obj) {
const _toString = Object.prototype.toString
// null, undefined, non-object, function
if (!obj || typeof obj !== 'object') {
return obj
}
// DOM Node
if (obj.nodeType && 'cloneNode' in obj) {
return obj.cloneNode(true)
}
// Date
if (_toString.call(obj) === '[object Date]') {
return new Date(obj.getTime())
}
// RegExp
if (_toString.call(obj) === '[object RegExp]') {
const flags = []
if (obj.global) { flags.push('g') }
if (obj.multiline) { flags.push('m') }
if (obj.ignoreCase) { flags.push('i') }
return new RegExp(obj.source, flags.join(''))
}
const result = Array.isArray(obj) ? [] : obj.constructor ? new obj.constructor() : {}
for (const key in obj) {
result[key] = deepClone(obj[key])
}
return result
}
====================================================================
import BaseBtnList from '@/components/BaseBtnList/index.vue'
BaseBtnList :
<template>
<div>
<template v-for="(item, index) in list">
<el-button
:type="item.type || 'primary'"
:icon="typeof item.icon != 'function' ? item.icon : null"
:key="index"
:plain="item.plain === false ? false : true"
:loading="item.loading === true ? true : false"
:style="item.style"
:class="item.class"
:disabled="item.disabled"
@click="item.event()"
v-if="item.show === false ? false : true"
>{{ item.name }}</el-button
>
</template>
</div>
</template>
<script>
export default {
props: {
list: {
type: Array,
default: () => [],
},
},
data() {
return {
};
},
created() {},
methods: {
},
};
</script>
<style scoped lang="scss">
::v-deep .el-button+.el-button {
margin-bottom: 10px;
}
</style>
=================================================================
import treeselect from "@riophae/vue-treeselect";
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
此引入需要安装插件 安装的命令如下,哪里需要哪里引入,同时使用的components中也需要在页面中使用
yarn add @riophae/vue-treeselect