宁夏万通需求:出库单行支持主/辅单位录入;物料型号维护;考勤节假日配置时间段;设备批量删除;质检导出按筛选条件

1. WLS 仓储:6 张出入库单行录入弹窗(采购入库、杂项出库、外协发料、生产领料、销售出库、采购退货)支持「主单位 + 辅单位(结合型号换算)」录入,新增 wls 单位换算工具与型号选择,行列表与接口适配新增字段。
2. 基础数据:物料新增「型号」子表维护(item-model 接口 + 型号配置组件),物料表单适配主辅单位与型号。
3. HRM 考勤:节假日配置由「单日」改为「时间段」(RangePicker 录入日期区间),列表按区间展示,筛选改为按日期区间交集查询。
4. MES 设备:设备台账支持批量删除。
5. MES 质检:IQC/IPQC/OQC/RQC 四个页面导出改为按当前筛选条件直接导出,删除原「导出区间」弹窗组件。
6. ERP 采购订单/采购申请表单编号占位提示调整。
已修改31个文件
已添加3个文件
已删除1个文件
1854 ■■■■■ 文件已修改
src/api/hrm/attendance/holiday/index.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mdm/item-model/index.ts 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/dv/machinery/index.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/wm/itemreceipt/line/index.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/wm/miscissue/line/index.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/wm/outsourceissue/line/index.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/wm/productissue/line/index.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/wm/productsales/line/index.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/wm/returnvendor/line/index.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/mdm/components/item-model-config.vue 211 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/mdm/modules/form.vue 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/order/data.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/request/data.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/attendance/holiday/data.ts 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/attendance/holiday/index.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/attendance/holiday/modules/form.vue 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/dv/machinery/index.vue 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/components/export-range-modal.vue 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/ipqc/index.vue 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/iqc/index.vue 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/oqc/index.vue 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/rqc/index.vue 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/itemreceipt/data.ts 394 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/itemreceipt/modules/line-form.vue 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/miscissue/data.ts 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/miscissue/modules/line-form.vue 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/outsourceissue/data.ts 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/outsourceissue/modules/line-form.vue 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productissue/data.ts 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productissue/modules/line-form.vue 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productsales/data.ts 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productsales/modules/line-form.vue 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnvendor/data.ts 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnvendor/modules/line-form.vue 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/utils/item-unit-convert.ts 435 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/hrm/attendance/holiday/index.ts
@@ -7,7 +7,10 @@
  export interface Holiday {
    id?: number;
    name?: string;
    date?: string;
    /** å¼€å§‹æ—¥æœŸ */
    startDate?: string;
    /** ç»“束日期 */
    endDate?: string;
    type?: number;
    typeName?: string;
    isLegal?: number;
src/api/mdm/item-model/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
import { requestClient } from '#/api/request';
export namespace MdmItemModelApi {
  /** ç‰©æ–™åž‹å·ï¼ˆåž‹å·åç§° + åž‹å·è®¡é‡å•位 + æ¢ç®—比率) */
  export interface ItemModel {
    id?: number; // åž‹å·ç¼–号
    itemId?: number; // ç‰©æ–™ç¼–号
    itemCode?: string; // ç‰©æ–™ç¼–码
    itemName?: string; // ç‰©æ–™åç§°
    model?: string; // åž‹å·åç§°
    unitMeasureId?: number; // åž‹å·è®¡é‡å•位编号(辅单位)
    unitMeasureName?: string; // åž‹å·è®¡é‡å•位名称
    unitMeasureRate?: number; // æ¢ç®—比率:1 åž‹å·è®¡é‡å•位 = N ç‰©æ–™ä¸»è®¡é‡å•位
    status?: number; // çŠ¶æ€ï¼ˆ0正常 1停用)
    remark?: string; // å¤‡æ³¨
    createTime?: string; // åˆ›å»ºæ—¶é—´
  }
}
/** æŸ¥è¯¢æŸç‰©æ–™çš„型号列表 */
export function getItemModelListByItem(itemId: number) {
  return requestClient.get<MdmItemModelApi.ItemModel[]>(
    '/mdm/item-model/list-by-item',
    { params: { itemId } },
  );
}
/** æ‰¹é‡æŸ¥è¯¢å¤šä¸ªç‰©æ–™çš„型号列表 */
export function getItemModelListByItems(itemIds: number[]) {
  return requestClient.get<MdmItemModelApi.ItemModel[]>(
    '/mdm/item-model/list-by-items',
    { params: { itemIds: itemIds.join(',') } },
  );
}
/** å…¨é‡è¦†ç›–保存某物料的型号列表(先删后插) */
export function saveItemModelList(
  itemId: number,
  list: MdmItemModelApi.ItemModel[],
) {
  return requestClient.post<boolean>(
    '/mdm/item-model/save-list',
    list,
    { params: { itemId } },
  );
}
src/api/mes/dv/machinery/index.ts
@@ -70,6 +70,11 @@
  return requestClient.delete(`/mes/dv/machinery/delete?id=${id}`);
}
/** æ‰¹é‡åˆ é™¤è®¾å¤‡ */
export function deleteMachineryList(ids: number[]) {
  return requestClient.delete(`/mes/dv/machinery/delete-list?ids=${ids.join(',')}`);
}
/** å¯¼å‡ºè®¾å¤‡ */
export function exportMachinery(params: any) {
  return requestClient.download('/mes/dv/machinery/export-excel', { params });
src/api/mes/wm/itemreceipt/line/index.ts
@@ -14,8 +14,14 @@
    itemCode?: string; // ç‰©æ–™ç¼–码
    itemName?: string; // ç‰©æ–™åç§°
    specification?: string; // è§„格型号
    unitMeasureName?: string; // å•位
    receivedQuantity?: number; // å…¥åº“数量
    unitMeasureId?: number; // ä¸»è®¡é‡å•位编号(物料主单位)
    unitMeasureName?: string; // ä¸»è®¡é‡å•位名称(物料主单位)
    itemModelId?: number; // ç‰©æ–™åž‹å·ç¼–号
    itemModel?: string; // ç‰©æ–™åž‹å·åç§°
    enterUnitMeasureId?: number; // å½•入单位编号
    enterUnitMeasureName?: string; // å½•入单位名称
    enterQuantity?: number; // å½•入数量(按录入单位计)
    receivedQuantity?: number; // å…¥åº“数量(物料主单位口径,选辅单位录入时由后端换算)
    batchId?: number; // æ‰¹æ¬¡ç¼–号
    batchCode?: string; // æ‰¹æ¬¡å·
    productionDate?: number; // ç”Ÿäº§æ—¥æœŸ
src/api/mes/wm/miscissue/line/index.ts
@@ -13,9 +13,15 @@
    itemCode?: string; // ç‰©æ–™ç¼–码
    itemName?: string; // ç‰©æ–™åç§°
    specification?: string; // è§„格型号
    unitMeasureId?: number; // ä¸»è®¡é‡å•位编号(物料主单位)
    unitMeasure?: string; // è®¡é‡å•位
    unitMeasureName?: string; // è®¡é‡å•位名称
    quantity?: number; // å‡ºåº“数量
    unitMeasureName?: string; // ä¸»è®¡é‡å•位名称(物料主单位)
    itemModelId?: number; // ç‰©æ–™åž‹å·ç¼–号
    itemModel?: string; // ç‰©æ–™åž‹å·åç§°
    enterUnitMeasureId?: number; // å½•入单位编号
    enterUnitMeasureName?: string; // å½•入单位名称
    enterQuantity?: number; // å½•入数量(按录入单位计)
    quantity?: number; // å‡ºåº“数量(物料主单位口径,选辅单位录入时由后端换算)
    batchId?: number; // æ‰¹æ¬¡ç¼–号
    batchCode?: string; // æ‰¹æ¬¡å·
    warehouseId?: number; // ä»“库编号
src/api/mes/wm/outsourceissue/line/index.ts
@@ -11,8 +11,14 @@
    itemCode?: string; // ç‰©æ–™ç¼–码
    itemName?: string; // ç‰©æ–™åç§°
    specification?: string; // è§„格型号
    unitMeasureName?: string; // è®¡é‡å•位名称
    quantity?: number; // å‘料数量
    unitMeasureId?: number; // ä¸»è®¡é‡å•位编号(物料主单位)
    unitMeasureName?: string; // ä¸»è®¡é‡å•位名称
    itemModelId?: number; // ç‰©æ–™åž‹å·ç¼–号
    itemModel?: string; // ç‰©æ–™åž‹å·åç§°
    enterUnitMeasureId?: number; // å½•入单位编号
    enterUnitMeasureName?: string; // å½•入单位名称
    enterQuantity?: number; // å½•入数量(按录入单位计)
    quantity?: number; // å‘料数量(物料主单位口径,按录入单位录入时由后端换算)
    materialStockId?: number; // åº“存编号
    batchId?: number; // æ‰¹æ¬¡ç¼–号
    batchCode?: string; // æ‰¹æ¬¡ç¼–码
src/api/mes/wm/productissue/line/index.ts
@@ -12,8 +12,14 @@
    itemCode?: string; // ç‰©æ–™ç¼–码
    itemName?: string; // ç‰©æ–™åç§°
    specification?: string; // è§„格型号
    unitMeasureName?: string; // å•位
    quantity?: number; // é¢†æ–™æ•°é‡
    unitMeasureId?: number; // ä¸»è®¡é‡å•位编号(物料主单位)
    unitMeasureName?: string; // ä¸»è®¡é‡å•位名称
    itemModelId?: number; // ç‰©æ–™åž‹å·ç¼–号
    itemModel?: string; // ç‰©æ–™åž‹å·åç§°
    enterUnitMeasureId?: number; // å½•入单位编号
    enterUnitMeasureName?: string; // å½•入单位名称
    enterQuantity?: number; // å½•入数量(按录入单位计)
    quantity?: number; // é¢†æ–™æ•°é‡ï¼ˆç‰©æ–™ä¸»å•位口径,按录入单位录入时由后端换算)
    batchId?: number; // æ‰¹æ¬¡ç¼–号
    remark?: string; // å¤‡æ³¨
  }
src/api/mes/wm/productsales/line/index.ts
@@ -13,8 +13,14 @@
    itemCode?: string; // ç‰©æ–™ç¼–码
    itemName?: string; // ç‰©æ–™åç§°
    specification?: string; // è§„格型号
    unitMeasureName?: string; // å•位
    quantity?: number; // å‡ºåº“数量
    unitMeasureId?: number; // ä¸»è®¡é‡å•位编号(物料主单位)
    unitMeasureName?: string; // ä¸»è®¡é‡å•位名称
    itemModelId?: number; // ç‰©æ–™åž‹å·ç¼–号
    itemModel?: string; // ç‰©æ–™åž‹å·åç§°
    enterUnitMeasureId?: number; // å½•入单位编号
    enterUnitMeasureName?: string; // å½•入单位名称
    enterQuantity?: number; // å½•入数量(按录入单位计)
    quantity?: number; // å‡ºåº“数量(物料主单位口径,按录入单位录入时由后端换算)
    pickedQuantity?: number; // å·²æ‹£è´§æ•°é‡
    batchId?: number; // æ‰¹æ¬¡ç¼–号
    batchCode?: string; // æ‰¹æ¬¡å·
src/api/mes/wm/returnvendor/line/index.ts
@@ -11,8 +11,14 @@
    itemCode?: string; // ç‰©æ–™ç¼–码
    itemName?: string; // ç‰©æ–™åç§°
    specification?: string; // è§„格型号
    unitMeasureName?: string; // å•位
    quantity?: number; // é€€è´§æ•°é‡
    unitMeasureId?: number; // ä¸»è®¡é‡å•位编号(物料主单位)
    unitMeasureName?: string; // ä¸»è®¡é‡å•位名称
    itemModelId?: number; // ç‰©æ–™åž‹å·ç¼–号
    itemModel?: string; // ç‰©æ–™åž‹å·åç§°
    enterUnitMeasureId?: number; // å½•入单位编号
    enterUnitMeasureName?: string; // å½•入单位名称
    enterQuantity?: number; // å½•入数量(按录入单位计)
    quantity?: number; // é€€è´§æ•°é‡ï¼ˆç‰©æ–™ä¸»å•位口径,按录入单位录入时由后端换算)
    batchId?: number; // æ‰¹æ¬¡ç¼–号
    batchCode?: string; // æ‰¹æ¬¡å·
    remark?: string; // å¤‡æ³¨
