zhou zhou
3 小时以前 7c2bffa1a495cc4a3a263f654c08c231009c5c4e
#i18n
54个文件已修改
3931 ■■■■ 已修改文件
rsf-design/src/components/biz/list-export-print/index.vue 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/components/biz/list-export-print/list-export-print.helpers.js 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/components/biz/list-export-print/list-print-document.js 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/components/biz/list-export-print/list-print-preview-dialog.vue 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/components/core/forms/art-excel-export/index.vue 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/components/core/layouts/art-breadcrumb/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/components/core/layouts/art-chat-window/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/components/core/layouts/art-menus/art-sidebar-menu/index.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/components/core/layouts/art-work-tab/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/components/core/tables/art-table/index.vue 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/locales/langs/en.json 914 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/locales/langs/zh.json 914 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/router/adapters/backendMenuAdapter.js 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/router/modules/system.js 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/utils/backend-menu-title.js 96 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/utils/router.js 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/utils/sys/requestGuard.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/basic-info/bas-station-area/basStationAreaPage.helpers.js 72 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/basic-info/bas-station-area/basStationAreaTable.columns.js 45 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/basic-info/bas-station-area/index.vue 90 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/basic-info/bas-station-area/modules/bas-station-area-detail-drawer.vue 51 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/basic-info/bas-station-area/modules/bas-station-area-dialog.vue 91 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/manager/task/index.vue 82 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/manager/task/modules/task-detail-drawer.vue 46 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/manager/task/modules/task-expand-panel.vue 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/manager/task/modules/task-flow-step-dialog.vue 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/manager/task/taskPage.helpers.js 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/manager/task/taskTable.columns.js 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/orders/asn-order/asnOrderPage.helpers.js 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/orders/asn-order/asnOrderTable.columns.js 85 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/orders/asn-order/index.vue 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/orders/asn-order/modules/asn-order-create-by-po-dialog.vue 54 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/orders/asn-order/modules/asn-order-detail-drawer.vue 39 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/orders/delivery/deliveryPage.helpers.js 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/orders/delivery/deliveryTable.columns.js 33 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/orders/delivery/index.vue 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/orders/delivery/modules/delivery-detail-drawer.vue 78 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/orders/transfer/index.vue 58 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/orders/transfer/modules/transfer-detail-drawer.vue 40 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/orders/transfer/modules/transfer-dialog.vue 106 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/orders/transfer/transferPage.helpers.js 58 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/orders/transfer/transferTable.columns.js 55 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/system/common/useCrudPage.js 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/system/common/usePrintExportPage.js 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/system/menu/index.vue 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/system/menu/menuPage.helpers.js 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/system/menu/menuTable.columns.js 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/system/menu/modules/menu-dialog.vue 62 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/system/role/index.vue 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/system/role/modules/role-edit-dialog.vue 39 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/system/role/modules/role-permission-dialog.vue 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/system/role/modules/role-search.vue 33 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/system/role/rolePage.helpers.js 55 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/system/role/roleTable.columns.js 97 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/components/biz/list-export-print/index.vue
@@ -1,7 +1,7 @@
<template>
  <ElSpace v-bind="attrs" wrap>
    <ElButton :disabled="disabled" @click="handleExport">导出</ElButton>
    <ElButton :disabled="disabled" @click="handlePrint">打印</ElButton>
    <ElButton :disabled="disabled" @click="handleExport">{{ t('common.actions.export') }}</ElButton>
    <ElButton :disabled="disabled" @click="handlePrint">{{ t('common.actions.print') }}</ElButton>
  </ElSpace>
  <ListPrintPreviewDialog
