Merge pull request '数据看板页面实现' (#4) from lm/feat/dashboard into master
Reviewed-on: #4
@ -115,6 +115,7 @@
|
|||||||
"lint-staged": "^15.2.2",
|
"lint-staged": "^15.2.2",
|
||||||
"postcss": "^8.4.35",
|
"postcss": "^8.4.35",
|
||||||
"postcss-html": "^1.6.0",
|
"postcss-html": "^1.6.0",
|
||||||
|
"postcss-pxtorem": "^6.1.0",
|
||||||
"postcss-scss": "^4.0.9",
|
"postcss-scss": "^4.0.9",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"prettier-eslint": "^16.3.0",
|
"prettier-eslint": "^16.3.0",
|
||||||
|
|||||||
12
pnpm-lock.yaml
generated
@ -270,6 +270,9 @@ importers:
|
|||||||
postcss-html:
|
postcss-html:
|
||||||
specifier: ^1.6.0
|
specifier: ^1.6.0
|
||||||
version: 1.7.0
|
version: 1.7.0
|
||||||
|
postcss-pxtorem:
|
||||||
|
specifier: ^6.1.0
|
||||||
|
version: 6.1.0(postcss@8.4.49)
|
||||||
postcss-scss:
|
postcss-scss:
|
||||||
specifier: ^4.0.9
|
specifier: ^4.0.9
|
||||||
version: 4.0.9(postcss@8.4.49)
|
version: 4.0.9(postcss@8.4.49)
|
||||||
@ -4249,6 +4252,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-MfcMpSUIaR/nNgeVS8AyvyDugXlADjN9AcV7e5rDfrF1wduIAGSkL4q2+wgrZgA3sHVAHLDO9FuauHhZYW2nBw==}
|
resolution: {integrity: sha512-MfcMpSUIaR/nNgeVS8AyvyDugXlADjN9AcV7e5rDfrF1wduIAGSkL4q2+wgrZgA3sHVAHLDO9FuauHhZYW2nBw==}
|
||||||
engines: {node: ^12 || >=14}
|
engines: {node: ^12 || >=14}
|
||||||
|
|
||||||
|
postcss-pxtorem@6.1.0:
|
||||||
|
resolution: {integrity: sha512-ROODSNci9ADal3zUcPHOF/K83TiCgNSPXQFSbwyPHNV8ioHIE4SaC+FPOufd8jsr5jV2uIz29v1Uqy1c4ov42g==}
|
||||||
|
peerDependencies:
|
||||||
|
postcss: ^8.0.0
|
||||||
|
|
||||||
postcss-resolve-nested-selector@0.1.6:
|
postcss-resolve-nested-selector@0.1.6:
|
||||||
resolution: {integrity: sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==}
|
resolution: {integrity: sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==}
|
||||||
|
|
||||||
@ -9569,6 +9577,10 @@ snapshots:
|
|||||||
postcss: 8.4.49
|
postcss: 8.4.49
|
||||||
postcss-safe-parser: 6.0.0(postcss@8.4.49)
|
postcss-safe-parser: 6.0.0(postcss@8.4.49)
|
||||||
|
|
||||||
|
postcss-pxtorem@6.1.0(postcss@8.4.49):
|
||||||
|
dependencies:
|
||||||
|
postcss: 8.4.49
|
||||||
|
|
||||||
postcss-resolve-nested-selector@0.1.6: {}
|
postcss-resolve-nested-selector@0.1.6: {}
|
||||||
|
|
||||||
postcss-safe-parser@6.0.0(postcss@8.4.49):
|
postcss-safe-parser@6.0.0(postcss@8.4.49):
|
||||||
|
|||||||
@ -1,5 +1,15 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: {
|
plugins: {
|
||||||
autoprefixer: {}
|
autoprefixer: {},
|
||||||
|
'postcss-pxtorem': {
|
||||||
|
rootValue: 16, // 设计稿基准值,1rem = 16px
|
||||||
|
unitPrecision: 5, // rem 的小数位数
|
||||||
|
propList: ['*'], // 需要转换的属性,* 表示所有属性
|
||||||
|
selectorBlackList: [], // 忽略的选择器,可以使用正则表达式
|
||||||
|
replace: true, // 是否替换而不是添加
|
||||||
|
mediaQuery: false, // 是否在媒体查询中转换 px
|
||||||
|
minPixelValue: 0, // 设置要替换的最小像素值,0 表示所有值都转换
|
||||||
|
exclude: /node_modules/i // 排除 node_modules 目录
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
src/assets/imgs/dashboard/dashboard-back.jpg
Normal file
|
After Width: | Height: | Size: 2.9 MiB |
1
src/assets/imgs/dashboard/icon-arrow.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1768528542812" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8272" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M838.4 473.6 505.6 134.4C499.2 128 480 134.4 480 147.2l0 256L217.6 134.4C211.2 128 192 134.4 192 147.2l0 729.6c0 12.8 12.8 19.2 25.6 12.8l262.4-262.4 0 256c0 12.8 12.8 19.2 25.6 12.8l339.2-339.2C864 531.2 864 492.8 838.4 473.6z" fill="#70B5C3" p-id="8273"></path></svg>
|
||||||
|
After Width: | Height: | Size: 602 B |
1
src/assets/imgs/dashboard/icon-card.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1768544147360" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12017" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M11.54 290.28999998v461.35h690.625l0.596-461.35c0-11.547-9.301-20.907-20.748-20.907h-649.77c-11.443 0-20.705 9.361-20.704 20.907z" p-id="12018" fill="#E08F53"></path><path d="M391.146 903.10199998h304.473c-0.018-0.844-0.118-1.686-0.118-2.567 0-6.217 3.718-32.475 5.727-39.252 7.63-25.817 42-98.624 130.985-98.624 90.645 0 123.372 72.806 131.003 98.624 1.986 6.778 5.707 33.036 5.707 39.252 0 0.881-0.101 1.723-0.14 2.567h22.697c11.446 0 20.726-9.342 20.726-20.889v-107.402c0-5.312-2.083-10.146-5.302-13.834v-156.209c0-120.832-79.019-183.293-82.396-185.899-3.634-2.804-8.048-4.307-12.598-4.307h-166.716c-6.141 0-11.585 2.747-15.381 7.036v343.996c0 8.434-5.454 13.91-13.812 13.91h-396.032c66.527 25.733 71.141 93.803 71.181 123.598zM765.92 578.73199998v-122.356h138.316c13.791 12.769 51.567 53.721 59.599 122.356h-197.915z" p-id="12019" fill="#E08F53"></path><path d="M53.653 774.19099998c0 0.201-0.059 108.028-0.059 108.028 0 11.549 9.258 20.889 20.704 20.889h43.722c-8.245-64.458 49.987-123.258 82.07-128.915-26.156-0.001-146.437-0.001-146.437-0.001z" p-id="12020" fill="#E08F53"></path><path d="M832.215 790.54399998c60.763 0 110.021 49.26 110.021 110.022 0 60.761-49.259 110.02-110.021 110.02s-110.02-49.26-110.02-110.02c0-60.763 49.258-110.022 110.02-110.022zM781.382 900.56699998c0 28.073 22.759 50.83 50.833 50.83s50.831-22.758 50.831-50.83c0-28.075-22.758-50.832-50.831-50.832-28.075 0-50.833 22.757-50.833 50.832z" p-id="12021" fill="#E08F53"></path><path d="M255.55 790.54399998c60.763 0 110.02 49.26 110.02 110.022 0 60.761-49.258 110.02-110.02 110.02s-110.021-49.26-110.021-110.02c0-60.763 49.259-110.022 110.021-110.022zM204.716 900.56699998c0 28.073 22.758 50.83 50.832 50.83s50.832-22.758 50.832-50.83c0-28.075-22.758-50.832-50.832-50.832s-50.832 22.757-50.832 50.832z" p-id="12022" fill="#E08F53"></path></svg>
|
||||||
|
After Width: | Height: | Size: 2.1 KiB |
1
src/assets/imgs/dashboard/icon-location.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1768544597038" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="25765" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M723.2 509.866667a298.496 298.496 0 0 0 79.274667-280.832l106.453333-45.653334a21.333333 21.333333 0 0 1 29.738667 19.626667V810.666667l-298.666667 128-256-128-268.928 115.242666a21.333333 21.333333 0 0 1-29.738667-19.626666V298.666667l133.504-57.216a298.368 298.368 0 0 0 81.962667 268.373333L512 721.066667l211.2-211.2z m-60.330667-60.373334L512 600.362667l-150.869333-150.869334a213.333333 213.333333 0 1 1 301.738666 0z" fill="#7CC897" p-id="25766"></path></svg>
|
||||||
|
After Width: | Height: | Size: 800 B |
1
src/assets/imgs/dashboard/icon-person.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1768544331333" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="14246" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M690.338 661.474c-41.498-18.374-103.937-65.868-195.163-82.197 23.332-24.985 40.564-63.996 58.672-110.231 10.528-26.786 8.425-49.62 8.425-82.185 0-24.002 4.507-62.528-1.502-83.71-20.073-71.577-71.002-91.29-130.507-91.29-59.583 0-110.473 19.828-130.557 91.452-5.848 21.24-1.355 59.627-1.355 83.548 0 32.623-1.764 55.56 8.787 82.359 18.247 46.477 35.683 85.444 58.865 110.347-90.428 16.525-148.928 63.73-190.16 82.047-85.32 38.11-83.219 79.828-83.219 79.828v70.698l685.644-0.128v-70.57c0-0.001-2.262-41.88-87.93-79.968zM659.56 227.805h271.187v59.352H659.56v-59.352zM659.56 357.765h165.782v60.375H659.56v-60.375z" fill="#8FEAFC" p-id="14247"></path><path d="M659.56 487.725h237.417V548.1H659.56v-60.375z" fill="#8FEAFC" p-id="14248"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
1
src/assets/imgs/dashboard/icon-person2.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1768544265577" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13983" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M328.4 292.6a182.9 182.9 0 1 0 365.8 0 182.9 182.9 0 1 0-365.8 0zM757.6 638.6L658.4 691c-15.6 8.3-15.6 30.7 0 38.9l99.2 52.4c6.4 3.4 14.1 3.4 20.6 0l99.2-52.4c15.6-8.3 15.6-30.7 0-38.9l-99.2-52.4c-6.5-3.4-14.2-3.4-20.6 0z" fill="#9D67D3" p-id="13984"></path><path d="M881.7 752.3c-1.1 0-3.3 1.1-5.1 2.2l-90 54.1c-11.7 6.9-26 6.9-37.7 0l-90.3-54.1c-1-0.7-3.5-2.2-5.1-2.2-4 0-7.3 3.3-7.3 7.3v18.3c0 4 2.6 7.3 5.9 8.8l96.9 58.5c11.7 6.9 26.3 6.9 37.7 0l96.9-58.5c3.3-1.5 5.5-4.8 5.5-8.8v-18.3c0-4-3.3-7.3-7.4-7.3z" fill="#9D67D3" p-id="13985"></path><path d="M881.7 816c-1.1 0-3.3 1.1-5.1 2.2l-90 54.1c-11.7 6.9-26 6.9-37.7 0l-90.3-54.1c-1-0.7-3.5-2.2-5.1-2.2-4 0-7.3 3.3-7.3 7.3v18.3c0 4 2.6 7.3 5.9 8.8l96.9 58.5c11.7 6.9 26.3 6.9 37.7 0l96.9-58.5c3.3-1.5 5.5-4.8 5.5-8.8v-18.3c0-4-3.3-7.3-7.4-7.3zM768 599.4c7.3 0 14.3 1.5 20.8 4 0.4 0 0.7 0.4 1.1 0.4 2.2 1.1 4.8 1.5 7.3 1.5 9.9 0 18.3-8 18.3-18.3 0-4-1.1-7.3-3.3-10.2-0.7-1.1-1.5-1.8-2.2-2.6-39.1-43.9-87.4-79.4-142.6-102.8-11.7-5.1-24.9-2.6-35.8 4.4-34.4 23-75.7 36.2-120.3 36.2-44.6 0-86.3-13.5-121.1-36.6-10.6-6.9-23.8-9.5-35.8-4.8-132.8 56-229.3 180.7-244.3 329.9-2.2 21.6 15 40.6 36.9 40.6h426.4c20.1 0 36.2-16.1 36.2-36.2 0.4-36.6 0.4-88.5 0.4-94.4 0-21.9 12.1-41.7 31.5-51.9l99.1-52.3c8.4-4.3 17.9-6.9 27.4-6.9z" fill="#9D67D3" p-id="13986"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
1
src/assets/imgs/dashboard/icon-require.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1768526192243" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4670" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M313.991837 914.285714c-20.37551 0-40.228571-6.269388-56.946939-18.808163-30.302041-21.942857-44.930612-58.514286-38.661225-95.085714l24.032654-141.061225c3.134694-18.285714-3.134694-36.571429-16.195919-49.110204L123.297959 509.910204c-26.644898-26.122449-36.04898-64.261224-24.555102-99.787755 11.493878-35.526531 41.795918-61.126531 78.889796-66.35102l141.583674-20.375511c18.285714-2.612245 33.959184-14.106122 41.795918-30.30204l63.216326-128.522449C440.946939 130.612245 474.383673 109.714286 512 109.714286s71.053061 20.897959 87.24898 54.334694L662.987755 292.571429c8.359184 16.195918 24.032653 27.689796 41.795918 30.30204l141.583674 20.375511c37.093878 5.22449 67.395918 30.82449 78.889796 66.35102 11.493878 35.526531 2.089796 73.665306-24.555102 99.787755l-102.4 99.787755c-13.061224 12.538776-19.330612 31.346939-16.195919 49.110204l24.032654 141.061225c6.269388 37.093878-8.359184 73.142857-38.661225 95.085714-30.302041 21.942857-69.485714 24.555102-102.4 7.314286L538.122449 836.440816c-16.195918-8.359184-35.526531-8.359184-51.722449 0l-126.955102 66.87347c-14.628571 7.314286-30.302041 10.971429-45.453061 10.971428z m162.481632-96.653061z" fill="#DA5159" p-id="4671"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
1
src/assets/imgs/dashboard/person.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1768460125201" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4673" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512.0181274414062 140.7236328125c-125.93957519531247 0-228.48156738281247 102.50326538085938-228.48156738281247 228.48074340820312 0 125.90167236328126 102.46701049804688 228.44448852539062 228.48156738281247 228.44448852539062 125.97665405273438 0 228.517822265625-102.54199218749999 228.517822265625-228.44448852539062 0-125.97747802734375-102.54116821289061-228.48074340820312-228.517822265625-228.48074340820312z" fill="#ffffff" p-id="4674"></path><path d="M688.11962890625 592.4355773925781c-48.68206787109375 38.48208618164063-109.30682373046875 62.32461547851563-176.10150146484375 62.32461547851563-66.79550170898438 0-127.38153076171872-23.915863037109375-176.17565917968753-62.32461547851563C243.4666748046875 651.2484130859374 179.14804077148432 754.5303344726562 170.31338500976562 874.1483764648438c32.343475341796875 4.396728515625001 89.89892578124999 9.127990722656248 172.03436279296875 9.127990722656248h339.1916198730469c82.17333984375 0 139.7650451660156-4.731262207031249 172.14724731445312-9.127990722656248-8.762145996093748-119.69384765625-73.07995605468749-222.89996337890622-165.5669860839844-281.71279907226557z" fill="#ffffff" p-id="4675"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
1
src/assets/imgs/dashboard/pie.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1768465043767" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9825" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M515 161.4c201.7 0 365.3 163.6 365.3 365.3S716.7 892 515 892 149.7 728.4 149.7 526.7 313.3 161.4 515 161.4z" fill="#7CC897" opacity=".7" p-id="9826"></path><path d="M514 526.7h448.2c0-246.9-200.2-447.1-447.1-447.1" fill="#7CC897" opacity=".7" p-id="9827"></path></svg>
|
||||||
|
After Width: | Height: | Size: 601 B |
@ -25,6 +25,10 @@ import '@/styles/index.scss'
|
|||||||
// 引入动画
|
// 引入动画
|
||||||
import '@/plugins/animate.css'
|
import '@/plugins/animate.css'
|
||||||
|
|
||||||
|
// 初始化 rem 移动端适配
|
||||||
|
import { initRem } from '@/utils/rem'
|
||||||
|
initRem()
|
||||||
|
|
||||||
// 路由
|
// 路由
|
||||||
import router, { setupRouter } from '@/router'
|
import router, { setupRouter } from '@/router'
|
||||||
|
|
||||||
|
|||||||
@ -53,13 +53,19 @@ const whiteList = [
|
|||||||
'/auth-redirect',
|
'/auth-redirect',
|
||||||
'/bind',
|
'/bind',
|
||||||
'/register',
|
'/register',
|
||||||
'/oauthLogin/gitee'
|
'/oauthLogin/gitee',
|
||||||
|
'/dashboard' // Dashboard 页面
|
||||||
]
|
]
|
||||||
|
|
||||||
// 路由加载前
|
// 路由加载前
|
||||||
router.beforeEach(async (to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
start()
|
start()
|
||||||
loadStart()
|
loadStart()
|
||||||
|
// 如果是主页路径或 dashboard 路径,直接放行(跳过权限验证)
|
||||||
|
if (to.path === '/dashboard') {
|
||||||
|
next()
|
||||||
|
return
|
||||||
|
}
|
||||||
if (getAccessToken()) {
|
if (getAccessToken()) {
|
||||||
if (to.path === '/login') {
|
if (to.path === '/login') {
|
||||||
next({ path: '/' })
|
next({ path: '/' })
|
||||||
|
|||||||
@ -174,6 +174,16 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/dashboard',
|
||||||
|
component: () => import('@/views/Dashboard/Index.vue'),
|
||||||
|
name: 'Dashboard',
|
||||||
|
meta: {
|
||||||
|
hidden: true,
|
||||||
|
title: 'Dashboard',
|
||||||
|
noTagsView: true
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/login',
|
path: '/login',
|
||||||
component: () => import('@/views/Login/Login.vue'),
|
component: () => import('@/views/Login/Login.vue'),
|
||||||
|
|||||||
@ -535,3 +535,6 @@ export const subString = (str: string, start: number, end: number) => {
|
|||||||
}
|
}
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// rem 移动端适配
|
||||||
|
export { initRem, getRem } from './rem'
|
||||||
|
|||||||
65
src/utils/rem.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* rem 移动端适配工具
|
||||||
|
* 根据屏幕宽度动态设置根元素字体大小
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 设计稿基准宽度(根据你的设计稿调整,常见值:375、750)
|
||||||
|
const DESIGN_WIDTH = 375
|
||||||
|
// 基准字体大小(与 postcss.config.js 中的 rootValue 保持一致)
|
||||||
|
const BASE_FONT_SIZE = 16
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置 rem 基准值
|
||||||
|
*/
|
||||||
|
function setRem() {
|
||||||
|
const docEl = document.documentElement
|
||||||
|
const width = docEl.clientWidth || window.innerWidth
|
||||||
|
|
||||||
|
// 计算根字体大小:屏幕宽度 / 设计稿宽度 * 基准字体大小
|
||||||
|
const rem = (width / DESIGN_WIDTH) * BASE_FONT_SIZE
|
||||||
|
|
||||||
|
// 设置根元素字体大小
|
||||||
|
docEl.style.fontSize = `${rem}px`
|
||||||
|
|
||||||
|
// 限制最大和最小字体大小(可选)
|
||||||
|
const maxRem = BASE_FONT_SIZE * 1.5 // 最大 24px
|
||||||
|
const minRem = BASE_FONT_SIZE * 0.8 // 最小 12.8px
|
||||||
|
|
||||||
|
if (rem > maxRem) {
|
||||||
|
docEl.style.fontSize = `${maxRem}px`
|
||||||
|
} else if (rem < minRem) {
|
||||||
|
docEl.style.fontSize = `${minRem}px`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化 rem 适配
|
||||||
|
*/
|
||||||
|
export function initRem() {
|
||||||
|
// 初始设置
|
||||||
|
setRem()
|
||||||
|
|
||||||
|
// 监听窗口大小变化
|
||||||
|
window.addEventListener('resize', setRem)
|
||||||
|
|
||||||
|
// 监听屏幕方向变化(移动端横竖屏切换)
|
||||||
|
window.addEventListener('orientationchange', () => {
|
||||||
|
setTimeout(setRem, 100)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听页面显示(处理浏览器标签页切换)
|
||||||
|
document.addEventListener('visibilitychange', () => {
|
||||||
|
if (!document.hidden) {
|
||||||
|
setRem()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前 rem 基准值
|
||||||
|
*/
|
||||||
|
export function getRem(): number {
|
||||||
|
const docEl = document.documentElement
|
||||||
|
const fontSize = window.getComputedStyle(docEl).fontSize
|
||||||
|
return parseFloat(fontSize)
|
||||||
|
}
|
||||||
485
src/views/Dashboard/Index.vue
Normal file
@ -0,0 +1,485 @@
|
|||||||
|
<template>
|
||||||
|
<div class="dashboard-container">
|
||||||
|
<div class="prison-name">王某某</div>
|
||||||
|
<div class="current-time">{{ currentTime }}</div>
|
||||||
|
<div class="dashboard-content">
|
||||||
|
<div class="dashboard-content-top">
|
||||||
|
<div class="dashboard-content-top-left">
|
||||||
|
<InfoCard :basic-info="basicInfo" :interview-records="interviewRecords" />
|
||||||
|
</div>
|
||||||
|
<div class="dashboard-content-top-center">
|
||||||
|
<div class="gauge-container">
|
||||||
|
<div class="dashboard-content-top-center-data">
|
||||||
|
<!-- 左侧第一个卡片 -->
|
||||||
|
<div class="info-card-item">
|
||||||
|
<div class="card-number">{{ centerLeftData.top.value }}</div>
|
||||||
|
<div class="card-label">{{ centerLeftData.top.label }}</div>
|
||||||
|
</div>
|
||||||
|
<!-- 左侧第二个卡片 -->
|
||||||
|
<div class="card-row">
|
||||||
|
<div class="info-card-item">
|
||||||
|
<div class="card-number">{{ centerLeftData.middle.left.value }}</div>
|
||||||
|
<div class="card-label">{{ centerLeftData.middle.left.label }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-card-item">
|
||||||
|
<div class="card-number">{{ centerLeftData.middle.right.value }}</div>
|
||||||
|
<div class="card-label">{{ centerLeftData.middle.right.label }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dashboard-content-top-center-center">
|
||||||
|
<GaugeChart :height="'200px'" :value="gaugeValue" :name="gaugeName" />
|
||||||
|
</div>
|
||||||
|
<div class="dashboard-content-top-center-data">
|
||||||
|
<!-- 右侧第一个卡片 -->
|
||||||
|
<div class="info-card-item">
|
||||||
|
<div class="card-number">{{ centerRightData.top.value }}</div>
|
||||||
|
<div class="card-label">{{ centerRightData.top.label }}</div>
|
||||||
|
</div>
|
||||||
|
<!-- 右侧第二个卡片 -->
|
||||||
|
<div class="card-row">
|
||||||
|
<div class="info-card-item">
|
||||||
|
<div class="card-number">{{ centerRightData.middle.left.value }}</div>
|
||||||
|
<div class="card-label">{{ centerRightData.middle.left.label }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-card-item">
|
||||||
|
<div class="card-number">{{ centerRightData.middle.right.value }}</div>
|
||||||
|
<div class="card-label">{{ centerRightData.middle.right.label }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="list-container">
|
||||||
|
<div class="list-card-item">
|
||||||
|
<div class="list-card-item-icon icon-location"></div>
|
||||||
|
<div class="list-card-item-value">108</div>
|
||||||
|
</div>
|
||||||
|
<div class="list-card-item">
|
||||||
|
<div class="list-card-item-icon icon-person"></div>
|
||||||
|
<div class="list-card-item-value">108</div>
|
||||||
|
</div>
|
||||||
|
<div class="list-card-item">
|
||||||
|
<div class="list-card-item-icon icon-person2"></div>
|
||||||
|
<div class="list-card-item-value">108</div>
|
||||||
|
</div>
|
||||||
|
<div class="list-card-item">
|
||||||
|
<div class="list-card-item-icon icon-car"></div>
|
||||||
|
<div class="list-card-item-value">108</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dashboard-content-top-right">
|
||||||
|
<ScoreAssessment />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dashboard-content-bottom">
|
||||||
|
<div class="dashboard-content-bottom-left">
|
||||||
|
<ConsumptionRecords />
|
||||||
|
</div>
|
||||||
|
<div class="dashboard-content-bottom-center">
|
||||||
|
<RecentRewardsPunishments />
|
||||||
|
</div>
|
||||||
|
<div class="dashboard-content-bottom-right">
|
||||||
|
<div class="dashboard-content-bottom-right-title">大帐统计</div>
|
||||||
|
<BarChart :height="'200px'" :data="barChartData" :card-data="barCardData" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// @ts-ignore
|
||||||
|
import GaugeChart from './components/GaugeChart.vue'
|
||||||
|
// @ts-ignore
|
||||||
|
import BarChart from './components/BarChart.vue'
|
||||||
|
// @ts-ignore
|
||||||
|
import InfoCard from './components/InfoCard/Index.vue'
|
||||||
|
// @ts-ignore
|
||||||
|
import ScoreAssessment from './components/ScoreAssessment/Index.vue'
|
||||||
|
// @ts-ignore
|
||||||
|
import RecentRewardsPunishments from './components/RecentRewardsPunishments/Index.vue'
|
||||||
|
// @ts-ignore
|
||||||
|
import ConsumptionRecords from './components/ConsumptionRecords/Index.vue'
|
||||||
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
|
|
||||||
|
defineOptions({ name: 'Dashboard' })
|
||||||
|
|
||||||
|
// 仪表盘数据
|
||||||
|
const gaugeValue = ref(92.23)
|
||||||
|
const gaugeName = ref('')
|
||||||
|
|
||||||
|
// 中心左侧数据
|
||||||
|
const centerLeftData = ref({
|
||||||
|
top: {
|
||||||
|
value: '1054',
|
||||||
|
label: 'XXXXXXXX'
|
||||||
|
},
|
||||||
|
middle: {
|
||||||
|
left: {
|
||||||
|
value: '124',
|
||||||
|
label: 'XXXXXXXX'
|
||||||
|
},
|
||||||
|
right: {
|
||||||
|
value: '78.24%',
|
||||||
|
label: 'XXXXXXXX'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bottom: {
|
||||||
|
value: '108位',
|
||||||
|
label: 'XXXXXXXXXX'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 中心右侧数据
|
||||||
|
const centerRightData = ref({
|
||||||
|
top: {
|
||||||
|
value: '13',
|
||||||
|
label: 'XXXXXXXX'
|
||||||
|
},
|
||||||
|
middle: {
|
||||||
|
left: {
|
||||||
|
value: '15',
|
||||||
|
label: 'XXXXXXXX'
|
||||||
|
},
|
||||||
|
right: {
|
||||||
|
value: '34',
|
||||||
|
label: 'XXXXXXXX'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bottomLeft: {
|
||||||
|
value: '58位',
|
||||||
|
label: 'XXXXXXXXX'
|
||||||
|
},
|
||||||
|
bottomRight: {
|
||||||
|
value: '58辆',
|
||||||
|
label: 'XXXXXXXXX'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 柱状图数据
|
||||||
|
const barChartData = ref([
|
||||||
|
{ category: '1月', monthlyStandard: 24, perCapita: 17 },
|
||||||
|
{ category: '2月', monthlyStandard: 18, perCapita: 24 },
|
||||||
|
{ category: '3月', monthlyStandard: 15, perCapita: 30 },
|
||||||
|
{ category: '4月', monthlyStandard: 37, perCapita: 18 },
|
||||||
|
{ category: '5月', monthlyStandard: 29, perCapita: 15 },
|
||||||
|
{ category: '6月', monthlyStandard: 17, perCapita: 24 },
|
||||||
|
{ category: '7月', monthlyStandard: 15, perCapita: 30 },
|
||||||
|
{ category: '8月', monthlyStandard: 15, perCapita: 30 },
|
||||||
|
{ category: '9月', monthlyStandard: 15, perCapita: 30 },
|
||||||
|
{ category: '10月', monthlyStandard: 15, perCapita: 30 },
|
||||||
|
{ category: '11月', monthlyStandard: 15, perCapita: 30 },
|
||||||
|
{ category: '12月', monthlyStandard: 15, perCapita: 30 }
|
||||||
|
])
|
||||||
|
|
||||||
|
// 卡片数据
|
||||||
|
const barCardData = ref({
|
||||||
|
inProgress: 5,
|
||||||
|
toWarehouse: 5,
|
||||||
|
outWarehouse: 5
|
||||||
|
})
|
||||||
|
|
||||||
|
// 基本信息数据
|
||||||
|
const basicInfo = ref({
|
||||||
|
district: '第1监区 十四分监区',
|
||||||
|
prisonNumber: 'ZF20230001',
|
||||||
|
sentenceStart: '2020/07/21',
|
||||||
|
sentenceEnd: '2020/07/21',
|
||||||
|
sentenceDays: 300,
|
||||||
|
age: 78,
|
||||||
|
hometown: '中国/江苏',
|
||||||
|
education: '大学',
|
||||||
|
maritalStatus: '未婚',
|
||||||
|
children: '无子女',
|
||||||
|
birthDate: '2025-02-02',
|
||||||
|
crimeType: '盗窃罪',
|
||||||
|
previousConvictions: '7次',
|
||||||
|
sentence: '有期徒刑1年6月'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 心理访谈记录数据
|
||||||
|
const interviewRecords = ref([
|
||||||
|
{
|
||||||
|
date: '2023-10',
|
||||||
|
content: '情绪稳定,遵规守纪良好'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2023-09',
|
||||||
|
content: '人际关系融洽,态度积极'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2023-08',
|
||||||
|
content: '偶有焦虑情绪,需关注'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2023-07',
|
||||||
|
content: '偶有焦虑情绪,需关注'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
// 当前时间
|
||||||
|
const currentTime = ref('')
|
||||||
|
|
||||||
|
// 格式化时间
|
||||||
|
const formatTime = () => {
|
||||||
|
const now = new Date()
|
||||||
|
const year = now.getFullYear()
|
||||||
|
const month = String(now.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(now.getDate()).padStart(2, '0')
|
||||||
|
const weekdays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
|
||||||
|
const weekday = weekdays[now.getDay()]
|
||||||
|
const hours = String(now.getHours()).padStart(2, '0')
|
||||||
|
const minutes = String(now.getMinutes()).padStart(2, '0')
|
||||||
|
const seconds = String(now.getSeconds()).padStart(2, '0')
|
||||||
|
|
||||||
|
return `${year}年${month}月${day}日 ${weekday} ${hours}:${minutes}:${seconds}`
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeInterval: NodeJS.Timeout | null = null
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 初始化时间
|
||||||
|
currentTime.value = formatTime()
|
||||||
|
|
||||||
|
// 每秒更新一次时间
|
||||||
|
timeInterval = setInterval(() => {
|
||||||
|
currentTime.value = formatTime()
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
|
// 可以在这里加载数据
|
||||||
|
// 例如:从 API 获取数据后更新 barChartData.value
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
// 清理定时器
|
||||||
|
if (timeInterval) {
|
||||||
|
clearInterval(timeInterval)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.dashboard-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-image: url('@/assets/imgs/dashboard/dashboard-back.jpg');
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
padding: 5% 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prison-name {
|
||||||
|
position: fixed;
|
||||||
|
top: 12px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-48%);
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 1000;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-time {
|
||||||
|
position: fixed;
|
||||||
|
top: 12px;
|
||||||
|
right: 32px;
|
||||||
|
color: white;
|
||||||
|
font-size: 12px;
|
||||||
|
z-index: 1000;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-content {
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-content-top {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
.dashboard-content-top-left {
|
||||||
|
width: 23%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.dashboard-content-top-center {
|
||||||
|
width: 54%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
border: 1px solid rgba(56, 102, 141, 0.5);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gauge-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 75%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.list-container {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 24px;
|
||||||
|
height: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-card-item {
|
||||||
|
width: 25%;
|
||||||
|
height: 80%;
|
||||||
|
background: #2d3d5f;
|
||||||
|
border: 1px solid rgba(56, 102, 141, 0.5);
|
||||||
|
display: flex;
|
||||||
|
padding-left: 10px;
|
||||||
|
justify-content: start;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-card-item-icon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
.icon-location {
|
||||||
|
background: url('@/assets/imgs/dashboard/icon-location.svg') no-repeat center center;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
.icon-person {
|
||||||
|
background: url('@/assets/imgs/dashboard/icon-person.svg') no-repeat center center;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
.icon-person2 {
|
||||||
|
background: url('@/assets/imgs/dashboard/icon-person2.svg') no-repeat center center;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
.icon-car {
|
||||||
|
background: url('@/assets/imgs/dashboard/icon-card.svg') no-repeat center center;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
.list-card-item-value {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
.dashboard-content-top-center-data {
|
||||||
|
width: 32%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 16px 0 10px;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-content-top-center-center {
|
||||||
|
width: 36%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 信息卡片通用样式
|
||||||
|
.info-card-item {
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
box-shadow: inset 0 0 15px 0 #2b4183;
|
||||||
|
border: 1px solid #2b4183;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-number {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 1.3;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-label {
|
||||||
|
font-size: 7px;
|
||||||
|
color: rgba(255, 255, 255, 0.65);
|
||||||
|
line-height: 1.3;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-icon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
.dashboard-content-top-right {
|
||||||
|
width: 23%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-content-bottom {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
.dashboard-content-bottom-left {
|
||||||
|
width: 38%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.dashboard-content-bottom-center {
|
||||||
|
width: 24%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.dashboard-content-bottom-right {
|
||||||
|
width: 38%;
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid rgba(56, 102, 141, 0.5);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
.dashboard-content-bottom-right-title {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ffffff;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
250
src/views/Dashboard/components/BarChart.vue
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
<template>
|
||||||
|
<div class="supply-chart-container">
|
||||||
|
<!-- 柱状图 -->
|
||||||
|
<EChart :options="barOption" :height="height" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { EChartsOption } from 'echarts'
|
||||||
|
// @ts-ignore
|
||||||
|
import EChart from '@/components/Echart/src/Echart.vue'
|
||||||
|
import { computed, watch } from 'vue'
|
||||||
|
|
||||||
|
defineOptions({ name: 'BarChart' })
|
||||||
|
|
||||||
|
interface ChartDataItem {
|
||||||
|
category: string
|
||||||
|
monthlyStandard: number
|
||||||
|
perCapita: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CardData {
|
||||||
|
inProgress: number
|
||||||
|
toWarehouse: number
|
||||||
|
outWarehouse: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
width?: number
|
||||||
|
height?: string
|
||||||
|
data?: ChartDataItem[]
|
||||||
|
cardData?: CardData
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
width: 400,
|
||||||
|
height: '300px',
|
||||||
|
data: () => [],
|
||||||
|
cardData: () => ({
|
||||||
|
inProgress: 5,
|
||||||
|
toWarehouse: 5,
|
||||||
|
outWarehouse: 5
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 创建图表配置
|
||||||
|
const createChartOption = (): EChartsOption => {
|
||||||
|
const categories = props.data.map((item) => item.category)
|
||||||
|
const monthlyStandardData = props.data.map((item) => item.monthlyStandard)
|
||||||
|
const perCapitaData = props.data.map((item) => item.perCapita)
|
||||||
|
|
||||||
|
// 创建底色数据(最大值50)
|
||||||
|
const maxValue = 50
|
||||||
|
const monthlyStandardBgData = categories.map((_, index) => maxValue - monthlyStandardData[index])
|
||||||
|
const perCapitaBgData = categories.map((_, index) => maxValue - perCapitaData[index])
|
||||||
|
|
||||||
|
return {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
grid: {
|
||||||
|
left: '10%',
|
||||||
|
right: '15%',
|
||||||
|
top: '20%',
|
||||||
|
bottom: '15%',
|
||||||
|
containLabel: false
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: categories,
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#38668D50'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: '#D8F0FF',
|
||||||
|
fontSize: 10,
|
||||||
|
interval: 0,
|
||||||
|
rotate: 0
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
min: 0,
|
||||||
|
max: 50,
|
||||||
|
interval: 10,
|
||||||
|
axisLine: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: '#D8F0FF',
|
||||||
|
fontSize: 10
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#38668D20',
|
||||||
|
type: 'dashed'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
data: ['支出', '收入'],
|
||||||
|
top: '5%',
|
||||||
|
right: '10%',
|
||||||
|
textStyle: {
|
||||||
|
color: '#6D869A',
|
||||||
|
fontSize: 9
|
||||||
|
},
|
||||||
|
itemWidth: 9,
|
||||||
|
itemHeight: 9,
|
||||||
|
itemGap: 25
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'shadow'
|
||||||
|
},
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||||
|
borderColor: 'rgba(0, 167, 255, 0.5)',
|
||||||
|
textStyle: {
|
||||||
|
color: '#D8F0FF',
|
||||||
|
fontSize: 10
|
||||||
|
},
|
||||||
|
formatter: function (params: any) {
|
||||||
|
let result = params[0].name + '<br/>'
|
||||||
|
// 只显示数据系列,不显示底色系列
|
||||||
|
params.forEach((param: any) => {
|
||||||
|
if (param.seriesName === '支出' || param.seriesName === '收入') {
|
||||||
|
result += param.marker + param.seriesName + ': ' + param.value + '<br/>'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
// 支出数据(渐变)- 先绘制,作为底层
|
||||||
|
{
|
||||||
|
name: '支出',
|
||||||
|
type: 'bar',
|
||||||
|
stack: 'monthly',
|
||||||
|
data: monthlyStandardData,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
x2: 0,
|
||||||
|
y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: '#10A0F2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: 'rgba(0, 82, 184, 0)'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
barWidth: '20%',
|
||||||
|
barGap: '20%'
|
||||||
|
},
|
||||||
|
// 支出底色 - 后绘制,堆叠在数据上方
|
||||||
|
{
|
||||||
|
name: '支出底色',
|
||||||
|
type: 'bar',
|
||||||
|
stack: 'monthly',
|
||||||
|
data: monthlyStandardBgData,
|
||||||
|
itemStyle: {
|
||||||
|
color: '#38668D70'
|
||||||
|
},
|
||||||
|
barWidth: '20%',
|
||||||
|
barGap: '20%',
|
||||||
|
silent: true,
|
||||||
|
tooltip: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 收入数据(渐变)
|
||||||
|
{
|
||||||
|
name: '收入',
|
||||||
|
type: 'bar',
|
||||||
|
stack: 'perCapita',
|
||||||
|
data: perCapitaData,
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
x2: 0,
|
||||||
|
y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: '#FFA58D'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: 'rgba(87, 140, 205, 0)'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
barWidth: '20%',
|
||||||
|
barGap: '80%'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '收入底色',
|
||||||
|
type: 'bar',
|
||||||
|
stack: 'perCapita',
|
||||||
|
data: perCapitaBgData,
|
||||||
|
itemStyle: {
|
||||||
|
color: '#38668D70'
|
||||||
|
},
|
||||||
|
barWidth: '20%',
|
||||||
|
barGap: '80%',
|
||||||
|
silent: true,
|
||||||
|
tooltip: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 柱状图配置
|
||||||
|
const barOption = computed(() => createChartOption())
|
||||||
|
|
||||||
|
// 监听数据变化,更新图表
|
||||||
|
watch(
|
||||||
|
() => [props.data, props.cardData],
|
||||||
|
() => {
|
||||||
|
// 数据变化时,computed 会自动更新
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.supply-chart-container {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
436
src/views/Dashboard/components/ConsumptionRecords/Index.vue
Normal file
@ -0,0 +1,436 @@
|
|||||||
|
<template>
|
||||||
|
<div class="consumption-records-container">
|
||||||
|
<div class="consumption-records-title">
|
||||||
|
<span class="title-text">费用明细</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="consumption-records-relationship">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in relationshipData"
|
||||||
|
:key="index"
|
||||||
|
class="relationship-item"
|
||||||
|
:class="`relate-${item.color}`"
|
||||||
|
>
|
||||||
|
<div class="relationship-icon" :class="`icon-${item.color}`">
|
||||||
|
<svg width="10" height="10" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path
|
||||||
|
d="M16 7c0-2.21-1.79-4-4-4S8 4.79 8 7c0 2.21 1.79 4 4 4s4-1.79 4-4zm-4 6c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M20 7c0-2.21-1.79-4-4-4s-4 1.79-4 4c0 2.21 1.79 4 4 4s4-1.79 4-4zm-4 6c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="relationship-name">{{ item.name }}</div>
|
||||||
|
<div class="relationship-relate">{{ item.relate }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 标题栏 -->
|
||||||
|
<div class="consumption-records-content">
|
||||||
|
<div class="consumption-records">
|
||||||
|
<div class="records-header">
|
||||||
|
<span class="header-title">消费记录</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 记录列表 -->
|
||||||
|
<div class="records-list">
|
||||||
|
<div v-for="(item, index) in recordsData" :key="index" class="record-item">
|
||||||
|
<div class="record-date">{{ item.date }}</div>
|
||||||
|
<div class="record-name" :class="`name-${item.nameColor}`">
|
||||||
|
{{ item.name }}
|
||||||
|
</div>
|
||||||
|
<div class="record-category">{{ item.category }}</div>
|
||||||
|
<div class="record-amount">¥{{ item.amount }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="consumption-records">
|
||||||
|
<div class="records-header">
|
||||||
|
<span class="header-title">近期汇款</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 记录列表 -->
|
||||||
|
<div class="records-list">
|
||||||
|
<div v-for="(item, index) in remittancesData" :key="index" class="record-item">
|
||||||
|
<div class="record-date">{{ item.date }}</div>
|
||||||
|
<div class="record-name" :class="`name-${item.nameColor}`">
|
||||||
|
{{ item.name }}
|
||||||
|
</div>
|
||||||
|
<div class="record-category">{{ item.category }}</div>
|
||||||
|
<div class="record-amount">¥{{ item.amount }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
defineOptions({ name: 'ConsumptionRecords' })
|
||||||
|
|
||||||
|
interface ConsumptionRecord {
|
||||||
|
date: string
|
||||||
|
name: string
|
||||||
|
nameColor: string
|
||||||
|
category: string
|
||||||
|
amount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RelationshipRecord {
|
||||||
|
name: string
|
||||||
|
relate: string
|
||||||
|
color: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const relationshipData = ref<RelationshipRecord[]>([
|
||||||
|
{
|
||||||
|
name: '陈小美',
|
||||||
|
relate: '妻子',
|
||||||
|
color: 'orange'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '李三三',
|
||||||
|
relate: '儿子',
|
||||||
|
color: 'green'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '李为一',
|
||||||
|
relate: '父亲',
|
||||||
|
color: 'yellow'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '王大秀',
|
||||||
|
relate: '母亲',
|
||||||
|
color: 'purple'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
// 消费记录数据
|
||||||
|
const recordsData = ref<ConsumptionRecord[]>([
|
||||||
|
{
|
||||||
|
date: '2021-01-02',
|
||||||
|
name: '张伟',
|
||||||
|
nameColor: 'purple',
|
||||||
|
category: '餐饮',
|
||||||
|
amount: 1000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2021-01-03',
|
||||||
|
name: '王强',
|
||||||
|
nameColor: 'green',
|
||||||
|
category: '购物',
|
||||||
|
amount: 500
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2021-01-04',
|
||||||
|
name: '陈丽',
|
||||||
|
nameColor: 'blue',
|
||||||
|
category: '交通',
|
||||||
|
amount: 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2021-01-05',
|
||||||
|
name: '赵敏',
|
||||||
|
nameColor: 'teal',
|
||||||
|
category: '餐饮',
|
||||||
|
amount: 800
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2021-01-06',
|
||||||
|
name: '周婷',
|
||||||
|
nameColor: 'pink',
|
||||||
|
category: '购物',
|
||||||
|
amount: 1200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2021-01-07',
|
||||||
|
name: '吴磊',
|
||||||
|
nameColor: 'light-purple',
|
||||||
|
category: '交通',
|
||||||
|
amount: 300
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2021-01-08',
|
||||||
|
name: '张伟',
|
||||||
|
nameColor: 'purple',
|
||||||
|
category: '餐饮',
|
||||||
|
amount: 600
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2021-01-09',
|
||||||
|
name: '王强',
|
||||||
|
nameColor: 'green',
|
||||||
|
category: '购物',
|
||||||
|
amount: 900
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
// 汇款记录数据
|
||||||
|
const remittancesData = ref<ConsumptionRecord[]>([
|
||||||
|
{
|
||||||
|
date: '2021-01-02',
|
||||||
|
name: '张伟',
|
||||||
|
nameColor: 'purple',
|
||||||
|
category: '手机银行',
|
||||||
|
amount: 1000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2021-01-03',
|
||||||
|
name: '王强',
|
||||||
|
nameColor: 'green',
|
||||||
|
category: '手机银行',
|
||||||
|
amount: 500
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2021-01-04',
|
||||||
|
name: '陈丽',
|
||||||
|
nameColor: 'blue',
|
||||||
|
category: '手机银行',
|
||||||
|
amount: 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2021-01-05',
|
||||||
|
name: '赵敏',
|
||||||
|
nameColor: 'teal',
|
||||||
|
category: '手机银行',
|
||||||
|
amount: 800
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2021-01-06',
|
||||||
|
name: '周婷',
|
||||||
|
nameColor: 'pink',
|
||||||
|
category: '手机银行',
|
||||||
|
amount: 1200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2021-01-07',
|
||||||
|
name: '吴磊',
|
||||||
|
nameColor: 'light-purple',
|
||||||
|
category: '手机银行',
|
||||||
|
amount: 300
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2021-01-08',
|
||||||
|
name: '张伟一',
|
||||||
|
nameColor: 'purple',
|
||||||
|
category: '手机银行',
|
||||||
|
amount: 600
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2021-01-09',
|
||||||
|
name: '王强',
|
||||||
|
nameColor: 'green',
|
||||||
|
category: '手机银行',
|
||||||
|
amount: 900
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
// 可以通过 props 接收外部数据
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
data?: ConsumptionRecord[]
|
||||||
|
relatData?: RelationshipRecord[]
|
||||||
|
remitData?: ConsumptionRecord[]
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
data: () => [],
|
||||||
|
relatData: () => [],
|
||||||
|
remitData: () => []
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 如果传入了外部数据,使用外部数据
|
||||||
|
if (props.data && props.data.length > 0) {
|
||||||
|
recordsData.value = props.data
|
||||||
|
}
|
||||||
|
if (props.relatData && props.relatData.length > 0) {
|
||||||
|
relationshipData.value = props.relatData
|
||||||
|
}
|
||||||
|
if (props.remitData && props.remitData.length > 0) {
|
||||||
|
remittancesData.value = props.remitData
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.consumption-records-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px 8px;
|
||||||
|
border: 1px solid rgba(56, 102, 141, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.consumption-records-title {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.consumption-records-content {
|
||||||
|
width: 100%;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.consumption-records {
|
||||||
|
width: 50%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标题栏
|
||||||
|
.records-header {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 8px;
|
||||||
|
background: #2c3d7e;
|
||||||
|
border-radius: 10px 10px 0 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-title {
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录列表
|
||||||
|
.records-list {
|
||||||
|
flex: 1 1 0;
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 8px 0;
|
||||||
|
background: #22283a;
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 0px;
|
||||||
|
}
|
||||||
|
border-radius: 0 0 10px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录项
|
||||||
|
.record-item {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1.4fr 0.8fr 1fr 0.8fr;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-bottom: 1px solid rgba(56, 102, 141, 0.15);
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-date {
|
||||||
|
font-size: 7px;
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-name {
|
||||||
|
font-size: 7px;
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
&.name-purple {
|
||||||
|
color: #a855f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.name-green {
|
||||||
|
color: #10c896;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.name-blue {
|
||||||
|
color: #3b82f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.name-teal {
|
||||||
|
color: #14b8a6;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.name-pink {
|
||||||
|
color: #ec4899;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.name-light-purple {
|
||||||
|
color: #c084fc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-category {
|
||||||
|
font-size: 7px;
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-amount {
|
||||||
|
font-size: 7px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: white;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.consumption-records-relationship {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
.relationship-item {
|
||||||
|
background: #422b1f;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
&.relate-orange {
|
||||||
|
background: #422b1f;
|
||||||
|
color: #ffa500;
|
||||||
|
}
|
||||||
|
&.relate-green {
|
||||||
|
background: #1d3056;
|
||||||
|
color: #00ffff;
|
||||||
|
}
|
||||||
|
&.relate-yellow {
|
||||||
|
background: #453f26;
|
||||||
|
color: #ffff00;
|
||||||
|
}
|
||||||
|
&.relate-purple {
|
||||||
|
background: #3a2679;
|
||||||
|
color: #ef9efa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.relationship-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: 1.5px;
|
||||||
|
svg {
|
||||||
|
width: 8px;
|
||||||
|
height: 9px;
|
||||||
|
}
|
||||||
|
&.icon-orange {
|
||||||
|
color: #ffa500;
|
||||||
|
}
|
||||||
|
&.icon-green {
|
||||||
|
color: #00ffff;
|
||||||
|
}
|
||||||
|
&.icon-yellow {
|
||||||
|
color: #ffff00;
|
||||||
|
}
|
||||||
|
&.icon-purple {
|
||||||
|
color: #ef9efa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.relationship-name {
|
||||||
|
font-size: 7px;
|
||||||
|
}
|
||||||
|
.relationship-relate {
|
||||||
|
font-size: 7px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
147
src/views/Dashboard/components/GaugeChart.vue
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
<template>
|
||||||
|
<div class="gauge-chart-wrapper">
|
||||||
|
<EChart :options="gaugeOption" :height="height" :width="width" />
|
||||||
|
<img :src="personIcon" class="center-icon" alt="person" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { EChartsOption } from 'echarts'
|
||||||
|
import * as echarts from 'echarts/core'
|
||||||
|
// @ts-ignore
|
||||||
|
import EChart from '@/components/Echart/src/Echart.vue'
|
||||||
|
import { computed, watch } from 'vue'
|
||||||
|
import personIcon from '@/assets/imgs/dashboard/person.svg?url'
|
||||||
|
|
||||||
|
defineOptions({ name: 'GaugeChart' })
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
width?: string
|
||||||
|
height?: string
|
||||||
|
value?: number
|
||||||
|
name?: string
|
||||||
|
min?: number
|
||||||
|
max?: number
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
width: '100%',
|
||||||
|
height: '300px',
|
||||||
|
value: 70,
|
||||||
|
name: '',
|
||||||
|
min: 0,
|
||||||
|
max: 100
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 仪表盘配置
|
||||||
|
const gaugeOption = computed(() => ({
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'gauge',
|
||||||
|
startAngle: 210,
|
||||||
|
endAngle: -30,
|
||||||
|
min: props.min,
|
||||||
|
max: props.max,
|
||||||
|
splitNumber: 10,
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
width: 15,
|
||||||
|
color: [
|
||||||
|
[
|
||||||
|
0.7,
|
||||||
|
new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: '#89C6C9'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 0.2,
|
||||||
|
color: '#2BC6E6'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 0.6,
|
||||||
|
color: '#E4BA86'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: '#D080A9'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
],
|
||||||
|
[1, '#440F6580']
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pointer: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
length: 8,
|
||||||
|
distance: -40,
|
||||||
|
lineStyle: {
|
||||||
|
color: 'auto',
|
||||||
|
width: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
length: 14,
|
||||||
|
distance: -44,
|
||||||
|
lineStyle: {
|
||||||
|
color: 'auto',
|
||||||
|
width: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
offsetCenter: [0, '-20%'],
|
||||||
|
fontSize: 20
|
||||||
|
},
|
||||||
|
detail: {
|
||||||
|
fontSize: 28,
|
||||||
|
offsetCenter: [0, '75%'],
|
||||||
|
valueAnimation: true,
|
||||||
|
formatter: function (value: number) {
|
||||||
|
return Math.round(value) + ''
|
||||||
|
},
|
||||||
|
color: '#1FBFF1'
|
||||||
|
},
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
value: props.value,
|
||||||
|
name: props.name
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}))
|
||||||
|
|
||||||
|
// 监听数据变化
|
||||||
|
watch(
|
||||||
|
() => [props.value, props.name, props.min, props.max],
|
||||||
|
() => {
|
||||||
|
// 数据变化时,computed 会自动更新
|
||||||
|
}
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.gauge-chart-wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center-icon {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -40%);
|
||||||
|
width: 50%;
|
||||||
|
height: 50%;
|
||||||
|
z-index: 100;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
294
src/views/Dashboard/components/InfoCard/Index.vue
Normal file
@ -0,0 +1,294 @@
|
|||||||
|
<template>
|
||||||
|
<div class="info-card-container">
|
||||||
|
<!-- 基本信息区域 -->
|
||||||
|
<div class="info-section">
|
||||||
|
<div class="section-header">
|
||||||
|
<span class="header-title">基本信息</span>
|
||||||
|
<div class="header-icon"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-content">
|
||||||
|
<div class="info-tag">{{ basicInfo.district }}</div>
|
||||||
|
<div class="info-tag">狱政编号: {{ basicInfo.prisonNumber }}</div>
|
||||||
|
<div class="info-tag">
|
||||||
|
刑期起/止日:{{ basicInfo.sentenceStart }}---{{ basicInfo.sentenceEnd }} ({{
|
||||||
|
basicInfo.sentenceDays
|
||||||
|
}}天)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-detail">
|
||||||
|
<div class="info-list">
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-label">年龄:</span>
|
||||||
|
<span class="info-value">{{ basicInfo.age }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-label">籍贯:</span>
|
||||||
|
<span class="info-value">{{ basicInfo.hometown }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-label">文化程度:</span>
|
||||||
|
<span class="info-value">{{ basicInfo.education }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-label">婚姻:</span>
|
||||||
|
<span class="info-value">{{ basicInfo.maritalStatus }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-label">生育:</span>
|
||||||
|
<span class="info-value">{{ basicInfo.children }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-label">出生日期:</span>
|
||||||
|
<span class="info-value">{{ basicInfo.birthDate }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-label">犯罪类型:</span>
|
||||||
|
<span class="info-value">{{ basicInfo.crimeType }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-label">前科次数:</span>
|
||||||
|
<span class="info-value">{{ basicInfo.previousConvictions }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-label">刑期:</span>
|
||||||
|
<span class="info-value">{{ basicInfo.sentence }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="records-content">
|
||||||
|
<div class="records-content-title">心理访谈记录</div>
|
||||||
|
<div v-for="(record, index) in interviewRecords" :key="index" class="record-item">
|
||||||
|
<div class="record-bullet"></div>
|
||||||
|
<div class="record-content">
|
||||||
|
<div class="record-date">{{ record.date }}</div>
|
||||||
|
<div class="record-text">{{ record.content }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineOptions({ name: 'InfoCard' })
|
||||||
|
|
||||||
|
interface BasicInfo {
|
||||||
|
district: string
|
||||||
|
prisonNumber: string
|
||||||
|
sentenceStart: string
|
||||||
|
sentenceEnd: string
|
||||||
|
sentenceDays: number
|
||||||
|
age: number
|
||||||
|
hometown: string
|
||||||
|
education: string
|
||||||
|
maritalStatus: string
|
||||||
|
children: string
|
||||||
|
birthDate: string
|
||||||
|
crimeType: string
|
||||||
|
previousConvictions: string
|
||||||
|
sentence: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface InterviewRecord {
|
||||||
|
date: string
|
||||||
|
content: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
basicInfo?: BasicInfo
|
||||||
|
interviewRecords?: InterviewRecord[]
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
basicInfo: () => ({
|
||||||
|
district: '第1监区 十四分监区',
|
||||||
|
prisonNumber: 'ZF20230001',
|
||||||
|
sentenceStart: '2020/07/21',
|
||||||
|
sentenceEnd: '2020/07/21',
|
||||||
|
sentenceDays: 300,
|
||||||
|
age: 78,
|
||||||
|
hometown: '中国/江苏',
|
||||||
|
education: '大学',
|
||||||
|
maritalStatus: '未婚',
|
||||||
|
children: '无子女',
|
||||||
|
birthDate: '2025-02-02',
|
||||||
|
crimeType: '盗窃罪',
|
||||||
|
previousConvictions: '7次',
|
||||||
|
sentence: '有期徒刑1年6月'
|
||||||
|
}),
|
||||||
|
interviewRecords: () => [
|
||||||
|
{
|
||||||
|
date: '2023-10',
|
||||||
|
content: '情绪稳定,遵规守纪良好'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2023-09',
|
||||||
|
content: '人际关系融洽,态度积极'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2023-08',
|
||||||
|
content: '偶有焦虑情绪,需关注'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2023-07',
|
||||||
|
content: '偶有焦虑情绪,需关注'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.info-card-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(13, 30, 50, 0.9);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
gap: 24px;
|
||||||
|
border: 1px solid rgba(56, 102, 141, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-section {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interview-records {
|
||||||
|
padding-left: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-title {
|
||||||
|
font-size: 8px;
|
||||||
|
margin-right: 2px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-icon {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
background: url('@/assets/imgs/dashboard/pie.svg') no-repeat center center;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-tag {
|
||||||
|
padding: 4px 6px;
|
||||||
|
background: #3f6973;
|
||||||
|
border: 1px solid rgba(56, 102, 141, 0.5);
|
||||||
|
border-radius: 2px;
|
||||||
|
font-size: 5px;
|
||||||
|
color: #d8f0ff;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-detail {
|
||||||
|
display: flex;
|
||||||
|
gap: 2px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.records-content-title {
|
||||||
|
font-size: 6px;
|
||||||
|
color: #d8f0ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 6px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
color: white;
|
||||||
|
margin-right: 4px;
|
||||||
|
min-width: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
color: white;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.records-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
height: 105px;
|
||||||
|
overflow-y: auto;
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-item {
|
||||||
|
display: flex;
|
||||||
|
gap: 3px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-bullet {
|
||||||
|
width: 5px;
|
||||||
|
height: 5px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #10a0f2;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: 4px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 7px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 2px;
|
||||||
|
height: calc(100% + 4px);
|
||||||
|
background: rgba(56, 102, 141, 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-item:last-child .record-bullet::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-content {
|
||||||
|
flex: 1;
|
||||||
|
padding-bottom: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-date {
|
||||||
|
font-size: 6px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: white;
|
||||||
|
margin-bottom: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-text {
|
||||||
|
font-size: 6px;
|
||||||
|
color: #d8f0ff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,250 @@
|
|||||||
|
<template>
|
||||||
|
<div class="rewards-punishments-container">
|
||||||
|
<!-- 标题栏 -->
|
||||||
|
<div class="rewards-header">
|
||||||
|
<span class="header-title">近期奖惩</span>
|
||||||
|
<div class="filter-tabs">
|
||||||
|
<div
|
||||||
|
v-for="tab in filterTabs"
|
||||||
|
:key="tab.value"
|
||||||
|
class="filter-tab"
|
||||||
|
:class="{ active: activeFilter === tab.value }"
|
||||||
|
@click="activeFilter = tab.value"
|
||||||
|
>
|
||||||
|
{{ tab.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 时间线列表 -->
|
||||||
|
<div class="timeline-container">
|
||||||
|
<div class="timeline-content">
|
||||||
|
<div class="timeline-line"></div>
|
||||||
|
<div class="timeline-items">
|
||||||
|
<div v-for="(item, index) in filteredList" :key="index" class="timeline-item">
|
||||||
|
<div class="timeline-dot" :class="item.type"></div>
|
||||||
|
<div class="timeline-card">
|
||||||
|
<div class="card-type" :class="item.type">{{ item.typeText }}</div>
|
||||||
|
<div class="card-description">{{ item.description }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
|
||||||
|
defineOptions({ name: 'RecentRewardsPunishments' })
|
||||||
|
|
||||||
|
interface RewardPunishmentItem {
|
||||||
|
type: 'reward' | 'punishment'
|
||||||
|
typeText: string
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过滤标签
|
||||||
|
const filterTabs = [
|
||||||
|
{ label: '全部', value: 'all' },
|
||||||
|
{ label: '奖励记录', value: 'reward' },
|
||||||
|
{ label: '惩罚记录', value: 'punishment' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const activeFilter = ref<string>('all')
|
||||||
|
|
||||||
|
// 示例数据
|
||||||
|
const listData = ref<RewardPunishmentItem[]>([
|
||||||
|
{
|
||||||
|
type: 'reward',
|
||||||
|
typeText: '表扬奖励',
|
||||||
|
description: '因积极参加劳动改造,表现突出,获得监区表扬。'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'reward',
|
||||||
|
typeText: '表扬奖励',
|
||||||
|
description: '因积极参加劳动改造,表现突出,获得监区表扬。'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'punishment',
|
||||||
|
typeText: '警告',
|
||||||
|
description: '因积极参加劳动改造,表现突出,获得监区表扬。'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'reward',
|
||||||
|
typeText: '表扬奖励',
|
||||||
|
description: '因积极参加劳动改造,表现突出,获得监区表扬。'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'punishment',
|
||||||
|
typeText: '扣分',
|
||||||
|
description: '因积极参加劳动改造,表现突出,获得监区表扬。'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
// 过滤后的列表
|
||||||
|
const filteredList = computed(() => {
|
||||||
|
if (activeFilter.value === 'all') {
|
||||||
|
return listData.value
|
||||||
|
} else if (activeFilter.value === 'reward') {
|
||||||
|
return listData.value.filter((item) => item.type === 'reward')
|
||||||
|
} else {
|
||||||
|
return listData.value.filter((item) => item.type === 'punishment')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 可以通过 props 接收外部数据
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
data?: RewardPunishmentItem[]
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
data: () => []
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 如果传入了外部数据,使用外部数据
|
||||||
|
if (props.data && props.data.length > 0) {
|
||||||
|
listData.value = props.data
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.rewards-punishments-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid rgba(56, 102, 141, 0.5);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标题栏
|
||||||
|
.rewards-header {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-title {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-tab {
|
||||||
|
padding: 2px 4px;
|
||||||
|
font-size: 7px;
|
||||||
|
color: rgba(255, 255, 255, 0.85);
|
||||||
|
background: rgba(56, 102, 141, 0.2);
|
||||||
|
border-radius: 2px;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: #37599d;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时间线容器
|
||||||
|
.timeline-container {
|
||||||
|
flex: 1 1 0;
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-left: 6px;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-content {
|
||||||
|
position: relative;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时间线
|
||||||
|
.timeline-line {
|
||||||
|
position: absolute;
|
||||||
|
left: -2px;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 1px;
|
||||||
|
background: #5e7fef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-items {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时间线项
|
||||||
|
.timeline-item {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时间线标记点
|
||||||
|
.timeline-dot {
|
||||||
|
position: absolute;
|
||||||
|
left: -5px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 7px;
|
||||||
|
height: 7px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid rgba(13, 30, 50, 0.8);
|
||||||
|
background: rgba(13, 30, 50, 0.8);
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
&.reward {
|
||||||
|
background: #10b981;
|
||||||
|
border-color: #10b981;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.punishment {
|
||||||
|
background: #ff0000;
|
||||||
|
border-color: #ff0000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 事件卡片
|
||||||
|
.timeline-card {
|
||||||
|
flex: 1;
|
||||||
|
background: rgba(56, 102, 141, 0.15);
|
||||||
|
border: 1px solid rgba(56, 102, 141, 0.3);
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-type {
|
||||||
|
font-size: 7px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
|
||||||
|
&.reward {
|
||||||
|
color: #10b981;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.punishment {
|
||||||
|
color: #ff0000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-description {
|
||||||
|
font-size: 5px;
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
321
src/views/Dashboard/components/ScoreAssessment/Index.vue
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
<template>
|
||||||
|
<div class="score-assessment-container">
|
||||||
|
<!-- 标题栏 -->
|
||||||
|
<div class="score-header">
|
||||||
|
<span class="header-title">计分考核</span>
|
||||||
|
<div class="header-icon"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 内容区域 -->
|
||||||
|
<div class="score-content">
|
||||||
|
<!-- 表头 -->
|
||||||
|
<div class="score-table-header">
|
||||||
|
<div class="header-cell">
|
||||||
|
<div class="required-star"></div>
|
||||||
|
<span>考核年月</span>
|
||||||
|
</div>
|
||||||
|
<div class="header-cell">加/扣</div>
|
||||||
|
<div class="header-cell">最终分</div>
|
||||||
|
<div class="header-cell">等级</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 数据列表 -->
|
||||||
|
<div class="score-table-body">
|
||||||
|
<div v-for="(item, index) in scoreData" :key="index" class="score-table-row">
|
||||||
|
<div class="row-cell cell-date">
|
||||||
|
<div class="row-icon"></div>
|
||||||
|
<div>{{ item.date }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="row-cell cell-score" :class="item.scoreType">
|
||||||
|
{{ item.score }}
|
||||||
|
</div>
|
||||||
|
<div class="row-cell cell-final">{{ item.finalScore }}</div>
|
||||||
|
<div class="row-cell cell-level">
|
||||||
|
<span class="level-badge" :class="`level-${item.level}`">
|
||||||
|
{{ item.levelText }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
defineOptions({ name: 'ScoreAssessment' })
|
||||||
|
|
||||||
|
interface ScoreItem {
|
||||||
|
date: string
|
||||||
|
score: string
|
||||||
|
scoreType: 'positive' | 'negative'
|
||||||
|
finalScore: number
|
||||||
|
level: 'excellent' | 'good' | 'poor'
|
||||||
|
levelText: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计分考核数据
|
||||||
|
const scoreData = ref<ScoreItem[]>([
|
||||||
|
{
|
||||||
|
date: '2023-05',
|
||||||
|
score: '+5',
|
||||||
|
scoreType: 'positive',
|
||||||
|
finalScore: 88,
|
||||||
|
level: 'excellent',
|
||||||
|
levelText: '优秀'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2023-05',
|
||||||
|
score: '-5',
|
||||||
|
scoreType: 'negative',
|
||||||
|
finalScore: 88,
|
||||||
|
level: 'good',
|
||||||
|
levelText: '良好'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2023-05',
|
||||||
|
score: '+5',
|
||||||
|
scoreType: 'positive',
|
||||||
|
finalScore: 88,
|
||||||
|
level: 'excellent',
|
||||||
|
levelText: '优秀'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2023-05',
|
||||||
|
score: '-5',
|
||||||
|
scoreType: 'negative',
|
||||||
|
finalScore: 88,
|
||||||
|
level: 'good',
|
||||||
|
levelText: '良好'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2023-05',
|
||||||
|
score: '+5',
|
||||||
|
scoreType: 'positive',
|
||||||
|
finalScore: 88,
|
||||||
|
level: 'excellent',
|
||||||
|
levelText: '优秀'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2023-05',
|
||||||
|
score: '-5',
|
||||||
|
scoreType: 'negative',
|
||||||
|
finalScore: 88,
|
||||||
|
level: 'poor',
|
||||||
|
levelText: '较差'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2023-05',
|
||||||
|
score: '-5',
|
||||||
|
scoreType: 'negative',
|
||||||
|
finalScore: 88,
|
||||||
|
level: 'poor',
|
||||||
|
levelText: '较差'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2023-05',
|
||||||
|
score: '-5',
|
||||||
|
scoreType: 'negative',
|
||||||
|
finalScore: 88,
|
||||||
|
level: 'poor',
|
||||||
|
levelText: '较差'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2023-05',
|
||||||
|
score: '-5',
|
||||||
|
scoreType: 'negative',
|
||||||
|
finalScore: 88,
|
||||||
|
level: 'poor',
|
||||||
|
levelText: '较差'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2023-05',
|
||||||
|
score: '-5',
|
||||||
|
scoreType: 'negative',
|
||||||
|
finalScore: 88,
|
||||||
|
level: 'poor',
|
||||||
|
levelText: '较差'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2023-05',
|
||||||
|
score: '-5',
|
||||||
|
scoreType: 'negative',
|
||||||
|
finalScore: 88,
|
||||||
|
level: 'poor',
|
||||||
|
levelText: '较差'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2023-05',
|
||||||
|
score: '+5',
|
||||||
|
scoreType: 'positive',
|
||||||
|
finalScore: 88,
|
||||||
|
level: 'excellent',
|
||||||
|
levelText: '优秀'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
// 可以通过 props 接收外部数据
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
data?: ScoreItem[]
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
data: () => []
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 如果传入了外部数据,使用外部数据
|
||||||
|
if (props.data && props.data.length > 0) {
|
||||||
|
scoreData.value = props.data
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.score-assessment-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 4px 8px 16px;
|
||||||
|
border: 1px solid rgba(56, 102, 141, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标题栏
|
||||||
|
.score-header {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-title {
|
||||||
|
font-size: 8px;
|
||||||
|
margin-right: 2px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-icon {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
background: url('@/assets/imgs/dashboard/pie.svg') no-repeat center center;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 内容区域
|
||||||
|
.score-content {
|
||||||
|
flex: 1 1 0;
|
||||||
|
// min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表头
|
||||||
|
.score-table-header {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1.5fr 1fr 1fr 1fr;
|
||||||
|
padding: 4px 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-cell {
|
||||||
|
font-size: 6px;
|
||||||
|
color: rgba(255, 255, 255, 0.85);
|
||||||
|
font-weight: 500;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.required-star {
|
||||||
|
background: url('@/assets/imgs/dashboard/icon-require.svg') no-repeat center center;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
width: 7px;
|
||||||
|
height: 7px;
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表格主体
|
||||||
|
.score-table-body {
|
||||||
|
flex: 1 1 0;
|
||||||
|
// min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-table-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1.5fr 1fr 1fr 1fr;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-icon {
|
||||||
|
background: url('@/assets/imgs/dashboard/icon-arrow.svg') no-repeat center center;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
width: 7px;
|
||||||
|
height: 7px;
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-cell {
|
||||||
|
font-size: 6px;
|
||||||
|
color: #ffffff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-date {
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-score {
|
||||||
|
&.positive {
|
||||||
|
color: #10c896;
|
||||||
|
text-shadow: 0 0 4px rgba(16, 200, 150, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.negative {
|
||||||
|
color: #ff6b6b;
|
||||||
|
text-shadow: 0 0 4px rgba(255, 107, 107, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-final {
|
||||||
|
color: #ffd700;
|
||||||
|
text-shadow: 0 0 4px rgba(255, 215, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-level {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.level-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 1px 4px;
|
||||||
|
border-radius: 2px;
|
||||||
|
color: #ffffff;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&.level-excellent {
|
||||||
|
background: #51869290;
|
||||||
|
border: 1px solid #518692;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.level-good {
|
||||||
|
background: #47363390;
|
||||||
|
border: 1px solid #473633;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.level-poor {
|
||||||
|
background: #33131f90;
|
||||||
|
border: 1px solid #33131f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
2
types/env.d.ts
vendored
@ -1,7 +1,7 @@
|
|||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
declare module '*.vue' {
|
declare module '*.vue' {
|
||||||
import { DefineComponent } from 'vue'
|
import type { DefineComponent } from 'vue'
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
|
||||||
const component: DefineComponent<{}, {}, any>
|
const component: DefineComponent<{}, {}, any>
|
||||||
export default component
|
export default component
|
||||||
|
|||||||