|
|
@@ -0,0 +1,436 @@
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, nextTick, onMounted } from 'vue'
|
|
|
+import { type VxeGridInstance, type VxeGridProps } from 'vxe-table'
|
|
|
+import DownloadDialog from './components/DownloadDialog.vue'
|
|
|
+// import { autoWidth } from '@/utils/table'
|
|
|
+import { useRowClickStyle } from '@/hooks/rowClickStyle'
|
|
|
+import dayjs from 'dayjs'
|
|
|
+import { formatTimezone, formatNumber } from '@/utils/tools'
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ height: {
|
|
|
+ type: Number,
|
|
|
+ default: 440
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const tableOriginColumnsField = ref()
|
|
|
+const handleColumns = (columns: any, status?: string) => {
|
|
|
+ const newColumns = columns.map((item: any) => {
|
|
|
+ let curColumn: any = {
|
|
|
+ title: item.title,
|
|
|
+ field: item.field,
|
|
|
+ sortable: true,
|
|
|
+ minWidth: 120
|
|
|
+ }
|
|
|
+ // 设置插槽
|
|
|
+ if (item.type === 'status' && status !== 'all') {
|
|
|
+ curColumn = {
|
|
|
+ ...curColumn,
|
|
|
+ slots: { default: 'status' }
|
|
|
+ }
|
|
|
+ } else if (item.type === 'link' && status !== 'all') {
|
|
|
+ curColumn = {
|
|
|
+ ...curColumn,
|
|
|
+ slots: { default: 'link' }
|
|
|
+ }
|
|
|
+ } else if (item.type === 'mode' && status !== 'all') {
|
|
|
+ curColumn = {
|
|
|
+ ...curColumn,
|
|
|
+ slots: { default: 'mode' },
|
|
|
+ formatter: ({ cellValue }: any) => {
|
|
|
+ return cellValue
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 格式化
|
|
|
+ if (item.formatter === 'date' || item.formatter === 'dateTime') {
|
|
|
+ curColumn = {
|
|
|
+ ...curColumn,
|
|
|
+ formatter: ({ cellValue }: any) => formatTimezone(cellValue)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return curColumn
|
|
|
+ })
|
|
|
+ return newColumns
|
|
|
+}
|
|
|
+
|
|
|
+// 获取表格列
|
|
|
+const getTableColumns = async () => {
|
|
|
+ tableLoadingColumn.value = true
|
|
|
+ await $api.getOperationTableColumns().then((res: any) => {
|
|
|
+ if (res.code === 200) {
|
|
|
+ tableData.value.columns = [
|
|
|
+ { type: 'checkbox', width: 50, fixed: 'left' },
|
|
|
+ ...handleColumns(res.data.OperationTableColumns)
|
|
|
+ ]
|
|
|
+ tableOriginColumnsField.value = res.data.OperationTableColumns
|
|
|
+ }
|
|
|
+ })
|
|
|
+ nextTick(() => {
|
|
|
+ // tableRef.value && autoWidth(tableData.value, tableRef.value)
|
|
|
+ tableLoadingColumn.value = false
|
|
|
+ selectedNumber.value = 0
|
|
|
+ selectedTableData.value = []
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+const pageInfo = ref({ pageNo: 1, pageSize: 20, total: 0 })
|
|
|
+const tempSearch = ref()
|
|
|
+// 获得表格数据后赋值
|
|
|
+const assignTableData = (data: any) => {
|
|
|
+ tableData.value.data = data.searchData || []
|
|
|
+ pageInfo.value.total = Number(data.rc) || 0
|
|
|
+ tempSearch.value = data.tmp_search
|
|
|
+ // 拥有所有字段的表格
|
|
|
+ setTimeout(() => {
|
|
|
+ allTable.value.columns = handleColumns(tableData.value.columns, 'all')
|
|
|
+ allTable.value.data = data.searchData || []
|
|
|
+ // 为了让导出的表格列宽度自适应
|
|
|
+ nextTick(() => {
|
|
|
+ // allTableRef.value && autoWidth(allTable.value, allTableRef.value)
|
|
|
+ })
|
|
|
+ }, 1000)
|
|
|
+}
|
|
|
+
|
|
|
+let searchdata: any = {}
|
|
|
+// 获取表格数据
|
|
|
+const getTableData = async (isPageChange?: boolean) => {
|
|
|
+ const rc = isPageChange ? pageInfo.value.total : -1
|
|
|
+ tableLoadingTableData.value = true
|
|
|
+ await $api
|
|
|
+ .SearchOperationLog({
|
|
|
+ cp: pageInfo.value.pageNo,
|
|
|
+ ps: pageInfo.value.pageSize,
|
|
|
+ rc,
|
|
|
+ ...searchdata
|
|
|
+ })
|
|
|
+ .then((res: any) => {
|
|
|
+ if (res.code === 200) {
|
|
|
+ assignTableData(res.data)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .finally(() => {
|
|
|
+ selectedNumber.value = 0
|
|
|
+ selectedTableData.value = []
|
|
|
+ nextTick(() => {
|
|
|
+ // tableRef.value && autoWidth(tableData.value, tableRef.value)
|
|
|
+ tableLoadingTableData.value = false
|
|
|
+ })
|
|
|
+ })
|
|
|
+}
|
|
|
+const SearchOperationLog = (val: any) => {
|
|
|
+ searchdata = val
|
|
|
+ tableLoadingTableData.value = true
|
|
|
+ $api
|
|
|
+ .SearchOperationLog({
|
|
|
+ cp: pageInfo.value.pageNo,
|
|
|
+ ps: pageInfo.value.pageSize,
|
|
|
+ rc: -1,
|
|
|
+ ...val
|
|
|
+ })
|
|
|
+ .then((res: any) => {
|
|
|
+ if (res.code === 200) {
|
|
|
+ assignTableData(res.data)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .finally(() => {
|
|
|
+ selectedNumber.value = 0
|
|
|
+ selectedTableData.value = []
|
|
|
+ nextTick(() => {
|
|
|
+ // tableRef.value && autoWidth(tableData.value, tableRef.value)
|
|
|
+ tableLoadingTableData.value = false
|
|
|
+ })
|
|
|
+ })
|
|
|
+}
|
|
|
+onMounted(() => {
|
|
|
+ Promise.all([getTableColumns(), getTableData(false)]).finally(() => {
|
|
|
+ nextTick(() => {
|
|
|
+ // tableRef.value && autoWidth(tableData.value, tableRef.value)
|
|
|
+ })
|
|
|
+ })
|
|
|
+})
|
|
|
+
|
|
|
+const tableRef = ref<VxeGridInstance>()
|
|
|
+const tableData = ref<VxeGridProps<any>>({
|
|
|
+ border: true,
|
|
|
+ round: true,
|
|
|
+ columns: [],
|
|
|
+ data: [],
|
|
|
+ scrollY: { enabled: true, oSize: 20, gt: 30 },
|
|
|
+ stripe: true,
|
|
|
+ emptyText: ' ',
|
|
|
+ showHeaderOverflow: true,
|
|
|
+ showOverflow: true,
|
|
|
+ headerRowStyle: {
|
|
|
+ backgroundColor: 'var(--color-table-header-bg)'
|
|
|
+ },
|
|
|
+ sortConfig: {
|
|
|
+ sortMethod: (params) => {
|
|
|
+ const { data, sortList } = params
|
|
|
+
|
|
|
+ // 如果没有排序条件,直接返回原数据
|
|
|
+ if (sortList.length === 0) return data
|
|
|
+
|
|
|
+ // 对数据进行多重排序
|
|
|
+ const sortedData = [...data].sort((a, b) => {
|
|
|
+ for (const { field, order } of sortList) {
|
|
|
+ const curColumn = tableOriginColumnsField.value.find((item: any) => item.field === field)
|
|
|
+ if (!curColumn) continue
|
|
|
+
|
|
|
+ const typeName = curColumn.type
|
|
|
+ const aValue = a[field]
|
|
|
+ const bValue = b[field]
|
|
|
+
|
|
|
+ const compareResult = (aValue: any, bValue: any) => {
|
|
|
+ // 如果 aValue 或 bValue 是 null 或 undefined,优先处理这些值
|
|
|
+ if (aValue == null && bValue == null) {
|
|
|
+ return 0 // 如果两个值都为 null 或 undefined,视为相等
|
|
|
+ } else if (aValue == null) {
|
|
|
+ return -1 // 如果 aValue 是 null,bValue 不是,则将 aValue 视为较小值
|
|
|
+ } else if (bValue == null) {
|
|
|
+ return 1 // 如果 bValue 是 null,aValue 不是,则将 bValue 视为较小值
|
|
|
+ }
|
|
|
+
|
|
|
+ if (typeName === 'datetime' || typeName === 'date' || typeName === 'time') {
|
|
|
+ return dayjs(aValue).unix() - dayjs(bValue).unix()
|
|
|
+ } else if (isNaN(Number(aValue)) || isNaN(Number(bValue))) {
|
|
|
+ return aValue.localeCompare(bValue)
|
|
|
+ } else {
|
|
|
+ return Number(aValue) - Number(bValue)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const result = compareResult(aValue, bValue)
|
|
|
+ if (result !== 0) {
|
|
|
+ return order === 'asc' ? result : -result
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0 // 如果所有字段都相等
|
|
|
+ })
|
|
|
+
|
|
|
+ return sortedData
|
|
|
+ }
|
|
|
+ },
|
|
|
+ columnConfig: { resizable: true, useKey: true },
|
|
|
+ rowConfig: { isHover: true },
|
|
|
+ exportConfig: {
|
|
|
+ types: ['csv', 'html', 'txt', 'xlsx'],
|
|
|
+ modes: ['current', 'selected', 'all']
|
|
|
+ }
|
|
|
+})
|
|
|
+const allTableRef = ref<VxeGridInstance>()
|
|
|
+const allTable = ref<VxeGridProps<any>>({
|
|
|
+ columns: [],
|
|
|
+ data: [],
|
|
|
+ showHeaderOverflow: true,
|
|
|
+ showOverflow: true,
|
|
|
+ scrollY: { enabled: true, oSize: 5, gt: 2 },
|
|
|
+ scrollX: { enabled: true, gt: 2 },
|
|
|
+ exportConfig: {
|
|
|
+ types: ['csv', 'html', 'txt', 'xlsx'],
|
|
|
+ modes: ['current', 'selected', 'all']
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 实现行点击样式
|
|
|
+useRowClickStyle(tableRef)
|
|
|
+
|
|
|
+const downloadDialogRef = ref()
|
|
|
+const handleDownload = () => {
|
|
|
+ const curSelectedColumns: string[] = []
|
|
|
+ tableRef.value?.columns?.forEach((item: any) => {
|
|
|
+ if (item.field) {
|
|
|
+ curSelectedColumns.push(item.title)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ downloadDialogRef.value.openDialog(
|
|
|
+ curSelectedColumns,
|
|
|
+ selectedNumber.value || pageInfo.value.total
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+const exportLoading = ref(false)
|
|
|
+// 获取导出表格数据
|
|
|
+const getExportTableData = (status: number) => {
|
|
|
+ // 如果有选中表格行数据,那么只到处选中的数据
|
|
|
+ if (selectedNumber.value > 0) {
|
|
|
+ exportTable(status)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ exportLoading.value = true
|
|
|
+ const buildColumnString = (columns: any[]): string => {
|
|
|
+ return columns
|
|
|
+ .filter((item) => item.field)
|
|
|
+ .map((item) => item.title)
|
|
|
+ .join(',')
|
|
|
+ }
|
|
|
+
|
|
|
+ let column = ''
|
|
|
+ if (status === 1) {
|
|
|
+ column = buildColumnString(tableData.value.columns)
|
|
|
+ } else {
|
|
|
+ column = buildColumnString(allTable.value.columns)
|
|
|
+ }
|
|
|
+ $api
|
|
|
+ .OperationLogDownload({
|
|
|
+ selected_fields: column,
|
|
|
+ tmp_search: tempSearch.value
|
|
|
+ })
|
|
|
+ .then((res: any) => {
|
|
|
+ if (res.code === 200) {
|
|
|
+ allTable.value.data = res.data.Data || []
|
|
|
+ nextTick(() => {
|
|
|
+ exportLoading.value = false
|
|
|
+ // 导出数据
|
|
|
+ exportTable(status)
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .finally(() => {
|
|
|
+ exportLoading.value = false
|
|
|
+ })
|
|
|
+}
|
|
|
+// 导出表格 status: 1 导出当前表格 2 导出所有字段表格
|
|
|
+const exportTable = (status: number) => {
|
|
|
+ const exportConfig: any = {
|
|
|
+ type: 'xlsx',
|
|
|
+ message: false,
|
|
|
+ filename: `Chat Log_${dayjs().format('YYYYMMDDHH[h]mm[m]ss[s]')}`
|
|
|
+ }
|
|
|
+ if (status === 1) {
|
|
|
+ exportConfig.columnFilterMethod = ({ column }: any) => {
|
|
|
+ const index = tableData.value.columns.findIndex((item: any) => item.field === column.field)
|
|
|
+ // 排除复选框列
|
|
|
+ return column.field && index !== -1
|
|
|
+ }
|
|
|
+ exportConfig.columns = tableData.value.columns
|
|
|
+ }
|
|
|
+ if (selectedNumber.value > 0) {
|
|
|
+ exportConfig.dataFilterMethod = ({ row }: any) => {
|
|
|
+ const index = selectedTableData.value.findIndex(
|
|
|
+ (item: any) => item._X_ROW_KEY === row._X_ROW_KEY
|
|
|
+ )
|
|
|
+ return index !== -1
|
|
|
+ }
|
|
|
+ }
|
|
|
+ allTableRef.value?.exportData(exportConfig)
|
|
|
+}
|
|
|
+
|
|
|
+const tableLoadingColumn = ref(false)
|
|
|
+const tableLoadingTableData = ref(false)
|
|
|
+
|
|
|
+const selectedNumber = ref(0)
|
|
|
+const selectedTableData = ref([])
|
|
|
+// 复选框选中事件
|
|
|
+const handleCheckboxChange = ({ records }: any) => {
|
|
|
+ selectedNumber.value = records.length
|
|
|
+ selectedTableData.value = records
|
|
|
+}
|
|
|
+const handleCheckAllChange = ({ records }: any) => {
|
|
|
+ selectedNumber.value = records.length
|
|
|
+ selectedTableData.value = records
|
|
|
+}
|
|
|
+defineExpose({
|
|
|
+ SearchOperationLog
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <div
|
|
|
+ style="padding: 0px 20px"
|
|
|
+ class="table-box"
|
|
|
+ v-loading.fullscreen.lock="exportLoading"
|
|
|
+ element-loading-text="Loading..."
|
|
|
+ element-loading-custom-class="element-loading"
|
|
|
+ element-loading-background="rgb(43, 47, 54, 0.7)"
|
|
|
+ >
|
|
|
+ <div class="table-tools">
|
|
|
+ <div class="left-total-records">{{ selectedNumber }} Selected</div>
|
|
|
+ <div class="right-tools-btn">
|
|
|
+ <el-button class="el-button--main el-button--pain-theme" @click="handleDownload">
|
|
|
+ <span style="margin-right: 8px" class="font_family icon-icon_download_b"></span>
|
|
|
+ Download
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <vxe-grid
|
|
|
+ ref="tableRef"
|
|
|
+ v-vloading="tableLoadingTableData || tableLoadingColumn"
|
|
|
+ :height="props.height"
|
|
|
+ :style="{ border: 'none' }"
|
|
|
+ v-bind="tableData"
|
|
|
+ @checkbox-change="handleCheckboxChange"
|
|
|
+ @checkbox-all="handleCheckAllChange"
|
|
|
+ >
|
|
|
+ <!-- 空数据时的插槽 -->
|
|
|
+ <template #empty v-if="!tableLoadingTableData && tableData.data.length === 0">
|
|
|
+ <VEmpty></VEmpty>
|
|
|
+ </template>
|
|
|
+ </vxe-grid>
|
|
|
+ <vxe-grid :height="10" ref="allTableRef" class="all-table" v-bind="allTable"> </vxe-grid>
|
|
|
+ <div class="bottom-pagination">
|
|
|
+ <div class="left-total-records">Total {{ formatNumber(pageInfo.total) }}</div>
|
|
|
+ <div class="right-pagination">
|
|
|
+ <el-pagination
|
|
|
+ v-model:current-page="pageInfo.pageNo"
|
|
|
+ v-model:page-size="pageInfo.pageSize"
|
|
|
+ :page-sizes="[20, 50, 100, 150]"
|
|
|
+ :pager-count="3"
|
|
|
+ background
|
|
|
+ layout="sizes, prev, pager, next"
|
|
|
+ :total="pageInfo.total"
|
|
|
+ @size-change="getTableData(true)"
|
|
|
+ @current-change="getTableData(true)"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <DownloadDialog @export="getExportTableData" ref="downloadDialogRef" />
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.table-tools {
|
|
|
+ position: relative;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ height: 48px;
|
|
|
+ padding: 8px 0;
|
|
|
+
|
|
|
+ .left-total-records {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 700;
|
|
|
+ line-height: 32px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.bottom-pagination {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ height: 40px;
|
|
|
+ margin-top: -1px;
|
|
|
+ padding: 4px 8px;
|
|
|
+ border: 1px solid var(--color-border);
|
|
|
+ border-radius: 0 0 12px 12px;
|
|
|
+
|
|
|
+ .left-total-records {
|
|
|
+ line-height: 32px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .right-pagination {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+}
|
|
|
+.table-box {
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ .all-table {
|
|
|
+ position: absolute;
|
|
|
+ top: -100000px;
|
|
|
+ width: 20px;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|