huminmin
8 天以前 50dcd8345bc5d7baa6c1c8d914793175a86d0b50
修改销售台账工艺路线配置页面,增加导出工艺路线
已修改2个文件
175 ■■■■■ 文件已修改
src/views/salesManagement/salesLedger/components/ProcessFlowConfigSelectDialog.vue 120 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesLedger/index.vue 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesLedger/components/ProcessFlowConfigSelectDialog.vue
@@ -16,20 +16,6 @@
              <span v-else style="color: #e6a23c;">未绑定</span>
            </div>
          </div>
          <div class="export-toolbar">
            <el-date-picker
              v-model="exportDateRange"
              type="daterange"
              value-format="YYYY-MM-DD"
              format="YYYY-MM-DD"
              range-separator="至"
              start-placeholder="开始日期"
              end-placeholder="结束日期"
              clearable
              style="width: 280px;"
            />
            <el-button type="success" plain @click="exportSelectedSteps">导出已勾选</el-button>
          </div>
        </div>
        <el-select
          v-model="selectedRouteId"
@@ -51,7 +37,7 @@
        <div style="font-weight: 600; margin-bottom: 8px;">步骤预览</div>
        <div style="font-size: 12px; color: #909399; margin-bottom: 10px;">
          根据所选配置展示流程图
          根据所选配置展示流程图,勾选表示该工序已完成
        </div>
      </el-col>
@@ -65,12 +51,15 @@
          >
            <div class="process-diagram-node">
              <el-checkbox
                v-model="step.checked"
                v-model="step.isCompleted"
                class="process-diagram-checkbox"
                @change="() => handleStepCheckedChange(step)"
                @change="() => handleStepCompletedChange(step)"
              />
              <div class="process-diagram-index">{{ idx + 1 }}</div>
              <div class="process-diagram-name">{{ step.processName }}</div>
              <div class="process-diagram-status" :class="{ 'is-done': Number(step.isCompleted) === 1 }">
                {{ Number(step.isCompleted) === 1 ? "已完成" : "未完成" }}
              </div>
            </div>
            <div v-if="idx < steps.length - 1" class="process-diagram-arrow">→</div>
          </div>
