From 1d95b134d85c3c60cf0e72739888c9741a0bb1ee Mon Sep 17 00:00:00 2001
From: zhou zhou <3272660260@qq.com>
Date: 星期五, 10 四月 2026 13:20:39 +0800
Subject: [PATCH] #页面优化

---
 rsf-design/src/locales/langs/en.json                                                 |  234 +++
 rsf-design/src/locales/langs/zh.json                                                 |  234 +++
 rsf-design/src/api/wh-mat.js                                                         |  147 ++
 rsf-design/src/views/basic-info/wh-mat/modules/wh-mat-batch-group-dialog.vue         |  113 +
 rsf-design/src/views/basic-info/bas-station-area/basStationAreaTable.columns.js      |   42 
 rsf-design/src/views/basic-info/wh-mat/modules/wh-mat-dialog.vue                     |  380 ++++++
 rsf-design/src/views/basic-info/wh-mat/modules/wh-mat-bind-loc-dialog.vue            |  184 +++
 rsf-design/src/views/basic-info/bas-station-area/basStationAreaPage.helpers.js       |   89 +
 rsf-design/src/views/basic-info/wh-mat/whMatPage.helpers.js                          |  446 ++++++-
 rsf-design/src/hooks/core/useAuth.js                                                 |    1 
 rsf-design/src/views/basic-info/wh-mat/index.vue                                     |  996 ++++++++++++++++
 rsf-design/src/views/basic-info/wh-mat/whMatTable.columns.js                         |  118 +
 rsf-design/src/views/basic-info/bas-station-area/modules/bas-station-area-dialog.vue |  215 +--
 rsf-design/src/views/basic-info/wh-mat/modules/wh-mat-batch-dialog.vue               |  232 ++++
 14 files changed, 3,082 insertions(+), 349 deletions(-)

