zhou zhou
14 小时以前 1553782fd262f97a336fecc8b38f8f309fc08ae6
#前端
2个文件已添加
7个文件已修改
780 ■■■■ 已修改文件
rsf-design/src/views/basic-info/matnr-group/index.vue 430 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/basic-info/matnr-group/matnrGroupTable.columns.js 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/basic-info/warehouse-areas/index.vue 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/basic-info/wh-mat/index.vue 94 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/common/domain/BaseParam.java 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/BasStationController.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/CompanysController.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/vo/BasStationOptionVo.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/vo/CompanysOptionVo.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/basic-info/matnr-group/index.vue
@@ -1,141 +1,99 @@
<template>
  <div class="matnr-group-page art-full-height flex flex-col gap-4 xl:flex-row">
    <ElCard class="w-full shrink-0 xl:w-[320px]">
      <div class="mb-3 flex items-center justify-between gap-3">
        <div>
          <div class="text-base font-medium text-[var(--art-text-primary)]">物料分组</div>
          <div class="text-xs text-[var(--art-text-secondary)]">
            {{ selectedGroupLabel }}
          </div>
        </div>
        <ElButton text @click="handleResetGroup">全部</ElButton>
      </div>
  <div class="matnr-group-page art-full-height">
    <ArtSearchBar
      v-model="searchForm"
      :items="searchItems"
      :show-expand="false"
      @search="handleSearch"
      @reset="handleReset"
    />
      <div class="mb-3 flex items-center gap-2">
        <ElInput
          v-model.trim="groupSearch"
          clearable
          placeholder="搜索物料分组"
          @clear="handleGroupSearch"
          @keyup.enter="handleGroupSearch"
        />
        <ElButton @click="handleGroupSearch">搜索</ElButton>
      </div>
    <ElCard class="art-table-card">
      <ArtTableHeader
        :loading="loading"
        :show-zebra="false"
        v-model:columns="columnChecks"
        @refresh="handleRefresh"
      >
        <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>
            <ElButton @click="toggleExpand" v-ripple>
              {{ isExpanded ? '收起' : '展开' }}
            </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="flattenedTableRows.length"
              :disabled="loading"
              @export="handleExport"
              @print="handlePrint"
            />
          </ElSpace>
        </template>
      </ArtTableHeader>
      <ElScrollbar class="h-[calc(100vh-260px)] pr-1">
        <div v-if="groupTreeLoading" class="py-6">
          <ElSkeleton :rows="10" animated />
        </div>
        <ElEmpty v-else-if="!groupTreeData.length" description="暂无物料分组" />
        <ElTree
          v-else
          :data="groupTreeData"
          :props="treeProps"
          node-key="id"
          highlight-current
          default-expand-all
          :current-node-key="selectedGroupId"
          @node-click="handleGroupNodeClick"
        >
          <template #default="{ data }">
            <div class="flex items-center gap-2">
              <span class="font-medium">{{ data.name || '--' }}</span>
              <span class="text-xs text-[var(--art-text-secondary)]">{{ data.code || '--' }}</span>
            </div>
          </template>
        </ElTree>
      </ElScrollbar>
    </ElCard>
    <div class="min-w-0 flex-1 space-y-4">
      <ArtSearchBar
        v-model="searchForm"
        :items="searchItems"
        :showExpand="true"
        @search="handleSearch"
        @reset="handleReset"
      <ArtTable
        ref="tableRef"
        rowKey="id"
        :loading="loading"
        :columns="columns"
        :data="tableData"
        :stripe="false"
        :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
        :default-expand-all="true"
        @selection-change="handleSelectionChange"
      />
      <ElCard class="art-table-card">
        <ArtTableHeader :loading="loading" v-model:columns="columnChecks" @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>
              <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>
      <MatnrGroupDialog
        v-model:visible="dialogVisible"
        :dialog-type="dialogType"
        :group-data="currentGroupData"
        :parent-group-options="parentGroupOptions"
        :resolve-parent-code="resolveGroupCode"
        @submit="handleDialogSubmit"
      />
        <ArtTable
          :loading="loading"
          :data="data"
          :columns="columns"
          :pagination="pagination"
          @selection-change="handleSelectionChange"
          @pagination:size-change="handleSizeChange"
          @pagination:current-change="handleCurrentChange"
        />
        <MatnrGroupDialog
          v-model:visible="dialogVisible"
          :dialog-type="dialogType"
          :group-data="currentGroupData"
          :parent-group-options="parentGroupOptions"
          :resolve-parent-code="resolveGroupCode"
          @submit="handleDialogSubmit"
        />
        <MatnrGroupDetailDrawer
          v-model:visible="detailDrawerVisible"
          :loading="detailLoading"
          :detail="detailData"
        />
      </ElCard>
    </div>
      <MatnrGroupDetailDrawer
        v-model:visible="detailDrawerVisible"
        :loading="detailLoading"
        :detail="detailData"
      />
    </ElCard>
  </div>
