project-detail.html 151 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535
  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. <div class="header-nav-tabs">
  19. @for (tab of tabs; track tab.id) {
  20. <button
  21. class="nav-tab"
  22. [class.active]="activeTab === tab.id"
  23. (click)="switchTab(tab.id)">
  24. {{ tab.name }}
  25. </button>
  26. }
  27. </div>
  28. </div>
  29. <!-- 四个环节圆圈导航 -->
  30. <div class="header-center">
  31. <div class="stage-navigation">
  32. <div class="stage-nav-item"
  33. [class.completed]="getSectionStatus('order') === 'completed'"
  34. [class.active]="getSectionStatus('order') === 'active'"
  35. (click)="toggleSection('order')">
  36. <div class="stage-nav-circle">
  37. <span class="stage-nav-number">1</span>
  38. </div>
  39. <div class="stage-nav-label">订单创建</div>
  40. </div>
  41. <div class="stage-nav-connector" [class.completed]="getSectionStatus('requirements') === 'completed' || getSectionStatus('requirements') === 'active'"></div>
  42. <div class="stage-nav-item"
  43. [class.completed]="getSectionStatus('requirements') === 'completed'"
  44. [class.active]="getSectionStatus('requirements') === 'active'"
  45. (click)="toggleSection('requirements')">
  46. <div class="stage-nav-circle">
  47. <span class="stage-nav-number">2</span>
  48. </div>
  49. <div class="stage-nav-label">确认需求</div>
  50. </div>
  51. <div class="stage-nav-connector" [class.completed]="getSectionStatus('delivery') === 'completed' || getSectionStatus('delivery') === 'active'"></div>
  52. <div class="stage-nav-item"
  53. [class.completed]="getSectionStatus('delivery') === 'completed'"
  54. [class.active]="getSectionStatus('delivery') === 'active'"
  55. (click)="toggleSection('delivery')">
  56. <div class="stage-nav-circle">
  57. <span class="stage-nav-number">3</span>
  58. </div>
  59. <div class="stage-nav-label">交付执行</div>
  60. </div>
  61. <div class="stage-nav-connector" [class.completed]="getSectionStatus('aftercare') === 'completed' || getSectionStatus('aftercare') === 'active'"></div>
  62. <div class="stage-nav-item"
  63. [class.completed]="getSectionStatus('aftercare') === 'completed'"
  64. [class.active]="getSectionStatus('aftercare') === 'active'"
  65. (click)="toggleSection('aftercare')">
  66. <div class="stage-nav-circle">
  67. <span class="stage-nav-number">4</span>
  68. </div>
  69. <div class="stage-nav-label">售后</div>
  70. </div>
  71. </div>
  72. </div>
  73. <div class="header-right">
  74. <div class="header-actions">
  75. <!-- 导出阶段报告 -->
  76. <button (click)="exportProjectReport()" class="action-btn secondary-btn">
  77. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  78. <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
  79. <polyline points="14 2 14 8 20 8"></polyline>
  80. <line x1="16" y1="13" x2="8" y2="13"></line>
  81. <line x1="16" y1="17" x2="8" y2="17"></line>
  82. </svg>
  83. 导出报告
  84. </button>
  85. <button (click)="generateReminderMessage()" class="action-btn stagnation-btn">
  86. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  87. <circle cx="12" cy="12" r="10"></circle>
  88. <polyline points="12 6 12 12 16 14"></polyline>
  89. </svg>
  90. 设置停滞
  91. </button>
  92. <!-- 切换项目下拉菜单 -->
  93. <div class="project-switcher">
  94. <button (click)="showDropdown = !showDropdown" class="action-btn switch-btn">
  95. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  96. <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
  97. <polyline points="9 22 9 12 15 12 15 22"></polyline>
  98. </svg>
  99. 切换项目
  100. <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="dropdown-icon">
  101. <polyline points="6 9 12 15 18 9"></polyline>
  102. </svg>
  103. </button>
  104. @if (showDropdown) {
  105. <div class="switch-dropdown" (click)="$event.stopPropagation()">
  106. @for (p of projects; track p.id) {
  107. <div (click)="switchProject(p.id); showDropdown = false"
  108. [class.active]="p.id === projectId"
  109. class="project-item">
  110. <span class="project-name">{{ p.name }}</span>
  111. <span class="project-status-badge"
  112. [class.ongoing]="p.status === '进行中'"
  113. [class.completed]="p.status === '已完成'"
  114. [class.pending]="p.status === '待处理'">
  115. {{ p.status }}
  116. </span>
  117. </div>
  118. }
  119. </div>
  120. }
  121. </div>
  122. <!-- 返回工作台按钮 -->
  123. <button (click)="backToWorkbench()" class="action-btn back-btn primary">
  124. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  125. <path d="M19 12H5"></path>
  126. <polyline points="12 19 5 12 12 5"></polyline>
  127. </svg>
  128. 返回工作台
  129. </button>
  130. </div>
  131. </div>
  132. <!-- 隐藏的文件输入元素 -->
  133. @for (process of deliveryProcesses; track process.id) {
  134. @for (space of process.spaces; track space.id) {
  135. <input
  136. type="file"
  137. [id]="'space-file-input-' + process.id + '-' + space.id"
  138. accept="image/*"
  139. multiple
  140. (change)="onSpaceFileSelected($event, process.id, space.id)"
  141. style="display: none;" />
  142. }
  143. }
  144. </div>
  145. <!-- 图片预览模态框 -->
  146. @if (showImagePreview) {
  147. <div class="image-preview-modal" (click)="closeImagePreview()">
  148. <div class="modal-backdrop"></div>
  149. <div class="modal-content" (click)="$event.stopPropagation()">
  150. <div class="modal-header">
  151. <h3>{{ previewImageData?.name }}</h3>
  152. <button class="close-btn" (click)="closeImagePreview()">
  153. <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  154. <line x1="18" y1="6" x2="6" y2="18"></line>
  155. <line x1="6" y1="6" x2="18" y2="18"></line>
  156. </svg>
  157. </button>
  158. </div>
  159. <div class="modal-body">
  160. <img [src]="previewImageData?.url" [alt]="previewImageData?.name" />
  161. </div>
  162. <div class="modal-footer">
  163. <div class="image-info">
  164. <span>文件大小: {{ previewImageData?.size }}</span>
  165. @if (previewImageData?.reviewStatus) {
  166. <span class="status-badge">{{ getImageReviewStatusText(previewImageData) }}</span>
  167. }
  168. </div>
  169. <div class="modal-actions">
  170. <button class="secondary-btn" (click)="downloadImage(previewImageData)">下载</button>
  171. <button class="danger-btn" (click)="removeImageFromPreview()">删除</button>
  172. </div>
  173. </div>
  174. </div>
  175. </div>
  176. }
  177. <!-- 设计师日历弹窗 -->
  178. @if (showDesignerCalendar) {
  179. <div class="image-preview-modal" (click)="closeDesignerCalendar()">
  180. <div class="modal-backdrop"></div>
  181. <div class="modal-content" (click)="$event.stopPropagation()">
  182. <div class="modal-header">
  183. <h3>选择设计师与排期</h3>
  184. <button class="close-btn" (click)="closeDesignerCalendar()">
  185. <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  186. <line x1="18" y1="6" x2="6" y2="18"></line>
  187. <line x1="6" y1="6" x2="18" y2="18"></line>
  188. </svg>
  189. </button>
  190. </div>
  191. <div class="modal-body">
  192. <app-designer-calendar
  193. [designers]="calendarDesigners"
  194. [selectedDate]="selectedCalendarDate"
  195. [projectGroups]="calendarGroups"
  196. (designerSelected)="onCalendarDesignerSelected($event)"
  197. (assignmentRequested)="onCalendarAssignmentRequested($event)"
  198. ></app-designer-calendar>
  199. </div>
  200. </div>
  201. </div>
  202. }
  203. <!-- 提醒消息弹窗 -->
  204. @if (reminderMessage) {
  205. <div class="reminder-popup">
  206. {{ reminderMessage }}
  207. </div>
  208. }
  209. <!-- 标准阶段进度(5阶段) -->
  210. <!-- 已采用@for,不变 -->
  211. <!-- 顶部导航标签页 -->
  212. <!-- 原有代码保留 -->
  213. <!-- 水平导航栏 - 已移动到顶部,此处删除 -->
  214. <div class="tab-content">
  215. <!-- 项目进度标签页 -->
  216. @if (isActiveTab('progress')) {
  217. <div class="progress-tab-content">
  218. <div class="main-content-layout">
  219. <!-- 左侧保留 -->
  220. <div class="left-column">
  221. <div class="project-info-card card" [class.collapsed-card]="!isCustomerInfoExpanded">
  222. <div class="card-header" (click)="toggleCustomerInfo()" style="cursor: pointer;">
  223. <h2>客户信息</h2>
  224. <div class="header-actions">
  225. <div class="sync-status" [class.syncing]="isSyncingCustomerInfo">
  226. @if (isSyncingCustomerInfo) {
  227. <span class="sync-indicator">
  228. <span class="sync-spinner"></span>
  229. 同步中...
  230. </span>
  231. } @else if (lastSyncTime) {
  232. <span class="sync-time">
  233. <i class="icon-sync"></i>
  234. 已同步: {{ formatTime(lastSyncTime) }}
  235. </span>
  236. }
  237. </div>
  238. <div class="toggle-icon" [class.expanded]="isCustomerInfoExpanded">
  239. <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
  240. <path d="M4 6L8 10L12 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
  241. </svg>
  242. </div>
  243. </div>
  244. </div>
  245. @if (project) {
  246. <div class="info-grid" [class.collapsed]="!isCustomerInfoExpanded">
  247. <!-- 显示订单创建时填写的客户信息,如果有的话 -->
  248. @if (orderCreationData?.customerInfo) {
  249. <!-- 基本客户信息 -->
  250. <div class="info-item key-info"><label>客户姓名</label><span>{{ orderCreationData.customerInfo.name }}</span></div>
  251. <div class="info-item key-info"><label>联系电话</label><span>{{ orderCreationData.customerInfo.phone }}</span></div>
  252. @if (orderCreationData.customerInfo.wechat) {
  253. <div class="info-item"><label>微信号</label><span>{{ orderCreationData.customerInfo.wechat }}</span></div>
  254. }
  255. <div class="info-item"><label>客户类型</label><span>{{ orderCreationData.customerInfo.customerType }}</span></div>
  256. @if (orderCreationData.customerInfo.source) {
  257. <div class="info-item"><label>客户来源</label><span>{{ orderCreationData.customerInfo.source }}</span></div>
  258. }
  259. @if (orderCreationData.customerInfo.remark) {
  260. <div class="info-item"><label>备注信息</label><span>{{ orderCreationData.customerInfo.remark }}</span></div>
  261. }
  262. } @else {
  263. <!-- 默认显示项目信息 -->
  264. <div class="info-item key-info"><label>项目负责人</label><span>{{ project.assigneeName }}</span></div>
  265. }
  266. <div class="info-item"><label>项目创建</label><span>{{ formatDate(project.createdAt) }}</span></div>
  267. <div class="info-item"><label>截止日期</label><span>{{ formatDate(project.deadline) }}</span></div>
  268. </div>
  269. <div class="tags-container" [class.collapsed]="!isCustomerInfoExpanded" style="margin-top: 12px;">
  270. <div class="tag-section">
  271. <h3>客户标签</h3>
  272. <div class="tags">
  273. @for (tag of project.customerTags; track $index) {
  274. <span class="tag">{{ tag.source }} · {{ tag.needType }} · {{ tag.preference }} · {{ tag.colorAtmosphere }}</span>
  275. }
  276. @if (project.customerTags.length === 0) { <span class="desc">暂无标签</span> }
  277. </div>
  278. </div>
  279. </div>
  280. } @else {
  281. <div class="loading-state">
  282. <div class="loading-spinner"></div>
  283. <div>正在加载客户信息...</div>
  284. </div>
  285. }
  286. </div>
  287. <!-- 方案确认卡片 - 优化样式与左侧客户信息卡片保持一致 -->
  288. <div class="proposal-confirm-card card">
  289. <div class="card-header">
  290. <h2>方案确认</h2>
  291. <div class="sync-status">
  292. <span class="progress-text">{{ getRequiredStagesProgress() }}% 完成</span>
  293. </div>
  294. </div>
  295. <!-- 素材解析状态 -->
  296. @if (isAnalyzing) {
  297. <div class="analysis-progress">
  298. <div class="progress-header">
  299. <h4>正在解析素材...</h4>
  300. <span class="progress-percentage">{{ analysisProgress.toFixed(0) }}%</span>
  301. </div>
  302. <div class="progress-bar">
  303. <div class="progress-fill" [style.width.%]="analysisProgress"></div>
  304. </div>
  305. <p class="progress-description">AI正在分析您的需求,生成专属设计方案</p>
  306. </div>
  307. }
  308. <!-- 方案展示区域 -->
  309. @if (proposalAnalysis && !isAnalyzing) {
  310. <div class="proposal-display">
  311. <!-- 方案概览 -->
  312. <div class="proposal-overview">
  313. <div class="overview-header">
  314. <h3>{{ proposalAnalysis.name }}</h3>
  315. <div class="proposal-meta">
  316. <span class="version">{{ proposalAnalysis.version }}</span>
  317. <span class="feasibility-score">可行性: {{ proposalAnalysis.feasibility.overall }}%</span>
  318. </div>
  319. </div>
  320. <!-- 快速摘要 -->
  321. <div class="quick-summary">
  322. <div class="summary-item">
  323. <span class="label">设计风格</span>
  324. <span class="value">{{ getStyleSummary() }}</span>
  325. </div>
  326. <div class="summary-item">
  327. <span class="label">色彩方案</span>
  328. <span class="value">{{ getColorSummary() }}</span>
  329. </div>
  330. <div class="summary-item">
  331. <span class="label">空间效率</span>
  332. <span class="value">{{ getSpaceEfficiency() }}%</span>
  333. </div>
  334. </div>
  335. </div>
  336. <!-- 详细方案内容 -->
  337. <div class="proposal-details">
  338. <!-- 材质规格与分类 -->
  339. <div class="detail-section">
  340. <div class="section-header">
  341. <h4>材质规格与分类</h4>
  342. <span class="section-count">{{ (proposalAnalysis && proposalAnalysis.materials ? proposalAnalysis.materials.length : 0) }} 类材质</span>
  343. </div>
  344. <div class="materials-grid">
  345. @for (material of (proposalAnalysis && proposalAnalysis.materials ? proposalAnalysis.materials : []); track material.category) {
  346. <div class="material-card" [class]="material.usage.priority">
  347. <div class="material-header">
  348. <h5>{{ material.category }}</h5>
  349. <span class="usage-percentage">{{ material.usage.percentage }}%</span>
  350. </div>
  351. <div class="material-specs">
  352. <div class="spec-item">
  353. <span class="spec-label">类型</span>
  354. <span class="spec-value">{{ material.specifications.type }}</span>
  355. </div>
  356. <div class="spec-item">
  357. <span class="spec-label">等级</span>
  358. <span class="spec-value">{{ material.specifications.grade }}</span>
  359. </div>
  360. <div class="spec-item">
  361. <span class="spec-label">应用区域</span>
  362. <span class="spec-value">{{ material.usage.area }}</span>
  363. </div>
  364. </div>
  365. <div class="material-properties">
  366. <span class="property-tag">{{ material.properties.texture }}</span>
  367. <span class="property-tag">{{ material.properties.color }}</span>
  368. </div>
  369. </div>
  370. }
  371. </div>
  372. </div>
  373. <!-- 设计风格特征 -->
  374. <div class="detail-section">
  375. <div class="section-header">
  376. <h4>设计风格特征</h4>
  377. <span class="style-name">{{ (proposalAnalysis && proposalAnalysis.designStyle ? proposalAnalysis.designStyle.primaryStyle : null) || '未定义' }}</span>
  378. </div>
  379. <div class="style-elements">
  380. @for (element of (proposalAnalysis && proposalAnalysis.designStyle && proposalAnalysis.designStyle.styleElements ? proposalAnalysis.designStyle.styleElements : []); track element.element) {
  381. <div class="style-element">
  382. <div class="element-header">
  383. <span class="element-name">{{ element.element }}</span>
  384. <span class="influence-score">{{ element.influence }}%</span>
  385. </div>
  386. <div class="element-description">{{ element.description }}</div>
  387. <div class="influence-bar">
  388. <div class="influence-fill" [style.width.%]="element.influence"></div>
  389. </div>
  390. </div>
  391. }
  392. </div>
  393. <div class="style-characteristics">
  394. @for (char of (proposalAnalysis && proposalAnalysis.designStyle && proposalAnalysis.designStyle.characteristics ? proposalAnalysis.designStyle.characteristics : []); track char.feature) {
  395. <div class="characteristic-item" [class]="char.importance">
  396. <span class="char-feature">{{ char.feature }}</span>
  397. <span class="char-value">{{ char.value }}</span>
  398. </div>
  399. }
  400. </div>
  401. </div>
  402. <!-- 色彩搭配方案及占比分析 -->
  403. <div class="detail-section">
  404. <div class="section-header">
  405. <h4>色彩搭配方案及占比分析</h4>
  406. <span class="harmony-type">{{ (proposalAnalysis && proposalAnalysis.colorScheme && proposalAnalysis.colorScheme.harmony ? proposalAnalysis.colorScheme.harmony.type : null) || '未定义' }}</span>
  407. </div>
  408. <div class="color-palette">
  409. @for (color of (proposalAnalysis && proposalAnalysis.colorScheme && proposalAnalysis.colorScheme.palette ? proposalAnalysis.colorScheme.palette : []); track color.hex) {
  410. <div class="color-item" [class]="color.role">
  411. <div class="color-swatch" [style.background-color]="color.hex"></div>
  412. <div class="color-info">
  413. <div class="color-name">{{ color.color }}</div>
  414. <div class="color-percentage">{{ color.percentage }}%</div>
  415. <div class="color-role">{{ color.role }}</div>
  416. </div>
  417. <div class="color-codes">
  418. <span class="hex-code">{{ color.hex }}</span>
  419. <span class="rgb-code">RGB({{ color.rgb }})</span>
  420. </div>
  421. </div>
  422. }
  423. </div>
  424. <div class="color-psychology">
  425. <div class="psychology-item">
  426. <span class="label">氛围营造</span>
  427. <span class="value">{{ (proposalAnalysis && proposalAnalysis.colorScheme && proposalAnalysis.colorScheme.psychology ? proposalAnalysis.colorScheme.psychology.mood : null) || '未定义' }}</span>
  428. </div>
  429. <div class="psychology-item">
  430. <span class="label">空间感受</span>
  431. <span class="value">{{ (proposalAnalysis && proposalAnalysis.colorScheme && proposalAnalysis.colorScheme.psychology ? proposalAnalysis.colorScheme.psychology.atmosphere : null) || '未定义' }}</span>
  432. </div>
  433. </div>
  434. </div>
  435. <!-- 空间尺寸数据及功能分区 -->
  436. <div class="detail-section">
  437. <div class="section-header">
  438. <h4>空间尺寸数据及功能分区</h4>
  439. <span class="total-area">总面积 {{ (proposalAnalysis && proposalAnalysis.spaceLayout && proposalAnalysis.spaceLayout.dimensions ? proposalAnalysis.spaceLayout.dimensions.area : 0) || 0 }}㎡</span>
  440. </div>
  441. <div class="space-dimensions">
  442. <div class="dimension-item">
  443. <span class="dim-label">长度</span>
  444. <span class="dim-value">{{ (proposalAnalysis && proposalAnalysis.spaceLayout && proposalAnalysis.spaceLayout.dimensions ? proposalAnalysis.spaceLayout.dimensions.length : 0) || 0 }}m</span>
  445. </div>
  446. <div class="dimension-item">
  447. <span class="dim-label">宽度</span>
  448. <span class="dim-value">{{ (proposalAnalysis && proposalAnalysis.spaceLayout && proposalAnalysis.spaceLayout.dimensions ? proposalAnalysis.spaceLayout.dimensions.width : 0) || 0 }}m</span>
  449. </div>
  450. <div class="dimension-item">
  451. <span class="dim-label">层高</span>
  452. <span class="dim-value">{{ (proposalAnalysis && proposalAnalysis.spaceLayout && proposalAnalysis.spaceLayout.dimensions ? proposalAnalysis.spaceLayout.dimensions.height : 0) || 0 }}m</span>
  453. </div>
  454. <div class="dimension-item">
  455. <span class="dim-label">体积</span>
  456. <span class="dim-value">{{ (proposalAnalysis && proposalAnalysis.spaceLayout && proposalAnalysis.spaceLayout.dimensions ? proposalAnalysis.spaceLayout.dimensions.volume : 0) || 0 }}m³</span>
  457. </div>
  458. </div>
  459. <div class="functional-zones">
  460. @for (zone of (proposalAnalysis && proposalAnalysis.spaceLayout && proposalAnalysis.spaceLayout.functionalZones ? proposalAnalysis.spaceLayout.functionalZones : []); track zone.zone) {
  461. <div class="zone-card">
  462. <div class="zone-header">
  463. <h5>{{ zone.zone }}</h5>
  464. <div class="zone-stats">
  465. <span class="zone-area">{{ zone.area }}㎡</span>
  466. <span class="zone-percentage">{{ zone.percentage }}%</span>
  467. </div>
  468. </div>
  469. <div class="zone-requirements">
  470. <div class="requirements-label">功能需求</div>
  471. <div class="requirements-tags">
  472. @for (req of zone.requirements; track req) {
  473. <span class="requirement-tag">{{ req }}</span>
  474. }
  475. </div>
  476. </div>
  477. <div class="zone-furniture">
  478. <div class="furniture-label">家具配置</div>
  479. <div class="furniture-list">
  480. @for (furniture of zone.furniture; track furniture; let isLast = $last) {
  481. {{ furniture }}@if (!isLast) {、}
  482. }
  483. </div>
  484. </div>
  485. </div>
  486. }
  487. </div>
  488. </div>
  489. <!-- 预算与时间线 -->
  490. <div class="detail-section">
  491. <div class="section-header">
  492. <h4>预算与时间线</h4>
  493. <span class="total-budget">总预算 ¥{{ ((proposalAnalysis && proposalAnalysis.budget ? proposalAnalysis.budget.total : 0) || 0).toLocaleString() }}</span>
  494. </div>
  495. <div class="budget-breakdown">
  496. @for (item of (proposalAnalysis && proposalAnalysis.budget && proposalAnalysis.budget.breakdown ? proposalAnalysis.budget.breakdown : []); track item.category) {
  497. <div class="budget-item">
  498. <div class="budget-category">{{ item.category }}</div>
  499. <div class="budget-amount">¥{{ item.amount.toLocaleString() }}</div>
  500. <div class="budget-percentage">{{ item.percentage }}%</div>
  501. <div class="budget-bar">
  502. <div class="budget-fill" [style.width.%]="item.percentage"></div>
  503. </div>
  504. </div>
  505. }
  506. </div>
  507. <div class="timeline">
  508. @for (phase of (proposalAnalysis && proposalAnalysis.timeline ? proposalAnalysis.timeline : []); track phase.phase) {
  509. <div class="timeline-item">
  510. <div class="phase-name">{{ phase.phase }}</div>
  511. <div class="phase-duration">{{ phase.duration }}天</div>
  512. </div>
  513. }
  514. </div>
  515. </div>
  516. </div>
  517. <!-- 方案操作按钮 -->
  518. <div class="proposal-actions">
  519. <button class="confirm-btn primary" (click)="confirmProposal()">
  520. 确认此方案
  521. </button>
  522. <button class="adjust-btn secondary" (click)="startMaterialAnalysis()">
  523. 重新分析
  524. </button>
  525. </div>
  526. </div>
  527. }
  528. <!-- 需求信息展示区域(原有内容,当没有方案分析时显示) -->
  529. @if (areRequiredStagesCompleted() && !proposalAnalysis && !isAnalyzing) {
  530. <div class="info-grid">
  531. <!-- 色调信息 -->
  532. @if (requirementKeyInfo.colorAtmosphere.description) {
  533. <div class="info-item">
  534. <label>色调</label>
  535. <span>{{ requirementKeyInfo.colorAtmosphere.description }}</span>
  536. @if (requirementKeyInfo.colorAtmosphere.mainColor) {
  537. <div class="color-preview" [style.background-color]="requirementKeyInfo.colorAtmosphere.mainColor"></div>
  538. }
  539. </div>
  540. }
  541. <!-- 材质信息 -->
  542. @if (requirementKeyInfo.colorAtmosphere.materials && requirementKeyInfo.colorAtmosphere.materials.length > 0) {
  543. <div class="info-item">
  544. <label>材质</label>
  545. <span>
  546. @for (material of requirementKeyInfo.colorAtmosphere.materials; track material; let isLast = $last) {
  547. {{ material }}@if (!isLast) {, }
  548. }
  549. </span>
  550. </div>
  551. }
  552. <!-- 空间结构 -->
  553. @if (requirementKeyInfo.spaceStructure.aspectRatio > 0) {
  554. <div class="info-item">
  555. <label>空间比例</label>
  556. <span>{{ requirementKeyInfo.spaceStructure.aspectRatio.toFixed(1) }}</span>
  557. </div>
  558. }
  559. @if (requirementKeyInfo.spaceStructure.ceilingHeight > 0) {
  560. <div class="info-item">
  561. <label>层高</label>
  562. <span>{{ requirementKeyInfo.spaceStructure.ceilingHeight }}m</span>
  563. </div>
  564. }
  565. <!-- 材质权重 -->
  566. @if (requirementKeyInfo.materialWeights.woodRatio > 0) {
  567. <div class="info-item">
  568. <label>木质比例</label>
  569. <span>{{ requirementKeyInfo.materialWeights.woodRatio }}%</span>
  570. </div>
  571. }
  572. @if (requirementKeyInfo.materialWeights.fabricRatio > 0) {
  573. <div class="info-item">
  574. <label>布艺比例</label>
  575. <span>{{ requirementKeyInfo.materialWeights.fabricRatio }}%</span>
  576. </div>
  577. }
  578. @if (requirementKeyInfo.materialWeights.metalRatio > 0) {
  579. <div class="info-item">
  580. <label>金属比例</label>
  581. <span>{{ requirementKeyInfo.materialWeights.metalRatio }}%</span>
  582. </div>
  583. }
  584. <!-- 预设氛围 -->
  585. @if (requirementKeyInfo.presetAtmosphere.name) {
  586. <div class="info-item">
  587. <label>预设氛围</label>
  588. <span>{{ requirementKeyInfo.presetAtmosphere.name }}</span>
  589. </div>
  590. }
  591. <!-- 订单金额 -->
  592. <!-- 订单金额 -->
  593. <div class="info-item">
  594. <label>订单金额</label>
  595. <span>¥{{ orderAmount || 0 }}</span>
  596. </div>
  597. </div>
  598. <!-- 开始分析按钮 -->
  599. <div class="proposal-actions">
  600. <button class="confirm-btn" (click)="startMaterialAnalysis()">
  601. 开始素材解析
  602. </button>
  603. </div>
  604. } @else if (!areRequiredStagesCompleted()) {
  605. <!-- 等待状态 -->
  606. <div class="waiting-state">
  607. <div class="waiting-content">
  608. <h5>等待需求信息完善</h5>
  609. <p>需求沟通阶段完成后,方案确认功能将自动开启</p>
  610. <div class="progress-info">
  611. <span>当前进度: {{ getRequiredStagesProgress() }}%</span>
  612. </div>
  613. </div>
  614. </div>
  615. }
  616. </div>
  617. </div>
  618. <!-- 右侧三分之二 - 制作流程进度 -->
  619. <div class="right-column">
  620. <!-- 移除顶部四个圆圈,直接展示阶段内容 -->
  621. <!-- 新增:客户信息右侧 2x2 售后模块布局 -->
  622. @if (expandedSection === 'aftercare') {
  623. <!-- 上方 2x2 网格:前4个模块 -->
  624. <div class="aftercare-grid">
  625. <!-- 1/4:尾款结算 -->
  626. <div class="aftercare-module settlement-module card">
  627. <div class="module-header">
  628. <h2>💰 尾款结算</h2>
  629. <p class="module-description">技术完成验收后自动发起尾款结算流程,支持多种支付方式自动化处理</p>
  630. </div>
  631. <div class="settlement-automation">
  632. <div class="automation-features">
  633. <div class="feature-card clickable" (click)="showFeatureDetail('自动触发流程', '技术验收完成后,系统会自动创建尾款结算记录,并通知客服跟进。整个流程无需人工干预,提高结算效率。')">
  634. <div class="feature-icon">🤖</div>
  635. <div class="feature-content">
  636. <h4>自动触发流程</h4>
  637. <p>技术验收完成后自动发起尾款结算申请</p>
  638. </div>
  639. </div>
  640. <div class="feature-card clickable" (click)="showFeatureDetail('自动解密发送', '客户通过小程序完成支付后,系统自动解锁渲染大图,并一键发送给客户,无需手动操作。')">
  641. <div class="feature-icon">🔓</div>
  642. <div class="feature-content">
  643. <h4>自动解密发送</h4>
  644. <p>小程序支付自动解密并发送大图给客户</p>
  645. </div>
  646. </div>
  647. <div class="feature-card clickable" (click)="showFeatureDetail('凭证智能识别', '上传微信或支付宝支付截图后,AI自动识别支付金额、支付方式和支付时间,提高录入效率。')">
  648. <div class="feature-icon">📱</div>
  649. <div class="feature-content">
  650. <h4>凭证智能识别</h4>
  651. <p>上传微信/支付宝截图自动提取金额和支付方式</p>
  652. </div>
  653. </div>
  654. <div class="feature-card clickable" (click)="showFeatureDetail('自动通知', '支付完成后,系统自动向客户和相关人员发送通知,告知尾款已到账、大图已解锁,确保信息及时传达。')">
  655. <div class="feature-icon">🔔</div>
  656. <div class="feature-content">
  657. <h4>自动通知</h4>
  658. <p>支付完成后自动发送"尾款已到账,大图已解锁"通知</p>
  659. </div>
  660. </div>
  661. </div>
  662. <div class="settlement-status">
  663. <app-settlement-card [settlements]="settlements"></app-settlement-card>
  664. </div>
  665. @if (canEditSection('aftercare') && isTechnicalView()) {
  666. <div class="automation-actions">
  667. <button
  668. class="primary-btn automation-btn"
  669. (click)="initiateAutoSettlement()"
  670. [disabled]="isAutoSettling"
  671. type="button">
  672. @if (isAutoSettling) {
  673. <span class="loading-spinner"></span>
  674. <span>自动化处理中...</span>
  675. } @else {
  676. <span>🚀 启动自动化结算</span>
  677. }
  678. </button>
  679. <button class="secondary-btn" (click)="uploadPaymentProof()" type="button">
  680. <span class="upload-icon">📎</span>
  681. <span>上传支付凭证</span>
  682. </button>
  683. </div>
  684. }
  685. </div>
  686. </div>
  687. <!-- 2/4:全景图合成 -->
  688. <div class="aftercare-module panoramic-module card">
  689. <div class="module-header">
  690. <h2>🖼️ 全景图合成</h2>
  691. <p class="module-description">集成内部全景图合成服务器,技术上传图片自动生成漫游式全景链接</p>
  692. </div>
  693. <div class="panoramic-synthesis">
  694. <div class="panoramic-features">
  695. <div class="feature-card clickable" (click)="showFeatureDetail('KR Panel集成', '系统集成了专业的KR Panel全景图合成工具,支持高质量全景图生成,确保最终效果达到行业标准。')">
  696. <div class="feature-icon">🖥️</div>
  697. <div class="feature-content">
  698. <h4>KR Panel集成</h4>
  699. <p>集成专业全景图合成工具,确保合成质量</p>
  700. </div>
  701. </div>
  702. <div class="feature-card clickable" (click)="showFeatureDetail('智能空间标注', '技术人员上传图片时,系统会智能识别文件名中的空间信息(如「客厅-角度1.jpg」),自动完成空间分类和标注。')">
  703. <div class="feature-icon">📸</div>
  704. <div class="feature-content">
  705. <h4>智能空间标注</h4>
  706. <p>技术上传图片并标注空间名称(如"客厅-角度1")</p>
  707. </div>
  708. </div>
  709. <div class="feature-card clickable" (click)="showFeatureDetail('自动生成链接', '全景图合成完成后,系统自动生成可分享的漫游链接,客户可以通过链接在线浏览360度全景效果。')">
  710. <div class="feature-icon">🔗</div>
  711. <div class="feature-content">
  712. <h4>自动生成链接</h4>
  713. <p>自动生成漫游式全景链接并发送给客户</p>
  714. </div>
  715. </div>
  716. </div>
  717. <div class="recent-syntheses">
  718. @if (panoramicSyntheses && panoramicSyntheses.length > 0) {
  719. <div class="panoramic-card-list">
  720. @for (item of panoramicSyntheses; track item.id) {
  721. <app-panoramic-synthesis-card
  722. [synthesis]="item"
  723. [showActions]="true">
  724. </app-panoramic-synthesis-card>
  725. }
  726. </div>
  727. } @else {
  728. <div class="desc">暂无最近合成记录</div>
  729. }
  730. </div>
  731. @if (canEditSection('aftercare')) {
  732. <div class="panoramic-actions">
  733. <button class="primary-btn" (click)="startPanoramicSynthesis()" type="button">
  734. <span>🎨 开始合成</span>
  735. </button>
  736. <button class="secondary-btn" (click)="viewPanoramicGallery()" type="button">
  737. <span>📁 查看全景图库</span>
  738. </button>
  739. </div>
  740. }
  741. </div>
  742. </div>
  743. <!-- 3/4:客户评价 -->
  744. <div class="aftercare-module review-module card">
  745. <div class="module-header">
  746. <h2>⭐ 客户评价</h2>
  747. <p class="module-description">邀请客户进行多维度评价并生成评价链接</p>
  748. </div>
  749. <div class="customer-review-enhanced">
  750. <div class="review-form-container">
  751. <app-customer-review-form
  752. [projectName]="project?.name || ''"
  753. [designerName]="getCurrentDesignerName()"
  754. (reviewSubmitted)="onReviewSubmitted($event)"
  755. (reviewSaved)="onReviewSaved($event)">
  756. </app-customer-review-form>
  757. </div>
  758. <div class="review-card-container" style="margin-top:12px;">
  759. <app-customer-review-card [feedbacks]="feedbacks" [detailedReviews]="detailedReviews"></app-customer-review-card>
  760. </div>
  761. @if (canEditSection('aftercare')) {
  762. <div class="review-actions">
  763. <button class="primary-btn" (click)="generateReviewLink()" type="button">
  764. <span>🔗 生成评价链接</span>
  765. </button>
  766. <button class="secondary-btn" (click)="confirmCustomerReview()" type="button">
  767. <span>✅ 确认评价完成</span>
  768. </button>
  769. </div>
  770. }
  771. </div>
  772. </div>
  773. <!-- 4/4:投诉处理 -->
  774. <div class="aftercare-module complaint-module card">
  775. <div class="module-header">
  776. <h2>📋 投诉处理</h2>
  777. <p class="module-description">支持人工创建和关键词自动抓取投诉,提升处理效率</p>
  778. </div>
  779. <div class="complaint-management-enhanced">
  780. <div class="complaint-features">
  781. <div class="feature-card">
  782. <div class="feature-icon">👥</div>
  783. <div class="feature-content">
  784. <h4>人工创建</h4>
  785. <p>组长或客服人工创建投诉记录</p>
  786. </div>
  787. </div>
  788. <div class="feature-card">
  789. <div class="feature-icon">🔍</div>
  790. <div class="feature-content">
  791. <h4>关键词抓取</h4>
  792. <p>自动监测企业微信群关键词(不满意、投诉、退款)</p>
  793. </div>
  794. </div>
  795. <div class="feature-card">
  796. <div class="feature-icon">🏷️</div>
  797. <div class="feature-content">
  798. <h4>智能标注</h4>
  799. <p>自动标注投诉环节和核心问题</p>
  800. </div>
  801. </div>
  802. <div class="feature-card">
  803. <div class="feature-icon">📊</div>
  804. <div class="feature-content">
  805. <h4>实时更新</h4>
  806. <p>处理进度实时更新至系统</p>
  807. </div>
  808. </div>
  809. </div>
  810. <div class="complaint-content">
  811. <app-complaint-card [complaints]="exceptionHistories"></app-complaint-card>
  812. </div>
  813. @if (canEditSection('aftercare')) {
  814. <div class="complaint-actions">
  815. <button class="primary-btn" (click)="createComplaintManually()" type="button">
  816. <span>📝 人工创建投诉</span>
  817. </button>
  818. <button class="secondary-btn" (click)="setupKeywordMonitoring()" type="button">
  819. <span>⚙️ 设置关键词监测</span>
  820. </button>
  821. <button class="success-btn" (click)="confirmComplaint()" type="button">
  822. <span>✅ 确认投诉处理完成</span>
  823. </button>
  824. </div>
  825. }
  826. </div>
  827. </div>
  828. </div>
  829. <!-- 下方横向展示:项目复盘模块 -->
  830. <div class="project-review-section">
  831. <div class="review-section-header">
  832. <div class="header-left">
  833. <h2 class="section-title">📊 项目复盘</h2>
  834. <p class="section-subtitle">基于SOP执行数据和经验总结,自动生成复盘报告</p>
  835. </div>
  836. <div class="header-right">
  837. @if (canEditSection('aftercare')) {
  838. <button class="generate-review-btn" (click)="generateReviewReport()" [disabled]="isGeneratingReview">
  839. @if (isGeneratingReview) {
  840. <span class="loading-spinner"></span>
  841. 生成中...
  842. } @else {
  843. 📊 生成复盘报告
  844. }
  845. </button>
  846. @if (projectReview) {
  847. <button class="export-review-btn" (click)="exportReviewReport()">
  848. 📤 导出报告
  849. </button>
  850. }
  851. }
  852. </div>
  853. </div>
  854. <!-- Tab切换导航 -->
  855. <div class="review-tabs">
  856. <button
  857. class="review-tab"
  858. [class.active]="activeReviewTab === 'sop'"
  859. (click)="activeReviewTab = 'sop'">
  860. <span class="tab-icon">📈</span>
  861. <span class="tab-label">SOP执行数据</span>
  862. </button>
  863. <button
  864. class="review-tab"
  865. [class.active]="activeReviewTab === 'experience'"
  866. (click)="activeReviewTab = 'experience'">
  867. <span class="tab-icon">💡</span>
  868. <span class="tab-label">经验复盘</span>
  869. </button>
  870. <button
  871. class="review-tab"
  872. [class.active]="activeReviewTab === 'suggestions'"
  873. (click)="activeReviewTab = 'suggestions'">
  874. <span class="tab-icon">🔧</span>
  875. <span class="tab-label">优化建议</span>
  876. </button>
  877. </div>
  878. <!-- Tab内容区域 -->
  879. <div class="review-content-area">
  880. <!-- SOP执行数据 -->
  881. @if (activeReviewTab === 'sop') {
  882. <div class="sop-data-content">
  883. <div class="sop-metrics-grid">
  884. <!-- 关键指标卡片 -->
  885. <div class="metric-card">
  886. <div class="metric-header">
  887. <span class="metric-icon">💬</span>
  888. <h4 class="metric-title">需求沟通次数</h4>
  889. </div>
  890. <div class="metric-value-large">{{ sopMetrics?.communicationCount || 0 }}</div>
  891. <div class="metric-footer">
  892. <span class="metric-label">平均水平:</span>
  893. <span class="metric-benchmark">{{ sopMetrics?.avgCommunication || 5 }}次</span>
  894. <span class="metric-status" [class.good]="(sopMetrics?.communicationCount || 0) <= (sopMetrics?.avgCommunication || 5)" [class.warning]="(sopMetrics?.communicationCount || 0) > (sopMetrics?.avgCommunication || 5)">
  895. {{ (sopMetrics?.communicationCount || 0) <= (sopMetrics?.avgCommunication || 5) ? '✓ 良好' : '⚠ 超标' }}
  896. </span>
  897. </div>
  898. </div>
  899. <div class="metric-card">
  900. <div class="metric-header">
  901. <span class="metric-icon">🔄</span>
  902. <h4 class="metric-title">改图次数</h4>
  903. </div>
  904. <div class="metric-value-large">{{ sopMetrics?.revisionCount || 0 }}</div>
  905. <div class="metric-footer">
  906. <span class="metric-label">平均水平:</span>
  907. <span class="metric-benchmark">{{ sopMetrics?.avgRevision || 2 }}次</span>
  908. <span class="metric-status" [class.good]="(sopMetrics?.revisionCount || 0) <= (sopMetrics?.avgRevision || 2)" [class.warning]="(sopMetrics?.revisionCount || 0) > (sopMetrics?.avgRevision || 2)">
  909. {{ (sopMetrics?.revisionCount || 0) <= (sopMetrics?.avgRevision || 2) ? '✓ 良好' : '⚠ 超标' }}
  910. </span>
  911. </div>
  912. </div>
  913. <div class="metric-card">
  914. <div class="metric-header">
  915. <span class="metric-icon">⏱️</span>
  916. <h4 class="metric-title">交付周期</h4>
  917. </div>
  918. <div class="metric-value-large">{{ sopMetrics?.deliveryCycle || 0 }}<span class="unit">天</span></div>
  919. <div class="metric-footer">
  920. <span class="metric-label">标准周期:</span>
  921. <span class="metric-benchmark">{{ sopMetrics?.standardCycle || 15 }}天</span>
  922. <span class="metric-status" [class.good]="(sopMetrics?.deliveryCycle || 0) <= (sopMetrics?.standardCycle || 15)" [class.warning]="(sopMetrics?.deliveryCycle || 0) > (sopMetrics?.standardCycle || 15)">
  923. {{ (sopMetrics?.deliveryCycle || 0) <= (sopMetrics?.standardCycle || 15) ? '✓ 达标' : '⚠ 延期' }}
  924. </span>
  925. </div>
  926. </div>
  927. <div class="metric-card">
  928. <div class="metric-header">
  929. <span class="metric-icon">⭐</span>
  930. <h4 class="metric-title">客户满意度</h4>
  931. </div>
  932. <div class="metric-value-large">{{ sopMetrics?.customerSatisfaction || 0 }}<span class="unit">/5</span></div>
  933. <div class="metric-footer">
  934. <div class="satisfaction-stars">
  935. @for (star of [1,2,3,4,5]; track star) {
  936. <span class="star" [class.filled]="star <= (sopMetrics?.customerSatisfaction || 0)">★</span>
  937. }
  938. </div>
  939. </div>
  940. </div>
  941. </div>
  942. <!-- 阶段执行详情 -->
  943. <div class="sop-stages-section">
  944. <h3 class="subsection-title">各阶段执行详情</h3>
  945. <div class="stages-timeline">
  946. @for (stage of sopStagesData; track stage.name) {
  947. <div class="stage-timeline-item" [class.completed]="stage.status === 'completed'" [class.ongoing]="stage.status === 'ongoing'" [class.delayed]="stage.isDelayed">
  948. <div class="stage-indicator">
  949. <div class="stage-dot"></div>
  950. @if (!$last) {
  951. <div class="stage-line"></div>
  952. }
  953. </div>
  954. <div class="stage-content-card">
  955. <div class="stage-card-header">
  956. <h4 class="stage-name">{{ stage.name }}</h4>
  957. <span class="stage-status-badge" [class]="stage.status">
  958. {{ stage.statusText }}
  959. </span>
  960. </div>
  961. <div class="stage-metrics">
  962. <div class="stage-metric-item">
  963. <span class="label">计划时长:</span>
  964. <span class="value">{{ stage.plannedDuration }}天</span>
  965. </div>
  966. <div class="stage-metric-item">
  967. <span class="label">实际时长:</span>
  968. <span class="value" [class.warning]="stage.actualDuration > stage.plannedDuration">{{ stage.actualDuration }}天</span>
  969. </div>
  970. <div class="stage-metric-item">
  971. <span class="label">执行评分:</span>
  972. <span class="value score" [class]="getScoreClass(stage.score)">{{ stage.score }}/100</span>
  973. </div>
  974. </div>
  975. @if (stage.issues && stage.issues.length > 0) {
  976. <div class="stage-issues">
  977. <span class="issues-label">⚠️ 问题:</span>
  978. <span class="issues-text">{{ stage.issues.join('、') }}</span>
  979. </div>
  980. }
  981. </div>
  982. </div>
  983. }
  984. </div>
  985. </div>
  986. <!-- 数据图表展示 -->
  987. <div class="sop-charts-section">
  988. <h3 class="subsection-title">数据分析图表</h3>
  989. <div class="charts-grid">
  990. <div class="chart-card">
  991. <h4 class="chart-title">阶段耗时对比</h4>
  992. <div class="chart-placeholder">
  993. <div class="bar-chart">
  994. @for (stage of sopStagesData; track stage.name) {
  995. <div class="bar-group">
  996. <div class="bar-label">{{ stage.name }}</div>
  997. <div class="bars">
  998. <div class="bar planned" [style.height.%]="(stage.plannedDuration / getMaxDuration()) * 100">
  999. <span class="bar-value">{{ stage.plannedDuration }}</span>
  1000. </div>
  1001. <div class="bar actual" [style.height.%]="(stage.actualDuration / getMaxDuration()) * 100">
  1002. <span class="bar-value">{{ stage.actualDuration }}</span>
  1003. </div>
  1004. </div>
  1005. </div>
  1006. }
  1007. </div>
  1008. <div class="chart-legend">
  1009. <span class="legend-item"><span class="legend-color planned"></span>计划时长</span>
  1010. <span class="legend-item"><span class="legend-color actual"></span>实际时长</span>
  1011. </div>
  1012. </div>
  1013. </div>
  1014. <div class="chart-card">
  1015. <h4 class="chart-title">执行评分雷达图</h4>
  1016. <div class="chart-placeholder radar-chart">
  1017. <div class="radar-info">
  1018. <p>各阶段平均得分:<strong>{{ getAverageScore() }}/100</strong></p>
  1019. <div class="score-items">
  1020. @for (stage of sopStagesData.slice(0, 5); track stage.name) {
  1021. <div class="score-item">
  1022. <span class="stage-label">{{ stage.name }}:</span>
  1023. <span class="stage-score" [class]="getScoreClass(stage.score)">{{ stage.score }}</span>
  1024. </div>
  1025. }
  1026. </div>
  1027. </div>
  1028. </div>
  1029. </div>
  1030. </div>
  1031. </div>
  1032. </div>
  1033. }
  1034. <!-- 经验复盘 -->
  1035. @if (activeReviewTab === 'experience') {
  1036. <div class="experience-content">
  1037. <div class="experience-intro">
  1038. <p class="intro-text">基于企业微信沟通记录智能提取关键信息,全面复盘项目过程</p>
  1039. </div>
  1040. <div class="experience-grid">
  1041. <!-- 客户需求 -->
  1042. <div class="experience-card needs-card">
  1043. <div class="card-header-custom">
  1044. <span class="card-icon">📝</span>
  1045. <h3 class="card-title-custom">客户需求</h3>
  1046. <span class="count-badge">{{ experienceData?.customerNeeds?.length || 0 }}项</span>
  1047. </div>
  1048. <div class="card-content-scrollable">
  1049. @if (experienceData?.customerNeeds && experienceData.customerNeeds.length > 0) {
  1050. <ul class="experience-list">
  1051. @for (need of experienceData.customerNeeds; track $index) {
  1052. <li class="experience-item">
  1053. <span class="item-bullet">•</span>
  1054. <div class="item-content">
  1055. <p class="item-text">{{ need.text }}</p>
  1056. <div class="item-meta">
  1057. <span class="meta-time">{{ need.timestamp }}</span>
  1058. <span class="meta-source">来源:{{ need.source }}</span>
  1059. </div>
  1060. </div>
  1061. </li>
  1062. }
  1063. </ul>
  1064. } @else {
  1065. <div class="empty-state-small">暂无客户需求记录</div>
  1066. }
  1067. </div>
  1068. </div>
  1069. <!-- 客户顾虑 -->
  1070. <div class="experience-card concerns-card">
  1071. <div class="card-header-custom">
  1072. <span class="card-icon">🤔</span>
  1073. <h3 class="card-title-custom">客户顾虑</h3>
  1074. <span class="count-badge">{{ experienceData?.customerConcerns?.length || 0 }}项</span>
  1075. </div>
  1076. <div class="card-content-scrollable">
  1077. @if (experienceData?.customerConcerns && experienceData.customerConcerns.length > 0) {
  1078. <ul class="experience-list">
  1079. @for (concern of experienceData.customerConcerns; track $index) {
  1080. <li class="experience-item concern">
  1081. <span class="item-bullet">⚠️</span>
  1082. <div class="item-content">
  1083. <p class="item-text">{{ concern.text }}</p>
  1084. <div class="item-meta">
  1085. <span class="meta-time">{{ concern.timestamp }}</span>
  1086. @if (concern.resolved) {
  1087. <span class="meta-resolved">✓ 已解决</span>
  1088. } @else {
  1089. <span class="meta-unresolved">待处理</span>
  1090. }
  1091. </div>
  1092. </div>
  1093. </li>
  1094. }
  1095. </ul>
  1096. } @else {
  1097. <div class="empty-state-small">暂无客户顾虑记录</div>
  1098. }
  1099. </div>
  1100. </div>
  1101. <!-- 投诉点 -->
  1102. <div class="experience-card complaints-card">
  1103. <div class="card-header-custom">
  1104. <span class="card-icon">📢</span>
  1105. <h3 class="card-title-custom">投诉点</h3>
  1106. <span class="count-badge warning">{{ experienceData?.complaintPoints?.length || 0 }}项</span>
  1107. </div>
  1108. <div class="card-content-scrollable">
  1109. @if (experienceData?.complaintPoints && experienceData.complaintPoints.length > 0) {
  1110. <ul class="experience-list">
  1111. @for (complaint of experienceData.complaintPoints; track $index) {
  1112. <li class="experience-item complaint">
  1113. <span class="item-bullet">❗</span>
  1114. <div class="item-content">
  1115. <p class="item-text">{{ complaint.text }}</p>
  1116. <div class="item-meta">
  1117. <span class="meta-time">{{ complaint.timestamp }}</span>
  1118. <span class="meta-severity" [class]="complaint.severity">{{ complaint.severityText }}</span>
  1119. </div>
  1120. @if (complaint.resolution) {
  1121. <div class="item-resolution">
  1122. <span class="resolution-label">解决方案:</span>
  1123. <span class="resolution-text">{{ complaint.resolution }}</span>
  1124. </div>
  1125. }
  1126. </div>
  1127. </li>
  1128. }
  1129. </ul>
  1130. } @else {
  1131. <div class="empty-state-small">✓ 暂无投诉记录</div>
  1132. }
  1133. </div>
  1134. </div>
  1135. <!-- 项目亮点 -->
  1136. <div class="experience-card highlights-card">
  1137. <div class="card-header-custom">
  1138. <span class="card-icon">✨</span>
  1139. <h3 class="card-title-custom">项目亮点</h3>
  1140. <span class="count-badge success">{{ experienceData?.projectHighlights?.length || 0 }}项</span>
  1141. </div>
  1142. <div class="card-content-scrollable">
  1143. @if (experienceData?.projectHighlights && experienceData.projectHighlights.length > 0) {
  1144. <ul class="experience-list">
  1145. @for (highlight of experienceData.projectHighlights; track $index) {
  1146. <li class="experience-item highlight">
  1147. <span class="item-bullet">⭐</span>
  1148. <div class="item-content">
  1149. <p class="item-text">{{ highlight.text }}</p>
  1150. <div class="item-meta">
  1151. <span class="meta-category">{{ highlight.category }}</span>
  1152. @if (highlight.praised) {
  1153. <span class="meta-praised">👍 客户点赞</span>
  1154. }
  1155. </div>
  1156. </div>
  1157. </li>
  1158. }
  1159. </ul>
  1160. } @else {
  1161. <div class="empty-state-small">暂无项目亮点记录</div>
  1162. }
  1163. </div>
  1164. </div>
  1165. </div>
  1166. <!-- 沟通记录摘要 -->
  1167. <div class="communication-summary">
  1168. <h3 class="subsection-title">关键沟通记录</h3>
  1169. <div class="communication-timeline">
  1170. @if (experienceData?.communications && experienceData.communications.length > 0) {
  1171. @for (comm of experienceData.communications; track $index) {
  1172. <div class="communication-item">
  1173. <div class="comm-time">{{ comm.timestamp }}</div>
  1174. <div class="comm-content">
  1175. <div class="comm-header">
  1176. <span class="comm-participant">{{ comm.participant }}</span>
  1177. <span class="comm-type" [class]="comm.type">{{ comm.typeText }}</span>
  1178. </div>
  1179. <p class="comm-message">{{ comm.message }}</p>
  1180. @if (comm.attachments && comm.attachments.length > 0) {
  1181. <div class="comm-attachments">
  1182. <span class="attachment-icon">📎</span>
  1183. <span class="attachment-count">{{ comm.attachments.length }}个附件</span>
  1184. </div>
  1185. }
  1186. </div>
  1187. </div>
  1188. }
  1189. } @else {
  1190. <div class="empty-state-small">暂无沟通记录</div>
  1191. }
  1192. </div>
  1193. </div>
  1194. </div>
  1195. }
  1196. <!-- 优化建议 -->
  1197. @if (activeReviewTab === 'suggestions') {
  1198. <div class="suggestions-content">
  1199. <div class="suggestions-intro">
  1200. <div class="intro-card">
  1201. <h3 class="intro-title">智能分析与建议</h3>
  1202. <p class="intro-desc">基于项目执行数据和历史经验,为您提供具体可操作的优化建议</p>
  1203. </div>
  1204. </div>
  1205. <div class="suggestions-list">
  1206. @if (optimizationSuggestions && optimizationSuggestions.length > 0) {
  1207. @for (suggestion of optimizationSuggestions; track $index) {
  1208. <div class="suggestion-card" [class]="suggestion.priority">
  1209. <div class="suggestion-header">
  1210. <div class="header-left-content">
  1211. <span class="suggestion-priority-badge" [class]="suggestion.priority">
  1212. {{ suggestion.priorityText }}
  1213. </span>
  1214. <span class="suggestion-category">{{ suggestion.category }}</span>
  1215. </div>
  1216. <div class="header-right-content">
  1217. <span class="suggestion-impact">预期提升:<strong>{{ suggestion.expectedImprovement }}</strong></span>
  1218. </div>
  1219. </div>
  1220. <div class="suggestion-body">
  1221. <div class="suggestion-problem">
  1222. <h4 class="problem-title">🔍 问题分析</h4>
  1223. <p class="problem-text">{{ suggestion.problem }}</p>
  1224. <div class="problem-data">
  1225. @for (data of suggestion.dataPoints; track $index) {
  1226. <div class="data-point">
  1227. <span class="data-label">{{ data.label }}:</span>
  1228. <span class="data-value" [class.warning]="data.isWarning">{{ data.value }}</span>
  1229. </div>
  1230. }
  1231. </div>
  1232. </div>
  1233. <div class="suggestion-solution">
  1234. <h4 class="solution-title">💡 优化建议</h4>
  1235. <p class="solution-text">{{ suggestion.solution }}</p>
  1236. </div>
  1237. <div class="suggestion-actions-plan">
  1238. <h4 class="actions-title">📋 行动计划</h4>
  1239. <ul class="actions-list">
  1240. @for (action of suggestion.actionPlan; track $index) {
  1241. <li class="action-item">
  1242. <span class="action-step">{{ $index + 1 }}.</span>
  1243. <span class="action-text">{{ action }}</span>
  1244. </li>
  1245. }
  1246. </ul>
  1247. </div>
  1248. @if (suggestion.references && suggestion.references.length > 0) {
  1249. <div class="suggestion-references">
  1250. <span class="references-label">📚 参考案例:</span>
  1251. @for (ref of suggestion.references; track $index) {
  1252. <span class="reference-tag">{{ ref }}</span>
  1253. }
  1254. </div>
  1255. }
  1256. </div>
  1257. <div class="suggestion-footer">
  1258. <button class="action-btn accept" (click)="acceptSuggestion(suggestion)">
  1259. ✓ 采纳建议
  1260. </button>
  1261. <button class="action-btn detail" (click)="viewSuggestionDetail(suggestion)">
  1262. 查看详情
  1263. </button>
  1264. </div>
  1265. </div>
  1266. }
  1267. } @else {
  1268. <div class="empty-state-large">
  1269. <div class="empty-icon">📊</div>
  1270. <h3 class="empty-title">暂无优化建议</h3>
  1271. <p class="empty-desc">项目数据收集完成后将自动生成智能优化建议</p>
  1272. </div>
  1273. }
  1274. </div>
  1275. <!-- 建议统计 -->
  1276. @if (optimizationSuggestions && optimizationSuggestions.length > 0) {
  1277. <div class="suggestions-stats">
  1278. <h3 class="subsection-title">建议统计</h3>
  1279. <div class="stats-grid">
  1280. <div class="stat-card">
  1281. <div class="stat-icon">🔴</div>
  1282. <div class="stat-content">
  1283. <div class="stat-value">{{ getSuggestionCountByPriority('high') }}</div>
  1284. <div class="stat-label">高优先级</div>
  1285. </div>
  1286. </div>
  1287. <div class="stat-card">
  1288. <div class="stat-icon">🟡</div>
  1289. <div class="stat-content">
  1290. <div class="stat-value">{{ getSuggestionCountByPriority('medium') }}</div>
  1291. <div class="stat-label">中优先级</div>
  1292. </div>
  1293. </div>
  1294. <div class="stat-card">
  1295. <div class="stat-icon">🟢</div>
  1296. <div class="stat-content">
  1297. <div class="stat-value">{{ getSuggestionCountByPriority('low') }}</div>
  1298. <div class="stat-label">低优先级</div>
  1299. </div>
  1300. </div>
  1301. <div class="stat-card">
  1302. <div class="stat-icon">📈</div>
  1303. <div class="stat-content">
  1304. <div class="stat-value">{{ getAverageImprovementPercent() }}%</div>
  1305. <div class="stat-label">平均预期提升</div>
  1306. </div>
  1307. </div>
  1308. </div>
  1309. </div>
  1310. }
  1311. </div>
  1312. }
  1313. </div>
  1314. </div>
  1315. }
  1316. <!-- 串式流程:10个阶段横向排列(保持) -->
  1317. <div class="stage-progress-container">
  1318. @for (stage of getVisibleStages(); track stage) {
  1319. <div class="vertical-stage-block" [attr.id]="stageToAnchor(stage)" [class.active]="getStageStatus(stage) === 'active'">
  1320. @if (stage !== '订单创建') {
  1321. <div class="vertical-stage-header">
  1322. <span class="dot" [class.completed]="getStageStatus(stage) === 'completed'" [class.active]="getStageStatus(stage) === 'active'"></span>
  1323. <h3>{{ stage === '需求沟通' ? '需求映射' : (stage === '方案确认' ? '色彩分析报告' : stage) }}</h3>
  1324. </div>
  1325. }
  1326. <!-- 直接复用原阶段内容卡片:按stage匹配显示 -->
  1327. <div class="vertical-stage-body">
  1328. @if (stage === '订单创建') {
  1329. <!-- 重构后的订单创建表单 - 组合order-creation-extra和consultation-order-panel -->
  1330. <div class="order-creation-form-container">
  1331. <!-- 订单分配头部 -->
  1332. <div class="order-assignment-header">
  1333. <h2 class="assignment-title">订单分配</h2>
  1334. <div class="assignment-actions">
  1335. <button
  1336. class="create-order-btn"
  1337. (click)="createOrder()"
  1338. [disabled]="!canCreateOrder()"
  1339. [class.disabled]="!canCreateOrder()"
  1340. >
  1341. <span>创建订单</span>
  1342. </button>
  1343. </div>
  1344. </div>
  1345. <!-- 可滚动的表单内容区域 -->
  1346. <div class="scrollable-form-content">
  1347. <!-- 客户信息显示(如果已同步) -->
  1348. @if (orderCreationData?.customerInfo) {
  1349. <section class="synced-customer-info">
  1350. <h3 class="form-title">客户信息(已同步)</h3>
  1351. <div class="customer-info-display">
  1352. <div class="info-row">
  1353. <div class="info-item">
  1354. <label>客户姓名:</label>
  1355. <span>{{ orderCreationData.customerInfo.name || '未填写' }}</span>
  1356. </div>
  1357. <div class="info-item">
  1358. <label>联系电话:</label>
  1359. <span>{{ orderCreationData.customerInfo.phone || '未填写' }}</span>
  1360. </div>
  1361. </div>
  1362. <div class="info-row">
  1363. <div class="info-item">
  1364. <label>微信号:</label>
  1365. <span>{{ orderCreationData.customerInfo.wechat || '未填写' }}</span>
  1366. </div>
  1367. <div class="info-item">
  1368. <label>客户类型:</label>
  1369. <span>{{ orderCreationData.customerInfo.customerType || '未填写' }}</span>
  1370. </div>
  1371. </div>
  1372. </div>
  1373. </section>
  1374. }
  1375. <!-- 核心必填信息表单 -->
  1376. <section class="core-requirements-form">
  1377. <h3 class="form-title">核心信息 <span class="required-note">(带 * 为必填项)</span></h3>
  1378. <form [formGroup]="orderCreationForm" class="order-form">
  1379. <!-- 第一行:订单金额、小图交付时间 -->
  1380. <div class="form-row">
  1381. <div class="form-field">
  1382. <label for="orderAmount" class="field-label">订单金额 <span class="required">*</span></label>
  1383. <div class="input-with-unit">
  1384. <input
  1385. type="number"
  1386. id="orderAmount"
  1387. formControlName="orderAmount"
  1388. placeholder="请输入订单总金额"
  1389. class="field-input"
  1390. min="0"
  1391. step="0.01">
  1392. <span class="input-unit">元</span>
  1393. </div>
  1394. @if (orderCreationForm.get('orderAmount')?.invalid && orderCreationForm.get('orderAmount')?.touched) {
  1395. <div class="field-error">订单金额为必填项</div>
  1396. }
  1397. <div class="field-hint">报价明细需拆分至具体空间,便于后续分工</div>
  1398. </div>
  1399. <div class="form-field">
  1400. <label for="smallImageDeliveryTime" class="field-label">小图交付时间 <span class="required">*</span></label>
  1401. <input
  1402. id="smallImageDeliveryTime"
  1403. type="date"
  1404. formControlName="smallImageDeliveryTime"
  1405. class="field-input"
  1406. />
  1407. @if (orderCreationForm.get('smallImageDeliveryTime')?.invalid && orderCreationForm.get('smallImageDeliveryTime')?.touched) {
  1408. <div class="field-error">小图交付时间为必填项</div>
  1409. }
  1410. <div class="field-hint">确保时间安排合理,便于设计师规划工作</div>
  1411. </div>
  1412. </div>
  1413. <!-- 第二行:装修类型、需求原因 -->
  1414. <div class="form-row">
  1415. <div class="form-field">
  1416. <label for="decorationType" class="field-label">装修类型 <span class="required">*</span></label>
  1417. <select id="decorationType" formControlName="decorationType" class="field-select">
  1418. <option value="">请选择装修类型</option>
  1419. <option value="家装">家装</option>
  1420. <option value="工装">工装</option>
  1421. <option value="软装">软装</option>
  1422. <option value="局部改造">局部改造</option>
  1423. </select>
  1424. @if (orderCreationForm.get('decorationType')?.invalid && orderCreationForm.get('decorationType')?.touched) {
  1425. <div class="field-error">装修类型为必填项</div>
  1426. }
  1427. <div class="field-hint">选择合适的装修类型有助于匹配专业设计师</div>
  1428. </div>
  1429. <div class="form-field">
  1430. <label for="requirementReason" class="field-label">需求原因 <span class="required">*</span></label>
  1431. <textarea
  1432. id="requirementReason"
  1433. formControlName="requirementReason"
  1434. class="field-textarea"
  1435. placeholder="请详细描述装修需求的原因和背景"
  1436. rows="3">
  1437. </textarea>
  1438. @if (orderCreationForm.get('requirementReason')?.invalid && orderCreationForm.get('requirementReason')?.touched) {
  1439. <div class="field-error">需求原因为必填项</div>
  1440. }
  1441. <div class="field-hint">详细的需求背景有助于设计师理解客户期望</div>
  1442. </div>
  1443. </div>
  1444. <!-- 第三行:多设计师标记 -->
  1445. <div class="form-row">
  1446. <div class="form-field full-width">
  1447. <label class="field-label">多设计师标记 <span class="required">*</span></label>
  1448. <div class="checkbox-group">
  1449. <label class="checkbox-item">
  1450. <input
  1451. type="checkbox"
  1452. formControlName="isMultiDesigner"
  1453. class="checkbox-input">
  1454. <span class="checkbox-label">需要多个设计师协作</span>
  1455. </label>
  1456. </div>
  1457. @if (orderCreationForm.get('isMultiDesigner')?.invalid && orderCreationForm.get('isMultiDesigner')?.touched) {
  1458. <div class="field-error">请确认是否需要多设计师协作</div>
  1459. }
  1460. <div class="field-hint">复杂项目建议启用多设计师协作模式</div>
  1461. </div>
  1462. </div>
  1463. </form>
  1464. </section>
  1465. <!-- 报价明细组件 -->
  1466. <section class="quotation-section">
  1467. <h3 class="form-title">报价明细</h3>
  1468. <app-quotation-details
  1469. [initialData]="quotationData"
  1470. (dataChange)="onQuotationDataChange($event)"
  1471. ></app-quotation-details>
  1472. </section>
  1473. <!-- 设计师分配组件 -->
  1474. <section class="designer-assignment-section">
  1475. <h3 class="form-title">设计师分配</h3>
  1476. <div class="designer-assignment-container">
  1477. <app-designer-assignment
  1478. [quotationItems]="quotationData.items"
  1479. [initialAssignment]="designerAssignmentData"
  1480. (assignmentChange)="onDesignerAssignmentChange($event)"
  1481. (designerClick)="onDesignerClick($event)"
  1482. ></app-designer-assignment>
  1483. </div>
  1484. </section>
  1485. <!-- 可选信息表单 -->
  1486. <section class="optional-requirements-form">
  1487. <div class="card-header" (click)="isOptionalFormExpanded = !isOptionalFormExpanded">
  1488. <h3 class="card-title">其他信息 <span class="optional-note">(选填)</span></h3>
  1489. <span class="toggle-icon" [class.expanded]="isOptionalFormExpanded">▼</span>
  1490. </div>
  1491. @if (isOptionalFormExpanded) {
  1492. <div class="card-content">
  1493. <form [formGroup]="optionalForm" class="optional-form">
  1494. <!-- 大图交付时间 -->
  1495. <div class="form-row">
  1496. <div class="form-field">
  1497. <label for="largeImageDeliveryTime" class="field-label">大图交付时间</label>
  1498. <input
  1499. id="largeImageDeliveryTime"
  1500. type="date"
  1501. formControlName="largeImageDeliveryTime"
  1502. class="field-input"
  1503. />
  1504. </div>
  1505. </div>
  1506. <!-- 详细需求 -->
  1507. <div class="form-row">
  1508. <div class="form-field full-width">
  1509. <label for="spaceRequirements" class="field-label">涉及空间</label>
  1510. <textarea
  1511. id="spaceRequirements"
  1512. formControlName="spaceRequirements"
  1513. rows="3"
  1514. class="field-textarea"
  1515. placeholder="请描述涉及的空间,如:客厅、卧室、厨房等"
  1516. ></textarea>
  1517. </div>
  1518. </div>
  1519. <div class="form-row">
  1520. <div class="form-field full-width">
  1521. <label for="designAngles" class="field-label">设计角度</label>
  1522. <textarea
  1523. id="designAngles"
  1524. formControlName="designAngles"
  1525. rows="3"
  1526. class="field-textarea"
  1527. placeholder="请明确各个空间的展示角度"
  1528. ></textarea>
  1529. </div>
  1530. </div>
  1531. <div class="form-row">
  1532. <div class="form-field full-width">
  1533. <label for="specialAreaHandling" class="field-label">特殊区域处理</label>
  1534. <textarea
  1535. id="specialAreaHandling"
  1536. formControlName="specialAreaHandling"
  1537. rows="3"
  1538. class="field-textarea"
  1539. placeholder="请描述特殊区域的处理要求"
  1540. ></textarea>
  1541. </div>
  1542. </div>
  1543. <div class="form-row">
  1544. <div class="form-field full-width">
  1545. <label for="materialRequirements" class="field-label">材质要求</label>
  1546. <textarea
  1547. id="materialRequirements"
  1548. formControlName="materialRequirements"
  1549. rows="3"
  1550. class="field-textarea"
  1551. placeholder="请描述对材质的具体要求"
  1552. ></textarea>
  1553. </div>
  1554. </div>
  1555. <div class="form-row">
  1556. <div class="form-field full-width">
  1557. <label for="lightingRequirements" class="field-label">灯光要求</label>
  1558. <textarea
  1559. id="lightingRequirements"
  1560. formControlName="lightingRequirements"
  1561. rows="3"
  1562. class="field-textarea"
  1563. placeholder="请描述对灯光的具体要求"
  1564. ></textarea>
  1565. </div>
  1566. </div>
  1567. </form>
  1568. </div>
  1569. }
  1570. </section>
  1571. </div>
  1572. </div>
  1573. } @else if (stage === '需求沟通') {
  1574. <!-- 需求沟通阶段:确认需求组件 -->
  1575. <app-requirements-confirm-card
  1576. (requirementConfirmed)="syncRequirementKeyInfo($event)"
  1577. (progressUpdated)="syncRequirementKeyInfo($event)"
  1578. (stageCompleted)="onRequirementsStageCompleted($event)"
  1579. (dataUpdated)="onRequirementDataUpdated($event)"
  1580. (mappingDataUpdated)="onMappingDataUpdated($event)"
  1581. (uploadModalRequested)="onUploadModalRequested($event)">
  1582. </app-requirements-confirm-card>
  1583. } @else if (stage === '方案确认') {
  1584. <!-- 需求映射面板(替换原色彩分析报告区域) -->
  1585. <div class="requirement-mapping-panel" style="width:100%; display:flex; flex-direction:column; gap:14px;">
  1586. <h3 class="panel-title" style="margin:0 0 16px 0; font-size:18px; font-weight:700; color:#495057;">🎯 需求映射</h3>
  1587. <div class="mapping-progress" style="background:#f8f9fa; border-radius:8px; padding:16px; margin-bottom:12px;">
  1588. @if (mappingUploadedFiles.length > 0) {
  1589. <div class="progress-badge" style="display:inline-block; padding:6px 14px; background:linear-gradient(135deg, #667eea 0%, #764ba2 100%); color:white; border-radius:14px; font-size:12px; font-weight:600; margin-bottom:12px;">
  1590. 📸 已同步 {{ mappingUploadedFiles.length }} 张参考图片
  1591. </div>
  1592. }
  1593. <div class="steps-list" style="display:flex; flex-direction:column; gap:10px;">
  1594. <div class="step-item" style="display:flex; align-items:center; gap:10px;">
  1595. <span class="step-index" style="width:18px;height:18px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;background:#e9ecef;color:#495057;font-size:12px;">1</span>
  1596. <span class="step-title" style="flex:1;font-size:14px;">图片上传</span>
  1597. <span class="step-status" style="font-size:12px;color:#666;">@if (mappingUploadedFiles.length > 0) { ✅ 完成 } @else { ⭕ 待上传 }</span>
  1598. </div>
  1599. <div class="step-item" style="display:flex; align-items:center; gap:10px;">
  1600. <span class="step-index" style="width:18px;height:18px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;background:#e9ecef;color:#495057;font-size:12px;">2</span>
  1601. <span class="step-title" style="flex:1;font-size:14px;">图片分析</span>
  1602. <span class="step-status" style="font-size:12px;color:#666;">@if (mappingIsAnalyzing) { ⏳ 进行中 } @else if (mappingAnalysisResult) { ✅ 完成 } @else { ⭕ 待开始 }</span>
  1603. </div>
  1604. <div class="step-item" style="display:flex; align-items:center; gap:10px;">
  1605. <span class="step-index" style="width:18px;height:18px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;background:#e9ecef;color:#495057;font-size:12px;">3</span>
  1606. <span class="step-title" style="flex:1;font-size:14px;">需求映射</span>
  1607. <span class="step-status" style="font-size:12px;color:#666;">@if (mappingIsGeneratingMapping) { ⏳ 生成中 } @else if (mappingRequirementMapping) { ✅ 完成 } @else { ⭕ 待生成 }</span>
  1608. </div>
  1609. </div>
  1610. </div>
  1611. @if (mappingAnalysisResult) {
  1612. <div class="analysis-summary-panel" style="background:#f8f9fa;border-radius:8px;padding:16px;">
  1613. <h4 style="margin:0 0 12px 0; font-size:15px; font-weight:600; color:#333;">图片分析摘要</h4>
  1614. <div class="param-items" style="display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:10px;">
  1615. <div class="param-row" style="font-size:13px;"><span style="color:#868e96;">主色</span><span style="font-weight:500;">{{ mappingAnalysisResult.primaryColor?.hex || '未知' }}</span></div>
  1616. <div class="param-row" style="font-size:13px;"><span style="color:#868e96;">材质</span><span style="font-weight:500;">{{ getMaterialName(mappingAnalysisResult.materialType) }}</span></div>
  1617. <div class="param-row" style="font-size:13px;"><span style="color:#868e96;">灯光</span><span style="font-weight:500;">{{ getLightingMoodName(mappingAnalysisResult.lightingMood) }}</span></div>
  1618. <div class="param-row" style="font-size:13px;"><span style="color:#868e96;">对比</span><span style="font-weight:500;">{{ mappingAnalysisResult.contrast || '未知' }}</span></div>
  1619. </div>
  1620. </div>
  1621. }
  1622. @if (mappingRequirementMapping) {
  1623. <div class="mapping-result-panel" style="background:#f8f9fa;border-radius:8px;padding:16px;">
  1624. <h4 style="margin:0 0 12px 0; font-size:15px; font-weight:600; color:#333;">需求映射结果</h4>
  1625. @if (mappingRequirementMapping.color) {
  1626. <div class="param-section" style="margin-bottom:12px;">
  1627. <div class="section-title" style="font-weight:600;color:#555;margin-bottom:8px;font-size:13px;">色彩参数</div>
  1628. <div class="param-items" style="display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:10px;">
  1629. <div class="param-row" style="font-size:13px;"><span style="color:#868e96;">和谐度</span><span style="font-weight:500;">{{ getColorHarmonyName(mappingRequirementMapping.color?.harmony) }}</span></div>
  1630. <div class="param-row" style="font-size:13px;"><span style="color:#868e96;">色温</span><span style="font-weight:500;">{{ getTemperatureName(mappingRequirementMapping.color?.temperature) }}</span></div>
  1631. </div>
  1632. </div>
  1633. }
  1634. @if (mappingRequirementMapping.space) {
  1635. <div class="param-section">
  1636. <div class="section-title" style="font-weight:600;color:#555;margin-bottom:8px;font-size:13px;">空间参数</div>
  1637. <div class="param-items" style="display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:10px;">
  1638. <div class="param-row" style="font-size:13px;"><span style="color:#868e96;">布局</span><span style="font-weight:500;">{{ getLayoutTypeName(mappingRequirementMapping.space?.layoutType) }}</span></div>
  1639. <div class="param-row" style="font-size:13px;"><span style="color:#868e96;">流线</span><span style="font-weight:500;">{{ getFlowTypeName(mappingRequirementMapping.space?.flowType) }}</span></div>
  1640. </div>
  1641. </div>
  1642. }
  1643. </div>
  1644. }
  1645. @if (mappingIsAnalyzing) {
  1646. <div class="analyzing-indicator" style="display:flex; align-items:center; gap:10px; background:#f8f9fa; padding:12px; border-radius:8px;">
  1647. <div class="spinner" style="width:16px;height:16px;border:2px solid #e5e7eb;border-top-color:#3b82f6;border-radius:50%;animation:spin 1s linear infinite;"></div>
  1648. <span style="font-size:14px; color:#666;">正在解析图片...</span>
  1649. </div>
  1650. }
  1651. @if (mappingIsGeneratingMapping) {
  1652. <div class="analyzing-indicator" style="display:flex; align-items:center; gap:10px; background:#f8f9fa; padding:12px; border-radius:8px;">
  1653. <div class="spinner" style="width:16px;height:16px;border:2px solid #e5e7eb;border-top-color:#10b981;border-radius:50%;animation:spin 1s linear infinite;"></div>
  1654. <span style="font-size:14px; color:#666;">正在生成需求映射...</span>
  1655. </div>
  1656. }
  1657. @if (!mappingAnalysisResult && !mappingIsAnalyzing && mappingUploadedFiles.length === 0) {
  1658. <div class="empty-state" style="text-align:center; padding:32px; color:#999;">
  1659. <div style="font-size:48px; margin-bottom:12px;">📊</div>
  1660. <div style="font-size:14px;">在需求沟通中上传图片后,这里将实时显示需求映射结果</div>
  1661. </div>
  1662. }
  1663. </div>
  1664. } @else if (stage === '建模') {
  1665. <!-- 建模阶段:直接显示建模相关内容 -->
  1666. <div class="modeling-stage-panel">
  1667. <!-- 空间列表 -->
  1668. <div class="space-list-container">
  1669. <div class="space-list-header">
  1670. <h4>空间列表</h4>
  1671. @if (canEditSection('delivery')) {
  1672. <button class="add-space-btn"
  1673. (click)="showAddSpaceInput['modeling'] = true">
  1674. <span class="add-icon">+</span>
  1675. <span>添加空间</span>
  1676. </button>
  1677. }
  1678. </div>
  1679. <!-- 添加空间输入框 -->
  1680. @if (showAddSpaceInput['modeling']) {
  1681. <div class="add-space-input-container">
  1682. <input type="text"
  1683. class="space-name-input"
  1684. placeholder="请输入空间名称(如:卧室、餐厅、厨房)"
  1685. [(ngModel)]="newSpaceName['modeling']"
  1686. (keyup.enter)="addSpace('modeling')"
  1687. #spaceInput>
  1688. <div class="input-actions">
  1689. <button class="confirm-btn" (click)="addSpace('modeling')">确认</button>
  1690. <button class="cancel-btn" (click)="cancelAddSpace('modeling')">取消</button>
  1691. </div>
  1692. </div>
  1693. }
  1694. <!-- 空间卡片列表 -->
  1695. <div class="space-cards">
  1696. @for (space of getActiveProcessSpaces('modeling'); track space.id) {
  1697. <div class="space-card" [class.expanded]="space.isExpanded">
  1698. <div class="space-header" (click)="toggleSpace('modeling', space.id)">
  1699. <div class="space-info">
  1700. <span class="space-name">{{ space.name }}</span>
  1701. <span class="space-progress">{{ getSpaceProgress('modeling', space.id) }}%</span>
  1702. </div>
  1703. <div class="space-actions">
  1704. @if (canEditSection('delivery')) {
  1705. <button class="delete-space-btn"
  1706. (click)="$event.stopPropagation(); removeSpace('modeling', space.id)"
  1707. title="删除空间">
  1708. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  1709. <polyline points="3,6 5,6 21,6"></polyline>
  1710. <path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
  1711. </svg>
  1712. </button>
  1713. }
  1714. <span class="expand-arrow" [class.expanded]="space.isExpanded">▼</span>
  1715. </div>
  1716. </div>
  1717. @if (space.isExpanded) {
  1718. <div class="space-content">
  1719. <!-- 图片上传区域 -->
  1720. <div class="upload-section">
  1721. @if (canEditSection('delivery')) {
  1722. <div class="upload-dropzone"
  1723. (click)="triggerSpaceFileInput('modeling', space.id)"
  1724. (dragover)="onDragOver($event)"
  1725. (dragleave)="onDragLeave($event)"
  1726. (drop)="onSpaceFileDrop($event, 'modeling', space.id)"
  1727. [class.drag-over]="isDragOver">
  1728. @if (getSpaceImages('modeling', space.id).length === 0) {
  1729. <div class="upload-placeholder">
  1730. <div class="upload-text">点击上传或拖拽文件到此处</div>
  1731. <div class="upload-hint">
  1732. 支持 JPG、PNG 格式,单个文件最大 10MB
  1733. </div>
  1734. </div>
  1735. } @else {
  1736. <!-- 确认上传按钮 -->
  1737. @if (canEditSection('delivery') && getSpaceImages('modeling', space.id).length > 0) {
  1738. <div class="confirm-upload-section">
  1739. <button class="confirm-upload-btn"
  1740. (click)="$event.stopPropagation(); confirmStageUpload('modeling')"
  1741. [disabled]="!canConfirmStageUpload('modeling')">
  1742. <span>确认上传</span>
  1743. </button>
  1744. </div>
  1745. }
  1746. <div class="uploaded-images-grid">
  1747. @for (img of getSpaceImages('modeling', space.id); track img.id) {
  1748. <div class="uploaded-image-item" (click)="previewImage(img)">
  1749. <img [src]="img.url" [alt]="img.name" />
  1750. <div class="image-overlay">
  1751. <div class="image-name">{{ img.name }}</div>
  1752. <div class="image-actions">
  1753. <button class="preview-btn" (click)="$event.stopPropagation(); previewImage(img)">
  1754. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  1755. <circle cx="11" cy="11" r="8"></circle>
  1756. <path d="m21 21-4.35-4.35"></path>
  1757. </svg>
  1758. </button>
  1759. @if (canEditSection('delivery')) {
  1760. <button class="delete-btn" (click)="$event.stopPropagation(); removeSpaceImage('modeling', space.id, img.id)">
  1761. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  1762. <polyline points="3,6 5,6 21,6"></polyline>
  1763. <path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
  1764. </svg>
  1765. </button>
  1766. }
  1767. </div>
  1768. </div>
  1769. </div>
  1770. }
  1771. <!-- 添加更多图片按钮 -->
  1772. @if (canEditSection('delivery')) {
  1773. <div class="add-more-images-btn" (click)="$event.stopPropagation(); triggerSpaceFileInput('modeling', space.id)">
  1774. <div class="add-icon">+</div>
  1775. <div class="add-text">添加更多</div>
  1776. </div>
  1777. }
  1778. </div>
  1779. }
  1780. </div>
  1781. } @else {
  1782. <div class="readonly-images">
  1783. @if (getSpaceImages('modeling', space.id).length > 0) {
  1784. <div class="uploaded-images-grid">
  1785. @for (img of getSpaceImages('modeling', space.id); track img.id) {
  1786. <div class="uploaded-image-item" (click)="previewImage(img)">
  1787. <img [src]="img.url" [alt]="img.name" />
  1788. <div class="image-overlay">
  1789. <div class="image-name">{{ img.name }}</div>
  1790. </div>
  1791. </div>
  1792. }
  1793. </div>
  1794. } @else {
  1795. <div class="empty-tip">暂无上传的图片</div>
  1796. }
  1797. </div>
  1798. }
  1799. </div>
  1800. <!-- 备注区域 -->
  1801. <div class="notes-section">
  1802. <label class="notes-label">备注</label>
  1803. @if (canEditSection('delivery')) {
  1804. <textarea #modelingNotes
  1805. class="notes-textarea"
  1806. placeholder="请输入备注信息..."
  1807. [value]="getSpaceNotes('modeling', space.id)"
  1808. (blur)="updateSpaceNotes('modeling', space.id, modelingNotes.value || '')">
  1809. </textarea>
  1810. } @else {
  1811. <div class="notes-readonly">
  1812. {{ getSpaceNotes('modeling', space.id) || '暂无备注' }}
  1813. </div>
  1814. }
  1815. </div>
  1816. </div>
  1817. }
  1818. </div>
  1819. }
  1820. </div>
  1821. </div>
  1822. </div>
  1823. } @else if (stage === '软装') {
  1824. <!-- 软装阶段:直接显示软装相关内容 -->
  1825. <div class="soft-decor-stage-panel">
  1826. <!-- 空间列表 -->
  1827. <div class="space-list-container">
  1828. <div class="space-list-header">
  1829. <h4>空间列表</h4>
  1830. @if (canEditSection('delivery')) {
  1831. <button class="add-space-btn"
  1832. (click)="showAddSpaceInput['softDecor'] = true">
  1833. <span class="add-icon">+</span>
  1834. <span>添加空间</span>
  1835. </button>
  1836. }
  1837. </div>
  1838. <!-- 添加空间输入框 -->
  1839. @if (showAddSpaceInput['softDecor']) {
  1840. <div class="add-space-input-container">
  1841. <input type="text"
  1842. class="space-name-input"
  1843. placeholder="请输入空间名称(如:卧室、餐厅、厨房)"
  1844. [(ngModel)]="newSpaceName['softDecor']"
  1845. (keyup.enter)="addSpace('softDecor')"
  1846. #spaceInput>
  1847. <div class="input-actions">
  1848. <button class="confirm-btn" (click)="addSpace('softDecor')">确认</button>
  1849. <button class="cancel-btn" (click)="cancelAddSpace('softDecor')">取消</button>
  1850. </div>
  1851. </div>
  1852. }
  1853. <!-- 空间卡片列表 -->
  1854. <div class="space-cards">
  1855. @for (space of getActiveProcessSpaces('softDecor'); track space.id) {
  1856. <div class="space-card" [class.expanded]="space.isExpanded">
  1857. <div class="space-header" (click)="toggleSpace('softDecor', space.id)">
  1858. <div class="space-info">
  1859. <span class="space-name">{{ space.name }}</span>
  1860. <span class="space-progress">{{ getSpaceProgress('softDecor', space.id) }}%</span>
  1861. </div>
  1862. <div class="space-actions">
  1863. @if (canEditSection('delivery')) {
  1864. <button class="delete-space-btn"
  1865. (click)="$event.stopPropagation(); removeSpace('softDecor', space.id)"
  1866. title="删除空间">
  1867. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  1868. <polyline points="3,6 5,6 21,6"></polyline>
  1869. <path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
  1870. </svg>
  1871. </button>
  1872. }
  1873. <span class="expand-arrow" [class.expanded]="space.isExpanded">▼</span>
  1874. </div>
  1875. </div>
  1876. @if (space.isExpanded) {
  1877. <div class="space-content">
  1878. <!-- 图片上传区域 -->
  1879. <div class="upload-section">
  1880. @if (canEditSection('delivery')) {
  1881. <div class="upload-dropzone"
  1882. (click)="triggerSpaceFileInput('softDecor', space.id)"
  1883. (dragover)="onDragOver($event)"
  1884. (dragleave)="onDragLeave($event)"
  1885. (drop)="onSpaceFileDrop($event, 'softDecor', space.id)"
  1886. [class.drag-over]="isDragOver">
  1887. @if (getSpaceImages('softDecor', space.id).length === 0) {
  1888. <div class="upload-placeholder">
  1889. <div class="upload-text">点击上传或拖拽文件到此处</div>
  1890. <div class="upload-hint">
  1891. 建议 ≤1MB 的 JPG/PNG 小图
  1892. </div>
  1893. </div>
  1894. } @else {
  1895. <!-- 确认上传按钮 -->
  1896. @if (canEditSection('delivery') && getSpaceImages('softDecor', space.id).length > 0) {
  1897. <div class="confirm-upload-section">
  1898. <button class="confirm-upload-btn"
  1899. (click)="$event.stopPropagation(); confirmStageUpload('softDecor')"
  1900. [disabled]="!canConfirmStageUpload('softDecor')">
  1901. <span>确认上传</span>
  1902. </button>
  1903. </div>
  1904. }
  1905. <div class="uploaded-images-grid">
  1906. @for (img of getSpaceImages('softDecor', space.id); track img.id) {
  1907. <div class="uploaded-image-item" (click)="previewImage(img)">
  1908. <img [src]="img.url" [alt]="img.name" />
  1909. <div class="image-overlay">
  1910. <div class="image-name">{{ img.name }}</div>
  1911. <div class="image-actions">
  1912. <button class="preview-btn" (click)="$event.stopPropagation(); previewImage(img)">
  1913. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  1914. <circle cx="11" cy="11" r="8"></circle>
  1915. <path d="m21 21-4.35-4.35"></path>
  1916. </svg>
  1917. </button>
  1918. @if (canEditSection('delivery')) {
  1919. <button class="delete-btn" (click)="$event.stopPropagation(); removeSpaceImage('softDecor', space.id, img.id)">
  1920. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  1921. <polyline points="3,6 5,6 21,6"></polyline>
  1922. <path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
  1923. </svg>
  1924. </button>
  1925. }
  1926. </div>
  1927. </div>
  1928. </div>
  1929. }
  1930. <!-- 添加更多图片按钮 -->
  1931. @if (canEditSection('delivery')) {
  1932. <div class="add-more-images-btn" (click)="$event.stopPropagation(); triggerSpaceFileInput('softDecor', space.id)">
  1933. <div class="add-icon">+</div>
  1934. <div class="add-text">添加更多</div>
  1935. </div>
  1936. }
  1937. </div>
  1938. }
  1939. </div>
  1940. } @else {
  1941. <div class="readonly-images">
  1942. @if (getSpaceImages('softDecor', space.id).length > 0) {
  1943. <div class="uploaded-images-grid">
  1944. @for (img of getSpaceImages('softDecor', space.id); track img.id) {
  1945. <div class="uploaded-image-item" (click)="previewImage(img)">
  1946. <img [src]="img.url" [alt]="img.name" />
  1947. <div class="image-overlay">
  1948. <div class="image-name">{{ img.name }}</div>
  1949. </div>
  1950. </div>
  1951. }
  1952. </div>
  1953. } @else {
  1954. <div class="empty-tip">暂无上传的图片</div>
  1955. }
  1956. </div>
  1957. }
  1958. </div>
  1959. <!-- 备注区域 -->
  1960. <div class="notes-section">
  1961. <label class="notes-label">备注</label>
  1962. @if (canEditSection('delivery')) {
  1963. <textarea #softDecorNotes
  1964. class="notes-textarea"
  1965. placeholder="请输入备注信息..."
  1966. [value]="getSpaceNotes('softDecor', space.id)"
  1967. (blur)="updateSpaceNotes('softDecor', space.id, softDecorNotes.value || '')">
  1968. </textarea>
  1969. } @else {
  1970. <div class="notes-readonly">
  1971. {{ getSpaceNotes('softDecor', space.id) || '暂无备注' }}
  1972. </div>
  1973. }
  1974. </div>
  1975. </div>
  1976. }
  1977. </div>
  1978. }
  1979. </div>
  1980. </div>
  1981. </div>
  1982. } @else if (stage === '渲染') {
  1983. <!-- 渲染阶段:直接显示渲染相关内容 -->
  1984. <div class="rendering-stage-panel">
  1985. <!-- 空间列表 -->
  1986. <div class="space-list-container">
  1987. <div class="space-list-header">
  1988. <h4>空间列表</h4>
  1989. @if (canEditSection('delivery')) {
  1990. <button class="add-space-btn"
  1991. (click)="showAddSpaceInput['rendering'] = true">
  1992. <span class="add-icon">+</span>
  1993. <span>添加空间</span>
  1994. </button>
  1995. }
  1996. </div>
  1997. <!-- 添加空间输入框 -->
  1998. @if (showAddSpaceInput['rendering']) {
  1999. <div class="add-space-input-container">
  2000. <input type="text"
  2001. class="space-name-input"
  2002. placeholder="请输入空间名称(如:卧室、餐厅、厨房)"
  2003. [(ngModel)]="newSpaceName['rendering']"
  2004. (keyup.enter)="addSpace('rendering')"
  2005. #spaceInput>
  2006. <div class="input-actions">
  2007. <button class="confirm-btn" (click)="addSpace('rendering')">确认</button>
  2008. <button class="cancel-btn" (click)="cancelAddSpace('rendering')">取消</button>
  2009. </div>
  2010. </div>
  2011. }
  2012. <!-- 空间卡片列表 -->
  2013. <div class="space-cards">
  2014. @for (space of getActiveProcessSpaces('rendering'); track space.id) {
  2015. <div class="space-card" [class.expanded]="space.isExpanded">
  2016. <div class="space-header" (click)="toggleSpace('rendering', space.id)">
  2017. <div class="space-info">
  2018. <span class="space-name">{{ space.name }}</span>
  2019. <span class="space-progress">{{ getSpaceProgress('rendering', space.id) }}%</span>
  2020. </div>
  2021. <div class="space-actions">
  2022. @if (canEditSection('delivery')) {
  2023. <button class="delete-space-btn"
  2024. (click)="$event.stopPropagation(); removeSpace('rendering', space.id)"
  2025. title="删除空间">
  2026. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  2027. <polyline points="3,6 5,6 21,6"></polyline>
  2028. <path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
  2029. </svg>
  2030. </button>
  2031. }
  2032. <span class="expand-arrow" [class.expanded]="space.isExpanded">▼</span>
  2033. </div>
  2034. </div>
  2035. @if (space.isExpanded) {
  2036. <div class="space-content">
  2037. <!-- 图片上传区域 -->
  2038. <div class="upload-section">
  2039. @if (canEditSection('delivery')) {
  2040. <div class="upload-dropzone"
  2041. (click)="triggerSpaceFileInput('rendering', space.id)"
  2042. (dragover)="onDragOver($event)"
  2043. (dragleave)="onDragLeave($event)"
  2044. (drop)="onSpaceFileDrop($event, 'rendering', space.id)"
  2045. [class.drag-over]="isDragOver">
  2046. @if (getSpaceImages('rendering', space.id).length === 0) {
  2047. <div class="upload-placeholder">
  2048. <div class="upload-text">点击上传或拖拽文件到此处</div>
  2049. <div class="upload-hint">
  2050. 需满足4K标准(最长边 ≥ 4000px)
  2051. </div>
  2052. </div>
  2053. } @else {
  2054. <!-- 确认上传按钮 -->
  2055. @if (canEditSection('delivery') && getSpaceImages('rendering', space.id).length > 0) {
  2056. <div class="confirm-upload-section">
  2057. <button class="confirm-upload-btn"
  2058. (click)="$event.stopPropagation(); confirmStageUpload('rendering')"
  2059. [disabled]="!canConfirmStageUpload('rendering')">
  2060. <span>确认上传</span>
  2061. </button>
  2062. </div>
  2063. }
  2064. <div class="uploaded-images-grid">
  2065. @for (img of getSpaceImages('rendering', space.id); track img.id) {
  2066. <div class="uploaded-image-item" (click)="previewImage(img)">
  2067. <img [src]="img.url" [alt]="img.name" />
  2068. <div class="image-overlay">
  2069. <div class="image-name">{{ img.name }}</div>
  2070. <div class="image-actions">
  2071. <button class="preview-btn" (click)="$event.stopPropagation(); previewImage(img)">
  2072. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  2073. <circle cx="11" cy="11" r="8"></circle>
  2074. <path d="m21 21-4.35-4.35"></path>
  2075. </svg>
  2076. </button>
  2077. @if (canEditSection('delivery')) {
  2078. <button class="delete-btn" (click)="$event.stopPropagation(); removeSpaceImage('rendering', space.id, img.id)">
  2079. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  2080. <polyline points="3,6 5,6 21,6"></polyline>
  2081. <path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
  2082. </svg>
  2083. </button>
  2084. }
  2085. </div>
  2086. </div>
  2087. </div>
  2088. }
  2089. <!-- 添加更多图片按钮 -->
  2090. @if (canEditSection('delivery')) {
  2091. <div class="add-more-images-btn" (click)="$event.stopPropagation(); triggerSpaceFileInput('rendering', space.id)">
  2092. <div class="add-icon">+</div>
  2093. <div class="add-text">添加更多</div>
  2094. </div>
  2095. }
  2096. </div>
  2097. }
  2098. </div>
  2099. } @else {
  2100. <div class="readonly-images">
  2101. @if (getSpaceImages('rendering', space.id).length > 0) {
  2102. <div class="uploaded-images-grid">
  2103. @for (img of getSpaceImages('rendering', space.id); track img.id) {
  2104. <div class="uploaded-image-item" (click)="previewImage(img)">
  2105. <img [src]="img.url" [alt]="img.name" />
  2106. <div class="image-overlay">
  2107. <div class="image-name">{{ img.name }}</div>
  2108. </div>
  2109. </div>
  2110. }
  2111. </div>
  2112. } @else {
  2113. <div class="empty-tip">暂无上传的图片</div>
  2114. }
  2115. </div>
  2116. }
  2117. </div>
  2118. <!-- 备注区域 -->
  2119. <div class="notes-section">
  2120. <label class="notes-label">备注</label>
  2121. @if (canEditSection('delivery')) {
  2122. <textarea #renderingNotes
  2123. class="notes-textarea"
  2124. placeholder="请输入备注信息..."
  2125. [value]="getSpaceNotes('rendering', space.id)"
  2126. (blur)="updateSpaceNotes('rendering', space.id, renderingNotes.value || '')">
  2127. </textarea>
  2128. } @else {
  2129. <div class="notes-readonly">
  2130. {{ getSpaceNotes('rendering', space.id) || '暂无备注' }}
  2131. </div>
  2132. }
  2133. </div>
  2134. </div>
  2135. }
  2136. </div>
  2137. }
  2138. </div>
  2139. </div>
  2140. </div>
  2141. } @else if (stage === '后期') {
  2142. <!-- 后期阶段:直接显示后期相关内容 -->
  2143. <div class="post-production-stage-panel">
  2144. <!-- 空间列表 -->
  2145. <div class="space-list-container">
  2146. <div class="space-list-header">
  2147. <h4>空间列表</h4>
  2148. @if (canEditSection('delivery')) {
  2149. <button class="add-space-btn"
  2150. (click)="showAddSpaceInput['postProduction'] = true">
  2151. <span class="add-icon">+</span>
  2152. <span>添加空间</span>
  2153. </button>
  2154. }
  2155. </div>
  2156. <!-- 添加空间输入框 -->
  2157. @if (showAddSpaceInput['postProduction']) {
  2158. <div class="add-space-input-container">
  2159. <input type="text"
  2160. class="space-name-input"
  2161. placeholder="请输入空间名称(如:卧室、餐厅、厨房)"
  2162. [(ngModel)]="newSpaceName['postProduction']"
  2163. (keyup.enter)="addSpace('postProduction')"
  2164. #spaceInput>
  2165. <div class="input-actions">
  2166. <button class="confirm-btn" (click)="addSpace('postProduction')">确认</button>
  2167. <button class="cancel-btn" (click)="cancelAddSpace('postProduction')">取消</button>
  2168. </div>
  2169. </div>
  2170. }
  2171. <!-- 空间卡片列表 -->
  2172. <div class="space-cards">
  2173. @for (space of getActiveProcessSpaces('postProcess'); track space.id) {
  2174. <div class="space-card" [class.expanded]="space.isExpanded">
  2175. <div class="space-header" (click)="toggleSpace('postProduction', space.id)">
  2176. <div class="space-info">
  2177. <span class="space-name">{{ space.name }}</span>
  2178. <span class="space-progress">{{ getSpaceProgress('postProduction', space.id) }}%</span>
  2179. </div>
  2180. <div class="space-actions">
  2181. @if (canEditSection('delivery')) {
  2182. <button class="delete-space-btn"
  2183. (click)="$event.stopPropagation(); removeSpace('postProduction', space.id)"
  2184. title="删除空间">
  2185. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  2186. <polyline points="3,6 5,6 21,6"></polyline>
  2187. <path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
  2188. </svg>
  2189. </button>
  2190. }
  2191. <span class="expand-arrow" [class.expanded]="space.isExpanded">▼</span>
  2192. </div>
  2193. </div>
  2194. @if (space.isExpanded) {
  2195. <div class="space-content">
  2196. <!-- 图片上传区域 -->
  2197. <div class="upload-section">
  2198. @if (canEditSection('delivery')) {
  2199. <div class="upload-dropzone"
  2200. (click)="triggerSpaceFileInput('postProduction', space.id)"
  2201. (dragover)="onDragOver($event)"
  2202. (dragleave)="onDragLeave($event)"
  2203. (drop)="onSpaceFileDrop($event, 'postProduction', space.id)"
  2204. [class.drag-over]="isDragOver">
  2205. @if (getSpaceImages('postProduction', space.id).length === 0) {
  2206. <div class="upload-placeholder">
  2207. <div class="upload-text">点击上传或拖拽文件到此处</div>
  2208. <div class="upload-hint">
  2209. 支持 JPG、PNG 格式,后期处理图片
  2210. </div>
  2211. </div>
  2212. } @else {
  2213. <!-- 确认上传按钮 -->
  2214. @if (canEditSection('delivery') && getSpaceImages('postProduction', space.id).length > 0) {
  2215. <div class="confirm-upload-section">
  2216. <button class="confirm-upload-btn"
  2217. (click)="$event.stopPropagation(); confirmStageUpload('postProduction')"
  2218. [disabled]="!canConfirmStageUpload('postProduction')">
  2219. <span>确认上传</span>
  2220. </button>
  2221. </div>
  2222. }
  2223. <div class="uploaded-images-grid">
  2224. @for (img of getSpaceImages('postProduction', space.id); track img.id) {
  2225. <div class="uploaded-image-item" (click)="previewImage(img)">
  2226. <img [src]="img.url" [alt]="img.name" />
  2227. <div class="image-overlay">
  2228. <div class="image-name">{{ img.name }}</div>
  2229. <div class="image-actions">
  2230. <button class="preview-btn" (click)="$event.stopPropagation(); previewImage(img)">
  2231. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  2232. <circle cx="11" cy="11" r="8"></circle>
  2233. <path d="m21 21-4.35-4.35"></path>
  2234. </svg>
  2235. </button>
  2236. @if (canEditSection('delivery')) {
  2237. <button class="delete-btn" (click)="$event.stopPropagation(); removeSpaceImage('postProduction', space.id, img.id)">
  2238. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  2239. <polyline points="3,6 5,6 21,6"></polyline>
  2240. <path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
  2241. </svg>
  2242. </button>
  2243. }
  2244. </div>
  2245. </div>
  2246. </div>
  2247. }
  2248. <!-- 添加更多图片按钮 -->
  2249. @if (canEditSection('delivery')) {
  2250. <div class="add-more-images-btn" (click)="$event.stopPropagation(); triggerSpaceFileInput('postProduction', space.id)">
  2251. <div class="add-icon">+</div>
  2252. <div class="add-text">添加更多</div>
  2253. </div>
  2254. }
  2255. </div>
  2256. }
  2257. </div>
  2258. } @else {
  2259. <div class="readonly-images">
  2260. @if (getSpaceImages('postProduction', space.id).length > 0) {
  2261. <div class="uploaded-images-grid">
  2262. @for (img of getSpaceImages('postProduction', space.id); track img.id) {
  2263. <div class="uploaded-image-item" (click)="previewImage(img)">
  2264. <img [src]="img.url" [alt]="img.name" />
  2265. <div class="image-overlay">
  2266. <div class="image-name">{{ img.name }}</div>
  2267. </div>
  2268. </div>
  2269. }
  2270. </div>
  2271. } @else {
  2272. <div class="empty-tip">暂无上传的图片</div>
  2273. }
  2274. </div>
  2275. }
  2276. </div>
  2277. <!-- 备注区域 -->
  2278. <div class="notes-section">
  2279. <label class="notes-label">备注</label>
  2280. @if (canEditSection('delivery')) {
  2281. <textarea #postProductionNotes
  2282. class="notes-textarea"
  2283. placeholder="请输入备注信息..."
  2284. [value]="getSpaceNotes('postProduction', space.id)"
  2285. (blur)="updateSpaceNotes('postProduction', space.id, postProductionNotes.value || '')">
  2286. </textarea>
  2287. } @else {
  2288. <div class="notes-readonly">
  2289. {{ getSpaceNotes('postProduction', space.id) || '暂无备注' }}
  2290. </div>
  2291. }
  2292. </div>
  2293. </div>
  2294. }
  2295. </div>
  2296. }
  2297. </div>
  2298. </div>
  2299. </div>
  2300. } @else if (stage === '尾款结算') {
  2301. <!-- 原尾款结算售后 tab 容器已移除,根据用户要求不再使用 -->
  2302. }
  2303. </div>
  2304. </div>
  2305. }
  2306. </div>
  2307. </div>
  2308. </div>
  2309. <!-- 项目人员标签页 -->
  2310. @if (isActiveTab('members')) {
  2311. <div class="members-tab-content">
  2312. <div class="main-content-layout">
  2313. <!-- 项目人员内容 -->
  2314. <div class="members-content">
  2315. <div class="members-header">
  2316. <h2>项目成员</h2>
  2317. <p class="members-count">共 {{ projectMembers.length }} 名成员</p>
  2318. </div>
  2319. <div class="members-grid">
  2320. @for (member of projectMembers; track member.id) {
  2321. <div class="member-card">
  2322. <div class="member-avatar">
  2323. <img [src]="member.avatar" [alt]="member.name">
  2324. </div>
  2325. <div class="member-info">
  2326. <h3 class="member-name">{{ member.name }}</h3>
  2327. <p class="member-role">{{ member.role }}</p>
  2328. <div class="member-stats">
  2329. <div class="stat-item">
  2330. <span class="stat-label">技能匹配度</span>
  2331. <div class="progress-bar">
  2332. <div class="progress-fill" [style.width.%]="member.skillMatch"></div>
  2333. </div>
  2334. <span class="stat-value">{{ member.skillMatch }}%</span>
  2335. </div>
  2336. <div class="stat-item">
  2337. <span class="stat-label">项目进度</span>
  2338. <div class="progress-bar">
  2339. <div class="progress-fill" [style.width.%]="member.progress"></div>
  2340. </div>
  2341. <span class="stat-value">{{ member.progress }}%</span>
  2342. </div>
  2343. <div class="stat-item">
  2344. <span class="stat-label">贡献度</span>
  2345. <div class="progress-bar">
  2346. <div class="progress-fill" [style.width.%]="member.contribution"></div>
  2347. </div>
  2348. <span class="stat-value">{{ member.contribution }}%</span>
  2349. </div>
  2350. </div>
  2351. </div>
  2352. </div>
  2353. }
  2354. </div>
  2355. @if (projectMembers.length === 0) {
  2356. <div class="empty-state">
  2357. <p>暂无项目成员信息</p>
  2358. </div>
  2359. }
  2360. </div>
  2361. </div>
  2362. </div>
  2363. }
  2364. <!-- 项目文件标签页 -->
  2365. @if (isActiveTab('files')) {
  2366. <div class="files-tab-content">
  2367. <div class="main-content-layout">
  2368. <!-- 项目文件内容 -->
  2369. <div class="files-content">
  2370. <div class="files-header">
  2371. <h2>项目文件</h2>
  2372. <p class="files-count">共 {{ projectFiles.length }} 个文件</p>
  2373. </div>
  2374. <div class="files-list">
  2375. @for (file of projectFiles; track file.id) {
  2376. <div class="file-item">
  2377. <div class="file-icon">
  2378. <span class="file-type">{{ file.type.toUpperCase() }}</span>
  2379. </div>
  2380. <div class="file-info">
  2381. <h3 class="file-name">{{ file.name }}</h3>
  2382. <div class="file-meta">
  2383. <span class="file-size">{{ file.size }}</span>
  2384. <span class="file-date">{{ file.date }}</span>
  2385. </div>
  2386. </div>
  2387. <div class="file-actions">
  2388. <button class="btn-download" (click)="downloadFile(file)">下载</button>
  2389. <button class="btn-preview" (click)="previewFile(file)">预览</button>
  2390. </div>
  2391. </div>
  2392. }
  2393. </div>
  2394. @if (projectFiles.length === 0) {
  2395. <div class="empty-state">
  2396. <p>暂无项目文件</p>
  2397. </div>
  2398. }
  2399. </div>
  2400. </div>
  2401. </div>
  2402. }
  2403. </div>
  2404. }
  2405. <!-- 上传成功弹窗 - 在根级别渲染以确保正确定位,避免被vertical-stage-block影响 -->
  2406. @if (true) {
  2407. <app-upload-success-modal
  2408. [isVisible]="showUploadSuccessModal"
  2409. [uploadedFiles]="uploadedFiles"
  2410. [uploadType]="uploadType"
  2411. [analysisResult]="colorAnalysisResult || undefined"
  2412. [isAnalyzing]="isAnalyzingColors"
  2413. (closeModal)="onModalClose()"
  2414. (analyzeColors)="onAnalyzeColors()"
  2415. (viewReport)="onViewReport()">
  2416. </app-upload-success-modal>
  2417. }