src/views/basicData/mdm/components/item-model-config.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,211 @@
<script lang="ts" setup>
import type { MdmItemModelApi } from '#/api/mdm/item-model';
import { ref } from 'vue';
import {
  Button,
  Divider,
  Empty,
  Input,
  InputNumber,
  Popconfirm,
  Select,
  Spin,
  Table,
} from 'ant-design-vue';
import {
  getItemModelListByItem,
  saveItemModelList,
} from '#/api/mdm/item-model';
import { getUnitPage } from '#/api/mdm/unit';
/** è¡Œå†…编辑需要稳定的行标识,故在接口数据上补一个本地 _key */
type ModelRow = MdmItemModelApi.ItemModel & { _key: number };
const rows = ref<ModelRow[]>([]);
const units = ref<{ label: string; value: number }[]>([]);
const loading = ref(false);
let rowKey = 0;
const columns = [
  { title: '型号名称', dataIndex: 'model', width: 200 },
  { title: '型号计量单位', dataIndex: 'unitMeasureId', width: 170 },
  { title: '换算比率', dataIndex: 'unitMeasureRate', width: 200 },
  { title: '备注', dataIndex: 'remark' },
  { title: '操作', dataIndex: 'action', width: 70, align: 'center' as const },
];
/** åŠ è½½è®¡é‡å•ä½ä¸‹æ‹‰é€‰é¡¹ */
async function loadUnits() {
  if (units.value.length > 0) {
    return;
  }
  const res = await getUnitPage({ pageNo: 1, pageSize: 100, status: 0 });
  units.value = (res.list || []).map((unit) => ({
    label: unit.name,
    value: unit.id!,
  }));
}
/** åŠ è½½æŒ‡å®šç‰©æ–™å·²é…ç½®çš„åž‹å· */
async function load(itemId?: number) {
  if (!itemId) {
    reset();
    return;
  }
  loading.value = true;
  try {
    await loadUnits();
    const list = await getItemModelListByItem(itemId);
    rows.value = (list || []).map((model) => ({
      ...model,
      _key: ++rowKey,
    }));
  } finally {
    loading.value = false;
  }
}
/** æ–°å¢žä¸€è¡Œåž‹å· */
async function handleAdd() {
  await loadUnits();
  rows.value.push({
    _key: ++rowKey,
    model: '',
    unitMeasureId: undefined,
    unitMeasureRate: undefined,
    status: 0,
    remark: '',
  });
}
/** åˆ é™¤æŸè¡Œåž‹å· */
function handleRemove(key: number) {
  rows.value = rows.value.filter((row) => row._key !== key);
}
/** æ¸…空 */
function reset() {
  rows.value = [];
}
/** åž‹å·è®¡é‡å•位变化时,同步单位名称 */
function handleUnitChange(row: ModelRow, value: unknown) {
  row.unitMeasureName = units.value.find((u) => u.value === value)?.label;
}
/** å•位下拉支持按名称搜索 */
function filterUnitOption(input: string, option: { label?: string }) {
  return String(option?.label ?? '')
    .toLowerCase()
    .includes(input.toLowerCase());
}
/** æ ¡éªŒåž‹å·åˆ—表,返回第一处问题;无问题返回 undefined */
function validate(): string | undefined {
  const names = new Set<string>();
  for (const [index, row] of rows.value.entries()) {
    const label = `第 ${index + 1} è¡Œ`;
    const name = row.model?.trim();
    if (!name) {
      return `${label}的型号名称不能为空,请填写型号名称`;
    }
    if (names.has(name)) {
      return `${label}的型号名称【${name}】与前面填写重复,同一物料下型号名称不能重名,请修改`;
    }
    names.add(name);
    if (row.unitMeasureId == null) {
      return `${label}型号【${name}】还未选择型号计量单位,请选择该型号实际收发货使用的单位`;
    }
    if (row.unitMeasureRate == null || row.unitMeasureRate <= 0) {
      return `${label}型号【${name}】的换算比率未填写或不大于 0,请填写 1 ä¸ªåž‹å·è®¡é‡å•位对应多少个物料主单位`;
    }
  }
  return undefined;
}
/** å…¨é‡è¦†ç›–保存型号列表 */
async function save(itemId: number) {
  const payload = rows.value.map((row) => ({
    id: row.id,
    itemId,
    model: row.model!.trim(),
    unitMeasureId: row.unitMeasureId,
    unitMeasureRate: row.unitMeasureRate,
    status: row.status ?? 0,
    remark: row.remark,
  }));
  await saveItemModelList(itemId, payload);
}
defineExpose({ load, reset, validate, save });
</script>
<template>
  <div class="mx-4 mt-2">
    <Divider orientation="left">型号配置(主辅单位换算)</Divider>
    <p class="mb-3 text-xs text-gray-400">
      åŒä¸€ç‰©æ–™ä¸åŒåž‹å·å¯ä½¿ç”¨ä¸åŒçš„计量单位与换算比率。换算比率含义:1 ä¸ªåž‹å·è®¡é‡å•位 = N ä¸ªç‰©æ–™ä¸»å•位(例:型号
      ã€Œ4ç±³/根」的计量单位填「根」、换算比率填 4,表示 1 æ ¹ = 4 ç±³ï¼‰ã€‚出入库时可按型号选择这些单位录入,系统自动换算为物料主单位数量记账。
    </p>
    <Button class="mb-3" type="primary" @click="handleAdd">添加型号</Button>
    <Spin :spinning="loading">
      <div>
      <Table
        v-if="rows.length > 0"
        :columns="columns"
        :data-source="rows"
        :pagination="false"
        row-key="_key"
        size="small"
        bordered
      >
        <template #bodyCell="{ column, record }">
          <template v-if="column.dataIndex === 'model'">
            <Input
              v-model:value="record.model"
              :maxlength="128"
              placeholder="如:4ç±³/æ ¹"
            />
          </template>
          <template v-else-if="column.dataIndex === 'unitMeasureId'">
            <Select
              v-model:value="record.unitMeasureId"
              :filter-option="filterUnitOption"
              :options="units"
              allow-clear
              class="w-full"
              placeholder="如:根"
              show-search
              @change="(value) => handleUnitChange(record, value)"
            />
          </template>
          <template v-else-if="column.dataIndex === 'unitMeasureRate'">
            <InputNumber
              v-model:value="record.unitMeasureRate"
              :min="0"
              :precision="6"
              class="!w-full"
              placeholder="1 åž‹å·å•位 = N ä¸»å•位"
            />
          </template>
          <template v-else-if="column.dataIndex === 'remark'">
            <Input v-model:value="record.remark" :maxlength="500" />
          </template>
          <template v-else-if="column.dataIndex === 'action'">
            <Popconfirm
              title="确认删除该型号吗?"
              @confirm="handleRemove(record._key)"
            >
              <Button danger size="small" type="link">删除</Button>
            </Popconfirm>
          </template>
        </template>
      </Table>
      <Empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" description="暂无型号,如需按辅单位(如根、袋、箱)收发货,请添加型号" />
      </div>
    </Spin>
  </div>
</template>
src/views/basicData/mdm/modules/form.vue
@@ -13,11 +13,13 @@
import { useFormSchema, clearUnitListCache, buildCategoryTree } from '../data';
import { getItemTypeSimpleList } from '#/api/mes/md/item/type';
import ItemModelConfig from '../components/item-model-config.vue';
defineOptions({ name: "ItemForm" });
const emit = defineEmits(['success']);
const formData = ref<MdmItemApi.Item>();
const itemModelRef = ref<InstanceType<typeof ItemModelConfig>>();
const [Form, formApi] = useVbenForm({
  commonConfig: {
@@ -38,10 +40,20 @@
    if (!valid) {
      return;
    }
    // åž‹å·å…ˆæ ¡éªŒï¼Œé¿å…ç‰©æ–™å·²ä¿å­˜è€Œåž‹å·æœªä¿å­˜
    const modelError = itemModelRef.value?.validate();
    if (modelError) {
      message.error(modelError);
      return;
    }
    modalApi.lock();
    const data = (await formApi.getValues()) as MdmItemApi.Item;
    try {
      await (formData.value?.id ? updateItem(data) : createItem(data));
      const itemId = formData.value?.id ?? (await createItem(data));
      if (formData.value?.id) {
        await updateItem(data);
      }
      await itemModelRef.value?.save(itemId);
      await modalApi.close();
      emit('success');
      message.success($t('ui.actionMessage.operationSuccess'));
@@ -52,6 +64,7 @@
  async onOpenChange(isOpen: boolean) {
    if (!isOpen) {
      formData.value = undefined;
      itemModelRef.value?.reset();
      return;
    }
    const data = modalApi.getData<MdmItemApi.Item>();
@@ -70,6 +83,7 @@
        // åŒæ­¥çŠ¶æ€æ˜ å°„ï¼šè¯¦æƒ…æŽ¥å£è¿”å›ž syncedMes,表单字段为 syncMes
        formData.value.syncMes = formData.value.syncedMes ?? true;
        await formApi.setValues(formData.value);
        await itemModelRef.value?.load(data.id);
      } finally {
        modalApi.unlock();
      }
@@ -77,6 +91,7 @@
      // æ–°å¢žæ¨¡å¼
      formData.value = undefined;
      await formApi.resetForm();
      itemModelRef.value?.reset();
    }
  },
});
@@ -85,5 +100,6 @@
<template>
  <Modal class="w-[1000px]" :title="formData?.id ? $t('ui.actionTitle.edit', ['物料']) : $t('ui.actionTitle.create', ['物料'])">
    <Form class="mx-4" />
    <ItemModelConfig ref="itemModelRef" />
  </Modal>
</template>
src/views/erp/purchase/order/data.ts
@@ -33,7 +33,7 @@
      label: '订单单号',
      component: 'Input',
      componentProps: {
        placeholder: '系统自动生成',
        placeholder: '订单号自定义',
        disabled: true,
      },
    },
src/views/erp/purchase/request/data.ts
@@ -36,7 +36,7 @@
      label: '申请编号',
      component: 'Input',
      componentProps: {
        placeholder: '系统自动生成',
        placeholder: '系统自定义编号',
        disabled: true,
      },
    },
