25 Commits 67806dd82d ... 90eb759b7f

Autor SHA1 Mensaje Fecha
  Jack Zhou 90eb759b7f Merge branch 'test_zyh' of United_Software/k_online_ui into test hace 1 semana
  Jack Zhou 264fd5bab2 Merge branch 'master_zyh' of United_Software/k_online_ui into master hace 1 semana
  Jack Zhou c45a0e7aeb feat: 合并test分支代码 hace 1 semana
  Jack Zhou 75d7e1a582 Merge branch 'test_zyh' of United_Software/k_online_ui into test hace 1 semana
  Jack Zhou 938bfea9aa feat: template页面表格新增两个字段 hace 1 semana
  Jack Zhou 2110e6765b Merge branch 'test_zyh' of United_Software/k_online_ui into test hace 2 semanas
  Jack Zhou 71f669d37d style: 调整mapping弹窗字段名称 hace 2 semanas
  Jack Zhou 0b12fd25a5 feat: 调整party Ids筛选项 hace 2 semanas
  Jack Zhou 1b49732adb Report Template Management页面新增party Ids筛选项 hace 2 semanas
  Jack Zhou 839074d845 Merge branch 'test_zyh' of United_Software/k_online_ui into test hace 2 semanas
  Jack Zhou d517bb280e feat: 实现report detail表格排序功能 hace 3 semanas
  Jack Zhou 9614de1437 Merge branch 'test_zyh' of United_Software/k_online_ui into test hace 3 semanas
  Jack Zhou 640cb016c7 style: 删除多余代码 hace 3 semanas
  Jack Zhou b51304acef feat: 调整report自定义字段逻辑 hace 3 semanas
  Jack Zhou 0593a78077 feat: Create Report Template页面Field字段新增Mapping功能 hace 3 semanas
  Jack Zhou d829437ec0 feat: 解决面包屑显示bug hace 4 semanas
  Jack Zhou 21e261257a feat: 解决面包屑显示bug hace 4 semanas
  Jack Zhou 2067767935 feat: 解决Create Report Template页面保存bug hace 4 semanas
  Jack Zhou 7b1b2d07f5 feat: Tracking详情页文件上传支持PDF Docx XLSX类型文件 hace 4 semanas
  Jack Zhou 9283e76186 feat: 调整格式化服务器时间功能 hace 4 semanas
  Jack Zhou 4ee4ce9ff2 feat: 修改report Template management 页面表格排序功能报错bug hace 1 mes
  Jack Zhou cba90b5051 Merge branch 'master_zyh' of United_Software/k_online_ui into master hace 1 mes
  Jack Zhou 38b453f56c feat: 调整地图请求域名 hace 1 mes
  Jack Zhou 4521d2b2f8 style: 调整departed tag样式 hace 1 mes
  Jack Zhou bb2aff4576 style: 解决Tracking页 departed tag样式bug hace 1 mes
Se han modificado 28 ficheros con 1199 adiciones y 643 borrados
  1. 3 0
      src/components/CustomizeColumns/src/CustomizeColumns.vue
  2. 2 2
      src/components/FliterTags/src/FilterTags.vue
  3. 2 2
      src/components/VBreadcrumb/src/VBreadcrumb.vue
  4. 10 10
      src/components/VTag/src/VTag.vue
  5. 18 10
      src/stores/modules/breadCrumb.ts
  6. 56 4
      src/styles/icons/iconfont.css
  7. 0 0
      src/styles/icons/iconfont.js
  8. 0 0
      src/styles/icons/iconfont.svg
  9. BIN
      src/styles/icons/iconfont.ttf
  10. BIN
      src/styles/icons/iconfont.woff
  11. BIN
      src/styles/icons/iconfont.woff2
  12. 32 16
      src/utils/axios.ts
  13. 14 1
      src/utils/tools.ts
  14. 4 2
      src/views/Booking/src/components/BookingDetail/src/components/EmailView.vue
  15. 81 82
      src/views/Layout/src/components/Menu/MenuView.vue
  16. 13 19
      src/views/Report/src/components/ReportDetail/src/ReportDetail.vue
  17. 28 13
      src/views/Report/src/components/ReportDetail/src/components/FieldsTable.vue
  18. 2 0
      src/views/Report/src/components/ReportSchedule/src/ReportSchedule.vue
  19. 1 0
      src/views/Report/src/components/ReportSchedule/src/components/FieldsTable.vue
  20. 109 7
      src/views/TemplateManagement/src/TemplateManagement.vue
  21. 49 448
      src/views/TemplateManagement/src/components/CreateReportTemplate/src/CreateReportTemplate.vue
  22. 4 1
      src/views/TemplateManagement/src/components/CreateReportTemplate/src/components/AccountSelect.vue
  23. 8 4
      src/views/TemplateManagement/src/components/CreateReportTemplate/src/components/AdjustmentField.vue
  24. 4 8
      src/views/TemplateManagement/src/components/CreateReportTemplate/src/components/PartyIDSelect.vue
  25. 733 0
      src/views/TemplateManagement/src/components/CreateReportTemplate/src/components/ReportFieldsConfiguration.vue
  26. 16 6
      src/views/TemplateManagement/src/components/TableView/src/TableView.vue
  27. 4 2
      src/views/Tracking/src/components/TrackingDetail/src/components/EmailDrawer.vue
  28. 6 6
      src/views/Tracking/src/components/TrackingDetail/src/components/UploadFilesDialog.vue

+ 3 - 0
src/components/CustomizeColumns/src/CustomizeColumns.vue

@@ -102,6 +102,9 @@ const scrollToItem = (itemId: string) => {
       // container.scrollTop = targetElement.offsetTop - container.offsetTop
 
       document.addEventListener('click', handleDocumentClick)
+      setTimeout(() => {
+        searchColumn.value = ''
+      }, 600)
     }
   }, 100)
 }

+ 2 - 2
src/components/FliterTags/src/FilterTags.vue

@@ -150,7 +150,7 @@ const checkedBox = (i: any, name: any, checked: any) => {
   background-color: var(--color-tag-cancelled-bg);
   color: var(--color-tag-cancelled);
 }
