|
|
@@ -1,20 +1,57 @@
|
|
|
<script setup lang="ts">
|
|
|
import type { TableProps } from '@/types/Tables/table'
|
|
|
+import type { FormInstance } from 'element-plus'
|
|
|
import { TableFilterType } from '@/types/Tables/table'
|
|
|
|
|
|
-import { computed, reactive, ref } from 'vue'
|
|
|
+import { nextTick, onMounted, reactive, ref } from 'vue'
|
|
|
import { Search } from '@element-plus/icons-vue'
|
|
|
+import { Plus, Operation } from '@element-plus/icons-vue'
|
|
|
+import { useRequest } from '@/hooks/useRequest'
|
|
|
|
|
|
+import axiosInstance from '@/utils/axios/axiosInstance'
|
|
|
+
|
|
|
+interface AccountData {
|
|
|
+ accountBalance: number // 账户余额
|
|
|
+ accountFlow: number // 账户流动
|
|
|
+ accountName: string // 账户名
|
|
|
+ accountStatus: string // 账户状态
|
|
|
+ action: string // 操作
|
|
|
+ adAccountCount: number // 广告账户数量
|
|
|
+ dailyBudget: number // 每日预算
|
|
|
+ loginEmail: string // 登录邮箱
|
|
|
+ mediaId: string // 媒体ID
|
|
|
+}
|
|
|
+
|
|
|
+type AccountKeys = keyof AccountData
|
|
|
+
|
|
|
+const { AllApi } = useRequest()
|
|
|
const props = withDefaults(defineProps<TableProps>(), {})
|
|
|
+const tableFieldLWidth = 200
|
|
|
+const tableFieldSWidth = 150
|
|
|
+const xScrollBaseOffset = 100
|
|
|
+
|
|
|
+// 表格字段
|
|
|
+const accountFields: AccountKeys[] = [
|
|
|
+ 'accountName',
|
|
|
+ 'accountBalance',
|
|
|
+ 'accountFlow',
|
|
|
|
|
|
-// 过滤表单的数据
|
|
|
+ 'accountStatus',
|
|
|
+ 'action',
|
|
|
+ 'adAccountCount',
|
|
|
+ 'dailyBudget',
|
|
|
+ 'loginEmail',
|
|
|
+ 'mediaId',
|
|
|
+]
|
|
|
+
|
|
|
+// 表单数据
|
|
|
const filterFormData = reactive<{
|
|
|
- [key: string]: {
|
|
|
- state: boolean
|
|
|
- value: any
|
|
|
- }
|
|
|
+ [key: string]: any
|
|
|
}>({})
|
|
|
|
|
|
+// 表单ref
|
|
|
+const filterFormRef = ref<FormInstance>()
|
|
|
+
|
|
|
// 过滤表单字段的状态信息
|
|
|
const filterFieldsStateList = reactive<
|
|
|
Array<{
|
|
|
@@ -27,16 +64,41 @@ const filterFieldsStateList = reactive<
|
|
|
// 当前已经选中的展示的过滤字段
|
|
|
const filterFields = ref<Array<any>>([])
|
|
|
|
|
|
+// 批量操作选中的值
|
|
|
+const batchOper = ref<string>()
|
|
|
+
|
|
|
+// 批量操作的选项数组
|
|
|
+const batchOperList = reactive<
|
|
|
+ Array<{
|
|
|
+ label: string
|
|
|
+ value: string
|
|
|
+ }>
|
|
|
+>([
|
|
|
+ {
|
|
|
+ label: '删除',
|
|
|
+ value: 'delete',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '新增',
|
|
|
+ value: 'add',
|
|
|
+ },
|
|
|
+])
|
|
|
+
|
|
|
+// 表格数据
|
|
|
+const tableData = reactive<Array<any>>([])
|
|
|
+
|
|
|
+// 需要横向固定的字段
|
|
|
+const fixedFields: Array<string> = ['action', 'accountName']
|
|
|
+
|
|
|
/**
|
|
|
* @description: 初始化查询表单
|
|
|
* @return {*}
|
|
|
*/
|
|
|
const initfilterForm = () => {
|
|
|
for (let [k, v] of Object.entries(props.filtersInfo)) {
|
|
|
- filterFormData[k] = {
|
|
|
- state: true,
|
|
|
- value: v.value,
|
|
|
- }
|
|
|
+ // 表单给初始值
|
|
|
+ filterFormData[k] = v.value ? v.value : ''
|
|
|
+
|
|
|
// 初始化过滤表单字段的状态信息
|
|
|
filterFieldsStateList.push({
|
|
|
label: v.label,
|
|
|
@@ -57,100 +119,250 @@ const initFilterFields = () => {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * @description: 当查询字段改变的时候,查询表单的数据也需要同步的增删
|
|
|
- * @param {*} val
|
|
|
+ * @description: 重置查询表单
|
|
|
* @return {*}
|
|
|
*/
|
|
|
-const changeFilterFields = (val: Array<any>) => {
|
|
|
- for (let item in filterFormData) {
|
|
|
- if (!val.includes(item)) filterFormData[item].state = false
|
|
|
- }
|
|
|
+const resetFilterForm = () => {
|
|
|
+ filterFormRef.value?.resetFields()
|
|
|
console.log(filterFormData)
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * @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
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * @description: 查询表格
|
|
|
+ * @return {*}
|
|
|
+ */
|
|
|
+const queryTable = () => {
|
|
|
+ let queryParams: {
|
|
|
+ [key: string]: any
|
|
|
+ } = createQueryParams()
|
|
|
+}
|
|
|
+
|
|
|
+const xScrollBox = ref<HTMLElement>()
|
|
|
+const xScroll = ref<HTMLElement>()
|
|
|
+const tableContent = ref<HTMLElement>()
|
|
|
+// const scrollView = ref<Element>()
|
|
|
+
|
|
|
+/**
|
|
|
+ * @description: 判断表格横向是否满足滚动条的生成条件
|
|
|
+ * @param {*} boolean
|
|
|
+ * @return {*}
|
|
|
+ */
|
|
|
+const canCreateTableXScroll = (): boolean => {
|
|
|
+ if (!xScrollBox.value || !tableContent.value || !xScroll.value) {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ return true
|
|
|
+}
|
|
|
+
|
|
|
+const initScroll = () => {
|
|
|
+ let scrViw = document.querySelector('.el-scrollbar__view') // 找到表格的实际容器
|
|
|
+ let lastColumn = document.getElementsByClassName('.el-table__cell')
|
|
|
+ console.log(lastColumn)
|
|
|
+ if (!scrViw || !canCreateTableXScroll()) return
|
|
|
+
|
|
|
+ let scrViewWidth = scrViw.clientWidth // 实际容器的宽度
|
|
|
+ let tableWidth = tableContent.value!.offsetWidth // 表格宽度
|
|
|
+ let residueClientWidth = tableWidth - fixedFields.length * tableFieldLWidth // 表格余下的可视宽度
|
|
|
+ let residueActualWidth = scrViewWidth - fixedFields.length * tableFieldLWidth // 表格余下的实际宽度
|
|
|
+
|
|
|
+ let scrOffsetLeft: number =
|
|
|
+ xScrollBaseOffset + fixedFields.length * tableFieldLWidth // 滑块box的偏移量
|
|
|
+ let scrBoxWidth: number = tableWidth - fixedFields.length * tableFieldLWidth // 滑块box的宽度
|
|
|
+ let xScrollWidth: number =
|
|
|
+ (residueClientWidth / residueActualWidth) * scrBoxWidth
|
|
|
+
|
|
|
+ xScrollBox.value!.style.left = `${scrOffsetLeft}px`
|
|
|
+ xScrollBox.value!.style.width = `${scrBoxWidth}px`
|
|
|
+ xScroll.value!.style.width = `${xScrollWidth}px`
|
|
|
+ console.log(scrOffsetLeft, scrBoxWidth, xScrollWidth)
|
|
|
+}
|
|
|
+
|
|
|
initfilterForm()
|
|
|
initFilterFields()
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ axiosInstance.get(AllApi.mockData).then(res => {
|
|
|
+ console.log(res)
|
|
|
+ tableData.splice(0, tableData.length, ...res.data)
|
|
|
+ initScroll()
|
|
|
+ })
|
|
|
+})
|
|
|
</script>
|
|
|
|
|
|
<template>
|
|
|
<div class="tableContainer">
|
|
|
<div class="filterContainer">
|
|
|
<div class="filterContent">
|
|
|
- <div class="filterFields">
|
|
|
- <el-input
|
|
|
- :readonly="true"
|
|
|
- placeholder="筛选字段"
|
|
|
- prefix-icon="Filter"
|
|
|
- style="width: 112px"
|
|
|
- >
|
|
|
- <template #append>
|
|
|
+ <el-form
|
|
|
+ :model="filterFormData"
|
|
|
+ label-width="auto"
|
|
|
+ ref="filterFormRef"
|
|
|
+ :inline="true"
|
|
|
+ >
|
|
|
+ <!-- <div class="filterFields"> -->
|
|
|
+ <el-form-item>
|
|
|
+ <el-input
|
|
|
+ :readonly="true"
|
|
|
+ placeholder="筛选字段"
|
|
|
+ prefix-icon="Filter"
|
|
|
+ style="width: 200px; margin-bottom: 10px"
|
|
|
+ >
|
|
|
+ <template #append>
|
|
|
+ <el-select
|
|
|
+ v-model="filterFields"
|
|
|
+ multiple
|
|
|
+ collapse-tags
|
|
|
+ collapse-tags-tooltip
|
|
|
+ placeholder="Select"
|
|
|
+ style="width: 100px"
|
|
|
+ >
|
|
|
+ <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
|
|
|
+ class="filterItem"
|
|
|
+ v-if="
|
|
|
+ filterFields.includes(item.name) &&
|
|
|
+ item.type === TableFilterType.Search
|
|
|
+ "
|
|
|
+ v-model="filterFormData[item.name]"
|
|
|
+ style="width: 240px"
|
|
|
+ placeholder="Please Input"
|
|
|
+ :suffix-icon="Search"
|
|
|
+ />
|
|
|
+
|
|
|
<el-select
|
|
|
- v-model="filterFields"
|
|
|
- multiple
|
|
|
- collapse-tags
|
|
|
- collapse-tags-tooltip
|
|
|
+ class="filterItem"
|
|
|
+ v-if="
|
|
|
+ filterFields.includes(item.name) &&
|
|
|
+ item.type === TableFilterType.Select
|
|
|
+ "
|
|
|
+ v-model="filterFormData[item.name]"
|
|
|
placeholder="Select"
|
|
|
- style="width: 180px"
|
|
|
- @change="changeFilterFields"
|
|
|
+ style="width: 240px"
|
|
|
>
|
|
|
+ <template #label="{ label, value }">
|
|
|
+ <span>{{ item.label }}: </span>
|
|
|
+ <span style="margin-left: 10px">{{ value }}</span>
|
|
|
+ </template>
|
|
|
<el-option
|
|
|
- v-for="item in filterFieldsStateList"
|
|
|
- :key="item.value"
|
|
|
- :label="item.label"
|
|
|
- :value="item.value"
|
|
|
+ v-for="option in item.options"
|
|
|
+ :key="option.value"
|
|
|
+ :label="option.label"
|
|
|
+ :value="option.value"
|
|
|
/>
|
|
|
</el-select>
|
|
|
- </template>
|
|
|
- </el-input>
|
|
|
- </div>
|
|
|
-
|
|
|
- <template v-for="item in filtersInfo" :key="item.name">
|
|
|
- <el-input
|
|
|
- class="filterItem"
|
|
|
- v-if="
|
|
|
- filterFields.includes(item.name) &&
|
|
|
- item.type === TableFilterType.Search
|
|
|
- "
|
|
|
- v-model="filterFormData[item.name].value"
|
|
|
- style="width: 240px"
|
|
|
- placeholder="Please Input"
|
|
|
- :suffix-icon="Search"
|
|
|
- />
|
|
|
-
|
|
|
+ </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>
|
|
|
+ <div class="operationContainer">
|
|
|
+ <div class="tableOperationLeft">
|
|
|
+ <slot name="addItem">
|
|
|
+ <el-button class="addItem w120" plain :icon="Plus"
|
|
|
+ >添加账户</el-button
|
|
|
+ >
|
|
|
+ </slot>
|
|
|
+ <slot name="batchOper">
|
|
|
<el-select
|
|
|
- class="filterItem"
|
|
|
- v-if="
|
|
|
- filterFields.includes(item.name) &&
|
|
|
- item.type === TableFilterType.Select
|
|
|
- "
|
|
|
- v-model="filterFormData[item.name].value"
|
|
|
- placeholder="Select"
|
|
|
- style="width: 240px"
|
|
|
+ class="batchOper w120"
|
|
|
+ v-model="batchOper"
|
|
|
+ placeholder="批量操作"
|
|
|
>
|
|
|
- <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"
|
|
|
+ v-for="item in batchOperList"
|
|
|
+ :key="item.value"
|
|
|
+ :label="item.label"
|
|
|
+ :value="item.value"
|
|
|
/>
|
|
|
</el-select>
|
|
|
- </template>
|
|
|
+ </slot>
|
|
|
</div>
|
|
|
- <div class="filterButtonContainer">
|
|
|
- <el-button class="queryBtn" color="#197afb">查询</el-button>
|
|
|
- <el-button class="queryBtn" color="#626aef" plain>重置</el-button>
|
|
|
+ <div class="tableOperationRight">
|
|
|
+ <slot name="exportData">
|
|
|
+ <el-button class="exportData w120 ml16" plain>导出数据</el-button>
|
|
|
+ </slot>
|
|
|
+ <slot name="customIndicator">
|
|
|
+ <el-button class="customIndicator w120 ml16" plain :icon="Operation"
|
|
|
+ >自定义指标</el-button
|
|
|
+ >
|
|
|
+ </slot>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="tableContent" ref="tableContent">
|
|
|
+ <!-- height="250" -->
|
|
|
+ <el-table
|
|
|
+ v-bind="{ ...$attrs, data: tableData }"
|
|
|
+ style="width: 100%"
|
|
|
+ border
|
|
|
+ :scrollbar-always-on="true"
|
|
|
+ >
|
|
|
+ <el-table-column
|
|
|
+ :width="fixedFields.includes(k) ? tableFieldLWidth : tableFieldSWidth"
|
|
|
+ v-for="k in accountFields"
|
|
|
+ :fixed="k === 'action'"
|
|
|
+ :prop="k"
|
|
|
+ :label="k"
|
|
|
+ />
|
|
|
+ </el-table>
|
|
|
+ <div class="xScrollBox" ref="xScrollBox">
|
|
|
+ <div class="xScroll" ref="xScroll"></div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- <div class="operationContainer"></div>
|
|
|
- <div class="tableContent"></div>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<style scoped>
|
|
|
+.w120 {
|
|
|
+ width: 120px;
|
|
|
+}
|
|
|
+
|
|
|
+.ml16 {
|
|
|
+ margin-left: 16px;
|
|
|
+}
|
|
|
+
|
|
|
.tableContainer {
|
|
|
width: 100%;
|
|
|
}
|
|
|
@@ -166,25 +378,15 @@ initFilterFields()
|
|
|
.filterContent {
|
|
|
width: 90%;
|
|
|
display: flex;
|
|
|
- /* align-items: center; */
|
|
|
- /* flex-direction: column; */
|
|
|
- /* display: flex; */
|
|
|
- /* justify-content: space-between; */
|
|
|
- /* flex-wrap: wrap; */
|
|
|
}
|
|
|
|
|
|
.filterButtonContainer {
|
|
|
width: 10%;
|
|
|
height: 100%;
|
|
|
display: flex;
|
|
|
- /* flex-direction: column; */
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
}
|
|
|
|
|
|
.queryBtn {
|
|
|
- /* width: 50%; */
|
|
|
- /* padding-bottom: 8px; */
|
|
|
margin-right: 8px;
|
|
|
}
|
|
|
|
|
|
@@ -194,6 +396,51 @@ initFilterFields()
|
|
|
}
|
|
|
|
|
|
.filterFields {
|
|
|
- margin-right: 150px;
|
|
|
+ width: 112px;
|
|
|
+}
|
|
|
+
|
|
|
+.operationContainer {
|
|
|
+ position: -webkit-sticky;
|
|
|
+ position: sticky;
|
|
|
+ width: 100%;
|
|
|
+ padding: 10px 20px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ border: 1px solid #e8eaec;
|
|
|
+}
|
|
|
+
|
|
|
+.tableOperationLeft,
|
|
|
+.tableOperationRight {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: flex-start;
|
|
|
+}
|
|
|
+.batchOper {
|
|
|
+ margin: 0 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.tableContent {
|
|
|
+ width: 100%;
|
|
|
+ position: relative;
|
|
|
+ /* padding: 10px 20px; */
|
|
|
+}
|
|
|
+
|
|
|
+.xScrollBox {
|
|
|
+ width: 811px;
|
|
|
+ position: fixed;
|
|
|
+ top: unset;
|
|
|
+ bottom: 0px;
|
|
|
+ left: 626px;
|
|
|
+ right: unset;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+
|
|
|
+.xScroll {
|
|
|
+ width: 257.698px;
|
|
|
+ transform: translateX(0px);
|
|
|
+ box-sizing: border-box;
|
|
|
+ height: 10px;
|
|
|
+ background-color: #c1c1c1;
|
|
|
+ border-radius: 3px;
|
|
|
}
|
|
|
</style>
|