From 33bd4dd1f0e41131cd8e5bbf87204a1f0b72bb08 Mon Sep 17 00:00:00 2001
From: zhou zhou <3272660260@qq.com>
Date: 星期六, 11 四月 2026 07:45:59 +0800
Subject: [PATCH] #页面优化

---
 rsf-design/src/views/orders/wave/modules/wave-order-rela-panel.vue |   60 +++
 rsf-design/src/views/orders/wave/index.vue                         |  241 ++++++++++++-
 rsf-design/src/views/orders/wave/modules/wave-detail-drawer.vue    |   78 +++-
 rsf-design/vite.config.js                                          |    2 
 rsf-design/src/locales/langs/en.json                               |   36 +
 rsf-design/src/views/orders/wave/modules/wave-item-panel.vue       |  191 +++++++++++
 rsf-design/src/locales/langs/zh.json                               |   36 +
 rsf-design/src/utils/http/index.js                                 |   69 +++
 rsf-design/src/api/wave.js                                         |   30 +
 rsf-design/src/views/orders/wave/wavePage.helpers.js               |   60 +++
 rsf-design/src/views/orders/wave/waveTable.columns.js              |   99 +++++
 rsf-design/.env.production                                         |    3 
 rsf-design/src/utils/http/error.js                                 |   56 +++
 13 files changed, 884 insertions(+), 77 deletions(-)

diff --git a/rsf-design/.env.production b/rsf-design/.env.production
index 1051271..d842b62 100644
--- a/rsf-design/.env.production
+++ b/rsf-design/.env.production
@@ -4,7 +4,8 @@
 VITE_BASE_URL = /
 
 # API 鍦板潃鍓嶇紑
-VITE_API_URL = /rsf-server
+# VITE_API_URL = /rsf-server
+VITE_API_URL = http://127.0.0.1:8085/rsf-server
 
 # Delete console
 VITE_DROP_CONSOLE = true
diff --git a/rsf-design/src/api/wave.js b/rsf-design/src/api/wave.js
index 84b69ff..2a8c967 100644
--- a/rsf-design/src/api/wave.js
+++ b/rsf-design/src/api/wave.js
@@ -55,6 +55,13 @@
   })
 }
 
+export function fetchWaveOrderRelaPage(params = {}) {
+  return request.post({
+    url: '/waveOrderRela/page',
+    params: buildWaveItemPageParams(params)
+  })
+}
+
 export function fetchGetWaveDetail(id) {
   return request.get({
     url: `/wave/${id}`
@@ -103,6 +110,29 @@
   })
 }
 
