ソースを参照

feat: 联调部分接口

Jack Zhou 1 ヶ月 前
コミット
3ea31d43c8

+ 3 - 1
src/api/index.ts

@@ -7,6 +7,7 @@ import * as notificationMessage from './module/notificationMessage'
 import * as system from './module/system'
 import * as AIRobot from './module/AIRobot'
 import * as Delivery from './module/Delivery'
+import * as report from './module/report'
 /**
  * api 对象接口定义
  */
@@ -27,7 +28,8 @@ const apis = generateApiMap({
   ...notificationMessage,
   ...system,
   ...AIRobot,
-  ...Delivery
+  ...Delivery,
+  ...report
 })
 export default {
   ...apis // 取出所有可遍历属性赋值在新的对象上

+ 34 - 0
src/api/module/report.ts

@@ -0,0 +1,34 @@
+import HttpAxios from '@/utils/axios'
+
+const base = import.meta.env.VITE_API_HOST
+const baseUrl = `${base}/main_new_version.php`
+
+/**
+ * 获取report template management表格列数据
+ */
+export const getReportTemplateManagementTable = (params: any, config: any) => {
+  return HttpAxios.get(
+    `${baseUrl}`,
+    {
+      action: 'report_config',
+      operate: 'search',
+      ...params
+    },
+    config
+  )
+}
+
+/**
+ * 获取Report Fields Configuration 列表数据
+ */
+export const getReportFieldsConfiguration = (params: any, config: any) => {
+  return HttpAxios.get(
+    `${baseUrl}`,
+    {
+      action: 'report_config',
+      operate: 'report_field_load',
+      ...params
+    },
+    config
+  )
+}

+ 31 - 22
src/views/TemplateManagement/src/TemplateManagement.vue

@@ -6,13 +6,11 @@ import { useRouter } from 'vue-router'
 const router = useRouter()
 const filterRef: Ref<HTMLElement | null> = ref(null)
 const containerHeight = useCalculatingHeight(document.documentElement, 290, [filterRef])
-const searchData = ref({
+const queryData = ref({
   text_search: '',
-  request_date_start: '',
-  request_date_end: '',
-  ai_model: '',
-  response_duration_type: '',
-  response_duration_num: null
+  is_active: '',
+  application_scope: '',
+  party_id: ''
 })
 
 const aiModelList = [
@@ -26,25 +24,32 @@ const aiModelList = [
   }
 ]
 
-const comparatorList = [
+const activeOptions = [
   {
-    label: '>=',
-    value: 'thanOrEqual'
+    label: 'Yes',
+    value: 't'
   },
   {
-    label: '=',
-    value: 'equal'
+    label: 'No',
+    value: 'f'
+  }
+]
+
+const applicationScopeOptions = [
+  {
+    label: 'All Users',
+    value: 'all'
   },
   {
-    label: '<=',
-    value: 'lessOrEqual'
+    label: 'Specific Users',
+    value: 'specific'
   }
 ]
 
 const tableRef = ref()
 
 const Search = () => {
-  tableRef.value.SearchOperationLog(searchData.value)
+  tableRef.value.SearchOperationLog(queryData.value)
 }
 
 const handleCreate = () => {
@@ -67,7 +72,7 @@ const handleCreate = () => {
         <div class="input-tips_filter">
           <el-input
             placeholder="Search report name"
-            v-model="searchData.text_search"
+            v-model="queryData.text_search"
             class="log_input"
           >
             <template #prefix>
@@ -81,27 +86,31 @@ const handleCreate = () => {
         </div>
 
         <div class="tips_filter">
-          <el-select v-model="searchData.ai_model" clearable placeholder="Is Active">
+          <el-select v-model="queryData.is_active" clearable placeholder="Is Active">
             <el-option
-              v-for="item in aiModelList"
-              :key="item.value"
+              v-for="item in activeOptions"
+              :key="item.label"
               :label="item.label"
               :value="item.value"
             />
           </el-select>
         </div>
         <div class="tips_filter">
-          <el-select v-model="searchData.ai_model" clearable placeholder="Application Scope">
+          <el-select
+            v-model="queryData.application_scope"
+            clearable
+            placeholder="Application Scope"
+          >
             <el-option
-              v-for="item in aiModelList"
-              :key="item.value"
+              v-for="item in applicationScopeOptions"
+              :key="item.label"
               :label="item.label"
               :value="item.value"
             />
           </el-select>
         </div>
         <div class="tips_filter">
-          <el-select v-model="searchData.ai_model" clearable placeholder="Party ID">
+          <el-select v-model="queryData.party_id" clearable placeholder="Party ID">
             <el-option
               v-for="item in aiModelList"
               :key="item.value"

+ 27 - 22
src/views/TemplateManagement/src/components/CreateReportTemplate/src/CreateReportTemplate.vue

@@ -3,6 +3,7 @@ import { useRouter } from 'vue-router'
 import partyIDSelect from './components/partyIDSelect.vue'
 import GroupNameSelect from './components/GroupNameSelect.vue'
 import { VueDraggable } from 'vue-draggable-plus'
+import AdjustmentField from './components/AdjustmentField.vue'
 
 const router = useRouter()
 const filterRef: Ref<HTMLElement | null> = ref(null)
@@ -41,39 +42,40 @@ interface Field {
   isSort: boolean
 }
 const fieldsList = ref<Field[]>([])
+const levelOptions = [
+  {
+    label: 'Shipment level',
+    value: 'Shipment level'
+  },
+  {
+    label: 'Container Level',
+    value: 'Container Level'
+  },
+  {
+    label: 'Item Level',
+    value: 'Item Level'
+  }
+]
 
 const handleDeleteField = (field: string) => {
   fieldsList.value = fieldsList.value.filter((item) => item.field !== field)
 }
 
-const CustomizeColumnsRef = ref()
+const AdjustmentFieldRef = ref()
 // 打开定制表格弹窗
 const handleCustomizeColumns = () => {
   const params = {
-    getData: {
-      action: 'ocean_booking',
-      operate: 'setting_display'
-    },
-    saveData: {
-      action: 'ajax',
-      operate: 'save_setting_display',
-      model_name: 'Booking_Search'
-    }
+    serial_no: '',
+    level: infoData.value.reportLevel
   }
-  CustomizeColumnsRef.value.openDialog(
+  AdjustmentFieldRef.value.openDialog(
     params,
     -220,
     'Drag item over to this selection or click "add" icon to show the field on report template list'
   )
 }
 // 定制表格
-const customizeColumns = async () => {
-  await $api.getBookingTableColumns().then((res: any) => {
-    if (res.code === 200) {
-      fieldsList.value = res.data.BookingTableColumns
-    }
-  })
-}
+const customizeColumns = async () => {}
 
 const newFieldInfo = ref<{
   name: string
@@ -166,9 +168,12 @@ const handleRightRemove = () => {}
                 <span>Report Level</span>
               </div>
               <el-select v-model="infoData.reportLevel" placeholder="Please enter...">
-                <el-option label="Level 1" value="level1"></el-option>
-                <el-option label="Level 2" value="level2"></el-option>
-                <el-option label="Level 3" value="level3"></el-option>
+                <el-option
+                  v-for="item in levelOptions"
+                  :label="item.label"
+                  :value="item.value"
+                  :key="item.value"
+                ></el-option>
               </el-select>
             </div>
           </div>
@@ -309,7 +314,7 @@ const handleRightRemove = () => {}
       </div>
     </div>
 
-    <CustomizeColumns @customize="customizeColumns" ref="CustomizeColumnsRef" />
+    <AdjustmentField @customize="customizeColumns" ref="AdjustmentFieldRef" />
     <el-dialog
       class="add-new-field-dialog"
       title="Add New Field"

+ 798 - 0
src/views/TemplateManagement/src/components/CreateReportTemplate/src/components/AdjustmentField.vue

@@ -0,0 +1,798 @@
+<script setup lang="ts">
+import { VueDraggable } from 'vue-draggable-plus'
+import { useRoute } from 'vue-router'
+
+const route = useRoute()
+const dialogVisible = ref(false)
+// search筛选的字段
+const searchColumn = ref('')
+// search筛选的options
+const searchOptions: any = ref()
+
+// 右侧箭头消失所需要translateX的值
+const rightArrowHideDistance = ref(0)
+// 控制tab栏的左右切换箭头
+const handleTabArrow = () => {
+  const parentElement: HTMLElement | null = document.querySelector('.left-all-columns')
+  if (!parentElement) return
+
+  // 左侧切换箭头
+  const leftArrow: HTMLElement | null = parentElement.querySelector('.el-tabs__nav-prev')
+  // 右侧切换箭头
+  const rightArrow: HTMLElement | null = parentElement.querySelector('.el-tabs__nav-next')
+  const targetObserverElement = parentElement.querySelector('.el-tabs__nav')
+
+  if (!targetObserverElement || !leftArrow || !rightArrow) return
+
+  // 创建一个函数来获取 translateX
+  const getTranslateX = () => {
+    const style = window.getComputedStyle(targetObserverElement)
+    const matrix = style.transform
+
+    if (matrix !== 'none' && matrix) {
+      // 提取 matrix 中的 translateX 值
+      const values = matrix.match(/matrix\(([^)]+)\)/)?.[1].split(', ')
+      if (!values) return 0
+      const translateX = parseFloat(values[4])
+      return translateX
+    }
+    return 0 // 如果没有 transform 或 translateX,默认返回 0
+  }
+
+  // 检查并更新箭头显示状态
+  const updateArrowVisibility = () => {
+    const translateX = getTranslateX()
+    if (translateX === 0) {
+      leftArrow.style.display = 'none'
+    } else {
+      leftArrow.style.display = 'inline-block'
+    }
+
+    if (translateX === rightArrowHideDistance.value) {
+      rightArrow.style.display = 'none'
+    } else {
+      rightArrow.style.display = 'inline-block'
+    }
+  }
+
+  // 监听 transitionend 事件,等待动画结束后再获取 translateX 值
+  targetObserverElement.addEventListener('transitionend', (event: any) => {
+    if (event.propertyName === 'transform') {
+      // 只有 transform 动画结束时才触发
+      updateArrowVisibility()
+    }
+  })
+
+  // 初次运行时手动检查一次
+  updateArrowVisibility()
+}
+
+// 筛选选中时滚动到对应的元素
+const handleDocumentClick = (event: any) => {
+  if (!scrollTargetElement.value.contains(event.target)) {
+    scrollTargetElement.value.className = scrollTargetElement.value.className.replace(
+      'search-select-item',
+      ''
+    )
+    scrollTargetElement.value = null
+    document.removeEventListener('click', handleDocumentClick)
+  }
+}
+const scrollTargetElement = ref()
+const scrollToItem = (itemId: string) => {
+  if (activeName.value !== 'All') {
+    activeName.value = 'All'
+  }
+  setTimeout(() => {
+    // 重置
+    if (scrollTargetElement.value) {
+      scrollTargetElement.value.className = scrollTargetElement.value.className.replace(
+        'search-select-item',
+        ''
+      )
+    }
+    // 获取目标元素
+    scrollTargetElement.value = document.querySelector(`[data-field='${itemId}']`)
+    if (scrollTargetElement.value) {
+      // 使用 scrollIntoView 滚动到该元素
+      scrollTargetElement.value.scrollIntoView({ behavior: 'smooth', block: 'center' })
+      scrollTargetElement.value.className += ' search-select-item'
+      // 或者使用自定义滚动
+      // const container = this.$refs.dataContainer
+      // container.scrollTop = targetElement.offsetTop - container.offsetTop
+
+      document.addEventListener('click', handleDocumentClick)
+    }
+  }, 100)
+}
+
+// 系统首次加载时,会有引导操作
+let firstLoad = ref()
+const step1 = ref()
+const open1 = ref(false)
+const isShowStep1 = ref(false)
+const step2 = ref()
+const open2 = ref(false)
+const isShowStep2 = ref(false)
+const handleCloseTour = (stepStr: string) => {
+  if (stepStr === 'step1') {
+    isShowStep1.value = false
+    open1.value = false
+  } else {
+    isShowStep2.value = false
+    open2.value = false
+  }
+  localStorage.setItem('firstLoadCustomizeColumns', 'true')
+  // firstLoad = 'true'
+}
+
+// 左侧选中的tab
+const activeName = ref()
+// 分组列
+const groupColumns: any = ref([])
+// 所有数据
+const allDataCopy: any = ref()
+
+const loading = ref(false)
+// 获取数据
+const getData = async (reset?: string) => {
+  loading.value = true
+  let paramsData: any = { ...params.value.getData }
+  if (reset === 'yes') {
+    paramsData.reset = 'yes'
+  }
+  await $api.getReportFieldsConfiguration(paramsData).then((res: any) => {
+    if (res.code === 200) {
+      // allDataCopy就是所有的数据
+      allDataCopy.value = res.data.GroupColumnsAll
+      groupColumns.value = res.data.GroupColumnsLeft
+      console.log('groupColumns.value', groupColumns.value)
+      activeName.value = allDataCopy.value?.[0]?.name
+      searchOptions.value = res.data.GroupColumnsLeft?.[0]?.children
+      // 右侧选中的数据
+      selectColumns.value = res.data.GroupColumnsRight
+      nextTick(() => {
+        handleTabArrow()
+        // 八秒后关闭引导
+        if (!firstLoad.value) {
+          setTimeout(() => {
+            handleCloseTour('step1')
+            handleCloseTour('step2')
+          }, 8000)
+        }
+      })
+    }
+  })
+  loading.value = false
+}
+
+const params = ref()
+// rightDistance是右侧箭头消失所需要translateX的值
+const openDialog = async (paramsData: Object, rightDistance: number) => {
+  firstLoad.value = localStorage.getItem('firstLoadCustomizeColumns')
+  params.value = paramsData
+  dialogVisible.value = true
+  await getData()
+  rightArrowHideDistance.value = rightDistance
+  nextTick(() => {
+    if (!firstLoad.value) {
+      open1.value = true
+      isShowStep1.value = true
+      open2.value = true
+      isShowStep2.value = true
+    }
+  })
+}
+
+const selectColumns: any = ref([])
+
+// 左侧Icon的显隐
+const hoverAllIcon = ref('')
+// 右侧Icon的显隐
+const hoverSelectIcon = ref('')
+
+const handleAddSelect = (item: any) => {
+  groupColumns.value.forEach((groupItem: any) => {
+    groupItem.children.forEach((child: any, index: number) => {
+      if (child.field === item.field) {
+        groupItem.children.splice(index, 1)
+      }
+    })
+  })
+  selectColumns.value.push(item)
+}
+
+// 从左侧拖拽到右侧时,删除其他分组中相同的数据
+const handleLeftRemove = (e: any) => {
+  if (e.to === e.from) return
+  const curItem = e.data
+  groupColumns.value.forEach((groupItem: any) => {
+    groupItem.children.forEach((child: any, index: number) => {
+      if (child.field === curItem.field) {
+        groupItem.children.splice(index, 1)
+      }
+    })
+  })
+}
+
+// 从右侧拖拽到左侧时,左侧根据分组添加数据
+const handleRightRemove = (e: any) => {
+  if (e.to === e.from) return
+  const curItem = e.data
+  // 获取当前移动项移入到了那一组
+  const curGroup = groupColumns.value.find((item: any) => {
+    return item.name == activeName.value
+  })
+  // 获取当前项应该对应哪一组
+  const originalGroup = allDataCopy.value.find((item: any) => {
+    if (item.name === 'All') {
+      return false
+    }
+    const index = item.children.findIndex((child: any) => {
+      return child.field === curItem.field
+    })
+    return index !== -1
+  })
+
+  if (curGroup.name !== originalGroup?.name && curGroup.name !== 'All') {
+    // 从当前分组中删除移入的数据
+    curGroup.children.forEach((item: any, index: number) => {
+      item.field === curItem.field && curGroup.children.splice(index, 1)
+    })
+    // 在对应分组中添加移入的数据
+    groupColumns.value.forEach((item: any) => {
+      item.name === originalGroup?.name && item.children.push(curItem)
+    })
+    // 添加到All分组里
+    groupColumns.value[0].children.push(curItem)
+  } else if (curGroup.name === 'All') {
+    // 在对应分组中添加移入的数据
+    groupColumns.value.forEach((item: any) => {
+      item.name === originalGroup?.name && item.children.push(curItem)
+    })
+  } else if (curGroup.name === originalGroup?.name) {
+    groupColumns.value[0].children.push(curItem)
+  }
+}
+// 点击右侧的减号删除选中的列,并添加到左侧
+const handleDeleteSelect = (curItem: any) => {
+  selectColumns.value.forEach((item: any, index: number) => {
+    if (item.field === curItem.field) {
+      selectColumns.value.splice(index, 1)
+    }
+  })
+  // 获取当前项应该对应哪一组
+  const originalGroup = allDataCopy.value.find((item: any) => {
+    if (item.name === 'All') {
+      return false
+    }
+    const index = item.children.findIndex((child: any) => {
+      return child.field === curItem.field
+    })
+    return index !== -1
+  })
+  // 在对应分组中添加移入的数据
+  groupColumns.value.forEach((item: any) => {
+    item.name === originalGroup?.name && item.children.push(curItem)
+  })
+  // 添加到All分组里
+  groupColumns.value[0].children.push(curItem)
+}
+
+const handleMoveUpSelect = (item: any) => {
+  const index = selectColumns.value.findIndex((i: any) => i.field === item.field)
+  if (index === 0) return
+  const temp = selectColumns.value[index]
+  selectColumns.value[index] = selectColumns.value[index - 1]
+  selectColumns.value[index - 1] = temp
+}
+const handleMoveDownSelect = (item: any) => {
+  const index = selectColumns.value.findIndex((i: any) => i.field === item.field)
+  if (index === selectColumns.value.length - 1) return
+  const temp = selectColumns.value[index]
+  selectColumns.value[index] = selectColumns.value[index + 1]
+  selectColumns.value[index + 1] = temp
+}
+
+const emits = defineEmits<{
+  customize: []
+  reset: []
+}>()
+const handleReset = () => {
+  getData('yes')
+}
+const handleApply = () => {
+  // const columnsList = selectColumns.value.map((item: any) => {
+  //   return item.ids
+  // })
+  // $api
+  //   .saveTableSettingColumns({
+  //     ...params.value.saveData,
+  //     ids: columnsList
+  //   })
+  //   .then((res: any) => {
+  //     if (res.code === 200) {
+  //       // ElMessage.success('Save successfully')
+  //       emits('customize')
+  //       dialogVisible.value = false
+  //     }
+  //   })
+}
+
+const clearData = () => {
+  open1.value = false
+  open2.value = false
+  activeName.value = ''
+  groupColumns.value = []
+  selectColumns.value = []
+  searchColumn.value = ''
+}
+
+defineExpose({
+  openDialog
+})
+</script>
+
+<template>
+  <el-dialog
+    class="customize-columns"
+    v-model="dialogVisible"
+    :width="1000"
+    title="Add/Edit Field"
+    @close="clearData"
+  >
+    <div class="search-header">
+      <div class="search-input" ref="searchRef">
+        <el-select
+          v-model="searchColumn"
+          @change="scrollToItem"
+          filterable
+          placeholder="Search field"
+        >
+          <template #prefix>
+            <span class="font_family icon-icon_search_b"></span>
+          </template>
+          <el-option
+            v-for="item in searchOptions"
+            :key="item.field"
+            :label="item.label"
+            :value="item.field"
+          />
+        </el-select>
+      </div>
+      <div class="tips">
+        <span style="font-size: 16px">* </span>
+        <span
+          >Drag item over to this selection or click "add" icon to show the field on report template
+          list</span
+        >
+      </div>
+    </div>
+    <div class="draggable-list">
+      <div class="left-all-columns" v-vloading="loading">
+        <div class="tabs">
+          <el-tabs v-model="activeName">
+            <el-tab-pane
+              v-for="groupItem in groupColumns"
+              :key="groupItem.name"
+              :label="groupItem.name"
+              :name="groupItem.name"
+            >
+              <VueDraggable
+                v-model="groupItem.children"
+                class="column-list"
+                ghost-class="ghost-column"
+                :forceFallback="true"
+                fallbackClass="fallback-class"
+                group="customizeColumns"
+                item-key="field"
+                @end="handleLeftRemove"
+              >
+                <template v-for="(item, index) in groupItem.children" :key="item.field">
+                  <div
+                    :data-field="item.field"
+                    class="column-item"
+                    @mouseenter="hoverAllIcon = item.field"
+                    @mouseleave="hoverAllIcon = ''"
+                  >
+                    <span class="font_family icon-icon_dragsort__b draggable-icon"></span>
+                    <span class="title">{{ item.label }}</span>
+                    <span
+                      ref="step1"
+                      v-if="hoverAllIcon === item.field || (index === 0 && isShowStep1)"
+                      class="font_family icon-icon_add_b move-icon"
+                      @click="handleAddSelect(item)"
+                    ></span>
+                  </div>
+                </template>
+              </VueDraggable>
+            </el-tab-pane>
+          </el-tabs>
+        </div>
+      </div>
+      <div class="right-select-columns">
+        <div class="title">Selected fields on report template list</div>
+        <VueDraggable
+          v-vloading="loading"
+          v-model="selectColumns"
+          class="column-list"
+          ghost-class="ghost-column"
+          :forceFallback="true"
+          fallback-class="fallback-class"
+          group="customizeColumns"
+          item-key="field"
+          @end="handleRightRemove"
+        >
+          <template v-for="(item, index) in selectColumns" :key="item.field">
+            <div
+              class="column-item"
+              @mouseenter="hoverSelectIcon = item.field"
+              @mouseleave="hoverSelectIcon = ''"
+            >
+              <span
+                class="font_family icon-icon_dragsort__b draggable-icon"
+                style="font-size: 16px"
+              ></span>
+              <span class="title">{{ item.label }}</span>
+              <span
+                v-if="hoverSelectIcon === item.field || (index === 0 && isShowStep2)"
+                class="font_family icon-icon_moveup_b move-icon"
+                @click="handleMoveUpSelect(item)"
+              ></span>
+              <span
+                v-if="hoverSelectIcon === item.field || (index === 0 && isShowStep2)"
+                class="font_family icon-icon_movedown_b move-icon"
+                @click="handleMoveDownSelect(item)"
+              ></span>
+              <span
+                ref="step2"
+                v-if="hoverSelectIcon === item.field || (index === 0 && isShowStep2)"
+                class="font_family icon-icon_reduce_b move-icon"
+                @click="handleDeleteSelect(item)"
+              ></span>
+            </div>
+          </template>
+        </VueDraggable>
+      </div>
+    </div>
+    <template #footer>
+      <el-button
+        type="default"
+        style="height: 40px; padding: 8px 40px"
+        @click="dialogVisible = false"
+        >Cancel</el-button
+      >
+      <el-button type="default" style="height: 40px; padding: 8px 20px" @click="handleReset"
+        >Reset to default</el-button
+      >
+      <el-button
+        class="el-button--dark"
+        style="height: 40px; padding: 8px 40px"
+        @click="handleApply"
+      >
+        Apply
+      </el-button>
+    </template>
+    <el-tour
+      :target-area-clickable="false"
+      class="step1-tour"
+      v-model="open1"
+      :mask="false"
+      type="primary"
+      v-if="step1?.[0]"
+    >
+      <el-tour-step :show-close="false" :target="step1?.[0]">
+        <template #default>
+          <div class="description">
+            <span>Drag</span> items to the right group or click the "<span>Add</span>" icon to add
+            columns to the {{ route.path.includes('booking') ? 'booking' : 'shipment' }} list.
+          </div>
+          <div class="got-it-text" @click="handleCloseTour('step1')">Got it</div>
+        </template>
+      </el-tour-step>
+    </el-tour>
+    <el-tour
+      :target-area-clickable="false"
+      class="step2-tour"
+      v-model="open2"
+      type="primary"
+      :mask="false"
+      v-if="step2?.[0]"
+    >
+      <el-tour-step :show-close="false" :target="step2?.[0]">
+        <template #default>
+          <div class="description">
+            <span>Drag</span> items to the left group or click the "<span>Remove</span>" icon to
+            delete columns from the
+            {{ route.path.includes('booking') ? 'booking' : 'shipment' }} list.
+          </div>
+          <div class="description">
+            <span>Drag</span> items up or down to reorder the
+            {{ route.path.includes('booking') ? 'booking' : 'shipment' }} list, or use the "<span
+              >Move up</span
+            >" and "<span>Move down</span>" icons.
+          </div>
+          <div class="got-it-text" @click="handleCloseTour('step2')">Got it</div>
+        </template>
+      </el-tour-step>
+    </el-tour>
+  </el-dialog>
+</template>
+
+<style lang="scss">
+.customize-columns {
+  .search-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    gap: 8px;
+    padding: 10px 0;
+
+    .search-input {
+      width: 50%;
+      padding-right: 16px;
+    }
+
+    .tips {
+      display: flex;
+      align-items: flex-start;
+      gap: 3px;
+      width: 50%;
+      padding-left: 5px;
+      vertical-align: middle;
+
+      span {
+        font-size: 12px;
+        color: var(--color-neutral-2);
+      }
+    }
+  }
+
+  .draggable-list {
+    display: flex;
+    user-select: none;
+    gap: 8px;
+  }
+}
+
+.right-select-columns,
+.left-all-columns {
+  width: 50%;
+
+  .column-list {
+    height: 400px;
+    overflow: auto;
+
+    .column-item {
+      display: flex;
+      align-items: center;
+      height: 40px;
+      margin-bottom: 5px;
+      padding-left: 12px;
+      border: 1px solid var(--color-border);
+      border-radius: 6px;
+      background-color: var(--color-customize-column-item-bg);
+
+      &:hover {
+        background-color: var(--color-customize-column-item-hover-bg);
+        box-shadow: 4px 4px 32px 0px rgba(0, 0, 0, 0.1);
+      }
+
+      & > .title {
+        flex: 1;
+      }
+
+      span.draggable-icon {
+        margin-right: 12px;
+        color: var(--color-customize-column-item-drag-icon);
+      }
+
+      .font_family {
+        font-size: 16px;
+        cursor: pointer;
+        margin-right: 16px;
+      }
+
+      .move-icon {
+        &:hover {
+          color: var(--color-theme);
+        }
+      }
+    }
+  }
+
+  .ghost-column {
+    cursor: move !important;
+    span {
+      opacity: 0;
+    }
+    border: 1px dashed var(--color-customize-column-item-drag-border) !important;
+    background-color: var(--color-customize-column-item-drag-bg) !important;
+    box-shadow: none !important;
+  }
+
+  .fallback-class {
+    opacity: 1 !important;
+    background-color: var(--color-customize-column-item-hover-bg) !important;
+    cursor: move !important;
+  }
+}
+
+.left-all-columns {
+  border: 1px solid var(--color-border);
+  border-radius: 12px;
+
+  .tabs {
+    position: relative;
+    height: 100%;
+
+    .el-tabs {
+      .el-tabs__header {
+        margin-bottom: 0px;
+        border-bottom: 1px solid var(--color-customize-column-tabs-header-border);
+      }
+
+      .el-tabs__item {
+        padding: 10px;
+      }
+    }
+  }
+
+  .column-list {
+    padding: 8px;
+    padding-bottom: 0px;
+  }
+
+  .search-select-item {
+    border: 1px solid var(--color-theme) !important;
+    box-shadow: 2px 2px 12px 0px rgba(237, 109, 0, 0.2);
+
+    .title {
+      color: var(--color-theme) !important;
+    }
+  }
+}
+
+.right-select-columns {
+  background-color: var(--color-customize-column-right-section-bg);
+  padding-top: 0;
+  border: 1px dashed var(--color-customize-column-right-section-border);
+  border-radius: 12px;
+
+  & > .title {
+    height: 40px;
+    padding: 8px;
+    line-height: 24px;
+    font-size: 16px;
+    font-weight: 700;
+  }
+
+  .column-list {
+    padding: 8px;
+    padding-bottom: 0px;
+  }
+}
+</style>
+<style lang="scss">
+.left-all-columns {
+  .el-tabs__nav-prev,
+  .el-tabs__nav-next {
+    height: 40px;
+    width: 40px;
+  }
+
+  .el-tabs__item {
+    color: var(--color-neutral-1);
+    font-weight: 400;
+    font-size: 14px;
+  }
+
+  .el-tabs__item.is-active,
+  .el-tabs__item:hover {
+    font-weight: 700;
+    font-size: 14px;
+    color: var(--color-neutral-1);
+  }
+
+  .el-tabs__nav-prev {
+    border-right: 1px solid var(--color-border);
+    box-shadow: 2px 0px 12px rgba(0, 0, 0, 0.3);
+    // .el-icon {
+    //   color: white;
+    // }
+    /* 左侧阴影 */
+  }
+
+  .el-tabs__nav-next {
+    border-left: 1px solid var(--color-border);
+    box-shadow: -2px 0px 12px rgba(0, 0, 0, 0.2);
+    /* 左侧阴影 */
+  }
+
+  .el-tabs__nav-wrap {
+    padding: 0 40px;
+  }
+
+  .el-tabs__item.is-active,
+  .el-tabs__item:hover {
+    color: var(--color-theme);
+  }
+
+  .el-tabs__active-bar {
+    background-color: var(--color-theme);
+  }
+}
+
+.search-header {
+  & > .search-input {
+    .el-select {
+      width: 100%;
+
+      .el-select__wrapper {
+        border-radius: 20px;
+      }
+    }
+  }
+}
+</style>
+
+<style lang="scss">
+.step1-tour {
+  .el-tour__content {
+    width: 240px;
+    height: 124px;
+    background-color: var(--color-theme);
+    z-index: 9999 !important;
+  }
+
+  .el-tour__arrow {
+    background-color: var(--color-theme);
+  }
+
+  .el-tour__footer {
+    display: none;
+  }
+}
+
+.step2-tour {
+  .el-tour__content {
+    width: 240px;
+    height: 200px;
+    background-color: var(--color-theme);
+    z-index: 9999 !important;
+  }
+
+  .el-tour__arrow {
+    background-color: var(--color-theme);
+  }
+
+  .el-tour__footer {
+    display: none;
+  }
+}
+
+.step1-tour,
+.step2-tour {
+  .el-tour__header {
+    display: none;
+  }
+
+  .description {
+    margin-bottom: 16px;
+    color: white;
+    line-height: 22px;
+
+    span {
+      color: white;
+      font-weight: 600;
+    }
+  }
+
+  .got-it-text {
+    float: right;
+    color: white;
+    font-weight: 700;
+    cursor: pointer;
+  }
+}
+</style>

+ 52 - 207
src/views/TemplateManagement/src/components/TableView/src/TableView.vue

@@ -1,11 +1,10 @@
 <script setup lang="ts">
 import { ref, nextTick, onMounted } from 'vue'
 import { type VxeGridInstance, type VxeGridProps } from 'vxe-table'
-import DownloadDialog from './components/DownloadDialog.vue'
 import { autoWidth } from '@/utils/table'
 import { useRowClickStyle } from '@/hooks/rowClickStyle'
 import dayjs from 'dayjs'
-import { formatTimezone, formatNumber } from '@/utils/tools'
+import { formatTimezone } from '@/utils/tools'
 import { useRouter } from 'vue-router'
 
 const router = useRouter()
@@ -17,27 +16,56 @@ const props = defineProps({
 })
 
 const tableOriginColumnsField = ref()
-const handleColumns = (columns: any, status?: string) => {
+const tableColumns = [
+  {
+    title: 'Report Name',
+    type: 'normal',
+    field: 'name'
+  },
+  {
+    title: 'Report Level',
+    type: 'normal',
+    field: 'level'
+  },
+  {
+    title: 'Is Active',
+    type: 'status',
+    field: 'is_active'
+  },
+  {
+    title: 'Application Scope',
+    type: 'normal',
+    field: 'access_type'
+  },
+  {
+    title: 'Creation Date',
+    type: 'normal',
+    field: 'creation_time',
+    formatter: 'dateTime',
+    sortable: true
+  }
+]
+const handleColumns = (columns: any) => {
   const newColumns = columns.map((item: any) => {
     let curColumn: any = {
       title: item.title,
       field: item.field,
-      sortable: true,
+      sortable: item.sortable || false,
       minWidth: 120,
       showOverflow: true
     }
     // 设置插槽
-    if (item.type === 'status' && status !== 'all') {
+    if (item.type === 'status') {
       curColumn = {
         ...curColumn,
         slots: { default: 'status' }
       }
-    } else if (item.type === 'link' && status !== 'all') {
+    } else if (item.type === 'link') {
       curColumn = {
         ...curColumn,
         slots: { default: 'link' }
       }
-    } else if (item.type === 'mode' && status !== 'all') {
+    } else if (item.type === 'mode') {
       curColumn = {
         ...curColumn,
         slots: { default: 'mode' },
@@ -60,40 +88,18 @@ const handleColumns = (columns: any, status?: string) => {
 
 // 获取表格列
 const getTableColumns = async () => {
-  tableLoadingColumn.value = true
-  await $api.getAIApiLogTableColumn().then((res: any) => {
-    if (res.code === 200) {
-      tableData.value.columns = [
-        { title: 'Action', width: 116, fixed: 'left', slots: { default: 'action' } },
-        ...handleColumns(res.data.OperationTableColumns)
-      ]
-      tableOriginColumnsField.value = res.data.OperationTableColumns
-    }
-  })
-  nextTick(() => {
-    tableRef.value && autoWidth(tableData.value, tableRef.value)
-    tableLoadingColumn.value = false
-    selectedNumber.value = 0
-    selectedTableData.value = []
-  })
+  tableData.value.columns = [
+    { title: 'Action', width: 116, fixed: 'left', slots: { default: 'action' } },
+    ...handleColumns(tableColumns)
+  ]
+  // tableRef.value && autoWidth(tableData.value, tableRef.value)
 }
 
 const pageInfo = ref({ pageNo: 1, pageSize: 20, total: 0 })
-const tempSearch = ref()
 // 获得表格数据后赋值
 const assignTableData = (data: any) => {
   tableData.value.data = data.searchData || []
   pageInfo.value.total = Number(data.rc) || 0
-  tempSearch.value = data.tmp_search
-  // 拥有所有字段的表格
-  setTimeout(() => {
-    allTable.value.columns = handleColumns(tableData.value.columns, 'all')
-    allTable.value.data = data.searchData || []
-    // 为了让导出的表格列宽度自适应
-    nextTick(() => {
-      allTableRef.value && autoWidth(allTable.value, allTableRef.value)
-    })
-  }, 1000)
 }
 
 let searchdata: any = {}
@@ -102,7 +108,7 @@ const getTableData = async (isPageChange?: boolean) => {
   const rc = isPageChange ? pageInfo.value.total : -1
   tableLoadingTableData.value = true
   await $api
-    .getAIApiLogTableData({
+    .getReportTemplateManagementTable({
       cp: pageInfo.value.pageNo,
       ps: pageInfo.value.pageSize,
       rc,
@@ -114,19 +120,17 @@ const getTableData = async (isPageChange?: boolean) => {
       }
     })
     .finally(() => {
-      selectedNumber.value = 0
-      selectedTableData.value = []
       nextTick(() => {
-        tableRef.value && autoWidth(tableData.value, tableRef.value)
+        // tableRef.value && autoWidth(tableData.value, tableRef.value)
         tableLoadingTableData.value = false
       })
     })
 }
-const SearchOperationLog = (val: any) => {
+const searchTableData = (val: any) => {
   searchdata = val
   tableLoadingTableData.value = true
   $api
-    .getAIApiLogTableData({
+    .getReportTemplateManagementTable({
       cp: pageInfo.value.pageNo,
       ps: pageInfo.value.pageSize,
       rc: -1,
@@ -138,19 +142,16 @@ const SearchOperationLog = (val: any) => {
       }
     })
     .finally(() => {
-      selectedNumber.value = 0
-      selectedTableData.value = []
       nextTick(() => {
-        tableRef.value && autoWidth(tableData.value, tableRef.value)
+        // tableRef.value && autoWidth(tableData.value, tableRef.value)
         tableLoadingTableData.value = false
       })
     })
 }
 onMounted(() => {
-  //
   Promise.all([getTableColumns(), getTableData(false)]).finally(() => {
     nextTick(() => {
-      tableRef.value && autoWidth(tableData.value, tableRef.value)
+      // tableRef.value && autoWidth(tableData.value, tableRef.value)
     })
   })
 })
@@ -229,137 +230,13 @@ const tableData = ref<VxeGridProps<any>>({
     modes: ['current', 'selected', 'all']
   }
 })
-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']
-  }
-})
 
 // 实现行点击样式
 useRowClickStyle(tableRef)
 
-const downloadDialogRef = ref()
-const handleDownload = () => {
-  const curSelectedColumns: string[] = []
-  tableRef.value?.columns?.forEach((item: any) => {
-    if (item.field) {
-      curSelectedColumns.push(item.title)
-    }
-  })
-  downloadDialogRef.value.openDialog(
-    curSelectedColumns,
-    selectedNumber.value || pageInfo.value.total
-  )
-}
-
-const exportLoading = ref(false)
-// 获取导出表格数据
-const getExportTableData = (status: number) => {
-  // 如果有选中表格行数据,那么只到处选中的数据
-  if (selectedNumber.value > 0) {
-    exportTable(status)
-    return
-  }
-  exportLoading.value = true
-  const buildColumnString = (columns: any[]): string => {
-    return columns
-      .filter((item) => item.field)
-      .map((item) => item.title)
-      .join(',')
-  }
-
-  let column = ''
-  if (status === 1) {
-    column = buildColumnString(tableData.value.columns)
-  } else {
-    column = buildColumnString(allTable.value.columns)
-  }
-  $api
-    .getAIApiLogAllTableData({
-      selected_fields: column,
-      tmp_search: tempSearch.value
-    })
-    .then((res: any) => {
-      if (res.code === 200) {
-        allTable.value.data = res.data.Data || []
-        nextTick(() => {
-          exportLoading.value = false
-          // 导出数据
-          exportTable(status)
-        })
-      }
-    })
-    .finally(() => {
-      exportLoading.value = false
-    })
-}
-// 导出表格 status: 1 导出当前表格 2 导出所有字段表格
-const exportTable = (status: number) => {
-  const exportConfig: any = {
-    type: 'xlsx',
-    message: false,
-    filename: `AI API Log_${dayjs().format('YYYYMMDDHH[h]mm[m]ss[s]')}`
-  }
-  if (status === 1) {
-    exportConfig.columnFilterMethod = ({ column }: any) => {
-      const index = tableData.value.columns.findIndex((item: any) => item.field === column.field)
-      // 排除复选框列
-      return column.field && index !== -1
-    }
-    exportConfig.columns = tableData.value.columns
-  }
-  if (selectedNumber.value > 0) {
-    exportConfig.dataFilterMethod = ({ row }: any) => {
-      const index = selectedTableData.value.findIndex(
-        (item: any) => item._X_ROW_KEY === row._X_ROW_KEY
-      )
-      return index !== -1
-    }
-  }
-  allTableRef.value?.exportData(exportConfig)
-}
-
 const tableLoadingColumn = ref(false)
 const tableLoadingTableData = ref(false)
 
-const selectedNumber = ref(0)
-const selectedTableData = ref([])
-// 复选框选中事件
-const handleCheckboxChange = ({ records }: any) => {
-  selectedNumber.value = records.length
-  selectedTableData.value = records
-}
-const handleCheckAllChange = ({ records }: any) => {
-  selectedNumber.value = records.length
-  selectedTableData.value = records
-}
-
-const logDialogRef = ref()
-const logLoading = ref(false)
-const handleLinkClick = (row) => {
-  logLoading.value = true
-  $api
-    .getAIApiLogDialog({
-      request_id: row['Request ID']
-    })
-    .then((res: any) => {
-      if (res.code === 200) {
-        logLoading.value = false
-        const data = res.data.Data
-        // 打开日志详情对话框
-        logDialogRef.value.openDialog(data.request_content, data.ai_response_content)
-      }
-    })
-}
-
 const handleCreate = () => {
   router.push({
     name: 'Create Report Template'
@@ -367,26 +244,18 @@ const handleCreate = () => {
 }
 
 defineExpose({
-  SearchOperationLog
+  searchTableData
 })
 </script>
 
 <template>
-  <div
-    class="table-box"
-    v-loading.fullscreen.lock="exportLoading"
-    element-loading-text="Loading..."
-    element-loading-custom-class="element-loading"
-    element-loading-background="rgb(43, 47, 54, 0.7)"
-  >
+  <div class="table-box">
     <vxe-grid
       ref="tableRef"
       v-vloading="tableLoadingTableData || tableLoadingColumn"
       :height="props.height"
       :style="{ border: 'none' }"
       v-bind="tableData"
-      @checkbox-change="handleCheckboxChange"
-      @checkbox-all="handleCheckAllChange"
     >
       <!-- action操作栏的插槽 -->
       <template #action="{ row }">
@@ -427,6 +296,10 @@ defineExpose({
           ></span>
         </el-button>
       </template>
+      <!-- Status字段的插槽 -->
+      <template #status="{ row, column }">
+        <VTag :type="row[column.field]">{{ row[column.field] }}</VTag>
+      </template>
       <!-- 空数据时的插槽 -->
       <template #empty v-if="!tableLoadingTableData && tableData.data.length === 0">
         <div class="empty-box">
@@ -436,35 +309,7 @@ defineExpose({
           <p>Click the "Create New Report Template" button to add a report template.</p>
         </div>
       </template>
-      <template #link="{ row, column }">
-        <span style="color: var(--color-theme); cursor: pointer" @click="handleLinkClick(row)">
-          {{ row[column.field] }}
-        </span>
-      </template>
     </vxe-grid>
-    <vxe-grid :height="10" ref="allTableRef" class="all-table" v-bind="allTable"> </vxe-grid>
-    <div class="bottom-pagination">
-      <div class="left-total-records">Total {{ formatNumber(pageInfo.total) }}</div>
-      <div class="right-pagination">
-        <el-pagination
-          v-model:current-page="pageInfo.pageNo"
-          v-model:page-size="pageInfo.pageSize"
-          :page-sizes="[20, 50, 100, 150]"
-          :pager-count="3"
-          background
-          layout="sizes, prev, pager, next"
-          :total="pageInfo.total"
-          @size-change="getTableData(true)"
-          @current-change="getTableData(true)"
-        />
-      </div>
-    </div>
-    <DownloadDialog
-      @export="getExportTableData"
-      :isHideSelectColumn="true"
-      ref="downloadDialogRef"
-    />
-    <LogDialog ref="logDialogRef" />
   </div>
 </template>
 

+ 0 - 196
src/views/TemplateManagement/src/components/TableView/src/components/DownloadDialog.vue

@@ -1,196 +0,0 @@
-<script setup lang="ts">
-const props = withDefaults(
-  defineProps<{
-    isHideSelectColumn: boolean
-  }>(),
-  { isHideSelectColumn: false }
-)
-const dialogVisible = ref(false)
-
-const openDialog = (selectedColumns: string[], slectedDataNumber: number) => {
-  selectedDataNumber.value = slectedDataNumber
-  columns.value = selectedColumns
-  dialogVisible.value = true
-}
-
-const isShowSelectColumn = ref(false)
-
-const downloadFilter = ref(1)
-const selectedDataNumber = ref(0)
-
-const columns = ref()
-
-const emits = defineEmits<{ export: [number] }>()
-const handleDownload = () => {
-  emits('export', downloadFilter.value)
-}
-
-const clearData = () => {
-  isShowSelectColumn.value = false
-  downloadFilter.value = 1
-}
-
-defineExpose({
-  openDialog,
-  handleDownload
-})
-</script>
-
-<template>
-  <div>
-    <el-dialog @close="clearData" v-model="dialogVisible" title="Download File" width="540">
-      <div class="download-dialog">
-        <div class="select-data">
-          <div style="display: inline-block">
-            Select data on your Opeartion Log list:<span style="color: var(--color-theme)">{{
-              selectedDataNumber
-            }}</span>
-          </div>
-        </div>
-        <div class="download-filter" v-if="!props.isHideSelectColumn">
-          <el-radio-group v-model="downloadFilter">
-            <el-radio :value="1"
-              >Download with selected columns
-              <span class="column-number">{{ columns.length }}</span>
-              <SeeAllIcon v-model="isShowSelectColumn" />
-            </el-radio>
-            <div
-              v-if="isShowSelectColumn"
-              class="select-columns"
-              :class="{ show: isShowSelectColumn }"
-            >
-              <div class="title">Selected columns</div>
-              <div class="content">
-                <div class="column-item" v-for="item in columns" :key="item">{{ item }}</div>
-              </div>
-            </div>
-            <el-radio :value="2">Download with all columns</el-radio>
-          </el-radio-group>
-        </div>
-      </div>
-      <template #footer>
-        <div class="dialog-footer">
-          <el-button class="cancel-btn" type="default" @click="dialogVisible = false"
-            >Cancel</el-button
-          >
-          <el-button class="download-btn el-button--dark" @click="handleDownload"
-            ><span style="margin-right: 8px" class="font_family icon-icon_download_b"></span>
-            Download</el-button
-          >
-        </div>
-      </template>
-    </el-dialog>
-  </div>
-</template>
-
-<style lang="scss" scoped>
-.download-dialog {
-  color: var(--color-neutral-1);
-}
-
-.select-data {
-  font-weight: 700;
-}
-
-.data-filter {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 8px;
-  max-height: 120px;
-  margin-top: 8px;
-  overflow: auto;
-
-  .filter-item {
-    height: 22px;
-    padding: 0px 8px;
-    background-color: var(--color-download-file-filter-tag-bg);
-    border-radius: 12px;
-    line-height: 22px;
-    font-size: 12px;
-  }
-}
-
-.download-filter {
-  margin-top: 16px;
-
-  .el-radio-group {
-    display: flex;
-    flex-direction: column;
-    align-items: flex-start;
-
-    .el-radio {
-      height: 40px;
-      align-items: center;
-    }
-
-    :deep(.el-radio__label) {
-      margin-top: 2px;
-      font-weight: 700;
-      color: var(--color-neutral-1);
-    }
-
-    .column-number {
-      padding: 3px 5px;
-      background-color: var(--color-theme);
-      border-radius: 12px;
-      font-size: 12px;
-      font-weight: 700;
-      color: #fff;
-    }
-
-    .see-all-btn {
-      margin-left: 8px;
-      color: var(--color-theme);
-      font-size: 12px;
-    }
-
-    .select-columns {
-      max-height: 350px;
-      padding: 8px;
-      margin-top: 8px;
-      background-color: var(--color-dialog-header-bg);
-      border-radius: 6px;
-      overflow: hidden;
-
-      &.show {
-        max-height: 500px;
-      }
-
-      .title {
-        font-size: 12px;
-        font-weight: 700;
-      }
-
-      .content {
-        display: flex;
-        flex-wrap: wrap;
-        margin-top: 8px;
-        gap: 8px;
-
-        .column-item {
-          height: 22px;
-          padding: 0px 8px;
-          background-color: var(--color-download-file-selected-column-tag-bg);
-          line-height: 22px;
-          border-radius: 12px;
-          font-size: 12px;
-        }
-      }
-    }
-  }
-}
-
-.dialog-footer {
-  .el-button {
-    height: 40px;
-  }
-
-  .cancel-btn {
-    width: 115px;
-  }
-
-  .download-btn {
-    width: 136px;
-  }
-}
-</style>