Index.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779
  1. <script lang="ts" setup>
  2. import * as echarts from 'echarts'
  3. import {
  4. Checked,
  5. CollectionTag,
  6. DataAnalysis,
  7. DocumentChecked,
  8. Files,
  9. Histogram,
  10. Postcard
  11. } from '@element-plus/icons-vue'
  12. import {
  13. ANIMATION,
  14. CHART_RENDERER,
  15. DESIGN_HEIGHT,
  16. DESIGN_WIDTH,
  17. FONT_FAMILY,
  18. THEME,
  19. createTooltip
  20. } from '@/utils/kb'
  21. import '@vuepic/vue-datepicker/dist/main.css'
  22. defineOptions({ name: 'HomeIndex' })
  23. type SummaryCard = {
  24. title: string
  25. value: string | number
  26. note: string
  27. accent: string
  28. glow: string
  29. icon: any
  30. }
  31. type ProgressItem = {
  32. label: string
  33. value: number
  34. color: string
  35. }
  36. const wrapperRef = ref<HTMLDivElement>()
  37. const scale = ref(1)
  38. const supportsZoom = ref(false)
  39. let resizeObserver: ResizeObserver | null = null
  40. let resizeRaf = 0
  41. const summaryCards: SummaryCard[] = [
  42. {
  43. title: '专家数量',
  44. value: 150,
  45. note: '较上月 +1.0%',
  46. accent: '#4f8dff',
  47. glow: 'rgba(79,141,255,.22)',
  48. icon: Postcard
  49. },
  50. {
  51. title: '科研项目数量',
  52. value: 10,
  53. note: '较上月 +0.0%',
  54. accent: '#43c7ca',
  55. glow: 'rgba(67,199,202,.22)',
  56. icon: DocumentChecked
  57. },
  58. {
  59. title: '核心技术',
  60. value: 8,
  61. note: '较上月 +0.0%',
  62. accent: '#ffb14a',
  63. glow: 'rgba(255,177,74,.22)',
  64. icon: DataAnalysis
  65. },
  66. {
  67. title: '专利',
  68. value: 18,
  69. note: '较上月 +150.0%',
  70. accent: '#4f8dff',
  71. glow: 'rgba(79,141,255,.22)',
  72. icon: Histogram
  73. },
  74. {
  75. title: '论文',
  76. value: 28,
  77. note: '较上月 +17.0%',
  78. accent: '#8d8cff',
  79. glow: 'rgba(141,140,255,.22)',
  80. icon: CollectionTag
  81. },
  82. {
  83. title: '荣誉',
  84. value: 38,
  85. note: '较上月 +100.0%',
  86. accent: '#ff93dd',
  87. glow: 'rgba(255,147,221,.22)',
  88. icon: Checked
  89. },
  90. {
  91. title: '资料库',
  92. value: 3800,
  93. note: '较上月 +100.0%',
  94. accent: '#4bc7ff',
  95. glow: 'rgba(75,199,255,.22)',
  96. icon: Files
  97. }
  98. ]
  99. const progressItems: ProgressItem[] = [
  100. { label: '审批中', value: 7, color: '#2e9f8c' },
  101. { label: '生效中', value: 18, color: '#208d78' },
  102. { label: '已验收', value: 12, color: '#2b6aa8' },
  103. { label: '已终止', value: 2, color: '#9da8a1' },
  104. { label: '延期', value: 5, color: '#cf4a4a' }
  105. ]
  106. const donutData = [
  107. { name: '进行中', value: 54, color: '#f35e5c' },
  108. { name: '结题', value: 28, color: '#8dd46c' },
  109. { name: '终止', value: 5, color: '#5578d4' },
  110. { name: '延期', value: 7, color: '#8f7cff' }
  111. ]
  112. const expertSeries = [
  113. { name: '技术', value: 195 },
  114. { name: '经营', value: 116 },
  115. { name: '商务', value: 86 },
  116. { name: '安全', value: 61 },
  117. { name: '法务', value: 50 },
  118. { name: '研发', value: 34 },
  119. { name: '质量', value: 23 },
  120. { name: '综合', value: 8 },
  121. { name: '其他', value: 6 }
  122. ]
  123. const budgetSeries = {
  124. month: [
  125. { date: '07-31', value: 61 },
  126. { date: '08-01', value: 64 },
  127. { date: '08-02', value: 63 },
  128. { date: '08-03', value: 62 },
  129. { date: '08-04', value: 60 },
  130. { date: '08-05', value: 62 },
  131. { date: '08-06', value: 62 }
  132. ],
  133. quarter: [
  134. { date: '06-30', value: 58 },
  135. { date: '07-15', value: 60 },
  136. { date: '07-31', value: 61 },
  137. { date: '08-10', value: 62 },
  138. { date: '08-20', value: 63 },
  139. { date: '08-30', value: 64 },
  140. { date: '09-10', value: 63 }
  141. ],
  142. year: [
  143. { date: '01', value: 57 },
  144. { date: '03', value: 58 },
  145. { date: '05', value: 59 },
  146. { date: '07', value: 61 },
  147. { date: '09', value: 62 },
  148. { date: '11', value: 63 },
  149. { date: '12', value: 64 }
  150. ]
  151. } as const
  152. const period = ref<keyof typeof budgetSeries>('month')
  153. const dateText = '2026-08-06'
  154. const donutRef = ref<HTMLDivElement | null>(null)
  155. const barRef = ref<HTMLDivElement | null>(null)
  156. const lineRef = ref<HTMLDivElement | null>(null)
  157. let donutChart: echarts.ECharts | null = null
  158. let barChart: echarts.ECharts | null = null
  159. let lineChart: echarts.ECharts | null = null
  160. const targetWrapperStyle = computed(() => ({
  161. width: `${DESIGN_WIDTH * scale.value}px`,
  162. height: `${DESIGN_HEIGHT * scale.value}px`
  163. }))
  164. const targetAreaStyle = computed(() => {
  165. const style = {
  166. width: `${DESIGN_WIDTH}px`,
  167. height: `${DESIGN_HEIGHT}px`,
  168. transformOrigin: '0 0'
  169. } as Record<string, string | number>
  170. if (supportsZoom.value) {
  171. style.zoom = scale.value
  172. } else {
  173. style.transform = `scale(${scale.value})`
  174. }
  175. return style
  176. })
  177. function updateScale() {
  178. cancelAnimationFrame(resizeRaf)
  179. resizeRaf = requestAnimationFrame(() => {
  180. const wrapper = wrapperRef.value
  181. if (!wrapper) return
  182. const { clientWidth, clientHeight } = wrapper
  183. if (!clientWidth || !clientHeight) return
  184. scale.value = Math.min(clientWidth / DESIGN_WIDTH, clientHeight / DESIGN_HEIGHT)
  185. nextTick(() => resizeCharts())
  186. })
  187. }
  188. function createDonutOption(): echarts.EChartsOption {
  189. return {
  190. ...ANIMATION,
  191. tooltip: createTooltip({ trigger: 'item' }),
  192. legend: {
  193. bottom: 0,
  194. icon: 'rect',
  195. itemWidth: 8,
  196. itemHeight: 8,
  197. textStyle: { color: '#5f6f88', fontSize: 11, fontFamily: FONT_FAMILY }
  198. },
  199. series: [
  200. {
  201. type: 'pie',
  202. radius: ['46%', '70%'],
  203. center: ['50%', '46%'],
  204. avoidLabelOverlap: false,
  205. label: {
  206. formatter: '{b|{b}} {c|{d}%}',
  207. rich: {
  208. b: { color: '#3e5575', fontWeight: 700, fontSize: 11 },
  209. c: { color: '#3e5575', fontWeight: 700, fontSize: 11 }
  210. }
  211. },
  212. labelLine: { length: 12, length2: 16 },
  213. itemStyle: { borderRadius: 8, borderColor: 'transparent', borderWidth: 4 },
  214. data: donutData.map((item) => ({
  215. value: item.value,
  216. name: item.name,
  217. itemStyle: { color: item.color }
  218. }))
  219. }
  220. ]
  221. }
  222. }
  223. function createBarOption(): echarts.EChartsOption {
  224. return {
  225. ...ANIMATION,
  226. grid: { left: 34, right: 18, top: 18, bottom: 0, containLabel: true },
  227. tooltip: createTooltip({ trigger: 'axis', axisPointer: { type: 'shadow' } }),
  228. xAxis: {
  229. type: 'value',
  230. max: 200,
  231. splitLine: { lineStyle: { color: THEME.split } },
  232. axisLine: { show: false },
  233. axisTick: { show: false },
  234. axisLabel: { color: '#51637f', fontSize: 11, fontFamily: FONT_FAMILY }
  235. },
  236. yAxis: {
  237. type: 'category',
  238. data: expertSeries.map((item) => item.name),
  239. axisLine: { show: false },
  240. axisTick: { show: false },
  241. axisLabel: { fontSize: 11, fontWeight: 700, fontFamily: FONT_FAMILY }
  242. },
  243. series: [
  244. {
  245. type: 'bar',
  246. data: expertSeries.map((item) => item.value).reverse(),
  247. barWidth: 14,
  248. itemStyle: {
  249. borderRadius: [0, 10, 10, 0],
  250. color: new echarts.graphic.LinearGradient(1, 0, 0, 0, [
  251. { offset: 0, color: '#2f74e8' },
  252. { offset: 1, color: '#7bb2ff' }
  253. ])
  254. },
  255. label: {
  256. show: true,
  257. position: 'right',
  258. color: '#2f74e8',
  259. fontWeight: 700,
  260. fontSize: 11
  261. }
  262. }
  263. ]
  264. }
  265. }
  266. function createLineOption(): echarts.EChartsOption {
  267. const series = budgetSeries[period.value]
  268. return {
  269. ...ANIMATION,
  270. grid: { left: 22, right: 18, top: 22, bottom: 0, containLabel: true },
  271. tooltip: createTooltip({ trigger: 'axis' }),
  272. xAxis: {
  273. type: 'category',
  274. boundaryGap: false,
  275. data: series.map((item) => item.date),
  276. axisLine: { lineStyle: { color: 'rgba(106,144,221,0.45)' } },
  277. axisTick: { show: false },
  278. axisLabel: { color: '#5b6f8f', fontSize: 11, fontFamily: FONT_FAMILY }
  279. },
  280. yAxis: {
  281. type: 'value',
  282. min: 0,
  283. max: 100,
  284. splitLine: { lineStyle: { color: 'rgba(104,139,205,0.22)', type: 'dashed' } },
  285. axisLabel: { color: '#8a9bb5', fontSize: 11, fontFamily: FONT_FAMILY }
  286. },
  287. series: [
  288. {
  289. type: 'line',
  290. smooth: true,
  291. symbol: 'circle',
  292. symbolSize: 7,
  293. lineStyle: { color: '#2e9e5f', width: 3 },
  294. itemStyle: { color: '#2e9e5f' },
  295. areaStyle: { color: 'rgba(46,158,95,0.14)' },
  296. data: series.map((item) => item.value),
  297. label: {
  298. show: true,
  299. position: 'top',
  300. color: '#2e9e5f',
  301. fontSize: 10,
  302. formatter: '{c}.00%'
  303. }
  304. }
  305. ]
  306. }
  307. }
  308. function initCharts() {
  309. if (donutRef.value) {
  310. donutChart?.dispose()
  311. donutChart = echarts.init(donutRef.value, undefined, { renderer: CHART_RENDERER })
  312. donutChart.setOption(createDonutOption(), true)
  313. }
  314. if (barRef.value) {
  315. barChart?.dispose()
  316. barChart = echarts.init(barRef.value, undefined, { renderer: CHART_RENDERER })
  317. barChart.setOption(createBarOption(), true)
  318. }
  319. if (lineRef.value) {
  320. lineChart?.dispose()
  321. lineChart = echarts.init(lineRef.value, undefined, { renderer: CHART_RENDERER })
  322. lineChart.setOption(createLineOption(), true)
  323. }
  324. }
  325. function resizeCharts() {
  326. donutChart?.resize({ animation: { duration: 180 } })
  327. barChart?.resize({ animation: { duration: 180 } })
  328. lineChart?.resize({ animation: { duration: 180 } })
  329. }
  330. watch(period, () => {
  331. nextTick(() => {
  332. lineChart?.setOption(createLineOption(), true)
  333. })
  334. })
  335. onMounted(() => {
  336. initCharts()
  337. updateScale()
  338. resizeObserver = new ResizeObserver(updateScale)
  339. if (wrapperRef.value) resizeObserver.observe(wrapperRef.value)
  340. window.addEventListener('resize', updateScale)
  341. })
  342. onBeforeUnmount(() => {
  343. resizeObserver?.disconnect()
  344. window.removeEventListener('resize', updateScale)
  345. donutChart?.dispose()
  346. barChart?.dispose()
  347. lineChart?.dispose()
  348. })
  349. </script>
  350. <template>
  351. <div ref="wrapperRef" class="bg absolute top-0 left-0 size-full z-10">
  352. <div class="mx-a overflow-hidden" :style="targetWrapperStyle">
  353. <div id="qhse-kanban" class="bg qhse-board" :style="targetAreaStyle">
  354. <header class="header" style="font-family: YouSheBiaoTiHei, sans-serif !important"
  355. >技术研发</header
  356. >
  357. <div class="summary-toolbar">
  358. <div class="summary-toolbar__tabs"> </div>
  359. <div class="summary-toolbar__date">
  360. <span class="summary-toolbar__date-label">日期:</span>
  361. <strong class="summary-toolbar__date-value">{{ dateText }}</strong>
  362. <button
  363. type="button"
  364. class="summary-toolbar__tab summary-toolbar__tab--mini is-active"
  365. @click="period = 'month'"
  366. >月度</button
  367. >
  368. <button
  369. type="button"
  370. class="summary-toolbar__tab summary-toolbar__tab--mini"
  371. @click="period = 'quarter'"
  372. >季度</button
  373. >
  374. <button
  375. type="button"
  376. class="summary-toolbar__tab summary-toolbar__tab--mini"
  377. @click="period = 'year'"
  378. >年度</button
  379. >
  380. <button
  381. type="button"
  382. class="summary-toolbar__tab summary-toolbar__tab--mini"
  383. @click="period = 'month'"
  384. >初始化</button
  385. >
  386. </div>
  387. </div>
  388. <div class="board-body">
  389. <section class="panel summary-panel kb-stage-card kb-stage-card--1">
  390. <div class="panel-title">
  391. <span class="icon-decorator"><span></span><span></span></span>
  392. 技术研发概况
  393. </div>
  394. <div class="summary-grid summary-grid--tight">
  395. <article
  396. v-for="card in summaryCards"
  397. :key="card.title"
  398. class="summary-card summary-tile"
  399. :style="
  400. {
  401. '--card-accent': card.accent,
  402. '--card-glow': card.glow
  403. } as any
  404. "
  405. >
  406. <span class="summary-card__shine"></span>
  407. <span class="summary-card__corner"></span>
  408. <div class="summary-card__header">
  409. <div class="summary-card__icon">
  410. <el-icon class="summary-card__icon-glyph">
  411. <component :is="card.icon" />
  412. </el-icon>
  413. </div>
  414. <div class="summary-card__label">{{ card.title }}</div>
  415. </div>
  416. <div class="summary-card__content">
  417. <div class="summary-tile__meta">
  418. <span class="summary-tile__value">月度累计 {{ card.value }}</span>
  419. <span class="summary-tile__note" :style="{ color: card.accent }">{{
  420. card.note
  421. }}</span>
  422. </div>
  423. </div>
  424. </article>
  425. </div>
  426. </section>
  427. <div class="board-main board-main--static">
  428. <section class="panel board-panel kb-stage-card kb-stage-card--3">
  429. <div class="panel-title panel-title--center">
  430. <span class="icon-decorator"><span></span><span></span></span>
  431. 项目进度统计
  432. </div>
  433. <div
  434. style="height: 308px"
  435. ref="donutRef"
  436. class="chart-panel chart-panel--echart chart-panel--compact"
  437. ></div>
  438. </section>
  439. <section class="panel board-panel kb-stage-card kb-stage-card--4">
  440. <div class="panel-title panel-title--center">
  441. <span class="icon-decorator"><span></span><span></span></span>
  442. 专家序列统计
  443. </div>
  444. <div
  445. ref="barRef"
  446. style="height: 308px"
  447. class="chart-panel chart-panel--echart chart-panel--compact"
  448. ></div>
  449. </section>
  450. <section class="panel board-panel kb-stage-card kb-stage-card--4">
  451. <div class="panel-title panel-title--center panel-title--with-actions">
  452. <span class="icon-decorator"><span></span><span></span></span>
  453. <span class="panel-title__text">预算费用</span>
  454. <div class="qhse-metric-tabs qhse-metric-tabs--compact">
  455. <button
  456. type="button"
  457. class="qhse-metric-tab"
  458. :class="{ 'is-active': period === 'month' }"
  459. @click="period = 'month'"
  460. >近一周</button
  461. >
  462. <button
  463. type="button"
  464. class="qhse-metric-tab"
  465. :class="{ 'is-active': period === 'quarter' }"
  466. @click="period = 'quarter'"
  467. >近六个月</button
  468. >
  469. </div>
  470. </div>
  471. <div
  472. ref="lineRef"
  473. style="height: 308px"
  474. class="chart-panel chart-panel--echart chart-panel--compact"
  475. ></div>
  476. </section>
  477. </div>
  478. <section class="panel progress-panel kb-stage-card kb-stage-card--5">
  479. <div class="panel-title">
  480. <span class="icon-decorator"><span></span><span></span></span>
  481. 项目组合进展
  482. </div>
  483. <div class="progress-list">
  484. <div v-for="item in progressItems" :key="item.label" class="progress-item">
  485. <span class="progress-item__label">{{ item.label }}</span>
  486. <div class="progress-item__track">
  487. <div
  488. class="progress-item__bar"
  489. :style="{ width: `${item.value * 5}%`, background: item.color }"
  490. ></div>
  491. </div>
  492. <span class="progress-item__value">{{ item.value }}</span>
  493. </div>
  494. </div>
  495. </section>
  496. </div>
  497. </div>
  498. </div>
  499. </div>
  500. </template>
  501. <style lang="scss" scoped>
  502. @import url('@/styles/qhse_kb.scss');
  503. .home-board {
  504. overflow: hidden;
  505. }
  506. .summary-toolbar {
  507. display: flex;
  508. align-items: center;
  509. justify-content: space-between;
  510. padding: 8px 18px 4px;
  511. }
  512. .summary-toolbar__tabs,
  513. .summary-toolbar__date {
  514. display: flex;
  515. align-items: center;
  516. gap: 10px;
  517. }
  518. .summary-toolbar__tab {
  519. min-width: 108px;
  520. height: 34px;
  521. padding: 0 16px;
  522. font-family: YouSheBiaoTiHei, sans-serif;
  523. font-size: 14px;
  524. color: #2b76e9;
  525. background: linear-gradient(180deg, rgb(255 255 255 / 92%) 0%, rgb(228 239 255 / 90%) 100%);
  526. border: 1px solid rgb(126 174 244 / 40%);
  527. border-radius: 10px;
  528. box-shadow:
  529. inset 0 1px 0 rgb(255 255 255 / 96%),
  530. 0 6px 12px rgb(63 103 171 / 8%);
  531. }
  532. .summary-toolbar__tab--mini {
  533. min-width: 68px;
  534. height: 30px;
  535. padding: 0 10px;
  536. font-size: 12px;
  537. }
  538. .summary-toolbar__tab.is-active {
  539. color: #fff;
  540. background: linear-gradient(180deg, #59a6ff 0%, #2d78ea 100%);
  541. }
  542. .summary-toolbar__date-label {
  543. font-size: 14px;
  544. font-weight: 700;
  545. color: #5c7393;
  546. }
  547. .summary-toolbar__date-value {
  548. font-size: 13px;
  549. font-weight: 700;
  550. color: #264564;
  551. }
  552. .board-body {
  553. padding: 4px 14px 12px;
  554. }
  555. .summary-panel {
  556. height: auto;
  557. }
  558. .summary-grid--tight {
  559. display: grid;
  560. grid-template-columns: repeat(7, minmax(0, 1fr));
  561. gap: 10px;
  562. padding: 4px 0 0;
  563. width: 100%;
  564. padding-bottom: 10px;
  565. }
  566. .summary-card {
  567. min-height: 130px;
  568. padding: 16px 14px 12px 18px;
  569. border-radius: 12px;
  570. display: flex;
  571. flex-direction: column;
  572. justify-content: space-between;
  573. gap: 10px;
  574. }
  575. .summary-card__header {
  576. display: flex;
  577. align-items: center;
  578. gap: 8px;
  579. }
  580. .summary-card__icon {
  581. display: flex;
  582. align-items: center;
  583. justify-content: center;
  584. width: 34px;
  585. height: 34px;
  586. border-radius: 10px;
  587. background: linear-gradient(135deg, rgb(255 255 255 / 74%) 0%, rgb(233 242 255 / 92%) 100%);
  588. box-shadow: inset 0 1px 0 rgb(255 255 255 / 90%);
  589. }
  590. .summary-card__icon-glyph {
  591. font-size: 16px;
  592. color: #2f74de;
  593. }
  594. .summary-card__content {
  595. display: flex;
  596. flex: 1;
  597. flex-direction: column;
  598. justify-content: space-between;
  599. gap: 8px;
  600. }
  601. .summary-card__title,
  602. .summary-card__label {
  603. font-size: 14px;
  604. font-weight: 600;
  605. line-height: 1.4;
  606. color: #26425f;
  607. }
  608. .summary-tile__meta {
  609. display: flex;
  610. flex-direction: column;
  611. align-items: flex-start;
  612. gap: 4px;
  613. margin-top: auto;
  614. }
  615. .summary-tile__value {
  616. font-size: 18px;
  617. font-weight: 700;
  618. line-height: 1.2;
  619. letter-spacing: 0.02em;
  620. color: #0f2f54;
  621. }
  622. .summary-tile__note {
  623. font-size: 12px;
  624. font-weight: 600;
  625. line-height: 1.45;
  626. color: #6b7b92;
  627. }
  628. .board-main--static {
  629. display: grid;
  630. grid-template-columns: repeat(3, minmax(0, 1fr));
  631. gap: 12px;
  632. margin-top: 10px;
  633. height: 408px;
  634. }
  635. .board-panel {
  636. min-height: 272px;
  637. }
  638. .chart-panel--compact {
  639. height: 208px;
  640. }
  641. .panel-title--with-actions {
  642. display: flex;
  643. align-items: center;
  644. justify-content: space-between;
  645. gap: 10px;
  646. }
  647. .panel-title__text {
  648. flex: 1;
  649. text-align: left;
  650. }
  651. .qhse-metric-tabs--compact {
  652. display: inline-flex;
  653. align-items: center;
  654. gap: 6px;
  655. padding: 4px;
  656. border: 1px solid rgb(121 169 242 / 40%);
  657. border-radius: 999px;
  658. background: linear-gradient(180deg, rgb(246 250 255 / 96%) 0%, rgb(232 241 255 / 92%) 100%);
  659. box-shadow: inset 0 1px 0 rgb(255 255 255 / 90%);
  660. }
  661. .qhse-metric-tabs--compact .qhse-metric-tab {
  662. min-height: 24px;
  663. padding: 0 10px;
  664. font-size: 12px;
  665. font-weight: 600;
  666. color: #5c7393;
  667. background: transparent;
  668. border: 0;
  669. border-radius: 999px;
  670. cursor: pointer;
  671. transition:
  672. background-color 0.2s ease,
  673. color 0.2s ease,
  674. box-shadow 0.2s ease;
  675. }
  676. .qhse-metric-tabs--compact .qhse-metric-tab.is-active {
  677. color: #fff;
  678. background: linear-gradient(135deg, #5aa8ff 0%, #2d78ea 100%);
  679. box-shadow: 0 4px 10px rgb(45 120 234 / 20%);
  680. }
  681. .progress-panel {
  682. margin-top: 10px;
  683. padding-bottom: 10px;
  684. height: 352px;
  685. }
  686. .progress-list {
  687. padding: 12px 12px 4px;
  688. }
  689. .progress-item {
  690. display: grid;
  691. grid-template-columns: 70px 1fr 28px;
  692. align-items: center;
  693. gap: 10px;
  694. margin-bottom: 10px;
  695. font-size: 12px;
  696. font-weight: 700;
  697. }
  698. .progress-item__track {
  699. height: 18px;
  700. border-radius: 999px;
  701. background: #e4ece7;
  702. overflow: hidden;
  703. }
  704. .progress-item__bar {
  705. height: 100%;
  706. border-radius: inherit;
  707. }
  708. .progress-item__value {
  709. text-align: right;
  710. }
  711. </style>