|
|
@@ -0,0 +1,457 @@
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, nextTick, onMounted } from 'vue'
|
|
|
+import { type VxeGridInstance, type VxeGridProps } from 'vxe-table'
|
|
|
+// import { autoWidth } from '@/utils/table'
|
|
|
+import { useRowClickStyle } from '@/hooks/rowClickStyle'
|
|
|
+import dayjs from 'dayjs'
|
|
|
+import { formatTimezone, formatNumber } from '@/utils/tools'
|
|
|
+import BookingDetailDialog from './components/BookingDetailDialog.vue'
|
|
|
+import EmailDialog from './components/EmailDialog.vue'
|
|
|
+import TipsDialog from './components/TipsDialog.vue'
|
|
|
+
|
|
|
+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)
|
|
|
+ ]
|
|
|
+ console.log('tableData.value.columns', tableData.value.columns)
|
|
|
+ const index = tableData.value.columns.findIndex((item: any) => item.title === 'Action')
|
|
|
+ if (index === -1) {
|
|
|
+ tableData.value.columns.push({
|
|
|
+ title: 'Action',
|
|
|
+ fixed: 'right',
|
|
|
+ width: 130,
|
|
|
+ slots: { default: 'action' }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ tableOriginColumnsField.value = res.data.OperationTableColumns
|
|
|
+ }
|
|
|
+ })
|
|
|
+ nextTick(() => {
|
|
|
+ // tableRef.value && autoWidth(tableData.value, tableRef.value)
|
|
|
+ tableLoadingColumn.value = false
|
|
|
+ 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
|
|
|
+
|
|
|
+ if (tableData.value.columns.length > 0) {
|
|
|
+ const index = tableData.value.columns.findIndex((item: any) => item.title === 'Action')
|
|
|
+ if (index === -1) {
|
|
|
+ tableData.value.columns.push({
|
|
|
+ title: 'Action',
|
|
|
+ fixed: 'right',
|
|
|
+ width: 130,
|
|
|
+ slots: { default: 'action' }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+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(() => {
|
|
|
+ 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(() => {
|
|
|
+ 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 },
|
|
|
+ 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']
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 实现行点击样式
|
|
|
+useRowClickStyle(tableRef)
|
|
|
+
|
|
|
+const CustomizeColumnsRef = ref()
|
|
|
+// 打开定制表格弹窗
|
|
|
+const handleCustomizeColumns = () => {
|
|
|
+ const params = {
|
|
|
+ getData: {
|
|
|
+ action: 'ocean_booking',
|
|
|
+ operate: 'setting_display'
|
|
|
+ },
|
|
|
+ saveData: {
|
|
|
+ action: 'ajax',
|
|
|
+ operate: 'save_setting_display',
|
|
|
+ model_name: 'Booking_Search'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ CustomizeColumnsRef.value.openDialog(params, -220)
|
|
|
+}
|
|
|
+// 定制表格
|
|
|
+const customizeColumns = async () => {
|
|
|
+ await getTableColumns()
|
|
|
+ // nextTick(() => {
|
|
|
+ // tableRef.value && autoWidth(tableData.value, tableRef.value)
|
|
|
+ // })
|
|
|
+}
|
|
|
+
|
|
|
+const tableLoadingColumn = ref(false)
|
|
|
+const tableLoadingTableData = ref(false)
|
|
|
+
|
|
|
+const selectedTableData = ref([])
|
|
|
+
|
|
|
+const bookingDetailDiaRef = ref()
|
|
|
+const clickViewBtn = (row: any) => {
|
|
|
+ bookingDetailDiaRef.value.openDialog(row)
|
|
|
+}
|
|
|
+
|
|
|
+const emailDialogRef = ref()
|
|
|
+const clickEmailBtn = (row: any) => {
|
|
|
+ emailDialogRef.value.openDialog(row)
|
|
|
+}
|
|
|
+
|
|
|
+const handleCreate = () => {
|
|
|
+ // Handle create new booking logic here
|
|
|
+ console.log('Create new booking')
|
|
|
+}
|
|
|
+
|
|
|
+const tipsDialogModel = ref(false)
|
|
|
+
|
|
|
+defineExpose({
|
|
|
+ SearchOperationLog
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <div
|
|
|
+ style="padding: 0px 20px"
|
|
|
+ class="table-box"
|
|
|
+ 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">Booking List</div>
|
|
|
+ <div class="right-tools-btn">
|
|
|
+ <el-button type="default" @click="handleCustomizeColumns">
|
|
|
+ <span style="margin-right: 8px" class="font_family icon-icon_column_b"></span>
|
|
|
+ Customize Columns
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <vxe-grid
|
|
|
+ ref="tableRef"
|
|
|
+ v-vloading="tableLoadingTableData || tableLoadingColumn"
|
|
|
+ :height="props.height"
|
|
|
+ :style="{ border: 'none' }"
|
|
|
+ v-bind="tableData"
|
|
|
+ >
|
|
|
+ <!-- 空数据时的插槽 -->
|
|
|
+ <template #empty v-if="!tableLoadingTableData && tableData.data.length === 0">
|
|
|
+ <div class="empty-box">
|
|
|
+ <img class="empty-img" src="./img/table-empty-img.png" alt="" />
|
|
|
+ <p>You haven't created any destination delivery bookings yet.</p>
|
|
|
+ <p>Book truck or rail delivery for your shipments to save time and</p>
|
|
|
+ <p>ensure smooth last-mile delivery.</p>
|
|
|
+ <el-button
|
|
|
+ style="height: 40px"
|
|
|
+ class="el-button--main el-button--pain-theme"
|
|
|
+ @click="handleCreate"
|
|
|
+ >
|
|
|
+ <span style="margin-right: 4px" class="font_family icon-icon_add_b"></span>
|
|
|
+ <span style="font-weight: 400">Create New Booking</span>
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <!-- action操作的插槽 -->
|
|
|
+ <template #action="{ row }">
|
|
|
+ <!-- view -->
|
|
|
+ <el-button
|
|
|
+ @click="clickViewBtn(row)"
|
|
|
+ class="action-btn el-button--blue"
|
|
|
+ style="height: 24px; width: 24px"
|
|
|
+ >
|
|
|
+ <span style="color: 'red'" class="font_family icon-icon_view_b"> </span>
|
|
|
+ </el-button>
|
|
|
+ <!-- email -->
|
|
|
+ <el-button
|
|
|
+ @click="clickEmailBtn(row)"
|
|
|
+ class="action-btn el-button--blue"
|
|
|
+ style="height: 24px; width: 24px"
|
|
|
+ >
|
|
|
+ <span style="color: 'red'" class="font_family icon-icon_email_b"> </span>
|
|
|
+ </el-button>
|
|
|
+ <!-- edit -->
|
|
|
+ <el-button
|
|
|
+ @click="tipsDialogModel = true"
|
|
|
+ class="action-btn el-button--blue"
|
|
|
+ style="height: 24px; width: 24px"
|
|
|
+ >
|
|
|
+ <span style="color: 'red'" class="font_family icon-icon_edit_b"> </span>
|
|
|
+ </el-button>
|
|
|
+ <!-- confirm -->
|
|
|
+ <el-button class="action-btn el-button--blue" style="height: 24px; width: 24px">
|
|
|
+ <span style="color: 'red'" class="font_family icon-icon_confirm_b"> </span>
|
|
|
+ </el-button>
|
|
|
+ <!-- reject -->
|
|
|
+ <el-button class="action-btn el-button--blue" style="height: 24px; width: 24px">
|
|
|
+ <span style="color: 'red'" class="font_family icon-icon_reject_b"> </span>
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </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>
|
|
|
+
|
|
|
+ <CustomizeColumns @customize="customizeColumns" ref="CustomizeColumnsRef" />
|
|
|
+ <BookingDetailDialog ref="bookingDetailDiaRef" />
|
|
|
+ <EmailDialog ref="emailDialogRef" />
|
|
|
+ <TipsDialog ref="tipsDialogRef" v-model="tipsDialogModel" type="reject" booking-no="123" />
|
|
|
+ </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;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.empty-box {
|
|
|
+ .empty-img {
|
|
|
+ width: 100px;
|
|
|
+ height: 100px;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ }
|
|
|
+ p {
|
|
|
+ color: var(--color-neutral-2);
|
|
|
+ line-height: 21px;
|
|
|
+ }
|
|
|
+ .el-button {
|
|
|
+ margin-top: 8px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.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;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.action-btn {
|
|
|
+ height: 24px;
|
|
|
+ width: 24px;
|
|
|
+ &:hover {
|
|
|
+ background-color: var(--color-btn-action-bg-hover);
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|