@@ -102,6 +91,8 @@
  visible: { type: Boolean, default: false },
  // 打开弹窗时的回显:若业务已绑定工艺路线则传入该 routeId;否则默认展示列表第一条
  defaultRouteId: { type: [Number, String, null], default: null },
  // 打开弹窗时的工序完成记录回显
  defaultRecordList: { type: Array, default: () => [] },
  // 页面提示:订单已绑定的工艺路线名称
  boundRouteName: { type: String, default: "" },
});
@@ -120,18 +111,17 @@
const routeList = ref([]);
const selectedRouteId = ref(null);
const steps = ref([]);
const exportDateRange = ref([]);
const saving = ref(false);
const normalizeStepsFromApi = (list) => {
  if (!Array.isArray(list)) return [];
  return list.map((s, idx) => ({
    stepId: s.stepId ?? s.id ?? null,
    processRouteItemId: s.processRouteItemId ?? s.process_route_item_id ?? s.id ?? null,
    processId: s.processId ?? s.process_id ?? s.id ?? null,
    processName: s.processName ?? s.process_name ?? s.name ?? "",
    sortNo: s.sortNo ?? idx + 1,
    isCompleted: Number(s.isCompleted ?? s.completed ?? 0),
    checked: Boolean(s.checked ?? false),
    isCompleted: Boolean(Number(s.isCompleted ?? s.completed ?? 0)),
  }));
};
@@ -142,6 +132,27 @@
    processRouteName: r.processRouteName ?? r.routeName ?? r.name ?? "",
    isDefault: Boolean(r.isDefault),
  }));
};
const applyRecordListToSteps = (stepList, recordList) => {
  if (!Array.isArray(stepList) || stepList.length === 0) return stepList;
  if (!Array.isArray(recordList) || recordList.length === 0) return stepList;
  const recordMap = new Map(
    recordList
      .filter((item) => item && item.processRouteItemId !== null && item.processRouteItemId !== undefined)
      .map((item) => [String(item.processRouteItemId), item])
  );
  return stepList.map((step) => {
    const matched = recordMap.get(String(step.processRouteItemId));
    if (!matched) return step;
    return {
      ...step,
      isCompleted: Boolean(Number(matched.isCompleted ?? 0)),
      completedTime: matched.completedTime ?? matched.completed_time ?? null,
    };
  });
};
const fetchRouteList = async () => {
@@ -158,7 +169,12 @@
  }
  const res = await salesProcessFlowConfigItemList(routeId);
  const raw = res?.data ?? res ?? [];
  steps.value = normalizeStepsFromApi(raw);
  const normalizedSteps = normalizeStepsFromApi(raw);
  if (String(routeId) === String(props.defaultRouteId)) {
    steps.value = applyRecordListToSteps(normalizedSteps, props.defaultRecordList);
    return;
  }
  steps.value = normalizedSteps;
};
watch(
@@ -189,8 +205,8 @@
  await fetchRouteSteps(selectedRouteId.value);
};
const handleStepCheckedChange = step => {
  step.checked = Boolean(step.checked);
const handleStepCompletedChange = (step) => {
  step.isCompleted = Boolean(step.isCompleted);
};
const handleClose = () => {
@@ -206,44 +222,18 @@
  }
  saving.value = true;
  try {
    emit("confirm", selectedRouteId.value);
    emit("confirm", {
      routeId: selectedRouteId.value,
      recordList: steps.value.map((step) => ({
        processRouteItemId: step.processRouteItemId,
        isCompleted: Number(step.isCompleted ?? 0),
      })),
    });
  } catch (e) {
    proxy?.$modal?.msgError?.("确认失败,请稍后重试");
  } finally {
    saving.value = false;
  }
};
const exportSelectedSteps = () => {
  const selectedSteps = steps.value.filter(step => step.checked);
  if (selectedSteps.length === 0) {
    proxy?.$modal?.msgWarning?.("请先勾选要导出的工序");
    return;
  }
  const payload = {
    exportDateRange: Array.isArray(exportDateRange.value) ? exportDateRange.value : [],
    routeId: selectedRouteId.value,
    routeName: routeList.value.find(item => String(item.routeId) === String(selectedRouteId.value))?.processRouteName || "",
    steps: selectedSteps.map(step => ({
      processId: step.processId,
      processName: step.processName,
      sortNo: step.sortNo,
      isCompleted: Number(step.isCompleted ?? 0),
    })),
  };
  const blob = new Blob([JSON.stringify(payload, null, 2)], {
    type: "application/json;charset=utf-8",
  });
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  const dateText =
    Array.isArray(exportDateRange.value) && exportDateRange.value.length === 2
      ? `${exportDateRange.value[0]}_${exportDateRange.value[1]}`
      : "all";
  a.href = url;
  a.download = `工艺路线导出_${dateText}.json`;
  a.click();
  URL.revokeObjectURL(url);
};
</script>
@@ -299,6 +289,17 @@
  text-overflow: ellipsis;
}
.process-diagram-status {
  margin-top: 4px;
  font-size: 12px;
  color: #909399;
}
.process-diagram-status.is-done {
  color: #67c23a;
  font-weight: 600;
}
.process-diagram-arrow {
  font-size: 18px;
  color: #909399;
@@ -326,12 +327,5 @@
  justify-content: space-between;
  align-items: flex-start;
  gap: 16px;
}
.export-toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
</style>
src/views/salesManagement/salesLedger/index.vue
@@ -832,6 +832,7 @@
        </div>
        <ProcessFlowConfigSelectDialog v-model:visible="processFlowSelectDialogVisible"
                                       :default-route-id="processFlowSelectDefaultRouteId"
                                       :default-record-list="processFlowSelectDefaultRecordList"
                                       :bound-route-name="processFlowSelectBoundRouteName"
                                       @confirm="handleProcessFlowSelectConfirm" />
        <div class="sales-ledger-toolbar-actions">
@@ -866,6 +867,17 @@
            <el-button type="primary"
                       @click="handleBulkDelivery"
                       :disabled="isBatchButtonDisabled('delivery')">发货</el-button>
            <el-date-picker v-model="processRouteExportDateRange"
                            type="datetimerange"
                            range-separator="至"
                            start-placeholder="开始时间"
                            end-placeholder="结束时间"
                            value-format="YYYY-MM-DD HH:mm:ss"
                            format="YYYY-MM-DD HH:mm:ss"
                            clearable
                            style="width: 340px;" />
            <el-button @click="handleExportProcessRoute"
                       :disabled="isBatchButtonDisabled('export')">导出工艺路线</el-button>
          </el-space>
          <el-space v-else-if="activeStatusTab === 'stocked'"
                    wrap
