gongchunyi
3 天以前 492802e4fc1b371ba21a2a490c8dcd67d7c8b29c
src/views/salesManagement/salesLedger/components/ProcessFlowConfigSelectDialog.vue
@@ -7,33 +7,41 @@
    @close="handleClose"
  >
    <el-row :gutter="20">
      <el-col :span="10">
        <div style="font-weight: 600; margin-bottom: 8px;">配置</div>
      <el-col :span="24">
        <div class="dialog-topbar">
          <div>
            <div style="font-weight: 600; margin-bottom: 8px;">配置</div>
            <div style="font-size: 12px; margin-bottom: 8px;">
              <span v-if="boundRouteName" style="color: #67c23a;">已绑定:{{ boundRouteName }}</span>
              <span v-else style="color: #e6a23c;">未绑定</span>
            </div>
          </div>
        </div>
        <el-select
          v-model="selectedConfigId"
          v-model="selectedRouteId"
          filterable
          clearable
          placeholder="请选择工艺路线配置"
          placeholder="请选择工艺路线"
          style="width: 100%;"
          @change="handleConfigChange"
          @change="handleRouteChange"
        >
          <el-option
            v-for="cfg in configList"
            :key="cfg.configId"
            :label="cfg.configName"
            :value="cfg.configId"
            v-for="cfg in routeList"
            :key="cfg.routeId"
            :label="cfg.processRouteName"
            :value="cfg.routeId"
          />
        </el-select>
        <el-divider style="margin: 16px 0;" />
        <div style="font-weight: 600; margin-bottom: 8px;">步骤预览</div>
        <div style="font-size: 12px; color: #909399; margin-bottom: 6px;">
          根据所选配置展示流程图
        <div style="font-size: 12px; color: #909399; margin-bottom: 10px;">
          根据所选配置展示流程图,勾选表示该工序已完成
        </div>
      </el-col>
      <el-col :span="14">
      <el-col :span="24">
        <div class="process-diagram">
          <div v-if="steps.length === 0" class="process-diagram-empty">暂无步骤</div>
          <div
@@ -42,13 +50,21 @@
            class="process-diagram-segment"
          >
            <div class="process-diagram-node">
              <el-checkbox
                v-model="step.isCompleted"
                class="process-diagram-checkbox"
                @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>
        </div>
        <div v-if="selectedConfigId === null" style="margin-top: 10px; font-size: 12px; color: #909399;">
        <div v-if="selectedRouteId === null" style="margin-top: 10px; font-size: 12px; color: #909399;">
          请先选择一条已维护好的工艺路线
        </div>
      </el-col>
