project-detail.html 60 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099
  1. <!-- 只展示修改处,未变更部分用占位注释表示 -->
  2. <div class="project-detail-container designer-page">
  3. <!-- 项目标题栏 -->
  4. <div class="project-header card">
  5. <div class="header-left">
  6. <div class="header-content">
  7. <h1>项目详情</h1>
  8. <div class="project-meta">
  9. <span class="project-id">项目ID: {{ projectId }}</span>
  10. @if (project) { <span class="project-status">{{ project.status }}</span> }
  11. <!-- 紧急与异常徽标(使用控制流指令) -->
  12. <!-- 保持已有@if 徽标逻辑不变 -->
  13. </div>
  14. </div>
  15. </div>
  16. <!-- 导航按钮区域 - 移动到标题左侧 -->
  17. <div class="header-nav">
  18. <app-vertical-nav
  19. [activeTab]="activeTab"
  20. (tabChange)="switchTab($event)"
  21. class="header-nav-tabs">
  22. </app-vertical-nav>
  23. </div>
  24. <!-- 四个环节圆圈导航 -->
  25. <div class="header-center">
  26. <div class="stage-navigation">
  27. <div class="stage-nav-item"
  28. [class.completed]="getSectionStatus('order') === 'completed'"
  29. [class.active]="getSectionStatus('order') === 'active'"
  30. (click)="toggleSection('order')">
  31. <div class="stage-nav-circle">
  32. <span class="stage-nav-number">1</span>
  33. </div>
  34. <div class="stage-nav-label">订单创建</div>
  35. </div>
  36. <div class="stage-nav-connector" [class.completed]="getSectionStatus('requirements') === 'completed' || getSectionStatus('requirements') === 'active'"></div>
  37. <div class="stage-nav-item"
  38. [class.completed]="getSectionStatus('requirements') === 'completed'"
  39. [class.active]="getSectionStatus('requirements') === 'active'"
  40. (click)="toggleSection('requirements')">
  41. <div class="stage-nav-circle">
  42. <span class="stage-nav-number">2</span>
  43. </div>
  44. <div class="stage-nav-label">确认需求</div>
  45. </div>
  46. <div class="stage-nav-connector" [class.completed]="getSectionStatus('delivery') === 'completed' || getSectionStatus('delivery') === 'active'"></div>
  47. <div class="stage-nav-item"
  48. [class.completed]="getSectionStatus('delivery') === 'completed'"
  49. [class.active]="getSectionStatus('delivery') === 'active'"
  50. (click)="toggleSection('delivery')">
  51. <div class="stage-nav-circle">
  52. <span class="stage-nav-number">3</span>
  53. </div>
  54. <div class="stage-nav-label">交付执行</div>
  55. </div>
  56. <div class="stage-nav-connector" [class.completed]="getSectionStatus('aftercare') === 'completed' || getSectionStatus('aftercare') === 'active'"></div>
  57. <div class="stage-nav-item"
  58. [class.completed]="getSectionStatus('aftercare') === 'completed'"
  59. [class.active]="getSectionStatus('aftercare') === 'active'"
  60. (click)="toggleSection('aftercare')">
  61. <div class="stage-nav-circle">
  62. <span class="stage-nav-number">4</span>
  63. </div>
  64. <div class="stage-nav-label">售后</div>
  65. </div>
  66. </div>
  67. </div>
  68. <div class="header-right">
  69. <div class="header-actions">
  70. <!-- 导出阶段报告 -->
  71. <button (click)="exportProjectReport()" class="action-btn secondary-btn">
  72. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  73. <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
  74. <polyline points="14 2 14 8 20 8"></polyline>
  75. <line x1="16" y1="13" x2="8" y2="13"></line>
  76. <line x1="16" y1="17" x2="8" y2="17"></line>
  77. </svg>
  78. 导出报告
  79. </button>
  80. <button (click)="generateReminderMessage()" class="action-btn stagnation-btn">
  81. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  82. <circle cx="12" cy="12" r="10"></circle>
  83. <polyline points="12 6 12 12 16 14"></polyline>
  84. </svg>
  85. 设置停滞
  86. </button>
  87. <!-- 切换项目下拉菜单 -->
  88. <div class="project-switcher">
  89. <button (click)="showDropdown = !showDropdown" class="action-btn switch-btn">
  90. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  91. <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
  92. <polyline points="9 22 9 12 15 12 15 22"></polyline>
  93. </svg>
  94. 切换项目
  95. <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="dropdown-icon">
  96. <polyline points="6 9 12 15 18 9"></polyline>
  97. </svg>
  98. </button>
  99. @if (showDropdown) {
  100. <div class="switch-dropdown" (click)="$event.stopPropagation()">
  101. @for (p of projects; track p.id) {
  102. <div (click)="switchProject(p.id); showDropdown = false"
  103. [class.active]="p.id === projectId"
  104. class="project-item">
  105. <span class="project-name">{{ p.name }}</span>
  106. <span class="project-status-badge"
  107. [class.ongoing]="p.status === '进行中'"
  108. [class.completed]="p.status === '已完成'"
  109. [class.pending]="p.status === '待处理'">
  110. {{ p.status }}
  111. </span>
  112. </div>
  113. }
  114. </div>
  115. }
  116. </div>
  117. <!-- 返回工作台按钮 -->
  118. <button (click)="backToWorkbench()" class="action-btn back-btn primary">
  119. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  120. <path d="M19 12H5"></path>
  121. <polyline points="12 19 5 12 12 5"></polyline>
  122. </svg>
  123. 返回工作台
  124. </button>
  125. </div>
  126. </div>
  127. </div>
  128. <!-- 图片预览模态框 -->
  129. @if (showImagePreview) {
  130. <div class="image-preview-modal" (click)="closeImagePreview()">
  131. <div class="modal-backdrop"></div>
  132. <div class="modal-content" (click)="$event.stopPropagation()">
  133. <div class="modal-header">
  134. <h3>{{ previewImageData?.name }}</h3>
  135. <button class="close-btn" (click)="closeImagePreview()">
  136. <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  137. <line x1="18" y1="6" x2="6" y2="18"></line>
  138. <line x1="6" y1="6" x2="18" y2="18"></line>
  139. </svg>
  140. </button>
  141. </div>
  142. <div class="modal-body">
  143. <img [src]="previewImageData?.url" [alt]="previewImageData?.name" />
  144. </div>
  145. <div class="modal-footer">
  146. <div class="image-info">
  147. <span>文件大小: {{ previewImageData?.size }}</span>
  148. @if (previewImageData?.reviewStatus) {
  149. <span class="status-badge">{{ getImageReviewStatusText(previewImageData) }}</span>
  150. }
  151. </div>
  152. <div class="modal-actions">
  153. <button class="secondary-btn" (click)="downloadImage(previewImageData)">下载</button>
  154. <button class="danger-btn" (click)="removeImageFromPreview()">删除</button>
  155. </div>
  156. </div>
  157. </div>
  158. </div>
  159. }
  160. <!-- 提醒消息弹窗 -->
  161. @if (reminderMessage) {
  162. <div class="reminder-popup">
  163. {{ reminderMessage }}
  164. </div>
  165. }
  166. <!-- 标准阶段进度(5阶段) -->
  167. <!-- 已采用@for,不变 -->
  168. <!-- 顶部导航标签页 -->
  169. <!-- 原有代码保留 -->
  170. <!-- 水平导航栏 - 已移动到顶部,此处删除 -->
  171. <div class="tab-content">
  172. <!-- 项目进度标签页 -->
  173. @if (isActiveTab('progress')) {
  174. <div class="progress-tab-content">
  175. <div class="main-content-layout">
  176. <!-- 左侧保留 -->
  177. <div class="left-column">
  178. <div class="project-info-card card">
  179. <div class="card-header" (click)="toggleCustomerInfo()" style="cursor: pointer;">
  180. <h2>客户信息</h2>
  181. <div class="header-actions">
  182. <div class="sync-status" [class.syncing]="isSyncingCustomerInfo">
  183. @if (isSyncingCustomerInfo) {
  184. <span class="sync-indicator">
  185. <span class="sync-spinner"></span>
  186. 同步中...
  187. </span>
  188. } @else if (lastSyncTime) {
  189. <span class="sync-time">
  190. <i class="icon-sync"></i>
  191. 已同步: {{ formatTime(lastSyncTime) }}
  192. </span>
  193. }
  194. </div>
  195. <div class="toggle-icon" [class.expanded]="isCustomerInfoExpanded">
  196. <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
  197. <path d="M4 6L8 10L12 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
  198. </svg>
  199. </div>
  200. </div>
  201. </div>
  202. @if (project) {
  203. <div class="info-grid" [class.collapsed]="!isCustomerInfoExpanded">
  204. <!-- 显示订单创建时填写的客户信息,如果有的话 -->
  205. @if (orderCreationData?.customerInfo) {
  206. <!-- 基本客户信息 -->
  207. <div class="info-item key-info"><label>客户姓名</label><span>{{ orderCreationData.customerInfo.name }}</span></div>
  208. <div class="info-item key-info"><label>联系电话</label><span>{{ orderCreationData.customerInfo.phone }}</span></div>
  209. @if (orderCreationData.customerInfo.wechat) {
  210. <div class="info-item"><label>微信号</label><span>{{ orderCreationData.customerInfo.wechat }}</span></div>
  211. }
  212. <div class="info-item"><label>客户类型</label><span>{{ orderCreationData.customerInfo.customerType }}</span></div>
  213. @if (orderCreationData.customerInfo.source) {
  214. <div class="info-item"><label>客户来源</label><span>{{ orderCreationData.customerInfo.source }}</span></div>
  215. }
  216. @if (orderCreationData.customerInfo.remark) {
  217. <div class="info-item"><label>备注信息</label><span>{{ orderCreationData.customerInfo.remark }}</span></div>
  218. }
  219. } @else {
  220. <!-- 默认显示项目信息 -->
  221. <div class="info-item key-info"><label>项目负责人</label><span>{{ project.assigneeName }}</span></div>
  222. }
  223. <div class="info-item"><label>项目创建</label><span>{{ formatDate(project.createdAt) }}</span></div>
  224. <div class="info-item"><label>截止日期</label><span>{{ formatDate(project.deadline) }}</span></div>
  225. </div>
  226. <div class="tags-container" [class.collapsed]="!isCustomerInfoExpanded" style="margin-top: 12px;">
  227. <div class="tag-section">
  228. <h3>客户标签</h3>
  229. <div class="tags">
  230. @for (tag of project.customerTags; track $index) {
  231. <span class="tag">{{ tag.source }} · {{ tag.needType }} · {{ tag.preference }} · {{ tag.colorAtmosphere }}</span>
  232. }
  233. @if (project.customerTags.length === 0) { <span class="desc">暂无标签</span> }
  234. </div>
  235. </div>
  236. </div>
  237. } @else {
  238. <div class="loading-state">
  239. <div class="loading-spinner"></div>
  240. <div>正在加载客户信息...</div>
  241. </div>
  242. }
  243. </div>
  244. <!-- 方案确认卡片 - 优化样式与左侧客户信息卡片保持一致 -->
  245. <div class="proposal-confirm-card card">
  246. <div class="card-header">
  247. <h2>方案确认</h2>
  248. <div class="sync-status">
  249. <span class="progress-text">{{ getRequiredStagesProgress() }}% 完成</span>
  250. </div>
  251. </div>
  252. <!-- 素材解析状态 -->
  253. @if (isAnalyzing) {
  254. <div class="analysis-progress">
  255. <div class="progress-header">
  256. <h4>正在解析素材...</h4>
  257. <span class="progress-percentage">{{ analysisProgress.toFixed(0) }}%</span>
  258. </div>
  259. <div class="progress-bar">
  260. <div class="progress-fill" [style.width.%]="analysisProgress"></div>
  261. </div>
  262. <p class="progress-description">AI正在分析您的需求,生成专属设计方案</p>
  263. </div>
  264. }
  265. <!-- 方案展示区域 -->
  266. @if (proposalAnalysis && !isAnalyzing) {
  267. <div class="proposal-display">
  268. <!-- 方案概览 -->
  269. <div class="proposal-overview">
  270. <div class="overview-header">
  271. <h3>{{ proposalAnalysis.name }}</h3>
  272. <div class="proposal-meta">
  273. <span class="version">{{ proposalAnalysis.version }}</span>
  274. <span class="feasibility-score">可行性: {{ proposalAnalysis.feasibility.overall }}%</span>
  275. </div>
  276. </div>
  277. <!-- 快速摘要 -->
  278. <div class="quick-summary">
  279. <div class="summary-item">
  280. <span class="label">设计风格</span>
  281. <span class="value">{{ getStyleSummary() }}</span>
  282. </div>
  283. <div class="summary-item">
  284. <span class="label">色彩方案</span>
  285. <span class="value">{{ getColorSummary() }}</span>
  286. </div>
  287. <div class="summary-item">
  288. <span class="label">空间效率</span>
  289. <span class="value">{{ getSpaceEfficiency() }}%</span>
  290. </div>
  291. </div>
  292. </div>
  293. <!-- 详细方案内容 -->
  294. <div class="proposal-details">
  295. <!-- 材质规格与分类 -->
  296. <div class="detail-section">
  297. <div class="section-header">
  298. <h4>材质规格与分类</h4>
  299. <span class="section-count">{{ proposalAnalysis.materials.length }} 类材质</span>
  300. </div>
  301. <div class="materials-grid">
  302. @for (material of proposalAnalysis.materials; track material.category) {
  303. <div class="material-card" [class]="material.usage.priority">
  304. <div class="material-header">
  305. <h5>{{ material.category }}</h5>
  306. <span class="usage-percentage">{{ material.usage.percentage }}%</span>
  307. </div>
  308. <div class="material-specs">
  309. <div class="spec-item">
  310. <span class="spec-label">类型</span>
  311. <span class="spec-value">{{ material.specifications.type }}</span>
  312. </div>
  313. <div class="spec-item">
  314. <span class="spec-label">等级</span>
  315. <span class="spec-value">{{ material.specifications.grade }}</span>
  316. </div>
  317. <div class="spec-item">
  318. <span class="spec-label">应用区域</span>
  319. <span class="spec-value">{{ material.usage.area }}</span>
  320. </div>
  321. </div>
  322. <div class="material-properties">
  323. <span class="property-tag">{{ material.properties.texture }}</span>
  324. <span class="property-tag">{{ material.properties.color }}</span>
  325. </div>
  326. </div>
  327. }
  328. </div>
  329. </div>
  330. <!-- 设计风格特征 -->
  331. <div class="detail-section">
  332. <div class="section-header">
  333. <h4>设计风格特征</h4>
  334. <span class="style-name">{{ proposalAnalysis.designStyle.primaryStyle }}</span>
  335. </div>
  336. <div class="style-elements">
  337. @for (element of proposalAnalysis.designStyle.styleElements; track element.element) {
  338. <div class="style-element">
  339. <div class="element-header">
  340. <span class="element-name">{{ element.element }}</span>
  341. <span class="influence-score">{{ element.influence }}%</span>
  342. </div>
  343. <div class="element-description">{{ element.description }}</div>
  344. <div class="influence-bar">
  345. <div class="influence-fill" [style.width.%]="element.influence"></div>
  346. </div>
  347. </div>
  348. }
  349. </div>
  350. <div class="style-characteristics">
  351. @for (char of proposalAnalysis.designStyle.characteristics; track char.feature) {
  352. <div class="characteristic-item" [class]="char.importance">
  353. <span class="char-feature">{{ char.feature }}</span>
  354. <span class="char-value">{{ char.value }}</span>
  355. </div>
  356. }
  357. </div>
  358. </div>
  359. <!-- 色彩搭配方案及占比分析 -->
  360. <div class="detail-section">
  361. <div class="section-header">
  362. <h4>色彩搭配方案及占比分析</h4>
  363. <span class="harmony-type">{{ proposalAnalysis.colorScheme.harmony.type }}</span>
  364. </div>
  365. <div class="color-palette">
  366. @for (color of proposalAnalysis.colorScheme.palette; track color.hex) {
  367. <div class="color-item" [class]="color.role">
  368. <div class="color-swatch" [style.background-color]="color.hex"></div>
  369. <div class="color-info">
  370. <div class="color-name">{{ color.color }}</div>
  371. <div class="color-percentage">{{ color.percentage }}%</div>
  372. <div class="color-role">{{ color.role }}</div>
  373. </div>
  374. <div class="color-codes">
  375. <span class="hex-code">{{ color.hex }}</span>
  376. <span class="rgb-code">RGB({{ color.rgb }})</span>
  377. </div>
  378. </div>
  379. }
  380. </div>
  381. <div class="color-psychology">
  382. <div class="psychology-item">
  383. <span class="label">氛围营造</span>
  384. <span class="value">{{ proposalAnalysis.colorScheme.psychology.mood }}</span>
  385. </div>
  386. <div class="psychology-item">
  387. <span class="label">空间感受</span>
  388. <span class="value">{{ proposalAnalysis.colorScheme.psychology.atmosphere }}</span>
  389. </div>
  390. </div>
  391. </div>
  392. <!-- 空间尺寸数据及功能分区 -->
  393. <div class="detail-section">
  394. <div class="section-header">
  395. <h4>空间尺寸数据及功能分区</h4>
  396. <span class="total-area">总面积 {{ proposalAnalysis.spaceLayout.dimensions.area }}㎡</span>
  397. </div>
  398. <div class="space-dimensions">
  399. <div class="dimension-item">
  400. <span class="dim-label">长度</span>
  401. <span class="dim-value">{{ proposalAnalysis.spaceLayout.dimensions.length }}m</span>
  402. </div>
  403. <div class="dimension-item">
  404. <span class="dim-label">宽度</span>
  405. <span class="dim-value">{{ proposalAnalysis.spaceLayout.dimensions.width }}m</span>
  406. </div>
  407. <div class="dimension-item">
  408. <span class="dim-label">层高</span>
  409. <span class="dim-value">{{ proposalAnalysis.spaceLayout.dimensions.height }}m</span>
  410. </div>
  411. <div class="dimension-item">
  412. <span class="dim-label">体积</span>
  413. <span class="dim-value">{{ proposalAnalysis.spaceLayout.dimensions.volume }}m³</span>
  414. </div>
  415. </div>
  416. <div class="functional-zones">
  417. @for (zone of proposalAnalysis.spaceLayout.functionalZones; track zone.zone) {
  418. <div class="zone-card">
  419. <div class="zone-header">
  420. <h5>{{ zone.zone }}</h5>
  421. <div class="zone-stats">
  422. <span class="zone-area">{{ zone.area }}㎡</span>
  423. <span class="zone-percentage">{{ zone.percentage }}%</span>
  424. </div>
  425. </div>
  426. <div class="zone-requirements">
  427. <div class="requirements-label">功能需求</div>
  428. <div class="requirements-tags">
  429. @for (req of zone.requirements; track req) {
  430. <span class="requirement-tag">{{ req }}</span>
  431. }
  432. </div>
  433. </div>
  434. <div class="zone-furniture">
  435. <div class="furniture-label">家具配置</div>
  436. <div class="furniture-list">
  437. @for (furniture of zone.furniture; track furniture; let isLast = $last) {
  438. {{ furniture }}@if (!isLast) {、}
  439. }
  440. </div>
  441. </div>
  442. </div>
  443. }
  444. </div>
  445. </div>
  446. <!-- 预算与时间线 -->
  447. <div class="detail-section">
  448. <div class="section-header">
  449. <h4>预算与时间线</h4>
  450. <span class="total-budget">总预算 ¥{{ proposalAnalysis.budget.total.toLocaleString() }}</span>
  451. </div>
  452. <div class="budget-breakdown">
  453. @for (item of proposalAnalysis.budget.breakdown; track item.category) {
  454. <div class="budget-item">
  455. <div class="budget-category">{{ item.category }}</div>
  456. <div class="budget-amount">¥{{ item.amount.toLocaleString() }}</div>
  457. <div class="budget-percentage">{{ item.percentage }}%</div>
  458. <div class="budget-bar">
  459. <div class="budget-fill" [style.width.%]="item.percentage"></div>
  460. </div>
  461. </div>
  462. }
  463. </div>
  464. <div class="timeline">
  465. @for (phase of proposalAnalysis.timeline; track phase.phase) {
  466. <div class="timeline-item">
  467. <div class="phase-name">{{ phase.phase }}</div>
  468. <div class="phase-duration">{{ phase.duration }}天</div>
  469. </div>
  470. }
  471. </div>
  472. </div>
  473. </div>
  474. <!-- 方案操作按钮 -->
  475. <div class="proposal-actions">
  476. <button class="confirm-btn primary" (click)="confirmProposal()">
  477. 确认此方案
  478. </button>
  479. <button class="adjust-btn secondary" (click)="startMaterialAnalysis()">
  480. 重新分析
  481. </button>
  482. </div>
  483. </div>
  484. }
  485. <!-- 需求信息展示区域(原有内容,当没有方案分析时显示) -->
  486. @if (areRequiredStagesCompleted() && !proposalAnalysis && !isAnalyzing) {
  487. <div class="info-grid">
  488. <!-- 色调信息 -->
  489. @if (requirementKeyInfo.colorAtmosphere.description) {
  490. <div class="info-item">
  491. <label>色调</label>
  492. <span>{{ requirementKeyInfo.colorAtmosphere.description }}</span>
  493. @if (requirementKeyInfo.colorAtmosphere.mainColor) {
  494. <div class="color-preview" [style.background-color]="requirementKeyInfo.colorAtmosphere.mainColor"></div>
  495. }
  496. </div>
  497. }
  498. <!-- 材质信息 -->
  499. @if (requirementKeyInfo.colorAtmosphere.materials && requirementKeyInfo.colorAtmosphere.materials.length > 0) {
  500. <div class="info-item">
  501. <label>材质</label>
  502. <span>
  503. @for (material of requirementKeyInfo.colorAtmosphere.materials; track material; let isLast = $last) {
  504. {{ material }}@if (!isLast) {, }
  505. }
  506. </span>
  507. </div>
  508. }
  509. <!-- 空间结构 -->
  510. @if (requirementKeyInfo.spaceStructure.aspectRatio > 0) {
  511. <div class="info-item">
  512. <label>空间比例</label>
  513. <span>{{ requirementKeyInfo.spaceStructure.aspectRatio.toFixed(1) }}</span>
  514. </div>
  515. }
  516. @if (requirementKeyInfo.spaceStructure.ceilingHeight > 0) {
  517. <div class="info-item">
  518. <label>层高</label>
  519. <span>{{ requirementKeyInfo.spaceStructure.ceilingHeight }}m</span>
  520. </div>
  521. }
  522. <!-- 材质权重 -->
  523. @if (requirementKeyInfo.materialWeights.woodRatio > 0) {
  524. <div class="info-item">
  525. <label>木质比例</label>
  526. <span>{{ requirementKeyInfo.materialWeights.woodRatio }}%</span>
  527. </div>
  528. }
  529. @if (requirementKeyInfo.materialWeights.fabricRatio > 0) {
  530. <div class="info-item">
  531. <label>布艺比例</label>
  532. <span>{{ requirementKeyInfo.materialWeights.fabricRatio }}%</span>
  533. </div>
  534. }
  535. @if (requirementKeyInfo.materialWeights.metalRatio > 0) {
  536. <div class="info-item">
  537. <label>金属比例</label>
  538. <span>{{ requirementKeyInfo.materialWeights.metalRatio }}%</span>
  539. </div>
  540. }
  541. <!-- 预设氛围 -->
  542. @if (requirementKeyInfo.presetAtmosphere.name) {
  543. <div class="info-item">
  544. <label>预设氛围</label>
  545. <span>{{ requirementKeyInfo.presetAtmosphere.name }}</span>
  546. </div>
  547. }
  548. <!-- 小图时间 -->
  549. @if (getEstimatedSmallImageTime()) {
  550. <div class="info-item">
  551. <label>预计时间</label>
  552. <span>{{ getEstimatedSmallImageTime() }}</span>
  553. </div>
  554. }
  555. </div>
  556. <!-- 开始分析按钮 -->
  557. <div class="proposal-actions">
  558. <button class="confirm-btn" (click)="startMaterialAnalysis()">
  559. 开始素材解析
  560. </button>
  561. </div>
  562. } @else if (!areRequiredStagesCompleted()) {
  563. <!-- 等待状态 -->
  564. <div class="waiting-state">
  565. <div class="waiting-content">
  566. <h5>等待需求信息完善</h5>
  567. <p>需求沟通阶段完成后,方案确认功能将自动开启</p>
  568. <div class="progress-info">
  569. <span>当前进度: {{ getRequiredStagesProgress() }}%</span>
  570. </div>
  571. </div>
  572. </div>
  573. }
  574. </div>
  575. </div>
  576. <!-- 右侧三分之二 - 制作流程进度 -->
  577. <div class="right-column">
  578. <!-- 移除顶部四个圆圈,直接展示阶段内容 -->
  579. <!-- 串式流程:10个阶段横向排列(保持) -->
  580. <div class="stage-progress-container">
  581. @for (stage of getVisibleStages(); track stage) {
  582. <div class="vertical-stage-block" [attr.id]="stageToAnchor(stage)" [class.active]="getStageStatus(stage) === 'active'">
  583. @if (stage !== '订单创建') {
  584. <div class="vertical-stage-header">
  585. <span class="dot" [class.completed]="getStageStatus(stage) === 'completed'" [class.active]="getStageStatus(stage) === 'active'"></span>
  586. <h3>{{ stage }}</h3>
  587. </div>
  588. }
  589. <!-- 直接复用原阶段内容卡片:按stage匹配显示 -->
  590. <div class="vertical-stage-body">
  591. @if (stage === '订单创建') {
  592. <app-consultation-order-panel
  593. [syncData]="projectData"
  594. (orderCreated)="onConsultationOrderSubmit($event)"
  595. (projectCreated)="onProjectCreated($event)"
  596. ></app-consultation-order-panel>
  597. } @else if (stage === '需求沟通') {
  598. <app-requirements-confirm-card
  599. (requirementConfirmed)="syncRequirementKeyInfo($event)"
  600. (progressUpdated)="syncRequirementKeyInfo($event)"
  601. (stageCompleted)="onRequirementsStageCompleted($event)"
  602. (dataUpdated)="onRequirementDataUpdated($event)">
  603. </app-requirements-confirm-card>
  604. } @else if (stage === '方案确认') {
  605. <!-- 方案确认阶段 - 已移动到左侧列 -->
  606. <div class="proposal-confirm-placeholder">
  607. <div class="placeholder-message">
  608. <p>方案确认卡片已移动到左侧区域</p>
  609. </div>
  610. </div>
  611. } @else if (stage === '建模') {
  612. <div class="upload-section">
  613. <div class="upload-header">
  614. <h4>上传白模图片</h4>
  615. <span class="hint">支持:JPG/PNG,不强制4K</span>
  616. </div>
  617. @if (canEditSection('delivery')) {
  618. <div class="upload-dropzone"
  619. (click)="whiteModelImages.length === 0 ? triggerFileInput('whiteModel') : null"
  620. (dragover)="whiteModelImages.length === 0 ? onDragOver($event) : null"
  621. (dragleave)="whiteModelImages.length === 0 ? onDragLeave($event) : null"
  622. (drop)="whiteModelImages.length === 0 ? onFileDrop($event, 'whiteModel') : null"
  623. [class.drag-over]="isDragOver && whiteModelImages.length === 0"
  624. [class.has-images]="whiteModelImages.length > 0">
  625. @if (whiteModelImages.length === 0) {
  626. <div class="upload-icon"></div>
  627. <div class="upload-text">点击此处或拖拽文件到此处上传</div>
  628. <div class="upload-hint">支持 JPG、PNG 格式,单个文件最大 10MB</div>
  629. } @else {
  630. <div class="uploaded-images-grid">
  631. @for (img of whiteModelImages; track img.id) {
  632. <div class="uploaded-image-item" (click)="previewImage(img)">
  633. <img [src]="img.url" [alt]="img.name" />
  634. <div class="image-overlay">
  635. <div class="image-name">{{ img.name }}</div>
  636. <div class="image-actions">
  637. <button class="preview-btn" (click)="$event.stopPropagation(); previewImage(img)">
  638. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  639. <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
  640. <circle cx="12" cy="12" r="3"></circle>
  641. </svg>
  642. </button>
  643. <button class="remove-btn" (click)="$event.stopPropagation(); removeWhiteModelImage(img.id)">
  644. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  645. <line x1="18" y1="6" x2="6" y2="18"></line>
  646. <line x1="6" y1="6" x2="18" y2="18"></line>
  647. </svg>
  648. </button>
  649. </div>
  650. </div>
  651. </div>
  652. }
  653. <div class="add-more-btn" (click)="triggerFileInput('whiteModel')">
  654. <div class="add-icon">+</div>
  655. <div class="add-text">添加更多</div>
  656. </div>
  657. </div>
  658. }
  659. <input type="file"
  660. id="whiteModelFileInput"
  661. accept="{{allowedImageTypes}}"
  662. multiple
  663. (change)="onWhiteModelSelected($event)"
  664. style="display: none;" />
  665. </div>
  666. }
  667. <div class="upload-actions">
  668. @if (canEditSection('delivery')) {
  669. <button class="primary-btn" [disabled]="whiteModelImages.length===0" (click)="confirmWhiteModelUpload()">确认上传</button>
  670. }
  671. @if (isTeamLeaderView()) { <button class="secondary-btn" (click)="syncUploadedImages('white')">同步图片信息</button> }
  672. @if (!canEditSection('delivery')) { <span class="desc">只读</span> }
  673. </div>
  674. </div>
  675. <div class="model-check-section">
  676. <h4>模型差异检查清单</h4>
  677. <div class="checklist">
  678. @for (item of modelCheckItems; track item.id) {
  679. <div class="checklist-item">
  680. <label class="checklist-label">
  681. <input type="checkbox" class="custom-checkbox" [checked]="item.isPassed" (change)="updateModelCheckItem(item.id, $any($event.target).checked)" [disabled]="!canEditSection('delivery')">
  682. <span class="checklist-text">{{ item.name }}</span>
  683. </label>
  684. <span class="check-status">{{ item.isPassed ? '已通过' : '待处理' }}</span>
  685. </div>
  686. }
  687. </div>
  688. </div>
  689. } @else if (stage === '软装') {
  690. <div class="softdecor-section">
  691. <h4>软装清单素材</h4>
  692. <div class="upload-section">
  693. <div class="upload-header">
  694. <h4>上传软装小图</h4>
  695. <span class="hint">建议 ≤1MB 的 JPG/PNG 小图</span>
  696. </div>
  697. @if (canEditSection('delivery')) {
  698. <div class="upload-dropzone"
  699. (click)="softDecorImages.length === 0 ? triggerFileInput('softDecor') : null"
  700. (dragover)="softDecorImages.length === 0 ? onDragOver($event) : null"
  701. (dragleave)="softDecorImages.length === 0 ? onDragLeave($event) : null"
  702. (drop)="softDecorImages.length === 0 ? onFileDrop($event, 'softDecor') : null"
  703. [class.drag-over]="isDragOver && softDecorImages.length === 0"
  704. [class.has-images]="softDecorImages.length > 0">
  705. @if (softDecorImages.length === 0) {
  706. <div class="upload-icon"></div>
  707. <div class="upload-text">点击此处或拖拽文件到此处上传</div>
  708. <div class="upload-hint">支持 JPG、PNG 格式,建议文件大小 ≤1MB</div>
  709. } @else {
  710. <div class="uploaded-images-grid">
  711. @for (img of softDecorImages; track img.id) {
  712. <div class="uploaded-image-item" (click)="previewImage(img)">
  713. <img [src]="img.url" [alt]="img.name" />
  714. <div class="image-overlay">
  715. <div class="image-name">{{ img.name }}</div>
  716. <div class="image-actions">
  717. <button class="preview-btn" (click)="$event.stopPropagation(); previewImage(img)">
  718. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  719. <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
  720. <circle cx="12" cy="12" r="3"></circle>
  721. </svg>
  722. </button>
  723. <button class="remove-btn" (click)="$event.stopPropagation(); removeSoftDecorImage(img.id)">
  724. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  725. <line x1="18" y1="6" x2="6" y2="18"></line>
  726. <line x1="6" y1="6" x2="18" y2="18"></line>
  727. </svg>
  728. </button>
  729. </div>
  730. </div>
  731. </div>
  732. }
  733. <div class="add-more-btn" (click)="triggerFileInput('softDecor')">
  734. <div class="add-icon">+</div>
  735. <div class="add-text">添加更多</div>
  736. </div>
  737. </div>
  738. }
  739. <input type="file"
  740. id="softDecorFileInput"
  741. accept="{{allowedImageTypes}}"
  742. multiple
  743. (change)="onSoftDecorSmallPicsSelected($event)"
  744. style="display: none;" />
  745. </div>
  746. }
  747. <div class="upload-actions">
  748. @if (canEditSection('delivery')) {
  749. <button class="primary-btn" [disabled]="softDecorImages.length===0" (click)="confirmSoftDecorUpload()">确认上传</button>
  750. }
  751. @if (isTeamLeaderView()) { <button class="secondary-btn" (click)="syncUploadedImages('soft')">同步图片信息</button> }
  752. @if (!canEditSection('delivery')) { <span class="desc">只读</span> }
  753. </div>
  754. </div>
  755. </div>
  756. } @else if (stage === '渲染') {
  757. <div class="render-progress-section">
  758. @if (isLoadingRenderProgress) {
  759. <div class="loading-state">
  760. <div class="loading-spinner"></div>
  761. <div>正在加载渲染进度...</div>
  762. </div>
  763. }
  764. @if (errorLoadingRenderProgress) {
  765. <div class="error-state">
  766. <div>渲染进度加载失败</div>
  767. <button class="secondary-btn" (click)="retryLoadRenderProgress()">重试</button>
  768. </div>
  769. }
  770. @if (!isLoadingRenderProgress && !errorLoadingRenderProgress && renderProgress) {
  771. <div class="progress-info" style="display:flex;gap:16px;align-items:center;margin:12px 0;">
  772. <span>状态:{{ renderProgress.status }}</span>
  773. <span>完成度:{{ renderProgress.completionRate }}%</span>
  774. <span>预计剩余:{{ renderProgress.estimatedTimeRemaining }} 小时</span>
  775. </div>
  776. }
  777. <div class="upload-section">
  778. <div class="upload-header">
  779. <h4>上传渲染大图</h4>
  780. <span class="hint">需满足4K标准(最长边 ≥ 4000px)</span>
  781. </div>
  782. @if (canEditSection('delivery')) {
  783. <div class="upload-dropzone"
  784. (click)="renderLargeImages.length === 0 ? triggerFileInput('render') : null"
  785. (dragover)="renderLargeImages.length === 0 ? onDragOver($event) : null"
  786. (dragleave)="renderLargeImages.length === 0 ? onDragLeave($event) : null"
  787. (drop)="renderLargeImages.length === 0 ? onFileDrop($event, 'render') : null"
  788. [class.drag-over]="isDragOver && renderLargeImages.length === 0"
  789. [class.has-images]="renderLargeImages.length > 0">
  790. @if (renderLargeImages.length === 0) {
  791. <div class="upload-icon"></div>
  792. <div class="upload-text">点击此处或拖拽文件到此处上传</div>
  793. <div class="upload-hint">支持 JPG、PNG 格式,需满足4K标准(最长边 ≥ 4000px)</div>
  794. } @else {
  795. <div class="uploaded-images-grid">
  796. @for (img of renderLargeImages; track img.id) {
  797. <div class="uploaded-image-item" (click)="previewImage(img)">
  798. <img [src]="img.url" [alt]="img.name" />
  799. <div class="image-overlay">
  800. <div class="image-name">{{ img.name }}</div>
  801. <div class="image-actions">
  802. <button class="preview-btn" (click)="$event.stopPropagation(); previewImage(img)">
  803. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  804. <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
  805. <circle cx="12" cy="12" r="3"></circle>
  806. </svg>
  807. </button>
  808. <button class="remove-btn" (click)="$event.stopPropagation(); removeRenderLargeImage(img.id)">
  809. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  810. <line x1="18" y1="6" x2="6" y2="18"></line>
  811. <line x1="6" y1="6" x2="18" y2="18"></line>
  812. </svg>
  813. </button>
  814. </div>
  815. </div>
  816. </div>
  817. }
  818. <div class="add-more-btn" (click)="triggerFileInput('render')">
  819. <div class="add-icon">+</div>
  820. <div class="add-text">添加更多</div>
  821. </div>
  822. </div>
  823. }
  824. <input type="file"
  825. id="renderFileInput"
  826. accept="{{allowedImageTypes}}"
  827. multiple
  828. (change)="onRenderLargePicsSelected($event)"
  829. style="display: none;" />
  830. </div>
  831. }
  832. <div class="upload-actions">
  833. @if (canEditSection('delivery')) {
  834. <button class="primary-btn" [disabled]="renderLargeImages.length===0" (click)="confirmRenderUpload()">确认上传</button>
  835. }
  836. @if (isTeamLeaderView()) { <button class="secondary-btn" (click)="syncUploadedImages('render')">同步图片信息</button> }
  837. @if (!canEditSection('delivery')) { <span class="desc">只读</span> }
  838. </div>
  839. </div>
  840. </div>
  841. } @else if (stage === '后期') {
  842. <div class="post-process-section">
  843. <div class="upload-section">
  844. <div class="upload-header">
  845. <h4>上传后期处理图片</h4>
  846. <span class="hint">包含调色、修图、特效等后期处理结果</span>
  847. </div>
  848. @if (canEditSection('delivery')) {
  849. <div class="upload-dropzone"
  850. (click)="postProcessImages.length === 0 ? triggerFileInput('postProcess') : null"
  851. (dragover)="postProcessImages.length === 0 ? onDragOver($event) : null"
  852. (dragleave)="postProcessImages.length === 0 ? onDragLeave($event) : null"
  853. (drop)="postProcessImages.length === 0 ? onFileDrop($event, 'postProcess') : null"
  854. [class.drag-over]="isDragOver && postProcessImages.length === 0"
  855. [class.has-images]="postProcessImages.length > 0">
  856. @if (postProcessImages.length === 0) {
  857. <div class="upload-icon"></div>
  858. <div class="upload-text">点击此处或拖拽文件到此处上传</div>
  859. <div class="upload-hint">支持 JPG、PNG 格式,展示后期处理效果</div>
  860. } @else {
  861. <div class="uploaded-images-grid">
  862. @for (img of postProcessImages; track img.id) {
  863. <div class="uploaded-image-item" (click)="previewImage(img)">
  864. <img [src]="img.url" [alt]="img.name" />
  865. <div class="image-overlay">
  866. <div class="image-name">{{ img.name }}</div>
  867. <div class="image-actions">
  868. <button class="preview-btn" (click)="$event.stopPropagation(); previewImage(img)">
  869. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  870. <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
  871. <circle cx="12" cy="12" r="3"></circle>
  872. </svg>
  873. </button>
  874. <button class="remove-btn" (click)="$event.stopPropagation(); removePostProcessImage(img.id)">
  875. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  876. <line x1="18" y1="6" x2="6" y2="18"></line>
  877. <line x1="6" y1="6" x2="18" y2="18"></line>
  878. </svg>
  879. </button>
  880. </div>
  881. </div>
  882. </div>
  883. }
  884. <div class="add-more-btn" (click)="triggerFileInput('postProcess')">
  885. <div class="add-icon">+</div>
  886. <div class="add-text">添加更多</div>
  887. </div>
  888. </div>
  889. }
  890. <input type="file"
  891. id="postProcessFileInput"
  892. accept="{{allowedImageTypes}}"
  893. multiple
  894. (change)="onPostProcessPicsSelected($event)"
  895. style="display: none;" />
  896. </div>
  897. }
  898. <div class="upload-actions">
  899. @if (canEditSection('delivery')) {
  900. <button class="primary-btn" [disabled]="postProcessImages.length===0" (click)="confirmPostProcessUpload()">确认上传</button>
  901. }
  902. @if (isTeamLeaderView()) { <button class="secondary-btn" (click)="syncUploadedImages('postProcess')">同步图片信息</button> }
  903. @if (!canEditSection('delivery')) { <span class="desc">只读</span> }
  904. </div>
  905. </div>
  906. </div>
  907. } @else if (stage === '尾款结算') {
  908. <div class="aftercare-section-container">
  909. <app-settlement-card [settlements]="settlements"></app-settlement-card>
  910. </div>
  911. @if (canEditSection('aftercare')) {
  912. <div class="upload-actions">
  913. <button
  914. class="primary-btn"
  915. [class.completed]="isSettlementCompleted"
  916. [disabled]="isConfirmingSettlement"
  917. (click)="confirmSettlement()">
  918. @if (isConfirmingSettlement) {
  919. <span class="loading-spinner"></span>
  920. 确认中...
  921. } @else if (isSettlementCompleted) {
  922. <span class="check-icon">✓</span>
  923. 已确认完成
  924. } @else {
  925. 确认尾款结算完成
  926. }
  927. </button>
  928. <button class="secondary-btn" (click)="uploadPaymentProof()">
  929. <span class="upload-icon">📎</span>
  930. 上传支付凭证
  931. </button>
  932. </div>
  933. }
  934. } @else if (stage === '客户评价') {
  935. <div class="aftercare-section-container">
  936. <app-customer-review-card [feedbacks]="feedbacks"></app-customer-review-card>
  937. </div>
  938. @if (canEditSection('aftercare')) {
  939. <div class="upload-actions">
  940. <button class="primary-btn" (click)="confirmCustomerReview()">确认客户评价完成</button>
  941. </div>
  942. }
  943. } @else if (stage === '投诉处理') {
  944. <div class="aftercare-section-container">
  945. <app-complaint-card [complaints]="exceptionHistories"></app-complaint-card>
  946. </div>
  947. @if (canEditSection('aftercare')) {
  948. <div class="upload-actions">
  949. <button class="primary-btn" (click)="confirmComplaint()">确认投诉处理完成</button>
  950. </div>
  951. }
  952. }
  953. </div>
  954. </div>
  955. }
  956. </div>
  957. </div>
  958. </div>
  959. </div>
  960. }
  961. <!-- 项目人员标签页 -->
  962. @if (isActiveTab('members')) {
  963. <div class="members-tab-content">
  964. <div class="main-content-layout">
  965. <!-- 项目人员内容 -->
  966. <div class="members-content">
  967. <div class="members-header">
  968. <h2>项目成员</h2>
  969. <p class="members-count">共 {{ projectMembers.length }} 名成员</p>
  970. </div>
  971. <div class="members-grid">
  972. @for (member of projectMembers; track member.id) {
  973. <div class="member-card">
  974. <div class="member-avatar">
  975. <img [src]="member.avatar" [alt]="member.name">
  976. </div>
  977. <div class="member-info">
  978. <h3 class="member-name">{{ member.name }}</h3>
  979. <p class="member-role">{{ member.role }}</p>
  980. <div class="member-stats">
  981. <div class="stat-item">
  982. <span class="stat-label">技能匹配度</span>
  983. <div class="progress-bar">
  984. <div class="progress-fill" [style.width.%]="member.skillMatch"></div>
  985. </div>
  986. <span class="stat-value">{{ member.skillMatch }}%</span>
  987. </div>
  988. <div class="stat-item">
  989. <span class="stat-label">项目进度</span>
  990. <div class="progress-bar">
  991. <div class="progress-fill" [style.width.%]="member.progress"></div>
  992. </div>
  993. <span class="stat-value">{{ member.progress }}%</span>
  994. </div>
  995. <div class="stat-item">
  996. <span class="stat-label">贡献度</span>
  997. <div class="progress-bar">
  998. <div class="progress-fill" [style.width.%]="member.contribution"></div>
  999. </div>
  1000. <span class="stat-value">{{ member.contribution }}%</span>
  1001. </div>
  1002. </div>
  1003. </div>
  1004. </div>
  1005. }
  1006. </div>
  1007. @if (projectMembers.length === 0) {
  1008. <div class="empty-state">
  1009. <p>暂无项目成员信息</p>
  1010. </div>
  1011. }
  1012. </div>
  1013. </div>
  1014. </div>
  1015. }
  1016. <!-- 项目文件标签页 -->
  1017. @if (isActiveTab('files')) {
  1018. <div class="files-tab-content">
  1019. <div class="main-content-layout">
  1020. <!-- 项目文件内容 -->
  1021. <div class="files-content">
  1022. <div class="files-header">
  1023. <h2>项目文件</h2>
  1024. <p class="files-count">共 {{ projectFiles.length }} 个文件</p>
  1025. </div>
  1026. <div class="files-list">
  1027. @for (file of projectFiles; track file.id) {
  1028. <div class="file-item">
  1029. <div class="file-icon">
  1030. <span class="file-type">{{ file.type.toUpperCase() }}</span>
  1031. </div>
  1032. <div class="file-info">
  1033. <h3 class="file-name">{{ file.name }}</h3>
  1034. <div class="file-meta">
  1035. <span class="file-size">{{ file.size }}</span>
  1036. <span class="file-date">{{ file.date }}</span>
  1037. </div>
  1038. </div>
  1039. <div class="file-actions">
  1040. <button class="btn-download" (click)="downloadFile(file)">下载</button>
  1041. <button class="btn-preview" (click)="previewFile(file)">预览</button>
  1042. </div>
  1043. </div>
  1044. }
  1045. </div>
  1046. @if (projectFiles.length === 0) {
  1047. <div class="empty-state">
  1048. <p>暂无项目文件</p>
  1049. </div>
  1050. }
  1051. </div>
  1052. </div>
  1053. </div>
  1054. }
  1055. </div>