</template>
<script setup>
  import { computed, nextTick, onMounted, reactive, ref } from 'vue'
  import { ElMessage } from 'element-plus'
  import { computed, onMounted, ref } from 'vue'
  import { useUserStore } from '@/store/modules/user'
  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 { usePrintExportPage } from '@/views/system/common/usePrintExportPage'
  import ListExportPrint from '@/components/biz/list-export-print/index.vue'
  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
  import {
    fetchDeleteMatnrGroup,
    fetchExportMatnrGroupReport,
    fetchGetMatnrGroupDetail,
    fetchGetMatnrGroupMany,
    fetchMatnrGroupPage,
    fetchMatnrGroupTree,
    fetchSaveMatnrGroup,
    fetchUpdateMatnrGroup
@@ -147,7 +105,6 @@
    MATNR_GROUP_REPORT_STYLE,
    MATNR_GROUP_REPORT_TITLE,
    buildMatnrGroupDialogModel,
    buildMatnrGroupPageQueryParams,
    buildMatnrGroupPrintRows,
    buildMatnrGroupReportMeta,
    buildMatnrGroupSavePayload,
@@ -155,11 +112,8 @@
    buildMatnrGroupTreeQueryParams,
    createMatnrGroupSearchState,
    createMatnrGroupTreeSelectOptions,
    getMatnrGroupPaginationKey,
    normalizeMatnrGroupDetailRecord,
    normalizeMatnrGroupListRow,
    normalizeMatnrGroupTreeRows,
    resolveMatnrGroupTreeNodeLabel
    normalizeMatnrGroupTreeRows
  } from './matnrGroupPage.helpers'
  defineOptions({ name: 'MatnrGroup' })
