Przeglądaj źródła

feat: 联调report剩余相关接口,修改report detail网络竞态处理问题

Jack Zhou 1 miesiąc temu
rodzic
commit
0d9c201f79
20 zmienionych plików z 329 dodań i 148 usunięć
  1. 18 3
      src/api/module/report.ts
  2. 1 1
      src/stores/modules/breadCrumb.ts
  3. 5 5
      src/styles/elementui.scss
  4. 6 1
      src/styles/theme.scss
  5. 10 2
      src/utils/axios.ts
  6. 1 1
      src/views/Report/src/ReportView.vue
  7. 12 1
      src/views/Report/src/components/ReportDetail/src/ReportDetail.vue
  8. 49 10
      src/views/Report/src/components/ReportDetail/src/components/FieldsTable.vue
  9. 0 2
      src/views/Report/src/components/ReportDetail/src/components/ManageReportFields.vue
  10. 67 50
      src/views/Report/src/components/ReportSchedule/src/ReportSchedule.vue
  11. 7 7
      src/views/Report/src/components/ReportSchedule/src/components/EmailConfiguration.vue
  12. 19 6
      src/views/Report/src/components/ReportSchedule/src/components/FieldsTable.vue
  13. 0 2
      src/views/Report/src/components/ReportSchedule/src/components/ManageReportFields.vue
  14. 19 3
      src/views/Report/src/components/ReportSchedule/src/components/TimeRange.vue
  15. 23 2
      src/views/Report/src/components/ReportSchedule/src/components/ValidityPeriod.vue
  16. 9 10
      src/views/TemplateManagement/src/TemplateManagement.vue
  17. 10 11
      src/views/TemplateManagement/src/components/CreateReportTemplate/src/CreateReportTemplate.vue
  18. 31 9
      src/views/TemplateManagement/src/components/CreateReportTemplate/src/components/GroupNameSelect.vue
  19. 30 8
      src/views/TemplateManagement/src/components/CreateReportTemplate/src/components/PartyIDSelect.vue
  20. 12 14
      src/views/TemplateManagement/src/components/TableView/src/TableView.vue

+ 18 - 3
src/api/module/report.ts

@@ -3,6 +3,21 @@ import HttpAxios from '@/utils/axios'
 const base = import.meta.env.VITE_API_HOST
 const baseUrl = `${base}/main_new_version.php`
 
+/**
+ * 获取report template management页面筛选项Party ID下拉框数据
+ */
+export const getFilterPartyID = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'report_config',
+      operate: 'parity_id',
+      ...params
+    },
+    config
+  )
+}
+
 /**
  * 获取report template management表格列数据
  */
@@ -144,9 +159,9 @@ export const getReportDetailTable = (params: any, config: any) => {
 }
 
 /**
- * 导出Report detail页表格数据
+ * 获取导出Report detail页表格数据
  */
