Sfoglia il codice sorgente

feat: 实现delivery部分新功能

Jack Zhou 1 mese fa
parent
commit
6579371d88

+ 16 - 264
src/views/DestinationDelivery/src/DestinationDelivery.vue

@@ -1,79 +1,11 @@
 <script lang="ts" setup>
-import { useCalculatingHeight } from '@/hooks/calculatingHeight'
-import TableView from './components/TableView'
-import DeliveryDate from './components/DeliveryDate.vue'
+import ListView from './components/ListView.vue'
+import CalendarView from './components/CalendarView.vue'
 import { useRouter } from 'vue-router'
 
 const router = useRouter()
-const filterRef: Ref<HTMLElement | null> = ref(null)
-const containerHeight = useCalculatingHeight(document.documentElement, 376, [filterRef])
-const queryData = ref({
-  text_search: '',
-  created_time_start: '',
-  created_time_end: '',
-  delivery_mode: '',
-  delivery_date_start: '',
-  delivery_date_end: '',
-  filterTag: ['ALL']
-})
 
-const modeList = [
-  {
-    label: 'Truck',
-    value: 'Truck'
-  },
-  {
-    label: 'Rail',
-    value: 'Rail'
-  }
-]
-
-const numberCards = ref([
-  {
-    label: 'Total Bookings',
-    key: 'ALL',
-    value: 0,
-    color: '#2b2f36'
-  },
-  {
-    label: 'Pending Approval',
-    value: 0,
-    color: '#edb82f',
-    icon: 'icon_time_b'
-  },
-  {
-    label: 'Approved',
-    value: 0,
-    color: '#00a870',
-    icon: 'icon_confirm_b'
-  },
-  {
-    label: 'Rejected',
-    value: 0,
-    color: '#c9353f',
-    icon: 'icon_reject_b'
-  },
-  {
-    label: 'Cancelled',
-    value: 0,
-    color: '#243041',
-    icon: 'icon_cancelled_b'
-  }
-])
-const clickCard = (filterTagItem: string) => {
-  queryData.value.filterTag.pop()
-  queryData.value.filterTag.push(filterTagItem)
-  handleSearch()
-}
-
-const DateChange = (date: any) => {
-  queryData.value.created_time_start = date ? date[0] : ''
-  queryData.value.created_time_end = date ? date[1] : ''
-}
-const deliveryDataChange = (date) => {
-  queryData.value.delivery_date_start = date ? date[0] : ''
-  queryData.value.delivery_date_end = date ? date[1] : ''
-}
+const listView = ref(null)
 
 const handleConfigurations = () => {
   router.push({ name: 'Configurations' })
@@ -82,16 +14,7 @@ const handleCreate = () => {
   router.push({ name: 'Create New Booking' })
 }
 
-const DateStart = ref([])
-const tableRef = ref()
-
-const setNumberCards = (cards) => {
-  numberCards.value = cards
-}
-
-const handleSearch = () => {
-  tableRef.value.SearchOperationLog()
-}
+const controllerType = ref('button')
 </script>
 <template>
   <div class="destination-delivery">
@@ -102,7 +25,7 @@ const handleSearch = () => {
           style="height: 40px"
           type="default"
           @click="handleConfigurations"
-          v-if="tableRef?.isEmployeeRole === true"
+          v-if="listView?.isEmployeeRole === true"
         >
           <span style="margin-right: 4px" class="font_family icon-icon_configurations_b"></span>
           <span style="font-weight: 400">Configurations</span></el-button
@@ -111,89 +34,27 @@ const handleSearch = () => {
           style="height: 38px"
           class="el-button--main el-button--pain-theme"
           @click="handleCreate"
-          v-if="tableRef?.isEmployeeRole === false"
+          v-if="listView?.isEmployeeRole === false"
         >
           <span style="margin-right: 4px" class="font_family icon-icon_add_b"></span>
           <span style="font-weight: 400">Create New Booking</span>
         </el-button>
       </div>
     </div>
-    <div class="display">
-      <div class="header_top">
-        <div class="date-tips_filter filter-item">
-          <span class="label">Delivery Date:</span>
-          <DeliveryDate @DateChange="deliveryDataChange" :Date="DateStart" />
-        </div>
-
-        <div class="tips_filter filter-item">
-          <span class="label">Delivery Mode:</span>
-          <el-select v-model="queryData.delivery_mode" placeholder="" clearable>
-            <el-option
-              v-for="item in modeList"
-              :key="item.value"
-              :label="item.label"
-              :value="item.value"
-            />
-          </el-select>
-        </div>
-        <div class="date-tips_filter filter-item">
-          <span class="label">Creation Date</span>
-          <CalendarDate :isShowPopupClass="true" @DateChange="DateChange"></CalendarDate>
-        </div>
-        <div class="input-tips_filter filter-item">
-          <el-input
-            placeholder="Search Question Booking No、HBOL No、MBL No、Container No、Consignee"
-            v-model="queryData.text_search"
-            class="log_input"
-          >
-            <template #prefix>
-              <span class="iconfont_icon">
-                <svg class="iconfont icon_dark" aria-hidden="true">
-                  <use xlink:href="#icon-icon_search_b"></use>
-                </svg>
-              </span>
-            </template>
-          </el-input>
-        </div>
-        <el-button class="el-button--dark" @click="handleSearch">Search</el-button>
-      </div>
-      <div class="number-cards">
-        <div
-          class="card"
-          :class="{
-            'is-active':
-              queryData.filterTag.includes(item.key as string | undefined) ||
-              queryData.filterTag.includes(item.label)
-          }"
-          @click="clickCard((item.key || item.label) as string)"
-          v-for="(item, index) in numberCards"
-          :key="index"
-        >
-          <div class="card-label">{{ item.label }}</div>
-          <div
-            class="card-value"
-            :style="{
-              color: item.label === 'Cancelled' ? `var(--color-card-number-cancelled)` : item.color
-            }"
-          >
-            {{ item.value }}
-          </div>
-          <div class="icon-box" v-if="item.icon">
-            <span class="font_family" :class="'icon-' + item.icon"></span>
-          </div>
-        </div>
-      </div>
-    </div>
-    <TableView
-      @get-number-cards="setNumberCards"
-      :height="containerHeight"
-      :queryData="queryData"
-      ref="tableRef"
-    ></TableView>
+    <el-radio-group v-model="controllerType">
+      <el-radio-button label="select" value="select" />
+      <el-radio-button label="button" value="button" />
+    </el-radio-group>
+    <ListView ref="listView" v-if="controllerType === 'select'"></ListView>
+    <CalendarView v-if="controllerType === 'button'"></CalendarView>
   </div>
 </template>
 
 <style lang="scss" scoped>
+.destination-delivery {
+  position: relative;
+  background-color: var(--color-mode);
+}
 .header {
   display: flex;
   height: 68px;
@@ -204,113 +65,4 @@ const handleSearch = () => {
   align-items: center;
   justify-content: space-between;
 }
-.header_top {
-  margin-bottom: 8px;
-  padding-right: 8px;
-  display: flex;
-  align-items: flex-end;
-}
-.number-cards {
-  display: flex;
-  justify-content: space-between;
-  margin: 8px 0;
-  gap: 8px;
-  .card {
-    position: relative;
-    flex: 1;
-    height: 80px;
-    padding: 12px 16px;
-    background-color: var(--color-email-bg);
-    border-radius: 12px;
-    & > .icon-box {
-      position: absolute;
-      right: 16px;
-      top: 12px;
-      width: 24px;
-      height: 24px;
-      border-radius: 6px;
-      background-color: var(--color-card-icon-box-bg);
-      text-align: center;
-      line-height: 24px;
-    }
-    &.is-active {
-      background-color: var(--color-mune-active-bg);
-      .card-label,
-      .card-value {
-        color: var(--color-theme) !important;
-      }
-      .icon-box {
-        background-color: rgba(#ff7500, 0.1);
-        span {
-          color: var(--color-theme);
-        }
-      }
-    }
-
-    .card-value {
-      margin-top: 4px;
-      font-size: 32px;
-      font-weight: 700;
-    }
-  }
-}
-
-.display {
-  border: 1px solid var(--color-border);
-  border-width: 0 0 1px 0;
-  padding: 0 24px;
-}
-:deep(.el-select__placeholder.is-transparent span) {
-  color: var(--tag-info-text-color) !important;
-}
-:deep(.ETD_title) {
-  margin-bottom: 0;
-}
-:deep(.ant-picker-range) {
-  width: 250px !important;
-  height: 32px;
-  background-color: var(--color-mode) !important;
-}
-.filter-item {
-  flex: 1;
-  display: flex;
-  flex-direction: column;
-  align-items: flex-end;
-  justify-content: flex-end;
-  margin-right: 8px;
-  .label {
-    align-self: flex-start;
-    font-size: 12px;
-    margin-bottom: 3px;
-  }
-}
-.tips_filter {
-  min-width: 170px;
-  max-width: 220px;
-}
-.input-tips_filter {
-  max-width: 540px;
-  :deep(.el-input__wrapper) {
-    height: 32px;
-    input {
-      width: 100%;
-      white-space: nowrap; /* 防止换行 */
-      overflow: hidden; /* 超出部分隐藏 */
-      text-overflow: ellipsis; /* 超出部分显示为省略号 */
-    }
-  }
-}
-.date-tips_filter {
-  max-width: 250px;
-  height: 56px;
-}
-
-.destination-delivery {
-  position: relative;
-  background-color: var(--color-mode);
-}
-:deep(.log_input .el-input__wrapper) {
-  box-shadow: 0 0 0 1px var(--color-select-border);
-  border-radius: 6px;
-}
 </style>

+ 142 - 0
src/views/DestinationDelivery/src/components/CalendarView.vue

@@ -0,0 +1,142 @@
+<script setup lang="ts">
+import { ref } from 'vue'
+
+const value = ref()
+
+// ✅ 修正:精确匹配年月日
+const getListData = (current: Date) => {
+  const dateStr = current.toISOString().split('T')[0] // "YYYY-MM-DD"
+
+  // 定义事件数据(示例:2026年1月)
+  const events: Record<string, Array<{ type: string; content: string }>> = {
+    '2026-01-08': [
+      { type: 'warning', content: 'This is warning event.' },
+      { type: 'success', content: 'This is usual event.' }
+    ],
+    '2026-01-10': [
+      { type: 'warning', content: 'This is warning event.' },
+      { type: 'success', content: 'This is usual event.' },
+      { type: 'error', content: 'This is error event.' }
+    ],
+    '2026-01-15': [
+      { type: 'warning', content: 'This is warning event' },
+      { type: 'success', content: 'This is very long usual event。。....' },
+      { type: 'error', content: 'This is error event 1.' },
+      { type: 'error', content: 'This is error event 2.' },
+      { type: 'error', content: 'This is error event 3.' },
+      { type: 'error', content: 'This is error event 4.' }
+    ]
+  }
+
+  return events[dateStr] || []
+}
+
+const getMonthData = (current: Date) => {
+  // 示例:2026年9月(注意:JS 月份从 0 开始)
+  if (current.getFullYear() === 2026 && current.getMonth() === 8) {
+    return 1394
+  }
+  return null
+}
+</script>
+
+<template>
+  <div class="calendar-container">
+    <!-- 关键:添加 fullscreen 属性 -->
+    <a-calendar
+      v-model:value="value"
+      fullscreen
+      :date-cell-render="({ current }) => $slots.dateCellRender?.({ current })"
+      :month-cell-render="({ current }) => $slots.monthCellRender?.({ current })"
+    >
+      <template #dateCellRender="{ current }">
+        <ul class="events">
+          <li v-for="(item, index) in getListData(current)" :key="index">
+            <a-badge :status="item.type" :text="item.content" />
+          </li>
+        </ul>
+      </template>
+
+      <template #monthCellRender="{ current }">
+        <div v-if="getMonthData(current)" class="notes-month">
+          <section>{{ getMonthData(current) }}</section>
+          <span>Backlog number</span>
+        </div>
+      </template>
+    </a-calendar>
+  </div>
+</template>
+
+<style scoped>
+/* 容器宽度必须足够 */
+.calendar-container {
+  width: 860px;
+  margin: 20px auto;
+}
+
+/* 事件列表样式 */
+.events {
+  list-style: none;
+  margin: 0;
+  padding: 0;
+  max-height: 80px;
+  overflow: hidden;
+}
+
+.events li {
+  margin-top: 4px;
+  font-size: 12px;
+}
+
+.events .ant-badge-status {
+  width: 100%;
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+}
+
+/* 月份备注样式 */
+.notes-month {
+  text-align: center;
+  font-size: 28px;
+  line-height: 1.2;
+}
+.notes-month span {
+  display: block;
+  font-size: 14px;
+  color: rgba(0, 0, 0, 0.45);
+}
+
+/* 使用 :deep() 穿透 scoped 限制 */
+:deep(.ant-picker-calendar) {
+  width: 100%;
+  max-width: 900px;
+  margin: 0 auto;
+}
+
+:deep(.ant-picker-calendar-date) {
+  width: calc(100% / 7) !important;
+  display: block !important;
+  float: left !important; /* 关键:确保横向排列 */
+  box-sizing: border-box !important;
+}
+
+/* 清除可能的 flex 干扰 */
+:deep(.ant-picker-calendar-body),
+:deep(.ant-picker-calendar-panel) {
+  display: block !important;
+  flex: none !important;
+}
+
+/* ✅ 修复:仅作用于弹窗日期选择器,不影响 ACalendar */
+/* .ant-picker-dropdown {
+  .ant-picker-cell-inner {
+    display: flex !important;
+    align-items: center !important;
+    justify-content: center !important;
+    width: 32px !important;
+    height: 32px !important;
+    border-radius: 6px !important;
+  }
+} */
+</style>

+ 281 - 0
src/views/DestinationDelivery/src/components/ListView.vue

@@ -0,0 +1,281 @@
+<script setup lang="ts">
+import { useCalculatingHeight } from '@/hooks/calculatingHeight'
+import TableView from './TableView'
+import DeliveryDate from './DeliveryDate.vue'
+
+const filterRef: Ref<HTMLElement | null> = ref(null)
+const containerHeight = useCalculatingHeight(document.documentElement, 376, [filterRef])
+
+const queryData = ref({
+  text_search: '',
+  created_time_start: '',
+  created_time_end: '',
+  delivery_mode: '',
+  delivery_date_start: '',
+  delivery_date_end: '',
+  filterTag: ['ALL']
+})
+const isEmployeeRole = computed(() => {
+  return tableRef.value?.isEmployeeRole
+})
+
+const modeList = [
+  {
+    label: 'Truck',
+    value: 'Truck'
+  },
+  {
+    label: 'Rail',
+    value: 'Rail'
+  }
+]
+
+const numberCards = ref([
+  {
+    label: 'Total Bookings',
+    key: 'ALL',
+    value: 0,
+    color: '#2b2f36'
+  },
+  {
+    label: 'Pending Approval',
+    value: 0,
+    color: '#edb82f',
+    icon: 'icon_time_b'
+  },
+  {
+    label: 'Approved',
+    value: 0,
+    color: '#00a870',
+    icon: 'icon_confirm_b'
+  },
+  {
+    label: 'Rejected',
+    value: 0,
+    color: '#c9353f',
+    icon: 'icon_reject_b'
+  },
+  {
+    label: 'Cancelled',
+    value: 0,
+    color: '#243041',
+    icon: 'icon_cancelled_b'
+  }
+])
+const clickCard = (filterTagItem: string) => {
+  queryData.value.filterTag.pop()
+  queryData.value.filterTag.push(filterTagItem)
+  handleSearch()
+}
+
+const DateChange = (date: any) => {
+  queryData.value.created_time_start = date ? date[0] : ''
+  queryData.value.created_time_end = date ? date[1] : ''
+}
+const deliveryDataChange = (date) => {
+  queryData.value.delivery_date_start = date ? date[0] : ''
+  queryData.value.delivery_date_end = date ? date[1] : ''
+}
+
+const DateStart = ref([])
+const tableRef = ref()
+
+const setNumberCards = (cards) => {
+  numberCards.value = cards
+}
+
+const handleSearch = () => {
+  tableRef.value.SearchOperationLog()
+}
+
+defineExpose({
+  isEmployeeRole
+})
+</script>
+
+<template>
+  <div class="display">
+    <div class="header_top">
+      <div class="date-tips_filter filter-item">
+        <span class="label">Delivery Date:</span>
+        <DeliveryDate @DateChange="deliveryDataChange" :Date="DateStart" />
+      </div>
+
+      <div class="tips_filter filter-item">
+        <span class="label">Delivery Mode:</span>
+        <el-select v-model="queryData.delivery_mode" placeholder="" clearable>
+          <el-option
+            v-for="item in modeList"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+      </div>
+      <div class="date-tips_filter filter-item">
+        <span class="label">Creation Date</span>
+        <CalendarDate :isShowPopupClass="true" @DateChange="DateChange"></CalendarDate>
+      </div>
+      <div class="input-tips_filter filter-item">
+        <el-input
+          placeholder="Search Question Booking No、HBOL No、MBL No、Container No、Consignee"
+          v-model="queryData.text_search"
+          class="log_input"
+        >
+          <template #prefix>
+            <span class="iconfont_icon">
+              <svg class="iconfont icon_dark" aria-hidden="true">
+                <use xlink:href="#icon-icon_search_b"></use>
+              </svg>
+            </span>
+          </template>
+        </el-input>
+      </div>
+      <el-button class="el-button--dark" @click="handleSearch">Search</el-button>
+    </div>
+    <div class="number-cards">
+      <div
+        class="card"
+        :class="{
+          'is-active':
+            queryData.filterTag.includes(item.key as string | undefined) ||
+            queryData.filterTag.includes(item.label)
+        }"
+        @click="clickCard((item.key || item.label) as string)"
+        v-for="(item, index) in numberCards"
+        :key="index"
+      >
+        <div class="card-label">{{ item.label }}</div>
+        <div
+          class="card-value"
+          :style="{
+            color: item.label === 'Cancelled' ? `var(--color-card-number-cancelled)` : item.color
+          }"
+        >
+          {{ item.value }}
+        </div>
+        <div class="icon-box" v-if="item.icon">
+          <span class="font_family" :class="'icon-' + item.icon"></span>
+        </div>
+      </div>
+    </div>
+  </div>
+  <TableView
+    @get-number-cards="setNumberCards"
+    :height="containerHeight"
+    :queryData="queryData"
+    ref="tableRef"
+  ></TableView>
+</template>
+
+<style lang="scss" scoped>
+.header_top {
+  margin-bottom: 8px;
+  padding-right: 8px;
+  display: flex;
+  align-items: flex-end;
+}
+.number-cards {
+  display: flex;
+  justify-content: space-between;
+  margin: 8px 0;
+  gap: 8px;
+  .card {
+    position: relative;
+    flex: 1;
+    height: 80px;
+    padding: 12px 16px;
+    background-color: var(--color-email-bg);
+    border-radius: 12px;
+    & > .icon-box {
+      position: absolute;
+      right: 16px;
+      top: 12px;
+      width: 24px;
+      height: 24px;
+      border-radius: 6px;
+      background-color: var(--color-card-icon-box-bg);
+      text-align: center;
+      line-height: 24px;
+    }
+    &.is-active {
+      background-color: var(--color-mune-active-bg);
+      .card-label,
+      .card-value {
+        color: var(--color-theme) !important;
+      }
+      .icon-box {
+        background-color: rgba(#ff7500, 0.1);
+        span {
+          color: var(--color-theme);
+        }
+      }
+    }
+
+    .card-value {
+      margin-top: 4px;
+      font-size: 32px;
+      font-weight: 700;
+    }
+  }
+}
+
+.display {
+  border: 1px solid var(--color-border);
+  border-width: 0 0 1px 0;
+  padding: 0 24px;
+}
+:deep(.el-select__placeholder.is-transparent span) {
+  color: var(--tag-info-text-color) !important;
+}
+:deep(.ETD_title) {
+  margin-bottom: 0;
+}
+:deep(.ant-picker-range) {
+  width: 250px !important;
+  height: 32px;
+  background-color: var(--color-mode) !important;
+}
+.filter-item {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: flex-end;
+  justify-content: flex-end;
+  margin-right: 8px;
+  .label {
+    align-self: flex-start;
+    font-size: 12px;
+    margin-bottom: 3px;
+  }
+}
+.tips_filter {
+  min-width: 170px;
+  max-width: 220px;
+}
+.input-tips_filter {
+  max-width: 540px;
+  :deep(.el-input__wrapper) {
+    height: 32px;
+    input {
+      width: 100%;
+      white-space: nowrap; /* 防止换行 */
+      overflow: hidden; /* 超出部分隐藏 */
+      text-overflow: ellipsis; /* 超出部分显示为省略号 */
+    }
+  }
+}
+.date-tips_filter {
+  max-width: 250px;
+  height: 56px;
+}
+
+.destination-delivery {
+  position: relative;
+  background-color: var(--color-mode);
+}
+:deep(.log_input .el-input__wrapper) {
+  box-shadow: 0 0 0 1px var(--color-select-border);
+  border-radius: 6px;
+}
+</style>