project-detail.html 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867
  1. <!-- 项目详情页面内容 -->
  2. <div class="project-detail-container ios-style">
  3. <!-- 顶部导航/Header -->
  4. <header class="project-header ios-header">
  5. <div class="header-left">
  6. <div class="project-info">
  7. <h1 class="project-title">{{ project()?.name || '现代简约风格三居室设计' }}</h1>
  8. <div class="project-meta">
  9. <span class="project-status {{ getProjectStatusClass(project()?.status) }}">
  10. {{ project()?.status || '进行中' }}
  11. </span>
  12. <span class="project-stage">当前阶段:{{ project()?.currentStage || '方案修改与确认' }}</span>
  13. <span class="project-date">最后更新:{{ formatDate(currentDate()) }}</span>
  14. </div>
  15. </div>
  16. </div>
  17. <div class="header-right">
  18. <div class="header-actions">
  19. <button class="action-btn secondary-btn btn-hover-effect">
  20. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  21. <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
  22. <polyline points="14 2 14 8 20 8"></polyline>
  23. <line x1="16" y1="13" x2="8" y2="13"></line>
  24. <line x1="16" y1="17" x2="8" y2="17"></line>
  25. <polyline points="10 9 9 9 8 9"></polyline>
  26. </svg>
  27. <span>导出报告</span>
  28. </button>
  29. <button class="action-btn primary-btn btn-hover-effect">
  30. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  31. <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
  32. <polyline points="22 4 12 14.01 9 11.01"></polyline>
  33. </svg>
  34. <span>联系客户</span>
  35. </button>
  36. <button class="action-btn back-btn" routerLink="/customer-service/dashboard">
  37. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  38. <path d="M19 12H5"></path>
  39. <polyline points="12 19 5 12 12 5"></polyline>
  40. </svg>
  41. 返回工作台
  42. </button>
  43. </div>
  44. </div>
  45. </header>
  46. <!-- 主要内容区域 -->
  47. <div class="main-content-area ios-content">
  48. <!-- 主内容区 (居中) -->
  49. <div class="project-content-main ios-main">
  50. <!-- 项目进度卡片 -->
  51. <div class="card progress-card">
  52. <div class="progress-header">
  53. <h3>项目进度</h3>
  54. <span class="progress-percentage">{{ completionProgress() }}%</span>
  55. </div>
  56. <div class="progress-bar">
  57. <div class="progress-fill" [style.width]="progressFillWidth()"></div>
  58. </div>
  59. <div class="progress-meta">
  60. <span>开始时间:{{ formatDate(project()?.createdAt || '2023-06-01') }}</span>
  61. <span>预计完成:{{ formatDate(project()?.deadline || '2023-07-15') }}</span>
  62. </div>
  63. </div>
  64. <!-- 历史服务记录 -->
  65. <div class="card historical-records-card">
  66. <div class="records-header">
  67. <h3>历史服务记录</h3>
  68. </div>
  69. <!-- 过往咨询记录 -->
  70. <div class="record-section">
  71. <h4>过往咨询记录</h4>
  72. <div class="consultation-list">
  73. @for (record of consultationRecords(); track record.id || $index) {
  74. <div class="consultation-item">
  75. <div class="consultation-date">{{ formatDate(record.date) }}</div>
  76. <div class="consultation-content">{{ record.content }}</div>
  77. <div class="consultation-status"
  78. [class.status-processed]="record.status === '已解决' || record.status === '成功'"
  79. [class.status-processing]="record.status === '处理中'"
  80. [class.status-pending]="record.status === '待处理'">
  81. {{ record.status }}
  82. </div>
  83. </div>
  84. }
  85. </div>
  86. </div>
  87. </div>
  88. <!-- 合作项目 -->
  89. <div class="record-section">
  90. <h4>合作项目</h4>
  91. <div class="projects-list">
  92. @for (proj of cooperationProjects(); track proj.id || $index) {
  93. <div class="project-item">
  94. <div class="project-name">{{ proj.name }}</div>
  95. <div class="project-period">{{ formatDate(proj.startDate) }} - {{ formatDate(proj.endDate) }}</div>
  96. <div class="project-description">{{ proj.description }}</div>
  97. <div class="project-status">{{ proj.status }}</div>
  98. </div>
  99. }
  100. </div>
  101. </div>
  102. </div>
  103. <!-- 历史反馈/评价 -->
  104. <div class="record-section">
  105. <h4>历史反馈/评价</h4>
  106. <div class="feedback-list">
  107. @for (feedback of historicalFeedbacks(); track feedback.id || $index) {
  108. <div class="feedback-item">
  109. <div class="feedback-date">{{ formatDate(feedback.date) }}</div>
  110. <div class="feedback-rating">
  111. @for (star of [1,2,3,4,5]; track star) {
  112. <span>
  113. <i class="fa" [ngClass]="{ 'fa-star': star <= feedback.rating, 'fa-star-o': star > feedback.rating }"></i>
  114. </span>
  115. }
  116. </div>
  117. <div class="feedback-content">{{ feedback.content }}</div>
  118. @if (feedback.response) {
  119. <div class="feedback-response">
  120. <strong>回复:</strong>{{ feedback.response }}
  121. </div>
  122. }
  123. </div>
  124. }
  125. </div>
  126. </div>
  127. </div>
  128. <!-- 进度时间轴 -->
  129. <div class="card timeline-card">
  130. <div class="timeline-header">
  131. <h3 class="card-title">项目阶段时间轴</h3>
  132. <!-- 新的流程状态显示条 - 与标题右对齐 -->
  133. <div class="process-status-display">
  134. <app-process-status-bar
  135. [stageData]="processStatusStages">
  136. </app-process-status-bar>
  137. </div>
  138. </div>
  139. <div class="project-timeline">
  140. @for (stage of projectStages; track $index; let i = $index) {
  141. <div class="timeline-item" [class.stage-completed]="stage.completed" [class.stage-in-progress]="stage.inProgress">
  142. <div class="timeline-icon" [class.icon-completed]="stage.completed" [class.icon-in-progress]="stage.inProgress">
  143. <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  144. <circle cx="12" cy="12" r="9"></circle>
  145. @if (stage.completed) {
  146. <path d="m5 12 5 5 10-10" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
  147. }
  148. @if (stage.inProgress) {
  149. <circle cx="12" cy="12" r="5"></circle>
  150. }
  151. </svg>
  152. </div>
  153. @if (i < (projectStages?.length || 0) - 1) {
  154. <div class="timeline-line" [class.line-completed]="stage.completed && projectStages?.[i+1]?.completed"></div>
  155. }
  156. <div class="timeline-content">
  157. <div class="timeline-header">
  158. <h4 class="stage-title">{{ stage.name }}</h4>
  159. <span class="stage-status">
  160. {{ stage.completed ? '已完成' : stage.inProgress ? '进行中' : '未开始' }}
  161. </span>
  162. </div>
  163. <div class="timeline-meta">
  164. <div class="stage-dates">
  165. @if (stage.startDate) {
  166. <span class="date-item">开始:{{ formatDate(stage.startDate) }}</span>
  167. }
  168. @if (stage.endDate) {
  169. <span class="date-item">完成:{{ formatDate(stage.endDate) }}</span>
  170. }
  171. </div>
  172. <div class="stage-responsible">负责人:{{ stage.responsible || '未分配' }}</div>
  173. </div>
  174. @if (stage.details) {
  175. <div class="stage-details">
  176. <p>{{ stage.details }}</p>
  177. </div>
  178. }
  179. </div>
  180. </div>
  181. }
  182. </div>
  183. </div>
  184. <!-- 项目详情标签页 -->
  185. <div class="project-tabs ios-tabs">
  186. <div class="tab-header">
  187. <button class="tab-btn btn-hover-effect" [class.active]="activeTab() === 'overview'" (click)="switchTab('overview')">
  188. <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  189. <path d="M5 12h14M12 5l7 7-7 7"></path>
  190. </svg>
  191. <span>概览</span>
  192. </button>
  193. <button class="tab-btn btn-hover-effect" [class.active]="activeTab() === 'milestones'" (click)="switchTab('milestones')">
  194. <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  195. <polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline>
  196. </svg>
  197. <span>里程碑</span>
  198. </button>
  199. <button class="tab-btn btn-hover-effect" [class.active]="activeTab() === 'tasks'" (click)="switchTab('tasks')">
  200. <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  201. <polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline>
  202. </svg>
  203. <span>任务</span>
  204. </button>
  205. <button class="tab-btn btn-hover-effect" [class.active]="activeTab() === 'messages'" (click)="switchTab('messages')">
  206. <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  207. <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
  208. </svg>
  209. <span>消息</span>
  210. </button>
  211. <button class="tab-btn btn-hover-effect" [class.active]="activeTab() === 'files'" (click)="switchTab('files')">
  212. <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  213. <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
  214. <polyline points="14 2 14 8 20 8"></polyline>
  215. <line x1="16" y1="13" x2="8" y2="13"></line>
  216. <line x1="16" y1="17" x2="8" y2="17"></line>
  217. <polyline points="10 9 9 9 8 9"></polyline>
  218. </svg>
  219. <span>文件</span>
  220. </button>
  221. </div>
  222. <!-- 消息标签内容 -->
  223. @if (activeTab() === 'messages') {
  224. }
  225. <div class="tab-content">
  226. <div class="messages-container">
  227. <div class="messages-list">
  228. @for (message of messages(); track $index) {
  229. <div class="message-item">
  230. <div class="message-avatar">
  231. {{ message.sender.charAt(0) }}
  232. </div>
  233. <div class="message-content">
  234. <div class="message-header">
  235. <span class="message-sender">{{ message.sender }}</span>
  236. <span class="message-time">{{ formatDateTime(message.timestamp) }}</span>
  237. </div>
  238. <div class="message-text">{{ message.content }}</div>
  239. </div>
  240. </div>
  241. }
  242. </div>
  243. <div class="message-input-area">
  244. <textarea
  245. [value]="newMessage()"
  246. (input)="onMessageInput($event)"
  247. placeholder="输入消息内容..."
  248. rows="3"
  249. (keydown.enter.shift)="$event.preventDefault()"
  250. (keydown.enter)="sendMessage()"
  251. ></textarea>
  252. <div class="message-actions">
  253. <button class="secondary-btn btn-hover-effect">
  254. <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  255. <path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"></path>
  256. <polyline points="14 2 14 8 20 8"></polyline>
  257. </svg>
  258. <span>上传文件</span>
  259. </button>
  260. <button class="primary-btn btn-hover-effect" (click)="sendMessage()" [disabled]="!newMessage().trim()">
  261. <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  262. <line x1="22" y1="2" x2="11" y2="13"></line>
  263. <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
  264. </svg>
  265. <span>发送</span>
  266. </button>
  267. </div>
  268. </div>
  269. </div>
  270. </div>
  271. <!-- 概览标签内容 -->
  272. @if (activeTab() === 'overview') {
  273. <div class="tab-content">
  274. <div class="overview-grid">
  275. <!-- 客户信息卡片 -->
  276. <div class="info-card">
  277. <h4 class="card-title">客户信息</h4>
  278. <div class="customer-info">
  279. <div class="info-item">
  280. <label>客户姓名</label>
  281. <span>{{ project()?.customerName || '王先生' }}</span>
  282. </div>
  283. <div class="info-item">
  284. <label>联系方式</label>
  285. <span>138****5678</span>
  286. </div>
  287. <div class="info-item">
  288. <label>标签</label>
  289. <div class="tag-container">
  290. <span class="tag">朋友圈</span>
  291. <span class="tag">软装</span>
  292. <span class="tag">现代风格</span>
  293. </div>
  294. </div>
  295. <div class="info-item">
  296. <label>高优先级需求</label>
  297. <ul class="need-list">
  298. @for (need of project()?.highPriorityNeeds || ['客厅光线充足', '储物空间充足', '环保材料']; track $index) {
  299. <li>
  300. <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  301. <polyline points="20 6 9 17 4 12"></polyline>
  302. </svg>
  303. {{ need }}
  304. </li>
  305. }
  306. </ul>
  307. </div>
  308. </div>
  309. </div>
  310. <!-- 项目团队卡片 -->
  311. <div class="info-card">
  312. <h4 class="card-title">项目团队</h4>
  313. <div class="team-info">
  314. <div class="team-member">
  315. <div class="member-avatar" title="客服小李">IMG</div>
  316. <div class="member-details">
  317. <div class="member-name">客服小李</div>
  318. <div class="member-role">客户经理</div>
  319. </div>
  320. <button class="message-btn">
  321. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  322. <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
  323. </svg>
  324. </button>
  325. </div>
  326. <div class="team-member">
  327. <div class="member-avatar" title="张设计师">IMG</div>
  328. <div class="member-details">
  329. <div class="member-name">张设计师</div>
  330. <div class="member-role">主设计师</div>
  331. </div>
  332. <button class="message-btn">
  333. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  334. <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
  335. </svg>
  336. </button>
  337. </div>
  338. </div>
  339. </div>
  340. <!-- 最近反馈卡片 -->
  341. <div class="info-card">
  342. <h4 class="card-title">客户反馈</h4>
  343. <div class="feedback-list">
  344. @for (feedback of feedbacks(); track feedback.id || $index) {
  345. <div class="feedback-item">
  346. <div class="feedback-header">
  347. <div class="feedback-author">{{ getFeedbackCustomerName(feedback) }}</div>
  348. <div class="feedback-rating">
  349. <span class="rating-stars">★★★★☆</span>
  350. <span class="rating-number">{{ getFeedbackRating(feedback) }}.0</span>
  351. </div>
  352. </div>
  353. <div class="feedback-content">{{ feedback?.content || '' }}</div>
  354. @if (feedback?.response) {
  355. <div class="feedback-response">
  356. <div class="response-label">客服回复:</div>
  357. <div class="response-text">{{ feedback.response }}</div>
  358. </div>
  359. }
  360. <div class="feedback-meta">
  361. <span class="feedback-date">{{ formatDate(feedback?.createdAt) }}</span>
  362. <span class="feedback-status" [class.status-processed]="feedback?.status === '已解决'" [class.status-pending]="feedback?.status === '待处理'" [class.status-processing]="feedback?.status === '处理中'">
  363. {{ feedback?.status || '未知状态' }}
  364. </span>
  365. </div>
  366. </div>
  367. }
  368. @if (feedbacks().length > 0) {
  369. <button class="view-all-btn btn-hover-effect">查看全部反馈</button>
  370. }
  371. </div>
  372. </div>
  373. </div>
  374. <!-- 订单创建板块 -->
  375. <div class="order-creation-section">
  376. <div class="section-header">
  377. <h4 class="section-title">
  378. <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  379. <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
  380. <polyline points="14 2 14 8 20 8"></polyline>
  381. <line x1="16" y1="13" x2="8" y2="13"></line>
  382. <line x1="16" y1="17" x2="8" y2="17"></line>
  383. </svg>
  384. 订单创建
  385. </h4>
  386. <button class="toggle-btn" (click)="toggleOrderCreation()" [class.expanded]="isOrderCreationExpanded()">
  387. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  388. <polyline points="6 9 12 15 18 9"></polyline>
  389. </svg>
  390. </button>
  391. </div>
  392. <div class="order-creation-content" [class.expanded]="isOrderCreationExpanded()">
  393. <div class="order-creation-container">
  394. <div class="scrollable-content">
  395. <!-- 客户信息长条栏 -->
  396. <section class="customer-info-bar">
  397. <div class="customer-bar-container">
  398. <!-- 左侧客户搜索和信息 -->
  399. <div class="customer-search-area">
  400. @if (!selectedOrderCustomer()) {
  401. <div class="search-input-container">
  402. <div class="search-icon">
  403. <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  404. <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
  405. <circle cx="12" cy="7" r="4"></circle>
  406. </svg>
  407. </div>
  408. <input
  409. type="text"
  410. [ngModel]="orderSearchKeyword()"
  411. (ngModelChange)="orderSearchKeyword.set($event); searchOrderCustomer()"
  412. placeholder="输入客户姓名或手机号快速匹配老客户..."
  413. class="customer-search-input"
  414. autocomplete="off"
  415. (keyup.enter)="quickFillOrderCustomerInfo(orderSearchKeyword())"
  416. />
  417. <button
  418. class="search-action-btn"
  419. (click)="quickFillOrderCustomerInfo(orderSearchKeyword())"
  420. [disabled]="!orderSearchKeyword().trim()"
  421. >
  422. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  423. <path d="M9 11l3 3 8-8"></path>
  424. <path d="M21 12c0 4.97-4.03 9-9 9s-9-4.03-9-9 4.03-9 9-9c1.51 0 2.93.37 4.18 1.03"></path>
  425. </svg>
  426. 匹配
  427. </button>
  428. </div>
  429. <!-- 搜索结果下拉 -->
  430. @if (orderSearchResults().length > 0) {
  431. <div class="search-dropdown">
  432. <div class="dropdown-header">
  433. <span>找到 {{ orderSearchResults().length }} 位客户</span>
  434. </div>
  435. @for (customer of orderSearchResults(); track customer.id) {
  436. <div class="customer-option" (click)="selectOrderCustomer(customer)">
  437. <div class="customer-avatar-sm">
  438. @if (customer.avatar) {
  439. <img [src]="customer.avatar" [alt]="customer.name">
  440. } @else {
  441. <span>{{ customer.name.charAt(0) }}</span>
  442. }
  443. </div>
  444. <div class="customer-details">
  445. <div class="customer-name">{{ customer.name }}</div>
  446. <div class="customer-phone">{{ customer.phone }}</div>
  447. </div>
  448. <div class="customer-tags">
  449. <span class="tag">{{ customer.customerType }}</span>
  450. </div>
  451. </div>
  452. }
  453. </div>
  454. }
  455. } @else {
  456. <!-- 已选择客户信息展示 -->
  457. <div class="selected-customer-info">
  458. <div class="customer-avatar-lg">
  459. @if (selectedOrderCustomer()?.avatar) {
  460. <img [src]="selectedOrderCustomer()?.avatar" [alt]="selectedOrderCustomer()?.name">
  461. } @else {
  462. <span>{{ selectedOrderCustomer()?.name?.charAt(0) }}</span>
  463. }
  464. </div>
  465. <div class="customer-main-info">
  466. <div class="customer-name">{{ selectedOrderCustomer()?.name }}</div>
  467. <div class="customer-phone">{{ selectedOrderCustomer()?.phone }}</div>
  468. <div class="customer-type">{{ selectedOrderCustomer()?.customerType }}</div>
  469. </div>
  470. <button class="clear-customer-btn" (click)="clearOrderCustomer()">
  471. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  472. <line x1="18" y1="6" x2="6" y2="18"></line>
  473. <line x1="6" y1="6" x2="18" y2="18"></line>
  474. </svg>
  475. 重新选择
  476. </button>
  477. </div>
  478. }
  479. </div>
  480. </div>
  481. </section>
  482. <!-- 项目需求表单 -->
  483. <section class="requirement-section">
  484. <div class="section-header">
  485. <h3>项目需求</h3>
  486. </div>
  487. <form [formGroup]="orderRequirementForm" class="requirement-form">
  488. <div class="form-row">
  489. <div class="form-group">
  490. <label for="projectType">项目类型 <span class="required">*</span></label>
  491. <select id="projectType" formControlName="projectType" class="field-select">
  492. <option value="">请选择项目类型</option>
  493. <option value="residential">住宅设计</option>
  494. <option value="commercial">商业空间</option>
  495. <option value="office">办公空间</option>
  496. <option value="restaurant">餐饮空间</option>
  497. </select>
  498. </div>
  499. <div class="form-group">
  500. <label for="area">面积 <span class="required">*</span></label>
  501. <div class="input-with-unit">
  502. <input type="number" id="area" formControlName="area" class="field-input" placeholder="请输入面积">
  503. <span class="input-unit">㎡</span>
  504. </div>
  505. </div>
  506. </div>
  507. <div class="form-row">
  508. <div class="form-group">
  509. <label for="style">装修风格</label>
  510. <select id="style" formControlName="style" class="field-select">
  511. <option value="">请选择装修风格</option>
  512. @for (style of decorationStyles; track style.value) {
  513. <option [value]="style.value">{{ style.label }}</option>
  514. }
  515. </select>
  516. </div>
  517. <div class="form-group">
  518. <label for="budget">预算范围</label>
  519. <select id="budget" formControlName="budget" class="field-select">
  520. <option value="">请选择预算范围</option>
  521. <option value="5-10">5-10万</option>
  522. <option value="10-20">10-20万</option>
  523. <option value="20-50">20-50万</option>
  524. <option value="50+">50万以上</option>
  525. </select>
  526. </div>
  527. </div>
  528. <div class="form-group full-width">
  529. <label for="requirements">具体需求</label>
  530. <textarea id="requirements" formControlName="requirements" class="field-textarea" placeholder="请详细描述您的设计需求..."></textarea>
  531. </div>
  532. </form>
  533. </section>
  534. </div>
  535. <!-- 操作按钮 -->
  536. <div class="order-actions">
  537. <button class="secondary-btn" (click)="resetOrderForm()">
  538. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  539. <polyline points="1 4 1 10 7 10"></polyline>
  540. <path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path>
  541. </svg>
  542. 重置
  543. </button>
  544. <button class="primary-btn" (click)="createOrder()" [disabled]="!isOrderFormValid()">
  545. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  546. <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
  547. <polyline points="22 4 12 14.01 9 11.01"></polyline>
  548. </svg>
  549. 创建订单
  550. </button>
  551. </div>
  552. </div>
  553. </div>
  554. </div>
  555. </div>
  556. }
  557. <!-- 里程碑标签内容 -->
  558. @if (activeTab() === 'milestones') {
  559. <div class="tab-content">
  560. <div class="milestones-timeline">
  561. @for (milestone of milestones(); track milestone.id || $index; let i = $index) {
  562. <div class="milestone-item">
  563. <div class="milestone-dot" [class.completed]="milestone.isCompleted"></div>
  564. @if (i < milestones().length - 1) {
  565. <div class="milestone-line" [class.completed]="milestone.isCompleted && milestones()[i+1].isCompleted"></div>
  566. }
  567. <div class="milestone-content">
  568. <div class="milestone-header">
  569. <h4 class="milestone-title">{{ milestone.title }}</h4>
  570. <span class="milestone-status" [class.status-completed]="milestone.isCompleted" [class.status-pending]="!milestone.isCompleted">
  571. {{ milestone.isCompleted ? '已完成' : '进行中' }}
  572. </span>
  573. </div>
  574. <p class="milestone-description">{{ milestone.description }}</p>
  575. <div class="milestone-dates">
  576. <div class="date-item">
  577. <label>截止日期</label>
  578. <span>{{ formatDate(milestone.dueDate) }}</span>
  579. </div>
  580. @if (milestone.completedDate) {
  581. <div class="date-item">
  582. <label>完成日期</label>
  583. <span>{{ formatDate(milestone.completedDate) }}</span>
  584. </div>
  585. }
  586. </div>
  587. @if (!milestone.isCompleted) {
  588. <div class="milestone-actions">
  589. <button class="primary-btn small btn-hover-effect" (click)="completeMilestone(milestone.id)">
  590. <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  591. <polyline points="20 6 9 17 4 12"></polyline>
  592. </svg>
  593. <span>标记完成</span>
  594. </button>
  595. </div>
  596. }
  597. </div>
  598. </div>
  599. }
  600. </div>
  601. </div>
  602. }
  603. <!-- 任务标签内容 -->
  604. @if (activeTab() === 'tasks') {
  605. <div class="tab-content">
  606. <div class="tasks-filter">
  607. <div class="filter-options">
  608. <button class="filter-btn active">全部任务</button>
  609. <button class="filter-btn">进行中</button>
  610. <button class="filter-btn">已完成</button>
  611. <button class="filter-btn">逾期</button>
  612. </div>
  613. <div class="search-box">
  614. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  615. <circle cx="11" cy="11" r="8"></circle>
  616. <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
  617. </svg>
  618. <input type="text" placeholder="搜索任务...">
  619. </div>
  620. </div>
  621. <div class="tasks-list">
  622. <!-- 修复任务列表中的状态显示,确保安全访问 -->
  623. @for (task of tasks(); track task.id || $index) {
  624. <div class="task-item">
  625. <div class="task-checkbox">
  626. <input type="checkbox" [checked]="task.isCompleted" (change)="task.isCompleted ? null : completeTask(task.id)">
  627. </div>
  628. <div class="task-content">
  629. <h4 class="task-title" [class.completed]="task.isCompleted">{{ task.title || '未命名任务' }}</h4>
  630. <p class="task-description">{{ task.description || '' }}</p>
  631. <div class="task-meta">
  632. <span class="task-assignee">
  633. <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  634. <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
  635. <circle cx="9" cy="7" r="4"></circle>
  636. <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
  637. <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
  638. </svg>
  639. {{ task.assignee || '未分配' }}
  640. </span>
  641. <span class="task-deadline" [class.overdue]="task.isOverdue">
  642. <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  643. <circle cx="12" cy="12" r="10"></circle>
  644. <polyline points="12 6 12 12 16 14"></polyline>
  645. </svg>
  646. {{ formatDate(task.deadline) }}
  647. </span>
  648. <span class="task-priority" [class.priority-high]="task.priority === 'high'" [class.priority-medium]="task.priority === 'medium'" [class.priority-low]="task.priority === 'low'">
  649. {{ task.priority === 'high' ? '高' : task.priority === 'medium' ? '中' : '低' }}
  650. </span>
  651. </div>
  652. </div>
  653. </div>
  654. }
  655. </div>
  656. </div>
  657. }
  658. <!-- 消息标签内容 -->
  659. @if (activeTab() === 'messages') {
  660. <div class="tab-content">
  661. <div class="messages-container">
  662. <div class="messages-list">
  663. @for (message of messages(); track $index) {
  664. <div class="message-item">
  665. <div class="message-avatar">
  666. {{ message.sender.charAt(0) }}
  667. </div>
  668. <div class="message-content">
  669. <div class="message-header">
  670. <span class="message-sender">{{ message.sender }}</span>
  671. <span class="message-time">{{ formatDateTime(message.timestamp) }}</span>
  672. </div>
  673. <div class="message-text">{{ message.content }}</div>
  674. </div>
  675. </div>
  676. }
  677. </div>
  678. </div>
  679. </div>
  680. <!-- 右侧边栏 - 企业微信聊天集成 -->
  681. <div class="wechat-sidebar ios-sidebar">
  682. <div class="wechat-header">
  683. <h3>项目群聊</h3>
  684. <div class="wechat-actions">
  685. <button class="search-btn">
  686. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  687. <circle cx="11" cy="11" r="8"></circle>
  688. <path d="m21 21-4.35-4.35"></path>
  689. </svg>
  690. </button>
  691. <button class="settings-btn">
  692. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  693. <circle cx="12" cy="12" r="3"></circle>
  694. <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
  695. </svg>
  696. </button>
  697. </div>
  698. </div>
  699. <!-- 聊天消息列表 -->
  700. <div class="wechat-messages" #wechatMessages>
  701. @for (msg of wechatMessagesList; track $index) {
  702. <div class="wechat-message-item">
  703. <div class="message-avatar">
  704. {{ msg.sender.charAt(0) }}
  705. </div>
  706. <div class="message-content">
  707. <div class="message-header">
  708. <span class="message-sender">{{ msg.sender }}</span>
  709. <span class="message-time">{{ formatTime(msg.timestamp) }}</span>
  710. </div>
  711. <div class="message-text">{{ msg.content }}</div>
  712. </div>
  713. </div>
  714. }
  715. </div>
  716. <!-- 消息输入框 -->
  717. <div class="wechat-input-area">
  718. <div class="input-actions">
  719. <button class="action-btn btn-hover-effect">
  720. <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  721. <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
  722. <circle cx="8.5" cy="8.5" r="1.5"></circle>
  723. <polyline points="21 15 16 10 5 21"></polyline>
  724. </svg>
  725. </button>
  726. <button class="action-btn btn-hover-effect">
  727. <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  728. <path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"></path>
  729. <polyline points="14 2 14 8 20 8"></polyline>
  730. </svg>
  731. </button>
  732. </div>
  733. <input
  734. type="text"
  735. [(ngModel)]="wechatInput"
  736. placeholder="输入消息..."
  737. class="wechat-input"
  738. (keydown.enter)="sendWechatMessage()"
  739. />
  740. <button class="send-btn btn-hover-effect" (click)="sendWechatMessage()" [disabled]="!wechatInput.trim()">
  741. 发送
  742. </button>
  743. </div>
  744. </div>
  745. }
  746. <!-- 售后处理入口 (固定在底部) -->
  747. <div class="after-sales-actions ios-actions">
  748. <div class="actions-container">
  749. <button class="action-btn primary btn-hover-effect" (click)="openModificationRequest()">
  750. <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  751. <path d="M12 20h9"></path>
  752. <path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path>
  753. </svg>
  754. <span>申请修改</span>
  755. </button>
  756. <button class="action-btn warning btn-hover-effect" (click)="openComplaintWarning()">
  757. <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  758. <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path>
  759. <line x1="12" y1="9" x2="12" y2="13"></line>
  760. <line x1="12" y1="17" x2="12.01" y2="17"></line>
  761. </svg>
  762. <span>投诉预警</span>
  763. </button>
  764. <button class="action-btn secondary btn-hover-effect" (click)="openRefundRequest()">
  765. <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  766. <path d="M22 12h-4l-3 9L9 3l-3 9H2"></path>
  767. </svg>
  768. <span>申请退款</span>
  769. </button>
  770. </div>
  771. </div>
  772. </div>
  773. <!-- 文件标签内容 -->
  774. @if (activeTab() === 'files') {
  775. <div class="tab-content">
  776. <div class="files-header">
  777. <h4>项目文件</h4>
  778. <div class="files-actions">
  779. <button class="primary-btn btn-hover-effect" (click)="openRenderPreview()" [disabled]="renderImages().length === 0">
  780. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  781. <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
  782. <circle cx="8.5" cy="8.5" r="1.5"></circle>
  783. <polyline points="21 15 16 10 5 21"></polyline>
  784. </svg>
  785. <span>查看渲染图</span>
  786. </button>
  787. </div>
  788. </div>
  789. <div class="files-list">
  790. @for (f of files(); track f.id || $index) {
  791. <div class="file-item">
  792. <div class="file-icon">{{ f.type === 'image' ? 'IMG' : 'DOC' }}</div>
  793. <div class="file-info">
  794. <div class="file-name">{{ f.name }}</div>
  795. <div class="file-meta">{{ f.size }} · 由 {{ f.uploadedBy }} 于 {{ formatDate(f.uploadedAt) }} 上传</div>
  796. </div>
  797. <div class="file-actions">
  798. <button class="link" (click)="previewFile(f)">预览</button>
  799. <button class="link" (click)="downloadFile(f)">下载</button>
  800. </div>
  801. </div>
  802. }
  803. </div>
  804. </div>
  805. }
  806. <!-- 只读渲染图预览弹窗 -->
  807. @if (showRenderPreviewModal) {
  808. <div class="modal-backdrop" (click)="closeRenderPreview()"></div>
  809. <div class="modal" role="dialog" aria-modal="true">
  810. <div class="modal-header">
  811. <h3>渲染图预览</h3>
  812. <button class="close-button" (click)="closeRenderPreview()" aria-label="关闭">×</button>
  813. </div>
  814. <div class="modal-body">
  815. @if (renderImages().length > 0) {
  816. <div class="thumb-list">
  817. @for (img of renderImages(); track img.id) {
  818. <div class="thumb-item">
  819. <img [src]="img.url" [alt]="img.name" />
  820. <div class="thumb-meta">
  821. <div class="name">{{ img.name }}</div>
  822. <div class="sub">{{ img.size }} · {{ formatDate(img.uploadedAt) }}</div>
  823. </div>
  824. </div>
  825. }
  826. </div>
  827. } @else {
  828. <div class="empty">
  829. <p>暂无可预览的渲染图</p>
  830. </div>
  831. }
  832. </div>
  833. <div class="modal-footer">
  834. <button class="secondary-btn" (click)="closeRenderPreview()">关闭</button>
  835. </div>
  836. </div>
  837. }