@@ -16,6 +16,7 @@
<script setup>
  import { computed, useAttrs } from 'vue'
  import { useI18n } from 'vue-i18n'
  import ListPrintPreviewDialog from './list-print-preview-dialog.vue'
  import {
    buildListExportPayload,
@@ -30,9 +31,10 @@
  })
  const attrs = useAttrs()
  const { t } = useI18n()
  const props = defineProps({
    reportTitle: { type: String, default: '报表' },
    reportTitle: { type: String, default: '' },
    selectedRows: { type: Array, default: () => [] },
    queryParams: { type: Object, default: () => ({}) },
    columns: { type: Array, default: () => [] },
rsf-design/src/components/biz/list-export-print/list-export-print.helpers.js
@@ -1,3 +1,5 @@
import { $t } from '@/locales'
const DEFAULT_MAX_RESULTS = 1000
const DEFAULT_PRINT_PAGE_SIZE = 20
const HIDDEN_EXPORT_COLUMNS = new Set(['selection', 'operation'])
@@ -81,7 +83,7 @@
  }
  return [
    { source: '__sequence__', label: '序号', align: 'center' },
    { source: '__sequence__', label: $t('table.index'), align: 'center' },
    ...normalizedColumns
  ]
}
rsf-design/src/components/biz/list-export-print/list-print-document.js
@@ -1,3 +1,5 @@
import { $t } from '@/locales'
function escapeHtml(value) {
  return String(value ?? '')
    .replaceAll('&', '&amp;')
@@ -35,10 +37,10 @@
function getMetaItems(meta = {}) {
  return [
    { key: 'reportDate', label: '报表日期', value: meta.reportDate ?? '--' },
    { key: 'operator', label: '打印人', value: meta.operator ?? '--' },
    { key: 'printedAt', label: '打印时间', value: meta.printedAt ?? '--' },
    { key: 'count', label: '记录数', value: meta.count ?? '--' }
    { key: 'reportDate', label: $t('print.reportDate'), value: meta.reportDate ?? '--' },
    { key: 'operator', label: $t('print.operator'), value: meta.operator ?? '--' },
    { key: 'printedAt', label: $t('print.printedAt'), value: meta.printedAt ?? '--' },
    { key: 'count', label: $t('print.count'), value: meta.count ?? '--' }
  ]
}
@@ -58,9 +60,9 @@
  return alignMap[column.align] ?? alignMap.left
}
export function buildPrintDocumentHtml({ title = '报表', meta = {}, rows = [], columns = [] } = {}) {
export function buildPrintDocumentHtml({ title = '', meta = {}, rows = [], columns = [] } = {}) {
  const reportStyle = getReportStyle(meta)
  const reportTitle = meta.reportTitle || title
  const reportTitle = meta.reportTitle || title || $t('print.defaultReportTitle')
  const titleClass = `${getTitleAlignClass(reportStyle.titleAlign)} ${getTitleLevelClass(reportStyle.titleLevel)}`
  const orientation = getPageOrientation(reportStyle)
  const showBorder = reportStyle.showBorder !== false
@@ -88,7 +90,7 @@
            return `<tr>${cells}</tr>`
          })
          .join('')
      : `<tr><td colspan="${Math.max(columns.length, 1)}" class="empty-cell">暂无打印数据</td></tr>`
      : `<tr><td colspan="${Math.max(columns.length, 1)}" class="empty-cell">${escapeHtml($t('print.noData'))}</td></tr>`
  const metaHtml = metaItems
    .map(
rsf-design/src/components/biz/list-export-print/list-print-preview-dialog.vue
@@ -14,13 +14,13 @@
      >
        <div class="flex flex-wrap items-center justify-end gap-2 pb-2">
          <ElRadioGroup v-model="currentShowBorder" size="small">
            <ElRadioButton :label="true">边框开</ElRadioButton>
            <ElRadioButton :label="false">边框关</ElRadioButton>
            <ElRadioButton :label="true">{{ t('print.borderOn') }}</ElRadioButton>
            <ElRadioButton :label="false">{{ t('print.borderOff') }}</ElRadioButton>
          </ElRadioGroup>
          <ElRadioGroup v-model="currentOrientation" size="small">
            <ElRadioButton label="portrait">竖版</ElRadioButton>
            <ElRadioButton label="landscape">横版</ElRadioButton>
            <ElRadioButton label="portrait">{{ t('print.portrait') }}</ElRadioButton>
            <ElRadioButton label="landscape">{{ t('print.landscape') }}</ElRadioButton>
          </ElRadioGroup>
        </div>
@@ -39,7 +39,7 @@
          v-if="hiddenRowCount > 0"
          class="mt-3 rounded-md border border-amber-200 bg-amber-50 px-2.5 py-1.5 text-[11px] leading-tight text-amber-700"
        >
          预览仅展示前 {{ previewRows.length }} 条,点击打印将输出全部 {{ rows.length }} 条数据。
          {{ t('print.previewLimited', { previewCount: previewRows.length, totalCount: rows.length }) }}
        </div>
        <div :class="tableWrapClass" class="mt-4 min-h-0 flex-1 overflow-auto">
@@ -61,7 +61,7 @@
                  :colspan="Math.max(columns.length, 1)"
                  :class="emptyCellClass"
                >
                  暂无打印数据
                  {{ t('print.noData') }}
                </td>
              </tr>
              <tr v-for="(row, index) in previewRows" :key="row?.id ?? index">
@@ -81,8 +81,8 @@
    <template #footer>
      <div class="flex items-center justify-end gap-2 print:hidden">
        <ElButton @click="visible = false">关闭</ElButton>
        <ElButton type="primary" @click="handlePrint">打印</ElButton>
        <ElButton @click="visible = false">{{ t('common.actions.close') }}</ElButton>
        <ElButton type="primary" @click="handlePrint">{{ t('common.actions.print') }}</ElButton>
      </div>
    </template>
  </ElDialog>
@@ -90,6 +90,7 @@
<script setup>
  import { computed, ref, watch } from 'vue'
  import { useI18n } from 'vue-i18n'
  import { printReportDocument } from './list-print-document.js'
  defineOptions({ name: 'ListPrintPreviewDialog' })
@@ -97,12 +98,13 @@
  const visible = defineModel('visible', { type: Boolean, default: false })
  const props = defineProps({
    title: { type: String, default: '打印预览' },
    title: { type: String, default: '' },
    meta: { type: Object, default: () => ({}) },
    rows: { type: Array, default: () => [] },
    columns: { type: Array, default: () => [] },
    maxPreviewRows: { type: Number, default: 50 }
  })
  const { t } = useI18n()
  const meta = computed(() => (props.meta && typeof props.meta === 'object' ? props.meta : {}))
  const reportStyleSource = computed(() =>
@@ -115,10 +117,10 @@
  const metaItems = computed(() => {
    return [
      { key: 'reportDate', label: '报表日期', value: meta.value.reportDate },
      { key: 'operator', label: '打印人', value: meta.value.operator },
      { key: 'printedAt', label: '打印时间', value: meta.value.printedAt },
      { key: 'count', label: '记录数', value: meta.value.count }
      { key: 'reportDate', label: t('print.reportDate'), value: meta.value.reportDate },
      { key: 'operator', label: t('print.operator'), value: meta.value.operator },
      { key: 'printedAt', label: t('print.printedAt'), value: meta.value.printedAt },
      { key: 'count', label: t('print.count'), value: meta.value.count }
    ]
  })
  const previewRows = computed(() => props.rows.slice(0, props.maxPreviewRows))
rsf-design/src/components/core/forms/art-excel-export/index.vue
@@ -1,4 +1,3 @@
<!-- 导出 Excel 文件 -->
<template>
  <ElButton
    :type="type"
@@ -12,19 +11,22 @@
      <ElIcon class="is-loading">
        <Loading />
      </ElIcon>
      {{ loadingText }}
      {{ resolvedLoadingText }}
    </template>
    <slot>{{ buttonText }}</slot>
    <slot>{{ resolvedButtonText }}</slot>
  </ElButton>
</template>
<script setup>
  import * as XLSX from 'xlsx'
  import FileSaver from 'file-saver'
  import { ref, computed, nextTick } from 'vue'
  import { ElMessage } from 'element-plus'
  import { ref, computed, nextTick, readonly } from 'vue'
  import { Loading } from '@element-plus/icons-vue'
  import { useThrottleFn } from '@vueuse/core'
  import { useI18n } from 'vue-i18n'
  defineOptions({ name: 'ArtExcelExport' })
  const { t } = useI18n()
  const props = defineProps({
    filename: {
      required: false,
@@ -34,10 +36,10 @@
    type: { required: false, default: 'primary' },
    size: { required: false, default: 'default' },
    disabled: { required: false, default: false },
    buttonText: { required: false, default: '导出 Excel' },
    loadingText: { required: false, default: '导出中...' },
    buttonText: { required: false, default: '' },
    loadingText: { required: false, default: '' },
    autoIndex: { required: false, default: false },
    indexColumnTitle: { required: false, default: '序号' },
    indexColumnTitle: { required: false, default: '' },
    columns: { required: false, default: () => ({}) },
    headers: { required: false, default: () => ({}) },
    maxRows: { required: false, default: 1e5 },
@@ -56,15 +58,18 @@
  }
  const isExporting = ref(false)
  const hasData = computed(() => Array.isArray(props.data) && props.data.length > 0)
  const resolvedButtonText = computed(() => props.buttonText || t('common.actions.export'))
  const resolvedLoadingText = computed(() => props.loadingText || t('common.actions.exporting'))
  const resolvedIndexColumnTitle = computed(() => props.indexColumnTitle || t('table.index'))
  const validateData = (data) => {
    if (!Array.isArray(data)) {
      throw new ExportError('数据必须是数组格式', 'INVALID_DATA_TYPE')
      throw new ExportError(t('message.exportInvalidDataType'), 'INVALID_DATA_TYPE')
    }
    if (data.length === 0) {
      throw new ExportError('没有可导出的数据', 'NO_DATA')
      throw new ExportError(t('message.exportNoData'), 'NO_DATA')
    }
    if (data.length > props.maxRows) {
      throw new ExportError(`数据行数超过限制(${props.maxRows}行)`, 'EXCEED_MAX_ROWS', {
      throw new ExportError(t('message.exportExceedMaxRows', { maxRows: props.maxRows }), 'EXCEED_MAX_ROWS', {
        currentRows: data.length,
        maxRows: props.maxRows
      })
@@ -82,7 +87,7 @@
      return value.toLocaleDateString('zh-CN')
    }
    if (typeof value === 'boolean') {
      return value ? '是' : '否'
      return value ? t('common.status.yes') : t('common.status.no')
    }
    return String(value)
  }
@@ -90,7 +95,7 @@
    const processedData = data.map((item, index) => {
      const processedItem = {}
      if (props.autoIndex) {
        processedItem[props.indexColumnTitle] = String(index + 1)
        processedItem[resolvedIndexColumnTitle.value] = String(index + 1)
      }
      Object.entries(item).forEach(([key, value]) => {
        let columnTitle = key
@@ -131,13 +136,13 @@
      if (props.workbookOptions) {
        workbook.Props = {
          Title: filename,
          Subject: '数据导出',
          Subject: t('message.exportWorkbookSubject'),
          Author: props.workbookOptions.creator || 'Art Design Pro',
          Manager: props.workbookOptions.lastModifiedBy || '',
          Company: '系统导出',
          Category: '数据',
          Keywords: 'excel,export,data',
          Comments: '由系统自动生成',
          Company: t('message.exportWorkbookCompany'),
          Category: t('message.exportWorkbookCategory'),
          Keywords: t('message.exportWorkbookKeywords'),
          Comments: t('message.exportWorkbookComments'),
          CreatedDate: props.workbookOptions.created || /* @__PURE__ */ new Date(),
          ModifiedDate: props.workbookOptions.modified || /* @__PURE__ */ new Date()
        }
@@ -164,7 +169,11 @@
      await nextTick()
      return Promise.resolve()
    } catch (error) {
      throw new ExportError(`Excel 导出失败: ${error.message}`, 'EXPORT_FAILED', error)
      throw new ExportError(
        t('message.exportExcelFailed', { message: error.message }),
        'EXPORT_FAILED',
        error
      )
    }
  }
  const handleExport = useThrottleFn(async () => {
@@ -177,7 +186,7 @@
      emit('export-success', props.filename, props.data.length)
      if (props.showSuccessMessage) {
        ElMessage.success({
          message: `成功导出 ${props.data.length} 条数据`,
          message: t('message.exportSuccessWithCount', { count: props.data.length }),
          duration: 3e3
        })
      }
@@ -185,7 +194,11 @@
      const exportError =
        error instanceof ExportError
          ? error
          : new ExportError(`导出失败: ${error.message}`, 'UNKNOWN_ERROR', error)
          : new ExportError(
              t('message.exportFailedUnknown', { message: error.message }),
              'UNKNOWN_ERROR',
              error
            )
      emit('export-error', exportError)
      if (props.showErrorMessage) {
        ElMessage.error({
rsf-design/src/components/core/layouts/art-breadcrumb/index.vue
@@ -95,7 +95,7 @@
        await router.push(item.path)
      }
    } catch (error) {
      console.error('导航失败:', error)
      console.error('Breadcrumb navigation failed:', error)
    }
  }
</script>
rsf-design/src/components/core/layouts/art-chat-window/index.vue
@@ -14,7 +14,7 @@
        <div class="min-w-0 flex-1">
          <div class="flex flex-wrap items-center gap-2">
            <h3 class="text-base font-semibold text-[var(--art-gray-900)]">{{ $t('ai.drawer.title') }}</h3>
            <ElTag v-if="streaming" type="success" effect="light" round>Streaming</ElTag>
            <ElTag v-if="streaming" type="success" effect="light" round>{{ $t('ai.drawer.streaming') }}</ElTag>
          </div>
          <p class="mt-1 truncate text-xs text-[var(--art-gray-500)]">
            {{ runtime?.promptName || runtime?.promptCode || DEFAULT_PROMPT_CODE }}
@@ -350,7 +350,7 @@
              <div class="mt-3 flex flex-wrap items-center justify-between gap-3">
                <div class="text-xs text-[var(--art-gray-500)]">
                  Enter 发送,Shift + Enter 换行
                  {{ $t('ai.drawer.inputHotkeyHint') }}
                </div>
                <div class="flex flex-wrap items-center gap-2">
rsf-design/src/components/core/layouts/art-menus/art-sidebar-menu/index.vue
@@ -19,7 +19,7 @@
            <ElTooltip
              class="box-item"
              effect="dark"
              :content="$t(menu.meta.title)"
              :content="formatMenuTitle(menu.meta.title)"
              placement="right"
              :offset="15"
              :hide-after="0"
@@ -43,7 +43,7 @@
                  }"
                />
                <span v-if="dualMenuShowText" class="text-md text-g-700">
                  {{ $t(menu.meta.title) }}
                  {{ formatMenuTitle(menu.meta.title) }}
                </span>
                <div v-if="menu.meta.showBadge" class="art-badge art-badge-dual" />
              </div>
@@ -132,6 +132,7 @@
<script setup>
  import AppConfig from '@/config'
  import { handleMenuJump } from '@/utils/navigation'
  import { formatMenuTitle } from '@/utils/router'
  import SidebarSubmenu from './widget/SidebarSubmenu.vue'
rsf-design/src/components/core/layouts/art-work-tab/index.vue
@@ -44,7 +44,7 @@
            class="text-base mr-1 group-hover:text-theme"
            :class="item.path === activeTab ? 'text-theme' : 'text-g-600'"
          />
          {{ item.customTitle || formatMenuTitle(item.title) }}
          {{ formatMenuTitle(item.customTitle || item.title) }}
          <span
            v-if="list.length > 1 && !item.fixedTab"
            class="inline-flex flex-cc relative ml-0.5 p-1 rounded-full tad-200 hover:bg-g-200"
rsf-design/src/components/core/tables/art-table/index.vue
@@ -48,7 +48,7 @@
      <template #empty>
        <div v-if="loading"></div>
        <ElEmpty v-else :description="emptyText" :image-size="120" />
        <ElEmpty v-else :description="resolvedEmptyText" :image-size="120" />
      </template>
    </ElTable>
@@ -73,6 +73,7 @@
<script setup>
  import { ref, computed, nextTick, watchEffect, getCurrentInstance, useAttrs } from 'vue'
  import { useI18n } from 'vue-i18n'
  import { storeToRefs } from 'pinia'
  import { useTableStore } from '@/store/modules/table'
  import { useCommon } from '@/hooks/core/useCommon'
@@ -83,6 +84,7 @@
  const elTableRef = ref(null)
  const paginationRef = ref()
  const tableHeaderRef = ref()
  const { t } = useI18n()
  const tableStore = useTableStore()
  const { isBorder, isZebra, tableSize, isFullScreen, isHeaderBackground } = storeToRefs(tableStore)
  const props = defineProps({
@@ -96,7 +98,7 @@
    border: { required: false, default: void 0 },
    size: { required: false, default: void 0 },
    emptyHeight: { required: false, default: '100%' },
    emptyText: { required: false, default: '暂无数据' },
    emptyText: { required: false, default: '' },
    showTableHeader: { required: false, default: true }
  })
  const instance = getCurrentInstance()
@@ -189,6 +191,7 @@
      : void 0
  }))
  const showPagination = computed(() => props.pagination && !isEmpty.value)
  const resolvedEmptyText = computed(() => props.emptyText || t('table.emptyText'))
  const shouldRenderSlotScope = (slotScope) => {
    return slotScope.$index === void 0 || slotScope.$index >= 0
  }
rsf-design/src/locales/langs/en.json
@@ -36,7 +36,87 @@
    "tips": "Prompt",
    "cancel": "Cancel",
    "confirm": "Confirm",
    "logOutTips": "Do you want to log out?"
    "logOutTips": "Do you want to log out?",
    "count": "{count} items",
    "listSeparator": ", ",
    "actions": {
      "search": "Search",
      "reset": "Reset",
      "refresh": "Refresh",
      "add": "Add",
      "batchDelete": "Batch Delete",
      "edit": "Edit",
      "delete": "Delete",
      "detail": "Detail",
      "items": "Items",
      "print": "Print",
      "export": "Export",
      "exporting": "Exporting...",
      "close": "Close",
      "complete": "Complete",
      "expand": "Expand",
      "collapse": "Collapse",
      "viewAll": "View All",
      "save": "Save",
      "submit": "Submit"
    },
    "status": {
      "enabled": "Enabled",
      "disabled": "Disabled",
      "normal": "Normal",
      "frozen": "Frozen",
      "unknown": "Unknown",
      "yes": "Yes",
      "no": "No"
    },
    "placeholder": {
      "empty": "--"
    }
  },
  "crud": {
    "messages": {
      "createSuccess": "Created successfully",
      "updateSuccess": "Updated successfully",
      "deleteSuccess": "Deleted successfully",
      "batchDeleteSuccess": "Batch deleted successfully",
      "submitFailed": "Submit failed",
      "deleteFailed": "Delete failed",
      "batchDeleteFailed": "Batch delete failed",
      "exportSuccess": "Export succeeded",
      "exportFailed": "Export failed",
      "exportFailedWithStatus": "Export failed ({status})",
      "printFailed": "Print failed",
      "loadFailed": "Load failed"
    },
    "confirm": {
      "deleteTitle": "Delete confirmation",
      "deleteMessage": "Are you sure you want to delete {entity} \"{label}\"?",
      "batchDeleteTitle": "Batch delete confirmation",
      "batchDeleteMessage": "Are you sure you want to delete {count} selected {entity}?"
    }
  },
  "print": {
    "previewTitle": "Print Preview",
    "defaultReportTitle": "Report",
    "borderOn": "Border On",
    "borderOff": "Border Off",
    "portrait": "Portrait",
    "landscape": "Landscape",
    "reportDate": "Report Date",
    "operator": "Operator",
    "printedAt": "Printed At",
    "count": "Records",
    "previewLimited": "Preview shows the first {previewCount} records only. Printing will output all {totalCount} records.",
    "noData": "No printable data"
  },
  "table": {
    "index": "No.",
    "unit": "Unit",
    "id": "ID",
    "wcs": "WCS",
    "source": "Source",
    "supplier": "Supplier",
    "supplierBatch": "Supplier Batch"
  },
  "search": {
    "placeholder": "Search page",
@@ -449,6 +529,7 @@
      "assistantRole": "AI",
      "thinking": "Thinking...",
      "inputPlaceholder": "Type your question. Press Enter to send, Shift + Enter for a new line",
      "inputHotkeyHint": "Enter to send, Shift + Enter for a new line",
      "clearInput": "Clear Input",
      "stop": "Stop",
      "send": "Send",
@@ -471,7 +552,8 @@
        "recentMetric": "Recent: {value}",
      "elapsedMetric": "Elapsed: {value} ms",
      "firstTokenMetric": "First token: {value} ms",
      "tokenMetric": "Tokens: prompt {prompt} / completion {completion} / total {total}"
      "tokenMetric": "Tokens: prompt {prompt} / completion {completion} / total {total}",
      "streaming": "Streaming"
    }
  },
  "table": {
@@ -498,8 +580,834 @@
      "expand": "Expand",
      "index": "Index"
    },
    "index": "Index",
    "id": "ID",
    "operation": "Operation",
    "status": "Status",
    "type": "Type",
    "source": "Source",
    "supplier": "Supplier",
    "supplierBatch": "Supplier Batch",
    "remark": "Remark",
    "updateBy": "Updated By",
    "updateTime": "Updated At",
    "createTime": "Created At",
    "unit": "Unit",
    "batch": "Batch",
    "quantity": "Quantity",
    "materialCode": "Material Code",
    "materialName": "Material Name",
    "wcs": "WCS",
    "menuType": "Menu Type",
    "iconPreview": "Icon Preview",
    "componentKey": "Component Key",
    "permissionKey": "Permission Key",
    "sort": "Sort",
    "zebra": "Zebra",
    "border": "Border",
    "headerBackground": "Header BG"
    "headerBackground": "Header BG",
    "topLevelMenu": "Top Level Menu",
    "emptyText": "No data"
  },
  "message": {
    "requestTimeoutStopped": "Request timed out and waiting has stopped",
    "exportTimeoutStopped": "Export request timed out and waiting has stopped",
    "printTimeoutStopped": "Print data loading timed out and waiting has stopped",
    "exportInvalidDataType": "Data must be an array",
    "exportNoData": "No data available for export",
    "exportExceedMaxRows": "Row count exceeds the limit ({maxRows} rows)",
    "exportExcelFailed": "Excel export failed: {message}",
    "exportSuccessWithCount": "Successfully exported {count} records",
    "exportFailedUnknown": "Export failed: {message}",
    "exportWorkbookSubject": "Data Export",
    "exportWorkbookCompany": "System Export",
    "exportWorkbookCategory": "Data",
    "exportWorkbookKeywords": "excel,export,data",
    "exportWorkbookComments": "Generated automatically by the system"
  },
  "pages": {
    "system": {
      "role": {
        "entity": "Role",
        "reportTitle": "Role Report",
        "buttons": {
          "add": "Add Role"
        },
        "search": {
          "name": "Role Name",
          "namePlaceholder": "Enter role name",
          "code": "Role Code",
          "codePlaceholder": "Enter role code",
          "memo": "Remark",
          "memoPlaceholder": "Enter remark",
          "condition": "Keyword",
          "conditionPlaceholder": "Search by keyword",
          "status": "Status",
          "statusPlaceholder": "Select status"
        },
        "table": {
          "name": "Role Name",
          "code": "Role Code",
          "memo": "Remark",
          "status": "Status",
          "updateTime": "Updated At",
          "createTime": "Created At",
          "operation": "Operation"
        },
        "actions": {
          "scopeMenu": "Web Permissions",
          "scopePda": "PDA Permissions",
          "scopeMatnr": "Material Permissions",
          "scopeWarehouse": "Warehouse Permissions",
          "edit": "Edit Role",
          "delete": "Delete Role"
        },
        "scopes": {
          "menu": "Web Permissions",
          "pda": "PDA Permissions",
          "matnr": "Material Permissions",
          "warehouse": "Warehouse Permissions"
        },
        "dialog": {
          "addTitle": "Add Role",
          "editTitle": "Edit Role",
          "validationName": "Please enter the role name",
          "name": "Role Name",
          "namePlaceholder": "Enter role name",
          "code": "Role Code",
          "codePlaceholder": "Enter role code",
          "status": "Status",
          "statusPlaceholder": "Select status",
          "memo": "Remark",
          "memoPlaceholder": "Enter remark"
        },
        "permission": {
          "title": "Role Permissions",
          "currentRole": "Current Role: ",
          "unselected": "No role selected",
          "selectAll": "Select All",
          "clear": "Clear",
          "saveCurrent": "Save Current Permissions",
          "searchPlaceholder": "Search permission tree",
          "authButton": "Button",
          "scopeLoadTimeout": "{title} loading timed out and waiting has stopped",
          "scopeLoadFailed": "Failed to load {title}",
          "saveSuccess": "Permissions saved successfully",
          "saveFailed": "Failed to save permissions"
        }
      },
      "menu": {
        "title": "Menu Management",
        "addMenu": "Add Menu",
        "menuName": "Menu Name",
        "route": "Route Path",
        "iconPreview": "Icon Preview",
        "menuType": "Menu Type",
        "componentKey": "Component Key",
        "authority": "Authority",
        "sort": "Sort",
        "status": "Status",
        "memo": "Remark",
        "operation": "Operation",
        "types": {
          "button": "Button",
          "directory": "Directory",
          "menu": "Menu"
        },
        "addPermission": "Add Permission",
        "deleteMenuMessage": "Are you sure you want to delete menu \"{label}\"? This action cannot be undone.",
        "deleteAuthMessage": "Are you sure you want to delete permission \"{label}\"? This action cannot be undone.",
        "selfParentError": "The parent menu cannot be the current menu"
      }
    },
    "orders": {
      "asnOrder": {
        "reportTitle": "ASN Report",
        "entity": "ASN",
        "buttons": {
          "createByPo": "Create by PO"
        },
        "search": {
          "condition": "Keyword",
          "conditionPlaceholder": "Enter ASN No./PO No./Supplier",
          "code": "ASN No.",
          "codePlaceholder": "Enter ASN No.",
          "poCode": "PO No.",
          "poCodePlaceholder": "Enter PO No.",
          "wkType": "Business Type",
          "wkTypePlaceholder": "Enter business type",
          "exceStatus": "Document Status",
          "supplierName": "Supplier",
          "supplierPlaceholder": "Enter supplier",
          "purchaseUserName": "Purchaser",
          "purchaseUserPlaceholder": "Enter purchaser"
        },
        "placeholder": {
          "condition": "Enter ASN No./PO No./Supplier",
          "code": "Enter ASN No.",
          "poCode": "Enter PO No.",
          "wkType": "Enter business type",
          "supplierName": "Enter supplier",
          "purchaseUserName": "Enter purchaser"
        },
        "status": {
          "pending": "Pending",
          "running": "In Progress",
          "receiving": "Received",
          "taskRunning": "Task Running",
          "completed": "Completed",
          "cancelled": "Cancelled",
          "closed": "Closed"
        },
        "actions": {
          "view": "View Detail",
          "items": "Receiving Items",
          "print": "Print",
          "complete": "Complete"
        },
        "detail": {
          "title": "ASN Detail",
          "baseInfo": "Basic Information",
          "items": "Order Items",
          "asnCode": "ASN No.",
          "poCode": "PO No.",
          "wkType": "Business Type",
          "orderType": "Order Type",
          "status": "Document Status",
          "purchaseOrg": "Purchasing Org",
          "purchaseUser": "Purchaser",
          "supplier": "Supplier",
          "anfme": "Expected Qty",
          "qty": "Received Qty",
          "updateTime": "Updated At",
          "createTime": "Created At",
          "memo": "Remark",
          "count": "{count} items",
          "completeTitle": "Complete Confirmation",
          "completeConfirm": "Are you sure you want to complete ASN {code}?",
          "completeSuccess": "ASN completed",
          "actionFailed": "ASN action failed",
          "detailTimeout": "ASN detail items timed out and waiting has stopped",
          "itemsTimeout": "ASN detail items timed out and waiting has stopped"
        },
        "createByPoDialog": {
          "title": "Create by PO",
          "purchaseList": "Available PO List",
          "purchasePreview": "PO Item Preview",
          "purchaseSelected": "Selected: {code}",
          "purchaseEmpty": "Select a PO from the left first",
          "purchaseGenerateHint": "Generate ASN from {count} items of PO {code}",
          "purchaseGenerateEmpty": "Please select an available PO",
          "generate": "Generate ASN",
          "refreshItems": "Refresh Items",
          "messages": {
            "purchaseItemsTimeout": "PO items timed out and waiting has stopped",
            "purchaseItemsAllTimeout": "Full PO items timed out and waiting has stopped",
            "purchaseRequired": "Please select a PO first",
            "purchaseItemsEmpty": "The selected PO has no buildable items",
            "createByPoSuccess": "ASN created from PO successfully",
            "createByPoFailed": "Create ASN by PO failed"
          },
          "search": {
            "condition": "Keyword",
            "conditionPlaceholder": "Enter PO No./source/supplier",
            "code": "PO No.",
            "codePlaceholder": "Enter PO No.",
            "source": "Source",
            "sourcePlaceholder": "Enter source",
            "supplierName": "Supplier",
            "supplierNamePlaceholder": "Enter supplier"
          }
        },
        "table": {
          "poItemId": "PO Line No.",
          "expectedQty": "Expected Qty",
          "receivedQty": "Received Qty",
          "remainingQty": "Creatable Qty",
          "poStatus": "PO Status",
          "purchaseQty": "Purchase Qty",
          "generatedQty": "Generated ASN Qty",
          "receivedQtyTotal": "Received Qty"
        }
      },
      "delivery": {
        "reportTitle": "DO Report",
        "detailReportTitle": "DO Item Report",
        "entity": "DO",
        "search": {
          "condition": "Keyword",
          "conditionPlaceholder": "Enter No./ERP master order/platform order",
          "code": "No.",
          "codePlaceholder": "Enter No.",
          "platId": "ERP Master Order ID",
          "platIdPlaceholder": "Enter ERP master order ID",
          "type": "Order Type",
          "typePlaceholder": "Enter order type",
          "wkType": "Business Type",
          "wkTypePlaceholder": "Enter business type",
          "source": "Order Source",
          "sourcePlaceholder": "Enter order source",
          "exceStatus": "Execution Status",
          "exceStatusPlaceholder": "Enter execution status",
          "memo": "Remark",
          "memoPlaceholder": "Enter remark"
        },
        "placeholder": {
          "condition": "Enter No./ERP master order/platform order",
          "code": "Enter No.",
          "platId": "Enter ERP master order ID",
          "type": "Enter order type",
          "wkType": "Enter business type",
          "source": "Enter order source",
          "exceStatus": "Enter execution status",
          "memo": "Enter remark"
        },
        "status": {
          "normal": "Normal",
          "disabled": "Disabled",
          "pending": "Pending",
          "running": "Running",
          "partial": "Partially Completed",
          "completed": "Completed"
        },
        "actions": {
          "view": "View Detail",
          "items": "Items",
          "delete": "Delete"
        },
        "detail": {
          "title": "Handover Order Detail",
          "baseInfo": "Basic Information",
          "auditInfo": "Audit Information",
          "items": "Handover Order Items",
          "code": "Handover No.",
          "platId": "ERP Master Order ID",
          "platCode": "Platform Order No.",
          "type": "Order Type",
          "wkType": "Business Type",
          "source": "Order Source",
          "anfme": "Expected Qty",
          "qty": "Received Qty",
          "workQty": "In-progress Qty",
          "status": "Status",
          "exceStatus": "Execution Status",
          "memo": "Remark",
          "startTime": "Planned Outbound Time",
          "endTime": "Planned Outbound End Time",
          "createBy": "Created By",
          "createTime": "Created At",
          "updateBy": "Updated By",
          "updateTime": "Updated At",
          "count": "{count} items"
        },
        "table": {
          "deliveryCode": "Handover No.",
          "platCode": "Platform Order No.",
          "platItemId": "Platform Line No.",
          "matnrCode": "Material Code",
          "maktx": "Material Name",
          "fieldsIndex": "Dynamic Field Index",
          "anfme": "Qty",
          "workQty": "Working Qty",
          "qty": "Outbound Qty",
          "startTime": "Planned Outbound Time",
          "endTime": "Planned Outbound End Time",
          "nromQty": "Std. Pack",
          "printQty": "Print Qty",
          "splrName": "Supplier Name",
          "splrCode": "Supplier Code",
          "splrBatch": "Supplier Batch"
        },
        "messages": {
          "itemsTimeout": "DO items timed out and waiting has stopped",
          "detailTimeout": "DO detail timed out and waiting has stopped",
          "detailLoadFailed": "Failed to load DO detail"
        }
      },
      "transfer": {
        "reportTitle": "Transfer Report",
        "entity": "Transfer Order",
        "buttons": {
          "add": "Add Transfer",
          "publish": "Dispatch"
        },
        "search": {
          "condition": "Keyword",
          "conditionPlaceholder": "Enter No./remark/warehouse/area",
          "code": "Transfer No.",
          "codePlaceholder": "Enter transfer No.",
          "type": "Transfer Type",
          "source": "Source",
          "exceStatus": "Execution Status",
          "orgWareName": "Source Warehouse",
          "orgWareNamePlaceholder": "Enter source warehouse",
          "tarWareName": "Target Warehouse",
          "tarWareNamePlaceholder": "Enter target warehouse",
          "orgAreaName": "Source Area",
          "orgAreaNamePlaceholder": "Enter source area",
          "tarAreaName": "Target Area",
          "tarAreaNamePlaceholder": "Enter target area",
          "status": "Status",
          "memo": "Remark",
          "memoPlaceholder": "Enter remark"
        },
        "status": {
          "sourceErp": "ERP",
          "sourceWms": "WMS Generated",
          "sourceExcel": "Excel Import",
          "sourceQms": "QMS",
          "pending": "Pending",
          "running": "Running",
          "completed": "Completed",
          "normal": "Normal",
          "frozen": "Frozen"
        },
        "actions": {
          "add": "Add Transfer",
          "view": "View Detail",
          "items": "Items",
          "edit": "Edit",
          "publish": "Dispatch",
          "delete": "Delete"
        },
        "placeholder": {
          "condition": "Enter No./remark/warehouse/area",
          "code": "Enter transfer No.",
          "orgWareName": "Enter source warehouse",
          "tarWareName": "Enter target warehouse",
          "orgAreaName": "Enter source area",
          "tarAreaName": "Enter target area",
          "memo": "Enter remark"
        },
        "detail": {
          "title": "Transfer Detail",
          "baseInfo": "Basic Information",
          "auditInfo": "Audit Information",
          "source": "Source",
          "orgWareName": "Source Warehouse",
          "tarWareName": "Target Warehouse",
          "orgAreaName": "Source Area",
          "tarAreaName": "Target Area",
          "memo": "Remark",
          "createBy": "Created By",
          "createTime": "Created At",
          "updateBy": "Updated By",
          "updateTime": "Updated At",
          "relatedOrders": "Related Orders",
          "relatedCode": "Related Order No.",
          "code": "Transfer No.",
          "type": "Transfer Type",
          "wkType": "Business Type",
          "exceStatus": "Execution Status",
          "status": "Status",
          "workQty": "In-progress Qty",
          "qty": "Completed Qty",
          "stationId": "Station No.",
          "businessTime": "Business Time"
        },
        "dialog": {
          "titleAdd": "Add Transfer",
          "titleEdit": "Edit Transfer",
          "tip": "The transfer number is generated by the system. When creating a new record, only maintain the transfer type, source/target area, and remark.",
          "code": "Transfer No.",
          "type": "Transfer Type",
          "orgAreaId": "Source Area",
          "tarAreaId": "Target Area",
          "status": "Status",
          "memo": "Remark",
          "placeholderCode": "Generated after saving",
          "placeholderType": "Please select a transfer type",
          "placeholderOrgAreaId": "Please select a source area",
          "placeholderTarAreaId": "Please select a target area",
          "placeholderStatus": "Please select a status",
          "placeholderMemo": "Please enter a remark",
          "validation": {
            "type": "Please select a transfer type",
            "orgAreaId": "Please select a source area",
            "tarAreaId": "Please select a target area"
          }
        },
        "messages": {
          "detailTimeout": "Transfer detail timed out and waiting has stopped",
          "ordersTimeout": "Transfer items timed out and waiting has stopped",
          "ordersLoadFailed": "Failed to load transfer items",
          "detailLoadFailed": "Failed to load transfer detail",
          "publishConfirm": "Are you sure you want to dispatch transfer order \"{code}\"?",
          "publishTitle": "Dispatch Confirmation",
          "publishSuccess": "Dispatched successfully",
          "publishFailed": "Dispatch failed",
          "typeOptionsTimeout": "Transfer type options timed out and waiting has stopped",
          "areaOptionsTimeout": "Area options timed out and waiting has stopped"
        }
      }
    },
    "task": {
      "title": "Task Management",
      "buttons": {
        "autoRun": "Enable Auto Dispatch",
        "pauseAutoRun": "Pause Auto Dispatch"
      },
      "placeholder": {
        "condition": "Enter task No./location/pallet code",
        "taskCode": "Enter task No.",
        "orgLoc": "Enter source location",
        "targLoc": "Enter target location",
        "barcode": "Enter pallet code"
      },
      "search": {
        "condition": "Keyword",
        "conditionPlaceholder": "Enter task No./location/pallet code",
        "taskCode": "Task No.",
        "taskCodePlaceholder": "Enter task No.",
        "orgLoc": "Source Location",
        "orgLocPlaceholder": "Enter source location",
        "targLoc": "Target Location",
        "targLocPlaceholder": "Enter target location",
        "barcode": "Pallet Code",
        "barcodePlaceholder": "Enter pallet code"
      },
      "actions": {
        "view": "View Detail",
        "flowStep": "Flow Steps",
        "complete": "Complete Task",
        "check": "Check Outbound",
        "pick": "Pick Outbound",
        "top": "Pin Task",
        "remove": "Cancel Task"
      },
      "detail": {
        "title": "Task Detail",
        "taskCode": "Task No.",
        "baseInfo": "Basic Information",
        "pathInfo": "Execution Path",
        "items": "Task Items",
        "itemsHint": "View related orders, materials, and execution records of the current task",
        "flowStep": "Flow Steps",
        "taskStatus": "Task Status",
        "taskType": "Task Type",
        "warehType": "Device Type",
        "priority": "Priority",
        "status": "Status",
        "robotCode": "Robot Code",
        "createTime": "Created At",
        "updateTime": "Updated At",
        "memo": "Remark",
        "orgLoc": "Source Location",
        "orgSite": "Source Station",
        "targLoc": "Target Location",
        "targSite": "Target Station",
        "barcode": "Pallet Code"
      },
      "expand": {
        "title": "Task Items",
        "empty": "No task items",
        "orderType": "Order Type",
        "wkType": "Business Type",
        "platWorkCode": "Work Order No.",
        "platItemId": "Line No.",
        "anfme": "Quantity"
      },
      "flowStepDialog": {
        "title": "Flow Steps",
        "currentTask": "Current Task",
        "flowInstanceNo": "Flow Instance No.",
        "stepCode": "Step Code",
        "stepName": "Step Name",
        "stepType": "Step Type",
        "executeResult": "Execution Result",
        "startTime": "Start Time",
        "endTime": "End Time",
        "timeout": "Flow steps timed out and waiting has stopped"
      },
      "messages": {
        "completeConfirm": "Are you sure you want to complete task {code}?",
        "completeSuccess": "Task completed successfully",
        "removeConfirm": "Are you sure you want to cancel task {code}?",
        "removeSuccess": "Task canceled successfully",
        "checkConfirm": "Are you sure you want to execute check outbound task {code}?",
        "checkSuccess": "Check outbound completed successfully",
        "pickConfirm": "Are you sure you want to execute pick outbound task {code}?",
        "pickSuccess": "Pick outbound completed successfully",
        "topSuccess": "Task pinned successfully",
        "actionFailed": "Task action failed",
        "autoRunEnabled": "Auto dispatch enabled",
        "autoRunPaused": "Auto dispatch paused",
        "autoRunFailed": "Failed to update auto dispatch settings",
        "detailLoadFailed": "Failed to load task items",
        "listTimeout": "Task list timed out and waiting has stopped",
        "autoRunTimeout": "Auto dispatch config timed out and waiting has stopped",
        "autoRunOnSuccess": "Auto dispatch enabled",
        "autoRunOffSuccess": "Auto dispatch paused",
        "autoRunUpdateFailed": "Failed to update auto dispatch settings",
        "itemsTimeout": "Task items timed out and waiting has stopped"
      }
    },
    "basicInfo": {
      "basStationArea": {
        "reportTitle": "Station Area Report",
        "entity": "Station Area",
        "buttons": {
          "add": "Add Station Area",
          "batchDelete": "Batch Delete"
        },
        "actions": {
          "add": "Add Station Area"
        },
        "placeholder": {
          "condition": "Enter station area name/code/remark",
          "timeStart": "Select start time",
          "timeEnd": "Select end time",
          "stationAreaName": "Enter station area name",
          "stationAreaId": "Enter station area code",
          "crossZoneArea": "Enter cross-zone area",
          "wcsData": "Enter WCS data",
          "containerType": "Enter container type",
          "barcode": "Enter barcode",
          "stationAlias": "Enter station alias",
          "memo": "Enter remark"
        },
        "search": {
          "condition": "Keyword",
          "conditionPlaceholder": "Enter station area name/code/remark",
          "timeStart": "Start Time",
          "timeStartPlaceholder": "Select start time",
          "timeEnd": "End Time",
          "timeEndPlaceholder": "Select end time",
          "stationAreaName": "Station Area Name",
          "stationAreaNamePlaceholder": "Enter station area name",
          "stationAreaId": "Station Area Code",
          "stationAreaIdPlaceholder": "Enter station area code",
          "type": "Station Type",
          "area": "Warehouse Area",
          "useStatus": "Usage Status",
          "inAble": "Inbound Allowed",
          "outAble": "Outbound Allowed",
          "isCrossZone": "Cross Zone",
          "crossZoneArea": "Cross-zone Area",
          "crossZoneAreaPlaceholder": "Enter cross-zone area",
          "isWcs": "WCS Enabled",
          "wcsData": "WCS Data",
          "wcsDataPlaceholder": "Enter WCS data",
          "containerType": "Container Type",
          "containerTypePlaceholder": "Enter container type",
          "autoTransfer": "Auto Transfer",
          "barcode": "Barcode",
          "barcodePlaceholder": "Enter barcode",
          "stationAlias": "Station Alias",
          "stationAliasPlaceholder": "Enter station alias",
          "status": "Status",
          "memo": "Remark",
          "memoPlaceholder": "Enter remark"
        },
        "type": {
          "smart": "Smart Station",
          "normal": "Normal Station"
        },
        "table": {
          "crossZoneArea": "Cross-zone Area",
          "inAble": "Inbound Allowed",
          "outAble": "Outbound Allowed",
          "isCrossZone": "Cross Zone"
        },
        "detail": {
          "title": "Station Area Detail",
          "baseInfo": "Basic Information",
          "auditInfo": "Audit Information",
          "stationAreaName": "Station Area Name",
          "stationAreaId": "Station Area Code",
          "type": "Station Type",
          "area": "Warehouse Area",
          "crossZoneArea": "Cross-zone Area",
          "containerType": "Container Type",
          "stationAlias": "Station Alias",
          "inAble": "Inbound Allowed",
          "outAble": "Outbound Allowed",
          "isCrossZone": "Cross Zone",
          "isWcs": "WCS Enabled",
          "autoTransfer": "Auto Transfer",
          "useStatus": "Usage Status",
          "barcode": "Barcode",
          "status": "Status",
          "wcsData": "WCS Data",
          "memo": "Remark",
          "createBy": "Created By",
          "createTime": "Created At",
          "updateBy": "Updated By",
          "updateTime": "Updated At"
        },
        "dialog": {
          "titleAdd": "Add Station Area",
          "titleEdit": "Edit Station Area",
          "stationAreaName": "Station Area Name",
          "stationAreaId": "Station Area Code",
          "type": "Station Type",
          "area": "Warehouse Area",
          "crossZoneArea": "Cross-zone Area",
          "containerType": "Container Type",
          "stationAlias": "Station Alias",
          "inAble": "Inbound Allowed",
          "outAble": "Outbound Allowed",
          "isCrossZone": "Cross Zone",
          "isWcs": "WCS Enabled",
          "autoTransfer": "Auto Transfer",
          "useStatus": "Usage Status",
          "wcsData": "WCS Data",
          "barcode": "Barcode",
          "status": "Status",
          "memo": "Remark",
          "validation": {
            "stationAreaName": "Please enter the station area name",
            "stationAreaId": "Please enter the station area code",
            "type": "Please select a station type",
            "area": "Please select a warehouse area",
            "containerType": "Please select container types",
            "stationAlias": "Please select station aliases"
          }
        },
        "messages": {
          "detailLoadFailed": "Failed to load station area detail",
          "detailTimeout": "Station area detail timed out and waiting has stopped",
          "stationAliasTimeout": "Station alias options timed out and waiting has stopped",
          "areaOptionsTimeout": "Area options timed out and waiting has stopped",
          "containerTypeTimeout": "Container type options timed out and waiting has stopped",
          "useStatusTimeout": "Usage status options timed out and waiting has stopped"
        }
      }
    },
    "system": {
      "role": {
        "entity": "Role",
        "reportTitle": "Role Report",
        "buttons": {
          "add": "Add Role"
        },
        "search": {
          "name": "Role Name",
          "namePlaceholder": "Enter role name",
          "code": "Role Code",
          "codePlaceholder": "Enter role code",
          "memo": "Remark",
          "memoPlaceholder": "Enter remark",
          "condition": "Keyword",
          "conditionPlaceholder": "Search by keyword",
          "status": "Status",
          "statusPlaceholder": "Select status"
        },
        "table": {
          "name": "Role Name",
          "code": "Role Code",
          "memo": "Remark",
          "status": "Status",
          "updateTime": "Updated At",
          "createTime": "Created At",
          "operation": "Operation"
        },
        "actions": {
          "scopeMenu": "Web Permissions",
          "scopePda": "PDA Permissions",
          "scopeMatnr": "Material Permissions",
          "scopeWarehouse": "Warehouse Permissions",
          "edit": "Edit Role",
          "delete": "Delete Role"
        },
        "scopes": {
          "menu": "Web Permissions",
          "pda": "PDA Permissions",
          "matnr": "Material Permissions",
          "warehouse": "Warehouse Permissions"
        },
        "dialog": {
          "addTitle": "Add Role",
          "editTitle": "Edit Role",
          "validationName": "Please enter the role name",
          "name": "Role Name",
          "namePlaceholder": "Enter role name",
          "code": "Role Code",
          "codePlaceholder": "Enter role code",
          "status": "Status",
          "statusPlaceholder": "Select status",
          "memo": "Remark",
          "memoPlaceholder": "Enter remark"
        },
        "permission": {
          "title": "Role Permissions",
          "currentRole": "Current Role: ",
          "unselected": "No role selected",
          "selectAll": "Select All",
          "clear": "Clear",
          "saveCurrent": "Save Current Permissions",
          "searchPlaceholder": "Search permission tree",
          "authButton": "Button",
          "scopeLoadTimeout": "{title} loading timed out and waiting has stopped",
          "scopeLoadFailed": "Failed to load {title}",
          "saveSuccess": "Permissions saved successfully",
          "saveFailed": "Failed to save permissions"
        }
      },
      "menu": {
        "title": "Menu Management",
        "entities": {
          "permission": "Permission"
        },
        "buttons": {
          "add": "Add Menu"
        },
        "actions": {
          "addAuth": "Add Permission",
          "expand": "Expand",
          "collapse": "Collapse"
        },
        "types": {
          "button": "Button",
          "directory": "Directory",
          "menu": "Menu"
        },
        "search": {
          "name": "Menu Name",
          "route": "Route"
        },
        "messages": {
          "menuSelfParent": "Parent menu cannot be the current menu",
          "loadFailed": "Failed to load menu",
          "loadTimeout": "Menu loading timed out and waiting has stopped",
          "submitFailed": "Submit failed",
          "authCount": "{count} permission keys",
          "deleteMenuConfirm": "Are you sure you want to delete menu \"{title}\"? This action cannot be undone",
          "deleteAuthConfirm": "Are you sure you want to delete permission \"{title}\"? This action cannot be undone"
        },
        "form": {
          "typeMenu": "Menu",
          "typeButton": "Button",
          "titleAddMenu": "Create Menu",
          "titleEditMenu": "Edit Menu",
          "titleAddButton": "Create Permission",
          "titleEditButton": "Edit Permission",
          "menuType": "Menu Type",
          "parentId": "Parent Menu",
          "nameMenu": "Menu Name",
          "nameButton": "Permission Name",
          "route": "Route",
          "component": "Component Key",
          "authority": "Authority",
          "icon": "Icon",
          "sort": "Sort",
          "status": "Status",
          "memo": "Remark",
          "placeholderParent": "Please select a parent menu",
          "placeholderMenuName": "Please enter a menu name",
          "placeholderButtonName": "Please enter a permission name",
          "placeholderRoute": "Please enter a route",
          "placeholderComponent": "Please enter a component key",
          "placeholderAuthority": "Please enter an authority",
          "placeholderIcon": "Please enter an icon name",
          "placeholderStatus": "Please select a status",
          "placeholderMemo": "Please enter a remark",
          "validationMenuName": "Please enter a menu name",
          "validationButtonName": "Please enter a permission name",
          "validationRoute": "Please enter a route",
          "validationAuthority": "Please enter an authority"
        }
      }
    }
  }
}
rsf-design/src/locales/langs/zh.json
@@ -36,7 +36,87 @@
    "tips": "提示",
    "cancel": "取消",
    "confirm": "确定",
    "logOutTips": "您是否要退出登录?"
    "logOutTips": "您是否要退出登录?",
    "count": "共 {count} 条",
    "listSeparator": "、",
    "actions": {
      "search": "查询",
      "reset": "重置",
      "refresh": "刷新",
      "add": "新增",
      "batchDelete": "批量删除",
      "edit": "编辑",
      "delete": "删除",
      "detail": "详情",
      "items": "明细",
      "print": "打印",
      "export": "导出",
      "exporting": "导出中...",
      "close": "关闭",
      "complete": "完成",
      "expand": "展开",
      "collapse": "收起",
      "viewAll": "查看全部",
      "save": "保存",
      "submit": "提交"
    },
    "status": {
      "enabled": "启用",
      "disabled": "禁用",
      "normal": "正常",
      "frozen": "冻结",
      "unknown": "未知",
      "yes": "是",
      "no": "否"
    },
    "placeholder": {
      "empty": "--"
    }
  },
  "crud": {
    "messages": {
      "createSuccess": "新增成功",
      "updateSuccess": "修改成功",
      "deleteSuccess": "删除成功",
      "batchDeleteSuccess": "批量删除成功",
      "submitFailed": "提交失败",
      "deleteFailed": "删除失败",
      "batchDeleteFailed": "批量删除失败",
      "exportSuccess": "导出成功",
      "exportFailed": "导出失败",
      "exportFailedWithStatus": "导出失败({status})",
      "printFailed": "打印失败",
      "loadFailed": "加载失败"
    },
    "confirm": {
      "deleteTitle": "删除确认",
      "deleteMessage": "确定要删除{entity}「{label}」吗?",
      "batchDeleteTitle": "批量删除确认",
      "batchDeleteMessage": "确定要批量删除选中的 {count} 个{entity}吗?"
    }
  },
  "print": {
    "previewTitle": "打印预览",
    "defaultReportTitle": "报表",
    "borderOn": "边框开",
    "borderOff": "边框关",
    "portrait": "竖版",
    "landscape": "横版",
    "reportDate": "报表日期",
    "operator": "打印人",
    "printedAt": "打印时间",
    "count": "记录数",
    "previewLimited": "预览仅展示前 {previewCount} 条,点击打印将输出全部 {totalCount} 条数据。",
    "noData": "暂无打印数据"
  },
  "table": {
    "index": "序号",
    "unit": "单位",
    "id": "ID",
    "wcs": "WCS",
    "source": "来源",
    "supplier": "供应商",
    "supplierBatch": "供应商批次"
  },
  "search": {
    "placeholder": "搜索页面",
@@ -451,6 +531,7 @@
      "assistantRole": "AI",
      "thinking": "思考中...",
      "inputPlaceholder": "输入你的问题,按 Enter 发送,Shift + Enter 换行",
      "inputHotkeyHint": "Enter 发送,Shift + Enter 换行",
      "clearInput": "清空输入",
      "stop": "停止",
      "send": "发送",
@@ -473,7 +554,8 @@
        "recentMetric": "Recent: {value}",
      "elapsedMetric": "耗时: {value} ms",
      "firstTokenMetric": "首包: {value} ms",
      "tokenMetric": "Tokens: prompt {prompt} / completion {completion} / total {total}"
      "tokenMetric": "Tokens: prompt {prompt} / completion {completion} / total {total}",
      "streaming": "生成中"
    }
  },
  "table": {
@@ -500,8 +582,834 @@
      "expand": "展开",
      "index": "序号"
    },
    "index": "序号",
    "id": "ID",
    "operation": "操作",
    "status": "状态",
    "type": "类型",
    "source": "来源",
    "supplier": "供应商",
    "supplierBatch": "供应商批次",
    "remark": "备注",
    "updateBy": "更新人",
    "updateTime": "更新时间",
    "createTime": "创建时间",
    "unit": "单位",
    "batch": "批次",
    "quantity": "数量",
    "materialCode": "物料编码",
    "materialName": "物料名称",
    "wcs": "WCS",
    "menuType": "菜单类型",
    "iconPreview": "图标预览",
    "componentKey": "组件标识",
    "permissionKey": "权限标识",
    "sort": "排序",
    "zebra": "斑马纹",
    "border": "边框",
    "headerBackground": "表头背景"
    "headerBackground": "表头背景",
    "topLevelMenu": "顶级菜单",
    "emptyText": "暂无数据"
  },
  "message": {
    "requestTimeoutStopped": "请求超时,已停止等待",
    "exportTimeoutStopped": "导出请求超时,已停止等待",
    "printTimeoutStopped": "打印数据加载超时,已停止等待",
    "exportInvalidDataType": "数据必须是数组格式",
    "exportNoData": "没有可导出的数据",
    "exportExceedMaxRows": "数据行数超过限制({maxRows}行)",
    "exportExcelFailed": "Excel 导出失败: {message}",
    "exportSuccessWithCount": "成功导出 {count} 条数据",
    "exportFailedUnknown": "导出失败: {message}",
    "exportWorkbookSubject": "数据导出",
    "exportWorkbookCompany": "系统导出",
    "exportWorkbookCategory": "数据",
    "exportWorkbookKeywords": "excel,export,data",
    "exportWorkbookComments": "由系统自动生成"
  },
  "pages": {
    "system": {
      "role": {
        "entity": "角色",
        "reportTitle": "角色管理报表",
        "buttons": {
          "add": "新增角色"
        },
        "search": {
          "name": "角色名称",
          "namePlaceholder": "请输入角色名称",
          "code": "角色编码",
          "codePlaceholder": "请输入角色编码",
          "memo": "备注",
          "memoPlaceholder": "请输入备注",
          "condition": "关键字",
          "conditionPlaceholder": "输入关键字搜索",
          "status": "状态",
          "statusPlaceholder": "请选择状态"
        },
        "table": {
          "name": "角色名称",
          "code": "角色编码",
          "memo": "备注",
          "status": "状态",
          "updateTime": "更新时间",
          "createTime": "创建时间",
          "operation": "操作"
        },
        "actions": {
          "scopeMenu": "网页权限",
          "scopePda": "PDA权限",
          "scopeMatnr": "物料权限",
          "scopeWarehouse": "仓库权限",
          "edit": "编辑角色",
          "delete": "删除角色"
        },
        "scopes": {
          "menu": "网页权限",
          "pda": "PDA权限",
          "matnr": "物料权限",
          "warehouse": "仓库权限"
        },
        "dialog": {
          "addTitle": "新增角色",
          "editTitle": "编辑角色",
          "validationName": "请输入角色名称",
          "name": "角色名称",
          "namePlaceholder": "请输入角色名称",
          "code": "角色编码",
          "codePlaceholder": "请输入角色编码",
          "status": "状态",
          "statusPlaceholder": "请选择状态",
          "memo": "备注",
          "memoPlaceholder": "请输入备注"
        },
        "permission": {
          "title": "角色权限",
          "currentRole": "当前角色:",
          "unselected": "未选择角色",
          "selectAll": "全选",
          "clear": "清空",
          "saveCurrent": "保存当前权限",
          "searchPlaceholder": "搜索权限树",
          "authButton": "按钮",
          "scopeLoadTimeout": "{title}加载超时,已停止等待",
          "scopeLoadFailed": "加载{title}失败",
          "saveSuccess": "权限保存成功",
          "saveFailed": "权限保存失败"
        }
      },
      "menu": {
        "title": "菜单管理",
        "addMenu": "添加菜单",
        "menuName": "菜单名称",
        "route": "路由地址",
        "iconPreview": "图标预览",
        "menuType": "菜单类型",
        "componentKey": "组件标识",
        "authority": "权限标识",
        "sort": "排序",
        "status": "状态",
        "memo": "备注",
        "operation": "操作",
        "types": {
          "button": "按钮",
          "directory": "目录",
          "menu": "菜单"
        },
        "addPermission": "新增权限",
        "deleteMenuMessage": "确定要删除菜单「{label}」吗?删除后无法恢复",
        "deleteAuthMessage": "确定要删除权限「{label}」吗?删除后无法恢复",
        "selfParentError": "上级菜单不能选择当前菜单"
      }
    },
    "orders": {
      "asnOrder": {
        "reportTitle": "入库通知单报表",
        "entity": "入库通知单",
        "buttons": {
          "createByPo": "按PO建单"
        },
        "search": {
          "condition": "关键字",
          "conditionPlaceholder": "请输入 ASN 单号/PO 单号/供应商",
          "code": "ASN单号",
          "codePlaceholder": "请输入 ASN 单号",
          "poCode": "PO单号",
          "poCodePlaceholder": "请输入 PO 单号",
          "wkType": "业务类型",
          "wkTypePlaceholder": "请输入业务类型",
          "exceStatus": "单据状态",
          "supplierName": "供应商",
          "supplierPlaceholder": "请输入供应商",
          "purchaseUserName": "采购员",
          "purchaseUserPlaceholder": "请输入采购员"
        },
        "placeholder": {
          "condition": "请输入 ASN 单号/PO 单号/供应商",
          "code": "请输入 ASN 单号",
          "poCode": "请输入 PO 单号",
          "wkType": "请输入业务类型",
          "supplierName": "请输入供应商",
          "purchaseUserName": "请输入采购员"
        },
        "status": {
          "pending": "未执行",
          "running": "执行中",
          "receiving": "收货完成",
          "taskRunning": "任务执行中",
          "completed": "已完成",
          "cancelled": "取消",
          "closed": "已关闭"
        },
        "actions": {
          "view": "查看详情",
          "items": "收货明细",
          "print": "打印",
          "complete": "完成"
        },
        "detail": {
          "title": "入库通知单详情",
          "baseInfo": "基础信息",
          "items": "单据明细",
          "asnCode": "ASN单号",
          "poCode": "PO单号",
          "wkType": "业务类型",
          "orderType": "单据类型",
          "status": "单据状态",
          "purchaseOrg": "采购组织",
          "purchaseUser": "采购员",
          "supplier": "供应商",
          "anfme": "应收数量",
          "qty": "已收数量",
          "updateTime": "更新时间",
          "createTime": "创建时间",
          "memo": "备注",
          "count": "共 {count} 条",
          "completeTitle": "完成确认",
          "completeConfirm": "确定完成入库通知单 {code} 吗?",
          "completeSuccess": "入库通知单已完成",
          "actionFailed": "入库通知单操作失败",
          "detailTimeout": "入库通知单明细加载超时,已停止等待",
          "itemsTimeout": "入库通知单明细加载超时,已停止等待"
        },
        "createByPoDialog": {
          "title": "按PO建单",
          "purchaseList": "可建单 PO 列表",
          "purchasePreview": "PO 明细预览",
          "purchaseSelected": "当前选择:{code}",
          "purchaseEmpty": "请先在左侧选择一个 PO 单",
          "purchaseGenerateHint": "将按 PO {code} 的 {count} 条明细生成入库通知单",
          "purchaseGenerateEmpty": "请选择一个可建单的 PO 单",
          "generate": "生成入库通知单",
          "refreshItems": "刷新明细",
          "messages": {
            "purchaseItemsTimeout": "PO 明细加载超时,已停止等待",
            "purchaseItemsAllTimeout": "PO 全量明细加载超时,已停止等待",
            "purchaseRequired": "请先选择一个 PO 单",
            "purchaseItemsEmpty": "当前 PO 单没有可建单明细",
            "createByPoSuccess": "已根据 PO 单生成入库通知单",
            "createByPoFailed": "按 PO 建单失败"
          },
          "search": {
            "condition": "关键字",
            "conditionPlaceholder": "请输入 PO 单号/来源/供应商",
            "code": "PO单号",
            "codePlaceholder": "请输入 PO 单号",
            "source": "来源",
            "sourcePlaceholder": "请输入来源",
            "supplierName": "供应商",
            "supplierNamePlaceholder": "请输入供应商"
          }
        },
        "table": {
          "poItemId": "PO行号",
          "expectedQty": "应收数量",
          "receivedQty": "已收数量",
          "remainingQty": "可建单数量",
          "poStatus": "PO状态",
          "purchaseQty": "采购数量",
          "generatedQty": "已生成ASN数量",
          "receivedQtyTotal": "收货数量"
        }
      },
      "delivery": {
        "reportTitle": "DO单报表",
        "detailReportTitle": "DO单明细报表",
        "entity": "DO单",
        "search": {
          "condition": "关键字",
          "conditionPlaceholder": "请输入单号/ERP主单标识/平台单号",
          "code": "单号",
          "codePlaceholder": "请输入单号",
          "platId": "ERP主单标识",
          "platIdPlaceholder": "请输入ERP主单标识",
          "type": "单据类型",
          "typePlaceholder": "请输入单据类型",
          "wkType": "业务类型",
          "wkTypePlaceholder": "请输入业务类型",
          "source": "单据来源",
          "sourcePlaceholder": "请输入单据来源",
          "exceStatus": "执行状态",
          "exceStatusPlaceholder": "请输入执行状态",
          "memo": "备注",
          "memoPlaceholder": "请输入备注"
        },
        "placeholder": {
          "condition": "请输入单号/ERP主单标识/平台单号",
          "code": "请输入单号",
          "platId": "请输入ERP主单标识",
          "type": "请输入单据类型",
          "wkType": "请输入业务类型",
          "source": "请输入单据来源",
          "exceStatus": "请输入执行状态",
          "memo": "请输入备注"
        },
        "status": {
          "normal": "正常",
          "disabled": "禁用",
          "pending": "未执行",
          "running": "执行中",
          "partial": "部分完成",
          "completed": "已完成"
        },
        "actions": {
          "view": "查看详情",
          "items": "明细",
          "delete": "删除"
        },
        "detail": {
          "title": "交接单详情",
          "baseInfo": "基础信息",
          "auditInfo": "审计信息",
          "items": "交接单明细",
          "code": "交接单号",
          "platId": "ERP主单标识",
          "platCode": "平台单号",
          "type": "单据类型",
          "wkType": "业务类型",
          "source": "单据来源",
          "anfme": "应收数量",
          "qty": "实收数量",
          "workQty": "执行中数量",
          "status": "状态",
          "exceStatus": "执行状态",
          "memo": "备注",
          "startTime": "计划出库时间",
          "endTime": "计划出库结束时间",
          "createBy": "创建人",
          "createTime": "创建时间",
          "updateBy": "更新人",
          "updateTime": "更新时间",
          "count": "共 {count} 条"
        },
        "table": {
          "deliveryCode": "交接单号",
          "platCode": "平台单号",
          "platItemId": "平台行号",
          "matnrCode": "物料编码",
          "maktx": "物料名称",
          "fieldsIndex": "动态字段索引",
          "anfme": "数量",
          "workQty": "执行数量",
          "qty": "已出数量",
          "startTime": "计划出库时间",
          "endTime": "计划出库结束时间",
          "nromQty": "标准包装",
          "printQty": "打印数量",
          "splrName": "供应商名称",
          "splrCode": "供应商编码",
          "splrBatch": "供应商批次"
        },
        "messages": {
          "itemsTimeout": "DO单明细加载超时,已停止等待",
          "detailTimeout": "DO单详情加载超时,已停止等待",
          "detailLoadFailed": "DO单详情加载失败"
        }
      },
      "transfer": {
        "reportTitle": "调拨单报表",
        "entity": "调拨单",
        "buttons": {
          "add": "新增调拨单",
          "publish": "下发执行"
        },
        "search": {
          "condition": "关键字",
          "conditionPlaceholder": "请输入单号/备注/仓库/库区",
          "code": "调拨单号",
          "codePlaceholder": "请输入调拨单号",
          "type": "调拨类型",
          "source": "来源",
          "exceStatus": "执行状态",
          "orgWareName": "源仓库",
          "orgWareNamePlaceholder": "请输入源仓库",
          "tarWareName": "目标仓库",
          "tarWareNamePlaceholder": "请输入目标仓库",
          "orgAreaName": "源库区",
          "orgAreaNamePlaceholder": "请输入源库区",
          "tarAreaName": "目标库区",
          "tarAreaNamePlaceholder": "请输入目标库区",
          "status": "状态",
          "memo": "备注",
          "memoPlaceholder": "请输入备注"
        },
        "status": {
          "sourceErp": "ERP系统",
          "sourceWms": "WMS系统生成",
          "sourceExcel": "EXCEL导入",
          "sourceQms": "QMS系统",
          "pending": "未执行",
          "running": "执行中",
          "completed": "执行完成",
          "normal": "正常",
          "frozen": "冻结"
        },
        "actions": {
          "add": "新增调拨单",
          "view": "查看详情",
          "items": "明细",
          "edit": "编辑",
          "publish": "下发执行",
          "delete": "删除"
        },
        "placeholder": {
          "condition": "请输入单号/备注/仓库/库区",
          "code": "请输入调拨单号",
          "orgWareName": "请输入源仓库",
          "tarWareName": "请输入目标仓库",
          "orgAreaName": "请输入源库区",
          "tarAreaName": "请输入目标库区",
          "memo": "请输入备注"
        },
        "detail": {
          "title": "调拨单详情",
          "baseInfo": "基础信息",
          "auditInfo": "审计信息",
          "source": "来源",
          "orgWareName": "源仓库",
          "tarWareName": "目标仓库",
          "orgAreaName": "源库区",
          "tarAreaName": "目标库区",
          "memo": "备注",
          "createBy": "创建人",
          "createTime": "创建时间",
          "updateBy": "更新人",
          "updateTime": "更新时间",
          "relatedOrders": "关联单据",
          "relatedCode": "关联单号",
          "code": "调拨单号",
          "type": "调拨类型",
          "wkType": "业务类型",
          "exceStatus": "执行状态",
          "status": "状态",
          "workQty": "执行中数量",
          "qty": "已完成数量",
          "stationId": "站点编号",
          "businessTime": "业务时间"
        },
        "dialog": {
          "titleAdd": "新增调拨单",
          "titleEdit": "编辑调拨单",
          "tip": "调拨单号由系统生成,新增时只需维护调拨类型、源/目标库区和备注。",
          "code": "调拨单号",
          "type": "调拨类型",
          "orgAreaId": "源库区",
          "tarAreaId": "目标库区",
          "status": "状态",
          "memo": "备注",
          "placeholderCode": "保存后自动生成",
          "placeholderType": "请选择调拨类型",
          "placeholderOrgAreaId": "请选择源库区",
          "placeholderTarAreaId": "请选择目标库区",
          "placeholderStatus": "请选择状态",
          "placeholderMemo": "请输入备注",
          "validation": {
            "type": "请选择调拨类型",
            "orgAreaId": "请选择源库区",
            "tarAreaId": "请选择目标库区"
          }
        },
        "messages": {
          "detailTimeout": "调拨单详情加载超时,已停止等待",
          "ordersTimeout": "调拨单明细加载超时,已停止等待",
          "ordersLoadFailed": "调拨单明细加载失败",
          "detailLoadFailed": "调拨单详情加载失败",
          "publishConfirm": "确定要下发调拨单「{code}」吗?",
          "publishTitle": "下发确认",
          "publishSuccess": "下发执行成功",
          "publishFailed": "下发执行失败",
          "typeOptionsTimeout": "调拨类型选项加载超时,已停止等待",
          "areaOptionsTimeout": "库区选项加载超时,已停止等待"
        }
      }
    },
    "task": {
      "title": "任务管理",
      "buttons": {
        "autoRun": "自动下发任务",
        "pauseAutoRun": "暂停自动下发"
      },
      "placeholder": {
        "condition": "请输入任务号/库位/托盘码",
        "taskCode": "请输入任务号",
        "orgLoc": "请输入源库位",
        "targLoc": "请输入目标库位",
        "barcode": "请输入托盘码"
      },
      "search": {
        "condition": "关键字",
        "conditionPlaceholder": "请输入任务号/库位/托盘码",
        "taskCode": "任务号",
        "taskCodePlaceholder": "请输入任务号",
        "orgLoc": "源库位",
        "orgLocPlaceholder": "请输入源库位",
        "targLoc": "目标库位",
        "targLocPlaceholder": "请输入目标库位",
        "barcode": "托盘码",
        "barcodePlaceholder": "请输入托盘码"
      },
      "actions": {
        "view": "查看详情",
        "flowStep": "流程步骤",
        "complete": "完成任务",
        "check": "盘点出库",
        "pick": "拣料出库",
        "top": "任务置顶",
        "remove": "取消任务"
      },
      "detail": {
        "title": "任务详情",
        "taskCode": "任务号",
        "baseInfo": "任务基础信息",
        "pathInfo": "执行路径",
        "items": "任务明细",
        "itemsHint": "查看当前任务关联的业务单据、物料和执行记录",
        "flowStep": "流程步骤",
        "taskStatus": "任务状态",
        "taskType": "任务类型",
        "warehType": "设备类型",
        "priority": "优先级",
        "status": "状态",
        "robotCode": "机器人编码",
        "createTime": "创建时间",
        "updateTime": "更新时间",
        "memo": "备注",
        "orgLoc": "源库位",
        "orgSite": "源站点",
        "targLoc": "目标库位",
        "targSite": "目标站点",
        "barcode": "托盘码"
      },
      "expand": {
        "title": "任务明细",
        "empty": "暂无任务明细",
        "orderType": "单据类型",
        "wkType": "业务类型",
        "platWorkCode": "工单号",
        "platItemId": "行号",
        "anfme": "数量"
      },
      "flowStepDialog": {
        "title": "流程步骤",
        "currentTask": "当前任务",
        "flowInstanceNo": "流程实例号",
        "stepCode": "步骤编码",
        "stepName": "步骤名称",
        "stepType": "步骤类型",
        "executeResult": "执行结果",
        "startTime": "开始时间",
        "endTime": "结束时间",
        "timeout": "流程步骤加载超时,已停止等待"
      },
      "messages": {
        "completeConfirm": "确定完成任务 {code} 吗?",
        "completeSuccess": "任务完成成功",
        "removeConfirm": "确定取消任务 {code} 吗?",
        "removeSuccess": "任务取消成功",
        "checkConfirm": "确定执行盘点出库任务 {code} 吗?",
        "checkSuccess": "盘点出库成功",
        "pickConfirm": "确定执行拣料出库任务 {code} 吗?",
        "pickSuccess": "拣料出库成功",
        "topSuccess": "任务置顶成功",
        "actionFailed": "任务操作失败",
        "autoRunEnabled": "已开启自动下发任务",
        "autoRunPaused": "已暂停自动下发任务",
        "autoRunFailed": "自动下发配置更新失败",
        "detailLoadFailed": "任务明细加载失败",
        "listTimeout": "任务列表加载超时,已停止等待",
        "autoRunTimeout": "自动下发配置加载超时,已停止等待",
        "autoRunOnSuccess": "已开启自动下发任务",
        "autoRunOffSuccess": "已暂停自动下发任务",
        "autoRunUpdateFailed": "自动下发配置更新失败",
        "itemsTimeout": "任务明细加载超时,已停止等待"
      }
    },
    "basicInfo": {
      "basStationArea": {
        "reportTitle": "站点区域报表",
        "entity": "站点区域",
        "buttons": {
          "add": "新增站点区域",
          "batchDelete": "批量删除"
        },
        "actions": {
          "add": "新增站点区域"
        },
        "placeholder": {
          "condition": "请输入站点区域名称/编号/备注",
          "timeStart": "请选择开始时间",
          "timeEnd": "请选择结束时间",
          "stationAreaName": "请输入站点区域名称",
          "stationAreaId": "请输入站点区域编号",
          "crossZoneArea": "请输入跨区区域",
          "wcsData": "请输入WCS数据",
          "containerType": "请输入容器类型",
          "barcode": "请输入条码",
          "stationAlias": "请输入站点别名",
          "memo": "请输入备注"
        },
        "search": {
          "condition": "关键字",
          "conditionPlaceholder": "请输入站点区域名称/编号/备注",
          "timeStart": "开始时间",
          "timeStartPlaceholder": "请选择开始时间",
          "timeEnd": "结束时间",
          "timeEndPlaceholder": "请选择结束时间",
          "stationAreaName": "站点区域名称",
          "stationAreaNamePlaceholder": "请输入站点区域名称",
          "stationAreaId": "站点区域编号",
          "stationAreaIdPlaceholder": "请输入站点区域编号",
          "type": "站点类型",
          "area": "所属库区",
          "useStatus": "使用状态",
          "inAble": "可入",
          "outAble": "可出",
          "isCrossZone": "是否跨区",
          "crossZoneArea": "跨区区域",
          "crossZoneAreaPlaceholder": "请输入跨区区域",
          "isWcs": "是否WCS",
          "wcsData": "WCS数据",
          "wcsDataPlaceholder": "请输入WCS数据",
          "containerType": "容器类型",
          "containerTypePlaceholder": "请输入容器类型",
          "autoTransfer": "自动调拨",
          "barcode": "条码",
          "barcodePlaceholder": "请输入条码",
          "stationAlias": "站点别名",
          "stationAliasPlaceholder": "请输入站点别名",
          "status": "状态",
          "memo": "备注",
          "memoPlaceholder": "请输入备注"
        },
        "type": {
          "smart": "智能站点",
          "normal": "普通站点"
        },
        "table": {
          "crossZoneArea": "跨区区域",
          "inAble": "可入",
          "outAble": "可出",
          "isCrossZone": "是否跨区"
        },
        "detail": {
          "title": "站点区域详情",
          "baseInfo": "基础信息",
          "auditInfo": "审计信息",
          "stationAreaName": "站点区域名称",
          "stationAreaId": "站点区域编号",
          "type": "站点类型",
          "area": "所属库区",
          "crossZoneArea": "可跨区库区",
          "containerType": "容器类型",
          "stationAlias": "站点别名",
          "inAble": "可入",
          "outAble": "可出",
          "isCrossZone": "是否跨区",
          "isWcs": "是否WCS",
          "autoTransfer": "自动调拨",
          "useStatus": "使用状态",
          "barcode": "条码",
          "status": "状态",
          "wcsData": "WCS数据",
          "memo": "备注",
          "createBy": "创建人",
          "createTime": "创建时间",
          "updateBy": "更新人",
          "updateTime": "更新时间"
        },
        "dialog": {
          "titleAdd": "新增站点区域",
          "titleEdit": "编辑站点区域",
          "stationAreaName": "站点区域名称",
          "stationAreaId": "站点区域编号",
          "type": "站点类型",
          "area": "所属库区",
          "crossZoneArea": "可跨区库区",
          "containerType": "容器类型",
          "stationAlias": "站点别名",
          "inAble": "可入",
          "outAble": "可出",
          "isCrossZone": "是否跨区",
          "isWcs": "是否WCS",
          "autoTransfer": "自动调拨",
          "useStatus": "使用状态",
          "wcsData": "WCS数据",
          "barcode": "条码",
          "status": "状态",
          "memo": "备注",
          "validation": {
            "stationAreaName": "请输入站点区域名称",
            "stationAreaId": "请输入站点区域编号",
            "type": "请选择站点类型",
            "area": "请选择所属库区",
            "containerType": "请选择容器类型",
            "stationAlias": "请选择站点别名"
          }
        },
        "messages": {
          "detailLoadFailed": "获取站点区域详情失败",
          "detailTimeout": "站点区域详情加载超时,已停止等待",
          "stationAliasTimeout": "站点别名选项加载超时,已停止等待",
          "areaOptionsTimeout": "库区选项加载超时,已停止等待",
          "containerTypeTimeout": "容器类型选项加载超时,已停止等待",
          "useStatusTimeout": "使用状态选项加载超时,已停止等待"
        }
      }
    },
    "system": {
      "role": {
        "entity": "角色",
        "reportTitle": "角色管理报表",
        "buttons": {
          "add": "新增角色"
        },
        "search": {
          "name": "角色名称",
          "namePlaceholder": "请输入角色名称",
          "code": "角色编码",
          "codePlaceholder": "请输入角色编码",
          "memo": "备注",
          "memoPlaceholder": "请输入备注",
          "condition": "关键字",
          "conditionPlaceholder": "输入关键字搜索",
          "status": "状态",
          "statusPlaceholder": "请选择状态"
        },
        "table": {
          "name": "角色名称",
          "code": "角色编码",
          "memo": "备注",
          "status": "状态",
          "updateTime": "更新时间",
          "createTime": "创建时间",
          "operation": "操作"
        },
        "actions": {
          "scopeMenu": "网页权限",
          "scopePda": "PDA权限",
          "scopeMatnr": "物料权限",
          "scopeWarehouse": "仓库权限",
          "edit": "编辑角色",
          "delete": "删除角色"
        },
        "scopes": {
          "menu": "网页权限",
          "pda": "PDA权限",
          "matnr": "物料权限",
          "warehouse": "仓库权限"
        },
        "dialog": {
          "addTitle": "新增角色",
          "editTitle": "编辑角色",
          "validationName": "请输入角色名称",
          "name": "角色名称",
          "namePlaceholder": "请输入角色名称",
          "code": "角色编码",
          "codePlaceholder": "请输入角色编码",
          "status": "状态",
          "statusPlaceholder": "请选择状态",
          "memo": "备注",
          "memoPlaceholder": "请输入备注"
        },
        "permission": {
          "title": "角色权限",
          "currentRole": "当前角色:",
          "unselected": "未选择角色",
          "selectAll": "全选",
          "clear": "清空",
          "saveCurrent": "保存当前权限",
          "searchPlaceholder": "搜索权限树",
          "authButton": "按钮",
          "scopeLoadTimeout": "{title}加载超时,已停止等待",
          "scopeLoadFailed": "加载{title}失败",
          "saveSuccess": "权限保存成功",
          "saveFailed": "权限保存失败"
        }
      },
      "menu": {
        "title": "菜单管理",
        "entities": {
          "permission": "权限"
        },
        "buttons": {
          "add": "添加菜单"
        },
        "actions": {
          "addAuth": "新增权限",
          "expand": "展开",
          "collapse": "收起"
        },
        "types": {
          "button": "按钮",
          "directory": "目录",
          "menu": "菜单"
        },
        "search": {
          "name": "菜单名称",
          "route": "路由地址"
        },
        "messages": {
          "menuSelfParent": "上级菜单不能选择当前菜单",
          "loadFailed": "获取菜单失败",
          "loadTimeout": "菜单加载超时,已停止等待",
          "submitFailed": "提交失败",
          "authCount": "{count} 个权限标识",
          "deleteMenuConfirm": "确定要删除菜单「{title}」吗?删除后无法恢复",
          "deleteAuthConfirm": "确定要删除权限「{title}」吗?删除后无法恢复"
        },
        "form": {
          "typeMenu": "菜单",
          "typeButton": "按钮",
          "titleAddMenu": "新建菜单",
          "titleEditMenu": "编辑菜单",
          "titleAddButton": "新建按钮",
          "titleEditButton": "编辑按钮",
          "menuType": "菜单类型",
          "parentId": "上级菜单",
          "nameMenu": "菜单名称",
          "nameButton": "权限名称",
          "route": "路由地址",
          "component": "组件标识",
          "authority": "权限标识",
          "icon": "图标",
          "sort": "排序",
          "status": "状态",
          "memo": "备注",
          "placeholderParent": "请选择上级菜单",
          "placeholderMenuName": "请输入菜单名称",
          "placeholderButtonName": "请输入权限名称",
          "placeholderRoute": "请输入路由地址",
          "placeholderComponent": "请输入组件标识",
          "placeholderAuthority": "请输入权限标识",
          "placeholderIcon": "请输入图标名称",
          "placeholderStatus": "请选择状态",
          "placeholderMemo": "请输入备注",
          "validationMenuName": "请输入菜单名称",
          "validationButtonName": "请输入权限名称",
          "validationRoute": "请输入路由地址",
          "validationAuthority": "请输入权限标识"
        }
      }
    }
  }
}
rsf-design/src/router/adapters/backendMenuAdapter.js
@@ -231,7 +231,7 @@
function buildMeta(node) {
  const meta = {
    title: normalizeTitle(node.name || node.meta?.title || '')
    title: normalizeTitle(node.name || node.meta?.title || '', node.component)
  }
  const metaSource = node.meta && typeof node.meta === 'object' ? node.meta : node
  const supportedKeys = [
@@ -255,11 +255,11 @@
  return meta
}
function normalizeTitle(title) {
function normalizeTitle(title, componentKey) {
  if (typeof title !== 'string') {
    return ''
    return resolveBackendMenuTitle('', componentKey)
  }
  return resolveBackendMenuTitle(title)
  return resolveBackendMenuTitle(title, componentKey)
}
function normalizeIcon(icon) {
rsf-design/src/router/modules/system.js
@@ -52,9 +52,9 @@
        keepAlive: true,
        roles: ['R_SUPER'],
        authList: [
          { title: '新增', authMark: 'add' },
          { title: '编辑', authMark: 'edit' },
          { title: '删除', authMark: 'delete' }
          { title: 'common.actions.add', authMark: 'add' },
          { title: 'common.actions.edit', authMark: 'edit' },
          { title: 'common.actions.delete', authMark: 'delete' }
        ]
      }
    }
rsf-design/src/utils/backend-menu-title.js
@@ -125,31 +125,89 @@
  'menu.whMat': '库区物料关系'
}
export function resolveBackendMenuTitle(title) {
  if (typeof title !== 'string') {
const DASHBOARD_MENU_KEYS = new Set(['menus.dashboard.title', 'menus.dashboard.console'])
const BACKEND_MENU_TITLE_KEY_MAP = Object.entries(LEGACY_BACKEND_MENU_TITLES).reduce(
  (accumulator, [key, value]) => {
    accumulator[key] = key
    accumulator[value] = key
    return accumulator
  },
  {
    '仪表盘': 'menus.dashboard.title',
    '工作台': 'menus.dashboard.console',
    'menus.dashboard.title': 'menus.dashboard.title',
    'menus.dashboard.console': 'menus.dashboard.console'
  }
)
function normalizeComponentMenuKey(componentKey) {
  if (typeof componentKey !== 'string') {
    return ''
  }
  const trimmedTitle = title.trim()
  if (!trimmedTitle) {
  const normalizedComponentKey = componentKey.trim()
  if (!normalizedComponentKey) {
    return ''
  }
  if (LEGACY_BACKEND_MENU_TITLES[trimmedTitle]) {
    return LEGACY_BACKEND_MENU_TITLES[trimmedTitle]
  if (normalizedComponentKey === 'console') {
    return 'menus.dashboard.console'
  }
  if (trimmedTitle.startsWith('menus.')) {
    const legacyMenuKey = `menu.${trimmedTitle.slice('menus.'.length)}`
    if (LEGACY_BACKEND_MENU_TITLES[legacyMenuKey]) {
      return LEGACY_BACKEND_MENU_TITLES[legacyMenuKey]
    }
    return trimmedTitle.split('.').pop() || trimmedTitle
  }
  if (trimmedTitle.startsWith('menu.')) {
    return trimmedTitle.split('.').pop() || trimmedTitle
  }
  return trimmedTitle
  return `menu.${normalizedComponentKey}`
}
function normalizeMenuKey(menuKey) {
  if (!menuKey) {
    return ''
  }
  if (DASHBOARD_MENU_KEYS.has(menuKey)) {
    return menuKey
  }
  if (menuKey.startsWith('menus.')) {
    const leaf = menuKey.slice('menus.'.length)
    const aliasKey = `menu.${leaf}`
    if (Object.prototype.hasOwnProperty.call(LEGACY_BACKEND_MENU_TITLES, aliasKey)) {
      return aliasKey
    }
  }
  return menuKey
}
function containsHan(value) {
  return /[\u3400-\u9fff]/.test(value)
}
export function resolveBackendMenuTitle(title, componentKey = '') {
  const trimmedTitle = typeof title === 'string' ? title.trim() : ''
  if (trimmedTitle) {
    if (trimmedTitle.startsWith('menu.') || trimmedTitle.startsWith('menus.')) {
      const normalizedExistingKey = normalizeMenuKey(trimmedTitle)
      if (normalizedExistingKey) {
        return normalizedExistingKey
      }
    }
    const mappedKey = BACKEND_MENU_TITLE_KEY_MAP[trimmedTitle]
    if (mappedKey) {
      return normalizeMenuKey(mappedKey)
    }
  }
  const componentMenuKey = normalizeMenuKey(normalizeComponentMenuKey(componentKey))
  if (componentMenuKey) {
    return componentMenuKey
  }
  if (trimmedTitle && !containsHan(trimmedTitle)) {
    return trimmedTitle
  }
  return ''
}
export { LEGACY_BACKEND_MENU_TITLES }
rsf-design/src/utils/router.js
@@ -20,19 +20,16 @@
}
const formatMenuTitle = (title) => {
  if (title) {
    if (i18n.global.te(title)) {
      return $t(title)
    }
    if (title.startsWith('menus.') || title.startsWith('menu.')) {
      if (i18n.global.te(title)) {
        return $t(title)
      } else {
        const fallbackTitle =
          title.startsWith('menus.') && title.split('.').pop()
            ? `menu.${title.split('.').pop()}`
            : ''
        if (fallbackTitle && i18n.global.te(fallbackTitle)) {
          return $t(fallbackTitle)
        }
        return title.split('.').pop() || title
      const leaf = title.startsWith('menus.') ? title.slice('menus.'.length) : ''
      const aliasTitle = leaf ? `menu.${leaf}` : ''
      if (aliasTitle && i18n.global.te(aliasTitle)) {
        return $t(aliasTitle)
      }
      return ''
    }
    return title
  }
rsf-design/src/utils/sys/requestGuard.js
@@ -1,7 +1,8 @@
import { ElMessage } from 'element-plus'
import { $t } from '@/locales'
const DEFAULT_REQUEST_GUARD_TIMEOUT = 8e3
const DEFAULT_REQUEST_TIMEOUT_MESSAGE = '请求超时,已停止等待'
const DEFAULT_REQUEST_TIMEOUT_MESSAGE = () => $t('message.requestTimeoutStopped')
function withRequestGuard(task, fallbackValue, options = {}) {
  const { timeoutMs = DEFAULT_REQUEST_GUARD_TIMEOUT, onFallback } = options
@@ -31,7 +32,7 @@
function guardRequestWithMessage(task, fallbackValue, options = {}) {
  const {
    timeoutMs = DEFAULT_REQUEST_GUARD_TIMEOUT,
    timeoutMessage = DEFAULT_REQUEST_TIMEOUT_MESSAGE,
    timeoutMessage = DEFAULT_REQUEST_TIMEOUT_MESSAGE(),
    errorMessage,
    resolveErrorMessage
  } = options
rsf-design/src/views/basic-info/bas-station-area/basStationAreaPage.helpers.js
@@ -1,24 +1,26 @@
import { $t } from '@/locales'
const STATUS_META = {
  1: { text: '正常', type: 'success', bool: true },
  0: { text: '冻结', type: 'danger', bool: false }
  1: { text: $t('common.status.normal'), type: 'success', bool: true },
  0: { text: $t('common.status.frozen'), type: 'danger', bool: false }
}
const TYPE_OPTIONS = [
  { label: '智能站点', value: 0 },
  { label: '普通站点', value: 1 }
  { label: $t('pages.basicInfo.basStationArea.type.smart'), value: 0 },
  { label: $t('pages.basicInfo.basStationArea.type.normal'), value: 1 }
]
const BINARY_OPTIONS = [
  { label: '否', value: 0 },
  { label: '是', value: 1 }
  { label: $t('common.status.no'), value: 0 },
  { label: $t('common.status.yes'), value: 1 }
]
const STATUS_OPTIONS = [
  { label: '正常', value: 1 },
  { label: '冻结', value: 0 }
  { label: $t('common.status.normal'), value: 1 },
  { label: $t('common.status.frozen'), value: 0 }
]
export const BAS_STATION_AREA_REPORT_TITLE = '站点区域报表'
export const BAS_STATION_AREA_REPORT_TITLE = $t('pages.basicInfo.basStationArea.reportTitle')
export const BAS_STATION_AREA_REPORT_STYLE = {
  titleAlign: 'center',
  titleLevel: 'strong',
@@ -40,13 +42,13 @@
}
function normalizeBooleanText(value) {
  if (value === 1 || value === '1' || value === true || value === '是') {
    return '是'
  if (value === 1 || value === '1' || value === true || value === $t('common.status.yes')) {
    return $t('common.status.yes')
  }
  if (value === 0 || value === '0' || value === false || value === '否') {
    return '否'
  if (value === 0 || value === '0' || value === false || value === $t('common.status.no')) {
    return $t('common.status.no')
  }
  return normalizeText(value) || '--'
  return normalizeText(value) || $t('common.placeholder.empty')
}
function normalizeIdArray(values = []) {
@@ -91,7 +93,7 @@
    })
    .filter(Boolean)
  return labels.length ? labels.join('、') : '--'
  return labels.length ? labels.join($t('common.listSeparator')) : $t('common.placeholder.empty')
}
function getStatusMeta(status) {
@@ -101,7 +103,7 @@
  if (status === false || Number(status) === 0) {
    return STATUS_META[0]
  }
  return { text: '未知', type: 'info', bool: false }
  return { text: $t('common.status.unknown'), type: 'info', bool: false }
}
export function createBasStationAreaSearchState() {
@@ -187,7 +189,7 @@
      }
      return {
        value: Number(value),
        label: normalizeText(item.name || item.areaName || item.code || item.areaCode || `库区 ${value}`)
        label: normalizeText(item.name || item.areaName || item.code || item.areaCode || `${$t('menu.warehouseAreas')} ${value}`)
      }
    })
    .filter(Boolean)
@@ -209,7 +211,7 @@
      }
      return {
        value: Number(value),
        label: normalizeText(item.stationName || item.stationId || item.name || `站点 ${value}`)
        label: normalizeText(item.stationName || item.stationId || item.name || `${$t('menu.basStation')} ${value}`)
      }
    })
    .filter(Boolean)
@@ -426,32 +428,36 @@
  return {
    ...record,
    id: normalizeIdValue(record.id),
    stationAreaName: normalizeText(record.stationAreaName) || '--',
    stationAreaId: normalizeText(record.stationAreaId) || '--',
    stationAreaName: normalizeText(record.stationAreaName) || $t('common.placeholder.empty'),
    stationAreaId: normalizeText(record.stationAreaId) || $t('common.placeholder.empty'),
    type: normalizeIdValue(record.type),
    typeText: normalizeText(
      record.type$ || record.typeText || resolvers.resolveTypeLabel?.(typeValue) || typeValue
    ) || '--',
    ) || $t('common.placeholder.empty'),
    inAble: normalizeIdValue(record.inAble),
    inAbleText: normalizeBooleanText(record.inAble),
    outAble: normalizeIdValue(record.outAble),
    outAbleText: normalizeBooleanText(record.outAble),
    useStatus: normalizeText(record.useStatus),
    useStatusText: normalizeText(record.useStatus$ || record.useStatusText || resolvers.resolveUseStatusLabel?.(record.useStatus) || record.useStatus) || '--',
    useStatusText:
      normalizeText(record.useStatus$ || record.useStatusText || resolvers.resolveUseStatusLabel?.(record.useStatus) || record.useStatus) ||
      $t('common.placeholder.empty'),
    area: normalizeIdValue(areaId),
    areaText: normalizeText(record.area$ || record.areaText || resolvers.resolveAreaLabel?.(areaId) || '') || '--',
    areaText: normalizeText(record.area$ || record.areaText || resolvers.resolveAreaLabel?.(areaId) || '') || $t('common.placeholder.empty'),
    isCrossZone: normalizeIdValue(record.isCrossZone),
    isCrossZoneText: normalizeBooleanText(record.isCrossZone),
    crossZoneArea: crossZoneAreaIds,
    crossZoneAreaText:
      resolveOptionText(crossZoneAreaIds, resolvers.resolveCrossZoneAreaLabel, record.crossZoneAreaText || []) || '--',
      resolveOptionText(crossZoneAreaIds, resolvers.resolveCrossZoneAreaLabel, record.crossZoneAreaText || []) ||
      $t('common.placeholder.empty'),
    isWcs: normalizeIdValue(record.isWcs),
    isWcsText: normalizeBooleanText(record.isWcs),
    wcsData: normalizeText(record.wcsData) || '--',
    wcsData: normalizeText(record.wcsData) || $t('common.placeholder.empty'),
    containerType: containerTypeIds,
    containerTypeText:
      resolveOptionText(containerTypeIds, resolvers.resolveContainerTypeLabel, record.containerTypesText || []) || '--',
    barcode: normalizeText(record.barcode) || '--',
      resolveOptionText(containerTypeIds, resolvers.resolveContainerTypeLabel, record.containerTypesText || []) ||
      $t('common.placeholder.empty'),
    barcode: normalizeText(record.barcode) || $t('common.placeholder.empty'),
    autoTransfer: normalizeIdValue(record.autoTransfer),
    autoTransferText: normalizeBooleanText(record.autoTransfer),
    stationAlias: stationAliasIds,
@@ -460,16 +466,16 @@
        stationAliasIds,
        resolvers.resolveStationAliasLabel,
        stationAliasNames.length ? stationAliasNames : record.stationAliasText || []
      ) || '--',
      ) || $t('common.placeholder.empty'),
    status: normalizeIdValue(record.status),
    statusText: statusMeta.text,
    statusType: statusMeta.type,
    statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
    memo: normalizeText(record.memo) || '--',
    createByText: normalizeText(record.createBy$ || record.createByText || ''),
    createTimeText: normalizeText(record.createTime$ || record.createTime || ''),
    updateByText: normalizeText(record.updateBy$ || record.updateByText || ''),
    updateTimeText: normalizeText(record.updateTime$ || record.updateTime || '')
    memo: normalizeText(record.memo) || $t('common.placeholder.empty'),
    createByText: normalizeText(record.createBy$ || record.createByText || '') || $t('common.placeholder.empty'),
    createTimeText: normalizeText(record.createTime$ || record.createTime || '') || $t('common.placeholder.empty'),
    updateByText: normalizeText(record.updateBy$ || record.updateByText || '') || $t('common.placeholder.empty'),
    updateTimeText: normalizeText(record.updateTime$ || record.updateTime || '') || $t('common.placeholder.empty')
  }
}
rsf-design/src/views/basic-info/bas-station-area/basStationAreaTable.columns.js
@@ -1,5 +1,6 @@
import { h } from 'vue'
import { ElTag } from 'element-plus'
import { $t } from '@/locales'
import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
import { getBasStationAreaStatusOptions } from './basStationAreaPage.helpers'
@@ -10,14 +11,14 @@
  canEdit = true,
  canDelete = true
} = {}) {
  const operations = [{ key: 'view', label: '详情', icon: 'ri:eye-line' }]
  const operations = [{ key: 'view', label: $t('common.actions.detail'), icon: 'ri:eye-line' }]
  if (canEdit && handleEdit) {
    operations.push({ key: 'edit', label: '编辑', icon: 'ri:pencil-line' })
    operations.push({ key: 'edit', label: $t('common.actions.edit'), icon: 'ri:pencil-line' })
  }
  if (canDelete && handleDelete) {
    operations.push({ key: 'delete', label: '删除', icon: 'ri:delete-bin-5-line', color: 'var(--art-error)' })
    operations.push({ key: 'delete', label: $t('common.actions.delete'), icon: 'ri:delete-bin-5-line', color: 'var(--art-error)' })
  }
  return [
@@ -28,111 +29,111 @@
    },
    {
      type: 'globalIndex',
      label: '序号',
      label: $t('table.index'),
      width: 72,
      align: 'center'
    },
    {
      prop: 'stationAreaId',
      label: '站点区域编号',
      label: $t('pages.basicInfo.basStationArea.search.stationAreaId'),
      minWidth: 150,
      showOverflowTooltip: true,
      formatter: (row) => row.stationAreaId || '--'
    },
    {
      prop: 'stationAreaName',
      label: '站点区域名称',
      label: $t('pages.basicInfo.basStationArea.search.stationAreaName'),
      minWidth: 180,
      showOverflowTooltip: true,
      formatter: (row) => row.stationAreaName || '--'
    },
    {
      prop: 'typeText',
      label: '站点类型',
      label: $t('pages.basicInfo.basStationArea.search.type'),
      width: 120,
      align: 'center',
      formatter: (row) => row.typeText || '--'
    },
    {
      prop: 'areaText',
      label: '所属库区',
      label: $t('pages.basicInfo.basStationArea.search.area'),
      minWidth: 150,
      showOverflowTooltip: true,
      formatter: (row) => row.areaText || '--'
    },
    {
      prop: 'crossZoneAreaText',
      label: '可跨区区域',
      label: $t('pages.basicInfo.basStationArea.table.crossZoneArea'),
      minWidth: 180,
      showOverflowTooltip: true,
      formatter: (row) => row.crossZoneAreaText || '--'
    },
    {
      prop: 'containerTypeText',
      label: '容器类型',
      label: $t('pages.basicInfo.basStationArea.search.containerType'),
      minWidth: 160,
      showOverflowTooltip: true,
      formatter: (row) => row.containerTypeText || '--'
    },
    {
      prop: 'stationAliasText',
      label: '站点别名',
      label: $t('pages.basicInfo.basStationArea.search.stationAlias'),
      minWidth: 180,
      showOverflowTooltip: true,
      formatter: (row) => row.stationAliasText || '--'
    },
    {
      prop: 'inAbleText',
      label: '可入',
      label: $t('pages.basicInfo.basStationArea.table.inAble'),
      width: 84,
      align: 'center',
      formatter: (row) => row.inAbleText || '--'
    },
    {
      prop: 'outAbleText',
      label: '可出',
      label: $t('pages.basicInfo.basStationArea.table.outAble'),
      width: 84,
      align: 'center',
      formatter: (row) => row.outAbleText || '--'
    },
    {
      prop: 'isCrossZoneText',
      label: '跨区',
      label: $t('pages.basicInfo.basStationArea.table.isCrossZone'),
      width: 84,
      align: 'center',
      formatter: (row) => row.isCrossZoneText || '--'
    },
    {
      prop: 'isWcsText',
      label: 'WCS',
      label: $t('table.wcs'),
      width: 84,
      align: 'center',
      formatter: (row) => row.isWcsText || '--'
    },
    {
      prop: 'autoTransferText',
      label: '自动调拨',
      label: $t('pages.basicInfo.basStationArea.search.autoTransfer'),
      width: 100,
      align: 'center',
      formatter: (row) => row.autoTransferText || '--'
    },
    {
      prop: 'useStatusText',
      label: '使用状态',
      label: $t('pages.basicInfo.basStationArea.search.useStatus'),
      minWidth: 120,
      showOverflowTooltip: true,
      formatter: (row) => row.useStatusText || '--'
    },
    {
      prop: 'barcode',
      label: '条码',
      label: $t('pages.basicInfo.basStationArea.search.barcode'),
      minWidth: 150,
      showOverflowTooltip: true,
      formatter: (row) => row.barcode || '--'
    },
    {
      prop: 'status',
      label: '状态',
      label: $t('pages.basicInfo.basStationArea.search.status'),
      width: 96,
      align: 'center',
      formatter: (row) => {
@@ -144,21 +145,21 @@
    },
    {
      prop: 'updateTimeText',
      label: '更新时间',
      label: $t('table.updateTime'),
      minWidth: 170,
      showOverflowTooltip: true,
      formatter: (row) => row.updateTimeText || '--'
    },
    {
      prop: 'memo',
      label: '备注',
      label: $t('pages.basicInfo.basStationArea.search.memo'),
      minWidth: 180,
      showOverflowTooltip: true,
      formatter: (row) => row.memo || '--'
    },
    {
      prop: 'operation',
      label: '操作',
      label: $t('table.operation'),
      width: 120,
      align: 'center',
      fixed: 'right',
rsf-design/src/views/basic-info/bas-station-area/index.vue
@@ -12,9 +12,9 @@
      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
        <template #left>
          <ElSpace wrap>
            <ElButton v-auth="'add'" @click="handleShowDialog('add')" v-ripple
              >新增站点区域</ElButton
            >
            <ElButton v-auth="'add'" @click="handleShowDialog('add')" v-ripple>
              {{ t('pages.basicInfo.basStationArea.actions.add') }}
            </ElButton>
            <ElButton
              v-auth="'delete'"
              type="danger"
@@ -22,7 +22,7 @@
              @click="handleBatchDelete"
              v-ripple
            >
              批量删除
              {{ t('common.actions.batchDelete') }}
            </ElButton>
            <ListExportPrint
              class="inline-flex"
@@ -77,6 +77,7 @@
<script setup>
  import { computed, onMounted, ref } from 'vue'
  import { ElMessage } from 'element-plus'
  import { useI18n } from 'vue-i18n'
  import ListExportPrint from '@/components/biz/list-export-print/index.vue'
  import { useAuth } from '@/hooks/core/useAuth'
  import { useTable } from '@/hooks/core/useTable'
@@ -126,6 +127,7 @@
  const { hasAuth } = useAuth()
  const userStore = useUserStore()
  const { t } = useI18n()
  const searchForm = ref(createBasStationAreaSearchState())
  const detailDrawerVisible = ref(false)
@@ -192,56 +194,56 @@
  const searchItems = computed(() => [
    {
      label: '关键字',
      label: t('pages.basicInfo.basStationArea.search.condition'),
      key: 'condition',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入站点区域名称/编号/备注'
        placeholder: t('pages.basicInfo.basStationArea.placeholder.condition')
      }
    },
    {
      label: '开始时间',
      label: t('pages.basicInfo.basStationArea.search.timeStart'),
      key: 'timeStart',
      type: 'date',
      props: {
        clearable: true,
        type: 'date',
        valueFormat: 'YYYY-MM-DD',
        placeholder: '请选择开始时间'
        placeholder: t('pages.basicInfo.basStationArea.placeholder.timeStart')
      }
    },
    {
      label: '结束时间',
      label: t('pages.basicInfo.basStationArea.search.timeEnd'),
      key: 'timeEnd',
      type: 'date',
      props: {
        clearable: true,
        type: 'date',
        valueFormat: 'YYYY-MM-DD',
        placeholder: '请选择结束时间'
        placeholder: t('pages.basicInfo.basStationArea.placeholder.timeEnd')
      }
    },
    {
      label: '站点区域名称',
      label: t('pages.basicInfo.basStationArea.search.stationAreaName'),
      key: 'stationAreaName',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入站点区域名称'
        placeholder: t('pages.basicInfo.basStationArea.placeholder.stationAreaName')
      }
    },
    {
      label: '站点区域编号',
      label: t('pages.basicInfo.basStationArea.search.stationAreaId'),
      key: 'stationAreaId',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入站点区域编号'
        placeholder: t('pages.basicInfo.basStationArea.placeholder.stationAreaId')
      }
    },
    {
      label: '站点类型',
      label: t('pages.basicInfo.basStationArea.search.type'),
      key: 'type',
      type: 'select',
      props: {
@@ -250,7 +252,7 @@
      }
    },
    {
      label: '所属库区',
      label: t('pages.basicInfo.basStationArea.search.area'),
      key: 'area',
      type: 'select',
      props: {
@@ -260,7 +262,7 @@
      }
    },
    {
      label: '使用状态',
      label: t('pages.basicInfo.basStationArea.search.useStatus'),
      key: 'useStatus',
      type: 'select',
      props: {
@@ -270,7 +272,7 @@
      }
    },
    {
      label: '可入',
      label: t('pages.basicInfo.basStationArea.search.inAble'),
      key: 'inAble',
      type: 'select',
      props: {
@@ -279,7 +281,7 @@
      }
    },
    {
      label: '可出',
      label: t('pages.basicInfo.basStationArea.search.outAble'),
      key: 'outAble',
      type: 'select',
      props: {
@@ -288,7 +290,7 @@
      }
    },
    {
      label: '是否跨区',
      label: t('pages.basicInfo.basStationArea.search.isCrossZone'),
      key: 'isCrossZone',
      type: 'select',
      props: {
@@ -297,16 +299,16 @@
      }
    },
    {
      label: '跨区区域',
      label: t('pages.basicInfo.basStationArea.search.crossZoneArea'),
      key: 'crossZoneArea',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入跨区区域'
        placeholder: t('pages.basicInfo.basStationArea.placeholder.crossZoneArea')
      }
    },
    {
      label: '是否WCS',
      label: t('pages.basicInfo.basStationArea.search.isWcs'),
      key: 'isWcs',
      type: 'select',
      props: {
@@ -315,25 +317,25 @@
      }
    },
    {
      label: 'WCS数据',
      label: t('pages.basicInfo.basStationArea.search.wcsData'),
      key: 'wcsData',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入WCS数据'
        placeholder: t('pages.basicInfo.basStationArea.placeholder.wcsData')
      }
    },
    {
      label: '容器类型',
      label: t('pages.basicInfo.basStationArea.search.containerType'),
      key: 'containerType',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入容器类型'
        placeholder: t('pages.basicInfo.basStationArea.placeholder.containerType')
      }
    },
    {
      label: '自动调拨',
      label: t('pages.basicInfo.basStationArea.search.autoTransfer'),
      key: 'autoTransfer',
      type: 'select',
      props: {
@@ -342,25 +344,25 @@
      }
    },
    {
      label: '条码',
      label: t('pages.basicInfo.basStationArea.search.barcode'),
      key: 'barcode',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入条码'
        placeholder: t('pages.basicInfo.basStationArea.placeholder.barcode')
      }
    },
    {
      label: '站点别名',
      label: t('pages.basicInfo.basStationArea.search.stationAlias'),
      key: 'stationAlias',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入站点别名'
        placeholder: t('pages.basicInfo.basStationArea.placeholder.stationAlias')
      }
    },
    {
      label: '状态',
      label: t('pages.basicInfo.basStationArea.search.status'),
      key: 'status',
      type: 'select',
      props: {
@@ -369,12 +371,12 @@
      }
    },
    {
      label: '备注',
      label: t('pages.basicInfo.basStationArea.search.memo'),
      key: 'memo',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入备注'
        placeholder: t('pages.basicInfo.basStationArea.placeholder.memo')
      }
    }
  ])