diff --git a/rsf-design/src/api/wh-mat.js b/rsf-design/src/api/wh-mat.js
index ed04047..2bc5a50 100644
--- a/rsf-design/src/api/wh-mat.js
+++ b/rsf-design/src/api/wh-mat.js
@@ -4,13 +4,53 @@
   return String(value ?? '').trim()
 }
 
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((item) => Number(item))
+      .filter((item) => Number.isFinite(item))
+      .join(',')
+  }
+  return String(ids ?? '')
+    .split(',')
+    .map((item) => Number(item))
+    .filter((item) => Number.isFinite(item))
+    .join(',')
+}
+
+function normalizeIdList(ids) {
+  if (Array.isArray(ids)) {
+    return ids.map((item) => Number(item)).filter((item) => Number.isFinite(item))
+  }
+  return String(ids ?? '')
+    .split(',')
+    .map((item) => Number(item))
+    .filter((item) => Number.isFinite(item))
+}
+
 function normalizeQueryParams(params = {}) {
+  const allowKeys = new Set([
+    'condition',
+    'code',
+    'name',
+    'spec',
+    'model',
+    'color',
+    'size',
+    'barcode',
+    'groupId',
+    'unit',
+    'status',
+    'flagCheck',
+    'validWarn'
+  ])
   const result = {
     current: params.current || 1,
-    pageSize: params.pageSize || params.size || 20
+    pageSize: params.pageSize || params.size || 20,
+    orderBy: normalizeText(params.orderBy) || 'create_time desc'
   }
 
-  ;['condition', 'code', 'name', 'spec', 'model', 'color', 'size', 'barcode', 'groupId'].forEach((key) => {
+  Array.from(allowKeys).forEach((key) => {
     const value = params[key]
     if (value === undefined || value === null || value === '') {
       return
@@ -19,6 +59,29 @@
       const trimmed = normalizeText(value)
       if (trimmed) {
         result[key] = trimmed
+      }
+      return
+    }
+    result[key] = value
+  })
+
+  Object.entries(params).forEach(([key, value]) => {
+    if (['current', 'pageSize', 'size', 'orderBy'].includes(key) || allowKeys.has(key)) {
+      return
+    }
+    if (value === undefined || value === null || value === '') {
+      return
+    }
+    if (typeof value === 'string') {
+      const trimmed = normalizeText(value)
+      if (trimmed) {
+        result[key] = trimmed
+      }
+      return
+    }
+    if (Array.isArray(value)) {
+      if (value.length) {
+        result[key] = value
       }
       return
     }
@@ -45,6 +108,53 @@
   return request.get({ url: `/matnr/${id}` })
 }
 
+export function fetchGetMatnrMany(ids) {
+  return request.post({ url: `/matnr/many/${normalizeIds(ids)}` })
+}
+
+export function fetchEnabledFields() {
+  return request.get({ url: '/fields/enable/list' })
+}
+
+export function fetchSaveMatnr(params = {}) {
+  return request.post({ url: '/matnr/save', params })
+}
+
+export function fetchUpdateMatnr(params = {}) {
+  return request.post({ url: '/matnr/update', params })
+}
+
+export function fetchDeleteMatnr(ids) {
+  return request.post({ url: `/matnr/remove/${normalizeIds(ids)}` })
+}
+
+export function fetchBindMatnrGroup(payload = {}) {
+  return request.post({
+    url: '/matnr/group/bind',
+    params: {
+      ids: normalizeIdList(payload.ids),
+      ...(payload.groupId !== undefined && payload.groupId !== null && payload.groupId !== ''
+        ? { groupId: Number(payload.groupId) }
+        : {})
+    }
+  })
+}
+
+export function fetchBatchUpdateMatnr(payload = {}) {
+  const matnr = payload.matnr && typeof payload.matnr === 'object' ? payload.matnr : {}
+  return request.post({
+    url: '/matnr/batch/update',
+    params: {
+      ids: normalizeIdList(payload.ids),
+      matnr: Object.fromEntries(
+        Object.entries(matnr).filter(
+          ([, value]) => value !== undefined && value !== null && value !== ''
+        )
+      )
+    }
+  })
+}
+
 export function fetchMatnrGroupTree(params = {}) {
   return request.post({
     url: '/matnrGroup/tree',
@@ -52,3 +162,36 @@
   })
 }
 
+export async function fetchExportMatnrReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/matnr/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
+
+export async function fetchDownloadMatnrTemplate(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/matnr/template/download`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
+
+export function fetchImportMatnr(file) {
+  const formData = new FormData()
+  formData.append('file', file)
+  return request.post({
+    url: '/matnr/import',
+    data: formData,
+    headers: {
+      'Content-Type': 'multipart/form-data'
+    }
+  })
+}
diff --git a/rsf-design/src/hooks/core/useAuth.js b/rsf-design/src/hooks/core/useAuth.js
index b222810..e7ffe24 100644
--- a/rsf-design/src/hooks/core/useAuth.js
+++ b/rsf-design/src/hooks/core/useAuth.js
@@ -28,6 +28,7 @@
 const BUTTON_ACTION_MAP = {
   query: 'list',
   add: 'save',
+  update: 'update',
   edit: 'update',
   delete: 'remove'
 }
diff --git a/rsf-design/src/locales/langs/en.json b/rsf-design/src/locales/langs/en.json
index 1bbe119..f4ef9d8 100644
--- a/rsf-design/src/locales/langs/en.json
+++ b/rsf-design/src/locales/langs/en.json
@@ -182,20 +182,11 @@
   "setting": {
     "menuType": {
       "title": "Menu Layout",
-      "list": [
-        "Vertical",
-        "Horizontal",
-        "Mixed",
-        "Dual"
-      ]
+      "list": ["Vertical", "Horizontal", "Mixed", "Dual"]
     },
     "theme": {
       "title": "Theme Style",
-      "list": [
-        "Light",
-        "Dark",
-        "System"
-      ]
+      "list": ["Light", "Dark", "System"]
     },
     "menu": {
       "title": "Menu Style"
@@ -205,17 +196,11 @@
     },
     "box": {
       "title": "Box Style",
-      "list": [
-        "Border",
-        "Shadow"
-      ]
+      "list": ["Border", "Shadow"]
     },
     "container": {
       "title": "Container Width",
-      "list": [
-        "Full",
-        "Boxed"
-      ]
+      "list": ["Full", "Boxed"]
     },
     "basics": {
       "title": "Basic Config",
@@ -659,21 +644,21 @@
       "send": "Send",
       "renameDialogTitle": "Rename Session",
       "sessionTitleField": "Session Title",
-        "requestMetric": "Req: {value}",
-        "sessionMetric": "Session: {id}",
-        "promptMetric": "Prompt: {value}",
-        "modelMetric": "Model: {value}",
-        "promptLabel": "Prompt",
-        "modelLabel": "Model",
-        "modelSelectorLabel": "Chat Model",
-        "modelSelectorHint": "Switching only affects subsequent replies in this session and does not change the global default model.",
-        "modelSwitchFailed": "Failed to switch the chat model",
-        "defaultModelSuffix": "(Default)",
-        "mcpMetric": "MCP: {value}",
-        "historyMetric": "History: {value}",
-        "mcpLabel": "MCP",
-        "historyLabel": "History",
-        "recentMetric": "Recent: {value}",
+      "requestMetric": "Req: {value}",
+      "sessionMetric": "Session: {id}",
+      "promptMetric": "Prompt: {value}",
+      "modelMetric": "Model: {value}",
+      "promptLabel": "Prompt",
+      "modelLabel": "Model",
+      "modelSelectorLabel": "Chat Model",
+      "modelSelectorHint": "Switching only affects subsequent replies in this session and does not change the global default model.",
+      "modelSwitchFailed": "Failed to switch the chat model",
+      "defaultModelSuffix": "(Default)",
+      "mcpMetric": "MCP: {value}",
+      "historyMetric": "History: {value}",
+      "mcpLabel": "MCP",
+      "historyLabel": "History",
+      "recentMetric": "Recent: {value}",
       "elapsedMetric": "Elapsed: {value} ms",
       "firstTokenMetric": "First token: {value} ms",
       "tokenMetric": "Tokens: prompt {prompt} / completion {completion} / total {total}",
@@ -848,12 +833,12 @@
           "systemPrompt": "System Prompt",
           "userPromptTemplate": "User Prompt Template"
         },
-          "dialog": {
-            "titleCreate": "Create Prompt",
-            "titleEdit": "Edit Prompt",
-            "titleDetail": "Prompt Detail",
-            "defaultPreviewInput": "Please summarize the current input",
-            "previewTitle": "Render Preview",
+        "dialog": {
+          "titleCreate": "Create Prompt",
+          "titleEdit": "Edit Prompt",
+          "titleDetail": "Prompt Detail",
+          "defaultPreviewInput": "Please summarize the current input",
+          "previewTitle": "Render Preview",
           "previewDescription": "Input sample content and metadata to preview the final rendering.",
           "previewAction": "Render Preview",
           "previewResolvedVariables": "Resolved variables: {value}",
@@ -2465,8 +2450,20 @@
       },
       "whMat": {
         "title": "Materials",
+        "entity": "Material",
         "labels": {
           "allMaterials": "All Materials"
+        },
+        "actions": {
+          "add": "Add Material",
+          "batchGroup": "Batch Group",
+          "batchWarn": "Batch Warning",
+          "batchFlagCheck": "Batch QC",
+          "batchStatus": "Batch Status",
+          "batchStockLevel": "Batch Stock Level",
+          "bindLoc": "Bind Location",
+          "import": "Import",
+          "downloadTemplate": "Download Template"
         },
         "search": {
           "groupKeywordPlaceholder": "Search material groups",
@@ -2478,19 +2475,76 @@
           "codePlaceholder": "Enter material code",
           "name": "Material Name",
           "namePlaceholder": "Enter material name",
+          "groupId": "Material Group",
+          "groupIdPlaceholder": "Select material group",
+          "platCode": "Platform Code",
+          "platCodePlaceholder": "Enter platform code",
           "spec": "Specification",
           "specPlaceholder": "Enter specification",
+          "model": "Model",
+          "modelPlaceholder": "Enter model",
+          "color": "Color",
+          "colorPlaceholder": "Enter color",
+          "size": "Size",
+          "sizePlaceholder": "Enter size",
+          "unit": "Unit",
+          "unitPlaceholder": "Enter unit",
+          "purUnit": "Purchase Unit",
+          "purUnitPlaceholder": "Enter purchase unit",
+          "stockUnit": "Stock Unit",
+          "stockUnitPlaceholder": "Enter stock unit",
           "barcode": "Barcode",
-          "barcodePlaceholder": "Enter barcode"
+          "barcodePlaceholder": "Enter barcode",
+          "describle": "Description",
+          "describlePlaceholder": "Enter description",
+          "rglarId": "Batch Rule",
+          "rglarIdPlaceholder": "Select batch rule",
+          "weight": "Weight",
+          "weightPlaceholder": "Enter weight",
+          "nromNum": "Standard Pack Qty",
+          "nromNumPlaceholder": "Enter standard pack qty",
+          "stockLevel": "Stock Level",
+          "stockLevelPlaceholder": "Select stock level",
+          "flagLabelMange": "Label Management",
+          "flagLabelMangePlaceholder": "Select label management",
+          "safeQty": "Safe Qty",
+          "safeQtyPlaceholder": "Enter safe qty",
+          "minQty": "Min Qty",
+          "minQtyPlaceholder": "Enter min qty",
+          "maxQty": "Max Qty",
+          "maxQtyPlaceholder": "Enter max qty",
+          "stagn": "Stagnant Days",
+          "stagnPlaceholder": "Enter stagnant days",
+          "valid": "Shelf Life Days",
+          "validPlaceholder": "Enter shelf life days",
+          "validWarn": "Expiry Warning",
+          "validWarnPlaceholder": "Enter expiry warning",
+          "flagCheck": "Exempt Inspection",
+          "flagCheckPlaceholder": "Select exempt inspection",
+          "status": "Status",
+          "statusPlaceholder": "Select status",
+          "memo": "Memo",
+          "memoPlaceholder": "Enter memo",
+          "dynamicPlaceholder": "Enter {field}"
         },
         "messages": {
           "emptyGroups": "No material groups",
           "groupTimeout": "Material groups loading timed out and waiting has stopped",
           "groupLoadFailed": "Failed to load material groups",
+          "serialRuleTimeout": "Batch rules loading timed out and waiting has stopped",
+          "serialRuleLoadFailed": "Failed to load batch rules",
           "listTimeout": "Material list loading timed out and waiting has stopped",
           "listLoadFailed": "Failed to load material list",
           "detailTimeout": "Material detail timed out and waiting has stopped",
-          "detailLoadFailed": "Failed to load material detail"
+          "detailLoadFailed": "Failed to load material detail",
+          "importSuccess": "Material import succeeded",
+          "importFailed": "Material import failed",
+          "templateDownloadSuccess": "Template downloaded successfully",
+          "templateDownloadFailed": "Template download failed",
+          "enabledFieldsTimeout": "Dynamic fields loading timed out",
+          "bindLocTimeout": "Bind-location options loading timed out",
+          "bindLocLoadFailed": "Failed to load bind-location options",
+          "selectAtLeastOne": "Please select at least one material"
         },
         "table": {
           "code": "Material Code",
@@ -2501,6 +2555,102 @@
           "spec": "Specification",
           "model": "Model"
         },
+        "dialog": {
+          "titleCreate": "Add Material",
+          "titleEdit": "Edit Material",
+          "tabs": {
+            "basic": "Basic Information",
+            "control": "Control Information",
+            "batchRule": "Batch Rule"
+          },
+          "fields": {
+            "code": "Material Code",
+            "name": "Material Name",
+            "groupId": "Material Group",
+            "useOrgName": "Using Organization",
+            "spec": "Specification",
+            "model": "Model",
+            "color": "Color",
+            "size": "Size",
+            "weight": "Weight",
+            "unit": "Unit",
+            "purUnit": "Purchase Unit",
+            "describle": "Description",
+            "safeQty": "Safety Stock",
+            "minQty": "Minimum Stock",
+            "maxQty": "Maximum Stock",
+            "stagn": "Stagnation Days",
+            "valid": "Shelf Life Days",
+            "validWarn": "Validity Warning Threshold",
+            "flagCheck": "Exempt Inspection",
+            "rglarId": "Batch Rule"
+          },
+          "placeholders": {
+            "code": "Enter material code",
+            "name": "Enter material name",
+            "groupId": "Select material group",
+            "useOrgName": "Enter using organization",
+            "spec": "Enter specification",
+            "model": "Enter model",
+            "color": "Enter color",
+            "size": "Enter size",
+            "unit": "Enter unit",
+            "purUnit": "Enter purchase unit",
+            "describle": "Enter description",
+            "flagCheck": "Select exempt inspection",
+            "rglarId": "Select batch rule"
+          },
+          "validation": {
+            "code": "Please enter material code",
+            "name": "Please enter material name",
+            "groupId": "Please select material group"
+          }
+        },
+        "batchDialog": {
+          "titles": {
+            "status": "Batch Update Status",
+            "stockLevel": "Batch Update Stock Level",
+            "validWarn": "Batch Update Expiry Warning",
+            "flagCheck": "Batch Update QC Status"
+          },
+          "fields": {
+            "stockLevel": "Stock Level"
+          },
+          "placeholders": {
+            "stockLevel": "Select stock level",
+            "validWarn": "Enter expiry warning",
+            "valid": "Enter shelf life days",
+            "flagCheck": "Select exempt inspection"
+          },
+          "validation": {
+            "status": "Please select status",
+            "stockLevel": "Please select stock level",
+            "validWarn": "Please enter expiry warning",
+            "valid": "Please enter shelf life days",
+            "flagCheck": "Please select exempt inspection"
+          }
+        },
+        "batchGroupDialog": {
+          "title": "Batch Update Material Group"
+        },
+        "bindLocDialog": {
+          "title": "Bind Location",
+          "fields": {
+            "areaMatId": "Area Material",
+            "areaId": "Area",
+            "locId": "Location"
+          },
+          "placeholders": {
+            "areaMatId": "Select area material",
+            "areaId": "Select area",
+            "locId": "Select locations"
+          },
+          "validation": {
+            "areaMatId": "Please select area material",
+            "areaId": "Please select area",
+            "locId": "Please select locations"
+          }
+        },
         "detail": {
           "title": "Material Detail",
           "sections": {
diff --git a/rsf-design/src/locales/langs/zh.json b/rsf-design/src/locales/langs/zh.json
index db85659..170cebe 100644
--- a/rsf-design/src/locales/langs/zh.json
+++ b/rsf-design/src/locales/langs/zh.json
@@ -182,20 +182,11 @@
   "setting": {
     "menuType": {
       "title": "鑿滃崟甯冨眬",
-      "list": [
-        "鍨傜洿",
-        "姘村钩",
-        "娣峰悎",
-        "鍙屽垪"
-      ]
+      "list": ["鍨傜洿", "姘村钩", "娣峰悎", "鍙屽垪"]
     },
     "theme": {
       "title": "涓婚椋庢牸",
-      "list": [
-        "娴呰壊",
-        "娣辫壊",
-        "绯荤粺"
-      ]
+      "list": ["娴呰壊", "娣辫壊", "绯荤粺"]
     },
     "menu": {
       "title": "鑿滃崟椋庢牸"
@@ -205,17 +196,11 @@
     },
     "box": {
       "title": "鐩掑瓙鏍峰紡",
-      "list": [
-        "杈规",
-        "闃村奖"
-      ]
+      "list": ["杈规", "闃村奖"]
     },
     "container": {
       "title": "瀹瑰櫒瀹藉害",
-      "list": [
-        "閾烘弧",
-        "瀹氬"
-      ]
+      "list": ["閾烘弧", "瀹氬"]
     },
     "basics": {
       "title": "鍩虹閰嶇疆",
@@ -661,21 +646,21 @@
       "send": "鍙戦��",
       "renameDialogTitle": "閲嶅懡鍚嶄細璇�",
       "sessionTitleField": "浼氳瘽鏍囬",
-        "requestMetric": "Req: {value}",
-        "sessionMetric": "Session: {id}",
-        "promptMetric": "Prompt: {value}",
-        "modelMetric": "Model: {value}",
-        "promptLabel": "Prompt",
-        "modelLabel": "Model",
-        "modelSelectorLabel": "瀵硅瘽妯″瀷",
-        "modelSelectorHint": "鍒囨崲鍚庝粎褰卞搷褰撳墠浼氳瘽鍚庣画鍥炲锛屼笉浼氭敼鍔ㄥ叏灞�榛樿妯″瀷銆�",
-        "modelSwitchFailed": "鍒囨崲瀵硅瘽妯″瀷澶辫触",
-        "defaultModelSuffix": "(榛樿)",
-        "mcpMetric": "MCP: {value}",
-        "historyMetric": "History: {value}",
-        "mcpLabel": "MCP",
-        "historyLabel": "History",
-        "recentMetric": "Recent: {value}",
+      "requestMetric": "Req: {value}",
+      "sessionMetric": "Session: {id}",
+      "promptMetric": "Prompt: {value}",
+      "modelMetric": "Model: {value}",
+      "promptLabel": "Prompt",
+      "modelLabel": "Model",
+      "modelSelectorLabel": "瀵硅瘽妯″瀷",
+      "modelSelectorHint": "鍒囨崲鍚庝粎褰卞搷褰撳墠浼氳瘽鍚庣画鍥炲锛屼笉浼氭敼鍔ㄥ叏灞�榛樿妯″瀷銆�",
+      "modelSwitchFailed": "鍒囨崲瀵硅瘽妯″瀷澶辫触",
+      "defaultModelSuffix": "(榛樿)",
+      "mcpMetric": "MCP: {value}",
+      "historyMetric": "History: {value}",
+      "mcpLabel": "MCP",
+      "historyLabel": "History",
+      "recentMetric": "Recent: {value}",
       "elapsedMetric": "鑰楁椂: {value} ms",
       "firstTokenMetric": "棣栧寘: {value} ms",
       "tokenMetric": "Tokens: prompt {prompt} / completion {completion} / total {total}",
@@ -850,12 +835,12 @@
           "systemPrompt": "绯荤粺鎻愮ず璇�",
           "userPromptTemplate": "鐢ㄦ埛鎻愮ず璇嶆ā鏉�"
         },
-          "dialog": {
-            "titleCreate": "鏂板缓 Prompt",
-            "titleEdit": "缂栬緫 Prompt",
-            "titleDetail": "Prompt 璇︽儏",
-            "defaultPreviewInput": "璇锋牴鎹綋鍓嶈緭鍏ョ粰鍑烘憳瑕�",
-            "previewTitle": "娓叉煋棰勮",
+        "dialog": {
+          "titleCreate": "鏂板缓 Prompt",
+          "titleEdit": "缂栬緫 Prompt",
+          "titleDetail": "Prompt 璇︽儏",
+          "defaultPreviewInput": "璇锋牴鎹綋鍓嶈緭鍏ョ粰鍑烘憳瑕�",
+          "previewTitle": "娓叉煋棰勮",
           "previewDescription": "杈撳叆绀轰緥鍐呭鍜� metadata锛岀洿鎺ラ瑙堟渶缁堟覆鏌撶粨鏋溿��",
           "previewAction": "娓叉煋棰勮",
           "previewResolvedVariables": "宸茶В鏋愬彉閲忥細{value}",
@@ -2473,8 +2458,20 @@
       },
       "whMat": {
         "title": "鐗╂枡",
+        "entity": "鐗╂枡",
         "labels": {
           "allMaterials": "鍏ㄩ儴鐗╂枡"
+        },
+        "actions": {
+          "add": "鏂板鐗╂枡",
+          "batchGroup": "鎵归噺鏀瑰垎缁�",
+          "batchWarn": "鎵归噺鏀归璀�",
+          "batchFlagCheck": "鎵归噺璐ㄦ",
+          "batchStatus": "鎵归噺鐘舵��",
+          "batchStockLevel": "鎵归噺搴撳瓨绛夌骇",
+          "bindLoc": "缁戝畾搴撲綅",
+          "import": "瀵煎叆",
+          "downloadTemplate": "涓嬭浇妯℃澘"
         },
         "search": {
           "groupKeywordPlaceholder": "鎼滅储鐗╂枡鍒嗙粍",
@@ -2486,19 +2483,76 @@
           "codePlaceholder": "璇疯緭鍏ョ墿鏂欑紪鐮�",
           "name": "鐗╂枡鍚嶇О",
           "namePlaceholder": "璇疯緭鍏ョ墿鏂欏悕绉�",
+          "groupId": "鐗╂枡鍒嗙粍",
+          "groupIdPlaceholder": "璇烽�夋嫨鐗╂枡鍒嗙粍",
+          "platCode": "骞冲彴缂栫爜",
+          "platCodePlaceholder": "璇疯緭鍏ュ钩鍙扮紪鐮�",
           "spec": "瑙勬牸",
           "specPlaceholder": "璇疯緭鍏ヨ鏍�",
+          "model": "鍨嬪彿",
+          "modelPlaceholder": "璇疯緭鍏ュ瀷鍙�",
+          "color": "棰滆壊",
+          "colorPlaceholder": "璇疯緭鍏ラ鑹�",
+          "size": "灏哄",
+          "sizePlaceholder": "璇疯緭鍏ュ昂瀵�",
+          "unit": "鍗曚綅",
+          "unitPlaceholder": "璇疯緭鍏ュ崟浣�",
+          "purUnit": "閲囪喘鍗曚綅",
+          "purUnitPlaceholder": "璇疯緭鍏ラ噰璐崟浣�",
+          "stockUnit": "搴撲綅鍗曚綅",
+          "stockUnitPlaceholder": "璇疯緭鍏ュ簱浣嶅崟浣�",
           "barcode": "鏉$爜",
-          "barcodePlaceholder": "璇疯緭鍏ユ潯鐮�"
+          "barcodePlaceholder": "璇疯緭鍏ユ潯鐮�",
+          "describle": "鎻忚堪",
+          "describlePlaceholder": "璇疯緭鍏ユ弿杩�",
+          "rglarId": "鎵规瑙勫垯",
+          "rglarIdPlaceholder": "璇烽�夋嫨鎵规瑙勫垯",
+          "weight": "閲嶉噺",
+          "weightPlaceholder": "璇疯緭鍏ラ噸閲�",
+          "nromNum": "鏍囧寘鏁伴噺",
+          "nromNumPlaceholder": "璇疯緭鍏ユ爣鍖呮暟閲�",
+          "stockLevel": "搴撳瓨绛夌骇",
+          "stockLevelPlaceholder": "璇烽�夋嫨搴撳瓨绛夌骇",
+          "flagLabelMange": "鏍囩绠$悊",
+          "flagLabelMangePlaceholder": "璇烽�夋嫨鏍囩绠$悊",
+          "safeQty": "瀹夊叏搴撳瓨",
+          "safeQtyPlaceholder": "璇疯緭鍏ュ畨鍏ㄥ簱瀛�",
+          "minQty": "鏈�灏忓簱瀛�",
+          "minQtyPlaceholder": "璇疯緭鍏ユ渶灏忓簱瀛�",
+          "maxQty": "鏈�澶у簱瀛�",
+          "maxQtyPlaceholder": "璇疯緭鍏ユ渶澶у簱瀛�",
+          "stagn": "鍋滄粸澶╂暟",
+          "stagnPlaceholder": "璇疯緭鍏ュ仠婊炲ぉ鏁�",
+          "valid": "淇濊川鏈熷ぉ鏁�",
+          "validPlaceholder": "璇疯緭鍏ヤ繚璐ㄦ湡澶╂暟",
+          "validWarn": "鏁堟湡棰勮闃堝��",
+          "validWarnPlaceholder": "璇疯緭鍏ユ晥鏈熼璀﹂槇鍊�",
+          "flagCheck": "鏄惁鍏嶆",
+          "flagCheckPlaceholder": "璇烽�夋嫨鏄惁鍏嶆",
+          "status": "鐘舵��",
+          "statusPlaceholder": "璇烽�夋嫨鐘舵��",
+          "memo": "澶囨敞",
+          "memoPlaceholder": "璇疯緭鍏ュ娉�",
+          "dynamicPlaceholder": "璇疯緭鍏field}"
         },
         "messages": {
           "emptyGroups": "鏆傛棤鐗╂枡鍒嗙粍",
           "groupTimeout": "鐗╂枡鍒嗙粍鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟",
           "groupLoadFailed": "鑾峰彇鐗╂枡鍒嗙粍澶辫触",
+          "serialRuleTimeout": "鎵规瑙勫垯鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟",
+          "serialRuleLoadFailed": "鑾峰彇鎵规瑙勫垯澶辫触",
           "listTimeout": "鐗╂枡鍒楄〃鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟",
           "listLoadFailed": "鑾峰彇鐗╂枡鍒楄〃澶辫触",
           "detailTimeout": "鐗╂枡璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟",
-          "detailLoadFailed": "鑾峰彇鐗╂枡璇︽儏澶辫触"
+          "detailLoadFailed": "鑾峰彇鐗╂枡璇︽儏澶辫触",
+          "importSuccess": "鐗╂枡瀵煎叆鎴愬姛",
+          "importFailed": "鐗╂枡瀵煎叆澶辫触",
+          "templateDownloadSuccess": "妯℃澘涓嬭浇鎴愬姛",
+          "templateDownloadFailed": "妯℃澘涓嬭浇澶辫触",
+          "enabledFieldsTimeout": "鎵╁睍瀛楁鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟",
+          "bindLocTimeout": "缁戝畾搴撲綅閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟",
+          "bindLocLoadFailed": "鑾峰彇缁戝畾搴撲綅閫夐」澶辫触",
+          "selectAtLeastOne": "璇疯嚦灏戦�夋嫨涓�鏉$墿鏂欒褰�"
         },
         "table": {
           "code": "鐗╂枡缂栫爜",
@@ -2509,6 +2563,102 @@
           "spec": "瑙勬牸",
           "model": "鍨嬪彿"
         },
+        "dialog": {
+          "titleCreate": "鏂板鐗╂枡",
+          "titleEdit": "缂栬緫鐗╂枡",
+          "tabs": {
+            "basic": "鍩虹淇℃伅",
+            "control": "鎺у埗淇℃伅",
+            "batchRule": "鎵规瑙勫垯"
+          },
+          "fields": {
+            "code": "鐗╂枡缂栫爜",
+            "name": "鐗╂枡鍚嶇О",
+            "groupId": "鐗╂枡鍒嗙粍",
+            "useOrgName": "浣跨敤缁勭粐",
+            "spec": "瑙勬牸",
+            "model": "鍨嬪彿",
+            "color": "棰滆壊",
+            "size": "灏哄",
+            "weight": "閲嶉噺",
+            "unit": "鍗曚綅",
+            "purUnit": "閲囪喘鍗曚綅",
+            "describle": "鎻忚堪",
+            "safeQty": "瀹夊叏搴撳瓨",
+            "minQty": "鏈�灏忓簱瀛�",
+            "maxQty": "鏈�澶у簱瀛�",
+            "stagn": "鍋滄粸澶╂暟",
+            "valid": "淇濊川鏈熷ぉ鏁�",
+            "validWarn": "鏁堟湡棰勮闃堝��",
+            "flagCheck": "鏄惁鍏嶆",
+            "rglarId": "鎵规瑙勫垯"
+          },
+          "placeholders": {
+            "code": "璇疯緭鍏ョ墿鏂欑紪鐮�",
+            "name": "璇疯緭鍏ョ墿鏂欏悕绉�",
+            "groupId": "璇烽�夋嫨鐗╂枡鍒嗙粍",
+            "useOrgName": "璇疯緭鍏ヤ娇鐢ㄧ粍缁�",
+            "spec": "璇疯緭鍏ヨ鏍�",
+            "model": "璇疯緭鍏ュ瀷鍙�",
+            "color": "璇疯緭鍏ラ鑹�",
+            "size": "璇疯緭鍏ュ昂瀵�",
+            "unit": "璇疯緭鍏ュ崟浣�",
+            "purUnit": "璇疯緭鍏ラ噰璐崟浣�",
+            "describle": "璇疯緭鍏ユ弿杩�",
+            "flagCheck": "璇烽�夋嫨鏄惁鍏嶆",
+            "rglarId": "璇烽�夋嫨鎵规瑙勫垯"
+          },
+          "validation": {
+            "code": "璇疯緭鍏ョ墿鏂欑紪鐮�",
+            "name": "璇疯緭鍏ョ墿鏂欏悕绉�",
+            "groupId": "璇烽�夋嫨鐗╂枡鍒嗙粍"
+          }
+        },
+        "batchDialog": {
+          "titles": {
+            "status": "鎵归噺淇敼鐘舵��",
+            "stockLevel": "鎵归噺淇敼搴撳瓨绛夌骇",
+            "validWarn": "鎵归噺淇敼鏁堟湡棰勮",
+            "flagCheck": "鎵归噺淇敼璐ㄦ鐘舵��"
+          },
+          "fields": {
+            "stockLevel": "搴撳瓨绛夌骇"
+          },
+          "placeholders": {
+            "stockLevel": "璇烽�夋嫨搴撳瓨绛夌骇",
+            "validWarn": "璇疯緭鍏ユ晥鏈熼璀﹂槇鍊�",
+            "valid": "璇疯緭鍏ヤ繚璐ㄦ湡澶╂暟",
+            "flagCheck": "璇烽�夋嫨鏄惁鍏嶆"
+          },
+          "validation": {
+            "status": "璇烽�夋嫨鐘舵��",
+            "stockLevel": "璇烽�夋嫨搴撳瓨绛夌骇",
+            "validWarn": "璇疯緭鍏ユ晥鏈熼璀﹂槇鍊�",
+            "valid": "璇疯緭鍏ヤ繚璐ㄦ湡澶╂暟",
+            "flagCheck": "璇烽�夋嫨鏄惁鍏嶆"
+          }
+        },
+        "batchGroupDialog": {
+          "title": "鎵归噺淇敼鐗╂枡鍒嗙粍"
+        },
+        "bindLocDialog": {
+          "title": "缁戝畾搴撲綅",
+          "fields": {
+            "areaMatId": "搴撳尯鐗╂枡",
+            "areaId": "搴撳尯",
+            "locId": "搴撲綅"
+          },
+          "placeholders": {
+            "areaMatId": "璇烽�夋嫨搴撳尯鐗╂枡",
+            "areaId": "璇烽�夋嫨搴撳尯",
+            "locId": "璇烽�夋嫨搴撲綅"
+          },
+          "validation": {
+            "areaMatId": "璇烽�夋嫨搴撳尯鐗╂枡",
+            "areaId": "璇烽�夋嫨搴撳尯",
+            "locId": "璇烽�夋嫨搴撲綅"
+          }
+        },
         "detail": {
           "title": "鐗╂枡璇︽儏",
           "sections": {
diff --git a/rsf-design/src/views/basic-info/bas-station-area/basStationAreaPage.helpers.js b/rsf-design/src/views/basic-info/bas-station-area/basStationAreaPage.helpers.js
index b3046db..c94a34a 100644
--- a/rsf-design/src/views/basic-info/bas-station-area/basStationAreaPage.helpers.js
+++ b/rsf-design/src/views/basic-info/bas-station-area/basStationAreaPage.helpers.js
@@ -181,7 +181,13 @@
       }
       return {
         value: Number(value),
-        label: normalizeText(item.name || item.areaName || item.code || item.areaCode || `${$t('menu.warehouseAreas')} ${value}`)
+        label: normalizeText(
+          item.name ||
+            item.areaName ||
+            item.code ||
+            item.areaCode ||
+            `${$t('menu.warehouseAreas')} ${value}`
+        )
       }
     })
     .filter(Boolean)
@@ -203,7 +209,9 @@
       }
       return {
         value: Number(value),
-        label: normalizeText(item.stationName || item.stationId || item.name || `${$t('menu.basStation')} ${value}`)
+        label: normalizeText(
+          item.stationName || item.stationId || item.name || `${$t('menu.basStation')} ${value}`
+        )
       }
     })
     .filter(Boolean)
@@ -290,7 +298,9 @@
     containerType: normalizeText(params.containerType),
     barcode: normalizeText(params.barcode),
     autoTransfer:
-      params.autoTransfer !== undefined && params.autoTransfer !== null && params.autoTransfer !== ''
+      params.autoTransfer !== undefined &&
+      params.autoTransfer !== null &&
+      params.autoTransfer !== ''
         ? Number(params.autoTransfer)
         : void 0,
     stationAlias: normalizeText(params.stationAlias),
@@ -302,7 +312,9 @@
   }
 
   return Object.fromEntries(
-    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+    Object.entries(searchParams).filter(
+      ([, value]) => value !== '' && value !== void 0 && value !== null
+    )
   )
 }
 
@@ -310,6 +322,7 @@
   return {
     current: params.current || 1,
     pageSize: params.pageSize || params.size || 20,
+    orderBy: normalizeText(params.orderBy) || 'create_time desc',
     ...buildBasStationAreaSearchParams(params)
   }
 }
@@ -356,7 +369,9 @@
     ...(formData.area !== void 0 && formData.area !== null && formData.area !== ''
       ? { area: Number(formData.area) }
       : {}),
-    ...(formData.isCrossZone !== void 0 && formData.isCrossZone !== null && formData.isCrossZone !== ''
+    ...(formData.isCrossZone !== void 0 &&
+    formData.isCrossZone !== null &&
+    formData.isCrossZone !== ''
       ? { isCrossZone: Number(formData.isCrossZone) }
       : {}),
     ...(Array.isArray(formData.crossZoneArea) && formData.crossZoneArea.length
@@ -370,7 +385,9 @@
       ? { containerType: normalizeIdArray(formData.containerType) }
       : {}),
     barcode: normalizeText(formData.barcode) || '',
-    ...(formData.autoTransfer !== void 0 && formData.autoTransfer !== null && formData.autoTransfer !== ''
+    ...(formData.autoTransfer !== void 0 &&
+    formData.autoTransfer !== null &&
+    formData.autoTransfer !== ''
       ? { autoTransfer: Number(formData.autoTransfer) }
       : {}),
     ...(Array.isArray(formData.stationAlias) && formData.stationAlias.length
@@ -384,10 +401,12 @@
   }
 }
 
-export function buildBasStationAreaDialogModel(record = {}, resolvers = {}) {
+export function buildBasStationAreaDialogModel(record = {}) {
   return {
     ...createBasStationAreaFormState(),
-    ...(record.id !== void 0 && record.id !== null && record.id !== '' ? { id: Number(record.id) } : {}),
+    ...(record.id !== void 0 && record.id !== null && record.id !== ''
+      ? { id: Number(record.id) }
+      : {}),
     stationAreaName: normalizeText(record.stationAreaName || ''),
     stationAreaId: normalizeText(record.stationAreaId || ''),
     type: normalizeIdValue(record.type),
@@ -395,13 +414,17 @@
     outAble: record.outAble !== void 0 && record.outAble !== null ? Number(record.outAble) : 0,
     useStatus: normalizeText(record.useStatus || ''),
     area: normalizeIdValue(record.area),
-    isCrossZone: record.isCrossZone !== void 0 && record.isCrossZone !== null ? Number(record.isCrossZone) : 0,
+    isCrossZone:
+      record.isCrossZone !== void 0 && record.isCrossZone !== null ? Number(record.isCrossZone) : 0,
     crossZoneArea: normalizeIdArray(record.crossZoneArea),
     isWcs: record.isWcs !== void 0 && record.isWcs !== null ? Number(record.isWcs) : 0,
     wcsData: normalizeText(record.wcsData || ''),
     containerType: normalizeIdArray(record.containerType ?? record.containerTypes),
     barcode: normalizeText(record.barcode || ''),
-    autoTransfer: record.autoTransfer !== void 0 && record.autoTransfer !== null ? Number(record.autoTransfer) : 0,
+    autoTransfer:
+      record.autoTransfer !== void 0 && record.autoTransfer !== null
+        ? Number(record.autoTransfer)
+        : 0,
     stationAlias: normalizeIdArray(record.stationAlias),
     status: record.status !== void 0 && record.status !== null ? Number(record.status) : 1,
     memo: normalizeText(record.memo || '')
@@ -424,32 +447,46 @@
     stationAreaName: normalizeText(record.stationAreaName) || t('common.placeholder.empty'),
     stationAreaId: normalizeText(record.stationAreaId) || t('common.placeholder.empty'),
     type: normalizeIdValue(record.type),
-    typeText: normalizeText(
-      record.type$ || record.typeText || resolvers.resolveTypeLabel?.(typeValue) || typeValue
-    ) || t('common.placeholder.empty'),
+    typeText:
+      normalizeText(
+        record.type$ || record.typeText || resolvers.resolveTypeLabel?.(typeValue) || typeValue
+      ) || t('common.placeholder.empty'),
     inAble: normalizeIdValue(record.inAble),
     inAbleText: normalizeBooleanText(record.inAble, t),
     outAble: normalizeIdValue(record.outAble),
     outAbleText: normalizeBooleanText(record.outAble, t),
     useStatus: normalizeText(record.useStatus),
     useStatusText:
-      normalizeText(record.useStatus$ || record.useStatusText || resolvers.resolveUseStatusLabel?.(record.useStatus) || record.useStatus) ||
-      t('common.placeholder.empty'),
+      normalizeText(
+        record.useStatus$ ||
+          record.useStatusText ||
+          resolvers.resolveUseStatusLabel?.(record.useStatus) ||
+          record.useStatus
+      ) || t('common.placeholder.empty'),
     area: normalizeIdValue(areaId),
-    areaText: normalizeText(record.area$ || record.areaText || resolvers.resolveAreaLabel?.(areaId) || '') || t('common.placeholder.empty'),
+    areaText:
+      normalizeText(
+        record.area$ || record.areaText || resolvers.resolveAreaLabel?.(areaId) || ''
+      ) || t('common.placeholder.empty'),
     isCrossZone: normalizeIdValue(record.isCrossZone),
     isCrossZoneText: normalizeBooleanText(record.isCrossZone, t),
     crossZoneArea: crossZoneAreaIds,
     crossZoneAreaText:
-      resolveOptionText(crossZoneAreaIds, resolvers.resolveCrossZoneAreaLabel, record.crossZoneAreaText || []) ||
-      t('common.placeholder.empty'),
+      resolveOptionText(
+        crossZoneAreaIds,
+        resolvers.resolveCrossZoneAreaLabel,
+        record.crossZoneAreaText || []
+      ) || t('common.placeholder.empty'),
     isWcs: normalizeIdValue(record.isWcs),
     isWcsText: normalizeBooleanText(record.isWcs, t),
     wcsData: normalizeText(record.wcsData) || t('common.placeholder.empty'),
     containerType: containerTypeIds,
     containerTypeText:
-      resolveOptionText(containerTypeIds, resolvers.resolveContainerTypeLabel, record.containerTypesText || []) ||
-      t('common.placeholder.empty'),
+      resolveOptionText(
+        containerTypeIds,
+        resolvers.resolveContainerTypeLabel,
+        record.containerTypesText || []
+      ) || t('common.placeholder.empty'),
     barcode: normalizeText(record.barcode) || t('common.placeholder.empty'),
     autoTransfer: normalizeIdValue(record.autoTransfer),
     autoTransferText: normalizeBooleanText(record.autoTransfer, t),
@@ -465,10 +502,14 @@
     statusType: statusMeta.type,
     statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
     memo: normalizeText(record.memo) || t('common.placeholder.empty'),
-    createByText: normalizeText(record.createBy$ || record.createByText || '') || t('common.placeholder.empty'),
-    createTimeText: normalizeText(record.createTime$ || record.createTime || '') || t('common.placeholder.empty'),
-    updateByText: normalizeText(record.updateBy$ || record.updateByText || '') || t('common.placeholder.empty'),
-    updateTimeText: normalizeText(record.updateTime$ || record.updateTime || '') || t('common.placeholder.empty')
+    createByText:
+      normalizeText(record.createBy$ || record.createByText || '') || t('common.placeholder.empty'),
+    createTimeText:
+      normalizeText(record.createTime$ || record.createTime || '') || t('common.placeholder.empty'),
+    updateByText:
+      normalizeText(record.updateBy$ || record.updateByText || '') || t('common.placeholder.empty'),
+    updateTimeText:
+      normalizeText(record.updateTime$ || record.updateTime || '') || t('common.placeholder.empty')
   }
 }
 
diff --git a/rsf-design/src/views/basic-info/bas-station-area/basStationAreaTable.columns.js b/rsf-design/src/views/basic-info/bas-station-area/basStationAreaTable.columns.js
index 476dfae..ec62b52 100644
--- a/rsf-design/src/views/basic-info/bas-station-area/basStationAreaTable.columns.js
+++ b/rsf-design/src/views/basic-info/bas-station-area/basStationAreaTable.columns.js
@@ -19,7 +19,12 @@
   }
 
   if (canDelete && handleDelete) {
-    operations.push({ key: 'delete', label: t('common.actions.delete'), icon: 'ri:delete-bin-5-line', color: 'var(--art-error)' })
+    operations.push({
+      key: 'delete',
+      label: t('common.actions.delete'),
+      icon: 'ri:delete-bin-5-line',
+      color: 'var(--art-error)'
+    })
   }
 
   return [
@@ -33,6 +38,13 @@
       label: t('table.index'),
       width: 72,
       align: 'center'
+    },
+    {
+      prop: 'id',
+      label: t('table.id'),
+      width: 90,
+      align: 'center',
+      formatter: (row) => row.id ?? '--'
     },
     {
       prop: 'stationAreaId',
@@ -138,11 +150,21 @@
       width: 96,
       align: 'center',
       formatter: (row) => {
-        const status = getBasStationAreaStatusOptions(t).find((item) => Number(item.value) === Number(row.status))
+        const status = getBasStationAreaStatusOptions(t).find(
+          (item) => Number(item.value) === Number(row.status)
+        )
         const text = status?.label || row.statusText || '--'
-        const type = Number(row.status) === 1 ? 'success' : Number(row.status) === 0 ? 'danger' : 'info'
+        const type =
+          Number(row.status) === 1 ? 'success' : Number(row.status) === 0 ? 'danger' : 'info'
         return h(ElTag, { type, effect: 'light' }, () => text)
       }
+    },
+    {
+      prop: 'updateByText',
+      label: t('table.updateBy'),
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateByText || '--'
     },
     {
       prop: 'updateTimeText',
@@ -152,6 +174,20 @@
       formatter: (row) => row.updateTimeText || '--'
     },
     {
+      prop: 'createByText',
+      label: t('table.createBy'),
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.createByText || '--'
+    },
+    {
+      prop: 'createTimeText',
+      label: t('table.createTime'),
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.createTimeText || '--'
+    },
+    {
       prop: 'memo',
       label: t('pages.basicInfo.basStationArea.search.memo'),
       minWidth: 180,
diff --git a/rsf-design/src/views/basic-info/bas-station-area/modules/bas-station-area-dialog.vue b/rsf-design/src/views/basic-info/bas-station-area/modules/bas-station-area-dialog.vue
index 32e068a..77c98c0 100644
--- a/rsf-design/src/views/basic-info/bas-station-area/modules/bas-station-area-dialog.vue
+++ b/rsf-design/src/views/basic-info/bas-station-area/modules/bas-station-area-dialog.vue
@@ -36,9 +36,7 @@
   import {
     buildBasStationAreaDialogModel,
     createBasStationAreaFormState,
-    getBasStationAreaBinaryOptions,
-    getBasStationAreaStatusOptions,
-    getBasStationAreaTypeOptions
+    getBasStationAreaStatusOptions
   } from '../basStationAreaPage.helpers'
 
   const props = defineProps({
@@ -48,7 +46,8 @@
     areaOptions: { type: Array, default: () => [] },
     crossZoneAreaOptions: { type: Array, default: () => [] },
     containerTypeOptions: { type: Array, default: () => [] },
-    stationOptions: { type: Array, default: () => [] }
+    stationOptions: { type: Array, default: () => [] },
+    useStatusOptions: { type: Array, default: () => [] }
   })
 
   const emit = defineEmits(['update:visible', 'submit'])
@@ -58,19 +57,47 @@
 
   const isEdit = computed(() => props.dialogType === 'edit')
   const dialogTitle = computed(() =>
-    t(isEdit.value ? 'pages.basicInfo.basStationArea.dialog.titleEdit' : 'pages.basicInfo.basStationArea.dialog.titleAdd')
+    t(
+      isEdit.value
+        ? 'pages.basicInfo.basStationArea.dialog.titleEdit'
+        : 'pages.basicInfo.basStationArea.dialog.titleAdd'
+    )
   )
 
   const rules = computed(() => ({
-    stationAreaName: [{ required: true, message: t('pages.basicInfo.basStationArea.dialog.validation.stationAreaName'), trigger: 'blur' }],
-    stationAreaId: [{ required: true, message: t('pages.basicInfo.basStationArea.dialog.validation.stationAreaId'), trigger: 'blur' }],
-    type: [{ required: true, message: t('pages.basicInfo.basStationArea.dialog.validation.type'), trigger: 'change' }],
-    area: [{ required: true, message: t('pages.basicInfo.basStationArea.dialog.validation.area'), trigger: 'change' }],
-    containerType: [{ type: 'array', required: true, message: t('pages.basicInfo.basStationArea.dialog.validation.containerType'), trigger: 'change' }],
-    stationAlias: [{ type: 'array', required: true, message: t('pages.basicInfo.basStationArea.dialog.validation.stationAlias'), trigger: 'change' }]
+    stationAreaName: [
+      {
+        required: true,
+        message: t('pages.basicInfo.basStationArea.dialog.validation.stationAreaName'),
+        trigger: 'blur'
+      }
+    ],
+    stationAreaId: [
+      {
+        required: true,
+        message: t('pages.basicInfo.basStationArea.dialog.validation.stationAreaId'),
+        trigger: 'blur'
+      }
+    ],
+    containerType: [
+      {
+        type: 'array',
+        required: true,
+        message: t('pages.basicInfo.basStationArea.dialog.validation.containerType'),
+        trigger: 'change'
+      }
+    ],
+    stationAlias: [
+      {
+        type: 'array',
+        required: true,
+        message: t('pages.basicInfo.basStationArea.dialog.validation.stationAlias'),
+        trigger: 'change'
+      }
+    ]
   }))
 
-  const formItems = computed(() => [
+  const baseLegacyItems = computed(() => [
     {
       label: t('pages.basicInfo.basStationArea.dialog.stationAreaName'),
       key: 'stationAreaName',
@@ -81,42 +108,12 @@
       }
     },
     {
-      label: t('pages.basicInfo.basStationArea.dialog.stationAreaId'),
-      key: 'stationAreaId',
-      type: 'input',
-      props: {
-        placeholder: t('pages.basicInfo.basStationArea.placeholder.stationAreaId'),
-        clearable: true
-      }
-    },
-    {
-      label: t('pages.basicInfo.basStationArea.dialog.type'),
-      key: 'type',
-      type: 'select',
-      props: {
-        placeholder: t('pages.basicInfo.basStationArea.search.type'),
-        clearable: true,
-        options: getBasStationAreaTypeOptions(t)
-      }
-    },
-    {
-      label: t('pages.basicInfo.basStationArea.dialog.area'),
-      key: 'area',
-      type: 'select',
-      props: {
-        placeholder: t('pages.basicInfo.basStationArea.search.area'),
-        clearable: true,
-        filterable: true,
-        options: props.areaOptions || []
-      }
-    },
-    {
-      label: t('pages.basicInfo.basStationArea.dialog.crossZoneArea'),
+      label: t('pages.basicInfo.basStation.table.crossZoneArea'),
       key: 'crossZoneArea',
       type: 'select',
       span: 24,
       props: {
-        placeholder: t('pages.basicInfo.basStationArea.placeholder.crossZoneArea'),
+        placeholder: t('pages.basicInfo.basStation.table.crossZoneArea'),
         clearable: true,
         multiple: true,
         collapseTags: true,
@@ -125,17 +122,26 @@
       }
     },
     {
-      label: t('pages.basicInfo.basStationArea.dialog.containerType'),
+      label: t('pages.basicInfo.basStation.table.containerTypes'),
       key: 'containerType',
       type: 'select',
       span: 24,
       props: {
-        placeholder: t('pages.basicInfo.basStationArea.placeholder.containerType'),
+        placeholder: t('pages.basicInfo.basStation.table.containerTypes'),
         clearable: true,
         multiple: true,
         collapseTags: true,
         filterable: true,
         options: props.containerTypeOptions || []
+      }
+    },
+    {
+      label: t('pages.basicInfo.basStationArea.dialog.stationAreaId'),
+      key: 'stationAreaId',
+      type: 'input',
+      props: {
+        placeholder: t('pages.basicInfo.basStationArea.placeholder.stationAreaId'),
+        clearable: true
       }
     },
     {
@@ -151,101 +157,22 @@
         filterable: true,
         options: props.stationOptions || []
       }
-    },
+    }
+  ])
+
+  const createOnlyItems = computed(() => [
     {
-      label: t('pages.basicInfo.basStationArea.dialog.inAble'),
-      key: 'inAble',
-      type: 'select',
-      props: {
-        placeholder: t('pages.basicInfo.basStationArea.search.inAble'),
-        clearable: true,
-        options: getBasStationAreaBinaryOptions(t)
-      }
-    },
-    {
-      label: t('pages.basicInfo.basStationArea.dialog.outAble'),
-      key: 'outAble',
-      type: 'select',
-      props: {
-        placeholder: t('pages.basicInfo.basStationArea.search.outAble'),
-        clearable: true,
-        options: getBasStationAreaBinaryOptions(t)
-      }
-    },
-    {
-      label: t('pages.basicInfo.basStationArea.dialog.isCrossZone'),
-      key: 'isCrossZone',
-      type: 'select',
-      props: {
-        placeholder: t('pages.basicInfo.basStationArea.search.isCrossZone'),
-        clearable: true,
-        options: getBasStationAreaBinaryOptions(t)
-      }
-    },
-    {
-      label: t('pages.basicInfo.basStationArea.dialog.isWcs'),
-      key: 'isWcs',
-      type: 'select',
-      props: {
-        placeholder: t('pages.basicInfo.basStationArea.search.isWcs'),
-        clearable: true,
-        options: getBasStationAreaBinaryOptions(t)
-      }
-    },
-    {
-      label: t('pages.basicInfo.basStationArea.dialog.autoTransfer'),
-      key: 'autoTransfer',
-      type: 'select',
-      props: {
-        placeholder: t('pages.basicInfo.basStationArea.search.autoTransfer'),
-        clearable: true,
-        options: getBasStationAreaBinaryOptions(t)
-      }
-    },
-    {
-      label: t('pages.basicInfo.basStationArea.dialog.useStatus'),
-      key: 'useStatus',
-      type: 'select',
-      props: {
-        placeholder: t('pages.basicInfo.basStationArea.search.useStatus'),
-        clearable: true,
-        filterable: true,
-        options: props.useStatusOptions || []
-      }
-    },
-    {
-      label: t('pages.basicInfo.basStationArea.dialog.wcsData'),
-      key: 'wcsData',
-      type: 'input',
-      span: 24,
-      props: {
-        type: 'textarea',
-        rows: 3,
-        placeholder: t('pages.basicInfo.basStationArea.placeholder.wcsData'),
-        clearable: true
-      }
-    },
-    {
-      label: t('pages.basicInfo.basStationArea.dialog.barcode'),
-      key: 'barcode',
-      type: 'input',
-      props: {
-        placeholder: t('pages.basicInfo.basStationArea.placeholder.barcode'),
-        clearable: true
-      }
-    },
-    {
-      label: t('pages.basicInfo.basStationArea.dialog.status'),
+      label: t('table.status'),
       key: 'status',
       type: 'select',
       props: {
-        placeholder: t('pages.basicInfo.basStationArea.search.status'),
+        placeholder: t('table.status'),
         clearable: true,
         options: getBasStationAreaStatusOptions(t)
       }
     },
     {
-      label: t('pages.basicInfo.basStationArea.dialog.memo'),
+      label: t('table.memo'),
       key: 'memo',
       type: 'input',
       span: 24,
@@ -257,6 +184,10 @@
       }
     }
   ])
+
+  const formItems = computed(() =>
+    isEdit.value ? baseLegacyItems.value : [...baseLegacyItems.value, ...createOnlyItems.value]
+  )
 
   const resetForm = () => {
     Object.assign(form, createBasStationAreaFormState())
@@ -271,7 +202,25 @@
     if (!formRef.value) return
     try {
       await formRef.value.validate()
-      emit('submit', { ...form })
+      const payload = isEdit.value
+        ? {
+            id: form.id,
+            stationAreaName: form.stationAreaName,
+            crossZoneArea: Array.isArray(form.crossZoneArea) ? [...form.crossZoneArea] : [],
+            containerType: Array.isArray(form.containerType) ? [...form.containerType] : [],
+            stationAreaId: form.stationAreaId,
+            stationAlias: Array.isArray(form.stationAlias) ? [...form.stationAlias] : []
+          }
+        : {
+            stationAreaName: form.stationAreaName,
+            crossZoneArea: Array.isArray(form.crossZoneArea) ? [...form.crossZoneArea] : [],
+            containerType: Array.isArray(form.containerType) ? [...form.containerType] : [],
+            stationAreaId: form.stationAreaId,
+            stationAlias: Array.isArray(form.stationAlias) ? [...form.stationAlias] : [],
+            status: form.status,
+            memo: form.memo
+          }
+      emit('submit', payload)
     } catch {
       return
     }
diff --git a/rsf-design/src/views/basic-info/wh-mat/index.vue b/rsf-design/src/views/basic-info/wh-mat/index.vue
index fb852a1..6d28ea1 100644
--- a/rsf-design/src/views/basic-info/wh-mat/index.vue
+++ b/rsf-design/src/views/basic-info/wh-mat/index.vue
@@ -5,7 +5,9 @@
         <ElCard class="wh-mat-page__sidebar-card">
           <div class="mb-3 flex items-center justify-between gap-3">
             <div>
-              <div class="text-base font-medium text-[var(--art-text-primary)]">{{ t('pages.basicInfo.whMat.title') }}</div>
+              <div class="text-base font-medium text-[var(--art-text-primary)]">{{
+                t('pages.basicInfo.whMat.title')
+              }}</div>
               <div class="text-xs text-[var(--art-text-secondary)]">
                 {{ selectedGroupLabel }}
               </div>
@@ -28,21 +30,26 @@
             <div v-if="groupTreeLoading" class="py-6">
               <ElSkeleton :rows="10" animated />
             </div>
-            <ElEmpty v-else-if="!groupTreeData.length" :description="t('pages.basicInfo.whMat.messages.emptyGroups')" />
+            <ElEmpty
+              v-else-if="!groupTreeData.length"
+              :description="t('pages.basicInfo.whMat.messages.emptyGroups')"
+            />
             <ElTree
               v-else
               :data="groupTreeData"
               :props="treeProps"
               node-key="id"
               highlight-current
-              default-expand-all
+              :default-expanded-keys="defaultExpandedGroupKeys"
               :current-node-key="selectedGroupId"
               @node-click="handleGroupNodeClick"
             >
               <template #default="{ data }">
                 <div class="flex items-center gap-2">
                   <span class="font-medium">{{ data.name || t('common.placeholder.empty') }}</span>
-                  <span class="text-xs text-[var(--art-text-secondary)]">{{ data.code || t('common.placeholder.empty') }}</span>
+                  <span class="text-xs text-[var(--art-text-secondary)]">{{
+                    data.code || t('common.placeholder.empty')
+                  }}</span>
                 </div>
               </template>
             </ElTree>
@@ -60,23 +67,155 @@
         />
 
         <ElCard class="art-table-card">
-          <ArtTableHeader :loading="loading" v-model:columns="columnChecks" @refresh="loadMatnrList" />
+          <ArtTableHeader
+            :loading="loading"
+            v-model:columns="columnChecks"
+            @refresh="handleRefresh"
+          >
+            <template #left>
+              <ElSpace wrap>
+                <ElButton v-auth="'add'" @click="handleShowDialog('add')" v-ripple>
+                  {{ t('pages.basicInfo.whMat.actions.add') }}
+                </ElButton>
+                <ElButton
+                  v-if="showBatchActionButtons"
+                  v-auth="'update'"
+                  :disabled="selectedRows.length === 0"
+                  @click="openBatchGroupDialog"
+                  v-ripple
+                >
+                  {{ t('pages.basicInfo.whMat.actions.batchGroup') }}
+                </ElButton>
+                <ElButton
+                  v-if="showBatchActionButtons"
+                  v-auth="'update'"
+                  :disabled="selectedRows.length === 0"
+                  @click="openBatchDialog('validWarn')"
+                  v-ripple
+                >
+                  {{ t('pages.basicInfo.whMat.actions.batchWarn') }}
+                </ElButton>
+                <ElButton
+                  v-if="showBatchActionButtons"
+                  v-auth="'update'"
+                  :disabled="selectedRows.length === 0"
+                  @click="openBatchDialog('flagCheck')"
+                  v-ripple
+                >
+                  {{ t('pages.basicInfo.whMat.actions.batchFlagCheck') }}
+                </ElButton>
+                <ElButton
+                  v-if="showBatchActionButtons"
+                  v-auth="'update'"
+                  :disabled="selectedRows.length === 0"
+                  @click="openBatchDialog('status')"
+                  v-ripple
+                >
+                  {{ t('pages.basicInfo.whMat.actions.batchStatus') }}
+                </ElButton>
+                <ElButton
+                  v-if="showBatchActionButtons"
+                  v-auth="'update'"
+                  :disabled="selectedRows.length === 0"
+                  @click="openBatchDialog('stockLevel')"
+                  v-ripple
+                >
+                  {{ t('pages.basicInfo.whMat.actions.batchStockLevel') }}
+                </ElButton>
+                <ElButton
+                  v-if="showBatchActionButtons"
+                  v-auth="'update'"
+                  :disabled="selectedRows.length === 0"
+                  @click="openBindLocDialog"
+                  v-ripple
+                >
+                  {{ t('pages.basicInfo.whMat.actions.bindLoc') }}
+                </ElButton>
+                <ElButton
+                  v-auth="'delete'"
+                  type="danger"
+                  :disabled="selectedRows.length === 0"
+                  @click="handleBatchDelete"
+                  v-ripple
+                >
+                  {{ t('common.actions.batchDelete') }}
+                </ElButton>
+                <div v-auth="'update'">
+                  <ElUpload
+                    :auto-upload="false"
+                    :show-file-list="false"
+                    accept=".xlsx,.xls"
+                    @change="handleImportFileChange"
+                  >
+                    <ElButton :loading="importing" v-ripple>
+                      {{ t('pages.basicInfo.whMat.actions.import') }}
+                    </ElButton>
+                  </ElUpload>
+                </div>
+                <ElButton :loading="templateDownloading" @click="handleDownloadTemplate" v-ripple>
+                  {{ t('pages.basicInfo.whMat.actions.downloadTemplate') }}
+                </ElButton>
+                <ListExportPrint
+                  class="inline-flex"
+                  :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>
 
           <ArtTable
             :loading="loading"
             :data="tableData"
             :columns="columns"
             :pagination="pagination"
+            row-key="id"
+            @selection-change="handleSelectionChange"
             @pagination:size-change="handleSizeChange"
             @pagination:current-change="handleCurrentChange"
-          >
-            <template #action="{ row }">
-              <ArtButtonTable icon="ri:eye-line" @click="openDetailDrawer(row)" />
-            </template>
-          </ArtTable>
+          />
         </ElCard>
       </div>
     </div>
+
+    <WhMatDialog
+      v-model:visible="dialogVisible"
+      :dialog-type="dialogType"
+      :material-data="currentMaterialData"
+      :group-options="groupOptions"
+      :serial-rule-options="serialRuleOptions"
+      @submit="handleDialogSubmit"
+    />
+
+    <WhMatBatchDialog
+      v-model:visible="batchDialogVisible"
+      :action-type="batchDialogType"
+      @submit="handleBatchDialogSubmit"
+    />
+
+    <WhMatBatchGroupDialog
+      v-model:visible="batchGroupDialogVisible"
+      :group-options="groupOptions"
+      @submit="handleBatchGroupSubmit"
+    />
+
+    <WhMatBindLocDialog
+      v-model:visible="bindLocDialogVisible"
+      :area-mat-options="areaMatOptions"
+      :area-options="areaOptions"
+      :loc-options="locOptions"
+      @submit="handleBindLocSubmit"
+    />
 
     <WhMatDetailDrawer
       v-model:visible="detailDrawerVisible"
@@ -87,38 +226,97 @@
 </template>
 
 <script setup>
-  import { ElMessage } from 'element-plus'
   import { computed, onMounted, reactive, ref } from 'vue'
+  import { ElMessage } from 'element-plus'
   import { useI18n } from 'vue-i18n'
-  import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+  import ListExportPrint from '@/components/biz/list-export-print/index.vue'
+  import { useAuth } from '@/hooks/core/useAuth'
   import { useTableColumns } from '@/hooks/core/useTableColumns'
+  import { useUserStore } from '@/store/modules/user'
+  import { fetchSerialRulePage } from '@/api/system-manage'
+  import { fetchWarehouseAreasList } from '@/api/warehouse-areas'
+  import { fetchLocPage } from '@/api/loc'
+  import { fetchLocAreaMatList } from '@/api/loc-area-mat'
+  import { fetchBindLocAreaMatRelaByMatnr } from '@/api/loc-area-mat-rela'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
   import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
-  import { fetchMatnrDetail, fetchMatnrGroupTree, fetchMatnrPage } from '@/api/wh-mat'
+  import { useCrudPage } from '@/views/system/common/useCrudPage'
+  import { usePrintExportPage } from '@/views/system/common/usePrintExportPage'
+  import {
+    fetchBatchUpdateMatnr,
+    fetchBindMatnrGroup,
+    fetchDeleteMatnr,
+    fetchDownloadMatnrTemplate,
+    fetchEnabledFields,
+    fetchExportMatnrReport,
+    fetchGetMatnrMany,
+    fetchImportMatnr,
+    fetchMatnrDetail,
+    fetchMatnrGroupTree,
+    fetchMatnrPage,
+    fetchSaveMatnr,
+    fetchUpdateMatnr
+  } from '@/api/wh-mat'
+  import WhMatBatchDialog from './modules/wh-mat-batch-dialog.vue'
+  import WhMatBatchGroupDialog from './modules/wh-mat-batch-group-dialog.vue'
+  import WhMatBindLocDialog from './modules/wh-mat-bind-loc-dialog.vue'
+  import WhMatDialog from './modules/wh-mat-dialog.vue'
   import WhMatDetailDrawer from './modules/wh-mat-detail-drawer.vue'
   import { createWhMatTableColumns } from './whMatTable.columns'
   import {
+    WH_MAT_REPORT_STYLE,
+    WH_MAT_REPORT_TITLE,
     buildMatnrGroupTreeQueryParams,
     buildMatnrPageQueryParams,
+    buildWhMatDialogModel,
+    buildWhMatPrintRows,
+    buildWhMatReportMeta,
+    buildWhMatSavePayload,
     createWhMatSearchState,
+    getWhMatDynamicFieldKey,
+    getWhMatFlagLabelManageOptions,
+    getWhMatFlagCheckOptions,
+    getWhMatStockLevelOptions,
+    getWhMatStatusOptions,
     getWhMatTreeNodeLabel,
     normalizeMatnrDetail,
+    normalizeWhMatEnabledFields,
     normalizeMatnrGroupTreeRows,
-    normalizeMatnrRow
+    normalizeMatnrRow,
+    resolveWhMatGroupOptions,
+    resolveWhMatSerialRuleOptions
   } from './whMatPage.helpers'
 
   defineOptions({ name: 'WhMat' })
-  const { t } = useI18n()
 
+  const { t } = useI18n()
+  const { hasAuth } = useAuth()
+  const userStore = useUserStore()
+
+  const showBatchActionButtons = false
   const loading = ref(false)
   const groupTreeLoading = ref(false)
   const detailDrawerVisible = ref(false)
   const detailLoading = ref(false)
+  const batchDialogVisible = ref(false)
+  const batchGroupDialogVisible = ref(false)
+  const bindLocDialogVisible = ref(false)
+  const bindLocOptionsLoading = ref(false)
+  const importing = ref(false)
+  const templateDownloading = ref(false)
   const tableData = ref([])
   const groupTreeData = ref([])
   const detailData = ref({})
+  const enabledFields = ref([])
+  const serialRuleOptions = ref([])
+  const areaOptions = ref([])
+  const areaMatOptions = ref([])
+  const locOptions = ref([])
   const selectedGroupId = ref(null)
   const groupSearch = ref('')
+  const batchDialogType = ref('status')
   const searchForm = ref(createWhMatSearchState())
+  let handleDeleteAction = null
 
   const pagination = reactive({
     current: 1,
@@ -130,6 +328,18 @@
     label: 'name',
     children: 'children'
   }
+
+  const reportTitle = WH_MAT_REPORT_TITLE
+  const groupOptions = computed(() => resolveWhMatGroupOptions(groupTreeData.value))
+  const defaultExpandedGroupKeys = computed(() => collectExpandedGroupKeys(groupTreeData.value))
+  const reportQueryParams = computed(() =>
+    buildMatnrPageQueryParams({
+      ...searchForm.value,
+      groupId: searchForm.value?.groupId || selectedGroupId.value,
+      current: 1,
+      pageSize: pagination.size
+    })
+  )
 
   const searchItems = computed(() => [
     {
@@ -160,12 +370,65 @@
       }
     },
     {
+      label: t('pages.basicInfo.whMat.search.groupId'),
+      key: 'groupId',
+      type: 'treeselect',
+      props: {
+        data: groupOptions.value,
+        props: {
+          label: 'displayLabel',
+          value: 'value',
+          children: 'children'
+        },
+        checkStrictly: true,
+        defaultExpandAll: true,
+        clearable: true,
+        placeholder: t('pages.basicInfo.whMat.search.groupIdPlaceholder')
+      }
+    },
+    {
+      label: t('pages.basicInfo.whMat.search.platCode'),
+      key: 'platCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: t('pages.basicInfo.whMat.search.platCodePlaceholder')
+      }
+    },
+    {
       label: t('pages.basicInfo.whMat.search.spec'),
       key: 'spec',
       type: 'input',
       props: {
         clearable: true,
         placeholder: t('pages.basicInfo.whMat.search.specPlaceholder')
+      }
+    },
+    {
+      label: t('pages.basicInfo.whMat.search.model'),
+      key: 'model',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: t('pages.basicInfo.whMat.search.modelPlaceholder')
+      }
+    },
+    {
+      label: t('pages.basicInfo.whMat.search.color'),
+      key: 'color',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: t('pages.basicInfo.whMat.search.colorPlaceholder')
+      }
+    },
+    {
+      label: t('pages.basicInfo.whMat.search.size'),
+      key: 'size',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: t('pages.basicInfo.whMat.search.sizePlaceholder')
       }
     },
     {
@@ -176,13 +439,214 @@
         clearable: true,
         placeholder: t('pages.basicInfo.whMat.search.barcodePlaceholder')
       }
-    }
+    },
+    {
+      label: t('pages.basicInfo.whMat.search.unit'),
+      key: 'unit',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: t('pages.basicInfo.whMat.search.unitPlaceholder')
+      }
+    },
+    {
+      label: t('pages.basicInfo.whMat.search.purUnit'),
+      key: 'purUnit',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: t('pages.basicInfo.whMat.search.purUnitPlaceholder')
+      }
+    },
+    {
+      label: t('pages.basicInfo.whMat.search.stockUnit'),
+      key: 'stockUnit',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: t('pages.basicInfo.whMat.search.stockUnitPlaceholder')
+      }
+    },
+    {
+      label: t('pages.basicInfo.whMat.search.describle'),
+      key: 'describle',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: t('pages.basicInfo.whMat.search.describlePlaceholder')
+      }
+    },
+    {
+      label: t('pages.basicInfo.whMat.search.rglarId'),
+      key: 'rglarId',
+      type: 'select',
+      props: {
+        clearable: true,
+        filterable: true,
+        placeholder: t('pages.basicInfo.whMat.search.rglarIdPlaceholder'),
+        options: serialRuleOptions.value
+      }
+    },
+    {
+      label: t('pages.basicInfo.whMat.search.weight'),
+      key: 'weight',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        valueOnClear: null,
+        placeholder: t('pages.basicInfo.whMat.search.weightPlaceholder')
+      }
+    },
+    {
+      label: t('pages.basicInfo.whMat.search.nromNum'),
+      key: 'nromNum',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        valueOnClear: null,
+        placeholder: t('pages.basicInfo.whMat.search.nromNumPlaceholder')
+      }
+    },
+    {
+      label: t('pages.basicInfo.whMat.search.stockLevel'),
+      key: 'stockLevel',
+      type: 'select',
+      props: {
+        clearable: true,
+        placeholder: t('pages.basicInfo.whMat.search.stockLevelPlaceholder'),
+        options: getWhMatStockLevelOptions()
+      }
+    },
+    {
+      label: t('pages.basicInfo.whMat.search.flagLabelMange'),
+      key: 'flagLabelMange',
+      type: 'select',
+      props: {
+        clearable: true,
+        placeholder: t('pages.basicInfo.whMat.search.flagLabelMangePlaceholder'),
+        options: getWhMatFlagLabelManageOptions(t)
+      }
+    },
+    {
+      label: t('pages.basicInfo.whMat.search.safeQty'),
+      key: 'safeQty',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        valueOnClear: null,
+        placeholder: t('pages.basicInfo.whMat.search.safeQtyPlaceholder')
+      }
+    },
+    {
+      label: t('pages.basicInfo.whMat.search.minQty'),
+      key: 'minQty',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        valueOnClear: null,
+        placeholder: t('pages.basicInfo.whMat.search.minQtyPlaceholder')
+      }
+    },
+    {
+      label: t('pages.basicInfo.whMat.search.maxQty'),
+      key: 'maxQty',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        valueOnClear: null,
+        placeholder: t('pages.basicInfo.whMat.search.maxQtyPlaceholder')
+      }
+    },
+    {
+      label: t('pages.basicInfo.whMat.search.stagn'),
+      key: 'stagn',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        valueOnClear: null,
+        placeholder: t('pages.basicInfo.whMat.search.stagnPlaceholder')
+      }
+    },
+    {
+      label: t('pages.basicInfo.whMat.search.valid'),
+      key: 'valid',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        valueOnClear: null,
+        placeholder: t('pages.basicInfo.whMat.search.validPlaceholder')
+      }
+    },
+    {
+      label: t('pages.basicInfo.whMat.search.validWarn'),
+      key: 'validWarn',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        valueOnClear: null,
+        placeholder: t('pages.basicInfo.whMat.search.validWarnPlaceholder')
+      }
+    },
+    {
+      label: t('pages.basicInfo.whMat.search.flagCheck'),
+      key: 'flagCheck',
+      type: 'select',
+      props: {
+        clearable: true,
+        placeholder: t('pages.basicInfo.whMat.search.flagCheckPlaceholder'),
+        options: getWhMatFlagCheckOptions(t)
+      }
+    },
+    {
+      label: t('pages.basicInfo.whMat.search.status'),
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        placeholder: t('pages.basicInfo.whMat.search.statusPlaceholder'),
+        options: getWhMatStatusOptions(t)
+      }
+    },
+    {
+      label: t('pages.basicInfo.whMat.search.memo'),
+      key: 'memo',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: t('pages.basicInfo.whMat.search.memoPlaceholder')
+      }
+    },
+    ...enabledFields.value.map((field) => ({
+      label: field.fieldsAlise,
+      key: getWhMatDynamicFieldKey(field.fields),
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: t('pages.basicInfo.whMat.search.dynamicPlaceholder', {
+          field: field.fieldsAlise
+        })
+      }
+    }))
   ])
 
-  const { columnChecks, columns } = useTableColumns(() =>
+  const { columnChecks, columns, resetColumns } = useTableColumns(() =>
     createWhMatTableColumns({
-      t,
-      handleViewDetail: openDetailDrawer
+      enabledFields: enabledFields.value,
+      handleViewDetail: openDetailDrawer,
+      handleEdit: hasAuth('update') ? openEditDialog : null,
+      handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null,
+      handlePrint: (row) => handlePrint({ ids: [row.id] }),
+      canEdit: hasAuth('update'),
+      canDelete: hasAuth('delete'),
+      t
     })
   )
 
@@ -208,6 +672,46 @@
       }
     }
     return null
+  }
+
+  function collectExpandedGroupKeys(nodes, depth = 1, maxExpandedDepth = 1) {
+    if (!Array.isArray(nodes) || depth > maxExpandedDepth) {
+      return []
+    }
+
+    return nodes.flatMap((node) => {
+      const currentId = node?.id !== undefined && node?.id !== null ? [node.id] : []
+      return [
+        ...currentId,
+        ...collectExpandedGroupKeys(node?.children || [], depth + 1, maxExpandedDepth)
+      ]
+    })
+  }
+
+  function normalizeOptionText(value) {
+    return String(value ?? '').trim()
+  }
+
+  function buildOption(value, label, extra = {}) {
+    return {
+      value,
+      label,
+      ...extra
+    }
+  }
+
+  async function loadEnabledFieldDefinitions() {
+    const fields = await guardRequestWithMessage(fetchEnabledFields(), [], {
+      timeoutMessage: t('pages.basicInfo.whMat.messages.enabledFieldsTimeout')
+    })
+    enabledFields.value = normalizeWhMatEnabledFields(fields)
+    enabledFields.value.forEach((field) => {
+      const dynamicKey = getWhMatDynamicFieldKey(field.fields)
+      if (searchForm.value[dynamicKey] === undefined) {
+        searchForm.value[dynamicKey] = ''
+      }
+    })
+    resetColumns()
   }
 
   function updatePaginationState(target, response, fallbackCurrent, fallbackSize) {
@@ -237,6 +741,99 @@
     }
   }
 
+  async function loadSerialRuleOptions() {
+    try {
+      const response = await guardRequestWithMessage(
+        fetchSerialRulePage({ current: 1, pageSize: 200 }),
+        { records: [] },
+        { timeoutMessage: t('pages.basicInfo.whMat.messages.serialRuleTimeout') }
+      )
+      serialRuleOptions.value = resolveWhMatSerialRuleOptions(
+        defaultResponseAdapter(response).records
+      )
+    } catch (error) {
+      serialRuleOptions.value = []
+      ElMessage.error(error?.message || t('pages.basicInfo.whMat.messages.serialRuleLoadFailed'))
+    }
+  }
+
+  async function ensureBindLocOptionsLoaded(force = false) {
+    if (
+      !force &&
+      areaOptions.value.length &&
+      areaMatOptions.value.length &&
+      locOptions.value.length
+    ) {
+      return
+    }
+    if (bindLocOptionsLoading.value) {
+      return
+    }
+
+    bindLocOptionsLoading.value = true
+    try {
+      const [areasResponse, areaMatResponse, locResponse] = await Promise.all([
+        guardRequestWithMessage(fetchWarehouseAreasList(), [], {
+          timeoutMessage: t('pages.basicInfo.whMat.messages.bindLocTimeout')
+        }),
+        guardRequestWithMessage(fetchLocAreaMatList(), [], {
+          timeoutMessage: t('pages.basicInfo.whMat.messages.bindLocTimeout')
+        }),
+        guardRequestWithMessage(
+          fetchLocPage({ current: 1, pageSize: 1000 }),
+          { records: [] },
+          {
+            timeoutMessage: t('pages.basicInfo.whMat.messages.bindLocTimeout')
+          }
+        )
+      ])
+
+      areaOptions.value = defaultResponseAdapter(areasResponse)
+        .records.map((item) =>
+          buildOption(
+            Number(item.id),
+            [normalizeOptionText(item.name), normalizeOptionText(item.code)]
+              .filter(Boolean)
+              .join(' 路 ') || t('common.placeholder.empty'),
+            {
+              areaId: Number(item.id),
+              warehouseId: item.warehouseId !== undefined ? Number(item.warehouseId) : void 0
+            }
+          )
+        )
+        .filter((item) => Number.isFinite(item.value))
+
+      areaMatOptions.value = defaultResponseAdapter(areaMatResponse)
+        .records.map((item) =>
+          buildOption(
+            Number(item.id),
+            [normalizeOptionText(item.code), normalizeOptionText(item.depict || item.name)]
+              .filter(Boolean)
+              .join(' 路 ') || t('common.placeholder.empty'),
+            {
+              areaMatId: Number(item.id),
+              areaId: item.areaId !== undefined ? Number(item.areaId) : void 0
+            }
+          )
+        )
+        .filter((item) => Number.isFinite(item.value))
+
+      locOptions.value = defaultResponseAdapter(locResponse)
+        .records.map((item) =>
+          buildOption(
+            Number(item.id),
+            normalizeOptionText(item.code) || t('common.placeholder.empty'),
+            {
+              areaId: item.areaId !== undefined ? Number(item.areaId) : void 0
+            }
+          )
+        )
+        .filter((item) => Number.isFinite(item.value))
+    } finally {
+      bindLocOptionsLoading.value = false
+    }
+  }
+
   async function loadMatnrList() {
     loading.value = true
     try {
@@ -244,7 +841,7 @@
         fetchMatnrPage(
           buildMatnrPageQueryParams({
             ...searchForm.value,
-            groupId: selectedGroupId.value,
+            groupId: searchForm.value?.groupId || selectedGroupId.value,
             current: pagination.current,
             pageSize: pagination.size
           })
@@ -258,7 +855,7 @@
         { timeoutMessage: t('pages.basicInfo.whMat.messages.listTimeout') }
       )
       tableData.value = Array.isArray(response?.records)
-        ? response.records.map((record) => normalizeMatnrRow(record, t))
+        ? response.records.map((record) => normalizeMatnrRow(record, t, enabledFields.value))
         : []
       updatePaginationState(pagination, response, pagination.current, pagination.size)
     } catch (error) {
@@ -269,16 +866,21 @@
     }
   }
 
+  async function loadMatnrDetail(id) {
+    return await guardRequestWithMessage(
+      fetchMatnrDetail(id),
+      {},
+      {
+        timeoutMessage: t('pages.basicInfo.whMat.messages.detailTimeout')
+      }
+    )
+  }
+
   async function openDetailDrawer(row) {
     detailDrawerVisible.value = true
     detailLoading.value = true
     try {
-      detailData.value = normalizeMatnrDetail(
-        await guardRequestWithMessage(fetchMatnrDetail(row.id), {}, {
-          timeoutMessage: t('pages.basicInfo.whMat.messages.detailTimeout')
-        }),
-        t
-      )
+      detailData.value = normalizeMatnrDetail(await loadMatnrDetail(row.id), t, enabledFields.value)
     } catch (error) {
       detailDrawerVisible.value = false
       detailData.value = {}
@@ -288,21 +890,236 @@
     }
   }
 
+  async function openEditDialog(row) {
+    try {
+      const detail = await loadMatnrDetail(row.id)
+      showDialog('edit', detail)
+    } catch (error) {
+      ElMessage.error(error?.message || t('pages.basicInfo.whMat.messages.detailLoadFailed'))
+    }
+  }
+
+  const {
+    dialogVisible,
+    dialogType,
+    currentRecord: currentMaterialData,
+    selectedRows,
+    handleSelectionChange,
+    showDialog,
+    handleDialogSubmit,
+    handleDelete,
+    handleBatchDelete
+  } = useCrudPage({
+    createEmptyModel: () =>
+      buildWhMatDialogModel({ groupId: selectedGroupId.value || searchForm.value?.groupId || '' }),
+    buildEditModel: (record) => buildWhMatDialogModel(record),
+    buildSavePayload: (formData) => buildWhMatSavePayload(formData),
+    saveRequest: fetchSaveMatnr,
+    updateRequest: fetchUpdateMatnr,
+    deleteRequest: fetchDeleteMatnr,
+    entityName: t('pages.basicInfo.whMat.entity'),
+    resolveRecordLabel: (record) => record?.name || record?.code || record?.id,
+    refreshCreate: loadMatnrList,
+    refreshUpdate: loadMatnrList,
+    refreshRemove: loadMatnrList
+  })
+  handleDeleteAction = handleDelete
+
+  const getSelectedIds = () =>
+    selectedRows.value.map((item) => Number(item?.id)).filter((id) => Number.isFinite(id))
+
+  const ensureSelectedRows = () => {
+    const ids = getSelectedIds()
+    if (!ids.length) {
+      ElMessage.warning(t('pages.basicInfo.whMat.messages.selectAtLeastOne'))
+      return []
+    }
+    return ids
+  }
+
+  function openBatchDialog(type) {
+    if (!ensureSelectedRows().length) {
+      return
+    }
+    batchDialogType.value = type
+    batchDialogVisible.value = true
+  }
+
+  function openBatchGroupDialog() {
+    if (!ensureSelectedRows().length) {
+      return
+    }
+    batchGroupDialogVisible.value = true
+  }
+
+  async function openBindLocDialog() {
+    if (!ensureSelectedRows().length) {
+      return
+    }
+    try {
+      await ensureBindLocOptionsLoaded()
+      bindLocDialogVisible.value = true
+    } catch (error) {
+      ElMessage.error(error?.message || t('pages.basicInfo.whMat.messages.bindLocLoadFailed'))
+    }
+  }
+
+  async function handleBatchDialogSubmit(formData) {
+    const ids = ensureSelectedRows()
+    if (!ids.length) {
+      batchDialogVisible.value = false
+      return
+    }
+
+    try {
+      await fetchBatchUpdateMatnr({
+        ids,
+        matnr: formData
+      })
+      ElMessage.success(t('crud.messages.updateSuccess'))
+      batchDialogVisible.value = false
+      selectedRows.value = []
+      await loadMatnrList()
+    } catch (error) {
+      ElMessage.error(error?.message || t('crud.messages.submitFailed'))
+    }
+  }
+
+  async function handleBatchGroupSubmit(formData) {
+    const ids = ensureSelectedRows()
+    if (!ids.length) {
+      batchGroupDialogVisible.value = false
+      return
+    }
+
+    try {
+      await fetchBindMatnrGroup({
+        ids,
+        groupId: formData.groupId
+      })
+      ElMessage.success(t('crud.messages.updateSuccess'))
+      batchGroupDialogVisible.value = false
+      selectedRows.value = []
+      await loadMatnrList()
+    } catch (error) {
+      ElMessage.error(error?.message || t('crud.messages.submitFailed'))
+    }
+  }
+
+  async function handleBindLocSubmit(formData) {
+    const ids = ensureSelectedRows()
+    if (!ids.length) {
+      bindLocDialogVisible.value = false
+      return
+    }
+
+    try {
+      await fetchBindLocAreaMatRelaByMatnr({
+        ...formData,
+        matnrId: ids
+      })
+      ElMessage.success(t('crud.messages.updateSuccess'))
+      bindLocDialogVisible.value = false
+      selectedRows.value = []
+      await loadMatnrList()
+    } catch (error) {
+      ElMessage.error(error?.message || t('crud.messages.submitFailed'))
+    }
+  }
+
+  const buildPreviewMeta = (rows) => {
+    const now = new Date()
+    return {
+      reportDate: now.toLocaleDateString('zh-CN'),
+      printedAt: now.toLocaleString('zh-CN', { hour12: false }),
+      operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+      count: rows.length,
+      reportStyle: { ...WH_MAT_REPORT_STYLE }
+    }
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetMatnrMany(payload.ids)).records
+    }
+    return tableData.value
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'matnr.xlsx',
+    requestExport: (payload) =>
+      fetchExportMatnrReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildWhMatPrintRows(records, t),
+    buildPreviewMeta
+  })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildWhMatReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation: previewMeta.value?.reportStyle?.orientation || WH_MAT_REPORT_STYLE.orientation
+    })
+  )
+
+  async function downloadFile(response, fallbackName) {
+    if (!response?.ok) {
+      throw new Error(
+        t('crud.messages.exportFailedWithStatus', { status: response?.status || '-' })
+      )
+    }
+    const blob = await response.blob()
+    const downloadUrl = window.URL.createObjectURL(blob)
+    const link = document.createElement('a')
+    link.href = downloadUrl
+    link.download = fallbackName
+    document.body.appendChild(link)
+    link.click()
+    link.remove()
+    window.URL.revokeObjectURL(downloadUrl)
+  }
+
+  function handleShowDialog(type) {
+    showDialog(type)
+  }
+
   function handleSearch(params) {
     searchForm.value = {
       ...searchForm.value,
-      ...params
+      ...params,
+      orderBy: searchForm.value?.orderBy || 'create_time desc'
     }
     pagination.current = 1
+    if (searchForm.value.groupId) {
+      selectedGroupId.value = null
+    }
     loadMatnrList()
   }
 
   async function handleReset() {
     searchForm.value = createWhMatSearchState()
+    enabledFields.value.forEach((field) => {
+      searchForm.value[getWhMatDynamicFieldKey(field.fields)] = ''
+    })
     pagination.current = 1
     selectedGroupId.value = null
     groupSearch.value = ''
     await Promise.all([loadGroupTree(), loadMatnrList()])
+  }
+
+  function handleRefresh() {
+    loadMatnrList()
   }
 
   function handleSizeChange(size) {
@@ -318,6 +1135,7 @@
 
   function handleGroupNodeClick(data) {
     selectedGroupId.value = data?.id ?? null
+    delete searchForm.value.groupId
     pagination.current = 1
     loadMatnrList()
   }
@@ -335,46 +1153,154 @@
     await Promise.all([loadGroupTree(), loadMatnrList()])
   }
 
+  async function handleImportFileChange(uploadFile) {
+    if (!uploadFile?.raw) {
+      return
+    }
+    importing.value = true
+    try {
+      await fetchImportMatnr(uploadFile.raw)
+      ElMessage.success(t('pages.basicInfo.whMat.messages.importSuccess'))
+      await loadMatnrList()
+    } catch (error) {
+      ElMessage.error(error?.message || t('pages.basicInfo.whMat.messages.importFailed'))
+    } finally {
+      importing.value = false
+    }
+  }
+
+  async function handleDownloadTemplate() {
+    templateDownloading.value = true
+    try {
+      const response = await fetchDownloadMatnrTemplate(
+        {},
+        {
+          headers: {
+            Authorization: userStore.accessToken || ''
+          }
+        }
+      )
+      await downloadFile(response, 'matnr-template.xlsx')
+      ElMessage.success(t('pages.basicInfo.whMat.messages.templateDownloadSuccess'))
+    } catch (error) {
+      ElMessage.error(error?.message || t('pages.basicInfo.whMat.messages.templateDownloadFailed'))
+    } finally {
+      templateDownloading.value = false
+    }
+  }
+
   onMounted(async () => {
-    await Promise.all([loadGroupTree(), loadMatnrList()])
+    await Promise.allSettled([
+      loadEnabledFieldDefinitions(),
+      loadGroupTree(),
+      loadSerialRuleOptions()
+    ])
+    await loadMatnrList()
   })
 </script>
 
 <style scoped>
+  .wh-mat-page-root {
+    height: 100%;
+    min-height: 0;
+    display: flex;
+    flex-direction: column;
+  }
+
   .wh-mat-page {
     display: flex;
     flex-direction: row;
-    align-items: flex-start;
+    align-items: stretch;
     gap: 16px;
+    flex: 1 1 auto;
+    min-height: 0;
   }
 
   .wh-mat-page__sidebar {
     width: 320px;
     flex: 0 0 320px;
+    min-height: 0;
+    display: flex;
+    flex-direction: column;
   }
 
   .wh-mat-page__sidebar-card {
-    position: sticky;
-    top: 16px;
+    height: 100%;
+    min-height: 0;
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+  }
+
+  .wh-mat-page__sidebar-card :deep(.el-card__body) {
+    height: 100%;
+    min-height: 0;
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
   }
 
   .wh-mat-page__tree-scroll {
-    height: calc(100vh - 320px);
-    min-height: 420px;
+    flex: 1 1 auto;
+    min-height: 0;
   }
 
   .wh-mat-page__content {
+    height: 100%;
     min-width: 0;
+    min-height: 0;
     flex: 1 1 auto;
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
   }
 
   .wh-mat-page__content > * + * {
     margin-top: 16px;
   }
 
+  .wh-mat-page__content > :deep(.art-search-bar) {
+    flex: 0 0 auto;
+  }
+
+  .wh-mat-page__content > :deep(.art-table-card) {
+    flex: 1 1 auto;
+    min-height: 0;
+    overflow: hidden;
+  }
+
+  .wh-mat-page__content > :deep(.art-table-card .el-card__body) {
+    display: flex;
+    min-height: 0;
+    flex-direction: column;
+  }
+
+  .wh-mat-page__content > :deep(.art-table-card #art-table-header) {
+    flex: 0 0 auto;
+  }
+
+  .wh-mat-page__content > :deep(.art-table-card .art-table) {
+    flex: 1 1 auto;
+    min-height: 0;
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+  }
+
+  .wh-mat-page__content > :deep(.art-table-card .art-table .el-table) {
+    flex: 1 1 auto;
+    min-height: 0;
+    height: auto;
+  }
+
+  .wh-mat-page__content > :deep(.art-table-card .art-table .pagination) {
+    flex: 0 0 auto;
+  }
+
   @media (max-width: 1024px) {
     .wh-mat-page {
       flex-direction: column;
+      flex: none;
     }
 
     .wh-mat-page__sidebar {
@@ -383,7 +1309,7 @@
     }
 
     .wh-mat-page__sidebar-card {
-      position: static;
+      height: auto;
     }
 
     .wh-mat-page__tree-scroll {
diff --git a/rsf-design/src/views/basic-info/wh-mat/modules/wh-mat-batch-dialog.vue b/rsf-design/src/views/basic-info/wh-mat/modules/wh-mat-batch-dialog.vue
new file mode 100644
index 0000000..c5aa0f5
--- /dev/null
+++ b/rsf-design/src/views/basic-info/wh-mat/modules/wh-mat-batch-dialog.vue
@@ -0,0 +1,232 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="640px"
+    align-center
+    destroy-on-close
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="120px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">{{ t('common.cancel') }}</ElButton>
+        <ElButton type="primary" @click="handleSubmit">{{ t('common.confirm') }}</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import { computed, nextTick, reactive, ref, watch } from 'vue'
+  import { useI18n } from 'vue-i18n'
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import {
+    getWhMatFlagCheckOptions,
+    getWhMatStatusOptions,
+    getWhMatStockLevelOptions
+  } from '../whMatPage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    actionType: { type: String, default: 'status' }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const { t } = useI18n()
+
+  const formRef = ref()
+  const form = reactive(createFormState())
+
+  function createFormState() {
+    return {
+      status: '',
+      stockLevel: '',
+      validWarn: null,
+      valid: null,
+      flagCheck: ''
+    }
+  }
+
+  const dialogTitle = computed(() =>
+    t(`pages.basicInfo.whMat.batchDialog.titles.${props.actionType}`)
+  )
+
+  const formItems = computed(() => {
+    if (props.actionType === 'status') {
+      return [
+        {
+          label: t('table.status'),
+          key: 'status',
+          type: 'select',
+          span: 24,
+          props: {
+            clearable: true,
+            placeholder: t('pages.basicInfo.whMat.search.statusPlaceholder'),
+            options: getWhMatStatusOptions(t)
+          }
+        }
+      ]
+    }
+
+    if (props.actionType === 'stockLevel') {
+      return [
+        {
+          label: t('pages.basicInfo.whMat.batchDialog.fields.stockLevel'),
+          key: 'stockLevel',
+          type: 'select',
+          span: 24,
+          props: {
+            clearable: true,
+            placeholder: t('pages.basicInfo.whMat.batchDialog.placeholders.stockLevel'),
+            options: getWhMatStockLevelOptions()
+          }
+        }
+      ]
+    }
+
+    if (props.actionType === 'validWarn') {
+      return [
+        {
+          label: t('pages.basicInfo.whMat.dialog.fields.validWarn'),
+          key: 'validWarn',
+          type: 'number',
+          props: {
+            min: 0,
+            controlsPosition: 'right',
+            valueOnClear: null,
+            placeholder: t('pages.basicInfo.whMat.batchDialog.placeholders.validWarn')
+          }
+        },
+        {
+          label: t('pages.basicInfo.whMat.dialog.fields.valid'),
+          key: 'valid',
+          type: 'number',
+          props: {
+            min: 0,
+            controlsPosition: 'right',
+            valueOnClear: null,
+            placeholder: t('pages.basicInfo.whMat.batchDialog.placeholders.valid')
+          }
+        }
+      ]
+    }
+
+    return [
+      {
+        label: t('pages.basicInfo.whMat.dialog.fields.flagCheck'),
+        key: 'flagCheck',
+        type: 'select',
+        span: 24,
+        props: {
+          clearable: true,
+          placeholder: t('pages.basicInfo.whMat.batchDialog.placeholders.flagCheck'),
+          options: getWhMatFlagCheckOptions(t)
+        }
+      }
+    ]
+  })
+
+  const rules = computed(() => {
+    if (props.actionType === 'status') {
+      return {
+        status: [
+          {
+            required: true,
+            message: t('pages.basicInfo.whMat.batchDialog.validation.status'),
+            trigger: 'change'
+          }
+        ]
+      }
+    }
+
+    if (props.actionType === 'stockLevel') {
+      return {
+        stockLevel: [
+          {
+            required: true,
+            message: t('pages.basicInfo.whMat.batchDialog.validation.stockLevel'),
+            trigger: 'change'
+          }
+        ]
+      }
+    }
+
+    if (props.actionType === 'validWarn') {
+      return {
+        validWarn: [
+          {
+            required: true,
+            message: t('pages.basicInfo.whMat.batchDialog.validation.validWarn'),
+            trigger: 'blur'
+          }
+        ],
+        valid: [
+          {
+            required: true,
+            message: t('pages.basicInfo.whMat.batchDialog.validation.valid'),
+            trigger: 'blur'
+          }
+        ]
+      }
+    }
+
+    return {
+      flagCheck: [
+        {
+          required: true,
+          message: t('pages.basicInfo.whMat.batchDialog.validation.flagCheck'),
+          trigger: 'change'
+        }
+      ]
+    }
+  })
+
+  function resetForm() {
+    Object.assign(form, createFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  async function handleSubmit() {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  function handleCancel() {
+    emit('update:visible', false)
+  }
+
+  function handleClosed() {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        resetForm()
+        nextTick(() => {
+          formRef.value?.clearValidate?.()
+        })
+      }
+    },
+    { immediate: true }
+  )
+</script>
diff --git a/rsf-design/src/views/basic-info/wh-mat/modules/wh-mat-batch-group-dialog.vue b/rsf-design/src/views/basic-info/wh-mat/modules/wh-mat-batch-group-dialog.vue
new file mode 100644
index 0000000..f75c105
--- /dev/null
+++ b/rsf-design/src/views/basic-info/wh-mat/modules/wh-mat-batch-group-dialog.vue
@@ -0,0 +1,113 @@
+<template>
+  <ElDialog
+    :title="t('pages.basicInfo.whMat.batchGroupDialog.title')"
+    :model-value="visible"
+    width="640px"
+    align-center
+    destroy-on-close
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="24"
+      :gutter="20"
+      label-width="120px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">{{ t('common.cancel') }}</ElButton>
+        <ElButton type="primary" @click="handleSubmit">{{ t('common.confirm') }}</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import { computed, nextTick, reactive, ref, watch } from 'vue'
+  import { useI18n } from 'vue-i18n'
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    groupOptions: { type: Array, default: () => [] }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const { t } = useI18n()
+
+  const formRef = ref()
+  const form = reactive({ groupId: '' })
+
+  const formItems = computed(() => [
+    {
+      label: t('pages.basicInfo.whMat.dialog.fields.groupId'),
+      key: 'groupId',
+      type: 'treeselect',
+      props: {
+        data: props.groupOptions,
+        props: {
+          label: 'displayLabel',
+          value: 'value',
+          children: 'children'
+        },
+        placeholder: t('pages.basicInfo.whMat.dialog.placeholders.groupId'),
+        clearable: false,
+        checkStrictly: true,
+        defaultExpandAll: true
+      }
+    }
+  ])
+
+  const rules = computed(() => ({
+    groupId: [
+      {
+        required: true,
+        message: t('pages.basicInfo.whMat.dialog.validation.groupId'),
+        trigger: 'change'
+      }
+    ]
+  }))
+
+  function resetForm() {
+    form.groupId = ''
+    formRef.value?.clearValidate?.()
+  }
+
+  async function handleSubmit() {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  function handleCancel() {
+    emit('update:visible', false)
+  }
+
+  function handleClosed() {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        resetForm()
+        nextTick(() => {
+          formRef.value?.clearValidate?.()
+        })
+      }
+    },
+    { immediate: true }
+  )
+</script>
diff --git a/rsf-design/src/views/basic-info/wh-mat/modules/wh-mat-bind-loc-dialog.vue b/rsf-design/src/views/basic-info/wh-mat/modules/wh-mat-bind-loc-dialog.vue
new file mode 100644
index 0000000..60f6862
--- /dev/null
+++ b/rsf-design/src/views/basic-info/wh-mat/modules/wh-mat-bind-loc-dialog.vue
@@ -0,0 +1,184 @@
+<template>
+  <ElDialog
+    :title="t('pages.basicInfo.whMat.bindLocDialog.title')"
+    :model-value="visible"
+    width="960px"
+    align-center
+    destroy-on-close
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="120px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">{{ t('common.cancel') }}</ElButton>
+        <ElButton type="primary" @click="handleSubmit">{{ t('common.confirm') }}</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import { computed, nextTick, reactive, ref, watch } from 'vue'
+  import { useI18n } from 'vue-i18n'
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    areaMatOptions: { type: Array, default: () => [] },
+    areaOptions: { type: Array, default: () => [] },
+    locOptions: { type: Array, default: () => [] }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const { t } = useI18n()
+
+  const formRef = ref()
+  const form = reactive({
+    areaMatId: '',
+    areaId: '',
+    locId: []
+  })
+
+  const filteredAreaMatOptions = computed(() => {
+    const selectedAreaId =
+      form.areaId !== undefined && form.areaId !== null && form.areaId !== ''
+        ? Number(form.areaId)
+        : void 0
+    if (selectedAreaId === void 0) {
+      return props.areaMatOptions
+    }
+    return props.areaMatOptions.filter(
+      (item) => item.areaId === void 0 || Number(item.areaId) === selectedAreaId
+    )
+  })
+
+  const filteredLocOptions = computed(() => {
+    const selectedAreaId =
+      form.areaId !== undefined && form.areaId !== null && form.areaId !== ''
+        ? Number(form.areaId)
+        : void 0
+    if (selectedAreaId === void 0) {
+      return props.locOptions
+    }
+    return props.locOptions.filter(
+      (item) => item.areaId === void 0 || Number(item.areaId) === selectedAreaId
+    )
+  })
+
+  const formItems = computed(() => [
+    {
+      label: t('pages.basicInfo.whMat.bindLocDialog.fields.areaMatId'),
+      key: 'areaMatId',
+      type: 'select',
+      props: {
+        clearable: true,
+        filterable: true,
+        placeholder: t('pages.basicInfo.whMat.bindLocDialog.placeholders.areaMatId'),
+        options: filteredAreaMatOptions.value
+      }
+    },
+    {
+      label: t('pages.basicInfo.whMat.bindLocDialog.fields.areaId'),
+      key: 'areaId',
+      type: 'select',
+      props: {
+        clearable: true,
+        filterable: true,
+        placeholder: t('pages.basicInfo.whMat.bindLocDialog.placeholders.areaId'),
+        options: props.areaOptions
+      }
+    },
+    {
+      label: t('pages.basicInfo.whMat.bindLocDialog.fields.locId'),
+      key: 'locId',
+      type: 'select',
+      span: 24,
+      props: {
+        clearable: true,
+        filterable: true,
+        multiple: true,
+        collapseTags: true,
+        placeholder: t('pages.basicInfo.whMat.bindLocDialog.placeholders.locId'),
+        options: filteredLocOptions.value
+      }
+    }
+  ])
+
+  const rules = computed(() => ({
+    areaMatId: [
+      {
+        required: true,
+        message: t('pages.basicInfo.whMat.bindLocDialog.validation.areaMatId'),
+        trigger: 'change'
+      }
+    ],
+    areaId: [
+      {
+        required: true,
+        message: t('pages.basicInfo.whMat.bindLocDialog.validation.areaId'),
+        trigger: 'change'
+      }
+    ],
+    locId: [
+      {
+        required: true,
+        message: t('pages.basicInfo.whMat.bindLocDialog.validation.locId'),
+        trigger: 'change'
+      }
+    ]
+  }))
+
+  function resetForm() {
+    form.areaMatId = ''
+    form.areaId = ''
+    form.locId = []
+    formRef.value?.clearValidate?.()
+  }
+
+  async function handleSubmit() {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', {
+        areaMatId: form.areaMatId,
+        areaId: form.areaId,
+        locId: Array.isArray(form.locId) ? [...form.locId] : []
+      })
+    } catch {
+      return
+    }
+  }
+
+  function handleCancel() {
+    emit('update:visible', false)
+  }
+
+  function handleClosed() {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        resetForm()
+        nextTick(() => {
+          formRef.value?.clearValidate?.()
+        })
+      }
+    },
+    { immediate: true }
+  )
+</script>
diff --git a/rsf-design/src/views/basic-info/wh-mat/modules/wh-mat-dialog.vue b/rsf-design/src/views/basic-info/wh-mat/modules/wh-mat-dialog.vue
new file mode 100644
index 0000000..19b2489
--- /dev/null
+++ b/rsf-design/src/views/basic-info/wh-mat/modules/wh-mat-dialog.vue
@@ -0,0 +1,380 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="960px"
+    align-center
+    destroy-on-close
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ElForm
+      ref="formRef"
+      :model="form"
+      :rules="rules"
+      label-width="110px"
+      class="wh-mat-dialog-form"
+    >
+      <ElTabs v-model="activeTab">
+        <ElTabPane :label="t('pages.basicInfo.whMat.dialog.tabs.basic')" name="basic">
+          <ElRow :gutter="20">
+            <ElCol :span="12">
+              <ElFormItem :label="t('pages.basicInfo.whMat.dialog.fields.code')" prop="code">
+                <ElInput
+                  v-model.trim="form.code"
+                  :placeholder="t('pages.basicInfo.whMat.dialog.placeholders.code')"
+                  clearable
+                />
+              </ElFormItem>
+            </ElCol>
+            <ElCol :span="12">
+              <ElFormItem :label="t('pages.basicInfo.whMat.dialog.fields.name')" prop="name">
+                <ElInput
+                  v-model.trim="form.name"
+                  :placeholder="t('pages.basicInfo.whMat.dialog.placeholders.name')"
+                  clearable
+                />
+              </ElFormItem>
+            </ElCol>
+            <ElCol :span="12">
+              <ElFormItem :label="t('pages.basicInfo.whMat.dialog.fields.groupId')" prop="groupId">
+                <ElTreeSelect
+                  v-model="form.groupId"
+                  :data="groupOptions"
+                  :props="groupTreeProps"
+                  check-strictly
+                  default-expand-all
+                  clearable
+                  :placeholder="t('pages.basicInfo.whMat.dialog.placeholders.groupId')"
+                />
+              </ElFormItem>
+            </ElCol>
+            <ElCol :span="12">
+              <ElFormItem
+                :label="t('pages.basicInfo.whMat.dialog.fields.useOrgName')"
+                prop="useOrgName"
+              >
+                <ElInput
+                  v-model.trim="form.useOrgName"
+                  :placeholder="t('pages.basicInfo.whMat.dialog.placeholders.useOrgName')"
+                  clearable
+                />
+              </ElFormItem>
+            </ElCol>
+            <ElCol :span="12">
+              <ElFormItem :label="t('pages.basicInfo.whMat.dialog.fields.spec')" prop="spec">
+                <ElInput
+                  v-model.trim="form.spec"
+                  :placeholder="t('pages.basicInfo.whMat.dialog.placeholders.spec')"
+                  clearable
+                />
+              </ElFormItem>
+            </ElCol>
+            <ElCol :span="12">
+              <ElFormItem :label="t('pages.basicInfo.whMat.dialog.fields.model')" prop="model">
+                <ElInput
+                  v-model.trim="form.model"
+                  :placeholder="t('pages.basicInfo.whMat.dialog.placeholders.model')"
+                  clearable
+                />
+              </ElFormItem>
+            </ElCol>
+            <ElCol :span="12">
+              <ElFormItem :label="t('pages.basicInfo.whMat.dialog.fields.color')" prop="color">
+                <ElInput
+                  v-model.trim="form.color"
+                  :placeholder="t('pages.basicInfo.whMat.dialog.placeholders.color')"
+                  clearable
+                />
+              </ElFormItem>
+            </ElCol>
+            <ElCol :span="12">
+              <ElFormItem :label="t('pages.basicInfo.whMat.dialog.fields.size')" prop="size">
+                <ElInput
+                  v-model.trim="form.size"
+                  :placeholder="t('pages.basicInfo.whMat.dialog.placeholders.size')"
+                  clearable
+                />
+              </ElFormItem>
+            </ElCol>
+            <ElCol :span="12">
+              <ElFormItem :label="t('pages.basicInfo.whMat.dialog.fields.weight')" prop="weight">
+                <ElInputNumber
+                  v-model="form.weight"
+                  :min="0"
+                  controls-position="right"
+                  class="w-full"
+                />
+              </ElFormItem>
+            </ElCol>
+            <ElCol :span="12">
+              <ElFormItem :label="t('pages.basicInfo.whMat.dialog.fields.unit')" prop="unit">
+                <ElInput
+                  v-model.trim="form.unit"
+                  :placeholder="t('pages.basicInfo.whMat.dialog.placeholders.unit')"
+                  clearable
+                />
+              </ElFormItem>
+            </ElCol>
+            <ElCol :span="12">
+              <ElFormItem :label="t('pages.basicInfo.whMat.dialog.fields.purUnit')" prop="purUnit">
+                <ElInput
+                  v-model.trim="form.purUnit"
+                  :placeholder="t('pages.basicInfo.whMat.dialog.placeholders.purUnit')"
+                  clearable
+                />
+              </ElFormItem>
+            </ElCol>
+            <ElCol :span="12">
+              <ElFormItem
+                :label="t('pages.basicInfo.whMat.dialog.fields.describle')"
+                prop="describle"
+              >
+                <ElInput
+                  v-model.trim="form.describle"
+                  :placeholder="t('pages.basicInfo.whMat.dialog.placeholders.describle')"
+                  clearable
+                />
+              </ElFormItem>
+            </ElCol>
+          </ElRow>
+        </ElTabPane>
+
+        <ElTabPane :label="t('pages.basicInfo.whMat.dialog.tabs.control')" name="control">
+          <ElRow :gutter="20">
+            <ElCol :span="12">
+              <ElFormItem :label="t('pages.basicInfo.whMat.dialog.fields.safeQty')" prop="safeQty">
+                <ElInputNumber
+                  v-model="form.safeQty"
+                  :min="0"
+                  controls-position="right"
+                  class="w-full"
+                />
+              </ElFormItem>
+            </ElCol>
+            <ElCol :span="12">
+              <ElFormItem :label="t('pages.basicInfo.whMat.dialog.fields.minQty')" prop="minQty">
+                <ElInputNumber
+                  v-model="form.minQty"
+                  :min="0"
+                  controls-position="right"
+                  class="w-full"
+                />
+              </ElFormItem>
+            </ElCol>
+            <ElCol :span="12">
+              <ElFormItem :label="t('pages.basicInfo.whMat.dialog.fields.maxQty')" prop="maxQty">
+                <ElInputNumber
+                  v-model="form.maxQty"
+                  :min="0"
+                  controls-position="right"
+                  class="w-full"
+                />
+              </ElFormItem>
+            </ElCol>
+            <ElCol :span="12">
+              <ElFormItem :label="t('pages.basicInfo.whMat.dialog.fields.stagn')" prop="stagn">
+                <ElInputNumber
+                  v-model="form.stagn"
+                  :min="0"
+                  controls-position="right"
+                  class="w-full"
+                />
+              </ElFormItem>
+            </ElCol>
+            <ElCol :span="12">
+              <ElFormItem :label="t('pages.basicInfo.whMat.dialog.fields.valid')" prop="valid">
+                <ElInputNumber
+                  v-model="form.valid"
+                  :min="0"
+                  controls-position="right"
+                  class="w-full"
+                />
+              </ElFormItem>
+            </ElCol>
+            <ElCol :span="12">
+              <ElFormItem
+                :label="t('pages.basicInfo.whMat.dialog.fields.validWarn')"
+                prop="validWarn"
+              >
+                <ElInputNumber
+                  v-model="form.validWarn"
+                  :min="0"
+                  controls-position="right"
+                  class="w-full"
+                />
+              </ElFormItem>
+            </ElCol>
+            <ElCol :span="12">
+              <ElFormItem
+                :label="t('pages.basicInfo.whMat.dialog.fields.flagCheck')"
+                prop="flagCheck"
+              >
+                <ElSelect
+                  v-model="form.flagCheck"
+                  clearable
+                  :placeholder="t('pages.basicInfo.whMat.dialog.placeholders.flagCheck')"
+                >
+                  <ElOption
+                    v-for="option in flagCheckOptions"
+                    :key="option.value"
+                    :label="option.label"
+                    :value="option.value"
+                  />
+                </ElSelect>
+              </ElFormItem>
+            </ElCol>
+          </ElRow>
+        </ElTabPane>
+
+        <ElTabPane :label="t('pages.basicInfo.whMat.dialog.tabs.batchRule')" name="batchRule">
+          <ElRow :gutter="20">
+            <ElCol :span="12">
+              <ElFormItem :label="t('pages.basicInfo.whMat.dialog.fields.rglarId')" prop="rglarId">
+                <ElSelect
+                  v-model="form.rglarId"
+                  clearable
+                  filterable
+                  :placeholder="t('pages.basicInfo.whMat.dialog.placeholders.rglarId')"
+                >
+                  <ElOption
+                    v-for="option in serialRuleOptions"
+                    :key="option.value"
+                    :label="option.label"
+                    :value="option.value"
+                  />
+                </ElSelect>
+              </ElFormItem>
+            </ElCol>
+          </ElRow>
+        </ElTabPane>
+      </ElTabs>
+    </ElForm>
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">{{ t('common.cancel') }}</ElButton>
+        <ElButton type="primary" @click="handleSubmit">{{ t('common.confirm') }}</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import { computed, nextTick, reactive, ref, watch } from 'vue'
+  import { useI18n } from 'vue-i18n'
+  import {
+    buildWhMatDialogModel,
+    createWhMatFormState,
+    getWhMatFlagCheckOptions
+  } from '../whMatPage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    dialogType: { type: String, default: 'add' },
+    materialData: { type: Object, default: () => ({}) },
+    groupOptions: { type: Array, default: () => [] },
+    serialRuleOptions: { type: Array, default: () => [] }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const { t } = useI18n()
+
+  const formRef = ref()
+  const activeTab = ref('basic')
+  const form = reactive(createWhMatFormState())
+
+  const dialogTitle = computed(() =>
+    props.dialogType === 'edit'
+      ? t('pages.basicInfo.whMat.dialog.titleEdit')
+      : t('pages.basicInfo.whMat.dialog.titleCreate')
+  )
+  const flagCheckOptions = computed(() => getWhMatFlagCheckOptions(t))
+  const groupTreeProps = {
+    label: 'displayLabel',
+    value: 'value',
+    children: 'children'
+  }
+
+  const rules = computed(() => ({
+    code: [
+      {
+        required: true,
+        message: t('pages.basicInfo.whMat.dialog.validation.code'),
+        trigger: 'blur'
+      }
+    ],
+    name: [
+      {
+        required: true,
+        message: t('pages.basicInfo.whMat.dialog.validation.name'),
+        trigger: 'blur'
+      }
+    ],
+    groupId: [
+      {
+        required: true,
+        message: t('pages.basicInfo.whMat.dialog.validation.groupId'),
+        trigger: 'change'
+      }
+    ]
+  }))
+
+  function loadFormData() {
+    Object.assign(form, buildWhMatDialogModel(props.materialData))
+    activeTab.value = 'basic'
+  }
+
+  function resetForm() {
+    Object.assign(form, createWhMatFormState())
+    formRef.value?.clearValidate?.()
+    activeTab.value = 'basic'
+  }
+
+  async function handleSubmit() {
+    try {
+      await formRef.value?.validate?.()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  function handleCancel() {
+    emit('update:visible', false)
+  }
+
+  function handleClosed() {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => {
+          formRef.value?.clearValidate?.()
+        })
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.materialData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
+
+<style scoped>
+  .wh-mat-dialog-form :deep(.el-select),
+  .wh-mat-dialog-form :deep(.el-tree-select) {
+    width: 100%;
+  }
+</style>
diff --git a/rsf-design/src/views/basic-info/wh-mat/whMatPage.helpers.js b/rsf-design/src/views/basic-info/wh-mat/whMatPage.helpers.js
index 5930c2f..c433717 100644
--- a/rsf-design/src/views/basic-info/wh-mat/whMatPage.helpers.js
+++ b/rsf-design/src/views/basic-info/wh-mat/whMatPage.helpers.js
@@ -1,5 +1,13 @@
 import { $t } from '@/locales'
 
+export const WH_MAT_REPORT_TITLE = '鐗╂枡鎶ヨ〃'
+export const WH_MAT_REPORT_STYLE = {
+  orientation: 'landscape',
+  titleAlign: 'center',
+  titleLevel: 'h2'
+}
+export const WH_MAT_DYNAMIC_FIELD_PREFIX = 'extendField__'
+
 function normalizeText(value) {
   return String(value ?? '').trim()
 }
@@ -20,33 +28,137 @@
   return Number.isFinite(numericValue) ? numericValue : null
 }
 
+function normalizeNullableInteger(value) {
+  const numericValue = normalizeNullableNumber(value)
+  return numericValue === null ? null : Math.trunc(numericValue)
+}
+
+function normalizeBooleanLikeText(value, t = $t) {
+  if (value === 1 || value === '1') return t('common.status.yes')
+  if (value === 0 || value === '0') return t('common.status.no')
+  return t('common.placeholder.empty')
+}
+
 export function createWhMatSearchState() {
   return {
     condition: '',
     code: '',
     name: '',
+    platCode: '',
     spec: '',
     model: '',
-    barcode: ''
+    color: '',
+    size: '',
+    unit: '',
+    purUnit: '',
+    stockUnit: '',
+    barcode: '',
+    describle: '',
+    groupId: '',
+    rglarId: '',
+    weight: null,
+    nromNum: null,
+    stockLevel: '',
+    flagLabelMange: '',
+    safeQty: null,
+    minQty: null,
+    maxQty: null,
+    stagn: null,
+    valid: null,
+    status: '',
+    flagCheck: '',
+    validWarn: null,
+    memo: '',
+    orderBy: 'create_time desc'
+  }
+}
+
+export function createWhMatFormState() {
+  return {
+    id: void 0,
+    code: '',
+    name: '',
+    groupId: '',
+    useOrgName: '',
+    spec: '',
+    model: '',
+    color: '',
+    size: '',
+    weight: void 0,
+    unit: '',
+    purUnit: '',
+    describle: '',
+    safeQty: void 0,
+    minQty: void 0,
+    maxQty: void 0,
+    stagn: void 0,
+    valid: void 0,
+    validWarn: void 0,
+    flagCheck: 0,
+    rglarId: ''
   }
 }
 
 export function buildWhMatPageQueryParams(params = {}) {
   const result = {
     current: params.current || 1,
-    pageSize: params.pageSize || params.size || 20
+    pageSize: params.pageSize || params.size || 20,
+    orderBy: normalizeText(params.orderBy) || 'create_time desc'
   }
 
-  ;['condition', 'code', 'name', 'spec', 'model', 'barcode'].forEach((key) => {
+  ;[
+    'condition',
+    'code',
+    'name',
+    'platCode',
+    'spec',
+    'model',
+    'color',
+    'size',
+    'unit',
+    'purUnit',
+    'stockUnit',
+    'barcode',
+    'describle',
+    'memo'
+  ].forEach((key) => {
     const value = normalizeText(params[key])
     if (value) {
       result[key] = value
     }
   })
+  ;[
+    'groupId',
+    'rglarId',
+    'weight',
+    'nromNum',
+    'stockLevel',
+    'flagLabelMange',
+    'safeQty',
+    'minQty',
+    'maxQty',
+    'stagn',
+    'valid',
+    'status',
+    'flagCheck',
+    'validWarn'
+  ].forEach((key) => {
+    const value = params[key]
+    if (value !== '' && value !== null && value !== undefined) {
+      const numericValue = Number(value)
+      result[key] = Number.isFinite(numericValue) ? numericValue : value
+    }
+  })
 
-  if (params.groupId !== undefined && params.groupId !== null && params.groupId !== '') {
-    result.groupId = String(params.groupId)
-  }
+  Object.entries(params).forEach(([key, value]) => {
+    if (!key.startsWith(WH_MAT_DYNAMIC_FIELD_PREFIX)) {
+      return
+    }
+    const normalizedValue = normalizeText(value)
+    if (normalizedValue) {
+      result[key.slice(WH_MAT_DYNAMIC_FIELD_PREFIX.length)] = normalizedValue
+    }
+  })
 
   return result
 }
@@ -63,7 +175,7 @@
   }
 
   return records.map((item) => {
-    const children = normalizeWhMatGroupTreeRows(item?.children || [])
+    const children = normalizeWhMatGroupTreeRows(item?.children || [], t)
     const id = normalizeNullableNumber(item?.id)
     const code = normalizeText(item?.code)
     const name = normalizeText(item?.name)
@@ -77,8 +189,12 @@
       name,
       label,
       displayLabel: label,
+      value: id,
       status: normalizeNullableNumber(item?.status),
-      statusText: normalizeNumber(item?.status, 1) === 1 ? t('common.status.normal') : t('common.status.frozen'),
+      statusText:
+        normalizeNumber(item?.status, 1) === 1
+          ? t('common.status.normal')
+          : t('common.status.frozen'),
       statusType: normalizeNumber(item?.status, 1) === 1 ? 'success' : 'danger',
       memo: normalizeText(item?.memo) || t('common.placeholder.empty'),
       children
@@ -86,62 +202,241 @@
   })
 }
 
-export function normalizeWhMatRow(record = {}, t = $t) {
-  const statusValue = normalizeNullableNumber(record?.status)
+export function resolveWhMatGroupOptions(treeRows = []) {
+  if (!Array.isArray(treeRows)) {
+    return []
+  }
+
+  return treeRows.map((item) => ({
+    id: item.id,
+    value: item.id,
+    label:
+      item.displayLabel || item.label || [item.name, item.code].filter(Boolean).join(' 路 ') || '-',
+    displayLabel:
+      item.displayLabel || item.label || [item.name, item.code].filter(Boolean).join(' 路 ') || '-',
+    children: resolveWhMatGroupOptions(item.children || [])
+  }))
+}
+
+export function resolveWhMatSerialRuleOptions(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records
+    .map((item) => ({
+      value: normalizeNullableNumber(item?.id),
+      label: normalizeText(item?.name || item?.code || item?.description)
+    }))
+    .filter((item) => item.value !== null && item.label)
+}
+
+export function getWhMatStatusOptions(t = $t) {
+  return [
+    { value: 1, label: t('common.status.normal') },
+    { value: 0, label: t('common.status.frozen') }
+  ]
+}
+
+export function getWhMatFlagCheckOptions(t = $t) {
+  return [
+    { value: 0, label: t('common.status.no') },
+    { value: 1, label: t('common.status.yes') }
+  ]
+}
+
+export function getWhMatStockLevelOptions() {
+  return [
+    { value: 0, label: 'A' },
+    { value: 1, label: 'B' },
+    { value: 2, label: 'C' }
+  ]
+}
+
+export function getWhMatFlagLabelManageOptions(t = $t) {
+  return [
+    { value: 0, label: t('common.status.no') },
+    { value: 1, label: t('common.status.yes') }
+  ]
+}
+
+export function getWhMatDynamicFieldKey(fieldName) {
+  return `${WH_MAT_DYNAMIC_FIELD_PREFIX}${fieldName}`
+}
+
+export function normalizeWhMatEnabledFields(fields = []) {
+  if (!Array.isArray(fields)) {
+    return []
+  }
+
+  return fields
+    .map((item) => ({
+      fields: normalizeText(item?.fields),
+      fieldsAlise: normalizeText(item?.fieldsAlise || item?.fieldsAlias || item?.fields)
+    }))
+    .filter((item) => item.fields)
+}
+
+export function attachWhMatDynamicFields(record = {}, enabledFields = []) {
+  const extendFields =
+    record?.extendFields &&
+    typeof record.extendFields === 'object' &&
+    !Array.isArray(record.extendFields)
+      ? record.extendFields
+      : {}
+  const dynamicValues = {}
+
+  enabledFields.forEach((field) => {
+    dynamicValues[getWhMatDynamicFieldKey(field.fields)] = extendFields[field.fields] || ''
+  })
+
   return {
     ...record,
-    code: normalizeText(record?.code) || t('common.placeholder.empty'),
-    name: normalizeText(record?.name) || t('common.placeholder.empty'),
-    groupName: normalizeText(record?.groupId$ || record?.groupCode) || t('common.placeholder.empty'),
-    shipperName: normalizeText(record?.shipperId$ || record?.shipperName) || t('common.placeholder.empty'),
-    barcode: normalizeText(record?.barcode) || t('common.placeholder.empty'),
-    spec: normalizeText(record?.spec) || t('common.placeholder.empty'),
-    model: normalizeText(record?.model) || t('common.placeholder.empty'),
-    color: normalizeText(record?.color) || t('common.placeholder.empty'),
-    size: normalizeText(record?.size) || t('common.placeholder.empty'),
-    unit: normalizeText(record?.unit) || t('common.placeholder.empty'),
-    purUnit: normalizeText(record?.purUnit) || t('common.placeholder.empty'),
-    stockUnit: normalizeText(record?.stockUnit) || t('common.placeholder.empty'),
-    stockLevelText: normalizeText(record?.stockLeval$) || t('common.placeholder.empty'),
-    flagLabelManageText: normalizeText(record?.flagLabelMange$) || t('common.placeholder.empty'),
-    flagCheckText:
-      record?.flagCheck === 1 || record?.flagCheck === '1'
-        ? t('common.status.yes')
-        : record?.flagCheck === 0 || record?.flagCheck === '0'
-          ? t('common.status.no')
-          : t('common.placeholder.empty'),
-    statusText:
-      normalizeText(record?.status$) ||
-      (statusValue === 1
-        ? t('common.status.normal')
-        : statusValue === 0
-          ? t('common.status.frozen')
-          : t('common.placeholder.empty')),
-    statusType: statusValue === 1 ? 'success' : statusValue === 0 ? 'danger' : 'info',
-    safeQty: record?.safeQty ?? t('common.placeholder.empty'),
-    minQty: record?.minQty ?? t('common.placeholder.empty'),
-    maxQty: record?.maxQty ?? t('common.placeholder.empty'),
-    valid: record?.valid ?? t('common.placeholder.empty'),
-    validWarn: record?.validWarn ?? t('common.placeholder.empty'),
-    stagn: record?.stagn ?? t('common.placeholder.empty'),
-    describle: normalizeText(record?.describle) || t('common.placeholder.empty'),
-    baseUnit: normalizeText(record?.baseUnit) || t('common.placeholder.empty'),
-    useOrgName: normalizeText(record?.useOrgName) || t('common.placeholder.empty'),
-    erpClsId: normalizeText(record?.erpClsId) || t('common.placeholder.empty'),
-    memo: normalizeText(record?.memo) || t('common.placeholder.empty'),
-    updateByText: normalizeText(record?.updateBy$) || t('common.placeholder.empty'),
-    createByText: normalizeText(record?.createBy$) || t('common.placeholder.empty'),
-    updateTimeText: normalizeText(record?.updateTime$ || record?.updateTime) || t('common.placeholder.empty'),
-    createTimeText: normalizeText(record?.createTime$ || record?.createTime) || t('common.placeholder.empty'),
-    extendFields:
-      record?.extendFields && typeof record.extendFields === 'object' && !Array.isArray(record.extendFields)
-        ? record.extendFields
-        : {}
+    ...dynamicValues,
+    extendFields
   }
 }
 
-export function normalizeWhMatDetail(record = {}, t = $t) {
-  return normalizeWhMatRow(record, t)
+export function normalizeWhMatRow(record = {}, t = $t, enabledFields = []) {
+  const statusValue = normalizeNullableNumber(record?.status)
+  const validWarn = record?.validWarn ?? record?.valid_warn
+  const purUnit = record?.purUnit ?? record?.purchaseUnit
+  const stockLevelLabel =
+    getWhMatStockLevelOptions().find(
+      (item) => item.value === normalizeNullableNumber(record?.stockLevel)
+    )?.label || ''
+  const flagLabelManageLabel =
+    getWhMatFlagLabelManageOptions(t).find(
+      (item) => item.value === normalizeNullableNumber(record?.flagLabelMange)
+    )?.label || ''
+
+  return attachWhMatDynamicFields(
+    {
+      ...record,
+      id: normalizeNullableNumber(record?.id),
+      code: normalizeText(record?.code) || t('common.placeholder.empty'),
+      name: normalizeText(record?.name) || t('common.placeholder.empty'),
+      groupId: normalizeNullableNumber(record?.groupId),
+      groupName:
+        normalizeText(record?.groupId$ || record?.groupCode || record?.groupName) ||
+        t('common.placeholder.empty'),
+      shipperName:
+        normalizeText(record?.shipperId$ || record?.shipperName) || t('common.placeholder.empty'),
+      barcode: normalizeText(record?.barcode) || t('common.placeholder.empty'),
+      platCode: normalizeText(record?.platCode) || t('common.placeholder.empty'),
+      spec: normalizeText(record?.spec) || t('common.placeholder.empty'),
+      model: normalizeText(record?.model) || t('common.placeholder.empty'),
+      color: normalizeText(record?.color) || t('common.placeholder.empty'),
+      size: normalizeText(record?.size) || t('common.placeholder.empty'),
+      weight: record?.weight ?? t('common.placeholder.empty'),
+      nromNum: record?.nromNum ?? t('common.placeholder.empty'),
+      unit: normalizeText(record?.unit) || t('common.placeholder.empty'),
+      purUnit: normalizeText(purUnit) || t('common.placeholder.empty'),
+      stockUnit: normalizeText(record?.stockUnit) || t('common.placeholder.empty'),
+      stockLevelText:
+        normalizeText(record?.stockLeval$ || record?.stockLevel$ || stockLevelLabel) ||
+        t('common.placeholder.empty'),
+      flagLabelManageText:
+        normalizeText(record?.flagLabelMange$ || record?.isLabelMange$ || flagLabelManageLabel) ||
+        t('common.placeholder.empty'),
+      flagCheckText: normalizeBooleanLikeText(record?.flagCheck, t),
+      statusText:
+        normalizeText(record?.status$) ||
+        (statusValue === 1
+          ? t('common.status.normal')
+          : statusValue === 0
+            ? t('common.status.frozen')
+            : t('common.placeholder.empty')),
+      statusType: statusValue === 1 ? 'success' : statusValue === 0 ? 'danger' : 'info',
+      safeQty: record?.safeQty ?? t('common.placeholder.empty'),
+      minQty: record?.minQty ?? t('common.placeholder.empty'),
+      maxQty: record?.maxQty ?? t('common.placeholder.empty'),
+      valid: record?.valid ?? t('common.placeholder.empty'),
+      validWarn: validWarn ?? t('common.placeholder.empty'),
+      stagn: record?.stagn ?? t('common.placeholder.empty'),
+      describle: normalizeText(record?.describle) || t('common.placeholder.empty'),
+      baseUnit: normalizeText(record?.baseUnit) || t('common.placeholder.empty'),
+      useOrgName: normalizeText(record?.useOrgName) || t('common.placeholder.empty'),
+      erpClsId: normalizeText(record?.erpClsId) || t('common.placeholder.empty'),
+      rglarId: normalizeNullableNumber(record?.rglarId),
+      rglarName:
+        normalizeText(record?.rglarId$ || record?.rglarName || record?.rglarCode) ||
+        t('common.placeholder.empty'),
+      memo: normalizeText(record?.memo) || t('common.placeholder.empty'),
+      updateByText: normalizeText(record?.updateBy$) || t('common.placeholder.empty'),
+      createByText: normalizeText(record?.createBy$) || t('common.placeholder.empty'),
+      updateTimeText:
+        normalizeText(record?.updateTime$ || record?.updateTime) || t('common.placeholder.empty'),
+      createTimeText:
+        normalizeText(record?.createTime$ || record?.createTime) || t('common.placeholder.empty')
+    },
+    enabledFields
+  )
+}
+
+export function normalizeWhMatDetail(record = {}, t = $t, enabledFields = []) {
+  return normalizeWhMatRow(record, t, enabledFields)
+}
+
+export function buildWhMatDialogModel(record = {}) {
+  const source = normalizeWhMatRow(record)
+  return {
+    ...createWhMatFormState(),
+    id: source.id ?? void 0,
+    code: normalizeText(record?.code),
+    name: normalizeText(record?.name),
+    groupId: source.groupId ?? '',
+    useOrgName: normalizeText(record?.useOrgName),
+    spec: normalizeText(record?.spec),
+    model: normalizeText(record?.model),
+    color: normalizeText(record?.color),
+    size: normalizeText(record?.size),
+    weight: normalizeNullableNumber(record?.weight),
+    unit: normalizeText(record?.unit),
+    purUnit: normalizeText(record?.purUnit || record?.purchaseUnit),
+    describle: normalizeText(record?.describle),
+    safeQty: normalizeNullableNumber(record?.safeQty),
+    minQty: normalizeNullableNumber(record?.minQty),
+    maxQty: normalizeNullableNumber(record?.maxQty),
+    stagn: normalizeNullableInteger(record?.stagn),
+    valid: normalizeNullableInteger(record?.valid),
+    validWarn: normalizeNullableInteger(record?.validWarn),
+    flagCheck: normalizeNullableInteger(record?.flagCheck) ?? 0,
+    rglarId: normalizeNullableNumber(record?.rglarId) ?? ''
+  }
+}
+
+export function buildWhMatSavePayload(formData = {}) {
+  const payload = {
+    ...(formData.id !== undefined && formData.id !== null ? { id: Number(formData.id) } : {}),
+    code: normalizeText(formData.code),
+    name: normalizeText(formData.name),
+    groupId:
+      formData.groupId !== undefined && formData.groupId !== null && formData.groupId !== ''
+        ? Number(formData.groupId)
+        : void 0,
+    useOrgName: normalizeText(formData.useOrgName),
+    spec: normalizeText(formData.spec),
+    model: normalizeText(formData.model),
+    color: normalizeText(formData.color),
+    size: normalizeText(formData.size),
+    weight: normalizeNullableNumber(formData.weight),
+    unit: normalizeText(formData.unit),
+    purUnit: normalizeText(formData.purUnit),
+    describle: normalizeText(formData.describle),
+    safeQty: normalizeNullableNumber(formData.safeQty),
+    minQty: normalizeNullableNumber(formData.minQty),
+    maxQty: normalizeNullableNumber(formData.maxQty),
+    stagn: normalizeNullableInteger(formData.stagn),
+    valid: normalizeNullableInteger(formData.valid),
+    validWarn: normalizeNullableInteger(formData.validWarn),
+    flagCheck: normalizeNullableInteger(formData.flagCheck),
+    rglarId:
+      formData.rglarId !== undefined && formData.rglarId !== null && formData.rglarId !== ''
+        ? Number(formData.rglarId)
+        : void 0
+  }
+
+  return Object.fromEntries(Object.entries(payload).filter(([, value]) => value !== undefined))
 }
 
 export function getWhMatTreeNodeLabel(node = {}) {
@@ -150,6 +445,39 @@
   return [name, code].filter(Boolean).join(' 路 ') || $t('common.placeholder.empty')
 }
 
+export function buildWhMatPrintRows(records = [], t = $t) {
+  return records.map((record) => {
+    const normalizedRecord = normalizeWhMatRow(record, t)
+    return {
+      鐗╂枡缂栫爜: normalizedRecord.code,
+      鐗╂枡鍚嶇О: normalizedRecord.name,
+      鐗╂枡鍒嗙粍: normalizedRecord.groupName,
+      瑙勬牸: normalizedRecord.spec,
+      鍨嬪彿: normalizedRecord.model,
+      鍗曚綅: normalizedRecord.unit,
+      鐘舵��: normalizedRecord.statusText,
+      鏇存柊鏃堕棿: normalizedRecord.updateTimeText
+    }
+  })
+}
+
+export function buildWhMatReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = WH_MAT_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: WH_MAT_REPORT_TITLE,
+    count,
+    ...previewMeta,
+    reportStyle: {
+      ...WH_MAT_REPORT_STYLE,
+      ...(previewMeta.reportStyle || {}),
+      orientation
+    }
+  }
+}
+
 export const buildMatnrPageQueryParams = buildWhMatPageQueryParams
 export const buildMatnrGroupTreeQueryParams = buildWhMatGroupTreeQueryParams
 export const normalizeMatnrGroupTreeRows = normalizeWhMatGroupTreeRows
diff --git a/rsf-design/src/views/basic-info/wh-mat/whMatTable.columns.js b/rsf-design/src/views/basic-info/wh-mat/whMatTable.columns.js
index 9d4b6d4..12aba26 100644
--- a/rsf-design/src/views/basic-info/wh-mat/whMatTable.columns.js
+++ b/rsf-design/src/views/basic-info/wh-mat/whMatTable.columns.js
@@ -1,9 +1,67 @@
 import { h } from 'vue'
 import { ElTag } from 'element-plus'
 import { $t } from '@/locales'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+import { getWhMatDynamicFieldKey } from './whMatPage.helpers'
 
-export function createWhMatTableColumns({ handleViewDetail, t = $t }) {
+export function createWhMatTableColumns({
+  handleViewDetail,
+  handleEdit,
+  handleDelete,
+  handlePrint,
+  enabledFields = [],
+  canEdit = true,
+  canDelete = true,
+  t = $t
+} = {}) {
+  const operations = [{ key: 'view', label: t('common.actions.detail'), icon: 'ri:eye-line' }]
+
+  if (canEdit && handleEdit) {
+    operations.push({ key: 'edit', label: t('common.actions.edit'), icon: 'ri:pencil-line' })
+  }
+
+  if (handlePrint) {
+    operations.push({ key: 'print', label: t('common.actions.print'), icon: 'ri:printer-line' })
+  }
+
+  if (canDelete && handleDelete) {
+    operations.push({
+      key: 'delete',
+      label: t('common.actions.delete'),
+      icon: 'ri:delete-bin-5-line',
+      color: 'var(--art-error)'
+    })
+  }
+
+  const dynamicColumns = Array.isArray(enabledFields)
+    ? enabledFields.map((field) => ({
+        prop: getWhMatDynamicFieldKey(field.fields),
+        label: field.fieldsAlise,
+        minWidth: 140,
+        showOverflowTooltip: true,
+        formatter: (row) => row[getWhMatDynamicFieldKey(field.fields)] || '--'
+      }))
+    : []
+
   return [
+    {
+      type: 'selection',
+      width: 48,
+      align: 'center'
+    },
+    {
+      type: 'globalIndex',
+      label: t('table.index'),
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'id',
+      label: t('table.id'),
+      width: 90,
+      align: 'center',
+      formatter: (row) => row.id ?? '--'
+    },
     {
       prop: 'code',
       label: t('pages.basicInfo.whMat.table.code'),
@@ -19,7 +77,7 @@
     {
       prop: 'groupName',
       label: t('pages.basicInfo.whMat.table.groupName'),
-      minWidth: 160,
+      minWidth: 180,
       showOverflowTooltip: true
     },
     {
@@ -31,15 +89,16 @@
     {
       prop: 'spec',
       label: t('pages.basicInfo.whMat.table.spec'),
-      minWidth: 150,
+      minWidth: 160,
       showOverflowTooltip: true
     },
     {
       prop: 'model',
       label: t('pages.basicInfo.whMat.table.model'),
-      minWidth: 150,
+      minWidth: 160,
       showOverflowTooltip: true
     },
+    ...dynamicColumns,
     {
       prop: 'unit',
       label: t('table.unit'),
@@ -51,7 +110,17 @@
       width: 100,
       align: 'center',
       formatter: (row) =>
-        h(ElTag, { type: row.statusType || 'info', effect: 'light' }, () => row.statusText || t('common.placeholder.empty'))
+        h(
+          ElTag,
+          { type: row.statusType || 'info', effect: 'light' },
+          () => row.statusText || t('common.placeholder.empty')
+        )
+    },
+    {
+      prop: 'updateByText',
+      label: t('table.updateBy'),
+      minWidth: 120,
+      showOverflowTooltip: true
     },
     {
       prop: 'updateTimeText',
@@ -60,12 +129,43 @@
       showOverflowTooltip: true
     },
     {
-      prop: 'action',
+      prop: 'createByText',
+      label: t('table.createBy'),
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'createTimeText',
+      label: t('table.createTime'),
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'memo',
+      label: t('table.memo'),
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'operation',
       label: t('table.operation'),
-      width: 100,
-      fixed: 'right',
+      width: 120,
       align: 'center',
-      useSlot: true
+      fixed: 'right',
+      formatter: (row) =>
+        h(
+          'div',
+          { class: 'flex justify-center' },
+          h(ArtButtonMore, {
+            list: operations,
+            onClick: (item) => {
+              if (item.key === 'view') handleViewDetail?.(row)
+              if (item.key === 'edit') handleEdit?.(row)
+              if (item.key === 'print') handlePrint?.(row)
+              if (item.key === 'delete') handleDelete?.(row)
+            }
+          })
+        )
     }
   ]
 }

--
Gitblit v1.9.1