+export function fetchSelectWaveTask(payload = {}) {
+  return request.post({
+    url: '/wave/selects/task',
+    params: payload
+  })
+}
+
+export function fetchWaveAutoExceFlag() {
+  return request.get({
+    url: '/config/flag/WaveAutoExce'
+  })
+}
+
+export function fetchUpdateWaveAutoExceFlag(enabled) {
+  return request.post({
+    url: '/config/byFlag',
+    params: {
+      val: !!enabled,
+      flag: 'WaveAutoExce'
+    }
+  })
+}
+
 export function fetchCreateOutStockWave(payload = {}) {
   return request.post({
     url: '/outStock/generate/wave',
diff --git a/rsf-design/src/locales/langs/en.json b/rsf-design/src/locales/langs/en.json
index e5ed2d3..e391e9f 100644
--- a/rsf-design/src/locales/langs/en.json
+++ b/rsf-design/src/locales/langs/en.json
@@ -12,7 +12,9 @@
     "requestCancelled": "Request cancelled",
     "networkError": "Network connection error, please check your connection",
     "requestFailed": "Request failed",
-    "requestConfigError": "Request configuration error"
+    "requestConfigError": "Request configuration error",
+    "invalidResponseFormat": "Unexpected API response format, please check the server response",
+    "invalidHtmlResponse": "The API returned an HTML page, please check proxy or deployment routing"
   },
   "topBar": {
     "search": {
@@ -2079,11 +2081,22 @@
           "codePlaceholder": "Enter wave No.",
           "type": "Wave Type",
           "exceStatus": "Wave Status",
+          "anfme": "Wave Qty",
+          "anfmePlaceholder": "Enter wave quantity",
+          "qty": "Executed Qty",
+          "qtyPlaceholder": "Enter executed quantity",
+          "orderNum": "Order Count",
+          "orderNumPlaceholder": "Enter order count",
           "status": "Status",
           "memo": "Remark",
           "memoPlaceholder": "Enter remark",
           "timeStart": "Start Time",
           "timeEnd": "End Time"
+        },
+        "buttons": {
+          "batchPublicTask": "Batch Dispatch",
+          "autoStart": "Enable Auto Run",
+          "autoPause": "Pause Auto Run"
         },
         "status": {
           "type": {
@@ -2110,6 +2123,7 @@
           "type": "Wave Type",
           "exceStatus": "Wave Status",
           "anfme": "Expected Qty",
+          "groupQty": "Merged Qty",
           "workQty": "Running Qty",
           "qty": "Completed Qty",
           "orderNum": "Document Count",
@@ -2117,6 +2131,13 @@
           "createTime": "Created At",
           "updateTime": "Updated At",
           "status": "Status"
+        },
+        "rela": {
+          "asnCode": "Document No.",
+          "platOrderCode": "Platform Order No.",
+          "spec": "Spec",
+          "model": "Model",
+          "unitPlaceholder": "Enter unit"
         },
         "preview": {
           "waveCode": "Wave No.",
@@ -2150,7 +2171,8 @@
           "updateBy": "Updated By",
           "updateTime": "Updated At",
           "memo": "Remark",
-          "previewTitle": "Wave Preview Items - Material Code"
+          "previewTitle": "Wave Preview Items - Material Code",
+          "itemTitle": "Wave Items"
         },
         "publicTask": {
           "title": "Dispatch Wave Task",
@@ -2171,7 +2193,15 @@
           "publicTaskTimeout": "Wave dispatch preview timed out and waiting has stopped",
           "publicTaskSuccess": "Wave dispatched",
           "publicTaskFailed": "Wave dispatch failed",
-          "publicTaskWarning": "Wave preview data is unavailable. Please check location configuration first."
+          "publicTaskWarning": "Wave preview data is unavailable. Please check location configuration first.",
+          "batchPublicTaskSelect": "Please select waves first",
+          "batchPublicTaskConfirm": "Are you sure you want to dispatch {count} selected waves?",
+          "batchPublicTaskTitle": "Batch Dispatch Confirmation",
+          "batchPublicTaskSuccess": "Waves dispatched in batch",
+          "batchPublicTaskFailed": "Batch dispatch failed",
+          "autoStartSuccess": "Auto run enabled",
+          "autoPauseSuccess": "Auto run paused",
+          "autoUpdateFailed": "Failed to update auto run settings"
         }
       },
       "waveItem": {
diff --git a/rsf-design/src/locales/langs/zh.json b/rsf-design/src/locales/langs/zh.json
index 27b8c95..b256485 100644
--- a/rsf-design/src/locales/langs/zh.json
+++ b/rsf-design/src/locales/langs/zh.json
@@ -12,7 +12,9 @@
     "requestCancelled": "璇锋眰宸插彇娑�",
     "networkError": "缃戠粶杩炴帴寮傚父锛岃妫�鏌ョ綉缁滆繛鎺�",
     "requestFailed": "璇锋眰澶辫触",
-    "requestConfigError": "璇锋眰閰嶇疆閿欒"
+    "requestConfigError": "璇锋眰閰嶇疆閿欒",
+    "invalidResponseFormat": "鎺ュ彛鍝嶅簲鏍煎紡寮傚父锛岃妫�鏌ユ湇鍔$杩斿洖",
+    "invalidHtmlResponse": "鎺ュ彛杩斿洖浜� HTML 椤甸潰锛岃妫�鏌ユ帴鍙d唬鐞嗘垨閮ㄧ讲璺敱閰嶇疆"
   },
   "topBar": {
     "search": {
@@ -2087,11 +2089,22 @@
           "codePlaceholder": "璇疯緭鍏ユ尝娆″崟鍙�",
           "type": "娉㈡绫诲瀷",
           "exceStatus": "娉㈡鐘舵��",
+          "anfme": "娉㈡鏁伴噺",
+          "anfmePlaceholder": "璇疯緭鍏ユ尝娆℃暟閲�",
+          "qty": "宸叉墽琛屾暟閲�",
+          "qtyPlaceholder": "璇疯緭鍏ュ凡鎵ц鏁伴噺",
+          "orderNum": "璁㈠崟鏁�",
+          "orderNumPlaceholder": "璇疯緭鍏ヨ鍗曟暟",
           "status": "鐘舵��",
           "memo": "澶囨敞",
           "memoPlaceholder": "璇疯緭鍏ュ娉�",
           "timeStart": "寮�濮嬫椂闂�",
           "timeEnd": "缁撴潫鏃堕棿"
+        },
+        "buttons": {
+          "batchPublicTask": "鎵归噺涓嬪彂",
+          "autoStart": "寮�鍚嚜鍔ㄦ墽琛�",
+          "autoPause": "鏆傚仠鑷姩鎵ц"
         },
         "status": {
           "type": {
@@ -2118,6 +2131,7 @@
           "type": "娉㈡绫诲瀷",
           "exceStatus": "娉㈡鐘舵��",
           "anfme": "搴旂洏鏁伴噺",
+          "groupQty": "鍚堝苟鏁伴噺",
           "workQty": "鎵ц鏁伴噺",
           "qty": "宸茬洏鏁伴噺",
           "orderNum": "鍗曟嵁鏁伴噺",
@@ -2125,6 +2139,13 @@
           "createTime": "鍒涘缓鏃堕棿",
           "updateTime": "鏇存柊鏃堕棿",
           "status": "鐘舵��"
+        },
+        "rela": {
+          "asnCode": "鍗曟嵁鍙�",
+          "platOrderCode": "骞冲彴鍗曞彿",
+          "spec": "瑙勬牸",
+          "model": "鍨嬪彿",
+          "unitPlaceholder": "璇疯緭鍏ュ崟浣�"
         },
         "preview": {
           "waveCode": "娉㈡鍙�",
@@ -2158,7 +2179,8 @@
           "updateBy": "鏇存柊浜�",
           "updateTime": "鏇存柊鏃堕棿",
           "memo": "澶囨敞",
-          "previewTitle": "娉㈡棰勮鏄庣粏 - 鐗╂枡缂栫爜"
+          "previewTitle": "娉㈡棰勮鏄庣粏 - 鐗╂枡缂栫爜",
+          "itemTitle": "娉㈡鏄庣粏"
         },
         "publicTask": {
           "title": "娉㈡涓嬪彂浠诲姟",
@@ -2179,7 +2201,15 @@
           "publicTaskTimeout": "娉㈡涓嬪彂棰勮鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟",
           "publicTaskSuccess": "娉㈡宸蹭笅鍙�",
           "publicTaskFailed": "娉㈡涓嬪彂澶辫触",
-          "publicTaskWarning": "娉㈡棰勮鏁版嵁涓嶅彲鐢紝璇峰厛妫�鏌ュ簱浣嶉厤缃�"
+          "publicTaskWarning": "娉㈡棰勮鏁版嵁涓嶅彲鐢紝璇峰厛妫�鏌ュ簱浣嶉厤缃�",
+          "batchPublicTaskSelect": "璇峰厛閫夋嫨娉㈡",
+          "batchPublicTaskConfirm": "纭畾涓嬪彂閫変腑鐨� {count} 涓尝娆″悧锛�",
+          "batchPublicTaskTitle": "鎵归噺涓嬪彂纭",
+          "batchPublicTaskSuccess": "娉㈡宸叉壒閲忎笅鍙�",
+          "batchPublicTaskFailed": "娉㈡鎵归噺涓嬪彂澶辫触",
+          "autoStartSuccess": "宸插紑鍚嚜鍔ㄦ墽琛�",
+          "autoPauseSuccess": "宸叉殏鍋滆嚜鍔ㄦ墽琛�",
+          "autoUpdateFailed": "鑷姩鎵ц寮�鍏虫洿鏂板け璐�"
         }
       },
       "waveItem": {
diff --git a/rsf-design/src/utils/http/error.js b/rsf-design/src/utils/http/error.js
index 499538f..7488f6f 100644
--- a/rsf-design/src/utils/http/error.js
+++ b/rsf-design/src/utils/http/error.js
@@ -9,6 +9,8 @@
     this.timestamp = /* @__PURE__ */ new Date().toISOString()
     this.url = options?.url
     this.method = options?.method
+    this.status = options?.status
+    this.contentType = options?.contentType
   }
   toLogData() {
     return {
@@ -18,8 +20,50 @@
       timestamp: this.timestamp,
       url: this.url,
       method: this.method,
+      status: this.status,
+      contentType: this.contentType,
       stack: this.stack
     }
+  }
+}
+function resolveRequestUrl(config) {
+  const baseURL = String(config?.baseURL || '').trim()
+  const requestUrl = String(config?.url || '').trim()
+  if (!baseURL && !requestUrl) {
+    return void 0
+  }
+  if (!baseURL) {
+    return requestUrl || void 0
+  }
+  if (!requestUrl) {
+    return baseURL
+  }
+  const isAbsoluteBase = /^https?:\/\//i.test(baseURL)
+  if (!isAbsoluteBase) {
+    const normalizedBase = baseURL.replace(/\/+$/, '')
+    const normalizedUrl = requestUrl.replace(/^\/+/, '')
+    if (!normalizedBase) {
+      return requestUrl
+    }
+    if (!normalizedUrl) {
+      return normalizedBase
+    }
+    return `${normalizedBase}/${normalizedUrl}`
+  }
+  try {
+    const origin = globalThis?.location?.origin || 'http://localhost'
+    const normalizedBase = new URL(baseURL, origin)
+    return new URL(requestUrl, normalizedBase).toString()
+  } catch {
+    const normalizedBase = baseURL.replace(/\/+$/, '')
+    const normalizedUrl = requestUrl.replace(/^\/+/, '')
+    if (!normalizedBase) {
+      return requestUrl
+    }
+    if (!normalizedUrl) {
+      return normalizedBase
+    }
+    return `${normalizedBase}/${normalizedUrl}`
   }
 }
 const getErrorMessage = (status) => {
@@ -52,7 +96,7 @@
   const requestConfig = error.config
   if (isRequestCancelled(error)) {
     throw new HttpError($t('httpMsg.requestCancelled'), 'REQUEST_CANCELLED', {
-      url: requestConfig?.url,
+      url: resolveRequestUrl(requestConfig),
       method: requestConfig?.method?.toUpperCase()
     })
   }
@@ -60,7 +104,7 @@
   const errorMessage = error.response?.data?.msg || error.message
   if (!error.response) {
     throw new HttpError($t('httpMsg.networkError'), ApiStatus.error, {
-      url: requestConfig?.url,
+      url: resolveRequestUrl(requestConfig),
       method: requestConfig?.method?.toUpperCase()
     })
   }
@@ -69,8 +113,10 @@
     : errorMessage || $t('httpMsg.requestFailed')
   throw new HttpError(message, statusCode || ApiStatus.error, {
     data: error.response.data,
-    url: requestConfig?.url,
-    method: requestConfig?.method?.toUpperCase()
+    url: resolveRequestUrl(requestConfig),
+    method: requestConfig?.method?.toUpperCase(),
+    status: statusCode,
+    contentType: error.response?.headers?.['content-type']
   })
 }
 function showError(error, showMessage = true) {
@@ -90,4 +136,4 @@
 const isHttpError = (error) => {
   return error instanceof HttpError
 }
-export { HttpError, handleError, isHttpError, showError, showSuccess }
+export { HttpError, handleError, isHttpError, resolveRequestUrl, showError, showSuccess }
diff --git a/rsf-design/src/utils/http/index.js b/rsf-design/src/utils/http/index.js
index 64720ae..129210c 100644
--- a/rsf-design/src/utils/http/index.js
+++ b/rsf-design/src/utils/http/index.js
@@ -1,7 +1,7 @@
 import axios from 'axios'
 import { useUserStore } from '@/store/modules/user'
 import { ApiStatus } from './status'
-import { HttpError, handleError, showError, showSuccess } from './error'
+import { HttpError, handleError, resolveRequestUrl, showError, showSuccess } from './error'
 import { $t } from '@/locales'
 const REQUEST_TIMEOUT = 30e3
 const LOGOUT_DELAY = 500
@@ -51,21 +51,40 @@
 )
 axiosInstance.interceptors.response.use(
   (response) => {
+    if (!isStandardResponse(response.data)) {
+      throw createInvalidResponseError(response)
+    }
     const { code, msg } = response.data
     if (code === ApiStatus.success) return response
-    if (code === ApiStatus.unauthorized) handleUnauthorizedError(msg)
-    throw createHttpError(msg || $t('httpMsg.requestFailed'), code)
+    if (code === ApiStatus.unauthorized) handleUnauthorizedError(msg, response.config)
+    throw createHttpError(
+      msg || $t('httpMsg.requestFailed'),
+      code ?? ApiStatus.error,
+      createErrorOptions(
+        response.config,
+        {
+          data: response.data,
+          status: response.status,
+          contentType: response.headers?.['content-type']
+        },
+        response
+      )
+    )
   },
   (error) => {
     if (error.response?.status === ApiStatus.unauthorized) handleUnauthorizedError()
     return Promise.reject(handleError(error))
   }
 )
-function createHttpError(message, code) {
-  return new HttpError(message, code)
+function createHttpError(message, code, options) {
+  return new HttpError(message, code, options)
 }
-function handleUnauthorizedError(message) {
-  const error = createHttpError(message || $t('httpMsg.unauthorized'), ApiStatus.unauthorized)
+function handleUnauthorizedError(message, config) {
+  const error = createHttpError(
+    message || $t('httpMsg.unauthorized'),
+    ApiStatus.unauthorized,
+    createErrorOptions(config)
+  )
   if (!isUnauthorizedErrorShown) {
     isUnauthorizedErrorShown = true
     logOut()
@@ -94,6 +113,42 @@
     ApiStatus.gatewayTimeout
   ].includes(statusCode)
 }
+function createErrorOptions(config, extra = {}, response) {
+  return {
+    url: response?.request?.responseURL || resolveRequestUrl(config),
+    method: config?.method?.toUpperCase(),
+    ...extra
+  }
+}
+function isStandardResponse(payload) {
+  return payload !== null && typeof payload === 'object' && !Array.isArray(payload) && 'code' in payload
+}
+function createInvalidResponseError(response) {
+  const responseData = response?.data
+  const responseText =
+    typeof responseData === 'string' ? responseData.slice(0, 300) : responseData
+  const contentType = String(response?.headers?.['content-type'] || '')
+  const looksLikeHtml =
+    contentType.includes('text/html') ||
+    (typeof responseData === 'string' &&
+      /<(!doctype|html|head|body)\b/i.test(responseData.trim().slice(0, 120)))
+  const message = looksLikeHtml
+    ? $t('httpMsg.invalidHtmlResponse')
+    : $t('httpMsg.invalidResponseFormat')
+  return createHttpError(
+    message,
+    ApiStatus.error,
+    createErrorOptions(
+      response?.config,
+      {
+        data: responseText,
+        status: response?.status,
+        contentType
+      },
+      response
+    )
+  )
+}
 async function retryRequest(config, retries = MAX_RETRIES) {
   try {
     return await request(config)
diff --git a/rsf-design/src/views/orders/wave/index.vue b/rsf-design/src/views/orders/wave/index.vue
index 7b2a419..5c36af9 100644
--- a/rsf-design/src/views/orders/wave/index.vue
+++ b/rsf-design/src/views/orders/wave/index.vue
@@ -11,20 +11,44 @@
     <ElCard class="art-table-card">
       <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
         <template #left>
-          <ListExportPrint
-            :preview-visible="previewVisible"
-            @update:previewVisible="handlePreviewVisibleChange"
-            :report-title="reportTitle"
-            :selected-rows="selectedRows"
-            :query-params="reportQueryParams"
-            :columns="columns"
-            :preview-rows="previewRows"
-            :preview-meta="resolvedPreviewMeta"
-            :total="pagination.total"
-            :disabled="loading"
-            @export="handleExport"
-            @print="handlePrint"
-          />
+          <ElSpace wrap>
+            <ElButton
+              type="primary"
+              :loading="batchTaskSubmitting"
+              :disabled="loading || batchTaskSubmitting || !selectedRows.length"
+              @click="handleBatchPublicTask"
+            >
+              {{ t('pages.orders.wave.buttons.batchPublicTask') }}
+            </ElButton>
+            <ElButton
+              :loading="autoExceSubmitting"
+              :disabled="loading || autoExceSubmitting || autoExce"
+              @click="toggleAutoExce(true)"
+            >
+              {{ t('pages.orders.wave.buttons.autoStart') }}
+            </ElButton>
+            <ElButton
+              :loading="autoExceSubmitting"
+              :disabled="loading || autoExceSubmitting || !autoExce"
+              @click="toggleAutoExce(false)"
+            >
+              {{ t('pages.orders.wave.buttons.autoPause') }}
+            </ElButton>
+            <ListExportPrint
+              :preview-visible="previewVisible"
+              @update:previewVisible="handlePreviewVisibleChange"
+              :report-title="reportTitle"
+              :selected-rows="selectedRows"
+              :query-params="reportQueryParams"
+              :columns="columns"
+              :preview-rows="previewRows"
+              :preview-meta="resolvedPreviewMeta"
+              :total="pagination.total"
+              :disabled="loading"
+              @export="handleExport"
+              @print="handlePrint"
+            />
+          </ElSpace>
         </template>
       </ArtTableHeader>
 
@@ -68,8 +92,8 @@
 </template>
 
 <script setup>
-  import { computed, reactive, ref } from 'vue'
-  import { ElMessage, ElMessageBox } from 'element-plus'
+  import { computed, onMounted, reactive, ref } from 'vue'
+  import { ElButton, ElMessage, ElMessageBox, ElSpace } from 'element-plus'
   import { useI18n } from 'vue-i18n'
   import { useUserStore } from '@/store/modules/user'
   import { useTable } from '@/hooks/core/useTable'
@@ -101,7 +125,10 @@
     fetchGetWaveMany,
     fetchPauseWave,
     fetchPublicWaveTask,
+    fetchSelectWaveTask,
     fetchStopWave,
+    fetchUpdateWaveAutoExceFlag,
+    fetchWaveAutoExceFlag,
     fetchWavePage,
     fetchWavePreviewPage
   } from '@/api/wave'
@@ -125,6 +152,9 @@
   const publicTaskSubmitting = ref(false)
   const publicTaskWave = ref({})
   const publicTaskRows = ref([])
+  const autoExce = ref(false)
+  const batchTaskSubmitting = ref(false)
+  const autoExceSubmitting = ref(false)
 
   const detailPagination = reactive({
     current: 1,
@@ -142,7 +172,9 @@
   const detailColumns = computed(() => createWaveDetailItemColumns(t))
   const publicTaskColumns = computed(() => createWavePreviewItemColumns(t))
   const publicTaskCanSubmit = computed(
-    () => publicTaskRows.value.length > 0 && publicTaskRows.value.every((row) => row.stockLocsText && row.stockLocsText !== '[]')
+    () =>
+      publicTaskRows.value.length > 0 &&
+      publicTaskRows.value.every((row) => row.stockLocsText && row.stockLocsText !== '[]')
   )
   const publicTaskWarningText = computed(() => t('pages.orders.wave.messages.publicTaskWarning'))
   const searchItems = computed(() => [
@@ -185,6 +217,36 @@
       }
     },
     {
+      label: t('pages.orders.wave.search.anfme'),
+      key: 'anfme',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        controlsPosition: 'right',
+        placeholder: t('pages.orders.wave.search.anfmePlaceholder')
+      }
+    },
+    {
+      label: t('pages.orders.wave.search.qty'),
+      key: 'qty',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        controlsPosition: 'right',
+        placeholder: t('pages.orders.wave.search.qtyPlaceholder')
+      }
+    },
+    {
+      label: t('pages.orders.wave.search.orderNum'),
+      key: 'orderNum',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        controlsPosition: 'right',
+        placeholder: t('pages.orders.wave.search.orderNumPlaceholder')
+      }
+    },
+    {
       label: t('pages.orders.wave.search.status'),
       key: 'status',
       type: 'select',
@@ -202,8 +264,18 @@
       type: 'input',
       props: { clearable: true, placeholder: t('pages.orders.wave.search.memoPlaceholder') }
     },
-    { label: t('pages.orders.wave.search.timeStart'), key: 'timeStart', type: 'date', props: { clearable: true, type: 'date', valueFormat: 'YYYY-MM-DD' } },
-    { label: t('pages.orders.wave.search.timeEnd'), key: 'timeEnd', type: 'date', props: { clearable: true, type: 'date', valueFormat: 'YYYY-MM-DD' } }
+    {
+      label: t('pages.orders.wave.search.timeStart'),
+      key: 'timeStart',
+      type: 'date',
+      props: { clearable: true, type: 'date', valueFormat: 'YYYY-MM-DD' }
+    },
+    {
+      label: t('pages.orders.wave.search.timeEnd'),
+      key: 'timeEnd',
+      type: 'date',
+      props: { clearable: true, type: 'date', valueFormat: 'YYYY-MM-DD' }
+    }
   ])
 
   function updatePaginationState(target, response, fallbackCurrent, fallbackSize) {
@@ -254,11 +326,15 @@
         return
       }
       if (action.key === 'stop') {
-        await ElMessageBox.confirm(t('pages.orders.wave.messages.stopConfirm', { code: row.code || '' }), t('pages.orders.wave.messages.stopTitle'), {
-          confirmButtonText: t('common.confirm'),
-          cancelButtonText: t('common.cancel'),
-          type: 'warning'
-        })
+        await ElMessageBox.confirm(
+          t('pages.orders.wave.messages.stopConfirm', { code: row.code || '' }),
+          t('pages.orders.wave.messages.stopTitle'),
+          {
+            confirmButtonText: t('common.confirm'),
+            cancelButtonText: t('common.cancel'),
+            type: 'warning'
+          }
+        )
         await fetchStopWave(row.id)
         ElMessage.success(t('pages.orders.wave.messages.stopSuccess'))
         await refreshData()
@@ -288,7 +364,8 @@
       columnsFactory: () => createWaveTableColumns({ handleActionClick, t })
     },
     transform: {
-      dataTransformer: (records) => (Array.isArray(records) ? records.map((item) => normalizeWaveRow(item, t)) : [])
+      dataTransformer: (records) =>
+        Array.isArray(records) ? records.map((item) => normalizeWaveRow(item, t)) : []
     }
   })
 
@@ -300,16 +377,33 @@
     detailLoading.value = true
     try {
       const [detailResponse, previewResponse] = await Promise.all([
-        guardRequestWithMessage(fetchGetWaveDetail(activeWaveId.value), {}, { timeoutMessage: t('pages.orders.wave.messages.detailTimeout') }),
         guardRequestWithMessage(
-          fetchWavePreviewPage(buildWaveDetailQueryParams({ waveId: activeWaveId.value, current: detailPagination.current, pageSize: detailPagination.size })),
+          fetchGetWaveDetail(activeWaveId.value),
+          {},
+          { timeoutMessage: t('pages.orders.wave.messages.detailTimeout') }
+        ),
+        guardRequestWithMessage(
+          fetchWavePreviewPage(
+            buildWaveDetailQueryParams({
+              waveId: activeWaveId.value,
+              current: detailPagination.current,
+              pageSize: detailPagination.size
+            })
+          ),
           { records: [], total: 0, current: detailPagination.current, size: detailPagination.size },
           { timeoutMessage: t('pages.orders.wave.messages.previewTimeout') }
         )
       ])
       detailData.value = normalizeWaveRow(detailResponse, t)
-      detailTableData.value = Array.isArray(previewResponse?.records) ? previewResponse.records.map((item) => normalizeWaveItemRow(item, t)) : []
-      updatePaginationState(detailPagination, previewResponse, detailPagination.current, detailPagination.size)
+      detailTableData.value = Array.isArray(previewResponse?.records)
+        ? previewResponse.records.map((item) => normalizeWaveItemRow(item, t))
+        : []
+      updatePaginationState(
+        detailPagination,
+        previewResponse,
+        detailPagination.current,
+        detailPagination.size
+      )
     } finally {
       detailLoading.value = false
     }
@@ -323,12 +417,30 @@
     publicTaskLoading.value = true
     try {
       const previewResponse = await guardRequestWithMessage(
-        fetchWavePreviewPage(buildWaveDetailQueryParams({ waveId: publicTaskWave.value.id, current: publicTaskPagination.current, pageSize: publicTaskPagination.size })),
-        { records: [], total: 0, current: publicTaskPagination.current, size: publicTaskPagination.size },
+        fetchWavePreviewPage(
+          buildWaveDetailQueryParams({
+            waveId: publicTaskWave.value.id,
+            current: publicTaskPagination.current,
+            pageSize: publicTaskPagination.size
+          })
+        ),
+        {
+          records: [],
+          total: 0,
+          current: publicTaskPagination.current,
+          size: publicTaskPagination.size
+        },
         { timeoutMessage: t('pages.orders.wave.messages.publicTaskTimeout') }
       )
-      publicTaskRows.value = Array.isArray(previewResponse?.records) ? previewResponse.records.map((item) => normalizeWaveItemRow(item, t)) : []
-      updatePaginationState(publicTaskPagination, previewResponse, publicTaskPagination.current, publicTaskPagination.size)
+      publicTaskRows.value = Array.isArray(previewResponse?.records)
+        ? previewResponse.records.map((item) => normalizeWaveItemRow(item, t))
+        : []
+      updatePaginationState(
+        publicTaskPagination,
+        previewResponse,
+        publicTaskPagination.current,
+        publicTaskPagination.size
+      )
     } finally {
       publicTaskLoading.value = false
     }
@@ -355,6 +467,65 @@
       ElMessage.error(error?.message || t('pages.orders.wave.messages.publicTaskFailed'))
     } finally {
       publicTaskSubmitting.value = false
+    }
+  }
+
+  async function handleBatchPublicTask() {
+    if (!selectedRows.value.length) {
+      ElMessage.warning(t('pages.orders.wave.messages.batchPublicTaskSelect'))
+      return
+    }
+    try {
+      await ElMessageBox.confirm(
+        t('pages.orders.wave.messages.batchPublicTaskConfirm', {
+          count: selectedRows.value.length
+        }),
+        t('pages.orders.wave.messages.batchPublicTaskTitle'),
+        {
+          confirmButtonText: t('common.confirm'),
+          cancelButtonText: t('common.cancel'),
+          type: 'warning'
+        }
+      )
+      batchTaskSubmitting.value = true
+      await fetchSelectWaveTask({
+        ids: selectedRows.value.map((item) => item.id)
+      })
+      ElMessage.success(t('pages.orders.wave.messages.batchPublicTaskSuccess'))
+      selectedRows.value = []
+      await refreshData()
+    } catch (error) {
+      if (error === 'cancel' || error?.message === 'cancel') return
+      ElMessage.error(error?.message || t('pages.orders.wave.messages.batchPublicTaskFailed'))
+    } finally {
+      batchTaskSubmitting.value = false
+    }
+  }
+
+  async function toggleAutoExce(enabled) {
+    try {
+      autoExceSubmitting.value = true
+      await fetchUpdateWaveAutoExceFlag(enabled)
+      autoExce.value = !!enabled
+      ElMessage.success(
+        enabled
+          ? t('pages.orders.wave.messages.autoStartSuccess')
+          : t('pages.orders.wave.messages.autoPauseSuccess')
+      )
+    } catch (error) {
+      ElMessage.error(error?.message || t('pages.orders.wave.messages.autoUpdateFailed'))
+    } finally {
+      autoExceSubmitting.value = false
+    }
+  }
+
+  async function loadAutoExceFlag() {
+    try {
+      const response = await fetchWaveAutoExceFlag()
+      const rawVal = response?.val ?? response?.value ?? false
+      autoExce.value = rawVal === true || rawVal === 'true'
+    } catch {
+      autoExce.value = false
     }
   }
 
