三方接口对接

This commit is contained in:
2026-04-24 17:04:50 +08:00
parent 7954b98fc2
commit 27586047dc
6 changed files with 241 additions and 51 deletions

View File

@@ -50,12 +50,13 @@
<table class="table">
<thead>
<tr>
<th>序号</th><th>全名称</th><th>清单编号</th><th>清单名称</th><th>单位</th><th>合同数量</th><th>合同金额</th><th>填报日期</th><th>完成数量</th><th>完成金额</th><th>累计完成数量</th>
<!-- <th>序号</th><th>全名称</th><th>清单编号</th><th>清单名称</th><th>单位</th><th>合同数量</th><th>合同金额</th><th>填报日期</th><th>完成数量</th><th>完成金额</th><th>累计完成数量</th>-->
<th>序号</th><th>全名称</th><th>清单编号</th><th>清单名称</th><th>单位</th><th>合同数量</th><th>合同金额</th><th>合同金额不含税</th>
</tr>
</thead>
<tbody>
<tr v-for="r in progressRows" :key="r.no">
<td>{{ r.no }}</td><td>{{ r.fullName }}</td><td>{{ r.code }}</td><td>{{ r.name }}</td><td>{{ r.unit }}</td><td>{{ formatNumber(r.contractQty,2) }}</td><td>{{ formatMoney(r.contractAmt) }}</td><td>{{ r.reportDate }}</td><td>{{ formatNumber(r.doneQty,2) }}</td><td>{{ formatMoney(r.doneAmt) }}</td><td>{{ formatNumber(r.cumDoneQty,2) }}</td>
<td>{{ r.no }}</td><td>{{ r.fullName }}</td><td>{{ r.workCode }}</td><td>{{ r.name }}</td><td>{{ r.workUnit }}</td><td>{{ formatNumber(r.meteringNum,2) }}</td><td>{{ formatMoney(r.meteringAmt) }}</td><td>{{ formatMoney(r.meteringNotaxAmt) }}</td>
</tr>
</tbody>
</table>
@@ -66,31 +67,22 @@
</template>
<script setup>
import { computed, reactive, ref } from "vue";
import { computed, onMounted, reactive, ref } from "vue";
import ModelPlaceholder from "../../components/model-placeholder/index.vue";
import { progressApi } from "../../service/api/progress.js";
const structures = [
{ id: "S-001", name: "主桥-0#墩" }, { id: "S-002", name: "主桥-1#墩" }, { id: "S-003", name: "主桥-2#墩" },
{ id: "S-004", name: "引桥-桩基" }, { id: "S-005", name: "引桥-承台" }, { id: "S-006", name: "引桥-盖梁" },
{ id: "S-007", name: "路基-填筑" }, { id: "S-008", name: "路面-基层" }, { id: "S-009", name: "路面-面层" },
];
const elementTree = [
{ id: "E-G-bridge", name: "桥梁工程", children: [
{ id: "E-G-main-bridge", name: "主桥", children: ["S-001", "S-002", "S-003"].map((id) => ({ id, name: structures.find((s) => s.id === id)?.name || id })) },
{ id: "E-G-approach", name: "引桥", children: ["S-004", "S-005", "S-006"].map((id) => ({ id, name: structures.find((s) => s.id === id)?.name || id })) },
] },
{ id: "E-G-road", name: "道路工程", children: ["S-007", "S-008", "S-009"].map((id) => ({ id, name: structures.find((s) => s.id === id)?.name || id })) },
];
const structures = ref([]);
const elementTree = ref([]);
const progressData = ref([]);
const sideCollapsed = ref(false);
const bottomCollapsed = ref(false);
const selectedStructureId = ref("S-001");
const expanded = ref(new Set(["E-G-bridge", "E-G-main-bridge", "E-G-approach", "E-G-road"]));
const selectedStructureId = ref("");
const expanded = ref(new Set());
const loading = ref(false);
const timeStatMode = ref("cutoff");
const baselineDate = ref(todayISODate());
const baselineOverallPercent = ref(61.54);
const baselineOverallPercent = ref(0);
const cutoffDate = ref(todayISODate());
const cutoffDateDraft = ref(cutoffDate.value);
@@ -99,45 +91,119 @@ const percentFilters = reactive({ all: true, p0: false, p0_50: false, p50_100: f
const visibleTreeRows = computed(() => {
const rows = [];
const walk = (node, level) => {
const leaf = !node.children || node.children.length === 0;
const leaf = node.isLeaf === true;
const open = expanded.value.has(node.id);
rows.push({ id: node.id, name: node.name, level, leaf, open });
if (!leaf && open) node.children.forEach((c) => walk(c, level + 1));
rows.push({ id: node.id, name: node.name, level, leaf, open, createDate: node.createDate });
if (!leaf && open && node.children) {
node.children.forEach((c) => walk(c, level + 1));
}
};
elementTree.forEach((n) => walk(n, 0));
elementTree.value.forEach((n) => walk(n, 0));
return rows;
});
const selectedStructureName = computed(() => structures.find((s) => s.id === selectedStructureId.value)?.name || "");
const selectedStructureName = computed(() => {
const findById = (nodes) => {
for (const n of nodes) {
if (n.id === selectedStructureId.value) return n.name;
if (n.children) {
const found = findById(n.children);
if (found) return found;
}
}
};
return findById(elementTree.value) || "";
});
const selectedStructureNode = computed(() => {
return findNode(elementTree.value, selectedStructureId.value);
});
const overallPercent = computed(() => {
const deltaDays = daysBetweenISO(baselineDate.value, cutoffDate.value);
return clamp(baselineOverallPercent.value + deltaDays * 0.06, 0, 100);
if (!selectedStructureNode.value) return 0;
return 0;
});
const progressRows = computed(() => {
const reportDate = cutoffDate.value;
return Array.from({ length: 16 }, (_, i) => {
const contractQty = 30 + i * 2.2;
const unitPrice = 860 + i * 30;
const contractAmt = contractQty * unitPrice;
const doneQty = contractQty * (0.03 + (i % 6) * 0.06);
const doneAmt = doneQty * unitPrice;
const cumDoneQty = contractQty * (0.18 + (i % 5) * 0.14);
return {
no: i + 1,
fullName: selectedStructureName.value || structures[i % structures.length].name,
code: `BOQ-${String(5001 + i)}`,
name: ["混凝土浇筑", "钢筋制安", "模板工程", "土方开挖", "路基填筑", "防水层"][i % 6],
unit: ["m³", "t", "㎡", "m³", "m³", "㎡"][i % 6],
contractQty,
contractAmt,
reportDate,
doneQty,
doneAmt,
cumDoneQty,
};
});
if (!selectedStructureId.value) return [];
return progressData.value.map((item, index) => ({
no: index + 1,
fullName: item.fullName || "",
workCode: item.workCode || "",
name: item.name || "",
workUnit: item.workUnit || "-",
meteringAmt: item.meteringAmt || 0,
meteringNotaxAmt: item.meteringNotaxAmt || 0,
meteringNum: item.meteringNum || 0,
doneQty: item.doneQty || 0,
doneAmt: item.doneAmt || 0,
cumDoneQty: item.cumDoneQty || 0,
}));
});
async function loadWbsTree(parentId) {
loading.value = true;
try {
const data = await progressApi.getTree(parentId);
console.log(7777,data)
if (!parentId) {
elementTree.value = data.map((item) => ({
...item,
children: item.isLeaf ? [] : [],
isLeaf: !!item.isLeaf,
}));
if (data.length > 0) {
const firstLeaf = findFirstLeaf(elementTree.value);
if (firstLeaf) selectedStructureId.value = firstLeaf.id;
}
} else {
updateChildren(elementTree.value, parentId, data);
}
} catch (e) {
console.error("加载WBS树失败:", e);
} finally {
loading.value = false;
}
}
function findFirstLeaf(nodes) {
for (const n of nodes) {
if (n.isLeaf) return n;
if (n.children && n.children.length > 0) {
const found = findFirstLeaf(n.children);
if (found) return found;
}
}
}
function updateChildren(nodes, parentId, children) {
for (const n of nodes) {
if (n.id === parentId) {
n.children = children.map((item) => ({
...item,
children: item.isLeaf ? [] : [],
isLeaf: !!item.isLeaf,
}));
return true;
}
if (n.children && updateChildren(n.children, parentId, children)) return true;
}
return false;
}
function findNode(nodes, id) {
for (const n of nodes) {
if (n.id === id) return n;
if (n.children) {
const found = findNode(n.children, id);
if (found) return found;
}
}
return null;
}
onMounted(() => {
loadWbsTree();
});
function todayISODate() {
@@ -193,8 +259,46 @@ function applyAllExclusive(key, checked) {
function togglePercentFilter(key, checked) { applyAllExclusive(key, checked); }
function toggleTreeExpand(row) { if (!row.leaf) (expanded.value.has(row.id) ? expanded.value.delete(row.id) : expanded.value.add(row.id)); }
function onTreeRowClick(row) { if (row.leaf) selectedStructureId.value = row.id; else toggleTreeExpand(row); }
function toggleTreeExpand(row) {
if (row.leaf) return;
if (expanded.value.has(row.id)) {
expanded.value.delete(row.id);
} else {
expanded.value.add(row.id);
const node = findNode(elementTree.value, row.id);
if (node && (!node.children || node.children.length === 0)) {
loadWbsTree(row.id);
}
}
}
function onTreeRowClick(row) {
console.log(8888,row)
if (row.leaf) {
selectedStructureId.value = row.id;
loadProgressData(row.id,row.createDate);
} else {
toggleTreeExpand(row);
}
}
async function loadProgressData(positionId,time) {
loading.value = true;
try {
const data = await progressApi.getPjProgress({
projectId: "12e3c0eb186243869d94e214363ba083",
positionIds: positionId,
period: time,
});
progressData.value = data;
console.log("进度数据:", data);
} catch (e) {
console.error("加载进度明细失败:", e);
progressData.value = [];
} finally {
loading.value = false;
}
}
</script>
<style scoped>

58
src/service/api/api.js Normal file
View File

@@ -0,0 +1,58 @@
const BASE_URL = import.meta.env.VITE_API_BASE;
const MOCK_DELAY = 300;
const mock = (data, delay = MOCK_DELAY) =>
new Promise((resolve) => setTimeout(() => resolve(data), delay));
export const fetchApi = async (url, options = {}) => {
const fullUrl = url.startsWith("http") ? url : BASE_URL + url;
const token = localStorage.getItem("token");
const config = {
headers: {
"Content-Type": "application/json",
...(token ? { Authorization: `Bearer ${token}` } : {}),
...options.headers,
},
...options,
};
try {
const res = await fetch(fullUrl, config);
const data = await res.json();
if (!res.ok) {
throw new Error(data.message || `请求失败: ${res.status}`);
}
return data;
} catch (error) {
console.error(`[API] ${options.method || "GET"} ${fullUrl}`, error);
throw error;
}
};
export const get = (url, params, options = {}) => {
const query = params
? "?" + new URLSearchParams(params).toString()
: "";
return fetchApi(url + query, { ...options, method: "GET" });
};
export const post = (url, data, options = {}) =>
fetchApi(url, {
...options,
method: "POST",
body: JSON.stringify(data),
});
export const put = (url, data, options = {}) =>
fetchApi(url, {
...options,
method: "PUT",
body: JSON.stringify(data),
});
export const del = (url, options = {}) =>
fetchApi(url, { ...options, method: "DELETE" });
export { mock };

View File

@@ -0,0 +1,25 @@
import { get, post } from "./api.js";
export const FILTER_NAMES = ["隧道工程", "桥梁工程", "道路工程"];
export const DEFAULT_PROJECT_ID = "12e3c0eb186243869d94e214363ba083";
export const progressApi = {
getTree(parentId) {
const params = { projectId: DEFAULT_PROJECT_ID };
if (parentId) params.parentId = parentId;
return get("/api/bim/wbs", params).then((res) => {
const raw = res?.data?.data || [];
if (!parentId) {
return raw.filter((item) => FILTER_NAMES.includes(item.name));
}
return raw;
});
},
getPjProgress({ projectId, positionIds, period }) {
return post("/api/bim/getPjDayListByPositionId", { projectId, positionIds, period }).then((res) => {
return res?.data?.data || [];
});
},
};