|
|
@@ -15,24 +15,23 @@
|
|
|
@if (canEdit) {
|
|
|
<div class="product-management">
|
|
|
<div class="product-header">
|
|
|
- <h3>产品设计产品 ({{ products.length }}个)</h3>
|
|
|
+ <h3>设计产品 ({{ products.length }}个空间)</h3>
|
|
|
<div class="product-actions">
|
|
|
<button class="btn-primary" (click)="generateQuotationFromProducts()">
|
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
- <path fill="currentColor" d="M447.1 96h-288C131.3 96 112 115.3 112 140.4v231.2C112 396.7 131.3 416 159.1 416h288c27.6 0 48-19.3 48-44.4V140.4C495.1 115.3 475.6 96 447.1 96zM447.1 144v192h-288V144H447.1zM336 320c17.7 0 32-14.3 32-32s-14.3-32-32-32c-11.4 0-21.4 5.9-27.1 14.9c-7.2-2.4-14.9-3.7-22.9-3.7-30.9 0-56 25.1-56 56s25.1 56 56 56c8 0 15.7-1.3 22.9-3.7C314.6 314.1 324.6 320 336 320z"/>
|
|
|
- <path fill="currentColor" d="M176 80C176 71.16 167.8 64 160 64H80C71.16 64 64 71.16 64 80s7.163 16 16 16h32L64 192C64 209.7 81.75 224 96 224s32-14.3 32-32L112 96h48C167.8 96 176 88.84 176 80z"/>
|
|
|
+ <path fill="currentColor" d="M447.1 96h-288C131.3 96 112 115.3 112 140.4v231.2C112 396.7 131.3 416 159.1 416h288c27.6 0 48-19.3 48-44.4V140.4C495.1 115.3 475.6 96 447.1 96zM447.1 144v192h-288V144H447.1z"/>
|
|
|
</svg>
|
|
|
生成报价
|
|
|
</button>
|
|
|
- <button class="btn-secondary" (click)="addProduct()">
|
|
|
+ <button class="btn-secondary" (click)="openAddProductModal()">
|
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
- <path fill="currentColor" d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM256 48c114.7 0 208 93.31 208 208s-93.31 208-208 208S48 370.7 48 256S141.3 48 256 48zM256 336c13.25 0 24-10.75 24-24V280h32c13.25 0 24-10.75 24-24s-10.75-24-24-24h-32V176c0-13.25-10.75-24-24-24s-24 10.75-24 24v32H200c-13.25 0-24 10.75-24 24s10.75 24 24 24h32v32C232 325.3 242.8 336 256 336z"/>
|
|
|
+ <path fill="currentColor" d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM256 48c114.7 0 208 93.31 208 208s-93.31 208-208 208S48 370.7 48 256S141.3 48 256 48zM256 336c13.25 0 24-10.75 24-24V280h32c13.25 0 24-10.75 24-24s-10.75-24-24-24h-32V176c0-13.25-10.75-24-24-24s-24 10.75-24 24v56H200c-13.25 0-24 10.75-24 24s10.75 24 24 24h32v32C232 325.3 242.8 336 256 336z"/>
|
|
|
</svg>
|
|
|
添加产品
|
|
|
</button>
|
|
|
<button class="btn-outline" (click)="saveQuotation()">
|
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
- <path fill="currentColor" d="M504.1 141C490.6 121.4 471.1 107.5 447.8 96C424.6 84.51 400.8 80 376.1 80H136c-24.74 0-48.48 4.511-71.79 16.01C40.88 107.5 21.36 121.4 7.85 141C-5.654 160.6-1.466 180.2 11.66 195.7L144.1 353c11.14 13.4 27.62 21 44.8 21h124.3c17.18 0 33.66-7.6 44.8-21l133.3-157.4C504.5 180.2 508.6 160.6 504.1 141zM434.1 165.6L300.7 322.1c-3.734 4.498-9.291 7.059-15.16 7.059H226.5c-5.871 0-11.43-2.561-15.16-7.059L77.86 165.6C72.16 158.7 70.54 149.7 73.65 141.3C76.77 132.9 83.98 126.5 92.95 123.4C107.3 118.7 122.4 116 137.7 116h236.5c15.28 0 30.43 2.687 44.77 7.393c8.972 3.104 16.18 9.516 19.3 17.94C441.5 149.7 439.8 158.7 434.1 165.6z"/>
|
|
|
+ <path fill="currentColor" d="M504.1 141C490.6 121.4 471.1 107.5 447.8 96C424.6 84.51 400.8 80 376.1 80H136c-24.74 0-48.48 4.511-71.79 16.01C40.88 107.5 21.36 121.4 7.85 141C-5.654 160.6-1.466 180.2 11.66 195.7L144.1 353c11.14 13.4 27.62 21 44.8 21h124.3c17.18 0 33.66-7.6 44.8-21l133.3-157.4C504.5 180.2 508.6 160.6 504.1 141z"/>
|
|
|
</svg>
|
|
|
保存报价
|
|
|
</button>
|
|
|
@@ -45,10 +44,10 @@
|
|
|
<!-- 空状态 - 有产品但未生成报价 -->
|
|
|
<div class="empty-state">
|
|
|
<svg class="icon empty-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
- <path fill="currentColor" d="M447.1 96h-288C131.3 96 112 115.3 112 140.4v231.2C112 396.7 131.3 416 159.1 416h288c27.6 0 48-19.3 48-44.4V140.4C495.1 115.3 475.6 96 447.1 96zM447.1 144v192h-288V144H447.1zM336 320c17.7 0 32-14.3 32-32s-14.3-32-32-32c-11.4 0-21.4 5.9-27.1 14.9c-7.2-2.4-14.9-3.7-22.9-3.7-30.9 0-56 25.1-56 56s25.1 56 56 56c8 0 15.7-1.3 22.9-3.7C314.6 314.1 324.6 320 336 320z"/>
|
|
|
+ <path fill="currentColor" d="M447.1 96h-288C131.3 96 112 115.3 112 140.4v231.2C112 396.7 131.3 416 159.1 416h288c27.6 0 48-19.3 48-44.4V140.4C495.1 115.3 475.6 96 447.1 96zM447.1 144v192h-288V144H447.1z"/>
|
|
|
</svg>
|
|
|
<p class="empty-message">尚未生成报价</p>
|
|
|
- <p class="empty-hint">已加载 {{ products.length }} 个产品设计产品,请点击"生成报价"按钮</p>
|
|
|
+ <p class="empty-hint">已加载 {{ products.length }} 个设计空间,请点击"生成报价"按钮</p>
|
|
|
@if (canEdit) {
|
|
|
<button class="btn-primary" (click)="generateQuotationFromProducts()">立即生成报价</button>
|
|
|
}
|
|
|
@@ -57,305 +56,504 @@
|
|
|
<!-- 完全空状态 - 无产品 -->
|
|
|
<div class="empty-state">
|
|
|
<svg class="icon empty-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
- <path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 464c-114.7 0-208-93.31-208-208S141.3 48 256 48s208 93.31 208 208S370.7 464 256 464zM256 224c-17.67 0-32 14.33-32 32c0 17.67 14.33 32 32 32s32-14.33 32-32C288 238.3 273.7 224 256 224zM320 128H192c-17.67 0-32 14.33-32 32v32c0 17.67 14.33 32 32 32h128c17.67 0 32-14.33 32-32v-32C352 142.3 337.7 128 320 128z"/>
|
|
|
+ <path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 464c-114.7 0-208-93.31-208-208S141.3 48 256 48s208 93.31 208 208S370.7 464 256 464z"/>
|
|
|
</svg>
|
|
|
- <p class="empty-message">暂无产品设计产品</p>
|
|
|
- <p class="empty-hint">该项目还没有创建任何产品设计产品</p>
|
|
|
+ <p class="empty-message">暂无设计产品</p>
|
|
|
+ <p class="empty-hint">该项目还没有创建任何设计产品</p>
|
|
|
@if (canEdit) {
|
|
|
- <button class="btn-primary" (click)="addProduct()">创建第一个产品</button>
|
|
|
+ <button class="btn-primary" (click)="openAddProductModal()">创建第一个产品</button>
|
|
|
}
|
|
|
</div>
|
|
|
} @else {
|
|
|
<!-- 报价工具栏 -->
|
|
|
- <div class="quotation-toolbar">
|
|
|
- <div class="toolbar-left">
|
|
|
- <h4 class="toolbar-title">报价明细 ({{ quotation.spaces.length }}个产品设计产品)</h4>
|
|
|
- <div class="toolbar-meta">
|
|
|
- @if (quotation.generatedAt) {
|
|
|
- <span class="generate-time">生成于: {{ quotation.generatedAt | date:'MM-dd HH:mm' }}</span>
|
|
|
- }
|
|
|
- @if (quotation.validUntil) {
|
|
|
- <span class="valid-until">有效期至: {{ quotation.validUntil | date:'yyyy-MM-dd' }}</span>
|
|
|
- }
|
|
|
+ <div class="quotation-toolbar">
|
|
|
+ <div class="toolbar-left">
|
|
|
+ <h4 class="toolbar-title">报价明细 ({{ quotation.spaces.length }}个设计空间)</h4>
|
|
|
+ <div class="toolbar-meta">
|
|
|
+ @if (quotation.generatedAt) {
|
|
|
+ <span class="generate-time">生成于: {{ quotation.generatedAt | date:'MM-dd HH:mm' }}</span>
|
|
|
+ }
|
|
|
+ @if (quotation.validUntil) {
|
|
|
+ <span class="valid-until">有效期至: {{ quotation.validUntil | date:'yyyy-MM-dd' }}</span>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="toolbar-right">
|
|
|
+ <button class="btn-icon" (click)="expandAll()" title="展开全部">
|
|
|
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
+ <path fill="currentColor" d="M112 184l144 144 144-144M256 328V88"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <button class="btn-icon" (click)="collapseAll()" title="折叠全部">
|
|
|
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
+ <path fill="currentColor" d="M112 328l144-144 144 144M256 184v240"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
</div>
|
|
|
</div>
|
|
|
- <div class="toolbar-right">
|
|
|
- <button class="btn-icon" (click)="expandAll()" title="展开全部">
|
|
|
- <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
- <path fill="currentColor" d="M112 184l144 144 144-144M256 328V88"/>
|
|
|
- </svg>
|
|
|
- </button>
|
|
|
- <button class="btn-icon" (click)="collapseAll()" title="折叠全部">
|
|
|
- <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
- <path fill="currentColor" d="M112 328l144-144 144 144M256 184v240"/>
|
|
|
- </svg>
|
|
|
- </button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
|
|
|
- <!-- 卡片视图 -->
|
|
|
- @if (viewMode === 'card') {
|
|
|
- <div class="quotation-products">
|
|
|
- @for (space of quotation.spaces; track space.name) {
|
|
|
- <div class="product-card" [class.expanded]="isProductExpanded(space.name)">
|
|
|
- <!-- 产品头部 -->
|
|
|
- <div class="product-header" (click)="toggleProductExpand(space.name)">
|
|
|
- <div class="product-info">
|
|
|
- <div class="product-title">
|
|
|
- <div class="product-icon">
|
|
|
- <i class="icon-{{ getProductIconForSpace(space.name) }}"></i>
|
|
|
- </div>
|
|
|
- <div class="product-details">
|
|
|
- <h3 class="product-name">{{ space.name }}</h3>
|
|
|
- <div class="product-meta">
|
|
|
- <span class="badge" [attr.data-color]="getStatusColorForSpace(space.productId)">
|
|
|
- {{ getStatusTextForSpace(space.productId) }}
|
|
|
- </span>
|
|
|
- <span class="designer-name">{{ getDesignerNameForSpace(space.productId) }}</span>
|
|
|
+ <!-- 卡片视图 -->
|
|
|
+ @if (viewMode === 'card') {
|
|
|
+ <div class="quotation-products">
|
|
|
+ @for (space of quotation.spaces; track space.name) {
|
|
|
+ <div class="product-card" [class.expanded]="isProductExpanded(space.name)">
|
|
|
+ <!-- 产品头部 -->
|
|
|
+ <div class="product-header" (click)="toggleProductExpand(space.name)">
|
|
|
+ <div class="product-info">
|
|
|
+ <div class="product-title">
|
|
|
+ <div class="product-icon">
|
|
|
+ <i class="icon-{{ getProductIconForSpace(space.name) }}"></i>
|
|
|
+ </div>
|
|
|
+ <div class="product-details">
|
|
|
+ <h3 class="product-name">{{ space.name }}</h3>
|
|
|
+ <div class="product-meta">
|
|
|
+ <span class="badge" [attr.data-color]="getStatusColorForSpace(space.productId)">
|
|
|
+ {{ getStatusTextForSpace(space.productId) }}
|
|
|
+ </span>
|
|
|
+ <span class="designer-name">{{ getDesignerNameForSpace(space.productId) }}</span>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
+ <div class="product-pricing">
|
|
|
+ <p class="product-subtotal">{{ formatPrice(calculateSpaceSubtotal(space)) }}</p>
|
|
|
+ @if (quotation.spaceBreakdown?.length > 1) {
|
|
|
+ <span class="percentage">{{ formatPercentage(getSpacePercentage(space.productId)) }}</span>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- <div class="product-pricing">
|
|
|
- <p class="product-subtotal">{{ formatPrice(calculateSpaceSubtotal(space)) }}</p>
|
|
|
- @if (quotation.spaceBreakdown?.length > 1) {
|
|
|
- <span class="percentage">{{ formatPercentage(getSpacePercentage(space.productId)) }}</span>
|
|
|
+ <div class="product-actions">
|
|
|
+ @if (canEdit) {
|
|
|
+ <button class="btn-icon" (click)="openEditProductModal(space.productId); $event.stopPropagation()" title="编辑">
|
|
|
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
+ <path fill="currentColor" d="M362.7 19.32C387.7-5.678 428.3-5.678 453.3 19.32l39.38 39.38c25 25 25 65.62 0 90.62l-109.5 109.5c-25 25-65.62 25-90.62 0l-109.5-109.5c-25-25-25-65.62 0-90.62L272.1 19.32z"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <button class="btn-icon danger" (click)="deleteProduct(space.productId); $event.stopPropagation()" title="删除">
|
|
|
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
+ <path fill="currentColor" d="M96 480c0 17.67 14.33 32 32 32h256c17.67 0 32-14.33 32-32V128H96V480zM312 192c13.25 0 24 10.75 24 24v208c0 13.25-10.75 24-24 24c-13.25 0-24-10.75-24-24V216C288 202.8 298.8 192 312 192zM200 192c13.25 0 24 10.75 24 24v208c0 13.25-10.75 24-24 24s-24-10.75-24-24V216C176 202.8 186.8 192 200 192z"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
}
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="product-actions">
|
|
|
- @if (canEdit) {
|
|
|
- <button class="btn-icon" (click)="editProduct(space.productId); $event.stopPropagation()" title="编辑">
|
|
|
+ <div class="product-toggle">
|
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
- <path fill="currentColor" d="M362.7 19.32C387.7-5.678 428.3-5.678 453.3 19.32l39.38 39.38c25 25 25 65.62 0 90.62l-109.5 109.5c-25 25-65.62 25-90.62 0l-109.5-109.5c-25-25-25-65.62 0-90.62L272.1 19.32zM336.5 256.1L227.1 365.5c-12.5 12.5-32.75 12.5-45.25 0s-12.5-32.75 0-45.25l109.5-109.5c12.5-12.5 32.75-12.5 45.25 0S349 243.6 336.5 256.1zM192 416h64v64h-64V416z"/>
|
|
|
+ <path fill="currentColor" d="M256 294.1L383 167c9.4-9.4 24.6-9.4 33.9 0s9.3 24.6 0 34L273 345c-9.1 9.1-23.7 9.3-33.1.7L95 201.1c-4.7-4.7-7-10.9-7-17s2.3-12.3 7-17c9.4-9.4 24.6-9.4 33.9 0l127.1 127z"/>
|
|
|
</svg>
|
|
|
- </button>
|
|
|
- <button class="btn-icon danger" (click)="deleteProduct(space.productId); $event.stopPropagation()" title="删除">
|
|
|
- <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
- <path fill="currentColor" d="M96 480c0 17.67 14.33 32 32 32h256c17.67 0 32-14.33 32-32V128H96V480zM312 192c13.25 0 24 10.75 24 24v208c0 13.25-10.75 24-24 24c-13.25 0-24-10.75-24-24V216C288 202.8 298.8 192 312 192zM200 192c13.25 0 24 10.75 24 24v208c0 13.25-10.75 24-24 24s-24-10.75-24-24V216C176 202.8 186.8 192 200 192zM472 64h-80V48c0-26.51-21.49-48-48-48h-176C141.5 0 120 21.49 120 48v64H48c-17.67 0-32 14.33-32 32s14.33 32 32 32h32v352c0 26.51 21.49 48 48 48h256c26.51 0 48-21.49 48-48V128h32c17.67 0 32-14.33 32-32S489.7 64 472 64zM168 48h176v16H168V48zm232 400H112V128h288V448z"/>
|
|
|
- </svg>
|
|
|
- </button>
|
|
|
- }
|
|
|
- <div class="product-toggle">
|
|
|
- <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
- <path fill="currentColor" d="M256 294.1L383 167c9.4-9.4 24.6-9.4 33.9 0s9.3 24.6 0 34L273 345c-9.1 9.1-23.7 9.3-33.1.7L95 201.1c-4.7-4.7-7-10.9-7-17s2.3-12.3 7-17c9.4-9.4 24.6-9.4 33.9 0l127.1 127z"/>
|
|
|
- </svg>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
|
- <!-- 产品详情 -->
|
|
|
- @if (isProductExpanded(space.name)) {
|
|
|
- <div class="product-content">
|
|
|
- <!-- 产品信息 -->
|
|
|
- @if (getProductForSpace(space.productId)) {
|
|
|
- <div class="product-details-section">
|
|
|
- <div class="detail-item">
|
|
|
- <span class="detail-label">产品类型:</span>
|
|
|
- <span class="detail-value">{{ getProductForSpace(space.productId)?.get('productType') }}</span>
|
|
|
- </div>
|
|
|
- <div class="detail-item">
|
|
|
- <span class="detail-label">空间面积:</span>
|
|
|
- <span class="detail-value">{{ getProductForSpace(space.productId)?.get('space')?.area || 0 }}㎡</span>
|
|
|
- </div>
|
|
|
- <div class="detail-item">
|
|
|
- <span class="detail-label">复杂度:</span>
|
|
|
- <span class="detail-value">{{ getProductForSpace(space.productId)?.get('space')?.complexity || 'medium' }}</span>
|
|
|
- </div>
|
|
|
- <div class="detail-item">
|
|
|
- <span class="detail-label">基础报价:</span>
|
|
|
- <span class="detail-value">{{ formatPrice(getProductForSpace(space.productId)?.get('quotation')?.price || 0) }}</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- }
|
|
|
-
|
|
|
- <!-- 工序网格 -->
|
|
|
- <div class="process-grid">
|
|
|
- @for (processType of processTypes; track processType.key) {
|
|
|
- <div
|
|
|
- class="process-item"
|
|
|
- [class.enabled]="isProcessEnabled(space, processType.key)">
|
|
|
- <div class="process-header" (click)="canEdit && toggleProcess(space, processType.key)">
|
|
|
- <label class="checkbox-wrapper">
|
|
|
- <input
|
|
|
- type="checkbox"
|
|
|
- class="checkbox-input"
|
|
|
- [checked]="isProcessEnabled(space, processType.key)"
|
|
|
- [disabled]="!canEdit" />
|
|
|
- <span class="checkbox-custom"></span>
|
|
|
- </label>
|
|
|
- <span class="badge" [attr.data-color]="processType.color">
|
|
|
- {{ processType.name }}
|
|
|
- </span>
|
|
|
+ <!-- 产品详情 -->
|
|
|
+ @if (isProductExpanded(space.name)) {
|
|
|
+ <div class="product-content">
|
|
|
+ <!-- 产品信息 -->
|
|
|
+ @if (getProductForSpace(space.productId)) {
|
|
|
+ <div class="product-details-section">
|
|
|
+ <h5 class="section-title">产品信息</h5>
|
|
|
+ <div class="detail-grid">
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="detail-label">产品类型:</span>
|
|
|
+ <span class="detail-value">{{ getProductForSpace(space.productId)?.get('productType') }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="detail-label">空间面积:</span>
|
|
|
+ <span class="detail-value">{{ getProductForSpace(space.productId)?.get('space')?.area || 0 }}㎡</span>
|
|
|
+ </div>
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="detail-label">复杂度:</span>
|
|
|
+ <span class="detail-value">{{ getProductForSpace(space.productId)?.get('space')?.complexity || 'medium' }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="detail-label">基础报价:</span>
|
|
|
+ <span class="detail-value price">{{ formatPrice(getProductForSpace(space.productId)?.get('quotation')?.basePrice || 0) }}</span>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
|
|
|
- @if (isProcessEnabled(space, processType.key)) {
|
|
|
- <div class="process-inputs">
|
|
|
- <div class="input-group">
|
|
|
- <label class="input-label">单价</label>
|
|
|
- <div class="input-with-note">
|
|
|
- <input
|
|
|
- class="input-field"
|
|
|
- type="number"
|
|
|
- [ngModel]="getProcessPrice(space, processType.key)"
|
|
|
- (ngModelChange)="setProcessPrice(space, processType.key, $event); onProcessChange()"
|
|
|
- [disabled]="!canEdit"
|
|
|
- placeholder="0" />
|
|
|
- <span class="input-note">元/{{ getProcessUnit(space, processType.key) }}</span>
|
|
|
+ <!-- 内部分配明细(3个分配:建模阶段10%、软装渲染40%、公司分配50%) -->
|
|
|
+ <div class="allocation-section-detail">
|
|
|
+ <h5 class="section-title">内部执行分配 <span class="section-subtitle">(基于设计图总价自动分配)</span></h5>
|
|
|
+ <div class="allocation-grid-detail">
|
|
|
+ @for (allocationType of allocationTypes; track allocationType.key) {
|
|
|
+ <div
|
|
|
+ class="allocation-item-detail"
|
|
|
+ [class.enabled]="isProcessEnabled(space, allocationType.key)"
|
|
|
+ [attr.data-type]="allocationType.key">
|
|
|
+ <div class="allocation-header-detail">
|
|
|
+ <div class="allocation-left">
|
|
|
+ <label class="checkbox-wrapper">
|
|
|
+ <input
|
|
|
+ type="checkbox"
|
|
|
+ class="checkbox-input"
|
|
|
+ [checked]="isProcessEnabled(space, allocationType.key)"
|
|
|
+ (change)="canEdit && toggleProcess(space, allocationType.key)"
|
|
|
+ [disabled]="!canEdit" />
|
|
|
+ <span class="checkbox-custom"></span>
|
|
|
+ </label>
|
|
|
+ <div class="allocation-info-detail">
|
|
|
+ <span class="allocation-name-detail">{{ allocationType.name }}</span>
|
|
|
+ <span class="allocation-desc-detail">{{ allocationType.description }}</span>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
+ <span class="allocation-percentage-badge">{{ allocationType.percentage }}%</span>
|
|
|
</div>
|
|
|
|
|
|
- <div class="input-group">
|
|
|
- <label class="input-label">数量</label>
|
|
|
- <div class="input-with-note">
|
|
|
- <input
|
|
|
- class="input-field"
|
|
|
- type="number"
|
|
|
- [ngModel]="getProcessQuantity(space, processType.key)"
|
|
|
- (ngModelChange)="setProcessQuantity(space, processType.key, $event); onProcessChange()"
|
|
|
- [disabled]="!canEdit"
|
|
|
- placeholder="0" />
|
|
|
- <span class="input-note">{{ getProcessUnit(space, processType.key) }}</span>
|
|
|
+ @if (isProcessEnabled(space, allocationType.key)) {
|
|
|
+ <div class="allocation-input-section">
|
|
|
+ <label class="input-label-small">分配金额(可编辑)</label>
|
|
|
+ <div class="input-with-currency">
|
|
|
+ <span class="currency-symbol">¥</span>
|
|
|
+ <input
|
|
|
+ class="input-field amount-input"
|
|
|
+ type="number"
|
|
|
+ [ngModel]="getAllocationAmount(space, allocationType.key)"
|
|
|
+ (ngModelChange)="setAllocationAmount(space, allocationType.key, $event); onProcessChange()"
|
|
|
+ [disabled]="!canEdit"
|
|
|
+ placeholder="0" />
|
|
|
+ </div>
|
|
|
+ <div class="allocation-hint">
|
|
|
+ 建议金额: {{ forSpacePrice(space,allocationType) }}
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="process-subtotal">
|
|
|
- 小计: ¥{{ calculateProcessSubtotal(space, processType.key).toFixed(2) }}
|
|
|
- </div>
|
|
|
+ }
|
|
|
</div>
|
|
|
}
|
|
|
</div>
|
|
|
- }
|
|
|
+ </div>
|
|
|
+
|
|
|
</div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+
|
|
|
+ <!-- 报价汇总 -->
|
|
|
+ <div class="quotation-summary">
|
|
|
+ <div class="summary-header">
|
|
|
+ <h4>报价汇总</h4>
|
|
|
+ @if (quotation.spaceBreakdown?.length > 1) {
|
|
|
+ <div class="breakdown-toggle">
|
|
|
+ <button class="btn-text" (click)="showBreakdown = !showBreakdown">
|
|
|
+ {{ showBreakdown ? '隐藏' : '显示'}}明细
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+
|
|
|
+ @if (quotation.spaceBreakdown?.length > 1 && showBreakdown) {
|
|
|
+ <div class="breakdown-list">
|
|
|
+ @for (item of quotation.spaceBreakdown; track item.spaceId) {
|
|
|
+ <div class="breakdown-item">
|
|
|
+ <span class="breakdown-name">{{ item.spaceName }}</span>
|
|
|
+ <span class="breakdown-amount">{{ formatPrice(item.amount) }}</span>
|
|
|
+ <span class="breakdown-percentage">{{ formatPercentage(item.percentage) }}</span>
|
|
|
</div>
|
|
|
}
|
|
|
</div>
|
|
|
}
|
|
|
- </div>
|
|
|
- }
|
|
|
|
|
|
- <!-- 表格视图 -->
|
|
|
- @if (viewMode === 'table') {
|
|
|
- <div class="quotation-table">
|
|
|
- @for (space of quotation.spaces; track space.name) {
|
|
|
- <div class="table-section">
|
|
|
- <div class="table-header">
|
|
|
- <h3 class="table-space-name">{{ space.name }}</h3>
|
|
|
- <span class="table-space-subtotal">小计: ¥{{ calculateSpaceSubtotal(space).toFixed(2) }}</span>
|
|
|
+ <!-- 内部执行分配 -->
|
|
|
+ @if (quotation.allocation) {
|
|
|
+ <div class="allocation-section">
|
|
|
+ <div class="allocation-header">
|
|
|
+ <h4>内部执行分配</h4>
|
|
|
+ <button class="btn-text" (click)="toggleAllocation()">
|
|
|
+ {{ showAllocation ? '隐藏' : '显示' }}
|
|
|
+ </button>
|
|
|
</div>
|
|
|
- <table class="process-table">
|
|
|
- <thead>
|
|
|
- <tr>
|
|
|
- <th class="col-checkbox"></th>
|
|
|
- <th class="col-process">工序</th>
|
|
|
- <th class="col-price">单价(元)</th>
|
|
|
- <th class="col-quantity">数量</th>
|
|
|
- <th class="col-unit">单位</th>
|
|
|
- <th class="col-subtotal">小计(元)</th>
|
|
|
- </tr>
|
|
|
- </thead>
|
|
|
- <tbody>
|
|
|
- @for (processType of processTypes; track processType.key) {
|
|
|
- <tr [class.enabled]="isProcessEnabled(space, processType.key)">
|
|
|
- <td class="col-checkbox">
|
|
|
- <label class="checkbox-wrapper">
|
|
|
- <input
|
|
|
- type="checkbox"
|
|
|
- [checked]="isProcessEnabled(space, processType.key)"
|
|
|
- (change)="canEdit && toggleProcess(space, processType.key)"
|
|
|
- [disabled]="!canEdit" />
|
|
|
- <span class="checkbox-custom"></span>
|
|
|
- </label>
|
|
|
- </td>
|
|
|
- <td class="col-process">
|
|
|
- <span class="badge" [attr.data-color]="processType.color">
|
|
|
- {{ processType.name }}
|
|
|
- </span>
|
|
|
- </td>
|
|
|
- <td class="col-price">
|
|
|
- @if (isProcessEnabled(space, processType.key)) {
|
|
|
- <input
|
|
|
- class="table-input"
|
|
|
- type="number"
|
|
|
- [ngModel]="getProcessPrice(space, processType.key)"
|
|
|
- (ngModelChange)="setProcessPrice(space, processType.key, $event); onProcessChange()"
|
|
|
- [disabled]="!canEdit"
|
|
|
- placeholder="0" />
|
|
|
- } @else {
|
|
|
- <span class="disabled-value">-</span>
|
|
|
- }
|
|
|
- </td>
|
|
|
- <td class="col-quantity">
|
|
|
- @if (isProcessEnabled(space, processType.key)) {
|
|
|
- <input
|
|
|
- class="table-input"
|
|
|
- type="number"
|
|
|
- [ngModel]="getProcessQuantity(space, processType.key)"
|
|
|
- (ngModelChange)="setProcessQuantity(space, processType.key, $event); onProcessChange()"
|
|
|
- [disabled]="!canEdit"
|
|
|
- placeholder="0" />
|
|
|
- } @else {
|
|
|
- <span class="disabled-value">-</span>
|
|
|
- }
|
|
|
- </td>
|
|
|
- <td class="col-unit">
|
|
|
- {{ getProcessUnit(space, processType.key) || '-' }}
|
|
|
- </td>
|
|
|
- <td class="col-subtotal">
|
|
|
- @if (isProcessEnabled(space, processType.key)) {
|
|
|
- <strong>¥{{ calculateProcessSubtotal(space, processType.key).toFixed(2) }}</strong>
|
|
|
- } @else {
|
|
|
- <span class="disabled-value">-</span>
|
|
|
- }
|
|
|
- </td>
|
|
|
- </tr>
|
|
|
- }
|
|
|
- </tbody>
|
|
|
- </table>
|
|
|
+
|
|
|
+ @if (showAllocation) {
|
|
|
+ <div class="allocation-list">
|
|
|
+ <!-- 建模阶段 -->
|
|
|
+ <div class="allocation-item modeling">
|
|
|
+ <div class="allocation-icon">
|
|
|
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
+ <path fill="currentColor" d="M234.5 5.709C248.4 .7377 263.6 .7377 277.5 5.709L469.5 74.28C494.1 83.38 512 107.5 512 134.6C512 161.7 494.1 185.8 469.5 194.9L277.5 263.5C263.6 268.5 248.4 268.5 234.5 263.5L42.47 194.9C17.05 185.8 0 161.7 0 134.6C0 107.5 17.05 83.38 42.47 74.28L234.5 5.709z"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="allocation-info">
|
|
|
+ <span class="allocation-name">{{ allocationRules.modeling.label }}</span>
|
|
|
+ <span class="allocation-desc">{{ allocationRules.modeling.description }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="allocation-values">
|
|
|
+ <span class="allocation-percentage">{{ quotation.allocation.modeling.percentage }}%</span>
|
|
|
+ <span class="allocation-amount">{{ formatPrice(quotation.allocation.modeling.amount) }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 软装渲染 -->
|
|
|
+ <div class="allocation-item decoration">
|
|
|
+ <div class="allocation-icon">
|
|
|
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
+ <path fill="currentColor" d="M384 160C366.3 160 352 145.7 352 128C352 110.3 366.3 96 384 96C401.7 96 416 110.3 416 128C416 145.7 401.7 160 384 160z"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="allocation-info">
|
|
|
+ <span class="allocation-name">{{ allocationRules.decoration.label }}</span>
|
|
|
+ <span class="allocation-desc">{{ allocationRules.decoration.description }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="allocation-values">
|
|
|
+ <span class="allocation-percentage">{{ quotation.allocation.decoration.percentage }}%</span>
|
|
|
+ <span class="allocation-amount">{{ formatPrice(quotation.allocation.decoration.amount) }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 公司分配 -->
|
|
|
+ <div class="allocation-item company">
|
|
|
+ <div class="allocation-icon">
|
|
|
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
+ <path fill="currentColor" d="M320 32c-8.1 0-16.1 1.4-23.7 4.1L15.8 137.4C6.3 140.9 0 149.9 0 160s6.3 19.1 15.8 22.6l57.9 20.9C57.3 229.3 48 259.8 48 291.9v28.1c0 28.4-10.8 57.7-22.3 80.8c-6.5 13-13.9 25.8-22.5 37.6C0 442.7-.9 448.3 .9 453.4s6 8.9 11.2 10.2l64 16c4.2 1.1 8.7 .3 12.4-2s6.3-6.1 7.1-10.4c8.6-42.8 4.3-81.2-2.1-108.7C90.3 344.3 86 329.8 80 316.5V295.6c0-4.2 .3-8.4 1-12.4L288.1 406.6c8.4 3.2 17.4 4.8 26.6 4.8H448c17.7 0 32-14.3 32-32V256c0-17.7-14.3-32-32-32H448c-17.7 0-32-14.3-32-32s14.3-32 32-32h32c9.7 0 18.5-5.8 22.2-14.8s1.7-19.3-5.2-26.2l-64-64c-12.5-12.5-32.8-12.5-45.3 0l-64 64c-6.9 6.9-8.9 17.2-5.2 26.2s12.5 14.8 22.2 14.8h32c17.7 0 32 14.3 32 32s-14.3 32-32 32H320z"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="allocation-info">
|
|
|
+ <span class="allocation-name">{{ allocationRules.company.label }}</span>
|
|
|
+ <span class="allocation-desc">{{ allocationRules.company.description }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="allocation-values">
|
|
|
+ <span class="allocation-percentage">{{ quotation.allocation.company.percentage }}%</span>
|
|
|
+ <span class="allocation-amount">{{ formatPrice(quotation.allocation.company.amount) }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="allocation-note">
|
|
|
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
+ <path fill="currentColor" d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24z"/>
|
|
|
+ </svg>
|
|
|
+ <span>内部执行分配为系统自动计算,基于报价总额按固定比例拆分,所有金额均为整数</span>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
</div>
|
|
|
}
|
|
|
+
|
|
|
+ <div class="total-section">
|
|
|
+ <div class="total-row">
|
|
|
+ <span class="total-label">报价总额</span>
|
|
|
+ <span class="total-amount">{{ formatPrice(quotation.total) }}</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ @if (quotation.generatedAt) {
|
|
|
+ <div class="total-meta">
|
|
|
+ <span class="generate-info">生成于 {{ quotation.generatedAt | date:'yyyy-MM-dd HH:mm' }}</span>
|
|
|
+ @if (quotation.validUntil) {
|
|
|
+ <span class="valid-info">有效期至 {{ quotation.validUntil | date:'yyyy-MM-dd' }}</span>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
</div>
|
|
|
}
|
|
|
+ }
|
|
|
+</div>
|
|
|
|
|
|
- <!-- 报价汇总 -->
|
|
|
- <div class="quotation-summary">
|
|
|
- <div class="summary-header">
|
|
|
- <h4>报价汇总</h4>
|
|
|
- @if (quotation.spaceBreakdown?.length > 1) {
|
|
|
- <div class="breakdown-toggle">
|
|
|
- <button class="btn-text" (click)="showBreakdown = !showBreakdown">
|
|
|
- {{ showBreakdown ? '隐藏' : '显示'}}明细
|
|
|
+<!-- 产品添加/编辑模态框 -->
|
|
|
+@if (showAddProductModal) {
|
|
|
+ <div class="modal-overlay" (click)="closeAddProductModal()">
|
|
|
+ <div class="modal-container add-product-modal" (click)="$event.stopPropagation()">
|
|
|
+ <div class="modal-header">
|
|
|
+ <h3>{{ isEditMode ? '编辑设计产品' : '添加设计产品' }}</h3>
|
|
|
+ <button class="close-btn" (click)="closeAddProductModal()">
|
|
|
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
|
+ <path fill="currentColor" d="M256 48C141.1 48 48 141.1 48 256s93.1 208 208 208 208-93.1 208-208S370.9 48 256 48zm52.7 283.3L256 278.6l-52.7 52.7c-6.2 6.2-16.4 6.2-22.6 0-3.1-3.1-4.7-7.2-4.7-11.3 0-4.1 1.6-8.2 4.7-11.3l52.7-52.7-52.7-52.7c-3.1-3.1-4.7-7.2-4.7-11.3 0-4.1 1.6-8.2 4.7-11.3 6.2-6.2 16.4-6.2 22.6 0l52.7 52.7 52.7-52.7c6.2-6.2 16.4-6.2 22.6 0 6.2 6.2 6.2 16.4 0 22.6L278.6 256l52.7 52.7c6.2 6.2 6.2 16.4 0 22.6-6.2 6.3-16.4 6.3-22.6 0z"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="modal-body">
|
|
|
+ <!-- 预设场景选择 -->
|
|
|
+ <div class="form-group">
|
|
|
+ <label class="form-label">选择空间场景</label>
|
|
|
+ <div class="scene-grid">
|
|
|
+ @for (scene of getPresetScenes(); track scene) {
|
|
|
+ <button
|
|
|
+ class="scene-card"
|
|
|
+ [class.selected]="newProduct.sceneName === scene"
|
|
|
+ (click)="selectScene(scene)">
|
|
|
+ <span class="scene-name">{{ scene }}</span>
|
|
|
+ </button>
|
|
|
+ }
|
|
|
+ <button
|
|
|
+ class="scene-card custom"
|
|
|
+ [class.selected]="newProduct.isCustom"
|
|
|
+ (click)="selectCustomScene()">
|
|
|
+ <span class="scene-name">自定义</span>
|
|
|
</button>
|
|
|
</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 自定义名称 -->
|
|
|
+ @if (newProduct.isCustom) {
|
|
|
+ <div class="form-group">
|
|
|
+ <label class="form-label">产品名称</label>
|
|
|
+ <input
|
|
|
+ type="text"
|
|
|
+ [(ngModel)]="newProduct.productName"
|
|
|
+ placeholder="例如:客厅、主卧、大堂等"
|
|
|
+ class="form-input">
|
|
|
+ </div>
|
|
|
}
|
|
|
- </div>
|
|
|
|
|
|
- @if (quotation.spaceBreakdown?.length > 1 && showBreakdown) {
|
|
|
- <div class="breakdown-list">
|
|
|
- @for (item of quotation.spaceBreakdown; track item.spaceId) {
|
|
|
- <div class="breakdown-item">
|
|
|
- <span class="breakdown-name">{{ item.spaceName }}</span>
|
|
|
- <span class="breakdown-amount">{{ formatPrice(item.amount) }}</span>
|
|
|
- <span class="breakdown-percentage">{{ formatPercentage(item.percentage) }}</span>
|
|
|
+ <!-- 家装专属配置 -->
|
|
|
+ @if (projectInfo.projectType === '家装') {
|
|
|
+ <div class="form-group">
|
|
|
+ <label class="form-label">空间类型</label>
|
|
|
+ <div class="radio-group">
|
|
|
+ <label class="radio-label">
|
|
|
+ <input type="radio" name="spaceType" value="平层" [(ngModel)]="newProduct.spaceType">
|
|
|
+ <span>平层</span>
|
|
|
+ </label>
|
|
|
+ <label class="radio-label">
|
|
|
+ <input type="radio" name="spaceType" value="跃层" [(ngModel)]="newProduct.spaceType">
|
|
|
+ <span>跃层</span>
|
|
|
+ </label>
|
|
|
+ <label class="radio-label">
|
|
|
+ <input type="radio" name="spaceType" value="挑空" [(ngModel)]="newProduct.spaceType">
|
|
|
+ <span>挑空</span>
|
|
|
+ </label>
|
|
|
+ <label class="radio-label">
|
|
|
+ <input type="radio" name="spaceType" value="卧室" [(ngModel)]="newProduct.spaceType">
|
|
|
+ <span>卧室</span>
|
|
|
+ </label>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-group">
|
|
|
+ <label class="form-label">风格等级</label>
|
|
|
+ <select [(ngModel)]="newProduct.styleLevel" class="form-select">
|
|
|
+ <option value="基础风格组">基础风格组</option>
|
|
|
+ <option value="中级风格组">中级风格组</option>
|
|
|
+ <option value="高级风格组">高级风格组</option>
|
|
|
+ <option value="顶级风格组">顶级风格组</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+
|
|
|
+ <!-- 工装专属配置 -->
|
|
|
+ @if (projectInfo.projectType === '工装') {
|
|
|
+ <div class="form-group">
|
|
|
+ <label class="form-label">业态类型</label>
|
|
|
+ <select [(ngModel)]="newProduct.businessType" class="form-select">
|
|
|
+ <option value="办公空间">办公空间</option>
|
|
|
+ <option value="商业空间">商业空间</option>
|
|
|
+ <option value="娱乐空间">娱乐空间</option>
|
|
|
+ <option value="酒店餐厅">酒店餐厅</option>
|
|
|
+ <option value="公共空间">公共空间</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-group">
|
|
|
+ <label class="form-label">空间类型</label>
|
|
|
+ <div class="radio-group">
|
|
|
+ <label class="radio-label">
|
|
|
+ <input type="radio" name="spaceType" value="门厅空间" [(ngModel)]="newProduct.spaceType">
|
|
|
+ <span>门厅空间</span>
|
|
|
+ </label>
|
|
|
+ <label class="radio-label">
|
|
|
+ <input type="radio" name="spaceType" value="封闭空间" [(ngModel)]="newProduct.spaceType">
|
|
|
+ <span>封闭空间</span>
|
|
|
+ </label>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+
|
|
|
+ <!-- 建筑类专属配置 -->
|
|
|
+ @if (projectInfo.projectType === '建筑类') {
|
|
|
+ <div class="form-group">
|
|
|
+ <label class="form-label">建筑类型</label>
|
|
|
+ <select [(ngModel)]="newProduct.architectureType" class="form-select">
|
|
|
+ <option value="门头">门头</option>
|
|
|
+ <option value="小型单体">小型单体</option>
|
|
|
+ <option value="大型单体">大型单体</option>
|
|
|
+ <option value="鸟瞰">鸟瞰</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+
|
|
|
+ <!-- 加价规则配置 -->
|
|
|
+ <div class="pricing-adjustments">
|
|
|
+ <h5 class="section-title">加价规则配置(可选)</h5>
|
|
|
+
|
|
|
+ @if (projectInfo.projectType === '家装' || projectInfo.projectType === '工装') {
|
|
|
+ <div class="form-group">
|
|
|
+ <label class="form-label">额外功能区数量</label>
|
|
|
+ <div class="input-with-hint">
|
|
|
+ <input
|
|
|
+ type="number"
|
|
|
+ [(ngModel)]="newProduct.adjustments.extraFunction"
|
|
|
+ min="0"
|
|
|
+ class="form-input"
|
|
|
+ placeholder="0">
|
|
|
+ <span class="input-hint">
|
|
|
+ @if (projectInfo.projectType === '家装') {
|
|
|
+ 每增加一个功能区 +100元
|
|
|
+ } @else {
|
|
|
+ 每增加一个功能区 +400元
|
|
|
+ }
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
}
|
|
|
- </div>
|
|
|
- }
|
|
|
|
|
|
- <div class="total-section">
|
|
|
- <div class="total-row">
|
|
|
- <span class="total-label">报价总额</span>
|
|
|
- <span class="total-amount">{{ formatPrice(quotation.total) }}</span>
|
|
|
+ <div class="form-group">
|
|
|
+ <label class="form-label">造型复杂度加价</label>
|
|
|
+ <div class="input-with-hint">
|
|
|
+ <input
|
|
|
+ type="number"
|
|
|
+ [(ngModel)]="newProduct.adjustments.complexity"
|
|
|
+ min="0"
|
|
|
+ max="200"
|
|
|
+ class="form-input"
|
|
|
+ placeholder="0">
|
|
|
+ <span class="input-hint">立面和吊顶造型复杂: 0-200元</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-group">
|
|
|
+ <label class="checkbox-label">
|
|
|
+ <input type="checkbox" [(ngModel)]="newProduct.adjustments.design">
|
|
|
+ <span>需要我们设计(原价×2)</span>
|
|
|
+ </label>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ @if (projectInfo.projectType === '工装') {
|
|
|
+ <div class="form-group">
|
|
|
+ <label class="checkbox-label">
|
|
|
+ <input type="checkbox" [(ngModel)]="newProduct.adjustments.panoramic">
|
|
|
+ <span>需要全景渲染(原价×2)</span>
|
|
|
+ </label>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
</div>
|
|
|
|
|
|
- @if (quotation.generatedAt) {
|
|
|
- <div class="total-meta">
|
|
|
- <span class="generate-info">生成于 {{ quotation.generatedAt | date:'yyyy-MM-dd HH:mm' }}</span>
|
|
|
- @if (quotation.validUntil) {
|
|
|
- <span class="valid-info">有效期至 {{ quotation.validUntil | date:'yyyy-MM-dd' }}</span>
|
|
|
- }
|
|
|
+ <!-- 价格预览 -->
|
|
|
+ <div class="price-preview">
|
|
|
+ <div class="price-preview-row">
|
|
|
+ <span class="label">基础价格:</span>
|
|
|
+ <span class="price">{{ formatPrice(calculatePreviewBasePrice()) }}</span>
|
|
|
</div>
|
|
|
- }
|
|
|
+ @if (calculatePreviewAdjustmentTotal() > 0) {
|
|
|
+ <div class="price-preview-row adjustment">
|
|
|
+ <span class="label">加价合计:</span>
|
|
|
+ <span class="price">+{{ formatPrice(calculatePreviewAdjustmentTotal()) }}</span>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ <div class="price-preview-row total">
|
|
|
+ <span class="label">最终价格:</span>
|
|
|
+ <span class="price">{{ formatPrice(calculatePreviewFinalPrice()) }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="modal-footer">
|
|
|
+ <button class="btn-secondary" (click)="closeAddProductModal()">取消</button>
|
|
|
+ <button
|
|
|
+ class="btn-primary"
|
|
|
+ (click)="confirmAddProduct()"
|
|
|
+ [disabled]="!isNewProductValid()">
|
|
|
+ {{ isEditMode ? '确认修改' : '确认添加' }}
|
|
|
+ </button>
|
|
|
</div>
|
|
|
</div>
|
|
|
- }
|
|
|
+ </div>
|
|
|
}
|