smart-bid-web/src/components/TableModel2/index.vue

675 lines
23 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<!-- 表格公共组件 -->
<div>
<!-- 表单搜索 -->
<el-card v-show="showSearch" class="search-card">
<el-form :inline="true" ref="queryFormRef" :rules="formRules" :model="queryParams" label-width="auto">
<el-form-item :key="v" :prop="item.f_model" v-for="(item, v) in formLabel"
:label="item.isShow ? item.f_label : ''">
<el-input clearable :maxlength="item.f_max || 50" v-if="item.f_type === 'ipt'"
v-model.trim="queryParams[item.f_model]" :placeholder="`请输入${item.f_label}`"
:style="{ width: item.f_width || '180px' }" />
<el-select clearable filterable v-if="item.f_type === 'sel'" v-model="queryParams[item.f_model]"
:placeholder="`请选择${item.f_label}`" :style="{ width: item.f_width || '180px' }">
<el-option :key="v" :label="sel.label" :value="sel.value" v-for="(sel, v) in item.f_selList" />
</el-select>
<el-cascader clearable style="width: 180px" :show-all-levels="false" :props="item.optionProps"
:options="item.f_selList" v-if="item.f_type === 'selCasAdd'" v-model="queryParams[item.f_model]"
@change="
handleCasAdd(
$event,
item.f_model,
cascadeFunc,
extraTableProp,
)
" />
<el-cascader style="width: 180px" :show-all-levels="false" :options="item.f_selList"
:props="item.optionProps" v-if="item.f_type === 'selCas'" v-model="queryParams[item.f_model]"
@change="handleCas($event, item.f_model)" />
<el-date-picker type="date" style="width: 240px" value-format="yyyy-MM-dd"
v-if="item.f_type === 'date'" v-model="queryParams[item.f_model]"
:placeholder="`请选择${item.f_label}`" />
<el-date-picker type="daterange" style="width: 240px" range-separator="至" value-format="yyyy-MM-dd"
start-placeholder="开始日期" end-placeholder="结束日期" v-if="item.f_type === 'dateRange'"
v-model="queryParams[item.f_model]" @change="onChangeTime($event, item.dateType)"
:picker-options="pickerOptions" />
<el-input-number :min="0" style="width: 240px" v-if="item.f_type === 'num'"
v-model="queryParams[item.f_model]" />
</el-form-item>
<!-- 自定义搜索插槽:父组件可自行插入 el-form-item -->
<slot name="form" :queryParams="queryParams"></slot>
<el-form-item v-if="showBtnCrews">
<el-button v-if="showQueryButtons" class="query-btn" @click="handleQuery">
查询
</el-button>
<el-button v-if="showQueryButtons" class="reset-btn" @click="resetQuery">
重置
</el-button>
<slot name="btn" :queryParams="queryParams"></slot>
</el-form-item>
</el-form>
</el-card>
<!-- 按钮集群 -->
<el-row class="btn-container">
<ToolbarModel v-if="showRightTools" :columns="columnCheckList" @queryTable="getTableList"
:showSearch.sync="showSearch" :handleShow.sync="handleShow" :indexNumShow.sync="indexNumShow"
:selectionShow.sync="selectionShow" :isSelectShow="isSelectShow" :showOperation="showOperation" />
</el-row>
<!-- 表格 -->
<el-card class="table-card" :style="tableCardStyle">
<!-- 表格头部插槽 -->
<div class="table-header" v-if="$slots.tableTitle || $slots.tableActions">
<div class="table-title">
<slot name="tableTitle"></slot>
</div>
<div class="table-actions">
<slot name="tableActions"></slot>
</div>
</div>
<!-- 表格容器,添加最大高度和滚动 -->
<div class="table-container" :style="tableContainerStyle">
<el-table ref="tableRef" :data="tableList" style="width: 100%" v-loading="loading" select-on-indeterminate
@selection-change="handleSelectionChange">
<el-table-column width="45" align="center" type="selection" :selectable="selectable"
v-if="selectionShow && isSelectShow" />
<el-table-column fixed width="55" label="序号" type="index" align="center" :index="(index) =>
(queryParams.pageNum - 1) * queryParams.pageSize +
index +
1
" v-if="indexNumShow" />
<el-table-column :key="v" align="center" :label="item.t_label" :prop="item.t_props"
:width="item.t_width" show-overflow-tooltip v-for="(item, v) in tableColumCheckProps">
<template slot-scope="scope">
<!-- 判断当前列数据是否需要使用插槽的数据 -->
<template v-if="item.t_slot">
<slot :data="scope.row" :name="item.t_slot"></slot>
</template>
<template v-else>
{{ scope.row[item.t_props] || '-' }}
</template>
</template>
</el-table-column>
<el-table-column align="center" label="操作" :width="handleColWidth" :show-overflow-tooltip="false"
fixed="right" v-if="handleShow && showOperation">
<template slot-scope="{ row }">
<div class="optionDivRef">
<slot :data="row" name="handle">-</slot>
</div>
</template>
</el-table-column>
</el-table>
</div>
<!-- 分页 -->
<pagination :total="total" @pagination="getTableList" :page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize" />
</el-card>
</div>
</template>
<script>
import ToolbarModel from '../ToolbarModel'
export default {
components: { ToolbarModel },
props: {
// 表单查询条件
formLabel: {
type: Array,
default: () => [],
},
// 列表请求接口
requestApi: {
type: Function,
default: () => function () { },
},
// 列表配置项
columnsList: {
type: Array,
default: () => [],
},
// 传递的额外参数
sendParams: {
type: Object,
default: () => null,
},
// 是否显示查询按钮 默认显示
showBtnCrews: {
type: Boolean,
default: true,
},
// 是否显示查询/重置按钮(不影响插槽按钮) 默认显示
showQueryButtons: {
type: Boolean,
default: true,
},
// 级联选择
cascadeFunc: {
type: Function,
default: () => null,
},
// 级联选择 额外参数
extraTableProp: {
type: Object,
default: () => null,
},
// 是否显示操作列 默认不显示
showOperation: {
type: Boolean,
default: false,
},
// 是否显示右侧工具栏 默认不显示
showRightTools: {
type: Boolean,
default: false,
},
// 复选框是否勾选
selectable: {
type: Function,
default: () => {
return true
},
},
// 是否显示复选框 由父组件传递 默认不显示
isSelectShow: {
type: Boolean,
default: false,
},
// 测试时使用的数据源
testTableList: {
type: Array,
default: () => [],
},
// 是否显示当天日期
isCurrentDate: {
type: Boolean,
default: false,
},
// 是否只可选择一个月的范围
isOneMonth: {
type: Boolean,
default: false,
},
// 表格最大高度像素默认600px
tableMaxHeight: {
type: [Number, String],
default: 600
},
},
computed: {
/* 根据操作栏控制表头是否显示 */
tableColumCheckProps() {
return this.columnCheckList.filter((e) => {
return e.checked != false
})
},
/* 表格卡片动态高度 */
tableCardStyle() {
const baseHeight = this.showSearch ? 200 : 120; // 搜索区域高度
const toolbarHeight = this.showRightTools ? 40 : 0; // 工具栏高度
const paginationHeight = 60; // 分页区域高度
const availableHeight = `calc(100vh - ${baseHeight + toolbarHeight + paginationHeight + 25}px)`;
return {
height: availableHeight,
display: 'flex',
flexDirection: 'column'
}
},
/* 表格容器样式 */
tableContainerStyle() {
return {
flex: 1,
overflow: 'hidden',
display: 'flex',
flexDirection: 'column'
}
},
},
watch: {
columnsList: {
handler(nv, ov) {
if (nv !== ov) {
this.columnsList = nv
this.columnCheckList = this.columnsList.map((e) => {
this.$set(e, 'checked', true)
return e
})
}
},
deep: true,
},
},
data() {
return {
// 表单校验
formRules: {},
loading: false,
// 列表接口查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
},
// 列表数据源
tableList: [],
// 选中数据
selectedData: [],
// 列表数据条数
total: 0,
// 搜索区域是否隐藏
showSearch: true,
// 是否显示复选框
selectionShow: true,
// 是否显示序号
indexNumShow: true,
// 是否显示操作列
handleShow: true,
// 列表每列 label
columnCheckList: [],
// 操作列最小宽度
dynamicWidth: 0,
// 操作列固定宽度,防止列数过少导致过宽
handleColWidth: 200,
// 自增id
idCount: 1,
// 日期查询条件 字段名称
typeList: [],
// 日期选择器配置
pickerOptions: {},
}
},
created() {
this.columnCheckList = this.columnsList.map((e) => {
this.$set(e, 'checked', true)
return e
})
/* 生成查询参数 */
this.formLabel.map((e) => {
if (e.f_type === 'dateRange') {
this.$set(this.queryParams, e.dateType[0], '')
this.$set(this.queryParams, e.dateType[1], '')
this.typeList = e.dateType
if (this.isOneMonth) {
this.pickerOptions = {
disabledDate: (time) => {
// 禁用今天之后的日期
return time.getTime() > Date.now()
},
onPick: ({ maxDate, minDate }) => {
// 当选择第一个日期后,设置第二个日期的可选范围
this.pickerOptions.minDate = minDate
if (minDate) {
const maxRangeDate = new Date(minDate)
maxRangeDate.setMonth(minDate.getMonth() + 1)
this.pickerOptions.maxDate = maxRangeDate
} else {
this.pickerOptions.maxDate = null
}
},
}
}
} else {
this.$set(this.queryParams, e.f_model, '')
}
})
if (this.sendParams !== null) {
for (let key in this.sendParams) {
this.$set(this.queryParams, key, this.sendParams[key])
}
}
this.getTableList()
},
updated() {
// 若需要自适应最小宽度,可启用下行;当前使用固定宽度 handleColWidth
this.dynamicWidth = this.getOperatorWidth()
},
methods: {
/** 获取列表数据 */
async getTableList() {
try {
if (
this.queryParams.time &&
this.queryParams.time.length !== 0
) {
this.queryParams[this.typeList[0]] =
this.queryParams.time[0]
this.queryParams[this.typeList[1]] =
this.queryParams.time[1]
}
const params = { ...this.queryParams }
const queryParams = JSON.parse(JSON.stringify(params))
delete queryParams.time // 剔除无关参数
// console.log(
// `%c🔍 列表查询入参 %c`,
// 'background: linear-gradient(90deg, #FF6B6B, #4ECDC4); color: white; padding: 5px 10px; border-radius: 5px; font-weight: bold;',
// '',
// queryParams,
// )
this.loading = true
const res = await this.requestApi(queryParams)
if (res.code === 200) {
this.tableList = res.rows
this.total = res.total
}
this.loading = false
} catch (error) {
this.loading = false
this.tableList = this.testTableList
}
},
/** 查询按钮 */
handleQuery() {
this.getTableList()
},
/** 重置按钮 */
resetQuery() {
this.$refs.queryFormRef.resetFields()
if (this.typeList.length > 1) {
if (this.isCurrentDate) {
this.queryParams[this.typeList[0]] = new Date()
.toISOString()
.split('T')[0]
this.queryParams[this.typeList[1]] = new Date()
.toISOString()
.split('T')[0]
this.queryParams.time = [
new Date().toISOString().split('T')[0],
new Date().toISOString().split('T')[0],
]
} else {
this.queryParams[this.typeList[0]] = ''
this.queryParams[this.typeList[1]] = ''
this.queryParams.time = []
}
} else {
this.queryParams[this.typeList[0]] = ''
}
this.queryParams.pageNum = 1
this.queryParams.pageSize = 10
this.getTableList()
},
/** 级联选择 */
handleCas(e, val) {
this.queryParams[val] = e[e.length - 1]
},
/** 级联选择只选最后一级 */
handleCasAdd(e, val, func, prop) {
if (e.length !== 0) {
this.queryParams[val] = e[e.length - 1]
let setObj = {}
// 合并
if (prop) {
Object.assign(setObj, prop)
}
// 设置id自增
this.$set(setObj, 'id', this.idCount)
this.idCount++
// 获取单位
func({
id: e[e.length - 1],
})
.then((res) => {
this.$set(setObj, 'name', res.data.parentName)
this.$set(setObj, 'unitName', res.data.unitName)
this.$set(setObj, 'typeName', res.data.name)
})
.catch((err) => { })
for (let key in this.queryParams) {
this.$set(setObj, key, this.queryParams[key])
}
this.tableList.unshift(setObj)
console.log(this.tableList)
}
},
/** 动态设置操作列的列宽 */
getOperatorWidth() {
const operatorColumn =
document.getElementsByClassName('optionDivRef')
// 默认宽度
let width = 100
// 内间距
let paddingSpacing = 0
// 按钮数量
let buttonCount = 0
if (operatorColumn.length > 0) {
Array.prototype.forEach.call(operatorColumn, function (item) {
// 最宽的宽度
width = width > item.offsetWidth ? width : item.offsetWidth
const buttons = item.getElementsByClassName('el-button')
buttonCount = buttons.length
buttonCount =
buttonCount > buttons.length
? buttonCount
: buttons.length
})
return width + 10
}
},
queryTableList(params) {
Object.assign(this.queryParams, params)
this.getTableList()
},
handleSelectionChange(e) {
this.selectedData = e
},
/* 时间change事件 */
onChangeTime(e, type) {
if (this.isOneMonth) {
if (e && e.length === 2) {
const [start, end] = e
const startDate = new Date(start)
const endDate = new Date(end)
// 计算两个日期之间的天数差
const diffTime = Math.abs(endDate - startDate)
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
// 如果超过31天自动调整结束日期
if (diffDays > 31) {
const newEndDate = new Date(startDate)
newEndDate.setDate(startDate.getDate() + 31)
this.queryParams.time = [
start,
newEndDate.toISOString().split('T')[0],
]
this.$message.warning(
'选择的时间范围超过31天查询将会缓慢已自动调整',
)
}
}
} else {
const [_1, _2] = type
const [_time1, _time2] = e
if (e.length > 0) {
this.queryParams[_1] = _time1
this.queryParams[_2] = _time2
}
}
},
},
watch: {
sendParams: {
handler(nv, ov) {
if (nv) {
for (let key in nv) {
this.$set(this.queryParams, key, this.sendParams[key])
}
}
},
deep: true,
},
},
}
</script>
<style scoped lang="scss">
.search-card {
margin-bottom: 10px;
::v-deep .el-form-item {
margin-bottom: 10px;
}
.query-btn {
width: 98px;
height: 36px;
background: #1F72EA;
box-shadow: 0px 4px 8px 0px rgba(51, 135, 255, 0.5);
border-radius: 4px 4px 4px 4px;
color: #fff;
border: none;
font-size: 14px;
transition: all 0.3s;
&:hover {
background: #4A8BFF;
box-shadow: 0px 6px 12px 0px rgba(51, 135, 255, 0.6);
}
}
.reset-btn {
width: 98px;
height: 36px;
background: #FFFFFF;
box-shadow: 0px 4px 8px 0px rgba(76, 76, 76, 0.2);
border-radius: 4px 4px 4px 4px;
color: #333;
border: none;
font-size: 14px;
transition: all 0.3s;
&:hover {
background: #f5f5f5;
box-shadow: 0px 6px 12px 0px rgba(76, 76, 76, 0.3);
color: #40a9ff;
}
}
}
.table-card {
::v-deep .el-card__body {
padding: 20px;
height: 100%;
display: flex;
flex-direction: column;
}
.table-header {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 12px;
flex-shrink: 0;
.table-title {
font-size: 16px;
font-weight: 600;
color: #333;
}
.table-actions {
display: flex;
align-items: center;
gap: 8px;
}
}
// 新增表格容器样式
.table-container {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
min-height: 0; // 确保容器可以收缩
::v-deep .el-table {
flex: 1;
height: 100%;
width: 100%;
margin-bottom: 0;
// 确保表格体可以滚动
.el-table__body-wrapper {
overflow-x: auto;
overflow-y: auto;
max-height: 100%;
}
}
}
::v-deep .el-pagination {
flex-shrink: 0;
margin-top: 16px;
padding: 0;
text-align: right;
background: #fff;
padding-top: 16px;
}
}
.btn-container {
margin-bottom: 6px;
display: flex;
justify-content: flex-end;
align-items: center;
}
::v-deep .btn-handler {
display: flex;
flex: 1;
.el-button {
padding: 6px 18px;
}
}
::v-deep .optionDivRef {
white-space: nowrap;
display: inline-block;
.el-button {
padding: 6px 12px;
}
}
// 表格样式优化
::v-deep .el-table {
.el-button--text {
padding: 0;
font-size: 14px;
}
// 表头样式
thead {
th {
background-color: #F1F6FF;
color: #424242;
font-size: 16px;
font-weight: 600;
}
}
// 表格边框样式
&.el-table--border {
border: 1px solid #ebeef5;
border-right: none;
border-bottom: none;
th,
td {
border-right: 1px solid #ebeef5;
}
}
}
</style>