From 325269c2feb55a8fb34447ff0870fb408fc46e44 Mon Sep 17 00:00:00 2001 From: zty Date: Fri, 12 Jun 2026 14:19:37 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=8E=86=E5=8F=B2=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E9=A1=B5=20+=20=E5=BF=AB=E9=80=9F=E5=AF=BC=E5=85=A5=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=20+=20=E6=8A=A5=E5=91=8A=E6=9F=A5=E7=9C=8B=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 历史记录(History.vue): 列已生成报告项目,筛选,详情/查看报告/复用 - 复用: 后端 POST /projects/:id/duplicate 复制项目+空间+材料为新草稿 - 快速导入(ImportProjectModal): 首页"快速导入"→选模板→fromTemplateId建项目 - 报告查看(Report.vue, /report/:id): 封面+各空间5污染物预测+超标标红+ 污染源溯源,支持打印/导出PDF;配置页生成后/历史页"查看报告"跳此 注: 数据库 10.0.11.51 当前不可达,以上构建+类型检查通过,待DB恢复后实跑验证。 Co-Authored-By: Claude Opus 4.8 (1M context) --- apps/api/src/projects/projects.controller.ts | 5 + apps/api/src/projects/projects.service.ts | 45 +++++ apps/web/src/api/projects.ts | 5 +- .../web/src/components/ImportProjectModal.vue | 70 ++++++++ apps/web/src/pages/History.vue | 99 ++++++++++- apps/web/src/pages/Home.vue | 10 +- apps/web/src/pages/ProjectConfig.vue | 1 + apps/web/src/pages/Report.vue | 156 ++++++++++++++++++ apps/web/src/router/index.ts | 1 + 9 files changed, 387 insertions(+), 5 deletions(-) create mode 100644 apps/web/src/components/ImportProjectModal.vue create mode 100644 apps/web/src/pages/Report.vue diff --git a/apps/api/src/projects/projects.controller.ts b/apps/api/src/projects/projects.controller.ts index 6598aef..e7205bb 100644 --- a/apps/api/src/projects/projects.controller.ts +++ b/apps/api/src/projects/projects.controller.ts @@ -50,4 +50,9 @@ export class ProjectsController { generate(@CurrentOrg() org: OrgPayload, @Param('id') id: string) { return this.projects.generate(org.id, id); } + + @Post(':id/duplicate') + duplicate(@CurrentOrg() org: OrgPayload, @Param('id') id: string) { + return this.projects.duplicate(org.id, id); + } } diff --git a/apps/api/src/projects/projects.service.ts b/apps/api/src/projects/projects.service.ts index 071b186..97cc6af 100644 --- a/apps/api/src/projects/projects.service.ts +++ b/apps/api/src/projects/projects.service.ts @@ -63,6 +63,51 @@ export class ProjectsService { }); } + /** 复用:把任意自有项目(或模板)复制成一个新草稿 */ + async duplicate(orgId: string, id: string) { + const src = await this.prisma.project.findUnique({ + where: { id }, + include: { spaces: { include: { materials: true } } }, + }); + if (!src) throw new NotFoundException('项目不存在'); + if (src.ownerOrgId !== orgId && !src.isPublic) throw new ForbiddenException('无权复用'); + + return this.prisma.project.create({ + data: { + id: this.genId('P'), + name: src.name + ' (复用)', + type: src.type, + province: src.province, + city: src.city, + area: src.area, + status: 'configuring', + ownerOrgId: orgId, + spaces: { + create: src.spaces.map((s) => ({ + id: this.genId('S'), + name: s.name, + type: s.type, + layout: s.layout, + height: s.height, + area: s.area, + volume: s.volume, + temperature: s.temperature, + humidity: s.humidity, + ventilationRate: s.ventilationRate, + standard: s.standard, + materials: { + create: s.materials.map((m) => ({ + materialId: m.materialId, + usageUnit: m.usageUnit, + usageAmount: m.usageAmount, + })), + }, + })), + }, + }, + }); + } + async list(orgId: string, q: QueryProjectsDto) { const where: Prisma.ProjectWhereInput = { ownerOrgId: orgId, isTemplate: false }; if (q.id) where.id = { contains: q.id, mode: 'insensitive' }; diff --git a/apps/web/src/api/projects.ts b/apps/web/src/api/projects.ts index 726a3a2..999e6ff 100644 --- a/apps/web/src/api/projects.ts +++ b/apps/web/src/api/projects.ts @@ -82,6 +82,9 @@ export function deleteProject(id: string) { export function generateReport(id: string) { return http.post(`/projects/${id}/generate`, {}); } -export function listProjects(params: { status?: string; unfinished?: string; page?: number; pageSize?: number }) { +export function listProjects(params: { status?: string; unfinished?: string; name?: string; type?: string; rating?: string; page?: number; pageSize?: number }) { return http.get>('/projects', { params }); } +export function duplicateProject(id: string) { + return http.post(`/projects/${id}/duplicate`, {}); +} diff --git a/apps/web/src/components/ImportProjectModal.vue b/apps/web/src/components/ImportProjectModal.vue new file mode 100644 index 0000000..b54029d --- /dev/null +++ b/apps/web/src/components/ImportProjectModal.vue @@ -0,0 +1,70 @@ + + + diff --git a/apps/web/src/pages/History.vue b/apps/web/src/pages/History.vue index 5ee11fd..cfba149 100644 --- a/apps/web/src/pages/History.vue +++ b/apps/web/src/pages/History.vue @@ -1,7 +1,102 @@ - + + + diff --git a/apps/web/src/pages/Home.vue b/apps/web/src/pages/Home.vue index 1d0444a..6393c75 100644 --- a/apps/web/src/pages/Home.vue +++ b/apps/web/src/pages/Home.vue @@ -22,6 +22,7 @@ + @@ -31,17 +32,18 @@ import { useRouter } from 'vue-router'; import { message } from 'ant-design-vue'; import { useAuthStore } from '../stores/auth'; import NewProjectModal from '../components/NewProjectModal.vue'; +import ImportProjectModal from '../components/ImportProjectModal.vue'; import type { ProjectDetail } from '../api/projects'; const router = useRouter(); const auth = useAuthStore(); -const todo = () => message.info('该功能将在后续阶段实现'); const createOpen = ref(false); +const importOpen = ref(false); const predictCards = [ { title: '新建项目预测', desc: '从头配置项目、空间、材料进行预测', action: () => (createOpen.value = true) }, - { title: '快速导入项目', desc: '根据模板或文件导入后调整配置预测', action: () => router.push({ name: 'template' }) }, + { title: '快速导入项目', desc: '根据模板导入后调整配置预测', action: () => (importOpen.value = true) }, { title: '继续配置预测', desc: '继续已保存、未提交的配置', action: () => router.push({ name: 'drafts' }) }, { title: '污染源识别 · 快速溯源', desc: '单空间快速预测,溯源主要污染材料', action: () => router.push('/source') }, ]; @@ -55,6 +57,10 @@ function onCreated(p: ProjectDetail) { createOpen.value = false; router.push({ name: 'predict', params: { id: p.id } }); } +function onImported(id: string) { + importOpen.value = false; + router.push({ name: 'predict', params: { id } }); +} diff --git a/apps/web/src/router/index.ts b/apps/web/src/router/index.ts index 00ca2bc..bd6fe1f 100644 --- a/apps/web/src/router/index.ts +++ b/apps/web/src/router/index.ts @@ -15,6 +15,7 @@ const routes = [ { path: 'history-project', name: 'history', component: () => import('../pages/History.vue') }, { path: 'drafts', name: 'drafts', component: () => import('../pages/Drafts.vue') }, { path: 'predict/:id', name: 'predict', component: () => import('../pages/ProjectConfig.vue') }, + { path: 'report/:id', name: 'report', component: () => import('../pages/Report.vue') }, ], }, ];