@@ -446,4 +617,8 @@
       t
     })
   )
+
+  onMounted(() => {
+    loadAutoExceFlag()
+  })
 </script>
diff --git a/rsf-design/src/views/orders/wave/modules/wave-detail-drawer.vue b/rsf-design/src/views/orders/wave/modules/wave-detail-drawer.vue
index c0ffe6a..615c5d7 100644
--- a/rsf-design/src/views/orders/wave/modules/wave-detail-drawer.vue
+++ b/rsf-design/src/views/orders/wave/modules/wave-detail-drawer.vue
@@ -8,32 +8,66 @@
     <ElScrollbar class="wave-detail-scroll">
       <div class="flex min-h-full flex-col gap-4 pr-2">
         <ElDescriptions :column="4" border>
-          <ElDescriptionsItem :label="t('pages.orders.wave.detail.code')">{{ detail.code || '--' }}</ElDescriptionsItem>
-          <ElDescriptionsItem :label="t('pages.orders.wave.detail.type')">{{ detail.typeLabel || '--' }}</ElDescriptionsItem>
-          <ElDescriptionsItem :label="t('pages.orders.wave.detail.exceStatus')">{{ detail.exceStatusText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem :label="t('pages.orders.wave.detail.code')">{{
+            detail.code || '--'
+          }}</ElDescriptionsItem>
+          <ElDescriptionsItem :label="t('pages.orders.wave.detail.type')">{{
+            detail.typeLabel || '--'
+          }}</ElDescriptionsItem>
+          <ElDescriptionsItem :label="t('pages.orders.wave.detail.exceStatus')">{{
+            detail.exceStatusText || '--'
+          }}</ElDescriptionsItem>
           <ElDescriptionsItem :label="t('pages.orders.wave.detail.status')">
             <ElTag :type="Number(detail.status) === 1 ? 'success' : 'danger'" effect="light">
               {{ detail.statusLabel || '--' }}
             </ElTag>
           </ElDescriptionsItem>
-          <ElDescriptionsItem :label="t('pages.orders.wave.detail.anfme')">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
-          <ElDescriptionsItem :label="t('pages.orders.wave.detail.workQty')">{{ detail.workQty ?? '--' }}</ElDescriptionsItem>
-          <ElDescriptionsItem :label="t('pages.orders.wave.detail.qty')">{{ detail.qty ?? '--' }}</ElDescriptionsItem>
-          <ElDescriptionsItem :label="t('pages.orders.wave.detail.orderNum')">{{ detail.orderNum ?? '--' }}</ElDescriptionsItem>
-          <ElDescriptionsItem :label="t('pages.orders.wave.detail.groupQty')">{{ detail.groupQty ?? '--' }}</ElDescriptionsItem>
-          <ElDescriptionsItem :label="t('pages.orders.wave.detail.targSite')">{{ detail.targSite || '--' }}</ElDescriptionsItem>
-          <ElDescriptionsItem :label="t('pages.orders.wave.detail.stationId')">{{ detail.stationId || '--' }}</ElDescriptionsItem>
-          <ElDescriptionsItem :label="t('pages.orders.wave.detail.locCode')">{{ detail.locCode || '--' }}</ElDescriptionsItem>
-          <ElDescriptionsItem :label="t('pages.orders.wave.detail.createBy')">{{ detail.createByText || '--' }}</ElDescriptionsItem>
-          <ElDescriptionsItem :label="t('pages.orders.wave.detail.createTime')">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
-          <ElDescriptionsItem :label="t('pages.orders.wave.detail.updateBy')">{{ detail.updateByText || '--' }}</ElDescriptionsItem>
-          <ElDescriptionsItem :label="t('pages.orders.wave.detail.updateTime')">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
-          <ElDescriptionsItem :label="t('pages.orders.wave.detail.memo')" :span="4">{{ detail.memo || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem :label="t('pages.orders.wave.detail.anfme')">{{
+            detail.anfme ?? '--'
+          }}</ElDescriptionsItem>
+          <ElDescriptionsItem :label="t('pages.orders.wave.detail.workQty')">{{
+            detail.workQty ?? '--'
+          }}</ElDescriptionsItem>
+          <ElDescriptionsItem :label="t('pages.orders.wave.detail.qty')">{{
+            detail.qty ?? '--'
+          }}</ElDescriptionsItem>
+          <ElDescriptionsItem :label="t('pages.orders.wave.detail.orderNum')">{{
+            detail.orderNum ?? '--'
+          }}</ElDescriptionsItem>
+          <ElDescriptionsItem :label="t('pages.orders.wave.detail.groupQty')">{{
+            detail.groupQty ?? '--'
+          }}</ElDescriptionsItem>
+          <ElDescriptionsItem :label="t('pages.orders.wave.detail.targSite')">{{
+            detail.targSite || '--'
+          }}</ElDescriptionsItem>
+          <ElDescriptionsItem :label="t('pages.orders.wave.detail.stationId')">{{
+            detail.stationId || '--'
+          }}</ElDescriptionsItem>
+          <ElDescriptionsItem :label="t('pages.orders.wave.detail.locCode')">{{
+            detail.locCode || '--'
+          }}</ElDescriptionsItem>
+          <ElDescriptionsItem :label="t('pages.orders.wave.detail.createBy')">{{
+            detail.createByText || '--'
+          }}</ElDescriptionsItem>
+          <ElDescriptionsItem :label="t('pages.orders.wave.detail.createTime')">{{
+            detail.createTimeText || '--'
+          }}</ElDescriptionsItem>
+          <ElDescriptionsItem :label="t('pages.orders.wave.detail.updateBy')">{{
+            detail.updateByText || '--'
+          }}</ElDescriptionsItem>
+          <ElDescriptionsItem :label="t('pages.orders.wave.detail.updateTime')">{{
+            detail.updateTimeText || '--'
+          }}</ElDescriptionsItem>
+          <ElDescriptionsItem :label="t('pages.orders.wave.detail.memo')" :span="4">{{
+            detail.memo || '--'
+          }}</ElDescriptionsItem>
         </ElDescriptions>
 
         <ElCard shadow="never" class="border border-[var(--art-border-color)]">
           <template #header>
-            <div class="text-sm font-medium text-[var(--art-text-gray-800)]">{{ t('pages.orders.wave.detail.previewTitle') }}</div>
+            <div class="text-sm font-medium text-[var(--art-text-gray-800)]">{{
+              t('pages.orders.wave.detail.previewTitle')
+            }}</div>
           </template>
           <ArtTable
             :loading="loading"
@@ -44,6 +78,15 @@
             @pagination:current-change="$emit('current-change', $event)"
           />
         </ElCard>
+
+        <ElCard shadow="never" class="border border-[var(--art-border-color)]">
+          <template #header>
+            <div class="text-sm font-medium text-[var(--art-text-gray-800)]">
+              {{ t('pages.orders.wave.detail.itemTitle') }}
+            </div>
+          </template>
+          <WaveItemPanel :wave-id="detail.id" />
+        </ElCard>
       </div>
     </ElScrollbar>
   </ElDrawer>
@@ -51,6 +94,7 @@
 
 <script setup>
   import { useI18n } from 'vue-i18n'
+  import WaveItemPanel from './wave-item-panel.vue'
 
   defineOptions({ name: 'WaveDetailDrawer' })
   const { t } = useI18n()
diff --git a/rsf-design/src/views/orders/wave/modules/wave-item-panel.vue b/rsf-design/src/views/orders/wave/modules/wave-item-panel.vue
new file mode 100644
index 0000000..b562d84
--- /dev/null
+++ b/rsf-design/src/views/orders/wave/modules/wave-item-panel.vue
@@ -0,0 +1,191 @@
+<template>
+  <div class="flex flex-col gap-4">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ArtTable
+      :loading="loading"
+      :data="rows"
+      :columns="columns"
+      :pagination="pagination"
+      @pagination:size-change="handleSizeChange"
+      @pagination:current-change="handleCurrentChange"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { computed, reactive, ref, watch } from 'vue'
+  import { useI18n } from 'vue-i18n'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import { fetchWaveItemPage } from '@/api/wave-item'
+  import {
+    buildWaveItemPageQueryParams,
+    createWaveItemSearchState,
+    normalizeWaveItemRow
+  } from '@/views/orders/wave-item/waveItemPage.helpers'
+  import { createWaveItemTableColumns } from '@/views/orders/wave-item/waveItemTable.columns'
+
+  const props = defineProps({
+    waveId: { type: [Number, String], default: undefined }
+  })
+
+  const { t } = useI18n()
+  const loading = ref(false)
+  const rows = ref([])
+  const searchForm = ref(createWaveItemSearchState())
+  const pagination = reactive({
+    current: 1,
+    size: 20,
+    total: 0
+  })
+
+  const columns = computed(() =>
+    createWaveItemTableColumns({
+      t,
+      handleActionClick: () => {}
+    }).filter((column) => column.prop !== 'operation')
+  )
+
+  const searchItems = computed(() => [
+    {
+      label: t('pages.orders.waveItem.search.condition'),
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: t('pages.orders.waveItem.search.conditionPlaceholder')
+      }
+    },
+    {
+      label: t('pages.orders.waveItem.search.waveCode'),
+      key: 'waveCode',
+      type: 'input',
+      props: { clearable: true, placeholder: t('pages.orders.waveItem.search.waveCodePlaceholder') }
+    },
+    {
+      label: t('pages.orders.waveItem.search.orderCode'),
+      key: 'orderCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: t('pages.orders.waveItem.search.orderCodePlaceholder')
+      }
+    },
+    {
+      label: t('pages.orders.waveItem.search.matnrCode'),
+      key: 'matnrCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: t('pages.orders.waveItem.search.matnrCodePlaceholder')
+      }
+    },
+    {
+      label: t('pages.orders.waveItem.search.maktx'),
+      key: 'maktx',
+      type: 'input',
+      props: { clearable: true, placeholder: t('pages.orders.waveItem.search.maktxPlaceholder') }
+    },
+    {
+      label: t('pages.orders.waveItem.search.batch'),
+      key: 'batch',
+      type: 'input',
+      props: { clearable: true, placeholder: t('pages.orders.waveItem.search.batchPlaceholder') }
+    },
+    {
+      label: t('pages.orders.waveItem.search.splrBatch'),
+      key: 'splrBatch',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: t('pages.orders.waveItem.search.splrBatchPlaceholder')
+      }
+    },
+    {
+      label: t('table.unit'),
+      key: 'unit',
+      type: 'input',
+      props: { clearable: true, placeholder: t('pages.orders.wave.rela.unitPlaceholder') }
+    },
+    {
+      label: t('pages.orders.waveItem.search.fieldsIndex'),
+      key: 'fieldsIndex',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: t('pages.orders.waveItem.search.fieldsIndexPlaceholder')
+      }
+    }
+  ])
+
+  function buildParams() {
+    return buildWaveItemPageQueryParams({
+      ...searchForm.value,
+      waveId: props.waveId,
+      current: pagination.current,
+      pageSize: pagination.size
+    })
+  }
+
+  async function loadRows() {
+    if (props.waveId === undefined || props.waveId === null || props.waveId === '') {
+      rows.value = []
+      pagination.total = 0
+      return
+    }
+    loading.value = true
+    try {
+      const response = await fetchWaveItemPage(buildParams())
+      const normalized = defaultResponseAdapter(response)
+      rows.value = Array.isArray(normalized.records)
+        ? normalized.records.map((item) => normalizeWaveItemRow(item, t))
+        : []
+      pagination.total = Number(normalized.total || 0)
+      pagination.current = Number(normalized.current || pagination.current || 1)
+      pagination.size = Number(normalized.size || pagination.size || 20)
+    } catch {
+      rows.value = []
+      pagination.total = 0
+    } finally {
+      loading.value = false
+    }
+  }
+
+  function handleSearch(params) {
+    searchForm.value = { ...searchForm.value, ...params }
+    pagination.current = 1
+    loadRows()
+  }
+
+  function handleReset() {
+    searchForm.value = createWaveItemSearchState()
+    pagination.current = 1
+    loadRows()
+  }
+
+  function handleSizeChange(size) {
+    pagination.size = size
+    pagination.current = 1
+    loadRows()
+  }
+
+  function handleCurrentChange(current) {
+    pagination.current = current
+    loadRows()
+  }
+
+  watch(
+    () => props.waveId,
+    () => {
+      pagination.current = 1
+      loadRows()
+    },
+    { immediate: true }
+  )
+</script>
diff --git a/rsf-design/src/views/orders/wave/modules/wave-order-rela-panel.vue b/rsf-design/src/views/orders/wave/modules/wave-order-rela-panel.vue
new file mode 100644
index 0000000..fd1e5e4
--- /dev/null
+++ b/rsf-design/src/views/orders/wave/modules/wave-order-rela-panel.vue
@@ -0,0 +1,60 @@
+<template>
+  <div class="wave-order-rela-panel">
+    <ArtTable :loading="loading" :data="rows" :columns="columns" />
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref, watch } from 'vue'
+  import { useI18n } from 'vue-i18n'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import { fetchWaveOrderRelaPage } from '@/api/wave'
+  import { normalizeWaveOrderRelaRow } from '../wavePage.helpers'
+  import { createWaveOrderRelaColumns } from '../waveTable.columns'
+
+  const props = defineProps({
+    waveId: { type: [Number, String], default: undefined }
+  })
+
+  const loading = ref(false)
+  const rows = ref([])
+  const { t } = useI18n()
+  const columns = computed(() => createWaveOrderRelaColumns(t))
+
+  async function loadRows() {
+    if (props.waveId === undefined || props.waveId === null || props.waveId === '') {
+      rows.value = []
+      return
+    }
+    loading.value = true
+    try {
+      const response = await fetchWaveOrderRelaPage({
+        waveId: Number(props.waveId),
+        current: 1,
+        pageSize: 200
+      })
+      const records = defaultResponseAdapter(response).records
+      rows.value = Array.isArray(records)
+        ? records.map((item) => normalizeWaveOrderRelaRow(item))
+        : []
+    } catch {
+      rows.value = []
+    } finally {
+      loading.value = false
+    }
+  }
+
+  watch(
+    () => props.waveId,
+    () => {
+      loadRows()
+    },
+    { immediate: true }
+  )
+</script>
+
+<style scoped>
+  .wave-order-rela-panel {
+    padding: 12px 0;
+  }
+</style>
diff --git a/rsf-design/src/views/orders/wave/wavePage.helpers.js b/rsf-design/src/views/orders/wave/wavePage.helpers.js
index 705c383..bb2df45 100644
--- a/rsf-design/src/views/orders/wave/wavePage.helpers.js
+++ b/rsf-design/src/views/orders/wave/wavePage.helpers.js
@@ -80,16 +80,20 @@
     code: '',
     type: '',
     exceStatus: '',