@@ -167,31 +121,23 @@
  const { hasAuth } = useAuth()
  const userStore = useUserStore()
  const searchForm = ref(createMatnrGroupSearchState())
  const groupSearch = ref('')
  const groupTreeLoading = ref(false)
  const groupTreeData = ref([])
  const selectedGroupId = ref(null)
  const loading = ref(false)
  const isExpanded = ref(true)
  const tableRef = ref()
  const detailDrawerVisible = ref(false)
  const detailLoading = ref(false)
  const detailData = ref({})
  const tableData = ref([])
  let handleDeleteAction = null
  const reportTitle = MATNR_GROUP_REPORT_TITLE
  const reportQueryParams = computed(() =>
    buildMatnrGroupPageQueryParams({
      ...searchForm.value,
      parentId: selectedGroupId.value ?? ''
    })
  )
  const initialSearchState = createMatnrGroupSearchState()
  const searchForm = reactive({ ...initialSearchState })
  const groupLookupMap = computed(() => buildMatnrGroupTreeLookupMap(groupTreeData.value))
  const parentGroupOptions = computed(() => createMatnrGroupTreeSelectOptions(groupTreeData.value))
  const treeProps = {
    label: 'displayLabel',
    children: 'children'
  }
  const reportQueryParams = computed(() => buildMatnrGroupTreeQueryParams(searchForm))
  const groupLookupMap = computed(() => buildMatnrGroupTreeLookupMap(tableData.value))
  const parentGroupOptions = computed(() => createMatnrGroupTreeSelectOptions(tableData.value))
  const flattenedTableRows = computed(() => flattenMatnrGroupRows(tableData.value))
  const searchItems = computed(() => [
    {
@@ -202,56 +148,16 @@
        clearable: true,
        placeholder: '请输入分组编码/名称/备注'
      }
    },
    {
      label: '分组编码',
      key: 'code',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入分组编码'
      }
    },
    {
      label: '分组名称',
      key: 'name',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入分组名称'
      }
    },
    {
      label: '上级编码',
      key: 'parCode',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入上级编码'
      }
    },
    {
      label: '状态',
      key: 'status',
      type: 'select',
      props: {
        clearable: true,
        options: [
          { label: '正常', value: 1 },
          { label: '冻结', value: 0 }
        ]
      }
    },
    {
      label: '备注',
      key: 'memo',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入备注'
      }
    }
  ])
  function flattenMatnrGroupRows(records = []) {
    if (!Array.isArray(records)) {
      return []
    }
    return records.flatMap((item) => [item, ...flattenMatnrGroupRows(item?.children || [])])
  }
  function resolveGroupCode(id) {
    return groupLookupMap.value.get(Number(id))?.code || ''
@@ -262,51 +168,53 @@
    if (!node) {
      return ''
    }
    return node.displayLabel || resolveMatnrGroupTreeNodeLabel(node)
    return node.displayLabel || node.label || [node.name, node.code].filter(Boolean).join(' · ')
  }
  const selectedGroupLabel = computed(() => {
    if (selectedGroupId.value === null || selectedGroupId.value === undefined) {
      return '全部分组'
    }
    return resolveGroupLabel(selectedGroupId.value) || '全部分组'
  })
  const { columnChecks, columns } = useTableColumns(() =>
    createMatnrGroupTableColumns({
      handleView: openDetail,
      handleEdit: hasAuth('update') ? openEditDialog : null,
      handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null,
      canEdit: hasAuth('update'),
      canDelete: hasAuth('delete')
    })
  )
  async function applyTableFilters() {
    replaceSearchParams(
      buildMatnrGroupPageQueryParams({
        ...searchForm.value,
        parentId: selectedGroupId.value ?? ''
  async function syncTreeExpandState() {
    await nextTick()
    if (!tableRef.value?.elTableRef) {
      return
    }
    const toggleRows = (rows = []) => {
      rows.forEach((row) => {
        if (Array.isArray(row.children) && row.children.length) {
          tableRef.value.elTableRef.toggleRowExpansion(row, isExpanded.value)
          toggleRows(row.children)
        }
      })
    )
    await getData()
    }
    toggleRows(tableData.value)
  }
  async function loadGroupTree() {
    groupTreeLoading.value = true
    let selectionCleared = false
    loading.value = true
    try {
      const records = await guardRequestWithMessage(
        fetchMatnrGroupTree(buildMatnrGroupTreeQueryParams({ condition: groupSearch.value })),
        fetchMatnrGroupTree(buildMatnrGroupTreeQueryParams(searchForm)),
        [],
        { timeoutMessage: '物料分组加载超时,已停止等待' }
      )
      const normalizedTree = normalizeMatnrGroupTreeRows(Array.isArray(records) ? records : [])
      groupTreeData.value = normalizedTree
      if (selectedGroupId.value && !groupLookupMap.value.get(Number(selectedGroupId.value))) {
        selectedGroupId.value = null
        selectionCleared = true
      }
      if (selectionCleared) {
        await applyTableFilters()
      }
      tableData.value = normalizeMatnrGroupTreeRows(Array.isArray(records) ? records : [])
      await syncTreeExpandState()
    } catch (error) {
      groupTreeData.value = []
      tableData.value = []
      ElMessage.error(error?.message || '物料分组加载失败')
    } finally {
      groupTreeLoading.value = false
      loading.value = false
    }
    return selectionCleared
  }
  async function loadGroupDetail(id) {
@@ -314,32 +222,6 @@
      timeoutMessage: '物料分组详情加载超时,已停止等待'
    })
  }
  const { columns, columnChecks, data, loading, pagination, getData, replaceSearchParams, resetSearchParams, handleSizeChange, handleCurrentChange, refreshData, refreshCreate, refreshUpdate, refreshRemove } =
    useTable({
      core: {
        apiFn: fetchMatnrGroupPage,
        apiParams: buildMatnrGroupPageQueryParams(reportQueryParams.value),
        paginationKey: getMatnrGroupPaginationKey(),
        columnsFactory: () =>
          createMatnrGroupTableColumns({
            handleView: openDetail,
            handleEdit: hasAuth('update') ? openEditDialog : null,
            handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null,
            resolveParentLabel: resolveGroupLabel,
            canEdit: hasAuth('update'),
            canDelete: hasAuth('delete')
          })
      },
      transform: {
        dataTransformer: (records) => {
          if (!Array.isArray(records)) {
            return []
          }
          return records.map((item) => normalizeMatnrGroupListRow(item, resolveGroupLabel))
        }
      }
    })
  const {
    dialogVisible,
@@ -354,8 +236,8 @@
  } = useCrudPage({
    createEmptyModel: () =>
      buildMatnrGroupDialogModel({}, {
        parentId: selectedGroupId.value ?? 0,
        parCode: resolveGroupCode(selectedGroupId.value ?? 0),
        parentId: 0,
        parCode: '',
        resolveParentCode: resolveGroupCode
      }),
    buildEditModel: (record) => buildMatnrGroupDialogModel(record, { resolveParentCode: resolveGroupCode }),
@@ -375,18 +257,11 @@
    deleteRequest: fetchDeleteMatnrGroup,
    entityName: '物料分组',
    resolveRecordLabel: (record) => record?.name || record?.code || record?.id,
    refreshCreate: refreshTreeAndTable,
    refreshUpdate: refreshTreeAndTable,
    refreshRemove: refreshTreeAndTable
    refreshCreate: loadGroupTree,
    refreshUpdate: loadGroupTree,
    refreshRemove: loadGroupTree
  })
  handleDeleteAction = handleDelete
  async function refreshTreeAndTable() {
    const selectionCleared = await loadGroupTree()
    if (!selectionCleared) {
      await refreshData()
    }
  }
  const buildPreviewMeta = (rows) => {
    const now = new Date()
@@ -401,15 +276,9 @@
  const resolvePrintRecords = async (payload) => {
    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
      return defaultResponseAdapter(await fetchGetMatnrGroupMany(payload.ids)).records
      return await fetchGetMatnrGroupMany(payload.ids)
    }
    return defaultResponseAdapter(
      await fetchMatnrGroupPage({
        ...reportQueryParams.value,
        current: 1,
        pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : Number(payload?.pageSize) || 20
      })
    ).records
    return flattenedTableRows.value
  }
  const {
@@ -440,34 +309,17 @@
    })
  )
  function handleSearch(params) {
    replaceSearchParams(
      buildMatnrGroupPageQueryParams({
        ...params,
        parentId: selectedGroupId.value ?? ''
      })
    )
    getData()
  function handleSearch() {
    loadGroupTree()
  }
  function handleReset() {
    Object.assign(searchForm.value, createMatnrGroupSearchState())
    selectedGroupId.value = null
    resetSearchParams()
    Object.assign(searchForm, { ...initialSearchState })
    loadGroupTree()
  }
  async function handleGroupSearch() {
    await loadGroupTree()
  }
  async function handleGroupNodeClick(node) {
    selectedGroupId.value = Number(node?.id || 0) || null
    await applyTableFilters()
  }
  async function handleResetGroup() {
    selectedGroupId.value = null
    await applyTableFilters()
  function handleRefresh() {
    loadGroupTree()
  }
  async function openDetail(row) {
@@ -494,8 +346,12 @@
    }
  }
  onMounted(async () => {
    await loadGroupTree()
    await getData()
  function toggleExpand() {
    isExpanded.value = !isExpanded.value
    syncTreeExpandState()
  }
  onMounted(() => {
    loadGroupTree()
  })
</script>
rsf-design/src/views/basic-info/matnr-group/matnrGroupTable.columns.js
@@ -1,13 +1,10 @@
import { h } from 'vue'
import { ElTag } from 'element-plus'
import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
import { getMatnrGroupStatusMeta } from './matnrGroupPage.helpers'
export function createMatnrGroupTableColumns({
  handleView,
  handleEdit,
  handleDelete,
  resolveParentLabel,
  canEdit = true,
  canDelete = true
}) {
@@ -28,88 +25,22 @@
      align: 'center'
    },
    {
      type: 'globalIndex',
      label: '序号',
      width: 72,
      align: 'center'
    },
    {
      prop: 'name',
      label: '分组名称',
      minWidth: 180,
      showOverflowTooltip: true
    },
    {
      prop: 'code',
      label: '分组编码',
      minWidth: 150,
      showOverflowTooltip: true
    },
    {
      prop: 'parentLabel',
      label: '上级分组',
      minWidth: 160,
      showOverflowTooltip: true,
      formatter: (row) => {
        if (typeof resolveParentLabel === 'function') {
          const resolved = resolveParentLabel(row.parentId)
          if (resolved) {
            return resolved
          }
        }
        return row.parentLabel || (Number(row.parentId) === 0 ? '顶级分组' : '--')
      }
      showOverflowTooltip: true
    },
    {
      prop: 'parCode',
      label: '上级编码',
      minWidth: 160,
      showOverflowTooltip: true
      showOverflowTooltip: true,
      formatter: (row) => row.parCode || '--'
    },
    {
      prop: 'sort',
      label: '排序',
      width: 90,
      align: 'center'
    },
    {
      prop: 'status',
      label: '状态',
      width: 100,
      align: 'center',
      formatter: (row) => {
        const statusMeta = getMatnrGroupStatusMeta(row.statusBool ?? row.status)
        return h(ElTag, { type: statusMeta.type, effect: 'light' }, () => statusMeta.text)
      }
    },
    {
      prop: 'memo',
      label: '备注',
      minWidth: 180,
      showOverflowTooltip: true
    },
    {
      prop: 'createByText',
      label: '创建人',
      minWidth: 120,
      showOverflowTooltip: true
    },
    {
      prop: 'createTimeText',
      label: '创建时间',
      minWidth: 170,
      showOverflowTooltip: true
    },
    {
      prop: 'updateByText',
      label: '更新人',
      minWidth: 120,
      showOverflowTooltip: true
    },
    {
      prop: 'updateTimeText',
      label: '更新时间',
      minWidth: 170,
      prop: 'name',
      label: '分组名称',
      minWidth: 240,
      showOverflowTooltip: true
    },
    {
rsf-design/src/views/basic-info/warehouse-areas/index.vue
@@ -12,7 +12,7 @@
      <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="'add'" @click="openWarehouseAreasDialog('add')" v-ripple>新增库区</ElButton>
            <ElButton
              v-auth="'delete'"
              type="danger"
@@ -102,6 +102,8 @@
  const shipperOptions = ref([])
  const supplierOptions = ref([])
  const typeOptions = ref([])
  const companyOptionsLoaded = ref(false)
  const companyOptionsLoading = ref(null)
  let handleDeleteAction = null
  const searchItems = computed(() => [
@@ -182,6 +184,7 @@
  async function openEditDialog(row) {
    try {
      await ensureCompanyOptions()
      const detail = await guardRequestWithMessage(fetchWarehouseAreasDetail(row.id), {}, {
        timeoutMessage: '库区详情加载超时,已停止等待'
      })
@@ -189,6 +192,11 @@
    } catch (error) {
      ElMessage.error(error?.message || '获取库区详情失败')
    }
  }
  async function openWarehouseAreasDialog(type, record) {
    await ensureCompanyOptions()
    showDialog(type, record)
  }
  const { columns, columnChecks, data, loading, pagination, getData, replaceSearchParams, resetSearchParams, handleSizeChange, handleCurrentChange, refreshData, refreshCreate, refreshUpdate, refreshRemove } =
@@ -251,6 +259,43 @@
    resetSearchParams()
  }
  async function ensureCompanyOptions() {
    if (companyOptionsLoaded.value) {
      return
    }
    if (companyOptionsLoading.value) {
      await companyOptionsLoading.value
      return
    }
    companyOptionsLoading.value = (async () => {
      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
        }))
      companyOptionsLoaded.value = true
    })()
    try {
      await companyOptionsLoading.value
    } finally {
      companyOptionsLoading.value = null
    }
  }
  async function loadWarehouseOptions() {
    const records = await guardRequestWithMessage(fetchWarehouseList(), [], {
      timeoutMessage: '仓库选项加载超时,已停止等待'
@@ -259,25 +304,6 @@
      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() {
@@ -298,6 +324,6 @@
  }
  onMounted(async () => {
    await Promise.all([loadWarehouseOptions(), loadCompanyOptions(), loadTypeOptions(), getData()])
    await Promise.all([loadWarehouseOptions(), loadTypeOptions(), getData()])
  })
</script>
rsf-design/src/views/basic-info/wh-mat/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="wh-mat-page art-full-height flex flex-col gap-4 xl:flex-row">
    <ElCard class="w-full shrink-0 xl:w-[320px]">
  <div class="wh-mat-page art-full-height">
    <div class="wh-mat-page__sidebar">
      <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)]">物料分组</div>
@@ -22,7 +23,7 @@
        <ElButton @click="handleGroupSearch">搜索</ElButton>
      </div>
      <ElScrollbar class="h-[calc(100vh-260px)] pr-1">
      <ElScrollbar class="wh-mat-page__tree-scroll pr-1">
        <div v-if="groupTreeLoading" class="py-6">
          <ElSkeleton :rows="10" animated />
        </div>
@@ -45,9 +46,10 @@
          </template>
        </ElTree>
      </ElScrollbar>
    </ElCard>
      </ElCard>
    </div>
    <div class="min-w-0 flex-1 space-y-4">
    <div class="wh-mat-page__content">
      <ArtSearchBar
        v-model="searchForm"
        :items="searchItems"
@@ -59,23 +61,23 @@
      <ElCard class="art-table-card">
        <ArtTableHeader :loading="loading" v-model:columns="columnChecks" @refresh="loadMatnrList" />
      <ArtTable
        :loading="loading"
        :data="tableData"
        :columns="columns"
        :pagination="pagination"
        @pagination:size-change="handleSizeChange"
        @pagination:current-change="handleCurrentChange"
      >
        <template #action="{ row }">
          <ArtButtonTable icon="ri:eye-line" @click="openDetailDrawer(row)" />
        </template>
      </ArtTable>
    </ElCard>
        <ArtTable
          :loading="loading"
          :data="tableData"
          :columns="columns"
          :pagination="pagination"
          @pagination:size-change="handleSizeChange"
          @pagination:current-change="handleCurrentChange"
        >
          <template #action="{ row }">
            <ArtButtonTable icon="ri:eye-line" @click="openDetailDrawer(row)" />
          </template>
        </ArtTable>
      </ElCard>
    </div>
    <WhMatDetailDrawer v-model:visible="detailDrawerVisible" :loading="detailLoading" :detail="detailData" />
  </div>
  <WhMatDetailDrawer v-model:visible="detailDrawerVisible" :loading="detailLoading" :detail="detailData" />
</template>
<script setup>
@@ -327,3 +329,55 @@
    await Promise.all([loadGroupTree(), loadMatnrList()])
  })
</script>
<style scoped>
  .wh-mat-page {
    display: flex;
    align-items: flex-start;
    gap: 16px;
  }
  .wh-mat-page__sidebar {
    width: 320px;
    flex: 0 0 320px;
  }
  .wh-mat-page__sidebar-card {
    position: sticky;
    top: 16px;
  }
  .wh-mat-page__tree-scroll {
    height: calc(100vh - 320px);
    min-height: 420px;
  }
  .wh-mat-page__content {
    min-width: 0;
    flex: 1 1 auto;
  }
  .wh-mat-page__content > * + * {
    margin-top: 16px;
  }
  @media (max-width: 1024px) {
    .wh-mat-page {
      flex-direction: column;
    }
    .wh-mat-page__sidebar {
      width: 100%;
      flex-basis: auto;
    }
    .wh-mat-page__sidebar-card {
      position: static;
    }
    .wh-mat-page__tree-scroll {
      height: 320px;
      min-height: 320px;
    }
  }
</style>
rsf-server/src/main/java/com/vincent/rsf/server/common/domain/BaseParam.java
@@ -79,18 +79,18 @@
            map.remove("condition");
        }
        FieldsService fieldsService = SpringUtils.getBean(FieldsService.class);
        List<Fields> fields = fieldsService.list(new LambdaQueryWrapper<Fields>().eq(Fields::getFlagEnable, 1).eq(Fields::getStatus, 1));
        if (!fields.isEmpty()) {
            for (Fields fields1 : fields) {
                if (null !=map.get(fields1.getFields())){
                    this.fields.put(fields1.getFields(), map.get(fields1.getFields()));
                    map.entrySet().removeIf(next -> next.getKey().equals(fields1.getFields())
                    );
                }
            }
        }