@@ -387,7 +389,7 @@
        fetchBasStationAreaDetail(row.id),
        {},
        {
          timeoutMessage: '站点区域详情加载超时,已停止等待'
          timeoutMessage: t('pages.basicInfo.basStationArea.messages.detailTimeout')
        }
      )
      detailData.value = normalizeBasStationAreaDetailRecord(detail, {
@@ -401,7 +403,7 @@
    } catch (error) {
      detailDrawerVisible.value = false
      detailData.value = {}
      ElMessage.error(error?.message || '获取站点区域详情失败')
      ElMessage.error(error?.message || t('pages.basicInfo.basStationArea.messages.detailLoadFailed'))
    } finally {
      detailLoading.value = false
    }
@@ -430,7 +432,7 @@
        ),
        { records: [] },
        {
          timeoutMessage: '站点别名选项加载超时,已停止等待'
          timeoutMessage: t('pages.basicInfo.basStationArea.messages.stationAliasTimeout')
        }
      )
      stationOptions.value = resolveBasStationAreaStationOptions(
@@ -457,12 +459,12 @@
        fetchBasStationAreaDetail(row.id),
        {},
        {
          timeoutMessage: '站点区域详情加载超时,已停止等待'
          timeoutMessage: t('pages.basicInfo.basStationArea.messages.detailTimeout')
        }
      )
      await handleShowDialog('edit', detail)
    } catch (error) {
      ElMessage.error(error?.message || '获取站点区域详情失败')
      ElMessage.error(error?.message || t('pages.basicInfo.basStationArea.messages.detailLoadFailed'))
    }
  }
