diff --git a/src/components/DataPreview/task/RefreshStatusModal.tsx b/src/components/DataPreview/task/RefreshStatusModal.tsx index 3a5bebfe1c87c643fba9c084116d0fb558cdc4a1..6a797b27a6f409627f6d4a3298adafcfcfdfb66c 100644 --- a/src/components/DataPreview/task/RefreshStatusModal.tsx +++ b/src/components/DataPreview/task/RefreshStatusModal.tsx @@ -1,8 +1,14 @@ import { nextTick } from '@/ts/base/common/timer'; -import { getAllNodes } from '@/ts/base/common/tree'; +import { buildTree, getAllNodes } from '@/ts/base/common/tree'; import { ReportTaskTreeNodeView } from '@/ts/base/model'; +import { XReception } from '@/ts/base/schema'; import { IReportDistribution } from '@/ts/core/work/assign/distribution/report'; -import { getStatus } from '@/ts/core/work/assign/reception/status'; +import { + ReceptionStatus, + getEmptySummary, + getStatus, + statusMap, +} from '@/ts/core/work/assign/reception/status'; import { Button, Modal, Progress, message } from 'antd'; import _ from 'lodash'; import React, { useState } from 'react'; @@ -14,6 +20,22 @@ interface Props { onClose: () => void; } +function deepSummary(tree: ReportTaskTreeNodeView[]) { + for (const node of tree) { + node.summary = getEmptySummary(); + node.summary.total = 1; + node.summary[node.taskStatus!] = 1; + if (!node.isLeaf) { + deepSummary(node.children); + + node.summary.total += _.sumBy(node.children, (n) => n.summary!.total); + for (const status of Object.keys(statusMap) as ReceptionStatus[]) { + node.summary[status] += _.sumBy(node.children, (n) => n.summary![status]); + } + } + } +} + export function RefreshStatusModal(props: Props) { const [progress, setProgress] = useState(0); const [isStart, setIsStart] = useState(false); @@ -22,30 +44,62 @@ export function RefreshStatusModal(props: Props) { try { const nodes = getAllNodes([props.treeNode]); const total = nodes.length; + + const groupReceptionColl = props.distribution.target.resource.receptionColl; + const publicReceptionColl = + props.distribution.target.resource.genTargetColl( + '-' + groupReceptionColl.collName, + ); + + let current = 0; const chunks = _.chunk(nodes, 1000); for (const chunk of chunks) { - const receptions = await props.distribution.findReportReceptions( + const receptionMap = await props.distribution.findReportReceptions( chunk.map((node) => node.id), ); + const receptions = Object.values(receptionMap).filter((r) => !!r) as XReception[]; + // 移动reception集合 + await groupReceptionColl.replaceMany(receptions); + await publicReceptionColl.removeMany(receptions); + await nextTick(); + // 更新taskStatus + for (const node of chunk) { + delete node.reception; + delete (node as any).children; + node.taskStatus = getStatus(receptionMap[node.id]); + } + + current += chunk.length; + setProgress(parseFloat(((current / total) * 75).toFixed(2))); + } + + //更新summary + const tree = buildTree(nodes); + deepSummary(tree); + await nextTick(); + + // 保存节点 + const nodes2 = getAllNodes(tree); + current = 0; + + const chunks2 = _.chunk(nodes2, 1000); + for (const chunk of chunks2) { const newNodes = chunk.map((node) => { - const newNode = _.cloneDeep(node); - delete newNode.reception; - delete (newNode as any).children; - newNode.taskStatus = getStatus(receptions[node.id]); + const newNode = _.cloneDeep(_.omit(node, ['children'])); return newNode; }); - const ret = await props.distribution.holder.tree!.nodeColl.replaceMany(newNodes); - console.warn(ret); + await props.distribution.holder.tree!.nodeColl.replaceMany(newNodes); current += chunk.length; - setProgress(parseFloat(((current / total) * 100).toFixed(2))); + setProgress(parseFloat(((current / total) * 25 + 75).toFixed(2))); } + message.success('更新完成'); } catch (error) { message.error(error instanceof Error ? error.message : String(error)); @@ -61,7 +115,7 @@ export function RefreshStatusModal(props: Props) { onCancel={props.onClose}>
- 将新的上报状态更新到当前空间 + 将新的上报状态和汇总数量更新到当前空间
{isStart ? (
diff --git a/src/components/DataPreview/task/distribution.tsx b/src/components/DataPreview/task/distribution.tsx index 956ecd33f55e5f7420348ec29fa3f39c5a8c1320..efc97e283b0ed3b5b9c38fed9f6b2dac72de21f1 100644 --- a/src/components/DataPreview/task/distribution.tsx +++ b/src/components/DataPreview/task/distribution.tsx @@ -86,7 +86,7 @@ const DistributionPreview: React.FC = (props) => { ), ); } else if (node.taskStatus) { - const receptionMap = await props.distribution.findReportReceptions([node.id]); + const receptionMap = await props.distribution.getPrivateProvider().findReportReceptions([node.id]); if (receptionMap[node.id]) { setCurrentReception( new ReportReception( @@ -97,7 +97,21 @@ const DistributionPreview: React.FC = (props) => { ), ); } else { - setCurrentReception(null); + const receptionMap = await props.distribution.findReportReceptions([node.id]); + if (receptionMap[node.id]) { + setCurrentReception( + new ReportReception( + receptionMap[node.id]!, + props.distribution.target, + props.distribution.holder, + false, + ), + ); + console.warn(`状态异常:节点 ${node.name} 有状态但任务接收在公共集合中`); + } else { + setCurrentReception(null); + console.warn(`状态异常:节点 ${node.name} 有状态但无任务接收`); + } } } else { setCurrentReception(null); @@ -197,7 +211,10 @@ const DistributionPreview: React.FC = (props) => { distribution={props.distribution} treeNode={treeNode!} visible={refreshVisible} - onClose={() => setRefreshVisible(false)} + onClose={() => { + setRefreshVisible(false); + loadData(); + }} /> )}
diff --git a/src/components/DataPreview/task/index.module.less b/src/components/DataPreview/task/index.module.less index c5700e6b01d1a17e62b88a83a911eefd66012228..e0ebf54a2bad9898bcb0d0d1cbb8d02e0d1fffbf 100644 --- a/src/components/DataPreview/task/index.module.less +++ b/src/components/DataPreview/task/index.module.less @@ -54,6 +54,31 @@ align-items: center; } + .left-content { + height: 100%; + display: flex; + flex-direction: column; + position: relative; + > :global(.dx-resizable) { + height: 1px; + flex: auto; + margin-bottom: 40px; + } + + &:global(.is-collapsed) { + > :global(.dx-resizable) { + display: none; + } + } + + .expand-btn { + position: absolute; + left: 0; + bottom: 0; + z-index: 200; + } + } + .tree-wrap { height: 100%; width: 100%; diff --git a/src/components/DataPreview/task/index.tsx b/src/components/DataPreview/task/index.tsx index ea6516d5e40c53797ec3c2afbd7552509c4f8dbb..11fb44a243246310b84840bd687ccef53501cd49 100644 --- a/src/components/DataPreview/task/index.tsx +++ b/src/components/DataPreview/task/index.tsx @@ -1,9 +1,8 @@ import { IReception } from '@/ts/core/work/assign/reception'; -import { Button, Empty, Spin, Tag, message } from 'antd'; +import { Button, Empty, Spin, Tag } from 'antd'; import React, { ReactNode, createContext, useMemo, useState } from 'react'; import cls from './index.module.less'; -import { XReportTreeNode } from '@/ts/base/schema'; -import { ReportTaskTreeNodeView, ReportTreeNodeView } from '@/ts/base/model'; +import { ReportTaskTreeNodeView } from '@/ts/base/model'; import { ReportTaskTree } from './ReportTaskTree'; import { ReceptionStart } from './ReceptionStart'; import { useEffectOnce } from 'react-use'; @@ -30,6 +29,7 @@ const ReceptionTask: React.FC = (props) => { const [loaded, setLoaded] = useState(false); const [tree, setTree] = useState([]); const [treeNode, setTreeNode] = useState(null); + const [expand, setExpand] = useState(true); const showTree = useMemo(() => { if (tree.length == 0) { @@ -139,12 +139,23 @@ const ReceptionTask: React.FC = (props) => {
{showTree ? ( - +
+ + +
) : ( <> )} diff --git a/src/ts/base/model.ts b/src/ts/base/model.ts index f1924b1130d90d6daf20c73f3a971679229de0ca..1386df1e52ff17785056d5e0166368db1677d96e 100644 --- a/src/ts/base/model.ts +++ b/src/ts/base/model.ts @@ -2135,6 +2135,7 @@ export interface ReportTaskTreeNodeView extends XReportTaskTreeNode { children: ReportTaskTreeNodeView[]; count: number; isLeaf: boolean; + reception?: XReception | null; } export interface ReportSummaryTreeNodeView extends XReportTreeNode { // 子节点 diff --git a/src/ts/base/schema.ts b/src/ts/base/schema.ts index 3cab42b86d4ab4e6d7613262312ad14bebafabe9..227c88f47530810cd01d9336318e7627896e546f 100644 --- a/src/ts/base/schema.ts +++ b/src/ts/base/schema.ts @@ -1,5 +1,5 @@ import { model } from '.'; -import { ReceptionStatus } from '../core/work/assign/reception/status'; +import { ReceptionStatus, ReportTaskTreeSummary } from '../core/work/assign/reception/status'; import { NodeType, PeriodType, ReportTreeNodeTypes, ReportTreeTypes } from './enum'; export type Xbase = { @@ -994,8 +994,8 @@ export interface XReportTreeNode extends XEntity { /** 报表任务树节点 */ export interface XReportTaskTreeNode extends XReportTreeNode { - reception?: XReception | null; taskStatus?: ReceptionStatus; + summary?: ReportTaskTreeSummary; } export interface XReportTree extends XStandard { diff --git a/src/ts/core/thing/standard/reporttree/index.ts b/src/ts/core/thing/standard/reporttree/index.ts index fec1417ee6917a4cdf927156c1b651ad2567a4d4..c8d75a2b959c96129ce487fbd14329411e6891d3 100644 --- a/src/ts/core/thing/standard/reporttree/index.ts +++ b/src/ts/core/thing/standard/reporttree/index.ts @@ -458,15 +458,17 @@ abstract class ReportTreeBase return [[], summary]; } - // 仅查找没有status的reception - const noStatusNodeIds = nodes - .filter((n: XReportTaskTreeNode) => !n.taskStatus) - .map((n) => n.id); - const receptionMap = await dist.findReportReceptions(noStatusNodeIds); + // 公共集合只有新的数据 + const nodeIds = nodes.map((n) => n.id); + const receptionMap = await dist.findReportReceptions(nodeIds); const pool = Object.values(nodeMap); for (const node of pool) { node.reception = receptionMap[node.id]; + // 如果有新的刷新状态 + if (node.reception) { + node.taskStatus = getStatus(node.reception); + } const parent = nodeMap[node.parentId!]; if (!parent) { diff --git a/src/ts/core/work/assign/distribution/report.ts b/src/ts/core/work/assign/distribution/report.ts index bc7a8a7e4cfb545fa84993955ba2d0486634a2bf..3c9a5eca7e6e371a6d090835022421a07d4662cf 100644 --- a/src/ts/core/work/assign/distribution/report.ts +++ b/src/ts/core/work/assign/distribution/report.ts @@ -27,6 +27,8 @@ export interface IReportDistribution findReportRootNode(belongId?: string): Promise; /** 导出上报状态 */ exportReceptionStatus(): Promise; + /** 返回一个查群私有集合的`IReceptionProvider` */ + getPrivateProvider(): IReceptionProvider; } export class ReportDistribution @@ -149,31 +151,36 @@ export class ReportDistribution async findReportReceptions( nodeIds: string[], + fromPrivate = false, ): Promise> { if (this.metadata.content.type != model.TaskContentType.Report) { return {}; } const ret: Dictionary = {}; - // 查公共集合 - const publicReceptionColl = this.target.resource.genTargetColl( - '-' + this.target.resource.receptionColl.collName, - ); + + const coll = fromPrivate + ? this.target.resource.receptionColl + : this.target.resource.genTargetColl( + '-' + this.target.resource.receptionColl.collName, + ); let res: schema.XReception[] = []; const chunks = _.chunk(nodeIds, 5000); for (const chunk of chunks) { - res = res.concat(await publicReceptionColl.loadSpace({ - options: { - match: { - 'content.treeNode.id': { - _in_: chunk, + res = res.concat( + await coll.loadSpace({ + options: { + match: { + 'content.treeNode.id': { + _in_: chunk, + }, + period: this.metadata.period, + taskId: this.metadata.taskId, }, - period: this.metadata.period, - taskId: this.metadata.taskId, }, - }, - })); + }), + ); // 该接口请求非常非常慢,等待浏览器空闲 await nextTick(); } @@ -190,6 +197,14 @@ export class ReportDistribution return ret; } + getPrivateProvider(): IReceptionProvider { + return { + findReportReceptions: (nodeIds) => { + return this.findReportReceptions(nodeIds, true); + }, + }; + } + async exportReceptionStatus(belongId?: string): Promise { const roots = await this.findReportRootNode(belongId); if (roots.length == 0) {