1、完成wbs数据进度数据对接页面交互
2、完成编码绑定、模型高亮、进度百分比高亮 3、后端接口
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user