|
|
|
|
@@ -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];
|
|
|
|
|
}
|
|
|
|
|
|