feat: integrate platform backend and application interfaces

This commit is contained in:
Qiufeng
2026-09-02 21:09:47 +08:00
parent cd9ac1af70
commit b16390dd41
648 changed files with 102488 additions and 17737 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

@@ -1,5 +0,0 @@
<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="49" height="49" rx="24.5" fill="#0052D9" stroke="none"/>
<path d="M23.9219 31H26.0859L22.0781 19.7266H19.8125L15.8125 31H17.8516L18.8203 28.1172H22.9688L23.9219 31ZM20.8359 21.875H20.9688L22.5 26.5234H19.2891L20.8359 21.875ZM30.6328 29.6172C29.7734 29.6172 29.1562 29.1875 29.1562 28.4688C29.1562 27.7734 29.6641 27.3828 30.75 27.3125L32.6797 27.1875V27.8672C32.6797 28.8594 31.8047 29.6172 30.6328 29.6172ZM30.0625 31.1406C31.1797 31.1406 32.1172 30.6562 32.5938 29.8281H32.7266V31H34.5938V25.1641C34.5938 23.3516 33.3594 22.2812 31.1641 22.2812C29.1328 22.2812 27.7188 23.2422 27.5625 24.75H29.3906C29.5703 24.1719 30.1797 23.8594 31.0703 23.8594C32.1172 23.8594 32.6797 24.3281 32.6797 25.1641V25.8828L30.4766 26.0156C28.3984 26.1406 27.2344 27.0312 27.2344 28.5781C27.2344 30.1406 28.4141 31.1406 30.0625 31.1406Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 979 B

@@ -1,5 +0,0 @@
<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="49" height="49" rx="24.5" fill="#0594FA" stroke="none"/>
<path d="M20.6641 31C23.0703 31 24.5391 29.7734 24.5391 27.7969C24.5391 26.3281 23.4922 25.2344 21.9766 25.0938V24.9531C23.1094 24.7734 23.9844 23.7266 23.9844 22.5391C23.9844 20.8047 22.6953 19.7266 20.5547 19.7266H15.8438V31H20.6641ZM17.8594 21.3281H20.0625C21.2812 21.3281 21.9922 21.9062 21.9922 22.8984C21.9922 23.9141 21.2344 24.4688 19.8047 24.4688H17.8594V21.3281ZM17.8594 29.3984V25.9219H20.125C21.6641 25.9219 22.4766 26.5156 22.4766 27.6406C22.4766 28.7891 21.6875 29.3984 20.2031 29.3984H17.8594ZM31.2812 31.1406C33.4375 31.1406 34.7891 29.4453 34.7891 26.7266C34.7891 23.9922 33.4453 22.3125 31.2812 22.3125C30.1094 22.3125 29.125 22.8828 28.6797 23.8125H28.5469V19.1484H26.6094V31H28.4766V29.6484H28.6094C29.0938 30.5859 30.0859 31.1406 31.2812 31.1406ZM30.6719 23.9609C31.9922 23.9609 32.7969 25.0078 32.7969 26.7266C32.7969 28.4453 32 29.4922 30.6719 29.4922C29.3438 29.4922 28.5156 28.4297 28.5156 26.7266C28.5156 25.0234 29.3516 23.9609 30.6719 23.9609Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

@@ -1,5 +0,0 @@
<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="49" height="49" rx="24.5" fill="#7587DB" stroke="none"/>
<path d="M21.1172 31.2812C23.7188 31.2812 25.625 29.7266 25.8906 27.4219H23.9062C23.6328 28.6875 22.5469 29.4844 21.1172 29.4844C19.1953 29.4844 18 27.9062 18 25.3594C18 22.8203 19.1953 21.2422 21.1094 21.2422C22.5312 21.2422 23.6172 22.1172 23.8984 23.4688H25.8828C25.6484 21.1172 23.6719 19.4453 21.1094 19.4453C17.9141 19.4453 15.9375 21.7031 15.9375 25.3672C15.9375 29.0156 17.9219 31.2812 21.1172 31.2812ZM35.3125 25.3281C35.1094 23.5312 33.7812 22.2812 31.5859 22.2812C29.0156 22.2812 27.5078 23.9297 27.5078 26.7031C27.5078 29.5156 29.0234 31.1719 31.5938 31.1719C33.7578 31.1719 35.1016 29.9688 35.3125 28.1797H33.4688C33.2656 29.0703 32.5938 29.5469 31.5859 29.5469C30.2656 29.5469 29.4688 28.5 29.4688 26.7031C29.4688 24.9297 30.2578 23.9062 31.5859 23.9062C32.6484 23.9062 33.2891 24.5 33.4688 25.3281H35.3125Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

@@ -1,5 +0,0 @@
<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="49" height="49" rx="24.5" fill="#00A870" stroke="none"/>
<path d="M15.875 19.7266V31H20.1016C23.4766 31 25.4062 28.9297 25.4062 25.3125C25.4062 21.7734 23.4531 19.7266 20.1016 19.7266H15.875ZM17.8906 21.4688H19.8359C22.0469 21.4688 23.3516 22.8828 23.3516 25.3438C23.3516 27.8594 22.0781 29.2578 19.8359 29.2578H17.8906V21.4688ZM30.5938 31.1406C31.7812 31.1406 32.7656 30.5859 33.25 29.6484H33.3828V31H35.2578V19.1484H33.3203V23.8125H33.1875C32.7344 22.875 31.7656 22.3125 30.5938 22.3125C28.4375 22.3125 27.0781 24.0156 27.0781 26.7188C27.0781 29.4375 28.4297 31.1406 30.5938 31.1406ZM31.1953 23.9609C32.5234 23.9609 33.3438 25.0234 33.3438 26.7266C33.3438 28.4453 32.5312 29.4922 31.1953 29.4922C29.8672 29.4922 29.0625 28.4531 29.0625 26.7266C29.0625 25.0078 29.875 23.9609 31.1953 23.9609Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 958 B

@@ -1,33 +0,0 @@
<svg width="200" height="140" viewBox="0 0 200 140" fill="none" xmlns="http://www.w3.org/2000/svg">
<g mask="url(#mask0_22_990)">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M144.569 105.61L96.5692 133.322L48.5693 105.61V83.7121L96.569 55.9995L144.569 83.7122V105.61Z"
fill="#97A3B7" />
<g filter="url(#filter0_f_22_990)">
<rect x="-3" y="33.9995" width="80" height="60" fill="#E3E6EB" />
</g>
<g filter="url(#filter1_f_22_990)">
<rect x="97" y="97.9995" width="80" height="60" fill="#E3E6EB" />
</g>
</g>
<mask id="mask1_22_990" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="53" y="16" width="86" height="69">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M113.357 42.715L129.829 33.2059C128.859 32.4995 127.789 31.823 126.643 31.1615C121.268 28.0584 114.723 26.0153 107.758 25.023C103.549 19.4606 97.5775 16.1248 90.4344 16.1945C83.6788 16.2821 74.9482 21.9412 68.9271 30.5602C68.096 30.975 67.2847 31.4099 66.4949 31.8659C52.1168 40.1664 49.5542 52.6155 59.0218 61.9309C57.9871 56.1259 58.712 51.0657 62.1231 45.7161C62.0653 46.3482 61.9127 50.143 61.8906 50.7834C61.2209 69.6969 76.9107 84.8409 88.03 84.7113C96.4806 84.6119 103.595 79.6976 108.349 72.0797C114.563 70.8487 120.438 68.786 125.443 65.8968C138.919 58.1167 142.01 46.7146 134.547 37.629L117.948 47.2113C119.71 50.8655 117.997 55.034 112.87 57.9936C107.744 60.9532 100.523 61.9424 94.1928 60.9252C91.3499 60.4563 88.6706 59.5834 86.4524 58.3029L86.4042 58.275L113.357 42.715ZM78.6546 53.7727C72.5285 49.7965 72.9717 43.5469 79.8498 39.5762C86.7276 35.6056 97.5532 35.3496 104.441 38.8864L78.6546 53.7727ZM93.5561 18.1703C98.1657 18.1302 102.284 20.5752 105.496 24.7401C97.0486 23.8219 88.1122 24.4143 79.9732 26.5054C83.672 21.3809 88.444 18.2301 93.5561 18.1703ZM91.3238 81.6169C85.471 81.685 80.3525 77.691 76.9473 71.2848C85.7921 73.6267 95.8719 74.0599 105.374 72.6022C101.618 78.1222 96.6601 81.5531 91.3238 81.6169Z"
fill="currentcolor" />
</mask>
<g mask="url(#mask1_22_990)">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M113.357 42.715L129.829 33.2059C128.859 32.4995 127.789 31.823 126.643 31.1615C121.268 28.0584 114.723 26.0153 107.758 25.023C103.549 19.4606 97.5775 16.1248 90.4344 16.1945C83.6788 16.2821 74.9482 21.9412 68.9271 30.5602C68.096 30.975 67.2847 31.4099 66.4949 31.8659C52.1168 40.1664 49.5542 52.6155 59.0218 61.9309C57.9871 56.1259 58.712 51.0657 62.1231 45.7161C62.0653 46.3482 61.9127 50.143 61.8906 50.7834C61.2209 69.6969 76.9107 84.8409 88.03 84.7113C96.4806 84.6119 103.595 79.6976 108.349 72.0797C114.563 70.8487 120.438 68.786 125.443 65.8968C138.919 58.1167 142.01 46.7146 134.547 37.629L117.948 47.2113C119.71 50.8655 117.997 55.034 112.87 57.9936C107.744 60.9532 100.523 61.9424 94.1928 60.9252C91.3499 60.4563 88.6706 59.5834 86.4524 58.3029L86.4042 58.275L113.357 42.715ZM78.6546 53.7727C72.5285 49.7965 72.9717 43.5469 79.8498 39.5762C86.7276 35.6056 97.5532 35.3496 104.441 38.8864L78.6546 53.7727ZM93.5561 18.1703C98.1657 18.1302 102.284 20.5752 105.496 24.7401C97.0486 23.8219 88.1122 24.4143 79.9732 26.5054C83.672 21.3809 88.444 18.2301 93.5561 18.1703ZM91.3238 81.6169C85.471 81.685 80.3525 77.691 76.9473 71.2848C85.7921 73.6267 95.8719 74.0599 105.374 72.6022C101.618 78.1222 96.6601 81.5531 91.3238 81.6169Z"
fill="currentcolor" />
<g opacity="0.3" filter="url(#filter2_f_22_990)">
<path d="M96.569 55.9995L144.569 83.7122V139.138L96.569 166.85L48.5692 139.138V83.7122L96.569 55.9995Z"
fill="#97A3B7" />
</g>
</g>
<ellipse cx="155" cy="78" rx="22" ry="22" transform="rotate(180 155 78)" fill="white" stroke="black" />
<path d="M155 83L155 65" stroke="black" />
<rect x="155" y="87" width="0.00390625" height="0.00390625" fill="#C4C4C4" stroke="black" stroke-width="2"
stroke-linejoin="round" />
<path d="M96.5693 112L96.5693 87.9995" stroke="white" stroke-width="2" />
<path d="M86.5693 97.9995L96.5693 87.9995L106.569 97.9995" stroke="white" stroke-width="2" />
</svg>

Before

Width:  |  Height:  |  Size: 3.9 KiB

@@ -1,49 +0,0 @@
<svg width="200" height="140" viewBox="0 0 200 140" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_216_313" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="80" y="9" width="78" height="88">
<path d="M119 9L157.105 31V75L119 97L80.8949 75V31L119 9Z" fill="currentColor"/>
</mask>
<g mask="url(#mask0_216_313)">
<path d="M119 9L157.105 31V75L119 97L80.8949 75V31L119 9Z" fill="currentColor"/>
<path opacity="0.9" d="M119 -35L157.105 -13V31L119 53L80.8949 31V-13L119 -35Z" fill="currentColor" />
<g opacity="0.3" filter="url(#filter0_f_216_313)">
<path d="M68 48L106.105 70V114L68 136L29.8949 114V70L68 48Z" fill="#97A3B7"/>
</g>
</g>
<mask id="mask1_216_313" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="29" y="48" width="78" height="88">
<path d="M68 48L106.105 70V114L68 136L29.8949 114V70L68 48Z" fill="#97A3B7"/>
</mask>
<g mask="url(#mask1_216_313)">
<path d="M68 48L106.105 70V114L68 136L29.8949 114V70L68 48Z" fill="#97A3B7"/>
<g filter="url(#filter1_f_216_313)">
<rect x="46.3906" y="92" width="80" height="60" fill="#E3E6EB"/>
</g>
<g filter="url(#filter2_f_216_313)">
<rect y="23" width="80" height="60" fill="#E3E6EB"/>
</g>
</g>
<path d="M41.8984 86.2866L44.7267 87.9197L44.727 94.4515L41.8987 92.8185L41.8984 86.2866Z" fill="white"/>
<path d="M53.9189 93.2273L56.7472 94.8603L56.7474 101.392L53.9191 99.7591L53.9189 93.2273Z" fill="white"/>
<path d="M44.7276 107.515L41.8994 105.882L41.8995 109.148L44.7278 110.781L44.7276 107.515L53.9195 112.823L53.9196 116.088L56.7479 117.721L56.7478 114.455L53.9195 112.823L53.9195 109.557L44.7275 104.249L44.7276 107.515Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M108.348 23.4792C106.188 25.9904 106.535 29.3829 109.395 31.5604C112.66 34.0461 117.963 34.007 121.24 31.4731C124.516 28.9392 124.526 24.8699 121.261 22.3841C118.401 20.2066 113.977 19.9666 110.721 21.6439L115.923 25.6047L113.55 27.4399L108.348 23.4792Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M120.865 33.0087L129.83 39.8346L132.203 37.9994L123.238 31.1735C122.917 31.521 122.554 31.8531 122.149 32.1657C121.745 32.4784 121.315 32.7594 120.865 33.0087ZM119.662 32.0932C120.123 31.8557 120.561 31.5798 120.967 31.2655C121.373 30.9513 121.73 30.6134 122.035 30.2579L122.035 30.2579C121.73 30.6134 121.373 30.9513 120.967 31.2655C120.561 31.5798 120.123 31.8557 119.662 32.0931L119.662 32.0932Z" fill="white"/>
<path d="M144 70L168.249 112H119.751L144 70Z" fill="white" stroke="#181818"/>
<path d="M144 100L144 82" stroke="#181818"/>
<path d="M144 105H144.004V105.004H144V105Z" stroke="#181818" stroke-width="2" stroke-linejoin="round"/>
<defs>
<filter id="filter0_f_216_313" x="23.8949" y="42" width="88.2102" height="100" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="3" result="effect1_foregroundBlur_216_313"/>
</filter>
<filter id="filter1_f_216_313" x="-3.60938" y="42" width="180" height="160" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="25" result="effect1_foregroundBlur_216_313"/>
</filter>
<filter id="filter2_f_216_313" x="-50" y="-27" width="180" height="160" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="25" result="effect1_foregroundBlur_216_313"/>
</filter>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 3.7 KiB

@@ -1,23 +0,0 @@
<svg width="200" height="140" viewBox="0 0 200 140" fill="none" xmlns="http://www.w3.org/2000/svg">
<g mask="url(#mask0_21_716)">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M33 46.843L96.3214 119L159.643 46.843C142.742 31.9998 120.583 23 96.3214 23C72.0601 23 49.9009 31.9998 33 46.843Z"
fill="#97A3B7" />
<g filter="url(#filter0_f_21_716)">
<rect x="95" y="21" width="80" height="60" fill="#E3E6EB" />
</g>
<g filter="url(#filter1_f_21_716)">
<rect x="-7" y="43" width="80" height="60" fill="#E3E6EB" />
</g>
</g>
<path
d="M72.8122 63.6882L69.6548 66.8455L75.9009 73.0916C71.2469 75.1648 66.9663 77.925 63.188 81.2433L96.3213 119L108.234 105.425L114.647 111.837L117.804 108.68L80.4504 71.3261C80.4505 71.3261 80.4503 71.3261 80.4504 71.3261L72.8122 63.6882Z"
fill="currentcolor" />
<path
d="M129.455 81.2433L114.137 98.6982L85.3974 69.9585C88.9142 69.1786 92.5697 68.7674 96.3213 68.7674C109.016 68.7674 120.611 73.4766 129.455 81.2433Z"
fill="currentcolor" />
<path
d="M152 21.822L156.867 38.875L157 39.3405L157.469 39.2228L174.671 34.911L162.336 47.6522L161.999 48L162.336 48.3478L174.671 61.089L157.469 56.7772L157 56.6595L156.867 57.125L152 74.178L147.133 57.125L147 56.6595L146.531 56.7772L129.329 61.089L141.664 48.3478L142.001 48L141.664 47.6522L129.329 34.911L146.531 39.2228L147 39.3405L147.133 38.875L152 21.822Z"
fill="white" stroke="black" />
<path d="M101 31L90 42L101 53L93 61" stroke="white" stroke-width="2" />
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