src/views/hrm/attendance/holiday/data.ts
@@ -29,11 +29,10 @@
      rules: 'required',
    },
    {
      fieldName: 'date',
      label: '日期',
      component: 'DatePicker',
      fieldName: 'dateRange',
      label: '日期区间',
      component: 'RangePicker',
      componentProps: {
        placeholder: '选择日期',
        valueFormat: 'YYYY-MM-DD',
        style: { width: '100%' },
      },
@@ -126,7 +125,14 @@
export function useGridColumns(): VxeTableGridOptions<HrmAttendanceHolidayApi.Holiday>['columns'] {
  return [
    { field: 'name', title: '节假日名称', minWidth: 150 },
    { field: 'date', title: '日期', width: 120 },
    {
      field: 'startDate',
      title: '日期区间',
      minWidth: 200,
      slots: {
        default: 'dateRange',
      },
    },
    {
      field: 'type',
      title: '类型',
src/views/hrm/attendance/holiday/index.vue
@@ -114,6 +114,11 @@
          ]"
        />
      </template>
      <template #dateRange="{ row }">
        <span v-if="!row.startDate && !row.endDate">-</span>
        <span v-else-if="row.startDate === row.endDate">{{ row.startDate }}</span>
        <span v-else>{{ row.startDate }} ~ {{ row.endDate }}</span>
      </template>
      <template #isLegal="{ row }">
        <Tag v-if="row.isLegal === 1" color="red">法定</Tag>
        <span v-else>-</span>
src/views/hrm/attendance/holiday/modules/form.vue
@@ -53,7 +53,15 @@
      return;
    }
    modalApi.lock();
    const data = (await formApi.getValues()) as HrmAttendanceHolidayApi.Holiday;
    const values = (await formApi.getValues()) as HrmAttendanceHolidayApi.Holiday & {
      dateRange?: string[];
    };
    const { dateRange, ...rest } = values;
    const data: HrmAttendanceHolidayApi.Holiday = {
      ...rest,
      startDate: dateRange?.[0],
      endDate: dateRange?.[1],
    };
    try {
      if (formType.value === 'update') {
        await updateAttendanceHoliday(data);
@@ -81,7 +89,14 @@
    }
    modalApi.lock();
    try {
      await formApi.setValues(await getAttendanceHoliday(data.id));
      const detail = await getAttendanceHoliday(data.id);
      await formApi.setValues({
        ...detail,
        dateRange:
          detail.startDate && detail.endDate
            ? [detail.startDate, detail.endDate]
            : undefined,
      });
    } finally {
      modalApi.unlock();
    }
src/views/mes/dv/machinery/index.vue
@@ -13,6 +13,7 @@
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
  deleteMachinery,
  deleteMachineryList,
  exportMachinery,
  getMachineryPage,
} from '#/api/mes/dv/machinery';
@@ -31,6 +32,15 @@
  records: MesDvMachineryApi.Machinery[];
}) {
  checkedIds.value = records.map((item) => item.id!);
}
/**
 * è¡¨æ ¼æ•°æ®é‡è½½åŽï¼ˆæŸ¥è¯¢/翻页/刷新/切换设备类型)清空勾选,
 * é¿å…å‹¾é€‰äº†ä¸Šä¸€é¡µçš„设备后,页面已刷新但仍按旧勾选删除
 */
