瀏覽代碼

feat:添加report页面

AmandaG 1 月之前
父節點
當前提交
641cd99b03

+ 25 - 1
src/router/index.ts

@@ -21,6 +21,27 @@ const router = createRouter({
           name: 'Booking',
           component: () => import('../views/Booking')
         },
+        {
+          path: '/report',
+          name: 'Report Management',
+          component: () => import('../views/Report')
+        },
+        {
+          path: '/report/detail',
+          name: 'ReportDetail',
+          component: () => import('../views/Report/src/components/ReportDetail'),
+          meta: {
+            activeMenu: '/report'
+          }
+        },
+        {
+          path: '/report/schedule',
+          name: 'ReportSchedule',
+          component: () => import('../views/Report/src/components/ReportSchedule'),
+          meta: {
+            activeMenu: '/report'
+          }
+        },
         {
           path: '/booking/detail',
           name: 'Booking Detail',
@@ -161,7 +182,10 @@ const router = createRouter({
         {
           path: '/destination-delivery/ConfiguRations/CreateNewRule',
           name: 'Destination Create New Rule',
-          component: () => import('../views/DestinationDelivery/src/components/ConfiguRations/src/components/CreateNewRule.vue')
+          component: () =>
+            import(
+              '../views/DestinationDelivery/src/components/ConfiguRations/src/components/CreateNewRule.vue'
+            )
         },
         {
           path: '/destination-delivery/modify-booking',

+ 31 - 3
src/stores/modules/breadCrumb.ts

@@ -19,6 +19,7 @@ const whiteList = [
   'Configurations',
   'Create New Booking',
   'Destination Create New Rule',
+  'Report Management'
 ]
 
 export const useBreadCrumb = defineStore('breadCrumb', {
@@ -99,7 +100,7 @@ export const useBreadCrumb = defineStore('breadCrumb', {
         ]
       } else if (toRoute.name === 'Destination Create New Rule') {
         let label = ''
-        if(toRoute.query.a != undefined) {
+        if (toRoute.query.a != undefined) {
           label = 'Modify Rule'
         } else {
           label = 'Create New Rule'
@@ -121,8 +122,9 @@ export const useBreadCrumb = defineStore('breadCrumb', {
             query: toRoute.query
           }
         ]
-      } else if (toRoute.name === 'Create New Booking') {let label = ''
-        if(toRoute.query.a != undefined) {
+      } else if (toRoute.name === 'Create New Booking') {
+        let label = ''
+        if (toRoute.query.a != undefined) {
           label = 'Modify Booking'
         } else {
           label = 'Create New Booking'
@@ -139,6 +141,32 @@ export const useBreadCrumb = defineStore('breadCrumb', {
             query: toRoute.query
           }
         ]
+      } else if (toRoute.name === 'ReportDetail') {
+        this.routeList = [
+          {
+            label: 'Report Management',
+            path: '/report',
+            query: ''
+          },
+          {
+            label: 'Details',
+            path: '/report/detail',
+            query: toRoute.query
+          }
+        ]
+      } else if (toRoute.name === 'ReportSchedule') {
+        this.routeList = [
+          {
+            label: 'Report Management',
+            path: '/report',
+            query: ''
+          },
+          {
+            label: 'Schedule Condiguration',
+            path: '/report/detail',
+            query: toRoute.query
+          }
+        ]
       } else if (toRoute.name && whiteList.includes(toRoute.name)) {
         this.routeList.push({
           label: toRoute?.meta?.breadName || toRoute.name,

+ 7 - 0
src/styles/elementui.scss

@@ -399,6 +399,7 @@ div.el-drawer {
 div .el-input__inner {
   color: var(--color-neutral-1);
   font-size: var(--font-size-3);
+  height: 32px;
 }
 .el-input__inner::placeholder {
   color: var(--color-neutral-3);
@@ -909,4 +910,10 @@ div .suggestion-item:hover {
   &:hover {
     background-color: var(--color-arrow-hoverL);
   }
+}
+div .manage-footer-class {
+  box-shadow: 0 -2px 6px rgba(0, 0, 0, 0.15) !important;
+}
+div .schedule-popper {
+  max-width: 320px;
 }

+ 3 - 0
src/styles/theme-g.scss

@@ -96,4 +96,7 @@
   --color-prompt-diaolog-bg: #3A4149;
   --color-prompt-disabled-bg: rgba(244, 244, 244, 0.20);
   --color-prompt-disabled-border: rgba(101, 111, 125, 0.30);
+
+  // report
+  --color-schedule-bg: #343A43;
 }

+ 3 - 0
src/styles/theme.scss

@@ -354,6 +354,9 @@
   --color-ant-picker-th: #b5b9bf;
 
   --color-json-item-hover: #e6f7ff;
+
+  // report
+  --color-schedule-bg: #F6F8FA;
 }
 
 :root.dark {

+ 4 - 1
src/styles/vxeTable.scss

@@ -48,9 +48,12 @@
 .vxe-table--render-default.vxe-editable .vxe-body--column {
   height: 40px !important;
 }
-
+div .vxe-grid .vxe-grid--table-container {
+  height: 100%;
+}
 div.vxe-table--render-default {
   color: var(--color-neutral-1);
+  height: 100%;
 }
 
 // 需要在表格配置中加上 round: true

+ 1 - 0
src/views/Booking/src/components/BookingTable/src/BookingTable.vue

@@ -376,6 +376,7 @@ const exportTable = (status: number) => {
       return index !== -1
     }
   }
+    console.log(exportConfig)
   allTableRef.value?.exportData(exportConfig)
   // 将所有表格的值赋值为当前表格的值
   allTable.value.data = curTableData.value

+ 14 - 1
src/views/Dashboard/src/DashboardView.vue

@@ -1545,7 +1545,10 @@ function handleImageClick(event) {
 }
 .filters_left {
   border-radius: var(--border-radius-6);
-  flex: 1 40%;
+  width: calc(50% - 4px);
+  flex: 0 0 calc(50% - 4px);
+  min-width: 0;
+  box-sizing: border-box;
 }
 .KPI_title {
   border-bottom: 1px solid var(--color-border);
@@ -1576,6 +1579,16 @@ function handleImageClick(event) {
 .echarts {
   padding: 0 22px;
   background-color: var(--color-mode);
+  :deep(> div) {
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: space-between;
+    gap: 8px;
+    width: 100%;
+    > * {
+      box-sizing: border-box;
+    }
+  }
 }
 .kpi {
   width: 50%;

+ 1 - 1
src/views/DestinationDelivery/src/components/CreateNewBooking/src/CreateNewbooking.vue

@@ -908,8 +908,8 @@ onMounted(() => {
             :showToday="false"
             style="width: 240px"
             :format="userStore.dateFormat"
-            placeholder="Please Select Date"
             valueFormat="YYYY.MM.DD"
+            placeholder="Please Select Date"
           >
             <template #renderExtraFooter>
               <div class="recommended">

+ 78 - 72
src/views/Layout/src/components/Menu/MenuView.vue

@@ -20,80 +20,86 @@ watch(
 const getMenuList = () => {
   $api.getMenuList().then((res) => {
     if (res.code === 200) {
-      menuList.value = res.data
+      // menuList.value = res.data
     }
   })
-  // menuList.value = [
-  //   {
-  //     index: '1',
-  //     label: 'Dashboard',
-  //     icon: 'icon_data_fill_b',
-  //     path: '/dashboard'
-  //   },
-  //   {
-  //     index: '2',
-  //     label: 'Booking',
-  //     icon: 'icon_booking__fill_b',
-  //     // path: '/booking',
-  //     type: 'list',
-  //     children: [
-  //       {
-  //         index: '2-1',
-  //         label: 'Booking Management',
-  //         path: '/booking'
-  //       },
-  //       {
-  //         index: '2-2',
-  //         label: 'Destination Delivery',
-  //         path: '/destination-delivery'
-  //       }
-  //     ]
-  //   },
-  //   {
-  //     index: '3',
-  //     label: 'Tracking',
-  //     icon: 'icon_tracking__fill_b',
-  //     path: '/tracking'
-  //   },
-  //   {
-  //     index: '4',
-  //     label: 'System Management',
-  //     icon: 'icon_system__management_fill_b',
-  //     type: 'list',
-  //     children: [
-  //       {
-  //         index: '4-1',
-  //         label: 'System Message',
-  //         path: '/system-message'
-  //       },
-  //       {
-  //         index: '4-2',
-  //         label: 'System Settings',
-  //         path: '/SystemSettings'
-  //       },
-  //       {
-  //         index: '4-3',
-  //         label: 'Chat Log',
-  //         path: '/chat-log'
-  //       },
-  //       {
-  //         index: '4-4',
-  //         label: 'AI API Log',
-  //         path: '/ai-api-log'
-  //       },
-  //       {
-  //         index: '4-5',
-  //         label: 'Operation Log',
-  //         path: '/Operationlog'
-  //       },
-  //       {
-  //         index: '4-6',
-  //         label: 'Prompt Configuration',
-  //         path: '/PromptConfiguration'
-  //       }
-  //     ]
-  //   }
-  // ]
+  menuList.value = [
+    {
+      index: '1',
+      label: 'Dashboard',
+      icon: 'icon_data_fill_b',
+      path: '/dashboard'
+    },
+    {
+      index: '2',
+      label: 'Booking',
+      icon: 'icon_booking__fill_b',
+      // path: '/booking',
+      type: 'list',
+      children: [
+        {
+          index: '2-1',
+          label: 'Booking Management',
+          path: '/booking'
+        },
+        {
+          index: '2-2',
+          label: 'Destination Delivery',
+          path: '/destination-delivery'
+        }
+      ]
+    },
+    {
+      index: '3',
+      label: 'Tracking',
+      icon: 'icon_tracking__fill_b',
+      path: '/tracking'
+    },
+    {
+      index: '4',
+      label: 'Report',
+      icon: 'icon_edoc_b',
+      path: '/report'
+    },
+    {
+      index: '5',
+      label: 'System Management',
+      icon: 'icon_system__management_fill_b',
+      type: 'list',
+      children: [
+        {
+          index: '5-1',
+          label: 'System Message',
+          path: '/system-message'
+        },
+        {
+          index: '5-2',
+          label: 'System Settings',
+          path: '/SystemSettings'
+        },
+        {
+          index: '5-3',
+          label: 'Chat Log',
+          path: '/chat-log'
+        },
+        {
+          index: '5-4',
+          label: 'AI API Log',
+          path: '/ai-api-log'
+        },
+        {
+          index: '5-5',
+          label: 'Operation Log',
+          path: '/Operationlog'
+        },
+        {
+          index: '5-6',
+          label: 'Prompt Configuration',
+          path: '/PromptConfiguration'
+        }
+      ]
+    }
+  ]
 }
 getMenuList()
 //监听窗口大小

+ 1 - 0
src/views/Report/index.ts

@@ -0,0 +1 @@
+export { default } from './src/ReportView.vue'

+ 255 - 0
src/views/Report/src/ReportView.vue

@@ -0,0 +1,255 @@
+<script setup lang="ts">
+import { ref, onMounted } from 'vue'
+import { type VxeGridInstance, type VxeGridProps } from 'vxe-table'
+import { useRowClickStyle } from '@/hooks/rowClickStyle'
+import { formatNumber } from '@/utils/tools'
+import { useRouter } from 'vue-router'
+import { useCalculatingHeight } from '@/hooks/calculatingHeight'
+
+const router = useRouter()
+const ReportSearch = ref('')
+// search report name
+const SearchInput = () => {
+  console.log(ReportSearch.value)
+}
+
+const filterRef: Ref<HTMLElement | null> = ref(null)
+const containerHeight = useCalculatingHeight(document.documentElement, 266, [filterRef])
+
+const columnstest = ref([
+  {
+    field: 'report_name',
+    title: 'Report Name',
+    type: 'normal',
+    formatter: ''
+  },
+  {
+    field: 'description',
+    title: 'Description',
+    type: 'normal',
+    formatter: ''
+  }
+])
+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)'
+  },
+  columnConfig: { resizable: true, useKey: true },
+  rowConfig: { isHover: true }
+})
+
+const tableRef = ref<VxeGridInstance | null>(null)
+const pageInfo = ref({ pageNo: 1, pageSize: 50, total: 0 })
+
+const handleColumns = (columns: any) => {
+  const newColumns = columns.map((item: any) => {
+    let curColumn: any = {
+      title: item.title,
+      field: item.field,
+      width: item.width
+    }
+    return curColumn
+  })
+  return newColumns
+}
+// 获取表格列
+const getTableColumns = async () => {
+  tableData.value.columns = handleColumns(columnstest.value)
+  tableData.value.columns?.push({
+    title: 'Action',
+    fixed: 'left',
+    width: 178,
+    slots: { default: 'action' }
+  })
+}
+// 获取表格数据
+const getTableData = (isPageChange?: boolean) => {
+  $api
+    .getConfigurationList({
+      cp: pageInfo.value.pageNo,
+      ps: pageInfo.value.pageSize,
+      rc: isPageChange ? pageInfo.value.total : -1
+    })
+    .then((res: any) => {
+      if (res.code === 200) {
+        pageInfo.value.total = Number(res.data.rc)
+        pageInfo.value.pageNo = res.data.cp
+        pageInfo.value.pageSize = res.data.ps
+        tableData.value.data = res.data.searchData
+      }
+    })
+}
+
+// 实现行点击样式
+useRowClickStyle(tableRef)
+
+const handleClickDetail = (row: any) => {
+  router.push({
+    name: 'ReportDetail',
+    params: {
+      id: row.id
+    }
+  })
+}
+const handleClickSchedule = (row: any) => {
+  router.push({
+    name: 'ReportSchedule',
+    params: {
+      id: row.id
+    }
+  })
+}
+
+onMounted(() => {
+  getTableColumns()
+  getTableData()
+})
+</script>
+
+<template>
+  <div class="Title">
+    <span>Report</span>
+  </div>
+  <div class="report_search">
+    <div class="search">
+      <el-input
+        placeholder="Search Report Name"
+        v-model="ReportSearch"
+        class="log_input"
+        @keyup.enter="SearchInput"
+      >
+        <template #prefix>
+          <span class="iconfont_icon">
+            <svg class="iconfont icon_search" aria-hidden="true">
+              <use xlink:href="#icon-icon_search_b"></use>
+            </svg>
+          </span>
+        </template>
+      </el-input>
+    </div>
+    <el-button class="el-button--dark" style="margin-left: 8px" @click="SearchInput"
+      >Search</el-button
+    >
+  </div>
+  <div class="SettingTable">
+    <vxe-grid
+      ref="tableRef"
+      :style="{ border: 'none'}"
+      v-bind="tableData"
+      :height="containerHeight"
+    >
+      <!-- 空数据时的插槽 -->
+      <template #empty>
+        <img src="./images/default_no_data@2x.png" />
+        <div class="empty-text">No data</div>
+      </template>
+      <template #action="{ row }">
+        <el-button
+          class="el-button--blue"
+          @click="handleClickDetail(row)"
+          style="height: 24px"
+        >
+          <span class="font_family icon-icon_edoc_b"></span>
+          <span style="font-size: 12px">Details</span>
+        </el-button>
+        <el-button
+          class="el-button--blue"
+          style="height: 24px"
+          @click="handleClickSchedule(row)"
+        >
+          <span class="font_family icon-icon_time_b"></span>
+          <span style="font-size: 12px">Schedule</span>
+        </el-button>
+      </template>
+    </vxe-grid>
+  </div>
+  <div class="pagination">
+    <span>Total {{ formatNumber(pageInfo.total) }}</span>
+    <el-pagination
+      v-model:current-page="pageInfo.pageNo"
+      v-model:page-size="pageInfo.pageSize"
+      :page-sizes="[50, 100, 200, 300, 400]"
+      :pagerCount="5"
+      background
+      layout="sizes, prev, pager, next"
+      :total="pageInfo.total"
+      @size-change="getTableData(true)"
+      @current-change="getTableData(true)"
+    />
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.Title {
+  display: flex;
+  height: 68px;
+  border-bottom: 1px solid var(--color-border);
+  font-size: var(--font-size-6);
+  font-weight: 700;
+  padding: 0 24px;
+  align-items: center;
+}
+.report_search {
+  border: 1px solid var(--color-border);
+  border-width: 0 0 1px 0;
+  padding: 8px 24px;
+  display: flex;
+}
+.search {
+  width: 400px;
+  height: 32px;
+}
+.SettingTable {
+  margin: 8px 24px 0 24px;
+  font-weight: 400;
+}
+.icon-icon_delete_b::before {
+  color: var(--color-btn-danger-bg);
+}
+.icon_alert::before {
+  color: var(--color-btn-warning-bg);
+}
+.delete_title {
+  font-size: 18px;
+  font-weight: 700;
+  padding: 20px 16px;
+  color: var(--color-neutral-1);
+}
+.delete_content {
+  font-size: 14px;
+  font-weight: 400;
+  color: var(--color-neutral-1);
+  padding: 15px 0 33px 37px;
+}
+.pagination {
+  display: flex;
+  justify-content: space-between;
+  font-weight: 400;
+  font-size: 15px;
+  align-items: center;
+  margin: 0 24px;
+  border: 1px solid var(--color-border);
+  border-top: none;
+  padding: 4px 8px;
+  border-radius: 0 0 6px 6px;
+}
+:deep(.el-icon svg) {
+  width: 1em !important;
+}
+.empty-text {
+  margin: 8px 0;
+  color: var(--color-neutral-2);
+  text-align: center;
+  font-size: 14px;
+  font-weight: 700;
+}
+</style>

+ 1 - 0
src/views/Report/src/components/ReportDetail/index.ts

@@ -0,0 +1 @@
+export { default } from './src/ReportDetail.vue'

+ 248 - 0
src/views/Report/src/components/ReportDetail/src/ReportDetail.vue

@@ -0,0 +1,248 @@
+<script setup lang="ts">
+import ManageReportFields from './components/ManageReportFields.vue'
+import FieldsTable from './components/FieldsTable.vue'
+
+const DownloadVisible = ref(false)
+const isFolded = ref(false)
+const ManageReportFieldsRef = ref()
+const FieldsTableRef = ref()
+const ManageCoulumns = ref([])
+const ShipperValue = ref('')
+const StatusValue = ref('')
+const VesselValue = ref('')
+const ContainerValue = ref('')
+const ServiceValue = ref('')
+
+const Statusoptions = [
+  {
+    value: 'Option1',
+    label: 'Option1',
+  },
+  {
+    value: 'Option2',
+    label: 'Option2',
+  },
+  {
+    value: 'Option3',
+    label: 'Option3',
+  },
+  {
+    value: 'Option4',
+    label: 'Option4',
+  },
+  {
+    value: 'Option5',
+    label: 'Option5',
+  },
+]
+const Serviceoptions = [
+  {
+    value: 'Option1',
+    label: 'Option1',
+  },
+  {
+    value: 'Option2',
+    label: 'Option2',
+  },
+  {
+    value: 'Option3',
+    label: 'Option3',
+  },
+  {
+    value: 'Option4',
+    label: 'Option4',
+  },
+  {
+    value: 'Option5',
+    label: 'Option5',
+  },
+]
+const getManageCoulumns = (column: any) => {
+  ManageCoulumns.value = column
+}
+
+const handleClickManageFields = () => {
+  ManageReportFieldsRef.value.openDialog(ManageCoulumns.value)
+}
+
+const handelSearchFilters = () => {
+  let obj = {
+    Shipper: ShipperValue.value,
+    Status: StatusValue.value,
+    Vessel: VesselValue.value,
+    Container: ContainerValue.value,
+    Service: ServiceValue.value
+  }
+  FieldsTableRef.value.handleSearch(obj)
+}
+const handleClickReset = () => {
+  ShipperValue.value = ''
+  StatusValue.value = ''
+  VesselValue.value = ''
+  ContainerValue.value = ''
+  ServiceValue.value = ''
+}
+const downloadCoulumn = ref()
+const handleClickDownload = (val: string) => {
+  DownloadVisible.value = false
+  FieldsTableRef.value.getExportTableData(val, downloadCoulumn.value)
+}
+
+const ApplyNewColumn = (column: any) => {
+  downloadCoulumn.value = column
+}
+
+
+</script>
+<template>
+  <div
+  >
+    <div class="Title">
+      <span>Shipment Status Report</span>
+      <div>
+        <el-popover
+          placement="bottom"
+          width="195"
+          :visible="DownloadVisible">
+            <p class="download-item" @click="handleClickDownload('xlsx')">Excel(.xlsx)</p>
+            <p class="download-item" @click="handleClickDownload('csv')">CSV(.csv)</p>
+          <template #reference>
+            <el-button 
+              @blur="DownloadVisible = false"
+              @click="DownloadVisible = !DownloadVisible" class="el-button--main download_button"
+            >
+              <span class="font_family icon-icon_download_b"></span><span style="margin: 0 4px;">Download Report</span><span class="font_family icon-icon_dropdown_b"></span>
+            </el-button>
+          </template>
+        </el-popover>
+        <el-button type="default" @click="handleClickManageFields">
+          <span class="font_family icon-icon_set_b"></span>Manage Fields
+        </el-button>
+        <ManageReportFields ref="ManageReportFieldsRef" @ApplyNewColumn="ApplyNewColumn"></ManageReportFields>
+      </div>
+    </div>
+    <div class="filters" :class="{'fold-filter' : isFolded}">
+      <div class="filers-flex">
+        <div class="filters-title">
+          <span class="font_family icon-icon_dropdown_b" :class="{'fold-class' : isFolded}" @click="isFolded = !isFolded"></span>
+          Filters
+        </div>
+        <div>
+          <el-button @click="handleClickReset" style="height: 32px;width: 80px;" type="default"><span class="font_family icon-icon_reset_b" style="margin-right: 8px;"></span>Reset</el-button>
+          <el-button style="height: 32px;width: 80px;" class="el-button--dark" @click="handelSearchFilters">Search</el-button>
+        </div>
+      </div>
+      <div class="filter-search">
+        <div class="filters-input">
+          <div class="filters-input-title">Shipper</div>
+          <el-input placeholder="Please enter..." v-model="ShipperValue"></el-input>
+        </div>
+        <div class="filters-input">
+          <div class="filters-input-title">Status</div>
+          <el-select v-model="StatusValue" placeholder="Please select...">
+            <el-option
+              v-for="item in Statusoptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </div>
+        <div class="filters-input">
+          <div class="filters-input-title">Vessel</div>
+          <el-input placeholder="Please enter..." v-model="VesselValue"></el-input>
+        </div>
+        <div class="filters-input">
+          <div class="filters-input-title">Container</div>
+          <el-input placeholder="Please enter..." v-model="ContainerValue"></el-input>
+        </div>
+        <div class="filters-input">
+          <div class="filters-input-title">Service</div>
+          <el-select v-model="ServiceValue" placeholder="Please select...">
+            <el-option
+              v-for="item in Serviceoptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </div>
+      </div>
+    </div>
+    <FieldsTable ref="FieldsTableRef" @getManageCoulumns="getManageCoulumns"></FieldsTable>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.Title {
+  display: flex;
+  justify-content: space-between;
+  height: 68px;
+  border-bottom: 1px solid var(--color-border);
+  font-size: var(--font-size-6);
+  font-weight: 700;
+  padding: 0 24px;
+  align-items: center;
+}
+.download_button {
+  padding: 8px 21px;
+}
+.download-item {
+  margin: 8px;
+  height: 40px;
+  padding: 9px 8px;
+  border-radius: 6px;
+}
+.download-item:hover {
+  background-color: var(--color-table-row-hover-bg);
+}
+.download-item:active {
+  color: var(--color-theme);
+}
+.filters {
+  margin: 8px 24px;
+  padding: 8px 8px 8px 16px;
+  border: 1px solid var(--color-border);
+  border-radius: 12px;
+  .filers-flex {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 20px;
+  }
+  .filters-title {
+    font-size: 18px;
+    font-weight: 700;
+    .icon-icon_dropdown_b {
+      display: inline-block;
+      transition: transform 0.3s;
+      cursor: pointer;
+    }
+    .fold-class {
+      transform: rotate(-90deg) !important;
+    }
+  }
+  .filter-search {
+    display: flex;
+    flex-wrap: wrap;
+    .filters-input {
+      width: 24.5%;
+      margin-right: 8px;
+      margin-bottom: 8px;
+      .filters-input-title {
+        font-size: 12px;
+        font-weight: 400;
+        color: var(--color-neutral-2);
+        margin-bottom: 4px;
+      }
+    }
+    .filters-input:nth-child(4n){
+      margin-right: 0;
+    }
+  }
+}
+.fold-filter {
+  height: 48px;
+  overflow: hidden;
+}
+</style>

+ 367 - 0
src/views/Report/src/components/ReportDetail/src/components/FieldsTable.vue

@@ -0,0 +1,367 @@
+<script setup lang="ts">
+import { ref, onMounted } from 'vue'
+import { type VxeGridInstance, type VxeGridProps } from 'vxe-table'
+import { useRowClickStyle } from '@/hooks/rowClickStyle'
+import { formatNumber } from '@/utils/tools'
+import dayjs from 'dayjs'
+import { autoWidth } from '@/utils/table'
+import { useLoadingState } from '@/stores/modules/loadingState'
+import { useCalculatingHeight } from '@/hooks/calculatingHeight'
+
+const filterRef: Ref<HTMLElement | null> = ref(null)
+
+const containerHeight = useCalculatingHeight(document.documentElement, 446, [filterRef])
+
+const columnstest = ref([
+  {
+    title: 'Reference No. ',
+    field: 'reference_no',
+    displayName: 'Reference No. ',
+    type: 'normal',
+    formatter:'',
+    checked: true
+  },
+  {
+    title: 'Purchase',
+    field: 'purchase',
+    displayName: 'Purchase',
+    type: 'normal',
+    formatter:'',
+    checked: true
+  },
+  {
+    title: 'House Bill of Lading',
+    field: 'house_bill_of_lading',
+    displayName: '',
+    type: 'normal',
+    formatter:'',
+    checked: true
+  },
+  {
+    title: 'Master Bill of Lading',
+    field: 'master_bill_of_lading',
+    displayName: '',
+    type: 'normal',
+    formatter:'',
+    checked: true
+  },
+  {
+    title: 'Vessel Name',
+    field: 'vessel_name',
+    displayName: '',
+    type: 'normal',
+    formatter:'',
+    checked: true
+  },
+  {
+    title: 'Status',
+    field: 'status',
+    displayName: '',
+    type: 'status',
+    formatter:'',
+    checked: true
+  },
+  {
+    title: 'Container Number',
+    field: 'container_number',
+    displayName: '',
+    type: 'normal',
+    formatter:'',
+    checked: true
+  },
+  {
+    title: 'Service Type',
+    field: 'service_type',
+    displayName: '',
+    type: 'normal',
+    formatter:'',
+    checked: true
+  },
+])
+const ColumnSortValue = ref('')
+const SelectedValue = ref('')
+const ColumnSortoptions = [
+  {
+    value: 'Option1',
+    label: 'Option1',
+  },
+  {
+    value: 'Option2',
+    label: 'Option2',
+  },
+  {
+    value: 'Option3',
+    label: 'Option3',
+  },
+  {
+    value: 'Option4',
+    label: 'Option4',
+  },
+  {
+    value: 'Option5',
+    label: 'Option5',
+  },
+]
+const sortOptions = ref([
+  {
+    label: 'Ascending',
+    value: 'Ascending'
+  },
+  {
+    label: 'Descending',
+    value: 'Descending'
+  }
+])
+
+const loadingState = useLoadingState()
+const tableLoadingColumn = ref(false)
+const tableLoadingTable = ref(false)
+const exportLoading = ref(false)
+
+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)'
+  },
+  columnConfig: { resizable: true, useKey: true },
+  rowConfig: { isHover: true },
+  exportConfig: {
+    types: ['csv', 'html', 'txt', 'xlsx'],
+    modes: ['current', 'selected', 'all']
+  }
+})
+
+const tableRef = ref<VxeGridInstance | null>(null)
+const pageInfo = ref({ pageNo: 1, pageSize: 50, total: 0 })
+
+const handleColumns = (columns: any) => {
+  const newColumns = columns.map((item: any) => {
+    let curColumn: any = {
+      title: item.title,
+      field: item.field,
+      width: item.width
+    }
+    // 设置插槽
+    if (item.type === 'status') {
+      curColumn = {
+        ...curColumn,
+        slots: { default: 'status' }
+      }
+    }
+    return curColumn
+  })
+  return newColumns
+}
+const emit = defineEmits(['getManageCoulumns'])
+// 获取表格列
+const getTableColumns = async () => {
+  // tableLoadingColumn.value = true
+  tableData.value.columns = handleColumns(columnstest.value)
+  emit('getManageCoulumns', columnstest.value)
+  nextTick(() => {
+    tableRef.value && autoWidth(tableData.value, tableRef.value)
+    tableLoadingColumn.value = false
+  })
+}
+// 获取表格数据
+const getTableData = (isPageChange?: boolean) => {
+  tableLoadingTable.value = true
+  $api
+    .getConfigurationList({
+      cp: pageInfo.value.pageNo,
+      ps: pageInfo.value.pageSize,
+      rc: isPageChange ? pageInfo.value.total : -1
+    })
+    .then((res: any) => {
+      if (res.code === 200) {
+        pageInfo.value.total = Number(res.data.rc)
+        pageInfo.value.pageNo = res.data.cp
+        pageInfo.value.pageSize = res.data.ps
+        tableData.value.data = [
+          {
+            reference_no: '1234567890',
+            purchase: 'Purchase',
+            house_bill_of_lading: 'House Bill of Lading',
+            master_bill_of_lading: 'Master Bill of Lading',
+            vessel_name: 'Vessel Name',
+            status: 'Created',
+            container_number: 'Container Number',
+            service_type: 'Service Type'
+          }
+        ]
+      }
+    })
+    .finally(() => {
+      nextTick(() => {
+        tableRef.value && autoWidth(tableData.value, tableRef.value)
+        tableLoadingTable.value = false
+      })
+    })
+}
+
+// 查询时调用接口
+const handleSearch = (val: any) => {
+  tableLoadingTable.value = true
+  setTimeout(() => {
+    tableLoadingTable.value = false
+  }, 1000);
+}
+// 下载
+const getExportTableData = (type: string, column: any) => {
+  if(column) {
+    const newColumns = column.map((item: any) => {
+      let curColumn: any = {
+        title: item.displayName != '' ? item.displayName : item.title,
+        field: item.field,
+        width: item.width
+      }
+      return curColumn
+    })
+    column = newColumns
+  }
+  exportLoading.value = true
+  const exportConfig: any = {
+    type: type,
+    message: false,
+    filename: `Report List_${dayjs().format('YYYYMMDDHH[h]mm[m]ss[s]')}`,
+    columns: column || tableData.value.columns
+  }
+
+  tableRef.value.exportData(exportConfig)
+  setTimeout(() => {
+    exportLoading.value = false
+  }, 1000)
+}
+
+// 实现行点击样式
+useRowClickStyle(tableRef)
+
+onMounted(() => {
+  getTableColumns()
+  getTableData()
+})
+
+defineExpose({
+  handleSearch,
+  getExportTableData
+})
+</script>
+<template>
+  <div
+    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="SettingTable">
+      <div class="flex">
+        <div class="Title">
+          Report Data Review
+        </div>
+        <div class="flex">
+          <span class="sort-text">Sort by:</span>
+          <el-select style="width: 160px;margin: 0 8px;" v-model="ColumnSortValue" placeholder="Please select...">
+            <el-option
+              v-for="item in ColumnSortoptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+          <el-select style="width: 124px;" v-model="SelectedValue" placeholder="Please select...">
+            <el-option
+              v-for="item in sortOptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </div>
+      </div>
+      <vxe-grid
+        ref="tableRef"
+        :style="{ border: 'none'}"
+        v-bind="tableData"
+        :height="containerHeight"
+        v-vloading="tableLoadingColumn || tableLoadingTable || loadingState.trackingTableLoading"
+      >
+        <!-- 空数据时的插槽 -->
+        <template #empty>
+          <img src="../images/default_no_data@2x.png" />>
+          <div class="empty-text">No data</div>
+        </template>
+        <!-- Status字段的插槽 -->
+        <template #status="{ row, column }">
+          <VTag :type="row[column.field]">{{ row[column.field] }}</VTag>
+        </template>
+      </vxe-grid>
+    </div>
+    <div class="pagination">
+      <span>Total {{ formatNumber(pageInfo.total) }}</span>
+      <el-pagination
+        v-model:current-page="pageInfo.pageNo"
+        v-model:page-size="pageInfo.pageSize"
+        :page-sizes="[50, 100, 200, 300, 400]"
+        :pagerCount="5"
+        background
+        layout="sizes, prev, pager, next"
+        :total="pageInfo.total"
+        @size-change="getTableData(true)"
+        @current-change="getTableData(true)"
+      />
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.flex {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  .sort-text {
+    font-size: 12px;
+    font-weight: 400;
+    color: var(--color-neutral-2);
+  }
+}
+.Title {
+  font-size: var(--font-size-6);
+  font-weight: 700;
+  margin-bottom: 13px;
+}
+.pagination {
+  display: flex;
+  justify-content: space-between;
+  font-weight: 400;
+  font-size: 15px;
+  align-items: center;
+  margin: 0 24px;
+  border: 1px solid var(--color-border);
+  border-top: none;
+  padding: 4px 8px;
+  border-radius: 0 0 6px 6px;
+}
+:deep(.el-icon svg) {
+  width: 1em !important;
+}
+.SettingTable {
+  padding: 13px 24px 0 24px;
+  font-weight: 400;
+  border-top: 1px solid var(--color-border);
+}
+.empty-text {
+  margin: 8px 0;
+  color: var(--color-neutral-2);
+  text-align: center;
+  font-size: 14px;
+  font-weight: 700;
+}
+</style>

+ 231 - 0
src/views/Report/src/components/ReportDetail/src/components/ManageReportFields.vue

@@ -0,0 +1,231 @@
+<script setup lang="ts">
+import { Item } from 'ant-design-vue/es/menu'
+import { VueDraggable } from 'vue-draggable-plus'
+interface ColumnItem {
+  name: string
+  title: string
+  field: string
+  checked: boolean
+  displayName: string
+  formatter: string
+  type: string
+}
+
+const ManageFieldsVisible = ref(false)
+const ManageFieldsColumns = ref<ColumnItem[]>([])
+const openDialog = (column: any) => {
+  ManageFieldsColumns.value = column
+  ManageFieldsVisible.value = true
+}
+const curSelectedColumns = computed(() => {
+  if (!ManageFieldsColumns.value) return []
+  return ManageFieldsColumns.value.filter((i: any) => i.checked)
+})
+const handleMoveUpSelect = (item: any) => {
+  const index = ManageFieldsColumns.value.findIndex((i: any) => i.title === item.title)
+  if (index === 0) return
+  const temp = ManageFieldsColumns.value[index]
+  ManageFieldsColumns.value[index] = ManageFieldsColumns.value[index - 1]
+  ManageFieldsColumns.value[index - 1] = temp
+}
+const handleMoveDownSelect = (item: any) => {
+  const index = ManageFieldsColumns.value.findIndex((i: any) => i.title === item.title)
+  if (index === ManageFieldsColumns.value.length - 1) return
+  const temp = ManageFieldsColumns.value[index]
+  ManageFieldsColumns.value[index] = ManageFieldsColumns.value[index + 1]
+  ManageFieldsColumns.value[index + 1] = temp
+}
+
+const ShowAll = (type: string) => {
+  ManageFieldsColumns.value.forEach((item: any) => {
+    item.checked = type === 'show'
+  })
+}
+const emit = defineEmits(['ApplyNewColumn'])
+const handleApply = () => {
+  ManageFieldsVisible.value = false
+  emit('ApplyNewColumn', curSelectedColumns.value)
+}
+
+defineExpose({
+  openDialog
+})
+
+</script>
+<template>
+  <el-dialog
+    title="Manage Report Fields"
+    v-model="ManageFieldsVisible"
+    :show-close="false"
+    footer-class="manage-footer-class"
+    width="800px">
+      <div class="flex">
+        <div class="flex">
+          <el-button style="height: 32px; padding: 0 13px;" type="default" @click="ShowAll('show')">Show All</el-button>
+          <el-button style="height: 32px; padding: 0 13px;" type="default" @click="ShowAll('hide')">Hide All</el-button>
+        </div>
+        <div class="fields-title">{{ curSelectedColumns.length }} of {{ ManageFieldsColumns.length }} Fields Visible</div>
+      </div>
+      <div class="system-list">
+        <div class="system-list-one"></div>
+        <div class="system-list-two"></div>
+        <div class="system-list-name">System Name</div>
+        <div class="system-list-display-name">Display Name in Report</div>
+        <div class="system-list-icon"></div>
+      </div>
+      <VueDraggable
+        v-model="ManageFieldsColumns"  
+        class="column-list"
+        ghost-class="ghost-column"
+        :forceFallback="true"
+        fallback-class="fallback-class"
+      >
+        <template v-for="item in ManageFieldsColumns" :key="item.name">
+          <div class="column-item">
+            <div class="system-list-one">
+              <span class="font_family icon-icon_dragsort__b draggable-icon"></span>
+            </div>
+            <div class="system-list-two">
+              <el-checkbox v-model="item.checked"></el-checkbox>
+            </div>
+            <div class="system-list-name" :class="{'hide-class' : !item.checked}">{{ item.title }}</div>
+            <div class="system-list-display-name">
+              <el-input v-model="item.displayName"></el-input>
+            </div>
+            <div class="system-list-icon">
+              <span
+                class="font_family icon-icon_moveup_b move-icon"
+                style="margin-right: 16px;"
+                @click="handleMoveUpSelect(item)"
+              ></span>
+              <span
+                class="font_family icon-icon_movedown_b move-icon"
+                @click="handleMoveDownSelect(item)"
+              ></span>
+            </div>
+          </div>
+        </template>
+      </VueDraggable>
+    <template #footer>
+      <el-button
+        type="default"
+        style="height: 40px; padding: 8px 40px"
+        @click="ManageFieldsVisible = false"
+        >Cancel</el-button
+      >
+      <el-button
+        class="el-button--dark"
+        style="height: 40px; padding: 8px 40px"
+        @click="handleApply"
+      >
+        Apply
+      </el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<style lang="scss" scoped>
+.flex {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.fields-title {
+  font-size: 12px;
+  color: var(--color-neutral-2);
+  font-weight: 400;
+}
+.system-list {
+  background-color: var(--color-header-bg);
+  height: 24px;
+  display: flex;
+  align-items: center;
+  border-radius: 6px;
+  margin: 10px 0 0 0;
+  color: var(--color-neutral-2);
+  .system-list-name,.system-list-display-name {
+    font-size: 12px;
+    color: var(--color-neutral-2);
+    font-weight: 400;
+  }
+}
+.system-list-one {
+  width: 40px;
+}
+.system-list-two {
+  width: 32px;
+}
+.system-list-name {
+  width: 28%;
+}
+.system-list-display-name {
+  width: 50%;
+}
+.system-list-icon {
+  width: 10%;
+  display: flex;
+  align-items: center;
+  justify-content: end;
+}
+.column-list {
+  height: 400px;
+  overflow: auto;
+  padding-right: 8px;
+  .column-item {
+    display: flex;
+    align-items: center;
+    height: 40px;
+    border-radius: 6px;
+    border: 1px solid var(--color-border);
+    background-color: var(--color-table-header-bg);
+    margin: 4px 0;
+    user-select: none;
+    .system-list-one {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
+    .system-list-name {
+      font-size: 14px;
+      font-weight: 400;
+      color: var(--color-neutral-1);
+    }
+    .hide-class {
+      text-decoration: line-through;
+      color: var(--color-neutral-2);
+    }
+    .system-list-display-name {
+      :deep(.el-input__wrapper) {
+        background-color: var(--color-table-header-bg);
+        border-radius: 6px;
+      }
+    }
+    span.draggable-icon {
+      color: var(--color-customize-column-item-drag-icon);
+    }
+    .font_family {
+      font-size: 16px;
+      cursor: pointer;
+    }
+    .move-icon {
+      &:hover {
+        color: var(--color-theme);
+      }
+    }
+  }
+}
+.ghost-column {
+  cursor: move !important;
+  span {
+    opacity: 0;
+  }
+  border: 1px dashed var(--color-customize-column-item-drag-border) !important;
+  background-color: var(--color-customize-column-item-drag-bg) !important;
+  box-shadow: none !important;
+}
+.fallback-class {
+  opacity: 1 !important;
+  background-color: var(--color-customize-column-item-hover-bg) !important;
+  cursor: move !important;
+}
+</style>

二進制
src/views/Report/src/components/ReportDetail/src/images/default_no_data@2x.png


+ 1 - 0
src/views/Report/src/components/ReportSchedule/index.ts

@@ -0,0 +1 @@
+export { default } from './src/ReportSchedule.vue'

+ 67 - 0
src/views/Report/src/components/ReportSchedule/src/ReportSchedule.vue

@@ -0,0 +1,67 @@
+<script setup lang="ts">
+import ValidityPeriod from './components/ValidityPeriod.vue'; 
+import TimeRange from './components/TimeRange.vue'; 
+const isSaveDisabled = computed(() => {
+  return true
+})
+</script>
+<template>
+  <div class="Title">
+    <span>Schedule Configuration - Shipment Status Report</span>
+    <el-button :disabled="isSaveDisabled" class="el-button--main save_button"><span class="font_family icon-icon_save_b icon_dark" style="margin-right: 5px;"></span>Save</el-button>
+  </div>
+  <div class="container">
+    <div class="schedule_rule">
+      <div class="schedule_title">
+        <span class="stars_red">*</span>
+        Schedule Rule Validity Period
+      </div>
+      <div class="schedule_container">
+        <ValidityPeriod></ValidityPeriod>
+      </div>
+    </div>
+    <div class="schedule_rule" style="margin: 8px 0;">
+      <div class="schedule_title">
+        Report Data Time Range
+      </div>
+      <div class="schedule_container">
+        <TimeRange></TimeRange>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.Title {
+  display: flex;
+  justify-content: space-between;
+  height: 68px;
+  border-bottom: 1px solid var(--color-border);
+  font-size: var(--font-size-6);
+  font-weight: 700;
+  padding: 0 24px;
+  align-items: center;
+}
+.save_button {
+  height: 40px;
+  padding: 8px 34px;
+}
+.container {
+  padding: 16px 24px;
+  .schedule_rule {
+    border: 1px solid var(--color-border);
+    border-radius: 12px;
+    .schedule_title {
+      padding: 13px 16px;
+      font-weight: 700;
+      font-size: 18px;
+      color: var(--color-neutral-1);
+      background-color: var(--color-schedule-bg);
+      border-radius: 12px 12px 0 0;
+      .stars_red {
+        color: var(--color-danger);
+      }
+    }
+  }
+}
+</style>

+ 40 - 0
src/views/Report/src/components/ReportSchedule/src/components/TimeRange.vue

@@ -0,0 +1,40 @@
+<script setup lang="ts">
+const DataTimeSelection = ref('')
+const DataTimeoptions = [
+  {
+    value: 'ETD',
+    label: 'ETD'
+  },
+  {
+    value: 'ETA',
+    label: 'ETA'
+  }
+]
+</script>
+<template>
+  <div style="padding: 8px 16px 16px 16px;">
+    <div class="title">
+      <span class="stars_red">*</span>
+      Data Time Reference Field Selection
+    </div>
+    <el-select v-model="DataTimeSelection" placeholder="Please select...">
+      <el-option
+        v-for="item in DataTimeoptions"
+        :key="item.value"
+        :label="item.label"
+        :value="item.value"
+      />
+    </el-select>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.title {
+  color: var(--color-neutral-2);
+  font-size: 12px;
+  font-weight: 400;
+}
+.stars_red {
+  color: var(--color-danger);
+}
+</style>

+ 113 - 0
src/views/Report/src/components/ReportSchedule/src/components/ValidityPeriod.vue

@@ -0,0 +1,113 @@
+<script setup lang="ts">
+import { useUserStore } from '@/stores/modules/user'
+
+const userStore = useUserStore()
+
+const radio = ref(0)
+const startDate = ref()
+const endDate = ref()
+const isShowEffective = computed(() => {
+  return radio.value == 2
+})
+</script>
+<template>
+  <div style="padding: 8px 16px 16px 16px;">
+    <el-radio-group v-model="radio">
+      <el-radio :value="1">
+        Permanent Valid
+        <el-tooltip
+          popper-class="schedule-popper"
+          effect="dark"
+          content="Active continuously once enabled, until manually disabled or deleted."
+          placement="right"
+        >
+          <span class="font_family icon-icon_info_b"></span>
+        </el-tooltip>
+      </el-radio>
+      <el-radio :value="2">
+        <div class="radio_custom">
+          <div style="height: 38px;">
+            Custom Period
+            <el-tooltip
+              popper-class="schedule-popper"
+              effect="dark"
+              content="Only automatically execute during specified time period."
+              placement="right"
+            >
+              <span class="font_family icon-icon_info_b"></span>
+            </el-tooltip>
+          </div>
+          <div v-if="isShowEffective" style="display: flex;">
+            <div style="margin-right: 9px;">
+              <div class="date_text">Effective Start Date</div>
+              <a-date-picker
+                :format="userStore.dateFormat"
+                valueFormat="YYYY.MM.DD"
+                :showToday="false"
+                style="width: 320px;"
+                v-model:value="startDate">
+                <template #suffixIcon>
+                  <span class="font_family icon-icon_date_b"></span>
+                </template>
+              </a-date-picker>
+            </div>
+            <div>
+              <div class="date_text">Effective End Date</div>
+              <a-date-picker
+                :format="userStore.dateFormat"
+                valueFormat="YYYY.MM.DD"
+                :showToday="false"
+                style="width: 320px;"
+                v-model:value="endDate">
+                <template #suffixIcon>
+                  <span class="font_family icon-icon_date_b"></span>
+                </template>
+              </a-date-picker>
+            </div>
+          </div>
+        </div>
+      </el-radio>
+    </el-radio-group>
+  </div>
+</template>
+<style lang="scss" scoped>
+:deep(.el-radio-group) {
+  display: block;
+}
+:deep(.el-radio) {
+  display: flex;
+  min-height: 40px;
+  border: 1px solid var(--color-system-border);
+  background-color: var(--color-system-body-bg);
+  margin-bottom: 8px;
+  border-radius: 6px;
+  padding: 0 8px;
+  margin-right: 0;
+  height: fit-content;
+  line-height: 40px;
+  align-items: start;
+}
+:deep(.radio_custom) {
+  flex-direction: column;
+}
+:deep(.el-radio__input.is-checked + .el-radio__label) {
+  color: var(--color-neutral-1);
+}
+:deep(.el-radio__label) {
+  display: flex;
+  align-items: center;
+  .font_family {
+    color: var(--color-neutral-2);
+    margin-left: 4px;
+  }
+}
+:deep(.el-radio__inner) {
+  margin-top: 11px;
+}
+.date_text {
+  height: 26px;
+  color: var(--color-neutral-2);
+  font-size: 12px;
+  font-weight: 400;
+}
+</style>

二進制
src/views/Report/src/images/default_no_data@2x.png