+12 -2
View File
@@ -47,6 +47,8 @@
.boardItemIcon {
opacity: 0.6;
width: 24px;
height: 24px;
}
.boardItemDesc {
@@ -59,12 +61,20 @@
.trendIcon {
border-radius: 50%;
width: 16px;
height: 16px;
width: 20px;
height: 20px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: 8px;
margin-right: 8px;
}
.trendIcon > svg {
width: 20px;
height: 20px;
}
.trendIconUp {
background: var(--td-error-color-2);
}
+3 -13
View File
@@ -1,6 +1,6 @@
import React from 'react';
import { ChevronRightIcon } from 'tdesign-icons-react';
import { Card } from 'tdesign-react';
import { ChevronRightIcon, TrendDownIcon, TrendUpIcon } from 'components/LineIcons';
import { Card } from '@miragenflow/ui';
import classnames from 'classnames';
import Style from './index.module.less';
@@ -33,17 +33,7 @@ export const TrendIcon = ({ trend, trendNum }: { trend?: ETrend; trendNum?: stri
[Style.trendIconDown]: trend === ETrend.down,
})}
>
{trend === ETrend.up ? (
<svg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path d='M4.5 8L8 4.5L11.5 8' stroke='currentColor' strokeWidth='1.5' />
<path d='M8 5V12' stroke='currentColor' strokeWidth='1.5' />
</svg>
) : (
<svg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path d='M11.5 8L8 11.5L4.5 8' stroke='currentColor' strokeWidth='1.5' />
<path d='M8 11L8 4' stroke='currentColor' strokeWidth='1.5' />
</svg>
)}
{trend === ETrend.up ? <TrendUpIcon /> : <TrendDownIcon />}
</div>
{trendNum}
</div>
+17 -8
View File
@@ -1,19 +1,28 @@
import React from 'react';
import { DateRangePicker, DateRangeValue } from 'tdesign-react';
import { DateRangePicker, type DateRangeValue } from '@miragenflow/ui/date-picker';
import dayjs from 'dayjs';
const RECENT_7_DAYS: DateRangeValue = [
dayjs().subtract(7, 'day').format('YYYY-MM-DD'),
dayjs().subtract(1, 'day').format('YYYY-MM-DD'),
];
type DatePickerProps = {
value?: DateRangeValue;
onChange?: (value: DateRangeValue) => void;
className?: string;
};
const LastWeekDatePicker = (onChange: (value: DateRangeValue) => void) => (
const LastWeekDatePicker = ({ value, onChange, className }: DatePickerProps) => (
<DateRangePicker
className={className}
mode='date'
placeholder={['开始时间', '结束时间']}
value={RECENT_7_DAYS}
value={value ?? undefined}
format='YYYY-MM-DD'
onChange={(value) => onChange(value)}
allowInput={false}
clearable
presets={{
'今天': [dayjs().toDate(), dayjs().toDate()],
'近 7 天': [dayjs().subtract(6, 'day').toDate(), dayjs().toDate()],
'近 30 天': [dayjs().subtract(29, 'day').toDate(), dayjs().toDate()],
}}
onChange={(nextValue) => onChange?.(nextValue)}
/>
);
+42 -5
View File
@@ -1,5 +1,10 @@
import React, { memo } from 'react';
import { Button } from 'tdesign-react';
import { Space } from '@miragenflow/ui/space';
import { useLocation, useNavigate } from 'react-router-dom';
import { MiragenFlowButton } from '@miragenflow/ui';
import { useAppDispatch, useAppSelector } from 'modules/store';
import { logout } from 'modules/user';
import { adminLogout } from 'services/platform';
import Light403Icon from 'assets/svg/assets-result-403.svg?component';
import Light404Icon from 'assets/svg/assets-result-404.svg?component';
@@ -16,34 +21,66 @@ interface IErrorPageProps {
code: ECode;
title?: string;
desc?: string;
requiredScope?: string;
}
const errorInfo = {
[ECode.forbidden]: {
title: '403 Forbidden',
title: '403 无权限',
desc: '抱歉,您无权限访问此页面',
icon: <Light403Icon />,
},
[ECode.notFount]: {
title: '404 Not Found',
title: '404 页面不存在',
desc: '抱歉,您访问的页面不存在。',
icon: <Light404Icon />,
},
[ECode.error]: {
title: '500 Internal Server Error',
title: '500 服务异常',
desc: '抱歉,服务器出错啦!',
icon: <Light500Icon />,
},
};
const permissionLabels: Record<string, string> = {
super_admin: '超级管理员',
'admin:read': '查看后台',
'admin:write': '管理后台',
'admin:finance': '财务管理',
'admin:security': '安全管理',
'admin:audit': '审计查看',
};
const ErrorPage: React.FC<IErrorPageProps> = (props) => {
const info = errorInfo[props.code];
const navigate = useNavigate();
const dispatch = useAppDispatch();
const location = useLocation();
const admin = useAppSelector((state) => state.user.userInfo as { roles?: string[]; scopes?: string[] });
const state = (location.state || {}) as { requiredScope?: string; roles?: string[]; requestId?: string };
const requiredScope = props.requiredScope || state.requiredScope;
const roles = state.roles?.length ? state.roles : admin.roles;
const readableRoles = roles?.map((role) => permissionLabels[role] || '其他角色');
const readableScope = requiredScope ? permissionLabels[requiredScope] || '相应权限' : '未标注';
const logoutAndLogin = () => { adminLogout().finally(() => { dispatch(logout()); navigate('/login', { replace: true }); }); };
const switchAdmin = () => { adminLogout().finally(() => { dispatch(logout()); navigate('/login', { replace: true, state: { switchAccount: true } }); }); };
let requestInfo: React.ReactNode = null;
if (props.code === ECode.forbidden) {
requestInfo = <div className={style.description}>当前角色:{readableRoles?.join('、') || '未知'};所需权限:{readableScope}{state.requestId ? `;请求 ID:${state.requestId}` : ''}</div>;
} else if (state.requestId) {
requestInfo = <div className={style.description}>请求 ID:{state.requestId}</div>;
}
return (
<div className={style.errorBox}>
{info?.icon}
<div className={style.title}>{info?.title}</div>
<div className={style.description}>{info?.desc}</div>
<Button theme='primary'>返回首页</Button>
{requestInfo}
<Space>
<MiragenFlowButton type='primary' onClick={() => navigate('/dashboard/base', { replace: true })}>返回系统概览</MiragenFlowButton>
<MiragenFlowButton type='outline' onClick={logoutAndLogin}>重新登录</MiragenFlowButton>
{props.code === ECode.forbidden ? <MiragenFlowButton type='outline' onClick={switchAdmin}>切换管理员</MiragenFlowButton> : null}
</Space>
</div>
);
};
@@ -0,0 +1,22 @@
Lineicons Free 0.0.1
Copyright (c) Lineicons
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+113
View File
@@ -0,0 +1,113 @@
/*
* Localized subset of Lineicons Free 0.0.1 (MIT; see LineIcons.LICENSE.txt).
* Generated from @lineiconshq/free-icons so the admin does not load the full icon catalog.
*/
import React from 'react';
const C = React.createElement;
const o = (type, props) => React.createElement(type, { ...props, children: React.Children.toArray(props.children) });
var NC = ({ size: o2 = 20, color: l = "currentColor", ...e }) => C("svg", { ...e, width: o2, height: o2, viewBox: "0 0 25 25", xmlns: "http://www.w3.org/2000/svg", children: C("path", { d: "M22.0679 11.9341C22.2258 12.0716 22.3257 12.2741 22.3257 12.5C22.3257 12.7249 22.2267 12.9267 22.0699 13.0641L18.1064 17.0302C17.8136 17.3231 17.3387 17.3233 17.0457 17.0305C16.7528 16.7377 16.7526 16.2628 17.0454 15.9698L19.7635 13.25L4.88801 13.25L7.60597 15.9699C7.89876 16.2629 7.89859 16.7377 7.6056 17.0305C7.3126 17.3233 6.83773 17.3231 6.54494 17.0301L2.54761 13.03C2.25496 12.7372 2.25496 12.2626 2.54763 11.9697L6.54496 7.96984C6.83776 7.67686 7.31263 7.6767 7.60562 7.9695C7.8986 8.2623 7.89875 8.73718 7.60595 9.03016L4.88785 11.75L19.7633 11.75L17.0454 9.03014C16.7526 8.73715 16.7528 8.26227 17.0458 7.96948C17.3388 7.67669 17.8136 7.67686 18.1064 7.96986L22.0679 11.9341Z", fill: l }) });
var KC = ({ size: o2 = 20, color: l = "currentColor", ...e }) => C("svg", { ...e, width: o2, height: o2, viewBox: "0 0 25 25", xmlns: "http://www.w3.org/2000/svg", children: C("path", { d: "M11.5781 5.0638L11.5781 19.9403L8.85829 17.2222C8.5653 16.9294 8.09043 16.9295 7.79763 17.2225C7.50483 17.5155 7.50498 17.9904 7.79797 18.2832L11.7978 22.2805C12.0907 22.5732 12.5653 22.5732 12.8581 22.2805L16.8583 18.2832C17.1513 17.9904 17.1514 17.5155 16.8586 17.2225C16.5659 16.9295 16.091 16.9294 15.798 17.2222L13.0781 19.9401L13.0781 5.06363L15.798 7.78174C16.0909 8.07454 16.5658 8.07439 16.8586 7.7814C17.1514 7.48841 17.1513 7.01354 16.8583 6.72074L12.8886 2.75357C12.7512 2.59919 12.551 2.50195 12.3281 2.50195C12.1042 2.50195 11.9031 2.60013 11.7657 2.7558L7.79798 6.72072C7.50498 7.01351 7.50482 7.48838 7.79761 7.78138C8.0904 8.07438 8.56527 8.07454 8.85827 7.78175L11.5781 5.0638Z", fill: l }) });
var ao = ({ size: o2 = 20, color: l = "currentColor", ...e }) => C("svg", { ...e, width: o2, height: o2, viewBox: "0 0 25 25", xmlns: "http://www.w3.org/2000/svg", children: C("path", { d: "M21.0791 12.519C21.0744 12.7044 21.0013 12.8884 20.8599 13.0299L14.8639 19.0301C14.5711 19.3231 14.0962 19.3233 13.8032 19.0305C13.5103 18.7377 13.5101 18.2629 13.8029 17.9699L18.5233 13.2461L4.32813 13.2461C3.91391 13.2461 3.57813 12.9103 3.57812 12.4961C3.57812 12.0819 3.91391 11.7461 4.32812 11.7461L18.5158 11.7461L13.8029 7.03016C13.5101 6.73718 13.5102 6.2623 13.8032 5.9695C14.0962 5.6767 14.5711 5.67685 14.8639 5.96984L20.813 11.9228C20.976 12.0603 21.0795 12.2661 21.0795 12.4961C21.0795 12.5038 21.0794 12.5114 21.0791 12.519Z", fill: l }) });
var Cl = ({ size: l = 20, color: e = "currentColor", ...t }) => o("svg", { ...t, width: l, height: l, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: [C("path", { d: "M3.5 5.25C3.5 4.83579 3.16421 4.5 2.75 4.5C2.33579 4.5 2 4.83579 2 5.25V17.25C2 18.4926 3.00736 19.5 4.25 19.5H21.25C21.6642 19.5 22 19.1642 22 18.75C22 18.3358 21.6642 18 21.25 18H4.25C3.83579 18 3.5 17.6642 3.5 17.25V5.25Z", fill: e }), C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M7 10.2773C5.89543 10.2773 5 11.1728 5 12.2773V15.7501C5 16.1643 5.33579 16.5001 5.75 16.5001H8.25C8.66421 16.5001 9 16.1643 9 15.7501V12.2773C9 11.1728 8.10457 10.2773 7 10.2773ZM6.5 12.2773C6.5 12.0012 6.72386 11.7773 7 11.7773C7.27614 11.7773 7.5 12.0012 7.5 12.2773V15.0001H6.5V12.2773Z", fill: e }), C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M10.5 6.5C10.5 5.39543 11.3954 4.5 12.5 4.5C13.6046 4.5 14.5 5.39543 14.5 6.5V15.7501C14.5 16.1643 14.1642 16.5001 13.75 16.5001H11.25C10.8358 16.5001 10.5 16.1643 10.5 15.7501V6.5ZM12.5 6C12.2239 6 12 6.22386 12 6.5V15.0001H13V6.5C13 6.22386 12.7761 6 12.5 6Z", fill: e }), C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M18 8.05859C16.8954 8.05859 16 8.95402 16 10.0586V15.7501C16 16.1643 16.3358 16.5001 16.75 16.5001H19.25C19.6642 16.5001 20 16.1643 20 15.7501V10.0586C20 8.95402 19.1046 8.05859 18 8.05859ZM17.5 10.0586C17.5 9.78245 17.7239 9.55859 18 9.55859C18.2761 9.55859 18.5 9.78245 18.5 10.0586V15.0001H17.5V10.0586Z", fill: e })] });
var il = ({ size: l = 20, color: e = "currentColor", ...t }) => o("svg", { ...t, width: l, height: l, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: [C("path", { d: "M17.8076 2C18.2218 2 18.5576 2.33579 18.5576 2.75V2.91898C19.3061 3.10815 19.8599 3.78601 19.8599 4.59323C19.8599 5.00744 19.5242 5.34323 19.1099 5.34323C18.6957 5.34323 18.3599 5.00744 18.3599 4.59323C18.3599 4.46796 18.2584 4.36641 18.1331 4.36641H17.7381C17.4714 4.36641 17.2553 4.58257 17.2553 4.84921C17.2553 5.05046 17.3801 5.2306 17.5686 5.30127L18.5734 5.67811C19.3473 5.96833 19.8599 6.70814 19.8599 7.53464C19.8599 8.39065 19.3175 9.11996 18.5576 9.39759V9.63372C18.5576 10.0479 18.2218 10.3837 17.8076 10.3837C17.3934 10.3837 17.0576 10.0479 17.0576 9.63372V9.46488C16.3092 9.2757 15.7553 8.59785 15.7553 7.79062C15.7553 7.37641 16.0911 7.04062 16.5053 7.04062C16.9195 7.04062 17.2553 7.37641 17.2553 7.79062C17.2553 7.91589 17.3568 8.01744 17.4821 8.01744H17.8771C18.1438 8.01744 18.3599 7.80129 18.3599 7.53464C18.3599 7.3334 18.2351 7.15326 18.0467 7.08259L17.0418 6.70575C16.268 6.41552 15.7553 5.67571 15.7553 4.84921C15.7553 3.99321 16.2977 3.2639 17.0576 2.98626V2.75C17.0576 2.33579 17.3934 2 17.8076 2Z", fill: e }), C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M5.5 3.25045C4.25736 3.25045 3.25 4.25781 3.25 5.50045V18.5005C3.25 19.7431 4.25736 20.7505 5.5 20.7505H5.83333C7.07597 20.7505 8.08333 19.7431 8.08333 18.5005V5.50045C8.08333 4.25781 7.07598 3.25045 5.83333 3.25045H5.5ZM4.75 5.50045C4.75 5.08624 5.08579 4.75045 5.5 4.75045H5.83333C6.24755 4.75045 6.58333 5.08624 6.58333 5.50045V18.5005C6.58333 18.9147 6.24755 19.2505 5.83333 19.2505H5.5C5.08579 19.2505 4.75 18.9147 4.75 18.5005V5.50045Z", fill: e }), C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M11.833 9.63424C10.5904 9.63424 9.58301 10.6416 9.58301 11.8842V18.5005C9.58301 19.7432 10.5904 20.7505 11.833 20.7505H12.1663C13.409 20.7505 14.4163 19.7432 14.4163 18.5005V11.8842C14.4163 10.6416 13.409 9.63424 12.1663 9.63424H11.833ZM11.083 11.8842C11.083 11.47 11.4188 11.1342 11.833 11.1342H12.1663C12.5806 11.1342 12.9163 11.47 12.9163 11.8842V18.5005C12.9163 18.9147 12.5806 19.2505 12.1663 19.2505H11.833C11.4188 19.2505 11.083 18.9147 11.083 18.5005V11.8842Z", fill: e }), C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M15.916 14.9223C15.916 13.6797 16.9234 12.6723 18.166 12.6723H18.4993C19.742 12.6723 20.7493 13.6797 20.7493 14.9223V18.5005C20.7493 19.7432 19.742 20.7505 18.4993 20.7505H18.166C16.9234 20.7505 15.916 19.7432 15.916 18.5005V14.9223ZM18.166 14.1723C17.7518 14.1723 17.416 14.5081 17.416 14.9223V18.5005C17.416 18.9147 17.7518 19.2505 18.166 19.2505H18.4993C18.9136 19.2505 19.2493 18.9147 19.2493 18.5005V14.9223C19.2493 14.5081 18.9136 14.1723 18.4993 14.1723H18.166Z", fill: e })] });
var vl = ({ size: o2 = 20, color: l = "currentColor", ...e }) => C("svg", { ...e, width: o2, height: o2, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M12.0016 2.00098C12.4158 2.00098 12.7516 2.33676 12.7516 2.75098V3.53801C16.5416 3.9143 19.5016 7.11197 19.5016 11.001V14.115L20.1938 15.9609C20.7454 17.4319 19.6581 19.001 18.0871 19.001H15.0628C15.0287 20.6631 13.6701 21.9995 11.9998 21.9995C10.3295 21.9995 8.97089 20.6631 8.93682 19.001H5.9161C4.34514 19.001 3.25776 17.4319 3.80936 15.9609L4.5016 14.115V11.001C4.5016 7.11197 7.46161 3.9143 11.2516 3.53801V2.75098C11.2516 2.33676 11.5874 2.00098 12.0016 2.00098ZM10.4375 19.001C10.471 19.8339 11.1573 20.4995 11.9998 20.4995C12.8423 20.4995 13.5286 19.8339 13.5622 19.001H10.4375ZM6.0016 11.001C6.0016 7.68727 8.68789 5.00098 12.0016 5.00098C15.3153 5.00098 18.0016 7.68727 18.0016 11.001V14.1168C18.0016 14.2955 18.0337 14.4727 18.0965 14.64L18.7893 16.4876C18.9732 16.9779 18.6108 17.501 18.0871 17.501H5.9161C5.39244 17.501 5.02998 16.9779 5.21385 16.4876L5.90673 14.64C5.96946 14.4727 6.0016 14.2955 6.0016 14.1168V11.001Z", fill: l }) });
var Ue = ({ size: o2 = 20, color: l = "currentColor", ...e }) => C("svg", { ...e, width: o2, height: o2, viewBox: "0 0 25 24", xmlns: "http://www.w3.org/2000/svg", children: C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M8.40585 5.78711C8.23261 5.4368 8.13525 5.04227 8.13525 4.625C8.13525 3.17525 9.31051 2 10.7603 2C11.4948 2 12.1588 2.30168 12.6353 2.78788C13.1117 2.30168 13.7757 2 14.5103 2C15.96 2 17.1353 3.17525 17.1353 4.625C17.1353 5.04227 17.0379 5.4368 16.8647 5.78711H19.1433C20.386 5.78711 21.3933 6.79447 21.3933 8.03711V8.50001C21.3933 9.31501 20.96 10.0288 20.3112 10.4236V19C20.3112 20.2426 19.3038 21.25 18.0612 21.25H7.2096C5.96696 21.25 4.9596 20.2426 4.9596 19V10.4236C4.31077 10.0288 3.87744 9.31501 3.87744 8.50001V8.03711C3.87744 6.79447 4.8848 5.78711 6.12744 5.78711H8.40585ZM14.5103 3.5C13.8889 3.5 13.3853 4.00368 13.3853 4.625V5.75H14.5103C15.1316 5.75 15.6353 5.24632 15.6353 4.625C15.6353 4.00368 15.1316 3.5 14.5103 3.5ZM11.8853 5.75V4.625C11.8853 4.00368 11.3816 3.5 10.7603 3.5C10.1389 3.5 9.63525 4.00368 9.63525 4.625C9.63525 5.24632 10.1389 5.75 10.7603 5.75H11.8853ZM18.8112 10.75H6.4596V19C6.4596 19.4142 6.79538 19.75 7.2096 19.75H18.0612C18.4754 19.75 18.8112 19.4142 18.8112 19V10.75ZM19.1433 9.25001C19.5576 9.25001 19.8933 8.91422 19.8933 8.50001V8.03711C19.8933 7.6229 19.5576 7.28711 19.1433 7.28711H6.12744C5.71323 7.28711 5.37744 7.6229 5.37744 8.03711V8.50001C5.37744 8.91422 5.71323 9.25001 6.12744 9.25001H19.1433Z", fill: l }) });
var Bi = ({ size: l = 20, color: e = "currentColor", ...t }) => o("svg", { ...t, width: l, height: l, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: [C("path", { d: "M7.5 5.75C7.5 5.33579 7.83579 5 8.25 5H15.75C16.1642 5 16.5 5.33579 16.5 5.75V7.25C16.5 7.66421 16.1642 8 15.75 8H8.25C7.83579 8 7.5 7.66421 7.5 7.25V5.75Z", fill: e }), C("path", { d: "M8.5 9.85C8.00294 9.85 7.6 10.2529 7.6 10.75C7.6 11.2471 8.00294 11.65 8.5 11.65C8.99706 11.65 9.4001 11.2471 9.4001 10.75C9.4001 10.2529 8.99706 9.85 8.5 9.85Z", fill: e }), C("path", { d: "M11.1 10.75C11.1 10.2529 11.5029 9.85 12 9.85C12.4971 9.85 12.9001 10.2529 12.9001 10.75C12.9001 11.2471 12.4972 11.65 12.0001 11.65C11.503 11.65 11.1 11.2471 11.1 10.75Z", fill: e }), C("path", { d: "M15.5 9.85C15.0029 9.85 14.6 10.2529 14.6 10.75C14.6 11.2471 15.0029 11.65 15.5 11.65C15.9971 11.65 16.4001 11.2471 16.4001 10.75C16.4001 10.2529 15.9971 9.85 15.5 9.85Z", fill: e }), C("path", { d: "M7.6 14.25C7.6 13.7529 8.00294 13.35 8.5 13.35C8.99706 13.35 9.4001 13.7529 9.4001 14.25C9.4001 14.7471 8.99716 15.15 8.5001 15.15C8.00304 15.15 7.6 14.7471 7.6 14.25Z", fill: e }), C("path", { d: "M12 13.35C11.5029 13.35 11.1 13.7529 11.1 14.25C11.1 14.7471 11.5029 15.15 12 15.15C12.4971 15.15 12.9001 14.7471 12.9001 14.25C12.9001 13.7529 12.4971 13.35 12 13.35Z", fill: e }), C("path", { d: "M14.6 14.25C14.6 13.7529 15.0029 13.35 15.5 13.35C15.9971 13.35 16.4001 13.7529 16.4001 14.25C16.4001 14.7471 15.9972 15.15 15.5001 15.15C15.003 15.15 14.6 14.7471 14.6 14.25Z", fill: e }), C("path", { d: "M8.5 16.85C8.00294 16.85 7.6 17.2529 7.6 17.75C7.6 18.2471 8.00294 18.65 8.5 18.65C8.99706 18.65 9.4001 18.2471 9.4001 17.75C9.4001 17.2529 8.99706 16.85 8.5 16.85Z", fill: e }), C("path", { d: "M11.1 17.75C11.1 17.2529 11.5029 16.85 12 16.85C12.4971 16.85 12.9001 17.2529 12.9001 17.75C12.9001 18.2471 12.4972 18.65 12.0001 18.65C11.503 18.65 11.1 18.2471 11.1 17.75Z", fill: e }), C("path", { d: "M15.5 16.85C15.0029 16.85 14.6 17.2529 14.6 17.75C14.6 18.2471 15.0029 18.65 15.5 18.65C15.9971 18.65 16.4001 18.2471 16.4001 17.75C16.4001 17.2529 15.9971 16.85 15.5 16.85Z", fill: e }), C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M4.5 4.25C4.5 3.00736 5.50736 2 6.75 2H17.25C18.4926 2 19.5 3.00736 19.5 4.25V19.75C19.5 20.9926 18.4926 22 17.25 22H6.75C5.50736 22 4.5 20.9926 4.5 19.75V4.25ZM6.75 3.5C6.33579 3.5 6 3.83579 6 4.25V19.75C6 20.1642 6.33579 20.5 6.75 20.5H17.25C17.6642 20.5 18 20.1642 18 19.75V4.25C18 3.83579 17.6642 3.5 17.25 3.5H6.75Z", fill: e })] });
var Jr = ({ size: l = 20, color: e = "currentColor", ...t }) => o("svg", { ...t, width: l, height: l, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: [C("path", { d: "M15.5071 10.5245C15.8 10.2316 15.8 9.75674 15.5071 9.46384C15.2142 9.17095 14.7393 9.17095 14.4464 9.46384L10.9649 12.9454L9.55359 11.5341C9.2607 11.2412 8.78582 11.2412 8.49293 11.5341C8.20004 11.827 8.20004 12.3019 8.49294 12.5947L10.4346 14.5364C10.7275 14.8293 11.2023 14.8292 11.4952 14.5364L15.5071 10.5245Z", fill: e }), C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2ZM3.5 12C3.5 7.30558 7.30558 3.5 12 3.5C16.6944 3.5 20.5 7.30558 20.5 12C20.5 16.6944 16.6944 20.5 12 20.5C7.30558 20.5 3.5 16.6944 3.5 12Z", fill: e })] });
var td = ({ size: l = 20, color: e = "currentColor", ...t }) => o("svg", { ...t, width: l, height: l, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: [C("path", { d: "M20.75 18.5V8.67794L19.25 10.1779V18.5C19.25 18.9142 18.9142 19.25 18.5 19.25H5.5C5.08579 19.25 4.75 18.9142 4.75 18.5V5.5C4.75 5.08579 5.08579 4.75 5.5 4.75H18.314L19.2936 3.77034C19.3842 3.67974 19.4806 3.59848 19.5816 3.52657C19.2607 3.35027 18.8921 3.25 18.5 3.25H5.5C4.25736 3.25 3.25 4.25736 3.25 5.5V18.5C3.25 19.7426 4.25736 20.75 5.5 20.75H18.5C19.7426 20.75 20.75 19.7426 20.75 18.5Z", fill: e }), C("path", { d: "M20.4838 6.51868C20.7767 6.22578 20.7767 5.75091 20.4838 5.45802C20.1909 5.16512 19.7161 5.16512 19.4232 5.45802L11.9298 12.9514L8.57686 9.59849C8.28396 9.3056 7.80909 9.3056 7.5162 9.5985C7.22331 9.89139 7.22331 10.3663 7.5162 10.6592L11.3995 14.5424C11.6924 14.8353 12.1672 14.8353 12.4601 14.5424L20.4838 6.51868Z", fill: e })] });
var Md = ({ size: o2 = 20, color: l = "currentColor", ...e }) => C("svg", { ...e, width: o2, height: o2, viewBox: "0 0 25 24", xmlns: "http://www.w3.org/2000/svg", children: C("path", { d: "M5.54779 9.09467C5.84069 8.80178 6.31556 8.80178 6.60846 9.09467L12.3281 14.8143L18.0478 9.09467C18.3407 8.80178 18.8156 8.80178 19.1085 9.09467C19.4013 9.38756 19.4013 9.86244 19.1085 10.1553L12.8585 16.4053C12.5656 16.6982 12.0907 16.6982 11.7978 16.4053L5.54779 10.1553C5.2549 9.86244 5.2549 9.38756 5.54779 9.09467Z", fill: l }) });
var Id = ({ size: o2 = 20, color: l = "currentColor", ...e }) => C("svg", { ...e, width: o2, height: o2, viewBox: "0 0 25 24", xmlns: "http://www.w3.org/2000/svg", children: C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M16.1864 3.75C15.9591 2.74801 15.063 2 13.9922 2H10.0547C8.98389 2 8.08781 2.74801 7.86044 3.75H6.77344C5.5308 3.75 4.52344 4.75736 4.52344 6V19.75C4.52344 20.9926 5.5308 22 6.77344 22H17.2734C18.5161 22 19.5234 20.9926 19.5234 19.75L19.5234 6C19.5234 4.75736 18.5161 3.75 17.2734 3.75H16.1864ZM9.30469 4.25C9.30469 3.83579 9.64047 3.5 10.0547 3.5H13.9922C14.4064 3.5 14.7422 3.83578 14.7422 4.25L14.7422 4.71875C14.7422 5.13296 14.4064 5.46875 13.9922 5.46875H10.0547C9.64047 5.46875 9.30469 5.13296 9.30469 4.71875V4.25ZM7.86777 5.25H6.77344C6.35922 5.25 6.02344 5.58579 6.02344 6V19.75C6.02344 20.1642 6.35922 20.5 6.77344 20.5H17.2734C17.6877 20.5 18.0234 20.1642 18.0234 19.75L18.0234 6C18.0234 5.58579 17.6876 5.25 17.2734 5.25H16.1791C15.9404 6.23626 15.0518 6.96875 13.9922 6.96875H10.0547C8.99506 6.96875 8.10651 6.23626 7.86777 5.25Z", fill: l }) });
var Yd = ({ size: o2 = 20, color: l = "currentColor", ...e }) => C("svg", { ...e, width: o2, height: o2, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M6.38687 9.98359C6.45685 6.94319 8.94344 4.5 12.0007 4.5C14.4558 4.5 16.5415 6.07501 17.3051 8.26898C19.9715 8.72575 22.0001 11.0476 22.0001 13.8439C22.0001 16.9678 19.4676 19.5003 16.3437 19.5003H6.76578C4.13371 19.5003 2 17.3666 2 14.7345C2 12.23 3.93195 10.1767 6.38687 9.98359ZM12.0007 6C9.72784 6 7.88536 7.84248 7.88536 10.1153V10.7188C7.88536 11.133 7.54957 11.4688 7.13536 11.4688H6.76578C4.96214 11.4688 3.5 12.9309 3.5 14.7345C3.5 16.5382 4.96214 18.0003 6.76578 18.0003H16.3437C18.6392 18.0003 20.5001 16.1394 20.5001 13.8439C20.5001 11.6577 18.8118 9.86517 16.6684 9.69996C16.3454 9.67506 16.0748 9.44553 15.9977 9.13086C15.5566 7.33307 13.9334 6 12.0007 6Z", fill: l }) });
var yh = ({ size: l = 20, color: e = "currentColor", ...t }) => o("svg", { ...t, width: l, height: l, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: [C("path", { d: "M6.38688 7.48329C6.45702 4.44302 8.94354 2 12.0007 2C15.0578 2 17.5444 4.44308 17.6144 7.48338C20.0688 7.67706 22.0001 9.7301 22.0001 12.2342C22.0001 12.2685 21.9997 12.3027 21.999 12.3368C21.4783 11.685 20.8414 11.13 20.1197 10.7032C19.571 9.67117 18.4847 8.96844 17.2343 8.96844H16.866C16.4518 8.96844 16.116 8.63266 16.116 8.21844V7.6153C16.116 5.34248 14.2735 3.5 12.0007 3.5C9.72784 3.5 7.88536 5.34248 7.88536 7.6153V8.21844C7.88536 8.63266 7.54957 8.96844 7.13536 8.96844H6.76578C4.96214 8.96844 3.5 10.4306 3.5 12.2342C3.5 14.0379 4.96214 15.5 6.76577 15.5H9.84159C9.78134 15.8661 9.75 16.2419 9.75 16.625C9.75 16.7508 9.75338 16.8758 9.76005 17H6.76577C4.13371 17 2 14.8663 2 12.2342C2 9.72969 3.93195 7.67638 6.38688 7.48329Z", fill: e }), C("path", { d: "M12.0223 15.7449C11.6331 15.603 11.4327 15.1725 11.5746 14.7834C11.8569 14.009 12.3151 13.306 12.9178 12.732C13.7399 11.9491 14.7887 11.4466 15.9139 11.2964C17.0392 11.1463 18.1831 11.3562 19.1817 11.8961C20.1659 12.4282 20.9592 13.254 21.4515 14.2579C21.5044 14.3525 21.5375 14.4597 21.5449 14.5739C21.5656 14.8717 21.4062 15.1656 21.1206 15.3024C20.9956 15.3624 20.8622 15.3845 20.7334 15.3735H19.0637C18.6495 15.3735 18.3137 15.0378 18.3137 14.6235C18.3137 14.2093 18.6495 13.8735 19.0637 13.8735H19.3547C19.0937 13.6146 18.7956 13.3926 18.4683 13.2156C17.7483 12.8263 16.9236 12.675 16.1123 12.7832C15.3011 12.8915 14.5449 13.2538 13.9522 13.8183C13.5177 14.2321 13.1874 14.7389 12.9838 15.2972C12.8419 15.6864 12.4114 15.8868 12.0223 15.7449Z", fill: e }), C("path", { d: "M14.781 20.0344C14.452 19.8564 14.1525 19.633 13.8905 19.3723H14.1851C14.5993 19.3723 14.9351 19.0365 14.9351 18.6223C14.9351 18.2081 14.5993 17.8723 14.1851 17.8723H12.4528C12.055 17.8723 11.7295 18.182 11.7043 18.5735C11.6958 18.6983 11.7185 18.827 11.7764 18.9479C12.267 19.9717 13.069 20.8139 14.0676 21.3538C15.0662 21.8938 16.2101 22.1037 17.3354 21.9535C18.4606 21.8034 19.5095 21.3008 20.3316 20.5179C20.9349 19.9433 21.3934 19.2394 21.6756 18.4641C21.8173 18.0749 21.6167 17.6445 21.2275 17.5028C20.8382 17.3611 20.4078 17.5617 20.2661 17.9509C20.0626 18.5099 19.7321 19.0174 19.2971 19.4317C18.7044 19.9961 17.9482 20.3584 17.137 20.4667C16.3257 20.575 15.501 20.4236 14.781 20.0344Z", fill: e })] });
var IL = ({ size: o2 = 20, color: l = "currentColor", ...e }) => C("svg", { ...e, width: o2, height: o2, viewBox: "0 0 25 24", xmlns: "http://www.w3.org/2000/svg", children: C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M3.9104 6.27879C2.71011 6.60041 1.99779 7.83416 2.31941 9.03446L4.75231 18.1016V18.4998C4.75231 19.7424 5.75967 20.7498 7.00231 20.7498H20.0023C21.2449 20.7498 22.2523 19.7424 22.2523 18.4998V9.82408C22.2523 8.59588 21.2682 7.59753 20.0455 7.57448L19.2231 4.50513C18.9015 3.30483 17.6677 2.59252 16.4674 2.91414L3.9104 6.27879ZM20.7523 10.8172V9.82408C20.7523 9.40986 20.4165 9.07408 20.0023 9.07408H7.00231C6.58809 9.07408 6.25231 9.40986 6.25231 9.82408V10.8172H20.7523ZM4.75231 12.3186V9.82408C4.75231 8.58143 5.75967 7.57408 7.00231 7.57408H18.4925L17.7742 4.89336C17.667 4.49326 17.2558 4.25582 16.8557 4.36303L4.29863 7.72768C3.89853 7.83488 3.6611 8.24613 3.7683 8.64623L4.75231 12.3186ZM6.25231 13.6145H20.7523V18.4998C20.7523 18.914 20.4165 19.2498 20.0023 19.2498H7.00231C6.5881 19.2498 6.25231 18.914 6.25231 18.4998V13.6145Z", fill: l }) });
var Ln = ({ size: l = 20, color: e = "currentColor", ...t }) => o("svg", { ...t, width: l, height: l, viewBox: "0 0 25 24", xmlns: "http://www.w3.org/2000/svg", children: [C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M5.60352 3.25C4.36088 3.25 3.35352 4.25736 3.35352 5.5V8.99998C3.35352 10.2426 4.36087 11.25 5.60352 11.25H9.10352C10.3462 11.25 11.3535 10.2426 11.3535 8.99998V5.5C11.3535 4.25736 10.3462 3.25 9.10352 3.25H5.60352ZM4.85352 5.5C4.85352 5.08579 5.1893 4.75 5.60352 4.75H9.10352C9.51773 4.75 9.85352 5.08579 9.85352 5.5V8.99998C9.85352 9.41419 9.51773 9.74998 9.10352 9.74998H5.60352C5.1893 9.74998 4.85352 9.41419 4.85352 8.99998V5.5Z", fill: e }), C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M5.60352 12.75C4.36088 12.75 3.35352 13.7574 3.35352 15V18.5C3.35352 19.7426 4.36087 20.75 5.60352 20.75H9.10352C10.3462 20.75 11.3535 19.7426 11.3535 18.5V15C11.3535 13.7574 10.3462 12.75 9.10352 12.75H5.60352ZM4.85352 15C4.85352 14.5858 5.1893 14.25 5.60352 14.25H9.10352C9.51773 14.25 9.85352 14.5858 9.85352 15V18.5C9.85352 18.9142 9.51773 19.25 9.10352 19.25H5.60352C5.1893 19.25 4.85352 18.9142 4.85352 18.5V15Z", fill: e }), C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M12.8535 5.5C12.8535 4.25736 13.8609 3.25 15.1035 3.25H18.6035C19.8462 3.25 20.8535 4.25736 20.8535 5.5V8.99998C20.8535 10.2426 19.8462 11.25 18.6035 11.25H15.1035C13.8609 11.25 12.8535 10.2426 12.8535 8.99998V5.5ZM15.1035 4.75C14.6893 4.75 14.3535 5.08579 14.3535 5.5V8.99998C14.3535 9.41419 14.6893 9.74998 15.1035 9.74998H18.6035C19.0177 9.74998 19.3535 9.41419 19.3535 8.99998V5.5C19.3535 5.08579 19.0177 4.75 18.6035 4.75H15.1035Z", fill: e }), C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M15.1035 12.75C13.8609 12.75 12.8535 13.7574 12.8535 15V18.5C12.8535 19.7426 13.8609 20.75 15.1035 20.75H18.6035C19.8462 20.75 20.8535 19.7426 20.8535 18.5V15C20.8535 13.7574 19.8462 12.75 18.6035 12.75H15.1035ZM14.3535 15C14.3535 14.5858 14.6893 14.25 15.1035 14.25H18.6035C19.0177 14.25 19.3535 14.5858 19.3535 15V18.5C19.3535 18.9142 19.0177 19.25 18.6035 19.25H15.1035C14.6893 19.25 14.3535 18.9142 14.3535 18.5V15Z", fill: e })] });
var gn = ({ size: o2 = 20, color: l = "currentColor", ...e }) => C("svg", { ...e, width: o2, height: o2, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M5.28636 3.71264C4.853 4.09212 4.5 4.60958 4.5 5.25V18.75C4.5 19.3904 4.853 19.9079 5.28636 20.2874C5.7212 20.6681 6.30718 20.9769 6.96654 21.2211C8.29107 21.7116 10.0708 22 12 22C13.9292 22 15.7089 21.7116 17.0335 21.2211C17.6928 20.9769 18.2788 20.6681 18.7136 20.2874C19.147 19.9079 19.5 19.3904 19.5 18.75V5.25C19.5 4.60958 19.147 4.09212 18.7136 3.71264C18.2788 3.33187 17.6928 3.02313 17.0335 2.77892C15.7089 2.28836 13.9292 2 12 2C10.0708 2 8.29107 2.28836 6.96654 2.77892C6.30718 3.02313 5.7212 3.33187 5.28636 3.71264ZM6.27454 4.84114C6.02476 5.05985 6 5.20007 6 5.25C6 5.29993 6.02476 5.44015 6.27454 5.65886C6.52284 5.87629 6.92537 6.10625 7.48751 6.31446C8.60601 6.72871 10.2013 7 12 7C13.7987 7 15.394 6.72871 16.5125 6.31446C17.0746 6.10625 17.4772 5.87629 17.7255 5.65886C17.9752 5.44015 18 5.29993 18 5.25C18 5.20007 17.9752 5.05985 17.7255 4.84114C17.4772 4.62371 17.0746 4.39375 16.5125 4.18554C15.394 3.77129 13.7987 3.5 12 3.5C10.2013 3.5 8.60601 3.77129 7.48751 4.18554C6.92537 4.39375 6.52284 4.62371 6.27454 4.84114ZM18 9.75V7.28202C17.7055 7.44688 17.3796 7.59287 17.0335 7.72108C15.7089 8.21164 13.9292 8.5 12 8.5C10.0708 8.5 8.29107 8.21164 6.96654 7.72108C6.62039 7.59287 6.29445 7.44688 6 7.28202V9.75C6 9.79993 6.02476 9.94015 6.27454 10.1589C6.52284 10.3763 6.92537 10.6063 7.48751 10.8145C8.60601 11.2287 10.2013 11.5 12 11.5C13.7987 11.5 15.394 11.2287 16.5125 10.8145C17.0746 10.6063 17.4772 10.3763 17.7255 10.1589C17.9752 9.94015 18 9.79993 18 9.75ZM6 11.782C6.29445 11.9469 6.62039 12.0929 6.96654 12.2211C8.29107 12.7116 10.0708 13 12 13C13.9292 13 15.7089 12.7116 17.0335 12.2211C17.3796 12.0929 17.7055 11.9469 18 11.782V14.25C18 14.2999 17.9752 14.4402 17.7255 14.6589C17.4772 14.8763 17.0746 15.1063 16.5125 15.3145C15.394 15.7287 13.7987 16 12 16C10.2013 16 8.60601 15.7287 7.48751 15.3145C6.92537 15.1063 6.52284 14.8763 6.27454 14.6589C6.02476 14.4402 6 14.2999 6 14.25V11.782ZM6 18.75V16.282C6.29445 16.4469 6.62039 16.5929 6.96654 16.7211C8.29107 17.2116 10.0708 17.5 12 17.5C13.9292 17.5 15.7089 17.2116 17.0335 16.7211C17.3796 16.5929 17.7055 16.4469 18 16.282V18.75C18 18.7999 17.9752 18.9401 17.7255 19.1589C17.4772 19.3763 17.0746 19.6063 16.5125 19.8145C15.394 20.2287 13.7987 20.5 12 20.5C10.2013 20.5 8.60601 20.2287 7.48751 19.8145C6.92537 19.6063 6.52284 19.3763 6.27454 19.1589C6.02476 18.9401 6 18.7999 6 18.75Z", fill: l }) });
var Kn = ({ size: l = 20, color: e = "currentColor", ...t }) => o("svg", { ...t, width: l, height: l, viewBox: "0 0 25 24", xmlns: "http://www.w3.org/2000/svg", children: [C("path", { d: "M12.25 6.625C12.6642 6.625 13 6.96079 13 7.375V7.81278C13.9908 7.96341 14.75 8.81897 14.75 9.85184C14.75 10.2661 14.4142 10.6018 14 10.6018C13.5858 10.6018 13.25 10.2661 13.25 9.85184C13.25 9.54113 12.9981 9.28924 12.6874 9.28924H12C11.5858 9.28924 11.25 9.62503 11.25 10.0392V10.3043C11.25 10.6169 11.4439 10.8968 11.7366 11.0066L13.2901 11.5891C14.1682 11.9185 14.75 12.758 14.75 13.6959V13.9609C14.75 15.0317 14.002 15.9278 13 16.1552V16.625C13 17.0392 12.6642 17.375 12.25 17.375C11.8358 17.375 11.5 17.0392 11.5 16.625V16.1874C10.5092 16.0368 9.75 15.1812 9.75 14.1483C9.75 13.7341 10.0858 13.3983 10.5 13.3983C10.9142 13.3983 11.25 13.7341 11.25 14.1483C11.25 14.4591 11.5019 14.7109 11.8126 14.7109H12.5C12.9142 14.7109 13.25 14.3752 13.25 13.9609V13.6959C13.25 13.3832 13.0561 13.1034 12.7634 12.9936L11.2099 12.411C10.3318 12.0817 9.75 11.2422 9.75 10.3043V10.0392C9.75 8.96845 10.498 8.07236 11.5 7.845V7.375C11.5 6.96079 11.8358 6.625 12.25 6.625Z", fill: e }), C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M12.25 2C6.72715 2 2.25 6.47715 2.25 12C2.25 17.5228 6.72715 22 12.25 22C17.7728 22 22.25 17.5228 22.25 12C22.25 6.47715 17.7728 2 12.25 2ZM3.75 12C3.75 7.30558 7.55558 3.5 12.25 3.5C16.9444 3.5 20.75 7.30558 20.75 12C20.75 16.6944 16.9444 20.5 12.25 20.5C7.55558 20.5 3.75 16.6944 3.75 12Z", fill: e })] });
var ks = ({ size: o2 = 20, color: l = "currentColor", ...e }) => C("svg", { ...e, width: o2, height: o2, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M22 6.25649V17.25C22 18.4926 20.9926 19.5 19.75 19.5H4.25C3.00736 19.5 2 18.4926 2 17.25V6.23398C2 6.22372 2.00021 6.2135 2.00061 6.20334C2.01781 5.25972 2.78812 4.5 3.73592 4.5H20.2644C21.2229 4.5 22 5.27697 22.0001 6.23549C22.0001 6.24249 22.0001 6.24949 22 6.25649ZM3.5 8.187V17.25C3.5 17.6642 3.83579 18 4.25 18H19.75C20.1642 18 20.5 17.6642 20.5 17.25V8.18747L13.2873 13.2171C12.5141 13.7563 11.4866 13.7563 10.7134 13.2171L3.5 8.187ZM20.5 6.2286L20.5 6.23398V6.24336C20.4976 6.31753 20.4604 6.38643 20.3992 6.42905L12.4293 11.9867C12.1716 12.1664 11.8291 12.1664 11.5713 11.9867L3.60116 6.42885C3.538 6.38481 3.50035 6.31268 3.50032 6.23568C3.50028 6.10553 3.60577 6 3.73592 6H20.2644C20.3922 6 20.4963 6.10171 20.5 6.2286Z", fill: l }) });
var Us = ({ size: l = 20, color: e = "currentColor", ...t }) => o("svg", { ...t, width: l, height: l, viewBox: "0 0 25 25", xmlns: "http://www.w3.org/2000/svg", children: [C("path", { d: "M11.5781 2.5C10.3355 2.5 9.32812 3.50736 9.32812 4.75V6.6285C9.44877 6.70925 9.56333 6.80292 9.66985 6.90952L10.8281 8.06853V4.75C10.8281 4.33579 11.1639 4 11.5781 4H17.5781C17.9923 4 18.3281 4.33579 18.3281 4.75V20.25C18.3281 20.6642 17.9923 21 17.5781 21H11.5781C11.1639 21 10.8281 20.6642 10.8281 20.25V16.9314L9.6699 18.0904C9.56336 18.197 9.44879 18.2907 9.32812 18.3715V20.25C9.32812 21.4926 10.3355 22.5 11.5781 22.5H17.5781C18.8208 22.5 19.8281 21.4926 19.8281 20.25V4.75C19.8281 3.50736 18.8208 2.5 17.5781 2.5H11.5781Z", fill: e }), C("path", { d: "M3.57812 12.5C3.57812 12.7259 3.67796 12.9284 3.83591 13.0659L7.79738 17.0301C8.09017 17.3231 8.56504 17.3233 8.85804 17.0305C9.15104 16.7377 9.1512 16.2629 8.85841 15.9699L6.14046 13.25L12.0781 13.25C12.4923 13.25 12.8281 12.9142 12.8281 12.5C12.8281 12.0858 12.4923 11.75 12.0781 11.75L6.14028 11.75L8.85839 9.03016C9.15119 8.73718 9.15104 8.2623 8.85806 7.9695C8.56507 7.6767 8.0902 7.67685 7.7974 7.96984L3.83388 11.9359C3.67711 12.0733 3.57812 12.2751 3.57812 12.5Z", fill: e })] });
var DH = ({ size: l = 20, color: e = "currentColor", ...t }) => o("svg", { ...t, width: l, height: l, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: [C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M10.9453 20.5C11.3327 21.0667 11.8027 21.5725 12.338 22H6.75C5.50736 22 4.5 20.9926 4.5 19.75V9.62105C4.5 9.02455 4.73686 8.45247 5.15851 8.03055L10.5262 2.65951C10.9482 2.23725 11.5207 2 12.1177 2H17.25C18.4926 2 19.5 3.00736 19.5 4.25V10.3782C19.0266 10.1599 18.5241 9.99391 18 9.88753V4.25C18 3.83579 17.6642 3.5 17.25 3.5H12.248L12.2509 7.4984C12.2518 8.74166 11.2442 9.75 10.0009 9.75H6V19.75C6 20.1642 6.33579 20.5 6.75 20.5H10.9453ZM10.7488 4.55876L7.05986 8.25H10.0009C10.4153 8.25 10.7512 7.91389 10.7509 7.49947L10.7488 4.55876Z", fill: e }), C("path", { d: "M15.8751 14.9999C15.8751 14.5857 16.2109 14.2499 16.6251 14.2499C17.0393 14.2499 17.3751 14.5857 17.3751 14.9999V15.8749H18.2502C18.6644 15.8749 19.0002 16.2107 19.0002 16.6249C19.0002 17.0391 18.6644 17.3749 18.2502 17.3749H17.3751V18.25C17.3751 18.6643 17.0393 19 16.6251 19C16.2109 19 15.8751 18.6643 15.8751 18.25V17.3749H15C14.5858 17.3749 14.25 17.0391 14.25 16.6249C14.25 16.2107 14.5858 15.8749 15 15.8749H15.8751V14.9999Z", fill: e }), C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M11.25 16.625C11.25 13.6565 13.6565 11.25 16.625 11.25C19.5935 11.25 22 13.6565 22 16.625C22 19.5935 19.5935 22 16.625 22C13.6565 22 11.25 19.5935 11.25 16.625ZM16.625 12.75C14.4849 12.75 12.75 14.4849 12.75 16.625C12.75 18.7651 14.4849 20.5 16.625 20.5C18.7651 20.5 20.5 18.7651 20.5 16.625C20.5 14.4849 18.7651 12.75 16.625 12.75Z", fill: e })] });
var kg = ({ size: o2 = 20, color: l = "currentColor", ...e }) => C("svg", { ...e, width: o2, height: o2, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M2 6C2 4.75736 3.00736 3.75 4.25 3.75H8.5C9.2082 3.75 9.87508 4.08344 10.3 4.65L11.65 6.45C11.7916 6.63885 12.0139 6.75 12.25 6.75H19.75C20.9926 6.75 22 7.75736 22 9V18C22 19.2426 20.9926 20.25 19.75 20.25H4.25C3.00736 20.25 2 19.2426 2 18V6ZM4.25 5.25C3.83579 5.25 3.5 5.58579 3.5 6V18C3.5 18.4142 3.83579 18.75 4.25 18.75H19.75C20.1642 18.75 20.5 18.4142 20.5 18V9C20.5 8.58579 20.1642 8.25 19.75 8.25H12.25C11.5418 8.25 10.8749 7.91656 10.45 7.35L9.1 5.55C8.95836 5.36115 8.73607 5.25 8.5 5.25H4.25Z", fill: l }) });
var _g = ({ size: l = 20, color: e = "currentColor", ...t }) => o("svg", { ...t, width: l, height: l, viewBox: "0 0 24 25", xmlns: "http://www.w3.org/2000/svg", children: [C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M11.9992 8.7743C9.88118 8.7743 8.16419 10.4913 8.16419 12.6093C8.16419 14.7273 9.88118 16.4443 11.9992 16.4443C14.1172 16.4443 15.8342 14.7273 15.8342 12.6093C15.8342 10.4913 14.1172 8.7743 11.9992 8.7743ZM9.66419 12.6093C9.66419 11.3197 10.7096 10.2743 11.9992 10.2743C13.2888 10.2743 14.3342 11.3197 14.3342 12.6093C14.3342 13.8989 13.2888 14.9443 11.9992 14.9443C10.7096 14.9443 9.66419 13.8989 9.66419 12.6093Z", fill: e }), C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M2.5809 8.9224C1.96404 9.99083 2.33012 11.357 3.39854 11.9739C3.88777 12.2563 3.88776 12.9625 3.39856 13.2449C2.33013 13.8618 1.96407 15.2279 2.58092 16.2964L4.09692 18.9222C4.71391 19.9908 6.08044 20.3568 7.14896 19.7399C7.63844 19.4573 8.25011 19.8106 8.25011 20.3754C8.25011 21.6092 9.2503 22.6094 10.4841 22.6094H13.5165C14.7502 22.6094 15.7501 21.6091 15.7501 20.3756C15.7501 19.8108 16.3615 19.458 16.8503 19.7402C17.9185 20.357 19.2845 19.991 19.9012 18.9227L21.4176 16.2963C22.0344 15.2279 21.6684 13.8617 20.6 13.2449C20.1108 12.9624 20.1108 12.2563 20.6 11.9739C21.6684 11.3571 22.0345 9.99089 21.4176 8.92247L19.9012 6.29604C19.2845 5.2278 17.9185 4.86179 16.8503 5.47854C16.3615 5.76076 15.7501 5.40794 15.7501 4.84314C15.7501 3.60961 14.7502 2.60938 13.5165 2.60938H10.4841C9.2503 2.60938 8.25011 3.60957 8.25011 4.84337C8.25011 5.40822 7.63842 5.76152 7.14894 5.47892C6.08042 4.86201 4.71388 5.22797 4.09689 6.29663L2.5809 8.9224ZM4.14854 10.6748C3.79755 10.4722 3.6773 10.0234 3.87994 9.6724L5.39593 7.04663C5.59863 6.69554 6.04772 6.57518 6.39894 6.77796C7.88811 7.63773 9.75011 6.56327 9.75011 4.84337C9.75011 4.43799 10.0787 4.10937 10.4841 4.10937L13.5165 4.10937C13.9216 4.10937 14.2501 4.43788 14.2501 4.84314C14.2501 6.56227 16.1112 7.63733 17.6003 6.77758C17.9511 6.57504 18.3997 6.69524 18.6022 7.04604L20.1186 9.67247C20.3212 10.0234 20.201 10.4722 19.85 10.6749C18.3608 11.5346 18.3608 13.6841 19.85 14.5439C20.2009 14.7465 20.3212 15.1953 20.1186 15.5463L18.6022 18.1727C18.3996 18.5235 17.9511 18.6437 17.6003 18.4412C16.1112 17.5815 14.2501 18.6565 14.2501 20.3756C14.2501 20.7809 13.9216 21.1094 13.5165 21.1094H10.4841C10.0787 21.1094 9.75011 20.7808 9.75011 20.3754C9.75011 18.6555 7.88812 17.5811 6.39896 18.4408C6.04774 18.6436 5.59866 18.5232 5.39596 18.1722L3.87996 15.5464C3.67732 15.1954 3.79757 14.7466 4.14856 14.5439C5.63778 13.6841 5.63775 11.5346 4.14854 10.6748Z", fill: e })] });
var tM = ({ size: l = 20, color: e = "currentColor", ...t }) => o("svg", { ...t, width: l, height: l, viewBox: "0 0 24 25", xmlns: "http://www.w3.org/2000/svg", children: [C("path", { d: "M4.25315 5.39728C5.24418 5.9627 6.47801 5.25034 6.48385 4.10938L7.98373 4.10938C7.98956 5.25038 9.22343 5.96275 10.2145 5.3973L10.9635 6.6946C11.3812 6.46162 11.8798 6.30786 12.3876 6.21697C12.3568 6.12702 12.3169 6.03871 12.2675 5.95326L11.5095 4.64028C11.0998 3.93076 10.1954 3.68503 9.48364 4.0873C9.47615 3.26978 8.81111 2.60938 7.99183 2.60938H6.47573C5.65647 2.60938 4.99145 3.26976 4.98393 4.08724C4.27222 3.68501 3.3678 3.93074 2.95817 4.64024L2.20012 5.95323C1.79048 6.66275 2.0299 7.56889 2.73413 7.98413C2.0299 8.39938 1.7905 9.30551 2.20013 10.015L2.95819 11.328C3.36782 12.0375 4.27222 12.2832 4.98394 11.881C4.98962 12.4994 5.37154 13.0279 5.91185 13.2486C5.909 12.6779 6.05286 12.0988 6.35883 11.5689L6.4266 11.4515C6.17233 10.5593 5.11989 10.0765 4.25316 10.571L3.50323 9.27204C4.48844 8.69649 4.48842 7.27175 3.50321 6.69621L4.25315 5.39728Z", fill: e }), C("path", { d: "M7.23401 9.48438C8.06244 9.48438 8.73401 8.8128 8.73401 7.98438C8.73401 7.15595 8.06244 6.48438 7.23401 6.48438C6.40558 6.48438 5.73401 7.15595 5.73401 7.98438C5.73401 8.8128 6.40558 9.48438 7.23401 9.48438Z", fill: e }), C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M11.6406 15.1107C11.6406 13.418 13.0129 12.0457 14.7056 12.0457C16.3984 12.0457 17.7706 13.418 17.7706 15.1107C17.7706 16.8035 16.3984 18.1757 14.7056 18.1757C13.0129 18.1757 11.6406 16.8035 11.6406 15.1107ZM14.7056 13.5457C13.8413 13.5457 13.1406 14.2464 13.1406 15.1107C13.1406 15.975 13.8413 16.6757 14.7056 16.6757C15.57 16.6757 16.2706 15.975 16.2706 15.1107C16.2706 14.2464 15.57 13.5457 14.7056 13.5457Z", fill: e }), C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M7.65797 17.9026C7.15164 17.0256 7.45257 15.9045 8.32916 15.3984C8.55144 15.27 8.55094 14.9496 8.32953 14.8218C7.45293 14.3157 7.15258 13.1948 7.65869 12.3182L8.76495 10.4021C9.27102 9.52553 10.3919 9.22535 11.2683 9.73135C11.49 9.85937 11.767 9.69928 11.767 9.44355C11.767 8.43154 12.5874 7.61115 13.5994 7.61115H15.8121C16.8245 7.61115 17.6448 8.43199 17.6448 9.44397C17.6448 9.7002 17.9221 9.85987 18.1434 9.73212C19.0198 9.22613 20.1404 9.52641 20.6464 10.4028L21.7531 12.3196C22.259 13.1959 21.9588 14.3164 21.0825 14.8223C20.8615 14.9499 20.861 15.2697 21.0829 15.3978C21.9591 15.9037 22.26 17.0245 21.7538 17.9012L20.6475 19.8173C20.1414 20.6939 19.0205 20.9942 18.1439 20.4881C17.9222 20.3601 17.6448 20.5201 17.6448 20.7764C17.6448 21.7886 16.8243 22.6094 15.8119 22.6094H13.5996C12.5875 22.6094 11.767 21.7889 11.767 20.7768C11.767 20.5211 11.4899 20.3606 11.2678 20.4888C10.3913 20.9949 9.27006 20.6948 8.76384 19.818L7.65797 17.9026ZM9.07916 16.6974C8.91962 16.7895 8.86512 16.9935 8.957 17.1526L10.0629 19.068C10.1547 19.2271 10.3584 19.2818 10.5178 19.1898C11.7392 18.4846 13.267 19.3656 13.267 20.7768C13.267 20.9605 13.4159 21.1094 13.5996 21.1094H15.8119C15.9957 21.1094 16.1448 20.9603 16.1448 20.7764C16.1448 19.3657 17.672 18.4836 18.8939 19.1891C19.0531 19.281 19.2566 19.2264 19.3485 19.0673L20.4548 17.1512C20.5465 16.9923 20.4921 16.7888 20.3329 16.6968C19.1119 15.9919 19.1106 14.2287 20.3325 13.5233C20.4913 13.4316 20.5458 13.2285 20.4541 13.0696L19.3474 11.1528C19.2556 10.9938 19.0523 10.9394 18.8934 11.0312C17.6715 11.7366 16.1448 10.8542 16.1448 9.44397C16.1448 9.2601 15.9957 9.11114 15.8121 9.11114L13.5994 9.11115C13.4158 9.11115 13.267 9.25997 13.267 9.44355C13.267 10.8543 11.7397 11.7356 10.5183 11.0304C10.3592 10.9385 10.1558 10.9931 10.064 11.1521L8.95773 13.0682C8.86583 13.2273 8.92037 13.4308 9.07953 13.5227C10.3018 14.2284 10.3006 15.9922 9.07916 16.6974Z", fill: e })] });
var hp = ({ size: o2 = 20, color: l = "currentColor", ...e }) => C("svg", { ...e, width: o2, height: o2, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M9.59859 3.49501C9.90762 2.65448 10.6463 2 11.616 2C12.5858 2 13.3245 2.65449 13.6335 3.49502C13.8851 3.38177 14.1652 3.31802 14.4678 3.31802C15.7613 3.31802 16.6437 4.48247 16.6437 5.69968V6.13703C16.8528 6.06365 17.0791 6.02338 17.3196 6.02338C18.6131 6.02338 19.4956 7.18783 19.4956 8.40504V14.1096C19.5135 14.8186 19.5172 16.7392 18.6678 18.5107C18.2348 19.4137 17.573 20.2935 16.5699 20.9459C15.565 21.5996 14.2658 21.9935 12.6103 22L12.6074 22C10.7934 22 9.18343 21.1137 8.00738 20.2156C6.81983 19.3087 5.98549 18.3213 5.70771 17.9747C5.64971 17.9023 5.59912 17.8417 5.54755 17.7856L3.20116 15.2304L3.18883 15.2167C2.38497 14.2968 2.38497 12.842 3.18883 11.9221C4.03934 10.9489 5.46177 10.943 6.31921 11.9044L6.58834 12.1788V5.69968C6.58834 4.48247 7.47077 3.31802 8.76424 3.31802C9.06687 3.31802 9.347 3.38177 9.59859 3.49501ZM9.44013 5.69273V11.0417C9.44013 11.4559 9.77592 11.7917 10.1901 11.7917C10.6043 11.7917 10.9401 11.4559 10.9401 11.0417L10.9402 5.69968C10.9402 5.69646 10.9401 5.69323 10.9401 5.69V4.38166C10.9401 3.79659 11.3345 3.5 11.616 3.5C11.8976 3.5 12.2919 3.79659 12.2919 4.38166V11.2299C12.2919 11.6441 12.6277 11.9799 13.0419 11.9799C13.4562 11.9799 13.7919 11.6441 13.7919 11.2299V5.69968C13.7919 5.11461 14.1863 4.81802 14.4678 4.81802C14.7494 4.81802 15.1437 5.11461 15.1437 5.69968V11.2299C15.1437 11.6441 15.4795 11.9799 15.8937 11.9799C16.308 11.9799 16.6437 11.6441 16.6437 11.2299V8.40504C16.6437 7.81997 17.0381 7.52338 17.3196 7.52338C17.6012 7.52338 17.9956 7.81997 17.9956 8.40504L17.9956 14.1192L17.9958 14.1386C18.013 14.7999 18.0053 16.4231 17.3152 17.8622C16.9765 18.5686 16.4808 19.2145 15.7521 19.6885C15.0254 20.1612 14.0188 20.4942 12.6059 20.5C11.2702 20.4995 9.98186 19.8361 8.91777 19.0235C7.86479 18.2194 7.11701 17.3346 6.87825 17.0367C6.81079 16.9525 6.73649 16.8626 6.65237 16.771L4.31323 14.2238C4.00849 13.8684 4.01018 13.2618 4.31832 12.9092C4.57632 12.6139 4.94736 12.6139 5.20537 12.9092C5.21482 12.92 5.22459 12.9305 5.23465 12.9408L6.80288 14.5398C7.01621 14.7573 7.33994 14.8242 7.62198 14.7089C7.90403 14.5937 8.08834 14.3193 8.08834 14.0146V5.69968C8.08834 5.11461 8.48271 4.81802 8.76424 4.81802C9.04466 4.81802 9.43702 5.11226 9.44013 5.69273Z", fill: l }) });
var Ap = ({ size: o2 = 20, color: l = "currentColor", ...e }) => C("svg", { ...e, width: o2, height: o2, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M11.8227 4.77124L12 4.94862L12.1773 4.77135C14.4244 2.52427 18.0676 2.52427 20.3147 4.77134C22.5618 7.01842 22.5618 10.6616 20.3147 12.9087L13.591 19.6324C12.7123 20.5111 11.2877 20.5111 10.409 19.6324L3.6853 12.9086C1.43823 10.6615 1.43823 7.01831 3.6853 4.77124C5.93237 2.52417 9.5756 2.52417 11.8227 4.77124ZM10.762 5.8319C9.10073 4.17062 6.40725 4.17062 4.74596 5.8319C3.08468 7.49319 3.08468 10.1867 4.74596 11.848L11.4697 18.5718C11.7625 18.8647 12.2374 18.8647 12.5303 18.5718L19.254 11.8481C20.9153 10.1868 20.9153 7.49329 19.254 5.83201C17.5927 4.17072 14.8993 4.17072 13.238 5.83201L12.5304 6.53961C12.3897 6.68026 12.199 6.75928 12 6.75928C11.8011 6.75928 11.6104 6.68026 11.4697 6.53961L10.762 5.8319Z", fill: l }) });
var Mc = ({ size: o2 = 20, color: l = "currentColor", ...e }) => C("svg", { ...e, width: o2, height: o2, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M12.45 4.90342C12.1833 4.70342 11.8167 4.70342 11.55 4.90342L5.05 9.77842C4.86115 9.92006 4.75 10.1423 4.75 10.3784V18.4998C4.75 18.9141 5.08579 19.2498 5.5 19.2498H9V16.9998C9 15.343 10.3431 13.9998 12 13.9998C13.6569 13.9998 15 15.343 15 16.9998V19.2498H18.5C18.9142 19.2498 19.25 18.9141 19.25 18.4998V10.3784C19.25 10.1423 19.1389 9.92006 18.95 9.77842L12.45 4.90342ZM10.65 3.70342C11.45 3.10342 12.55 3.10342 13.35 3.70342L19.85 8.57842C20.4166 9.00334 20.75 9.67021 20.75 10.3784V18.4998C20.75 19.7425 19.7426 20.7498 18.5 20.7498H14.25C13.8358 20.7498 13.5 20.4141 13.5 19.9998V16.9998C13.5 16.1714 12.8284 15.4998 12 15.4998C11.1716 15.4998 10.5 16.1714 10.5 16.9998V19.9998C10.5 20.4141 10.1642 20.7498 9.75 20.7498H5.5C4.25736 20.7498 3.25 19.7425 3.25 18.4998V10.3784C3.25 9.67021 3.58344 9.00334 4.15 8.57842L10.65 3.70342Z", fill: l }) });
var au = ({ size: l = 20, color: e = "currentColor", ...t }) => o("svg", { ...t, width: l, height: l, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: [C("path", { d: "M12 8.24961C12.4142 8.24961 12.75 8.5854 12.75 8.99961V9.43739C13.7408 9.58802 14.5 10.4436 14.5 11.4765C14.5 11.8907 14.1642 12.2265 13.75 12.2265C13.3358 12.2265 13 11.8907 13 11.4765C13 11.1657 12.7481 10.9139 12.4374 10.9139H11.75C11.3358 10.9139 11 11.2496 11 11.6639V11.9289C11 12.2416 11.1939 12.5214 11.4866 12.6312L13.0401 13.2138C13.9182 13.5431 14.5 14.3826 14.5 15.3205V15.5855C14.5 16.6563 13.752 17.5524 12.75 17.7798V18.2496C12.75 18.6638 12.4142 18.9996 12 18.9996C11.5858 18.9996 11.25 18.6638 11.25 18.2496V17.812C10.2592 17.6614 9.5 16.8058 9.5 15.7729C9.5 15.3587 9.83579 15.0229 10.25 15.0229C10.6642 15.0229 11 15.3587 11 15.7729C11 16.0837 11.2519 16.3355 11.5626 16.3355H12.25C12.6642 16.3355 13 15.9998 13 15.5855V15.3205C13 15.0078 12.8061 14.728 12.5134 14.6182L10.9599 14.0356C10.0818 13.7063 9.5 12.8668 9.5 11.9289V11.6639C9.5 10.5931 10.248 9.69697 11.25 9.46961V8.99961C11.25 8.5854 11.5858 8.24961 12 8.24961Z", fill: e }), C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M13.0927 3.01432C13.0296 2.97929 12.9653 2.94752 12.9 2.91899V1.99961C12.9 1.50255 12.4971 1.09961 12 1.09961C11.5029 1.09961 11.1 1.50255 11.1 1.99961V2.91899C11.0347 2.94752 10.9704 2.97929 10.9073 3.01432L5.6573 5.93099C4.94301 6.32782 4.5 7.08071 4.5 7.89784V19.7496C4.5 20.9923 5.50736 21.9996 6.75 21.9996H17.25C18.4926 21.9996 19.5 20.9923 19.5 19.7496V7.89784C19.5 7.08071 19.057 6.32782 18.3427 5.93099L13.0927 3.01432ZM12 6.64961C12.4971 6.64961 12.9 6.24667 12.9 5.74961V4.6232L17.6142 7.24222C17.8523 7.3745 18 7.62546 18 7.89784V19.7496C18 20.1638 17.6642 20.4996 17.25 20.4996H6.75C6.33579 20.4996 6 20.1638 6 19.7496V7.89784C6 7.62546 6.14767 7.3745 6.38577 7.24222L11.1 4.6232V5.74961C11.1 6.24667 11.5029 6.64961 12 6.64961Z", fill: e })] });
var Tu = ({ size: l = 20, color: e = "currentColor", ...t }) => o("svg", { ...t, width: l, height: l, viewBox: "0 0 25 24", xmlns: "http://www.w3.org/2000/svg", children: [C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M12.8609 4.96887C12.3192 4.7747 11.727 4.7747 11.1853 4.96887L2.91085 7.935C1.73804 8.35542 1.73806 10.014 2.91085 10.4344L11.1853 13.4006C11.727 13.5948 12.3192 13.5948 12.8609 13.4006L21.1354 10.4344C22.3082 10.014 22.3082 8.35542 21.1354 7.93501L12.8609 4.96887ZM11.6915 6.38089C11.9059 6.30403 12.1403 6.30403 12.3547 6.38089L20.1764 9.18473L12.3547 11.9886C12.1403 12.0654 11.9059 12.0654 11.6915 11.9886L3.86977 9.18473L11.6915 6.38089Z", fill: e }), C("path", { d: "M2.91085 13.5646L5.05398 12.7964L7.27658 13.5931L3.86977 14.8144L11.6915 17.6182C11.9059 17.6951 12.1403 17.6951 12.3547 17.6182L20.1764 14.8144L16.7695 13.5931L18.9921 12.7964L21.1354 13.5646C22.3082 13.9851 22.3082 15.6437 21.1354 16.0641L12.8609 19.0302C12.3192 19.2244 11.727 19.2244 11.1853 19.0302L2.91085 16.0641C1.73806 15.6437 1.73804 13.9851 2.91085 13.5646Z", fill: e })] });
var Xu = ({ size: o2 = 20, color: l = "currentColor", ...e }) => C("svg", { ...e, width: o2, height: o2, viewBox: "0 0 25 24", xmlns: "http://www.w3.org/2000/svg", children: C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M5.52344 3.25C4.2808 3.25 3.27344 4.25736 3.27344 5.5V18.5C3.27344 19.7426 4.2808 20.75 5.52344 20.75H18.5234C19.7661 20.75 20.7734 19.7426 20.7734 18.5V5.5C20.7734 4.25736 19.7661 3.25 18.5234 3.25H5.52344ZM4.77344 5.5C4.77344 5.08579 5.10922 4.75 5.52344 4.75H18.5234C18.9377 4.75 19.2734 5.08579 19.2734 5.5V8.58301L4.77344 8.58301V5.5ZM4.77344 10.083V18.5C4.77344 18.9142 5.10922 19.25 5.52344 19.25H8.60645L8.60645 10.083H4.77344ZM10.1064 10.083V13.916H19.2734V10.083L10.1064 10.083ZM10.1064 15.416H19.2734V18.5C19.2734 18.9142 18.9377 19.25 18.5234 19.25H10.1064V15.416Z", fill: l }) });
var XV = ({ size: l = 20, color: e = "currentColor", ...t }) => o("svg", { ...t, width: l, height: l, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: [C("path", { d: "M10.5 16C10.5 15.1716 11.1716 14.5 12 14.5C12.8284 14.5 13.5 15.1716 13.5 16V17.5C13.5 18.3284 12.8284 19 12 19C11.1716 19 10.5 18.3284 10.5 17.5V16Z", fill: e }), C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M6.75 7.25C6.75 4.3505 9.10051 2 12 2C14.8995 2 17.25 4.35051 17.25 7.25V9.125H18.5C19.7426 9.125 20.75 10.1324 20.75 11.375V17.2495C20.75 19.8729 18.6234 21.9995 16 21.9995H8C5.37665 21.9995 3.25 19.8729 3.25 17.2495V11.375C3.25 10.1324 4.25736 9.125 5.5 9.125H6.75V7.25ZM8.25 9.125H15.75V7.25C15.75 5.17893 14.0711 3.5 12 3.5C9.92893 3.5 8.25 5.17893 8.25 7.25V9.125ZM5.5 10.625C5.08579 10.625 4.75 10.9608 4.75 11.375V17.2495C4.75 19.0444 6.20507 20.4995 8 20.4995H16C17.7949 20.4995 19.25 19.0444 19.25 17.2495V11.375C19.25 10.9608 18.9142 10.625 18.5 10.625H5.5Z", fill: e })] });
var bZ = ({ size: l = 20, color: e = "currentColor", ...t }) => o("svg", { ...t, width: l, height: l, viewBox: "0 0 25 24", xmlns: "http://www.w3.org/2000/svg", children: [C("path", { d: "M3.5625 6C3.5625 5.58579 3.89829 5.25 4.3125 5.25H20.3125C20.7267 5.25 21.0625 5.58579 21.0625 6C21.0625 6.41421 20.7267 6.75 20.3125 6.75L4.3125 6.75C3.89829 6.75 3.5625 6.41422 3.5625 6Z", fill: e }), C("path", { d: "M3.5625 18C3.5625 17.5858 3.89829 17.25 4.3125 17.25L20.3125 17.25C20.7267 17.25 21.0625 17.5858 21.0625 18C21.0625 18.4142 20.7267 18.75 20.3125 18.75L4.3125 18.75C3.89829 18.75 3.5625 18.4142 3.5625 18Z", fill: e }), C("path", { d: "M4.3125 11.25C3.89829 11.25 3.5625 11.5858 3.5625 12C3.5625 12.4142 3.89829 12.75 4.3125 12.75L20.3125 12.75C20.7267 12.75 21.0625 12.4142 21.0625 12C21.0625 11.5858 20.7267 11.25 20.3125 11.25L4.3125 11.25Z", fill: e })] });
var zv = ({ size: l = 20, color: e = "currentColor", ...t }) => o("svg", { ...t, width: l, height: l, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: [C("path", { d: "M8.55814 11.25C8.14393 11.25 7.80814 11.5858 7.80814 12C7.80814 12.4142 8.14393 12.75 8.55814 12.75H15.4419C15.8561 12.75 16.1919 12.4142 16.1919 12C16.1919 11.5858 15.8561 11.25 15.4419 11.25H8.55814Z", fill: e }), C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2ZM3.5 12C3.5 7.30558 7.30558 3.5 12 3.5C16.6944 3.5 20.5 7.30558 20.5 12C20.5 16.6944 16.6944 20.5 12 20.5C7.30558 20.5 3.5 16.6944 3.5 12Z", fill: e })] });
var $a = ({ size: o2 = 20, color: l = "currentColor", ...e }) => C("svg", { ...e, width: o2, height: o2, viewBox: "0 0 24 25", xmlns: "http://www.w3.org/2000/svg", children: C("path", { d: "M20.5565 10.6338C21.0009 9.27575 20.8528 7.76958 20.1367 6.53501C19.0503 4.63378 16.8528 3.67081 14.7046 4.11526C13.7663 3.05353 12.3836 2.46094 10.9515 2.46094C8.75399 2.46094 6.82807 3.86835 6.13671 5.94242C4.7293 6.23872 3.51943 7.10291 2.80338 8.36217C1.71696 10.2634 1.96387 12.6338 3.42066 14.2634C2.97622 15.6461 3.14906 17.1276 3.8651 18.3622C4.95152 20.2634 7.14906 21.2511 9.2972 20.7819C10.2602 21.8437 11.6182 22.4609 13.0503 22.4609C15.2478 22.4609 17.1737 21.0535 17.8651 18.9795C19.2725 18.6832 20.4824 17.819 21.1984 16.5597C22.2849 14.6585 22.0379 12.2634 20.5565 10.6338ZM13.0503 21.1523C12.1614 21.1523 11.3219 20.856 10.6552 20.2881C10.6799 20.2634 10.754 20.2387 10.7787 20.214L14.754 17.9177C14.9515 17.7943 15.075 17.5967 15.075 17.3498V11.7449L16.754 12.7079C16.7787 12.7079 16.7787 12.7325 16.7787 12.7572V17.3992C16.8034 19.4733 15.1244 21.1523 13.0503 21.1523ZM5.00091 17.7202C4.55646 16.9548 4.40831 16.0659 4.55646 15.2017C4.58115 15.2264 4.63054 15.2511 4.67992 15.2758L8.65523 17.572C8.85276 17.6955 9.09967 17.6955 9.2972 17.572L14.1614 14.7572V16.7079C14.1614 16.7325 14.1614 16.7572 14.1367 16.7572L10.112 19.0782C8.33424 20.1153 6.03794 19.498 5.00091 17.7202ZM3.96387 9.02884C4.40831 8.26341 5.09967 7.69551 5.91449 7.37452V12.1153C5.91449 12.3375 6.03794 12.5597 6.23548 12.6832L11.0997 15.498L9.42066 16.4609C9.39597 16.4609 9.37128 16.4856 9.37128 16.4609L5.34659 14.1399C3.51943 13.1029 2.92683 10.8066 3.96387 9.02884ZM17.791 12.2387L12.9268 9.4239L14.6058 8.46094C14.6305 8.46094 14.6552 8.43625 14.6552 8.46094L18.6799 10.7819C20.4824 11.819 21.075 14.1153 20.0379 15.893C19.5935 16.6585 18.9021 17.2264 18.0873 17.5227V12.8066C18.112 12.5844 17.9886 12.3622 17.791 12.2387ZM19.4454 9.7202C19.4207 9.69551 19.3713 9.67081 19.3219 9.64612L15.3466 7.34983C15.1491 7.22637 14.9021 7.22637 14.7046 7.34983L9.84041 10.1646V8.21402C9.84041 8.18933 9.84041 8.16464 9.86511 8.16464L13.8898 5.84365C15.6923 4.80662 17.9639 5.4239 19.0009 7.22637C19.4454 7.96711 19.5935 8.856 19.4454 9.7202ZM8.92683 13.177L7.24782 12.214C7.22313 12.214 7.22313 12.1893 7.22313 12.1646V7.52267C7.22313 5.44859 8.90214 3.76958 10.9762 3.76958C11.8651 3.76958 12.7046 4.06588 13.3713 4.63378C13.3466 4.65847 13.2972 4.68316 13.2478 4.70785L9.27251 7.00415C9.07498 7.1276 8.95152 7.32514 8.95152 7.57205V13.177H8.92683ZM9.84041 11.2017L12.0133 9.94242L14.1861 11.2017V13.6955L12.0133 14.9548L9.84041 13.6955V11.2017Z", fill: l }) });
var tx = ({ size: o2 = 20, color: l = "currentColor", ...e }) => C("svg", { ...e, width: o2, height: o2, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M19.3028 3.7801C18.4241 2.90142 16.9995 2.90142 16.1208 3.7801L14.3498 5.5511C14.3442 5.55633 14.3387 5.56166 14.3333 5.5671C14.3279 5.57253 14.3225 5.57803 14.3173 5.58359L5.83373 14.0672C5.57259 14.3283 5.37974 14.6497 5.27221 15.003L4.05205 19.0121C3.9714 19.2771 4.04336 19.565 4.23922 19.7608C4.43508 19.9567 4.72294 20.0287 4.98792 19.948L8.99703 18.7279C9.35035 18.6203 9.67176 18.4275 9.93291 18.1663L20.22 7.87928C21.0986 7.0006 21.0986 5.57598 20.22 4.6973L19.3028 3.7801ZM14.8639 7.15833L6.89439 15.1278C6.80735 15.2149 6.74306 15.322 6.70722 15.4398L5.8965 18.1036L8.56029 17.2928C8.67806 17.257 8.7852 17.1927 8.87225 17.1057L16.8417 9.13619L14.8639 7.15833ZM17.9024 8.07553L19.1593 6.81862C19.4522 6.52572 19.4522 6.05085 19.1593 5.75796L18.2421 4.84076C17.9492 4.54787 17.4743 4.54787 17.1814 4.84076L15.9245 6.09767L17.9024 8.07553Z", fill: l }) });
var Nx = ({ size: o2 = 20, color: l = "currentColor", ...e }) => C("svg", { ...e, width: o2, height: o2, viewBox: "0 0 25 24", xmlns: "http://www.w3.org/2000/svg", children: C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M19.4357 13.9174C20.8659 13.0392 20.8659 10.9608 19.4357 10.0826L9.55234 4.01389C8.05317 3.09335 6.125 4.17205 6.125 5.93128L6.125 18.0688C6.125 19.828 8.05317 20.9067 9.55234 19.9861L19.4357 13.9174ZM18.6508 11.3609C19.1276 11.6536 19.1276 12.3464 18.6508 12.6391L8.76745 18.7079C8.26772 19.0147 7.625 18.6552 7.625 18.0688L7.625 5.93128C7.625 5.34487 8.26772 4.9853 8.76745 5.29215L18.6508 11.3609Z", fill: l }) });
var lk = ({ size: o2 = 20, color: l = "currentColor", ...e }) => C("svg", { ...e, width: o2, height: o2, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: C("path", { d: "M11.2502 6C11.2502 5.58579 11.586 5.25 12.0002 5.25C12.4145 5.25 12.7502 5.58579 12.7502 6V11.2502H18.0007C18.4149 11.2502 18.7507 11.586 18.7507 12.0002C18.7507 12.4145 18.4149 12.7502 18.0007 12.7502H12.7502V18.0007C12.7502 18.4149 12.4145 18.7507 12.0002 18.7507C11.586 18.7507 11.2502 18.4149 11.2502 18.0007V12.7502H6C5.58579 12.7502 5.25 12.4145 5.25 12.0002C5.25 11.586 5.58579 11.2502 6 11.2502H11.2502V6Z", fill: l }) });
var Mk = ({ size: l = 20, color: e = "currentColor", ...t }) => o("svg", { ...t, width: l, height: l, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: [C("path", { d: "M11.25 13.42C11.25 13.8342 11.5858 14.17 12 14.17C12.4142 14.17 12.75 13.8342 12.75 13.42V2.75C12.75 2.33579 12.4142 2 12 2C11.5858 2 11.25 2.33579 11.25 2.75V13.42Z", fill: e }), C("path", { d: "M19.6248 12.8747C19.6248 9.44687 17.3628 6.54733 14.25 5.58733V4.02942C18.2014 5.0315 21.1248 8.61171 21.1248 12.8747C21.1248 17.9142 17.0394 21.9995 11.9999 21.9995C6.96043 21.9995 2.875 17.9142 2.875 12.8747C2.875 8.61165 5.79852 5.0314 9.75 4.02938V5.58728C6.63713 6.54722 4.375 9.44681 4.375 12.8747C4.375 17.0858 7.78883 20.4995 11.9999 20.4995C16.211 20.4995 19.6248 17.0858 19.6248 12.8747Z", fill: e })] });
var Kk = ({ size: l = 20, color: e = "currentColor", ...t }) => o("svg", { ...t, width: l, height: l, viewBox: "0 0 25 24", xmlns: "http://www.w3.org/2000/svg", children: [C("path", { d: "M3.13644 9.54175C3.02923 9.94185 3.26667 10.3531 3.66676 10.4603C4.06687 10.5675 4.47812 10.3301 4.58533 9.92998C5.04109 8.22904 6.04538 6.72602 7.44243 5.65403C8.83948 4.58203 10.5512 4.00098 12.3122 4.00098C14.0731 4.00098 15.7848 4.58203 17.1819 5.65403C18.3999 6.58866 19.3194 7.85095 19.8371 9.28639L18.162 8.34314C17.801 8.1399 17.3437 8.26774 17.1405 8.62867C16.9372 8.98959 17.0651 9.44694 17.426 9.65017L20.5067 11.3849C20.68 11.4825 20.885 11.5072 21.0766 11.4537C21.2682 11.4001 21.4306 11.2727 21.5282 11.0993L23.2629 8.01828C23.4661 7.65734 23.3382 7.2 22.9773 6.99679C22.6163 6.79358 22.159 6.92145 21.9558 7.28239L21.195 8.63372C20.5715 6.98861 19.5007 5.54258 18.095 4.464C16.436 3.19099 14.4033 2.50098 12.3122 2.50098C10.221 2.50098 8.1883 3.19099 6.52928 4.464C4.87027 5.737 3.67766 7.52186 3.13644 9.54175Z", fill: e }), C("path", { d: "M21.4906 14.4582C21.5978 14.0581 21.3604 13.6469 20.9603 13.5397C20.5602 13.4325 20.1489 13.6699 20.0417 14.07C19.5859 15.7709 18.5816 17.274 17.1846 18.346C15.7875 19.418 14.0758 19.999 12.3149 19.999C10.5539 19.999 8.84219 19.418 7.44514 18.346C6.2292 17.4129 5.31079 16.1534 4.79261 14.721L6.45529 15.6573C6.81622 15.8605 7.27356 15.7327 7.47679 15.3718C7.68003 15.0108 7.55219 14.5535 7.19127 14.3502L4.11056 12.6155C3.93723 12.5179 3.73222 12.4932 3.54065 12.5467C3.34907 12.6003 3.18662 12.7278 3.08903 12.9011L1.3544 15.9821C1.15119 16.3431 1.27906 16.8004 1.64 17.0036C2.00094 17.2068 2.45828 17.079 2.66149 16.718L3.42822 15.3562C4.05115 17.0054 5.12348 18.4552 6.532 19.536C8.19102 20.809 10.2237 21.499 12.3149 21.499C14.406 21.499 16.4387 20.809 18.0977 19.536C19.7568 18.263 20.9494 16.4781 21.4906 14.4582Z", fill: e })] });
var Vm = ({ size: o2 = 20, color: l = "currentColor", ...e }) => C("svg", { ...e, width: o2, height: o2, viewBox: "0 0 24 25", xmlns: "http://www.w3.org/2000/svg", children: C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M11.25 2.75C6.14154 2.75 2 6.89029 2 11.998C2 17.1056 6.14154 21.2459 11.25 21.2459C13.5335 21.2459 15.6238 20.4187 17.2373 19.0475L20.7182 22.5287C21.011 22.8216 21.4859 22.8217 21.7788 22.5288C22.0717 22.2359 22.0718 21.761 21.7789 21.4681L18.2983 17.9872C19.6714 16.3736 20.5 14.2826 20.5 11.998C20.5 6.89029 16.3585 2.75 11.25 2.75ZM3.5 11.998C3.5 7.71905 6.96962 4.25 11.25 4.25C15.5304 4.25 19 7.71905 19 11.998C19 16.2769 15.5304 19.7459 11.25 19.7459C6.96962 19.7459 3.5 16.2769 3.5 11.998Z", fill: l }) });
var Am = ({ size: l = 20, color: e = "currentColor", ...t }) => o("svg", { ...t, width: l, height: l, viewBox: "0 0 24 25", xmlns: "http://www.w3.org/2000/svg", children: [C("path", { d: "M15.277 13.2502C15.277 13.6645 14.9412 14.0002 14.527 14.0002L7.96792 14.0002C7.5537 14.0002 7.21792 13.6645 7.21792 13.2502C7.21792 12.836 7.5537 12.5002 7.96792 12.5002L14.527 12.5002C14.9412 12.5002 15.277 12.836 15.277 13.2502Z", fill: e }), C("path", { d: "M12.8468 9.75025C12.8468 10.1645 12.511 10.5002 12.0968 10.5002H7.96719C7.55297 10.5002 7.21719 10.1645 7.21719 9.75025C7.21719 9.33603 7.55297 9.00025 7.96719 9.00025L12.0968 9.00025C12.511 9.00025 12.8468 9.33603 12.8468 9.75025Z", fill: e }), C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M2 11.4989C2 6.39126 6.14154 2.25098 11.25 2.25098C16.3585 2.25098 20.5 6.39126 20.5 11.4989C20.5 13.7836 19.6714 15.8747 18.2983 17.4883L21.7791 20.9695C22.072 21.2624 22.072 21.7372 21.7791 22.0301C21.4862 22.323 21.0113 22.323 20.7184 22.0301L17.2372 18.5486C15.6237 19.9197 13.5334 20.7469 11.25 20.7469C6.14154 20.7469 2 16.6066 2 11.4989ZM11.25 3.75098C6.96962 3.75098 3.5 7.22003 3.5 11.4989C3.5 15.7779 6.96962 19.2469 11.25 19.2469C15.5304 19.2469 19 15.7779 19 11.4989C19 7.22003 15.5304 3.75098 11.25 3.75098Z", fill: e })] });
var YS = ({ size: l = 20, color: e = "currentColor", ...t }) => o("svg", { ...t, width: l, height: l, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: [C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M10.787 15.6126C11.1768 15.7025 11.5829 15.75 12 15.75C14.9685 15.75 17.375 13.3435 17.375 10.375C17.375 7.40647 14.9685 5 12 5C9.03147 5 6.625 7.40647 6.625 10.375C6.625 12.0921 7.43016 13.6211 8.68354 14.6052L7.33598 16.4521C6.83707 17.1359 7.04126 18.1013 7.77431 18.5245C8.50736 18.9477 9.44555 18.6419 9.78827 17.8679L10.787 15.6126ZM12 6.5C9.8599 6.5 8.125 8.2349 8.125 10.375C8.125 11.5943 8.68811 12.682 9.56845 13.3923L11.0434 11.3708C11.2694 11.0611 11.6924 10.9717 12.0243 11.1633C12.3562 11.3549 12.4903 11.766 12.3351 12.1165L11.4101 14.2054C11.6024 14.2348 11.7994 14.25 12 14.25C14.1401 14.25 15.875 12.5151 15.875 10.375C15.875 8.2349 14.1401 6.5 12 6.5Z", fill: e }), C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M18 2C19.2426 2 20.25 3.00736 20.25 4.25V19.75C20.25 20.9926 19.2426 22 18 22H6C4.75736 22 3.75 20.9926 3.75 19.75V4.25C3.75 3.00736 4.75736 2 6 2H18ZM18.75 4.25C18.75 3.83579 18.4142 3.5 18 3.5L6 3.5C5.58579 3.5 5.25 3.83579 5.25 4.25L5.25 19.75C5.25 20.1642 5.58579 20.5 6 20.5H18C18.4142 20.5 18.75 20.1642 18.75 19.75L18.75 4.25Z", fill: e })] });
var Ny = ({ size: l = 20, color: e = "currentColor", ...t }) => o("svg", { ...t, width: l, height: l, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: [C("path", { d: "M14.7223 12.7585C14.7426 12.3448 14.4237 11.9929 14.01 11.9726C13.5963 11.9522 13.2444 12.2711 13.2241 12.6848L12.9999 17.2415C12.9796 17.6552 13.2985 18.0071 13.7122 18.0274C14.1259 18.0478 14.4778 17.7289 14.4981 17.3152L14.7223 12.7585Z", fill: e }), C("path", { d: "M9.98802 11.9726C9.5743 11.9929 9.25542 12.3448 9.27577 12.7585L9.49993 17.3152C9.52028 17.7289 9.87216 18.0478 10.2859 18.0274C10.6996 18.0071 11.0185 17.6552 10.9981 17.2415L10.774 12.6848C10.7536 12.2711 10.4017 11.9522 9.98802 11.9726Z", fill: e }), C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M10.249 2C9.00638 2 7.99902 3.00736 7.99902 4.25V5H5.5C4.25736 5 3.25 6.00736 3.25 7.25C3.25 8.28958 3.95503 9.16449 4.91303 9.42267L5.54076 19.8848C5.61205 21.0729 6.59642 22 7.78672 22H16.2113C17.4016 22 18.386 21.0729 18.4573 19.8848L19.085 9.42267C20.043 9.16449 20.748 8.28958 20.748 7.25C20.748 6.00736 19.7407 5 18.498 5H15.999V4.25C15.999 3.00736 14.9917 2 13.749 2H10.249ZM14.499 5V4.25C14.499 3.83579 14.1632 3.5 13.749 3.5H10.249C9.83481 3.5 9.49902 3.83579 9.49902 4.25V5H14.499ZM5.5 6.5C5.08579 6.5 4.75 6.83579 4.75 7.25C4.75 7.66421 5.08579 8 5.5 8H18.498C18.9123 8 19.248 7.66421 19.248 7.25C19.248 6.83579 18.9123 6.5 18.498 6.5H5.5ZM6.42037 9.5H17.5777L16.96 19.7949C16.9362 20.191 16.6081 20.5 16.2113 20.5H7.78672C7.38995 20.5 7.06183 20.191 7.03807 19.7949L6.42037 9.5Z", fill: e })] });
var rO = ({ size: o2 = 20, color: l = "currentColor", ...e }) => C("svg", { ...e, width: o2, height: o2, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: C("path", { d: "M4.75 4C4.75 3.58579 4.41421 3.25 4 3.25C3.58579 3.25 3.25 3.58579 3.25 4V18.5C3.25 19.7426 4.25736 20.75 5.5 20.75H20.0005C20.4147 20.75 20.7505 20.4142 20.7505 20C20.7505 19.5858 20.4147 19.25 20.0005 19.25H5.5C5.08579 19.25 4.75 18.9142 4.75 18.5V10.3913L8.66929 14.3108C8.80994 14.4514 9.00071 14.5304 9.19963 14.5304C9.39854 14.5304 9.58931 14.4514 9.72996 14.3108L13.1523 10.8884L16.9328 14.6689H15.2115C14.7973 14.6689 14.4615 15.0047 14.4615 15.4189C14.4615 15.8332 14.7973 16.1689 15.2115 16.1689H18.7473C18.9462 16.1689 19.137 16.0899 19.2777 15.9493C19.4183 15.8086 19.4973 15.6178 19.4973 15.4189L19.4971 11.8834C19.497 11.4691 19.1612 11.1334 18.747 11.1334C18.3328 11.1334 17.997 11.4692 17.9971 11.8835L17.9972 13.612L13.6826 9.29743C13.542 9.15678 13.3512 9.07776 13.1523 9.07776C12.9534 9.07776 12.7626 9.15678 12.622 9.29743L9.19964 12.7198L4.75 8.26994V4Z", fill: l }) });
var wO = ({ size: o2 = 20, color: l = "currentColor", ...e }) => C("svg", { ...e, width: o2, height: o2, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: C("path", { d: "M4.75 4C4.75 3.58579 4.41421 3.25 4 3.25C3.58579 3.25 3.25 3.58579 3.25 4V18.5C3.25 19.7426 4.25736 20.75 5.5 20.75H20.0005C20.4147 20.75 20.7505 20.4142 20.7505 20C20.7505 19.5858 20.4147 19.25 20.0005 19.25H5.5C5.08579 19.25 4.75 18.9142 4.75 18.5V15.7301L9.19964 11.2802L12.622 14.7026C12.7626 14.8432 12.9534 14.9222 13.1523 14.9222C13.3512 14.9222 13.542 14.8432 13.6826 14.7026L17.9972 10.388L17.9971 12.1165C17.997 12.5308 18.3328 12.8666 18.747 12.8666C19.1612 12.8666 19.497 12.5309 19.4971 12.1166L19.4973 8.58111C19.4973 8.38218 19.4183 8.19141 19.2777 8.05074C19.137 7.91008 18.9462 7.83105 18.7473 7.83105H15.2115C14.7973 7.83105 14.4615 8.16684 14.4615 8.58105C14.4615 8.99527 14.7973 9.33105 15.2115 9.33105H16.9328L13.1523 13.1116L9.72996 9.68923C9.58931 9.54857 9.39854 9.46956 9.19963 9.46956C9.00071 9.46956 8.80994 9.54858 8.66929 9.68924L4.75 13.6087V4Z", fill: l }) });
var KO = ({ size: l = 20, color: e = "currentColor", ...t }) => o("svg", { ...t, width: l, height: l, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: [C("path", { d: "M10.5 16C10.5 15.1716 11.1716 14.5 12 14.5C12.8284 14.5 13.5 15.1716 13.5 16V17.5C13.5 18.3284 12.8284 19 12 19C11.1716 19 10.5 18.3284 10.5 17.5V16Z", fill: e }), C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M12 2C9.10051 2 6.75 4.35051 6.75 7.25V9.125H5.5C4.25736 9.125 3.25 10.1324 3.25 11.375V17.2495C3.25 19.8729 5.37665 21.9995 8 21.9995H16C18.6234 21.9995 20.75 19.8729 20.75 17.2495V11.375C20.75 10.1324 19.7426 9.125 18.5 9.125H8.25V7.25C8.25 5.17893 9.92893 3.5 12 3.5C13.4184 3.5 14.6541 4.28724 15.2919 5.45221C15.4909 5.81553 15.9466 5.9488 16.31 5.74987C16.6733 5.55095 16.8066 5.09516 16.6076 4.73184C15.7172 3.10553 13.9882 2 12 2ZM7.47268 10.625C7.48175 10.6253 7.49085 10.6255 7.5 10.6255C7.50915 10.6255 7.51825 10.6253 7.52732 10.625H18.5C18.9142 10.625 19.25 10.9608 19.25 11.375V17.2495C19.25 19.0444 17.7949 20.4995 16 20.4995H8C6.20507 20.4995 4.75 19.0444 4.75 17.2495V11.375C4.75 10.9608 5.08579 10.625 5.5 10.625H7.47268Z", fill: e })] });
var wD = ({ size: l = 20, color: e = "currentColor", ...t }) => o("svg", { ...t, width: l, height: l, viewBox: "0 0 25 24", xmlns: "http://www.w3.org/2000/svg", children: [C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M16.4337 6.35C16.4337 8.74 14.4937 10.69 12.0937 10.69L12.0837 10.68C9.69365 10.68 7.74365 8.73 7.74365 6.34C7.74365 3.95 9.70365 2 12.0937 2C14.4837 2 16.4337 3.96 16.4337 6.35ZM14.9337 6.34C14.9337 4.78 13.6637 3.5 12.0937 3.5C10.5337 3.5 9.25365 4.78 9.25365 6.34C9.25365 7.9 10.5337 9.18 12.0937 9.18C13.6537 9.18 14.9337 7.9 14.9337 6.34Z", fill: e }), C("path", { d: "M12.0235 12.1895C14.6935 12.1895 16.7835 12.9395 18.2335 14.4195V14.4095C20.2801 16.4956 20.2739 19.2563 20.2735 19.4344L20.2735 19.4395C20.2635 19.8495 19.9335 20.1795 19.5235 20.1795H19.5135C19.0935 20.1695 18.7735 19.8295 18.7735 19.4195C18.7735 19.3695 18.7735 17.0895 17.1535 15.4495C15.9935 14.2795 14.2635 13.6795 12.0235 13.6795C9.78346 13.6795 8.05346 14.2795 6.89346 15.4495C5.27346 17.0995 5.27346 19.3995 5.27346 19.4195C5.27346 19.8295 4.94346 20.1795 4.53346 20.1795C4.17346 20.1995 3.77346 19.8595 3.77346 19.4495L3.77345 19.4448C3.77305 19.2771 3.76646 16.506 5.81346 14.4195C7.26346 12.9395 9.35346 12.1895 12.0235 12.1895Z", fill: e })] });
var pD = ({ size: l = 20, color: e = "currentColor", ...t }) => o("svg", { ...t, width: l, height: l, viewBox: "0 0 25 24", xmlns: "http://www.w3.org/2000/svg", children: [C("path", { d: "M15.3289 11.4955C14.4941 11.4955 13.724 11.2188 13.1051 10.7522C13.3972 10.3301 13.6284 9.86262 13.786 9.36254C14.1827 9.7539 14.7276 9.99545 15.3289 9.99545C16.5422 9.99545 17.5258 9.01185 17.5258 7.79851C17.5258 6.58517 16.5422 5.60156 15.3289 5.60156C14.7276 5.60156 14.1827 5.84312 13.786 6.23449C13.6284 5.73441 13.3972 5.26698 13.1051 4.84488C13.7239 4.37824 14.4941 4.10156 15.3289 4.10156C17.3706 4.10156 19.0258 5.75674 19.0258 7.79851C19.0258 9.84027 17.3706 11.4955 15.3289 11.4955Z", fill: e }), C("path", { d: "M14.7723 13.1891C15.0227 13.437 15.2464 13.6945 15.4463 13.9566C16.7954 13.9826 17.7641 14.3143 18.4675 14.7651C19.2032 15.2366 19.6941 15.8677 20.0242 16.5168C20.3563 17.1698 20.5204 17.8318 20.6002 18.337C20.6398 18.5878 20.6579 18.795 20.6661 18.9365C20.6702 19.0071 20.6717 19.061 20.6724 19.0952L20.6726 19.1161L20.6727 19.1313L20.6727 19.1363L21.4197 19.1486C20.6793 19.1358 20.6728 19.136 20.6727 19.1363L20.6727 19.1376C20.6666 19.5509 20.9961 19.8914 21.4096 19.8985C21.8237 19.9057 22.1653 19.5758 22.1725 19.1617L21.4284 19.1488C22.1725 19.1617 22.1725 19.1621 22.1725 19.1617L22.1725 19.1599L22.1726 19.1575L22.1726 19.1511L22.1727 19.1319C22.1727 19.1163 22.1726 19.0951 22.1721 19.0686C22.1712 19.0158 22.1689 18.9419 22.1636 18.85C22.153 18.6665 22.1303 18.4094 22.0819 18.1029C21.9856 17.4936 21.7848 16.6697 21.3612 15.8368C20.9357 15 20.2801 14.1451 19.2768 13.5022C18.2708 12.8574 16.9604 12.4549 15.274 12.4549C14.8284 12.4549 14.4092 12.483 14.0148 12.5362C14.2852 12.7384 14.5376 12.9566 14.7723 13.1891Z", fill: e }), C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M5.13173 7.79855C5.13173 5.75678 6.7869 4.1016 8.82867 4.1016C10.8704 4.1016 12.5256 5.75678 12.5256 7.79855C12.5256 9.84031 10.8704 11.4955 8.82867 11.4955C6.7869 11.4955 5.13173 9.84031 5.13173 7.79855ZM8.82867 5.6016C7.61533 5.6016 6.63173 6.58521 6.63173 7.79855C6.63173 9.01189 7.61533 9.99549 8.82867 9.99549C10.042 9.99549 11.0256 9.01189 11.0256 7.79855C11.0256 6.58521 10.042 5.6016 8.82867 5.6016Z", fill: e }), C("path", { d: "M3.37502 19.1374C3.38126 19.5507 3.0517 19.8914 2.63812 19.8986C2.22397 19.9058 1.88241 19.5759 1.87522 19.1617L2.62511 19.1487C1.87522 19.1617 1.87523 19.1621 1.87522 19.1617L1.87519 19.1599L1.87516 19.1575L1.87509 19.1511L1.875 19.1319C1.87499 19.1163 1.87512 19.0951 1.87559 19.0687C1.87653 19.0158 1.87882 18.942 1.88413 18.85C1.89474 18.6665 1.91745 18.4094 1.96585 18.103C2.0621 17.4936 2.26292 16.6697 2.68648 15.8368C3.11206 15 3.76758 14.1452 4.77087 13.5022C5.77688 12.8575 7.08727 12.455 8.77376 12.455C10.4602 12.455 11.7706 12.8575 12.7767 13.5022C13.7799 14.1452 14.4355 15 14.861 15.8368C15.2846 16.6697 15.4854 17.4936 15.5817 18.103C15.6301 18.4094 15.6528 18.6665 15.6634 18.85C15.6687 18.942 15.671 19.0158 15.6719 19.0687C15.6724 19.0951 15.6725 19.1163 15.6725 19.1319L15.6724 19.1511L15.6724 19.1575L15.6723 19.1599C15.6723 19.1603 15.6723 19.1617 14.9282 19.1488L15.6723 19.1617C15.6651 19.5759 15.3235 19.9058 14.9094 19.8986C14.4959 19.8914 14.1664 19.5509 14.1725 19.1376L14.1725 19.1364C14.1726 19.1361 14.1791 19.1358 14.9199 19.1487L14.1725 19.1364L14.1725 19.1314L14.1724 19.1161L14.1722 19.0952C14.1716 19.061 14.17 19.0072 14.1659 18.9366C14.1577 18.7951 14.1396 18.5878 14.1 18.337C14.0202 17.8319 13.8561 17.1699 13.524 16.5168C13.1939 15.8677 12.703 15.2366 11.9673 14.7651C11.2343 14.2954 10.2132 13.955 8.77376 13.955C7.33434 13.955 6.31319 14.2954 5.58022 14.7651C4.84453 15.2366 4.35363 15.8677 4.02351 16.5168C3.6914 17.1699 3.52727 17.8319 3.44749 18.337C3.40787 18.5878 3.38981 18.7951 3.38163 18.9366C3.37756 19.0072 3.37596 19.061 3.37536 19.0952C3.37505 19.1123 3.375 19.1245 3.375 19.1314L3.37502 19.1374Z", fill: e })] });
var wj = ({ size: o2 = 20, color: l = "currentColor", ...e }) => C("svg", { ...e, width: o2, height: o2, viewBox: "0 0 25 24", xmlns: "http://www.w3.org/2000/svg", children: C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M5.75 3.25C4.50736 3.25 3.5 4.25736 3.5 5.5V18.5C3.5 19.7426 4.50736 20.75 5.75 20.75H18.75C19.9926 20.75 21 19.7426 21 18.5V9.5C21 8.25736 19.9926 7.25 18.75 7.25H18.5V5.5C18.5 4.25736 17.4926 3.25 16.25 3.25H5.75ZM17 7.25V5.5C17 5.08579 16.6642 4.75 16.25 4.75H5.75C5.33579 4.75 5 5.08579 5 5.5V7.25H17ZM5 8.75V18.5C5 18.9142 5.33579 19.25 5.75 19.25H18.75C19.1642 19.25 19.5 18.9142 19.5 18.5V9.5C19.5 9.08579 19.1642 8.75 18.75 8.75H5Z", fill: l }) });
var hb = ({ size: l = 20, color: e = "currentColor", ...t }) => o("svg", { ...t, width: l, height: l, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: [C("path", { d: "M8.78362 8.78412C8.49073 9.07702 8.49073 9.55189 8.78362 9.84478L10.9388 12L8.78362 14.1552C8.49073 14.4481 8.49073 14.923 8.78362 15.2159C9.07652 15.5088 9.55139 15.5088 9.84428 15.2159L11.9995 13.0607L14.1546 15.2158C14.4475 15.5087 14.9224 15.5087 15.2153 15.2158C15.5082 14.9229 15.5082 14.448 15.2153 14.1551L13.0602 12L15.2153 9.84485C15.5082 9.55196 15.5082 9.07708 15.2153 8.78419C14.9224 8.4913 14.4475 8.4913 14.1546 8.78419L11.9995 10.9393L9.84428 8.78412C9.55139 8.49123 9.07652 8.49123 8.78362 8.78412Z", fill: e }), C("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2ZM3.5 12C3.5 7.30558 7.30558 3.5 12 3.5C16.6944 3.5 20.5 7.30558 20.5 12C20.5 16.6944 16.6944 20.5 12 20.5C7.30558 20.5 3.5 16.6944 3.5 12Z", fill: e })] });
export {
Bi as AddAndSubtractIcon,
lk as AddIcon,
$a as AiIcon,
il as BillIcon,
Cl as ChartIcon,
wO as ChartLineIcon,
Jr as CheckCircleIcon,
Md as ChevronDownIcon,
ao as ChevronRightIcon,
hb as CloseCircleIcon,
Yd as CloudIcon,
IL as CreditcardIcon,
Mc as Dashboard1Icon,
Ln as DashboardIcon,
Ny as DeleteIcon,
au as DiscountIcon,
tx as EditIcon,
Am as FileSearchIcon,
kg as FolderIcon,
YS as FolderOpenIcon,
Ue as GiftIcon,
Ap as HeartIcon,
Tu as LayersIcon,
KO as LockCheckedIcon,
XV as LockOnIcon,
Us as LogoutIcon,
ks as MailIcon,
zv as MinusCircleIcon,
Kn as MoneyIcon,
vl as NotificationIcon,
DH as OrderIcon,
Nx as PlayCircleIcon,
Mk as PoweroffIcon,
Kk as RefreshIcon,
yh as RotateIcon,
Vm as SearchIcon,
gn as ServerIcon,
_g as SettingIcon,
hp as StopCircleIcon,
NC as SwapIcon,
_g as SystemSettingIcon,
td as TaskCheckedIcon,
Id as TaskIcon,
tM as TaskSettingIcon,
Xu as TemplateIcon,
rO as TrendDownIcon,
wO as TrendUpIcon,
KC as UndertakeTransactionIcon,
wD as UserCircleIcon,
wD as UserIcon,
pD as UserListIcon,
bZ as ViewListIcon,
wj as WalletIcon
};
+2
View File
@@ -10,11 +10,13 @@ export const CHART_COLORS = {
placeholderColor: 'rgba(0, 0, 0, 0.35)',
borderColor: '#dcdcdc',
containerColor: '#fff',
secondaryContainerColor: '#f3f4f6',
},
[ETheme.dark]: {
textColor: 'rgba(255, 255, 255, 0.9)',
placeholderColor: 'rgba(255, 255, 255, 0.35)',
borderColor: '#5e5e5e',
containerColor: '#242424',
secondaryContainerColor: '#343434',
},
};
-22
View File
@@ -1,22 +0,0 @@
export default {
mock: {
// 本地mock数据
API: '',
},
development: {
// 开发环境接口请求
API: 'https://service-exndqyuk-1257786608.gz.apigw.tencentcs.com',
},
test: {
// 测试环境接口地址
API: 'https://service-exndqyuk-1257786608.gz.apigw.tencentcs.com',
},
release: {
// 正式环境接口地址
API: 'https://service-bv448zsw-1257786608.gz.apigw.tencentcs.com',
},
site: {
// TDesign部署特殊需要 与release功能一致
API: 'https://service-bv448zsw-1257786608.gz.apigw.tencentcs.com',
},
};
+2 -1
View File
@@ -48,6 +48,7 @@ declare module 'tvision-color';
declare interface ImportMeta {
env: {
MODE: 'development' | 'test' | 'release' | 'mock' | 'site';
MODE: 'development' | 'test' | 'release' | 'site';
BASE_URL: string;
};
}
+44 -3
View File
@@ -7,6 +7,48 @@ import lodashSet from 'lodash/set';
import lodashMap from 'lodash/map';
import { ETheme } from '../types';
const resolveChartTokens = (value: any, colors: Record<string, any>): any => {
if (typeof value === 'string') {
return value
.replaceAll('var(--td-component-border)', colors.borderColor)
.replaceAll('var(--td-text-color-secondary)', colors.placeholderColor)
.replaceAll('var(--td-text-color-primary)', colors.textColor)
.replaceAll('var(--td-bg-color-secondarycontainer)', colors.secondaryContainerColor)
.replaceAll('var(--td-bg-color-container)', colors.containerColor)
.replaceAll('var(--td-brand-color)', colors.colorList?.[0] || colors.textColor)
.replaceAll('var(--td-success-color)', colors.colorList?.[2] || colors.textColor)
.replaceAll('var(--td-warning-color)', colors.colorList?.[3] || colors.textColor)
.replaceAll('var(--td-error-color)', colors.colorList?.[5] || colors.textColor);
}
if (Array.isArray(value)) return value.map((item) => resolveChartTokens(item, colors));
if (!value || typeof value !== 'object') return value;
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, resolveChartTokens(item, colors)]));
};
const applyChartTextColor = (value: any, color: string, key = ''): any => {
if (Array.isArray(value)) return value.map((item) => applyChartTextColor(item, color, key));
if (!value || typeof value !== 'object') return value;
const result = Object.fromEntries(Object.entries(value).map(([childKey, item]) => [childKey, applyChartTextColor(item, color, childKey)]));
if (['textStyle', 'axisLabel', 'nameTextStyle', 'label'].includes(key) && result.color === undefined) result.color = color;
// ECharts 4+ expects text properties directly on title/legend/tooltip.
// Keeping the values flat avoids the deprecated `textStyle` hierarchy warning.
if (['title', 'legend', 'tooltip'].includes(key)) {
const nestedTextStyle = result.textStyle;
if (nestedTextStyle && typeof nestedTextStyle === 'object') {
Object.entries(nestedTextStyle).forEach(([property, propertyValue]) => {
if (result[property] === undefined) result[property] = propertyValue;
});
}
if (result.color === undefined) result.color = nestedTextStyle?.color || color;
delete result.textStyle;
}
if (['xAxis', 'yAxis'].includes(key)) {
result.axisLabel = { ...(result.axisLabel || {}), color: result.axisLabel?.color || color };
result.nameTextStyle = { ...(result.nameTextStyle || {}), color: result.nameTextStyle?.color || color };
}
return result;
};
export type TChartColorKey = keyof (typeof CHART_COLORS)[ETheme.light];
/**
* 根据当前主题色返回动态的图表颜色列表
@@ -21,9 +63,8 @@ export default function useDynamicChart(
const { theme, color } = useAppSelector(selectGlobal);
return useMemo(() => {
const dynamicColor = getChartColor(theme, color);
const newOptions = {
...options,
};
const newOptions = applyChartTextColor(resolveChartTokens(options, dynamicColor), dynamicColor.textColor);
newOptions.textStyle = { ...(newOptions.textStyle || {}), color: newOptions.textStyle?.color || dynamicColor.textColor };
// 设置动态的图表颜色
lodashSet(newOptions, 'color', dynamicColor.colorList);
if (configs) {
@@ -1,4 +1,5 @@
.sidePanel {
position: relative;
height: 100%;
display: flex;
flex-direction: row!important;
@@ -27,3 +28,17 @@
.mixContent {
overflow: auto;
}
@media (max-width: 999px) {
.sideContainer,
.topPanel,
.mixMain,
.mixContent {
min-width: 0;
width: auto;
}
.sideContainer {
width: 100%;
}
}
+2 -2
View File
@@ -1,5 +1,5 @@
import React from 'react';
import { Layout } from 'tdesign-react';
import { Layout } from '@miragenflow/ui/layout';
import { ELayout } from 'modules/global';
import Header from './Header';
import Footer from './Footer';
@@ -41,7 +41,7 @@ const MixLayout = React.memo(() => (
</Layout>
));
const FullPageLayout = React.memo(() => <Content />);
const FullPageLayout = React.memo(() => <Content fullPage />);
export default {
[ELayout.side]: SideLayout,
+63 -20
View File
@@ -1,14 +1,23 @@
import React, { Suspense, memo } from 'react';
import { Routes, Route, Navigate } from 'react-router-dom';
import { Layout, Loading } from 'tdesign-react';
import React, { Suspense, memo, useEffect, useState } from 'react';
import { Routes, Route, Navigate, useLocation } from 'react-router-dom';
import { Loading } from '@miragenflow/ui';
import { Layout } from '@miragenflow/ui/layout';
import routers, { IRouter } from 'router';
import { resolve } from 'utils/path';
import Page from './Page';
import Style from './AppRouter.module.less';
import { useAppDispatch, useAppSelector } from 'modules/store';
import { getUserInfo, setToken } from 'modules/user';
import { refreshAdminSession } from 'services/platform';
const { Content } = Layout;
type TRenderRoutes = (routes: IRouter[], parentPath?: string, breadcrumbs?: string[]) => React.ReactNode[];
const PermissionGate = ({ scope, children }: { scope?: string; children: React.ReactNode }) => {
const user = useAppSelector((state) => state.user.userInfo as { roles?: string[]; scopes?: string[] });
if (scope && !user.roles?.includes('super_admin') && !user.scopes?.includes(scope)) return <Navigate to='/403' replace state={{ requiredScope: scope, roles: user.roles || [] }} />;
return <>{children}</>;
};
/**
* 渲染应用路由
* @param routes
@@ -18,7 +27,7 @@ type TRenderRoutes = (routes: IRouter[], parentPath?: string, breadcrumbs?: stri
const renderRoutes: TRenderRoutes = (routes, parentPath = '', breadcrumb = []) =>
routes.map((route, index: number) => {
const { Component, children, redirect, meta } = route;
const currentPath = resolve(parentPath, route.path);
const currentPath = route.absolute ? route.path : resolve(parentPath, route.path);
let currentBreadcrumb = breadcrumb;
if (meta?.title) {
@@ -37,9 +46,11 @@ const renderRoutes: TRenderRoutes = (routes, parentPath = '', breadcrumb = []) =
key={index}
path={currentPath}
element={
<Page isFullPage={route.isFullPage} breadcrumbs={currentBreadcrumb}>
<Component />
</Page>
<PermissionGate scope={meta?.requiredScope}>
<Page isFullPage={route.isFullPage} breadcrumbs={currentBreadcrumb}>
<Component />
</Page>
</PermissionGate>
}
/>
);
@@ -48,18 +59,50 @@ const renderRoutes: TRenderRoutes = (routes, parentPath = '', breadcrumb = []) =
return children ? renderRoutes(children, currentPath, currentBreadcrumb) : null;
});
const AppRouter = () => (
<Content>
<Suspense
fallback={
<div className={Style.loading}>
<Loading />
</div>
}
>
<Routes>{renderRoutes(routers)}</Routes>
</Suspense>
</Content>
);
const AppRouter = ({ fullPage = false }: { fullPage?: boolean }) => {
const location = useLocation();
const token = useAppSelector((state) => state.user.token);
const dispatch = useAppDispatch();
const [bootstrapping, setBootstrapping] = useState(!location.pathname.startsWith('/login'));
const userInfo = useAppSelector((state) => state.user.userInfo as { roles?: string[]; scopes?: string[] });
useEffect(() => {
let active = true;
if (location.pathname.startsWith('/login')) {
setBootstrapping(false);
return () => { active = false; };
}
let loadUser: Promise<unknown>;
if (token && (userInfo.roles?.length || userInfo.scopes?.length)) {
loadUser = Promise.resolve();
} else if (token) {
loadUser = dispatch(getUserInfo()).unwrap();
} else {
loadUser = refreshAdminSession().then((next) => {
if (!next) return undefined;
dispatch(setToken(next));
return dispatch(getUserInfo()).unwrap();
});
}
loadUser.catch(() => undefined).finally(() => {
if (active) setBootstrapping(false);
});
return () => { active = false; };
}, [dispatch, location.pathname, token, userInfo.roles?.length, userInfo.scopes?.length]);
if (bootstrapping) return <div className={Style.loading}><Loading /></div>;
if (!token && !location.pathname.startsWith('/login')) return <Navigate to='/login' replace />;
const routesContent = <Suspense
fallback={
<div className={Style.loading}>
<Loading />
</div>
}
>
<Routes>{renderRoutes(routers)}</Routes>
</Suspense>;
return fullPage ? routesContent : <Content>
{routesContent}
</Content>;
};
export default memo(AppRouter);
+3 -2
View File
@@ -1,5 +1,6 @@
import React from 'react';
import { Layout, Row } from 'tdesign-react';
import { Row } from '@miragenflow/ui/grid';
import { Layout } from '@miragenflow/ui/layout';
import { useAppSelector } from 'modules/store';
import { selectGlobal } from 'modules/global';
@@ -13,7 +14,7 @@ const Footer = () => {
return (
<TFooter>
<Row justify='center'>Copyright © 2022-{new Date().getFullYear()} Tencent. All Rights Reserved</Row>
<Row justify='center'>元境幻生管理台 · MiragenFlow</Row>
</TFooter>
);
};
@@ -12,23 +12,39 @@
}
}
.menuIcon {
width: 30px !important;
height: 30px !important;
width: 40px !important;
height: 40px !important;
}
.menuIcon > svg {
width: 24px !important;
height: 24px !important;
}
.dropItem {
display: flex;
align-items: center;
min-height: 28px;
svg {
width: 24px;
height: 24px;
flex: 0 0 auto;
}
span {
margin-left: 8px;
}
}
.dropdown {
min-width: 40px;
height: 40px !important;
padding-left: 0;
padding-right: 0;
justify-content: flex-start;
text-align: left;
.icon {
font-size: 20px;
width: 24px;
height: 24px;
display: inline-block;
margin: 0 5px;
flex: 0 0 auto;
margin: 0 4px;
}
:global {
.t-button--variant-text {
@@ -38,17 +54,27 @@
.t-button__text {
display: flex;
align-items: center;
justify-content: flex-start;
text-align: left;
}
.t-dropdown__item {
max-width: none !important;
width: 117px;
width: 136px;
&-text {
display: flex;
align-items: center;
}
.t-icon {
margin-right: 8px;
}
}
}
}
@media (max-width: 600px) {
.text {
display: none;
}
.dropdown .icon {
margin-left: 2px;
margin-right: 2px;
}
}
@@ -1,18 +1,14 @@
import React, { memo } from 'react';
import { useNavigate } from 'react-router-dom';
import { Button, Popup, Badge, Dropdown, Space } from 'tdesign-react';
import {
Icon,
LogoGithubIcon,
MailIcon,
HelpCircleIcon,
SettingIcon,
PoweroffIcon,
UserCircleIcon,
} from 'tdesign-icons-react';
import { Dropdown } from '@miragenflow/ui/dropdown';
import { Popup } from '@miragenflow/ui/popup';
import { Space } from '@miragenflow/ui/space';
import { MiragenFlowButton } from '@miragenflow/ui';
import { ChevronDownIcon, DashboardIcon, SettingIcon, PoweroffIcon, UserCircleIcon } from 'components/LineIcons';
import { useAppDispatch } from 'modules/store';
import { toggleSetting } from 'modules/global';
import { logout } from 'modules/user';
import { adminLogout } from 'services/platform';
import Style from './HeaderIcon.module.less';
const { DropdownMenu, DropdownItem } = Dropdown;
@@ -21,63 +17,37 @@ export default memo(() => {
const dispatch = useAppDispatch();
const navigate = useNavigate();
const gotoWiki = () => {
window.open('https://tdesign.tencent.com/react/overview');
};
const gotoGitHub = () => {
window.open('https://github.com/Tencent/tdesign-react-starter');
};
const clickHandler = (data: any) => {
if (data.value === 1) {
navigate('/user/index');
}
const handleOverview = () => {
navigate('/dashboard/base');
};
const handleLogout = async () => {
await dispatch(logout());
navigate('/login/index');
try {
await adminLogout();
} finally {
try {
await dispatch(logout());
} finally {
navigate('/login');
}
}
};
return (
<Space align='center'>
<Badge className={Style.badge} count={6} dot={false} maxCount={99} shape='circle' showZero={false} size='medium'>
<Button className={Style.menuIcon} shape='square' size='large' variant='text' icon={<MailIcon />} />
</Badge>
<Popup content='代码仓库' placement='bottom' showArrow destroyOnClose>
<Button
className={Style.menuIcon}
shape='square'
size='large'
variant='text'
onClick={gotoGitHub}
icon={<LogoGithubIcon />}
/>
</Popup>
<Popup content='帮助文档' placement='bottom' showArrow destroyOnClose>
<Button
className={Style.menuIcon}
shape='square'
size='large'
variant='text'
onClick={gotoWiki}
icon={<HelpCircleIcon />}
/>
</Popup>
<Dropdown trigger={'click'} onClick={clickHandler}>
<Button variant='text' className={Style.dropdown}>
<Icon name='user-circle' className={Style.icon} />
<span className={Style.text}>Tencent</span>
<Icon name='chevron-down' className={Style.icon} />
</Button>
<Dropdown trigger={'click'}>
<MiragenFlowButton aria-label='管理员菜单' type='text' className={Style.dropdown}>
<UserCircleIcon className={Style.icon} />
<span className={Style.text}>元境幻生管理员</span>
<ChevronDownIcon className={Style.icon} />
</MiragenFlowButton>
<DropdownMenu>
<DropdownItem value={1}>
<DropdownItem value='overview' onClick={handleOverview}>
<div className={Style.dropItem}>
<UserCircleIcon />
<span>个人中心</span>
<DashboardIcon />
<span>系统概览</span>
</div>
</DropdownItem>
<DropdownItem value={1} onClick={handleLogout}>
<DropdownItem value='logout' onClick={handleLogout}>
<div className={Style.dropItem}>
<PoweroffIcon />
<span>退出登录</span>
@@ -86,11 +56,12 @@ export default memo(() => {
</DropdownMenu>
</Dropdown>
<Popup content='页面设置' placement='bottom' showArrow destroyOnClose>
<Button
<MiragenFlowButton
aria-label='页面设置'
className={Style.menuIcon}
shape='square'
size='large'
variant='text'
type='text'
onClick={() => dispatch(toggleSetting())}
icon={<SettingIcon />}
/>
@@ -1,14 +0,0 @@
.panel {
:global {
.t-input {
border: none;
}
.t-input:hover {
background: var(--td-bg-color-secondarycontainer);
transition: background var(--td-anim-duration-base) linear
}
.t-input--focused {
box-shadow: none;
}
}
}
@@ -1,8 +0,0 @@
import React from 'react';
import { Input } from 'tdesign-react';
import { SearchIcon } from 'tdesign-icons-react';
import Style from './Search.module.less';
const Search = () => <Input className={Style.panel} prefixIcon={<SearchIcon />} placeholder='请输入搜索内容' />;
export default React.memo(Search);
@@ -9,3 +9,27 @@
justify-content: space-between;
border-bottom: 1px solid var(--td-component-stroke);
}
.menuButton {
width: 40px;
height: 40px;
}
.menuButton > svg {
width: 24px !important;
height: 24px !important;
}
@media (max-width: 600px) {
.panel {
padding-left: 8px;
padding-right: 8px;
:global {
.t-space {
min-width: 0;
gap: 4px;
}
}
}
}
@@ -1,11 +1,12 @@
import React, { memo } from 'react';
import { Layout, Button, Space } from 'tdesign-react';
import { ViewListIcon } from 'tdesign-icons-react';
import { Layout } from '@miragenflow/ui/layout';
import { Space } from '@miragenflow/ui/space';
import { MiragenFlowButton } from '@miragenflow/ui';
import { ViewListIcon } from 'components/LineIcons';
import { useAppDispatch, useAppSelector } from 'modules/store';
import { selectGlobal, toggleMenu } from 'modules/global';
import HeaderIcon from './HeaderIcon';
import { HeaderMenu } from '../Menu';
import Search from './Search';
import Style from './index.module.less';
const { Header } = Layout;
@@ -28,14 +29,15 @@ export default memo((props: { showMenu?: boolean }) => {
} else {
HeaderLeft = (
<Space align='center'>
<Button
<MiragenFlowButton
aria-label={globalState.collapsed ? '展开菜单' : '收起菜单'}
className={Style.menuButton}
shape='square'
size='large'
variant='text'
type='text'
onClick={() => dispatch(toggleMenu(null))}
icon={<ViewListIcon />}
/>
<Search />
</Space>
);
}
+79 -2
View File
@@ -9,17 +9,41 @@
opacity: 0.4;
height: 38px;
line-height: 38px;
text-align: center;
padding: 0 20px;
text-align: left;
}
.menuLogo {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
justify-content: flex-start;
align-items: center;
padding: 0 20px;
box-sizing: border-box;
cursor: pointer;
color: var(--td-text-color-primary);
text-align: left;
}
.brandLogo {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 2px;
line-height: 1.1;
color: var(--td-text-color-primary);
}
.brandLogo strong {
font-size: 18px;
letter-spacing: 0;
}
.brandLogo small {
color: var(--td-text-color-secondary);
font-size: 11px;
font-weight: 400;
}
.menuMiniLogo {
@@ -32,3 +56,56 @@
width: 184px;
height: 32px;
}
.menuPanel2 :global(.t-menu__item > svg:not(.t-fake-arrow)),
.menuPanel2 :global(.t-submenu__title > svg:not(.t-fake-arrow)),
.headerMenu :global(.t-menu__item > svg:not(.t-fake-arrow)),
.headerMenu :global(.t-submenu__title > svg:not(.t-fake-arrow)) {
width: 24px;
height: 24px;
flex: 0 0 auto;
margin-right: var(--td-comp-margin-s);
}
.menuPanel2 :global(.t-menu__item > .t-fake-arrow),
.menuPanel2 :global(.t-submenu__title > .t-fake-arrow),
.headerMenu :global(.t-menu__item > .t-fake-arrow),
.headerMenu :global(.t-submenu__title > .t-fake-arrow) {
width: 16px;
height: 16px;
margin-right: 0;
}
// Keep the navigation label block anchored to the icon/text start even when
// TDesign's internal flex defaults change between menu themes.
.menuPanel2 :global(.t-menu__item),
.menuPanel2 :global(.t-submenu__title),
.headerMenu :global(.t-menu__item),
.headerMenu :global(.t-submenu__title) {
justify-content: flex-start;
text-align: left;
}
.mobileMenuBackdrop {
display: none;
}
@media (max-width: 999px) {
.menuPanel2 {
position: fixed !important;
inset: 0 auto 0 0;
z-index: 1001;
width: 232px !important;
box-shadow: var(--td-shadow-3);
}
.mobileMenuBackdrop {
position: fixed;
inset: 0 0 0 232px;
z-index: 1000;
display: block;
padding: 0;
border: 0;
background: rgba(0, 0, 0, 0.32);
}
}
+73 -91
View File
@@ -1,115 +1,97 @@
import React, { memo, useState } from 'react';
import React, { memo, useEffect, useMemo, useState } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { Menu, MenuValue } from 'tdesign-react';
import { Menu, type MenuValue } from '@miragenflow/ui/menu';
import { MiragenFlowButton } from '@miragenflow/ui';
import router, { IRouter } from 'router';
import { resolve } from 'utils/path';
import { useAppSelector } from 'modules/store';
import { selectGlobal } from 'modules/global';
import { useAppDispatch, useAppSelector } from 'modules/store';
import { selectGlobal, toggleMenu } from 'modules/global';
import MenuLogo from './MenuLogo';
import Style from './Menu.module.less';
const { SubMenu, MenuItem, HeadMenu } = Menu;
interface IMenuProps { showLogo?: boolean; showOperation?: boolean; }
interface IMenuProps {
showLogo?: boolean;
showOperation?: boolean;
}
const renderMenuItems = (menu: IRouter[], parentPath = '') => {
const navigate = useNavigate();
return menu.map((item) => {
const { children, meta, path } = item;
if (!meta || meta?.hidden === true) {
// 无meta信息 或 hidden == true,路由不显示为菜单
return null;
}
const { Icon, title, single } = meta;
const routerPath = resolve(parentPath, path);
if (!children || children.length === 0) {
return (
<MenuItem
key={routerPath}
value={routerPath}
icon={Icon ? <Icon /> : undefined}
onClick={() => navigate(routerPath)}
>
{title}
</MenuItem>
);
}
if (single && children?.length > 0) {
const firstChild = children[0];
if (firstChild?.meta && !firstChild?.meta?.hidden) {
const { Icon, title } = meta;
const singlePath = resolve(resolve(parentPath, path), firstChild.path);
return (
<MenuItem
key={singlePath}
value={singlePath}
icon={Icon ? <Icon /> : undefined}
onClick={() => navigate(singlePath)}
>
{title}
</MenuItem>
);
}
}
return (
<SubMenu key={routerPath} value={routerPath} title={title} icon={Icon ? <Icon /> : undefined}>
{renderMenuItems(children, routerPath)}
</SubMenu>
);
});
const routePath = (item: IRouter, parentPath = '') => item.absolute ? item.path : `${parentPath}${item.path}` || '/';
const isVisible = (item: IRouter, scopes: string[], roles: string[]) => {
if (!item.meta || item.meta.hidden) return false;
if (item.meta.requiredScope && !roles.includes('super_admin') && !scopes.includes(item.meta.requiredScope)) return false;
return true;
};
const visibleChildren = (item: IRouter, scopes: string[], roles: string[]) => (item.children || []).filter((child) => isVisible(child, scopes, roles));
const hasVisibleChild = (item: IRouter, scopes: string[], roles: string[]) => visibleChildren(item, scopes, roles).length > 0;
const renderMenuItems = (menu: IRouter[], scopes: string[], roles: string[], parentPath = ''): React.ReactNode[] => menu.map((item) => {
const { children, meta } = item;
if (!meta || !isVisible(item, scopes, roles)) return null;
const currentPath = routePath(item, parentPath);
if (children?.length) {
const visible = visibleChildren(item, scopes, roles);
if (!visible.length) return null;
if (visible.length === 1) {
const child = visible[0];
const childPath = routePath(child, currentPath);
const ChildIcon = child.meta?.Icon || meta.Icon;
return <MenuItem key={childPath} value={childPath} icon={ChildIcon ? <ChildIcon /> : undefined}>{child.meta?.title || meta.title}</MenuItem>;
}
return <SubMenu key={currentPath} value={currentPath} title={meta.title} icon={meta.Icon ? <meta.Icon /> : undefined}>
{renderMenuItems(visible, scopes, roles, currentPath)}
</SubMenu>;
}
return <MenuItem key={currentPath} value={currentPath} icon={meta.Icon ? <meta.Icon /> : undefined}>{meta.title}</MenuItem>;
});
const expandedFor = (menu: IRouter[], pathname: string, scopes: string[], roles: string[], parent = ''): string[] => menu.flatMap((item) => {
if (!item.children?.length || !hasVisibleChild(item, scopes, roles)) return [];
const visible = visibleChildren(item, scopes, roles);
if (visible.length === 1) return [];
const current = routePath(item, parent);
const childPaths = visible.map((child) => routePath(child, current));
return childPaths.includes(pathname) ? [current] : expandedFor(visible, pathname, scopes, roles, current);
});
/**
* 顶部菜单
*/
export const HeaderMenu = memo(() => {
const globalState = useAppSelector(selectGlobal);
const location = useLocation();
const [active, setActive] = useState<MenuValue>(location.pathname); // todo
return (
<HeadMenu
expandType='popup'
style={{ marginBottom: 20 }}
value={active}
theme={globalState.theme}
onChange={(v) => setActive(v)}
>
{renderMenuItems(router)}
</HeadMenu>
);
const navigate = useNavigate();
const user = useAppSelector((state) => state.user.userInfo as { scopes?: string[]; roles?: string[] });
const [active, setActive] = useState<MenuValue>(location.pathname);
useEffect(() => setActive(location.pathname), [location.pathname]);
return <HeadMenu className={Style.headerMenu} expandType='popup' style={{ marginBottom: 20 }} value={active} theme={globalState.theme} onChange={(value) => { setActive(value); navigate(String(value)); }}>
{renderMenuItems(router, user.scopes || [], user.roles || [])}
</HeadMenu>;
});
/**
* 左侧菜单
*/
export default memo((props: IMenuProps) => {
const location = useLocation();
const navigate = useNavigate();
const dispatch = useAppDispatch();
const globalState = useAppSelector(selectGlobal);
const { version } = globalState;
const bottomText = globalState.collapsed ? version : `元境幻生管理台 ${version}`;
return (
const user = useAppSelector((state) => state.user.userInfo as { scopes?: string[]; roles?: string[] });
const scopes = user.scopes || [];
const roles = user.roles || [];
const requiredExpanded = useMemo(() => expandedFor(router, location.pathname, scopes, roles), [location.pathname, scopes.join(','), roles.join(',')]);
const [expanded, setExpanded] = useState<MenuValue[]>(requiredExpanded);
useEffect(() => {
setExpanded((current) => Array.from(new Set([...current, ...requiredExpanded])));
}, [requiredExpanded.join('|')]);
const onNavigate = (value: MenuValue) => {
navigate(String(value));
if (window.innerWidth < 1000) dispatch(toggleMenu(true));
};
if (globalState.collapsed) return null;
return <>
<MiragenFlowButton htmlType='button' type='text' className={Style.mobileMenuBackdrop} aria-label='关闭菜单' onClick={() => dispatch(toggleMenu(true))} />
<Menu
width='232px'
style={{ flexShrink: 0, height: '100%' }}
className={Style.menuPanel2}
value={location.pathname}
expanded={expanded}
onChange={onNavigate}
onExpand={setExpanded}
theme={globalState.theme}
collapsed={globalState.collapsed}
operations={props.showOperation ? <div className={Style.menuTip}>{bottomText}</div> : undefined}
logo={props.showLogo ? <MenuLogo collapsed={globalState.collapsed} /> : undefined}
>
{renderMenuItems(router)}
</Menu>
);
operations={props.showOperation ? <div className={Style.menuTip}>元境幻生管理台 {globalState.version}</div> : undefined}
logo={props.showLogo ? <MenuLogo collapsed={false} /> : undefined}
>{renderMenuItems(router, scopes, roles)}</Menu>
</>;
});
+10 -4
View File
@@ -1,8 +1,8 @@
import React, { memo } from 'react';
import Style from './Menu.module.less';
import FullLogo from 'assets/svg/assets-logo-full.svg?component';
import MiniLogo from 'assets/svg/assets-t-logo.svg?component';
import { useNavigate } from 'react-router-dom';
import { MiragenFlowButton } from '@miragenflow/ui';
interface IProps {
collapsed?: boolean;
@@ -16,8 +16,14 @@ export default memo((props: IProps) => {
};
return (
<div className={Style.menuLogo} onClick={handleClick}>
{props.collapsed ? <MiniLogo /> : <FullLogo />}
</div>
<MiragenFlowButton
aria-label='返回管理台首页'
className={Style.menuLogo}
htmlType='button'
onClick={handleClick}
type='text'
>
{props.collapsed ? <MiniLogo /> : <span className={Style.brandLogo}><strong>元境幻生</strong><small>管理台</small></span>}
</MiragenFlowButton>
);
});
@@ -7,3 +7,9 @@
.breadcrumb {
margin-bottom: 8px;
}
@media (max-width: 600px) {
.panel {
margin: 8px;
}
}
+2 -1
View File
@@ -1,7 +1,8 @@
import React, { useEffect } from 'react';
import { useAppDispatch, useAppSelector } from '../../modules/store';
import { selectGlobal, switchFullPage } from '../../modules/global';
import { Layout, Breadcrumb } from 'tdesign-react';
import { Layout } from '@miragenflow/ui/layout';
import { Breadcrumb } from '@miragenflow/ui/breadcrumb';
import Style from './Page.module.less';
const { Content } = Layout;
@@ -10,6 +10,11 @@
border-radius: 50%;
}
.box:focus-visible {
outline: 2px solid var(--td-brand-color);
outline-offset: 3px;
}
.item {
width: 24px;
height: 24px;
@@ -1,24 +1,70 @@
import React, { useState } from 'react';
import React, { useRef, useState } from 'react';
import { defaultColor } from 'configs/color';
import { Popup, ColorPickerPanel } from 'tdesign-react';
import { ColorPickerPanel } from '@miragenflow/ui/color-picker';
import { Popup } from '@miragenflow/ui/popup';
import Style from './RadioColor.module.less';
interface IProps {
defaultValue?: number | string;
value?: number | string;
onChange: (color: string) => void;
}
const RadioColor = (props: IProps) => {
const [isColoPickerDisplay, onPopupVisibleChange] = useState(false);
const panelRef = useRef<HTMLDivElement>(null);
const selectedIndex = defaultColor.indexOf(props.value as string);
const activeIndex = selectedIndex === -1 ? defaultColor.length : selectedIndex;
const colorOptionCount = defaultColor.length + 1;
const focusOption = (index: number) => {
panelRef.current?.querySelectorAll<HTMLElement>('[role="radio"]')[index]?.focus();
};
const handleKeyDown = (event: React.KeyboardEvent<HTMLElement>, index: number) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
if (index === defaultColor.length) {
onPopupVisibleChange(true);
} else {
props.onChange(defaultColor[index]);
}
return;
}
let nextIndex = index;
if (event.key === 'ArrowRight' || event.key === 'ArrowDown') {
nextIndex = (index + 1) % colorOptionCount;
} else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {
nextIndex = (index - 1 + colorOptionCount) % colorOptionCount;
} else if (event.key === 'Home') {
nextIndex = 0;
} else if (event.key === 'End') {
nextIndex = colorOptionCount - 1;
} else {
return;
}
event.preventDefault();
focusOption(nextIndex);
if (nextIndex < defaultColor.length) {
props.onChange(defaultColor[nextIndex]);
onPopupVisibleChange(false);
}
};
return (
<div className={Style.panel}>
<div ref={panelRef} className={Style.panel} role='radiogroup' aria-label='主题色'>
{defaultColor.map((color, index) => (
<div
key={index}
role='radio'
aria-label={color}
aria-checked={selectedIndex === index}
tabIndex={activeIndex === index ? 0 : -1}
onClick={() => props?.onChange(color)}
onKeyDown={(event) => handleKeyDown(event, index)}
className={Style.box}
style={{ borderColor: props.defaultValue === color && !isColoPickerDisplay ? color : 'transparent' }}
style={{ borderColor: props.value === color && !isColoPickerDisplay ? color : 'transparent' }}
>
<div className={Style.item} style={{ backgroundColor: color }} />
</div>
@@ -37,17 +83,23 @@ const RadioColor = (props: IProps) => {
colorModes={['monochrome']}
format='HEX'
swatchColors={[]}
defaultValue={props.defaultValue as string}
value={typeof props.value === 'string' ? props.value : undefined}
/>
}
>
<div
key='dynamic'
role='radio'
aria-label='自定义颜色'
aria-checked={selectedIndex === -1}
tabIndex={activeIndex === defaultColor.length ? 0 : -1}
aria-haspopup='dialog'
onKeyDown={(event) => handleKeyDown(event, defaultColor.length)}
className={Style.box}
style={{
borderColor:
isColoPickerDisplay || defaultColor.indexOf(props.defaultValue as string) === -1
? (props.defaultValue as string)
isColoPickerDisplay || defaultColor.indexOf(props.value as string) === -1
? (props.value as string)
: 'transparent',
}}
>
@@ -3,11 +3,20 @@
justify-content: space-between;
}
.rectOption {
cursor: pointer;
border-radius: 4px;
}
.rectOption:focus-visible {
outline: 2px solid var(--td-brand-color);
outline-offset: 3px;
}
.rectItem {
border: 2px solid #e3e6eb;
padding: 8px;
border-radius: 3px;
cursor: pointer;
}
.rectItemSelected {
@@ -1,4 +1,4 @@
import React, { memo, useState } from 'react';
import React, { memo, useEffect, useState } from 'react';
import classname from 'classnames';
import Style from './RadioRect.module.less';
@@ -9,21 +9,58 @@ interface IOption {
}
interface IProps {
value?: number | string;
defaultValue?: number | string;
onChange: (value?: any) => void;
options: IOption[];
ariaLabel?: string;
}
export default memo((props: IProps) => {
const [selectValue, setSelectValue] = useState(props.defaultValue);
const isControlled = props.value !== undefined;
const currentValue = isControlled ? props.value : selectValue;
const handleClick = (option: IOption) => {
setSelectValue(option.value);
props?.onChange(option.value);
useEffect(() => {
if (!isControlled) {
setSelectValue(props.defaultValue);
}
}, [isControlled, props.defaultValue]);
const handleChange = (option: IOption) => {
if (!isControlled) {
setSelectValue(option.value);
}
props.onChange(option.value);
};
const handleKeyDown = (event: React.KeyboardEvent<HTMLElement>, index: number) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
handleChange(props.options[index]);
return;
}
let nextIndex = index;
if (event.key === 'ArrowRight' || event.key === 'ArrowDown') {
nextIndex = (index + 1) % props.options.length;
} else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {
nextIndex = (index - 1 + props.options.length) % props.options.length;
} else if (event.key === 'Home') {
nextIndex = 0;
} else if (event.key === 'End') {
nextIndex = props.options.length - 1;
} else {
return;
}
event.preventDefault();
handleChange(props.options[nextIndex]);
event.currentTarget.parentElement?.querySelectorAll<HTMLElement>('[role="radio"]')[nextIndex]?.focus();
};
return (
<div className={Style.radioRectPanel}>
<div className={Style.radioRectPanel} role='radiogroup' aria-label={props.ariaLabel}>
{props.options.map((item, index) => {
let ImageItem = item.image;
if (typeof item.image === 'string') {
@@ -31,10 +68,17 @@ export default memo((props: IProps) => {
}
return (
<div key={index}>
<div
key={item.value ?? index}
className={Style.rectOption}
role='radio'
aria-checked={currentValue === item.value}
tabIndex={currentValue === item.value || (currentValue === undefined && index === 0) ? 0 : -1}
onClick={() => handleChange(item)}
onKeyDown={(event) => handleKeyDown(event, index)}
>
<div
className={classname(Style.rectItem, { [Style.rectItemSelected]: selectValue === item.value })}
onClick={() => handleClick(item)}
className={classname(Style.rectItem, { [Style.rectItemSelected]: currentValue === item.value })}
>
{ImageItem}
</div>
@@ -11,3 +11,14 @@
line-height: 21px;
margin-bottom: 24px;
}
.layoutPreview {
width: 88px;
height: 48px;
display: grid;
place-items: center;
border-radius: 3px;
background: var(--td-bg-color-container);
color: var(--td-text-color-secondary);
font-size: 13px;
}
+13 -10
View File
@@ -1,5 +1,6 @@
import React, { memo } from 'react';
import { Row, Col, Switch } from 'tdesign-react';
import { Row, Col } from '@miragenflow/ui/grid';
import { Switch } from '@miragenflow/ui';
import { useAppDispatch, useAppSelector } from 'modules/store';
import {
selectGlobal,
@@ -47,15 +48,15 @@ const themeList = [
const layoutList = [
{
value: ELayout.side,
image: 'https://tdesign.gtimg.com/starter/setting/side.png',
image: <div className={Style.layoutPreview}>侧</div>,
},
{
value: ELayout.top,
image: 'https://tdesign.gtimg.com/starter/setting/top.png',
image: <div className={Style.layoutPreview}>顶</div>,
},
{
value: ELayout.mix,
image: 'https://tdesign.gtimg.com/starter/setting/mix.png',
image: <div className={Style.layoutPreview}>混</div>,
},
];
@@ -76,25 +77,27 @@ export default memo(() => {
<div>
<div className={Style.settingTitle}>主题模式</div>
<RadioRect
defaultValue={globalState.systemTheme ? ESettingTheme.system : globalState.theme}
value={globalState.systemTheme ? ESettingTheme.system : globalState.theme}
onChange={handleThemeSwitch}
options={themeList}
ariaLabel='主题模式'
/>
<div className={Style.settingTitle}>主题色</div>
<RadioColor defaultValue={globalState.color} onChange={(value) => dispatch(switchColor(value))} />
<RadioColor value={globalState.color} onChange={(value) => dispatch(switchColor(value))} />
<div className={Style.settingTitle}>导航布局</div>
<RadioRect
defaultValue={globalState.layout}
value={globalState.layout}
onChange={(value) => dispatch(switchLayout(value))}
options={layoutList}
ariaLabel='导航布局'
/>
<div className={Style.settingTitle}>元素开关</div>
<Row justify='space-between'>
<Col>
<div className={Style.settingSubTitle}>显示 Header</div>
<div className={Style.settingSubTitle}>显示顶部栏</div>
</Col>
<Col>
<Switch size='large' value={globalState.showHeader} onChange={() => dispatch(toggleShowHeader())} />
@@ -103,7 +106,7 @@ export default memo(() => {
<Row justify='space-between'>
<Col>
<div className={Style.settingSubTitle}>显示 Breadcrumbs</div>
<div className={Style.settingSubTitle}>显示面包屑导航</div>
</Col>
<Col>
<Switch size='large' value={globalState.showBreadcrumbs} onChange={() => dispatch(toggleShowBreadcrumbs())} />
@@ -112,7 +115,7 @@ export default memo(() => {
<Row justify='space-between'>
<Col>
<div className={Style.settingSubTitle}>显示 Footer</div>
<div className={Style.settingSubTitle}>显示页脚</div>
</Col>
<Col>
<Switch size='large' value={globalState.showFooter} onChange={() => dispatch(toggleShowFooter())} />
+43 -16
View File
@@ -1,24 +1,49 @@
import React, { memo, useEffect } from 'react';
import { Drawer, Layout } from 'tdesign-react';
import { Layout } from '@miragenflow/ui/layout';
import { Drawer } from '@miragenflow/ui';
import { useLocation } from 'react-router-dom';
import throttle from 'lodash/throttle';
import { useAppSelector, useAppDispatch } from 'modules/store';
import { selectGlobal, toggleSetting, toggleMenu, ELayout, switchTheme } from 'modules/global';
import { selectGlobal, setSetting, toggleMenu, switchTheme } from 'modules/global';
import Setting from './components/Setting';
import AppLayout from './components/AppLayout';
import AppRouter from './components/AppRouter';
import Style from './index.module.less';
export default memo(() => {
const globalState = useAppSelector(selectGlobal);
const dispatch = useAppDispatch();
const AppContainer = AppLayout[globalState.isFullPage ? ELayout.fullPage : globalState.layout];
const location = useLocation();
const isLoginRoute = location.pathname.startsWith('/login');
const closeSettings = () => {
dispatch(setSetting(false));
window.requestAnimationFrame(() => {
document.querySelector<HTMLElement>('[aria-label="页面设置"]')?.focus();
});
};
const settingsDrawer = (
<Drawer
destroyOnClose
visible={globalState.setting}
size='min(458px, calc(100vw - 24px))'
footer={false}
header='页面配置'
closeBtn
closeOnOverlayClick
closeOnEscKeydown
onClose={closeSettings}
onOverlayClick={closeSettings}
>
<Setting />
</Drawer>
);
useEffect(() => {
dispatch(switchTheme(globalState.theme));
const handleResize = throttle(() => {
if (window.innerWidth < 900) {
if (window.innerWidth < 1000) {
dispatch(toggleMenu(true));
} else if (window.innerWidth > 1000) {
} else {
dispatch(toggleMenu(false));
}
}, 100);
@@ -28,19 +53,21 @@ export default memo(() => {
};
}, []);
// 登录页是独立的全屏入口,不需要被后台的 Layout/Content 再包一层。
// 这样移动端会直接复用桌面左侧主体的定位,不会出现额外容器边界。
if (isLoginRoute) {
return <>
<AppRouter fullPage />
{settingsDrawer}
</>;
}
const AppContainer = AppLayout[globalState.layout];
return (
<Layout className={Style.panel}>
<AppContainer />
<Drawer
destroyOnClose
visible={globalState.setting}
size='458px'
footer={false}
header='页面配置'
onClose={() => dispatch(toggleSetting())}
>
<Setting />
</Drawer>
{settingsDrawer}
</Layout>
);
});
+28 -4
View File
@@ -2,6 +2,7 @@ import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import { TDesignProvider } from '@miragenflow/ui';
import store from 'modules/store';
import App from 'layouts/index';
@@ -9,8 +10,26 @@ import 'tdesign-react/es/style/index.css';
import './styles/index.less';
const env = import.meta.env.MODE || 'development';
const baseRouterName = env === 'site' ? '/starter/react/' : import.meta.env.BASE_URL.replace(/\/$/, '');
const baseRouterName = (() => {
const configuredBaseUrl = import.meta.env.BASE_URL || '/';
// Vite allows an absolute `base` URL for the static site build. React
// Router, however, expects `basename` to contain only the current path.
// Normalize both `/admin/` and `https://host/starter/react/` to their
// pathname so deep links continue to render instead of matching no route.
try {
const { pathname } = new URL(configuredBaseUrl, window.location.origin);
const normalized = pathname === '/' ? '' : pathname.replace(/\/$/, '');
const current = window.location.pathname.replace(/\/$/, '') || '/';
if (!normalized || current === normalized || current.startsWith(`${normalized}/`)) return normalized;
// When the admin bundle is mounted through the web app's `/admin` proxy,
// prefer the runtime mount path if a stale/absolute build base is present.
if (current === '/admin' || current.startsWith('/admin/')) return '/admin';
return '';
} catch {
return configuredBaseUrl.replace(/\/$/, '');
}
})();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const root = document.getElementById('app')!;
@@ -18,8 +37,13 @@ const root = document.getElementById('app')!;
const renderApp = () => {
ReactDOM.createRoot(root).render(
<Provider store={store}>
<BrowserRouter basename={baseRouterName}>
<App />
<BrowserRouter
basename={baseRouterName}
future={{ v7_startTransition: true, v7_relativeSplatPath: true }}
>
<TDesignProvider locale='zh-CN'>
<App />
</TDesignProvider>
</BrowserRouter>
</Provider>,
);
+10 -8
View File
@@ -73,6 +73,9 @@ const globalSlice = createSlice({
toggleSetting: (state) => {
state.setting = !state.setting;
},
setSetting: (state, action: PayloadAction<boolean>) => {
state.setting = Boolean(action.payload);
},
toggleShowHeader: (state) => {
state.showHeader = !state.showHeader;
},
@@ -94,14 +97,12 @@ const globalSlice = createSlice({
},
openSystemTheme: (state) => {
const media = window.matchMedia('(prefers-color-scheme:dark)');
if (media.matches) {
const finalTheme = media.matches ? ETheme.dark : ETheme.light;
state.chartColors = CHART_COLORS[finalTheme];
// 切换主题颜色
state.theme = finalTheme;
state.systemTheme = true;
document.documentElement.setAttribute('theme-mode', finalTheme);
}
const finalTheme = media.matches ? ETheme.dark : ETheme.light;
state.chartColors = CHART_COLORS[finalTheme];
// 切换主题颜色
state.theme = finalTheme;
state.systemTheme = true;
document.documentElement.setAttribute('theme-mode', finalTheme);
},
switchColor: (state, action) => {
if (action?.payload) {
@@ -138,6 +139,7 @@ export const selectGlobal = (state: RootState) => state.global;
export const {
toggleMenu,
toggleSetting,
setSetting,
toggleShowHeader,
toggleShowBreadcrumbs,
toggleShowFooter,
-64
View File
@@ -1,64 +0,0 @@
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { RootState } from '../store';
import { getContractList, IContract } from 'services/contract';
const namespace = 'list/base';
interface IInitialState {
loading: boolean;
current: number;
pageSize: number;
total: number;
contractList: IContract[];
}
const initialState: IInitialState = {
loading: true,
current: 1,
pageSize: 10,
total: 0,
contractList: [],
};
export const getList = createAsyncThunk(
`${namespace}/getList`,
async (params: { pageSize: number; current: number }) => {
const result = await getContractList(params);
return {
list: result?.list,
total: result?.total,
pageSize: params.pageSize,
current: params.current,
};
},
);
const listBaseSlice = createSlice({
name: namespace,
initialState,
reducers: {
clearPageState: () => initialState,
},
extraReducers: (builder) => {
builder
.addCase(getList.pending, (state) => {
state.loading = true;
})
.addCase(getList.fulfilled, (state, action) => {
state.loading = false;
state.contractList = action.payload?.list;
state.total = action.payload?.total;
state.pageSize = action.payload?.pageSize;
state.current = action.payload?.current;
})
.addCase(getList.rejected, (state) => {
state.loading = false;
});
},
});
export const { clearPageState } = listBaseSlice.actions;
export const selectListBase = (state: RootState) => state.listBase;
export default listBaseSlice.reducer;
-73
View File
@@ -1,73 +0,0 @@
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
import { RootState } from '../store';
import { getProductList, IProduct } from 'services/product';
const namespace = 'list/card';
interface IInitialState {
pageLoading: boolean;
loading: boolean;
current: number;
pageSize: number;
total: number;
productList: IProduct[];
}
const initialState: IInitialState = {
pageLoading: true,
loading: true,
current: 1,
pageSize: 12,
total: 0,
productList: [],
};
export const getList = createAsyncThunk(
`${namespace}/getList`,
async (params: { pageSize: number; current: number }) => {
const { pageSize, current } = params;
const result = await getProductList({
pageSize,
current,
});
return {
list: result?.list,
total: result?.total,
pageSize: params.pageSize,
current: params.current,
};
},
);
const listCardSlice = createSlice({
name: namespace,
initialState,
reducers: {
clearPageState: () => initialState,
switchPageLoading: (state, action: PayloadAction<boolean>) => {
state.pageLoading = action.payload;
},
},
extraReducers: (builder) => {
builder
.addCase(getList.pending, (state) => {
state.loading = true;
})
.addCase(getList.fulfilled, (state, action) => {
state.loading = false;
state.productList = action.payload?.list;
state.total = action.payload?.total;
state.pageSize = action.payload?.pageSize;
state.current = action.payload?.current;
})
.addCase(getList.rejected, (state) => {
state.loading = false;
});
},
});
export const { clearPageState, switchPageLoading } = listCardSlice.actions;
export const selectListCard = (state: RootState) => state.listCard;
export default listCardSlice.reducer;
-64
View File
@@ -1,64 +0,0 @@
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { RootState } from '../store';
import { getContractList, IContract } from 'services/contract';
const namespace = 'list/select';
interface IInitialState {
loading: boolean;
current: number;
pageSize: number;
total: number;
contractList: IContract[];
}
const initialState: IInitialState = {
loading: true,
current: 1,
pageSize: 10,
total: 0,
contractList: [],
};
export const getList = createAsyncThunk(
`${namespace}/getSelectList`,
async (params: { pageSize: number; current: number }) => {
const result = await getContractList(params);
return {
list: result?.list,
total: result?.total,
pageSize: params.pageSize,
current: params.current,
};
},
);
const listSelectSlice = createSlice({
name: namespace,
initialState,
reducers: {
clearPageState: () => initialState,
},
extraReducers: (builder) => {
builder
.addCase(getList.pending, (state) => {
state.loading = true;
})
.addCase(getList.fulfilled, (state, action) => {
state.loading = false;
state.contractList = action.payload?.list;
state.total = action.payload?.total;
state.pageSize = action.payload?.pageSize;
state.current = action.payload?.current;
})
.addCase(getList.rejected, (state) => {
state.loading = false;
});
},
});
export const { clearPageState } = listSelectSlice.actions;
export const selectListSelect = (state: RootState) => state.listSelect;
export default listSelectSlice.reducer;
-6
View File
@@ -3,16 +3,10 @@ import { TypedUseSelectorHook, useSelector, useDispatch } from 'react-redux';
import global from './global';
import user from './user';
import listBase from './list/base';
import listSelect from './list/select';
import listCard from './list/card';
const reducer = combineReducers({
global,
user,
listBase,
listSelect,
listCard,
});
export const store = configureStore({
+14 -60
View File
@@ -1,90 +1,46 @@
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { RootState } from '../store';
import { adminRequest, setAdminAccessToken } from 'services/platform';
const namespace = 'user';
const TOKEN_NAME = 'tdesign-starter';
const initialState = {
token: localStorage.getItem(TOKEN_NAME) || 'main_token', // 默认token不走权限
token: '',
userInfo: {},
};
// login
export const login = createAsyncThunk(`${namespace}/login`, async (userInfo: Record<string, unknown>) => {
const mockLogin = async (userInfo: Record<string, unknown>) => {
// 登录请求流程
console.log(userInfo);
// const { account, password } = userInfo;
// if (account !== 'td') {
// return {
// code: 401,
// message: '账号不存在',
// };
// }
// if (['main_', 'dev_'].indexOf(password) === -1) {
// return {
// code: 401,
// message: '密码错误',
// };
// }
// const token = {
// main_: 'main_token',
// dev_: 'dev_token',
// }[password];
return {
code: 200,
message: '登陆成功',
data: 'main_token',
};
};
const res = await mockLogin(userInfo);
if (res.code === 200) {
return res.data;
}
throw res;
const result = await adminRequest<{ accessToken?: string; mfaRequired?: boolean; challengeId?: string }>('/api/v1/admin/auth/login', { method: 'POST', body: JSON.stringify({ email: userInfo.account, password: userInfo.password, mfaCode: userInfo.mfaCode, recoveryCode: userInfo.recoveryCode, challengeId: userInfo.challengeId, deviceId: 'admin-browser', captchaChallengeId: userInfo.captchaChallengeId, captchaAnswer: userInfo.captchaAnswer }) });
if (result.accessToken) setAdminAccessToken(result.accessToken);
return result;
});
// getUserInfo
export const getUserInfo = createAsyncThunk(`${namespace}/getUserInfo`, async (_, { getState }: any) => {
const { token } = getState();
const mockRemoteUserInfo = async (token: string) => {
if (token === 'main_token') {
return {
name: 'td_main',
roles: ['all'],
};
}
return {
name: 'td_dev',
roles: ['userIndex', 'dashboardBase', 'login'],
};
};
const res = await mockRemoteUserInfo(token);
return res;
const result = await adminRequest<{ id?: string; email: string; roles?: string[]; scopes?: string[]; mfaRequired?: boolean; mfaEnabled?: boolean; selfApprovalAllowed?: boolean }>('/api/v1/admin/me');
return { id: result.id, name: result.email, email: result.email, roles: result.roles || [], scopes: result.scopes || [], mfaRequired: result.mfaRequired, mfaEnabled: result.mfaEnabled, selfApprovalAllowed: result.selfApprovalAllowed === true };
});
const userSlice = createSlice({
name: namespace,
initialState,
reducers: {
setToken: (state, action: { payload: string }) => { state.token = action.payload; },
logout: (state) => {
localStorage.removeItem(TOKEN_NAME);
state.token = '';
state.userInfo = {};
},
remove: (state) => {
state.token = '';
},
clearMfaChallenge: (state) => {
state.userInfo = { ...state.userInfo, mfaChallengeId: undefined, mfaRequired: undefined };
},
},
extraReducers: (builder) => {
builder
.addCase(login.fulfilled, (state, action) => {
localStorage.setItem(TOKEN_NAME, action.payload);
state.token = action.payload;
if (action.payload.accessToken) state.token = action.payload.accessToken;
state.userInfo = { ...state.userInfo, mfaChallengeId: action.payload.challengeId, mfaRequired: action.payload.mfaRequired };
})
.addCase(getUserInfo.fulfilled, (state, action) => {
state.userInfo = action.payload;
@@ -92,8 +48,6 @@ const userSlice = createSlice({
},
});
export const selectListBase = (state: RootState) => state.listBase;
export const { logout, remove } = userSlice.actions;
export const { logout, remove, setToken, clearMfaChallenge } = userSlice.actions;
export default userSlice.reducer;
+103
View File
@@ -0,0 +1,103 @@
import React, { useMemo } from 'react';
import { Card, Statistic } from '@miragenflow/ui';
import ReactEcharts from 'echarts-for-react';
import useDynamicChart from 'hooks/useDynamicChart';
import Style from './index.module.less';
type FinanceRow = Record<string, any>;
type FinanceKind = 'billing' | 'recharges' | 'payments';
const numberValue = (value: unknown) => {
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : 0;
};
const dateValue = (row: FinanceRow) => {
const value = row.createdAt || row.paidAt || row.appliedAt;
return typeof value === 'string' && value.length >= 10 ? value.slice(0, 10) : '未知日期';
};
const baseGrid = { left: 48, right: 24, top: 26, bottom: 34, containLabel: true };
const axisStyle = { axisLine: { lineStyle: { color: 'var(--td-component-border)' } }, splitLine: { lineStyle: { color: 'var(--td-component-border)' } } };
const statusLabels: Record<string, string> = { pending: '待处理', processing: '处理中', queued: '排队中', received: '已接收', paid: '已到账', succeeded: '成功', applied: '已应用', confirmed_success: '已确认成功', rejected: '已拒绝', failed: '失败', confirmed_failure: '已确认失败', canceled: '已取消', refunded: '已退款', unknown: '未知' };
const providerLabels: Record<string, string> = { manual: '人工', mock: '模拟', adapter: '适配器', webhook: '支付回调', unknown: '未知' };
const ChartPanel = ({ title, option }: { title: string; option: Record<string, any> }) => {
const themedOption = useDynamicChart(option);
return <Card title={title} bordered={false} className={Style.chartCard}>
<ReactEcharts option={themedOption} notMerge lazyUpdate style={{ height: 270, width: '100%' }} />
</Card>;
};
const emptyOption = (title: string) => ({ title: { text: title, left: 'center', top: 'middle', fontSize: 14, fontWeight: 400 }, xAxis: { show: false }, yAxis: { show: false }, series: [] });
const StatCards = ({ items }: { items: Array<{ title: string; value: number; suffix?: string; decimalPlaces?: number }> }) => <div className={Style.stats}>{items.map((item) => <Card key={item.title} className={Style.statCard} bordered={false}><Statistic title={item.title} value={item.value} suffix={item.suffix} decimalPlaces={item.decimalPlaces} /></Card>)}</div>;
const statusOptionFor = (rows: FinanceRow[], fallback: string) => {
const counts = rows.reduce<Record<string, number>>((result, row) => {
const status = String(row.status || 'unknown');
result[status] = (result[status] || 0) + 1;
return result;
}, {});
const data = Object.entries(counts).reduce<Array<{ name: string; value: number }>>((result, [status, value]) => {
const name = statusLabels[status] || '其他状态';
const item = result.find((entry) => entry.name === name);
if (item) item.value += value; else result.push({ name, value });
return result;
}, []);
return data.length ? { tooltip: { trigger: 'item' }, legend: { bottom: 0 }, series: [{ type: 'pie', radius: ['45%', '72%'], center: ['50%', '45%'], data, label: { formatter: '{b}\n{d}%' } }] } : emptyOption(fallback);
};
export const FinanceCharts = ({ kind, rows }: { kind: FinanceKind; rows: FinanceRow[] }) => {
const billingSummary = useMemo(() => {
const credit = rows.reduce((sum, row) => { const amount = numberValue(row.amount); return sum + (amount > 0 ? amount : 0); }, 0);
const debit = rows.reduce((sum, row) => { const amount = numberValue(row.amount); return sum + (amount < 0 ? Math.abs(amount) : 0); }, 0);
return { total: rows.length, credit, debit, net: credit - debit };
}, [rows]);
const billingTrend = useMemo(() => {
const byDate = rows.reduce<Record<string, { credit: number; debit: number }>>((result, row) => {
const date = dateValue(row); const amount = numberValue(row.amount); const current = result[date] || { credit: 0, debit: 0 };
if (amount >= 0) current.credit += amount; else current.debit += Math.abs(amount);
result[date] = current; return result;
}, {});
const dates = Object.keys(byDate).sort().slice(-14);
return dates.length ? { tooltip: { trigger: 'axis' }, grid: baseGrid, legend: { bottom: 0, data: ['入账金币', '扣减金币'] }, xAxis: { type: 'category', data: dates.map((date) => date.slice(5)), ...axisStyle }, yAxis: { type: 'value', min: 0, name: '金币', ...axisStyle }, series: [{ name: '入账金币', type: 'bar', data: dates.map((date) => byDate[date].credit), barMaxWidth: 26 }, { name: '扣减金币', type: 'bar', data: dates.map((date) => byDate[date].debit), barMaxWidth: 26 }] } : emptyOption('暂无账本趋势');
}, [rows]);
const billingTypes = useMemo(() => {
const counts = rows.reduce<Record<string, number>>((result, row) => { const type = String(row.type || 'unknown'); result[type] = (result[type] || 0) + 1; return result; }, {});
const labels: Record<string, string> = { credit: '充值入账', recharge: '充值入账', debit: '任务扣费', settle: '任务扣费', adjustment: '余额调整', balance_adjustment: '余额调整', manual_recharge: '人工充值', refund: '退款', purchase: '套餐购买', reserve: '余额预留', release: '预留释放', charge: '任务消费', unknown: '未知' };
const data = Object.entries(counts).reduce<Array<{ name: string; value: number }>>((result, [type, value]) => {
const name = labels[type] || '其他类型';
const item = result.find((entry) => entry.name === name);
if (item) item.value += value; else result.push({ name, value });
return result;
}, []);
return data.length ? { tooltip: { trigger: 'item' }, legend: { bottom: 0 }, series: [{ type: 'pie', radius: ['45%', '72%'], center: ['50%', '45%'], data, label: { formatter: '{b}\n{d}%' } }] } : emptyOption('暂无账本类型');
}, [rows]);
const rechargeTrend = useMemo(() => {
const byDate = rows.reduce<Record<string, { fiat: number; coins: number }>>((result, row) => {
const date = dateValue(row); const current = result[date] || { fiat: 0, coins: 0 }; current.fiat += numberValue(row.fiatAmount); current.coins += numberValue(row.balanceAmount); result[date] = current; return result;
}, {});
const dates = Object.keys(byDate).sort().slice(-14);
return dates.length ? { tooltip: { trigger: 'axis' }, grid: baseGrid, legend: { bottom: 0, data: ['支付金额', '到账金币'] }, xAxis: { type: 'category', data: dates.map((date) => date.slice(5)), ...axisStyle }, yAxis: [{ type: 'value', name: '金额', min: 0, ...axisStyle }, { type: 'value', name: '金币', min: 0, ...axisStyle }], series: [{ name: '支付金额', type: 'bar', data: dates.map((date) => byDate[date].fiat), barMaxWidth: 26 }, { name: '到账金币', type: 'line', yAxisIndex: 1, data: dates.map((date) => byDate[date].coins), smooth: true, symbolSize: 7 }] } : emptyOption('暂无充值趋势');
}, [rows]);
const providerOption = useMemo(() => {
const counts = rows.reduce<Record<string, number>>((result, row) => { const provider = providerLabels[String(row.provider || 'unknown')] || '其他支付方式'; result[provider] = (result[provider] || 0) + 1; return result; }, {});
const data = Object.entries(counts).sort(([, a], [, b]) => b - a).slice(0, 12);
return data.length ? { tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } }, grid: { ...baseGrid, left: 76 }, xAxis: { type: 'value', min: 0, ...axisStyle }, yAxis: { type: 'category', data: data.map(([name]) => name), ...axisStyle }, series: [{ name: '事件数', type: 'bar', data: data.map(([, value]) => value), barMaxWidth: 24, label: { show: true, position: 'right' } }] } : emptyOption('暂无支付适配器数据');
}, [rows]);
if (kind === 'billing') return <><StatCards items={[{ title: '账本记录', value: billingSummary.total }, { title: '累计入账', value: billingSummary.credit, suffix: '金币', decimalPlaces: 2 }, { title: '累计扣减', value: billingSummary.debit, suffix: '金币', decimalPlaces: 2 }, { title: '净变化', value: billingSummary.net, suffix: '金币', decimalPlaces: 2 }]} /><div className={Style.chartGrid}><ChartPanel title='近 14 日金币流动' option={billingTrend} /><ChartPanel title='账本类型分布' option={billingTypes} /></div></>;
if (kind === 'recharges') {
const pending = rows.filter((row) => ['pending', 'processing', 'queued'].includes(String(row.status))).length;
const paid = rows.filter((row) => ['paid', 'succeeded', 'applied', 'confirmed_success'].includes(String(row.status))).length;
const fiat = rows.reduce((sum, row) => sum + numberValue(row.fiatAmount), 0);
return <><StatCards items={[{ title: '充值订单', value: rows.length }, { title: '已到账订单', value: paid }, { title: '待处理订单', value: pending }, { title: '累计订单金额', value: fiat, suffix: '元', decimalPlaces: 2 }]} /><div className={Style.chartGrid}><ChartPanel title='充值订单状态' option={statusOptionFor(rows, '暂无充值订单')} /><ChartPanel title='近 14 日充值金额与金币' option={rechargeTrend} /></div></>;
}
const applied = rows.filter((row) => ['applied', 'succeeded', 'confirmed_success', 'paid'].includes(String(row.status))).length;
const rejected = rows.filter((row) => ['rejected', 'failed', 'confirmed_failure'].includes(String(row.status))).length;
const pending = rows.filter((row) => ['pending', 'processing', 'queued', 'received', 'unknown'].includes(String(row.status))).length;
return <><StatCards items={[{ title: '支付事件', value: rows.length }, { title: '已应用事件', value: applied }, { title: '异常事件', value: rejected }, { title: '待处理事件', value: pending }]} /><div className={Style.chartGrid}><ChartPanel title='支付事件状态' option={statusOptionFor(rows, '暂无支付事件')} /><ChartPanel title='支付适配器事件量' option={providerOption} /></div></>;
};
@@ -0,0 +1,328 @@
import React, { useMemo } from 'react';
import { Card, Statistic } from '@miragenflow/ui';
import ReactEcharts from 'echarts-for-react';
import useDynamicChart from 'hooks/useDynamicChart';
import Style from '../index.module.less';
type ChartRow = Record<string, any>;
const isChartRow = (value: unknown): value is ChartRow => Boolean(value && typeof value === 'object' && !Array.isArray(value));
const chartRows = (value: unknown) => Array.isArray(value) ? value.filter(isChartRow) : [];
const numberValue = (value: unknown) => {
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : 0;
};
const percent = (value: unknown) => `${(numberValue(value) * 100).toFixed(1)}%`;
const emptyOption = (title: string) => ({
title: { text: title, left: 'center', top: 'middle', fontSize: 14, fontWeight: 400 },
xAxis: { show: false },
yAxis: { show: false },
series: [],
});
const ChartPanel = ({ title, option }: { title: string; option: Record<string, any> }) => {
const themedOption = useDynamicChart(option);
return <Card title={title} bordered={false} className={Style.chartCard}>
<ReactEcharts option={themedOption} notMerge lazyUpdate style={{ height: 270, width: '100%' }} />
</Card>;
};
const baseGrid = { left: 48, right: 24, top: 26, bottom: 34, containLabel: true };
const axisStyle = { axisLine: { lineStyle: { color: 'var(--td-component-border)' } }, splitLine: { lineStyle: { color: 'var(--td-component-border)' } } };
export const OverviewCharts = ({ dashboard }: { dashboard: ChartRow }) => {
const trend = chartRows(dashboard?.trend);
const modelMetrics = chartRows(dashboard?.modelMetrics);
const channelMetrics = chartRows(dashboard?.channelMetrics);
const messageTrend = chartRows(dashboard?.messageTrend);
const paymentTrend = chartRows(dashboard?.paymentTrend);
const revenueTrend = chartRows(dashboard?.revenueTrend);
const users = numberValue(dashboard?.users);
const activeUsers = Math.min(users, Math.max(0, numberValue(dashboard?.activeUsers)));
const channels = numberValue(dashboard?.channels);
const availableChannels = Math.min(channels, Math.max(0, numberValue(dashboard?.availableChannels)));
const p50 = dashboard?.p50LatencyMs === undefined ? undefined : Math.max(0, numberValue(dashboard.p50LatencyMs));
const p95 = dashboard?.p95LatencyMs === undefined ? undefined : Math.max(0, numberValue(dashboard.p95LatencyMs));
const maxLatency = Math.max(100, p95 || 0, p50 || 0);
const activityOption = useMemo(() => users ? ({
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' }, valueFormatter: (value: number) => `${value} 人` },
grid: { ...baseGrid, left: 72, right: 30 },
xAxis: { type: 'value', max: users, ...axisStyle },
yAxis: { type: 'category', data: ['活跃用户', '其他用户'], ...axisStyle },
series: [{ type: 'bar', barWidth: 22, showBackground: true, backgroundStyle: { color: 'var(--td-bg-color-secondarycontainer)' }, data: [activeUsers, Math.max(0, users - activeUsers)], itemStyle: { borderRadius: [0, 5, 5, 0] }, label: { show: true, position: 'right', formatter: '{c} 人' } }],
}) : emptyOption('暂无用户数据'), [activeUsers, users]);
const channelOption = useMemo(() => channels ? ({
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' }, valueFormatter: (value: number) => `${value} 个` },
grid: { ...baseGrid, left: 72, right: 36, top: 48, bottom: 42 },
xAxis: { type: 'value', min: 0, max: channels, ...axisStyle },
yAxis: { type: 'category', data: ['渠道可用情况'], ...axisStyle },
series: [
{ name: '可用渠道', type: 'bar', stack: 'total', barWidth: 30, data: [availableChannels], itemStyle: { color: 'var(--td-success-color)', borderRadius: [6, 0, 0, 6] }, label: { show: true, position: 'inside', formatter: `${availableChannels} 个` } },
{ name: '不可用渠道', type: 'bar', stack: 'total', barWidth: 30, data: [Math.max(0, channels - availableChannels)], itemStyle: { color: 'var(--td-bg-color-secondarycontainer)', borderRadius: [0, 6, 6, 0] }, label: { show: true, position: 'inside', color: 'var(--td-text-color-secondary)', formatter: (params: ChartRow) => params.value ? `${params.value} 个` : '' } },
],
graphic: [{ type: 'text', left: 'center', top: 8, style: { text: `可用率 ${Math.round((availableChannels / channels) * 100)}%`, fontSize: 16, fontWeight: 600, fill: 'var(--td-text-color-primary)' } }],
}) : emptyOption('暂无渠道数据'), [availableChannels, channels]);
const latencyOption = useMemo(() => {
if (p50 === undefined && p95 === undefined) return emptyOption('暂无延迟样本');
const ring = (value: number | undefined, center: [string, string], color: string) => {
const ratio = value === undefined ? 0 : Math.min(1, Math.max(0, value / maxLatency));
return {
type: 'pie',
center,
radius: ['52%', '68%'],
startAngle: 90,
clockwise: true,
silent: true,
label: { show: false },
labelLine: { show: false },
data: [
{ value: ratio, itemStyle: { color } },
{ value: 1 - ratio, itemStyle: { color: 'var(--td-bg-color-secondarycontainer)' } },
],
};
};
const centerText = (value: number | undefined, label: string, left: string) => ({
type: 'group', left, top: 0, width: '16%', height: '100%',
children: [
{ type: 'text', left: 'center', top: '38%', style: { text: value === undefined ? '暂无样本' : `${Math.round(value)} 毫秒`, textAlign: 'center', fill: 'var(--td-text-color-primary)', fontSize: value === undefined ? 13 : 16, fontWeight: 600 } },
{ type: 'text', left: 'center', top: '61%', style: { text: label, textAlign: 'center', fill: 'var(--td-text-color-secondary)', fontSize: 12 } },
],
});
return {
tooltip: { trigger: 'item', formatter: () => '延迟数据按最近调用样本计算' },
series: [p50 === undefined ? undefined : ring(p50, ['28%', '50%'], 'var(--td-brand-color)'), p95 === undefined ? undefined : ring(p95, ['72%', '50%'], 'var(--td-warning-color)')].filter(Boolean),
graphic: [centerText(p50, 'P50', '20%'), centerText(p95, 'P95', '64%')],
};
}, [maxLatency, p50, p95]);
const trendOption = useMemo(() => trend.length ? ({
tooltip: { trigger: 'axis' },
grid: baseGrid,
legend: { bottom: 0, data: ['任务数', '成功率'] },
xAxis: { type: 'category', data: trend.map((item: ChartRow) => String(item.date).slice(5)), ...axisStyle },
yAxis: [{ type: 'value', name: '任务数', ...axisStyle }, { type: 'value', name: '成功率', max: 1, axisLabel: { formatter: (value: number) => `${Math.round(value * 100)}%` }, ...axisStyle }],
series: [
{ name: '任务数', type: 'bar', barMaxWidth: 26, data: trend.map((item: ChartRow) => numberValue(item.tasks)) },
{ name: '成功率', type: 'line', yAxisIndex: 1, smooth: true, data: trend.map((item: ChartRow) => numberValue(item.successRate)), symbolSize: 7 },
],
}) : emptyOption('暂无任务趋势'), [trend]);
const channelSuccessOption = useMemo(() => channelMetrics.length ? ({
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' }, formatter: (params: ChartRow[]) => {
const item = params?.[0];
const source = channelMetrics.find((row: ChartRow) => String(row.label) === String(item?.name));
return `${item?.name || '渠道'}:${percent(item?.value)}${source ? `(${numberValue(source.attempts)} 次调用)` : ''}`;
} },
grid: { ...baseGrid, left: 88, right: 28 },
xAxis: { type: 'value', min: 0, max: 1, axisLabel: { formatter: (value: number) => `${Math.round(value * 100)}%` }, ...axisStyle },
yAxis: { type: 'category', data: channelMetrics.map((item: ChartRow, index: number) => String(item.label || `渠道 ${index + 1}`)), ...axisStyle },
series: [{ name: '成功率', type: 'bar', barMaxWidth: 22, data: channelMetrics.map((item: ChartRow) => {
let color = 'var(--td-warning-color)';
if (item.health === 'healthy') color = 'var(--td-success-color)';
else if (['open', 'disabled'].includes(String(item.health))) color = 'var(--td-error-color)';
return { value: Math.min(1, Math.max(0, numberValue(item.successRate))), itemStyle: { color } };
}), label: { show: true, position: 'right', formatter: (params: ChartRow) => percent(params.value) } }],
}) : emptyOption('暂无来源渠道数据'), [channelMetrics]);
const messageTrendOption = useMemo(() => messageTrend.length ? ({
tooltip: { trigger: 'axis' },
grid: baseGrid,
legend: { bottom: 0, data: ['发送失败', '待发送', '失败率'] },
xAxis: { type: 'category', data: messageTrend.map((item: ChartRow) => String(item.date || '').slice(5)), ...axisStyle },
yAxis: [{ type: 'value', min: 0, name: '条数', ...axisStyle }, { type: 'value', min: 0, max: 1, name: '失败率', axisLabel: { formatter: (value: number) => `${Math.round(value * 100)}%` }, ...axisStyle }],
series: [{ name: '发送失败', type: 'bar', barMaxWidth: 24, data: messageTrend.map((item: ChartRow) => numberValue(item.failed)) }, { name: '待发送', type: 'bar', barMaxWidth: 24, data: messageTrend.map((item: ChartRow) => numberValue(item.pending)) }, { name: '失败率', type: 'line', yAxisIndex: 1, smooth: true, data: messageTrend.map((item: ChartRow) => numberValue(item.failureRate)), symbolSize: 7 }],
}) : emptyOption('暂无消息趋势'), [messageTrend]);
const paymentTrendOption = useMemo(() => paymentTrend.length ? ({
tooltip: { trigger: 'axis' },
grid: baseGrid,
legend: { bottom: 0, data: ['已处理', '支付异常', '待处理'] },
xAxis: { type: 'category', data: paymentTrend.map((item: ChartRow) => String(item.date || '').slice(5)), ...axisStyle },
yAxis: { type: 'value', min: 0, name: '事件数', ...axisStyle },
series: [{ name: '已处理', type: 'bar', barMaxWidth: 24, data: paymentTrend.map((item: ChartRow) => numberValue(item.applied)) }, { name: '支付异常', type: 'bar', barMaxWidth: 24, data: paymentTrend.map((item: ChartRow) => numberValue(item.rejected)) }, { name: '待处理', type: 'line', smooth: true, data: paymentTrend.map((item: ChartRow) => numberValue(item.pending)), symbolSize: 7 }],
}) : emptyOption('暂无支付趋势'), [paymentTrend]);
const revenueOption = useMemo(() => revenueTrend.length ? ({
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
grid: baseGrid,
legend: { bottom: 0, data: ['收入(元)', '到账金币'] },
xAxis: { type: 'category', data: revenueTrend.map((item: ChartRow) => String(item.date || '').slice(5)), ...axisStyle },
yAxis: [{ type: 'value', min: 0, name: '元', ...axisStyle }, { type: 'value', min: 0, name: '金币', ...axisStyle }],
series: [
{ name: '收入(元)', type: 'bar', barMaxWidth: 24, data: revenueTrend.map((item: ChartRow) => numberValue(item.fiatAmount)) },
{ name: '到账金币', type: 'line', yAxisIndex: 1, smooth: true, symbolSize: 7, data: revenueTrend.map((item: ChartRow) => numberValue(item.balanceAmount)) },
],
}) : emptyOption('暂无收入趋势'), [revenueTrend]);
const modelOption = useMemo(() => modelMetrics.length ? ({
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' }, valueFormatter: (value: number) => percent(value) },
grid: baseGrid,
xAxis: { type: 'value', max: 1, axisLabel: { formatter: (value: number) => `${Math.round(value * 100)}%` }, ...axisStyle },
yAxis: { type: 'category', data: modelMetrics.map((item: ChartRow) => String(item.modelName || item.name || '未命名模型')), ...axisStyle },
series: [{ name: '成功率', type: 'bar', data: modelMetrics.map((item: ChartRow) => numberValue(item.successRate)), barMaxWidth: 24 }],
}) : emptyOption('暂无模型指标'), [modelMetrics]);
const statusOption = useMemo(() => {
const total = numberValue(dashboard?.tasks);
const counts = dashboard?.taskStatusCounts && typeof dashboard.taskStatusCounts === 'object' ? dashboard.taskStatusCounts as Record<string, unknown> : {};
const succeeded = numberValue(counts.succeeded ?? dashboard?.succeededTasks);
const queued = numberValue(counts.queued ?? dashboard?.queuedTasks);
const running = numberValue(counts.running);
const failed = numberValue(counts.failed);
const canceled = numberValue(counts.canceled);
const unknown = numberValue(counts.unknown);
const partial = numberValue(counts.partial);
const accounted = succeeded + queued + running + failed + canceled + unknown + partial;
const other = Math.max(0, total - accounted);
return total ? {
tooltip: { trigger: 'item', formatter: (params: ChartRow) => `${params.name}:${params.value}(${params.percent}%)` },
legend: { bottom: 0 },
series: [{ type: 'pie', radius: ['45%', '72%'], center: ['50%', '45%'], data: [{ name: '成功', value: succeeded }, { name: '部分成功', value: partial }, { name: '排队中', value: queued }, { name: '运行中', value: running }, { name: '失败', value: failed }, { name: '已取消', value: canceled }, { name: '待对账', value: unknown }, { name: '其他', value: other }].filter((item) => item.value > 0), label: { formatter: '{b}\n{d}%' } }],
} : emptyOption('暂无任务状态');
}, [dashboard]);
return <div className={Style.chartGrid}>
<ChartPanel title='用户活跃度' option={activityOption} />
<ChartPanel title='渠道可用率' option={channelOption} />
<ChartPanel title='任务延迟' option={latencyOption} />
<ChartPanel title='近 7 日任务趋势' option={trendOption} />
<ChartPanel title='模型成功率' option={modelOption} />
<ChartPanel title='任务状态分布' option={statusOption} />
<ChartPanel title='来源渠道成功率' option={channelSuccessOption} />
<ChartPanel title='消息失败趋势' option={messageTrendOption} />
<ChartPanel title='支付异常趋势' option={paymentTrendOption} />
<ChartPanel title='收入与到账金币趋势' option={revenueOption} />
</div>;
};
export const ChannelHealthCharts = ({ rows }: { rows: ChartRow[] }) => {
const safeRows = useMemo(() => rows.filter(isChartRow), [rows]);
const channels = useMemo(() => safeRows.slice(0, 12), [safeRows]);
const performanceOption = useMemo(() => channels.length ? ({
tooltip: { trigger: 'axis' },
grid: baseGrid,
legend: { bottom: 0, data: ['成功率', 'P95 延迟'] },
xAxis: { type: 'category', data: channels.map((row, index) => String(row.label || `渠道 ${index + 1}`)), axisLabel: { rotate: channels.length > 7 ? 28 : 0 }, ...axisStyle },
yAxis: [{ type: 'value', max: 1, axisLabel: { formatter: (value: number) => `${Math.round(value * 100)}%` }, ...axisStyle }, { type: 'value', name: '毫秒', ...axisStyle }],
series: [{ name: '成功率', type: 'bar', data: channels.map((row) => numberValue(row.successRate)), barMaxWidth: 24 }, { name: 'P95 延迟', type: 'line', yAxisIndex: 1, data: channels.map((row) => numberValue(row.latencyP95Ms)), smooth: true, symbolSize: 7 }],
}) : emptyOption('暂无渠道健康数据'), [channels]);
const stateOption = useMemo(() => {
const stateCount = safeRows.reduce<Record<string, number>>((result, row) => {
const state = !row.lastTestedAt && row.lastTestHealthy === undefined ? 'untested' : String(row.health || 'unknown');
result[state] = (result[state] || 0) + 1;
return result;
}, {});
const labels: Record<string, string> = { healthy: '健康', degraded: '降级', open: '熔断', disabled: '已停用', untested: '未测试', unknown: '未知' };
const data = Object.entries(stateCount).map(([key, value]) => ({ name: labels[key] || '未知', value }));
return data.length ? { tooltip: { trigger: 'item' }, legend: { bottom: 0 }, series: [{ type: 'pie', radius: ['45%', '72%'], center: ['50%', '45%'], data, label: { formatter: '{b}\n{d}%' } }] } : emptyOption('暂无渠道状态');
}, [safeRows]);
return <div className={Style.chartGrid}>
<ChartPanel title='渠道成功率与延迟' option={performanceOption} />
<ChartPanel title='渠道状态分布' option={stateOption} />
</div>;
};
export const TaskCharts = ({ rows }: { rows: ChartRow[] }) => {
const safeRows = useMemo(() => rows.filter(isChartRow), [rows]);
const statusLabels: Record<string, string> = { queued: '排队中', running: '运行中', succeeded: '成功', partial: '部分成功', failed: '失败', canceled: '已取消', unknown: '待对账' };
const statusOption = useMemo(() => {
const statusCount = safeRows.reduce<Record<string, number>>((result, row) => {
const key = String(row.status || 'unknown');
result[key] = (result[key] || 0) + 1;
return result;
}, {});
const data = Object.entries(statusCount).map(([key, value]) => ({ name: statusLabels[key] || '未知状态', value }));
return data.length ? { tooltip: { trigger: 'item' }, legend: { bottom: 0 }, series: [{ type: 'pie', radius: ['45%', '72%'], center: ['50%', '45%'], data, label: { formatter: '{b}\n{d}%' } }] } : emptyOption('暂无任务状态');
}, [safeRows]);
const trendOption = useMemo(() => {
const days = safeRows.reduce<Record<string, { total: number; success: number; failed: number }>>((result, row) => {
const date = typeof row.createdAt === 'string' ? row.createdAt.slice(0, 10) : '未知日期';
const current = result[date] || { total: 0, success: 0, failed: 0 };
current.total += 1;
if (row.status === 'succeeded' || row.status === 'partial') current.success += 1;
if (row.status === 'failed' || row.status === 'canceled') current.failed += 1;
result[date] = current;
return result;
}, {});
const dates = Object.keys(days).sort().slice(-14);
return dates.length ? { tooltip: { trigger: 'axis' }, grid: baseGrid, legend: { bottom: 0, data: ['任务数', '成功', '失败'] }, xAxis: { type: 'category', data: dates.map((date) => date.slice(5)), ...axisStyle }, yAxis: { type: 'value', ...axisStyle }, series: [{ name: '任务数', type: 'bar', data: dates.map((date) => days[date].total), barMaxWidth: 26 }, { name: '成功', type: 'line', data: dates.map((date) => days[date].success), smooth: true }, { name: '失败', type: 'line', data: dates.map((date) => days[date].failed), smooth: true }] } : emptyOption('暂无任务趋势');
}, [safeRows]);
return <div className={Style.chartGrid}>
<ChartPanel title='任务状态分布' option={statusOption} />
<ChartPanel title='任务创建趋势' option={trendOption} />
</div>;
};
export const RealtimeMetricsCharts = ({ rows, history }: { rows: ChartRow[]; history: ChartRow[] }) => {
const safeRows = useMemo(() => rows.filter(isChartRow), [rows]);
const safeHistory = useMemo(() => history.filter(isChartRow), [history]);
const samples = useMemo(() => {
const latest = safeRows[0] || {};
if (safeHistory.length) return safeHistory;
return safeRows.length ? [{ ...latest, recordedAt: new Date().toISOString() }] : [];
}, [safeHistory, safeRows]);
const metricOption = useMemo(() => samples.length ? ({
tooltip: { trigger: 'axis' },
grid: baseGrid,
legend: { bottom: 0, data: ['连接数', '订阅数'] },
xAxis: { type: 'category', data: samples.map((item) => new Date(item.recordedAt || Date.now()).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })), ...axisStyle },
yAxis: { type: 'value', ...axisStyle },
series: [{ name: '连接数', type: 'line', data: samples.map((item) => numberValue(item.connections)), smooth: true, areaStyle: {} }, { name: '订阅数', type: 'line', data: samples.map((item) => numberValue(item.subscriptions)), smooth: true }],
}) : emptyOption('等待实时数据'), [samples]);
const queueOption = useMemo(() => samples.length ? ({
tooltip: { trigger: 'axis' },
grid: baseGrid,
legend: { bottom: 0, data: ['任务队列', '事件积压'] },
xAxis: { type: 'category', data: samples.map((item) => new Date(item.recordedAt || Date.now()).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })), ...axisStyle },
yAxis: { type: 'value', ...axisStyle },
series: [{ name: '任务队列', type: 'bar', data: samples.map((item) => numberValue(item.taskQueues)), barMaxWidth: 26 }, { name: '事件积压', type: 'line', data: samples.map((item) => numberValue(item.eventBacklog)), smooth: true, symbolSize: 7 }],
}) : emptyOption('等待队列数据'), [samples]);
return <div className={Style.chartGrid}>
<ChartPanel title='连接与订阅趋势' option={metricOption} />
<ChartPanel title='队列与事件积压' option={queueOption} />
</div>;
};
export const AuditDashboardCharts = ({ rows }: { rows: ChartRow[] }) => {
const safeRows = useMemo(() => rows.filter(isChartRow), [rows]);
const statusFor = (row: ChartRow) => {
const status = Number(row.statusCode);
if (status >= 400) return '异常';
return /失败|拒绝|错误|死信|failed|rejected|error|dead/i.test(`${row.action || ''} ${row.actionLabel || ''}`) ? '异常' : '正常';
};
const categoryOption = useMemo(() => {
const counts = safeRows.reduce<Record<string, number>>((result, row) => { const key = String(row.category || '其他管理'); result[key] = (result[key] || 0) + 1; return result; }, {});
const categoryLabels: Record<string, string> = { admin: '认证与安全', audit: '审计访问', user: '用户管理', channel: '渠道管理', channel_group: '渠道管理', model_product: '模型与价格', pricing: '模型与价格', plan: '套餐与权益', task: '任务处理', payment: '财务与支付', approval: '审批管理', system_settings: '系统设置', message: '消息通知', '认证与安全': '认证与安全', '审计访问': '审计访问', '用户管理': '用户管理', '渠道管理': '渠道管理', '模型与价格': '模型与价格', '套餐与权益': '套餐与权益', '任务处理': '任务处理', '财务与支付': '财务与支付', '审批管理': '审批管理', '系统设置': '系统设置', '消息通知': '消息通知' };
const data = Object.entries(counts).map(([name, value]) => ({ name: categoryLabels[name] || '其他管理', value }));
return data.length ? { tooltip: { trigger: 'item' }, legend: { bottom: 0 }, series: [{ type: 'pie', radius: ['45%', '72%'], center: ['50%', '45%'], data, label: { formatter: '{b}\n{d}%' } }] } : emptyOption('暂无审计记录');
}, [safeRows]);
const statusOption = useMemo(() => {
const counts = safeRows.reduce<Record<string, number>>((result, row) => { const key = statusFor(row); result[key] = (result[key] || 0) + 1; return result; }, {});
const data = [{ name: '正常(HTTP < 400)', value: counts.正常 || 0 }, { name: '异常(HTTP >= 400 或业务错误)', value: counts.异常 || 0 }].filter((item) => item.value > 0);
return data.length ? { tooltip: { trigger: 'item' }, legend: { bottom: 0 }, series: [{ type: 'pie', radius: ['45%', '72%'], center: ['50%', '45%'], data, label: { formatter: '{b}\n{d}%' } }] } : emptyOption('暂无日志状态');
}, [safeRows]);
const summary = useMemo(() => {
const abnormal = safeRows.filter((row) => statusFor(row) === '异常').length;
return { total: safeRows.length, normal: safeRows.length - abnormal, abnormal, abnormalRate: safeRows.length ? abnormal / safeRows.length : 0 };
}, [safeRows]);
const timelineOption = useMemo(() => {
const days = safeRows.reduce<Record<string, { normal: number; error: number }>>((result, row) => {
const date = typeof row.createdAt === 'string' ? row.createdAt.slice(0, 10) : '未知日期';
const current = result[date] || { normal: 0, error: 0 };
if (statusFor(row) === '正常') current.normal += 1;
else current.error += 1;
result[date] = current;
return result;
}, {});
const dates = Object.keys(days).sort().slice(-14);
return dates.length ? { tooltip: { trigger: 'axis' }, grid: baseGrid, legend: { bottom: 0, data: ['正常 < 400', '异常 >= 400 或业务错误'] }, xAxis: { type: 'category', data: dates.map((date) => date.slice(5)), ...axisStyle }, yAxis: { type: 'value', ...axisStyle }, series: [{ name: '正常 < 400', type: 'bar', stack: 'status', data: dates.map((date) => days[date].normal), barMaxWidth: 28 }, { name: '异常 >= 400 或业务错误', type: 'bar', stack: 'status', data: dates.map((date) => days[date].error), barMaxWidth: 28 }] } : emptyOption('暂无日志趋势');
}, [safeRows]);
return <>
<div className={Style.stats}>
<Card className={Style.statCard} bordered={false}><Statistic title='日志总数' value={summary.total} /></Card>
<Card className={Style.statCard} bordered={false}><Statistic title='正常日志(HTTP < 400)' value={summary.normal} /></Card>
<Card className={Style.statCard} bordered={false}><Statistic title='异常日志(HTTP >= 400 或业务错误)' value={summary.abnormal} /></Card>
<Card className={Style.statCard} bordered={false}><Statistic title='异常占比' value={Number((summary.abnormalRate * 100).toFixed(1))} suffix='%' /></Card>
</div>
<div className={Style.chartGrid}>
<ChartPanel title='日志分类占比' option={categoryOption} />
<ChartPanel title='正常与异常日志' option={statusOption} />
<ChartPanel title='日志数量趋势' option={timelineOption} />
</div>
</>;
};
@@ -0,0 +1 @@
export { AuditDashboardCharts, ChannelHealthCharts, OverviewCharts, RealtimeMetricsCharts, TaskCharts } from './Charts';
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-261
View File
@@ -1,261 +0,0 @@
import type { EChartOption } from 'echarts';
import { getChartDataSet, ONE_WEEK_LIST } from 'utils/chart';
export const getLineChartOptions = (dateTime: Array<string> = []): EChartOption => {
const [timeArray, inArray, outArray] = getChartDataSet(dateTime);
return {
tooltip: {
trigger: 'item',
},
grid: {
left: '0',
right: '20px',
top: '5px',
bottom: '36px',
containLabel: true,
},
legend: {
left: 'center',
bottom: '0',
orient: 'horizontal', // legend 横向布局。
data: ['本月', '上月'],
textStyle: {
fontSize: 12,
},
},
xAxis: {
type: 'category',
data: timeArray,
boundaryGap: false,
axisLine: {
lineStyle: {
color: '#E3E6EB',
width: 1,
},
},
},
yAxis: {
type: 'value',
},
series: [
{
name: '本月',
data: outArray,
type: 'line',
smooth: false,
showSymbol: true,
symbol: 'circle',
symbolSize: 8,
itemStyle: {
borderWidth: 1,
},
areaStyle: {
color: '#0053D92F',
},
},
{
name: '上月',
data: inArray,
type: 'line',
smooth: false,
showSymbol: true,
symbol: 'circle',
symbolSize: 8,
itemStyle: {
borderWidth: 1,
},
},
],
};
};
export const getPieChartOptions = (radius = 42): EChartOption => ({
tooltip: {
trigger: 'item',
},
grid: {
top: '0',
right: '0',
},
legend: {
itemWidth: 12,
itemHeight: 4,
textStyle: {
fontSize: 12,
},
left: 'center',
bottom: '0',
orient: 'horizontal', // legend 横向布局。
},
series: [
{
name: '销售渠道',
type: 'pie',
radius: ['48%', '60%'],
avoidLabelOverlap: false,
silent: true,
itemStyle: {
borderWidth: 1,
},
label: {
show: true,
position: 'center',
formatter: ['{value|{d}%}', '{name|{b}渠道占比}'].join('\n'),
rich: {
value: {
fontSize: 28,
fontWeight: 'normal',
lineHeight: 46,
},
name: {
color: '#909399',
fontSize: 12,
lineHeight: 14,
},
},
},
labelLine: {
show: false,
},
data: [
{ value: 1048, name: '线上' },
{ value: radius * 7, name: '门店' },
],
},
],
});
export const getBarChartOptions = (dateTime: Array<string> = []): EChartOption => {
const [timeArray, inArray, outArray] = getChartDataSet(dateTime);
return {
tooltip: {
trigger: 'item',
},
xAxis: {
type: 'category',
data: timeArray,
axisLine: {
lineStyle: {
width: 1,
},
},
},
yAxis: {
type: 'value',
},
grid: {
top: '5%',
left: '25px',
right: 0,
bottom: '60px',
},
legend: {
icon: 'rect',
itemWidth: 12,
itemHeight: 4,
itemGap: 48,
textStyle: {
fontSize: 12,
color: 'rgba(0, 0, 0, 0.6)',
},
left: 'center',
bottom: '0',
orient: 'horizontal',
data: ['本月', '上月'],
},
series: [
{
name: '本月',
data: outArray,
type: 'bar',
},
{
name: '上月',
data: inArray,
type: 'bar',
},
],
};
};
// PieChartIcon Data
export const MICRO_CHART_OPTIONS_LINE: EChartOption = {
xAxis: {
type: 'category',
show: false,
data: ONE_WEEK_LIST,
},
yAxis: {
show: false,
type: 'value',
},
grid: {
top: 0,
left: 0,
right: 0,
bottom: 0,
tooltip: {
show: false,
},
},
color: ['#fff'],
series: [
{
data: [150, 230, 224, 218, 135, 147, 260],
type: 'line',
showSymbol: false,
},
],
};
// BarChartIcon Data
export const MICRO_CHART_OPTIONS_BAR: EChartOption = {
xAxis: {
type: 'category',
show: false,
data: ONE_WEEK_LIST,
},
yAxis: {
show: false,
type: 'value',
},
grid: {
top: 0,
left: 0,
right: 0,
bottom: 0,
tooltip: {
show: false,
},
},
series: [
{
data: [
100,
130,
184,
218,
{
value: 135,
itemStyle: {
opacity: 0.2,
},
},
{
value: 118,
itemStyle: {
opacity: 0.2,
},
},
{
value: 60,
itemStyle: {
opacity: 0.2,
},
},
],
type: 'bar',
barWidth: 9,
},
],
};
@@ -1,3 +0,0 @@
.middleChartPanel {
margin-top: 16px;
}
@@ -1,47 +0,0 @@
import React, { useState } from 'react';
import { Col, Row, Card } from 'tdesign-react';
import ReactEcharts from 'echarts-for-react';
import useDynamicChart from 'hooks/useDynamicChart';
import LastWeekDatePicker from 'components/DatePicker';
import { getLineChartOptions, getPieChartOptions } from '../chart';
import Style from './MiddleChart.module.less';
const lineOptions = getLineChartOptions();
const pieOptions = getPieChartOptions();
const MiddleChart = () => {
const [customOptions, setCustomOptions] = useState(lineOptions);
const onTimeChange = (value: Array<string>) => {
const options = getLineChartOptions(value);
setCustomOptions(options);
};
const dynamicLineChartOption = useDynamicChart(customOptions, {
placeholderColor: ['legend.textStyle.color', 'xAxis.axisLabel.color', 'yAxis.axisLabel.color'],
borderColor: ['series.0.itemStyle.borderColor', 'series.1.itemStyle.borderColor'],
});
const dynamicPieChartOption = useDynamicChart(pieOptions, {
placeholderColor: ['legend.textStyle.color'],
containerColor: ['series.0.itemStyle.borderColor'],
textColor: ['label.color', 'label.color'],
});
return (
<Row gutter={[16, 16]} className={Style.middleChartPanel}>
<Col xs={12} xl={9}>
<Card title='统计数据' subtitle='(万元)' actions={LastWeekDatePicker(onTimeChange)} bordered={false}>
<ReactEcharts option={dynamicLineChartOption} notMerge={true} lazyUpdate={false} />
</Card>
</Col>
<Col xs={12} xl={3}>
<Card title='销售渠道' subtitle='2021-12' bordered={false}>
<ReactEcharts option={dynamicPieChartOption} notMerge={true} lazyUpdate={true} />
</Card>
</Col>
</Row>
);
};
export default React.memo(MiddleChart);
@@ -1,5 +0,0 @@
.overviewPanel {
background-color: var(--td-bg-color-container);
border-radius: 3px;
margin-top: 16px;
}
@@ -1,69 +0,0 @@
import React, { useState } from 'react';
import { Button, Col, Row, Card } from 'tdesign-react';
import ReactEcharts from 'echarts-for-react';
import useDynamicChart from 'hooks/useDynamicChart';
import Board, { ETrend } from 'components/Board';
import LastWeekDatePicker from 'components/DatePicker';
import { getBarChartOptions } from '../chart';
import Style from './Overview.module.less';
const options = getBarChartOptions();
const Overview = (): React.ReactElement => {
const [customOptions, setCustomOptions] = useState(options);
const onTimeChange = (value: any) => {
// eslint-disable-next-line no-shadow
const options = getBarChartOptions(value);
setCustomOptions(options);
};
const dynamicChartOption = useDynamicChart(customOptions, {
placeholderColor: ['legend.textStyle.color', 'xAxis.axisLabel.color', 'yAxis.axisLabel.color'],
});
return (
<div className={Style.overviewPanel}>
<Row>
<Col xs={12} xl={9} span={12}>
<Card title=' 出入库概览 ' subtitle='(件)' actions={LastWeekDatePicker(onTimeChange)} bordered={false}>
<ReactEcharts
option={dynamicChartOption} // option:图表配置项
notMerge={true}
lazyUpdate={true}
style={{ height: 351 }}
/>
</Card>
</Col>
<Col xs={12} xl={3} span={12}>
<Card actions={<Button>导出数据</Button>} bordered={false}>
<Row>
<Col xl={12} xs={6} span={12}>
<Board
title='本月出库总计(件)'
count='1726'
trend={ETrend.down}
trendNum='20.3%'
desc='自从上周以来'
border={false}
style={{ height: '168px' }}
/>
</Col>
<Col xl={12} xs={6} span={12}>
<Board
title='活跃用户(个)'
count='1126'
trend={ETrend.down}
trendNum='20.5%'
desc='自从上周以来'
border={false}
style={{ height: '168px' }}
/>
</Col>
</Row>
</Card>
</Col>
</Row>
</div>
);
};
export default React.memo(Overview);
@@ -1,19 +0,0 @@
.rankListPanel {
margin-top: 16px;
}
.rankIndex {
display: inline-flex;
width: 24px;
height: 24px;
border-radius: 50%;
color: #fff;
font-size: 14px;
background-color: var(--td-gray-color-5);
align-items: center;
justify-content: center;
font-weight: 700;
}
.rankIndexTop {
background: var(--td-brand-color);
}
@@ -1,133 +0,0 @@
import React from 'react';
import { Col, Radio, Row, Table, Button, Card } from 'tdesign-react';
import { TdPrimaryTableProps } from 'tdesign-react/es/table';
import classnames from 'classnames';
import { TrendIcon, ETrend } from 'components/Board';
import { PURCHASE_TREND_LIST, SALE_TREND_LIST } from '../constant';
import Style from './RankList.module.less';
const DateRadioGroup = (
<Radio.Group defaultValue='recent_week'>
<Radio.Button value='recent_week'>本周</Radio.Button>
<Radio.Button value='recent_month'>近三个月</Radio.Button>
</Radio.Group>
);
const SALE_COLUMNS: TdPrimaryTableProps['columns'] = [
{
align: 'center',
colKey: 'index',
title: '排名',
width: 80,
fixed: 'left',
cell: ({ rowIndex }) => (
<span className={classnames(Style.rankIndex, { [Style.rankIndexTop]: rowIndex < 3 })}>{rowIndex + 1}</span>
),
},
{
align: 'left',
ellipsis: true,
colKey: 'productName',
title: '客户名称',
width: 200,
},
{
align: 'center',
colKey: 'growUp',
width: 100,
title: '较上周',
cell: ({ row }) => <TrendIcon trend={row.growUp < 0 ? ETrend.down : ETrend.up} trendNum={Math.abs(row.growUp)} />,
},
{
align: 'center',
colKey: 'count',
title: '订单量',
width: 100,
},
{
align: 'center',
colKey: 'date',
width: 140,
title: '合同签订日期',
},
{
align: 'center',
colKey: 'operation',
fixed: 'right',
title: '操作',
width: 80,
cell: ({ row }) => (
<Button variant='text' theme='primary' onClick={() => console.log(row)}>
操作
</Button>
),
},
];
const PURCHASE_COLUMNS: TdPrimaryTableProps['columns'] = [
{
align: 'center',
colKey: 'index',
title: '排名',
width: 80,
fixed: 'left',
cell: ({ rowIndex }) => (
<span className={classnames(Style.rankIndex, { [Style.rankIndexTop]: rowIndex < 3 })}>{rowIndex + 1}</span>
),
},
{
align: 'left',
ellipsis: true,
colKey: 'productName',
title: '供应商名称',
width: 200,
},
{
align: 'center',
colKey: 'growUp',
width: 100,
title: '较上周',
cell: ({ row }) => <TrendIcon trend={row.growUp < 0 ? ETrend.down : ETrend.up} trendNum={Math.abs(row.growUp)} />,
},
{
align: 'center',
colKey: 'count',
title: '订单量',
width: 100,
},
{
align: 'center',
colKey: 'date',
width: 140,
title: '合同签订日期',
},
{
align: 'center',
colKey: 'operation',
title: '操作',
fixed: 'right',
width: 80,
cell: ({ row }) => (
<Button variant='text' theme='primary' onClick={() => console.log(row)}>
操作
</Button>
),
},
];
const RankList = () => (
<Row gutter={[16, 16]} className={Style.rankListPanel}>
<Col xs={12} xl={6} span={12}>
<Card title='销售订单排名' actions={DateRadioGroup} bordered={false}>
<Table columns={SALE_COLUMNS} rowKey='productName' size='medium' data={SALE_TREND_LIST} />
</Card>
</Col>
<Col xs={12} xl={6} span={12}>
<Card title='采购订单排名' actions={DateRadioGroup} bordered={false}>
<Table columns={PURCHASE_COLUMNS} rowKey='productName' size='medium' data={PURCHASE_TREND_LIST} />
</Card>
</Col>
</Row>
);
export default React.memo(RankList);
@@ -1,35 +0,0 @@
.iconWrap {
display: inline-flex;
align-items: center;
justify-content: center;
width: 56px;
height: 56px;
background: var(--td-brand-color-1);
border-radius: 50%;
position: absolute;
top: 32px;
right: 32px;
}
.svgIcon {
font-size: 24px;
color: var(--td-brand-color);
}
.paneLineChart {
width: 103px;
height: 56px;
overflow: hidden;
position: absolute;
top: 55px;
right: 32px;
}
.paneBarChart {
width: 103px;
height: 36px;
-webkit-tap-highlight-color: transparent;
user-select: none;
position: absolute;
top: 55px;
right: 32px;
}
@@ -1,92 +0,0 @@
import React, { memo } from 'react';
import { Col, Row } from 'tdesign-react';
import { UsergroupIcon, FileIcon } from 'tdesign-icons-react';
import ReactEcharts from 'echarts-for-react';
import Board, { ETrend, IBoardProps } from 'components/Board';
import useDynamicChart from 'hooks/useDynamicChart';
import { MICRO_CHART_OPTIONS_BAR, MICRO_CHART_OPTIONS_LINE } from '../chart';
import Style from './TopPanel.module.less';
const BarChartIcon = memo(() => {
const dynamicChartOption = useDynamicChart(MICRO_CHART_OPTIONS_BAR);
return (
<div className={Style.paneBarChart}>
<ReactEcharts
option={dynamicChartOption} // option:图表配置项
notMerge={true}
lazyUpdate={true}
style={{ height: 36 }}
/>
</div>
);
});
const PieChartIcon = memo(() => (
<div className={Style.paneLineChart}>
<ReactEcharts
option={MICRO_CHART_OPTIONS_LINE} // option:图表配置项
notMerge={true}
lazyUpdate={true}
style={{ height: 56 }}
/>
</div>
));
const PANE_LIST: Array<IBoardProps> = [
{
title: '总收入',
count: '¥ 28,425.00',
trend: ETrend.up,
trendNum: '20.5%',
Icon: <PieChartIcon />,
},
{
title: '总退款',
count: '¥ 768.00',
trend: ETrend.down,
trendNum: '20.5%',
Icon: <BarChartIcon />,
},
{
title: '活跃用户(个)',
count: '1126',
trend: ETrend.down,
trendNum: '20.5%',
Icon: (
<div className={Style.iconWrap}>
<UsergroupIcon className={Style.svgIcon} />
</div>
),
},
{
title: '产生订单(个)',
count: '527',
trend: ETrend.down,
trendNum: '20.5%',
Icon: (
<div className={Style.iconWrap}>
<FileIcon className={Style.svgIcon} />
</div>
),
},
];
const TopPanel = () => (
<Row gutter={[16, 16]}>
{PANE_LIST.map((item, index) => (
<Col key={item.title} xs={6} xl={3}>
<Board
title={item.title}
trend={item.trend}
trendNum={item.trendNum}
count={item.count}
desc={'自从上周以来'}
Icon={item.Icon}
dark={index === 0}
/>
</Col>
))}
</Row>
);
export default React.memo(TopPanel);
@@ -1,77 +0,0 @@
export const SALE_TREND_LIST = [
{
growUp: 1,
productName: '国家电网有限公司',
count: 7059,
date: '2021-09-01',
},
{
growUp: -1,
productName: '深圳燃气集团股份有限公司',
count: 6437,
date: '2021-09-01',
},
{
growUp: 4,
productName: '国家烟草专卖局',
count: 4221,
date: '2021-09-01',
},
{
growUp: 3,
productName: '中国电信集团有限公司',
count: 3317,
date: '2021-09-01',
},
{
growUp: -3,
productName: '中国移动通信集团有限公司',
count: 3015,
date: '2021-09-01',
},
{
growUp: -3,
productName: '新余市办公用户采购项目',
count: 2015,
date: '2021-09-12',
},
];
export const PURCHASE_TREND_LIST = [
{
growUp: 1,
productName: '腾讯科技(深圳)有限公司',
count: 3015,
date: '2021-09-01',
},
{
growUp: -1,
productName: '大润发有限公司',
count: 2015,
date: '2021-09-01',
},
{
growUp: 6,
productName: '四川海底捞股份有限公司',
count: 1815,
date: '2021-09-11',
},
{
growUp: -3,
productName: '索尼(中国)有限公司',
count: 1015,
date: '2021-09-21',
},
{
growUp: -4,
productName: '松下电器(中国)有限公司',
count: 445,
date: '2021-09-19',
},
{
growUp: -3,
productName: '新余市办公用户采购项目',
count: 2015,
date: '2021-09-12',
},
];
-16
View File
@@ -1,16 +0,0 @@
import React, { memo } from 'react';
import TopPanel from './components/TopPanel';
import MiddleChart from './components/MiddleChart';
import RankList from './components/RankList';
import Overview from './components/Overview';
const DashBoard = () => (
<div style={{ overflowX: 'hidden' }}>
<TopPanel />
<MiddleChart />
<RankList />
<Overview />
</div>
);
export default memo(DashBoard);
-203
View File
@@ -1,203 +0,0 @@
import type { EChartOption } from 'echarts';
import { ONE_WEEK_LIST, getChartDataSet, getTimeArray, getRandomInt } from 'utils/chart';
// line chart options
export const getLineChartOptions = (dateTime: Array<string> = []): EChartOption => {
let dateArray: Array<string> = ONE_WEEK_LIST;
if (dateTime.length > 0) {
const dividedNum = 7;
dateArray = getTimeArray(dateTime, dividedNum, 'YYYY-MM-DD');
}
return {
grid: {
top: '5%',
right: '10px',
left: '30px',
bottom: '60px',
},
legend: {
left: 'center',
bottom: '0',
orient: 'horizontal', // legend 横向布局。
data: ['杯子', '茶叶', '蜂蜜', '面粉'],
},
xAxis: {
type: 'category',
data: dateArray,
boundaryGap: false,
axisLabel: {
color: 'rgba(0, 0, 0, 0.4)',
},
axisLine: {
lineStyle: {
color: '#E3E6EB',
width: 1,
},
},
},
yAxis: {
type: 'value',
axisLabel: {
color: 'rgba(0, 0, 0, 0.4)',
},
},
tooltip: {
trigger: 'item',
},
series: [
{
showSymbol: true,
symbol: 'circle',
symbolSize: 8,
name: '杯子',
stack: '总量',
data: [
getRandomInt(),
getRandomInt(),
getRandomInt(),
getRandomInt(),
getRandomInt(),
getRandomInt(),
getRandomInt(),
],
type: 'line',
itemStyle: {
borderColor: '#ffffff',
borderWidth: 1,
},
},
{
showSymbol: true,
symbol: 'circle',
symbolSize: 8,
name: '茶叶',
stack: '总量',
data: [
getRandomInt(),
getRandomInt(),
getRandomInt(),
getRandomInt(),
getRandomInt(),
getRandomInt(),
getRandomInt(),
],
type: 'line',
itemStyle: {
borderColor: '#ffffff',
borderWidth: 1,
},
},
{
showSymbol: true,
symbol: 'circle',
symbolSize: 8,
name: '蜂蜜',
stack: '总量',
data: [
getRandomInt(),
getRandomInt(),
getRandomInt(),
getRandomInt(),
getRandomInt(),
getRandomInt(),
getRandomInt(),
],
type: 'line',
itemStyle: {
borderColor: '#ffffff',
borderWidth: 1,
},
},
{
showSymbol: true,
symbol: 'circle',
symbolSize: 8,
name: '面粉',
stack: '总量',
data: [
getRandomInt(),
getRandomInt(),
getRandomInt(),
getRandomInt(),
getRandomInt(),
getRandomInt(),
getRandomInt(),
],
type: 'line',
itemStyle: {
borderColor: '#ffffff',
borderWidth: 1,
},
},
],
};
};
export const getScatterChartOptions = (dateTime: Array<string> = []): EChartOption => {
const [timeArray, inArray, outArray] = getChartDataSet(dateTime);
return {
xAxis: {
data: timeArray,
axisLabel: {
color: 'rgba(0, 0, 0, 0.4)',
},
splitLine: { show: false },
axisLine: {
lineStyle: {
color: '#E3E6EB',
width: 1,
},
},
},
yAxis: {
type: 'value',
// splitLine: { show: false},
axisLabel: {
color: 'rgba(0, 0, 0, 0.4)',
},
nameTextStyle: {
padding: [0, 0, 0, 60],
},
axisTick: {
show: false,
},
axisLine: {
show: false,
},
},
tooltip: {
trigger: 'item',
},
grid: {
top: '5px',
left: '25px',
right: '5px',
bottom: '60px',
},
legend: {
left: 'center',
bottom: '0',
orient: 'horizontal', // legend 横向布局。
data: ['按摩仪', '咖啡机'],
itemHeight: 8,
itemWidth: 8,
},
series: [
{
name: '按摩仪',
symbolSize: 10,
data: outArray,
type: 'scatter',
},
{
name: '咖啡机',
symbolSize: 10,
data: inArray,
type: 'scatter',
},
],
};
};
@@ -1,25 +0,0 @@
import React from 'react';
import { Col, Row, Card } from 'tdesign-react';
import Board from 'components/Board';
import { PANE_LIST } from '../constant';
const MonthPurchase = () => (
<Card title='本月采购申请情况' bordered={false}>
<Row gutter={[16, 16]}>
{PANE_LIST.map((item) => (
<Col key={item.title} xs={6} xl={3} span={12}>
<Board
title={item.title}
trend={item.trend}
trendNum={item.trendNum}
count={item.count}
desc={'环比'}
border
/>
</Col>
))}
</Row>
</Card>
);
export default React.memo(MonthPurchase);
@@ -1,56 +0,0 @@
.purchaseTrendPanel {
margin-top: 16px;
}
.productTrendPanel {
display: flex;
flex-direction: column;
border-radius: 3px;
overflow: hidden;
cursor: pointer;
color: var(--td-text-color-secondary);
background: var(--td-bg-color-container);
padding: 24px 32px;
}
.productLogo {
width: 56px;
height: 56px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
background: var(--td-brand-color-1);
font-size: 32px;
color: var(--td-brand-color);
}
.productName {
margin: 24px 0 8px;
font-size: 16px;
font-weight: 400;
color: var(--td-text-color-primary);
}
.productDesc {
font-size: 12px;
line-height: 20px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
margin-bottom: 24px;
height: 40px;
}
.iconWrap {
position: relative;
.lightBtn {
position: absolute;
left: 22px;
background-color: var(--td-brand-color-2);
--ripple-color: var(--td-brand-color-2);
color: var(--td-brand-color);
}
}
@@ -1,101 +0,0 @@
import React, { useState } from 'react';
import { Col, Dropdown, Row, Tag, Card, Avatar } from 'tdesign-react';
import { Icon, AddIcon } from 'tdesign-icons-react';
import ReactEcharts from 'echarts-for-react';
import LastWeekDatePicker from 'components/DatePicker';
import useDynamicChart from 'hooks/useDynamicChart';
import { PRODUCT_LIST } from '../constant';
import { getLineChartOptions } from '../chart';
import Style from './PurchaseThrend.module.less';
interface IProductTrendProps {
description: string;
isSetup: boolean;
name: string;
type: string;
icon: string;
}
const ProductTrend = ({ type, isSetup, description, name, icon }: IProductTrendProps) => (
<div className={Style.productTrendPanel}>
<Row justify='space-between'>
<div className={Style.productLogo}>
<Icon name={icon} />
</div>
<Tag theme='success'>{isSetup ? '已启用' : '已停用'}</Tag>
</Row>
<p className={Style.productName}>{name}</p>
<p className={Style.productDesc}>{description}</p>
<Row justify='space-between' align='middle'>
<Avatar.Group cascading='left-up' max={2}>
<Avatar>{type}</Avatar>
<Avatar icon={<AddIcon />}></Avatar>
</Avatar.Group>
<Dropdown
disabled={!isSetup}
options={[
{
content: '管理',
value: 'manage',
onClick: () => {},
},
{
content: '删除',
value: 'delete',
onClick: () => {},
},
]}
>
<Icon name='more' />
</Dropdown>
</Row>
</div>
);
const options = getLineChartOptions();
const PurchaseTrend = () => {
const [customOptions, setCustomOptions] = useState(options);
const onTimeChange = (value: Array<string>) => {
const options = getLineChartOptions(value);
setCustomOptions(options);
};
const dynamicChartOptions = useDynamicChart(customOptions, {
placeholderColor: ['legend.textStyle.color', 'xAxis.axisLabel.color', 'yAxis.axisLabel.color'],
});
return (
<Row className={Style.purchaseTrendPanel} gutter={[16, 16]}>
<Col xs={12} xl={9}>
<Card title='采购商品申请趋势' subtitle='(件)' actions={LastWeekDatePicker(onTimeChange)} bordered={false}>
<ReactEcharts
option={dynamicChartOptions} // option:图表配置项
notMerge={true}
lazyUpdate={true}
style={{ height: 453 }}
/>
</Card>
</Col>
<Col xs={12} xl={3}>
<Row gutter={[16, 16]}>
{PRODUCT_LIST.map((item, index) => (
<Col xs={12} key={index}>
<ProductTrend
type={item.type}
isSetup={item.isSetup}
description={item.description}
name={item.name}
icon={item.icon}
/>
</Col>
))}
</Row>
</Col>
</Row>
);
};
export default React.memo(PurchaseTrend);
@@ -1,14 +0,0 @@
.satisfactionPanel {
margin-top: 16px;
}
.operation {
display: flex;
}
.exportBtn {
margin-left: 8px;
}
@@ -1,44 +0,0 @@
import React, { useState } from 'react';
import { Button, Card } from 'tdesign-react';
import ReactEcharts from 'echarts-for-react';
import LastWeekDatePicker from 'components/DatePicker';
import useDynamicChart from 'hooks/useDynamicChart';
import { getScatterChartOptions } from '../chart';
import Style from './Satisfaction.module.less';
const Satisfaction = () => {
const options = getScatterChartOptions();
const [customOptions, setCustomOptions] = useState(options);
const onTimeChange = (value: Array<string>) => {
const options = getScatterChartOptions(value);
setCustomOptions(options);
};
const dynamicChartOption = useDynamicChart(customOptions, {
placeholderColor: ['legend.textStyle.color', 'xAxis.axisLabel.color', 'yAxis.axisLabel.color'],
});
return (
<div className={Style.satisfactionPanel}>
<Card
title='采购商品满意度分布'
actions={
<div className={Style.operation}>
{LastWeekDatePicker(onTimeChange)}
<Button className={Style.exportBtn}>导出数据</Button>
</div>
}
bordered={false}
>
<ReactEcharts
option={dynamicChartOption} // option:图表配置项
notMerge={true}
lazyUpdate={true}
style={{ height: 374 }}
/>
</Card>
</div>
);
};
export default React.memo(Satisfaction);
@@ -1,59 +0,0 @@
import { IBoardProps, ETrend } from 'components/Board';
export const PANE_LIST: Array<IBoardProps> = [
{
title: '总申请数(次)',
count: '1126',
trendNum: '10%',
trend: ETrend.up,
},
{
title: '供应商数量(个)',
count: '13',
trendNum: '13%',
trend: ETrend.down,
},
{
title: '采购商品品类(类)',
count: '4',
trendNum: '10%',
trend: ETrend.up,
},
{
title: '申请人数量(人)',
count: '90',
trendNum: '44%',
trend: ETrend.down,
},
{
title: '申请完成率(%)',
count: '80.5',
trendNum: '70%',
trend: ETrend.up,
},
{
title: '到货及时率(%)',
count: '78',
trendNum: '16%',
trend: ETrend.up,
},
];
export const PRODUCT_LIST = [
{
description: 'SSL证书又叫服务器证书,腾讯云为您提供证书的一站式服务,包括免费、付费证书的申请、管理及部',
index: 1,
isSetup: true,
name: 'SSL证书',
type: 'D',
icon: 'user',
},
{
description: 'SSL证书又叫服务器证书,腾讯云为您提供证书的一站式服务,包括免费、付费证书的申请、管理及部',
index: 1,
isSetup: true,
name: 'SSL证书',
type: 'C',
icon: 'calendar',
},
];
@@ -1,12 +0,0 @@
import React, { memo } from 'react';
import MonthPurchase from './components/MonthPurchase';
import PurchaseTrend from './components/PurchaseTrend';
import PurchaseSatisfaction from './components/Satisfaction';
export default memo(() => (
<div>
<MonthPurchase />
<PurchaseTrend />
<PurchaseSatisfaction />
</div>
));
@@ -1,41 +0,0 @@
.infoBox {
column-count: 2;
.infoBoxItem {
padding: 12px 0;
display: flex;
color: var(--td-text-color-primary);
h1 {
font-weight: 400;
font-size: 14px;
color: var(--td-text-color-secondary);
text-align: left;
line-height: 22px;
margin: 0;
}
span {
margin-left: 24px;
font-size: 14px;
line-height: 1.5;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
i {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--td-success-color-5);
margin-right: 5px;
}
.inProgress {
color: var(--td-success-color-5);
}
.pdf {
color: var(--td-brand-color);
&:hover {
cursor: pointer;
}
}
}
}
@@ -1,28 +0,0 @@
import React from 'react';
import { Card } from 'tdesign-react';
import classnames from 'classnames';
import { dataInfo } from '../consts';
import Style from './Base.module.less';
const Base = () => (
<Card title='基本信息' bordered={false}>
<div className={Style.infoBox}>
{dataInfo.map((item) => (
<div key={item.id} className={Style.infoBoxItem}>
<h1>{item.name}</h1>
<span
className={classnames({
[Style.inProgress]: item.type === 'status',
[Style.pdf]: item.type === 'link',
})}
>
{item.type === 'status' && <i />}
{item.value}
</span>
</div>
))}
</div>
</Card>
);
export default React.memo(Base);
@@ -1,110 +0,0 @@
import React from 'react';
import { Button, Table, Tag, Card } from 'tdesign-react';
import { dataBuyList, total } from '../consts';
import Style from '../index.module.less';
const Detail = () => (
<Card title='产品采购明细' className={Style.cardBox} bordered={false}>
<Table
data={dataBuyList}
columns={[
{
align: 'left',
width: 300,
ellipsis: true,
colKey: 'number',
title: '申请号',
},
{
align: 'left',
width: 200,
ellipsis: true,
colKey: 'name',
title: '产品名称',
cell({ row }) {
return (
<>
{row.name}
<Tag theme='default' size='small'>
{row.tag}
</Tag>
</>
);
},
},
{
align: 'left',
width: 200,
ellipsis: true,
colKey: 'code',
title: '产品编号',
},
{
align: 'left',
width: 100,
ellipsis: true,
colKey: 'amount',
title: '采购数量',
cell({ row }) {
return row.amount;
},
},
{
align: 'left',
width: 200,
ellipsis: true,
colKey: 'department',
title: '申请部门',
},
{
align: 'left',
width: 500,
ellipsis: true,
colKey: 'createtime',
title: '创建时间',
},
{
align: 'left',
fixed: 'right',
width: 300,
colKey: 'op',
title: '操作',
cell() {
return (
<>
<Button theme='primary' variant='text'>
管理
</Button>
<Button theme='primary' variant='text'>
删除
</Button>
</>
);
},
},
]}
rowKey='id'
verticalAlign='top'
hover
pagination={{
defaultPageSize: 10,
total,
defaultCurrent: 1,
showJumper: true,
onChange(pageInfo) {
console.log(pageInfo, 'onChange pageInfo');
},
onCurrentChange(current, pageInfo) {
console.log(current, 'onCurrentChange current');
console.log(pageInfo, 'onCurrentChange pageInfo');
},
onPageSizeChange(size, pageInfo) {
console.log(size, 'onPageSizeChange size');
console.log(pageInfo, 'onPageSizeChange pageInfo');
},
}}
/>
</Card>
);
export default React.memo(Detail);
@@ -1,41 +0,0 @@
import React from 'react';
import { Col, Row, Card } from 'tdesign-react';
import ProductCard from './ProductCard';
import Style from '../index.module.less';
const Product = () => (
<Card title='产品目录' className={Style.cardBox} bordered={false}>
<div>
<Row gutter={[16, 16]}>
<Col xs={12} xl={4}>
<ProductCard isAdd={true} />
</Col>
<Col xs={12} xl={4}>
<ProductCard
title='MacBook Pro 2021'
tags={['13.3 英寸', 'Apple M1', 'RAM 16GB']}
desc='最高可选配 16GB 内存 · 最高可选配 2TB 存储设备 电池续航最长达 18 小时'
percent='1420 / 1500(台)'
Icon='cart'
progress={(1420 / 1500) * 100}
trackColor='#D4E3FC'
/>
</Col>
<Col xs={12} xl={4}>
<ProductCard
title='Surface Laptop Go'
tags={['12.4 英寸', 'Core i7', 'RAM 16GB']}
desc='常规使用 Surface,续航时间最长可达13小时 随时伴您工作'
percent='120 / 2000(台)'
Icon='cart'
progress={(120 / 2000) * 100}
color='#E24D59'
trackColor='#FCD4D4'
/>
</Col>
</Row>
</div>
</Card>
);
export default React.memo(Product);
@@ -1,108 +0,0 @@
.productionCard {
position: relative;
background-color: var(--td-bg-color-container);
border: 1px solid var(--td-component-border);
border-radius: 3px;
padding: 20px 32px 24px;
height: 212px;
box-sizing: content-box;
}
.productionTitle {
margin-bottom: 25px;
position: relative;
}
.title {
font-weight: 500;
font-size: 24px;
color: var(--td-text-color-primary);
}
.productionIcon {
position: absolute;
top: 0px;
right: 0px;
background: var(--td-brand-color-1);
color: var(--td-brand-color);
font-size: 56px;
padding: 14px;
border-radius: 100%;
}
.tag {
margin-right: 4px;
margin-top: 8px;
margin-left: unset;
border: unset;
}
.item {
position: relative;
padding-top: 8px;
padding-bottom: 8px;
}
.info {
display: inline-block;
width: 60%;
text-align: left;
font-size: 14px;
color: var(--td-text-color-placeholder);
}
.icon {
position: absolute;
bottom: 8px;
right: 0;
color: rgba(0, 0, 0, 0.26);
}
.footer {
position: absolute;
width: 100%;
bottom: 0px;
left: 0;
.percent {
position: absolute;
bottom: 15px;
right: 32px;
color: var(--td-text-color-placeholder);
}
.progress {
margin: 0;
}
}
.productionAdd {
position: relative;
background-color: var(--td-bg-color-container);
border: 1px dashed var(--td-component-border);
border-radius: 3px;
padding: 20px 32px 24px;
height: 212px;
box-sizing: content-box;
display: flex;
justify-content: center;
align-items: center;
}
.productionAddBtn {
font-size: 14px;
color: var(--td-text-color-secondary);
margin: 0 auto;
text-align: center;
line-height: 22px;
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
.productionAddIcon {
background: var(--td-brand-color-1);
color: var(--td-brand-color);
font-size: 33px;
padding: 8px;
border-radius: 100%;
margin-bottom: 12px;
}
}
@@ -1,55 +0,0 @@
import React from 'react';
import { Progress, Tag } from 'tdesign-react';
import { AddIcon, ChevronRightIcon, Icon } from 'tdesign-icons-react';
import Styles from './ProductCard.module.less';
interface IProps {
isAdd?: boolean;
title?: string;
tags?: string[];
desc?: string;
progress?: number;
percent?: string;
Icon?: string;
color?: string;
trackColor?: string;
}
const ProductCard = (props: IProps) => (
<div className={Styles.productCard}>
{props.isAdd ? (
<div className={Styles.productionAdd}>
<div className={Styles.productionAddBtn}>
<AddIcon className={Styles.productionAddIcon} />
<span>新增产品</span>
</div>
</div>
) : (
<div className={Styles.productionCard}>
<div className={Styles.productionTitle}>
<Icon name={props.Icon} className={Styles.productionIcon} />
<div className={Styles.title}>{props.title}</div>
<div className={Styles.tags}>
{props?.tags?.map((tag, index) => (
<Tag key={index} className={Styles.tag} theme='success' variant='dark' size='small'>
{tag}
</Tag>
))}
</div>
</div>
<div className={Styles.item}>
<span className={Styles.info}>{props.desc}</span>
<ChevronRightIcon className={Styles.icon} />
</div>
<div className={Styles.footer}>
<span className={Styles.percent}>{props.percent}</span>
<div className={Styles.progress}>
<Progress percentage={props.progress} label={false} color={props.color} trackColor={props.trackColor} />
</div>
</div>
</div>
)}
</div>
);
export default React.memo(ProductCard);
@@ -1,18 +0,0 @@
import React from 'react';
import { Steps, Card } from 'tdesign-react';
import { dataStep, stepCurrent } from '../consts';
import Style from '../index.module.less';
const { StepItem } = Steps;
const Progress = () => (
<Card title='发票进度' className={Style.cardBox} bordered={false}>
<Steps current={stepCurrent}>
{dataStep.map((item) => (
<StepItem key={item.id} title={item.name} content={item.detail} />
))}
</Steps>
</Card>
);
export default React.memo(Progress);
-54
View File
@@ -1,54 +0,0 @@
// Mock Data of 基本信息
interface InfoItem {
id: number;
name: string;
value: string;
type?: string;
}
export const dataInfo: InfoItem[] = [
{ id: 1, name: '合同名称', value: '总部办公用品采购项目' },
{ id: 2, name: '合同状态', value: '履行中', type: 'status' },
{ id: 3, name: '合同编号', value: 'BH00010' },
{ id: 4, name: '合同类型', value: '主合同' },
{ id: 5, name: '合同收付类型', value: '付款' },
{ id: 6, name: '合同金额', value: '5,000,000元' },
{ id: 7, name: '甲方', value: '腾讯科技(深圳)有限公司' },
{ id: 8, name: '乙方', value: '欧尚' },
{ id: 9, name: '合同签订日期', value: '2020-12-20' },
{ id: 10, name: '合同生效日期', value: '2021-01-20' },
{ id: 11, name: '合同结束日期', value: '2022-12-20' },
{ id: 12, name: '合同附件', value: '总部办公用品采购项目合同.pdf', type: 'link' },
{ id: 13, name: '备注', value: '--' },
{ id: 14, name: '创建时间', value: '2020-12-22 10:00:00' },
];
// Mock Data of 变更记录
interface IStepItem {
id: number;
name: string;
detail?: string;
}
export const dataStep: IStepItem[] = [
{ id: 1, name: '申请提交', detail: '已于12月21日提交' },
{ id: 2, name: '电子发票', detail: '预计1~3个工作日' },
{ id: 3, name: '发票已邮寄', detail: '电子发票开出后7个工作日内联系' },
{ id: 4, name: '完成', detail: '' },
];
export const stepCurrent = 2;
// Mock Data of 产品采购明细
export const total = 36;
const listTables: any = [];
for (let i = 0; i < total; i++) {
listTables.push({
id: i,
number: 'S20201228115950963',
name: 'Macbook ',
tag: '电子产品',
code: 'p_tmp_60a637cd0d ',
amount: 52,
department: '财务部',
createtime: '2021-12-30 10:43:56',
});
}
export const dataBuyList = listTables;
@@ -1,3 +0,0 @@
.cardBox {
margin-top: 15px;
}
-14
View File
@@ -1,14 +0,0 @@
import React, { memo } from 'react';
import Base from './components/Base';
import ProgressComp from './components/Progress';
import Product from './components/Product';
import Detail from './components/Detail';
export default memo(() => (
<div>
<Base />
<ProgressComp />
<Product />
<Detail />
</div>
));
-22
View File
@@ -1,22 +0,0 @@
export const dataInfo = [
{ id: 1, name: '合同名称', value: '总部办公用品采购项目' },
{ id: 2, name: '合同状态', value: '履行中', type: 'status' },
{ id: 3, name: '合同编号', value: 'BH00010' },
{ id: 4, name: '合同类型', value: '主合同' },
{ id: 5, name: '合同收付类型', value: '付款' },
{ id: 6, name: '合同金额', value: '5,000,000元' },
{ id: 7, name: '甲方', value: '腾讯科技(深圳)有限公司' },
{ id: 8, name: '乙方', value: '欧尚' },
{ id: 9, name: '合同签订日期', value: '2020-12-20' },
{ id: 10, name: '合同生效日期', value: '2021-01-20' },
{ id: 11, name: '合同结束日期', value: '2022-12-20' },
{ id: 12, name: '合同附件', value: '总部办公用品采购项目合同.pdf', type: 'link' },
{ id: 13, name: '备注', value: '--' },
{ id: 14, name: '创建时间', value: '2020-12-22 10:00:00' },
];
export const dataStep = [
{ id: 1, name: '上传合同附件', detail: '这里是提示文字' },
{ id: 2, name: '修改合同金额', detail: '这里是提示文字' },
{ id: 3, name: '新建合同', detail: '2020-12-01 15:00:00 管理员-李川操作' },
];
@@ -1,57 +0,0 @@
@Wrap: infoBox;
.@{Wrap} {
column-count: 2;
&Item {
padding: 12px 0;
display: flex;
color: var(--td-text-color-primary);
h1 {
font-weight: 400;
font-size: 14px;
color: var(--td-text-color-secondary);
text-align: left;
line-height: 22px;
margin: 0;
}
span {
margin-left: 24px;
font-size: 14px;
line-height: 1.5;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
i {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--td-success-color-5);
margin-right: 5px;
}
.inProgress {
color: var(--td-success-color-5);
}
.pdf {
color: var(--td-brand-color);
&:hover {
cursor: pointer;
}
}
}
}
.logBox {
margin-top: 15px;
}
@media (min-width: 992px) {
.@{Wrap} {
&Item {
h1 {
width: 200px;
}
}
}
}
-39
View File
@@ -1,39 +0,0 @@
import React, { memo } from 'react';
import { Steps, Card } from 'tdesign-react';
import classnames from 'classnames';
import { dataInfo, dataStep } from './consts';
import Style from './index.module.less';
const { StepItem } = Steps;
export default memo(() => (
<div>
<Card title='基本信息' bordered={false}>
<div className={classnames(Style.infoBox)}>
{dataInfo.map((item) => (
<div key={item.id} className={classnames(Style.infoBoxItem)}>
<h1>{item.name}</h1>
<span
className={classnames({
[Style.inProgress]: item.type === 'status',
[Style.pdf]: item.type === 'link',
})}
>
{item.type === 'status' && <i />}
{item.value}
</span>
</div>
))}
</div>
</Card>
<Card title='变更记录' className={Style.logBox} bordered={false}>
<div>
<Steps layout='vertical' theme='dot' current={1}>
{dataStep.map((item) => (
<StepItem key={item.id} title={item.name} content={item.detail} />
))}
</Steps>
</div>
</Card>
</div>
));
-158
View File
@@ -1,158 +0,0 @@
import type { EChartOption } from 'echarts';
import { getTimeArray, getRandomInt } from 'utils/chart';
export function getLineOptions(dateTime: any = []): EChartOption {
let dateArray: Array<string> = ['00:00', '02:00', '04:00', '06:00'];
if (dateTime.length > 0) {
const divideNum = 7;
dateArray = getTimeArray(dateTime, divideNum);
}
return {
tooltip: {
trigger: 'item',
},
grid: {
top: '10px',
left: '0',
right: '20px',
bottom: '36px',
containLabel: true,
},
xAxis: {
type: 'category',
data: dateArray,
boundaryGap: false,
axisLine: {
lineStyle: {
color: '#E3E6EB',
width: 1,
},
},
},
yAxis: {
type: 'value',
},
legend: {
data: ['本月', '上月'],
icon: 'circle',
bottom: '0',
itemGap: 48,
itemHeight: 8,
itemWidth: 8,
},
series: [
{
name: '上月',
data: [
getRandomInt(),
getRandomInt(),
getRandomInt(),
getRandomInt(),
getRandomInt(),
getRandomInt(),
getRandomInt(),
],
type: 'line',
smooth: true,
showSymbol: true,
symbol: 'circle',
symbolSize: 8,
areaStyle: {
color: '#0053D92F',
},
},
{
name: '本月',
data: [
getRandomInt(),
getRandomInt(),
getRandomInt(),
getRandomInt(),
getRandomInt(),
getRandomInt(),
getRandomInt(),
],
type: 'line',
smooth: true,
showSymbol: true,
symbol: 'circle',
symbolSize: 8,
},
],
};
}
export const lastYearList: Array<number> = [100, 120, 140, 160, 180, 200, 210];
export function getBarOptions(isMonth = false): EChartOption {
let lastYearListCopy = lastYearList.concat([]);
let thisYearListCopy = lastYearList.concat([]);
if (isMonth) {
lastYearListCopy = lastYearListCopy.reverse();
thisYearListCopy = thisYearListCopy.reverse();
}
return {
tooltip: {
trigger: 'item',
},
grid: {
top: '10px',
left: '0',
right: '0',
bottom: '36px',
containLabel: true,
},
xAxis: [
{
type: 'category',
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
axisTick: {
alignWithLabel: true,
},
axisLine: {
lineStyle: {
color: '#E3E6EB',
width: 1,
},
},
},
],
yAxis: [
{
type: 'value',
axisLabel: {
color: 'rgba(0, 0, 0, 0.4)',
},
},
],
legend: {
data: ['去年', '今年'],
bottom: '0',
icon: 'rect',
itemGap: 48,
itemHeight: 4,
itemWidth: 12,
// itemStyle: {},
},
series: [
{
name: '去年',
type: 'bar',
barWidth: '30%',
data: lastYearListCopy,
itemStyle: {
color: '#BCC4D0',
},
},
{
name: '今年',
type: 'bar',
barWidth: '30%',
data: thisYearListCopy,
},
],
};
}
@@ -1,10 +0,0 @@
.operationLink {
color: var(--td-brand-color);
text-decoration: none;
margin-right: 24px;
cursor: pointer;
transition: color 0.2s cubic-bezier(0.38, 0, 0.24, 1);
&:last-child {
margin-right: 0;
}
}
@@ -1,72 +0,0 @@
import React, { useEffect, useState } from 'react';
import { Table, Card } from 'tdesign-react';
import { TableSort, TdPrimaryTableProps } from 'tdesign-react/es/table';
import request from 'utils/request';
import { TABLE_COLUMNS } from '../constant';
import ManagementPopup from './ManagementPopup';
import Style from './BottomTable.module.less';
const BottomTable = () => {
const [sort, setSort] = useState<TableSort>({ sortBy: 'name', descending: true });
const [visible, setVisible] = useState(false);
const [{ tableData }, setTableData] = useState({ tableData: [] });
const pagination = {
pageSize: 10,
total: tableData.length,
pageSizeOptions: [],
};
useEffect(() => {
request.get('/api/get-project-list').then((res: any) => {
if (res.code === 0) {
const { list = [] } = res.data;
setTableData({ tableData: list });
}
});
}, []);
const removeRow = (rowIndex: number) => {
console.log(' rowIndex = ', rowIndex);
console.log(' tableData = ', tableData);
tableData.splice(rowIndex, 1);
setTableData({ tableData });
};
const getTableColumns = (columns: TdPrimaryTableProps['columns']): TdPrimaryTableProps['columns'] => {
if (columns) {
columns[4].cell = (context) => {
const { rowIndex } = context;
return (
<>
<a className={Style.operationLink} onClick={() => setVisible(!visible)}>
管理
</a>
<a className={Style.operationLink} onClick={() => removeRow(rowIndex)}>
删除
</a>
</>
);
};
}
return columns;
};
return (
<>
<Card title='项目列表' bordered={false}>
<Table
columns={getTableColumns(TABLE_COLUMNS)}
rowKey='index'
pagination={pagination}
data={tableData}
sort={sort}
onSortChange={(newSort: TableSort) => setSort(newSort)}
/>
</Card>
{visible && <ManagementPopup visible={visible} />}
</>
);
};
export default React.memo(BottomTable);
@@ -1,30 +0,0 @@
import React, { useEffect, useState } from 'react';
import { EChartOption } from 'echarts';
import ReactEcharts from 'echarts-for-react';
import useDynamicChart from 'hooks/useDynamicChart';
import { getLineOptions } from '../chart';
const DynamicLineChart = () => {
const [lineOptions, setLineOptions] = useState<EChartOption>(getLineOptions());
const dynamicLineChartOptions = useDynamicChart(lineOptions, {
placeholderColor: ['legend.textStyle.color', 'xAxis.axisLabel.color', 'yAxis.axisLabel.color'],
});
useEffect(() => {
const timer = setInterval(() => setLineOptions(getLineOptions()), 3000);
return () => {
clearInterval(timer);
};
}, []);
return (
<ReactEcharts
option={dynamicLineChartOptions} // option:图表配置项
notMerge={true}
lazyUpdate={true}
style={{ height: 265 }}
/>
);
};
export default React.memo(DynamicLineChart);
@@ -1,30 +0,0 @@
.popupBox {
max-height: 620px;
}
.popupItem {
padding: 12px 0;
display: flex;
height: 46px;
align-items: center;
box-sizing: border-box;
> h1 {
margin: 0;
width: 84px;
font-size: 14px;
color: var(--td-text-color-secondary);
text-align: left;
line-height: 22px;
}
> p {
margin: 0 0 0 24px;
}
}
.popupItem_blue {
color: var(--td-brand-color-8);
}
.popupItem_green {
color: var(--td-success-color-5);
}
@@ -1,44 +0,0 @@
import React, { useState } from 'react';
import { Dialog } from 'tdesign-react';
import classnames from 'classnames';
import { BASE_INFO_DATA } from '../constant';
import Style from './ManagementPopup.module.less';
interface IProps {
visible: boolean;
}
const ManagementPopup = ({ visible }: IProps): React.ReactElement => {
const [isShow, setVisible] = useState<boolean>(visible);
const handleConfirm = () => setVisible(!isShow);
return (
<Dialog
header='基本信息'
visible={isShow}
onClose={handleConfirm}
onConfirm={handleConfirm}
onCancel={handleConfirm}
>
<div>
<div className={Style.popupBox}>
{BASE_INFO_DATA.map((item, index) => (
<div key={index} className={Style.popupItem}>
<h1>{item.name}</h1>
<p
className={classnames({
[Style.popupItem_green]: item.type && item.type.value === 'green',
[Style.popupItem_blue]: item.type && item.type.value === 'blue',
})}
>
{item.value}
</p>
</div>
))}
</div>
</div>
</Dialog>
);
};
export default React.memo(ManagementPopup);
@@ -1,8 +0,0 @@
.panel {
margin-bottom: 16px;
}
.deployPanelLeft {
border-radius: 3px;
min-width: 230px;
width: 100%;
}
@@ -1,51 +0,0 @@
import React, { useState } from 'react';
import { Col, Radio, Row, Card } from 'tdesign-react';
import { EChartOption } from 'echarts';
import ReactEcharts from 'echarts-for-react';
import { getBarOptions } from '../chart';
import useDynamicChart from 'hooks/useDynamicChart';
import DynamicLineChart from './DynamicLineChart';
import Style from './TopChart.module.less';
const TopChart = () => {
const [barOptions, setBarOptions] = useState<EChartOption>(getBarOptions());
const tabChange = (isMonth: boolean) => {
setBarOptions(getBarOptions(isMonth));
};
const dynamicBarChartOptions = useDynamicChart(barOptions, {
placeholderColor: ['legend.textStyle.color', 'xAxis.0.axisLabel.color', 'yAxis.0.axisLabel.color'],
});
return (
<Row gutter={16} className={Style.panel}>
<Col span={6}>
<Card title='部署趋势' bordered={false}>
<div className={Style.deployPanelLeft}>
<DynamicLineChart />
</div>
</Card>
</Col>
<Col span={6}>
<Card
title='告警情况'
actions={
<Radio.Group defaultValue='week' onChange={(val) => tabChange(val === 'month')}>
<Radio.Button value='week'>本周</Radio.Button>
<Radio.Button value='month'>本月</Radio.Button>
</Radio.Group>
}
bordered={false}
>
<ReactEcharts
option={dynamicBarChartOptions} // option:图表配置项
notMerge={true}
lazyUpdate={true}
style={{ height: 255 }}
/>
</Card>
</Col>
</Row>
);
};
export default React.memo(TopChart);
-197
View File
@@ -1,197 +0,0 @@
import type { TdPrimaryTableProps, SorterFun, TableRowData } from 'tdesign-react/es/table';
export interface TableModel {
name: string;
adminName: string;
updateTime: string;
telephone: string | number;
op?: undefined | string | number;
}
const nameSorter: SorterFun<TableRowData> = (a, b) => {
const result = a.name.charCodeAt(0) - b.name.charCodeAt(0);
if (result > 0) return 1;
if (result < 0) return -1;
return 0;
};
export const TABLE_COLUMNS: TdPrimaryTableProps['columns'] = [
{
width: '448',
ellipsis: true,
colKey: 'name',
title: '项目名称',
sortType: 'all',
sorter: nameSorter,
},
{
width: '224',
ellipsis: true,
colKey: 'adminName',
title: '管理员',
},
{
width: '224',
ellipsis: true,
colKey: 'telephone',
title: '联系方式',
},
{
width: '224',
className: 'test',
ellipsis: true,
colKey: 'updateTime',
title: '创建时间',
sortType: 'all',
sorter: true,
},
{
align: 'left',
width: '200',
className: 'test2',
ellipsis: true,
colKey: 'op',
fixed: 'right',
title: '操作',
},
];
export const POPUP_DATA = {
formData: {
name: '',
warning: '',
success: '',
failB: '',
warningB: '',
loading: '',
add: '',
help: '',
},
options: [
{
label: '资源初始化后',
value: 'beijing',
},
{
label: '上海',
value: 'shanghai',
},
],
options1: [
{
label: '资源初始化后',
value: 'guangzhou',
},
{
label: '深圳',
value: 'shenzhen',
},
{
label: '东莞',
value: 'dongguang',
},
],
options2: [
{
label: '资源初始化后',
value: 'nanjing',
},
{
label: '苏州',
value: '苏州',
},
{
label: '无锡',
value: 'wuxi',
},
],
tSelectOptions: [
{
label: 'Sanzhang',
value: '1',
},
{
label: 'ls',
value: '2',
},
{
label: 'James',
value: '3',
},
],
};
export const BASE_INFO_DATA = [
{
name: '集群名',
value: 'helloworld',
},
{
name: '集群ID',
value: 'cls - 2ntelvxw',
type: {
key: 'color',
value: 'blue',
},
},
{
name: '状态',
value: '运行中',
type: {
key: 'color',
value: 'green',
},
},
{
name: 'K8S版本',
value: '1.7.8',
},
{
name: '配置',
value: '6.73 核 10.30 GB',
},
{
name: '所在地域',
value: '广州',
},
{
name: '新增资源所属项目',
value: '默认项目',
},
{
name: '节点数量',
value: '4 个',
},
{
name: '节点网络',
value: 'vpc - 5frmkm1x',
type: {
key: 'color',
value: 'blue',
},
},
{
name: '容器网络',
value: '172.16.0.0 / 16',
},
{
name: '集群凭证',
value: '显示凭证',
type: {
key: 'color',
value: 'blue',
},
},
{
name: '创建/更新',
value: '2018-05-31 22:11:44 2018-05-31 22:11:44',
type: {
key: 'contractAnnex',
value: 'pdf',
},
},
{
name: '描述',
value: 'istio_test',
},
];
-12
View File
@@ -1,12 +0,0 @@
import React, { memo } from 'react';
import TopChart from './components/TopChart';
import BottomTable from './components/BottomTable';
const Deploy = () => (
<div>
<TopChart />
<BottomTable />
</div>
);
export default memo(Deploy);
@@ -1,60 +0,0 @@
export type TStatus = 1 | 2; // 1: 未读 2:已读
export interface IItem {
id: number;
content: string;
tag: string;
createTime: string;
status: TStatus;
type: number;
}
export const dataItemList: IItem[] = [
{
id: 1,
content: '腾讯大厦一楼改造施工项目 已通过审核!',
tag: '合同动态',
createTime: '2021-01-01 08:00',
status: 1,
type: 1,
},
{
id: 2,
content: '三季度生产原材料采购项目 开票成功!',
tag: '票务动态',
createTime: '2021-01-01 08:00',
status: 2,
type: 2,
},
{
id: 3,
content: '2021-01-01 10:00的【国家电网线下签约】会议即将开始,请提前10分钟前往 会议室1 进行签到!',
tag: '会议通知',
createTime: '2021-01-01 08:00',
status: 1,
type: 3,
},
{
id: 4,
content: '一季度生产原材料采购项目 开票成功!',
tag: '票务动态',
createTime: '2021-01-01 08:00',
status: 1,
type: 2,
},
{
id: 5,
content: '二季度生产原材料采购项目 开票成功!',
tag: '票务动态',
createTime: '2021-01-01 08:00',
status: 1,
type: 2,
},
{
id: 6,
content: '三季度生产原材料采购项目 开票成功!',
tag: '票务动态',
createTime: '2021-01-01 08:00',
status: 1,
type: 2,
},
];
@@ -1,54 +0,0 @@
.secondaryNotification {
background-color: var(--td-bg-color-container);
border-radius: 3px;
padding: 24px 32px;
.msgList {
height: 70vh;
.listItem {
cursor: pointer;
padding: 13px 24px 13px 0;
&:hover {
background-color: var(--td-bg-color-container-hover);
}
&:hover .createTime {
display: none;
}
.action {
display: none;
}
&:hover .action {
display: block;
}
&:after {
content: "";
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 1px;
background: var(--td-border-level-1-color);
}
}
.content {
margin: 0;
font-size: 14px;
color: var(--td-text-color-placeholder);
text-align: left;
line-height: 22px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tag {
margin-right: 8px;
}
.noData {
text-align: center;
height: 28px;
line-height: 28px;
}
.unread {
color: var(--td-brand-color-8);
}
}
}
-113
View File
@@ -1,113 +0,0 @@
import React, { memo, useState } from 'react';
import { Tabs, List, Tag, Row, Col, Popup } from 'tdesign-react';
import { AddRectangleIcon, DeleteIcon, ChatIcon } from 'tdesign-icons-react';
import classnames from 'classnames';
import { dataItemList, IItem, TStatus } from './consts';
import Style from './index.module.less';
const { TabPanel } = Tabs;
const { ListItem } = List;
const typeMap: {
[key: number]: 'default' | 'primary' | 'warning' | 'danger' | 'success';
} = {
1: 'danger',
2: 'primary',
3: 'warning',
};
interface IMsgListProps {
list: IItem[];
onDelete: Function;
onUpdate: Function;
}
const MsgList = memo((props: IMsgListProps) => {
const { list, onUpdate, onDelete } = props;
return (
<List className={Style.msgList}>
{list?.map((item) => (
<ListItem
className={Style.listItem}
key={item.id}
action={
<li>
<div className={Style.createTime}>{item.createTime}</div>
<div className={Style.action}>
<Row gutter={8}>
<Col>
{item.status === 1 && (
<Popup trigger='hover' showArrow content='设为已读'>
<AddRectangleIcon onClick={() => onUpdate(item, 2)} />
</Popup>
)}
{item.status === 2 && (
<Popup trigger='hover' showArrow content='设为未读'>
<ChatIcon onClick={() => onUpdate(item, 1)} />
</Popup>
)}
</Col>
<Col>
<Popup trigger='hover' showArrow content='删除通知'>
<DeleteIcon onClick={() => onDelete(item)} />
</Popup>
</Col>
</Row>
</div>
</li>
}
>
<div
className={classnames(Style.content, {
[Style.unread]: item.status === 1,
})}
>
<Tag variant='light' theme={typeMap[item.type]} className={Style.tag}>
{item.tag}
</Tag>
{item.content}
</div>
</ListItem>
))}
{list.length === 0 && <div className={Style.noData}>暂无数据</div>}
</List>
);
});
export default memo(() => {
const [list, setList] = useState(dataItemList);
const deleteItem = (item: IItem) => {
setList((value) => value.filter((val) => val.id !== item.id));
};
const updateStatus = (item: IItem, status: TStatus) => {
setList((value) => {
value.forEach((val) => {
if (val.id === item.id) {
val.status = status;
}
return val;
});
return [...value];
});
};
return (
<div>
<section className={Style.secondaryNotification}>
<Tabs placement='top' size='medium' defaultValue='1'>
<TabPanel value='1' label='全部通知'>
<MsgList list={list} onDelete={deleteItem} onUpdate={updateStatus} />
</TabPanel>
<TabPanel value='2' label='未读通知'>
<MsgList list={list.filter((item) => item.status === 1)} onDelete={deleteItem} onUpdate={updateStatus} />
</TabPanel>
<TabPanel value='3' label='已读通知'>
<MsgList list={list.filter((item) => item.status === 2)} onDelete={deleteItem} onUpdate={updateStatus} />
</TabPanel>
</Tabs>
</section>
</div>
);
});
@@ -1,23 +0,0 @@
.formContainer {
width: 676px;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--td-bg-color-container);
padding: 30px 0 134px;
}
.titleText {
font-size: 20px;
margin: 64px 0 32px;
color: var(--td-text-color-primary);
}
.dateCol {
:global {
.t-form__controls-content {
display: inline;
}
}
}
-194
View File
@@ -1,194 +0,0 @@
import React, { memo, useRef } from 'react';
import {
Form,
Row,
Col,
Input,
Radio,
Button,
DatePicker,
Select,
Textarea,
Avatar,
Upload,
MessagePlugin,
} from 'tdesign-react';
import classnames from 'classnames';
import { SubmitContext, FormInstanceFunctions } from 'tdesign-react/es/form/type';
import CommonStyle from 'styles/common.module.less';
import Style from './index.module.less';
const { FormItem } = Form;
const { Option } = Select;
const { Group } = Avatar;
const INITIAL_DATA = {
name: '',
type: '',
payment: '',
partyA: '',
partyB: '',
signDate: '',
effectiveDate: '',
endDate: '',
remark: '',
notary: '',
file: [],
};
export default memo(() => {
const formRef = useRef<FormInstanceFunctions>();
const onSubmit = (e: SubmitContext) => {
if (e.validateResult === true) {
console.log('form 值', formRef.current?.getFieldsValue?.(true));
MessagePlugin.info('提交成功');
}
};
const handleFail = ({ file }: { file: any }) => {
console.error(`文件 ${file.name} 上传失败`);
};
return (
<div className={classnames(CommonStyle.pageWithColor)}>
<div className={Style.formContainer}>
<Form ref={formRef} onSubmit={onSubmit} labelWidth={100} labelAlign='top'>
<div className={Style.titleBox}>
<div className={Style.titleText}>合同信息</div>
</div>
<Row gutter={[32, 24]}>
<Col span={6}>
<FormItem
label='合同名称'
name='name'
initialData={INITIAL_DATA.name}
rules={[{ required: true, message: '合同名称必填', type: 'error' }]}
>
<Input placeholder='请输入内容' />
</FormItem>
</Col>
<Col span={6}>
<FormItem
label='合同类型'
name='type'
initialData={INITIAL_DATA.type}
rules={[{ required: true, message: '合同类型必填', type: 'error' }]}
>
<Select placeholder='请选择类型'>
<Option key='A' label='类型A' value='A' />
<Option key='B' label='类型B' value='B' />
<Option key='C' label='类型C' value='C' />
</Select>
</FormItem>
</Col>
<Col span={12}>
<FormItem
label='合同收付类型'
name='payment'
initialData={INITIAL_DATA.payment}
rules={[{ required: true }]}
>
<Radio.Group>
<Radio value='0'>收款</Radio>
<Radio value='1'>付款</Radio>
</Radio.Group>
<Input placeholder='请输入金额' style={{ width: 160 }} />
</FormItem>
</Col>
<Col span={6}>
<FormItem label='甲方' name='partyA' initialData={INITIAL_DATA.partyA} rules={[{ required: true }]}>
<Select placeholder='请选择类型'>
<Option key='A' label='公司A' value='A' />
<Option key='B' label='公司B' value='B' />
<Option key='C' label='公司C' value='C' />
</Select>
</FormItem>
</Col>
<Col span={6}>
<FormItem label='乙方' name='partyB' initialData={INITIAL_DATA.partyB} rules={[{ required: true }]}>
<Select value='A' placeholder='请选择类型'>
<Option key='A' label='公司A' value='A' />
<Option key='B' label='公司B' value='B' />
<Option key='C' label='公司C' value='C' />
</Select>
</FormItem>
</Col>
<Col span={6} className={Style.dateCol}>
<FormItem
label='合同签订日期'
name='signDate'
initialData={INITIAL_DATA.signDate}
rules={[{ required: true }]}
>
<DatePicker mode='date' />
</FormItem>
</Col>
<Col span={6} className={Style.dateCol}>
<FormItem
label='合同生效日期'
name='effectiveDate'
initialData={INITIAL_DATA.effectiveDate}
rules={[{ required: true }]}
>
<DatePicker mode='date' />
</FormItem>
</Col>
<Col span={6} className={Style.dateCol}>
<FormItem
label='合同结束日期'
name='endDate'
initialData={INITIAL_DATA.endDate}
rules={[{ required: true }]}
>
<DatePicker mode='date' />
</FormItem>
</Col>
<Col span={6}>
<FormItem label='合同文件' name='file' initialData={INITIAL_DATA.file}>
<Upload
onFail={handleFail}
tips='请上传pdf文件,大小在60M以内'
action='//service-bv448zsw-1257786608.gz.apigw.tencentcs.com/api/upload-demo'
/>
</FormItem>
</Col>
</Row>
<div className={Style.titleBox}>
<div className={Style.titleText}>其他信息</div>
</div>
<FormItem label='备注' name='remark' initialData={INITIAL_DATA.remark}>
<Textarea placeholder='请输入备注' />
</FormItem>
<FormItem label='公证人' name='notary' initialData={INITIAL_DATA.notary}>
<Group>
<Avatar>D</Avatar>
<Avatar>S</Avatar>
<Avatar>+</Avatar>
</Group>
</FormItem>
<FormItem>
<Button type='submit' theme='primary'>
提交
</Button>
<Button type='reset' style={{ marginLeft: 12 }}>
重置
</Button>
</FormItem>
</Form>
</div>
</div>
);
});
@@ -1,44 +0,0 @@
import React, { memo } from 'react';
import { useNavigate } from 'react-router-dom';
import { CheckCircleFilledIcon } from 'tdesign-icons-react';
import { Button } from 'tdesign-react';
import Style from './index.module.less';
export default memo((props: { callback: Function }) => {
const navigate = useNavigate();
const { callback } = props;
const onClickAgain = () => {
callback('first');
};
const onCheck = () => {
const url = '/detail/advanced';
navigate(url);
};
return (
<div className={Style.stepFourWrapper}>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
}}
>
<CheckCircleFilledIcon className={Style.icon} />
<div className={Style.title}>完成开票申请</div>
<p>预计1~3个工作日会将电子发票发至邮箱,发票邮寄请耐心等待</p>
<div className='tdesign-demo-block-column' style={{ marginTop: '20px' }}>
<Button theme='primary' variant='base' onClick={onClickAgain}>
再次申请
</Button>
<Button theme='default' variant='base' style={{ marginLeft: '15px' }} onClick={onCheck}>
查看进度
</Button>
</div>
</div>
</div>
);
});

Some files were not shown because too many files have changed in this diff Show More