+    anfme: '',
+    qty: '',
+    orderNum: '',
     status: '',
     memo: '',
     timeStart: '',
-    timeEnd: ''
+    timeEnd: '',
+    orderBy: 'create_time desc'
   }
 }
 
 export function buildWaveSearchParams(params = {}) {
   const result = {}
-  ;['condition', 'code', 'memo', 'timeStart', 'timeEnd'].forEach((key) => {
+  ;['condition', 'code', 'memo', 'timeStart', 'timeEnd', 'orderBy'].forEach((key) => {
     const value = normalizeText(params[key])
     if (value) {
       result[key] = value
@@ -108,6 +112,12 @@
     result.status = Number(params.status)
   }
 
+  ;['anfme', 'qty', 'orderNum'].forEach((key) => {
+    if (params[key] !== '' && params[key] !== undefined && params[key] !== null) {
+      result[key] = Number(params[key])
+    }
+  })
+
   return result
 }
 
@@ -115,6 +125,7 @@
   return {
     current: params.current || 1,
     pageSize: params.pageSize || params.size || 20,
+    orderBy: normalizeText(params.orderBy) || 'create_time desc',
     ...buildWaveSearchParams(params)
   }
 }
@@ -129,19 +140,30 @@
 
 export function normalizeWaveRow(record = {}, t) {
   const statusConfig = getStatusConfig(record.exceStatus, record['exceStatus$'], t)
-  const progress = normalizeNumber(record.anfme) > 0
-    ? Math.min(Math.round((normalizeNumber(record.workQty) / normalizeNumber(record.anfme)) * 100), 100)
-    : 0
+  const progress =
+    normalizeNumber(record.anfme) > 0
+      ? Math.min(
+          Math.round((normalizeNumber(record.workQty) / normalizeNumber(record.anfme)) * 100),
+          100
+        )
+      : 0
 
   return {
     ...record,
     code: record.code || '-',
-    typeLabel: record['type$'] || translate(t, `pages.orders.wave.status.type.${record.type}`) || record.type || '-',
+    typeLabel:
+      record['type$'] ||
+      translate(t, `pages.orders.wave.status.type.${record.type}`) ||
+      record.type ||
+      '-',
     exceStatusText: statusConfig.label,
     exceStatusTagType: statusConfig.tagType,
     statusLabel:
       record['status$'] ||
-      translate(t, Number(record.status) === 1 ? 'common.status.normal' : 'common.status.disabled') ||
+      translate(
+        t,
+        Number(record.status) === 1 ? 'common.status.normal' : 'common.status.disabled'
+      ) ||
       record.status ||
       '-',
     anfme: normalizeNumber(record.anfme),
@@ -169,13 +191,18 @@
   const statusConfig = getItemStatusConfig(record.exceStatus, record['exceStatus$'], t)
   return {
     ...record,
+    id: record.id ?? null,
+    waveId: record.waveId ?? '-',
     waveCode: record.waveCode || '-',
     orderCode: record.orderCode || '-',
+    orderItemId: record.orderItemId ?? '-',
+    matnrId: record.matnrId ?? '-',
     matnrCode: record.matnrCode || '-',
     maktx: record.maktx || '-',
     batch: record.batch || '-',
     splrBatch: record.splrBatch || '-',
     unit: record.unit || '-',
+    memo: record.memo || '-',
     trackCode: record.trackCode || '-',
     fieldsIndex: record.fieldsIndex || '-',
     anfme: normalizeNumber(record.anfme),
@@ -183,6 +210,8 @@
     workQty: normalizeNumber(record.workQty),
     stockQty: normalizeNumber(record.stockQty),
     stockLocsText: normalizeStockLocs(record.stockLocs),
+    updateByText: record['updateBy$'] || record.updateBy || '-',
+    createByText: record['createBy$'] || record.createBy || '-',
     statusLabel: record['status$'] || record.status || '-',
     exceStatusText: statusConfig.label,
     exceStatusTagType: statusConfig.tagType,
@@ -319,3 +348,20 @@
     ...buildWaveItemSearchParams(params)
   }
 }
+
+export function normalizeWaveOrderRelaRow(record = {}) {
+  return {
+    ...record,
+    id: record.id ?? null,
+    asnCode: record.asnCode || record.orderCode || '-',
+    matnrCode: record.matnrCode || '-',
+    maktx: record.maktx || '-',
+    splrBatch: record.splrBatch || '-',
+    platOrderCode: record.platOrderCode || '-',
+    spec: record.spec || '-',
+    model: record.model || '-',
+    stockUnit: record.stockUnit || '-',
+    splrName: record.splrName || '-',
+    anfme: normalizeNumber(record.anfme)
+  }
+}
diff --git a/rsf-design/src/views/orders/wave/waveTable.columns.js b/rsf-design/src/views/orders/wave/waveTable.columns.js
index 0de06b2..eebbcc0 100644
--- a/rsf-design/src/views/orders/wave/waveTable.columns.js
+++ b/rsf-design/src/views/orders/wave/waveTable.columns.js
@@ -3,12 +3,30 @@
 import { $t } from '@/locales'
 import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
 import { getWaveActionList } from './wavePage.helpers'
+import WaveOrderRelaPanel from './modules/wave-order-rela-panel.vue'
 
 export function createWaveTableColumns({ handleActionClick, t }) {
   const translate = typeof t === 'function' ? t : $t
   return [
+    {
+      type: 'expand',
+      width: 56,
+      formatter: (row) => ({
+        render() {
+          return h(WaveOrderRelaPanel, {
+            waveId: row.id
+          })
+        }
+      })
+    },
     { type: 'selection', width: 48, align: 'center' },
     { type: 'globalIndex', label: translate('table.index'), width: 72, align: 'center' },
+    {
+      prop: 'id',
+      label: translate('table.id'),
+      width: 90,
+      align: 'center'
+    },
     {
       prop: 'code',
       label: translate('pages.orders.wave.table.code'),
@@ -34,6 +52,12 @@
     {
       prop: 'anfme',
       label: translate('pages.orders.wave.table.anfme'),
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'groupQty',
+      label: translate('pages.orders.wave.table.groupQty'),
       width: 110,
       align: 'right'
     },
@@ -81,6 +105,18 @@
       showOverflowTooltip: true
     },
     {
+      prop: 'updateByText',
+      label: translate('table.updateBy'),
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'createByText',
+      label: translate('table.createBy'),
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
       prop: 'statusLabel',
       label: translate('pages.orders.wave.table.status'),
       width: 100,
@@ -90,6 +126,12 @@
           { type: Number(row.status) === 1 ? 'success' : 'danger', effect: 'light' },
           () => row.statusLabel
         )
+    },
+    {
+      prop: 'memo',
+      label: translate('table.memo'),
+      minWidth: 150,
+      showOverflowTooltip: true
     },
     {
       prop: 'operation',
@@ -181,3 +223,60 @@
 export function createWaveDetailItemColumns(t) {
   return createWavePreviewItemColumns(t)
 }
+
+export function createWaveOrderRelaColumns(t) {
+  const translate = typeof t === 'function' ? t : $t
+  return [
+    { type: 'globalIndex', label: translate('table.index'), width: 72, align: 'center' },
+    {
+      prop: 'asnCode',
+      label: translate('pages.orders.wave.rela.asnCode'),
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'matnrCode',
+      label: translate('table.materialCode'),
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'maktx',
+      label: translate('table.materialName'),
+      minWidth: 220,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'splrBatch',
+      label: translate('table.supplierBatch'),
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'platOrderCode',
+      label: translate('pages.orders.wave.rela.platOrderCode'),
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'spec',
+      label: translate('pages.orders.wave.rela.spec'),
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'model',
+      label: translate('pages.orders.wave.rela.model'),
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    { prop: 'anfme', label: translate('table.quantity'), width: 100, align: 'right' },
+    { prop: 'stockUnit', label: translate('table.unit'), width: 90, align: 'center' },
+    {
+      prop: 'splrName',
+      label: translate('table.supplier'),
+      minWidth: 150,
+      showOverflowTooltip: true
+    }
+  ]
+}
diff --git a/rsf-design/vite.config.js b/rsf-design/vite.config.js
index a142af7..95692d4 100644
--- a/rsf-design/vite.config.js
+++ b/rsf-design/vite.config.js
@@ -56,7 +56,7 @@
           manualChunks: createManualChunks
         }
       },
-      minify: 'terser',
+      minify: 'esbuild',
       terserOptions: {
         compress: {
           drop_console: true,

--
Gitblit v1.9.1