project-timeline.scss 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841
  1. .project-timeline-container {
  2. width: 100%;
  3. height: 100%;
  4. display: flex;
  5. flex-direction: column;
  6. background: #ffffff;
  7. border-radius: 8px;
  8. overflow: hidden;
  9. }
  10. // 顶部筛选栏
  11. .timeline-header {
  12. padding: 16px;
  13. background: #f9fafb;
  14. border-bottom: 1px solid #e5e7eb;
  15. }
  16. .filter-section {
  17. display: flex;
  18. align-items: center;
  19. gap: 16px;
  20. flex-wrap: wrap;
  21. }
  22. .filter-group {
  23. display: flex;
  24. align-items: center;
  25. gap: 8px;
  26. label {
  27. font-size: 14px;
  28. font-weight: 500;
  29. color: #374151;
  30. white-space: nowrap;
  31. }
  32. }
  33. .filter-select {
  34. padding: 6px 12px;
  35. border: 1px solid #d1d5db;
  36. border-radius: 6px;
  37. font-size: 14px;
  38. background: #ffffff;
  39. cursor: pointer;
  40. transition: all 0.2s;
  41. &:hover {
  42. border-color: #9ca3af;
  43. }
  44. &:focus {
  45. outline: none;
  46. border-color: #3b82f6;
  47. box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
  48. }
  49. }
  50. .quick-filters {
  51. margin-left: auto;
  52. }
  53. .filter-btn {
  54. padding: 6px 12px;
  55. border: 1px solid #d1d5db;
  56. border-radius: 6px;
  57. background: #ffffff;
  58. font-size: 13px;
  59. cursor: pointer;
  60. transition: all 0.2s;
  61. &:hover {
  62. background: #f3f4f6;
  63. }
  64. &.active {
  65. background: #3b82f6;
  66. color: #ffffff;
  67. border-color: #3b82f6;
  68. }
  69. }
  70. .sort-controls,
  71. .time-scale-controls {
  72. border-left: 1px solid #e5e7eb;
  73. padding-left: 16px;
  74. }
  75. .sort-btn,
  76. .scale-btn,
  77. .refresh-btn {
  78. padding: 6px 12px;
  79. border: 1px solid #d1d5db;
  80. border-radius: 6px;
  81. background: #ffffff;
  82. font-size: 13px;
  83. cursor: pointer;
  84. transition: all 0.2s;
  85. &:hover {
  86. background: #f3f4f6;
  87. }
  88. &.active {
  89. background: #3b82f6;
  90. color: #ffffff;
  91. border-color: #3b82f6;
  92. }
  93. }
  94. // 时间尺度切换按钮特殊样式
  95. .time-scale-controls {
  96. .scale-btn.active {
  97. background: #10b981;
  98. border-color: #10b981;
  99. font-weight: 600;
  100. }
  101. }
  102. // 🆕 刷新按钮特殊样式
  103. .refresh-btn {
  104. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  105. color: #ffffff;
  106. border: none;
  107. font-weight: 600;
  108. &:hover {
  109. background: linear-gradient(135deg, #5568d3 0%, #63408b 100%);
  110. transform: scale(1.05);
  111. }
  112. &:active {
  113. animation: refresh-spin 0.6s ease-in-out;
  114. }
  115. }
  116. @keyframes refresh-spin {
  117. from {
  118. transform: rotate(0deg);
  119. }
  120. to {
  121. transform: rotate(360deg);
  122. }
  123. }
  124. // 设计师统计面板
  125. .designer-stats-panel {
  126. margin-top: 12px;
  127. padding: 12px 16px;
  128. background: #ffffff;
  129. border: 1px solid #e5e7eb;
  130. border-radius: 6px;
  131. }
  132. .stats-header {
  133. display: flex;
  134. align-items: center;
  135. justify-content: space-between;
  136. margin-bottom: 12px;
  137. h3 {
  138. margin: 0;
  139. font-size: 16px;
  140. font-weight: 600;
  141. color: #111827;
  142. }
  143. }
  144. .workload-badge {
  145. padding: 4px 12px;
  146. border-radius: 12px;
  147. font-size: 13px;
  148. font-weight: 500;
  149. &.level-low {
  150. background: #d1fae5;
  151. color: #065f46;
  152. }
  153. &.level-medium {
  154. background: #fef3c7;
  155. color: #92400e;
  156. }
  157. &.level-high {
  158. background: #fee2e2;
  159. color: #991b1b;
  160. }
  161. }
  162. .stats-body {
  163. display: grid;
  164. grid-template-columns: repeat(3, 1fr);
  165. gap: 12px;
  166. }
  167. .stat-item {
  168. display: flex;
  169. flex-direction: column;
  170. padding: 8px;
  171. background: #f9fafb;
  172. border-radius: 4px;
  173. }
  174. .stat-label {
  175. font-size: 12px;
  176. color: #6b7280;
  177. margin-bottom: 4px;
  178. }
  179. .stat-value {
  180. font-size: 18px;
  181. font-weight: 600;
  182. color: #111827;
  183. &.urgent {
  184. color: #ea580c;
  185. }
  186. &.overdue {
  187. color: #dc2626;
  188. }
  189. }
  190. // 时间轴主体
  191. .timeline-body {
  192. flex: 1;
  193. overflow-y: auto;
  194. padding: 16px;
  195. }
  196. // 空状态
  197. .empty-state {
  198. display: flex;
  199. align-items: center;
  200. justify-content: center;
  201. padding: 60px 20px;
  202. text-align: center;
  203. p {
  204. margin: 0;
  205. font-size: 15px;
  206. color: #9ca3af;
  207. }
  208. }
  209. // 时间轴视图容器
  210. .timeline-view-container {
  211. position: relative;
  212. width: 100%;
  213. min-height: 400px;
  214. }
  215. // 图例说明
  216. .timeline-legend {
  217. display: flex;
  218. justify-content: center;
  219. align-items: center;
  220. gap: 24px;
  221. padding: 12px 20px;
  222. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  223. border-radius: 8px 8px 0 0;
  224. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  225. }
  226. .legend-item {
  227. display: flex;
  228. align-items: center;
  229. gap: 8px;
  230. }
  231. .legend-icon {
  232. display: flex;
  233. align-items: center;
  234. justify-content: center;
  235. width: 24px;
  236. height: 24px;
  237. border-radius: 50%;
  238. font-size: 16px;
  239. color: #ffffff;
  240. box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
  241. &.start-icon {
  242. background: #10b981;
  243. }
  244. &.review-icon {
  245. background: #8b5cf6;
  246. }
  247. &.delivery-icon {
  248. background: #f59e0b;
  249. border-radius: 4px;
  250. transform: rotate(45deg);
  251. }
  252. // 🆕 阶段文本图标样式
  253. &.phase-icon {
  254. font-size: 14px; // 调整字体大小使其在小圆圈中居中
  255. font-weight: bold;
  256. width: 28px;
  257. height: 28px;
  258. border-radius: 50%;
  259. background: rgba(255, 255, 255, 0.2); // 半透明背景
  260. border: 1px solid rgba(255, 255, 255, 0.4); // 描边
  261. color: #ffffff;
  262. box-shadow: none; // 移除阴影
  263. }
  264. }
  265. .legend-bar-demo {
  266. width: 40px;
  267. height: 12px;
  268. border-radius: 4px;
  269. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  270. }
  271. // 🆕 四种紧急度颜色图例
  272. .legend-bar-green {
  273. background: linear-gradient(135deg, #86EFAC 0%, #4ADE80 100%);
  274. }
  275. .legend-bar-yellow {
  276. background: linear-gradient(135deg, #FEF08A 0%, #EAB308 100%);
  277. }
  278. .legend-bar-orange {
  279. background: linear-gradient(135deg, #FCD34D 0%, #F59E0B 100%);
  280. }
  281. .legend-bar-red {
  282. background: linear-gradient(135deg, #FCA5A5 0%, #EF4444 100%);
  283. }
  284. .legend-label {
  285. font-size: 13px;
  286. font-weight: 500;
  287. color: #ffffff;
  288. text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
  289. }
  290. // 🆕 图例注释样式
  291. .legend-note {
  292. margin-left: auto;
  293. padding-left: 16px;
  294. border-left: 1px solid rgba(255, 255, 255, 0.3);
  295. .legend-label {
  296. font-size: 12px;
  297. font-weight: 600;
  298. color: #fef3c7;
  299. opacity: 0.95;
  300. }
  301. }
  302. // 时间刻度尺
  303. .timeline-ruler {
  304. display: flex;
  305. position: sticky;
  306. top: 0;
  307. z-index: 10;
  308. background: #ffffff;
  309. border-bottom: 2px solid #e5e7eb;
  310. padding: 8px 0;
  311. }
  312. .ruler-header {
  313. width: 180px;
  314. min-width: 180px;
  315. padding: 12px 12px;
  316. font-weight: 600;
  317. font-size: 14px;
  318. color: #111827;
  319. border-right: 2px solid #e5e7eb;
  320. background: #f9fafb;
  321. }
  322. .ruler-ticks {
  323. flex: 1;
  324. display: flex;
  325. position: relative;
  326. }
  327. .ruler-tick {
  328. flex: 1;
  329. text-align: center;
  330. border-right: 1px solid #e5e7eb;
  331. padding: 8px 4px;
  332. display: flex;
  333. flex-direction: column;
  334. gap: 2px;
  335. &:last-child {
  336. border-right: none;
  337. }
  338. &.first {
  339. border-left: 2px solid #3b82f6;
  340. }
  341. }
  342. .tick-date {
  343. font-size: 14px;
  344. color: #111827;
  345. font-weight: 600;
  346. line-height: 1.2;
  347. }
  348. .tick-weekday {
  349. font-size: 11px;
  350. color: #6b7280;
  351. font-weight: 500;
  352. line-height: 1.2;
  353. }
  354. // 🆕 今日标记线(实时移动,精确到分钟)- 重构版
  355. .today-line {
  356. position: absolute;
  357. top: 0;
  358. bottom: 0;
  359. z-index: 10;
  360. pointer-events: none;
  361. left: 0; // 通过 [style.left] 动态设置
  362. }
  363. // 🆕 今日时间标签(顶部显示完整时间)
  364. .today-label {
  365. position: absolute;
  366. top: -40px;
  367. left: 50%;
  368. transform: translateX(-50%);
  369. padding: 8px 16px;
  370. background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
  371. color: #ffffff;
  372. font-size: 13px;
  373. font-weight: 700;
  374. border-radius: 8px;
  375. white-space: nowrap;
  376. box-shadow: 0 4px 16px rgba(239, 68, 68, 0.5);
  377. letter-spacing: 0.5px;
  378. animation: today-label-pulse 2s ease-in-out infinite;
  379. // 小三角箭头
  380. &::after {
  381. content: '';
  382. position: absolute;
  383. top: 100%;
  384. left: 50%;
  385. transform: translateX(-50%);
  386. width: 0;
  387. height: 0;
  388. border-left: 6px solid transparent;
  389. border-right: 6px solid transparent;
  390. border-top: 6px solid #dc2626;
  391. }
  392. }
  393. // 🆕 顶部圆点指示器(更大更明显)
  394. .today-dot {
  395. position: absolute;
  396. top: -4px;
  397. left: 50%;
  398. transform: translateX(-50%);
  399. width: 16px;
  400. height: 16px;
  401. background: #ef4444;
  402. border-radius: 50%;
  403. border: 3px solid #ffffff;
  404. box-shadow: 0 0 0 2px #ef4444, 0 4px 12px rgba(239, 68, 68, 0.6);
  405. animation: today-dot-pulse 1.5s ease-in-out infinite;
  406. }
  407. // 🆕 主竖线条(更宽更明显)
  408. .today-bar {
  409. position: absolute;
  410. top: 0;
  411. bottom: 0;
  412. left: 50%;
  413. transform: translateX(-50%);
  414. width: 4px;
  415. background: linear-gradient(180deg,
  416. rgba(239, 68, 68, 0.95) 0%,
  417. rgba(239, 68, 68, 0.85) 50%,
  418. rgba(239, 68, 68, 0.95) 100%
  419. );
  420. box-shadow:
  421. 0 0 8px rgba(239, 68, 68, 0.6),
  422. 0 0 16px rgba(239, 68, 68, 0.4);
  423. animation: today-bar-pulse 2s ease-in-out infinite;
  424. }
  425. // 🆕 时间标签脉动动画
  426. @keyframes today-label-pulse {
  427. 0%, 100% {
  428. transform: translateX(-50%) scale(1);
  429. box-shadow: 0 4px 16px rgba(239, 68, 68, 0.5);
  430. }
  431. 50% {
  432. transform: translateX(-50%) scale(1.05);
  433. box-shadow: 0 6px 24px rgba(239, 68, 68, 0.7);
  434. }
  435. }
  436. // 🆕 圆点脉动动画(更明显)
  437. @keyframes today-dot-pulse {
  438. 0%, 100% {
  439. transform: translateX(-50%) scale(1);
  440. box-shadow: 0 0 0 2px #ef4444, 0 4px 12px rgba(239, 68, 68, 0.6);
  441. }
  442. 50% {
  443. transform: translateX(-50%) scale(1.4);
  444. box-shadow: 0 0 0 4px rgba(239, 68, 68, 0.5), 0 6px 20px rgba(239, 68, 68, 0.8);
  445. }
  446. }
  447. // 🆕 竖线脉动动画
  448. @keyframes today-bar-pulse {
  449. 0%, 100% {
  450. opacity: 1;
  451. box-shadow:
  452. 0 0 8px rgba(239, 68, 68, 0.6),
  453. 0 0 16px rgba(239, 68, 68, 0.4);
  454. }
  455. 50% {
  456. opacity: 0.9;
  457. box-shadow:
  458. 0 0 12px rgba(239, 68, 68, 0.8),
  459. 0 0 24px rgba(239, 68, 68, 0.6);
  460. }
  461. }
  462. // 项目时间轴
  463. .timeline-projects {
  464. position: relative;
  465. min-height: 300px;
  466. }
  467. .timeline-row {
  468. display: flex;
  469. border-bottom: 1px solid #f3f4f6;
  470. cursor: pointer;
  471. transition: background 0.2s;
  472. &:hover {
  473. background: #f9fafb;
  474. .project-bar {
  475. transform: scaleY(1.1);
  476. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  477. }
  478. .event-marker {
  479. transform: scale(1.3);
  480. }
  481. }
  482. }
  483. // 项目标签区
  484. .project-label {
  485. width: 180px;
  486. min-width: 180px;
  487. padding: 12px 12px;
  488. display: flex;
  489. flex-direction: column;
  490. gap: 4px;
  491. border-right: 2px solid #e5e7eb;
  492. background: #fafafa;
  493. }
  494. .project-name-label {
  495. font-size: 14px;
  496. font-weight: 500;
  497. color: #111827;
  498. white-space: nowrap;
  499. overflow: hidden;
  500. text-overflow: ellipsis;
  501. }
  502. .designer-label {
  503. font-size: 12px;
  504. color: #6b7280;
  505. }
  506. .priority-badge {
  507. font-size: 16px;
  508. line-height: 1;
  509. }
  510. // 🆕 空间与交付物统计徽章
  511. .space-deliverable-badge {
  512. padding: 2px 8px;
  513. border-radius: 10px;
  514. font-size: 10px;
  515. font-weight: 600;
  516. color: white;
  517. margin-left: 4px;
  518. white-space: nowrap;
  519. cursor: help;
  520. transition: transform 0.2s, box-shadow 0.2s;
  521. &:hover {
  522. transform: scale(1.05);
  523. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  524. }
  525. }
  526. // 时间轴轨道
  527. .timeline-track {
  528. flex: 1;
  529. position: relative;
  530. height: 70px;
  531. padding: 19px 0;
  532. background: repeating-linear-gradient(
  533. 90deg,
  534. transparent,
  535. transparent calc(100% / 7 - 1px),
  536. #f3f4f6 calc(100% / 7 - 1px),
  537. #f3f4f6 calc(100% / 7)
  538. );
  539. }
  540. // 项目条形图
  541. .project-bar {
  542. position: absolute;
  543. top: 50%;
  544. transform: translateY(-50%);
  545. height: 32px;
  546. border-radius: 6px;
  547. transition: all 0.3s;
  548. overflow: hidden;
  549. box-shadow: 0 3px 8px rgba(0, 0, 0, 0.12);
  550. border: 2px solid rgba(255, 255, 255, 0.5);
  551. opacity: 0.95;
  552. &.status-overdue {
  553. border: 3px solid #dc2626;
  554. animation: pulse 2s infinite;
  555. box-shadow: 0 0 15px rgba(220, 38, 38, 0.4);
  556. }
  557. &:hover {
  558. opacity: 1;
  559. }
  560. }
  561. // 进度填充
  562. .progress-fill {
  563. position: absolute;
  564. top: 0;
  565. left: 0;
  566. bottom: 0;
  567. background: linear-gradient(90deg,
  568. rgba(0, 0, 0, 0.25) 0%,
  569. rgba(0, 0, 0, 0.15) 100%
  570. );
  571. transition: width 0.3s;
  572. border-right: 2px solid rgba(255, 255, 255, 0.6);
  573. }
  574. // 事件标记
  575. .event-marker {
  576. position: absolute;
  577. top: 50%;
  578. transform: translateY(-50%);
  579. width: 28px;
  580. height: 28px;
  581. display: flex;
  582. align-items: center;
  583. justify-content: center;
  584. font-size: 20px;
  585. color: #ffffff;
  586. border-radius: 50%;
  587. cursor: pointer;
  588. transition: all 0.2s;
  589. z-index: 10;
  590. box-shadow: 0 3px 10px rgba(0, 0, 0, 0.3);
  591. border: 2px solid rgba(255, 255, 255, 0.9);
  592. &.start {
  593. font-size: 16px;
  594. width: 24px;
  595. height: 24px;
  596. }
  597. &.review {
  598. font-size: 18px;
  599. width: 26px;
  600. height: 26px;
  601. border-radius: 50%;
  602. }
  603. &.delivery {
  604. font-size: 22px;
  605. width: 30px;
  606. height: 30px;
  607. border-radius: 4px;
  608. transform: translateY(-50%) rotate(45deg);
  609. &:hover {
  610. transform: translateY(-50%) rotate(45deg) scale(1.3);
  611. }
  612. }
  613. &.blink {
  614. animation: blink 1s infinite;
  615. }
  616. &.phase-deadline {
  617. font-size: 14px; // 调整字体大小使其在小圆圈中居中
  618. font-weight: bold;
  619. width: 28px;
  620. height: 28px;
  621. border-radius: 50%;
  622. // background 会由 [style.background] 动态设置
  623. color: #ffffff;
  624. }
  625. &:hover {
  626. transform: translateY(-50%) scale(1.4);
  627. z-index: 20;
  628. box-shadow: 0 4px 15px rgba(0, 0, 0, 0.4);
  629. }
  630. }
  631. // 动画
  632. @keyframes pulse {
  633. 0%, 100% {
  634. opacity: 1;
  635. }
  636. 50% {
  637. opacity: 0.7;
  638. }
  639. }
  640. @keyframes blink {
  641. 0%, 100% {
  642. opacity: 1;
  643. }
  644. 50% {
  645. opacity: 0.3;
  646. }
  647. }
  648. // 响应式设计
  649. @media (max-width: 768px) {
  650. .filter-section {
  651. flex-direction: column;
  652. align-items: stretch;
  653. }
  654. .filter-group {
  655. width: 100%;
  656. &.quick-filters {
  657. margin-left: 0;
  658. }
  659. &.view-controls,
  660. &.sort-controls {
  661. border-left: none;
  662. border-top: 1px solid #e5e7eb;
  663. padding-left: 0;
  664. padding-top: 12px;
  665. }
  666. }
  667. .stats-body {
  668. grid-template-columns: 1fr;
  669. }
  670. // 时间轴视图响应式
  671. .timeline-legend {
  672. flex-wrap: wrap;
  673. gap: 12px;
  674. padding: 8px 12px;
  675. }
  676. .legend-item {
  677. gap: 6px;
  678. }
  679. .legend-label {
  680. font-size: 11px;
  681. }
  682. .ruler-header,
  683. .project-label {
  684. width: 100px;
  685. min-width: 100px;
  686. padding: 8px;
  687. }
  688. .project-name-label {
  689. font-size: 11px;
  690. }
  691. .designer-label {
  692. font-size: 10px;
  693. }
  694. .tick-date {
  695. font-size: 11px;
  696. }
  697. .tick-weekday {
  698. font-size: 9px;
  699. }
  700. .timeline-track {
  701. height: 50px;
  702. padding: 14px 0;
  703. }
  704. .project-bar {
  705. height: 22px;
  706. }
  707. .event-marker {
  708. width: 20px;
  709. height: 20px;
  710. font-size: 14px;
  711. &.start {
  712. width: 18px;
  713. height: 18px;
  714. font-size: 12px;
  715. }
  716. &.review {
  717. width: 19px;
  718. height: 19px;
  719. font-size: 13px;
  720. }
  721. &.delivery {
  722. width: 22px;
  723. height: 22px;
  724. font-size: 16px;
  725. }
  726. }
  727. }