<template>
|
<el-dialog
|
v-model="visible"
|
title="选择工艺路线配置"
|
width="1000px"
|
:close-on-click-modal="false"
|
@close="handleClose"
|
>
|
<el-row :gutter="20">
|
<el-col :span="24">
|
<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>
|
<el-select
|
v-model="selectedRouteId"
|
filterable
|
clearable
|
placeholder="请选择工艺路线"
|
style="width: 100%;"
|
@change="handleRouteChange"
|
>
|
<el-option
|
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: 10px;">
|
根据所选配置展示流程图
|
</div>
|
</el-col>
|
|
<el-col :span="24">
|
<div class="process-diagram">
|
<div v-if="steps.length === 0" class="process-diagram-empty">暂无步骤</div>
|
<div
|
v-for="(step, idx) in steps"
|
:key="String(step.processId) + '_' + idx"
|
class="process-diagram-segment"
|
>
|
<div class="process-diagram-node">
|
<div class="process-diagram-index">{{ idx + 1 }}</div>
|
<div class="process-diagram-name">{{ step.processName }}</div>
|
</div>
|
<div v-if="idx < steps.length - 1" class="process-diagram-arrow">→</div>
|
</div>
|
</div>
|
<div v-if="selectedRouteId === null" style="margin-top: 10px; font-size: 12px; color: #909399;">
|
请先选择一条已维护好的工艺路线
|
</div>
|
</el-col>
|
</el-row>
|
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button @click="handleClose">取消</el-button>
|
<el-button type="primary" :loading="saving" @click="confirmSelect">
|
确定
|
</el-button>
|
</div>
|
</template>
|
</el-dialog>
|
</template>
|
|
<script setup>
|
import { computed, getCurrentInstance, ref, watch } from "vue";
|
import { salesProcessFlowConfigList, salesProcessFlowConfigItemList } from "@/api/salesManagement/salesProcessFlowConfig.js";
|
|
const emit = defineEmits(["update:visible", "confirm"]);
|
|
const props = defineProps({
|
visible: { type: Boolean, default: false },
|
// 打开弹窗时的回显:若业务已绑定工艺路线则传入该 routeId;否则默认展示列表第一条
|
defaultRouteId: { type: [Number, String, null], default: null },
|
// 页面提示:订单已绑定的工艺路线名称
|
boundRouteName: { type: String, default: "" },
|
});
|
|
const { proxy } = getCurrentInstance();
|
|
const visible = computed({
|
get() {
|
return props.visible;
|
},
|
set(v) {
|
emit("update:visible", v);
|
},
|
});
|
|
const routeList = ref([]);
|
const selectedRouteId = ref(null);
|
const steps = ref([]);
|
const saving = ref(false);
|
|
const normalizeStepsFromApi = (list) => {
|
if (!Array.isArray(list)) return [];
|
return list.map((s, idx) => ({
|
stepId: s.stepId ?? 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,
|
}));
|
};
|
|
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 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 salesProcessFlowConfigItemList(routeId);
|
const raw = res?.data ?? res ?? [];
|
steps.value = normalizeStepsFromApi(raw);
|
};
|
|
watch(
|
() => props.visible,
|
async (v) => {
|
if (v) {
|
try {
|
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?.("获取工艺路线配置失败");
|
}
|
}
|
}
|
);
|
|
const handleRouteChange = async () => {
|
await fetchRouteSteps(selectedRouteId.value);
|
};
|
|
const handleClose = () => {
|
emit("update:visible", false);
|
saving.value = false;
|
};
|
|
const confirmSelect = async () => {
|
if (saving.value) return;
|
if (selectedRouteId.value === null || selectedRouteId.value === undefined || selectedRouteId.value === "") {
|
proxy?.$modal?.msgWarning?.("请选择工艺路线");
|
return;
|
}
|
saving.value = true;
|
try {
|
emit("confirm", selectedRouteId.value);
|
} catch (e) {
|
proxy?.$modal?.msgError?.("确认失败,请稍后重试");
|
} finally {
|
saving.value = false;
|
}
|
};
|
</script>
|
|
<style scoped>
|
.process-diagram {
|
display: flex;
|
align-items: center;
|
gap: 0;
|
flex-wrap: nowrap;
|
overflow-x: auto;
|
padding: 10px 0;
|
}
|
|
.process-diagram-segment {
|
display: flex;
|
align-items: center;
|
}
|
|
.process-diagram-node {
|
width: 160px;
|
min-width: 160px;
|
height: 78px;
|
border: 1px solid #ebeef5;
|
border-radius: 10px;
|
background: #fff;
|
display: flex;
|
flex-direction: column;
|
justify-content: center;
|
padding: 10px 12px;
|
margin-right: 10px;
|
box-sizing: border-box;
|
}
|
|
.process-diagram-index {
|
font-size: 12px;
|
color: #909399;
|
margin-bottom: 4px;
|
}
|
|
.process-diagram-name {
|
font-size: 14px;
|
font-weight: 600;
|
color: #303133;
|
white-space: nowrap;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
}
|
|
.process-diagram-arrow {
|
font-size: 18px;
|
color: #909399;
|
margin-right: 14px;
|
margin-left: -6px;
|
}
|
|
.process-diagram-empty {
|
width: 100%;
|
text-align: center;
|
padding: 40px 0;
|
color: #909399;
|
border: 1px dashed #ebeef5;
|
border-radius: 8px;
|
}
|
|
.dialog-footer {
|
display: flex;
|
justify-content: flex-end;
|
gap: 10px;
|
}
|
</style>
|