project-detail.html 163 KB

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