@@ -531,7 +533,7 @@
    saveRequest: fetchSaveBasStationArea,
    updateRequest: fetchUpdateBasStationArea,
    deleteRequest: fetchDeleteBasStationArea,
    entityName: '站点区域',
    entityName: t('pages.basicInfo.basStationArea.entity'),
    resolveRecordLabel: (record) => record?.stationAreaName || record?.stationAreaId || record?.id,
    refreshCreate,
    refreshUpdate,
@@ -605,7 +607,7 @@
  async function loadAreaOptions() {
    const response = await guardRequestWithMessage(fetchWarehouseAreasList(), [], {
      timeoutMessage: '库区选项加载超时,已停止等待'
      timeoutMessage: t('pages.basicInfo.basStationArea.messages.areaOptionsTimeout')
    })
    const options = resolveBasStationAreaWarehouseAreaOptions(
      defaultResponseAdapter(response).records
@@ -623,7 +625,7 @@
        status: 1
      }),
      { records: [] },
      { timeoutMessage: '容器类型选项加载超时,已停止等待' }
      { timeoutMessage: t('pages.basicInfo.basStationArea.messages.containerTypeTimeout') }
    )
    containerTypeOptions.value = resolveBasStationAreaContainerTypeOptions(
      defaultResponseAdapter(response).records
@@ -639,7 +641,7 @@
        status: 1
      }),
      { records: [] },
      { timeoutMessage: '使用状态选项加载超时,已停止等待' }
      { timeoutMessage: t('pages.basicInfo.basStationArea.messages.useStatusTimeout') }
    )
    useStatusOptions.value = resolveBasStationAreaUseStatusOptions(
      defaultResponseAdapter(response).records
rsf-design/src/views/basic-info/bas-station-area/modules/bas-station-area-detail-drawer.vue
@@ -1,7 +1,7 @@
<template>
  <ElDrawer
    :model-value="visible"
    title="站点区域详情"
    :title="t('pages.basicInfo.basStationArea.detail.title')"
    size="960px"
    destroy-on-close
    @update:model-value="handleVisibleChange"
@@ -11,35 +11,35 @@
        <ElSkeleton :rows="12" animated />
      </div>
      <div v-else class="space-y-4">
        <ElDescriptions title="基础信息" :column="2" border>
          <ElDescriptionsItem label="站点区域名称">{{ detail.stationAreaName || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="站点区域编号">{{ detail.stationAreaId || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="站点类型">{{ detail.typeText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="所属库区">{{ detail.areaText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="可跨区库区" :span="2">{{ detail.crossZoneAreaText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="容器类型" :span="2">{{ detail.containerTypeText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="站点别名" :span="2">{{ detail.stationAliasText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="可入">{{ detail.inAbleText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="可出">{{ detail.outAbleText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="是否跨区">{{ detail.isCrossZoneText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="是否WCS">{{ detail.isWcsText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="自动调拨">{{ detail.autoTransferText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="使用状态">{{ detail.useStatusText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="条码">{{ detail.barcode || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="状态">
        <ElDescriptions :title="t('pages.basicInfo.basStationArea.detail.baseInfo')" :column="2" border>
          <ElDescriptionsItem :label="t('pages.basicInfo.basStationArea.detail.stationAreaName')">{{ detail.stationAreaName || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.basicInfo.basStationArea.detail.stationAreaId')">{{ detail.stationAreaId || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.basicInfo.basStationArea.detail.type')">{{ detail.typeText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.basicInfo.basStationArea.detail.area')">{{ detail.areaText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.basicInfo.basStationArea.detail.crossZoneArea')" :span="2">{{ detail.crossZoneAreaText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.basicInfo.basStationArea.detail.containerType')" :span="2">{{ detail.containerTypeText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.basicInfo.basStationArea.detail.stationAlias')" :span="2">{{ detail.stationAliasText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.basicInfo.basStationArea.detail.inAble')">{{ detail.inAbleText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.basicInfo.basStationArea.detail.outAble')">{{ detail.outAbleText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.basicInfo.basStationArea.detail.isCrossZone')">{{ detail.isCrossZoneText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.basicInfo.basStationArea.detail.isWcs')">{{ detail.isWcsText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.basicInfo.basStationArea.detail.autoTransfer')">{{ detail.autoTransferText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.basicInfo.basStationArea.detail.useStatus')">{{ detail.useStatusText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.basicInfo.basStationArea.detail.barcode')">{{ detail.barcode || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.basicInfo.basStationArea.detail.status')">
            <ElTag :type="detail.statusType || 'info'" effect="light">
              {{ detail.statusText || '--' }}
            </ElTag>
          </ElDescriptionsItem>
          <ElDescriptionsItem label="WCS数据" :span="2">{{ detail.wcsData || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="备注" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.basicInfo.basStationArea.detail.wcsData')" :span="2">{{ detail.wcsData || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.basicInfo.basStationArea.detail.memo')" :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 :title="t('pages.basicInfo.basStationArea.detail.auditInfo')" :column="2" border>
          <ElDescriptionsItem :label="t('pages.basicInfo.basStationArea.detail.createBy')">{{ detail.createByText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.basicInfo.basStationArea.detail.createTime')">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.basicInfo.basStationArea.detail.updateBy')">{{ detail.updateByText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.basicInfo.basStationArea.detail.updateTime')">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
        </ElDescriptions>
      </div>
    </ElScrollbar>
@@ -48,6 +48,7 @@
<script setup>
  import { computed } from 'vue'
  import { useI18n } from 'vue-i18n'
  const props = defineProps({
    visible: { type: Boolean, default: false },
@@ -56,6 +57,7 @@
  })
  const emit = defineEmits(['update:visible'])
  const { t } = useI18n()
  const visible = computed({
    get: () => props.visible,
@@ -66,4 +68,3 @@
    visible.value = value
  }
</script>
rsf-design/src/views/basic-info/bas-station-area/modules/bas-station-area-dialog.vue
@@ -22,8 +22,8 @@
    <template #footer>
      <span class="dialog-footer">
        <ElButton @click="handleCancel">取消</ElButton>
        <ElButton type="primary" @click="handleSubmit">确定</ElButton>
        <ElButton @click="handleCancel">{{ t('common.cancel') }}</ElButton>
        <ElButton type="primary" @click="handleSubmit">{{ t('common.confirm') }}</ElButton>
      </span>
    </template>
  </ElDialog>
@@ -31,6 +31,7 @@
<script setup>
  import { computed, nextTick, reactive, ref, watch } from 'vue'
  import { useI18n } from 'vue-i18n'
  import ArtForm from '@/components/core/forms/art-form/index.vue'
  import {
    buildBasStationAreaDialogModel,
@@ -51,68 +52,71 @@
  })
  const emit = defineEmits(['update:visible', 'submit'])
  const { t } = useI18n()
  const formRef = ref()
  const form = reactive(createBasStationAreaFormState())
  const isEdit = computed(() => props.dialogType === 'edit')
  const dialogTitle = computed(() => (isEdit.value ? '编辑站点区域' : '新增站点区域'))
  const dialogTitle = computed(() =>
    t(isEdit.value ? 'pages.basicInfo.basStationArea.dialog.titleEdit' : 'pages.basicInfo.basStationArea.dialog.titleAdd')
  )
  const rules = computed(() => ({
    stationAreaName: [{ required: true, message: '请输入站点区域名称', trigger: 'blur' }],
    stationAreaId: [{ required: true, message: '请输入站点区域编号', trigger: 'blur' }],
    type: [{ required: true, message: '请选择站点类型', trigger: 'change' }],
    area: [{ required: true, message: '请选择所属库区', trigger: 'change' }],
    containerType: [{ type: 'array', required: true, message: '请选择容器类型', trigger: 'change' }],
    stationAlias: [{ type: 'array', required: true, message: '请选择站点别名', trigger: 'change' }]
    stationAreaName: [{ required: true, message: t('pages.basicInfo.basStationArea.dialog.validation.stationAreaName'), trigger: 'blur' }],
    stationAreaId: [{ required: true, message: t('pages.basicInfo.basStationArea.dialog.validation.stationAreaId'), trigger: 'blur' }],
    type: [{ required: true, message: t('pages.basicInfo.basStationArea.dialog.validation.type'), trigger: 'change' }],
    area: [{ required: true, message: t('pages.basicInfo.basStationArea.dialog.validation.area'), trigger: 'change' }],
    containerType: [{ type: 'array', required: true, message: t('pages.basicInfo.basStationArea.dialog.validation.containerType'), trigger: 'change' }],
    stationAlias: [{ type: 'array', required: true, message: t('pages.basicInfo.basStationArea.dialog.validation.stationAlias'), trigger: 'change' }]
  }))
  const formItems = computed(() => [
    {
      label: '站点区域名称',
      label: t('pages.basicInfo.basStationArea.dialog.stationAreaName'),
      key: 'stationAreaName',
      type: 'input',
      props: {
        placeholder: '请输入站点区域名称',
        placeholder: t('pages.basicInfo.basStationArea.placeholder.stationAreaName'),
        clearable: true
      }
    },
    {
      label: '站点区域编号',
      label: t('pages.basicInfo.basStationArea.dialog.stationAreaId'),
      key: 'stationAreaId',
      type: 'input',
      props: {
        placeholder: '请输入站点区域编号',
        placeholder: t('pages.basicInfo.basStationArea.placeholder.stationAreaId'),
        clearable: true
      }
    },
    {
      label: '站点类型',
      label: t('pages.basicInfo.basStationArea.dialog.type'),
      key: 'type',
      type: 'select',
      props: {
        placeholder: '请选择站点类型',
        placeholder: t('pages.basicInfo.basStationArea.search.type'),
        clearable: true,
        options: getBasStationAreaTypeOptions()
      }
    },
    {
      label: '所属库区',
      label: t('pages.basicInfo.basStationArea.dialog.area'),
      key: 'area',
      type: 'select',
      props: {
        placeholder: '请选择所属库区',
        placeholder: t('pages.basicInfo.basStationArea.search.area'),
        clearable: true,
        filterable: true,
        options: props.areaOptions || []
      }
    },
    {
      label: '可跨区库区',
      label: t('pages.basicInfo.basStationArea.dialog.crossZoneArea'),
      key: 'crossZoneArea',
      type: 'select',
      span: 24,
      props: {
        placeholder: '请选择可跨区库区',
        placeholder: t('pages.basicInfo.basStationArea.placeholder.crossZoneArea'),
        clearable: true,
        multiple: true,
        collapseTags: true,
@@ -121,12 +125,12 @@
      }
    },
    {
      label: '容器类型',
      label: t('pages.basicInfo.basStationArea.dialog.containerType'),
      key: 'containerType',
      type: 'select',
      span: 24,
      props: {
        placeholder: '请选择容器类型',
        placeholder: t('pages.basicInfo.basStationArea.placeholder.containerType'),
        clearable: true,
        multiple: true,
        collapseTags: true,
@@ -135,12 +139,12 @@
      }
    },
    {
      label: '站点别名',
      label: t('pages.basicInfo.basStationArea.dialog.stationAlias'),
      key: 'stationAlias',
      type: 'select',
      span: 24,
      props: {
        placeholder: '请选择站点别名',
        placeholder: t('pages.basicInfo.basStationArea.placeholder.stationAlias'),
        clearable: true,
        multiple: true,
        collapseTags: true,
@@ -149,106 +153,106 @@
      }
    },
    {
      label: '可入',
      label: t('pages.basicInfo.basStationArea.dialog.inAble'),
      key: 'inAble',
      type: 'select',
      props: {
        placeholder: '请选择可入',
        placeholder: t('pages.basicInfo.basStationArea.search.inAble'),
        clearable: true,
        options: getBasStationAreaBinaryOptions()
      }
    },
    {
      label: '可出',
      label: t('pages.basicInfo.basStationArea.dialog.outAble'),
      key: 'outAble',
      type: 'select',
      props: {
        placeholder: '请选择可出',
        placeholder: t('pages.basicInfo.basStationArea.search.outAble'),
        clearable: true,
        options: getBasStationAreaBinaryOptions()
      }
    },
    {
      label: '是否跨区',
      label: t('pages.basicInfo.basStationArea.dialog.isCrossZone'),
      key: 'isCrossZone',
      type: 'select',
      props: {
        placeholder: '请选择是否跨区',
        placeholder: t('pages.basicInfo.basStationArea.search.isCrossZone'),
        clearable: true,
        options: getBasStationAreaBinaryOptions()
      }
    },
    {
      label: '是否WCS',
      label: t('pages.basicInfo.basStationArea.dialog.isWcs'),
      key: 'isWcs',
      type: 'select',
      props: {
        placeholder: '请选择是否WCS',
        placeholder: t('pages.basicInfo.basStationArea.search.isWcs'),
        clearable: true,
        options: getBasStationAreaBinaryOptions()
      }
    },
    {
      label: '自动调拨',
      label: t('pages.basicInfo.basStationArea.dialog.autoTransfer'),
      key: 'autoTransfer',
      type: 'select',
      props: {
        placeholder: '请选择自动调拨',
        placeholder: t('pages.basicInfo.basStationArea.search.autoTransfer'),
        clearable: true,
        options: getBasStationAreaBinaryOptions()
      }
    },
    {
      label: '使用状态',
      label: t('pages.basicInfo.basStationArea.dialog.useStatus'),
      key: 'useStatus',
      type: 'select',
      props: {
        placeholder: '请选择使用状态',
        placeholder: t('pages.basicInfo.basStationArea.search.useStatus'),
        clearable: true,
        filterable: true,
        options: props.useStatusOptions || []
      }
    },
    {
      label: 'WCS数据',
      label: t('pages.basicInfo.basStationArea.dialog.wcsData'),
      key: 'wcsData',
      type: 'input',
      span: 24,
      props: {
        type: 'textarea',
        rows: 3,
        placeholder: '请输入WCS数据',
        placeholder: t('pages.basicInfo.basStationArea.placeholder.wcsData'),
        clearable: true
      }
    },
    {
      label: '条码',
      label: t('pages.basicInfo.basStationArea.dialog.barcode'),
      key: 'barcode',
      type: 'input',
      props: {
        placeholder: '请输入条码',
        placeholder: t('pages.basicInfo.basStationArea.placeholder.barcode'),
        clearable: true
      }
    },
    {
      label: '状态',
      label: t('pages.basicInfo.basStationArea.dialog.status'),
      key: 'status',
      type: 'select',
      props: {
        placeholder: '请选择状态',
        placeholder: t('pages.basicInfo.basStationArea.search.status'),
        clearable: true,
        options: getBasStationAreaStatusOptions()
      }
    },
    {
      label: '备注',
      label: t('pages.basicInfo.basStationArea.dialog.memo'),
      key: 'memo',
      type: 'input',
      span: 24,
      props: {
        type: 'textarea',
        rows: 3,
        placeholder: '请输入备注',
        placeholder: t('pages.basicInfo.basStationArea.placeholder.memo'),
        clearable: true
      }
    }
@@ -304,4 +308,3 @@
    { deep: true }
  )
</script>
rsf-design/src/views/manager/task/index.vue
@@ -19,7 +19,7 @@
              :loading="autoRunLoading"
              @click="handleToggleAutoRun(true)"
            >
              自动下发任务
              {{ t('pages.task.buttons.autoRun') }}
            </ElButton>
            <ElButton
              v-else
@@ -28,7 +28,7 @@
              :loading="autoRunLoading"
              @click="handleToggleAutoRun(false)"
            >
              暂停自动下发
              {{ t('pages.task.buttons.pauseAutoRun') }}
            </ElButton>
          </ElSpace>
        </template>
@@ -67,6 +67,7 @@
<script setup>
  import { ElMessage } from 'element-plus'
  import { computed, h, onMounted, onUnmounted, reactive, ref } from 'vue'
  import { useI18n } from 'vue-i18n'
  import { useTableColumns } from '@/hooks/core/useTableColumns'
  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
  import {
@@ -93,6 +94,7 @@
  } from './taskPage.helpers'
  defineOptions({ name: 'Task' })
  const { t } = useI18n()
  const loading = ref(false)
  const tableData = ref([])
@@ -121,106 +123,106 @@
  const searchItems = computed(() => [
    {
      label: '关键字',
      label: t('pages.task.search.condition'),
      key: 'condition',
      type: 'input',
      props: { clearable: true, placeholder: '请输入任务号/库位/托盘码' }
      props: { clearable: true, placeholder: t('pages.task.placeholder.condition') }
    },
    {
      label: '任务号',
      label: t('pages.task.search.taskCode'),
      key: 'taskCode',
      type: 'input',
      props: { clearable: true, placeholder: '请输入任务号' }
      props: { clearable: true, placeholder: t('pages.task.placeholder.taskCode') }
    },
    {
      label: '源库位',
      label: t('pages.task.search.orgLoc'),
      key: 'orgLoc',
      type: 'input',
      props: { clearable: true, placeholder: '请输入源库位' }
      props: { clearable: true, placeholder: t('pages.task.placeholder.orgLoc') }
    },
    {
      label: '目标库位',
      label: t('pages.task.search.targLoc'),
      key: 'targLoc',
      type: 'input',
      props: { clearable: true, placeholder: '请输入目标库位' }
      props: { clearable: true, placeholder: t('pages.task.placeholder.targLoc') }
    },
    {
      label: '托盘码',
      label: t('pages.task.search.barcode'),
      key: 'barcode',
      type: 'input',
      props: { clearable: true, placeholder: '请输入托盘码' }
      props: { clearable: true, placeholder: t('pages.task.placeholder.barcode') }
    }
  ])
  const detailColumns = computed(() => [
    {
      type: 'globalIndex',
      label: '序号',
      label: t('table.index'),
      width: 72,
      align: 'center'
    },
    {
      prop: 'orderTypeLabel',
      label: '单据类型',
      label: t('pages.orders.delivery.detail.type'),
      minWidth: 120,
      showOverflowTooltip: true
    },
    {
      prop: 'wkTypeLabel',
      label: '业务类型',
      label: t('pages.orders.delivery.detail.wkType'),
      minWidth: 120,
      showOverflowTooltip: true
    },
    {
      prop: 'platWorkCode',
      label: '工单号',
      label: t('pages.orders.transfer.detail.relatedCode'),
      minWidth: 150,
      showOverflowTooltip: true
    },
    {
      prop: 'platItemId',
      label: '行号',
      label: t('pages.orders.delivery.table.platItemId'),
      minWidth: 100,
      showOverflowTooltip: true
    },
    {
      prop: 'matnrCode',
      label: '物料编码',
      label: t('pages.orders.delivery.table.matnrCode'),
      minWidth: 150,
      showOverflowTooltip: true
    },
    {
      prop: 'maktx',
      label: '物料名称',
      label: t('pages.orders.delivery.table.maktx'),
      minWidth: 220,
      showOverflowTooltip: true
    },
    {
      prop: 'batch',
      label: '批次',
      label: t('table.batch'),
      minWidth: 140,
      showOverflowTooltip: true
    },
    {
      prop: 'unit',
      label: '单位',
      label: t('table.unit'),
      width: 100
    },
    {
      prop: 'anfme',
      label: '数量',
      label: t('pages.orders.delivery.table.anfme'),
      width: 100,
      align: 'right'
    },
    {
      prop: 'updateByText',
      label: '更新人',
      label: t('pages.orders.delivery.detail.updateBy'),
      minWidth: 120,
      showOverflowTooltip: true
    },
    {
      prop: 'updateTimeText',
      label: '更新时间',
      label: t('pages.orders.delivery.detail.updateTime'),
      minWidth: 180,
      showOverflowTooltip: true
    }
@@ -255,24 +257,24 @@
      }
      if (action.key === 'complete') {
        await confirmTaskAction(`确定完成任务 ${row.taskCode || ''} 吗?`)
        await confirmTaskAction(t('pages.task.messages.completeConfirm', { code: row.taskCode || '' }))
        await fetchCompleteTask(row.id)
        ElMessage.success('任务完成成功')
        ElMessage.success(t('pages.task.messages.completeSuccess'))
      } else if (action.key === 'remove') {
        await confirmTaskAction(`确定取消任务 ${row.taskCode || ''} 吗?`)
        await confirmTaskAction(t('pages.task.messages.removeConfirm', { code: row.taskCode || '' }))
        await fetchRemoveTask(row.id)
        ElMessage.success('任务取消成功')
        ElMessage.success(t('pages.task.messages.removeSuccess'))
      } else if (action.key === 'check') {
        await confirmTaskAction(`确定执行盘点出库任务 ${row.taskCode || ''} 吗?`)
        await confirmTaskAction(t('pages.task.messages.checkConfirm', { code: row.taskCode || '' }))
        await fetchCheckTask(row.id)
        ElMessage.success('盘点出库成功')
        ElMessage.success(t('pages.task.messages.checkSuccess'))
      } else if (action.key === 'pick') {
        await confirmTaskAction(`确定执行拣料出库任务 ${row.taskCode || ''} 吗?`)
        await confirmTaskAction(t('pages.task.messages.pickConfirm', { code: row.taskCode || '' }))
        await fetchPickTask(row.id)
        ElMessage.success('拣料出库成功')
        ElMessage.success(t('pages.task.messages.pickSuccess'))
      } else if (action.key === 'top') {
        await fetchTopTask(row.id)
        ElMessage.success('任务置顶成功')
        ElMessage.success(t('pages.task.messages.topSuccess'))
      }
      await loadPageData()
@@ -283,7 +285,7 @@
      if (error === 'cancel') {
        return
      }
      ElMessage.error(error?.message || '任务操作失败')
      ElMessage.error(error?.message || t('pages.task.messages.actionFailed'))
    }
  }
@@ -322,7 +324,7 @@
          current: pagination.current,
          size: pagination.size
        },
        { timeoutMessage: '任务列表加载超时,已停止等待' }
        { timeoutMessage: t('pages.task.messages.listTimeout') }
      )
      tableData.value = Array.isArray(response?.records)
        ? response.records.map((record) => normalizeTaskRow(record))
@@ -337,7 +339,7 @@
    autoRunLoading.value = true
    try {
      const response = await guardRequestWithMessage(fetchTaskAutoRunFlag(), { val: false }, {
        timeoutMessage: '自动下发配置加载超时,已停止等待'
        timeoutMessage: t('pages.task.messages.autoRunTimeout')
      })
      const rawValue = response?.val
      autoRunEnabled.value =
@@ -352,9 +354,9 @@
    try {
      await fetchUpdateTaskAutoRunFlag(enabled)
      autoRunEnabled.value = enabled
      ElMessage.success(enabled ? '已开启自动下发任务' : '已暂停自动下发任务')
      ElMessage.success(enabled ? t('pages.task.messages.autoRunOnSuccess') : t('pages.task.messages.autoRunOffSuccess'))
    } catch (error) {
      ElMessage.error(error?.message || '自动下发配置更新失败')
      ElMessage.error(error?.message || t('pages.task.messages.autoRunUpdateFailed'))
    } finally {
      autoRunLoading.value = false
    }
@@ -379,7 +381,7 @@
          current: detailPagination.current,
          size: detailPagination.size
        },
        { timeoutMessage: '任务明细加载超时,已停止等待' }
        { timeoutMessage: t('pages.task.messages.itemsTimeout') }
      )
      detailData.value = normalizeTaskRow(activeTaskRow.value)
@@ -389,7 +391,7 @@
      updatePaginationState(detailPagination, taskItemResponse, detailPagination.current, detailPagination.size)
    } catch (error) {
      detailTableData.value = []
      ElMessage.error(error?.message || '任务明细加载失败')
      ElMessage.error(error?.message || t('pages.task.messages.detailLoadFailed'))
    } finally {
      detailLoading.value = false
    }
rsf-design/src/views/manager/task/modules/task-detail-drawer.vue
@@ -1,7 +1,7 @@
<template>
  <ElDrawer
    :model-value="visible"
    title="任务详情"
    :title="t('pages.task.detail.title')"
    size="85%"
    @update:model-value="handleVisibleChange"
  >
@@ -10,7 +10,7 @@
        <ElCard shadow="never" class="border border-[var(--el-border-color-lighter)]">
          <template #header>
            <div class="flex items-center justify-between">
              <span class="font-medium text-[var(--art-text-gray-900)]">任务基础信息</span>
              <span class="font-medium text-[var(--art-text-gray-900)]">{{ t('pages.task.detail.baseInfo') }}</span>
              <ElTag size="small" effect="plain" type="primary">
                {{ detail.taskCode || '--' }}
              </ElTag>
@@ -18,45 +18,45 @@
          </template>
          <ElDescriptions :column="2" border>
            <ElDescriptionsItem label="任务状态">{{ detail.taskStatusLabel || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem label="任务类型">{{ detail.taskTypeLabel || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem label="设备类型">{{ detail.warehTypeLabel || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem label="优先级">{{ detail.sort ?? '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem label="状态">{{ detail.statusText || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem label="机器人编码">{{ detail.robotCode || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem label="创建时间">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem label="更新时间">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem label="备注" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.task.detail.taskStatus')">{{ detail.taskStatusLabel || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.task.detail.taskType')">{{ detail.taskTypeLabel || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.task.detail.warehType')">{{ detail.warehTypeLabel || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.task.detail.priority')">{{ detail.sort ?? '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.task.detail.status')">{{ detail.statusText || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.task.detail.robotCode')">{{ detail.robotCode || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.task.detail.createTime')">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.task.detail.updateTime')">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.task.detail.memo')" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
          </ElDescriptions>
        </ElCard>
        <ElCard shadow="never" class="border border-[var(--el-border-color-lighter)]">
          <template #header>
            <div class="flex items-center justify-between">
              <span class="font-medium text-[var(--art-text-gray-900)]">执行路径</span>
              <ElButton text type="primary" @click="$emit('flow-step')">流程步骤</ElButton>
              <span class="font-medium text-[var(--art-text-gray-900)]">{{ t('pages.task.detail.pathInfo') }}</span>
              <ElButton text type="primary" @click="$emit('flow-step')">{{ t('pages.task.detail.flowStep') }}</ElButton>
            </div>
          </template>
          <ElDescriptions :column="1" border>
            <ElDescriptionsItem label="源库位">{{ detail.orgLoc || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem label="源站点">{{ detail.orgSiteLabel || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem label="目标库位">{{ detail.targLoc || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem label="目标站点">{{ detail.targSiteLabel || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem label="托盘码">{{ detail.barcode || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.task.detail.orgLoc')">{{ detail.orgLoc || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.task.detail.orgSite')">{{ detail.orgSiteLabel || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.task.detail.targLoc')">{{ detail.targLoc || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.task.detail.targSite')">{{ detail.targSiteLabel || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.task.detail.barcode')">{{ detail.barcode || '--' }}</ElDescriptionsItem>
          </ElDescriptions>
        </ElCard>
      </div>
      <div class="flex items-center justify-between">
        <div>
          <div class="text-sm font-medium text-[var(--art-text-gray-900)]">任务明细</div>
          <div class="text-sm font-medium text-[var(--art-text-gray-900)]">{{ t('pages.task.detail.items') }}</div>
          <div class="mt-1 text-xs text-[var(--art-text-gray-500)]">
            查看当前任务关联的业务单据、物料和执行记录
            {{ t('pages.task.detail.itemsHint') }}
          </div>
        </div>
        <div class="flex items-center gap-2">
          <ElButton :loading="loading" @click="$emit('refresh')">刷新</ElButton>
          <ElButton :loading="loading" @click="$emit('refresh')">{{ t('common.actions.refresh') }}</ElButton>
        </div>
      </div>
@@ -73,6 +73,10 @@
</template>
<script setup>
  import { useI18n } from 'vue-i18n'
  const { t } = useI18n()
  defineProps({
    visible: { type: Boolean, default: false },
    loading: { type: Boolean, default: false },
rsf-design/src/views/manager/task/modules/task-expand-panel.vue
@@ -1,15 +1,15 @@
<template>
  <div class="rounded-xl bg-[var(--el-fill-color-blank)] px-4 py-4">
    <div class="mb-3 flex items-center justify-between">
      <div class="text-sm font-medium text-[var(--art-gray-900)]">任务明细</div>
      <ElButton text size="small" :loading="loading" @click="loadData">刷新</ElButton>
      <div class="text-sm font-medium text-[var(--art-gray-900)]">{{ t('pages.task.expand.title') }}</div>
      <ElButton text size="small" :loading="loading" @click="loadData">{{ t('common.actions.refresh') }}</ElButton>
    </div>
    <ArtTable
      :loading="loading"
      :data="rows"
      :columns="columns"
      empty-text="暂无任务明细"
      :empty-text="t('pages.task.expand.empty')"
      :empty-height="'220px'"
    />
  </div>
@@ -17,6 +17,7 @@
<script setup>
  import { onMounted, ref, watch } from 'vue'
  import { useI18n } from 'vue-i18n'
  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
  import { fetchTaskItemPage } from '@/api/task'
  import { normalizeTaskItemRow } from '../taskPage.helpers'
@@ -30,76 +31,77 @@
  const loading = ref(false)
  const rows = ref([])
  const { t } = useI18n()
  const columns = [
    {
      type: 'globalIndex',
      label: '序号',
      label: t('table.index'),
      width: 72,
      align: 'center'
    },
    {
      prop: 'orderTypeLabel',
      label: '单据类型',
      label: t('pages.task.expand.orderType'),
      minWidth: 120,
      showOverflowTooltip: true
    },
    {
      prop: 'wkTypeLabel',
      label: '业务类型',
      label: t('pages.task.expand.wkType'),
      minWidth: 120,
      showOverflowTooltip: true
    },
    {
      prop: 'platWorkCode',
      label: '工单号',
      label: t('pages.task.expand.platWorkCode'),
      minWidth: 150,
      showOverflowTooltip: true
    },
    {
      prop: 'platItemId',
      label: '行号',
      label: t('pages.task.expand.platItemId'),
      minWidth: 100,
      showOverflowTooltip: true
    },
    {
      prop: 'matnrCode',
      label: '物料编码',
      label: t('table.materialCode'),
      minWidth: 150,
      showOverflowTooltip: true
    },
    {
      prop: 'maktx',
      label: '物料名称',
      label: t('table.materialName'),
      minWidth: 220,
      showOverflowTooltip: true
    },
    {
      prop: 'batch',
      label: '批次',
      label: t('table.batch'),
      minWidth: 140,
      showOverflowTooltip: true
    },
    {
      prop: 'unit',
      label: '单位',
      label: t('table.unit'),
      width: 100
    },
    {
      prop: 'anfme',
      label: '数量',
      label: t('pages.task.expand.anfme'),
      width: 100,
      align: 'right'
    },
    {
      prop: 'updateByText',
      label: '更新人',
      label: t('table.updateBy'),
      minWidth: 120,
      showOverflowTooltip: true
    },
    {
      prop: 'updateTimeText',
      label: '更新时间',
      label: t('table.updateTime'),
      minWidth: 180,
      showOverflowTooltip: true
    }
@@ -120,7 +122,7 @@
          pageSize: 50
        }),
        { records: [] },
        { timeoutMessage: '任务明细加载超时,已停止等待' }
        { timeoutMessage: t('pages.task.messages.itemsTimeout') }
      )
      rows.value = Array.isArray(response?.records)
        ? response.records.map((record) => normalizeTaskItemRow(record))
rsf-design/src/views/manager/task/modules/task-flow-step-dialog.vue
@@ -4,16 +4,16 @@
    width="80%"
    top="6vh"
    destroy-on-close
    title="流程步骤"
    :title="t('pages.task.flowStepDialog.title')"
    @update:model-value="emit('update:visible', $event)"
  >
    <div class="flex flex-col gap-4">
      <div class="rounded-xl border border-[var(--el-border-color-lighter)] bg-[var(--el-fill-color-blank)] px-4 py-3">
        <div class="text-sm text-[var(--art-gray-500)]">当前任务</div>
        <div class="text-sm text-[var(--art-gray-500)]">{{ t('pages.task.flowStepDialog.currentTask') }}</div>
        <div class="mt-1 flex flex-wrap items-center gap-x-6 gap-y-2 text-sm text-[var(--art-gray-900)]">
          <span>任务号:{{ taskRow?.taskCode || '--' }}</span>
          <span>任务状态:{{ taskRow?.taskStatusLabel || '--' }}</span>
          <span>任务类型:{{ taskRow?.taskTypeLabel || '--' }}</span>
          <span>{{ t('pages.task.detail.taskCode') }}:{{ taskRow?.taskCode || '--' }}</span>
          <span>{{ t('pages.task.detail.taskStatus') }}:{{ taskRow?.taskStatusLabel || '--' }}</span>
          <span>{{ t('pages.task.detail.taskType') }}:{{ taskRow?.taskTypeLabel || '--' }}</span>
        </div>
      </div>
@@ -31,6 +31,7 @@
<script setup>
  import { computed, reactive, ref, watch } from 'vue'
  import { useI18n } from 'vue-i18n'
  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
  import { fetchFlowStepInstancePage } from '@/api/flow-step-instance'
  import { normalizeFlowStepInstanceRow } from '@/views/system/flow-step-instance/flowStepInstancePage.helpers'
@@ -47,6 +48,7 @@
  })
  const emit = defineEmits(['update:visible'])
  const { t } = useI18n()
  const loading = ref(false)
  const rows = ref([])
@@ -59,55 +61,55 @@
  const columns = computed(() => [
    {
      type: 'globalIndex',
      label: '序号',
      label: t('table.index'),
      width: 72,
      align: 'center'
    },
    {
      prop: 'flowInstanceNo',
      label: '流程实例号',
      label: t('pages.task.flowStepDialog.flowInstanceNo'),
      minWidth: 160,
      showOverflowTooltip: true
    },
    {
      prop: 'stepCode',
      label: '步骤编码',
      label: t('pages.task.flowStepDialog.stepCode'),
      minWidth: 140,
      showOverflowTooltip: true
    },
    {
      prop: 'stepName',
      label: '步骤名称',
      label: t('pages.task.flowStepDialog.stepName'),
      minWidth: 180,
      showOverflowTooltip: true
    },
    {
      prop: 'stepType',
      label: '步骤类型',
      label: t('pages.task.flowStepDialog.stepType'),
      minWidth: 140,
      showOverflowTooltip: true
    },
    {
      prop: 'executeResult',
      label: '执行结果',
      label: t('pages.task.flowStepDialog.executeResult'),
      minWidth: 140,
      showOverflowTooltip: true
    },
    {
      prop: 'statusText',
      label: '状态',
      label: t('table.status'),
      width: 120,
      showOverflowTooltip: true
    },
    {
      prop: 'startTimeText',
      label: '开始时间',
      label: t('pages.task.flowStepDialog.startTime'),
      minWidth: 180,
      showOverflowTooltip: true
    },
    {
      prop: 'endTimeText',
      label: '结束时间',
      label: t('pages.task.flowStepDialog.endTime'),
      minWidth: 180,
      showOverflowTooltip: true
    }
@@ -138,7 +140,7 @@
          current: pagination.current,
          size: pagination.size
        },
        { timeoutMessage: '流程步骤加载超时,已停止等待' }
        { timeoutMessage: t('pages.task.flowStepDialog.timeout') }
      )
      rows.value = Array.isArray(response?.records)
        ? response.records.map((record) => normalizeFlowStepInstanceRow(record))
rsf-design/src/views/manager/task/taskPage.helpers.js
@@ -1,4 +1,5 @@
import { ElMessageBox } from 'element-plus'
import { $t } from '@/locales'
function normalizeText(value) {
  return String(value ?? '').trim()
@@ -97,19 +98,19 @@
  return [
    {
      key: 'view',
      label: '查看详情',
      label: $t('pages.task.actions.view'),
      icon: 'ri:eye-line'
    },
    {
      key: 'flowStep',
      label: '流程步骤',
      label: $t('pages.task.actions.flowStep'),
      icon: 'ri:node-tree'
    },
    ...(row.canComplete
      ? [
          {
            key: 'complete',
            label: '完成任务',
            label: $t('pages.task.actions.complete'),
            icon: 'ri:checkbox-circle-line',
            auth: 'update'
          }
@@ -119,7 +120,7 @@
      ? [
          {
            key: 'check',
            label: '盘点出库',
            label: $t('pages.task.actions.check'),
            icon: 'ri:file-check-line',
            auth: 'update'
          }
@@ -129,7 +130,7 @@
      ? [
          {
            key: 'pick',
            label: '拣料出库',
            label: $t('pages.task.actions.pick'),
            icon: 'ri:paint-line',
            auth: 'update'
          }
@@ -139,7 +140,7 @@
      ? [
          {
            key: 'top',
            label: '任务置顶',
            label: $t('pages.task.actions.top'),
            icon: 'ri:pushpin-line',
            auth: 'update'
          }
@@ -149,7 +150,7 @@
      ? [
          {
            key: 'remove',
            label: '取消任务',
            label: $t('pages.task.actions.remove'),
            icon: 'ri:close-circle-line',
            color: '#f56c6c',
            auth: 'delete'
@@ -160,9 +161,9 @@
}
export async function confirmTaskAction(message) {
  await ElMessageBox.confirm(message, '提示', {
  await ElMessageBox.confirm(message, $t('crud.confirm.deleteTitle'), {
    type: 'warning',
    confirmButtonText: '确定',
    cancelButtonText: '取消'
    confirmButtonText: $t('common.confirm'),
    cancelButtonText: $t('common.cancel')
  })
}
rsf-design/src/views/manager/task/taskTable.columns.js
@@ -1,4 +1,5 @@
import { h } from 'vue'
import { $t } from '@/locales'
import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
import { getTaskActionList } from './taskPage.helpers'
@@ -15,79 +16,79 @@
      : []),
    {
      prop: 'taskCode',
      label: '任务号',
      label: $t('pages.task.search.taskCode'),
      minWidth: 170,
      showOverflowTooltip: true
    },
    {
      prop: 'taskStatusLabel',
      label: '任务状态',
      label: $t('table.status'),
      minWidth: 140,
      showOverflowTooltip: true
    },
    {
      prop: 'taskTypeLabel',
      label: '任务类型',
      label: $t('table.type'),
      minWidth: 140,
      showOverflowTooltip: true
    },
    {
      prop: 'warehTypeLabel',
      label: '设备类型',
      label: $t('pages.task.detail.warehType'),
      minWidth: 120,
      showOverflowTooltip: true
    },
    {
      prop: 'orgLoc',
      label: '源库位',
      label: $t('pages.task.search.orgLoc'),
      minWidth: 140,
      showOverflowTooltip: true
    },
    {
      prop: 'orgSiteLabel',
      label: '源站点',
      label: $t('pages.task.detail.orgSite'),
      minWidth: 160,
      showOverflowTooltip: true
    },
    {
      prop: 'targLoc',
      label: '目标库位',
      label: $t('pages.task.search.targLoc'),
      minWidth: 140,
      showOverflowTooltip: true
    },
    {
      prop: 'targSiteLabel',
      label: '目标站点',
      label: $t('pages.task.detail.targSite'),
      minWidth: 160,
      showOverflowTooltip: true
    },
    {
      prop: 'barcode',
      label: '托盘码',
      label: $t('pages.task.search.barcode'),
      minWidth: 150,
      showOverflowTooltip: true
    },
    {
      prop: 'robotCode',
      label: '机器人编码',
      label: $t('pages.task.detail.robotCode'),
      minWidth: 150,
      showOverflowTooltip: true
    },
    {
      prop: 'sort',
      label: '优先级',
      label: $t('pages.task.detail.priority'),
      width: 100,
      align: 'right'
    },
    {
      prop: 'updateTimeText',
      label: '更新时间',
      label: $t('table.updateTime'),
      minWidth: 180,
      showOverflowTooltip: true
    },
    {
      prop: 'operation',
      label: '操作',
      label: $t('table.operation'),
      width: 120,
      align: 'center',
      fixed: 'right',
rsf-design/src/views/orders/asn-order/asnOrderPage.helpers.js
@@ -1,4 +1,6 @@
export const ASN_ORDER_REPORT_TITLE = '入库通知单报表'
import { $t } from '@/locales'
export const ASN_ORDER_REPORT_TITLE = $t('pages.orders.asnOrder.reportTitle')
export const ASN_ORDER_REPORT_STYLE = {
  titleAlign: 'center',
  titleLevel: 'strong',
@@ -8,13 +10,13 @@
}
const ASN_ORDER_STATUS_MAP = {
  0: { label: '未执行', tagType: 'info' },
  1: { label: '执行中', tagType: 'warning' },
  2: { label: '收货完成', tagType: 'success' },
  3: { label: '任务执行中', tagType: 'warning' },
  4: { label: '已完成', tagType: 'success' },
  8: { label: '取消', tagType: 'danger' },
  9: { label: '已关闭', tagType: 'info' }
  0: { label: $t('pages.orders.asnOrder.status.pending'), tagType: 'info' },
  1: { label: $t('pages.orders.asnOrder.status.running'), tagType: 'warning' },
  2: { label: $t('pages.orders.asnOrder.status.receiving'), tagType: 'success' },
  3: { label: $t('pages.orders.asnOrder.status.taskRunning'), tagType: 'warning' },
  4: { label: $t('pages.orders.asnOrder.status.completed'), tagType: 'success' },
  8: { label: $t('pages.orders.asnOrder.status.cancelled'), tagType: 'danger' },
  9: { label: $t('pages.orders.asnOrder.status.closed'), tagType: 'info' }
}
function normalizeText(value) {
@@ -241,22 +243,22 @@
  return [
    {
      key: 'view',
      label: '查看详情',
      label: $t('pages.orders.asnOrder.actions.view'),
      icon: 'ri:eye-line'
    },
    {
      key: 'items',
      label: '收货明细',
      label: $t('pages.orders.asnOrder.actions.items'),
      icon: 'ri:list-check-3'
    },
    {
      key: 'print',
      label: '打印',
      label: $t('pages.orders.asnOrder.actions.print'),
      icon: 'ri:printer-line'
    },
    {
      key: 'complete',
      label: '完成',
      label: $t('pages.orders.asnOrder.actions.complete'),
      icon: 'ri:check-line',
      color: 'var(--el-color-success)',
      disabled: !normalizedRow.canComplete
rsf-design/src/views/orders/asn-order/asnOrderTable.columns.js
@@ -1,5 +1,6 @@
import { h } from 'vue'
import { ElButton, ElTag } from 'element-plus'
import { $t } from '@/locales'
import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
import { getAsnOrderActionList } from './asnOrderPage.helpers'
@@ -13,55 +14,55 @@
    },
    {
      type: 'globalIndex',
      label: '序号',
      label: $t('table.index'),
      width: 72,
      align: 'center'
    },
    {
      prop: 'code',
      label: 'ASN单号',
      label: $t('pages.orders.asnOrder.search.code'),
      minWidth: 170,
      showOverflowTooltip: true
    },
    {
      prop: 'poCode',
      label: 'PO单号',
      label: $t('pages.orders.asnOrder.search.poCode'),
      minWidth: 170,
      showOverflowTooltip: true
    },
    {
      prop: 'wkTypeLabel',
      label: '业务类型',
      label: $t('pages.orders.asnOrder.search.wkType'),
      minWidth: 130,
      showOverflowTooltip: true
    },
    {
      prop: 'anfme',
      label: '应收数量',
      label: $t('pages.orders.asnOrder.detail.anfme'),
      width: 110,
      align: 'right'
    },
    {
      prop: 'qty',
      label: '已收数量',
      label: $t('pages.orders.asnOrder.detail.qty'),
      width: 110,
      align: 'right'
    },
    {
      prop: 'supplierName',
      label: '供应商',
      label: $t('pages.orders.asnOrder.search.supplierName'),
      minWidth: 180,
      showOverflowTooltip: true
    },
    {
      prop: 'purchaseUserName',
      label: '采购员',
      label: $t('pages.orders.asnOrder.search.purchaseUserName'),
      minWidth: 120,
      showOverflowTooltip: true
    },
    {
      prop: 'exceStatusText',
      label: '单据状态',
      label: $t('pages.orders.asnOrder.search.exceStatus'),
      width: 120,
      formatter: (row) =>
        h(
@@ -72,13 +73,13 @@
    },
    {
      prop: 'updateTimeText',
      label: '更新时间',
      label: $t('pages.orders.asnOrder.detail.updateTime'),
      minWidth: 170,
      showOverflowTooltip: true
    },
    {
      prop: 'operation',
      label: '操作',
      label: $t('table.operation'),
      width: 120,
      align: 'center',
      fixed: 'right',
@@ -95,59 +96,59 @@
  return [
    {
      type: 'globalIndex',
      label: '序号',
      label: $t('table.index'),
      width: 72,
      align: 'center'
    },
    {
      prop: 'matnrCode',
      label: '物料编码',
      label: $t('table.materialCode'),
      minWidth: 150,
      showOverflowTooltip: true
    },
    {
      prop: 'maktx',
      label: '物料名称',
      label: $t('table.materialName'),
      minWidth: 220,
      showOverflowTooltip: true
    },
    {
      prop: 'platItemId',
      label: 'PO行号',
      label: $t('pages.orders.asnOrder.table.poItemId'),
      width: 110
    },
    {
      prop: 'splrBatch',
      label: '供应商批次',
      label: $t('table.supplierBatch'),
      minWidth: 140,
      showOverflowTooltip: true
    },
    {
      prop: 'splrName',
      label: '供应商',
      label: $t('table.supplier'),
      minWidth: 180,
      showOverflowTooltip: true
    },
    {
      prop: 'stockUnit',
      label: '单位',
      label: $t('table.unit'),
      width: 100
    },
    {
      prop: 'anfme',
      label: '应收数量',
      label: $t('pages.orders.asnOrder.table.expectedQty'),
      width: 110,
      align: 'right'
    },
    {
      prop: 'qty',
      label: '已收数量',
      label: $t('pages.orders.asnOrder.table.receivedQty'),
      width: 110,
      align: 'right'
    },
    {
      prop: 'memo',
      label: '备注',
      label: $t('table.remark'),
      minWidth: 180,
      showOverflowTooltip: true
    }
@@ -158,55 +159,55 @@
  return [
    {
      type: 'globalIndex',
      label: '序号',
      label: $t('table.index'),
      width: 72,
      align: 'center'
    },
    {
      prop: 'code',
      label: 'PO单号',
      label: $t('pages.orders.asnOrder.search.poCode'),
      minWidth: 170,
      showOverflowTooltip: true
    },
    {
      prop: 'wkTypeLabel',
      label: '业务类型',
      label: $t('pages.orders.asnOrder.search.wkType'),
      minWidth: 130,
      showOverflowTooltip: true
    },
    {
      prop: 'source',
      label: '来源',
      label: $t('table.source'),
      minWidth: 140,
      showOverflowTooltip: true
    },
    {
      prop: 'supplierName',
      label: '供应商',
      label: $t('pages.orders.asnOrder.search.supplierName'),
      minWidth: 180,
      showOverflowTooltip: true
    },
    {
      prop: 'purchaseUserName',
      label: '采购员',
      label: $t('pages.orders.asnOrder.search.purchaseUserName'),
      minWidth: 120,
      showOverflowTooltip: true
    },
    {
      prop: 'remainingQty',
      label: '可建单数量',
      label: $t('pages.orders.asnOrder.table.remainingQty'),
      width: 120,
      align: 'right'
    },
    {
      prop: 'exceStatusText',
      label: 'PO状态',
      label: $t('pages.orders.asnOrder.table.poStatus'),
      minWidth: 120,
      showOverflowTooltip: true
    },
    {
      prop: 'operation',
      label: '操作',
      label: $t('table.operation'),
      width: 120,
      align: 'center',
      fixed: 'right',
@@ -223,65 +224,65 @@
  return [
    {
      type: 'globalIndex',
      label: '序号',
      label: $t('table.index'),
      width: 72,
      align: 'center'
    },
    {
      prop: 'platItemId',
      label: 'PO行号',
      label: $t('pages.orders.asnOrder.table.poItemId'),
      width: 110
    },
    {
      prop: 'matnrCode',
      label: '物料编码',
      label: $t('table.materialCode'),
      minWidth: 150,
      showOverflowTooltip: true
    },
    {
      prop: 'maktx',
      label: '物料名称',
      label: $t('table.materialName'),
      minWidth: 220,
      showOverflowTooltip: true
    },
    {
      prop: 'splrName',
      label: '供应商',
      label: $t('table.supplier'),
      minWidth: 180,
      showOverflowTooltip: true
    },
    {
      prop: 'splrBatch',
      label: '供应商批次',
      label: $t('table.supplierBatch'),
      minWidth: 140,
      showOverflowTooltip: true
    },
    {
      prop: 'unit',
      label: '单位',
      label: $t('table.unit'),
      width: 100
    },
    {
      prop: 'anfme',
      label: '采购数量',
      label: $t('pages.orders.asnOrder.table.purchaseQty'),
      width: 110,
      align: 'right'
    },
    {
      prop: 'qty',
      label: '已生成ASN数量',
      label: $t('pages.orders.asnOrder.table.generatedQty'),
      width: 130,
      align: 'right'
    },
    {
      prop: 'nromQty',
      label: '收货数量',
      label: $t('pages.orders.asnOrder.table.receivedQtyTotal'),
      width: 110,
      align: 'right'
    },
    {
      prop: 'selectAction',
      label: '操作提示',
      label: $t('table.operation'),
      minWidth: 120,
      formatter: () =>
        h(
@@ -291,7 +292,7 @@
            type: 'primary',
            class: '!px-0'
          },
          () => '随 PO 全量建单'
          () => $t('pages.orders.asnOrder.buttons.createByPo')
        )
    }
  ]
rsf-design/src/views/orders/asn-order/index.vue
@@ -12,7 +12,7 @@
      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
        <template #left>
          <ElSpace wrap>
            <ElButton type="primary" @click="poDialogVisible = true">按PO建单</ElButton>
            <ElButton type="primary" @click="poDialogVisible = true">{{ t('pages.orders.asnOrder.buttons.createByPo') }}</ElButton>
            <ListExportPrint
              :preview-visible="previewVisible"
              @update:previewVisible="handlePreviewVisibleChange"
@@ -61,6 +61,7 @@
<script setup>
  import { computed, reactive, ref } from 'vue'
  import { useRouter } from 'vue-router'
  import { useI18n } from 'vue-i18n'
  import { ElMessage, ElMessageBox } from 'element-plus'
  import { useUserStore } from '@/store/modules/user'
  import { useTable } from '@/hooks/core/useTable'
@@ -99,6 +100,7 @@
  const userStore = useUserStore()
  const router = useRouter()
  const { t } = useI18n()
  const reportTitle = ASN_ORDER_REPORT_TITLE
  const searchForm = ref(createAsnOrderSearchState())
  const selectedRows = ref([])
@@ -120,43 +122,43 @@
  const detailColumns = computed(() => createAsnOrderDetailItemColumns())
  const searchItems = computed(() => [
    {
      label: '关键字',
      label: t('pages.orders.asnOrder.search.condition'),
      key: 'condition',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入 ASN 单号/PO 单号/供应商'
        placeholder: t('pages.orders.asnOrder.placeholder.condition')
      }
    },
    {
      label: 'ASN单号',
      label: t('pages.orders.asnOrder.search.code'),
      key: 'code',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入 ASN 单号'
        placeholder: t('pages.orders.asnOrder.placeholder.code')
      }
    },
    {
      label: 'PO单号',
      label: t('pages.orders.asnOrder.search.poCode'),
      key: 'poCode',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入 PO 单号'
        placeholder: t('pages.orders.asnOrder.placeholder.poCode')
      }
    },
    {
      label: '业务类型',
      label: t('pages.orders.asnOrder.search.wkType'),
      key: 'wkType',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入业务类型'
        placeholder: t('pages.orders.asnOrder.placeholder.wkType')
      }
    },
    {
      label: '单据状态',
      label: t('pages.orders.asnOrder.search.exceStatus'),
      key: 'exceStatus',
      type: 'select',
      props: {
@@ -165,21 +167,21 @@
      }
    },
    {
      label: '供应商',
      label: t('pages.orders.asnOrder.search.supplierName'),
      key: 'supplierName',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入供应商'
        placeholder: t('pages.orders.asnOrder.placeholder.supplierName')
      }
    },
    {
      label: '采购员',
      label: t('pages.orders.asnOrder.search.purchaseUserName'),
      key: 'purchaseUserName',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入采购员'
        placeholder: t('pages.orders.asnOrder.placeholder.purchaseUserName')
      }
    }
  ])
@@ -220,13 +222,17 @@
      }
      if (action.key === 'complete') {
        await ElMessageBox.confirm(`确定完成入库通知单 ${row.code || ''} 吗?`, '完成确认', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        })
        await ElMessageBox.confirm(
          t('pages.orders.asnOrder.detail.completeConfirm', { code: row.code || '' }),
          t('pages.orders.asnOrder.detail.completeTitle'),
          {
            confirmButtonText: t('common.confirm'),
            cancelButtonText: t('common.cancel'),
            type: 'warning'
          }
        )
        await fetchCompleteAsnOrder(row.id)
        ElMessage.success('入库通知单已完成')
        ElMessage.success(t('pages.orders.asnOrder.detail.completeSuccess'))
        await refreshData()
        if (detailDrawerVisible.value && activeOrderId.value === row.id) {
          await loadDetailResources()
@@ -236,7 +242,7 @@
      if (error === 'cancel' || error?.message === 'cancel') {
        return
      }
      ElMessage.error(error?.message || '入库通知单操作失败')
      ElMessage.error(error?.message || t('pages.orders.asnOrder.detail.actionFailed'))
    }
  }
@@ -296,7 +302,7 @@
          size: detailPagination.size
        },
        {
          timeoutMessage: '入库通知单明细加载超时,已停止等待'
          timeoutMessage: t('pages.orders.asnOrder.detail.itemsTimeout')
        }
      )
rsf-design/src/views/orders/asn-order/modules/asn-order-create-by-po-dialog.vue
@@ -1,7 +1,7 @@
<template>
  <ElDialog
    :model-value="visible"
    title="按PO建单"
    :title="t('pages.orders.asnOrder.createByPoDialog.title')"
    width="90%"
    top="4vh"
    destroy-on-close
@@ -20,8 +20,8 @@
        <ElCard class="art-table-card" shadow="never">
          <template #header>
            <div class="flex items-center justify-between gap-3">
              <div class="text-sm font-medium">可建单 PO 列表</div>
              <ElButton :loading="purchaseLoading" @click="refreshPurchaseData">刷新</ElButton>
              <div class="text-sm font-medium">{{ t('pages.orders.asnOrder.createByPoDialog.purchaseList') }}</div>
              <ElButton :loading="purchaseLoading" @click="refreshPurchaseData">{{ t('common.actions.refresh') }}</ElButton>
            </div>
          </template>
@@ -39,12 +39,12 @@
          <template #header>
            <div class="flex items-center justify-between gap-3">
              <div class="flex flex-col">
                <span class="text-sm font-medium">PO 明细预览</span>
                <span class="text-sm font-medium">{{ t('pages.orders.asnOrder.createByPoDialog.purchasePreview') }}</span>
                <span class="text-xs text-[var(--art-gray-600)]">
                  {{
                    selectedPurchase.code
                      ? `当前选择:${selectedPurchase.code}`
                      : '请先在左侧选择一个 PO 单'
                      ? t('pages.orders.asnOrder.createByPoDialog.purchaseSelected', { code: selectedPurchase.code })
                      : t('pages.orders.asnOrder.createByPoDialog.purchaseEmpty')
                  }}
                </span>
              </div>
@@ -53,7 +53,7 @@
                :loading="itemLoading"
                @click="reloadSelectedPurchaseItems"
              >
                刷新明细
                {{ t('pages.orders.asnOrder.createByPoDialog.refreshItems') }}
              </ElButton>
            </div>
          </template>
@@ -68,19 +68,19 @@
        <div class="text-xs text-[var(--art-gray-600)]">
          {{
            selectedPurchase.id
              ? `将按 PO ${selectedPurchase.code} 的 ${purchaseItems.length} 条明细生成入库通知单`
              : '请选择一个可建单的 PO 单'
              ? t('pages.orders.asnOrder.createByPoDialog.purchaseGenerateHint', { code: selectedPurchase.code, count: purchaseItems.length })
              : t('pages.orders.asnOrder.createByPoDialog.purchaseGenerateEmpty')
          }}
        </div>
        <ElSpace>
          <ElButton @click="handleVisibleChange(false)">取消</ElButton>
          <ElButton @click="handleVisibleChange(false)">{{ t('common.cancel') }}</ElButton>
          <ElButton
            type="primary"
            :loading="submitLoading"
            :disabled="!selectedPurchase.id"
            @click="handleConfirm"
          >
            生成入库通知单
            {{ t('pages.orders.asnOrder.createByPoDialog.generate') }}
          </ElButton>
        </ElSpace>
      </div>
@@ -91,6 +91,7 @@
<script setup>
  import { computed, ref, watch } from 'vue'
  import { ElMessage } from 'element-plus'
  import { useI18n } from 'vue-i18n'
  import { useTable } from '@/hooks/core/useTable'
  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
  import {
@@ -122,6 +123,7 @@
  })
  const emit = defineEmits(['update:visible', 'success'])
  const { t } = useI18n()
  const searchForm = ref(createPurchaseFilterSearchState())
  const selectedPurchase = ref({})
@@ -131,39 +133,39 @@
  const searchItems = computed(() => [
    {
      label: '关键字',
      label: t('pages.orders.asnOrder.createByPoDialog.search.condition'),
      key: 'condition',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入 PO 单号/来源/供应商'
        placeholder: t('pages.orders.asnOrder.createByPoDialog.search.conditionPlaceholder')
      }
    },
    {
      label: 'PO单号',
      label: t('pages.orders.asnOrder.createByPoDialog.search.code'),
      key: 'code',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入 PO 单号'
        placeholder: t('pages.orders.asnOrder.createByPoDialog.search.codePlaceholder')
      }
    },
    {
      label: '来源',
      label: t('pages.orders.asnOrder.createByPoDialog.search.source'),
      key: 'source',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入来源'
        placeholder: t('pages.orders.asnOrder.createByPoDialog.search.sourcePlaceholder')
      }
    },
    {
      label: '供应商',
      label: t('pages.orders.asnOrder.createByPoDialog.search.supplierName'),
      key: 'supplierName',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入供应商'
        placeholder: t('pages.orders.asnOrder.createByPoDialog.search.supplierNamePlaceholder')
      }
    }
  ])
@@ -250,8 +252,8 @@
          current: 1,
          size: 200
        },
        {
          timeoutMessage: 'PO 明细加载超时,已停止等待'
          {
          timeoutMessage: t('pages.orders.asnOrder.createByPoDialog.messages.purchaseItemsTimeout')
        }
      )
@@ -272,7 +274,7 @@
            size: total
          },
          {
            timeoutMessage: 'PO 全量明细加载超时,已停止等待'
            timeoutMessage: t('pages.orders.asnOrder.createByPoDialog.messages.purchaseItemsAllTimeout')
          }
        )
        purchaseItems.value = Array.isArray(fullPage?.records)
@@ -313,11 +315,11 @@
  async function handleConfirm() {
    if (!selectedPurchase.value?.id) {
      ElMessage.warning('请先选择一个 PO 单')
      ElMessage.warning(t('pages.orders.asnOrder.createByPoDialog.messages.purchaseRequired'))
      return
    }
    if (!purchaseItems.value.length) {
      ElMessage.warning('当前 PO 单没有可建单明细')
      ElMessage.warning(t('pages.orders.asnOrder.createByPoDialog.messages.purchaseItemsEmpty'))
      return
    }
@@ -327,11 +329,11 @@
        purchaseId: selectedPurchase.value.id,
        items: purchaseItems.value.map((item) => ({ ...item }))
      })
      ElMessage.success('已根据 PO 单生成入库通知单')
      ElMessage.success(t('pages.orders.asnOrder.createByPoDialog.messages.createByPoSuccess'))
      emit('success')
      handleVisibleChange(false)
    } catch (error) {
      ElMessage.error(error?.message || '按 PO 建单失败')
      ElMessage.error(error?.message || t('pages.orders.asnOrder.createByPoDialog.messages.createByPoFailed'))
    } finally {
      submitLoading.value = false
    }
rsf-design/src/views/orders/asn-order/modules/asn-order-detail-drawer.vue
@@ -1,35 +1,35 @@
<template>
  <ElDrawer
    :model-value="visible"
    title="入库通知单详情"
    :title="t('pages.orders.asnOrder.detail.title')"
    size="1180px"
    destroy-on-close
    @update:model-value="handleVisibleChange"
  >
    <ElScrollbar class="h-[calc(100vh-180px)] pr-1">
      <div class="space-y-4">
        <ElDescriptions title="基础信息" :column="4" border>
          <ElDescriptionsItem label="ASN单号">{{ detail.code || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="PO单号">{{ detail.poCode || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="业务类型">{{ detail.wkTypeLabel || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="单据类型">{{ detail.orderTypeLabel || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="单据状态">{{ detail.exceStatusText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="采购组织">{{ detail.purchaseOrgName || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="采购员">{{ detail.purchaseUserName || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="供应商">{{ detail.supplierName || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="应收数量">{{ detail.anfme ?? 0 }}</ElDescriptionsItem>
          <ElDescriptionsItem label="已收数量">{{ detail.qty ?? 0 }}</ElDescriptionsItem>
          <ElDescriptionsItem label="更新时间">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="创建时间">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="备注" :span="4">{{ detail.memo || '--' }}</ElDescriptionsItem>
        <ElDescriptions :title="t('pages.orders.asnOrder.detail.baseInfo')" :column="4" border>
          <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.asnCode')">{{ detail.code || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.poCode')">{{ detail.poCode || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.wkType')">{{ detail.wkTypeLabel || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.orderType')">{{ detail.orderTypeLabel || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.status')">{{ detail.exceStatusText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.purchaseOrg')">{{ detail.purchaseOrgName || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.purchaseUser')">{{ detail.purchaseUserName || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.supplier')">{{ detail.supplierName || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.anfme')">{{ detail.anfme ?? 0 }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.qty')">{{ detail.qty ?? 0 }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.updateTime')">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.createTime')">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.memo')" :span="4">{{ detail.memo || '--' }}</ElDescriptionsItem>
        </ElDescriptions>
        <div class="space-y-3">
          <div class="flex items-center justify-between">
            <div class="text-sm font-medium text-[var(--art-gray-900)]">单据明细</div>
            <div class="text-sm font-medium text-[var(--art-gray-900)]">{{ t('pages.orders.asnOrder.detail.items') }}</div>
            <div class="flex items-center gap-3">
              <ElTag effect="plain">共 {{ data.length }} 条</ElTag>
              <ElButton :loading="loading" @click="$emit('refresh')">刷新</ElButton>
              <ElTag effect="plain">{{ t('pages.orders.asnOrder.detail.count', { count: data.length }) }}</ElTag>
              <ElButton :loading="loading" @click="$emit('refresh')">{{ t('common.actions.refresh') }}</ElButton>
            </div>
          </div>
@@ -48,7 +48,10 @@
</template>
<script setup>
  import { useI18n } from 'vue-i18n'
  defineOptions({ name: 'AsnOrderDetailDrawer' })
  const { t } = useI18n()
  defineProps({
    visible: { type: Boolean, default: false },
rsf-design/src/views/orders/delivery/deliveryPage.helpers.js
@@ -1,16 +1,18 @@
import { $t } from '@/locales'
const DELIVERY_STATUS_META = {
  1: { text: '正常', type: 'success', bool: true },
  0: { text: '禁用', type: 'danger', bool: false }
  1: { text: $t('pages.orders.delivery.status.normal'), type: 'success', bool: true },
  0: { text: $t('pages.orders.delivery.status.disabled'), type: 'danger', bool: false }
}
const DELIVERY_EXCE_STATUS_META = {
  0: { text: '未执行', type: 'info' },
  1: { text: '执行中', type: 'warning' },
  2: { text: '部分完成', type: 'primary' },
  3: { text: '已完成', type: 'success' }
  0: { text: $t('pages.orders.delivery.status.pending'), type: 'info' },
  1: { text: $t('pages.orders.delivery.status.running'), type: 'warning' },
  2: { text: $t('pages.orders.delivery.status.partial'), type: 'primary' },
  3: { text: $t('pages.orders.delivery.status.completed'), type: 'success' }
}
export const DELIVERY_REPORT_TITLE = 'DO单报表'
export const DELIVERY_REPORT_TITLE = $t('pages.orders.delivery.reportTitle')
export const DELIVERY_REPORT_STYLE = {
  titleAlign: 'center',
  titleLevel: 'strong',
@@ -38,7 +40,7 @@
  if (status === false || Number(status) === 0) {
    return DELIVERY_STATUS_META[0]
  }
  return { text: '未知', type: 'info', bool: false }
  return { text: $t('common.status.unknown'), type: 'info', bool: false }
}
function normalizeExceStatusMeta(exceStatus, exceStatusText) {
@@ -229,7 +231,7 @@
  orientation = DELIVERY_REPORT_STYLE.orientation
} = {}) {
  return {
    reportTitle: 'DO单明细报表',
    reportTitle: $t('pages.orders.delivery.detailReportTitle'),
    reportDate: previewMeta.reportDate,
    printedAt: previewMeta.printedAt,
    operator: previewMeta.operator,
@@ -246,12 +248,12 @@
  const actions = [
    {
      key: 'view',
      label: '查看详情',
      label: $t('pages.orders.delivery.actions.view'),
      icon: 'ri:eye-line'
    },
    {
      key: 'items',
      label: '明细',
      label: $t('pages.orders.delivery.actions.items'),
      icon: 'ri:list-check-3'
    }
  ]
@@ -259,7 +261,7 @@
  if (Number(normalizedRow.exceStatus) === 0) {
    actions.push({
      key: 'delete',
      label: '删除',
      label: $t('pages.orders.delivery.actions.delete'),
      icon: 'ri:delete-bin-5-line',
      color: 'var(--art-error)'
    })
rsf-design/src/views/orders/delivery/deliveryTable.columns.js
@@ -1,78 +1,79 @@
import { h } from 'vue'
import { ElTag } from 'element-plus'
import { $t } from '@/locales'
import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
import { getDeliveryActionList } from './deliveryPage.helpers.js'
export function createDeliveryTableColumns({ handleActionClick } = {}) {
  return [
    { type: 'selection', width: 48, align: 'center' },
    { type: 'globalIndex', label: '序号', width: 72, align: 'center' },
    { type: 'globalIndex', label: $t('table.index'), width: 72, align: 'center' },
    {
      prop: 'code',
      label: '单号',
      label: $t('pages.orders.delivery.search.code'),
      minWidth: 170,
      showOverflowTooltip: true,
      formatter: (row) => row.code || '--'
    },
    {
      prop: 'platId',
      label: 'ERP主单标识',
      label: $t('pages.orders.delivery.search.platId'),
      minWidth: 150,
      showOverflowTooltip: true,
      formatter: (row) => row.platId || '--'
    },
    {
      prop: 'typeLabel',
      label: '单据类型',
      label: $t('pages.orders.delivery.search.type'),
      minWidth: 120,
      showOverflowTooltip: true,
      formatter: (row) => row.typeLabel || '--'
    },
    {
      prop: 'wkTypeLabel',
      label: '业务类型',
      label: $t('pages.orders.delivery.search.wkType'),
      minWidth: 120,
      showOverflowTooltip: true,
      formatter: (row) => row.wkTypeLabel || '--'
    },
    {
      prop: 'source',
      label: '单据来源',
      label: $t('pages.orders.delivery.search.source'),
      minWidth: 140,
      showOverflowTooltip: true,
      formatter: (row) => row.source || '--'
    },
    {
      prop: 'anfme',
      label: '应收数量',
      label: $t('pages.orders.delivery.table.anfme'),
      width: 110,
      align: 'right',
      formatter: (row) => row.anfme ?? '--'
    },
    {
      prop: 'workQty',
      label: '执行中数量',
      label: $t('pages.orders.delivery.table.workQty'),
      width: 110,
      align: 'right',
      formatter: (row) => row.workQty ?? '--'
    },
    {
      prop: 'qty',
      label: '实收数量',
      label: $t('pages.orders.delivery.table.qty'),
      width: 110,
      align: 'right',
      formatter: (row) => row.qty ?? '--'
    },
    {
      prop: 'platCode',
      label: '平台单号',
      label: $t('pages.orders.delivery.table.platCode'),
      minWidth: 140,
      showOverflowTooltip: true,
      formatter: (row) => row.platCode || '--'
    },
    {
      prop: 'status',
      label: '状态',
      label: $t('pages.orders.transfer.search.status'),
      width: 96,
      align: 'center',
      formatter: (row) =>
@@ -80,7 +81,7 @@
    },
    {
      prop: 'exceStatusText',
      label: '执行状态',
      label: $t('pages.orders.delivery.search.exceStatus'),
      minWidth: 120,
      showOverflowTooltip: true,
      formatter: (row) =>
@@ -88,28 +89,28 @@
    },
    {
      prop: 'startTimeText',
      label: '计划出库时间',
      label: $t('pages.orders.delivery.table.startTime'),
      minWidth: 170,
      showOverflowTooltip: true,
      formatter: (row) => row.startTimeText || '--'
    },
    {
      prop: 'endTimeText',
      label: '计划出库结束时间',
      label: $t('pages.orders.delivery.table.endTime'),
      minWidth: 170,
      showOverflowTooltip: true,
      formatter: (row) => row.endTimeText || '--'
    },
    {
      prop: 'memo',
      label: '备注',
      label: $t('pages.orders.delivery.search.memo'),
      minWidth: 180,
      showOverflowTooltip: true,
      formatter: (row) => row.memo || '--'
    },
    {
      prop: 'operation',
      label: '操作',
      label: $t('table.operation'),
      width: 120,
      align: 'center',
      fixed: 'right',
rsf-design/src/views/orders/delivery/index.vue
@@ -56,6 +56,7 @@
<script setup>
  import { computed, reactive, ref } from 'vue'
  import { useRouter } from 'vue-router'
  import { useI18n } from 'vue-i18n'
  import { ElMessage, ElMessageBox } from 'element-plus'
  import { useUserStore } from '@/store/modules/user'
  import { useTable } from '@/hooks/core/useTable'
@@ -89,7 +90,8 @@
  const userStore = useUserStore()
  const router = useRouter()
  const reportTitle = 'DO单报表'
  const { t } = useI18n()
  const reportTitle = t('pages.orders.delivery.reportTitle')
  const searchForm = ref(createDeliverySearchState())
  const selectedRows = ref([])
  const detailDrawerVisible = ref(false)
@@ -107,75 +109,75 @@
  const reportQueryParams = computed(() => buildDeliverySearchParams(searchForm.value))
  const searchItems = computed(() => [
    {
      label: '关键字',
      label: t('pages.orders.delivery.search.condition'),
      key: 'condition',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入单号/ERP主单标识/平台单号'
        placeholder: t('pages.orders.delivery.placeholder.condition')
      }
    },
    {
      label: '单号',
      label: t('pages.orders.delivery.search.code'),
      key: 'code',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入单号'
        placeholder: t('pages.orders.delivery.placeholder.code')
      }
    },
    {
      label: 'ERP主单标识',
      label: t('pages.orders.delivery.search.platId'),
      key: 'platId',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入ERP主单标识'
        placeholder: t('pages.orders.delivery.placeholder.platId')
      }
    },
    {
      label: '单据类型',
      label: t('pages.orders.delivery.search.type'),
      key: 'type',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入单据类型'
        placeholder: t('pages.orders.delivery.placeholder.type')
      }
    },
    {
      label: '业务类型',
      label: t('pages.orders.delivery.search.wkType'),
      key: 'wkType',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入业务类型'
        placeholder: t('pages.orders.delivery.placeholder.wkType')
      }
    },
    {
      label: '单据来源',
      label: t('pages.orders.delivery.search.source'),
      key: 'source',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入单据来源'
        placeholder: t('pages.orders.delivery.placeholder.source')
      }
    },
    {
      label: '执行状态',
      label: t('pages.orders.delivery.search.exceStatus'),
      key: 'exceStatus',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入执行状态'
        placeholder: t('pages.orders.delivery.placeholder.exceStatus')
      }
    },
    {
      label: '备注',
      label: t('pages.orders.delivery.search.memo'),
      key: 'memo',
      type: 'input',
      props: {
        clearable: true,
        placeholder: '请输入备注'
        placeholder: t('pages.orders.delivery.placeholder.memo')
      }
    }
  ])
@@ -202,7 +204,7 @@
          })
        ),
        { records: [] },
        { timeoutMessage: '交接单明细加载超时,已停止等待' }
        { timeoutMessage: t('pages.orders.delivery.messages.itemsTimeout') }
      )
      const normalizedResponse = defaultResponseAdapter(response)
      detailItemRows.value = normalizedResponse.records.map((item) => normalizeDeliveryItemRow(item))
@@ -220,7 +222,7 @@
      const detail = await guardRequestWithMessage(
        fetchGetDeliveryDetail(row.id),
        {},
        { timeoutMessage: '交接单详情加载超时,已停止等待' }
        { timeoutMessage: t('pages.orders.delivery.messages.detailTimeout') }
      )
      detailData.value = normalizeDeliveryRow(detail)
    } finally {
@@ -240,23 +242,30 @@
      await loadDetailItems(row.id)
    } catch (error) {
      detailDrawerVisible.value = false
      ElMessage.error(error?.message || '获取交接单详情失败')
      ElMessage.error(error?.message || t('pages.orders.delivery.messages.detailLoadFailed'))
    }
  }
  async function handleDelete(row) {
    try {
      await ElMessageBox.confirm(`确定要删除交接单「${row.code || row.id}」吗?`, '删除确认', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      })
      await ElMessageBox.confirm(
        t('crud.confirm.deleteMessage', {
          entity: t('pages.orders.delivery.entity'),
          label: row.code || row.id
        }),
        t('crud.confirm.deleteTitle'),
        {
          confirmButtonText: t('common.confirm'),
          cancelButtonText: t('common.cancel'),
          type: 'warning'
        }
      )
      await fetchDeleteDelivery(row.id)
      ElMessage.success('删除成功')
      ElMessage.success(t('crud.messages.deleteSuccess'))
      await refreshRemove()
    } catch (error) {
      if (error !== 'cancel') {
        ElMessage.error(error?.message || '删除失败')
        ElMessage.error(error?.message || t('crud.messages.deleteFailed'))
      }
    }
  }
rsf-design/src/views/orders/delivery/modules/delivery-detail-drawer.vue
@@ -1,7 +1,7 @@
<template>
  <ElDrawer
    :model-value="visible"
    title="交接单详情"
    :title="t('pages.orders.delivery.detail.title')"
    size="1180px"
    destroy-on-close
    @update:model-value="handleVisibleChange"
@@ -11,42 +11,42 @@
        <ElSkeleton :rows="12" animated />
      </div>
      <div v-else class="space-y-4">
        <ElDescriptions title="基础信息" :column="2" border>
          <ElDescriptionsItem label="交接单号">{{ detail.code || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="ERP主单标识">{{ detail.platId || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="平台单号">{{ detail.platCode || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="单据类型">{{ detail.typeLabel || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="业务类型">{{ detail.wkTypeLabel || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="单据来源">{{ detail.source || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="应收数量">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="实收数量">{{ detail.qty ?? '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="执行中数量">{{ detail.workQty ?? '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="状态">
        <ElDescriptions :title="t('pages.orders.delivery.detail.baseInfo')" :column="2" border>
          <ElDescriptionsItem :label="t('pages.orders.delivery.detail.code')">{{ detail.code || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.delivery.detail.platId')">{{ detail.platId || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.delivery.detail.platCode')">{{ detail.platCode || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.delivery.detail.type')">{{ detail.typeLabel || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.delivery.detail.wkType')">{{ detail.wkTypeLabel || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.delivery.detail.source')">{{ detail.source || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.delivery.detail.anfme')">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.delivery.detail.qty')">{{ detail.qty ?? '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.delivery.detail.workQty')">{{ detail.workQty ?? '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.delivery.detail.status')">
            <ElTag :type="detail.statusType || 'info'" effect="light">
              {{ detail.statusText || '--' }}
            </ElTag>
          </ElDescriptionsItem>
          <ElDescriptionsItem label="执行状态">
          <ElDescriptionsItem :label="t('pages.orders.delivery.detail.exceStatus')">
            <ElTag :type="detail.exceStatusTagType || 'info'" effect="light">
              {{ detail.exceStatusText || '--' }}
            </ElTag>
          </ElDescriptionsItem>
          <ElDescriptionsItem label="备注" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="计划出库时间">{{ detail.startTimeText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="计划出库结束时间">{{ detail.endTimeText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.delivery.detail.memo')" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.delivery.detail.startTime')">{{ detail.startTimeText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.delivery.detail.endTime')">{{ detail.endTimeText || '--' }}</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 :title="t('pages.orders.delivery.detail.auditInfo')" :column="2" border>
          <ElDescriptionsItem :label="t('pages.orders.delivery.detail.createBy')">{{ detail.createByText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.delivery.detail.createTime')">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.delivery.detail.updateBy')">{{ detail.updateByText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.delivery.detail.updateTime')">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
        </ElDescriptions>
        <div class="space-y-3">
          <div class="flex items-center justify-between">
            <div class="text-sm font-medium text-[var(--art-gray-900)]">交接单明细</div>
            <ElTag effect="plain">共 {{ itemRows.length }} 条</ElTag>
            <div class="text-sm font-medium text-[var(--art-gray-900)]">{{ t('pages.orders.delivery.detail.items') }}</div>
            <ElTag effect="plain">{{ t('pages.orders.delivery.detail.count', { count: itemRows.length }) }}</ElTag>
          </div>
          <ArtTable
            :loading="itemsLoading"
@@ -64,9 +64,11 @@
<script setup>
  import { computed } from 'vue'
  import { useI18n } from 'vue-i18n'
  import ArtTable from '@/components/core/tables/art-table/index.vue'
  defineOptions({ name: 'DeliveryDetailDrawer' })
  const { t } = useI18n()
  const props = defineProps({
    visible: { type: Boolean, default: false },
@@ -80,88 +82,88 @@
  const emit = defineEmits(['update:visible', 'size-change', 'current-change'])
  const itemColumns = [
    { type: 'globalIndex', label: '序号', width: 72, align: 'center' },
    { type: 'globalIndex', label: t('table.index'), width: 72, align: 'center' },
    {
      prop: 'deliveryCode',
      label: '交接单号',
      label: t('pages.orders.delivery.table.deliveryCode'),
      minWidth: 160,
      showOverflowTooltip: true
    },
    {
      prop: 'platItemId',
      label: '平台行号',
      label: t('pages.orders.delivery.table.platItemId'),
      minWidth: 130,
      showOverflowTooltip: true
    },
    {
      prop: 'matnrCode',
      label: '物料编码',
      label: t('pages.orders.delivery.table.matnrCode'),
      minWidth: 150,
      showOverflowTooltip: true
    },
    {
      prop: 'maktx',
      label: '物料名称',
      label: t('pages.orders.delivery.table.maktx'),
      minWidth: 220,
      showOverflowTooltip: true
    },
    {
      prop: 'fieldsIndex',
      label: '动态字段索引',
      label: t('pages.orders.delivery.table.fieldsIndex'),
      minWidth: 150,
      showOverflowTooltip: true
    },
    {
      prop: 'unit',
      label: '单位',
      label: t('table.unit'),
      width: 90,
      align: 'center'
    },
    {
      prop: 'anfme',
      label: '数量',
      label: t('pages.orders.delivery.table.anfme'),
      width: 110,
      align: 'right'
    },
    {
      prop: 'workQty',
      label: '执行数量',
      label: t('pages.orders.delivery.table.workQty'),
      width: 110,
      align: 'right'
    },
    {
      prop: 'qty',
      label: '已出数量',
      label: t('pages.orders.delivery.table.qty'),
      width: 110,
      align: 'right'
    },
    {
      prop: 'nromQty',
      label: '标准包装',
      label: t('pages.orders.delivery.table.nromQty'),
      width: 110,
      align: 'right'
    },
    {
      prop: 'printQty',
      label: '打印数量',
      label: t('pages.orders.delivery.table.printQty'),
      width: 110,
      align: 'right'
    },
    {
      prop: 'splrName',
      label: '供应商名称',
      label: t('pages.orders.delivery.table.splrName'),
      minWidth: 180,
      showOverflowTooltip: true
    },
    {
      prop: 'splrCode',
      label: '供应商编码',
      label: t('pages.orders.delivery.table.splrCode'),
      minWidth: 140,
      showOverflowTooltip: true
    },
    {
      prop: 'splrBatch',
      label: '供应商批次',
      label: t('pages.orders.delivery.table.splrBatch'),
      minWidth: 140,
      showOverflowTooltip: true
    }
rsf-design/src/views/orders/transfer/index.vue
@@ -12,7 +12,7 @@
      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
        <template #left>
          <ElSpace wrap>
            <ElButton v-if="canCreate" type="primary" @click="showDialog('add')" v-ripple>新增调拨单</ElButton>
            <ElButton v-if="canCreate" type="primary" @click="showDialog('add')" v-ripple>{{ t('pages.orders.transfer.actions.add') }}</ElButton>
            <ElButton
              v-if="canDelete"
              type="danger"
@@ -20,7 +20,7 @@
              @click="handleBatchDelete"
              v-ripple
            >
              批量删除
              {{ t('common.actions.batchDelete') }}
            </ElButton>
            <ListExportPrint
              class="inline-flex"
@@ -78,6 +78,7 @@
<script setup>
  import { computed, onMounted, reactive, ref } from 'vue'
  import { useRouter } from 'vue-router'
  import { useI18n } from 'vue-i18n'
  import { ElMessage, ElMessageBox } from 'element-plus'
  import { useAuth } from '@/hooks/core/useAuth'
  import { useUserStore } from '@/store/modules/user'
@@ -131,6 +132,7 @@
  const { hasAuth } = useAuth()
  const userStore = useUserStore()
  const router = useRouter()
  const { t } = useI18n()
  const reportTitle = TRANSFER_REPORT_TITLE
  const searchForm = ref(createTransferSearchState())
@@ -157,37 +159,37 @@
  const reportQueryParams = computed(() => buildTransferSearchParams(searchForm.value))
  const searchItems = computed(() => [
    { label: '关键字', key: 'condition', type: 'input', props: { clearable: true, placeholder: '请输入单号/备注/仓库/库区' } },
    { label: '调拨单号', key: 'code', type: 'input', props: { clearable: true, placeholder: '请输入调拨单号' } },
    { label: t('pages.orders.transfer.search.condition'), key: 'condition', type: 'input', props: { clearable: true, placeholder: t('pages.orders.transfer.placeholder.condition') } },
    { label: t('pages.orders.transfer.search.code'), key: 'code', type: 'input', props: { clearable: true, placeholder: t('pages.orders.transfer.placeholder.code') } },
    {
      label: '调拨类型',
      label: t('pages.orders.transfer.search.type'),
      key: 'type',
      type: 'select',
      props: { clearable: true, filterable: true, options: typeOptions.value }
    },
    {
      label: '来源',
      label: t('pages.orders.transfer.search.source'),
      key: 'source',
      type: 'select',
      props: { clearable: true, options: getTransferSourceOptions() }
    },
    {
      label: '执行状态',
      label: t('pages.orders.transfer.search.exceStatus'),
      key: 'exceStatus',
      type: 'select',
      props: { clearable: true, options: getTransferExceStatusOptions() }
    },
    { label: '源仓库', key: 'orgWareName', type: 'input', props: { clearable: true, placeholder: '请输入源仓库' } },
    { label: '目标仓库', key: 'tarWareName', type: 'input', props: { clearable: true, placeholder: '请输入目标仓库' } },
    { label: '源库区', key: 'orgAreaName', type: 'input', props: { clearable: true, placeholder: '请输入源库区' } },
    { label: '目标库区', key: 'tarAreaName', type: 'input', props: { clearable: true, placeholder: '请输入目标库区' } },
    { label: t('pages.orders.transfer.search.orgWareName'), key: 'orgWareName', type: 'input', props: { clearable: true, placeholder: t('pages.orders.transfer.placeholder.orgWareName') } },
    { label: t('pages.orders.transfer.search.tarWareName'), key: 'tarWareName', type: 'input', props: { clearable: true, placeholder: t('pages.orders.transfer.placeholder.tarWareName') } },
    { label: t('pages.orders.transfer.search.orgAreaName'), key: 'orgAreaName', type: 'input', props: { clearable: true, placeholder: t('pages.orders.transfer.placeholder.orgAreaName') } },
    { label: t('pages.orders.transfer.search.tarAreaName'), key: 'tarAreaName', type: 'input', props: { clearable: true, placeholder: t('pages.orders.transfer.placeholder.tarAreaName') } },
    {
      label: '状态',
      label: t('pages.orders.transfer.search.status'),
      key: 'status',
      type: 'select',
      props: { clearable: true, options: getTransferStatusOptions() }
    },
    { label: '备注', key: 'memo', type: 'input', props: { clearable: true, placeholder: '请输入备注' } }
    { label: t('pages.orders.transfer.search.memo'), key: 'memo', type: 'input', props: { clearable: true, placeholder: t('pages.orders.transfer.placeholder.memo') } }
  ])
  function handleSelectionChange(rows) {
@@ -201,7 +203,7 @@
      const response = await guardRequestWithMessage(
        fetchTransferDetail(transferId),
        {},
        { timeoutMessage: '调拨单详情加载超时,已停止等待' }
        { timeoutMessage: t('pages.orders.transfer.messages.detailTimeout') }
      )
      detailData.value = normalizeTransferDetailRecord(response)
    } finally {
@@ -221,7 +223,7 @@
          })
        ),
        { records: [], total: 0, current: detailOrderPagination.current, size: detailOrderPagination.size },
        { timeoutMessage: '关联单据加载超时,已停止等待' }
        { timeoutMessage: t('pages.orders.transfer.messages.ordersTimeout') }
      )
      const normalized = defaultResponseAdapter(response)
      detailOrderRows.value = normalized.records.map((item) => normalizeTransferOrderRow(item))
@@ -231,7 +233,7 @@
    } catch (error) {
      detailOrderRows.value = []
      detailOrderPagination.total = 0
      ElMessage.error(error?.message || '获取关联单据失败')
      ElMessage.error(error?.message || t('pages.orders.transfer.messages.ordersLoadFailed'))
    } finally {
      detailOrdersLoading.value = false
    }
@@ -252,7 +254,7 @@
      detailDrawerVisible.value = false
      detailData.value = {}
      detailOrderRows.value = []
      ElMessage.error(error?.message || '获取调拨单详情失败')
      ElMessage.error(error?.message || t('pages.orders.transfer.messages.detailLoadFailed'))
    }
  }
@@ -261,26 +263,26 @@
      const detail = await guardRequestWithMessage(
        fetchTransferDetail(row.id),
        {},
        { timeoutMessage: '调拨单详情加载超时,已停止等待' }
        { timeoutMessage: t('pages.orders.transfer.messages.detailTimeout') }
      )
      showDialog('edit', detail)
    } catch (error) {
      ElMessage.error(error?.message || '获取调拨单详情失败')
      ElMessage.error(error?.message || t('pages.orders.transfer.messages.detailLoadFailed'))
    }
  }
  async function handlePublish(row) {
    try {
      await ElMessageBox.confirm(`确定要下发调拨单「${row.code || row.id}」吗?`, '下发确认', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
      await ElMessageBox.confirm(t('pages.orders.transfer.messages.publishConfirm', { code: row.code || row.id }), t('pages.orders.transfer.messages.publishTitle'), {
        confirmButtonText: t('common.confirm'),
        cancelButtonText: t('common.cancel'),
        type: 'warning'
      })
      const response = await fetchTransferPubOutStock({ id: row.id })
      if (response?.code !== 200 && response?.success !== true) {
        throw new Error(response?.message || '下发执行失败')
        throw new Error(response?.message || t('pages.orders.transfer.messages.publishFailed'))
      }
      ElMessage.success(response?.message || '下发执行成功')
      ElMessage.success(response?.message || t('pages.orders.transfer.messages.publishSuccess'))
      await refreshData()
      if (detailDrawerVisible.value && activeTransferId.value === row.id) {
        await loadTransferDetail(row.id)
@@ -288,7 +290,7 @@
      }
    } catch (error) {
      if (error === 'cancel' || error?.message === 'cancel') return
      ElMessage.error(error?.message || '下发执行失败')
      ElMessage.error(error?.message || t('pages.orders.transfer.messages.publishFailed'))
    }
  }
@@ -368,7 +370,7 @@
    saveRequest: fetchSaveTransfer,
    updateRequest: fetchUpdateTransfer,
    deleteRequest: fetchDeleteTransfer,
    entityName: '调拨单',
    entityName: t('pages.orders.transfer.entity'),
    resolveRecordLabel: (record) => record?.code || record?.id,
    refreshCreate,
    refreshUpdate,
@@ -387,7 +389,7 @@
    const response = await guardRequestWithMessage(
      fetchDictDataPage({ current: 1, pageSize: 200, dictTypeCode: 'sys_transfer_type', status: 1 }),
      { records: [] },
      { timeoutMessage: '调拨类型选项加载超时,已停止等待' }
      { timeoutMessage: t('pages.orders.transfer.messages.typeOptionsTimeout') }
    )
    typeOptions.value = resolveTransferTypeOptions(defaultResponseAdapter(response).records)
  }
@@ -396,7 +398,7 @@
    const response = await guardRequestWithMessage(
      fetchWarehouseAreasList(),
      { records: [] },
      { timeoutMessage: '库区选项加载超时,已停止等待' }
      { timeoutMessage: t('pages.orders.transfer.messages.areaOptionsTimeout') }
    )
    areaOptions.value = resolveTransferAreaOptions(defaultResponseAdapter(response).records)
  }
rsf-design/src/views/orders/transfer/modules/transfer-detail-drawer.vue
@@ -1,7 +1,7 @@
<template>
  <ElDrawer
    :model-value="visible"
    title="调拨单详情"
    :title="t('pages.orders.transfer.detail.title')"
    size="1200px"
    destroy-on-close
    @update:model-value="handleVisibleChange"
@@ -11,42 +11,42 @@
        <ElSkeleton :rows="12" animated />
      </div>
      <div v-else class="space-y-4">
        <ElDescriptions title="基础信息" :column="2" border>
          <ElDescriptionsItem label="调拨单号">{{ detail.code || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="调拨类型">{{ detail.typeLabel || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="来源">
        <ElDescriptions :title="t('pages.orders.transfer.detail.baseInfo')" :column="2" border>
          <ElDescriptionsItem :label="t('pages.orders.transfer.detail.code')">{{ detail.code || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.transfer.detail.type')">{{ detail.typeLabel || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.transfer.detail.source')">
            <ElTag :type="detail.sourceTagType || 'info'" effect="light">
              {{ detail.sourceText || '--' }}
            </ElTag>
          </ElDescriptionsItem>
          <ElDescriptionsItem label="执行状态">
          <ElDescriptionsItem :label="t('pages.orders.transfer.detail.exceStatus')">
            <ElTag :type="detail.exceStatusTagType || 'info'" effect="light">
              {{ detail.exceStatusText || '--' }}
            </ElTag>
          </ElDescriptionsItem>
          <ElDescriptionsItem label="源仓库">{{ detail.orgWareName || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="目标仓库">{{ detail.tarWareName || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="源库区">{{ detail.orgAreaName || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="目标库区">{{ detail.tarAreaName || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem label="状态">
          <ElDescriptionsItem :label="t('pages.orders.transfer.detail.orgWareName')">{{ detail.orgWareName || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.transfer.detail.tarWareName')">{{ detail.tarWareName || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.transfer.detail.orgAreaName')">{{ detail.orgAreaName || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.transfer.detail.tarAreaName')">{{ detail.tarAreaName || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.transfer.detail.status')">
            <ElTag :type="detail.statusType || 'info'" effect="light">
              {{ detail.statusText || '--' }}
            </ElTag>
          </ElDescriptionsItem>
          <ElDescriptionsItem label="备注" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.transfer.detail.memo')" :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 :title="t('pages.orders.transfer.detail.auditInfo')" :column="2" border>
          <ElDescriptionsItem :label="t('pages.orders.transfer.detail.createBy')">{{ detail.createByText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.transfer.detail.createTime')">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.transfer.detail.updateBy')">{{ detail.updateByText || '--' }}</ElDescriptionsItem>
          <ElDescriptionsItem :label="t('pages.orders.transfer.detail.updateTime')">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
        </ElDescriptions>
        <div class="space-y-3">
          <div class="flex items-center justify-between">
            <div class="text-sm font-medium text-[var(--art-gray-900)]">关联单据</div>
            <ElTag effect="plain">共 {{ orderRows.length }} 条</ElTag>
            <div class="text-sm font-medium text-[var(--art-gray-900)]">{{ t('pages.orders.transfer.detail.relatedOrders') }}</div>
            <ElTag effect="plain">{{ t('common.count', { count: orderRows.length }) }}</ElTag>
          </div>
          <ArtTable
            :loading="ordersLoading"
@@ -64,6 +64,7 @@
<script setup>
  import { computed } from 'vue'
  import { useI18n } from 'vue-i18n'
  import ArtTable from '@/components/core/tables/art-table/index.vue'
  import { createTransferOrderTableColumns } from '../transferTable.columns.js'
@@ -79,6 +80,7 @@
  })
  const emit = defineEmits(['update:visible', 'size-change', 'current-change'])
  const { t } = useI18n()
  const orderColumns = createTransferOrderTableColumns()
rsf-design/src/views/orders/transfer/modules/transfer-dialog.vue
@@ -1,40 +1,6 @@
<template>
  <ElDialog
    :title="dialogTitle"
    :model-value="visible"
    width="760px"
    align-center
    destroy-on-close
    @update:model-value="handleCancel"
    @closed="handleClosed"
  >
    <div class="mb-3 rounded-lg border border-[var(--art-border-color)] bg-[var(--art-bg-color)] px-3 py-2 text-xs text-[var(--art-text-gray-600)]">
      调拨单号由系统生成,新增时只需维护调拨类型、源/目标库区和备注。
    </div>
    <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" :loading="submitLoading" @click="handleSubmit">确定</ElButton>
      </span>
    </template>
  </ElDialog>
</template>
<script setup>
  import { computed, nextTick, reactive, ref, watch } from 'vue'
  import { useI18n } from 'vue-i18n'
  import ArtForm from '@/components/core/forms/art-form/index.vue'
  import {
    buildTransferDialogModel,
@@ -54,79 +20,82 @@
  const emit = defineEmits(['update:visible', 'submit'])
  const formRef = ref()
  const form = reactive(createTransferFormState())
  const { t } = useI18n()
  const isEdit = computed(() => props.dialogType === 'edit')
  const dialogTitle = computed(() => (isEdit.value ? '编辑调拨单' : '新增调拨单'))
  const dialogTitle = computed(() =>
    isEdit.value ? t('pages.orders.transfer.dialog.titleEdit') : t('pages.orders.transfer.dialog.titleAdd')
  )
  const rules = computed(() => ({
    type: [{ required: true, message: '请选择调拨类型', trigger: 'change' }],
    orgAreaId: [{ required: true, message: '请选择源库区', trigger: 'change' }],
    tarAreaId: [{ required: true, message: '请选择目标库区', trigger: 'change' }]
    type: [{ required: true, message: t('pages.orders.transfer.dialog.validation.type'), trigger: 'change' }],
    orgAreaId: [{ required: true, message: t('pages.orders.transfer.dialog.validation.orgAreaId'), trigger: 'change' }],
    tarAreaId: [{ required: true, message: t('pages.orders.transfer.dialog.validation.tarAreaId'), trigger: 'change' }]
  }))
  const formItems = computed(() => [
    {
      label: '调拨单号',
      label: t('pages.orders.transfer.dialog.code'),
      key: 'code',
      type: 'input',
      span: 24,
      props: {
        disabled: true,
        placeholder: '保存后自动生成'
        placeholder: t('pages.orders.transfer.dialog.placeholderCode')
      }
    },
    {
      label: '调拨类型',
      label: t('pages.orders.transfer.dialog.type'),
      key: 'type',
      type: 'select',
      props: {
        placeholder: '请选择调拨类型',
        placeholder: t('pages.orders.transfer.dialog.placeholderType'),
        clearable: true,
        filterable: true,
        options: props.typeOptions
      }
    },
    {
      label: '源库区',
      label: t('pages.orders.transfer.dialog.orgAreaId'),
      key: 'orgAreaId',
      type: 'select',
      props: {
        placeholder: '请选择源库区',
        placeholder: t('pages.orders.transfer.dialog.placeholderOrgAreaId'),
        clearable: true,
        filterable: true,
        options: props.areaOptions
      }
    },
    {
      label: '目标库区',
      label: t('pages.orders.transfer.dialog.tarAreaId'),
      key: 'tarAreaId',
      type: 'select',
      props: {
        placeholder: '请选择目标库区',
        placeholder: t('pages.orders.transfer.dialog.placeholderTarAreaId'),
        clearable: true,
        filterable: true,
        options: props.areaOptions
      }
    },
    {
      label: '状态',
      label: t('pages.orders.transfer.dialog.status'),
      key: 'status',
      type: 'select',
      props: {
        placeholder: '请选择状态',
        placeholder: t('pages.orders.transfer.dialog.placeholderStatus'),
        clearable: true,
        options: getTransferStatusOptions()
      }
    },
    {
      label: '备注',
      label: t('pages.orders.transfer.dialog.memo'),
      key: 'memo',
      type: 'input',
      span: 24,
      props: {
        type: 'textarea',
        rows: 3,
        placeholder: '请输入备注',
        placeholder: t('pages.orders.transfer.dialog.placeholderMemo'),
        clearable: true
      }
    }
@@ -182,3 +151,38 @@
    { deep: true }
  )
</script>
<template>
  <ElDialog
    :title="dialogTitle"
    :model-value="visible"
    width="760px"
    align-center
    destroy-on-close
    @update:model-value="handleCancel"
    @closed="handleClosed"
  >
    <div class="mb-3 rounded-lg border border-[var(--art-border-color)] bg-[var(--art-bg-color)] px-3 py-2 text-xs text-[var(--art-text-gray-600)]">
      {{ t('pages.orders.transfer.dialog.tip') }}
    </div>
    <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">{{ t('common.cancel') }}</ElButton>
        <ElButton type="primary" :loading="submitLoading" @click="handleSubmit">{{ t('common.confirm') }}</ElButton>
      </span>
    </template>
  </ElDialog>
</template>
rsf-design/src/views/orders/transfer/transferPage.helpers.js
@@ -1,22 +1,24 @@
import { $t } from '@/locales'
const TRANSFER_SOURCE_META = {
  1: { text: 'ERP系统', type: 'info' },
  2: { text: 'WMS系统生成', type: 'primary' },
  3: { text: 'EXCEL导入', type: 'warning' },
  4: { text: 'QMS系统', type: 'success' }
  1: { text: $t('pages.orders.transfer.status.sourceErp'), type: 'info' },
  2: { text: $t('pages.orders.transfer.status.sourceWms'), type: 'primary' },
  3: { text: $t('pages.orders.transfer.status.sourceExcel'), type: 'warning' },
  4: { text: $t('pages.orders.transfer.status.sourceQms'), type: 'success' }
}
const TRANSFER_EXCE_STATUS_META = {
  0: { text: '未执行', type: 'info' },
  1: { text: '执行中', type: 'warning' },
  2: { text: '执行完成', type: 'success' }
  0: { text: $t('pages.orders.transfer.status.pending'), type: 'info' },
  1: { text: $t('pages.orders.transfer.status.running'), type: 'warning' },
  2: { text: $t('pages.orders.transfer.status.completed'), type: 'success' }
}
const TRANSFER_STATUS_META = {
  1: { text: '正常', type: 'success', bool: true },
  0: { text: '冻结', type: 'danger', bool: false }
  1: { text: $t('pages.orders.transfer.status.normal'), type: 'success', bool: true },
  0: { text: $t('pages.orders.transfer.status.frozen'), type: 'danger', bool: false }
}
export const TRANSFER_REPORT_TITLE = '调拨单报表'
export const TRANSFER_REPORT_TITLE = $t('pages.orders.transfer.reportTitle')
export const TRANSFER_REPORT_STYLE = {
  titleAlign: 'center',
  titleLevel: 'strong',
@@ -102,25 +104,25 @@
export function getTransferSourceOptions() {
  return [
    { label: 'ERP系统', value: 1 },
    { label: 'WMS系统生成', value: 2 },
    { label: 'EXCEL导入', value: 3 },
    { label: 'QMS系统', value: 4 }
    { label: $t('pages.orders.transfer.status.sourceErp'), value: 1 },
    { label: $t('pages.orders.transfer.status.sourceWms'), value: 2 },
    { label: $t('pages.orders.transfer.status.sourceExcel'), value: 3 },
    { label: $t('pages.orders.transfer.status.sourceQms'), value: 4 }
  ]
}
export function getTransferStatusOptions() {
  return [
    { label: '正常', value: 1 },
    { label: '冻结', value: 0 }
    { label: $t('pages.orders.transfer.status.normal'), value: 1 },
    { label: $t('pages.orders.transfer.status.frozen'), value: 0 }
  ]
}
export function getTransferExceStatusOptions() {
  return [
    { label: '未执行', value: 0 },
    { label: '执行中', value: 1 },
    { label: '执行完成', value: 2 }
    { label: $t('pages.orders.transfer.status.pending'), value: 0 },
    { label: $t('pages.orders.transfer.status.running'), value: 1 },
    { label: $t('pages.orders.transfer.status.completed'), value: 2 }
  ]
}
@@ -217,7 +219,7 @@
}
export function normalizeTransferRow(record = {}) {
  const statusMeta = metaByValue(record.statusBool ?? record.status, TRANSFER_STATUS_META, '未知')
  const statusMeta = metaByValue(record.statusBool ?? record.status, TRANSFER_STATUS_META, $t('common.status.unknown'))
  const exceStatusMeta = metaByValue(record.exceStatus, TRANSFER_EXCE_STATUS_META, record.exceStatusText)
  const sourceMeta = metaByValue(record.source, TRANSFER_SOURCE_META, record.sourceText)
  return {
@@ -249,7 +251,7 @@
}
export function normalizeTransferOrderRow(record = {}) {
  const statusMeta = metaByValue(record.statusBool ?? record.status, TRANSFER_STATUS_META, '未知')
  const statusMeta = metaByValue(record.statusBool ?? record.status, TRANSFER_STATUS_META, $t('common.status.unknown'))
  const exceStatusMeta = metaByValue(record.exceStatus, TRANSFER_EXCE_STATUS_META, record.exceStatusText)
  return {
    ...record,
@@ -305,13 +307,13 @@
export function getTransferActionList(row = {}) {
  const normalizedRow = normalizeTransferRow(row)
  const actions = [
    { key: 'view', label: '查看详情', icon: 'ri:eye-line' },
    { key: 'items', label: '明细', icon: 'ri:list-check-3' },
    { key: 'edit', label: '编辑', icon: 'ri:pencil-line' }
    { key: 'view', label: $t('pages.orders.transfer.actions.view'), icon: 'ri:eye-line' },
    { key: 'items', label: $t('pages.orders.transfer.actions.items'), icon: 'ri:list-check-3' },
    { key: 'edit', label: $t('pages.orders.transfer.actions.edit'), icon: 'ri:pencil-line' }
  ]
  if (Number(normalizedRow.exceStatus) === 0) {
    actions.push({ key: 'publish', label: '下发执行', icon: 'ri:send-plane-line' })
    actions.push({ key: 'delete', label: '删除', icon: 'ri:delete-bin-5-line', color: 'var(--art-error)' })
    actions.push({ key: 'publish', label: $t('pages.orders.transfer.actions.publish'), icon: 'ri:send-plane-line' })
    actions.push({ key: 'delete', label: $t('pages.orders.transfer.actions.delete'), icon: 'ri:delete-bin-5-line', color: 'var(--art-error)' })
  }
  return actions
}
@@ -325,7 +327,7 @@
      if (value === void 0) return null
      return {
        value,
        label: normalizeText(item.name || item.areaName || item.code || item.warehouseId$ || item.warehouseName || `库区 ${value}`),
        label: normalizeText(item.name || item.areaName || item.code || item.warehouseId$ || item.warehouseName || `${$t('menu.warehouseAreas')} ${value}`),
        raw: item
      }
    })
@@ -341,7 +343,7 @@
      if (value === void 0) return null
      return {
        value,
        label: normalizeText(item.label || item.name || item.dictLabel || `类型 ${value}`)
        label: normalizeText(item.label || item.name || item.dictLabel || `${$t('pages.orders.transfer.dialog.type')} ${value}`)
      }
    })
    .filter(Boolean)
rsf-design/src/views/orders/transfer/transferTable.columns.js
@@ -1,29 +1,30 @@
import { h } from 'vue'
import { ElTag } from 'element-plus'
import { $t } from '@/locales'
import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
import { getTransferActionList } from './transferPage.helpers.js'
export function createTransferTableColumns({ handleActionClick } = {}) {
  return [
    { type: 'selection', width: 48, align: 'center' },
    { type: 'globalIndex', label: '序号', width: 72, align: 'center' },
    { type: 'globalIndex', label: $t('table.index'), width: 72, align: 'center' },
    {
      prop: 'code',
      label: '调拨单号',
      label: $t('pages.orders.transfer.search.code'),
      minWidth: 170,
      showOverflowTooltip: true,
      formatter: (row) => row.code || '--'
    },
    {
      prop: 'typeLabel',
      label: '调拨类型',
      label: $t('pages.orders.transfer.search.type'),
      minWidth: 120,
      showOverflowTooltip: true,
      formatter: (row) => row.typeLabel || '--'
    },
    {
      prop: 'sourceText',
      label: '来源',
      label: $t('pages.orders.transfer.search.source'),
      minWidth: 120,
      showOverflowTooltip: true,
      formatter: (row) =>
@@ -31,42 +32,42 @@
    },
    {
      prop: 'orgWareName',
      label: '源仓库',
      label: $t('pages.orders.transfer.search.orgWareName'),
      minWidth: 160,
      showOverflowTooltip: true,
      formatter: (row) => row.orgWareName || '--'
    },
    {
      prop: 'tarWareName',
      label: '目标仓库',
      label: $t('pages.orders.transfer.search.tarWareName'),
      minWidth: 160,
      showOverflowTooltip: true,
      formatter: (row) => row.tarWareName || '--'
    },
    {
      prop: 'orgAreaName',
      label: '源库区',
      label: $t('pages.orders.transfer.search.orgAreaName'),
      minWidth: 160,
      showOverflowTooltip: true,
      formatter: (row) => row.orgAreaName || '--'
    },
    {
      prop: 'tarAreaName',
      label: '目标库区',
      label: $t('pages.orders.transfer.search.tarAreaName'),
      minWidth: 160,
      showOverflowTooltip: true,
      formatter: (row) => row.tarAreaName || '--'
    },
    {
      prop: 'exceStatusText',
      label: '执行状态',
      label: $t('pages.orders.transfer.search.exceStatus'),
      minWidth: 120,
      formatter: (row) =>
        h(ElTag, { type: row.exceStatusTagType || 'info', effect: 'light' }, () => row.exceStatusText || '--')
    },
    {
      prop: 'statusText',
      label: '状态',
      label: $t('pages.orders.transfer.search.status'),
      width: 96,
      align: 'center',
      formatter: (row) =>
@@ -74,28 +75,28 @@
    },
    {
      prop: 'updateTimeText',
      label: '更新时间',
      label: $t('table.updateTime'),
      minWidth: 170,
      showOverflowTooltip: true,
      formatter: (row) => row.updateTimeText || '--'
    },
    {
      prop: 'createTimeText',
      label: '创建时间',
      label: $t('table.createTime'),
      minWidth: 170,
      showOverflowTooltip: true,
      formatter: (row) => row.createTimeText || '--'
    },
    {
      prop: 'memo',
      label: '备注',
      label: $t('pages.orders.transfer.search.memo'),
      minWidth: 180,
      showOverflowTooltip: true,
      formatter: (row) => row.memo || '--'
    },
    {
      prop: 'operation',
      label: '操作',
      label: $t('table.operation'),
      width: 120,
      align: 'center',
      fixed: 'right',
@@ -110,45 +111,45 @@
export function createTransferOrderTableColumns() {
  return [
    { type: 'globalIndex', label: '序号', width: 72, align: 'center' },
    { type: 'globalIndex', label: $t('table.index'), width: 72, align: 'center' },
    {
      prop: 'code',
      label: '关联单号',
      label: $t('pages.orders.transfer.detail.relatedCode'),
      minWidth: 170,
      showOverflowTooltip: true,
      formatter: (row) => row.code || '--'
    },
    {
      prop: 'poCode',
      label: '调拨单号',
      label: $t('pages.orders.transfer.detail.code'),
      minWidth: 170,
      showOverflowTooltip: true,
      formatter: (row) => row.poCode || '--'
    },
    {
      prop: 'typeLabel',
      label: '单据类型',
      label: $t('pages.orders.transfer.detail.type'),
      minWidth: 120,
      showOverflowTooltip: true,
      formatter: (row) => row.typeLabel || '--'
    },
    {
      prop: 'wkTypeLabel',
      label: '业务类型',
      label: $t('pages.orders.transfer.detail.wkType'),
      minWidth: 120,
      showOverflowTooltip: true,
      formatter: (row) => row.wkTypeLabel || '--'
    },
    {
      prop: 'exceStatusText',
      label: '执行状态',
      label: $t('pages.orders.transfer.detail.exceStatus'),
      minWidth: 120,
      formatter: (row) =>
        h(ElTag, { type: row.exceStatusTagType || 'info', effect: 'light' }, () => row.exceStatusText || '--')
    },
    {
      prop: 'statusText',
      label: '状态',
      label: $t('pages.orders.transfer.detail.status'),
      width: 96,
      align: 'center',
      formatter: (row) =>
@@ -156,42 +157,42 @@
    },
    {
      prop: 'anfme',
      label: '数量',
      label: $t('table.quantity'),
      width: 110,
      align: 'right',
      formatter: (row) => row.anfme ?? '--'
    },
    {
      prop: 'workQty',
      label: '执行数量',
      label: $t('pages.orders.transfer.detail.workQty'),
      width: 110,
      align: 'right',
      formatter: (row) => row.workQty ?? '--'
    },
    {
      prop: 'qty',
      label: '已完成数量',
      label: $t('pages.orders.transfer.detail.qty'),
      width: 110,
      align: 'right',
      formatter: (row) => row.qty ?? '--'
    },
    {
      prop: 'stationId',
      label: '站点',
      label: $t('pages.orders.transfer.detail.stationId'),
      minWidth: 120,
      showOverflowTooltip: true,
      formatter: (row) => row.stationId || '--'
    },
    {
      prop: 'businessTimeText',
      label: '业务时间',
      label: $t('pages.orders.transfer.detail.businessTime'),
      minWidth: 170,
      showOverflowTooltip: true,
      formatter: (row) => row.businessTimeText || '--'
    },
    {
      prop: 'createTimeText',
      label: '创建时间',
      label: $t('table.createTime'),
      minWidth: 170,
      showOverflowTooltip: true,
      formatter: (row) => row.createTimeText || '--'
rsf-design/src/views/system/common/useCrudPage.js
@@ -1,5 +1,6 @@
import { ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { $t } from '@/locales'
export function useCrudPage({
  createEmptyModel,
@@ -43,35 +44,35 @@
    try {
      if (dialogType.value === 'edit') {
        await updateRequest(payload)
        ElMessage.success('修改成功')
        ElMessage.success($t('crud.messages.updateSuccess'))
        closeDialog()
        await refreshUpdate?.()
        return
      }
      await saveRequest(payload)
      ElMessage.success('新增成功')
      ElMessage.success($t('crud.messages.createSuccess'))
      closeDialog()
      await refreshCreate?.()
    } catch (error) {
      ElMessage.error(error?.message || '提交失败')
      ElMessage.error(error?.message || $t('crud.messages.submitFailed'))
    }
  }
  const handleDelete = async (record) => {
    try {
      const recordLabel = resolveRecordLabel?.(record) || record?.id
      await ElMessageBox.confirm(`确定要删除${entityName}「${recordLabel}」吗?`, '删除确认', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
      await ElMessageBox.confirm($t('crud.confirm.deleteMessage', { entity: entityName, label: recordLabel }), $t('crud.confirm.deleteTitle'), {
        confirmButtonText: $t('common.confirm'),
        cancelButtonText: $t('common.cancel'),
        type: 'warning'
      })
      await deleteRequest(record.id)
      ElMessage.success('删除成功')
      ElMessage.success($t('crud.messages.deleteSuccess'))
      await refreshRemove?.()
    } catch (error) {
      if (error !== 'cancel') {
        ElMessage.error(error?.message || '删除失败')
        ElMessage.error(error?.message || $t('crud.messages.deleteFailed'))
      }
    }
  }
@@ -84,18 +85,18 @@
    if (!ids.length) return
    try {
      await ElMessageBox.confirm(`确定要批量删除选中的 ${ids.length} 个${entityName}吗?`, '批量删除确认', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
      await ElMessageBox.confirm($t('crud.confirm.batchDeleteMessage', { count: ids.length, entity: entityName }), $t('crud.confirm.batchDeleteTitle'), {
        confirmButtonText: $t('common.confirm'),
        cancelButtonText: $t('common.cancel'),
        type: 'warning'
      })
      await deleteRequest(ids.join(','))
      ElMessage.success('批量删除成功')
      ElMessage.success($t('crud.messages.batchDeleteSuccess'))
      selectedRows.value = []
      await refreshRemove?.()
    } catch (error) {
      if (error !== 'cancel') {
        ElMessage.error(error?.message || '批量删除失败')
        ElMessage.error(error?.message || $t('crud.messages.batchDeleteFailed'))
      }
    }
  }
rsf-design/src/views/system/common/usePrintExportPage.js
@@ -1,6 +1,7 @@
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
import { $t } from '@/locales'
export function usePrintExportPage({
  downloadFileName,
@@ -27,13 +28,13 @@
    try {
      const response = await guardRequestWithMessage(requestExport(payload), null, {
        timeoutMs,
        timeoutMessage: '导出请求超时,已停止等待'
        timeoutMessage: $t('message.exportTimeoutStopped')
      })
      if (!response) {
        return
      }
      if (!response.ok) {
        throw new Error(`导出失败 (${response.status})`)
        throw new Error($t('crud.messages.exportFailedWithStatus', { status: response.status }))
      }
      const blob = await response.blob()
@@ -45,9 +46,9 @@
      link.click()
      link.remove()
      window.URL.revokeObjectURL(downloadUrl)
      ElMessage.success('导出成功')
      ElMessage.success($t('crud.messages.exportSuccess'))
    } catch (error) {
      ElMessage.error(error?.message || '导出失败')
      ElMessage.error(error?.message || $t('crud.messages.exportFailed'))
    }
  }
@@ -62,7 +63,7 @@
    try {
      const records = await guardRequestWithMessage(resolvePrintRecords(payload), null, {
        timeoutMs,
        timeoutMessage: '打印数据加载超时,已停止等待'
        timeoutMessage: $t('message.printTimeoutStopped')
      })
      if (activePrintToken.value !== token) {
        return
@@ -79,7 +80,7 @@
      if (activePrintToken.value !== token) {
        return
      }
      ElMessage.error(error?.message || '打印失败')
      ElMessage.error(error?.message || $t('crud.messages.printFailed'))
    }
  }
rsf-design/src/views/system/menu/index.vue
@@ -1,4 +1,3 @@
<!-- 菜单管理页面 -->
<template>
  <div class="menu-page art-full-height">
    <ArtSearchBar
@@ -17,9 +16,9 @@
        @refresh="handleRefresh"
      >
        <template #left>
          <ElButton v-auth="'add'" @click="handleAddMenu" v-ripple>添加菜单</ElButton>
          <ElButton v-auth="'add'" @click="handleAddMenu" v-ripple>{{ t('pages.system.menu.buttons.add') }}</ElButton>
          <ElButton @click="toggleExpand" v-ripple>
            {{ isExpanded ? '收起' : '展开' }}
            {{ isExpanded ? t('pages.system.menu.actions.collapse') : t('pages.system.menu.actions.expand') }}
          </ElButton>
        </template>
      </ArtTableHeader>
@@ -48,6 +47,8 @@
</template>
<script setup>
  import { computed, nextTick, onMounted, reactive, ref } from 'vue'
  import { useI18n } from 'vue-i18n'
  import MenuDialog from './modules/menu-dialog.vue'
  import { formatMenuTitle } from '@/utils/router'
@@ -71,6 +72,7 @@
  } from './menuPage.helpers'
  defineOptions({ name: 'Menus' })
  const { t } = useI18n()
  const loading = ref(false)
  const isExpanded = ref(false)
@@ -89,13 +91,13 @@
  const formItems = computed(() => [
    {
      label: '菜单名称',
      label: t('pages.system.menu.search.name'),
      key: 'name',
      type: 'input',
      props: { clearable: true }
    },
    {
      label: '路由地址',
      label: t('pages.system.menu.search.route'),
      key: 'route',
      type: 'input',
      props: { clearable: true }
@@ -106,7 +108,7 @@
    loading.value = true
    try {
      const list = await guardRequestWithMessage(fetchGetMenuList({}), null, {
        timeoutMessage: '菜单加载超时,已停止等待'
        timeoutMessage: t('pages.system.menu.messages.loadTimeout')
      })
      if (list === null) {
        tableData.value = []
@@ -116,7 +118,7 @@
      tableData.value = Array.isArray(list) ? list : []
      menuTreeOptions.value = buildMenuTreeOptions(tableData.value, formatMenuTitle)
    } catch (error) {
      ElMessage.error(error?.message || '获取菜单失败')
      ElMessage.error(error?.message || t('pages.system.menu.messages.loadFailed'))
    } finally {
      loading.value = false
    }
@@ -183,59 +185,67 @@
  async function handleSubmit(formData) {
    const payload = buildMenuSubmitPayload(formData)
    if (payload.id && payload.id === payload.parentId) {
      ElMessage.error('上级菜单不能选择当前菜单')
      ElMessage.error(t('pages.system.menu.messages.menuSelfParent'))
      return
    }
    try {
      if (payload.id) {
        await fetchUpdateMenu(payload)
        ElMessage.success('修改成功')
        ElMessage.success(t('crud.messages.updateSuccess'))
      } else {
        await fetchSaveMenu(payload)
        ElMessage.success('新增成功')
        ElMessage.success(t('crud.messages.createSuccess'))
      }
      closeDialog()
      await loadMenuResources()
    } catch (error) {
      ElMessage.error(error?.message || '提交失败')
      ElMessage.error(error?.message || t('pages.system.menu.messages.submitFailed'))
    }
  }
  async function handleDeleteMenu(row) {
    try {
      await ElMessageBox.confirm(
        `确定要删除菜单「${getMenuDisplayTitle(row, formatMenuTitle)}」吗?删除后无法恢复`,
        '删除确认',
        t('pages.system.menu.messages.deleteMenuConfirm', {
          title: getMenuDisplayTitle(row, formatMenuTitle)
        }),
        t('crud.confirm.deleteTitle'),
        {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          confirmButtonText: t('common.confirm'),
          cancelButtonText: t('common.cancel'),
          type: 'warning'
        }
      )
      await fetchDeleteMenu(row.id)
      ElMessage.success('删除成功')
      ElMessage.success(t('crud.messages.deleteSuccess'))
      await loadMenuResources()
    } catch (error) {
      if (error !== 'cancel') {
        ElMessage.error(error?.message || '删除失败')
        ElMessage.error(error?.message || t('crud.messages.deleteFailed'))
      }
    }
  }
  async function handleDeleteAuth(row) {
    try {
      await ElMessageBox.confirm(`确定要删除权限「${row.name || row.authority || row.id}」吗?删除后无法恢复`, '删除确认', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      })
      await ElMessageBox.confirm(
        t('pages.system.menu.messages.deleteAuthConfirm', {
          title: row.name || row.authority || row.id
        }),
        t('crud.confirm.deleteTitle'),
        {
          confirmButtonText: t('common.confirm'),
          cancelButtonText: t('common.cancel'),
          type: 'warning'
        }
      )
      await fetchDeleteMenu(row.id)
      ElMessage.success('删除成功')
      ElMessage.success(t('crud.messages.deleteSuccess'))
      await loadMenuResources()
    } catch (error) {
      if (error !== 'cancel') {
        ElMessage.error(error?.message || '删除失败')
        ElMessage.error(error?.message || t('crud.messages.deleteFailed'))
      }
    }
  }
rsf-design/src/views/system/menu/menuPage.helpers.js
@@ -1,3 +1,5 @@
import { $t } from '@/locales'
export function createMenuSearchState() {
  return {
    name: '',
@@ -22,7 +24,7 @@
  if (!title) {
    return ''
  }
  return String(title).split('.').pop() || String(title)
  return String(title).trim()
}
export function getMenuDisplayTitle(row = {}, titleFormatter = defaultMenuTitleFormatter) {
@@ -44,15 +46,15 @@
}
export function getMenuTypeText(row = {}) {
  if (row.meta?.isAuthButton || Number(row.type) === 1) return '按钮'
  if (hasNestedMenus(row)) return '目录'
  return '菜单'
  if (row.meta?.isAuthButton || Number(row.type) === 1) return $t('pages.system.menu.types.button')
  if (hasNestedMenus(row)) return $t('pages.system.menu.types.directory')
  return $t('pages.system.menu.types.menu')
}
export function getMenuStatusMeta(status) {
  return normalizeMenuNumber(status, 1) === 1
    ? { text: '启用', type: 'success' }
    : { text: '禁用', type: 'danger' }
    ? { text: $t('common.status.enabled'), type: 'success' }
    : { text: $t('common.status.disabled'), type: 'danger' }
}
export function normalizeMenuTreeOptions(nodes = [], titleFormatter = defaultMenuTitleFormatter) {
@@ -70,7 +72,7 @@
export function buildMenuTreeOptions(tree = [], titleFormatter = defaultMenuTitleFormatter) {
  return [
    {
      label: '顶级菜单',
      label: $t('table.topLevelMenu'),
      value: 0,
      children: normalizeMenuTreeOptions(tree, titleFormatter)
    }
rsf-design/src/views/system/menu/menuTable.columns.js
@@ -1,5 +1,6 @@
import { h } from 'vue'
import { ElTag } from 'element-plus'
import { $t } from '@/locales'
import ArtSvgIcon from '@/components/core/base/art-svg-icon/index.vue'
import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
import {
@@ -21,13 +22,13 @@
  return [
    {
      prop: 'meta.title',
      label: '菜单名称',
      label: $t('pages.system.menu.search.name'),
      minWidth: 180,
      formatter: (row) => getMenuDisplayTitle(row, titleFormatter)
    },
    {
      prop: 'meta.icon',
      label: '图标预览',
      label: $t('table.iconPreview'),
      width: 96,
      align: 'center',
      formatter: (row) => {
@@ -47,14 +48,14 @@
    },
    {
      prop: 'type',
      label: '菜单类型',
      label: $t('table.menuType'),
      width: 110,
      formatter: (row) =>
        h(ElTag, { type: getMenuTypeTag(row), effect: 'light' }, () => getMenuTypeText(row))
    },
    {
      prop: 'route',
      label: '路由',
      label: $t('pages.system.menu.search.route'),
      minWidth: 180,
      formatter: (row) => {
        if (row.meta?.isAuthButton) return ''
@@ -63,7 +64,7 @@
    },
    {
      prop: 'component',
      label: '组件标识',
      label: $t('table.componentKey'),
      minWidth: 160,
      showOverflowTooltip: true,
      formatter: (row) => {
@@ -73,30 +74,32 @@
    },
    {
      prop: 'authority',
      label: '权限标识',
      label: $t('table.permissionKey'),
      minWidth: 180,
      formatter: (row) => {
        if (row.meta?.isAuthButton) {
          return row.authority || row.meta?.authMark || ''
        }
        if (!row.meta?.authList?.length) return row.authority || ''
        return `${row.meta.authList.length} 个权限标识`
        return $t('pages.system.menu.messages.authCount', {
          count: row.meta.authList.length
        })
      }
    },
    {
      prop: 'sort',
      label: '排序',
      label: $t('table.sort'),
      width: 90
    },
    {
      prop: 'id',
      label: 'ID',
      label: $t('table.id'),
      width: 96,
      align: 'center'
    },
    {
      prop: 'status',
      label: '状态',
      label: $t('table.status'),
      width: 100,
      formatter: (row) => {
        const statusMeta = getMenuStatusMeta(row.status)
@@ -105,14 +108,14 @@
    },
    {
      prop: 'memo',
      label: '备注',
      label: $t('table.remark'),
      minWidth: 180,
      showOverflowTooltip: true,
      formatter: (row) => row.memo || '-'
    },
    {
      prop: 'operation',
      label: '操作',
      label: $t('table.operation'),
      width: 180,
      align: 'center',
      formatter: (row) => {
@@ -133,7 +136,7 @@
          h(ArtButtonTable, {
            type: 'add',
            onClick: () => handleAddAuth(row),
            title: '新增权限'
            title: $t('pages.system.menu.actions.addAuth')
          }),
          h(ArtButtonTable, {
            type: 'edit',
rsf-design/src/views/system/menu/modules/menu-dialog.vue
@@ -21,22 +21,23 @@
    >
      <template #menuType>
        <ElRadioGroup v-model="form.menuType" :disabled="disableMenuType">
          <ElRadioButton value="menu">菜单</ElRadioButton>
          <ElRadioButton value="button">按钮</ElRadioButton>
          <ElRadioButton value="menu">{{ t('pages.system.menu.form.typeMenu') }}</ElRadioButton>
          <ElRadioButton value="button">{{ t('pages.system.menu.form.typeButton') }}</ElRadioButton>
        </ElRadioGroup>
      </template>
    </ArtForm>
    <template #footer>
      <span class="dialog-footer">
        <ElButton @click="handleCancel">取消</ElButton>
        <ElButton type="primary" @click="handleSubmit">确定</ElButton>
        <ElButton @click="handleCancel">{{ t('common.cancel') }}</ElButton>
        <ElButton type="primary" @click="handleSubmit">{{ t('common.confirm') }}</ElButton>
      </span>
    </template>
  </ElDialog>
</template>
<script setup>
  import { useI18n } from 'vue-i18n'
  import ArtForm from '@/components/core/forms/art-form/index.vue'
  const createMenuFormState = () => ({
@@ -62,30 +63,35 @@
  })
  const emit = defineEmits(['update:visible', 'submit'])
  const { t } = useI18n()
  const formRef = ref()
  const form = reactive(createMenuFormState())
  const isEdit = computed(() => Boolean(form.id))
  const dialogTitle = computed(() => `${isEdit.value ? '编辑' : '新建'}${form.menuType === 'button' ? '按钮' : '菜单'}`)
  const dialogTitle = computed(() =>
    form.menuType === 'button'
      ? t(isEdit.value ? 'pages.system.menu.form.titleEditButton' : 'pages.system.menu.form.titleAddButton')
      : t(isEdit.value ? 'pages.system.menu.form.titleEditMenu' : 'pages.system.menu.form.titleAddMenu')
  )
  const disableMenuType = computed(() => props.lockType || isEdit.value)
  const rules = computed(() => ({
    name: [{ required: true, message: form.menuType === 'button' ? '请输入权限名称' : '请输入菜单名称', trigger: 'blur' }],
    name: [{ required: true, message: form.menuType === 'button' ? t('pages.system.menu.form.validationButtonName') : t('pages.system.menu.form.validationMenuName'), trigger: 'blur' }],
    route:
      form.menuType === 'menu'
        ? [{ required: true, message: '请输入路由地址', trigger: 'blur' }]
        ? [{ required: true, message: t('pages.system.menu.form.validationRoute'), trigger: 'blur' }]
        : [],
    authority:
      form.menuType === 'button'
        ? [{ required: true, message: '请输入权限标识', trigger: 'blur' }]
        ? [{ required: true, message: t('pages.system.menu.form.validationAuthority'), trigger: 'blur' }]
        : []
  }))
  const formItems = computed(() => {
    const items = [
      { label: '菜单类型', key: 'menuType', span: 24 },
      { label: t('pages.system.menu.form.menuType'), key: 'menuType', span: 24 },
      {
        label: '上级菜单',
        label: t('pages.system.menu.form.parentId'),
        key: 'parentId',
        type: 'treeselect',
        span: 24,
@@ -96,19 +102,19 @@
            value: 'value',
            children: 'children'
          },
          placeholder: '请选择上级菜单',
          placeholder: t('pages.system.menu.form.placeholderParent'),
          checkStrictly: true,
          clearable: false,
          defaultExpandAll: true
        }
      },
      {
        label: form.menuType === 'button' ? '权限名称' : '菜单名称',
        label: form.menuType === 'button' ? t('pages.system.menu.form.nameButton') : t('pages.system.menu.form.nameMenu'),
        key: 'name',
        type: 'input',
        span: 24,
        props: {
          placeholder: form.menuType === 'button' ? '请输入权限名称' : '请输入菜单名称',
          placeholder: form.menuType === 'button' ? t('pages.system.menu.form.placeholderButtonName') : t('pages.system.menu.form.placeholderMenuName'),
          clearable: true
        }
      }
@@ -117,22 +123,22 @@
    if (form.menuType === 'menu') {
      items.push(
        {
          label: '路由地址',
          label: t('pages.system.menu.form.route'),
          key: 'route',
          type: 'input',
          span: 24,
          props: {
            placeholder: '请输入路由地址',
            placeholder: t('pages.system.menu.form.placeholderRoute'),
            clearable: true
          }
        },
        {
          label: '组件标识',
          label: t('pages.system.menu.form.component'),
          key: 'component',
          type: 'input',
          span: 24,
          props: {
            placeholder: '请输入组件标识',
            placeholder: t('pages.system.menu.form.placeholderComponent'),
            clearable: true
          }
        }
@@ -141,27 +147,27 @@
    items.push(
      {
        label: '权限标识',
        label: t('pages.system.menu.form.authority'),
        key: 'authority',
        type: 'input',
        span: 24,
        props: {
          placeholder: '请输入权限标识',
          placeholder: t('pages.system.menu.form.placeholderAuthority'),
          clearable: true
        }
      },
      {
        label: '图标',
        label: t('pages.system.menu.form.icon'),
        key: 'icon',
        type: 'input',
        span: 24,
        props: {
          placeholder: '请输入图标名称',
          placeholder: t('pages.system.menu.form.placeholderIcon'),
          clearable: true
        }
      },
      {
        label: '排序',
        label: t('pages.system.menu.form.sort'),
        key: 'sort',
        type: 'number',
        span: 24,
@@ -172,27 +178,27 @@
        }
      },
      {
        label: '状态',
        label: t('pages.system.menu.form.status'),
        key: 'status',
        type: 'select',
        span: 24,
        props: {
          placeholder: '请选择状态',
          placeholder: t('pages.system.menu.form.placeholderStatus'),
          options: [
            { label: '启用', value: 1 },
            { label: '禁用', value: 0 }
            { label: t('common.status.enabled'), value: 1 },
            { label: t('common.status.disabled'), value: 0 }
          ]
        }
      },
      {
        label: '备注',
        label: t('pages.system.menu.form.memo'),
        key: 'memo',
        type: 'input',
        span: 24,
        props: {
          type: 'textarea',
          rows: 3,
          placeholder: '请输入备注',
          placeholder: t('pages.system.menu.form.placeholderMemo'),
          clearable: true
        }
      }
rsf-design/src/views/system/role/index.vue
@@ -17,7 +17,7 @@
      >
        <template #left>
          <ElSpace wrap>
            <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>新增角色</ElButton>
            <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>{{ t('pages.system.role.buttons.add') }}</ElButton>
            <ElButton
              v-auth="'delete'"
              type="danger"
@@ -25,7 +25,7 @@
              @click="handleBatchDelete"
              v-ripple
            >
              批量删除
              {{ t('common.actions.batchDelete') }}
            </ElButton>
            <span v-auth="'query'" class="inline-flex">
              <ListExportPrint
@@ -76,6 +76,7 @@
<script setup>
  import { useUserStore } from '@/store/modules/user'
  import { useI18n } from 'vue-i18n'
  import {
    fetchExportRoleReport,
    fetchDeleteRole,
@@ -102,21 +103,22 @@
    buildRoleSavePayload,
    buildRoleSearchParams,
    createRoleSearchState,
    getRoleReportTitle,
    getRolePaginationKey,
    normalizeRoleListRow,
    ROLE_REPORT_STYLE,
    ROLE_REPORT_TITLE,
    resolveRoleReportColumns
  } from './rolePage.helpers'
  defineOptions({ name: 'Role' })
  const { t } = useI18n()
  const searchForm = ref(createRoleSearchState())
  const showSearchBar = ref(false)
  const permissionDialogVisible = ref(false)
  const permissionScopeType = ref('menu')
  const userStore = useUserStore()
  const reportTitle = ROLE_REPORT_TITLE
  const reportTitle = computed(() => getRoleReportTitle())
  const reportQueryParams = computed(() => buildRoleSearchParams(searchForm.value))
  const roleActionHandlers = {
    'scope-menu': (row) => openScopeDialog('menu', row),
@@ -186,7 +188,7 @@
    saveRequest: fetchSaveRole,
    updateRequest: fetchUpdateRole,
    deleteRequest: fetchDeleteRole,
    entityName: '角色',
    entityName: t('pages.system.role.entity'),
    resolveRecordLabel: (record) => record?.name || record?.code || record?.id,
    refreshCreate,
    refreshUpdate,
@@ -196,7 +198,7 @@
  const buildPreviewDialogMeta = (rows) => {
    const now = new Date()
    return {
      reportTitle,
      reportTitle: reportTitle.value,
      reportDate: now.toLocaleDateString('zh-CN'),
      printedAt: now.toLocaleString('zh-CN', { hour12: false }),
      operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
rsf-design/src/views/system/role/modules/role-edit-dialog.vue
@@ -21,8 +21,8 @@
    <template #footer>
      <span class="dialog-footer">
        <ElButton @click="handleCancel">取消</ElButton>
        <ElButton type="primary" @click="handleSubmit">确定</ElButton>
        <ElButton @click="handleCancel">{{ t('common.cancel') }}</ElButton>
        <ElButton type="primary" @click="handleSubmit">{{ t('common.confirm') }}</ElButton>
      </span>
    </template>
  </ElDialog>
@@ -31,6 +31,7 @@
<script setup>
  import ArtForm from '@/components/core/forms/art-form/index.vue'
  import { buildRoleDialogModel, createRoleFormState, getRoleStatusOptions } from '../rolePage.helpers'
  import { useI18n } from 'vue-i18n'
  const props = defineProps({
    visible: { required: false, default: false },
@@ -41,12 +42,15 @@
  const emit = defineEmits(['update:visible', 'submit'])
  const formRef = ref()
  const form = reactive(createRoleFormState())
  const { t } = useI18n()
  const isEdit = computed(() => props.dialogType === 'edit')
  const dialogTitle = computed(() => (isEdit.value ? '编辑角色' : '新增角色'))
  const dialogTitle = computed(() =>
    isEdit.value ? t('pages.system.role.dialog.editTitle') : t('pages.system.role.dialog.addTitle')
  )
  const rules = computed(() => ({
    name: [{ required: true, message: '请输入角色名称', trigger: 'blur' }]
    name: [{ required: true, message: t('pages.system.role.dialog.validationName'), trigger: 'blur' }]
  }))
  function createInputFormItem(label, key, placeholder, extraProps = {}, extraConfig = {}) {
@@ -79,10 +83,29 @@
  }
  const formItems = computed(() => [
    createInputFormItem('角色名称', 'name', '请输入角色名称'),
    createInputFormItem('角色编码', 'code', '请输入角色编码'),
    createSelectFormItem('状态', 'status', '请选择状态', getRoleStatusOptions()),
    createInputFormItem('备注', 'memo', '请输入备注', { type: 'textarea', rows: 3 }, { span: 24 })
    createInputFormItem(
      t('pages.system.role.dialog.name'),
      'name',
      t('pages.system.role.dialog.namePlaceholder')
    ),
    createInputFormItem(
      t('pages.system.role.dialog.code'),
      'code',
      t('pages.system.role.dialog.codePlaceholder')
    ),
    createSelectFormItem(
      t('pages.system.role.dialog.status'),
      'status',
      t('pages.system.role.dialog.statusPlaceholder'),
      getRoleStatusOptions()
    ),
    createInputFormItem(
      t('pages.system.role.dialog.memo'),
      'memo',
      t('pages.system.role.dialog.memoPlaceholder'),
      { type: 'textarea', rows: 3 },
      { span: 24 }
    )
  ])
  const resetForm = () => {
rsf-design/src/views/system/role/modules/role-permission-dialog.vue
@@ -1,14 +1,14 @@
<template>
  <ElDrawer
    :model-value="visible"
    title="角色权限"
    :title="t('pages.system.role.permission.title')"
    size="860px"
    destroy-on-close
    @update:model-value="handleVisibleChange"
    @closed="handleClosed"
  >
    <div class="mb-4 text-sm text-[var(--art-text-secondary)]">
      当前角色:{{ roleLabel }}
      {{ t('pages.system.role.permission.currentRole') }}{{ roleLabel }}
    </div>
    <ElTabs v-model="activeScopeType" class="role-scope-tabs">
@@ -24,21 +24,21 @@
        <div v-else class="space-y-3">
          <div class="flex items-center justify-between gap-3">
            <ElSpace wrap>
              <ElButton @click="handleSelectAll(config.scopeType)">全选</ElButton>
              <ElButton @click="handleClear(config.scopeType)">清空</ElButton>
              <ElButton @click="handleSelectAll(config.scopeType)">{{ t('pages.system.role.permission.selectAll') }}</ElButton>
              <ElButton @click="handleClear(config.scopeType)">{{ t('pages.system.role.permission.clear') }}</ElButton>
            </ElSpace>
            <ElButton type="primary" @click="handleSave(config.scopeType)">保存当前权限</ElButton>
            <ElButton type="primary" @click="handleSave(config.scopeType)">{{ t('pages.system.role.permission.saveCurrent') }}</ElButton>
          </div>
          <div class="flex items-center gap-3">
            <ElInput
              v-model.trim="scopeState[config.scopeType].condition"
              clearable
              placeholder="搜索权限树"
              :placeholder="t('pages.system.role.permission.searchPlaceholder')"
              @clear="handleSearch(config.scopeType)"
              @keyup.enter="handleSearch(config.scopeType)"
            />
            <ElButton @click="handleSearch(config.scopeType)">搜索</ElButton>
            <ElButton @click="handleSearch(config.scopeType)">{{ t('common.actions.search') }}</ElButton>
          </div>
          <ElScrollbar height="56vh">
@@ -56,7 +56,7 @@
                <div class="flex items-center gap-2">
                  <span>{{ resolveScopeNodeLabel(data) }}</span>
                  <ElTag v-if="data.isAuthButton" type="info" effect="plain" size="small">
                    按钮
                    {{ t('pages.system.role.permission.authButton') }}
                  </ElTag>
                </div>
              </template>
@@ -78,8 +78,10 @@
  } from '../rolePage.helpers'
  import { fetchGetRoleScopeList, fetchGetRoleScopeTree, fetchUpdateRoleScope } from '@/api/system-manage'
  import { resolveBackendMenuTitle } from '@/utils/backend-menu-title'
  import { formatMenuTitle } from '@/utils/router'
  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
  import { ElMessage } from 'element-plus'
  import { useI18n } from 'vue-i18n'
  const props = defineProps({
    visible: { required: false, default: false },
@@ -88,6 +90,7 @@
  })
  const emit = defineEmits(['update:visible', 'success'])
  const { t } = useI18n()
  const scopeConfigs = ['menu', 'pda', 'matnr', 'warehouse'].map((scopeType) => getRoleScopeConfig(scopeType))
  const activeScopeType = ref(props.scopeType || 'menu')
@@ -103,7 +106,7 @@
    set: (value) => emit('update:visible', value)
  })
  const roleLabel = computed(() => props.roleData?.name || props.roleData?.code || '未选择角色')
  const roleLabel = computed(() => props.roleData?.name || props.roleData?.code || t('pages.system.role.permission.unselected'))
  function createScopeTabState() {
    return {
@@ -138,7 +141,7 @@
        Promise.all([selectionRequest, treeRequest]),
        null,
        {
          timeoutMessage: `${config.title}加载超时,已停止等待`
          timeoutMessage: t('pages.system.role.permission.scopeLoadTimeout', { title: config.title })
        }
      )
      if (!guardedResult) {
@@ -154,7 +157,7 @@
      state.halfCheckedKeys = []
      state.loaded = true
    } catch (error) {
      ElMessage.error(error?.message || `加载${config.title}失败`)
      ElMessage.error(error?.message || t('pages.system.role.permission.scopeLoadFailed', { title: config.title }))
    } finally {
      state.loading = false
      nextTick(() => {
@@ -215,11 +218,11 @@
          scopeState[scopeType].halfCheckedKeys
        )
      )
      ElMessage.success('权限保存成功')
      ElMessage.success(t('pages.system.role.permission.saveSuccess'))
      emit('success')
      visible.value = false
    } catch (error) {
      ElMessage.error(error?.message || '权限保存失败')
      ElMessage.error(error?.message || t('pages.system.role.permission.saveFailed'))
    }
  }
@@ -236,7 +239,11 @@
    if (!rawLabel) {
      return ''
    }
    return resolveBackendMenuTitle(rawLabel)
    if (activeScopeType.value === 'menu') {
      const resolvedTitle = resolveBackendMenuTitle(rawLabel, data?.component || '')
      return resolvedTitle ? formatMenuTitle(resolvedTitle) : ''
    }
    return rawLabel
  }
  const handleClosed = () => {
rsf-design/src/views/system/role/modules/role-search.vue
@@ -11,6 +11,7 @@
<script setup>
  import { createRoleSearchState, getRoleStatusOptions } from '../rolePage.helpers'
  import { useI18n } from 'vue-i18n'
  const props = defineProps({
    modelValue: { required: true }
@@ -18,6 +19,7 @@
  const emit = defineEmits(['update:modelValue', 'search', 'reset'])
  const searchBarRef = ref()
  const { t } = useI18n()
  const formData = computed({
    get: () => props.modelValue,
@@ -50,11 +52,32 @@
  }
  const formItems = computed(() => [
    createInputSearchItem('角色名称', 'name', '请输入角色名称'),
    createInputSearchItem('角色编码', 'code', '请输入角色编码'),
    createInputSearchItem('备注', 'memo', '请输入备注'),
    createInputSearchItem('关键字', 'condition', '输入关键字搜索'),
    createSelectSearchItem('状态', 'status', '请选择状态', getRoleStatusOptions())
    createInputSearchItem(
      t('pages.system.role.search.name'),
      'name',
      t('pages.system.role.search.namePlaceholder')
    ),
    createInputSearchItem(
      t('pages.system.role.search.code'),
      'code',
      t('pages.system.role.search.codePlaceholder')
    ),
    createInputSearchItem(
      t('pages.system.role.search.memo'),
      'memo',
      t('pages.system.role.search.memoPlaceholder')
    ),
    createInputSearchItem(
      t('pages.system.role.search.condition'),
      'condition',
      t('pages.system.role.search.conditionPlaceholder')
    ),
    createSelectSearchItem(
      t('pages.system.role.search.status'),
      'status',
      t('pages.system.role.search.statusPlaceholder'),
      getRoleStatusOptions()
    )
  ])
  function handleReset() {
rsf-design/src/views/system/role/rolePage.helpers.js
@@ -1,12 +1,9 @@
const ROLE_STATUS_META = {
  1: { type: 'success', text: '正常', bool: true },
  0: { type: 'danger', text: '禁用', bool: false }
}
import { $t } from '@/locales'
const ROLE_STATUS_OPTIONS = [
  { label: '正常', value: 1 },
  { label: '禁用', value: 0 }
]
const ROLE_STATUS_META = {
  1: { type: 'success', key: 'common.status.normal', bool: true },
  0: { type: 'danger', key: 'common.status.disabled', bool: false }
}
export function createRoleSearchState() {
  return {
@@ -37,7 +34,10 @@
}
export function getRoleStatusOptions() {
  return ROLE_STATUS_OPTIONS.map((option) => ({ ...option }))
  return [
    { label: $t('common.status.normal'), value: 1 },
    { label: $t('common.status.disabled'), value: 0 }
  ]
}
export function buildRoleSearchParams(params = {}) {
@@ -69,19 +69,21 @@
}
const ROLE_REPORT_COLUMNS = [
  { source: 'name', label: '角色名称' },
  { source: 'code', label: '角色编码' },
  { source: 'statusText', label: '状态' },
  { source: 'memo', label: '备注' },
  { source: 'createTimeText', label: '创建时间' },
  { source: 'updateTimeText', label: '更新时间' }
  { source: 'name', labelKey: 'pages.system.role.table.name' },
  { source: 'code', labelKey: 'pages.system.role.table.code' },
  { source: 'statusText', labelKey: 'pages.system.role.table.status' },
  { source: 'memo', labelKey: 'pages.system.role.table.memo' },
  { source: 'createTimeText', labelKey: 'pages.system.role.table.createTime' },
  { source: 'updateTimeText', labelKey: 'pages.system.role.table.updateTime' }
]
const ROLE_REPORT_SOURCE_ALIAS = {
  status: 'statusText'
}
export const ROLE_REPORT_TITLE = '角色管理报表'
export function getRoleReportTitle() {
  return $t('pages.system.role.reportTitle')
}
export const ROLE_REPORT_STYLE = {
  titleAlign: 'center',
@@ -89,7 +91,10 @@
}
export function getRoleReportColumns() {
  return ROLE_REPORT_COLUMNS.map((column) => ({ ...column }))
  return ROLE_REPORT_COLUMNS.map((column) => ({
    source: column.source,
    label: $t(column.labelKey)
  }))
}
export function resolveRoleReportColumns(columns = []) {
@@ -116,7 +121,7 @@
      const allowedColumn = allowedColumns.get(source)
      return {
        source,
        label: column.label || allowedColumn.label
        label: column.label || $t(allowedColumn.labelKey)
      }
    })
    .filter(Boolean)
@@ -137,7 +142,7 @@
  titleLevel = ROLE_REPORT_STYLE.titleLevel
} = {}) {
  return {
    reportTitle: ROLE_REPORT_TITLE,
    reportTitle: getRoleReportTitle(),
    reportDate: previewMeta.reportDate,
    printedAt: previewMeta.printedAt,
    operator: previewMeta.operator,
@@ -168,7 +173,7 @@
  return {
    ...record,
    statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
    statusText: statusMeta.text,
    statusText: $t(statusMeta.key),
    statusType: statusMeta.type,
    createTimeText: normalizeText(record.createTime$ || record.createTime),
    updateTimeText: normalizeText(record.updateTime$ || record.updateTime)
@@ -182,32 +187,32 @@
  if (status === false || status === 0 || status === '0') {
    return ROLE_STATUS_META[0]
  }
  return { type: 'info', text: '未知', bool: false }
  return { type: 'info', key: 'common.status.unknown', bool: false }
}
export function getRoleScopeConfig(scopeType) {
  const configMap = {
    menu: {
      scopeType: 'menu',
      title: '网页权限',
      title: $t('pages.system.role.scopes.menu'),
      listUrl: '/role/scope/list',
      treeUrl: '/menu/tree'
    },
    pda: {
      scopeType: 'pda',
      title: 'PDA权限',
      title: $t('pages.system.role.scopes.pda'),
      listUrl: '/rolePda/scope/list',
      treeUrl: '/menuPda/tree'
    },
    matnr: {
      scopeType: 'matnr',
      title: '物料权限',
      title: $t('pages.system.role.scopes.matnr'),
      listUrl: '/roleMatnr/scope/list',
      treeUrl: '/menuMatnrGroup/tree'
    },
    warehouse: {
      scopeType: 'warehouse',
      title: '仓库权限',
      title: $t('pages.system.role.scopes.warehouse'),
      listUrl: '/roleWarehouse/scope/list',
      treeUrl: '/menuWarehouse/tree'
    }
rsf-design/src/views/system/role/roleTable.columns.js
@@ -1,47 +1,50 @@
import { h } from 'vue'
import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
import { ElTag } from 'element-plus'
import { $t } from '@/locales'
import { getRoleStatusMeta } from './rolePage.helpers'
const ROLE_MORE_ACTIONS = [
  {
    key: 'scope-menu',
    label: '网页权限',
    icon: 'ri:layout-2-line',
    auth: 'edit'
  },
  {
    key: 'scope-pda',
    label: 'PDA权限',
    icon: 'ri:smartphone-line',
    auth: 'edit'
  },
  {
    key: 'scope-matnr',
    label: '物料权限',
    icon: 'ri:archive-line',
    auth: 'edit'
  },
  {
    key: 'scope-warehouse',
    label: '仓库权限',
    icon: 'ri:store-2-line',
    auth: 'edit'
  },
  {
    key: 'edit',
    label: '编辑角色',
    icon: 'ri:edit-2-line',
    auth: 'edit'
  },
  {
    key: 'delete',
    label: '删除角色',
    icon: 'ri:delete-bin-4-line',
    color: '#f56c6c',
    auth: 'delete'
  }
]
function createRoleMoreActions() {
  return [
    {
      key: 'scope-menu',
      label: $t('pages.system.role.actions.scopeMenu'),
      icon: 'ri:layout-2-line',
      auth: 'edit'
    },
    {
      key: 'scope-pda',
      label: $t('pages.system.role.actions.scopePda'),
      icon: 'ri:smartphone-line',
      auth: 'edit'
    },
    {
      key: 'scope-matnr',
      label: $t('pages.system.role.actions.scopeMatnr'),
      icon: 'ri:archive-line',
      auth: 'edit'
    },
    {
      key: 'scope-warehouse',
      label: $t('pages.system.role.actions.scopeWarehouse'),
      icon: 'ri:store-2-line',
      auth: 'edit'
    },
    {
      key: 'edit',
      label: $t('pages.system.role.actions.edit'),
      icon: 'ri:edit-2-line',
      auth: 'edit'
    },
    {
      key: 'delete',
      label: $t('pages.system.role.actions.delete'),
      icon: 'ri:delete-bin-4-line',
      color: '#f56c6c',
      auth: 'delete'
    }
  ]
}
function createTextColumn(prop, label, minWidth, extra = {}) {
  return {
@@ -68,27 +71,27 @@
export function createRoleTableColumns(handleActionClick) {
  return [
    { type: 'selection', width: 52, fixed: 'left' },
    createTextColumn('name', '角色名称', 140),
    createTextColumn('code', '角色编码', 140),
    createTextColumn('memo', '备注', 180),
    createTagColumn('status', '状态', 120, (row) => getRoleStatusMeta(row.statusBool ?? row.status)),
    createTextColumn('updateTimeText', '更新时间', 180, {
    createTextColumn('name', $t('pages.system.role.table.name'), 140),
    createTextColumn('code', $t('pages.system.role.table.code'), 140),
    createTextColumn('memo', $t('pages.system.role.table.memo'), 180),
    createTagColumn('status', $t('pages.system.role.table.status'), 120, (row) => getRoleStatusMeta(row.statusBool ?? row.status)),
    createTextColumn('updateTimeText', $t('pages.system.role.table.updateTime'), 180, {
      sortable: true,
      formatter: (row) => row.updateTimeText || '-'
    }),
    createTextColumn('createTimeText', '创建时间', 180, {
    createTextColumn('createTimeText', $t('pages.system.role.table.createTime'), 180, {
      sortable: true,
      formatter: (row) => row.createTimeText || '-'
    }),
    {
      prop: 'operation',
      label: '操作',
      label: $t('pages.system.role.table.operation'),
      width: 120,
      align: 'center',
      fixed: 'right',
      formatter: (row) =>
        h(ArtButtonMore, {
          list: ROLE_MORE_ACTIONS,
          list: createRoleMoreActions(),
          onClick: (item) => handleActionClick(item, row)
        })
    }