模型上传管理和绑定构件,构建树形
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="progress-page">
|
||||
<div class="canvas">
|
||||
<header class="topbar"><div class="topbar-center"><div class="model-title">XXX特大桥主体模型.rvt</div></div></header>
|
||||
<header class="topbar"><div class="topbar-center"><div class="model-title">{{ modelCenterStore.activeModelName }}</div></div></header>
|
||||
|
||||
<section class="model-stage">
|
||||
<ModelPlaceholder ref="modelRef" @componentBindEncoding="onComponentBindEncoding" @codeBindComponent="codeBindComponent" />
|
||||
@@ -42,8 +42,9 @@
|
||||
</div>
|
||||
|
||||
<div v-if="showContextMenu" class="context-menu" :style="{ left: contextMenuX + 'px', top: contextMenuY + 'px' }" @click.stop>
|
||||
<div class="context-menu-item" @click="onAddComponent">绑定编码</div>
|
||||
<div class="context-menu-item" @click="onViewComponent">查看编辑</div>
|
||||
<div class="context-menu-summary">已绑定 {{ contextBoundCount }}</div>
|
||||
<button class="context-menu-action" type="button" @click="beginBindingTask('bind')">绑定构件</button>
|
||||
<button class="context-menu-action" type="button" @click="beginBindingTask('unbind')">解绑构件</button>
|
||||
</div>
|
||||
|
||||
<div v-if="showAddModal" class="add-modal" :style="modalPosition" @pointerdown.stop="onModalPointerDown">
|
||||
@@ -73,6 +74,35 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<section class="construct-tree-panel">
|
||||
<header class="construct-tree-head">
|
||||
<div>
|
||||
<div class="construct-tree-title">构件树</div>
|
||||
<div class="construct-tree-sub">从模型构件树中选择需要绑定的构件</div>
|
||||
</div>
|
||||
<button class="construct-tree-refresh" type="button" @click="refreshConstructTreeData">刷新</button>
|
||||
</header>
|
||||
<div class="construct-tree-search">
|
||||
<input v-model.trim="constructTreeQuery" type="search" placeholder="搜索构件" autocomplete="off" />
|
||||
<span>已选 {{ componentList.length }} 个</span>
|
||||
</div>
|
||||
<div class="construct-tree-list">
|
||||
<button
|
||||
v-for="row in visibleConstructTreeRows"
|
||||
:key="row.key"
|
||||
class="construct-tree-row"
|
||||
:class="{ 'is-group': !row.leaf, 'is-selected': row.selected }"
|
||||
type="button"
|
||||
:style="{ paddingLeft: `${12 + row.level * 14}px` }"
|
||||
@click="onConstructTreeRowClick(row)"
|
||||
>
|
||||
<span class="construct-tree-caret" @click.stop="toggleConstructTreeExpand(row)">{{ row.leaf ? (row.selected ? '✓' : '•') : row.open ? '▾' : '▸' }}</span>
|
||||
<span class="construct-tree-name">{{ row.name }}</span>
|
||||
<span v-if="row.count" class="construct-tree-count">{{ row.count }}</span>
|
||||
</button>
|
||||
<div v-if="!visibleConstructTreeRows.length" class="construct-tree-empty">暂无构件树数据,请等待模型加载完成后刷新</div>
|
||||
</div>
|
||||
</section>
|
||||
<div class="add-modal-footer">
|
||||
<button class="add-modal-btn add-modal-btn-secondary" type="button" @click="showAddModalHandle">取消</button>
|
||||
<button class="add-modal-btn add-modal-btn-primary" type="button" @click="onSaveComponent">保存</button>
|
||||
@@ -114,6 +144,41 @@
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<section v-if="bindingTask.active" class="binding-task-page">
|
||||
<aside class="binding-task-card">
|
||||
<div class="binding-task-label">{{ bindingTask.mode === 'unbind' ? '解绑构件' : '绑定构件' }}</div>
|
||||
<div class="binding-task-title">{{ getStructureName(bindingTask.part?.id) }}</div>
|
||||
<div class="binding-task-count">已选择 {{ componentList.length }} 个模型构件</div>
|
||||
<div class="binding-task-divider"></div>
|
||||
<button class="binding-task-tree-toggle" type="button">构件树 <span>▴</span></button>
|
||||
<div class="binding-task-quick-head">
|
||||
<span>快速选择模型构件</span>
|
||||
<span>已选 {{ componentList.length }} 组</span>
|
||||
</div>
|
||||
<input v-model.trim="constructTreeQuery" class="binding-task-search" type="search" placeholder="搜索构件" autocomplete="off" />
|
||||
<div class="binding-task-tree-list">
|
||||
<button
|
||||
v-for="row in visibleConstructTreeRows"
|
||||
:key="row.key"
|
||||
class="binding-task-tree-row"
|
||||
:class="{ 'is-group': !row.leaf, 'is-selected': row.selected }"
|
||||
type="button"
|
||||
:style="{ paddingLeft: `${12 + row.level * 14}px` }"
|
||||
@click="onConstructTreeRowClick(row)"
|
||||
>
|
||||
<span class="binding-task-check" @click.stop="toggleConstructTreeExpand(row)">{{ row.leaf ? (row.selected ? '✓' : '') : row.open ? '▾' : '▸' }}</span>
|
||||
<span>{{ row.name }}</span>
|
||||
</button>
|
||||
<div v-if="!visibleConstructTreeRows.length" class="binding-task-empty">暂无构件树数据,请等待模型加载完成后刷新</div>
|
||||
</div>
|
||||
</aside>
|
||||
<div v-if="bindingTask.mode === 'unbind' && boundComponentList.length" class="binding-task-tip">已高亮当前工程部位已绑定构件,可直接取消选择后确定解绑</div>
|
||||
<div class="binding-task-actions-float">
|
||||
<button class="binding-task-cancel" type="button" @click="cancelBindingTask">取消</button>
|
||||
<button class="binding-task-confirm" type="button" @click="confirmBindingTask">{{ bindingTask.mode === 'unbind' ? '确定解绑' : '确定绑定' }}</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="bottompanel" :class="{ 'is-collapsed': bottomCollapsed }">
|
||||
<header class="bottompanel-header"><div class="tabs"><button class="tab is-on" type="button">项目日进度明细</button></div></header>
|
||||
<button class="iconbtn bottompanel-toggle" type="button" @click="bottomCollapsed = !bottomCollapsed">{{ bottomCollapsed ? "▴" : "▾" }}</button>
|
||||
@@ -140,11 +205,15 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {computed, onMounted, reactive, ref} from "vue";
|
||||
import {computed, nextTick, onMounted, reactive, ref} from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import ModelPlaceholder from "../../components/model-placeholder/index.vue";
|
||||
import {progressApi} from "../../service/api/progress.js";
|
||||
import { useModelCenterStore } from "../../stores/modelCenter.js";
|
||||
|
||||
const structures = ref([]);
|
||||
const router = useRouter();
|
||||
const modelCenterStore = useModelCenterStore();
|
||||
const modelRef = ref(null);
|
||||
const elementTree = ref([]);
|
||||
const progressData = ref([]);
|
||||
@@ -158,9 +227,15 @@ const showContextMenu = ref(false);
|
||||
const contextMenuX = ref(0);
|
||||
const contextMenuY = ref(0);
|
||||
const contextMenuRow = ref(null);
|
||||
const contextBoundCount = ref(0);
|
||||
const showAddModal = ref(false);
|
||||
const componentList = ref([]);
|
||||
const partList = ref([]);
|
||||
const boundComponentList = ref([]);
|
||||
const constructTreeData = ref([]);
|
||||
const constructTreeQuery = ref("");
|
||||
const constructTreeExpanded = ref(new Set());
|
||||
const bindingTask = reactive({ active: false, mode: "bind", part: null });
|
||||
const showViewModal = ref(false);
|
||||
const viewPartList = ref([]);
|
||||
const viewComponentList = ref([]);
|
||||
@@ -174,8 +249,13 @@ let modalDragging = false;
|
||||
let modalStartX = 0, modalStartY = 0;
|
||||
let modalStartLeft = 0, modalStartTop = 0;
|
||||
|
||||
function getActiveBackendModelId() {
|
||||
const id = String(modelCenterStore.activeId || "").trim();
|
||||
return /^\d+$/.test(id) ? id : undefined;
|
||||
}
|
||||
|
||||
function onModalPointerDown(e) {
|
||||
if (e.target.closest(".add-modal-close") || e.button !== 0) return;
|
||||
if (e.target.closest(".add-modal-close, button, input, .construct-tree-panel, .add-modal-list") || e.button !== 0) return;
|
||||
modalDragging = true;
|
||||
modalStartX = e.clientX;
|
||||
modalStartY = e.clientY;
|
||||
@@ -255,9 +335,9 @@ const percentColorMap = {
|
||||
const visibleTreeRows = computed(() => {
|
||||
const rows = [];
|
||||
const walk = (node, level) => {
|
||||
const leaf = node.isLeaf === true;
|
||||
const leaf = normalizeIsLeaf(node);
|
||||
const open = expanded.value.has(node.id);
|
||||
rows.push({ id: node.id, name: node.name, level, leaf, open, createDate: selectedPeriod.value, projectId: node.projectId });
|
||||
rows.push({ id: node.id, name: node.name, level, leaf, isLeaf: leaf, is_leaf: leaf, open, createDate: selectedPeriod.value, projectId: node.projectId });
|
||||
if (!leaf && open && node.children) {
|
||||
node.children.forEach((c) => walk(c, level + 1));
|
||||
}
|
||||
@@ -307,6 +387,34 @@ const progressRows = computed(() => {
|
||||
}));
|
||||
});
|
||||
|
||||
const visibleConstructTreeRows = computed(() => {
|
||||
const rows = [];
|
||||
const query = constructTreeQuery.value.trim().toLowerCase();
|
||||
const selectedCodes = new Set(componentList.value.map((item) => String(item?.code || "")));
|
||||
|
||||
const matches = (node) => {
|
||||
if (!query) return true;
|
||||
if (String(node?.name || "").toLowerCase().includes(query)) return true;
|
||||
return getConstructChildren(node).some((child) => matches(child));
|
||||
};
|
||||
|
||||
const walk = (node, level, parentKey, inheritedUrl) => {
|
||||
if (!matches(node)) return;
|
||||
const key = `${parentKey}/${String(node?.name || "node")}-${String(node?.id || node?.url || rows.length)}`;
|
||||
const children = getConstructChildren(node);
|
||||
const ids = getConstructNodeIdsDeep(node);
|
||||
const leaf = !children.length;
|
||||
const url = String(node?.url || inheritedUrl || "").trim();
|
||||
const open = query ? true : constructTreeExpanded.value.has(key);
|
||||
const selected = ids.length > 0 && ids.every((id) => selectedCodes.has(String(id)));
|
||||
rows.push({ key, name: String(node?.name || node?.id || "未命名构件"), level, leaf, open, selected, ids, url, count: leaf ? ids.length : children.length });
|
||||
if (!leaf && open) children.forEach((child) => walk(child, level + 1, key, url));
|
||||
};
|
||||
|
||||
constructTreeData.value.forEach((node, index) => walk(node, 0, `root-${index}`, node?.url));
|
||||
return rows;
|
||||
});
|
||||
|
||||
async function loadWbsTree(parentId) {
|
||||
loading.value = true;
|
||||
try {
|
||||
@@ -314,8 +422,9 @@ async function loadWbsTree(parentId) {
|
||||
if (!parentId) {
|
||||
elementTree.value = data.map((item) => ({
|
||||
...item,
|
||||
children: item.isLeaf ? [] : [],
|
||||
isLeaf: !!item.isLeaf,
|
||||
children: normalizeIsLeaf(item) ? [] : [],
|
||||
isLeaf: normalizeIsLeaf(item),
|
||||
is_leaf: normalizeIsLeaf(item),
|
||||
}));
|
||||
if (data.length > 0) {
|
||||
const firstLeaf = findFirstLeaf(elementTree.value);
|
||||
@@ -346,8 +455,9 @@ function updateChildren(nodes, parentId, children) {
|
||||
if (n.id === parentId) {
|
||||
n.children = children.map((item) => ({
|
||||
...item,
|
||||
children: item.isLeaf ? [] : [],
|
||||
isLeaf: !!item.isLeaf,
|
||||
children: normalizeIsLeaf(item) ? [] : [],
|
||||
isLeaf: normalizeIsLeaf(item),
|
||||
is_leaf: normalizeIsLeaf(item),
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
@@ -367,6 +477,81 @@ function findNode(nodes, id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function getConstructChildren(node) {
|
||||
return Array.isArray(node?.children) ? node.children : [];
|
||||
}
|
||||
|
||||
function getConstructNodeIds(node) {
|
||||
const ids = [];
|
||||
if (Array.isArray(node?.ids)) ids.push(...node.ids);
|
||||
if (node?.id != null) ids.push(node.id);
|
||||
return Array.from(new Set(ids.map((id) => String(id)).filter(Boolean)));
|
||||
}
|
||||
|
||||
function getConstructNodeIdsDeep(node) {
|
||||
const ids = new Set(getConstructNodeIds(node));
|
||||
getConstructChildren(node).forEach((child) => getConstructNodeIdsDeep(child).forEach((id) => ids.add(id)));
|
||||
return Array.from(ids);
|
||||
}
|
||||
|
||||
function normalizeConstructTreeData(raw) {
|
||||
return Array.isArray(raw?.level) ? raw.level : [];
|
||||
}
|
||||
|
||||
function refreshConstructTreeData() {
|
||||
const raw = modelRef.value?.getConstructTreeData?.();
|
||||
constructTreeData.value = normalizeConstructTreeData(raw);
|
||||
if (constructTreeData.value.length > 0 && constructTreeExpanded.value.size === 0) {
|
||||
constructTreeData.value.forEach((node, index) => expandConstructTreeFirstLevel(node, `root-${index}`, index));
|
||||
}
|
||||
}
|
||||
|
||||
function expandConstructTreeFirstLevel(node, parentKey, index) {
|
||||
const key = `${parentKey}/${String(node?.name || "node")}-${String(node?.id || node?.url || index)}`;
|
||||
constructTreeExpanded.value.add(key);
|
||||
}
|
||||
|
||||
function removeComponentFromBindingList(code) {
|
||||
const target = String(code || "");
|
||||
componentList.value = componentList.value.filter((item) => String(item?.code || "") !== target);
|
||||
}
|
||||
|
||||
function onConstructTreeRowClick(row) {
|
||||
if (!row.ids.length) {
|
||||
toggleConstructTreeExpand(row);
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedCodes = new Set(componentList.value.map((item) => String(item?.code || "")));
|
||||
const allSelected = row.ids.length > 0 && row.ids.every((id) => selectedCodes.has(String(id)));
|
||||
if (allSelected) {
|
||||
row.ids.forEach((id) => removeComponentFromBindingList(id));
|
||||
} else {
|
||||
row.ids.forEach((id) => addComponentToBindingList({ id, url: row.url, name: row.name }));
|
||||
highlightConstructTreeRow(row);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleConstructTreeExpand(row) {
|
||||
if (!row || row.leaf) return;
|
||||
const next = new Set(constructTreeExpanded.value);
|
||||
if (next.has(row.key)) next.delete(row.key);
|
||||
else next.add(row.key);
|
||||
constructTreeExpanded.value = next;
|
||||
}
|
||||
|
||||
function highlightConstructTreeRow(row) {
|
||||
if (!row?.url || !row?.ids?.length) return;
|
||||
const ids = row.ids.map((id) => Number(id)).filter((id) => Number.isFinite(id));
|
||||
if (!ids.length) return;
|
||||
modelRef.value?.highlightModels?.([{ url: row.url, ids }]);
|
||||
}
|
||||
|
||||
function normalizeIsLeaf(node) {
|
||||
const isTruthyLeaf = (value) => value === true || value === 1 || value === "1" || value === "true";
|
||||
return isTruthyLeaf(node?.isLeaf) || isTruthyLeaf(node?.is_leaf);
|
||||
}
|
||||
|
||||
onMounted(async() => {
|
||||
await loadWbsTree();
|
||||
window.addEventListener("click", closeContextMenu);
|
||||
@@ -590,7 +775,7 @@ async function onTreeRowClick(row) {
|
||||
}
|
||||
modelRef.value?.cancelLightModels();
|
||||
normalLight.value = [];
|
||||
const data = await progressApi.getByPartId(row.id);
|
||||
const data = await progressApi.getByPartId(row.id, getActiveBackendModelId());
|
||||
if (showViewModal.value) {
|
||||
applyViewModalData(row, data);
|
||||
}
|
||||
@@ -632,28 +817,135 @@ function getParams(str) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
function onTreeRightClick(e, row) {
|
||||
async function onTreeRightClick(e, row) {
|
||||
// if (!row.leaf) return;
|
||||
contextMenuX.value = e.clientX;
|
||||
contextMenuY.value = e.clientY;
|
||||
contextMenuRow.value = row;
|
||||
contextBoundCount.value = 0;
|
||||
showContextMenu.value = true;
|
||||
try {
|
||||
const data = await progressApi.getByPartId(row.id, getActiveBackendModelId());
|
||||
contextBoundCount.value = Array.isArray(data) ? data.length : 0;
|
||||
} catch (error) {
|
||||
console.error("获取绑定数量失败:", error);
|
||||
}
|
||||
}
|
||||
|
||||
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: selectedPeriod.value });
|
||||
const isLeaf = normalizeIsLeaf(row) || row.leaf === true;
|
||||
partList.value.push({ id: row.id, createDate: selectedPeriod.value, is_leaf: isLeaf, isLeaf });
|
||||
}
|
||||
|
||||
function onAddComponent() {
|
||||
async function onAddComponent() {
|
||||
// Start a new binding session from current right-click row.
|
||||
partList.value = [];
|
||||
addPartToBindingList(contextMenuRow.value);
|
||||
componentList.value = [];
|
||||
constructTreeQuery.value = "";
|
||||
showAddModal.value = true;
|
||||
showContextMenu.value = false;
|
||||
await nextTick();
|
||||
refreshConstructTreeData();
|
||||
}
|
||||
|
||||
async function beginBindingTask(mode) {
|
||||
const row = contextMenuRow.value;
|
||||
if (!row?.id) return;
|
||||
showContextMenu.value = false;
|
||||
router.push({
|
||||
path: "/progress/binding",
|
||||
query: {
|
||||
mode: mode === "unbind" ? "unbind" : "bind",
|
||||
partId: row.id,
|
||||
partName: row.name,
|
||||
isLeaf: String(row.leaf === true || normalizeIsLeaf(row)),
|
||||
createDate: selectedPeriod.value,
|
||||
modelId: getActiveBackendModelId(),
|
||||
},
|
||||
});
|
||||
return;
|
||||
bindingTask.active = true;
|
||||
bindingTask.mode = mode === "unbind" ? "unbind" : "bind";
|
||||
bindingTask.part = row;
|
||||
showContextMenu.value = false;
|
||||
showAddModal.value = false;
|
||||
showViewModal.value = false;
|
||||
partList.value = [];
|
||||
componentList.value = [];
|
||||
boundComponentList.value = [];
|
||||
constructTreeQuery.value = "";
|
||||
addPartToBindingList(row);
|
||||
await nextTick();
|
||||
refreshConstructTreeData();
|
||||
try {
|
||||
const data = await progressApi.getByPartId(row.id, getActiveBackendModelId());
|
||||
boundComponentList.value = (data || []).map((item) => toBindingCodeItem(item)).filter(Boolean);
|
||||
componentList.value = boundComponentList.value.map((item) => ({ ...item }));
|
||||
if (boundComponentList.value.length) {
|
||||
highlightBoundComponents();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取已绑定构件失败:", error);
|
||||
showToast("获取已绑定构件失败", "error");
|
||||
}
|
||||
}
|
||||
|
||||
function cancelBindingTask() {
|
||||
bindingTask.active = false;
|
||||
bindingTask.part = null;
|
||||
componentList.value = [];
|
||||
boundComponentList.value = [];
|
||||
partList.value = [];
|
||||
constructTreeQuery.value = "";
|
||||
modelRef.value?.cancelLightModels?.();
|
||||
}
|
||||
|
||||
async function confirmBindingTask() {
|
||||
if (!bindingTask.active || !bindingTask.part?.id) return;
|
||||
if (!componentList.value.length && bindingTask.mode === "bind") {
|
||||
showToast("请先选择需要绑定的模型构件", "error");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const selectedCodes = new Set(componentList.value.map((item) => String(item?.code || "")));
|
||||
const codeIds = bindingTask.mode === "unbind"
|
||||
? boundComponentList.value.filter((item) => !selectedCodes.has(String(item?.code || "")))
|
||||
: componentList.value;
|
||||
if (bindingTask.mode === "unbind" && !codeIds.length) {
|
||||
showToast("请取消选择需要解绑的构件", "error");
|
||||
return;
|
||||
}
|
||||
const payload = { partIds: partList.value, codeIds, modelId: getActiveBackendModelId() };
|
||||
const res = bindingTask.mode === "unbind"
|
||||
? await progressApi.deleteBatch(payload)
|
||||
: await progressApi.saveBatch(payload);
|
||||
if (res?.code === 200) {
|
||||
showToast(bindingTask.mode === "unbind" ? "解绑成功!" : "绑定成功!", "success");
|
||||
cancelBindingTask();
|
||||
} else {
|
||||
showToast(res?.message || "操作失败!", "error");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("绑定任务保存失败:", error);
|
||||
showToast("操作失败!", "error");
|
||||
}
|
||||
}
|
||||
|
||||
function highlightBoundComponents() {
|
||||
const grouped = new Map();
|
||||
boundComponentList.value.forEach((item) => {
|
||||
const url = String(item?.data?.url || "").trim();
|
||||
const id = Number(item?.code);
|
||||
if (!url || !Number.isFinite(id)) return;
|
||||
if (!grouped.has(url)) grouped.set(url, []);
|
||||
grouped.get(url).push(id);
|
||||
});
|
||||
const refs = Array.from(grouped.entries()).map(([url, ids]) => ({ url, ids }));
|
||||
if (refs.length) modelRef.value?.highlightModels?.(refs);
|
||||
}
|
||||
|
||||
async function onViewComponent() {
|
||||
@@ -661,7 +953,7 @@ async function onViewComponent() {
|
||||
if (!partId) return;
|
||||
showContextMenu.value = false;
|
||||
try {
|
||||
const data = await progressApi.getByPartId(partId);
|
||||
const data = await progressApi.getByPartId(partId, getActiveBackendModelId());
|
||||
applyViewModalData(contextMenuRow.value, data);
|
||||
} catch (e) {
|
||||
console.error("获取构件列表失败:", e);
|
||||
@@ -759,7 +1051,7 @@ async function onDeleteComponent() {
|
||||
const codeIds = viewComponentList.value;
|
||||
if (!partIds.length || !codeIds.length) return;
|
||||
try {
|
||||
const res = await progressApi.deleteBatch({ partIds, codeIds });
|
||||
const res = await progressApi.deleteBatch({ partIds, codeIds, modelId: getActiveBackendModelId() });
|
||||
if (res?.code === 200) {
|
||||
showToast("保存成功!", "success");
|
||||
showViewModal.value = false;
|
||||
@@ -799,10 +1091,12 @@ function onComponentBindEncoding(data) {
|
||||
}
|
||||
componentList.value = [];
|
||||
partList.value = [];
|
||||
constructTreeQuery.value = "";
|
||||
showAddModal.value = true;
|
||||
for (let component of data.components) {
|
||||
addComponentToBindingList(component);
|
||||
}
|
||||
nextTick().then(() => refreshConstructTreeData());
|
||||
}
|
||||
async function codeBindComponent(data) {
|
||||
const inBindMode = showAddModal.value;
|
||||
@@ -838,7 +1132,7 @@ async function onSaveComponent() {
|
||||
// const codeIds = componentList.value.filter((c) => c.code).map((c) => c.code);
|
||||
if (!partIds.length || !codeIds.length) return;
|
||||
try {
|
||||
const res = await progressApi.saveBatch({ partIds, codeIds });
|
||||
const res = await progressApi.saveBatch({ partIds, codeIds, modelId: getActiveBackendModelId() });
|
||||
if (res.code === 200) {
|
||||
showToast("保存成功!", "success");
|
||||
showAddModal.value = false;
|
||||
@@ -859,11 +1153,13 @@ function showToast(text, type = "success") {
|
||||
async function showAddModalHandle() {
|
||||
partList.value = [];
|
||||
componentList.value = [];
|
||||
constructTreeQuery.value = "";
|
||||
showAddModal.value = false;
|
||||
}
|
||||
async function showAddModalClose() {
|
||||
partList.value = [];
|
||||
componentList.value = [];
|
||||
constructTreeQuery.value = "";
|
||||
showAddModal.value = false;
|
||||
}
|
||||
|
||||
@@ -893,7 +1189,7 @@ async function loadProgressData(positionId,time) {
|
||||
.model-title { font-size: 20px; letter-spacing: 1px; font-weight: 700; color: rgba(255,128,52,.95); text-shadow: 0 2px 10px rgba(0,0,0,.25); }
|
||||
.model-stage { position: absolute; inset: 0; }
|
||||
|
||||
.module-topbar { position: absolute; left: 50%; top: 20px; transform: translateX(-50%); width: min(800px, calc(100% - 180px)); z-index: 99; }
|
||||
.module-topbar { position: absolute; left: 514px; top: 36px; width: min(800px, calc(100% - 554px)); z-index: 99; }
|
||||
.module-topbar-panel { border-radius: 18px; border: 1px solid rgba(83,214,206,.26); background: radial-gradient(420px 120px at 18% 0%, rgba(83,214,206,.22), transparent 70%), linear-gradient(180deg, rgba(18,32,40,.94), rgba(15,25,32,.88)); box-shadow: 0 20px 56px rgba(0,0,0,.28), 0 0 0 1px rgba(83,214,206,.12) inset; padding: 10px 14px; display: grid; gap: 10px; }
|
||||
.field { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
||||
.field-time { flex-wrap: nowrap; }
|
||||
@@ -957,7 +1253,7 @@ async function loadProgressData(positionId,time) {
|
||||
.table tbody tr:hover td { background: linear-gradient(90deg, rgba(69,102,130,.36), rgba(56,86,113,.32)); }
|
||||
|
||||
@media (max-width: 1300px) {
|
||||
.module-topbar { width: calc(100% - 32px); }
|
||||
.module-topbar { left: 24px; right: 24px; top: 108px; width: auto; }
|
||||
.field-time, .field-percent { flex-wrap: wrap; }
|
||||
.kpi { margin-left: 0; }
|
||||
.sidepanel { width: 280px; }
|
||||
@@ -967,28 +1263,177 @@ async function loadProgressData(positionId,time) {
|
||||
.context-menu {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(83, 214, 206, 0.25);
|
||||
background: linear-gradient(180deg, rgba(20, 31, 37, 0.95), rgba(15, 23, 29, 0.92));
|
||||
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.4);
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(190, 215, 255, 0.76);
|
||||
background: rgba(244, 249, 255, 0.96);
|
||||
box-shadow: 0 18px 42px rgba(17, 34, 64, 0.22);
|
||||
}
|
||||
|
||||
.context-menu-item {
|
||||
padding: 10px 16px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: rgba(207, 247, 242, 0.9);
|
||||
.context-menu-summary {
|
||||
padding: 7px 8px;
|
||||
border-right: 1px solid rgba(60, 92, 140, 0.16);
|
||||
color: rgba(42, 64, 98, 0.82);
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.context-menu-action {
|
||||
height: 30px;
|
||||
border-radius: 7px;
|
||||
border: 1px solid rgba(65, 116, 220, 0.28);
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
color: rgba(28, 55, 112, 0.92);
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.context-menu-item:hover {
|
||||
background: rgba(83, 214, 206, 0.18);
|
||||
.context-menu-action:hover {
|
||||
background: rgba(226, 238, 255, 0.96);
|
||||
}
|
||||
|
||||
.binding-task-page {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 120;
|
||||
background: rgba(132, 143, 154, 0.88);
|
||||
}
|
||||
|
||||
.binding-task-card {
|
||||
position: absolute;
|
||||
left: 22px;
|
||||
top: 22px;
|
||||
width: 300px;
|
||||
max-height: calc(100% - 120px);
|
||||
overflow: hidden;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(25, 37, 45, 0.96);
|
||||
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.28);
|
||||
padding: 14px;
|
||||
color: rgba(240, 247, 250, 0.96);
|
||||
}
|
||||
|
||||
.binding-task-label { font-size: 13px; font-weight: 900; }
|
||||
.binding-task-title { margin-top: 8px; font-size: 17px; font-weight: 900; }
|
||||
.binding-task-count { margin-top: 8px; font-size: 12px; font-weight: 800; color: rgba(218, 231, 237, 0.76); }
|
||||
.binding-task-divider { height: 1px; margin: 14px 0 10px; background: rgba(255,255,255,.12); }
|
||||
|
||||
.binding-task-tree-toggle {
|
||||
width: 100%;
|
||||
height: 38px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(255,255,255,.12);
|
||||
background: rgba(255,255,255,.10);
|
||||
color: rgba(240,247,250,.95);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 12px;
|
||||
font-size: 13px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.binding-task-quick-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 12px;
|
||||
color: rgba(234, 244, 248, 0.86);
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.binding-task-search {
|
||||
width: 100%;
|
||||
height: 34px;
|
||||
margin-top: 12px;
|
||||
border-radius: 7px;
|
||||
border: 1px solid rgba(255,255,255,.14);
|
||||
background: rgba(255,255,255,.08);
|
||||
color: rgba(245,250,252,.94);
|
||||
padding: 0 10px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.binding-task-tree-list {
|
||||
margin-top: 14px;
|
||||
max-height: min(430px, calc(100vh - 360px));
|
||||
overflow: auto;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(255,255,255,.08);
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.binding-task-tree-row {
|
||||
width: 100%;
|
||||
min-height: 34px;
|
||||
border: 0;
|
||||
border-radius: 7px;
|
||||
background: transparent;
|
||||
color: rgba(231, 241, 246, 0.86);
|
||||
display: grid;
|
||||
grid-template-columns: 20px minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.binding-task-tree-row:hover { background: rgba(255,255,255,.07); }
|
||||
.binding-task-tree-row.is-group { color: rgba(210, 226, 233, 0.78); font-weight: 900; }
|
||||
.binding-task-tree-row.is-selected { color: rgba(245, 255, 253, 0.98); }
|
||||
.binding-task-check { width: 16px; height: 16px; border-radius: 4px; border: 1px solid rgba(203, 226, 236, 0.36); display: grid; place-items: center; color: rgba(83,214,206,.95); font-weight: 900; }
|
||||
.binding-task-tree-row.is-group .binding-task-check { border: 0; }
|
||||
.binding-task-empty { padding: 22px 8px; text-align: center; color: rgba(221,235,241,.58); font-size: 12px; }
|
||||
|
||||
.binding-task-tip {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 120px;
|
||||
transform: translateX(-50%);
|
||||
border-radius: 10px;
|
||||
background: rgba(245, 250, 255, 0.96);
|
||||
color: rgba(48, 65, 96, 0.9);
|
||||
padding: 10px 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
box-shadow: 0 18px 44px rgba(30, 42, 60, 0.18);
|
||||
}
|
||||
|
||||
.binding-task-actions-float {
|
||||
position: absolute;
|
||||
right: 18px;
|
||||
bottom: 18px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
background: rgba(18, 31, 39, 0.95);
|
||||
box-shadow: 0 16px 40px rgba(0,0,0,.28);
|
||||
}
|
||||
|
||||
.binding-task-cancel,
|
||||
.binding-task-confirm {
|
||||
min-width: 128px;
|
||||
height: 46px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 900;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.binding-task-cancel { border: 0; background: rgba(255,255,255,.94); color: rgba(46,58,74,.82); }
|
||||
.binding-task-confirm { border: 1px solid rgba(83,214,206,.34); background: linear-gradient(180deg, rgba(38,190,178,.95), rgba(21,149,142,.95)); color: white; }
|
||||
|
||||
.add-modal {
|
||||
position: fixed;
|
||||
width: min(520px, 86vw);
|
||||
width: min(780px, 90vw);
|
||||
border-radius: 16px;
|
||||
border: 1px solid rgba(83, 214, 206, 0.25);
|
||||
background: linear-gradient(180deg, rgba(20, 31, 37, 0.95), rgba(15, 23, 29, 0.92));
|
||||
@@ -1032,8 +1477,8 @@ async function loadProgressData(positionId,time) {
|
||||
|
||||
.add-modal-body {
|
||||
padding: 18px;
|
||||
min-height: 300px;
|
||||
max-height: 400px;
|
||||
min-height: 520px;
|
||||
max-height: min(720px, 82vh);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@@ -1065,6 +1510,145 @@ async function loadProgressData(positionId,time) {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.construct-tree-panel {
|
||||
margin-top: 16px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid rgba(83, 214, 206, 0.18);
|
||||
background: rgba(0, 0, 0, 0.16);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.construct-tree-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 12px 14px;
|
||||
border-bottom: 1px solid rgba(83, 214, 206, 0.14);
|
||||
background: rgba(83, 214, 206, 0.08);
|
||||
}
|
||||
|
||||
.construct-tree-title {
|
||||
font-size: 13px;
|
||||
font-weight: 900;
|
||||
color: rgba(207, 247, 242, 0.95);
|
||||
}
|
||||
|
||||
.construct-tree-sub {
|
||||
margin-top: 3px;
|
||||
font-size: 11px;
|
||||
color: rgba(203, 230, 236, 0.58);
|
||||
}
|
||||
|
||||
.construct-tree-refresh {
|
||||
flex: none;
|
||||
height: 30px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(83, 214, 206, 0.28);
|
||||
background: rgba(83, 214, 206, 0.12);
|
||||
color: rgba(207, 247, 242, 0.9);
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.construct-tree-search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid rgba(83, 214, 206, 0.12);
|
||||
}
|
||||
|
||||
.construct-tree-search input {
|
||||
flex: 1;
|
||||
height: 32px;
|
||||
min-width: 0;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(83, 214, 206, 0.22);
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
color: rgba(236, 246, 250, 0.92);
|
||||
padding: 0 10px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.construct-tree-search span {
|
||||
flex: none;
|
||||
color: rgba(203, 230, 236, 0.62);
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.construct-tree-list {
|
||||
display: grid;
|
||||
align-content: start;
|
||||
gap: 4px;
|
||||
max-height: 250px;
|
||||
overflow: auto;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.construct-tree-row {
|
||||
width: 100%;
|
||||
min-height: 32px;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
color: rgba(222, 238, 244, 0.86);
|
||||
display: grid;
|
||||
grid-template-columns: 22px minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.construct-tree-row:hover {
|
||||
background: rgba(83, 214, 206, 0.10);
|
||||
}
|
||||
|
||||
.construct-tree-row.is-group {
|
||||
color: rgba(255, 220, 164, 0.9);
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.construct-tree-row.is-selected {
|
||||
background: rgba(83, 214, 206, 0.18);
|
||||
color: rgba(230, 255, 252, 0.98);
|
||||
}
|
||||
|
||||
.construct-tree-caret {
|
||||
color: rgba(83, 214, 206, 0.92);
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.construct-tree-name {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.construct-tree-count {
|
||||
min-width: 22px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
color: rgba(203, 230, 236, 0.68);
|
||||
font-size: 11px;
|
||||
font-weight: 900;
|
||||
text-align: center;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
|
||||
.construct-tree-empty {
|
||||
padding: 22px 12px;
|
||||
color: rgba(203, 230, 236, 0.58);
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.add-modal-tag {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
Reference in New Issue
Block a user