-export const exportReportDetailTable = (params: any, config: any) => {
+export const getReportAllTableData = (params: any, config: any) => {
   return HttpAxios.post(
     `${baseUrl}`,
     {
@@ -211,7 +226,7 @@ export const saveReportScheduleData = (params: any, config: any) => {
     `${baseUrl}`,
     {
       action: 'shipment_status_report',
-      operate: 'report_schedule',
+      operate: 'report_schedule_save',
       ...params
     },
     config

+ 1 - 1
src/stores/modules/breadCrumb.ts

@@ -163,7 +163,7 @@ export const useBreadCrumb = defineStore('breadCrumb', {
             query: ''
           },
           {
-            label: 'Schedule Condiguration',
+            label: 'Schedule Configuration',
             path: '/report/detail',
             query: toRoute.query
           }

+ 5 - 5
src/styles/elementui.scss

@@ -638,12 +638,12 @@ div .el-range-editor.is-active,
 .el-range-editor.is-active:hover,
 .el-date-editor.el-input__wrapper:hover,
 .el-date-editor.el-input__wrapper:hover {
-  box-shadow: 0 0 0 1px var(--color-theme) !important;
+  box-shadow: 0 0 0 1px var(--color-theme) inset !important;
   border-color: var(--color-theme);
 }
 
-table.el-date-table td.available:hover {
-  span {
+table.el-date-table td.available:not(.start-date):not(.end-date):hover {
+   span {
     color: var(--color-theme);
   }
 }
@@ -664,11 +664,11 @@ div .el-date-table td.end-date .el-date-table-cell__text,
   color: #ffffff;
 }
 div .el-date-table td.available:hover {
-  color: var(--color-theme);
+  color: var(--color-neutral-1);
 }
 div .el-date-editor .el-range-input,
 div .el-date-editor .el-range-separator {
-  color: var(--color-btn-default-dark-bg);
+  // color: var(--color-btn-default-dark-bg);
   font-size: var(--font-size-3);
 }
 div .el-range-editor.el-input__wrapper {

+ 6 - 1
src/styles/theme.scss

@@ -254,7 +254,9 @@
   .el-input {
     --el-border: #eaebed;
   }
-
+ .el-date-editor {
+    --el-input-border-color: #eaebed;
+  }
   --color-vxe-table-visited-row-bg: #f2f2f2;
 
   --color-public-tracking-empty-bg: #fff;
@@ -489,6 +491,9 @@
   .el-input {
     --el-border: #656f7d;
   }
+  .el-date-editor {
+    --el-input-border-color: #656f7d;
+  }
   .el-radio {
     --el-radio-input-border: #656f7d;
   }

+ 10 - 2
src/utils/axios.ts

@@ -76,24 +76,32 @@ class HttpAxios {
   }
 
   _checkResponseError = (error: any) => {
+    // ✅ 第一步:如果是用户取消的请求,静默处理 or 特殊处理
+    if (axios.isCancel(error)) {
+      return Promise.reject(error) // 通常仍 reject,但上层可选择忽略
+    }
+
+    // ✅ 第二步:超时错误(ECONNABORTED)
     if (error.code === 'ECONNABORTED') {
       ElMessage.error({
-        message: 'Request timed out, please try again later!!',
+        message: 'Request timed out, please try again later!',
         grouping: true
       })
       return Promise.reject(error)
     }
 
+    // ✅ 第三步:其他真实错误
     const status = error.response?.status
     const statusText = error.response?.statusText
     const message = error.message
+
     ElMessage.error({
       message: CODE_MESSAGE[status] || statusText || message,
       grouping: true
     })
+
     return Promise.reject(error)
   }
-
   sendRequest = (url: string, params: any, method = 'post', config?: AxiosRequestConfig) => {
     if (!this.instance) return
 

+ 1 - 1
src/views/Report/src/ReportView.vue

@@ -12,7 +12,7 @@ const textSearch = ref('')
 const SearchInput = () => {}
 
 const filterRef: Ref<HTMLElement | null> = ref(null)
-const containerHeight = useCalculatingHeight(document.documentElement, 266, [filterRef])
+const containerHeight = useCalculatingHeight(document.documentElement, 256, [filterRef])
 
 const tableData = ref<VxeGridProps<any>>({
   border: true,

+ 12 - 1
src/views/Report/src/components/ReportDetail/src/ReportDetail.vue

@@ -151,6 +151,17 @@ const applyNewColumn = () => {
           </div>
           <div v-if="item.type === 'date'" style="display: flex; gap: 4px; align-items: center">
             <el-date-picker
+              v-model="item.value"
+              type="daterange"
+              range-separator="To"
+              start-placeholder="Start date"
+              end-placeholder="End date"
+              style="height: 32px"
+              value-format="MM/DD/YYYY"
+              :format="formatDate"
+            />
+
+            <!-- <el-date-picker
               v-model="item.value[0]"
               type="date"
               placeholder="Pick a Date"
@@ -164,7 +175,7 @@ const applyNewColumn = () => {
               placeholder="Pick a Date"
               :format="formatDate"
               valueFormat="MM/DD/YYYY"
-            />
+            /> -->
           </div>
         </div>
       </div>

+ 49 - 10
src/views/Report/src/components/ReportDetail/src/components/FieldsTable.vue

@@ -53,6 +53,20 @@ const tableData = ref<VxeGridProps<any>>({
   }
 })
 
+const allTableRef = ref<VxeGridInstance>()
+const allTable = ref<VxeGridProps<any>>({
+  columns: [],
+  data: [],
+  showHeaderOverflow: true,
+  showOverflow: true,
+  scrollY: { enabled: true, oSize: 5, gt: 2 },
+  scrollX: { enabled: true, gt: 2 },
+  exportConfig: {
+    types: ['csv', 'html', 'txt', 'xlsx'],
+    modes: ['current', 'selected', 'all']
+  }
+})
+
 const tableRef = ref<VxeGridInstance | null>(null)
 const pageInfo = ref({ pageNo: 1, pageSize: 50, total: 0 })
 
@@ -104,6 +118,9 @@ const getTableData = (isPageChange?: boolean) => {
         pageInfo.value.pageSize = tableDataValue.ps
         tableData.value.data = tableDataValue.searchData
         tableData.value.columns = handleColumns(res.data.tableColumns)
+        allTable.value.columns = handleColumns(res.data.tableColumns)
+
+        tmpSearch = res.data.tmp_search
 
         const sortByData = res.data.sortBy
         sortByOptions.value = sortByData.options
@@ -115,6 +132,7 @@ const getTableData = (isPageChange?: boolean) => {
     .finally(() => {
       nextTick(() => {
         tableRef.value && autoWidth(tableData.value, tableRef.value)
+
         tableLoadingTable.value = false
       })
     })
@@ -124,8 +142,10 @@ const getTableData = (isPageChange?: boolean) => {
 const handleSearch = () => {
   getTableData()
 }
+
+let tmpSearch = ''
 // 下载
-const getExportTableData = (type: string, column: any) => {
+const getExportTableData = async (type: string, column: any) => {
   if (column) {
     const newColumns = column.map((item: any) => {
       let curColumn: any = {
@@ -138,17 +158,28 @@ const getExportTableData = (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'
+      }
+    } else if (res.code === 500) {
+      ElMessage.error(res.data.msg)
+    }
+  })
+  await nextTick(async () => {
+    debugger
+    await autoWidth(allTable.value, allTableRef.value)
+  })
   const exportConfig: any = {
     type: type,
     message: false,
-    filename: `Report List_${dayjs().format('YYYYMMDDHH[h]mm[m]ss[s]')}`,
-    columns: column || tableData.value.columns
+    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)
+  allTableRef.value.exportData(exportConfig)
+  exportLoading.value = false
 }
 
 // 实现行点击样式
@@ -170,7 +201,7 @@ defineExpose({
     element-loading-custom-class="element-loading"
     element-loading-background="rgb(43, 47, 54, 0.7)"
   >
-    <div class="SettingTable">
+    <div class="setting-table">
       <div class="flex">
         <div class="Title">Report Data Review</div>
         <div class="flex" style="margin-bottom: 6px">
@@ -209,6 +240,7 @@ defineExpose({
           <VTag :type="row[column.field]">{{ row[column.field] }}</VTag>
         </template>
       </vxe-grid>
+      <vxe-grid :height="10" ref="allTableRef" class="all-table" v-bind="allTable"> </vxe-grid>
     </div>
     <div class="pagination">
       <span>Total {{ formatNumber(pageInfo.total) }}</span>
@@ -258,10 +290,17 @@ defineExpose({
 :deep(.el-icon svg) {
   width: 1em !important;
 }
-.SettingTable {
+.setting-table {
+  position: relative;
+  overflow: hidden;
   padding: 13px 24px 0 24px;
   font-weight: 400;
   border-top: 1px solid var(--color-border);
+  .all-table {
+    position: absolute;
+    top: -100000px;
+    width: 20px;
+  }
 }
 .empty-text {
   margin: 8px 0;

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

@@ -52,11 +52,9 @@ const ShowAll = (type: string) => {
 }
 const emit = defineEmits(['applyNewColumn'])
 const handleApply = () => {
-  console.log(manageFieldsColumns.value, 'value')
   $api
     .saveManageFieldsList({ serial_no: serialNo.value, fieldsList: manageFieldsColumns.value })
     .then((res: any) => {
-      console.log(res, 'value')
       if (res.code === 200) {
         emit('applyNewColumn')
         manageFieldsVisible.value = false

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

@@ -3,16 +3,16 @@ import ValidityPeriod from './components/ValidityPeriod.vue'
 import TimeRange from './components/TimeRange.vue'
 import EmailConfiguration from './components/EmailConfiguration.vue'
 import FieldsTable from './components/FieldsTable.vue'
-import { useRoute } from 'vue-router'
+import { useRoute, useRouter } from 'vue-router'
 
 const route = useRoute()
+const router = useRouter()
 const serialNo = route.query.id as string
 
 const fieldsTableRef = ref()
-
 const customStartDate = ref('')
 const customEndDate = ref('')
-
+const loading = ref(false)
 const pageData: any = ref({
   validityPeriod: {
     type: '',
@@ -30,27 +30,31 @@ const pageData: any = ref({
     emailRecipients: '',
     scheduleDetails: {},
     timezone: ''
-  }
+  },
+  orderBy: ''
 })
 
 onMounted(() => {
-  $api.getReportScheduleData({ serial_no: serialNo }).then((res: any) => {
-    if (res.code === 200) {
-      console.log(res.data)
-      pageData.value = res.data.showData
-    }
-  })
+  loading.value = true
+  $api
+    .getReportScheduleData({ serial_no: serialNo })
+    .then((res: any) => {
+      if (res.code === 200) {
+        pageData.value = res.data.showData
+      }
+    })
+    .then(() => {
+      loading.value = false
+    })
 })
 
 // 提交ValidityPeriod组件数据
 const handleSubmitValidity = (data: any) => {
   customStartDate.value = data.startDate
   customEndDate.value = data.endDate
-  console.log(data)
 }
 
 const changeTimeRange = (data: any) => {
-  console.log(data, 'value')
   const queryData = {
     startDate: data.startDate,
     endDate: data.endDate,
@@ -70,6 +74,7 @@ const handleSave = () => {
   const validityPeriod = validityPeriodRef.value.getData()
   const timeRange = timeRangeRef.value.getData()
   const emailConfiguration = emailConfigurationRef.value.getData()
+  const orderBy = fieldsTableRef.value.getData()
   // 如果有一个返回false, 则代表有未填选项 结束保存
   if (!validityPeriod || !timeRange || !emailConfiguration) return
   const queryData = {
@@ -82,57 +87,69 @@ const handleSave = () => {
     endDate: timeRange.endValue,
     emailRecipients: emailConfiguration.emailRecipients,
     deliveryFrequency: emailConfiguration.deliveryFrequency,
-    scheduleDetails: emailConfiguration.scheduleDetails,
-    timezone: emailConfiguration.timezone
+    timezone: emailConfiguration.timezone,
+    ...emailConfiguration.scheduleDetails,
+    orderBy
   }
   $api
     .saveReportScheduleData({
       serial_no: serialNo,
       ...queryData
     })
-    .then((res: any) => {})
+    .then((res: any) => {
+      if (res.code === 200) {
+        ElMessage.success('Save Success')
+        router.push({ path: '/report' })
+      }
+    })
 }
 </script>
 <template>
-  <div class="Title">
-    <span>Schedule Configuration - Shipment Status Report</span>
-    <el-button @click="handleSave" 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
-          ref="validityPeriodRef"
-          :data="pageData.validityPeriod"
-          @handleSubmitValidity="handleSubmitValidity"
-        >
-        </ValidityPeriod>
-      </div>
+  <div v-vloading="loading">
+    <div class="Title">
+      <span>Schedule Configuration - Shipment Status Report</span>
+      <el-button @click="handleSave" 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="schedule_rule" style="margin: 8px 0">
-      <div class="schedule_title">Report Data Time Range</div>
-      <div class="schedule_container">
-        <TimeRange ref="timeRangeRef" :data="pageData.timeRange" @changeTimeRange="changeTimeRange">
-        </TimeRange>
+    <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
+            ref="validityPeriodRef"
+            :data="pageData.validityPeriod"
+            @handleSubmitValidity="handleSubmitValidity"
+          >
+          </ValidityPeriod>
+        </div>
       </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
-          ref="emailConfigurationRef"
-          :data="pageData.deliveryFrequency"
-        ></EmailConfiguration>
+      <div class="schedule_rule" style="margin: 8px 0">
+        <div class="schedule_title">Report Data Time Range</div>
+        <div class="schedule_container">
+          <TimeRange
+            ref="timeRangeRef"
+            :data="pageData.timeRange"
+            @changeTimeRange="changeTimeRange"
+          >
+          </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
+            ref="emailConfigurationRef"
+            :data="pageData.deliveryFrequency"
+          ></EmailConfiguration>
+        </div>
       </div>
+      <FieldsTable :orderBy="pageData.orderBy" ref="fieldsTableRef"></FieldsTable>
     </div>
-    <FieldsTable ref="fieldsTableRef"></FieldsTable>
   </div>
 </template>
 

+ 7 - 7
src/views/Report/src/components/ReportSchedule/src/components/EmailConfiguration.vue

@@ -9,7 +9,7 @@ watch(
   () => props.data,
   (newVal) => {
     emailValue.value = newVal.emailRecipients
-    timeZoneValue.value = newVal.timeZone
+    timezoneValue.value = newVal.timezone
     frequencyValue.value = newVal.deliveryFrequency
     const dateData = newVal.scheduleDetails
     timeValue.value = dateData.time
@@ -22,8 +22,8 @@ watch(
 )
 
 const emailValue = ref('')
-const timeZoneValue = ref('UTC' + moment().tz(moment.tz.guess()).format('Z'))
-const frequencyValue = ref('daily')
+const timezoneValue = ref('UTC' + moment().tz(moment.tz.guess()).format('Z'))
+const frequencyValue = ref('')
 const timeValue = ref('')
 const generateTimeOptions = (intervalMinutes = 30): Array<{ value: string; label: string }> => {
   const options = []
@@ -203,8 +203,8 @@ const handleClickFrequency = () => {
 }
 
 const getData = () => {
-  if (!frequencyValue.value || !emailValue.value || !timeZoneValue.value) {
-    ElMessage.error('Please select the Report Delivery Frequency & Email Configuration')
+  if (!frequencyValue.value || !emailValue.value || !timezoneValue.value) {
+    ElMessage.warning('Please select the Report Delivery Frequency & Email Configuration')
     return false
   }
   if (
@@ -222,7 +222,7 @@ const getData = () => {
   }
   return {
     emailRecipients: emailValue.value,
-    timeZone: timeZoneValue.value,
+    timezone: timezoneValue.value,
     deliveryFrequency: frequencyValue.value,
     scheduleDetails: {
       time: timeValue.value,
@@ -261,7 +261,7 @@ defineExpose({
           <span class="stars_red">*</span>
           Timezone
         </div>
-        <el-select class="select_time" v-model="timeZoneValue" placeholder="Please select...">
+        <el-select class="select_time" v-model="timezoneValue" placeholder="Please select...">
           <el-option
             v-for="item in timezoneoptions"
             :key="item.value"

+ 19 - 6
src/views/Report/src/components/ReportSchedule/src/components/FieldsTable.vue

@@ -10,8 +10,16 @@ import { useRoute } from 'vue-router'
 const route = useRoute()
 
 const orderBy = ref('')
-const ManageReportFieldsRef = ref()
-const ColumnSortoptions = [
+const props = defineProps<{
+  orderBy: any
+}>()
+watch(
+  () => props.orderBy,
+  (newVal) => (orderBy.value = newVal)
+)
+
+const manageReportFieldsRef = ref()
+const columnSortoptions = [
   {
     label: 'ETA',
     value: 'eta'
@@ -116,7 +124,7 @@ const handleSearch = (val: any) => {
 }
 
 const handleClickManageFields = () => {
-  ManageReportFieldsRef.value.openDialog(route.query.id as string)
+  manageReportFieldsRef.value.openDialog(route.query.id as string)
 }
 
 const applyNewColumn = () => {
@@ -126,8 +134,13 @@ const applyNewColumn = () => {
 // 实现行点击样式
 useRowClickStyle(tableRef)
 
+const getData = () => {
+  return orderBy.value
+}
+
 defineExpose({
-  handleSearch
+  handleSearch,
+  getData
 })
 </script>
 <template>
@@ -143,7 +156,7 @@ defineExpose({
             placeholder="Please select..."
           >
             <el-option
-              v-for="item in ColumnSortoptions"
+              v-for="item in columnSortoptions"
               :key="item.value"
               :label="item.label"
               :value="item.value"
@@ -153,7 +166,7 @@ defineExpose({
             <span class="font_family icon-icon_set_b"></span>Manage Fields
           </el-button>
           <ManageReportFields
-            ref="ManageReportFieldsRef"
+            ref="manageReportFieldsRef"
             @applyNewColumn="applyNewColumn"
           ></ManageReportFields>
         </div>

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

@@ -52,11 +52,9 @@ const ShowAll = (type: string) => {
 }
 const emit = defineEmits(['applyNewColumn'])
 const handleApply = () => {
-  console.log(manageFieldsColumns.value, 'value')
   $api
     .saveManageFieldsList({ serial_no: serialNo.value, fieldsList: manageFieldsColumns.value })
     .then((res: any) => {
-      console.log(res, 'value')
       if (res.code === 200) {
         emit('applyNewColumn')
         manageFieldsVisible.value = false

+ 19 - 3
src/views/Report/src/components/ReportSchedule/src/components/TimeRange.vue

@@ -34,8 +34,8 @@ watch(
       rollingValueStart.value = newVal.startDate
       rollingValueEnd.value = newVal.endDate
     } else if (type.value === 'fixed') {
-      startDate.value = newVal.startDate
-      endDate.value = newVal.endDate
+      startDate.value = dayjs(newVal.startDate, 'YYYY-MM-DD', true).format('MM/DD/YYYY')
+      endDate.value = dayjs(newVal.endDate, 'YYYY-MM-DD', true).format('MM/DD/YYYY')
     }
   }
 )
@@ -71,6 +71,18 @@ const clampedValueEnd = computed({
   }
 })
 
+const disabledDate = (date: any) => {
+  if (!startDate.value) return false
+  const currentDate = dayjs(date).format('YYYY-MM-DD')
+  return dayjs(currentDate).isBefore(startDate.value)
+}
+
+const disabledDateEnd = (date: any) => {
+  if (!endDate.value) return false
+  const currentDate = dayjs(date).format('YYYY-MM-DD')
+  return dayjs(currentDate).isAfter(endDate.value)
+}
+
 const emits = defineEmits<{
   (e: 'changeTimeRange', val: any): void
 }>()
@@ -174,7 +186,7 @@ const changeFixedRange = (val: any) => {
 
 const getData = () => {
   if (!fieldType.value || !type.value) {
-    ElMessage.error('Please select the Report Data Time Range')
+    ElMessage.warning('Please select the Report Data Time Range')
     return false
   }
 
@@ -327,9 +339,11 @@ defineExpose({
                       v-model="startDate"
                       type="date"
                       class="date-picker"
+                      clearable
                       placeholder="Pick a Date"
                       :format="userStore.dateFormat"
                       valueFormat="MM/DD/YYYY"
+                      :disabled-date="disabledDateEnd"
                       @change="changeTime('fixed')"
                     />
                   </div>
@@ -339,9 +353,11 @@ defineExpose({
                       v-model="endDate"
                       type="date"
                       class="date-picker"
+                      clearable
                       placeholder="Pick a Date"
                       :format="userStore.dateFormat"
                       valueFormat="MM/DD/YYYY"
+                      :disabled-date="disabledDate"
                       @change="changeTime('fixed')"
                     />
                   </div>

+ 23 - 2
src/views/Report/src/components/ReportSchedule/src/components/ValidityPeriod.vue

@@ -1,4 +1,5 @@
 <script setup lang="ts">
+import dayjs from 'dayjs'
 import { useUserStore } from '@/stores/modules/user'
 
 const userStore = useUserStore()
@@ -18,8 +19,12 @@ const endDate = ref('')
 watch(
   () => props.data,
   (newVal) => {
-    startDate.value = newVal.startDate
-    endDate.value = newVal.endDate
+    const isCustom = newVal.type === 'custom'
+    const formatDate = (date: string) =>
+      isCustom && date ? dayjs(date, 'YYYY-MM-DD', true).format('MM/DD/YYYY') : date
+
+    startDate.value = formatDate(newVal.startDate)
+    endDate.value = formatDate(newVal.endDate)
     validityPeriodType.value = newVal.type
   },
   { immediate: true }
@@ -33,6 +38,18 @@ const ChangeValidity = () => {
   startDate.value = ''
   endDate.value = ''
 }
+
+const disabledDate = (date: any) => {
+  if (!startDate.value) return false
+  const currentDate = dayjs(date).format('YYYY-MM-DD')
+  return dayjs(currentDate).isBefore(startDate.value)
+}
+
+const disabledDateEnd = (date: any) => {
+  if (!endDate.value) return false
+  const currentDate = dayjs(date).format('YYYY-MM-DD')
+  return dayjs(currentDate).isAfter(endDate.value)
+}
 const getData = () => {
   if (
     !validityPeriodType.value ||
@@ -89,7 +106,9 @@ defineExpose({
                 type="date"
                 style="width: 320px"
                 placeholder="Pick a Date"
+                clearable
                 :format="formatDate"
+                :disabled-date="disabledDateEnd"
                 valueFormat="MM/DD/YYYY"
               />
             </div>
@@ -100,7 +119,9 @@ defineExpose({
                 type="date"
                 style="width: 320px"
                 placeholder="Pick a Date"
+                clearable
                 :format="formatDate"
+                :disabled-date="disabledDate"
                 valueFormat="MM/DD/YYYY"
               />
             </div>

+ 9 - 10
src/views/TemplateManagement/src/TemplateManagement.vue

@@ -13,16 +13,7 @@ const queryData = ref({
   party_id: ''
 })
 
-const aiModelList = [
-  {
-    label: 'Deepseek',
-    value: 'Deepseek'
-  },
-  {
-    label: 'Claude',
-    value: 'Claude'
-  }
-]
+const aiModelList = ref([])
 
 const activeOptions = [
   {
@@ -48,6 +39,14 @@ const applicationScopeOptions = [
 
 const tableRef = ref()
 
+onMounted(() => {
+  $api.getFilterPartyID().then((res) => {
+    if (res.code === 200) {
+      aiModelList.value = res.data
+    }
+  })
+})
+
 const Search = () => {
   tableRef.value.searchTableData(queryData.value)
 }

+ 10 - 11
src/views/TemplateManagement/src/components/CreateReportTemplate/src/CreateReportTemplate.vue

@@ -21,7 +21,8 @@ onMounted(() => {
       .editReportTemplate({ serial_no: route.query.serial_no })
       .then((res) => {
         infoData.value = {
-          reportName: res.data.reportName,
+          // 如果是复制的话,清空Report Name
+          reportName: route.query.copy !== 't' ? res.data.reportName : '',
           reportLevel: res.data.reportLevel,
           reportDescription: res.data.reportDescription
         }
@@ -184,12 +185,6 @@ const handleCancel = () => {
 }
 
 const handleSave = () => {
-  const tipsLabel = [
-    {
-      label: 'Report Name',
-      key: 'reportName'
-    }
-  ]
   if (!infoData.value.reportName.trim()) {
     ElMessage.warning('Please enter the Report Name.')
   }
@@ -213,14 +208,18 @@ const handleSave = () => {
     access_type: accessControlType.value,
     party_ids: specificRoles.value.partyId || [],
     group_names: specificRoles.value.groupName || [],
-    fieldsList: fieldsList.value,
-    serial_no: route.query.serial_no || ''
+    fieldsList: fieldsList.value
+  }
+  let serial_no = ''
+  if (route.query.copy !== 't' && route.query.serial_no) {
+    serial_no = String(route.query.serial_no)
   }
-  $api.saveNewReportTemplate(data).then((res: any) => {
+  $api.saveNewReportTemplate({ ...data, serial_no }).then((res: any) => {
     if (res.code === 200) {
       ElMessage.success('Report Template saved successfully!')
+      router.push('/template-management')
     } else {
-      ElMessage.error(res.message || 'Failed to save Report Template.')
+      ElMessage.error(res.data.msg || 'Failed to save Report Template.')
     }
   })
 }

+ 31 - 9
src/views/TemplateManagement/src/components/CreateReportTemplate/src/components/GroupNameSelect.vue

@@ -1,5 +1,6 @@
 <script setup lang="ts">
-import { cloneDeep } from 'lodash'
+import { cloneDeep, debounce } from 'lodash'
+import axios from 'axios'
 
 const props = defineProps({
   data: {
@@ -7,6 +8,8 @@ const props = defineProps({
     default: () => []
   }
 })
+
+const selectViewRef = ref(null)
 const selectData = ref<string[]>([])
 watch(
   () => props.data,
@@ -36,14 +39,19 @@ onMounted(() => {
   options.value = []
 })
 
+const currentController = ref<AbortController | null>(null)
 // 搜索方法
 const remoteMethod = (query: string) => {
+  currentController.value?.abort()
+
+  const newController = new AbortController()
+  currentController.value = newController
   loading.value = true
   $api
-    .getSpecificRolesGroupName({ term: query })
+    .getSpecificRolesGroupName({ term: query }, { signal: newController.signal })
     .then((res: any) => {
-      if (res.code == 200) {
-        options.value = res.data.map((item: any) => ({
+      if (!newController.signal.aborted && res.code == 200) {
+        options.value = (res.data || []).map((item: any) => ({
           id: item.id,
           label: item.label,
           code: item.code,
@@ -51,12 +59,26 @@ const remoteMethod = (query: string) => {
         }))
       }
     })
+    .catch((err) => {
+      options.value = []
+      if (!axios.isCancel(err) && !newController.signal.aborted) {
+        ElMessage.error('Failed to load options')
+      }
+    })
     .finally(() => {
-      loading.value = false
+      // 仅当这是最新请求时,才关闭 loading
+      if (currentController.value === newController) {
+        loading.value = false
+      }
     })
 }
 
-const testRef = ref(null)
+// 防抖版本(可选)
+const debouncedRemoteMethod = debounce(remoteMethod, 200)
+
+onUnmounted(() => {
+  currentController.value?.abort()
+})
 </script>
 
 <template>
@@ -68,9 +90,9 @@ const testRef = ref(null)
     placeholder="Select Group Name (Multi-select allowed)"
     :loading="loading"
     style="width: 100%"
-    ref="testRef"
+    ref="selectViewRef"
     popper-class="group-name-select-popper"
-    :filter-method="remoteMethod"
+    :filter-method="debouncedRemoteMethod"
     @change="changeData"
   >
     <el-option
@@ -83,7 +105,7 @@ const testRef = ref(null)
       <el-checkbox :model-value="selectData.includes(item.label)" style="flex: 1">
         <div class="select-option">
           <span>{{ item.label }}</span>
-          <span class="value" :style="{ width: testRef?.$el?.offsetWidth - 70 + 'px' }">
+          <span class="value" :style="{ width: selectViewRef?.$el?.offsetWidth - 70 + 'px' }">
             {{ item.id }}
           </span>
         </div>

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

@@ -1,5 +1,6 @@
 <script setup lang="ts">
-import { cloneDeep } from 'lodash'
+import { cloneDeep, debounce } from 'lodash'
+import axios from 'axios'
 
 const props = defineProps({
   data: {
@@ -32,25 +33,46 @@ interface ListItem {
 
 const options = ref<ListItem[]>([])
 const loading = ref(false)
+const currentController = ref<AbortController | null>(null)
 
-// 搜索方法
 const remoteMethod = (query: string) => {
+  currentController.value?.abort()
+
+  const newController = new AbortController()
+  currentController.value = newController
   loading.value = true
+
   $api
-    .getSpecificRolesPartyID({ term: query })
-    .then((res: any) => {
-      if (res.code === 200) {
-        options.value = (res.data || []).map((item: any) => ({
+    .getSpecificRolesPartyID({ term: query }, { signal: newController.signal })
+    .then((res) => {
+      if (!newController.signal.aborted && res.code === 200) {
+        options.value = (res.data || []).map((item) => ({
           id: item.id,
           label: item.label
         }))
       }
     })
+    .catch((err) => {
+      options.value = []
+      if (!axios.isCancel(err) && !newController.signal.aborted) {
+        ElMessage.error('Failed to load options')
+      }
+    })
     .finally(() => {
-      loading.value = false
+      // 仅当这是最新请求时,才关闭 loading
+      if (currentController.value === newController) {
+        loading.value = false
+      }
     })
 }
 
+// 防抖版本(可选)
+const debouncedRemoteMethod = debounce(remoteMethod, 200)
+
+onUnmounted(() => {
+  currentController.value?.abort()
+})
+
 // 首次聚焦或输入时加载(可选:如果希望空搜也加载)
 // 但通常 remote 场景是“输入才搜”,所以这里只在 filter 时调用
 </script>
@@ -65,7 +87,7 @@ const remoteMethod = (query: string) => {
     :loading="loading"
     style="width: 100%"
     popper-class="part-id-select-popper"
-    :filter-method="remoteMethod"
+    :filter-method="debouncedRemoteMethod"
     @change="changeData"
   >
     <el-option

+ 12 - 14
src/views/TemplateManagement/src/components/TableView/src/TableView.vue

@@ -255,7 +255,7 @@ const handleEdit = (serial_no: string) => {
 const handleCopy = (serial_no: string) => {
   router.push({
     name: 'Create Report Template',
-    query: { serial_no, isCopy: 't' }
+    query: { serial_no, copy: 't' }
   })
 }
 
@@ -287,30 +287,22 @@ defineExpose({
     >
       <!-- action操作栏的插槽 -->
       <template #action="{ row }">
-        <el-button
-          class="el-button--blue"
-          @click="handleEdit(row.serial_no)"
-          style="height: 24px; padding: 8px 4px; padding-left: 5px; font-size: 12px"
-        >
+        <el-button class="el-button--blue action-btn" @click="handleEdit(row.serial_no)">
           <span
             style="margin-right: 2px; font-size: 15px"
             class="font_family icon-icon_edit_b"
           ></span>
         </el-button>
-        <el-button
-          class="el-button--blue"
-          style="height: 24px; padding: 8px 4px; padding-left: 5px; font-size: 12px"
-        >
+        <el-button class="el-button--blue action-btn" @click="handleCopy(row.serial_no)">
           <span
             style="margin-right: 2px; font-size: 15px"
             class="font_family icon-icon_clone_b"
           ></span>
         </el-button>
         <el-button
-          class="el-button--blue"
+          class="el-button--blue action-btn"
           v-if="row.is_active === 't'"
           @click="handleClick(row, false)"
-          style="height: 24px; padding: 8px 4px; padding-left: 5px; font-size: 12px"
         >
           <span
             style="margin-right: 2px; font-size: 16px"
@@ -318,10 +310,9 @@ defineExpose({
           ></span>
         </el-button>
         <el-button
-          class="el-button--blue"
+          class="el-button--blue action-btn"
           v-if="row.is_active === 'f'"
           @click="handleClick(row, true)"
-          style="height: 24px; padding: 8px 4px; padding-left: 5px; font-size: 12px"
         >
           <span
             style="margin-right: 2px; font-size: 13px"
@@ -412,6 +403,13 @@ defineExpose({
       margin-top: 12px;
     }
   }
+  .action-btn {
+    height: 24px;
+    width: 26px;
+    padding: 8px 4px;
+    padding-left: 5px;
+    font-size: 12px;
+  }
 }
 .pagination {
   display: flex;