8 Ревизии 2295344477 ... f0a8f8efde

Автор SHA1 Съобщение Дата
  AmandaG f0a8f8efde Merge branch 'dev' into dev_g преди 3 седмици
  AmandaG e859969597 feat:添加report 页面 преди 3 седмици
  AmandaG 641cd99b03 feat:添加report页面 преди 1 месец
  Jack Zhou 026a8ee450 Merge branch 'dev_zyh' of United_Software/k_online_ui into dev преди 1 месец
  Jack Zhou 50c2ff192d Merge branch 'dev_g' of United_Software/k_online_ui into dev преди 1 месец
  Jack Zhou e7731ae9f2 Merge branch 'dev' into dev_zyh преди 1 месец
  Jack Zhou 903ea200e8 fix: 删除多余代码 преди 1 месец
  Jack Zhou df2040057a Merge branch 'dev' into dev_zyh преди 1 месец
променени са 28 файла, в които са добавени 3246 реда и са изтрити 83 реда
  1. 0 2
      .gitignore
  2. 25 1
      src/router/index.ts
  3. 31 3
      src/stores/modules/breadCrumb.ts
  4. 35 0
      src/styles/Antdui.scss
  5. 7 0
      src/styles/elementui.scss
  6. 4 0
      src/styles/theme-g.scss
  7. 4 0
      src/styles/theme.scss
  8. 4 1
      src/styles/vxeTable.scss
  9. 1 0
      src/views/Booking/src/components/BookingTable/src/BookingTable.vue
  10. 14 1
      src/views/Dashboard/src/DashboardView.vue
  11. 1 1
      src/views/DestinationDelivery/src/components/CreateNewBooking/src/CreateNewbooking.vue
  12. 0 1
      src/views/DestinationDelivery/src/components/TableView/src/TableView.vue
  13. 79 73
      src/views/Layout/src/components/Menu/MenuView.vue
  14. 1 0
      src/views/Report/index.ts
  15. 255 0
      src/views/Report/src/ReportView.vue
  16. 1 0
      src/views/Report/src/components/ReportDetail/index.ts
  17. 248 0
      src/views/Report/src/components/ReportDetail/src/ReportDetail.vue
  18. 367 0
      src/views/Report/src/components/ReportDetail/src/components/FieldsTable.vue
  19. 231 0
      src/views/Report/src/components/ReportDetail/src/components/ManageReportFields.vue
  20. BIN
      src/views/Report/src/components/ReportDetail/src/images/default_no_data@2x.png
  21. 1 0
      src/views/Report/src/components/ReportSchedule/index.ts
  22. 104 0
      src/views/Report/src/components/ReportSchedule/src/ReportSchedule.vue
  23. 646 0
      src/views/Report/src/components/ReportSchedule/src/components/EmailConfiguration.vue
  24. 361 0
      src/views/Report/src/components/ReportSchedule/src/components/FieldsTable.vue
  25. 231 0
      src/views/Report/src/components/ReportSchedule/src/components/ManageReportFields.vue
  26. 469 0
      src/views/Report/src/components/ReportSchedule/src/components/TimeRange.vue
  27. 126 0
      src/views/Report/src/components/ReportSchedule/src/components/ValidityPeriod.vue
  28. BIN
      src/views/Report/src/images/default_no_data@2x.png

+ 0 - 2
.gitignore

@@ -17,8 +17,6 @@ components.d.ts
 package-lock.json
 pnpm-lock.yaml
 auto-imports.d.ts
-*.zip
-*.rar
 
 stats.html
 

+ 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,

+ 35 - 0
src/styles/Antdui.scss

@@ -209,4 +209,39 @@ tr
 }
 .ant-checkbox-checked:after{
   border-color: var(--color-theme) !important;
+}
+.ant-btn-primary {
+  background-color: var(--color-theme) !important;
+  span {
+    color: var(--color-white);
+  }
+  &:hover {
+    background-color: var(--color-btn-main-bg-hover);
+    color: var(--color-white);
+  }
+}
+.ant-picker-dropdown .ant-picker-time-panel-column >li.ant-picker-time-panel-cell-selected .ant-picker-time-panel-cell-inner {
+  background-color: var(--color-orange-6);
+  color: var(--color-theme);
+  &:hover {
+    background-color: var(--color-orange-6);
+  }
+}
+.ant-picker-dropdown .ant-picker-time-panel-column >li.ant-picker-time-panel-cell .ant-picker-time-panel-cell-inner {
+  &:hover {
+    background-color: var(--color-orange-6);
+  }
+}
+.ant-select:not(.ant-select-disabled):not(.ant-select-customize-input):not(.ant-pagination-size-changer):hover .ant-select-selector {
+  border-color: var(--color-theme);
+}
+.ant-select-dropdown .ant-select-item-option-active:not(.ant-select-item-option-disabled) {
+  background-color: var(--color-orange-6);
+}
+.ant-select-dropdown .ant-select-item-option-selected:not(.ant-select-item-option-disabled) {
+  background-color: var(--color-orange-6);
+  div {
+    color: var(--color-theme);
+    font-weight: 400;
+  }
 }

+ 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;
 }

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

@@ -96,4 +96,8 @@
   --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;
+  --color-schedule-details-bg : #343A43;
 }

+ 4 - 0
src/styles/theme.scss

@@ -354,6 +354,10 @@
   --color-ant-picker-th: #b5b9bf;
 
   --color-json-item-hover: #e6f7ff;
+
+  // report
+  --color-schedule-bg: #F6F8FA;
+  --color-schedule-details-bg: #F5F7FA;
 }
 
 :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">

