فهرست منبع

Merge branch 'master' of http://git.fmode.cn:3000/nkkj/yss-project

0235711 17 ساعت پیش
والد
کامیت
2ee603ce60

+ 100 - 34
src/app/pages/designer/project-detail/debug-styles.scss

@@ -1,6 +1,6 @@
 /* 调试样式文件 - 增强版本,确保布局正确显示 */
 
-@use '../ios-theme.scss' as *;
+@import '../../../../styles/_variables';
 
 /* 重置所有可能冲突的样式 - 使用最高优先级 */
 * {
@@ -33,6 +33,15 @@
   width: 100% !important;
   padding: 0 !important;
   margin: 0 !important;
+  
+  /* 减少标签页内容的垂直间距 */
+  > * {
+    margin-bottom: 12px !important; /* 统一减少子元素的底部间距 */
+    
+    &:last-child {
+      margin-bottom: 0 !important;
+    }
+  }
 }
 
 /* 重置项目进度标签页内容 */
@@ -40,12 +49,21 @@
   width: 100% !important;
   padding: 0 !important;
   margin: 0 !important;
+  
+  /* 减少进度标签页内容的垂直间距 */
+  > * {
+    margin-bottom: 12px !important; /* 统一减少子元素的底部间距 */
+    
+    &:last-child {
+      margin-bottom: 0 !important;
+    }
+  }
 }
 
 /* 上传区域样式优化 - 确保在并排显示时内容清晰可读 */
 .upload-section {
   /* 基础样式保持不变 */
-  margin-bottom: 16px !important;
+  margin-bottom: 12px !important; /* 从16px减少到12px */
   
   /* 针对四个卡片并排时的特殊优化 */
   .stage-progress-container:has(.vertical-stage-block:nth-child(4):last-child) & {
@@ -187,14 +205,14 @@
 .left-column .project-info-card .info-grid {
   display: grid !important;
   grid-template-columns: 1fr 1fr !important;
-  gap: 8px !important;
-  margin-bottom: 12px !important;
+  gap: 6px !important; // 从8px减少到6px
+  margin-bottom: 8px !important; // 从12px减少到8px
 }
 
 .left-column .project-info-card .info-item {
   display: flex !important;
   flex-direction: column !important;
-  padding: 8px !important;
+  padding: 6px !important; // 从8px减少到6px
   background: #f8f9fa !important;
   border-radius: 6px !important;
   border: 1px solid #e9ecef !important;
@@ -204,7 +222,7 @@
 .left-column .project-info-card .info-item label {
   font-weight: 500 !important;
   color: #6c757d !important;
-  margin-bottom: 4px !important;
+  margin-bottom: 2px !important; // 从4px减少到2px
   font-size: 11px !important;
 }
 
@@ -215,18 +233,18 @@
 }
 
 .left-column .project-info-card .tags-container {
-  margin-top: 8px !important;
+  margin-top: 6px !important; // 从8px减少到6px
 }
 
 .left-column .project-info-card .tag-section {
-  margin-bottom: 8px !important;
+  margin-bottom: 6px !important; // 从8px减少到6px
 }
 
 .left-column .project-info-card .tag-section h3 {
   font-size: 12px !important;
   font-weight: 600 !important;
   color: #495057 !important;
-  margin-bottom: 6px !important;
+  margin-bottom: 4px !important; // 从6px减少到4px
 }
 
 .left-column .project-info-card .tags .tag {
@@ -346,24 +364,41 @@
 .delivery-stage-header .dot.completed { background: $ios-success; }
 .delivery-stage-header .dot.active { background: $ios-primary; }
 
-.delivery-stage-body { display: flex; flex-direction: column; gap: $ios-spacing-md; }
+/* 优化垂直间距和内边距 - 减少空白 */
+.delivery-stage-body { 
+  display: flex; 
+  flex-direction: column; 
+  gap: $ios-spacing-xs; // 从 $ios-spacing-sm 减少到 $ios-spacing-xs
+}
 
-/* 统一纵向内容容器 */
-.section-vertical { display: flex; flex-direction: column; gap: $ios-spacing-lg; }
+/* 减少垂直区域间距 */
+.section-vertical { 
+  display: flex; 
+  flex-direction: column; 
+  gap: $ios-spacing-sm; // 从 $ios-spacing-md 减少到 $ios-spacing-sm
+}
 
+/* 减少垂直阶段块的内边距 */
 .vertical-stage-block {
-  background: $ios-background-secondary;
-  border: 1px solid $ios-border;
-  border-radius: $ios-radius-md;
-  padding: $ios-spacing-md;
+  border-radius: 8px; // 替换未定义的变量为固定值
+  background: white; // 替换未定义的变量为固定值
+  padding: 10px; // 进一步减少内边距,从 $ios-spacing-xs 减少到固定值10px
+  margin-bottom: 10px; // 添加底部间距控制
 }
 
-.vertical-stage-header { display: flex; align-items: center; gap: $ios-spacing-sm; margin-bottom: $ios-spacing-sm; }
-.vertical-stage-header .dot { width: 10px; height: 10px; border-radius: 50%; background: $ios-border; }
-.vertical-stage-header .dot.completed { background: $ios-success; }
-.vertical-stage-header .dot.active { background: $ios-primary; }
+.vertical-stage-header { 
+  display: flex; 
+  align-items: center; 
+  gap: 8px; // 从 $ios-spacing-xs 减少到固定值8px
+  margin-bottom: 8px; // 从 $ios-spacing-xs 减少到固定值8px
+}
 
-.vertical-stage-body { display: flex; flex-direction: column; gap: $ios-spacing-md; }
+/* 减少垂直阶段主体间距 */
+.vertical-stage-body { 
+  display: flex; 
+  flex-direction: column; 
+  gap: 10px; // 从 $ios-spacing-sm 减少到固定值10px
+}
 
 /* 缩略图列表适配到新容器 */
 .section-panel .thumb-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: $ios-spacing-md; }
@@ -422,8 +457,8 @@
   display: flex !important;
   justify-content: space-between !important;
   align-items: center !important;
-  margin-bottom: 16px !important;
-  padding-bottom: 12px !important;
+  margin-bottom: 12px !important; // 从16px减少到12px
+  padding-bottom: 8px !important; // 从12px减少到8px
   border-bottom: 1px solid #f0f0f0 !important;
   transition: all 0.3s ease !important;
 }
@@ -1295,7 +1330,7 @@
 /* 阶段卡片横向排列(按板块的阶段数量自适应列数) */
 .stage-progress-container {
   display: grid !important;
-  gap: 12px !important;
+  gap: 8px !important; // 从10px进一步减少到8px
   align-items: stretch !important; // 保证同一行的卡片等高
   margin-top: 0 !important; // 删除标题后,移除上边距以优化空间利用
   
@@ -1307,25 +1342,26 @@
     /* 笔记本端适配(1366px及以上) */
     @media (min-width: 1366px) {
       grid-template-columns: repeat(4, 1fr) !important;
-      gap: 16px !important;
+      gap: 12px !important; // 从16px减少到12px
     }
     
     /* 24寸PC端适配(1920px及以上) */
     @media (min-width: 1920px) {
       grid-template-columns: repeat(4, 1fr) !important;
-      gap: 20px !important;
+      gap: 16px !important; // 从20px减少到16px
     }
     
     /* 较小屏幕的降级处理(小于1366px) */
     @media (max-width: 1365px) {
       grid-template-columns: repeat(2, 1fr) !important;
-      gap: 12px !important;
+      gap: 10px !important; // 从12px减少到10px
     }
   }
   
   /* 其他情况保持原有的自适应布局 */
   &:not(:has(.vertical-stage-block:nth-child(4):last-child)) {
     grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)) !important;
