diff --git a/components/modal/ViewBomInfoModal.tsx b/components/modal/ViewBomInfoModal.tsx index 54ad7344..39b5138c 100644 --- a/components/modal/ViewBomInfoModal.tsx +++ b/components/modal/ViewBomInfoModal.tsx @@ -4,6 +4,7 @@ import { FC } from "react"; import { PairInfo } from "./EditorProductSystem"; import { shortStr } from "@lib/utils"; import { handleContentRender } from "utils"; +import { isTagType } from "@lib/tagtools"; interface ViewBomInfoModalProps { onClose: () => void; @@ -14,10 +15,10 @@ const ViewBomInfoModal: FC = ({ onClose, ...props }) => { const { modelBomInfo = "" } = props; const value = modelBomInfo && JSON.parse(modelBomInfo); - const customSort = (a: { tagType: string }, b: { tagType: string }) => { - if (a.tagType === "REFERENCE" && b.tagType !== "REFERENCE") { + const customSort = (a: { tagType: string | string[] }, b: { tagType: string | string[] }) => { + if (isTagType(a.tagType, "REFERENCE") && !isTagType(b.tagType, "REFERENCE")) { return -1; // 'REFERENCE' 排在前面 - } else if (a.tagType !== "REFERENCE" && b.tagType === "REFERENCE") { + } else if (!isTagType(a.tagType, "REFERENCE") && isTagType(b.tagType, "REFERENCE")) { return 1; // 'REFERENCE' 排在后面 } else { return 0; // 保持原始顺序 diff --git a/components/routes/blockchain.tsx b/components/routes/blockchain.tsx index 0e97da93..c2f462a8 100644 --- a/components/routes/blockchain.tsx +++ b/components/routes/blockchain.tsx @@ -196,7 +196,7 @@ export function Blockchain() { ); return (
-
+
{t("Trust Label NFT Viewer")} diff --git a/components/routes/car.tsx b/components/routes/car.tsx index ffbac1b1..ec65297d 100644 --- a/components/routes/car.tsx +++ b/components/routes/car.tsx @@ -388,7 +388,7 @@ export function Car() { {isMobile ? ( <>{loading ? : <>{data ? : vin ? : null}} ) : ( - + {loading ? ( ) : ( diff --git a/components/routes/tools/inventoryResult.tsx b/components/routes/tools/inventoryResult.tsx index a7e26d74..0e69bc9b 100644 --- a/components/routes/tools/inventoryResult.tsx +++ b/components/routes/tools/inventoryResult.tsx @@ -2,71 +2,45 @@ import Chart from "@components/common/Chart"; import { Button } from "@components/common/button"; import { Loading } from "@components/common/loading"; import { ToolsLayout } from "@components/common/toolsLayout"; -import { Wrapmermaid } from "@components/common/wrapmermaid"; import { exportLcaResultExcel, getLcaProductDetailList, getLcaResultDetail } from "@lib/http"; +import { BomNode, deepSetBomChild, isTagType } from "@lib/tagtools"; import { tryParse } from "@lib/utils"; +import CarbonFooter from "@public/carbon_footer.svg"; import classNames from "classnames"; -import { EChartsOption, PieSeriesOption } from "echarts"; +import { EChartsOption, SankeySeriesOption } from "echarts"; import _ from "lodash"; import { useRouter } from "next/router"; -import { useEffect, useMemo, useState } from "react"; -import { BsCaretUpFill } from "react-icons/bs"; -import { useToggle } from "react-use"; +import { ReactNode, useEffect, useMemo, useState } from "react"; -function Expand(p: { text: string; onChange: Function }) { - const [open, setOpen] = useState(true); - const { text, onChange } = p; - useMemo(() => { - onChange && onChange(open); - }, [open]); +function MCard(p: { children?: ReactNode; tit: string; className?: string }) { + const { children, tit, className } = p; return ( -
setOpen(!open)}> -
- {text} - +
+
+ {tit}
+ {children}
); } -function GeneralInfo(p: { data: any }) { - const [open, setOpen] = useState(true); - const { data } = p; - const list = [ - { label: "碳足迹批次", text: data.loadName }, - { label: "产品系统", text: data.productSystemName }, - { label: "目标产品", text: data.targetName }, - { label: "目标产品数量", text: data.targetAmount }, - { label: "Impact Method(环境影响评价方法)", text: "IPCC 2021" }, - { label: "Allocation Method(分摊方法)", text: "Process Defaults" }, - { label: "Cutoff", text: "None" }, - { label: "计算结果生成时间", text: data.calculateSuccessTime }, - ]; +function InfoItem(p: { tit: string; value: string }) { + const { tit, value } = p; return ( -
- setOpen(v)} /> - {open && ( -
- {list.map((v: any, i: number) => { - return ( -
- - {v.text} -
- ); - })} -
- )} -
- ); -} -function Result(p: { data: any }) { - const [open, setOpen] = useState(true); - const { data } = p; - return ( -
- setOpen(v)} /> - {open &&
{data}
} +
+
+
+ {tit} +
+
{value}
); } @@ -92,141 +66,132 @@ export function InventoryResult() { getList(); }, [query.id]); - const { generalInfo, carbonResult, graphData, pieData } = useMemo(() => { + const { generalInfo, carbonResult, chartData } = useMemo(() => { let generalInfo: any = []; let referenceUnit = ""; - let carbonResult: any = ""; - let graphData = ""; - let pieData: EChartsOption = {}; + let carbonResult: [string, string] = ["", ""]; + + let chartData: EChartsOption = {}; if (value) { // general infos const { lca, bominfo } = value; const val = tryParse(lca.lcaResult); // console.info("val::", val); if (val) { + const splited = (val.extra?.targetAmount || "").split("Item(s)"); generalInfo = { productSystemName: val.extra?.productSystemName, methodName: val.extra?.methodName, - targetAmount: val.extra?.targetAmount, + targetName: splited[1] || val.extra?.productSystemName, + targetAmount: `${Math.round(splited[0] || 1)} (件)`, calculateSuccessTime: lca.calculateSuccessTime, loadNumber: lca.loadNumber, loadName: lca.loadName, }; referenceUnit = (val.totalImpacts && val.totalImpacts[0]?.impact.referenceUnit) || ""; const total = _.round(val.totalResult || val.treeNode?.result || 0, 2); - carbonResult = `${total || 0} ${referenceUnit}`; + carbonResult = [`${total || 0}`, referenceUnit]; } - type BomNode = { - flowId: string; - tagType: string; - flowName: string; - processId: string; - partNumbers: string[]; - childFlowIds: string[]; - }; // mermaid data const boms = tryParse(bominfo); const tagResult = tryParse<{ result: number; processId: string; flowId: string }[]>(lca.lcaTagResult); // console.info("booms:", boms); // console.info("tagRes:", tagResult); if (boms && tagResult) { - graphData = "classDiagram"; const mapTagResult = _.groupBy(tagResult, "flowId"); const mapBoms = _.groupBy(boms, "flowId"); - const indexMap: { - bomIndex: number; - stageIndex: number; - [k: string]: number; - } = { - bomIndex: 1, - stageIndex: 1, + deepSetBomChild(boms); + const sortBoms = boms; + chartData = { + padding: 20, + tooltip: { + trigger: "item", + formatter: (item: any) => { + const content = `${item.name} ${item.value}(${referenceUnit})`; + if (item.marker) return `${item.marker} ${content}`; + return content; + }, + }, + series: { + type: "sankey", + data: [], + links: [], + }, }; - const getIndex = (item: BomNode) => { - if (!indexMap[item.flowId]) { - indexMap[item.flowId] = item.tagType === "STAGE" ? indexMap.stageIndex++ : indexMap.bomIndex++; - } - return indexMap[item.flowId]; + const getOtherName = (item: BomNode) => { + if (isTagType(item.tagType, "STAGE01")) return "使用环节"; + if (isTagType(item.tagType, "STAGE02")) return "运输销售环节"; + if (isTagType(item.tagType, "STAGE03")) return "生产制造环节"; + return ""; }; - const itemTit = (item: BomNode) => { - return item.tagType === "REFERENCE" ? "目标产品" : item.tagType + getIndex(item); + + const getOtherNameForDeep = (item: BomNode) => { + if (isTagType(item.tagType, "REFERENCE")) return "使用环节"; + if (isTagType(item.tagType, "STAGE")) { + if (item._depth === 1) return "生产制造环节"; + if (item._depth === 2) return "运输销售环节"; + } + return ""; }; - const sortBoms = _.sortBy(boms, (item) => (item.tagType === "BOM" ? 2 : item.tagType === "STAGE" ? 1 : 0)); - // links + const NameFlag: { [k: string]: boolean } = {}; + + // data and links sortBoms.forEach((item) => { + if (isTagType(item.tagType, "REFERENCE")) generalInfo.targetName = item.flowName; + const links = (chartData.series as SankeySeriesOption).links; + const data = (chartData.series as SankeySeriesOption).data; + const value = _.first(mapTagResult[item.flowId])?.result || 0; + + // add data + if (!NameFlag[item.flowName]) { + NameFlag[item.flowName] = true; + data?.push({ name: item.flowName, value: _.round(value, 2), depth: item._depth }); + } + + // add links if (item.childFlowIds && item.childFlowIds.length) { item.childFlowIds.forEach((flowId) => { const flowRes = _.first(mapTagResult[flowId]); const flowBom = _.first(mapBoms[flowId]); if (flowBom && flowRes) { - const p = itemTit(item); - const c = itemTit(flowBom); - graphData += `\n ${p} <|-- ${c}`; + const bomV = _.first(mapTagResult[flowBom.flowId]); + const ftmValue = _.round(bomV?.result || 0, 2); + links?.push({ target: item.flowName, source: flowBom.flowName, value: ftmValue }); } }); } - }); - pieData = { - padding: 20, - legend: { - top: 30, - padding: 20, - type: "scroll", - orient: "horizontal", - align: "auto", - }, - tooltip: { - trigger: "item", - formatter: (item: any) => { - return `
- ${(item as any).marker} - ${(item as any).name} (${item.percent}%) -
${(item as any).value} ${referenceUnit}
-
`; - }, - }, - series: { - type: "pie", - radius: "60%", - data: [], - center: ["50%", "56%"], - label: { - formatter: (item) => { - return `${item.percent}%`; - }, - }, - }, - }; - // contents - sortBoms.forEach((item) => { - const p = itemTit(item); - const v = _.first(mapTagResult[item.flowId]); - // const value = - // item.tagType === "REFERENCE" - // ? v?.result || 0 - // : (v?.result || 0) - _.sumBy(item.childFlowIds, (flowId) => _.first(mapTagResult[flowId])?.result || 0); - const value = - (v?.result || 0) - _.sumBy(item.childFlowIds, (flowId) => _.first(mapTagResult[flowId])?.result || 0); - const ftmValue = _.round(value, 2); - const content = `${p} : ${item.flowName.replaceAll("(", "(").replaceAll(")", ")")} - ${p} : +PCF(${ftmValue} ${referenceUnit})`; - if (item.tagType !== "REFERENCE" || item.childFlowIds.length > 0) graphData += `\n${content}`; - if (item.tagType === "REFERENCE") { - pieData.title = { text: item.flowName, left: "center", top: 10 }; - generalInfo.targetName = item.flowName; - graphData += `\n${p} : Total(${_.round(v?.result || 0, 2)} ${referenceUnit})`; + // add other + const other = _.round( + value - _.sumBy(item.childFlowIds || [], (flowId) => mapTagResult[flowId][0]?.result), + 2, + ); + + const otherName = getOtherName(item); + if (otherName && item._depth > 0) { + if (!NameFlag[otherName]) { + NameFlag[otherName] = true; + data?.push({ name: otherName, depth: item._depth - 1 }); + } + links?.push({ target: item.flowName, source: otherName, value: other }); + } else if (other > 0 && item._depth > 0) { + const otherNameDeep = getOtherNameForDeep(item); + if (otherNameDeep) { + if (!NameFlag[otherNameDeep]) { + NameFlag[otherNameDeep] = true; + data?.push({ name: otherNameDeep, depth: item._depth - 1 }); + } + links?.push({ target: item.flowName, source: otherNameDeep, value: other }); + } } - if (ftmValue > 0) (pieData.series as PieSeriesOption).data?.push({ name: item.flowName, value: ftmValue }); }); } } - - // console.info("gD:", graphData); + console.info("cd:", chartData); return { generalInfo, carbonResult, - graphData, - pieData, + chartData, }; }, [value]); @@ -253,7 +218,6 @@ export function InventoryResult() { } } }; - const [showBoms, toggleBom] = useToggle(true); return ( @@ -262,22 +226,29 @@ export function InventoryResult() {
) : ( -
-

碳足迹结果

-
- - - {!_.isEmpty(graphData) && !_.isEmpty(pieData) && ( -
- toggleBom(v)} /> - {showBoms && ( - <> - - - - )} +
+
+ +
+ +
+ {carbonResult[0]} + {carbonResult[1]} +
+
+
+ +
+ + + + +
- )} +
+ + {chartData && } +
+ ); diff --git a/lib/tagtools.ts b/lib/tagtools.ts new file mode 100644 index 00000000..75a0ce83 --- /dev/null +++ b/lib/tagtools.ts @@ -0,0 +1,38 @@ +import _ from "lodash"; + +export type BomNode = { + flowId: string; + tagType: string | string[]; + flowName: string; + processId: string; + partNumbers: string[]; + childFlowIds: string[]; + _child?: BomNode[]; + _depth: number; +}; + +export function deepSetBomChild(boms: BomNode[]) { + const mapBoms = _.groupBy(boms, "flowId"); + const deepSet = (items: BomNode[]) => { + items.forEach((item) => { + if (!item._child) { + item._child = (item.childFlowIds || []).map((flowId) => mapBoms[flowId][0]); + deepSet(item._child); + } + if (item._child.length === 0) { + item._depth = 0; + } else { + item._depth = (_.maxBy(item._child, "_depth")?._depth || 0) + 1; + } + }); + }; + deepSet(boms) +} + +export const isTagType = ( + tagType: string | string[], + type: `STAGE${"" | "01" | "02" | "03"}` | "BOM" | "REFERENCE", +) => { + const types = typeof tagType === "string" ? [tagType] : tagType; + return !!_.find(types, (t) => t.startsWith(type)); +}; diff --git a/public/carbon_footer.svg b/public/carbon_footer.svg new file mode 100644 index 00000000..5d21ac27 --- /dev/null +++ b/public/carbon_footer.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/utils/index.ts b/utils/index.ts index 2adf4acb..9af65521 100644 --- a/utils/index.ts +++ b/utils/index.ts @@ -11,7 +11,7 @@ export const scrollToTop = () => { document.documentElement.scrollTop = 0; }; -export const handleContentRender = (text: string, width: number) => { +export const handleContentRender = (text: any, width: number) => { if (text.length > width) { return text; } else {