@@ -2609,11 +2621,13 @@
  });
  const total = ref(0);
  const fileList = ref([]);
  const processRouteExportDateRange = ref([]);
  // 工艺路线配置选择弹窗(绑定到台账产品)
  const processFlowSelectDialogVisible = ref(false);
  const processFlowSelectLedgerRow = ref(null);
  const processFlowSelectDefaultRouteId = ref(null);
  const processFlowSelectDefaultRecordList = ref([]);
  const processFlowSelectBoundRouteId = ref(null);
  const processFlowSelectBoundRouteName = ref("");
@@ -4282,6 +4296,7 @@
    processFlowSelectLedgerRow.value = ledgerRow;
    processFlowSelectDefaultRouteId.value = null;
    processFlowSelectDefaultRecordList.value = [];
    processFlowSelectBoundRouteId.value = null;
    processFlowSelectBoundRouteName.value = "";
@@ -4291,25 +4306,29 @@
      const boundId = info?.processRouteId ?? info?.routeId ?? info?.id ?? null;
      const boundName =
        info?.processRouteName ?? info?.routeName ?? info?.name ?? "";
      const recordList = Array.isArray(info?.recordList) ? info.recordList : [];
      processFlowSelectBoundRouteId.value = boundId;
      processFlowSelectBoundRouteName.value = boundName;
      processFlowSelectDefaultRouteId.value = boundId;
      processFlowSelectDefaultRecordList.value = recordList;
    } catch (e) {
      // 查询失败时按未绑定处理,不阻塞弹窗
      processFlowSelectBoundRouteId.value = null;
      processFlowSelectBoundRouteName.value = "";
      processFlowSelectDefaultRouteId.value = null;
      processFlowSelectDefaultRecordList.value = [];
    }
    processFlowSelectDialogVisible.value = true;
  };
  // 绑定工艺路线到当前台账数据
  const handleProcessFlowSelectConfirm = async routeId => {
  const handleProcessFlowSelectConfirm = async payload => {
    const ledgerRow = processFlowSelectLedgerRow.value;
    if (!ledgerRow?.id) return;
    const finalRouteId = routeId ?? null;
    const finalRouteId = payload?.routeId ?? payload ?? null;
    const recordList = Array.isArray(payload?.recordList) ? payload.recordList : [];
    if (!finalRouteId) return;
    const oldRouteId = processFlowSelectBoundRouteId.value;
@@ -4339,6 +4358,7 @@
      await saleProcessBind({
        salesLedgerId: ledgerRow.id,
        processRouteId: finalRouteId,
        recordList,
      });
      proxy?.$modal?.msgSuccess?.("工艺路线绑定成功");
@@ -5292,6 +5312,37 @@
    proxy.download("/sales/ledger/exportWithProducts", params, "销售台账.xlsx");
  };
  const handleExportProcessRoute = () => {
    if (selectedRows.value.length === 0) {
      proxy?.$modal?.msgWarning?.("请选择要导出的销售台账");
      return;
    }
    const salesLedgerIds = selectedRows.value
      .map(item => item.id)
      .filter(id => id !== null && id !== undefined && id !== "");
    if (salesLedgerIds.length === 0) {
      proxy?.$modal?.msgWarning?.("请选择要导出的销售台账");
      return;
    }
    const params = {
      salesLedgerIds: salesLedgerIds.join(","),
    };
    if (
      Array.isArray(processRouteExportDateRange.value) &&
      processRouteExportDateRange.value.length === 2
    ) {
      params.completedTimeStart = processRouteExportDateRange.value[0];
      params.completedTimeEnd = processRouteExportDateRange.value[1];
    }
    proxy.download(
      "/sales/ledger/exportProcessRoute",
      params,
      "销售台账工艺路线导出.xlsx"
    );
  };
  /** 判断单个产品是否已发货(根据shippingStatus判断,已发货或审核通过不可编辑和删除) */
  const isProductShipped = product => {
    if (!product) return false;