+    gap: 10px !important; // 添加间距控制
   }
 }
 
@@ -1333,7 +1369,7 @@
   display: flex !important;
   flex-direction: column !important;
   height: 100% !important;
-  padding: 12px !important;
+  padding: 8px !important; // 从10px进一步减少到8px
   background: white !important;
   border-radius: 8px !important;
   border: 1px solid #e9ecef !important;
@@ -1346,20 +1382,20 @@
   .stage-progress-container:has(.vertical-stage-block:nth-child(4):last-child) & {
     /* 笔记本端和PC端的内容优化 */
     @media (min-width: 1366px) {
-      padding: 16px !important;
-      min-height: 300px !important; /* 确保卡片有足够高度 */
+      padding: 12px !important; // 从16px减少到12px
+      min-height: 280px !important; /* 从300px减少到280px */
     }
     
     /* 24寸PC端的内容优化 */
     @media (min-width: 1920px) {
-      padding: 20px !important;
-      min-height: 320px !important;
+      padding: 16px !important; // 从20px减少到16px
+      min-height: 300px !important; // 从320px减少到300px
     }
     
     /* 较小屏幕时保持紧凑布局 */
     @media (max-width: 1365px) {
-      padding: 12px !important;
-      min-height: 280px !important;
+      padding: 10px !important; // 从12px减少到10px
+      min-height: 260px !important; // 从280px减少到260px
     }
   }
 }
@@ -1452,6 +1488,35 @@
   background: #ff4d4f !important;
   box-shadow: 0 0 0 4px rgba(255, 77, 79, 0.2) !important;
 }
