云
5 小时以前 fb4db4ae9b6a7f113ba049f6789e6916c0dcc276
src/views/wls/productsales/modules/form.vue
@@ -16,6 +16,7 @@
  createProductSales,
  finishProductSales,
  getProductSales,
  previewProductSalesScan,
  scanProductSales,
  shippingProductSales,
  stockProductSales,
@@ -26,12 +27,15 @@
import { useFormSchema } from '../data';
import LineList from './line-list.vue';
import PickingBags from './picking-bags.vue';
const emit = defineEmits(['success']);
const formType = ref<FormType>('create');
const formData = ref<MesWmProductSalesApi.ProductSales>();
const scanContent = ref('');
const scanQuantity = ref<number>();
const scanType = ref<'BAG' | 'PALLET' | 'BATCH'>('BAG');
const scanPreview = ref<MesWmProductSalesApi.ProductSalesScanResponse>();
const selectedBagIds = ref<number[]>([]);
const scanLoading = ref(false);
const scanRefreshKey = ref(0);
const originalSnapshot = ref(''); // 表单原始数据快照,用于提交时跳过未变更的保存请求
@@ -104,25 +108,45 @@
  }
}
/** 扫码拣货:扫码枪通常以回车结束输入 */
/** 先识别对象并选择袋码,回车不会直接写入拣货。 */
async function handleScan() {
  if (!formData.value?.id || !scanContent.value.trim() || scanLoading.value) {
    return;
  }
  scanLoading.value = true;
  try {
    scanPreview.value = undefined;
    selectedBagIds.value = [];
    const result = await previewProductSalesScan(scanContent.value.trim(), scanType.value, formData.value.id);
    scanPreview.value = result;
    if (result.scanType === 'BAG') selectedBagIds.value = result.bags?.filter(b => b.selectable).map(b => b.id) || [];
    if (!result.scanQuantity) message.warning('当前对象没有可拣袋码,请核对质检和库存状态');
  } finally { scanLoading.value = false; }
}
async function confirmScan() {
  if (!formData.value?.id || !scanPreview.value || !selectedBagIds.value.length || scanLoading.value) return;
  const source = scanPreview.value;
  const bagIds = [...selectedBagIds.value];
  scanLoading.value = true;
  try { await confirm(`确认将所选 ${bagIds.length} 袋加入拣货并关联追溯袋码?`); }
  catch { scanLoading.value = false; return; }
  try {
    const result = await scanProductSales({
      quantity: scanQuantity.value,
      quantity: bagIds.length,
      salesId: formData.value.id,
      scanContent: scanContent.value.trim(),
      scanContent: source.scanContent!,
      scanType: source.scanType as 'BAG' | 'PALLET' | 'BATCH',
      bagIds,
    });
    message[result.alreadyScanned ? 'warning' : 'success'](
      result.alreadyScanned
        ? `该码已扫描,明细编号:${result.detailId ?? '-'}`
        : `${result.message || '扫码成功'}${result.scanType ? `(${result.scanType === 'PALLET' ? '托盘码' : '袋码'})` : ''}`,
        : `已加入拣货 ${result.outboundQuantity} 袋,逐袋关联追溯码`,
    );
    scanContent.value = '';
    scanQuantity.value = undefined;
    scanPreview.value = undefined;
    selectedBagIds.value = [];
    scanRefreshKey.value += 1;
  } finally {
    scanLoading.value = false;
@@ -228,6 +252,9 @@
    }
  },
  async onOpenChange(isOpen: boolean) {
    scanPreview.value = undefined;
    selectedBagIds.value = [];
    scanContent.value = '';
    if (!isOpen) {
      formData.value = undefined;
      originalSnapshot.value = '';
@@ -262,25 +289,28 @@
      <Divider>物料信息</Divider>
      <div class="mx-4">
        <div v-if="isStock" class="mb-3 flex items-center gap-2 px-4">
          <label for="sales-picking-type" class="sr-only">拣货扫码类型</label>
          <a-select id="sales-picking-type" v-model:value="scanType" :disabled="scanLoading" style="width:140px" @change="scanPreview = undefined; selectedBagIds = []">
            <a-select-option value="BAG">袋码</a-select-option><a-select-option value="PALLET">托盘码</a-select-option><a-select-option value="BATCH">生产批次码</a-select-option>
          </a-select>
          <a-input
            v-model:value="scanContent"
            :disabled="scanLoading"
            autofocus
            class="flex-1"
            placeholder="请扫描袋码或托盘码,回车提交"
            placeholder="扫描所选类型码,回车识别"
            @press-enter="handleScan"
          />
          <a-input-number
            v-model:value="scanQuantity"
            :disabled="scanLoading"
            :min="0"
            placeholder="数量(可选)"
            style="width: 140px"
          />
          <Button :loading="scanLoading" type="default" @click="handleScan">
            扫码
            识别码值
          </Button>
        </div>
        <section v-if="isStock && scanPreview" class="mb-4 rounded-lg border border-border p-4">
          <h3 class="font-semibold">核对拣货对象</h3><p>批次 {{scanPreview.batchCode}} · 可拣 {{scanPreview.scanQuantity}} 袋</p>
          <PickingBags v-if="scanPreview.scanType !== 'BAG'" v-model="selectedBagIds" :bags="scanPreview.bags || []" :disabled="scanLoading" />
          <p v-else>袋码 {{scanPreview.bags?.[0]?.code}} · 本次 {{selectedBagIds.length}} 袋</p>
          <div class="mt-3 flex gap-2"><Button type="primary" :loading="scanLoading" :disabled="!selectedBagIds.length" @click="confirmScan">确认加入拣货</Button><Button :disabled="scanLoading" @click="scanPreview = undefined">取消本次扫码</Button></div>
        </section>
        <LineList
          :form-type="formType"
          :notice-id="formData.noticeId"
@@ -298,7 +328,7 @@
        >
          <Button type="primary">提交</Button>
        </Popconfirm>
        <Button v-if="isStock" type="primary" @click="handleStock">
        <Button v-if="isStock" type="primary" :disabled="scanLoading || !!scanPreview" @click="handleStock">
          执行拣货
        </Button>
        <Popconfirm