fix: 用量查看优化
This commit is contained in:
50
Yi.Ai.Vue3/pnpm-lock.yaml
generated
50
Yi.Ai.Vue3/pnpm-lock.yaml
generated
@@ -158,7 +158,7 @@ importers:
|
||||
devDependencies:
|
||||
'@antfu/eslint-config':
|
||||
specifier: ^4.16.2
|
||||
version: 4.17.0(@vue/compiler-sfc@3.5.17)(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.15.30)(@vitest/browser@3.2.4)(jiti@2.4.2)(sass-embedded@1.89.2)(sass@1.97.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))
|
||||
version: 4.17.0(@vue/compiler-sfc@3.5.17)(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4)
|
||||
'@changesets/cli':
|
||||
specifier: ^2.29.5
|
||||
version: 2.29.5
|
||||
@@ -188,7 +188,7 @@ importers:
|
||||
version: 8.6.14(react-dom@19.2.3(react@19.1.0))(react@19.1.0)(storybook@8.6.14(prettier@3.6.2))
|
||||
'@storybook/experimental-addon-test':
|
||||
specifier: ^8.6.14
|
||||
version: 8.6.14(@vitest/browser@3.2.4(playwright@1.57.0)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(sass-embedded@1.89.2)(sass@1.97.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))(vitest@3.2.4))(@vitest/runner@3.2.4)(react-dom@19.2.3(react@19.1.0))(react@19.1.0)(storybook@8.6.14(prettier@3.6.2))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.15.30)(@vitest/browser@3.2.4)(jiti@2.4.2)(sass-embedded@1.89.2)(sass@1.97.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))
|
||||
version: 8.6.14(@vitest/browser@3.2.4)(@vitest/runner@3.2.4)(react-dom@19.2.3(react@19.1.0))(react@19.1.0)(storybook@8.6.14(prettier@3.6.2))(vitest@3.2.4)
|
||||
'@storybook/manager-api':
|
||||
specifier: ^8.6.14
|
||||
version: 8.6.14(storybook@8.6.14(prettier@3.6.2))
|
||||
@@ -227,7 +227,7 @@ importers:
|
||||
version: 3.2.4(playwright@1.57.0)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(sass-embedded@1.89.2)(sass@1.97.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))(vitest@3.2.4)
|
||||
'@vitest/coverage-v8':
|
||||
specifier: ^3.2.4
|
||||
version: 3.2.4(@vitest/browser@3.2.4(playwright@1.57.0)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(sass-embedded@1.89.2)(sass@1.97.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))(vitest@3.2.4))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.15.30)(@vitest/browser@3.2.4)(jiti@2.4.2)(sass-embedded@1.89.2)(sass@1.97.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))
|
||||
version: 3.2.4(@vitest/browser@3.2.4)(vitest@3.2.4)
|
||||
'@vue/tsconfig':
|
||||
specifier: ^0.7.0
|
||||
version: 0.7.0(typescript@5.8.3)(vue@3.5.17(typescript@5.8.3))
|
||||
@@ -427,28 +427,24 @@ packages:
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@ast-grep/napi-linux-arm64-musl@0.36.3':
|
||||
resolution: {integrity: sha512-2XRmNYuovZu0Pa4J3or4PKMkQZnXXfpVcCrPwWB/2ytX7XUo+TWLgYE8rPVnJOyw5zujkveFb0XUrro9mQgLzw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@ast-grep/napi-linux-x64-gnu@0.36.3':
|
||||
resolution: {integrity: sha512-mTwPRbBi1feGqR2b5TWC5gkEDeRi8wfk4euF5sKNihfMGHj6pdfINHQ3QvLVO4C7z0r/wgWLAvditFA0b997dg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@ast-grep/napi-linux-x64-musl@0.36.3':
|
||||
resolution: {integrity: sha512-tMGPrT+zuZzJK6n1cD1kOii7HYZE9gUXjwtVNE/uZqXEaWP6lmkfoTMbLjnxEe74VQbmaoDGh1/cjrDBnqC6Uw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@ast-grep/napi-win32-arm64-msvc@0.36.3':
|
||||
resolution: {integrity: sha512-7pFyr9+dyV+4cBJJ1I57gg6PDXP3GBQeVAsEEitzEruxx4Hb4cyNro54gGtlsS+6ty+N0t004tPQxYO2VrsPIg==}
|
||||
@@ -1245,35 +1241,30 @@ packages:
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@napi-rs/canvas-linux-arm64-musl@0.1.84':
|
||||
resolution: {integrity: sha512-VyZq0EEw+OILnWk7G3ZgLLPaz1ERaPP++jLjeyLMbFOF+Tr4zHzWKiKDsEV/cT7btLPZbVoR3VX+T9/QubnURQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@napi-rs/canvas-linux-riscv64-gnu@0.1.84':
|
||||
resolution: {integrity: sha512-PSMTh8DiThvLRsbtc/a065I/ceZk17EXAATv9uNvHgkgo7wdEfTh2C3aveNkBMGByVO3tvnvD5v/YFtZL07cIg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@napi-rs/canvas-linux-x64-gnu@0.1.84':
|
||||
resolution: {integrity: sha512-N1GY3noO1oqgEo3rYQIwY44kfM11vA0lDbN0orTOHfCSUZTUyiYCY0nZ197QMahZBm1aR/vYgsWpV74MMMDuNA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@napi-rs/canvas-linux-x64-musl@0.1.84':
|
||||
resolution: {integrity: sha512-vUZmua6ADqTWyHyei81aXIt9wp0yjeNwTH0KdhdeoBb6azHmFR8uKTukZMXfLCC3bnsW0t4lW7K78KNMknmtjg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@napi-rs/canvas-win32-x64-msvc@0.1.84':
|
||||
resolution: {integrity: sha512-YSs8ncurc1xzegUMNnQUTYrdrAuaXdPMOa+iYYyAxydOtg0ppV386hyYMsy00Yip1NlTgLCseRG4sHSnjQx6og==}
|
||||
@@ -1330,42 +1321,36 @@ packages:
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-arm-musl@2.5.1':
|
||||
resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-linux-arm64-glibc@2.5.1':
|
||||
resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-arm64-musl@2.5.1':
|
||||
resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-linux-x64-glibc@2.5.1':
|
||||
resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-x64-musl@2.5.1':
|
||||
resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-win32-arm64@2.5.1':
|
||||
resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
|
||||
@@ -1450,67 +1435,56 @@ packages:
|
||||
resolution: {integrity: sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.41.1':
|
||||
resolution: {integrity: sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.41.1':
|
||||
resolution: {integrity: sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.41.1':
|
||||
resolution: {integrity: sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-loongarch64-gnu@4.41.1':
|
||||
resolution: {integrity: sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-powerpc64le-gnu@4.41.1':
|
||||
resolution: {integrity: sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.41.1':
|
||||
resolution: {integrity: sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.41.1':
|
||||
resolution: {integrity: sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.41.1':
|
||||
resolution: {integrity: sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.41.1':
|
||||
resolution: {integrity: sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.41.1':
|
||||
resolution: {integrity: sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-win32-arm64-msvc@4.41.1':
|
||||
resolution: {integrity: sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==}
|
||||
@@ -6913,8 +6887,8 @@ packages:
|
||||
vue-component-type-helpers@2.2.12:
|
||||
resolution: {integrity: sha512-YbGqHZ5/eW4SnkPNR44mKVc6ZKQoRs/Rux1sxC6rdwXb4qpbOSYfDr9DsTHolOTGmIKgM9j141mZbBeg05R1pw==}
|
||||
|
||||
vue-component-type-helpers@3.1.8:
|
||||
resolution: {integrity: sha512-oaowlmEM6BaYY+8o+9D9cuzxpWQWHqHTMKakMxXu0E+UCIOMTljyIPO15jcnaCwJtZu/zWDotK7mOIHvWD9mcw==}
|
||||
vue-component-type-helpers@3.2.3:
|
||||
resolution: {integrity: sha512-lpJTa8a+12Cgy/n5OdlQTzQhSWOCu+6zQoNFbl3KYxwAoB95mYIgMLKEYMvQykPJ2ucBDjJJISdIBHc1d9Hd3w==}
|
||||
|
||||
vue-demi@0.14.10:
|
||||
resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
|
||||
@@ -7133,7 +7107,7 @@ snapshots:
|
||||
'@jridgewell/gen-mapping': 0.3.8
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
|
||||
'@antfu/eslint-config@4.17.0(@vue/compiler-sfc@3.5.17)(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.15.30)(@vitest/browser@3.2.4)(jiti@2.4.2)(sass-embedded@1.89.2)(sass@1.97.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))':
|
||||
'@antfu/eslint-config@4.17.0(@vue/compiler-sfc@3.5.17)(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4)':
|
||||
dependencies:
|
||||
'@antfu/install-pkg': 1.1.0
|
||||
'@clack/prompts': 0.11.0
|
||||
@@ -7142,7 +7116,7 @@ snapshots:
|
||||
'@stylistic/eslint-plugin': 5.2.0(eslint@9.31.0(jiti@2.4.2))
|
||||
'@typescript-eslint/eslint-plugin': 8.37.0(@typescript-eslint/parser@8.37.0(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)
|
||||
'@typescript-eslint/parser': 8.37.0(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)
|
||||
'@vitest/eslint-plugin': 1.3.4(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.15.30)(@vitest/browser@3.2.4)(jiti@2.4.2)(sass-embedded@1.89.2)(sass@1.97.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))
|
||||
'@vitest/eslint-plugin': 1.3.4(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4)
|
||||
ansis: 4.1.0
|
||||
cac: 6.7.14
|
||||
eslint: 9.31.0(jiti@2.4.2)
|
||||
@@ -8494,7 +8468,7 @@ snapshots:
|
||||
dependencies:
|
||||
type-fest: 2.19.0
|
||||
|
||||
'@storybook/experimental-addon-test@8.6.14(@vitest/browser@3.2.4(playwright@1.57.0)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(sass-embedded@1.89.2)(sass@1.97.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))(vitest@3.2.4))(@vitest/runner@3.2.4)(react-dom@19.2.3(react@19.1.0))(react@19.1.0)(storybook@8.6.14(prettier@3.6.2))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.15.30)(@vitest/browser@3.2.4)(jiti@2.4.2)(sass-embedded@1.89.2)(sass@1.97.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))':
|
||||
'@storybook/experimental-addon-test@8.6.14(@vitest/browser@3.2.4)(@vitest/runner@3.2.4)(react-dom@19.2.3(react@19.1.0))(react@19.1.0)(storybook@8.6.14(prettier@3.6.2))(vitest@3.2.4)':
|
||||
dependencies:
|
||||
'@storybook/global': 5.0.0
|
||||
'@storybook/icons': 1.6.0(react-dom@19.2.3(react@19.1.0))(react@19.1.0)
|
||||
@@ -8639,7 +8613,7 @@ snapshots:
|
||||
ts-dedent: 2.2.0
|
||||
type-fest: 2.19.0
|
||||
vue: 3.5.17(typescript@5.8.3)
|
||||
vue-component-type-helpers: 3.1.8
|
||||
vue-component-type-helpers: 3.2.3
|
||||
|
||||
'@stylistic/eslint-plugin@5.2.0(eslint@9.31.0(jiti@2.4.2))':
|
||||
dependencies:
|
||||
@@ -9301,7 +9275,7 @@ snapshots:
|
||||
- utf-8-validate
|
||||
- vite
|
||||
|
||||
'@vitest/coverage-v8@3.2.4(@vitest/browser@3.2.4(playwright@1.57.0)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(sass-embedded@1.89.2)(sass@1.97.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))(vitest@3.2.4))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.15.30)(@vitest/browser@3.2.4)(jiti@2.4.2)(sass-embedded@1.89.2)(sass@1.97.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))':
|
||||
'@vitest/coverage-v8@3.2.4(@vitest/browser@3.2.4)(vitest@3.2.4)':
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
'@bcoe/v8-coverage': 1.0.2
|
||||
@@ -9322,7 +9296,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@vitest/eslint-plugin@1.3.4(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.15.30)(@vitest/browser@3.2.4)(jiti@2.4.2)(sass-embedded@1.89.2)(sass@1.97.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))':
|
||||
'@vitest/eslint-plugin@1.3.4(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4)':
|
||||
dependencies:
|
||||
'@typescript-eslint/utils': 8.33.1(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)
|
||||
eslint: 9.31.0(jiti@2.4.2)
|
||||
@@ -14721,7 +14695,7 @@ snapshots:
|
||||
|
||||
vue-component-type-helpers@2.2.12: {}
|
||||
|
||||
vue-component-type-helpers@3.1.8: {}
|
||||
vue-component-type-helpers@3.2.3: {}
|
||||
|
||||
vue-demi@0.14.10(vue@3.5.17(typescript@5.8.3)):
|
||||
dependencies:
|
||||
|
||||
@@ -211,3 +211,36 @@ export function getPremiumPackageTokenUsage() {
|
||||
"percentage": 0
|
||||
}
|
||||
] */
|
||||
|
||||
// 获取当前用户近24小时每小时Token消耗统计
|
||||
export function getLast24HoursTokenUsage() {
|
||||
return get<any>('/usage-statistics/last24Hours-token-usage').json();
|
||||
}
|
||||
/* 返回数据
|
||||
[
|
||||
{
|
||||
"hour": "2026-01-23T13:32:49.237Z",
|
||||
"totalTokens": 0,
|
||||
"modelBreakdown": [
|
||||
{
|
||||
"modelId": "string",
|
||||
"tokens": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
*/
|
||||
|
||||
// 获取当前用户今日各模型使用量统计
|
||||
export function getTodayModelUsage() {
|
||||
return get<any>('/usage-statistics/today-model-usage').json();
|
||||
}
|
||||
/* 返回数据
|
||||
[
|
||||
{
|
||||
"modelId": "string",
|
||||
"usageCount": 0,
|
||||
"totalTokens": 0
|
||||
}
|
||||
]
|
||||
*/
|
||||
|
||||
@@ -3,6 +3,7 @@ import { FullScreen, PieChart } from '@element-plus/icons-vue';
|
||||
import { useElementSize } from '@vueuse/core';
|
||||
import { BarChart, PieChart as EPieChart, LineChart } from 'echarts/charts';
|
||||
import {
|
||||
BrushComponent,
|
||||
GraphicComponent,
|
||||
GridComponent,
|
||||
LegendComponent,
|
||||
@@ -11,7 +12,7 @@ import {
|
||||
} from 'echarts/components';
|
||||
import * as echarts from 'echarts/core';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import { getLast7DaysTokenUsage, getModelTokenUsage, getSelectableTokenInfo } from '@/api';
|
||||
import { getLast7DaysTokenUsage, getModelTokenUsage, getSelectableTokenInfo, getLast24HoursTokenUsage, getTodayModelUsage } from '@/api';
|
||||
|
||||
// 注册必要的组件
|
||||
echarts.use([
|
||||
@@ -23,6 +24,7 @@ echarts.use([
|
||||
GridComponent,
|
||||
LegendComponent,
|
||||
GraphicComponent,
|
||||
BrushComponent,
|
||||
CanvasRenderer,
|
||||
]);
|
||||
|
||||
@@ -30,9 +32,11 @@ echarts.use([
|
||||
const lineChart = ref(null);
|
||||
const pieChart = ref(null);
|
||||
const barChart = ref(null);
|
||||
const hourlyBarChart = ref(null); // 新增:近24小时每小时Token消耗柱状图
|
||||
let lineChartInstance: any = null;
|
||||
let pieChartInstance: any = null;
|
||||
let barChartInstance: any = null;
|
||||
let hourlyBarChartInstance: any = null; // 新增:近24小时柱状图实例
|
||||
|
||||
// 全屏状态
|
||||
const isFullscreen = ref(false);
|
||||
@@ -47,6 +51,8 @@ const loading = ref(false);
|
||||
const totalTokens = ref(0);
|
||||
const usageData = ref<any[]>([]);
|
||||
const modelUsageData = ref<any[]>([]);
|
||||
const hourlyUsageData = ref<any[]>([]); // 新增:近24小时每小时Token消耗数据
|
||||
const todayModelUsageData = ref<any[]>([]); // 新增:今日各模型使用量数据
|
||||
|
||||
// Token选择相关
|
||||
const selectedTokenId = ref<string>(''); // 空字符串表示查询全部
|
||||
@@ -64,6 +70,74 @@ const selectedTokenName = computed(() => {
|
||||
return token?.name || '未知API密钥';
|
||||
});
|
||||
|
||||
// 计算属性:获取近24小时数据中所有唯一的模型ID(按总token使用量排序,只统计有数据的模型)
|
||||
const hourlyModels = computed(() => {
|
||||
const modelTokenMap = new Map<string, { tokens: number; iconUrl: string }>();
|
||||
hourlyUsageData.value.forEach((hour) => {
|
||||
hour.modelBreakdown?.forEach((model: any) => {
|
||||
// 只统计有实际使用量的模型
|
||||
if (model.tokens > 0) {
|
||||
const existing = modelTokenMap.get(model.modelId);
|
||||
modelTokenMap.set(model.modelId, {
|
||||
tokens: (existing?.tokens || 0) + model.tokens,
|
||||
iconUrl: model.iconUrl || '',
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
return Array.from(modelTokenMap.entries())
|
||||
.sort((a, b) => b[1].tokens - a[1].tokens)
|
||||
.map(([modelId, data]) => ({ modelId, iconUrl: data.iconUrl }));
|
||||
});
|
||||
|
||||
// 计算属性:模型颜色映射(保持一致性)
|
||||
const modelColors = computed(() => {
|
||||
const baseColors = [
|
||||
'#3a4de9', '#6a5acd', '#9370db', '#8a2be2', '#9932cc',
|
||||
'#ba55d3', '#da70d6', '#ee82ee', '#dda0dd', '#ff00ff',
|
||||
'#667eea', '#764ba2', '#f093fb', '#f5576c', '#4facfe',
|
||||
'#00f2fe', '#43e97b', '#38f9d7', '#fa709a', '#fee140',
|
||||
'#4361ee', '#3a0ca3', '#7209b7', '#f72585', '#4cc9f0',
|
||||
];
|
||||
const colorMap: Record<string, string> = {};
|
||||
|
||||
// 先为近24小时数据的模型分配颜色
|
||||
hourlyModels.value.forEach(({ modelId }, index) => {
|
||||
if (!colorMap[modelId]) {
|
||||
colorMap[modelId] = baseColors[index % baseColors.length];
|
||||
}
|
||||
});
|
||||
|
||||
// 为今日模型数据中没有颜色的模型分配颜色
|
||||
let colorIndex = hourlyModels.value.length;
|
||||
todayModelUsageData.value.forEach((item: any) => {
|
||||
if (!colorMap[item.modelId]) {
|
||||
colorMap[item.modelId] = baseColors[colorIndex % baseColors.length];
|
||||
colorIndex++;
|
||||
}
|
||||
});
|
||||
|
||||
return colorMap;
|
||||
});
|
||||
|
||||
// 计算属性:模型图标URL映射
|
||||
const modelIconUrls = computed(() => {
|
||||
const iconMap: Record<string, string> = {};
|
||||
hourlyModels.value.forEach(({ modelId, iconUrl }) => {
|
||||
iconMap[modelId] = iconUrl;
|
||||
});
|
||||
return iconMap;
|
||||
});
|
||||
|
||||
// 计算属性:今日模型数据的图标映射
|
||||
const todayModelIcons = computed(() => {
|
||||
const iconMap: Record<string, string> = {};
|
||||
todayModelUsageData.value.forEach((item: any) => {
|
||||
iconMap[item.modelId] = item.iconUrl || '';
|
||||
});
|
||||
return iconMap;
|
||||
});
|
||||
|
||||
// 获取可选择的Token列表
|
||||
async function fetchTokenOptions() {
|
||||
try {
|
||||
@@ -93,13 +167,17 @@ async function fetchUsageData() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const tokenId = selectedTokenId.value || undefined;
|
||||
const [res, res2] = await Promise.all([
|
||||
const [res, res2, res3, res4] = await Promise.all([
|
||||
getLast7DaysTokenUsage(tokenId),
|
||||
getModelTokenUsage(tokenId),
|
||||
getLast24HoursTokenUsage(),
|
||||
getTodayModelUsage(),
|
||||
]);
|
||||
|
||||
usageData.value = res.data || [];
|
||||
modelUsageData.value = res2.data || [];
|
||||
hourlyUsageData.value = res3.data || [];
|
||||
todayModelUsageData.value = res4.data || [];
|
||||
totalTokens.value = usageData.value.reduce((sum, item) => sum + item.tokens, 0);
|
||||
|
||||
updateCharts();
|
||||
@@ -124,6 +202,9 @@ function initCharts() {
|
||||
if (barChart.value) {
|
||||
barChartInstance = echarts.init(barChart.value);
|
||||
}
|
||||
if (hourlyBarChart.value) {
|
||||
hourlyBarChartInstance = echarts.init(hourlyBarChart.value);
|
||||
}
|
||||
|
||||
window.addEventListener('resize', resizeCharts);
|
||||
}
|
||||
@@ -133,6 +214,7 @@ function updateCharts() {
|
||||
updateLineChart();
|
||||
updatePieChart();
|
||||
updateBarChart();
|
||||
updateHourlyBarChart(); // 新增:更新近24小时柱状图
|
||||
}
|
||||
|
||||
// 更新折线图
|
||||
@@ -512,11 +594,473 @@ function updateBarChart() {
|
||||
barChartInstance.setOption(option, true);
|
||||
}
|
||||
|
||||
// 更新近24小时每小时Token消耗柱状图
|
||||
function updateHourlyBarChart() {
|
||||
if (!hourlyBarChartInstance)
|
||||
return;
|
||||
|
||||
// 空数据状态
|
||||
if (hourlyUsageData.value.length === 0 || hourlyModels.value.length === 0) {
|
||||
const emptyOption = {
|
||||
graphic: [
|
||||
{
|
||||
type: 'group',
|
||||
left: 'center',
|
||||
top: 'center',
|
||||
children: [
|
||||
{
|
||||
type: 'rect',
|
||||
shape: {
|
||||
width: 160,
|
||||
height: 160,
|
||||
r: 12,
|
||||
},
|
||||
style: {
|
||||
fill: '#f5f7fa',
|
||||
stroke: '#e9ecef',
|
||||
lineWidth: 2,
|
||||
},
|
||||
left: -80,
|
||||
top: -80,
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
style: {
|
||||
text: '📊',
|
||||
fontSize: 48,
|
||||
x: -24,
|
||||
y: -40,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
style: {
|
||||
text: '暂无数据',
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
fill: '#909399',
|
||||
x: -36,
|
||||
y: 20,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
style: {
|
||||
text: '近24小时暂无使用记录',
|
||||
fontSize: 14,
|
||||
fill: '#c0c4cc',
|
||||
x: -80,
|
||||
y: 50,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
hourlyBarChartInstance.setOption(emptyOption, true);
|
||||
return;
|
||||
}
|
||||
|
||||
const hours = hourlyUsageData.value.map(item => {
|
||||
const date = new Date(item.hour);
|
||||
return `${date.getHours().toString().padStart(2, '0')}:00`;
|
||||
});
|
||||
const totalTokens = hourlyUsageData.value.map(item => item.totalTokens);
|
||||
|
||||
const isMobile = window.innerWidth < 768;
|
||||
|
||||
// 找出24小时中单小时模型数量最多的值
|
||||
const maxModelsPerHour = hourlyUsageData.value.reduce((max, hour) => {
|
||||
const count = hour.modelBreakdown?.length || 0;
|
||||
return Math.max(max, count);
|
||||
}, 0);
|
||||
|
||||
// 智能计算柱子宽度:基于最大模型数量动态计算
|
||||
// 柱子宽度 = 单元格宽度 / 最大模型数量 - 间距占比
|
||||
let barWidth: number;
|
||||
let barGap: number | string;
|
||||
let barCategoryGap: number | string;
|
||||
|
||||
// 估算每个时间段的可用宽度(像素)
|
||||
// 假设图表容器宽度,移动端约320-375px,桌面端约1000-1400px
|
||||
const containerWidth = isMobile ? 350 : 1200;
|
||||
const hourCount = hourlyUsageData.value.length;
|
||||
const categoryWidth = containerWidth / hourCount; // 每个小时类别的宽度
|
||||
|
||||
// 柱子间距配置(像素)
|
||||
const gapBetweenBars = isMobile ? 3 : 6; // 柱子之间的间距
|
||||
const gapBetweenCategories = isMobile ? 8 : 15; // 类别之间的间距
|
||||
|
||||
// 计算柱子宽度:可用宽度除以最大模型数
|
||||
// 可用宽度 = 类别宽度 - 类别间距 - (柱子数-1)*柱子间距
|
||||
const availableWidth = categoryWidth - gapBetweenCategories - ((maxModelsPerHour - 1) * gapBetweenBars);
|
||||
barWidth = Math.max(4, Math.floor(availableWidth / maxModelsPerHour)); // 最小4px
|
||||
|
||||
// 将间距转换为百分比以便ECharts自适应
|
||||
barGap = `${(gapBetweenBars / barWidth) * 100}%`;
|
||||
barCategoryGap = `${(gapBetweenCategories / categoryWidth) * 100}%`;
|
||||
|
||||
// 限制最大柱子宽度,避免太少模型时柱子过粗
|
||||
const maxBarWidth = isMobile ? 25 : 60;
|
||||
barWidth = Math.min(barWidth, maxBarWidth);
|
||||
|
||||
// 构建每个模型的数据系列(并排柱状图)
|
||||
const series = hourlyModels.value.map(({ modelId }, index) => {
|
||||
const data = hourlyUsageData.value.map(hour => {
|
||||
const modelData = hour.modelBreakdown?.find((m: any) => m.modelId === modelId);
|
||||
return modelData?.tokens || 0;
|
||||
});
|
||||
|
||||
// 根据最大模型数量决定是否显示标签
|
||||
const showLabel = maxModelsPerHour <= 4 && !isMobile;
|
||||
|
||||
return {
|
||||
name: modelId,
|
||||
type: 'bar',
|
||||
barWidth,
|
||||
barGap,
|
||||
barCategoryGap,
|
||||
data,
|
||||
itemStyle: {
|
||||
color: modelColors.value[modelId],
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
borderWidth: 0,
|
||||
},
|
||||
label: {
|
||||
show: showLabel,
|
||||
position: 'top',
|
||||
formatter: (params: any) => {
|
||||
if (params.value === 0)
|
||||
return '';
|
||||
return params.value >= 1000 ? `${(params.value / 1000).toFixed(1)}k` : params.value.toString();
|
||||
},
|
||||
fontSize: isMobile ? 9 : 11,
|
||||
color: '#666',
|
||||
},
|
||||
emphasis: {
|
||||
focus: 'series',
|
||||
itemStyle: {
|
||||
shadowBlur: 8,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.2)',
|
||||
},
|
||||
},
|
||||
// 确保系列按总使用量排序的顺序
|
||||
seriesIndex: index,
|
||||
};
|
||||
});
|
||||
|
||||
const option = {
|
||||
graphic: [],
|
||||
calculable: true,
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
label: {
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
formatter: (params: any) => {
|
||||
if (!params || params.length === 0)
|
||||
return '';
|
||||
const hour = params[0].axisValue;
|
||||
|
||||
// 过滤出有数据的模型并按token使用量降序
|
||||
const activeParams = params
|
||||
.filter((p: any) => p.value > 0)
|
||||
.sort((a: any, b: any) => b.value - a.value);
|
||||
|
||||
if (activeParams.length === 0)
|
||||
return `<div style="padding: 8px 12px; font-size: 13px; color: #999;">${hour}<br/>暂无数据</div>`;
|
||||
|
||||
// 计算总token
|
||||
const totalTokens = activeParams.reduce((sum: number, p: any) => sum + p.value, 0);
|
||||
|
||||
let result = `<div style="margin-bottom: 10px; padding-bottom: 8px; border-bottom: 1px solid #eee;">
|
||||
<div style="font-size: 14px; font-weight: bold; color: #333;">${hour}</div>
|
||||
<div style="font-size: 12px; color: #999; margin-top: 2px;">总计: ${totalTokens.toLocaleString()} tokens</div>
|
||||
</div>`;
|
||||
|
||||
// 限制显示的模型数量(最多显示前8个)
|
||||
const maxDisplay = 8;
|
||||
const displayParams = activeParams.slice(0, maxDisplay);
|
||||
|
||||
displayParams.forEach((param: any, index: number) => {
|
||||
const percent = ((param.value / totalTokens) * 100).toFixed(1);
|
||||
result += `<div style="display: flex; align-items: center; justify-content: space-between; gap: ${isMobile ? 12 : 20}px; margin-bottom: ${index === displayParams.length - 1 && activeParams.length > maxDisplay ? 6 : 4}px;">
|
||||
<span style="display: flex; align-items: center;">
|
||||
<span style="display: inline-block; width: 10px; height: 10px; border-radius: 2px; background: ${param.color}; margin-right: 8px;"></span>
|
||||
<span style="font-size: ${isMobile ? 11 : 12}px; color: #555;">${param.seriesName}</span>
|
||||
</span>
|
||||
<span style="display: flex; align-items: center; gap: 8px;">
|
||||
<span style="font-size: ${isMobile ? 11 : 12}px; font-weight: 600; color: #333;">${param.value.toLocaleString()}</span>
|
||||
<span style="font-size: ${isMobile ? 10 : 11}px; color: #999;">${percent}%</span>
|
||||
</span>
|
||||
</div>`;
|
||||
});
|
||||
|
||||
// 如果有更多模型,显示提示
|
||||
if (activeParams.length > maxDisplay) {
|
||||
result += `<div style="margin-top: 6px; padding-top: 6px; border-top: 1px dashed #eee; font-size: 11px; color: #999; text-align: center;">
|
||||
还有 ${activeParams.length - maxDisplay} 个模型未显示
|
||||
</div>`;
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
confine: true,
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.98)',
|
||||
borderColor: '#e9ecef',
|
||||
borderWidth: 1,
|
||||
borderRadius: 8,
|
||||
padding: [12, 16],
|
||||
textStyle: {
|
||||
fontSize: 13,
|
||||
color: '#333',
|
||||
},
|
||||
position(point: any, params: any, dom: any, rect: any, size: any) {
|
||||
if (isMobile) {
|
||||
return ['50%', '10%'];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
},
|
||||
toolbox: {
|
||||
show: !isMobile,
|
||||
feature: {
|
||||
dataZoom: {
|
||||
yAxisIndex: 'none',
|
||||
title: {
|
||||
zoom: '区域缩放',
|
||||
back: '还原缩放',
|
||||
},
|
||||
},
|
||||
brush: {
|
||||
type: ['lineX', 'clear'],
|
||||
title: {
|
||||
lineX: '横向选择',
|
||||
clear: '清除选择',
|
||||
},
|
||||
},
|
||||
restore: {
|
||||
show: true,
|
||||
title: '还原',
|
||||
},
|
||||
saveAsImage: {
|
||||
show: true,
|
||||
title: '保存为图片',
|
||||
pixelRatio: 2,
|
||||
},
|
||||
},
|
||||
right: 15,
|
||||
top: 5,
|
||||
itemSize: 14,
|
||||
iconStyle: {
|
||||
borderColor: '#667eea',
|
||||
borderWidth: 1.5,
|
||||
},
|
||||
emphasis: {
|
||||
iconStyle: {
|
||||
borderColor: '#764ba2',
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
show: true,
|
||||
position: 'bottom',
|
||||
formatter: (param: any) => {
|
||||
return param.title;
|
||||
},
|
||||
textStyle: {
|
||||
fontSize: 11,
|
||||
},
|
||||
},
|
||||
},
|
||||
// 区域选框缩放配置
|
||||
brush: {
|
||||
id: 'brush',
|
||||
xAxisIndex: 0,
|
||||
link: ['x'],
|
||||
transform: {
|
||||
type: 'bar',
|
||||
},
|
||||
throttleType: 'debounce',
|
||||
throttleDelay: 300,
|
||||
removeOnClick: true,
|
||||
brushLink: 'all',
|
||||
brushType: false,
|
||||
inBrush: {
|
||||
opacity: 1,
|
||||
},
|
||||
outOfBrush: {
|
||||
opacity: 0.3,
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
show: true,
|
||||
type: hourlyModels.value.length > 8 || isMobile ? 'scroll' : 'plain',
|
||||
top: isMobile ? '0%' : '3%',
|
||||
left: 'center',
|
||||
itemWidth: 14,
|
||||
itemHeight: 10,
|
||||
itemGap: isMobile ? 8 : 10,
|
||||
textStyle: {
|
||||
fontSize: isMobile ? 10 : 12,
|
||||
color: '#666',
|
||||
},
|
||||
data: hourlyModels.value.map(({ modelId }) => ({
|
||||
name: modelId,
|
||||
icon: 'rect',
|
||||
})),
|
||||
pageTextStyle: {
|
||||
color: '#999',
|
||||
},
|
||||
pageIconColor: '#667eea',
|
||||
pageIconInactiveColor: '#ccc',
|
||||
pageButtonItemGap: 5,
|
||||
},
|
||||
grid: {
|
||||
top: maxModelsPerHour > 8 ? '15%' : '12%',
|
||||
left: '1%',
|
||||
right: isMobile ? '8%' : '10%',
|
||||
bottom: isMobile ? '15%' : '12%',
|
||||
containLabel: true,
|
||||
},
|
||||
dataZoom: [
|
||||
{
|
||||
show: !isMobile,
|
||||
start: hourlyUsageData.value.length > 12 ? 100 - Math.round((12 / hourlyUsageData.value.length) * 100) : 80,
|
||||
end: 100,
|
||||
xAxisIndex: [0],
|
||||
bottom: '3%',
|
||||
height: 18,
|
||||
borderColor: 'transparent',
|
||||
fillerColor: 'rgba(102, 126, 234, 0.2)',
|
||||
handleStyle: {
|
||||
color: '#667eea',
|
||||
},
|
||||
textStyle: {
|
||||
color: '#999',
|
||||
fontSize: 11,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'inside',
|
||||
start: hourlyUsageData.value.length > 12 ? 100 - Math.round((12 / hourlyUsageData.value.length) * 100) : 80,
|
||||
end: 100,
|
||||
xAxisIndex: [0],
|
||||
zoomOnMouseWheel: true,
|
||||
moveOnMouseMove: true,
|
||||
moveOnMouseWheel: false,
|
||||
},
|
||||
{
|
||||
show: !isMobile && maxModelsPerHour > 3,
|
||||
yAxisIndex: [0],
|
||||
filterMode: 'empty',
|
||||
width: 28,
|
||||
height: '70%',
|
||||
showDataShadow: false,
|
||||
left: '96%',
|
||||
borderColor: 'transparent',
|
||||
fillerColor: 'rgba(102, 126, 234, 0.15)',
|
||||
handleStyle: {
|
||||
color: '#667eea',
|
||||
},
|
||||
},
|
||||
],
|
||||
// 区域选框缩放配置(配合calculable使用)
|
||||
brush: {
|
||||
id: 'brush',
|
||||
xAxisIndex: 0,
|
||||
link: ['x'],
|
||||
throttleType: 'debounce',
|
||||
throttleDelay: 300,
|
||||
removeOnClick: true,
|
||||
brushLink: 'all',
|
||||
brushType: false,
|
||||
inBrush: {
|
||||
opacity: 1,
|
||||
},
|
||||
outOfBrush: {
|
||||
opacity: 0.15,
|
||||
},
|
||||
// 选框样式
|
||||
brushStyle: {
|
||||
borderWidth: 1,
|
||||
color: 'rgba(102, 126, 234, 0.15)',
|
||||
borderColor: '#667eea',
|
||||
},
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: hours,
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#e9ecef',
|
||||
width: 1,
|
||||
},
|
||||
},
|
||||
axisTick: {
|
||||
alignWithLabel: true,
|
||||
length: 4,
|
||||
},
|
||||
axisLabel: {
|
||||
interval: isMobile ? 3 : 0,
|
||||
rotate: isMobile ? 45 : 0,
|
||||
fontSize: isMobile ? 10 : 12,
|
||||
color: '#666',
|
||||
fontFamily: 'monospace',
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: 'Token用量',
|
||||
nameTextStyle: {
|
||||
fontSize: isMobile ? 11 : 12,
|
||||
color: '#999',
|
||||
padding: [0, 0, 0, 0],
|
||||
},
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: '#e9ecef',
|
||||
width: 1,
|
||||
},
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: {
|
||||
fontSize: isMobile ? 10 : 12,
|
||||
color: '#666',
|
||||
formatter: (value: number) => {
|
||||
if (value >= 1000000)
|
||||
return `${(value / 1000000).toFixed(1)}M`;
|
||||
if (value >= 1000)
|
||||
return `${(value / 1000).toFixed(0)}K`;
|
||||
return value.toString();
|
||||
},
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#f0f0f0',
|
||||
type: 'dashed',
|
||||
width: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
series,
|
||||
};
|
||||
|
||||
hourlyBarChartInstance.setOption(option, true);
|
||||
}
|
||||
|
||||
// 调整图表大小
|
||||
function resizeCharts() {
|
||||
lineChartInstance?.resize();
|
||||
pieChartInstance?.resize();
|
||||
barChartInstance?.resize();
|
||||
hourlyBarChartInstance?.resize();
|
||||
}
|
||||
|
||||
// 切换全屏
|
||||
@@ -544,6 +1088,7 @@ onBeforeUnmount(() => {
|
||||
lineChartInstance?.dispose();
|
||||
pieChartInstance?.dispose();
|
||||
barChartInstance?.dispose();
|
||||
hourlyBarChartInstance?.dispose();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -605,6 +1150,84 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 近24小时每小时Token消耗柱状图 -->
|
||||
<el-card v-loading="loading" class="chart-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="card-title">⏰ 近24小时每小时Token消耗</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="chart-container">
|
||||
<div ref="hourlyBarChart" class="chart hourly-bar-chart" />
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 今日各模型使用量卡片列表 -->
|
||||
<el-card v-loading="loading" class="chart-card today-model-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="card-title">📋 今日各模型使用量统计</span>
|
||||
<el-tag v-if="todayModelUsageData.length > 0" type="success" effect="plain">
|
||||
共 {{ todayModelUsageData.length }} 个模型
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<div class="today-model-cards">
|
||||
<div v-if="todayModelUsageData.length === 0" class="empty-state">
|
||||
<div class="empty-icon">📊</div>
|
||||
<div class="empty-text">暂无数据</div>
|
||||
<div class="empty-hint">今日暂无模型使用记录</div>
|
||||
</div>
|
||||
<div v-else class="model-cards-grid">
|
||||
<div
|
||||
v-for="item in todayModelUsageData"
|
||||
:key="item.modelId"
|
||||
class="model-card"
|
||||
:style="{ borderColor: modelColors[item.modelId] || '#e9ecef' }"
|
||||
>
|
||||
<div class="model-card-header">
|
||||
<div class="model-icon-wrapper">
|
||||
<img
|
||||
v-if="item.iconUrl"
|
||||
:src="item.iconUrl"
|
||||
:alt="item.modelId"
|
||||
class="model-logo"
|
||||
@error="(e) => { (e.target as HTMLImageElement).style.display = 'none'; (e.target as HTMLImageElement).nextElementSibling?.classList.remove('fallback-icon'); }"
|
||||
/>
|
||||
<div v-else class="model-logo-fallback" :style="{ background: modelColors[item.modelId] || '#667eea' }">
|
||||
{{ item.modelId.charAt(0).toUpperCase() }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="model-info">
|
||||
<div class="model-name" :title="item.modelId">{{ item.modelId }}</div>
|
||||
<div class="model-usage-count">
|
||||
<el-icon><i-ep-data-analysis /></el-icon>
|
||||
使用 {{ item.usageCount }} 次
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="model-card-footer">
|
||||
<div class="token-info">
|
||||
<span class="token-label">Token消耗</span>
|
||||
<span class="token-value" :style="{ color: modelColors[item.modelId] || '#667eea' }">
|
||||
{{ item.totalTokens.toLocaleString() }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="token-chart-mini">
|
||||
<div
|
||||
class="token-bar"
|
||||
:style="{
|
||||
width: `${Math.min(100, (item.totalTokens / Math.max(...todayModelUsageData.map((d: any) => d.totalTokens))) * 100)}%`,
|
||||
background: `linear-gradient(90deg, ${modelColors[item.modelId] || '#667eea'} 0%, ${modelColors[item.modelId] || '#764ba2'} 100%)`,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card v-loading="loading" class="chart-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
@@ -797,6 +1420,168 @@ onBeforeUnmount(() => {
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.hourly-bar-chart {
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
/* 今日模型使用量卡片样式 */
|
||||
.today-model-card {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.today-model-cards {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 64px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #909399;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.empty-hint {
|
||||
font-size: 14px;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.model-cards-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.model-card {
|
||||
background: #fff;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 12px;
|
||||
padding: 14px;
|
||||
transition: all 0.25s ease;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
.model-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
border-color: currentColor;
|
||||
}
|
||||
|
||||
.model-card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.model-icon-wrapper {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.model-logo {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
border-radius: 8px;
|
||||
padding: 2px;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.model-logo-fallback {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 8px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.model-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.model-name {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 2px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.model-usage-count {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.model-card-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.token-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.token-label {
|
||||
font-size: 10px;
|
||||
color: #aaa;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.token-value {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.token-chart-mini {
|
||||
flex: 1;
|
||||
height: 4px;
|
||||
background: #f0f0f0;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
margin-left: 12px;
|
||||
align-self: center;
|
||||
max-width: 80px;
|
||||
}
|
||||
|
||||
.token-bar {
|
||||
height: 100%;
|
||||
border-radius: 2px;
|
||||
transition: width 0.6s ease;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.usage-statistics {
|
||||
padding: 12px;
|
||||
@@ -863,6 +1648,45 @@ onBeforeUnmount(() => {
|
||||
height: 400px !important;
|
||||
}
|
||||
|
||||
.hourly-bar-chart {
|
||||
height: 340px !important;
|
||||
}
|
||||
|
||||
.model-cards-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.model-card {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.model-icon-wrapper {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.model-logo-fallback {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.model-name {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.model-usage-count {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.token-value {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.token-chart-mini {
|
||||
max-width: 60px;
|
||||
height: 3px;
|
||||
}
|
||||
|
||||
.usage-statistics.fullscreen-mode {
|
||||
padding: 12px;
|
||||
}
|
||||
@@ -901,6 +1725,45 @@ onBeforeUnmount(() => {
|
||||
height: 380px !important;
|
||||
}
|
||||
|
||||
.hourly-bar-chart {
|
||||
height: 300px !important;
|
||||
}
|
||||
|
||||
.model-cards-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.model-card {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.model-icon-wrapper {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.model-logo-fallback {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.model-name {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.model-usage-count {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.token-value {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.token-chart-mini {
|
||||
max-width: 60px;
|
||||
height: 3px;
|
||||
}
|
||||
|
||||
.chart-card {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user