@@ -67,13 +83,18 @@
<script setup>
import { computed, getCurrentInstance, ref, watch } from "vue";
import { salesProcessFlowConfigList, salesProcessFlowConfigGetById } from "@/api/salesManagement/salesProcessFlowConfig.js";
import { salesProcessFlowConfigList, salesProcessFlowConfigItemList } from "@/api/salesManagement/salesProcessFlowConfig.js";
const emit = defineEmits(["update:visible", "confirm"]);
const props = defineProps({
  visible: { type: Boolean, default: false },
  defaultConfigId: { type: [Number, String, null], default: null },
  // 打开弹窗时的回显:若业务已绑定工艺路线则传入该 routeId;否则默认展示列表第一条
  defaultRouteId: { type: [Number, String, null], default: null },
  // 打开弹窗时的工序完成记录回显
  defaultRecordList: { type: Array, default: () => [] },
  // 页面提示:订单已绑定的工艺路线名称
  boundRouteName: { type: String, default: "" },
});
const { proxy } = getCurrentInstance();
@@ -87,8 +108,8 @@
  },
});
const configList = ref([]);
const selectedConfigId = ref(null);
const routeList = ref([]);
const selectedRouteId = ref(null);
const steps = ref([]);
const saving = ref(false);
@@ -96,32 +117,64 @@
  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: Boolean(Number(s.isCompleted ?? s.completed ?? 0)),
  }));
};
const fetchConfigList = async () => {
  const res = await salesProcessFlowConfigList();
  const list = res?.data ?? res?.records ?? res ?? [];
  configList.value = Array.isArray(list) ? list : [];
const normalizeRouteList = (list) => {
  if (!Array.isArray(list)) return [];
  return list.map((r) => ({
    routeId: r.routeId ?? r.id ?? null,
    processRouteName: r.processRouteName ?? r.routeName ?? r.name ?? "",
    isDefault: Boolean(r.isDefault),
  }));
};
const fetchConfigDetail = async (id) => {
  if (!id) {
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 () => {
  // 选择弹窗:尽量一次性拉全,避免分页影响选择体验
  const res = await salesProcessFlowConfigList({ current: 1, size: 1000 });
  const records = res?.records ?? res?.data?.records ?? res?.data ?? res ?? [];
  routeList.value = normalizeRouteList(records).filter((r) => r.routeId !== null && r.routeId !== undefined && r.routeId !== "");
};
const fetchRouteSteps = async (routeId) => {
  if (!routeId) {
    steps.value = [];
    return;
  }
  const res = await salesProcessFlowConfigGetById(id);
  const detail = res?.data ?? res ?? {};
  steps.value = normalizeStepsFromApi(detail?.steps ?? []);
};
const initDefault = async () => {
  await fetchConfigList();
  selectedConfigId.value = props.defaultConfigId ?? null;
  await fetchConfigDetail(selectedConfigId.value);
  const res = await salesProcessFlowConfigItemList(routeId);
  const raw = res?.data ?? res ?? [];
  const normalizedSteps = normalizeStepsFromApi(raw);
  if (String(routeId) === String(props.defaultRouteId)) {
    steps.value = applyRecordListToSteps(normalizedSteps, props.defaultRecordList);
    return;
  }
  steps.value = normalizedSteps;
};
watch(
@@ -129,7 +182,18 @@
  async (v) => {
    if (v) {
      try {
        await initDefault();
        await fetchRouteList();
        // 回显绑定:
        // 1. 若传入 defaultRouteId,则优先使用它
        // 2. 否则优先选中标记为默认(isDefault=true)的工艺路线
        // 3. 若都没有,则回退为第一条
        const first = routeList.value?.[0] ?? null;
        const defaultRoute =
          routeList.value.find((r) => r.isDefault) ?? first;
        const desired = props.defaultRouteId ?? (defaultRoute ? defaultRoute.routeId : null);
        selectedRouteId.value = desired ?? null;
        await fetchRouteSteps(selectedRouteId.value);
      } catch {
        proxy?.$modal?.msgError?.("获取工艺路线配置失败");
      }
@@ -137,8 +201,12 @@
  }
);
const handleConfigChange = async () => {
  await fetchConfigDetail(selectedConfigId.value);
const handleRouteChange = async () => {
  await fetchRouteSteps(selectedRouteId.value);
};
const handleStepCompletedChange = (step) => {
  step.isCompleted = Boolean(step.isCompleted);
};
const handleClose = () => {
@@ -148,14 +216,19 @@
const confirmSelect = async () => {
  if (saving.value) return;
  if (selectedConfigId.value === null || selectedConfigId.value === undefined || selectedConfigId.value === "") {
    proxy?.$modal?.msgWarning?.("请选择工艺路线配置");
  if (selectedRouteId.value === null || selectedRouteId.value === undefined || selectedRouteId.value === "") {
    proxy?.$modal?.msgWarning?.("请选择工艺路线");
    return;
  }
  saving.value = true;
  try {
    emit("confirm", selectedConfigId.value);
    handleClose();
    emit("confirm", {
      routeId: selectedRouteId.value,
      recordList: steps.value.map((step) => ({
        processRouteItemId: step.processRouteItemId,
        isCompleted: Number(step.isCompleted ?? 0),
      })),
    });
  } catch (e) {
    proxy?.$modal?.msgError?.("确认失败,请稍后重试");
  } finally {
@@ -192,6 +265,13 @@
  padding: 10px 12px;
  margin-right: 10px;
  box-sizing: border-box;
  position: relative;
}
.process-diagram-checkbox {
  position: absolute;
  top: 8px;
  right: 8px;
}
.process-diagram-index {
@@ -207,6 +287,17 @@
  white-space: nowrap;
  overflow: hidden;
  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 {
@@ -230,5 +321,11 @@
  justify-content: flex-end;
  gap: 10px;
}
</style>
.dialog-topbar {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 16px;
}
</style>