+/* 新增:三板块垂直高度填充 —— 客户信息、需求沟通、方案确认 */
+/* 让主布局的两列在同一行内等高伸展 */
+.progress-tab-content > .main-content-layout {
+  align-items: stretch !important;
+}
+
+/* 客户信息卡片设定统一的最低高度以填补左侧空白 */
+.left-column .project-info-card.card {
+  min-height: 420px !important;
+}
+
+/* 当右侧仅展示 需求沟通 + 方案确认 两个阶段时,强制两列并排,设定统一最低高度 */
+.right-column .stage-progress-container:has(#stage-requirements-talk):has(#stage-proposal-confirm) {
+  grid-template-columns: repeat(2, 1fr) !important;
+}
+
+.right-column .stage-progress-container:has(#stage-requirements-talk):has(#stage-proposal-confirm) .vertical-stage-block {
+  min-height: 420px !important;
+}
+
+/* 根据屏幕尺寸微调高度,保证在大屏下更饱满,在小屏下不压迫 */
+@media (min-width: 1920px) {
+  .left-column .project-info-card.card { min-height: 480px !important; }
+  .right-column .stage-progress-container:has(#stage-requirements-talk):has(#stage-proposal-confirm) .vertical-stage-block { min-height: 480px !important; }
+}
+
+@media (max-width: 1365px) {
+  .left-column .project-info-card.card { min-height: 360px !important; }
+  .right-column .stage-progress-container:has(#stage-requirements-talk):has(#stage-proposal-confirm) .vertical-stage-block { min-height: 360px !important; }
 
 /* 方案确认卡片样式 - 与客户信息卡片保持一致 */
 .left-column .proposal-confirm-card {
@@ -1570,4 +1635,5 @@
   font-size: 12px !important;
   color: #1890ff !important;
   font-weight: 500 !important;
+}
 }

+ 28 - 28
src/app/pages/designer/project-detail/project-detail.scss

@@ -12,7 +12,7 @@
     display: flex;
     align-items: center;
     justify-content: center;
-    gap: 6px; // 减小间距以适配更紧凑的布局
+    gap: 12px; // 放大横向布局间距
 
     .stage-nav-item {
       display: flex;
@@ -22,26 +22,26 @@
       transition: all 0.3s ease;
 
       .stage-nav-circle {
-        width: 24px; // 减小圆圈大小
-        height: 24px; // 减小圆圈大小
+        width: 40px; // 放大圆圈
+        height: 40px; // 放大圆圈
         border-radius: 50%;
         background: #e5e5ea;
         border: 2px solid #d1d1d6;
         display: flex;
         align-items: center;
         justify-content: center;
-        margin-bottom: 3px; // 减小底部间距
+        margin-bottom: 6px; // 放大下方间距以容纳标签
         transition: all 0.3s ease;
 
         .stage-nav-number {
-          font-size: 10px; // 减小字体大小
+          font-size: 14px; // 放大数字
           font-weight: 600;
           color: #8e8e93;
         }
       }
 
       .stage-nav-label {
-        font-size: 9px; // 减小字体大小
+        font-size: 12px; // 放大标签文字
         font-weight: 500;
         color: #3c3c43;
         text-align: center;
@@ -91,11 +91,11 @@
     }
 
     .stage-nav-connector {
-      width: 20px; // 减小连接线宽度
+      width: 36px; // 放大连接线宽度
       height: 2px;
       background: #d1d1d6;
-      margin: 0 3px; // 减小左右间距
-      margin-bottom: 12px; // 减小底部间距
+      margin: 0 6px; // 增加左右间距
+      margin-bottom: 14px; // 适配更大的圆圈
       transition: all 0.3s ease;
 
       &.completed {
@@ -749,17 +749,17 @@
 .proposal-confirm-section {
   background: white;
   border-radius: 12px;
-  padding: 24px;
+  padding: 18px; // 从24px减少到18px
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
   border: 1px solid rgba(0, 0, 0, 0.06);
-  margin-bottom: 20px;
+  margin-bottom: 16px; // 从20px减少到16px
 
   .section-header {
     display: flex;
     align-items: center;
     justify-content: space-between;
-    margin-bottom: 24px;
-    padding-bottom: 16px;
+    margin-bottom: 18px; // 从24px减少到18px
+    padding-bottom: 12px; // 从16px减少到12px
     border-bottom: 1px solid rgba(0, 0, 0, 0.08);
 
     h4 {
@@ -1057,17 +1057,17 @@
   .requirement-summary-card {
     background: white;
     border-radius: 12px;
-    padding: 24px;
+    padding: 18px; // 从24px减少到18px
     box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
     border: 1px solid rgba(0, 0, 0, 0.06);
-    margin-bottom: 20px;
+    margin-bottom: 16px; // 从20px减少到16px
 
     .card-header {
       display: flex;
       align-items: center;
       justify-content: space-between;
-      margin-bottom: 20px;
-      padding-bottom: 16px;
+      margin-bottom: 16px; // 从20px减少到16px
+      padding-bottom: 12px; // 从16px减少到12px
       border-bottom: 1px solid rgba(0, 0, 0, 0.08);
 
       .header-left {
@@ -1129,13 +1129,13 @@
     .requirement-info-grid {
       display: grid;
       grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
-      gap: 16px;
+      gap: 12px; // 从16px减少到12px
 
       .info-section {
         background: rgba(0, 122, 255, 0.02);
         border: 1px solid rgba(0, 122, 255, 0.08);
         border-radius: 10px;
-        padding: 16px;
+        padding: 12px; // 从16px减少到12px
         transition: all 0.2s ease;
 
         &:hover {
@@ -1148,7 +1148,7 @@
           display: flex;
           align-items: center;
           gap: 8px;
-          margin-bottom: 12px;
+          margin-bottom: 10px; // 从12px减少到10px
 
           .title-icon {
             width: 20px;
@@ -1174,7 +1174,7 @@
             display: flex;
             align-items: center;
             justify-content: space-between;
-            padding: 10px 0;
+            padding: 8px 0; // 从10px减少到8px
             border-bottom: 1px solid rgba(0, 0, 0, 0.04);
 
             &:last-child {
@@ -1857,7 +1857,7 @@
 .main-content-layout {
   display: grid;
   grid-template-columns: 1fr 28%; // 使用百分比:主内容区和右侧栏(28%)
-  gap: 2%;
+  gap: 1.5%; // 从2%减少到1.5%
   max-width: 96vw; // 使用视口单位,留出4%边距
   margin: 0 auto;
   overflow: visible;
@@ -2083,10 +2083,10 @@
 
 /* 水平导航栏容器 */
 .horizontal-nav-container {
-  margin-bottom: $ios-spacing-md; // 缩小与主体内容的间距
+  margin-bottom: $ios-spacing-sm; // 进一步缩小与主体内容的间距
   background-color: white;
   border-radius: $ios-radius-lg;
-  padding: $ios-spacing-md;
+  padding: $ios-spacing-sm; // 减少内边距
   box-shadow: $ios-shadow-sm;
 }
 
@@ -2183,7 +2183,7 @@
     // 新的大型上传框样式
     .upload-dropzone {
       width: 100%;
-      min-height: 200px;
+      min-height: 100px; // 进一步降低最小高度以减少空白
       border: 2px dashed #d0d7de;
       border-radius: 12px;
       background: #f8f9fa;
@@ -2194,7 +2194,7 @@
       cursor: pointer;
       transition: all 0.3s ease;
       position: relative;
-      margin-bottom: 16px;
+      margin-bottom: 12px; // 从16px减少到12px
       
       &:hover {
         border-color: #4A90E2;
@@ -2222,8 +2222,8 @@
       
       // 有图片时的样式
       &.has-images {
-        min-height: 250px;
-        padding: 16px;
+        min-height: 120px; // 从180px进一步降低到120px
+        padding: 12px; // 从16px减少到12px
         cursor: default;
         
         &:hover {

+ 61 - 0
src/app/shared/components/full-report-overlay/full-report-overlay.component.html

@@ -0,0 +1,61 @@
+@if (visible) {
+  <div class="overlay">
+    <div class="report-container" (click)="$event.stopPropagation()">
+      <div class="report-header">
+        <h2>完整分析报告</h2>
+        <button class="close-btn" (click)="onClose()">关闭</button>
+      </div>
+
+      <div class="report-body">
+        <!-- 颜色分析 -->
+        @if (colorResult) {
+          <section class="section">
+            <h3>颜色分析</h3>
+            <div class="color-grid">
+              @for (c of colorResult.colors; track c.hex) {
+                <div class="color-item">
+                  <div class="swatch" [style.background-color]="c.hex"></div>
+                  <div class="meta">
+                    <span class="hex">{{ c.hex }}</span>
+                    <span class="pct">{{ c.percentage }}%</span>
+                  </div>
+                </div>
+              }
+            </div>
+          </section>
+        }
+
+        <!-- CAD洞察 -->
+        @if (cadResult) {
+          <section class="section">
+            <h3>CAD结构与空间洞察</h3>
+            <div class="subsection">
+              <h4>结构要素</h4>
+              <ul>
+                @for (el of cadResult.structuralElements; track el.type + el.position) {
+                  <li>{{ el.type }} · 位置:{{ el.position }} · {{ el.changeable ? '可调整' : '不可调整' }}</li>
+                }
+              </ul>
+            </div>
+            <div class="subsection">
+              <h4>空间指标</h4>
+              <ul>
+                @for (m of cadResult.spaceMetrics; track m.room) {
+                  <li>{{ m.room }} · 比例:{{ m.ratio }} · 通道宽:{{ m.width }}</li>
+                }
+              </ul>
+            </div>
+            <div class="subsection">
+              <h4>动线与规范</h4>
+              <ul>
+                @for (f of cadResult.flowMetrics; track f.area) {
+                  <li>{{ f.area }} · 宽度:{{ f.width }} · {{ f.compliance ? '符合规范' : '需优化' }}</li>
+                }
+              </ul>
+            </div>
+          </section>
+        }
+      </div>
+    </div>
+  </div>
+}

+ 58 - 0
src/app/shared/components/full-report-overlay/full-report-overlay.component.scss

@@ -0,0 +1,58 @@
+.overlay {
+  position: fixed;
+  inset: 0;
+  background: rgba(0,0,0,0.55);
+  backdrop-filter: blur(4px);
+  z-index: 10000;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.report-container {
+  width: 96vw;
+  height: 90vh;
+  max-width: 1280px;
+  background: #fff;
+  border-radius: 16px;
+  box-shadow: 0 20px 60px rgba(0,0,0,0.25);
+  display: flex;
+  flex-direction: column;
+}
+
+.report-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 16px 20px;
+  border-bottom: 1px solid #eee;
+}
+
+.close-btn {
+  border: none;
+  background: #f5f5f7;
+  padding: 8px 12px;
+  border-radius: 8px;
+  cursor: pointer;
+}
+
+.report-body {
+  padding: 20px;
+  overflow: auto;
+}
+
+.section { margin-bottom: 24px; }
+.color-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
+  gap: 12px;
+}
+.color-item { background: #fafafa; border: 1px solid #eee; border-radius: 10px; padding: 12px; }
+.swatch { width: 64px; height: 64px; border-radius: 8px; box-shadow: inset 0 0 0 2px rgba(255,255,255,0.6); }
+.meta { margin-top: 8px; display: flex; gap: 8px; font-size: 12px; color: #333; }
+.subsection { margin-top: 12px; }
+.subsection ul { margin: 8px 0 0; padding-left: 18px; }
+
+@media (max-width: 768px) {
+  .report-container { width: 96vw; height: 92vh; }
+}

+ 22 - 0
src/app/shared/components/full-report-overlay/full-report-overlay.component.ts

@@ -0,0 +1,22 @@
+import { Component, Input, Output, EventEmitter } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { ColorAnalysisResult } from '../../services/color-analysis.service';
+import { CadAnalysisResult } from '../../services/cad-analysis.service';
+
+@Component({
+  selector: 'app-full-report-overlay',
+  standalone: true,
+  imports: [CommonModule],
+  templateUrl: './full-report-overlay.component.html',
+  styleUrls: ['./full-report-overlay.component.scss']
+})
+export class FullReportOverlayComponent {
+  @Input() visible = false;
+  @Input() colorResult?: ColorAnalysisResult;
+  @Input() cadResult?: CadAnalysisResult;
+  @Output() close = new EventEmitter<void>();
+
+  onClose(): void {
+    this.close.emit();
+  }
+}

+ 31 - 0
src/app/shared/components/global-prompt/global-prompt.component.html

@@ -0,0 +1,31 @@
+@if (visible) {
+  <!-- 全屏遮罩模式 -->
+  @if (mode === 'fullscreen') {
+    <div class="gp-backdrop" role="presentation" (click)="onClose()"></div>
+    <div class="gp-modal" role="dialog" aria-modal="true" aria-label="全局提示">
+      <div class="gp-header">
+        <div class="gp-icon" [class.success]="icon==='success'" [class.info]="icon==='info'" [class.warning]="icon==='warning'"></div>
+        <div class="gp-title">{{ title }}</div>
+        <button class="gp-close" (click)="onClose()" aria-label="关闭">×</button>
+      </div>
+      <div class="gp-content">{{ message }}</div>
+      <div class="gp-actions">
+        <button class="btn-primary" (click)="onAction()">确定</button>
+      </div>
+    </div>
+  }
+
+  <!-- 角落提示模式(无遮罩) -->
+  @if (mode === 'corner') {
+    <div class="gp-toast" [class.top-right]="position==='top-right'" [class.bottom-right]="position==='bottom-right'" role="status" aria-live="polite">
+      <div class="gp-toast-inner">
+        <div class="gp-icon" [class.success]="icon==='success'" [class.info]="icon==='info'" [class.warning]="icon==='warning'"></div>
+        <div class="gp-texts">
+          <div class="gp-title">{{ title }}</div>
+          <div class="gp-content">{{ message }}</div>
+        </div>
+        <button class="gp-close" (click)="onClose()" aria-label="关闭">×</button>
+      </div>
+    </div>
+  }
+}

+ 113 - 0
src/app/shared/components/global-prompt/global-prompt.component.scss

@@ -0,0 +1,113 @@
+// 层级与断点
+$z-index-backdrop: 9990;
+$z-index-modal: 9991;
+$z-index-toast: 1050;
+
+$mobile: 768px;
+$tablet: 1024px;
+
+.gp-backdrop {
+  position: fixed;
+  inset: 0;
+  background: rgba(0,0,0,0.5);
+  backdrop-filter: blur(2px);
+  z-index: $z-index-backdrop;
+}
+
+.gp-modal {
+  position: fixed;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  z-index: $z-index-modal;
+  width: min(560px, 90vw);
+  background: #fff;
+  border-radius: 16px;
+  box-shadow: 0 24px 64px rgba(0,0,0,0.18);
+  overflow: hidden;
+}
+
+.gp-header {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  padding: 16px 20px;
+  border-bottom: 1px solid #eee;
+}
+
+.gp-icon {
+  width: 24px;
+  height: 24px;
+  border-radius: 50%;
+  &.success { background: #10B981; }
+  &.info { background: #3B82F6; }
+  &.warning { background: #F59E0B; }
+}
+
+.gp-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #1f2937;
+}
+
+.gp-close {
+  margin-left: auto;
+  border: none;
+  background: transparent;
+  font-size: 18px;
+  line-height: 1;
+  cursor: pointer;
+  color: #6b7280;
+}
+
+.gp-content {
+  padding: 16px 20px;
+  color: #374151;
+  font-size: 14px;
+}
+
+.gp-actions {
+  display: flex;
+  justify-content: flex-end;
+  gap: 8px;
+  padding: 0 20px 16px 20px;
+}
+
+.btn-primary {
+  padding: 8px 14px;
+  border-radius: 8px;
+  border: none;
+  background: #007aff;
+  color: #fff;
+  cursor: pointer;
+}
+
+.gp-toast {
+  position: fixed;
+  z-index: $z-index-toast;
+  &.top-right { top: 16px; right: 16px; }
+  &.bottom-right { bottom: 16px; right: 16px; }
+}
+
+.gp-toast-inner {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  background: #fff;
+  border: 1px solid #e5e7eb;
+  box-shadow: 0 8px 24px rgba(0,0,0,0.12);
+  border-radius: 12px;
+  padding: 12px 14px;
+  min-width: 280px;
+}
+
+.gp-texts {
+  display: flex;
+  flex-direction: column;
+  .gp-title { font-size: 14px; font-weight: 600; color: #111827; }
+  .gp-content { font-size: 12px; color: #4b5563; padding: 0; }
+}
+
+@media (max-width: $mobile) {
+  .gp-toast-inner { min-width: 240px; }
+}

+ 43 - 0
src/app/shared/components/global-prompt/global-prompt.component.ts

@@ -0,0 +1,43 @@
+import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+type PromptMode = 'fullscreen' | 'corner';
+type CornerPosition = 'top-right' | 'bottom-right';
+
+@Component({
+  selector: 'app-global-prompt',
+  standalone: true,
+  imports: [CommonModule],
+  templateUrl: './global-prompt.component.html',
+  styleUrls: ['./global-prompt.component.scss'],
+  changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class GlobalPromptComponent {
+  @Input() visible = false;
+  @Input() title = '上传成功!';
+  @Input() message = '';
+  @Input() mode: PromptMode = 'fullscreen';
+  @Input() position: CornerPosition = 'bottom-right';
+  @Input() icon: 'success' | 'info' | 'warning' = 'success';
+  @Input() autoDismissMs = 0; // 0 表示不自动关闭
+
+  @Output() closed = new EventEmitter<void>();
+  @Output() action = new EventEmitter<void>();
+
+  ngOnChanges() {
+    if (this.visible && this.autoDismissMs > 0) {
+      window.clearTimeout((this as any)._dismissTimer);
+      (this as any)._dismissTimer = window.setTimeout(() => {
+        this.onClose();
+      }, this.autoDismissMs);
+    }
+  }
+
+  onClose() {
+    this.closed.emit();
+  }
+
+  onAction() {
+    this.action.emit();
+  }
+}

+ 20 - 1
src/app/shared/components/requirements-confirm-card/requirements-confirm-card.html

@@ -773,4 +773,23 @@
   (closeModal)="onModalClose()"
   (analyzeColors)="onAnalyzeColors()"
   (viewReport)="onViewReport()">
-</app-upload-success-modal>
+</app-upload-success-modal>
+
+<!-- 全局提示(支持全屏与角落两模式) -->
+<app-global-prompt
+  [visible]="showGlobalPrompt"
+  [title]="promptTitle"
+  [message]="promptMessage"
+  [mode]="promptMode"
+  [position]="promptPosition"
+  [autoDismissMs]="4000"
+  (closed)="onPromptClose()">
+</app-global-prompt>
+
+<!-- 完整报告全屏覆盖层 -->
+<app-full-report-overlay
+  [visible]="showFullReportOverlay"
+  [colorResult]="colorAnalysisResult"
+  [cadResult]="cadAnalysisResult"
+  (close)="onCloseFullReport()">
+</app-full-report-overlay>

+ 79 - 14
src/app/shared/components/requirements-confirm-card/requirements-confirm-card.ts

@@ -2,7 +2,10 @@ import { Component, Input, Output, EventEmitter, OnInit, OnDestroy, ChangeDetect
 import { CommonModule } from '@angular/common';
 import { FormsModule, ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
 import { UploadSuccessModalComponent } from '../upload-success-modal/upload-success-modal.component';
+import { GlobalPromptComponent } from '../global-prompt/global-prompt.component';
 import { ColorAnalysisService, ColorAnalysisResult } from '../../services/color-analysis.service';
+import { FullReportOverlayComponent } from '../full-report-overlay/full-report-overlay.component';
+import { CadAnalysisService, CadAnalysisResult } from '../../services/cad-analysis.service';
 
 // 素材文件接口
 interface MaterialFile {
@@ -75,7 +78,7 @@ interface RequirementItem {
 @Component({
   selector: 'app-requirements-confirm-card',
   standalone: true,
-  imports: [CommonModule, FormsModule, ReactiveFormsModule, UploadSuccessModalComponent],
+  imports: [CommonModule, FormsModule, ReactiveFormsModule, UploadSuccessModalComponent, GlobalPromptComponent, FullReportOverlayComponent],
   templateUrl: './requirements-confirm-card.html',
   styleUrls: ['./requirements-confirm-card.scss'],
   changeDetection: ChangeDetectionStrategy.Default // 确保使用默认变更检测策略
@@ -198,8 +201,23 @@ export class RequirementsConfirmCardComponent implements OnInit, OnDestroy {
   uploadedFiles: { id: string; name: string; url: string; size?: number; type?: 'image' | 'cad' | 'text'; preview?: string }[] = [];
   uploadType: 'image' | 'document' | 'mixed' = 'image';
   colorAnalysisResult?: ColorAnalysisResult;
-
-  constructor(private fb: FormBuilder, private cdr: ChangeDetectorRef, private colorAnalysisService: ColorAnalysisService) {}
+  cadAnalysisResult?: CadAnalysisResult;
+
+  // 全局提示组件状态
+  showGlobalPrompt = false;
+  promptMode: 'fullscreen' | 'corner' = 'corner';
+  promptPosition: 'top-right' | 'bottom-right' = 'bottom-right';
+  promptTitle = '上传成功!';
+  promptMessage = '';
+  // 全屏报告覆盖层
+  showFullReportOverlay = false;
+
+  constructor(
+    private fb: FormBuilder,
+    private cdr: ChangeDetectorRef,
+    private colorAnalysisService: ColorAnalysisService,
+    private cadAnalysisService: CadAnalysisService
+  ) {}
 
   ngOnInit() {
     this.initializeForms();
@@ -351,6 +369,13 @@ export class RequirementsConfirmCardComponent implements OnInit, OnDestroy {
       this.materials.push(materialFile);
       this.isUploading = false;
       
+      // 显示全局提示(角落模式,无遮罩,不遮挡操作)
+      this.promptTitle = '上传成功!';
+      this.promptMessage = `已上传文件:${file.name}`;
+      this.promptMode = 'corner';
+      this.promptPosition = 'bottom-right';
+      this.showGlobalPrompt = true;
+
       // 显示上传成功弹窗
       this.showUploadSuccessModal = true;
       this.uploadedFiles = [{
@@ -391,10 +416,47 @@ export class RequirementsConfirmCardComponent implements OnInit, OnDestroy {
   private analyzeMaterial(material: MaterialFile) {
     this.isAnalyzing = true;
     
-    // 模拟解析过程
+    // CAD采用服务分析,其它类型保持模拟
+    if (material.type === 'cad') {
+      const fileUrl = material.url || '';
+      this.cadAnalysisService.analyzeByUrl(fileUrl, material.name).subscribe({
+        next: (result) => {
+          this.cadAnalysisResult = result;
+          const analysis: MaterialAnalysis = {
+            structuralElements: result.structuralElements,
+            spaceMetrics: result.spaceMetrics,
+            flowMetrics: result.flowMetrics
+          };
+          material.analysis = analysis;
+          this.isAnalyzing = false;
+          this.updateRequirementsFromAnalysis(analysis);
+        },
+        error: () => {
+          this.cadAnalysisService.simulateCadAnalysis(material.name).subscribe({
+            next: (mock) => {
+              this.cadAnalysisResult = mock;
+              const analysis: MaterialAnalysis = {
+                structuralElements: mock.structuralElements,
+                spaceMetrics: mock.spaceMetrics,
+                flowMetrics: mock.flowMetrics
+              };
+              material.analysis = analysis;
+              this.isAnalyzing = false;
+              this.updateRequirementsFromAnalysis(analysis);
+            },
+            error: () => {
+              this.isAnalyzing = false;
+              console.error('CAD分析失败');
+            }
+          });
+        }
+      });
+      return;
+    }
+
+    // 非CAD类型模拟
     setTimeout(() => {
       let analysis: MaterialAnalysis = {};
-      
       switch (material.type) {
         case 'text':
           analysis = this.analyzeTextMaterial(material);
@@ -402,15 +464,11 @@ export class RequirementsConfirmCardComponent implements OnInit, OnDestroy {
         case 'image':
           analysis = this.analyzeImageMaterial(material);
           break;
-        case 'cad':
-          analysis = this.analyzeCADMaterial(material);
-          break;
       }
-      
       material.analysis = analysis;
       this.isAnalyzing = false;
       this.updateRequirementsFromAnalysis(analysis);
-    }, 2000);
+    }, 1200);
   }
 
   private analyzeTextMaterial(material: MaterialFile): MaterialAnalysis {
@@ -1437,6 +1495,11 @@ export class RequirementsConfirmCardComponent implements OnInit, OnDestroy {
     this.colorAnalysisResult = undefined;
   }
 
+  // 全局提示关闭
+  onPromptClose(): void {
+    this.showGlobalPrompt = false;
+  }
+
   onAnalyzeColors(): void {
     if (this.uploadedFiles.length > 0 && this.uploadType === 'image') {
       const imageFile = this.uploadedFiles[0];
@@ -1462,10 +1525,12 @@ export class RequirementsConfirmCardComponent implements OnInit, OnDestroy {
   }
 
   onViewReport(): void {
-    if (this.colorAnalysisResult?.reportPath) {
-      // 在新窗口中打开报告
-      window.open(this.colorAnalysisResult.reportPath, '_blank');
-    }
+    // 打开全屏报告覆盖层
+    this.showFullReportOverlay = true;
+  }
+
+  onCloseFullReport(): void {
+    this.showFullReportOverlay = false;
   }
 
   // 新增:发射实时数据更新事件的方法

+ 77 - 0
src/app/shared/services/cad-analysis.service.ts

@@ -0,0 +1,77 @@
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { Observable, of } from 'rxjs';
+import { catchError, map } from 'rxjs/operators';
+
+export interface CadStructuralElement {
+  type: string;
+  position: string;
+  changeable: boolean;
+}
+
+export interface CadSpaceMetric {
+  room: string;
+  ratio: string;
+  width: string;
+}
+
+export interface CadFlowMetric {
+  area: string;
+  width: string;
+  compliance: boolean;
+}
+
+export interface CadAnalysisResult {
+  structuralElements: CadStructuralElement[];
+  spaceMetrics: CadSpaceMetric[];
+  flowMetrics: CadFlowMetric[];
+  reportPath?: string;
+}
+
+@Injectable({ providedIn: 'root' })
+export class CadAnalysisService {
+  private readonly API_BASE = '/api/cad-analysis';
+
+  constructor(private http: HttpClient) {}
+
+  /**
+   * 通过文件URL触发后端CAD解析
+   */
+  analyzeByUrl(fileUrl: string, fileName?: string): Observable<CadAnalysisResult> {
+    const payload = { fileUrl, fileName };
+    return this.http.post<any>(`${this.API_BASE}/analyze-url`, payload).pipe(
+      map(res => {
+        if (res && res.success && res.data) {
+          return res.data as CadAnalysisResult;
+        }
+        throw new Error('CAD分析返回无效');
+      }),
+      catchError(err => {
+        console.warn('CAD分析服务不可用,回退到本地模拟:', err?.message || err);
+        return this.simulateCadAnalysis(fileName || '未知CAD文件');
+      })
+    );
+  }
+
+  /**
+   * 本地模拟CAD分析,用于开发联调前
+   */
+  simulateCadAnalysis(fileName: string): Observable<CadAnalysisResult> {
+    const mock: CadAnalysisResult = {
+      structuralElements: [
+        { type: '承重柱', position: '客厅中央', changeable: false },
+        { type: '门窗', position: '南墙', changeable: false }
+      ],
+      spaceMetrics: [
+        { room: '客厅', ratio: '16:9', width: '4.2m' },
+        { room: '卧室', ratio: '4:3', width: '3.6m' }
+      ],
+      flowMetrics: [
+        { area: '主通道', width: '1.2m', compliance: true },
+        { area: '次通道', width: '0.8m', compliance: false }
+      ],
+      reportPath: `/reports/cad/${encodeURIComponent(fileName)}.html`
+    };
+    return of(mock);
+  }
+}

+ 15 - 1
src/styles/_variables.scss

@@ -31,15 +31,29 @@ $ios-spacing-sm: 8px;
 $ios-spacing-md: 16px;
 $ios-spacing-lg: 24px;
 $ios-spacing-xl: 32px;
+$ios-spacing-xxl: 48px;
 
 // 圆角
 $ios-radius-sm: 4px;
 $ios-radius-md: 8px;
 $ios-radius-lg: 12px;
 $ios-radius-xl: 16px;
+$ios-radius-full: 50px;
+
+// 阴影
+$ios-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1);
+$ios-shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
+$ios-shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
+
+// 其他颜色
+$ios-success: #34C759;
+$ios-warning: #FF9500;
 
 // 兼容旧的变量名
 $ios-border-radius-sm: $ios-radius-sm;
 $ios-border-radius-md: $ios-radius-md;
 $ios-border-radius-lg: $ios-radius-lg;
-$ios-border-radius-xl: $ios-radius-xl;
+$ios-border-radius-xl: $ios-radius-xl;
+
+// 字体权重兼容
+$ios-font-weight-regular: $ios-font-weight-normal;