1、完成wbs数据进度数据对接页面交互

2、完成编码绑定、模型高亮、进度百分比高亮
3、后端接口
This commit is contained in:
2026-05-06 10:44:25 +08:00
parent 53abc3a165
commit 4afa6a92ef
6 changed files with 301 additions and 43 deletions

View File

@@ -1 +1 @@
VITE_API_BASE=http://localhost:8099
VITE_API_BASE=http://syliang.nat100.top

View File

@@ -1 +1 @@
VITE_API_BASE=http://localhost:8099
VITE_API_BASE=http://syliang.nat100.top

View File

@@ -1 +1 @@
VITE_API_BASE=http://localhost:8099
VITE_API_BASE=http://syliang.nat100.top

View File

@@ -37,6 +37,8 @@
<script setup>
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from "vue";
import { BimEngine } from "iflow-engin-xg";
import {progressApi} from "../../service/api/progress.js";
const props = defineProps({
modelUrl: {
@@ -50,17 +52,35 @@ const emit = defineEmits(["componentBindEncoding","codeBindComponent"]);
defineExpose({
highlightModels,
cancelLightModels,
otherLightModels,
getData,
});
let positionIdsCache = [];
function highlightModels(ids) {
engine.engine?.highlightModel(ids);
// engine.engine?.setModelColor(
// ids,
// '#ff4d4f',
// 0.6
// );
engine.engine?.highlightModel(ids);
positionIdsCache = ids;
}
function cancelLightModels() {
engine.engine?.unhighlightAllModels()
console.log(88888)
engine.engine?.restoreAllModelsColor()
engine.engine?.unhighlightAllModels();
}
function otherLightModels(data) {
cancelLightModels();
data.codeData.forEach( i => {
engine.engine?.setModelColor(
i.codeData,
i.color,
0.6
);
})
}
function getData() {
@@ -113,13 +133,12 @@ function bindEncodingEvents() {
engine.on("encoding:error", () => {
encoding.value = false;
}),
engine.on("engine:model-loading-completed", () => {
engine.on("engine:model-loading-completed", async () => {
const componentWbsCodes = await progressApi.getCodeWbsMappings()
if (disposed) return;
handleModelLoadingCompleted();
engine.engine?.setComponentWbsCodes([
{ id: '350518', wbsCode: 'WBS-01-02-001' },
{ id: '350520', wbsCode: 'WBS-01-02-002' }
]);
console.log("加载模型2")
engine.engine?.setComponentWbsCodes(componentWbsCodes);
}),
engine.on("component:clicked", (data) => {
emit("codeBindComponent", data);

View File

@@ -91,7 +91,7 @@
<div class="add-modal-section-title">部位列表</div>
<div class="add-modal-list">
<div class="add-modal-tag" v-for="(id, index) in viewPartList" :key="index">
<span>{{ getStructureName(id) }}</span>
<span>{{ getStructureName(id?.id || id) }}</span>
</div>
</div>
</div>
@@ -99,7 +99,7 @@
<div class="add-modal-section-title">构件列表</div>
<div class="add-modal-list">
<div class="add-modal-tag" v-for="(item, index) in viewComponentList" :key="index">
<span>{{ item.codeId }}</span>
<span>{{ getViewComponentCode(item) }}</span>
<button class="add-modal-tag-delete" type="button" @click="viewComponentList.splice(index, 1)"></button>
</div>
</div>
@@ -167,6 +167,7 @@ const viewComponentList = ref([]);
const toastText = ref("");
const toastType = ref("success");
const normalLight = ref([]);
const taskId = ref("");
const modalPosition = ref({ left: "50%", top: "50%", transform: "translate(-50%, -50%)" });
let modalDragging = false;
@@ -240,7 +241,15 @@ const baselineOverallPercent = ref(0);
const cutoffDate = ref(todayISODate());
const cutoffDateDraft = ref(cutoffDate.value);
const percentFilters = reactive({ all: true, p0: false, p0_50: false, p50_100: false, p100: false });
const percentFilters = reactive({ all: false, p0: false, p0_50: false, p50_100: false, p100: false });
const filteredProgressCodeData = ref([]);
const filteredProgressColorParams = ref([]);
const percentColorMap = {
p0: "#6E7D96",
p0_50: "#D9363E",
p50_100: "#156CFF",
p100: "#2F8F2F",
};
const visibleTreeRows = computed(() => {
const rows = [];
@@ -356,10 +365,11 @@ function findNode(nodes, id) {
return null;
}
onMounted(() => {
loadWbsTree();
onMounted(async() => {
await loadWbsTree();
window.addEventListener("click", closeContextMenu);
console.log(6666,progressApi.calculateProgress());
const result = await progressApi.calculateProgress();
taskId.value = result.taskId
});
function todayISODate() {
@@ -406,6 +416,100 @@ function formatNumber(value, digits = 0) {
return Number(value).toLocaleString("zh-CN", { minimumFractionDigits: digits, maximumFractionDigits: digits });
}
function extractProgressItems(response) {
if (Array.isArray(response)) return response;
if (Array.isArray(response?.current?.data)) return response.current.data;
if (Array.isArray(response?.data?.data)) return response.data.data;
if (Array.isArray(response?.data)) return response.data;
return [];
}
function normalizeProgressRatio(value) {
const n = Number(value);
if (!Number.isFinite(n)) return 0;
if (n > 1) return clamp(n / 100, 0, 1);
return clamp(n, 0, 1);
}
function isProgressMatched(ratio, key) {
const EPS = 1e-8;
if (key === "p0") return Math.abs(ratio) <= EPS;
if (key === "p0_50") return ratio > EPS && ratio <= 0.5 + EPS;
if (key === "p50_100") return ratio > 0.5 + EPS && ratio < 1 - EPS;
if (key === "p100") return Math.abs(ratio - 1) <= EPS;
return false;
}
function buildModelCodeData(list) {
const groupedByUrl = new Map();
for (const item of list) {
const rawCodeData = String(item?.codeData || "").trim();
if (!rawCodeData) continue;
const params = getParams(rawCodeData);
const url = String(params.url || "").trim();
const rawId = String(params.id || "").trim();
if (!url || !rawId) continue;
const id = Number(rawId);
const idValue = Number.isFinite(id) ? id : rawId;
if (!groupedByUrl.has(url)) groupedByUrl.set(url, new Set());
groupedByUrl.get(url).add(idValue);
}
return Array.from(groupedByUrl.entries()).map(([url, idSet]) => ({
url,
ids: Array.from(idSet),
}));
}
async function refreshFilteredProgressCodeData() {
if (!taskId.value) {
filteredProgressCodeData.value = [];
filteredProgressColorParams.value = [];
modelRef.value?.cancelLightModels();
return;
}
try {
const response = await progressApi.getProgress(taskId.value);
const items = extractProgressItems(response);
const enabledKeys = ["p0", "p0_50", "p50_100", "p100"].filter((k) => percentFilters[k]);
const activeKeys = percentFilters.all ? ["p0", "p0_50", "p50_100", "p100"] : enabledKeys;
if (activeKeys.length === 0) {
filteredProgressCodeData.value = [];
filteredProgressColorParams.value = [];
modelRef.value?.cancelLightModels();
return;
}
const groupedParams = [];
const allMatchedCodeData = [];
activeKeys.forEach((key) => {
const matched = items.filter((item) => isProgressMatched(normalizeProgressRatio(item.progressData), key));
if (!matched.length) return;
allMatchedCodeData.push(...matched.map((item) => item.codeData).filter(Boolean));
const modelCodeData = buildModelCodeData(matched);
if (!modelCodeData.length) return;
groupedParams.push({
range: key,
codeData: modelCodeData,
color: percentColorMap[key],
});
});
filteredProgressCodeData.value = Array.from(new Set(allMatchedCodeData));
filteredProgressColorParams.value = groupedParams;
if (groupedParams.length > 0) {
modelRef.value?.otherLightModels({ codeData: groupedParams, color: "" });
} else {
modelRef.value?.cancelLightModels();
}
} catch (e) {
console.error("按百分比筛选构件失败:", e);
filteredProgressCodeData.value = [];
filteredProgressColorParams.value = [];
modelRef.value?.cancelLightModels();
}
}
function applyCutoffDate() {
cutoffDate.value = cutoffDateDraft.value || cutoffDate.value || todayISODate();
}
@@ -423,7 +527,8 @@ function proportion(count, allCount) {
return `${count} / ${allCount} (${percent}%)`
}
function applyAllExclusive(key, checked) {
async function applyAllExclusive(key, checked) {
const isUncheckAction = checked === false;
if (key === "all") {
percentFilters.all = checked;
if (checked) {
@@ -437,14 +542,26 @@ function applyAllExclusive(key, checked) {
percentFilters.p50_100 = false;
percentFilters.p100 = false;
}
if (isUncheckAction) {
modelRef.value?.cancelLightModels();
}
await refreshFilteredProgressCodeData();
return;
}
percentFilters[key] = checked;
if (checked) percentFilters.all = false;
if (checked) {
percentFilters.all = false;
} else {
// 取消任一区间时,"全部"必须退出选中态
percentFilters.all = false;
modelRef.value?.cancelLightModels();
}
if (!percentFilters.p0 && !percentFilters.p0_50 && !percentFilters.p50_100 && !percentFilters.p100) percentFilters.all = false;
await refreshFilteredProgressCodeData();
}
function togglePercentFilter(key, checked) { applyAllExclusive(key, checked); }
function togglePercentFilter(key, checked) { applyAllExclusive(key, checked).catch((e) => console.error("切换百分比筛选失败:", e)); }
function toggleTreeExpand(row) {
if (row.leaf) return;
@@ -460,9 +577,15 @@ function toggleTreeExpand(row) {
}
async function onTreeRowClick(row) {
if (showAddModal.value) {
addPartToBindingList(row);
}
modelRef.value?.cancelLightModels();
normalLight.value = [];
const data = await progressApi.getByPartId(row.id);
if (showViewModal.value) {
applyViewModalData(row, data);
}
if (data.length > 0) {
for (const item of data) {
const codeData = getParams(item.codeData);
@@ -509,9 +632,18 @@ function onTreeRightClick(e, row) {
showContextMenu.value = true;
}
function addPartToBindingList(row) {
if (!row?.id) return;
const exists = partList.value.some((item) => item.id === row.id);
if (exists) return;
partList.value.push({ id: row.id, createDate: row.createDate });
}
function onAddComponent() {
partList.value.push({id:contextMenuRow.value.id,createDate:contextMenuRow.value.createDate});
componentList.value = []
// Start a new binding session from current right-click row.
partList.value = [];
addPartToBindingList(contextMenuRow.value);
componentList.value = [];
showAddModal.value = true;
showContextMenu.value = false;
}
@@ -522,35 +654,113 @@ async function onViewComponent() {
showContextMenu.value = false;
try {
const data = await progressApi.getByPartId(partId);
// const grouped = processCodeData(data);
// console.log("处理后数据:", grouped);
viewPartList.value = [partId];
viewComponentList.value = data;
showViewModal.value = true;
applyViewModalData(contextMenuRow.value, data);
} catch (e) {
console.error("获取构件列表失败:", e);
showToast("获取失败", "error");
}
}
function applyViewModalData(row, data) {
const partId = row?.id;
if (!partId) return;
viewPartList.value = [{ id: partId, createDate: row?.createDate }];
const mapped = (data || [])
.map((item) => toBindingCodeItem(item))
.filter(Boolean);
const seen = new Set();
viewComponentList.value = mapped.filter((item) => {
if (seen.has(item.code)) return false;
seen.add(item.code);
return true;
});
showViewModal.value = true;
}
function resolveComponentCode(item) {
const directId = item?.codeId ?? item?.code ?? item?.id;
if (directId != null && String(directId).trim()) return String(directId).trim();
const rawCodeData = String(item?.codeData || "").trim();
if (rawCodeData) {
try {
const parsed = getParams(rawCodeData);
const parsedId = String(parsed?.id || "").trim();
if (parsedId) return parsedId;
} catch (e) {
// ignore malformed codeData records
}
}
return "";
}
function resolveComponentCodeDataPayload(item, code) {
const rawCodeData = String(item?.codeData || "").trim();
if (rawCodeData) {
try {
return getParams(rawCodeData);
} catch (e) {
// fallback below
}
}
const rawData = item?.data;
if (typeof rawData === "string" && rawData.trim()) {
try {
return getParams(rawData.trim());
} catch (e) {
return { value: rawData.trim(), id: code };
}
}
if (rawData && typeof rawData === "object") {
const nestedCodeData = String(rawData?.codeData || "").trim();
if (nestedCodeData) {
try {
return getParams(nestedCodeData);
} catch (e) {
// fallback below
}
}
const nestedUrl = String(rawData?.url || "").trim();
const nestedId = String(rawData?.id || code || "").trim();
if (nestedUrl && nestedId) return { url: nestedUrl, id: nestedId };
}
const url = String(item?.url || "").trim();
if (url && code) return { url, id: code };
return rawData && typeof rawData === "object" ? rawData : { id: code };
}
function toBindingCodeItem(item) {
const code = resolveComponentCode(item);
if (!code) return null;
const data = resolveComponentCodeDataPayload(item, code);
if (data && typeof data === "object" && !Array.isArray(data) && !data.id) {
data.id = code;
}
return { code, data };
}
function getViewComponentCode(item) {
const code = resolveComponentCode(item);
if (code) return code;
return "--";
}
async function onDeleteComponent() {
const partIds = viewPartList.value;
const allIds = [];
for (const item of viewComponentList.value) {
if (item.ids) allIds.push(...item.ids);
}
if (!partIds.length || !allIds.length) return;
const codeIds = viewComponentList.value;
if (!partIds.length || !codeIds.length) return;
try {
const res = await progressApi.deleteBatch({ partIds, codeIds: allIds });
const res = await progressApi.deleteBatch({ partIds, codeIds });
if (res?.code === 200) {
showToast("删除成功!", "success");
showToast("保存成功!", "success");
showViewModal.value = false;
} else {
showToast(res?.message || "删除失败!", "error");
showToast(res?.message || "保存失败!", "error");
}
} catch (e) {
console.error("删除失败:", e);
showToast("删除失败!", "error");
console.error("保存失败:", e);
showToast("保存失败!", "error");
}
}
@@ -558,6 +768,22 @@ function closeContextMenu() {
showContextMenu.value = false;
}
function addComponentToBindingList(component) {
const code = String(component?.id || "").trim();
if (!code) return;
const exists = componentList.value.some((item) => String(item?.code || "") === code);
if (exists) return;
componentList.value.push({ code, data: component });
}
function addComponentToViewList(component) {
const mapped = toBindingCodeItem(component);
if (!mapped) return;
const exists = viewComponentList.value.some((item) => String(item?.code || "") === mapped.code);
if (exists) return;
viewComponentList.value.push(mapped);
}
function onComponentBindEncoding(data) {
if (showAddModal.value) {
showAddModal.value = false;
@@ -567,14 +793,14 @@ function onComponentBindEncoding(data) {
partList.value = [];
showAddModal.value = true;
for (let component of data.components) {
componentList.value.push({code:component.id,data:component})
addComponentToBindingList(component);
}
}
async function codeBindComponent(data) {
// if (!showAddModal.value) {
// return
// }
const inBindMode = showAddModal.value;
const inViewMode = showViewModal.value;
if (!data?.components?.length) return;
progressData.value = []
const newData = [];
for (const comp of data.components) {
@@ -588,7 +814,12 @@ async function codeBindComponent(data) {
}
}
}
componentList.value.push({ code: comp.id || "", data: comp });
if (inBindMode) {
addComponentToBindingList(comp);
}
if (inViewMode) {
addComponentToViewList(comp);
}
}
progressData.value = [...progressData.value, ...newData];
}

View File

@@ -57,10 +57,18 @@ export const progressApi = {
},
calculateProgress() {
return post("/api/partCode/calculateProgress", {}).then((res) => res);
return get("/api/bim/progressData", {}).then((res) => res);
},
getProgress(taskId) {
return get(`/api/partCode/progress/${taskId}`).then((res) => res);
return get(`/api/bim/progressData/${taskId}`).then((res) => res);
},
getCodeWbsMappings() {
return get("/api/partCode/codeWbsMappings").then((res) => {
if (res?.code === 200) return res?.data || [];
throw new Error(res?.message || "Failed to fetch codeWbsMappings");
});
},
};