//        FieldsService fieldsService = SpringUtils.getBean(FieldsService.class);
//        List<Fields> fields = fieldsService.list(new LambdaQueryWrapper<Fields>().eq(Fields::getFlagEnable, 1).eq(Fields::getStatus, 1));
//        if (!fields.isEmpty()) {
//            for (Fields fields1 : fields) {
//                if (null !=map.get(fields1.getFields())){
//                    this.fields.put(fields1.getFields(), map.get(fields1.getFields()));
//                    map.entrySet().removeIf(next -> next.getKey().equals(fields1.getFields())
//                    );
//                }
//            }
//
//        }
        this.setMap(map);
    }
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/BasStationController.java
@@ -10,6 +10,8 @@
import com.vincent.rsf.server.common.domain.BaseParam;
import com.vincent.rsf.server.common.domain.KeyValVo;
import com.vincent.rsf.server.common.domain.PageParam;
import com.vincent.rsf.server.common.domain.PageResult;
import com.vincent.rsf.server.manager.controller.vo.BasStationOptionVo;
import com.vincent.rsf.server.manager.entity.BasStation;
import com.vincent.rsf.server.manager.enums.LocStsType;
import com.vincent.rsf.server.manager.service.BasStationService;
@@ -59,7 +61,15 @@
    public R pagev22(@RequestBody Map<String, Object> map) {
        BaseParam baseParam = buildParam(map, BaseParam.class);
        PageParam<BasStation, BaseParam> pageParam = new PageParam<>(baseParam, BasStation.class);
        return R.ok().add(basStationService.page(pageParam, pageParam.buildWrapper(true)));
        PageParam<BasStation, BaseParam> page = basStationService.page(
                pageParam,
                pageParam.buildWrapper(true).select("id", "station_name", "station_id")
        );
        PageResult<BasStationOptionVo> pageResult = new PageResult<>();
        pageResult
                .setTotal(page.getTotal())
                .setRecords(page.getRecords().stream().map(this::buildOptionVo).collect(Collectors.toList()));
        return R.ok().add(pageResult);
    }
    @PreAuthorize("hasAuthority('manager:basStation:list')")
