三方接口对接

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

1
.env.development Normal file
View File

@@ -0,0 +1 @@
VITE_API_BASE=http://localhost:8099

1
.env.production Normal file
View File

@@ -0,0 +1 @@
VITE_API_BASE=http://localhost:8099

1
.env.test Normal file
View File

@@ -0,0 +1 @@
VITE_API_BASE=http://localhost:8099

View File

@@ -50,12 +50,13 @@
<table class="table"> <table class="table">
<thead> <thead>
<tr> <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> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="r in progressRows" :key="r.no"> <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> </tr>
</tbody> </tbody>
</table> </table>
@@ -66,31 +67,22 @@
</template> </template>
<script setup> <script setup>
import { computed, reactive, ref } from "vue"; import { computed, onMounted, reactive, ref } from "vue";
import ModelPlaceholder from "../../components/model-placeholder/index.vue"; import ModelPlaceholder from "../../components/model-placeholder/index.vue";
import { progressApi } from "../../service/api/progress.js";
const structures = [ const structures = ref([]);
{ id: "S-001", name: "主桥-0#墩" }, { id: "S-002", name: "主桥-1#墩" }, { id: "S-003", name: "主桥-2#墩" }, const elementTree = ref([]);
{ id: "S-004", name: "引桥-桩基" }, { id: "S-005", name: "引桥-承台" }, { id: "S-006", name: "引桥-盖梁" }, const progressData = ref([]);
{ 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 sideCollapsed = ref(false); const sideCollapsed = ref(false);
const bottomCollapsed = ref(false); const bottomCollapsed = ref(false);
const selectedStructureId = ref("S-001"); const selectedStructureId = ref("");
const expanded = ref(new Set(["E-G-bridge", "E-G-main-bridge", "E-G-approach", "E-G-road"])); const expanded = ref(new Set());
const loading = ref(false);
const timeStatMode = ref("cutoff"); const timeStatMode = ref("cutoff");
const baselineDate = ref(todayISODate()); const baselineDate = ref(todayISODate());
const baselineOverallPercent = ref(61.54); const baselineOverallPercent = ref(0);
const cutoffDate = ref(todayISODate()); const cutoffDate = ref(todayISODate());
const cutoffDateDraft = ref(cutoffDate.value); 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 visibleTreeRows = computed(() => {
const rows = []; const rows = [];
const walk = (node, level) => { const walk = (node, level) => {
const leaf = !node.children || node.children.length === 0; const leaf = node.isLeaf === true;
const open = expanded.value.has(node.id); const open = expanded.value.has(node.id);
rows.push({ id: node.id, name: node.name, level, leaf, open }); rows.push({ id: node.id, name: node.name, level, leaf, open, createDate: node.createDate });
if (!leaf && open) node.children.forEach((c) => walk(c, level + 1)); 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; 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 overallPercent = computed(() => {
const deltaDays = daysBetweenISO(baselineDate.value, cutoffDate.value); if (!selectedStructureNode.value) return 0;
return clamp(baselineOverallPercent.value + deltaDays * 0.06, 0, 100); return 0;
}); });
const progressRows = computed(() => { const progressRows = computed(() => {
const reportDate = cutoffDate.value; if (!selectedStructureId.value) return [];
return Array.from({ length: 16 }, (_, i) => { return progressData.value.map((item, index) => ({
const contractQty = 30 + i * 2.2; no: index + 1,
const unitPrice = 860 + i * 30; fullName: item.fullName || "",
const contractAmt = contractQty * unitPrice; workCode: item.workCode || "",
const doneQty = contractQty * (0.03 + (i % 6) * 0.06); name: item.name || "",
const doneAmt = doneQty * unitPrice; workUnit: item.workUnit || "-",
const cumDoneQty = contractQty * (0.18 + (i % 5) * 0.14); meteringAmt: item.meteringAmt || 0,
return { meteringNotaxAmt: item.meteringNotaxAmt || 0,
no: i + 1, meteringNum: item.meteringNum || 0,
fullName: selectedStructureName.value || structures[i % structures.length].name, doneQty: item.doneQty || 0,
code: `BOQ-${String(5001 + i)}`, doneAmt: item.doneAmt || 0,
name: ["混凝土浇筑", "钢筋制安", "模板工程", "土方开挖", "路基填筑", "防水层"][i % 6], cumDoneQty: item.cumDoneQty || 0,
unit: ["m³", "t", "㎡", "m³", "m³", "㎡"][i % 6], }));
contractQty,
contractAmt,
reportDate,
doneQty,
doneAmt,
cumDoneQty,
};
}); });
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() { function todayISODate() {
@@ -193,8 +259,46 @@ function applyAllExclusive(key, checked) {
function togglePercentFilter(key, checked) { 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 toggleTreeExpand(row) {
function onTreeRowClick(row) { if (row.leaf) selectedStructureId.value = row.id; else 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> </script>
<style scoped> <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 || [];
});
},
};