From a50cc7a916a14628ae16f3c5c5578cc433e23a3d Mon Sep 17 00:00:00 2001
From: zhou zhou <3272660260@qq.com>
Date: 星期一, 30 三月 2026 17:23:13 +0800
Subject: [PATCH] feat: add warehouse areas page

---
 rsf-design/src/router/adapters/backendMenuAdapter.js                                      |    4 
 rsf-design/src/views/basic-info/warehouse-areas/warehouseAreasPage.helpers.js             |  230 +++++++++
 rsf-design/src/views/basic-info/warehouse-areas/modules/warehouse-areas-dialog.vue        |  240 ++++++++++
 rsf-design/src/router/routes/staticRoutes.js                                              |   40 +
 rsf-design/src/api/warehouse-areas.js                                                     |  184 +++++++
 rsf-design/src/views/basic-info/warehouse-areas/index.vue                                 |  303 ++++++++++++
 rsf-design/tests/basic-info-warehouse-areas-page-contract.test.mjs                        |  177 +++++++
 rsf-design/src/views/basic-info/warehouse-areas/modules/warehouse-areas-detail-drawer.vue |   63 ++
 rsf-design/src/views/basic-info/warehouse-areas/warehouseAreasTable.columns.js            |  145 ++++++
 9 files changed, 1,386 insertions(+), 0 deletions(-)

diff --git a/rsf-design/src/api/warehouse-areas.js b/rsf-design/src/api/warehouse-areas.js
new file mode 100644
index 0000000..c5a0631
--- /dev/null
+++ b/rsf-design/src/api/warehouse-areas.js
@@ -0,0 +1,184 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildWarehouseAreasPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildWarehouseAreasSavePayload(formData = {}) {
+  return {
+    ...(formData.id !== undefined && formData.id !== null && formData.id !== ''
+      ? { id: Number(formData.id) }
+      : {}),
+    ...(formData.warehouseId !== undefined && formData.warehouseId !== null && formData.warehouseId !== ''
+      ? { warehouseId: Number(formData.warehouseId) }
+      : {}),
+    code: normalizeText(formData.code) || '',
+    name: normalizeText(formData.name) || '',
+    type: normalizeText(formData.type) || '',
+    ...(formData.shipperId !== undefined && formData.shipperId !== null && formData.shipperId !== ''
+      ? { shipperId: Number(formData.shipperId) }
+      : {}),
+    ...(formData.supplierId !== undefined && formData.supplierId !== null && formData.supplierId !== ''
+      ? { supplierId: Number(formData.supplierId) }
+      : {}),
+    ...(formData.flagMinus !== undefined && formData.flagMinus !== null && formData.flagMinus !== ''
+      ? { flagMinus: Number(formData.flagMinus) }
+      : {}),
+    ...(formData.flagLabelMange !== undefined && formData.flagLabelMange !== null && formData.flagLabelMange !== ''
+      ? { flagLabelMange: Number(formData.flagLabelMange) }
+      : {}),
+    ...(formData.flagMix !== undefined && formData.flagMix !== null && formData.flagMix !== ''
+      ? { flagMix: Number(formData.flagMix) }
+      : {}),
+    status:
+      formData.status !== undefined && formData.status !== null && formData.status !== ''
+        ? Number(formData.status)
+        : 1,
+    ...(formData.sort !== undefined && formData.sort !== null && formData.sort !== ''
+      ? { sort: Number(formData.sort) }
+      : {}),
+    memo: normalizeText(formData.memo) || ''
+  }
+}
+
+export function buildWarehouseAreasSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    warehouseId:
+      params.warehouseId !== undefined && params.warehouseId !== null && params.warehouseId !== ''
+        ? Number(params.warehouseId)
+        : void 0,
+    code: normalizeText(params.code),
+    name: normalizeText(params.name),
+    type: normalizeText(params.type),
+    shipperId:
+      params.shipperId !== undefined && params.shipperId !== null && params.shipperId !== ''
+        ? Number(params.shipperId)
+        : void 0,
+    supplierId:
+      params.supplierId !== undefined && params.supplierId !== null && params.supplierId !== ''
+        ? Number(params.supplierId)
+        : void 0,
+    status:
+      params.status !== undefined && params.status !== null && params.status !== ''
+        ? Number(params.status)
+        : void 0,
+    memo: normalizeText(params.memo)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function fetchWarehouseAreasPage(params = {}) {
+  return request.post({
+    url: '/warehouseAreas/page',
+    params: buildWarehouseAreasPageParams(params)
+  })
+}
+
+export function fetchWarehouseAreasList() {
+  return request.post({
+    url: '/warehouseAreas/list',
+    data: {}
+  })
+}
+
+export function fetchWarehouseAreasDetail(id) {
+  return request.get({
+    url: `/warehouseAreas/${id}`
+  })
+}
+
+export function fetchWarehouseAreasMany(ids) {
+  return request.post({
+    url: `/warehouseAreas/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchSaveWarehouseAreas(params = {}) {
+  return request.post({
+    url: '/warehouseAreas/save',
+    params: buildWarehouseAreasSavePayload(params)
+  })
+}
+
+export function fetchUpdateWarehouseAreas(params = {}) {
+  return request.post({
+    url: '/warehouseAreas/update',
+    params: buildWarehouseAreasSavePayload(params)
+  })
+}
+
+export function fetchDeleteWarehouseAreas(ids) {
+  return request.post({
+    url: `/warehouseAreas/remove/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchWarehouseAreasQuery(condition = '') {
+  return request.post({
+    url: '/warehouseAreas/query',
+    params: { condition: normalizeText(condition) }
+  })
+}
+
+export function fetchWarehouseList() {
+  return request.post({
+    url: '/warehouse/list',
+    data: {}
+  })
+}
+
+export function fetchCompanysList() {
+  return request.post({
+    url: '/companys/list',
+    data: {}
+  })
+}
+
+export async function fetchExportWarehouseAreasReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/warehouseAreas/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/router/adapters/backendMenuAdapter.js b/rsf-design/src/router/adapters/backendMenuAdapter.js
index 8aa8538..bc62f9d 100644
--- a/rsf-design/src/router/adapters/backendMenuAdapter.js
+++ b/rsf-design/src/router/adapters/backendMenuAdapter.js
@@ -19,6 +19,10 @@
   fieldsItem: '/system/fields-item',
   whMat: '/basic-info/wh-mat',
   matnr: '/basic-info/wh-mat',
+  matnrGroup: '/basic-info/matnr-group',
+  basContainer: '/basic-info/bas-container',
+  warehouse: '/basic-info/warehouse',
+  warehouseAreas: '/basic-info/warehouse-areas',
   warehouseStock: '/stock/warehouse-stock',
   warehouseAreasItem: '/stock/warehouse-areas-item',
   qlyInspect: '/manager/qly-inspect',
diff --git a/rsf-design/src/router/routes/staticRoutes.js b/rsf-design/src/router/routes/staticRoutes.js
index 7057f97..4988184 100644
--- a/rsf-design/src/router/routes/staticRoutes.js
+++ b/rsf-design/src/router/routes/staticRoutes.js
@@ -40,6 +40,46 @@
           icon: 'ri:bill-line',
           keepAlive: false
         }
+      },
+      {
+        path: 'matnr-group',
+        name: 'MatnrGroup',
+        component: () => import('@views/basic-info/matnr-group/index.vue'),
+        meta: {
+          title: 'menu.matnrGroup',
+          icon: 'ri:git-branch-line',
+          keepAlive: false
+        }
+      },
+      {
+        path: 'bas-container',
+        name: 'BasContainer',
+        component: () => import('@views/basic-info/bas-container/index.vue'),
+        meta: {
+          title: 'menu.basContainer',
+          icon: 'ri:archive-line',
+          keepAlive: false
+        }
+      },
+      {
+        path: 'warehouse',
+        name: 'Warehouse',
+        component: () => import('@views/basic-info/warehouse/index.vue'),
+        meta: {
+          title: 'menu.warehouse',
+          icon: 'ri:store-2-line',
+          keepAlive: false
+        }
+      },
+      {
+        path: 'warehouse-areas',
+        name: 'WarehouseAreas',
+        component: () => import('@views/basic-info/warehouse-areas/index.vue'),
+        meta: {
+          title: 'menu.warehouseAreas',
+          icon: 'ri:layout-grid-line',
+          keepAlive: false
+        }
       }
     ]
   },
diff --git a/rsf-design/src/views/basic-info/warehouse-areas/index.vue b/rsf-design/src/views/basic-info/warehouse-areas/index.vue
new file mode 100644
index 0000000..1043d16
--- /dev/null
+++ b/rsf-design/src/views/basic-info/warehouse-areas/index.vue
@@ -0,0 +1,303 @@
+<template>
+  <div class="warehouse-areas-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>鏂板搴撳尯</ElButton>
+            <ElButton
+              v-auth="'delete'"
+              type="danger"
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchDelete"
+              v-ripple
+            >
+              鎵归噺鍒犻櫎
+            </ElButton>
+          </ElSpace>
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <WarehouseAreasDialog
+        v-model:visible="dialogVisible"
+        :dialog-type="dialogType"
+        :warehouse-areas-data="currentWarehouseAreasData"
+        :warehouse-options="warehouseOptions"
+        :shipper-options="shipperOptions"
+        :supplier-options="supplierOptions"
+        :type-options="typeOptions"
+        @submit="handleDialogSubmit"
+      />
+
+      <WarehouseAreasDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :detail="detailData"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { computed, onMounted, ref } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import { useAuth } from '@/hooks/core/useAuth'
+  import { useTable } from '@/hooks/core/useTable'
+  import { useTableColumns } from '@/hooks/core/useTableColumns'
+  import { useCrudPage } from '@/views/system/common/useCrudPage'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { fetchDictDataPage } from '@/api/system-manage'
+  import {
+    fetchCompanysList,
+    fetchDeleteWarehouseAreas,
+    fetchWarehouseAreasDetail,
+    fetchWarehouseAreasPage,
+    fetchSaveWarehouseAreas,
+    fetchUpdateWarehouseAreas,
+    fetchWarehouseList
+  } from '@/api/warehouse-areas'
+  import WarehouseAreasDialog from './modules/warehouse-areas-dialog.vue'
+  import WarehouseAreasDetailDrawer from './modules/warehouse-areas-detail-drawer.vue'
+  import { createWarehouseAreasTableColumns } from './warehouseAreasTable.columns'
+  import {
+    buildWarehouseAreasDialogModel,
+    buildWarehouseAreasPageQueryParams,
+    buildWarehouseAreasSavePayload,
+    buildWarehouseAreasSearchParams,
+    createWarehouseAreasSearchState,
+    getWarehouseAreasPaginationKey,
+    getWarehouseAreasStatusOptions,
+    normalizeWarehouseAreasDetailRecord,
+    normalizeWarehouseAreasListRow
+  } from './warehouseAreasPage.helpers'
+
+  defineOptions({ name: 'WarehouseAreas' })
+
+  const { hasAuth } = useAuth()
+
+  const searchForm = ref(createWarehouseAreasSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  const warehouseOptions = ref([])
+  const shipperOptions = ref([])
+  const supplierOptions = ref([])
+  const typeOptions = ref([])
+  let handleDeleteAction = null
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ簱鍖哄悕绉�/缂栫爜/澶囨敞'
+      }
+    },
+    {
+      label: '浠撳簱',
+      key: 'warehouseId',
+      type: 'select',
+      props: {
+        clearable: true,
+        filterable: true,
+        options: warehouseOptions.value
+      }
+    },
+    {
+      label: '搴撳尯缂栫爜',
+      key: 'code',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ簱鍖虹紪鐮�'
+      }
+    },
+    {
+      label: '搴撳尯鍚嶇О',
+      key: 'name',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ簱鍖哄悕绉�'
+      }
+    },
+    {
+      label: '涓氬姟绫诲瀷',
+      key: 'type',
+      type: 'select',
+      props: {
+        clearable: true,
+        filterable: true,
+        options: typeOptions.value
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getWarehouseAreasStatusOptions()
+      }
+    }
+  ])
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      const detail = await guardRequestWithMessage(fetchWarehouseAreasDetail(row.id), {}, {
+        timeoutMessage: '搴撳尯璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      detailData.value = normalizeWarehouseAreasDetailRecord(detail)
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇搴撳尯璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function openEditDialog(row) {
+    try {
+      const detail = await guardRequestWithMessage(fetchWarehouseAreasDetail(row.id), {}, {
+        timeoutMessage: '搴撳尯璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      showDialog('edit', detail)
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇搴撳尯璇︽儏澶辫触')
+    }
+  }
+
+  const { columns, columnChecks, data, loading, pagination, getData, replaceSearchParams, resetSearchParams, handleSizeChange, handleCurrentChange, refreshData, refreshCreate, refreshUpdate, refreshRemove } =
+    useTable({
+      core: {
+        apiFn: fetchWarehouseAreasPage,
+        apiParams: buildWarehouseAreasPageQueryParams(searchForm.value),
+        paginationKey: getWarehouseAreasPaginationKey(),
+        columnsFactory: () =>
+          createWarehouseAreasTableColumns({
+            handleView: openDetail,
+            handleEdit: hasAuth('update') ? openEditDialog : null,
+            handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null,
+            canEdit: hasAuth('update'),
+            canDelete: hasAuth('delete')
+          })
+      },
+      transform: {
+        dataTransformer: (records) => {
+          if (!Array.isArray(records)) {
+            return []
+          }
+          return records.map((item) => normalizeWarehouseAreasListRow(item))
+        }
+      }
+    })
+
+  const {
+    dialogVisible,
+    dialogType,
+    currentRecord: currentWarehouseAreasData,
+    selectedRows,
+    handleSelectionChange,
+    showDialog,
+    handleDialogSubmit,
+    handleDelete,
+    handleBatchDelete
+  } = useCrudPage({
+    createEmptyModel: () => buildWarehouseAreasDialogModel(),
+    buildEditModel: (record) => buildWarehouseAreasDialogModel(record),
+    buildSavePayload: (formData) => buildWarehouseAreasSavePayload(formData),
+    saveRequest: fetchSaveWarehouseAreas,
+    updateRequest: fetchUpdateWarehouseAreas,
+    deleteRequest: fetchDeleteWarehouseAreas,
+    entityName: '搴撳尯',
+    resolveRecordLabel: (record) => record?.name || record?.code || record?.id,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  })
+  handleDeleteAction = handleDelete
+
+  function handleSearch(params) {
+    replaceSearchParams(buildWarehouseAreasSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createWarehouseAreasSearchState())
+    resetSearchParams()
+  }
+
+  async function loadWarehouseOptions() {
+    const records = await guardRequestWithMessage(fetchWarehouseList(), [], {
+      timeoutMessage: '浠撳簱閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+    })
+    warehouseOptions.value = defaultResponseAdapter(records).records.map((item) => ({
+      label: item.name || item.code || `浠撳簱 ${item.id}`,
+      value: item.id
+    }))
+  }
+
+  async function loadCompanyOptions() {
+    const records = await guardRequestWithMessage(fetchCompanysList(), [], {
+      timeoutMessage: '寰�鏉ヤ紒涓氶�夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+    })
+    const normalizedRecords = defaultResponseAdapter(records).records
+    shipperOptions.value = normalizedRecords
+      .filter((item) => item.type === 'shipper')
+      .map((item) => ({
+        label: item.name || item.code || `璐т富 ${item.id}`,
+        value: item.id
+      }))
+    supplierOptions.value = normalizedRecords
+      .filter((item) => item.type === 'supplier')
+      .map((item) => ({
+        label: item.name || item.code || `渚涘簲鍟� ${item.id}`,
+        value: item.id
+      }))
+  }
+
+  async function loadTypeOptions() {
+    const response = await guardRequestWithMessage(
+      fetchDictDataPage({
+        current: 1,
+        pageSize: 200,
+        dictTypeCode: 'sys_ware_areas_type',
+        status: 1
+      }),
+      { records: [] },
+      { timeoutMessage: '涓氬姟绫诲瀷鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+    )
+    typeOptions.value = defaultResponseAdapter(response).records.map((item) => ({
+      label: item.label || item.name || item.value,
+      value: item.value
+    }))
+  }
+
+  onMounted(async () => {
+    await Promise.all([loadWarehouseOptions(), loadCompanyOptions(), loadTypeOptions(), getData()])
+  })
+</script>
diff --git a/rsf-design/src/views/basic-info/warehouse-areas/modules/warehouse-areas-detail-drawer.vue b/rsf-design/src/views/basic-info/warehouse-areas/modules/warehouse-areas-detail-drawer.vue
new file mode 100644
index 0000000..ef73bef
--- /dev/null
+++ b/rsf-design/src/views/basic-info/warehouse-areas/modules/warehouse-areas-detail-drawer.vue
@@ -0,0 +1,63 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="搴撳尯璇︽儏"
+    size="960px"
+    destroy-on-close
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-180px)] pr-1">
+      <div v-if="loading" class="py-6">
+        <ElSkeleton :rows="12" animated />
+      </div>
+      <div v-else class="space-y-4">
+        <ElDescriptions title="鍩虹淇℃伅" :column="2" border>
+          <ElDescriptionsItem label="浠撳簱">{{ detail.warehouseName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="搴撳尯缂栫爜">{{ detail.code || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="搴撳尯鍚嶇О">{{ detail.name || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="涓氬姟绫诲瀷">{{ detail.typeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="璐т富">{{ detail.shipperName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="渚涘簲鍟�">{{ detail.supplierName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏀寔娣锋斁">{{ detail.flagMixText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍏佽璐熷簱瀛�">{{ detail.flagMinusText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏍囩绠$悊">{{ detail.flagLabelMangeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="detail.statusType || 'info'" effect="light">
+              {{ detail.statusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="鎺掑簭">{{ detail.sort ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <ElDescriptions title="瀹¤淇℃伅" :column="2" border>
+          <ElDescriptionsItem label="鍒涘缓浜�">{{ detail.createByText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊浜�">{{ detail.updateByText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+      </div>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const visible = computed({
+    get: () => props.visible,
+    set: (value) => emit('update:visible', value)
+  })
+
+  function handleVisibleChange(value) {
+    visible.value = value
+  }
+</script>
diff --git a/rsf-design/src/views/basic-info/warehouse-areas/modules/warehouse-areas-dialog.vue b/rsf-design/src/views/basic-info/warehouse-areas/modules/warehouse-areas-dialog.vue
new file mode 100644
index 0000000..8e39da2
--- /dev/null
+++ b/rsf-design/src/views/basic-info/warehouse-areas/modules/warehouse-areas-dialog.vue
@@ -0,0 +1,240 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="920px"
+    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="110px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import { computed, nextTick, reactive, ref, watch } from 'vue'
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import {
+    buildWarehouseAreasDialogModel,
+    createWarehouseAreasFormState,
+    getWarehouseAreasFlagOptions,
+    getWarehouseAreasStatusOptions
+  } from '../warehouseAreasPage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    dialogType: { type: String, default: 'add' },
+    warehouseAreasData: { type: Object, default: () => ({}) },
+    warehouseOptions: { type: Array, default: () => [] },
+    shipperOptions: { type: Array, default: () => [] },
+    supplierOptions: { type: Array, default: () => [] },
+    typeOptions: { type: Array, default: () => [] }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createWarehouseAreasFormState())
+
+  const isEdit = computed(() => props.dialogType === 'edit')
+  const dialogTitle = computed(() => (isEdit.value ? '缂栬緫搴撳尯' : '鏂板搴撳尯'))
+
+  const rules = computed(() => ({
+    warehouseId: [{ required: true, message: '璇烽�夋嫨浠撳簱', trigger: 'change' }],
+    code: [{ required: true, message: '璇疯緭鍏ュ簱鍖虹紪鐮�', trigger: 'blur' }],
+    name: [{ required: true, message: '璇疯緭鍏ュ簱鍖哄悕绉�', trigger: 'blur' }],
+    type: [{ required: true, message: '璇烽�夋嫨涓氬姟绫诲瀷', trigger: 'change' }],
+    flagMinus: [{ required: true, message: '璇烽�夋嫨鍏佽璐熷簱瀛�', trigger: 'change' }],
+    flagMix: [{ required: true, message: '璇烽�夋嫨鏀寔娣锋斁', trigger: 'change' }]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: '浠撳簱',
+      key: 'warehouseId',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨浠撳簱',
+        clearable: true,
+        filterable: true,
+        options: props.warehouseOptions
+      }
+    },
+    {
+      label: '搴撳尯缂栫爜',
+      key: 'code',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ュ簱鍖虹紪鐮�',
+        clearable: true
+      }
+    },
+    {
+      label: '搴撳尯鍚嶇О',
+      key: 'name',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ュ簱鍖哄悕绉�',
+        clearable: true
+      }
+    },
+    {
+      label: '涓氬姟绫诲瀷',
+      key: 'type',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨涓氬姟绫诲瀷',
+        clearable: true,
+        filterable: true,
+        options: props.typeOptions
+      }
+    },
+    {
+      label: '璐т富',
+      key: 'shipperId',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨璐т富',
+        clearable: true,
+        filterable: true,
+        options: props.shipperOptions
+      }
+    },
+    {
+      label: '渚涘簲鍟�',
+      key: 'supplierId',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨渚涘簲鍟�',
+        clearable: true,
+        filterable: true,
+        options: props.supplierOptions
+      }
+    },
+    {
+      label: '鍏佽璐熷簱瀛�',
+      key: 'flagMinus',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨',
+        options: getWarehouseAreasFlagOptions()
+      }
+    },
+    {
+      label: '鏍囩绠$悊',
+      key: 'flagLabelMange',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨',
+        options: getWarehouseAreasFlagOptions()
+      }
+    },
+    {
+      label: '鏀寔娣锋斁',
+      key: 'flagMix',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨',
+        options: getWarehouseAreasFlagOptions()
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鐘舵��',
+        options: getWarehouseAreasStatusOptions()
+      }
+    },
+    {
+      label: '鎺掑簭',
+      key: 'sort',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        style: { width: '100%' }
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ュ娉�',
+        clearable: true
+      }
+    }
+  ])
+
+  const loadFormData = () => {
+    Object.assign(form, buildWarehouseAreasDialogModel(props.warehouseAreasData))
+  }
+
+  const resetForm = () => {
+    Object.assign(form, createWarehouseAreasFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  const handleSubmit = async () => {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  const handleCancel = () => {
+    emit('update:visible', false)
+  }
+
+  const handleClosed = () => {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => {
+          formRef.value?.clearValidate?.()
+        })
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.warehouseAreasData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/basic-info/warehouse-areas/warehouseAreasPage.helpers.js b/rsf-design/src/views/basic-info/warehouse-areas/warehouseAreasPage.helpers.js
new file mode 100644
index 0000000..ffed312
--- /dev/null
+++ b/rsf-design/src/views/basic-info/warehouse-areas/warehouseAreasPage.helpers.js
@@ -0,0 +1,230 @@
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success', bool: true },
+  0: { text: '鍐荤粨', type: 'danger', bool: false }
+}
+
+export const WAREHOUSE_AREAS_REPORT_TITLE = '搴撳尯鎶ヨ〃'
+export const WAREHOUSE_AREAS_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'portrait',
+  density: 'compact',
+  showSequence: true
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeFlagText(value) {
+  if (value === 1 || value === '1' || value === true || value === '鏄�') {
+    return '鏄�'
+  }
+  if (value === 0 || value === '0' || value === false || value === '鍚�') {
+    return '鍚�'
+  }
+  return normalizeText(value) || '-'
+}
+
+export function createWarehouseAreasSearchState() {
+  return {
+    condition: '',
+    warehouseId: '',
+    code: '',
+    name: '',
+    type: '',
+    shipperId: '',
+    supplierId: '',
+    status: '',
+    memo: ''
+  }
+}
+
+export function createWarehouseAreasFormState() {
+  return {
+    id: void 0,
+    warehouseId: void 0,
+    code: '',
+    name: '',
+    type: '',
+    shipperId: void 0,
+    supplierId: void 0,
+    flagMinus: 0,
+    flagLabelMange: 0,
+    flagMix: 0,
+    status: 1,
+    sort: void 0,
+    memo: ''
+  }
+}
+
+export function getWarehouseAreasPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getWarehouseAreasStatusOptions() {
+  return [
+    { label: '姝e父', value: 1 },
+    { label: '鍐荤粨', value: 0 }
+  ]
+}
+
+export function getWarehouseAreasFlagOptions() {
+  return [
+    { label: '鍚�', value: 0 },
+    { label: '鏄�', value: 1 }
+  ]
+}
+
+export function buildWarehouseAreasSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    warehouseId:
+      params.warehouseId !== undefined && params.warehouseId !== null && params.warehouseId !== ''
+        ? Number(params.warehouseId)
+        : void 0,
+    code: normalizeText(params.code),
+    name: normalizeText(params.name),
+    type: normalizeText(params.type),
+    shipperId:
+      params.shipperId !== undefined && params.shipperId !== null && params.shipperId !== ''
+        ? Number(params.shipperId)
+        : void 0,
+    supplierId:
+      params.supplierId !== undefined && params.supplierId !== null && params.supplierId !== ''
+        ? Number(params.supplierId)
+        : void 0,
+    status:
+      params.status !== undefined && params.status !== null && params.status !== ''
+        ? Number(params.status)
+        : void 0,
+    memo: normalizeText(params.memo)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function buildWarehouseAreasPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildWarehouseAreasSearchParams(params)
+  }
+}
+
+export function buildWarehouseAreasSavePayload(formData = {}) {
+  return {
+    ...(formData.id !== void 0 && formData.id !== null && formData.id !== ''
+      ? { id: Number(formData.id) }
+      : {}),
+    ...(formData.warehouseId !== void 0 && formData.warehouseId !== null && formData.warehouseId !== ''
+      ? { warehouseId: Number(formData.warehouseId) }
+      : {}),
+    code: normalizeText(formData.code) || '',
+    name: normalizeText(formData.name) || '',
+    type: normalizeText(formData.type) || '',
+    ...(formData.shipperId !== void 0 && formData.shipperId !== null && formData.shipperId !== ''
+      ? { shipperId: Number(formData.shipperId) }
+      : {}),
+    ...(formData.supplierId !== void 0 && formData.supplierId !== null && formData.supplierId !== ''
+      ? { supplierId: Number(formData.supplierId) }
+      : {}),
+    ...(formData.flagMinus !== void 0 && formData.flagMinus !== null && formData.flagMinus !== ''
+      ? { flagMinus: Number(formData.flagMinus) }
+      : {}),
+    ...(formData.flagLabelMange !== void 0 && formData.flagLabelMange !== null && formData.flagLabelMange !== ''
+      ? { flagLabelMange: Number(formData.flagLabelMange) }
+      : {}),
+    ...(formData.flagMix !== void 0 && formData.flagMix !== null && formData.flagMix !== ''
+      ? { flagMix: Number(formData.flagMix) }
+      : {}),
+    status:
+      formData.status !== void 0 && formData.status !== null && formData.status !== ''
+        ? Number(formData.status)
+        : 1,
+    ...(formData.sort !== void 0 && formData.sort !== null && formData.sort !== ''
+      ? { sort: Number(formData.sort) }
+      : {}),
+    memo: normalizeText(formData.memo) || ''
+  }
+}
+
+export function buildWarehouseAreasDialogModel(record = {}) {
+  return {
+    ...createWarehouseAreasFormState(),
+    ...(record.id !== void 0 && record.id !== null && record.id !== '' ? { id: Number(record.id) } : {}),
+    warehouseId:
+      record.warehouseId !== void 0 && record.warehouseId !== null && record.warehouseId !== ''
+        ? Number(record.warehouseId)
+        : void 0,
+    code: normalizeText(record.code || ''),
+    name: normalizeText(record.name || ''),
+    type: normalizeText(record.type || ''),
+    shipperId:
+      record.shipperId !== void 0 && record.shipperId !== null && record.shipperId !== ''
+        ? Number(record.shipperId)
+        : void 0,
+    supplierId:
+      record.supplierId !== void 0 && record.supplierId !== null && record.supplierId !== ''
+        ? Number(record.supplierId)
+        : void 0,
+    flagMinus: record.flagMinus !== void 0 && record.flagMinus !== null ? Number(record.flagMinus) : 0,
+    flagLabelMange:
+      record.flagLabelMange !== void 0 && record.flagLabelMange !== null ? Number(record.flagLabelMange) : 0,
+    flagMix: record.flagMix !== void 0 && record.flagMix !== null ? Number(record.flagMix) : 0,
+    status: record.status !== void 0 && record.status !== null ? Number(record.status) : 1,
+    sort: record.sort !== void 0 && record.sort !== null && record.sort !== '' ? Number(record.sort) : void 0,
+    memo: normalizeText(record.memo || '')
+  }
+}
+
+export function getWarehouseAreasStatusMeta(status) {
+  if (status === true || Number(status) === 1) {
+    return STATUS_META[1]
+  }
+  if (status === false || Number(status) === 0) {
+    return STATUS_META[0]
+  }
+  return { text: '鏈煡', type: 'info', bool: false }
+}
+
+export function normalizeWarehouseAreasDetailRecord(record = {}) {
+  const statusMeta = getWarehouseAreasStatusMeta(record.statusBool ?? record.status)
+  return {
+    ...record,
+    warehouseName: normalizeText(record.warehouseId$ || record.warehouseName || ''),
+    typeText: normalizeText(record.type$ || record.type || ''),
+    shipperName: normalizeText(record.shipperId$ || record.shipperName || ''),
+    supplierName: normalizeText(record.supplierId$ || record.supplierName || ''),
+    code: normalizeText(record.code || ''),
+    name: normalizeText(record.name || ''),
+    memo: normalizeText(record.memo || ''),
+    flagMinusText: normalizeFlagText(record.flagMinus$ ?? record.flagMinus),
+    flagLabelMangeText: normalizeFlagText(record.flagLabelMange$ ?? record.flagLabelMange),
+    flagMixText: normalizeFlagText(record.flagMix$ ?? record.flagMix),
+    statusText: statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
+    createByText: normalizeText(record.createBy$ || record.createByText || ''),
+    createTimeText: normalizeText(record.createTime$ || record.createTime || ''),
+    updateByText: normalizeText(record.updateBy$ || record.updateByText || ''),
+    updateTimeText: normalizeText(record.updateTime$ || record.updateTime || '')
+  }
+}
+
+export function normalizeWarehouseAreasListRow(record = {}) {
+  return normalizeWarehouseAreasDetailRecord(record)
+}
+
+export function buildWarehouseAreasPrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeWarehouseAreasListRow(record))
+}
+
diff --git a/rsf-design/src/views/basic-info/warehouse-areas/warehouseAreasTable.columns.js b/rsf-design/src/views/basic-info/warehouse-areas/warehouseAreasTable.columns.js
new file mode 100644
index 0000000..1952002
--- /dev/null
+++ b/rsf-design/src/views/basic-info/warehouse-areas/warehouseAreasTable.columns.js
@@ -0,0 +1,145 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+import { getWarehouseAreasStatusMeta } from './warehouseAreasPage.helpers'
+
+export function createWarehouseAreasTableColumns({
+  handleView,
+  handleEdit,
+  handleDelete,
+  canEdit = true,
+  canDelete = true
+} = {}) {
+  const operations = [{ key: 'view', label: '璇︽儏', icon: 'ri:eye-line' }]
+
+  if (canEdit && handleEdit) {
+    operations.push({ key: 'edit', label: '缂栬緫', icon: 'ri:pencil-line' })
+  }
+
+  if (canDelete && handleDelete) {
+    operations.push({ key: 'delete', label: '鍒犻櫎', icon: 'ri:delete-bin-5-line', color: 'var(--art-error)' })
+  }
+
+  return [
+    {
+      type: 'selection',
+      width: 48,
+      align: 'center'
+    },
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'warehouseName',
+      label: '浠撳簱',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.warehouseName || '--'
+    },
+    {
+      prop: 'code',
+      label: '搴撳尯缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.code || '--'
+    },
+    {
+      prop: 'name',
+      label: '搴撳尯鍚嶇О',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.name || '--'
+    },
+    {
+      prop: 'typeText',
+      label: '涓氬姟绫诲瀷',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.typeText || '--'
+    },
+    {
+      prop: 'shipperName',
+      label: '璐т富',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.shipperName || '--'
+    },
+    {
+      prop: 'supplierName',
+      label: '渚涘簲鍟�',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.supplierName || '--'
+    },
+    {
+      prop: 'flagMixText',
+      label: '鏀寔娣锋斁',
+      width: 100,
+      align: 'center',
+      formatter: (row) => row.flagMixText || '--'
+    },
+    {
+      prop: 'flagMinusText',
+      label: '鍏佽璐熷簱瀛�',
+      width: 100,
+      align: 'center',
+      formatter: (row) => row.flagMinusText || '--'
+    },
+    {
+      prop: 'flagLabelMangeText',
+      label: '鏍囩绠$悊',
+      width: 100,
+      align: 'center',
+      formatter: (row) => row.flagLabelMangeText || '--'
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 100,
+      align: 'center',
+      formatter: (row) => {
+        const statusMeta = getWarehouseAreasStatusMeta(row.statusBool ?? row.status)
+        return h(ElTag, { type: statusMeta.type, effect: 'light' }, () => statusMeta.text)
+      }
+    },
+    {
+      prop: 'sort',
+      label: '鎺掑簭',
+      width: 80,
+      align: 'center',
+      formatter: (row) => row.sort ?? '--'
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.memo || '--'
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 160,
+      align: 'right',
+      formatter: (row) =>
+        h(ArtButtonMore, {
+          list: operations,
+          onClick: (item) => {
+            if (item.key === 'view') handleView?.(row)
+            if (item.key === 'edit') handleEdit?.(row)
+            if (item.key === 'delete') handleDelete?.(row)
+          }
+        })
+    }
+  ]
+}
diff --git a/rsf-design/tests/basic-info-warehouse-areas-page-contract.test.mjs b/rsf-design/tests/basic-info-warehouse-areas-page-contract.test.mjs
new file mode 100644
index 0000000..280c44e
--- /dev/null
+++ b/rsf-design/tests/basic-info-warehouse-areas-page-contract.test.mjs
@@ -0,0 +1,177 @@
+import assert from 'node:assert/strict'
+import { readFile } from 'node:fs/promises'
+import { fileURLToPath } from 'node:url'
+import test from 'node:test'
+
+const apiPath = new URL('../src/api/warehouse-areas.js', import.meta.url)
+const helpersPath = new URL('../src/views/basic-info/warehouse-areas/warehouseAreasPage.helpers.js', import.meta.url)
+const columnsPath = new URL('../src/views/basic-info/warehouse-areas/warehouseAreasTable.columns.js', import.meta.url)
+const pagePath = new URL('../src/views/basic-info/warehouse-areas/index.vue', import.meta.url)
+const backendMenuAdapterPath = new URL('../src/router/adapters/backendMenuAdapter.js', import.meta.url)
+const staticRoutesPath = new URL('../src/router/routes/staticRoutes.js', import.meta.url)
+
+test('warehouse areas api uses real backend endpoints', async () => {
+  const apiSource = await readFile(fileURLToPath(apiPath), 'utf8')
+
+  assert.match(apiSource, /fetchWarehouseAreasPage/)
+  assert.match(apiSource, /fetchWarehouseAreasDetail/)
+  assert.match(apiSource, /fetchWarehouseAreasMany/)
+  assert.match(apiSource, /fetchSaveWarehouseAreas/)
+  assert.match(apiSource, /fetchUpdateWarehouseAreas/)
+  assert.match(apiSource, /fetchDeleteWarehouseAreas/)
+  assert.match(apiSource, /fetchWarehouseAreasQuery/)
+  assert.match(apiSource, /fetchExportWarehouseAreasReport/)
+  assert.match(apiSource, /url: '\/warehouseAreas\/page'/)
+  assert.match(apiSource, /url: '\/warehouseAreas\/list'/)
+  assert.match(apiSource, /url: `\/warehouseAreas\/many\/\$\{normalizeIds\(ids\)\}`/)
+  assert.match(apiSource, /url: '\/warehouseAreas\/save'/)
+  assert.match(apiSource, /url: '\/warehouseAreas\/update'/)
+  assert.match(apiSource, /url: `\/warehouseAreas\/remove\/\$\{normalizeIds\(ids\)\}`/)
+  assert.match(apiSource, /url: '\/warehouseAreas\/query'/)
+  assert.match(apiSource, /warehouseAreas\/export/)
+})
+
+test('warehouse areas helpers keep page, save and detail contracts stable', async () => {
+  const helpers = await import('../src/views/basic-info/warehouse-areas/warehouseAreasPage.helpers.js')
+
+  assert.deepEqual(helpers.createWarehouseAreasSearchState(), {
+    condition: '',
+    warehouseId: '',
+    code: '',
+    name: '',
+    type: '',
+    shipperId: '',
+    supplierId: '',
+    status: '',
+    memo: ''
+  })
+
+  assert.deepEqual(helpers.getWarehouseAreasPaginationKey(), {
+    current: 'current',
+    size: 'pageSize'
+  })
+
+  assert.deepEqual(
+    helpers.buildWarehouseAreasPageQueryParams({
+      current: 2,
+      pageSize: 30,
+      condition: '  搴撳尯A  ',
+      warehouseId: 8,
+      code: '  A01 ',
+      name: '  涓�妤煎簱鍖� ',
+      type: '  normal ',
+      shipperId: 5,
+      supplierId: 7,
+      status: 1,
+      memo: '  memo  '
+    }),
+    {
+      current: 2,
+      pageSize: 30,
+      condition: '搴撳尯A',
+      warehouseId: 8,
+      code: 'A01',
+      name: '涓�妤煎簱鍖�',
+      type: 'normal',
+      shipperId: 5,
+      supplierId: 7,
+      status: 1,
+      memo: 'memo'
+    }
+  )
+
+  assert.deepEqual(
+    helpers.buildWarehouseAreasSavePayload({
+      id: '9',
+      warehouseId: '3',
+      code: ' A01 ',
+      name: ' 涓�妤煎簱鍖� ',
+      type: ' normal ',
+      shipperId: '11',
+      supplierId: '12',
+      flagMinus: 1,
+      flagLabelMange: 0,
+      flagMix: 1,
+      status: '',
+      sort: '2',
+      memo: ' memo '
+    }),
+    {
+      id: 9,
+      warehouseId: 3,
+      code: 'A01',
+      name: '涓�妤煎簱鍖�',
+      type: 'normal',
+      shipperId: 11,
+      supplierId: 12,
+      flagMinus: 1,
+      flagLabelMange: 0,
+      flagMix: 1,
+      status: 1,
+      sort: 2,
+      memo: 'memo'
+    }
+  )
+
+  const detail = helpers.normalizeWarehouseAreasDetailRecord({
+    id: 1,
+    warehouseId: 4,
+    warehouseId$: '涓讳粨',
+    type: 'A',
+    type$: '甯告俯',
+    name: ' 涓�妤煎簱鍖� ',
+    code: ' A01 ',
+    shipperId$: '璐т富A',
+    supplierId$: '渚涘簲鍟咮',
+    flagMinus: 1,
+    flagLabelMange: 0,
+    flagMix: 1,
+    status: 1,
+    sort: 3,
+    memo: ' memo ',
+    createBy$: 'root',
+    createTime$: '2026-03-30 10:00:00',
+    updateBy$: 'root',
+    updateTime$: '2026-03-30 10:10:00'
+  })
+
+  assert.equal(detail.statusText, '姝e父')
+  assert.equal(detail.flagMixText, '鏄�')
+  assert.equal(detail.warehouseName, '涓讳粨')
+  assert.equal(detail.typeText, '甯告俯')
+  assert.equal(detail.shipperName, '璐т富A')
+  assert.equal(detail.supplierName, '渚涘簲鍟咮')
+  assert.equal(detail.memo, 'memo')
+})
+
+test('warehouse areas columns expose detail action slot and status tag', async () => {
+  const columnsSource = await readFile(fileURLToPath(columnsPath), 'utf8')
+
+  assert.match(columnsSource, /createWarehouseAreasTableColumns/)
+  assert.match(columnsSource, /ArtButtonMore|ArtButtonTable/)
+  assert.match(columnsSource, /label: '鎿嶄綔'/)
+  assert.match(columnsSource, /useSlot: true|formatter:/)
+  assert.match(columnsSource, /label: '鐘舵��'/)
+})
+
+test('warehouse areas page uses real query, tree-like references and detail drawer structure', async () => {
+  const pageSource = await readFile(fileURLToPath(pagePath), 'utf8')
+
+  assert.match(pageSource, /fetchWarehouseAreasPage/)
+  assert.match(pageSource, /fetchWarehouseAreasDetail/)
+  assert.match(pageSource, /fetchSaveWarehouseAreas/)
+  assert.match(pageSource, /fetchUpdateWarehouseAreas/)
+  assert.match(pageSource, /fetchDeleteWarehouseAreas/)
+  assert.match(pageSource, /WarehouseAreasDetailDrawer/)
+  assert.match(pageSource, /ArtSearchBar/)
+  assert.match(pageSource, /ArtTable/)
+})
+
+test('backend menu adapter releases warehouse areas route and static route is registered', async () => {
+  const backendMenuAdapterSource = await readFile(fileURLToPath(backendMenuAdapterPath), 'utf8')
+  const staticRoutesSource = await readFile(fileURLToPath(staticRoutesPath), 'utf8')
+
+  assert.match(backendMenuAdapterSource, /warehouseAreas:\s*'\/basic-info\/warehouse-areas'/)
+  assert.match(staticRoutesSource, /path: 'warehouse-areas'/)
+  assert.match(staticRoutesSource, /title:\s*'menu\.warehouseAreas'/)
+})

--
Gitblit v1.9.1