@@ -189,4 +199,12 @@
        ExcelUtil.build(ExcelUtil.create(basStationService.list(), BasStation.class), response);
    }
    private BasStationOptionVo buildOptionVo(BasStation station) {
        BasStationOptionVo optionVo = new BasStationOptionVo();
        optionVo.setId(station.getId());
        optionVo.setStationName(station.getStationName());
        optionVo.setStationId(station.getStationId());
        return optionVo;
    }
}
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/CompanysController.java
@@ -10,6 +10,7 @@
import com.vincent.rsf.server.common.domain.BaseParam;
import com.vincent.rsf.server.common.domain.KeyValVo;
import com.vincent.rsf.server.common.domain.PageParam;
import com.vincent.rsf.server.manager.controller.vo.CompanysOptionVo;
import com.vincent.rsf.server.manager.entity.Companys;
import com.vincent.rsf.server.manager.entity.Warehouse;
import com.vincent.rsf.server.manager.service.CompanysService;
@@ -43,7 +44,13 @@
    @PreAuthorize("hasAuthority('manager:companys:list')")
    @PostMapping("/companys/list")
    public R list(@RequestBody Map<String, Object> map) {
        return R.ok().add(companysService.list());
        List<CompanysOptionVo> records = companysService
                .list(new LambdaQueryWrapper<Companys>()
                        .select(Companys::getId, Companys::getCode, Companys::getName, Companys::getType))
                .stream()
                .map(this::buildOptionVo)
                .toList();
        return R.ok().add(records);
    }
    @PreAuthorize("hasAuthority('manager:companys:list')")
@@ -158,4 +165,13 @@
        ExcelUtil.build(ExcelUtil.create(companies, Companys.class), response);
    }
    private CompanysOptionVo buildOptionVo(Companys companys) {
        CompanysOptionVo optionVo = new CompanysOptionVo();
        optionVo.setId(companys.getId());
        optionVo.setCode(companys.getCode());
        optionVo.setName(companys.getName());
        optionVo.setType(companys.getType());
        return optionVo;
    }
}
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/vo/BasStationOptionVo.java
New file
@@ -0,0 +1,21 @@
package com.vincent.rsf.server.manager.controller.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@Data
public class BasStationOptionVo implements Serializable {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value = "id")
    private Long id;
    @ApiModelProperty(value = "站点编号")
    private String stationName;
    @ApiModelProperty(value = "站点名称")
    private String stationId;
}
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/vo/CompanysOptionVo.java
New file
@@ -0,0 +1,24 @@
package com.vincent.rsf.server.manager.controller.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@Data
public class CompanysOptionVo implements Serializable {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value = "ID")
    private Long id;
    @ApiModelProperty(value = "编号")
    private String code;
    @ApiModelProperty(value = "名称")
    private String name;
    @ApiModelProperty(value = "帐号类型")
    private String type;
}