-.v-tag__departure {
+.v-tag__departed {
   font-weight: 400;
   background-color: var(--color-tag-departure-bg);
   color: var(--color-tag-departure);
@@ -179,4 +179,4 @@ const checkedBox = (i: any, name: any, checked: any) => {
   background-color: var(--color-tag-all-bg);
   color: var(--color-tag-all);
 }
-</style>
+</style>

+ 2 - 2
src/components/VBreadcrumb/src/VBreadcrumb.vue

@@ -57,11 +57,11 @@ const jumpLinkMonitoring = () => {
     <template v-for="(routeItem, index) in breadCrumb.routeList" :key="routeItem.label">
       <template v-if="index + 1 !== breadCrumb.routeList.length">
         <span @click="jumpLink(routeItem.label, routeItem.query)" class="previous-route">{{
-          routeItem.label
+          routeItem.breadName
         }}</span>
         <span class="interval">|</span>
       </template>
-      <span v-else>{{ routeItem.label }}</span>
+      <span v-else>{{ routeItem.breadName }}</span>
     </template>
   </div>
   <div v-else></div>

+ 10 - 10
src/components/VTag/src/VTag.vue

@@ -26,10 +26,10 @@ const mappingTable = new Map([
   ['Created', 'created'],
   ['Booked', 'booked'],
   ['Cargo Received', 'cargo-received'],
-  ['Departure', 'departure'],
+  // ['Departure', 'departure'],
   ['Arrived', 'arrived'],
   ['Completed', 'completed'],
-  ['Departed', 'Departed'],
+  ['Departed', 'departed'],
   ['Pending Approval', 'pending-approval'],
   ['Active', 'active'],
   ['Inactive', 'inactive']
@@ -104,7 +104,7 @@ defineProps<internalProps>()
       background-color: var(--color-tag-cargo-received);
     }
   }
-  &.v-tag__departure {
+  &.v-tag__departed {
     background-color: var(--color-tag-departure-bg);
     color: var(--color-tag-departure);
     .dot {
@@ -125,13 +125,13 @@ defineProps<internalProps>()
       background-color: var(--color-tag-completed);
     }
   }
-  &.v-tag__Departed {
-    background-color: var(--color-tag-Departed-bg);
-    color: var(--color-tag-Departed);
-    .dot {
-      background-color: var(--color-tag-Departed);
-    }
-  }
+  // &.v-tag__Departed {
+  //   background-color: var(--color-tag-Departed-bg);
+  //   color: var(--color-tag-Departed);
+  //   .dot {
+  //     background-color: var(--color-tag-Departed);
+  //   }
+  // }
   &.v-tag__pending-approval {
     background-color: var(--color-tag-unfinished-approval-bg);
     color: var(--color-tag-unfinished-approval);

+ 18 - 10
src/stores/modules/breadCrumb.ts

@@ -1,7 +1,8 @@
 import { defineStore } from 'pinia'
 
 interface Route {
-  label: string
+  breadName: string
+
   path: string
   query?: string
 }
@@ -43,44 +44,49 @@ export const useBreadCrumb = defineStore('breadCrumb', {
         return
       }
       if (toRoute.name === 'Destination Create New Rule') {
-        let label = ''
+        let breadName = ''
         if (toRoute.query.a != undefined) {
-          label = 'Modify Rule'
+          breadName = 'Modify Rule'
         } else {
-          label = 'Create New Rule'
+          breadName = 'Create New Rule'
         }
         this.routeList = [
           {
+            breadName: 'Destination Delivery',
             label: 'Destination Delivery',
             path: '/destination-delivery',
             query: ''
           },
           {
+            breadName: 'Configurations',
             label: 'Configurations',
             path: '/destination-delivery/configurations',
             query: ''
           },
           {
-            label: label,
+            breadName: breadName,
+            label: toRoute.name,
             path: '/destination-delivery/create-new-rule',
             query: toRoute.query
           }
         ]
       } else if (toRoute.name === 'Create New Booking') {
-        let label = ''
+        let breadName = ''
         if (toRoute.query.a != undefined) {
-          label = 'Modify Booking'
+          breadName = 'Modify Booking'
         } else {
-          label = 'Create New Booking'
+          breadName = 'Create New Booking'
         }
         this.routeList = [
           {
+            breadName: 'Destination Delivery',
             label: 'Destination Delivery',
             path: '/destination-delivery',
             query: ''
           },
           {
-            label: label,
+            breadName: breadName,
+            label: toRoute.name,
             path: '/destination-delivery/create-new-booking',
             query: toRoute.query
           }
@@ -88,7 +94,8 @@ export const useBreadCrumb = defineStore('breadCrumb', {
 
       } else if (toRoute.name && whiteList.includes(toRoute.name)) {
         this.routeList.push({
-          label: toRoute?.meta?.breadName || toRoute.name,
+          breadName: toRoute?.meta?.breadName || toRoute.name,
+          label: toRoute.name,
           path: toRoute.path,
           query: toRoute.query
         })
@@ -96,6 +103,7 @@ export const useBreadCrumb = defineStore('breadCrumb', {
         this.routeList = [
           {
             label: toRoute.name,
+            breadName: toRoute?.meta?.breadName || toRoute.name,
             path: toRoute.path,
             query: toRoute.query
           }

+ 56 - 4
src/styles/icons/iconfont.css

@@ -1,9 +1,9 @@
 @font-face {
   font-family: "font_family"; /* Project id 4672385 */
-  src: url('iconfont.woff2?t=1764126605090') format('woff2'),
-       url('iconfont.woff?t=1764126605090') format('woff'),
-       url('iconfont.ttf?t=1764126605090') format('truetype'),
-       url('iconfont.svg?t=1764126605090#font_family') format('svg');
+  src: url('iconfont.woff2?t=1767838502999') format('woff2'),
+       url('iconfont.woff?t=1767838502999') format('woff'),
+       url('iconfont.ttf?t=1767838502999') format('truetype'),
+       url('iconfont.svg?t=1767838502999#font_family') format('svg');
 }
 
 .font_family {
@@ -14,6 +14,58 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
+.icon-icon_convert_b:before {
+  content: "\e751";
+}
+
+.icon-icon_comment_b:before {
+  content: "\e74e";
+}
+
+.icon-icon_lost_b1:before {
+  content: "\e74f";
+}
+
+.icon-icon_won_b:before {
+  content: "\e750";
+}
+
+.icon-icon_lost_b:before {
+  content: "\e74d";
+}
+
+.icon-icon_page_b:before {
+  content: "\e74c";
+}
+
+.icon-icon_door_b1:before {
+  content: "\e74a";
+}
+
+.icon-icon_airport_b:before {
+  content: "\e74b";
+}
+
+.icon-icon_support_party_b:before {
+  content: "\e745";
+}
+
+.icon-icon_close_b:before {
+  content: "\e746";
+}
+
+.icon-icon_preview_b1:before {
+  content: "\e747";
+}
+
+.icon-icon_communic_ation_b1:before {
+  content: "\e748";
+}
+
+.icon-icon_detail_b:before {
+  content: "\e749";
+}
+
 .icon-icon_active:before {
   content: "\e744";
 }

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
src/styles/icons/iconfont.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
src/styles/icons/iconfont.svg


BIN
src/styles/icons/iconfont.ttf


BIN
src/styles/icons/iconfont.woff


BIN
src/styles/icons/iconfont.woff2


+ 32 - 16
src/utils/axios.ts

@@ -8,23 +8,39 @@ interface codeMessage {
   [key: number]: string
 }
 
-const CODE_MESSAGE: codeMessage = {
-  200: '服务器成功返回请求的数据。',
-  400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
-  401: '用户没有权限(令牌、用户名、密码错误)。',
-  403: '用户得到授权,但是访问是被禁止的。',
-  404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
-  406: '请求的格式不可得。',
-  410: '请求的资源被永久删除,且不会再得到的。',
-  422: '当创建一个对象时,发生一个验证错误。',
-  456: 'refreshToken过期',
-  457: 'accessToken过期',
-  500: '服务器发生错误,请检查服务器。',
-  502: '网关错误。',
-  503: '服务不可用,服务器暂时过载或维护。',
-  504: '网关超时。'
-}
+// const CODE_MESSAGE: codeMessage = {
+//   200: '服务器成功返回请求的数据。',
+//   400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
+//   401: '用户没有权限(令牌、用户名、密码错误)。',
+//   403: '用户得到授权,但是访问是被禁止的。',
+//   404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
+//   406: '请求的格式不可得。',
+//   410: '请求的资源被永久删除,且不会再得到的。',
+//   422: '当创建一个对象时,发生一个验证错误。',
+//   456: 'refreshToken过期',
+//   457: 'accessToken过期',
+//   500: '服务器发生错误,请检查服务器。',
+//   502: '网关错误。',
+//   503: '服务不可用,服务器暂时过载或维护。',
+//   504: '网关超时。'
+// }
 
+const CODE_MESSAGE: codeMessage = {
+  200: 'The server successfully returned the requested data.',
+  400: 'The request was invalid; the server did not create or modify any data.',
+  401: 'Unauthorized: invalid token, username, or password.',
+  403: 'Access is forbidden despite valid authentication.',
+  404: 'The requested resource does not exist; no action was taken by the server.',
+  406: 'The requested format is not available.',
+  410: 'The requested resource has been permanently deleted and will not be available again.',
+  422: 'A validation error occurred while creating an object.',
+  456: 'Refresh token expired.',
+  457: 'Access token expired.',
+  500: 'An internal server error occurred. Please check the server.',
+  502: 'Bad gateway.',
+  503: 'Service unavailable: the server is temporarily overloaded or under maintenance.',
+  504: 'Gateway timeout.'
+};
 class HttpAxios {
   instance: AxiosInstance
   timeout = 30000

+ 14 - 1
src/utils/tools.ts

@@ -1,6 +1,8 @@
-import moment from 'moment-timezone'
+import moment from 'moment-timezone';
+import 'moment-timezone/data/packed/latest.json';
 import { useUserStore } from '@/stores/modules/user'
 
+
 const userStore = useUserStore()
 const formatString = computed(() => {
   return userStore.dateFormat || 'MM/DD/YYYY'
@@ -32,6 +34,17 @@ export const formatTimezone = (time: string, timezone?: string, is12HourClock?:
   }
 }
 
+/**
+ * 将服务器时间转换为用户时区时间
+ * @param
+ * @returns
+ */
+export const formatTimezoneByUser = (time: string, timeFormate: string, showHour?: boolean) => {
+  if (!time) return '--'
+  let curFormatString = showHour ? formatString.value + ' HH:mm' : formatString.value
+  return moment.tz(time, timeFormate, 'America/Los_Angeles').local().format(curFormatString)
+}
+
 /**
  * 返回传入地区的UTC时区格式化
  * @param timezone

+ 4 - 2
src/views/Booking/src/components/BookingDetail/src/components/EmailView.vue

@@ -2,7 +2,7 @@
 import '@wangeditor/editor/dist/css/style.css'
 import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
 import { i18nChangeLanguage, DomEditor } from '@wangeditor/editor'
-import { formatTimezone } from '@/utils/tools'
+import { formatTimezoneByUser } from '@/utils/tools'
 
 i18nChangeLanguage('en')
 
@@ -225,7 +225,9 @@ const sendEmail = () => {
             <div>{{ item.name?.slice(0, 1) }}</div>
           </div>
           <div class="name">{{ item.name }}</div>
-          <div class="date">{{ formatTimezone(item.creatTime) }}</div>
+          <div class="date">
+            {{ formatTimezoneByUser(item.creatTime, 'MM/DD/YYYY HH:mm:ss', true) }}
+          </div>
         </div>
         <div class="content">
           {{ item.content }}

+ 81 - 82
src/views/Layout/src/components/Menu/MenuView.vue

@@ -23,87 +23,87 @@ const getMenuList = () => {
       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',
-      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_report__fill_b',
-      path: '/report'
-    },
-    {
-      index: '5',
-      label: 'System Management',
-      icon: 'icon_system__management_fill_b',
-      type: 'list',
-      children: [
-        {
-          index: '5-7',
-          label: 'Template Management',
-          path: '/template-management'
-        },
-        {
-          index: '5-1',
-          label: 'System Message',
-          path: '/system-message'
-        },
-        {
-          index: '5-2',
-          label: 'System Settings',
-          path: '/system-settings'
-        },
-        {
-          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: '/operation-log'
-        },
-        {
-          index: '5-6',
-          label: 'Prompt Configuration',
-          path: '/prompt-configuration'
-        }
-      ]
-    }
-  ]
+  // menuList.value = [
+  //   {
+  //     index: '1',
+  //     label: 'Dashboard',
+  //     icon: 'icon_data_fill_b',
+  //     path: '/dashboard'
+  //   },
+  //   {
+  //     index: '2',
+  //     label: 'Booking',
+  //     icon: 'icon_booking__fill_b',
+  //     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_report__fill_b',
+  //     path: '/report'
+  //   },
+  //   {
+  //     index: '5',
+  //     label: 'System Management',
+  //     icon: 'icon_system__management_fill_b',
+  //     type: 'list',
+  //     children: [
+  //       {
+  //         index: '5-7',
+  //         label: 'Template Management',
+  //         path: '/template-management'
+  //       },
+  //       {
+  //         index: '5-1',
+  //         label: 'System Message',
+  //         path: '/system-message'
+  //       },
+  //       {
+  //         index: '5-2',
+  //         label: 'System Settings',
+  //         path: '/system-settings'
+  //       },
+  //       {
+  //         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: '/operation-log'
+  //       },
+  //       {
+  //         index: '5-6',
+  //         label: 'Prompt Configuration',
+  //         path: '/prompt-configuration'
+  //       }
+  //     ]
+  //   }
+  // ]
 }
 getMenuList()
 //监听窗口大小
@@ -203,7 +203,6 @@ const changeRouter = (path: any) => {
 const handleCollapseClick = () => {
   isCollapse.value = !isCollapse.value
 }
-const menuRef = ref()
 
 // 友情链接
 const activeName = ref('1')

+ 13 - 19
src/views/Report/src/components/ReportDetail/src/ReportDetail.vue

@@ -18,14 +18,19 @@ const ManageReportFieldsRef = ref()
 const fieldsTableRef = ref()
 
 const filterList = ref([])
-const handleFilterData = (data) => {
+const reportName = ref('')
+const handleFilterData = (filterData, name) => {
+  reportName.value = name
   if (filterList.value.length) return
-  filterList.value = data.map((item) => {
+  filterList.value = filterData.map((item) => {
     let curData: any = {}
     if (item.data_type === 'string') {
       curData.value = ''
     } else if (item.data_type === 'number' || item.data_type === 'date') {
       curData.value = []
+    } else if (item.data_type === 'select') {
+      curData.options = item.options
+      curData.value = ''
     }
     return {
       label: item.label,
@@ -65,7 +70,7 @@ const applyNewColumn = () => {
 <template>
   <div>
     <div class="Title">
-      <span>Shipment Status Report</span>
+      <span>{{ reportName }}</span>
       <div>
         <el-popover placement="bottom" width="195" :visible="DownloadVisible">
           <p class="download-item" @click="handleClickDownload('xlsx')">Excel(.xlsx)</p>
@@ -121,10 +126,12 @@ const applyNewColumn = () => {
             v-if="item.type === 'string'"
             placeholder="Please enter..."
             v-model="item.value"
+            clearable
           ></el-input>
           <el-select
             v-else-if="item.type === 'select'"
             v-model="item.value"
+            clearable
             placeholder="Please select..."
           >
             <el-option
@@ -140,6 +147,7 @@ const applyNewColumn = () => {
               class="no-spinner"
               type="number"
               v-model="item.value[0]"
+              clearable
             ></el-input>
             -
             <el-input
@@ -147,6 +155,7 @@ const applyNewColumn = () => {
               class="no-spinner"
               type="number"
               v-model="item.value[1]"
+              clearable
             ></el-input>
           </div>
           <div v-if="item.type === 'date'" style="display: flex; gap: 4px; align-items: center">
@@ -157,25 +166,10 @@ const applyNewColumn = () => {
               start-placeholder="Start date"
               end-placeholder="End date"
               style="height: 32px"
+              clearable
               value-format="MM/DD/YYYY"
               :format="formatDate"
             />
-
-            <!-- <el-date-picker
-              v-model="item.value[0]"
-              type="date"
-              placeholder="Pick a Date"
-              :format="formatDate"
-              valueFormat="MM/DD/YYYY"
-            />
-            -
-            <el-date-picker
-              v-model="item.value[1]"
-              type="date"
-              placeholder="Pick a Date"
-              :format="formatDate"
-              valueFormat="MM/DD/YYYY"
-            /> -->
           </div>
         </div>
       </div>

+ 28 - 13
src/views/Report/src/components/ReportDetail/src/components/FieldsTable.vue

@@ -96,7 +96,7 @@ const getTableData = (isPageChange?: boolean) => {
   tableLoadingTable.value = true
   let queryParams = {}
   props.filterData.forEach((item) => {
-    if (item.type === 'string') {
+    if (item.type === 'string' || item.type === 'select') {
       queryParams[item.field] = item.value
     } else if (item.type === 'number' || item.type === 'date') {
       queryParams[item.field] = item.value
@@ -108,6 +108,8 @@ const getTableData = (isPageChange?: boolean) => {
       ps: pageInfo.value.pageSize,
       rc: isPageChange ? pageInfo.value.total : -1,
       serial_no: route.query.id,
+      sortByField: sortBy.value,
+      sortByOrder: sortOrder.value,
       ...queryParams
     })
     .then((res: any) => {
@@ -122,6 +124,7 @@ const getTableData = (isPageChange?: boolean) => {
         allTable.value.columns = handleColumns(res.data.tableColumns)
 
         tmpSearch = res.data.tmp_search
+        tmpMapping = res.data.tmp_mapping
 
         reportName.value = res.data.reportName
 
@@ -129,13 +132,12 @@ const getTableData = (isPageChange?: boolean) => {
         sortByOptions.value = sortByData.options
         sortBy.value = sortByData.field
         sortOrder.value = sortByData.order
-        emit('filterOptionsLoaded', res.data.filtersList)
+        emit('filterOptionsLoaded', res.data.filtersList, reportName.value)
       }
     })
     .finally(() => {
       nextTick(() => {
         tableRef.value && autoWidth(tableData.value, tableRef.value)
-
         tableLoadingTable.value = false
       })
     })
@@ -147,6 +149,7 @@ const handleSearch = () => {
 }
 
 let tmpSearch = ''
+let tmpMapping = ''
 // 下载
 const getExportTableData = async (type: string, column: any) => {
   if (column) {
@@ -161,16 +164,22 @@ const getExportTableData = async (type: string, column: any) => {
     column = newColumns
   }
   exportLoading.value = true
-  await $api.getReportAllTableData({ tmp_search: tmpSearch }).then((res: any) => {
-    if (res.code === 200) {
-      allTable.value.data = res.data.Data || []
-      if (allTable.value.data.length > 20000) {
-        type = 'csv'
+  await $api
+    .getReportAllTableData({ tmp_search: tmpSearch, tmp_mapping: tmpMapping })
+    .then((res: any) => {
+      if (res.code === 200) {
+        allTable.value.data = res.data.Data || []
+        if (allTable.value.data.length > 20000) {
+          type = 'csv'
+        }
+      } else if (res.code === 500) {
+        ElMessage.error(res.data.msg)
       }
-    } else if (res.code === 500) {
-      ElMessage.error(res.data.msg)
-    }
-  })
+    })
+    .catch((err: any) => {
+      exportLoading.value = false
+      return
+    })
   await nextTick(async () => {
     await autoWidth(allTable.value, allTableRef.value)
   })
@@ -211,11 +220,17 @@ defineExpose({
           <el-select
             style="width: 200px; margin: 0 8px"
             v-model="sortBy"
+            @change="getTableData()"
             placeholder="Please select..."
           >
             <el-option v-for="item in sortByOptions" :key="item" :label="item" :value="item" />
           </el-select>
-          <el-select style="width: 124px" v-model="sortOrder" placeholder="Please select...">
+          <el-select
+            @change="getTableData()"
+            style="width: 124px"
+            v-model="sortOrder"
+            placeholder="Please select..."
+          >
             <el-option
               v-for="item in sortOptions"
               :key="item.value"

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

@@ -41,6 +41,8 @@ onMounted(() => {
     .then((res: any) => {
       if (res.code === 200) {
         pageData.value = res.data.showData
+        // 初始化的时候如果Report Data Time Range 有值 则调用一次查询
+        changeTimeRange(pageData.value.timeRange)
       }
     })
     .then(() => {

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

@@ -153,6 +153,7 @@ defineExpose({
           <el-select
             style="width: 160px; margin: 0 8px"
             v-model="orderBy"
+            @change="getTableData()"
             placeholder="Please select..."
           >
             <el-option

+ 109 - 7
src/views/TemplateManagement/src/TemplateManagement.vue

@@ -57,6 +57,67 @@ const handleCreate = () => {
     name: 'Create Report Template'
   })
 }
+
+import { debounce } from 'lodash'
+import axios from 'axios'
+
+const emit = defineEmits(['changeData'])
+const changeData = (val: string[]) => {
+  // 同步选中状态
+  emit('changeData', val)
+}
+
+interface ListItem {
+  label: string
+  id: string
+  code: string
+}
+
+const options = ref<ListItem[]>([])
+const loading = ref(false)
+const currentController = ref<AbortController | null>(null)
+
+const handleVisibleChange = (visible) => {
+  !visible && (options.value = [])
+}
+const remoteMethod = (query: string) => {
+  currentController.value?.abort()
+
+  const newController = new AbortController()
+  currentController.value = newController
+  loading.value = true
+
+  $api
+    .getSpecificRolesPartyID({ term: query }, { signal: newController.signal })
+    .then((res) => {
+      if (!newController.signal.aborted && res.code === 200) {
+        options.value = (res.data || []).map((item) => ({
+          id: item.id,
+          code: item.code,
+          label: item.label
+        }))
+      }
+    })
+    .catch((err) => {
+      options.value = []
+      if (!axios.isCancel(err) && !newController.signal.aborted) {
+        ElMessage.error('Failed to load options')
+      }
+    })
+    .finally(() => {
+      // 仅当这是最新请求时,才关闭 loading
+      if (currentController.value === newController) {
+        loading.value = false
+      }
+    })
+}
+
+// 防抖版本(可选)
+const debouncedRemoteMethod = debounce(remoteMethod, 200)
+
+onUnmounted(() => {
+  currentController.value?.abort()
+})
 </script>
 <template>
   <div class="dashboard">
@@ -108,14 +169,34 @@ const handleCreate = () => {
             />
           </el-select>
         </div>
-        <div class="tips_filter">
-          <el-select v-model="queryData.party_id" clearable placeholder="Party ID">
+        <div class="party-id-tips-filter">
+          <el-select
+            v-model="queryData.party_id"
+            multiple
+            filterable
+            reserve-keyword
+            placeholder="Party IDs"
+            :loading="loading"
+            collapse-tags
+            collapse-tags-tooltip
+            :max-collapse-tags="1"
+            popper-class="part-id-select-popper"
+            :filter-method="debouncedRemoteMethod"
+            @change="changeData"
+            @visible-change="handleVisibleChange"
+          >
             <el-option
-              v-for="item in aiModelList"
-              :key="item.value"
-              :label="item.label"
-              :value="item.value"
-            />
+              v-for="item in options"
+              :key="item.code"
+              :label="item.code"
+              :value="item.code"
+            >
+              <div class="select-option">
+                <el-checkbox :model-value="queryData.party_id.includes(item.code)">
+                  <span class="text-ellipsis" style="width: 240px">{{ item.code }}</span>
+                </el-checkbox>
+              </div>
+            </el-option>
           </el-select>
         </div>
 
@@ -156,6 +237,16 @@ const handleCreate = () => {
   max-width: 190px;
   margin-right: 8px;
 }
+.party-id-tips-filter {
+  flex: 1;
+  height: 30px;
+  width: 280px;
+  max-width: 280px;
+  margin-right: 8px;
+  :deep(.el-tag) {
+    max-width: 220px !important;
+  }
+}
 .input-tips_filter {
   flex: 1;
   max-width: 320px;
@@ -183,4 +274,15 @@ const handleCreate = () => {
   position: relative;
   background-color: var(--color-mode);
 }
+.text-ellipsis {
+  display: inline-block;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  overflow: hidden;
+}
+</style>
+<style lang="scss">
+.el-select__selection {
+  max-width: 450px;
+}
 </style>

+ 49 - 448
src/views/TemplateManagement/src/components/CreateReportTemplate/src/CreateReportTemplate.vue

@@ -3,9 +3,7 @@ import { useRouter, useRoute } from 'vue-router'
 import partyIDSelect from './components/partyIDSelect.vue'
 import GroupNameSelect from './components/GroupNameSelect.vue'
 import AccountSelect from './components/AccountSelect.vue'
-import { VueDraggable } from 'vue-draggable-plus'
-import AdjustmentField from './components/AdjustmentField.vue'
-import { cloneDeep } from 'lodash'
+import ReportFieldsConfiguration from './components/ReportFieldsConfiguration.vue'
 
 const router = useRouter()
 const route = useRoute()
@@ -30,6 +28,7 @@ onMounted(() => {
           reportLevel: data.reportLevel,
           reportDescription: data.reportDescription
         }
+        oldReportLevel.value = data.reportLevel
         fieldsList.value = data.reportFields.map((item) => {
           return {
             ...item,
@@ -72,13 +71,15 @@ interface Field {
   value?: string
   isFilter: boolean
   isSort: boolean
+  mapping?: { system: string; converted: string }[]
   groupName: string
+  isFieldDataMapping?: string
 }
 const fieldsList = ref<Field[]>([])
 const levelOptions = [
   {
-    label: 'Shipment level',
-    value: 'Shipment level'
+    label: 'Shipment Level',
+    value: 'Shipment Level'
   },
   {
     label: 'Container Level',
@@ -95,151 +96,6 @@ const generate8DigitUnique = () => {
   return Math.floor(10000000 + Math.random() * 90000000).toString()
 }
 
-const changeFieldConfig = (state: any, field: string, index: number, key: string) => {
-  if (!state) return
-  fieldsList.value.forEach((item) => {
-    if (item.field === field) {
-      item[key] = false
-    }
-  })
-  const firstMatch = fieldsList.value[index]
-  if (firstMatch) {
-    firstMatch[key] = true
-  }
-}
-
-const handleDeleteField = (index: number, uniqueId: string) => {
-  fieldsList.value.splice(index, 1)
-
-  Object.keys(copyFieldsList.value).forEach((key) => {
-    copyFieldsList.value[key] = copyFieldsList.value[key].filter(
-      (item) => item.uniqueId !== uniqueId
-    )
-  })
-}
-const copyFieldsList = ref({})
-const handleCopyField = (index: number) => {
-  const curField = cloneDeep(fieldsList.value[index])
-  curField.displayName = `${curField.displayName} (Copy)`
-  curField.isFilter = false
-  curField.isSort = false
-  curField.uniqueId = generate8DigitUnique()
-  if (copyFieldsList.value[curField.field]) {
-    copyFieldsList.value[curField.field].push(cloneDeep(curField))
-  } else {
-    copyFieldsList.value[curField.field] = [cloneDeep(curField)]
-  }
-  fieldsList.value.splice(index + 1, 0, cloneDeep(curField))
-}
-
-const AdjustmentFieldRef = ref()
-// 打开定制表格弹窗
-const handleCustomizeColumns = () => {
-  if (!infoData.value.reportLevel) {
-    ElMessage.warning('Please select the report level.')
-    return
-  }
-  const params = {
-    serial_no: '',
-    level: infoData.value.reportLevel
-  }
-  const seen = new Set()
-  const uniqueArray = fieldsList.value.filter((item) => {
-    if (seen.has(item.field)) {
-      return false // 已存在,跳过
-    }
-    seen.add(item.field)
-    return true // 第一次出现,保留
-  })
-
-  AdjustmentFieldRef.value.openDialog(
-    params,
-    -220,
-    'Drag item over to this selection or click "add" icon to show the field on report template list',
-    uniqueArray
-  )
-}
-
-const newFieldInfo = ref<{
-  name: string
-  fieldType: 'Blank' | 'Fixed Value'
-  value: string
-}>({
-  name: '',
-  fieldType: 'Blank',
-  value: ''
-})
-const addNewFieldVisible = ref(false)
-// 添加新字段
-const handleAddNewField = () => {
-  addNewFieldVisible.value = true
-}
-const handleFieldTypeChange = () => {
-  newFieldInfo.value.value = ''
-}
-const addNewField = () => {
-  if (!newFieldInfo.value.name?.trim()) {
-    ElMessage.warning('Please enter the new field name.')
-    return
-  }
-
-  if (newFieldInfo.value.fieldType === 'Fixed Value' && !newFieldInfo.value.value?.trim()) {
-    ElMessage.warning('Please enter the fixed value.')
-    return
-  }
-  fieldsList.value.unshift({
-    uniqueId: generate8DigitUnique(),
-    field: newFieldInfo.value.name,
-    title: newFieldInfo.value.name,
-    displayName: newFieldInfo.value.name,
-    value: newFieldInfo.value.value,
-    fieldType: 'Custom',
-    groupName: '',
-    isFilter: false,
-    isSort: false
-  })
-  newFieldInfo.value = {
-    name: '',
-    fieldType: 'Blank',
-    value: ''
-  }
-  addNewFieldVisible.value = false
-}
-// 调整应用字段
-const handleApplay = (data: any) => {
-  const customizeData = fieldsList.value.filter((item: any) => item.field_type === 'Custom')
-  fieldsList.value = data.map((item: any) => {
-    return {
-      ...item,
-      label: item.label,
-      title: item.title || item.label,
-      displayName: item.displayName || item.label,
-      isFilter: !!item.isFilter,
-      isSort: !!item.isSort,
-      uniqueId: item.uniqueId || generate8DigitUnique()
-    }
-  })
-  fieldsList.value = [...customizeData, ...fieldsList.value]
-
-  const validFields = new Set(fieldsList.value.map((item) => item.field))
-  for (const field in copyFieldsList.value) {
-    if (!validFields.has(field)) {
-      delete copyFieldsList.value[field]
-    }
-  }
-
-  // === 第三步:从后往前插入副本(不修改原始项)===
-  for (let i = fieldsList.value.length - 1; i >= 0; i--) {
-    const field = fieldsList.value[i].field
-    const copies = copyFieldsList.value[field]
-
-    if (copies?.length) {
-      // 插入副本到原项后面
-      fieldsList.value.splice(i + 1, 0, ...copies)
-    }
-  }
-}
-
 const accessControlType = ref('All Users')
 const detailRef: Ref<HTMLElement | null> = ref(null)
 
@@ -248,6 +104,11 @@ const specificRoles = ref({
   groupName: [],
   systemAccount: []
 })
+
+const changeControlType = () => {
+  specificRoles.value = { partyId: [], groupName: [], systemAccount: [] }
+}
+
 const changePartyId = (val: string[]) => {
   specificRoles.value.partyId = val
 }
@@ -259,13 +120,22 @@ const changeAccount = (val: string[]) => {
   specificRoles.value.systemAccount = val
 }
 
-const fieldLoading = ref(false)
-const handleRightRemove = () => {}
-
 const handleCancel = () => {
   router.push('/template-management')
 }
 
+const fieldsConfigurationRef = ref()
+const oldReportLevel = ref('')
+const changeLevel = (val: string) => {
+  fieldsConfigurationRef.value.changeReportLevel(val, oldReportLevel.value)
+}
+const cancelChangeLevel = (val: string) => {
+  infoData.value.reportLevel = val
+}
+const changeOldLevel = (val: string) => {
+  oldReportLevel.value = val
+}
+
 const handlePageSave = () => {
   let verified = true
   if (!infoData.value.reportName.trim()) {
@@ -282,11 +152,13 @@ const handlePageSave = () => {
   }
   if (
     accessControlType.value === 'Specific Roles' &&
-    specificRoles.value.partyId?.length === 0 &&
-    specificRoles.value.groupName?.length === 0 &&
-    specificRoles.value.systemAccount?.length === 0
+    !specificRoles.value.partyId?.length &&
+    !specificRoles.value.groupName?.length &&
+    !specificRoles.value.systemAccount?.length
   ) {
-    ElMessage.warning('Please select Party ID or Group Name for Specific Roles access control.')
+    ElMessage.warning(
+      'Please select Party ID or Group Name or KLN ONLINE Account for Specific Roles access control.'
+    )
     verified = false
   }
   if (!verified) {
@@ -301,7 +173,7 @@ const handlePageSave = () => {
     party_ids: specificRoles.value.partyId || [],
     group_names: specificRoles.value.groupName || [],
     system_account: specificRoles.value.systemAccount || [],
-    fieldsList: fieldsList.value
+    fieldsList: fieldsConfigurationRef.value.getFieldsList()
   }
   let serial_no = ''
   if (route.query.copy !== 't' && route.query.serial_no) {
@@ -324,7 +196,7 @@ const handlePageSave = () => {
 </script>
 <template>
   <div class="dashboard" v-vloading="pageLoading">
-    <div class="Title">
+    <div class="title">
       <span>Create New Report Template</span>
       <div class="button-group">
         <el-button type="default" @click="handleCancel">
@@ -354,7 +226,11 @@ const handlePageSave = () => {
                 <span style="color: var(--color-danger)">*</span>
                 <span>Report Level</span>
               </div>
-              <el-select v-model="infoData.reportLevel" placeholder="Please enter...">
+              <el-select
+                v-model="infoData.reportLevel"
+                @change="changeLevel"
+                placeholder="Please enter..."
+              >
                 <el-option
                   v-for="item in levelOptions"
                   :label="item.label"
@@ -377,108 +253,21 @@ const handlePageSave = () => {
           </div>
         </div>
       </div>
-      <div class="fields-configuration template-box">
-        <div class="header">
-          <span>Report Fields Configuration</span>
-
-          <div class="right-option">
-            <el-button
-              class="el-button--dark"
-              @click="handleAddNewField"
-              style="width: 148px; padding-top: 11px"
-            >
-              <span style="margin-right: 3px" class="font_family icon-icon_add_b"></span>
-              <span>Add New Field</span>
-            </el-button>
-            <el-button
-              v-if="fieldsList.length > 0"
-              class="el-button--dark"
-              @click="handleCustomizeColumns()"
-              style="width: 110px; padding-top: 11px"
-            >
-              <span style="margin-right: 3px" class="font_family icon-icon_add_b"></span>
-              <span>Select Field</span>
-            </el-button>
-          </div>
-        </div>
-        <div class="content-box">
-          <div class="empty-box" v-if="fieldsList.length === 0">
-            <el-button class="el-button--dark" @click="handleCustomizeColumns">
-              <span class="font_family icon-icon_add_b"></span>Add/Edit Field
-            </el-button>
-            <p>No field selected. click “Add Field” to get started.</p>
-          </div>
-          <div class="fields-list" v-else>
-            <VueDraggable
-              v-vloading="fieldLoading"
-              v-model="fieldsList"
-              class="column-list"
-              ghost-class="ghost-column"
-              :forceFallback="true"
-              fallback-class="fallback-class"
-              group="customizeColumns"
-              item-key="uniqueId"
-              @end="handleRightRemove"
-              handle=".handle-draggable"
-            >
-              <div
-                class="field-item"
-                v-for="(fieldItem, index) in fieldsList"
-                :key="fieldItem.uniqueId"
-              >
-                <span
-                  class="font_family icon-icon_dragsort__b draggable-icon handle-draggable"
-                  style="margin-right: 12px; font-size: 16px"
-                ></span>
-                <div class="label handle-draggable">
-                  <span style="font-weight: 700">[{{ fieldItem.field }}]</span>
-                  <span style="margin-left: 8px">{{ fieldItem.title }}</span>
-                </div>
-                <el-input
-                  :id="fieldItem.uniqueId"
-                  :name="fieldItem.uniqueId"
-                  class="display-name"
-                  v-model="fieldItem.displayName"
-                  placeholder="Display Name in Report"
-                ></el-input>
-                <div class="actions">
-                  <div class="checkbox-group">
-                    <el-checkbox
-                      :disabled="
-                        fieldItem.fieldType !== 'System' ||
-                        fieldItem.groupName === 'Container Status' ||
-                        fieldItem.groupName === 'Milestone'
-                      "
-                      v-model="fieldItem.isFilter"
-                      @change="changeFieldConfig($event, fieldItem.field, index, 'isFilter')"
-                      >Filter</el-checkbox
-                    >
-                    <el-checkbox
-                      :disabled="fieldItem.fieldType !== 'System'"
-                      v-model="fieldItem.isSort"
-                      @change="changeFieldConfig($event, fieldItem.field, index, 'isSort')"
-                      >Sort</el-checkbox
-                    >
-                  </div>
-                  <span
-                    style="margin-right: 4px"
-                    @click="handleCopyField(index)"
-                    class="font_family icon-icon_clone_b"
-                  ></span>
-                  <span
-                    @click="handleDeleteField(index, fieldItem.uniqueId)"
-                    class="font_family icon-icon_delete_b"
-                  ></span>
-                </div>
-              </div>
-            </VueDraggable>
-          </div>
-        </div>
-      </div>
+      <ReportFieldsConfiguration
+        :report-level="infoData.reportLevel"
+        :pageData="fieldsList"
+        @confirm-change-level="changeOldLevel"
+        @cancel-change-level="cancelChangeLevel"
+        ref="fieldsConfigurationRef"
+      ></ReportFieldsConfiguration>
       <div class="report-access-control template-box">
         <div class="header">Report Access Control</div>
         <div class="content-box">
-          <el-radio-group class="radio-group" v-model="accessControlType">
+          <el-radio-group
+            class="radio-group"
+            @change="changeControlType"
+            v-model="accessControlType"
+          >
             <el-radio class="radio-item" value="All Users">
               <template #default>
                 <div class="radio-content">
@@ -540,58 +329,11 @@ const handlePageSave = () => {
         </div>
       </div>
     </div>
-
-    <AdjustmentField @apply="handleApplay" ref="AdjustmentFieldRef" />
-    <el-dialog
-      class="add-new-field-dialog"
-      title="Add New Field"
-      v-model="addNewFieldVisible"
-      width="480"
-    >
-      <div>
-        <div class="field-item">
-          <div class="label">
-            <span class="required-symbol">*</span>
-            <span>New Field Name</span>
-          </div>
-          <el-input placeholder="Please enter..." v-model="newFieldInfo.name"></el-input>
-        </div>
-        <div class="field-item field-value">
-          <div class="label">
-            <span class="required-symbol">*</span>
-            <span>Field Value</span>
-          </div>
-          <el-radio-group v-model="newFieldInfo.fieldType" @change="handleFieldTypeChange">
-            <el-radio label="Blank">Blank</el-radio>
-            <el-radio label="Fixed Value">Fixed Value</el-radio>
-          </el-radio-group>
-        </div>
-        <div class="field-item" v-if="newFieldInfo.fieldType === 'Fixed Value'">
-          <div class="label">
-            <span class="required-symbol">*</span>
-            <span>Fixed Value</span>
-          </div>
-          <el-input placeholder="Please enter..." v-model="newFieldInfo.value"></el-input>
-        </div>
-      </div>
-      <template #footer>
-        <el-button
-          style="height: 40px; width: 115px"
-          class="cancel-btn"
-          type="default"
-          @click="addNewFieldVisible = false"
-          >Cancel</el-button
-        >
-        <el-button style="height: 40px; width: 120px" class="el-button--dark" @click="addNewField"
-          >Apply</el-button
-        >
-      </template>
-    </el-dialog>
   </div>
 </template>
 
 <style lang="scss" scoped>
-.Title {
+.title {
   position: sticky;
   top: 0;
   z-index: 100;
@@ -605,12 +347,6 @@ const handlePageSave = () => {
   align-items: center;
   background-color: var(--color-mode);
 }
-.heaer_top {
-  margin-top: 6.57px;
-  margin-bottom: 8px;
-  padding-right: 8px;
-  display: flex;
-}
 
 .display {
   max-height: calc(100vh - 140px);
@@ -644,105 +380,7 @@ const handlePageSave = () => {
     padding: 8px 16px 16px;
   }
 }
-.fields-configuration {
-  div.content-box {
-    display: flex;
-    align-items: flex-start;
-    justify-content: center;
-    min-height: 272px;
-    max-height: 400px;
-    width: 100%;
-    padding-bottom: 8px;
-    padding-right: 0px;
-    // overflow: auto;
-    .empty-box {
-      align-self: center;
-      width: 100%;
-      text-align: center;
-      p {
-        margin-top: 12px;
-        color: var(--color-neutral-2);
-      }
-    }
-    .fields-list {
-      width: 100%;
-      max-height: 400px;
-      padding: 8px 0;
-      padding-right: 16px;
-      overflow: auto;
-      user-select: none;
-      .field-item {
-        display: flex;
-        align-items: center;
-        height: 48px;
-        margin-bottom: 8px;
-        padding: 0 16px;
-        border-radius: 6px;
-        border: 1px solid var(--color-border);
-        .label {
-          flex: 1;
-          .required-symbol {
-            color: var(--color-danger);
-          }
-        }
-        .display-name {
-          flex: 1.2;
-          margin: 0 16px;
-          :deep(.el-input__wrapper) {
-            height: 32px;
-          }
-        }
-        .actions {
-          display: flex;
-          align-items: center;
-          justify-content: space-between;
-          width: 240px;
-          padding-left: 30px;
-          .checkbox-group {
-            display: flex;
-          }
-          .el-checkbox {
-            margin-right: 16px;
-            :deep(.el-checkbox__inner) {
-              height: 16px;
-              width: 16px;
-              &::after {
-                border-width: 2px;
-                height: 9px;
-                width: 5px;
-                left: 4px;
-              }
-            }
-            :deep(.el-checkbox__label) {
-              margin-top: 3px;
-              padding-left: 4px;
-              line-height: 2;
-            }
-          }
-          .font_family {
-            float: right;
-            cursor: pointer;
-          }
-        }
-      }
-    }
-  }
-  .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;
-  }
-}
 .basic-info {
   .info-item {
     &:first-child {
@@ -817,9 +455,6 @@ const handlePageSave = () => {
         }
       }
     }
-    // .radio-item.specific-roles {
-    //   padding: 0;
-    // }
   }
 }
 
@@ -834,37 +469,3 @@ const handlePageSave = () => {
   }
 }
 </style>
-<style lang="scss">
-.add-new-field-dialog {
-  .field-item {
-    margin-bottom: 16px;
-    .label {
-      margin-bottom: 4px;
-    }
-    &:last-child {
-      margin-bottom: 0;
-    }
-  }
-  .field-value {
-    .el-radio-group {
-      width: 100%;
-      .el-radio {
-        flex: 1;
-        margin-right: 0;
-        padding-left: 12px;
-        border: 1px solid var(--color-border);
-        &:first-child {
-          border-radius: 6px 0 0 6px;
-          border-right: none;
-        }
-        &:last-child {
-          border-radius: 0 6px 6px 0;
-        }
-        .el-radio__label {
-          color: var(--color-neutral-1);
-        }
-      }
-    }
-  }
-}
-</style>

+ 4 - 1
src/views/TemplateManagement/src/components/CreateReportTemplate/src/components/AccountSelect.vue

@@ -34,7 +34,9 @@ interface ListItem {
 const options = ref<ListItem[]>([])
 const loading = ref(false)
 const currentController = ref<AbortController | null>(null)
-
+const handleVisibleChange = (visible) => {
+  !visible && (options.value = [])
+}
 const remoteMethod = (query: string) => {
   currentController.value?.abort()
 
@@ -89,6 +91,7 @@ onUnmounted(() => {
     popper-class="part-id-select-popper"
     :filter-method="debouncedRemoteMethod"
     @change="changeData"
+    @visible-change="handleVisibleChange"
   >
     <el-option v-for="item in options" :key="item.id" :label="item.label" :value="item.label">
       <div class="select-option">

+ 8 - 4
src/views/TemplateManagement/src/components/CreateReportTemplate/src/components/AdjustmentField.vue

@@ -102,6 +102,9 @@ const scrollToItem = (itemId: string) => {
       // container.scrollTop = targetElement.offsetTop - container.offsetTop
 
       document.addEventListener('click', handleDocumentClick)
+      setTimeout(() => {
+        searchColumn.value = ''
+      }, 600)
     }
   }, 100)
 }
@@ -137,8 +140,8 @@ const loading = ref(false)
 // 获取数据
 const getData = async (selectedList?: Array<any>) => {
   loading.value = true
-  let paramsData: any = { ...params.value }
-
+  let defaultValue = selectedList?.length && selectedList.length !== 0 ? false : true
+  let paramsData: any = { ...params.value, default: defaultValue }
   await $api.getReportFieldsConfiguration(paramsData).then((res: any) => {
     if (res.code === 200) {
       // allDataCopy就是所有的数据
@@ -150,6 +153,7 @@ const getData = async (selectedList?: Array<any>) => {
       // 右侧 当没有初始值时才需要从接口获取数据
       if (selectedList?.length && selectedList.length !== 0) {
         const newArr = selectedList.map((item: any) => {
+          handleAddSelect(item, false)
           return {
             ...item,
             label: item.title
@@ -206,7 +210,7 @@ const hoverAllIcon = ref('')
 // 右侧Icon的显隐
 const hoverSelectIcon = ref('')
 
-const handleAddSelect = (item: any) => {
+const handleAddSelect = (item: any, isNeedAdd = true) => {
   groupColumns.value.forEach((groupItem: any) => {
     groupItem.children.forEach((child: any, index: number) => {
       if (child.field === item.field) {
@@ -214,7 +218,7 @@ const handleAddSelect = (item: any) => {
       }
     })
   })
-  selectColumns.value.push(item)
+  isNeedAdd && selectColumns.value.push(item)
 }
 
 // 从左侧拖拽到右侧时,删除其他分组中相同的数据

+ 4 - 8
src/views/TemplateManagement/src/components/CreateReportTemplate/src/components/PartyIDSelect.vue

@@ -35,6 +35,9 @@ const options = ref<ListItem[]>([])
 const loading = ref(false)
 const currentController = ref<AbortController | null>(null)
 
+const handleVisibleChange = (visible) => {
+  !visible && (options.value = [])
+}
 const remoteMethod = (query: string) => {
   currentController.value?.abort()
 
@@ -89,6 +92,7 @@ onUnmounted(() => {
     popper-class="part-id-select-popper"
     :filter-method="debouncedRemoteMethod"
     @change="changeData"
+    @visible-change="handleVisibleChange"
   >
     <el-option
       v-for="item in options"
@@ -139,11 +143,3 @@ onUnmounted(() => {
   overflow: hidden;
 }
 </style>
-
-<style lang="scss">
-.part-id-select-popper {
-  // width: 100% !important;
-  // width: auto;
-  // min-width: unset !important;
-}
-</style>

+ 733 - 0
src/views/TemplateManagement/src/components/CreateReportTemplate/src/components/ReportFieldsConfiguration.vue

@@ -0,0 +1,733 @@
+<script setup lang="ts">
+import AdjustmentField from './AdjustmentField.vue'
+import { VueDraggable } from 'vue-draggable-plus'
+import { cloneDeep } from 'lodash'
+
+const props = defineProps<{
+  reportLevel: string
+  pageData
+}>()
+
+interface Field {
+  uniqueId: string
+  field: string
+  title: string
+  displayName: string
+  fieldType: string
+  value?: string
+  isFilter: boolean
+  fieldLevel?: string
+  isSort: boolean
+  mapping?: { system: string; converted: string }[]
+  groupName: string
+  isFieldDataMapping?: string
+}
+const fieldsList = ref<Field[]>([])
+const addNewFieldVisible = ref(false)
+const fieldLoading = ref(false)
+
+const newFieldInfo = ref<{
+  name: string
+  fieldType: 'Blank' | 'Fixed Value'
+  value: string
+}>({
+  name: '',
+  fieldType: 'Blank',
+  value: ''
+})
+
+watch(
+  () => props.pageData,
+  (newVal) => {
+    if (newVal && Array.isArray(newVal)) {
+      fieldsList.value = cloneDeep(newVal)
+    } else {
+      fieldsList.value = []
+    }
+  },
+  { immediate: true, deep: true }
+)
+
+const emit = defineEmits<{
+  (e: 'cancelChangeLevel', value: string): void
+  (e: 'confirmChangeLevel', value: string): void
+}>()
+
+const changeReportLevel = (newVal: string, oldVal: string) => {
+  // 定义每种切换场景的配置
+  const scenarios: Record<string, Record<string, { message: string; levelsToClear: string[] }>> = {
+    'Item Level': {
+      'Container Level': {
+        message: 'If the Level value is "Item Level", the field will be cleared.',
+        levelsToClear: ['Item Level']
+      },
+      'Shipment Level': {
+        message:
+          'If the Level value is "Item Level" or "Container Level", the field will be cleared.',
+        levelsToClear: ['Item Level', 'Container Level']
+      }
+    },
+    'Container Level': {
+      'Shipment Level': {
+        message: 'If the Level value is "Container Level", the field will be cleared.',
+        levelsToClear: ['Container Level']
+      }
+    }
+  }
+
+  const scenario = scenarios[oldVal]?.[newVal]
+
+  if (!scenario) {
+    // 无特殊清理逻辑,直接确认变更
+    emit('confirmChangeLevel', newVal)
+    return
+  }
+
+  ElMessageBox.confirm(scenario.message, 'Prompt', {
+    confirmButtonText: 'Change',
+    cancelButtonText: 'Cancel',
+    type: 'warning',
+    confirmButtonClass: 'el-button--dark',
+    cancelButtonClass: 'el-button--default',
+    distinguishCancelAndClose: true
+  })
+    .then(() => {
+      // 用户确认:过滤字段并提交新值
+      fieldsList.value = fieldsList.value.filter(
+        (item) => !scenario.levelsToClear.includes(item.fieldLevel)
+      )
+      emit('confirmChangeLevel', newVal)
+    })
+    .catch(() => {
+      // 用户取消:回滚到旧值
+      emit('cancelChangeLevel', oldVal)
+    })
+}
+
+const handleAddNewField = () => {
+  addNewFieldVisible.value = true
+}
+const handleFieldTypeChange = () => {
+  newFieldInfo.value.value = ''
+}
+const addNewField = () => {
+  if (!newFieldInfo.value.name?.trim()) {
+    ElMessage.warning('Please enter the new field name.')
+    return
+  }
+
+  if (newFieldInfo.value.fieldType === 'Fixed Value' && !newFieldInfo.value.value?.trim()) {
+    ElMessage.warning('Please enter the fixed value.')
+    return
+  }
+  fieldsList.value.unshift({
+    uniqueId: generate8DigitUnique(),
+    field: newFieldInfo.value.name,
+    title: newFieldInfo.value.name,
+    displayName: newFieldInfo.value.name,
+    value: newFieldInfo.value.value,
+    fieldType: 'Custom',
+    groupName: '',
+    isFilter: false,
+    isSort: false
+  })
+  clearNewFieldData()
+  addNewFieldVisible.value = false
+}
+const clearNewFieldData = () => {
+  newFieldInfo.value = {
+    name: '',
+    fieldType: 'Blank',
+    value: ''
+  }
+}
+
+const changeFieldConfig = (state: any, field: string, index: number, key: string) => {
+  if (!state) return
+  fieldsList.value.forEach((item) => {
+    if (item.field === field) {
+      item[key] = false
+    }
+  })
+  const firstMatch = fieldsList.value[index]
+  if (firstMatch) {
+    firstMatch[key] = true
+  }
+}
+const AdjustmentFieldRef = ref()
+// 打开定制表格弹窗
+const handleCustomizeColumns = () => {
+  if (!props.reportLevel) {
+    ElMessage.warning('Please select the report level.')
+    return
+  }
+  const params = {
+    serial_no: '',
+    level: props.reportLevel
+  }
+  const seen = new Set()
+  const uniqueArray = fieldsList.value.filter((item) => {
+    if (seen.has(item.field)) {
+      return false // 已存在,跳过
+    }
+    seen.add(item.field)
+    return true // 第一次出现,保留
+  })
+
+  AdjustmentFieldRef.value.openDialog(
+    params,
+    -220,
+    'Drag item over to this selection or click "add" icon to show the field on report template list',
+    uniqueArray
+  )
+}
+
+const fieldMappingVisible = ref(false)
+const curFieldItem = ref()
+const mappingData = ref([
+  {
+    system: '',
+    converted: ''
+  }
+])
+const copyFieldsList = ref({})
+
+const handleMappingField = (index: number) => {
+  curFieldItem.value = fieldsList.value[index]
+  mappingData.value =
+    curFieldItem.value.mapping && curFieldItem.value.mapping.length
+      ? cloneDeep(curFieldItem.value.mapping)
+      : [
+          {
+            system: '',
+            converted: ''
+          }
+        ]
+  fieldMappingVisible.value = true
+}
+const handleDeleteField = (index: number, uniqueId: string) => {
+  fieldsList.value.splice(index, 1)
+
+  Object.keys(copyFieldsList.value).forEach((key) => {
+    copyFieldsList.value[key] = copyFieldsList.value[key].filter(
+      (item) => item.uniqueId !== uniqueId
+    )
+  })
+}
+
+// 前端使用的唯一标识符
+const generate8DigitUnique = () => {
+  return Math.floor(10000000 + Math.random() * 90000000).toString()
+}
+const handleCopyField = (index: number) => {
+  const curField = cloneDeep(fieldsList.value[index])
+  curField.displayName = `${curField.displayName} (Copy)`
+  curField.isFilter = false
+  curField.isSort = false
+  curField.uniqueId = generate8DigitUnique()
+  if (copyFieldsList.value[curField.field]) {
+    copyFieldsList.value[curField.field].push(cloneDeep(curField))
+  } else {
+    copyFieldsList.value[curField.field] = [cloneDeep(curField)]
+  }
+  fieldsList.value.splice(index + 1, 0, cloneDeep(curField))
+}
+
+const handleAddMapping = () => {
+  curFieldItem.value.mapping = mappingData.value
+  clearMappingData()
+  fieldMappingVisible.value = false
+}
+const handleAddMappingItem = () => {
+  mappingData.value.push({
+    system: '',
+    converted: ''
+  })
+}
+const clearMappingData = () => {
+  mappingData.value = [
+    {
+      system: '',
+      converted: ''
+    }
+  ]
+}
+
+// 调整应用字段
+const handleApplay = (data: any) => {
+  const customizeData = fieldsList.value.filter((item: any) => item.field_type === 'Custom')
+  fieldsList.value = data.map((item: any) => {
+    return {
+      ...item,
+      label: item.label,
+      title: item.title || item.label,
+      displayName: item.displayName || item.label,
+      isFilter: !!item.isFilter,
+      isSort: !!item.isSort,
+      uniqueId: item.uniqueId || generate8DigitUnique()
+    }
+  })
+  fieldsList.value = [...customizeData, ...fieldsList.value]
+
+  const validFields = new Set(fieldsList.value.map((item) => item.field))
+  for (const field in copyFieldsList.value) {
+    if (!validFields.has(field)) {
+      delete copyFieldsList.value[field]
+    }
+  }
+
+  // === 第三步:从后往前插入副本(不修改原始项)===
+  for (let i = fieldsList.value.length - 1; i >= 0; i--) {
+    const field = fieldsList.value[i].field
+    const copies = copyFieldsList.value[field]
+
+    if (copies?.length) {
+      // 插入副本到原项后面
+      fieldsList.value.splice(i + 1, 0, ...copies)
+    }
+  }
+}
+
+const handleDeleteMappingField = (index: number) => {
+  mappingData.value.splice(index, 1)
+}
+const getFieldsList = () => {
+  return fieldsList.value || []
+}
+
+defineExpose({
+  getFieldsList,
+  changeReportLevel
+})
+</script>
+
+<template>
+  <div class="fields-configuration template-box">
+    <div class="header">
+      <span>Report Fields Configuration</span>
+
+      <div class="right-option">
+        <el-button
+          class="el-button--dark"
+          @click="handleAddNewField"
+          style="width: 148px; padding-top: 11px"
+        >
+          <span style="margin-right: 3px" class="font_family icon-icon_add_b"></span>
+          <span>Add New Field</span>
+        </el-button>
+        <el-button
+          v-if="fieldsList.length > 0"
+          class="el-button--dark"
+          @click="handleCustomizeColumns()"
+          style="width: 110px; padding-top: 11px"
+        >
+          <span style="margin-right: 3px" class="font_family icon-icon_add_b"></span>
+          <span>Select Field</span>
+        </el-button>
+      </div>
+    </div>
+    <div class="content-box">
+      <div class="empty-box" v-if="fieldsList.length === 0">
+        <el-button class="el-button--dark" @click="handleCustomizeColumns">
+          <span class="font_family icon-icon_add_b"></span>Add/Edit Field
+        </el-button>
+        <p>No field selected. click “Add Field” to get started.</p>
+      </div>
+      <div class="fields-list" v-else>
+        <VueDraggable
+          v-vloading="fieldLoading"
+          v-model="fieldsList"
+          class="column-list"
+          ghost-class="ghost-column"
+          :forceFallback="true"
+          fallback-class="fallback-class"
+          group="customizeColumns"
+          item-key="uniqueId"
+          handle=".handle-draggable"
+        >
+          <div
+            class="field-item"
+            v-for="(fieldItem, index) in fieldsList"
+            :key="fieldItem.uniqueId"
+          >
+            <span
+              class="font_family icon-icon_dragsort__b draggable-icon handle-draggable"
+              style="margin-right: 12px; font-size: 16px"
+            ></span>
+            <div class="label handle-draggable" style="line-height: 2.7">
+              <span style="font-weight: 700">[{{ fieldItem.field }}]</span>
+              <span style="margin-left: 8px">{{ fieldItem.title }}</span>
+            </div>
+            <el-input
+              :id="fieldItem.uniqueId"
+              :name="fieldItem.uniqueId"
+              class="display-name"
+              v-model="fieldItem.displayName"
+              placeholder="Display Name in Report"
+            ></el-input>
+            <div class="actions">
+              <div class="checkbox-group">
+                <el-checkbox
+                  :disabled="
+                    fieldItem.fieldType !== 'System' ||
+                    fieldItem.groupName === 'Container Status' ||
+                    fieldItem.groupName === 'Milestone'
+                  "
+                  v-model="fieldItem.isFilter"
+                  @change="changeFieldConfig($event, fieldItem.field, index, 'isFilter')"
+                  >Filter</el-checkbox
+                >
+                <el-checkbox
+                  :disabled="fieldItem.fieldType !== 'System'"
+                  v-model="fieldItem.isSort"
+                  @change="changeFieldConfig($event, fieldItem.field, index, 'isSort')"
+                  >Sort</el-checkbox
+                >
+              </div>
+              <div class="action-icon">
+                <span
+                  style="padding: 5px"
+                  v-if="fieldItem.fieldType === 'System' && fieldItem.isFieldDataMapping === 't'"
+                  @click="handleMappingField(index)"
+                  class="font_family icon-icon_convert_b"
+                ></span>
+                <span v-else style="width: 20px"></span>
+                <span
+                  style="padding: 5px"
+                  @click="handleCopyField(index)"
+                  class="font_family icon-icon_clone_b"
+                ></span>
+                <span
+                  style="padding: 5px 7px"
+                  @click="handleDeleteField(index, fieldItem.uniqueId)"
+                  class="font_family icon-icon_delete_b"
+                ></span>
+              </div>
+            </div>
+          </div>
+        </VueDraggable>
+      </div>
+    </div>
+  </div>
+
+  <AdjustmentField @apply="handleApplay" ref="AdjustmentFieldRef" />
+  <el-dialog
+    class="add-new-field-dialog"
+    title="Add New Field"
+    v-model="addNewFieldVisible"
+    @closed="clearNewFieldData"
+    width="480"
+  >
+    <div>
+      <div class="field-item">
+        <div class="label">
+          <span class="required-symbol">*</span>
+          <span>New Field Name</span>
+        </div>
+        <el-input placeholder="Please enter..." v-model="newFieldInfo.name"></el-input>
+      </div>
+      <div class="field-item field-value">
+        <div class="label">
+          <span class="required-symbol">*</span>
+          <span>Field Value</span>
+        </div>
+        <el-radio-group v-model="newFieldInfo.fieldType" @change="handleFieldTypeChange">
+          <el-radio label="Blank">Blank</el-radio>
+          <el-radio label="Fixed Value">Fixed Value</el-radio>
+        </el-radio-group>
+      </div>
+      <div class="field-item" v-if="newFieldInfo.fieldType === 'Fixed Value'">
+        <div class="label">
+          <span class="required-symbol">*</span>
+          <span>Fixed Value</span>
+        </div>
+        <el-input placeholder="Please enter..." v-model="newFieldInfo.value"></el-input>
+      </div>
+    </div>
+    <template #footer>
+      <el-button
+        style="height: 40px; width: 115px"
+        class="cancel-btn"
+        type="default"
+        @click="addNewFieldVisible = false"
+        >Cancel</el-button
+      >
+      <el-button style="height: 40px; width: 120px" class="el-button--dark" @click="addNewField"
+        >Apply</el-button
+      >
+    </template>
+  </el-dialog>
+  <el-dialog
+    class="field-mapping-dialog"
+    v-model="fieldMappingVisible"
+    title="Mapping (Field: Final Destination)"
+    width="800"
+    @close="clearMappingData"
+  >
+    <div>
+      <div class="mapping-list">
+        <div class="label">
+          <div class="left-label">
+            <span style="color: var(--color-danger)">*</span>
+            <span>System Value</span>
+          </div>
+          <div class="right-label">
+            <span style="color: var(--color-danger)">*</span>
+            <span>Output Value</span>
+          </div>
+        </div>
+        <div style="max-height: 380px; overflow-y: auto">
+          <div class="list-item" v-for="(item, index) in mappingData" :key="index">
+            <div class="left-system-value">
+              <el-input placeholder="Please enter..." clearable v-model="item.system"></el-input>
+            </div>
+            <div class="right-converted-value">
+              <el-input placeholder="Please enter..." clearable v-model="item.converted"></el-input>
+            </div>
+            <div class="delete-icon">
+              <span
+                @click="handleDeleteMappingField(index)"
+                class="font_family icon-icon_delete_b"
+              ></span>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <el-button
+      class="el-button--text"
+      @click="handleAddMappingItem"
+      style="height: 32px; margin-top: 8px; padding: 8px"
+    >
+      <span class="font_family icon-icon_add_b" style="color: var(--color-theme)"></span>
+      <span style="color: var(--color-theme)">Add Mapping</span>
+    </el-button>
+    <template #footer>
+      <el-button
+        style="height: 40px; width: 115px"
+        class="cancel-btn"
+        type="default"
+        @click="fieldMappingVisible = false"
+        >Cancel</el-button
+      >
+      <el-button
+        style="height: 40px; width: 120px"
+        class="el-button--dark"
+        @click="handleAddMapping"
+        >Apply</el-button
+      >
+    </template>
+  </el-dialog>
+</template>
+
+<style lang="scss" scoped>
+.template-box {
+  margin-bottom: 8px;
+  border: 1px solid var(--color-border);
+  border-radius: 12px;
+  overflow: hidden;
+  .header {
+    height: 48px;
+    border-bottom: 1px solid var(--color-border);
+    display: flex;
+    align-items: center;
+    padding: 0 16px;
+    border-radius: 12px 12px 0 0;
+    background-color: var(--color-header-bg);
+    font-size: 18px;
+    font-weight: bold;
+  }
+  .right-option {
+    margin-left: auto;
+  }
+  .content-box {
+    height: 100%;
+    padding: 8px 16px 16px;
+  }
+}
+.fields-configuration {
+  div.content-box {
+    display: flex;
+    align-items: flex-start;
+    justify-content: center;
+    min-height: 272px;
+    max-height: 400px;
+    width: 100%;
+    padding-bottom: 8px;
+    padding-right: 0px;
+    // overflow: auto;
+    .empty-box {
+      align-self: center;
+      width: 100%;
+      text-align: center;
+      p {
+        margin-top: 12px;
+        color: var(--color-neutral-2);
+      }
+    }
+    .fields-list {
+      width: 100%;
+      max-height: 400px;
+      padding: 8px 0;
+      padding-right: 16px;
+      overflow: auto;
+      user-select: none;
+      .field-item {
+        display: flex;
+        align-items: center;
+        height: 48px;
+        margin-bottom: 8px;
+        padding-left: 16px;
+        border-radius: 6px;
+        border: 1px solid var(--color-border);
+        .label {
+          flex: 1;
+          .required-symbol {
+            color: var(--color-danger);
+          }
+        }
+        .display-name {
+          flex: 1.2;
+          margin: 0 16px;
+          :deep(.el-input__wrapper) {
+            height: 32px;
+          }
+        }
+        .actions {
+          display: flex;
+          align-items: center;
+          justify-content: space-between;
+          width: 303px;
+          padding-left: 30px;
+          .checkbox-group {
+            display: flex;
+          }
+          .el-checkbox {
+            margin-right: 16px;
+            :deep(.el-checkbox__inner) {
+              height: 16px;
+              width: 16px;
+              &::after {
+                border-width: 2px;
+                height: 9px;
+                width: 5px;
+                left: 4px;
+              }
+            }
+            :deep(.el-checkbox__label) {
+              margin-top: 3px;
+              padding-left: 4px;
+              line-height: 2;
+            }
+          }
+          .font_family {
+            float: right;
+            cursor: pointer;
+          }
+          .action-icon {
+            width: 144px;
+            display: flex;
+            justify-content: space-around;
+          }
+        }
+      }
+    }
+  }
+  .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>
+<style lang="scss">
+.add-new-field-dialog {
+  .field-item {
+    margin-bottom: 16px;
+    .label {
+      margin-bottom: 4px;
+    }
+    &:last-child {
+      margin-bottom: 0;
+    }
+  }
+  .field-value {
+    .el-radio-group {
+      width: 100%;
+      .el-radio {
+        flex: 1;
+        margin-right: 0;
+        padding-left: 12px;
+        border: 1px solid var(--color-border);
+        &:first-child {
+          border-radius: 6px 0 0 6px;
+          border-right: none;
+        }
+        &:last-child {
+          border-radius: 0 6px 6px 0;
+        }
+        .el-radio__label {
+          color: var(--color-neutral-1);
+        }
+      }
+    }
+  }
+}
+.field-mapping-dialog {
+  .mapping-list {
+    border-radius: 6px;
+    overflow: hidden;
+    .label {
+      display: flex;
+      height: 24px;
+      width: 768px;
+      background-color: var(--color-header-bg);
+      .left-label {
+        width: 364px;
+        padding: 4px 8px;
+      }
+      .right-label {
+        width: 404px;
+        padding: 4px 8px;
+      }
+    }
+    .list-item {
+      display: flex;
+      height: 48px;
+      border: 1px solid var(--color-border);
+      overflow: hidden;
+
+      .left-system-value,
+      .right-converted-value {
+        flex: 1;
+        padding: 8px;
+      }
+      .left-system-value {
+        border-right: 1px solid var(--color-border);
+      }
+      .delete-icon {
+        width: 48px;
+        text-align: center;
+        line-height: 48px;
+      }
+      &:nth-child(n + 3) {
+        border-top: none;
+      }
+
+      &:last-child {
+        border-radius: 0 0 6px 6px;
+      }
+    }
+  }
+}
+</style>

+ 16 - 6
src/views/TemplateManagement/src/components/TableView/src/TableView.vue

@@ -5,7 +5,7 @@ import { useRowClickStyle } from '@/hooks/rowClickStyle'
 import dayjs from 'dayjs'
 import utc from 'dayjs/plugin/utc'
 import timezone from 'dayjs/plugin/timezone'
-import { formatNumber } from '@/utils/tools'
+import { formatNumber, formatTimezoneByUser } from '@/utils/tools'
 import { useRouter } from 'vue-router'
 import { useUserStore } from '@/stores/modules/user'
 
@@ -24,7 +24,6 @@ const formatString = computed(() => {
   return userStore.dateFormat || 'MM/DD/YYYY'
 })
 
-const tableOriginColumnsField = ref()
 const tableColumns = [
   {
     title: 'Report Name',
@@ -52,8 +51,20 @@ const tableColumns = [
     field: 'created_time',
     formatter: 'dateTime',
     sortable: true
+  },
+  {
+    title: 'Created By',
+    type: 'normal',
+    field: 'create_by'
+  },
+  {
+    title: 'Modify By',
+    type: 'normal',
+    field: 'modify_by'
   }
 ]
+const tableOriginColumnsField = ref([...tableColumns])
+
 const handleColumns = (columns: any) => {
   const newColumns = columns.map((item: any) => {
     let curColumn: any = {
@@ -84,11 +95,10 @@ const handleColumns = (columns: any) => {
       }
     }
     // 格式化
-    if (item.formatter === 'date' || item.formatter === 'dateTime') {
+    if (item.formatter === 'dateTime') {
       curColumn = {
         ...curColumn,
-        formatter: ({ cellValue }: any) =>
-          (dayjs as any).tz(cellValue, 'US/Pacific').format(formatString.value + ' HH:mm')
+        formatter: ({ cellValue }: any) => formatTimezoneByUser(cellValue, 'YYYY-MM-DD HH:mm', true)
       }
     }
     return curColumn
@@ -130,7 +140,7 @@ const assignTableData = (data: any) => {
     return item.title === 'Action'
   })
   actionColumn.width = isShowDeleteBtn.value ? 150 : 120
-  tableRef.value.loadColumn(tableData.value.columns)
+  tableRef.value.loadColumn(tableData.value.columns || [])
 }
 
 let searchdata: any = {}

+ 4 - 2
src/views/Tracking/src/components/TrackingDetail/src/components/EmailDrawer.vue

@@ -2,7 +2,7 @@
 import '@wangeditor/editor/dist/css/style.css'
 import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
 import { i18nChangeLanguage, DomEditor } from '@wangeditor/editor'
-import { formatTimezone } from '@/utils/tools'
+import { formatTimezoneByUser } from '@/utils/tools'
 
 i18nChangeLanguage('en')
 
@@ -228,7 +228,9 @@ const sendEmail = () => {
             <div>{{ item.name?.slice(0, 1) }}</div>
           </div>
           <div class="name">{{ item.name }}</div>
-          <div class="date">{{ formatTimezone(item.creatTime) }}</div>
+          <div class="date">
+            {{ formatTimezoneByUser(item.creatTime, 'MM/DD/YYYY HH:mm:ss', true) }}
+          </div>
         </div>
         <div class="content">
           {{ item.content }}

+ 6 - 6
src/views/Tracking/src/components/TrackingDetail/src/components/UploadFilesDialog.vue

@@ -122,13 +122,13 @@ const clearData = () => {
 const beforeAvatarUpload = (rawFile: any) => {
   if (
     ![
-      'application/pdf'
-      // 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
-      // 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
+      'application/pdf',
+      'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+      'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
     ].includes(rawFile.type)
   ) {
     // , DOCX, and XLSX
-    ElMessage.error('The file types allowed for upload are: PDF.')
+    ElMessage.error('The file types allowed for upload are: PDF, XLSX and DOCX.')
     return false
   } else if (rawFile.size / 1024 / 1024 > 5) {
     ElMessage.error('File size must not exceed 5MB!')
@@ -165,7 +165,7 @@ const disableUpload = ref(false)
         ref="uploadRef"
         drag
         :limit="5"
-        :accept="'application/pdf'"
+        :accept="'.pdf,.xlsx,.docx'"
         :show-file-list="false"
         :action="url"
         :auto-upload="false"
@@ -183,7 +183,7 @@ const disableUpload = ref(false)
           <div class="label">
             <span class="font_family icon-icon_info_b" style="vertical-align: baseline"></span>
             <!-- , docx, xlsx  -->
-            <span>Supported formats: pdf ; </span>
+            <span>Supported formats: .pdf, .xlsx, .docx </span>
           </div>
           <span>Maximum Size: 5MB; </span>
           <span>Maximum Number: 5 files</span>

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio