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