|
@@ -2,42 +2,86 @@
|
|
|
import type {
|
|
|
BaseFieldItem,
|
|
|
TableFields,
|
|
|
- TableFilterItem,
|
|
|
TableProps,
|
|
|
} from '@/types/Tables/table'
|
|
|
-import type { FormInstance } from 'element-plus'
|
|
|
-import type { TableData, BaseFieldInfo } from '@/types/Tables/table'
|
|
|
|
|
|
-import { TableFilterType } from '@/types/Tables/table'
|
|
|
-import { resetReactive } from '@/utils/common'
|
|
|
-import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue'
|
|
|
-import { Search } from '@element-plus/icons-vue'
|
|
|
+import type { TableData } from '@/types/Tables/table'
|
|
|
+import type { PaginationConfig } from '@/types/Tables/pagination'
|
|
|
+
|
|
|
+import { computed, onMounted, reactive, ref, watch } from 'vue'
|
|
|
+
|
|
|
import { Plus, Operation } from '@element-plus/icons-vue'
|
|
|
import { useTable } from '@/hooks/useTable'
|
|
|
-import { useDate } from '@/hooks/useDate'
|
|
|
+import { useTableScroll } from '@/hooks/useTableScroll'
|
|
|
|
|
|
import customIndicatorDialog from '../dialog/customIndicatorDialog.vue'
|
|
|
+import TableQueryForm from './TableQueryForm.vue'
|
|
|
+
|
|
|
+type TableQueryForm = InstanceType<typeof TableQueryForm>
|
|
|
+type CustomIndicatorDialog = InstanceType<typeof customIndicatorDialog>
|
|
|
+
|
|
|
+const props = withDefaults(defineProps<TableProps>(), {
|
|
|
+ remotePagination: false,
|
|
|
+})
|
|
|
|
|
|
-type CustomIndicatorDialog = typeof customIndicatorDialog
|
|
|
+// table 容器
|
|
|
+const tableContent = ref<HTMLElement | null>(null)
|
|
|
|
|
|
-const props = withDefaults(defineProps<TableProps>(), {})
|
|
|
+// table的横向滚动条
|
|
|
+const elScrollBarH = ref<HTMLElement | null>(null)
|
|
|
|
|
|
-const emits = defineEmits(['updateCustomIndicator'])
|
|
|
+// 表头
|
|
|
+const tableHeaderRef = ref<HTMLElement | null>(null)
|
|
|
|
|
|
-const { isFixedField } = useTable()
|
|
|
-const { disableDate, shortcuts } = useDate()
|
|
|
+// 表格整体容器
|
|
|
+const tableContainer = ref<HTMLElement | null>(null)
|
|
|
+
|
|
|
+// 自定义指标对话框Ref
|
|
|
+const customIndicatorDialogRef = ref<CustomIndicatorDialog>()
|
|
|
|
|
|
+// 表格上方查询表单
|
|
|
+const tableQueryFormRef = ref<TableQueryForm>()
|
|
|
+
|
|
|
+const emits = defineEmits([
|
|
|
+ 'updateCustomIndicator',
|
|
|
+ 'pageSizeChange',
|
|
|
+ 'curPageChange',
|
|
|
+])
|
|
|
+
|
|
|
+const {
|
|
|
+ isFixedField,
|
|
|
+ initTableFields,
|
|
|
+ initIndicatorFields,
|
|
|
+ setCacheTableData,
|
|
|
+} = useTable()
|
|
|
+
|
|
|
+const { initScroll, setScrollAndHeader, obScroll } = useTableScroll(
|
|
|
+ elScrollBarH,
|
|
|
+ tableContent,
|
|
|
+ tableContainer,
|
|
|
+ tableHeaderRef,
|
|
|
+)
|
|
|
+
|
|
|
+const rowHeight = 56 // 表格行高
|
|
|
const tableFieldLWidth = 200 // 长单元格的宽度
|
|
|
const tableFieldSWidth = 150 // 短单元格的宽度
|
|
|
|
|
|
// 表格数据
|
|
|
const tableData = reactive<Array<TableData>>(props.tableData)
|
|
|
|
|
|
-// table 容器
|
|
|
-const tableContent = ref<HTMLElement>()
|
|
|
+// 缓存的表格数据,如果使用了远程分页,则将每一页的表格数据缓存下来
|
|
|
+const cacheTableData = reactive<Array<Array<TableData>>>([])
|
|
|
|
|
|
-// table的横向滚动条
|
|
|
-const elScrollBarH = ref<HTMLElement>()
|
|
|
+// 表格分页信息
|
|
|
+const paginationConfig = reactive<PaginationConfig>({
|
|
|
+ curPage: 1,
|
|
|
+ curPageSize: 0,
|
|
|
+ pageSizeList: [],
|
|
|
+ total: 0,
|
|
|
+})
|
|
|
+
|
|
|
+// 表格可见性的观察者
|
|
|
+const tableVisOb = new IntersectionObserver(obScroll)
|
|
|
|
|
|
// 表格字段信息
|
|
|
const tableFieldsInfo = reactive<Array<TableFields>>([])
|
|
@@ -45,35 +89,9 @@ const tableFieldsInfo = reactive<Array<TableFields>>([])
|
|
|
// 自定义指标的字段信息,为了不影响原本的表格字段信息
|
|
|
const indicatorFields = reactive<Array<BaseFieldItem<TableFields>>>([])
|
|
|
|
|
|
-// 表格整体容器
|
|
|
-const tableContainer = ref<HTMLElement>()
|
|
|
-
|
|
|
// 自定义指标侧边栏的默认选中
|
|
|
const defaultActiveNav = ref<string>('')
|
|
|
|
|
|
-// 表单数据
|
|
|
-const filterFormData = reactive<{
|
|
|
- [key: string]: any
|
|
|
-}>({})
|
|
|
-
|
|
|
-// 表单搜索框所对应的字段,因为可能是多选,会在初始化的时候给初值
|
|
|
-const searchSelected = ref('')
|
|
|
-
|
|
|
-// 表单ref
|
|
|
-const filterFormRef = ref<FormInstance>()
|
|
|
-
|
|
|
-// 过滤表单字段的状态信息
|
|
|
-const filterFieldsStateList = reactive<
|
|
|
- Array<{
|
|
|
- label: string
|
|
|
- state: boolean
|
|
|
- value: any
|
|
|
- }>
|
|
|
->([])
|
|
|
-
|
|
|
-// 当前已经选中的展示的过滤字段
|
|
|
-const filterFields = ref<Array<any>>([])
|
|
|
-
|
|
|
// 批量操作选中的值
|
|
|
const batchOper = ref<string>()
|
|
|
|
|
@@ -94,171 +112,58 @@ const batchOperList = reactive<
|
|
|
},
|
|
|
])
|
|
|
|
|
|
-/**
|
|
|
- * @description: 初始化自定义指标的字段信息
|
|
|
- * @return {*}
|
|
|
- */
|
|
|
-const initIndicatorFields = () => {
|
|
|
- resetReactive(indicatorFields)
|
|
|
- indicatorFields.splice(
|
|
|
- 0,
|
|
|
- indicatorFields.length,
|
|
|
- ...JSON.parse(JSON.stringify(props.tableFields)),
|
|
|
- )
|
|
|
- defaultActiveNav.value = indicatorFields[0].name
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
- * @description: 初始化表格字段,把所有字段都放到一个数组中
|
|
|
- * @return {*}
|
|
|
- */
|
|
|
-const initTableFields = () => {
|
|
|
- resetReactive(tableFieldsInfo)
|
|
|
- if (props.sortedTableFields.length > 0) {
|
|
|
- tableFieldsInfo.splice(
|
|
|
- 0,
|
|
|
- tableFieldsInfo.length,
|
|
|
- ...props.sortedTableFields,
|
|
|
- )
|
|
|
+// 分页后的表格数据
|
|
|
+const paginationTableData = computed<Array<TableData>>(() => {
|
|
|
+ let result: Array<TableData> = []
|
|
|
+ if (props.remotePagination) {
|
|
|
+ result = cacheTableData[paginationConfig.curPage - 1]
|
|
|
} else {
|
|
|
- indicatorFields.forEach(item => {
|
|
|
- item.children.forEach(child => {
|
|
|
- if (child.fixed || child.state) {
|
|
|
- item.value.push(child.name)
|
|
|
- child.state = true
|
|
|
- }
|
|
|
- tableFieldsInfo.push(JSON.parse(JSON.stringify(child)))
|
|
|
- })
|
|
|
- })
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// 自定义指标中需要固定的指标
|
|
|
-
|
|
|
-/**
|
|
|
- * @description: 初始化查询表单
|
|
|
- * @return {*}
|
|
|
- */
|
|
|
-const initfilterForm = () => {
|
|
|
- // filterFormData
|
|
|
- resetReactive(filterFormData)
|
|
|
- resetReactive(filterFieldsStateList)
|
|
|
- for (let [k, v] of Object.entries(props.filtersInfo)) {
|
|
|
- let val = JSON.parse(JSON.stringify(v)) as TableFilterItem
|
|
|
- // 如果是搜索框,需要赋初值字段,即作为搜索的字段
|
|
|
- if (k === 'search') {
|
|
|
- searchSelected.value = val.value
|
|
|
- }
|
|
|
- // 把时间类型给个默认值
|
|
|
- if (val.type === TableFilterType.Date) {
|
|
|
- val.value = [shortcuts[0].value()[0], shortcuts[0].value()[1]]
|
|
|
- }
|
|
|
-
|
|
|
- // 表单给初始值
|
|
|
- filterFormData[k] = val.value ? val.value : ''
|
|
|
-
|
|
|
- // 初始化查询表单字段的状态信息
|
|
|
- filterFieldsStateList.push({
|
|
|
- label: val.label,
|
|
|
- state: true,
|
|
|
- value: val.name,
|
|
|
- })
|
|
|
+ let curPage = paginationConfig.curPage
|
|
|
+ let pageSize = paginationConfig.curPageSize
|
|
|
+ result = tableData.slice((curPage - 1) * pageSize, curPage * pageSize)
|
|
|
}
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
- * @description: 初始化过滤字段,把所有字段展示出来
|
|
|
- * @return {*}
|
|
|
- */
|
|
|
-const initFilterFields = () => {
|
|
|
- resetReactive(filterFields.value)
|
|
|
- filterFieldsStateList.forEach(v => {
|
|
|
- filterFields.value.push(v.value)
|
|
|
- })
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
- * @description: 重置查询表单
|
|
|
- * @return {*}
|
|
|
- */
|
|
|
-const resetFilterForm = () => {
|
|
|
- filterFormRef.value?.resetFields()
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
- * @description:根据现有的筛选字段重新生成查询参数
|
|
|
- * @return {[key: string]: any} 查询参数
|
|
|
- */
|
|
|
-const createQueryParams = (): {
|
|
|
- [key: string]: any
|
|
|
-} => {
|
|
|
- let queryParams: {
|
|
|
- [key: string]: any
|
|
|
- } = {}
|
|
|
- for (let i in filterFormData) {
|
|
|
- // 当前字段没有被隐藏,则加入查询参数
|
|
|
- if (filterFields.value.includes(i)) {
|
|
|
- queryParams[i] = filterFormData[i]
|
|
|
- }
|
|
|
- }
|
|
|
- return queryParams
|
|
|
-}
|
|
|
+ return result
|
|
|
+})
|
|
|
|
|
|
/**
|
|
|
* @description: 查询表格
|
|
|
* @return {*}
|
|
|
*/
|
|
|
-const queryTable = () => {
|
|
|
- let queryParams: {
|
|
|
- [key: string]: any
|
|
|
- } = createQueryParams()
|
|
|
-}
|
|
|
+const queryTable = () => {}
|
|
|
+
|
|
|
+const resetTable = () => {}
|
|
|
|
|
|
/**
|
|
|
- * @description: 判定当前横向滑动条是否在表格内,有BUG
|
|
|
- *
|
|
|
+ * @description: 初始化分页配置项
|
|
|
* @return {*}
|
|
|
*/
|
|
|
-const isIntable = () => {
|
|
|
- if (!tableContainer.value && !tableContent.value) return false
|
|
|
- const { scrollTop, clientHeight, scrollHeight } =
|
|
|
- tableContainer.value as HTMLElement
|
|
|
-
|
|
|
- return scrollTop + clientHeight < scrollHeight
|
|
|
+const initPagination = () => {
|
|
|
+ Object.assign(paginationConfig, { ...props.paginationConfig })
|
|
|
+ paginationConfig.curPageSize = paginationConfig.pageSizeList[0]
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * @description: 初始化滑动条
|
|
|
+ * @description: 表格分页大小改变
|
|
|
* @return {*}
|
|
|
*/
|
|
|
-const initScroll = () => {
|
|
|
- let sc = document.querySelector('.el-scrollbar__bar') as HTMLElement
|
|
|
-
|
|
|
- elScrollBarH.value = sc
|
|
|
- if (isIntable()) {
|
|
|
- elScrollBarH.value.style.position = 'fixed'
|
|
|
- elScrollBarH.value.style.left =
|
|
|
- tableContent.value?.getBoundingClientRect().left + 'px'
|
|
|
+const tableSizeChange = (size: number) => {
|
|
|
+ // 如果当前是远程分页,则需要通知父组件重新请求数据
|
|
|
+ if (props.remotePagination) {
|
|
|
+ emits('pageSizeChange', size)
|
|
|
}
|
|
|
+ paginationConfig.curPage = 1
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * @description: 设置横向滑动条的位置
|
|
|
- * 只有滑动位置到达表格内部时,才需要固定横向滑动条,否则让滚动条回到原来的初始位置,即表格的底部
|
|
|
+ * @description: 表格页数改变
|
|
|
* @return {*}
|
|
|
*/
|
|
|
-const setElScrollBar = () => {
|
|
|
- if (isIntable()) {
|
|
|
- elScrollBarH.value!.style.position = 'fixed'
|
|
|
- elScrollBarH.value!.style.left =
|
|
|
- tableContent.value?.getBoundingClientRect().left + 'px'
|
|
|
- } else {
|
|
|
- elScrollBarH.value!.style.position = 'absolute'
|
|
|
+const tablePageChange = (page: number) => {
|
|
|
+ if (props.remotePagination) {
|
|
|
+ emits('curPageChange', page)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-const customIndicatorDialogRef = ref<CustomIndicatorDialog>()
|
|
|
-
|
|
|
/**
|
|
|
* @description: 展示自定义指标弹窗
|
|
|
* @return {*}
|
|
@@ -283,10 +188,18 @@ watch(
|
|
|
() => props.tableData,
|
|
|
newData => {
|
|
|
tableData.splice(0, tableData.length, ...newData)
|
|
|
- initfilterForm()
|
|
|
- initFilterFields()
|
|
|
- initIndicatorFields()
|
|
|
- initTableFields()
|
|
|
+ initPagination()
|
|
|
+ setCacheTableData(
|
|
|
+ props.remotePagination,
|
|
|
+ paginationConfig,
|
|
|
+ cacheTableData,
|
|
|
+ newData,
|
|
|
+ )
|
|
|
+
|
|
|
+ tableQueryFormRef.value?.initfilterForm()
|
|
|
+ tableQueryFormRef.value?.initFilterFields()
|
|
|
+ initIndicatorFields(indicatorFields, props.tableFields, defaultActiveNav)
|
|
|
+ initTableFields(tableFieldsInfo, props.sortedTableFields, indicatorFields)
|
|
|
},
|
|
|
{
|
|
|
deep: true,
|
|
@@ -294,129 +207,19 @@ watch(
|
|
|
)
|
|
|
onMounted(() => {
|
|
|
initScroll()
|
|
|
+ tableVisOb.observe(tableContent.value as HTMLElement)
|
|
|
})
|
|
|
</script>
|
|
|
|
|
|
<template>
|
|
|
- <div class="tableContainer" ref="tableContainer" @scroll="setElScrollBar">
|
|
|
+ <div class="tableContainer" ref="tableContainer" @scroll="setScrollAndHeader">
|
|
|
<div class="testContainer">
|
|
|
- <div class="filterContainer">
|
|
|
- <div class="filterContent">
|
|
|
- <el-form
|
|
|
- :model="filterFormData"
|
|
|
- label-width="0"
|
|
|
- ref="filterFormRef"
|
|
|
- :inline="true"
|
|
|
- >
|
|
|
- <!-- <div class="filterFields"> -->
|
|
|
- <el-form-item>
|
|
|
- <el-input
|
|
|
- :readonly="true"
|
|
|
- placeholder="筛选字段"
|
|
|
- prefix-icon="Filter"
|
|
|
- style="width: 220px; margin-bottom: 10px"
|
|
|
- >
|
|
|
- <template #append>
|
|
|
- <el-select
|
|
|
- v-model="filterFields"
|
|
|
- multiple
|
|
|
- collapse-tags
|
|
|
- collapse-tags-tooltip
|
|
|
- placeholder="Select"
|
|
|
- style="width: 110px"
|
|
|
- >
|
|
|
- <el-option
|
|
|
- v-for="item in filterFieldsStateList"
|
|
|
- :key="item.value"
|
|
|
- :label="item.label"
|
|
|
- :value="item.value"
|
|
|
- />
|
|
|
- </el-select>
|
|
|
- </template>
|
|
|
- </el-input>
|
|
|
- </el-form-item>
|
|
|
- <!-- </div> -->
|
|
|
- <template v-for="item in filtersInfo" :key="item.name">
|
|
|
- <el-form-item :prop="item.name">
|
|
|
- <el-input
|
|
|
- v-model="filterFormData[item.name]"
|
|
|
- style="max-width: 600px"
|
|
|
- placeholder="输入关键字搜索"
|
|
|
- class="input-with-select"
|
|
|
- v-if="
|
|
|
- filterFields.includes(item.name) &&
|
|
|
- item.type === TableFilterType.Search
|
|
|
- "
|
|
|
- >
|
|
|
- <template #prepend>
|
|
|
- <el-select v-model="searchSelected" style="width: 115px">
|
|
|
- <el-option
|
|
|
- v-for="option in item.options"
|
|
|
- :label="option.label"
|
|
|
- :value="option.value"
|
|
|
- />
|
|
|
- </el-select>
|
|
|
- </template>
|
|
|
- <template #append>
|
|
|
- <el-button :icon="Search" />
|
|
|
- </template>
|
|
|
- </el-input>
|
|
|
-
|
|
|
- <el-select
|
|
|
- class="filterItem"
|
|
|
- v-if="
|
|
|
- filterFields.includes(item.name) &&
|
|
|
- item.type === TableFilterType.Select
|
|
|
- "
|
|
|
- v-model="filterFormData[item.name]"
|
|
|
- placeholder="Select"
|
|
|
- style="width: 240px"
|
|
|
- >
|
|
|
- <template #label="{ label, value }">
|
|
|
- <span>{{ item.label }}: </span>
|
|
|
- <span style="margin-left: 10px">{{ value }}</span>
|
|
|
- </template>
|
|
|
- <el-option
|
|
|
- v-for="option in item.options"
|
|
|
- :key="option.value"
|
|
|
- :label="option.label"
|
|
|
- :value="option.value"
|
|
|
- />
|
|
|
- </el-select>
|
|
|
-
|
|
|
- <el-date-picker
|
|
|
- v-if="
|
|
|
- filterFields.includes(item.name) &&
|
|
|
- item.type === TableFilterType.Date
|
|
|
- "
|
|
|
- v-model="filterFormData[item.name]"
|
|
|
- type="daterange"
|
|
|
- unlink-panels
|
|
|
- :disableDate="disableDate"
|
|
|
- range-separator="至"
|
|
|
- :default-value="[item.startDate, item.endDate]"
|
|
|
- start-placeholder="开始时间"
|
|
|
- end-placeholder="结束时间"
|
|
|
- :shortcuts="shortcuts"
|
|
|
- size="small"
|
|
|
- />
|
|
|
- </el-form-item>
|
|
|
- </template>
|
|
|
- </el-form>
|
|
|
- </div>
|
|
|
- <div class="filterButtonContainer">
|
|
|
- <el-button class="queryBtn" color="#197afb" @click="queryTable"
|
|
|
- >查询</el-button
|
|
|
- >
|
|
|
- <el-button
|
|
|
- class="queryBtn"
|
|
|
- color="#626aef"
|
|
|
- plain
|
|
|
- @click="resetFilterForm"
|
|
|
- >重置</el-button
|
|
|
- >
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ <TableQueryForm
|
|
|
+ ref="tableQueryFormRef"
|
|
|
+ :filters-info="props.filtersInfo"
|
|
|
+ @query-table="queryTable"
|
|
|
+ @reset-table="resetTable"
|
|
|
+ ></TableQueryForm>
|
|
|
<div class="operationContainer">
|
|
|
<div class="tableOperationLeft">
|
|
|
<slot name="addItem">
|
|
@@ -456,11 +259,12 @@ onMounted(() => {
|
|
|
</div>
|
|
|
<div class="tableContent" ref="tableContent">
|
|
|
<el-table
|
|
|
- v-bind="{ ...$attrs, data: tableData }"
|
|
|
+ v-bind="{ ...$attrs, data: paginationTableData }"
|
|
|
style="width: 100%"
|
|
|
border
|
|
|
table-layout="fixed"
|
|
|
:scrollbar-always-on="true"
|
|
|
+ :row-style="() => `height:${rowHeight}px`"
|
|
|
>
|
|
|
<template v-for="item in tableFieldsInfo" :key="item.name">
|
|
|
<el-table-column
|
|
@@ -468,14 +272,14 @@ onMounted(() => {
|
|
|
label="操作"
|
|
|
fixed
|
|
|
width="160"
|
|
|
+ show-overflow-tooltip
|
|
|
>
|
|
|
<template #default="scope">
|
|
|
- <slot name="operations" :row="scope.row">
|
|
|
- <!-- {{ scope.row[item.prop] }} -->
|
|
|
- </slot>
|
|
|
+ <slot name="operations" :row="scope.row"> </slot>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column
|
|
|
+ show-overflow-tooltip
|
|
|
:width="
|
|
|
isFixedField(props.fixedFields, item)
|
|
|
? tableFieldLWidth
|
|
@@ -488,14 +292,19 @@ onMounted(() => {
|
|
|
>
|
|
|
</el-table-column>
|
|
|
</template>
|
|
|
- <!-- <el-table-column label="操作" fixed width="200">
|
|
|
- <template #default="scope">
|
|
|
- <slot name="operations" :row="scope.row">
|
|
|
-
|
|
|
- </slot>
|
|
|
- </template>
|
|
|
- </el-table-column> -->
|
|
|
</el-table>
|
|
|
+ <div class="paginationBox">
|
|
|
+ <el-pagination
|
|
|
+ v-model:current-page="paginationConfig.curPage"
|
|
|
+ v-model:page-size="paginationConfig.curPageSize"
|
|
|
+ :page-sizes="paginationConfig.pageSizeList"
|
|
|
+ :size="'default'"
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
+ :total="paginationConfig.total"
|
|
|
+ @size-change="tableSizeChange"
|
|
|
+ @current-change="tablePageChange"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<customIndicatorDialog
|