+ 0 - 1
src/views/DestinationDelivery/src/components/TableView/src/TableView.vue

@@ -58,7 +58,6 @@ const handleColumns = (columns: any, status?: string) => {
         formatter: ({ cellValue }: any) => {
           if (!cellValue) return '--'
           const rangeData = cellValue.split(';')
-          console.log('rangeData', rangeData)
           const leftDate = rangeData[0] ? formatTimezone(rangeData[0]) : '_'
           const rightDate = rangeData[1] ? formatTimezone(rangeData[1]) : '_'
           return leftDate + ' -- ' + rightDate

+ 79 - 73
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()
 //监听窗口大小
@@ -152,7 +158,7 @@ const changeRouter = (path: any) => {
     localStorage.removeItem('loginAI')
     emitter.emit('login-success')
   }
-  if (path == '/PromptConfiguration') {
+  if (path == '/PromptConfiguration' || path == '/report') {
     emitter.emit('checkPrompt')
   } else {
     if (localStorage.getItem('loginAI')) {

+ 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>

BIN
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'

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

@@ -0,0 +1,104 @@
+<script setup lang="ts">
+import ValidityPeriod from './components/ValidityPeriod.vue'; 
+import TimeRange from './components/TimeRange.vue'; 
+import EmailConfiguration from './components/EmailConfiguration.vue'; 
+import FieldsTable from './components/FieldsTable.vue'; 
+const isSaveDisabled = computed(() => {
+  return true
+})
+const FieldsTableRef = ref()
+
+const customStartDate = ref('')
+const customEndDate = ref('')
+const rollingStartDate = ref('')
+const rollingEndDate = ref('')
+const dataTimeField = ref('')
+// 提交ValidityPeriod组件数据
+const handleSubmitValidity = (data: any) => {
+  customStartDate.value = data.startDate
+  customEndDate.value = data.endDate
+  console.log(data)
+}
+
+// 提交TimeRange组件数据
+const handleSubmitRolling = (data: any) => {
+  dataTimeField.value = data.DataTimeSelection
+  rollingStartDate.value = data.rollingStartDate
+  rollingEndDate.value = data.rollingEndDate
+}
+</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
+          @handleSubmitValidity="handleSubmitValidity"
+        >
+        </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
+          @handleSubmitRolling="handleSubmitRolling"
+        >
+        </TimeRange>
+      </div>
+    </div>
+    <div class="schedule_rule" style="margin: 8px 0;">
+      <div class="schedule_title">
+        Report Delivery Frequency & Email Configuration
+      </div>
+      <div class="schedule_container">
+        <EmailConfiguration></EmailConfiguration>
+      </div>
+    </div>
+    <FieldsTable ref="FieldsTableRef"></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;
+}
+.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>

+ 646 - 0
src/views/Report/src/components/ReportSchedule/src/components/EmailConfiguration.vue

@@ -0,0 +1,646 @@
+<script setup lang="ts">
+import { ElMessage } from 'element-plus'
+import type { SelectProps } from 'ant-design-vue';
+
+const emailValue = ref('')
+const TimeZoneValue = ref('')
+const FrequencyValue = ref('')
+const timeValue = ref('')
+const TimeItem = ref<SelectProps['options']>([
+  {
+    value: '00:00',
+    label: '00:00'
+  },
+  {
+    value: '00:30',
+    label: '00:30'
+  },
+  {
+    value: '01:00',
+    label: '01:00'
+  },
+  {
+    value: '01:30',
+    label: '01:30'
+  },
+  {
+    value: '02:00',
+    label: '02:00'
+  },
+  {
+    value: '02:30',
+    label: '02:30'
+  },
+  {
+    value: '03:00',
+    label: '03:00'
+  },
+  {
+    value: '03:30',
+    label: '03:30'
+  },
+  {
+    value: '04:00',
+    label: '04:00'
+  },
+  {
+    value: '04:30',
+    label: '04:30'
+  },
+  {
+    value: '05:00',
+    label: '05:00'
+  },
+  {
+    value: '05:30',
+    label: '05:30'
+  },
+  {
+    value: '06:00',
+    label: '06:00'
+  },
+  {
+    value: '06:30',
+    label: '06:30'
+  },
+  {
+    value: '07:00',
+    label: '07:00'
+  },
+  {
+    value: '07:30',
+    label: '07:30'
+  },
+  {
+    value: '08:00',
+    label: '08:00'
+  },
+  {
+    value: '08:30',
+    label: '08:30'
+  },
+  {
+    value: '09:00',
+    label: '09:00'
+  },
+  {
+    value: '09:30',
+    label: '09:30'
+  },
+  {
+    value: '10:00',
+    label: '10:00'
+  },
+  {
+    value: '10:30',
+    label: '10:30'
+  },
+  {
+    value: '11:00',
+    label: '11:00'
+  },
+  {
+    value: '11:30',
+    label: '11:30'
+  },
+  {
+    value: '12:00',
+    label: '12:00'
+  },
+  {
+    value: '12:30',
+    label: '12:30'
+  },
+  {
+    value: '13:00',
+    label: '13:00'
+  },
+  {
+    value: '13:30',
+    label: '13:30'
+  },
+  {
+    value: '14:00',
+    label: '14:00'
+  },
+  {
+    value: '14:30',
+    label: '14:30'
+  },
+  {
+    value: '15:00',
+    label: '15:00'
+  },
+  {
+    value: '15:30',
+    label: '15:30'
+  },
+  {
+    value: '16:00',
+    label: '16:00'
+  },
+  {
+    value: '16:30',
+    label: '16:30'
+  },
+  {
+    value: '17:00',
+    label: '17:00'
+  },
+  {
+    value: '17:30',
+    label: '17:30'
+  },
+  {
+    value: '18:00',
+    label: '18:00'
+  },
+  {
+    value: '18:30',
+    label: '18:30'
+  },
+  {
+    value: '19:00',
+    label: '19:00'
+  },
+  {
+    value: '19:30',
+    label: '19:30'
+  },
+  {
+    value: '20:00',
+    label: '20:00'
+  },
+  {
+    value: '20:30',
+    label: '20:30'
+  },
+  {
+    value: '21:00',
+    label: '21:00'
+  },
+  {
+    value: '21:30',
+    label: '21:30'
+  },
+  {
+    value: '22:00',
+    label: '22:00'
+  },
+  {
+    value: '22:30',
+    label: '22:30'
+  },
+  {
+    value: '23:00',
+    label: '23:00'
+  },
+  {
+    value: '23:30',
+    label: '23:30'
+  }
+])
+const Timezoneoptions = ref(
+  [
+    {
+      label: 'UTC-08',
+      value: 'UTC-08:00'
+    },
+    {
+      label: 'UTC-07',
+      value: 'UTC-07:00'
+    },
+    {
+      label: 'UTC-06',
+      value: 'UTC-06:00'
+    },
+    {
+      label: 'UTC-05',
+      value: 'UTC-05:00'
+    },
+    {
+      label: 'UTC-04',
+      value: 'UTC-04:00'
+    },
+    {
+      label: 'UTC-03',
+      value: 'UTC-03:00'
+    },
+    {
+      label: 'UTC-02',
+      value: 'UTC-02:00'
+    },
+    {
+      label: 'UTC-01',
+      value: 'UTC-01:00'
+    },
+    {
+      label: 'UTC-00',
+      value: 'UTC-00:00'
+    },
+    {
+      label: 'UTC+01',
+      value: 'UTC+01:00'
+    },
+    {
+      label: 'UTC+02',
+      value: 'UTC+02:00'
+    },
+    {
+      label: 'UTC+03',
+      value: 'UTC+03:00'
+    },
+    {
+      label: 'UTC+04',
+      value: 'UTC+04:00'
+    },
+    {
+      label: 'UTC+05',
+      value: 'UTC+05:00'
+    },
+    {
+      label: 'UTC+05:30',
+      value: 'UTC+05:30'
+    },
+    {
+      label: 'UTC+06',
+      value: 'UTC+06:00'
+    },
+    {
+      label: 'UTC+07',
+      value: 'UTC+07:00'
+    },
+    {
+      label: 'UTC+08',
+      value: 'UTC+08:00'
+    },
+    {
+      label: 'UTC+09',
+      value: 'UTC+09:00'
+    },
+    {
+      label: 'UTC+10',
+      value: 'UTC+10:00'
+    },
+    {
+      label: 'UTC+11',
+      value: 'UTC+11:00'
+    },
+    {
+      label: 'UTC+12',
+      value: 'UTC+12:00'
+    }
+  ]
+)
+const Frequencyoptions = ref(
+  [
+    {
+      value: 'Daily',
+      label: 'Daily'
+    },
+    {
+      value: 'Weekly',
+      label: 'Weekly'
+    },
+    {
+      value: 'Monthly',
+      label: 'Monthly'
+    },
+    {
+      value: 'Quarterly',
+      label: 'Quarterly'
+    },
+    {
+      value: 'Yearly',
+      label: 'Yearly'
+    }
+  ]
+)
+
+const weeklyCheckList = ref(['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'])
+const maxWeeklySelected = computed(() => weeklyCheckList.value.length - 1)
+const weeklyChecked = ref([])
+
+const MonthlyCheckList = computed(() => {
+  const now = new Date()
+  const year = now.getFullYear()
+  const month = now.getMonth()
+  const lastDay = new Date(year, month + 1, 0).getDate()
+  const daysArray: string[] = []
+  for (let i = 1; i <= lastDay; i++) {
+    daysArray.push(i.toString())
+  }
+  return daysArray
+})
+const maxMonthlySelected = computed(() => MonthlyCheckList.value.length - 1)
+const monthlyChecked = ref([])
+
+const isFebruary = computed(() => MonthlyCheckList.value.length <= 28)
+const is29 = computed(() => MonthlyCheckList.value.length == 29)
+
+const YearlyCheckList = ref(['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'])
+const dayList = ref(['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', 'Last Day'])
+const maxYearlySelected = computed(() => YearlyCheckList.value.length - 1)
+const yearlyChecked = ref([])
+const YearlyDayValue = ref('')
+
+const QuarterMonth = ref(['1st Month', '2nd Month', '3rd Month'])
+const QuarterMonthValue = ref('')
+
+const showWarning = () => {
+  ElMessage({
+    message: 'If you want to select all, please choose the Daily option!',
+    type: 'warning'
+  })
+}
+const handleChangeCheckbox = (value: any) => {
+  if(value == 'weekly') {
+    if(weeklyChecked.value.length == maxWeeklySelected.value) {
+      showWarning()
+    }
+  } else if(value == 'monthly') { 
+    if(monthlyChecked.value.length == maxMonthlySelected.value) {
+      showWarning()
+    }
+  } else if(value == 'yearly') { 
+    if(yearlyChecked.value.length == maxYearlySelected.value) {
+      showWarning()
+    }
+  }
+}
+
+// 切换frequency时,清空内容
+const handleClickFrequency = (value: string) => {
+  if(value == 'Daily') {
+    weeklyChecked.value = []
+    monthlyChecked.value = []
+    yearlyChecked.value = []
+    QuarterMonthValue.value = ''
+    YearlyDayValue.value = ''
+  } else if(value == 'Weekly') { 
+    monthlyChecked.value = []
+    yearlyChecked.value = []
+    QuarterMonthValue.value = ''
+    YearlyDayValue.value = ''
+  } else if(value == 'Monthly') { 
+    weeklyChecked.value = []
+    yearlyChecked.value = []
+    QuarterMonthValue.value = ''
+    YearlyDayValue.value = ''
+  } else if(value == 'Quarterly') { 
+    weeklyChecked.value = []
+    monthlyChecked.value = []
+    yearlyChecked.value = []
+    YearlyDayValue.value = ''
+  } else if(value == 'Yearly') { 
+    weeklyChecked.value = []
+    monthlyChecked.value = []
+    QuarterMonthValue.value = ''
+  }
+}
+</script>
+<template>
+  <div style="padding: 8px 16px 0 16px;">
+    <div class="title">
+      <span class="stars_red">*</span>
+      Email Recipients
+    </div>
+    <el-input v-model="emailValue" :rows="4" type="textarea" placeholder="Enter email address separated by commas" style="margin-top: 4px;"></el-input>
+  </div>
+  <div class="line"></div>
+  <div style="padding: 0 16px;">
+    <div class="flex">
+      <div class="timezone" style="margin-right: 8px;">
+        <div class="title">
+          <span class="stars_red">*</span>
+          Timezone
+        </div>
+        <el-select class="select_time" v-model="TimeZoneValue" placeholder="Please select...">
+          <el-option
+            v-for="item in Timezoneoptions"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+      </div>
+      <div class="timezone">
+        <div class="title">
+          <span class="stars_red">*</span>
+          Delivery Frequency
+        </div>
+        <el-select class="select_time" v-model="FrequencyValue" placeholder="Please select..." @change="handleClickFrequency">
+          <el-option
+            v-for="item in Frequencyoptions"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+      </div>
+    </div>
+    <div class="schedule_details">
+      <div class="detail_title">
+        Schedule Details
+      </div>
+      <div class="weelkly_detailes" v-if="FrequencyValue == 'Weekly'">
+        <div class="title">
+          <span class="stars_red">*</span>
+          Days of Week (Select multiple)
+        </div>
+        <el-checkbox-group
+          :max="maxWeeklySelected"
+          v-model="weeklyChecked"
+          style="margin: 4px 0 16px 0;"
+          @change="handleChangeCheckbox('weekly')"
+        >
+          <el-checkbox border v-for="item in weeklyCheckList" :key="item" :value="item">
+            {{ item }}
+          </el-checkbox>
+        </el-checkbox-group>
+      </div>
+      <div class="monthly_detailes" :class="{'february_class' : isFebruary, 'nine_class' : is29}" v-else-if="FrequencyValue == 'Monthly'">
+        <div class="title">
+          <span class="stars_red">*</span>
+          Days of Month (Select multiple)
+        </div>
+        <el-checkbox-group
+          :max="maxMonthlySelected"
+          v-model="monthlyChecked"
+          style="margin: 4px 0 16px 0;"
+          @change="handleChangeCheckbox('monthly')"
+        >
+          <el-checkbox border v-for="item in MonthlyCheckList" :key="item" :value="item">
+            {{ item }}
+          </el-checkbox>
+        </el-checkbox-group>
+      </div>
+      <div class="yearly_detailes" v-else-if="FrequencyValue == 'Yearly'">
+        <div class="title">
+          <span class="stars_red">*</span>
+          Months (Select multiple)
+        </div>
+        <el-checkbox-group
+          :max="maxYearlySelected"
+          v-model="yearlyChecked"
+          style="margin: 4px 0 16px 0;"
+          @change="handleChangeCheckbox('yearly')"
+        >
+          <el-checkbox border v-for="item in YearlyCheckList" :key="item" :value="item">
+            {{ item }}
+          </el-checkbox>
+        </el-checkbox-group>
+      </div>
+      <div style="display: flex;">
+        <div class="details_time" v-if="FrequencyValue == 'Quarterly'">
+          <div class="title">
+            <span class="stars_red">*</span>
+            Quarter Month
+          </div>
+          <el-select 
+            style="width: 480px;margin: 4px 8px 0 0;"
+            v-model="QuarterMonthValue"
+            placeholder="Please select...">
+            <el-option
+              v-for="item in QuarterMonth"
+              :key="item"
+              :label="item"
+              :value="item"
+            />
+          </el-select>
+        </div>
+        <div class="details_time" v-if="FrequencyValue == 'Yearly'">
+          <div class="title">
+            <span class="stars_red">*</span>
+            Day
+          </div>
+          <el-select 
+            style="width: 480px;margin: 4px 8px 0 0;"
+            v-model="YearlyDayValue"
+            placeholder="Please select...">
+            <el-option
+              v-for="item in dayList"
+              :key="item"
+              :label="item"
+              :value="item"
+            />
+          </el-select>
+        </div>
+        <div class="details_time">
+          <div class="title">
+            <span class="stars_red">*</span>
+            Time
+          </div>
+          <a-select
+            v-model:value="timeValue"
+            :options="TimeItem"
+            style="width: 480px;margin: 4px 0 0 0;"
+            placeholder="Please select..."
+          >
+            <template #suffixIcon>
+              <span class="font_family icon-icon_time_b"></span>
+            </template>
+          </a-select>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.title {
+  color: var(--color-neutral-2);
+  font-size: 12px;
+  font-weight: 400;
+}
+.stars_red {
+  color: var(--color-danger);
+}
+:deep(.el-textarea) {
+  .el-textarea__inner {
+    resize: none; // 去除右下角图标
+    padding: 5px 7px 5px 10px;
+    box-shadow: 0 0 0 1px var(--color-select-border) inset;
+  }
+}
+.line {
+  margin: 16px 0 ;
+  border-bottom: 1px solid var(--color-border);
+}
+.flex {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.timezone {
+  width: 50%;
+}
+.select_time {
+  margin-top: 4px;
+}
+.schedule_details {
+  background-color: var(--color-schedule-details-bg);
+  border-radius: 6px;
+  margin: 8px 0 20px 0;
+  padding: 11px 0 8px 8px;
+  .detail_title {
+    color: var(--color-neutral-1);
+    font-weight: 700;
+    font-size: 14px;
+    margin-bottom: 20px;
+  }
+}
+:deep(.el-checkbox.is-bordered) {
+  width: 14.2%;
+  margin-right: 0;
+  border-radius: 0;
+  border: 1px solid var(--color-select-border);
+  background-color: var(--management-bg-color);
+  height: 40px;
+}
+:deep(.el-checkbox.is-bordered:first-child) {
+  border-radius: 6px 0 0 6px;
+}
+:deep(.el-checkbox.is-bordered:last-child) {
+  border-radius: 0 6px 6px 0;
+}
+:deep(.monthly_detailes .el-checkbox.is-bordered:first-child) {
+  border-radius: 6px 0 0 0;
+}
+:deep(.monthly_detailes .el-checkbox.is-bordered:nth-child(7)) {
+  border-radius: 0 6px 0 0;
+}
+:deep(.monthly_detailes .el-checkbox.is-bordered:nth-child(28)) {
+  border-radius: 0 0 6px 0;
+}
+:deep(.monthly_detailes .el-checkbox.is-bordered:nth-child(29)) {
+  border-radius: 0 0 0 6px;
+}
+:deep(.february_class .el-checkbox.is-bordered:nth-child(29)) {
+  border-radius: 0 0 0 0;
+}
+:deep(.february_class .el-checkbox.is-bordered:nth-child(22)) {
+  border-radius: 0 0 0 6px;
+}
+:deep(.monthly_detailes .el-checkbox.is-bordered:last-child) {
+  border-radius: 0 0 6px 0;
+}
+:deep(.nine_class .el-checkbox.is-bordered:last-child) {
+  border-radius: 0 0 6px 6px;
+}
+:deep(.yearly_detailes .el-checkbox.is-bordered) {
+  width: 16.6%;
+}
+:deep(.yearly_detailes .el-checkbox.is-bordered:first-child) {
+  border-radius: 6px 0 0 0;
+}
+:deep(.yearly_detailes .el-checkbox.is-bordered:nth-child(6)) {
+  border-radius: 0 6px 0 0;
+}
+:deep(.yearly_detailes .el-checkbox.is-bordered:nth-child(7)) {
+  border-radius: 0 0 0 6px;
+}
+:deep(.yearly_detailes .el-checkbox.is-bordered:last-child) {
+  border-radius: 0 0 6px 0;
+}
+</style>

+ 361 - 0
src/views/Report/src/components/ReportSchedule/src/components/FieldsTable.vue

@@ -0,0 +1,361 @@
+<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'
+import ManageReportFields from './ManageReportFields.vue'
+
+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 ManageReportFieldsRef = ref()
+const ColumnSortoptions = [
+  {
+    value: 'Option1',
+    label: 'Option1',
+  },
+  {
+    value: 'Option2',
+    label: 'Option2',
+  },
+  {
+    value: 'Option3',
+    label: 'Option3',
+  },
+  {
+    value: 'Option4',
+    label: 'Option4',
+  },
+  {
+    value: 'Option5',
+    label: 'Option5',
+  },
+]
+
+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 getTableColumns = async () => {
+  // tableLoadingColumn.value = true
+  tableData.value.columns = handleColumns(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)
+}
+
+const handleClickManageFields = () => {
+  ManageReportFieldsRef.value.openDialog(tableData.value.columns)
+}
+
+const ApplyNewColumn = (column: any) => {
+  tableData.value.columns = column
+}
+
+// 实现行点击样式
+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-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>
+      <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 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>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.flex {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 13px;
+  .sort-text {
+    font-size: 12px;
+    font-weight: 400;
+    color: var(--color-neutral-2);
+  }
+}
+.Title {
+  font-size: var(--font-size-6);
+  font-weight: 700;
+}
+.pagination {
+  display: flex;
+  justify-content: space-between;
+  font-weight: 400;
+  font-size: 15px;
+  align-items: center;
+  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 24px 24px;
+  font-weight: 400;
+  border: 1px solid var(--color-border);
+  border-radius: 12px;
+  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/ReportSchedule/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>

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

@@ -0,0 +1,469 @@
+<script setup lang="ts">
+import { useUserStore } from '@/stores/modules/user'
+import dayjs from 'dayjs'
+
+const userStore = useUserStore()
+const DataTimeSelection = ref('')
+const startDate = ref('')
+const endDate = ref('')
+const RollingValueStart = ref()
+const RollingValueeEnd = ref()
+const radio = ref(0)
+const fixedRangeRadio = ref('')
+const rollingRangeRadio = ref('')
+const DataTimeoptions = ref(
+  [
+    {
+      value: 'ETD',
+      label: 'ETD'
+    },
+    {
+      value: 'ETA',
+      label: 'ETA'
+    }
+  ]
+)
+
+const isshowRolling = computed(() => radio.value === 1)
+const isShowFixed = computed(() => radio.value === 2)
+
+
+const clampedValueStart = computed({
+  get: () => RollingValueStart.value,
+  set: (newVal) => {
+    // 转换为整数
+    const num = parseInt(newVal, 10)
+    // 处理非数字和NaN情况
+    if (isNaN(num)) {
+      RollingValueStart.value = 0
+      return
+    }
+    // 范围限制
+    RollingValueStart.value = Math.max(0, Math.min(365, num))
+  }
+})
+const clampedValueEnd = computed({
+  get: () => RollingValueeEnd.value,
+  set: (newVal) => {
+    // 转换为整数
+    const num = parseInt(newVal, 10)
+    // 处理非数字和NaN情况
+    if (isNaN(num)) {
+      RollingValueeEnd.value = 0
+      return
+    }
+    // 范围限制
+    RollingValueeEnd.value = Math.max(0, Math.min(365, num))
+  }
+})
+
+let RollingRangeTimeStr: any = ''
+
+const emits = defineEmits(['handleSubmitRolling'])
+
+// 输入时间
+const changeTime = (val: any) => {
+  if (val == 1) {
+    startDate.value = ''
+    endDate.value = ''
+    if (
+      typeof clampedValueStart.value == 'number' &&
+      typeof clampedValueEnd.value == 'number'
+    ) {
+      if (clampedValueStart.value == 0 && clampedValueEnd.value == 30) {
+        rollingRangeRadio.value = 'Next 30 days'
+      } else if (clampedValueStart.value == 0 && clampedValueEnd.value == 60) {
+        rollingRangeRadio.value = 'Next 60 days'
+      } else if (clampedValueStart.value == 10 && clampedValueEnd.value == 60) {
+        rollingRangeRadio.value = 'Past 10 days to next 60 day'
+      } else if (clampedValueStart.value == 30 && clampedValueEnd.value == 0) {
+        rollingRangeRadio.value = 'Past 30 days'
+      } else {
+        rollingRangeRadio.value = 'Customize'
+      }
+      if (clampedValueStart.value == 0 && clampedValueEnd.value == 0) {
+        RollingRangeTimeStr = ''
+      } else {
+        RollingRangeTimeStr =
+          'ETD: minus ' +
+          clampedValueStart.value +
+          ' Day(s) to Plus ' +
+          clampedValueEnd.value +
+          ' Day(s)'
+      }
+    } else {
+      RollingRangeTimeStr = ''
+    }
+    emits('handleSubmitRolling', {rollingStartDate: clampedValueStart.value, rollingEndDate: clampedValueEnd.value, DataTimeSelection: DataTimeSelection.value})
+  } else if(val == 2) {
+    clampedValueStart.value = 0
+    clampedValueEnd.value = 0
+    if (startDate.value == dayjs().startOf('month').format('YYYY.MM.DD') && endDate.value == dayjs().endOf('month').format('YYYY.MM.DD')) {
+      fixedRangeRadio.value = 'This Month'
+    } else if (startDate.value == dayjs().subtract(1, 'month').startOf('month').format('YYYY.MM.DD') && endDate.value == dayjs().subtract(1, 'month').endOf('month').format('YYYY.MM.DD')) {
+      fixedRangeRadio.value = 'Last Month'
+    } else if (startDate.value == dayjs().month(Math.floor(dayjs().month() / 3) * 3).startOf('month').format('YYYY.MM.DD') && endDate.value == dayjs().month(Math.floor(dayjs().month() / 3) * 3 + 2).endOf('month').format('YYYY.MM.DD')) {
+      fixedRangeRadio.value = 'This Quarter'
+    } else if (startDate.value == dayjs().month(Math.floor(dayjs().month() / 3) * 3 - 3).startOf('month').format('YYYY.MM.DD') && endDate.value == dayjs().month(Math.floor(dayjs().month() / 3) * 3 - 1).endOf('month').format('YYYY.MM.DD')) {
+      fixedRangeRadio.value = 'Last Quarter'
+    } else if (startDate.value == dayjs().startOf('year').format('YYYY.MM.DD') && endDate.value == dayjs().endOf('year').format('YYYY.MM.DD')) {
+      fixedRangeRadio.value = 'This Year'
+    } else if (startDate.value == dayjs().subtract(1, 'year').startOf('year').format('YYYY.MM.DD') && endDate.value == dayjs().subtract(1, 'year').endOf('year').format('YYYY.MM.DD')) {
+      fixedRangeRadio.value = 'Last Year'
+    } else {
+      fixedRangeRadio.value = 'Customize'
+    }
+    emits('handleSubmitRolling', {rollingStartDate: startDate.value, rollingEndDate: endDate.value, DataTimeSelection: DataTimeSelection.value})
+  } else {
+    RollingRangeTimeStr = ''
+  }
+}
+// 选择Dynamic Rolling Range默认值
+const ChangeRollingRangeRadio = (val: any) => { 
+  if (val == 'Next 30 days') {
+    clampedValueStart.value = 0
+    clampedValueEnd.value = 30
+  } else if (val == 'Next 60 days') {
+    clampedValueStart.value = 0
+    clampedValueEnd.value = 60
+  } else if (val == 'Past 30 days') {
+    clampedValueStart.value = 30
+    clampedValueEnd.value = 0
+  } else if (val == 'Past 10 days to next 60 days') {
+    clampedValueStart.value = 10
+    clampedValueEnd.value = 60
+  } else {
+    clampedValueStart.value = 0
+    clampedValueEnd.value = 0
+  }
+  if (clampedValueStart.value == 0 && clampedValueEnd.value == 0) {
+    RollingRangeTimeStr = ''
+  } else {
+    RollingRangeTimeStr =
+      'ETD: minus ' +
+      clampedValueStart.value +
+      ' Day(s) to Plus ' +
+      clampedValueEnd.value +
+      ' Day(s)'
+  }
+  emits('handleSubmitRolling', {rollingStartDate: clampedValueStart.value, rollingEndDate: clampedValueEnd.value, DataTimeSelection: DataTimeSelection.value})
+}
+
+// 选择fixed range默认值
+const changeFixedRange = (val: any) => {
+  if(val == 'This Month') {
+    startDate.value = dayjs().startOf('month').format('YYYY.MM.DD')
+    endDate.value = dayjs().endOf('month').format('YYYY.MM.DD')
+  } else if(val == 'Last Month') {
+    startDate.value = dayjs().subtract(1, 'month').startOf('month').format('YYYY.MM.DD')
+    endDate.value = dayjs().subtract(1, 'month').endOf('month').format('YYYY.MM.DD')
+  } else if(val == 'This Quarter') {
+    startDate.value = dayjs().month(Math.floor(dayjs().month() / 3) * 3).startOf('month').format('YYYY.MM.DD')
+    endDate.value = dayjs().month(Math.floor(dayjs().month() / 3) * 3 + 2).endOf('month').format('YYYY.MM.DD')
+  } else if(val == 'Last Quarter') {
+    startDate.value = dayjs().month(Math.floor(dayjs().month() / 3) * 3 - 3).startOf('month').format('YYYY.MM.DD')
+    endDate.value = dayjs().month(Math.floor(dayjs().month() / 3) * 3 - 1).endOf('month').format('YYYY.MM.DD')
+  } else if(val == 'This Year') {
+    startDate.value = dayjs().startOf('year').format('YYYY.MM.DD')
+    endDate.value = dayjs().endOf('year').format('YYYY.MM.DD')
+  } else if(val == 'Last Year') {
+    startDate.value = dayjs().subtract(1, 'year').startOf('year').format('YYYY.MM.DD')
+    endDate.value = dayjs().subtract(1, 'year').endOf('year').format('YYYY.MM.DD')
+  }
+  emits('handleSubmitRolling', {rollingStartDate: startDate.value, rollingEndDate: endDate.value, DataTimeSelection: DataTimeSelection.value})
+}
+</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 class="select_time" 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 class="schedule_rule">
+      <div class="schedule_title">
+        <span class="stars_red">*</span>
+        Data Range Configuration Method
+      </div>
+      <div class="schedule_container">
+        <el-radio-group v-model="radio" @change="changeTime">
+          <el-radio :value="1">
+            <div class="radio_custom">
+              <div style="height: 38px;">
+                Dynamic Rolling Range
+                <el-tooltip
+                  popper-class="schedule-popper"
+                  effect="dark"
+                  placement="right"
+                >
+                  <template #content>
+                    Configuration: Past X days to Future Y days, always dynamically calculated based on current date when generating reports.<br/>
+                    Usage: For example, setting 5 days to 3 days means the data range differs each time it's automatically generated.
+                    Used for short-cycle operational monitoring.
+                  </template>
+                  <span class="font_family icon-icon_info_b"></span>
+                </el-tooltip>
+              </div>
+              <div v-if="isshowRolling" class="oceanCheckbox2">
+                <el-radio-group v-model="rollingRangeRadio" @change="ChangeRollingRangeRadio">
+                  <el-radio-button label="Next 30 days" value="Next 30 days" />
+                  <el-radio-button label="Next 60 days" value="Next 60 days" />
+                  <el-radio-button
+                    label="Past 10 days to next 60 days"
+                    value="Past 10 days to next 60 days"
+                  />
+                  <el-radio-button label="Past 30 days" value="Past 30 days" />
+                  <el-radio-button label="Customize" value="Customize" />
+                </el-radio-group>
+                <div class="flex" style="align-items: end; margin: 0 8px 8px 0; flex-wrap: wrap">
+                  <div class="date_flex" style="margin-right: 10px;">
+                    <div class="time_title">Start Date</div>
+                    <div class="flex">
+                      <div class="currentTime">Past</div>
+                      <el-input
+                        v-model="clampedValueStart"
+                        @input="changeTime('1')"
+                        class="input-with-select"
+                      >
+                      </el-input>
+                      <div class="Days">Day(s)</div>
+                    </div>
+                  </div>
+                  <div class="date_flex">
+                    <div class="time_title">End Date</div>
+                    <div class="flex">
+                      <div class="currentTime">Future</div>
+                      <el-input
+                        v-model="clampedValueEnd"
+                        @input="changeTime('1')"
+                        class="input-with-select"
+                      >
+                      </el-input>
+                      <div class="Days">Day(s)</div>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </el-radio>
+          <el-radio :value="2">
+            <div class="radio_custom">
+              <div style="height: 38px;">
+                Fixed Range
+                <el-tooltip
+                  popper-class="schedule-popper"
+                  effect="dark"
+                  placement="right"
+                >
+                  <template #content>
+                    Configuration: Specific start and end dates, always query this range when automatically generating reports.<br />
+                    Example: Start date [2025-01-01], End date [2025-12-31]
+                  </template>
+                  <span class="font_family icon-icon_info_b"></span>
+                </el-tooltip>
+              </div>
+              <div v-if="isShowFixed">
+                <div class="oceanCheckbox2">
+                  <el-radio-group v-model="fixedRangeRadio" @change="changeFixedRange" class="oceanCheckbox2">
+                    <el-radio-button label="This Month" value="This Month" />
+                    <el-radio-button label="Last Month" value="Last Month" />
+                    <el-radio-button label="This Quarter" value="This Quarter"/>
+                    <el-radio-button label="Last Quarter" value="Last Quarter" />
+                    <el-radio-button label="This Year" value="This Year"/>
+                    <el-radio-button label="Last Year" value="Last Year" />
+                    <el-radio-button label="Customize" value="Customize" />
+                  </el-radio-group>
+                </div>
+                <div style="display: flex;">
+                  <div style="margin-right: 9px;width: 50%;">
+                  <div class="date_text">Start Date</div>
+                  <a-date-picker
+                    :format="userStore.dateFormat"
+                    valueFormat="YYYY.MM.DD"
+                    :showToday="false"
+                    @change="changeTime('2')"
+                    style="width: 100%;"
+                    v-model:value="startDate">
+                    <template #suffixIcon>
+                      <span class="font_family icon-icon_date_b"></span>
+                    </template>
+                  </a-date-picker>
+                  </div>
+                  <div style="width: 50%;">
+                    <div class="date_text">End Date</div>
+                    <a-date-picker
+                      :format="userStore.dateFormat"
+                      valueFormat="YYYY.MM.DD"
+                      :showToday="false"
+                      @change="changeTime('2')"
+                      style="width: 100%;"
+                      v-model:value="endDate">
+                      <template #suffixIcon>
+                        <span class="font_family icon-icon_date_b"></span>
+                      </template>
+                    </a-date-picker>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </el-radio>
+        </el-radio-group>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.title {
+  color: var(--color-neutral-2);
+  font-size: 12px;
+  font-weight: 400;
+}
+.stars_red {
+  color: var(--color-danger);
+}
+.select_time {
+  margin: 4px 0 8px 0;
+  width: 320px;
+}
+.schedule_rule {
+  border: 1px solid var(--color-border);
+  background-color: var(--color-schedule-bg);
+  border-radius: 12px;
+  .schedule_title {
+    padding: 11px 9px;
+    font-weight: 700;
+    font-size: 18px;
+    color: var(--color-neutral-1);
+  }
+  .schedule_container {
+    margin: 0 9px 8px 9px;
+  }
+}
+: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;
+}
+.oceanCheckbox2 {
+  :deep(.el-radio-group) {
+    display: flex;
+    flex-direction: row;
+  }
+}
+.oceanCheckbox2 {
+  :deep(.el-radio-button) {
+    flex: 1;
+    border: 1px solid var(--color-system-input-border);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+  :deep(.el-radio-button:first-child) {
+    border-radius: 6px 0 0 6px;
+  }
+  :deep(.el-radio-button:last-child) {
+    border-radius: 0 6px 6px 0;
+  }
+}
+:deep(.el-radio-button__inner) {
+  border: none;
+  height: 32px;
+  flex: 1;
+}
+:deep(.el-radio-button:first-child .el-radio-button__inner) {
+  border-left: none;
+  border-radius: 6px 0 0 6px;
+}
+:deep(.el-radio-button:last-child .el-radio-button__inner) {
+  border-radius: 0 6px 6px 0;
+}
+:deep(.radio_custom) {
+  flex-direction: column;
+  width: 100%;
+}
+:deep(.el-radio__input.is-checked + .el-radio__label) {
+  color: var(--color-neutral-1);
+}
+.input-with-select {
+  border-radius: 0;
+}
+:deep(.el-input__wrapper) {
+  border-radius: 0;
+  opacity: 0.8;
+  height: 32px;
+}
+: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;
+}
+:deep(.el-radio__input) {
+  margin-top: 2px;
+}
+.date_text {
+  height: 26px;
+  color: var(--color-neutral-2);
+  font-size: 12px;
+  font-weight: 400;
+}
+.flex {
+  display: flex;
+  align-items: center;
+}
+.currentTime {
+  background-color: var(--el-disabled-bg-color);
+  border-radius: 6px 0 0 6px;
+  padding: 0 16px;
+  height: 32px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border: 1px solid var(--color-system-input-border);
+}
+.Days {
+  width: 150px;
+  border: 1px solid var(--color-system-input-border);
+  border-radius: 0 6px 6px 0;
+  background-color: var(--color-system-body-bg);
+  border-left: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  opacity: 0.8;
+  height: 32px;
+}
+.time_title {
+  color: var(--color-neutral-2);
+  font-size: 12px;
+  height: 25px;
+  margin-bottom: 4px;
+}
+.date_flex {
+  flex: 1;
+}
+</style>

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

@@ -0,0 +1,126 @@
+<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
+})
+const emit = defineEmits(['handleSubmitValidity'])
+// 选择Validity Period部分
+const ChangeValidity = (val:any) => {
+  if(val == 2) {
+    emit('handleSubmitValidity', {type: 'Custom Period', startDate: startDate.value? startDate.value : '', endDate: endDate.value? endDate.value : ''})
+  } else if(val == 1) {
+    emit('handleSubmitValidity', {type: 'Permanent Valid'})
+    startDate.value = ''
+    endDate.value = ''
+  }
+}
+</script>
+<template>
+  <div style="padding: 8px 16px 16px 16px;">
+    <el-radio-group v-model="radio" @change="ChangeValidity">
+      <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;"
+                @change="ChangeValidity(2)"
+                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"
+                @change="ChangeValidity(2)"
+                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: 12px;
+}
+.date_text {
+  height: 26px;
+  color: var(--color-neutral-2);
+  font-size: 12px;
+  font-weight: 400;
+}
+</style>

BIN
src/views/Report/src/images/default_no_data@2x.png