function handleDataChange() {
  checkedIds.value = [];
  gridApi.grid.clearCheckboxRow();
}
const [FormModal, formModalApi] = useVbenModal({
@@ -79,17 +89,21 @@
/** æ‰¹é‡åˆ é™¤è®¾å¤‡ */
async function handleDeleteBatch() {
  if (checkedIds.value.length === 0) {
    return;
  }
  const count = checkedIds.value.length;
  const hideLoading = message.loading({
    content: $t('ui.actionMessage.deleting', [`${checkedIds.value.length} å°`]),
    content: $t('ui.actionMessage.deleting', [`${count} å°`]),
    duration: 0,
  });
  try {
    await Promise.all(checkedIds.value.map((id) => deleteMachinery(id)));
    message.success($t('ui.actionMessage.deleteSuccess', ['']));
    checkedIds.value = [];
    handleRefresh();
    await deleteMachineryList(checkedIds.value);
    message.success($t('ui.actionMessage.deleteSuccess', [`${count} å°`]));
  } finally {
    hideLoading();
    handleDataChange();
    handleRefresh();
  }
}
@@ -143,6 +157,7 @@
  gridEvents: {
    checkboxAll: handleRowCheckboxChange,
    checkboxChange: handleRowCheckboxChange,
    dataChange: handleDataChange,
  },
});
</script>
@@ -174,6 +189,7 @@
                  auth: ['mes:dv-machinery:delete'],
                  disabled: checkedIds.length === 0,
                  popConfirm: {
                    disabled: checkedIds.length === 0,
                    title: $t('ui.actionMessage.deleteConfirm', [
                      `${checkedIds.length} å°`,
                    ]),
src/views/mes/qc/components/export-range-modal.vue
ÎļþÒÑɾ³ý
src/views/mes/qc/ipqc/index.vue
@@ -4,6 +4,7 @@
import { Page, useVbenModal } from '@vben/common-ui';
import { MesQcStatusEnum } from '@vben/constants';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
@@ -16,7 +17,6 @@
  importIpqcTemplate,
} from '#/api/mes/qc/ipqc';
import { $t } from '#/locales';
import QcExportRangeModal from '#/views/mes/qc/components/export-range-modal.vue';
import QcImportFormModal from '#/views/mes/qc/components/import-form-modal.vue';
import QcReportGenerateModal from '#/views/mes/qc/components/report-generate-modal.vue';
@@ -28,11 +28,6 @@
const [FormModal, formModalApi] = useVbenModal({
  connectedComponent: Form,
  destroyOnClose: true,
});
const [ExportRangeModal, exportRangeModalApi] = useVbenModal({
  connectedComponent: QcExportRangeModal,
  destroyOnClose: true,
});
@@ -101,15 +96,10 @@
  }
}
/** å¯¼å‡ºè¡¨æ ¼ï¼ˆå¼¹çª—选检验时间范围) */
function handleExport() {
  exportRangeModalApi
    .setData({
      title: '按检验时间导出过程检验单',
      exportApi: exportIpqc,
      fileName: '过程检验单.xls',
    })
    .open();
/** å¯¼å‡ºè¡¨æ ¼ï¼ˆæŒ‰åˆ—表筛选条件导出) */
async function handleExport() {
  const data = await exportIpqc(await gridApi.formApi.getValues());
  downloadFileFromBlobPart({ fileName: '过程检验单.xls', source: data });
}
/** å‡ºæŠ¥å‘Šï¼šæ•°æ®ç”±åŽç«¯ä»Žè¯¥è´¨æ£€å•取,前端只选模板 */
@@ -153,7 +143,6 @@
<template>
  <Page auto-content-height>
    <FormModal @success="handleRefresh" />
    <ExportRangeModal />
    <ReportModal />
    <ImportModal @success="handleRefresh" />
    <Grid table-title="过程检验单列表">
src/views/mes/qc/iqc/index.vue
@@ -7,6 +7,7 @@
import { Page, useVbenModal } from '@vben/common-ui';
import { MesQcStatusEnum } from '@vben/constants';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
@@ -19,7 +20,6 @@
  importIqcTemplate,
} from '#/api/mes/qc/iqc';
import { $t } from '#/locales';
import QcExportRangeModal from '#/views/mes/qc/components/export-range-modal.vue';
import QcImportFormModal from '#/views/mes/qc/components/import-form-modal.vue';
import QcReportGenerateModal from '#/views/mes/qc/components/report-generate-modal.vue';
@@ -35,11 +35,6 @@
const [FormModal, formModalApi] = useVbenModal({
  connectedComponent: Form,
  destroyOnClose: true,
});
const [ExportRangeModal, exportRangeModalApi] = useVbenModal({
  connectedComponent: QcExportRangeModal,
  destroyOnClose: true,
});
@@ -107,15 +102,10 @@
  }
}
/** å¯¼å‡ºè¡¨æ ¼ï¼ˆå¼¹çª—选检验时间范围) */
function handleExport() {
  exportRangeModalApi
    .setData({
      title: '按检验时间导出来料检验单',
      exportApi: exportIqc,
      fileName: '来料检验单.xls',
    })
    .open();
/** å¯¼å‡ºè¡¨æ ¼ï¼ˆæŒ‰åˆ—表筛选条件导出) */
async function handleExport() {
  const data = await exportIqc(await gridApi.formApi.getValues());
  downloadFileFromBlobPart({ fileName: '来料检验单.xls', source: data });
}
/** å‡ºæŠ¥å‘Šï¼šæ•°æ®ç”±åŽç«¯ä»Žè¯¥è´¨æ£€å•取,前端只选模板 */
@@ -166,7 +156,6 @@
<template>
  <Page auto-content-height>
    <FormModal @success="handleRefresh" />
    <ExportRangeModal />
    <ReportModal />
    <ImportModal @success="handleRefresh" />
    <Grid table-title="来料检验单列表">
src/views/mes/qc/oqc/index.vue
@@ -4,6 +4,7 @@
import { Page, useVbenModal } from '@vben/common-ui';
import { MesQcStatusEnum } from '@vben/constants';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
@@ -16,7 +17,6 @@
  importOqcTemplate,
} from '#/api/mes/qc/oqc';
import { $t } from '#/locales';
import QcExportRangeModal from '#/views/mes/qc/components/export-range-modal.vue';
import QcImportFormModal from '#/views/mes/qc/components/import-form-modal.vue';
import QcReportGenerateModal from '#/views/mes/qc/components/report-generate-modal.vue';
@@ -28,11 +28,6 @@
const [FormModal, formModalApi] = useVbenModal({
  connectedComponent: Form,
  destroyOnClose: true,
});
const [ExportRangeModal, exportRangeModalApi] = useVbenModal({
  connectedComponent: QcExportRangeModal,
  destroyOnClose: true,
});
@@ -100,15 +95,10 @@
  }
}
/** å¯¼å‡ºè¡¨æ ¼ï¼ˆå¼¹çª—选检验时间范围) */
function handleExport() {
  exportRangeModalApi
    .setData({
      title: '按检验时间导出货检验单',
      exportApi: exportOqc,
      fileName: '出货检验单.xls',
    })
    .open();
/** å¯¼å‡ºè¡¨æ ¼ï¼ˆæŒ‰åˆ—表筛选条件导出) */
async function handleExport() {
  const data = await exportOqc(await gridApi.formApi.getValues());
  downloadFileFromBlobPart({ fileName: '出货检验单.xls', source: data });
}
/** å‡ºæŠ¥å‘Šï¼šæ•°æ®ç”±åŽç«¯ä»Žè¯¥è´¨æ£€å•取,前端只选模板 */
@@ -152,7 +142,6 @@
<template>
  <Page auto-content-height>
    <FormModal @success="handleRefresh" />
    <ExportRangeModal />
    <ReportModal />
    <ImportModal @success="handleRefresh" />
    <Grid table-title="出货检验单列表">
src/views/mes/qc/rqc/index.vue
@@ -4,13 +4,13 @@
import { Page, useVbenModal } from '@vben/common-ui';
import { MesQcStatusEnum } from '@vben/constants';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteRqc, exportRqc, getRqcPage } from '#/api/mes/qc/rqc';
import { $t } from '#/locales';
import QcExportRangeModal from '#/views/mes/qc/components/export-range-modal.vue';
import QcReportGenerateModal from '#/views/mes/qc/components/report-generate-modal.vue';
import { useGridColumns, useGridFormSchema } from './data';
@@ -21,11 +21,6 @@
const [FormModal, formModalApi] = useVbenModal({
  connectedComponent: Form,
  destroyOnClose: true,
});
const [ExportRangeModal, exportRangeModalApi] = useVbenModal({
  connectedComponent: QcExportRangeModal,
  destroyOnClose: true,
});
@@ -69,15 +64,10 @@
  }
}
/** å¯¼å‡ºè¡¨æ ¼ï¼ˆå¼¹çª—选检验时间范围) */
function handleExport() {
  exportRangeModalApi
    .setData({
      title: '按检验时间导退货检验单',
      exportApi: exportRqc,
      fileName: '退货检验单.xls',
    })
    .open();
/** å¯¼å‡ºè¡¨æ ¼ï¼ˆæŒ‰åˆ—表筛选条件导出) */
async function handleExport() {
  const data = await exportRqc(await gridApi.formApi.getValues());
  downloadFileFromBlobPart({ fileName: '退货检验单.xls', source: data });
}
/** å‡ºæŠ¥å‘Šï¼šæ•°æ®ç”±åŽç«¯ä»Žè¯¥è´¨æ£€å•取,前端只选模板 */
@@ -121,7 +111,6 @@
<template>
  <Page auto-content-height>
    <FormModal @success="handleRefresh" />
    <ExportRangeModal />
    <ReportModal />
    <Grid table-title="退货检验单列表">
      <template #toolbar-tools>
src/views/wls/itemreceipt/data.ts
@@ -1,10 +1,13 @@
import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MdmItemModelApi } from '#/api/mdm/item-model';
import type { MesMdItemApi } from '#/api/mes/md/item';
import type { MesWmArrivalNoticeApi } from '#/api/mes/wm/arrivalnotice';
import type { MesWmArrivalNoticeLineApi } from '#/api/mes/wm/arrivalnotice/line';
import type { MesWmItemReceiptApi } from '#/api/mes/wm/itemreceipt';
import type { MesWmItemReceiptDetailApi } from '#/api/mes/wm/itemreceipt/detail';
import type { MesWmItemReceiptLineApi } from '#/api/mes/wm/itemreceipt/line';
import type { SrmSupplierApi } from '#/api/srm/supplier';
import { h, markRaw } from 'vue';
@@ -16,10 +19,12 @@
import { Button } from 'ant-design-vue';
import { getItem as getMdmItem } from '#/api/mdm/item';
import { getItemModelListByItem } from '#/api/mdm/item-model';
import { generateAutoCode } from '#/api/mes/md/autocode/record';
import { getRangePickerDefaultProps } from '#/utils';
import { MdItemSelect } from '#/views/mes/md/item/components';
import { ErpSupplierSelect } from '#/views/erp/purchase/supplier/components';
import { SrmSupplierSelect } from '#/views/srm/supplier/components';
import {
  WmArrivalNoticeLineSelect,
  WmArrivalNoticeSelect,
@@ -132,10 +137,10 @@
    {
      fieldName: 'vendorId',
      label: '供应商',
      component: markRaw(ErpSupplierSelect),
      component: markRaw(SrmSupplierSelect),
      componentProps: {
        placeholder: '请选择供应商',
        onChange: async (supplier?: any) => {
        onChange: async (supplier?: SrmSupplierApi.SupplierVO) => {
          await formApi?.setFieldValue('vendorName', supplier?.name);
        },
      },
@@ -193,7 +198,7 @@
    {
      fieldName: 'vendorId',
      label: '供应商',
      component: markRaw(ErpSupplierSelect),
      component: markRaw(SrmSupplierSelect),
      componentProps: {
        placeholder: '请选择供应商',
      },
@@ -285,9 +290,24 @@
      minWidth: 120,
    },
    {
      field: 'itemModel',
      title: '物料型号',
      minWidth: 120,
    },
    {
      field: 'unitMeasureName',
      title: '单位',
      width: 80,
      title: '主计量单位',
      width: 100,
    },
    {
      field: 'enterUnitMeasureName',
      title: '录入单位',
      width: 90,
    },
    {
      field: 'enterQuantity',
      title: '录入数量',
      width: 100,
    },
    {
      field: 'receivedQuantity',
@@ -312,11 +332,221 @@
  ];
}
/** å½•入单位选项(含到物料主单位的换算比率) */
export interface ItemEnterUnitOption {
  id: number;
  name: string;
  rate: number;
}
/** åž‹å·ä¸‹æ‹‰é€‰é¡¹ï¼šApiSelect ä¼šæŠŠ id/model æ˜ å°„成 value/label,其余字段原样保留 */
type ItemModelSelectOption = Pick<
  MdmItemModelApi.ItemModel,
  'unitMeasureId' | 'unitMeasureName' | 'unitMeasureRate'
>;
/** å…¥åº“单行表单中与双单位换算相关的字段 */
interface LineUnitFormValues {
  enterQuantity?: number;
  enterUnitMeasureId?: number;
  enterUnitMeasureName?: string;
  enterUnitRate?: number;
  itemId?: number;
  itemModelId?: number;
  itemPrimaryUnitId?: number;
  itemPrimaryUnitName?: string;
}
/** åŒå•位换算用的临时表单字段(只供前端换算与展示,提交前必须剔除) */
const LINE_TEMP_FIELDS = [
  'itemPrimaryUnitId',
  'itemPrimaryUnitName',
  'enterUnitMeasureName',
  'enterUnitRate',
] as const;
/** å‰”除双单位换算用的临时字段,得到可直接提交的行数据 */
export function omitLineTempFields(
  values: Record<string, unknown>,
): MesWmItemReceiptLineApi.ItemReceiptLine {
  const data = { ...values };
  for (const field of LINE_TEMP_FIELDS) {
    delete data[field];
  }
  return data as MesWmItemReceiptLineApi.ItemReceiptLine;
}
/** æ¢ç®—比率是否有效(必须大于 0) */
function isValidRate(rate?: number): rate is number {
  return typeof rate === 'number' && rate > 0;
}
/** æ¢ç®—结果按 6 ä½å°æ•°å±•示(最终以后端换算结果为准) */
function roundQuantity(quantity: number): number {
  return Math.round(quantity * 1e6) / 1e6;
}
/**
 * æŸ¥è¯¢ç‰©æ–™å¯é€‰çš„录入单位:主单位 + è¾…单位
 *
 * é€‰å®šåž‹å·æ—¶åªä¿ç•™ã€Œç‰©æ–™ä¸»å•位」与「型号计量单位」两种口径,与后端换算规则保持一致
 */
export async function getItemEnterUnits(params: {
  itemId?: number;
  itemModelId?: number;
}): Promise<ItemEnterUnitOption[]> {
  const itemId = params.itemId;
  if (!itemId) {
    return [];
  }
  const item = await getMdmItem(itemId);
  if (!item?.unitMeasureId) {
    return [];
  }
  // ä¸»å•位始终可选,按主单位录入时不做换算
  const primaryUnit: ItemEnterUnitOption = {
    id: item.unitMeasureId,
    name: item.unitMeasureName || '物料主单位',
    rate: 1,
  };
  const unitOptions: ItemEnterUnitOption[] = [primaryUnit];
  const pushUnit = (id?: number, name?: string, rate?: number) => {
    if (
      !id ||
      id === primaryUnit.id ||
      !isValidRate(rate) ||
      unitOptions.some((unit) => unit.id === id)
    ) {
      return;
    }
    unitOptions.push({ id, name: name || '辅单位', rate });
  };
  if (params.itemModelId) {
    const models = await getItemModelListByItem(itemId);
    const modelOption = models?.find((option) => option.id === params.itemModelId);
    pushUnit(
      modelOption?.unitMeasureId,
      modelOption?.unitMeasureName,
      modelOption?.unitMeasureRate,
    );
    return unitOptions;
  }
  // æœªé€‰åž‹å·æ—¶ï¼Œç‰©æ–™ä¸»æ•°æ®ä¸Šé…ç½®äº†æ¢ç®—比率的辅单位可选
  pushUnit(item.unitMeasureId2, item.unitMeasureName2, item.unitMeasureRate1);
  pushUnit(item.unitMeasureId3, item.unitMeasureName3, item.unitMeasureRate2);
  return unitOptions;
}
/** æŸ¥è¯¢æŸä¸ªå½•入单位到物料主单位的换算比率(编辑回显时补齐前端换算上下文) */
export async function getEnterUnitRate(
  itemId?: number,
  itemModelId?: number,
  enterUnitMeasureId?: number,
): Promise<number | undefined> {
  if (!itemId || !enterUnitMeasureId) {
    return undefined;
  }
  const unitOptions = await getItemEnterUnits({ itemId, itemModelId });
  return unitOptions.find((unit) => unit.id === enterUnitMeasureId)?.rate;
}
/** å…¥åº“单行新增/修改的表单 */
export function useLineFormSchema(
  hasNotice: boolean,
  formApi?: VbenFormApi,
): VbenFormSchema[] {
  /** æ˜¯å¦æŒ‰ç‰©æ–™ä¸»å•位录入(未选录入单位、或录入单位就是物料主单位) */
  function isPrimaryUnitEnter(values: LineUnitFormValues) {
    return (
      !values.enterUnitMeasureId ||
      values.enterUnitMeasureId === values.itemPrimaryUnitId
    );
  }
  /** åˆ‡æ¢ç‰©æ–™ï¼šé‡æ–°è®°å½•物料主单位,并清空型号、录入单位与数量(口径都随物料变化) */
  async function handleItemChange(itemId?: number) {
    // å…ˆæ¸…空,避免上一个物料的型号与单位口径残留
    await formApi?.setValues({
      itemId,
      itemModelId: undefined,
      itemPrimaryUnitId: undefined,
      itemPrimaryUnitName: undefined,
      enterUnitMeasureId: undefined,
      enterUnitMeasureName: undefined,
      enterUnitRate: undefined,
      enterQuantity: undefined,
      receivedQuantity: undefined,
    });
    if (!itemId) {
      return;
    }
    const item = await getMdmItem(itemId);
    await formApi?.setValues({
      itemPrimaryUnitId: item?.unitMeasureId,
      itemPrimaryUnitName: item?.unitMeasureName,
    });
  }
  /** é€‰æ‹©åž‹å·ï¼šé»˜è®¤æŒ‰åž‹å·è®¡é‡å•位录入,并记录换算比率 */
  async function handleModelChange(
    option: ItemModelSelectOption | undefined,
    values: LineUnitFormValues,
  ) {
    const enterUnitMeasureId =
      option?.unitMeasureId && option.unitMeasureId !== values.itemPrimaryUnitId
        ? option.unitMeasureId
        : undefined;
    await formApi?.setValues({
      enterUnitMeasureId,
      enterUnitMeasureName: enterUnitMeasureId
        ? option?.unitMeasureName
        : undefined,
      enterUnitRate: enterUnitMeasureId ? option?.unitMeasureRate : undefined,
      enterQuantity: undefined,
      // åˆ‡åˆ°åž‹å·å•位录入后入库数量由系统换算,先清掉上一次口径的数值
      ...(enterUnitMeasureId ? { receivedQuantity: undefined } : {}),
    });
  }
  /** åˆ‡æ¢å½•入单位:记录换算比率并立即重算入库数量;改回主单位时清掉录入数量 */
  async function handleEnterUnitChange(
    value: number | undefined,
    option: { label?: string; rate?: number } | undefined,
    values: LineUnitFormValues,
  ) {
    if (!value || value === values.itemPrimaryUnitId) {
      await formApi?.setValues({
        enterUnitMeasureName: undefined,
        enterUnitRate: undefined,
        enterQuantity: undefined,
      });
      return;
    }
    await formApi?.setValues({
      enterUnitMeasureName: option?.label,
      enterUnitRate: option?.rate,
      ...(values.enterQuantity == null || !isValidRate(option?.rate)
        ? { receivedQuantity: undefined }
        : {
            receivedQuantity: roundQuantity(values.enterQuantity * option.rate),
          }),
    });
  }
  /** å½•入数量变化:按录入单位与换算比率试算入库数量,供用户核对换算结果 */
  async function handleEnterQuantityChange(
    value: number | undefined,
    values: LineUnitFormValues,
  ) {
    if (isPrimaryUnitEnter(values) || !isValidRate(values.enterUnitRate)) {
      return;
    }
    await formApi?.setFieldValue(
      'receivedQuantity',
      value == null ? undefined : roundQuantity(value * values.enterUnitRate),
    );
  }
  return [
    {
      fieldName: 'noticeId',
@@ -331,14 +561,17 @@
      label: '到货通知单行',
      component: markRaw(WmArrivalNoticeLineSelect),
      componentProps: {
        // é€‰æ‹©åˆ°è´§é€šçŸ¥å•行后,自动回填物料和入库数量
        // é€‰æ‹©åˆ°è´§é€šçŸ¥å•行后,自动回填物料与入库数量
        onChange: async (
          line?: MesWmArrivalNoticeLineApi.ArrivalNoticeLine,
        ) => {
          await formApi?.setValues({
            itemId: line?.itemId,
            receivedQuantity: line?.arrivalQuantity,
          });
          await handleItemChange(line?.itemId);
          if (line?.arrivalQuantity != null) {
            await formApi?.setFieldValue(
              'receivedQuantity',
              line.arrivalQuantity,
            );
          }
        },
      },
      dependencies: {
@@ -355,6 +588,8 @@
      component: markRaw(MdItemSelect),
      componentProps: {
        placeholder: '请选择物料',
        // åˆ‡æ¢ç‰©æ–™åŽï¼Œåž‹å·ã€å½•入单位与数量都要重新选
        onChange: (item?: MesMdItemApi.Item) => handleItemChange(item?.id),
      },
      rules: 'selectRequired',
      dependencies: {
@@ -365,6 +600,121 @@
        }),
      },
    },
    // ç‰©æ–™ä¸»å•位随所选物料变化,但表单 props åªèƒ½é™æ€å£°æ˜Žï¼Œæ•…用隐藏字段承载
    {
      fieldName: 'itemPrimaryUnitId',
      component: 'Input',
      dependencies: {
        triggerFields: [''],
        show: () => false,
      },
    },
    {
      fieldName: 'itemPrimaryUnitName',
      component: 'Input',
      dependencies: {
        triggerFields: [''],
        show: () => false,
      },
    },
    {
      fieldName: 'itemModelId',
      label: '物料型号',
      component: 'ApiSelect',
      componentProps: {
        labelField: 'model',
        valueField: 'id',
        allowClear: true,
        api: (params: { itemId?: number }) =>
          getItemModelListByItem(params.itemId!),
        shouldFetch: (params: { itemId?: number }) => !!params?.itemId,
        placeholder: '请先选择物料',
      },
      dependencies: {
        triggerFields: ['itemId'],
        componentProps: (values) => ({
          disabled: !values.itemId,
          params: { itemId: values.itemId },
          placeholder: values.itemId ? '请选择物料型号' : '请先选择物料',
          // é€‰å®šåž‹å·åŽé»˜è®¤æŒ‰å…¶è®¡é‡å•位录入
          onChange: (option?: ItemModelSelectOption) =>
            handleModelChange(option, values),
        }),
      },
    },
    {
      fieldName: 'enterUnitMeasureId',
      label: '录入单位',
      component: 'ApiSelect',
      componentProps: {
        allowClear: true,
        labelField: 'name',
        valueField: 'id',
        api: (params: { itemId?: number; itemModelId?: number }) =>
          getItemEnterUnits(params),
        shouldFetch: (params: { itemId?: number }) => !!params?.itemId,
        placeholder: '默认为物料主计量单位',
      },
      dependencies: {
        triggerFields: ['itemId', 'itemModelId', 'itemPrimaryUnitName'],
        componentProps: (values) => ({
          disabled: !values.itemId,
          params: { itemId: values.itemId, itemModelId: values.itemModelId },
          placeholder: values.itemId
            ? `默认按物料主单位${values.itemPrimaryUnitName ? `【${values.itemPrimaryUnitName}】` : ''}录入`
            : '请先选择物料',
          onChange: (
            value: number | undefined,
            option?: { label?: string; rate?: number },
          ) => handleEnterUnitChange(value, option, values),
        }),
      },
    },
    // å½•入单位名称与换算比率随所选单位变化,同样用隐藏字段承载
    {
      fieldName: 'enterUnitMeasureName',
      component: 'Input',
      dependencies: {
        triggerFields: [''],
        show: () => false,
      },
    },
    {
      fieldName: 'enterUnitRate',
      component: 'Input',
      dependencies: {
        triggerFields: [''],
        show: () => false,
      },
    },
    {
      fieldName: 'enterQuantity',
      label: '录入数量',
      component: 'InputNumber',
      componentProps: {
        class: '!w-full',
        min: 0,
        precision: 6,
      },
      dependencies: {
        triggerFields: [
          'enterUnitMeasureId',
          'enterUnitMeasureName',
          'itemPrimaryUnitId',
        ],
        show: (values) => !isPrimaryUnitEnter(values),
        // è¿”回 null è¡¨ç¤ºä¸è¿½åŠ è§„åˆ™ï¼ˆå›žé€€åŸºç¡€ rules)
        rules: (values) => (isPrimaryUnitEnter(values) ? null : 'required'),
        componentProps: (values) => ({
          placeholder: values.enterUnitMeasureName
            ? `请填写按【${values.enterUnitMeasureName}】计的数量`
            : '请按录入单位填写数量',
          // å½•入数量变化时立即试算入库数量
          onChange: (value: number | undefined) =>
            handleEnterQuantityChange(value, values),
        }),
      },
    },
    {
      fieldName: 'receivedQuantity',
      label: '入库数量',
@@ -372,10 +722,26 @@
      componentProps: {
        class: '!w-full',
        min: 0,
        placeholder: '请输入入库数量',
        precision: 2,
        precision: 6,
      },
      rules: 'required',
      dependencies: {
        triggerFields: [
          'enterUnitMeasureId',
          'itemPrimaryUnitId',
          'itemPrimaryUnitName',
        ],
        // æŒ‰ä¸»å•位录入时手工填写、必填;按辅单位录入时为系统换算结果,无需校验
        rules: (values) => (isPrimaryUnitEnter(values) ? 'required' : null),
        componentProps: (values) => {
          const primary = isPrimaryUnitEnter(values);
          return {
            disabled: !primary,
            placeholder: primary
              ? `请按物料主单位${values.itemPrimaryUnitName ? `【${values.itemPrimaryUnitName}】` : ''}填写数量`
              : '按录入单位与换算比率自动换算',
          };
        },
      },
    },
    {
      fieldName: 'productionDate',
src/views/wls/itemreceipt/modules/line-form.vue
@@ -15,7 +15,11 @@
} from '#/api/mes/wm/itemreceipt/line';
import { $t } from '#/locales';
import { useLineFormSchema } from '../data';
import {
  getEnterUnitRate,
  omitLineTempFields,
  useLineFormSchema,
} from '../data';
const emit = defineEmits(['success']);
const formData = ref<MesWmItemReceiptLineApi.ItemReceiptLine>();
@@ -49,9 +53,8 @@
      return;
    }
    modalApi.lock();
    // æäº¤è¡¨å•
    const data =
      (await formApi.getValues()) as MesWmItemReceiptLineApi.ItemReceiptLine;
    // æäº¤è¡¨å•(剔除仅用于前端换算/展示的临时字段)
    const data = omitLineTempFields(await formApi.getValues());
    data.receiptId = receiptId.value;
    try {
      await (formData.value?.id
@@ -87,9 +90,26 @@
    }
    modalApi.lock();
    try {
      formData.value = await getItemReceiptLine(data.id);
      // è®¾ç½®åˆ° values
      await formApi.setValues({ ...formData.value, noticeId: noticeId.value });
      const line = await getItemReceiptLine(data.id);
      formData.value = line;
      // è®¾ç½®åˆ° values;详情里的 unitMeasureId/unitMeasureName å³ç‰©æ–™ä¸»å•位,补到前端换算用的隐藏字段上
      await formApi.setValues({
        ...line,
        noticeId: noticeId.value,
        itemPrimaryUnitId: line.unitMeasureId,
        itemPrimaryUnitName: line.unitMeasureName,
      });
      // å›žæ˜¾å½•入单位时补齐换算比率(前端预览换算需要)
      if (line.enterUnitMeasureId) {
        await formApi.setFieldValue(
          'enterUnitRate',
          await getEnterUnitRate(
            line.itemId,
            line.itemModelId,
            line.enterUnitMeasureId,
          ),
        );
      }
    } finally {
      modalApi.unlock();
    }
src/views/wls/miscissue/data.ts
@@ -13,6 +13,10 @@
import { generateAutoCode } from '#/api/mes/md/autocode/record';
import { getRangePickerDefaultProps } from '#/utils';
import {
  useLineUnitHandlers,
  buildItemUnitSchema,
} from '#/views/wls/utils/item-unit-convert';
import { WmMaterialStockSelect } from '#/views/wls/materialstock/components';
import {
  WmWarehouseAreaSelect,
@@ -283,9 +287,24 @@
      minWidth: 120,
    },
    {
      field: 'itemModel',
      title: '物料型号',
      minWidth: 120,
    },
    {
      field: 'unitMeasureName',
      title: '单位',
      width: 80,
      title: '主计量单位',
      width: 100,
    },
    {
      field: 'enterUnitMeasureName',
      title: '录入单位',
      width: 90,
    },
    {
      field: 'enterQuantity',
      title: '录入数量',
      width: 100,
    },
    {
      field: 'quantity',
@@ -327,6 +346,7 @@
/** å‡ºåº“单行新增/修改的表单 */
export function useLineFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
  const unitHandlers = useLineUnitHandlers(formApi);
  return [
    {
      fieldName: 'itemId',
@@ -351,10 +371,11 @@
      componentProps: {
        // é€‰æ‹©åº“存物资后,自动回填物料、批次号、仓库位置和最大可出库数量
        onChange: async (stock?: MesWmMaterialStockApi.MaterialStock) => {
          // åˆ‡æ¢åº“存物资等于切换物料,先清掉上一份型号与录入单位口径
          await unitHandlers.handleItemChange(stock?.itemId);
          await formApi?.setValues({
            areaId: stock?.areaId,
            batchCode: stock?.batchCode,
            itemId: stock?.itemId,
            locationId: stock?.locationId,
            quantityMax: stock?.quantity,
            warehouseId: stock?.warehouseId,
@@ -369,28 +390,8 @@
        }),
      },
    },
    {
      fieldName: 'quantity',
      label: '出库数量',
      component: 'InputNumber',
      componentProps: {
        class: '!w-full',
        min: 0.01,
        placeholder: '请输入出库数量',
        precision: 2,
      },
      rules: 'required',
      dependencies: {
        triggerFields: ['quantityMax'],
        componentProps: (values) => ({
          class: '!w-full',
          max: values.quantityMax,
          min: 0.01,
          placeholder: '请输入出库数量',
          precision: 2,
        }),
      },
    },
    // ç‰©æ–™åž‹å· / å½•入单位 / å½•入数量 / å‡ºåº“数量(主单位口径)
    ...buildItemUnitSchema(formApi),
    {
      fieldName: 'batchCode',
      label: '批次号',
src/views/wls/miscissue/modules/line-form.vue
@@ -14,6 +14,10 @@
  updateMiscIssueLine,
} from '#/api/mes/wm/miscissue/line';
import { $t } from '#/locales';
import {
  getEnterUnitRate,
  omitLineTempFields,
} from '#/views/wls/utils/item-unit-convert';
import { useLineFormSchema } from '../data';
@@ -48,9 +52,10 @@
      return;
    }
    modalApi.lock();
    // æäº¤è¡¨å•
    const data =
      (await formApi.getValues()) as MesWmMiscIssueLineApi.MiscIssueLine;
    // æäº¤è¡¨å•(剔除仅用于前端换算/展示的临时字段)
    const data = omitLineTempFields(
      (await formApi.getValues()) as MesWmMiscIssueLineApi.MiscIssueLine,
    );
    data.issueId = issueId.value;
    try {
      await (formData.value?.id
@@ -81,9 +86,25 @@
    }
    modalApi.lock();
    try {
      formData.value = await getMiscIssueLine(data.id);
      // è®¾ç½®åˆ° values
      await formApi.setValues(formData.value);
      const line = await getMiscIssueLine(data.id);
      formData.value = line;
      // è®¾ç½®åˆ° values;详情里的 unitMeasureId/unitMeasureName å³ç‰©æ–™ä¸»å•位,补到前端换算用的隐藏字段上
      await formApi.setValues({
        ...line,
        itemPrimaryUnitId: line.unitMeasureId,
        itemPrimaryUnitName: line.unitMeasureName,
      });
      // å›žæ˜¾å½•入单位时补齐换算比率(前端预览换算需要)
      if (line.enterUnitMeasureId) {
        await formApi.setFieldValue(
          'enterUnitRate',
          await getEnterUnitRate(
            line.itemId,
            line.itemModelId,
            line.enterUnitMeasureId,
          ),
        );
      }
    } finally {
      modalApi.unlock();
    }
src/views/wls/outsourceissue/data.ts
@@ -1,5 +1,6 @@
import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesMdItemApi } from '#/api/mes/md/item';
import type { MesProWorkOrderApi } from '#/api/mes/pro/workorder';
import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
import type { MesWmOutsourceIssueApi } from '#/api/mes/wm/outsourceissue';
@@ -22,6 +23,10 @@
import { MdItemSelect } from '#/views/mes/md/item/components';
import { MdVendorSelect } from '#/views/mes/md/vendor/components';
import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
import {
  buildItemUnitSchema,
  useLineUnitHandlers,
} from '#/views/wls/utils/item-unit-convert';
import { WmMaterialStockSelect } from '#/views/wls/materialstock/components';
import {
  WmWarehouseAreaSelect,
@@ -254,13 +259,28 @@
      minWidth: 120,
    },
    {
      field: 'itemModel',
      title: '物料型号',
      minWidth: 120,
    },
    {
      field: 'unitMeasureName',
      title: '单位',
      width: 80,
      title: '主计量单位',
      width: 100,
    },
    {
      field: 'enterUnitMeasureName',
      title: '录入单位',
      width: 90,
    },
    {
      field: 'enterQuantity',
      title: '录入数量',
      width: 100,
    },
    {
      field: 'quantity',
      title: '领料数量',
      title: '发料数量',
      width: 100,
    },
    {
@@ -287,7 +307,8 @@
}
/** å‘料单行新增/修改的表单 */
export function useLineFormSchema(): VbenFormSchema[] {
export function useLineFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
  const unitHandlers = useLineUnitHandlers(formApi);
  return [
    {
      fieldName: 'itemId',
@@ -295,21 +316,15 @@
      component: markRaw(MdItemSelect),
      componentProps: {
        placeholder: '请选择物料',
        // åˆ‡æ¢ç‰©æ–™åŽï¼Œé‡æ–°è®°å½•物料主单位并清空型号、录入单位口径
        onChange: async (item?: MesMdItemApi.Item) => {
          await unitHandlers.handleItemChange(item?.id);
        },
      },
      rules: 'selectRequired',
    },
    {
      fieldName: 'quantity',
      label: '发料数量',
      component: 'InputNumber',
      componentProps: {
        class: '!w-full',
        min: 0,
        placeholder: '请输入发料数量',
        precision: 2,
      },
      rules: 'required',
    },
    // ç‰©æ–™åž‹å· / å½•入单位 / å½•入数量 / å‘料数量(物料主单位口径)
    ...buildItemUnitSchema(formApi, { quantityLabel: '发料数量' }),
    {
      fieldName: 'batchCode',
      label: '批次号',
src/views/wls/outsourceissue/modules/line-form.vue
@@ -14,6 +14,10 @@
  updateOutsourceIssueLine,
} from '#/api/mes/wm/outsourceissue/line';
import { $t } from '#/locales';
import {
  getEnterUnitRate,
  omitLineTempFields,
} from '#/views/wls/utils/item-unit-convert';
import { useLineFormSchema } from '../data';
@@ -36,7 +40,7 @@
    labelWidth: 90,
  },
  layout: 'horizontal',
  schema: useLineFormSchema(),
  schema: [],
  showDefaultActions: false,
  wrapperClass: 'grid-cols-3',
});
@@ -48,9 +52,10 @@
      return;
    }
    modalApi.lock();
    // æäº¤è¡¨å•
    const data =
      (await formApi.getValues()) as MesWmOutsourceIssueLineApi.OutsourceIssueLine;
    // æäº¤è¡¨å•(剔除仅用于前端换算/展示的临时字段)
    const data = omitLineTempFields(
      (await formApi.getValues()) as MesWmOutsourceIssueLineApi.OutsourceIssueLine,
    );
    data.issueId = issueId.value;
    try {
      await (formData.value?.id
@@ -69,6 +74,7 @@
      formData.value = undefined;
      return;
    }
    formApi.setState({ schema: useLineFormSchema(formApi) });
    // åŠ è½½æ•°æ®
    const data = modalApi.getData<{
      id?: number;
@@ -80,9 +86,25 @@
    }
    modalApi.lock();
    try {
      formData.value = await getOutsourceIssueLine(data.id);
      // è®¾ç½®åˆ° values
      await formApi.setValues(formData.value);
      const line = await getOutsourceIssueLine(data.id);
      formData.value = line;
      // è®¾ç½®åˆ° values;详情里的 unitMeasureId/unitMeasureName å³ç‰©æ–™ä¸»å•位,补到前端换算用的隐藏字段上
      await formApi.setValues({
        ...line,
        itemPrimaryUnitId: line.unitMeasureId,
        itemPrimaryUnitName: line.unitMeasureName,
      });
      // å›žæ˜¾å½•入单位时补齐换算比率(前端预览换算需要)
      if (line.enterUnitMeasureId) {
        await formApi.setFieldValue(
          'enterUnitRate',
          await getEnterUnitRate(
            line.itemId,
            line.itemModelId,
            line.enterUnitMeasureId,
          ),
        );
      }
    } finally {
      modalApi.unlock();
    }
src/views/wls/productissue/data.ts
@@ -1,5 +1,6 @@
import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesMdItemApi } from '#/api/mes/md/item';
import type { MesProTaskApi } from '#/api/mes/pro/task';
import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
import type { MesWmProductIssueApi } from '#/api/mes/wm/productissue';
@@ -24,6 +25,10 @@
import { ProTaskSelect } from '#/views/mes/pro/task/components';
import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
import { WmMaterialStockSelect } from '#/views/wls/materialstock/components';
import {
  buildItemUnitSchema,
  useLineUnitHandlers,
} from '#/views/wls/utils/item-unit-convert';
import {
  WmWarehouseAreaSelect,
  WmWarehouseLocationSelect,
@@ -287,9 +292,24 @@
      minWidth: 120,
    },
    {
      field: 'itemModel',
      title: '物料型号',
      minWidth: 120,
    },
    {
      field: 'unitMeasureName',
      title: '单位',
      width: 80,
      title: '主计量单位',
      width: 100,
    },
    {
      field: 'enterUnitMeasureName',
      title: '录入单位',
      width: 90,
    },
    {
      field: 'enterQuantity',
      title: '录入数量',
      width: 100,
    },
    {
      field: 'quantity',
@@ -310,7 +330,8 @@
}
/** é¢†æ–™å•行新增/修改的表单 */
export function useLineFormSchema(): VbenFormSchema[] {
export function useLineFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
  const unitHandlers = useLineUnitHandlers(formApi);
  return [
    {
      fieldName: 'itemId',
@@ -318,21 +339,15 @@
      component: markRaw(MdItemSelect),
      componentProps: {
        placeholder: '请选择产品物料',
        // åˆ‡æ¢ç‰©æ–™åŽï¼Œé‡æ–°è®°å½•物料主单位并清空型号、录入单位口径
        onChange: async (item?: MesMdItemApi.Item) => {
          await unitHandlers.handleItemChange(item?.id);
        },
      },
      rules: 'selectRequired',
    },
    {
      fieldName: 'quantity',
      label: '领料数量',
      component: 'InputNumber',
      componentProps: {
        class: '!w-full',
        min: 0,
        placeholder: '请输入领料数量',
        precision: 2,
      },
      rules: 'required',
    },
    // ç‰©æ–™åž‹å· / å½•入单位 / å½•入数量 / é¢†æ–™æ•°é‡ï¼ˆç‰©æ–™ä¸»å•位口径)
    ...buildItemUnitSchema(formApi, { quantityLabel: '领料数量' }),
    {
      fieldName: 'remark',
      label: '备注',
src/views/wls/productissue/modules/line-form.vue
@@ -14,6 +14,10 @@
  updateProductIssueLine,
} from '#/api/mes/wm/productissue/line';
import { $t } from '#/locales';
import {
  getEnterUnitRate,
  omitLineTempFields,
} from '#/views/wls/utils/item-unit-convert';
import { useLineFormSchema } from '../data';
@@ -36,7 +40,7 @@
    labelWidth: 90,
  },
  layout: 'horizontal',
  schema: useLineFormSchema(),
  schema: [],
  showDefaultActions: false,
  wrapperClass: 'grid-cols-3',
});
@@ -48,9 +52,10 @@
      return;
    }
    modalApi.lock();
    // æäº¤è¡¨å•
    const data =
      (await formApi.getValues()) as MesWmProductIssueLineApi.ProductIssueLine;
    // æäº¤è¡¨å•(剔除仅用于前端换算/展示的临时字段)
    const data = omitLineTempFields(
      (await formApi.getValues()) as MesWmProductIssueLineApi.ProductIssueLine,
    );
    data.issueId = issueId.value;
    try {
      await (formData.value?.id
@@ -69,6 +74,7 @@
      formData.value = undefined;
      return;
    }
    formApi.setState({ schema: useLineFormSchema(formApi) });
    // åŠ è½½æ•°æ®
    const data = modalApi.getData<{ id?: number; issueId: number }>();
    issueId.value = data.issueId;
@@ -77,9 +83,25 @@
    }
    modalApi.lock();
    try {
      formData.value = await getProductIssueLine(data.id);
      // è®¾ç½®åˆ° values
      await formApi.setValues(formData.value);
      const line = await getProductIssueLine(data.id);
      formData.value = line;
      // è®¾ç½®åˆ° values;详情里的 unitMeasureId/unitMeasureName å³ç‰©æ–™ä¸»å•位,补到前端换算用的隐藏字段上
      await formApi.setValues({
        ...line,
        itemPrimaryUnitId: line.unitMeasureId,
        itemPrimaryUnitName: line.unitMeasureName,
      });
      // å›žæ˜¾å½•入单位时补齐换算比率(前端预览换算需要)
      if (line.enterUnitMeasureId) {
        await formApi.setFieldValue(
          'enterUnitRate',
          await getEnterUnitRate(
            line.itemId,
            line.itemModelId,
            line.enterUnitMeasureId,
          ),
        );
      }
    } finally {
      modalApi.unlock();
    }
src/views/wls/productsales/data.ts
@@ -1,5 +1,6 @@
import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesMdItemApi } from '#/api/mes/md/item';
import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
import type { MesWmProductSalesApi } from '#/api/mes/wm/productsales';
import type { MesWmProductSalesDetailApi } from '#/api/mes/wm/productsales/detail';
@@ -25,6 +26,10 @@
import { getRangePickerDefaultProps } from '#/utils';
import { MdItemSelect } from '#/views/mes/md/item/components';
import { WmMaterialStockSelect } from '#/views/wls/materialstock/components';
import {
  buildItemUnitSchema,
  useLineUnitHandlers,
} from '#/views/wls/utils/item-unit-convert';
import {
  WmSalesNoticeLineSelect,
  WmSalesNoticeSelect,
@@ -426,9 +431,24 @@
      minWidth: 120,
    },
    {
      field: 'itemModel',
      title: '物料型号',
      minWidth: 120,
    },
    {
      field: 'unitMeasureName',
      title: '单位',
      width: 80,
      title: '主计量单位',
      width: 100,
    },
    {
      field: 'enterUnitMeasureName',
      title: '录入单位',
      width: 90,
    },
    {
      field: 'enterQuantity',
      title: '录入数量',
      width: 100,
    },
    {
      field: 'quantity',
@@ -472,6 +492,7 @@
  hasNotice: boolean,
  formApi?: VbenFormApi,
): VbenFormSchema[] {
  const unitHandlers = useLineUnitHandlers(formApi);
  return [
    {
      fieldName: 'noticeId',
@@ -488,6 +509,8 @@
      componentProps: {
        // é€‰æ‹©å‘货通知单行后,自动回填物料、数量、批次、是否检验
        onChange: async (line?: MesWmSalesNoticeLineApi.SalesNoticeLine) => {
          // åˆ‡æ¢é€šçŸ¥è¡Œç­‰äºŽåˆ‡æ¢äº§å“ï¼Œå…ˆé‡ç½®åž‹å·ä¸Žå•位口径(会清空数量),再按通知行回填
          await unitHandlers.handleItemChange(line?.itemId);
          await formApi?.setValues({
            batchCode: line?.batchCode,
            itemId: line?.itemId,
@@ -510,6 +533,10 @@
      component: markRaw(MdItemSelect),
      componentProps: {
        placeholder: '请选择产品',
        // åˆ‡æ¢äº§å“åŽï¼Œé‡æ–°è®°å½•物料主单位并清空型号、录入单位口径
        onChange: async (item?: MesMdItemApi.Item) => {
          await unitHandlers.handleItemChange(item?.id);
        },
      },
      rules: 'selectRequired',
      dependencies: {
@@ -520,6 +547,8 @@
        }),
      },
    },
    // ç‰©æ–™åž‹å· / å½•入单位 / å½•入数量 / å‡ºåº“数量(物料主单位口径)
    ...buildItemUnitSchema(formApi),
    {
      fieldName: 'batchCode',
      label: '批次号',
@@ -527,18 +556,6 @@
      componentProps: {
        placeholder: '请输入批次号',
      },
    },
    {
      fieldName: 'quantity',
      label: '出库数量',
      component: 'InputNumber',
      componentProps: {
        class: '!w-full',
        min: 0,
        placeholder: '请输入出库数量',
        precision: 2,
      },
      rules: 'required',
    },
    {
      fieldName: 'oqcCheckFlag',
src/views/wls/productsales/modules/line-form.vue
@@ -14,6 +14,10 @@
  updateProductSalesLine,
} from '#/api/mes/wm/productsales/line';
import { $t } from '#/locales';
import {
  getEnterUnitRate,
  omitLineTempFields,
} from '#/views/wls/utils/item-unit-convert';
import { useLineFormSchema } from '../data';
@@ -49,9 +53,10 @@
      return;
    }
    modalApi.lock();
    // æäº¤è¡¨å•
    const data =
      (await formApi.getValues()) as MesWmProductSalesLineApi.ProductSalesLine;
    // æäº¤è¡¨å•(剔除仅用于前端换算/展示的临时字段)
    const data = omitLineTempFields(
      (await formApi.getValues()) as MesWmProductSalesLineApi.ProductSalesLine,
    );
    data.salesId = salesId.value;
    try {
      await (formData.value?.id
@@ -87,9 +92,26 @@
    }
    modalApi.lock();
    try {
      formData.value = await getProductSalesLine(data.id);
      // è®¾ç½®åˆ° values
      await formApi.setValues({ ...formData.value, noticeId: noticeId.value });
      const line = await getProductSalesLine(data.id);
      formData.value = line;
      // è®¾ç½®åˆ° values;详情里的 unitMeasureId/unitMeasureName å³ç‰©æ–™ä¸»å•位,补到前端换算用的隐藏字段上
      await formApi.setValues({
        ...line,
        noticeId: noticeId.value,
        itemPrimaryUnitId: line.unitMeasureId,
        itemPrimaryUnitName: line.unitMeasureName,
      });
      // å›žæ˜¾å½•入单位时补齐换算比率(前端预览换算需要)
      if (line.enterUnitMeasureId) {
        await formApi.setFieldValue(
          'enterUnitRate',
          await getEnterUnitRate(
            line.itemId,
            line.itemModelId,
            line.enterUnitMeasureId,
          ),
        );
      }
    } finally {
      modalApi.unlock();
    }
src/views/wls/returnvendor/data.ts
@@ -1,5 +1,6 @@
import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesMdItemApi } from '#/api/mes/md/item';
import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
import type { MesWmReturnVendorApi } from '#/api/mes/wm/returnvendor';
import type { MesWmReturnVendorDetailApi } from '#/api/mes/wm/returnvendor/detail';
@@ -17,6 +18,10 @@
import { ErpPurchaseOrderSelect } from '#/views/erp/purchase/order/components';
import { WmBatchSelect } from '#/views/wls/batch/components';
import { WmMaterialStockSelect } from '#/views/wls/materialstock/components';
import {
  buildItemUnitSchema,
  useLineUnitHandlers,
} from '#/views/wls/utils/item-unit-convert';
import {
  WmWarehouseAreaSelect,
  WmWarehouseLocationSelect,
@@ -301,9 +306,24 @@
      minWidth: 120,
    },
    {
      field: 'itemModel',
      title: '物料型号',
      minWidth: 120,
    },
    {
      field: 'unitMeasureName',
      title: '单位',
      width: 80,
      title: '主计量单位',
      width: 100,
    },
    {
      field: 'enterUnitMeasureName',
      title: '录入单位',
      width: 90,
    },
    {
      field: 'enterQuantity',
      title: '录入数量',
      width: 100,
    },
    {
      field: 'quantity',
@@ -333,6 +353,7 @@
  vendorId?: number,
  formApi?: VbenFormApi,
): VbenFormSchema[] {
  const unitHandlers = useLineUnitHandlers(formApi);
  return [
    {
      fieldName: 'itemId',
@@ -340,21 +361,15 @@
      component: markRaw(MdItemSelect),
      componentProps: {
        placeholder: '请选择产品物料',
        // åˆ‡æ¢äº§å“åŽï¼Œé‡æ–°è®°å½•物料主单位并清空型号、录入单位口径
        onChange: async (item?: MesMdItemApi.Item) => {
          await unitHandlers.handleItemChange(item?.id);
        },
      },
      rules: 'selectRequired',
    },
    {
      fieldName: 'quantity',
      label: '退货数量',
      component: 'InputNumber',
      componentProps: {
        class: '!w-full',
        min: 0,
        placeholder: '请输入退货数量',
        precision: 2,
      },
      rules: 'required',
    },
    // ç‰©æ–™åž‹å· / å½•入单位 / å½•入数量 / é€€è´§æ•°é‡ï¼ˆç‰©æ–™ä¸»å•位口径)
    ...buildItemUnitSchema(formApi, { quantityLabel: '退货数量' }),
    {
      fieldName: 'batchId',
      label: '批次号',
src/views/wls/returnvendor/modules/line-form.vue
@@ -14,6 +14,10 @@
  updateReturnVendorLine,
} from '#/api/mes/wm/returnvendor/line';
import { $t } from '#/locales';
import {
  getEnterUnitRate,
  omitLineTempFields,
} from '#/views/wls/utils/item-unit-convert';
import { useLineFormSchema } from '../data';
@@ -48,9 +52,10 @@
      return;
    }
    modalApi.lock();
    // æäº¤è¡¨å•
    const data =
      (await formApi.getValues()) as MesWmReturnVendorLineApi.ReturnVendorLine;
    // æäº¤è¡¨å•(剔除仅用于前端换算/展示的临时字段)
    const data = omitLineTempFields(
      (await formApi.getValues()) as MesWmReturnVendorLineApi.ReturnVendorLine,
    );
    data.returnId = returnId.value;
    try {
      await (formData.value?.id
@@ -82,9 +87,25 @@
    }
    modalApi.lock();
    try {
      formData.value = await getReturnVendorLine(data.id);
      // è®¾ç½®åˆ° values
      await formApi.setValues(formData.value);
      const line = await getReturnVendorLine(data.id);
      formData.value = line;
      // è®¾ç½®åˆ° values;详情里的 unitMeasureId/unitMeasureName å³ç‰©æ–™ä¸»å•位,补到前端换算用的隐藏字段上
      await formApi.setValues({
        ...line,
        itemPrimaryUnitId: line.unitMeasureId,
        itemPrimaryUnitName: line.unitMeasureName,
      });
      // å›žæ˜¾å½•入单位时补齐换算比率(前端预览换算需要)
      if (line.enterUnitMeasureId) {
        await formApi.setFieldValue(
          'enterUnitRate',
          await getEnterUnitRate(
            line.itemId,
            line.itemModelId,
            line.enterUnitMeasureId,
          ),
        );
      }
    } finally {
      modalApi.unlock();
    }
src/views/wls/utils/item-unit-convert.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,435 @@
import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
import type { MdmItemModelApi } from '#/api/mdm/item-model';
import { getItem as getMdmItem } from '#/api/mdm/item';
import { getItemModelListByItem } from '#/api/mdm/item-model';
/** å½•入单位选项(含到物料主单位的换算比率) */
export interface ItemEnterUnitOption {
  id: number;
  name: string;
  rate: number;
}
/** åž‹å·ä¸‹æ‹‰é€‰é¡¹ï¼šApiSelect ä¼šæŠŠ id/model æ˜ å°„成 value/label,其余字段原样保留 */
export type ItemModelSelectOption = Pick<
  MdmItemModelApi.ItemModel,
  'unitMeasureId' | 'unitMeasureName' | 'unitMeasureRate'
>;
/** å‡ºåº“/入库单行表单中与双单位换算相关的字段 */
export interface LineUnitFormValues {
  enterQuantity?: number;
  enterUnitMeasureId?: number;
  enterUnitMeasureName?: string;
  enterUnitRate?: number;
  itemId?: number;
  itemModelId?: number;
  itemPrimaryUnitId?: number;
  itemPrimaryUnitName?: string;
  quantityMax?: number;
}
/** åŒå•位换算用的临时表单字段(只供前端换算与展示,提交前必须剔除) */
const LINE_TEMP_FIELDS = [
  'itemPrimaryUnitId',
  'itemPrimaryUnitName',
  'enterUnitMeasureName',
  'enterUnitRate',
] as const;
/** å‰”除双单位换算用的临时字段,得到可直接提交的行数据 */
export function omitLineTempFields<T extends object>(values: T): T {
  const data = { ...values } as Record<string, unknown>;
  for (const field of LINE_TEMP_FIELDS) {
    delete data[field];
  }
  return data as T;
}
/** æ¢ç®—比率是否有效(必须大于 0) */
function isValidRate(rate?: number): rate is number {
  return typeof rate === 'number' && rate > 0;
}
/** æ¢ç®—结果按 6 ä½å°æ•°å±•示(最终以后端换算结果为准) */
function roundQuantity(quantity: number): number {
  return Math.round(quantity * 1e6) / 1e6;
}
/**
 * æŸ¥è¯¢ç‰©æ–™å¯é€‰çš„录入单位:主单位 + è¾…单位
 *
 * é€‰å®šåž‹å·æ—¶åªä¿ç•™ã€Œç‰©æ–™ä¸»å•位」与「型号计量单位」两种口径,与后端换算规则保持一致
 */
export async function getItemEnterUnits(params: {
  itemId?: number;
  itemModelId?: number;
}): Promise<ItemEnterUnitOption[]> {
  const itemId = params.itemId;
  if (!itemId) {
    return [];
  }
  const item = await getMdmItem(itemId);
  if (!item?.unitMeasureId) {
    return [];
  }
  // ä¸»å•位始终可选,按主单位录入时不做换算
  const primaryUnit: ItemEnterUnitOption = {
    id: item.unitMeasureId,
    name: item.unitMeasureName || '物料主单位',
    rate: 1,
  };
  const unitOptions: ItemEnterUnitOption[] = [primaryUnit];
  const pushUnit = (id?: number, name?: string, rate?: number) => {
    if (
      !id ||
      id === primaryUnit.id ||
      !isValidRate(rate) ||
      unitOptions.some((unit) => unit.id === id)
    ) {
      return;
    }
    unitOptions.push({ id, name: name || '辅单位', rate });
  };
  if (params.itemModelId) {
    const models = await getItemModelListByItem(itemId);
    const modelOption = models?.find(
      (option) => option.id === params.itemModelId,
    );
    pushUnit(
      modelOption?.unitMeasureId,
      modelOption?.unitMeasureName,
      modelOption?.unitMeasureRate,
    );
    return unitOptions;
  }
  // æœªé€‰åž‹å·æ—¶ï¼Œç‰©æ–™ä¸»æ•°æ®ä¸Šé…ç½®äº†æ¢ç®—比率的辅单位可选
  pushUnit(item.unitMeasureId2, item.unitMeasureName2, item.unitMeasureRate1);
  pushUnit(item.unitMeasureId3, item.unitMeasureName3, item.unitMeasureRate2);
  return unitOptions;
}
/** æŸ¥è¯¢æŸä¸ªå½•入单位到物料主单位的换算比率(编辑回显时补齐前端换算上下文) */
export async function getEnterUnitRate(
  itemId?: number,
  itemModelId?: number,
  enterUnitMeasureId?: number,
): Promise<number | undefined> {
  if (!itemId || !enterUnitMeasureId) {
    return undefined;
  }
  const unitOptions = await getItemEnterUnits({ itemId, itemModelId });
  return unitOptions.find((unit) => unit.id === enterUnitMeasureId)?.rate;
}
/** è¡Œè¡¨å•双单位录入的联动方法 */
export interface LineUnitHandlers {
  /** æ˜¯å¦æŒ‰ç‰©æ–™ä¸»å•位录入(未选录入单位、或录入单位就是物料主单位) */
  isPrimaryUnitEnter: (values: LineUnitFormValues) => boolean;
  /** åˆ‡æ¢ç‰©æ–™ï¼šé‡æ–°è®°å½•物料主单位,并清空型号、录入单位与数量(口径都随物料变化) */
  handleItemChange: (itemId?: number) => Promise<void>;
  /** é€‰æ‹©åž‹å·ï¼šé»˜è®¤æŒ‰åž‹å·è®¡é‡å•位录入,并记录换算比率 */
  handleModelChange: (
    option: ItemModelSelectOption | undefined,
    values: LineUnitFormValues,
  ) => Promise<void>;
  /** åˆ‡æ¢å½•入单位:记录换算比率并立即重算主单位数量;改回主单位时清掉录入数量 */
  handleEnterUnitChange: (
    value: number | undefined,
    option: { label?: string; rate?: number } | undefined,
    values: LineUnitFormValues,
  ) => Promise<void>;
  /** å½•入数量变化:按录入单位与换算比率试算主单位数量,供用户核对换算结果 */
  handleEnterQuantityChange: (
    value: number | undefined,
    values: LineUnitFormValues,
  ) => Promise<void>;
}
/**
 * ç”Ÿæˆè¡Œè¡¨å•的双单位录入联动方法
 *
 * @param quantityField ç‰©æ–™ä¸»å•位口径的数量字段名(出库为 quantity,入库为 receivedQuantity)
 */
export function useLineUnitHandlers(
  formApi?: VbenFormApi,
  quantityField = 'quantity',
): LineUnitHandlers {
  function isPrimaryUnitEnter(values: LineUnitFormValues) {
    return (
      !values.enterUnitMeasureId ||
      values.enterUnitMeasureId === values.itemPrimaryUnitId
    );
  }
  async function handleItemChange(itemId?: number) {
    // å…ˆæ¸…空,避免上一个物料的型号与单位口径残留
    await formApi?.setValues({
      itemId,
      itemModelId: undefined,
      itemPrimaryUnitId: undefined,
      itemPrimaryUnitName: undefined,
      enterUnitMeasureId: undefined,
      enterUnitMeasureName: undefined,
      enterUnitRate: undefined,
      enterQuantity: undefined,
      [quantityField]: undefined,
    });
    if (!itemId) {
      return;
    }
    const item = await getMdmItem(itemId);
    await formApi?.setValues({
      itemPrimaryUnitId: item?.unitMeasureId,
      itemPrimaryUnitName: item?.unitMeasureName,
    });
  }
  async function handleModelChange(
    option: ItemModelSelectOption | undefined,
    values: LineUnitFormValues,
  ) {
    const enterUnitMeasureId =
      option?.unitMeasureId && option.unitMeasureId !== values.itemPrimaryUnitId
        ? option.unitMeasureId
        : undefined;
    await formApi?.setValues({
      enterUnitMeasureId,
      enterUnitMeasureName: enterUnitMeasureId
        ? option?.unitMeasureName
        : undefined,
      enterUnitRate: enterUnitMeasureId ? option?.unitMeasureRate : undefined,
      enterQuantity: undefined,
      // åˆ‡åˆ°åž‹å·å•位录入后主单位数量由系统换算,先清掉上一次口径的数值
      ...(enterUnitMeasureId ? { [quantityField]: undefined } : {}),
    });
  }
  async function handleEnterUnitChange(
    value: number | undefined,
    option: { label?: string; rate?: number } | undefined,
    values: LineUnitFormValues,
  ) {
    if (!value || value === values.itemPrimaryUnitId) {
      await formApi?.setValues({
        enterUnitMeasureName: undefined,
        enterUnitRate: undefined,
        enterQuantity: undefined,
      });
      return;
    }
    await formApi?.setValues({
      enterUnitMeasureName: option?.label,
      enterUnitRate: option?.rate,
      ...(values.enterQuantity == null || !isValidRate(option?.rate)
        ? { [quantityField]: undefined }
        : {
            [quantityField]: roundQuantity(
              values.enterQuantity * option.rate,
            ),
          }),
    });
  }
  async function handleEnterQuantityChange(
    value: number | undefined,
    values: LineUnitFormValues,
  ) {
    if (isPrimaryUnitEnter(values) || !isValidRate(values.enterUnitRate)) {
      return;
    }
    await formApi?.setFieldValue(
      quantityField,
      value == null ? undefined : roundQuantity(value * values.enterUnitRate),
    );
  }
  return {
    handleEnterQuantityChange,
    handleEnterUnitChange,
    handleItemChange,
    handleModelChange,
    isPrimaryUnitEnter,
  };
}
/** è¡Œè¡¨å•「物料型号 / å½•入单位 / å½•入数量 / ä¸»å•位数量」相关字段的通用配置 */
export interface ItemUnitSchemaOptions {
  /** ç‰©æ–™ä¸»å•位口径的数量字段名(默认 quantity) */
  quantityField?: string;
  /** ç‰©æ–™ä¸»å•位口径的数量字段标签(默认「出库数量」) */
  quantityLabel?: string;
}
/**
 * ç”Ÿæˆè¡Œè¡¨å•的双单位字段组(型号、录入单位、录入数量、主单位数量及换算用隐藏字段)
 *
 * ä¸»å•位数量字段在按辅单位录入时由系统换算、不可手工修改;按主单位录入时手工填写并受库存上限约束
 */
export function buildItemUnitSchema(
  formApi?: VbenFormApi,
  options: ItemUnitSchemaOptions = {},
): VbenFormSchema[] {
  const quantityField = options.quantityField ?? 'quantity';
  const quantityLabel = options.quantityLabel ?? '出库数量';
  const handlers = useLineUnitHandlers(formApi, quantityField);
  return [
    // ç‰©æ–™ä¸»å•位随所选物料变化,但表单 props åªèƒ½é™æ€å£°æ˜Žï¼Œæ•…用隐藏字段承载
    {
      fieldName: 'itemPrimaryUnitId',
      component: 'Input',
      dependencies: {
        triggerFields: [''],
        show: () => false,
      },
    },
    {
      fieldName: 'itemPrimaryUnitName',
      component: 'Input',
      dependencies: {
        triggerFields: [''],
        show: () => false,
      },
    },
    {
      fieldName: 'itemModelId',
      label: '物料型号',
      component: 'ApiSelect',
      componentProps: {
        labelField: 'model',
        valueField: 'id',
        allowClear: true,
        api: (params: { itemId?: number }) =>
          getItemModelListByItem(params.itemId!),
        shouldFetch: (params: { itemId?: number }) => !!params?.itemId,
        placeholder: '请先选择物料',
      },
      dependencies: {
        triggerFields: ['itemId'],
        componentProps: (values) => ({
          disabled: !values.itemId,
          params: { itemId: values.itemId },
          placeholder: values.itemId ? '请选择物料型号' : '请先选择物料',
          // é€‰å®šåž‹å·åŽé»˜è®¤æŒ‰å…¶è®¡é‡å•位录入
          onChange: (option?: ItemModelSelectOption) =>
            handlers.handleModelChange(option, values),
        }),
      },
    },
    {
      fieldName: 'enterUnitMeasureId',
      label: '录入单位',
      component: 'ApiSelect',
      componentProps: {
        allowClear: true,
        labelField: 'name',
        valueField: 'id',
        api: (params: { itemId?: number; itemModelId?: number }) =>
          getItemEnterUnits(params),
        shouldFetch: (params: { itemId?: number }) => !!params?.itemId,
        placeholder: '默认为物料主计量单位',
      },
      dependencies: {
        triggerFields: ['itemId', 'itemModelId', 'itemPrimaryUnitName'],
        componentProps: (values) => ({
          disabled: !values.itemId,
          params: { itemId: values.itemId, itemModelId: values.itemModelId },
          placeholder: values.itemId
            ? `默认按物料主单位${values.itemPrimaryUnitName ? `【${values.itemPrimaryUnitName}】` : ''}录入`
            : '请先选择物料',
          onChange: (
            value: number | undefined,
            option?: { label?: string; rate?: number },
          ) => handlers.handleEnterUnitChange(value, option, values),
        }),
      },
    },
    // å½•入单位名称与换算比率随所选单位变化,同样用隐藏字段承载
    {
      fieldName: 'enterUnitMeasureName',
      component: 'Input',
      dependencies: {
        triggerFields: [''],
        show: () => false,
      },
    },
    {
      fieldName: 'enterUnitRate',
      component: 'Input',
      dependencies: {
        triggerFields: [''],
        show: () => false,
      },
    },
    {
      fieldName: 'enterQuantity',
      label: '录入数量',
      component: 'InputNumber',
      componentProps: {
        class: '!w-full',
        min: 0,
        precision: 6,
      },
      dependencies: {
        triggerFields: [
          'enterUnitMeasureId',
          'enterUnitMeasureName',
          'itemPrimaryUnitId',
          'quantityMax',
          'enterUnitRate',
        ],
        show: (values) => !handlers.isPrimaryUnitEnter(values),
        // è¿”回 null è¡¨ç¤ºä¸è¿½åŠ è§„åˆ™ï¼ˆå›žé€€åŸºç¡€ rules)
        rules: (values) =>
          handlers.isPrimaryUnitEnter(values) ? null : 'required',
        componentProps: (values) => ({
          placeholder: values.enterUnitMeasureName
            ? `请填写按【${values.enterUnitMeasureName}】计的数量`
            : '请按录入单位填写数量',
          // æŒ‰è¾…单位录入时,可录入上限为「库存上限 Ã· æ¢ç®—比率」
          max:
            values.quantityMax != null && isValidRate(values.enterUnitRate)
              ? Math.floor((values.quantityMax / values.enterUnitRate) * 1e6) /
                1e6
              : undefined,
          // å½•入数量变化时立即试算主单位数量
          onChange: (value: number | undefined) =>
            handlers.handleEnterQuantityChange(value, values),
        }),
      },
    },
    {
      fieldName: quantityField,
      label: quantityLabel,
      component: 'InputNumber',
      componentProps: {
        class: '!w-full',
        min: 0.01,
        precision: 6,
      },
      dependencies: {
        triggerFields: [
          'enterUnitMeasureId',
          'itemPrimaryUnitId',
          'itemPrimaryUnitName',
          'quantityMax',
        ],
        // æŒ‰ä¸»å•位录入时手工填写、必填;按辅单位录入时为系统换算结果,无需校验
        rules: (values) =>
          handlers.isPrimaryUnitEnter(values) ? 'required' : null,
        componentProps: (values) => {
          const primary = handlers.isPrimaryUnitEnter(values);
          return {
            disabled: !primary,
            max: values.quantityMax,
            placeholder: primary
              ? `请按物料主单位${values.itemPrimaryUnitName ? `【${values.itemPrimaryUnitName}】` : ''}填写数量`
              : '按录入单位与换算比率自动换算',
          };
        },
      },
    },
  ];
}